tooling(differential): every measured contest has an owner (#498) - #507
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #507 +/- ##
=======================================
Coverage 98.66% 98.66%
=======================================
Files 45 45
Lines 3219 3219
=======================================
Hits 3176 3176
Misses 43 43 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…winners Every 1.4.0 diff two or more rules admit, where the winner beats a loser by neither narrow-first nesting nor a `precedes_narrower` declaration and no roster row pins it: fourteen names over seven (winner, loser) pairs, measured 2026-09-05 against the 1.4.0 wheel. SEVEN sit in an EQUAL-`fields` pair -- PAIR A's six and 'Bob Jones, compositeur' -- the clearest case of the class the docs say _CROSS_RULE_WINNERS "stays the instrument" for, a class they scope to merely-intersecting `fields` too; four are contract tier. Nothing is owed at 2.0.0, 2.1.0 or 2.2.0. Each name gets a shape in _RECORDED_DIFFS and a winner in _CROSS_RULE_WINNERS with a per-pair argument. The five that were _WATCHED_DIFFS rows at 1.4.0 move with their shapes unchanged and keep their 2.x rows, where each has one admitter -- so the watched roster falls 41 -> 36 at 1.4.0 (107 -> 102 rows) over the same 50 distinct names. Re-derived: _RECORDED_DIFFS at 1.4.0 is 45 rows, 32 of them on radar-tier names and 13 with a single admitter at their recorded shape. Every count-carrying site for those figures is updated with its recompute; dated records of what an earlier tree measured keep their numbers and gain a "since #498" clause. Gate at all four baselines: 352 / 247 / 155 / 14 intentional, 0 unexplained, 0 radar unclassified, exit 0, summary lines byte-identical to master. A pin moves no classification. Refs #498 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ration must be pinned `unowned_contests` walks a run's own `diffing` list, re-asks _entry_matches of every rule, and reports each (winner, loser) pair where the winner beats a loser by neither narrow-first nesting nor a `precedes_narrower` declaration and the name has no _RECORDED_DIFFS row. Equal `fields` and a bare intersection are both reported; narrow-first stays #382's declaration-free default and a declared wide-first pair stays covered by its `why`. A wide-first UNDECLARED pair is classified (kind `wide-undeclared`) rather than assumed unreachable, because the tests hand this rule lists nothing validated. The three kinds are TOKENS the caller renders, the shape _Dormant set: `_UNOWNED_WHY` beside `_DORMANT_WHY` holds the wording. Run-time by necessity: a pin is a shape beside a winner and the shape is what a run measured, so there is no static form of the question -- without a diff the population is the 60-pair static reach and with one it was 14. A _WATCHED_DIFFS row does not justify: it pins no winner, and the `set(winners) == set(shapes)` guard is what makes a _RECORDED_DIFFS key mean one exists. No vacancy half, and the docstring says why a dissolved contest keeps its pin. Pure and unwired: nothing calls it yet, so the gate is byte-identical at all four baselines and the machinery bisects apart from the severity. Refs #498 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
main() calls unowned_contests over its own `diffing`, the sorted rules, the exclusions, the ledger's _RECORDED_DIFFS keys and the tier map, and prints a block grouped by (winner, loser) pair: one lead carrying the repair (pin the name in both rosters with the argument, or narrow the accident away) and the disclaimer (a contest is not a defect, an unowned one is), then one line per name with its order tag, tier, shape and the _UNOWNED_WHY clause its kind renders to. The count is PAIR(S) and not diff(s), the rows being one per (name, losing rule), so a name contesting two losers is two of them. The declared-order limit prints only when a row carries an order, the way NOT CHECKED names its own, and the lead says that the population is the entries this run loaded -- a `--corpus` subset can only under-report. The block sits after the three MOVED SHAPE blocks and above the Role-vocabulary legend, and `unowned` joins the exit expression beside `shape_bad`: fatal on both tiers, because a contest row is fatal on either tier for carrying an argument and an owed row is fatal for carrying none yet. MUTATION CONTROL on the shipped tree, in memory around main() so the worktree is untouched: with 'Smith, Ph. D. Jr.' deleted from _RECORDED_DIFFS['expected_since_1.4.0.toml'], a 1.4.0 run exits 1 with UNPINNED CONTEST expected_since_1.4.0.toml: 1 contested pair(s) ... naming that row at [contract] ['suffix', 'title'] (equal `fields`, file order the whole decision). Unmutated, no block prints at any of the four baselines. Gate at all four: 352 / 247 / 155 / 14 intentional, exit 0, summary lines byte-identical to master. Refs #498 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
decisions.md gains a dated `### differential-ledger, the #498 completeness check (2026-09-05)` entry after the #501 adjudication: the four-baseline population figures with their recipe (47/18/14 at 1.4.0, 3/3/0, 1/1/0, 0/0/0), the equal-fields finding, the scope decision with the 9-more-names / 23-in-all figure and that #501's gap (ii) stays open by choice, the coverage argument that says what the 2026-09-02 decline declined and what this is, the seven verdicts with their measured old and new values, and the mutation control. The 2026-09-02 "nesting is sufficient, not necessary" bullet and the run-time-detection Declined bullet each gain one sentence pointing here, so neither reads as the last word on a class that has since been closed. README's "What counts as a contest" gains the second predicate, its severity, and the no-vacancy-half asymmetry; the #498 paragraph goes from "blind spot, filed" to "closed by", keeping the worked case and the static figures. mechanisms.md#CROSS-RULE-OUTCOME-PINS says the roster's completeness is now enforced rather than stated, and #LEDGER-RULE-SEPARATION's "#498 carrying the worked case" clause says how it was closed. Refs #498 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
derek73
force-pushed
the
claude/498-contest-pins
branch
from
September 6, 2026 05:34
5351e3c to
24fad28
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every measured contest in the differential ledger now has an owner. A new run-time check,
UNPINNED CONTEST, fails the gate where a diff two or more rules admit is won by neither narrow-first nesting nor aprecedes_narrowerdeclaration and no_RECORDED_DIFFSrow pins it; the fourteen names that lacked one are adjudicated and pinned. Closes #498.What was open
#382's
undeclared_contestsrefuses a WIDER rule sitting ahead of a NARROWER one it shares a corpus name with, unless the wider rule declares why. Nesting is sufficient for an order-decided contest and not necessary: two rules whosefieldsmerely intersect, or are equal, both admit any diff inside the intersection,classify()returns the first, and the static check looks past the pair in either arrangement. The docs said_CROSS_RULE_WINNERS"stays the instrument" for that class. Nothing checked that the instrument had been applied.Measured 2026-09-05 by driving
main()at all four baselines withclassify()spied (recipe in the decisions entry):The fourteen come from seven (winner, loser) pairs over fifteen name-pair rows. Seven of the names sit in EQUAL-
fieldspairs — six underfix(#325)against the lone-post-comma rule, andBob Jones, compositeur— the clearest case of the class the docs named the roster for, and none was pinned. Four are contract tier: the issue's three nakaguro names andSmith, Ph. D. Jr..Why run-time, and what the 2026-09-02 decline declined
A pin is a shape beside a winner, and the shape is what a run measured. Without a diff the population is the static reach of every pair the check can report, 71 pairs (60 overlapping, 11 with equal
fields), and the roster nobody writes; with a diff it is 14 names. So the check has no static form. The recorded decline of run-time contest detection was of REPLACING the static check, on coverage; this adds to it: the static check covers the hazard that has a declaration site, this covers the hazard that has none. Speed was never the reason — a whole gate run is a fraction of a second, and the "multi-minute" phrase was withdrawn 2026-09-03.The commits
_RECORDED_DIFFSrow and a_CROSS_RULE_WINNERSwinner with a per-pair argument that says what the pin buys (a reorder moves 7 / 3 / 9 / 15 / 2 / 3 / 8 of the 352 classifications, pair by pair). Five names move from_WATCHED_DIFFS; every 31-row figure in the tree becomes its 45-row successor, re-measured, with the dated records keeping their numbers and gaining a clause.unowned_contests, a pure function behind hermetic tests, with_UNOWNED_WHYrendering itskindtokens the way_DORMANT_WHYdoes. Mutation-tested: dropping theordernarrowing, thesorted, anycontinue, or returning early each fails exactly one named test.UNPINNED CONTESTjoins the exit code, fatal on both tiers, rows grouped by pair, after theMOVED SHAPEblocks;main()-level tests including that a_WATCHED_DIFFSrow does not silence it and a radar name fails as a contract one does. Mutation control on the real ledger: deletingSmith, Ph. D. Jr.'s row in memory exits 1 naming the name, its winner and its loser; unmutated, no block prints at any baseline.fieldsoverlap without nesting? (マイケル・ジャクソンgoes to whichever of two rules comes first, and the contest check never sees the pair) #498's class; the wider narrow-first one costs 9 more names, 23 in all, and is declined with Six contested diffs in the 2.x ledgers have no pin, and the empty rosters read as if none existed (MD, PHDgoes to whichever rule comes first) #501's gap (ii) left open by choice), and the coverage argument; the two 2026-09-02 bullets each gain a sentence pointing here; README's "What counts as a contest" carries the second predicate; mechanisms.md's cross-rule pins section says the roster's completeness is now checked rather than stated.Scope decision
The predicate stops where the winner beats a loser with equal or non-nested
fields. A pin for every undeclared contest, narrow-first ones included, would also have forced the argument #501's regex accident lacked, at the cost of pins the declaration-free default already justifies. Recorded as declined, with the figure.Verification
Intentional counts 352 / 247 / 155 / 14 byte-identical at every commit (a pin moves no classification; a check that fires on nothing prints nothing), 0 unexplained, 0 radar-unclassified, no finding block at any baseline. Full suite green; ruff and mypy clean. Every commit went through a spec-compliance review and a quality review; the review rounds found and fixed one stale present-tense roster total, two mutation survivors, a fail-open
.get, and four comment sentences a measurement contradicted. A final five-reviewer pass over the whole branch then closed two more: the pin gate now applies only to default-order comparisons, since a recorded shape is a default-order shape and cannot own a declared-order contest, and the function sorts its rules internally asdormant_rulesdoes.🤖 Generated with Claude Code