Skip to content

The title-run bundle: a run addresses by its last title (#489), a trailing period-marked title is a title (#316) - #520

Merged
derek73 merged 12 commits into
masterfrom
claude/489-title-run
Sep 10, 2026
Merged

The title-run bundle: a run addresses by its last title (#489), a trailing period-marked title is a title (#316)#520
derek73 merged 12 commits into
masterfrom
claude/489-title-run

Conversation

@derek73

@derek73 derek73 commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Four measured changes to how a title run is read, in one bisectable order, then the docs and the ledgers.

1. The leading title peel leaves a name word a suffix cannot be. Dr King Jr reads title Dr, family King, suffix Jr — what v1 wanted, what rules.md#S2's descriptive note predicted, and what the comma form Dr. King, Jr. has always given. The peel's only floor was "leave one piece"; it now also refuses to leave a word that is nothing but suffix vocabulary standing as the name, giving back its last title word when that word is one word and a name candidate. MD DDS, Jr. Ph. D., Marquess of Bath and Prince of Wales Jr are untouched. One corpus name moves; Dr. King MD and Dr Jr move with it. Two residuals recorded in decisions.md#H3: a run ending in a dual-vocabulary word (Dr King MD PhD) and the bare-numeral fork (Dr King V).

2. A title run addresses by its LAST title (#489). Her Majesty Queen Elizabeth reads given Elizabeth; so do Reverend Mother Teresa and Dr. Sir John. The lookup was keyed on the whole run joined, and the shipped vocabulary has no multi-word entry, so no two-word run could ever be exempted from the fold. Both sites that ask — H1's retag and P5's join licence — move together through one predicate in _lexicon.py, so the 2026-08-22 #369 invariant holds; only its 'mr sir' example flips. The test is the whole run's key OR the last word's key, so a caller's phrase entry (lt col) still matches. Sir John Smith, His Excellency Lord Duncan and Her Royal Highness Princess Anne do not move; prince/princess/lord/lady are deliberately still not given-name titles (#519).

3. A trailing run of period-marked title words is a title (#316a, new rule H5). John Smith Prof. reads title Prof., family Smith, and the two comma paths agree at last. A RUN mirroring H3 (John Smith Prof. Dr. → title Prof. Dr.; Dr. John Smith Prof.Dr. Prof. in input order), TRANSPARENT to the suffix peel: X Prof. Y reads as X Y reads plus the title, so John Smith Jr. Prof. keeps suffix Jr. and John Prof. MA keeps S2's bare-acronym reserve. Where the chain leaves one name word, H1 decides its field, and H1 now reaches a run standing BEHIND the word (Smith Sir. → given Smith). The family-comma segment path gets the same walk (Smith, John Prof.). The doctrine, now stated in rules.md's H Background: the LEADING slot has a shape rule that outranks vocabulary (H2 unchanged — Esq. Smith is still a title); the TRAILING slot has no shape rule and reads vocabulary only, so John Smith Xyz. keeps its family name, a BARE trailing title word is a name word (Mary Jane King), and a period-marked surname-collision word at the back is an accepted cost under the input-is-a-name premise (Mary Jane King. → title King.). No ambiguity is reported. Five corpus names move at the fix, including one the drafting did not expect: Andrew Perkins (Mgr.).

4. esq leaves SUFFIX_ACRONYMS. A behavior change. John Smith E.S.Q. stops reading a suffix; Esq, Esq. and Esquire are unchanged through the word set. Esquire is a contraction rather than an initialism, the entry came in with the 2019 bulk import (#93), and the multi-dot spelling was its only unique coverage. What it buys is SUFFIX_ACRONYMS ∩ SUFFIX_WORDS == ∅, asserted at import, retiring two comment blocks, a gotcha and a case row.

Review rounds (two commits after the six). /pr-review-toolkit found one real defect and its root: P5's bound-given reserve counted a trailing title piece as a name word to spare, and H1 read the leading and trailing title runs as ONE run, so Sir John Prof. folded John to the family while Sir John kept it given. Both fixed: the reserve subtracts the H5 run, and H1 addresses by the run standing BEFORE the name word — the first name word, not the first non-title token, since a nickname in front must not decide (Dr. 'Smitty' Sir John); a run behind the word decides only when none stands before it (Smith Sir. → given, Dr. Smith Sir. → family). P5 already read the leading run, so the two sites agree again. Also: the W4 script-order interaction pinned (毛 泽东 Dr. keeps family 毛), the one-word gate on the trailing walk pinned (John Smith Prof. and Dr.), H5's particle-chain and maiden-run boundaries recorded, esq pinned on the comma path, and the stale counts swept. One pre-existing gap recorded as a parity row, not fixed: after a family comma the bound join fires over a trailing title (Berg, abdul Prof. → given abdul Prof.).

/simplify round (one commit). Assign and the P5 reserve now share one tail reading in _pieces.py: peel, chain the trailing titles, splice, re-peel, to a fixed point. The reserve had modelled assign's splice-and-re-peel as "peel then subtract", which differed exactly at S2's bare-acronym reserve (abdul rahman MA Prof. joined where abdul rahman MA did not), and the fixed point closes John Prof. MA Prof.. The wrapper's frame is paid for by _effective_order taking indices instead of building a list. Mechanical: trailing_titles returns the kept count, one "previous kept piece" helper in the comma walk, the first pass reused when the walk took nothing (−7 frames on Smith, John Quincy), a redundant import-time assert dropped, duplicated comment blocks folded, two case rows that pinned already-pinned forks dropped. That commit's frame trade was 3.11-only (a list comprehension; PEP 709 inlines them from 3.12), so CI's 3.12-3.15 jobs sat one call over the band; the follow-up commit moves the nickname scan, a generator resumed once per token, into the one branch that reads it, which brings every interpreter back to its recorded baseline (3.11 409/446, 3.12 388/425, 3.14 406/443).

Measured. Eight corpus names move against a0b93f0 out of 1123, 23 on the grown corpus once the rules.md examples enter corpus_rules.jsonl. Gate: 392 / 320 / 234 / 97 intentional, unexplained: 0, radar unclassified: 0 at all four baselines. Frame budget: at or one under the recorded baseline on every interpreter; two inline gates written to match the walk's predicate were removed in review (mechanisms.md#ONE-PREDICATE-PER-QUESTION) and the walk's one frame is paid for by dropping a dead list comprehension, because master's facade count already sits at the band's in-session ceiling. Every case row classified against the 1.4.0 wheel. Six review rounds (two per code commit, one on esq, the design-docs reviewer on the docs) folded in by amendment, then the two PR review-round commits above.

Out of scope by choice. #316(b), the bare-safe subset (Smith Dr) — it needs #348's census, and the corpora show the bare trailing dr swallowed by P2's particle chain, a different mechanism. #348, #490 and #514 likewise. prince/princess/lord/lady as given-name titles is #519.

Closes #489
Closes #316

🤖 Generated with Claude Code

derek73 and others added 6 commits September 8, 2026 22:10
…t be

The leading peel runs before the trailing suffix peel and its only
floor was "leave one piece", so `Dr King Jr` peeled `Dr King` and left
`Jr` to be the name -- title 'Dr King', family 'Jr', no suffix. Derek's
question put it plainly: Jr is a recognized suffix, so it could not
count as a following name.

leading_titles gains a second floor. Where everything behind the run is
suffix pieces and the run's last word is not itself one, the run gives
that word back. The word given back must be a name CANDIDATE, which is
what leaves the all-suffix inputs alone -- `MD DDS` and `Jr. Ph. D.`
are unchanged because `md` and `jr` are suffix vocabulary -- and the
all-title ones too, there being no rest for the floor to read.

It lands in the leaf rather than in assign because leading_titles is
the one predicate for the leading run
(mechanisms.md#ONE-PREDICATE-PER-QUESTION): assign sets the roles and
group's chain reads the same count.

`Dr King Jr` now reads title 'Dr', family 'King', suffix 'Jr' -- what
v1 wanted, what rules.md#S2's descriptive note predicted, and what the
comma form `King, Dr Jr` has always given. `Dr. King MD` moves the same
way. Both report `title-or-name`, `king` being title vocabulary and now
being the word left standing. One corpus name moves, measured over all
1123.

Accepted edges: `Dr Jr` reads given 'Dr', suffix 'Jr' and `Sir Jr`
given 'Sir', suffix 'Jr' -- with the title given back there is no title
left to make either reading H1's.

Two things the measurement forced, beyond the shape above. The floor's
gate is an inline tag read rather than is_suffix_piece: leading_titles
runs four times per parse, and asking the predicate first cost 8 frames
against a band with room for two, so the two tag tests -- the cheapest
NECESSARY condition for the next piece to be a suffix piece at all --
keep the ordinary titled name out of the branch entirely. Frame delta
is now zero on both entry points. And under test_parser's overlap
lexicon `Dr. Do Jr.` moves the same way `Dr. King MD` does, so the
no-op-prefix-chain test names the fork it is about (PARTICLE_OR_GIVEN)
instead of asserting silence: the report it now sees is H4's, about the
word the floor left standing.

Review round. The give-back is gated to a ONE-WORD piece: without it
`Prince of Wales Jr` gave up its title entirely (given `Prince of
Wales`, no title), where 1.4.0 and the pre-commit tree both read title
`Prince of Wales`, family `Jr` -- a new parity row,
`title_run_floor_keeps_a_joined_title_unit`, and a leaf test pin it,
and the frame count is unmoved at 416 parse / 453 facade on 3.11. The
docstring and the comment now say "its last piece, when that piece is
one word" rather than "its last word", claim only that the piece given
back is a name candidate, and name two residuals: a run whose last
word IS suffix vocabulary is not given back (`Dr King MD PhD` keeps
title `Dr King MD`), and the floor asks is_suffix_piece, which vetoes
a bare initial-shaped numeral, so `Dr King V` still reads family `V`.
The 8-call history is marked as measured on the plan's ordering, the
committed shape having since hoisted the pieces[n-1] test into the
guard. Three test notes were wrong: the `Sir Jr` row is renamed
`title_run_floor_gives_back_a_given_name_title_run` and says it
carries rules.md#S2's provenance rather than a second mechanism, the
`MD DDS` row no longer calls itself the bare-suffix carve-out (that is
`DDS MD`; `MD DDS` reads title `MD` plus H1's fold and reports
nothing), and test_king drops its duplicated "v1 aspired" sentence for
what `king` being title vocabulary still buys. test_parser's no-op
chain test pins full silence on five rows and the exact
`TITLE_OR_NAME` tuple on `Dr. Do Jr.` -- superseding the paragraph
above, which describes the filter-one-kind-out shape it replaced.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`Her Majesty Queen Elizabeth` read family 'Elizabeth' because the
given-name-title lookup was keyed on the whole run -- 'her majesty
queen' is no shipped entry. A run is written as several titles and
addresses the way its final one does, so the run's folded key is now
matched whole OR by its last word.

The whole-run ARM is what keeps a caller's multi-word phrase entry
working: `lt col` is stored and matched as one key. Its order carries
nothing -- the `or` short-circuits but decides nothing, since for a
one-word run the key IS its last word. Every shipped given-name title
is a single word, now asserted in test_lexicon.py, so that arm is dead
for the default set: it can only ever match a one-word run, which the
last-word arm reads the same way.

Both sites move together, through one predicate in _lexicon beside
_title_key, because the 2026-08-22 #369 decision rests on H1 and P5's
licence never reading one run two ways. That invariant holds under
last-word keying, and 'mr sir' -- #369's own example of a run neither
site treats as a given-name title -- now IS one to both. The leaf test
that pinned the old reading of that example, test_group's
test_the_title_run_is_one_key_as_h1_reads_it, is renamed
test_the_title_run_is_read_as_h1_reads_it and pins the invariant rather
than the key's shape: 'mr sir abdul rahman' now joins.

`Reverend Mother Teresa`, `Dr. Sir John`, `Mr Sir John` and `Xyz. Sir
John` move with it, and through P5's licence `Sir Sheikh abdul rahman`
reads given 'abdul rahman' with no family. What does not move: `His
Excellency Lord Duncan` and `Her Royal Highness Princess Anne` --
`lord` and `princess` are not given-name titles, and whether they
should be is a vocabulary question with its own frequency argument,
deliberately left alone. One corpus name moves, measured over all 1123.

The v1 suite's #489 xfail is dropped; xfail_strict would fail on the
XPASS otherwise.

Frame delta is zero on both entry points (416 parse / 453 facade on
3.11, unmoved): each site's read sits inside the branch it serves, and
the reference name `Dr. Juan0000 de la Vega III` enters neither -- H1's
guard wants an unoccupied family, P5's a bound given-name word.

Review round. The predicate is one fold and one pair of lookups over
that key, no list and no empty-run guard, measured unmoved on both
counts. The docstrings drop the ordering language and say what the two
arms reach instead: the last word is the last word of the FOLDED key,
so a run token that folds away (the lone '.' the conjunction merge can
leave, `Sir and . John`) cannot empty that arm; and an unlisted
abbreviation can never MATCH as a last-word key, but may sit inside a
caller's phrase entry, since given_name_titles is deliberately not
validated against `titles`. _title_key's own claim that a lone '.' is
not a title token was false and now says the fold drops it. post_rules'
two-level `if` collapses to one condition and cites decisions.md#P5,
where the #369 entry lives; group's comment is re-wrapped;
test_lexicon's phrase-entry test folds into the per-word fold test it
duplicated; and test_group's conjunction test is renamed for the
mutation it catches, keyed over every token of the piece.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`John Smith Prof.` read family 'Prof.' and lost the surname, while
`Smith, Prof.` read title 'Prof.' -- the two comma paths disagreed, and
#316 is the question. They agree now: after the trailing suffix peel
has taken its run, successive single words that wear the
period-abbreviation shape AND are title vocabulary chain into the title
from the end, leaving one name word standing.

A RUN, mirroring H3, so `John Smith Prof. Dr.` reads title 'Prof. Dr.'
rather than making `Prof.` a name word. The title view joins TITLE
tokens in token order, so `Dr. John Smith Prof.` reads title 'Dr.
Prof.' with no rendering change.

The doctrine, stated rather than left to be inferred: the LEADING slot
has a shape rule that outranks vocabulary (H2 -- `Esq. Smith` is a
title though `esq` is post-nominal vocabulary, and that is unchanged);
the TRAILING slot has no shape rule and reads vocabulary only. So
`John Smith Xyz.` keeps family 'Xyz.', and a BARE trailing title word
is a name word -- `John Smith Sir` and `Mary Jane King` are untouched,
TITLES holding ordinary surnames being exactly why.

The walk uses is_title_piece, never is_leading_title: that predicate
carries H2's inference and would take `Xyz.`. No ambiguity is reported:
under the input-is-a-name premise a period-marked title word is not a
fork a reader would hesitate over.

Two decisions taken with the spec's questions open. A trailing title is
TRANSPARENT to the suffix peel -- `X Prof. Y` reads exactly as `X Y`
reads, plus the title. The peel ahead of the walk is PROVISIONAL: all
it settles is where the walk starts, because the walk can remove the
very word that stopped it. So the pieces the walk takes are spliced
out and ONE peel then runs over what stands, in original order, and
that peel alone places a piece or reports a fork. `John Smith Jr.
Prof.` reads suffix 'Jr.' where a single pass promoted a generational
suffix to the family name; `John Prof. MA` reads the family 'MA' that
`John MA` reads, S2's reserve keeping a bare ambiguous acronym the
family of a two-word name, where a second peel laid over a first read
family 'John', suffix 'MA'; and `John Smith V Prof. VI` reads what
`John Smith V VI` reads -- middle 'Smith V', family 'VI', nothing
reported -- where two peels each reporting their own last piece read
suffix 'V VI' and reported the numeral fork twice. And the
family-comma segment-1 walk ships: the comma gate does NOT route
`Smith, John Prof.`, which read middle 'Prof.' at every baseline --
the `Smith, Dr.` family of rows that already route go through the
no-name gate, a different mechanism. The same principle governs its
candidates, which are the pieces that segment does not read as a
SUFFIX, asked of the segment's own predicate rather than of a copy of
half of it: `Smith, John Prof. V` is `Smith, John V` plus a title
because the #144 lenient tail claims the numeral, and `Smith, John V
Prof.` is too, because the walk moved where this segment's name ends
and the lenient test follows it. Both are corpus-neutral, measured.

Five corpus names move, measured over all 1123 -- the four `John Smith
<Title>.` rows the issue planted, and `Andrew Perkins (Mgr.)`, which
measurement found: its trailing period keeps the parenthetical out of
nickname parsing and `mgr` is title vocabulary.

One PINNED row moves that is not a corpus name, and is named here
rather than left to be found: `Smith, PSM Dr. I`
(family_comma_title_resets_the_credential_run) reads title 'Dr.' where
it read middle 'Dr.', which is `Smith, PSM I` plus a title. The reset
that row exists to pin still fires and the row still fails without it.
Its parity claim went with the move and had outlived the audit anyway
-- 1.4.0 reads suffix 'Dr., I', `dr` still being post-nominal
vocabulary before #296.

Two consequences worth naming. `titled` in the lone-name-word emitter
is now true for a name whose only title is a trailing one, so `Smith
Prof.` reports nothing where an untitled lone name word reports
`given-or-family` -- H1 decides the field, the same answer `Dr. Smith`
gets. And H4's JOIN clause gains one reach: `John of Prince Prof.`
reports `title-or-name` because the walk left the unit `John of Prince`
standing alone, which is what that unit already reports on its own.
H4's single-word half cannot be reached this way -- a lone
title-vocabulary word in front of a trailing title is taken by the
LEADING run first, so `King Prof.` still reads title 'King'.

Neither call site is gated. The question is asked by its predicate and
by nothing else (mechanisms.md#ONE-PREDICATE-PER-QUESTION), so the
walk's floor and its vocabulary read are the only things that answer
it and a mutation removing either is visible. That costs the reference
name one frame on each entry point -- inside the plan's design target
of two -- and one frame comes back beside it: the group-flagged suffix
scan collected a list it never read again, and a comprehension is a
frame of its own on 3.11. Measured delta is 0 on both entry points,
cold (416 parse / 453 facade on 3.11) and in a full pytest session
(416 / 455). The give-back is not optional: 455 of the 455.94 allowed
facade calls is what MASTER already costs in a full session, so the
walk's frame alone measured 456 and failed the band -- the 453/454
pair a module-only run reports is not the number the test sees.

Review round: both inline necessary conditions are gone, the walk is
called directly at both sites, the mixed-provenance `Peel._replace`
and the double numeral report with it, and `_report_numeral` is
inlined back at the one site left. Six rows pin the transparency
readings and each fails under its own mutation of the two peels or of
the comma candidates; the seven-name corpus sweep against a0b93f0 is
unchanged by any of it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… are asserted disjoint

Esquire is a contraction, not an initialism. The SUFFIX_ACRONYMS entry
arrived in the 2019 bulk Wikipedia post-nominal import (af5bdab, #93)
and was never reviewed; its only unique coverage is the spelling
"E.S.Q.", which nobody writes. Same criterion as the rai/cha decision:
does the entry describe the WORD or the machinery.

`John Smith E.S.Q.` now reads family 'E.S.Q.' where every release since
1.4.0 read suffix. `Esq` and `Esq.` are untouched -- the SUFFIX_WORDS
membership carries them, and it stops being inert -- and so are `Smith,
Esq.` and `Esq. Smith`. One corpus name moves.

What that buys is an invariant: SUFFIX_ACRONYMS and SUFFIX_WORDS are
now asserted disjoint at import. The two sets normalize differently, so
a word in both is matched by two rules and which one fired is
unreadable from outside. 'esq' was the only overlap and the reason the
assert could not exist; the two comment blocks defending it, the
AGENTS.md gotcha and the `deliberately not asserted disjoint` note all
retire with it.

A behavior change, not a fix: a 2.x parity break, classified on all
four ledgers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
 recorded

rules.md gains H5, the trailing title run: after the suffix peel,
successive single period-marked title-vocabulary words chain into the
title from the end, floor one name piece, and the title is TRANSPARENT
to the suffix reading. The H Background states the doctrine the two
slots split on — shape at the front, vocabulary at the back, a bare
title word a name word because TITLES holds surnames. H1 gains the run
clause (a run addresses as its last title does), H3 the peel floor,
H4's Accepted clause is rewritten now that `Dr. King MD` reports, P5's
"one key" sentence and S2's descriptive note are replaced by the
argument each asked for, and the code citations are upgraded to
verbatim excerpts.

decisions.md gains `### H3` and `### H5` and amends five entries: H1
(the last-word keying, #489), P5's 2026-08-22 #369 entry ('mr sir' IS
a given-name-title run to both sites now, the invariant kept), H2's
`Open: #316` hook closed, H4's #491 population corrected from six to
ten with seven contract and three radar, the trailing-position
prohibition scoped to BARE words, the esq Excluded block replaced by
the drop and its criterion, and the v1-xfail triage's `Dr King Jr`
bullet moved from NOT FIXED to FIXED with #489 out of FIX CANDIDATES.
Each measured population is scoped to the tree it was taken on, with
today's figure beside it, since this commit's own examples grow the
corpus.

mechanisms.md re-checks TWO-LAYER-ASSIGN's one named exception against
H5 (still one) and adds `trailing_titles` to ONE-PREDICATE-PER-
QUESTION's census. release_log.rst gains three fix bullets and the esq
behavior change. AGENTS.md's "runs in one direction only" line becomes
the doctrine, the esq gotcha retires with the assert that replaced it,
and 694 → 746, measured. usage.rst and customize.rst follow.

corpus_rules.jsonl regenerated: 23 rows, 15 new names.

Design-docs review of this commit (2026-09-09), six findings folded in
by fixup. decisions.md#P5's trailing-position bullet RETRACTS half its
own argument: a period-marked trailing word is not one nobody writes as
a surname, and the collision it was said to prevent is simply accepted
there -- `Mary Jane King.` reads title 'King.', given 'Mary', family
'Jane', `John Smith Judge.` reads title 'Judge.', and 627 of the 746
titles in no suffix set read as a trailing title once a period is
written behind them, the 119 that do not being held out by the
abbreviation SHAPE alone (a digit, a hyphen, an apostrophe, a script
with combining marks), no plain ASCII-letter title missing. rules.md#H5
carries the cost as an Accepted clause with `Mary Jane King.` as its
executable example.

H5's transparency sentence is SCOPED: it claims what it claims where
two or more name words stand, and where the chain leaves ONE, H1
decides that word's field -- `Smith Prof.` reads family 'Smith' where
`Smith` alone reads given 'Smith' and reports given-or-family.
`_assign.py`'s verbatim excerpt follows the new wording.

H1 gains the TRAILING REACH it always had in the code: a title run
standing BEHIND the one name word decides its field the same way, so
`Smith Sir.` and `Smith Queen.` read given 'Smith' while `Smith Dr.`
reads family. `"Smith Sir." -> given="Smith"` is an example line, H1
and H5 cross-link in `interacts:`, and no code moved for it.

Three counts corrected. release_log's "Ten names ... gain a report" is
EIGHT distinct names in ten corpus rows, measured at a0b93f0, and
decisions.md#H4's "these ten names" says eight, twelve after this
bundle, with the recompute beside it, so the entry's two TENs stop
naming two quantities. decisions.md#P5's renamed group test pins P5's
side alone, the two-site agreement resting on the shared
`_run_addresses_by_given` and on
test_parser.py::test_the_p5_licence_and_h1_read_a_title_run_the_same_way
rather than on that leaf. And #H5's A2 "moves NO corpus name" is scoped
to the fix: `Smith, John Prof.` enters the corpus here, and the rule's
population is FIVE at the fix and FOURTEEN here, re-measured with the
walk stubbed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Twenty-one corpus names move: the eight the parser changes reach, plus
the thirteen the rules-doc corpus gained when Task 5 regenerated it
from rules.md's own examples -- eleven at first and two more from the
2026-09-09 design-docs review of that commit, `Mary Jane King.` and
`Smith Sir.`.

Before, on this branch with the corpora already regenerated:

  corpus: 1135 names; intentional diffs: 367; unexplained: 16; radar unclassified: 5
  corpus: 1142 names; intentional diffs: 297; unexplained: 16; radar unclassified: 5
  corpus: 1142 names; intentional diffs: 211; unexplained: 16; radar unclassified: 5
  corpus: 1142 names; intentional diffs:  74; unexplained: 16; radar unclassified: 5

After:

  corpus: 1135 names; intentional diffs: 388; unexplained: 0; radar unclassified: 0; 283 of 388 changed names are Latin-only
  corpus: 1142 names; intentional diffs: 318; unexplained: 0; radar unclassified: 0; 218 of 318 changed names are Latin-only
  corpus: 1142 names; intentional diffs: 232; unexplained: 0; radar unclassified: 0; 218 of 232 changed names are Latin-only
  corpus: 1142 names; intentional diffs:  95; unexplained: 0; radar unclassified: 0;  85 of  95 changed names are Latin-only

Exit 0 at all four. Every baseline rises by the same 21, which is the
before-run's unexplained plus its radar unclassified: at the older
baselines the names do not arrive, they move from unclassified to
explained. 'John Smith Dr.' is the one that changed RULES rather than
arriving there, which the fix(#296) paragraph below records.

FOUR rules, one per argument, in all four ledgers and last in each
file. Anchored alternations of names, so the reach IS the mover list:

  fix(#489) a title run addresses by its last title           2 names
  change(suffix-acronym-collisions) esq leaves the acronym set 1 name
  fix(#489) the title peel leaves a name word a suffix cannot be
                                                              4 names
  fix(#316) a trailing period-marked title word reads as a title
                                                             14 names

Last in the file is narrow-first rather than a preference: every rule
already present that REACHES one of the nineteen declares a strict
subset of the bundle rule claiming it, and a wide rule ahead of a
narrower one it shares a name with is an order-decided contest the run
refuses (#382). Measured: at 1.4.0 fix(#296)'s trailing `dr` reaches
two of the trailing-title names, the two comma routings reach 'Smith,
John Prof.', and the two-token `jr` rule reaches two of the floor's
four; at 2.0.0 and 2.1.0 only fix(#296) reaches any; at 2.2.0 none
does. None of them ADMITS the diff it reaches, so no rule lost a name
it was explaining and no [[change.precedes_narrower]] block was needed
for the bundle itself.

`_initials` is in none of the four field lists. The plan predicted it
on six of the eight movers; the run puts it on none, because the
derived view enters a diff only where every role and ambiguity kind
agrees, and all nineteen move roles. validate_rules refuses it beside
another field in any case.

THE fix(#296) `dr` RULE, reconciled by widening nothing. 'John Smith
Dr.' moves {title, suffix} at the three older baselines and {title,
middle, family} at 2.2.0, neither a subset of that rule's fields, so
it falls through to the #316 rule below it -- narrow-first placement
means the DIFF decides the handover, not file order. The rule's
closing sentence, which said no trailing title-only word routes to
title on the no-comma path and called #316 the open question for the
class, is retracted in the same edit; the bare spelling ('Smith Dr',
the 'dr ... dr' names) stays with it, #316(b) being out of scope.

The gate then called that rule OVER-DECLARED: with 'John Smith Dr.'
gone, none of the three names it still explains moves a middle name,
so `fields` narrows {middle, family, suffix} -> {family, suffix}. That
narrowing made it a strict subset of the glued-CJK-honorific rule's
three roles in the 2.0.0 ledger, where both regexes reach '田中さん,
Dr.' -- a name that produces no diff at that baseline at all. The
honorific rule keeps its place with a [[change.precedes_narrower]]
block declaring the LATENT pair: it describes the compound the dr rule
knows nothing about.

ROSTERS in tests/v2/test_ledger_guards.py, every number taken from
`_claim` rather than typed. _CORPUS_CLAIMS gains the four rules per
ledger (2 / 1 / 4 / 14, identical digests across the four files, only
the floor rule's roles differing -- `_ambiguities` at the 2.x
baselines, absent at 1.4.0). Five existing claims moved with the
corpus and each says which name arrived: fix(#296) `dr` 11 -> 12
('John Smith Prof. Dr.'), the two comma routings 288 -> 289 ('Smith,
John Prof.'), the two-token `jr` rule 5 -> 7 ('Dr Jr', 'Sir Jr') and
the connective-run initials rule 96 -> 97 ('Prince of Wales Jr').
_NOT_A_VOCABULARY_COPY gains three alternations, _MUST_NOT_MATCH four
keys with eighteen probes, and _ORDER_EXEMPTION_EFFECT its first
2.0.0 row.

The last two members of the #316 rule, 'Mary Jane King.' and 'Smith
Sir.', arrived with the 2026-09-09 review of the docs commit. Both are
new rules.md examples and both diff identically at all four baselines.
'Smith Sir.' sits HERE rather than on the fix(#489) run rule because
the DIFF decides: it moves {title, family}, the walk taking 'Sir.' out
of the name, while its given/family reading is what every baseline
already gave -- and the #489 rule declares {family, given}, which does
not reach a title role at all. Neither takes a _WATCHED_DIFFS row: a
rules.md example line is a test literal, executed by test_rules_doc.py,
so neither is that roster's population shape.

The #316 rule spells 'Andrew Perkins (Mgr.)' with \x28 and \x29. A
literal parenthesis inside an alternation body, escaped or not, hides
the whole group from test_ledger_guards._alternations, and a
twelve-member alternation should not be invisible to the pass that
demands every alternation declare what it copies.

_WATCHED_DIFFS: the two rows this bundle could have moved, 'Smith,
Prof.' at 1.4.0 and 'Esq. van Gogh' at 1.4.0 and 2.0.0, are unchanged
and no run reports a moved shape. None of the nineteen sits at a shape
two rules could admit, so none belongs there on that ground. One does
belong on the roster's own POPULATION clause: 'John Smith Rev.' is the
only one of the nineteen named by no test literal anywhere, so a
ledger rule is its only watcher, and it takes a row at each baseline.
That addition moves the row counts and nothing else -- the population
paragraph was not re-derived.

Full suite: 7536 passed, 177 skipped, 4 xfailed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@derek73 derek73 added bug enhancement docs Documentation fixes and updates by-design labels Sep 9, 2026
@derek73 derek73 self-assigned this Sep 9, 2026
@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.76543% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 98.67%. Comparing base (a0b93f0) to head (ed8ef12).

Files with missing lines Patch % Lines
nameparser/_lexicon.py 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #520      +/-   ##
==========================================
- Coverage   98.68%   98.67%   -0.02%     
==========================================
  Files          45       45              
  Lines        3262     3309      +47     
==========================================
+ Hits         3219     3265      +46     
- Misses         43       44       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@derek73 derek73 added this to the v2.3 milestone Sep 9, 2026
derek73 and others added 6 commits September 9, 2026 14:57
P5's bound-given reserve counted a trailing period-marked title word
as a name word to spare, so `Prof. abdul rahman Prof.` joined the
bound pair and read family 'abdul rahman' where `Prof. abdul rahman`
reads given 'abdul', family 'rahman'. The reserve now subtracts the
H5 run from both views -- assign's second peel runs over the pieces
that walk LEFT, so the reserve has to read the same list -- and the
same-suffix comparison is read over the shortened lists too, which is
what stops `Sir abdul Prof.` joining a title word into the given name.
No corpus name moves: the 21-mover list against a0b93f0 is unchanged.
Frames are unchanged at 416 parse / 453 facade on py3.11; the
reference name never enters the reserve branch, having no bound given
word, so its two new calls cost nothing.

rules.md#P5 names the H5 walk in the sentence the code cites; H5 gains
the P2 and M2 boundaries (`John van der Berg Prof.` keeps family 'van
der Berg Prof.', `Mary Smith née Jones Prof.` maiden 'Jones Prof.')
and the P5 clause; H5/P2/M2/P5 gain each other's `interacts:`.

Tests: the script-order half of "set before the positional read"
(`毛 泽东 Dr.`), the conjunction-merged unit that actually pins the
one-word gate in `trailing_titles`, `Dr. Do Jr.`'s own test kept out
of the no-op-chain parametrization's silence claim, and case rows for
the two bound-given readings, `Smith, E.S.Q.`, the two maiden
orderings, `Smith, John, Prof.` and `John Smith Prof. and Dr.`.

Stale counts and false comments, all re-measured: 12/nineteen ->
14/twenty-one for the title-run bundle in the four ledgers and the
guard, the twelve-member alternation is fourteen, the release log's
bullet names the accepted `Mary Jane King.` cost, and the ledgers'
role paragraph says which baseline has three two-role movers and
which has two. `John Prof. MA` does report an ambiguity, so the
"none of the fourteen" sentence is corrected to the one that matters
(`title-or-name`, still none). The first suffix peel is provisional
only where the walk takes something; `trailing_titles` is entered by
every parse with a name word to place and not by all 1289 corpus
parses (52 return first); its `rest` is the caller's name pieces, and
group is now a third caller, which mechanisms.md's census records.
Two defensive branches are marked measured-inert over 191,146
generated inputs -- the folded-vs-raw last word and the titled-piece
skip -- and the third the review named, `walkable` starting at `n`,
is NOT inert: dropping it moves 24 of those inputs, so it is marked
load-bearing with the shape that moves.

compare.py's RECOMPUTE paragraph: the strict row counts are 38/34/33/8
over 52 names, 50 of them in corpus_issues.jsonl. The every-file pair
recorded on 2026-09-05 is retracted rather than bumped -- it exceeded
the strict pair, which is impossible -- and replaced with a derivation
a reader can run without a baseline wheel.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…w classified

H1 collected every `Role.TITLE` token into ONE run, and with #316(a)'s
trailing run shipped a name can carry titles at both ends, so the run
behind the name decided the field of the word in front of it. `Sir John
Prof.` keyed 'sir prof' -- addressed by neither word -- and read family
'John' where `Sir John` reads given 'John'; `Queen Elizabeth Prof.` lost
its given name the same way, and `Dr. Smith Sir.` moved the other
direction, the trailing 'sir' making the leading 'dr' address by given
name.

The rule is #H5's transparency principle applied to the one-word name:
`X Prof.` is `X` plus a title, so adding the title cannot change how the
words in FRONT of the name are read. The run standing before the one
name word addresses; a run standing behind it decides only when none
stands before, which is what keeps `Smith Sir.` reading given 'Smith'.
Found by this PR's own review of P5: group's licence has always read
only the pieces ahead of the bound word (`range(fk)`), so the 2026-08-22
#369 invariant -- H1 and P5 cannot read one run two ways -- had been
broken by the composite keying, and `_group.py` now records that beside
the licence.

The split is at the NAME WORD. Splitting at the first token of another
role -- the first shape written -- is defeated by a nickname in front of
the titles: `'Smitty' Sir Jones Prof.` found no leading run and read
family 'Jones', and `Dr. 'Smitty' Sir John` lost the run it had. H1's
rationale decides it, a nickname beside the name word not deciding this
reading, so a run written around one is one run. The design review's
two-input invariant is what caught it.

`Sir abdul rahman Prof.`'s case row flips to given 'abdul rahman': the
P5 reserve fix was right AND this one was needed, the join firing for
the right reason while the field stayed wrong. Rows added for `Sir John
Prof.`, `Queen Elizabeth Prof.` and `Dr. Smith Sir.`; `Smith Sir.` and
`Dr. King Sir.` (title 'Dr. King', given 'Sir.', title-or-name) are
unmoved. The invariant test's trailing-title half now requires the
titled spelling to give the same given and family as the spelling
without it, over the nickname spellings too: restoring the whole-titles
key fails six of its eleven rows and so does the first split, where
comparing the two spellings' "no family" to each other passed under both.

rules.md#H1 states the ordering and gains `"Sir John Prof."` and
`"Dr. Smith Sir."`; #H5's one-name-word clause is conditioned the same
way. Its P5 clause is scoped to the comma-less writing -- after a family
comma the reserve reads no peel, so `Berg, abdul Prof.` takes the title
into the given name, which is 1.4.0 parity and is recorded with a case
row rather than an example, tracked as part of #316.

`毛 泽东 Dr.` printed UNCLASSIFIED (radar) at all four gates and now has
its own literal rule, `fields` differing per baseline -- it is the one
bundle rule that cannot join an alternation, a script-classified member
belonging to the honorific pin. The trailing-title alternation goes to
sixteen, the bundle to twenty-four corpus names, and the four ledgers,
the guard, the release log, decisions.md#H5's population (SEVENTEEN by
its own recipe, the CJK name having been missing from it) and compare.py
are re-measured.

Twenty-four movers against a0b93f0; the only two this commit moves are
the two rules.md examples it adds. All four gates read `unexplained: 0;
radar unclassified: 0`. Frames unchanged at 416 parse / 453 facade on
py3.11 -- the run is built inside H1's guard and the reference name has
a family.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ound on PR #520

The trailing peel (S2) and the trailing title chain (H5) were derived
twice: assign ran peel -> chain -> splice -> re-peel inline, and group's
bound-given reserve modelled that as the peel's count minus the chain's
take on each of the two views the join compares. The two disagreed at
S2's bare-ambiguous reserve -- `abdul rahman MA` declined the join while
`abdul rahman MA Prof.` took it, contradicting H5's stated clause that a
bound given-name word behind a trailing title joins as it joins with the
title absent. And assign's re-peel ran once, so `John Prof. MA Prof.`
un-peeled the acronym and re-exposed the first title, reading family
`Prof.` where `John Prof. MA` reads family `MA`.

_pieces.tail_reading is now the one function: peel, chain, splice, peel
again until the chain takes nothing, returning the walk, the chained
pieces and the final peel. Assign calls it in place of its inline
sequence; the reserve calls it on both views and compares the suffix
lists, both subtractions deleted, with the join's refusal to take a
chained piece said as the rule it is rather than left to the arithmetic.
trailing_titles returns the count it LEAVES STANDING, the way
peel_trailing counts, so the two compose without arithmetic at all.

The two readings above are the only behaviour that moves: all seven
fields, the ambiguity kinds and the recorded order over every corpus
name and every cases.py literal are identical, 2674 names on both trees,
none moved. Both take case rows.

The helper costs one frame on the common path, paid by _effective_order
taking the name-piece indices instead of a list built for it; the
reference name stays at 416 parse / 453 facade on 3.11.

Also in this round: leading_titles' floor as a for/else; the family-comma
walk's `titled` as a parameter rather than a closure over a rebound
local, one "previous kept piece" helper for both `prev` and the name's
end, and the placement pass reading the first pass's own memo where the
chain took nothing (-7 frames on `Smith, John Quincy`); _lexicon's fold
factored so "the last word of the FOLDED key" is structural; suffixes.py's
unreachable third assert folded into the disjointness one; duplicated
prose in _group, _post_rules, compare.py and test_ledger_guards reduced to
one copy; and the `Sir Jr` example and case row dropped, `Dr Jr` reading
by the same branches -- which moves two ledger rosters (7 -> 6, 4 -> 3).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…eads it

CI's 3.12-3.15 jobs failed the facade band at 452.0 against a ceiling of
451.86 after the /simplify commit: the frame it traded for the shared
tail reading was a list comprehension in _effective_order, which is a
frame on 3.11 alone (PEP 709 inlines comprehensions from 3.12), so on
the newer interpreters the wrapper went unpaid.

`has_nickname` was computed at the top of the positional read as
`any(t.role is Role.NICKNAME for t in tokens)`, a generator that every
interpreter resumes once per token -- seven call events on the
reference name -- and read by exactly one branch, the one-piece
segment beside a nickname (rules.md#N3). The scan now sits last in that
branch's condition, so an ordinary name never starts it.

Measured with tools/perf/call_count.py on `Dr. Juan0000 de la Vega III`:
py3.11 416/453 -> 409/446 (baseline 410/447); py3.12 395/432 -> 388/425
(baseline 388/425); py3.14 413/450 -> 406/443 (baseline 406/443). Every
interpreter is back at or one under its recorded row; no row moves.
Parses are unchanged: the branch's answer is the same, only when it is
computed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…mes tail_reading

Six review findings on the pushed branch, all re-measured on this tree
rather than carried over.

decisions.md#H5's class bullet stated one width for two defects and
named the wrong shape for it. The 88 shapes the fix closes PARTITION:
56 carry one period-marked title word and a bound given-name word
besides -- the subtraction model -- and 32 carry two, the truncated
fixed point. The 156 movers over the 82,712-input sweep split 60/96
the same way, and the two-title population is 784, of which 164
differed before and 132 still do.

The population bullet's disabling stub was wrong the moment the same
commit made `trailing_titles` return the count it LEAVES STANDING: a 0
stub now takes everything and raises IndexError on 'John Smith'. The
recipe says `lambda rest, *a: len(rest)`, bound over both the `_pieces`
name and the one `_assign` imported, and says why the fix's own tree
needs the other stub. Re-run that way the five and the seventeen hold.

mechanisms.md's ONE-PREDICATE-PER-QUESTION census was one round out of
date: an AST call-site census over `_pipeline/*.py` says the shared
predicate is `tail_reading`, that `peel_trailing` has no caller outside
`_pieces.py` at all, and that `trailing_titles` has exactly one.

#H4's rosters lose `Sir Jr`, which left corpus_rules.jsonl in the same
round: eleven names in fourteen rows, the peel half nine, six contract
and three radar -- with the tier rule spelled as the README states it,
contract files sorted first. The release bullet counts the set once,
three corpus names plus `Sir Jr` as an input `Dr Jr`'s row pins, and
the four ledgers' peel-floor comment no longer claims a reach of four.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s reach

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug by-design docs Documentation fixes and updates enhancement

Projects

None yet

1 participant