Skip to content

stamp: retire the HCL rewrite and module_prefix; refusal registry re-measured (#644) - #944

Merged
lex00 merged 5 commits into
mainfrom
live/stamp-retire-644
Sep 7, 2026
Merged

stamp: retire the HCL rewrite and module_prefix; refusal registry re-measured (#644)#944
lex00 merged 5 commits into
mainfrom
live/stamp-retire-644

Conversation

@lex00

@lex00 lex00 commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

The shared-layer re-measure of the core set has NOT been run. No
gauntlet run, no emulator, no change to live/gauntlet.json or the
rendered progress pages, on the maintainer's instruction mid-unit. This PR
is the code half of #644 only, and it must not merge as progress until the
core set is re-measured by the nightly or a later pass. Everything below is
measured with instruments that need no cloud.

Closes the code half of #644.

The unit

Issue #644, ruled 2026-09-04 in the refactor-1500 epic (#831, see #825):
the stamp is retired, not split, once the live_plan.go split (#819) has
merged. #819 is closed. HANDOFF "The order" item 3 names the three pieces:
the HCL-rewriting stamp, module_prefix, and LayerStamp's refusals - and
says explicitly that the static evaluator does not retire.

929 insertions, 12,109 deletions across 59 files; the stamp package
itself goes from 15,758 lines across 62 files to 5,593 across 39.

Inventory: what the ruling retires

1. The HCL rewrite. internal/live/stamp/stamp.go (2633),
perinstance.go (358), sharedbody.go (143), and the 21 test files that
drive them. Stamp, Request, Result, Schemas, Skip, SkipReason
and its constants, Untagged, Stamped, and the whole stamper type:
markerObject, addressExpr, forEachLookupAddressExpr,
splitAddressMarker, templateChunkMarkers, chunkCount, slotExpr,
tagsWrite, mergeEntries, verify/verifyValue, mustStamp,
unstampable/unstampableAt, privateBody, moduleResources,
childExpansion. Every one of them was already dead on the default path:
#451 gated the pass off entirely when CHOUDOUFU_NODE_RESOLVE is on, which
it has been by default since 2026-08-25.

Its five copies of the config-subset evaluator (evalStatic,
staticValue, staticString, staticCount, staticForEachKeys) die with
it rather than migrating to internal/live/staticeval - the copies #934
deliberately left for this unit.

Callers, all updated:

caller what it was what it is now
live_plan.go statelessStamp the pass, plus three no-estate-name warnings statelessMarkerEstate - the warnings only
live_plan.go statelessStampGaps re-read the pass's report for silent skips gone; there is no report
live_plan.go statelessNeedsDiscovery, recordBackedNeedsDiscoveryBlocks inputs to the two above gone
live_mode.go the same pass on the apply path same
live_policy.go statelessPolicyUntagMap, statelessPolicyReport's stampRes declared_tagged = "untag"'s released keys gone - see "what this removes" below
internal/live/check already off it (#454's nodestamp.go) unchanged; uses the registry, the summaries and UnmarkedDiscoveryDetail
tools/refusal-probe/cause.go UnmarkedDiscoveryDetail, SummaryUnmarkedApply unchanged

2. module_prefix. markers.ModulePrefixAttr / ModulePrefixRef /
ModulePrefix (internal/live/markers/modulemarker.go); the
GetTerraformAttr arms in internal/configs/static_scope.go and
internal/tofu/evaluate.go; configs.StaticEvaluator.WithModuleInstance
and the moduleInstance/moduleInstanceSet pair that existed only to make
that arm answerable; lint's RuleReservedSymbol
(internal/live/lint/reserved_symbol.go) and its fixture; the two
internal/live/passthrough "Marker module prefix" entries.

The symbol existed for exactly one reader. A module call's several instances
share one *hclsyntax.Body, so #378 wrote a template over
${tofu.marker_module_prefix} because a configuration rewrite had no other
way to produce a different address per instance.
NodeResolver.AdjustConfigValue is handed one concrete
addrs.AbsResourceInstance and writes the escaped address as a plain
string.

Read before deleting, as the issue asks: internal/live/lint/module_instance_eval.go
(#580) does not use WithModuleInstance. It rebuilds a child's var.*
closure per instance through identity.ChildModuleRepetitionData and the
static evaluator's repetition axis, neither of which this PR touches.

Retiring the reservation is not a loss of protection: its purpose was to
keep a fork-added symbol out of a configuration's hands, and with the symbol
gone a configuration naming it gets stock OpenTofu's own "Unsupported
attribute", which is both correct and portable. Keeping the rule would mean
refusing a configuration for naming an attribute that does not exist.

3. LayerStamp's refusals. Five of eight retire, three stay:

summary verdict why
No configuration to stamp retired a guard on the rewrite's own Request struct. There is no Request.
No estate name to stamp with retired same
No provider schemas for marker stamping retired same
Two resources share one configuration body retired a diagnostic about the rewrite sharing one *hclsyntax.Body (#280). The node path is called once per concrete instance with its own value and rewrites no body; structurally absent, not merely unmeasured.
Ownership marker could not be checked retired "a tag is set to an expression this run cannot evaluate" is a property of reading HCL text statically. The node path receives the tag already evaluated, so the case does not exist: a disagreeing value is Ownership marker conflict, an agreeing one is a no-op.
Ownership marker conflict stays raised by internal/live/projection (SummaryMarkerConflict, matched text by construction) and surfaced under LayerStamp by check's port.
Ownership markers not stamped stays raised by check's nodeStampUnmarkedApply, and by statelessMarkerEstate's three no-estate warnings.
Unmarked apply of a marker-only resource stays raised by check's nodeStampUnmarkedApply. 55 corpus sites.

The registry stays in internal/live/stamp under RaisedByStamp, because
that is the layer a reader of live/LIMITATIONS.md looks the refusal up
under. refusals.go carries the per-entry account above.

What stays, and why

  • The static evaluator does not retire. HANDOFF item 3 is explicit; it
    is the estate-wide demand computation live-import, live-mv, live-check,
    discovery and the instruments all consume.
  • UnmarkedDiscoveryDetail (moved to unmarked.go) and its
    rendered-sentence tests. Two live callers, and the sentences are what an
    operator reads. discoverycause_test.go now drives it directly instead of
    through the deleted engine, keeping every wording assertion.
  • The taggability pin - taggability_test.go,
    taggability_live_test.go, taggability_survey_test.go and the 30
    stamp_cohort_*_test.go files. It pins which admitted types have a
    settable tags map, which is the same question the node writer's
    markers.TagSurface asks per instance, and
    internal/live/harness/burndown.go cites the chain by name.
    markerlessdocs_test.go and markeronlysplit_test.go never called
    Stamp at all.

What this removes that was not already dead

Two things, both already inert on the default path before this PR, both
named rather than dropped quietly:

  1. declared_tagged = "untag"'s marker suppression. It lived only in
    the HCL rewrite (stamp.Request.PolicyUntag), and the node writer has no
    equivalent, so it stopped happening on 2026-08-25 when
    CHOUDOUFU_NODE_RESOLVE defaulted on. The view's Untagged section has
    been empty for a fortnight. Porting it to AdjustConfigValue is real
    work with its own decision (a per-instance withhold, not a per-block one)
    and is not this issue's; live_policy.go carries the note.
  2. statelessStampGaps' plan-time "Unstamped marker-only resource"
    error
    and its foundation: a universal per-instance record, so every type stock supports is admitted and rung is a metric #364 record-backed downgrade. The node path never raised
    either. internal/live/check still reports the same finding offline, at
    the same 55 sites.

The safety fix this deletion forced

Deleting the rewrite left CHOUDOUFU_NODE_RESOLVE=0 with no marker
writer at all
- ConfigValueAdjuster nil, nothing stamping, every
resource created with no tofu-estate and no tofu-address. Silently. The
flag used to select two things at once, and only one of them still exists.

So the halves are separated (619a1f8): NodeResolver is built,
populated and installed as tofu.ConfigValueAdjuster for every run; the
flag governs only what it always named, whether identity resolution goes
through tofu.ResourceIdentityResolver. Two call sites used
resolver != nil as a stand-in for the flag and had to move to
nodeResolveEnabled(), because that expression is now always true:

Five-row table

difference row action
the HCL rewrite is a second implementation of what the node seam does per instance not a stock difference - a foundation item (HANDOFF "The order" 3) deleted
=0 would have had no marker writer handling it would write a wrong marker (its write-side twin: no marker at all, silently) the writer is unconditional; the flag keeps only the identity path
declared_tagged = "untag" has no node-path equivalent handling it would write a wrong marker if guessed at named, not guessed; unreachable implementation deleted, port left as its own work

The generic rule, and how many types it reaches

Zero hand-wired type names were added or removed in control flow. The
retired mechanism was never per type: mustStamp reads
identity.DiscoveryCause, taggability reads markers.Taggable /
markers.TagSurface off the provider schema, and the module-prefix template
was a property of module expansion, not of any resource type. The rule
that replaces the whole thing - "write the marker from the instance address
you already have" - reaches every admitted type: 1,699 rows in
identity.DefaultTable, of which the taggable population is what the
surviving pin in taggability_test.go covers.

live/derivation_guard_test.go's registry is unchanged, and correctly
so: it excludes _test.go files, and every type literal this PR deletes or
adds is in a test fixture or a pinned test table. Verified green.

Refusal registry, re-measured

refusal-probe -schemas -allow-partial-corpus, both runs in this worktree,
before at 22d9a3f099 and after at 0baf59209a.

The issue's baseline of sites 10363, instances 4912, blocked 203 at
5f2402e95a does not reproduce, and not because of this change
: the
corpus manifest has moved since (228 entries reachable here, against the
250/259 the older figures were taken over, and 5 manifest sources whose
globs match a repository root with no top-level configuration). The new
baseline, measured at 22d9a3f099 before a line was deleted, is
sites 8247, instances 4632, blocked 193, 228 entries. Both runs are
partial in the identical way, which is what -diff checks before it will
compare them.

sites      8247 -> 8247  (+0)
instances  4632 -> 4632  (+0)
blocked     193 ->  193  (+0)
unresolved  126 ->  126  (+0) module calls
by refusal ID: (nothing)
entries improved: 0        entries WORSE: 0

Per entry, not aggregate - all 27, no entry got worse and none moved at
all
:

Resolves at plan time via a data-source read     6073 -> 6073
unadmitted-type                                   651 ->  651
Unable to compute static value                    430 ->  430
logical-resource                                  295 ->  295
Dynamic value in static context                   208 ->  208
Non-static identity argument                       85 ->   85
Non-static for_each expression                     70 ->   70
Unresolvable identity                              64 ->   64
markerless-type                                    60 ->   60
Unmarked apply of a marker-only resource           55 ->   55
Ambiguous list-valued identity argument            55 ->   55
Non-static count expression                        52 ->   52
moved-block                                        37 ->   37
Identity not resolvable from configuration         32 ->   32
child-module                                       17 ->   17
Data source not readable before resolution         16 ->   16
count-index                                        11 ->   11
Data source provider not configurable              10 ->   10
Identity argument not set                           8 ->    8
Module output not supported in static context       5 ->    5
Invalid operand                                     4 ->    4
provisioner / module-providers / Null identity      2 ->    2  (each)
Two resources with the same identity                1 ->    1
Resource type outside the live-markers subset       1 ->    1
Sensitive count expression                          1 ->    1

The eight registry entries this PR deletes measured zero sites in the
before run
and so appear in neither column: the five stamp caller-error
and rewrite-mechanism refusals, reserved-symbol, and the two
Marker module prefix passthrough entries. Unmarked apply of a marker-only resource - the one nonzero stamp entry - is unchanged at 55, which is the
number that had to hold.

live/LIMITATIONS.md regenerated (go run ./tools/limits-gen): 223
refusals -> 215, 28 lint rules -> 27.

TestIdentityGolden

It moved, by exactly two operations, and neither is a changed identity.
0 identities changed across the branch. Never -update: the golden was
edited by hand and the test verifies the whole file including its body
digest, so the result is byte-identical to what -update would have
written.

1 removed: live/e2e/limits/reserved-symbol aws_s3_bucket.reserved,
CONCRETE, tofu-stateless-limits-reserved-symbol,
bucket=tofu-stateless-limits-reserved-symbol. That directory existed only
to give RuleReservedSymbol something to refuse; the rule retires, and
TestLimitsDirsMatchTable requires every live/e2e/limits directory to map
to a rule, so the fixture goes with it. A bucket's identity is its own
bucket argument, which the retired symbol never touched.

2 added: internal/live/check/testdata/stamp-uniquename-exempt
aws_cloudfront_cache_policy.exempt and .refused, both NEEDS_DISCOVERY,
both rendering nothing - which is what a server-assigned type renders
in this sweep, and the point of the fixture is that the two differ by
cause, not by identity.

Net: instances 1107 -> 1108, dirs 624 -> 624, CONCRETE 607 -> 606,
NEEDS_DISCOVERY 411 -> 413. Pins, the body digest and HANDOFF.md's figure
table all updated with the reason attached.

Assertions on rendered identities, never on a predicate

  • internal/live/stamp/discoverycause_test.go keeps every one of its
    sentence assertions on the string UnmarkedDiscoveryDetail returns, one
    per identity.DiscoveryCause, including the "six distinct sentences"
    check that catches a switch falling through.
  • TestStampGate_UniqueNameCauseIsExemptFromTheUnmarkedApplyRefusal (new)
    asserts on the rendered finding, not on BindsByName(). It replaces
    the exemption half of the deleted
    TestUnmarkedDiscoveryDetail_uniqueNameIsNotRefused, which had been the
    only test that would have caught the exemption's removal.
  • Both new guards were proved red on purpose:
    forcing mustStamp := true in nodeStampUnmarkedApply fails the
    exemption test with the exempt resource's own site quoted; re-gating the
    resolver population on r.nodeResolve fails
    TestStatelessBegin_nodeResolveOptOut with "the resolver was constructed
    but never given an estate name".

The exemption test's first draft used aws_cloudfront_origin_access_control
as its negative control and the control never fired - that type is
markerless-vetoed at LayerLint and never reaches the stamp layer. The test
said so rather than passing green, which is why the control is now the same
type on both sides: two aws_cloudfront_cache_policy instances, one stating
its documented account-unique name and one omitting it, one schema serving
both, so the only difference is the cause.

Tests

go build ./...                                              ok
gofmt -l <every .go file this branch touches>               clean
go vet ./internal/... ./tools/... ./live/...                clean
go test ./internal/live/...                                 ok   (incl. docrefs, marksafe,
                                                                  check, projection, identity,
                                                                  discovery, lint, stamp, harness)
go test ./internal/command/                                 ok   65s
go test ./internal/tofu/ ./internal/configs/                ok
go test ./live/                                             ok   (derivation registry, pins,
                                                                  golden pin, HANDOFF figures,
                                                                  ci coverage)
go test ./tools/... ./cmd/...                               ok

TestGodocCitationsResolve caught ten citations naming deleted symbols and
they were rewritten rather than de-bracketed (e070707).

Reproduce

git checkout live/stamp-retire-644
ln -s <a-tree-with>/.corpus .corpus
env -u PWD go run ./tools/refusal-probe -schemas -allow-partial-corpus -out after.json
git checkout 6e24aa99b4 -- . && env -u PWD go run ./tools/refusal-probe -schemas -allow-partial-corpus -out before.json
env -u PWD go run ./tools/refusal-probe -diff before.json,after.json
env -u PWD go test -count=1 ./internal/live/check -run TestIdentityGolden
env -u PWD go test -count=1 ./internal/live/check -run TestStampGate_UniqueNameCauseIsExempt
env -u PWD go test -count=1 ./internal/command -run TestStatelessBegin_nodeResolveOptOut

Not done, and why

🤖 Generated with Claude Code

https://claude.ai/code/session_018QSftBd7j3Dp4mnB6feRKL

lex00 and others added 5 commits September 6, 2026 22:55
The inventory this unit works from, taken at 22d9a3f before anything
was deleted.

WHAT THE RULING RETIRES (issue #644, ruled 2026-09-04 in #831/#825:
"the stamp is retired, not split"; HANDOFF "The order" item 3).

1. The HCL rewrite in stamp.Stamp - internal/live/stamp/stamp.go (2633),
   perinstance.go (358), sharedbody.go (143) and 21 test files that drive
   it (10,285 test lines). Entry points: Stamp, Request, Result, Schemas,
   Skip, SkipReason and its 30-odd constants, Untagged, Stamped.

   Callers, all of them already dead on the default path since
   CHOUDOUFU_NODE_RESOLVE flipped on 2026-08-25:
     internal/command/live_plan.go  statelessStamp (gated off at #451),
                                    statelessStampGaps,
                                    statelessNeedsDiscovery,
                                    recordBackedNeedsDiscoveryBlocks
     internal/command/live_mode.go  the same pass on the apply path
     internal/command/live_policy.go statelessPolicyUntagMap and the
                                    stampRes half of statelessPolicyReport
     internal/live/check            ALREADY off it (#454's nodestamp.go);
                                    uses only the summaries, the registry
                                    and UnmarkedDiscoveryDetail
     tools/refusal-probe/cause.go   uses only UnmarkedDiscoveryDetail and
                                    SummaryUnmarkedApply

2. module_prefix - markers.ModulePrefixAttr / ModulePrefixRef /
   ModulePrefix, the evaluator arms in internal/configs/static_scope.go
   and internal/tofu/evaluate.go, lint's RuleReservedSymbol that reserves
   the symbol, and the two internal/live/passthrough refusals that
   describe the static evaluator refusing it. Sole writer was stamp.go's
   modulePrefixTemplate. Landed in a later commit on this branch.

3. LayerStamp's refusals - five of the eight retire (NoConfig,
   NoEstateName, NoSchemas, SharedBody, MarkerUncheckable); three stay,
   because internal/live/check's node-path port and internal/live/projection
   still raise them. refusals.go carries the per-entry reason.

WHAT STAYS, AND WHY

 - The static evaluator does NOT retire. HANDOFF item 3 is explicit and
   #934 already made internal/live/staticeval the one copy; stamp's five
   copies (evalStatic, staticValue, staticString, staticCount,
   staticForEachKeys) die with stamp.go rather than being migrated.
 - UnmarkedDiscoveryDetail (moved to unmarked.go) and its rendered-sentence
   tests: two live callers, and the sentences are what an operator reads.
 - The taggability pin (taggability_test.go, taggability_live_test.go,
   taggability_survey_test.go, the 30 stamp_cohort_*_test.go files). It
   pins which admitted types have a settable tags map, which is the
   question the NODE path's markers.TagSurface asks per instance, and
   internal/live/harness/burndown.go cites the chain by name.
 - markerlessdocs_test.go and markeronlysplit_test.go: identity/row-gen
   guards that never called Stamp.

WHAT THIS COMMIT CHANGES BEHAVIOURALLY, on the CHOUDOUFU_NODE_RESOLVE=0
opt-out only (the default path is byte-identical, because every deleted
call was already gated off):

 - declared_tagged = "untag"'s marker suppression had no node-path
   equivalent and so has been inert since 2026-08-25; its unreachable
   implementation is deleted and named in live_policy.go's own comment.
 - statelessStampGaps' "Unstamped marker-only resource" plan-time error
   and its #364 record-backed downgrade go with it; the node path never
   raised either. internal/live/check still reports the same finding
   offline.

Reference: refusal-probe -schemas at 22d9a3f over the 228-entry corpus
reads sites 8247, instances 4632, blocked 193. Unmarked apply of a
marker-only resource: 55 sites. Re-measured after the deletion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QSftBd7j3Dp4mnB6feRKL
…644)

The symbol existed for exactly one reader, and that reader is gone.

tofu.marker_module_prefix (markers.ModulePrefixAttr) was this fork's one
addition to the language's terraform/tofu object. Issue #378 added it
because a module call's several instances share ONE *hclsyntax.Body for a
resource's tags argument, so no literal tofu-address in that body is right
for all of them - the module-instance segment had no expression the
language could interpolate, the way count.index and each.key cover a
repeated resource. The HCL rewrite wrote a template over it.

The node-path writer (projection.NodeResolver.AdjustConfigValue) is handed
one concrete addrs.AbsResourceInstance and its already-evaluated
configuration value, so it writes the escaped instance address as a plain
string. There is no shared body, no template, and nothing left that reads
the symbol. Deleted:

  internal/live/markers/modulemarker.go   ModulePrefixAttr, ModulePrefixRef,
                                          ModulePrefix
  internal/configs/static_scope.go        the GetTerraformAttr arm and its
                                          two refusals
  internal/configs/static_evaluator.go    WithModuleInstance and the
                                          moduleInstance/moduleInstanceSet
                                          pair, which existed only to make
                                          that arm answerable
  internal/tofu/evaluate.go               the plan-time arm
  internal/live/lint/reserved_symbol.go   RuleReservedSymbol, the whole rule
  internal/live/passthrough/refusals.go   the two "Marker module prefix"
                                          passthrough entries
  live/e2e/limits/reserved-symbol/        the rule's fixture

Checked before deleting, as issue #644 asks: internal/live/lint/
module_instance_eval.go (#580) does NOT use WithModuleInstance. It rebuilds
a child's var.* closure per instance through
identity.ChildModuleRepetitionData and configs.StaticEvaluator's repetition
axis, neither of which this commit touches. The static evaluator itself
does not retire - HANDOFF "The order" item 3 is explicit, and #934 already
made internal/live/staticeval the one copy.

Retiring the reservation is not a loss of protection. Its whole purpose was
to keep a FORK-ADDED symbol out of a configuration's hands; with the symbol
gone, a configuration naming tofu.marker_module_prefix gets stock
OpenTofu's own "Unsupported attribute", which is both the correct answer
and the portable one. Keeping the rule would have meant refusing a
configuration for naming an attribute that no longer exists.

TestIdentityGolden: 0 identities changed, 0 added, 1 removed. The one
removed row is live/e2e/limits/reserved-symbol's aws_s3_bucket.reserved,
CONCRETE, "tofu-stateless-limits-reserved-symbol",
bucket=tofu-stateless-limits-reserved-symbol - the fixture deleted above.
The golden was edited by hand rather than with -update, and the test
verifies the whole file including its body digest, so the result is
byte-identical to what -update would have produced. Pins updated with the
reason: identityGoldenPinInstances 1107 -> 1106, identityGoldenPinDirs
624 -> 623, classes CONCRETE 607 -> 606, identityGoldenPinBodyDigest, and
HANDOFF.md's figure table.

Regenerated: live/LIMITATIONS.md (`go run ./tools/limits-gen`), 223
refusals -> 215, 28 lint rules -> 27. Hand-written prose that described the
retired mechanism was rewritten rather than deleted, in LIMITATIONS.md's
"Behavioral limits", live/MARKERS.md and
site/content/docs/use/compatibility.md: what a keyed module's marker IS did
not change, only how it comes to be written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QSftBd7j3Dp4mnB6feRKL
Deleting the HCL rewrite left the opt-out with no marker writer at all.

Before this branch, CHOUDOUFU_NODE_RESOLVE=0 selected two things at once:
the static identity path AND internal/live/stamp's configuration rewrite.
The flag's own doc comment says so. With the rewrite gone, an opted-out run
would have wired neither writer - ConfigValueAdjuster nil, no stamping
anywhere - and created every resource with no tofu-estate and no
tofu-address on it. Silently, and unrecoverably for any instance a marker
is the only handle on. That is HANDOFF's "never write a wrong marker"
pointed at the write side, and it is not something this unit is allowed to
introduce as a side effect of a deletion.

So the two halves are separated. [projection.NodeResolver] is constructed
and populated for every run and installed as tofu.ConfigValueAdjuster
unconditionally; the flag now governs only what it always named, whether
identity RESOLUTION goes through tofu.ResourceIdentityResolver. The fields
only the identity path reads (RecordStore, MarkerIndex, NoSourceCreate,
Unowned) are set either way and are simply never read by an opted-out run,
because it never installs the method that reads them.

Two call sites keyed off "resolver != nil" as a stand-in for the flag and
had to move to nodeResolveEnabled(), because that expression is now always
true:

  - live_plan.go's identity.DowngradeForNodeResolution. Caught by
    TestLivePlan_identityFatal's own opt-out subtest going from exit 1 to
    exit 0: the static evaluator's "Identity argument not set" stopped
    being fatal and the run planned a CREATE for an instance nothing had
    identified. Exactly the failure that subtest exists to catch, caught
    by it. live_mode.go's copy already read r.nodeResolve and was right.
  - live_plan.go's recordShrinkStore (#388 edge 3), whose whole contract
    is that a flag-off run sees a byte-identical marker-sweep demand.

TestStatelessBegin_nodeResolveOptOut is rewritten to pin the new contract
on both sides - identity path off, writer on - and it asserts the writer by
VALUE (resolver.Estate is populated) rather than by presence, because
AdjustConfigValue writes nothing when Estate is empty and a constructed
but unpopulated resolver would stamp exactly as little as no resolver at
all. Proved red: re-gating the population block on r.nodeResolve fails it
with "the resolver was constructed but never given an estate name".

go test ./internal/command/: ok, 65.6s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QSftBd7j3Dp4mnB6feRKL
TestGodocCitationsResolve (internal/live/docrefs) named ten of them:
[stamp.Stamp] x7, [stamp.Request], [stamp.SkipReason.Unknown] x2, all in
internal/live/check, all pointing at symbols this branch deleted.

Rewritten rather than mechanically de-bracketed, because each was doing
real work in its comment: nodestamp.go's own doc comment is #454's argument
for why the port reports the same thing the rewrite did, and it now reads
as the record of a completed retirement instead of a pending one.
catalog.go's LayerStamp comment names [nodeStampDiagnostics] as what
actually computes the section. stamp.go's syntheticStampEstate argument -
that a synthetic estate name cannot manufacture a marker-conflict finding -
is unchanged in substance and now cites the functions that implement it.
stamp_gate_test.go's #230 invariant now cites nodeStampUnmarkedApply's
!hasSchema arm, which is where the rule lives.

go test ./internal/live/...: ok.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QSftBd7j3Dp4mnB6feRKL
The one exemption in nodeStampUnmarkedApply had no test of its own once the
stamp package's went with the engine.

A resource whose instances can only be found by their ownership marker is
refused when its type has nowhere to write one. identity.DiscoveryUniqueName
is exempt: AWS itself refuses to issue the name the configuration states
twice, so the object is findable in a listing with no marker, and refusing
it would refuse every apply of the population GitHub issue #272 admitted.
internal/live/check's port (nodestamp.go, #454) implements the exemption -
`mustStamp := !disco.Cause.BindsByName()` - and until now the only test that
would have caught its removal was internal/live/stamp's
TestUnmarkedDiscoveryDetail_uniqueNameIsNotRefused, which drove the deleted
HCL-rewrite engine.

TestStampGate_UniqueNameCauseIsExemptFromTheUnmarkedApplyRefusal is that
guard, on the surviving path, asserted on the rendered finding rather than
on a predicate. Its negative control is in the same fixture, the same run
and the SAME TYPE: two aws_cloudfront_cache_policy instances, one stating
its documented account-unique name (cause UNIQUE_NAME, must not be refused)
and one omitting it (the provider mints the whole identity, must be
refused). One schema serves both, so the only difference between them is
the cause resolution assigned - a port that stopped reading the cause fails
the first assertion and one that stopped refusing anything fails the
second.

Proved red: forcing `mustStamp := true` fails it with the exempt resource's
own site quoted. Restored, it is green.

The first draft used aws_cloudfront_origin_access_control as the negative
control (the type TestStampGate_GenuinelyUntaggableTypeStillRefuses uses)
and the control never fired: that type is markerless-vetoed at LayerLint
and never reaches the stamp layer at all. The test said so rather than
passing, which is why the control is there.

TestIdentityGolden: 2 ADDED rows, 0 changed, 0 removed - the fixture's two
instances, both NEEDS_DISCOVERY, both rendering nothing, which is what a
server-assigned type renders in this sweep. Combined with the removal in
de01425d29, this branch's net golden movement is 1107 -> 1108 instances and
624 -> 624 directories, 0 identities changed. Pins and HANDOFF.md updated
with both halves named; the golden itself was edited by hand, never with
-update, and the test verifies the file including its body digest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QSftBd7j3Dp4mnB6feRKL
@lex00
lex00 merged commit 5eb0fa7 into main Sep 7, 2026
3 checks passed
@lex00
lex00 deleted the live/stamp-retire-644 branch September 7, 2026 05:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant