Repository navigation
The one fields-only ledger rule absorbs any diff of its shape, and no guard can see it grow #451
Description
tools/differential/expected_since_1.4.0.toml's fix(suffix-routing) two-token name with unambiguous trailing suffix stays suffix is the only rule in any of the three ledgers with no name_regex:
# tomllib over all three ledgers
expected_since_1.4.0.toml: 59 rules, 1 with NO name_regex
expected_since_2.0.0.toml: 58 rules, 0
expected_since_2.1.0.toml: 51 rules, 0issue = "fix(suffix-routing) two-token name with unambiguous trailing suffix stays suffix"
# 'Johnson PhD' / 'Smith Jr.' / 'John V' / 'QC MP': ... Four corpus names,
# and the prose fits all four.
fields = ["given", "family", "suffix"]It explains 14 names today, not four.
Why this shape absorbs silently
compare.py admits a rule when diff_fields <= set(fields). With no name_regex, this rule claims any name whose diff fits {given, family, suffix} — a very common shape — so a newly-moved name lands under it and the gate stays green with nothing to read.
The guard cannot see it either. _CORPUS_CLAIMS records names as "corpus names the name_regex reaches; the whole corpus when a rule has none" — so this rule's entry is _Claim(1090, ('family', 'given', 'suffix'), …), the entire corpus. It is already at its maximum, so arrivals never move it. test_every_rule_claims_the_recorded_share_of_the_corpus passes whether the rule explains 4 names or 40.
It has absorbed real movements twice
- fix(post_rules): H1 counts name words, not annotations (#410) #444 — two nickname names (
Senator "Rick" Edmonds,Xyz. (Bud) Smith) moved and landed here. Detected only because someone compared the rule's heading count before and after: 14 → 17. They were given their own rule, and the ledger records the surgery as the precedent set by Ledger rules classify diffs they do not describe, and a rule that stops explaining anything is invisible #372's names off this same catch-all. - fix(post_rules): a maiden name makes the lone name word the family (#445) #450 — after narrowing the
fix(#335)rules, a{given, family}regression onSmith (née Jones)is still explained at 1.4.0 by this rule. That PR records the limit rather than closing it, since the catch-all is long-standing and out of its scope.
So the ledger's central promise — that every behaviour change is classified in writing, by the rule that caused it — is not held for any diff of this shape at the 1.4.0 baseline.
Worth deciding
Whether a fields-only rule should be permitted at all. compare.py's validate_rules currently allows it (the docstring calls it "the most unbounded shape validate_rules permits"), and _CORPUS_CLAIMS's comment already names it as the shape "most worth writing down" — the recording exists precisely because the risk was understood.
Options, roughly:
- Give it a
name_regex. It explains 14 names; an anchored alternation would bound it and make_CORPUS_CLAIMSmeaningful. Cheapest, and it matches what fix(post_rules): H1 counts name words, not annotations (#410) #444 and Ledger rules classify diffs they do not describe, and a rule that stops explaining anything is invisible #372 already did piecemeal. - Forbid fields-only rules in
validate_rules. Structural, and there is exactly one to migrate. Would need a rule for any legitimately open-ended class. - Make the guard see reach rather than regex coverage. Record what a rule explains, not what its regex matches, so a growing catch-all fails the guard. Larger change; would make every rule's claim tighter.
Its comment says "Four corpus names, and the prose fits all four" — that has been wrong since at least #414's rules-corpus harvest, and nothing recomputes it. Whatever is decided, that figure should either become a recompute one-liner or go.
Raised from #450's review, where the same shape was found still absorbing after a targeted narrowing.
Activity
fix(differential,guards,decisions): the five defects the PR review co…
fix(differential,guards,docs): the four Important review findings, an…
test(guards,differential): pin the jr rule's surplus and the dormant/…
fix(differential,docs): the review findings on the fields ban
fix(differential,guards): port the derek73#360 mc and derek73#367 de …
fix(differential,guards): name the A2 emptying instead of a comma rule
fix(differential,guards): name the emoji token division
fix(differential,guards): stop the catch-all declaring a role it neve…
fix(differential,guards): port the derek73#380 vd rule to the 1.4 ledger
fix(differential): drop four false claims the 2.x ledgers copied from…
fix(differential,guards): give the nickname-plus-suffix name its own …
fix(differential,guards): give the two NOT-WANTED regressions their o…
fix(differential,guards): retire the fields-only catch-all for four b…
fix(differential): reject a rule with fields and no name_regex (derek…
docs(decisions): record the fields-only ledger arc
docs(mechanisms,decisions,differential): the ban falsified six standi…
docs(decisions): three derek73#451 claims the derek73#452 review fals…
feat(differential): reject a rule with name_regex and no fields (dere…
docs: settle R4's verification limit, sweep the branch's stale prose
tooling(differential): pin the exemption shape checks, and refuse a r…
docs(design): the recorded-shapes arc