fix(post_rules): H1 counts name words, not annotations (#410) - #444
Merged
Conversation
A title followed by one name word names the family, but the guard declined whenever a suffix, a nickname or a maiden name stood beside it, so "Dr. Smith née Jones" read given 'Smith' with no family where "Dr. Smith" reads family 'Smith'. None of the three is a name word: each stands beside the name rather than in it. The rule and its implementation move together -- H1 said "and nothing else", which the code read as "and no annotation either", and test_doc_citations couples the two. H1 now says name words, carries the maiden example that showed the gap, and names the rules it meets there; M2 points back at H1, which #410 asked for. corpus_rules.jsonl is generated from the doc's example lines, so it regenerates here to take the new ones in, and two recorded ledger effects move by exactly those names. The v1 suite already recorded the correct reading for the nickname half as a strict xfail; it passes now and the marker goes. The corpus-wide maiden-clause invariant loses the shape it was skipping and gains M2's own boundary in its place. N3 moves with it: "'Smitty' Dr. Jones" declines N3's one-piece count as it always did, and H1 now names the family behind it, so the rule and its decision entry stop claiming an empty family there. Closes #410 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One rule each at 2.0.0 and 2.1.0, keyed on the names by literal rather than on a fields-only shape: "a title, one name word, and an annotation" is a parse tree, not a string, and a fields-only rule would classify every future given/family swap in the corpus. 1.4.0 takes TWO rules. Three of the names move given/family there as they do at the later baselines, but 'Dr. Smith née Jones' moves four fields -- that baseline has no maiden support, so the marker sat in `middle` and the birth surname in `family` -- and one rule carrying the union of those fields would let the other three move a middle or a maiden name unnoticed. At the 1.4.0 baseline this also takes three of them off fix(suffix-routing), which was absorbing them silently -- its heading went 14 -> 17 while _CORPUS_CLAIMS, recording the whole corpus for a rule with no name_regex, could not see the growth. Same surgery as #372's names, on the same catch-all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
H1 gains the decision entry it never had -- its rationale is doing normative work now, so the argument needs a durable home and a history: pointer like every other rule. #410 leaves Open (M2), and the two places that cited its lone-residual shape as a standing boundary stop describing behavior that no longer exists. The entry is explicit that the rationale is stated for H1 alone: N3 counts a suffix the other way, and which reading is right for a nickname-led name is not decided here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five corpus names change reading, not four: `Freiherr von Richthofen V` moves too, and it never surfaced because the existing fix(#424) rule's fields are a superset of its narrowed diff -- a real movement absorbed by a green gate. It is also the suffix flavor, which three ledger comments claimed no corpus carries. N3's new sentence swept in the given-name titles it should have carved out (`'Smitty' Sir John` names no family), the release log contradicted itself inside one unreleased section, and H1's known gap -- the statement counts name words, the guard tests which roles are unoccupied -- is now on record rather than implied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #444 +/- ##
==========================================
- Coverage 98.60% 98.60% -0.01%
==========================================
Files 45 45
Lines 3091 3090 -1
==========================================
- Hits 3048 3047 -1
Misses 43 43 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ew round The skip predicate is reverted: widening it with `nickname` was backwards. A nickname-only name belongs in the skip class, not out of it -- '(Bud) née Jones' reads given 'née', family 'Jones' and no maiden, M2 having left the marker a word -- so the widening would have sent such a name to an assertion it must fail. `maiden` was worse than wrong: the parametrization filters out every name carrying a marker, so the term could never fire. The nickname case is documented in the docstring, where it belongs, instead. fix(#424)'s `fields` is narrowed to drop `given`, closing the absorber the previous commit only described. Since #410 that name's diff is {family, suffix}; the declaration kept claiming {given, family, suffix}, and a subset match let the diff shrink underneath it without a word from any gate. All three baselines stay at exit 0. The rest is prose the reviewers caught: two ledger comments claimed the suffix flavor appears in no corpus, the known-gap paragraph counted tokens where it meant units, H1 was missing from the module docstring's history list and the guard comment had no pointer to the gap recorded against it, and the release log's "one of them reaches the default name order" framing is retired rather than incremented -- nearly all of this cycle's fixes reach it. Two stale counts beside machine-checked twins are recounted: 34 -> 46 and 47 -> 63 (with 11 -> 9 alongside it). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four independent mutations of the widened guard survived the whole suite: declining on any comma, on a second maiden token, on a second suffix token, and on a nickname and a suffix together. The rule fires across far more shapes than the four single-annotation rows pinned it at, and each mutation is a plausible mis-rewrite of it. "Dr. King, Jr." is the one that matters most. It is the most ordinary real-world shape the rule touches, and nothing in the suite could see the class: the corpus-wide maiden-clause property test filters commas out of its parametrization, and "Smith, Dr." takes its family from the comma rule rather than from H1. 1.4.0 had the same empty family, so the row records a v1 bug fixed. The other four cover the arities and combinations the single- annotation rows cannot reach, including the given-name carve-out at a two-word maiden clause -- the rule now says "whatever maiden name stands beside it", and the "whatever" was asserted at one word only. Two smaller repairs: tests/test_titles.py lost its assertion that `first` is empty when the expectation moved to `last`, and the carve-out row's note claimed `given` without saying that this is a default-order claim -- under either family-first order that name reads family 'John', H1 being a no-op once assign has placed the word. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ragraph form The #410 entries came in hard-wrapped at ~70 columns; every other entry in this file is one paragraph on one line, its neighbours running 728 and 5,211 characters. Pure reflow: no rewording, no re-paragraphing, and the doc parsers see the same text. 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 #410.
Dr. Smithreads familySmith;Dr. Smith née Jonesread givenSmithwith no family. H1's "and nothing else" guard counted a suffix, a nickname or a maiden name as a further name word, and all three suppressed the rule — one term in one guard, so the term went rather than the maiden role being special-cased.Dr. Smith née Jonesfirst='Smith'middle='née'last='Jones'given='Smith'family=''family='Smith'maiden='Jones'Dr. Smith PhDfirst='Smith'last='PhD'given='Smith'family=''family='Smith'suffix='PhD'Dr. "Smitty" Smithfirst='Smith'last=''given='Smith'family=''family='Smith'Sir John née Jonesfirst='John'middle='née'given='John'family=''Five corpus names change reading. Four arrive as new diffs and are classified in all three ledgers. The fifth,
Freiherr von Richthofen V, was already claimed by thefix(#424)rule: this change narrows its diff from{given, family, suffix}to{family, suffix}, and sinceclassify()accepts a rule whose declaredfieldsare a superset of the observed diff, a shrinking diff kept matching and no run ever named the movement. That declaration is now narrowed to match, so the nextgivenregression on that name surfaces instead of being absorbed.At the 1.4.0 baseline three of the four were being absorbed by the fields-only
fix(suffix-routing)catch-all (heading 14 → 17), invisible to_CORPUS_CLAIMSbecause that constant records the whole corpus for a rule with noname_regex. They have their own rules now, as #372's names got one off the same catch-all.N3 moves without changing:
'Smitty' Dr. Jonesdeclines its one-piece count as always, and H1 then names the family. Its Accepted clause dropped a half that is no longer true and gained two executable examples, including'Smitty' Sir John, where a given-name title means no family is named after all.H1 also gains the
decisions.mdentry it never had, including one thing left unfixed on purpose: the statement says "exactly one name word", but the guard tests which roles are unoccupied and retags every given token, soFreiherr de V Jryields familyde V. No corpus name or example reaches it; recorded rather than repaired, since this change is about which roles disqualify the rule, not how it counts.The v1 suite already shipped the correct reading for the nickname shape as a strict xfail (
tests/test_nicknames.py::test_nickname_and_last_name_with_title); it passes now and the marker is gone.Review rounds. Six in total (spec compliance, design-docs ×2, full-branch code review, then comment / test-coverage / code review on the open PR). No Critical code findings at any point — the behavior change reproduces as exactly the intended
given→familymove across 16,000 parses and six policies. What they found was prose, and mutation gaps: four independent guard mutations survived all 5,819 tests, the worst being that H1 could be made to decline on every comma-bearing name unnoticed, because the one broad property test filters commas out of its corpus. Fivecases.pyrows now pin those shapes —Dr. King, Jr.among them, which 1.4.0 also read with an empty family — and each kills exactly one mutation.Verification: suite 5844 passed;
compare.pyexits 0 at 1.4.0, 2.0.0 and 2.1.0; mypy and ruff clean. Three review rounds (spec compliance, design-docs ×2, full-branch code review) found 16 issues, all in prose, all fixed — the code survived a 16,000-parse sweep across six policies with every diff being exactly the intendedgiven→familymove.🤖 Generated with Claude Code