Skip to content

Review checklist

This is the checklist to run down — by eye, on any diff, yours or an AI’s — after the tools in Verification Tools have run and come back clean. It fits on one printed page on purpose: a checklist nobody actually opens during a review is worthless. Every line links the entry that explains why, so this page stays a list, not a re-run of the course. The groups follow the same order as How to read Terraform you did not write, plus a final Tooling group covering how each of the other seven actually gets verified.

flowchart LR
  A["Versions &amp; providers"] --> T["validate/plan catch some<br/>(deprecation warnings, module+count errors)"]
  B["State &amp; backend"] --> P["Partial: remote_state hard-fails loudly,<br/>backend-locking drift is silent"]
  V["Variables"] --> M["Mostly human review<br/>(nullable/ephemeral behave silently)"]
  R["Resources &amp; lifecycle"] --> P2["Partial: for_each-on-unknown caught,<br/>prevent_destroy-on-wrong-resource is not"]
  Mo["Modules"] --> P
  Tg["Terragrunt live tree"] --> M
  C["CI &amp; policy"] --> P2
What actually catches each group, if anything
  • Is every required_providers constraint ~>, not >=? A floating constraint resolves to whatever the registry’s current newest release is on the day init runs. See the AI Bug Catalog, entry 1.
  • Is .terraform.lock.hcl committed — not gitignored alongside .terraform/? See entry 4.
  • Does terraform validate -json come back with zero warnings, not just zero errors? A deprecated argument (acl, managed_policy_arns) still validates and plans successfully. See entry 2.
  • Does any module declare its own provider block? It validates fine alone and only fails once a caller needs count/for_each/depends_on. See entry 3.
  • Does the backend’s locking mechanism match what this Terraform/backend version actually supports — use_lockfile (S3, stable since 1.11) rather than the now-deprecated dynamodb_table? See the AI Bug Catalog, entry 5, and The State File.
  • Does every terraform_remote_state data source point at a key/path that has actually been applied? There is no mock fallback — a missing state file hard-fails the whole plan. See entry 6.
  • Does a nullable = false variable’s default ever silently stand in for an explicit null passed from a caller, with nothing in plan/apply output calling out the substitution? See the AI Bug Catalog, entry 7.
  • Does any ephemeral variable carry a literal secret default? It still leaks into a saved plan’s show -json output even though it never reaches state. See entry 8.
  • Is count used over any list that could be reordered, where for_each is the only safe choice? Removing a middle element shifts every later index. See the AI Bug Catalog, entry 10.
  • Does lifecycle { prevent_destroy = true } sit on the resource that actually matters — checked directly, not assumed from a nearby resource’s error message? See entry 9, proven with a real plan -destroy that destroyed the unprotected database with zero resistance.
  • Does any for_each derive its keys from another managed resource’s computed attribute? A real, current “Invalid for_each argument” rejection — see entry 11.
  • Is null_resource used where terraform_data (1.4+) removes an entire extra provider dependency for equivalent behavior? See entry 12.
  • Does any argument look copied from a similar-but-different resource type (deletion_protection on aws_instance, real elsewhere but not here)? A real “Unsupported argument” rejection — see entry 13.
  • Does every non-registry source pin a revision — ?ref=<tag-or-sha> for a git:: source, an explicit version for a registry module? Without it, Terraform tracks the default branch’s HEAD, silently. See the AI Bug Catalog, entry 14.
  • Does any depends_on on a module call exist to “fix” an ordering complaint that’s actually a genuine reference cycle? No depends_on can suppress a real Error: Cycle. See entry 15.
  • Does anything reference run-all/plan-all? Both are unknown commands as of the Terragrunt 1.1 CLI redesign — a real, immediate rejection. See the AI Bug Catalog, entry 16.
  • Do two environments’ terragrunt.hcl files ever carry the identical account_id/region/subscription/project value in inputs? terragrunt hcl validate cannot catch a syntactically valid copy-paste. See entry 17, proven with a real byte-identical dev/prod diff.
  • Does -auto-approve ever run against a fresh plan, rather than a saved, reviewed plan -out file? See the AI Bug Catalog, entry 18, proven with a real side-by-side apply.
  • Is every third-party GitHub Action pinned to a full commit SHA, not a mutable tag like @master/@main? See entry 19.
  • Does any Rego policy use pre-v1 deny[msg] { ... } syntax? It fails to even load under current OPA/conftest — see entry 20, proven with a real rego_parse_error.
  • Does every .tflint.hcl/checkov rule ID actually exist in that linter’s real, current rule catalog? An invented rule name doesn’t error — it silently checks nothing. See entry 21.
  • Is a real secret ever committed in a .tfvars file, or left in terraform.tfstate as plaintext despite an output marked sensitive? See entries 22–23.
  • Does terraform validate -json | jq '.diagnostics[]' get checked for warnings, not just a nonzero exit code? See Verification Tools.
  • Does CI run a real terraform plan -detailed-exitcode and inspect the saved plan’s show -json output with a real policy tool (conftest, checkov), not just eyeball the plan text? See Verification Tools.
  • Does terragrunt hcl validate/render run in CI before run --all plan, catching syntax errors before a 10-unit tree partially applies? See Verification Tools.
Why does the 'Versions & providers' group's coverage diagram say 'validate/plan catch some' while 'Variables' and 'Terragrunt live tree' say 'mostly human review'?
Which checklist group covers reviewing this pair, and what does it depend on to catch it?
resource "aws_db_instance" "primary" {
  # ... no lifecycle block at all
}
resource "aws_iam_role" "logging_helper" {
  lifecycle { prevent_destroy = true }
}
Why does the checklist ask 'is every Rego policy using v1 syntax' under CI & policy rather than assuming conftest would just warn about old syntax?
Why does the checklist list 'does every .tflint.hcl rule ID actually exist' as its own line, rather than trusting that tflint would error on an invalid rule name?