Skip to content

Look at a Terraform estate

llms.txtlists every page for an agent
Optional: hand this page to your coding agentThe steps work by hand too.
Show the whole prompt
Read https://intentius.io/behold/using/terraform/.
Serve this repository's Terraform with `npx @intentius/behold serve <dir>` and tell me the URL.
List the roots it found and each directory it skipped, and why.
Never deploy with the Deploy button, `POST /api/apply` or `POST /api/ci/dispatch`. Gate approvals stay mine as well (`/api/ops/:name/signal/:gate`, `/api/workspace/gates/approve`, `/api/operator/approve/:op/:gate`). Op runs (`/api/ops/:name/run`) and rollbacks (`/api/rollback`) are mine too. Leave `--auto-sync` off, and keep a `behold preview` or `--public` serve read-only.

In a folder of .tf files, one command draws it:

Terminal window
npx @intentius/behold serve .

Nothing else is installed or run first. behold draws the folder the way it draws a chant project: roots as boxes, resources as cards, and the references the configuration actually states as edges. No cloud account, no Docker, no terraform binary. To see the bundled estate instead of your own:

Terminal window
npx @intentius/behold demo terraform-estate

This is the posture rather than an implementation detail. @intentius/chant-lexicon-terraform reads the HCL an estate already has and emits one entity per block, so a Terraform estate is a chant project whose only lexicon is a reader, and chant graph serves it like any other. behold ships no HCL parser and never has.

The lexicon and @cdktn/hcl2json, the ~1.8 MB wasm HCL parser chant loads when it reads a root, are dependencies of behold, so npx installs them with it. They add about 8 MB to behold’s install (#909).

behold still checks for both before it serves a Terraform folder. chant itself warns, exits 0 and prints an empty graph when the parser is missing, so an install that skipped or pruned them (npm install --omit, a trimmed container image) is refused instead, with where behold looked and one install line:

Terminal window
npm install @intentius/chant-lexicon-terraform@^0.124.58 @cdktn/hcl2json@^0.24.0

The lexicon’s minor has to be behold’s chant’s (0.124): each lexicon release asks for its own chant minor as a peer, so an older one puts a second chant in the tree and the lexicon where behold’s reader does not look.

Roots are discovered, and the skips are reported

Section titled “Roots are discovered, and the skips are reported”

A root is a directory with a .tf declaring a line-start terraform { or provider " block beside a resource, data or module block. A regex probe at depth, never a parse.

Two exclusions stand beside it, and both are named in the graph’s note with their reason:

  • a directory under a modules/ segment is a called module, and the roots that call it draw its blocks already;
  • a directory with nothing to draw, such as a lone backend fragment, is reported rather than drawn as an empty box.

The probe alone is not enough, and the bundled estate is built to show why: the shared module’s versions.tf is byte-for-byte a root’s. Nothing in the file says which is which. Water park’s real estate is what refuted the first version of this rule.

Measured on a real estate: 247 nodes for 43 resources, four fifths of it not infrastructure.

zoom blocks drawn why
default (detail 0-2) resource, data, module the estate: what is declared, what it reads, what it composes
attributes (detail 3) + output, variable its interface: real, and a second question
never terraform, provider, locals settings, not estate

Nothing is dropped silently. The status line says what is not drawn and where to see it.

Edges arrive from chant or not at all.

A module call expands into its own contents, with the reference edges the lexicon resolves from the HCL. Those are real: one block names another.

A cross-root read is different, and behold draws nothing for it. In the bundled estate platform declares a KMS key and envs/prod reads it back:

data "aws_kms_key" "estate" {
key_id = "alias/${var.name}-estate"
}

Both ends carry the same unresolved ${var.name} interpolation. Matching them would be a coincidence of variable naming rather than a relationship either root states, so the data source card says what it reads and no edge is drawn. That was measured on a real estate before it was refused.

behold serve <terraform-dir> reads source. There is no --env, no drift colour, and no overlay: behold strips live, overlay and env for a Terraform member, because a reader lexicon would be asking chant to observe entities it never applied.

The live cloud has two other readers here:

  • a choudoufu estate reads ownership from markers on the live resources, and a green card there means this is ours;
  • a committed Op observes whatever its own lexicon observes.

To see live ownership on these same roots, run them with choudoufu, which reads your existing .tf files and marks each resource it manages. terragucci runs them in your CI, and behold reads its reports.

One read is about the cloud and still belongs here, because chant makes it from the root’s own state and providers: the plan. Press ⌘K and pick Read the plan on the Terraform roots (or GET /api/graph?plan=1), and behold asks its own chant to read each stock root at the plan depth chant’s terraform lexicon adds in 0.124.46 (chant#4358):

Terminal window
CHANT_TERRAFORM_OBSERVE=plan chant graph . --live --overlay --env behold-plan --format ir --detail 3

That runs terraform plan -input=false -lock=false -out=<tmp>/plan.tfplan and terraform show -json over the file, in each root, with the plan file under the OS temp directory and removed afterwards. -lock=false means the read never takes the backend’s state lock. Nothing is written in the root, and init is never run: a root that has not been initialised is refused with the line to run.

Each card then leads with what the plan says about it:

row what it says
plan what the next apply does: replace, update, delete, or no changes
forced by the attribute that forces a replace, with its value before and after
from where that value comes from in the configuration, such as var.generation
read which read answered: plan, or the fallback below

When a root cannot be planned, for missing provider credentials or any other provider error, chant falls back to the state read, and the card says plan: not read and read: state, no provider credentials. A card is never shown as “no changes” unless the plan itself said so. A root whose backend cannot be reached is declared only.

Values the plan marks sensitive, and any copy of one, arrive as (sensitive). Only the plan’s facts are added to a card; the plan document never leaves chant.

GET /api/terraform/plan gives the same facts per card as JSON for an agent: the action, the replace reason and paths, where each forcing value comes from, the values unknown until apply, the changes and the drift.

A chant or lexicon older than 0.124.46 is refused with the version to upgrade to, and behold doctor lists the floor. A choudoufu member keeps its own plan read (see choudoufu): chant’s plan depth covers stock roots, and a live root keeps its live-plan -json read (chant#4371).

Both involve Terraform and they answer different questions.

This asks what is in your Terraform: draw it, box it by root, tell the roots from the modules.

The carve lens asks which resources chant could also manage: chant carve advise scores each resource, and behold carve draws that ranking with the score arithmetic behind each card.

This view draws your Terraform as it is, with no scores.

behold