terraform-aws-modules/terraform-aws-lambda examples/simple (tag v8.8.1)

Source: https://github.com/terraform-aws-modules/terraform-aws-lambda.git at v8.8.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.

StageVerdictDurationDetail
Cold deploypass13s8 resources, genuinely cold, genuinely unmarked
Migratepass14s3 stamped, 4 recorded, 0 failed, 1 skipped
Replan from nothingpass2sno resource change proposed
No-op applypass5sno-op apply (0 added, 0 changed, 0 destroyed); tofu-estate-tagged object count unchanged at 3; markers and record store intact
Drift and reconvergepass19sone object tampered (memory_size 128->256), exactly module.lambda_function.aws_lambda_function.this[0] proposed by both choudoufu and stock with the identical change, apply changed 1 and memory_size reads back as 128
Renamepass25smoved block: module.lambda_function renamed to module.lambda_function_moved with zero churn (0 add, 3 change, 0 destroy) across all seven of its stateful children, three taggable markers rewritten in place, three record-located children moved via their own per-resource moved blocks with zero diff, one config-derived child (aws_iam_role_policy.logs) needing none; stock oracle over the identical seven-resource move on cold_deploy’s own state also shows zero churn beyond the module’s own pre-existing null_resource.archive[0] package-timestamp noise (confirmed present on an unrelated baseline replan too); live-mv: module.lambda_function_moved renamed to module.lambda_function_final across all three taggable children (the function, the role, the log group), one call each, zero churn, markers rewritten in place - the internal/live/mv/mv.go materialize() RecordStore wiring gap (build.go:1676’s “Record-backed instance with no record store”) is fixed; all three live objects unchanged throughout, read via the AWS CLI; final replan is empty
Remove a blockpass12schoudoufu: deleting module.lambda_function_final’s block proposed 7 destroys (the function, the role, its inline aws_iam_role_policy.logs[0] CloudWatch Logs policy, and all three record-located children always; the log group’s only when floci’s GetResources happens to index it - a documented emulator gap, confirmed by reading logs:list-tags-for-resource directly against the same live object), applied cleanly, the function, the role and the inline log policy genuinely gone from the live account (read via the AWS CLI, not choudoufu’s own report), and the next plan proposes no further resource action; classifyOrphans did not withhold any destroy as a possible rename. WANT_DESTROY_COUNT moved from 5/6 to 6/7 in this same commit: the inline log policy was previously missing from this stage’s own checklist entirely - a genuine leak (an untaggable IAM permission left behind on every destroy of this estate), not a stale assertion, fixed as part of the day2_replace unit that re-measured this stage
Change countpass27ssynthetic count block, and the header says why: .corpus/lambda declares 38 count/for_each expressions and not one is scalable - 36 are boolean create toggles (count = … ? 1 : 0), the for_each maps are empty in this example, and the only two numeric ones (number_of_policies, number_of_policy_jsons) are gated off by default and drive the untaggable aws_iam_role_policy_attachment, so turning either on would both change the estate every earlier stage measured and leave no marker to read back. So aws_iam_role.count_test (count = 2), a self-contained block of a type this estate already exercises, added after Part E’s completed removal. choudoufu: scaling 2 -> 1 proposed exactly 0 add, 0 change, 1 destroy, destroying count_test[1] and never naming count_test[0]; after the apply lambda-simple-count-test-1 no longer answers GetRole at all (verified absence) while count_test[0] kept its server-minted RoleId AROA2ENGPG5QBV97X3TZ and its tofu-address=aws_iam_role.count_test:0 marker, both read through the AWS CLI. Scaling 1 -> 2 proposed exactly 1 add, 0 change, 0 destroy and count_test[1] came back as a genuinely NEW object - RoleId AROAVOM62TQCT6T7PNMQ -> AROAJM7H1AYUKB7LV9Z5 under the same deterministic name, the witness an IAM role’s ARN cannot provide since it is rebuilt from account plus name (the same reason this estate’s own Lambda function ARN could not have witnessed it) - carrying tofu-address=aws_iam_role.count_test:1, with count_test[0] untouched throughout; the next plan is empty. Stock oracle (G0): the identical block stood up for real with plain terraform in its own working directory on the idle greenfield-oracle endpoint shows the identical shape - destroy the higher index only (0 add, 0 change, 1 destroy), create the higher index back under a new RoleId (AROAYU2CIN7JC3CE5OF8 -> AROAARZJM95N0B4KHL7R), the lower index’s RoleId (AROAJ6P6BPUF59UL1JKF) unchanged both times. BREAK_COUNT=1 asserts the WRONG instance (count_test[0]) was destroyed and reports this stage fail, so the assertion is load-bearing
Replace with create_before_destroypass18schoudoufu: changing module.lambda_function_final’s ForceNew logging_log_group-derived name proposed exactly one replace at the same declared address (the log group; -/+ destroy and then create) cascading into two expected in-place updates (the function’s logging_config, the inline log policy’s document) and nothing else beyond the module’s own pre-existing null_resource.archive[0] package-timestamp noise; applied cleanly; the old object (arn:aws:logs:eu-west-1:000000000000:log-group:/aws/lambda/exact-moray-lambda-simple) is confirmed gone and the new object (arn:aws:logs:eu-west-1:000000000000:log-group:/aws/lambda/exact-moray-lambda-simple-v2) carries the marker, both via the AWS CLI; the local record store’s record at the same address now names the new object’s import_id, not the destroyed one (/aws/lambda/exact-moray-lambda-simple -> /aws/lambda/exact-moray-lambda-simple-v2); the next plan proposes no resource action beyond the same known noise; stock oracle on cold_deploy’s own state (F-ORACLE) also proposes exactly one replace plus the same in-place cascade (plan only, not applied); BREAK=replace confirms a manufactured marker collision is reported loudly 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.
Crash between create and destroynot run
Teardownnot run
Plan, review, applypass14sone argument edited (module.lambda_function’s cloudwatch_logs_retention_in_days, unset -> 14, which reaches module.lambda_function.aws_cloudwatch_log_group.lambda[0] and nothing else - the module call carries no tags argument and every tags-shaped knob it has would reach three children at once), “plan -out=approved.tfplan” wrote a 31817-byte stock-format plan file whose whole change set is that one in-place update; the world then moved out of band (exact-moray-lambda-simple’s memory_size 128->256, this estate’s own STAGE 5 mutation lifted, through the AWS CLI and 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 the extra row as " module.lambda_function.aws_lambda_function.this[0] Update exact-moray-lambda-simple" - both module.lambda_function.aws_lambda_function.this[0] and the live identity it was computed against - with “Exit status 3” spelled out for a pipeline; nothing was applied - /aws/lambda/exact-moray-lambda-simple still carried no retentionInDays, 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 memory_size put back and nothing else changed, the IDENTICAL file applied - 0 added, 1 changed, 0 destroyed - and the log group read back with retentionInDays=14, so the refusal is earned by the drift and not handed out to every plan file. Reverted and reconverged in P5 (retention unset again, memory_size still 128, next plan proposes no resource action, no state file left behind) so PART D starts where it would have. BREAK_APPROVAL=1 asserts stage 12’s own recorded Break line (apply the planfile after a mutation and expect success) and correctly fails
Greenfield applypass25s8 resources from nothing (3 taggable + 5 record-backed/config-derived), all three module-nested markers verified via the AWS CLI, 8 records in the local record store (#364 A2), replan empty, stock oracle in its own namespace matches structurally (runtime, handler, memory, timeout, log-group retention)
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.9s. Oracle: stock terraform 1.16.1, stock tofu 1.12.6 (matches the current pin).

Re-verified 2026-08-18 in a fresh isolated worktree off local main (bcf78bacbd), for real (Docker/floci/AWS CLI, not read from a prior note). Stages 1 and 2 still pass exactly as landed: cold apply creates 8 resources, live-import verifies ‘3 of 8 resource instance(s) are eligible for stamping’, -approve reports ‘3 resource(s) newly stamped, 0 already stamped, 0 failed, 5 skipped’, and module.lambda_function.{aws_lambda_function,aws_iam_role,aws_cloudwatch_log_group} carry the expected module-qualified tofu-address/tofu-estate tags read straight through the AWS CLI. #303 (the count=var.enable_x?1:0 zero-instance admission gap on aws_lambda_function_url.this and aws_lambda_function_recursion_config.this) is CONFIRMED FIXED: re-running live-plan against current main, neither type appears anywhere in the diagnostics any more, as an error or a warning - stage 3 now fails on exactly one Error block, not two-plus. That block is local_file.archive_plan (module.lambda_function’s package.tf:44, count = var.create && var.create_package ? 1 : 0, both true by default in this example so a real non-zero instance, not a zero-count block #303’s fix would clear), refused under the logical-resource rule. Investigated whether this is a bug or correct behavior: it is correct, deliberate, and already ruled on. Issues #237 and #238 (both closed 2026-08-18) put local_file through exactly this question and #238’s closing comment states local_file is ‘deliberately left OTHER_REFUSED with a documented reason: neither of lint’s two classes fits it correctly (its identity is argument-derived, not record-backed, and promoting it would silently reopen a count.index collision hazard a dedicated test already guards) - a genuine third-classification gap, not an omission, correctly left open rather than forced.’ local_file’s identity is a filename on the local disk of whatever machine ran apply - not a cloud object, nothing taggable, nothing an AWS CLI call could ever read back to confirm it still exists - so there is no live counterpart for a stateless replan to reconcile against. Considered scoping the estate around it the way corpus-vpc-complete/corpus-sumaform-aws scope around their own out-of-scope resources (the module’s create_package=false + local_existing_package= toggle skips package.tf’s local_file entirely) and rejected it: unlike sumaform’s provision=false, which picks between the module’s own equally-real published deployment modes to route around an infra-emulation gap in floci, swapping to a pre-built zip would replace the actual thing ‘simple’ demonstrates - the module’s own default packaging pipeline - with a materially different scenario this corpus entry was never meant to test. Left as a real, reported block; run.sh’s header carries the full investigation. One piece of relevant good news found along the way: #275 (closed 2026-08-18) built a record_store-gated residue mechanism for exactly the aws_lambda_function.filename/source_code_hash/publish phantom-diff problem a filename-deployed Lambda would otherwise hit under stateless replanning - this estate already declares a record_store, so once local_file gets its own identity class nothing here looks likely to re-hit that problem. test_apply and drift_reconverge remain not_run because test_plan does not pass; not attempted this pass since attempting them against a still-refused plan would prove nothing. No issue currently tracks the missing ‘argument-derived-but-safe’ LogicalClass itself (the actual unblock for this estate) - #237/#238 are both closed and did not spawn a follow-up; one may be worth filing if this crossing is prioritized again. Follow-up pass 2026-08-18 (#313 cross-check, fresh worktree off main at d303d9d425, real Docker/floci/AWS CLI run): confirmed the local_file.archive_plan block above is NOT #313’s data.aws_availability_zones/static-context wall - grepped the full live-plan output, zero occurrences of that diagnostic. Filed the missing-LogicalClass follow-up this note flagged as worth filing but hadn’t been: #314 (’local_file needs a fourth LogicalClass (argument-derived identity)’), citing #237/#238’s rulings and the count-index guard test. No fix attempted - real multi-package work (lint + identity resolution + row-gen), not a quick derivation. Follow-up pass 2026-08-19 (#314 fixed and merged, d878aa914b/e7bb1f4b41): the fourth LogicalClass exists, but not the one the issue’s own framing predicted - two of that framing’s premises turned out false, checked rather than assumed. hashicorp/local’s provider implements NO ImportState for local_file at all (confirmed against stock tofu: ‘This resource does not support import’), so an argument-derived identity handed to internal/live/projection would have turned a lint refusal into a hard Cannot-import-for-projection error - strictly worse. And this estate’s own filename argument isn’t static anyway (it reads a data.external result). The real fix is ClassExternalAdmitted/EXTERNAL_ADMITTED: a record_store admits local_file the same way it admits ClassRecordAdmitted, resolving through ClassRecordBacked - the record is the only carrier that can bring prior state back for a type the provider itself cannot re-derive. Reaches exactly one type (local_file; local_sensitive_file is secret-bearing) but the RULE generalizes: live/logical-schemas.json’s per-provider store_only now SELECTS between the two admitted classes instead of gating whether a type derives a row at all, which also retired the hand-written local_sensitive_file exception in ClassifyLogicalType - a net type-name-literal deletion, not an addition. Count-index guard (TestLocalFileKeepsItsCountIndexCheck) confirmed still holding via two separate mutation checks. Real re-crossing: local_file is admitted and appears nowhere in live-plan’s diagnostics any more (asserted by absence) - but test_plan stays fail, now BLOCKED at 5 sites, a FOURTH wall newly reached rather than caused. All five trace to one expression, function_name = “${random_pet.this.id}-lambda-simple”: random_pet.this is RECORD_ADMITTED so its id lives only in the record store, and the identity resolver declines to read that carrier for the three dependent resources (aws_iam_role.lambda, aws_iam_role_policy.logs, aws_lambda_function.this, aws_cloudwatch_log_group.lambda, one cascade) even though all three are already stamped and CLI-verified by stage 2 of the same run - choudoufu already holds the value and the objects are already marked, so this reads as an identity-resolver gap rather than a missing carrier. Not filed (no issue number assigned) - worth a slot. test_apply/drift_reconverge remain not_run, blocked on this new wall. Two real corrections made along the way: the crossing script had no AWS provider version pin (silently drifted to whatever the newest release was, now pinned =6.59.0 matching corpus-cloudfront’s discipline), and live/LIMITATIONS.md’s local-file section stated ’no cloud counterpart to reconcile against’ as fact - false; the local filesystem is the counterpart, now corrected there too. Follow-up pass 2026-08-19/20 (#336 fixed and merged, 821c769715/c41279989a): #336’s own diagnosis was wrong in two places, checked rather than assumed. The identity resolver was NOT declining to read the record-store carrier - resolver.parentPart already read random_pet.this.id correctly on unmodified main. What actually refused was coalesce(): iam.tf/main.tf’s role_name/policy_name/log-group-name chains all select through coalesce(var.X, var.Y, “*”)-shaped expressions, and resolver.isSymbolic reads only an expression’s traversal ROOTS - var/local are never symbolic, so a selection sitting behind a module argument or a local looked entirely static, failed whole-expression evaluation, and had nothing left to try (the decomposition switch is only reached when a resource is named directly inside the expression). Fixed generically (internal/live/identity/coalesce.go, new): resolveCoalesceCall decides which argument the language selects using two proofs (provably-null-or-empty to skip, provably-non-null-non-empty to select), declining the whole call on anything undecidable rather than silently falling through - mutation-tested three ways (drop the non-emptiness proof, fall through on undecidable, remove the call entirely), each caught. Measured reach: refusal-probe sites 16075->15964 (-111), instances 4499->4522 (+23), 12 entries improved across six unrelated sources, 0 worse - schema-less mode, an under-report since it’s blind to the record-backed half of this estate’s own chain. Real re-crossing: live-plan diagnostics 5->0, the plan runs to completion for the FIRST time - but test_plan still FAILS, now on a genuinely NEW, fifth wall: ’live-plan is not empty’, proposing to create every record-backed resource (random_pet.this first) from scratch. Root cause, and #336’s second wrong premise: live-import’s Approve loop only calls #327’s recordResidueFor for a STAMPED entry; a record-backed resource is by definition not stampable (no live cloud object to tag) and hits OutcomeSkipped, continuing past the residue call entirely - so the record store is empty for every record-backed instance after a clean migrate, not populated as #336 assumed. Filed as #340, not attempted - the fix is migrate seeding the record store from the migrated state’s own object for every record-backed instance, a sibling call to recordResidueFor on the skipped-because-record-backed path. test_apply/drift_reconverge remain not_run, now blocked on #340 instead of #336’s five diagnostics. Follow-up pass 2026-08-20 (#340 fixed and merged, d30daa156f/8d34e3ded9): the issue’s own framing was half right - recordResidueFor does NOT gate on ‘stamped’, it runs for any entry with an eligible; the real gate is one line earlier, Ratify never building an eligible for a record-backed type at all. Fixed with Approve’s second write path, the sibling of the tag write: projection.SeedRecordForInstance writes a record-backed instance’s object into the record store, byte-identical to what an apply’s WriteBack would write, reading before writing so an already-correct record is a no-op and a genuinely different one refuses rather than clobbers. Keys on identity.TypeIdentity.RecordBacked and nothing else - 15 types across 4 providers (local/null/random/time/terraform_data), no aws_/random_ name in the control flow. Real re-crossing: STAGE 2 migrate reports ‘3 newly stamped, 0 already stamped, 4 newly recorded, 0 already recorded, 0 failed, 1 skipped’, the store’s own files grepped for random_pet.this’s generated id. STAGE 3: live-plan now raises ZERO diagnostics for the first time ever on this estate, every identity resolves, no record-backed resource is proposed for creation - but the plan is not empty: 0 to add, 2 to change, 0 to destroy, a SIXTH wall. Both changes are real and distinct from every prior wall: (1) a nested-block round-trip on aws_lambda_function (- environment {} / + logging_config { log_format = “Text” }, floci’s Lambda read vs the module’s config), (2) a sensitivity-only diff on local_file.content (OpenTofu’s own renderer says ‘The value is unchanged’ - a genuine limitation the fixing agent found and pinned rather than hid: ResourceInstanceObjectSrc.Decode re-applies AttrSensitivePaths so the decoded value is marked, ctyjson.Marshal panics on a marked leaf, the fix unmarks before encoding, and projection.recordPayload has nowhere to store the sensitivity path - so the record carries the value but not the mark. projection.WriteBack shares this same hole, worse (no unmark of its own, would panic on the identical object after a real apply) - unfiled, not touched, worth a slot). test_apply/drift_reconverge remain not_run, blocked on this sixth wall now. Separately, #341 (found by the sibling corpus-mastino-dns crossing, same ratify.go gate but a different population - untaggable ordinary AWS types like aws_route53_record, architecturally guaranteed never to be RecordBacked per TestResolveNeverEmitsRecordBackedForAWSEstate) is CONFIRMED STILL OPEN after #340 - the two issues share a root-cause line but #340’s fix is deliberately scoped to the disjoint RecordBacked population and does not reach it. #340’s own recordable/Approve sibling-carrier pattern is flagged as a reasonable template for #341’s eventual fix. Follow-up pass 2026-08-20 (worktree ../wt/lambda-simple-sixth-wall, branch live/lambda-simple-sixth-wall, off LOCAL main ea9fd62fc0): the sixth wall was REPRODUCED for real first, not read off this note - STAGE 1 PASS, STAGE 2 PASS, STAGE 3 with zero diagnostics and ‘Plan: 0 to add, 2 to change, 0 to destroy’, both changes verbatim as recorded above. Stages are UNCHANGED at 2 of 5, and the reason is now measured rather than argued: the two changes belong to two different projects. (a) local_file.archive_plan’s sensitivity-only diff IS choudoufu’s and is FIXED (f66bc9e043). projection.recordPayload gained SensitiveAttrs, encoded exactly the way a state file encodes sensitive_attributes: encodeRecordPayload splits the value from its marks itself so no caller unmarks, decodeRecordPayload puts them back the way ResourceInstanceObjectSrc.Decode does, and materializeRecord re-marks after the schema conversion so obj.Encode derives AttrSensitivePaths from them. The mechanism the wall turned on is that live-plan runs the plan graph with SkipRefresh (live_plan.go:499), so a projected object’s AttrSensitivePaths is the ONLY marks the plan’s ‘before’ side ever has - upstream re-marks a refreshed object at node_resource_abstract_instance.go:1106 and that line is never reached - while the ‘after’ side is re-marked from the config and the provider schema every run at :1383. Derived from the object’s own marks and nothing else, so it reaches any record-backed type with any sensitive attribute at any path; a mark that is not marks.Sensitive is refused rather than dropped. Four mutation checks, each caught, and the shape test consults an EXTERNAL source (it writes a real state file through internal/states/statefile and requires the same JSON for the same paths) rather than round-tripping against itself. TestIdentityGolden 0 changed, 0 added, 0 removed; ./internal/live/…, ./tools/…, ./live/…, ./cmd/… and ./internal/command/ all green. (b) aws_lambda_function’s ‘- environment {}’ / ‘+ logging_config { log_format = “Text” }’ pair is NOT choudoufu’s, and this is now proven by a CONTROL rather than reasoned about: run.sh’s new step 3b runs plain terraform, its own state file, its own refresh, ’terraform plan -detailed-exitcode’ immediately after its own cold apply with no choudoufu anywhere in the run, and it replans NOT EMPTY on exactly module.lambda_function.aws_lambda_function.this[0] and nothing else. Asserted by value, so a new emulator gap breaks the script instead of hiding in a bucket labelled expected, and a fixed one breaks it too. Filed as lex00/floci#83 with both causes located in LambdaController.buildFunctionConfiguration: Environment is emitted unconditionally (‘SDK expects it even when empty’) where real AWS omits it for a function that never had one - which is why terraform-provider-aws reads it under ‘if function.Environment != nil’ - and LoggingConfig is neither stored nor emitted at all, where real AWS always returns one defaulting to Text. The module declares zero environment blocks (main.tf:90, a dynamic block over an empty map) and one logging_config unconditionally (main.tf:136), so both fire on the module’s DEFAULTS. Stage 3 now splits its own plan against the step-3b control with comm and names only the remainder as choudoufu’s. WHAT IS NOT VERIFIED, stated rather than implied: the post-fix crossing was started and its choudoufu init was killed by the harness before stage 2, so the sensitivity fix has NOT been observed clearing the diff in a real end-to-end run - it is verified by unit tests that drive the two real paths (WriteBack after an apply, then BuildWith/materializeRecord on the next plan) and assert inst.Current.AttrSensitivePaths by value. The next run of this script is what settles it, and it should still end at ’live-plan is not empty’ with the Lambda alone until lex00/floci#83 lands. Two further issues filed from this pass, neither fixed: #343 (builder.materialize applies no schema.Block.ValueMarks to what a provider Read returned, so the identical perpetual diff exists for any CONCRETE cloud object with a Sensitive attribute - separate because it also changes what b.live means for identity composition, and unmeasured over the corpus) and #344 (a record written before SensitiveAttrs existed now conflicts with its own re-migration though the value is identical, because SeedRecordForInstance compares bytes; population is local_file plus any config-derived mark, and the format is one day old). Stages 4 and 5 remain unwritten: an empty stage-3 plan is their precondition and lex00/floci#83 is what stands between this estate and one. Follow-up pass 2026-08-20 (worktree ../wt/lambda-simple-floci83, branch live/lambda-simple-floci83): lex00/floci#83 is FIXED and CLOSED, settled against real AWS (one throwaway Lambda function + IAM role created and immediately deleted, disclosed to the user per standing permission) rather than assumed - GetFunction/GetFunctionConfiguration omits Environment entirely for a function that never had one (not present-but-empty) and always returns LoggingConfig, defaulting to Text. Fixed in LambdaController/LambdaService/LambdaFunction, verified three ways (local instance, direct probe, and the same probe against the published GHCR image). Re-crossing after the re-pin: STAGE 3 (test_plan) now raises zero diagnostics and proposes changing ZERO resources for the first time ever on this estate - both prior sixth-wall diffs (the sensitivity-only local_file.content diff and the aws_lambda_function nested-block mismatch) are gone, verified by reading the raw terraform plan output directly rather than trusting extracted variables. But the plan is still not empty: all 23 of the example module’s root-level output blocks render as + new on every run. Root cause: internal/live/projection.Manager.GetRootOutputValues always returns an empty map - nothing evaluates the config’s own output blocks against the reconstructed prior state before the plan graph asks for it. A genuinely new, generalizable gap (neither corpus-mastino-dns nor corpus-evoteum-modules, the two other closest-to-5/5 crossings, declares any root-level output, so nothing had hit this before). Filed as INTENTIUS/choudoufu#348, not attempted - core projection-architecture work, not a quick derivation. Stages remain 2 of 5; the estate’s real blocker moved from floci#83 to #348, which is now the sole thing standing between this estate and an empty stage-3 plan. Follow-up pass 2026-08-20 (primary checkout, local main db1f412cfd, real Docker/floci run - GitHub issue #340 verification): #340 was found ALREADY FIXED on main (d30daa156f/8d34e3ded9, confirmed by commit history and by internal/live/liveimport/record_test.go’s TestApprove_SeedsTheRecordStoreForARecordBackedInstance/TestRecordBackedTypeReadsTheGeneratedTable, the latter covering random_pet/null_resource/terraform_data/local_file/time_sleep/random_id generically), so this pass re-verified rather than re-fixed. Re-crossing confirms #340’s own fix by absence again: ’no record-backed resource is proposed for creation: the migrate seeded all four’ and ’no sensitivity-only diff on local_file.archive_plan: the record carries its marks’ both print in stage 3’s own output. #349 (‘see through provably-zero-instance blocks when evaluating root outputs’, 88d7e3961e, landed after this note’s #348 paragraph) cut the output-only diff from all 23 root outputs to exactly 2: ’lambda_function_arn_static’ and ’local_filename’, both still ‘+’ on every run. Stage 3 (test_plan) is still BLOCKED - not yet empty - but the wall is now two output lines, not twenty-three, and #340 itself contributes zero diagnostics and zero sites to what remains. Stages unchanged at 2 of 5 pass; the residual 2-output gap is #348/#349’s remaining scope, not #340’s, and was not investigated further here (out of this issue’s scope). Follow-up pass 2026-08-21 (isolated worktree off local main 860c29e129, real Docker/floci/AWS CLI run, identical harness run TWICE with only TOFU_BIN swapped - not read from any prior note): #349’s sub-problem 2, the root-output data-source read, is now built, and this estate’s stage-3 output diff went from 2 lines to 1. Measured: at 860c29e129 the plan’s ‘Changes to Outputs:’ block carries ’lambda_function_arn_static’ and ’local_filename’; with the fix it carries ’local_filename’ alone. lambda_function_arn_static vanishes from the diff entirely rather than rendering as ‘~ old -> new’, which is the stronger result: the plan graph independently computed the same value the pre-plan read computed for the prior side, so they cancel. The three data sources behind it (data.aws_partition.current, data.aws_region.current and data.aws_caller_identity.current, all in module.lambda_function) are read live before the plan through the same configured aws provider instance the projection already reads this estate through. local_filename is UNCHANGED and stays refused ON PURPOSE: it reaches data.external.archive_prepare, whose read runs package.py on the machine running the plan, and the new demand class is confined to providers this configuration manages live objects through (dataread.LiveProviders) - the external provider serves no managed resource type at all, in this or any configuration, so it is excluded structurally rather than by name. Stages are UNCHANGED at 2 of 5: cold_deploy pass, migrate pass, test_plan still FAIL (one output line is still one output line, so the plan is still not empty), test_apply and drift_reconverge still not reached. This narrowed the stage-3 diagnostic count; it did not clear the stage. Follow-up pass 2026-08-29 (isolated worktree off local main 499f9f5e80, real Docker/floci/AWS CLI run - GitHub issue #498, ‘migrate pass and fail 23 minutes apart at the same emulator pin’): CONFIRMED as a real, deterministic defect, not a flake and not #497’s runner-resource-pressure hypothesis. Reproduced 100% of the time under a controlled variable rather than by chance: migrate PASSED 7/7 consecutive local runs (this machine’s stock terraform, v1.15.8) with byte-identical output every time, then FAILED 2/2 runs the instant HashiCorp Terraform v1.16.0 was forced first on PATH for stage 1’s cold deploy, with the exact nightly failure text (’live-import -approve did not stamp 3 and record 4 of 8 resources cleanly’). Root cause, isolated to one byte: Terraform >=1.16.0’s built-in terraform_data resource gained a new ‘store’ nested block (verified directly via terraform providers schema -json against terraform.io/builtin/terraform, no choudoufu involved) that choudoufu’s own terraform_data schema (internal/builtin/providers/tf/resource_data.go, unchanged since the OpenTofu fork) does not declare; decoding a stock-terraform-1.16-produced cold.tfstate’s terraform_data instance against that older schema failed with ‘unsupported attribute “store”’ (internal/live/liveimport/ratify.go’s ratifyRecordBacked, its inst.Current.Decode(schema.Block.ImpliedType()) call), demoting terraform_data.package_filename_for_hash from RECORDED to SKIPPED and changing live-import -approve’s summary line, which trips run.sh’s exact-string assertion. The ‘pass at 10:23:19Z, fail at 10:46:59Z, same commit’ shape in #498 was never nondeterminism in the same environment: the passing row was a local worker’s run against an older pinned-by-brew terraform (like this pass’s own baseline), the failing row was CI’s hashicorp/setup-terraform@v3 with terraform_version: latest (confirmed 1.16.0 from the nightly’s own log) - two different environments measuring the identical commit near-simultaneously, not one environment flip-flopping. Downloaded the eks-basic sibling failure from the same nightly run for comparison and confirmed it is a DIFFERENT failure (a runner tofu-on-PATH casualty per #497, mis-attributed to whatever CURRENT_STAGE was set to when it died) - #498’s own caveat about #497 does not reach this estate’s failure, which is fully explained by the terraform_data schema gap alone. Fixed generically for the one type it can reach (dataStoreResourceSchema() now declares ‘store’ as a NestedType Object attribute mirroring the real schema’s own field shape and WriteOnly/Sensitive flags, verified by decoding the real terraform-1.16.0-produced state); this is the resource’s own implementation file, not classification control flow, so it carries no live/derivation_guard_test.go entry. ctyjson.Unmarshal was independently verified (a standalone throwaway program, not assumed) to default a type attribute missing from raw JSON to null, so old choudoufu-written terraform_data state predating this field decodes unaffected - TestManagedDataUpgradeStateMissingStore pins that boundary directly. Re-crossing after the fix: migrate PASSES under terraform 1.16.0 too (2/2), with the correct ‘3 stamped, 4 recorded, 0 failed, 1 skipped’ split restored, and the whole estate clears end to end (exit 0) under both terraform versions - no other stage regressed. Not verified: HashiCorp Terraform versions between 1.15.8 and 1.16.0 (whichever one first introduced ‘store’) and any version after 1.16.0 that might change the block’s shape again; the fix targets the exact field shape read off 1.16.0 and would need re-verification against a materially different future schema.

Reproduce it#

go run ./tools/gauntlet run corpus-lambda-simple

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/corpus-lambda-simple/run.sh; BREAK=1 corrupts its assertions to show they are load-bearing.