Repository navigation
de Mesnil Juan should read family de Mesnil plus given Juan — the fold takes the particle run, not the rest of the name #390
Description
Activity
First attempt closed unmerged — three routes still violate P1, and one regresses
PR #391 implemented the claim in
assign, before positions are handed
out. That part works for the shape this issue names:de Mesnil Juan → family='de Mesnil' given='Juan' (all three orders)It passed 3459 tests,
tools/differentialexit 0 at 1.4.0/2.0.0/2.1.0,
ruff and mypy. A/pr-review-toolkit:review-prpass with three agents
then found eight issues, all verified by measurement. Recording them
here so the next attempt starts from them rather than rediscovering
them.Blocking:
de los Santosregresses to a wrong surnamede los Santos 2.1.0: last='de los Santos' attempt: given='Santos' family='de los' de las Casas 2.1.0: last='de las Casas' attempt: given='Casas' family='de las'los,las,das,elare not in the particle vocabulary — only
de,la,der,denare. So the claim takesde+losand hands
the real surname togiven.This is the important one, because it inverts the bundle's sequencing.
"Takes everything" is insensitive to vocabulary gaps; "takes the group"
is not. The old sweep was masking an incomplete particle set, and
narrowing the claim converts that gap into user-visible breakage on a
very common Hispanic name shape. #360 has to land first, with C-i
applied to the unexamined articles (los/lasare articles, borne as
no one's name, so C-i puts them in the never-given half).The differential could not see it: no
de losname exists in any of the
three corpora. Corpus-blindness hiding a regression rather than a small
count.Blocking: a multi-particle run still folds the whole name
de la Cruz Maria → family='de la Cruz Maria' (P1: family='de la Cruz', given='Maria') de la Vega Juan → family='de la Vega Juan' (P1: family='de la Vega', given='Juan')Cause is
rules.md#P4: the leading particle chains nothing, sodeis
its own piece and the next particle chains everything after it —
pieces = [['de'], ['la','Vega','Juan']]. Takingpieces[:2]takes the
whole name.de Mesnil Juanworks only because it has no second
particle. So the implementation takes the particle run plus one
piece, where P1 says one word; the two differ exactly when the run
spans more than one piece.Blocking: the family-comma path never reaches the claim
Smith, de Mesnil Juan → family='Smith de Mesnil Juan' (P1: family='Smith de Mesnil', given='Juan')_assign_mainis not called forFAMILY_COMMA, so the old
whole-remainder sweep survives inpost_rulesfor segment 1.All three agents converged independently on the last two, which is the
strongest signal in the set.#365 does NOT close with this
The attempt claimed it did. It does not — verified:
Mesnil Garcia de FF given='Garcia' middle='de' family='Mesnil' ← stranded FFGL family='Mesnil Garcia de' ← foldedThe surviving
post_rulessite is still ROLE-keyed, which is #365's
actual defect. Relocating the leading half does not reach it.Other verified findings
PARTICLE_OR_GIVENemits a false detail.de Mesnil van MD Gogh
reports "leading 'van' may be a family-name particle" —vanis the
third piece. Cause:name_piecesis rebound to the leftover before
the emitter readsname_pieces[0], silently changing its meaning.
_ambiguitiesis a compared differential field, so this is a latent
unexplained diff.- FF and FFGL diverge at two leftovers and nothing tests it.
de Mesnil Juan Carlos→ FFGL swaps given/middle. The
_name_positions(order, count + 1)/ drop-FAMILY line is pinned only
in its degenerate one-leftover case, where all three orders coincide. - A replacement test was inert.
Dr. de Mesnilcannot exercise the
title peel under the module's fixture lexicon (titles={"mr","sir"}—
drabsent), sode Mesnilchains into one piece, the claim never
fires, and the test passes with the feature disabled. Mutation-verified.
Mr.is the title that works here. _CROSS_RULE_WINNERSneeds a row forde Mesnil Garcia: without
the new ledger rule it falls to the fields-onlyfix(suffix-routing)
catch-all with the gate still at 0 unexplained.- ~8 prose sites contradict the new behavior and the same-PR
amendment rule puts them in the diff: norelease_log.rstbullet, the
existing particles_ambiguous has no effect on the parse under name_order=FAMILY_FIRST #359 bullet now false,nameparser/config/particles.py
docstrings (autodoc-rendered API reference),docs/customize.rst,
docs/usage.rst,AGENTS.md:198,mechanisms.md#PIECESstill naming
_post_rules.pyas the pieces reader, and both stage docstrings.
Every doctest keeps passing because every example is a two-word name.
What the next attempt needs
Mc DonaldandSte Marieread the particle as the given name #360 first. The claim cannot be narrowed while the particle set
has gaps.- The claim belongs in grouping, not assign. Two of the three
blocking routes need a chained piece SPLIT (de la Vega Juan) and
the third needs theFAMILY_COMMAbranch — neither is reachable from
whereassignhands out positions. - Keep the
deviates: #364markers onrules.md#P1until all three
routes hold. Removing them while the comma and multi-particle routes
still sweep is what let the doc claim a rule the parser does not have.
Branch
fix/390-fold-takes-particle-groupis pushed if any of it is
worth salvaging;masteris untouched.- added a commit that references this issue
on Aug 18, 2026 Second attempt also closed unmerged (PR #394) — and the rework strategy is now clearer
#394 put the claim in grouping, which fixed the three routes #391 could
not reach. It still regressed four shapes and stated several things in
the normative docs that are false. A three-agent review found them; all
were verified by measurement againstmaster.What #394 got right, and should be kept
de la Vega Juan→family='de la Vega',given='Juan'(the route
The leading-particle claim takes its group, not the rest of the name (#390, #365) #391 could not reach at all)de la Cruz Maria,de Mesnil Juanlikewise, in all three ordersde los Santossurvives —Mc DonaldandSte Marieread the particle as the given name #360 landing first did its job- The FAMILY_COMMA route reached at last
- P4 amended so a never-given particle leads a bounded chain while an
ambiguous one does not, and mid-name runs stay greedy (P2's join
genuinely unchanged, verified) - Differential exit 0 at all three baselines with a proper ledger rule
Regressions from master
master #394 de and Mesnil Juan given='de and Mesnil' family='de and Mesnil Juan' de y Mesnil Juan family='de y Mesnil Juan' family='de y' <- zero name words St de Mesnil Juan (n/a) family='de Mesnil Juan' <- the whole-name fold de abdul Rahman Juan family='de abdul Rahman' family='de abdul' <- splits a P5 bound pairFalse statements no test could see
rules.md#P4: "A title before either changes nothing" — three TITLES
members (st,do,freiherr) change everything.rules.md#P2: "The final group reads as the family name" — false for
every name the PR moves (de Mesnil van der Berg→family='de Mesnil',given='van der Berg'). P2 got no amendment and has no
interacts:line, so nothing points back at P1/P4.- The guard comment said "three conditions" over four conjuncts, and
creditednot titleforst/do/freiherr— which are in the
AMBIGUOUS half, so a different conjunct already excludes them. - "v1 gave
family='Smith de Mesnil'" — v1.4.0 actually gives
last='de Mesnil Smith'. Wrong indecisions.mdand a test comment. - Ledger prose about
fix(suffix-routing)being "below" was
copy-pasted into the 2.0.0 and 2.1.0 ledgers, which have no
fields-only catch-all at all.
The core new code was unpinned
Verified by mutation on a copy: replacing the
_name_positions(order, count + 1)minusFAMILYconstruction with a
hardcoded[GIVEN] + [MIDDLE]*(n-1)— discardingname_orderfor the
leftover entirely — passes the whole suite. So does removing the
prefix(leading)conjunct, which is also inert (no input distinguishes
it). The two-leftover shape (de Mesnil Juan Carlos), where the orders
actually diverge, appears in no test, no case row and no rule example.Also a THIRD fixture-trap casualty:
Dr. de MD Mesnilcannot exercise
the leading chain, becausedris absent fromtest_post_rules.py's
reduced_LEX.The rework, corrected
My first proposal was to walk tokens and ignore piece boundaries. That
is wrong, and Derek's correction is why: the conjunction rule means a
"word" in other rules can be several words.Vega y Santosis one
unit, sode la Vega y Santos Juan→family='de la Vega y Santos',
given='Juan'is CORRECT — a review finding against it was a false
positive. Ignoring piece boundaries would split it.The formulation that explains every case, including both regressions:
The family group is the particle run plus exactly ONE NAME WORD,
where a conjunction-joined run counts as one word. Stop extending as
soon as the group contains a name word; do not extend at all if it
already does; claim nothing if it never does.de run has no name word -> given='de' (P1 Accepted) de Mesnil Juan [de] has none, take Mesnil -> family='de Mesnil' de and Mesnil Juan [de and Mesnil] ALREADY has one -> stop, given='Juan' de y Mesnil Juan [de y] has none, take Mesnil -> family='de y Mesnil' de la Vega y Santos Juan Vega y Santos is ONE unit -> family='de la Vega y Santos'Both #394 regressions are this rule not being applied: it extended past
a piece that already had a name word, and it stopped on a piece that
had none. Stated this way the conjunction merge and the title overlap
stop being special cases.Whatever lands next also needs, per the review:
- a test at TWO leftovers across all three orders (the only place
name_ordermatters for the remainder) - a negative control for each guard conjunct, or fewer conjuncts
rules.md#P2amended or marked — its "final group is the family"
sentence does not survive this change- the stale
DEVIATION #364comment in_post_rules.pyremoved - doc examples that use the DEFAULT vocabulary, not the reduced
_LEX
Branch
fix/390-retry-claim-in-groupingis pushed if any of it is
worth salvaging.masteris untouched.Superseded by #395.
The direction changed after two failed attempts: the stopping point is now keyed on
name_orderrather than on position alone. Declaring a family-first order is precisely an assertion that what follows the family is not more surname, so the default order keeps the greedy reading (de Mesnil Juan→family='de Mesnil Juan', the same shape aspennie von bergen wessels) and only family-first orders stop at the first name word.That makes this issue's title false — it asserts the bounded reading for the default order.
decisions.md#P1carries a 2026-08-17 entry superseding the "no name_order moves that stopping point" sentence this issue was written under.Everything the two attempts established — the guard traps, the coverage gaps, the fixture trap, the docs needing amendment — is carried forward to #395.
- added a commit that references this issue
on Aug 22, 2026
rules.md#P1states that a leading never-given particle makes theparticle run and the one name word it attaches to the family, with any
further name words read by position. The parser still folds every
remaining name word in, so the rule and the code disagree.
Behavior, all three orders
name_orderdeliberately has no effect on this input class, andthat is the decision rather than an oversight. A leading never-given
particle is evidence about how the name is written — the Latin-script
analogue of
rules.md#W4, where a wholly-hangul name readsfamily-first under every declared order because the script settles it.
decisions.md#O4draws the line the same way: "Words no vocabularyhas claimed read by position", so the declared order governs the
unclaimed remainder, which is most inputs but not this one.
The alternative — particle group takes position one,
name_ordernames the position — is Declined at
decisions.md#P1: it makesde la Vegareadgiven='de la Vega'with no family underGIVEN_FIRST, breaking a corpus name and v1 parity, unless a furtherrule forces a particle-headed lone group into the family, at which
point it produces this issue's reading anyway.
Where the decision lives
Decided 2026-08-16 in #386 as part of the order-precedence keystone,
recorded at
decisions.md#P1(the2026-08-16 #364entry). #364asked the question and closed once it was answered; this issue is the
implementation, so the two
deviates: #364markers onrules.md#P1have a live target.
"Takes everything" was never argued for — it was the shape of v1's
handle_non_first_name_prefix, not a decision.Measured scope
One corpus name moves, over all 782 names of the three differential
corpora with no prefilter:
Two lookalikes do not move:
de la Vegais one group start tofinish, and
de Mesnil Jr.has two name words becauseJr.is asuffix.
Measurement trap, from
mechanisms.md: the particle run chainsthrough any particle, not only the never-given ones. A detector
walking only the never-given run splits
de la Vegaafterde laandreports 50 false movers.
Implementation note
The fold is in
nameparser/_pipeline/_post_rules.pyand today readsfor i in givens + middles: _retag(tokens, i, Role.FAMILY)— everyremaining name word. It needs the particle's own group instead, which
state.piecesalready carries. TheDEVIATION #364comment above itcomes out in the same diff.
Sibling
#365 is the same rule reached under
FAMILY_FIRST, where the particlestrands in
middle. Both close together: once grouping isorder-independent, the middle position needs no third fold site.
Verification
tools/differentialat all three baselines, with a ledger entry forde Mesnil Garciadeviates: #364markers removed fromrules.md#P1in the samePR (the runner asserts today's value, so the suite fails until they
go)
Mesnil Garcia destrands the particle under FAMILY_FIRST but folds it under FAMILY_FIRST_GIVEN_LAST #365's family-first shapes come back with 0 diffs, the property thatissue asks for