@intentius/behold 0.22.0 → 0.24.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 +97 -6
- package/README.md +72 -8
- package/demos.json +2 -2
- package/dist/cli.js +3380 -837
- package/example-choudoufu-estate/chant.workspace.json +1 -1
- package/example-terraform-estate/README.md +12 -2
- package/example-terraform-estate/chant.workspace.json +1 -1
- package/package.json +6 -5
- package/web/app.js +393 -3
- package/web/index.html +29 -0
- package/web/terragucci.js +138 -0
- package/web/terragucci.test.js +96 -0
- package/web/why.js +107 -0
- package/web/why.test.js +69 -0
package/AGENTS.md
CHANGED
|
@@ -8,7 +8,8 @@ apply creds.
|
|
|
8
8
|
## The division of labour
|
|
9
9
|
|
|
10
10
|
- **behold** serves the live, mixed-substrate graph (and, later, the deployment-lanes
|
|
11
|
-
timeline). It reads; it
|
|
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 starts none.
|
|
12
13
|
- **chant's MCP** is where the real capabilities live. Prefer it over shelling.
|
|
13
14
|
- Reads: `lifecycle-diff`, `lifecycle-snapshot`, `build`, `lint`.
|
|
14
15
|
- Actions (delegated writes): `op-run` (start an `ApplyOp`/`ReconcileOp`),
|
|
@@ -19,18 +20,73 @@ apply creds.
|
|
|
19
20
|
A directory with a `chant.workspace.json` is a chant workspace (chant ≥ 0.81.0,
|
|
20
21
|
INTENTIUS/chant#2524). `behold serve <root>` serves it from chant's own member
|
|
21
22
|
list (`chant workspace ls --json`, read contract 1; any other contract is
|
|
22
|
-
refused), and reads each `chant` and `
|
|
23
|
-
workspace graph --member <name>`. A `choudoufu` member
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
refused), and reads each `chant`, `terraform` and `choudoufu` member through
|
|
24
|
+
`chant workspace graph --member <name>`. A `choudoufu` member takes its block
|
|
25
|
+
graph from it (the estate's own edges, each block's `file` and `line`) and
|
|
26
|
+
joins it onto choudoufu's `live-check` roster, which still names the cards,
|
|
27
|
+
the rungs and the cross-estate references: chant's graph is one node per
|
|
28
|
+
block, ownership is per instance, and chant carries no rung or reference
|
|
29
|
+
(src/choudoufu-workspace.ts, #465). The ids are the roster's, as before, so a
|
|
30
|
+
saved layout keeps matching. behold's own lexicon read of a choudoufu
|
|
31
|
+
directory stays for a loose view only. Node ids are `<member>/<id>` with the declared member name, the same
|
|
26
32
|
form chant composes. A member chant cannot read is drawn as one
|
|
27
33
|
`UnreadableMember` node carrying `attrs._unreadable: {code, message}` with
|
|
28
34
|
chant's reason code. An `other` member is listed on startup and in doctor and
|
|
29
|
-
draws nothing.
|
|
35
|
+
draws nothing. A declaration with `members: []` (terragucci writes one for its
|
|
36
|
+
gates) declares nothing to draw, so serve, export and doctor read the
|
|
37
|
+
directory as they would without one, and say so (#489). `.behold.json`'s `members` are ignored beside a declaration;
|
|
30
38
|
`behold doctor --fix` writes the declaration for an estate that has only them.
|
|
31
39
|
Several directories on the command line (`behold serve a b c`) stay the loose
|
|
32
40
|
view behold composes itself.
|
|
33
41
|
|
|
42
|
+
Every workspace read goes through one module, `src/workspace-reader.ts`: one
|
|
43
|
+
chant call per read, the document handed back as chant printed it, any
|
|
44
|
+
contract but 1 refused. chant's reader conformance suite
|
|
45
|
+
(`@intentius/chant/workspace/conformance`) runs against that module in `npm
|
|
46
|
+
test` (`src/workspace-reader-conformance.test.ts`, #468), so a new workspace
|
|
47
|
+
read is added to the reader and to the suite's `commands`, never shelled from
|
|
48
|
+
somewhere else.
|
|
49
|
+
|
|
50
|
+
`GET /api/workspace/why?member=<name>` or `?node=<member>/<id>` (#471) answers
|
|
51
|
+
why a member or a card is the way it is, from `chant workspace graph --intent
|
|
52
|
+
<region> --json` (chant 0.102.0 or newer writes its `why`): the decisions
|
|
53
|
+
covering it in chant's order, the agent runs and the commits behind it, and the
|
|
54
|
+
gaps chant names. The region is the member's declared directory or the node id,
|
|
55
|
+
never a path from the caller. It takes seconds on a small workspace and tens of
|
|
56
|
+
seconds on a large one, so the inspect pane reads it only on a click, and it is
|
|
57
|
+
a scheduled read: it holds a slot in the read budget, has the read deadline,
|
|
58
|
+
and stops when the request does. `serve --hud <url>` (or `BEHOLD_HUD_URL`)
|
|
59
|
+
links a proposed decision to its review in hud.
|
|
60
|
+
|
|
61
|
+
## A terragucci estate, from its reports (#490)
|
|
62
|
+
|
|
63
|
+
`behold serve <repo> --terragucci <dir|s3://bucket/prefix|https://…>`
|
|
64
|
+
(`--terragucci-project <host/path>` when the bucket holds several projects
|
|
65
|
+
and none is the checkout's git remote) adds two reads, in
|
|
66
|
+
`src/terragucci-view.ts`:
|
|
67
|
+
|
|
68
|
+
- `GET /api/terragucci`: `{source, project, validation, read, reports, files,
|
|
69
|
+
estate?, roots[], cards{}, waiting[], unmatched}`. `roots[]` is every
|
|
70
|
+
Terraform root of the checkout with the newest `plan`, `drift` and `apply`
|
|
71
|
+
run that held it; a missing stage means no report holds that root, which is
|
|
72
|
+
not "no changes". `cards` maps a card id to its marks (`verdict: drift |
|
|
73
|
+
plan | wave`, terragucci's words, the instance addresses, the run). Nothing
|
|
74
|
+
sets `_status`: a report is never painted as live. `waiting[]` carries each
|
|
75
|
+
wave's roots, destroys, and the `command` a person runs. `estate` is
|
|
76
|
+
terragucci's own counts when the source has `estate.json`. A key such as a
|
|
77
|
+
run's `report` opens at `files` + the key. Refusals are `422 {code:
|
|
78
|
+
"terragucci-report"}`. `?fresh=1` skips the 30 s cache.
|
|
79
|
+
- `GET /api/terragucci/file?key=<key>`: one report file from the source,
|
|
80
|
+
sandboxed.
|
|
81
|
+
|
|
82
|
+
The reads are `src/terragucci-reports.ts` (index, each run's report, estate;
|
|
83
|
+
terragucci's JSON Schemas when `@intentius/terragucci` resolves, a structural
|
|
84
|
+
check otherwise; the package is looked up on every read and the compiled
|
|
85
|
+
schemas cached by its path and files' mtimes, #500) and the join is `src/terragucci-overlay.ts` (root path to
|
|
86
|
+
`attrs.root`, instance address to block path). An S3 source shells the
|
|
87
|
+
operator's `aws s3 cp <key> -`. There is no approve route, by design: hand the
|
|
88
|
+
operator `waiting[].command`.
|
|
89
|
+
|
|
34
90
|
## Getting a server
|
|
35
91
|
|
|
36
92
|
```sh
|
|
@@ -565,6 +621,25 @@ If a request would have behold write to a cloud or to source directly, it's wron
|
|
|
565
621
|
behold shows truth and triggers Ops. Authority stays in the committed source and the
|
|
566
622
|
executor.
|
|
567
623
|
|
|
624
|
+
The commands behold starts run with behold's own environment, so Deploy (`POST
|
|
625
|
+
/api/apply`, chant's local executor) applies with the credentials of whoever
|
|
626
|
+
started it. On a terragucci repo (a `terragucci.yml` in the served directory or
|
|
627
|
+
above it, up to the git root) behold starts nothing: every POST that deploys,
|
|
628
|
+
approves or starts a run answers `409` with `code: "terragucci"` and names the
|
|
629
|
+
pipeline (`src/terragucci-repo.ts`, #489), `/api/project` carries `terragucci`
|
|
630
|
+
so the page offers no write, and auto-sync declines. The boundary is an
|
|
631
|
+
allowlist (#500): every request whose method is not GET, HEAD or OPTIONS, on
|
|
632
|
+
any path, answers that `409` unless `TERRAGUCCI_ALLOWED_WRITES` names its
|
|
633
|
+
method and route, and that list holds only the exceptions below (the layout
|
|
634
|
+
sidecar, project open and reveal, demo open, refresh, and the four carve
|
|
635
|
+
steps). A new write route is refused on a terragucci repo until it is added
|
|
636
|
+
there. `src/terragucci-repo.test.ts` walks every non-GET route of the real
|
|
637
|
+
app (`app.routes`, built plain, with a carve report and with `--terragucci`)
|
|
638
|
+
and asserts each one is allowlisted or answers 409, and that every allowlist
|
|
639
|
+
entry is a registered route. `TERRAGUCCI_REFUSED_WRITES` only words the
|
|
640
|
+
refusal for known routes. The remedy names `npx terragucci approve wave-<k>
|
|
641
|
+
--plan <digest>`, the digest from the wave's gate card.
|
|
642
|
+
|
|
568
643
|
### The exceptions, and their exact size
|
|
569
644
|
|
|
570
645
|
`POST /api/layout` (#228) writes **one** file in the served project:
|
|
@@ -607,6 +682,22 @@ and it must be an address the served report already ranks, the value that
|
|
|
607
682
|
reaches the spawn's argv comes from a closed set read off disk. No cloud write,
|
|
608
683
|
no Terraform mutation, no edit to anyone's chant source.
|
|
609
684
|
|
|
685
|
+
`behold export --publish s3://<bucket>/<prefix>/views/<name>` (#491) is the
|
|
686
|
+
one write to a cloud, and it is not a server route: an export, asked for it on
|
|
687
|
+
the command line, uploads the bundle it just wrote, signed with the AWS
|
|
688
|
+
credentials in the environment (src/s3-object.ts). `publishTarget` refuses
|
|
689
|
+
any destination that is not a `views/<name>` directory, terragucci's reserved
|
|
690
|
+
viewer prefix, so the bundle's `index.html` can never land on a page behold did
|
|
691
|
+
not make. `publishBundle` (src/export.ts, #500) uploads every other file
|
|
692
|
+
first, then `manifest.json`, then `index.html`. Snapshot names carry the first
|
|
693
|
+
16 hex digits of their SHA-256 (`CONTENT_ADDRESSED`) and go up with
|
|
694
|
+
`Cache-Control: public, max-age=31536000, immutable`; everything else gets
|
|
695
|
+
`no-cache`. It GETs the previous `manifest.json` under the prefix before
|
|
696
|
+
uploading (no list call) and, after `index.html`, deletes the
|
|
697
|
+
`snapshots/<name>` files that manifest named and this export did not write.
|
|
698
|
+
Nothing outside the prefix is deleted. A refused delete stops the sweep with
|
|
699
|
+
a warning naming `s3:DeleteObject`, and the publish still succeeds.
|
|
700
|
+
|
|
610
701
|
A choudoufu estate member (#366) adds no exception. A move there is one tag
|
|
611
702
|
write through choudoufu's own `live-mv`, and behold never makes it: `GET
|
|
612
703
|
/api/choudoufu/moves` reads a plan (`carve.json`) from inside a served member,
|
package/README.md
CHANGED
|
@@ -145,6 +145,26 @@ Set the Worker name with `--name`, or edit `wrangler.jsonc`. Auth via
|
|
|
145
145
|
Workers deploy). GitHub Pages, S3, nginx and Cloudflare Pages
|
|
146
146
|
(`wrangler pages deploy .`) all work too.
|
|
147
147
|
|
|
148
|
+
A bundle is made to be opened by other people, so it carries the picture and
|
|
149
|
+
nothing about the machine that made it: no list of your recent projects, no
|
|
150
|
+
user name, and every absolute path reduced to its directory's name. Each card
|
|
151
|
+
of a Terraform root carries that root's whole source file (`attrs.source`);
|
|
152
|
+
`--no-source` leaves it out.
|
|
153
|
+
|
|
154
|
+
`--terragucci <src>` (with `--terragucci-project`, as for `serve`) captures the
|
|
155
|
+
terragucci marks too, for the view terragucci's estate job can publish beside
|
|
156
|
+
`estate.html`. The bundle's report links are relative to
|
|
157
|
+
`<prefix>/views/behold/` (`--reports-base` changes that), so uploaded there they
|
|
158
|
+
open the bucket's own report pages through the same presigned link or front
|
|
159
|
+
door. An export asked for marks it cannot read fails rather than publishing a
|
|
160
|
+
picture without them.
|
|
161
|
+
|
|
162
|
+
`--publish s3://<bucket>/<prefix>/views/<name>` uploads the bundle there too,
|
|
163
|
+
each file with its content type, signed with the AWS credentials in the
|
|
164
|
+
environment (no SDK, no aws CLI needed). It writes only under a `views/<name>`
|
|
165
|
+
directory, terragucci's reserved viewer prefix, so a bundle's `index.html`
|
|
166
|
+
never lands on a page behold did not make.
|
|
167
|
+
|
|
148
168
|
## Try it: your first apply, no cloud account
|
|
149
169
|
|
|
150
170
|
The bundled `example-writes` is one S3 bucket. `serve --local` boots *that
|
|
@@ -202,21 +222,33 @@ including the exact commands and what each state looks like over the API:
|
|
|
202
222
|
|
|
203
223
|
## Read-only core, delegated gated writes (the invariant)
|
|
204
224
|
|
|
205
|
-
**behold
|
|
225
|
+
**behold writes nothing to a cloud or to your source itself.** Every write it
|
|
226
|
+
offers is a command it starts for you, run by a tool you already trust.
|
|
206
227
|
|
|
207
228
|
- The vizes only read (`chant graph`, snapshots, Temporal history).
|
|
208
|
-
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
229
|
+
- Sync starts your committed `ApplyOp`; Adopt starts your `ReconcileOp` (opens a
|
|
230
|
+
PR a human merges). Those run on your executor.
|
|
231
|
+
- Deploy on a project with no committed ApplyOp runs `chant run <component>
|
|
232
|
+
--components` on chant's local executor, as a child of behold. That child has
|
|
233
|
+
behold's environment, so it applies with the credentials of whoever started
|
|
234
|
+
behold: to Deploy from behold, that environment needs write roles. Reads
|
|
235
|
+
alone need only read roles.
|
|
236
|
+
- An env `.behold.json` designates to a forge is never applied from the laptop;
|
|
237
|
+
Deploy dispatches the forge's workflow through your `gh` instead.
|
|
238
|
+
- `behold export --publish s3://…/views/<name>` uploads the bundle it just made.
|
|
239
|
+
It is the one write behold makes to a cloud, and only when an export is
|
|
240
|
+
asked for it.
|
|
241
|
+
- A terragucci repo (a `terragucci.yml` in the served directory or above it) is
|
|
242
|
+
applied by its pipeline only. behold refuses every write there (Deploy, Op
|
|
243
|
+
runs, gate approvals, pipeline dispatch, rollback) with `409 terragucci` and
|
|
244
|
+
offers none of them on the page.
|
|
213
245
|
|
|
214
246
|
## Why a Node service (not an edge function)
|
|
215
247
|
|
|
216
248
|
The live path (`chant graph --live --overlay`, `chant lifecycle plan`) shells
|
|
217
249
|
`kubectl`/`aws`/`az`/the Temporal client and holds cloud creds. That needs a real
|
|
218
|
-
process, so behold is a Node service you run where your creds live.
|
|
219
|
-
roles (describe/list)
|
|
250
|
+
process, so behold is a Node service you run where your creds live. Viewing needs only **read**
|
|
251
|
+
roles (describe/list); a Deploy from behold applies with whatever credentials its environment holds.
|
|
220
252
|
|
|
221
253
|
## Agent-drivable, on chant's MCP
|
|
222
254
|
|
|
@@ -359,10 +391,42 @@ workspace block keys them, so the two show the same set. Standalone, a gate's
|
|
|
359
391
|
button says who chant will record (the user running behold) before the click.
|
|
360
392
|
Every approve route refuses in preview mode.
|
|
361
393
|
|
|
394
|
+
In a declared workspace, the inspect pane of a member's box or a card has a
|
|
395
|
+
"why" section. "Read why" asks `chant workspace graph --intent <member dir or
|
|
396
|
+
node id> --json` (chant 0.102.0 or newer in the workspace root) and shows the
|
|
397
|
+
decisions covering it, in chant's order, with the agent runs and commits behind
|
|
398
|
+
it. Nothing is read until the click: on chant's own repository one member takes
|
|
399
|
+
about 30 s, a small workspace about 1 s. While it reads the section says
|
|
400
|
+
"reading…" with a Stop button, and picking something else stops the read.
|
|
401
|
+
Start behold with `--hud <url>` (or `BEHOLD_HUD_URL`) and a proposed decision
|
|
402
|
+
links to its review in hud; otherwise it is named by id. Framed, the section is
|
|
403
|
+
drawn the same, and clicks on the graph still go to the host.
|
|
404
|
+
|
|
362
405
|
A host that already watches the workspace tells behold it changed with
|
|
363
406
|
`POST /api/refresh?notify=1`, instead of running behold with `--poll` beside
|
|
364
407
|
it. behold drops its cached reads and every open page re-pulls.
|
|
365
408
|
|
|
409
|
+
## A terragucci estate, from its reports: `--terragucci`
|
|
410
|
+
|
|
411
|
+
```sh
|
|
412
|
+
npx @intentius/behold serve . --terragucci s3://acme-terragucci-reports # or a synced dir, or an https address
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
In a [terragucci](https://intentius.io/terragucci/) repo, behold draws every
|
|
416
|
+
root from the checkout and marks each card with what terragucci's newest runs
|
|
417
|
+
found: the newest `tf-drift` and `tf-plan` report per root (one GET of
|
|
418
|
+
`index.json`, then one of each run's `report.json`), and the waves waiting for
|
|
419
|
+
an approval. Every mark is dated by its run and links its report and job
|
|
420
|
+
("drift found 06:04 UTC, 4h ago"); none is a fill, and nothing is read from a
|
|
421
|
+
cloud. Counts and waiting waves come from `estate.json` when the source has
|
|
422
|
+
one, so behold agrees with terragucci's estate page and links it. A waiting
|
|
423
|
+
wave offers `npx terragucci approve wave-<k> --plan <digest>` to copy; there is
|
|
424
|
+
no approve button, and every behold write refuses on a terragucci repo.
|
|
425
|
+
Documents are checked against terragucci's JSON Schemas when
|
|
426
|
+
`@intentius/terragucci` is installed, structurally otherwise. Full page:
|
|
427
|
+
docs `using/terragucci`. `example-terragucci-reports/` is a bucket for
|
|
428
|
+
terragucci's own example, written by terragucci's code.
|
|
429
|
+
|
|
366
430
|
## Terraform carve-out: `behold carve <report.json>`
|
|
367
431
|
|
|
368
432
|
`chant carve advise` ranks a Terraform estate by **peelability**: how cleanly
|
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 @cdktn/hcl2json (the
|
|
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.121.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": [],
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
"name": "choudoufu-estate",
|
|
92
|
-
"description": "A tag-owned estate: four choudoufu estates (an OpenTofu fork whose ownership is two AWS tags) served composed — the terralith applied to a scratch floci, three team estates whose declared resources are the monolith's (every card `owned by` it, dashed to its card), a cross-estate data-source edge, and a move plan whose `live-mv` lines you run by hand while behold previews with -dry-run and reads the receipt. A card's colour is ownership, not \"nothing changed\": attribute drift is a separate plan read, opt-in through ⌘K → \"Re-check live with plan\". Docker + choudoufu (0.16.0+, or a build from main) on PATH; provider download on first init. scripts/choudoufu-down.sh removes the container.",
|
|
92
|
+
"description": "A tag-owned estate: four choudoufu estates (an OpenTofu fork whose ownership is two AWS tags) served composed — the terralith applied to a scratch floci, three team estates whose declared resources are the monolith's (every card `owned by` it, dashed to its card), a cross-estate data-source edge, and a move plan whose `live-mv` lines you run by hand while behold previews with -dry-run and reads the receipt. A card's colour is ownership, not \"nothing changed\": attribute drift is a separate plan read, opt-in through ⌘K → \"Re-check live with plan\". Docker + choudoufu (0.16.0+, or a build from main) on PATH, and chant's terraform lexicon beside behold (it supplies the choudoufu kind; the copy is linked to it); provider download on first init. scripts/choudoufu-down.sh removes the container.",
|
|
93
93
|
"source": "bundled",
|
|
94
94
|
"dir": "example-choudoufu-estate",
|
|
95
95
|
"requires": [
|