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.
- package/dist/config/vector-cortex-ml5a.js +28 -0
- package/dist/config/vector-cortex.js +4 -5
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
- package/dist/extensions/mega-events/context-handler/afterCompact.js +33 -4
- package/dist/extensions/mega-events/context-handler/controller.js +161 -0
- package/dist/src/config/vector-cortex-ml5a.js +28 -0
- package/dist/src/config/vector-cortex.js +4 -5
- package/dist/src/config.js +1 -1
- package/dist/src/vector-cortex/encoder/calibrate.js +55 -0
- package/dist/src/vector-cortex/encoder/heads.js +87 -0
- package/dist/src/vector-cortex/encoder/select.js +10 -0
- package/dist/src/vector-cortex/heal/_vc6c-impl-fixture.js +29 -0
- package/dist/src/vector-cortex/reconstruct/rebuild.js +43 -0
- package/dist/src/vector-cortex/reconstruct/repair-plan.js +57 -0
- package/dist/vector-cortex/encoder/calibrate.js +55 -0
- package/dist/vector-cortex/encoder/heads.js +87 -0
- package/dist/vector-cortex/encoder/select.js +10 -0
- package/dist/vector-cortex/heal/_vc6c-impl-fixture.js +29 -0
- package/dist/vector-cortex/reconstruct/rebuild.js +43 -0
- package/dist/vector-cortex/reconstruct/repair-plan.js +57 -0
- package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
- package/extensions/mega-events/context-handler/afterCompact.ts +40 -4
- package/extensions/mega-events/context-handler/controller.ts +230 -0
- package/package.json +1 -1
- package/src/config/vector-cortex-ml5a.ts +30 -0
- package/src/config/vector-cortex.ts +4 -5
- package/src/config.ts +1 -0
- package/src/vector-cortex/encoder/calibrate.ts +49 -0
- package/src/vector-cortex/encoder/heads.ts +106 -0
- package/src/vector-cortex/encoder/select.ts +17 -0
- package/src/vector-cortex/heal/_vc6c-impl-fixture.ts +43 -0
- package/src/vector-cortex/reconstruct/rebuild.ts +75 -0
- 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.
|
|
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 +
|
|
273
|
-
// vector-cortex
|
|
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
|
|
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
|
@@ -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
|
+
}
|