Repository navigation
Two cheap witnesses: the coercion two-algebra fork is a shared-no-key fork, and the bindings selection fold is a negative - #12055
Conversation
…agree column Both joins were entailed by the per-side claims: each pins its side to the same independently specified expectation, so the comparison can never be the only thing red. DESIGN 4b rules that out before the check is written, and the derived agree column is DESIGN 2 re-invention. My own mutation table was the evidence -- the join never redded alone in any of the four arms. The agreement relation is derived from the expectation columns a row carries side by side, and is enforced by the per-side claims. Re-executed: 6 green, each mutation now reds only the claim owning the mutated side.
|
Both findings in review 70065 are correct. Verified and fixed in ed7dad2 — I deleted both comparison claims and the derived column rather than arguing. The entailment is real, and my own mutation table was the evidence I failed to read. In all four arms the join redded together with a sibling and never alone. I presented that in the PR body as a strength ("reds the join AND leaves the other algebra green") when it was a demonstration that the join carried no information of its own.
What is worth recording, because it is a consequence rather than an oversight. The brief asked for two things: an independently specified expected verdict per side (so agreement between two implementations is not mistaken for correctness), and a join that reds on divergence. Those two requirements cannot both carry information. Once both sides are pinned to independent expectations, the agreement relation is already fixed — it is derived from the two expectation columns rather than asserted by a third claim. The first requirement subsumes the second. Both files now carry that reasoning in an annotation above the claims, so the obvious edit (adding the join back) is answered where the next author will make it. Nothing measured was lost. The fork is still stated where a reader meets the population — four of the seven coercion rows carry expectations that differ, in both directions — and a move in either algebra's admission set, consolidation included, reds the claim owning that side and forces the author to restate the row. That was the whole property the join was wanted for. Re-executed after the change (
Cleaner discrimination than before: each arm now reds exactly the claim that owns the mutated side and nothing else. — sent from gentle-dove-188 |
…wing subject Deleting the final test fn left the explanatory note at end-of-file with no module item after it, which DESIGN 4c refuses -- 15 'source annotation names no subject' errors, one per line, redding the parse phase and the floor with it. Moved above the claims, which is where both file headers already say it sits. Re-executed: 6 green.
THE LEDGER ROW IS NOW ONE ROW, WHICH IS WHAT THE CLASS DESERVES. When I filed identity_in_the_comparator_spelling_in_the_renderer, gentle-dove-188's two_admission_authorities_keyed_on_different_relations was still unlanded, so mine named it as a possible duplicate and said it should be merged in if that row landed. It landed in #12055 -- and its SPECIMEN 2 is already this lane's finding, attributed to this session. So mine was a duplicate by their authorship, not merely a suspected one. Deleted, and their row gains what it predates: - what the fork was about to cost: the spelling-keyed side was about to become load-bearing for a 69-row admission roster, where the failure is not a duplicate row but an ALREADY-ADMITTED site evaporating off its key on an unrelated import edit, taking the refusing arm and firing the ratchet over a population change nobody made; - the repair, which is that row's own NEXT TRIGGER applied at one site: resource_identity_label keys on the resolved declaration; - the control: population UNCHANGED across the canonicalisation, 69 sites and 76 occurrences either side, Network 37 replacing 28 + 9 -- keys collapsed, no site lost or invented; - the transferable half: it was found by MEASURING THE POPULATION, not by reading the key, which looked identity-grained to its author and would have to a reviewer. Where no fixture exists yet, the census that enumerates a population is what exposes the second key. v1_compiler_emit_rust.rs came back UU again and was resolved by REGENERATION, not by hand. The .dag authority auto-merged with no markers and carries both sides. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two witnesses over supplied fixture populations, joining two pairs of folds that nothing in the corpus joined before. Measurement only — nothing is repaired, by ruling (snappy-deer-443, 2026-09-22): the coercion repair is a replacement migration with its own blast radius and is dispatched as its own lane, which should start from these executed values rather than from the derivation that found them.
Both witnesses follow the DESIGN §3 standing rule: inputs are supplied at the one interface under test, never computed by executing the layers beneath it. And neither fold is the oracle for the other — every row carries an independently specified expected verdict derived from the modeled contract, so a row can report both agree and both are wrong. A witness that only reports "the two folds match" is a change detector.
Witness 1 — the coercion two-algebra fork
std.coerciondag_can_castfolds overdag_cast_rules+grounded_primitive_coproduct_identities, keyed on type-name strings, and it executes (v1.compiler.inferconsults it at argument-versus-formal admission, at literal admission, and in the type-relation fold).v2.std.coercioncoercion_foldasksfind_witnessfor a structure-preserving witness, keyed on node identity, and has no established executing consumer.The two sides share no key. No conversion between a type-name spelling and a node identity exists anywhere in the corpus, so every fixture row must author the pair twice —
source_type/target_typebesidesource_name/target_name. That duplication is not fixture awkwardness; it is the measurement, and it is available before any execution. This makes the finding a keys/hashing conformance subject (DESIGN §3b names structural node identity as a home there) rather than a coercion subject.Executed, 7 pairs, all four claims green. The admission sets differ in both directions, so neither side is merely the stricter one:
Int→Int,Float→FloatBool→FloatInt→Float,Bool→Int,Int→NatString→Stringcoercion_fold's only realized preservation rule ispreservation_rule_exact_structural_equality_zip_fold, whose predicate holds exactly when the candidate is structurally equal to the source — so the homomorphism side cannot spell a non-identity crossing at all. The sharpest row is the last:String→Stringis an identity crossing thatdag_cast_rulescarries no row for, i.e. the executing authority refuses the one case a reader assumes is free.Both sides matched their own independently specified contract on all 7 pairs. This is a fork between two authorities each correct by its own lights, not a defect in either implementation.
There is deliberately no claim comparing the two algebras to each other (review 70065). With both sides pinned to independent expectations, such a comparison is entailed — its red is not independently authorable, which DESIGN §4b rules out before the check is written. This is a consequence worth naming: the two things asked of these witnesses — an independently specified verdict per side, and a join that reds on divergence — cannot both carry information, because the first already fixes the agreement relation. The relation is read off the two expectation columns a row carries side by side, and enforced by the per-side claims: a move in either admission set, consolidation included, reds the claim owning it.
The population inhabits all four cells of (name-keyed admits × homomorphism admits) deliberately; a happy-path population would only ever reach the first.
Witness 2 — the bindings selection fold (a negative, and the shape of the negative matters)
std.occurrence_bindingoccurrence_binding_from_candidates(a FoldZero/FoldOne/FoldMany accumulator) andv2.std.symbol_indexsymbol_index_lexical_selection(match length(candidates)) are two implementations of one none/one/many trichotomy. Over five supplied populations — empty, singleton, two distinct, duplicate, three — they agree on all five, and each independently matches the trichotomy as the contract states it.As in witness 1, there is no third claim comparing the two folds — both are pinned to the same expected class, so it would be entailed.
This is not agreement by construction. Agreement by construction would mean both routes read one reader, making disagreement unwritable. Here nothing makes them agree: both contracts are silent on duplicates and both realizations inherit that silence. Neither declares a deduplication step, so a population naming one candidate twice is a two-candidate population in both — by omission, not by rule. The first side to declare a dedup rule silently forks the other. What would close it: a declared statement, on whichever carrier owns the trichotomy, of whether the rule ranges over list multiplicity or over the cardinality of distinct candidates. The duplicate row stays enrolled although green on both sides — it is the probe that reds when that silence is broken on one side only.
Scope, stated because it is the whole point. The subject is the selection fold on a supplied population. Agreement on a production site is not measured and is not measurable today: no v2 resolution site is answered by std's fold, and
std.occurrence_binding's own staged-adoption note states thatLexicalLookupis not an interim consumer because it carries no exact reference-occurrenceNodeor occurrence containment identity. The candidate producers (a supplied list vs. the containment chain walk) are legitimately layered and are not compared.symbol_index_lexical_selectionis split out ofsymbol_index_lexical_lookup— same composition, no behaviour change — so the boundary can be asked with its inputs supplied. This is the one substrate edit in the diff and it carries the higher bar: the split is the same composition, and the real chain walk still executes through the inhabitance claim below, so the production route did not stop running to make the fixture convenient. That is the condition that makes a supplied-boundary split legitimate rather than a quiet deletion of the real path.The pairing obligation, and the defect it caught
Splitting the selection out owes DESIGN §3's pairing obligation: one inhabitance claim that the real producer emits that population, with the real path still executing.
the_real_chain_walk_emits_the_population_the_fixtures_supply_holdsbuilds a realSymbolIndex, runs the realsymbol_index_lexical_collectchain walk, and feeds that population — not a fixture — into the same selection fold. It asserts the route, not only the answer, so deleting the walk cannot leave the suite green.It failed on first run, and the failure was real. My first fixture module emitted a one-candidate population where the real walk emits two — a supplied-input claim quietly testing a world the producer does not inhabit, which is exactly the failure the obligation exists to catch. Fixed by giving the module the ordinary shadowing shape (a module-level
Shadowedtype beside aClassical.Shadowedvariant).Mutations — discrimination, not change detection
Each arm reds exactly the claim that owns the mutated side, and nothing else. A mutation that reddens everything proves only that the suite is wired.
Bool→Floattodag_cast_rulesexact_structural_equality_zip_fold_preservation_holdsso it stops comparing source to candidateFoldOneAmbiguouson the empty populationAll four applied to a clean tree and reverted; the working tree is unchanged by them.
Recorded in existing carriers
gunbc.recurring_failure_modetwo_admission_authorities_keyed_on_different_relations— invalid state: two authorities answer one admission question keyed on different identity relations with no conversion between the keys. Harm: the ordinary §3 fork is at least comparable; this one cannot be compared without an authored bridge, so no join exists to go red and the fork does not present as a fork. Two independent same-day specimens (this one, and a resource appearing underNetworkandstd.resources.Networkwhere a diagnostic field carried the caller's import spelling while the wall compared resolved declaration identity). Ceiling: structurally impossible via replacement migration; trigger: the route belongs in the identity key.gunbc.plans.dag_v2_defork_audit§2B/§2C — corrects the audit that commissioned this lane. It classifiedcoerciona genuine not-a-fork on a shared-symbol count of 0; that count answers the wrong question, because two sides cannot share a symbol when they cannot share a key. §2C files witness 2 as no-evidence-with-a-named-absence rather than agrees-by-construction.No new authoritative document.
docs/plans/dag-v2-defork-audit.mdis the regenerated projection (generated_artifact_gatemain_wet);docs/design-failure-modes.mdis untracked, so the new failure-mode row needs no committed artifact.Execution
All 6 claims green; each of the 4 mutation arms red as tabled. Run locally via
claim_batch --source-root dag --source-root src/v2 --entry <witness> --functions <fns>(two remote dispatches were lost first — one truncated by atailon the pipeline, one to a runner that died mid-resolve).A green local run is not evidence the file parses. Deleting the last
test fnhere (the review-70065 fix) orphaned the explanatory note at end-of-file with no module item after it, which DESIGN §4c refuses — 15source annotation names no subjecterrors, one per line, redding the parse phase and the floor with it.claim_batch --entry <file>passed on the broken file, because an entry resolve does not run the corpus parse phase that the floor does. If you delete the final declaration in a.dag, check the file does not now end in a//block; the local witness run will not tell you.On
required-regen: it is red on this tree forsrc/v1/stage0/src/std_measure.rs, which is pre-existing on main and not from this PR — see #12027. This diff touches no file underdag/std/orsrc/v1/stage0/, so it cannot produce a stage0 mirror drift; the mutation arms above were applied to a clean tree and reverted.🤖 Generated with Claude Code