Skip to content

Produce a completed plan with your usual Terraform or OpenTofu workflow. Rootform reads its JSON export locally. It does not plan, refresh, apply, contact providers, or fetch missing Dialects. Run rootform run from the project whose rootform.lock selects the Dialects you want; --project chooses another project directory explicitly. Evaluate selected Policy Packs separately with rootform check.

Protect the plan files

Produce the accepted JSON

Rootform accepts completed plan and state exports in JSON format 1.x. This is the export format, not the Terraform/OpenTofu or provider version. Other major formats are refused. Each JSON or saved Form input is limited to 128 MiB.

Save the plan and export that same saved plan. OpenTofu users use tofu where Terraform users use terraform:

terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
Shell

Use show -json on the saved plan. plan -json is a planning event stream, not the completed export Rootform accepts. Preserve the binary plan for verification if you need expression evidence. Where state already exists, export it with terraform show -json > state.json, or tofu show -json > state.json. A new working directory has no state to export.

Pair the saved plan

Pass the plan JSON export and its saved plan:

rootform run plan.json --plan-file plan.tfplan --require-enrichment --no-serve -o analysis.json
Shell
Paired saved plan excerpt OUTPUT
Plan analyzed
Input plan.json
Plan completeness Complete, as reported in the plan
Enrichment Saved plan paired with this plan JSON (1 module)
Wrote analysis.json

Enrichment confirms that the saved plan and the JSON export agree on the recorded tool version, timestamp, and configuration shape. These checks decide whether the saved plan may enrich this export; they do not validate the plan in general, and they do not prove one planning operation. The count is the number of configuration modules read from the saved plan. Input names the export. Producer appears only when the tool is established, as Terraform or OpenTofu, without a version. The JSON's shared terraform_version field and provider registry hosts do not identify the tool; --producer records your explicit attestation. The Form retains the reported version, hints and attestations. Plan completeness repeats what the plan reports about itself; it does not say that the analysis settled every fact.

A paired saved plan lets Rootform inspect direct identity traversals in the saved configuration, even when an endpoint's evaluated ID is unknown until apply. It does not execute the configuration. The Form records enrichment.snapshot.status as verified when the pair passes these checks, refused, or absent.

If the pair is mismatched or unreadable, Rootform reports the refusal on standard error. Without --require-enrichment, analysis continues from JSON alone and records refused; the missing traversal evidence can leave closures indeterminate. With --require-enrichment, refusal exits 3. Export the JSON from the saved plan you pass, rather than trying to pair a new plan with an earlier export.

Compare both sides of one plan

A plan's planned stage shows proposed instances. refreshed describes the state the plan starts from; the plan does not record whether or how far refresh ran. recorded is reconstructed from drift records and can be partial. The same plan may report three comparisons: Reported drift (recorded to refreshed), Planned changes (refreshed to planned), and Net change (recorded to planned). A state export has only recorded.

In accepted plan format 1.x, Terraform and OpenTofu omit prior_state exactly when the state before the plan has no resource and no root output. Rootform records an empty Refreshed stage and reconstructs Recorded from drift records. Recorded can contain a resource deleted during refresh even when Refreshed is empty. Terraform 1.12.2 and OpenTofu 1.10.7 exports are qualified; other 1.x exports are accepted by shape.

The Recorded to Refreshed comparison is Reported drift. The separate drift report lists the producer's drift records and their architectural consequences. What does this plan change? shows where the Explorer lists these views.

Read plan comparisons correctly

-refresh=false prevents Terraform or OpenTofu from checking live objects. “No drift reported in this plan” means the export contains no drift records; it does not prove that infrastructure is unchanged. -target or -exclude can leave instances outside the plan's scope. Terraform may report complete: false for such plans, while OpenTofu may omit a completeness field. Rootform preserves that uncertainty. A missing planned instance is not automatically a deletion or proof of zero instances. An instance present before the plan that it neither changes nor deletes remains in Planned with status carried; its population is unverified and the report says the plan did not evaluate it. When a fact cannot be settled on both sides, the comparison records it as indeterminate rather than inventing an addition, removal, or no change. See stages and facts and comparisons and drift.

Record scope and tool claims

--plan-complete=attested records your explicit claim that the plan covers its intended scope when the export does not establish completeness. It is not inferred from absent entries. --producer terraform or --producer opentofu records the tool you used; the shared terraform_version JSON field alone does not establish that identity. --provider-map observed=binding makes an explicit provider registry mapping when a Dialect binding needs it. These options record your claims, not facts independently verified by Rootform. Do not use them to hide a targeted or partial plan.

Interpret unknown and sensitive evidence

A planned value can be known, unknown until apply, sensitive, or unavailable. A verified direct traversal can identify an endpoint despite an unknown ID; a transformed expression or dependency list alone cannot. Sensitive values are discarded before Form output, reports, SARIF, and the Explorer. Dialect-declared external identities may still be disclosed at their declared tier. Read limitations before relying on a missing fact as a negative conclusion.

Stream the export

To avoid an intermediate JSON file, pipe the saved-plan export into Rootform. The binary saved plan still needs protection:

terraform show -json plan.tfplan | rootform run - --plan-file plan.tfplan --no-serve
Shell

For OpenTofu, replace terraform with tofu. The saved plan remains local. Rootform's outputs still describe infrastructure names, structure, and relationships, so apply your internal sharing rules.

To compare two plans, or a state Form with a later plan, follow Compare two Forms. For plans from two Git revisions, Review a pull request adds isolated checkouts and cleanup. To review a completed plan in automation, see Other CI/CD or GitHub.