Rationale
The differential harness keeps measured facts in prose — how many names a rule
explains, what shape a name diffs, how large a corpus is. Nothing recomputes
them. Three have now been caught stale in a single session, one of them while
actively misleading a reader.
docs/design/AGENTS.md already states the convention: "A count in a dated
entry is evidence, not a live fact", and "A count that carries no argument is
better deleted than dated." These sites predate or ignore it.
The three, measured 2026-09-02
1. tests/v2/_differential_fixtures.py — the comment above
_UNCLASSIFIED_NAMES says the old expression "rescanned all 751 names per
candidate name rather than per rule". len(_CORPUS_NAMES) is 1116. During a
code review the same day, a reviewer read 751 off this comment and reported it
as the live corpus population in a review of unrelated code. The count carries a
real argument (why a rescan was expensive — a measured ~400x) so the fix is to
keep the argument and drop the digits: "rescanned every corpus name per
candidate rather than per rule".
2. _CROSS_RULE_WINNERS, the 田中さん II row — recorded the diff shape as
("given", "suffix"); measured against the 1.4.0 wheel it is
{family, given, suffix} (first '田中さん'→'', last 'II'→'田中',
suffix ''→'さん, II'). Four sites carried the wrong shape — the row, a
neighbouring note, the _MUST_NOT_MATCH comment, and two more in
expected_since_1.4.0.toml. Fixed in the #382 branch; listed here because
of what it exposes, below.
3. expected_since_1.4.0.toml, fix(comma-family) lone post-comma piece routes to suffix/title, not first — its comment says "this rule now claims
exactly one name, 'Andrews, M.D.'". A differential run at baseline 1.4.0
measures eight. Not fixed; it is the reason this issue exists rather than a
fourth commit on #382.
The structural half, which matters more than the three
test_the_recorded_rule_still_wins_each_contested_name cannot catch a wrong
diff shape. It feeds classify() the recorded shape and asserts the
recorded winner — so a guessed shape agrees with itself forever. That is how
田中さん II sat wrong under a roster whose own docstring says "The diff shapes
are measured against the 1.4.0 wheel, not guessed." The roster is a
mechanisms.md#RECORDED-ROSTERS instance whose recorded half is unverified in
one dimension.
A guard that compares each _CROSS_RULE_WINNERS key's shape against a real
comparison would close it. That needs the pinned-wheel worker pass, so it
belongs in the differential run rather than the unit suite — which is the same
placement undeclared_contests uses in #382.
A trap for whoever does this. "How many names does a rule explain" cannot be
answered by driving classify() over the names its regex reaches at its own
declared fields — that asks which rule would win if a name diffed in the
widest shape the rule admits, and returns 278 for the rule in item 3. A rule
explains a name only when that name ACTUALLY diffs and the rule classifies that
real diff, which requires the 1.4.0 worker pass. The cheap proxy is off by a
factor of thirty here, and it is the obvious thing to reach for.
Scope
- Sweep
expected_since_*.toml, tests/v2/test_ledger_guards.py and
tests/v2/_differential_fixtures.py for measured claims in prose — counts of
names, diff shapes, reach figures, corpus sizes.
- For each: reground it with a stated recompute, or rephrase so the argument
outlives the digits, or delete it where it carries no argument. Prefer the
rephrasing — AGENTS.md gives "the two shares differ by orders of magnitude"
outliving "58% vs 0.65%" as the model.
- Add the diff-shape guard to the differential run so item 2's class cannot
recur silently.
Recorded roster VALUES are ground truth and must not be edited to match a new
measurement — a moved row is a finding. This issue is about the prose around
them, plus the one guard that would make the values self-checking.
Not in scope
#382's own exemption reasons. Every claim in those was measured in the pass that
wrote them, and each names its recompute.
Rationale
The differential harness keeps measured facts in prose — how many names a rule
explains, what shape a name diffs, how large a corpus is. Nothing recomputes
them. Three have now been caught stale in a single session, one of them while
actively misleading a reader.
docs/design/AGENTS.mdalready states the convention: "A count in a datedentry is evidence, not a live fact", and "A count that carries no argument is
better deleted than dated." These sites predate or ignore it.
The three, measured 2026-09-02
1.
tests/v2/_differential_fixtures.py— the comment above_UNCLASSIFIED_NAMESsays the old expression "rescanned all 751 names percandidate name rather than per rule".
len(_CORPUS_NAMES)is 1116. During acode review the same day, a reviewer read 751 off this comment and reported it
as the live corpus population in a review of unrelated code. The count carries a
real argument (why a rescan was expensive — a measured ~400x) so the fix is to
keep the argument and drop the digits: "rescanned every corpus name per
candidate rather than per rule".
2.
_CROSS_RULE_WINNERS, the田中さん IIrow — recorded the diff shape as("given", "suffix"); measured against the 1.4.0 wheel it is{family, given, suffix}(first '田中さん'→'',last 'II'→'田中',suffix ''→'さん, II'). Four sites carried the wrong shape — the row, aneighbouring note, the
_MUST_NOT_MATCHcomment, and two more inexpected_since_1.4.0.toml. Fixed in the #382 branch; listed here becauseof what it exposes, below.
3.
expected_since_1.4.0.toml,fix(comma-family) lone post-comma piece routes to suffix/title, not first— its comment says "this rule now claimsexactly one name, 'Andrews, M.D.'". A differential run at baseline 1.4.0
measures eight. Not fixed; it is the reason this issue exists rather than a
fourth commit on #382.
The structural half, which matters more than the three
test_the_recorded_rule_still_wins_each_contested_namecannot catch a wrongdiff shape. It feeds
classify()the recorded shape and asserts therecorded winner — so a guessed shape agrees with itself forever. That is how
田中さん IIsat wrong under a roster whose own docstring says "The diff shapesare measured against the 1.4.0 wheel, not guessed." The roster is a
mechanisms.md#RECORDED-ROSTERSinstance whose recorded half is unverified inone dimension.
A guard that compares each
_CROSS_RULE_WINNERSkey's shape against a realcomparison would close it. That needs the pinned-wheel worker pass, so it
belongs in the differential run rather than the unit suite — which is the same
placement
undeclared_contestsuses in #382.A trap for whoever does this. "How many names does a rule explain" cannot be
answered by driving
classify()over the names its regex reaches at its owndeclared
fields— that asks which rule would win if a name diffed in thewidest shape the rule admits, and returns 278 for the rule in item 3. A rule
explains a name only when that name ACTUALLY diffs and the rule classifies that
real diff, which requires the 1.4.0 worker pass. The cheap proxy is off by a
factor of thirty here, and it is the obvious thing to reach for.
Scope
expected_since_*.toml,tests/v2/test_ledger_guards.pyandtests/v2/_differential_fixtures.pyfor measured claims in prose — counts ofnames, diff shapes, reach figures, corpus sizes.
outlives the digits, or delete it where it carries no argument. Prefer the
rephrasing —
AGENTS.mdgives "the two shares differ by orders of magnitude"outliving "58% vs 0.65%" as the model.
recur silently.
Recorded roster VALUES are ground truth and must not be edited to match a new
measurement — a moved row is a finding. This issue is about the prose around
them, plus the one guard that would make the values self-checking.
Not in scope
#382's own exemption reasons. Every claim in those was measured in the pass that
wrote them, and each names its recompute.