-
Notifications
You must be signed in to change notification settings - Fork 112
Expand file tree
/
Copy pathexpected_changes.toml
More file actions
202 lines (190 loc) · 11.1 KB
/
Copy pathexpected_changes.toml
File metadata and controls
202 lines (190 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# Every rule needs `issue`; optional `name_regex` and `fields` narrow
# it. An unexplained diff is a release blocker until classified (spec
# S5). Rules are seeded from docs/superpowers/plans/notes-m12-diffs.md
# and the `classification="fix(...)"` rows in tests/v2/cases.py; keep
# each entry's `name_regex`/`fields` as tight as the diff allows.
# File order is not load-bearing: compare.py sorts `name_regex` rules
# ahead of `fields`-only ones before matching.
[[change]]
issue = "fix(#271/#272) native-script CJK: family-first order, hangul segmentation, the kana license and the nakaguro"
# '毛 泽东', '김민준': script_orders flips first/last for a name written
# wholly in Han or Hangul, and the Korean surnames that now ship as
# default vocabulary additionally split an unspaced hangul token into
# last + first. '高橋 みなみ', 'マイケル・ジャクソン': #272 extends the
# same order table to kana-bearing Japanese names and makes the
# katakana middle dot a token separator, which moves pieces between the
# same three fields. Seeded from the fix(#271) and fix(#272) rows in
# tests/v2/cases.py.
#
# One rule, not two, because it is one diff shape: both issues move
# name pieces between first/middle/last on native-script CJK input, and
# splitting the class by issue would need a rule per script with no
# gain in tightness (the fields list is already the narrow half).
#
# Scoped by script, which is exactly the scope of the behavior --
# nothing here can touch a name with no CJK character in it. Han's
# astral block (U+20000-U+323AF) is deliberately left out of the class:
# no name in either corpus reaches it, and a rule should be no wider
# than the diffs it has to explain. Extend it -- and the sync pin in
# tests/v2/test_regex_sync.py, which fails if the two disagree -- if
# one ever does. The kana spans are BMP-only for a different reason:
# supplementary-plane kana is hentaigana and other archaic forms no
# modern name uses, so _SCRIPT_RANGES does not list it either.
#
# U+FF65 is the one span here that _SCRIPT_RANGES does NOT have, and
# it is deliberate: the halfwidth middle dot separates tokens like its
# fullwidth twin, so 'マイケル・ジャクソン' splits where 1.4 left one token,
# but halfwidth kana is excluded from CLASSIFICATION on purpose (a
# separate normalization problem). The rest of the halfwidth block
# (U+FF66-U+FF9F) is left out for the usual tightness reason, and it
# was measured rather than assumed: a dotless halfwidth name such as
# 'マイケル ジャクソン' is byte-identical on both sides, so covering the
# block would pre-excuse a future regression on a shape that is
# parity today. The dot is the whole mechanism; the dot is the whole
# span.
#
# corpus_cjk.jsonl (#295) exists so this rule fires in every real
# run: build_cjk_corpus.py harvests every CJK-bearing text in
# tests/v2/cases.py -- the fix(#271)/fix(#272)/fix(shime-mark) rows
# AND the parity rows, whose value here is pinning NON-diffs (マイケル
# must not change against 1.4) -- closing the blind spot the other
# two corpora have by construction: v1's banks had no reason to test
# CJK, and build_issues_corpus.py requires an internal space, which
# unspaced names never have.
name_regex = "[\\u3005-\\u3006\\u3040-\\u309F\\u30A0-\\u30FF\\u3400-\\u4DBF\\u4E00-\\u9FFF\\uF900-\\uFAFF\\uAC00-\\uD7A3\\uFF65-\\uFF65]"
fields = ["first", "middle", "last"]
[[change]]
issue = "fix(#274) maiden markers consumed"
name_regex = "(?i)\\b(n[ée]e|born|geb\\.?|roz\\.?)\\b"
fields = ["maiden", "middle", "last"]
[[change]]
issue = "fix(comma-family) lone post-comma piece routes to suffix/title, not first"
# 'Smith, Dr.' / 'Andrews, M.D.': v1 put the lone strict-suffix-or-title
# post-comma piece in `first`; 2.0 routes it to `suffix`/`title` instead
# (family/`last` is unchanged either way -- "pre-comma is definitionally
# family").
name_regex = ","
fields = ["first", "title", "suffix"]
[[change]]
issue = "fix(suffix-routing) two-token name with unambiguous trailing suffix stays suffix"
# 'Johnson PhD' / 'Mr. Johnson PhD': v1 routed a lone trailing suffix
# to family/first (no comma present); 2.0 keeps recognized suffixes in
# `suffix`.
fields = ["first", "last", "suffix"]
[[change]]
issue = "fix(suffix-delimiter-rendering) no-space delimiter core token kept whole"
# Only fires when a custom suffix delimiter is configured (Policy /
# Constants.suffix_delimiters); the corpus runs with default policy, so
# this rule is expected to match nothing here. Kept for documentation
# parity with tests/v2/cases.py's 'suffix_delimiter_no_space_core' row
# (anti-#100, migration plan deviation 5).
name_regex = "/"
fields = ["suffix"]
[[change]]
issue = "ambiguous-surname-acronym data change: parenthesized (MA)/(DO) now stays nickname"
# suffix_acronyms_ambiguous gained 'ma'/'do' so bare 'Jack Ma' keeps its
# surname (v1 parity restored); side effect: parenthesized/quoted "MA"
# or "DO" no longer escape to suffix (v1 did, since v1 treated them as
# unambiguous there) -- they now fall through to nickname parsing.
# Not expected to fire against this corpus (no such strings survived
# into the v1 banks); kept for documentation completeness.
name_regex = "(?i)[(\"'](m\\.?a\\.?|d\\.?o\\.?)[)\"']"
fields = ["suffix", "nickname"]
[[change]]
issue = "feat(#269) Arabic بن prefix chains onto family (non-Latin new-recognition)"
# 'محمد بن سلمان': #269 adds the native-script Arabic
# patronymic particle بن ("bin"/"son of") to PREFIXES/
# NON_FIRST_NAME_PREFIXES. v1 had no such entry, so it left بن a plain
# middle-name token ('سلمان' alone as last); 2.0 now chains it onto the
# family the same way 'von'/'bin' (Latin) do, giving family 'بن سلمان'.
# This is new-recognition on non-Latin input -- the exact behavior
# #269 exists to add -- not a Latin-corpus regression, so it is
# classified rather than reverted.
# Word-bounded: a bare "بن" would also match the substring inside e.g.
# لبنان ("Lebanon") and silently absorb unrelated middle/last diffs.
name_regex = "\\bبن\\b"
fields = ["middle", "last"]
[[change]]
issue = "feat(#273) typographic nickname delimiters recognized by default"
# 'John “Jack” Kennedy', 'Hans „Hansi“ Müller', 'Jean «Petit» Dupont':
# v1 knew only straight quotes and parentheses, so a smart-quoted or
# guillemet-wrapped nickname leaked into `middle` as literal text.
# Matches only when such a delimiter is actually present -- the ASCII
# pairs stay unclassified, since a diff there would be a regression.
name_regex = "[“”„«»「」『』()]"
fields = ["middle", "nickname"]
[[change]]
issue = "fix(cjk-delimited-nickname) delimiter recognition compounds with the CJK order flip"
# '山田「タロ」太郎', '山田 太郎 (マイケル・ジャクソン)': one name, two
# intended changes at once -- the corner-bracket/nakaguro handling
# (2.0's typographic delimiters; 2.1's dot separator rendering the
# nickname join with a space) changes `nickname`, while the same
# name's wholly-CJK remainder takes the 2.1 family-first flip in
# first/last. Neither single-change rule may claim the union (the
# delimiter rule's fields exclude first/last, the CJK rule's exclude
# nickname -- each on purpose, so a lone regression in the other's
# fields stays loud). Both lookaheads are required: the delimiters
# alone would match 'John 「Jack」 Kennedy' -- the brackets sit in CJK
# Symbols and Punctuation, OUTSIDE every classified span, so a Latin
# name can carry them -- and would then absorb a bare first/last
# regression on it. Requiring a classified codepoint too confines the
# rule to names the order flip can actually reach. The second
# lookahead's class is the same hand copy of _SCRIPT_RANGES the rule
# above carries, pinned by the same sync test. One absorption is
# inherent and accepted: a nickname-ONLY diff on a nakaguro name
# ('マイケル・ジャクソン') classifies here, because subset-matching
# cannot REQUIRE a field to have changed. Positional note: this rule
# overlaps the CJK rule above and the delimiter rule below; within
# the name_regex tier file order decides which label a diff reports
# under, so it sits after the tighter single-change rules on purpose.
# The issue slug deliberately avoids the literal #271/#272 strings --
# test_regex_sync's differential pin selects the canonical CJK rule
# by those substrings and asserts it is unique.
name_regex = "(?s)(?=.*[「」『』・・])(?=.*[\\u3005-\\u3006\\u3040-\\u309F\\u30A0-\\u30FF\\u3400-\\u4DBF\\u4E00-\\u9FFF\\uF900-\\uFAFF\\uAC00-\\uD7A3\\uFF65-\\uFF65])"
fields = ["first", "last", "nickname"]
[[change]]
issue = "feat(#269) non-Latin titles/conjunctions recognized"
# 'г-н Иван Петров' (Cyrillic title), 'Хосе И Мария Сантос' (Cyrillic
# conjunction И joining two given names). v1 shipped Latin-only
# vocabulary, so these were plain name tokens and everything shifted
# one position. New recognition on non-Latin input -- the point of
# #269 -- not a Latin-corpus regression.
#
# Cyrillic only, and only the three fields the shift can touch. The
# first draft allowed five fields across four scripts, which put every
# Arabic name in the corpus -- including ones that are currently
# PARITY -- into its shadow, and pre-excused Greek and Hebrew, against
# which nothing has ever been compared. A leading title or an absorbed
# conjunction moves a word from `first` to `title`, or from `middle`
# into `first`; it cannot reach `last` or `suffix`. Widen this only
# with a name that demonstrates the need, and prefer a new rule per
# script over loosening this one -- the \bبن\b rule below is the
# model.
name_regex = "[\\u0400-\\u04FF]"
fields = ["title", "first", "middle"]
[[change]]
issue = "fix(leading-credential) a split 'Ph. D.' before the name stays one unit"
# 'Ph. D. John Smith'. v1 healed the adjacent 'Ph.'/'D.' pair only when
# it TRAILED; leading, it split them -- title 'Ph.', given 'D.', and
# the real given name pushed to middle. 2.0 keeps the credential whole
# and routes it to `suffix`, which is the post-nominal field, so a
# leading credential lands there rather than in `title`. Slightly odd
# positionally, and still strictly better than manufacturing a given
# name out of 'D.'.
#
# Anchored to the START on purpose. Trailing 'Ph. D.' is PARITY and
# stays unclassified below; widening this regex would mask a
# regression in the shape that is the whole reason fix_phd exists.
name_regex = "^Ph\\. ?D\\."
fields = ["title", "first", "middle", "suffix"]
# Deliberately NOT a [[change]] rule: TRAILING 'Ph. D.' split-token
# healing ('John Ph. D.', 'John Smith, Ph. D.') is PARITY, not a 2.0
# behavior change -- v1's fix_phd healing of the adjacent 'Ph.'/'D.'
# pair into one suffix unit is replicated exactly by 2.0's 'joined'
# vocab tag (tests/v2/cases.py classification="parity" on both
# 'phd_split' and 'suffix_comma_split_phd'; verified empirically, zero
# diff against the 1.4 worker). Adding a suppression rule for it would
# risk masking a real regression in this exact shape, so it is
# intentionally left unclassified: if it ever starts diffing, the
# harness must fail. (The leading case above is a separate shape, which
# is why it needed its own anchored rule rather than a widened one.)