pi-mega-compact 0.20.44 → 0.20.45
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-enc0c.js +35 -0
- package/dist/config/vector-cortex.js +1 -0
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
- package/dist/src/config/vector-cortex-enc0c.js +35 -0
- package/dist/src/config/vector-cortex.js +1 -0
- package/dist/src/config.js +1 -1
- package/dist/src/vector-cortex/encoder/heads-candidate.js +127 -0
- package/dist/src/vector-cortex/encoder/heads.js +4 -0
- package/dist/vector-cortex/encoder/heads-candidate.js +127 -0
- package/dist/vector-cortex/encoder/heads.js +4 -0
- package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
- package/package.json +1 -1
- package/src/config/vector-cortex-enc0c.ts +37 -0
- package/src/config/vector-cortex.ts +1 -1
- package/src/config.ts +1 -0
- package/src/vector-cortex/encoder/heads-candidate.ts +148 -0
- package/src/vector-cortex/encoder/heads.ts +14 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config/vector-cortex-enc0c.ts — ENC-0c five-head supervision transfer 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-enc0a.ts / enc0b.ts and
|
|
6
|
+
* the VC8C/VC9A-D/ML5A-E/DEDUP_ATTR siblings were. vector-cortex.ts re-exports
|
|
7
|
+
* the flag below and root src/config.ts re-exports it, so no consumer import
|
|
8
|
+
* path changes.
|
|
9
|
+
*
|
|
10
|
+
* ENC-0c trains the five real heads onto the frozen ENC-0b bge-small trunk via
|
|
11
|
+
* supervision transfer, and stages a qualified candidate under
|
|
12
|
+
* ~/.pi/mega-compact-encoder/candidates/ (only when the developer trains it —
|
|
13
|
+
* the extension never stages one by itself). When the flag is ON and a
|
|
14
|
+
* qualified candidate exists, `loadHeadCandidate` (encoder/heads.ts seam) serves
|
|
15
|
+
* the trained head weights; when the flag is OFF (MEGACOMPACT_ENC_0C=0) or no
|
|
16
|
+
* candidate is staged, the heads keep serving the ENC-0b survivor exactly —
|
|
17
|
+
* byte-identical, no weight change. The flag gates ONLY the candidate-load seam;
|
|
18
|
+
* the survivor path is untouched.
|
|
19
|
+
*
|
|
20
|
+
* The split is purely mechanical: ENC_0C_ENABLED follows ENC_0B_ENABLED in name,
|
|
21
|
+
* semantics, and default, and vector-cortex.ts re-exports it so every existing
|
|
22
|
+
* `from "./config/vector-cortex.js"` import keeps resolving unchanged.
|
|
23
|
+
*
|
|
24
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
|
|
25
|
+
*/
|
|
26
|
+
import { sprintFlag } from "./vector-cortex-flag.js";
|
|
27
|
+
/**
|
|
28
|
+
* ENC-0c — five-head supervision transfer on the frozen bge-small trunk.
|
|
29
|
+
* Default ON. `MEGACOMPACT_ENC_0C=0` disables and is byte-identical to the
|
|
30
|
+
* predecessor (ENC-0b): no head candidate is loaded and the heads keep serving
|
|
31
|
+
* the ENC-0b survivor defaults exactly as before. This flag MUST also be a
|
|
32
|
+
* dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
|
|
33
|
+
* mirroring ENC_0A and ENC_0B.
|
|
34
|
+
*/
|
|
35
|
+
export const ENC_0C_ENABLED = () => sprintFlag("MEGACOMPACT_ENC_0C");
|
|
@@ -251,5 +251,6 @@ export { ML5E_ENABLED } from "./vector-cortex-ml5e.js";
|
|
|
251
251
|
export { DEDUP_ATTR_ENABLED } from "./vector-cortex-dedup-attr.js";
|
|
252
252
|
export { ENC_0A_ENABLED } from "./vector-cortex-enc0a.js";
|
|
253
253
|
export { ENC_0B_ENABLED } from "./vector-cortex-enc0b.js";
|
|
254
|
+
export { ENC_0C_ENABLED } from "./vector-cortex-enc0c.js";
|
|
254
255
|
// Breaker constants (TRIAD_RESILIENCE.md §breaker) extracted to vector-cortex-breakers.ts.
|
|
255
256
|
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, ML5A_ENABLED, ML5B_ENABLED, ML5C_ENABLED, ML5D_ENABLED, ML5E_ENABLED, DEDUP_ATTR_ENABLED, ENC_0A_ENABLED, ENC_0B_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, ML5B_ENABLED, ML5C_ENABLED, ML5D_ENABLED, ML5E_ENABLED, DEDUP_ATTR_ENABLED, ENC_0A_ENABLED, ENC_0B_ENABLED, ENC_0C_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";
|
|
@@ -60,5 +60,6 @@ export const VECTOR_CORTEX_SETTINGS = {
|
|
|
60
60
|
boolDirect("MEGACOMPACT_DEDUP_ATTR", "Dedup Tier Attribution Rollup", "Dedup tier-attribution rollup: per-tier dedup catch shares (L0/L1/L2/new percent of dedup decisions) read from the local events.log dedup_audit stream (GET /api/dedup-tier-attribution). OFF = 404 + no cache file, byte-identical predecessor.", true),
|
|
61
61
|
boolDirect("MEGACOMPACT_ENC_0A", "ENC-0a Encoder Backend Decision", "ENC-0a learned-encoder backend-decision lock: records the transformers.js/WASM vs onnxruntime-node choice, per-platform install matrix, opset-21 baseline and pinned digests in docs/vector-cortex/encoder-backend-decision.md. OFF = no decision record written / no resolver runs, mode B trigram byte-identical predecessor.", true),
|
|
62
62
|
boolDirect("MEGACOMPACT_ENC_0B", "ENC-0b Real Trunk Fetch & Gated Path", "ENC-0b real ONNX trunk fetch + gated inference: replaces the LCG placeholder with the real bge-small int8 model through an ONNX InferenceSession (onnxruntime-web WASM). OFF = LCG placeholder serves byte-identical predecessor output, no ONNX session built.", true),
|
|
63
|
+
boolDirect("MEGACOMPACT_ENC_0C", "ENC-0c Five-Head Supervision Transfer", "ENC-0c five-head supervision transfer on the frozen bge-small trunk: when a developer-trained head candidate is staged under ~/.pi/mega-compact-encoder/candidates/, the five heads serve the trained weights. OFF = no candidate is loaded, the heads serve byte-identical ENC-0b survivor defaults.", true),
|
|
63
64
|
],
|
|
64
65
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config/vector-cortex-enc0c.ts — ENC-0c five-head supervision transfer 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-enc0a.ts / enc0b.ts and
|
|
6
|
+
* the VC8C/VC9A-D/ML5A-E/DEDUP_ATTR siblings were. vector-cortex.ts re-exports
|
|
7
|
+
* the flag below and root src/config.ts re-exports it, so no consumer import
|
|
8
|
+
* path changes.
|
|
9
|
+
*
|
|
10
|
+
* ENC-0c trains the five real heads onto the frozen ENC-0b bge-small trunk via
|
|
11
|
+
* supervision transfer, and stages a qualified candidate under
|
|
12
|
+
* ~/.pi/mega-compact-encoder/candidates/ (only when the developer trains it —
|
|
13
|
+
* the extension never stages one by itself). When the flag is ON and a
|
|
14
|
+
* qualified candidate exists, `loadHeadCandidate` (encoder/heads.ts seam) serves
|
|
15
|
+
* the trained head weights; when the flag is OFF (MEGACOMPACT_ENC_0C=0) or no
|
|
16
|
+
* candidate is staged, the heads keep serving the ENC-0b survivor exactly —
|
|
17
|
+
* byte-identical, no weight change. The flag gates ONLY the candidate-load seam;
|
|
18
|
+
* the survivor path is untouched.
|
|
19
|
+
*
|
|
20
|
+
* The split is purely mechanical: ENC_0C_ENABLED follows ENC_0B_ENABLED in name,
|
|
21
|
+
* semantics, and default, and vector-cortex.ts re-exports it so every existing
|
|
22
|
+
* `from "./config/vector-cortex.js"` import keeps resolving unchanged.
|
|
23
|
+
*
|
|
24
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
|
|
25
|
+
*/
|
|
26
|
+
import { sprintFlag } from "./vector-cortex-flag.js";
|
|
27
|
+
/**
|
|
28
|
+
* ENC-0c — five-head supervision transfer on the frozen bge-small trunk.
|
|
29
|
+
* Default ON. `MEGACOMPACT_ENC_0C=0` disables and is byte-identical to the
|
|
30
|
+
* predecessor (ENC-0b): no head candidate is loaded and the heads keep serving
|
|
31
|
+
* the ENC-0b survivor defaults exactly as before. This flag MUST also be a
|
|
32
|
+
* dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
|
|
33
|
+
* mirroring ENC_0A and ENC_0B.
|
|
34
|
+
*/
|
|
35
|
+
export const ENC_0C_ENABLED = () => sprintFlag("MEGACOMPACT_ENC_0C");
|
|
@@ -251,5 +251,6 @@ export { ML5E_ENABLED } from "./vector-cortex-ml5e.js";
|
|
|
251
251
|
export { DEDUP_ATTR_ENABLED } from "./vector-cortex-dedup-attr.js";
|
|
252
252
|
export { ENC_0A_ENABLED } from "./vector-cortex-enc0a.js";
|
|
253
253
|
export { ENC_0B_ENABLED } from "./vector-cortex-enc0b.js";
|
|
254
|
+
export { ENC_0C_ENABLED } from "./vector-cortex-enc0c.js";
|
|
254
255
|
// Breaker constants (TRIAD_RESILIENCE.md §breaker) extracted to vector-cortex-breakers.ts.
|
|
255
256
|
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/src/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, ML5A_ENABLED, ML5B_ENABLED, ML5C_ENABLED, ML5D_ENABLED, ML5E_ENABLED, DEDUP_ATTR_ENABLED, ENC_0A_ENABLED, ENC_0B_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, ML5B_ENABLED, ML5C_ENABLED, ML5D_ENABLED, ML5E_ENABLED, DEDUP_ATTR_ENABLED, ENC_0A_ENABLED, ENC_0B_ENABLED, ENC_0C_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";
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* encoder/heads-candidate.ts — ENC-0c head-candidate load seam (delegate impl).
|
|
3
|
+
*
|
|
4
|
+
* Loads/validates a `head-candidate-v1` five-head candidate staged under
|
|
5
|
+
* `~/.pi/mega-compact-encoder/candidates/<version>/` after training on the
|
|
6
|
+
* frozen ENC-0b bge-small trunk. Flag-off / absent / malformed / wrong dims /
|
|
7
|
+
* non-finite / digest / trunk mismatch each return null or {ok:false} — a bad
|
|
8
|
+
* candidate is NEVER force-loaded; the runtime keeps the ENC-0b survivor
|
|
9
|
+
* byte-identical. No `any` (PREVENT-011), local-only, structured logging.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { ENC_0C_ENABLED } from "../../config/vector-cortex.js";
|
|
15
|
+
import { ENCODER_HEAD_DIMS, ENCODER_HEAD_ORDER } from "./types.js";
|
|
16
|
+
export const HEAD_CANDIDATE_SCHEMA = "head-candidate-v1";
|
|
17
|
+
const MANIFEST_FILE = "manifest.json";
|
|
18
|
+
export const HEAD_CANDIDATE_FAIL = {
|
|
19
|
+
INVALID: "ENC0C_CANDIDATE_INVALID",
|
|
20
|
+
TRUNK_MISMATCH: "ENC0C_TRUNK_MISMATCH",
|
|
21
|
+
DIM_MISMATCH: "ENC0C_DIM_MISMATCH",
|
|
22
|
+
NON_FINITE: "ENC0C_NON_FINITE",
|
|
23
|
+
DIGEST_MISMATCH: "ENC0C_DIGEST_MISMATCH",
|
|
24
|
+
};
|
|
25
|
+
function sha256(buf) { return createHash("sha256").update(buf).digest("hex"); }
|
|
26
|
+
function finiteDim(values, dim) {
|
|
27
|
+
if (values.length !== dim)
|
|
28
|
+
return false;
|
|
29
|
+
for (const v of values)
|
|
30
|
+
if (!Number.isFinite(v))
|
|
31
|
+
return false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
function parseManifest(m) {
|
|
35
|
+
if (!m || m["schema"] !== HEAD_CANDIDATE_SCHEMA)
|
|
36
|
+
return null;
|
|
37
|
+
const version = typeof m["version"] === "string" ? m["version"] : "";
|
|
38
|
+
const trunkDigest = typeof m["trunkDigest"] === "string" ? m["trunkDigest"] : "";
|
|
39
|
+
if (!version || !trunkDigest || !Array.isArray(m["heads"]))
|
|
40
|
+
return null;
|
|
41
|
+
const heads = [];
|
|
42
|
+
for (const item of m["heads"]) {
|
|
43
|
+
const rec = item;
|
|
44
|
+
if (!rec || typeof rec !== "object")
|
|
45
|
+
return null;
|
|
46
|
+
const name = rec["name"];
|
|
47
|
+
if (typeof name !== "string" || !ENCODER_HEAD_ORDER.includes(name))
|
|
48
|
+
return null;
|
|
49
|
+
heads.push({
|
|
50
|
+
name: name,
|
|
51
|
+
dim: Number(rec["dim"] ?? 0),
|
|
52
|
+
sha256: typeof rec["sha256"] === "string" ? rec["sha256"] : "",
|
|
53
|
+
bytes: Number(rec["bytes"] ?? 0),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { version, trunkDigest, heads };
|
|
57
|
+
}
|
|
58
|
+
/** All 5 dims match, weights finite, digests hold. */
|
|
59
|
+
export function validateHeadCandidate(candidate) {
|
|
60
|
+
for (const h of ENCODER_HEAD_ORDER) {
|
|
61
|
+
const dim = candidate.dims[h];
|
|
62
|
+
if (dim !== ENCODER_HEAD_DIMS[h])
|
|
63
|
+
return { ok: false, code: HEAD_CANDIDATE_FAIL.DIM_MISMATCH };
|
|
64
|
+
const w = candidate.weights[h];
|
|
65
|
+
if (!w || !finiteDim(w, dim))
|
|
66
|
+
return { ok: false, code: HEAD_CANDIDATE_FAIL.NON_FINITE };
|
|
67
|
+
const buf = new Uint8Array(w.buffer, w.byteOffset, w.byteLength);
|
|
68
|
+
if (sha256(buf) !== candidate.digests[h])
|
|
69
|
+
return { ok: false, code: HEAD_CANDIDATE_FAIL.DIGEST_MISMATCH };
|
|
70
|
+
}
|
|
71
|
+
return { ok: true };
|
|
72
|
+
}
|
|
73
|
+
/** Load a candidate against the frozen trunk; null on any violation, never throws. */
|
|
74
|
+
export function loadHeadCandidate(candidateDir, manifest) {
|
|
75
|
+
if (!ENC_0C_ENABLED())
|
|
76
|
+
return null;
|
|
77
|
+
let raw;
|
|
78
|
+
try {
|
|
79
|
+
raw = readFileSync(join(candidateDir, MANIFEST_FILE), "utf8");
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
let parsed;
|
|
85
|
+
try {
|
|
86
|
+
parsed = JSON.parse(raw);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const m = parseManifest(parsed);
|
|
92
|
+
if (!m || m.trunkDigest !== manifest.onnx.sha256)
|
|
93
|
+
return null;
|
|
94
|
+
const weights = {};
|
|
95
|
+
const digests = {};
|
|
96
|
+
const dims = {};
|
|
97
|
+
for (const rec of m.heads) {
|
|
98
|
+
const want = rec.dim * 4;
|
|
99
|
+
if (!Number.isInteger(want) || want <= 0 || rec.dim !== ENCODER_HEAD_DIMS[rec.name])
|
|
100
|
+
return null;
|
|
101
|
+
let bytes;
|
|
102
|
+
try {
|
|
103
|
+
bytes = readFileSync(join(candidateDir, `${rec.name}.bin`));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const f = new Float32Array(bytes.buffer, bytes.byteOffset, rec.dim);
|
|
109
|
+
const d = sha256(bytes);
|
|
110
|
+
if (bytes.length !== want || d !== rec.sha256)
|
|
111
|
+
return null;
|
|
112
|
+
weights[rec.name] = f.slice();
|
|
113
|
+
digests[rec.name] = d;
|
|
114
|
+
dims[rec.name] = rec.dim;
|
|
115
|
+
}
|
|
116
|
+
for (const h of ENCODER_HEAD_ORDER) {
|
|
117
|
+
if (weights[h] === undefined || digests[h] === undefined || dims[h] === undefined)
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const candidate = {
|
|
121
|
+
schema: HEAD_CANDIDATE_SCHEMA, version: m.version, trunkDigest: m.trunkDigest,
|
|
122
|
+
dims: dims,
|
|
123
|
+
weights: weights,
|
|
124
|
+
digests: digests,
|
|
125
|
+
};
|
|
126
|
+
return validateHeadCandidate(candidate).ok ? candidate : null;
|
|
127
|
+
}
|
|
@@ -198,3 +198,7 @@ export function projectHeadFromTrunk(head, trunk, table) {
|
|
|
198
198
|
return { head, dim, values: l2Normalize(out) };
|
|
199
199
|
}
|
|
200
200
|
export { ENCODER_HEAD_ORDER, ENCODER_HEAD_DIMS, ENCODER_HEAD_LOSS_SUM, ENCODER_SEED, NOOP_VC2B_REPORTER };
|
|
201
|
+
// ENC-0c five-head candidate seam (delegate-shell): the load/validate impl
|
|
202
|
+
// lives in heads-candidate.ts; these re-exports keep the public import path
|
|
203
|
+
// stable at heads.ts without growing this survivor file over the soft limit.
|
|
204
|
+
export { HEAD_CANDIDATE_SCHEMA, HEAD_CANDIDATE_FAIL, loadHeadCandidate, validateHeadCandidate, } from "./heads-candidate.js";
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* encoder/heads-candidate.ts — ENC-0c head-candidate load seam (delegate impl).
|
|
3
|
+
*
|
|
4
|
+
* Loads/validates a `head-candidate-v1` five-head candidate staged under
|
|
5
|
+
* `~/.pi/mega-compact-encoder/candidates/<version>/` after training on the
|
|
6
|
+
* frozen ENC-0b bge-small trunk. Flag-off / absent / malformed / wrong dims /
|
|
7
|
+
* non-finite / digest / trunk mismatch each return null or {ok:false} — a bad
|
|
8
|
+
* candidate is NEVER force-loaded; the runtime keeps the ENC-0b survivor
|
|
9
|
+
* byte-identical. No `any` (PREVENT-011), local-only, structured logging.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { ENC_0C_ENABLED } from "../../config/vector-cortex.js";
|
|
15
|
+
import { ENCODER_HEAD_DIMS, ENCODER_HEAD_ORDER } from "./types.js";
|
|
16
|
+
export const HEAD_CANDIDATE_SCHEMA = "head-candidate-v1";
|
|
17
|
+
const MANIFEST_FILE = "manifest.json";
|
|
18
|
+
export const HEAD_CANDIDATE_FAIL = {
|
|
19
|
+
INVALID: "ENC0C_CANDIDATE_INVALID",
|
|
20
|
+
TRUNK_MISMATCH: "ENC0C_TRUNK_MISMATCH",
|
|
21
|
+
DIM_MISMATCH: "ENC0C_DIM_MISMATCH",
|
|
22
|
+
NON_FINITE: "ENC0C_NON_FINITE",
|
|
23
|
+
DIGEST_MISMATCH: "ENC0C_DIGEST_MISMATCH",
|
|
24
|
+
};
|
|
25
|
+
function sha256(buf) { return createHash("sha256").update(buf).digest("hex"); }
|
|
26
|
+
function finiteDim(values, dim) {
|
|
27
|
+
if (values.length !== dim)
|
|
28
|
+
return false;
|
|
29
|
+
for (const v of values)
|
|
30
|
+
if (!Number.isFinite(v))
|
|
31
|
+
return false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
function parseManifest(m) {
|
|
35
|
+
if (!m || m["schema"] !== HEAD_CANDIDATE_SCHEMA)
|
|
36
|
+
return null;
|
|
37
|
+
const version = typeof m["version"] === "string" ? m["version"] : "";
|
|
38
|
+
const trunkDigest = typeof m["trunkDigest"] === "string" ? m["trunkDigest"] : "";
|
|
39
|
+
if (!version || !trunkDigest || !Array.isArray(m["heads"]))
|
|
40
|
+
return null;
|
|
41
|
+
const heads = [];
|
|
42
|
+
for (const item of m["heads"]) {
|
|
43
|
+
const rec = item;
|
|
44
|
+
if (!rec || typeof rec !== "object")
|
|
45
|
+
return null;
|
|
46
|
+
const name = rec["name"];
|
|
47
|
+
if (typeof name !== "string" || !ENCODER_HEAD_ORDER.includes(name))
|
|
48
|
+
return null;
|
|
49
|
+
heads.push({
|
|
50
|
+
name: name,
|
|
51
|
+
dim: Number(rec["dim"] ?? 0),
|
|
52
|
+
sha256: typeof rec["sha256"] === "string" ? rec["sha256"] : "",
|
|
53
|
+
bytes: Number(rec["bytes"] ?? 0),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { version, trunkDigest, heads };
|
|
57
|
+
}
|
|
58
|
+
/** All 5 dims match, weights finite, digests hold. */
|
|
59
|
+
export function validateHeadCandidate(candidate) {
|
|
60
|
+
for (const h of ENCODER_HEAD_ORDER) {
|
|
61
|
+
const dim = candidate.dims[h];
|
|
62
|
+
if (dim !== ENCODER_HEAD_DIMS[h])
|
|
63
|
+
return { ok: false, code: HEAD_CANDIDATE_FAIL.DIM_MISMATCH };
|
|
64
|
+
const w = candidate.weights[h];
|
|
65
|
+
if (!w || !finiteDim(w, dim))
|
|
66
|
+
return { ok: false, code: HEAD_CANDIDATE_FAIL.NON_FINITE };
|
|
67
|
+
const buf = new Uint8Array(w.buffer, w.byteOffset, w.byteLength);
|
|
68
|
+
if (sha256(buf) !== candidate.digests[h])
|
|
69
|
+
return { ok: false, code: HEAD_CANDIDATE_FAIL.DIGEST_MISMATCH };
|
|
70
|
+
}
|
|
71
|
+
return { ok: true };
|
|
72
|
+
}
|
|
73
|
+
/** Load a candidate against the frozen trunk; null on any violation, never throws. */
|
|
74
|
+
export function loadHeadCandidate(candidateDir, manifest) {
|
|
75
|
+
if (!ENC_0C_ENABLED())
|
|
76
|
+
return null;
|
|
77
|
+
let raw;
|
|
78
|
+
try {
|
|
79
|
+
raw = readFileSync(join(candidateDir, MANIFEST_FILE), "utf8");
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
let parsed;
|
|
85
|
+
try {
|
|
86
|
+
parsed = JSON.parse(raw);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const m = parseManifest(parsed);
|
|
92
|
+
if (!m || m.trunkDigest !== manifest.onnx.sha256)
|
|
93
|
+
return null;
|
|
94
|
+
const weights = {};
|
|
95
|
+
const digests = {};
|
|
96
|
+
const dims = {};
|
|
97
|
+
for (const rec of m.heads) {
|
|
98
|
+
const want = rec.dim * 4;
|
|
99
|
+
if (!Number.isInteger(want) || want <= 0 || rec.dim !== ENCODER_HEAD_DIMS[rec.name])
|
|
100
|
+
return null;
|
|
101
|
+
let bytes;
|
|
102
|
+
try {
|
|
103
|
+
bytes = readFileSync(join(candidateDir, `${rec.name}.bin`));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const f = new Float32Array(bytes.buffer, bytes.byteOffset, rec.dim);
|
|
109
|
+
const d = sha256(bytes);
|
|
110
|
+
if (bytes.length !== want || d !== rec.sha256)
|
|
111
|
+
return null;
|
|
112
|
+
weights[rec.name] = f.slice();
|
|
113
|
+
digests[rec.name] = d;
|
|
114
|
+
dims[rec.name] = rec.dim;
|
|
115
|
+
}
|
|
116
|
+
for (const h of ENCODER_HEAD_ORDER) {
|
|
117
|
+
if (weights[h] === undefined || digests[h] === undefined || dims[h] === undefined)
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const candidate = {
|
|
121
|
+
schema: HEAD_CANDIDATE_SCHEMA, version: m.version, trunkDigest: m.trunkDigest,
|
|
122
|
+
dims: dims,
|
|
123
|
+
weights: weights,
|
|
124
|
+
digests: digests,
|
|
125
|
+
};
|
|
126
|
+
return validateHeadCandidate(candidate).ok ? candidate : null;
|
|
127
|
+
}
|
|
@@ -198,3 +198,7 @@ export function projectHeadFromTrunk(head, trunk, table) {
|
|
|
198
198
|
return { head, dim, values: l2Normalize(out) };
|
|
199
199
|
}
|
|
200
200
|
export { ENCODER_HEAD_ORDER, ENCODER_HEAD_DIMS, ENCODER_HEAD_LOSS_SUM, ENCODER_SEED, NOOP_VC2B_REPORTER };
|
|
201
|
+
// ENC-0c five-head candidate seam (delegate-shell): the load/validate impl
|
|
202
|
+
// lives in heads-candidate.ts; these re-exports keep the public import path
|
|
203
|
+
// stable at heads.ts without growing this survivor file over the soft limit.
|
|
204
|
+
export { HEAD_CANDIDATE_SCHEMA, HEAD_CANDIDATE_FAIL, loadHeadCandidate, validateHeadCandidate, } from "./heads-candidate.js";
|
|
@@ -264,5 +264,11 @@ export const VECTOR_CORTEX_SETTINGS: SettingGroup = {
|
|
|
264
264
|
"ENC-0b real ONNX trunk fetch + gated inference: replaces the LCG placeholder with the real bge-small int8 model through an ONNX InferenceSession (onnxruntime-web WASM). OFF = LCG placeholder serves byte-identical predecessor output, no ONNX session built.",
|
|
265
265
|
true,
|
|
266
266
|
),
|
|
267
|
+
boolDirect(
|
|
268
|
+
"MEGACOMPACT_ENC_0C",
|
|
269
|
+
"ENC-0c Five-Head Supervision Transfer",
|
|
270
|
+
"ENC-0c five-head supervision transfer on the frozen bge-small trunk: when a developer-trained head candidate is staged under ~/.pi/mega-compact-encoder/candidates/, the five heads serve the trained weights. OFF = no candidate is loaded, the heads serve byte-identical ENC-0b survivor defaults.",
|
|
271
|
+
true,
|
|
272
|
+
),
|
|
267
273
|
],
|
|
268
274
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mega-compact",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.45",
|
|
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,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config/vector-cortex-enc0c.ts — ENC-0c five-head supervision transfer 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-enc0a.ts / enc0b.ts and
|
|
6
|
+
* the VC8C/VC9A-D/ML5A-E/DEDUP_ATTR siblings were. vector-cortex.ts re-exports
|
|
7
|
+
* the flag below and root src/config.ts re-exports it, so no consumer import
|
|
8
|
+
* path changes.
|
|
9
|
+
*
|
|
10
|
+
* ENC-0c trains the five real heads onto the frozen ENC-0b bge-small trunk via
|
|
11
|
+
* supervision transfer, and stages a qualified candidate under
|
|
12
|
+
* ~/.pi/mega-compact-encoder/candidates/ (only when the developer trains it —
|
|
13
|
+
* the extension never stages one by itself). When the flag is ON and a
|
|
14
|
+
* qualified candidate exists, `loadHeadCandidate` (encoder/heads.ts seam) serves
|
|
15
|
+
* the trained head weights; when the flag is OFF (MEGACOMPACT_ENC_0C=0) or no
|
|
16
|
+
* candidate is staged, the heads keep serving the ENC-0b survivor exactly —
|
|
17
|
+
* byte-identical, no weight change. The flag gates ONLY the candidate-load seam;
|
|
18
|
+
* the survivor path is untouched.
|
|
19
|
+
*
|
|
20
|
+
* The split is purely mechanical: ENC_0C_ENABLED follows ENC_0B_ENABLED in name,
|
|
21
|
+
* semantics, and default, and vector-cortex.ts re-exports it so every existing
|
|
22
|
+
* `from "./config/vector-cortex.js"` import keeps resolving unchanged.
|
|
23
|
+
*
|
|
24
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { sprintFlag } from "./vector-cortex-flag.js";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* ENC-0c — five-head supervision transfer on the frozen bge-small trunk.
|
|
31
|
+
* Default ON. `MEGACOMPACT_ENC_0C=0` disables and is byte-identical to the
|
|
32
|
+
* predecessor (ENC-0b): no head candidate is loaded and the heads keep serving
|
|
33
|
+
* the ENC-0b survivor defaults exactly as before. This flag MUST also be a
|
|
34
|
+
* dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
|
|
35
|
+
* mirroring ENC_0A and ENC_0B.
|
|
36
|
+
*/
|
|
37
|
+
export const ENC_0C_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_ENC_0C");
|
|
@@ -281,7 +281,7 @@ export { ML5E_ENABLED } from "./vector-cortex-ml5e.js";
|
|
|
281
281
|
export { DEDUP_ATTR_ENABLED } from "./vector-cortex-dedup-attr.js";
|
|
282
282
|
export { ENC_0A_ENABLED } from "./vector-cortex-enc0a.js";
|
|
283
283
|
export { ENC_0B_ENABLED } from "./vector-cortex-enc0b.js";
|
|
284
|
-
|
|
284
|
+
export { ENC_0C_ENABLED } from "./vector-cortex-enc0c.js";
|
|
285
285
|
// Breaker constants (TRIAD_RESILIENCE.md §breaker) extracted to vector-cortex-breakers.ts.
|
|
286
286
|
export {
|
|
287
287
|
BREAKER_WINDOW_MS,
|
package/src/config.ts
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* encoder/heads-candidate.ts — ENC-0c head-candidate load seam (delegate impl).
|
|
3
|
+
*
|
|
4
|
+
* Loads/validates a `head-candidate-v1` five-head candidate staged under
|
|
5
|
+
* `~/.pi/mega-compact-encoder/candidates/<version>/` after training on the
|
|
6
|
+
* frozen ENC-0b bge-small trunk. Flag-off / absent / malformed / wrong dims /
|
|
7
|
+
* non-finite / digest / trunk mismatch each return null or {ok:false} — a bad
|
|
8
|
+
* candidate is NEVER force-loaded; the runtime keeps the ENC-0b survivor
|
|
9
|
+
* byte-identical. No `any` (PREVENT-011), local-only, structured logging.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
|
|
16
|
+
import { ENC_0C_ENABLED } from "../../config/vector-cortex.js";
|
|
17
|
+
import { ENCODER_HEAD_DIMS, ENCODER_HEAD_ORDER } from "./types.js";
|
|
18
|
+
import type { EncoderHeadName, ModelManifestV1 } from "./types.js";
|
|
19
|
+
|
|
20
|
+
export const HEAD_CANDIDATE_SCHEMA = "head-candidate-v1" as const;
|
|
21
|
+
const MANIFEST_FILE = "manifest.json";
|
|
22
|
+
|
|
23
|
+
export interface HeadCandidateHeadDigest {
|
|
24
|
+
readonly name: EncoderHeadName;
|
|
25
|
+
readonly dim: number;
|
|
26
|
+
readonly sha256: string;
|
|
27
|
+
readonly bytes: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** On-disk candidate contract; `trunkDigest` pins the frozen trunk. */
|
|
31
|
+
export interface HeadCandidateManifest {
|
|
32
|
+
readonly schema: typeof HEAD_CANDIDATE_SCHEMA;
|
|
33
|
+
readonly version: string;
|
|
34
|
+
readonly trunkDigest: string;
|
|
35
|
+
readonly heads: readonly HeadCandidateHeadDigest[];
|
|
36
|
+
readonly totalBytes: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Loaded candidate: parsed Float32Array weights per head. */
|
|
40
|
+
export interface HeadCandidate {
|
|
41
|
+
readonly schema: typeof HEAD_CANDIDATE_SCHEMA;
|
|
42
|
+
readonly version: string;
|
|
43
|
+
readonly trunkDigest: string;
|
|
44
|
+
readonly dims: Readonly<Record<EncoderHeadName, number>>;
|
|
45
|
+
readonly weights: Readonly<Record<EncoderHeadName, Float32Array>>;
|
|
46
|
+
readonly digests: Readonly<Record<EncoderHeadName, string>>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type HeadCandidateValidation = { readonly ok: true } | { readonly ok: false; readonly code: string };
|
|
50
|
+
|
|
51
|
+
export const HEAD_CANDIDATE_FAIL = {
|
|
52
|
+
INVALID: "ENC0C_CANDIDATE_INVALID",
|
|
53
|
+
TRUNK_MISMATCH: "ENC0C_TRUNK_MISMATCH",
|
|
54
|
+
DIM_MISMATCH: "ENC0C_DIM_MISMATCH",
|
|
55
|
+
NON_FINITE: "ENC0C_NON_FINITE",
|
|
56
|
+
DIGEST_MISMATCH: "ENC0C_DIGEST_MISMATCH",
|
|
57
|
+
} as const;
|
|
58
|
+
|
|
59
|
+
function sha256(buf: Uint8Array): string { return createHash("sha256").update(buf).digest("hex"); }
|
|
60
|
+
function finiteDim(values: Float32Array, dim: number): boolean {
|
|
61
|
+
if (values.length !== dim) return false;
|
|
62
|
+
for (const v of values) if (!Number.isFinite(v)) return false;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Parse the candidate manifest object, or null if invalid. */
|
|
67
|
+
type ParsedManifest = { version: string; trunkDigest: string; heads: HeadCandidateHeadDigest[] };
|
|
68
|
+
function parseManifest(m: Record<string, unknown> | null): ParsedManifest | null {
|
|
69
|
+
if (!m || m["schema"] !== HEAD_CANDIDATE_SCHEMA) return null;
|
|
70
|
+
const version = typeof m["version"] === "string" ? m["version"] : "";
|
|
71
|
+
const trunkDigest = typeof m["trunkDigest"] === "string" ? m["trunkDigest"] : "";
|
|
72
|
+
if (!version || !trunkDigest || !Array.isArray(m["heads"])) return null;
|
|
73
|
+
const heads: HeadCandidateHeadDigest[] = [];
|
|
74
|
+
for (const item of m["heads"] as unknown[]) {
|
|
75
|
+
const rec = item as Record<string, unknown> | null;
|
|
76
|
+
if (!rec || typeof rec !== "object") return null;
|
|
77
|
+
const name = rec["name"];
|
|
78
|
+
if (typeof name !== "string" || !ENCODER_HEAD_ORDER.includes(name as EncoderHeadName)) return null;
|
|
79
|
+
heads.push({
|
|
80
|
+
name: name as EncoderHeadName,
|
|
81
|
+
dim: Number(rec["dim"] ?? 0),
|
|
82
|
+
sha256: typeof rec["sha256"] === "string" ? rec["sha256"] : "",
|
|
83
|
+
bytes: Number(rec["bytes"] ?? 0),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return { version, trunkDigest, heads };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** All 5 dims match, weights finite, digests hold. */
|
|
90
|
+
export function validateHeadCandidate(candidate: HeadCandidate): HeadCandidateValidation {
|
|
91
|
+
for (const h of ENCODER_HEAD_ORDER) {
|
|
92
|
+
const dim = candidate.dims[h];
|
|
93
|
+
if (dim !== ENCODER_HEAD_DIMS[h]) return { ok: false, code: HEAD_CANDIDATE_FAIL.DIM_MISMATCH };
|
|
94
|
+
const w = candidate.weights[h];
|
|
95
|
+
if (!w || !finiteDim(w, dim)) return { ok: false, code: HEAD_CANDIDATE_FAIL.NON_FINITE };
|
|
96
|
+
const buf = new Uint8Array(w.buffer, w.byteOffset, w.byteLength);
|
|
97
|
+
if (sha256(buf) !== candidate.digests[h]) return { ok: false, code: HEAD_CANDIDATE_FAIL.DIGEST_MISMATCH };
|
|
98
|
+
}
|
|
99
|
+
return { ok: true };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Load a candidate against the frozen trunk; null on any violation, never throws. */
|
|
103
|
+
export function loadHeadCandidate(candidateDir: string, manifest: ModelManifestV1): HeadCandidate | null {
|
|
104
|
+
if (!ENC_0C_ENABLED()) return null;
|
|
105
|
+
let raw: string;
|
|
106
|
+
try {
|
|
107
|
+
raw = readFileSync(join(candidateDir, MANIFEST_FILE), "utf8");
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
let parsed: unknown;
|
|
112
|
+
try {
|
|
113
|
+
parsed = JSON.parse(raw);
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const m = parseManifest(parsed as Record<string, unknown> | null);
|
|
118
|
+
if (!m || m.trunkDigest !== manifest.onnx.sha256) return null;
|
|
119
|
+
const weights: Partial<Record<EncoderHeadName, Float32Array>> = {};
|
|
120
|
+
const digests: Partial<Record<EncoderHeadName, string>> = {};
|
|
121
|
+
const dims: Partial<Record<EncoderHeadName, number>> = {};
|
|
122
|
+
for (const rec of m.heads) {
|
|
123
|
+
const want = rec.dim * 4;
|
|
124
|
+
if (!Number.isInteger(want) || want <= 0 || rec.dim !== ENCODER_HEAD_DIMS[rec.name]) return null;
|
|
125
|
+
let bytes: Buffer;
|
|
126
|
+
try {
|
|
127
|
+
bytes = readFileSync(join(candidateDir, `${rec.name}.bin`));
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const f = new Float32Array(bytes.buffer, bytes.byteOffset, rec.dim);
|
|
132
|
+
const d = sha256(bytes);
|
|
133
|
+
if (bytes.length !== want || d !== rec.sha256) return null;
|
|
134
|
+
weights[rec.name] = f.slice();
|
|
135
|
+
digests[rec.name] = d;
|
|
136
|
+
dims[rec.name] = rec.dim;
|
|
137
|
+
}
|
|
138
|
+
for (const h of ENCODER_HEAD_ORDER) {
|
|
139
|
+
if (weights[h] === undefined || digests[h] === undefined || dims[h] === undefined) return null;
|
|
140
|
+
}
|
|
141
|
+
const candidate: HeadCandidate = {
|
|
142
|
+
schema: HEAD_CANDIDATE_SCHEMA, version: m.version, trunkDigest: m.trunkDigest,
|
|
143
|
+
dims: dims as Record<EncoderHeadName, number>,
|
|
144
|
+
weights: weights as Record<EncoderHeadName, Float32Array>,
|
|
145
|
+
digests: digests as Record<EncoderHeadName, string>,
|
|
146
|
+
};
|
|
147
|
+
return validateHeadCandidate(candidate).ok ? candidate : null;
|
|
148
|
+
}
|
|
@@ -246,3 +246,17 @@ export function projectHeadFromTrunk(
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
export { ENCODER_HEAD_ORDER, ENCODER_HEAD_DIMS, ENCODER_HEAD_LOSS_SUM, ENCODER_SEED, NOOP_VC2B_REPORTER };
|
|
249
|
+
|
|
250
|
+
// ENC-0c five-head candidate seam (delegate-shell): the load/validate impl
|
|
251
|
+
// lives in heads-candidate.ts; these re-exports keep the public import path
|
|
252
|
+
// stable at heads.ts without growing this survivor file over the soft limit.
|
|
253
|
+
export {
|
|
254
|
+
HEAD_CANDIDATE_SCHEMA,
|
|
255
|
+
HEAD_CANDIDATE_FAIL,
|
|
256
|
+
loadHeadCandidate,
|
|
257
|
+
validateHeadCandidate,
|
|
258
|
+
type HeadCandidate,
|
|
259
|
+
type HeadCandidateManifest,
|
|
260
|
+
type HeadCandidateHeadDigest,
|
|
261
|
+
type HeadCandidateValidation,
|
|
262
|
+
} from "./heads-candidate.js";
|