Terraform Policy Graph and State Migration Activity


The hashicorp/terraform main branch had 22 commits in this window, with the most useful signal sitting around policy evaluation, graph output, and state migration metadata. For data teams using Terraform to manage warehouses, queues, object stores, and schedulers, this activity is less about a release headline and more about how future automation will be checked, explained, and moved.

The largest thread is policy evaluation. The hook based policy result change moves plan and apply policy reporting through Terraform hooks, instead of pushing results through several operation specific paths. That shows up across internal/command/meta_policy.go, command views, stack runtime hooks, and Terraform core hook interfaces.

That is mostly internal refactor, but it matters for operators because policy output needs to appear in the same place regardless of whether the work starts from classic Terraform core or Stacks. If policy failures are going to gate automation, the result transport cannot depend on which command path produced the run.

There is also a behavior change in the policy evaluation for no op changes. Terraform adds a NO_OP operation to the policy protocol and forces policy nodes for unchanged managed resources when a policy client is present. That gives policy engines a chance to reject a resource that did not change in the plan but no longer satisfies the current rules. For long lived data platform resources, that is the difference between checking only edits and checking the fleet state that still matters.

Stacks policy coverage also widened. The refresh and destroy policy change removes a prior restriction that skipped policy clients outside normal plans. The test changes in internal/stacks/stackruntime/plan_test.go make the intent explicit: refresh mode reports module and provider policy results, and destroy mode reports resource policy results. That is the right direction for environments where destroy plans need the same guardrails as creates.

Terraform query is still experimental territory, but this window added a concrete policy path. The experimental policies flag for query adds -policies parsing, validation through AllowExperimentalFeatures, and policy client wiring into the query operation request. The CLI surface is visible in internal/command/query.go.

The more interesting part is not the flag. It is the new list resource policy node, which builds policy inputs from list block results. Terraform tries provider supplied config generation first, then falls back to legacy state extraction. If a list result has no state because include_resource = false, policy evaluation is skipped with a warning rather than failing every result one by one.

That behavior is cautious. Query can discover resources before they are written as normal managed resources, so policy inputs are often partial. A warning is better than pretending that an incomplete list response can support the same decision as a planned resource update.

The most visible operator feature is the Mermaid graph format change. Terraform graph now accepts -format, with dot as the default and mermaid as the new option. The work touches the argument parser, command help, resource only graph output, full operation graph rendering, and a new Mermaid renderer in the DAG package.

terraform graph -format=mermaid -type=plan

This is small but practical. DOT output is still the stable default, so existing tooling should not move. Mermaid gives teams a lower friction path for design docs, pull request notes, and runbooks where GraphViz is not already present. It also preserves the useful Terraform graph options such as cycle drawing and module depth for operation graphs.

The implementation in internal/dag/mermaid.go is intentionally simple. It emits flowchart LR, sorts nodes and edges for deterministic output, maps common DOT shapes into Mermaid node forms, and adds a CSS class for cycle edges when requested. That is enough for inspection diagrams. It is not trying to become a full diagram editor.

State migration got a more operational fix. The backend state file update for state migrate makes terraform state migrate write the saved backend state file after a successful move. The affected command path is internal/command/state_migrate.go.

Before this change, migration could copy the state to the destination while the local backend state metadata still described the old location. That is a classic automation footgun. The next command can make a decision based on stale local metadata, even though the migration itself succeeded. The new path builds a BackendStateFile for either backend or state_store, serializes the destination config, and persists it only after the migration completes.

The companion state store provider supply annotation records how the state store provider is supplied. This matters because pluggable state storage has extra provider resolution rules. A saved state location without provider supply context is not enough for later automation to reason about how Terraform got there.

There were related guardrails in init. The lock file error message update gives a more specific recovery path when -state-provider-lock-file does not contain the state store provider. The explicit upgrade false test also pins the expectation that locked provider versions are not upgraded when -upgrade=false is set. Most of that work concentrates in internal/command/init_run.go and internal/command/init.go.

First, policy evaluation is spreading across command modes. Teams testing Terraform Policy should check refresh, destroy, query, and no op cases, not only normal plan and apply.

Second, Mermaid graph output should be treated as a documentation aid until it has real use in your own graph sizes. Deterministic output is useful for reviews, but large Terraform graphs can still become unreadable in any visual format.

Third, state migration changes are worth testing in automation that uses pluggable state storage. The new metadata write is safer, but it also means failed writes to the local backend state file become part of the command outcome after migration succeeds.