@intentius/behold 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +246 -7
- package/demos.json +1 -1
- package/dist/cli.js +10118 -1770
- package/example-choudoufu-estate/chant.workspace.json +1 -1
- package/example-terraform-estate/README.md +2 -2
- package/example-terraform-estate/chant.workspace.json +1 -1
- package/package.json +6 -5
- package/web/app.js +303 -17
- package/web/dangling.js +29 -0
- package/web/dangling.test.js +34 -0
- package/web/edge-kind.js +93 -0
- package/web/edge-kind.test.js +70 -0
- package/web/index.html +50 -0
- package/web/releases.js +121 -0
- package/web/sql-yodeler-lane.js +129 -0
- package/web/terragucci-lifecycle.js +103 -11
- package/web/terragucci-lifecycle.test.js +36 -1
- package/web/terragucci-timeline.js +77 -0
- package/web/workspace-gates.js +29 -0
- package/web/yodel-report.js +99 -0
- package/web/yodel-report.test.js +18 -0
package/AGENTS.md
CHANGED
|
@@ -9,7 +9,8 @@ apply creds.
|
|
|
9
9
|
|
|
10
10
|
- **behold** serves the live, mixed-substrate graph (and, later, the deployment-lanes
|
|
11
11
|
timeline). It reads; every write it offers is a command it starts (an Op, a
|
|
12
|
-
`chant run`, a forge dispatch), and on a terragucci repo it
|
|
12
|
+
`chant run`, a forge dispatch), and on a terragucci or SQL Yodeler repo it
|
|
13
|
+
starts none.
|
|
13
14
|
- **chant's MCP** is where the real capabilities live. Prefer it over shelling.
|
|
14
15
|
- Reads: `lifecycle-diff`, `lifecycle-snapshot`, `build`, `lint`.
|
|
15
16
|
- Actions (delegated writes): `op-run` (start an `ApplyOp`/`ReconcileOp`),
|
|
@@ -88,6 +89,12 @@ and none is the checkout's git remote) adds two reads, in
|
|
|
88
89
|
report of its wave's other entries (same gate and digest). No
|
|
89
90
|
`audit.jsonl` is `present: false` plus an `absent` sentence, never an
|
|
90
91
|
empty history; a bad line is the `422 terragucci-report` refusal naming it.
|
|
92
|
+
#523: an S3 403 on an optional read (`audit.jsonl` here, `run.json` for
|
|
93
|
+
`/api/terragucci/progress` and the estate's run views) is not a refusal:
|
|
94
|
+
the source throws `TerragucciDenied` (`src/terragucci-source.ts`), and the
|
|
95
|
+
answer carries `present: false`, `unreadable: true` and an `absent`
|
|
96
|
+
sentence naming the key and `s3:GetObject` (or `s3:ListBucket`). A 403 on
|
|
97
|
+
`index.json`, a report or `estate.json` still fails.
|
|
91
98
|
`src/terragucci-timeline.ts` holds the read and the check (terragucci's
|
|
92
99
|
`dist/audit.schema.json` when it resolves, structural otherwise). The SPA
|
|
93
100
|
draws it in the Terragucci tab and, filtered to the card's root, in the
|
|
@@ -95,11 +102,79 @@ and none is the checkout's git remote) adds two reads, in
|
|
|
95
102
|
snapshot and does not fail on it.
|
|
96
103
|
- `GET /api/terragucci/lifecycle` (#505): the repo's `chant/lifecycle` lane,
|
|
97
104
|
`{branch, found, via, origin?, fetch: {ok, error?}, commit?: {sha,
|
|
98
|
-
committed}, read: {at}, gates[], locks[], note}`. `gates[]` is one
|
|
99
|
-
`wave-<k
|
|
100
|
-
|
|
101
|
-
`
|
|
102
|
-
|
|
105
|
+
committed}, read: {at}, approval?, gates[], locks[], note}`. `gates[]` is one
|
|
106
|
+
entry per wave gate (`wave-<k>`, chant op-waves' `<gate>-wave-<k>` or a
|
|
107
|
+
product's `<name>-wave-<k>`: `WAVE_GATE`, #521) in `_gates/tf-apply.jsonl` and
|
|
108
|
+
`_gates/tf-apply/applied.jsonl`, decided by chant's `decideGateApproval`
|
|
109
|
+
(`@intentius/chant/gate-decision`, #520) with spent approvals left out, on
|
|
110
|
+
lines parsed by chant's `parseGateLedger` and `parseAppliedGateLines`. The
|
|
111
|
+
states:
|
|
112
|
+
- `waiting`: no approval of the wave's digest counts; `command` is the
|
|
113
|
+
approve line (`--sign` under `sealed`), or `chant workspace points answer
|
|
114
|
+
<id>` when the pending fact carries `answer: {point, id}` (#521).
|
|
115
|
+
- `expired`: the pending fact's `expiresAt` passed with none.
|
|
116
|
+
- `refused`: an unspent approval stands for another digest
|
|
117
|
+
(`refused: {digest, by, at}`); no approve line.
|
|
118
|
+
- `approved`: an unspent approval of the digest counts, not applied yet.
|
|
119
|
+
- `applied` / `failed`: a wave applied under that approval, and how the
|
|
120
|
+
apply finished when terragucci recorded it. chant's op waves write no
|
|
121
|
+
`finished` line: then `applied.result` is absent and the page says
|
|
122
|
+
"applied (outcome not recorded)".
|
|
123
|
+
- `refused-by-policy` (#521): a denial on `_gates/policy-override.jsonl`
|
|
124
|
+
(chant's `recordedDenials`) newer than anything on the wave's gate, that
|
|
125
|
+
no override counts for (chant's `decideOverride`, with the overriders at
|
|
126
|
+
base: `.chant/overriders`, or terragucci's `policy.override`) and no run
|
|
127
|
+
applied an override of since. `denials[]` carries each run, its plan and
|
|
128
|
+
rules, and chant's `overrideCommand` line; no approve line. The wave is
|
|
129
|
+
the one the denial's pending fact names (`wave: {number, name, op}`,
|
|
130
|
+
typed on chant's `PendingGateRecord`, whose parser drops a wave of the
|
|
131
|
+
wrong shape, chant#3887), else, for a fact an older chant wrote, the one
|
|
132
|
+
whose pending fact lists the run.
|
|
133
|
+
- `held` (#521): a pending fact on another gate (any `_gates/<op>.jsonl`)
|
|
134
|
+
that names this op's wave (`wave: {number, name, op}`), with no approval
|
|
135
|
+
of its digest that counts under the mode the fact records. `heldOn: {op,
|
|
136
|
+
gate, digest, since, heldBy, description}`; `heldBy: {hook, moment,
|
|
137
|
+
target?}` is the hook that holds the wave (chant#3886), and the card
|
|
138
|
+
(`holderWords` in `web/terragucci-lifecycle.js`) names it from there,
|
|
139
|
+
or from `description` for a fact an older chant wrote. `command`
|
|
140
|
+
approves that gate (`chant approve <op> <gate> --plan`) or answers its
|
|
141
|
+
decision point. A hold fact that names no wave is not read as a hold.
|
|
142
|
+
Each entry also carries `digest`, `pending` (with `members` and
|
|
143
|
+
`neverOverMcp` when recorded, `description`, `url`, `answer`), `approval`
|
|
144
|
+
(by, at, `signed` when the seal has a signature, via (`review` for chant's
|
|
145
|
+
`review: {pr, head, approvers}` line), pr, head, reviewers, relayedBy, note), `uncounted` (an
|
|
146
|
+
approval of this digest the mode did not count, with chant's reason, such
|
|
147
|
+
as an unsealed one under `sealed`), `spent` (an approval of another digest
|
|
148
|
+
an apply used), `applied` (with `wave` and `review` when present), the kept
|
|
149
|
+
reports under `_gates/tf-apply/wave-<k>/`, and `command`. `approval` on the
|
|
150
|
+
answer is the rule read at base, the tip of origin's default branch fetched
|
|
151
|
+
depth 1 into the same cache (`refs/behold/base`): `mode` from
|
|
152
|
+
terragucci.yml's `approval:` key, else `sealed` when chant.workspace.json
|
|
153
|
+
lists `identity.gates`, else `ledger`, and the signers read with chant's
|
|
154
|
+
`gateSignersAt`. Each wave is decided under the approval mode its newest
|
|
155
|
+
pending fact records (`approvalMode`, `withRecordedModes`), else the mode at
|
|
156
|
+
base; the entry carries `approvalMode: {mode, from: "pending" | "base"}`,
|
|
157
|
+
and `approval.inferred` is set only when some wave's fact records none. A
|
|
158
|
+
gate `identity.gates` names at base is decided with `refuses` from chant's
|
|
159
|
+
`identityGateAdmissionAt`, which checks the seal against the signers at
|
|
160
|
+
base and, when the gate names a class, the role grants in
|
|
161
|
+
`.chant/trust.json` at base, whatever the mode; the entry carries
|
|
162
|
+
`identity: {class}`. A class a pinned package supplies is read by chant
|
|
163
|
+
from `./workspace-principals` (chant#3888): a path pin's file at base, a
|
|
164
|
+
package pin's from the package installed in the served checkout's
|
|
165
|
+
node_modules (behold passes the checkout as `packages`, since the bare
|
|
166
|
+
cache has none), at the pinned version. When that package is missing or
|
|
167
|
+
at another version, a gate naming a domain class or `human` refuses every
|
|
168
|
+
approval and chant's reason names the pin. Under `pr-review`, a spec's `headPlans` (the op-waves spec name)
|
|
169
|
+
has the newest 50 `_wave-plans/<name>/<head>.json` records read with
|
|
170
|
+
chant's `readHeadPlans` (`@intentius/chant/lifecycle/wave-plans`) through
|
|
171
|
+
`ledgerPort`, a `LedgerReadPort` over the fetched commit. The op, ledger paths, gate names, approve line and
|
|
172
|
+
mode reader are a `GateLaneSpec` (`TERRAGUCCI_LANE` is terragucci's), for
|
|
173
|
+
another product's lane: `opWavesLane(op, over?)` gives one with chant's
|
|
174
|
+
approve line, mode rule and overriders, and `readLifecycle(checkout, {spec})`
|
|
175
|
+
reads it. `terragucciApproveCommand` is the one approve line the reports,
|
|
176
|
+
the estate and the lane share, and `gatePolicyName` reads `on-destroy` as
|
|
177
|
+
`on-destructive` (terragucci#863). `locks[]` is
|
|
103
178
|
`_locks/tf-apply.json`, one per root (`pr, by, at, head, via?, stage?`).
|
|
104
179
|
Every gate and lock carries `source: {commit, committed, path}`, the newest
|
|
105
180
|
commit of the branch that changed its file. The branch is fetched from the
|
|
@@ -111,6 +186,27 @@ and none is the checkout's git remote) adds two reads, in
|
|
|
111
186
|
`fetch.error`); a remote without the branch is `found: false, via:
|
|
112
187
|
"fetch"`, not an error. 30 s cache, `?fresh=1` skips it. Reader:
|
|
113
188
|
`src/terragucci-lifecycle.ts`.
|
|
189
|
+
- `GET /api/lifecycle/history?op=<op>&gate=<gate>&limit=<n>` (#522): the
|
|
190
|
+
gate ledgers' history on `chant/lifecycle`, for any product, registered
|
|
191
|
+
whether or not `--terragucci` is given. Same bare cache and fallback as
|
|
192
|
+
the lifecycle lane; per `_gates/<op>.jsonl` it runs `git log -p
|
|
193
|
+
--unified=0 --no-renames` and hands the text to chant's
|
|
194
|
+
`gateLedgerLinesFromLog`, then all ledgers to `deriveGateAudit`
|
|
195
|
+
(`@intentius/chant/lifecycle/gate-audit`). behold parses no ledger line
|
|
196
|
+
itself. Answer: `{branch, source: "ledger", found, via, origin?, fetch,
|
|
197
|
+
commit?, read, ledgers[], op?, gate?, total, limit, entries[], gaps[],
|
|
198
|
+
note}`, newest first, 200 by default and 1000 at most. An entry is `{id,
|
|
199
|
+
kind, at, op, gate, wave?, digest?, actor?, seal?, note?, rules?, subject?,
|
|
200
|
+
source: {path, line?, commit, committer, committed, change}, audit}`;
|
|
201
|
+
`kind` is chant's (`approval-requested | approved | override-requested |
|
|
202
|
+
override-recorded | revoked`), `audit` is chant's whole entry. A revocation
|
|
203
|
+
is a line a commit removed, and its `actor` is that commit's author. The
|
|
204
|
+
ledger holds no applies, so `applied` and `override-spent` appear only in
|
|
205
|
+
a product's own record (terragucci's `audit.jsonl`). The Terragucci tab's
|
|
206
|
+
Timeline draws it under the audit record, labelled "ledger"; when the
|
|
207
|
+
source has no `audit.jsonl` the record's lane says so and the ledger is
|
|
208
|
+
the history. An export carries it as one snapshot. Reader:
|
|
209
|
+
`src/lifecycle-history.ts`.
|
|
114
210
|
- `GET /api/terragucci/events` (#505): SSE. While at least one page holds it
|
|
115
211
|
open, a timer (`--terragucci-poll <secs>`, default 30, 0 off) asks
|
|
116
212
|
whether `index.json` changed (`If-None-Match` with the last ETag for S3 and
|
|
@@ -173,6 +269,112 @@ checkout declares no Terraform root (`controlRepo` in
|
|
|
173
269
|
structural check otherwise. The page (`web/terragucci-estate.js`) draws it
|
|
174
270
|
in a layer over the graph area.
|
|
175
271
|
|
|
272
|
+
## Releases per environment, from the release ledger (#525)
|
|
273
|
+
|
|
274
|
+
`GET /api/lifecycle/releases[?fresh=1]` (src/lifecycle-releases.ts) reads
|
|
275
|
+
`<env>/releases.jsonl` on the served checkout's `chant/lifecycle` with chant's
|
|
276
|
+
own reader (`listReleaseEnvironments`, `readReleaseLedger`,
|
|
277
|
+
`latestPerComponent`, `recordsForDigest`). The branch is fetched into the
|
|
278
|
+
same bare cache the terragucci lane uses (`fetchIntoCache` in
|
|
279
|
+
src/terragucci-lifecycle.ts). When that fetch fails, the checkout's
|
|
280
|
+
`refs/remotes/origin/chant/lifecycle` is fetched into a second cache of
|
|
281
|
+
behold's and read there (`via: "checkout-ref"`); the checkout is never
|
|
282
|
+
written. Reads are reused for 30s.
|
|
283
|
+
|
|
284
|
+
The answer: `environments` (wave order from `yodel-waves.json` when present,
|
|
285
|
+
`order: "waves"`, else by name), `components` (a tenant is
|
|
286
|
+
`<component>/<tenant>`, as SQL Yodeler records it), and `cells[]`, one per
|
|
287
|
+
component and environment that has a release: `digest`, `timestamp`,
|
|
288
|
+
`actor`, `approver?`, `gitSha`, `runId`, `runUrl?`, `source` (the newest
|
|
289
|
+
commit that changed that environment's ledger), and `differs: {from,
|
|
290
|
+
digest?, everHad}` when the environment before it holds another digest or
|
|
291
|
+
none. `/api/project` says `releaseLedger: true` for a checkout with a
|
|
292
|
+
`yodel.config.ts` or `yodel-waves.json`; the page then opens a Releases tab
|
|
293
|
+
(`web/releases.js`), and `behold export` captures the route as a snapshot.
|
|
294
|
+
|
|
295
|
+
## SQL Yodeler's report and drift, from files (#526)
|
|
296
|
+
|
|
297
|
+
`behold serve <dir> --yodel-report <file|dir>` adds `GET /api/yodel-report`
|
|
298
|
+
(`src/yodel-report.ts`). It reads one `yodel report --json` output and any
|
|
299
|
+
`yodel drift <env> --json` output in the same directory, written by the
|
|
300
|
+
person or their pipeline. behold never runs `yodel` and never connects to
|
|
301
|
+
the database.
|
|
302
|
+
|
|
303
|
+
- The answer is `{source, validation, report, environments[], drift[],
|
|
304
|
+
marks[], ignored[]}`. Each environment carries its applied migrations, the
|
|
305
|
+
rest with their state, its gaps and notes, dated by the report's
|
|
306
|
+
`generatedAt`. Each mark is one object a drift file names (`changed` or
|
|
307
|
+
`missing`), dated by the file's mtime (drift JSON has no time of its own)
|
|
308
|
+
and named by its file.
|
|
309
|
+
- Every file is checked against SQL Yodeler's schemas: from
|
|
310
|
+
`@intentius/sql-yodeler/schemas/` when the package resolves beside the
|
|
311
|
+
served project or behold, otherwise from the copy pinned in
|
|
312
|
+
`src/sql-yodeler-schemas/` (sql-yodeler 19ebbff). A file that fails is a
|
|
313
|
+
422 with `code: "yodel-report"` naming the file, and nothing is drawn.
|
|
314
|
+
Update the pinned copy by copying the three files from sql-yodeler's
|
|
315
|
+
`schemas/` and changing `PINNED_COMMIT`.
|
|
316
|
+
- The page (`web/yodel-report.js`) joins a mark to the card whose id is the
|
|
317
|
+
object's name, or ends in `/<name>` in a composed estate, and draws a
|
|
318
|
+
corner tag with its age; never a fill. Objects no card is are listed in
|
|
319
|
+
the SQL Yodeler tab, which also holds the environments and gaps.
|
|
320
|
+
- `audit[]` (#537) carries the report's audit entries with the report file
|
|
321
|
+
they came from. The tab lists them under their environment, each dated by
|
|
322
|
+
its own `at` and named by its source (a ledger's op and gate, or a history
|
|
323
|
+
table's row and run).
|
|
324
|
+
- `behold export --yodel-report <file|dir>` (#537) captures `/api/yodel-report`
|
|
325
|
+
as a snapshot, and a refusal fails the export, as `--terragucci` does. A
|
|
326
|
+
SQL Yodeler repo's export also captures `/api/sql-yodeler/lifecycle` and
|
|
327
|
+
`/api/lifecycle/history`, so the Pipeline lane draws in static mode,
|
|
328
|
+
without its Read again button.
|
|
329
|
+
|
|
330
|
+
## A SQL Yodeler project: kinds and a lifecycle lane (#524)
|
|
331
|
+
|
|
332
|
+
A SQL Yodeler project is a chant project with `lexicons: ["sql"]` and a
|
|
333
|
+
`yodel.config.ts` (#517's detection, `productRepo` in src/terragucci-repo.ts).
|
|
334
|
+
Its graph draws from `chant graph` as any project's does. src/sql-kinds.ts
|
|
335
|
+
registers a pinhole pack for the `sql` lexicon: a line glyph per
|
|
336
|
+
`Postgres::*` and `ClickHouse::*` kind, and cards that lead with the kind in
|
|
337
|
+
words and the qualified name. A `REFERENCES ${other}` foreign key is a `ref`
|
|
338
|
+
edge; e2e/fixtures/sql-yodeler-fk holds a project with one, and
|
|
339
|
+
src/sql-yodeler-lane.test.ts graphs it. `@intentius/chant-lexicon-sql` is a
|
|
340
|
+
devDependency for that test; from 0.124.13 its lint rules use chant's own
|
|
341
|
+
TypeScript (chant#3864), so they run beside behold's TypeScript 7.
|
|
342
|
+
|
|
343
|
+
`GET /api/sql-yodeler/lifecycle[?fresh=1]` (src/sql-yodeler-lane.ts) reads
|
|
344
|
+
`chant/lifecycle` with `readLifecycle` and a spec per op, sharing one fetch:
|
|
345
|
+
|
|
346
|
+
- `opWavesLane("yodel-apply")` for the apply pipeline's
|
|
347
|
+
`yodel-apply-wave-<k>` gates, with the mode at base from `yodel-waves.json`
|
|
348
|
+
when every wave names the same `approval`. When they differ (#537) the
|
|
349
|
+
rule carries `waves[]` and each wave is decided under its own mode
|
|
350
|
+
(`waveRule`; a gate decided so carries `mode`). Under `pr-review` a gate not
|
|
351
|
+
yet approved carries `headPlans[]`: the heads whose
|
|
352
|
+
`_wave-plans/yodel-apply/<head>.json` (chant's HeadPlans, newest 50 read)
|
|
353
|
+
name its digest.
|
|
354
|
+
- `migrateOpLane(env)` for each `migrate-<env>` op that `ops/` or
|
|
355
|
+
`yodel-waves.json` names: its one gate `approve-migrate-<env>`, drawn as
|
|
356
|
+
wave 1 of that op.
|
|
357
|
+
|
|
358
|
+
The answer is the lane's header (branch, via, commit, rule, note) with
|
|
359
|
+
`environments[]` in the order of applies (`yodel-waves.json`, then any
|
|
360
|
+
environment only `ops/` names), each with its wave (`k`, targets, gate
|
|
361
|
+
policy, approval) and `gates[]` (`kind: "wave" | "migrate"`, the GateEntry
|
|
362
|
+
of #520/#521). A waiting gate carries `npx yodel approve <env> --plan
|
|
363
|
+
<digest>` (`--sign` under sealed). Waves the spec does not name go in
|
|
364
|
+
`unplaced`. A served directory that is not SQL Yodeler gets 404 with
|
|
365
|
+
`code: "sql-yodeler"`. The page's Pipeline tab (`web/sql-yodeler-lane.js`)
|
|
366
|
+
draws it, with `/api/lifecycle/history` (#522) below. Reads are reused 30s.
|
|
367
|
+
|
|
368
|
+
A SQL Yodeler project that is a chant workspace member (#537) keeps its
|
|
369
|
+
records under `_members/<member>/` on `chant/lifecycle`. src/member-ledger.ts
|
|
370
|
+
asks chant's `resolveMemberLedger` (`@intentius/chant/lifecycle/member-ledger`)
|
|
371
|
+
for the prefix; the lane passes it as `GateLaneSpec.ledgerPrefix`, which
|
|
372
|
+
`readLifecycle` puts before every branch path, and `/api/lifecycle/releases`
|
|
373
|
+
hands it to chant's `listReleaseEnvironments` and `readReleaseLedger` as
|
|
374
|
+
`ledgerPrefix` (chant#3865), which list and read the environments under it. Both answers then carry
|
|
375
|
+
`member: { members, prefix, error? }`, and `yodel-waves.json` at base is read
|
|
376
|
+
in the project's own directory.
|
|
377
|
+
|
|
176
378
|
## Getting a server
|
|
177
379
|
|
|
178
380
|
```sh
|
|
@@ -726,8 +928,42 @@ entry is a registered route. `TERRAGUCCI_REFUSED_WRITES` only words the
|
|
|
726
928
|
refusal for known routes. The remedy names `npx terragucci approve wave-<k>
|
|
727
929
|
--plan <digest>`, the digest from the wave's gate card.
|
|
728
930
|
|
|
931
|
+
A SQL Yodeler repo (a `yodel.config.ts` in the served directory or above it,
|
|
932
|
+
up to the git root) is guarded the same way (#517): the same allowlist, the
|
|
933
|
+
same refused routes, auto-sync declined, and `/api/project` carries
|
|
934
|
+
`sqlYodeler` so the page offers no write. The refusal is `409` with `code:
|
|
935
|
+
"sql-yodeler"`, and its remedy names `npx yodel approve <env> --plan <digest>`
|
|
936
|
+
(with `--sign` when the environment's gate is sealed) and `yodel apply <env>`
|
|
937
|
+
in the project's pipeline. `productRepo()` in `src/terragucci-repo.ts` looks
|
|
938
|
+
for terragucci's config along the whole walk first, so a yodel project nested
|
|
939
|
+
inside a terragucci repo is refused as terragucci.
|
|
940
|
+
|
|
941
|
+
Op discovery (`src/ops.ts`) reads a gate from `signalName: "<name>"` or from
|
|
942
|
+
`gate("<name>", ...)`, the form chant's op builder and SQL Yodeler write, so
|
|
943
|
+
a gated Op is never listed or run as ungated on any repo (#517).
|
|
944
|
+
|
|
945
|
+
A policy denial is never approved from behold (#519, `src/policy-override.ts`).
|
|
946
|
+
`GET /api/workspace/gates` gives a gate of op `policy-override` an `override`
|
|
947
|
+
field `{subject, rules, plan, digest, state, spentAt, product, command,
|
|
948
|
+
source}`: the rules and denied plan come from `chant/lifecycle:_gates/
|
|
949
|
+
policy-override.jsonl` (read with `git show`, the local branch then
|
|
950
|
+
`origin/chant/lifecycle`), `state` is `waiting`, `overridden` (status says an
|
|
951
|
+
override counts) or `spent` (a line in `_gates/policy-override/applied.jsonl`
|
|
952
|
+
for its digest), and `command` is the product's (`npx terragucci override`,
|
|
953
|
+
`npx yodel override`) or chant's `chant approve policy-override <subject>
|
|
954
|
+
--rule <id> --reason "<why>" --plan <digest>`. The card shows that command and
|
|
955
|
+
no button, and `POST /api/workspace/gates/approve` answers such a gate `422`
|
|
956
|
+
with `code: "override"`. The ledger lines are parsed by chant's
|
|
957
|
+
`@intentius/chant/override-gate` (`recordedDenials`, `parseAppliedOverrides`,
|
|
958
|
+
`overrideCommand`) and `parseGateLedger`, and the product is `productRepo()`
|
|
959
|
+
from `src/terragucci-repo.ts` (#537).
|
|
960
|
+
|
|
729
961
|
### The exceptions, and their exact size
|
|
730
962
|
|
|
963
|
+
These are also the only writes a guarded product repo answers: terragucci
|
|
964
|
+
(`terragucci.yml` and its siblings) and SQL Yodeler (`yodel.config.ts`), both
|
|
965
|
+
listed in `TERRAGUCCI_ALLOWED_WRITES`.
|
|
966
|
+
|
|
731
967
|
`POST /api/layout` (#228) writes **one** file in the served project:
|
|
732
968
|
`.behold/layout.json`, the hand-layout sidecar, `{version, lenses: {<lens>:
|
|
733
969
|
{<node id>: {dx,dy,dw,dh}}}}`. That is the whole of behold's write surface
|
|
@@ -1158,7 +1394,10 @@ water park's `access/` before it was taken:
|
|
|
1158
1394
|
The scratch project is `<tmpdir>/behold-tf-<hash of the estate path>`:
|
|
1159
1395
|
`behold-*` and cleared through `assertScratch` (src/scratch.ts), one directory
|
|
1160
1396
|
per estate reused across runs, asserted to be outside the estate before a byte
|
|
1161
|
-
is written. It holds the generated `chant.config.ts
|
|
1397
|
+
is written. It holds the generated `chant.config.ts`, a `package.json` that
|
|
1398
|
+
declares it an ES module (chant's compiled CLI, 0.124.14 on, otherwise reads a
|
|
1399
|
+
config tsx compiled as CommonJS as `{default: config}` and finds no lexicon:
|
|
1400
|
+
INTENTIUS/chant#3897), and two symlinks —
|
|
1162
1401
|
`node_modules` to behold's own, which is how the config resolves the lexicon,
|
|
1163
1402
|
and `estate` to the served directory, which is how each root's `dir` is spelled.
|
|
1164
1403
|
The second is not decoration: the lexicon sets a root's module boundary to the
|
package/demos.json
CHANGED
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
"name": "terraform-estate",
|
|
68
|
-
"description": "A multi-root Terraform estate, declared only: two roots applied separately (envs/prod and platform) plus the shared module both are built on — 37 cards boxed by root, module calls expanded into their own contents, and the two look-alike versions.tf files told apart. Shows what behold does NOT infer as clearly as what it draws: platform declares the KMS key and prod reads it back by alias, and no edge is drawn between them, because both ends carry the same unresolved interpolation (#381). Needs chant's terraform lexicon beside behold, which behold does not install: npm install --no-save @intentius/chant-lexicon-terraform@^0.
|
|
68
|
+
"description": "A multi-root Terraform estate, declared only: two roots applied separately (envs/prod and platform) plus the shared module both are built on — 37 cards boxed by root, module calls expanded into their own contents, and the two look-alike versions.tf files told apart. Shows what behold does NOT infer as clearly as what it draws: platform declares the KMS key and prod reads it back by alias, and no edge is drawn between them, because both ends carry the same unresolved interpolation (#381). Needs chant's terraform lexicon beside behold, which behold does not install: npm install --no-save @intentius/chant-lexicon-terraform@^0.124.0 @cdktn/hcl2json@^0.24.0 (without them the demo refuses with that line); the copy is linked to the lexicon behold resolved, since chant reads a workspace's pinned kinds from beside the workspace. No cloud, no Docker, no terraform binary.",
|
|
69
69
|
"source": "bundled",
|
|
70
70
|
"dir": "example-terraform-estate",
|
|
71
71
|
"requires": [],
|