pi-mega-compact 0.20.42 → 0.20.43

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 (43) hide show
  1. package/assets/vector-cortex/encoder-v1/manifest.json +1 -1
  2. package/dist/config/vector-cortex-enc0a.js +32 -0
  3. package/dist/config/vector-cortex.js +2 -2
  4. package/dist/config.js +1 -1
  5. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
  6. package/dist/src/config/vector-cortex-enc0a.js +32 -0
  7. package/dist/src/config/vector-cortex.js +2 -2
  8. package/dist/src/config.js +1 -1
  9. package/dist/src/vector-cortex/_acceptance-enc0a-contract.js +60 -0
  10. package/dist/src/vector-cortex/_acceptance-vc2a-conformance.js +90 -0
  11. package/dist/src/vector-cortex/_acceptance-vc2a-runtime.js +285 -0
  12. package/dist/src/vector-cortex/_acceptance-vc2b-conformance.js +92 -0
  13. package/dist/src/vector-cortex/_acceptance-vc2b-heads.js +221 -0
  14. package/dist/src/vector-cortex/encoder/asset.js +3 -2
  15. package/dist/src/vector-cortex/encoder/decision.js +75 -0
  16. package/dist/src/vector-cortex/encoder/types-vc2c.js +41 -0
  17. package/dist/src/vector-cortex/encoder/types.js +13 -34
  18. package/dist/vector-cortex/_acceptance-enc0a-contract.js +60 -0
  19. package/dist/vector-cortex/_acceptance-vc2a-conformance.js +90 -0
  20. package/dist/vector-cortex/_acceptance-vc2a-runtime.js +285 -0
  21. package/dist/vector-cortex/_acceptance-vc2b-conformance.js +92 -0
  22. package/dist/vector-cortex/_acceptance-vc2b-heads.js +221 -0
  23. package/dist/vector-cortex/encoder/asset.js +3 -2
  24. package/dist/vector-cortex/encoder/decision.js +75 -0
  25. package/dist/vector-cortex/encoder/types-vc2c.js +41 -0
  26. package/dist/vector-cortex/encoder/types.js +13 -34
  27. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
  28. package/package.json +1 -1
  29. package/src/config/vector-cortex-enc0a.ts +34 -0
  30. package/src/config/vector-cortex.ts +2 -2
  31. package/src/config.ts +1 -0
  32. package/src/vector-cortex/_acceptance-enc0a-contract.ts +71 -0
  33. package/src/vector-cortex/_acceptance-vc2a-conformance.ts +119 -0
  34. package/src/vector-cortex/_acceptance-vc2a-runtime.ts +300 -0
  35. package/src/vector-cortex/_acceptance-vc2b-conformance.ts +121 -0
  36. package/src/vector-cortex/_acceptance-vc2b-heads.ts +234 -0
  37. package/src/vector-cortex/encoder/asset.ts +3 -2
  38. package/src/vector-cortex/encoder/bench-export.ts +2 -2
  39. package/src/vector-cortex/encoder/decision.ts +125 -0
  40. package/src/vector-cortex/encoder/runtime-native.ts +1 -1
  41. package/src/vector-cortex/encoder/runtime-wasm.ts +1 -1
  42. package/src/vector-cortex/encoder/types-vc2c.ts +124 -0
  43. package/src/vector-cortex/encoder/types.ts +18 -115
@@ -0,0 +1,234 @@
1
+ /** VC2B acceptance — multi-head invariant + independence + forced triad through
2
+ * the REAL encode-or-fallback router (no mocks, including unique failure
3
+ * injection where the learned asset is deleted after A selection but before
4
+ * inference). Extracted from vc2b-acceptance.test.ts so the aggregator stays
5
+ * under the soft line limit. Shared context (withFlagsOn, stageVerifyingAssetDir,
6
+ * token/order constants) is injected by the aggregator to avoid a circular import.
7
+ */
8
+ import { test, describe } from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { rmSync } from "node:fs";
11
+ import { join } from "node:path";
12
+
13
+ import { ENC_FAIL, ENCODER_HEAD_DIMS } from "./encoder/types.js";
14
+ import { encodeVectorSet, l2Norm } from "./encoder/heads.js";
15
+ import { ENCODER_TRIGRAM_WIDTH } from "./encoder/trigram.js";
16
+ import { embedLexical, ENCODER_LEXICAL_WIDTH } from "./encoder/lexical.js";
17
+ import { encodeOrFallback } from "./encoder/router.js";
18
+ import { createEncoderRuntime } from "./encoder/runtime.js";
19
+ import { createEncoderHeadsReporter } from "./encoder/emit-vc2b.js";
20
+
21
+ export interface HeadsCtx {
22
+ withFlagsOn: (fn: () => void) => void;
23
+ stageVerifyingAssetDir: (maxTokens?: number) => string;
24
+ SET_TOKENS: number[];
25
+ EMPTY_TOKENS: number[];
26
+ ORDERED_DIMS: number[];
27
+ }
28
+
29
+ export function registerHeads(ctx: HeadsCtx): void {
30
+ const { withFlagsOn, stageVerifyingAssetDir, SET_TOKENS, EMPTY_TOKENS, ORDERED_DIMS } = ctx;
31
+
32
+ // Suite 4 — invariant + unique failure injection + forced triad
33
+ describe("multi-head invariant + independence + triad", () => {
34
+ test("invariant: every emitted norm is 0 or within 1e-6 of 1", () => {
35
+ for (const tokens of [SET_TOKENS, EMPTY_TOKENS, [7], Array.from({ length: 300 }, (_, i) => i)]) {
36
+ const set = encodeVectorSet(tokens);
37
+ for (const hv of set.heads) {
38
+ const n = l2Norm(hv.values);
39
+ assert.equal(n === 0 || Math.abs(n - 1) <= 1e-6, true, `${hv.head} norm ${n}`);
40
+ }
41
+ }
42
+ });
43
+
44
+ test("repeat drift <= 1e-6 across repeated seeded exports (all five heads)", () => {
45
+ for (let rep = 0; rep < 3; rep++) {
46
+ const a = encodeVectorSet(SET_TOKENS);
47
+ const b = encodeVectorSet(SET_TOKENS);
48
+ for (let i = 0; i < a.heads.length; i++) {
49
+ for (let j = 0; j < a.heads[i]!.values.length; j++) {
50
+ assert.equal(Math.abs(a.heads[i]!.values[j]! - b.heads[i]!.values[j]!) <= 1e-6, true);
51
+ }
52
+ }
53
+ }
54
+ });
55
+
56
+ test("unique failure injection: delete model after A selection but before inference; router catches the real load() failure and selects independently initialized B", () => {
57
+ // This scenario is ON-dependent: it asserts a fallback emission, which is
58
+ // VC2B-flag-gated, and drives the VC2A runtime into an A load — so it self-pins
59
+ // both flags ON and is thus valid under either the default-ON run or the
60
+ // MEGACOMPACT_VC2B=0 parity run.
61
+ withFlagsOn(() => {
62
+ // Stage a learned asset the VC2A runtime would VERIFY into mode A, then
63
+ // REMOVE model.onnx before encoding. The router's load() returns the real
64
+ // ENC_ASSET_UNREADABLE failure code and must hand off to the independently
65
+ // initialized asset-free trigram B — emitting vector_cortex_encoder_fallback_selected
66
+ // from the production seam (S2: a true end-to-end router test, not a
67
+ // simulated direct call to selectTrigramBFallback).
68
+ const dir = stageVerifyingAssetDir();
69
+ const emitted: string[] = [];
70
+ const reporter = createEncoderHeadsReporter((e) => emitted.push(e));
71
+ try {
72
+ // A is selectable at this point (a staging runtime verifies it).
73
+ const probe = createEncoderRuntime();
74
+ assert.equal(probe.load(dir).ok, true, "staged asset verifies into A");
75
+ // "After A selection but before inference": the on-disk model is gone.
76
+ rmSync(join(dir, "model.onnx"), { force: true });
77
+ const verdict = encodeOrFallback({ tokens: SET_TOKENS }, dir, { reporter });
78
+ assert.equal(verdict.ok, true);
79
+ assert.equal(verdict.mode, "B", "router handoff selects independently initialized B");
80
+ assert.equal(verdict.width, 512);
81
+ if (verdict.ok) {
82
+ assert.equal(verdict.vector.length, 512);
83
+ assert.equal(verdict.code, ENC_FAIL.ASSET_UNREADABLE, "load() reported the real failure code");
84
+ }
85
+ assert.ok(
86
+ emitted.includes("vector_cortex_encoder_fallback_selected"),
87
+ "fallback-selected fired from the real router seam: " + emitted.join(","),
88
+ );
89
+ // Distinct vectors for distinct inputs — the fallback is not a constant.
90
+ const again = encodeOrFallback({ tokens: [9, 8, 7, 6] }, dir, { reporter });
91
+ assert.equal(again.ok, true);
92
+ if (verdict.ok && again.ok && verdict.mode === "B" && again.mode === "B") {
93
+ let diff = 0;
94
+ for (let i = 0; i < verdict.vector.length; i++) diff += Math.abs(verdict.vector[i]! - again.vector[i]!);
95
+ assert.ok(diff > 1e-3, "independent trigram B is input-sensitive");
96
+ }
97
+ } finally {
98
+ rmSync(dir, { recursive: true, force: true });
99
+ }
100
+ });
101
+ });
102
+
103
+ test("forced triad A / B / C through the encode-or-fallback router", () => {
104
+ // ON-dependent: asserts heads_emitted / fallback_selected emissions, so it
105
+ // self-pins both flags ON (valid under either the default-ON run or the
106
+ // MEGACOMPACT_VC2B=0 parity run).
107
+ withFlagsOn(() => {
108
+ // A = learned projections: a verifying asset dir routes to a VectorSetV1 with
109
+ // the five heads in ordered dims (emitting heads_emitted).
110
+ const dirA = stageVerifyingAssetDir();
111
+ const emittedA: string[] = [];
112
+ try {
113
+ const reporterA = createEncoderHeadsReporter((e) => emittedA.push(e));
114
+ const a = encodeOrFallback({ tokens: SET_TOKENS }, dirA, { reporter: reporterA });
115
+ assert.equal(a.ok, true);
116
+ assert.equal(a.mode, "A");
117
+ if (a.ok) {
118
+ assert.equal(a.vectorSet.heads.length, 5);
119
+ assert.deepEqual(a.vectorSet.heads.map((h) => h.dim), ORDERED_DIMS);
120
+ }
121
+ assert.ok(emittedA.includes("vector_cortex_encoder_heads_emitted"));
122
+ } finally {
123
+ rmSync(dirA, { recursive: true, force: true });
124
+ }
125
+ // B = 512d trigram selected when the learned asset directory is REMOVED: the
126
+ // router's load() fails (no manual fetch) and hands off to B. Remove a staged
127
+ // asset dir so the directory is genuinely absent, proving B needs no asset.
128
+ const dirB = stageVerifyingAssetDir();
129
+ rmSync(dirB, { recursive: true, force: true }); // asset directory REMOVED
130
+ const emittedB: string[] = [];
131
+ const reporterB = createEncoderHeadsReporter((e) => emittedB.push(e));
132
+ const b = encodeOrFallback({ tokens: SET_TOKENS }, dirB, { reporter: reporterB });
133
+ assert.equal(b.ok, true);
134
+ assert.equal(b.mode, "B", "B works without an asset dir");
135
+ assert.equal(b.width, 512);
136
+ if (b.ok) {
137
+ assert.equal(b.vector.length, 512);
138
+ assert.equal(b.limitation, null);
139
+ }
140
+ assert.ok(emittedB.includes("vector_cortex_encoder_fallback_selected"), "B selection emits fallback-selected");
141
+ // C = token/phrase lexical forced when both A and B runtimes are disabled.
142
+ const emittedC: string[] = [];
143
+ const reporterC = createEncoderHeadsReporter((e) => emittedC.push(e));
144
+ const c = encodeOrFallback({ tokens: SET_TOKENS }, dirB, { reporter: reporterC, forceFallback: "C" });
145
+ assert.equal(c.ok, true);
146
+ assert.equal(c.mode, "C");
147
+ assert.equal(c.width, ENCODER_LEXICAL_WIDTH);
148
+ if (c.ok && c.mode === "C") {
149
+ assert.equal(c.vector.length, ENCODER_LEXICAL_WIDTH);
150
+ assert.ok((c.limitation ?? "").length > 0, "C reports its semantic-context limitation");
151
+ // Q04: a FORCED C is an intentional selection, not a demotion or rollback,
152
+ // so it must NOT be stamped with ENC_FAIL.ROLLBACK.
153
+ assert.equal(c.code, null, "forced C is not a rollback/demotion (code = null)");
154
+ }
155
+ assert.ok(emittedC.includes("vector_cortex_encoder_fallback_selected"), "C selection emits fallback-selected");
156
+ // Widths are disjoint across the triad (no shared feature space).
157
+ const aWidths = Object.values(ENCODER_HEAD_DIMS);
158
+ for (const w of [...aWidths, ENCODER_LEXICAL_WIDTH]) assert.notEqual(w, ENCODER_TRIGRAM_WIDTH);
159
+ });
160
+ });
161
+
162
+ test("forced fallback (B/C) wins over the empty-input degenerate case (Q02)", () => {
163
+ // Q02: a caller that explicitly forces a fallback mode must get that mode
164
+ // even for EMPTY input — empty tokens must NOT silently short-circuit to B.
165
+ // Asserts fallback_selected emissions → ON-dependent, so it self-pins via
166
+ // withFlagsOn (valid under either external env, same as the forced-triad test).
167
+ withFlagsOn(() => {
168
+ const emittedC: string[] = [];
169
+ const reporterC = createEncoderHeadsReporter((e) => emittedC.push(e));
170
+ const c = encodeOrFallback({ tokens: EMPTY_TOKENS }, "", { reporter: reporterC, forceFallback: "C" });
171
+ assert.equal(c.ok, true);
172
+ assert.equal(c.mode, "C", "forced C must win over empty-input B selection");
173
+ if (c.ok && c.mode === "C") {
174
+ assert.equal(c.vector.length, ENCODER_LEXICAL_WIDTH);
175
+ assert.equal(c.code, null, "forced C carries no rollback/demotion code (Q04)");
176
+ }
177
+ assert.ok(emittedC.includes("vector_cortex_encoder_fallback_selected"));
178
+ // Forced B on empty input also stays B (force mode is honored, no failure
179
+ // code — a forced mode is not a rollback/demotion, so code is null).
180
+ const emittedB: string[] = [];
181
+ const reporterB = createEncoderHeadsReporter((e) => emittedB.push(e));
182
+ const b = encodeOrFallback({ tokens: EMPTY_TOKENS }, "", { reporter: reporterB, forceFallback: "B" });
183
+ assert.equal(b.ok, true);
184
+ assert.equal(b.mode, "B");
185
+ if (b.ok && b.mode === "B") {
186
+ assert.equal(b.vector.length, ENCODER_TRIGRAM_WIDTH);
187
+ assert.equal(b.code, null, "forced B carries no rollback/demotion code (Q04)");
188
+ }
189
+ });
190
+ });
191
+
192
+ test("A/B/C use disjoint widths and independent algorithms", () => {
193
+ // A head widths are 384/128/128/64/32; B is 512; C is 256 — no shared space.
194
+ const aWidths = Object.values(ENCODER_HEAD_DIMS);
195
+ const bWidth = ENCODER_TRIGRAM_WIDTH;
196
+ const cWidth = ENCODER_LEXICAL_WIDTH;
197
+ for (const w of [...aWidths, cWidth]) assert.notEqual(w, bWidth);
198
+ // B works with the asset absent; C works with both vector runtimes disabled
199
+ // (C does not depend on B or A — it embeds tokens directly).
200
+ const cTokens = embedLexical("independently computed lexical with vector runtimes disabled");
201
+ assert.equal(cTokens.length, ENCODER_LEXICAL_WIDTH);
202
+ });
203
+
204
+ test("router seam enforces the verified per-manifest token capacity: over-cap input routes to B with ENC_SHAPE_INVALID, never an over-cap A VectorSet", () => {
205
+ // Q01: the router's mode-A path must enforce the VC2A contract
206
+ // "only batch1/<=maxTokens verified assets reach inference" at its own seam.
207
+ // A verified asset declaring maxTokens=64 with an input of 100 tokens must
208
+ // NOT produce an ok:true mode-A VectorSetV1 whose inputTokens breach the
209
+ // model's declared capacity — instead the router rejects it and falls back
210
+ // to the asset-free trigram B, reporting the real shape failure code.
211
+ withFlagsOn(() => {
212
+ const dir = stageVerifyingAssetDir(64); // verified low-cap manifest
213
+ try {
214
+ const over = encodeOrFallback({ tokens: Array.from({ length: 100 }, (_, i) => i) }, dir);
215
+ assert.equal(over.ok, true, "over-cap input still yields a usable (fallback) verdict");
216
+ assert.equal(over.mode, "B", "over-cap input must route to the B fallback, not an A VectorSet");
217
+ if (over.ok) {
218
+ assert.equal(over.code, ENC_FAIL.SHAPE_INVALID, "reported the real shape failure code");
219
+ assert.equal(over.vector.length, ENCODER_TRIGRAM_WIDTH);
220
+ }
221
+ // A within-cap input against the SAME verified manifest still reaches a
222
+ // qualified mode-A VectorSet — the capacity rejection is input-scoped,
223
+ // not a blanket demotion of the verified asset.
224
+ const within = encodeOrFallback({ tokens: SET_TOKENS }, dir);
225
+ assert.equal(within.ok, true);
226
+ assert.equal(within.mode, "A", "within-cap input still reaches mode A");
227
+ if (within.ok) assert.equal(within.vectorSet.heads.length, 5);
228
+ } finally {
229
+ rmSync(dir, { recursive: true, force: true });
230
+ }
231
+ });
232
+ });
233
+ });
234
+ }
@@ -2,7 +2,8 @@
2
2
  * vector-cortex/encoder/asset.ts — VC2A asset verification (task 2).
3
3
  *
4
4
  * Verifies a ModelManifestV1 before any allocation: SHA-256 the ONNX and
5
- * tokenizer against the manifest digests, require opset 17, batch exactly 1 and
5
+ * tokenizer against the manifest digests, require opset 21 (ENC-0a re-baseline),
6
+ * batch exactly 1 and
6
7
  * maximum 512 tokens, and confirm the current platform is in the supported
7
8
  * matrix. On ANY of these the caller demotes to mode B (asset-free trigram) —
8
9
  * never a remote fetch (PREVENT-PI-004). A truncated/unreadable asset during
@@ -88,7 +89,7 @@ function isManifest(m: unknown): m is ModelManifestV1 {
88
89
  *
89
90
  * - manifest parse/shape failure -> ENC_MANIFEST_INVALID -> mode B
90
91
  * - unsupported platform -> ENC_PLATFORM_UNSUPPORTED -> mode B
91
- * - opset != 17 -> ENC_OPSET_INVALID -> mode B
92
+ * - opset != 21 -> ENC_OPSET_INVALID -> mode B
92
93
  * - batch != 1 -> ENC_BATCH_INVALID -> mode B
93
94
  * - maxTokens > 512 -> ENC_TOKENS_EXCEEDED -> mode B
94
95
  * - on-disk digest != manifest digest -> ENC_DIGEST_MISMATCH (one-byte mutation)
@@ -17,7 +17,7 @@ export interface BenchGatesV1 {
17
17
  readonly latency: boolean;
18
18
  /** steady-state marginal RSS over the process baseline <= 150 MiB. */
19
19
  readonly rss: boolean;
20
- /** the loaded model's declared opset_import equals 17. */
20
+ /** the loaded model's declared opset_import equals ENCODER_OPSET (21, ENC-0a re-baseline). */
21
21
  readonly opset: boolean;
22
22
  /** SHA-256 of the embedding output identical across 3 runs (maxAbsDelta=0). */
23
23
  readonly determinism: boolean;
@@ -53,7 +53,7 @@ export interface BenchResultV1 {
53
53
  readonly rssBaselineMib: number | null;
54
54
  /** rssMib - rssBaselineMib: the encoder's marginal footprint. */
55
55
  readonly rssMarginalMib: number | null;
56
- /** declared opset_import (17); null when no asset manifest is readable. */
56
+ /** declared opset_import (21, ENC-0a re-baseline); null when no asset manifest is readable. */
57
57
  readonly opset: number | null;
58
58
  /** true when the output SHA-256 is identical across 3 runs. */
59
59
  readonly deterministic: boolean;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * vector-cortex/encoder/decision.ts — ENC-0a backend-decision contract.
3
+ *
4
+ * The durable EncoderBackendDecisionV1 record: which ONNX runtime backend the
5
+ * real learned encoder ships on (transformers.js/WASM vs onnxruntime-node
6
+ * native), whether the 80 MiB install budget holds, the per-platform install
7
+ * matrix, the opset baseline (locked 21 by ENC-0a), the license verdict, and
8
+ * the pinned model/tokenizer sha256 digests.
9
+ *
10
+ * ENC-0a is the DECISION + MEASUREMENT sprint: this contract is what the
11
+ * deterministic resolver (`scripts/encoder/resolve-backend-decision.mjs`) and
12
+ * the durable record (`docs/vector-cortex/encoder-backend-decision.md`) both
13
+ * consume. ENC-0a also owns the opset flip: ENCODER_OPSET is re-baselined to 21
14
+ * in `types.ts` alongside the placeholder manifest (the 2026-08-05 trunk
15
+ * research dropped the earlier Xenova opset-17 requirement). ENC-0b asserts the
16
+ * staged real asset is opset 21 — no further constant change.
17
+ *
18
+ * Contract-first (ENGINEERING_PRACTICES §3). Pi-agnostic, dependency-free
19
+ * (PREVENT-PI-004 — local computation only, never a fetch). No `any`
20
+ * (PREVENT-011).
21
+ */
22
+
23
+ import type { EncoderPlatform } from "./types.js";
24
+
25
+ /** Backend demotion disposition for a platform row. */
26
+ export type BackendDemotion = "none" | "wasm" | "modeB";
27
+
28
+ /** One platform row: the concrete runtime + install size + demotion disposition. */
29
+ export interface EncoderPlatformRow {
30
+ readonly runtime: string;
31
+ readonly installMiB: number;
32
+ readonly demotion: BackendDemotion;
33
+ }
34
+
35
+ /**
36
+ * EncoderBackendDecisionV1 — the locked learned-encoder runtime-backend decision.
37
+ *
38
+ * `opset` is pinned to the literal 21 (the ENC-0a re-baseline); `artifacts`
39
+ * carries only aggregate digests/sizes — never message content (EVAL-REDACT-002);
40
+ * `blockedBy` names the open hard-gate items that DEFER part of the decision
41
+ * (e.g. HG-4 — the darwin-x64 demotion is ENC-0e's job).
42
+ */
43
+ export interface EncoderBackendDecisionV1 {
44
+ readonly schema: "encoder-backend-decision-v1";
45
+ readonly backend: "wasm" | "native";
46
+ readonly budgetOk: boolean;
47
+ readonly opset: 21;
48
+ readonly platformMatrix: Readonly<Record<EncoderPlatform, EncoderPlatformRow>>;
49
+ readonly license: { readonly spdx: "MIT"; readonly redistribution: true };
50
+ readonly artifacts: {
51
+ readonly model: { readonly path: string; readonly bytes: number; readonly sha256: string };
52
+ readonly tokenizer: { readonly path: string; readonly bytes: number; readonly sha256: string };
53
+ };
54
+ readonly p95Ms: number | null;
55
+ readonly blockedBy: readonly string[];
56
+ }
57
+
58
+ /**
59
+ * The normative ENC-0a budget: the 80 MiB install/asset cap (MODEL_ASSET.md
60
+ * §Qualification). Backend qualifies (budgetOk) iff the shipped byte-count fits.
61
+ */
62
+ export const ENCODER_INSTALL_BUDGET_MIB = 80;
63
+
64
+ /** The p95 latency gate at 512 tokens / 4 threads on linux-x64 (ms). */
65
+ export const ENCODER_DECISION_P95_MS = 40;
66
+
67
+ /**
68
+ * buildDecision — assemble a valid, platform-complete EncoderBackendDecisionV1.
69
+ *
70
+ * Pure helper consumed by the acceptance aggregator (and any TS consumer of the
71
+ * decision). Every EncoderPlatform must resolve to a row, and the passed
72
+ * per-platform rows must be complete (no row omitted) — a partial matrix is a
73
+ * contract violation, not a valid decision. All fields are passed in; this is a
74
+ * structural constructor, not a rules engine (the resolver owns the decision
75
+ * rule).
76
+ */
77
+ export function buildDecision(input: {
78
+ readonly backend: "wasm" | "native";
79
+ readonly budgetOk: boolean;
80
+ readonly p95Ms: number | null;
81
+ readonly platformMatrix: Readonly<Record<EncoderPlatform, EncoderPlatformRow>>;
82
+ readonly modelPath: string;
83
+ readonly modelBytes: number;
84
+ readonly modelSha256: string;
85
+ readonly tokenizerPath: string;
86
+ readonly tokenizerBytes: number;
87
+ readonly tokenizerSha256: string;
88
+ readonly blockedBy: readonly string[];
89
+ }): EncoderBackendDecisionV1 {
90
+ const { platformMatrix } = input;
91
+ const platforms: readonly EncoderPlatform[] = [
92
+ "linux-x64",
93
+ "linux-arm64",
94
+ "darwin-x64",
95
+ "darwin-arm64",
96
+ "win32-x64",
97
+ ];
98
+ for (const p of platforms) {
99
+ if (!Object.prototype.hasOwnProperty.call(platformMatrix, p)) {
100
+ throw new Error(`platform matrix is incomplete: missing row for ${p}`);
101
+ }
102
+ }
103
+ return {
104
+ schema: "encoder-backend-decision-v1",
105
+ backend: input.backend,
106
+ budgetOk: input.budgetOk,
107
+ opset: 21,
108
+ platformMatrix,
109
+ license: { spdx: "MIT", redistribution: true },
110
+ artifacts: {
111
+ model: {
112
+ path: input.modelPath,
113
+ bytes: input.modelBytes,
114
+ sha256: input.modelSha256,
115
+ },
116
+ tokenizer: {
117
+ path: input.tokenizerPath,
118
+ bytes: input.tokenizerBytes,
119
+ sha256: input.tokenizerSha256,
120
+ },
121
+ },
122
+ p95Ms: input.p95Ms,
123
+ blockedBy: input.blockedBy,
124
+ };
125
+ }
@@ -45,7 +45,7 @@ export interface OrtNativeModule {
45
45
 
46
46
  /** The backend's inference session — a thin wrapper over the real native session. */
47
47
  export interface NativeSession {
48
- /** The declared ONNX opset in the loaded manifest (normative 17). */
48
+ /** The declared ONNX opset in the loaded manifest (normative 21). */
49
49
  readonly opset: number;
50
50
  /** The semantic embedding width (normative 384). */
51
51
  readonly semanticWidth: number;
@@ -45,7 +45,7 @@ export interface OrtWasmModule {
45
45
 
46
46
  /** The backend's inference session — a thin wrapper over the real WASM session. */
47
47
  export interface WasmSession {
48
- /** The declared ONNX opset in the loaded manifest (normative 17). */
48
+ /** The declared ONNX opset in the loaded manifest (normative 21). */
49
49
  readonly opset: number;
50
50
  /** The semantic embedding width (normative 384). */
51
51
  readonly semanticWidth: number;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * vector-cortex/encoder/types-vc2c.ts — VC2C qualification + calibration
3
+ * contracts (QualifiedEncoderV1 / CalibrationV1 / EVALUATION_THRESHOLDS).
4
+ *
5
+ * Extracted from types.ts (delegate-shell split) when types.ts crossed the
6
+ * 300-line soft limit. types.ts re-exports everything here; consumers import
7
+ * from types.ts unchanged.
8
+ *
9
+ * Pi-agnostic and dependency-free (PREVENT-PI-004). No `any` (PREVENT-011).
10
+ */
11
+
12
+ import type { EncoderHeadName } from "./types.js";
13
+
14
+ /**
15
+ * Held-out metrics recorded as qualification evidence for a candidate asset
16
+ * (EVALUATION.md §metrics). Semantic metrics are Spearman rho + recall@10;
17
+ * dependency directed precision/recall; contradiction precision/recall/ECE;
18
+ * cache precision/recall; payload-routing macro-F1 + exact/anchor recall;
19
+ * reconstruction is the binary causality/exact/closure/task-success set. The
20
+ * qualification decision (task 3 `select.ts`) consumes ONLY the per-head rows
21
+ * this record carries; the true EVALUATION thresholds are the normative
22
+ * constants in MODEL_ASSET (mirrored in `EVALUATION_THRESHOLDS` below).
23
+ */
24
+ export interface EncoderHeldOutMetrics {
25
+ /** Semantic: Spearman rho (>= .75) and recall@10 (>= .90). */
26
+ readonly semantic: { readonly spearman: number; readonly recallAt10: number };
27
+ /** Dependency: directed precision (>= .97) and recall (>= .95). */
28
+ readonly dependency: { readonly precision: number; readonly recall: number };
29
+ /** Contradiction: precision (>= .98), recall (>= .90), ECE (<= .05). */
30
+ readonly contradiction: { readonly precision: number; readonly recall: number; readonly ece: number };
31
+ /** Cache: precision (>= .999, zero false-stable) and recall (>= .90). */
32
+ readonly cacheStability: { readonly precision: number; readonly recall: number };
33
+ /** Payload routing: macro-F1 (>= .97) and exact/anchor recall (1.0). */
34
+ readonly payloadRouting: { readonly macroF1: number; readonly exactAnchorRecall: number };
35
+ /** Reconstruction: binary gates — zero causal/tool/anchor/exact violations. */
36
+ readonly reconstruction: {
37
+ readonly votesOk: boolean;
38
+ readonly dependencyClosureRecall: number;
39
+ readonly taskSuccessNonInferior: boolean;
40
+ };
41
+ }
42
+
43
+ /** Normative per-head + asset qualification thresholds (MODEL_ASSET §qualification
44
+ * + EVALUATION.md §metrics). These are the constants `select.ts` evaluates the
45
+ * candidate's held-out metrics against (task 3 atomic check). */
46
+ export const EVALUATION_THRESHOLDS = {
47
+ semantic: { spearman: 0.75, recallAt10: 0.9 },
48
+ dependency: { precision: 0.97, recall: 0.95 },
49
+ contradiction: { precision: 0.98, recall: 0.9, ece: 0.05 },
50
+ cacheStability: { precision: 0.999, recall: 0.9 },
51
+ payloadRouting: { macroF1: 0.97, exactAnchorRecall: 1.0 },
52
+ reconstruction: { dependencyClosureRecall: 1.0 },
53
+ asset: { maxTokens: 512, maxLatencyP95Ms: 40, maxRssDeltaMib: 150 },
54
+ } as const;
55
+
56
+ /**
57
+ * CalibrationV1 — fitted temperature/isotonic calibration, frozen on the
58
+ * CALIBRATION split only (VC2C task 2). Held-out test labels NEVER enter the
59
+ * fit inputs (calibration-fit prohibition). The split digest proves the exact
60
+ * calibration assignment (grouped by repository+session); the frozen temp/threshold
61
+ * values are what `select.ts` stamps into a `QualifiedEncoderV1`.
62
+ */
63
+ export interface CalibrationV1 {
64
+ readonly schema: "calibration-v1";
65
+ readonly headOrder: readonly EncoderHeadName[];
66
+ /** SHA-256 of the calibration split assignment (grouped repository+session). */
67
+ readonly calibrationSplitDigest: string;
68
+ /** Fitted on the calibration split only; held-out labels excluded from fit. */
69
+ readonly fittedOnCalibrationOnly: true;
70
+ /** Frozen per-head temperature (isotonic calibration reference points). */
71
+ readonly temperatures: Readonly<Record<EncoderHeadName, number>>;
72
+ /** Frozen per-head decision thresholds for the qualified decision. */
73
+ readonly thresholds: Readonly<Record<EncoderHeadName, number>>;
74
+ /** Seed of the deterministic calibration fit. */
75
+ readonly seed: number;
76
+ }
77
+
78
+ /**
79
+ * QualifiedEncoderV1 — the VC2C-owned eligibility record (mode A). Produced by
80
+ * `select.ts` ONLY when EVERY MODEL_ASSET + per-head EVALUATION threshold
81
+ * passes (atomic — one failed field demotes all of A). Pins the asset digest,
82
+ * the calibration digest, the held-out metrics that justified eligibility, and
83
+ * the calibration reference, so VC3A receives a fully self-describing candidate.
84
+ */
85
+ export interface QualifiedEncoderV1 {
86
+ readonly schema: "qualified-encoder-v1";
87
+ readonly modelVersion: string;
88
+ readonly mode: "A";
89
+ /** SHA-256 of the asset manifest bytes (ModelManifestV1) that qualified.
90
+ * Identical semantics to the dashboard health card's `encoderAssetDigest`
91
+ * (both hash the committed manifest.json ModelManifestV1 bytes), so
92
+ * downstream consumers (VC3A) pin the same digest across the seam. */
93
+ readonly assetDigest: string;
94
+ /** SHA-256 of the calibration split assignment (grouped repository+session)
95
+ * that the CalibrationV1 was fitted on — the calibration's core identity. */
96
+ readonly calibrationDigest: string;
97
+ /** SHA-256 of the qualified asset's verified ONNX bytes (digest-pinned). */
98
+ readonly onnxDigest: string;
99
+ /** Held-out metrics recorded as the eligibility evidence. */
100
+ readonly heldOut: EncoderHeldOutMetrics;
101
+ /** Calibration reference this qualification is grounded in. */
102
+ readonly calibration: CalibrationV1;
103
+ }
104
+
105
+ /** VC2C-specific qualification failure codes (returned, never thrown). */
106
+ export const ENC_QUALIFICATION_FAIL = {
107
+ /** The qualification manifest hash does not match the calibration that was fit
108
+ * (corrupt qualification manifest after calibration before selection). */
109
+ DIGEST_MISMATCH: "ENC_QUALIFICATION_DIGEST_MISMATCH",
110
+ /** One or more per-head EVALUATION thresholds failed (demotes all of A). */
111
+ THRESHOLD_FAILED: "ENC_QUALIFICATION_THRESHOLD_FAILED",
112
+ /** An asset-field qualification check (asset/latency/RSS) failed. */
113
+ ASSET_FAILED: "ENC_QUALIFICATION_ASSET_FAILED",
114
+ /** Calibration was attempted using held-out labels (fit prohibition). */
115
+ HELD_OUT_IN_FIT: "ENC_QUALIFICATION_HELD_OUT_IN_FIT",
116
+ } as const;
117
+
118
+ /** The 4 registered VC2C conformance IDs (task 1: "register ENC-017..020"). */
119
+ export const ENC2C_IDS: readonly string[] = [
120
+ "ENC-017",
121
+ "ENC-018",
122
+ "ENC-019",
123
+ "ENC-020",
124
+ ];