fix(maiden): a marker stops the particle chain that swallowed it - #409
Merged
Conversation
`Ursula von der Leyen geb. Albrecht` lost the maiden name that `Ursula Leyen geb. Albrecht` kept: grouping's prefix chain absorbed everything up to the next prefix or suffix piece, and the marker is consumed AFTER the chain merges, by which point there is no lone marker piece left to find. rules.md carried that as an Accepted limitation, but it was a consequence of stage order rather than a judgment, so M2 now states the stop and the limitation is gone. The chain's stop and the maiden handler's consumer share one definition of a marker piece. A chain that stopped where the consumer would not then take would strand the marker inside the family name -- the very defect the stop exists to fix. Two shapes it fixes for free. Under a family-first order the marker used to survive as an ordinary name word and take the leftover given slot (`de la Cruz née Vega` -> given 'née'); dropped, it never reaches the placement, which answers #399's open question with no second change. And stopping the chain can leave a family of nothing but particles, which #404's R2 reading already covers: `Jane de la née Jones` reports family 'de la' with a non-empty base. Tests are the whole verification here. No differential corpus name has a particle ahead of a marker, so the gate is blind to this class and its clean exit says nothing about it. Closes #399 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
derek73
force-pushed
the
fix/399-maiden-marker-stops-chain
branch
from
August 19, 2026 08:36
b633270 to
829de84
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #409 +/- ##
=======================================
Coverage 98.55% 98.55%
=======================================
Files 44 44
Lines 2977 2981 +4
=======================================
+ Hits 2934 2938 +4
Misses 43 43 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Review found the first cut of #399 traded one stranding for a worse one. Sharing a marker-piece predicate between the chain's stop and the maiden consumer settles what a marker piece IS; it does not settle whether the consumer TAKES, because the consumer also needs a non-suffix piece after the marker. Stopping without one left the marker standing as its own piece, and a lone trailing piece takes a role field: Jane van der Berg née was middle 'van der Berg', family 'née' The marker became the surname and the real surname was demoted -- the defect the stop exists to prevent, one field over. The stop now tests the consumer's own condition, so the two halves cannot disagree. Three measured consequences, each fixed by the gate. 'Jane van der Nee' moved a bearer of the attested surname Nee out of the family, which M1 already warns about. 'Ursula von der Leyen geb.', an ordinary truncated record, reported the marker as the surname. And 'St St née' stopped reporting its particle-or-given fork, because group's emitter is guarded on the chain having merged something and an ungated stop made it merge nothing for a different reason than the guard assumes -- an A1 violation of the shape #405 tracks, and worse, since it removes a report callers already see. Pinned six ways: four case rows and two piece-level tests in test_group.py. The piece level is where the two halves can disagree without any field looking wrong enough to fail -- the misaligned version passed the entire suite. Also from review: - Two limits the deleted Accepted clause used to cover are back in M2, scoped: the bound reaches only a lone marker, so P3's connective join and P5's bound-given join each still absorb one first. #399 fixed one instance of the join-swallow and left two. - P2's statement enumerated the join's stops and named neither the marker nor the suffix. Both are stated now, and its two code citations move with it. - The new normative sentence moved ahead of M2's examples, because the citation checker cuts a statement at its first example line -- stated after them, it could never be cited from code. - decisions.md: the false invariant is corrected, the quoted removal is verbatim so `git log -S` finds it, and the recorded reason for preferring the stop over a stage reorder is rewritten. The example it cited does not discriminate: 'Anna Müller geb. von der Berg' is identical before #399, after it, and under a reconstructed reorder. - A release_log.rst bullet, which the first commit omitted. - Three case notes corrected: one claimed only the maiden field moved when the family moved too, one credited R2 for what P4 and P1 do, and one promised a pin the partition invariant does not deliver. - 'Jane née and Jones Smith' pins the marker predicate's lone-piece guard, which was an unkilled mutant: dropping it left 4229 tests green while changing behaviour through a conjunction merge. - The cross-script row puts a name of this shape in the CJK corpus, so the class is gate-visible for the first time, with its own ledger rule and recorded corpus claim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two surviving join-swallows are recorded in M2 but named no issue, unlike the fullwidth-colon limit beside them which points at #317. They are #412 now. decisions.md's Open (M2) list also picks up #410 and #411 -- the two empty-family paths a maiden name opens, both pre-existing and both widened by the stop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…inery The row existed to reach the CJK corpus builder, which is the only route a case row has into any differential corpus -- corpus.jsonl regenerates from v1's test banks at a pinned ref and corpus_issues.jsonl harvests the tracker. Getting #399's class in front of the gate that way was a back door: that corpus's provenance is CJK names, not Latin mechanisms carrying one CJK token. It also pinned nothing. Measured with the shipped lexicon, "Jane van der Berg 旧姓 Jones" and "Jane van der Berg née Jones" produce the same segments and the same pieces -- script_segment does not split at the boundary -- so the cross-script spelling exercised no path the Latin rows do not. Removing it takes the CJK corpus entry, the ledger rule, its _LATIN_ALTERNATION_SOURCES declaration and the recorded corpus claims with it; those three files return to their master content exactly. All three baselines still exit clean over 751 names. The class is gate-invisible again, and decisions.md says so rather than claiming coverage it does not have. #413 is the honest fix: the issues harvester matches only quoted names, while this tracker writes them in backticks, so it misses 104 names -- `Beethoven, Ludwig van` and `Berg, Jan vd` among them, and `Ursula von der Leyen geb. Albrecht`, which sat in #399's own title the whole time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
derek73
added a commit
that referenced
this pull request
Aug 22, 2026
Review found the reserve fix necessary but not sufficient, and two prose claims measurably false. Excluding the departing words is not enough on its own. Where a suffix sits INSIDE the name the marker walk stops early, so the excluded span is short, enough words survive it to clear the reserve, and the join fires and takes the MARKER -- after which nothing leaves at all and the count that authorised the join was reasoning about a name that never came to be. `abd née Jones Jr Smith Berg` read given 'abd née'. The join now declines outright when the piece it would absorb is a marker, which is what P5 meant all along: it joins the bound word to a name word, and a marker is not one. Tested against the marker directly rather than through the span, because the two differ exactly where it matters -- a marker with nothing but a suffix after it has no span at all, yet the join would still have absorbed it (`Berg, abdul née PhD` read given 'abdul née'). That closes the P5 half of #412 for real; the first cut only narrowed it while claiming it closed. The docstring's "the count and the removal cannot disagree" was the same overstatement this session already shipped once. Sharing the span helper settles what a maiden span IS; it does not settle what leaves, because the two calls run at different times on different piece lists and the join between them can invalidate the span. Forbidding the join to absorb a marker is what closes that -- the sharing only rules out disagreeing about the span itself. The pre-existence claim was backwards. `abd Berg née Jones` reads CORRECTLY at 2.1.0; `abd` acquired the defect only when #400 made it a bound given-name word this cycle, so for that spelling this fixes a regression introduced earlier in the same release. The pre-existing witness is `abdul`/`abdel`/`abdal`, which read the defect at 2.0.0 and 2.1.0 alike. The issue body carries the same error and needs it too. Six more rows, each closing a measured gap: - `Abd Berg née Mary Jones`. Capping the excluded span at two pieces reproduced #411 exactly with the whole suite green -- every existing row had a one-word maiden name, so none could tell the span from the marker plus one. A particle-led maiden name does not serve; P2 makes it a single piece. - The two shapes the new guard fixes, and `Berg, abd née Jones`, where a declining join after a family comma leaves S2's suffix reading and the name has no given name at all -- as `Berg, abd` alone always parsed. It reads alarmingly and was in no bullet and no row. - The marker immediately after the bound word, and the Arabic script. Plus a piece-level pin that the join never merges a marker, and the bound-given shapes added to the #409 chain invariant's inputs -- that invariant is keyed on the particle chain, and the distinction is now exercised rather than assumed. `Berg, abd née Jones` arrived UNEXPLAINED at 1.4.0 on four fields, the subset test correctly rejecting both nearby rules, and is read once here. 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 #399.
Ursula von der Leyen geb. Albrechtlost the maiden name that the same words one particle chain apart kept:The fix
Grouping's prefix chain absorbs everything up to the next prefix or suffix piece, and the maiden marker is consumed after the chain merges — by which point there is no lone marker piece left to find. The chain now stops at a marker the way it already stops at a suffix.
Only a non-leading particle ever reached the marker, which is why a leading single particle always worked (P4 chains nothing) while a leading run of two did not — the second particle's own chain fired.
The stop is gated on the consumer actually taking
This is the substance of the change, and it came out of review rather than the first cut. Sharing one marker-piece predicate settles what a marker piece is; it does not settle whether the consumer takes, because the consumer also needs a non-suffix piece after the marker. Stopping without one left the marker standing as its own piece, and a lone trailing piece takes a role field:
The marker became the surname and the real surname was demoted — the defect the stop exists to prevent, one field over. The gated reading is also what M2 already said should happen: a marker with nothing after it "is just a word", exactly as
Jones née→family='née'.Three measured consequences of the ungated form, each closed by the gate:
Jane van der Neemoved a bearer of the attested surname Nee out of the family. M1 already names Nee as a surname.Ursula von der Leyen geb.— an ordinary truncated record — reported the marker as the surname.St St néestopped reporting itsparticle-or-givenfork. Group's emitter is guarded on the chain having merged something, and an ungated stop made it merge nothing for a different reason than the guard assumes: an A1 violation of the shape A tussenvoegsel attachment decides a particle-or-given fork and reports no ambiguity, which A1 requires #405 tracks, and worse than A tussenvoegsel attachment decides a particle-or-given fork and reports no ambiguity, which A1 requires #405's, because it removes a report callers already see.Two shapes fixed without a second change
Ursula von der Leyen geb. Albrechtloses the maiden name — a particle chain swallows the marker where a suffix stops it #399's open question. Under a family-first order the marker used to compete for the leftover given slot, sode la Cruz née Vegareportedgiven='née'. Consumed and dropped, it never reaches the placement, and both family-first orders now agree. A marker the consumer declines still reaches it — that is M2's "just a word" reading, not a leak.Jane de la née Jones→family='de la'with a non-empty base, via Should a particle that joins nothing still count as a particle? (Jong van derandJong, van deralready disagree) #404's R2 reading. A 305,364-parse sweep at review found no empty-base or partition violation either side of the change.Limits, now stated rather than implicit
The bound reaches only a marker standing as a word of its own, so P3's connective join and P5's bound-given join each still absorb one first —
Jane van der Berg née y Jonesandvan der Berg, abdul née Jonesare unchanged. The deletedAccepted:clause covered a class of which this fixes one instance and leaves two; both survivors are back in M2 with examples.Docs
rules.md#M2: the stop and its gate are in the statement, ahead of the examples — the citation checker cuts a statement at its first example line, so a rule stated after them can never be cited from code.rules.md#P2: its statement enumerated the join's stops and named neither the marker nor the suffix. Both are named now, and its two code citations move with it.decisions.md#M2: the false invariant is corrected; the quoted removal is verbatim sogit log -Sfinds it; and the recorded reason for preferring the stop over a stage reorder is rewritten, because the example it cited does not discriminate —Anna Müller geb. von der Bergis identical beforeUrsula von der Leyen geb. Albrechtloses the maiden name — a particle chain swallows the marker where a suffix stops it #399, after it, and under a reconstructed reorder.docs/release_log.rstgets its Behavior Changes bullet, which the first commit omitted.Verification
Tests carry the whole verification. No corpus name has a particle ahead of a marker, so the differential gate is blind to this class and its clean exit says nothing about the change — all three baselines exit clean (1.4.0, 2.0.0, 2.1.0), the 1.4.0 baseline verified genuine rather than the mislabelled cached tree, over 751 names unchanged.
A synthetic
Jane van der Berg 旧姓 Jonesrow was added to reach the CJK corpus builder — the only route a case row has into any corpus — and then removed. It used that corpus's provenance as a back door, and it pinned nothing: measured with the shipped lexicon it produces the same segments and pieces as the Latin spelling, sincescript_segmentdoes not split at the boundary. #413 is the honest fix — the issues harvester matches only quoted names while this tracker writes them in backticks, so it misses 104, includingUrsula von der Leyen geb. Albrechtfrom #399's own title.20 case rows plus two piece-level tests in
test_group.py. The piece level matters because that is where the two halves can disagree without any field looking wrong enough to fail — the misaligned version passed the entire suite. Removing the gate now fails 6 tests; dropping the marker predicate's lone-piece guard, previously an unkilled mutant, now fails 2.Full suite green: 4293 passed. ruff and mypy clean,
_group.pyand_post_rules.pyat 100% statement and branch coverage.🤖 Generated with Claude Code