Skip to content

feat(workbench): point behold at internal estates — choudoufu, terralith, terraform, chant — from a local catalog (#386) - #392

Merged
lex00 merged 8 commits into
mainfrom
feat/workbench-386
Sep 10, 2026
Merged

feat(workbench): point behold at internal estates — choudoufu, terralith, terraform, chant — from a local catalog (#386)#392
lex00 merged 8 commits into
mainfrom
feat/workbench-386

Conversation

@lex00

@lex00 lex00 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

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-gen at scale, water park's Terraform roots, the chant examples and fountain-ops — from one catalog that lives in the checkout. just example terralith-4 stands up a scratch floci, renders the terralith at scale 4, applies it with choudoufu and serves 301 resources green.

What landed, by milestone

Things to know

  • chant floor moved to ^0.61.0. No published terraform lexicon loads under chant 0.54 (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.
  • The lexicon is opt-in. npm install --no-save @intentius/chant-lexicon-terraform @cdktf/hcl2json beside behold; npm needs --legacy-peer-deps (the lexicon peers typescript@^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.
  • Spec corrections found by running it. terralith at scale 4 is 301 resources with today's generator, not 205. live-import needs -estate= on this build (its identities come from the state file alone). estate-gen -cohort resolves by CFN service, so the scripts render -all and keep the one cohort.
  • Two cohorts do not apply on the pinned floci (s3 stops on an S3 Control tag read, ec2-networking on a transit-gateway call); their cards serve declared and unbound and the descriptions say so. iam-ecr applies clean.
  • fountain-ops is in the catalog but was not run: its just up is a five-minute k3d boot in the working copy that switches the kubectl context. The description says all of that.
  • Merged from three worktree branches. Two agents each added the "directory is its own member" shape; one word (itself) and one path (composed) were kept.

Numbers (from the e2e)

bash e2e/workbench-e2e.sh on the author's machine: 8 entries green, 3 skipped, 475s.

entry nodes overlay (env=live) setup
choudoufu-workbench 42 bound 24, unowned 12 (owned by the monolith), neutral 6 51s
choudoufu-cohort-s3 6 neutral 6 (apply refused by floci) 35s
choudoufu-cohort-iam-ecr 6 bound 6 34s
choudoufu-cohort-ec2-networking 49 neutral 49 (apply refused by floci) 27s
terralith-1 79 bound 79 48s
terralith-4 301 bound 301 105s
terralith-4-adopt 301 before the by-hand live-import: bound 84, unowned 85, neutral 132; after: bound 301 115s + import
waterpark 58 (49 edges) source only; checkout untouched 1s
chant-getting-started, chant-local-cloud-trio skipped: no node_modules in ../chant/examples, and an in-place entry is never installed
fountain-ops skipped by name: its just up is a five-minute k3d boot that switches the kubectl context

🤖 Generated with Claude Code

https://claude.ai/code/session_01DoXg2GDWwXJRPQo76t9Nrq

lex00 and others added 8 commits September 9, 2026 15:21
…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
@lex00
lex00 merged commit 072480e into main Sep 10, 2026
1 check passed
@lex00
lex00 deleted the feat/workbench-386 branch September 10, 2026 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

workbench: point behold at internal estates — choudoufu, terralith, terraform, chant — from a local catalog

1 participant