Skip to content

Report JSON schema

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/terragucci/reference/report-schema/.
Write a jq script that reads terragucci-report/report.json and prints every destroy and replacement by address, with its root and the wave that applies it.
Read only. Never apply, approve (a pull request review or `terragucci approve`), override a policy denial (`terragucci override`), use `--mode apply`, or merge; never touch `.chant/allowed_signers` or `chant/lifecycle`.

report.json sits beside every report.html, which also carries it inline:

Terminal window
sed -n '/id="terragucci-report"/,/<\/script>/p' report.html | sed '1d;$d' | jq '.named[] | select(.action == "delete") | .address'
Field Holds
schema terragucci.report/v1; a minor version only adds fields
minor the minor version of the schema the report was written with
run project, commit, base, stage, binary, runtime, start and finish times, and the job
run.wave, run.terragucci the wave a tf-apply report is for, and the terragucci version that wrote the report
run.share on a tf-apply wave split across jobs (waves.jobs), the share the report applied; its waves[0].roots are the share’s roots
run.commit_url, run.pull_request, run.pull_request_url the commit’s page, and the pull or merge request the run planned with its page
run.report_url where this report.html is served from the bucket, when reports.url is set
run.trace_id, run.trace_url the run’s trace, when the stage sent one, and its link when telemetry.trace_url is set
change_set the set digest over every root’s plan digest, the digest an approval names
unit, units what the groups count: member or instance, and how many
groups[] a stable id per normalized change, its roots and the change
totals the run’s changes by action, over the roots whose plan can apply; a root the policy denied is left out, though its changes stay in the report. GitLab’s reports:terraform counts come from it
roots[] path, plan digest, counts by action, its group, its changes and why it is open
waves[] one entry per wave; its fields are below
named[] every destroy, replacement and refusal by address, and every import and forget apart from them
holes[] a resource instance the report could not read a change for, with its root, address and the reason; always present, and empty when nothing is missing
roots[].plan paths to the root’s full plan text and JSON, and the job that ran it
roots[].binary the binary a root or Terragrunt unit ran: name (tofu, terraform or choudoufu), version, and pin, where the root pinned that version (.opentofu-version, .terraform-version, required_version or terragucci.yml version <glob>); pin is absent for a root that runs the job’s binary unpinned; for a unit, terragrunt with the Terragrunt version that ran it and pin: terragrunt_version_constraint when the unit pinned it (minor 24)
deferred[] Terragrunt units planned once the units they wait for apply, and what each waits for
mock_reads[] Terragrunt dependencies that would have read mock_outputs, with the upstream and the reason
roots[].terragrunt for a Terragrunt unit: its stack (the directory of the explicit stack that generates it, else its parent directory), stack_file, the terragrunt.stack.hcl that generates it (minor 29), why it was selected, whether its plan is a provisional preview, and its result in Terragrunt’s run report
intent the description check’s decision: status, whether it flagged the pull request, the decision, probability and threshold, model, state_digest and the destroys and replacements the text leaves out (unmentioned); present only after respond description ran on the report
cost with cost set, on a tf-plan run or a tf-apply wave: the estimator, the currency, the monthly change and the totals before and after over the roots estimated, and per root (roots[]) the same three figures, the path of the estimator’s output (output) or why there is no estimate (error)
policy the policy check, when policy is on: engine, input mode, namespace, whether the policy came from the checkout or the base branch, the roots it denied and the warning count; with policy.override at base, overriders and the denied roots an override stands for, overridden
roots[].policy the policy’s verdict on the root’s plan: passed, denied or error, the denial messages, the ids of the rules that denied (rules), the warnings and, for error, why it could not run; a denied root is failed and keeps its changes
roots[].policy.override the override that stands for the root’s plan and rules: by, at, rules, reason, plan_digest, the ledger line’s digest, and sealed; on a tf-apply wave the root then applies and is planned
redaction the marker that replaced sensitive values, and how many it replaced
tips[] advice, each with the rule that produced it; absent with tips: false
timings the run’s roots or Terragrunt units, slowest first, and its slowest resource instances across roots
roots[].resources on a tf-apply wave, for a root that applied or had nothing to apply: every managed resource it holds afterwards, from the plan’s planned values, each with address, type and provider; never a value
roots[].applied_changes on a tf-apply wave, for a root that applied: what the apply did to each resource (actions: create, update, replace, delete, import, move or forget), the top-level attributes an update or a replacement changed, by name, and previous_address for a move; never a value
roots[].applied_changes[].record_versions for a choudoufu root: the record’s versions after the apply, from choudoufu live-history: kept, store, versions (version_id, last_modified, current, deleted), error when they could not be listed, and read (minor 32)
roots[].state on a tf-apply wave, for a root that applied or had nothing to apply: the state its backend holds afterwards, read from the object’s metadata and never its contents: backend, location (s3://<bucket>/<key>, gs://<bucket>/<key>, az://<account>/<container>/<key> or the local file), version_id (S3’s version id, a GCS generation, an Azure version id or snapshot), versioning (on; off when the backend keeps no history, as local, pg, kubernetes, consul and a plain http backend do; unknown when it may keep versions terragucci does not read) and a note saying why there is no version
roots[].dependencies a Terragrunt unit’s dependency and dependencies blocks: the units whose outputs it reads, as plain paths in its terragrunt.hcl; for a plain root, the roots waves.after puts before it; absent when it names none
roots[].steps the steps that ran for the root, in order: name, when (such as before-plan), status (passed; failed, which failed the root; approval, a failed on_failure: approve step that holds the wave), exit and seconds; the steps’ output stays in the job log
roots[].reads the roots whose state the root reads through terraform_remote_state, or a Terragrunt unit’s upstreams whose planned outputs it read through its dependency blocks: upstream, the block’s label (data), and outputs: planned, the upstream’s plan in the same run, with unknown naming the outputs known only once it applies, or applied, its state as it stands, with why in a pull request’s plan
roots[].unknown_reads the root’s terraform_remote_state blocks whose address is not plain strings in the code: data, the block’s label, and why; no edge orders the root after the root that writes that state (minor 30)
roots[].unaddressed when a root of the run reads state through terraform_remote_state: why the root’s own state has no address, so a reader of it is not ordered after it (minor 30)
blast on a tf-plan run in which a root’s plan changes something: roots, the roots whose plan changes a resource or an output, and downstream, nearest first, each root that reads the state of a root in the radius through terraform_remote_state or that waves.after puts after one (in a Terragrunt repo, each unit whose dependency or dependencies block names a unit in the radius), with the roots it reads there (reads), depth (1 when it reads a changed root itself), its wave, and whether the run planned it
blast.resources when a root is downstream, for plain roots: each resource a plan changes (root, address, actions) and reaches, nearest first, each resource that depends on it with its root, its configuration address, and in another root through, the output it reads and the root that makes it (minor 31)
waves[].held_by_steps the roots whose on_failure: approve step failed, so the gate holds the wave when it changes anything, whatever gate says
roots[].timings the root’s wall time, its plan’s and, on a tf-apply wave, its apply’s (apply_seconds); the slowest resources, provider calls, provider start-up and lock waits from the binary’s spans; summed spans of a large estate; source: terragrunt when the times come from Terragrunt’s run report; and a note when the binary sent nothing per resource
Field On Holds
number, roots, set_digest every wave the wave’s number, its roots and the set digest
approval every wave waiting, approved or not-required on a tf-apply wave; not-requested on a plan
gate a gated wave the ledger’s branch and path
waiting_since a waiting wave when it began waiting for an approval of this digest
refused a tf-apply wave that planned but applied nothing the reason (approval, review or override when its plans changed after one, policy when the policy denied a root), the digest approved and by whom, and the roots that moved or were denied
review a waiting tf-apply wave under approval: pr-review the pull_request whose approving review of its head would approve the wave, and the url to review it on
review_digest a plan’s wave the set digest over the roots whose plan changes something, which approval: pr-review binds a review to
waits a plan’s wave whether the gate or cost.approve_above will hold it
state every wave planned on a plan; on a tf-apply wave waiting, applying (its share jobs apply), applied, refused or failed
reads a wave whose roots read other roots’ state the waves those roots are in
replans_after a plan’s wave that read outputs known only once these waves apply it plans again after they apply and waits for an approval of that plan; its review_digest is null
preview a tf-apply wave of Terragrunt units the merged pull request previewed (minor 25) the pull_request, and per previewed unit (units[]) its differences from the preview, absent when it plans as previewed
cost a wave, with cost set the wave’s monthly change and totals over the roots it estimated, the roots it could not estimate (unestimated), and with cost.approve_above at base the amount (approve_above) and whether the change is over it

Its JSON Schema is @intentius/terragucci/report.schema.json in the package. The reports bucket lists where each object lives and the schema of each.

To find Read
every destroy, replacement and refusal named[], filtered on action
the roots a group folds groups[].units, matched to roots[].path
the digest an approval binds waves[].set_digest of a tf-apply wave, over the roots[].plan_digest of each root whose plan changes something and, with cost.approve_above at base, the amount, the currency and the wave’s monthly change; a plan’s waves carry the same digest as review_digest
the digest a pull request review binds waves[].review_digest in the plan report, compared with the same digest the wave plans after the merge
why a root is shown open roots[].why
the full plan of a root roots[].plan
the slowest resources of a run timings.resources, then roots[].timings.resources
how long a root waited for its state lock roots[].timings.lock_waits, with the attempts it took
what the policy denied or warned about in a root roots[].policy
who overrode a denial, and why roots[].policy.override
the binary and version each root ran roots[].binary
the trace of the run, to search your tracing backend run.trace_id

Approvals stay on your repo’s chant/lifecycle branch. The report names each record’s branch and path and never copies it.

Every index.json in the bucket is terragucci.report-index/v1 (JSON Schema dist/report-index.schema.json), one row per run, newest first.

Row field What it holds
project, commit, stage, wave, finished, path the run, and its directory relative to the index
share the share of a tf-apply wave split across jobs; each share has its own row
roots, groups, totals, refused the report’s counts
failed roots that failed to plan or apply
changed roots with a change; on a tf-drift row, the roots that drifted
wave_digests on a tf-plan row, each wave’s set digest and review digest: the digests an apply of the same plans binds
drifted_roots on a tf-drift row, up to 50 of the roots that drifted
drift_since on a tf-drift row that found drift, when the project’s open drift was first found
drift_cleared on a tf-drift row that found none after one that found some: since, when that drift was first found, and its roots; a later check of the same commit replaces the row that found it, so this row keeps it
approval, waiting_since a tf-apply wave’s gate, and when a waiting wave began waiting
applied when a tf-apply wave finished applying
overridden roots the policy denied that a recorded override let through; absent when none
destroys, destroys_total up to 50 destroys and replacements, and how many there are when the row lists fewer
commit_url, pull_request, pull_request_url, job_url, trace_url links

From those rows and each project’s newest run view, terragucci estate builds estate.json, schema terragucci.estate/v1 (dist/estate.schema.json):

Field What it holds
generated when the page was built; every age_seconds is as of then
totals projects, waiting waves, drifted projects and roots, failed roots, unreadable indexes, overridden_roots when an override let a root through, and resources when a project has an inventory
projects[] each project’s latest plan, latest drift check, the waves of its newest applied commit, its waiting waves with age_seconds, status (ok, no-index or error), inventory: its resource count, the count of each type (types) and each root’s resources with the wave that recorded them (roots), states: each root’s state location, versioning and the version ids its applies left, newest first, each with its commit, wave and report, edges: each root that reads another’s state (consumer, producer, via), with the consumer’s last plan (consumer_planned) and the producer’s last apply that changed it (producer_applied), each a run’s stage, commit, finished, wave, pull_request, version_id and report, and a status: stale when the producer applied after that plan, current, or unknown, run_view: the commit of the run view the graph read, when a wave last wrote it (updated) and its page, and ephemeral: each live ephemeral environment, with its pull_request, its roots and each one’s state location, the commit it applied from, when it expires, approved_by, and status (live, or destroy-failed until the sweep destroys it)
recent[] the 20 newest runs across every project
audit the audit trail beside the page: page, entries and generated, when terragucci audit wrote one
history the resource history beside the page: page, how many addresses it holds (resources) and generated, once an apply changed a resource; each listed resource with a history links its section as history
graph the dependency graph, once a project has a run view: nodes, each root by project, root and wave, and edges, each from a root to a root that reads its state through terraform_remote_state, or a Terragrunt unit to a unit that depends on it; an edge between two projects matched a read of a state outside the reader’s project to the root of the other whose backend holds it
dora the delivery metrics beside the page: file (dora.json), generated, and the estate’s applied waves in their window as deployments

A project’s inventory.json is terragucci.inventory/v1 (dist/inventory.schema.json). A tf-apply wave’s upload replaces the list of each root it applied, unless the file holds a newer one.

Field What it holds
roots[] by root path: root, the commit, wave and finished time of the wave that recorded the list, the wave’s directory as path, relative to the project’s index.json, and resources, each with address, type and provider

Its changes.json, terragucci.changes/v1 (dist/changes.schema.json), has one row per resource each applied tf-apply wave changed, newest first, up to 20,000 rows. A rerun of the same wave replaces its rows.

Row field What it holds
address, type, actions, attributes, previous_address what the wave did to the resource, as in roots[].applied_changes
root, commit, wave, finished, path the root and the wave, and the wave’s directory relative to the project’s index.json
plan_digest, set_digest the root’s plan digest, and the wave’s set digest, which its approval binds
pull_request the pull or merge request the wave applied

terragucci estate also builds history.json beside the page, terragucci.history/v1 (dist/history.schema.json), from every project’s changes.json and the audit trail.

Field What it holds
generated when it was built
audit whether audit.jsonl was read for the approvers
resources[] each address by project and root, with its type, its id (the anchor on history.html) and applies, oldest first: the row’s actions, attributes, commit, wave, time and digests, the report link, and approver from the wave’s apply entry in the audit trail, with that entry’s approval id; approver is null when no gate held the wave, and absent when the audit trail has no entry for it

A project’s states.json holds the version ids its roots’ applies left (never a state’s contents); its schema is terragucci.state-versions/v1, checked by dist/state-versions.schema.json. When a wave uploads its report, every root it applied adds its version, unless the root already lists that version. A root keeps its newest 20.

Field What it holds
roots[].root, backend, location the root, its backend type, and s3://<bucket>/<key> or the local file
roots[].versioning, note on, off (the backend keeps no history) or unknown (it may keep versions terragucci does not read) as the newest apply found it, and why there is no version
roots[].checked when that apply finished
roots[].versions[] newest first: version_id, the commit, wave and finished time of the wave that recorded it, and its directory as path

A project’s edges.json holds, for each root, the roots whose state it reads and the runs that planned and applied it, as root paths and run facts with no state contents or output values. Its schema is terragucci.state-edges/v1, checked by dist/state-edges.schema.json. An upload writes it when a root of the report reads another’s state, or when a tf-apply wave changed a root.

Field What it holds
roots[].root the root
roots[].reads[] the roots whose state it reads, root and via (terraform_remote_state from the report’s roots[].reads, dependency from roots[].dependencies), as the newest plan or apply of the default branch found them; a pull request’s code and a drift check never set them
roots[].reads_seen when that run finished
roots[].planned the newest run that planned the root (a pull request’s plan, a drift check or an apply wave’s plan; never a Terragrunt preview): stage, commit, finished, wave, pull_request and its directory as path
roots[].applied the newest tf-apply wave that changed one of its resources, the same fields and the state version_id it left

terragucci

These docs count page views and clicks with PostHog. They set no cookies, store nothing in your browser, and send nothing when your browser asks not to be tracked.