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.
Files changed (36) hide show
  1. package/dist/config/vector-cortex-ml5a.js +28 -0
  2. package/dist/config/vector-cortex-ml5b.js +26 -0
  3. package/dist/config/vector-cortex.js +5 -5
  4. package/dist/config.js +1 -1
  5. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
  6. package/dist/monitoring.js +172 -0
  7. package/dist/src/config/vector-cortex-ml5a.js +28 -0
  8. package/dist/src/config/vector-cortex-ml5b.js +26 -0
  9. package/dist/src/config/vector-cortex.js +5 -5
  10. package/dist/src/config.js +1 -1
  11. package/dist/src/monitoring.js +19 -0
  12. package/dist/src/store/backfill.js +1 -0
  13. package/dist/src/vector-cortex/encoder/bench-export.js +13 -0
  14. package/dist/src/vector-cortex/encoder/bench.js +100 -0
  15. package/dist/src/vector-cortex/encoder/calibrate.js +55 -0
  16. package/dist/src/vector-cortex/encoder/heads.js +87 -0
  17. package/dist/src/vector-cortex/encoder/select.js +10 -0
  18. package/dist/vector-cortex/encoder/bench-export.js +13 -0
  19. package/dist/vector-cortex/encoder/bench.js +100 -0
  20. package/dist/vector-cortex/encoder/calibrate.js +55 -0
  21. package/dist/vector-cortex/encoder/heads.js +87 -0
  22. package/dist/vector-cortex/encoder/select.js +10 -0
  23. package/dist/vectorStore/dedup-audit.js +104 -0
  24. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
  25. package/package.json +1 -1
  26. package/src/config/vector-cortex-ml5a.ts +30 -0
  27. package/src/config/vector-cortex-ml5b.ts +28 -0
  28. package/src/config/vector-cortex.ts +5 -5
  29. package/src/config.ts +2 -0
  30. package/src/monitoring.ts +24 -0
  31. package/src/store/backfill.ts +1 -0
  32. package/src/vector-cortex/encoder/bench-export.ts +65 -0
  33. package/src/vector-cortex/encoder/bench.ts +109 -0
  34. package/src/vector-cortex/encoder/calibrate.ts +49 -0
  35. package/src/vector-cortex/encoder/heads.ts +106 -0
  36. package/src/vector-cortex/encoder/select.ts +17 -0
package/src/monitoring.ts CHANGED
@@ -205,6 +205,30 @@ export function logRecallQuality(path: string, ev: RecallQualityEvent): void {
205
205
  }
206
206
  }
207
207
 
208
+ // ---------------------------------------------------------------------------
209
+ // ML5-B encoder bench events (consumer-facing for the dashboard + evidence)
210
+ // ---------------------------------------------------------------------------
211
+
212
+ /**
213
+ * Append a structured ML5-B bench event to events.log (best-effort, non-fatal).
214
+ * Mirrors the extension's appendEvent schema ({ ts, event, ...fields }) so the
215
+ * dashboard live-stream tail and evidence tooling parse the four
216
+ * `vector_cortex_encoder_bench_*` events identically. The bench is developer/
217
+ * evidence tooling with no runtime gating; this only records its results.
218
+ */
219
+ export function logBenchEvent(
220
+ path: string,
221
+ event: string,
222
+ fields: Record<string, unknown>,
223
+ ): void {
224
+ try {
225
+ mkdirSync(dirname(path), { recursive: true });
226
+ appendFileSync(path, JSON.stringify({ ts: Date.now(), event, ...fields }) + "\n", "utf8");
227
+ } catch {
228
+ /* best-effort — never break the caller */
229
+ }
230
+ }
231
+
208
232
  // ---------------------------------------------------------------------------
209
233
  // Dedup audit trail (external-audit item #2)
210
234
  // ---------------------------------------------------------------------------
@@ -134,6 +134,7 @@ export function backfillContentHashes(stateDir: string = getStateDir()): Backfil
134
134
 
135
135
  if (THROTTLE_MS > 0) {
136
136
  // No-op in this synchronous build; placeholder for future streaming backfill.
137
+ // guardrails-allow PREVENT-STUB-001: ML5-C
137
138
  }
138
139
 
139
140
  return { processed, updated, duplicatesResolved };
@@ -0,0 +1,65 @@
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
+
14
+ /** The four ML5-B bench gates, each independently measured. */
15
+ export interface BenchGatesV1 {
16
+ /** p95 latency at 512 tokens on `threads` threads <= 40 ms. */
17
+ readonly latency: boolean;
18
+ /** steady-state marginal RSS over the process baseline <= 150 MiB. */
19
+ readonly rss: boolean;
20
+ /** the loaded model's declared opset_import equals 17. */
21
+ readonly opset: boolean;
22
+ /** SHA-256 of the embedding output identical across 3 runs (maxAbsDelta=0). */
23
+ readonly determinism: boolean;
24
+ /** conjunctive: every gate passed. */
25
+ readonly all: boolean;
26
+ }
27
+
28
+ /**
29
+ * BenchResultV1 — one qualification run of the ONNX encoder bench.
30
+ *
31
+ * Shape is fixed by the ML5-B spec (task 4). `p95Ms`/`rssMib`/`rssMarginalMib`/
32
+ * `digest` are null when the runtime package is absent (degraded run) or a
33
+ * gate could not be measured; `gates.all` is false in that case and `error`
34
+ * (optional) records the honest degradation reason.
35
+ */
36
+ export interface BenchResultV1 {
37
+ readonly timestamp: number;
38
+ /** `${process.platform}-${process.arch}` (e.g. linux-x64, darwin-arm64). */
39
+ readonly platform: string;
40
+ /** true = onnxruntime-node (native); false = onnxruntime-web (WASM). */
41
+ readonly encoderNative: boolean;
42
+ /** intraOpNumThreads used for the latency gate (normative 4). */
43
+ readonly threads: number;
44
+ /** token count per inference (normative 512). */
45
+ readonly tokens: number;
46
+ /** total tokens in the corpus the bench streamed over. */
47
+ readonly corpusTokens: number;
48
+ /** p95 latency in ms (null on degraded/absent runtime). */
49
+ readonly p95Ms: number | null;
50
+ /** steady-state RSS (MiB) over the process baseline, post-GC. */
51
+ readonly rssMib: number | null;
52
+ /** RSS (MiB) sampled at process start before loading the encoder. */
53
+ readonly rssBaselineMib: number | null;
54
+ /** rssMib - rssBaselineMib: the encoder's marginal footprint. */
55
+ readonly rssMarginalMib: number | null;
56
+ /** declared opset_import (17); null when no asset manifest is readable. */
57
+ readonly opset: number | null;
58
+ /** true when the output SHA-256 is identical across 3 runs. */
59
+ readonly deterministic: boolean;
60
+ /** SHA-256 of the embedding output buffer (null on degraded/absent). */
61
+ readonly digest: string | null;
62
+ readonly gates: BenchGatesV1;
63
+ /** Optional: honest degradation / failure reason (no runtime package, etc.). */
64
+ readonly error?: string;
65
+ }
@@ -0,0 +1,109 @@
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
+
19
+ import { spawnSync } from "node:child_process";
20
+ import { dirname, join } from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+ import { getStateDir } from "../../store.js";
23
+ import { defaultEventsPath, logBenchEvent } from "../../monitoring.js";
24
+ import type { BenchResultV1 } from "./bench-export.js";
25
+
26
+ const HERE = dirname(fileURLToPath(import.meta.url));
27
+ const REPO_ROOT = join(HERE, "..", "..", "..");
28
+ const BENCH_SCRIPT = join(REPO_ROOT, "scripts", "ml5", "bench-onnx-prod.mjs");
29
+
30
+ /** Default events.log beside the state dir (mirrors defaultEventsPath). */
31
+ function benchEventsPath(stateDir: string): string {
32
+ return defaultEventsPath(stateDir);
33
+ }
34
+
35
+ /**
36
+ * Run the ONNX bench once and record its four events. Returns the parsed
37
+ * BenchResultV1. On any failure (script missing, non-zero exit, unparsable
38
+ * output) it returns a degraded result with gates.all:false — never throws, so
39
+ * the caller's agent loop is never broken (non-fatal store/write contract).
40
+ */
41
+ export function runBench(stateDir: string = getStateDir()): BenchResultV1 {
42
+ const noop = (error: string): BenchResultV1 => ({
43
+ timestamp: Date.now(),
44
+ platform: `${process.platform}-${process.arch}`,
45
+ encoderNative: false,
46
+ threads: 4,
47
+ tokens: 512,
48
+ corpusTokens: 0,
49
+ p95Ms: null,
50
+ rssMib: null,
51
+ rssBaselineMib: null,
52
+ rssMarginalMib: null,
53
+ opset: null,
54
+ deterministic: false,
55
+ digest: null,
56
+ gates: { latency: false, rss: false, opset: false, determinism: false, all: false },
57
+ error,
58
+ });
59
+
60
+ const fallback = (error: string): BenchResultV1 => {
61
+ const r = noop(error);
62
+ emitEvents(stateDir, r);
63
+ return r;
64
+ };
65
+
66
+ try {
67
+ const res = spawnSync(process.execPath, ["--expose-gc", BENCH_SCRIPT], {
68
+ cwd: REPO_ROOT,
69
+ encoding: "utf8",
70
+ timeout: 600_000,
71
+ });
72
+ const stdout = (res.stdout ?? "").trim();
73
+ if (res.status === null) {
74
+ return fallback("bench timed out or failed to spawn");
75
+ }
76
+ const parsed: unknown = JSON.parse(stdout || "");
77
+ if (!isBenchResultV1(parsed)) {
78
+ return fallback("bench output was not a BenchResultV1");
79
+ }
80
+ emitEvents(stateDir, parsed);
81
+ return parsed;
82
+ } catch (e) {
83
+ return fallback(`bench failed: ${(e as Error)?.message ?? String(e)}`);
84
+ }
85
+ }
86
+
87
+ function isBenchResultV1(v: unknown): v is BenchResultV1 {
88
+ if (typeof v !== "object" || v === null) return false;
89
+ const o = v as Record<string, unknown>;
90
+ return (
91
+ typeof o.timestamp === "number" &&
92
+ typeof o.platform === "string" &&
93
+ typeof o.encoderNative === "boolean" &&
94
+ typeof o.threads === "number" &&
95
+ typeof o.tokens === "number" &&
96
+ typeof o.corpusTokens === "number" &&
97
+ typeof o.gates === "object" && o.gates !== null &&
98
+ typeof (o.gates as Record<string, unknown>).all === "boolean"
99
+ );
100
+ }
101
+
102
+ function emitEvents(stateDir: string, r: BenchResultV1): void {
103
+ const path = benchEventsPath(stateDir);
104
+ const run = { platform: r.platform, encoderNative: r.encoderNative, threads: r.threads, tokens: r.tokens, digest: r.digest, corpusTokens: r.corpusTokens };
105
+ logBenchEvent(path, "vector_cortex_encoder_bench_p95_ms", { ...run, p95Ms: r.p95Ms, pass: r.gates.latency });
106
+ logBenchEvent(path, "vector_cortex_encoder_bench_rss_mib", { ...run, rssMib: r.rssMib, rssBaselineMib: r.rssBaselineMib, rssMarginalMib: r.rssMarginalMib, pass: r.gates.rss });
107
+ logBenchEvent(path, "vector_cortex_encoder_bench_opset_ok", { ...run, opset: r.opset, pass: r.gates.opset });
108
+ logBenchEvent(path, "vector_cortex_encoder_bench_deterministic", { ...run, deterministic: r.deterministic, pass: r.gates.determinism });
109
+ }
@@ -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) {