feat(workbench): point behold at internal estates — choudoufu, terralith, terraform, chant — from a local catalog (#386) - #392
Merged
Merged
Conversation
…387) choudoufu's leading estate declaration is the `estate.chdf.hcl` sidecar, not the `live { … }` block in a root *.tf: `tools/estate-gen` writes one into every cohort and a migrated terralith carries one. behold's probe knew only the block, so a rendered s3 cohort failed `memberKindOf` and doctor read it as "no chant.config.ts here, and no estate members declared". `hasLiveBlock` becomes `isChoudoufuEstate` and takes either form: the sidecar's presence in the directory (found, never read), or the unchanged regex over root *.tf files — not widened, so a `live {` in a subdirectory or in a notes.txt is still not evidence. Both forms at once is an error choudoufu itself reports, and not the probe's business. Two things followed. `detectProjectShape` reads a directory that IS a member of a non-chant kind as an estate of one (`membersFrom: "itself"`): a cohort is a bare directory nobody wrote a member list for, `behold serve <it>` has taken one since #369, and doctor was the only reader still calling it a dead end. And `readChoudoufuLive`'s refusal for a member with no estate name now says which two places name one, since the sidecar is the first place to look. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DoXg2GDWwXJRPQo76t9Nrq
The mechanism for #386; M3 seeds it. `behold demo` knew two sources, bundled and git, so an estate in a sibling checkout was served by hand, with its emulator and its `init` done by hand each time. A third source, `local`. Its `path` resolves against the directory of the catalog file that named it, never the cwd behold started in — the intentius checkouts are siblings, so a catalog writes `../choudoufu` and means the same directory from every terminal. `inPlace: true` serves it where it sits; the default copies to `behold-demos/<name>` with the bundled node_modules filter, because anything whose setup writes into the tree is copied or generated into the target, never run in a checkout — which is how #366's "behold never runs `choudoufu init` in a served project" survives contact with the workbench. An entry with no `path` at all is a generator: its setup renders the estate into an empty target, with `BEHOLD_WORKBENCH_DIR` and `BEHOLD_DEMO_NAME` in its environment so the script can reach its siblings and name its own scratch. A second catalog file, `workbench.json`, read beside `demos.json` and after it the file `BEHOLD_WORKBENCH` names. It is committed but kept out of package.json `files`: an entry naming `../choudoufu` means nothing inside an npm install. A name that collides with a bundled one is dropped with a stderr line rather than shadowing it. Every entry carries the catalog it came from — `--list` prints bundled and `workbench (this checkout)` as two blocks, `/api/demos` carries the field per row, and a path nobody checked out reads exactly like a missing binary: listed, disabled, saying what is absent, CI clean. And `choudoufuBinary()`: `CHOUDOUFU_BIN` or `choudoufu`, at every spawn, the version probe, the doctor line (which now prints which binary answered) and the demo requirement check. The Homebrew release is 0.15.0, below behold's floor, and the build that carries the floor's fields is one somebody built from main and left outside PATH. The two shell scripts that run choudoufu themselves spell the same fallback. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DoXg2GDWwXJRPQo76t9Nrq
…r kind over a scratch reader config (#384) #379/#380/#382 made a Terraform estate legible for a project that already declares the lexicon. Every estate this lane exists to draw declares nothing: `behold doctor access/envs/prod` answered "no chant.config.ts here", correctly and uselessly. #378 chose the other path when INTENTIUS/waterpark#88 was withdrawn — behold generates the reader config in a scratch directory, so an estate is browsable with no file in its own repo — and this builds it. `behold serve <terraform-dir>` now discovers the roots, writes a `chant.config.ts` naming them into `<tmpdir>/behold-tf-<hash>`, and reads that with chant. Nothing is written under the served directory; the Invariant's one in-project write is still `.behold/layout.json`. #384's three open questions, settled and recorded in AGENTS.md: 1. The lexicon is an OPTIONAL PEER, never installed by behold, probed at serve and doctor time and refused with the one install line and where behold looked. `@cdktf/hcl2json` — the ~1.8 MB wasm parser — stays out of the install of everyone who serves chant projects. Its chant peer is what moved behold's own `@intentius/chant` floor to ^0.61.0: chant 0.54 loads no published version of the lexicon (`applyLineage is not a function`). 2. Roots are discovered by a line-start `terraform {` / `provider "` probe beside a resource, data or module block — the same regex depth the choudoufu probe uses, no HCL parsed — with two exclusions the real estate forced and the note reports: a directory under a `modules/` segment, and one with nothing to draw. #384's proposed probe alone was refuted by water park: `modules/persona`'s versions.tf is `baseline`'s byte for byte. 3. A `terraform` member kind, after chant and choudoufu — a scaffold over chant, not a second reader — so a Terraform root composes in an estate beside a chant project and a choudoufu estate for free (#368), and `detectProjectShape` answers a directory that is its own member with `membersFrom: "probe"` instead of the `none` dead end. Measured on water park's `access/` (INTENTIUS/waterpark, read-only, byte identical before and after): 5 roots — envs/prod, identity, github, baseline, satellites/waterpark-runner — 2 skipped, 58 cards over 247 IR nodes, 49 edges, and the note saying all of it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DoXg2GDWwXJRPQo76t9Nrq
…ord for a directory that is its own member Both #387 and #384 taught detectProjectShape that a bare directory a non-chant kind claims is an estate of one. #387 landed first and called it `itself`; #384 called it `probe`. One word, #387's, and the doctor line says what the member is rather than the member count. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DoXg2GDWwXJRPQo76t9Nrq
…states, estate-gen cohorts, terralith at scale 1 and 4, terralith adopted by hand (#389) M2 built the mechanism and left `workbench.json` empty. Ten entries fill it, and every one of them was stood up on a real machine before it was written down. Two chant examples served in place from ../chant, source graphs, no substrate (8 nodes each). fountain-ops in place on the working checkout, whose setup is that checkout's own `just up` — the only entry here that runs in somebody's working copy and switches their kubectl context, and its description says so. The live-mv workbench's four estates, copied out of ../choudoufu rather than out of behold's bundled copy that drifts: 42 cards, the monolith green, the taggable team cards owned by it. Three estate-gen cohorts and three terraliths, rendered into an empty target by their own setup, because a generator is a source. The numbers, measured here against the pinned floci: terralith-1, 79 resources, 79 green cards, 50s cold. terralith-4, 301 (the composition has grown since #386 wrote 205), 301 green, 105s. terralith-4-adopt, the same estate applied by stock terraform first so nothing wears a marker — 301 cards, none owned, until `choudoufu live-import -state=terraform.tfstate -estate=… -approve` run by hand in the target stamps 137 and records all 301, and a reload paints every one of them green. It takes -estate after all: live-import's identities come from the state file alone, so there is no configuration for it to read the sidecar from. iam-ecr, 6 resources, 6 green. s3 and ec2-networking render and init and serve their sidecar-declared rosters (6 and 49 cards) but do not apply — floci answers S3 Control tag reads on a hostname that does not resolve and refuses a transit gateway call — so their apply is best effort and their cards are honestly unbound. choudoufu measures this itself in live/cohort-acceptance.json: 5 of 31 cohorts apply clean. estate-gen is asked for `-all` rather than `-cohort <name>` because the latter resolves types by CFN service, which answers for "s3" but not for a registry cohort like "iam-ecr" — one schema acquisition renders all 31 in 13s, and the one we came for is kept. Scratch discipline is asserted in bash, at the boot site: `behold-wb-<entry>`, one port each from 4651, never a shared emulator's, refused when the name is taken by a stopped container whose emulated cloud is gone while the target's records still name those resources. Each up script writes the matching `scripts/down.sh` into its target, and each is idempotent — a second `behold demo <entry>` reuses the render and the running floci and restarts the serve in 7s rather than re-rendering over live state. One thing had to change in src/, and it is what the seeded catalog was for. A LONE choudoufu estate — every generated entry is one — served the no-project card, because the single-project read is `chant graph <dir>` and such a directory has no lexicon for chant to find. `servesAsEstate` routes one directory of a non-chant kind through the estate compose path, where the member's own kind reads it; one member composes as four do, ids namespaced under the member's short name, so the graph, the pane and the morph agree on what a node is called. Nothing about a chant project's serve moves. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DoXg2GDWwXJRPQo76t9Nrq
…a lone non-chant directory #384 read a lone non-chant directory through its kind's via on the single branch; #389 composes it as an estate of one so the overlay and the pane work the same way for one member as for four. The composed path wins, the single-branch read stays for a caller that sets projectDir alone, and the terraform roots note is read off the composed members. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DoXg2GDWwXJRPQo76t9Nrq
… directory, and an in-place entry is never installed (#390) waterpark joins the catalog as a local, in-place entry served through the terraform member kind: five roots, 58 cards, nothing written under ../waterpark. Serving a checkout in place now means exactly that — loadDemo no longer runs npm install there, which M3 found leaving a node_modules and a package-lock.json in ../chant; the two chant entries say what they need. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DoXg2GDWwXJRPQo76t9Nrq
e2e/workbench-e2e.sh and `just e2e-workbench`: every entry in workbench.json loaded through `behold demo <entry> <tmp target> --port <p>`, one behold per entry on its own port from 4720 up, in catalog order. What each entry is asserted on: /healthz answers, /api/graph carries nodes (the count and the edge count are printed), and a live entry's /api/overlay?env=live answers with its bound/unowned/neutral split — the three words src/choudoufu-live.ts paints with. terralith-4-adopt is read twice: 85 UNOWNED while nothing wears a marker, then the `choudoufu live-import` line its up script PRINTED, run by the script itself in the target the way a person would, then 301 bound and nothing unowned. behold never runs that write (#372). waterpark is checked for a checkout that reads the same after the serve as before it (#384: nothing written under the estate; #390: an in-place entry is never installed). `missingRequirements` (src/demos.ts) decides what runs, so the skips carry the reason a person can act on: a binary that is not on PATH, a sibling checkout nobody has, a chant example with no node_modules of its own, the Terraform lexicon behold declares as an optional peer and does not install. CI has none of them, every entry skips, the run exits 0. fountain-ops is skipped by name everywhere — its setup is ../fountain-ops's own `just up`, a five-minute k3d cluster built in that working copy, and it switches your kubectl context. Teardown is each target's own scripts/down.sh, written there by the entry's up script — never `docker rm` by pattern. The run refuses to start when a behold-wb-* container is already standing and fails when one is left behind. Timings per entry. Measured here: 8 entries green, 3 skipped, 475s. choudoufu-workbench 42 cards (24 bound, 12 owned by the monolith, 6 neutral), the three estate-gen cohorts 6/6/49, terralith-1 79 bound, terralith-4 301 bound, terralith-4-adopt 301 bound after the import, waterpark 58 cards and 49 edges. The docs: AGENTS.md's "The workbench catalog" gains the eleven entries with what each serves and what it needs, the scratch container/port table (behold-wb-<entry> on 4651-4657), and the e2e; a line under "Getting a server" points at the second catalog. README's workbench paragraph now says what the catalog actually holds — eleven entries, water park and fountain-ops included — and that workbench.json is this checkout's and is not shipped. workbench.json's own terralith-4-adopt description takes the measured split. demos.json untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DoXg2GDWwXJRPQo76t9Nrq
This was referenced Sep 10, 2026
This was referenced Sep 10, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #386, #387, #388, #389, #390, #391, #384.
behold can now be pointed at the internal estates it should be developed against — choudoufu's cohorts and its live-mv workbench,
tools/terralith-genat scale, water park's Terraform roots, the chant examples and fountain-ops — from one catalog that lives in the checkout.just example terralith-4stands up a scratch floci, renders the terralith at scale 4, applies it with choudoufu and serves 301 resources green.What landed, by milestone
estate.chdf.hclsidecar, choudoufu's leading declaration, beside thelive {}block. Every estate-gen cohort failed the probe before this. A bare directory a non-chant kind claims is now an estate of one (membersFrom: "itself"), sobehold doctoron a cohort reports the member instead of "no chant.config.ts here".local(path,inPlace, generator entries with no path), and a second catalog:workbench.jsonbesidedemos.json, not in the npmfiles, paths relative to the catalog file so the sibling checkouts (../choudoufu,../waterpark,../chant) make it committed and reproducible; an entry nobody checked out is unsatisfiable exactly like a missing binary.BEHOLD_WORKBENCHnames a catalog elsewhere.CHOUDOUFU_BINat every spawn site, the doctor probe and the requirement check.just example name=….behold serve <terraform-dir>: aterraformmember kind whose read discovers the roots (aterraform {/provider "probe beside a resource, data or module block; directories undermodules/and directories with nothing to draw are skipped and the note says so), writes the reader config into<tmpdir>/behold-tf-<hash>, and reads it with chant. Nothing is written under the estate. The lexicon is an optional peer behold never installs; the refusal carries the install line. On water park: 5 roots, 58 cards,modules/personacorrectly not a root.live-importturns it green). A lone non-chant directory now serves through the composed path (servesAsEstate), which is what made the generated single-estate entries work.waterparkentry over serve a bare Terraform directory: generate the reader config in a scratch dir, so an estate needs no file of its own (#378) #384, and an in-place entry is nevernpm installed (M3 found the install leaving a lockfile in../chant).e2e/workbench-e2e.sh(just e2e-workbench): every satisfiable entry loaded, graphed and torn down through its own down script; skips with the reason in CI. AGENTS.md "The workbench catalog"; README.Things to know
applyLineage is not a function), and every lexicon's chant peer is minor-locked. Three doctor tests hard-coded the old floor. This is the one change outside serve a bare Terraform directory: generate the reader config in a scratch dir, so an estate needs no file of its own (#378) #384's stated scope.npm install --no-save @intentius/chant-lexicon-terraform @cdktf/hcl2jsonbeside behold; npm needs--legacy-peer-deps(the lexicon peerstypescript@^5.9.3) and refuses to install a package it already counts as a satisfied optional peer, so the e2e and the doctor say exactly what to run.live-importneeds-estate=on this build (its identities come from the state file alone).estate-gen -cohortresolves by CFN service, so the scripts render-alland keep the one cohort.s3stops on an S3 Control tag read,ec2-networkingon a transit-gateway call); their cards serve declared and unbound and the descriptions say so.iam-ecrapplies clean.just upis a five-minute k3d boot in the working copy that switches the kubectl context. The description says all of that.itself) and one path (composed) were kept.Numbers (from the e2e)
bash e2e/workbench-e2e.shon the author's machine: 8 entries green, 3 skipped, 475s.just upis a five-minute k3d boot that switches the kubectl context🤖 Generated with Claude Code
https://claude.ai/code/session_01DoXg2GDWwXJRPQo76t9Nrq