pi-mega-compact 0.20.35 → 0.20.38
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-ml5b.js +26 -0
- package/dist/config/vector-cortex.js +5 -5
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
- package/dist/monitoring.js +172 -0
- package/dist/src/config/vector-cortex-ml5a.js +28 -0
- package/dist/src/config/vector-cortex-ml5b.js +26 -0
- package/dist/src/config/vector-cortex.js +5 -5
- package/dist/src/config.js +1 -1
- package/dist/src/monitoring.js +19 -0
- package/dist/src/store/backfill.js +1 -0
- package/dist/src/vector-cortex/encoder/bench-export.js +13 -0
- package/dist/src/vector-cortex/encoder/bench.js +100 -0
- 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/vector-cortex/encoder/bench-export.js +13 -0
- package/dist/vector-cortex/encoder/bench.js +100 -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/vectorStore/dedup-audit.js +104 -0
- package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
- package/package.json +1 -1
- package/src/config/vector-cortex-ml5a.ts +30 -0
- package/src/config/vector-cortex-ml5b.ts +28 -0
- package/src/config/vector-cortex.ts +5 -5
- package/src/config.ts +2 -0
- package/src/monitoring.ts +24 -0
- package/src/store/backfill.ts +1 -0
- package/src/vector-cortex/encoder/bench-export.ts +65 -0
- package/src/vector-cortex/encoder/bench.ts +109 -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
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config/vector-cortex-ml5a.ts — ML5-A five-head training + calibration flag.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from vector-cortex.ts so that file stays under the 300-line soft
|
|
5
|
+
* limit (soft-as-hard gate), exactly as vector-cortex-vc9a.ts..vector-cortex-vc9d.ts
|
|
6
|
+
* were. This is the ML5 training sprint flag. vector-cortex.ts re-exports the
|
|
7
|
+
* ENUM below and root src/config.ts re-exports it, so no consumer import path
|
|
8
|
+
* changes.
|
|
9
|
+
*
|
|
10
|
+
* The split is purely mechanical: ML5A_ENABLED is byte-identical in name,
|
|
11
|
+
* semantics, and default to the definition it replaces, and vector-cortex.ts
|
|
12
|
+
* re-exports it so every existing `from "./config/vector-cortex.js"` import
|
|
13
|
+
* keeps resolving unchanged.
|
|
14
|
+
*
|
|
15
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
|
|
16
|
+
*/
|
|
17
|
+
import { sprintFlag } from "./vector-cortex-flag.js";
|
|
18
|
+
/**
|
|
19
|
+
* ML5-A — five-head training + calibrated onnx asset. Default ON.
|
|
20
|
+
* `MEGACOMPACT_ML5_A=0` disables and is byte-identical to the placeholder
|
|
21
|
+
* predecessor (VC2C-era): `calibrate.ts`/`heads.ts` keep serving the LCG fake
|
|
22
|
+
* projections and the placeholder `fitTemperature`/`fitThreshold`, mode B
|
|
23
|
+
* trigram continues serving, and no trained artifact is loaded (a fresh/no
|
|
24
|
+
* corpus also no-ops gracefully — asset_emitted:false, placeholder behavior,
|
|
25
|
+
* byte-identical). This flag MUST also be a dashboard SETTINGS toggle (visible
|
|
26
|
+
* in config UI, never in EXCLUDED_SETTINGS), mirroring VC4A..VC9D.
|
|
27
|
+
*/
|
|
28
|
+
export const ML5A_ENABLED = () => sprintFlag("MEGACOMPACT_ML5_A");
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config/vector-cortex-ml5b.ts — ML5-B production bench harness flag.
|
|
3
|
+
*
|
|
4
|
+
* Sibling extract mirroring vector-cortex-ml5a.ts, so vector-cortex.ts stays
|
|
5
|
+
* under its 300-line soft limit (soft-as-hard gate). This is the ONNX Runtime
|
|
6
|
+
* evaluation/benchmark sprint flag. vector-cortex.ts re-exports the ENUM below
|
|
7
|
+
* and root src/config.ts re-exports it, so no consumer import path changes.
|
|
8
|
+
*
|
|
9
|
+
* ML5-B introduces NO runtime code path: the bench harness and corpus export
|
|
10
|
+
* are developer/evidence tooling (scripts/) plus a consumer-facing TypeScript
|
|
11
|
+
* shell (src/vector-cortex/encoder/bench.ts) that only writes monitoring
|
|
12
|
+
* events. The flag records intent and scopes the sprint's evidence assets; it
|
|
13
|
+
* gates nothing at runtime today. There is no HTTP endpoint and no dashboard
|
|
14
|
+
* change, so there is no SETTINGS toggle and no EXCLUDED_SETTINGS interaction.
|
|
15
|
+
*
|
|
16
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
|
|
17
|
+
*/
|
|
18
|
+
import { sprintFlag } from "./vector-cortex-flag.js";
|
|
19
|
+
/**
|
|
20
|
+
* ML5-B — production bench harness (ONNX Runtime eval). Default ON.
|
|
21
|
+
* `MEGACOMPACT_ML5_B=0` disables and is byte-identical to the ML5-A survivor:
|
|
22
|
+
* no bench endpoint exists and mode B continues to serve all clients exactly as
|
|
23
|
+
* before. The flag does not gate the harness itself — the harness is an on-demand
|
|
24
|
+
* developer tool with no runtime path.
|
|
25
|
+
*/
|
|
26
|
+
export const ML5B_ENABLED = () => sprintFlag("MEGACOMPACT_ML5_B");
|
|
@@ -239,16 +239,16 @@ export const VC8A_ENABLED = () => sprintFlag("MEGACOMPACT_VC8A");
|
|
|
239
239
|
* mirroring VC4A..VC8A.
|
|
240
240
|
*/
|
|
241
241
|
export const VC8B_ENABLED = () => sprintFlag("MEGACOMPACT_VC8B");
|
|
242
|
-
// VC8C (canary selection +
|
|
243
|
-
// vector-cortex
|
|
244
|
-
// Re-exported here so every existing `from "./config/vector-cortex.js"`
|
|
245
|
-
// import keeps resolving unchanged.
|
|
242
|
+
// VC8C (canary selection + Rust parity) extracted to vector-cortex-vc8c.ts;
|
|
243
|
+
// re-exported so existing `./config/vector-cortex.js` imports keep resolving.
|
|
246
244
|
export { VC8C_ENABLED } from "./vector-cortex-vc8c.js";
|
|
247
|
-
// VC9A/VC9B/VC9C/VC9D split to
|
|
245
|
+
// VC9A/VC9B/VC9C/VC9D/PCC/ML5A split to sibling files to stay under the 300-line soft limit.
|
|
248
246
|
export { VC9A_ENABLED } from "./vector-cortex-vc9a.js";
|
|
249
247
|
export { VC9B_ENABLED } from "./vector-cortex-vc9b.js";
|
|
250
248
|
export { VC9C_ENABLED } from "./vector-cortex-vc9c.js";
|
|
251
249
|
export { VC9D_ENABLED } from "./vector-cortex-vc9d.js";
|
|
252
250
|
export { PCC_ENABLED } from "./vector-cortex-pcc.js";
|
|
251
|
+
export { ML5A_ENABLED } from "./vector-cortex-ml5a.js";
|
|
252
|
+
export { ML5B_ENABLED } from "./vector-cortex-ml5b.js";
|
|
253
253
|
// Breaker constants (TRIAD_RESILIENCE.md §breaker) extracted to vector-cortex-breakers.ts.
|
|
254
254
|
export { BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./vector-cortex-breakers.js";
|
package/dist/config.js
CHANGED
|
@@ -114,4 +114,4 @@ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
|
|
|
114
114
|
// default ON, `=0`/`_DISABLED` off. Re-exported from src/config/vector-cortex.ts
|
|
115
115
|
// so root consumers share one source of truth.
|
|
116
116
|
// ---------------------------------------------------------------------------
|
|
117
|
-
export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, VC8B_ENABLED, VC8C_ENABLED, VC9A_ENABLED, VC9B_ENABLED, VC9C_ENABLED, VC9D_ENABLED, PCC_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
|
|
117
|
+
export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, VC8B_ENABLED, VC8C_ENABLED, VC9A_ENABLED, VC9B_ENABLED, VC9C_ENABLED, VC9D_ENABLED, PCC_ENABLED, ML5A_ENABLED, ML5B_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
|
|
@@ -53,5 +53,6 @@ export const VECTOR_CORTEX_SETTINGS = {
|
|
|
53
53
|
boolDirect("MEGACOMPACT_VC9C", "VC9C SetupTab Cortex Sub-tab", "SetupTab Cortex sub-tab (client UI): a Cortex sub-tab inside SetupTab that consumes the VC9A status endpoint (GET /api/setup-cortex-status) + the VC9B action endpoints. It surfaces the encoder mode A/B/C, asset digest prefix, qualification verdict + threshold failures, the open hard-gate blockers, and the confirmation-gated fetch/bench/verify actions. OFF = byte-identical predecessor (VC9B-era): the Cortex sub-tab is filtered from SUB_TABS and the Setup tab renders exactly as before.", true),
|
|
54
54
|
boolDirect("MEGACOMPACT_VC9D", "VC9D Embedder Detect Consolidation", "Embedder-detect consolidation + VC9 workstream roll-up: memoizes /api/setup-detect against the mutable input (resolved binary path + mtime) so consecutive requests reuse the result without re-spawning, and unifies the embedder + cortex sub-tabs' 5s poll contract. OFF = byte-identical predecessor (VC9C-era): detect spawns fresh per request and the embedder poll keeps its previous cadence.", true),
|
|
55
55
|
boolDirect("MEGACOMPACT_PC_C", "PC-C Dashboard Cache Visibility", "Dashboard per-turn prompt-cache visibility: surfaces the per-turn stable-prefix ratio trend (GET /api/prefix-stability) in the CacheTab PrefixStabilityCard. Reads aggregate ratios/counts from the local monitoring events log only — no payload bytes. OFF = byte-identical predecessor (PC-B-era): /api/prefix-stability returns 404 and the CacheTab omits the PrefixStabilityCard.", true),
|
|
56
|
+
boolDirect("MEGACOMPACT_ML5_A", "ML5-A Five-Head Training Load", "ML5-A real trained-head loading: loadHeadProjections (trained-heads-v1) feeds selectQualifiedEncoder (trainedHeadsPath atomic demotion) + loadCalibrationV1. ON (default) = a pinned trained-heads path must load for mode A. OFF = loaders return null and selection ignores trainedHeadsPath — byte-identical to the placeholder-weighted VC2C path.", true),
|
|
56
57
|
],
|
|
57
58
|
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* monitoring.ts — local dedup monitoring + alerting (Sprint 14, Phase 7).
|
|
3
|
+
*
|
|
4
|
+
* Per-decision structured events go to `events.log` (append-only JSON).
|
|
5
|
+
* Aggregate metrics (hit rate, FP rate, per-tier p95 latency, storage) go to
|
|
6
|
+
* `dashboard.json` — the SAME local-only file the /dashboard UI reads. There is
|
|
7
|
+
* NO Prometheus port and NO network listener (PREVENT-PI-004). Alerting is local
|
|
8
|
+
* only: an FP-rate breach flips the tier to MARK_ONLY and writes a warning.
|
|
9
|
+
*
|
|
10
|
+
* Best-effort: logging/metrics never throw into the add()/search() path.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from "node:fs";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { STATE_DIR_DEFAULT } from "./config.js";
|
|
15
|
+
const TIERS = ["L0", "L1", "L2", "RAPTOR"];
|
|
16
|
+
function emptyMetrics() {
|
|
17
|
+
const dec = {};
|
|
18
|
+
const dp = {};
|
|
19
|
+
const fp = {};
|
|
20
|
+
const lat = {};
|
|
21
|
+
for (const t of TIERS) {
|
|
22
|
+
dec[t] = 0;
|
|
23
|
+
dp[t] = 0;
|
|
24
|
+
fp[t] = 0;
|
|
25
|
+
lat[t] = [];
|
|
26
|
+
}
|
|
27
|
+
return { decisions: dec, deduped: dp, falsePositives: fp, latency: lat, storageBytes: 0 };
|
|
28
|
+
}
|
|
29
|
+
/** Append a structured decision event to events.log (best-effort). */
|
|
30
|
+
export function logDecision(path, ev) {
|
|
31
|
+
try {
|
|
32
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
33
|
+
appendFileSync(path, `${JSON.stringify(ev)}\n`);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* never break the extension on a log failure */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Load metrics from dashboard.json, or return a fresh empty snapshot.
|
|
41
|
+
* Kept simple + synchronous (no network).
|
|
42
|
+
*/
|
|
43
|
+
export function loadMetrics(path) {
|
|
44
|
+
try {
|
|
45
|
+
if (existsSync(path)) {
|
|
46
|
+
const raw = readFileSync(path, "utf-8");
|
|
47
|
+
const parsed = JSON.parse(raw);
|
|
48
|
+
const base = emptyMetrics();
|
|
49
|
+
return {
|
|
50
|
+
decisions: { ...base.decisions, ...(parsed.decisions ?? {}) },
|
|
51
|
+
deduped: { ...base.deduped, ...(parsed.deduped ?? {}) },
|
|
52
|
+
falsePositives: { ...base.falsePositives, ...(parsed.falsePositives ?? {}) },
|
|
53
|
+
latency: { ...base.latency, ...(parsed.latency ?? {}) },
|
|
54
|
+
storageBytes: parsed.storageBytes ?? 0,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* corrupt metrics → fresh */
|
|
60
|
+
}
|
|
61
|
+
return emptyMetrics();
|
|
62
|
+
}
|
|
63
|
+
/** Persist metrics to dashboard.json (best-effort). */
|
|
64
|
+
export function saveMetrics(path, m) {
|
|
65
|
+
try {
|
|
66
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
67
|
+
writeFileSync(path, JSON.stringify(m));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* never break the extension */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Compute the p95 latency (ms) for a tier from its samples. */
|
|
74
|
+
export function p95(samples) {
|
|
75
|
+
if (samples.length === 0)
|
|
76
|
+
return 0;
|
|
77
|
+
const sorted = [...samples].sort((a, b) => a - b);
|
|
78
|
+
const idx = Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95));
|
|
79
|
+
return sorted[idx];
|
|
80
|
+
}
|
|
81
|
+
/** FP rate for a tier over the current window (0..1). */
|
|
82
|
+
export function fpRate(m, tier) {
|
|
83
|
+
const decisions = m.decisions[tier] ?? 0;
|
|
84
|
+
if (decisions === 0)
|
|
85
|
+
return 0;
|
|
86
|
+
return (m.falsePositives[tier] ?? 0) / decisions;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Evaluate FP-rate breaches against the config thresholds. A breached fuzzy tier
|
|
90
|
+
* (L0 vs L1/L2 have different thresholds) is auto-downgraded to MARK_ONLY — the
|
|
91
|
+
* local re-map of "alertmanager" (QA #18/#19): record but don't collapse, no
|
|
92
|
+
* remote alert. Returns the tiers flipped so the caller can mutate its config.
|
|
93
|
+
*/
|
|
94
|
+
export function evaluateAlerts(m, cfg) {
|
|
95
|
+
const breached = [];
|
|
96
|
+
const warnings = [];
|
|
97
|
+
for (const tier of TIERS) {
|
|
98
|
+
const rate = fpRate(m, tier);
|
|
99
|
+
const limit = tier === "L0" ? cfg.FP_RATE_L0 : cfg.FP_RATE_L1L2;
|
|
100
|
+
if (rate > limit) {
|
|
101
|
+
breached.push(tier);
|
|
102
|
+
warnings.push(`DEDUP FP BREACH tier=${tier} rate=${rate.toFixed(4)} > ${limit}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { breached, warnings };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Record one decision into the metrics snapshot (mutates `m` in place) and
|
|
109
|
+
* returns the updated snapshot. Caps stored latency samples to keep memory
|
|
110
|
+
* bounded (last 1000 per tier).
|
|
111
|
+
*/
|
|
112
|
+
export function recordDecision(m, tier, result, latencyMs, falsePositive = false) {
|
|
113
|
+
m.decisions[tier] = (m.decisions[tier] ?? 0) + 1;
|
|
114
|
+
if (result === "deduped")
|
|
115
|
+
m.deduped[tier] = (m.deduped[tier] ?? 0) + 1;
|
|
116
|
+
if (falsePositive)
|
|
117
|
+
m.falsePositives[tier] = (m.falsePositives[tier] ?? 0) + 1;
|
|
118
|
+
const arr = m.latency[tier] ?? (m.latency[tier] = []);
|
|
119
|
+
arr.push(latencyMs);
|
|
120
|
+
if (arr.length > 1000)
|
|
121
|
+
arr.shift();
|
|
122
|
+
return m;
|
|
123
|
+
}
|
|
124
|
+
/** Default metrics path alongside the state dir. */
|
|
125
|
+
export function defaultMetricsPath(stateDir = STATE_DIR_DEFAULT) {
|
|
126
|
+
return join(stateDir, "dashboard.json");
|
|
127
|
+
}
|
|
128
|
+
/** Default events-log path alongside the state dir. */
|
|
129
|
+
export function defaultEventsPath(stateDir = STATE_DIR_DEFAULT) {
|
|
130
|
+
return join(stateDir, "events.log");
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Append a RecallQualityEvent to events.log (best-effort, non-fatal).
|
|
134
|
+
* Follows the same append-one-JSON-line pattern as logDecision.
|
|
135
|
+
*/
|
|
136
|
+
export function logRecallQuality(path, ev) {
|
|
137
|
+
try {
|
|
138
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
139
|
+
appendFileSync(path, JSON.stringify(ev) + "\n", "utf-8");
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
/* best-effort — never break the extension */
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// ML5-B encoder bench events (consumer-facing for the dashboard + evidence)
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
/**
|
|
149
|
+
* Append a structured ML5-B bench event to events.log (best-effort, non-fatal).
|
|
150
|
+
* Mirrors the extension's appendEvent schema ({ ts, event, ...fields }) so the
|
|
151
|
+
* dashboard live-stream tail and evidence tooling parse the four
|
|
152
|
+
* `vector_cortex_encoder_bench_*` events identically. The bench is developer/
|
|
153
|
+
* evidence tooling with no runtime gating; this only records its results.
|
|
154
|
+
*/
|
|
155
|
+
export function logBenchEvent(path, event, fields) {
|
|
156
|
+
try {
|
|
157
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
158
|
+
appendFileSync(path, JSON.stringify({ ts: Date.now(), event, ...fields }) + "\n", "utf8");
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
/* best-effort — never break the caller */
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// Dedup audit trail (external-audit item #2)
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// The event shape and its append helper are DEFINED in vectorStore/dedup-audit.ts,
|
|
168
|
+
// co-located with the only recorder that emits them (this file already carries
|
|
169
|
+
// decision events + the metrics snapshot + FP alerting and must stay under its
|
|
170
|
+
// 300-line soft limit). Re-exported here so callers that treat monitoring.ts as
|
|
171
|
+
// the events.log barrel — including the dashboard SSE tail — keep one import.
|
|
172
|
+
export { logDedupAudit } from "./vectorStore/dedup-audit.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config/vector-cortex-ml5a.ts — ML5-A five-head training + calibration flag.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from vector-cortex.ts so that file stays under the 300-line soft
|
|
5
|
+
* limit (soft-as-hard gate), exactly as vector-cortex-vc9a.ts..vector-cortex-vc9d.ts
|
|
6
|
+
* were. This is the ML5 training sprint flag. vector-cortex.ts re-exports the
|
|
7
|
+
* ENUM below and root src/config.ts re-exports it, so no consumer import path
|
|
8
|
+
* changes.
|
|
9
|
+
*
|
|
10
|
+
* The split is purely mechanical: ML5A_ENABLED is byte-identical in name,
|
|
11
|
+
* semantics, and default to the definition it replaces, and vector-cortex.ts
|
|
12
|
+
* re-exports it so every existing `from "./config/vector-cortex.js"` import
|
|
13
|
+
* keeps resolving unchanged.
|
|
14
|
+
*
|
|
15
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
|
|
16
|
+
*/
|
|
17
|
+
import { sprintFlag } from "./vector-cortex-flag.js";
|
|
18
|
+
/**
|
|
19
|
+
* ML5-A — five-head training + calibrated onnx asset. Default ON.
|
|
20
|
+
* `MEGACOMPACT_ML5_A=0` disables and is byte-identical to the placeholder
|
|
21
|
+
* predecessor (VC2C-era): `calibrate.ts`/`heads.ts` keep serving the LCG fake
|
|
22
|
+
* projections and the placeholder `fitTemperature`/`fitThreshold`, mode B
|
|
23
|
+
* trigram continues serving, and no trained artifact is loaded (a fresh/no
|
|
24
|
+
* corpus also no-ops gracefully — asset_emitted:false, placeholder behavior,
|
|
25
|
+
* byte-identical). This flag MUST also be a dashboard SETTINGS toggle (visible
|
|
26
|
+
* in config UI, never in EXCLUDED_SETTINGS), mirroring VC4A..VC9D.
|
|
27
|
+
*/
|
|
28
|
+
export const ML5A_ENABLED = () => sprintFlag("MEGACOMPACT_ML5_A");
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config/vector-cortex-ml5b.ts — ML5-B production bench harness flag.
|
|
3
|
+
*
|
|
4
|
+
* Sibling extract mirroring vector-cortex-ml5a.ts, so vector-cortex.ts stays
|
|
5
|
+
* under its 300-line soft limit (soft-as-hard gate). This is the ONNX Runtime
|
|
6
|
+
* evaluation/benchmark sprint flag. vector-cortex.ts re-exports the ENUM below
|
|
7
|
+
* and root src/config.ts re-exports it, so no consumer import path changes.
|
|
8
|
+
*
|
|
9
|
+
* ML5-B introduces NO runtime code path: the bench harness and corpus export
|
|
10
|
+
* are developer/evidence tooling (scripts/) plus a consumer-facing TypeScript
|
|
11
|
+
* shell (src/vector-cortex/encoder/bench.ts) that only writes monitoring
|
|
12
|
+
* events. The flag records intent and scopes the sprint's evidence assets; it
|
|
13
|
+
* gates nothing at runtime today. There is no HTTP endpoint and no dashboard
|
|
14
|
+
* change, so there is no SETTINGS toggle and no EXCLUDED_SETTINGS interaction.
|
|
15
|
+
*
|
|
16
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
|
|
17
|
+
*/
|
|
18
|
+
import { sprintFlag } from "./vector-cortex-flag.js";
|
|
19
|
+
/**
|
|
20
|
+
* ML5-B — production bench harness (ONNX Runtime eval). Default ON.
|
|
21
|
+
* `MEGACOMPACT_ML5_B=0` disables and is byte-identical to the ML5-A survivor:
|
|
22
|
+
* no bench endpoint exists and mode B continues to serve all clients exactly as
|
|
23
|
+
* before. The flag does not gate the harness itself — the harness is an on-demand
|
|
24
|
+
* developer tool with no runtime path.
|
|
25
|
+
*/
|
|
26
|
+
export const ML5B_ENABLED = () => sprintFlag("MEGACOMPACT_ML5_B");
|
|
@@ -239,16 +239,16 @@ export const VC8A_ENABLED = () => sprintFlag("MEGACOMPACT_VC8A");
|
|
|
239
239
|
* mirroring VC4A..VC8A.
|
|
240
240
|
*/
|
|
241
241
|
export const VC8B_ENABLED = () => sprintFlag("MEGACOMPACT_VC8B");
|
|
242
|
-
// VC8C (canary selection +
|
|
243
|
-
// vector-cortex
|
|
244
|
-
// Re-exported here so every existing `from "./config/vector-cortex.js"`
|
|
245
|
-
// import keeps resolving unchanged.
|
|
242
|
+
// VC8C (canary selection + Rust parity) extracted to vector-cortex-vc8c.ts;
|
|
243
|
+
// re-exported so existing `./config/vector-cortex.js` imports keep resolving.
|
|
246
244
|
export { VC8C_ENABLED } from "./vector-cortex-vc8c.js";
|
|
247
|
-
// VC9A/VC9B/VC9C/VC9D split to
|
|
245
|
+
// VC9A/VC9B/VC9C/VC9D/PCC/ML5A split to sibling files to stay under the 300-line soft limit.
|
|
248
246
|
export { VC9A_ENABLED } from "./vector-cortex-vc9a.js";
|
|
249
247
|
export { VC9B_ENABLED } from "./vector-cortex-vc9b.js";
|
|
250
248
|
export { VC9C_ENABLED } from "./vector-cortex-vc9c.js";
|
|
251
249
|
export { VC9D_ENABLED } from "./vector-cortex-vc9d.js";
|
|
252
250
|
export { PCC_ENABLED } from "./vector-cortex-pcc.js";
|
|
251
|
+
export { ML5A_ENABLED } from "./vector-cortex-ml5a.js";
|
|
252
|
+
export { ML5B_ENABLED } from "./vector-cortex-ml5b.js";
|
|
253
253
|
// Breaker constants (TRIAD_RESILIENCE.md §breaker) extracted to vector-cortex-breakers.ts.
|
|
254
254
|
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, 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, 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";
|
package/dist/src/monitoring.js
CHANGED
|
@@ -143,6 +143,25 @@ export function logRecallQuality(path, ev) {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
// ---------------------------------------------------------------------------
|
|
146
|
+
// ML5-B encoder bench events (consumer-facing for the dashboard + evidence)
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
/**
|
|
149
|
+
* Append a structured ML5-B bench event to events.log (best-effort, non-fatal).
|
|
150
|
+
* Mirrors the extension's appendEvent schema ({ ts, event, ...fields }) so the
|
|
151
|
+
* dashboard live-stream tail and evidence tooling parse the four
|
|
152
|
+
* `vector_cortex_encoder_bench_*` events identically. The bench is developer/
|
|
153
|
+
* evidence tooling with no runtime gating; this only records its results.
|
|
154
|
+
*/
|
|
155
|
+
export function logBenchEvent(path, event, fields) {
|
|
156
|
+
try {
|
|
157
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
158
|
+
appendFileSync(path, JSON.stringify({ ts: Date.now(), event, ...fields }) + "\n", "utf8");
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
/* best-effort — never break the caller */
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
146
165
|
// Dedup audit trail (external-audit item #2)
|
|
147
166
|
// ---------------------------------------------------------------------------
|
|
148
167
|
// The event shape and its append helper are DEFINED in vectorStore/dedup-audit.ts,
|
|
@@ -94,6 +94,7 @@ export function backfillContentHashes(stateDir = getStateDir()) {
|
|
|
94
94
|
}
|
|
95
95
|
if (THROTTLE_MS > 0) {
|
|
96
96
|
// No-op in this synchronous build; placeholder for future streaming backfill.
|
|
97
|
+
// guardrails-allow PREVENT-STUB-001: ML5-C
|
|
97
98
|
}
|
|
98
99
|
return { processed, updated, duplicatesResolved };
|
|
99
100
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/encoder/bench-export.ts — ML5-B bench result contract.
|
|
3
|
+
*
|
|
4
|
+
* BenchResultV1 is the typed surface `bench.ts` parses from the qualification
|
|
5
|
+
* harness (`scripts/ml5/bench-onnx-prod.mjs`) and the dashboard / evidence
|
|
6
|
+
* tooling consume. It carries AGGREGATE measurements + a digest only — never
|
|
7
|
+
* chunk/message content (EVAL-REDACT-002).
|
|
8
|
+
*
|
|
9
|
+
* Contract-first (ENGINEERING_PRACTICES §3): this types file is the reviewed
|
|
10
|
+
* gate; implementations import from it. Pi-agnostic, dependency-free
|
|
11
|
+
* (PREVENT-PI-004 / PREVENT-011).
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/encoder/bench.ts — ML5-B bench runner (consumer-facing shell).
|
|
3
|
+
*
|
|
4
|
+
* Calls `scripts/ml5/bench-onnx-prod.mjs` via child_process, parses the
|
|
5
|
+
* BenchResultV1 it emits, and writes the four `vector_cortex_encoder_bench_*`
|
|
6
|
+
* events to the monitoring events.log (the dashboard / ML5-D surface consume
|
|
7
|
+
* them later). This is NOT a runtime path — it is developer/evidence tooling.
|
|
8
|
+
*
|
|
9
|
+
* Events written (all best-effort / non-fatal):
|
|
10
|
+
* - vector_cortex_encoder_bench_p95_ms
|
|
11
|
+
* - vector_cortex_encoder_bench_rss_mib
|
|
12
|
+
* - vector_cortex_encoder_bench_opset_ok
|
|
13
|
+
* - vector_cortex_encoder_bench_deterministic
|
|
14
|
+
*
|
|
15
|
+
* Pi-agnostic, dependency-free (PREVENT-PI-004 — the child bench is pure local
|
|
16
|
+
* computation). No `any` (PREVENT-011).
|
|
17
|
+
*/
|
|
18
|
+
import { spawnSync } from "node:child_process";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
import { getStateDir } from "../../store.js";
|
|
22
|
+
import { defaultEventsPath, logBenchEvent } from "../../monitoring.js";
|
|
23
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const REPO_ROOT = join(HERE, "..", "..", "..");
|
|
25
|
+
const BENCH_SCRIPT = join(REPO_ROOT, "scripts", "ml5", "bench-onnx-prod.mjs");
|
|
26
|
+
/** Default events.log beside the state dir (mirrors defaultEventsPath). */
|
|
27
|
+
function benchEventsPath(stateDir) {
|
|
28
|
+
return defaultEventsPath(stateDir);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Run the ONNX bench once and record its four events. Returns the parsed
|
|
32
|
+
* BenchResultV1. On any failure (script missing, non-zero exit, unparsable
|
|
33
|
+
* output) it returns a degraded result with gates.all:false — never throws, so
|
|
34
|
+
* the caller's agent loop is never broken (non-fatal store/write contract).
|
|
35
|
+
*/
|
|
36
|
+
export function runBench(stateDir = getStateDir()) {
|
|
37
|
+
const noop = (error) => ({
|
|
38
|
+
timestamp: Date.now(),
|
|
39
|
+
platform: `${process.platform}-${process.arch}`,
|
|
40
|
+
encoderNative: false,
|
|
41
|
+
threads: 4,
|
|
42
|
+
tokens: 512,
|
|
43
|
+
corpusTokens: 0,
|
|
44
|
+
p95Ms: null,
|
|
45
|
+
rssMib: null,
|
|
46
|
+
rssBaselineMib: null,
|
|
47
|
+
rssMarginalMib: null,
|
|
48
|
+
opset: null,
|
|
49
|
+
deterministic: false,
|
|
50
|
+
digest: null,
|
|
51
|
+
gates: { latency: false, rss: false, opset: false, determinism: false, all: false },
|
|
52
|
+
error,
|
|
53
|
+
});
|
|
54
|
+
const fallback = (error) => {
|
|
55
|
+
const r = noop(error);
|
|
56
|
+
emitEvents(stateDir, r);
|
|
57
|
+
return r;
|
|
58
|
+
};
|
|
59
|
+
try {
|
|
60
|
+
const res = spawnSync(process.execPath, ["--expose-gc", BENCH_SCRIPT], {
|
|
61
|
+
cwd: REPO_ROOT,
|
|
62
|
+
encoding: "utf8",
|
|
63
|
+
timeout: 600_000,
|
|
64
|
+
});
|
|
65
|
+
const stdout = (res.stdout ?? "").trim();
|
|
66
|
+
if (res.status === null) {
|
|
67
|
+
return fallback("bench timed out or failed to spawn");
|
|
68
|
+
}
|
|
69
|
+
const parsed = JSON.parse(stdout || "");
|
|
70
|
+
if (!isBenchResultV1(parsed)) {
|
|
71
|
+
return fallback("bench output was not a BenchResultV1");
|
|
72
|
+
}
|
|
73
|
+
emitEvents(stateDir, parsed);
|
|
74
|
+
return parsed;
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
return fallback(`bench failed: ${e?.message ?? String(e)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function isBenchResultV1(v) {
|
|
81
|
+
if (typeof v !== "object" || v === null)
|
|
82
|
+
return false;
|
|
83
|
+
const o = v;
|
|
84
|
+
return (typeof o.timestamp === "number" &&
|
|
85
|
+
typeof o.platform === "string" &&
|
|
86
|
+
typeof o.encoderNative === "boolean" &&
|
|
87
|
+
typeof o.threads === "number" &&
|
|
88
|
+
typeof o.tokens === "number" &&
|
|
89
|
+
typeof o.corpusTokens === "number" &&
|
|
90
|
+
typeof o.gates === "object" && o.gates !== null &&
|
|
91
|
+
typeof o.gates.all === "boolean");
|
|
92
|
+
}
|
|
93
|
+
function emitEvents(stateDir, r) {
|
|
94
|
+
const path = benchEventsPath(stateDir);
|
|
95
|
+
const run = { platform: r.platform, encoderNative: r.encoderNative, threads: r.threads, tokens: r.tokens, digest: r.digest, corpusTokens: r.corpusTokens };
|
|
96
|
+
logBenchEvent(path, "vector_cortex_encoder_bench_p95_ms", { ...run, p95Ms: r.p95Ms, pass: r.gates.latency });
|
|
97
|
+
logBenchEvent(path, "vector_cortex_encoder_bench_rss_mib", { ...run, rssMib: r.rssMib, rssBaselineMib: r.rssBaselineMib, rssMarginalMib: r.rssMarginalMib, pass: r.gates.rss });
|
|
98
|
+
logBenchEvent(path, "vector_cortex_encoder_bench_opset_ok", { ...run, opset: r.opset, pass: r.gates.opset });
|
|
99
|
+
logBenchEvent(path, "vector_cortex_encoder_bench_deterministic", { ...run, deterministic: r.deterministic, pass: r.gates.determinism });
|
|
100
|
+
}
|
|
@@ -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
|
+
}
|