Skip to content

BL-2: split the subject roster from the execution batch, delete the aggregate helpers - #8208

Merged
briansrls merged 26 commits into
mainfrom
session/warm-fox-179
Aug 13, 2026
Merged

briansrls merged 26 commits into
mainfrom
session/warm-fox-179

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Draft until #8228 merges. This branch still carries BL-0's four files, so its diff currently duplicates #8228. Once that lands, merging main drops them and this PR reduces to the BL-2 census work alone. Holding it in draft rather than opening two PRs that both claim the same BL-0 change.

Reworked per the operator ruling of 2026-08-13, which requested changes on the combined PR and judged BL-0 good on its own. BL-0 was split out to #8228; the three BL-2 defects are repaired here.

1 & 2 — the aggregate helpers, deleted rather than fixed

count_stage_matching and retained_member_count are gone.

  • A refusal became an integer. The count answered 0 - 1 for CensusRefusedAtWorld and 0 - 2 for CensusRefusedAtReconciliation, so a typed refusal and a population count shared one type — a caller could add, compare or display a failure as a measurement, and -1 reads as a number rather than as a stop.
  • Presentation became semantic authority. It counted by comparing stage_label_of(s) against a String, but FrontendStage is a closed coproduct: a label rename would change the count with no stage changing, and a label collision would merge distinct stages.

Deleted rather than corrected because the module already states that the per-member observation is the finding and that no aggregate verdict is warranted over subjects not each separately established — the helpers contradicted the stated scope, so there was nothing for a fixed aggregate to mean. Both failures are recorded on the module so they are not reintroduced.

3 — the roster was wearing the population's name

Three rows were declared as body_lowering_population_roster while the prose said ten subjects. That made the exact planning error this instrument exists to detect representable in its own carrier.

Now two authorities:

  • body_lowering_subject_roster — the denominator, with identities cited rather than asserted. Two SH-D shards; five std members from the executed table in docs/plans/v2-frontend-std-ingestion-frontier-exact-head.md (logic, optional, diagnostic, occurrence_identity as its NORM_RETAINED rows, node as its NOT RE-OBSERVED row).
  • body_lowering_execution_batch — what one run selected, derived from the roster by label selection rather than re-authored beside it, so a batch row cannot name a path the roster does not, and a subject with no committed realization cannot enter a batch at all.

BodyLoweringObservationStanding is three states — SubjectObserved / SubjectUnmeasured{cause} / SubjectInterrupted{receipt} — because observed-or-absent cannot carry this population: dropped-for-cost, budget-interrupted and genuinely-measured are three positions with three remedies, and collapsing the first two into absence renders "we did not look" as "there is nothing there". SubjectInterrupted stays distinct because node's 900s overrun is a real lower bound on cost; body_lowering_prior_standing gives that arm its producer from the cited historical receipt, declared apart from this run so the two can never be read as one measurement.

The population is eight, derived rather than chosen

Seven exact identities plus one subject for the vacuity bare-expression specimen class. The class grounds; its members do not — vacuity_bool_witness_body_lowering_ceiling_note describes "many isolated configurations" and enumerates none, no fixture carries them, and its optional_absent copy is the same subject as the rostered optional member. Rostering three would have minted two identities to satisfy a prose sentence, which is what a roster exists to prevent.

The prose "ten" is recorded as retired, not silently corrected, so the next reader does not reconcile eight against it and reopen the question. The note also states that eight is not an authority either: completeness is an identity join, not a count equality (DESIGN §5), so a ninth grounded identity joins and the specimen placeholder dissolves into real members when committed.

Not done here

No new census run. The 2h51m ten-member failure bounds the next one, which should be one independent job per subject — each emitting its own identity, stage receipt, runtime and terminal standing — so the pathological subject is identified rather than attributed by file size.

gunbc-ci-auto-heal and others added 13 commits August 12, 2026 00:03
OtherAssembleRefusal is the catch-all arm of AssembleRejectionCause, and it
carried only a reason symbol. A reason symbol does not say WHICH file refused,
so recovering that meant re-lexing an entire import closure by hand — measured
on use_site_verdict, whose 15-file closure had to be folded through tokenize
one source at a time to learn that dag/std/algebra.dag was the refusing file.

The location is carried as a probe-owned projection rather than a bare Locus.
Locus admits NodeLocus and PortLocus, which have no manifest serialization; a
host emitter that errored on them would abort a 27-module survey over one
unrepresentable position. FrontierProbeCauseLocation is serializable by
construction and total, and its unavailable arm carries a typed reason so the
deficit is counted rather than fabricated or fatal.

Location is kept structurally separate from the cause key: two modules refusing
for the same reason at different positions are the same detailed cause, so a
clustering key reads the cause and not this field.

The survey binary is the third consumer and is updated with it — the DAG
carrier change alone would have emitted a manifest that no longer typechecks
against the new field, which is worse than dropping it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e its variants

The location was a field of OtherAssembleRefusal, which made a position
structurally part of what the refusal IS: two modules refusing for the same
reason at different positions read as different detailed causes at a
clustering key. It now sits beside the cause on AssembleRejectionDetail.

CauseLocationUnavailable { reason: Symbol } is replaced by the closed pair
CauseLocationPort { port } and CauseLocationNodeNotSerializable. A PortLocus
carries real serializable data and no longer degrades to a reason string;
only a Node, which has no manifest rendering, reaches an unavailable arm.

The survey emitter follows the field and the arms, and stays total.

Controls (all executed): each of the three Locus arms reaches its own arm;
the byte range is asserted exactly, so substituting WholeFile reds; and a
two-diagnostic control pins reason and location to the SAME diagnostic
(RED verified by asserting the tail's offset).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fixture was hand-edited to the reshaped carrier, which is exactly what
its own note forbids: a hand-written approximation tests the author's idea
of the format instead of the emitter's. These bytes are copied verbatim out
of a survey run at commit 2880549, tree 932acf8, executable sha256
677c1bf0673c70ef… over src/v2/compiler/use_site_verdict.dag, and the reload
half passes against them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eipts

frontier_gap_clustering_from_receipts counted blocker CLASSES and then wrote
a hardcoded located_reason literal into whichever branch fired, so the
verdict reported a reason no receipt had to carry: the one real survey
measures ^tokenize_lex_e1_unrecognized_char, which no branch could name. Its
HeterogeneousGaps arm reported module_count as the distinct-reason count,
which is a count of modules wearing the name of a count of reasons. And
count_receipts_with_located_reason, the function that reads the measured
axis, had zero callers.

Both numbers are now read off the receipts. CommonRoot requires unanimity
rather than a plurality, because any plurality bar is a threshold and a
threshold is the smuggled heuristic a closed system never needs. An empty
survey reports a distinct count of zero rather than becoming a common root.

Controls (executed, both REDs verified): a unanimous survey reports the
measured reason and counts the receipts carrying it; a mixed survey reports
2 distinct reasons over 3 receipts, where the old arm reported 3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
These eight files are the one-off measurement instruments behind the A/B
finding — the real-closure door probe, the cause and reason readouts, the
lex localizer and three tokenize fixtures. They were never meant to land;
the PR description says so explicitly. A blanket 'git add -A src/v2' put
them in the tree anyway, which is experimental residue and a description
that no longer described the diff.

They stay local, excluded via .git/info/exclude so the next add cannot
repeat this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tally ran unconditionally and its result was discarded on the
heterogeneous arm, which needs the distinct count alone. Cheap at survey
scale, but a copied fold whose output is thrown away is a cost-shape
defect regardless of the realized n, and 'n is small here' is not a
time-stable fact. Found by review 51421.

All four clustering claims still pass by execution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MEASURED, not hypothesised. src/v2/compiler/03_resolve.dag needs 71 source
reads against the manifest transport's 64-row inline cap, so the manifest
emits SourceRootManifestElided with produced_row_count 0 and an Empty
ingest. Handed that, frontier_probe_emit_from_ingest answered
^resolve_module_not_found with cause MissingModuleIdentityUnavailable — a
survey row asserting the module is missing when the truth is that its
sources were never supplied. Two states, different remedies, and the one
reported was the plausible one.

frontier_probe_coverage_gate already classifies an elided manifest
correctly, but it guards only the discovery path; this entry receives the
ingest directly and never consults coverage, so the conflation was
reachable from every from_ingest caller.

An empty ingest is now a typed, located, counted population refusal naming
^frontier_probe_empty_ingest.

Residue, declared rather than closed: emptiness is decidable here, coverage
is not, because the entry is not given the SourceRootCoverage — a PARTIAL
closure still answers. Dissolve-on is on the carrier: route the coverage
receipt into this entry so the refusal becomes an arm of the existing gate.

Four claims green by execution; RED verified by disabling the wall, which
reds three of them and restores the module-not-found answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tern

Every other match site was updated when the field landed; this one kept the
two-field pattern. Review 51431 flagged it.

The predicted typecheck failure does not occur — the file's five claims were
green by execution before this change and are green after — so the language
admits the partial pattern. That is the reason to fix it rather than to
leave it: a pattern that silently keeps matching when the carrier grows a
field is how a match stops covering what its author thought it covered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review 51432 is right that this claim's behavior against the checked-in
stub changed. What it was before is the part worth naming: with receipts
Empty, clustering asked self_emit_ready_count == module_count, both were 0,
and it answered CommonRoot with dominant_located_reason ^probe_stage_emit_ok
— a survey with no receipts reporting that every module reached emit. The
one claim whose subject IS the clustering verdict was passing vacuously on
a fabricated verdict.

Receipt-derived clustering answers HeterogeneousGaps for an empty survey,
so the claim now reds on the stub. That is the same state its siblings were
already in and is the file's declared offline condition, not a CI failure
shape: compiler_frontier_per_module_probe_survey_holds compares the receipt
count against the 27-row roster, and 0 is not 27.

The length check makes THIS claim's red say 'no receipts' explicitly rather
than leaving it to read as a clustering disagreement. Verified by execution
that the conjunction is false on an empty survey rather than erroring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The observer carried two arms over three reachable states, and the missing
arm failed OPEN: every Rejected outcome fell to the else branch and reported
BodyLoweringLowered. The instrument used to measure the body-lowering gap
was counting failures as successes, so any population read through it
undercounted by exactly the refusals.

That arm is reachable by construction, not in theory: the rejection-
propagation repair deliberately made body_lower_finish_for_normalize
propagate genuine rejection through rejected_with_pending, and this
classifier then discarded the distinction it created.

Recognition had the same fail-open by a second route. It read the diagnostic
HEAD alone, while retention travels through diagnostics_merge and
rejected_with_pending — both of which place OUTER diagnostics first — so a
retained body behind any pending diagnostic also read as lowered. It now
scans the whole list.

Six claims green by execution. RED verified by restoring the previous
classifier: the rejection claim, the rejection-diagnostics claim and the
behind-a-pending-diagnostic claim all red, while the three negative controls
stay green — so the claims pin these two defects rather than failing
broadly. The controls are the load-bearing half: a classifier that answered
retained more often would satisfy the positive claims and destroy the count.

Two existing consumers gain the third arm. Residue declared on the carrier:
recognition still reads an encoding rather than a producer-returned variant,
correct for every encoding the single retention producer can emit, with
dissolve-on naming the producer change that makes this function the identity.

First step of the sequence gunbc.roadmap_authority already declares: honest
frontier, then the normalized-tree construction boundary, then the retained
population to zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title Body Lowering BL-0: give the body-lowering frontier its third state Aug 13, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 13, 2026 04:00
…ation probe

The stage classifier and reconciliation machinery already existed in
gunbc.tools.frontier_ingestion_probe, hardcoded to the 15-member std
ingestion roster. Measuring a second population needed the roster to be an
argument rather than a second copy of the mechanism — two classifiers are
two authorities that can disagree.

take_census_over(rows) is the general form; take_frontend_census() is now
that call with the module roster. Roster identity derives from the rows
PASSED, never the module-level roster, so a receipt cannot claim a
denominator it was not measured over — the exact misreading
census_receipt_provenance_note exists to prevent, which a parameterised
census stamping the default identity would have reintroduced. All 14
existing claims over the instrument still pass.

The new probe measures the body-lowering population: the two SH-D shards
with measured door receipts, the std members the pre-repair observation
recorded as retained plus the one it never re-ran, and the seam's own
modules. It is deliberately not the whole corpus, and it carries no
aggregate pass/fail — the population IS the finding, and a Boolean over it
would be a verdict on subjects not separately established.

WHY BEFORE THE WALL: normalize returns Accepted for a tree whose diagnostics
carry wrapper-retention, and resolve consumes it. Making retention
unconstructible changes which modules normalize accepts, so the newly
refusing population must be known before the wall lands rather than
discovered from a red tree afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title BL-0: give the body-lowering frontier its third state BL-0: give the body-lowering frontier its third state, and the BL-2 census instrument Aug 13, 2026
gunbc-ci-auto-heal and others added 3 commits August 13, 2026 07:59
Executed, three-member roster: use_site_verdict NORM_ACCEPTED,
materialization_carriers NORM_OTHER, optional NORM_RETAINED.

That corrects the shard rows rather than the finding. Neither shard retains
as a file — use_site_verdict normalizes clean — so the wrapper-retained
residue measured in their door chains comes from OTHER modules in their
import closures, of which optional is one confirmed instance. 'The shard is
body-lowering blocked' was too coarse: the shard is blocked because its
closure contains retaining modules, and the repair subjects are those.

The roster is three because ten was executed and ABANDONED at 2h51m and
8.3 GiB resident with no verdict, on a host whose shared 20 GiB slice is
reaped largest-task-first — a hazard to other sessions before it was a slow
measurement. The instrument's own note already owed a realized execution
for exactly this reason. The seven dropped members are unmeasured, not
excluded, and the carrier says so.

The 10-to-3 shrink is nonlinear (2h51m to ~2min), so a dropped member is
pathological rather than merely larger. Which one is NOT established, and
guessing the largest file would be a guess dressed as a finding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as draft August 13, 2026 16:46
gunbc-ci-auto-heal added 2 commits August 13, 2026 16:48
…ed text

Two defects, both reachable only because an aggregate was authored over a
roster this module's own note says carries no aggregate verdict.

A refusal became an integer. count_stage_matching answered 0 - 1 for
CensusRefusedAtWorld and 0 - 2 for CensusRefusedAtReconciliation, so a typed
refusal and a genuine population count inhabited one type: a caller could add,
compare or display a failure as a measurement, and -1 reads as a number rather
than as a stop. The census already carries typed refusal arms, so this discarded
exactly the distinction they exist to preserve.

Presentation became semantic authority. The count compared stage_label_of(s)
against a target String, but FrontendStage is a closed coproduct — a label
rename would change the count with no stage changing, and a label collision
would merge distinct stages. Counting belongs to the structural variant or to
frontend_stage_eq, never to rendered text.

Deleted rather than repaired: the module already states the per-member
observation IS the finding and that no aggregate verdict is warranted over
subjects that have not each been separately established. The helpers
contradicted the stated scope, so there is nothing for a corrected aggregate to
mean here. Int and Bool were left import-only and are dropped with them.
The previous revision declared three rows named body_lowering_population_roster
while the module's prose said the subjects were ten. That made the planning
error this instrument exists to detect representable in its own carrier: seven
unmeasured subjects erased, one retained member observed, and a bare count
licensing "the retained population is one".

Two authorities now. body_lowering_subject_roster is the denominator, carrying
exact identities cited rather than asserted — the two SH-D shards, and five std
members from the executed table in
docs/plans/v2-frontend-std-ingestion-frontier-exact-head.md (logic, optional,
diagnostic, occurrence_identity as its NORM_RETAINED rows; node as its NOT
RE-OBSERVED row). body_lowering_execution_batch is what one run selected, and it
is DERIVED from the roster by label selection rather than re-authored beside it,
so a batch row cannot name a path the roster does not.

Standing is three states, because observed-or-absent cannot carry this
population: a subject dropped for cost, a subject whose run a budget
interrupted, and a subject genuinely measured are three epistemic positions with
three remedies, and collapsing the first two into absence renders "we did not
look" as "there is nothing there". SubjectInterrupted stays separate from
SubjectUnmeasured because node's 900s overrun is a real lower bound on cost that
an unmeasured subject does not carry; body_lowering_prior_standing gives that arm
its producer from the cited historical receipt, declared apart from this run so
the two can never be read as one measurement.

The population is EIGHT, derived rather than chosen (operator ruling
2026-08-13). Seven exact identities plus one subject for the vacuity
bare-expression specimen CLASS, which grounds as a class and not as members: the
vacuity note describes "many isolated configurations" and enumerates none, and
its optional_absent copy is the same subject as the rostered optional member.
Minting three rows would have fabricated two identities to satisfy a prose
sentence. The prose "ten" is recorded as retired rather than silently corrected,
and the note states that eight is not an authority either — completeness is an
identity join, not a count equality.
@gunbai-bot gunbai-bot Bot changed the title BL-0: give the body-lowering frontier its third state, and the BL-2 census instrument BL-2: split the subject roster from the execution batch, delete the aggregate helpers Aug 13, 2026
gunbc-ci-auto-heal added 3 commits August 13, 2026 16:58
Four blocking diagnostics, found by compiling the entry rather than by
inspection, and one of them is worth recording because it is a language-layer
trap rather than a typo.

A BRACE INSIDE A PROSE NOTE IS INTERPOLATION SYNTAX. The roster note described a
standing as SubjectUnmeasured{no_committed_specimen_identity}, and the compiler
read the braces as an interpolation and refused an undefined variable — inside a
String literal, at a position no reader would look for code. Escaped as \{, which
is the existing modeled form. This is the class DESIGN records from the
${...}-in-strings incident: the tempting move is to respell the prose around the
obstacle, and the modeled escape already exists.

The other three are empty list literals in fold seeds, which carry no element
type on their own. Written as [] as List<T>, the idiom already used by
frontier_ingestion_probe and normalize_retention_contract_probe_test.

Result: 0 blocking errors on dag/tools/body_lowering_population_probe.dag. The
248 remaining advisories are pre-existing and corpus-wide.
body_lowering_program_receipt was run against the live tree (73s) and returned
eight standings. Recorded because compiling is not running, and the join is the
part worth proving: eight subjects in and eight standings out establishes that
no subject is dropped between roster and receipt, the three observed rows
reproduce the earlier batch reading so deriving the batch from the roster did
not change what executes, and the specimen class routes to
no_committed_specimen_identity rather than to not_selected_into_execution_batch
— the distinction the realization coproduct exists to make.

The note also states what the run does not establish: the four unmeasured
subjects are unobserved, not staged, and node's prior interruption is neither
confirmed nor refuted here.
Merges main through 3c533ba rather than through its tip. #8228 landed at
9c5d007, so this drops the BL-0 files this branch was duplicating and
reduces the diff to the BL-2 work alone.

It deliberately stops one commit short. #8214 (9156b78) introduced in-body
source annotations across 13 files, confirmed by compiling dag/std/affine_space.dag
against the live tree, so merging main's tip today would import a known
compile-clean red into a branch that is currently green — turning an unrelated
lane's defect into this PR's failure. 3c533ba is the newest main commit whose
tree carries neither the in-body nor the unattached annotation form.

The remainder of main merges once that class is repaired.
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 13, 2026 19:20
Review on #8208 noted that stage_label_of paralleled frontier_ingestion_probe's
frontend_stage_label and disagreed with it on one variant — NORM_ACCEPTED
against ACCEPTED — and judged it non-blocking because it is presentation-only.
Taking it anyway: it is a second renderer for one closed coproduct, and it had
already drifted, which is the §3 fork rather than a style preference. This
module's own note claims there is one stage classifier in the repository and not
two; forking the label while importing the classifier honoured that in the half
that was harder to get wrong.

frontend_stage_label is imported and the fork deleted. Confirmed by execution
that the divergence was real and is now gone: census_report prints
"use_site_verdict ACCEPTED | materialization_carriers NORM_OTHER | optional
NORM_RETAINED", where the forked renderer printed NORM_ACCEPTED for the same
stage.

The recorded first reading named stages by their rendered label, which would now
be stale. It names them by VARIANT instead, and says why: the spelling belongs
to the renderer, so a note pinning it goes stale the moment that renderer is
edited — the same reason counting may not read labels. Five variant imports were
left import-only by the deletion and are dropped; NormalizeRetained stays,
since the prior-standing rows construct it.
gunbc-ci-auto-heal added 3 commits August 13, 2026 20:25
Review 51759 (REQUEST_CHANGES) found that body_lowering_program_receipt routed
EVERY live-file subject through the census-refusal cause. Verified and correct:
on CensusRefusedAtWorld, logic, diagnostic, occurrence_identity and node — four
subjects this run was never going to execute — were rewritten from "not selected"
into "the census refused".

That is state-space conflation, and it undid the roster/batch split on the one
path where the distinction is least visible. The two states have different
remedies: a not-selected subject needs a bigger batch or a cheaper instrument,
while a refused census needs the infrastructure repaired, so a reader deciding
what to do next was misdirected. More generally it is a failure at one scope
attributed to subjects outside that scope — the absorbing fallback wearing an
error's name, manufacturing information about subjects nobody looked at.

The refusal cause now reaches only selected subjects. A subject outside the
batch keeps not_selected_into_execution_batch under refusal exactly as under
success, because nothing about it was attempted and its standing does not depend
on the census. The specimen class keeps its own structural cause under both.

Proven by execution with a discriminating control, not by inspection.
census_refusal_does_not_reach_a_non_batch_subject_RED returns false against the
previous arm and true against this one; the other three assert that a batch
member does receive the cause, that the cause is carried rather than fixed, and
that the specimen class is unaffected. The arm takes a cause symbol as input, so
it is reachable by ordinary call and needs no broken tree to observe.
…identity row

Per-subject censuses (one subject per run) measured six of the seven runnable
subjects. Two consequences, plus the reading that keeps them from being
misread.

THE STALE RECEIPT ROW. occurrence_identity measures NormalizeOther, not the
NormalizeRetained the exact-head receipt records. That receipt is explicit that
the member was never re-run after the repair, so the cell was unknown rather
than confirmed — and it is now known stale. Corrected where the receipt lives
rather than only in a message, because a stale cell in a cited authority is the
failure that survives on the assumption that someone else checked it. logic
independently reproduced what the receipt predicted for it, which is evidence
the receipt was right about the mechanism and wrong only about the row it could
not re-run.

WHAT A STAGE LICENSES IS NOW DERIVED, not left to prose. Leaving NormalizeRetained
is not evidence of repair: NormalizeOther is by construction neither retention nor
a graft refusal, so it names where a module refuses and never why. ObservedReading
projects that — ObservedClean for the one admitting stage, ObservedRefusedCauseNamed
where the classifier identified the kind, ObservedRefusedCauseUnclassified for
NormalizeOther alone. Derived from the stage rather than stored beside it, so it
cannot disagree with the coproduct it reads.

A READING EXISTS ONLY WHERE SOMETHING WAS OBSERVED. A first draft of
reading_of_standing mapped SubjectUnmeasured and SubjectInterrupted onto the
unclassified-refusal arm on the reasoning that all three leave the cause unknown.
That is the census-refusal conflation again, in the module that just repaired it:
unknown-because-unclassified and unknown-because-unattempted are different
unknowns with different remedies. StandingReading keeps them apart.

Nine witnesses, all executed. Two are discriminating controls for the classes
above: NormalizeOther must not read clean, and an unmeasured subject must have no
reading at all.

node is not included in any of this. It was SIGKILLed at 346s while every other
subject finished between 138s and 292s; the signal is observed and the cause is
not established, so it stands interrupted with a fresh lower bound and carries no
stage. The six completions sit within a factor of 2.1, so the earlier 2h51m
ten-member run is unexplained by them, and node dying rather than finishing left
node-is-the-outlier versus superlinear-in-roster-size open.
… can complete

The earlier merge deliberately stopped at 3c533ba, one commit short of #8214,
because that commit put in-body source annotations across 13 files and merging
main's tip would have imported a known compile-clean red. #8235 moved all 87
body-grain annotations to module-item grain and main now carries none, so the
reason for stopping short is gone and this takes the remainder.

Recorded because the earlier stop was itself declared: a branch that resumes a
partial merge should say what condition cleared, not silently catch up.
@briansrls
briansrls merged commit 132c81b into main Aug 13, 2026
5 checks passed
@briansrls
briansrls deleted the session/warm-fox-179 branch August 13, 2026 23:11
briansrls pushed a commit that referenced this pull request Aug 14, 2026
…t reach resolve (#8256)

* Carry the refusal location on the catch-all assemble cause

OtherAssembleRefusal is the catch-all arm of AssembleRejectionCause, and it
carried only a reason symbol. A reason symbol does not say WHICH file refused,
so recovering that meant re-lexing an entire import closure by hand — measured
on use_site_verdict, whose 15-file closure had to be folded through tokenize
one source at a time to learn that dag/std/algebra.dag was the refusing file.

The location is carried as a probe-owned projection rather than a bare Locus.
Locus admits NodeLocus and PortLocus, which have no manifest serialization; a
host emitter that errored on them would abort a 27-module survey over one
unrepresentable position. FrontierProbeCauseLocation is serializable by
construction and total, and its unavailable arm carries a typed reason so the
deficit is counted rather than fabricated or fatal.

Location is kept structurally separate from the cause key: two modules refusing
for the same reason at different positions are the same detailed cause, so a
clustering key reads the cause and not this field.

The survey binary is the third consumer and is updated with it — the DAG
carrier change alone would have emitted a manifest that no longer typechecks
against the new field, which is worse than dropping it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* SH-D D0: move the refusal location onto AssembleRejectionDetail, close its variants

The location was a field of OtherAssembleRefusal, which made a position
structurally part of what the refusal IS: two modules refusing for the same
reason at different positions read as different detailed causes at a
clustering key. It now sits beside the cause on AssembleRejectionDetail.

CauseLocationUnavailable { reason: Symbol } is replaced by the closed pair
CauseLocationPort { port } and CauseLocationNodeNotSerializable. A PortLocus
carries real serializable data and no longer degrades to a reason string;
only a Node, which has no manifest rendering, reaches an unavailable arm.

The survey emitter follows the field and the arms, and stays total.

Controls (all executed): each of the three Locus arms reaches its own arm;
the byte range is asserted exactly, so substituting WholeFile reds; and a
two-diagnostic control pins reason and location to the SAME diagnostic
(RED verified by asserting the tail's offset).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* SH-D D0: regenerate the round-trip fixture from real emitted bytes

The fixture was hand-edited to the reshaped carrier, which is exactly what
its own note forbids: a hand-written approximation tests the author's idea
of the format instead of the emitter's. These bytes are copied verbatim out
of a survey run at commit 2880549, tree 932acf8, executable sha256
677c1bf0673c70ef… over src/v2/compiler/use_site_verdict.dag, and the reload
half passes against them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* SH-D D0: derive the clustering verdict's dominant reason from the receipts

frontier_gap_clustering_from_receipts counted blocker CLASSES and then wrote
a hardcoded located_reason literal into whichever branch fired, so the
verdict reported a reason no receipt had to carry: the one real survey
measures ^tokenize_lex_e1_unrecognized_char, which no branch could name. Its
HeterogeneousGaps arm reported module_count as the distinct-reason count,
which is a count of modules wearing the name of a count of reasons. And
count_receipts_with_located_reason, the function that reads the measured
axis, had zero callers.

Both numbers are now read off the receipts. CommonRoot requires unanimity
rather than a plurality, because any plurality bar is a threshold and a
threshold is the smuggled heuristic a closed system never needs. An empty
survey reports a distinct count of zero rather than becoming a common root.

Controls (executed, both REDs verified): a unanimous survey reports the
measured reason and counts the receipts carrying it; a mixed survey reports
2 distinct reasons over 3 receipts, where the old arm reported 3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* SH-D: unland the local probe instruments swept in by git add -A

These eight files are the one-off measurement instruments behind the A/B
finding — the real-closure door probe, the cause and reason readouts, the
lex localizer and three tokenize fixtures. They were never meant to land;
the PR description says so explicitly. A blanket 'git add -A src/v2' put
them in the tree anyway, which is experimental residue and a description
that no longer described the diff.

They stay local, excluded via .git/info/exclude so the next add cannot
repeat this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Compute the reason tally only on the arm that consumes it

The tally ran unconditionally and its result was discarded on the
heterogeneous arm, which needs the distinct count alone. Cheap at survey
scale, but a copied fold whose output is thrown away is a cost-shape
defect regardless of the realized n, and 'n is small here' is not a
time-stable fact. Found by review 51421.

All four clustering claims still pass by execution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refuse an empty ingest instead of reporting the module missing

MEASURED, not hypothesised. src/v2/compiler/03_resolve.dag needs 71 source
reads against the manifest transport's 64-row inline cap, so the manifest
emits SourceRootManifestElided with produced_row_count 0 and an Empty
ingest. Handed that, frontier_probe_emit_from_ingest answered
^resolve_module_not_found with cause MissingModuleIdentityUnavailable — a
survey row asserting the module is missing when the truth is that its
sources were never supplied. Two states, different remedies, and the one
reported was the plausible one.

frontier_probe_coverage_gate already classifies an elided manifest
correctly, but it guards only the discovery path; this entry receives the
ingest directly and never consults coverage, so the conflation was
reachable from every from_ingest caller.

An empty ingest is now a typed, located, counted population refusal naming
^frontier_probe_empty_ingest.

Residue, declared rather than closed: emptiness is decidable here, coverage
is not, because the entry is not given the SourceRootCoverage — a PARTIAL
closure still answers. Dissolve-on is on the carrier: route the coverage
receipt into this entry so the refusal becomes an arm of the existing gate.

Four claims green by execution; RED verified by disabling the wall, which
reds three of them and restores the module-not-found answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Bind the location field in the last stale AssembleRejectionDetail pattern

Every other match site was updated when the field landed; this one kept the
two-field pattern. Review 51431 flagged it.

The predicted typecheck failure does not occur — the file's five claims were
green by execution before this change and are green after — so the language
admits the partial pattern. That is the reason to fix it rather than to
leave it: a pattern that silently keeps matching when the carrier grows a
field is how a match stops covering what its author thought it covered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Make the clustering witness require a nonempty survey

Review 51432 is right that this claim's behavior against the checked-in
stub changed. What it was before is the part worth naming: with receipts
Empty, clustering asked self_emit_ready_count == module_count, both were 0,
and it answered CommonRoot with dominant_located_reason ^probe_stage_emit_ok
— a survey with no receipts reporting that every module reached emit. The
one claim whose subject IS the clustering verdict was passing vacuously on
a fabricated verdict.

Receipt-derived clustering answers HeterogeneousGaps for an empty survey,
so the claim now reds on the stub. That is the same state its siblings were
already in and is the file's declared offline condition, not a CI failure
shape: compiler_frontier_per_module_probe_survey_holds compares the receipt
count against the 27-row roster, and 0 is not 27.

The length check makes THIS claim's red say 'no receipts' explicitly rather
than leaving it to read as a clustering disagreement. Verified by execution
that the conjunction is false on an empty survey rather than erroring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* BL-0: give the body-lowering frontier its third state

The observer carried two arms over three reachable states, and the missing
arm failed OPEN: every Rejected outcome fell to the else branch and reported
BodyLoweringLowered. The instrument used to measure the body-lowering gap
was counting failures as successes, so any population read through it
undercounted by exactly the refusals.

That arm is reachable by construction, not in theory: the rejection-
propagation repair deliberately made body_lower_finish_for_normalize
propagate genuine rejection through rejected_with_pending, and this
classifier then discarded the distinction it created.

Recognition had the same fail-open by a second route. It read the diagnostic
HEAD alone, while retention travels through diagnostics_merge and
rejected_with_pending — both of which place OUTER diagnostics first — so a
retained body behind any pending diagnostic also read as lowered. It now
scans the whole list.

Six claims green by execution. RED verified by restoring the previous
classifier: the rejection claim, the rejection-diagnostics claim and the
behind-a-pending-diagnostic claim all red, while the three negative controls
stay green — so the claims pin these two defects rather than failing
broadly. The controls are the load-bearing half: a classifier that answered
retained more often would satisfy the positive claims and destroy the count.

Two existing consumers gain the third arm. Residue declared on the carrier:
recognition still reads an encoding rather than a producer-returned variant,
correct for every encoding the single retention producer can emit, with
dissolve-on naming the producer change that makes this function the identity.

First step of the sequence gunbc.roadmap_authority already declares: honest
frontier, then the normalized-tree construction boundary, then the retained
population to zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* BL-2: parameterise the census roster, and add the body-lowering population probe

The stage classifier and reconciliation machinery already existed in
gunbc.tools.frontier_ingestion_probe, hardcoded to the 15-member std
ingestion roster. Measuring a second population needed the roster to be an
argument rather than a second copy of the mechanism — two classifiers are
two authorities that can disagree.

take_census_over(rows) is the general form; take_frontend_census() is now
that call with the module roster. Roster identity derives from the rows
PASSED, never the module-level roster, so a receipt cannot claim a
denominator it was not measured over — the exact misreading
census_receipt_provenance_note exists to prevent, which a parameterised
census stamping the default identity would have reintroduced. All 14
existing claims over the instrument still pass.

The new probe measures the body-lowering population: the two SH-D shards
with measured door receipts, the std members the pre-repair observation
recorded as retained plus the one it never re-ran, and the seam's own
modules. It is deliberately not the whole corpus, and it carries no
aggregate pass/fail — the population IS the finding, and a Boolean over it
would be a verdict on subjects not separately established.

WHY BEFORE THE WALL: normalize returns Accepted for a tree whose diagnostics
carry wrapper-retention, and resolve consumes it. Making retention
unconstructible changes which modules normalize accepts, so the newly
refusing population must be known before the wall lands rather than
discovered from a red tree afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* BL-2 first reading: neither SH-D shard retains as a file

Executed, three-member roster: use_site_verdict NORM_ACCEPTED,
materialization_carriers NORM_OTHER, optional NORM_RETAINED.

That corrects the shard rows rather than the finding. Neither shard retains
as a file — use_site_verdict normalizes clean — so the wrapper-retained
residue measured in their door chains comes from OTHER modules in their
import closures, of which optional is one confirmed instance. 'The shard is
body-lowering blocked' was too coarse: the shard is blocked because its
closure contains retaining modules, and the repair subjects are those.

The roster is three because ten was executed and ABANDONED at 2h51m and
8.3 GiB resident with no verdict, on a host whose shared 20 GiB slice is
reaped largest-task-first — a hazard to other sessions before it was a slow
measurement. The instrument's own note already owed a realized execution
for exactly this reason. The seven dropped members are unmeasured, not
excluded, and the carrier says so.

The 10-to-3 shrink is nonlinear (2h51m to ~2min), so a dropped member is
pathological rather than merely larger. Which one is NOT established, and
guessing the largest file would be a guess dressed as a finding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* BL-2: delete the aggregate helpers, which counted refusals and rendered text

Two defects, both reachable only because an aggregate was authored over a
roster this module's own note says carries no aggregate verdict.

A refusal became an integer. count_stage_matching answered 0 - 1 for
CensusRefusedAtWorld and 0 - 2 for CensusRefusedAtReconciliation, so a typed
refusal and a genuine population count inhabited one type: a caller could add,
compare or display a failure as a measurement, and -1 reads as a number rather
than as a stop. The census already carries typed refusal arms, so this discarded
exactly the distinction they exist to preserve.

Presentation became semantic authority. The count compared stage_label_of(s)
against a target String, but FrontendStage is a closed coproduct — a label
rename would change the count with no stage changing, and a label collision
would merge distinct stages. Counting belongs to the structural variant or to
frontend_stage_eq, never to rendered text.

Deleted rather than repaired: the module already states the per-member
observation IS the finding and that no aggregate verdict is warranted over
subjects that have not each been separately established. The helpers
contradicted the stated scope, so there is nothing for a corrected aggregate to
mean here. Int and Bool were left import-only and are dropped with them.

* BL-2: separate the subject roster from the execution batch

The previous revision declared three rows named body_lowering_population_roster
while the module's prose said the subjects were ten. That made the planning
error this instrument exists to detect representable in its own carrier: seven
unmeasured subjects erased, one retained member observed, and a bare count
licensing "the retained population is one".

Two authorities now. body_lowering_subject_roster is the denominator, carrying
exact identities cited rather than asserted — the two SH-D shards, and five std
members from the executed table in
docs/plans/v2-frontend-std-ingestion-frontier-exact-head.md (logic, optional,
diagnostic, occurrence_identity as its NORM_RETAINED rows; node as its NOT
RE-OBSERVED row). body_lowering_execution_batch is what one run selected, and it
is DERIVED from the roster by label selection rather than re-authored beside it,
so a batch row cannot name a path the roster does not.

Standing is three states, because observed-or-absent cannot carry this
population: a subject dropped for cost, a subject whose run a budget
interrupted, and a subject genuinely measured are three epistemic positions with
three remedies, and collapsing the first two into absence renders "we did not
look" as "there is nothing there". SubjectInterrupted stays separate from
SubjectUnmeasured because node's 900s overrun is a real lower bound on cost that
an unmeasured subject does not carry; body_lowering_prior_standing gives that arm
its producer from the cited historical receipt, declared apart from this run so
the two can never be read as one measurement.

The population is EIGHT, derived rather than chosen (operator ruling
2026-08-13). Seven exact identities plus one subject for the vacuity
bare-expression specimen CLASS, which grounds as a class and not as members: the
vacuity note describes "many isolated configurations" and enumerates none, and
its optional_absent copy is the same subject as the rostered optional member.
Minting three rows would have fabricated two identities to satisfy a prose
sentence. The prose "ten" is recorded as retired rather than silently corrected,
and the note states that eight is not an authority either — completeness is an
identity join, not a count equality.

* BL-2: make the roster module compile

Four blocking diagnostics, found by compiling the entry rather than by
inspection, and one of them is worth recording because it is a language-layer
trap rather than a typo.

A BRACE INSIDE A PROSE NOTE IS INTERPOLATION SYNTAX. The roster note described a
standing as SubjectUnmeasured{no_committed_specimen_identity}, and the compiler
read the braces as an interpolation and refused an undefined variable — inside a
String literal, at a position no reader would look for code. Escaped as \{, which
is the existing modeled form. This is the class DESIGN records from the
${...}-in-strings incident: the tempting move is to respell the prose around the
obstacle, and the modeled escape already exists.

The other three are empty list literals in fold seeds, which carry no element
type on their own. Written as [] as List<T>, the idiom already used by
frontier_ingestion_probe and normalize_retention_contract_probe_test.

Result: 0 blocking errors on dag/tools/body_lowering_population_probe.dag. The
248 remaining advisories are pre-existing and corpus-wide.

* BL-2: record the executed program receipt

body_lowering_program_receipt was run against the live tree (73s) and returned
eight standings. Recorded because compiling is not running, and the join is the
part worth proving: eight subjects in and eight standings out establishes that
no subject is dropped between roster and receipt, the three observed rows
reproduce the earlier batch reading so deriving the batch from the roster did
not change what executes, and the specimen class routes to
no_committed_specimen_identity rather than to not_selected_into_execution_batch
— the distinction the realization coproduct exists to make.

The note also states what the run does not establish: the four unmeasured
subjects are unobserved, not staged, and node's prior interruption is neither
confirmed nor refuted here.

* BL-2: render stages through the one label authority

Review on #8208 noted that stage_label_of paralleled frontier_ingestion_probe's
frontend_stage_label and disagreed with it on one variant — NORM_ACCEPTED
against ACCEPTED — and judged it non-blocking because it is presentation-only.
Taking it anyway: it is a second renderer for one closed coproduct, and it had
already drifted, which is the §3 fork rather than a style preference. This
module's own note claims there is one stage classifier in the repository and not
two; forking the label while importing the classifier honoured that in the half
that was harder to get wrong.

frontend_stage_label is imported and the fork deleted. Confirmed by execution
that the divergence was real and is now gone: census_report prints
"use_site_verdict ACCEPTED | materialization_carriers NORM_OTHER | optional
NORM_RETAINED", where the forked renderer printed NORM_ACCEPTED for the same
stage.

The recorded first reading named stages by their rendered label, which would now
be stale. It names them by VARIANT instead, and says why: the spelling belongs
to the renderer, so a note pinning it goes stale the moment that renderer is
edited — the same reason counting may not read labels. Five variant imports were
left import-only by the deletion and are dropped; NormalizeRetained stays,
since the prior-standing rows construct it.

* BL-2: scope a census refusal to the batch, not to the roster

Review 51759 (REQUEST_CHANGES) found that body_lowering_program_receipt routed
EVERY live-file subject through the census-refusal cause. Verified and correct:
on CensusRefusedAtWorld, logic, diagnostic, occurrence_identity and node — four
subjects this run was never going to execute — were rewritten from "not selected"
into "the census refused".

That is state-space conflation, and it undid the roster/batch split on the one
path where the distinction is least visible. The two states have different
remedies: a not-selected subject needs a bigger batch or a cheaper instrument,
while a refused census needs the infrastructure repaired, so a reader deciding
what to do next was misdirected. More generally it is a failure at one scope
attributed to subjects outside that scope — the absorbing fallback wearing an
error's name, manufacturing information about subjects nobody looked at.

The refusal cause now reaches only selected subjects. A subject outside the
batch keeps not_selected_into_execution_batch under refusal exactly as under
success, because nothing about it was attempted and its standing does not depend
on the census. The specimen class keeps its own structural cause under both.

Proven by execution with a discriminating control, not by inspection.
census_refusal_does_not_reach_a_non_batch_subject_RED returns false against the
previous arm and true against this one; the other three assert that a batch
member does receive the cause, that the cause is carried rather than fixed, and
that the specimen class is unaffected. The arm takes a cause symbol as input, so
it is reachable by ordinary call and needs no broken tree to observe.

* BL-2: derive what a stage licenses, and correct the stale occurrence_identity row

Per-subject censuses (one subject per run) measured six of the seven runnable
subjects. Two consequences, plus the reading that keeps them from being
misread.

THE STALE RECEIPT ROW. occurrence_identity measures NormalizeOther, not the
NormalizeRetained the exact-head receipt records. That receipt is explicit that
the member was never re-run after the repair, so the cell was unknown rather
than confirmed — and it is now known stale. Corrected where the receipt lives
rather than only in a message, because a stale cell in a cited authority is the
failure that survives on the assumption that someone else checked it. logic
independently reproduced what the receipt predicted for it, which is evidence
the receipt was right about the mechanism and wrong only about the row it could
not re-run.

WHAT A STAGE LICENSES IS NOW DERIVED, not left to prose. Leaving NormalizeRetained
is not evidence of repair: NormalizeOther is by construction neither retention nor
a graft refusal, so it names where a module refuses and never why. ObservedReading
projects that — ObservedClean for the one admitting stage, ObservedRefusedCauseNamed
where the classifier identified the kind, ObservedRefusedCauseUnclassified for
NormalizeOther alone. Derived from the stage rather than stored beside it, so it
cannot disagree with the coproduct it reads.

A READING EXISTS ONLY WHERE SOMETHING WAS OBSERVED. A first draft of
reading_of_standing mapped SubjectUnmeasured and SubjectInterrupted onto the
unclassified-refusal arm on the reasoning that all three leave the cause unknown.
That is the census-refusal conflation again, in the module that just repaired it:
unknown-because-unclassified and unknown-because-unattempted are different
unknowns with different remedies. StandingReading keeps them apart.

Nine witnesses, all executed. Two are discriminating controls for the classes
above: NormalizeOther must not read clean, and an unmeasured subject must have no
reading at all.

node is not included in any of this. It was SIGKILLed at 346s while every other
subject finished between 138s and 292s; the signal is observed and the cause is
not established, so it stands interrupted with a fresh lower bound and carries no
stage. The six completions sit within a factor of 2.1, so the earlier 2h51m
ten-member run is unexplained by them, and node dying rather than finishing left
node-is-the-outlier versus superlinear-in-roster-size open.

* BL-1: seal NormalizedTree behind an admission door so retention cannot reach resolve

normalize computed 'this root's body lowering left no wrapper behind' and had
nowhere to put it: NormalizedTree was an alias for Node, so the fact travelled
as diagnostics beside a bare Node and was dropped at the first FreeMonoid<Node>
carrier before resolve.

Restore the carrier through the root-carrying signatures and give it a door.
NormalizedTree becomes a sole_constructor record; admit_normalized_tree refuses
a root carrying wrapper-retention evidence. The retention recognizer lives once,
beside its single producer in v2.std.compilers.body_lowering, and scans the whole
diagnostic list -- rejected_with_pending prepends outer diagnostics, so a
head-only read is systematically wrong.

Rung: accepted refinement with executing refusal, not structural impossibility.

Also records the typecheck-blindness receipt on the hollow-alias lane: the
conversion reported 0 blocking errors with a record in a Node position AND a raw
Node in the NormalizedTree field, and surfaced only under execution.

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

* BL-1: correct the typecheck-blindness receipt to wrapper-payload conflation

The ContentHash specimen was characterised as a family member in union
position. Read on main, Fnv1a64Structural is the coproduct's PAYLOAD, not a
member -- and that is the stronger fact: no alias participates in it, so the
class is not a quirk of alias declarations.

Both specimens are one shape. The typechecker does not distinguish a wrapper
from the thing it wraps.

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

* BL-1: delete the retention-recognizer fork, consume the producer's own

Review 51940 is right and this was my error. body_lowering_fold already carried
body_lowering_diagnostic_is_wrapper_retained and the whole-list
body_lowering_any_diagnostic_is_wrapper_retained -- landed by BL-0 -- and I
minted a second copy in v2.std.compilers.body_lowering under the SAME names,
then pointed the admission door at the fork. Two modules could answer 'does this
carry retention?' and drift when the reason symbol moved: the exact parallel
authority the change was supposed to close, authored by the commit message that
claimed to close it.

The std/ copy is deleted whole (that file is now byte-identical to main).
body_lowering_fold gains one Diagnostics-grain reading beside its existing pair,
delegating to the same whole-list scan, and normalized_tree imports it from
there. No cycle: body_lowering_fold does not reach normalized_tree.

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

* BL-1: convert the name-resolve fixtures, and close three seams they exposed

Review 51952 is right that the name-resolve fixtures still passed raw
FreeMonoid<Node>. Converting them and RUNNING them exposed three genuine
production seams in this diff, none reported by the typechecker:

  admit_import_root_find declared Outcome<Node>              -> no field 'children'
  symbol_index_fill_module_roots declared FreeMonoid<Node>   -> no field 'kind'
  ModuleRootFound / PassingCandidateFold declared Node       -> no field 'kind'

The third reached a shared std carrier, so PassingCandidateFold is parameterised
over its candidate type: the cardinality of a search result is not a fact about
what was searched, and it was declared over Node only because every consumer
happened to fold over Node. A local fold type would have been a second name for
one concept; projecting and re-wrapping would have re-minted the carrier outside
the door.

Fixtures admit through the real door, and the refusal arm fails the witness
rather than fabricating a value.

Records the denominator in section 8.2: 39 declarations across 7 modules, every
Node-meeting point resolved as one of 16 .root projections, 1 list projection,
or 3 converted callees.

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

* BL-1: convert the witness callers the floor found, and re-cut the denominator at its true boundary

The floor reported nine runtime 'no field root on type Node' failures across
three witness modules. All nine are mine: the first denominator was drawn at
production modules reachable from the converted chain, and witness modules meet
a converted value exactly as production modules do.

Adds admit_normalized_roots -- the plural door, refusing as a whole so a
partially admitted list has no representation -- and routes every hand-built
fixture through it. Negative witnesses keep a distinct arm for fixture-admission
failure so a broken fixture cannot masquerade as the rejection under test.

Denominator re-cut as a caller census with its boundary named: 28 modules
reference the converted APIs, 13 feed from normalize and are unaffected, 15
hand-build roots and 12 needed conversion. Residue stated rather than implied --
the instrument is execution, so a witness that meets a converted value and does
not execute is broken silently; intersecting the 190-file no-executing-consumer
roster against the callers yields 6 files, 4 hand-built, all executed green.

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

* BL-1: stop the plural door duplicating its shared diagnostics N+1 times

Review 52023 flagged admit_normalized_roots re-passing the outer diagnostics
into each per-element admit. It is a real defect, not just a smell: bind_outcome
MERGES accumulated with incoming, the seed carried the shared set, and every step
carried it again -- so an N-element list accumulated N+1 copies of one diagnostic
set. Reachable only from tests today because every caller passes None, where
merging is harmless.

The obvious fix -- admit each element under None -- would have made the plural
door total, i.e. a cast around the door rather than the door. So the decision
stays per element (same verdict each time, since it reads only the shared
diagnostics), the threading becomes explicit instead of bind_outcome, the
refusal keeps carrying its pending set exactly once via admit_normalized_tree,
and the accepted diagnostics are attached once at the end.

plural_admission_does_not_duplicate_shared_diagnostics_RED asserts the result's
diagnostics equal the input for a two-element list; restoring the bind_outcome
threading flips it to false.

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

* BL-1: convert the self-host intermediate the caller census missed

Review 52034 found frontier_probe_first_missing_import_lookup declaring
roots: FreeMonoid<Node> while receiving validated_roots from
validate_module_roots and forwarding it into module_root_lookup -- the exact
seam this PR closes, left open on a self-host consumer.

The census had classified that module safe by checking WHERE its roots came
from (an already-admitted ingest fold) rather than WHAT each intermediate
declared. Provenance is not a substitute for declaration, and that is the third
time this population was drawn at the wrong boundary.

Replaces the judgement with a mechanical check: for every function, does it pass
one of its own Node / FreeMonoid<Node> parameters into a converted API. Corpus
wide it reports zero -- and the instrument is validated against the defect it
must catch, reporting exactly the frontier_probe site when that parameter is
reintroduced and zero when the fix is restored.

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

* BL-1: close the conversion population by fixpoint over three syntactic shapes

Review 52042 found a witness helper passing normalize's result into
qualified_name_from_module_node without projecting .root -- a shape neither
earlier check covered, on a path backing an enrolled long-lane witness.

The class has three shapes and they compose into a fixpoint:
  A  own Node parameter passed into a converted API
  B  a NormalizedTree-producer binding passed into a Node-declared parameter
  C  a function declaring -> Outcome<Node> while returning a producer

Fixing a shape-C return type makes its consumers visible to shape B, and fixing
those exposes more shape C. Iterated to convergence: A and B report zero; the
four remaining C hits are confirmed false positives, feeding resolve, which takes
the admitted carrier, with ResolvedTree = Node so their return is correct.

Closed here: 6 witness/lens helpers, 6 return declarations, 8 fixpoint-surfaced
consumers. Each shape was discovered only after a review or CI surfaced an
instance -- with no checker signal there is no way to derive the shapes, only to
enumerate them, and that is the finding worth keeping.

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

* BL-1: revert two over-applied .root projections, and record the direction asymmetry

The 58e7410 red was NOT another instance of the invisible conflation. It was
the inverse: .root applied to a genuine Node, which the checker DOES catch
statically at resolve. Two sites, both introduced by my own bulk string replaces
hitting call sites whose value came from resolve (ResolvedTree = Node) rather
than from normalize -- parse_binding_fidelity_support pbf_resolved_add_arrow and
stage_bridge pipeline_match_corpus_resolve_relationship_holds.

Records the asymmetry in the receipt, because citing both directions as
typecheck blindness would inflate the class: payload-supplied-where-wrapper-
declared is invisible; wrapper-projection-on-payload is caught.

Also records that what kept being wrong was the ENUMERATION, not the fix. The
population was cut three times -- production, then _test modules, then support
modules -- each drawn where the last failure landed rather than from a rule. All
three were proxies keyed on path or role. The sweeps key on the only thing that
matters, whether a declaration meets a converted value, and glob the whole tree
with no path filter.

Verified: all 18 files carrying an introduced projection resolve clean, and the
three tree-wide sweeps report A=0, B=0, C=4-known-false-positives.

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

* BL-1: correct 8.2's direction rule -- one symptom, two defects, truthfulness of the declaration is the discriminator

The asymmetry this receipt claimed (payload-into-wrapper invisible,
wrapper-onto-payload caught) is refuted by its own two runs: the SAME projection
on the SAME two types produced 81 runtime failures on one floor run and 1 resolve
failure on the next.

Verified against both. The 81 came from validate_module_roots, whose fold binder
is DECLARED NormalizedTree, so root.root is statically correct and the real
defect sits upstream at the call boundary -- the invisible class, whose symptom
is a projection failing at runtime wherever the untruthful value is finally read.
The 1 came from pbf_resolved_add_arrow, whose binder is declared Node, so the
declaration itself is the mismatch and resolve refuses it.

The discriminator is whether the declaration at the site is truthful: a mismatch
is caught exactly where it is expressible against a declaration, and invisible
where the declaration is right and only the value flowing in is wrong. Also notes
that 81 occurrences are not 81 instances -- they are one symptom of fewer
boundary defects, read at many sites.

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

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Aug 14, 2026
… of casting past it (#8269)

* Carry the refusal location on the catch-all assemble cause

OtherAssembleRefusal is the catch-all arm of AssembleRejectionCause, and it
carried only a reason symbol. A reason symbol does not say WHICH file refused,
so recovering that meant re-lexing an entire import closure by hand — measured
on use_site_verdict, whose 15-file closure had to be folded through tokenize
one source at a time to learn that dag/std/algebra.dag was the refusing file.

The location is carried as a probe-owned projection rather than a bare Locus.
Locus admits NodeLocus and PortLocus, which have no manifest serialization; a
host emitter that errored on them would abort a 27-module survey over one
unrepresentable position. FrontierProbeCauseLocation is serializable by
construction and total, and its unavailable arm carries a typed reason so the
deficit is counted rather than fabricated or fatal.

Location is kept structurally separate from the cause key: two modules refusing
for the same reason at different positions are the same detailed cause, so a
clustering key reads the cause and not this field.

The survey binary is the third consumer and is updated with it — the DAG
carrier change alone would have emitted a manifest that no longer typechecks
against the new field, which is worse than dropping it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* SH-D D0: move the refusal location onto AssembleRejectionDetail, close its variants

The location was a field of OtherAssembleRefusal, which made a position
structurally part of what the refusal IS: two modules refusing for the same
reason at different positions read as different detailed causes at a
clustering key. It now sits beside the cause on AssembleRejectionDetail.

CauseLocationUnavailable { reason: Symbol } is replaced by the closed pair
CauseLocationPort { port } and CauseLocationNodeNotSerializable. A PortLocus
carries real serializable data and no longer degrades to a reason string;
only a Node, which has no manifest rendering, reaches an unavailable arm.

The survey emitter follows the field and the arms, and stays total.

Controls (all executed): each of the three Locus arms reaches its own arm;
the byte range is asserted exactly, so substituting WholeFile reds; and a
two-diagnostic control pins reason and location to the SAME diagnostic
(RED verified by asserting the tail's offset).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* SH-D D0: regenerate the round-trip fixture from real emitted bytes

The fixture was hand-edited to the reshaped carrier, which is exactly what
its own note forbids: a hand-written approximation tests the author's idea
of the format instead of the emitter's. These bytes are copied verbatim out
of a survey run at commit 2880549, tree 932acf8, executable sha256
677c1bf0673c70ef… over src/v2/compiler/use_site_verdict.dag, and the reload
half passes against them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* SH-D D0: derive the clustering verdict's dominant reason from the receipts

frontier_gap_clustering_from_receipts counted blocker CLASSES and then wrote
a hardcoded located_reason literal into whichever branch fired, so the
verdict reported a reason no receipt had to carry: the one real survey
measures ^tokenize_lex_e1_unrecognized_char, which no branch could name. Its
HeterogeneousGaps arm reported module_count as the distinct-reason count,
which is a count of modules wearing the name of a count of reasons. And
count_receipts_with_located_reason, the function that reads the measured
axis, had zero callers.

Both numbers are now read off the receipts. CommonRoot requires unanimity
rather than a plurality, because any plurality bar is a threshold and a
threshold is the smuggled heuristic a closed system never needs. An empty
survey reports a distinct count of zero rather than becoming a common root.

Controls (executed, both REDs verified): a unanimous survey reports the
measured reason and counts the receipts carrying it; a mixed survey reports
2 distinct reasons over 3 receipts, where the old arm reported 3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* SH-D: unland the local probe instruments swept in by git add -A

These eight files are the one-off measurement instruments behind the A/B
finding — the real-closure door probe, the cause and reason readouts, the
lex localizer and three tokenize fixtures. They were never meant to land;
the PR description says so explicitly. A blanket 'git add -A src/v2' put
them in the tree anyway, which is experimental residue and a description
that no longer described the diff.

They stay local, excluded via .git/info/exclude so the next add cannot
repeat this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Compute the reason tally only on the arm that consumes it

The tally ran unconditionally and its result was discarded on the
heterogeneous arm, which needs the distinct count alone. Cheap at survey
scale, but a copied fold whose output is thrown away is a cost-shape
defect regardless of the realized n, and 'n is small here' is not a
time-stable fact. Found by review 51421.

All four clustering claims still pass by execution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refuse an empty ingest instead of reporting the module missing

MEASURED, not hypothesised. src/v2/compiler/03_resolve.dag needs 71 source
reads against the manifest transport's 64-row inline cap, so the manifest
emits SourceRootManifestElided with produced_row_count 0 and an Empty
ingest. Handed that, frontier_probe_emit_from_ingest answered
^resolve_module_not_found with cause MissingModuleIdentityUnavailable — a
survey row asserting the module is missing when the truth is that its
sources were never supplied. Two states, different remedies, and the one
reported was the plausible one.

frontier_probe_coverage_gate already classifies an elided manifest
correctly, but it guards only the discovery path; this entry receives the
ingest directly and never consults coverage, so the conflation was
reachable from every from_ingest caller.

An empty ingest is now a typed, located, counted population refusal naming
^frontier_probe_empty_ingest.

Residue, declared rather than closed: emptiness is decidable here, coverage
is not, because the entry is not given the SourceRootCoverage — a PARTIAL
closure still answers. Dissolve-on is on the carrier: route the coverage
receipt into this entry so the refusal becomes an arm of the existing gate.

Four claims green by execution; RED verified by disabling the wall, which
reds three of them and restores the module-not-found answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Bind the location field in the last stale AssembleRejectionDetail pattern

Every other match site was updated when the field landed; this one kept the
two-field pattern. Review 51431 flagged it.

The predicted typecheck failure does not occur — the file's five claims were
green by execution before this change and are green after — so the language
admits the partial pattern. That is the reason to fix it rather than to
leave it: a pattern that silently keeps matching when the carrier grows a
field is how a match stops covering what its author thought it covered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Make the clustering witness require a nonempty survey

Review 51432 is right that this claim's behavior against the checked-in
stub changed. What it was before is the part worth naming: with receipts
Empty, clustering asked self_emit_ready_count == module_count, both were 0,
and it answered CommonRoot with dominant_located_reason ^probe_stage_emit_ok
— a survey with no receipts reporting that every module reached emit. The
one claim whose subject IS the clustering verdict was passing vacuously on
a fabricated verdict.

Receipt-derived clustering answers HeterogeneousGaps for an empty survey,
so the claim now reds on the stub. That is the same state its siblings were
already in and is the file's declared offline condition, not a CI failure
shape: compiler_frontier_per_module_probe_survey_holds compares the receipt
count against the 27-row roster, and 0 is not 27.

The length check makes THIS claim's red say 'no receipts' explicitly rather
than leaving it to read as a clustering disagreement. Verified by execution
that the conjunction is false on an empty survey rather than erroring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* BL-0: give the body-lowering frontier its third state

The observer carried two arms over three reachable states, and the missing
arm failed OPEN: every Rejected outcome fell to the else branch and reported
BodyLoweringLowered. The instrument used to measure the body-lowering gap
was counting failures as successes, so any population read through it
undercounted by exactly the refusals.

That arm is reachable by construction, not in theory: the rejection-
propagation repair deliberately made body_lower_finish_for_normalize
propagate genuine rejection through rejected_with_pending, and this
classifier then discarded the distinction it created.

Recognition had the same fail-open by a second route. It read the diagnostic
HEAD alone, while retention travels through diagnostics_merge and
rejected_with_pending — both of which place OUTER diagnostics first — so a
retained body behind any pending diagnostic also read as lowered. It now
scans the whole list.

Six claims green by execution. RED verified by restoring the previous
classifier: the rejection claim, the rejection-diagnostics claim and the
behind-a-pending-diagnostic claim all red, while the three negative controls
stay green — so the claims pin these two defects rather than failing
broadly. The controls are the load-bearing half: a classifier that answered
retained more often would satisfy the positive claims and destroy the count.

Two existing consumers gain the third arm. Residue declared on the carrier:
recognition still reads an encoding rather than a producer-returned variant,
correct for every encoding the single retention producer can emit, with
dissolve-on naming the producer change that makes this function the identity.

First step of the sequence gunbc.roadmap_authority already declares: honest
frontier, then the normalized-tree construction boundary, then the retained
population to zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* BL-2: parameterise the census roster, and add the body-lowering population probe

The stage classifier and reconciliation machinery already existed in
gunbc.tools.frontier_ingestion_probe, hardcoded to the 15-member std
ingestion roster. Measuring a second population needed the roster to be an
argument rather than a second copy of the mechanism — two classifiers are
two authorities that can disagree.

take_census_over(rows) is the general form; take_frontend_census() is now
that call with the module roster. Roster identity derives from the rows
PASSED, never the module-level roster, so a receipt cannot claim a
denominator it was not measured over — the exact misreading
census_receipt_provenance_note exists to prevent, which a parameterised
census stamping the default identity would have reintroduced. All 14
existing claims over the instrument still pass.

The new probe measures the body-lowering population: the two SH-D shards
with measured door receipts, the std members the pre-repair observation
recorded as retained plus the one it never re-ran, and the seam's own
modules. It is deliberately not the whole corpus, and it carries no
aggregate pass/fail — the population IS the finding, and a Boolean over it
would be a verdict on subjects not separately established.

WHY BEFORE THE WALL: normalize returns Accepted for a tree whose diagnostics
carry wrapper-retention, and resolve consumes it. Making retention
unconstructible changes which modules normalize accepts, so the newly
refusing population must be known before the wall lands rather than
discovered from a red tree afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* BL-2 first reading: neither SH-D shard retains as a file

Executed, three-member roster: use_site_verdict NORM_ACCEPTED,
materialization_carriers NORM_OTHER, optional NORM_RETAINED.

That corrects the shard rows rather than the finding. Neither shard retains
as a file — use_site_verdict normalizes clean — so the wrapper-retained
residue measured in their door chains comes from OTHER modules in their
import closures, of which optional is one confirmed instance. 'The shard is
body-lowering blocked' was too coarse: the shard is blocked because its
closure contains retaining modules, and the repair subjects are those.

The roster is three because ten was executed and ABANDONED at 2h51m and
8.3 GiB resident with no verdict, on a host whose shared 20 GiB slice is
reaped largest-task-first — a hazard to other sessions before it was a slow
measurement. The instrument's own note already owed a realized execution
for exactly this reason. The seven dropped members are unmeasured, not
excluded, and the carrier says so.

The 10-to-3 shrink is nonlinear (2h51m to ~2min), so a dropped member is
pathological rather than merely larger. Which one is NOT established, and
guessing the largest file would be a guess dressed as a finding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* BL-2: delete the aggregate helpers, which counted refusals and rendered text

Two defects, both reachable only because an aggregate was authored over a
roster this module's own note says carries no aggregate verdict.

A refusal became an integer. count_stage_matching answered 0 - 1 for
CensusRefusedAtWorld and 0 - 2 for CensusRefusedAtReconciliation, so a typed
refusal and a genuine population count inhabited one type: a caller could add,
compare or display a failure as a measurement, and -1 reads as a number rather
than as a stop. The census already carries typed refusal arms, so this discarded
exactly the distinction they exist to preserve.

Presentation became semantic authority. The count compared stage_label_of(s)
against a target String, but FrontendStage is a closed coproduct — a label
rename would change the count with no stage changing, and a label collision
would merge distinct stages. Counting belongs to the structural variant or to
frontend_stage_eq, never to rendered text.

Deleted rather than repaired: the module already states the per-member
observation IS the finding and that no aggregate verdict is warranted over
subjects that have not each been separately established. The helpers
contradicted the stated scope, so there is nothing for a corrected aggregate to
mean here. Int and Bool were left import-only and are dropped with them.

* BL-2: separate the subject roster from the execution batch

The previous revision declared three rows named body_lowering_population_roster
while the module's prose said the subjects were ten. That made the planning
error this instrument exists to detect representable in its own carrier: seven
unmeasured subjects erased, one retained member observed, and a bare count
licensing "the retained population is one".

Two authorities now. body_lowering_subject_roster is the denominator, carrying
exact identities cited rather than asserted — the two SH-D shards, and five std
members from the executed table in
docs/plans/v2-frontend-std-ingestion-frontier-exact-head.md (logic, optional,
diagnostic, occurrence_identity as its NORM_RETAINED rows; node as its NOT
RE-OBSERVED row). body_lowering_execution_batch is what one run selected, and it
is DERIVED from the roster by label selection rather than re-authored beside it,
so a batch row cannot name a path the roster does not.

Standing is three states, because observed-or-absent cannot carry this
population: a subject dropped for cost, a subject whose run a budget
interrupted, and a subject genuinely measured are three epistemic positions with
three remedies, and collapsing the first two into absence renders "we did not
look" as "there is nothing there". SubjectInterrupted stays separate from
SubjectUnmeasured because node's 900s overrun is a real lower bound on cost that
an unmeasured subject does not carry; body_lowering_prior_standing gives that arm
its producer from the cited historical receipt, declared apart from this run so
the two can never be read as one measurement.

The population is EIGHT, derived rather than chosen (operator ruling
2026-08-13). Seven exact identities plus one subject for the vacuity
bare-expression specimen CLASS, which grounds as a class and not as members: the
vacuity note describes "many isolated configurations" and enumerates none, and
its optional_absent copy is the same subject as the rostered optional member.
Minting three rows would have fabricated two identities to satisfy a prose
sentence. The prose "ten" is recorded as retired rather than silently corrected,
and the note states that eight is not an authority either — completeness is an
identity join, not a count equality.

* BL-2: make the roster module compile

Four blocking diagnostics, found by compiling the entry rather than by
inspection, and one of them is worth recording because it is a language-layer
trap rather than a typo.

A BRACE INSIDE A PROSE NOTE IS INTERPOLATION SYNTAX. The roster note described a
standing as SubjectUnmeasured{no_committed_specimen_identity}, and the compiler
read the braces as an interpolation and refused an undefined variable — inside a
String literal, at a position no reader would look for code. Escaped as \{, which
is the existing modeled form. This is the class DESIGN records from the
${...}-in-strings incident: the tempting move is to respell the prose around the
obstacle, and the modeled escape already exists.

The other three are empty list literals in fold seeds, which carry no element
type on their own. Written as [] as List<T>, the idiom already used by
frontier_ingestion_probe and normalize_retention_contract_probe_test.

Result: 0 blocking errors on dag/tools/body_lowering_population_probe.dag. The
248 remaining advisories are pre-existing and corpus-wide.

* BL-2: record the executed program receipt

body_lowering_program_receipt was run against the live tree (73s) and returned
eight standings. Recorded because compiling is not running, and the join is the
part worth proving: eight subjects in and eight standings out establishes that
no subject is dropped between roster and receipt, the three observed rows
reproduce the earlier batch reading so deriving the batch from the roster did
not change what executes, and the specimen class routes to
no_committed_specimen_identity rather than to not_selected_into_execution_batch
— the distinction the realization coproduct exists to make.

The note also states what the run does not establish: the four unmeasured
subjects are unobserved, not staged, and node's prior interruption is neither
confirmed nor refuted here.

* BL-2: render stages through the one label authority

Review on #8208 noted that stage_label_of paralleled frontier_ingestion_probe's
frontend_stage_label and disagreed with it on one variant — NORM_ACCEPTED
against ACCEPTED — and judged it non-blocking because it is presentation-only.
Taking it anyway: it is a second renderer for one closed coproduct, and it had
already drifted, which is the §3 fork rather than a style preference. This
module's own note claims there is one stage classifier in the repository and not
two; forking the label while importing the classifier honoured that in the half
that was harder to get wrong.

frontend_stage_label is imported and the fork deleted. Confirmed by execution
that the divergence was real and is now gone: census_report prints
"use_site_verdict ACCEPTED | materialization_carriers NORM_OTHER | optional
NORM_RETAINED", where the forked renderer printed NORM_ACCEPTED for the same
stage.

The recorded first reading named stages by their rendered label, which would now
be stale. It names them by VARIANT instead, and says why: the spelling belongs
to the renderer, so a note pinning it goes stale the moment that renderer is
edited — the same reason counting may not read labels. Five variant imports were
left import-only by the deletion and are dropped; NormalizeRetained stays,
since the prior-standing rows construct it.

* BL-2: scope a census refusal to the batch, not to the roster

Review 51759 (REQUEST_CHANGES) found that body_lowering_program_receipt routed
EVERY live-file subject through the census-refusal cause. Verified and correct:
on CensusRefusedAtWorld, logic, diagnostic, occurrence_identity and node — four
subjects this run was never going to execute — were rewritten from "not selected"
into "the census refused".

That is state-space conflation, and it undid the roster/batch split on the one
path where the distinction is least visible. The two states have different
remedies: a not-selected subject needs a bigger batch or a cheaper instrument,
while a refused census needs the infrastructure repaired, so a reader deciding
what to do next was misdirected. More generally it is a failure at one scope
attributed to subjects outside that scope — the absorbing fallback wearing an
error's name, manufacturing information about subjects nobody looked at.

The refusal cause now reaches only selected subjects. A subject outside the
batch keeps not_selected_into_execution_batch under refusal exactly as under
success, because nothing about it was attempted and its standing does not depend
on the census. The specimen class keeps its own structural cause under both.

Proven by execution with a discriminating control, not by inspection.
census_refusal_does_not_reach_a_non_batch_subject_RED returns false against the
previous arm and true against this one; the other three assert that a batch
member does receive the cause, that the cause is carried rather than fixed, and
that the specimen class is unaffected. The arm takes a cause symbol as input, so
it is reachable by ordinary call and needs no broken tree to observe.

* BL-2: derive what a stage licenses, and correct the stale occurrence_identity row

Per-subject censuses (one subject per run) measured six of the seven runnable
subjects. Two consequences, plus the reading that keeps them from being
misread.

THE STALE RECEIPT ROW. occurrence_identity measures NormalizeOther, not the
NormalizeRetained the exact-head receipt records. That receipt is explicit that
the member was never re-run after the repair, so the cell was unknown rather
than confirmed — and it is now known stale. Corrected where the receipt lives
rather than only in a message, because a stale cell in a cited authority is the
failure that survives on the assumption that someone else checked it. logic
independently reproduced what the receipt predicted for it, which is evidence
the receipt was right about the mechanism and wrong only about the row it could
not re-run.

WHAT A STAGE LICENSES IS NOW DERIVED, not left to prose. Leaving NormalizeRetained
is not evidence of repair: NormalizeOther is by construction neither retention nor
a graft refusal, so it names where a module refuses and never why. ObservedReading
projects that — ObservedClean for the one admitting stage, ObservedRefusedCauseNamed
where the classifier identified the kind, ObservedRefusedCauseUnclassified for
NormalizeOther alone. Derived from the stage rather than stored beside it, so it
cannot disagree with the coproduct it reads.

A READING EXISTS ONLY WHERE SOMETHING WAS OBSERVED. A first draft of
reading_of_standing mapped SubjectUnmeasured and SubjectInterrupted onto the
unclassified-refusal arm on the reasoning that all three leave the cause unknown.
That is the census-refusal conflation again, in the module that just repaired it:
unknown-because-unclassified and unknown-because-unattempted are different
unknowns with different remedies. StandingReading keeps them apart.

Nine witnesses, all executed. Two are discriminating controls for the classes
above: NormalizeOther must not read clean, and an unmeasured subject must have no
reading at all.

node is not included in any of this. It was SIGKILLed at 346s while every other
subject finished between 138s and 292s; the signal is observed and the cause is
not established, so it stands interrupted with a fresh lower bound and carries no
stage. The six completions sit within a factor of 2.1, so the earlier 2h51m
ten-member run is unexplained by them, and node dying rather than finishing left
node-is-the-outlier versus superlinear-in-roster-size open.

* BL-1: seal NormalizedTree behind an admission door so retention cannot reach resolve

normalize computed 'this root's body lowering left no wrapper behind' and had
nowhere to put it: NormalizedTree was an alias for Node, so the fact travelled
as diagnostics beside a bare Node and was dropped at the first FreeMonoid<Node>
carrier before resolve.

Restore the carrier through the root-carrying signatures and give it a door.
NormalizedTree becomes a sole_constructor record; admit_normalized_tree refuses
a root carrying wrapper-retention evidence. The retention recognizer lives once,
beside its single producer in v2.std.compilers.body_lowering, and scans the whole
diagnostic list -- rejected_with_pending prepends outer diagnostics, so a
head-only read is systematically wrong.

Rung: accepted refinement with executing refusal, not structural impossibility.

Also records the typecheck-blindness receipt on the hollow-alias lane: the
conversion reported 0 blocking errors with a record in a Node position AND a raw
Node in the NormalizedTree field, and surfaced only under execution.

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

* BL-1: correct the typecheck-blindness receipt to wrapper-payload conflation

The ContentHash specimen was characterised as a family member in union
position. Read on main, Fnv1a64Structural is the coproduct's PAYLOAD, not a
member -- and that is the stronger fact: no alias participates in it, so the
class is not a quirk of alias declarations.

Both specimens are one shape. The typechecker does not distinguish a wrapper
from the thing it wraps.

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

* BL-1: delete the retention-recognizer fork, consume the producer's own

Review 51940 is right and this was my error. body_lowering_fold already carried
body_lowering_diagnostic_is_wrapper_retained and the whole-list
body_lowering_any_diagnostic_is_wrapper_retained -- landed by BL-0 -- and I
minted a second copy in v2.std.compilers.body_lowering under the SAME names,
then pointed the admission door at the fork. Two modules could answer 'does this
carry retention?' and drift when the reason symbol moved: the exact parallel
authority the change was supposed to close, authored by the commit message that
claimed to close it.

The std/ copy is deleted whole (that file is now byte-identical to main).
body_lowering_fold gains one Diagnostics-grain reading beside its existing pair,
delegating to the same whole-list scan, and normalized_tree imports it from
there. No cycle: body_lowering_fold does not reach normalized_tree.

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

* BL-1: convert the name-resolve fixtures, and close three seams they exposed

Review 51952 is right that the name-resolve fixtures still passed raw
FreeMonoid<Node>. Converting them and RUNNING them exposed three genuine
production seams in this diff, none reported by the typechecker:

  admit_import_root_find declared Outcome<Node>              -> no field 'children'
  symbol_index_fill_module_roots declared FreeMonoid<Node>   -> no field 'kind'
  ModuleRootFound / PassingCandidateFold declared Node       -> no field 'kind'

The third reached a shared std carrier, so PassingCandidateFold is parameterised
over its candidate type: the cardinality of a search result is not a fact about
what was searched, and it was declared over Node only because every consumer
happened to fold over Node. A local fold type would have been a second name for
one concept; projecting and re-wrapping would have re-minted the carrier outside
the door.

Fixtures admit through the real door, and the refusal arm fails the witness
rather than fabricating a value.

Records the denominator in section 8.2: 39 declarations across 7 modules, every
Node-meeting point resolved as one of 16 .root projections, 1 list projection,
or 3 converted callees.

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

* BL-1: convert the witness callers the floor found, and re-cut the denominator at its true boundary

The floor reported nine runtime 'no field root on type Node' failures across
three witness modules. All nine are mine: the first denominator was drawn at
production modules reachable from the converted chain, and witness modules meet
a converted value exactly as production modules do.

Adds admit_normalized_roots -- the plural door, refusing as a whole so a
partially admitted list has no representation -- and routes every hand-built
fixture through it. Negative witnesses keep a distinct arm for fixture-admission
failure so a broken fixture cannot masquerade as the rejection under test.

Denominator re-cut as a caller census with its boundary named: 28 modules
reference the converted APIs, 13 feed from normalize and are unaffected, 15
hand-build roots and 12 needed conversion. Residue stated rather than implied --
the instrument is execution, so a witness that meets a converted value and does
not execute is broken silently; intersecting the 190-file no-executing-consumer
roster against the callers yields 6 files, 4 hand-built, all executed green.

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

* BL-1: stop the plural door duplicating its shared diagnostics N+1 times

Review 52023 flagged admit_normalized_roots re-passing the outer diagnostics
into each per-element admit. It is a real defect, not just a smell: bind_outcome
MERGES accumulated with incoming, the seed carried the shared set, and every step
carried it again -- so an N-element list accumulated N+1 copies of one diagnostic
set. Reachable only from tests today because every caller passes None, where
merging is harmless.

The obvious fix -- admit each element under None -- would have made the plural
door total, i.e. a cast around the door rather than the door. So the decision
stays per element (same verdict each time, since it reads only the shared
diagnostics), the threading becomes explicit instead of bind_outcome, the
refusal keeps carrying its pending set exactly once via admit_normalized_tree,
and the accepted diagnostics are attached once at the end.

plural_admission_does_not_duplicate_shared_diagnostics_RED asserts the result's
diagnostics equal the input for a two-element list; restoring the bind_outcome
threading flips it to false.

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

* BL-1: convert the self-host intermediate the caller census missed

Review 52034 found frontier_probe_first_missing_import_lookup declaring
roots: FreeMonoid<Node> while receiving validated_roots from
validate_module_roots and forwarding it into module_root_lookup -- the exact
seam this PR closes, left open on a self-host consumer.

The census had classified that module safe by checking WHERE its roots came
from (an already-admitted ingest fold) rather than WHAT each intermediate
declared. Provenance is not a substitute for declaration, and that is the third
time this population was drawn at the wrong boundary.

Replaces the judgement with a mechanical check: for every function, does it pass
one of its own Node / FreeMonoid<Node> parameters into a converted API. Corpus
wide it reports zero -- and the instrument is validated against the defect it
must catch, reporting exactly the frontier_probe site when that parameter is
reintroduced and zero when the fix is restored.

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

* BL-1: close the conversion population by fixpoint over three syntactic shapes

Review 52042 found a witness helper passing normalize's result into
qualified_name_from_module_node without projecting .root -- a shape neither
earlier check covered, on a path backing an enrolled long-lane witness.

The class has three shapes and they compose into a fixpoint:
  A  own Node parameter passed into a converted API
  B  a NormalizedTree-producer binding passed into a Node-declared parameter
  C  a function declaring -> Outcome<Node> while returning a producer

Fixing a shape-C return type makes its consumers visible to shape B, and fixing
those exposes more shape C. Iterated to convergence: A and B report zero; the
four remaining C hits are confirmed false positives, feeding resolve, which takes
the admitted carrier, with ResolvedTree = Node so their return is correct.

Closed here: 6 witness/lens helpers, 6 return declarations, 8 fixpoint-surfaced
consumers. Each shape was discovered only after a review or CI surfaced an
instance -- with no checker signal there is no way to derive the shapes, only to
enumerate them, and that is the finding worth keeping.

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

* BL-1: revert two over-applied .root projections, and record the direction asymmetry

The 58e7410 red was NOT another instance of the invisible conflation. It was
the inverse: .root applied to a genuine Node, which the checker DOES catch
statically at resolve. Two sites, both introduced by my own bulk string replaces
hitting call sites whose value came from resolve (ResolvedTree = Node) rather
than from normalize -- parse_binding_fidelity_support pbf_resolved_add_arrow and
stage_bridge pipeline_match_corpus_resolve_relationship_holds.

Records the asymmetry in the receipt, because citing both directions as
typecheck blindness would inflate the class: payload-supplied-where-wrapper-
declared is invisible; wrapper-projection-on-payload is caught.

Also records that what kept being wrong was the ENUMERATION, not the fix. The
population was cut three times -- production, then _test modules, then support
modules -- each drawn where the last failure landed rather than from a rule. All
three were proxies keyed on path or role. The sweeps key on the only thing that
matters, whether a declaration meets a converted value, and glob the whole tree
with no path filter.

Verified: all 18 files carrying an introduced projection resolve clean, and the
three tree-wide sweeps report A=0, B=0, C=4-known-false-positives.

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

* BL-1: correct 8.2's direction rule -- one symptom, two defects, truthfulness of the declaration is the discriminator

The asymmetry this receipt claimed (payload-into-wrapper invisible,
wrapper-onto-payload caught) is refuted by its own two runs: the SAME projection
on the SAME two types produced 81 runtime failures on one floor run and 1 resolve
failure on the next.

Verified against both. The 81 came from validate_module_roots, whose fold binder
is DECLARED NormalizedTree, so root.root is statically correct and the real
defect sits upstream at the call boundary -- the invisible class, whose symptom
is a projection failing at runtime wherever the untruthful value is finally read.
The 1 came from pbf_resolved_add_arrow, whose binder is declared Node, so the
declaration itself is the mismatch and resolve refuses it.

The discriminator is whether the declaration at the site is truthful: a mismatch
is caught exactly where it is expressible against a declaration, and invisible
where the declaration is right and only the value flowing in is wrong. Also notes
that 81 occurrences are not 81 instances -- they are one symptom of fewer
boundary defects, read at many sites.

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

* Admit the add-arrow fixture through the door instead of casting past it

Main has been red since BL-1 on one witness, five merges deep:
arrow_body_form_eval_value_vertical_holds fails with `no field 'root' on
type 'Node'`.

`resolve` declares `tree: NormalizedTree` and dereferences `tree.root`.
The witness passed `dag_add_arrow_with_body_node()`, which returns a raw
`Node`. ABF-1 was authored against the old bare alias
(`type NormalizedTree = Node`) and merged after BL-1 sealed the carrier —
both sides individually correct, the hazard is the merge order.

Nothing refused it at compile time because `module_skips_direct_call_arg_check`
exempts every `v2.*` module from the direct-call argument TYPE judgment, and
this witness is `v2.test.claim.body_lowering`. So it surfaced only where
`.root` was actually dereferenced, at runtime.

The repair is the smaller one: the fixture reaches `resolve` through
`admit_normalized_tree`, the `Outcome` is threaded, and the `Rejected` arm
FAILS the witness. No `.root` accessor at the call site, no widening of
`resolve` to take a `Node`, no weakening of the carrier — reaching past the
door is exactly what BL-1 made unwritable, and a fixture that cast past it
would plant the wrapper-payload conflation in the witness population.
Pattern follows `admission_fail_closed.dag` (`fc_admitted` / `fc_with_roots`).

Green by execution: the witness returns `true`. The discriminating RED needs
no construction — it is main's current CI failure on this exact function.

NOT fixed here, reported rather than silently left: three sibling sites in
`manual/body_lowering_normalize_add.dag` pass raw `Node`s into the same
parameter (its `body_lowering_normalized_module` is declared
`-> Optional<Node>`). They are latent, not absent — `manual/` is outside
per-PR discovery, so nothing executes them today.

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

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant