You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
compare.py computes by_issue -- the exact set of names each rule explained -- prints it (tools/differential/compare.py:868), and then passes only set(by_issue) to dormant_rules. Membership is checked; the counts never are. The only property any run asserts about a rule's reach is "it explained at least one name."
The shape
classify() takes the first rule whose declared fields are a superset of the observed diff. A rule therefore keeps matching when the diff beneath it shrinks -- and shrinking is the common direction, since most parser fixes move fewer fields, not more.
Freiherr von Richthofen V is the case that already happened. #410 narrowed its diff from {given, family, suffix} to {family, suffix}; the fix(#424) a title-led chain before the numeral is the one name piece rule declared all three, so it kept claiming the name and no run named the movement. docs/design/decisions.md#H1 records it:
a shrinking diff kept matching and no run ever named it. A real movement behind a green gate
That rule's fields are ["family", "suffix"] today, narrowed by hand during #410, and it explains exactly 1 name. The instance is closed. The mechanism that let it hide is not.
_CORPUS_CLAIMS measures what a rule's name_regexreaches, not what it explains -- the two differ by the fields test and by rule order. It is a unit test with no baseline worker, so it cannot measure explanation even in principle: the diff set only exists inside a compare.py run.
_CROSS_RULE_WINNERS pins which rule wins for a hand-picked list of contested names. A wall around known arguments, not a census.
This is the gap #451 reports from the other side. #451's rule hides growth because it has no name_regex; fix(#424) hid a shrink despite having one. Both are "a rule broader than the diff it explains", which decisions.md#H1 already calls "the lesson worth keeping rather than either fix."
Sketch
A per-rule declared reach in the ledger, checked by compare.py against the run's own by_issue:
[[change]]
issue = "fix(#424) a title-led chain before the numeral is the one name piece"name_regex = "(?i)^freiherr\\s+von\\s+richthofen\\s+v$"fields = ["family", "suffix"]
explains = 1
Open in the design, not decided here:
It generalizes dormant.dormant is explains = 0 carrying a reason. Whether the keys merge or sit side by side is part of the design.
Count, or count plus digest?_Claim already documents why a bare count is weak -- "swapping feat(Support smart quotes (“Jack”), guillemets («Petit»), and CJK brackets as nickname delimiters #273)'s delimiter class for a single accented letter holds the count at 6 while claiming six entirely different names." A digest closes that, at the cost of an unreadable field in a file that is otherwise all prose.
The cost to weigh. The corpora grow: corpus_issues.jsonl is append-only from the tracker, corpus_rules.jsonl is generated from rules.md, so one harvest moves numbers across many rules at once. _CORPUS_CLAIMS and _CORPUS_FLOORS both already accept a version of this cost, and the discipline is set: "Re-measure rather than adjust them if a parser change moves one: a diff shape that shifted is a finding, not a number to update."
Where it must live.compare.py, not tests/v2/test_ledger_guards.py -- the guards file has no baseline worker.
Independent and deliberately unbundled. #451's bundle retires the fields-only rule and lands without this; this closes the half of the shape a name_regex does not protect against. Bundling them would make #451's commits unbisectable.
compare.pycomputesby_issue-- the exact set of names each rule explained -- prints it (tools/differential/compare.py:868), and then passes onlyset(by_issue)todormant_rules. Membership is checked; the counts never are. The only property any run asserts about a rule's reach is "it explained at least one name."The shape
classify()takes the first rule whose declaredfieldsare a superset of the observed diff. A rule therefore keeps matching when the diff beneath it shrinks -- and shrinking is the common direction, since most parser fixes move fewer fields, not more.Freiherr von Richthofen Vis the case that already happened. #410 narrowed its diff from{given, family, suffix}to{family, suffix}; thefix(#424) a title-led chain before the numeral is the one name piecerule declared all three, so it kept claiming the name and no run named the movement.docs/design/decisions.md#H1records it:That rule's
fieldsare["family", "suffix"]today, narrowed by hand during #410, and it explains exactly 1 name. The instance is closed. The mechanism that let it hide is not.Why no existing guard sees it
dormant(Make a ledger rule that explains nothing say why (#372) #373) covers one point on the scale: zero. A rule that explained 8 names and now explains 3 is not dormant, and nothing reports it._CORPUS_CLAIMSmeasures what a rule'sname_regexreaches, not what it explains -- the two differ by thefieldstest and by rule order. It is a unit test with no baseline worker, so it cannot measure explanation even in principle: the diff set only exists inside acompare.pyrun._CROSS_RULE_WINNERSpins which rule wins for a hand-picked list of contested names. A wall around known arguments, not a census.This is the gap #451 reports from the other side. #451's rule hides growth because it has no
name_regex;fix(#424)hid a shrink despite having one. Both are "a rule broader than the diff it explains", whichdecisions.md#H1already calls "the lesson worth keeping rather than either fix."Sketch
A per-rule declared reach in the ledger, checked by
compare.pyagainst the run's ownby_issue:Open in the design, not decided here:
dormant.dormantisexplains = 0carrying a reason. Whether the keys merge or sit side by side is part of the design._Claimalready documents why a bare count is weak -- "swapping feat(Support smart quotes (“Jack”), guillemets («Petit»), and CJK brackets as nickname delimiters #273)'s delimiter class for a single accented letter holds the count at 6 while claiming six entirely different names." A digest closes that, at the cost of an unreadable field in a file that is otherwise all prose.corpus_issues.jsonlis append-only from the tracker,corpus_rules.jsonlis generated fromrules.md, so one harvest moves numbers across many rules at once._CORPUS_CLAIMSand_CORPUS_FLOORSboth already accept a version of this cost, and the discipline is set: "Re-measure rather than adjust them if a parser change moves one: a diff shape that shifted is a finding, not a number to update."compare.py, nottests/v2/test_ledger_guards.py-- the guards file has no baseline worker.Relationship to #451
Independent and deliberately unbundled. #451's bundle retires the fields-only rule and lands without this; this closes the half of the shape a
name_regexdoes not protect against. Bundling them would make #451's commits unbisectable.