autojanet/skills/terrashark/references/identity-churn.md
Zoë cfec11bb46
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
fix: convert skill submodules to plain directories
stop-slop, taste-skill, terrashark had embedded .git dirs causing
Woodpecker clone to fail on submodule update.
2026-05-30 15:44:44 -07:00

3.8 KiB

Identity Churn

Use this guide when resource addresses or object identity can shift unexpectedly.

Symptoms

  • plan shows broad replace actions after small list edits
  • renaming resources/modules triggers destroy/create
  • refactor from count to for_each causes churn
  • imported resources keep drifting because addressing is unstable

Primary causes

  • index-based identity (count) used for long-lived logical objects
  • keys derived from unstable data (sorted lists, transient IDs)
  • missing moved blocks during refactor
  • hidden dependencies forcing replacement chains
  • for_each keys derived from values unknown at plan time

Prevention rules

  • use for_each for long-lived identities
  • choose stable keys from business identity (e.g., zone-a, payments-api)
  • keep identity attributes separate from mutable attributes
  • add moved blocks before first apply after rename/restructure

Decision matrix: count vs for_each

Use count only when:

  • resource is truly optional singleton (0 or 1)
  • no downstream references depend on stable per-item addresses

Use for_each when:

  • multiple logical instances are expected
  • insertion/removal/reordering happens over time
  • downstream references need stable keys
  • keys are fully known during planning

If keys are unknown at plan time, for_each will fail planning. In that case:

  • drive for_each from known input keys, or
  • use count for conditional/singleton creation when key-stable for_each is not possible

Safe migration playbook (count -> for_each)

  1. define stable key map
  2. refactor resource to for_each
  3. add one moved block per old index
  4. verify plan reports move operations, not replace
  5. apply in lower environment first

Example:

locals {
  app_subnets = {
    a = { cidr = "10.40.1.0/24", az = "us-east-1a" }
    b = { cidr = "10.40.2.0/24", az = "us-east-1b" }
  }
}

resource "aws_subnet" "app" {
  for_each          = local.app_subnets
  vpc_id            = aws_vpc.main.id
  cidr_block        = each.value.cidr
  availability_zone = each.value.az
  tags = {
    Name = "app-${each.key}"
  }
}

moved {
  from = aws_subnet.app[0]
  to   = aws_subnet.app["a"]
}

moved {
  from = aws_subnet.app[1]
  to   = aws_subnet.app["b"]
}

Rename playbook

When renaming resource/module labels, add moved first:

moved {
  from = module.network_core
  to   = module.network_foundation
}

LLM mistake checklist

Common model mistakes to correct:

  • defaults to count for every collection
  • omits moved blocks in refactors
  • uses list index as identity key
  • suggests terraform state mv in automation where moved is safer and reviewable
  • builds for_each keys from computed IDs not known until apply

Known-at-plan example (for_each failure pattern)

Bad (key depends on apply-time value):

resource "aws_security_group_rule" "egress" {
  for_each                 = toset([aws_security_group.ecs.id])
  type                     = "egress"
  from_port                = 443
  to_port                  = 443
  protocol                 = "tcp"
  security_group_id        = each.value
  cidr_blocks              = ["0.0.0.0/0"]
}

Safer fallback for optional singleton behavior:

resource "aws_security_group_rule" "egress" {
  count                    = var.enable_egress_rule ? 1 : 0
  type                     = "egress"
  from_port                = 443
  to_port                  = 443
  protocol                 = "tcp"
  security_group_id        = aws_security_group.ecs.id
  cidr_blocks              = ["0.0.0.0/0"]
}

Verification commands

terraform fmt -check
terraform validate
terraform plan -out=plan.bin
terraform show plan.bin | grep -i moved

OpenTofu equivalent:

tofu fmt -check
tofu validate
tofu plan -out=plan.bin
tofu show plan.bin | grep -i moved