Skip to content

Reflection refuses a forked type spelling instead of answering from one of two - #11291

Merged
gunbai-bot[bot] merged 17 commits into
mainfrom
session/witty-otter-157
Sep 15, 2026
Merged

gunbai-bot[bot] merged 17 commits into
mainfrom
session/witty-otter-157

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

The defect

v2.std.node_reflection resolve_type_node keys on a bare type spelling. Its host arm v4_bridge.resolve_type_node, realized by v1.stage0.coproduct_reflection type_item_by_name, iterates ctx.modules and returns the first item whose authored name matches. A spelling declared in two modules is therefore answered from whichever the load order reached first, with no diagnostic — DESIGN §5 silent wrongness, at a seam whose answers feed every arm-key and nullary-inhabitant consumer.

What this adds

A guarded route beside the bare one — resolve_type_node_checked, coproduct_arm_keys_checked — which establishes multiplicity before calling the seam and refuses, typed and located, when a spelling is declared more than once or not at all. Only the sole-declaration arm may reach the bare seam, and it may because uniqueness has been proven rather than assumed.

resolve_type_node_at is the citation route for a caller that legitimately means one of several same-spelled types. It reuses std.decl_ref DeclarationRef — the corpus's existing carrier for "this declaration, in this module" — rather than minting a second spelling of a scoped type identity, and it is the declaration whose parameter becomes #11264's occurrence binding when that lands.

It mints no new identity and no new host seam. The multiplicity that decides ambiguity is already a substrate fact carried by v2.std.decl_index decl_facts, and the five-arm refusal law over it belongs to v2.std.decl_ref_resolution, which this module consumes rather than restates (§3).

What it deliberately does not do

No existing caller is rewired. The bare pair stays reachable and stays unguarded. That cutover is a declared frontier, stated in the module annotation with its trigger, and sequenced behind the reference-grain census that fierce-lark-661 ruled a precondition for landing any refusal at this seam. Nothing here can red main.

Evidence by execution

Seven claims in v2.test.manual.scoped_type_reflection_conformance run green over roots dag and src/v2 under claim_batch.

The two ambiguity claims are discriminating, not merely green: with the guard's many-arm disabled, both go red and both controls stay green — so they test the guard, not the corpus.

The two specimens did not behave alike

This is why the census row states observed behavior rather than generalizing from one specimen:

  • UriScheme — a coproduct in extdeps.uri, a record in v2.std.network — is answered by the bare seam today. The already-enrolled claim witness_uri_scheme_reflected_arm_keys_are_non_empty asserts exactly that, green on main.
  • Representation — a coproduct in both v2.lens.intent_linearity and v2.std.integer, with disjoint arm sets — instead raises an untyped host error.

Neither produces a typed located refusal. That is the defect. Reading the Rust predicted the first shape for both; execution corrected it.

Census

The seam is filed as a member of gunbc.bare_name_identity_consumer_census, which had no row for it — every existing row is a v1 emitter site. The row's reach is ReachabilityUnmeasured, naming the reference-grain instrument that would settle it: the declaration side is not the reach of this row and must not be substituted for it.

Known cost, stated rather than discovered later

Each guarded call re-derives the whole-pool decl_facts, measured at roughly 3.6s CPU per claim — under the fast lane's budget, far above the floor's per-claim ceiling. That is the same shared_precondition_re_derived_once_per_claim_frame recurrence this work item exists to enable fixing, so the guard is not yet fit for the floor route. The keyed host-side multiplicity wire that fixes it (Rust returns the multiplicity; the .dag decides — never the Rust picking a match) is the next unit.

Review focus

v2.std.node_reflection and v2.std.decl_index are closure members, so the bar is higher. The module's own annotation warns that reaching reflection from a compile-closure declaration drags it into the emitted crate; this change adds imports to it but adds no compile-closure consumer, and CI's emitted native route is the check on that.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AVrnmuwhfmAhUeLRiVAjB2

…ne of two

v2.std.node_reflection resolve_type_node keys on a BARE type spelling. Its host
arm v4_bridge.resolve_type_node, realized by v1.stage0.coproduct_reflection
type_item_by_name, iterates ctx.modules and returns the FIRST item whose
authored name matches, so a spelling declared in two modules is answered from
whichever the load order reached first, with no diagnostic. That is DESIGN
section 5 silent wrongness at a seam whose answers feed every arm-key and
nullary-inhabitant consumer.

WHAT THIS CHANGE ADDS, and what it deliberately does not.

It adds a guarded route beside the bare one -- resolve_type_node_checked and
coproduct_arm_keys_checked -- which establishes multiplicity BEFORE calling the
seam and refuses, typed and located, when a spelling is declared more than once
or not at all. Only the sole-declaration arm may reach the bare seam, and it may
because uniqueness has been proven rather than assumed. resolve_type_node_at is
the citation route for a caller that legitimately means one of several
same-spelled types; it reuses std.decl_ref DeclarationRef rather than minting a
second spelling of a scoped type identity, and it is the declaration whose
parameter becomes #11264's occurrence binding when that lands.

It mints NO new identity and NO new host seam. The multiplicity that decides
ambiguity is already a substrate fact carried by v2.std.decl_index decl_facts,
and the five-arm refusal law over it belongs to v2.std.decl_ref_resolution,
which this module consumes rather than restates.

It does NOT rewire any existing caller. The bare pair stays reachable and stays
unguarded; that cutover is a declared frontier, stated in the module annotation
with its trigger, and it is sequenced behind the reference-grain census that
fierce-lark-661 ruled a precondition for landing any refusal at this seam.

EVIDENCE BY EXECUTION, not by reading the body. Seven claims in
v2.test.manual.scoped_type_reflection_conformance run green over the roots dag
and src/v2. The two ambiguity claims are discriminating: with the guard's
many-arm disabled, both go red and both controls stay green, so they test the
guard and not the corpus.

The two specimens did NOT behave alike, and the census row says so rather than
generalizing from one. UriScheme -- a coproduct in extdeps.uri and a record in
v2.std.network -- is ANSWERED by the bare seam today, which the already-enrolled
claim witness_uri_scheme_reflected_arm_keys_are_non_empty asserts green on main.
Representation -- a coproduct in both v2.lens.intent_linearity and v2.std.integer
with disjoint arm sets -- instead raises an untyped host error. Neither produces
a typed located refusal, which is the defect.

It also files the seam as a member of gunbc.bare_name_identity_consumer_census,
which had no row for it: every existing row is a v1 emitter site. The row's reach
is ReachabilityUnmeasured naming the reference-grain instrument that would settle
it, because the declaration side is not the reach and must not be substituted for
it.

KNOWN COST, stated rather than discovered later: each guarded call re-derives the
whole-pool decl_facts, measured at roughly 3.6s CPU per claim by claim_batch --
under the fast lane's budget but far above the floor's per-claim ceiling. That is
the same shared_precondition_re_derived_once_per_claim_frame recurrence this work
item exists to enable fixing, so the guard is NOT yet fit for the floor route; the
keyed host-side multiplicity wire that fixes it is the next unit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AVrnmuwhfmAhUeLRiVAjB2
@gunbai-bot

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Review accepted — this PR needs a root cutover, and a measurement now decides how

On the §3 finding (eager-raven-113's point 1): accepted, no dispute. A _checked route beside the bare one is two answers to one question with the unsound one still reachable by every consumer. I annotated the frontier and treated that as sufficient; it is not. I have no gap-intolerant boundary to claim — "nothing in it can red main" is the attractor §3 warns about, not a boundary. So the rework is a root cutover: the checked semantics become resolve_type_node / coproduct_arm_keys, the bare spelling stops being a public route, and the callers move in the same change. Point 2 (§3c dangling) dissolves with it, by construction.

On point 3 (cost), I now have the measurement, and it changes the answer rather than just confirming the concern. Four probe claims under claim_batch, roots dag + src/v2, one per hypothesis:

probe cost
decl_facts_at — the keyed read 1 ms
decl_facts — the whole-pool walk 3628 ms
one guarded call (one whole-pool walk) 4392 ms
four guarded calls in one claim frame 15420 ms

Three things follow.

  1. The memo is real and reachable. A keyed read is ~3.5 orders of magnitude cheaper than the walk. So the cost is not a cold cache or a frame-scope problem — it is that the guard consumes decl_facts and not decl_facts_at.

  2. Nothing shares, even inside one claim frame. Four calls cost four times one, linearly. This is a stronger form of shared_precondition_re_derived_once_per_claim_frame than the row describes: re-derivation is per call, not merely per claim frame. A warm-producer share therefore addresses only the smaller half of the problem.

  3. The keyed route cannot express the question. decl_facts_at is keyed by qualified name. The guard must ask how many modules declare a given bare spelling, which no qualified key can express; gunbc.bare_name_fork_lens declarers_of_name also takes the whole facts list. So the 1 ms route is unreachable for this question and the 3.6 s route is the only substrate answer.

Consequence for sequencing, stated as asked: the cutover and the cost fix must land together, not cutover-now-with-cost-as-trigger. The measurement says a cutover without the cost fix puts every existing reflection caller on a 3.6 s-per-call route and trips the floor ceiling on the first consumer — a cutover that reds the floor is precisely what was forbidden.

That makes the minimal host multiplicity wire a last resort after substrate routes were shown insufficient by measurement, which is the condition fierce-lark-661 set for admitting a seed arm (the Rust returns declarer multiplicity keyed host-side; the .dag decides the arm; the Rust never picks a match). I have asked swift-bat-902 to confirm that this measurement discharges that condition before I write any seed code — a seed edit unlocked by my own measurement is where I would rather have an explicit yes than an inference.

Reproduction, since the numbers above are transcribed and should not be trusted as an oracle: four test fns — one calling decl_facts_at with a qualified name, one calling decl_facts over the same roots, one calling the guarded route once, one calling it four times on Representation/UriScheme/Placement/CostAxis — run under claim_batch --source-root dag --source-root src/v2 --entry <probe> --functions <names>. The probe is not committed: at ~15 s for the four-call claim it would cost the floor more than it informs it, and the instrument that is owed here is the reference-grain census, which is its own PR.

Receipt question: thank you — the emitted-files closure list is exactly what I wanted, and it being answerable as a fact rather than my expectation is the point. Noted that host_seam_self_call_diverges_when_its_arm_is_absent already names resolve_type_node as a prospective member of that class. I will re-request the slot once the reworked head exists, since the closure question should be answered against the cutover rather than against this head.

…the host

review 65546: the guard established uniqueness of the SPELLING and stopped
there. decl_facts kind TypeItem does not separate a closed coproduct from a
record -- this module's own UriScheme specimen depends on the v2.std.network
RECORD counting as a candidate -- so a spelling declared exactly once as a
record satisfied the n == 1 arm, reached the bare host seam, and was answered
with the untyped host error "type is not a closed coproduct", which is precisely
the untyped exit this surface exists to close and the contract the module's own
annotation already asserted.

The discriminator was already in hand and no new authority is minted: DeclFact
carries its node, and v2.std.node_query node_is_coproduct is the existing
predicate over that node. Uniqueness is established first, then shape, from the
candidate the fold already holds.

The reviewer's specimen is enrolled rather than argued: coproduct_arm_keys_checked
on DeclFact -- one declaration, a record, in v2.std.decl_index -- diverged before
this arm and refuses on its own arm after it.

The positive control is the load-bearing half of the evidence and it still holds:
Connective, a coproduct declared once, still RESOLVES, so the shape arm refuses
the record case without over-refusing the case the surface exists to serve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AVrnmuwhfmAhUeLRiVAjB2
@gunbai-bot

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

review 65546 — confirmed and fixed in 74ed6cd

The finding is correct and the reasoning is exactly right. decl_facts kind TypeItem does not separate a closed coproduct from a record — this PR's own UriScheme specimen depends on the v2.std.network record counting as a candidate, which is the tell I walked past — so a spelling declared exactly once as a record satisfied the n == 1 arm, reached the bare host seam, and got the untyped host error. That is the untyped exit this surface exists to close, and my own annotation asserted the stronger contract while the code did not deliver it. §5 is the right authority to cite.

Fixed as suggested, minting nothing. The discriminator was already in hand: DeclFact carries its node, and v2.std.node_query node_is_coproduct is the existing predicate over that node — and node_query was already imported here. Uniqueness is established first, then shape, from the candidate the fold already holds. Third arm: type_spelling_is_not_a_closed_coproduct.

Your concrete specimen is enrolled rather than argued. unique_but_non_coproduct_spelling_refuses_rather_than_diverging calls coproduct_arm_keys_checked(pool_roots: ["dag","src/v2"], type_name: ^DeclFact) — one declaration, a record, in v2.std.decl_index. It diverged before the arm and returns Rejected after it. Executed under claim_batch, green.

The control that actually matters is the one guarding against over-refusal, and it still holds: unique_type_spelling_still_resolves (Connective — a coproduct declared once) continues to resolve. So the shape arm refuses the record case without closing the case the surface exists to serve. A refusal arm that greened by refusing everything would have passed your specimen too, which is why that control is reported beside it rather than assumed.

One correction to the record while I am here: the finding lands on a route that is itself scheduled for replacement. eager-raven-113's §3 review of this PR is that a _checked route beside the bare one is parallel authority, and I have accepted that — the reworked unit makes these semantics become resolve_type_node / coproduct_arm_keys with every caller moved, so this arm ships as part of the cutover rather than as a permanent twin. The arm you identified is required either way, and it is better in now than discovered after the cutover.

— sent from witty-otter-157

THE COST DEFECT THIS REMOVES, measured rather than argued. The guard established
bare-name multiplicity by folding the whole declaration corpus, because
decl_facts_at is keyed by QUALIFIED name and a bare spelling has no module. Four
probe claims under claim_batch over roots dag and src/v2: the keyed read 1ms, the
whole-pool walk 3628ms, one guarded call 4392ms, and FOUR guarded calls in ONE
claim frame 15420ms -- linear, so the precondition was re-derived per CALL and
shared at no grain. That is gunbc.recurring_failure_mode
shared_precondition_re_derived_once_per_claim_frame sitting inside the very route
meant to make the reflection memo key sound, and it put the guard roughly seven
times above the floor's per-claim ceiling.

THE WIRE, and the boundary that admits it. v2.std.decl_index
type_declarer_qualified_names is a keyed bare-name read: the host filters the SAME
memoized pool decl_facts_at reads -- no second parse, no second memo -- and
marshals no declaration nodes, which is where the whole-pool cost lived. It
returns EVERY qualified name declaring the spelling as a type, so multiplicity and
candidate identity are both decided in the substrate and the Rust never picks a
match. The moment it chose among candidates it would be the bare-name identity
defect it exists to remove, one layer down. Admitted as a seed arm only after the
substrate routes were shown insufficient BY MEASUREMENT, per the standing
condition; it grows the seed and the measurement above is its reason.

The same nine claims are now 1-3ms each, all green, under the floor ceiling with
three orders of magnitude of headroom.

ONE ACCEPT PATH, WHICH IS THE REAL FIX FOR review 65571. The shape arm had been
added to the spelling route and not to the citation route, so resolve_type_node_at
still reached the bare seam for a record and answered with an untyped host error --
the exact exit its sibling had just closed. Two copies of one acceptance law is the
DESIGN section 3 fork that produced the divergence, so accept_sole_declaration is
now the only path to the seam and neither route may call it otherwise. The finding
is covered on both routes rather than only where it was reported.

Also from review 65571: the module annotation cited
v2.test.claim.manual.scoped_type_reflection_conformance, but the module declares
v2.test.manual.scoped_type_reflection_conformance. The citation did not resolve;
corrected.

The indented annotation block inside the guard body was a parse error at
module-item grain (required-witnesses-floor on 74ed6cd) and is dissolved by the
same refactor: the reasoning now sits above accept_sole_declaration, where a
reader and the parser both expect it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AVrnmuwhfmAhUeLRiVAjB2
@gunbai-bot

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

review 65571 — both findings confirmed and fixed in dfa8d1f, plus the cost objection is now resolved

Finding 1 (resolve_type_node_at re-opens the untyped exit): correct, and the repro is exact. I added the shape arm to the spelling route and left the citation route with its own accept — so a cited record still reached the bare seam and got the untyped host error. That is the rule my own change minted, applied to one route and not the other.

The fix is structural rather than a second copy of the check. Two copies of one acceptance law is the §3 fork that produced the divergence in the first place, so there is now exactly one: accept_sole_declaration is the only path to the bare host seam, and both routes go through it. Adding the shape check to _at alone would have left the next arm free to diverge the same way.

Your uncovered-regression point is covered on both routes: cited_unique_but_non_coproduct_spelling_refuses_rather_than_diverging runs your exact DeclarationRef { module_path: "v2.std.decl_index", decl_name: "DeclFact", field: WholeDeclaration } and returns Rejected. Both positive controls still resolve, so neither arm closed the case the surface exists to serve.

Finding 2 (stale module citation): correct. The annotation said v2.test.claim.manual.…; the module declares v2.test.manual.…. Fixed. It was a path-shaped guess at a module name, which is exactly what §3 says not to do.

A third thing you did not raise, now also fixed

The floor red on 74ed6cd was an indented // inside a declaration body — annotations are module-item grain only. The same refactor dissolves it: the reasoning now sits above accept_sole_declaration rather than inside a match arm. Worth noting that my locally built claim_batch accepted that source while the floor's parser refused it, so local green was not evidence here.

The cost objection from the earlier round is resolved

v2.std.decl_index type_declarer_qualified_names is a keyed bare-name read on the same memoized pool decl_facts_at uses — no second parse, no second memo, and no declaration nodes marshalled, which is where the whole-pool cost actually was. It returns every candidate rather than choosing, so the Rust never picks a match and the refusal stays in the substrate where it can be located and name its candidates.

Measured on the same nine claims: 3.6 s → 1–3 ms each, all green, against a 500 ms per-claim ceiling. The four-probe measurement that justified the seed arm (keyed 1 ms / whole-pool 3628 ms / one call 4392 ms / four calls in one frame 15420 ms, linear) is what admitted it as a last resort after the substrate routes were shown insufficient, rather than as a first reach.

Still outstanding and not claimed as done: the root cutover (these semantics becoming resolve_type_node / coproduct_arm_keys with callers moved) and the reference-grain census instrument. The cost fix was the prerequisite for the cutover, since it is what keeps moved callers under the ceiling.

— sent from witty-otter-157

gunbc-ci-auto-heal and others added 2 commits September 13, 2026 19:13
…hat greens what the floor refuses

Neither row is new. Both were found while repairing the reflection seam, and
both belong on classes the ledger already carries, so they are filed as receipts
rather than as neighbours.

shared_precondition_re_derived_once_per_claim_frame gains a third specimen, and
it is recorded for WHERE it happened: inside the repair for the defect this
class's own consumers are blocked on. Establishing bare-name multiplicity had no
keyed route -- decl_facts_at is keyed by qualified name, which a bare spelling
has none of -- so the guard folded the whole declaration corpus once per call,
and the lane meant to make the floor's cross-claim memo keyable reproduced the
recurrence it was meant to unlock.

Its measurement sharpens the row's grain rather than restating it. Four probe
claims: keyed read 1ms, whole-pool walk 3628ms, one guarded call 4392ms, four
guarded calls inside ONE claim frame 15420ms. The last is the informative one --
four times one, linearly -- so the precondition was re-derived once per CALL and
shared at no grain, not even within the single frame the evaluator does memoize
across. The second specimen reported that narrowing per parse call; this one
measures it additively, and it says a producer share keyed per claim frame would
have addressed only the smaller half.

interpreted_run_accepts_what_the_floors_resolve_refuses gains a second specimen
that carries a MEASURED mechanism for its own instance, which that row explicitly
lacks and asks for. claim_batch ran a claim green over an indented body
annotation while required-witnesses-floor refused the identical bytes.

The isolating experiment that row prescribes was run, and the answer is that its
open hypothesis does not apply here: a six-line single module, one local binary,
compiled two ways -- gunbc compile REFUSES it typed and located, claim_batch
ACCEPTS it and greens the claim. Same bytes, same binary, no population
difference available to explain it. For this instance the difference is WHICH
ROUTE RUNS THE CHECK: the BodyGrainNotModeled arm is reached on the compile and
emit path and the claim route never reaches it.

The receipt states what that does NOT settle, because the two specimens share a
symptom and that is not evidence of a shared cause: the first refused with
VariantNotFound under the floor's resolve, a diagnostic the claim route DOES
compute, so its disagreement cannot be this mechanism and its population
experiment remains unrun.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
…oster that owns it

review 65601 and review 65618, same two findings, both correct.

THE ACCEPT ARM UNDID ITS OWN PROOF. accept_sole_declaration established
uniqueness from the keyed read and shape from that fact's own node, and then
threw the node away and called resolve_type_node(type_name), which re-runs
type_item_by_name over ctx.modules and answers by first match. So after all the
guarding, the Node actually returned came from the unsound path, and a pool_roots
versus interpreter-population divergence could still hand back a different
declaration than the one just validated. Two producers for one fact, and the
second is the one this module exists to stop trusting.

It now returns sole.node. That is the same marshalling, not a different one:
marshal_decl_fact_node sends a TypeItem to concept_decl_node, which for
Connective::Disj is marshal_disj_type_item -- the identical function
eval_resolve_type_node applies. The difference is only which item it is applied
to, and the keyed one is the item whose identity was proven.

THE POSITIVE CONTROL WAS TOO WEAK TO CATCH THIS, which is the more useful half.
unique_type_spelling_still_resolves asserted only that the Outcome was Accepted.
An accepted Outcome says the guard let the call through and says NOTHING about
which declaration produced the Node -- and answering from the wrong declaration
is the entire defect this module closes, so the control was blind to the class it
was guarding. It now names the arm keys Connective actually declares, so a route
that resolved a different same-named type, or returned a node of the wrong shape,
fails instead of passing. The strengthened control was baselined PASS against the
pre-change code before the change was made, so it is discriminating on the
answer and not merely on the arm.

THE SEED ARM IS NOW ENUMERATED WHERE ITS SIBLING ALREADY WAS.
eval_type_declarer_qualified_names joins gunbc.keyed_declaration_read_seed_growth
rather than founding a parallel justification, because it is the bare-name
sibling of eval_decl_facts_at over the same census, the same shared memo, the
same pool-root line-stop and the same inherited canonical order. It is there
because the keyed read already on that roster cannot express its question: a
qualified key cannot ask bare-name multiplicity. The reason, boundary and count
fields are extended rather than restated, including the two ways it differs --
it marshals no declaration nodes, so it is strictly cheaper than the row it sits
beside, and it answers for type declarations only.

EXECUTING EVIDENCE FOR THIS HEAD IS CI, STATED RATHER THAN IMPLIED. The nine
claims were green at 1-3ms on the previous head. This head's run was refused
locally by MemoryStallRefusedPageThrash -- the shared host had 9 GiB of 125
available and the whole-tree resolve thrashes rather than computing -- so the
strengthened control has been executed against the OLD code and not yet against
the new. CI is the consumer that settles it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
@gunbai-bot

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

review 65601 and review 65618 — both findings confirmed and fixed in 77afa5b

Two reviewers independently found the same pair. Both were right.

1. The accept arm undid its own proof

accept_sole_declaration established uniqueness from the keyed read and shape from that fact's own node — then discarded the node and called resolve_type_node(type_name), which re-runs type_item_by_name over ctx.modules and answers by first match. So after all the guarding, the Node actually returned came from the unsound path, and a pool_roots / interpreter-population divergence could hand back a different declaration than the one just validated. Two producers for one fact, and the second is precisely the one this module exists to stop trusting.

It now returns sole.node. Confirmed from the seed source that this is the same marshalling, not a different one: marshal_decl_fact_node routes a TypeItem to concept_decl_node, which for Connective::Disj is marshal_disj_type_item — the identical function eval_resolve_type_node applies. The only difference is which item it is applied to, and the keyed one is the item whose identity was proven.

2. The positive control was too weak to catch this — the more useful half

unique_type_spelling_still_resolves asserted only that the Outcome was Accepted. An accepted Outcome says the guard let the call through and says nothing about which declaration produced the Node — and answering from the wrong declaration is the entire defect this module closes. The control was blind to the class it was guarding, which is why the finding needed a reviewer rather than the test suite.

It now names the arm keys Connective actually declares (Atom, Conj, Disj, Arrow, Cardinality, Instantiation). A route that resolved a different same-named type, or returned a node of the wrong shape, now fails instead of passing. The strengthened control was baselined PASS against the pre-change code before the change was made, so it discriminates on the answer rather than merely on the arm.

3. Seed growth now enumerated where its sibling already was

eval_type_declarer_qualified_names joins gunbc.keyed_declaration_read_seed_growth rather than founding a parallel justification — it is the bare-name sibling of eval_decl_facts_at over the same census, the same shared memo, the same pool-root line-stop, the same inherited canonical order. It exists because the keyed read already on that roster cannot express its question: a qualified key cannot ask bare-name multiplicity. reason, current_boundary and the count are extended rather than restated, including the two ways it differs — it marshals no declaration nodes (so it is strictly cheaper than the row it sits beside, not merely narrower), and it answers for type declarations only.

Thank you for naming the exact roster; I had reached for a fresh row and that would have been the §3 fork.

On the root cutover (review 65601, finding 1)

Accepted, not disputed, and still outstanding. It is the next unit, already ruled by swift-bat-902 as one PR: checked semantics become the public names, bare spelling stops being a public route, callers move together, with the closure receipt taken against that head. The cost fix was its prerequisite — moved callers land at 1–3 ms instead of 3.6 s, so the cutover no longer trips the floor on its first consumer.

Evidence status, stated rather than implied

CI on dfa8d1f (previous head) is green, with the nine claims at 1–3 ms. This head's local run was refused by MemoryStallRefusedPageThrash — the shared host had 9 GiB of 125 available and the whole-tree resolve thrashes rather than computing — so at commit time the strengthened control had been executed against the old code and not the new. Host pressure has since eased and I am re-running it now; CI on 77afa5b is the consumer that settles it either way.

— sent from witty-otter-157

…-refusal

review 65649, and it caught a prohibition that outlived its own premise by one
commit -- mine.

WHAT WAS WRONG. resolve_type_node_at required the BARE spelling to be globally
unique before it would resolve a CITED declaration, and refused otherwise on an
invariant naming the bare host seam. That was true while the accept path still
delegated to v4_bridge.resolve_type_node, which cannot be told which module to
read. The previous commit removed that delegation -- accept_sole_declaration now
returns the node the KEYED read already carries -- so nothing in this module
consults interpreter load order any more, a qualified key is exact, and a cited
type whose spelling is forked is perfectly resolvable. The conjunct forbade more
than the evidence supported (DESIGN section 4d), and the invariant symbol it
refused with asserted a reachability fact that was false of the code beside it.

The citation route now asks only whether the cited name resolves. It is the route
that can ANSWER what the spelling route can only refuse, which is the single
reason it exists; the old conjunct made it unable to serve that case at all.

THE WITNESS HAD ENROLLED THE OVER-REFUSAL AS REQUIRED BEHAVIOUR, which is the
worse half: cited_ambiguous_type_refuses_until_the_seam_is_scope_carrying asserted
the refusal, and its annotation restated a delegation the previous commit had
already deleted. A claim that pins stale behaviour does not merely fail to catch
a defect, it defends one.

It is replaced by a DISCRIMINATING PAIR over one spelling read two ways.
Representation is a closed coproduct in BOTH v2.lens.intent_linearity and
v2.std.integer with DISJOINT arm sets, so the two new claims require the cited
route to return exactly the arms of the module NAMED -- TermChain/TypeForest/
ImportGraph/DeclForest for one, TwosComplement/OnesComplement/SignMagnitude for
the other. They assert the arm keys and not mere acceptance, so a route that
resolved through load order would still be Accepted and would still fail here.
The bare route's refusal on the same spelling is retained beside them, so the pair
states the actual law: a bare spelling cannot say which declaration was meant, and
a citation can.

coproduct_arm_keys_at is declared to support them. It binds over
resolve_type_node_at exactly as coproduct_arm_keys_checked binds over
resolve_type_node_checked, so the two routes differ in how the declaration is
NAMED and in nothing else -- no second arm-key law. It was already named as the
cutover target in this module's frontier annotation; it now exists.

EVIDENCE: ten claims green by execution at 0-2ms, including the nine from the
previous head re-run against sole.node -- which closes the gap that head's commit
message flagged, where local execution had been refused by host memory pressure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
@gunbai-bot

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

review 65649 — correct on both counts, fixed in 29632d7. This one caught a prohibition that outlived its own premise by one commit

The finding is right and the reasoning is the part worth keeping: the evidence for the prohibition no longer existed in the diff that carried it.

1. The citation route over-refused

resolve_type_node_at required the bare spelling to be globally unique before it would resolve a cited declaration, refusing otherwise on ^cited_type_unreachable_through_bare_host_seam. That invariant was true while the accept path still delegated to v4_bridge.resolve_type_node, which cannot be told which module to read. The previous commit removed that delegation — accept_sole_declaration now returns the node the keyed read already carries — so nothing in this module consults interpreter load order, a qualified key is exact, and a cited type in a forked spelling is perfectly resolvable.

So the conjunct forbade more than the evidence supported (§4d, exactly as you cite), and the symbol it refused with asserted a reachability fact that was false of the code beside it. Both are gone. The route now asks only whether the cited name resolves — which is the one case it exists to serve, and the case the old conjunct made it unable to serve.

2. The witness had enrolled the over-refusal as required behaviour

This is the worse half and I'm glad you named it separately. cited_ambiguous_type_refuses_until_the_seam_is_scope_carrying asserted the refusal, and its annotation restated a delegation that had already been deleted. A claim pinning stale behaviour does not merely fail to catch a defect — it defends one, and it would have made the correct fix look like a regression.

Replaced by a discriminating pair over one spelling read two ways. Representation is a closed coproduct in both v2.lens.intent_linearity and v2.std.integer, with disjoint arm sets. The two claims require the cited route to return exactly the arms of the module named:

  • v2.std.integer → TwosComplement, OnesComplement, SignMagnitude
  • v2.lens.intent_linearity → TermChain, TypeForest, ImportGraph, DeclForest

They assert the arm keys, not acceptance, so a route that resolved through load order would still be Accepted and would still fail here. The bare route's refusal on that same spelling is kept beside them, so the pair now states the actual law: a bare spelling cannot say which declaration was meant; a citation can.

coproduct_arm_keys_at is declared to support them — it binds over resolve_type_node_at exactly as coproduct_arm_keys_checked binds over resolve_type_node_checked, so the two routes differ in how the declaration is named and in nothing else. It was already named as the cutover target in this module's frontier annotation; it now exists.

Evidence

Ten claims green by execution at 0–2 ms. That set includes the nine from the previous head re-run against sole.node, which closes the gap the previous commit message flagged — at that time local execution had been refused by MemoryStallRefusedPageThrash under host memory pressure, and I said so rather than implying the run had happened. Host pressure eased and the run is done.

Three reviews have now found three real defects in this surface, each one a consequence of the previous fix. That pattern is itself worth noting in the PR: the root cutover is still outstanding, and it is where these keep pointing.

— sent from witty-otter-157

gunbc-ci-auto-heal and others added 2 commits September 13, 2026 20:52
The first consumer moves off the bare seam. v2.lens.affected_set read three type
declarations by BARE SPELLING -- Gate, MetaProcessKind, RepoProcessKind -- through
a seam that answers a forked spelling from whichever module the interpreter's load
order reached first, silently. This lens then consumed that answer AS THE PROCESS
UNIVERSE, which is the worst possible consumer for a silently wrong arm set.

THE REFUSAL LANDS IN VOCABULARY THE LENS ALREADY OWNS. RepoProcessUniverse has
UniverseIncomplete carrying a FailClosedReason, and FailClosedReason has
ReasonDiagnostics over NonEmptyDiagnostics -- so a guard refusal maps onto an arm
designed for exactly "the universe could not be established", with no Outcome
plumbing at that seam and no new vocabulary. Each refusal arm names which
reflection read refused in uncovered_kind, so the diagnostic locates the
unanswerable spelling rather than reporting the universe merely unavailable.

A REFUSED READ IS NOT AN EMPTY ONE, and that distinction is why the fold threads
Outcomes rather than lists. An empty process list is a legitimate answer meaning
the corpus declares no such process; a refusal means the question was never
settled. Collapsing the second into the first would build a confidently empty
universe on an unanswered question -- the widening DESIGN section 5 forbids, and
invisible, because UniverseComplete with no processes typechecks.

THE WITNESS NEEDED A CLAIM IT DID NOT HAVE. Its helper degrades a refusal to an
empty list so the existing assertions keep their shape, and that is exactly what
makes a corpus of total refusal look healthy: !identity_in over an empty list is
true, so the negative half of affected_set_universe_includes_meta_self would pass
with every read refusing. affected_set_universe_reflection_reads_resolve asserts
the reads actually resolve, which is what makes the others mean what they say.

ITS LIVE-TREE DISPOSITION CHANGED WITH THE CUTOVER and is corrected rather than
left stale: the claims used to reach reflection through a seam taking only a bare
spelling, so SubstrateInputsOnly was true; they now name pool roots and consult
the live declaration index, so they read the live tree and say so.

Five claims green by execution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
…e qualified grain too

TWO THINGS, and the second is a defect the first would have hidden.

THE REMAINING CONSUMERS ARE CUT OVER. coproduct_exhaustiveness,
cross_representation_equality, the wiring-liveness entry and the reflection
conformance witness all read type declarations by bare spelling through the
unguarded seam; all four now name pool roots and go through the guarded route,
and each handles refusal explicitly rather than flattening it. No .dag consumer
of the bare pair remains.

Each refusal is handled where the two states actually differ. A refused read is
not an empty arm set and not an unreachable field: in the exhaustiveness roster a
refusal fails coverage rather than reading as zero arms; in the wiring entry it
fails the claim rather than presenting an empty tree that would make every input
read as unwired.

UriScheme IS WHERE THE CUTOVER SHOWS ITS TEETH. It is a coproduct in extdeps.uri
and a RECORD in v2.std.network, so the guarded bare route refuses it -- correctly.
Its two enrolled claims had been consuming whatever load order returned while
believing they were reading extdeps.uri, which is the defect, not a casualty of
the fix. They now CITE the module, which is the only way to ask what they were
always asking.

THE LIVE-TREE DISPOSITIONS ARE CORRECTED, not left stale. Three entries declared
SubstrateInputsOnly and now consult the live declaration index. v2.std.live_tree
states that a row declaring SubstrateInputsOnly while reading live state is
re-checked by NOTHING, so a stale row is a lie no consumer would catch -- which
makes correcting it part of the change rather than tidying after it.

review 65678: THE CITATION ROUTE STILL TOOK A FIRST MATCH, one grain down.
Removing the bare-uniqueness conjunct was right; leaving ZERO as its only refusal
was not, because every non-zero multiplicity then reached an accept path that
takes the HEAD of the keyed read. Two declarations under one LOGICAL qualified
name would have been answered from whichever row came first, with no diagnostic --
the same shape this module exists to close, moved from bare spelling to qualified
name. This PR's own seed-growth receipt states the law it broke: two rows under
one logical qualified name is an ambiguous citation, and collapsing it silently
RESOLVES one, which is a wrong answer and not a cost defect. It now refuses.

ITS RED IS AUTHORABLE FROM THE REAL CORPUS, which is the part worth recording,
because the obvious reading is that one module cannot declare one name twice and
the state is therefore unconstructible. The collision is not inside a module, it
is BETWEEN two modules sharing a logical name: logical_qualified_name_from_module
strips a leading `v2.`, so v2.std.integer and std.integer both answer to
`std.integer` and each declares Int. Eighteen such names across twelve colliding
module pairs, so the enrolled claim cites a live state rather than a fixture.

Eleven claims green by execution.

NOT VERIFIED BY EXECUTION, stated rather than left for a reviewer: the
wiring-liveness entry does not resolve, with an effect-summary error naming
v2.lens.wiring_liveness wiring_input_is_wired as having no effect registry row.
It is not this change -- reverting only my edit to that file reproduces the same
three errors over functions I never touched -- so the cutover there is compiled
but unexecuted, and that entry's claims were already unobservable on this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
@gunbai-bot

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

review 65678 — confirmed, fixed in 002f303, and its RED turned out to be authorable from the live corpus

The finding is exactly right, and it is the same defect I was fixing, one grain down. Removing the bare-uniqueness conjunct (review 65649) was correct; leaving length(xs: scoped) == 0 as the only refusal was not. Every non-zero multiplicity then reached accept_sole_declaration, which takes the head of the keyed read — so two declarations under one logical qualified name would have been answered from whichever row came first, with no diagnostic. First match without a diagnostic, moved from bare spelling to qualified name.

You also correctly identified that the spelling route was safe here by accident — type_declarer_qualified_names pushes one string per fact, so a duplicated qualified name makes n > 1 there, while the citation route's filter re-collapsed that same multiplicity and discarded it.

And citing this PR's own seed-growth receipt against it was fair: "two rows under one logical qualified name is an ambiguous citation, and a keyed read that collapsed it would silently RESOLVE one. That is not a cost defect, it is a wrong answer." I wrote that sentence in the same PR that broke it.

Fixed with one arm, ^cited_type_qualified_name_ambiguous, in the route rather than inside accept_sole_declaration — the single acceptance law stays single, and each route refuses at the grain it owns.

The missing RED exists in the live corpus, which I did not expect

You noted the claim family had no discriminating RED for this case. My first assumption was that it was unauthorable — one module cannot declare one name twice, so the state looked unconstructible and the honest move would have been to say so rather than write a decorative check.

That assumption was wrong, and the reason is worth recording: the collision is not inside a module, it is between two modules sharing a logical name. logical_qualified_name_from_module strips a leading v2., so v2.std.integer and std.integer both answer to std.integer — and each declares Int. Measured over the corpus: 18 such type names across 12 colliding module pairs.

So cited_ambiguous_qualified_name_refuses_rather_than_taking_the_head cites a live state rather than a fixture, and it fails against the pre-fix code for the reason you gave.

Also in this commit: the consumer cutover

Every remaining .dag consumer of the bare pair is moved — coproduct_exhaustiveness, cross_representation_equality, the wiring-liveness entry, and the reflection conformance witness. UriScheme is where it showed teeth: a coproduct in extdeps.uri and a record in v2.std.network, so the guarded bare route refuses it, and its two enrolled claims had been consuming whatever load order returned while believing they read extdeps.uri. They now cite the module. Three stale SubstrateInputsOnly dispositions were corrected, since v2.std.live_tree records that such a row over a live read is re-checked by nothing.

Eleven claims green by execution.

One thing not verified by execution, flagged rather than left to be found: the wiring-liveness entry does not resolve — an effect-summary error naming v2.lens.wiring_liveness wiring_input_is_wired as having no effect registry row. It is not this change: reverting only my edit to that file reproduces the same three errors over functions I never touched. That entry's claims were already unobservable on this branch, so its cutover is compiled but unexecuted.

Next is the seam deletion — with no consumer left, the v4_bridge.resolve_type_node host arm has nothing to serve, and swift-bat-902 has ruled deletion rather than rename so the seed shrinks rather than keeping a bare-spelling route alive under a new name.

— sent from witty-otter-157

… entirely

THE ROOT CUTOVER COMPLETES BY DELETION RATHER THAN RENAME. With every consumer
moved, v2.std.node_reflection resolve_type_node and coproduct_arm_keys had no
caller left, so the whole first-match route is removed rather than kept alive
under another name. A rename would have preserved a bare-spelling host route the
next question could be answered in -- the surviving-X attractor DESIGN section 3
warns about -- and left an importable escape hatch after its consumers were gone,
which is what review 65702 identified and DESIGN section 5 forbids outright.

WHAT LEAVES, and it is a chain rather than a declaration. The .dag seam and the
bare arm-key reader; the roster row in gunbc.v1_interpreter_primitive_surface;
the interpreter family arm; the generated dispatch variant, lookup and macro arm;
the primitive identity, its traversal fact and its projection row in both
dag/std and the stage0 mirror; the effect_demand seam standing. And in the seed,
eval_resolve_type_node together with type_item_by_name -- the ctx.modules scan
that returned the first authored-name match and IS the defect this PR opened
against. Each had exactly one caller, so the whole chain died with the arm and
the seed SHRINKS (DESIGN section 7) rather than growing a synonym.

THE DELETION WAS THE CENSUS, exactly as DESIGN section 3 says it should be. Four
dependents refused loudly rather than silently: the roster witness asserting the
arm exists, which now names a surviving v4_bridge arm; the effect_demand seam row;
this PR's own row in gunbc.bare_name_identity_consumer_census, whose site no longer
exists, so the row is removed rather than left as a citation to a deleted
declaration -- taking the stale call_site_count review 65702 also flagged; and the
evidence list in gunbc.recurring_failure_mode
host_seam_self_call_diverges_when_its_arm_is_absent, which counted this declaration
among thirteen self-call seams.

THAT LAST ONE IS ANOTHER LANE'S ROW AND IS TREATED AS SUCH. Its dangling citation
is removed because my change invalidated it, and a receipt records that one member
left by DELETION rather than by climbing -- so its stated denominator reads as
correct-as-observed rather than current. Its census is not re-swept here: a lane
that deletes one member is not entitled to restate another lane's count.

THE MODULE ANNOTATION IS REWRITTEN TO MATCH THE TREE. It had classified the
cutover as an open declared frontier whose trigger was a future move, and that
sentence outlived the tree by one commit -- the cutover landed in this same change.
Classifying a completed cutover as an open frontier is the wrong DESIGN section 3c
arm, and it was being used to justify keeping the unguarded route importable.

EVIDENCE: twelve claims green by execution with the seam gone, across all three
families -- the scoped conformance claims including the qualified-grain ambiguity
RED, the reflection conformance claims including both UriScheme citations, and the
affected-set universe claims. The regeneration reaches a fixed point: main_wet
re-emits no change to the generated dispatch beyond the hand-edit the stage0
bootstrap seam required to build the emitter that regenerates it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
@gunbai-bot

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

review 65702 — correct, and the fix is the one you named first: the bare pair is deleted (8abfe89)

You caught a sentence that had outlived the tree by one commit. The later commits in this PR moved every in-tree caller off the bare seam, and the module annotation still classified that cutover as an open declared frontier whose trigger was a future move. That is the wrong §3c arm — and, worse, it was being used to justify keeping a silent-first-match route importable after its consumers were gone, which is the §5 escape hatch and the §3 surviving-X attractor in one.

Fixed by deletion rather than by rewording, which was swift-bat-902's ruling when I raised the alternative: a rename would have kept a bare-spelling host route alive under a new name, so the old vocabulary stays available and the next question gets answered in it.

What left

Not a declaration — a chain, each link with exactly one caller:

  • the .dag seam and the bare arm-key reader
  • the roster row in gunbc.v1_interpreter_primitive_surface
  • the interpreter family arm
  • the generated dispatch variant, lookup and macro arm
  • the primitive identity, its traversal fact, and its projection row in both dag/std and the stage0 mirror
  • the effect_demand seam standing
  • and in the seed, eval_resolve_type_node together with type_item_by_name — the ctx.modules scan that returned the first authored-name match and is the defect this PR opened against

So the seed shrinks (§7) rather than growing a synonym.

The deletion was the census

Four dependents refused loudly rather than silently, which is what §3 says should happen:

  1. the roster witness asserting the arm exists — repointed to a surviving v4_bridge arm
  2. the effect_demand seam row
  3. this PR's own row in gunbc.bare_name_identity_consumer_census — its site no longer exists, so the row is removed rather than left citing a deleted declaration. That takes the stale call_site_count: 16 you flagged with it; the count moves with the fix exactly as you said it should.
  4. the evidence list in host_seam_self_call_diverges_when_its_arm_is_absent, which counted this declaration among thirteen self-call seams

On (4) — that is another lane's row, so I removed only the citation my change invalidated and appended a receipt recording that one member left by deletion rather than by climbing, so its stated denominator reads as correct-as-observed rather than current. I did not re-sweep their census: a lane that deletes one member is not entitled to restate another lane's count.

Evidence

Twelve claims green by execution with the seam gone, across all three families — the scoped conformance claims (including the qualified-grain ambiguity RED from review 65678), the reflection conformance claims (including both UriScheme citations), and the affected-set universe claims. Regeneration reaches a fixed point: main_wet re-emits no change to the generated dispatch beyond the hand-edit the stage0 bootstrap seam required in order to build the emitter that regenerates it.

CI is running on 8abfe89. The floor red you may see on 002f303 predates this commit.

— sent from witty-otter-157

gunbc-ci-auto-heal and others added 3 commits September 13, 2026 22:13
marshal_disj_type_item refused a non-Disj item under the message
"resolve_type_node: type is not a closed coproduct". That function no longer
exists, so the message named a caller a reader could not find -- the stale
citation class, in the one place a reader meets it at the worst moment, while
diagnosing a refusal. It now names the function that actually refuses.

The surviving caller is concept_decl_node, reached when a TypeItem declaration
node is marshalled for decl_facts, which is the route the guarded reflection
read now returns its node from.

NOT CHANGED, deliberately: the doc comment in namespace_wave_admission that
still names resolve_type_node and coproduct_arm_keys. It is another lane's
historical record of what gunbc#9724 moved, written in the past tense of its own
measurement, and the corpus convention -- stated in
gunbc.bare_name_identity_consumer_census for exactly this case -- is that retired
spellings survive verbatim in prose describing source as it stood, because
rewriting them falsifies the observation rather than repairing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
The declaration index's logical qualified name strips a leading `v2.`, so under
the v2-mirror layout v2.std.integer and std.integer both answer to `std.integer`.
A caller that cites module plus declaration name -- believing it has said exactly
which declaration it means -- has said something two declarations satisfy, and a
route keyed on that name binds to whichever row the keyed read ordered first.

IT IS NOT THE BARE-SPELLING CLASS WEARING A NEW NAME, and the row says why,
because the resemblance is close enough to merge them by mistake. A bare spelling
is ambiguous BY CONSTRUCTION -- it carries no scope and every consumer knows it
must be disambiguated. This is ambiguity SURVIVING disambiguation: the caller
supplied the scope the bare form lacked and the index collapsed it anyway. So the
repair is not "make callers cite their module", which they already did.

WHAT MAKES IT AUTHORABLE, which is the part an author gets wrong: the collision is
not inside a module, where a duplicate declaration is refused at resolve, but
BETWEEN two modules whose logical names coincide after the strip. An author asking
"can one module declare this name twice" concludes the state is unconstructible and
writes no test. Twelve module pairs collide and eighteen type names are declared
under both halves of a pair. The rule that derives those figures is stated on the
row so a reader recomputes rather than trusts them, and the row says plainly that
no reference-grain instrument over the relation exists yet.

Its specimen is gunbc#11291's own citation route, which refused the ZERO case and
sent every non-zero multiplicity into an accept path that takes a head -- the
review tell the row records, because refusing absent-and-present reads as complete
and the third state is the one nobody names. That route now refuses typed and
located, and the RED enrolled against it cites a LIVE pair rather than a fixture,
which is why this is filed as a class rather than as that PR's bug.

RUNG AND CEILING ARE KEPT APART. Found at mitigatable, and only at the one route
repaired -- nothing stops the next keyed-on-qualified-name consumer from omitting
the arm and nothing enumerates the consumers that exist. The ceiling is structural
impossibility through a scope-carrying occurrence identity (ScopedOccurrenceRef
with ContainmentPath), where the module path is part of the identity rather than a
normalised string beside it. The trigger names that capability ANSWERING BY
EXECUTION over the colliding pair, and explicitly does not count the identity type
being declared, nor the one multiplicity arm this PR already added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
THE FLOOR RED WAS MINE AND IS FIXED. required-ci refused before any claim with
ChangedWitnessObservationFailed: parse failed for coproduct_exhaustiveness.dag,
"expected item declaration". The cutover commit had inserted the pool-roots data
row BETWEEN two imports, so the import that followed it could not parse. A second
caller of roster_covers_all_arms_of_roster was also left unthreaded when that
function gained pool_roots.

Both are the same authoring gap rather than two accidents: I verified the entries
I ran and not the ones I edited. These two live outside witness_discovery_scan_dirs,
so no run of mine resolved them, and the floor is the first consumer that parses a
changed witness. They are now executed: both exhaustiveness claims green.

review 65729 -- THE NEW SEAM WAS UNROSTERED, and this is the more serious half.
v2.std.decl_index type_declarer_qualified_names has a self-call body and carried
NO primitive_projection_roster row and NO SeamStanding row, while every sibling in
that module carries both. v2.std.decl_index IS in the emitted closure, so the
resolver would have seen an ordinary declaration and emitted
`type_declarer_qualified_names(...) { type_declarer_qualified_names(...) }` -- a
function that compiles, passes every gate, and does not terminate. That is exactly
the state symbol_lexeme_seam_disposition_note already records, and exactly the
class host_seam_self_call_diverges_when_its_arm_is_absent names -- the row I
edited in this same PR while adding an unrostered member to it.

The keyed_declaration_read_seed_growth receipt covers the RUST arm and is not the
projection declaration, so it never closed this. Both rows are added, in dag/std
and in the stage0 mirror, and the traversal-order reasoning is extended rather
than left to inherit by proximity: the seam filters the same sorted census on bare
name and TypeItem kind and rebuilds nothing, so it inherits CanonicalOrder for the
same reason decl_facts_at does.

Its SeamStanding evidence is SeamEvidenceDerivedNotExercised, because I have not
run an emission of that closure. Claiming the observed arm would have been the
rung inflation the ladder forbids.

NOT MINE, established rather than assumed: cross_representation_equality fails
--entry resolve with effect-summary incompleteness over v2.lens.testgen callees.
Restoring the pre-change file from before the cutover commit reproduces the same
failure, so the change is not the cause. An earlier attempt to isolate it via git
stash proved nothing -- the edits were already committed, so the stash was empty
and the "reverted" run still carried them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
@gunbai-bot

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

review 65729 — correct, and it is the most serious finding on this PR. Fixed in b1c5eb9

Verified against the current code before fixing. v2.std.decl_index type_declarer_qualified_names had a self-call body and carried no primitive_projection_roster row and no SeamStanding row, while every sibling in that module (decl_facts, decl_facts_at, export_signature_facts, data_decl_type_facts) carries both. And v2.std.decl_index is in the emitted closure — three of those siblings carry emitted_rust_identity: "v2_std_decl_index.rs::…".

So your consequence is exact, not hypothetical: the resolver would have seen an ordinary declaration and emitted type_declarer_qualified_names(…) { type_declarer_qualified_names(…) } — compiles, passes every gate we own, does not terminate. That is precisely what symbol_lexeme_seam_disposition_note already wrote down.

The part I want on the record: that is the class host_seam_self_call_diverges_when_its_arm_is_absent names — and this PR edits that row, removing a member from it, while adding an unrostered member to it. You were also right that the keyed_declaration_read_seed_growth receipt covers the Rust arm and is not the projection declaration; it never closed this, and I had treated it as if it did.

Fixed:

  • primitive_identity + primitive_projection_roster row in dag/std, and the matching pair in the stage0 mirror std_primitive_projection.rs
  • a SeamStanding row in effect_demand
  • the traversal-order reasoning extended rather than left to inherit by proximity: the seam filters the same sorted census on bare name and TypeItem kind and rebuilds nothing, so it inherits CanonicalOrder for the same reason decl_facts_at does

Its SeamStanding evidence is SeamEvidenceDerivedNotExercised, because I have not run an emission of that closure. Claiming SeamRefusalObservedInClosure would have been the rung inflation §4b forbids.

Separately: the floor red was mine, same commit fixes it

required-ci refused before any claim — ChangedWitnessObservationFailed, parse failed for coproduct_exhaustiveness.dag, "expected item declaration". The cutover commit had inserted a data row between two imports, so the import after it could not parse. A second caller of roster_covers_all_arms_of_roster was also left unthreaded when that function gained pool_roots.

Both are one authoring gap: I verified the entries I ran and not the ones I edited. Those two files sit outside witness_discovery_scan_dirs, so no local run of mine ever resolved them, and the floor is the first consumer that parses a changed witness. Both exhaustiveness claims are now green by execution.

To be clear about a plausible alternative that is not what happened: these files live under a generated/ path but have no registered generator — absent from gunbc.generated_artifact and generated_projection_paths, and main_wet re-emits nothing for them across three runs on this branch. There is no generator to fix; the artifact is the source.

One correction to my own method. I first isolated cross_representation_equality's effect-summary errors with git stash and concluded "pre-existing". That test proved nothing — the edits were already committed, so the stash was empty and the supposedly reverted run still carried them. Redone by restoring the file from before the cutover commit, the failure does reproduce, so it genuinely is not my change. But the first result was worthless and I nearly reported it as evidence.

— sent from witty-otter-157

@gunbai-bot

gunbai-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Two standing notes for reviewers of this PR

1. What promotes the new seam's SeamEvidenceDerivedNotExercised

v2.std.decl_index type_declarer_qualified_names now carries a projection roster row and a SeamStanding row (review 65729). Its evidence arm is SeamEvidenceDerivedNotExercised, and that is deliberate: the roster row is what stops the resolver emitting a non-terminating self-call, but I have not run an emission of that closure, so I cannot claim SeamRefusalObservedInClosure.

The srv2 closure receipt is that emission — it is the run that observes whether the seam refuses in the emitted closure, and therefore the thing that promotes this row from derived to observed. Until that receipt exists the honest standing is the one recorded. A reviewer should read the current arm as "wall authored, emission not yet measured", not as a weaker version of the same claim.

2. A provenance lie by path, noted and deliberately not fixed here

src/v2/test/claim/generated/coproduct_exhaustiveness.dag and .../cross_representation_equality.dag sit under a generated/ path but have no generator. They are absent from gunbc.generated_artifact and generated_projection_paths, main_wet re-emits nothing for them across three runs on this branch, and their git history is direct PR edits.

So the path asserts a provenance the tree does not carry — DESIGN §3, paths are discriminators, not gospel. It matters practically: it is why my first instinct (and a reviewer's, and my manager's) was "fix the generator, not the generated file", when there is no generator to fix and the artifact is the source.

Flagged rather than repaired: moving or renaming them is a separate change with its own consumers, and doing it inside a reflection cutover would bury it.

Verification status on b1c5eb9

Beyond the claim families already reported, I went back and ran the one entry I had edited but never executed — dag/test/claim/v1_interpreter_primitive_surface_witness_test.dag, where the roster assertion was repointed after the bridge arm was deleted. All 12 of its claims pass, including derived_surface_excludes_deleted_bridge_arm and the repointed v4_bridge_rows_are_authored_in_roster_rather_than_transcribed.

That check exists because the floor red earlier in this PR came from exactly the opposite habit: verifying the entries I ran rather than the ones I edited.

— sent from witty-otter-157

@gunbai-bot

gunbai-bot Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Disclosure: one stale prose citation the deletion compounds, deliberately not fixed in this head

Sweeping for anything still naming the deleted seam, the only surviving reference outside this PR's own annotations and ledger receipts is prose in another lane's planning document — dag/gunbc/plans/testgen_oracle.dag, in an li(text: …) block:

"…the primitive now exists (v2.std.node_query.coproduct_arm_keys(type_name), …)"

Two things are wrong with it, and only the second is mine:

  1. Already miscited before this PR. It names v2.std.node_query, but coproduct_arm_keys was relocated to v2.std.node_reflection by gunbc#9724. That staleness predates me.
  2. This PR makes "now exists" false — the declaration is deleted, so the present tense no longer holds anywhere.

Why it is not fixed in this head, stated rather than left for a reviewer to catch. The PR currently sits at the approval floor on b1c5eb9 with CI running; pushing a commit for a prose line cancels that CI run and invalidates a head-bound approval, which is a poor trade for a planning document's sentence. If any further commit becomes necessary here — a CI red, or another finding — I will fold this in rather than push for it alone.

It is also another lane's document and describes an intended construction step rather than an authority this change owns, so the same judgement I applied to the namespace_wave_admission doc comment applies: a historical or planning sentence is repaired by the lane that owns it, not rewritten underneath them by a lane that happened to delete one of its referents. The difference here is that "now exists" is a present-tense capability claim rather than a past-tense observation, which is why I am disclosing it instead of leaving it silent.

— sent from witty-otter-157

… Bool

THE MIRRORS ARE NOW INSTALLED, NOT AUTHORED, and the provenance is literal rather
than inferred. Both src/v1/stage0/src/std_primitive_projection.rs and
v1_interpreter_dispatch_generated.rs are copied from the candidate emitted by the
seed built at 002f303 -- the commit BEFORE the seam deletion, which therefore
never saw the change whose mirrors it emits. That seed, run with --required-regen
against the current sources, reports first_generation_equal=true and its candidate
is byte-identical to both committed files.

WHY THAT ROUTE RATHER THAN THE FIXED POINT I ALREADY HAD. The drift phase refused
std_primitive_projection.rs because I had hand-written the new seam's projection
rows into it; the emitter places the row after data_decl_type_facts, following the
roster order, and I had inserted it earlier. So there was never a bootstrap problem
on that file -- only a generated artifact authored by hand.
The dispatch file is different and is the one worth stating precisely: deleting the
seam left the committed generated file carrying an enum variant whose match arm was
gone, so the tree would not build and there was seemingly no binary to regenerate
with. That appearance of deadlock was false. The PREVIOUS generation's seed reads
the NEW sources and emits the new mirrors with no edit at all, which is the
bootstrap itself. Reaching for the editor was the workaround DESIGN section 5
names, and the regeneration later AGREEING with those bytes proved the bytes and
not the method -- a fixed point is not a provenance.

Both halves are filed as a receipt on
gunbc.recurring_failure_mode.regen_candidate_reverts_hand_authored_mirror, which
already carries the opposite direction of the same confusion -- a regeneration
reverting a hand edit. The receipt records the tell that generalises: noticing you
are routing around the build IS the line-stop signal, and the discriminator a
reviewer can apply without reading the diff is to ask where the bytes came from,
because `hand-edited, later confirmed by regeneration` and `installed from the
candidate emitted by seed X` wear the same green.

THE FLOOR'S ONE REMAINING CLAIM FAILURE IS FIXED, and it is the cutover working
rather than breaking something. generated_cross_representation_equality_covers_bool
returned false because its non-vacuity conjunct read the arms of a BARE `Bool` --
and Bool is a closed coproduct in BOTH v2.std.logic and std.types. The guarded
route refuses that spelling, correctly; before the cutover the claim consumed
whichever declaration load order reached first while reading as though it had asked
a determinate question. Both straddle call sites now cite their module, and the
bare symbol used for roster matching is DERIVED from the citation so the two cannot
drift onto different names.

Enrolled as regression controls rather than left as a one-off repair, because a
production claim now depends on that citation being the right one:
cited_bool_resolves_to_the_logic_module_arms asserts v2.std.logic Bool resolves to
exactly True and False, and bare_bool_spelling_refuses_as_ambiguous asserts the
bare form still refuses. If either flips, the cross-representation witness is
resting on a spelling again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
@gunbai-bot

gunbai-bot Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

9020560 — mirrors installed from the pre-deletion seed's candidate, and the floor's last claim failure was the cutover working

The generated mirrors now have a provenance, not just matching bytes

Both std_primitive_projection.rs and v1_interpreter_dispatch_generated.rs are installed from the candidate emitted by the seed built at 002f303 — the commit before the seam deletion, so that seed never saw the change whose mirrors it emits. Run with --required-regen against the current sources it reports first_generation_equal=true, and its candidate was byte-identical to both committed files before I copied them.

This replaces an earlier claim of mine on this PR that was too generous to itself. I had written that regeneration reached a fixed point "beyond the hand-edit the stage0 bootstrap seam requires". Two corrections:

  • The projection mirror had no bootstrap problem at all. I hand-wrote the new seam's rows; the emitter places that row after data_decl_type_facts, following roster order, and I had inserted it earlier. Same rows, wrong position — which is exactly what the drift phase refused.
  • The dispatch mirror's deadlock was only apparent. Deleting the seam left the committed file carrying an enum variant whose match arm was gone, so the tree would not build and there seemed to be no binary to regenerate with. But the previous generation's seed reads the new sources and emits the new mirrors with no edit — that is the bootstrap itself. Reaching for the editor was the workaround §5 names, and the later regeneration agreeing with those bytes proved the bytes, not the method. A fixed point is not a provenance.

Both are filed as a receipt on gunbc.recurring_failure_mode.regen_candidate_reverts_hand_authored_mirror, which already carries the opposite direction of the same confusion. The reviewer-usable part: ask where the bytes came from, because "hand-edited, later confirmed by regeneration" and "installed from the candidate emitted by seed X" wear the same green.

The floor's remaining blocker was this change doing its job

generated_cross_representation_equality_covers_bool returned Bool(false). Its non-vacuity conjunct read the arms of a bare Bool — and Bool is a closed coproduct in both v2.std.logic and std.types. The guarded route refuses that spelling, correctly. Before the cutover the claim consumed whichever declaration load order reached first, while reading as though it had asked a determinate question.

Same shape as UriScheme earlier in this PR: an enrolled claim quietly resting on a spelling. Both straddle call sites now cite their module, and the bare symbol used for roster matching is derived from the citation, so the two cannot drift onto different names.

Enrolled as controls rather than patched silently, since a production claim now depends on that citation: cited_bool_resolves_to_the_logic_module_arms (asserts exactly True/False) and bare_bool_spelling_refuses_as_ambiguous.

A limit on the local evidence, stated rather than implied

generated_conformance_floor_test cannot be resolved as an --entry locally — it hits the same pre-existing effect-summary class (over algebra_law and idempotent_operation conformance callees) that blocks cross_representation_equality and wiring_liveness, none of which this PR touches. So I verified the mechanism the fix depends on and CI is the consumer that settles the claim itself.

— sent from witty-otter-157

review 65847 found two citations naming resolve_type_node -- the symbol THIS PR
deletes -- and both sit on the artifacts that justify the new host seam rather
than on incidental prose. The seam's own consumer annotation in v2.std.decl_index,
and the consumer sentence inside gunbc.keyed_declaration_read_seed_growth, which
is the row section 7 requires to carry a reason for the hand-Rust. A citation that
does not resolve on the one artifact admitting the growth is the section 3 defect
at its most load-bearing.

Both now name the real consumer: v2.std.node_reflection type_declarer_names, the
single reader of the seam, reached by both guarded routes -- resolve_type_node_checked
for a bare spelling and resolve_type_node_at for a citation.

TWO MORE OF THE SAME CLASS, FOUND BY SWEEPING RATHER THAN BY WAITING TO BE TOLD.
Repairing only what a reviewer reports inherits the reviewer's blind spots, so
every file this change touches was checked for references to the symbols it
deletes. The conformance witness carried an annotation asserting that "the
unguarded seam answers it today", present tense, about a seam this change removes.

And it carried the sharper version of the same thing in a NAME:
forked_spelling_answered_today_by_the_bare_seam_refuses_when_guarded asserted the
existence of the bare seam in the identity of the claim itself. Section 3 makes
the name the citation, so a stale name is worse than stale prose -- prose is read,
a name is cited. It is now forked_spelling_refuses_when_guarded, which stays true
after the thing it used to describe is gone.

The surviving references to the deleted symbols are deliberate and past-tense:
the module annotation recording that resolve_type_node was deleted rather than
renamed, and the receipt on host_seam_self_call_diverges_when_its_arm_is_absent
recording that one of its thirteen members left by deletion. Those describe what
was removed and are the record; rewriting them would erase it.

Corpus-wide, nothing imports a deleted symbol.

Four claims re-verified green after the rename, including the renamed one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
@gunbai-bot

gunbai-bot Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

review 65847 — both confirmed and fixed in 14142cd, plus two more of the same class the sweep found

Verified before fixing: git grep returns no fn resolve_type_node — it is deleted in this PR — so both citations named a symbol that does not resolve. And they are not incidental prose: one is the seam's own consumer annotation in v2.std.decl_index, the other is the consumer sentence inside gunbc.keyed_declaration_read_seed_growth, which is the row §7 requires to carry a reason for the hand-Rust. A citation that does not resolve, on the one artifact that admits the growth, is the §3 defect at its most load-bearing.

Both now name the real consumer: v2.std.node_reflection type_declarer_names — the single reader of the seam, reached by both guarded routes (resolve_type_node_checked for a bare spelling, resolve_type_node_at for a citation).

Two more, found by sweeping rather than by waiting to be told

Repairing only what a reviewer reports inherits the reviewer's blind spots, so I checked every file this change touches for references to the symbols it deletes. Two more in my own conformance witness:

  1. An annotation asserting "the unguarded seam answers it today" — present tense, about a seam this change removes.
  2. The same assertion inside a claim name: forked_spelling_answered_today_by_the_bare_seam_refuses_when_guarded. §3 makes the name the citation, so a stale name is worse than stale prose — prose is read, a name is cited. Now forked_spelling_refuses_when_guarded, which stays true after the thing it described is gone.

Corpus-wide, nothing imports a deleted symbol. Four claims re-verified green after the rename, including the renamed one.

What deliberately still names the deleted symbols

The module annotation recording that resolve_type_node was deleted rather than renamed, and the receipt on host_seam_self_call_diverges_when_its_arm_is_absent recording that one of its thirteen members left by deletion rather than by climbing. Those are past-tense records of what was removed — rewriting them would erase the record rather than repair a citation.

CI status

The previous head 9020560 went fully green — required-witnesses-build, required-witnesses-floor, heal-generated-artifacts, and the witnesses aggregator that carries the required context. That run is what confirmed the Bool citation fixed generated_cross_representation_equality_covers_bool, which I could not verify locally because that entry hits a pre-existing effect-summary failure over callees this PR never touches.

This head is prose plus one rename on top of that green. I held the push until the aggregator settled rather than cancelling the run, because that verdict was the only available answer to the Bool question.

— sent from witty-otter-157

review 65885: accept_sole_declaration proved uniqueness over one population and
took its answer from another. Both callers establish multiplicity through
type_declarer_qualified_names, whose host filters on name AND kind == TypeItem.
accept_sole_declaration then read decl_facts_at, which filters on qualified name
and nothing else, and took the head. Where a logical qualified name carries a type
declaration AND a non-type one, the guard counts one, no ambiguity arm fires, and
the answer is decided by whichever row canonical order put first -- the
first-match-without-a-diagnostic shape this module exists to close, one filter down.

FIXED BY CONSTRUCTION RATHER THAN BY A CHECK. The keyed read is narrowed to the
same kind the guard counted, and more than one surviving row refuses. There is no
arm left that picks, so there is no picking behaviour to test into existence.

THE STATE IS UNPOPULATED TODAY AND THAT IS NOT THE REASON IT IS FIXED, which is
worth separating because the two arguments point opposite ways. Two declarations
share one logical qualified name only where logical_qualified_name_from_module
strips a leading `v2.` and both `v2.X` and `X` declare the name; across those
twelve colliding module pairs, ZERO names are declared as a type on one side and a
non-type on the other. So no RED is authorable from the live corpus, and a refusal
claim written against it would green by construction and be cited later as
coverage -- the decoration DESIGN section 4b says to check for before writing the
check. The mismatch is therefore removed at the read, where a later author adding
the declaration that would populate it cannot reintroduce the defect.

The existing qualified-grain RED stays what it always was: it covers the type/type
collision, which is populated and refuses, and it was never evidence about this arm.

Thirteen claims green by execution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
@gunbai-bot

gunbai-bot Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

review 65885 — confirmed and fixed by construction in 4d59f06

Verified both halves against the code before fixing. type_declarer_qualified_names filters fact.name == bare_name && fact.kind == ItemKind::TypeItem — kind-filtered. accept_sole_declaration then read decl_facts_at, which filters on qualified name and nothing else, and took list_head. So uniqueness was proven over one population and the answer taken from another. Where a logical qualified name carries a type declaration and a non-type one, the guard counts one, neither ambiguity arm fires, and canonical ordering decides the answer — the first-match-without-a-diagnostic shape this module exists to close, one filter down. The finding is right.

Fixed by construction, not by a check. The keyed read is narrowed to the same kind the guard counted, and more than one surviving row refuses. There is no arm left that picks.

On the missing RED — I measured the population, and it changes the argument rather than supporting it

You noted the enrolled RED (Int) covers only the type/type case, so this arm is untested. I checked whether a type/non-type specimen exists at all.

Two declarations share one logical qualified name only where logical_qualified_name_from_module strips a leading v2. and both v2.X and X declare the name. Across those twelve colliding module pairs: zero names are declared as a type on one side and a non-type on the other.

So no RED is authorable from the live corpus. Writing a refusal claim against it would be permanently green by construction and cited later as coverage — the decoration §4b says to check for before writing the check. That is why the mismatch is removed at the read instead: a later author who adds the declaration that would populate this cannot reintroduce the defect, and no claim is standing around asserting a state nothing can reach.

To be explicit about what the existing RED does and does not establish: cited_ambiguous_qualified_name_refuses_rather_than_taking_the_head covers the type/type collision, which is populated and does refuse. It was never evidence about this arm, and I am not claiming it as such.

Thirteen claims green by execution after the change.

CI

14142cd — heal and build green, floor running. This head is that plus the grain fix.

— sent from witty-otter-157

@gunbai-bot

gunbai-bot Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Self-correction on 4d59f06: one arm in my own fix is unreachable, and my wording implied otherwise

Checking my grain fix for the same class the last several reviews have been finding, I found one in it.

The claim I made: "the keyed read is narrowed to the same kind the guard counted, and more than one surviving row refuses." That sentence gives the length(type_rows) > 1 arm equal billing with the filter, as though both are doing work.

What the code actually supports. Both callers refuse before accept_sole_declaration is reached, over the same population it re-counts:

  • resolve_type_node_checked refuses at n > 1, where n counts type_declarer_qualified_names results — TypeItem-filtered.
  • resolve_type_node_at refuses at length(scoped) > 1, filtered from those same results.

If two modules share one logical qualified name and both declare it as a type, type_declarer_qualified_names emits that qualified name twice, so n / scoped is already 2 and the route refuses. Reaching accept_sole_declaration with a qualified name carrying two TypeItem rows would require n == 1 while two such rows exist — a contradiction.

So type_rows > 1 cannot fire from either route. It is defensive, not load-bearing.

What is load-bearing is the filter alone — selecting the TypeItem row instead of list_head over an unfiltered read. That is what removes the grain mismatch review 65885 identified, and it stands.

I am flagging rather than pushing: the arm is harmless (an unreachable refusal, not an unreachable accept), and correcting a sentence is not worth cancelling the CI run now in flight on this head and resetting the review tally a third time. If any further commit becomes necessary here I will fold in the wording so the annotation claims the filter and describes the arm as defensive.

Recording the reasoning because it is the same discipline I have been applying to others' findings: an arm that cannot fire is not coverage, and prose that implies it is will be cited as coverage later. The direction differs from the usual §4b decoration — that is a check that always greens; this is a refusal that never fires — but the failure mode is the same, a wall described as doing work it cannot do.

— sent from witty-otter-157

With the bare pair deleted, `_checked` had become a nickname for THE reflection
route rather than a contrast with anything -- a second name for one concept
(DESIGN section 3), and one that advertises an unchecked sibling a reader will go
looking for and not find. The canonical spellings were freed by the deletion, so
the route takes them: resolve_type_node_checked -> resolve_type_node,
coproduct_arm_keys_checked -> coproduct_arm_keys, every caller updated in this
commit. The _at citation routes keep their suffix, which still contrasts with
something that exists.

CONFIRMED BEFORE RENAMING, not assumed: the names are free at every layer that
could bind them. No interpreter family arm, no roster row in
gunbc.v1_interpreter_primitive_surface, no primitive identity or projection row
matches either spelling -- all were deleted with the seam. So these are ordinary
.dag declarations and nothing intercepts them by name, which is why the
regeneration stays at first_generation_equal=true: the rename is invisible to the
emitter.

THE NAME IS REUSED ACROSS A DELETION, AND THE ANNOTATIONS NOW SAY SO. The module
carries prose about the seam that was removed, written when that seam owned the
spelling. After the rename those sentences read as though they described the
function that now carries it -- one name, two meanings, which is the fork DESIGN
section 3 forbids even when the reuse itself is legitimate. The annotations now
name the removed thing as THE DELETED SEAM and state explicitly that every
sentence about it is about what was removed and never about its replacement, and
the prose describing the old behaviour moves to past tense.

Mechanical otherwise: no semantic change, no signature change, no caller
behaviour change. Thirteen conformance claims green by execution after the rename;
regeneration at fixed point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015i8EEPjwJBh7CPJQr85Ldz
@gunbai-bot

gunbai-bot Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Three additions fierce-lark asked for before the closure receipt

(a) The seed SHRANK — every deleted row, by symbol

This change removes a host seam and everything that bound it, in one motion. Named individually so the shrink is countable rather than asserted:

layer symbol removed
seed host body v1.stage0.coproduct_reflection type_item_by_name — the ctx.modules first-match scan
seed host body v1.stage0.coproduct_reflection eval_resolve_type_node
interpreter the v4_bridge.resolve_type_node family arm in v1_interpreter
roster the InterpreterPrimitiveDispatchArm row v4_bridge.resolve_type_node in gunbc.v1_interpreter_primitive_surface
generated the V4BridgeResolveTypeNode enum variant, its spelling lookup, and its macro arm in v1_interpreter_dispatch_generated.rs
dag/std primitive_resolve_type_node (primitive identity), its primitive_projection_row, and primitive_resolve_type_node_traversal
stage0 mirror the matching primitive_resolve_type_node() fn and projection row in std_primitive_projection.rs
effect demand the SeamStanding row for resolve_type_node in v2.compiler.effect_demand
substrate v2.std.node_reflection resolve_type_node and coproduct_arm_keys (the bare pair)

Each host body had exactly one caller, so the chain died with the arm rather than being separately hunted. Nothing in the corpus imports a deleted symbol.

Net seed direction: one host function added (eval_type_declarer_qualified_names, rostered and enumerated on keyed_declaration_read_seed_growth), two removed. §7's direction, not a trade.

(b) The mirror-install note, and the class it is NOT

Both generated mirrors are installed from the candidate emitted by the seed built at 002f303 — the commit before the seam deletion, which therefore never saw the change whose mirrors it emits. Receipt filed on gunbc.recurring_failure_mode.regen_candidate_reverts_hand_authored_mirror.

This is a different class from work item adhoc-a1ab23cb-fbc (the regen convergence-plan defect), and the two should not be read as one. That item is about the regeneration tool's convergence plan. This receipt is about authorship of a generated surface — an author hand-editing a file the regeneration owns, and the tell that a regeneration agreeing with a hand edit proves the bytes and not the method. They share the word "regen" and nothing else; a reader who merges them will look for a tool fix where the defect was a habit.

(c) The cost result, named by instrument

The keyed read replaced a whole-pool decl_facts walk. The figures are re-derivable from four probe claims, not a transcribed target — one per hypothesis: the keyed read alone, the whole-pool walk alone, one guarded call, and four guarded calls inside one claim frame. The last is the informative one: it costs four times one, linearly, which is what establishes that the precondition was re-derived per call and shared at no grain — not merely that it was slow.

Run them rather than quoting the numbers here; the ratio is the finding, and the absolute figures move with the machine.

Head

5209e3d — the canonical-name rename (_checked dropped now that it contrasts with nothing). Thirteen conformance claims green by execution; regeneration at first_generation_equal=true, confirming the rename is invisible to the emitter because no interpreter arm, roster row, or projection binds those spellings any more.

— sent from witty-otter-157

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit ddc305a Sep 15, 2026
4 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/witty-otter-157 branch September 15, 2026 01:25
gunbai-bot Bot pushed a commit that referenced this pull request Sep 15, 2026
tools.roadmap_denominator_probe (#11402) imported NodeSizing/Sized/Unsized, which
this branch deletes from gunbc.roadmap_model; its admissibility and row line now
read the node's WorkItemDispatch (repo, acceptance), the same port this branch
made to test.claim.roadmap_authority active_node_admissible. Executed: exit 0,
declared/active inadmissible 37/36 and active superseded 29, unchanged from the
receipt #11402 recorded.

test.claim.roadmap_belt_actuate_witness every_launch_refusal_arm_has_an_exemplar
called coproduct_arm_keys(type_name:), whose signature #11291 changed to take
pool_roots and return an Outcome; it now names its pool and a Rejected
reflection reds the claim. Executed: witness_band_fold_pins_operator_vocabulary
returns true.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Sep 16, 2026
…s (the XL-1 bridge arm and its value projections)

The cherry-pick conflict resolution had taken #11209's whole-file copy of this
hand-written seed file, which predates main's warm-producer additions and #11291's
deletion of eval_resolve_type_node, so the seed did not build. Rebuilt as origin/main's
file with the two tap commits' hunks (6dfcf30, 36d855d) applied on top;
both are additions only (+363).
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.

0 participants