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,57 @@
1
+ /**
2
+ * vector-cortex/reconstruct/repair-plan.ts — VC6C-IMPL production repair plan seam.
3
+ *
4
+ * Maps a compact result into the production-facing plan the handler emits. The
5
+ * pure `heal/repair-types.ts` contract stays the canonical design carrier; this
6
+ * file owns the PRODUCTION shape (`RepairPlanV1` / `RepairEventV1`) that the
7
+ * post-compact controller drives, plus the builder that turns a per-subsystem
8
+ * gap into a plan.
9
+ *
10
+ * RELATIONSHIP TO heal/. The `heal/` modules (VC6C) already ship the pure
11
+ * gap-detection / backoff / rebuild / switch primitives and 74 passing tests.
12
+ * This sprint wires them into the production compact path; it does NOT fork
13
+ * them. `buildRepairPlan` reuses `computeBackoff` for the deterministic
14
+ * exponential delay (30s * 2^attempt, 15 min cap, ±10% SHA-256-derived jitter,
15
+ * never `Math.random`) so a plan's `backoffMs` is byte-reproducible in a
16
+ * fixture. Gap arithmetic mirrors `heal/controller.ts#gapRange`: the plan's seq
17
+ * window is `derived post-count + 1 .. durable authority high-water`, inclusive,
18
+ * exactly the unbuilt range.
19
+ *
20
+ * THE AUTHORITY IS NEVER WRITTEN. A plan carries `authorityHighWater` for
21
+ * identity only; no code here (or anywhere in this sprint) mutates the durable
22
+ * authority. `generation` is the NEW derived generation the rebuild writes into
23
+ * (always `current + 1`), mirroring the heal copy-then-switch rule.
24
+ *
25
+ * PURE. `node:crypto` is used only transitively through `computeBackoff`; no
26
+ * storage, no console, no clock of its own (`nowMs` is injected), no network
27
+ * (PREVENT-PI-004 / PREVENT-011).
28
+ */
29
+ import { computeBackoff } from "../heal/controller.js";
30
+ /**
31
+ * The size of the gap (how many seq steps the derived tier fell behind the
32
+ * authority), used for the `gapSize` event payload — derived from the plan
33
+ * inputs, never a hardcoded literal.
34
+ */
35
+ export function gapSizeOf(gap) {
36
+ return Math.max(0, gap.authorityHighWater - gap.postCount);
37
+ }
38
+ /**
39
+ * Map one subsystem's post-compact gap into a production `RepairPlanV1`.
40
+ *
41
+ * Mirrors `heal/controller.ts#planRebuild`: the seq window is
42
+ * `[postCount + 1, authorityHighWater]`, the generation targets `current + 1`,
43
+ * and the backoff is the deterministic heal `computeBackoff`. No clock is read
44
+ * here — the production shape carries no `scheduledAt`; the `nowMs`-injected
45
+ * rate-limit/backoff decisions live in the heal controller (`detectGaps`), which
46
+ * the handler drives with its own injected clock for reproducible fixtures.
47
+ */
48
+ export function buildRepairPlan(gap) {
49
+ const backoffMs = computeBackoff(gap.subsystem, gap.failedAttempts ?? 0);
50
+ return {
51
+ schema: "repair-plan-v1",
52
+ subsystem: gap.subsystem,
53
+ range: [gap.postCount + 1, gap.authorityHighWater],
54
+ generation: gap.generation + 1,
55
+ backoffMs,
56
+ };
57
+ }
@@ -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
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * vector-cortex/reconstruct/repair-plan.ts — VC6C-IMPL production repair plan seam.
3
+ *
4
+ * Maps a compact result into the production-facing plan the handler emits. The
5
+ * pure `heal/repair-types.ts` contract stays the canonical design carrier; this
6
+ * file owns the PRODUCTION shape (`RepairPlanV1` / `RepairEventV1`) that the
7
+ * post-compact controller drives, plus the builder that turns a per-subsystem
8
+ * gap into a plan.
9
+ *
10
+ * RELATIONSHIP TO heal/. The `heal/` modules (VC6C) already ship the pure
11
+ * gap-detection / backoff / rebuild / switch primitives and 74 passing tests.
12
+ * This sprint wires them into the production compact path; it does NOT fork
13
+ * them. `buildRepairPlan` reuses `computeBackoff` for the deterministic
14
+ * exponential delay (30s * 2^attempt, 15 min cap, ±10% SHA-256-derived jitter,
15
+ * never `Math.random`) so a plan's `backoffMs` is byte-reproducible in a
16
+ * fixture. Gap arithmetic mirrors `heal/controller.ts#gapRange`: the plan's seq
17
+ * window is `derived post-count + 1 .. durable authority high-water`, inclusive,
18
+ * exactly the unbuilt range.
19
+ *
20
+ * THE AUTHORITY IS NEVER WRITTEN. A plan carries `authorityHighWater` for
21
+ * identity only; no code here (or anywhere in this sprint) mutates the durable
22
+ * authority. `generation` is the NEW derived generation the rebuild writes into
23
+ * (always `current + 1`), mirroring the heal copy-then-switch rule.
24
+ *
25
+ * PURE. `node:crypto` is used only transitively through `computeBackoff`; no
26
+ * storage, no console, no clock of its own (`nowMs` is injected), no network
27
+ * (PREVENT-PI-004 / PREVENT-011).
28
+ */
29
+ import { computeBackoff } from "../heal/controller.js";
30
+ /**
31
+ * The size of the gap (how many seq steps the derived tier fell behind the
32
+ * authority), used for the `gapSize` event payload — derived from the plan
33
+ * inputs, never a hardcoded literal.
34
+ */
35
+ export function gapSizeOf(gap) {
36
+ return Math.max(0, gap.authorityHighWater - gap.postCount);
37
+ }
38
+ /**
39
+ * Map one subsystem's post-compact gap into a production `RepairPlanV1`.
40
+ *
41
+ * Mirrors `heal/controller.ts#planRebuild`: the seq window is
42
+ * `[postCount + 1, authorityHighWater]`, the generation targets `current + 1`,
43
+ * and the backoff is the deterministic heal `computeBackoff`. No clock is read
44
+ * here — the production shape carries no `scheduledAt`; the `nowMs`-injected
45
+ * rate-limit/backoff decisions live in the heal controller (`detectGaps`), which
46
+ * the handler drives with its own injected clock for reproducible fixtures.
47
+ */
48
+ export function buildRepairPlan(gap) {
49
+ const backoffMs = computeBackoff(gap.subsystem, gap.failedAttempts ?? 0);
50
+ return {
51
+ schema: "repair-plan-v1",
52
+ subsystem: gap.subsystem,
53
+ range: [gap.postCount + 1, gap.authorityHighWater],
54
+ generation: gap.generation + 1,
55
+ backoffMs,
56
+ };
57
+ }
@@ -222,5 +222,11 @@ export const VECTOR_CORTEX_SETTINGS: SettingGroup = {
222
222
  "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.",
223
223
  true,
224
224
  ),
225
+ boolDirect(
226
+ "MEGACOMPACT_ML5_A",
227
+ "ML5-A Five-Head Training Load",
228
+ "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.",
229
+ true,
230
+ ),
225
231
  ],
226
232
  };
@@ -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 {
11
12
  openStore,
12
13
  writeCheckpointEpoch,
@@ -30,6 +31,10 @@ import type { EmbeddedChunk } from "../../../src/topics/types.js";
30
31
  import type { MegaConfig } from "../../mega-config.js";
31
32
  import { reportClosureOptimized } from "../../../src/vector-cortex/heal/emit.js";
32
33
  import { reportRepairPlanned } from "../../../src/vector-cortex/heal/repair-emit.js";
34
+ import {
35
+ buildPostCompactViews,
36
+ drivePostCompactRepair,
37
+ } from "./controller.js";
33
38
  import { VC6A_ENABLED, VC6C_ENABLED } from "../../../src/config/vector-cortex.js";
34
39
 
35
40
  /** Shape of the compact result consumed by the epoch/maintenance writes. */
@@ -278,9 +283,8 @@ export async function persistEpochAndMaintain(
278
283
  }
279
284
  }
280
285
 
281
- // VC6 Heal lifecycle emits (post-compact). Wiring stubs — dashboard graph +
282
- // event counts move; real gap detection/rebuild is a future sprint.
283
- // VC6A: closure-optimization savings from the compact token delta.
286
+ // VC6 Heal lifecycle emits (post-compact). VC6A: closure-optimization
287
+ // savings from the compact token delta.
284
288
  const savings = Math.max(
285
289
  0,
286
290
  (ran.result.originalTokenEstimate ?? 0) - (ran.result.tokenEstimate ?? 0),
@@ -301,8 +305,40 @@ export async function persistEpochAndMaintain(
301
305
  /* non-fatal: VC6A heal emit never breaks compaction */
302
306
  }
303
307
  }
304
- // VC6C: repair-planner placeholder (no real gap detection yet).
308
+ // VC6C: real post-compact gap detection + atomic repair drive (VC6C-IMPL).
309
+ // Builds each derived subsystem's pre/post compact view against the durable
310
+ // authority high-water, runs the heal eligibility policy (gap-ness, frozen
311
+ // authority, mode C, the 5-min rate limit), and only on a REAL gap routes
312
+ // plan -> rebuild -> emit the three repair events. No real gap => emit
313
+ // NOTHING (VC6C-IMPL-006: no rebuild without a real gap). Flag OFF keeps the
314
+ // predecessor placeholder byte-identical (reportRepairPlanned with hardcoded
315
+ // backoffMs:0, gapSize:compactedFrom) and rebuild is a no-op — the reported
316
+ // seam is flag-gated, so the placeholder emits nothing, exactly as before.
305
317
  if (VC6C_ENABLED()) {
318
+ try {
319
+ const emit = (name: string, payload: unknown) =>
320
+ runtime.appendEvent(name, payload as Record<string, unknown>);
321
+ const views = buildPostCompactViews(
322
+ ran.result.compactedFrom,
323
+ runtime.rt.compactCount,
324
+ );
325
+ drivePostCompactRepair(views, BigInt(Date.now()), emit, () => {
326
+ // The derived generation for the repaired range is re-materialized
327
+ // from the compact summary and verified as a strict successor.
328
+ const bytes = new Uint8Array(
329
+ Buffer.from(ran.result.summary, "utf8"),
330
+ );
331
+ const digest = createHash("sha256")
332
+ .update(bytes)
333
+ .digest("hex");
334
+ return { sourceBytes: bytes, expectedDigest: digest };
335
+ });
336
+ } catch {
337
+ /* non-fatal: VC6C heal repair never breaks compaction */
338
+ }
339
+ } else {
340
+ // Flag-off: predecessor placeholder, byte-identical (emits nothing via
341
+ // the flag-gated reporter seam).
306
342
  try {
307
343
  reportRepairPlanned(
308
344
  (name, payload) =>