Repository navigation
Merge admission: eliminate the ten first() comparisons before the Optional repair turns them silently false - #9912
Conversation
…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
…hree silent arms from one repair, two authored while fixing the previous one When a change alters what a value IS, every consumer that reads the OLD representation becomes a seam, and at each seam the author's instinct is to keep the observable behaviour the same. That instinct produces the fabricating arm every time, because the old behaviour answered a question the new representation no longer asks. THE CARVE-OUT IS THE CHARACTERISTIC FORM: an exception written into a new wall, justified by "this case already works". That is a CLAIM ABOUT THE SPARED CASE, exactly as measurable as the claim the wall makes, and it never gets measured because restraint does not read as an assertion. Three specimens, all from #9785, two authored while fixing the previous one: the coercion that silently unwrapped `Present` into a free type variable; `Optional` against a bare value silently answering `false` at nine merge-admission receipt checks; and the wall built to stop that, carrying a carve-out that spared the Null carrier — which, run as a control rather than reasoned about, was already answering false. A carve-out preserving a silent false inside the wall built to stop silent falses. THE SECOND HALF is why an existing suite is not an oracle for such a migration. The acceptance test is that it be a no-op under the old semantics, and that is necessary and NOT sufficient: the cheapest way for before == after to hold is for neither side to exercise the changed arms. Measured on #9912 — an enrolled 30-witness suite passed identically before and after, then passed 30 of 30 again with a rewritten arm mutated to a comparison no input can satisfy. Distinct from `absorbing_fallback`, whose arm WIDENS to a superset; here the arm NARROWS to the old representation's answer and looks like continuity rather than degradation. Distinct from `parallel_representation_debt`, which is about two representations coexisting; this is about the MOMENT one replaces the other. Projections regenerated by the actuator (`main_wet_one` for `DESIGN.md` and `docs/design-ledgers.md`) rather than hand-edited. 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
|
Both findings verified and fixed in b152146. Review 58126 was right on both counts, and the second one is sharper than it may have looked. Finding 1 — the subject parser's four guards had no rows. Correct. The rows added in the previous commit exercised only Finding 2 — the sha256 comparison was uncovered. Also correct, and it is the more interesting one: the old row accepted a valid Mutation control, on exactly the five you named. Mutating the subject parser's three blank guards, its schema comparison and the sha256 prefix turns five rows red by name: Full evidence, one binary across all three arms (the change is
One incidental receipt worth recording: the sha256 row went red on its first run because my fixture carried a 72-character hex string. The witness discriminated on its own input before it ever discriminated on the change. The shadowed-guard note in the body still stands unchanged — the receipt parser's blank-roster guard does not flip under mutation, because a blank roster line is independently rejected downstream by — sent from still-swift-363 |
…e arm that prompted the hunt The mutation-control half of this row said to mutate the lines you edited before spending the before/after arms. It did not say WHICH lines, and the omission has a receipt from the same PR the row already cites. On #9912 the author found one parser's guards uncovered, fixed them, and shipped the identical gap for the SIBLING parser in the same file — under a comment claiming both were covered. Five of ten rewritten comparisons had no discriminating evidence, and it took a reviewer to find it. A lesson learned at one site does not propagate itself: the mutation control's target list is the diff's own changed lines, and the second site is the least likely place to look precisely because it feels already handled. It belongs in this row rather than a new one because it is a qualifier on a rule already here, not a second class — the row's subject is still a representation change turning every consumer into a seam, and this says how to be sure you have found all of them. `docs/design-ledgers.md` regenerated by the actuator. `DESIGN.md` carries only the identity index, which is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23
#9912 squash-merged, so main carries its content while this branch carries the same commits plus the record-field work. Resolved by taking this branch's side at all six regions and verifying the result is byte-identical to the pre-merge file -- main added nothing to this witness beyond #9912's own head content, checked by diffing main's version against b152146's.
The typed row and its projection said nine of the fourteen Optional-vs-bare comparison sites were merge-admission receipt schema checks. #9912's already- landed consumer census established ten -- four in merge_admission_produce, six in merge_admission_subject -- leaving four elsewhere. The number is not cosmetic: this row's bounded population is what a later cut reads to decide whether the silent-false class is closed, so understating the merge-admission share by one understates exactly the part a cut would check. The clause now carries the 14 = 10 + 4 partition, names #9912 as the census rather than counting here, and records that the earlier draft said nine. Projection regenerated through the artifact gate, not hand-edited. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23
…and the parse_int argument site (#9926) * Merge admission: eliminate the ten `first()` comparisons, and cover the 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 * Cover the five comparisons the first cut left blind: the subject parser'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 * wip carrier-grain * Merge admission: eliminate the five `first()` record-field assignments, and prove the receipt parser is gated on the builtin grounding The comparison migration (#9912) is a no-op under `main` AND STILL WRONG UNDER THE REPAIR. Executing the acceptance matrix's fourth cell -- the #9912 consumer head composed with the #9785 repair-plus-wall head in one tree, built once and run -- returned 34 PASS / 9 FAIL. Neither cause was the comparison class. FIVE OF THOSE FAILURES ARE THIS CHANGE'S SUBJECT. `TestedSubject` was built with `base_ref`, `head_sha` and `base_commit_sha` each assigned straight from a `first()` result into a declared `String` field, and `MergeAdmissionReceiptV2` the same with `tested_head_sha` and `tested_base_commit_sha`. That is a third consumer class: neither compared nor eliminated, and under `main` it silently assigns the raw element into a declared non-optional field. Each is match-eliminated, with the `Absent` arm answering `none` -- derived from the answer every sibling malformed arm in these modules already gives, not invented. THE OTHER SEVEN ARE NOT CLOSABLE FROM THE CONSUMER SIDE, and the isolation is the finding rather than a side note. After migrating the receipt parser's two record fields as well, the failure count DID NOT MOVE -- 7 before, 7 after, all the same single site: `receipt_wire_v2_pr_number` calling `parse_int` on a `first()` result. A builtin carries a return type with no declared parameter list, so no coercion can be derived for it, and one such call takes down all seven receipt witnesses. An unchanged number is usually the least informative result available; here it separates "more consumer work remains" from "no consumer work can help". cell 4, comparisons only 34 PASS, 9 FAIL cell 4, + subject record fields 36 PASS, 7 FAIL subject parser GREEN cell 4, + receipt record fields 36 PASS, 7 FAIL unchanged: consumer side exhausted **The subject parser is closed under the repair. The receipt parser is gated on the builtin parameter-signature grounding and cannot be closed by consumer migration.** That red is the correct state to land with. NO-OP AT CARRIER GRAIN, WHICH IS THE ONLY GRAIN THAT CAN SEE THIS CLASS. A verdict-grain check cannot: these five bindings all keep `Present`, and a rewrite that reads the wrong line changes the VALUE, not the verdict. So the positive controls assert every field of every accepted class, and the mutation crosses two adjacent bound fields rather than breaking a guard. baseline (#9912 head, main semantics) 43 PASS migrated 43 PASS, verdict for verdict identical field-crossing mutation 39 PASS, 4 named FAIL The four are exactly the carrier-grain rows -- both roundtrip witnesses and both new positive controls. Under a verdict-grain suite that mutation is invisible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 * The receipt parser was NOT gated on the builtin grounding: one unmigrated first() was receipt_wire_v2_pr_number passed lines.skip(n: 6).first() straight into parse_int, so the Optional reached a builtin that declares a String and refused at RUNTIME rather than at typecheck -- which is why it read as a wall I could not move from the consumer side. Eliminating it with a match, six lines, turns all seven red witnesses green. Both cells measured, not argued. Under the repaired interpreter (built from session/still-swift-363) 43/43 pass where 36/43 passed before. Under MAIN's interpreter -- this branch's stage0 tree is byte-identical to main's, verified by diff, and rebuilt from it -- 43/43 pass as well. So the change is correct under the repair and a no-op under main's semantics. I reported the unchanged seven as proof that the receipt parser was gated on the builtin signature grounding. That was wrong, and a ruling was retracted on the strength of it. --------- Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Why this exists, and why it must land before the
first()repairTen sites in
gunbc.merge_admission_produceandgunbc.merge_admission_subjectcompare afirst()result to a bare value.dag/std/algebra.dagdeclaresfirstas returningOptional<..>, so once the interpreter constructs what that row declares, every one of these compares across two representations andValue::eqcannot decide them. Measured on the branch that constructs it (#9785):["schema-v2", "body"].first() == "schema-v2"answersfalse.These are the receipt schema and blank-required-field checks on the path every lane merges through. A quiet
falserejects a valid receipt with no diagnostic. They are correct today — onmainthe interpreter'sfirstisitems.front().cloned().unwrap_or(Value::Null), the raw element, so the comparison is String-to-String — and it is the repair that breaks them. That makes this change a precondition for #9785, not housekeeping.What changes
Each site moves from the compared class into the match-eliminated class, the one class that already agrees under both semantics.
The
Absentarm is derived, not decided.parse_receipt_wire_v2declares-> MergeAdmissionReceiptV2?and already answersnonefor every malformed case it handles — wrong line count, wrong schema line, blank required field, unparseable attempt id, conclusion, gate roster hash or PR number.parse_tested_subject_wireandparse_git_object_id_wireare the same shape, andreceipt_wire_v2_pr_numberalready carries the exact target form (match parse_int(...) { ... Absent => WirePrMalformed }). So "the receipt has no first line" is an instance of an answer these modules already give. No new refusal vocabulary is minted.It is a no-op today — the obligation this PR has to meet
It lands on
main, wherefirstreturns the raw element and no wall exists. The interpreter'smatch_patternbinds aPresent { value: v }pattern to a raw value, so each rewritten site binds the same string it compared before and answers the same verdict. TheAbsentarm is unreachable under the length guard each function already applies ahead of these checks.Measured — one binary, three arms. The change is
.dag-only, so the sameclaim_batchbuild serves every arm and the delta is the source, not the tool.main, 36 witnessesSix witnesses are added because the suite could not see these arms
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_wireandcompose_walk_attempt_iddirectly, 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 — 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
wire_v2_refuses_a_blank_head_sha_lineandwire_v2_refuses_a_blank_base_commit_lineand not the blank-roster row, because a blank roster line is independently rejected downstream byparse_gate_roster_hash_wire. That guard is shadowed rather than discriminated, and this says so rather than claiming three for three.Each new row varies exactly one line of a wire that parses, which is the convention the existing rows in that module state, and a positive control asserts the all-correct builder still parses — otherwise every refusal row could be satisfied by a wire malformed for some other reason.
Scope
Ten of the fourteen known comparison sites in the generated-artifact gate's 944-module import closure. Those counts are lower bounds and not populations: the classification's largest non-
matchbucket is 31 sites where a one-line textual reader cannot decide a record-field assignment from a function argument, because the opening brace is on a previous line, so it names its own undecidability rather than guessing. The completion criterion for the wider migration is that the gate's closure LOADS, not that a count is reached — this PR asserts only that the ten named sites are repaired and verified. The other four known comparison sites (extdeps.languages.yaml.ingest,gunbc.os_install_actuator_selection,gunbc.roadmap.roadmap_dispatch_actuator,std.realization_schedule) are a separate change; the three builtin-argument sites need the builtin parameter-signature grounding and stay refusing until it lands.🤖 Generated with Claude Code
https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23