Skip to content
Sarj Code Standards

no-terraform-guard-blocks

Ban Terraform validation, precondition, postcondition, and check blocks.

Why

The team keeps infrastructure declarations concise and reuses native provider controls and existing shared policy. Guard blocks can add a second policy layer to those declarations. This is an explicit design policy, not a claim that Terraform conditions are invalid or always redundant.

Fix

First identify the existing native or shared owner of the invariant. Do not replace a guard with a bespoke verifier, source-text test, repeated API audit, or deployment wrapper merely to satisfy this rule. Keep investigation and rollout readbacks one-off unless they close a demonstrated recurring enforcement gap. Preserve existing deployment safety invariants. Use reviewed immutable guard contracts when removing a guard would change deployment behavior. For other guards, move an invariant only after proving equivalent enforcement phase and behavior. Preserve Terraform types, provider validation, deletion protection and lifecycle settings. Review the behavior before removing a guard; no automatic deletion is provided.

Examples

Before — flagged Keep deployment assertions outside infrastructure declarations
main.tf
check "ready" {
assert {
condition = var.ready
error_message = "Require readiness."
}
}
After — preferred Declare an output without a deployment assertion
main.tf
output "ready" { value = var.ready }
Before — flagged Move data-source postconditions to their owning policy
main.tf
data "example_image" "selected" {
lifecycle {
postcondition {
condition = self.approved
error_message = "Require an approved image."
}
}
}
After — preferred Read the selected image without an embedded policy block
main.tf
data "example_image" "selected" { name = var.image }
Before — flagged Move custom input policy out of variable declarations
main.tf
variable "replicas" {
type = number
validation {
condition = var.replicas > 0
error_message = "Require a positive replica count."
}
}
After — preferred Keep typed declarations and native lifecycle protection
main.tf
variable "replicas" { type = number }
resource "example_service" "app" {
count = var.enabled ? 1 : 0
replicas = var.replicas
deletion_protection = true
lifecycle { prevent_destroy = true }
}
Before — flagged Review resource preconditions with their existing enforcement owner
main.tf
resource "example_service" "app" {
lifecycle {
precondition {
condition = var.ready
error_message = "Require readiness."
}
}
}
After — preferred Keep native resource lifecycle protection
main.tf
resource "example_service" "app" {
lifecycle { prevent_destroy = true }
}