terraform-aws-modules/terraform-aws-dynamodb-table, examples/basic (tag v5.5.1, commit 02b2d66ad2396389381c8dbe3423682114ed5350)
Source: https://github.com/terraform-aws-modules/terraform-aws-dynamodb-table.git at v5.5.1.
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 | 22s | Apply complete! Resources: 3 added, 0 changed, 0 destroyed.; 0 objects carry tofu-estate=dynamodb-table-basic-crossing before migration |
| Migrate | pass | 1m28s | 1 resource(s) newly stamped, 0 already stamped, 1 newly recorded, 0 re-recorded for sensitivity only, 0 already recorded, 0 failed, 1 skipped.; Apply complete! Resources: 0 added, 0 changed, 1 destroyed. (tofu-slot convergence) |
| Replan from nothing | pass | 2s | no resource change proposed, nothing foreign; identity re-check (via the AWS CLI) unchanged |
| No-op apply | pass | 3s | genuine no-op: 1 objects before, 1 after, no state file either time |
| Drift and reconverge | pass | 6s | one object tampered (arn:aws:dynamodb:eu-west-1:000000000000:table/my-table-stunning-piranha’s Terraform tag), plan proposed fixing exactly one object, apply changed 1 and reconverged the tag |
| Rename | pass | 11s | moved block: module.dynamodb_table renamed to module.dynamodb_table_moved with zero churn (0 add, 1 change, 0 destroy) - the table’s own marker rewritten in place, the untaggable resource policy unaffected; live-mv: module.dynamodb_table_moved renamed to module.dynamodb_table_final with zero churn, marker rewritten in place; stock oracle over the same net module rename on cold_deploy’s own state also shows zero churn (0 add, 0 change, 0 destroy); the table’s ARN unchanged throughout, read via the AWS CLI |
| Remove a block | pass | 6s | choudoufu: deleting module.dynamodb_table_final’s block proposed exactly two destroys (0 add, 0 change, 2 destroy: the table and its untaggable resource policy), applied cleanly (0 added, 0 changed, 2 destroyed), the table is genuinely gone from the live account (dynamodb describe-table on the old name now returns ResourceNotFoundException, 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 (D-REMOVE-ORACLE) also proposes exactly two destroys for the same two objects; classifyOrphans did not withhold either destroy because module.disabled_dynamodb_table declares zero instances of the same block key (create_table=false), so nothing is ever pending against it |
| Change count | pass | 31s | choudoufu: scaling the synthetic aws_dynamodb_table.count_test from 2 to 1 (issue #359/#488’s own fallback clause - this estate’s real module has no honest resource-level count/for_each knob: create_table is boolean-shaped and replica_regions/global_secondary_indexes drive dynamic blocks nested inside the SAME table resource, not a separate resource instance, confirmed by reading main.tf directly) destroyed exactly count_test[1] (0 add, 0 change, 1 destroy), confirmed gone via the AWS CLI, its local record correctly tombstoned rather than left claiming a live identity (#398-guard shape, has(tombstone) and not has(identity)), and left count_test[0]’s live TableId and tofu-address marker unchanged; scaling back from 1 to 2 created exactly count_test[1] again under the SAME ARN (deterministic from region+account+name - established directly against floci with no tofu in the loop before writing this assertion) but a NEW TableId (0 add -> 1 add, 0 change, 0 destroy), and its local record returned to a live identity, while count_test[0] stayed untouched throughout; the next plan is empty; the G-ORACLE stock oracle on the identical 2-instance count block, applied for real in a dedicated always-idle account never shared with this one, shows the identical shape: destroy the higher index only, create it back under the same ARN but a new TableId, the lower index’s TableId unchanged both times. BREAK_COUNT=1 confirms the wrong-instance assertion correctly fails to hold. |
| Replace with create_before_destroy | pass | 13s | choudoufu: changing module.dynamodb_table_final’s ForceNew name argument proposed exactly one table replace at the same declared address, cascading into the untaggable resource policy (its resource_arn argument follows the table’s ARN and is not independently updatable, so it also replaces - F-ORACLE’s own finding); applied cleanly; the old table is confirmed gone via the AWS CLI (ResourceNotFoundException) and the new table carries the marker; the local record store’s record at the same address now names the new table’s name, not the destroyed one (my-table-stunning-piranha -> my-table-stunning-piranha-v2); the next plan proposes no resource action; stock oracle on cold_deploy’s own state (F-ORACLE) also proposes replacing the table at the same address (plan only, not applied - it shares floci’s account with $EST); BREAK=replace confirms a manufactured marker collision is reported loudly (“Two live resources claiming one slot”) rather than silently proposed as nothing. 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-ec2-instance-complete’s/corpus-sqs-basic’s matching ones. |
| Crash between create and destroy | not run | ||
| Teardown | not run | ||
| Plan, review, apply | pass | 12s | one argument edited (the resource_policy heredoc’s statement Sid, AllowDummyRoleAccess -> AllowDummyRoleAccessReviewed, reaching only module.dynamodb_table.aws_dynamodb_resource_policy.this[0]), “plan -out=approved.tfplan” wrote a 21207-byte stock-format plan file whose whole change set is that one update; the world then moved out of band (arn:aws:dynamodb:eu-west-1:000000000000:table/my-table-stunning-piranha’s Terraform tag, through the AWS CLI, never through choudoufu - the same mutation STAGE 5 uses) 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.dynamodb_table.aws_dynamodb_table.this[0] and the live table id my-table-stunning-piranha it was computed against, with “Exit status 3” spelled out for a pipeline; nothing was applied - dynamodb get-resource-policy still returned a policy without the reviewed Sid, 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 table’s tag put back and nothing else changed, the IDENTICAL file applied - 0 added, 1 changed, 0 destroyed - and the live policy read back WITH the reviewed Sid, so the refusal is earned by the drift and not handed out to every plan file. The edit was then reverted, re-applied and the estate replanned empty, so PART D starts where it would have. This estate owns only two objects the plan acts on, so the review deliberately sits on the resource policy and the move on the table: that is what makes the refusal an EXTRA row it can name rather than a values-only disagreement about one row. 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 | 46s | 3 resources from nothing (random_pet + table + resource policy), the table’s markers verified via the AWS CLI, 3 records in the local record store (#364 A2), replan empty, stock oracle in its own namespace matches structurally on key schema/attributes/table class/deletion protection/on-demand billing/GSI/resource policy |
| 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 4m0s.
Oracle: stock terraform 1.16.1, stock tofu 1.12.6 (matches the current pin).
Sourced and scaffolded 2026-08-20 (4d4a3f6513, worktree ../wt/new-terraform-estate, branch live/dynamodb-table-basic) as exploratory/blocked work: terraform-aws-dynamodb-table is one of the single most-downloaded modules in the terraform-aws-modules org (35.6M Terraform Registry downloads), never crossed before, and stage 1 could not complete at all - the example’s aws_dynamodb_resource_policy calls PutResourcePolicy on create, and floci had no handler for it (lex00/floci#86). Stage 1 landed 2026-08-21 after #86 was fixed (build e61a987). Stage 2 then hit a SEPARATE, real floci gap: DescribeTable never returned a GSI’s OnDemandThroughput, so the AWS provider’s own Read dropped it from state and the very next plan proposed replacing the GSI on a table that had not drifted - reproduced with plain stock terraform too (HANDOFF.md label 1, PARITY, not a choudoufu defect), filed as lex00/floci#91. FULL 5/5 PASS 2026-08-21 after #91 was fixed (build 8539609c, PR lex00/floci#92, published digest sha256:8f1fc4a500a3553e362c689cdcb6c5e31784bbaa7ad914de22bdd1c088a785f5, tag 8539609) and live/floci-image re-pinned to it. Re-crossed for real end to end against the new pin, nothing cherry-picked or assumed: cold_deploy PASS (‘Apply complete! Resources: 3 added, 0 changed, 0 destroyed’, table live and unmarked before migration, 0 objects carrying tofu-estate before migration). migrate PASS in both halves - live-import’s dry run correctly reports ‘1 of 3 resource instance(s) are eligible for stamping’ and ‘UNTAGGABLE (2)’ (aws_dynamodb_resource_policy has no tags argument in the provider’s schema at all, and random_pet.this is record-backed with no live object to tag, neither a choudoufu gap), -approve stamps the table, and tofu-address verifies directly against DynamoDB via the AWS CLI as module.dynamodb_table.aws_dynamodb_table.this:0. The tofu-slot convergence apply that follows - previously the exact wall #91 fixed - now completes cleanly (‘Apply complete! Resources: 0 added, 0 changed, 1 destroyed’, the destroyed resource being the synthetic tofu-slot placeholder for the module’s own zero-instance branch, the same shape corpus-iam-policy’s TOFU-SLOT FINDING documents, not the table - the table’s ARN and marker are reconfirmed live immediately after). test_plan PASS: live-plan with the state file deleted proposes no resource change and reports ‘Foreign resources: none’, and the table’s tofu-address re-read directly from DynamoDB after the state file has never existed this run is unchanged. test_apply PASS: applying the empty plan is a genuine no-op, ‘Resources: 0 added, 0 changed, 0 destroyed’, 1 tagged object before and after, no state file either time. drift_reconverge PASS: the table’s Terraform tag is tampered out of band via the AWS CLI, live-plan proposes fixing exactly module.dynamodb_table.aws_dynamodb_table.this[0] and nothing else, and the reconverge apply (‘Resources: 0 added, 1 changed, 0 destroyed’) restores the tag to its configured value, verified by re-reading the tag directly from DynamoDB. A NEW estate at full parity: terraform-aws-dynamodb-table, 35.6M registry downloads, never crossed before this session, now clears all five stages. THE RANDOM_PET GAP (issue #314, still open, same DELTA 3 shape live/e2e/corpus-s3-bucket-complete uses) remains real, tracked product debt, not hidden: the table’s own identity-bearing name argument is "my-table-${random_pet.this.id}", a TAGGED resource’s identity computed from a record-backed value - live-import does not resolve this generically yet, so the already-applied pet value is substituted as a literal ahead of stage 2, called out in the script. BREAK=1 not exercised this session (a clean run was the priority now that the estate finally clears; stages 3 and 5 both carry BREAK-gated assertions in the script, ready for a future run to confirm they are load-bearing).
Reproduce it#
go run ./tools/gauntlet run corpus-dynamodb-table-basicNeeds 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-dynamodb-table-basic/run.sh; BREAK=1 corrupts its assertions to show they are load-bearing.