hand-written reference project: the plainest getting-started shape (VPC/subnet/SG/IGW/EC2)

Set: core. Lane: reference.

Why it is in the core set: the plainest hand-written reference shape, kept in this repository

Clear. Every headline stage passes.

StageVerdictDurationDetail
Cold deploypass1m29s5 resources from plain terraform, a real terraform.tfstate, zero markers
Migratepass54s5 of 5 verified, 5 stamped, 0 skipped
Replan from nothingpass2spost-adoption plan is empty; markers read back through the AWS CLI in part A
No-op applypass2sno-op apply (0 added, 0 changed, 0 destroyed); tofu-estate-tagged object count unchanged at 5
Drift and reconvergepass5sone object tampered, exactly aws_instance.main proposed, apply changed 1 and the tag reads back as configured
Renamepass8smoved block: aws_security_group renamed with zero churn (0 add, 1 change, 0 destroy), marker rewritten in place; live-mv: aws_internet_gateway renamed with zero churn, marker rewritten in place; stock oracle over the same two-resource rename on cold_deploy’s own state also shows zero churn (0 add, 0 change, 0 destroy); both live ids unchanged, read via the AWS CLI
Remove a blockpass6schoudoufu: deleting aws_internet_gateway.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 (describe-internet-gateways on the old id no longer returns it, read via the AWS CLI, not choudoufu’s own report), and the next plan is empty; stock oracle on cold_deploy’s own state (B1.6) also proposes exactly one destroy for the same object; classifyOrphans did not withhold the destroy because no other aws_internet_gateway block is declared anywhere in this config
Change countpass17schoudoufu: scaling aws_security_group.count_test from 2 to 1 destroyed exactly count_test[1] (0 add, 0 change, 1 destroy), leaving count_test[0]’s live id and tofu-address marker unchanged; scaling back from 1 to 2 created exactly count_test[1] under a NEW live id (0 add, 0 change -> 1 add, 0 change, 0 destroy) while count_test[0] stayed untouched throughout; the next plan is empty; the B1.7 stock oracle on the same 2-instance count block, applied fresh in the idle greenfield account, shows the identical shape: destroy the higher index only, create the higher index back under a new id, the lower index’s id unchanged both times
Replace with create_before_destroypass26schoudoufu: changing aws_instance.main’s ForceNew ami argument proposed exactly one isolated instance replace at the same declared address (1 to add, 1 to destroy, nothing else), matching B1.8’s own plan shape; applied cleanly; the old instance (i-4a8fa12bbcc81ed5f) is confirmed terminated and the new instance (i-f9bccd2c53fc90693) carries the marker, both via the AWS CLI; the local record store’s record at the same address now names the new instance, not the terminated one; the next plan proposes no resource action. No BREAK=replace leg - see this section’s own header comment (reusing corpus-security-group-complete’s own finding from this same unit rather than re-measuring it here).
Crash between create and destroypass35schoudoufu: a real create_before_destroy replace of aws_instance.main was interrupted with SIGTERM (landed on attempt 1 of 3; deterministic by construction, not by timing luck - internal/command/apply_e2etesting_crash.go self-signals synchronously inside the single -parallelism=1 graph-walker goroutine the instant the create half’s own write-back commits in memory, replacing #483’s external tail/grep/kill race that produced issue #490’s own retry-lottery evidence) strictly between the create committing (new object i-de649e8f32492dbcf, confirmed running via the AWS CLI) and the destroy of the deposed old object (i-f9bccd2c53fc90693, confirmed still running and untouched via the AWS CLI) ever dispatching; the local record’s one write-back correctly carried both facts at once (current=i-de649e8f32492dbcf, deposed=i-f9bccd2c53fc90693). Real investigation before writing this check found a genuine engine gap: issue #415’s record-backed collision branch (internal/live/discovery/discovery.go, decl.recordBacked’s 2-claimant path) called collisionProblem unconditionally with no deposed-record lookup at all, so a record-backed address’s own crash window - exactly what a real crash’s write-back leaves, since it answers the address’s CURRENT identity in the same commit - could never recover on its own; fixed generically (mirrors the scalar path’s own matchDeposedClaimant call, no resource type name in the fix), covered by two new unit tests (internal/live/discovery/deposed_test.go). The next plan proposed exactly one destroy (the deposed object, 0 add, 0 change, 1 destroy) and nothing else, matching stock’s own documented deposed-object semantics (Stock records the old object as deposed and destroys it on the next apply); applying it destroyed exactly that object (confirmed terminated via the AWS CLI), cleared the deposed record entry, and left the current identity untouched; the plan after that is empty. BREAK_CRASH=1 confirms the empty-plan assertion this stage’s Break text names correctly fails to hold against the same real crash window.
Teardownnot run
Plan, review, applypass12sone argument edited (aws_subnet.main’s tags gain Reviewed=yes - the one leaf of this five-resource estate no later part renames, removes, replaces or crashes, and a tags-only update that is not ForceNew, so the subnet id aws_instance.main points at is untouched), “plan -out=approved.tfplan” wrote a 9805-byte stock-format plan file whose whole change set is one update on aws_subnet.main; the world then moved out of band (i-4a8fa12bbcc81ed5f’s Name 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 aws_instance.main and the live i-4a8fa12bbcc81ed5f it was computed against, with “Exit status 3” spelled out for a pipeline; nothing was applied - subnet-d3904764 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 subnet-d3904764 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 applypass4s5-object structural comparison (vpc/subnet/igw/sg/instance) between the greenfield estate and stock’s cold deploy matches, via the AWS CLI on both endpoints, marker tags never compared; local record store held 5 records, one per instance (#364 A2); replanned empty both with and without the local record store
Strict profile (not a headline stage)pass1severy strict toggle on (secrets = refuse, no_source_create = refuse, marker_repair = never with a markers “record” selection naming aws_ebs_volume) against a scratch estate carrying one resource, random_password.db: exactly one refusal, matching live/LIMITATIONS.md’s “strict-secrets” text word for word (Logical resource is not admitted / SECRET_REFUSED / strict { secrets = “refuse” }); no_source_create and marker_repair are on and silent, reaching nothing this config declares. BREAK_STRICT=1 turns secrets back to “store” alone: the refusal disappears, the plan becomes an ordinary create, and no other refusal appears. Not part of the headline bars: tools/gauntlet/stages.go keeps Status planned here, because isClear (tools/gauntlet/artifact.go) and NextUnits (tools/gauntlet/next.go) both key strictly off ActiveStages today, with no exemption for a stage the docs already call non-headline - flipping Status without first adding that exemption would silently start gating the two headline bars on this stage, which #363 did not ask for and this unit did not build.

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 4m21.7s. Oracle: stock terraform 1.16.1, stock tofu 1.12.6 (matches the current pin).

Verified end-to-end 2026-08-17/18. Drift-and-reconverge added 2026-08-18: the adopted estate’s EC2 instance Name tag is tampered directly via the AWS CLI against the running floci container, choudoufu plan proposes fixing exactly aws_instance.main and nothing else, and apply reconverges it - verified with a real clean run and a real BREAK=1 run (BREAK also tampers a second object’s Name tag, and the single-object assertion is confirmed to fail when it does).

Reproduce it#

go run ./tools/gauntlet run reference-ec2-vpc

Needs 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/reference-ec2-vpc/run.sh; BREAK=1 corrupts its assertions to show they are load-bearing.