pi-mega-compact 0.20.10 → 0.20.12

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 (86) hide show
  1. package/dist/config/vector-cortex-breakers.js +31 -0
  2. package/dist/config/vector-cortex.js +17 -26
  3. package/dist/config.js +1 -1
  4. package/dist/extensions/dashboard-server/api-contracts/vector-cortex-cache.js +11 -0
  5. package/dist/extensions/dashboard-server/route-dispatch.js +6 -0
  6. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
  7. package/dist/extensions/dashboard-server/routes-vector-cortex-crystals.js +65 -0
  8. package/dist/src/config/vector-cortex-breakers.js +31 -0
  9. package/dist/src/config/vector-cortex.js +17 -26
  10. package/dist/src/config.js +1 -1
  11. package/dist/src/vector-cortex/cache/_crystal-fixture.js +71 -0
  12. package/dist/src/vector-cortex/cache/crystal-emit.js +76 -0
  13. package/dist/src/vector-cortex/cache/crystal.js +201 -0
  14. package/dist/src/vector-cortex/cache/store.js +196 -0
  15. package/dist/src/vector-cortex/cache/types.js +73 -0
  16. package/dist/vector-cortex/cache/_crystal-fixture.js +71 -0
  17. package/dist/vector-cortex/cache/crystal-emit.js +76 -0
  18. package/dist/vector-cortex/cache/crystal.js +201 -0
  19. package/dist/vector-cortex/cache/store.js +196 -0
  20. package/dist/vector-cortex/cache/types.js +73 -0
  21. package/extensions/dashboard-client/dist/assets/{AreaChart-BDMjyRQp.js → AreaChart-CB-U7ViX.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{AreaChart-BDMjyRQp.js.map → AreaChart-CB-U7ViX.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/{BarChart-CqzPzOkj.js → BarChart-DnLw0fxA.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{BarChart-CqzPzOkj.js.map → BarChart-DnLw0fxA.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{CacheTab-Bo8SH9q8.js → CacheTab-CjyTVDiP.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{CacheTab-Bo8SH9q8.js.map → CacheTab-CjyTVDiP.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{EventsTab-DIEs6zC-.js → EventsTab-Bz3QEWam.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{EventsTab-DIEs6zC-.js.map → EventsTab-Bz3QEWam.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{HealthTab-3a8IjwIK.js → HealthTab-CUylEvEY.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{HealthTab-3a8IjwIK.js.map → HealthTab-CUylEvEY.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-CdKwAcXo.js → MaintenanceTab-Cz_hj_E7.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-CdKwAcXo.js.map → MaintenanceTab-Cz_hj_E7.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-D2hHuNj-.js → MemoryMapTab-Dh8brIR1.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-D2hHuNj-.js.map → MemoryMapTab-Dh8brIR1.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{MetricsTab-CFhIhqrr.js → MetricsTab-CkqSXc6e.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{MetricsTab-CFhIhqrr.js.map → MetricsTab-CkqSXc6e.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{OverviewTab-Dzp5ZoiA.js → OverviewTab-DGbJcx6d.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{OverviewTab-Dzp5ZoiA.js.map → OverviewTab-DGbJcx6d.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{ReposTab-CFJT9mDR.js → ReposTab-BawS3nfF.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{ReposTab-CFJT9mDR.js.map → ReposTab-BawS3nfF.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{SessionsTab-BhP1z6_y.js → SessionsTab-CUVnmYks.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{SessionsTab-BhP1z6_y.js.map → SessionsTab-CUVnmYks.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{SetupTab-2Glh7MKk.js → SetupTab-cxTuyZNk.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{SetupTab-2Glh7MKk.js.map → SetupTab-cxTuyZNk.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DmwBDhkw.js → TimeSavedCard-Cv5SENkE.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DmwBDhkw.js.map → TimeSavedCard-Cv5SENkE.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{TurnsTab-CYIaOUUZ.js → TurnsTab-yl6BBG_F.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{TurnsTab-CYIaOUUZ.js.map → TurnsTab-yl6BBG_F.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/VectorCortexTab-94fqVjat.js +2 -0
  50. package/extensions/dashboard-client/dist/assets/VectorCortexTab-94fqVjat.js.map +1 -0
  51. package/extensions/dashboard-client/dist/assets/{WikiTab-CP-JEd17.js → WikiTab-DKv-0xCQ.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{WikiTab-CP-JEd17.js.map → WikiTab-DKv-0xCQ.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{button-B1RhLsGs.js → button-CVPrO4UU.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{button-B1RhLsGs.js.map → button-CVPrO4UU.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{card-BLPT2-8G.js → card-BFLnQJEo.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{card-BLPT2-8G.js.map → card-BFLnQJEo.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DVOMJzL2.js → generateCategoricalChart-B-IUoLd1.js} +2 -2
  58. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DVOMJzL2.js.map → generateCategoricalChart-B-IUoLd1.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{index-CuLdiHRl.js → index-Do749WlW.js} +3 -3
  60. package/extensions/dashboard-client/dist/assets/{index-CuLdiHRl.js.map → index-Do749WlW.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{switch-C5tqzhcl.js → switch-3uFnZmtq.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{switch-C5tqzhcl.js.map → switch-3uFnZmtq.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{toggle-HcM6W2Yl.js → toggle-C1rYLeXe.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{toggle-HcM6W2Yl.js.map → toggle-C1rYLeXe.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/{useSSE-BtWUs2kL.js → useSSE-D1qzLzgR.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{useSSE-BtWUs2kL.js.map → useSSE-D1qzLzgR.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/index.html +1 -1
  68. package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
  69. package/extensions/dashboard-client/src/tabs/VectorCortexCrystalsCard.tsx +46 -0
  70. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +8 -0
  71. package/extensions/dashboard-client/src/types/vector-cortex.ts +20 -0
  72. package/extensions/dashboard-server/api-contracts/vector-cortex-cache.ts +53 -0
  73. package/extensions/dashboard-server/route-dispatch.ts +5 -0
  74. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
  75. package/extensions/dashboard-server/routes-vector-cortex-crystals.ts +74 -0
  76. package/package.json +1 -1
  77. package/src/config/vector-cortex-breakers.ts +32 -0
  78. package/src/config/vector-cortex.ts +31 -26
  79. package/src/config.ts +1 -0
  80. package/src/vector-cortex/cache/_crystal-fixture.ts +126 -0
  81. package/src/vector-cortex/cache/crystal-emit.ts +99 -0
  82. package/src/vector-cortex/cache/crystal.ts +217 -0
  83. package/src/vector-cortex/cache/store.ts +213 -0
  84. package/src/vector-cortex/cache/types.ts +206 -0
  85. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD7vGaRS.js +0 -2
  86. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD7vGaRS.js.map +0 -1
@@ -0,0 +1,31 @@
1
+ /**
2
+ * config/vector-cortex-breakers.ts — breaker state machine constants.
3
+ *
4
+ * Extracted from vector-cortex.ts to keep that file under the 300-line soft
5
+ * limit (soft-as-hard gate). These constants are normative in
6
+ * TRIAD_RESILIENCE.md §breaker and consumed by VC0C's breaker seam.
7
+ *
8
+ * Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
9
+ */
10
+ /** Rolling eligibility window (milliseconds). */
11
+ export const BREAKER_WINDOW_MS = 60_000;
12
+ /** Minimum attempts before a breaker may trip or promote. */
13
+ export const BREAKER_MIN_ATTEMPTS = 20;
14
+ /** Performance trip: ≥ this many failures in window, or ≥ this fraction. */
15
+ export const BREAKER_PERF_FAILURES = 5;
16
+ export const BREAKER_PERF_FAILURE_RATE = 0.1;
17
+ /** Correctness trip trips on the first correctness failure. */
18
+ export const BREAKER_CORRECTNESS_FAILURES = 1;
19
+ /** Cooldown before an open breaker may probe (milliseconds). */
20
+ export const BREAKER_COOLDOWN_MS = 30_000;
21
+ /** Consecutive successful probes required to advance a state. */
22
+ export const BREAKER_PROBE_COUNT = 3;
23
+ /** Exponential retry base: 30s * 2^attempt, capped, ±10% jitter. */
24
+ export const BREAKER_RETRY_BASE_MS = 30_000;
25
+ export const BREAKER_RETRY_CAP_MS = 15 * 60_000;
26
+ export const BREAKER_RETRY_JITTER = 0.1;
27
+ /** Promotion hysteresis: failure rate must be < this and p95 within budget. */
28
+ export const BREAKER_HYSTERESIS_FAILURE_RATE = 0.02;
29
+ export const BREAKER_HYSTERESIS_BUDGET_P95_MS = 50;
30
+ /** Minimum healthy residence before a further promotion (milliseconds). */
31
+ export const BREAKER_MIN_HEALTHY_RESIDENCE_MS = 5 * 60_000;
@@ -237,29 +237,20 @@ export const VC6B_ENABLED = () => sprintFlag("MEGACOMPACT_VC6B");
237
237
  * mirroring VC4A/VC4B/VC4C/VC5A/VC5B/VC5C/VC6A/VC6B.
238
238
  */
239
239
  export const VC6C_ENABLED = () => sprintFlag("MEGACOMPACT_VC6C");
240
- // ---------------------------------------------------------------------------
241
- // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
242
- // Rolled numbers for one 60s window; VC0C consumes these at its breaker seam.
243
- // ---------------------------------------------------------------------------
244
- /** Rolling eligibility window (milliseconds). */
245
- export const BREAKER_WINDOW_MS = 60_000;
246
- /** Minimum attempts before a breaker may trip or promote. */
247
- export const BREAKER_MIN_ATTEMPTS = 20;
248
- /** Performance trip: this many failures in window, or this fraction. */
249
- export const BREAKER_PERF_FAILURES = 5;
250
- export const BREAKER_PERF_FAILURE_RATE = 0.1;
251
- /** Correctness trip trips on the first correctness failure. */
252
- export const BREAKER_CORRECTNESS_FAILURES = 1;
253
- /** Cooldown before an open breaker may probe (milliseconds). */
254
- export const BREAKER_COOLDOWN_MS = 30_000;
255
- /** Consecutive successful probes required to advance a state. */
256
- export const BREAKER_PROBE_COUNT = 3;
257
- /** Exponential retry base: 30s * 2^attempt, capped, ±10% jitter. */
258
- export const BREAKER_RETRY_BASE_MS = 30_000;
259
- export const BREAKER_RETRY_CAP_MS = 15 * 60_000;
260
- export const BREAKER_RETRY_JITTER = 0.1;
261
- /** Promotion hysteresis: failure rate must be < this and p95 within budget. */
262
- export const BREAKER_HYSTERESIS_FAILURE_RATE = 0.02;
263
- export const BREAKER_HYSTERESIS_BUDGET_P95_MS = 50;
264
- /** Minimum healthy residence before a further promotion (milliseconds). */
265
- export const BREAKER_MIN_HEALTHY_RESIDENCE_MS = 5 * 60_000;
240
+ /**
241
+ * VC7A frozen range crystals. Default ON. `MEGACOMPACT_VC7A=0` disables and is
242
+ * byte-identical to the predecessor (VC6C): the `encodeCrystalKey` /
243
+ * `CrystalStore` arithmetic STILL RUNS (it is PURE — a canonical length-prefixed
244
+ * encoding plus SHA-256, with no clock, storage, or network), so a crystal is
245
+ * keyed identically and a same-key/different-bytes write is still refused with
246
+ * the flag off. The flag gates ONLY the `vector_cortex_crystal_written` /
247
+ * `vector_cortex_crystal_collision` events and the cache-crystals dashboard seam,
248
+ * which reports `enabled:false` + mode C when off. Flag OFF never gates the
249
+ * crystal/store arithmetic, so flag-off outbound/predecessor golden bytes match
250
+ * exactly. This flag MUST also be a dashboard SETTINGS toggle (visible in config
251
+ * UI, never in EXCLUDED_SETTINGS), mirroring VC4A..VC6C.
252
+ */
253
+ export const VC7A_ENABLED = () => sprintFlag("MEGACOMPACT_VC7A");
254
+ // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker) extracted to
255
+ // vector-cortex-breakers.ts to keep this file under the 300-line soft limit.
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, 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, 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,11 @@
1
+ /**
2
+ * api-contracts/vector-cortex-cache.ts — VC7A frozen-range-crystal API contract.
3
+ *
4
+ * Split from vector-cortex-heal.ts (a separate concern, not a size overflow):
5
+ * crystals are a derived CACHE, not a repair path, and keeping the contract in
6
+ * its own file leaves both well under the 400-line extension limit as VC7B lands.
7
+ *
8
+ * PREVENT-PI-004: type definitions only, no network code.
9
+ * PREVENT-011: no `any` type.
10
+ */
11
+ export {};
@@ -12,6 +12,10 @@ import { handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGame
12
12
  // VC6C repair lives in its own module (routes-vector-cortex-repair.ts) so the
13
13
  // heal route file stays well under the 400-line extension soft limit.
14
14
  import { handleVectorCortexRepair } from "./routes-vector-cortex-repair.js";
15
+ // VC7A frozen-range crystals likewise get their own module so the cache seam
16
+ // stays independent of the heal/repair handlers and every file stays well under
17
+ // the 400-line extension limit.
18
+ import { handleVectorCortexCrystals } from "./routes-vector-cortex-crystals.js";
15
19
  /**
16
20
  * Dispatch a request through every registered route handler.
17
21
  * Returns true if a handler claimed the request (ended the response).
@@ -105,5 +109,7 @@ export function dispatchRoutes(req, res, ctx) {
105
109
  return true;
106
110
  if (handleVectorCortexRepair(req, res, ctx))
107
111
  return true;
112
+ if (handleVectorCortexCrystals(req, res, ctx))
113
+ return true;
108
114
  return false;
109
115
  }
@@ -42,5 +42,6 @@ export const VECTOR_CORTEX_SETTINGS = {
42
42
  boolDirect("MEGACOMPACT_VC6A", "VC6A Advanced Closure Optimization", "Advanced closure optimization: deterministically reduces the already-mandatory VC4C closure by transitive reduction over depends edges, emitting a ClosureProofV2 receipt per closure and a verifier that replays reductions against the conservative oracle (HEAL_PROOF_SET_MISMATCH on selected-set divergence). Protected edges (tool-pair / anchor / contradiction / sole-dependency) are never removed. The optimized selected set is byte-identical to the conservative closure. OFF = byte-identical predecessor (VC5C); arithmetic runs, only the reporter + dashboard seam is suppressed.", true),
43
43
  boolDirect("MEGACOMPACT_VC6B", "VC6B Exact Source Restoration", "Exact source restoration: restores the original bytes of closure spans ONLY from an exact shard (mode A) or a verified ledger range scan (mode B) — never inferred, reconstructed, or paraphrased from embeddings or semantic text. Requests are hard-bounded at 64 spans / 4MiB (HEAL_RESTORE_LIMIT); every span's SHA-256 is recomputed and bytes are inserted only after all requested span metadata validates, so a single bad span fails the whole request closed (HEAL_RESTORE_DIGEST_MISMATCH, HEAL_RESTORE_RANGE_MISMATCH). When no exact source exists (HEAL_RESTORE_SOURCE_MISSING) the old context is omitted and the loss is disclosed (mode C) rather than filled in. OFF = byte-identical predecessor (VC6A).", true),
44
44
  boolDirect("MEGACOMPACT_VC6C", "VC6C Self-Healing Derived State", "Self-healing derived-state controller: detects gaps between each derived subsystem's high-water mark and the durable authority high-water, then rebuilds derived state by copy -> root-digest verification -> atomic pointer switch, so a partially rebuilt subsystem is never made visible. A targeted single-subsystem rebuild is mode A; an ambiguous gap escalates to a full deterministic rebuild (mode B); if both rebuild paths fail the derived state is disabled rather than served stale (mode C). Rebuilds are rate-limited to one per subsystem per 5 minutes with deterministic exponential backoff, so a persistently failing subsystem cannot spin. OFF = byte-identical predecessor (VC6B); no controller runs, so the dashboard reports mode C.", true),
45
+ boolDirect("MEGACOMPACT_VC7A", "VC7A Frozen Range Crystals", "Frozen range crystals: caches a rendered prompt under an IMMUTABLE key built from the source ranges it covers, the digest of those covered bytes, the validated durable dependency high-water, and the renderer + provider profile — and deliberately NOT the global ledger frontier, so an unrelated append leaves the key unchanged instead of invalidating every crystal on every turn. Any covered-byte, dependency, renderer, or profile change invalidates 100%. Ranges are sorted by source start and overlapping ranges are rejected (CRY_RANGE_OVERLAP) rather than merged. The store is content-addressed and write-once: identical bytes re-written are idempotent, but a same-key/different-bytes write returns CRY_KEY_COLLISION and never overwrites, surfacing renderer non-determinism instead of hiding it. A store hit is mode A, a miss/collision forces a fresh deterministic render (mode B), and an unavailable store bypasses the cache entirely (mode C). OFF = byte-identical predecessor (VC6C); the crystal/store arithmetic still runs, only the reporter + dashboard seam is suppressed.", true),
45
46
  ],
46
47
  };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * dashboard-server/routes-vector-cortex-crystals.ts — VC7A frozen-range-crystal
3
+ * dashboard route.
4
+ *
5
+ * Reader-only GET /api/vector-cortex/cache-crystals returning the crystal
6
+ * store's aggregate diagnostics: whether the flag is enabled, the runtime triad
7
+ * mode, how many crystals are held and at what byte volume, hit/miss/hit-byte
8
+ * counters, write/duplicate/collision counters, and the last CRY_* failure code.
9
+ *
10
+ * COUNTS + BYTES + ERROR CODES ONLY. A crystal is a FROZEN RENDERED PROMPT, so
11
+ * this is the surface where a careless payload field would leak the whole framed
12
+ * conversation. It NEVER exposes frozen bytes, covered ranges, span or covered
13
+ * digests, request digests, session ids, or key digests (reader-only,
14
+ * SECURITY_PRIVACY — per-key detail belongs in the structured event log). There
15
+ * is no mutation seam either: crystals are written by the render path, never by
16
+ * a dashboard request, and the store is write-once by construction, so a
17
+ * dashboard-driven write could not overwrite anything even if it existed.
18
+ * Non-GET is rejected outright.
19
+ *
20
+ * Split into its own file (rather than grown into routes-vector-cortex-repair.ts)
21
+ * to keep every extensions/ file well under the 400-line soft-as-hard limit.
22
+ *
23
+ * Guardrails: PREVENT-PI-004 (local in-process state only), PREVENT-011 (no
24
+ * `any`), reader-only aggregate (counts + codes only).
25
+ */
26
+ import { VC7A_ENABLED } from "../../src/config.js";
27
+ import { sendJson } from "./routes-vector-cortex-shared.js";
28
+ /**
29
+ * Reader-only GET /api/vector-cortex/cache-crystals (VC7A).
30
+ *
31
+ * Counts, byte volumes, and CRY_* codes only — a static reader-only aggregate
32
+ * seam with the same shape as the VC6A/VC6B/VC6C handlers.
33
+ */
34
+ export function handleVectorCortexCrystals(req, res, _ctx) {
35
+ const url = req.url ?? "";
36
+ const path = url.split("?")[0] ?? url;
37
+ if (path !== "/api/vector-cortex/cache-crystals")
38
+ return false;
39
+ if (req.method !== "GET") {
40
+ sendJson(res, 405, { error: "method_not_allowed" });
41
+ return true;
42
+ }
43
+ const enabled = VC7A_ENABLED();
44
+ // Flag-off routes to mode C: with VC7A off nothing is served from the crystal
45
+ // cache, which is exactly the spec's "cache bypass" outcome. Reporting A (hit)
46
+ // or B (fresh render forced by a miss) would imply a cache path that is not
47
+ // wired at all. Mirrors how VC6C's OFF view reports the mode it actually takes.
48
+ const mode = enabled ? "A" : "C";
49
+ const body = {
50
+ enabled,
51
+ mode,
52
+ crystalCount: 0,
53
+ totalBytes: 0,
54
+ hits: 0,
55
+ misses: 0,
56
+ hitBytes: 0,
57
+ writes: 0,
58
+ duplicateWrites: 0,
59
+ collisions: 0,
60
+ lastFailure: null,
61
+ updatedAt: new Date().toISOString(),
62
+ };
63
+ sendJson(res, 200, body);
64
+ return true;
65
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * config/vector-cortex-breakers.ts — breaker state machine constants.
3
+ *
4
+ * Extracted from vector-cortex.ts to keep that file under the 300-line soft
5
+ * limit (soft-as-hard gate). These constants are normative in
6
+ * TRIAD_RESILIENCE.md §breaker and consumed by VC0C's breaker seam.
7
+ *
8
+ * Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
9
+ */
10
+ /** Rolling eligibility window (milliseconds). */
11
+ export const BREAKER_WINDOW_MS = 60_000;
12
+ /** Minimum attempts before a breaker may trip or promote. */
13
+ export const BREAKER_MIN_ATTEMPTS = 20;
14
+ /** Performance trip: ≥ this many failures in window, or ≥ this fraction. */
15
+ export const BREAKER_PERF_FAILURES = 5;
16
+ export const BREAKER_PERF_FAILURE_RATE = 0.1;
17
+ /** Correctness trip trips on the first correctness failure. */
18
+ export const BREAKER_CORRECTNESS_FAILURES = 1;
19
+ /** Cooldown before an open breaker may probe (milliseconds). */
20
+ export const BREAKER_COOLDOWN_MS = 30_000;
21
+ /** Consecutive successful probes required to advance a state. */
22
+ export const BREAKER_PROBE_COUNT = 3;
23
+ /** Exponential retry base: 30s * 2^attempt, capped, ±10% jitter. */
24
+ export const BREAKER_RETRY_BASE_MS = 30_000;
25
+ export const BREAKER_RETRY_CAP_MS = 15 * 60_000;
26
+ export const BREAKER_RETRY_JITTER = 0.1;
27
+ /** Promotion hysteresis: failure rate must be < this and p95 within budget. */
28
+ export const BREAKER_HYSTERESIS_FAILURE_RATE = 0.02;
29
+ export const BREAKER_HYSTERESIS_BUDGET_P95_MS = 50;
30
+ /** Minimum healthy residence before a further promotion (milliseconds). */
31
+ export const BREAKER_MIN_HEALTHY_RESIDENCE_MS = 5 * 60_000;
@@ -237,29 +237,20 @@ export const VC6B_ENABLED = () => sprintFlag("MEGACOMPACT_VC6B");
237
237
  * mirroring VC4A/VC4B/VC4C/VC5A/VC5B/VC5C/VC6A/VC6B.
238
238
  */
239
239
  export const VC6C_ENABLED = () => sprintFlag("MEGACOMPACT_VC6C");
240
- // ---------------------------------------------------------------------------
241
- // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
242
- // Rolled numbers for one 60s window; VC0C consumes these at its breaker seam.
243
- // ---------------------------------------------------------------------------
244
- /** Rolling eligibility window (milliseconds). */
245
- export const BREAKER_WINDOW_MS = 60_000;
246
- /** Minimum attempts before a breaker may trip or promote. */
247
- export const BREAKER_MIN_ATTEMPTS = 20;
248
- /** Performance trip: this many failures in window, or this fraction. */
249
- export const BREAKER_PERF_FAILURES = 5;
250
- export const BREAKER_PERF_FAILURE_RATE = 0.1;
251
- /** Correctness trip trips on the first correctness failure. */
252
- export const BREAKER_CORRECTNESS_FAILURES = 1;
253
- /** Cooldown before an open breaker may probe (milliseconds). */
254
- export const BREAKER_COOLDOWN_MS = 30_000;
255
- /** Consecutive successful probes required to advance a state. */
256
- export const BREAKER_PROBE_COUNT = 3;
257
- /** Exponential retry base: 30s * 2^attempt, capped, ±10% jitter. */
258
- export const BREAKER_RETRY_BASE_MS = 30_000;
259
- export const BREAKER_RETRY_CAP_MS = 15 * 60_000;
260
- export const BREAKER_RETRY_JITTER = 0.1;
261
- /** Promotion hysteresis: failure rate must be < this and p95 within budget. */
262
- export const BREAKER_HYSTERESIS_FAILURE_RATE = 0.02;
263
- export const BREAKER_HYSTERESIS_BUDGET_P95_MS = 50;
264
- /** Minimum healthy residence before a further promotion (milliseconds). */
265
- export const BREAKER_MIN_HEALTHY_RESIDENCE_MS = 5 * 60_000;
240
+ /**
241
+ * VC7A frozen range crystals. Default ON. `MEGACOMPACT_VC7A=0` disables and is
242
+ * byte-identical to the predecessor (VC6C): the `encodeCrystalKey` /
243
+ * `CrystalStore` arithmetic STILL RUNS (it is PURE — a canonical length-prefixed
244
+ * encoding plus SHA-256, with no clock, storage, or network), so a crystal is
245
+ * keyed identically and a same-key/different-bytes write is still refused with
246
+ * the flag off. The flag gates ONLY the `vector_cortex_crystal_written` /
247
+ * `vector_cortex_crystal_collision` events and the cache-crystals dashboard seam,
248
+ * which reports `enabled:false` + mode C when off. Flag OFF never gates the
249
+ * crystal/store arithmetic, so flag-off outbound/predecessor golden bytes match
250
+ * exactly. This flag MUST also be a dashboard SETTINGS toggle (visible in config
251
+ * UI, never in EXCLUDED_SETTINGS), mirroring VC4A..VC6C.
252
+ */
253
+ export const VC7A_ENABLED = () => sprintFlag("MEGACOMPACT_VC7A");
254
+ // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker) extracted to
255
+ // vector-cortex-breakers.ts to keep this file under the 300-line soft limit.
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";
@@ -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, 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, 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,71 @@
1
+ /**
2
+ * cache/_crystal-fixture.ts — conformance fixture I/O for VC7A crystal rows.
3
+ *
4
+ * Sibling of `../heal/_restore-fixture.ts`, same job for a different corpus:
5
+ * turn canonical JSON back into the REAL production types the cache modules
6
+ * consume. Fixtures cannot express bigints, so `dependencyHighWater` and the
7
+ * span seq bounds are stored as numbers and converted here — if that conversion
8
+ * were lossy the encoded keys would diverge and the acceptance rows would fail
9
+ * loudly, which is exactly the guarantee an identity sprint needs.
10
+ *
11
+ * No mocks, no stubs, no parallel "test shape": the decoded objects ARE
12
+ * `CrystalKeyV1` / `DagSpan` and are fed verbatim into `encodeCrystalKey` and
13
+ * `CrystalStore`.
14
+ */
15
+ import { readFileSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import assert from "node:assert/strict";
18
+ import { computeCoveredDigest } from "./crystal.js";
19
+ import { V2, readManifest } from "../heal/_acceptance-fixture.js";
20
+ /** Read one registered cache-crystal fixture (asserting it IS registered). */
21
+ export function crystalFixture(id) {
22
+ const m = readManifest();
23
+ const row = m.fixtures.find((f) => f.id === id && f.path.startsWith("cache-crystals/"));
24
+ assert.ok(row, `fixture ${id} registered under cache-crystals/ in manifest`);
25
+ return JSON.parse(readFileSync(join(V2, row.path), "utf8"));
26
+ }
27
+ /** JSON number seq bounds -> the bigint bounds `DagSpan` declares. */
28
+ export function decodeSpan(s) {
29
+ return {
30
+ sessionId: s.sessionId,
31
+ startSeq: BigInt(s.startSeq),
32
+ endSeq: BigInt(s.endSeq),
33
+ startByte: s.startByte,
34
+ endByte: s.endByte,
35
+ digest: s.digest,
36
+ };
37
+ }
38
+ /**
39
+ * Reconstitute a real `CrystalKeyV1`. `coveredDigest` is DERIVED from the ranges
40
+ * rather than carried in the corpus: the covered digest is a function of the
41
+ * ranges, so storing it would let a fixture assert a self-inconsistent identity
42
+ * that the encoder would silently re-derive anyway.
43
+ */
44
+ export function decodeKey(k) {
45
+ const sourceRanges = k.sourceRanges.map(decodeSpan);
46
+ return {
47
+ profileId: k.profileId,
48
+ profileVersion: k.profileVersion,
49
+ requestDigest: k.requestDigest,
50
+ rendererVersion: k.rendererVersion,
51
+ dependencyHighWater: BigInt(k.dependencyHighWater),
52
+ sourceRanges,
53
+ coveredDigest: computeCoveredDigest(sourceRanges),
54
+ };
55
+ }
56
+ /** Flag-pinned wrapper: VC7A gated by MEGACOMPACT_VC7A (defaults ON). */
57
+ export function withVc7aFlag(value, fn) {
58
+ return () => {
59
+ const saved = process.env.MEGACOMPACT_VC7A;
60
+ process.env.MEGACOMPACT_VC7A = value;
61
+ try {
62
+ fn();
63
+ }
64
+ finally {
65
+ if (saved === undefined)
66
+ delete process.env.MEGACOMPACT_VC7A;
67
+ else
68
+ process.env.MEGACOMPACT_VC7A = saved;
69
+ }
70
+ };
71
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * vector-cortex/cache/crystal-emit.ts — VC7A event reporter seam.
3
+ *
4
+ * Mirrors `../heal/restore-emit.ts`: a thin `safe()` wrapper around an optional
5
+ * injected `emit` (unit tests pass `undefined` and stay pure), and the two event
6
+ * names the sprint spec requires verbatim:
7
+ * - `vector_cortex_crystal_written` — a crystal was frozen and published.
8
+ * - `vector_cortex_crystal_collision` — a same-key, different-bytes write was
9
+ * refused.
10
+ *
11
+ * FLAG SEMANTICS. `encodeCrystalKey` and every `CrystalStore` operation are PURE
12
+ * and run REGARDLESS of `MEGACOMPACT_VC7A`. The flag gates ONLY this reporting +
13
+ * dashboard seam: with the flag off we still key crystals identically, still
14
+ * write once, and still refuse collisions — we just do not announce it under the
15
+ * VC7A event namespace, and the dashboard reports `enabled:false` + mode C. That
16
+ * is what makes flag-off byte-identical to VC6C: the arithmetic is never skipped,
17
+ * only the emission.
18
+ *
19
+ * PAYLOAD DISCIPLINE. These events carry the KEY DIGEST, byte COUNTS, and the
20
+ * failure code — never the frozen bytes, never covered source text, never the
21
+ * span digests of user content. A crystal is a rendered prompt: an unguarded
22
+ * `payload` field here would dump the entire framed conversation into a log file
23
+ * (SECURITY_PRIVACY — the exact ledger is not diagnostic data).
24
+ *
25
+ * No console, no storage, no network (PREVENT-PI-004 / PREVENT-011). Every line
26
+ * is a structured JSON event with `ts` + `event`.
27
+ */
28
+ import { VC7A_ENABLED } from "../../config/vector-cortex.js";
29
+ /** Run `fn` only when an emit exists; a reporting failure is never fatal. */
30
+ function safe(emit, fn) {
31
+ if (emit === undefined)
32
+ return;
33
+ try {
34
+ fn(emit);
35
+ }
36
+ catch {
37
+ // Non-fatal: a reporting failure must never break the agent loop.
38
+ }
39
+ }
40
+ /** The event names VC7A emits, exported for the dashboard seam and tests. */
41
+ export const CRYSTAL_EVENT_NAMES = [
42
+ "vector_cortex_crystal_written",
43
+ "vector_cortex_crystal_collision",
44
+ ];
45
+ /**
46
+ * Report a published crystal. Key digest + byte count + mode only — enough to
47
+ * see whether the cache is filling and being hit, without disclosing what was
48
+ * frozen.
49
+ */
50
+ export function reportCrystalWritten(emit, payload) {
51
+ if (!VC7A_ENABLED())
52
+ return;
53
+ safe(emit, (e) => e("vector_cortex_crystal_written", {
54
+ ts: undefined,
55
+ event: "vector_cortex_crystal_written",
56
+ keyDigest: payload.keyDigest,
57
+ byteCount: payload.byteCount,
58
+ mode: payload.mode,
59
+ }));
60
+ }
61
+ /**
62
+ * Report a refused write. A collision means two renders of the SAME identity
63
+ * disagreed — a determinism bug — so the code is surfaced rather than swallowed.
64
+ * Suppressed under flag-off.
65
+ */
66
+ export function reportCrystalCollision(emit, payload) {
67
+ if (!VC7A_ENABLED())
68
+ return;
69
+ safe(emit, (e) => e("vector_cortex_crystal_collision", {
70
+ ts: undefined,
71
+ event: "vector_cortex_crystal_collision",
72
+ keyDigest: payload.keyDigest,
73
+ code: payload.code,
74
+ mode: payload.mode,
75
+ }));
76
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * vector-cortex/cache/crystal.ts — canonical crystal key encoding (VC7A).
3
+ *
4
+ * Turns a `CrystalKeyV1` into a single stable digest. Everything about the
5
+ * encoding exists to make two properties simultaneously true:
6
+ *
7
+ * 1. IDENTICAL INPUTS ⇒ IDENTICAL KEY, regardless of how the caller ordered
8
+ * its ranges or which host produced them. Hence the explicit sort and the
9
+ * length-prefixed field framing below.
10
+ * 2. ANY IDENTITY CHANGE ⇒ DIFFERENT KEY, with no accidental aliasing. Hence
11
+ * length prefixes rather than delimiters: a delimiter-joined encoding lets
12
+ * an attacker (or an unlucky session name) push a separator into a field
13
+ * and forge a collision — `("a|b", "c")` and `("a", "b|c")` hash the same.
14
+ * Prefixing every variable-length field with its byte length makes the
15
+ * encoding injective, so a collision requires an actual SHA-256 collision.
16
+ *
17
+ * WHAT IS DELIBERATELY ABSENT. The global ledger frontier. It is not a
18
+ * parameter, it is not readable from here, and there is no code path that could
19
+ * fold it in. An unrelated append advances the frontier constantly; including it
20
+ * would invalidate every crystal on every turn and the cache would never hit.
21
+ * The key covers what the render DEPENDED ON, not what the world has since done.
22
+ *
23
+ * OVERLAP IS REJECTED, NOT MERGED. Two overlapping ranges in the same session
24
+ * make "the covered bytes" ambiguous: the overlap region would be hashed twice,
25
+ * and if the two spans pinned different digests the key would silently encode a
26
+ * contradiction. `CRY_RANGE_OVERLAP` fails the key closed instead. Ranges in
27
+ * DIFFERENT sessions never conflict — they cover disjoint byte streams by
28
+ * construction — so cross-session keys are legal and common.
29
+ *
30
+ * PURE. No clock, no storage, no console, no network (PREVENT-PI-004 /
31
+ * PREVENT-011). Runs identically with `MEGACOMPACT_VC7A` on or off — the flag
32
+ * gates only the reporter/dashboard seam in `crystal-emit.ts`.
33
+ */
34
+ import { createHash } from "node:crypto";
35
+ import { CRYSTAL_LIMIT_BYTES, CRYSTAL_LIMIT_RANGES, } from "./types.js";
36
+ /** Encoding version, folded into the digest so a future change cannot alias. */
37
+ const KEY_ENCODING_VERSION = "crystal-key-v1";
38
+ /**
39
+ * Length-prefixed field append. `<byteLength>:<bytes>` makes the concatenation
40
+ * injective, so no combination of field contents can impersonate another.
41
+ */
42
+ function field(parts, value) {
43
+ parts.push(`${Buffer.byteLength(value, "utf8")}:${value}`);
44
+ }
45
+ /**
46
+ * Total order over covered ranges: session, then start seq, then start byte.
47
+ *
48
+ * Ordering by SOURCE START (not by insertion, not by digest) is what makes the
49
+ * key independent of how the planner happened to enumerate its spans. `sessionId`
50
+ * leads because seq numbers are only comparable within a session.
51
+ */
52
+ export function compareSpans(a, b) {
53
+ if (a.sessionId !== b.sessionId)
54
+ return a.sessionId < b.sessionId ? -1 : 1;
55
+ if (a.startSeq !== b.startSeq)
56
+ return a.startSeq < b.startSeq ? -1 : 1;
57
+ if (a.startByte !== b.startByte)
58
+ return a.startByte - b.startByte;
59
+ if (a.endSeq !== b.endSeq)
60
+ return a.endSeq < b.endSeq ? -1 : 1;
61
+ return a.endByte - b.endByte;
62
+ }
63
+ /** Sort covered ranges into canonical order (never mutates the input array). */
64
+ export function sortSpans(spans) {
65
+ return [...spans].sort(compareSpans);
66
+ }
67
+ /** A range is malformed if either bound runs backwards or a byte bound is negative. */
68
+ function isMalformed(s) {
69
+ return (s.endSeq < s.startSeq ||
70
+ s.startByte < 0 ||
71
+ s.endByte < s.startByte ||
72
+ !Number.isSafeInteger(s.startByte) ||
73
+ !Number.isSafeInteger(s.endByte));
74
+ }
75
+ /**
76
+ * Byte-range overlap between two spans of the SAME session. Byte bounds are
77
+ * half-open (`[startByte, endByte)`), so touching ranges (`a.end === b.start`)
78
+ * are adjacent, not overlapping, and are legal.
79
+ */
80
+ function overlaps(a, b) {
81
+ return a.sessionId === b.sessionId && a.startByte < b.endByte && b.startByte < a.endByte;
82
+ }
83
+ /**
84
+ * Validate covered ranges: non-empty, bounded, well-formed, and disjoint within
85
+ * each session. Returns deduplicated codes in a deterministic order.
86
+ */
87
+ export function validateRanges(spans) {
88
+ const codes = new Set();
89
+ if (spans.length === 0)
90
+ codes.add("CRY_RANGE_EMPTY");
91
+ if (spans.length > CRYSTAL_LIMIT_RANGES)
92
+ codes.add("CRY_KEY_LIMIT");
93
+ let totalBytes = 0;
94
+ for (const s of spans) {
95
+ if (isMalformed(s))
96
+ codes.add("CRY_RANGE_INVALID");
97
+ else
98
+ totalBytes += s.endByte - s.startByte;
99
+ }
100
+ if (totalBytes > CRYSTAL_LIMIT_BYTES)
101
+ codes.add("CRY_KEY_LIMIT");
102
+ // Sorted order makes overlap a neighbour check within each session run.
103
+ const sorted = sortSpans(spans);
104
+ for (let i = 1; i < sorted.length; i += 1) {
105
+ const prev = sorted[i - 1];
106
+ const cur = sorted[i];
107
+ if (prev !== undefined && cur !== undefined && overlaps(prev, cur)) {
108
+ codes.add("CRY_RANGE_OVERLAP");
109
+ break;
110
+ }
111
+ }
112
+ const order = [
113
+ "CRY_RANGE_EMPTY",
114
+ "CRY_RANGE_INVALID",
115
+ "CRY_RANGE_OVERLAP",
116
+ "CRY_KEY_LIMIT",
117
+ ];
118
+ return order.filter((c) => codes.has(c));
119
+ }
120
+ /**
121
+ * The covered-bytes digest: SHA-256 over the SORTED ranges' identities and their
122
+ * pinned span digests, `sha256:` prefixed (matching the `DagSpan.digest`
123
+ * convention the ranges themselves carry).
124
+ *
125
+ * The span digests are what make this sensitive to a single covered BYTE: the
126
+ * ranges alone would be identical if a byte inside an unchanged range mutated,
127
+ * but the span's pinned digest would not be (CRY-COVERED-002).
128
+ */
129
+ export function computeCoveredDigest(spans) {
130
+ const h = createHash("sha256");
131
+ for (const s of sortSpans(spans)) {
132
+ const parts = [];
133
+ field(parts, s.sessionId);
134
+ field(parts, s.startSeq.toString());
135
+ field(parts, s.endSeq.toString());
136
+ field(parts, String(s.startByte));
137
+ field(parts, String(s.endByte));
138
+ field(parts, s.digest);
139
+ h.update(parts.join(""), "utf8");
140
+ }
141
+ return `sha256:${h.digest("hex")}`;
142
+ }
143
+ /**
144
+ * Canonical key bytes. Field order is fixed and every field is length-prefixed;
145
+ * the ranges are emitted in canonical sort order with an explicit count so a
146
+ * key with N ranges can never encode the same bytes as one with M.
147
+ */
148
+ export function encodeCrystalKeyBytes(key) {
149
+ const parts = [];
150
+ field(parts, KEY_ENCODING_VERSION);
151
+ field(parts, key.profileId);
152
+ field(parts, key.profileVersion);
153
+ field(parts, key.requestDigest);
154
+ field(parts, key.rendererVersion);
155
+ field(parts, key.dependencyHighWater.toString());
156
+ field(parts, key.coveredDigest);
157
+ const sorted = sortSpans(key.sourceRanges);
158
+ field(parts, String(sorted.length));
159
+ for (const s of sorted) {
160
+ field(parts, s.sessionId);
161
+ field(parts, s.startSeq.toString());
162
+ field(parts, s.endSeq.toString());
163
+ field(parts, String(s.startByte));
164
+ field(parts, String(s.endByte));
165
+ field(parts, s.digest);
166
+ }
167
+ return parts.join("");
168
+ }
169
+ /**
170
+ * Build the canonical key digest for a crystal identity.
171
+ *
172
+ * The returned `key` carries the ranges in canonical sorted order and the
173
+ * RE-DERIVED `coveredDigest`, so a caller that supplied a stale or wrong covered
174
+ * digest cannot mint a key that disagrees with its own ranges. The digest itself
175
+ * is bare lowercase hex (it addresses an identity, not source bytes).
176
+ */
177
+ export function encodeCrystalKey(key) {
178
+ const codes = validateRanges(key.sourceRanges);
179
+ if (codes.length > 0)
180
+ return { ok: false, codes };
181
+ const normalized = {
182
+ ...key,
183
+ sourceRanges: sortSpans(key.sourceRanges),
184
+ coveredDigest: computeCoveredDigest(key.sourceRanges),
185
+ };
186
+ const keyDigest = createHash("sha256")
187
+ .update(encodeCrystalKeyBytes(normalized), "utf8")
188
+ .digest("hex");
189
+ return { ok: true, keyDigest, key: normalized };
190
+ }
191
+ /**
192
+ * Whether two identities are the same crystal. Used by invalidation fixtures to
193
+ * state the sprint invariant directly: the key changes IFF an identity field
194
+ * changes — an unrelated frontier append is not an identity field, so it cannot
195
+ * appear here at all.
196
+ */
197
+ export function sameCrystalKey(a, b) {
198
+ const ea = encodeCrystalKey(a);
199
+ const eb = encodeCrystalKey(b);
200
+ return ea.ok && eb.ok && ea.keyDigest === eb.keyDigest;
201
+ }