terraform-aws-modules/terraform-aws-iam examples/iam-policy
Source: https://github.com/terraform-aws-modules/terraform-aws-iam.git at v6.8.0.
Set: core. Lane: terraform-popular.
Why it is in the core set: a most-downloaded terraform-aws-modules example, pinned by tag; the shape most people deploy
Clear. Every headline stage passes.
| Stage | Verdict | Duration | Detail |
|---|---|---|---|
| Cold deploy | pass | 16s | Apply complete! Resources: 2 added, 0 changed, 0 destroyed.; 0 objects carry tofu-estate=iam-policy-crossing before migration |
| Migrate | pass | 17s | 2 of 2 stamped, both carrying tofu-slot=0/0 read back through IAM (choudoufu #372); Apply complete! Resources: 0 added, 0 changed, 0 destroyed. - nothing left to converge |
| Replan from nothing | pass | 2s | no resource change proposed, nothing foreign; identity re-check (via the AWS CLI) both unchanged |
| No-op apply | pass | 3s | genuine no-op: 2 objects before, 2 after, no state file either time |
| Drift and reconverge | pass | 5s | one object tampered (arn:aws:iam::000000000000:policy/example_from_data_source’s Example tag), plan proposed fixing exactly one object, apply changed 1 and reconverged the tag |
| Rename | pass | 9s | moved block: module.iam_policy_from_data_source renamed with zero churn (0 add, 1 change, 0 destroy), marker rewritten in place; live-mv: module.iam_policy renamed with zero churn, marker rewritten in place (found and fixed live-mv’s own missing issue #266 tag-index fallback and the arnJoinTable’s missing iam:policy entry to get here); stock oracle over the same two-module rename on cold_deploy’s own state also shows zero churn (0 add, 0 change, 0 destroy); both ARNs unchanged, read via the AWS CLI |
| Remove a block | pass | 8s | choudoufu: deleting module.iam_policy_renamed’s block proposed exactly one destroy (0 add, 0 change, 1 destroy), applied cleanly (0 added, 0 changed, 1 destroyed), the object is genuinely gone from the live account (iam get-policy on the old ARN now returns NoSuchEntity, read via the AWS CLI, not choudoufu’s own report), and the next plan proposes no resource action; stock oracle on cold_deploy’s own state (STAGE 1.5.5) also proposes exactly one destroy for the same object; classifyOrphans did not withhold the destroy even though module.iam_policy_renamed2’s policy shares the same block key, because that surviving instance is bound, not unclaimed |
| Change count | pass | 41s | choudoufu: scaling aws_iam_policy.count_test from 2 to 1 destroyed exactly count_test[1] (0 add, 0 change, 1 destroy), leaving count_test[0]’s live arn and tofu-address marker unchanged; scaling back from 1 to 2 created exactly count_test[1] under a NEW arn (0 add, 0 change -> 1 add, 0 change, 0 destroy) while count_test[0] stayed untouched throughout; the next plan is empty; the G0 stock oracle on the same 2-instance count block, applied fresh against the idle adopted-estate endpoint, shows the identical shape: destroy the higher index only, create the higher index back under a new arn, the lower index’s arn unchanged both times. Synthetic block: this estate’s only real count knob (aws_iam_policy.policy’s count = var.create ? 1 : 0) is a boolean create toggle, not a scalable set - sanctioned fallback per live/GAUNTLET.md #8 and reference-ec2-vpc’s own Part F. |
| Replace with create_before_destroy | pass | 7s | choudoufu: changing module.iam_policy_renamed2’s ForceNew name_prefix argument proposed exactly one replace at the same declared address (1 add, 0 change, 1 destroy; -/+ destroy and then create), applied cleanly; the old policy (arn:aws:iam::000000000000:policy/example-4719029f3289740ef77b321f1c) is confirmed gone and the new policy (arn:aws:iam::000000000000:policy/example-v2-dc7057267790957e020e9df438) carries the marker, both via the AWS CLI; the local record store’s record at the same address now names the new object’s ARN, not the destroyed one (arn:aws:iam::000000000000:policy/example-4719029f3289740ef77b321f1c -> arn:aws:iam::000000000000:policy/example-v2-dc7057267790957e020e9df438); the next plan proposes no resource action; stock oracle on cold_deploy’s own state (STAGE 1.5.6) also proposes exactly one replace at the same address (plan only, not applied). Scope note: this exercises OpenTofu’s default destroy-then-create ordering, not the create_before_destroy variant the stage’s Title names - see this section’s own header comment and corpus-sqs-basic’s matching one. BREAK=replace’s manufactured marker collision IS now reported for this type (GitHub issue #411, fixed): ‘Indistinguishable instances without per-instance markers’ - GitHub issue #409, layered on top of #411’s own fix, is why this is not corpus-sqs-basic’s ‘Two live resources claiming one slot’ text despite the same shape - see PART F’s own header and its BREAK=replace branch. |
| Crash between create and destroy | not run | ||
| Teardown | not run | ||
| Plan, review, apply | pass | 12s | one argument edited (module.iam_policy’s tags gain Reviewed=yes), “plan -out=approved.tfplan” wrote a 11127-byte stock-format plan file whose whole change set is one update on module.iam_policy.aws_iam_policy.policy[0]; the world then moved out of band (arn:aws:iam::000000000000:policy/example_from_data_source’s Example tag, through the AWS CLI, never through choudoufu) and “apply approved.tfplan” refused with “The approved plan no longer matches the live system” at exit 3, classifying the drift under “This apply would do, and the approved plan does not include:” and naming both module.iam_policy_from_data_source.aws_iam_policy.policy[0] and the live arn:aws:iam::000000000000:policy/example_from_data_source it was computed against, with “Exit status 3” spelled out for a pipeline; nothing was applied - arn:aws:iam::000000000000:policy/example-4719029f3289740ef77b321f1c still carried no Reviewed tag, read back through the AWS CLI rather than from the absence of an “Apply complete!” line. Inverted control on the same run (the shape live/smoke/scenarios/apply-what-was-approved.sh reasons out): with the tag put back and nothing else changed, the IDENTICAL file applied - 0 added, 1 changed, 0 destroyed - and arn:aws:iam::000000000000:policy/example-4719029f3289740ef77b321f1c read back with Reviewed=yes, so the refusal is earned by the drift and not handed out to every plan file. BREAK_APPROVAL=1 asserts stage 12’s own recorded Break line (apply the planfile after a mutation and expect success) and correctly fails |
| Greenfield apply | pass | 54s | 2 resources from nothing (both aws_iam_policy), markers verified via the AWS CLI, 2 records in the local record store (#364 A2), replan empty both with and without the local record store, both policies’ documents and paths match stock’s cold-deploy container (STAGE 1, untouched) object by object, marker tags never compared |
| Strict profile (not a headline stage) | not run |
Last run at commit 933618dec4 on 2026-09-08T22:17:50Z, exit code 0, against emulator image ghcr.io/lex00/floci@sha256:a39185cc3971d0188663d61043cb038dff1260d8a975b1aa72c4e2bb1feac3cb. Stale: the current pin is ghcr.io/lex00/floci@sha256:d9207de14c919f4bfa50e956376cc441970f3679aabfdd43f3dbf4b779b20805. Total run time 2m54.7s.
Oracle: stock terraform 1.16.1, stock tofu 1.12.6 (matches the current pin).
Upgraded from a real but pre-#274-pipeline predecessor script (choudoufu apply from a live block present from the start, delete state, replan empty twice) to the current five-stage shape, following corpus-vpc-complete/corpus-lambda-simple’s structure. Verified for real in a fresh isolated worktree off local main (ff106e63a7), Docker/floci/AWS CLI throughout, not read from the predecessor’s prior notes. All five stages pass cleanly: cold_deploy (plain terraform apply, “Apply complete! Resources: 2 added”, confirmed 0 objects tagged before migration), migrate (live-import dry run verifies “2 of 2 resource instance(s) are eligible for stamping”, -approve reports “2 resource(s) newly stamped, 0 already stamped, 0 failed, 0 skipped”, both tofu-address/tofu-estate tags read directly through the AWS CLI: module.iam_policy.aws_iam_policy.policy:0 and module.iam_policy_from_data_source.aws_iam_policy.policy:0), test_plan (live-plan genuinely empty, both identities re-read unchanged after the state file’s only copy was deleted), test_apply (“0 added, 0 changed, 0 destroyed”, object count unchanged at 2), and drift_reconverge (one policy’s Example tag tampered directly against floci, live-plan proposes fixing exactly that object, apply reconverges it to “0 added, 1 changed, 0 destroyed”). BREAK=1 verified twice, independently, against each stage it targets: run as committed it fails stage 3’s identity check (expects the real policy’s tofu-address on a module that was never created); run separately with stage 3’s corruption disabled, it correctly fails stage 5 by tampering a second object and proving the “exactly one object” count assertion is load-bearing (both objects flagged, not silently 1). NEW FINDING, not previously documented in any real crossing that reached this deep: live-import -approve deliberately writes only tofu-estate and tofu-address, never tofu-slot (internal/live/stamp/doc.go’s own “tofu-slot comes in from outside” - a slot is minted from a monotonic counter over the live set that a read-only, one-state-file view cannot compute). Both of this estate’s aws_iam_policy resources declare count = var.create ? 1 : 0, exactly the shape that needs one, so the FIRST live-plan straight after live-import -approve is not empty - it proposes adding tofu-slot=“0” to both, and nothing else. Folded into stage 2 as one ordinary choudoufu apply (“0 added, 2 changed, 0 destroyed”) before stage 3 is attempted; every replan after is genuinely empty. This is real, deliberate, already-documented product behavior, not a defect - but it will recur on any count-based resource crossing that reaches this far and had not yet been noticed in one that actually got here. Also caught and fixed while verifying: a self-authored bug where stage 5’s negative drift assertion compared a live-plan diff header’s address (bracket form, “policy[0]”) against the escaped tag-value form (“policy:0”) and could never have matched - a vacuous check that a stricter assertion in the sibling script (see corpus-iam-read-only-policy) surfaced; fixed here by keeping both forms as separate variables.
Reproduce it#
go run ./tools/gauntlet run corpus-iam-policyNeeds Docker (the emulator is pulled at the pinned digest), the AWS CLI, and a
stock terraform or tofu binary on PATH for the cold deploy. The script is
live/e2e/corpus-iam-policy/run.sh; BREAK=1 corrupts its assertions to show they are load-bearing.