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,28 @@
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
+ import { sprintFlag } from "./vector-cortex-flag.js";
18
+ /**
19
+ * ML5-A — five-head training + calibrated onnx asset. Default ON.
20
+ * `MEGACOMPACT_ML5_A=0` disables and is byte-identical to the placeholder
21
+ * predecessor (VC2C-era): `calibrate.ts`/`heads.ts` keep serving the LCG fake
22
+ * projections and the placeholder `fitTemperature`/`fitThreshold`, mode B
23
+ * trigram continues serving, and no trained artifact is loaded (a fresh/no
24
+ * corpus also no-ops gracefully — asset_emitted:false, placeholder behavior,
25
+ * byte-identical). This flag MUST also be a dashboard SETTINGS toggle (visible
26
+ * in config UI, never in EXCLUDED_SETTINGS), mirroring VC4A..VC9D.
27
+ */
28
+ export const ML5A_ENABLED = () => sprintFlag("MEGACOMPACT_ML5_A");
@@ -239,16 +239,15 @@ export const VC8A_ENABLED = () => sprintFlag("MEGACOMPACT_VC8A");
239
239
  * mirroring VC4A..VC8A.
240
240
  */
241
241
  export const VC8B_ENABLED = () => sprintFlag("MEGACOMPACT_VC8B");
242
- // VC8C (canary selection + external Rust parity) extracted to
243
- // vector-cortex-vc8c.ts to keep this file under the 300-line soft limit.
244
- // Re-exported here so every existing `from "./config/vector-cortex.js"`
245
- // import keeps resolving unchanged.
242
+ // VC8C (canary selection + Rust parity) extracted to vector-cortex-vc8c.ts;
243
+ // re-exported so existing `./config/vector-cortex.js` imports keep resolving.
246
244
  export { VC8C_ENABLED } from "./vector-cortex-vc8c.js";
247
- // VC9A/VC9B/VC9C/VC9D split to vector-cortex-vc9{a,b,c,d}.ts to stay under the 300-line soft limit.
245
+ // VC9A/VC9B/VC9C/VC9D/PCC/ML5A split to sibling files to stay under the 300-line soft limit.
248
246
  export { VC9A_ENABLED } from "./vector-cortex-vc9a.js";
249
247
  export { VC9B_ENABLED } from "./vector-cortex-vc9b.js";
250
248
  export { VC9C_ENABLED } from "./vector-cortex-vc9c.js";
251
249
  export { VC9D_ENABLED } from "./vector-cortex-vc9d.js";
252
250
  export { PCC_ENABLED } from "./vector-cortex-pcc.js";
251
+ export { ML5A_ENABLED } from "./vector-cortex-ml5a.js";
253
252
  // Breaker constants (TRIAD_RESILIENCE.md §breaker) extracted to vector-cortex-breakers.ts.
254
253
  export { BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./vector-cortex-breakers.js";
package/dist/config.js CHANGED
@@ -114,4 +114,4 @@ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
114
114
  // default ON, `=0`/`_DISABLED` off. Re-exported from src/config/vector-cortex.ts
115
115
  // so root consumers share one source of truth.
116
116
  // ---------------------------------------------------------------------------
117
- export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, VC8B_ENABLED, VC8C_ENABLED, VC9A_ENABLED, VC9B_ENABLED, VC9C_ENABLED, VC9D_ENABLED, PCC_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
117
+ export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, VC8B_ENABLED, VC8C_ENABLED, VC9A_ENABLED, VC9B_ENABLED, VC9C_ENABLED, VC9D_ENABLED, PCC_ENABLED, ML5A_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
@@ -53,5 +53,6 @@ export const VECTOR_CORTEX_SETTINGS = {
53
53
  boolDirect("MEGACOMPACT_VC9C", "VC9C SetupTab Cortex Sub-tab", "SetupTab Cortex sub-tab (client UI): a Cortex sub-tab inside SetupTab that consumes the VC9A status endpoint (GET /api/setup-cortex-status) + the VC9B action endpoints. It surfaces the encoder mode A/B/C, asset digest prefix, qualification verdict + threshold failures, the open hard-gate blockers, and the confirmation-gated fetch/bench/verify actions. OFF = byte-identical predecessor (VC9B-era): the Cortex sub-tab is filtered from SUB_TABS and the Setup tab renders exactly as before.", true),
54
54
  boolDirect("MEGACOMPACT_VC9D", "VC9D Embedder Detect Consolidation", "Embedder-detect consolidation + VC9 workstream roll-up: memoizes /api/setup-detect against the mutable input (resolved binary path + mtime) so consecutive requests reuse the result without re-spawning, and unifies the embedder + cortex sub-tabs' 5s poll contract. OFF = byte-identical predecessor (VC9C-era): detect spawns fresh per request and the embedder poll keeps its previous cadence.", true),
55
55
  boolDirect("MEGACOMPACT_PC_C", "PC-C Dashboard Cache Visibility", "Dashboard per-turn prompt-cache visibility: surfaces the per-turn stable-prefix ratio trend (GET /api/prefix-stability) in the CacheTab PrefixStabilityCard. Reads aggregate ratios/counts from the local monitoring events log only — no payload bytes. OFF = byte-identical predecessor (PC-B-era): /api/prefix-stability returns 404 and the CacheTab omits the PrefixStabilityCard.", true),
56
+ boolDirect("MEGACOMPACT_ML5_A", "ML5-A Five-Head Training Load", "ML5-A real trained-head loading: loadHeadProjections (trained-heads-v1) feeds selectQualifiedEncoder (trainedHeadsPath atomic demotion) + loadCalibrationV1. ON (default) = a pinned trained-heads path must load for mode A. OFF = loaders return null and selection ignores trainedHeadsPath — byte-identical to the placeholder-weighted VC2C path.", true),
56
57
  ],
57
58
  };
@@ -7,6 +7,7 @@
7
7
  * raw_transcript, and fire-and-forgets the dedup pipeline. All best-effort +
8
8
  * non-fatal — a failure never breaks the agent loop.
9
9
  */
10
+ import { createHash } from "node:crypto";
10
11
  import { openStore, writeCheckpointEpoch, } from "../../../src/store/sqlite.js";
11
12
  import { epochIdFor } from "../../../src/mirror/epoch.js";
12
13
  import { stampTurnsEpochFor } from "../../mega-turn-store.js";
@@ -17,6 +18,7 @@ import { assignNewMemoriesIncremental } from "../../../src/wiki/index.js";
17
18
  import { TrigramEmbedder } from "../../../src/embedder.js";
18
19
  import { reportClosureOptimized } from "../../../src/vector-cortex/heal/emit.js";
19
20
  import { reportRepairPlanned } from "../../../src/vector-cortex/heal/repair-emit.js";
21
+ import { buildPostCompactViews, drivePostCompactRepair, } from "./controller.js";
20
22
  import { VC6A_ENABLED, VC6C_ENABLED } from "../../../src/config/vector-cortex.js";
21
23
  /**
22
24
  * Persist the checkpoint_epoch, stamp turns, rebuild the auto-wiki, seed the
@@ -215,9 +217,8 @@ export async function persistEpochAndMaintain(runtime, config, ran) {
215
217
  runtime.logger.warn("db-mirror-epoch-fail", { error: String(e) });
216
218
  }
217
219
  }
218
- // VC6 Heal lifecycle emits (post-compact). Wiring stubs — dashboard graph +
219
- // event counts move; real gap detection/rebuild is a future sprint.
220
- // VC6A: closure-optimization savings from the compact token delta.
220
+ // VC6 Heal lifecycle emits (post-compact). VC6A: closure-optimization
221
+ // savings from the compact token delta.
221
222
  const savings = Math.max(0, (ran.result.originalTokenEstimate ?? 0) - (ran.result.tokenEstimate ?? 0));
222
223
  if (VC6A_ENABLED()) {
223
224
  try {
@@ -232,8 +233,36 @@ export async function persistEpochAndMaintain(runtime, config, ran) {
232
233
  /* non-fatal: VC6A heal emit never breaks compaction */
233
234
  }
234
235
  }
235
- // VC6C: repair-planner placeholder (no real gap detection yet).
236
+ // VC6C: real post-compact gap detection + atomic repair drive (VC6C-IMPL).
237
+ // Builds each derived subsystem's pre/post compact view against the durable
238
+ // authority high-water, runs the heal eligibility policy (gap-ness, frozen
239
+ // authority, mode C, the 5-min rate limit), and only on a REAL gap routes
240
+ // plan -> rebuild -> emit the three repair events. No real gap => emit
241
+ // NOTHING (VC6C-IMPL-006: no rebuild without a real gap). Flag OFF keeps the
242
+ // predecessor placeholder byte-identical (reportRepairPlanned with hardcoded
243
+ // backoffMs:0, gapSize:compactedFrom) and rebuild is a no-op — the reported
244
+ // seam is flag-gated, so the placeholder emits nothing, exactly as before.
236
245
  if (VC6C_ENABLED()) {
246
+ try {
247
+ const emit = (name, payload) => runtime.appendEvent(name, payload);
248
+ const views = buildPostCompactViews(ran.result.compactedFrom, runtime.rt.compactCount);
249
+ drivePostCompactRepair(views, BigInt(Date.now()), emit, () => {
250
+ // The derived generation for the repaired range is re-materialized
251
+ // from the compact summary and verified as a strict successor.
252
+ const bytes = new Uint8Array(Buffer.from(ran.result.summary, "utf8"));
253
+ const digest = createHash("sha256")
254
+ .update(bytes)
255
+ .digest("hex");
256
+ return { sourceBytes: bytes, expectedDigest: digest };
257
+ });
258
+ }
259
+ catch {
260
+ /* non-fatal: VC6C heal repair never breaks compaction */
261
+ }
262
+ }
263
+ else {
264
+ // Flag-off: predecessor placeholder, byte-identical (emits nothing via
265
+ // the flag-gated reporter seam).
237
266
  try {
238
267
  reportRepairPlanned((name, payload) => runtime.appendEvent(name, payload), {
239
268
  subsystem: "post_compact",
@@ -0,0 +1,161 @@
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
+ import { isPlannable } from "../../../src/vector-cortex/heal/controller.js";
30
+ import { reportRepairBackoff, reportRepairPlanned, reportRepairPointerSwitched, } from "../../../src/vector-cortex/heal/repair-emit.js";
31
+ import { buildRepairPlan, gapSizeOf, } from "../../../src/vector-cortex/reconstruct/repair-plan.js";
32
+ import { rebuildRepairRange, } from "../../../src/vector-cortex/reconstruct/rebuild.js";
33
+ /**
34
+ * Detect the subsystems whose POST-compact derived high-water fell behind the
35
+ * durable authority. `left` is the pre-compact view, `right` the post-compact
36
+ * view (aligned by subsystem name); a subsystem qualifies when its POST count
37
+ * is strictly below its durable authority high-water. Pure — no clock, no
38
+ * writes.
39
+ */
40
+ export function detectPostCompactGaps(left, right) {
41
+ const byName = new Map(right.map((v) => [v.subsystem, v]));
42
+ const gapped = [];
43
+ for (const l of left) {
44
+ const r = byName.get(l.subsystem);
45
+ if (r === undefined)
46
+ continue;
47
+ if (r.postCount < r.authorityHighWater)
48
+ gapped.push(r);
49
+ }
50
+ return gapped;
51
+ }
52
+ /** Map a production post-compact view into the heal `RepairState` judge shape. */
53
+ export function toRepairState(view) {
54
+ return {
55
+ subsystem: view.subsystem,
56
+ derivedHighWater: BigInt(view.postCount),
57
+ authorityHighWater: BigInt(view.authorityHighWater),
58
+ lastRebuildAt: view.lastRebuildAtMs,
59
+ generation: view.generation,
60
+ mode: view.mode,
61
+ ...(view.failedAttempts !== undefined ? { failedAttempts: view.failedAttempts } : {}),
62
+ ...(view.authorityFrozen !== undefined ? { authorityFrozen: view.authorityFrozen } : {}),
63
+ };
64
+ }
65
+ /** Build the production plan for one gapped view. */
66
+ export function planFor(view) {
67
+ return buildRepairPlan(view);
68
+ }
69
+ function rebuildInputFor(plan, src) {
70
+ return {
71
+ subsystem: plan.subsystem,
72
+ range: {
73
+ sessionId: plan.subsystem,
74
+ seqStart: BigInt(plan.range[0]),
75
+ seqEnd: BigInt(plan.range[1]),
76
+ byteStart: 0,
77
+ byteEnd: 0,
78
+ },
79
+ generation: plan.generation,
80
+ sourceBytes: src.sourceBytes,
81
+ expectedDigest: src.expectedDigest,
82
+ };
83
+ }
84
+ /**
85
+ * Drive one repair for a gapped subsystem: plan → rebuild → emit.
86
+ *
87
+ * Emits `reportRepairPlanned` first (the plan with its deterministic backoff),
88
+ * then executes the atomic rebuild; a verified strict-successor switch emits
89
+ * `reportRepairPointerSwitched`, a failed rebuild emits `reportRepairBackoff`.
90
+ * `currentGeneration` (the live generation) is read for the monotonic switch.
91
+ */
92
+ export function driveOneRepair(view, emit, rebuildSource) {
93
+ const plan = planFor(view);
94
+ reportRepairPlanned(emit, {
95
+ subsystem: plan.subsystem,
96
+ generation: plan.generation,
97
+ backoffMs: plan.backoffMs,
98
+ gapSize: gapSizeOf(view),
99
+ });
100
+ const rebuilt = rebuildRepairRange(plan, rebuildInputFor(plan, rebuildSource), view.generation, view.mode);
101
+ if (rebuilt.pointer.switched) {
102
+ reportRepairPointerSwitched(emit, {
103
+ subsystem: plan.subsystem,
104
+ fromGeneration: view.generation,
105
+ toGeneration: plan.generation,
106
+ mode: view.mode,
107
+ });
108
+ }
109
+ else {
110
+ reportRepairBackoff(emit, {
111
+ subsystem: plan.subsystem,
112
+ code: rebuilt.result.ok ? "HEAL_REPAIR_RATE_LIMITED" : (rebuilt.result.code ?? "HEAL_REBUILD_FAILED"),
113
+ backoffMs: plan.backoffMs,
114
+ attempt: view.failedAttempts ?? 0,
115
+ });
116
+ }
117
+ return { plan, rebuilt };
118
+ }
119
+ /**
120
+ * The full post-compact repair drive. Applies heal's eligibility policy
121
+ * (`isPlannable` — rate limit, no gap, frozen authority, mode C) per subsystem,
122
+ * and only runs `driveOneRepair` for subsystems with a REAL, actionable gap. A
123
+ * subsystem with no real gap, or inside its rate-limit window, emits NOTHING.
124
+ *
125
+ * `rebuildSourceFor` is an injected executor that materializes a new generation
126
+ * for a plannable subsystem (the handler supplies the real one; fixtures supply
127
+ * a deterministic one), keeping the drive testable without a runtime.
128
+ */
129
+ export function drivePostCompactRepair(views, nowMs, emit, rebuildSourceFor) {
130
+ for (const view of views) {
131
+ const state = toRepairState(view);
132
+ if (!isPlannable(state, nowMs))
133
+ continue;
134
+ driveOneRepair(view, emit, rebuildSourceFor(view));
135
+ }
136
+ }
137
+ /**
138
+ * Build the production post-compact subsystem views from a compact result.
139
+ *
140
+ * `compactedFrom` is the committed seq frontier after compaction. In a NORMAL
141
+ * compact the derived post-count equals the durable authority high-water (they
142
+ * advance together), so the resulting view has NO real gap — the drive emits
143
+ * nothing (VC6C-IMPL-006). A caller that derives per-subsystem counts where a
144
+ * derived tier fell behind authority supplies those lower counts here, and the
145
+ * drive will detect the gap and repair it. `currentGeneration` seeds the derived
146
+ * generation counter.
147
+ */
148
+ export function buildPostCompactViews(compactedFrom, currentGeneration, authorityHighWater = compactedFrom, postCount = compactedFrom) {
149
+ return [
150
+ {
151
+ subsystem: "post_compact",
152
+ preCount: compactedFrom,
153
+ postCount,
154
+ authorityHighWater,
155
+ generation: currentGeneration,
156
+ failedAttempts: 0,
157
+ mode: "A",
158
+ lastRebuildAtMs: null,
159
+ },
160
+ ];
161
+ }
@@ -0,0 +1,28 @@
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
+ import { sprintFlag } from "./vector-cortex-flag.js";
18
+ /**
19
+ * ML5-A — five-head training + calibrated onnx asset. Default ON.
20
+ * `MEGACOMPACT_ML5_A=0` disables and is byte-identical to the placeholder
21
+ * predecessor (VC2C-era): `calibrate.ts`/`heads.ts` keep serving the LCG fake
22
+ * projections and the placeholder `fitTemperature`/`fitThreshold`, mode B
23
+ * trigram continues serving, and no trained artifact is loaded (a fresh/no
24
+ * corpus also no-ops gracefully — asset_emitted:false, placeholder behavior,
25
+ * byte-identical). This flag MUST also be a dashboard SETTINGS toggle (visible
26
+ * in config UI, never in EXCLUDED_SETTINGS), mirroring VC4A..VC9D.
27
+ */
28
+ export const ML5A_ENABLED = () => sprintFlag("MEGACOMPACT_ML5_A");
@@ -239,16 +239,15 @@ export const VC8A_ENABLED = () => sprintFlag("MEGACOMPACT_VC8A");
239
239
  * mirroring VC4A..VC8A.
240
240
  */
241
241
  export const VC8B_ENABLED = () => sprintFlag("MEGACOMPACT_VC8B");
242
- // VC8C (canary selection + external Rust parity) extracted to
243
- // vector-cortex-vc8c.ts to keep this file under the 300-line soft limit.
244
- // Re-exported here so every existing `from "./config/vector-cortex.js"`
245
- // import keeps resolving unchanged.
242
+ // VC8C (canary selection + Rust parity) extracted to vector-cortex-vc8c.ts;
243
+ // re-exported so existing `./config/vector-cortex.js` imports keep resolving.
246
244
  export { VC8C_ENABLED } from "./vector-cortex-vc8c.js";
247
- // VC9A/VC9B/VC9C/VC9D split to vector-cortex-vc9{a,b,c,d}.ts to stay under the 300-line soft limit.
245
+ // VC9A/VC9B/VC9C/VC9D/PCC/ML5A split to sibling files to stay under the 300-line soft limit.
248
246
  export { VC9A_ENABLED } from "./vector-cortex-vc9a.js";
249
247
  export { VC9B_ENABLED } from "./vector-cortex-vc9b.js";
250
248
  export { VC9C_ENABLED } from "./vector-cortex-vc9c.js";
251
249
  export { VC9D_ENABLED } from "./vector-cortex-vc9d.js";
252
250
  export { PCC_ENABLED } from "./vector-cortex-pcc.js";
251
+ export { ML5A_ENABLED } from "./vector-cortex-ml5a.js";
253
252
  // Breaker constants (TRIAD_RESILIENCE.md §breaker) extracted to vector-cortex-breakers.ts.
254
253
  export { BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./vector-cortex-breakers.js";
@@ -114,4 +114,4 @@ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
114
114
  // default ON, `=0`/`_DISABLED` off. Re-exported from src/config/vector-cortex.ts
115
115
  // so root consumers share one source of truth.
116
116
  // ---------------------------------------------------------------------------
117
- export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, VC8B_ENABLED, VC8C_ENABLED, VC9A_ENABLED, VC9B_ENABLED, VC9C_ENABLED, VC9D_ENABLED, PCC_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
117
+ export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, VC8B_ENABLED, VC8C_ENABLED, VC9A_ENABLED, VC9B_ENABLED, VC9C_ENABLED, VC9D_ENABLED, PCC_ENABLED, ML5A_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
@@ -22,6 +22,8 @@
22
22
  * (PREVENT-011).
23
23
  */
24
24
  import { createHash } from "node:crypto";
25
+ import { readFileSync } from "node:fs";
26
+ import { ML5A_ENABLED } from "../../config/vector-cortex.js";
25
27
  import { ENCODER_HEAD_ORDER, ENCODER_SEED, ENC_QUALIFICATION_FAIL, } from "./types.js";
26
28
  /** Canonical digests of a sorted stable representation (order-invariant). */
27
29
  function digestStrings(values) {
@@ -172,3 +174,56 @@ export function fitCalibration(examples, options = {}) {
172
174
  };
173
175
  return { ok: true, calibration };
174
176
  }
177
+ /**
178
+ * Load a persisted `CalibrationV1` artifact (schema "calibration-v1") from disk.
179
+ * ML5-A: gated on MEGACOMPACT_ML5_A; flag-off, absent file, malformed JSON,
180
+ * wrong schema, non-canonical five-head order, or non-finite temp/threshold each
181
+ * return null (non-fatal, never throws). Deterministic, local (PREVENT-PI-004).
182
+ */
183
+ export function loadCalibrationV1(path) {
184
+ if (!ML5A_ENABLED())
185
+ return null;
186
+ let raw;
187
+ try {
188
+ raw = readFileSync(path, "utf8");
189
+ }
190
+ catch {
191
+ return null;
192
+ }
193
+ let parsed;
194
+ try {
195
+ parsed = JSON.parse(raw);
196
+ }
197
+ catch {
198
+ return null;
199
+ }
200
+ const r = parsed;
201
+ if (!r || r["schema"] !== "calibration-v1")
202
+ return null;
203
+ const order = r["headOrder"];
204
+ if (!Array.isArray(order))
205
+ return null;
206
+ if (order.length !== ENCODER_HEAD_ORDER.length || !ENCODER_HEAD_ORDER.every((h, i) => order[i] === h)) {
207
+ return null;
208
+ }
209
+ const temperatures = r["temperatures"];
210
+ const thresholds = r["thresholds"];
211
+ const splitDigest = r["calibrationSplitDigest"];
212
+ if (!temperatures || !thresholds || typeof splitDigest !== "string" || splitDigest.length !== 64)
213
+ return null;
214
+ for (const h of ENCODER_HEAD_ORDER) {
215
+ const t = Number(temperatures[h]);
216
+ const th = Number(thresholds[h]);
217
+ if (!Number.isFinite(t) || !Number.isFinite(th))
218
+ return null;
219
+ }
220
+ return {
221
+ schema: "calibration-v1",
222
+ headOrder: [...ENCODER_HEAD_ORDER],
223
+ calibrationSplitDigest: splitDigest,
224
+ fittedOnCalibrationOnly: true,
225
+ temperatures: { ...temperatures },
226
+ thresholds: { ...thresholds },
227
+ seed: Number(r["seed"] ?? ENCODER_SEED),
228
+ };
229
+ }
@@ -19,6 +19,8 @@
19
19
  *
20
20
  * Pi-agnostic, zero network (PREVENT-PI-004), no `any` (PREVENT-011).
21
21
  */
22
+ import { readFileSync } from "node:fs";
23
+ import { ML5A_ENABLED } from "../../config/vector-cortex.js";
22
24
  import { ENCODER_HEAD_DIMS, ENCODER_HEAD_ORDER, ENCODER_HEAD_LOSS_WEIGHTS, ENCODER_HEAD_LOSS_SUM, ENCODER_SEED, } from "./types.js";
23
25
  import { createEncoderHeadsReporter, NOOP_VC2B_REPORTER, } from "./emit-vc2b.js";
24
26
  /** The stable head index of a head name (its position in ENCODER_HEAD_ORDER). */
@@ -110,4 +112,89 @@ export function encodeVectorSet(tokens, options = {}) {
110
112
  export function headLossWeights() {
111
113
  return { ...ENCODER_HEAD_LOSS_WEIGHTS };
112
114
  }
115
+ /** True when every head's output dim + weight length matches the contract. */
116
+ export function headsShapeValid(t) {
117
+ return ENCODER_HEAD_ORDER.every((h) => t.dims[h] === ENCODER_HEAD_DIMS[h] && t.weights[h].length === ENCODER_HEAD_DIMS[h] * t.trunkDim);
118
+ }
119
+ /**
120
+ * Load a `trained-heads-v1` artifact into a `HeadProjectionTable`. Gated on
121
+ * MEGACOMPACT_ML5_A: flag-off, absent file, malformed JSON, wrong schema,
122
+ * wrong seed, or a shape mismatch each return null (non-fatal). Deterministic
123
+ * and local (PREVENT-PI-004).
124
+ */
125
+ export function loadHeadProjections(path) {
126
+ if (!ML5A_ENABLED())
127
+ return null;
128
+ let raw;
129
+ try {
130
+ raw = readFileSync(path, "utf8");
131
+ }
132
+ catch {
133
+ return null;
134
+ }
135
+ let parsed;
136
+ try {
137
+ parsed = JSON.parse(raw);
138
+ }
139
+ catch {
140
+ return null;
141
+ }
142
+ const r = parsed;
143
+ if (!r || r["schema"] !== "trained-heads-v1")
144
+ return null;
145
+ if (r["seed"] !== ENCODER_SEED)
146
+ return null;
147
+ const dims = r["dims"];
148
+ const heads = r["heads"];
149
+ if (!dims || !heads)
150
+ return null;
151
+ const trunkDim = Number(r["trunkDim"] ?? 0);
152
+ if (!Number.isFinite(trunkDim) || trunkDim <= 0)
153
+ return null;
154
+ const weights = {};
155
+ const temperatures = {};
156
+ for (const h of ENCODER_HEAD_ORDER) {
157
+ const hd = heads[h];
158
+ if (!hd || typeof hd !== "object")
159
+ return null;
160
+ const w = hd["weights"];
161
+ if (!Array.isArray(w))
162
+ return null;
163
+ weights[h] = Float32Array.from(w);
164
+ if (Number(hd["dim"] ?? 0) !== ENCODER_HEAD_DIMS[h])
165
+ return null;
166
+ temperatures[h] = Number(hd["temperature"] ?? 1);
167
+ if (!Number.isFinite(dims[h]))
168
+ return null;
169
+ }
170
+ const table = {
171
+ schema: "trained-heads-v1",
172
+ seed: Number(r["seed"]),
173
+ trunkDim,
174
+ dims: { semantic: 384, dependency: 128, contradiction: 128, cacheStability: 64, payloadRouting: 32 },
175
+ weights: weights,
176
+ temperatures: temperatures,
177
+ };
178
+ if (!headsShapeValid(table))
179
+ return null;
180
+ return table;
181
+ }
182
+ /**
183
+ * Project a trunk embedding through a trained head's real weights, applying the
184
+ * row-major matrix then L2-normalizing (all-zero on zero norm). Returns a
185
+ * `HeadVector` of the head's normative dimension.
186
+ */
187
+ export function projectHeadFromTrunk(head, trunk, table) {
188
+ const dim = ENCODER_HEAD_DIMS[head];
189
+ const W = table.weights[head];
190
+ const t = table.trunkDim;
191
+ const out = new Float32Array(dim);
192
+ for (let i = 0; i < dim; i++) {
193
+ let acc = 0;
194
+ for (let j = 0; j < t; j++)
195
+ acc += W[i * t + j] * (trunk[j] ?? 0);
196
+ out[i] = acc;
197
+ }
198
+ return { head, dim, values: l2Normalize(out) };
199
+ }
113
200
  export { ENCODER_HEAD_ORDER, ENCODER_HEAD_DIMS, ENCODER_HEAD_LOSS_SUM, ENCODER_SEED, NOOP_VC2B_REPORTER };
@@ -33,6 +33,8 @@
33
33
  * `any` (PREVENT-011).
34
34
  */
35
35
  import { createHash } from "node:crypto";
36
+ import { ML5A_ENABLED } from "../../config/vector-cortex.js";
37
+ import { loadHeadProjections } from "./heads.js";
36
38
  import { ENC_QUALIFICATION_FAIL, EVALUATION_THRESHOLDS, } from "./types.js";
37
39
  import { createEncoderQualificationReporter, } from "./emit-vc2c.js";
38
40
  /** Canonical digest over a CalibrationV1's stable identity (split digest, heads,
@@ -118,6 +120,14 @@ export function selectQualifiedEncoder(candidate, options = {}) {
118
120
  }
119
121
  // Atomic: collect EVERY failed field across asset + all heads + reconstruction.
120
122
  const failed = [];
123
+ // ML5-A: real trained-head weights must load for mode A. When the gate is on
124
+ // and a trained-heads path is pinned, an unloadable/wrong-seed/malformed
125
+ // artifact is a qualification failure (any failed field demotes ALL of A).
126
+ if (ML5A_ENABLED() && candidate.trainedHeadsPath !== undefined) {
127
+ if (loadHeadProjections(candidate.trainedHeadsPath) === null) {
128
+ failed.push("head.weights.trainedHeadsPath");
129
+ }
130
+ }
121
131
  assetPasses(candidate.asset, failed);
122
132
  const heads = ["semantic", "dependency", "contradiction", "cacheStability", "payloadRouting"];
123
133
  for (const h of heads) {
@@ -0,0 +1,29 @@
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
+ import { readFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import assert from "node:assert/strict";
16
+ import { V2, readManifest } from "./_acceptance-fixture.js";
17
+ const PREFIX = "self-healing";
18
+ /** Read one registered VC6C-IMPL fixture (asserting it IS registered). */
19
+ export function vc6cImplFixture(id) {
20
+ const m = readManifest();
21
+ const row = m.fixtures.find((f) => f.id === id && f.path.startsWith(`${PREFIX}/`));
22
+ assert.ok(row, `fixture ${id} registered under ${PREFIX}/ in manifest`);
23
+ return JSON.parse(readFileSync(join(V2, row.path), "utf8"));
24
+ }
25
+ /**
26
+ * The six VC6C-IMPL fixture ids, in corpus order. The acceptance test drives
27
+ * each through the real production seam and asserts its pinned verdict.
28
+ */
29
+ export const VC6C_IMPL_IDS = Array.from({ length: 6 }, (_v, i) => `VC6C-IMPL-${String(i + 1).padStart(3, "0")}`);
@@ -0,0 +1,43 @@
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
+ import { rebuildAndSwitch, } from "../heal/rebuild.js";
31
+ /**
32
+ * Materialize + atomically switch a planned repair range.
33
+ *
34
+ * `rebuildInput` carries the materialized new-generation bytes and the root
35
+ * digest the plan pinned. The helper reuses `heal/rebuild.ts#rebuildAndSwitch`,
36
+ * which verifies the digest FIRST and refuses to switch under any combination of
37
+ * failed verification or non-strict generation — "switch without verifying" is
38
+ * not expressible.
39
+ */
40
+ export function rebuildRepairRange(plan, rebuildInput, currentGeneration, mode = "A") {
41
+ const { result, pointer } = rebuildAndSwitch(rebuildInput, currentGeneration, mode);
42
+ return { plan, result, pointer };
43
+ }