fix(group): a suffix never begins a name (#371) - #474
Merged
Conversation
`parse("Ph. D. Van Johnson")` read given 'Van Johnson' with an empty
family and suffix 'Ph. D.'. It now reads title 'Ph.', given 'D.',
family 'Van Johnson'.
Position outranks the vocabulary match. The v1 fix_phd merge -- which
heals a split `Ph.`/`D.` into one credential -- was applied wherever
the pair stood, and that is what made a leading credential possible at
all: every other suffix-shaped word standing first already falls out
as a title (Jr., MD, Esq., Sr., via TITLES or H2's abbreviation
clause) or as an ordinary name word (PhD, III). The pair was the only
shape that reached the defect.
RESTORES 1.4.0 rather than deviating from it. v1 matched the pair by
regex on the raw string and healed it only where it TRAILED; leading,
it split them exactly as this does -- measured on the released wheel,
all four corpus names of the shape return to their v1 reading. So
expected_since_1.4.0.toml LOSES its fix(leading-credential) rule (the
gate said EXPLAINED NOTHING, which is how a parity restoration
announces itself) while 2.0.0 and 2.1.0 GAIN one claiming the same
four names, five roles and digest. What shipped in 2.0 was a
regression from applying the merge unconditionally.
Opening the NAME is not opening a piece list: a credential run
legitimately opens segment 1 after a family comma ("Smith, Ph. D.
Jr."), which C1 reads as a listing. A first draft tested `k == 0`
alone, broke fifteen tests and took the gate red at three baselines.
Accepted: Parser.revise(suffix="Ph. D.") now renders 'Ph., D.'.
revise() sub-parses the string it is given and a field value has no
head. A draft carved that out by requiring a name to displace, and the
carve-out made the head reading depend on what FOLLOWED -- appending a
maiden clause changed whether 'Ph.' was a title, which the maiden
property test caught. Dropping the carve-out removed both problems.
rules.md#S2 states the rule; three mutations verified; gate green at
all three baselines.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #474 +/- ##
=======================================
Coverage 98.66% 98.66%
=======================================
Files 45 45
Lines 3214 3214
=======================================
Hits 3171 3171
Misses 43 43 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The gate meant a different "head" than the rule it implemented, in two directions at once, and the corpus could see neither. TOO LATE: `k == 0` is a PIECE index, and extract_delimited removes a quoted or bracketed clause before grouping -- so `"Bob" Ph. D. John Smith` reached the merge with the pair at k == 0 and a word standing before it in the input. v1 merges there (its regex needed only a preceding space); declining broke parity on 112 measured names, every one opening with a quote or bracket, none of them in any corpus. The test is now `a[0] == 0`, the first TOKEN, which is v1's boundary. TOO EARLY: a title is a piece, so `Sir Ph. D. Van Johnson` keeps the credential and the empty family -- #371's own symptom one word to the left. That is 1.4.0 parity and it cannot be fixed here: "the first piece of the name" is not computable before this merge, because H2's abbreviation test is true of `Ph.` itself, so a scan stepping over titles would step over the very piece being judged. rules.md#S2 now says OPENS THE STRING and carries the boundary example. Prose corrected with it, all of it mine and all of it wrong: - "restores 1.4.0 on all four" -- three. `Ph. D., Jr.` moves the `D.` from given to family, two roles apart, and rides under a pre-comma rule that predates this change. - "EXPLAINED NOTHING is how a parity restoration announces itself" -- the gate said SHADOWED, which compare.py separates from reverted precisely because the fixes differ. #426 is the precedent. - "v1 healed the pair only when it TRAILS" -- false in both directions; v1 healed it everywhere except the head, and reading that sentence as the contract would license narrowing the merge. - two live references to the rule this PR deleted, in the same ledger; acting on them takes the gate red. - the 2026-07 phd-merge entry promised the two spellings "read alike", which #371 scopes to non-leading position. Tests: the guard test this PR narrowed was left VACUOUS -- `II` is not suffix vocabulary under the file's reduced lexicon, so removing the `Ph. D.` witness left it passing under the exact mutation it is named for. `PhD` is the replacement witness and the mutation is killed again. Added Case rows for `Ph. D. Van Johnson` (the issue's subject, whose empty fields are the symptom) and the bare `Ph. D.`, and pinned both new conditions. Four mutations verified. Gate green at all three baselines.
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.
A name that opens with a spaced
Ph. D.stops losing its surname.Closes #371
The defect
The rule
A suffix never opens the string: position outranks the vocabulary match.
rules.md#S2states it.The v1
fix_phdmerge — which heals a splitPh./D.into one credential — was applied wherever the pair stood, and that is what made a leading credential possible at all. Every other suffix-shaped word standing first already falls out correctly (Jr.,MD,Esq.,Sr.as titles;PhD,IIIas name words), viaTITLESmembership or H2's abbreviation clause. A reviewer swept all 650 one-word suffix-vocabulary entries at the head of two tails — 8,612 parses — and none yields a suffix. ThePh./D.pair is the only shape that reaches the defect.Everything else is untouched: the merge still heals a trailing credential, and a family comma still opens a listing rather than a name.
"The head" means the head of the STRING, and review found both ways to get that wrong
The first draft tested the piece index.
extract_delimitedremoves a quoted or bracketed clause before grouping, so"Bob" Ph. D. John Smithreached the merge with the pair atk == 0and a word standing before it in the input. v1 merges there — its regex\s(ph\.?\s+d\.?)needed only a preceding space — and declining broke parity on 112 measured names, every one opening with a quote or bracket, and not one of them in any corpus. The test is now the token index, which is v1's boundary exactly.The other direction cannot be fixed here, and finding out why settled the design. A leading title is a piece, so
Sir Ph. D. Van Johnsonkeeps the credential and the empty family — #371's own symptom one word to the left. That is 1.4.0's reading, and the principled alternative does not exist: "the first piece of the name" is not computable before this merge, because H2's abbreviation test is true ofPh.itself, so a scan stepping over titles would step over the very piece being judged.rules.md#S2therefore says opens the string, with the title case pinned as an Accepted boundary rather than left as an unmarked counterexample.Parity with 1.4.0, stated precisely
Three of the four corpus names of this shape return to their v1 reading exactly:
Ph. D. Van JohnsonPh., firstD., lastVan JohnsonPh. D. John SmithJohn, lastSmithPh. D.Ph., lastD.Ph. D., Jr.Ph., firstD., suffixJr.D.— not restoredThe fourth moves the
D.from given to family, two roles apart, and its residual diff rides underfix(comma-precomma-family), which predates this change.So what shipped in 2.0 was a regression from applying the merge unconditionally.
expected_since_1.4.0.tomlloses itsfix(leading-credential)rule; 2.0.0 and 2.1.0 each gain one claiming the same four names, five roles and the same digest.Read the diagnosis, not the error. The gate reported that rule
SHADOWED, not reverted —compare.pyseparates the two "because they have three different fixes", and #426 is the precedent for dropping a shadowed rule. An earlier draft of this PR generalisedEXPLAINED NOTHINGinto "how a parity restoration announces itself", which would teach the next maintainer to delete a shadowed rule whose behaviour is still diverging.Review
Four agents; two regressions found, both mine, both above. Nine further findings, all fixed. Three worth naming:
Ph. D.witness leftII Van Johnsonalone — andIIis not suffix vocabulary under that file's reduced lexicon, so the test passed under the exact mutation it is named for.PhDis the replacement witness; the mutation is killed again.John Ph. D. Smith.(?!\s*ph\.)carve-out is what letsPh. D., Jr.reach its own rule.Also added:
Caserows forPh. D. Van Johnson(the issue's subject, whose empty fields are the symptom —test_caseasserts the whole dict) and the barePh. D., neither of which had a pin outside an out-of-band tool run.Verification
unexplained: 0.One check is red
build (3.12)failstest_facade_thousand_names_under_a_secondat 1.06–1.08s against a fixed 1.0s bound. Measured three ways against this branch's own parent — uninstrumented (~1%, inside the parent's own spread), coverage-instrumented (indistinguishable), and the whole benchmark file under--cov(branch faster) — no method reproduces a slowdown. Tracked as #475, which carries the full evidence including the CI control run that favours master.🤖 Generated with Claude Code