Child of #378. The last behold-side piece of the Terraform lane, and the one #378 chose but did not build.
What does not work
#383 made a Terraform estate legible — cards titled by resource type, roots as boxes, the zoom decision — but only for a project that already declares the lexicon. Point behold at a real Terraform directory and it refuses:
$ behold doctor access/envs/prod
fail project no chant.config.ts here, and no estate members declared
fix Point behold at a chant project, or run `behold demo`
That is the correct answer today and the wrong answer for the lane. Every estate this exists to draw is a directory of .tf files and nothing else.
What #378 decided
When INTENTIUS/waterpark#88 was withdrawn — the estate is more useful untouched — the epic picked the other path and recorded it:
behold generates the reader config in a scratch directory at serve time, so an estate is browsable without a file in the estate's own repo.
So: behold serve <terraform-dir> writes a chant.config.ts naming the roots into a scratch directory, points chant at it, and renders what comes back. Nothing is written in the estate.
The three things that decide the shape
1. Where the lexicon comes from. chant resolves @intentius/chant-lexicon-terraform from the config file's own location, so the scratch project needs it resolvable. behold does not depend on it today (@intentius/chant, @intentius/chant-k8s-client, @intentius/pinhole, hono). Options, and this is the real decision:
- A dependency. Simplest to use, and it puts an HCL parser (
@cdktf/hcl2json, a ~1.8 MB wasm blob, itself an optional peer of the lexicon) in behold's install for every user, most of whom serve chant projects. Weigh against carve-lens.ts's posture — behold has kept Terraform tooling out of its tree on purpose.
- An optional dependency, probed like a demo's
requires and refused politely with an install line when absent. Matches how behold demo already gates on binaries it does not ship, and how the choudoufu member refuses when its binary is missing.
- Resolve from the estate, if the repo happens to have it. Zero install for the repos that already use chant, nothing for the ones that do not — which is every repo this feature is for.
My read is the optional dependency, but it is a real trade and the issue exists to settle it.
2. Which directories are roots. chant carve advise --from <parent> over the parent of six root modules returns zero resources — the parse is one root at a time, and the lexicon's config names roots explicitly. So behold has to discover them. A root is a directory holding .tf files; a modules/ directory that is only ever called by source = "../../modules/x" is not a root of its own, and including it would draw the module's internals twice. Measured on a real estate: six directories hold .tf, one of them (modules/persona) is a shared module called by three others.
Cheap discovery that is probably right: directories with at least one .tf declaring a terraform {} or provider block, which is what a root has and a called module does not. Behold parses no HCL, so this is the same regex-depth probe the choudoufu member kind uses for its live { block.
3. Member kind, or not. #378 says there is no terraform member kind, and means it about reading: behold parses no HCL and the render goes through chant. A kind whose read shells chant graph against a generated config does not contradict that — it is a scaffold, not a second reader — and it would inherit the probe, the cache stamp, the doctor line and the estate composition for free (#368). The alternative is a serve-time special case outside the member system.
Worth deciding deliberately rather than by whichever is quicker, because the answer decides whether a Terraform root can sit in a composed estate beside a chant project and a choudoufu estate.
Definition of done
behold serve <dir> on a directory of .tf files renders it, with no file written anywhere under <dir>.
- The scratch directory follows the write boundary: behold's only in-project write stays
.behold/layout.json (AGENTS.md, "Invariant"). A generated config lives outside the estate, is behold's own, and is cleaned up or reused deterministically.
- Root discovery reports what it found and what it skipped, so a missing root is visible rather than silently absent.
- A
behold doctor line for the lexicon: present, absent with an install line, or resolved from the estate.
- The refusal when the lexicon is unavailable names the one command that fixes it, the way chant's own carve refusal does ("Terraform carve-out needs the HCL parser, which is not installed. Install it once: npm install -D @cdktf/hcl2json").
- AGENTS.md's "Rendering a Terraform estate" section gains the serve path beside the passes.
Acceptance
behold serve on a copy of a real multi-root estate draws one box per root with the cards #383 already produces, and the estate directory is byte-identical afterwards.
Note
Edges arrive separately: chant#2265 is in progress, and when it lands this same path renders them with no change here.
🤖 Claude Code — https://claude.ai/code/session_0124XP1LJTZxgJRy7Dh1UfXt
Child of #378. The last behold-side piece of the Terraform lane, and the one #378 chose but did not build.
What does not work
#383 made a Terraform estate legible — cards titled by resource type, roots as boxes, the zoom decision — but only for a project that already declares the lexicon. Point behold at a real Terraform directory and it refuses:
That is the correct answer today and the wrong answer for the lane. Every estate this exists to draw is a directory of
.tffiles and nothing else.What #378 decided
When INTENTIUS/waterpark#88 was withdrawn — the estate is more useful untouched — the epic picked the other path and recorded it:
So:
behold serve <terraform-dir>writes achant.config.tsnaming the roots into a scratch directory, points chant at it, and renders what comes back. Nothing is written in the estate.The three things that decide the shape
1. Where the lexicon comes from. chant resolves
@intentius/chant-lexicon-terraformfrom the config file's own location, so the scratch project needs it resolvable. behold does not depend on it today (@intentius/chant,@intentius/chant-k8s-client,@intentius/pinhole,hono). Options, and this is the real decision:@cdktf/hcl2json, a ~1.8 MB wasm blob, itself an optional peer of the lexicon) in behold's install for every user, most of whom serve chant projects. Weigh againstcarve-lens.ts's posture — behold has kept Terraform tooling out of its tree on purpose.requiresand refused politely with an install line when absent. Matches howbehold demoalready gates on binaries it does not ship, and how the choudoufu member refuses when its binary is missing.My read is the optional dependency, but it is a real trade and the issue exists to settle it.
2. Which directories are roots.
chant carve advise --from <parent>over the parent of six root modules returns zero resources — the parse is one root at a time, and the lexicon's config names roots explicitly. So behold has to discover them. A root is a directory holding.tffiles; amodules/directory that is only ever called bysource = "../../modules/x"is not a root of its own, and including it would draw the module's internals twice. Measured on a real estate: six directories hold.tf, one of them (modules/persona) is a shared module called by three others.Cheap discovery that is probably right: directories with at least one
.tfdeclaring aterraform {}orproviderblock, which is what a root has and a called module does not. Behold parses no HCL, so this is the same regex-depth probe the choudoufu member kind uses for itslive {block.3. Member kind, or not. #378 says there is no
terraformmember kind, and means it about reading: behold parses no HCL and the render goes through chant. A kind whosereadshellschant graphagainst a generated config does not contradict that — it is a scaffold, not a second reader — and it would inherit the probe, the cache stamp, the doctor line and the estate composition for free (#368). The alternative is a serve-time special case outside the member system.Worth deciding deliberately rather than by whichever is quicker, because the answer decides whether a Terraform root can sit in a composed estate beside a chant project and a choudoufu estate.
Definition of done
behold serve <dir>on a directory of.tffiles renders it, with no file written anywhere under<dir>..behold/layout.json(AGENTS.md, "Invariant"). A generated config lives outside the estate, is behold's own, and is cleaned up or reused deterministically.behold doctorline for the lexicon: present, absent with an install line, or resolved from the estate.Acceptance
behold serveon a copy of a real multi-root estate draws one box per root with the cards #383 already produces, and the estate directory is byte-identical afterwards.Note
Edges arrive separately: chant#2265 is in progress, and when it lands this same path renders them with no change here.
🤖 Claude Code — https://claude.ai/code/session_0124XP1LJTZxgJRy7Dh1UfXt