@intentius/behold 0.19.0 → 0.21.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.
Files changed (52) hide show
  1. package/AGENTS.md +90 -5
  2. package/README.md +54 -1
  3. package/demos.json +3 -2
  4. package/dist/cli.js +1327 -303
  5. package/example-argo-estate/README.md +8 -8
  6. package/example-argo-estate/app-a/package.json +2 -2
  7. package/example-argo-estate/app-b/package.json +2 -2
  8. package/example-argo-estate/chant.workspace.json +27 -0
  9. package/example-argo-estate/control-plane/package.json +2 -2
  10. package/example-argo-estate/package-lock.json +133 -56
  11. package/example-carve/README.md +14 -14
  12. package/example-carve/app/package-lock.json +72 -9
  13. package/example-carve/app/package.json +2 -2
  14. package/example-carve/legacy-tf/versions.tf +1 -1
  15. package/example-choudoufu-estate/README.md +4 -4
  16. package/example-choudoufu-estate/chant.workspace.json +32 -0
  17. package/example-choudoufu-estate/scripts/choudoufu-up.sh +21 -0
  18. package/example-flux-estate/README.md +5 -5
  19. package/example-flux-estate/app-a/package.json +2 -2
  20. package/example-flux-estate/app-b/package.json +2 -2
  21. package/example-flux-estate/chant.workspace.json +27 -0
  22. package/example-flux-estate/control-plane/package.json +2 -2
  23. package/example-flux-estate/package-lock.json +133 -56
  24. package/example-k8s/README.md +20 -20
  25. package/example-k8s/chant.config.ts +1 -1
  26. package/example-k8s/ops/k3d-apply.op.ts +1 -1
  27. package/example-k8s/package-lock.json +129 -211
  28. package/example-k8s/package.json +2 -3
  29. package/example-terraform-estate/README.md +5 -5
  30. package/example-terraform-estate/chant.workspace.json +22 -0
  31. package/example-writes/README.md +5 -5
  32. package/example-writes/chant.config.ts +1 -1
  33. package/example-writes/ops/apply.op.ts +1 -1
  34. package/example-writes/ops/floci.op.ts +2 -2
  35. package/example-writes/ops/reconcile.op.ts +1 -1
  36. package/example-writes/package-lock.json +62 -175
  37. package/example-writes/package.json +2 -3
  38. package/package.json +6 -6
  39. package/web/app.js +431 -17
  40. package/web/behaviour-scale.js +32 -0
  41. package/web/behaviour-scale.test.js +39 -0
  42. package/web/embed.js +76 -0
  43. package/web/embed.test.js +45 -0
  44. package/web/index.html +20 -0
  45. package/web/operator.js +14 -1
  46. package/web/panel.js +11 -3
  47. package/web/run-playhead.js +13 -1
  48. package/web/view-url.js +138 -0
  49. package/web/view-url.test.js +84 -0
  50. package/web/workspace-gates.js +44 -0
  51. package/web/workspace-gates.test.js +34 -0
  52. package/example-choudoufu-estate/.behold.json +0 -8
package/AGENTS.md CHANGED
@@ -14,6 +14,23 @@ apply creds.
14
14
  - Actions (delegated writes): `op-run` (start an `ApplyOp`/`ReconcileOp`),
15
15
  `op-signal` (approve a gate), `op-status` / `op-report` (watch it).
16
16
 
17
+ ## A declared workspace
18
+
19
+ A directory with a `chant.workspace.json` is a chant workspace (chant ≥ 0.81.0,
20
+ INTENTIUS/chant#2524). `behold serve <root>` serves it from chant's own member
21
+ list (`chant workspace ls --json`, read contract 1; any other contract is
22
+ refused), and reads each `chant` and `terraform` member through `chant
23
+ workspace graph --member <name>`. A `choudoufu` member keeps behold's own
24
+ reader, whose vocabulary (rungs, ownership, adoptable rows) chant's graph does
25
+ not carry. Node ids are `<member>/<id>` with the declared member name, the same
26
+ form chant composes. A member chant cannot read is drawn as one
27
+ `UnreadableMember` node carrying `attrs._unreadable: {code, message}` with
28
+ chant's reason code. An `other` member is listed on startup and in doctor and
29
+ draws nothing. `.behold.json`'s `members` are ignored beside a declaration;
30
+ `behold doctor --fix` writes the declaration for an estate that has only them.
31
+ Several directories on the command line (`behold serve a b c`) stay the loose
32
+ view behold composes itself.
33
+
17
34
  ## Getting a server
18
35
 
19
36
  ```sh
@@ -239,7 +256,8 @@ Two sources, in this order:
239
256
  1. `attrs._behaviour` on the nodes, as `chant graph --live --overlay` painted
240
257
  them, with `meta._behaviour` beside them for the graph-level half.
241
258
  2. `behaviour.<env>.json` at a member's root — `{meta, entities: {<address>:
242
- block}}` — read **only** when no node of that member carries the attr, and
259
+ block}}` — read **only** when no node of that member carries the attr and
260
+ the member's read carried no `meta._behaviour` either (#402), and
243
261
  **only on the overlay** (`/api/graph` is the source graph; a prediction
244
262
  about a live account has no business on it). The file's keys are the
245
263
  member's own addresses; behold prefixes them to reach the composed id
@@ -261,6 +279,41 @@ overlay is untouched either way: nothing in this pass reads or writes
261
279
  `_status`. `?logical=1` projects a different picture and carries no behaviour
262
280
  block in M1.
263
281
 
282
+ ### Live versus declared (#402, M5)
283
+
284
+ With a traffic level, `/api/overlay` predicts each member twice and carries
285
+ both figures and their difference. The level is `?traffic=` on the request,
286
+ else `.behold.json`'s `{"behaviour": {"traffic": "100 rps, p50"}}`, else none;
287
+ with none nothing is asked for and the payload is exactly M1's. behold never
288
+ defaults a level, because every figure is *at* one.
289
+
290
+ `meta.behaviour` gains `live`, `declared` (each `{perHour, currency, basis}`,
291
+ where `basis` is `total` for a figure the engine stated and `sum` for behold
292
+ adding the engine's per-entity figures) and `delta` (`{perHour, currency}`,
293
+ live minus declared), plus the same three per member under `members[<name>]`.
294
+ A delta is only ever like with like: one currency, and two totals or two sums.
295
+ Anything else yields no delta and a line in `diagnostics` saying why. A node
296
+ also carries the file's block under `attrs._behaviourDeclared`, including a
297
+ node the account no longer holds, which has no `_behaviour` at all.
298
+
299
+ Who reads what is `src/behaviour-delta.ts`'s header. A chant member's live
300
+ half is its own overlay read with `--traffic` added (sent only to a chant at
301
+ 0.75.0 or newer; an older one rejects the flag). A choudoufu member is not
302
+ read by chant, so behold predicts it through a generated reader project with
303
+ `binary: "choudoufu"` (the Terraform member's scratch project, under
304
+ `behold-chdf-*`), which needs the optional terraform lexicon. A terraform
305
+ member has no live half and so no delta.
306
+
307
+ The declared half goes through `memberIr` and is cached under the member's
308
+ source stamp plus the traffic level. The live half goes through `overlayIr`
309
+ and is dropped by whatever drops the overlay. With a level set, the
310
+ `behaviour.<env>.json` documents are not read at all: a fixture's figures
311
+ beside a real prediction would be two answers to one question.
312
+
313
+ `/api/graph?traffic=` (or the config default) paints the declared side alone
314
+ as `attrs._behaviour` with `meta.behaviour.declared`. The file is the only
315
+ estate that route has, so it carries no live half and no delta.
316
+
264
317
  ### Colour by drift, cost or headroom (#399, M2)
265
318
 
266
319
  The SPA colours the graph by one of three modes. The mode is **client-side
@@ -534,7 +587,17 @@ inside a project, and it does not weaken the invariant above:
534
587
  render with the deltas baked into the SVG, which is how `behold export` and
535
588
  static snapshots honour a hand layout.
536
589
 
537
- The second exception is the carve walkthrough's two steps above (#254), and it
590
+ A declared workspace (#464) keeps that file in the workspace root, the
591
+ directory holding `chant.workspace.json`, rather than in its first member.
592
+
593
+ `behold doctor --fix` (#464, ws-015) is the third exception, and it runs only
594
+ when you type the flag: for an estate root whose members are listed in
595
+ `.behold.json`, it writes `chant.workspace.json` in that root (never over an
596
+ existing one) and moves the saved `.behold/layout.json` from the first member to
597
+ the root with its node ids rewritten. It then runs `chant workspace check`. The
598
+ old layout file and `.behold.json` are left as they were.
599
+
600
+ The fourth exception is the carve walkthrough's two steps above (#254), and it
538
601
  is narrower still: they exist only when behold booted a `behold demo carve`
539
602
  copy, they write only into `<copy>/app/carveout/`, and the directory they write
540
603
  into is a scratch dir behold created inside a directory it copied for you a
@@ -736,7 +799,7 @@ its own graph and overlay counts as it runs.
736
799
  | `terralith-1` | `terralith-gen` at scale 1 plus the `estate.chdf.hcl` sidecar it omits, applied by choudoufu from nothing: 79 cards, all 79 bound | docker, choudoufu, go |
737
800
  | `terralith-4` | the same at scale 4: 301 cards, all 301 bound. The estate behold is sized against | the same |
738
801
  | `terralith-4-adopt` | scale 4 again, but STOCK terraform applies it first, so nothing wears a marker: 301 cards — 85 UNOWNED, 84 bound by derived identity alone, 132 neutral. One `choudoufu live-import` line, which the up script prints and you run, and all 301 read bound | docker, choudoufu, go, terraform |
739
- | `waterpark` | `../waterpark/access` in place as a bare Terraform directory (#384): five roots as boxes, 58 cards, 49 edges, nothing written under the estate | `@intentius/chant-lexicon-terraform` + `@cdktf/hcl2json` beside behold — optional peers behold declares and does not install |
802
+ | `waterpark` | `../waterpark/access` in place as a bare Terraform directory (#384): five roots as boxes, 58 cards, 49 edges, nothing written under the estate | `@intentius/chant-lexicon-terraform` + `@cdktn/hcl2json` beside behold — optional peers behold declares and does not install |
740
803
 
741
804
  **The scratch, by name and port.** Each emulator is the entry's own, booted by
742
805
  its up script and removed by the `scripts/down.sh` that script wrote into the
@@ -866,9 +929,9 @@ water park's `access/` before it was taken:
866
929
 
867
930
  1. **The lexicon is opt-in.** chant resolves the lexicon from the config file's
868
931
  own location, so the generated project has to see it, and making it a
869
- dependency would put `@cdktf/hcl2json`, a ~1.8 MB wasm blob, in the install
932
+ dependency would put `@cdktn/hcl2json`, a ~1.8 MB wasm blob, in the install
870
933
  of every user who serves a chant project. `@intentius/chant-lexicon-terraform`
871
- and `@cdktf/hcl2json` are therefore **optional peers**: declared in
934
+ and `@cdktn/hcl2json` are therefore **optional peers**: declared in
872
935
  package.json (the only place their versions are named, the refusal reads
873
936
  them from there), never installed by behold, probed at serve and doctor time,
874
937
  and refused with the one install line and where behold looked. The same gate
@@ -1001,6 +1064,28 @@ and marking every one of them would say nothing.
1001
1064
  This is presentation, not vocabulary. `statusVocabulary` gets no terraform row:
1002
1065
  there is no status to name. See #429 for the measurement that established it.
1003
1066
 
1067
+ ### An adoptable row is painted like an unowned one, plus its evidence (#413)
1068
+
1069
+ A `NEEDS_DISCOVERY` omission paired with an `adoptable[]` row at the same
1070
+ address paints `warn` with `ownership: "foreign"`, the same verdict the UNOWNED
1071
+ branch gives and for the same reason: the live object carries no marker for
1072
+ this estate. On its own, `NEEDS_DISCOVERY` stays neutral — the declaration
1073
+ names no identity, and with nothing found that is a "could not answer" rather
1074
+ than an action.
1075
+
1076
+ The card leads with `attrs.adopt`, which is choudoufu's own `adopt_command`
1077
+ here rather than the marker pair an unowned row carries. `adoptLine`'s
1078
+ composed-command branch had no real caller until this. behold composes no
1079
+ command of its own: the API that writes a tag differs per resource type.
1080
+
1081
+ `attrs.matchedOn` is the difference in kind. An unowned row needs no evidence
1082
+ because the identity IS the match; here choudoufu asserts that a live object is
1083
+ a declaration because their identity-bearing arguments agree, and a person
1084
+ about to stamp two tags onto somebody's resource is owed the list.
1085
+
1086
+ `web/app.js` renders both through the one copyable row, and its tooltip says
1087
+ whether the line is a command or a pair of tags.
1088
+
1004
1089
  ### The adoption sweep is off unless a serve asks (#412)
1005
1090
 
1006
1091
  `live-plan -json` carries `adoptable[]` (live resources the estate-wide sweep
package/README.md CHANGED
@@ -310,6 +310,55 @@ behold shells the **project's own** chant (resolved from the project's
310
310
  `@intentius/chant ^0.18.1` or later for the live overlay; `graph --live` observed
311
311
  nothing before that fix.
312
312
 
313
+ ## Linking to a view, and framing behold in a host
314
+
315
+ The view is in the URL. behold reads it at boot and writes it back as the view
316
+ changes, so a reload keeps it and a link opens where the sender was:
317
+
318
+ ```
319
+ http://localhost:4600/?member=delivery&zoom=components&env=prod&node=delivery/appService
320
+ ```
321
+
322
+ `member`, `zoom`, `env`, `tier`, `lens` (the colour: `drift`, `cost` or
323
+ `headroom`), `node` and `radial` (`1` or `0`). An empty `env=` is the source
324
+ graph. A value the estate can't honour (a member it doesn't have, a zoom it
325
+ doesn't offer) is named on the now line. A static export takes the same
326
+ parameters against what it captured.
327
+
328
+ A host pane, such as arugula's workspace block, frames behold with
329
+ `?embed=1&host=<the host's origin>`, and optionally `theme=light`, `dark` or
330
+ any theme name the picker lists:
331
+
332
+ - the panel and the inspect pane start folded, and the Deploy tab and the theme
333
+ picker are gone
334
+ - picking a card posts `{type: "behold:select", member, node}` to the parent at
335
+ `host`; picking a member's box posts the same with `node: null`
336
+ - the parent moves the view with `{type: "behold:view", ...}`, carrying any of
337
+ the URL's fields (`member: null` is the whole estate). Messages from any
338
+ other origin are ignored
339
+ - gates are the host's to approve: behold draws them and says so
340
+
341
+ `gates=<env>` sets the env the gate strip reads, apart from the graph's, so a
342
+ host can open behold on the source graph and still show the gates of the env
343
+ it watches.
344
+
345
+ behold binds 127.0.0.1 and answers only loopback names, since it runs writes
346
+ with the credentials of whoever started it. A page from another site can't
347
+ write to it. A host that frames behold through a proxy of its own passes the
348
+ proxy's name with `--allow-host <name>` (or `BEHOLD_ALLOWED_HOSTS`); `--host`
349
+ (or `BEHOLD_HOST`) binds another address, which then needs `--allow-host` for
350
+ the names it is reached by.
351
+
352
+ In a declared workspace, the gates waiting on a person come from
353
+ `chant workspace status <env> --json`, keyed `member/op/gate` the way arugula's
354
+ workspace block keys them, so the two show the same set. Standalone, a gate's
355
+ button says who chant will record (the user running behold) before the click.
356
+ Every approve route refuses in preview mode.
357
+
358
+ A host that already watches the workspace tells behold it changed with
359
+ `POST /api/refresh?notify=1`, instead of running behold with `--poll` beside
360
+ it. behold drops its cached reads and every open page re-pulls.
361
+
313
362
  ## Terraform carve-out: `behold carve <report.json>`
314
363
 
315
364
  `chant carve advise` ranks a Terraform estate by **peelability**: how cleanly
@@ -375,7 +424,11 @@ config, kept separate from `chant.config.ts` so behold's concerns (like the
375
424
  tier picker) don't leak into chant's. Three keys: `tiers` below, `executor`
376
425
  (which forge deploys an environment), and `members`, spelled `[{ "dir": "x", "kind":
377
426
  "chant" | "choudoufu" | "terraform" }]`, how an estate root names what it
378
- composes, fail-closed on a kind behold has no reader for. First, the
427
+ composes, fail-closed on a kind behold has no reader for. `members` is
428
+ deprecated: a chant workspace declaration (`chant.workspace.json`) lists an
429
+ estate's members for every reader, and beside one, `.behold.json`'s list is
430
+ ignored. `behold doctor --fix` writes the declaration from it (see
431
+ [Serve a chant workspace](https://intentius.io/behold/using/workspace/)). First, the
379
432
  project's deploy-**tier** axis, a dimension orthogonal to `environment` (chant
380
433
  has no native tier concept; it is entirely a project convention, e.g. Loom's
381
434
  components branching on an env-conditioned `namingParams.tier`):
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 @cdktf/hcl2json (the refusal names the exact line). No cloud, no Docker, no terraform binary.",
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 refusal names the exact line). No cloud, no Docker, no terraform binary.",
69
69
  "source": "bundled",
70
70
  "dir": "example-terraform-estate",
71
71
  "requires": [],
@@ -109,7 +109,8 @@
109
109
  "AWS_ENDPOINT_URL": "http://127.0.0.1:4650",
110
110
  "AWS_ACCESS_KEY_ID": "test",
111
111
  "AWS_SECRET_ACCESS_KEY": "test",
112
- "AWS_REGION": "us-east-1"
112
+ "AWS_REGION": "us-east-1",
113
+ "TOFU_LIVE_COLLECT_UNCLAIMED": "1"
113
114
  }
114
115
  }
115
116
  },