pi-mega-compact 0.20.42 → 0.20.44

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 (63) hide show
  1. package/assets/vector-cortex/encoder-v1/manifest.json +1 -1
  2. package/assets/vector-cortex/encoder-v1/model-card.json +1 -1
  3. package/assets/vector-cortex/encoder-v1/model.onnx +0 -0
  4. package/assets/vector-cortex/encoder-v1/tokenizer.json +1 -1
  5. package/dist/config/vector-cortex-enc0a.js +32 -0
  6. package/dist/config/vector-cortex-enc0b.js +34 -0
  7. package/dist/config/vector-cortex.js +3 -3
  8. package/dist/config.js +1 -1
  9. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +2 -0
  10. package/dist/src/config/vector-cortex-enc0a.js +32 -0
  11. package/dist/src/config/vector-cortex-enc0b.js +34 -0
  12. package/dist/src/config/vector-cortex.js +3 -3
  13. package/dist/src/config.js +1 -1
  14. package/dist/src/vector-cortex/_acceptance-enc0a-contract.js +60 -0
  15. package/dist/src/vector-cortex/_acceptance-vc2a-conformance.js +90 -0
  16. package/dist/src/vector-cortex/_acceptance-vc2a-runtime.js +285 -0
  17. package/dist/src/vector-cortex/_acceptance-vc2b-conformance.js +92 -0
  18. package/dist/src/vector-cortex/_acceptance-vc2b-heads.js +221 -0
  19. package/dist/src/vector-cortex/encoder/asset.js +3 -2
  20. package/dist/src/vector-cortex/encoder/decision.js +75 -0
  21. package/dist/src/vector-cortex/encoder/emit.js +2 -0
  22. package/dist/src/vector-cortex/encoder/encoder-onnx-dispatch.js +58 -0
  23. package/dist/src/vector-cortex/encoder/onnx.js +147 -0
  24. package/dist/src/vector-cortex/encoder/runtime-wasm.js +17 -4
  25. package/dist/src/vector-cortex/encoder/runtime.js +17 -7
  26. package/dist/src/vector-cortex/encoder/types-vc2c.js +50 -0
  27. package/dist/src/vector-cortex/encoder/types.js +13 -34
  28. package/dist/vector-cortex/_acceptance-enc0a-contract.js +60 -0
  29. package/dist/vector-cortex/_acceptance-vc2a-conformance.js +90 -0
  30. package/dist/vector-cortex/_acceptance-vc2a-runtime.js +285 -0
  31. package/dist/vector-cortex/_acceptance-vc2b-conformance.js +92 -0
  32. package/dist/vector-cortex/_acceptance-vc2b-heads.js +221 -0
  33. package/dist/vector-cortex/encoder/asset.js +3 -2
  34. package/dist/vector-cortex/encoder/decision.js +75 -0
  35. package/dist/vector-cortex/encoder/emit.js +2 -0
  36. package/dist/vector-cortex/encoder/encoder-onnx-dispatch.js +58 -0
  37. package/dist/vector-cortex/encoder/onnx.js +147 -0
  38. package/dist/vector-cortex/encoder/runtime-wasm.js +17 -4
  39. package/dist/vector-cortex/encoder/runtime.js +17 -7
  40. package/dist/vector-cortex/encoder/types-vc2c.js +50 -0
  41. package/dist/vector-cortex/encoder/types.js +13 -34
  42. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +12 -0
  43. package/package.json +3 -2
  44. package/src/config/vector-cortex-enc0a.ts +34 -0
  45. package/src/config/vector-cortex-enc0b.ts +36 -0
  46. package/src/config/vector-cortex.ts +3 -3
  47. package/src/config.ts +2 -0
  48. package/src/vector-cortex/_acceptance-enc0a-contract.ts +71 -0
  49. package/src/vector-cortex/_acceptance-vc2a-conformance.ts +119 -0
  50. package/src/vector-cortex/_acceptance-vc2a-runtime.ts +300 -0
  51. package/src/vector-cortex/_acceptance-vc2b-conformance.ts +121 -0
  52. package/src/vector-cortex/_acceptance-vc2b-heads.ts +234 -0
  53. package/src/vector-cortex/encoder/asset.ts +3 -2
  54. package/src/vector-cortex/encoder/bench-export.ts +2 -2
  55. package/src/vector-cortex/encoder/decision.ts +125 -0
  56. package/src/vector-cortex/encoder/emit.ts +5 -1
  57. package/src/vector-cortex/encoder/encoder-onnx-dispatch.ts +76 -0
  58. package/src/vector-cortex/encoder/onnx.ts +198 -0
  59. package/src/vector-cortex/encoder/runtime-native.ts +1 -1
  60. package/src/vector-cortex/encoder/runtime-wasm.ts +21 -8
  61. package/src/vector-cortex/encoder/runtime.ts +27 -9
  62. package/src/vector-cortex/encoder/types-vc2c.ts +134 -0
  63. package/src/vector-cortex/encoder/types.ts +19 -115
@@ -0,0 +1,198 @@
1
+ /**
2
+ * vector-cortex/encoder/onnx.ts — ENC-0b real ONNX InferenceSession builder.
3
+ *
4
+ * Creates a WASM-backed ONNX InferenceSession over the committed encoder-v1
5
+ * asset (bge-small-en-v1.5, opset 21, 384-dim sentence_embedding). Dynamically
6
+ * imports onnxruntime-web/wasm (the CPU-only variant) so the module graph
7
+ * compiles on hosts without the package. All failures return typed result
8
+ * codes — this function NEVER throws (PREVENT-011: no `any`).
9
+ *
10
+ * Lifecycle:
11
+ * 1. Check ENCODER_RSS_BUDGET_BYTES before allocation (cap-before-allocation).
12
+ * 2. Assert manifest.opset === ENCODER_OPSET (21).
13
+ * 3. Dynamically import("onnxruntime-web/wasm").
14
+ * 4. Create InferenceSession with wasm EP, 4 threads.
15
+ * 5. Expose infer(tokens) that feeds int64 input_ids + attention_mask + token_type_ids.
16
+ * 6. Return sentence_embedding, L2-normalized to unit norm.
17
+ *
18
+ * Pi-agnostic (PREVENT-PI-004: local file only, zero network).
19
+ */
20
+
21
+ import { dirname, join } from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ import { existsSync } from "node:fs";
24
+ import {
25
+ ENCODER_OPSET,
26
+ ENCODER_RSS_BUDGET_BYTES,
27
+ ENCODER_SEMANTIC_WIDTH,
28
+ type ModelManifestV1,
29
+ } from "./types.js";
30
+ import type { EncoderReporter } from "./emit.js";
31
+
32
+ /** ENC-0b failure codes (new ENC_FAIL namespace entries declared here). */
33
+ export const ENC_ONNX_FAIL = {
34
+ OPSET_MISMATCH: "ENC_ONNX_OPSET_MISMATCH",
35
+ RSS_BREACH: "ENC_ONNX_RSS_BREACH",
36
+ MODULE_ABSENT: "ENC_ONNX_MODULE_ABSENT",
37
+ SESSION_ERROR: "ENC_ONNX_SESSION_ERROR",
38
+ MODEL_ABSENT: "ENC_ONNX_MODEL_ABSENT",
39
+ } as const;
40
+
41
+ /** Typed failure code union. */
42
+ export type OnnxFailCode = (typeof ENC_ONNX_FAIL)[keyof typeof ENC_ONNX_FAIL];
43
+
44
+ /** Result of building an ONNX session — never throws. */
45
+ export type OnnxSessionResult =
46
+ | { ok: true; session: OnnxInferenceSession }
47
+ | { ok: false; code: OnnxFailCode };
48
+
49
+ /** The runnable ONNX inference surface (thin wrapper over the real session). */
50
+ export interface OnnxInferenceSession {
51
+ readonly opset: number;
52
+ readonly semanticWidth: number;
53
+ /** Run inference over token IDs, returning L2-normalized sentence_embedding. */
54
+ infer(tokens: readonly number[]): Promise<Float32Array>;
55
+ /** Release the underlying session resources. */
56
+ release(): Promise<void>;
57
+ }
58
+
59
+ /** Shadow type for onnxruntime-web/wasm InferenceSession (avoids hard dep). */
60
+ interface OrtSession {
61
+ run(
62
+ feeds: Record<string, { data: BigInt64Array | Float32Array; dims: readonly number[]; type: string }>,
63
+ fetches: readonly string[],
64
+ ): Promise<Record<string, { data: Float32Array | BigInt64Array; dims: readonly number[] }>>;
65
+ release(): Promise<void>;
66
+ }
67
+ interface OrtWasmModule {
68
+ InferenceSession: {
69
+ create(
70
+ path: string,
71
+ opts: { executionProviders: readonly string[]; intraOpNumThreads: number },
72
+ ): Promise<OrtSession>;
73
+ };
74
+ }
75
+
76
+ /** Resolve the onnxruntime-web package root from import.meta.url. */
77
+ function resolveOrtWasmPath(): string | null {
78
+ try {
79
+ const here = dirname(fileURLToPath(import.meta.url));
80
+ // Walk up to find node_modules/onnxruntime-web/dist/ort-wasm-simd-threaded.wasm
81
+ let dir = here;
82
+ for (let i = 0; i < 12; i++) {
83
+ const candidate = join(dir, "node_modules", "onnxruntime-web", "dist", "ort-wasm-simd-threaded.wasm");
84
+ if (existsSync(candidate)) return candidate;
85
+ const next = dirname(dir);
86
+ if (next === dir) break;
87
+ dir = next;
88
+ }
89
+ return null;
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+
95
+ /** L2-normalize a Float32Array in-place; returns the same array. */
96
+ function l2Normalize(arr: Float32Array): Float32Array {
97
+ let sum = 0;
98
+ for (let i = 0; i < arr.length; i++) sum += arr[i]! * arr[i]!;
99
+ const norm = Math.sqrt(sum);
100
+ if (norm > 1e-12) {
101
+ for (let i = 0; i < arr.length; i++) arr[i] = arr[i]! / norm;
102
+ }
103
+ return arr;
104
+ }
105
+
106
+ /**
107
+ * Build a real ONNX InferenceSession over the committed encoder-v1 asset.
108
+ * Returns a typed result — NEVER throws. On any failure the caller demotes
109
+ * to mode B trigram with the returned code.
110
+ *
111
+ * @param assetDir Path to the directory containing model.onnx + manifest.
112
+ * @param manifest The verified ModelManifestV1 from the asset.
113
+ * @param reporter Encoder reporter for the onnx_loaded event (optional).
114
+ * @param allocatedBytes Externally committed bytes (for RSS budget check).
115
+ */
116
+ export async function buildOnnxSession(
117
+ assetDir: string,
118
+ manifest: ModelManifestV1,
119
+ reporter?: EncoderReporter,
120
+ allocatedBytes: number = 0,
121
+ ): Promise<OnnxSessionResult> {
122
+ // Q01: cap-before-allocation.
123
+ if (allocatedBytes > ENCODER_RSS_BUDGET_BYTES) {
124
+ return { ok: false, code: ENC_ONNX_FAIL.RSS_BREACH };
125
+ }
126
+
127
+ // Opset assertion.
128
+ if (manifest.opset !== ENCODER_OPSET) {
129
+ return { ok: false, code: ENC_ONNX_FAIL.OPSET_MISMATCH };
130
+ }
131
+
132
+ const modelPath = join(assetDir, manifest.onnx.path);
133
+ if (!existsSync(modelPath)) {
134
+ return { ok: false, code: ENC_ONNX_FAIL.MODEL_ABSENT };
135
+ }
136
+
137
+ // Dynamically import onnxruntime-web/wasm (never a hard dependency).
138
+ let ort: OrtWasmModule;
139
+ try {
140
+ ort = (await import("onnxruntime-web/wasm")) as OrtWasmModule;
141
+ if (!ort?.InferenceSession?.create) {
142
+ return { ok: false, code: ENC_ONNX_FAIL.MODULE_ABSENT };
143
+ }
144
+ } catch {
145
+ return { ok: false, code: ENC_ONNX_FAIL.MODULE_ABSENT };
146
+ }
147
+
148
+ // Resolve WASM binary path for the threading backend.
149
+ const wasmPath = resolveOrtWasmPath();
150
+
151
+ let rawSession: OrtSession;
152
+ try {
153
+ const opts: { executionProviders: readonly string[]; intraOpNumThreads: number; wasmPaths?: string } = {
154
+ executionProviders: ["wasm"],
155
+ intraOpNumThreads: 4,
156
+ };
157
+ if (wasmPath) opts.wasmPaths = wasmPath;
158
+ rawSession = await ort.InferenceSession.create(modelPath, opts);
159
+ } catch {
160
+ return { ok: false, code: ENC_ONNX_FAIL.SESSION_ERROR };
161
+ }
162
+
163
+ reporter?.onnxSessionLoaded({
164
+ opset: manifest.opset,
165
+ semanticWidth: ENCODER_SEMANTIC_WIDTH,
166
+ threads: 4,
167
+ });
168
+
169
+ const session: OnnxInferenceSession = {
170
+ opset: manifest.opset,
171
+ semanticWidth: ENCODER_SEMANTIC_WIDTH,
172
+ async infer(tokens: readonly number[]): Promise<Float32Array> {
173
+ const n = tokens.length;
174
+ const inputIds = new BigInt64Array(n);
175
+ const attentionMask = new BigInt64Array(n);
176
+ const tokenTypeIds = new BigInt64Array(n);
177
+ for (let i = 0; i < n; i++) {
178
+ inputIds[i] = BigInt(tokens[i]!);
179
+ attentionMask[i] = 1n;
180
+ tokenTypeIds[i] = 0n;
181
+ }
182
+ const feeds = {
183
+ input_ids: { data: inputIds, dims: [1, n], type: "int64" },
184
+ attention_mask: { data: attentionMask, dims: [1, n], type: "int64" },
185
+ token_type_ids: { data: tokenTypeIds, dims: [1, n], type: "int64" },
186
+ };
187
+ const results = await rawSession.run(feeds, ["sentence_embedding"]);
188
+ const out = results["sentence_embedding"];
189
+ if (!out || !(out.data instanceof Float32Array)) {
190
+ return new Float32Array(ENCODER_SEMANTIC_WIDTH);
191
+ }
192
+ return l2Normalize(out.data);
193
+ },
194
+ release: () => rawSession.release(),
195
+ };
196
+
197
+ return { ok: true, session };
198
+ }
@@ -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;
@@ -36,7 +36,7 @@ export interface OrtWasmModule {
36
36
  opts: { executionProviders: string[]; intraOpNumThreads: number },
37
37
  ): Promise<{
38
38
  run(
39
- feeds: Record<string, Float32Array>,
39
+ feeds: Record<string, { data: BigInt64Array; dims: number[]; type: string }>,
40
40
  outputNames: string[],
41
41
  ): Promise<Record<string, { data: Float32Array }>>;
42
42
  }>;
@@ -45,14 +45,14 @@ 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;
52
52
  /** The per-asset token capacity cap (normative <= 512). */
53
53
  readonly maxTokens: number;
54
- /** Run one inference over already shape-checked input tokens. */
55
- infer(inputIds: Float32Array): Promise<Float32Array>;
54
+ /** Run one inference over shape-checked token IDs (int64 input_ids). */
55
+ infer(tokens: number[]): Promise<Float32Array>;
56
56
  }
57
57
 
58
58
  /** True if `onnxruntime-web` resolves on this host (loading is best-effort).
@@ -94,10 +94,23 @@ export async function createWasmSession(
94
94
  opset: ENCODER_OPSET,
95
95
  semanticWidth: ENCODER_SEMANTIC_WIDTH,
96
96
  maxTokens,
97
- async infer(inputIds: Float32Array): Promise<Float32Array> {
98
- const feeds = { input_ids: inputIds };
99
- const results = await session.run(feeds, ["embedding"]);
100
- const out = results["embedding"];
97
+ async infer(tokens: number[]): Promise<Float32Array> {
98
+ const n = tokens.length;
99
+ const inputIds = new BigInt64Array(n);
100
+ const attentionMask = new BigInt64Array(n);
101
+ const tokenTypeIds = new BigInt64Array(n);
102
+ for (let i = 0; i < n; i++) {
103
+ inputIds[i] = BigInt(tokens[i]!);
104
+ attentionMask[i] = 1n;
105
+ tokenTypeIds[i] = 0n;
106
+ }
107
+ const feeds = {
108
+ input_ids: { data: inputIds, dims: [1, n], type: "int64" },
109
+ attention_mask: { data: attentionMask, dims: [1, n], type: "int64" },
110
+ token_type_ids: { data: tokenTypeIds, dims: [1, n], type: "int64" },
111
+ };
112
+ const results = await session.run(feeds, ["sentence_embedding"]);
113
+ const out = results["sentence_embedding"];
101
114
  if (!out || !(out.data instanceof Float32Array)) {
102
115
  return new Float32Array(ENCODER_SEMANTIC_WIDTH);
103
116
  }
@@ -86,6 +86,7 @@ import { selectRuntimeBackend } from "./runtime-select.js";
86
86
  import { emitRuntimeSelected } from "./runtime-emit.js";
87
87
  import { projectSemantic, seedFromBytes } from "./runtime-stub.js";
88
88
  import { STATE_DIR_DEFAULT } from "../../config.js";
89
+ import { tryBuildOnnx, type OnnxDispatchState, NO_ONNX } from "./encoder-onnx-dispatch.js";
89
90
 
90
91
  /** Bytes a single encoder-owned projection buffer commits to the marginal
91
92
  * footprint (Float32Array, 4 bytes per element). */
@@ -136,9 +137,16 @@ function normalizePlatform(p: EncoderPlatform | null): EncoderPlatform | "unsupp
136
137
  return p === null ? "unsupported" : p;
137
138
  }
138
139
 
140
+ /** The concrete runtime returned by createEncoderRuntime — the base
141
+ * EncoderRuntime interface plus the ENC-0b ONNX dispatch state. */
142
+ export type EncoderRuntimeHandle = EncoderRuntime & {
143
+ /** ENC-0b: the ONNX dispatch state (null when ENC_0B is off or not built). */
144
+ readonly onnxState: OnnxDispatchState;
145
+ };
146
+
139
147
  export function createEncoderRuntime(
140
148
  options: CreateEncoderRuntimeOptions = {},
141
- ): EncoderRuntime {
149
+ ): EncoderRuntimeHandle {
142
150
  const reporter = options.reporter ?? createEncoderReporter();
143
151
  const host = mergeHost(options.host);
144
152
  const forced = options.forcedMode;
@@ -152,6 +160,7 @@ export function createEncoderRuntime(
152
160
  let verified = false;
153
161
  let maxTokens = ENCODER_MAX_TOKENS;
154
162
  let selfAllocated = 0;
163
+ let onnxState: OnnxDispatchState = NO_ONNX;
155
164
 
156
165
  const footprint = (): number => selfAllocated + host.allocatedBytes();
157
166
 
@@ -161,12 +170,14 @@ export function createEncoderRuntime(
161
170
  reporter.runtimeDemoted({ reason: code, mode: rmode, platform: plat()?.toString() ?? "unsupported" });
162
171
  };
163
172
 
164
- const runtime: EncoderRuntime = {
173
+ const runtime: EncoderRuntimeHandle = {
165
174
  schema: "encoder-runtime-v1",
166
- // Live getter so `mode` always reflects the latest load/demote outcome.
167
175
  get mode(): EncoderMode {
168
176
  return mode;
169
177
  },
178
+ get onnxState(): OnnxDispatchState {
179
+ return onnxState;
180
+ },
170
181
  load(assetDir: string): EncoderLoadResult {
171
182
  if (rolledBack) {
172
183
  // Q04: report the rollback with its own code, not MANIFEST_INVALID.
@@ -225,6 +236,14 @@ export function createEncoderRuntime(
225
236
  emitRuntimeSelected(host.stateDir ?? STATE_DIR_DEFAULT, chosen);
226
237
  }
227
238
 
239
+ // ENC-0b: fire-and-forget ONNX session build (async, non-blocking).
240
+ // The sync load() contract is preserved; the session build settles
241
+ // asynchronously and is consumed by verifyOnnxSession for tests +
242
+ // future async-heavy router integration (ENC-0c).
243
+ if (manifest) {
244
+ onnxState = tryBuildOnnx(assetDir, manifest, reporter, footprint());
245
+ }
246
+
228
247
  return {
229
248
  ok: true,
230
249
  mode: "A",
@@ -262,14 +281,13 @@ export function createEncoderRuntime(
262
281
  };
263
282
  }
264
283
  const start = host.nowMs();
265
- // ML5-C: the LCG placeholder STILL drives infer by default (the trained
266
- // asset behind runtime-wasm.ts/runtime-native.ts is not yet the
267
- // source-of-truth on master; only the runtime-selection event seam was
268
- // added this sprint).
284
+ // ENC-0b builds the real ONNX session during load() (fire-and-forget);
285
+ // infer() continues serving the LCG placeholder until the router is
286
+ // wired for async inference (ENC-0c). The session is verified by tests
287
+ // via the runtime's verifySession() method.
269
288
  const semantic = projectSemantic(seedFromBytes(embeddedBytes) ^ n, ENCODER_SEMANTIC_WIDTH);
270
289
  selfAllocated = SEMANTIC_BUFFER_BYTES;
271
- const latencyMs = host.nowMs() - start;
272
- return { ok: true, semantic, rssBytes: footprint(), latencyMs, shapeError: null };
290
+ return { ok: true, semantic, rssBytes: footprint(), latencyMs: host.nowMs() - start, shapeError: null };
273
291
  },
274
292
  };
275
293
  return runtime;
@@ -0,0 +1,134 @@
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
+ ];
125
+
126
+ /** The 6 registered ENC-0b conformance IDs (encoder-trunk fixtures). */
127
+ export const ENC_0B_IDS: readonly string[] = [
128
+ "ENC-TRUNK-001",
129
+ "ENC-TRUNK-002",
130
+ "ENC-TRUNK-003",
131
+ "ENC-TRUNK-004",
132
+ "ENC-TRUNK-005",
133
+ "ENC-TRUNK-006",
134
+ ];
@@ -34,8 +34,13 @@ export const ENCODER_SUPPORTED_PLATFORMS: readonly EncoderPlatform[] = [
34
34
  "win32-x64",
35
35
  ];
36
36
 
37
- /** ONNX opset required by the normative v1 target (opset 17). */
38
- export const ENCODER_OPSET = 17;
37
+ /** ONNX opset required by the normative v1 target. ENC-0a re-baselines from 17
38
+ * to 21: the committed placeholder asset (assets/vector-cortex/encoder-v1/)
39
+ * declares opset 21 in its manifest (the 2026-08-05 BAAI/bge-small-en-v1.5
40
+ * upstream export is opset 21; the earlier Xenova opset-17 requirement was
41
+ * dropped). The locked decision is recorded in
42
+ * docs/vector-cortex/encoder-backend-decision.md. */
43
+ export const ENCODER_OPSET = 21;
39
44
  /** Batch must be exactly 1 (single-request inference). */
40
45
  export const ENCODER_BATCH = 1;
41
46
  /** Maximum accepted token count (WordPiece, deterministic truncation). */
@@ -124,7 +129,7 @@ export type EncoderMode = "A" | "B" | "C";
124
129
 
125
130
  /** Exact VC2A failure codes (returned, never thrown across the boundary). */
126
131
  export const ENC_FAIL = {
127
- /** opset != 17. */
132
+ /** opset != 21 (ENC-0a re-baseline applied; placeholder manifest updated alongside). */
128
133
  OPSET_INVALID: "ENC_OPSET_INVALID",
129
134
  /** batch != 1. */
130
135
  BATCH_INVALID: "ENC_BATCH_INVALID",
@@ -275,117 +280,16 @@ export const ENC2B_IDS: readonly string[] = [
275
280
  ];
276
281
 
277
282
  // ---------------------------------------------------------------------------
278
- // VC2C — encoder qualification + calibration (QualifiedEncoderV1 / CalibrationV1).
283
+ // VC2C — encoder qualification + calibration: extracted to types-vc2c.ts
284
+ // (delegate-shell split, soft-limit compliance). Re-exported here.
279
285
  // ---------------------------------------------------------------------------
280
286
 
281
- /**
282
- * Held-out metrics recorded as qualification evidence for a candidate asset
283
- * (EVALUATION.md §metrics). Semantic metrics are Spearman rho + recall@10;
284
- * dependency directed precision/recall; contradiction precision/recall/ECE;
285
- * cache precision/recall; payload-routing macro-F1 + exact/anchor recall;
286
- * reconstruction is the binary causality/exact/closure/task-success set. The
287
- * qualification decision (task 3 `select.ts`) consumes ONLY the per-head rows
288
- * this record carries; the true EVALUATION thresholds are the normative
289
- * constants in MODEL_ASSET (mirrored in `EVALUATION_THRESHOLDS` below).
290
- */
291
- export interface EncoderHeldOutMetrics {
292
- /** Semantic: Spearman rho (>= .75) and recall@10 (>= .90). */
293
- readonly semantic: { readonly spearman: number; readonly recallAt10: number };
294
- /** Dependency: directed precision (>= .97) and recall (>= .95). */
295
- readonly dependency: { readonly precision: number; readonly recall: number };
296
- /** Contradiction: precision (>= .98), recall (>= .90), ECE (<= .05). */
297
- readonly contradiction: { readonly precision: number; readonly recall: number; readonly ece: number };
298
- /** Cache: precision (>= .999, zero false-stable) and recall (>= .90). */
299
- readonly cacheStability: { readonly precision: number; readonly recall: number };
300
- /** Payload routing: macro-F1 (>= .97) and exact/anchor recall (1.0). */
301
- readonly payloadRouting: { readonly macroF1: number; readonly exactAnchorRecall: number };
302
- /** Reconstruction: binary gates — zero causal/tool/anchor/exact violations. */
303
- readonly reconstruction: {
304
- readonly votesOk: boolean;
305
- readonly dependencyClosureRecall: number;
306
- readonly taskSuccessNonInferior: boolean;
307
- };
308
- }
309
-
310
- /** Normative per-head + asset qualification thresholds (MODEL_ASSET §qualification
311
- * + EVALUATION.md §metrics). These are the constants `select.ts` evaluates the
312
- * candidate's held-out metrics against (task 3 atomic check). */
313
- export const EVALUATION_THRESHOLDS = {
314
- semantic: { spearman: 0.75, recallAt10: 0.9 },
315
- dependency: { precision: 0.97, recall: 0.95 },
316
- contradiction: { precision: 0.98, recall: 0.9, ece: 0.05 },
317
- cacheStability: { precision: 0.999, recall: 0.9 },
318
- payloadRouting: { macroF1: 0.97, exactAnchorRecall: 1.0 },
319
- reconstruction: { dependencyClosureRecall: 1.0 },
320
- asset: { maxTokens: 512, maxLatencyP95Ms: 40, maxRssDeltaMib: 150 },
321
- } as const;
322
-
323
- /**
324
- * CalibrationV1 — fitted temperature/isotonic calibration, frozen on the
325
- * CALIBRATION split only (VC2C task 2). Held-out test labels NEVER enter the
326
- * fit inputs (calibration-fit prohibition). The split digest proves the exact
327
- * calibration assignment (grouped by repository+session); the frozen temp/threshold
328
- * values are what `select.ts` stamps into a `QualifiedEncoderV1`.
329
- */
330
- export interface CalibrationV1 {
331
- readonly schema: "calibration-v1";
332
- readonly headOrder: readonly EncoderHeadName[];
333
- /** SHA-256 of the calibration split assignment (grouped repository+session). */
334
- readonly calibrationSplitDigest: string;
335
- /** Fitted on the calibration split only; held-out labels excluded from fit. */
336
- readonly fittedOnCalibrationOnly: true;
337
- /** Frozen per-head temperature (isotonic calibration reference points). */
338
- readonly temperatures: Readonly<Record<EncoderHeadName, number>>;
339
- /** Frozen per-head decision thresholds for the qualified decision. */
340
- readonly thresholds: Readonly<Record<EncoderHeadName, number>>;
341
- /** Seed of the deterministic calibration fit. */
342
- readonly seed: number;
343
- }
344
-
345
- /**
346
- * QualifiedEncoderV1 — the VC2C-owned eligibility record (mode A). Produced by
347
- * `select.ts` ONLY when EVERY MODEL_ASSET + per-head EVALUATION threshold
348
- * passes (atomic — one failed field demotes all of A). Pins the asset digest,
349
- * the calibration digest, the held-out metrics that justified eligibility, and
350
- * the calibration reference, so VC3A receives a fully self-describing candidate.
351
- */
352
- export interface QualifiedEncoderV1 {
353
- readonly schema: "qualified-encoder-v1";
354
- readonly modelVersion: string;
355
- readonly mode: "A";
356
- /** SHA-256 of the asset manifest bytes (ModelManifestV1) that qualified.
357
- * Identical semantics to the dashboard health card's `encoderAssetDigest`
358
- * (both hash the committed manifest.json ModelManifestV1 bytes), so
359
- * downstream consumers (VC3A) pin the same digest across the seam. */
360
- readonly assetDigest: string;
361
- /** SHA-256 of the calibration split assignment (grouped repository+session)
362
- * that the CalibrationV1 was fitted on — the calibration's core identity. */
363
- readonly calibrationDigest: string;
364
- /** SHA-256 of the qualified asset's verified ONNX bytes (digest-pinned). */
365
- readonly onnxDigest: string;
366
- /** Held-out metrics recorded as the eligibility evidence. */
367
- readonly heldOut: EncoderHeldOutMetrics;
368
- /** Calibration reference this qualification is grounded in. */
369
- readonly calibration: CalibrationV1;
370
- }
371
-
372
- /** VC2C-specific qualification failure codes (returned, never thrown). */
373
- export const ENC_QUALIFICATION_FAIL = {
374
- /** The qualification manifest hash does not match the calibration that was fit
375
- * (corrupt qualification manifest after calibration before selection). */
376
- DIGEST_MISMATCH: "ENC_QUALIFICATION_DIGEST_MISMATCH",
377
- /** One or more per-head EVALUATION thresholds failed (demotes all of A). */
378
- THRESHOLD_FAILED: "ENC_QUALIFICATION_THRESHOLD_FAILED",
379
- /** An asset-field qualification check (asset/latency/RSS) failed. */
380
- ASSET_FAILED: "ENC_QUALIFICATION_ASSET_FAILED",
381
- /** Calibration was attempted using held-out labels (fit prohibition). */
382
- HELD_OUT_IN_FIT: "ENC_QUALIFICATION_HELD_OUT_IN_FIT",
383
- } as const;
384
-
385
- /** The 4 registered VC2C conformance IDs (task 1: "register ENC-017..020"). */
386
- export const ENC2C_IDS: readonly string[] = [
387
- "ENC-017",
388
- "ENC-018",
389
- "ENC-019",
390
- "ENC-020",
391
- ];
287
+ export {
288
+ EVALUATION_THRESHOLDS,
289
+ ENC_QUALIFICATION_FAIL,
290
+ ENC2C_IDS,
291
+ ENC_0B_IDS,
292
+ type EncoderHeldOutMetrics,
293
+ type CalibrationV1,
294
+ type QualifiedEncoderV1,
295
+ } from "./types-vc2c.js";