pi-mega-compact 0.20.34 → 0.20.36

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 (34) hide show
  1. package/dist/config/vector-cortex-ml5a.js +28 -0
  2. package/dist/config/vector-cortex.js +4 -5
  3. package/dist/config.js +1 -1
  4. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
  5. package/dist/extensions/mega-events/context-handler/afterCompact.js +33 -4
  6. package/dist/extensions/mega-events/context-handler/controller.js +161 -0
  7. package/dist/src/config/vector-cortex-ml5a.js +28 -0
  8. package/dist/src/config/vector-cortex.js +4 -5
  9. package/dist/src/config.js +1 -1
  10. package/dist/src/vector-cortex/encoder/calibrate.js +55 -0
  11. package/dist/src/vector-cortex/encoder/heads.js +87 -0
  12. package/dist/src/vector-cortex/encoder/select.js +10 -0
  13. package/dist/src/vector-cortex/heal/_vc6c-impl-fixture.js +29 -0
  14. package/dist/src/vector-cortex/reconstruct/rebuild.js +43 -0
  15. package/dist/src/vector-cortex/reconstruct/repair-plan.js +57 -0
  16. package/dist/vector-cortex/encoder/calibrate.js +55 -0
  17. package/dist/vector-cortex/encoder/heads.js +87 -0
  18. package/dist/vector-cortex/encoder/select.js +10 -0
  19. package/dist/vector-cortex/heal/_vc6c-impl-fixture.js +29 -0
  20. package/dist/vector-cortex/reconstruct/rebuild.js +43 -0
  21. package/dist/vector-cortex/reconstruct/repair-plan.js +57 -0
  22. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
  23. package/extensions/mega-events/context-handler/afterCompact.ts +40 -4
  24. package/extensions/mega-events/context-handler/controller.ts +230 -0
  25. package/package.json +1 -1
  26. package/src/config/vector-cortex-ml5a.ts +30 -0
  27. package/src/config/vector-cortex.ts +4 -5
  28. package/src/config.ts +1 -0
  29. package/src/vector-cortex/encoder/calibrate.ts +49 -0
  30. package/src/vector-cortex/encoder/heads.ts +106 -0
  31. package/src/vector-cortex/encoder/select.ts +17 -0
  32. package/src/vector-cortex/heal/_vc6c-impl-fixture.ts +43 -0
  33. package/src/vector-cortex/reconstruct/rebuild.ts +75 -0
  34. package/src/vector-cortex/reconstruct/repair-plan.ts +112 -0
@@ -0,0 +1,230 @@
1
+ /**
2
+ * context-handler/controller.ts — VC6C-IMPL production post-compact gap
3
+ * detection + repair drive.
4
+ *
5
+ * The production seam that makes the VC6C self-healing controller REAL: after a
6
+ * compact, compare each derived subsystem's POST-compact chunk count against the
7
+ * durable authority high-water. A subsystem whose derived high-water fell behind
8
+ * authority has a REAL gap; only then does the drive route through the plan →
9
+ * rebuild → emit pipeline. When there is no real gap, NOTHING is emitted (no
10
+ * rebuild without a real gap — VC6C-IMPL-006).
11
+ *
12
+ * PURE POLICY DEFERS TO heal/. Gap-ness, the four refusal rules (frozen
13
+ * authority / no gap / mode C / rate limit), and the deterministic backoff are
14
+ * the VC6C heal primitives' job (`detectGaps`, `isPlannable`, `computeBackoff`
15
+ * — 74 tested lines). This file owns ONLY the production mapping: `PostCompactView`
16
+ * → `RepairState` (so heal policy can judge it) → `RepairPlanV1` (production
17
+ * shape) → `AtomicRebuild` (atomic pointer switch) → the three repair events.
18
+ * Flag OFF = the placeholder continues firing exactly as today and rebuild is a
19
+ * no-op; see `drivePostCompactRepair`'s caller in afterCompact.ts.
20
+ *
21
+ * THE AUTHORITY IS NEVER WRITTEN. `PostCompactView.authorityHighWater` is read to
22
+ * decide gap-ness; no code here has a write path to the durable authority.
23
+ *
24
+ * PURE-ish + CONSTANT-FREE. `nowMs` is always injected (fake-clock fixtures).
25
+ * Backoff/gap come from the plan, never a literal. No console, no network
26
+ * (PREVENT-PI-004). Emit is an injected callback so the drive is unit-testable
27
+ * without a runtime.
28
+ */
29
+
30
+ import { isPlannable } from "../../../src/vector-cortex/heal/controller.js";
31
+ import type { RepairState } from "../../../src/vector-cortex/heal/repair-types.js";
32
+ import {
33
+ reportRepairBackoff,
34
+ reportRepairPlanned,
35
+ reportRepairPointerSwitched,
36
+ type RepairEmit,
37
+ } from "../../../src/vector-cortex/heal/repair-emit.js";
38
+ import {
39
+ buildRepairPlan,
40
+ gapSizeOf,
41
+ type PostCompactGap,
42
+ type RepairPlanV1,
43
+ } from "../../../src/vector-cortex/reconstruct/repair-plan.js";
44
+ import {
45
+ rebuildRepairRange,
46
+ type AtomicRebuild,
47
+ type RebuildInput,
48
+ } from "../../../src/vector-cortex/reconstruct/rebuild.js";
49
+ import type { Mode } from "../../../src/vector-cortex/heal/repair-types.js";
50
+
51
+ /** One derived subsystem's pre/post compact counts against durable authority. */
52
+ export interface PostCompactView {
53
+ readonly subsystem: string;
54
+ /** Derived chunk count BEFORE compaction. */
55
+ readonly preCount: number;
56
+ /** Derived chunk count AFTER compaction (the derived high-water, inclusive). */
57
+ readonly postCount: number;
58
+ /** Durable CONTIGUOUS authority high-water (inclusive). Read, never written. */
59
+ readonly authorityHighWater: number;
60
+ /** CURRENT live derived generation. A plan targets `generation + 1`. */
61
+ readonly generation: number;
62
+ readonly failedAttempts?: number;
63
+ readonly mode: Mode;
64
+ /** True while the durable authority frontier is frozen (outage). */
65
+ readonly authorityFrozen?: boolean;
66
+ /** Monotonic ms of the last rebuild, or null if never rebuilt. */
67
+ readonly lastRebuildAtMs: bigint | null;
68
+ }
69
+
70
+ /**
71
+ * Detect the subsystems whose POST-compact derived high-water fell behind the
72
+ * durable authority. `left` is the pre-compact view, `right` the post-compact
73
+ * view (aligned by subsystem name); a subsystem qualifies when its POST count
74
+ * is strictly below its durable authority high-water. Pure — no clock, no
75
+ * writes.
76
+ */
77
+ export function detectPostCompactGaps(
78
+ left: readonly PostCompactView[],
79
+ right: readonly PostCompactView[],
80
+ ): readonly PostCompactView[] {
81
+ const byName = new Map(right.map((v) => [v.subsystem, v]));
82
+ const gapped: PostCompactView[] = [];
83
+ for (const l of left) {
84
+ const r = byName.get(l.subsystem);
85
+ if (r === undefined) continue;
86
+ if (r.postCount < r.authorityHighWater) gapped.push(r);
87
+ }
88
+ return gapped;
89
+ }
90
+
91
+ /** Map a production post-compact view into the heal `RepairState` judge shape. */
92
+ export function toRepairState(view: PostCompactView): RepairState {
93
+ return {
94
+ subsystem: view.subsystem,
95
+ derivedHighWater: BigInt(view.postCount),
96
+ authorityHighWater: BigInt(view.authorityHighWater),
97
+ lastRebuildAt: view.lastRebuildAtMs,
98
+ generation: view.generation,
99
+ mode: view.mode,
100
+ ...(view.failedAttempts !== undefined ? { failedAttempts: view.failedAttempts } : {}),
101
+ ...(view.authorityFrozen !== undefined ? { authorityFrozen: view.authorityFrozen } : {}),
102
+ };
103
+ }
104
+
105
+ /** Build the production plan for one gapped view. */
106
+ export function planFor(view: PostCompactView): RepairPlanV1 {
107
+ return buildRepairPlan(view as PostCompactGap);
108
+ }
109
+
110
+ /** Turn a production plan + view into the heal `RebuildInput` builder surface. */
111
+ export interface RebuildSource {
112
+ /** Materialized bytes of the new derived generation. */
113
+ readonly sourceBytes: Uint8Array;
114
+ /** Root digest (BARE lowercase hex) the plan pins for the new generation. */
115
+ readonly expectedDigest: string;
116
+ }
117
+
118
+ function rebuildInputFor(plan: RepairPlanV1, src: RebuildSource): RebuildInput {
119
+ return {
120
+ subsystem: plan.subsystem,
121
+ range: {
122
+ sessionId: plan.subsystem,
123
+ seqStart: BigInt(plan.range[0]),
124
+ seqEnd: BigInt(plan.range[1]),
125
+ byteStart: 0,
126
+ byteEnd: 0,
127
+ },
128
+ generation: plan.generation,
129
+ sourceBytes: src.sourceBytes,
130
+ expectedDigest: src.expectedDigest,
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Drive one repair for a gapped subsystem: plan → rebuild → emit.
136
+ *
137
+ * Emits `reportRepairPlanned` first (the plan with its deterministic backoff),
138
+ * then executes the atomic rebuild; a verified strict-successor switch emits
139
+ * `reportRepairPointerSwitched`, a failed rebuild emits `reportRepairBackoff`.
140
+ * `currentGeneration` (the live generation) is read for the monotonic switch.
141
+ */
142
+ export function driveOneRepair(
143
+ view: PostCompactView,
144
+ emit: RepairEmit | undefined,
145
+ rebuildSource: RebuildSource,
146
+ ): { plan: RepairPlanV1; rebuilt: AtomicRebuild } {
147
+ const plan = planFor(view);
148
+ reportRepairPlanned(emit, {
149
+ subsystem: plan.subsystem,
150
+ generation: plan.generation,
151
+ backoffMs: plan.backoffMs,
152
+ gapSize: gapSizeOf(view as PostCompactGap),
153
+ });
154
+ const rebuilt = rebuildRepairRange(
155
+ plan,
156
+ rebuildInputFor(plan, rebuildSource),
157
+ view.generation,
158
+ view.mode,
159
+ );
160
+ if (rebuilt.pointer.switched) {
161
+ reportRepairPointerSwitched(emit, {
162
+ subsystem: plan.subsystem,
163
+ fromGeneration: view.generation,
164
+ toGeneration: plan.generation,
165
+ mode: view.mode,
166
+ });
167
+ } else {
168
+ reportRepairBackoff(emit, {
169
+ subsystem: plan.subsystem,
170
+ code: rebuilt.result.ok ? "HEAL_REPAIR_RATE_LIMITED" : (rebuilt.result.code ?? "HEAL_REBUILD_FAILED"),
171
+ backoffMs: plan.backoffMs,
172
+ attempt: view.failedAttempts ?? 0,
173
+ });
174
+ }
175
+ return { plan, rebuilt };
176
+ }
177
+
178
+ /**
179
+ * The full post-compact repair drive. Applies heal's eligibility policy
180
+ * (`isPlannable` — rate limit, no gap, frozen authority, mode C) per subsystem,
181
+ * and only runs `driveOneRepair` for subsystems with a REAL, actionable gap. A
182
+ * subsystem with no real gap, or inside its rate-limit window, emits NOTHING.
183
+ *
184
+ * `rebuildSourceFor` is an injected executor that materializes a new generation
185
+ * for a plannable subsystem (the handler supplies the real one; fixtures supply
186
+ * a deterministic one), keeping the drive testable without a runtime.
187
+ */
188
+ export function drivePostCompactRepair(
189
+ views: readonly PostCompactView[],
190
+ nowMs: bigint,
191
+ emit: RepairEmit | undefined,
192
+ rebuildSourceFor: (view: PostCompactView) => RebuildSource,
193
+ ): void {
194
+ for (const view of views) {
195
+ const state = toRepairState(view);
196
+ if (!isPlannable(state, nowMs)) continue;
197
+ driveOneRepair(view, emit, rebuildSourceFor(view));
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Build the production post-compact subsystem views from a compact result.
203
+ *
204
+ * `compactedFrom` is the committed seq frontier after compaction. In a NORMAL
205
+ * compact the derived post-count equals the durable authority high-water (they
206
+ * advance together), so the resulting view has NO real gap — the drive emits
207
+ * nothing (VC6C-IMPL-006). A caller that derives per-subsystem counts where a
208
+ * derived tier fell behind authority supplies those lower counts here, and the
209
+ * drive will detect the gap and repair it. `currentGeneration` seeds the derived
210
+ * generation counter.
211
+ */
212
+ export function buildPostCompactViews(
213
+ compactedFrom: number,
214
+ currentGeneration: number,
215
+ authorityHighWater: number = compactedFrom,
216
+ postCount: number = compactedFrom,
217
+ ): readonly PostCompactView[] {
218
+ return [
219
+ {
220
+ subsystem: "post_compact",
221
+ preCount: compactedFrom,
222
+ postCount,
223
+ authorityHighWater,
224
+ generation: currentGeneration,
225
+ failedAttempts: 0,
226
+ mode: "A",
227
+ lastRebuildAtMs: null,
228
+ },
229
+ ];
230
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.20.34",
3
+ "version": "0.20.36",
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",
@@ -0,0 +1,30 @@
1
+ /**
2
+ * config/vector-cortex-ml5a.ts — ML5-A five-head training + calibration flag.
3
+ *
4
+ * Extracted from vector-cortex.ts so that file stays under the 300-line soft
5
+ * limit (soft-as-hard gate), exactly as vector-cortex-vc9a.ts..vector-cortex-vc9d.ts
6
+ * were. This is the ML5 training sprint flag. vector-cortex.ts re-exports the
7
+ * ENUM below and root src/config.ts re-exports it, so no consumer import path
8
+ * changes.
9
+ *
10
+ * The split is purely mechanical: ML5A_ENABLED is byte-identical in name,
11
+ * semantics, and default to the definition it replaces, and vector-cortex.ts
12
+ * re-exports it so every existing `from "./config/vector-cortex.js"` import
13
+ * keeps resolving unchanged.
14
+ *
15
+ * Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
16
+ */
17
+
18
+ import { sprintFlag } from "./vector-cortex-flag.js";
19
+
20
+ /**
21
+ * ML5-A — five-head training + calibrated onnx asset. Default ON.
22
+ * `MEGACOMPACT_ML5_A=0` disables and is byte-identical to the placeholder
23
+ * predecessor (VC2C-era): `calibrate.ts`/`heads.ts` keep serving the LCG fake
24
+ * projections and the placeholder `fitTemperature`/`fitThreshold`, mode B
25
+ * trigram continues serving, and no trained artifact is loaded (a fresh/no
26
+ * corpus also no-ops gracefully — asset_emitted:false, placeholder behavior,
27
+ * byte-identical). This flag MUST also be a dashboard SETTINGS toggle (visible
28
+ * in config UI, never in EXCLUDED_SETTINGS), mirroring VC4A..VC9D.
29
+ */
30
+ export const ML5A_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_ML5_A");
@@ -269,18 +269,17 @@ export const VC8A_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC8A");
269
269
  */
270
270
  export const VC8B_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC8B");
271
271
 
272
- // VC8C (canary selection + external Rust parity) extracted to
273
- // vector-cortex-vc8c.ts to keep this file under the 300-line soft limit.
274
- // Re-exported here so every existing `from "./config/vector-cortex.js"`
275
- // import keeps resolving unchanged.
272
+ // VC8C (canary selection + Rust parity) extracted to vector-cortex-vc8c.ts;
273
+ // re-exported so existing `./config/vector-cortex.js` imports keep resolving.
276
274
  export { VC8C_ENABLED } from "./vector-cortex-vc8c.js";
277
275
 
278
- // VC9A/VC9B/VC9C/VC9D split to vector-cortex-vc9{a,b,c,d}.ts to stay under the 300-line soft limit.
276
+ // VC9A/VC9B/VC9C/VC9D/PCC/ML5A split to sibling files to stay under the 300-line soft limit.
279
277
  export { VC9A_ENABLED } from "./vector-cortex-vc9a.js";
280
278
  export { VC9B_ENABLED } from "./vector-cortex-vc9b.js";
281
279
  export { VC9C_ENABLED } from "./vector-cortex-vc9c.js";
282
280
  export { VC9D_ENABLED } from "./vector-cortex-vc9d.js";
283
281
  export { PCC_ENABLED } from "./vector-cortex-pcc.js";
282
+ export { ML5A_ENABLED } from "./vector-cortex-ml5a.js";
284
283
 
285
284
  // Breaker constants (TRIAD_RESILIENCE.md §breaker) extracted to vector-cortex-breakers.ts.
286
285
  export {
package/src/config.ts CHANGED
@@ -183,6 +183,7 @@ export {
183
183
  VC9C_ENABLED,
184
184
  VC9D_ENABLED,
185
185
  PCC_ENABLED,
186
+ ML5A_ENABLED,
186
187
  BREAKER_WINDOW_MS,
187
188
  BREAKER_MIN_ATTEMPTS,
188
189
  BREAKER_PERF_FAILURES,
@@ -23,6 +23,8 @@
23
23
  */
24
24
 
25
25
  import { createHash } from "node:crypto";
26
+ import { readFileSync } from "node:fs";
27
+ import { ML5A_ENABLED } from "../../config/vector-cortex.js";
26
28
  import {
27
29
  ENCODER_HEAD_ORDER,
28
30
  ENCODER_SEED,
@@ -223,3 +225,50 @@ export function fitCalibration(
223
225
  };
224
226
  return { ok: true, calibration };
225
227
  }
228
+
229
+ /**
230
+ * Load a persisted `CalibrationV1` artifact (schema "calibration-v1") from disk.
231
+ * ML5-A: gated on MEGACOMPACT_ML5_A; flag-off, absent file, malformed JSON,
232
+ * wrong schema, non-canonical five-head order, or non-finite temp/threshold each
233
+ * return null (non-fatal, never throws). Deterministic, local (PREVENT-PI-004).
234
+ */
235
+ export function loadCalibrationV1(path: string): CalibrationV1 | null {
236
+ if (!ML5A_ENABLED()) return null;
237
+ let raw: string;
238
+ try {
239
+ raw = readFileSync(path, "utf8");
240
+ } catch {
241
+ return null;
242
+ }
243
+ let parsed: unknown;
244
+ try {
245
+ parsed = JSON.parse(raw);
246
+ } catch {
247
+ return null;
248
+ }
249
+ const r = parsed as Record<string, unknown> | null;
250
+ if (!r || r["schema"] !== "calibration-v1") return null;
251
+ const order = r["headOrder"];
252
+ if (!Array.isArray(order)) return null;
253
+ if (order.length !== ENCODER_HEAD_ORDER.length || !ENCODER_HEAD_ORDER.every((h, i) => order[i] === h)) {
254
+ return null;
255
+ }
256
+ const temperatures = r["temperatures"] as Record<string, unknown> | undefined;
257
+ const thresholds = r["thresholds"] as Record<string, unknown> | undefined;
258
+ const splitDigest = r["calibrationSplitDigest"];
259
+ if (!temperatures || !thresholds || typeof splitDigest !== "string" || splitDigest.length !== 64) return null;
260
+ for (const h of ENCODER_HEAD_ORDER) {
261
+ const t = Number(temperatures[h]);
262
+ const th = Number(thresholds[h]);
263
+ if (!Number.isFinite(t) || !Number.isFinite(th)) return null;
264
+ }
265
+ return {
266
+ schema: "calibration-v1",
267
+ headOrder: [...ENCODER_HEAD_ORDER],
268
+ calibrationSplitDigest: splitDigest,
269
+ fittedOnCalibrationOnly: true,
270
+ temperatures: { ...(temperatures as Record<EncoderHeadName, number>) },
271
+ thresholds: { ...(thresholds as Record<EncoderHeadName, number>) },
272
+ seed: Number(r["seed"] ?? ENCODER_SEED),
273
+ };
274
+ }
@@ -20,6 +20,8 @@
20
20
  * Pi-agnostic, zero network (PREVENT-PI-004), no `any` (PREVENT-011).
21
21
  */
22
22
 
23
+ import { readFileSync } from "node:fs";
24
+ import { ML5A_ENABLED } from "../../config/vector-cortex.js";
23
25
  import {
24
26
  ENCODER_HEAD_DIMS,
25
27
  ENCODER_HEAD_ORDER,
@@ -139,4 +141,108 @@ export function headLossWeights(): Readonly<Record<EncoderHeadName, number>> {
139
141
  return { ...ENCODER_HEAD_LOSS_WEIGHTS };
140
142
  }
141
143
 
144
+ // ---------------------------------------------------------------------------
145
+ // ML5-A real trained-head loading. Produces the projection matrices that the
146
+ // deterministic placeholder `projectHead` approximates: row-major
147
+ // `weights[h]` of length `headDim * trunkDim`, applied `W[i*t+j]*trunk[j]`,
148
+ // L2-normalized. Loaded from the `trained-heads-v1` JSON that
149
+ // `training/vector-cortex/train.py` emits, under the MEGACOMPACT_ML5_A gate.
150
+ // Non-fatal: any violation (flag off, absent, malformed, wrong seed, wrong
151
+ // shape) yields null, never a throw (all loaders return null on violation).
152
+ // ---------------------------------------------------------------------------
153
+
154
+ /** The real loadable form of the trained five-head projection table. */
155
+ export interface HeadProjectionTable {
156
+ readonly schema: "trained-heads-v1";
157
+ readonly seed: number;
158
+ /** Input/trunk embedding dimension every head projects from (uniform 384). */
159
+ readonly trunkDim: number;
160
+ /** Per-head OUTPUT dimension (semantic 384 / ... / payloadRouting 32). */
161
+ readonly dims: Readonly<Record<EncoderHeadName, number>>;
162
+ /** Row-major `[headDim * trunkDim]` projection matrix per head. */
163
+ readonly weights: Readonly<Record<EncoderHeadName, Float32Array>>;
164
+ readonly temperatures: Readonly<Record<EncoderHeadName, number>>;
165
+ }
166
+
167
+ /** True when every head's output dim + weight length matches the contract. */
168
+ export function headsShapeValid(t: HeadProjectionTable): boolean {
169
+ return ENCODER_HEAD_ORDER.every(
170
+ (h) => t.dims[h] === ENCODER_HEAD_DIMS[h] && t.weights[h].length === ENCODER_HEAD_DIMS[h] * t.trunkDim,
171
+ );
172
+ }
173
+
174
+ /**
175
+ * Load a `trained-heads-v1` artifact into a `HeadProjectionTable`. Gated on
176
+ * MEGACOMPACT_ML5_A: flag-off, absent file, malformed JSON, wrong schema,
177
+ * wrong seed, or a shape mismatch each return null (non-fatal). Deterministic
178
+ * and local (PREVENT-PI-004).
179
+ */
180
+ export function loadHeadProjections(path: string): HeadProjectionTable | null {
181
+ if (!ML5A_ENABLED()) return null;
182
+ let raw: string;
183
+ try {
184
+ raw = readFileSync(path, "utf8");
185
+ } catch {
186
+ return null;
187
+ }
188
+ let parsed: unknown;
189
+ try {
190
+ parsed = JSON.parse(raw);
191
+ } catch {
192
+ return null;
193
+ }
194
+ const r = parsed as Record<string, unknown> | null;
195
+ if (!r || r["schema"] !== "trained-heads-v1") return null;
196
+ if (r["seed"] !== ENCODER_SEED) return null;
197
+ const dims = r["dims"] as Record<string, unknown> | undefined;
198
+ const heads = r["heads"] as Record<string, unknown> | undefined;
199
+ if (!dims || !heads) return null;
200
+ const trunkDim = Number(r["trunkDim"] ?? 0);
201
+ if (!Number.isFinite(trunkDim) || trunkDim <= 0) return null;
202
+ const weights: Record<string, Float32Array> = {};
203
+ const temperatures: Record<string, number> = {};
204
+ for (const h of ENCODER_HEAD_ORDER) {
205
+ const hd = heads[h] as Record<string, unknown> | undefined;
206
+ if (!hd || typeof hd !== "object") return null;
207
+ const w = hd["weights"];
208
+ if (!Array.isArray(w)) return null;
209
+ weights[h] = Float32Array.from(w as number[]);
210
+ if (Number(hd["dim"] ?? 0) !== ENCODER_HEAD_DIMS[h]) return null;
211
+ temperatures[h] = Number(hd["temperature"] ?? 1);
212
+ if (!Number.isFinite(dims[h])) return null;
213
+ }
214
+ const table: HeadProjectionTable = {
215
+ schema: "trained-heads-v1",
216
+ seed: Number(r["seed"]),
217
+ trunkDim,
218
+ dims: { semantic: 384, dependency: 128, contradiction: 128, cacheStability: 64, payloadRouting: 32 } as unknown as Record<EncoderHeadName, number>,
219
+ weights: weights as unknown as Record<EncoderHeadName, Float32Array>,
220
+ temperatures: temperatures as unknown as Record<EncoderHeadName, number>,
221
+ };
222
+ if (!headsShapeValid(table)) return null;
223
+ return table;
224
+ }
225
+
226
+ /**
227
+ * Project a trunk embedding through a trained head's real weights, applying the
228
+ * row-major matrix then L2-normalizing (all-zero on zero norm). Returns a
229
+ * `HeadVector` of the head's normative dimension.
230
+ */
231
+ export function projectHeadFromTrunk(
232
+ head: EncoderHeadName,
233
+ trunk: Float32Array,
234
+ table: HeadProjectionTable,
235
+ ): HeadVector {
236
+ const dim = ENCODER_HEAD_DIMS[head];
237
+ const W = table.weights[head];
238
+ const t = table.trunkDim;
239
+ const out = new Float32Array(dim);
240
+ for (let i = 0; i < dim; i++) {
241
+ let acc = 0;
242
+ for (let j = 0; j < t; j++) acc += W[i * t + j]! * (trunk[j] ?? 0);
243
+ out[i] = acc;
244
+ }
245
+ return { head, dim, values: l2Normalize(out) };
246
+ }
247
+
142
248
  export { ENCODER_HEAD_ORDER, ENCODER_HEAD_DIMS, ENCODER_HEAD_LOSS_SUM, ENCODER_SEED, NOOP_VC2B_REPORTER };
@@ -34,6 +34,8 @@
34
34
  */
35
35
 
36
36
  import { createHash } from "node:crypto";
37
+ import { ML5A_ENABLED } from "../../config/vector-cortex.js";
38
+ import { loadHeadProjections } from "./heads.js";
37
39
  import {
38
40
  ENC_QUALIFICATION_FAIL,
39
41
  EVALUATION_THRESHOLDS,
@@ -83,6 +85,13 @@ export interface QualificationCandidate {
83
85
  * corrupt-qualification-manifest injection (ENC_QUALIFICATION_DIGEST_MISMATCH).
84
86
  */
85
87
  readonly expectedQualificationManifestDigest?: string;
88
+ /**
89
+ * ML5-A: path to the `trained-heads-v1` artifact. When supplied AND the
90
+ * MEGACOMPACT_ML5_A gate is on, the candidate qualifies to mode A only if the
91
+ * real trained head weights load; a load failure atomically demotes all of A
92
+ * to B (non-fatal, reported as THRESHOLD_FAILED / head.weights.trainedHeadsPath).
93
+ */
94
+ readonly trainedHeadsPath?: string;
86
95
  }
87
96
 
88
97
  /**
@@ -183,6 +192,14 @@ export function selectQualifiedEncoder(
183
192
 
184
193
  // Atomic: collect EVERY failed field across asset + all heads + reconstruction.
185
194
  const failed: string[] = [];
195
+ // ML5-A: real trained-head weights must load for mode A. When the gate is on
196
+ // and a trained-heads path is pinned, an unloadable/wrong-seed/malformed
197
+ // artifact is a qualification failure (any failed field demotes ALL of A).
198
+ if (ML5A_ENABLED() && candidate.trainedHeadsPath !== undefined) {
199
+ if (loadHeadProjections(candidate.trainedHeadsPath) === null) {
200
+ failed.push("head.weights.trainedHeadsPath");
201
+ }
202
+ }
186
203
  assetPasses(candidate.asset, failed);
187
204
  const heads: EncoderHeadName[] = ["semantic", "dependency", "contradiction", "cacheStability", "payloadRouting"];
188
205
  for (const h of heads) {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * heal/_vc6c-impl-fixture.ts — conformance fixture I/O for VC6C-IMPL
3
+ * self-healing-controller rows.
4
+ *
5
+ * VC6C's base corpus lives under `healing-controller/` (read by
6
+ * `_repair-fixture.ts`); VC6C-IMPL emits its six fixtures under
7
+ * `self-healing/` per the sprint brief. Both share the one canonical
8
+ * `healing-controller-fixture.schema.json`, so this loader reuses the
9
+ * `RepairFx` envelope (`_repair-fixture.ts`) but resolves fixture paths from
10
+ * the `self-healing/` directory. No mocks — the committed fixtures are fed
11
+ * verbatim into the real heal / reconstruct production modules.
12
+ */
13
+
14
+ import { readFileSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import assert from "node:assert/strict";
17
+
18
+ import { V2, readManifest } from "./_acceptance-fixture.js";
19
+ import type { RepairFx } from "./_repair-fixture.js";
20
+
21
+ const PREFIX = "self-healing";
22
+
23
+ /** Read one registered VC6C-IMPL fixture (asserting it IS registered). */
24
+ export function vc6cImplFixture(id: string): RepairFx {
25
+ const m = readManifest();
26
+ const row = m.fixtures.find(
27
+ (f) => f.id === id && f.path.startsWith(`${PREFIX}/`),
28
+ );
29
+ assert.ok(
30
+ row,
31
+ `fixture ${id} registered under ${PREFIX}/ in manifest`,
32
+ );
33
+ return JSON.parse(readFileSync(join(V2, row!.path), "utf8")) as RepairFx;
34
+ }
35
+
36
+ /**
37
+ * The six VC6C-IMPL fixture ids, in corpus order. The acceptance test drives
38
+ * each through the real production seam and asserts its pinned verdict.
39
+ */
40
+ export const VC6C_IMPL_IDS: readonly string[] = Array.from(
41
+ { length: 6 },
42
+ (_v, i) => `VC6C-IMPL-${String(i + 1).padStart(3, "0")}`,
43
+ );
@@ -0,0 +1,75 @@
1
+ /**
2
+ * vector-cortex/reconstruct/rebuild.ts — VC6C-IMPL production atomic rebuild.
3
+ *
4
+ * The thin *production executor* over the pure `heal/rebuild.ts` copy-verify-
5
+ * switch primitives. It materializes a NEW derived generation for the planned
6
+ * range, verifies the root manifest digest is a STRICT SUCCESSOR (the planned
7
+ * `generation` is `current + 1` and the switch refuses any non-monotonic move),
8
+ * and swaps the pointer in a single atomic commit. A failed verification keeps
9
+ * the old pointer and DELETES NO EVIDENCE: the orphaned generation is retained
10
+ * for inspection (heal/rebuild.ts crash-safety contract).
11
+ *
12
+ * REUSES, DOES NOT FORK. `rebuildGeneration` + `switchPointer` are the same
13
+ * functions VC6C shipped and tested (74 tests). This file only binds them to
14
+ * the production `RepairPlanV1` shape and the atomic-commit framing the
15
+ * post-compact handler calls — the whole point of VC6C-IMPL is that the pure
16
+ * primitives already exist and only the production seam was missing.
17
+ *
18
+ * STRICT SUCCESSOR. The pointer moves only when (a) verification passed and
19
+ * (b) the new generation is STRICTLY greater than the current one. Replaying a
20
+ * stale plan after a restart cannot roll the pointer backwards — the same
21
+ * monotonic guard `heal/rebuild.ts#switchPointer` enforces.
22
+ *
23
+ * THE AUTHORITY IS NEVER MUTATED. This rebuild only swaps the DERIVED generation
24
+ * pointer; the durable authority is untouched. `currentGeneration` is read to
25
+ * enforce monotonicity, never written.
26
+ *
27
+ * PURE. No storage, no console, no network (PREVENT-PI-004 / PREVENT-011);
28
+ * `node:crypto` comes via the heal digest helper.
29
+ */
30
+
31
+ import {
32
+ rebuildAndSwitch,
33
+ type PointerSwitch,
34
+ type RebuildInput,
35
+ type RebuildResult,
36
+ } from "../heal/rebuild.js";
37
+ import type { Mode } from "../heal/repair-types.js";
38
+ import type { RepairPlanV1 } from "./repair-plan.js";
39
+
40
+ export type { PointerSwitch, RebuildInput, RebuildResult };
41
+
42
+ /**
43
+ * The outcome of one atomic rebuild attempt. `result` is the verification
44
+ * verdict; `pointer` is the atomic commit — `switched:true` only when the root
45
+ * digest verified AND the generation advanced strictly. On `switched:false`
46
+ * the live generation is unchanged and the orphaned generation is retained.
47
+ */
48
+ export interface AtomicRebuild {
49
+ readonly plan: RepairPlanV1;
50
+ readonly result: RebuildResult;
51
+ readonly pointer: PointerSwitch;
52
+ }
53
+
54
+ /**
55
+ * Materialize + atomically switch a planned repair range.
56
+ *
57
+ * `rebuildInput` carries the materialized new-generation bytes and the root
58
+ * digest the plan pinned. The helper reuses `heal/rebuild.ts#rebuildAndSwitch`,
59
+ * which verifies the digest FIRST and refuses to switch under any combination of
60
+ * failed verification or non-strict generation — "switch without verifying" is
61
+ * not expressible.
62
+ */
63
+ export function rebuildRepairRange(
64
+ plan: RepairPlanV1,
65
+ rebuildInput: RebuildInput,
66
+ currentGeneration: number,
67
+ mode: Mode = "A",
68
+ ): AtomicRebuild {
69
+ const { result, pointer } = rebuildAndSwitch(
70
+ rebuildInput,
71
+ currentGeneration,
72
+ mode,
73
+ );
74
+ return { plan, result, pointer };
75
+ }