pi-mega-compact 0.20.70 → 0.20.72

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 (41) hide show
  1. package/dist/extensions/dashboard-server/routes-setup-cortex.js +7 -1
  2. package/dist/extensions/dashboard-server/routes-vector-cortex-crystals.js +45 -30
  3. package/dist/extensions/dashboard-server/routes-vector-cortex-diagnostics.js +50 -29
  4. package/dist/extensions/dashboard-server/routes-vector-cortex-economics.js +33 -20
  5. package/dist/extensions/dashboard-server/routes-vector-cortex-helpers.js +54 -0
  6. package/dist/extensions/dashboard-server/routes-vector-cortex-policy.js +33 -16
  7. package/dist/src/vector-cortex/cache/store.js +25 -2
  8. package/dist/src/vector-cortex/encoder/asset.js +34 -2
  9. package/dist/src/vector-cortex/encoder/router.js +31 -3
  10. package/dist/src/vector-cortex/livewire/livewire-live.js +163 -0
  11. package/dist/src/vector-cortex/livewire/livewire-registry.js +73 -0
  12. package/dist/src/vector-cortex/livewire/livewire-runtime.js +107 -0
  13. package/dist/src/vector-cortex/livewire/livewire-snapshot.js +101 -0
  14. package/dist/src/vector-cortex/livewire/livewire-types.js +19 -0
  15. package/dist/src/vector-cortex/provider/registry.js +44 -12
  16. package/dist/src/vector-cortex/setup-cortex-blockers-compute.js +43 -7
  17. package/dist/vector-cortex/cache/store.js +25 -2
  18. package/dist/vector-cortex/encoder/asset.js +34 -2
  19. package/dist/vector-cortex/encoder/router.js +31 -3
  20. package/dist/vector-cortex/provider/registry.js +44 -12
  21. package/dist/vector-cortex/setup-cortex-blockers-compute.js +43 -7
  22. package/extensions/dashboard-server/routes-setup-cortex.ts +8 -1
  23. package/extensions/dashboard-server/routes-vector-cortex-crystals.ts +46 -30
  24. package/extensions/dashboard-server/routes-vector-cortex-diagnostics.ts +52 -29
  25. package/extensions/dashboard-server/routes-vector-cortex-economics.ts +35 -20
  26. package/extensions/dashboard-server/routes-vector-cortex-helpers.ts +84 -0
  27. package/extensions/dashboard-server/routes-vector-cortex-policy.ts +35 -16
  28. package/package.json +1 -1
  29. package/src/vector-cortex/cache/store.ts +26 -2
  30. package/src/vector-cortex/encoder/asset.ts +46 -3
  31. package/src/vector-cortex/encoder/router.ts +56 -2
  32. package/src/vector-cortex/encoder/types.ts +3 -0
  33. package/src/vector-cortex/livewire/livewire-live.ts +223 -0
  34. package/src/vector-cortex/livewire/livewire-registry.ts +91 -0
  35. package/src/vector-cortex/livewire/livewire-runtime.ts +117 -0
  36. package/src/vector-cortex/livewire/livewire-snapshot.ts +108 -0
  37. package/src/vector-cortex/livewire/livewire-types.ts +84 -0
  38. package/src/vector-cortex/provider/economics.ts +9 -31
  39. package/src/vector-cortex/provider/registry.ts +82 -11
  40. package/src/vector-cortex/provider/types.ts +37 -0
  41. package/src/vector-cortex/setup-cortex-blockers-compute.ts +48 -7
@@ -28,18 +28,25 @@ import type { RouteContext } from "./routes-core.js";
28
28
  import { VC7B_ENABLED } from "../../src/config.js";
29
29
  import { sendJson } from "./routes-vector-cortex-shared.js";
30
30
  import { deriveVcStatus } from "./vc-status.js";
31
+ import { readLivewireSnapshot } from "../../src/vector-cortex/livewire/livewire-registry.js";
31
32
  import type { VectorCortexEconomicsView } from "./api-contracts/vector-cortex-economics.js";
32
33
 
34
+ // The flag-off deferred reason, kept byte-identical to the pre-LIVEWIRE
35
+ // predecessor (VC7B: `economics_not_computed_v0_20_23`).
36
+ const DEFERRED_REASON = "economics_not_computed_v0_20_23";
37
+
33
38
  /**
34
39
  * Reader-only GET /api/vector-cortex/cache-economics (VC7B).
35
40
  *
36
- * Counts, profile tallies, and ECON_* codes only a static reader-only
37
- * aggregate seam with the same shape as the VC7A crystals handler.
41
+ * Counts, profile tallies, and ECON_* codes only. With the flag ON it surfaces
42
+ * the LIVEWIRE-provided profile economics tallies + the runtime `computed` bit;
43
+ * with the flag OFF it returns the byte-identical legacy deferred view (mode C,
44
+ * deferredReason present).
38
45
  */
39
46
  export function handleVectorCortexEconomics(
40
47
  req: IncomingMessage,
41
48
  res: ServerResponse,
42
- _ctx: RouteContext,
49
+ ctx: RouteContext,
43
50
  ): boolean {
44
51
  const url = req.url ?? "";
45
52
  const path = url.split("?")[0] ?? url;
@@ -50,25 +57,33 @@ export function handleVectorCortexEconomics(
50
57
  }
51
58
 
52
59
  const enabled = VC7B_ENABLED();
53
- // Flag-off routes to mode C: with VC7B off no cache economics are served, which
54
- // is exactly the spec's "economics bypass" outcome. Reporting A (cached render
55
- // priced) or B (fresh render priced) would imply an economics path that is not
56
- // wired at all. Mirrors how VC6C/VC7A OFF views report the mode they take.
57
- const mode: "A" | "B" | "C" = enabled ? "A" : "C";
60
+ if (!enabled) {
61
+ // Flag-off parity: byte-identical to the predecessor (mode C + deferred).
62
+ const body: VectorCortexEconomicsView = {
63
+ enabled: false,
64
+ mode: "C",
65
+ profileCount: 0,
66
+ provenExclusions: 0,
67
+ unprovenExclusions: 0,
68
+ lastFailure: null,
69
+ updatedAt: new Date().toISOString(),
70
+ deferredReason: DEFERRED_REASON,
71
+ status: deriveVcStatus({ enabled: false, hasData: false }),
72
+ };
73
+ sendJson(res, 200, body);
74
+ return true;
75
+ }
76
+
77
+ const econ = readLivewireSnapshot(ctx.stateDir).economics;
58
78
  const body: VectorCortexEconomicsView = {
59
- enabled,
60
- mode,
61
- profileCount: 0,
62
- provenExclusions: 0,
63
- unprovenExclusions: 0,
64
- lastFailure: null,
79
+ enabled: true,
80
+ mode: "A",
81
+ profileCount: econ.profileCount,
82
+ provenExclusions: econ.provenExclusions,
83
+ unprovenExclusions: econ.unprovenExclusions,
84
+ lastFailure: econ.lastFailure,
65
85
  updatedAt: new Date().toISOString(),
66
- deferredReason: "economics_not_computed_v0_20_23",
67
- status: deriveVcStatus({
68
- enabled,
69
- deferredReason: "economics_not_computed_v0_20_23",
70
- hasData: false,
71
- }),
86
+ status: deriveVcStatus({ enabled: true, hasData: econ.computed }),
72
87
  };
73
88
  sendJson(res, 200, body);
74
89
  return true;
@@ -108,3 +108,87 @@ export async function seedEval(dir: string): Promise<void> {
108
108
  { session: "s1", seq: 4, event: "encode", value: 12, unit: "ms", mode: "A" },
109
109
  ]);
110
110
  }
111
+
112
+ /**
113
+ * Seed a LIVEWIRE aggregate snapshot directly on disk so the spawned dashboard
114
+ * server (a separate process) rehydrates real subsystem counts and its reader-only
115
+ * routes report LIVE (non-deferred) data. This is how the LIVEWIRE route tests
116
+ * prove the wiring: the runtimes' persisted counts are exactly what a reader
117
+ * process reconstructs. Counts + codes + triad mode only (SECURITY_PRIVACY).
118
+ */
119
+ export async function seedLivewireSnapshot(
120
+ dir: string,
121
+ overrides?: {
122
+ crystals?: Partial<Record<
123
+ "crystalCount" | "totalBytes" | "hits" | "misses" | "hitBytes" |
124
+ "writes" | "duplicateWrites" | "collisions",
125
+ number
126
+ >>;
127
+ diagnostics?: Partial<Record<
128
+ "profileMisses" | "rangeMisses" | "dependencyMisses" | "requestMisses" |
129
+ "generationMisses" | "unknownMisses" | "serveBlocked",
130
+ number
131
+ >> & { breakerState?: string };
132
+ economics?: {
133
+ computed?: boolean;
134
+ profileCount?: number;
135
+ provenExclusions?: number;
136
+ unprovenExclusions?: number;
137
+ };
138
+ policy?: {
139
+ shadowDecisions?: number;
140
+ clampedDecisions?: number;
141
+ rejectedInputs?: number;
142
+ pressureVersion?: number;
143
+ liveMutations?: number;
144
+ };
145
+ },
146
+ ): Promise<void> {
147
+ const { saveLivewireSnapshot } = await import(
148
+ "../../src/vector-cortex/livewire/livewire-snapshot.js"
149
+ );
150
+ const c = overrides?.crystals ?? {};
151
+ const d = overrides?.diagnostics ?? {};
152
+ const e = overrides?.economics ?? {};
153
+ const p = overrides?.policy ?? {};
154
+ saveLivewireSnapshot(dir, {
155
+ schema: "vector-cortex-livewire-v1",
156
+ crystals: {
157
+ mode: "A",
158
+ crystalCount: c.crystalCount ?? 3,
159
+ totalBytes: c.totalBytes ?? 1024,
160
+ hits: c.hits ?? 0,
161
+ misses: c.misses ?? 0,
162
+ hitBytes: c.hitBytes ?? 0,
163
+ writes: c.writes ?? 0,
164
+ duplicateWrites: c.duplicateWrites ?? 0,
165
+ collisions: c.collisions ?? 0,
166
+ },
167
+ diagnostics: {
168
+ profileMisses: d.profileMisses ?? 0,
169
+ rangeMisses: d.rangeMisses ?? 0,
170
+ dependencyMisses: d.dependencyMisses ?? 0,
171
+ requestMisses: d.requestMisses ?? 0,
172
+ generationMisses: d.generationMisses ?? 0,
173
+ unknownMisses: d.unknownMisses ?? 0,
174
+ serveBlocked: d.serveBlocked ?? 0,
175
+ breakerState: d.breakerState ?? "CLOSED_A",
176
+ lastFailure: null,
177
+ },
178
+ economics: {
179
+ profileCount: e.profileCount ?? 4,
180
+ provenExclusions: e.provenExclusions ?? 1,
181
+ unprovenExclusions: e.unprovenExclusions ?? 0,
182
+ computed: e.computed ?? true,
183
+ lastFailure: null,
184
+ },
185
+ policy: {
186
+ shadowDecisions: p.shadowDecisions ?? 0,
187
+ clampedDecisions: p.clampedDecisions ?? 0,
188
+ rejectedInputs: p.rejectedInputs ?? 0,
189
+ liveMutations: p.liveMutations ?? 0,
190
+ pressureVersion: (p.pressureVersion ?? 1) as 1 | 2,
191
+ lastFailure: null,
192
+ },
193
+ });
194
+ }
@@ -21,13 +21,18 @@ import type { RouteContext } from "./routes-core.js";
21
21
  import { VC8B_ENABLED } from "../../src/config.js";
22
22
  import { sendJson } from "./routes-vector-cortex-shared.js";
23
23
  import { deriveVcStatus } from "./vc-status.js";
24
+ import { readLivewireSnapshot } from "../../src/vector-cortex/livewire/livewire-registry.js";
24
25
  import type { VectorCortexPolicyView } from "./api-contracts/vector-cortex-policy.js";
25
26
 
27
+ // The flag-off deferred reason, kept byte-identical to the pre-LIVEWIRE
28
+ // predecessor (VC8B: `shadow_controller_not_instantiated_v0_20_23`).
29
+ const DEFERRED_REASON = "shadow_controller_not_instantiated_v0_20_23";
30
+
26
31
  /** GET /api/vector-cortex/policy — reader-only policy + shadow aggregate (VC8B). */
27
32
  export function handleVectorCortexPolicy(
28
33
  req: IncomingMessage,
29
34
  res: ServerResponse,
30
- _ctx: RouteContext,
35
+ ctx: RouteContext,
31
36
  ): boolean {
32
37
  const url = req.url ?? "";
33
38
  const path = url.split("?")[0] ?? url;
@@ -39,23 +44,37 @@ export function handleVectorCortexPolicy(
39
44
  }
40
45
 
41
46
  const enabled = VC8B_ENABLED();
42
- const mode: VectorCortexPolicyView["mode"] = enabled ? "A" : "C";
47
+ if (!enabled) {
48
+ // Flag-off parity: byte-identical to the predecessor (mode C + deferred).
49
+ const body: VectorCortexPolicyView = {
50
+ enabled: false,
51
+ mode: "C",
52
+ shadowDecisions: 0,
53
+ clampedDecisions: 0,
54
+ rejectedInputs: 0,
55
+ liveMutations: 0,
56
+ pressureVersion: 1,
57
+ lastFailure: null,
58
+ updatedAt: new Date().toISOString(),
59
+ deferredReason: DEFERRED_REASON,
60
+ status: deriveVcStatus({ enabled: false, hasData: false }),
61
+ };
62
+ sendJson(res, 200, body);
63
+ return true;
64
+ }
65
+
66
+ const policy = readLivewireSnapshot(ctx.stateDir).policy;
43
67
  const body: VectorCortexPolicyView = {
44
- enabled,
45
- mode,
46
- shadowDecisions: 0,
47
- clampedDecisions: 0,
48
- rejectedInputs: 0,
49
- liveMutations: 0,
50
- pressureVersion: 1,
51
- lastFailure: null,
68
+ enabled: true,
69
+ mode: "A",
70
+ shadowDecisions: policy.shadowDecisions,
71
+ clampedDecisions: policy.clampedDecisions,
72
+ rejectedInputs: policy.rejectedInputs,
73
+ liveMutations: policy.liveMutations,
74
+ pressureVersion: policy.pressureVersion,
75
+ lastFailure: policy.lastFailure,
52
76
  updatedAt: new Date().toISOString(),
53
- deferredReason: "shadow_controller_not_instantiated_v0_20_23",
54
- status: deriveVcStatus({
55
- enabled,
56
- deferredReason: "shadow_controller_not_instantiated_v0_20_23",
57
- hasData: false,
58
- }),
77
+ status: deriveVcStatus({ enabled: true, hasData: policy.shadowDecisions > 0 }),
59
78
  };
60
79
  sendJson(res, 200, body);
61
80
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.20.70",
3
+ "version": "0.20.72",
4
4
  "description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
5
5
  "type": "module",
6
6
  "license": "BSD-3-Clause",
@@ -78,6 +78,11 @@ export class CrystalStore {
78
78
  private writes = 0;
79
79
  private duplicateWrites = 0;
80
80
  private collisions = 0;
81
+ /** Restart-survival crystal count/bytes (LIVEWIRE rehydrate). The `committed`
82
+ * map is empty in a fresh process; these offsets carry the persisted totals
83
+ * so the dashboard does not reset to zero on restart. */
84
+ private rehydratedCrystalCount = 0;
85
+ private rehydratedTotalBytes = 0;
81
86
 
82
87
  /** Freeze a crystal object for a key/bytes pair (digest computed here). */
83
88
  static freeze(keyDigest: string, bytes: Uint8Array, key: CrystalV1["key"]): CrystalV1 {
@@ -196,11 +201,11 @@ export class CrystalStore {
196
201
 
197
202
  /** Reader-only aggregate for the dashboard seam — counts and bytes only. */
198
203
  stats(): CrystalStoreStats {
199
- let totalBytes = 0;
204
+ let totalBytes = this.rehydratedTotalBytes;
200
205
  for (const c of this.committed.values()) totalBytes += c.byteCount;
201
206
  return {
202
207
  mode: this.mode(),
203
- crystalCount: this.committed.size,
208
+ crystalCount: this.rehydratedCrystalCount + this.committed.size,
204
209
  totalBytes,
205
210
  hits: this.hits,
206
211
  misses: this.misses,
@@ -210,4 +215,23 @@ export class CrystalStore {
210
215
  collisions: this.collisions,
211
216
  };
212
217
  }
218
+
219
+ /**
220
+ * Restart survival: seed the CUMULATIVE counters from a previously-persisted
221
+ * aggregate (LIVEWIRE). The in-memory `committed` map is naturally empty in a
222
+ * fresh process — this only restores the running totals the dashboard reports,
223
+ * so a process restart does not reset the dashboard to zero. It deliberately
224
+ * does NOT reconstruct crystals: frozen bytes are never persisted to the
225
+ * reader aggregate, only the counts.
226
+ */
227
+ rehydrate(stats: CrystalStoreStats): void {
228
+ this.rehydratedCrystalCount = stats.crystalCount;
229
+ this.rehydratedTotalBytes = stats.totalBytes;
230
+ this.hits = stats.hits;
231
+ this.misses = stats.misses;
232
+ this.hitBytes = stats.hitBytes;
233
+ this.writes = stats.writes;
234
+ this.duplicateWrites = stats.duplicateWrites;
235
+ this.collisions = stats.collisions;
236
+ }
213
237
  }
@@ -26,7 +26,19 @@ import {
26
26
  } from "./types.js";
27
27
 
28
28
  export type AssetVerifyResult =
29
- | { ok: true; embeddedBytes: number; maxTokens: number; onnxDigest: string; tokenizerDigest: string }
29
+ | {
30
+ ok: true;
31
+ embeddedBytes: number;
32
+ maxTokens: number;
33
+ onnxDigest: string;
34
+ tokenizerDigest: string;
35
+ /**
36
+ * ML5-A (VC2B-2): digest of the manifest-pinned `trained-heads.json`
37
+ * sibling when the manifest declares `headWeights`; null when the
38
+ * manifest ships no head weights (the committed placeholder bundle).
39
+ */
40
+ headWeightsDigest: string | null;
41
+ }
30
42
  | { ok: false; code: string };
31
43
 
32
44
  /** True when `p` is a single basename: non-empty, no path separators, no "..",
@@ -80,7 +92,14 @@ function isManifest(m: unknown): m is ModelManifestV1 {
80
92
  typeof o.onnx.path === "string" &&
81
93
  typeof o.onnx.sha256 === "string" &&
82
94
  typeof o.tokenizer.path === "string" &&
83
- typeof o.tokenizer.sha256 === "string"
95
+ typeof o.tokenizer.sha256 === "string" &&
96
+ // ML5-A (VC2B-2): optional headWeights must, when present, be a valid
97
+ // ManifestAssetFile (path basename + sha256 + bytes).
98
+ (o.headWeights === undefined ||
99
+ (!!o.headWeights &&
100
+ typeof o.headWeights.path === "string" &&
101
+ typeof o.headWeights.sha256 === "string" &&
102
+ typeof o.headWeights.bytes === "number"))
84
103
  );
85
104
  }
86
105
 
@@ -122,6 +141,11 @@ export function verifyEncoderAsset(
122
141
  if (!isBasename(manifest.onnx.path) || !isBasename(manifest.tokenizer.path)) {
123
142
  return { ok: false, code: ENC_FAIL.MANIFEST_INVALID };
124
143
  }
144
+ // ML5-A (VC2B-2): a manifest-declared headWeights path must also be a bare
145
+ // basename (no traversal into arbitrary paths off the asset dir).
146
+ if (manifest.headWeights !== undefined && !isBasename(manifest.headWeights.path)) {
147
+ return { ok: false, code: ENC_FAIL.MANIFEST_INVALID };
148
+ }
125
149
 
126
150
  const onnxPath = join(assetDir, manifest.onnx.path);
127
151
  const onnxDigest = digestFile(onnxPath);
@@ -133,13 +157,32 @@ export function verifyEncoderAsset(
133
157
  if (tokDigest === null) return { ok: false, code: ENC_FAIL.ASSET_UNREADABLE };
134
158
  if (tokDigest !== manifest.tokenizer.sha256) return { ok: false, code: ENC_FAIL.DIGEST_MISMATCH };
135
159
 
160
+ // ML5-A (VC2B-2): verify the manifest-pinned trained-heads sibling when the
161
+ // manifest declares it. Absent declaration -> headWeightsDigest: null and no
162
+ // failure (committed placeholder bundle ships no trained weights).
163
+ let headWeightsDigest: string | null = null;
164
+ if (manifest.headWeights !== undefined) {
165
+ const hwPath = join(assetDir, manifest.headWeights.path);
166
+ const hwDigest = digestFile(hwPath);
167
+ if (hwDigest === null) return { ok: false, code: ENC_FAIL.ASSET_UNREADABLE };
168
+ if (hwDigest !== manifest.headWeights.sha256) return { ok: false, code: ENC_FAIL.DIGEST_MISMATCH };
169
+ headWeightsDigest = hwDigest;
170
+ }
171
+
136
172
  let embeddedBytes = 0;
137
173
  try {
138
174
  embeddedBytes = statSync(onnxPath).size + statSync(tokPath).size;
139
175
  } catch {
140
176
  return { ok: false, code: ENC_FAIL.ASSET_UNREADABLE };
141
177
  }
142
- return { ok: true, embeddedBytes, maxTokens: manifest.maxTokens, onnxDigest, tokenizerDigest: tokDigest };
178
+ return {
179
+ ok: true,
180
+ embeddedBytes,
181
+ maxTokens: manifest.maxTokens,
182
+ onnxDigest,
183
+ tokenizerDigest: tokDigest,
184
+ headWeightsDigest,
185
+ };
143
186
  }
144
187
 
145
188
  /**
@@ -26,15 +26,24 @@
26
26
  */
27
27
 
28
28
  import { createEncoderRuntime } from "./runtime.js";
29
- import { encodeVectorSet, type HeadProjectionOptions } from "./heads.js";
29
+ import {
30
+ encodeVectorSet,
31
+ loadHeadProjections,
32
+ projectHeadFromTrunk,
33
+ type HeadProjectionOptions,
34
+ type HeadProjectionTable,
35
+ } from "./heads.js";
30
36
  import { embedTrigram512, selectTrigramBFallback } from "./trigram.js";
31
37
  import { embedLexical, selectLexicalC } from "./lexical.js";
32
38
  import { createEncoderHeadsReporter, type EncoderHeadsReporter } from "./emit-vc2b.js";
39
+ import { ML5A_ENABLED } from "../../config/vector-cortex.js";
33
40
  import {
34
41
  ENC_FAIL,
42
+ ENCODER_HEAD_ORDER,
35
43
  type EncoderInput,
36
44
  type EncoderLoadResult,
37
45
  type EncoderRuntime,
46
+ type HeadVector,
38
47
  type VectorSetV1,
39
48
  } from "./types.js";
40
49
 
@@ -61,6 +70,15 @@ export interface RouterOptions extends HeadProjectionOptions {
61
70
  /** Forced fallback: skips the A load and selects the named VC2B fallback
62
71
  * (used to exercise C when A and B are both disabled). Optional. */
63
72
  readonly forceFallback?: "B" | "C";
73
+ /**
74
+ * ML5-A (VC2B-2): path to the `trained-heads-v1` artifact. When supplied AND
75
+ * the MEGACOMPACT_ML5_A gate is on AND the artifact loads (correct seed +
76
+ * shape), the mode-A VectorSet is produced by projecting the real trained head
77
+ * matrices over the trunk embedding (`projectHeadFromTrunk`). When absent /
78
+ * flag-off / unloadable, the mode-A producer falls back to the deterministic
79
+ * LCG placeholder `encodeVectorSet` (byte-identical predecessor).
80
+ */
81
+ readonly trainedHeadsPath?: string;
64
82
  }
65
83
 
66
84
  /** Deterministic text derived from an int token sequence so the asset-free
@@ -133,10 +151,46 @@ export function encodeOrFallback(
133
151
  if (!inferred.ok) {
134
152
  return fallbackFromLoad({ ok: false, mode: "B", code: inferred.code }, reporter, tokens);
135
153
  }
136
- const vectorSet = encodeVectorSet(tokens, { reporter, seed: options.seed });
154
+ const vectorSet = produceVectorSet(inferred.semantic, tokens, options, reporter);
137
155
  return { ok: true, mode: "A", vectorSet, code: null };
138
156
  }
139
157
 
158
+ /**
159
+ * Produce a mode-A `VectorSetV1` from the [1,384] trunk embedding (the
160
+ * `runtime.infer` result). VC2B-2 ML5-A: when real trained heads are loaded
161
+ * (`MEGACOMPACT_ML5_A` on + a `trainedHeadsPath` that loads), the multi-head
162
+ * output is projected through the real trained projection matrices via
163
+ * `projectHeadFromTrunk`. Otherwise (flag-off / absent / unloadable artifact)
164
+ * the deterministic LCG placeholder `encodeVectorSet` serves mode A —
165
+ * byte-identical to the VC2B predecessor. Non-fatal: an unloadable artifact
166
+ * degrades to the placeholder, never a throw.
167
+ */
168
+ function produceVectorSet(
169
+ trunkEmbedding: Float32Array,
170
+ tokens: readonly number[],
171
+ options: RouterOptions,
172
+ reporter: EncoderHeadsReporter,
173
+ ): VectorSetV1 {
174
+ const table: HeadProjectionTable | null =
175
+ ML5A_ENABLED() && options.trainedHeadsPath !== undefined
176
+ ? loadHeadProjections(options.trainedHeadsPath)
177
+ : null;
178
+ if (table !== null) {
179
+ const heads: HeadVector[] = ENCODER_HEAD_ORDER.map((h) =>
180
+ projectHeadFromTrunk(h, trunkEmbedding, table),
181
+ );
182
+ reporter.headsEmitted({
183
+ heads: heads.length,
184
+ dims: heads.map((h) => h.dim).join("/"),
185
+ normalized: true,
186
+ tokens: tokens.length,
187
+ });
188
+ return { schema: "vector-set-v1", inputTokens: [...tokens], heads, normalized: true };
189
+ }
190
+ // ML5-A placeholder fallback (byte-identical predecessor).
191
+ return encodeVectorSet(tokens, { reporter, seed: options.seed });
192
+ }
193
+
140
194
  /**
141
195
  * Catch a (real or forced) A load failure (ok === false only) and select the
142
196
  * B/C fallback that emits `vector_cortex_encoder_fallback_selected` from the
@@ -92,6 +92,9 @@ export interface ModelManifestV1 {
92
92
  readonly heads: EncoderHeads;
93
93
  readonly onnx: ManifestAssetFile;
94
94
  readonly tokenizer: ManifestAssetFile;
95
+ /** ML5-A (VC2B-2): optional sibling `trained-heads.json` (`trained-heads-v1`) shipping
96
+ * REAL trained head weights; absent in the placeholder bundle, non-fatal when missing. */
97
+ readonly headWeights?: ManifestAssetFile;
95
98
  readonly totalBytes: number;
96
99
  readonly trainingManifestDigest: string;
97
100
  }