feat(differential): a fourth corpus from the rules doc's own examples - #415
Open
derek73 wants to merge 3 commits into
Open
feat(differential): a fourth corpus from the rules doc's own examples#415derek73 wants to merge 3 commits into
derek73 wants to merge 3 commits into
Conversation
6389f06 moved `mc` and `ste` into the never-given half of the particle vocabulary and added `los`, `las` and `das`, which were missing from it entirely. It is on master, in no release tag, and had no Behavior Changes bullet -- so a user-visible change was heading for 2.2 with nothing in the changelog. Five name shapes move: `Mc Donald`, `Ste Marie`, `Los Santos`, `Las Casas` and `Das Silva` each go from a given-plus-family reading to a single folded family name, and the PARTICLE_OR_GIVEN ambiguity they used to report goes with them. Found by the rules-doc corpus (#414): `Mc Donald` is a rules.md#P1 example, and classifying its diff meant finding out which change had moved it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs/design/rules.md's 156 example names fed no differential corpus. 113 of them were in no corpus at all, and 12 had moved during the 2.2 cycle with nothing observing it. The doc tests are not already this. They pin each example against an expectation stored beside it, so a deliberate behaviour change edits both in one commit and the suite stays green -- a doc example cannot warn about the change that edited it. PR #409 is the worked example: it changed M2's "Jane de la née Jones" from family 'de la née Jones' to family 'de la', the doc test followed the edit, and nothing else observed the movement. Parsing the same name against a RELEASED baseline can, because no commit edits 1.4.0. build_rules_corpus.py selects through the doc's own parser rather than a second regex, the way build_cjk_corpus.py selects through the shipped script table, and test_ledger_guards.py pins the checked-in file against the generator. Classifying the 12 is most of this diff, and it found four things: - An unlogged behaviour change, committed separately: #360's never-given vocabulary move, which is what moves `Mc Donald`. - Two ledger rules broad enough to absorb a regression, both harmless until a name of the right shape existed. fix(#274) matched a PARENTHESIZED `Nee` -- a nickname under the default facade the differential parses with, where no marker is active at all -- and stood ready to explain a maiden diff on a name with no marker in it. The acronym rule's `m\.?a\.?` reached the real name `John Smith M.A.`. The vocabulary-presence and member-reach guards caught both; they are the absorption detectors and this is the first corpus that gave them something to detect. - Two arrivals the ledger predicted in writing. fix(#379) said a vd-shaped name "should arrive unexplained and be read once"; fix(#367) named `Sir de Mesnil` as a future arrival. Both arrived, and both are now their own rules -- separate rather than widened literals, because the guards allow one vocabulary alternation per rule and widening both a title and a particle slot needs two. - Two pre-existing behaviours that had never had a corpus name: a nakaguro division meeting a glued hangul honorific, and two typographic quote spans read as one nickname set. All three baselines exit 0 over 864 names. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #415 +/- ##
=======================================
Coverage 98.55% 98.55%
=======================================
Files 44 44
Lines 2981 2981
=======================================
Hits 2938 2938
Misses 43 43 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Review found the first cut of #414 doing the thing this ledger exists to prevent, in two rules at once. fix(#399) reached six corpus names and explained three. The other three were exactly the shapes its own comment says it must not claim: the trailing-marker shape, the connective join and the post-comma bound-given join -- M2's accepted limits, two of them tracked at #412. The comment justified the reach with "the consumer needs a non-suffix word after the marker", which `\s+\S` does not express: PhD matches `\S` perfectly well. Anchored now to one word after the marker and no comma in the name, which takes it to four. fix(#274)'s reach went 4 -> 16 when the corpus grew, and nine of the sixteen do not move. Seven arrived with this PR, four of them M2 BOUNDARY examples -- 'Jones nee', 'nee Jones' -- which the vocabulary-presence guard cannot see, because those names really do carry marker vocabulary. It now tests what the rule has always meant: a marker with a name word before it and one after it. Reach 16 -> 11, and all seven names it explains are inside the eleven. Both rules still claim one static name each ('Jane van der Berg nee PhD', 'abd Allah'). Excluding either needs the regex to ask a vocabulary question, which is a second alternation the guards allow only one of per rule -- so they are named in the comments instead, the way fix(#379) names 'Nguyen, Van'. Two new guards, for holes the review demonstrated rather than argued: - _MUST_NOT_MATCH. A rule claiming exactly one corpus name can be widened with its claim count unmoved, because the names it newly reaches are outside the corpora. Six such widenings passed the whole suite: `^mc\s+\S+$` to `^mc`, the vd rule to a bare `\bvd\b`, and four more. The roster is a wall rather than a snapshot -- wrong at recording time too -- seeded from the names the rules' own comments argue about. It catches all six, plus reverting both narrowings. - A rule-count floor and a per-rule contribution check on the rules doc. The staleness pin compares the file against the generator's CURRENT output, so a generator that silently selects less agrees with a regenerated file and both are wrong together. _CORPUS_FLOORS bounded that to a few names; these bound the shapes it cannot see. Also from review: - The builder emitted "" as a name: `[subject]` examples carry no text, and rules.md's three collapsed into one empty string the doc never wrote. Filtered, 156 -> 155. - compare.py returned normally when a corpus file was DELETED -- the glob just found fewer files and the summary read like a full run. The floors already name every corpus that should exist, so main() asks them. - _CROSS_RULE_WINNERS gained the three names that land on the 1.4 catch-all with nothing recording it, and lost a header count that had gone stale twice over. - The #360 bullet claimed non-leading names are untouched. True for mc and ste, which only changed halves; los, las and das were not particles at all, so 'Maria das Neves' moves too -- 6389f06's own headline example. Same for the ambiguity sentence: three of the six names never reported a fork. - The #399 bullet's "no corpus name carries a particle ahead of a marker" stopped being true the moment this corpus landed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #414.
docs/design/rules.md's 156 example names fed no differential corpus. 113 were in no corpus at all, and 12 had moved during the 2.2 cycle with nothing observing it.Why the doc tests are not already this
test_rules_doc.pyexecutes every example, which pins them harder than corpus membership pins anything — but against an expectation stored beside them. Change behavior deliberately and the expected value is edited in the same commit; the test goes green and says nothing. A doc example cannot warn about the change that edited it.PR #409 is the worked example. It changed M2's
to
family="de la". The doc test followed the edit. The name was in no corpus, so nothing independent observed the movement — the only reason it is explained anywhere is that I chose to explain it.Parsing the same name against a released baseline can observe it, because no commit can edit 1.4.0.
The builder
build_rules_corpus.py, shaped afterbuild_cjk_corpus.py: it selects through the doc's own parser rather than a second regex, andtest_ledger_guards.pypins the checked-in file against the generator so a stale corpus fails the suite instead of silently narrowing the gate. Policy annotations are ignored and boundary strings kept, both following the CJK builder's stated reasoning.Classifying the 12 is most of this diff, and it found four things
An unlogged behavior change.
Mc Donaldmoves because #360 movedmcinto the never-given particles. That commit is on master, in no release tag, and had no release-log bullet — a user-visible change heading for 2.2 with nothing in the changelog. Committed separately here.Two ledger rules broad enough to absorb a regression, both harmless until a name of the right shape existed:
fix(#274)matched a parenthesizedNee. Under the default facade — which is what the differential parses with — no maiden delimiter is configured, so that word is a nickname and no marker is active at all. The rule stood ready to explain amaidendiff on a name with no marker in it.m\.?a\.?reached the real nameJohn Smith M.A..The vocabulary-presence and member-reach guards caught both. They are the absorption detectors, and this is the first corpus that gave them something to detect.
Two arrivals the ledger predicted in writing.
fix(#379)said a vd-shaped name "should arrive unexplained and be read once";fix(#367)namedSir de Mesnilas a future arrival. Both arrived. Both are now their own rules rather than widened literals — the guards allow one vocabulary alternation per rule, and widening both a title and a particle slot needs two, whosecoverssets cannot both be recorded under one key.Two pre-existing behaviors that had never had a corpus name: a nakaguro division meeting a glued hangul honorific, and two typographic quote spans read as one nickname set.
What review changed
The first cut had two rules doing the thing this ledger exists to prevent.
fix(#399)reached six corpus names and explained three. The other three were exactly the shapes its own comment says it must not claim — M2's accepted limits, two of them tracked at #412. The comment justified the reach with "the consumer needs a non-suffix word after the marker," which\s+\Sdoes not express:PhDmatches\Sperfectly well. Now anchored to one word after the marker and no comma in the name: six → four.fix(#274)'s reach went 4 → 16 when the corpus grew, and nine of the sixteen do not move. Seven arrived with this PR, four of them M2 boundary examples (Jones née,née Jones) that the vocabulary-presence guard cannot see — those names really do carry marker vocabulary. It now tests what the rule has always meant: a marker with a name word before it and one after it. 16 → 11, with all seven explained names inside the eleven.Both still claim one static name each (
Jane van der Berg née PhD,abd Allah). Excluding either needs the regex to ask a vocabulary question, which is a second alternation the guards permit only one of per rule — so they're named in the comments instead, the wayfix(#379)namesNguyen, Van.Two new guards, for holes the review demonstrated rather than argued:
_MUST_NOT_MATCH. A rule claiming exactly one corpus name can be widened with its claim count unmoved, because the names it newly reaches are outside the corpora. Six such widenings passed the entire suite —^mc\s+\S+$to^mc, the vd rule to a bare\bvd\b, four more. The roster is a wall rather than a snapshot, so it's wrong at recording time too. It catches all six, plus reverting both narrowings._CORPUS_FLOORSbounded that to a few names; this bounds the shapes it can't see.Smaller fixes: the builder emitted
""(the[subject]example form carries no text, and rules.md's three collapsed into one empty string), 156 → 155;compare.pyreturned normally when a corpus file was deleted, since the glob just found fewer files;_CROSS_RULE_WINNERSgained the three names landing on the 1.4 catch-all and lost a header count stale twice over; and two release-log claims were wrong —los/las/dasweren't particles at all before #360, soMaria das Nevesmoves from a non-leading position, and the #399 bullet's "no corpus name carries a particle ahead of a marker" stopped being true the moment this corpus landed.Verification
All three baselines exit 0 over 864 names (751 → 864), the 1.4.0 baseline verified genuine rather than the mislabelled cached tree. Full suite green: 4403 passed, up from 4289 as the new corpus adds differential cases. ruff and mypy clean.
Every
_CORPUS_CLAIMSgrowth was checked against the two absorption guards before recording, per the guard's own instruction.🤖 Generated with Claude Code