Skip to content

Repair the first() interpreter/emitted semantic divergence — census the 187 candidate sites, then derive both arms from one authority - #9921

Closed
gunbai-bot[bot] wants to merge 27 commits into
mainfrom
scratch/cell34
Closed

gunbai-bot[bot] wants to merge 27 commits into
mainfrom
scratch/cell34

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

Auto-opened by session-dashboard for session still-swift-363.
Pushing to scratch/cell34 advances this PR.

Worker attestation

Before flipping this PR to ready for review, confirm each item:

  • Title describes the change (not the session id or branch).
  • PR body summarises what and why (replace the TODO below).
  • Tests run: name the command (e.g. npm test, cargo test) and the result.
  • If this closes a work item, the body contains a Closes #N directive.
  • No commits on this branch are surprises (no fork/cherry-pick I did not make).
  • No secrets / credentials / large binaries staged.

Summary

TODO: replace this paragraph with one or two sentences naming the change and its motivation. Reviewers read this first.

Test plan

  • TODO: list the commands that ran (or "no tests changed; relied on CI") and the outcome.

gunbc-ci-auto-heal and others added 27 commits August 31, 2026 02:30
…f that has an authority

`dag/std/algebra.dag` declares `first`/`last`/`get`/`lookup`/`map_get` with
`return_type: OptionalOf { inner: ReceiverElement }`. The Rust emit arm realizes that row
(`{recv}.first().cloned()` -> `Option<T>`); the interpreter answered the same question by hand and
answered it differently -- `items.front().cloned().unwrap_or(Value::Null)`, the RAW element. Two
realizations of one declared signature that disagree are DESIGN.md section 5 silent wrongness,
outside the guarantee ladder rather than low on it.

CENSUS (docs/plans/first-optional-divergence-census.md). 186 terminal `|> first` sites over 81
files in dag/ + src/v2 on main, rostered at identity grain in three shapes: 143 eliminated by
`match` (the population the interpreter's compensating raw-unwrap arms already made agree, and the
one the repair must not break), 37 returned onward as the enclosing function's `T?`, 6 flowing into
a value position. The count is for reconciliation with the parent lane's 187/82 only; the roster is
the deliverable.

The census did not stop at the pipeline spelling, and that is where it earned its keep. The METHOD
form `.first()` is a separate population of 646 occurrences over 180 files whose dominant idiom is
the value position -- `parse_int(s: fields.first())`, `trim(tokens.first())`,
`percent(scalars.first())`. Those work today because the emitted arm inserts
`rust_call_arg_fail_closed_unwrap`'s `.expect(..)` while the interpreter needs no coercion at all,
having never wrapped in the first place. So the raw-element arm is not one bad handler: it is the
compensation the interpreter's MISSING argument coercion has been leaning on corpus-wide, and
repairing `first` alone converts a silent agreement into a silent disagreement.

MEASURED, not argued. A five-case probe run through `gunbc run` fixes the divergence (`[Absent] |>
first` read as an empty list; `(["x"] |> filter(..) |> first) == Present { value: "x" }` false
interpreted and true emitted). Those five rows are enrolled in
dag/test/claim/first_optional_construction_witness_test.dag, 7/7 green with this change and 4 red
against the unmodified arm, with three green positive controls separating "constructs the Optional"
from "refuses everything". branded_list_first_optional_witness stays 8/8 green.

REPAIRED HERE: `first`/`last`/`get`/`lookup` construct the Optional their roster row declares,
decided by call site rather than value shape (the rule `map_lookup_as_optional` already states);
`eval_algebra_method_inner` refuses when an arm's result does not inhabit the optionality
`all_algebra_field_templates()` declares for it; `.value` on an absent Optional refuses instead of
returning `Value::Null`; and `call_function_inner` gains the optional-into-required-parameter
coercion the Rust emitter already had, unwrapping `Present` and stopping the line on `Absent`.

NOT CLOSED, and the census says why: a builtin call never reaches `call_function_inner`, and
`builtin_function_registry` maps a builtin to a RETURN TYPE only, so argument cardinality cannot be
derived for one. Deciding it from the argument's value shape is validation standing where
construction was available. The grounding this class waits on is builtin PARAMETER signatures; the
doc names it as the blocker rather than working around it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK
The parent lane's read of the first draft is right — a shape says where the value goes, only a
disposition says whether the two realizations answer differently on an input the corpus can reach,
and this document is about to be cited instead of re-derived.

186 occurrences resolve to 181 real sites over 81 files plus 5 non-sites (4 inside `//`
annotations, 1 inside a string literal carrying a probe program), which reconciles exactly with the
parent's 187/82 as that count minus #9775's own known-red row.

Dispositions, each measured rather than asserted:

- AgreesUnderCompensation, 142. Its failure condition is an element type that is itself `Optional`,
  and the corpus declares five list-of-optional carriers in total, all in witness tests, none
  reaching a `first`. Zero harmed today — which is exactly why the class stayed invisible: the shape
  that dominates the corpus is the one the compensation covers.
- Propagates, 36. Resolved one level out by following all 36 functions to their call sites: 72
  callers eliminate by `match`, 2 tail-propagate into another `T?`, and 4 compare `== none`, which
  agrees only because a miss is `Null` on one side and `Absent` on the other and both compare equal
  to that one constructor. Zero harmed today, by a margin one constructor wide.
- HarmedNow, 3, listed in full: `cache_facts_for_id` declaring `-> CacheInterfaceFacts` over a
  `first()` (with `cache_layer_plan_primary`/`_fallback` as sibling defects in the same module), and
  two `measure_count(m: .. |> first)` argument sites that agree while non-empty and diverge on empty.

Three of 181 read alone argues the class is not worth repairing. It is the wrong denominator, and
the method-spelling section already says why. Both filters over that population are now named
beside their producers — 646/180 here, 655/178 by the parent's independent filter — because they
disagree, and a disagreement is the reason to cite the producer rather than the figure.

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

Reading std/ before authoring turned the routed step-1 task into a different one, and the
difference is the second time on this class that the obvious repair was the wrong one.

dag/std/primitive_identity.dag ALREADY models the callable-signature grounding and already
executes: PrimitiveSignatureGrounding, PrimitiveSemanticContract, PrimitiveSignatureResolution
(SignatureResolved carries `parameters: List<AlgebraTypeTemplate>`), primitive_signature and
primitive_arity, green in primitive_signature_grounding_witness_test. Its own doc comment refuses
the fork this lane was about to commit -- "the contract carries a KEY into the one authority, never
its contents" -- and it keys on (canonical_name, profile) rather than name alone precisely because
`get` reads differently on the List and Map profiles. So there is no carrier to mint, and authoring
one would have been the section 3 nickname the routing message warned about.

The gap is coverage, and it is measured: of builtin_function_registry's 131 names, 20 resolve
through primitive_signature and 111 answer SignatureNotGrounded. parse_int -- the name that reds the
corpus control -- is one of the 111.

The 111 are two populations and nothing separates them: language primitives that should carry a
signature, and host/lens transports whose parameter shape is a Realization fact and belongs with
the transport. The obvious discriminator fails, measured rather than assumed:
gunbc.v1_interpreter_primitive_surface enumerates an arm for both by construction, so joining on it
classifies doc_graph_orphan_count and parse_int identically. Splitting on a naming convention would
be the smuggled heuristic section 5 names, so this lane raises the question instead of picking.

Also recorded, independent of this class: the 20 overlapping names are two authorities that already
DISAGREE. The registry is receiver-blind -- `reverse` types as List where algebra says ReceiverSelf
(so a String reverse), `map_keys` and `map_values` share one element type variable where algebra
distinguishes ReceiverKey from ReceiverValue, `concat` types as String where algebra says
ReceiverSelf. Same fork as the first() divergence, one layer up: not two realizations of one
declaration, but two declarations of one operation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK
…ss is Phase B of an open lane

THE NUMBER, CORRECTED AND THEN ENUMERATED. An earlier revision reported the partial repair as
flipping ONE witness. That was a two-file claim_batch sample reported as a corpus bound, and worse:
the witness it named, bmc_capability firmware_wire_version_is_parsed_before_track_matching, has
disposition declined_outside_gate_closure / not_executed, so the floor never runs it. The sample was
drawn from outside the population the floor measures.

The floor reports failed=442 of 3141. The job log prints only six per-claim lines, which reads as
truncation and is not -- the required-floor-disposition ARTIFACT separates the outcomes the summary
folds: 442 runtime-errored-before-verdict, 6 failed (assertion), 1 budget-refused, 47 route-gap, 15
known-red-held. The 442 errored before reaching a verdict; they did not assert and fail.

ALL 442 ARE v2.test.* and none is a dag/test/claim witness. The v2 compiler is .dag interpreted by
the v1 seed, so changing the interpreter's projections changes v2's own behaviour as it runs. The
blast radius is the interpreted v2 compiler, which is a different shape from the value-position
argument sites this census predicted.

A MECHANISM CORRECTION, which matters more than the verdict it supported. This document said the
four `== none` sites agree "because a miss is Null on one side and Absent on the other and both
compare equal to none". Wrong: in the interpreter `none` EVALUATES TO Value::Null, so the raw side
compares equal because it IS Null, and a constructed Absent variant does not compare equal at all.
Those sites agree BEFORE the construction and break after it; two are among the six assertion
failures. The verdict was right about the pre-change state by the wrong route, and the wrong route
is what hid the none-literal migration from the first draft.

THE CLASS ALREADY HAS AN AUTHORED PROGRAM. gunbc.plans.value_null_split (lane keen-ferret-250)
models Value::Null's four overloaded meanings and phases the repair A-E. This branch is its Phase B,
built without knowing the plan existed. Its Phase-A witness predicted this branch's failure BY NAME:
"raw_get_miss_differs_from_optional_absent .. flips RED in Phase B when get+Optional routes through
map_lookup_as_optional" -- and it is one of the six. That is the enrolled signal Phase B landed, not
a defect. Section 0 of that plan also pre-refutes a blanket cross-representation equality guard,
because present == None -> false is legitimate at ~218 sites.

So the completion has THREE gates: the argument coercion (needs the primitive denominator), Phase D's
none-literal migration over ~218 sites in 66 files, and Phase C's bridge deletion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK
Conflict in dag/gunbc/recurring_failure_mode.dag: main's #9769 appended
surface_shorthand_preempts_resolved_identity while this branch appended
coarser_parallel_authority; both also appended to the roster. Kept both,
main's first, in the declaration region and the roster.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK
…e field that carries known-red discrimination

The census gains the post-merge re-measurement with a trunk control (main at
b41d564 is 0 errored / 0 failed / 3065 passed against this branch's 426/6/2619),
which is what makes the attribution a measurement rather than a reading of the
diff. It also refuted an attribution this document would otherwise have carried:
two failures name self_host_symbol_identity_binding_witness, merged in from main
the same hour, and the clean trunk says they are this branch's.

main_wet is added as a measured victim outside the floor -- it refuses under this
branch's own coercion arm -- which is why the DESIGN.md and design-ledgers.md
projections are deliberately left inconsistent rather than regenerated from a
stock-interpreter seed.

floor_non_verdict gains two sentences naming which field carries the property
readers cite it for: non_verdict_unenrolled, not known_red_now_passing. An
earlier draft of this lane proposed a 4b class row asserting an unguarded
conflation there; that was wrong -- the wall exists and fired -- and a row
claiming a missing guarantee over a working wall is rung deflation. Annotation
only, semantically inert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK
Same conflict shape as the previous merge: main's #9786 appended meaning_fork and
externalized_degradation while this branch carries coarser_parallel_authority, and
all three also append to recurring_failure_mode_roster. Kept all three, main's
first, in both the declaration region and the roster.

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

rust-unit-tests nfr_roster_receipt refused this branch with one unrostered
non-fold residue site: value_null_phase_eq, added by the plan amendment. It
matched a, then matched b inside each of seven arms with a '_ => false'
wildcard, which is a wildcard over a CLOSED coproduct -- un-migrated modeling
under DESIGN section 6, and the detector is right to flag it.

Fixed by construction rather than by rostering it. The roster entry was
available and would have greened the test, but registering the site admits debt
where a fold was available. Equality now derives from value_null_phase_ordinal,
one exhaustive 7-arm projection with no wildcard, following std.fermi
fermi_ordinal.

The safety difference is why the detector exists: under the old form an eighth
phase would silently take the '_' arm at seven sites and compile clean; under
the new one it makes the ordinal non-exhaustive and the compiler refuses.

Verified: nfr_ suite 14/14, including red_control_wildcard_over_closed_coproduct
_is_residue -- the detector still discriminates, so the site is gone rather than
the check blunted.

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

Added by 32606e4 as a one-afternoon probe and never removed. It is the §6
experimental-residue tell: a hand-authored root-level shell script with no final
consumer, raw shell implementing semantics expressible in .dag, nothing calling
it and no CI reference. It does not survive the terminal architecture.

The reason it must not merge is stronger than hygiene. Its roster comes from
grep -E '^FAILED|FAILED in' over the run log, and this branch established that
the log FOLDS distinctions the required-floor-disposition artifact splits --
log failed=427 against the artifact's 6 failed plus 421 runtime-errored. Worse,
runtime-errored claims emit no FAILED line at all, so this script reports them
as absent. That is exactly how this lane first reported the blast radius as one
witness when it was two orders of magnitude larger. Checking it in would hand
the next reader the instrument that caused that error, with the repo's implicit
sanction, at the moment the better instrument was proven.

If a standing floor probe is worth having it is a modeled entry point reading
required_floor_disposition.tsv, authored as its own change rather than as cargo
on a semantic repair. Local probes belong in the scratchpad.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK
…on is reachability not a new call

required-witnesses-build fails after refreshing onto main (42 commits) where it
passed at abee235. Not stale artifacts -- the obvious hypothesis and the wrong
one. run_generated_artifact_drift_gate_body refuses with NoSuchField { Optional,
shape } at extdeps.bmc.types:183, 'matches.first().shape', a field read straight
off a first() result.

The site predates this branch (#9238) and was present at abee235 under a green
build lane. Nothing in main is defective and nothing here changed to reach it;
main widened generated_artifact_gate by 98 lines and the site entered the gate's
evaluation closure. Neither side is broken alone.

In-class rather than a census miss: it is a value-position method-form site, a
member of the 646-occurrence population this document names and deliberately does
not roster at identity grain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK
…ate drift, not the bmc.types field read

The previous commit recorded NoSuchField at extdeps.bmc.types:183 as the cause of
the required-witnesses-build red and explicitly ruled out stale artifacts. Both
halves were wrong. The CI log reports drifted=2 (DESIGN.md, docs/design-ledgers.md)
and unadjudicated=3 (three workflow yml artifacts refusing with the same
CallContractMismatch on outcome_accepted that main_wet gives) -- so the ruled-out
hypothesis was half the answer and the coercion arm is the other half.

The error was method, not arithmetic: I reproduced A failure locally and treated
it as THE failure. The local entry evaluates the gate body as one expression and
dies at its first refusal; CI adjudicates per artifact path and records an outcome
for all 35. Same subject, two routes, different first failures.

Also records what made the lane blocking: main #9814 restored generated-artifact
drift to required CI, so the deliberate projection drift is no longer latent debt.
The refusal to fabricate those bytes from a stock-interpreter seed stands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK
Third consecutive integration conflicting on dag/gunbc/recurring_failure_mode.dag
and always the same way: main appended instrument_output_read_as_subject_content
and executed_conjunct_discriminates_nothing while this branch carries
coarser_parallel_authority, and all three also append to the roster. Kept all
three, main's first, in both regions.

The resolution again orphaned the closing 'evidence: [],' and '}' of the LAST
appended block -- the known append-vs-append class, where git's conflict region
ends before the shared tail so the block at the boundary loses it with no marker
to signal the loss, and a name-based declared-vs-rostered join passes anyway. The
repair is now generic (any 'authored:' line not followed by 'evidence:') rather
than a hand fix per instance. Verified structurally at 27/27 well-formed with the
roster joining both ways, and by execution: the module evaluates and returns all
27 rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK
Fourth consecutive integration conflicting on dag/gunbc/recurring_failure_mode.dag,
identically each time: main appends rows and roster entries at the same two append
points this branch's coarser_parallel_authority uses. Kept both sides, main's
first, in both regions -- 30 declarations, 30 roster entries, joining both ways.

The resolution again orphaned the last block's closing 'evidence: [],' and '}'.
That is structural, not incidental: git's conflict region ends before the shared
tail, so whichever side's final block sits at the boundary loses it, no markers
survive to signal the loss, and a declared-vs-rostered NAME join passes over a
file that no longer parses. Verified structurally (30/30 well-formed, no identity
mismatch, no duplicates) and by execution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK
# Conflicts:
#	dag/gunbc/recurring_failure_mode.dag
…nt and refusing Absent

Constructing the Optional for `first`/`last`/`get`/`lookup` created a second
obligation -- an `Optional<T>` argument now meets a parameter declared `T` --
and the coercion this branch added to satisfy it was wrong on GENERIC formals.

`param_declares_required_value` asked three questions of a parameter's declared
type and none of them could see a free type variable. For
`fn outcome_accepted<T>(value: T)`, `T` is not spelled `Optional`, carries no
`CardOptional` flag, and is not the parameter's own name, so the predicate
answered "required" for a formal that declares nothing about cardinality at all.
`Optional<Node>` is a legitimate instantiation of `T`, not a cardinality escape.

BOTH ARMS WERE WRONG, and the quiet one is the worse one. `Present` was
UNWRAPPED into the callee: a caller whose `T = Optional<Int>` had the callee
receive `Int`, which is precisely the silent semantic divergence this branch
exists to remove, reintroduced at a new seam by the repair itself. `Absent` was
REFUSED with a located `CallContractMismatch`, which is loud but equally false,
and it took down the generated-artifact regen actuator -- three workflow
projections reached no verdict in the required build lane for this reason.

THE FIX READS THE DECLARATION, NOT THE SPELLING. `v1.compiler.parse`
`parse_fn_body_from_prefix` builds a fn node's `params` as
`concat(type_params, value_params)`, and a type parameter is exactly the entry
whose declared type is its own name -- the same shape the positional-parameter
filter beside it already uses. Naming that set is what lets the predicate tell a
free type variable from a declared non-optional formal, so the coercion declines
where the declaration is silent instead of fabricating a requirement.

This is the producer that `gunbc.recurring_failure_mode`
`mitigation_injected_where_judgment_declined` names as its trigger, arrived at
from the interpreter side: that row was filed against the Rust emitter for the
same seam and the same callee.

EVIDENCE, both enrolled and both discriminating. Measured on the head that
carried the defect, a `Present` into a free type variable reported "UNWRAPPED --
arrived as a bare value" and an `Absent` raised CallContractMismatch; both now
report the Optional whole. They stay enrolled as regression controls beside the
existing positive control rather than retiring with the climb.

NOT FIXED HERE, and unchanged: builtin calls bypass this path entirely, so
`parse_int(s: fields.first())` still receives the Variant raw. That is the
declared grounding hold -- the builtin registry maps a name to a return type
with no parameter list -- and it has 9 located sites inside the regen actuator's
own import closure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23
… argument, got Variant` said nothing about where

`eval_builtin` receives a name and a value list and has no span, so an
argument-shape refusal reached the operator as a typed sentence with no file and
no line. DESIGN section 5 admits typed AND LOCATED; typed alone is half of it,
and the half that is missing is the one a reader needs to act.

It became load-bearing in this branch. Constructing the `Optional` for
`first`/`last`/`get` started routing Optionals into builtins, and a builtin
carries a return type with no declared parameter list, so no coercion can be
derived for it and the refusal is the ONLY signal the reader gets. An unlocated
one hands them a corpus to search.

The call node is in scope at the builtin dispatch site and carries the span, so
the location is attached at the one seam that knows it, in the `file:offset`
form the interpreter's other located diagnostics already use.

NARROW BY CONSTRUCTION, and deliberately so: this locates refusals raised by
builtin dispatch and nothing else. Interpreter-wide diagnostic location is a
separate class with its own trigger and is not absorbed into this change.

RECEIPT, and it corrects something I would otherwise have reported wrongly. The
same actuator run, twice, same binary and same arguments, refuses in two
DIFFERENT places: once `NoSuchField { type_name: "Optional", field: "shape" }`
and once `dag/extdeps/ollama/capability.dag:4740: parse_int expects a string
argument, got Variant`. The population of un-migrated consumers is walked in a
map order that is not stable across runs, so any single run names one member of
it. Without the location I would have read the second run as the first defect
having moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23
… of the operation was never measured

The census declared its population as "every terminal `|> first` occurrence".
That is a SYNTAX, not the operation. The corpus writes the same declared
operation two ways and the pipe form is the smaller one: 224 pipe-form
occurrences against 661 `.first()`, 19 `.last()`, 41 `.lookup()` and 7 `.get()`
across 183 files.

Every consumer that has actually failed in production is in the half the census
never looked at -- `parse_int(s: fields.first())`, `matches.first().shape`,
`lines.first() == schema`.

RE-DERIVED AGAINST THE POPULATION THAT ACTUALLY GATES LANDING: the 944-module
import closure of `tools.generated_artifact_gate`, classified by what CONSUMES
the result. 270 consumer sites -- 137 match-eliminated, 96 propagated, 14
compared to a bare value, 11 declared-fn arguments, 5 field accesses on the
result, 3 builtin arguments, 1 method call on the result. Twenty-three are
un-migrated and are why the closure will not load.

THE CLASS THE CENSUS NEVER NAMED IS THE LARGEST AND THE WORST. Fourteen sites
compare a `first()` result to a bare value. Comparison is the one shape with no
pattern for a compensation arm to intercept -- which the census's own probe
table establishes from the other side -- and nine of the fourteen are
merge-admission receipt parsing, where a schema check that silently answers
false is exactly the failure this branch exists to remove.

The mechanism, root cause, two-sided argument and probe table are unaffected;
they are about the mechanism, not the population. The disposition counts must
not be cited as a population or as completeness, and the document now says so at
the head of the section that carries them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23
…fabricated `false` instead of refusing

Constructing the `Optional` for `first`/`last`/`get`/`lookup` made every consumer
that COMPARES the result to a bare value compare across two representations.
`Value::eq` cannot decide those, so the comparison silently answered `false`.
DESIGN §5 forbids exactly that: a failure arm must refuse, never fabricate a
plausible answer. This is the second silent arm this repair produced -- the
free-type-variable unwrap was the first -- and the reason is structural: a change
to what values ARE turns every consumer of the old representation into a seam
that must be made loud.

MEASURED BEFORE THE WALL, both shapes silent:
  `["schema-v2", "body"].first() == "schema-v2"`  ->  false
  `[] |> first == none`                            ->  false

Nine of the fourteen comparison sites in the generated-artifact gate's 944-module
import closure are merge-admission receipt schema checks, where a quiet `false`
rejects a valid receipt with no diagnostic -- on the path every other lane merges
through. The wall makes them loud, and it makes the remaining population
self-announcing rather than something a textual census has to find.

`CrossRepresentationEquality` already existed and did not fire here. It does now.

THE `x == none` CARVE-OUT WAS DELETED AFTER MEASURING IT. The first version of
this wall spared `Optional` against `Value::Null`, on the reasoning that `none`
evaluates to the Null carrier and refusing it would break the corpus's emptiness
idiom rather than the bug. The control said otherwise: `[] |> first == none`
already answered `false`, so the carve-out was preserving a silent false rather
than a working test. It now refuses with its own sentence naming the two carriers
of absence. It stays narrow by construction, not by exception: a declared `T?`
whose absent state IS `Value::Null` still compares `Null == Null` and never
reaches the check, so only a CONSTRUCTED `Optional` meeting the Null carrier
fires -- exactly the un-migrated population.

EVIDENCE. Three fixture arms, each discriminating: the bare-value straddle
refuses, the Null straddle refuses with the distinct sentence, and the positive
control -- `Optional` against `Optional` -- still compares and answers true.
The positive control is ENROLLED here. The two REDs are fixture-measured and NOT
enrolled, which the witness module states rather than glosses: a `test fn`
returns `Bool` and an interpreter refusal aborts evaluation, so this harness
cannot express "this expression refuses". A harness that can catch a refusal and
assert its reason is this seam's next-rung trigger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23
…he guard arms the suite could not see

Ten sites in `gunbc.merge_admission_produce` and `gunbc.merge_admission_subject`
compare a `first()` result to a bare value. `dag/std/algebra.dag` declares
`first` as returning `Optional<..>`, so once the interpreter constructs what that
row declares, every one of these compares across two representations and
`Value::eq` cannot decide them -- measured on the branch that constructs it,
`["schema-v2", "body"].first() == "schema-v2"` answers FALSE. These are the
receipt schema and blank-required-field checks on the path every lane merges
through, so a quiet `false` rejects a valid receipt with no diagnostic.

Each site moves from the COMPARED class into the MATCH-ELIMINATED class, which is
the class that already agrees under both semantics. The `Absent` arm is DERIVED,
not decided: `parse_receipt_wire_v2` declares `-> MergeAdmissionReceiptV2?` and
already answers `none` for every malformed case it handles -- wrong line count,
wrong schema line, blank field, unparseable attempt id, conclusion, roster hash
or PR number -- and `parse_tested_subject_wire` and `parse_git_object_id_wire`
are the same shape. `receipt_wire_v2_pr_number` already carries the exact target
form. So "the receipt has no first line" is an instance of an answer these
modules already give, and no new refusal vocabulary is minted.

IT IS A NO-OP TODAY, AND THAT IS THE OBLIGATION THIS CHANGE HAS TO MEET. It lands
on `main`, where `first` returns the raw element. The interpreter's `match_pattern`
binds a `Present { value: v }` pattern to a raw value, so each rewritten site
binds the same string it compared before and answers the same verdict; the
`Absent` arm is unreachable under the length guard each function already applies
before these checks.

MEASURED, ONE BINARY, THREE ARMS -- the change is `.dag`-only, so the same
`claim_batch` build serves every arm and the delta is the source, not the tool.

  pristine main, 36 witnesses   36 PASS
  migrated,      36 witnesses   36 PASS, verdict-for-verdict identical
  mutation control              34 PASS, 2 named FAIL

THE SUITE COULD NOT SEE THESE ARMS BEFORE, WHICH IS WHY SIX WITNESSES ARE ADDED.
Mutating the blank-field comparison to a string no field can equal left the
existing 30 witnesses ALL PASSING: they reach `parse_gate_roster_hash_wire` and
`compose_walk_attempt_id` directly and feed the wire parsers only well-formed
text, a trailing line and a malformed PR line. An uncovered guard reads as a
covered one, and without these rows the rewrite would have been "verified" by a
suite blind to it. Under the same mutation the new rows go red by name.

ONE HONEST LIMIT ON THAT COVERAGE. The mutation flips the blank-head and
blank-base rows and NOT the blank-roster row, because a blank roster line is
independently rejected downstream by `parse_gate_roster_hash_wire`. That guard is
therefore shadowed rather than discriminated, and this states it instead of
claiming three for three.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23
…er's four guards and a valid sha256 wire

Review of #9912 found the rows added in the previous commit exercised only
`parse_receipt_wire_v2` while the comment claimed both parsers. Five of the ten
rewritten comparisons therefore had no discriminating evidence at all -- the
subject parser's schema guard and its three blank-field guards, and the `sha256`
algorithm prefix, whose row accepted a valid sha1 wire and rejected md5 and so
never supplied a valid sha256 at all.

The finding is right and it is the same defect one parser over from the one the
mutation control caught. The lesson had been learned about the receipt parser and
then not carried across the file, which is what the comment's overclaim recorded.

SEVEN ROWS ADDED. Four for `parse_tested_subject_wire` over its own five-line
wire -- wrong schema line, blank base_ref, blank head_sha, blank base_commit_sha
-- plus a positive control that the all-correct builder parses, without which
every refusal row could be satisfied by a wire malformed for some other reason.
The object-id row is split into three: a valid sha1 wire, a valid sha256 wire,
and an undeclared algorithm refused. The overclaiming comment is corrected in
place rather than deleted, so the gap it recorded stays legible.

MUTATION CONTROL, ON EXACTLY THE FIVE THE REVIEW NAMED. Mutating the subject
parser's blank guards, its schema comparison and the sha256 prefix turns five
rows red BY NAME:

  object_id_wire_accepts_a_valid_sha256_wire
  subject_wire_refuses_a_wrong_schema_line
  subject_wire_refuses_a_blank_base_ref_line
  subject_wire_refuses_a_blank_head_sha_line
  subject_wire_refuses_a_blank_base_commit_line

THE FULL EVIDENCE, one binary across all three arms:

  pristine main, 43 witnesses    43 PASS
  migrated,      43 witnesses    43 PASS, verdict for verdict identical
  mutation control               38 PASS, 5 named FAIL

The sha256 row also earned its place before it was enrolled: the first fixture
carried a 72-character hex string and the row went red, which is the witness
discriminating on its own input rather than on the change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23
… closure LOADS

The re-derivation's largest non-`match` bucket is 31 sites where a one-line
textual reader cannot decide whether `algorithm: parts.first(),` is a
record-field assignment or a function argument, because the opening brace is on a
previous line. That bucket names its own undecidability rather than being guessed
into whichever class looked likelier — a guessed split would have produced a
tidier table no reader could question.

Record-field assignment into a declared non-optional field is a real consumer
class, it is somewhere inside those 31, and it cannot be counted from source
text. So 23 is what is KNOWN to block the closure, never the population, and this
document now says so where the number appears.

The consequence is a better completion criterion than any count: THE CLOSURE
LOADS. It is self-verifying, needs no population known in advance, and each fix
lets the closure load further so the next refusal names the next site —
exhaustive by construction and terminating exactly when the property we want is
true. "The known set is repaired" and "the migration is complete" are two
different assertions, and the document now requires reports to say which.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23
… were wrong

A catch-all is only visible once something has fallen out of it, so the first
census of anything should be assumed to have one. It goes here because it is what
a reader needs in order to weigh every number below it, and because this document
is its own two receipts.

First layer: the population was a SPELLING — "every terminal `|> first`
occurrence" is a syntax, not the operation, and the method-call form is the
larger by far. Second layer: the re-derivation that fixed that produced a tidy
table with six named classes and a 96-site bucket called `propagated / returned
onward, declared type honest`, which is not a class but what was left after five
were named, described in a way that reassures. It had already eaten
record-field assignment into a declared non-optional field.

THE CORRECTION WAS NOT RESTRAINT, and recording it as restraint would leave a
virtue nobody can act on. Three classifiers were written and the first two both
produced the tidy table; the bucket that now names its own undecidability
appeared only after a SPECIMEN fell out of the catch-all and showed what it was
hiding. Without it the tidy table would have shipped a third time.

The three operative rules are stated in the document: hunt the catch-all before a
reader finds it, with the tell being a bucket defined by what it is NOT or
described with a reassurance; name undecidability rather than the likelier class
and say what would decide it; and report a lower bound with a self-verifying
completion criterion rather than a number.

The class also belongs in `gunbc.recurring_failure_mode`, and is deliberately NOT
filed there yet: that carrier is mid-merge-forward under another lane, and a
third lane appending to it today would be an instance of the failure it rosters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23
`true` and `false`, zero bytes each, added in 62f11ae. They are shell
redirect residue -- not on `main`, not referenced by anything, and they reached
the branch because I staged with `git add -A` without reading what it had picked
up. Nothing to preserve; the fix is the deletion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23
@gunbai-bot

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Closing: scratch/cell34 is a measurement tree, not deliverable work. It exists to execute the acceptance matrix's cell four — the consumer head from #9912 composed with the repair-plus-wall head from #9785 — because future code need not be on main to be executable. The findings from it are reported on #9912 and #9785; nothing here is intended to land.

— sent from still-swift-363

@gunbai-bot gunbai-bot Bot closed this Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants