Ask segment whether a post-comma run is name text (#319) - #327
Merged
Conversation
Pure refactor -- behavior unchanged. The peel needs the same question
segment already answers ("is this run name text?"), and a copy in
_script_segment would be a third definition of one rule.
Named for the house idiom rather than the obvious alternatives:
_script_matcher's whole=True already means "every element, and empty
does not count", which is exactly this predicate's empty rule.
all_suffixes would have promised the opposite, since Python's all([])
is True. is_suffix_run reads as a third sibling of is_suffix_strict /
is_suffix_lenient, which are the same question at token scale -- and
confusing those IS #319.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The peel scoped itself to segments[:2] under a family comma on the premise that segments[1] is name text. segment does not guarantee it: a one-word part before the comma reads as FAMILY_COMMA even when the part after it is entirely suffix-shaped, so the scan walked into a run of post-nominals, took an initial-shaped token as the site, found no tail there and gave up. Ask segment's own predicate instead of re-deriving it. '田中さん, V.' and '田中さん, Ph. D.' now peel as '田中さん, PhD' already did -- that spelling peeled under the old scope because 'PhD' satisfies _is_post_nominal's strict test, so the scan-back stepped OVER it and reached 田中さん; 'V.' and the 'Ph.'/'D.' pair do not, which is why one credential in three spellings gave two answers. One answer now. Policy(lenient_comma_suffixes=False) keeps the old behavior on purpose: the knob makes the strict predicate read 'V.' as name text, so the run is scanned and the peel is abandoned as before. That is the knob doing its job. The case rows pinning both sides come with the next commit -- this one leaves the #318 known-limit row failing on purpose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ja_honorific_glued_family_comma_suffixy_second_run was a known limit stated rather than fixed, on the grounds that closing it wanted segment's suffixy test extracted into a shared predicate. That predicate is _vocab.is_wholly_suffix and the peel now asks it, so the row moves: '田中さん, V.' gives family 田中 / suffix さん, and the classification goes parity -> fix(#319). Two rows come with it, both UNDETERMINED until they are measured against 1.4.0. The credential-pair row is the third spelling #319 named and the only one the Ph./D. merge reaches, which is what folds its run into a single wholly-suffix unit. The strict-knob row is the same input as the flipped one under Policy(lenient_comma_suffixes=False), pinning the pre-#319 fields where they remain reachable. The notes are careful about two things the rows would otherwise overclaim. The peel now gives one answer across all three spellings, but the DOWNSTREAM placement still differs by spelling -- title 'PhD', given 'V.', suffix 'さん, Ph. D.' -- and that is assign's question. And the strict knob does not blanket-freeze the old behavior: it holds for the initial-shaped suffix words, where the strict/lenient gap lives, but not for 'Ph. D.', whose merged form satisfies is_suffix_strict too, so that run is declined and the peel fires under the knob as well. Four neighbouring notes made claims this falsifies. The property that moved is WHICH RUNS THE PEEL SCANS: every family-comma row whose post-comma run is wholly suffix now scans segments[0] alone, so any note describing the scan crossing the comma is stale whether or not its row still passes. - ja_honorific_glued_family_comma said the scan steps OVER PhD to reach 田中さん and that the spaced form peels for the same reason. The run is declined instead and PhD is never examined; the two spellings now agree by different mechanisms. - ja_honorific_with_a_period_no_comma justified its existence by the comma form flattening TWO runs where it has one. The comma form flattens one now. Its real distinction is that this is the row where #320's fix acts through the scan-back. - ja_honorific_period_does_not_stop_the_peel said both runs were always in the peel's reach and only the strict test's answer moved. #319 took the second run back out of reach, and the veto now acts through is_wholly_suffix rather than the scan-back. Simulating the pre-#320 veto still strands さん, so the fields and the classification stand while the mechanism prose is rewritten. - ko_honorific_period_under_strict_comma_suffixes called itself the table's only exercise of lenient_comma_suffixes; it is one of two. ko_honorific_glued_given_suffix_comma_initial also claimed to be the only row noticing a scan widened to every segment. Checked by patching the scope out: the flipped row and credential_pair notice as well, from the family-comma side. The strict-knob row does NOT -- under the knob is_wholly_suffix is False on that run, so the scan already crossed and widening changes nothing. It remains the only suffix comma of the three. The module docstring gains UNDETERMINED as an explicitly temporary fourth classification. corpus_cjk.jsonl is generated from the case table and gains the one new text. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both #319 rows added in the previous commit carried the placeholder classification because nobody had run their inputs against 1.4.0 yet. Measured, with the worker's pin actually reporting 1.4.0 from inside the interpreter that answered: '田中さん, Ph. D.' -> first 田中さん, suffix 'Ph. D.' '田中さん, V.' -> first 'V.', last 田中さん ja_honorific_glued_family_comma_credential_pair is fix(#319). It carries two deviations from 1.4.0, as ja_honorific_glued_family_comma does: first -> family is comma-family's and 2.0 already had it before this branch (family 田中さん / suffix 'Ph. D.', measured at the commit before the peel change), while the peel that takes さん off 田中さん is what #319 moves. ja_honorific_glued_family_comma_strict_knob is parity. The knob has no v1 spelling, so as with ko_honorific_period_under_strict_comma_suffixes the row is judged against 1.4.0's single reading of the same text -- and there it agrees field for field, which is precisely what the knob exists to keep reachable. The differential harness is clean at these commits: 743 names, 99 intentional diffs, unexplained 0, no new rule needed in expected_changes.toml. Both #319 inputs land under the existing fix(cjk-comma-compound) rule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The three prose sites that stated the old limit as behavior all said the same thing: the peel presumes segments[1] is name text. It no longer presumes it -- it asks _vocab.is_wholly_suffix, segment's own suffix-comma predicate -- so all three say that instead. AGENTS.md names the predicate and keeps the paragraph's density. docs/release_log.rst EDITS the #312 entry rather than contradicting it (2.1.0 is unreleased) and adds the #319 entry after it. docs/usage.rst is rewritten at its own level of detail. Three facts the rewrites are careful about, all measured rather than assumed: * the peel is uniform across the three spellings; the PLACEMENT is not. 'PhD' -> title, 'V.' -> given, 'Ph. D.' -> suffix (as 'さん, Ph. D.'). "One answer" is true of the peel alone, and each site says so. * lenient_comma_suffixes=False is NOT a blanket freeze. It restores the pre-#319 reading for the initial-shaped suffixes ('V.', 'V', 'I') -- where the strict/lenient gap lives -- and not for 'Ph. D.', whose merged 'phd' passes the strict test, so that run is declined and the peel fires under the knob too. * the scan set changed for EVERY wholly-suffix post-comma run, not only the #319 inputs. '田中さん, PhD' and '田中さん, 様.' reach さん now by declining the run where they used to reach it by stepping over the credential. Same answer, different traversal. Then a sweep on that property rather than on phrasings: the peel was instrumented to record which runs it scans, run over the whole case table under five policies, and the flagged rows read against their notes. Everything Task 3 fixed checks out, and the two other flagged rows (ko_honorific_after_comma, ko_honorific_written_with_a_period) describe classify's work, not the traversal, so they are unaffected. The one stale thing the sweep did turn up is older than #319: test_the_peel_crosses_a_family_comma_and_stops_there claimed both its inputs pin the two-run slice. Measured by widening the slice to three, only '김, 민준씨, 박씨' does -- 'Jr.' is a post-nominal by the strict test as well, so a wider scan steps over it and peels correctly anyway. The comment had borrowed the strict/lenient argument from the test above it, where it belongs to 'V.'. Comment only; the assertions are right and unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The #319 decline read "this run is wholly suffix" as "this run is not name text", but is_wholly_suffix reaches period_joined_vocab, which calls an interior-period token a suffix when ANY chunk is suffix vocabulary -- and every honorific tail is a suffix WORD by the Lexicon invariant. So a run's only claim to being suffix-shaped can be the honorific glued to it, which is precisely what the peel exists to remove: "이, J.씨" declined, segments[0] held no tail to peel instead, and the given name went to suffix glued to its honorific. Decline only where segments[0] holds a peel site of its own, so declining can never cost the only site. "田中さん, V." keeps #319's answer (さん is right there to peel), and the two-honorific "김민준씨, J.씨" keeps it too -- both runs offer a site, so the person's own 씨 is peeled rather than the junk one behind the comma. The gate asks for that site with _peel_site, the peel's own scan-back lifted out and shared, rather than approximating it: a cheaper test for a token ENDING in a tail counts one that IS a tail entire, which the scan skips as a site, so "선생님, J.씨" would decline and then find nothing to cut. Measured over an 87,073-input sweep on an alphabet built from the routes into is_wholly_suffix (period_joined_vocab, splits_into_suffixes, the initial-shaped suffix words): 0 lost peels against the pre-#319 unconditional scan under the default policy, Policy(lenient_comma_suffixes=False) and the JA pack; the approximating gate loses 418 and 410 on the first two. Three case rows pin it, classified against 1.4.0 (worker_v1.py, nameparser.__version__ 1.4.0): 1.4 peels none of the three. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A mutation pass found the decline pinned exclusively by field-level
rows. Reverting the fix outright -- always segments[:2] under
FAMILY_COMMA -- failed six node ids, all of them case-table
parametrizations, and not one of test_script_segment.py's eight peel
tests, the file whose whole subject is which runs the peel scans. A
field-level pin passes for the wrong reason as soon as the traversal
changes, which is how three survivors got in.
- Declining only runs of <=2 tokens survives the whole suite: both
#319 rows have a 1- and a 2-token run.
- The peel handing is_wholly_suffix a policy with
extra_suffix_delimiters emptied survives, though the stage header
promises the field reaches here.
- Restricting the Ph./D. merge to position 0 survives, and it moves
plain Latin: "John Smith, Jr. Ph. D." flips SUFFIX_COMMA ->
FAMILY_COMMA, and "John Smith, MD, Jr. Ph. D." keeps every field
while gaining a spurious comma-structure ambiguity, so even a
field-level differential misses it.
Three stage tests state the traversal itself, in the peel section
alongside test_the_peel_crosses_a_family_comma_and_stops_there, which
was the standard: a wholly-suffix post-comma run is not scanned at
all; a THREE-token one is not either (the decline reads vocabulary,
not length); and a configured suffix delimiter reaches the peel
through both of its routes -- splits_into_suffixes for a no-whitespace
core inside one token ("PhD/MD"), plain core membership for a padded
one that tokenizes standalone ("Jr. - V.") -- each against the
bare-policy baseline that shows it is the delimiter doing the work.
Measured: the three fail under the wholesale revert, the last two
under the length gate, the delimiter one under the emptied policy.
The merge gets its own two, away from the peel: an is_wholly_suffix
assertion on ["Jr.", "Ph.", "D."] and the segment-stage structure of
"John Smith, Jr. Ph. D.". Two more assertions cover the routes into
is_wholly_suffix that until now died only in the v1 banks and the case
table -- period_joined_vocab and splits_into_suffixes -- so a break in
either is diagnosed where it happens.
Three case rows carry what belongs at field level, all classified
against 1.4.0 (worker_v1.py by script path, nameparser.__version__
reporting 1.4.0 from inside):
- "김민준씨, V." and "王先生, V.", the decline in the other two
scripts. Nothing in it reads a script -- it asks a vocabulary
predicate about the post-comma run and the peel's scan-back about
segments[0] -- but every witness the table had was written in
kana. 1.4.0 gives first "V." / last 김민준씨 and 王先生, peeling
neither: fix(#319) both.
- "田中さん, Ph. D." under Policy(lenient_comma_suffixes=False).
Two rows and a stage comment assert that the knob does not freeze
the decline wholesale -- the merged "phd" satisfies
is_suffix_strict, so this run is declined under either setting --
and nothing held it. Identical fields to its default-policy twin,
same fix(#319) against 1.4.0's first 田中さん / suffix "Ph. D.".
Four neighbouring notes counted the knob's exercises at two; there are
three. corpus_cjk.jsonl regenerates from the table and gains the two
new CJK texts; the harness stays clean at 748 names, 104 intentional
diffs, unexplained 0, both new inputs landing under the existing
fix(cjk-comma-compound) rule with no new entry in
expected_changes.toml.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four reviewers found prose on this branch claiming mechanisms the code does not have. Each claim below was re-measured before rewriting. The load-bearing error: two case-table notes said the #320 initial veto would reach is_wholly_suffix and abandon the peel on "田中さん, 様." the way it once abandoned it on the scan-back. It cannot. Under the row's default policy is_wholly_suffix selects is_suffix_lenient, whose contract is suffix_words accepted unconditionally, bypassing the veto, and _normalize("様.") is "様" -- a suffix word. Simulating the veto peels さん there rather than stranding it; the described mechanism holds only under Policy(lenient_comma_suffixes=False), which neither row sets. Commit 4aff219's message carries the same claim and cannot be amended, so the correction is recorded in the row. The release log's #320 entry made the same mistake in behavior terms: reverting #320 no longer strands さん in the comma form. Swapped its example to the spaced "田中さん 様.", which still does, and said what the comma form now depends on #320 for. 2.1.0 is unreleased, the same standard already applied to the #312 entry. The #319 entry described an unconditional decline; the decline has taken a second condition since 838de67. It now says so, names "이, J.씨" as what the condition protects and "김민준씨, J.씨" as where the decline still stands, stops calling the listed honorific "様." a credential, and scopes "every such input" to inputs whose run is actually declined. Its "no name that peeled before stops peeling" was verified rather than assumed: a 57k-input sweep against the pre-#319 tree over four policies finds 0 lost peels at HEAD and 68 at 60f1922, the commit before the site condition. Five more, each measured: - the stage header stated exactly the plural-of-_is_post_nominal equation that _vocab's docstring and test_is_wholly_suffix_is_not_the_plural_of_is_post_nominal warn against; it now names the disagreement instead - "田中さん, Jr./V." flips through splits_into_suffixes, not through a delimiter-core token: period_joined_vocab reads "Jr./V." as a title and "Jr./V." is not in cores. The bare-core route is "田中さん, /" - the peeled remainder is 田中 and lands in family under all three credential spellings; what differs is where the CREDENTIAL lands - the junk-tail hazard cited "Dr 김민준씨, Jr., 박씨", which is SUFFIX_COMMA with 박씨 in a third run and out of reach under either scope rule. "김민준씨, J.씨" is the input the guard really answers, and it agrees with the test comment that already got this right - a cases.py note cited a passage 559e95c deleted Plus two the flag reaches that the docs did not: customize.rst's lenient_comma_suffixes row now records that the same test decides whether the peel crosses a family comma, and _group.py notes that is_wholly_suffix has two callers to keep its Ph./D. merge in sync with. _segment.py's header said "every post-first segment" where only the second decides, and "lenient" where the knob can make it strict. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six follow-ups from the review of the previous commit and of 838de67. All prose; all the same species, a rule true in its context written as though true everywhere. Each re-measured before rewriting. "The two predicates disagree on exactly the initial-shaped suffix words" was false, and false in the sentence written to prevent this confusion -- the same sentence lists period_joined_vocab and the delimiter routes as the run predicate's own, which is where the rest of the disagreement comes from: V. shaped=True strict=False wholly=True Msc.Ed. shaped=False strict=False wholly=True J.씨 shaped=False strict=False wholly=True Not curiosities: "田中さん, Msc.Ed." and "田中さん, J.씨" both go family 田中さん -> family 田中 with さん in suffix, so they are #319 fixes this branch ships unmentioned. Corrected in all three places that carried the wording -- the stage header, the peel's inline comment, and AGENTS.md. AGENTS.md and docs/usage.rst described the decline as unconditional; both were last touched at 60f1922, one commit before 838de67 added the site condition. Measured, the test is necessary and not sufficient: 이, J.씨 wholly=True site0=False declined=False 선생님, J.씨 wholly=True site0=False declined=False 김민준씨, J.씨 wholly=True site0=True declined=True The worked outcomes in usage.rst do not move -- a name with no site before the comma has nothing to peel either way -- so it was the mechanism that was wrong, not the examples. customize.rst's lenient_comma_suffixes row, added by the previous commit, had the same sole-decider framing and now says one of two. "segment gives SUFFIX_COMMA whenever more than one word precedes the comma" (in _peel_site's docstring and a cases.py note) omits the other conjunct: "Dr 김민준, 지훈" has two words before the comma and is FAMILY_COMMA. The conclusion survives, because the gate is asked only where the second run is already suffix-shaped, so a FAMILY_COMMA that reaches it failed on the word count and segments[0] holds at most one token -- 0 violations over 2400 gate calls here, and the reviewer's 32,175. Both sites now state the implication with what the gate knows. _peel_site's "circular at THIS call site alone (segment asks it of a run nothing has peeled yet)" did not distinguish the call sites, since segment asks just as early. The distinction that holds is how the answer is spent: segment reads the run's shape and stops, the answer BEING the structure, while the peel reads it and then decides whether to strip the honorific that produced it. And _segment.py's header said a segment beyond the second that is not entirely suffix is flagged COMMA_STRUCTURE, where line 82 skips empties -- "John Smith, MD,, Jr." reports nothing, "John Smith, MD, Bart" does. The inline comment had the carve-out; only the header omitted it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A finer mutation round on the gate itself found three survivors of the
previous commit, on axes every existing input holds constant. Two
inputs close all three, both stage tests, both in the peel section.
- Keying the gate on "there is a second run" instead of on
FAMILY_COMMA survives (84 field divergences). The table already
states the intent it breaks -- ko_honorific_glued_given_suffix_
comma_initial says a SUFFIX comma keeps the whole name in
segments[0] -- but that row cannot see it: its segments[0] is
"Dr 김민준씨", which holds a site, so the gate answers the same
either way. The witness is its twin, two words before the comma
with NO site among them and a junk tail beyond it:
"John Smith, J.씨" peels 씨 off a stray post-nominal under the
mutant and must not.
- Reading segments[-1] rather than segments[1] survives, and so does
requiring exactly two segments before declining (864 divergences)
-- the direct sibling of the token-count gate closed in the last
commit, on the segment-count axis instead. Every #319 row and all
three new stage tests have exactly two segments, which is why it
stayed open. "田中さん, V., 太郎" fails identically under both:
the peel is silently lost, family 田中さん.
Measured, each introduced and watched to fail: the suffix-comma
witness gives ['John','Smith','J.','씨'] against ['John','Smith',
'J.씨'], and the three-segment witness ['田中さん','V.','太郎']
against ['田中','さん','V.','太郎'] under both of its mutants.
test_a_wholly_suffix_post_comma_run_is_not_scanned_at_all is renamed
to ..._run_after_a_family_comma_is_declined. Its body pins the
two-segment FAMILY_COMMA instance and all three survivors above passed
it, so the name asserted a universal the assertion did not hold -- the
same over-claiming this branch spent two commits correcting in prose,
this time in a test name. The two new tests hold the rest of the
universal; the name now says which instance it is.
Last commit's Latin Ph./D. pin stayed at the segment stage on the
grounds that a case row would need a new classification slug. That was
the wrong reason -- the table carries three ad-hoc slugs of the same
shape already -- so the row goes in:
suffix_comma_split_phd_after_another_suffix,
"John Smith, Jr. Ph. D.", fix(credential-pair-order). 1.4.0 gives
suffix "Ph. D., Jr." where 2.0 gives "Jr. Ph. D.": fix_phd extracted
the pair pre-parse and re-appended it, reordering the tail, where 2.0
renders it as written. It takes the V14 kill count from two node ids
to four.
That divergence turned out to be guarded nowhere. expected_changes.
toml deliberately files no rule for trailing "Ph. D." on the grounds
that the healing is parity and "if it ever starts diffing, the harness
must fail" -- but it does not: measured on a probe corpus, this input
comes out unexplained 0, absorbed by fix(comma-family), whose
name_regex is a bare comma and whose fields list contains suffix. The
comment there now scopes its parity claim to the two inputs it names
and records the hole. Not fixed in passing, and the note says why:
classify() takes the FIRST matching rule and the sort has only two
tiers, so a narrower rule could win only by file position -- making
file order load-bearing again, which the sort exists to prevent.
Giving classify() a real specificity order is a harness change, not a
rule change.
Gates: 2936 passed, 19 skipped, 11 xfailed; ruff and mypy clean;
sphinx html and doctest builds clean, README doctest clean; the
differential unchanged at 748 names, 104 intentional diffs,
unexplained 0 (corpus_cjk.jsonl regenerates identical -- the two new
witnesses are stage tests and the case row is Latin). All seven
mutants of this branch verified killed against the final tree.
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 #327 +/- ##
=======================================
Coverage 98.47% 98.47%
=======================================
Files 41 41
Lines 2811 2823 +12
=======================================
+ Hits 2768 2780 +12
Misses 43 43 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The lone-token argument -- why segments[0] holds at most one token where the gate asks, and why the word count alone does not say so -- was written out at full length twice, in _peel_site's docstring and in the 선생님 row's note, counterexample and all. Keep it at the definition site: someone changing the gate or the scan lands there, and the bound is a precondition on the code they are touching. The row keeps what it pins and why it is one token before the comma, and points at the derivation instead of repeating it. 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 #319.
The bug
Same credential, three spellings, two answers:
#312 scoped the peel's site to
segments[:2]under a family comma, on the premise thatsegments[1]is name text.segment()does not guarantee it:SUFFIX_COMMAneedssuffixy(groups[1]) and len(groups[0]) > 1, so a one-word part before the comma falls through toFAMILY_COMMAeven when the part after it is entirely suffix-shaped. The peel walked into a run of post-nominals, tookV.for its site, found no listed tail there and abandoned — silently.The fix
Stop re-deriving suffix-ness token by token and ask
segment()'s own question.suffixyis lifted into_vocab.is_wholly_suffix(texts, lexicon, policy)so the two stages cannot drift, and the peel declines a wholly-suffix second run.With a second condition, which is not decoration. Every honorific tail is also a suffix word (
Lexiconenforces it), so a glued honorific is itself part of what makes its run read as suffix-shaped — the predicate is circular at this call site.segments[0]must therefore offer a peel site of its own before the second run is declined, established by a_peel_sitehelper the gate and the peel both call, so the gate asks the peel's own question rather than an approximation of it.Without that condition
이, J.씨loses its given name entirely. See below.Note on the name
is_wholly_suffixis not the plural of_is_post_nominal. That one asksis_suffix_strictof a single token; this asks the policy-selected predicate of a run, plusperiod_joined_vocab, delimiter transparency and thePh./D.merge.V.is a suffix run and is not a post-nominal — and confusing the two is the #319 defect.is_wholly_suffixechoes the housewhole=Trueidiom, which already carries the non-empty rule;all_suffixeswould have promised the opposite, since Python'sall([])is True.What the review round changed
Four reviewers went over the branch after the first six commits. The last four commits are the result, and the first of them fixes a regression the original change introduced.
838de67— a name lost its given name. With the decline unconditional,period_joined_vocabcounted a glued honorific as evidence its run was suffix-shaped:265 lost peels across policies, 104 under default, reachable with no configuration and visible through
HumanName.lenient_comma_suffixes=Falsedid not gate it. Fixed by the site condition above.a795c49anda3ae04f— six mutation survivors. A wholesale revert of the fix originally killed only two case rows; the eight-test peel section intest_script_segment.pywas untouched by it. The decline gate had three independent unpinned axes — run length, segment index, segment count — plusextra_suffix_delimitersreaching the peel (documented, untested) and thePh./D.merge past position 0 (which silently flips a Latin input's structure). All now pinned at the stage layer, each verified to fail its mutant.60f1922,346b6d9,1226ffd— prose. Sixteen findings, in four distinct staleness shapes: claims about a traversal that changed; a contract in a module header; a mechanism attributed to the wrong cause; and exhaustiveness claims (exactly,whenever,declined) stated more strongly than they hold.Verification
-Wclean, doctests cleanunexplained: 0zhandkoequivalents pinned alongside the Japanese ones; the fix is script-agnosticKnown, not addressed
expected_changes.tomlpromises that trailingPh. D.healing is parity and that "if it ever starts diffing, the harness must fail" — it does not.fix(comma-family)'sname_regexis just",", so a suffix-only diff on a comma-bearing name is absorbed. The divergence (1.4.0"Ph. D., Jr."vs 2.x"Jr. Ph. D.") is now pinned by a case row instead. Narrowing the rule would needclassify()to have a real specificity order — today it returns the first match across two sort tiers, so a narrower rule could only win by file position, reintroducing the load-bearing file order that sort exists to prevent. That is a harness change and wants its own decision.🤖 Generated with Claude Code