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,217 @@
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
+
35
+ import { createHash } from "node:crypto";
36
+
37
+ import {
38
+ CRYSTAL_LIMIT_BYTES,
39
+ CRYSTAL_LIMIT_RANGES,
40
+ type CrystalFailureCode,
41
+ type CrystalKeyResult,
42
+ type CrystalKeyV1,
43
+ type DagSpan,
44
+ } from "./types.js";
45
+
46
+ /** Encoding version, folded into the digest so a future change cannot alias. */
47
+ const KEY_ENCODING_VERSION = "crystal-key-v1";
48
+
49
+ /**
50
+ * Length-prefixed field append. `<byteLength>:<bytes>` makes the concatenation
51
+ * injective, so no combination of field contents can impersonate another.
52
+ */
53
+ function field(parts: string[], value: string): void {
54
+ parts.push(`${Buffer.byteLength(value, "utf8")}:${value}`);
55
+ }
56
+
57
+ /**
58
+ * Total order over covered ranges: session, then start seq, then start byte.
59
+ *
60
+ * Ordering by SOURCE START (not by insertion, not by digest) is what makes the
61
+ * key independent of how the planner happened to enumerate its spans. `sessionId`
62
+ * leads because seq numbers are only comparable within a session.
63
+ */
64
+ export function compareSpans(a: DagSpan, b: DagSpan): number {
65
+ if (a.sessionId !== b.sessionId) return a.sessionId < b.sessionId ? -1 : 1;
66
+ if (a.startSeq !== b.startSeq) return a.startSeq < b.startSeq ? -1 : 1;
67
+ if (a.startByte !== b.startByte) return a.startByte - b.startByte;
68
+ if (a.endSeq !== b.endSeq) return a.endSeq < b.endSeq ? -1 : 1;
69
+ return a.endByte - b.endByte;
70
+ }
71
+
72
+ /** Sort covered ranges into canonical order (never mutates the input array). */
73
+ export function sortSpans(spans: readonly DagSpan[]): readonly DagSpan[] {
74
+ return [...spans].sort(compareSpans);
75
+ }
76
+
77
+ /** A range is malformed if either bound runs backwards or a byte bound is negative. */
78
+ function isMalformed(s: DagSpan): boolean {
79
+ return (
80
+ s.endSeq < s.startSeq ||
81
+ s.startByte < 0 ||
82
+ s.endByte < s.startByte ||
83
+ !Number.isSafeInteger(s.startByte) ||
84
+ !Number.isSafeInteger(s.endByte)
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Byte-range overlap between two spans of the SAME session. Byte bounds are
90
+ * half-open (`[startByte, endByte)`), so touching ranges (`a.end === b.start`)
91
+ * are adjacent, not overlapping, and are legal.
92
+ */
93
+ function overlaps(a: DagSpan, b: DagSpan): boolean {
94
+ return a.sessionId === b.sessionId && a.startByte < b.endByte && b.startByte < a.endByte;
95
+ }
96
+
97
+ /**
98
+ * Validate covered ranges: non-empty, bounded, well-formed, and disjoint within
99
+ * each session. Returns deduplicated codes in a deterministic order.
100
+ */
101
+ export function validateRanges(spans: readonly DagSpan[]): readonly CrystalFailureCode[] {
102
+ const codes = new Set<CrystalFailureCode>();
103
+ if (spans.length === 0) codes.add("CRY_RANGE_EMPTY");
104
+ if (spans.length > CRYSTAL_LIMIT_RANGES) codes.add("CRY_KEY_LIMIT");
105
+
106
+ let totalBytes = 0;
107
+ for (const s of spans) {
108
+ if (isMalformed(s)) codes.add("CRY_RANGE_INVALID");
109
+ else totalBytes += s.endByte - s.startByte;
110
+ }
111
+ if (totalBytes > CRYSTAL_LIMIT_BYTES) codes.add("CRY_KEY_LIMIT");
112
+
113
+ // Sorted order makes overlap a neighbour check within each session run.
114
+ const sorted = sortSpans(spans);
115
+ for (let i = 1; i < sorted.length; i += 1) {
116
+ const prev = sorted[i - 1];
117
+ const cur = sorted[i];
118
+ if (prev !== undefined && cur !== undefined && overlaps(prev, cur)) {
119
+ codes.add("CRY_RANGE_OVERLAP");
120
+ break;
121
+ }
122
+ }
123
+
124
+ const order: CrystalFailureCode[] = [
125
+ "CRY_RANGE_EMPTY",
126
+ "CRY_RANGE_INVALID",
127
+ "CRY_RANGE_OVERLAP",
128
+ "CRY_KEY_LIMIT",
129
+ ];
130
+ return order.filter((c) => codes.has(c));
131
+ }
132
+
133
+ /**
134
+ * The covered-bytes digest: SHA-256 over the SORTED ranges' identities and their
135
+ * pinned span digests, `sha256:` prefixed (matching the `DagSpan.digest`
136
+ * convention the ranges themselves carry).
137
+ *
138
+ * The span digests are what make this sensitive to a single covered BYTE: the
139
+ * ranges alone would be identical if a byte inside an unchanged range mutated,
140
+ * but the span's pinned digest would not be (CRY-COVERED-002).
141
+ */
142
+ export function computeCoveredDigest(spans: readonly DagSpan[]): string {
143
+ const h = createHash("sha256");
144
+ for (const s of sortSpans(spans)) {
145
+ const parts: string[] = [];
146
+ field(parts, s.sessionId);
147
+ field(parts, s.startSeq.toString());
148
+ field(parts, s.endSeq.toString());
149
+ field(parts, String(s.startByte));
150
+ field(parts, String(s.endByte));
151
+ field(parts, s.digest);
152
+ h.update(parts.join(""), "utf8");
153
+ }
154
+ return `sha256:${h.digest("hex")}`;
155
+ }
156
+
157
+ /**
158
+ * Canonical key bytes. Field order is fixed and every field is length-prefixed;
159
+ * the ranges are emitted in canonical sort order with an explicit count so a
160
+ * key with N ranges can never encode the same bytes as one with M.
161
+ */
162
+ export function encodeCrystalKeyBytes(key: CrystalKeyV1): string {
163
+ const parts: string[] = [];
164
+ field(parts, KEY_ENCODING_VERSION);
165
+ field(parts, key.profileId);
166
+ field(parts, key.profileVersion);
167
+ field(parts, key.requestDigest);
168
+ field(parts, key.rendererVersion);
169
+ field(parts, key.dependencyHighWater.toString());
170
+ field(parts, key.coveredDigest);
171
+ const sorted = sortSpans(key.sourceRanges);
172
+ field(parts, String(sorted.length));
173
+ for (const s of sorted) {
174
+ field(parts, s.sessionId);
175
+ field(parts, s.startSeq.toString());
176
+ field(parts, s.endSeq.toString());
177
+ field(parts, String(s.startByte));
178
+ field(parts, String(s.endByte));
179
+ field(parts, s.digest);
180
+ }
181
+ return parts.join("");
182
+ }
183
+
184
+ /**
185
+ * Build the canonical key digest for a crystal identity.
186
+ *
187
+ * The returned `key` carries the ranges in canonical sorted order and the
188
+ * RE-DERIVED `coveredDigest`, so a caller that supplied a stale or wrong covered
189
+ * digest cannot mint a key that disagrees with its own ranges. The digest itself
190
+ * is bare lowercase hex (it addresses an identity, not source bytes).
191
+ */
192
+ export function encodeCrystalKey(key: CrystalKeyV1): CrystalKeyResult {
193
+ const codes = validateRanges(key.sourceRanges);
194
+ if (codes.length > 0) return { ok: false, codes };
195
+
196
+ const normalized: CrystalKeyV1 = {
197
+ ...key,
198
+ sourceRanges: sortSpans(key.sourceRanges),
199
+ coveredDigest: computeCoveredDigest(key.sourceRanges),
200
+ };
201
+ const keyDigest = createHash("sha256")
202
+ .update(encodeCrystalKeyBytes(normalized), "utf8")
203
+ .digest("hex");
204
+ return { ok: true, keyDigest, key: normalized };
205
+ }
206
+
207
+ /**
208
+ * Whether two identities are the same crystal. Used by invalidation fixtures to
209
+ * state the sprint invariant directly: the key changes IFF an identity field
210
+ * changes — an unrelated frontier append is not an identity field, so it cannot
211
+ * appear here at all.
212
+ */
213
+ export function sameCrystalKey(a: CrystalKeyV1, b: CrystalKeyV1): boolean {
214
+ const ea = encodeCrystalKey(a);
215
+ const eb = encodeCrystalKey(b);
216
+ return ea.ok && eb.ok && ea.keyDigest === eb.keyDigest;
217
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * vector-cortex/cache/store.ts — content-addressed, write-once crystal store
3
+ * (VC7A).
4
+ *
5
+ * WRITE ONCE, NEVER OVERWRITE. A key that already holds bytes is final. Writing
6
+ * the SAME bytes again is accepted and idempotent (two concurrent renders raced;
7
+ * both are right). Writing DIFFERENT bytes under the same key returns
8
+ * `CRY_KEY_COLLISION` and the stored crystal is left exactly as it was.
9
+ *
10
+ * Why refuse rather than take the newer bytes? Because the key already names
11
+ * everything the render depended on — covered ranges, their digest, the validated
12
+ * dependency high-water, the renderer, the profile. If two renders of that same
13
+ * identity disagree, the renderer is NOT deterministic, and that is a bug to
14
+ * surface loudly, not to paper over by picking a winner. Last-write-wins would
15
+ * make the failure invisible and let a corrupted render silently displace a good
16
+ * one. So the store is a one-way ratchet per key.
17
+ *
18
+ * CONTENT ADDRESSED. Every crystal carries the SHA-256 of exactly its own bytes
19
+ * (bare lowercase hex, matching `ExactShardV1.digest`), recomputed HERE from the
20
+ * bytes rather than trusted from the caller — a caller-supplied digest would let
21
+ * a mismatched pair be stored and later "verify" against itself.
22
+ *
23
+ * ATOMIC COMMIT (the crash invariant). Writes go through a staging slot and are
24
+ * only published by `commit`. A write interrupted before commit leaves the
25
+ * staging entry orphaned and the visible map untouched, so `read` can never
26
+ * observe a partial crystal. On restart, `recover()` DISCARDS staged entries —
27
+ * it never promotes them — and a fresh write then produces exactly one valid
28
+ * crystal. This models the real filesystem shape (write temp file → fsync →
29
+ * rename) without doing any I/O here: persistence is the runtime's job, and
30
+ * `src/` stays storage-free and pure (PREVENT-PI-004).
31
+ *
32
+ * MODE C. `setAvailable(false)` marks the store unavailable: reads serve nothing
33
+ * and writes refuse with `CRY_STORE_UNAVAILABLE`. Mode C is a real triad state,
34
+ * not an error path — the caller must fall back to a fresh render and disclose
35
+ * that nothing came from cache.
36
+ *
37
+ * No console, no network (PREVENT-PI-004 / PREVENT-011).
38
+ */
39
+
40
+ import { createHash } from "node:crypto";
41
+
42
+ import type {
43
+ CrystalMode,
44
+ CrystalStoreStats,
45
+ CrystalV1,
46
+ CrystalWriteResult,
47
+ } from "./types.js";
48
+
49
+ /** SHA-256 over bytes, BARE lowercase hex (the content address). */
50
+ export function contentAddress(bytes: Uint8Array): string {
51
+ return createHash("sha256").update(bytes).digest("hex");
52
+ }
53
+
54
+ /** Byte equality without allocating — a length check first, then a scan. */
55
+ function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
56
+ if (a.length !== b.length) return false;
57
+ for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return false;
58
+ return true;
59
+ }
60
+
61
+ /**
62
+ * An in-memory, content-addressed, write-once crystal store.
63
+ *
64
+ * Deliberately holds no filesystem handle: the process-local map IS the store as
65
+ * far as `src/` is concerned, and durability is layered on by the runtime. That
66
+ * keeps the write-once/collision arithmetic testable end-to-end with real
67
+ * modules and no I/O mocking.
68
+ */
69
+ export class CrystalStore {
70
+ /** Published crystals, keyed by canonical key digest. */
71
+ private readonly committed = new Map<string, CrystalV1>();
72
+ /** Staged-but-uncommitted writes — invisible to `read` until committed. */
73
+ private readonly staged = new Map<string, CrystalV1>();
74
+ private available = true;
75
+ private hits = 0;
76
+ private misses = 0;
77
+ private hitBytes = 0;
78
+ private writes = 0;
79
+ private duplicateWrites = 0;
80
+ private collisions = 0;
81
+
82
+ /** Freeze a crystal object for a key/bytes pair (digest computed here). */
83
+ static freeze(keyDigest: string, bytes: Uint8Array, key: CrystalV1["key"]): CrystalV1 {
84
+ const copy = new Uint8Array(bytes);
85
+ return {
86
+ schema: "crystal-v1",
87
+ keyDigest,
88
+ bytes: copy,
89
+ contentDigest: contentAddress(copy),
90
+ byteCount: copy.length,
91
+ key,
92
+ };
93
+ }
94
+
95
+ /** Toggle store availability (mode C when false). */
96
+ setAvailable(value: boolean): void {
97
+ this.available = value;
98
+ }
99
+
100
+ isAvailable(): boolean {
101
+ return this.available;
102
+ }
103
+
104
+ /**
105
+ * Stage a write without publishing it. Returns the staging handle (the key
106
+ * digest) so a test — or a crash — can leave it uncommitted.
107
+ */
108
+ stage(crystal: CrystalV1): CrystalWriteResult {
109
+ if (!this.available) {
110
+ return { ok: false, code: "CRY_STORE_UNAVAILABLE", contentDigest: crystal.contentDigest };
111
+ }
112
+ this.staged.set(crystal.keyDigest, crystal);
113
+ return { ok: true, written: false, contentDigest: crystal.contentDigest };
114
+ }
115
+
116
+ /**
117
+ * Publish a staged write. Enforces write-once at the commit point: an existing
118
+ * key with identical bytes is idempotent, with different bytes is a collision
119
+ * and is NEVER overwritten.
120
+ */
121
+ commit(keyDigest: string): CrystalWriteResult {
122
+ const pending = this.staged.get(keyDigest);
123
+ if (pending === undefined) {
124
+ return { ok: false, code: "CRY_STORE_UNAVAILABLE", contentDigest: "" };
125
+ }
126
+ this.staged.delete(keyDigest);
127
+ if (!this.available) {
128
+ return { ok: false, code: "CRY_STORE_UNAVAILABLE", contentDigest: pending.contentDigest };
129
+ }
130
+ const existing = this.committed.get(keyDigest);
131
+ if (existing !== undefined) {
132
+ if (bytesEqual(existing.bytes, pending.bytes)) {
133
+ this.duplicateWrites += 1;
134
+ return { ok: true, written: false, contentDigest: existing.contentDigest };
135
+ }
136
+ this.collisions += 1;
137
+ return { ok: false, code: "CRY_KEY_COLLISION", contentDigest: existing.contentDigest };
138
+ }
139
+ this.committed.set(keyDigest, pending);
140
+ this.writes += 1;
141
+ return { ok: true, written: true, contentDigest: pending.contentDigest };
142
+ }
143
+
144
+ /** Stage + commit in one step (the normal path). */
145
+ write(crystal: CrystalV1): CrystalWriteResult {
146
+ const staged = this.stage(crystal);
147
+ if (!staged.ok) return staged;
148
+ return this.commit(crystal.keyDigest);
149
+ }
150
+
151
+ /**
152
+ * Restart recovery: DISCARD every staged entry. A write interrupted before
153
+ * commit is never promoted — a fresh write afterwards produces exactly one
154
+ * valid crystal. Returns how many partial writes were dropped.
155
+ */
156
+ recover(): number {
157
+ const dropped = this.staged.size;
158
+ this.staged.clear();
159
+ return dropped;
160
+ }
161
+
162
+ /** Read a committed crystal. Mode C (unavailable) serves nothing. */
163
+ read(keyDigest: string): CrystalV1 | undefined {
164
+ if (!this.available) {
165
+ this.misses += 1;
166
+ return undefined;
167
+ }
168
+ const found = this.committed.get(keyDigest);
169
+ if (found === undefined) {
170
+ this.misses += 1;
171
+ return undefined;
172
+ }
173
+ this.hits += 1;
174
+ this.hitBytes += found.byteCount;
175
+ return found;
176
+ }
177
+
178
+ /** Whether a key holds a committed crystal (does not count as a read). */
179
+ has(keyDigest: string): boolean {
180
+ return this.available && this.committed.has(keyDigest);
181
+ }
182
+
183
+ /** Number of writes staged but not yet committed (0 after `recover`). */
184
+ pendingCount(): number {
185
+ return this.staged.size;
186
+ }
187
+
188
+ /**
189
+ * The triad mode the store is currently in: C when unavailable, A once a read
190
+ * has been served from the store, B while every read has forced a fresh render.
191
+ */
192
+ mode(): CrystalMode {
193
+ if (!this.available) return "C";
194
+ return this.hits > 0 ? "A" : "B";
195
+ }
196
+
197
+ /** Reader-only aggregate for the dashboard seam — counts and bytes only. */
198
+ stats(): CrystalStoreStats {
199
+ let totalBytes = 0;
200
+ for (const c of this.committed.values()) totalBytes += c.byteCount;
201
+ return {
202
+ mode: this.mode(),
203
+ crystalCount: this.committed.size,
204
+ totalBytes,
205
+ hits: this.hits,
206
+ misses: this.misses,
207
+ hitBytes: this.hitBytes,
208
+ writes: this.writes,
209
+ duplicateWrites: this.duplicateWrites,
210
+ collisions: this.collisions,
211
+ };
212
+ }
213
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * vector-cortex/cache/types.ts — VC7A frozen range crystal contract.
3
+ *
4
+ * A CRYSTAL is a frozen, immutable rendering of a bounded set of source ranges.
5
+ * VC5B proved a render is deterministic; VC7A makes that determinism REUSABLE by
6
+ * freezing the rendered bytes under a key that names exactly what they depend on.
7
+ *
8
+ * THE IDENTITY RULE (the whole sprint in one sentence). A crystal is keyed by
9
+ * the source ranges it COVERS, the digest of those covered bytes, the VALIDATED
10
+ * dependency high-water it was rendered against, and the renderer/profile that
11
+ * produced it — and by NOTHING ELSE. In particular the GLOBAL LEDGER FRONTIER is
12
+ * deliberately excluded from identity. That exclusion is the point: appending an
13
+ * unrelated turn advances the global frontier on every single request, so a key
14
+ * that included it would invalidate every crystal continuously and the cache
15
+ * would never hit. Conversely, the four fields that ARE in the key are precisely
16
+ * the ones whose change would make the frozen bytes WRONG:
17
+ *
18
+ * - covered ranges / `coveredDigest` — different bytes render differently;
19
+ * - `dependencyHighWater` — a dependency that has since advanced
20
+ * may contradict what was frozen;
21
+ * - `rendererVersion` — a renderer change changes the output;
22
+ * - `profileId` + `profileVersion` — a provider profile changes framing.
23
+ *
24
+ * So: unrelated append leaves the key UNCHANGED (CRY-FRONTIER-001); one covered
25
+ * byte, one dependency tick, one renderer bump, or one profile bump invalidates
26
+ * it 100% (CRY-COVERED-002 / CRY-DEP-003).
27
+ *
28
+ * VALIDATED, NOT OBSERVED, HIGH-WATER. `dependencyHighWater` is the DURABLE
29
+ * contiguous authority high-water the render was VALIDATED against, not whatever
30
+ * the writer happened to see. TRIAD_RESILIENCE freezes the derived high-water
31
+ * during an authority outage precisely so a crystal cannot be minted claiming
32
+ * evidence the authority has not durably accepted.
33
+ *
34
+ * DIGEST CONVENTIONS (three fields, two shapes — do not mix them).
35
+ * - `DagSpan.digest` and `CrystalKeyV1.coveredDigest` are `sha256:<hex>`, WITH
36
+ * the prefix (they name COVERED SOURCE BYTES, matching the DAG span
37
+ * convention the ranges themselves carry).
38
+ * - `CrystalKeyV1.requestDigest` is BARE lowercase hex, matching
39
+ * `RenderManifestV1.requestDigest`.
40
+ * - `CrystalV1.contentDigest` (the store's content address) is BARE lowercase
41
+ * hex, matching `ExactShardV1.digest`.
42
+ * Mixing these would either make every lookup miss or — far worse — make a
43
+ * prefix-stripped comparison accidentally succeed across granularities.
44
+ *
45
+ * Pure types + registered conformance IDs: no storage, no console, no network
46
+ * (PREVENT-PI-004 / PREVENT-011).
47
+ */
48
+
49
+ import type { DagSpan } from "../prompt-dag/types.js";
50
+
51
+ /**
52
+ * The immutable identity of a frozen crystal.
53
+ *
54
+ * Every field here is an IDENTITY field: changing any one of them yields a
55
+ * different key, and the key is stable under any change to a field NOT listed
56
+ * here (most importantly the global ledger frontier).
57
+ */
58
+ export interface CrystalKeyV1 {
59
+ /** Provider profile that framed the render (VC5B `ProviderProfile.id`). */
60
+ readonly profileId: string;
61
+ /** Profile version — a profile bump must invalidate frozen bytes. */
62
+ readonly profileVersion: string;
63
+ /** Digest of the canonical request shape, BARE lowercase hex (VC5B). */
64
+ readonly requestDigest: string;
65
+ /** The source ranges this crystal covers. Sorted + disjoint (see `crystal.ts`). */
66
+ readonly sourceRanges: readonly DagSpan[];
67
+ /** Digest over the covered ranges' pinned digests, `sha256:` prefixed. */
68
+ readonly coveredDigest: string;
69
+ /** VALIDATED durable dependency high-water — never the global frontier. */
70
+ readonly dependencyHighWater: bigint;
71
+ /** Renderer version that produced the frozen bytes (VC5B renderer). */
72
+ readonly rendererVersion: string;
73
+ }
74
+
75
+ /**
76
+ * A frozen crystal: immutable bytes plus the manifest that explains them.
77
+ *
78
+ * `bytes` are the rendered output verbatim. `contentDigest` is the SHA-256 of
79
+ * exactly those bytes (bare lowercase hex) and is what makes the store
80
+ * CONTENT-ADDRESSED: two writers who independently render the same key must
81
+ * produce the same digest, and if they do not, one of them is wrong and the
82
+ * store refuses rather than picking a winner (`CRY_KEY_COLLISION`).
83
+ */
84
+ export interface CrystalV1 {
85
+ readonly schema: "crystal-v1";
86
+ /** Canonical encoding of the key (see `encodeCrystalKey`). */
87
+ readonly keyDigest: string;
88
+ /** The frozen rendered bytes, verbatim and immutable. */
89
+ readonly bytes: Uint8Array;
90
+ /** SHA-256 over `bytes`, BARE lowercase hex (the content address). */
91
+ readonly contentDigest: string;
92
+ /** Byte length of `bytes`, carried so readers need not touch the payload. */
93
+ readonly byteCount: number;
94
+ /** The identity this crystal was frozen under. */
95
+ readonly key: CrystalKeyV1;
96
+ }
97
+
98
+ /** VC7A failure codes (registered CRY codes). */
99
+ export type CrystalFailureCode =
100
+ /** Two covered ranges in the same session overlap — identity is ambiguous. */
101
+ | "CRY_RANGE_OVERLAP"
102
+ /** A key already exists with DIFFERENT bytes; the store never overwrites. */
103
+ | "CRY_KEY_COLLISION"
104
+ /** A covered range is malformed (reversed/negative seq or byte bounds). */
105
+ | "CRY_RANGE_INVALID"
106
+ /** The key names no covered ranges — a crystal must cover something. */
107
+ | "CRY_RANGE_EMPTY"
108
+ /** The key exceeds the covered-range or aggregate-byte bound. */
109
+ | "CRY_KEY_LIMIT"
110
+ /** The store is unavailable — triad mode C, nothing is served from cache. */
111
+ | "CRY_STORE_UNAVAILABLE";
112
+
113
+ /** The verdict of canonical key encoding. */
114
+ export type CrystalKeyResult =
115
+ | { readonly ok: true; readonly keyDigest: string; readonly key: CrystalKeyV1 }
116
+ | { readonly ok: false; readonly codes: readonly CrystalFailureCode[] };
117
+
118
+ /**
119
+ * The verdict of a store write.
120
+ *
121
+ * `written` distinguishes a FIRST write from an idempotent re-write of identical
122
+ * bytes: both are `ok`, because re-freezing the same crystal is legitimate (two
123
+ * concurrent renders raced), but only the first actually stored anything. A
124
+ * different-bytes write is never `ok` and never mutates the stored crystal.
125
+ */
126
+ export type CrystalWriteResult =
127
+ | { readonly ok: true; readonly written: boolean; readonly contentDigest: string }
128
+ | { readonly ok: false; readonly code: CrystalFailureCode; readonly contentDigest: string };
129
+
130
+ /**
131
+ * Runtime triad mode for the crystal path (TRIAD_RESILIENCE).
132
+ *
133
+ * A — the crystal store answered the read (the fast, normal path);
134
+ * B — a miss or a collision forced a FRESH deterministic render. This is an
135
+ * INDEPENDENT algorithm: no crystal, no store index, the renderer runs
136
+ * from the plan exactly as it would have before VC7A existed;
137
+ * C — the store is unavailable, so the cache is BYPASSED entirely. Mode C
138
+ * serves nothing from cache and states the loss of old semantic context;
139
+ * it never substitutes a stale or partially-written crystal.
140
+ */
141
+ export type CrystalMode = "A" | "B" | "C";
142
+
143
+ /** Reader-only aggregate for GET /api/vector-cortex/cache-crystals. */
144
+ export interface CrystalStoreStats {
145
+ readonly mode: CrystalMode;
146
+ /** Distinct crystals currently held. */
147
+ readonly crystalCount: number;
148
+ /** Aggregate frozen bytes held (sum of `byteCount`). */
149
+ readonly totalBytes: number;
150
+ /** Reads answered from the store (mode A). */
151
+ readonly hits: number;
152
+ /** Reads with no stored crystal, forcing a fresh render (mode B). */
153
+ readonly misses: number;
154
+ /** Bytes served from hits — the cache's observable benefit. */
155
+ readonly hitBytes: number;
156
+ /** First writes that actually stored a crystal. */
157
+ readonly writes: number;
158
+ /** Idempotent re-writes of byte-identical crystals. */
159
+ readonly duplicateWrites: number;
160
+ /** Same-key, different-bytes writes refused (`CRY_KEY_COLLISION`). */
161
+ readonly collisions: number;
162
+ }
163
+
164
+ /**
165
+ * Maximum covered ranges in one key. A key is caller-shaped input; this bound
166
+ * exists so a single lookup cannot be turned into an unbounded sort/hash of
167
+ * attacker-chosen ranges (CRY_KEY_LIMIT).
168
+ */
169
+ export const CRYSTAL_LIMIT_RANGES = 256;
170
+
171
+ /** Maximum aggregate covered bytes across a key's ranges (8 MiB). */
172
+ export const CRYSTAL_LIMIT_BYTES = 8 * 1024 * 1024;
173
+
174
+ /**
175
+ * Registered VC7A crystal conformance ID range (CRY-001..015). The acceptance
176
+ * aggregator reads these rows from the v2 manifest and asserts each returns its
177
+ * manifest `ok`/`code`.
178
+ */
179
+ export const CRYSTAL_IDS: readonly string[] = Array.from(
180
+ { length: 15 },
181
+ (_v, i) => `CRY-${String(i + 1).padStart(3, "0")}`,
182
+ );
183
+
184
+ /**
185
+ * Registered VC7A provider-identity conformance rows (PRO-016..023), continuing
186
+ * VC5B's PRO-001..015. These pin the PROFILE half of crystal identity: a profile
187
+ * or renderer bump must invalidate, and a profile-irrelevant change must not.
188
+ */
189
+ export const CRYSTAL_PROVIDER_IDS: readonly string[] = Array.from(
190
+ { length: 8 },
191
+ (_v, i) => `PRO-${String(i + 16).padStart(3, "0")}`,
192
+ );
193
+
194
+ /** Named VC7A conformance assertions (the sprint's headline rows). */
195
+ export const CRYSTAL_NAMED_IDS = [
196
+ "CRY-FRONTIER-001",
197
+ "CRY-COVERED-002",
198
+ "CRY-DEP-003",
199
+ ] as const;
200
+
201
+ /** The two structured events the VC7A reporter emits. */
202
+ export type CrystalEventName =
203
+ | "vector_cortex_crystal_written"
204
+ | "vector_cortex_crystal_collision";
205
+
206
+ export type { DagSpan };