The three differential corpora draw from v1's test banks (corpus.jsonl), the issue tracker (corpus_issues.jsonl) and the CJK rows of the case table (corpus_cjk.jsonl). None of them reads docs/design/rules.md.
That leaves the project's most reviewed names outside the differential gate:
rules.md example names (distinct): 156
of those, in no corpus: 113
of those, MOVED 2.1.0 -> 2.2 branch: 12
Why the doc tests are not already this
The instinct is that rules.md examples are well pinned, since test_rules_doc.py executes every one. But they are self-updating pins: when behavior changes deliberately, the expected value is edited in the same commit and the test goes green. A doc example cannot warn about the change that edited it.
The corpus is the opposite kind of guard. It compares against a released baseline that cannot be edited, so a moved name arrives UNEXPLAINED and has to be classified in a ledger with a written justification.
PR #409 is a worked example. rules.md#M2 carried:
"Jane de la née Jones" → family="de la née Jones"
and #409 changed it to family="de la". The doc test followed the edit. Nothing independent observed the movement — the only reason it is explained anywhere is that the author chose to explain it. The name is in no corpus, so the gate never saw it.
The 12 that moved
All intended changes from this cycle, which is the healthy result — adopting this is classification work, not bug-hunting:
Beethoven, Ludwig van middle 'van' -> '' last -> 'van Beethoven' #379
Berg, Jan van der middle 'van der' -> '' last -> 'van der Berg' #379
Berg, Jan vd suffix 'vd' -> '' last -> 'vd Berg' #380
Jong, Anke de middle 'de' -> '' last -> 'de Jong' #379
Nguyen, Thi Van middle 'Van' -> '' last -> 'Van Nguyen' #379
Mc Donald first 'Mc' -> '' last -> 'Mc Donald' #385
Sir de Mesnil first 'de Mesnil' -> '' last -> 'de Mesnil' #367
abd Allah Smith first 'abd' -> 'abd Allah' #400
Salam, abd Allah first 'abd' -> 'abd Allah' #400
Jane de la née Jones last -> 'de la' maiden '' -> 'Jones' #399
Jane van der Berg née Jones last -> 'van der Berg' maiden '' -> 'Jones' #399
John van der Berg née Jones last -> 'van der Berg' maiden '' -> 'Jones' #399
Relationship to #413
Nearly disjoint, so both are worth doing and neither subsumes the other:
#413 would add (backtick-only names from issues): 104
rules.md names in no corpus: 113
overlap: 9
of the 12 moved names, reachable via #413: 4
The other eight are reachable only from the doc — they appear in no issue at all.
Shape of the fix
A fourth builder with the same shape as build_cjk_corpus.py, which is the closest precedent and already solves the hard parts:
- Derive from the reviewed source rather than a hand-maintained list, so a name enters the corpus by being written down where it already gets reviewed.
- Harvest with the doc's own parser (
tests/v2/rules_doc.parse_rules_doc), not a second regex — the same argument that made the CJK builder use the shipped script table.
- Pin the checked-in file against the builder in
test_ledger_guards.py, so a stale corpus fails the suite rather than silently narrowing the gate.
- Ignore the examples' policy annotations. The corpus carries name STRINGS and the run parses them with the default facade; a family-first-scoped example is simply one more name to diff, exactly as
build_cjk_corpus.py argues for its zh-scoped rows.
Two things to decide rather than assume:
- Boundary strings. rules.md deliberately includes
'(', '.,', "Anna () Smith" and similar. Over-collection is the established position across the other builders ("costs one parse and produces no diff"), so these are probably fine, but they are more concentrated here than elsewhere.
- Ledger work. The 12 above arrive
UNEXPLAINED on the first run and need rules in up to three ledgers. Several are already explained by existing rules for the same fix, so some will classify without new entries — worth measuring before writing any.
The three differential corpora draw from v1's test banks (
corpus.jsonl), the issue tracker (corpus_issues.jsonl) and the CJK rows of the case table (corpus_cjk.jsonl). None of them readsdocs/design/rules.md.That leaves the project's most reviewed names outside the differential gate:
Why the doc tests are not already this
The instinct is that rules.md examples are well pinned, since
test_rules_doc.pyexecutes every one. But they are self-updating pins: when behavior changes deliberately, the expected value is edited in the same commit and the test goes green. A doc example cannot warn about the change that edited it.The corpus is the opposite kind of guard. It compares against a released baseline that cannot be edited, so a moved name arrives
UNEXPLAINEDand has to be classified in a ledger with a written justification.PR #409 is a worked example.
rules.md#M2carried:and #409 changed it to
family="de la". The doc test followed the edit. Nothing independent observed the movement — the only reason it is explained anywhere is that the author chose to explain it. The name is in no corpus, so the gate never saw it.The 12 that moved
All intended changes from this cycle, which is the healthy result — adopting this is classification work, not bug-hunting:
Relationship to #413
Nearly disjoint, so both are worth doing and neither subsumes the other:
The other eight are reachable only from the doc — they appear in no issue at all.
Shape of the fix
A fourth builder with the same shape as
build_cjk_corpus.py, which is the closest precedent and already solves the hard parts:tests/v2/rules_doc.parse_rules_doc), not a second regex — the same argument that made the CJK builder use the shipped script table.test_ledger_guards.py, so a stale corpus fails the suite rather than silently narrowing the gate.build_cjk_corpus.pyargues for its zh-scoped rows.Two things to decide rather than assume:
'(','.,',"Anna () Smith"and similar. Over-collection is the established position across the other builders ("costs one parse and produces no diff"), so these are probably fine, but they are more concentrated here than elsewhere.UNEXPLAINEDon the first run and need rules in up to three ledgers. Several are already explained by existing rules for the same fix, so some will classify without new entries — worth measuring before writing any.