@mailwoman/neural 4.9.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +102 -0
  2. package/out/browser.d.ts +1 -0
  3. package/out/browser.d.ts.map +1 -1
  4. package/out/browser.js +5 -0
  5. package/out/browser.js.map +1 -1
  6. package/out/case-normalize.d.ts +34 -0
  7. package/out/case-normalize.d.ts.map +1 -0
  8. package/out/case-normalize.js +50 -0
  9. package/out/case-normalize.js.map +1 -0
  10. package/out/classifier.d.ts +24 -2
  11. package/out/classifier.d.ts.map +1 -1
  12. package/out/classifier.js +123 -23
  13. package/out/classifier.js.map +1 -1
  14. package/out/index.d.ts +2 -0
  15. package/out/index.d.ts.map +1 -1
  16. package/out/index.js +2 -0
  17. package/out/index.js.map +1 -1
  18. package/out/onnx-runner.d.ts +7 -0
  19. package/out/onnx-runner.d.ts.map +1 -1
  20. package/out/onnx-runner.js +10 -0
  21. package/out/onnx-runner.js.map +1 -1
  22. package/out/postcode-repair.d.ts +17 -0
  23. package/out/postcode-repair.d.ts.map +1 -1
  24. package/out/postcode-repair.js +48 -0
  25. package/out/postcode-repair.js.map +1 -1
  26. package/out/scorer.d.ts +105 -0
  27. package/out/scorer.d.ts.map +1 -0
  28. package/out/scorer.js +286 -0
  29. package/out/scorer.js.map +1 -0
  30. package/out/soft-features.d.ts +67 -0
  31. package/out/soft-features.d.ts.map +1 -0
  32. package/out/soft-features.js +49 -0
  33. package/out/soft-features.js.map +1 -0
  34. package/out/weights.d.ts +109 -0
  35. package/out/weights.d.ts.map +1 -1
  36. package/out/weights.js +157 -1
  37. package/out/weights.js.map +1 -1
  38. package/out/word-consistency.d.ts +60 -0
  39. package/out/word-consistency.d.ts.map +1 -0
  40. package/out/word-consistency.js +140 -0
  41. package/out/word-consistency.js.map +1 -0
  42. package/package.json +5 -3
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The SOFT-FEATURE channels (#718) — the per-piece anchor + gazetteer clues the model conditions on
7
+ * alongside `input_ids`. This module is the single, PURE, browser-safe home for the channel
8
+ * choreography that used to live inline in `NeuralAddressClassifier.#decode`: build the postcode
9
+ * anchor, build the gazetteer clue, and (when paired with the matching train-time half) suppress
10
+ * the gazetteer clue adjacent to a postcode-anchor hit.
11
+ *
12
+ * It is the LOAD-BEARING contract surface for the ProductionScorer (#718): the scorer asserts which
13
+ * channels are fed, this function decides HOW they are fed. Keeping it a pure function means both
14
+ * the classifier and any harness build features identically — there is exactly one choreography.
15
+ *
16
+ * No `fs`, no Node builtins: the caller hands in the already-parsed lookup/lexicon (mirrors
17
+ * `anchor-inference.ts` / `gazetteer-inference.ts`, which are themselves pure for the same
18
+ * reason).
19
+ */
20
+ import { type AnchorLookup } from "./anchor-inference.js";
21
+ import { type GazetteerLexicon } from "./gazetteer-inference.js";
22
+ import type { TokenizedPiece } from "./tokenizer.js";
23
+ /** A built soft-feature channel: per-piece feature rows + per-piece confidence. */
24
+ export interface SoftFeatureChannel {
25
+ features: number[][];
26
+ confidence: number[];
27
+ }
28
+ /** The soft-feature channels fed to the runner. Each is present only when its source is configured. */
29
+ export interface SoftFeatures {
30
+ /** Postcode-anchor channel (#239/#240) — present iff `postcodeAnchorLookup` was supplied. */
31
+ anchor?: SoftFeatureChannel;
32
+ /**
33
+ * Gazetteer-anchor channel (#464) — present iff `gazetteerLexicon` was supplied. Already
34
+ * choreographed: when `suppressGazetteerNearPostcode` is set AND an anchor channel exists, the
35
+ * clue is zeroed adjacent to postcode-anchor hits before it's returned here.
36
+ */
37
+ gazetteer?: SoftFeatureChannel;
38
+ }
39
+ /** Sources + choreography for {@link buildSoftFeatures}. Mirrors the classifier's config fields. */
40
+ export interface SoftFeatureSources {
41
+ /** Postcode→anchor lookup (#239/#240). Omit to skip the anchor channel. */
42
+ postcodeAnchorLookup?: AnchorLookup;
43
+ /** Gazetteer-anchor lexicon (#464). Omit to skip the gazetteer channel. */
44
+ gazetteerLexicon?: GazetteerLexicon;
45
+ /**
46
+ * Channel choreography (#464, v0.9.13 postcode fix): zero the gazetteer clue on pieces adjacent
47
+ * to a postcode-anchor hit. Needs BOTH a `gazetteerLexicon` and a `postcodeAnchorLookup` to take
48
+ * effect (the suppression is keyed off the anchor's confidence). PAIRING IS LOAD-BEARING — enable
49
+ * this IFF the model was trained with the matching train-time choreography. See
50
+ * `suppressGazetteerNearPostcode` in `gazetteer-inference.ts`.
51
+ */
52
+ suppressGazetteerNearPostcode?: boolean;
53
+ }
54
+ /**
55
+ * Build the soft-feature channels for `text`/`pieces` from the configured sources — the EXACT
56
+ * choreography previously inlined in `NeuralAddressClassifier.#decode`:
57
+ *
58
+ * 1. Anchor channel from `postcodeAnchorLookup` (no-op when unset).
59
+ * 2. Gazetteer channel from `gazetteerLexicon` (no-op when unset).
60
+ * 3. If both channels exist AND `suppressGazetteerNearPostcode`, zero the gazetteer clue adjacent to
61
+ * postcode-anchor hits.
62
+ *
63
+ * Pure + byte-stable: the returned channels are identical to the pre-#718 inline path, so wiring
64
+ * this into `#decode` is a behavior-preserving refactor.
65
+ */
66
+ export declare function buildSoftFeatures(text: string, pieces: ReadonlyArray<TokenizedPiece>, sources: SoftFeatureSources): SoftFeatures;
67
+ //# sourceMappingURL=soft-features.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soft-features.d.ts","sourceRoot":"","sources":["../soft-features.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAyD,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AACvH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD,mFAAmF;AACnF,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAA;IACpB,UAAU,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,uGAAuG;AACvG,MAAM,WAAW,YAAY;IAC5B,6FAA6F;IAC7F,MAAM,CAAC,EAAE,kBAAkB,CAAA;IAC3B;;;;OAIG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAA;CAC9B;AAED,oGAAoG;AACpG,MAAM,WAAW,kBAAkB;IAClC,2EAA2E;IAC3E,oBAAoB,CAAC,EAAE,YAAY,CAAA;IACnC,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC;;;;;;OAMG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAA;CACvC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,EACrC,OAAO,EAAE,kBAAkB,GACzB,YAAY,CAgBd"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The SOFT-FEATURE channels (#718) — the per-piece anchor + gazetteer clues the model conditions on
7
+ * alongside `input_ids`. This module is the single, PURE, browser-safe home for the channel
8
+ * choreography that used to live inline in `NeuralAddressClassifier.#decode`: build the postcode
9
+ * anchor, build the gazetteer clue, and (when paired with the matching train-time half) suppress
10
+ * the gazetteer clue adjacent to a postcode-anchor hit.
11
+ *
12
+ * It is the LOAD-BEARING contract surface for the ProductionScorer (#718): the scorer asserts which
13
+ * channels are fed, this function decides HOW they are fed. Keeping it a pure function means both
14
+ * the classifier and any harness build features identically — there is exactly one choreography.
15
+ *
16
+ * No `fs`, no Node builtins: the caller hands in the already-parsed lookup/lexicon (mirrors
17
+ * `anchor-inference.ts` / `gazetteer-inference.ts`, which are themselves pure for the same
18
+ * reason).
19
+ */
20
+ import { buildAnchorFeatures } from "./anchor-inference.js";
21
+ import { buildGazetteerFeatures, suppressGazetteerNearPostcode } from "./gazetteer-inference.js";
22
+ /**
23
+ * Build the soft-feature channels for `text`/`pieces` from the configured sources — the EXACT
24
+ * choreography previously inlined in `NeuralAddressClassifier.#decode`:
25
+ *
26
+ * 1. Anchor channel from `postcodeAnchorLookup` (no-op when unset).
27
+ * 2. Gazetteer channel from `gazetteerLexicon` (no-op when unset).
28
+ * 3. If both channels exist AND `suppressGazetteerNearPostcode`, zero the gazetteer clue adjacent to
29
+ * postcode-anchor hits.
30
+ *
31
+ * Pure + byte-stable: the returned channels are identical to the pre-#718 inline path, so wiring
32
+ * this into `#decode` is a behavior-preserving refactor.
33
+ */
34
+ export function buildSoftFeatures(text, pieces, sources) {
35
+ const anchor = sources.postcodeAnchorLookup
36
+ ? buildAnchorFeatures(text, pieces, sources.postcodeAnchorLookup)
37
+ : undefined;
38
+ const gazetteer = sources.gazetteerLexicon
39
+ ? buildGazetteerFeatures(text, pieces, sources.gazetteerLexicon)
40
+ : undefined;
41
+ const gazFed = gazetteer && anchor && sources.suppressGazetteerNearPostcode
42
+ ? suppressGazetteerNearPostcode(gazetteer, anchor.confidence)
43
+ : gazetteer;
44
+ return {
45
+ ...(anchor ? { anchor } : {}),
46
+ ...(gazFed ? { gazetteer: gazFed } : {}),
47
+ };
48
+ }
49
+ //# sourceMappingURL=soft-features.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soft-features.js","sourceRoot":"","sources":["../soft-features.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,mBAAmB,EAAqB,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAyB,MAAM,0BAA0B,CAAA;AAqCvH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAChC,IAAY,EACZ,MAAqC,EACrC,OAA2B;IAE3B,MAAM,MAAM,GAAG,OAAO,CAAC,oBAAoB;QAC1C,CAAC,CAAC,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,oBAAoB,CAAC;QACjE,CAAC,CAAC,SAAS,CAAA;IACZ,MAAM,SAAS,GAAG,OAAO,CAAC,gBAAgB;QACzC,CAAC,CAAC,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC;QAChE,CAAC,CAAC,SAAS,CAAA;IACZ,MAAM,MAAM,GACX,SAAS,IAAI,MAAM,IAAI,OAAO,CAAC,6BAA6B;QAC3D,CAAC,CAAC,6BAA6B,CAAC,SAAS,EAAE,MAAM,CAAC,UAAU,CAAC;QAC7D,CAAC,CAAC,SAAS,CAAA;IAEb,OAAO;QACN,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7B,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxC,CAAA;AACF,CAAC"}
package/out/weights.d.ts CHANGED
@@ -36,6 +36,12 @@ export interface ResolveWeightsOpts {
36
36
  * checkpoint via explicit paths.
37
37
  */
38
38
  modelCardPath?: string;
39
+ /**
40
+ * Serving tier (#718 D1). `"server"` (default) = anchor + gazetteer channels; `"pocket"` =
41
+ * anchor-only (skip the gazetteer lexicon even when shipped). Selects which soft-feature sibling
42
+ * artifacts {@link resolveWeights} surfaces — the loader feeds only the resolved channels.
43
+ */
44
+ tier?: "server" | "pocket";
39
45
  }
40
46
  export interface ResolvedWeights {
41
47
  modelPath: string;
@@ -52,6 +58,26 @@ export interface ResolvedWeights {
52
58
  * exist (pre-v0.6.0 bundles or CE-only training).
53
59
  */
54
60
  crfTransitionsPath?: string;
61
+ /**
62
+ * Path to the postcode→anchor source shipped beside the resolved model (#718 D1) — the soft-feed
63
+ * `loadFromWeights` reads to feed the anchor channel without a callsite change. Prefer the
64
+ * compact PCB1 binary (`postcode-<cc>.bin`, decoded via
65
+ * `PostcodeBinaryResolver.toAnchorLookup()`), else a JSON anchor lookup (`anchor-lookup.json`,
66
+ * parsed via `parseAnchorLookup`). `undefined` when the package ships neither (a plain/pre-#718
67
+ * bundle) — the loader then runs anchor-OFF. The `binary` flag tells the loader which parser to
68
+ * use.
69
+ */
70
+ anchorLookupPath?: {
71
+ path: string;
72
+ binary: boolean;
73
+ };
74
+ /**
75
+ * Path to the gazetteer-anchor lexicon (`anchor-lexicon-v1.json`, #464) shipped beside the
76
+ * resolved model. `undefined` when the package doesn't ship it, OR when `opts.tier === "pocket"`
77
+ * (pocket is anchor-only — the gazetteer channel is deliberately skipped). Read by the
78
+ * `loadFromWeights` soft-feed via `parseGazetteerLexicon`.
79
+ */
80
+ gazetteerLexiconPath?: string;
55
81
  /** "explicit" if both paths came from opts; "package:<name>" if resolved via require.resolve. */
56
82
  source: string;
57
83
  }
@@ -68,6 +94,89 @@ export declare function resolveWeights(opts: ResolveWeightsOpts): ResolvedWeight
68
94
  * and should be loud, not silently re-defaulted.
69
95
  */
70
96
  export declare function readLabelsFromModelCard(modelCardPath: string | undefined): readonly string[] | undefined;
97
+ /**
98
+ * The structured `requires` block of a `model-card.json` (#718) — the declared SHIP-CONFIG the
99
+ * model was trained against. The ProductionScorer reads this and FAILS CLOSED when a declared
100
+ * channel isn't actually fed (silent OOD is the #566/#685 trap). Each channel is optional; a
101
+ * missing channel means "not declared" (treated as not-required).
102
+ */
103
+ export interface RequiredChannels {
104
+ /** Postcode-anchor channel (#239/#240). */
105
+ anchor?: {
106
+ required: boolean;
107
+ };
108
+ /** Gazetteer-anchor channel (#464). */
109
+ gazetteer?: {
110
+ required: boolean;
111
+ };
112
+ /** Address-system conventions (#511 Tier A). `mode` mirrors `ParseOpts.addressSystemConventions`. */
113
+ conventions?: {
114
+ required: boolean;
115
+ mode?: "auto" | string;
116
+ };
117
+ /** Punctuation-gap span bridge (v4.4.0 corrective). */
118
+ bridge?: {
119
+ required: boolean;
120
+ };
121
+ /** Near-postcode gazetteer choreography (#464, v0.9.13). */
122
+ suppress_gazetteer_near_postcode?: boolean;
123
+ }
124
+ /**
125
+ * Read the structured `requires` block from a `model-card.json` (#718). DEFENSIVE: returns
126
+ * `undefined` when the card is absent, unreadable, or has no `requires` field (callers then INFER
127
+ * the required channels from the ONNX graph — see `inferRequiredChannelsFromInputs`). Throws ONLY
128
+ * when the field is PRESENT but corrupt (not an object, or a channel entry with a non-boolean
129
+ * `required`) — a malformed declared contract is a loud artifact bug, not a silent re-default.
130
+ */
131
+ export declare function readRequiredChannels(modelCardPath: string | undefined): RequiredChannels | undefined;
132
+ /**
133
+ * Back-compat inference of the required soft-feature channels from an ONNX model's declared input
134
+ * names (#718). A model that exports `anchor_features` / `gazetteer_features` declared those
135
+ * channels mandatory at train time — feeding zeros is the channel-off identity, but a model TRAINED
136
+ * with the channel is OOD when scored without it. Cards without a `requires` block (every pre-#718
137
+ * bundle) route through here so the fail-closed guard still protects them. Conventions/bridge are
138
+ * NOT graph-observable (no dedicated input), so they're left undeclared here — only the card
139
+ * declares them.
140
+ */
141
+ export declare function inferRequiredChannelsFromInputs(inputNames: readonly string[]): RequiredChannels;
142
+ /**
143
+ * One tag's certified capability under a (tier × address-system) cell of the capability manifest
144
+ * (#718/#719). `maskOffF1` is the model's measured per-tag exact-match F1 with the conventions mask
145
+ * OFF; `maskOnF1` is the same with the mask ON — recorded ONLY for tags some codex `forbiddenTags`
146
+ * row suppresses, because that's the only place the loader's delta-gate consults it.
147
+ */
148
+ export interface TagCapability {
149
+ /** Measured per-tag F1 (percent) with the conventions mask OFF — the model's real capability. */
150
+ maskOffF1: number;
151
+ /** Measured per-tag F1 (percent) with the mask ON. Present only for codex-forbidden tags. */
152
+ maskOnF1?: number;
153
+ }
154
+ /**
155
+ * The `capabilities` block of a `model-card.json` (#718/#719): per serving TIER (`server` =
156
+ * anchor+gazetteer; `pocket` = anchor-only) × per codex address-system × per tag, the model's
157
+ * certified per-tag capability. The `createScorer` loader reads this to FAIL CLOSED when a
158
+ * conventions mask would forbid a tag the model is certified to emit — the structural fix that
159
+ * makes the D2/#719 bug-class (a mask destroying a demonstrated capability) impossible.
160
+ *
161
+ * Shape: `capabilities[tier][system][tag] = { maskOffF1, maskOnF1? }`. A `$comment` provenance key
162
+ * may sit alongside the tier keys and is ignored by readers.
163
+ */
164
+ export type CapabilityManifest = Record<string, Record<string, Record<string, TagCapability>>>;
165
+ /**
166
+ * Read the `capabilities` block from a `model-card.json` (#718/#719). DEFENSIVE, mirroring
167
+ * `readRequiredChannels`: returns `undefined` when the card is absent, unreadable, or has no
168
+ * `capabilities` field (a pre-#718 card → the loader's delta-gate is skipped, back-compat). Throws
169
+ * ONLY when the field is PRESENT but not an object — a corrupt declared contract is a loud artifact
170
+ * bug, not a silent skip. Tier/system/tag sub-shapes are read leniently (a malformed cell simply
171
+ * yields no capability claim — `undefined` from `lookupTagCapability`).
172
+ */
173
+ export declare function readCapabilityManifest(modelCardPath: string | undefined): CapabilityManifest | undefined;
174
+ /**
175
+ * Resolve `capabilities[tier][system][tag]` to a `TagCapability`, returning `undefined` for any
176
+ * missing/malformed cell (a tag the model is NOT certified for — the loader treats that as legal:
177
+ * the model can't emit it, so a mask can't destroy it). Skips the `$comment` provenance key.
178
+ */
179
+ export declare function lookupTagCapability(manifest: CapabilityManifest | undefined, tier: string, system: string, tag: string): TagCapability | undefined;
71
180
  export interface CrfTransitions {
72
181
  transitions: number[][];
73
182
  startTransitions: number[];
@@ -1 +1 @@
1
- {"version":3,"file":"weights.d.ts","sourceRoot":"","sources":["../weights.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH,MAAM,WAAW,kBAAkB;IAClC,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iFAAiF;IACjF,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,iGAAiG;IACjG,MAAM,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,eAAe,CAkDxE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAwBxG;AAED,MAAM,WAAW,cAAc;IAC9B,WAAW,EAAE,MAAM,EAAE,EAAE,CAAA;IACvB,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,cAAc,EAAE,MAAM,EAAE,CAAA;CACxB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CA0B1F"}
1
+ {"version":3,"file":"weights.d.ts","sourceRoot":"","sources":["../weights.ts"],"names":[],"mappings":"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"}
package/out/weights.js CHANGED
@@ -66,7 +66,42 @@ export function resolveWeights(opts) {
66
66
  const modelCardPath = existsSync(modelCardCandidate) ? modelCardCandidate : undefined;
67
67
  const crfCandidate = resolve(packageDir, "crf-transitions.json");
68
68
  const crfTransitionsPath = existsSync(crfCandidate) ? crfCandidate : undefined;
69
- return { modelPath, tokenizerPath, modelCardPath, crfTransitionsPath, source: `package:${packageName}` };
69
+ // Soft-feature sibling artifacts (#718 D1): the anchor + gazetteer sources the package ships so
70
+ // `loadFromWeights` can feed the channels the model was trained against — without a callsite
71
+ // change. Resolved package-dir-relative via the same `existsSync → undefined` pattern as the CRF
72
+ // transitions above. The locale tag's region subtag (`en-us` → `us`) names the PCB1 binary.
73
+ const country = locale.split("-")[1] ?? "";
74
+ const anchorLookupPath = resolveAnchorLookupSibling(packageDir, country);
75
+ // Tier `"pocket"` is anchor-only — never surface the gazetteer lexicon (the loader then skips it).
76
+ const gazetteerCandidate = resolve(packageDir, "anchor-lexicon-v1.json");
77
+ const gazetteerLexiconPath = opts.tier === "pocket" ? undefined : existsSync(gazetteerCandidate) ? gazetteerCandidate : undefined;
78
+ return {
79
+ modelPath,
80
+ tokenizerPath,
81
+ modelCardPath,
82
+ crfTransitionsPath,
83
+ ...(anchorLookupPath ? { anchorLookupPath } : {}),
84
+ ...(gazetteerLexiconPath ? { gazetteerLexiconPath } : {}),
85
+ source: `package:${packageName}`,
86
+ };
87
+ }
88
+ /**
89
+ * Locate the package's postcode→anchor source for the soft-feed (#718 D1), preferring the compact
90
+ * PCB1 binary (`postcode-<cc>.bin`, ~0.66 MB) over the much larger JSON lookup
91
+ * (`anchor-lookup.json`, the 3.2 MB pilot dump). Returns the path + a `binary` flag so the loader
92
+ * picks the right parser (`PostcodeBinaryResolver.toAnchorLookup()` vs `parseAnchorLookup`).
93
+ * `undefined` when neither ships.
94
+ */
95
+ function resolveAnchorLookupSibling(packageDir, country) {
96
+ if (country) {
97
+ const binary = resolve(packageDir, `postcode-${country}.bin`);
98
+ if (existsSync(binary))
99
+ return { path: binary, binary: true };
100
+ }
101
+ const json = resolve(packageDir, "anchor-lookup.json");
102
+ if (existsSync(json))
103
+ return { path: json, binary: false };
104
+ return undefined;
70
105
  }
71
106
  /**
72
107
  * Read the `labels` array from a `model-card.json` file. Returns `undefined` when the file is
@@ -107,6 +142,127 @@ export function readLabelsFromModelCard(modelCardPath) {
107
142
  }
108
143
  return Object.freeze(labels.slice());
109
144
  }
145
+ /**
146
+ * Read the structured `requires` block from a `model-card.json` (#718). DEFENSIVE: returns
147
+ * `undefined` when the card is absent, unreadable, or has no `requires` field (callers then INFER
148
+ * the required channels from the ONNX graph — see `inferRequiredChannelsFromInputs`). Throws ONLY
149
+ * when the field is PRESENT but corrupt (not an object, or a channel entry with a non-boolean
150
+ * `required`) — a malformed declared contract is a loud artifact bug, not a silent re-default.
151
+ */
152
+ export function readRequiredChannels(modelCardPath) {
153
+ if (!modelCardPath || !existsSync(modelCardPath))
154
+ return undefined;
155
+ let raw;
156
+ try {
157
+ raw = readFileSync(modelCardPath, "utf8");
158
+ }
159
+ catch {
160
+ return undefined;
161
+ }
162
+ let parsed;
163
+ try {
164
+ parsed = JSON.parse(raw);
165
+ }
166
+ catch {
167
+ return undefined;
168
+ }
169
+ if (typeof parsed !== "object" || parsed === null)
170
+ return undefined;
171
+ const requires = parsed.requires;
172
+ if (requires === undefined)
173
+ return undefined;
174
+ if (typeof requires !== "object" || requires === null || Array.isArray(requires)) {
175
+ throw new Error(`model-card.json at ${modelCardPath} has a malformed \`requires\` field — ` +
176
+ `expected an object, got ${JSON.stringify(requires)}.`);
177
+ }
178
+ const obj = requires;
179
+ // Channel entries must be `{ required: boolean, ... }`; a present-but-shapeless entry is corrupt.
180
+ for (const channel of ["anchor", "gazetteer", "conventions", "bridge"]) {
181
+ const entry = obj[channel];
182
+ if (entry === undefined)
183
+ continue;
184
+ if (typeof entry !== "object" ||
185
+ entry === null ||
186
+ typeof entry.required !== "boolean") {
187
+ throw new Error(`model-card.json at ${modelCardPath} has a malformed \`requires.${channel}\` entry — ` +
188
+ `expected { required: boolean }, got ${JSON.stringify(entry)}.`);
189
+ }
190
+ }
191
+ if (obj.suppress_gazetteer_near_postcode !== undefined && typeof obj.suppress_gazetteer_near_postcode !== "boolean") {
192
+ throw new Error(`model-card.json at ${modelCardPath} has a malformed \`requires.suppress_gazetteer_near_postcode\` ` +
193
+ `field — expected a boolean, got ${JSON.stringify(obj.suppress_gazetteer_near_postcode)}.`);
194
+ }
195
+ return requires;
196
+ }
197
+ /**
198
+ * Back-compat inference of the required soft-feature channels from an ONNX model's declared input
199
+ * names (#718). A model that exports `anchor_features` / `gazetteer_features` declared those
200
+ * channels mandatory at train time — feeding zeros is the channel-off identity, but a model TRAINED
201
+ * with the channel is OOD when scored without it. Cards without a `requires` block (every pre-#718
202
+ * bundle) route through here so the fail-closed guard still protects them. Conventions/bridge are
203
+ * NOT graph-observable (no dedicated input), so they're left undeclared here — only the card
204
+ * declares them.
205
+ */
206
+ export function inferRequiredChannelsFromInputs(inputNames) {
207
+ const names = new Set(inputNames);
208
+ return {
209
+ ...(names.has("anchor_features") ? { anchor: { required: true } } : {}),
210
+ ...(names.has("gazetteer_features") ? { gazetteer: { required: true } } : {}),
211
+ };
212
+ }
213
+ /**
214
+ * Read the `capabilities` block from a `model-card.json` (#718/#719). DEFENSIVE, mirroring
215
+ * `readRequiredChannels`: returns `undefined` when the card is absent, unreadable, or has no
216
+ * `capabilities` field (a pre-#718 card → the loader's delta-gate is skipped, back-compat). Throws
217
+ * ONLY when the field is PRESENT but not an object — a corrupt declared contract is a loud artifact
218
+ * bug, not a silent skip. Tier/system/tag sub-shapes are read leniently (a malformed cell simply
219
+ * yields no capability claim — `undefined` from `lookupTagCapability`).
220
+ */
221
+ export function readCapabilityManifest(modelCardPath) {
222
+ if (!modelCardPath || !existsSync(modelCardPath))
223
+ return undefined;
224
+ let raw;
225
+ try {
226
+ raw = readFileSync(modelCardPath, "utf8");
227
+ }
228
+ catch {
229
+ return undefined;
230
+ }
231
+ let parsed;
232
+ try {
233
+ parsed = JSON.parse(raw);
234
+ }
235
+ catch {
236
+ return undefined;
237
+ }
238
+ if (typeof parsed !== "object" || parsed === null)
239
+ return undefined;
240
+ const capabilities = parsed.capabilities;
241
+ if (capabilities === undefined)
242
+ return undefined;
243
+ if (typeof capabilities !== "object" || capabilities === null || Array.isArray(capabilities)) {
244
+ throw new Error(`model-card.json at ${modelCardPath} has a malformed \`capabilities\` field — ` +
245
+ `expected an object, got ${JSON.stringify(capabilities)}.`);
246
+ }
247
+ return capabilities;
248
+ }
249
+ /**
250
+ * Resolve `capabilities[tier][system][tag]` to a `TagCapability`, returning `undefined` for any
251
+ * missing/malformed cell (a tag the model is NOT certified for — the loader treats that as legal:
252
+ * the model can't emit it, so a mask can't destroy it). Skips the `$comment` provenance key.
253
+ */
254
+ export function lookupTagCapability(manifest, tier, system, tag) {
255
+ const tierCell = manifest?.[tier];
256
+ if (!tierCell || typeof tierCell !== "object")
257
+ return undefined;
258
+ const systemCell = tierCell[system];
259
+ if (!systemCell || typeof systemCell !== "object")
260
+ return undefined;
261
+ const cap = systemCell[tag];
262
+ if (!cap || typeof cap !== "object" || typeof cap.maskOffF1 !== "number")
263
+ return undefined;
264
+ return cap;
265
+ }
110
266
  /**
111
267
  * Read learned CRF transition parameters from `crf-transitions.json`. Returns `undefined` when the
112
268
  * file is missing or malformed — callers fall back to the structural BIO mask only.
@@ -1 +1 @@
1
- {"version":3,"file":"weights.js","sourceRoot":"","sources":["../weights.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"weights.js","sourceRoot":"","sources":["../weights.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Per-word BIO tag-consistency repair (#727 + the fr.country / admin-token fragmentation class).
7
+ *
8
+ * The model emits per-PIECE BIO labels. On rows where an admin token is adjacent to a non-latin
9
+ * (byte-fallback) locality, carries diacritics, or is all-caps/reordered, the per-piece labels
10
+ * can DISAGREE within a single word — `VERMONT` → `VER`[B-locality] + `MONT`[B-region], `Lozère`
11
+ * → `Loz`[locality] + `ère`[B-region] — and the span decoder reads that as a tag-change
12
+ * mid-token, fracturing the admin component. The model already KNOWS the word's tag (99.7% of
13
+ * normal rows are unanimous); the defect is the lack of a word-level consistency constraint.
14
+ *
15
+ * Fix (DeepSeek-Pro consult `contested-frag`, 2026-06-19): a SentencePiece word — a `▁`-started
16
+ * piece + its non-`▁` continuations — must carry ONE tag. The tag is chosen by a
17
+ * CONFIDENCE-WEIGHTED vote, NOT first-piece-wins: sum each piece's softmax mass per TAG TYPE
18
+ * (B-X
19
+ *
20
+ * - I-X collapsed; `O` included) across the word, argmax the type, and force `B-<type>` then
21
+ * `I-<type>` (or all `O`). The near-certain `ère`→region then pulls the whole word to region,
22
+ * healing both the fragment and the `Loz` bleed. Operates ONLY within a `▁`-delimited word,
23
+ * so cross-word multi-token names ("Saint Paul" → two words) are untouched and the decoder's
24
+ * existing cross-word merge still joins them.
25
+ *
26
+ * Safety: a word whose pieces already agree is left byte-identical (no change). The vote includes
27
+ * `O`, so a genuinely-`O` word stays `O` (no spurious spans).
28
+ *
29
+ * Gate outcome (2026-06-19, fr-admin-split-gate + per-locale-f1, MAILWOMAN_WORD_CONSISTENCY=1): NOT
30
+ * a clean win, so this ships DEFAULT-OFF. It heals clean-latin fragments (`PRUNIÈRES LOZÈRE
31
+ * FRANCE` → region=`LOZÈRE`), but on OOD byte-soup rows where the per-piece confidence is itself
32
+ * unreliable the vote AMPLIFIES that noise — e.g. `VERMONT, ウェストミンスター` lets the region word
33
+ * absorb the adjacent CJK locality — and it net-regressed street −12.6 on the adversarial golden.
34
+ * The confidence-weighted vote out-votes a stray mis-tagged piece only when the surviving pieces
35
+ * are themselves trustworthy; on byte-fallback pieces that premise breaks. A confidence-gated
36
+ * variant (skip the heal when the word's mean p(bestType) is below a floor, or when any piece is
37
+ * a raw byte-fallback piece) is the path to a clean win — tracked on #727.
38
+ */
39
+ export interface WordConsistencyResult {
40
+ /** A new per-piece label-index array, word-consistent (input is not mutated). */
41
+ labelIndices: number[];
42
+ /** PieceIndex → mean p(chosen type) across the word, for pieces in a word that was HEALED. */
43
+ healedConfidence: Map<number, number>;
44
+ /** Count of words whose labels were rewritten (0 = byte-identical to the input). */
45
+ healedWords: number;
46
+ }
47
+ /**
48
+ * Rewrite per-piece label indices so every `▁`-delimited word carries one tag, chosen by a
49
+ * confidence-weighted vote over the post-prior `emissions`. See the module docstring.
50
+ *
51
+ * @param pieces SentencePiece pieces (the `▁`-marked surface is the word-boundary signal).
52
+ * @param emissions Per-piece × per-label scores AFTER all priors/masks (the distribution the argmax
53
+ * would see). Softmaxed per piece for the vote so each piece's confidence carries its weight.
54
+ * @param labels The BIO label vocabulary (index ↔ label).
55
+ * @param labelIndices The current per-piece decision (viterbi path or argmax). Not mutated.
56
+ */
57
+ export declare function enforceWordConsistency(pieces: ReadonlyArray<{
58
+ piece: string;
59
+ }>, emissions: ReadonlyArray<ReadonlyArray<number>>, labels: readonly string[], labelIndices: readonly number[]): WordConsistencyResult;
60
+ //# sourceMappingURL=word-consistency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"word-consistency.d.ts","sourceRoot":"","sources":["../word-consistency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAKH,MAAM,WAAW,qBAAqB;IACrC,iFAAiF;IACjF,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,8FAA8F;IAC9F,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,oFAAoF;IACpF,WAAW,EAAE,MAAM,CAAA;CACnB;AASD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACrC,MAAM,EAAE,aAAa,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EACxC,SAAS,EAAE,aAAa,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAC/C,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,YAAY,EAAE,SAAS,MAAM,EAAE,GAC7B,qBAAqB,CA+EvB"}