Skip to content

Commit baea5a2

Browse files
derek73claude
andcommitted
docs(design): record the shared peel and the join that changes no suffix reading (derek73#425)
decisions.md#P5 carries the dated entry: why the hand mirror was retired for one function, where it lives and why, how the view models merge(), how BoundJoin was restated, the principle that came out of writing the ledger ('abdul Smith Ma' had read family 'Ma' since 2.0, the derek73#423 entry called that lossless, and the 1.4.0 gate's catch-all count said otherwise -- the count is the tell, per the derek73#401 entry; it is back at 15), the design-docs review's 67,860-shape sweep in place of derek73#423's unrecomputable 132 (2,160 family-losing shapes before, none after; 5,166 move against master, none post-comma), and the three things the principle does not reach: the post-comma path (the family is fixed there -- NOT "no peel to read", which was the first draft's false reason), the unambiguous-vocabulary decline (a bare ambiguous acronym the peel does not take is a name word), and the ambiguity report. The title-word class the code review surfaced is recorded with its 1.4.0 readings. rules.md#P5 gains six examples -- the acronym fork both ways, the boundary 'abdul Smith Berg Ma', the title word 'abdul Sir Smith Berg', and two Accepted readings, 'Berg, abdul V' (moved under its own clause) and 'abdul Ma Smith'. They carry the shapes into corpus_rules.jsonl (180 names). The acronym shapes are 1.4.0 parity, so one fix(derek73#425) rule at 2.0.0 and 2.1.0 explains both; 'abdul Ma Smith' is a 2.0 reading first witnessed here, so the 1.4.0 ledger gains an accepted rule for it. The 1.4.0 suffix-routing reach pin moves 1036 -> 1041; the derek73#421 rule comments quote the statement as it now reads. One release-log bullet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 9f472f3 commit baea5a2

8 files changed

Lines changed: 84 additions & 7 deletions

File tree

‎docs/design/decisions.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,12 @@ Excluded (SUFFIX_ACRONYMS / SUFFIX_WORDS — the esq dual membership, deliberate
370370

371371
Fixed with one predicate, `reads_as_suffix`: a suffix piece, or the trailing roman numeral assign's fork takes — and that fork is now `_vocab.is_trailing_numeral_suffix`, lifted out of assign so the two stages share its definition instead of the reserve carrying a copy (the `_ROMAN` pattern moved with it; the regex-sync pin follows it). The reserve counts a piece out when it reads as a suffix, and the join declines the piece it would take when it does — the same decline it already made for a marker. The mirror is assign's fork condition for condition: the numeral is the LAST piece and not the first name piece — not "the piece before it is not a title", which the first draft wrote and the design-docs review falsified, `jr` being title vocabulary as well as a suffix, so `abdul Smith Jr V` lost its family exactly as before — and the piece before it is read as the join would leave it, the joined pair's first token being the bound word (`abdul J. V`, the design-docs review's other find, otherwise suppressed the fork for the reserve alone and read given 'abdul J.', family ''; the reserve now declines, and assign, seeing the unjoined pieces, reads given 'abdul', middle 'J.', family 'V', exactly as it reads `John J. V`). Two more the code review found: assign drops the group-flagged credential pieces (the Ph. D. merge) from its walk at any position before the trailing peel, so a numeral can be last in that walk without being the last piece — `abdul Smith V Ph. D.` lost its family until "last" and "the piece before" were read over the pieces assign keeps — pinned at the piece level only, because a trailing split credential is the shape the 1.4.0 ledger's `[[never]]` entry refuses to explain any diff on, and v1's reading of that name carried the #401 family loss; the example stayed out of the rules corpus rather than the never-entry being narrowed for one bound-word spelling; and the post-comma walk has no numeral fork at all (a trailing numeral there is read by a lenient last-of-two rule, or as a middle initial with a third part), so a mirror applied under LENIENT declined `Berg, abdul V` and `Berg, abdul V, jr` against every baseline with nothing classifying it — the numeral branch is scoped to STRICT, and after a family comma the reserve counts suffix pieces alone, as it always did. So `abdul Smith V Jr` still joins and assign reads family 'V', and `abdul V Smith` still joins an initial, which is a name word. What is NOT mirrored: S2's other positional fork, the bare ambiguous acronym. Where the join leaves two pieces no family is lost — `abdul Smith Ma` reads family 'Ma' where `John Smith Ma` reads suffix 'Ma', assign keeping the acronym as the family of a two-piece name — but with a suffix word between, the join leaves three, assign peels the acronym and then the suffix, and the family is gone: `abdul Smith Jr Ma` reads given 'abdul Smith', family '', suffix 'Jr, Ma' where `John Smith Jr Ma` keeps family 'Smith'. Pre-existing and byte-identical before this fix, and the whole of what remains of #401's shape on the main walk: a review sweep of the STRICT path found 132 family-losing shapes, every one ending in a bare ambiguous acronym. Left for #425 rather than widened into here, and framed there as sharing assign's peel over a post-join VIEW rather than mirroring a second fork by hand: with a view the count is the view's length, the remap and the first-piece carve-out fall away, and the other two sites that stop at a suffix with the same initial veto — P2's chain and M2's walk, which take a trailing numeral the same way (#424) — would ask the question once. The walk was also computed once rather than per piece after review measured the per-piece rebuild as quadratic (13x growth for 4x the input against the benchmark's 6x bound, on a shape no benchmark unit led with); `bound_given` is that unit now. Readings: `abdul Smith V` → given 'abdul', family 'Smith', suffix 'V', the SUFFIX_OR_NAME report unchanged; `abdul Jr Smith Berg` → given 'abdul', middle 'Jr Smith', where `John Jr Smith Berg` puts it; `abdul Ph. D. Smith Berg` → given 'abdul', middle 'Smith', suffix 'Ph. D.' — the SUFFIX field is 1.4.0's again, the given is not: v1's fix_phd extracted the credential before parsing and the join then took 'Smith', reading given 'abdul Smith' — which the issue reports correctly, and then mislabels its own proposed reading as "1.4.0's reading restored". The decline is chosen over reaching past the credential for the next name word, because P5 joins "the word after it": a join that skipped a suffix would read a shape no one writes as if the suffix were not there, and the marker decline already sets the precedent that the join takes the word after it or nothing. A judgement, recorded as one; `Berg, abdul Jr Smith` → suffix 'Jr'; and `Sir abdul V`, the shape #369's licence had widened #401 onto, reads suffix 'V'. #369's licence-local suffix clause is retired, subsumed by the general decline. `bound_given_join_sees_only_the_surviving_name` re-pins to given 'abd', middle 'Jr Smith', as the #418 entry anticipated. No differential corpus name has either shape — measured: the gate was byte-identical at all three baselines before the rules.md examples went in — so the examples carry the shapes into the rules corpus, where they move against every baseline and are classified there. The 1.4.0 `fix(#401)` rule lists three fields where the 2.x rules list two, because 1.4.0 did not even read the V as a suffix; a draft with two fields handed the name to the fields-only suffix-routing catch-all, whose capped printout hid it, and this entry briefly called #401 a 2.0 regression on that inert measurement — the design-docs review caught it. EXPLAINED NOTHING is not byte-identical.
372372

373+
- 2026-08-22 #425 — the reserve runs assign's peel over the view the join would leave. #423 had the reserve re-derive assign's trailing peel by hand — the flagged-credential drop, the walk's start, the numeral fork's conditions, the preceding piece remapped to "as the join would leave it" — with each seam held by a pin, and the bare-ambiguous-acronym fork not mirrored at all: `abdul Smith Jr Ma` read given 'abdul Smith', family '', suffix 'Jr, Ma' where `John Smith Jr Ma` keeps family 'Smith', the whole residue of #401's shape on the main walk (every family-losing bound-word shape in the sweep below contains one). The altitude review's framing is taken: the peel is one function, `peel_trailing`, housed in group beside the piece predicates because assign imports those and group cannot import assign; assign calls it and reports what it returns (the numeral fork's piece, the acronym picks, in the order it always reported them — byte-identical over the suite and the three gates); and the reserve builds the VIEW merge() would leave — the pair one piece, its tags the union — and calls the same function. `BoundJoin`'s "the value IS the threshold" trick survives restated as post-join name pieces: STRICT 2, LENIENT 1. The remap and the `k == first_name_k` carve-out are gone, the joined piece being two tokens and never a suffix piece.
374+
375+
One principle came out of writing the ledger that the view alone did not state. `abdul Smith Ma` had read given 'abdul Smith', family 'Ma' since 2.0, and the #423 entry called that the lossless shape; the 1.4.0 gate's catch-all count said otherwise — 1.4.0 reads first 'abdul', last 'Smith', suffix 'Ma', as `John Smith Ma` reads, and the fields-only catch-all had absorbed the diff since the example went in (the count is the tell, per the #401 entry). Unjoined, the acronym has words to spare and peels as a credential; joined, the view is two pieces and the fork keeps it as the family — the join was CHANGING what assign reads. So the rule is stated as P5 now has it: the join joins two name words into one and changes nothing else, which the code asks as "the view leaves exactly one name word fewer than the pieces as they stand". That equality subsumes the unjoined-piece check #423 added for `abdul V` (joined, the V would stop being a suffix — the view leaves one where the pieces leave one, not one fewer) and restores `abdul Smith Ma` to 1.4.0's reading; `abdul Smith Berg Ma` keeps its join, the acronym peeling either way. Every #423 pin holds. Measured: the 1.4.0 gate is byte-identical at this change — all three `Ma` examples are v1 parity, the catch-all back at 15 — and `abdul Smith Jr Ma` and `abdul Smith Ma` move against 2.0.0 and 2.1.0, classified there; no hand corpus has the shapes. The population the corpus cannot see, from the design-docs review's sweep of 67,860 constructed main-walk shapes (three bound words × twelve words × tails of one to four): before this change 2,160 lost the family and every one contained a bare ambiguous acronym (864 with further suffixes behind it, so "ending in" was an artefact of #423's shorter sweep); after it none does, and all 67,860 match the family and suffix their ordinary-given twin reads. 5,166 move against master in all — 3,006 where the acronym was the family and is now the suffix (`Sir abdul Smith Ma`), 2,160 where the family is restored — and none after a family comma.
376+
377+
Three things the principle does not reach, recorded so nobody applies it there. The post-comma LENIENT path is untouched, and NOT for the reason first written here ("its walk has no peel" — it reads a trailing numeral by the lenient last-of-two rule, as the #423 entry says): the family is fixed there and the pair is the given whatever follows, so the principle is not applied, and `Berg, abdul V` keeps given 'abdul V' though the V would be the suffix unjoined — 1.4.0 parity, and exactly the regression #423 measured when a mirror was applied there; P5 now carries it as Accepted. The vocabulary decline is the UNAMBIGUOUS vocabulary: a bare ambiguous acronym the peel does not take is a name word, so `abdul Ma Smith` reads given 'abdul Ma' (1.4.0 read middle 'Ma' — a 2.x difference no corpus witnesses, now an Accepted example). And the principle is about the suffix reading, not the report: `Sheik abdul Ma` reports no SUFFIX_OR_NAME where `Sheik John Ma` does, the #369 precedent — a bound word read as the bound word is not a fork. One more class moves, found by the code review and then measured against 1.4.0: the 2.x reserve had excluded TITLE pieces from its count — an unrecorded deviation from v1, whose join counted suffixes out and nothing else — and the shared peel, following assign, reads a mid-name title word as the name word it is. `Berg, abdul Sir` (given 'abdul', middle 'Sir' since 2.0) and `abdul Smith Sir` (declined since 2.0) read given 'abdul Sir' and given 'abdul Smith', family 'Sir' — 1.4.0's readings, both; `abdul Sir Smith Berg` never stopped reading given 'abdul Sir'. No corpus name has the shapes; P5 says it now and `abdul Sir Smith Berg` is an example. What the review actually caught was the tag artefact that exclusion had been hiding at four pieces: the conjunction merge derives a `title` piece tag for a chain such as "Sheikh and Ahmad", merge()'s union handed it to the pair, and assign peeled `abdul Sheikh and Ahmad Bakar` as title 'abdul Sheikh and Ahmad' (pre-existing at five pieces; v1 read it as the given). The bound join drops the tag, and the view drops it the same way — the pair is a given name whatever tag the word carried. #424 — P2's chain and M2's walk taking a trailing numeral — is the remaining asker of the same question and now has `peel_trailing` to ask it of.
378+
373379
Excluded (BOUND_GIVEN_NAMES):
374380

375381
- mohamad — a standalone given name in its own right; binding it would eat the middle name.

‎docs/design/rules.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,11 @@ P5. Rationale: some given-name words are incomplete alone — "abdul"
288288
"abdul Smith V" → family="Smith"
289289
"abdul Smith V" → suffix="V"
290290
"abdul Smith Jr V" → family="Smith"
291+
"abdul Smith Jr Ma" → family="Smith"
292+
"abdul Smith Jr Ma" → suffix="Jr, Ma"
293+
"abdul Smith Ma" → suffix="Ma"
294+
"abdul Smith Berg Ma" → family="Berg" · boundary
295+
"abdul Sir Smith Berg" → given="abdul Sir"
291296
"abdul Jr Smith Berg" → given="abdul"
292297
"abdul Jr Smith Berg" → middle="Jr Smith"
293298
"abdul Ph. D. Smith Berg" → suffix="Ph. D."
@@ -301,6 +306,7 @@ P5. Rationale: some given-name words are incomplete alone — "abdul"
301306
is fixed there and the pair is the given; and a bare ambiguous
302307
acronym the peel does not take joins as any name word does.
303308
"Berg, abdul V" → given="abdul V"
309+
"abdul Ma Smith" → given="abdul Ma"
304310
Accepted: a given-name title plus a bound pair leaves the family
305311
empty, as H1 accepts for "Sir John" — the input names no family.
306312
Under a family-first order the joined pair is the family and the

‎docs/release_log.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ Release Log
3939

4040
- Fix a bound given name joining a suffix as "the word after it": ``"abdul Jr Smith Berg"`` read given ``abdul Jr``, and where the suffix was the split credential the bound word joined *into* it -- ``"abdul Ph. D. Smith Berg"`` read suffix ``abdul Ph. D.``, a 2.0 regression -- 1.4.0 extracted the credential before parsing and read suffix ``Ph. D.`` with given ``abdul Smith``. The join now declines a suffix piece the way it already declines a maiden marker: ``"abdul Jr Smith Berg"`` reads given ``abdul``, middle ``Jr Smith`` (where ``"John Jr Smith Berg"`` puts it), ``"abdul Ph. D. Smith Berg"`` reads given ``abdul``, middle ``Smith``, suffix ``Ph. D.`` -- the suffix field restored, the given deliberately not, since the join takes the word after it or nothing rather than reaching past a credential -- and ``"Berg, abdul Jr Smith"`` reads suffix ``Jr``. Both shapes move against every baseline once the rules examples carry them; no name of the differential corpora had either (closes #421)
4141

42+
- Fix a bound given name joining past a credential that the suffix rule then takes, leaving no family: ``"abdul Smith Jr Ma"`` read given ``abdul Smith`` with no family, where ``"John Smith Jr Ma"`` reads family ``Smith`` with suffix ``Jr, Ma``. The join's reserve had re-derived the suffix rule's trailing walk by hand and left out one of its two forks -- a bare acronym such as ``Ma`` is a credential only with words to spare -- so it counted the acronym as a name word, joined, and the suffix rule then peeled it. The two stages now share one walk: the reserve tries the join, runs the suffix rule's own peel over the pieces as the join would leave them, and joins only when two name words became one and nothing else changed. ``"abdul Smith Jr Ma"`` reads family ``Smith``, suffix ``Jr, Ma``; ``"abdul Smith Ma"`` reads family ``Smith``, suffix ``Ma`` rather than joining the pair and keeping the credential as the family -- both as 1.4.0 read them and as ``John`` reads in their place; ``"abdul Smith Berg Ma"`` keeps its join, and a title word standing after the bound word joins again as it did in 1.4.0 (``"Berg, abdul Sir"`` reads given ``abdul Sir``) without the pair ever being read as a title (``"abdul Sheikh and Ahmad Bakar"`` reads given ``abdul Sheikh and Ahmad``). No name of the differential corpora has the shapes; the rules examples carry them, byte-identical to 1.4.0 and moving against 2.0.0 and 2.1.0 (closes #425)
43+
4244
- Fix a name opening with a particle that is *never* a given name being split at the particle under a family-first name order -- ``Policy(name_order=FAMILY_FIRST)`` and ``Policy(name_order=FAMILY_FIRST_GIVEN_LAST)`` alike, and identically: ``"de Mesnil"`` read as family ``de``, given ``Mesnil``, and ``"de la Vega"`` as family ``de``, given ``la Vega``. Each is now the whole surname, as it has always been in the default order. The rule enforcing it asked for the particle by the ``GIVEN`` role, which under a family-first order belongs to the token *after* the particle, so the test read the wrong word and declined. It now also asks by position -- the piece that opens the name -- so both shapes of the same rule are caught: where such a particle stands alone as a piece, either opening the name or in the given position, the name is left with no given name at all, the given and the middles folding into the family. Standing *alone* is the whole of it, and the rule claims nothing wider: ``"Juan de la Vega"`` under ``FAMILY_FIRST`` still reports given ``de la Vega``, because there the particle chained onto the words after it rather than standing alone, and a bare ``"de"`` with nothing to fold into is still reported as the given name. The decision behind the fix: a word that can never be a given name leaves ``name_order`` nothing to decide, so declaring family-first is not a reason to make ``de`` a surname on its own. A leading particle that *may* be a given name is genuinely order-dependent and is untouched -- ``"van Gogh"`` still reads as family ``van``, given ``Gogh`` under both family-first orders. This is also what gives ``Lexicon.particles_ambiguous`` an effect outside the default order: taking a word out of it now changes the parsed fields under a family-first order, where before it moved only the ambiguity report. Seven of the 751 differential corpus names move, the same seven under each family-first order; default-order output is byte-identical over all 751, at the 1.4.0, 2.0.0 and 2.1.0 differential baselines alike (closes #359)
4345

4446
- Fix a family name made only of particle words reporting no base on the 2.0 API, so the surname vanished from ``family_base`` and, on both APIs, from the initials: ``parse("Anh Do")`` gave family ``Do`` with ``family_base`` ``''`` and initials ``A.``, and under ``Policy(name_order=FAMILY_FIRST)`` ``"Del Toro"`` gave family ``Del`` the same way. The empty base was a 2.0 regression rather than a longstanding bug -- 1.4.0's own guard kept ``HumanName("Anh Do").last_base`` at ``Do``, and the facade has kept it right throughout; what 1.4.0 and 2.1 shared was the missing initial. A particle earns its name by joining forward to the word it modifies, so a particle standing alone in a name part is not doing a particle's work there and reads as an ordinary name word: it anchors the base, leaves the particles view, and contributes an initial. ``"Anh Do"`` is now base ``Do``, initials ``A. D.``; ``"Juan van der"`` is base ``van der``, initials ``J. v. d.``; ``"Nguyen, Van Le"`` initials ``V. L. N.`` where the middle name used to be dropped. Position decides this, not vocabulary -- whether the word is borne as a surname somewhere does not enter into it, which reverses the reasoning ``rules.md#R2`` carried before. The invariant it exists to hold: a non-empty family always has a non-empty base, because a particle needs a base to attach to. Where the particles DO join a name word nothing changes -- ``"Juan de la Vega"`` keeps base ``Vega``, particles ``de la`` and initials ``J. V.``. The parse fields themselves do not move: ``title``, ``given``, ``middle``, ``family``, ``suffix``, ``nickname`` and ``maiden`` are byte-identical over all 751 differential corpus names in all three name orders, and only the derived views and the initials change -- which is why the differential harness, which compares those seven roles, reports no diffs for this at any baseline. One consequence to know about: where the whole family is particles AND a tussenvoegsel was attached to it (#379), the two APIs order the initials differently -- ``parse("der, y van").initials()`` is ``y. d. v.`` against the facade's ``y. v. d.`` -- because ``initials()`` reads tokens in written order while the family FIELD renders folded words first. That affects 87 constructed inputs and no corpus name; it is a pre-existing ordering gap in ``initials()`` that this change makes visible, tracked at #408 (closes #385, closes #402)

0 commit comments

Comments
 (0)