Repository navigation
Ledger rules classify diffs they do not describe, and a rule that stops explaining anything is invisible #372
Description
Activity
- added 7 commits that reference this issue
on Aug 12, 2026 Closing — all three confirmed instances resolved, both proposals declined on measurement.
Instance 1,
fix(suffix-routing)— #376. It claimed 25 names in four shapes while its prose described four. Split intofix(cjk-glued-honorific-peel)(17), a widenedfix(cjk-honorific-suffix)(11), andfix(comma-precomma-family)(3, which move no suffix at all). Now claims exactly the 4 its original prose names.It was never too broad. It is the only
fields-only rule in any ledger, so it sorts after everyname_regexrule and takes whatever nothing narrower named. When it grows, the question is which rule is missing.Instance 2,
fix(comma-family)— #375. Bare,reached every comma in every script; 7 of its 8 names were CJK. Latin-anchored at U+0250; now claimsAndrews, M.D.alone. The 7 went tofix(cjk-comma-honorific-peel), added because the first fix merely relocated them onto a rule that also didn't describe them.Instance 3, the blanket CJK rule — not live, and not quite as filed.
fix(#271/#272/#298)carriesfields = ["given", "middle", "family"], and every rule it was said to shadow includes a field outside that set (maiden,nickname,suffix,title). Fields separate them, not order, so it cannot claim their characteristic diffs. Verified: all seven CJK-specific rules fire at 1.4.0 and all four at 2.0.0. If one ever were shadowed, #373 now reports it asEXPLAINED NOTHING ... shadowed by <issue>.The structural gap — #373. A rule explaining nothing now fails the run, in both directions, with three diagnoses. #374 made the diagnosis a
kindrather than prose. The--strictsuggestion is implemented as always-on rather than behind a flag: a check nobody passes a flag to is a check that doesn't exist.Report ambiguity — declined. Measured: 28% of claimed (name × role) pairs already have two or more matching rules, 432 of them
fix(comma-family)underfix(suffix-routing)alone. A report firing on 28% of what it inspects is wallpaper. The actionable slice ships instead as theshadowed by <issue>diagnosis, which speaks only when a rule is fully shadowed.Specificity floor — declined. There is exactly one
fields-only rule in any ledger, naming 3 of 7 roles. A six-of-seven floor matches nothing, and nothing would reveal it was vacuous.Also added:
_CROSS_RULE_WINNERSpins which rule wins for contested names. Measured — a pure file reorder, no regex or fields change, fails that pin and nothing else in 3218 tests.- added a commit that references this issue
on Aug 22, 2026 - added a commit that references this issue
on Aug 29, 2026 - added 6 commits that reference this issue
on Aug 29, 2026 - added a commit that references this issue
on Oct 8, 2026
tools/differential/compare.pyclassifies a diff by the first ledger rule that matches, wherefieldsmatches by subset (compare.py:469) andname_regexrules sort ahead of fields-only ones (compare.py:114). Nothing checks that the rule which claims a diff actually describes it, so a broad rule silently explains changes it says nothing about — and the run exits 0.Found while reviewing #370, which hit it twice: once as a discovery, once by reproducing it.
Confirmed instances
1.
fix(suffix-routing)claims the whole corpus.expected_since_1.4.0.toml:166— the only fields-only rule in any ledger,fields = ["given","family","suffix"], no regex, so it matches all 751 names. Tallying classifier-of-record over every (name × field) pair, it owns 1639 of 5257 — 31% of the space. #370's diff landed on it before a rule was written:2.
fix(comma-family)matches on a bare comma.expected_since_1.4.0.toml:118—name_regex = ",",fields = ["given","title","suffix"], reaching 236 corpus names and 715 (name × field) pairs. It latently absorbs #367's own comma-bearing class:No such name is in the corpus today, so this is latent — it becomes a green-on-regression the day one is added. That rule's prose already concedes it absorbed five CJK honorific rows it "has nothing to do with", which is documented; the bare-comma reach is not.
3. A blanket CJK rule shadows five to seven specific ones.
expected_since_1.4.0.toml:62and the 2.0.0 twin —name_regexis a bare CJK/Hangul/Kana character class,fields = ["given","middle","family"], matching all 97 CJK corpus names. It mechanically shadowsfix(cjk-maiden-marker),fix(cjk-comma-compound),fix(cjk-honorific-suffix),fix(cjk-delimited-nickname),fix(cjk-fullwidth-paren-nickname), and in the 2.0.0 ledger alsofix(#308/#312/#319/#320)andfix(#307/#308/#320). A future regression in Japanese or Korean given/family splitting is labelled with 2.1-era issue numbers and exits 0.The structural gap: a rule that stops explaining anything is invisible
_CORPUS_CLAIMSintests/v2/test_ledger_guards.pyrecords what a rule's regex matches, which is independent of the parser. Reverting #370's fix and re-running showed 5 unit tests failing (good) but no ledger or differential failure — the 2.1.0 baseline reports 0 diffs, 0 unexplained, exit 0, with its only rule now explaining nothing. Deleting a rule is caught (test_every_rule_claims_the_recorded_share_of_the_corpus); a rule going inert is not.Two cheap mechanical checks
Full over-claim detection is human judgement — no code decides whether "lone post-comma piece routes to suffix/title" describes
Dr. Do Van Johnson, MD. But two checks are not judgement, and either would have surfaced instances 1 and 2 at authoring time:classify()returns on first match. Have it collect every matching rule and printAMBIGUOUS 'Mr. Van Nguyen' -> [fix(#367), fix(suffix-routing)]alongside the classification. Exit code unchanged; the operator sees the overlap without having to delete a rule to discover it.validate_rules(compare.py:362) already rejects afieldslist naming all seven roles, but permits a fields-only rule with six. A fields-only rule reaching every corpus name is the shape most worth a warning, or an explicit declaration of unbounded reach.A
--strictmode asserting every rule fired at least once would close the third gap.Note
#370 tightened its own
fix(#367)rule after this was found — it originally reached 11 corpus names includingVincent van Gogh, whichAGENTS.md:148names as a standard regression canary — so the new rule is not an instance. The three above are pre-existing.