From 30291dbec168f6aa0160c8c3bb8644b7ab8378e6 Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Sat, 12 Sep 2026 13:12:43 -0700 Subject: [PATCH] chore(release): open the 2.4 cycle, and record the roster AGENTS.md missed Release checklist steps 8 and 9, run after 2.3.0 went to PyPI. - expected_since_2.3.0.toml, opened empty. DEFAULT_BASELINE and the ledger are coupled: _allowlist_for treats a missing file as a hard error, so the baseline cannot advance without it. - compare.py DEFAULT_BASELINE 2.2.0 -> 2.3.0. - VERSION (2,4,0) with PRE_RELEASE 'dev', normalizing to 2.4.0.dev0. The docstring's examples moved with it. CITATION.cff stays at 2.3.0 by design. - "* 2.4.0 - Unreleased" opened at the top of the release log. - The new ledger enrolled, empty, in every roster keyed by filename: _SPAN_BEARING_RULES, _CORPUS_CLAIMS, _CROSS_RULE_WINNERS and _ORDER_EXEMPTION_EFFECT in the test module; _RECORDED_DIFFS and _WATCHED_DIFFS in compare.py. The "open cycle" comments that sat on the 2.2.0 entries described a ledger with one rule and are dropped. AGENTS.md step 8 named five filename rosters; there are six. _ORDER_EXEMPTION_EFFECT arrived with f478fb0 (2026-09-02) after the list was last written, and surfaced as a red test_the_recorded_order_contests_are_what_the_ledgers_hold run with the new ledger enrolled nowhere -- which is also the fastest way to find them all, so step 8 now says to do exactly that, and lists the sixth. Verified: gate exit 0 at all five baselines (1.4.0/2.0.0/2.1.0/2.2.0 at 412/340/254/117 intentional, unexplained 0; 2.3.0 at 0/0, the correct day-one reading). 7816 tests, mypy and ruff clean, sphinx -W clean. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 22 +++++++++---- docs/release_log.rst | 4 +++ nameparser/_version.py | 10 +++--- tests/v2/test_ledger_guards.py | 10 +++--- tools/differential/compare.py | 6 ++-- tools/differential/expected_since_2.3.0.toml | 34 ++++++++++++++++++++ 6 files changed, 68 insertions(+), 18 deletions(-) create mode 100644 tools/differential/expected_since_2.3.0.toml diff --git a/AGENTS.md b/AGENTS.md index 24c297de..92304477 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -172,14 +172,17 @@ uv run sphinx-build -b html docs dist/docs # covers those is _CORPUS_CLAIMS, which records what every rule claims # -- its regex's corpus reach, its roles, and which names -- and so # needs no notion of how a copy is spelled. -# FOUR rosters are keyed by FILENAME and checked by EQUALITY, so a new +# SIX rosters are keyed by FILENAME and checked by EQUALITY, so a new # ledger must be enrolled in every one of them on the day it lands, even -# empty -- each fails loudly and names itself, but that is four separate -# red runs if you add them one at a time. Three sit in the test module -# and one in tools/differential/compare.py; a fifth, compare.py's -# _RECORDED_DIFFS, is enrolled transitively, since the guard holds -# set(_RECORDED_DIFFS) == set(_CROSS_RULE_WINNERS). Two more are keyed -# by rule CONTENT and apply only where such a rule exists. +# empty -- each fails loudly and names itself, but that is six separate +# red runs if you add them one at a time. Four sit in the test module +# and two in tools/differential/compare.py. Faster than reading this +# list: create the ledger, move DEFAULT_BASELINE, and run +# tests/v2/test_ledger_guards.py once -- every roster still missing the +# file fails by name. (Opening the 2.4 cycle found _ORDER_EXEMPTION_EFFECT +# that way; it landed with f478fb0 after this list was last written.) +# Two more are keyed by rule CONTENT and apply only where such a rule +# exists. # Required, by filename: # - _SPAN_BEARING_RULES: add the filename, mapped to the set of issue # tags whose rules carry a script-span class (empty set if none). @@ -201,6 +204,11 @@ uv run sphinx-build -b html docs dist/docs # run itself refuses, pre-worker, a ledger missing from either # shape roster -- an empty section is a statement, a missing one is # nobody having looked -- so the guard and the run agree on it. +# - _ORDER_EXEMPTION_EFFECT: add the filename mapped to [] while the +# ledger has no order-decided contest. It is the recorded negative +# control for precedes_narrower -- every contest measured with +# exemptions ignored -- so a row appears when two rules land that +# file order alone arbitrates, whether or not one declares it. # Conditional, by rule content: # - _HONORIFIC_SOURCES: if the ledger has a CJK honorific rule, add a # substring of its issue (keyed that way, not by tag) mapped to the diff --git a/docs/release_log.rst b/docs/release_log.rst index e15e2f1a..63f70bc4 100644 --- a/docs/release_log.rst +++ b/docs/release_log.rst @@ -1,5 +1,9 @@ Release Log =========== +* 2.4.0 - Unreleased + + nameparser 2.4 is under development. + * 2.3.0 - September 12, 2026 nameparser 2.3 is parsing fixes and new honorific vocabulary; diff --git a/nameparser/_version.py b/nameparser/_version.py index d7df1fcc..7a45feb7 100644 --- a/nameparser/_version.py +++ b/nameparser/_version.py @@ -4,16 +4,16 @@ #: #: Bumped when a cycle OPENS, not when it ships, so the tree says what #: it is building. Note the cost of the tuple being purely numeric: it -#: cannot carry the dev marker, so `VERSION >= (2, 2, 0)` is already -#: true here while 2.2.0 is unreleased. Compare `__version__` instead +#: cannot carry the dev marker, so `VERSION >= (2, 4, 0)` is already +#: true here while 2.4.0 is unreleased. Compare `__version__` instead #: where that distinction matters. -VERSION = (2, 3, 0) +VERSION = (2, 4, 0) #: PEP 440 pre-release/dev segment appended to the numeric version, or #: "" for a final release. Joined WITHOUT a dot ("2.0.0rc1", not #: "2.0.0.rc1"); setuptools reads __version__ as the package version. #: #: "dev" through the cycle, cleared at release. It normalizes to -#: 2.3.0.dev0, which sorts above 2.2.0 and BELOW 2.3.0, so an install +#: 2.4.0.dev0, which sorts above 2.3.0 and BELOW 2.4.0, so an install #: from master can never masquerade as the release it precedes. -PRE_RELEASE = "" +PRE_RELEASE = "dev" __version__ = ".".join(map(str, VERSION)) + PRE_RELEASE diff --git a/tests/v2/test_ledger_guards.py b/tests/v2/test_ledger_guards.py index ccff26f9..9a24336a 100644 --- a/tests/v2/test_ledger_guards.py +++ b/tests/v2/test_ledger_guards.py @@ -354,9 +354,8 @@ def test_script_ranges_membership_is_decided() -> None: "fix(#298)", # the 间隔号 lookahead }), "expected_since_2.1.0.toml": frozenset(), # 2.2 cycle: no span-bearing rule - # open cycle: its one rule, fix(#462), is a Latin letter shape and - # copies no script range - "expected_since_2.2.0.toml": frozenset(), + "expected_since_2.2.0.toml": frozenset(), # 2.3 cycle: no span-bearing rule + "expected_since_2.3.0.toml": frozenset(), # open cycle, no rules yet } #: The leading `fix(...)`/`feat(...)` tag of a rule's `issue`, which is @@ -3367,6 +3366,7 @@ def _claim(rule: dict) -> _Claim: _Claim(17, ('_ambiguities', 'family', 'given', 'middle', 'suffix', 'title'), "ef4a7afe791a", None), }, + "expected_since_2.3.0.toml": {}, # open cycle, no rules yet } @@ -3467,8 +3467,9 @@ def test_every_rule_claims_the_recorded_share_of_the_corpus() -> None: #: default-order only, the blind spot `orders` (#468) opened. One copy #: of each fact, since two means one of them goes quietly stale. _CROSS_RULE_WINNERS: dict[str, dict[str, str]] = { - # open cycle: one rule, so nothing for a second one to contest "expected_since_2.2.0.toml": {}, + # open cycle: no rules, so no contest + "expected_since_2.3.0.toml": {}, "expected_since_1.4.0.toml": { # Spelled out since #508: the bare `fix(comma-family)` this row # carried is a prefix of THREE rules in this ledger (measured @@ -4978,6 +4979,7 @@ def test_a_rule_reaching_no_corpus_name_says_why_it_is_kept() -> None: ], "expected_since_2.1.0.toml": [], "expected_since_2.2.0.toml": [], + "expected_since_2.3.0.toml": [], # open cycle, no rules yet } diff --git a/tools/differential/compare.py b/tools/differential/compare.py index 549a344d..9c2e62fc 100644 --- a/tools/differential/compare.py +++ b/tools/differential/compare.py @@ -31,7 +31,7 @@ FIELDS = ("title", "first", "middle", "last", "suffix", "nickname", "maiden") -DEFAULT_BASELINE = "2.2.0" +DEFAULT_BASELINE = "2.3.0" REPO_ROOT = HERE.parents[1] #: The v2 API's names for the same seven roles FIELDS names in v1 #: vocabulary. Both are compared from baseline 2.0 on. @@ -1746,8 +1746,9 @@ class _ShapeMismatch(NamedTuple): #: every run. 'Nguyen, Van' is classified by nothing only because it #: diffs from nothing. _RECORDED_DIFFS: dict[str, dict[str, tuple[str, ...]]] = { - # open cycle: one rule, so nothing for a second one to contest "expected_since_2.2.0.toml": {}, + # open cycle: no rules, so nothing for a second one to contest + "expected_since_2.3.0.toml": {}, "expected_since_1.4.0.toml": { "Andrews, M.D.": ("given", "suffix"), "田中, 太郎さん": ("given", "suffix"), @@ -2151,6 +2152,7 @@ class _ShapeMismatch(NamedTuple): "Lala Lajpat Rai": ("family", "middle", "suffix"), "Smith, John E, III, Jr": ("_initials",), }, + "expected_since_2.3.0.toml": {}, # open cycle, no watched name yet } diff --git a/tools/differential/expected_since_2.3.0.toml b/tools/differential/expected_since_2.3.0.toml new file mode 100644 index 00000000..952867fa --- /dev/null +++ b/tools/differential/expected_since_2.3.0.toml @@ -0,0 +1,34 @@ +# Ledger for baseline 2.3.0 -- what changes for a user upgrading from +# the previous minor. Same rule grammar as the other ledgers: `issue`, +# `name_regex` and `fields` are every one of them required. Neither +# narrowing key may stand on its own -- #451 banned a rule with +# `fields` and no regex, #456 banned the reverse -- so a rule here +# narrows by name AND by role, never by just one. +# +# Opened empty the day 2.3.0 shipped (AGENTS.md release step 8), and it +# is opened before it has anything to say because DEFAULT_BASELINE and +# the ledger are coupled: _allowlist_for treats a missing file as a +# hard error, on the reasoning that an absent ledger classifies nothing +# and would make every diff report as unexplained. So the baseline +# could not advance to 2.3.0 without this file. +# +# Do not copy rules across from expected_since_2.2.0.toml: that ledger +# classifies what 2.3 changed on top of 2.2, and a rule copied without +# checking either over-matches -- hiding a real 2.4 regression behind a +# 2.3-era label -- or never fires at all. Where a 2.4 change is visible +# from several baselines the rule may legitimately appear in more than +# one file, but each copy is checked against its own run rather than +# assumed. +# +# tests/v2/test_differential.py permits exactly one empty ledger, the +# one DEFAULT_BASELINE names -- which is this file. +# +# tests/v2/test_ledger_guards.py and tools/differential/compare.py +# enroll this file, empty, in every roster keyed by ledger filename +# (AGENTS.md release step 8 lists them). The first rule that copies a +# script range, a CJK honorific or a Latin vocabulary has to be +# recorded in its roster or the sweep fails. +# +# There is deliberately no `change = []` line. TOML forbids appending a +# [[change]] table to a statically defined array, so that line would +# block the first entry.