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.
- package/assets/vector-cortex/encoder-v1/manifest.json +1 -1
- package/assets/vector-cortex/encoder-v1/model-card.json +1 -1
- package/assets/vector-cortex/encoder-v1/model.onnx +0 -0
- package/assets/vector-cortex/encoder-v1/tokenizer.json +1 -1
- package/dist/config/vector-cortex-enc0a.js +32 -0
- package/dist/config/vector-cortex-enc0b.js +34 -0
- package/dist/config/vector-cortex.js +3 -3
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +2 -0
- package/dist/src/config/vector-cortex-enc0a.js +32 -0
- package/dist/src/config/vector-cortex-enc0b.js +34 -0
- package/dist/src/config/vector-cortex.js +3 -3
- package/dist/src/config.js +1 -1
- package/dist/src/vector-cortex/_acceptance-enc0a-contract.js +60 -0
- package/dist/src/vector-cortex/_acceptance-vc2a-conformance.js +90 -0
- package/dist/src/vector-cortex/_acceptance-vc2a-runtime.js +285 -0
- package/dist/src/vector-cortex/_acceptance-vc2b-conformance.js +92 -0
- package/dist/src/vector-cortex/_acceptance-vc2b-heads.js +221 -0
- package/dist/src/vector-cortex/encoder/asset.js +3 -2
- package/dist/src/vector-cortex/encoder/decision.js +75 -0
- package/dist/src/vector-cortex/encoder/emit.js +2 -0
- package/dist/src/vector-cortex/encoder/encoder-onnx-dispatch.js +58 -0
- package/dist/src/vector-cortex/encoder/onnx.js +147 -0
- package/dist/src/vector-cortex/encoder/runtime-wasm.js +17 -4
- package/dist/src/vector-cortex/encoder/runtime.js +17 -7
- package/dist/src/vector-cortex/encoder/types-vc2c.js +50 -0
- package/dist/src/vector-cortex/encoder/types.js +13 -34
- package/dist/vector-cortex/_acceptance-enc0a-contract.js +60 -0
- package/dist/vector-cortex/_acceptance-vc2a-conformance.js +90 -0
- package/dist/vector-cortex/_acceptance-vc2a-runtime.js +285 -0
- package/dist/vector-cortex/_acceptance-vc2b-conformance.js +92 -0
- package/dist/vector-cortex/_acceptance-vc2b-heads.js +221 -0
- package/dist/vector-cortex/encoder/asset.js +3 -2
- package/dist/vector-cortex/encoder/decision.js +75 -0
- package/dist/vector-cortex/encoder/emit.js +2 -0
- package/dist/vector-cortex/encoder/encoder-onnx-dispatch.js +58 -0
- package/dist/vector-cortex/encoder/onnx.js +147 -0
- package/dist/vector-cortex/encoder/runtime-wasm.js +17 -4
- package/dist/vector-cortex/encoder/runtime.js +17 -7
- package/dist/vector-cortex/encoder/types-vc2c.js +50 -0
- package/dist/vector-cortex/encoder/types.js +13 -34
- package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +12 -0
- package/package.json +3 -2
- package/src/config/vector-cortex-enc0a.ts +34 -0
- package/src/config/vector-cortex-enc0b.ts +36 -0
- package/src/config/vector-cortex.ts +3 -3
- package/src/config.ts +2 -0
- package/src/vector-cortex/_acceptance-enc0a-contract.ts +71 -0
- package/src/vector-cortex/_acceptance-vc2a-conformance.ts +119 -0
- package/src/vector-cortex/_acceptance-vc2a-runtime.ts +300 -0
- package/src/vector-cortex/_acceptance-vc2b-conformance.ts +121 -0
- package/src/vector-cortex/_acceptance-vc2b-heads.ts +234 -0
- package/src/vector-cortex/encoder/asset.ts +3 -2
- package/src/vector-cortex/encoder/bench-export.ts +2 -2
- package/src/vector-cortex/encoder/decision.ts +125 -0
- package/src/vector-cortex/encoder/emit.ts +5 -1
- package/src/vector-cortex/encoder/encoder-onnx-dispatch.ts +76 -0
- package/src/vector-cortex/encoder/onnx.ts +198 -0
- package/src/vector-cortex/encoder/runtime-native.ts +1 -1
- package/src/vector-cortex/encoder/runtime-wasm.ts +21 -8
- package/src/vector-cortex/encoder/runtime.ts +27 -9
- package/src/vector-cortex/encoder/types-vc2c.ts +134 -0
- 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
|
|
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,
|
|
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
|
|
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
|
|
55
|
-
infer(
|
|
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(
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
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
|
-
):
|
|
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:
|
|
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
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
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
|
-
|
|
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
|
|
38
|
-
|
|
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 !=
|
|
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
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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";
|