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,63 @@
1
+ /**
2
+ * vector-cortex/encoder/emit-vc2b.ts — VC2B observability seam.
3
+ *
4
+ * Owns the two VC2B events (task 5), gated on `MEGACOMPACT_VC2B` so the
5
+ * flag-OFF path emits zero events (mode C parity, byte-identical predecessor):
6
+ *
7
+ * vector_cortex_encoder_heads_emitted — a multi-head VectorSetV1 produced
8
+ * vector_cortex_encoder_fallback_selected — a mode B/C fallback selected
9
+ *
10
+ * No dashboard or API change is necessary for this internal sprint (task 5).
11
+ * Every event is a JSON line with `ts` + `event` (ENGINEERING_PRACTICES §8); the
12
+ * emitters are non-fatal (never break the agent loop). Pi-agnostic, zero network
13
+ * (PREVENT-PI-004), no `any` (PREVENT-011).
14
+ */
15
+ import { VC2B_ENABLED } from "../../config/vector-cortex.js";
16
+ import { Logger } from "../../log.js";
17
+ /** A flag-gated no-op reporter (zero emissions, structural no-op). */
18
+ export const NOOP_VC2B_REPORTER = {
19
+ headsEmitted: () => { },
20
+ fallbackSelected: () => { },
21
+ };
22
+ /**
23
+ * The default emitter: routes both VC2B events into the append-only structured
24
+ * logger (`src/log.ts`) as JSON lines with `ts` + `event`. Supplying `emit:` to
25
+ * `createEncoderHeadsReporter` replaces this with a caller-provided sink (used
26
+ * by tests and downstream consumers). Making the default a REAL producer means a
27
+ * caller that just invokes the producer seam (`encodeOrFallback`, `encodeVectorSet`,
28
+ * `selectTrigramBFallback`, `selectLexicalC`) without injecting an emitter still
29
+ * yields structured telemetry instead of silently dropping every event (task 5,
30
+ * code-review Q01). Best-effort: the logger swallows all I/O errors.
31
+ */
32
+ function defaultEmitFor(logPath) {
33
+ const logger = new Logger(logPath === undefined ? {} : { path: logPath });
34
+ return (event, fields) => {
35
+ logger.info(event, fields);
36
+ };
37
+ }
38
+ /**
39
+ * Flag-gated emit, defaulting to a real logger-backed sink. The returned
40
+ * reporter is itself flag-gated (`VC2B_ENABLED`), so wiring it into a producer
41
+ * seam yields zero emissions when `MEGACOMPACT_VC2B=0` (byte-identical to the
42
+ * predecessor). Pass an explicit `emit` to route elsewhere (tests, downstream
43
+ * consumers); omit it to emit real structured log lines (Q01: the default is a
44
+ * live producer, not a silent no-op). `opts.logPath` only redirects the default
45
+ * sink and is ignored when `emit` is supplied.
46
+ */
47
+ export function createEncoderHeadsReporter(emit, opts = {}) {
48
+ const sink = emit ?? defaultEmitFor(opts.logPath);
49
+ const fire = (event, fields) => {
50
+ if (!VC2B_ENABLED())
51
+ return;
52
+ try {
53
+ sink(event, { ...fields, ts: new Date().toISOString() });
54
+ }
55
+ catch {
56
+ /* non-fatal observability */
57
+ }
58
+ };
59
+ return {
60
+ headsEmitted: (fields) => fire("vector_cortex_encoder_heads_emitted", fields),
61
+ fallbackSelected: (fields) => fire("vector_cortex_encoder_fallback_selected", fields),
62
+ };
63
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * vector-cortex/encoder/emit.ts — VC2A observability seam.
3
+ *
4
+ * Emits the two VC2A structured events, gated on `MEGACOMPACT_VC2A` (mode C
5
+ * parity: flag OFF => zero emissions). Every event is a JSON line with `ts` +
6
+ * `event` (ENGINEERING_PRACTICES §8); the emitters are never fatal on consumer
7
+ * failure (non-fatal observability, never breaks the agent loop).
8
+ *
9
+ * vector_cortex_encoder_asset_verified — a qualified manifest+digest load
10
+ * vector_cortex_encoder_runtime_demoted — a demotion to mode B or C
11
+ *
12
+ * No network, no side effects beyond the supplied emit callback
13
+ * (PREVENT-PI-004 / PREVENT-011).
14
+ */
15
+ import { VC2A_ENABLED } from "../../config/vector-cortex.js";
16
+ /** A flag-gated no-op reporter (zero emissions, default when none injected). */
17
+ export const NOOP_ENCODER_REPORTER = {
18
+ assetVerified: () => { },
19
+ runtimeDemoted: () => { },
20
+ };
21
+ /**
22
+ * Flag-gated emit: no-op when VC2A is off or no emitter is supplied. The
23
+ * returned reporter is itself flag-gated (`VC2A_ENABLED`), so wiring it into a
24
+ * runtime seam yields zero emissions when `MEGACOMPACT_VC2A=0` (byte-identical
25
+ * to the predecessor).
26
+ */
27
+ export function createEncoderReporter(emit) {
28
+ const fire = (event, fields) => {
29
+ if (!VC2A_ENABLED())
30
+ return;
31
+ try {
32
+ emit?.(event, { ...fields, ts: new Date().toISOString() });
33
+ }
34
+ catch {
35
+ /* non-fatal observability */
36
+ }
37
+ };
38
+ return {
39
+ assetVerified: (fields) => fire("vector_cortex_encoder_asset_verified", fields),
40
+ runtimeDemoted: (fields) => fire("vector_cortex_encoder_runtime_demoted", fields),
41
+ };
42
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * vector-cortex/encoder/heads.ts — VC2B multi-head encoder (tasks 1–2).
3
+ *
4
+ * Produces a `VectorSetV1`: five independent L2-normalized projection heads in
5
+ * STABLE order — semantic 384, dependency 128, contradiction 128, cacheStability
6
+ * 64, payloadRouting 32 (MODEL_ASSET §decision record). Each head L2-normalizes
7
+ * its raw projection; a zero-norm projection maps to an all-zero vector (task 2).
8
+ *
9
+ * The raw per-head projection is a deterministic seeded compression of the input
10
+ * token sequence (seeded by `ENCODER_SEED` and the head's stable index), which
11
+ * mirrors the VC2A `projectSemantic` placeholder pattern: the contract, shape
12
+ * gating, normalization, zero-norm mapping, ordering and loss/seed constants are
13
+ * all normative here; real trained weights are substituted in VC2C. This keeps
14
+ * the mode-A multi-head path fully testable end-to-end today with zero network.
15
+ *
16
+ * The VC2B emit seam (task 5) is wired: producing a VectorSetV1 emits
17
+ * `vector_cortex_encoder_heads_emitted`; selecting a mode B/C fallback emits
18
+ * `vector_cortex_encoder_fallback_selected` — both gated on MEGACOMPACT_VC2B.
19
+ *
20
+ * Pi-agnostic, zero network (PREVENT-PI-004), no `any` (PREVENT-011).
21
+ */
22
+ import { ENCODER_HEAD_DIMS, ENCODER_HEAD_ORDER, ENCODER_HEAD_LOSS_WEIGHTS, ENCODER_HEAD_LOSS_SUM, ENCODER_SEED, } from "./types.js";
23
+ import { createEncoderHeadsReporter, NOOP_VC2B_REPORTER, } from "./emit-vc2b.js";
24
+ /** The stable head index of a head name (its position in ENCODER_HEAD_ORDER). */
25
+ const HEAD_INDEX = {
26
+ semantic: 0,
27
+ dependency: 1,
28
+ contradiction: 2,
29
+ cacheStability: 3,
30
+ payloadRouting: 4,
31
+ };
32
+ /** Deterministic 32-bit LCG step (matches runtime.ts projectSemantic). */
33
+ function nextState(state) {
34
+ return (state * 1664525 + 1013904223) >>> 0;
35
+ }
36
+ /**
37
+ * L2-normalize a float vector in place semantics (returns a new Float32Array).
38
+ * A zero-norm (or empty) input maps to an all-zero vector of the same length
39
+ * (task 2: "mapping zero norm to an all-zero vector"). All finite.
40
+ */
41
+ export function l2Normalize(values) {
42
+ const out = new Float32Array(values.length);
43
+ let sum = 0;
44
+ for (const v of values)
45
+ sum += v * v;
46
+ const norm = Math.sqrt(sum);
47
+ if (!(norm > 0))
48
+ return out; // zero norm -> all-zero
49
+ for (let i = 0; i < values.length; i++)
50
+ out[i] = values[i] / norm;
51
+ return out;
52
+ }
53
+ /** L2 norm of a Float32Array (0 for empty/all-zero). */
54
+ export function l2Norm(values) {
55
+ let sum = 0;
56
+ for (const v of values)
57
+ sum += v * v;
58
+ return Math.sqrt(sum);
59
+ }
60
+ /** A deterministic per-head projection over the token sequence, pre-normalization. */
61
+ function projectRaw(head, tokens, seed) {
62
+ const dim = ENCODER_HEAD_DIMS[head];
63
+ const out = new Float32Array(dim);
64
+ // An EMPTY token sequence has no signal: the raw projection is the zero vector,
65
+ // so after L2 normalization it maps to the all-zero vector (task 2: "mapping
66
+ // zero norm to an all-zero vector"; ENC-ZERO-002). This keeps empty input
67
+ // finite and zero-norm instead of seeding spurious unit-norm noise.
68
+ if (tokens.length === 0)
69
+ return out;
70
+ // Mix the stable head index + ENCODER_SEED + seed into a per-head state so
71
+ // each head is a distinct independent projection (failure-triad independence).
72
+ let state = (((ENCODER_SEED ^ HEAD_INDEX[head]) >>> 0) ^ (seed >>> 0)) ^ 0x9e3779b9;
73
+ for (const t of tokens)
74
+ state = nextState(state ^ ((t >>> 0) * 2654435761));
75
+ for (let i = 0; i < dim; i++) {
76
+ state = nextState(state ^ seed);
77
+ out[i] = (state / 4294967296) * 2 - 1;
78
+ }
79
+ return out;
80
+ }
81
+ /**
82
+ * Compute one head's L2-normalized vector (all-zero on zero norm) for a token
83
+ * sequence. Deterministic for a given seed (repeat drift == 0).
84
+ */
85
+ export function projectHead(head, tokens, seed = ENCODER_SEED) {
86
+ const raw = projectRaw(head, tokens, seed);
87
+ return { head, dim: ENCODER_HEAD_DIMS[head], values: l2Normalize(raw) };
88
+ }
89
+ /**
90
+ * Encode a token sequence into a `VectorSetV1`: the five heads in stable order,
91
+ * each L2-normalized (all-zero on zero norm). Emits `heads_emitted` via the
92
+ * reporter (non-fatal, flag-gated). Deterministic for a given seed.
93
+ */
94
+ export function encodeVectorSet(tokens, options = {}) {
95
+ const seed = options.seed ?? ENCODER_SEED;
96
+ const reporter = options.reporter ?? createEncoderHeadsReporter();
97
+ const heads = ENCODER_HEAD_ORDER.map((h) => projectHead(h, tokens, seed));
98
+ reporter.headsEmitted({
99
+ heads: heads.length,
100
+ dims: heads.map((h) => h.dim).join("/"),
101
+ normalized: true,
102
+ tokens: tokens.length,
103
+ });
104
+ return { schema: "vector-set-v1", inputTokens: [...tokens], heads, normalized: true };
105
+ }
106
+ /**
107
+ * The per-head loss weights (must sum to ENCODER_HEAD_LOSS_SUM exactly).
108
+ * Exposed for training/tests to assert the normative .35/.20/.20/.15/.10 split.
109
+ */
110
+ export function headLossWeights() {
111
+ return { ...ENCODER_HEAD_LOSS_WEIGHTS };
112
+ }
113
+ export { ENCODER_HEAD_ORDER, ENCODER_HEAD_DIMS, ENCODER_HEAD_LOSS_SUM, ENCODER_SEED, NOOP_VC2B_REPORTER };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * vector-cortex/encoder/lexical.ts — VC2B mode C: token/phrase lexical encoder.
3
+ *
4
+ * Lexical C is a token/phrase lexical feature generator used when both mode A
5
+ * (learned asset) and mode B (trigram) are unavailable or fail. It is continuity,
6
+ * NOT semantic completeness: C operates on exact current tokens/phrases only and
7
+ * MUST state that it has lost old semantic context (task 4 + TRIAD_RESILIENCE.
8
+ * "C is continuity, not semantic completeness: it may omit old context and must
9
+ * report that limitation").
10
+ *
11
+ * C never imports the learned asset or learned calibration (task 4): it is a
12
+ * pure token/phrase lexical projection (token counts + phrase hashes) computed
13
+ * from the exact input. It is independently implemented from B (which hashes
14
+ * byte-level trigrams) — C works at the token/phrase level, B at the byte-ngram
15
+ * level, so the two share no algorithm.
16
+ *
17
+ * Authority outage freezes derived high-water: C never advances any derived
18
+ * frontier; it is purely a local reconstruction from the exact present tokens.
19
+ *
20
+ * Pi-agnostic, zero network (PREVENT-PI-004), no `any` (PREVENT-011).
21
+ */
22
+ import { createHash } from "node:crypto";
23
+ import { l2Normalize } from "./heads.js";
24
+ import { createEncoderHeadsReporter, } from "./emit-vc2b.js";
25
+ /** Fixed output width of lexical C. */
26
+ export const ENCODER_LEXICAL_WIDTH = 256;
27
+ /** The documented limitation lexical C reports (continuity, not semantics). */
28
+ export const ENCODER_LEXICAL_LIMITATION = "lexical C: token/phrase-level continuity only; old semantic context is omitted";
29
+ export function tokenizeLexical(text) {
30
+ // Split into lowercase token/phrase units on non-alphanumeric boundaries.
31
+ return text.toLowerCase().split(/[^a-z0-9_]+/).filter((t) => t.length > 0);
32
+ }
33
+ /**
34
+ * Normalize a single token/phrase unit the same way `tokenizeLexical` does for
35
+ * the string form: lowercase and strip leading/trailing non-alphanumeric runs.
36
+ * Applied to array-form tokens so both accepted input forms of `embedLexical`
37
+ * hash identical conceptual content to identical buckets, regardless of how the
38
+ * caller chose to pass it (code-review Q03).
39
+ */
40
+ function normalizeToken(tok) {
41
+ return tok.toLowerCase().split(/[^a-z0-9_]+/).join("");
42
+ }
43
+ /**
44
+ * Resolve either accepted input form to a normalized token sequence: the string
45
+ * form routes through `tokenizeLexical` (split on non-alphanumeric boundaries,
46
+ * fragmented tokens discarded); the array form applies the same per-token
47
+ * lowercase/strip normalization and drops tokens that normalize to empty. Both
48
+ * paths therefore agree on hash buckets for the same conceptual content.
49
+ */
50
+ function resolveTokens(tokensOrText) {
51
+ if (typeof tokensOrText === "string")
52
+ return tokenizeLexical(tokensOrText);
53
+ const out = [];
54
+ for (const raw of tokensOrText) {
55
+ const norm = normalizeToken(raw);
56
+ if (norm.length > 0)
57
+ out.push(norm);
58
+ }
59
+ return out;
60
+ }
61
+ /**
62
+ * Encode a token/phrase sequence into a 256-dim L2-normalized lexical vector
63
+ * (all-zero on empty input). Features: exact token count + token id-hash sums +
64
+ * phrase-adjacency hashes. Deterministic (repeat drift == 0). Pure local compute.
65
+ */
66
+ export function embedLexical(tokensOrText) {
67
+ const width = ENCODER_LEXICAL_WIDTH;
68
+ const out = new Float32Array(width);
69
+ const tokens = resolveTokens(tokensOrText);
70
+ for (let i = 0; i < tokens.length; i++) {
71
+ const tok = tokens[i];
72
+ const h = createHash("sha256").update(`t:${tok}`).digest();
73
+ const bucket = h.readUInt32BE(0) % width;
74
+ const weight = (h.readUInt32BE(4) / 4294967295) * 2 - 1;
75
+ out[bucket] += weight;
76
+ // Phrase adjacency: bigram hash blended in so semantic-free ordering matters.
77
+ if (i > 0) {
78
+ const pair = createHash("sha256").update(`p:${tokens[i - 1]}:${tok}`).digest();
79
+ const pb = pair.readUInt32BE(0) % width;
80
+ const pw = (pair.readUInt32BE(4) / 4294967295) * 2 - 1;
81
+ out[pb] += pw;
82
+ }
83
+ }
84
+ return l2Normalize(out);
85
+ }
86
+ /** The documented limitation string, surfaced when lexical C is selected. */
87
+ export function selectLexicalC(options = {}) {
88
+ const reporter = options.reporter ?? createEncoderHeadsReporter();
89
+ const selection = {
90
+ ok: true,
91
+ mode: "C",
92
+ dim: ENCODER_LEXICAL_WIDTH,
93
+ width: ENCODER_LEXICAL_WIDTH,
94
+ limitation: ENCODER_LEXICAL_LIMITATION,
95
+ };
96
+ reporter.fallbackSelected({
97
+ mode: selection.mode,
98
+ dim: selection.dim,
99
+ width: selection.width,
100
+ limitation: selection.limitation,
101
+ });
102
+ return selection;
103
+ }
104
+ export { l2Normalize };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * vector-cortex/encoder/router.ts — VC2B encode-or-fallback router (S2).
3
+ *
4
+ * The single production seam that catches a real VC2A `load()` failure and
5
+ * hands off to the independently initialized VC2B fallbacks:
6
+ *
7
+ * - mode A: the learned multi-head projection — an EncoderRuntime that
8
+ * verifies+loads a local qualified ONNX (VC2A) plus `encodeVectorSet`
9
+ * producing VectorSetV1, emitting `vector_cortex_encoder_heads_emitted`;
10
+ * - when that `load()` fails for ANY reason (removed model ->
11
+ * ENC_ASSET_UNREADABLE, digest mismatch -> ENC_DIGEST_MISMATCH, missing
12
+ * manifest -> ENC_MANIFEST_INVALID, unsupported platform, RSS over budget,
13
+ * ...) the router catches the real failure and selects the independently
14
+ * initialized asset-free trigram B (`selectTrigramBFallback`) — or lexical C
15
+ * when the runtime reports B-unavailable / the caller forces mode C. The
16
+ * `vector_cortex_encoder_fallback_selected` event fires from the REAL
17
+ * producer seam, not test wiring (task 5 + code-review S1).
18
+ *
19
+ * Best-effort and non-fatal: every branch returns an explicit verdict and never
20
+ * throws across the boundary into the agent loop. Flag-OFF parity: with
21
+ * `MEGACOMPACT_VC2B=0` the VC2B reporter is a no-op (zero emissions), and the
22
+ * mode-A path is governed by the VC2A runtime itself — the router only adds the
23
+ * fallback handoff and changes no producer bytes.
24
+ *
25
+ * Pi-agnostic, zero network (PREVENT-PI-004), no `any` (PREVENT-011).
26
+ */
27
+ import { createEncoderRuntime } from "./runtime.js";
28
+ import { encodeVectorSet } from "./heads.js";
29
+ import { embedTrigram512, selectTrigramBFallback } from "./trigram.js";
30
+ import { embedLexical, selectLexicalC } from "./lexical.js";
31
+ import { createEncoderHeadsReporter } from "./emit-vc2b.js";
32
+ import { ENC_FAIL, } from "./types.js";
33
+ /** Deterministic text derived from an int token sequence so the asset-free
34
+ * fallback producers operate on the same authority the learned path encoded. */
35
+ function textFromTokens(tokens) {
36
+ return tokens.join("-");
37
+ }
38
+ /**
39
+ * Try to produce an encoding for an input token sequence. Mode A: verify+load
40
+ * the local learned asset via the EncoderRuntime; on a real `load()` failure
41
+ * (removed model, digest mismatch, missing manifest, ...) the router catches it
42
+ * and selects the independently initialized trigram B or lexical C, emitting
43
+ * `vector_cortex_encoder_fallback_selected` (and `heads_emitted` when A wins).
44
+ */
45
+ export function encodeOrFallback(input, assetDir, options = {}) {
46
+ const reporter = options.reporter ?? createEncoderHeadsReporter();
47
+ const tokens = Array.isArray(input?.tokens) ? input.tokens : [];
48
+ const runtime = options.runtime ?? createEncoderRuntime();
49
+ // A caller that explicitly forces a fallback mode wins even over the empty-
50
+ // input degenerate case: the forced-mode contract must hold for ANY input, so
51
+ // handle forceFallback before the empty-input selection below (Q02). A forced
52
+ // B/C is NOT a rollback and NOT a demotion — it is an intentional selection
53
+ // for capacity/testing — so the verdict carries `code: null`, never
54
+ // ENC_FAIL.ROLLBACK, so a consumer that reads `verdict.code` as "what
55
+ // triggered the fallback" won't misread a deliberately forced mode as a
56
+ // rollback and take rollback-specific action (code-review Q04).
57
+ if (options.forceFallback !== undefined) {
58
+ if (options.forceFallback === "C") {
59
+ const sel = selectLexicalC({ reporter });
60
+ const vector = embedLexical(textFromTokens(tokens));
61
+ return { ok: true, mode: "C", vector, width: sel.width, limitation: sel.limitation, code: null };
62
+ }
63
+ const sel = selectTrigramBFallback({ reporter });
64
+ const vector = embedTrigram512(textFromTokens(tokens));
65
+ return { ok: true, mode: "B", vector, width: sel.width, limitation: null, code: null };
66
+ }
67
+ // Empty input yields the asset-free fallback (finite, deterministic zero
68
+ // vector, ENC-ZERO-002). This is legitimate degenerate behavior, NOT a shape
69
+ // failure, so the verdict carries no failure code (code === null) — a consumer
70
+ // that interprets `code` as "what went wrong" must not misread a valid all-zero
71
+ // B vector as a shape rejection (Q05). Only reached when no mode is forced, so
72
+ // a forced B/C is never subverted by empty tokens.
73
+ if (tokens.length === 0) {
74
+ const sel = selectTrigramBFallback({ reporter });
75
+ const vector = embedTrigram512("");
76
+ return { ok: true, mode: "B", vector, width: sel.width, limitation: null, code: null };
77
+ }
78
+ // Mode A attempt — reached only when no mode is forced (the forceFallback
79
+ // guard at the top already returned), so `options.forceFallback` is undefined here.
80
+ const loaded = runtime.load(assetDir);
81
+ if (!loaded.ok) {
82
+ return fallbackFromLoad(loaded, reporter, tokens);
83
+ }
84
+ // Q01/Q03: a qualified mode-A load is not enough — the verified per-manifest
85
+ // token capacity (maxTokens, <= global 512) must also be enforced before we
86
+ // produce a VectorSetV1. run inference over the input; an over-cap sequence
87
+ // (e.g. 100 tokens against a verified maxTokens=64 manifest) or an
88
+ // over-budget inference is rejected here and routed to the B/C fallback with
89
+ // the real failure code, rather than silently emitting an ok:true mode-A
90
+ // set whose inputTokens breach the model's declared capacity.
91
+ const inferred = runtime.infer({ tokens });
92
+ if (!inferred.ok) {
93
+ return fallbackFromLoad({ ok: false, mode: "B", code: inferred.code }, reporter, tokens);
94
+ }
95
+ const vectorSet = encodeVectorSet(tokens, { reporter, seed: options.seed });
96
+ return { ok: true, mode: "A", vectorSet, code: null };
97
+ }
98
+ /**
99
+ * Catch a (real or forced) A load failure (ok === false only) and select the
100
+ * B/C fallback that emits `vector_cortex_encoder_fallback_selected` from the
101
+ * production seam. The parameter is narrowed to the failed-load variant because
102
+ * the router hands off here only on a non-A/failed load — a qualified mode-A
103
+ * success never reaches this function (Q04).
104
+ */
105
+ function fallbackFromLoad(loaded, reporter, tokens) {
106
+ if (loaded.mode === "C") {
107
+ const sel = selectLexicalC({ reporter });
108
+ const vector = embedLexical(textFromTokens(tokens));
109
+ return { ok: true, mode: "C", vector, width: sel.width, limitation: sel.limitation, code: loaded.code };
110
+ }
111
+ const sel = selectTrigramBFallback({ reporter });
112
+ const vector = embedTrigram512(textFromTokens(tokens));
113
+ return { ok: true, mode: "B", vector, width: sel.width, limitation: null, code: loaded.code };
114
+ }
115
+ export { ENC_FAIL };
@@ -0,0 +1,228 @@
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
+ import { detectPlatform, readEncoderManifest, verifyEncoderAsset, } from "./asset.js";
47
+ import { createEncoderReporter } from "./emit.js";
48
+ import { VC2A_ENABLED } from "../../config/vector-cortex.js";
49
+ import { ENC_FAIL, ENCODER_MAX_TOKENS, ENCODER_RSS_BUDGET_BYTES, ENCODER_SEMANTIC_WIDTH, } from "./types.js";
50
+ /** Bytes a single encoder-owned projection buffer commits to the marginal
51
+ * footprint (Float32Array, 4 bytes per element). */
52
+ const SEMANTIC_BUFFER_BYTES = ENCODER_SEMANTIC_WIDTH * 4;
53
+ const DEFAULT_HOST = {
54
+ allocatedBytes: () => 0,
55
+ allocatorFails: () => false,
56
+ nowMs: () => Date.now(),
57
+ };
58
+ function mergeHost(partial) {
59
+ return { ...DEFAULT_HOST, ...partial };
60
+ }
61
+ /** A deterministic seeded projection so the mode-A inference path is testable
62
+ * end-to-end without onnxruntime (real weights + execution are VC2C). */
63
+ function projectSemantic(seed, n) {
64
+ const out = new Float32Array(n);
65
+ let state = (seed >>> 0) ^ 0x9e3779b9;
66
+ let sum = 0;
67
+ for (let i = 0; i < n; i++) {
68
+ state = (state * 1664525 + 1013904223) >>> 0;
69
+ out[i] = (state / 4294967296) * 2 - 1;
70
+ sum += out[i] * out[i];
71
+ }
72
+ const norm = Math.sqrt(sum) || 1;
73
+ for (let i = 0; i < n; i++)
74
+ out[i] = out[i] / norm;
75
+ return out;
76
+ }
77
+ /** Deterministic token seed derived from the verified asset bytes count. */
78
+ function seedFromBytes(embeddedBytes) {
79
+ return (embeddedBytes * 2654435761) >>> 0;
80
+ }
81
+ function modeLabel(mode) {
82
+ return mode === "A" ? "qualified-onnx" : mode === "B" ? "trigram" : "lexical";
83
+ }
84
+ export function createEncoderRuntime(options = {}) {
85
+ const reporter = options.reporter ?? createEncoderReporter();
86
+ const host = mergeHost(options.host);
87
+ const forced = options.forcedMode;
88
+ const plat = options.platform ?? detectPlatform;
89
+ // Q04: rollback contract — MEGACOMPACT_VC2A=0 selects mode C (byte-identical
90
+ // to the predecessor: no asset read/verify, no learned infer). An explicit
91
+ // forcedMode "C" takes precedence; otherwise the flag gates the default.
92
+ const rolledBack = forced === "C" || !VC2A_ENABLED();
93
+ let mode = rolledBack ? "C" : "C";
94
+ let embeddedBytes = 0;
95
+ let verified = false;
96
+ /** Per-manifest token capacity (<= 512) from the verified asset; enforced at
97
+ * inference (Q03). Defaults to the global ceiling before a load. */
98
+ let maxTokens = ENCODER_MAX_TOKENS;
99
+ /** Bytes this runtime itself has allocated. This models a SINGLE reusable
100
+ * 384-float projection buffer: the first inference allocates it (1536
101
+ * bytes), every later inference reuses it, so the counter is capped at
102
+ * `SEMANTIC_BUFFER_BYTES` and never grows without bound (Q01). Combined
103
+ * with `host.allocatedBytes()` it drives the 150 MiB marginal budget (Q02),
104
+ * never whole-process RSS. */
105
+ let selfAllocated = 0;
106
+ /** The encoder's marginal working-set footprint, in bytes. */
107
+ const footprint = () => selfAllocated + host.allocatedBytes();
108
+ const demoteTo = (rmode, code) => {
109
+ mode = rmode;
110
+ verified = false;
111
+ reporter.runtimeDemoted({ reason: code, mode: rmode, platform: plat()?.toString() ?? "unsupported" });
112
+ };
113
+ const runtime = {
114
+ schema: "encoder-runtime-v1",
115
+ // Live getter so `mode` always reflects the latest load/demote outcome
116
+ // (a plain property would freeze at its construction-time value forever).
117
+ get mode() {
118
+ return mode;
119
+ },
120
+ load(assetDir) {
121
+ if (rolledBack) {
122
+ // Rollback path (forcedMode "C" or MEGACOMPACT_VC2A=0): mode C restores
123
+ // the prior derived pointer; no asset is read or verified; no emission.
124
+ // Q04: report the rollback with its own code, not MANIFEST_INVALID, so a
125
+ // correctly-shaped, digest-correct asset is not mis-read as corrupted.
126
+ mode = "C";
127
+ verified = false;
128
+ return { ok: false, mode: "C", code: ENC_FAIL.ROLLBACK };
129
+ }
130
+ // Attempt A: verify the local qualified ONNX asset (never a remote fetch).
131
+ const manifest = readEncoderManifest(assetDir);
132
+ let verify;
133
+ if (manifest === null) {
134
+ verify = { ok: false, code: ENC_FAIL.MANIFEST_INVALID };
135
+ }
136
+ else {
137
+ verify = verifyEncoderAsset(assetDir, manifest, plat());
138
+ }
139
+ if (!verify.ok) {
140
+ // A failed -> B, unless B init itself fails (allocator) -> C.
141
+ if (host.allocatorFails()) {
142
+ demoteTo("C", ENC_FAIL.ASSET_UNREADABLE);
143
+ return { ok: false, mode: "C", code: ENC_FAIL.ASSET_UNREADABLE };
144
+ }
145
+ demoteTo("B", verify.code);
146
+ return { ok: false, mode: "B", code: verify.code };
147
+ }
148
+ // Allocate only after verification (task 3). Simulate allocator failure.
149
+ if (host.allocatorFails()) {
150
+ demoteTo("B", ENC_FAIL.ASSET_UNREADABLE);
151
+ return { ok: false, mode: "B", code: ENC_FAIL.ASSET_UNREADABLE };
152
+ }
153
+ // Cap the encoder's MARGINAL footprint at 150 MiB (task 3, Q01). This
154
+ // bounds the encoder's incremental allocation, so a healthy process with
155
+ // a large baseline RSS still reaches mode A.
156
+ if (footprint() > ENCODER_RSS_BUDGET_BYTES) {
157
+ demoteTo("B", ENC_FAIL.RSS_BUDGET_EXCEEDED);
158
+ return { ok: false, mode: "B", code: ENC_FAIL.RSS_BUDGET_EXCEEDED };
159
+ }
160
+ embeddedBytes = verify.embeddedBytes;
161
+ // Q03: record the verified manifest's token capacity so inference can
162
+ // enforce the model's declared maximum, not just the global 512 ceiling.
163
+ maxTokens = verify.maxTokens;
164
+ verified = true;
165
+ mode = "A";
166
+ reporter.assetVerified({
167
+ mode: "A",
168
+ embeddedBytes: verify.embeddedBytes,
169
+ onnxDigest: verify.onnxDigest.slice(0, 12),
170
+ });
171
+ return {
172
+ ok: true,
173
+ mode: "A",
174
+ embeddedBytes: verify.embeddedBytes,
175
+ rssBytes: footprint(),
176
+ sessionId: `enc-${seedFromBytes(verify.embeddedBytes).toString(16)}`,
177
+ };
178
+ },
179
+ infer(input) {
180
+ if (!verified || mode !== "A") {
181
+ // Only batch1/max512 verified assets reach inference (mode B/C do not).
182
+ return {
183
+ ok: false,
184
+ code: ENC_FAIL.SHAPE_INVALID,
185
+ shapeError: "no verified learned asset; mode is " + modeLabel(mode),
186
+ };
187
+ }
188
+ if (!input || !Array.isArray(input.tokens)) {
189
+ return { ok: false, code: ENC_FAIL.SHAPE_INVALID, shapeError: "missing tokens array" };
190
+ }
191
+ const n = input.tokens.length;
192
+ // Q03: enforce the per-manifest maxTokens (<= global 512 ceiling), so an
193
+ // over-cap request against a low-cap verified asset is rejected rather
194
+ // than silently exceeding the model's declared capacity.
195
+ if (n < 1 || n > maxTokens) {
196
+ return {
197
+ ok: false,
198
+ code: ENC_FAIL.SHAPE_INVALID,
199
+ shapeError: `token count ${n} outside 1..${maxTokens} (manifest cap)`,
200
+ };
201
+ }
202
+ // Q03: cap-before-allocation on the inference path too. Check the
203
+ // marginal footprint BEFORE allocating the projection buffer; an
204
+ // over-budget inference demotes to mode B consistently with load() (the
205
+ // ENC_FAIL.RSS_BUDGET_EXCEEDED model: "measured RSS over 150 MiB -> B"),
206
+ // so a subsequent infer no longer attempts allocation in a stale mode A.
207
+ if (footprint() > ENCODER_RSS_BUDGET_BYTES) {
208
+ demoteTo("B", ENC_FAIL.RSS_BUDGET_EXCEEDED);
209
+ return {
210
+ ok: false,
211
+ code: ENC_FAIL.RSS_BUDGET_EXCEEDED,
212
+ shapeError: "encoder footprint over budget during inference",
213
+ };
214
+ }
215
+ const start = host.nowMs();
216
+ // Batch is always 1 (single request); shape is (1, n) for n in 1..maxTokens.
217
+ const semantic = projectSemantic(seedFromBytes(embeddedBytes) ^ n, ENCODER_SEMANTIC_WIDTH);
218
+ // Q01: the projection buffer is a single reusable 384-float array; the
219
+ // marginal footprint is a fixed SEMANTIC_BUFFER_BYTES once it exists, so
220
+ // selfAllocated is SET (never accumulated) — bounded regardless of how
221
+ // many inferences run on a long-lived runtime.
222
+ selfAllocated = SEMANTIC_BUFFER_BYTES;
223
+ const latencyMs = host.nowMs() - start;
224
+ return { ok: true, semantic, rssBytes: footprint(), latencyMs, shapeError: null };
225
+ },
226
+ };
227
+ return runtime;
228
+ }