finetype-model 0.6.49

Candle-based transformer model for FineType
Documentation
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
//! `guards.rs` — the deterministic post-Sense Sharpen guard layer:
//! the `apply_post_sharpen_guards` dispatcher and its substance-guard /
//! veto / recovery leaves, extracted from mod.rs (mechanical split, no
//! behaviour change). These attach to `ColumnClassifier` as a second impl.

use super::*;

/// Fraction of non-empty values carrying an organisation / investment-vehicle
/// suffix token — the value-side signal for `org_name_geography_demotion`. A place
/// name never carries one (US states / countries / regions / cities incl. `West
/// Bank` → 0.00), while a company/fund column is dense with them (gleif `name` →
/// 0.97). Deliberately DISTINCT from (and broader than) the entity classifier's
/// `org_suffixes` regex: that one is tuned narrow for person-vs-org demotion and
/// lacks the modern fund suffixes (`Fund`,`Capital`,`Trust`,`LP`,`Holdings`,…) that
/// dominate registry data, and it includes place-ambiguous tokens (`Bank`,`Co`,`SA`)
/// this guard must exclude to leave `West Bank`/`Cork` alone.
///
/// A match counts ONLY when the value has ≥2 whitespace tokens — i.e. the suffix is
/// a suffix OF a longer name (`DEUTZ AG`, `Kaanapali Land, LLC`), never the whole
/// value. This is load-bearing: several 2-letter company forms (`AB`,`AG`,`NV`,`SE`,
/// `BV`) are also US/Canada state codes and ISO country codes, so a bare-code `state`
/// column of `AB` (Alberta) / `country_code` of `SE` (Sweden) would otherwise match
/// at 100% and be wrongly demoted (corpus spot-check finding, mirrors the
/// constant-column lesson).
fn org_suffix_ratio(values: &[String]) -> f32 {
    use std::sync::OnceLock;
    static RE: OnceLock<regex::Regex> = OnceLock::new();
    let re = RE.get_or_init(|| {
        // `\b`-delimited (the Rust regex crate has no look-around); calibrated
        // gleif `name` 0.97 vs every place / bare-geo-code category 0.00.
        regex::Regex::new(concat!(
            r"(?i)\b(Inc|Incorporated|LLC|LLP|LLLP|LP|Ltd|Limited|Corp|",
            r"Corporation|PLC|GmbH|AG|NV|BV|SE|OYJ|ASA|AB|Company|Fund|Funds|Trust|",
            r"Capital|Holdings|Holding|Partners|Partnership|Advisors|Advisers|",
            r"Management|Portfolios|Portfolio|Ventures|Associates|Securities|Insurance|",
            r"Investments|Investment|Bancorp|Bancshares|Group|Foundation|Enterprises|",
            r"Industries|Technologies|Solutions)\b",
        ))
        .expect("org_suffix_ratio regex")
    });
    let non_empty: Vec<&String> = values.iter().filter(|v| !v.trim().is_empty()).collect();
    if non_empty.is_empty() {
        return 0.0;
    }
    non_empty
        .iter()
        .filter(|v| v.split_whitespace().count() >= 2 && re.is_match(v))
        .count() as f32
        / non_empty.len() as f32
}

/// True when a value looks geographic — the value discriminator for
/// [`ColumnClassifier::region_nonmembership_veto`]. A value is a place if it is a
/// place NAME (`place_names`: GeoNames admin1 + countries + cities≥15k), an
/// ISO-3166-2 subdivision code (`US-TX`), a bare US/CA/AU state code (`CA`/`NV`),
/// or a `City, State` / `City (State)` composite whose `,`/`/`/`(`/`)`-delimited
/// parts are place names. Bare state codes are included at the value level so a
/// genuine state column stays, yet seismic `net` (a few state-named networks among
/// many non-geo codes) still falls below the veto's 50% column bar.
fn value_is_place(v: &str) -> bool {
    let t = v.trim();
    if t.is_empty() {
        return false;
    }
    if finetype_core::membership::place_names(t)
        || finetype_core::membership::iso_3166_2(t)
        || STATE_CODES.contains(&t.to_ascii_uppercase().as_str())
    {
        return true;
    }
    // Composite `City, State` / `City (State)` — any delimited part is a place name.
    t.split([',', '/', '(', ')'])
        .any(|part| finetype_core::membership::place_names(part.trim()))
}

impl ColumnClassifier {
    /// Guards that must fire on the POST-sharpen label — including labels a
    /// header hint created via an early `return` inside `apply_header_sharpen`.
    /// Every value-identical-boundary guard whose target a hint can synthesise
    /// belongs here, where it runs unconditionally, not inside
    /// `apply_header_sharpen` where the hint branches would make it unreachable.
    pub(crate) fn apply_post_sharpen_guards(
        &self,
        result: &mut ColumnResult,
        header: &str,
        sample: &[String],
        values: &[String],
    ) {
        self.amount_bare_number_veto(result, header, sample);
        self.url_bare_number_veto(result, header, sample);
        self.utc_bare_number_veto(result, header, sample);
        self.checksum_substance_guard(result, header, sample);
        self.membership_substance_guard(result, header, sample);
        self.isbn_header_recovery(result, header, sample);
        self.binary_vocab_veto(result, header, sample, values);
        self.increment_substance_veto(result, values);
        // AFTER increment_substance_veto: the sequential-detection promotion
        // routes code columns through `increment`, and the veto lands them on
        // integer_number — this recovery must see that final integer, not the
        // intermediate increment.
        self.numeric_code_header_recovery(result, header, sample);
        self.unlocode_format_veto(result, sample);
        self.city_region_header_corroboration(result, header, sample);
        self.country_code_corroboration(result, header, sample);
        self.geo_code_membership_vote(result, header, sample);
        self.geo_code_nonmembership_demotion(result, header, sample);
        self.geo_subdivision_membership_promote(result, header, sample);
        // AFTER the geo-membership promotes (a real ISO subdivision has 0% org
        // suffix, so it is never in scope): demote an org-name column that the
        // model mistyped as a place (gleif `name`->region) to entity_name.
        self.org_name_geography_demotion(result, sample);
        // AFTER the geo promotes AND org-name demotion: a real ISO subdivision
        // (US-TX) is protected by iso_3166_2 membership and a company name is
        // already entity_name, so this veto only sees a residual `region` overcall
        // on a non-place column (usgs net/type, gleif category).
        self.region_nonmembership_veto(result, header, sample);
        // AFTER geo_subdivision_membership_promote: keeps the geo-membership
        // promotes grouped. UN/LOCODE (`USLAX`) is hyphenless, so the ISO-3166-2
        // promote above never claims it — no fight.
        self.unlocode_membership_recovery(result, sample);
        self.timezone_abbreviation_recovery(result, header, sample);
        self.naics_industry_recovery(result, header, sample);
        // ticker: header-gated membership promote — recovers finance.securities.ticker
        // from the state_code / word attractor (EDGAR ticker external-band finding).
        self.ticker_membership_recovery(result, header, sample);
        // tld: header-gated membership promote — recovers top_level_domain from the
        // continent overcall (majestic TLD external-band finding).
        self.tld_geography_recovery(result, header, sample);
        // Header-gated code recoveries grouped with naics: CPT and HS have no
        // check digit and their bare shapes are value-identical with ZIP / a
        // plain integer, so the header token is the sole discriminator.
        self.cpt_procedure_recovery(result, header, sample);
        self.hs_code_header_recovery(result, header, sample);
        // IMEI: 15-digit Luhn is NOT self-precise (a 15-digit Amex card is
        // Luhn-valid by construction), so this one is header-gated too.
        self.imei_checksum_recovery(result, header, sample);
        self.s_expression_recovery(result, sample);
        // color_rgb: anchored `rgb(`/`rgba(` prefix, self-precise like s_expression.
        self.color_rgb_recovery(result, sample);
        self.ceded_leaf_recovery(result, sample);
        // AFTER ceded_leaf_recovery: ISIN and ISRC share a 12-char shape, so the
        // regex-only ceded recovery mislabels digit-tailed ISINs as isrc. This
        // corrects that using the ISIN check digit (which the regex validator
        // cannot see), so it must run last to override the isrc misassertion.
        self.isin_checksum_recovery(result, sample);
        // Value-only checksum recoveries grouped with isin: CUSIP / SEDOL / DEA
        // each land on a value-identical id attractor (word / numeric_code /
        // alphanumeric_id), and their scheme-specific check digit — which the
        // regex-only ceded recovery cannot see — is the self-precise discriminator,
        // so no header gate is needed and they round-trip headerless.
        self.cusip_checksum_recovery(result, sample);
        self.sedol_checksum_recovery(result, sample);
        self.dea_checksum_recovery(result, sample);
        // LAST: the recovery guards above get first crack at relocating a
        // wrongly-jwt/mime column to a real type; these substance guards then demote
        // to `unknown` only what remains stubbornly labelled jwt/mime_type, and
        // nothing after can re-promote it via the shape-only validator.
        self.mime_type_substance_guard(result, sample);
        self.locale_code_substance_guard(result, sample);
        self.password_substance_guard(result, sample);
        self.jwt_substance_guard(result, sample);
    }

    /// `timezone_abbreviation_recovery` (default ON). Recovers the
    /// `datetime.offset.timezone_abbreviation` leaf (spec
    /// 2026-06-25-timezone-abbreviation-type). The 240-dim model cannot predict
    /// this mined leaf, and the header-hint machinery routes a `timezone` header
    /// to `datetime.offset.iana` — but a column of bare abbreviations (EDT/CEST)
    /// is not an IANA zone name (Region/City). Promote a residual / iana label to
    /// the abbreviation leaf when the values pass its closed-set validator AND the
    /// header corroborates a timezone column. The header gate is load-bearing:
    /// EST/CST/PST overlap estimate/cost, so the abbreviation set alone over-emits
    /// (corpus: 1,442/1,444 tz columns are uppercase, but a non-tz "EST" status
    /// column would still match the set — the header is what excludes it).
    /// Value-based (0048), RHH-disableable. CORROBORATION + veto-consistency gated
    /// like `structured_string_refinement`.
    fn timezone_abbreviation_recovery(
        &self,
        result: &mut ColumnResult,
        header: &str,
        sample: &[String],
    ) {
        if rhh::is_disabled("timezone_abbreviation_recovery") {
            return;
        }
        const LEAF: &str = "datetime.offset.timezone_abbreviation";
        // Precision rests on TWO gates, NOT a source-label allowlist: the header
        // must name a timezone column AND >=90% of values must pass the closed-set
        // validator. The model funnels uppercase abbreviations into 3-letter-code
        // attractors (country_code/iata_code) as readily as into a residual, so an
        // allowlist misses them — but a genuine country_code/iata column fails the
        // tz validator (US/LHR are not tz abbreviations) and carries no timezone
        // header, so neither gate admits it. Only the leaf itself is excluded.
        if result.label == LEAF || !header_corroborates_timezone(header) {
            return;
        }
        match self.taxonomy.as_ref() {
            Some(tax) if label_validates_sample(tax, LEAF, sample) => {
                result.label = LEAF.to_string();
                result.confidence = result.confidence.max(0.95);
                result.disambiguation_applied = true;
                result.disambiguation_rule = Some(format!(
                    "timezone_abbreviation_recovery:{}",
                    header.to_lowercase()
                ));
                result.detected_locale = None;
            }
            _ => {}
        }
    }

    /// `naics_industry_recovery` (default ON). Recovers the
    /// `identity.industry.naics` leaf (company-reference audit W3). The shipped
    /// 244-dim model cannot predict this leaf, and even its residual home
    /// (`numeric_code`) needs header rescue from the F5 leading-zero demotion —
    /// so a NAICS column reaches this guard as integer_number or numeric_code.
    /// Promote when the header names a NAICS column (`header_corroborates_naics`
    /// — the distinctive `naics` token; `sic` and bare `industry` deliberately
    /// excluded) AND ≥90% of values are members of the published Census code
    /// list (`membership::naics_codes`, labels/sets/naics_codes.txt). The
    /// two-gate discipline mirrors `timezone_abbreviation_recovery`: sector
    /// codes are value-identical with small integers, so the header gate is
    /// load-bearing; a quantity column fails the header gate, a naics-headed
    /// text column fails membership. Value-based (0048), RHH-disableable.
    fn naics_industry_recovery(&self, result: &mut ColumnResult, header: &str, sample: &[String]) {
        if rhh::is_disabled("naics_industry_recovery") {
            return;
        }
        const LEAF: &str = "identity.industry.naics";
        if result.label == LEAF {
            return;
        }
        let non_empty = non_empty_trimmed(sample);
        if non_empty.len() < 3 {
            return;
        }
        // Header gate, two tiers: the distinctive `naics` token admits any code
        // level, while a generic code-ish header (`code`, the real product
        // surface's bare header) is admitted only for >=4-digit codes — the
        // 2,129-code set makes 6-digit coincidence ~0.1%, but 2-digit sectors
        // span 11-92, which a rating/age-like column headed `code` could
        // fully occupy by accident.
        let mut lens: Vec<usize> = non_empty.iter().map(|v| v.len()).collect();
        lens.sort_unstable();
        let median_len = lens[lens.len() / 2];
        if !(header_corroborates_naics(header)
            || (header_corroborates_numeric_code(header) && median_len >= 4))
        {
            return;
        }
        let members = non_empty
            .iter()
            .filter(|v| finetype_core::membership::naics_codes(v))
            .count();
        // >=90% in the published code list (members*10 >= len*9).
        if members * 10 < non_empty.len() * 9 {
            return;
        }
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.95);
        result.disambiguation_applied = true;
        result.disambiguation_rule =
            Some(format!("naics_industry_recovery:{}", header.to_lowercase()));
        result.detected_locale = None;
    }

    /// `ticker_membership_recovery` (default ON). Recovers the
    /// `finance.securities.ticker` leaf (company-reference external band: EDGAR
    /// `ticker` was over-emitted as `geography.location.state_code`). The
    /// 244-dim model cannot predict this leaf, and a short uppercase symbol
    /// column lands on state_code / word / alphanumeric_id. Promote when the
    /// header names a ticker column (`header_corroborates_ticker` — `ticker` /
    /// `symbol`) AND ≥90% of values are US-listed symbols
    /// (`membership::us_tickers`, labels/sets/us_tickers.txt) AND ≥3 DISTINCT
    /// pass. The header gate is load-bearing: 15 of the 50 US state codes (`MA`,
    /// `TX`, …) are themselves real tickers, so membership alone cannot separate
    /// a ticker column from a state column — the header is the discriminator. The
    /// ≥3-distinct gate blocks a constant column matching one symbol by
    /// coincidence (the `unlocode_membership_recovery` constant-column lesson).
    /// Value-based (0048), RHH-disableable.
    fn ticker_membership_recovery(
        &self,
        result: &mut ColumnResult,
        header: &str,
        sample: &[String],
    ) {
        if rhh::is_disabled("ticker_membership_recovery") {
            return;
        }
        const LEAF: &str = "finance.securities.ticker";
        if result.label == LEAF || !header_corroborates_ticker(header) {
            return;
        }
        let mut checked = 0usize;
        let mut passed = 0usize;
        let mut distinct_pass: std::collections::HashSet<&str> = std::collections::HashSet::new();
        for v in sample {
            let t = v.trim();
            if t.is_empty() {
                continue;
            }
            checked += 1;
            if finetype_core::membership::us_tickers(t) {
                passed += 1;
                distinct_pass.insert(t);
            }
        }
        // >=90% US-listed membership AND >=3 DISTINCT passing values.
        if checked < 3 || distinct_pass.len() < 3 || passed * 10 < checked * 9 {
            return;
        }
        let from = result.label.clone();
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.90);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some(format!("ticker_membership_recovery:{from}"));
        result.detected_locale = None;
    }

    /// `org_name_geography_demotion` (default ON). Demotes a geography overcall on
    /// an organisation-name column to `representation.text.entity_name`
    /// (company-reference external band, seam 1c: gleif `name` → region). The raw
    /// Sense model reaches for a place when it sees proper-noun text, so an org-name
    /// column lands on region/city/country. The value-side tell is self-precise: a
    /// company name carries an org/fund suffix (`… PLC`, `… Fund`, `… Capital`,
    /// `… LP`) and a place name never does — measured gleif `name` 0.97, US-state /
    /// country / region / city columns 0.00 (incl. `West Bank`, since `Bank`/`Co`/`SA`
    /// are deliberately excluded from the suffix set as place-ambiguous). Demote when
    /// ≥50% of values carry an org suffix (`org_suffix_ratio`) AND the label is a
    /// place-NAME leaf. No header gate: the suffix signal is self-precise, so it corrects
    /// an org column even under a generic `name`/`value` header (where a header veto could
    /// not). Value-based (0048), RHH-disableable.
    ///
    /// Scope is the place-NAME leaves only (`city`/`region`/`country`/`continent`) — NOT
    /// the address leaves (`geography.address.*`). A street address is legitimately
    /// multi-word free text carrying directional/building tokens that collide with org
    /// suffixes: `4th Street SE` (SE = South-East, not the Societas-Europaea form),
    /// `Royal Trust Tower` (a building, not a trust), `Bairro Asa` (a Brasília district,
    /// not the Norwegian ASA form). Those are 100% of the observed false positives and
    /// they live entirely in address columns; the seam itself (name → region/city) never
    /// touches an address leaf, so gating on place-name leaves removes the whole FP class
    /// structurally rather than by chasing false-friend tokens. Bare code leaves
    /// (`country_code`/`state_code`) are also out of scope — a code column carries no org
    /// suffix and must never become an entity name.
    fn org_name_geography_demotion(&self, result: &mut ColumnResult, sample: &[String]) {
        if rhh::is_disabled("org_name_geography_demotion") {
            return;
        }
        const LEAF: &str = "representation.text.entity_name";
        const PLACE_NAME_LEAVES: [&str; 4] = [
            "geography.location.city",
            "geography.location.region",
            "geography.location.country",
            "geography.location.continent",
        ];
        if !PLACE_NAME_LEAVES.contains(&result.label.as_str()) {
            return;
        }
        // >=50% org-suffix — huge margin below the 0.97 org-column rate and above the
        // 0.0 place-column rate; a geography leaf never legitimately clears it.
        if org_suffix_ratio(sample) < 0.5 {
            return;
        }
        let from = result.label.clone();
        result.label = LEAF.to_string();
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some(format!("org_name_geography_demotion:{from}"));
        result.detected_locale = None;
    }

    /// `region_nonmembership_veto` (default ON). Demotes a `geography.location.region`
    /// OVERCALL to `representation.text.word` when a column's distinct values are mostly
    /// NOT real places — the tier-3 geography seam (external band). The raw Sense model
    /// treats `region` as a garbage-magnet for short catalog codes and enum words: usgs
    /// `net`/`type`/`magSource`/`locationSource` (seismic network + event codes), gleif
    /// `category` (`GENERAL`/`FUND`), seattle `checkouttype` (`Horizon`/`OverDrive`), nyc
    /// `permit_subtype`. A value counts as a place (`value_is_place`) if it is a place NAME
    /// (`membership::place_names` — GeoNames admin1 + countries + cities≥15k), an ISO-3166-2
    /// subdivision code (`US-TX`, so a code-valued `iso_region` column stays), a bare state
    /// code (`CA`/`NV`), or a `City, State` / `City (State)` composite whose parts are
    /// places. Cities and composites are load-bearing: an admin1-only gazetteer wrongly
    /// demoted real city/county columns (`Austin`, `Durham County, NC`) at a measured 15%
    /// false-positive rate — the 33k spot-check that caught it is exactly the discipline the
    /// gate (oracle-blind here) cannot provide. Demote when < 50% of distinct values are
    /// places AND the header is not a strong region/state header. A genuine region/city/
    /// county column clears ~0.9; the false catalog columns clear ~0.0 (seismic `net` sits
    /// at ~0.33 — a few networks are named after states — safely below the bar). Value-based
    /// (0048), RHH-disableable.
    fn region_nonmembership_veto(
        &self,
        result: &mut ColumnResult,
        header: &str,
        sample: &[String],
    ) {
        if rhh::is_disabled("region_nonmembership_veto") {
            return;
        }
        const LEAF: &str = "representation.text.word";
        if result.label != "geography.location.region" {
            return;
        }
        // A strong region/state header protects a genuine subdivision column whose
        // names the gazetteer may miss (obscure / non-English admin1 divisions).
        if header_corroborates_region(header) || header_corroborates_state(header) {
            return;
        }
        // A bare-CODE subdivision column (state=`AB`/`NV`, which a sibling guard
        // normalises state_code->region) is geographic but scores 0% on the
        // names+hyphenated-codes gazetteer. `values_look_like_state_codes` (>=80%
        // STATE_CODES) keeps it — and does NOT rescue the false columns: usgs `net`
        // is `us`-dominated (a country code, not a state code), so it stays <80%.
        if values_look_like_state_codes(sample) {
            return;
        }
        let mut distinct: std::collections::HashSet<&str> = std::collections::HashSet::new();
        for v in sample {
            let t = v.trim();
            if !t.is_empty() {
                distinct.insert(t);
            }
        }
        // >=2 distinct: a single value carries too little signal to overturn the
        // model, and a constant real-region column (a dataset all in one state) must
        // not be vetoed on one gazetteer miss.
        if distinct.len() < 2 {
            return;
        }
        let places = distinct.iter().filter(|v| value_is_place(v)).count();
        if places * 2 >= distinct.len() {
            return;
        }
        let from = result.label.clone();
        result.label = LEAF.to_string();
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some(format!("region_nonmembership_veto:{from}"));
        result.detected_locale = None;
    }

    /// `tld_geography_recovery` (default ON). Recovers `technology.internet.top_level_domain`
    /// from a `geography.location.continent` (or other geo) overcall — the company-reference
    /// external band's TLD→continent miss (majestic-million `TLD`/`IDN_TLD`). The raw Sense
    /// model, seeing short lowercase tokens (`com`,`org`,`uk`), reaches for a place. Promote
    /// when the header names a TLD column (`header_corroborates_tld`) AND ≥90% of values are
    /// IANA-delegated TLDs (`membership::tld_codes`) AND ≥3 DISTINCT pass. The header gate is
    /// load-bearing: a pure-ccTLD column is value-identical to a country-code column, so
    /// membership alone over-promotes; the `tld` header marks it as domains. Value-based
    /// (0048), RHH-disableable.
    fn tld_geography_recovery(&self, result: &mut ColumnResult, header: &str, sample: &[String]) {
        if rhh::is_disabled("tld_geography_recovery") {
            return;
        }
        const LEAF: &str = "technology.internet.top_level_domain";
        if result.label == LEAF || !header_corroborates_tld(header) {
            return;
        }
        let mut checked = 0usize;
        let mut passed = 0usize;
        let mut distinct_pass: std::collections::HashSet<&str> = std::collections::HashSet::new();
        for v in sample {
            let t = v.trim();
            if t.is_empty() {
                continue;
            }
            checked += 1;
            if finetype_core::membership::tld_codes(t) {
                passed += 1;
                distinct_pass.insert(t);
            }
        }
        if checked < 3 || distinct_pass.len() < 3 || passed * 10 < checked * 9 {
            return;
        }
        let from = result.label.clone();
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.90);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some(format!("tld_geography_recovery:{from}"));
        result.detected_locale = None;
    }

    /// `s_expression_recovery` (default ON). Recovers the
    /// `container.object.s_expression` leaf (company-reference audit; author-
    /// approved 2026-07-05 as a general S-expression type over a narrow
    /// parse_tree leaf). A parse tree's Penn comma-tokens `(, ,)` fool the
    /// delimiter detector into reading a CSV array, so 1,292 corpus columns of
    /// constituency parses / code ASTs (`trees`/`parse_tree`/`ast`) land on
    /// `container.array.comma_separated`. The 244-dim model cannot predict the
    /// leaf. Promote when >=90% of values pass the balanced-nested-paren
    /// structural check (`finetype_core::structure::is_s_expression`). NO header
    /// gate — the structural signature is self-precise (measured corpus
    /// over-recovery: zero, 1,292/1,292 were genuine S-expressions), unlike the
    /// value-ambiguous checksum/membership recoveries. Value-based (0048),
    /// RHH-disableable.
    fn s_expression_recovery(&self, result: &mut ColumnResult, sample: &[String]) {
        if rhh::is_disabled("s_expression_recovery") {
            return;
        }
        const LEAF: &str = "container.object.s_expression";
        if result.label == LEAF {
            return;
        }
        let non_empty = non_empty_trimmed(sample);
        if non_empty.len() < 3 {
            return;
        }
        let valid = non_empty
            .iter()
            .filter(|v| finetype_core::structure::is_s_expression(v))
            .count();
        // >=90% pass the balanced-nested-paren structural check.
        if valid * 10 < non_empty.len() * 9 {
            return;
        }
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.95);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some("s_expression_recovery".to_string());
        result.detected_locale = None;
    }

    /// `jwt_substance_guard` (default ON). Demotes `technology.cryptographic.jwt`
    /// to `unknown` when the column's values are not real JWTs. The taxonomy
    /// pattern checks only the three-base64url-segment SHAPE, so the model
    /// over-emits jwt on text at corpus scale (7,920 of the 33k-sample columns —
    /// Windows file paths, prose, entity names; the gated-YDF oracle refutes
    /// every one, 0 confirmed jwt). A genuine JWT header base64url-decodes to a
    /// JSON object with an `alg` key (`finetype_core::structure::is_jwt`); when
    /// fewer than half a column's values carry that certainty the jwt assertion
    /// is wrong. We demote to `unknown` rather than guess the text leaf —
    /// asserting only the certainty (not-a-JWT), never simulating the semantic
    /// finding of *which* text type (that stays the model's job). Value-based
    /// (0048), demote-only, RHH-disableable.
    fn jwt_substance_guard(&self, result: &mut ColumnResult, sample: &[String]) {
        demote_when_substance_fails(
            result,
            sample,
            "technology.cryptographic.jwt",
            "jwt_substance_guard",
            finetype_core::structure::is_jwt,
        );
    }

    /// `mime_type_substance_guard` (default ON). Demotes `representation.file.mime_type`
    /// to `unknown` when the column's values are not real media types. The taxonomy
    /// pattern `^[a-zA-Z]+/[a-zA-Z0-9.+\-]+(;.*)?$` accepts ANY word as the
    /// top-level type, so the model over-emits `mime_type` on every `word/word`
    /// string at corpus scale (~1,372 live of the stale snapshot's 3,214 — slugs
    /// `recipes/…`, qualified paths `ccs/stc2010`/`geoId/15`, namespaces). A genuine
    /// media type leads with one of the ten RFC 6838 top-level types
    /// (`finetype_core::structure::is_mime_type`); when fewer than half a column's
    /// values carry that certainty the mime_type assertion is wrong. We demote to
    /// `unknown` rather than guess the text leaf — asserting only the certainty
    /// (not-a-media-type), never simulating *which* text type it is (that stays the
    /// model's job). Value-based (0048), demote-only, RHH-disableable. Twin of
    /// `jwt_substance_guard`; runs alongside it, after the recovery guards.
    fn mime_type_substance_guard(&self, result: &mut ColumnResult, sample: &[String]) {
        demote_when_substance_fails(
            result,
            sample,
            "representation.file.mime_type",
            "mime_type_substance_guard",
            finetype_core::structure::is_mime_type,
        );
    }

    /// `locale_code_substance_guard` (default ON). Demotes `technology.code.locale_code`
    /// to `unknown` when the column's values are not real locale codes. The taxonomy
    /// pattern `^[a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4})*$` accepts ANY 2–3 letter word,
    /// so the model over-emits `locale_code` (largest tier-3 live surface, ~1,933 of
    /// a stale 4,246) on text/code columns — survey fragments, dialogue-act tags,
    /// single words. A genuine locale leads with a real ISO 639 language and any
    /// script/region subtag is a real ISO 15924 / 3166-1 code
    /// (`finetype_core::structure::is_locale_code`, delimiter-tolerant); when fewer
    /// than half a column's values carry that certainty the assertion is wrong. We
    /// demote to `unknown` — asserting only the certainty (not-a-locale), never
    /// guessing which text type it is (that stays the model's job). Value-based
    /// (0048), demote-only, RHH-disableable. Twin of `mime_type_substance_guard`;
    /// runs alongside it, after the recovery guards.
    ///
    /// The keep-bar stays at the ≥50% convention (not higher): the 2-letter ISO-639
    /// space is collision-dense, so a bare-2-letter-word column can pass the check —
    /// but under demote-only that is a harmless false-keep (status quo), never a
    /// false-demote. Calibration: `output/certainty-locale/findings.md`.
    fn locale_code_substance_guard(&self, result: &mut ColumnResult, sample: &[String]) {
        demote_when_substance_fails(
            result,
            sample,
            "technology.code.locale_code",
            "locale_code_substance_guard",
            finetype_core::structure::is_locale_code,
        );
    }

    /// `password_substance_guard` (default ON). Demotes `identity.person.password`
    /// to `unknown` when the column is plainly not a credential field. The taxonomy
    /// validator is `minLength: 1, maxLength: 255` (`designation: broad_characters`) —
    /// it certifies NOTHING, so the flat softmax scatters `password` onto free text
    /// at corpus scale: i18n/UI strings, song/anime/artist titles, country names,
    /// prose. On the corpus sample essentially NONE of the survivors are genuine
    /// passwords (real credential columns are PII and barely appear), so this is a
    /// near-pure false-positive attractor.
    ///
    /// Unlike mime/locale, a password has **no positive substance** — it is defined by
    /// the *absence* of structure (a high-entropy secret), so there is no "is-a-password"
    /// test to build (that would be simulating semantics, the certainty direction's one
    /// forbidden move). The guard therefore keys on the single self-precise **anti-signal**:
    /// a credential never contains internal whitespace, so a value with a space is not a
    /// password. When fewer than half the values are credential-shaped (whitespace-free),
    /// the column is text and the label is wrong → demote to `unknown` (asserting only the
    /// certainty *not-a-credential*, never guessing which text type). This catches the
    /// space-heavy bulk (phrases/titles/i18n); the whitespace-free residual (code
    /// identifiers) stays `password` — a harmless false-keep under demote-only, since the
    /// genuine-password population is ~empty. The rare passphrase-with-spaces column is the
    /// only theoretical false-demote, and such columns do not occur in practice.
    /// Value-based (0048), demote-only, RHH-disableable. Substrate: `output/certainty-password/`.
    fn password_substance_guard(&self, result: &mut ColumnResult, sample: &[String]) {
        // Credential-shaped = no internal whitespace. A password never contains a
        // space, so a value with one is not a password; when the majority carry
        // whitespace the column is text (phrases/titles/i18n) and the label is wrong.
        demote_when_substance_fails(
            result,
            sample,
            "identity.person.password",
            "password_substance_guard",
            |v| !v.chars().any(char::is_whitespace),
        );
    }

    /// `unlocode_format_veto` (default ON). UN/LOCODE is a closed 5-char shape
    /// (`^[A-Z]{2}[A-Z2-9]{3}$` — no space, no digit after the leading 2 letters).
    /// The model over-emits it at high confidence on value-identical short codes —
    /// notably alphanumeric postcodes (`CM13 3GF`, a UK postcode) that carry a
    /// space/digit unlocode forbids. unlocode is rare, so an assertion whose OWN
    /// values fail the unlocode pattern (`sample_contradicts_label`, <=10% pass) is
    /// a misfire. Demote-only. Shape-aware route: if the contradicting values pass a
    /// CONCRETE postal locale pattern, assert postal_code (lands the UK-postcode gold
    /// col); otherwise fall back to `unknown` — NOT the permissive universal postal
    /// block (minLength 3 / maxLength 10), per the Precision Principle. Value-based
    /// (0048); RHH-disableable.
    fn unlocode_format_veto(&self, result: &mut ColumnResult, sample: &[String]) {
        if rhh::is_disabled("unlocode_format_veto")
            || result.label != "geography.transportation.unlocode"
        {
            return;
        }
        let Some(tax) = self.taxonomy.as_ref() else {
            return;
        };
        if !sample_contradicts_label(tax, "geography.transportation.unlocode", sample) {
            return;
        }
        // Route ONLY on a concrete locale match, NOT the permissive universal postal
        // block; otherwise demote to unknown.
        let locale = detect_locale_from_validation(sample, "geography.address.postal_code", tax);
        let target = if locale.is_some() {
            "geography.address.postal_code"
        } else {
            "unknown"
        };
        result.label = target.to_string();
        result.detected_locale = locale;
        result.confidence = result.confidence.min(0.6);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some("unlocode_format_veto".to_string());
    }

    /// `isbn_header_recovery` (default ON). Recovers `identity.commerce.isbn` on a
    /// column whose header names ISBN and whose values carry a valid ISBN check
    /// digit, but which the 240-dim model funnelled into a digit-shaped lookalike
    /// (numeric_code / integer_number / npi / alphanumeric_id) — an ISBN is just
    /// digits, so neither the model nor `checksum_substance_guard` can tell it from
    /// a bare number. The mod-11 (ISBN-10) / mod-10 (ISBN-13) check digit is exactly
    /// what distinguishes a real ISBN column from a same-length integer, and the
    /// header confirms intent. Promote when the header matches AND >=90% of values
    /// pass `finetype_core::checksum::isbn`. Recovery-only: a non-ISBN numeric column
    /// fails the checksum and is untouched. Runs AFTER `checksum_substance_guard`
    /// (re-promotes from the integer that guard may have demoted to). Value-based
    /// (0048), RHH-disableable.
    fn isbn_header_recovery(&self, result: &mut ColumnResult, header: &str, sample: &[String]) {
        if rhh::is_disabled("isbn_header_recovery") {
            return;
        }
        const LEAF: &str = "identity.commerce.isbn";
        if result.label == LEAF || !header_corroborates_isbn(header) {
            return;
        }
        let non_empty = non_empty_trimmed(sample);
        if non_empty.len() < 3 {
            return;
        }
        let valid = non_empty
            .iter()
            .filter(|v| finetype_core::checksum::isbn(v))
            .count();
        // >=90% pass the ISBN check digit (valid*10 >= len*9).
        if valid * 10 < non_empty.len() * 9 {
            return;
        }
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.95);
        result.disambiguation_applied = true;
        result.disambiguation_rule =
            Some(format!("isbn_header_recovery:{}", header.to_lowercase()));
        result.detected_locale = None;
    }

    /// `numeric_code_header_recovery` (default ON). Feature rule F5 demotes
    /// `numeric_code` to `integer_number` whenever a column has no leading
    /// zeros — which structurally locks every no-leading-zero code system
    /// (NAICS sectors run 11–92, EDGAR CIKs never start with 0) out of its
    /// intended type: the model predicts numeric_code and the deterministic
    /// layer erases it (company-reference audit, gold rows compref:naics /
    /// compref:sec_edgar). Values alone cannot separate "digits that identify"
    /// from "digits that quantify", so this is a 0094 header-corroboration
    /// boundary: restore numeric_code when the header names a code column
    /// (`header_corroborates_numeric_code` — token-aware, postal tokens veto)
    /// AND ≥90% of values are all-digit with median length ≥2 (bare 0/1 flag
    /// columns stay integers). Recovery-only: quantity columns (`employees`,
    /// `founded`) carry no code token and are untouched. Value-based veto
    /// discipline + header corroboration per 0094; RHH-disableable.
    fn numeric_code_header_recovery(
        &self,
        result: &mut ColumnResult,
        header: &str,
        sample: &[String],
    ) {
        if rhh::is_disabled("numeric_code_header_recovery") {
            return;
        }
        const LEAF: &str = "representation.identifier.numeric_code";
        if result.label != "representation.numeric.integer_number"
            || !header_corroborates_numeric_code(header)
        {
            return;
        }
        let non_empty = non_empty_trimmed(sample);
        if non_empty.len() < 3 {
            return;
        }
        let all_digit = non_empty
            .iter()
            .filter(|v| !v.is_empty() && v.chars().all(|c| c.is_ascii_digit()))
            .count();
        if all_digit * 10 < non_empty.len() * 9 {
            return;
        }
        let mut lens: Vec<usize> = non_empty.iter().map(|v| v.len()).collect();
        lens.sort_unstable();
        if lens[lens.len() / 2] < 2 {
            return;
        }
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.85);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some(format!(
            "numeric_code_header_recovery:{}",
            header.to_lowercase()
        ));
        result.detected_locale = None;
    }

    /// `ceded_leaf_recovery` (default ON). The recall side of the model label-space
    /// reshape (spec 2026-06-27-model-label-space-reshape ac-3). The reshaped Sense
    /// model no longer emits the closed/format/checksum leaves (they were dropped
    /// from its softmax head); this re-asserts them deterministically from VALUES,
    /// so recall is recovered, not lost.
    ///
    /// Fires when ≥90% of the sample passes exactly ONE eligible leaf's OWN strict
    /// taxonomy validator (`label_validates_sample` — the same gate the validation
    /// veto uses, so the two stay a single source of truth). The eligible set is the
    /// ac-0 CONCLUSIVE (value-self-sufficient) cede subset MINUS the leaves a
    /// more-specific rule already owns (datetime FORMATS → `datetime_format_refinement`;
    /// url/message_id/windows_path/qualified_name → `structured_string_refinement`;
    /// isbn → `isbn_header_recovery`) MINUS the permissive-validator leaves the ac-0
    /// adversarial challenge demoted (color_hex/rgb — optional `#` lets bare numbers
    /// pass). Each remaining validator is structurally exclusive, so a match is
    /// correct-by-construction.
    ///
    /// AUTHORITATIVE OVERRIDE: a conclusive validator may override even a confident
    /// model prediction (a real `uuid` IS a uuid, not the `npi`/`tsid` the reshaped
    /// model relocates it to — ac-2 destination drift). EXACTLY-ONE-MATCH gate: never
    /// guess on overlap; if two eligible leaves both validate, defer (the common
    /// overlaps — ip_v4 vs ip_v4_with_port, json vs json_array — are mutually
    /// exclusive in practice, so this rarely costs recall). Value-based (decision
    /// 0048); RHH-disableable.
    fn ceded_leaf_recovery(&self, result: &mut ColumnResult, sample: &[String]) {
        if rhh::is_disabled("ceded_leaf_recovery") {
            return;
        }
        let Some(tax) = self.taxonomy.as_ref() else {
            return;
        };
        let non_empty = sample.iter().filter(|v| !v.trim().is_empty()).count();
        if non_empty < 3 {
            // Too few values to assert a conclusive type safely.
            return;
        }
        let mut matches = CEDED_RECOVERY_LEAVES
            .iter()
            .copied()
            .filter(|leaf| *leaf != result.label.as_str())
            .filter(|leaf| label_validates_sample(tax, leaf, sample));
        let Some(leaf) = matches.next() else {
            return;
        };
        // Ambiguity guard: a second eligible leaf also validates — do not guess.
        if matches.next().is_some() {
            return;
        }
        result.label = leaf.to_string();
        result.confidence = result.confidence.max(0.9);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some("ceded_leaf_recovery".to_string());
        result.detected_locale = detect_locale_from_validation(sample, leaf, tax);
    }

    /// `isin_checksum_recovery` (default ON). Corrects the one non-exclusive
    /// overlap in the ceded set: `identity.commerce.isrc` (12 chars, shape-only)
    /// and `finance.securities.isin` (12 chars + ISIN check digit) share a shape,
    /// and a digit-tailed ISIN matches ISRC's `^[A-Z]{2}[A-Z0-9]{3}\d{7}$` pattern.
    /// `validate_value_for_label` is regex-only, so `ceded_leaf_recovery` cannot
    /// see the check digit and mislabels a real ISIN column as isrc. Promote to
    /// isin when the sample matches the ISIN shape (2-letter country prefix,
    /// excludes bare 12-digit codes) AND ≥90% pass the ISIN check digit
    /// (`finetype_core::checksum::isin`) — the discriminator isrc cannot satisfy.
    /// Runs after `ceded_leaf_recovery` so it has the last word over the isrc
    /// misassertion. Value-based (decision 0048); RHH-disableable.
    fn isin_checksum_recovery(&self, result: &mut ColumnResult, sample: &[String]) {
        if rhh::is_disabled("isin_checksum_recovery") {
            return;
        }
        if result.label == "finance.securities.isin" {
            return;
        }
        let Some(tax) = self.taxonomy.as_ref() else {
            return;
        };
        // Shape gate: the ISIN pattern requires a 2-letter country prefix, which
        // excludes bare 12-digit codes that could coincidentally pass the check.
        if !label_validates_sample(tax, "finance.securities.isin", sample) {
            return;
        }
        let mut checked = 0usize;
        let mut passed = 0usize;
        for v in sample {
            let t = v.trim();
            if t.is_empty() {
                continue;
            }
            checked += 1;
            if finetype_core::checksum::isin(t) {
                passed += 1;
            }
        }
        if checked < 3 || (passed as f64) / (checked as f64) < 0.9 {
            return;
        }
        result.label = "finance.securities.isin".to_string();
        result.confidence = result.confidence.max(0.9);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some("isin_checksum_recovery".to_string());
    }

    /// `cusip_checksum_recovery` (default ON). The 244-dim model cannot predict
    /// `finance.securities.cusip` (a 9-char security id: 8 issuer/issue chars + a
    /// check digit), so real CUSIP columns land on the word / alphanumeric_id /
    /// integer_number attractor. The taxonomy shape `^[A-Z0-9]{8}[0-9]$` also
    /// matches any 9-char alnum id (SKUs, 9-digit account numbers), so shape alone
    /// is not enough. The CUSIP mod-10 check digit (`finetype_core::checksum::cusip`)
    /// is the discriminator the attractor cannot satisfy: a non-CUSIP id's 9th char
    /// passes that arithmetic only ~1 in 10 by chance, so a >=90% column-wide pass is
    /// unreachable for anything but a genuine CUSIP column. Value-only — no header,
    /// round-trips headerless. ISIN is 12 chars and SEDOL 7, so nothing else in the
    /// finance family competes at this length. Value-based (0048); RHH-disableable.
    fn cusip_checksum_recovery(&self, result: &mut ColumnResult, sample: &[String]) {
        if rhh::is_disabled("cusip_checksum_recovery") {
            return;
        }
        const LEAF: &str = "finance.securities.cusip";
        if result.label == LEAF {
            return;
        }
        let Some(tax) = self.taxonomy.as_ref() else {
            return;
        };
        // Shape gate: `^[A-Z0-9]{8}[0-9]$`. Mirrors isin_checksum_recovery's use of
        // the leaf's own taxonomy validator before the check-digit test.
        if !label_validates_sample(tax, LEAF, sample) {
            return;
        }
        let mut checked = 0usize;
        let mut passed = 0usize;
        let mut distinct_pass: std::collections::HashSet<&str> = std::collections::HashSet::new();
        for v in sample {
            let t = v.trim();
            if t.is_empty() {
                continue;
            }
            checked += 1;
            if finetype_core::checksum::cusip(t) {
                passed += 1;
                distinct_pass.insert(t);
            }
        }
        // >=3 DISTINCT passing values, not just >=90% of a repeated one: a constant /
        // low-cardinality numeric column (gold `phkey` = one repeated 9-digit key
        // 484158167) can pass the weak mod-10 CUSIP check at 100% by coincidence, so a
        // single coincidental pass must not assert this rare type — require the column
        // to carry distributional evidence.
        if checked < 3 || distinct_pass.len() < 3 || (passed as f64) / (checked as f64) < 0.9 {
            return;
        }
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.9);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some("cusip_checksum_recovery".to_string());
    }

    /// `sedol_checksum_recovery` (default ON). Recovers `finance.securities.sedol`.
    /// The 244-dim model cannot predict this leaf, so a real SEDOL column lands on
    /// its numeric_code / alphanumeric_id attractor. The SEDOL shape (6 no-vowel
    /// alphanumerics + a trailing check digit) is shared by bare 7-digit numeric
    /// codes, so SHAPE alone cannot recover it — the check digit is the discriminator
    /// a numeric code cannot satisfy. Promote when the sample passes the sedol
    /// taxonomy shape (uppercase, no vowels, 7 chars — constraints the checksum fn's
    /// alnum_value does NOT itself impose) AND >=90% pass the SEDOL check digit
    /// (`finetype_core::checksum::sedol`). Value-only, like isin_checksum_recovery:
    /// a random 7-char column passes the 1/10 check ~10% of the time, far below the
    /// 90% bar, so it round-trips headerless. Value-based (0048); RHH-disableable.
    fn sedol_checksum_recovery(&self, result: &mut ColumnResult, sample: &[String]) {
        if rhh::is_disabled("sedol_checksum_recovery") {
            return;
        }
        const LEAF: &str = "finance.securities.sedol";
        if result.label == LEAF {
            return;
        }
        let Some(tax) = self.taxonomy.as_ref() else {
            return;
        };
        if !label_validates_sample(tax, LEAF, sample) {
            return;
        }
        let mut checked = 0usize;
        let mut passed = 0usize;
        let mut distinct_pass: std::collections::HashSet<&str> = std::collections::HashSet::new();
        for v in sample {
            let t = v.trim();
            if t.is_empty() {
                continue;
            }
            checked += 1;
            if finetype_core::checksum::sedol(t) {
                passed += 1;
                distinct_pass.insert(t);
            }
        }
        // >=3 DISTINCT passing values: same constant-column guard as cusip — a bare
        // 7-digit numeric code repeated across a low-cardinality column can pass the
        // weighted SEDOL check by coincidence, so require distributional evidence.
        if checked < 3 || distinct_pass.len() < 3 || (passed as f64) / (checked as f64) < 0.9 {
            return;
        }
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.9);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some("sedol_checksum_recovery".to_string());
    }

    /// `dea_checksum_recovery` (default ON). The 244-dim model cannot predict
    /// `identity.medical.dea_number`; a real DEA column lands on the `alphanumeric_id`
    /// attractor (2 letters + 7 digits reads as a generic letter+digit id). Promote to
    /// dea_number when the sample matches the DEA shape
    /// (`^[ABFMPRabfmpr][A-Za-z]\d{7}$`, which pins the first char to a registrant-type
    /// letter the checksum fn does NOT check) AND >=90% pass the DEA check digit
    /// (`finetype_core::checksum::dea`). The mod-10 formula is DEA-specific — no
    /// non-target id scheme (credit cards use Luhn) is built to satisfy it, so a
    /// same-shape alphanumeric_id column passes at ~10%, well under the bar. Self-precise
    /// value signal → no header gate; round-trips headerless. Value-based (0048),
    /// RHH-disableable.
    fn dea_checksum_recovery(&self, result: &mut ColumnResult, sample: &[String]) {
        if rhh::is_disabled("dea_checksum_recovery") {
            return;
        }
        const LEAF: &str = "identity.medical.dea_number";
        if result.label == LEAF {
            return;
        }
        let Some(tax) = self.taxonomy.as_ref() else {
            return;
        };
        // Shape gate: the dea_number pattern pins the first char to a registrant-type
        // letter {A,B,F,M,P,R}, which checksum::dea alone does not enforce.
        if !label_validates_sample(tax, LEAF, sample) {
            return;
        }
        let mut checked = 0usize;
        let mut passed = 0usize;
        let mut distinct_pass: std::collections::HashSet<&str> = std::collections::HashSet::new();
        for v in sample {
            let t = v.trim();
            if t.is_empty() {
                continue;
            }
            checked += 1;
            if finetype_core::checksum::dea(t) {
                passed += 1;
                distinct_pass.insert(t);
            }
        }
        // >=3 DISTINCT passing values: same constant-column guard as cusip/sedol. The
        // DEA shape needs two leading letters (an all-numeric column can never match),
        // so the risk is lower, but the cardinality bar keeps the three uniform.
        if checked < 3 || distinct_pass.len() < 3 || (passed as f64) / (checked as f64) < 0.9 {
            return;
        }
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.9);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some("dea_checksum_recovery".to_string());
    }

    /// `imei_checksum_recovery` (default ON). Recovers `technology.code.imei`. The
    /// 244-dim model cannot predict it, so a genuine 15-digit IMEI column lands on
    /// `integer_number`. Luhn ALONE is NOT a discriminator here — a 15-digit American
    /// Express card column is Luhn-valid BY CONSTRUCTION — so promote requires an
    /// `imei` header (`header_corroborates_imei`, which a payment header never yields)
    /// AND a value gate: >=90% exactly-15-digit Luhn-valid values. The two gates are
    /// jointly load-bearing — the header excludes the Amex collision the value gate
    /// cannot. Value-based (0048); RHH-disableable.
    fn imei_checksum_recovery(&self, result: &mut ColumnResult, header: &str, sample: &[String]) {
        if rhh::is_disabled("imei_checksum_recovery") {
            return;
        }
        const LEAF: &str = "technology.code.imei";
        if result.label == LEAF || !header_corroborates_imei(header) {
            return;
        }
        let non_empty = non_empty_trimmed(sample);
        if non_empty.len() < 3 {
            return;
        }
        let valid = non_empty
            .iter()
            .filter(|v| {
                v.len() == 15
                    && v.bytes().all(|b| b.is_ascii_digit())
                    && finetype_core::checksum::luhn(v)
            })
            .count();
        // >=90% are exactly-15-digit Luhn-valid IMEIs (valid*10 >= len*9).
        if valid * 10 < non_empty.len() * 9 {
            return;
        }
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.95);
        result.disambiguation_applied = true;
        result.disambiguation_rule =
            Some(format!("imei_checksum_recovery:{}", header.to_lowercase()));
        result.detected_locale = None;
    }

    /// `cpt_procedure_recovery` (default ON). Recovers `identity.medical.cpt`. The
    /// 244-dim model cannot predict this leaf; the bare 5-digit form lands on the
    /// numeric_code / integer / postal attractor. CPT has no check digit or membership
    /// set, and `^\d{5}$` is value-identical with a US ZIP code, so the distinctive
    /// `cpt`/`procedure` header token (`header_corroborates_cpt`, NO generic `code`
    /// tier — that would admit a ZIP column headed `code`) is the SOLE discriminator.
    /// Promote when the header corroborates AND >=90% pass the CPT taxonomy validator
    /// (`^\d{5}$|^\d{4}[FTU]$`). Value-based (0048); RHH-disableable.
    fn cpt_procedure_recovery(&self, result: &mut ColumnResult, header: &str, sample: &[String]) {
        if rhh::is_disabled("cpt_procedure_recovery") {
            return;
        }
        const LEAF: &str = "identity.medical.cpt";
        if result.label == LEAF || !header_corroborates_cpt(header) {
            return;
        }
        let non_empty = non_empty_trimmed(sample);
        if non_empty.len() < 3 {
            return;
        }
        match self.taxonomy.as_ref() {
            Some(tax) if label_validates_sample(tax, LEAF, sample) => {
                result.label = LEAF.to_string();
                result.confidence = result.confidence.max(0.95);
                result.disambiguation_applied = true;
                result.disambiguation_rule =
                    Some(format!("cpt_procedure_recovery:{}", header.to_lowercase()));
                result.detected_locale = None;
            }
            _ => {}
        }
    }

    /// `hs_code_header_recovery` (default ON). Recovers the
    /// `geography.transportation.hs_code` leaf. The 244-dim model cannot predict it, so
    /// a customs-tariff column lands on the numeric / `word` attractor (the multi-dot
    /// forms like `0901.11.00.10` are not valid decimals, so the model reads `word`);
    /// R20 `hs_code_validation_gate` in value_sharpen only DEMOTES a wrong hs_code, it
    /// never recovers one the model never emitted. Header-gated because HS codes carry
    /// NO check digit and the bare form is value-identical to a plain integer/year: the
    /// header token (`header_corroborates_hs_code`) is the sole discriminator. A
    /// median-length floor of 6 kills the bare-4-digit-year false pass that
    /// `is_hs_code_format`'s loose no-dot branch admits. Promote when the header
    /// corroborates AND median length >=6 AND >=90% pass `is_hs_code_format`.
    /// Value-based (0048); RHH-disableable.
    fn hs_code_header_recovery(&self, result: &mut ColumnResult, header: &str, sample: &[String]) {
        if rhh::is_disabled("hs_code_header_recovery") {
            return;
        }
        const LEAF: &str = "geography.transportation.hs_code";
        if result.label == LEAF || !header_corroborates_hs_code(header) {
            return;
        }
        let non_empty = non_empty_trimmed(sample);
        if non_empty.len() < 3 {
            return;
        }
        // Length floor: is_hs_code_format's no-dot branch accepts bare 4-5 digit
        // numbers (years, small ints) the taxonomy pattern (min 6 digits) rejects;
        // require median length >= 6 so a 4-digit-year column cannot pass.
        let mut lens: Vec<usize> = non_empty.iter().map(|v| v.len()).collect();
        lens.sort_unstable();
        if lens[lens.len() / 2] < 6 {
            return;
        }
        let valid = non_empty.iter().filter(|v| is_hs_code_format(v)).count();
        // >=90% match the HS digit-group format (valid*10 >= len*9).
        if valid * 10 < non_empty.len() * 9 {
            return;
        }
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.95);
        result.disambiguation_applied = true;
        result.disambiguation_rule =
            Some(format!("hs_code_header_recovery:{}", header.to_lowercase()));
        result.detected_locale = None;
    }

    /// `unlocode_membership_recovery` (default ON). Promotes the
    /// `geography.transportation.unlocode` leaf (the twin of the demote-only
    /// `unlocode_format_veto`). The 244-dim model cannot predict it, so a real UN/LOCODE
    /// column (`USLAX`/`GBLON`) lands on the `word` attractor. The 5-char `CC + 3`
    /// shape is shared by stock tickers and SKUs, so SHAPE alone cannot recover it —
    /// EXACT membership in the published UN/LOCODE set (`membership::unlocode`, ~0.5% of
    /// the shape space AND each member needs a valid ISO-3166-1 country prefix) is the
    /// discriminator a ticker/SKU column is ~never 90% inside. NO header gate — membership
    /// at 0.90 density is self-precise, so it round-trips headerless. The demote-only
    /// veto owns the label==unlocode case, so this promote and that veto never touch the
    /// same column. Value-based (0048); RHH-disableable.
    fn unlocode_membership_recovery(&self, result: &mut ColumnResult, sample: &[String]) {
        if rhh::is_disabled("unlocode_membership_recovery") {
            return;
        }
        const LEAF: &str = "geography.transportation.unlocode";
        if result.label == LEAF {
            return;
        }
        let mut checked = 0usize;
        let mut passed = 0usize;
        let mut distinct_pass: std::collections::HashSet<&str> = std::collections::HashSet::new();
        for v in sample {
            let t = v.trim();
            if t.is_empty() {
                continue;
            }
            checked += 1;
            if finetype_core::membership::unlocode(t) {
                passed += 1;
                distinct_pass.insert(t);
            }
        }
        // >=90% published UN/LOCODE membership AND >=3 DISTINCT passing values. The
        // distinct gate is load-bearing: the 110k-entry set makes 5-char collisions
        // common, so a constant / low-cardinality column (a fund `symbol` column of one
        // repeated ticker `FRMUF`/`DELRF`, a `city` column of `Essen`, a `Namespace`
        // column of `Debug`) matches a single UN/LOCODE entry at 100% by coincidence.
        // A single coincidental match carries no distributional evidence and must not
        // assert this type — mirror cusip/sedol/dea_checksum_recovery's distinct gate.
        if checked < 3 || distinct_pass.len() < 3 || passed * 10 < checked * 9 {
            return;
        }
        let from = result.label.clone();
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.85);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some(format!("unlocode_membership_recovery:{from}"));
        result.detected_locale = None;
    }

    /// `color_rgb_recovery` (default ON). Recovers `representation.format.color_rgb`
    /// (not in the 244-dim softmax) from VALUES. The taxonomy validator
    /// `^(?:rgb)?\(?(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)?$` is PERMISSIVE — `rgb`,
    /// `(` and `)` are all optional, so a bare comma triple `255,0,0` (a coordinate, a
    /// comma_separated array, or the `word` attractor) passes; that is why color_rgb was
    /// excluded from `ceded_leaf_recovery`. This PROMOTE guard restores recall precisely
    /// by requiring the literal `rgb(` / `rgba(` prefix a bare triple cannot carry —
    /// self-precise, so NO header gate (mirror of `s_expression_recovery`). Value-based
    /// (0048); RHH-disableable.
    fn color_rgb_recovery(&self, result: &mut ColumnResult, sample: &[String]) {
        if rhh::is_disabled("color_rgb_recovery") {
            return;
        }
        const LEAF: &str = "representation.format.color_rgb";
        if result.label == LEAF {
            return;
        }
        let non_empty = non_empty_trimmed(sample);
        if non_empty.len() < 3 {
            return;
        }
        // Anchored substance check: literal rgb(/rgba( prefix + closing ) + 3 (rgb)
        // or 4 (rgba) comma components whose first three are integers in 0..=255.
        let is_rgb = |raw: &str| -> bool {
            let lower = raw.trim().to_ascii_lowercase();
            let inner = lower
                .strip_prefix("rgba(")
                .or_else(|| lower.strip_prefix("rgb("));
            let Some(inner) = inner.and_then(|s| s.strip_suffix(')')) else {
                return false;
            };
            let parts: Vec<&str> = inner.split(',').map(str::trim).collect();
            if parts.len() != 3 && parts.len() != 4 {
                return false;
            }
            parts.iter().take(3).all(|p| {
                let n = p.strip_suffix('%').unwrap_or(p);
                n.parse::<u16>().map(|v| v <= 255).unwrap_or(false)
            })
        };
        let valid = non_empty.iter().filter(|v| is_rgb(v)).count();
        // >=90% carry the literal rgb(...) certainty.
        if valid * 10 < non_empty.len() * 9 {
            return;
        }
        result.label = LEAF.to_string();
        result.confidence = result.confidence.max(0.95);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some("color_rgb_recovery".to_string());
        result.detected_locale = None;
    }

    /// `increment_substance_veto` (default ON). The first full-column-statistics
    /// rule (spec 2026-06-16-column-statistics-lever). A genuine auto-increment
    /// (`representation.identifier.increment`) is a CONTIGUOUS, near-unique run
    /// of non-negative integers — a fact only the full column reveals. The
    /// `value_sharpen` sequential check runs on the 100-value STEPPED sample,
    /// where a true 1..N run becomes `1, k, 2k, …` (uniform diffs but not
    /// contiguous) and any evenly-spaced numeric column looks sequential — so it
    /// over-emits increment badly (gold precision 0.056: 1 TP, 17 FP, all gold
    /// `integer_number`). Re-check the FULL column: a real increment fills its
    /// own range (distinct ≈ max−min+1) with almost no duplicates. Otherwise the
    /// values are a plain integer column wearing the wrong label — demote to
    /// `integer_number`. Value-based (decision 0048); demotion-only, so a true
    /// increment passes through untouched.
    fn increment_substance_veto(&self, result: &mut ColumnResult, values: &[String]) {
        if rhh::is_disabled("increment_substance_veto")
            || result.label != "representation.identifier.increment"
        {
            return;
        }
        // Keep `increment` whenever the FULL column is a confirmed contiguous near-unique
        // run (`Some(true)`); demote `None` (too few integers to judge) and `Some(false)`
        // (a stepped sample that only looks sequential) to integer. NOTE: the aggressive
        // "default everything to integer unless a counter header" variant was corpus-honest
        // NO-GO — the gated-YDF oracle confirms ~128k contiguous-run increments on the
        // corpus, so wholesale demotion collapses 98% of its oracle support. The
        // increment-vs-integer-ID boundary is corpus-contested; gold's 8 integer-ID
        // over-emits cannot be recovered without that collapse, so only the value-decidable
        // None/Some(false) slice is taken (spec 2026-06-27-composed-accuracy-roadmap #12).
        if values_form_increment(values) == Some(true) {
            return;
        }
        result.label = "representation.numeric.integer_number".to_string();
        result.confidence = result.confidence.min(0.6);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some("increment_substance_veto".to_string());
        if let Some(taxonomy) = self.taxonomy.as_ref() {
            result.detected_locale = detect_locale_from_validation(values, &result.label, taxonomy);
        }
    }

    /// `binary_vocab_veto` (default ON). `representation.boolean.binary` requires
    /// a strictly two-valued {0,1} domain, but the model over-emits it on sparse
    /// integer COUNT columns — mostly zeros with the occasional larger value
    /// (`Points` …0,0,8,0…; `Comments` …5,0,0…). Acts only on all-integer columns
    /// (so true/false/yes/no binaries are untouched): if any value falls outside
    /// {0,1}, the column is a count, not a flag — demote to integer. Genuine
    /// numeric binaries ({0,1} only) carry no out-of-domain value and pass
    /// through. Measured on gold: 12 of 18 integer columns the model called
    /// `binary` carry a value above 1.
    ///
    /// Scans the FULL column (`values`), not the down-sampled `sample` (BACKLOG
    /// #14): the rare value above 1 that proves "count, not flag" is exactly what
    /// stride-sampling drops (`Comments` … 5/14/30 in 3 of 162 rows; `noc` … 17;
    /// `confirmed` … 81397), so a sample-only scan misses it and leaves the count
    /// mislabelled `binary`.
    fn binary_vocab_veto(
        &self,
        result: &mut ColumnResult,
        header: &str,
        sample: &[String],
        values: &[String],
    ) {
        if rhh::is_disabled("binary_vocab_veto") || result.label != "representation.boolean.binary"
        {
            return;
        }
        let non_empty = non_empty_trimmed(values);
        if non_empty.len() < 3 {
            return;
        }
        let mut any_outside_01 = false;
        for v in &non_empty {
            match v.parse::<i64>() {
                Ok(n) => {
                    if n != 0 && n != 1 {
                        any_outside_01 = true;
                    }
                }
                // Non-integer value (true/false, decimal, text) — not a numeric
                // binary this veto should touch.
                Err(_) => return,
            }
        }
        if !any_outside_01 {
            return;
        }
        result.label = "representation.numeric.integer_number".to_string();
        result.confidence = result.confidence.min(0.6);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some(format!("binary_vocab_veto:{}", header.to_lowercase()));
        if let Some(taxonomy) = self.taxonomy.as_ref() {
            result.detected_locale = detect_locale_from_validation(sample, &result.label, taxonomy);
        }
    }

    /// `checksum_substance_guard` (default ON). Generic substance check for the
    /// self-validating identifier types — replaces the per-type
    /// `*_checkdigit_veto` pattern. Any column the model labels with a
    /// checksum-bearing type (the taxonomy `checksum:` directive — isbn, aba,
    /// cusip, sedol today) is re-checked against the real check-digit arithmetic
    /// from the canonical `finetype_core::checksum` module rather than a
    /// hand-rolled copy.
    ///
    /// The taxonomy's shape pattern (10 or 13 digits) lets a large financial
    /// integer like `marketCap` 5150000128 look like an ISBN; the checksum is
    /// what distinguishes "is this type" from "is not". A column whose values
    /// mostly FAIL their type's checksum is not that type — demote by value
    /// shape: bare-number columns (ISBN/ABA lookalikes) to decimal/integer,
    /// alphanumeric columns (CUSIP/SEDOL lookalikes) to alphanumeric_id or
    /// categorical by cardinality. Genuine identifiers pass the checksum and are
    /// untouched. Measured on gold: marketCap/otherLiab/… emitted as `isbn`,
    /// longTermInvestments/… as `aba_routing`, citation_id/case_number as
    /// `cusip`/`sedol`.
    ///
    /// The checksum is owned HERE, not in the shared compiled validator. Wiring
    /// it into the validator looks tidier but regresses: the generic
    /// `value_sharpen` schema-demotion rules also consult that validator and,
    /// on a checksum-failing column, demote it to their own fallback
    /// (`numeric_code`/categorical) — a worse target than the gold-correct
    /// `integer_number` this guard produces, and they run first. So the
    /// directive scopes this guard while the validator stays shape-only.
    fn checksum_substance_guard(
        &self,
        result: &mut ColumnResult,
        _header: &str,
        sample: &[String],
    ) {
        if rhh::is_disabled("checksum_substance_guard") {
            return;
        }
        let Some(taxonomy) = self.taxonomy.as_ref() else {
            return;
        };
        // Only act on labels carrying a `checksum:` directive; resolve its
        // canonical validating function from crate::checksum.
        let Some(checksum) = taxonomy
            .get(&result.label)
            .and_then(|def| def.checksum.as_deref())
            .and_then(finetype_core::checksum::resolve)
        else {
            return;
        };
        demote_by_shape_when_substance_fails(
            result,
            sample,
            taxonomy,
            "checksum_substance_guard",
            checksum,
        );
    }

    /// `membership_substance_guard` (default ON). Twin of
    /// `checksum_substance_guard` for types whose substance is CLOSED-SET
    /// membership rather than a check digit (the taxonomy `membership:`
    /// directive — icao_airports, iata_airports, naics_codes, tld, unlocode
    /// today; see `finetype_core::membership` and labels/sets/). The taxonomy's shape
    /// pattern (`^[A-Z]{4}$` / `^[A-Z]{3}$`) confirms every same-shape token —
    /// a 4-letter stock-ticker column validates 100% as icao_code, which not
    /// only survives the veto but disarms the attractor demotion
    /// (`validation_confirmed`). Membership is what distinguishes "is this
    /// type" from "is not": a column whose values are mostly OUTSIDE the
    /// published code list is not that type — demote by value shape exactly as
    /// the checksum guard does. Genuine airport-code columns are list members
    /// and untouched. Company-reference audit W2
    /// (output/company-reference-audit/findings_and_action_plan.md).
    fn membership_substance_guard(
        &self,
        result: &mut ColumnResult,
        _header: &str,
        sample: &[String],
    ) {
        if rhh::is_disabled("membership_substance_guard") {
            return;
        }
        let Some(taxonomy) = self.taxonomy.as_ref() else {
            return;
        };
        // Only act on labels carrying a `membership:` directive; resolve its
        // canonical set from crate::membership.
        let Some(is_member) = taxonomy
            .get(&result.label)
            .and_then(|def| def.membership.as_deref())
            .and_then(finetype_core::membership::resolve)
        else {
            return;
        };
        demote_by_shape_when_substance_fails(
            result,
            sample,
            taxonomy,
            "membership_substance_guard",
            is_member,
        );
    }

    /// `url_bare_number_veto` (default ON). Twin of `amount_bare_number_veto`.
    /// A link-ish header (`Publisher URL`, `perm_unlink`, `link_type`) makes the
    /// header-hint machinery promote the column to `technology.internet.url` via
    /// an early `return`, so the early `schema_validation_gate` never re-checks
    /// the hint's assertion. But a URL is text (`https://…`); a column of bare
    /// numbers (`0`/`1`/`-1` flags, large integers) cannot be one. Undo the
    /// hint's promotion by value shape: a bare-number `url` is demoted to
    /// decimal/integer. Demotion only — genuine URL columns contain non-numeric
    /// values and pass through untouched. Measured on gold: 6 whole-number
    /// columns headed `Publisher URL`/`perm_unlink`/`link_type` were emitted as
    /// `url` (url precision 0.721).
    fn url_bare_number_veto(&self, result: &mut ColumnResult, header: &str, sample: &[String]) {
        if rhh::is_disabled("url_bare_number_veto") || result.label != "technology.internet.url" {
            return;
        }
        let (is_bare, any_decimal) = values_look_like_bare_numbers(sample);
        if is_bare {
            demote_bare_to_numeric(
                result,
                sample,
                self.taxonomy.as_ref(),
                any_decimal,
                "url_bare_number_veto",
                header,
            );
        }
    }

    /// `utc_bare_number_veto` (default ON). Twin of `url_bare_number_veto`.
    /// A `utc_offset`/`tz`-ish header makes the header-hint machinery promote the
    /// column to `datetime.offset.utc` via an early `return`, so the early
    /// `schema_validation_gate` never re-checks the hint's assertion. But the
    /// taxonomy's `datetime.offset.utc` is an explicit offset STRING ("UTC
    /// +05:00", validator `^UTC [+-]\d{2}:\d{2}$`); a column of bare hour-offsets
    /// (`-8`, `5.5`, `0`, `3.5`) cannot be one — it fails that validator ~100%.
    /// Undo the hint's promotion by value shape: a bare-number `utc` is demoted
    /// to decimal/integer. Demotion only — a genuine `UTC +HH:MM` column contains
    /// non-numeric values and passes through untouched. Measured on gold: 5
    /// `utc_offset` columns (OpenFlights + 4 gittables) were emitted as
    /// `datetime.offset.utc`; four are already gold-labelled decimal (the fifth
    /// re-adjudicated to decimal — same bare-number shape). Value-based last-resort
    /// Sharpen (decisions 0038/0048); the v24 CORROBORATION_SCOPE add for utc was
    /// inert/regressive, so this is the correct lever (roadmap #3).
    fn utc_bare_number_veto(&self, result: &mut ColumnResult, header: &str, sample: &[String]) {
        if rhh::is_disabled("utc_bare_number_veto") || result.label != "datetime.offset.utc" {
            return;
        }
        let (is_bare, _) = values_look_like_bare_numbers(sample);
        if is_bare {
            // A numeric UTC-offset column is decimal the moment it carries a
            // half/quarter-hour zone (+5.5, -3.5, +5.45). Those sort after the
            // integer offsets, beyond the 16-value window
            // `values_look_like_bare_numbers` scans, so decide decimal-vs-integer
            // over the WHOLE sample here — otherwise an integer-headed column
            // (OpenFlights `utc_offset`) misses its true decimal type.
            let any_decimal = sample
                .iter()
                .any(|v| v.contains('.') && v.trim().parse::<f64>().is_ok());
            demote_bare_to_numeric(
                result,
                sample,
                self.taxonomy.as_ref(),
                any_decimal,
                "utc_bare_number_veto",
                header,
            );
        }
    }

    /// `city_region_header_corroboration` (default ON). The model's
    /// `geography.location.city` and `.region` are value-identical siblings —
    /// both are textual place names — so the flat softmax confuses them. When the
    /// header explicitly names an administrative division above city level
    /// (`region`, `county`, `district`, `borough`, `province`, …) the header is
    /// the disambiguator: promote a `city` prediction to `region`. Measured on
    /// gold: 6 `region` columns headed `Region`/`County`/`district`/`borough`
    /// (Zimbabwe & Ecuador provinces, US counties) were emitted as `city`
    /// (region recall 0.467, city precision 0.632). Header-gated and
    /// promotion-only, so genuine cities — which do not carry admin-division
    /// headers — are untouched. Follows the choice-0094 header-corroboration
    /// pattern; ships default-on pending the corpus-honest relocation gate.
    fn city_region_header_corroboration(
        &self,
        result: &mut ColumnResult,
        header: &str,
        _sample: &[String],
    ) {
        if rhh::is_disabled("city_region_header_corroboration")
            || result.label != "geography.location.city"
            || !header_corroborates_region(header)
        {
            return;
        }
        result.label = "geography.location.region".to_string();
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some(format!(
            "city_region_header_corroboration:{}",
            header.to_lowercase()
        ));
    }

    /// `country_code_corroboration` (default ON). Two-letter ISO 3166-1 codes
    /// (`US`, `HK`, `MT`) are value-identical to other short geographic tokens,
    /// so the flat softmax files them under `region`/`state`/`city`/`country`.
    /// The `country_code` enum — 249 official ISO codes, the taxonomy's most
    /// precise validator — is the disambiguator: a confusable geography label
    /// whose values are MOSTLY valid ISO codes is a country_code. Value-based
    /// (decision 0048) and promotion-only — genuine region/state/city columns
    /// carry place names, not ISO codes, so they fail the enum and are
    /// untouched. `state`/`state_code` are deliberately NOT confusable here:
    /// their 2-letter codes legitimately overlap the ISO set, so promoting them
    /// would trade one error for another. Measured on gold: 5 columns
    /// (country_code_filter=MT, exchange_country=HK, PA, id=AU/AT/…, Country)
    /// emitted as `region`/`country`.
    fn country_code_corroboration(
        &self,
        result: &mut ColumnResult,
        _header: &str,
        sample: &[String],
    ) {
        if rhh::is_disabled("country_code_corroboration") {
            return;
        }
        const CONFUSABLE: [&str; 3] = [
            "geography.location.region",
            "geography.location.city",
            "geography.location.country",
        ];
        if !CONFUSABLE.contains(&result.label.as_str()) {
            return;
        }
        let Some(taxonomy) = self.taxonomy.as_ref() else {
            return;
        };
        let Some(validator) = taxonomy.get_validator("geography.location.country_code") else {
            return;
        };
        let non_empty = non_empty_trimmed(sample);
        if non_empty.len() < 3 {
            return;
        }
        let pass = non_empty.iter().filter(|v| validator.is_valid(v)).count();
        // Promote only on a clear majority of exact ISO codes.
        if pass * 2 <= non_empty.len() {
            return;
        }
        result.label = "geography.location.country_code".to_string();
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some("country_code_corroboration".to_string());
        result.detected_locale = None;
    }

    /// `geo_code_membership_vote` (default ON). Dominant-member vote for the
    /// `state_code` <-> `country_code` confusion, which `country_code_corroboration`
    /// deliberately does NOT touch: 31 of the 56 US-state codes (AL, CA, DE, IN,
    /// …) are ALSO ISO-3166-1 country codes, so a genuine US-states column already
    /// scores ~0.51 country coverage — a simple >50% majority would wrongly promote
    /// it. This is the set-vs-set case membership sets exist for (certainties, not
    /// simulated semantics): count each value's membership in the country_code enum
    /// vs the union of state_code's subdivision-locale enums (US/CA/AU) and assign
    /// the winner ONLY when it clears a high bar (>=0.70) AND beats the loser by a
    /// margin (>=0.20). A genuinely ambiguous column (all values valid as both) fails
    /// the margin and keeps the model's label.
    ///
    /// Motivating case (dataset-descriptor audit): GLEIF `jurisdiction` is 89% ISO
    /// country codes (IN, IT, DE, GB) + 11% ISO-3166-2 subdivisions (US-DE) — the
    /// 11% tail drags the model to `state_code`; measured coverage country 0.89 vs
    /// us_state 0.21 votes it back to `country_code`. A genuine US-states column
    /// (state 1.0, country 0.51) votes `state_code`. Value-based (0048), RHH-disableable.
    /// Reads existing taxonomy enums — no new set files.
    fn geo_code_membership_vote(
        &self,
        result: &mut ColumnResult,
        _header: &str,
        sample: &[String],
    ) {
        if rhh::is_disabled("geo_code_membership_vote") {
            return;
        }
        const COUNTRY: &str = "geography.location.country_code";
        const STATE: &str = "geography.location.state_code";
        if result.label != COUNTRY && result.label != STATE {
            return;
        }
        let Some(taxonomy) = self.taxonomy.as_ref() else {
            return;
        };
        let Some(country_v) = taxonomy.get_validator(COUNTRY) else {
            return;
        };
        // Subdivision substance is the UNION of every state_code locale enum
        // (EN_US 56 + EN_CA 13 + EN_AU 8), not the top-level `^[A-Z]{2}$` shape and
        // not US alone: Canadian province codes (NL, SK, YT, QC…) collide with
        // country codes (Netherlands, Slovakia, Mayotte), so a US-only set would
        // score a Canadian-province column ~0.5 country / low state and wrongly
        // vote it country_code. Unioning all supported locales counts it as a
        // subdivision column (measured: CA provinces score subdiv 1.0 / country 0.5).
        let subdivisions: std::collections::HashSet<String> = taxonomy
            .get(STATE)
            .and_then(|d| d.validation_by_locale.as_ref())
            .map(|by_locale| {
                by_locale
                    .values()
                    .filter_map(|v| v.enum_values.as_ref())
                    .flatten()
                    .map(|s| s.to_uppercase())
                    .collect()
            })
            .unwrap_or_default();
        if subdivisions.is_empty() {
            return;
        }
        let non_empty: Vec<String> = sample
            .iter()
            .map(|v| v.trim().to_uppercase())
            .filter(|v| !v.is_empty())
            .collect();
        if non_empty.len() < 3 {
            return;
        }
        let n = non_empty.len() as f64;
        let country_cov = non_empty.iter().filter(|v| country_v.is_valid(v)).count() as f64 / n;
        let state_cov = non_empty
            .iter()
            .filter(|v| subdivisions.contains(*v))
            .count() as f64
            / n;
        const WIN: f64 = 0.70;
        const MARGIN: f64 = 0.20;
        let winner = if country_cov >= WIN && country_cov >= state_cov + MARGIN {
            COUNTRY
        } else if state_cov >= WIN && state_cov >= country_cov + MARGIN {
            STATE
        } else {
            // No clear dominant member (e.g. a column of codes valid as both) —
            // leave the model's call rather than guess.
            return;
        };
        if winner == result.label {
            return;
        }
        let from = result.label.clone();
        result.label = winner.to_string();
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some(format!("geo_code_membership_vote:{from}"));
        result.detected_locale = detect_locale_from_validation(sample, &result.label, taxonomy);
    }

    /// `geo_code_nonmembership_demotion` (default ON). The set-vs-set companion to
    /// `geo_code_membership_vote`: the vote ARBITRATES a `state_code` <-> `country_code`
    /// column but ABSTAINS — keeps the model's label — when NEITHER set covers the
    /// column. That abstention leaves the wrong label on a NON-geographic short-code
    /// column the flat softmax pulled onto a 2-letter geo type: `work_type` (OT/EQ/MH),
    /// `permit_type` (EW/FO/DM), `ticker` (NVDA). These PASS `state_code`/`country_code`'s
    /// shape-only `^[A-Z]{2}$` validator (so `schema_fail_demotion`, which keys on
    /// validator failure, cannot touch them) yet are not location codes. Membership is
    /// the discriminator: measure coverage against the country enum UNION the state
    /// subdivision enums — counting bare codes AND the ISO-3166-2 hyphenated form
    /// (`US-MA`), because a genuine subdivision column in hyphenated form scores 0% on
    /// the bare set and would otherwise be FALSE-demoted (the gleif `region` control).
    /// When < 50% of values are ANY location code, demote to the cardinality residual
    /// (`word` for a small vocabulary, `alphanumeric_id` for high-card) — matching
    /// `schema_fail_demotion`. Measured (external band): work_type 39% / permit_type 43%
    /// / permit_subtype 26% / ticker 9% coverage vs genuine country 100% / jurisdiction
    /// 100% / region 100% — a 0.50 floor separates them cleanly. A SHAPE GATE fires the
    /// guard ONLY on the 2-letter `^[A-Z]{2}$` attractor form: a genuine country column
    /// in ALPHA-3 (`FRA`) or numeric form is a real geography type the enum doesn't list,
    /// and demoting it is a false-positive (the gold `primaryCountry.alpha3Code` /
    /// `Country Code` regression, caught by a gold-flat re-check). Value-based (0048),
    /// demote-only, RHH-disableable. KNOWN LIMIT: a genuine state column from a locale
    /// outside the US/CA/AU subdivision enums (e.g. German Bundesland codes) scores low
    /// and would demote — expanding the subdivision roster is the fix, not relaxing this.
    fn geo_code_nonmembership_demotion(
        &self,
        result: &mut ColumnResult,
        _header: &str,
        sample: &[String],
    ) {
        if rhh::is_disabled("geo_code_nonmembership_demotion") {
            return;
        }
        const COUNTRY: &str = "geography.location.country_code";
        const STATE: &str = "geography.location.state_code";
        if result.label != COUNTRY && result.label != STATE {
            return;
        }
        let Some(taxonomy) = self.taxonomy.as_ref() else {
            return;
        };
        // Build the country + subdivision sets directly from the taxonomy enums —
        // the country enum from `validation.enum_values`, the subdivision union the
        // same way `geo_code_membership_vote` builds it. (The full taxonomy carries
        // the complete ISO-3166-1 list; the inline test fixture a curated subset.)
        let country_codes: std::collections::HashSet<String> = taxonomy
            .get(COUNTRY)
            .and_then(|d| d.validation.as_ref())
            .and_then(|val| val.enum_values.as_ref())
            .map(|e| e.iter().map(|s| s.to_uppercase()).collect())
            .unwrap_or_default();
        let subdivisions: std::collections::HashSet<String> = taxonomy
            .get(STATE)
            .and_then(|d| d.validation_by_locale.as_ref())
            .map(|by_locale| {
                by_locale
                    .values()
                    .filter_map(|v| v.enum_values.as_ref())
                    .flatten()
                    .map(|s| s.to_uppercase())
                    .collect()
            })
            .unwrap_or_default();
        if country_codes.is_empty() || subdivisions.is_empty() {
            return;
        }
        let non_empty: Vec<String> = sample
            .iter()
            .map(|v| v.trim().to_uppercase())
            .filter(|v| !v.is_empty())
            .collect();
        if non_empty.len() < 3 {
            return;
        }
        // Shape gate: only fire on the 2-letter attractor shape `^[A-Z]{2}$` — the
        // exact form that pulls a NON-geo short code onto `state_code`/`country_code`
        // (work_type OT/EQ, permit_type EW/FO). A genuine country column in ALPHA-3
        // (`FRA`, `USA`) or numeric form is NOT this shape and is a real geography
        // type the enum simply doesn't list — demoting it is a false-positive (the
        // gold `primaryCountry.alpha3Code` / `Country Code` control). Requiring the
        // 2-letter majority leaves those columns untouched.
        let two_letter = non_empty
            .iter()
            .filter(|v| v.len() == 2 && v.chars().all(|c| c.is_ascii_uppercase()))
            .count();
        if two_letter * 2 < non_empty.len() {
            return;
        }
        // "Any location code" = a country-enum member, a bare subdivision code, or an
        // ISO-3166-2 hyphenated subdivision (`US-MA`) whose prefix is a real country.
        // The hyphenated arm is load-bearing: without it a genuine `US-MA` region
        // column scores 0% and is wrongly demoted.
        let is_geo_code = |v: &str| -> bool {
            if country_codes.contains(v) || subdivisions.contains(v) {
                return true;
            }
            if let Some((cc, sub)) = v.split_once('-') {
                if cc.len() == 2
                    && !sub.is_empty()
                    && sub.len() <= 3
                    && sub.chars().all(|c| c.is_ascii_alphanumeric())
                    && country_codes.contains(cc)
                {
                    return true;
                }
            }
            false
        };
        let geo_cov =
            non_empty.iter().filter(|v| is_geo_code(v)).count() as f64 / non_empty.len() as f64;
        const FLOOR: f64 = 0.50;
        if geo_cov >= FLOOR {
            // Enough real location codes -> genuine geography column, leave it.
            return;
        }
        // Not a location-code column. Demote to the cardinality residual, matching
        // `schema_fail_demotion` (small vocabulary -> word; high-card -> alphanumeric_id).
        let mut distinct: Vec<&str> = non_empty.iter().map(String::as_str).collect();
        distinct.sort_unstable();
        distinct.dedup();
        let residual = if (1..=20).contains(&distinct.len()) {
            "representation.text.word"
        } else {
            "representation.identifier.alphanumeric_id"
        };
        let from = result.label.clone();
        result.label = residual.to_string();
        result.confidence = result.confidence.min(0.6);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some(format!("geo_code_nonmembership_demotion:{from}"));
        result.detected_locale = None;
    }

    /// `geo_subdivision_membership_promote` (default ON). The promote companion to
    /// the geo membership guards: a column of ISO-3166-2 subdivision codes
    /// (`US-PA`, `GB-ENG`) is a `geography.location.region`, but the flat softmax —
    /// which never learned the hyphenated form — files it under a residual
    /// (`alphanumeric_id`/`unknown`) or a lookalike (`last_name`, `locale_code`),
    /// and the unlocode `membership_substance_guard` then demotes the model's
    /// `unlocode` guess to `unknown` (external band: ourairports `iso_region`
    /// US-PA → unknown). The `CC-SSS` shape is NOT precise — product/OS/locale
    /// hyphen-codes share it (100 alphanumeric_id + 16 os + 13 entity columns at
    /// corpus scale) — so this keys on published ISO-3166-2 MEMBERSHIP
    /// (`finetype_core::membership::iso_3166_2`, labels/sets/iso_3166_2_codes.txt,
    /// 5046 codes across 200 countries). When ≥90% of values are real subdivision
    /// codes, promote to region. Membership at that density is self-precise (a
    /// non-geo column is ~never 90% exact subdivision codes), so it fires on ANY
    /// source label except an already-correct region — mirroring the naics /
    /// s_expression recoveries. Value-based (0048), promote-only, RHH-disableable.
    fn geo_subdivision_membership_promote(
        &self,
        result: &mut ColumnResult,
        _header: &str,
        sample: &[String],
    ) {
        if rhh::is_disabled("geo_subdivision_membership_promote") {
            return;
        }
        const REGION: &str = "geography.location.region";
        // Already the target — nothing to promote.
        if result.label == REGION {
            return;
        }
        let non_empty: Vec<&str> = sample
            .iter()
            .map(|v| v.trim())
            .filter(|v| !v.is_empty())
            .collect();
        if non_empty.len() < 3 {
            return;
        }
        let members = non_empty
            .iter()
            .filter(|v| finetype_core::membership::iso_3166_2(v))
            .count();
        // ≥90% published ISO-3166-2 membership — self-precise at this density, so
        // the source label is irrelevant (a non-geo column is ~never 90% exact
        // subdivision codes). Shape alone would false-promote hyphenated
        // product/OS/locale codes; membership is the discriminator.
        if members * 10 < non_empty.len() * 9 {
            return;
        }
        let from = result.label.clone();
        result.label = REGION.to_string();
        result.confidence = result.confidence.max(0.85);
        result.disambiguation_applied = true;
        result.disambiguation_rule = Some(format!("geo_subdivision_membership_promote:{from}"));
        result.detected_locale = None;
    }

    /// `amount_bare_number_veto` (default ON). Runs AFTER `apply_header_sharpen`,
    /// from the caller, because the currency.amount over-emission is CREATED inside
    /// the header-hint machinery via an early `return` (a money-ish header promotes
    /// the model's correct integer/decimal to currency.amount — P=0.105 on gold),
    /// which a guard *within* apply_header_sharpen cannot reach. The header cannot
    /// disambiguate (genuine amounts — base_salary, price — carry money headers
    /// too), but a genuine currency.amount carries a currency signal (£45.17,
    /// EUR 4 459 807) while the false positives are bare numbers (netIncome
    /// 795000000). So undo the hint's promotion by value shape: a bare-number
    /// currency.amount is demoted back to decimal/integer. Demotion only.
    fn amount_bare_number_veto(&self, result: &mut ColumnResult, header: &str, sample: &[String]) {
        if rhh::is_disabled("amount_bare_number_veto") || result.label != "finance.currency.amount"
        {
            return;
        }
        let (is_bare, any_decimal) = values_look_like_bare_numbers(sample);
        if is_bare {
            demote_bare_to_numeric(
                result,
                sample,
                self.taxonomy.as_ref(),
                any_decimal,
                "amount_bare_number_veto",
                header,
            );
        }
    }
}