tooling(differential): the six 2.x contests get their winners, and a regex accident is narrowed away - #506
Merged
Merged
Conversation
…nger honorific
Both ledgers ship the same glued-peel `name_regex`, and both matched a
listed honorific sitting INSIDE a longer listed one. 박사님, 선생님 and
교수님 are the only entries of that alternation containing another
entry of it, so the interior 님 was the one way a SPACED honorific
could reach a rule whose title says a glued one was peeled off the
name. It reached two corpus names, and on one of them the label was
simply false: '김민준 박사님' has nothing peeled off it at all --
rules.md#W2's second sentence says a part that is not name text is
never the name's end, and its witness line for this very string is
suffix="박사님", the honorific whole and standing as its own word. That
is a FALSE label on a contract-tier name, held green by every guard in
the suite. The other, '선생님', moves no `suffix` whatsoever: its diff
is the order flip's {given, family} and the CJK order rule claims it
either way.
`(?<!박사)(?<!선생)(?<!교수)` goes in immediately after the existing
`(?<=[^\s,])` in both ledgers. It refuses the interior position only:
a glued '김민준박사님' still matches on 박사님 itself.
MEASURED CONSEQUENCE, 2026-09-05. Exactly ONE name changes hands per
ledger and it is '김민준 박사님' -- to the spaced rule
fix(#307/#308/#320) at 2.0.0, whose title is what happens to it, and to
fix(cjk-honorific-suffix) at 1.4.0. Diffed as (name, order, rule)
triples by wrapping classify() and driving main() at all four
baselines, before and after: 1 change at 1.4.0, 1 at 2.0.0, 0 at 2.1.0,
0 at 2.2.0, and no name entered or left the compared population.
Summary lines byte-identical at every baseline -- 352 / 247 / 155 / 14
intentional, 0 unexplained, 0 radar unclassified, exit 0 -- with no
MOVED SHAPE and no finding block. order_contests is unchanged: 11
wide-first nested contests at 1.4.0 over the same names, the
compound/peel row still 17, and 0 at each 2.x ledger. No 1.4.0 pin
moves. _HONORIFIC_SOURCES is untouched, the alternation body being
untouched, and its guard passes.
RE-RECORDED, the roster's own way (_claim over the shipped rule, never
a hand-edited digest): both _CORPUS_CLAIMS rows go
_Claim(37, ('family', 'given', 'suffix'), "719c31233502", None) ->
_Claim(35, ('family', 'given', 'suffix'), "9a1b4c202a65", None), the
two names lost from reach being the two above.
Ledger prose that the change made stale, re-measured rather than
adjusted: the 1.4 peel rule explains 16 corpus names, not 17; twelve of
those are contract tier, where three `precedes_narrower` blocks said
fourteen -- one of which the period-class demotion had already taken
and one the narrowing takes. The claim that deleting the rule leaves
TWELVE contract names unexplained still holds, before and after, and
says so now.
A hermetic test pins the property rather than the pattern: over both
ledgers read off disk, `_entry_matches` is False for '김민준 박사님' and
'선생님' and True for the glued '김민준씨', so a narrowing that
overshoots fails as loudly as the accident did not.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The two 2.x sections of _CROSS_RULE_WINNERS and of _RECORDED_DIFFS stood empty on a position: contests were MEASURED there -- 5 of the 247 diffs at 2.0.0 and 1 of the 155 at 2.1.0 move a shape two or more rules admit -- and the rule was that a row is owed when somebody ARGUES a boundary, not when one is merely measured. #501 argued all six (2026-09-05). The five rows at 2.0.0 and the one at 2.1.0 move from compare._WATCHED_DIFFS to _RECORDED_DIFFS with their shapes unchanged, which is what that dict's NO WINNER clause says happens the day a winner is argued, and the six winners go in beside them. WHAT THE POSITION GOT WRONG was WHERE an argument counts. Three of the six boundaries were already argued -- in the 2.0.0 ledger's own comment on fix(#308/#312/#319/#320), which named '田中さん 様.', '田中さん, 様.' and '김민준 박사님' one by one and said for each why the glued rule should win. One of those three arguments was FALSE, and it stood for a month with every guard green because no row here made classify() answer for it. That is the case for the criterion being ARGUED SOMEWHERE rather than argued in this file: a ledger comment's claim about which rule wins a name is checked by nothing, and this roster is the only thing in the tree that can check it. THE SIX, with the argument each rests on: '田中さん 様.' and '田中さん, 様.' -> fix(#308/#312/#319/#320). The only live two-runner contest of the six. A compound of two mechanisms with one rule per half and fields that do not nest, so no precedes_narrower block can name the pair and file order is the whole decision. #312/#319/#320 are the glued rule's clauses and they name both strings; no clause of the spaced rule mentions either. '김민준 박사님' -> fix(#307/#308/#320), the SPACED rule, which only wins after the narrowing in the commit before this one. '선생님' -> fix(#271/#272/#298). The whole diff is the order flip's {given, family}; the glued rule's claim declared a `suffix` that moves nowhere. The pair is the #498 class -- intersecting `fields`, neither nested -- so undeclared_contests looks past it. 'MD, PHD' at 2.0.0 and at 2.1.0 -> fix(#296) a credential-only comma string reads a name and its postnominal. Both #296 rules declare EQUAL `fields` here, so only file order decides; the distinguishing clause is a property of the STRING, which holds no name. MUTATION CONTROLS, in memory around the guard, per row so that a failure on one cannot mask the rest: (i) glued <-> spaced at 2.0.0 -- fails on both '様.' names: "expected_since_2.0.0.toml: '田中さん 様.' diffing ['family', 'given', 'suffix'] is now explained by 'fix(#307/#308/#320) spaced CJK postnominal honorific routed to suffix', not 'fix(#308/#312/#319/#320) glued CJK honorific peeled off the name into suffix'." (ii) the two fix(#296) credential rules swapped -- fails at BOTH baselines: "expected_since_2.0.0.toml: 'MD, PHD' diffing ['suffix', 'title'] is now explained by 'fix(#296) a lone post-comma credential is a suffix', not 'fix(#296) a credential-only comma string reads a name and its postnominal'." and the same sentence under expected_since_2.1.0.toml. (iii) the pre-narrowing glued regex restored AND the CJK order rule swapped with it -- fails on both remaining rows: "expected_since_2.0.0.toml: '김민준 박사님' ... is now explained by 'fix(#308/#312/#319/#320) ...', not 'fix(#307/#308/#320) ...'." and "expected_since_2.0.0.toml: '선생님' diffing ['family', 'given'] is now explained by 'fix(#308/#312/#319/#320) glued CJK honorific peeled off the name into suffix', not 'fix(#271/#272/#298) native-script CJK: ...'." Every one of the six rows is moved by some control; no pin is inert. The regex restore is part of (iii) because the reorder ALONE is now inert -- 0 of the 247 classifications move -- which the narrowing did and which the '선생님' row's comment records: after it, that row's work is against a re-widening rather than against a reorder. The widening alone suffices for '김민준 박사님', the glued rule already sitting ahead of the spaced one. Each control leaves `unexplained` at 0, so the gate's summary line reports none of them. COUNTS, recomputed with the script in the dict's own header rather than adjusted: _WATCHED_DIFFS goes 41 / 32 + 4 / 31 / 5 = 113 rows to 41 / 31 / 30 / 5 = 107, over 50 names where it was 51 plus the four contests -- and the roster is now exactly the sole-watched population the sweep draws, the rows beyond it having left. 48 of the 50 sit in corpus_issues.jsonl and 3 in corpus.jsonl, overlapping on 'dr Vincent van Gogh dr'. The tests/-only scan still says 52 names; the two it counts that this dict does not are 'Carod i' and, since this commit, 'MD, PHD', both carrying a contest row at every baseline they diff at. THREE of the six pinned names are RADAR tier ('田中さん 様.', '田中さん, 様.', 'MD, PHD'), so a moved shape on them is now fatal where a watched row would have printed. Accepted as the SEVERITY rule written: a contest row is fatal on either tier because it carries an argument. 22 of the 31 rows at 1.4.0 are radar names on those same terms. Gate x4 byte-identical to the previous commit's, line for line, apart from the baseline worker's temp path: 352 / 247 / 155 / 14 intentional, 0 unexplained, 0 radar unclassified, exit 0, no MOVED SHAPE and no NOT CHECKED. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A dated entry in decisions.md for the ninth ledger arc, the amendments the two commits before it made owed to the arc above, and one sentence each in the two mechanisms entries the arc moves. THE ENTRY records the six verdicts one at a time -- the admitting rules with their file positions, `fields` and pair class, the measured old -> new field values at the pinned wheel, the ledger or rules.md sentence each verdict rests on quoted, and the in-memory swap the pin now catches -- then the narrowing and its per-ledger consequence, four findings, three declines and the recompute for all of it. MEASURED THIS SESSION at ddd8b2b, from one instrumented run of main() at all four baselines with classify() and _run_worker spied. 352 / 247 / 155 / 14 intentional, 0 unexplained, 0 radar unclassified, exit 0, no MOVED SHAPE and no NOT CHECKED. The controls reproduce ddd8b2b's captures byte for byte. THE SHARPEST FINDING is that the narrowing dissolved two of the six contests. At 2.0.0 only three of the 247 classifications now have more than one admitting rule; '김민준 박사님' and '선생님' are one-runner rows, of the kind 13 of the 31 at 1.4.0 are, and they stay because the test is mechanisms.md#RECORDED-ROSTERS' -- would the consumer answer differently if the row were wrong. Measured: '김민준 박사님' routes to fix(#271/#272/#298) under other shapes as 11 of those 13 do, and '선생님' routes nowhere else, its whole work being against a re-widening. The plan's control (iii) as written was therefore INERT -- the reorder alone moves 0 of the 247 -- and the honest control restores the wide regex AND swaps. TWO CORRECTIONS to figures this branch made stale, both as dated clauses rather than rewrites: 'MD, PHD' at 2.0.0 was itself a _WATCHED_DIFFS row, so FIVE rows left that section and not four and six in all; and the pair class of the '様.' contest is strictly NESTED and narrow-first ({family, given, suffix} inside {family, given, middle, suffix}), not non-nesting -- what survives is the conclusion, that no precedes_narrower block is owed, and order_contests reports the pair only in the arrangement nobody ships (measured both ways). THE WATCHED-SHAPES ARC gets seven dated amendments, in the form line 811 already uses: 41 / 36 / 31 / 5 = 113 over 51 names became 41 / 31 / 30 / 5 = 107 over 50, the roster now being exactly the sole-watched population; the exposure bullet's figures re-derived over the 107 as 363 / 66 gross, 310 / 58 net, 35 other-rule, with both of the figures its argument rests on reproducing unchanged; the per-file split 48 + 3 over 50; and its `Declined:` bullet on pinning the six marked REVERSED the same day, its text untouched. ONE FIGURE IS LEFT UNRECONCILED and says so: 22 of the 31 rows at 1.4.0 sit on radar names today against the 21 measured 2026-09-03. No corpus file and no _CORPUS_TIERS value differs between the two trees and the section held the same 31 rows on both days, so it is a recipe difference; the entry gives the recompute and enumerates the nine contract rows so the next reader can settle it by hand. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Prose only; no rule, roster row, regex or assertion moves, and every figure below was measured on this tree this session. FIVE CLAIMS. (1) _sorted_rules' docstring said three names match both honorific rules; two do since the narrowing (measured over the corpus*.jsonl union: '田中さん 様.' and '田中さん, 様.'; three at master, e004ccf, where '김민준 박사님' was the third). It is the twin of the ledger sentence a6a82d6 already fixed. (2) The '様.' row's comment called the pair one whose fields do not nest. They nest strictly -- the glued rule's {given, family, suffix} inside the spaced rule's {given, middle, family, suffix} -- and the glued rule is written FIRST, so the pair is nested NARROW-FIRST and no `precedes_narrower` is owed because the narrower rule already wins. Measured over the corpus union, order_contests reports nothing for this ledger as it stands and reports this pair, both names, only with the two rules swapped; undeclared_contests likewise. What is unusual about the pair survives the correction and is now stated as itself: narrow-first's JUSTIFICATION does not apply, both mechanisms firing at once with each rule describing one half, so the row pins a default that is correct without being justified. (3) '5 of the 247 at 2.0.0 and 1 of the 155 at 2.1.0' is the pre-narrowing figure and now says so; the same recipe here gives 3 and 1, the narrowing having dissolved two of the six contests, which the paragraph below it already said. (4) 'THREE of the six are RADAR-tier names' counted names and said rows: four of the six ROWS are radar, three distinct NAMES, 'MD, PHD' carrying a row at each 2.x baseline. (5) 'the commit before this one' is now the 2026-09-05 narrowing of the glued-peel `name_regex` named outright. THE 21/22 COUNT is a tier move, not a recipe difference, and the row is '김민준 씨.': eb2d0d2, the period-class demotion of PR #505, moved it from corpus_cjk.jsonl (contract) to corpus_cjk_tolerated.jsonl (radar), so it counted contract on 2026-09-03 and counts radar now. The 31 rows are byte-identical between the two trees. The #501 entry's "no corpus file changed" check ran `git show` at `88b025b^` = d0faed4, three commits AFTER the move -- the one thing the window could not see. Both candidate recipes were run on both trees and at baselines 1.4.0 and 2.0.0: first-corpus-file-with-contract-first, and main()'s own tier_of, give 21/10 at 17a9e66 and 22/9 here, name for name. They can differ in principle -- a string in files of both tiers whose contract copy is order-bearing loses that copy to the baseline-minimum skip -- and no row of this section is in that position today. The "NOT reconciled" sentence goes; README.md's tier paragraph, which carries the live recompute, says what the 21 became and why. VERIFIED: pytest tests/v2/ -k "doc or rules or decisions or mechanisms or guard or citation" 704 passed 1 skipped; test_ledger_guards.py + test_differential.py 298 passed; ruff clean; mypy clean over 111 files; gate exit 0 at 2.0.0 (247 intentional, 0 unexplained, 0 radar unclassified) and at 1.4.0 (352, 0, 0), no MOVED SHAPE, no NOT CHECKED. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
All five sit in docs/design/decisions.md and were made false, or written wrong, by this branch's own commits. Every figure below measured 2026-09-05 on this tree. (1) The #501 entry's lead counted NAMES where the roster counts ROWS. _RECORDED_DIFFS' 2.x sections hold SIX rows over FIVE distinct names: radar is 4 rows / 3 names ('田中さん 様.' and '田中さん, 様.' in corpus_cjk_tolerated.jsonl, 'MD, PHD' in corpus_issues.jsonl with a row at each 2.x baseline), contract 2 rows / 2 names ('김민준 박사님', '선생님', corpus_cjk.jsonl). "THREE of the six pinned names are radar tier" was neither reading. Now says both, as compare.py's own 2.x comment does. (2) The recorded-shapes arc (2026-09-03, #497) still described the two 2.x sections as empty and #501 as open. _CROSS_RULE_WINNERS reads 31 / 5 / 1 / 0 on this tree and #501 closes with this PR. A dated superseding parenthetical goes in beside the original sentences, in the style that arc already uses one bullet earlier, and the header's pointer loses "empty". The criterion those sentences state is what FILLED the sections and is not superseded: a row is owed when someone argues a boundary, and three of the six were found already argued, in the 2.0.0 ledger's own comment. (3) The watched-shapes arc said three of the four #501 rows sit in contract corpora. TWO do: '김민준 박사님' and '선생님' are in corpus_cjk.jsonl and corpus_rules.jsonl, the two '様.' strings in corpus_cjk_tolerated.jsonl only -- the same four files at HEAD and at master (e004ccf), so this is not a move the branch made. The same sentence pointed at a line in _WATCHED_DIFFS' header ("a row consults the case row before this roster") that this branch DELETED, e004ccf's compare.py:1805 having no counterpart here; past-tensed, with why the header no longer needs it. (4) Two relative pointers replaced by what they point at: "the narrowing in the commit before it" -> the 2026-09-05 narrowing of the glued-peel name_regex in both ledgers (a6a82d6); "each of the branch's two commits" -> every commit of the branch, the branch having four, with the review round's own two-baseline record named rather than implied. (5) The declined fatal-on-radar bullet's 51 now carries its tree, as its siblings in the same arc already do: the 51 as measured before the #501 adjudication, 50 after. The arc's three counts (50 / 51 / 52) each say which tree they are on. MEASURED with the scratch scripts, against compare.py and test_ledger_guards.py loaded from this worktree: row and name counts per ledger section, the tier of each pinned name by the first corpus file holding it with contract files sorted first, and the corpus membership of the four #501 names on both trees by git show. VERIFIED: pytest tests/v2/ -k "doc or rules or decisions or mechanisms or guard or citation" 704 passed 1 skipped; ruff clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #506 +/- ##
=======================================
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:
|
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.
The six contested diffs in the 2.x ledgers get pinned winners, each with its argument, and the regex accident that put a false label on a contract-tier name is narrowed away in both ledgers that shipped it. Closes #501.
The adjudication
#501 asked whether contests that are measured but unargued should be pinned. They were argued — per name, against each admitting rule's prose and the measured old/new field values at the pinned wheel (the #372 test: does the winning rule DESCRIBE the change?) — and three of the six turned out to be argued already, in
expected_since_2.0.0.toml's own comment naming the names. One of those three arguments was wrong.田中さん 様.,田中さん, 様.fix(#308/#312/#319/#320)田中さんshrinking to田中김민준 박사님fix(#307/#308/#320)— after narrowing님inside박사님; measured, nothing is peeled (rules.md#W2's second sentence, its witness); its own comment conceded the accident선생님fix(#271/#272/#298)님accident withsuffixmoving nowhere; fields intersect without nesting (#498's class)MD, PHDfix(#296) a credential-only comma string…fields; "the string is nothing but credentials" is a property of the string, not the shapeThe narrowing
(?<!박사)(?<!선생)(?<!교수)on the glued-peel rule'sname_regexin both the 2.0.0 and 1.4.0 ledgers (byte-identical patterns; the three are the only listed honorifics containing another). Measured: exactly one name changes hands per ledger (김민준 박사님), summary lines byte-identical to master, no 1.4.0 pin moves,order_contestsunchanged, two_CORPUS_CLAIMSreaches 37 → 35 losing exactly김민준 박사님and선생님. Its sharpest consequence: two of the six contests dissolved —김민준 박사님and선생님are now one-runner rows, pinned the way 13 of the 1.4.0 rows are — so the plan's swap control for선생님was inert as written; the honest control (wide regex restored and the swap) fails on both names.What the records now say
_CROSS_RULE_WINNERS's 2.x sections stop being "a stated position": the criterion is argued anywhere, not argued in the guard module — a ledger comment's argument is exactly what no guard can see, and the roster is the only thing that can check it (mechanisms.md#CROSS-RULE-OUTCOME-PINS)._WATCHED_DIFFSdrops to 41 / 31 / 30 / 5 = 107 rows over 50 names, now exactly the sole-watched population (MD, PHDat 2.0.0 was a watched row too, so five moved). A dateddecisions.mdentry carries the six verdicts, the narrowing's measurements, the three controls, and three doctrine gaps the adjudication exposed: a two-mechanism compound with one rule per half has no "which half is the change" test; a regex accident in a narrow-first or non-nested pair has no declaration site (precedes_narroweris wide-first-only — the shape that put a false label on a contract name and stayed green; closed here by narrowing the accident away); and "argued in this file" was the wrong locus.Verification
Intentional counts 352 / 247 / 155 / 14 byte-identical at every commit, 0 unexplained, 0 radar-unclassified, no
MOVED SHAPE/NOT CHECKED. Three in-memory controls, each failing the winner guard on exactly the expected names. Every figure re-derived by an independent reviewer (regex completeness, per-ledger classification diff against master's exported ledgers, regenerated claim digests, all ledger-comment counts). Full suite green; ruff and mypy clean.🤖 Generated with Claude Code