pi-mega-compact 0.17.1 → 0.18.1

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 (47) hide show
  1. package/dist/config/vector-cortex.js +19 -0
  2. package/dist/config.js +117 -0
  3. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +4 -0
  4. package/dist/extensions/mega-events/context-handler/dbMirrorAppend.js +63 -0
  5. package/dist/extensions/mega-events/context-handler/gateCheck.js +59 -0
  6. package/dist/extensions/mega-events/context-handler/liveTrim.js +178 -0
  7. package/dist/extensions/mega-events/context-handler/pipelineRun.js +37 -0
  8. package/dist/extensions/mega-events/context-handler.js +39 -305
  9. package/dist/log.js +47 -0
  10. package/dist/src/config/vector-cortex.js +19 -0
  11. package/dist/src/config.js +1 -1
  12. package/dist/src/vector-cortex/encoder/asset.js +142 -0
  13. package/dist/src/vector-cortex/encoder/emit-vc2b.js +63 -0
  14. package/dist/src/vector-cortex/encoder/emit.js +42 -0
  15. package/dist/src/vector-cortex/encoder/heads.js +113 -0
  16. package/dist/src/vector-cortex/encoder/lexical.js +104 -0
  17. package/dist/src/vector-cortex/encoder/router.js +115 -0
  18. package/dist/src/vector-cortex/encoder/runtime.js +228 -0
  19. package/dist/src/vector-cortex/encoder/trigram.js +75 -0
  20. package/dist/src/vector-cortex/encoder/types.js +138 -0
  21. package/dist/vector-cortex/encoder/asset.js +142 -0
  22. package/dist/vector-cortex/encoder/emit-vc2b.js +63 -0
  23. package/dist/vector-cortex/encoder/emit.js +42 -0
  24. package/dist/vector-cortex/encoder/heads.js +113 -0
  25. package/dist/vector-cortex/encoder/lexical.js +104 -0
  26. package/dist/vector-cortex/encoder/router.js +115 -0
  27. package/dist/vector-cortex/encoder/runtime.js +228 -0
  28. package/dist/vector-cortex/encoder/trigram.js +75 -0
  29. package/dist/vector-cortex/encoder/types.js +138 -0
  30. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +14 -0
  31. package/extensions/mega-events/context-handler/dbMirrorAppend.ts +93 -0
  32. package/extensions/mega-events/context-handler/gateCheck.ts +101 -0
  33. package/extensions/mega-events/context-handler/liveTrim.ts +241 -0
  34. package/extensions/mega-events/context-handler/pipelineRun.ts +79 -0
  35. package/extensions/mega-events/context-handler.ts +45 -347
  36. package/package.json +1 -1
  37. package/src/config/vector-cortex.ts +21 -0
  38. package/src/config.ts +2 -0
  39. package/src/vector-cortex/encoder/asset.ts +155 -0
  40. package/src/vector-cortex/encoder/emit-vc2b.ts +82 -0
  41. package/src/vector-cortex/encoder/emit.ts +51 -0
  42. package/src/vector-cortex/encoder/heads.ts +142 -0
  43. package/src/vector-cortex/encoder/lexical.ts +123 -0
  44. package/src/vector-cortex/encoder/router.ts +163 -0
  45. package/src/vector-cortex/encoder/runtime.ts +283 -0
  46. package/src/vector-cortex/encoder/trigram.ts +85 -0
  47. package/src/vector-cortex/encoder/types.ts +275 -0
@@ -0,0 +1,283 @@
1
+ /**
2
+ * vector-cortex/encoder/runtime.ts — VC2A EncoderRuntime (task 3).
3
+ *
4
+ * Allocates (prepares an inference session) ONLY after manifest verification;
5
+ * rejects any non (batch 1, tokens <= maxTokens, <=512) input with
6
+ * ENC_SHAPE_INVALID; caps the encoder's MARGINAL footprint at 150 MiB
7
+ * (ENC_RSS_BUDGET_EXCEEDED -> mode B); and yields a deterministic mode-A
8
+ * inference over the verified asset (the trained weights are substituted in
9
+ * VC2C — the contract, shape gating and budgets all land here).
10
+ *
11
+ * MEMORY BUDGET (Q01/Q02): the 150 MiB cap measures the encoder's INCREMENTAL
12
+ * footprint — an in-process allocation counter (`selfAllocated`) plus any
13
+ * externally staged asset working set (`host.allocatedBytes()`) — NOT the
14
+ * whole-process RSS. In a live pi extension the process baseline (node:sqlite
15
+ * DatabaseSync + dashboard + loaded context) routinely exceeds 150 MiB, so an
16
+ * absolute-RSS cap would permanently demote a qualified asset to mode B and
17
+ * make mode A unreachable in production. Bounding the marginal footprint keeps
18
+ * mode A reachable while still enforcing the budget. `selfAllocated` models a
19
+ * single REUSABLE 384-float projection buffer (first inference allocates it,
20
+ * every later inference reuses it), so it is capped at `SEMANTIC_BUFFER_BYTES`
21
+ * — the marginal footprint can never grow without bound (Q01), and a long-lived
22
+ * runtime cannot drift over budget from healthy operation. The check runs
23
+ * BEFORE the allocation on both the load and the inference path
24
+ * (cap-before-allocation, task 3), and an over-budget inference demotes the
25
+ * runtime to mode B just as an over-budget load does (consistent demotion per
26
+ * ENC_FAIL.RSS_BUDGET_EXCEEDED).
27
+ *
28
+ * TOKEN CAPACITY (Q03): the per-manifest `maxTokens` (<= 512) is stored at load
29
+ * and enforced at inference — an input longer than the verified manifest's
30
+ * declared capacity is rejected with ENC_SHAPE_INVALID, honoring the model
31
+ * contract rather than a global 512 ceiling.
32
+ *
33
+ * FLAG GATING (Q04): the default factory consults `MEGACOMPACT_VC2A`; when the
34
+ * flag is OFF the runtime is fixed at mode C (rollback, byte-identical to the
35
+ * predecessor — no asset is read or verified). `forcedMode: "C"` is the
36
+ * explicit override for the same rollback path.
37
+ *
38
+ * Triad: A = qualified local ONNX (verified); B = asset-free trigram (forced by
39
+ * a missing/unsupported/digest-bad asset, no remote fetch); C = lexical forced
40
+ * when A verification fails AND B initialization itself fails. Demotions always
41
+ * select B/C locally and never attempt a network fetch (PREVENT-PI-004).
42
+ *
43
+ * Pi-agnostic. No `any` (PREVENT-011). Emits the two VC2A events via the
44
+ * reporter (non-fatal).
45
+ */
46
+
47
+ import {
48
+ detectPlatform,
49
+ readEncoderManifest,
50
+ verifyEncoderAsset,
51
+ type AssetVerifyResult,
52
+ } from "./asset.js";
53
+ import { createEncoderReporter, type EncoderReporter } from "./emit.js";
54
+ import { VC2A_ENABLED } from "../../config/vector-cortex.js";
55
+ import {
56
+ ENC_FAIL,
57
+ ENCODER_MAX_TOKENS,
58
+ ENCODER_RSS_BUDGET_BYTES,
59
+ ENCODER_SEMANTIC_WIDTH,
60
+ type EncoderInferResult,
61
+ type EncoderInput,
62
+ type EncoderLoadResult,
63
+ type EncoderMode,
64
+ type EncoderRuntime,
65
+ } from "./types.js";
66
+
67
+ /** Bytes a single encoder-owned projection buffer commits to the marginal
68
+ * footprint (Float32Array, 4 bytes per element). */
69
+ const SEMANTIC_BUFFER_BYTES = ENCODER_SEMANTIC_WIDTH * 4;
70
+
71
+ /** Injectable seam for allocation accounting + allocator + clock so tests can
72
+ * drive failures deterministically. Allocation accounting is the encoder's
73
+ * MARGINAL footprint (Q01) — NOT whole-process RSS. */
74
+ export interface RuntimeHost {
75
+ /** External bytes already committed to the encoder's incremental working set
76
+ * (e.g. an ONNX session buffer staged outside this runtime). Default 0. */
77
+ readonly allocatedBytes: () => number;
78
+ readonly allocatorFails: () => boolean;
79
+ readonly nowMs: () => number;
80
+ }
81
+
82
+ const DEFAULT_HOST: RuntimeHost = {
83
+ allocatedBytes: () => 0,
84
+ allocatorFails: () => false,
85
+ nowMs: () => Date.now(),
86
+ };
87
+
88
+ export interface CreateEncoderRuntimeOptions {
89
+ readonly reporter?: EncoderReporter;
90
+ readonly host?: Partial<RuntimeHost>;
91
+ /** Force the rollback path: load() always returns mode C without verifying
92
+ * any asset (byte-identical to the pre-triad derived pointer). A/B forcing
93
+ * is intentionally not offered — those are reached by verification outcome,
94
+ * not by fiat. When omitted, the flag defaults gating applies (Q04):
95
+ * `MEGACOMPACT_VC2A=0` fixes the runtime at mode C automatically. */
96
+ readonly forcedMode?: "C";
97
+ /** Override the platform detector (tests / cross-platform demotion). */
98
+ readonly platform?: () => ReturnType<typeof detectPlatform>;
99
+ }
100
+
101
+ function mergeHost(partial?: Partial<RuntimeHost>): RuntimeHost {
102
+ return { ...DEFAULT_HOST, ...partial };
103
+ }
104
+
105
+ /** A deterministic seeded projection so the mode-A inference path is testable
106
+ * end-to-end without onnxruntime (real weights + execution are VC2C). */
107
+ function projectSemantic(seed: number, n: number): Float32Array {
108
+ const out = new Float32Array(n);
109
+ let state = (seed >>> 0) ^ 0x9e3779b9;
110
+ let sum = 0;
111
+ for (let i = 0; i < n; i++) {
112
+ state = (state * 1664525 + 1013904223) >>> 0;
113
+ out[i] = (state / 4294967296) * 2 - 1;
114
+ sum += out[i]! * out[i]!;
115
+ }
116
+ const norm = Math.sqrt(sum) || 1;
117
+ for (let i = 0; i < n; i++) out[i] = out[i]! / norm;
118
+ return out;
119
+ }
120
+
121
+ /** Deterministic token seed derived from the verified asset bytes count. */
122
+ function seedFromBytes(embeddedBytes: number): number {
123
+ return (embeddedBytes * 2654435761) >>> 0;
124
+ }
125
+
126
+ function modeLabel(mode: EncoderMode): string {
127
+ return mode === "A" ? "qualified-onnx" : mode === "B" ? "trigram" : "lexical";
128
+ }
129
+
130
+ export function createEncoderRuntime(
131
+ options: CreateEncoderRuntimeOptions = {},
132
+ ): EncoderRuntime {
133
+ const reporter = options.reporter ?? createEncoderReporter();
134
+ const host = mergeHost(options.host);
135
+ const forced = options.forcedMode;
136
+ const plat = options.platform ?? detectPlatform;
137
+
138
+ // Q04: rollback contract — MEGACOMPACT_VC2A=0 selects mode C (byte-identical
139
+ // to the predecessor: no asset read/verify, no learned infer). An explicit
140
+ // forcedMode "C" takes precedence; otherwise the flag gates the default.
141
+ const rolledBack = forced === "C" || !VC2A_ENABLED();
142
+ let mode: EncoderMode = rolledBack ? "C" : "C";
143
+ let embeddedBytes = 0;
144
+ let verified = false;
145
+ /** Per-manifest token capacity (<= 512) from the verified asset; enforced at
146
+ * inference (Q03). Defaults to the global ceiling before a load. */
147
+ let maxTokens = ENCODER_MAX_TOKENS;
148
+ /** Bytes this runtime itself has allocated. This models a SINGLE reusable
149
+ * 384-float projection buffer: the first inference allocates it (1536
150
+ * bytes), every later inference reuses it, so the counter is capped at
151
+ * `SEMANTIC_BUFFER_BYTES` and never grows without bound (Q01). Combined
152
+ * with `host.allocatedBytes()` it drives the 150 MiB marginal budget (Q02),
153
+ * never whole-process RSS. */
154
+ let selfAllocated = 0;
155
+
156
+ /** The encoder's marginal working-set footprint, in bytes. */
157
+ const footprint = (): number => selfAllocated + host.allocatedBytes();
158
+
159
+ const demoteTo = (rmode: "B" | "C", code: string): void => {
160
+ mode = rmode;
161
+ verified = false;
162
+ reporter.runtimeDemoted({ reason: code, mode: rmode, platform: plat()?.toString() ?? "unsupported" });
163
+ };
164
+
165
+ const runtime: EncoderRuntime = {
166
+ schema: "encoder-runtime-v1",
167
+ // Live getter so `mode` always reflects the latest load/demote outcome
168
+ // (a plain property would freeze at its construction-time value forever).
169
+ get mode(): EncoderMode {
170
+ return mode;
171
+ },
172
+ load(assetDir: string): EncoderLoadResult {
173
+ if (rolledBack) {
174
+ // Rollback path (forcedMode "C" or MEGACOMPACT_VC2A=0): mode C restores
175
+ // the prior derived pointer; no asset is read or verified; no emission.
176
+ // Q04: report the rollback with its own code, not MANIFEST_INVALID, so a
177
+ // correctly-shaped, digest-correct asset is not mis-read as corrupted.
178
+ mode = "C";
179
+ verified = false;
180
+ return { ok: false, mode: "C", code: ENC_FAIL.ROLLBACK };
181
+ }
182
+ // Attempt A: verify the local qualified ONNX asset (never a remote fetch).
183
+ const manifest = readEncoderManifest(assetDir);
184
+ let verify: AssetVerifyResult;
185
+ if (manifest === null) {
186
+ verify = { ok: false, code: ENC_FAIL.MANIFEST_INVALID };
187
+ } else {
188
+ verify = verifyEncoderAsset(assetDir, manifest, plat());
189
+ }
190
+
191
+ if (!verify.ok) {
192
+ // A failed -> B, unless B init itself fails (allocator) -> C.
193
+ if (host.allocatorFails()) {
194
+ demoteTo("C", ENC_FAIL.ASSET_UNREADABLE);
195
+ return { ok: false, mode: "C", code: ENC_FAIL.ASSET_UNREADABLE };
196
+ }
197
+ demoteTo("B", verify.code);
198
+ return { ok: false, mode: "B", code: verify.code };
199
+ }
200
+
201
+ // Allocate only after verification (task 3). Simulate allocator failure.
202
+ if (host.allocatorFails()) {
203
+ demoteTo("B", ENC_FAIL.ASSET_UNREADABLE);
204
+ return { ok: false, mode: "B", code: ENC_FAIL.ASSET_UNREADABLE };
205
+ }
206
+
207
+ // Cap the encoder's MARGINAL footprint at 150 MiB (task 3, Q01). This
208
+ // bounds the encoder's incremental allocation, so a healthy process with
209
+ // a large baseline RSS still reaches mode A.
210
+ if (footprint() > ENCODER_RSS_BUDGET_BYTES) {
211
+ demoteTo("B", ENC_FAIL.RSS_BUDGET_EXCEEDED);
212
+ return { ok: false, mode: "B", code: ENC_FAIL.RSS_BUDGET_EXCEEDED };
213
+ }
214
+
215
+ embeddedBytes = verify.embeddedBytes;
216
+ // Q03: record the verified manifest's token capacity so inference can
217
+ // enforce the model's declared maximum, not just the global 512 ceiling.
218
+ maxTokens = verify.maxTokens;
219
+ verified = true;
220
+ mode = "A";
221
+ reporter.assetVerified({
222
+ mode: "A",
223
+ embeddedBytes: verify.embeddedBytes,
224
+ onnxDigest: verify.onnxDigest.slice(0, 12),
225
+ });
226
+ return {
227
+ ok: true,
228
+ mode: "A",
229
+ embeddedBytes: verify.embeddedBytes,
230
+ rssBytes: footprint(),
231
+ sessionId: `enc-${seedFromBytes(verify.embeddedBytes).toString(16)}`,
232
+ };
233
+ },
234
+ infer(input: EncoderInput): EncoderInferResult {
235
+ if (!verified || mode !== "A") {
236
+ // Only batch1/max512 verified assets reach inference (mode B/C do not).
237
+ return {
238
+ ok: false,
239
+ code: ENC_FAIL.SHAPE_INVALID,
240
+ shapeError: "no verified learned asset; mode is " + modeLabel(mode),
241
+ };
242
+ }
243
+ if (!input || !Array.isArray(input.tokens)) {
244
+ return { ok: false, code: ENC_FAIL.SHAPE_INVALID, shapeError: "missing tokens array" };
245
+ }
246
+ const n = input.tokens.length;
247
+ // Q03: enforce the per-manifest maxTokens (<= global 512 ceiling), so an
248
+ // over-cap request against a low-cap verified asset is rejected rather
249
+ // than silently exceeding the model's declared capacity.
250
+ if (n < 1 || n > maxTokens) {
251
+ return {
252
+ ok: false,
253
+ code: ENC_FAIL.SHAPE_INVALID,
254
+ shapeError: `token count ${n} outside 1..${maxTokens} (manifest cap)`,
255
+ };
256
+ }
257
+ // Q03: cap-before-allocation on the inference path too. Check the
258
+ // marginal footprint BEFORE allocating the projection buffer; an
259
+ // over-budget inference demotes to mode B consistently with load() (the
260
+ // ENC_FAIL.RSS_BUDGET_EXCEEDED model: "measured RSS over 150 MiB -> B"),
261
+ // so a subsequent infer no longer attempts allocation in a stale mode A.
262
+ if (footprint() > ENCODER_RSS_BUDGET_BYTES) {
263
+ demoteTo("B", ENC_FAIL.RSS_BUDGET_EXCEEDED);
264
+ return {
265
+ ok: false,
266
+ code: ENC_FAIL.RSS_BUDGET_EXCEEDED,
267
+ shapeError: "encoder footprint over budget during inference",
268
+ };
269
+ }
270
+ const start = host.nowMs();
271
+ // Batch is always 1 (single request); shape is (1, n) for n in 1..maxTokens.
272
+ const semantic = projectSemantic(seedFromBytes(embeddedBytes) ^ n, ENCODER_SEMANTIC_WIDTH);
273
+ // Q01: the projection buffer is a single reusable 384-float array; the
274
+ // marginal footprint is a fixed SEMANTIC_BUFFER_BYTES once it exists, so
275
+ // selfAllocated is SET (never accumulated) — bounded regardless of how
276
+ // many inferences run on a long-lived runtime.
277
+ selfAllocated = SEMANTIC_BUFFER_BYTES;
278
+ const latencyMs = host.nowMs() - start;
279
+ return { ok: true, semantic, rssBytes: footprint(), latencyMs, shapeError: null };
280
+ },
281
+ };
282
+ return runtime;
283
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * vector-cortex/encoder/trigram.ts — VC2B mode B: asset-free trigram encoder.
3
+ *
4
+ * Trigram B is a deterministic, asset-free (no learned model, no manifest, no
5
+ * calibration) 512-dim fixed feature encoding of a token/phrase sequence. It is
6
+ * the mode-B fallback selected when the learned asset (mode A) is removed,
7
+ * missing, unsupported, or digest-bad — and it never imports the learned asset
8
+ * or learned calibration (task 4). It derives directly from textual authority:
9
+ * the same document hashed via its byte-level trigrams yields the same 512-dim
10
+ * vector regardless of the asset state.
11
+ *
12
+ * Width is fixed at `ENCODER_TRIGRAM_WIDTH = 512` (VC2B task 4 "trigram B at 512
13
+ * dimensions"). The vector is L2-normalized; a zero-norm (empty) input maps to
14
+ * the all-zero vector, matching the heads convention of the VectorSet.
15
+ *
16
+ * Failure-triad independence: B's algorithm/index is distinct from A (learned
17
+ * projections) and C (token/phrase lexical) — it is a deterministic hashed
18
+ * n-gram bag-of-hashes, computed purely in-process with no external asset.
19
+ *
20
+ * Pi-agnostic, zero network (PREVENT-PI-004), no `any` (PREVENT-011).
21
+ */
22
+
23
+ import { createHash } from "node:crypto";
24
+ import { l2Normalize } from "./heads.js";
25
+ import {
26
+ createEncoderHeadsReporter,
27
+ type EncoderHeadsReporter,
28
+ } from "./emit-vc2b.js";
29
+
30
+ /** Fixed output width of trigram B (VC2B task 4). */
31
+ export const ENCODER_TRIGRAM_WIDTH = 512;
32
+
33
+ /** Tokenize a phrase into byte-level trigrams (3-byte sliding windows). For a
34
+ * short phrase with fewer than 3 bytes we still emit the available shingles. */
35
+ function trigrams(text: string): string[] {
36
+ const bytes = Buffer.from(text, "utf8");
37
+ if (bytes.length === 0) return [];
38
+ const out: string[] = [];
39
+ const n = bytes.length;
40
+ // `Math.max(1, n - 2)` already emits a single whole-string shingle for 1- and
41
+ // 2-byte phrases (slice(0,3) covers the whole buffer), so there is NO separate
42
+ // short-phrase block — adding one would hash the same shingle twice (Q05).
43
+ for (let i = 0; i < Math.max(1, n - 2); i++) {
44
+ const chunk = bytes.slice(i, i + 3);
45
+ out.push(chunk.toString("hex"));
46
+ }
47
+ return out;
48
+ }
49
+
50
+ /**
51
+ * Encode a phrase into a 512-dim L2-normalized trigram vector (all-zero on
52
+ * empty input). Deterministic: the same text always yields the same vector
53
+ * (repeat drift == 0) — no asset, no calibration, no network.
54
+ */
55
+ export function embedTrigram512(text: string): Float32Array {
56
+ const width = ENCODER_TRIGRAM_WIDTH;
57
+ const out = new Float32Array(width);
58
+ // Feistel-style double hashing of each trigram into a bucket index + weight.
59
+ for (const tg of trigrams(text)) {
60
+ const h1 = createHash("sha256").update(tg).digest();
61
+ const bucket = h1.readUInt32BE(0) % width;
62
+ const weight = (h1.readUInt32BE(4) / 4294967295) * 2 - 1;
63
+ out[bucket] += weight;
64
+ }
65
+ return l2Normalize(out);
66
+ }
67
+
68
+ /**
69
+ * The 512-dim vector is produced even when the learned asset is absent: this is
70
+ * the mode-B selection point. Returns `{ ok: true, dim, width }` always — there
71
+ * is no asset to consult (task 4 + ENC-FALLBACK-003). Selecting mode B also
72
+ * emits `vector_cortex_encoder_fallback_selected` via the flag-gated reporter
73
+ * (task 5) — the production seam that makes the fallback event live in the
74
+ * runtime, not dead test-only wiring.
75
+ */
76
+ export function selectTrigramBFallback(
77
+ options: { readonly reporter?: EncoderHeadsReporter } = {},
78
+ ): { ok: true; dim: number; width: number; mode: "B" } {
79
+ const reporter = options.reporter ?? createEncoderHeadsReporter();
80
+ const selection = { ok: true as const, mode: "B" as const, dim: ENCODER_TRIGRAM_WIDTH, width: ENCODER_TRIGRAM_WIDTH };
81
+ reporter.fallbackSelected({ mode: selection.mode, dim: selection.dim, width: selection.width });
82
+ return selection;
83
+ }
84
+
85
+ export { l2Normalize };
@@ -0,0 +1,275 @@
1
+ /**
2
+ * vector-cortex/encoder/types.ts — VC2A contract (ModelManifestV1 /
3
+ * EncoderRuntime).
4
+ *
5
+ * The offline encoder runtime owns the learned-asset path (triad mode A: a
6
+ * qualified local ONNX). MODEL_ASSET.md is the normative target. This
7
+ * sprint (VC2A) ships the manifest + verification + shaped-inference contract;
8
+ * the trained weights are packaged in VC2C (MODEL_ASSET: "package.json changes
9
+ * occur only in VC2C"), but the verification, digest-before-load, platform
10
+ * demotion, shape rejection and RSS/latency budget all land here so a later
11
+ * sprint only substitutes real weights.
12
+ *
13
+ * Contract-first (ENGINEERING_PRACTICES §3): this types file is the reviewed
14
+ * gate; implementations import from it; consumers import only types + factory.
15
+ *
16
+ * Pi-agnostic and dependency-free (PREVENT-PI-004 — local assets only, the
17
+ * runtime never fetches). No `any` (PREVENT-011).
18
+ */
19
+
20
+ /** The normative encoder pool; a tokenizer must be digest-covered. */
21
+ export type EncoderPlatform =
22
+ | "linux-x64"
23
+ | "linux-arm64"
24
+ | "darwin-x64"
25
+ | "darwin-arm64"
26
+ | "win32-x64";
27
+
28
+ /** Supported matrix from MODEL_ASSET.md §qualification. */
29
+ export const ENCODER_SUPPORTED_PLATFORMS: readonly EncoderPlatform[] = [
30
+ "linux-x64",
31
+ "linux-arm64",
32
+ "darwin-x64",
33
+ "darwin-arm64",
34
+ "win32-x64",
35
+ ];
36
+
37
+ /** ONNX opset required by the normative v1 target (opset 17). */
38
+ export const ENCODER_OPSET = 17;
39
+ /** Batch must be exactly 1 (single-request inference). */
40
+ export const ENCODER_BATCH = 1;
41
+ /** Maximum accepted token count (WordPiece, deterministic truncation). */
42
+ export const ENCODER_MAX_TOKENS = 512;
43
+ /** Caps the encoder's MARGINAL footprint (bytes) at 150 MiB (MODEL_ASSET
44
+ * §qualification). The budget bounds the encoder's own incremental allocation
45
+ * (a reusable projection buffer + any externally staged asset working set),
46
+ * NOT the whole-process RSS — in a live pi extension the process baseline
47
+ * routinely exceeds 150 MiB, so measuring absolute RSS would make mode A
48
+ * unreachable in production. This is the "RSS" figure the acceptance metric
49
+ * and ENC_FAIL.RSS_BUDGET_EXCEEDED refer to: it is the encoder's marginal
50
+ * footprint, never the process RSS (code-review Q01/Q02). */
51
+ export const ENCODER_RSS_BUDGET_BYTES = 150 * 1024 * 1024;
52
+ /** p95 inference budget in milliseconds (MODEL_ASSET §qualification). */
53
+ export const ENCODER_LATENCY_P95_MS = 40;
54
+ /** Semantic projection head width (MODEL_ASSET: 384 float32 L2-normalized). */
55
+ export const ENCODER_SEMANTIC_WIDTH = 384;
56
+
57
+ /** A digest-pinned asset file declared in the manifest. */
58
+ export interface ManifestAssetFile {
59
+ readonly path: string;
60
+ readonly sha256: string;
61
+ readonly bytes: number;
62
+ }
63
+
64
+ /** The five independent projection heads (MODEL_ASSET §decision record). */
65
+ export interface EncoderHeads {
66
+ readonly semantic: number;
67
+ readonly dependency: number;
68
+ readonly contradiction: number;
69
+ readonly cacheStability: number;
70
+ readonly payloadRouting: number;
71
+ }
72
+
73
+ /**
74
+ * ModelManifestV1 — the digest/opset/platform/input/output contract for the
75
+ * offline encoder asset. Every byte of the ONNX + tokenizer is covered by a
76
+ * SHA-256 recorded here; verification hashes the files BEFORE load.
77
+ */
78
+ export interface ModelManifestV1 {
79
+ readonly schema: "model-manifest-v1";
80
+ readonly modelVersion: string;
81
+ readonly opset: number;
82
+ readonly batch: number;
83
+ readonly maxTokens: number;
84
+ readonly platform: EncoderPlatform;
85
+ readonly hiddenWidth: number;
86
+ readonly semanticWidth: number;
87
+ readonly heads: EncoderHeads;
88
+ readonly onnx: ManifestAssetFile;
89
+ readonly tokenizer: ManifestAssetFile;
90
+ readonly totalBytes: number;
91
+ readonly trainingManifestDigest: string;
92
+ }
93
+
94
+ /** Result of loading the encoder runtime (mode A qualified load). */
95
+ export type EncoderLoadResult =
96
+ | { ok: true; mode: "A"; embeddedBytes: number; rssBytes: number; sessionId: string }
97
+ | { ok: false; mode: "B" | "C"; code: string };
98
+
99
+ /** A single shaped inference request: batch 1, max 512 tokens. */
100
+ export interface EncoderInput {
101
+ readonly tokens: readonly number[];
102
+ }
103
+
104
+ /** Result of a single inference (mode A only; mode B/C do not infer). */
105
+ export type EncoderInferResult =
106
+ | { ok: true; semantic: Float32Array; rssBytes: number; latencyMs: number; shapeError: null }
107
+ | { ok: false; code: string; shapeError: string };
108
+
109
+ /**
110
+ * EncoderRuntime — allocate only after manifest verification; infer only for a
111
+ * verified, qualified asset. `mode` is "A" when the local qualified ONNX is
112
+ * active; "B" when an unsupported platform / missing asset / digest mismatch
113
+ * demoted to the asset-free trigram (no remote fetch); "C" when both A and B
114
+ * initialization failed (lexical fallback).
115
+ */
116
+ export interface EncoderRuntime {
117
+ readonly schema: "encoder-runtime-v1";
118
+ readonly mode: EncoderMode;
119
+ load(assetDir: string): EncoderLoadResult;
120
+ infer(input: EncoderInput): EncoderInferResult;
121
+ }
122
+
123
+ export type EncoderMode = "A" | "B" | "C";
124
+
125
+ /** Exact VC2A failure codes (returned, never thrown across the boundary). */
126
+ export const ENC_FAIL = {
127
+ /** opset != 17. */
128
+ OPSET_INVALID: "ENC_OPSET_INVALID",
129
+ /** batch != 1. */
130
+ BATCH_INVALID: "ENC_BATCH_INVALID",
131
+ /** maxTokens > 512. */
132
+ TOKENS_EXCEEDED: "ENC_TOKENS_EXCEEDED",
133
+ /** input token count > declared maxTokens / 512, or not batch 1. */
134
+ SHAPE_INVALID: "ENC_SHAPE_INVALID",
135
+ /** asset file unreadable (truncated during digest read, allocator failure). */
136
+ ASSET_UNREADABLE: "ENC_ASSET_UNREADABLE",
137
+ /** on-disk digest does not match the manifest (one-byte mutation). */
138
+ DIGEST_MISMATCH: "ENC_DIGEST_MISMATCH",
139
+ /** platform not in the supported matrix (selects trigram B). */
140
+ PLATFORM_UNSUPPORTED: "ENC_PLATFORM_UNSUPPORTED",
141
+ /** manifest missing/invalid (selects trigram B). */
142
+ MANIFEST_INVALID: "ENC_MANIFEST_INVALID",
143
+ /** encoder MARGINAL footprint over the 150 MiB budget (selects trigram B).
144
+ * This is the encoder's own incremental allocation (a reusable projection
145
+ * buffer + any externally staged asset working set), NOT whole-process RSS
146
+ * — see ENCODER_RSS_BUDGET_BYTES. */
147
+ RSS_BUDGET_EXCEEDED: "ENC_RSS_BUDGET_EXCEEDED",
148
+ /** mode C forced by the rollback path (MEGACOMPACT_VC2A=0 / forcedMode "C").
149
+ * Distinct from MANIFEST_INVALID so a non-corrupt, correctly-shaped asset
150
+ * present on disk is not mis-reported as "manifest invalid" when the runtime
151
+ * is simply rolled back to the predecessor path (code-review Q04). */
152
+ ROLLBACK: "ENC_ROLLBACK_ACTIVE",
153
+ } as const;
154
+
155
+ /** The 8 registered VC2A conformance IDs (task 1: "register ENC-001..008"). */
156
+ export const ENC_IDS: readonly string[] = [
157
+ "ENC-001",
158
+ "ENC-002",
159
+ "ENC-003",
160
+ "ENC-004",
161
+ "ENC-005",
162
+ "ENC-006",
163
+ "ENC-007",
164
+ "ENC-008",
165
+ ];
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // VC2B — multi-head encoder (VectorSetV1 / HeadCalibrationDraft).
169
+ // ---------------------------------------------------------------------------
170
+
171
+ /** The five independent projection heads in STABLE order (MODEL_ASSET
172
+ * §decision record; VC2B task 2 "stable order"). The array order is the
173
+ * normative ordering consumed by consumers: semantic, dependency,
174
+ * contradiction, cache-stability, payload-routing. */
175
+ export const ENCODER_HEAD_ORDER = [
176
+ "semantic",
177
+ "dependency",
178
+ "contradiction",
179
+ "cacheStability",
180
+ "payloadRouting",
181
+ ] as const;
182
+
183
+ /** Five head names (stable, matching ENCODER_HEAD_ORDER). */
184
+ export type EncoderHeadName = (typeof ENCODER_HEAD_ORDER)[number];
185
+
186
+ /** The ordered per-head output dimensions: semantic 384, dependency 128,
187
+ * contradiction 128, cacheStability 64, payloadRouting 32 (VC2B task 2). */
188
+ export const ENCODER_HEAD_DIMS: Readonly<Record<EncoderHeadName, number>> = {
189
+ semantic: 384,
190
+ dependency: 128,
191
+ contradiction: 128,
192
+ cacheStability: 64,
193
+ payloadRouting: 32,
194
+ };
195
+
196
+ /** Ordered dimension list matching ENCODER_HEAD_ORDER (384/128/128/64/32). */
197
+ export const ENCODER_HEAD_DIM_ORDER: readonly number[] = ENCODER_HEAD_ORDER.map(
198
+ (h) => ENCODER_HEAD_DIMS[h],
199
+ );
200
+
201
+ /**
202
+ * Weighted training losses per head (MODEL_ASSET §data/losses/calibration):
203
+ * semantic .35, dependency .20, contradiction .20, cache .15, payload .10.
204
+ * These are normative (VC2B task 3: "losses exactly .35/.20/.20/.15/.10").
205
+ */
206
+ export const ENCODER_HEAD_LOSS_WEIGHTS: Readonly<Record<EncoderHeadName, number>> = {
207
+ semantic: 0.35,
208
+ dependency: 0.2,
209
+ contradiction: 0.2,
210
+ cacheStability: 0.15,
211
+ payloadRouting: 0.1,
212
+ };
213
+
214
+ /** Sum of the five loss weights must be exactly 1.0 (asserted in tests). */
215
+ export const ENCODER_HEAD_LOSS_SUM = 1.0;
216
+
217
+ /** Deterministic seed shared by Python/NumPy training and ONNX export (VC2B
218
+ * task 3: "seed ... at 1729"). */
219
+ export const ENCODER_SEED = 1729;
220
+
221
+ /**
222
+ * A single produced per-head vector. `head` names the head (stable order),
223
+ * `dim` is that head's declared dimension, `values` is the L2-normalized
224
+ * Float32Array (all-zero when the raw projection had zero norm — VC2B task 2).
225
+ */
226
+ export interface HeadVector {
227
+ readonly head: EncoderHeadName;
228
+ readonly dim: number;
229
+ readonly values: Float32Array;
230
+ }
231
+
232
+ /**
233
+ * VectorSetV1 — the produced multi-head encoded vectors for one input slice,
234
+ * in STABLE order (semantic → payloadRouting). This is the VC2B-owned contract
235
+ * handed to VC3A ("VC3A receives qualified VectorSet or explicit B/C mode").
236
+ */
237
+ export interface VectorSetV1 {
238
+ readonly schema: "vector-set-v1";
239
+ readonly inputTokens: readonly number[];
240
+ readonly heads: readonly HeadVector[];
241
+ /** True when every head was L2-normalized (or all-zero on zero norm). */
242
+ readonly normalized: boolean;
243
+ }
244
+
245
+ /**
246
+ * HeadCalibrationDraft — the VC2B-owned calibration draft produced BEFORE
247
+ * training/export logic (task 1). Calibration is FITTED on the calibration
248
+ * split only in VC2C (CalibrationV1); this draft records the frozen per-head
249
+ * losses, seed, dimension order, and the corpus/split digests that training
250
+ * must reproduce (task 3).
251
+ */
252
+ export interface HeadCalibrationDraft {
253
+ readonly schema: "head-calibration-draft-v1";
254
+ readonly headOrder: readonly EncoderHeadName[];
255
+ readonly dims: Readonly<Record<EncoderHeadName, number>>;
256
+ readonly losses: Readonly<Record<EncoderHeadName, number>>;
257
+ readonly seed: number;
258
+ /** SHA-256 of the training corpus manifest (task 3, persisted). */
259
+ readonly corpusDigest: string;
260
+ /** SHA-256 of the split assignment (train/calibration/test) by
261
+ * repository+session group (EVALUATION.md §corpus). */
262
+ readonly splitDigest: string;
263
+ }
264
+
265
+ /** The 16 registered VC2B conformance IDs (task 1: "register ENC-009..016"). */
266
+ export const ENC2B_IDS: readonly string[] = [
267
+ "ENC-009",
268
+ "ENC-010",
269
+ "ENC-011",
270
+ "ENC-012",
271
+ "ENC-013",
272
+ "ENC-014",
273
+ "ENC-015",
274
+ "ENC-016",
275
+ ];