Skip to content

fix(bound-given): count the reserve against the name that will remain - #416

Merged
derek73 merged 3 commits into
masterfrom
fix/411-bound-given-reserve
Aug 22, 2026
Merged

fix(bound-given): count the reserve against the name that will remain#416
derek73 merged 3 commits into
masterfrom
fix/411-bound-given-reserve

Conversation

@derek73

@derek73 derek73 commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Closes #411.

abd Berg              ->  given='abd'      family='Berg'
abd Berg née Jones    ->  given='abd Berg'  family=''       <- the bug
                      ->  given='abd'      family='Berg'   maiden='Jones'

The cause

rules.md#P5 has always said the join "needs a name word to spare, so two name words alone do not join." The implementation counted the reserve while the maiden marker and the maiden name were still pieces — group()'s marker pass runs later in the same stage. So it answered the question about a name that would not exist: four words counted, the join fired, and when the two departed nothing was left for the family.

The count now excludes what the marker will take, through the same span helper the marker pass itself uses. One definition of what leaves, so the count and the removal cannot disagree — the shape #399 settled for the marker predicate, and the reason the helper is shared rather than restated.

Pre-existing: the marker-less spelling behaved this way at 2.1.0 too. What changed is reach — #399's chain stop put the particle spellings in front of the reserve for the first time, which is how it was found.

It closes half of #412 as a side effect

With the reserve counted correctly the join declines on van der Berg, abdul née Jones, so the marker is never merged into abdul née and M2's bound sees a lone marker piece:

van der Berg, abdul née Jones   was: given='abdul née'  middle='Jones'
                                now: given='abdul'  family='van der Berg'  maiden='Jones'

That was one of the two join-swallows rules.md#M2 recorded as accepted. P3's connective join is the one that survives, so #412 narrows to it — M2's Accepted: clause and the case row that pinned the old reading move with it.

What review changed

Excluding the departing words was necessary but not sufficient. Where a suffix sits inside the name the marker walk stops early, so the excluded span is short, enough words survive to clear the reserve, and the join fires and takes the marker — after which nothing leaves and the count that authorised the join was reasoning about a name that never came to be:

abd née Jones Jr Smith Berg   was: given='abd née'
                              now: given='abd'  middle='Jr Smith'  family='Berg'  maiden='Jones'

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'). So the P5 half of #412 is genuinely closed now; the first cut only narrowed it while claiming it closed.

My "cannot disagree" docstring 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. Issue #411 carried the same error and has been corrected.

Six more rows, each closing a measured gap. The sharpest: capping the excluded span at two pieces reproduced #411 exactly with the whole suite green, because every existing row had a one-word maiden name — nothing distinguished "the span" from "the marker plus one". Also 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 (matching how Berg, abd alone has always parsed) — it reads alarmingly and was in no bullet and no row.

Plus a piece-level pin that the join never merges a marker, and the bound-given shapes added to #409's chain-invariant 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 in its own rule.

A /simplify pass, third commit

No behavior change — all three baselines still exit 0 and the suite is unmoved.

marker(k) now sits beside title/prefix/suffix/conj, with both the chain stop and P5's guard routed through it; "is this piece a marker" had three spellings in one function. The reserve gained a comment at its own site — it sat under a block entirely about #397 and read as covered by it — and leaving/going collapsed to one name.

Two prose claims measured false and corrected: the Arabic row claimed it "reaches the same arithmetic through script segmentation" (segments and effective script are identical to the Latin row's — it adds vocabulary coverage, not a code path), and decisions.md recorded the reorder's cost as "P3's rootname count dropping when the maiden words leave early", which under this PR's own principle is not a cost but the second fix.

And the piece-level test asserted more than its name. "No wide piece holds a marker" is false in general and contradicts the test directly above it, which pins the particle chain building exactly that; it held only because of which strings the loop listed. Keyed on the bound-given token now, verified to catch the same violations when the guard is reverted.

Two findings filed rather than applied

Both are behavior changes, so out of scope here:

Verification

Ten case rows, including a control (abd Allah Smith née Jones, which has a word to spare and must still join), the multi-word maiden name that is the only shape distinguishing the span from the marker-plus-one, and Berg, abd née Jones, where a declining join after a family comma leaves the suffix reading and the name has no given name at all.

All three baselines exit 0 over 868 names. The differential caught the #412 side effect as an UNEXPLAINED diff and made me classify it, and later caught Berg, abd née Jones the same way on four fields — the subset test correctly rejecting both nearby rules. No baseline shows a #411-attributable diff, since every released version already reads those names the way the fix does, so the ledger rules record the side effects rather than the defect and say so. (abd Berg née Jones is in corpus_rules.jsonl and goes unexplained at all three baselines if the fix is reverted — the guard working.)

Full suite green: 4463 passed. ruff and mypy clean.

🤖 Generated with Claude Code

`abd Berg née Jones` read given 'abd Berg' with an EMPTY family, where
`abd Berg` alone correctly reads given 'abd', family 'Berg'.

P5 has always said the join "needs a name word to spare, so two name
words alone do not join". The implementation counted while the maiden
marker and the maiden name were still pieces, because group's marker
pass runs later in the same stage -- so it answered the question about
a name that would not exist. Four words counted, the join fired, and
when the two departed nothing was left for the family.

The count now excludes what the marker will take, through the same span
helper the marker pass itself uses. One definition of what leaves, so
the count and the removal cannot disagree -- the shape #399 settled for
the marker predicate, and the reason that helper is shared rather than
restated.

Pre-existing: the marker-less spelling behaved this way at 2.1.0 too.
What changed is reach -- #399's chain stop put the particle spellings
in front of the reserve for the first time.

It also closes one of the two join-swallows M2 recorded as accepted:
with the reserve counted correctly the join declines on
`van der Berg, abdul née Jones`, so the marker is never merged into
'abdul née' and M2's bound sees a lone marker piece. maiden 'Jones'
where the name read given 'abdul née'. P3's connective join is the one
that survives, and #412 narrows to it -- M2's Accepted clause and the
case row that pinned the old reading move with it.

The corpus name that demonstrates that side effect exists only because
the rules-doc corpus (#414) added it a day earlier as an M2 example.
#411 itself has no corpus name, so the ledger rule records the side
effect rather than the defect, and says so.

Closes #411

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@derek73 derek73 added the bug label Aug 22, 2026
@derek73 derek73 self-assigned this Aug 22, 2026
@codecov

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.56%. Comparing base (390c02a) to head (d3548bd).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #416   +/-   ##
=======================================
  Coverage   98.55%   98.56%           
=======================================
  Files          44       44           
  Lines        2981     2990    +9     
=======================================
+ Hits         2938     2947    +9     
  Misses         43       43           

☔ 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.

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>
@derek73 derek73 added this to the v2.2 milestone Aug 22, 2026
A /simplify pass over the #411 work. No behavior change -- all three
baselines still exit 0 and the suite is unmoved at 4463.

- `marker(k)` joins `title`/`prefix`/`suffix`/`conj` as a closure, and
  both the chain stop and P5's guard route through it. "Is this piece a
  marker" was spelled three ways in one function.
- The reserve gets a comment at its own site. It sat under a block
  entirely about #397 and read as covered by it, with its actual reason
  300 lines up in a docstring. `leaving`/`going` collapse to one name
  for one concept, matching what `group()` already calls the same
  value.
- The marker pass keeps only the half of its comment that still lives
  there; which pieces the span covers is `_maiden_span`'s to state, and
  it was being stated in two places 400 lines apart.

Two prose corrections, both measured false by review:

- The Arabic row claimed it "reaches the same arithmetic through script
  segmentation as well". It does not -- segments and effective script
  are identical to the Latin row's, so what it adds is vocabulary
  coverage, not a second code path. A note that names a mechanism gets
  believed.
- decisions.md recorded the reorder's cost as "P3's rootname count
  dropping when the maiden words leave early". Under the principle this
  PR establishes that is not a cost, it is the second fix -- the branch
  was asserting a principle six lines below a sentence asserting its
  opposite. #418 now tracks the P3 count on its own, and the narrow
  reorder nobody had weighed (marker pass after P2/P3, before P5) is
  recorded beside the one that was rejected.

The new piece-level test asserted more than its name: "no wide piece
holds a marker" is false in general and contradicts the test directly
above it, which pins the particle chain building exactly that. It held
only because of which strings the loop listed, so extending the list --
the obvious next move -- would have produced a failure that misdescribed
the defect. Keyed on the bound-given token now; verified to catch the
same two violations when the guard is reverted.

Two findings were behavior changes and are filed rather than applied:
#417 (the chain stop restates the consumer's condition instead of
sharing it, stranding a marker in 'Jane van der Berg nee Jr Jones') and
#418 (P3's count has the identical defect this PR fixed in P5's, and
unlike #411's own shape it moves 7 corpus names).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@derek73
derek73 merged commit 8c11633 into master Aug 22, 2026
11 checks passed
@derek73
derek73 deleted the fix/411-bound-given-reserve branch August 22, 2026 04:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Abd van der Berg née Jones reports no family name — the bound-given join counts words the maiden name takes away

1 participant