pi-mega-compact 0.16.3 → 0.17.1

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 (207) hide show
  1. package/dist/config/vector-cortex.js +91 -0
  2. package/dist/dedup/digest.js +30 -0
  3. package/dist/dedup/l1-lsh-v2.js +60 -0
  4. package/dist/dedup/l1-lsh.js +52 -0
  5. package/dist/dedup/l1-minhash-v2.js +193 -0
  6. package/dist/dedup/l1-minhash.js +91 -0
  7. package/dist/dedup/l1-verify.js +54 -0
  8. package/dist/dedup/mmr.js +45 -0
  9. package/dist/dedup/normalize.js +39 -0
  10. package/dist/dedup/raptor/buildHistory.js +164 -0
  11. package/dist/dedup/raptor/guardrails.js +83 -0
  12. package/dist/dedup/raptor/incremental.js +304 -0
  13. package/dist/dedup/raptor/index.js +190 -0
  14. package/dist/dedup/raptor/kmeans.js +152 -0
  15. package/dist/dedup/raptor/multilevel.js +184 -0
  16. package/dist/dedup/raptor/retrieval.js +93 -0
  17. package/dist/dedup/raptor/summarizer.js +86 -0
  18. package/dist/dedup/raptor/tree.js +202 -0
  19. package/dist/dedup/topk.js +60 -0
  20. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry-ext.js +30 -0
  21. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +4 -14
  22. package/dist/extensions/dashboard-server/api-contracts/vector-cortex.js +12 -0
  23. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +11 -0
  24. package/dist/extensions/dashboard-server/routes-vector-cortex.js +292 -0
  25. package/dist/extensions/dashboard-server/routes.js +1 -0
  26. package/dist/extensions/dashboard-server/server.js +9 -1
  27. package/dist/extensions/mega-events/context-handler.js +11 -0
  28. package/dist/extensions/mega-runtime/vector-cortex-ledger.js +131 -0
  29. package/dist/extensions/mega-runtime/vector-cortex-safety.js +100 -0
  30. package/dist/src/config/vector-cortex.js +91 -0
  31. package/dist/src/config.js +6 -0
  32. package/dist/src/dedup/l1-lsh-v2.js +60 -0
  33. package/dist/src/dedup/l1-minhash-v2.js +193 -0
  34. package/dist/src/dedup/normalize.js +1 -1
  35. package/dist/src/vector-cortex/conformance/emit.js +46 -0
  36. package/dist/src/vector-cortex/conformance/manifest.js +235 -0
  37. package/dist/src/vector-cortex/conformance/runner.js +111 -0
  38. package/dist/src/vector-cortex/conformance/triadB-reader.js +95 -0
  39. package/dist/src/vector-cortex/eval/annotations.js +41 -0
  40. package/dist/src/vector-cortex/eval/metrics.js +86 -0
  41. package/dist/src/vector-cortex/eval/observer.js +61 -0
  42. package/dist/src/vector-cortex/eval/persist.js +67 -0
  43. package/dist/src/vector-cortex/eval/reader.js +29 -0
  44. package/dist/src/vector-cortex/eval/types.js +33 -0
  45. package/dist/src/vector-cortex/ledger/adapter.js +55 -0
  46. package/dist/src/vector-cortex/ledger/compat-journal.js +206 -0
  47. package/dist/src/vector-cortex/ledger/emit.js +53 -0
  48. package/dist/src/vector-cortex/ledger/event-codec.js +72 -0
  49. package/dist/src/vector-cortex/ledger/event-codecB.js +76 -0
  50. package/dist/src/vector-cortex/ledger/sqlite.js +200 -0
  51. package/dist/src/vector-cortex/ledger/store.js +184 -0
  52. package/dist/src/vector-cortex/ledger/types.js +52 -0
  53. package/dist/src/vector-cortex/ledger/validator.js +117 -0
  54. package/dist/src/vector-cortex/migrations/effective-cut-v2.js +103 -0
  55. package/dist/src/vector-cortex/migrations/minhash-v2.js +209 -0
  56. package/dist/src/vector-cortex/migrations/occurrence-v2.js +139 -0
  57. package/dist/src/vector-cortex/replay/cut.js +123 -0
  58. package/dist/src/vector-cortex/replay/emit.js +58 -0
  59. package/dist/src/vector-cortex/replay/replay.js +260 -0
  60. package/dist/src/vector-cortex/replay/replayB.js +73 -0
  61. package/dist/src/vector-cortex/replay/types.js +52 -0
  62. package/dist/src/vector-cortex/resilience/breaker-core.js +359 -0
  63. package/dist/src/vector-cortex/resilience/breaker.js +12 -0
  64. package/dist/src/vector-cortex/resilience/emit.js +56 -0
  65. package/dist/src/vector-cortex/resilience/spool-core.js +347 -0
  66. package/dist/src/vector-cortex/resilience/spool.js +12 -0
  67. package/dist/src/vector-cortex/resilience/types.js +32 -0
  68. package/dist/vector-cortex/conformance/emit.js +46 -0
  69. package/dist/vector-cortex/conformance/manifest.js +235 -0
  70. package/dist/vector-cortex/conformance/runner.js +111 -0
  71. package/dist/vector-cortex/conformance/triadB-reader.js +95 -0
  72. package/dist/vector-cortex/eval/annotations.js +41 -0
  73. package/dist/vector-cortex/eval/metrics.js +86 -0
  74. package/dist/vector-cortex/eval/observer.js +61 -0
  75. package/dist/vector-cortex/eval/persist.js +67 -0
  76. package/dist/vector-cortex/eval/reader.js +29 -0
  77. package/dist/vector-cortex/eval/types.js +33 -0
  78. package/dist/vector-cortex/ledger/adapter.js +55 -0
  79. package/dist/vector-cortex/ledger/compat-journal.js +206 -0
  80. package/dist/vector-cortex/ledger/emit.js +53 -0
  81. package/dist/vector-cortex/ledger/event-codec.js +72 -0
  82. package/dist/vector-cortex/ledger/event-codecB.js +76 -0
  83. package/dist/vector-cortex/ledger/sqlite.js +200 -0
  84. package/dist/vector-cortex/ledger/store.js +184 -0
  85. package/dist/vector-cortex/ledger/types.js +52 -0
  86. package/dist/vector-cortex/ledger/validator.js +117 -0
  87. package/dist/vector-cortex/migrations/effective-cut-v2.js +103 -0
  88. package/dist/vector-cortex/migrations/minhash-v2.js +209 -0
  89. package/dist/vector-cortex/migrations/occurrence-v2.js +139 -0
  90. package/dist/vector-cortex/replay/cut.js +123 -0
  91. package/dist/vector-cortex/replay/emit.js +58 -0
  92. package/dist/vector-cortex/replay/replay.js +260 -0
  93. package/dist/vector-cortex/replay/replayB.js +73 -0
  94. package/dist/vector-cortex/replay/types.js +52 -0
  95. package/dist/vector-cortex/resilience/breaker-core.js +359 -0
  96. package/dist/vector-cortex/resilience/breaker.js +12 -0
  97. package/dist/vector-cortex/resilience/emit.js +56 -0
  98. package/dist/vector-cortex/resilience/spool-core.js +347 -0
  99. package/dist/vector-cortex/resilience/spool.js +12 -0
  100. package/dist/vector-cortex/resilience/types.js +32 -0
  101. package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js → AreaChart-BQXFF7UI.js} +2 -2
  102. package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js.map → AreaChart-BQXFF7UI.js.map} +1 -1
  103. package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js → BarChart-BVtoz9Eg.js} +2 -2
  104. package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js.map → BarChart-BVtoz9Eg.js.map} +1 -1
  105. package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js → CacheTab-trI2TIGy.js} +2 -2
  106. package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js.map → CacheTab-trI2TIGy.js.map} +1 -1
  107. package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js → EventsTab-CNUbk9__.js} +2 -2
  108. package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js.map → EventsTab-CNUbk9__.js.map} +1 -1
  109. package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js → HealthTab-K-bd_CM-.js} +2 -2
  110. package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js.map → HealthTab-K-bd_CM-.js.map} +1 -1
  111. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js → MaintenanceTab-CoEioKzx.js} +2 -2
  112. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js.map → MaintenanceTab-CoEioKzx.js.map} +1 -1
  113. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js → MemoryMapTab-CJo24ulv.js} +2 -2
  114. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js.map → MemoryMapTab-CJo24ulv.js.map} +1 -1
  115. package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js → MetricsTab-DCNlQWWP.js} +2 -2
  116. package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js.map → MetricsTab-DCNlQWWP.js.map} +1 -1
  117. package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js → OverviewTab-CQQlyIQ6.js} +2 -2
  118. package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js.map → OverviewTab-CQQlyIQ6.js.map} +1 -1
  119. package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js → ReposTab-Cqm82_yn.js} +2 -2
  120. package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js.map → ReposTab-Cqm82_yn.js.map} +1 -1
  121. package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js → SessionsTab-A8xTAdET.js} +2 -2
  122. package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js.map → SessionsTab-A8xTAdET.js.map} +1 -1
  123. package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js → SetupTab-CqaSgaZl.js} +2 -2
  124. package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js.map → SetupTab-CqaSgaZl.js.map} +1 -1
  125. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js → TimeSavedCard-C_f3q8GR.js} +2 -2
  126. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js.map → TimeSavedCard-C_f3q8GR.js.map} +1 -1
  127. package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js → TurnsTab-BEsafACo.js} +2 -2
  128. package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js.map → TurnsTab-BEsafACo.js.map} +1 -1
  129. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD1CMK80.js +2 -0
  130. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD1CMK80.js.map +1 -0
  131. package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js → WikiTab-Bf01M2_Z.js} +2 -2
  132. package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js.map → WikiTab-Bf01M2_Z.js.map} +1 -1
  133. package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js → button-BEJPTk_E.js} +2 -2
  134. package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js.map → button-BEJPTk_E.js.map} +1 -1
  135. package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js → card-DK1V382s.js} +2 -2
  136. package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js.map → card-DK1V382s.js.map} +1 -1
  137. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js → generateCategoricalChart-CRtaPphL.js} +2 -2
  138. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js.map → generateCategoricalChart-CRtaPphL.js.map} +1 -1
  139. package/extensions/dashboard-client/dist/assets/{index-B93cewiX.js → index-CB6BslE4.js} +34 -29
  140. package/extensions/dashboard-client/dist/assets/index-CB6BslE4.js.map +1 -0
  141. package/extensions/dashboard-client/dist/assets/index-CfvziyBi.css +1 -0
  142. package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js → switch-C4FV6B9k.js} +2 -2
  143. package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js.map → switch-C4FV6B9k.js.map} +1 -1
  144. package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js → toggle-GBmeENMd.js} +2 -2
  145. package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js.map → toggle-GBmeENMd.js.map} +1 -1
  146. package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js → useSSE-BOKZGSn1.js} +2 -2
  147. package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js.map → useSSE-BOKZGSn1.js.map} +1 -1
  148. package/extensions/dashboard-client/dist/index.html +2 -2
  149. package/extensions/dashboard-client/src/App.tsx +2 -0
  150. package/extensions/dashboard-client/src/api/vector-cortex.ts +54 -0
  151. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +272 -0
  152. package/extensions/dashboard-client/src/tabs/registry.ts +4 -1
  153. package/extensions/dashboard-client/src/types/vector-cortex.ts +84 -0
  154. package/extensions/dashboard-client/tsconfig.json +1 -1
  155. package/extensions/dashboard-server/api-contracts/endpoints/registry-ext.ts +67 -0
  156. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +4 -21
  157. package/extensions/dashboard-server/api-contracts/index.ts +3 -0
  158. package/extensions/dashboard-server/api-contracts/vector-cortex.ts +136 -0
  159. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +41 -0
  160. package/extensions/dashboard-server/routes-vector-cortex.ts +332 -0
  161. package/extensions/dashboard-server/routes.ts +6 -0
  162. package/extensions/dashboard-server/server.ts +8 -0
  163. package/extensions/mega-events/context-handler.ts +16 -0
  164. package/extensions/mega-runtime/vector-cortex-ledger.ts +165 -0
  165. package/extensions/mega-runtime/vector-cortex-safety.ts +181 -0
  166. package/package.json +2 -1
  167. package/src/config/vector-cortex.ts +98 -0
  168. package/src/config.ts +28 -0
  169. package/src/dedup/l1-lsh-v2.ts +78 -0
  170. package/src/dedup/l1-minhash-v2.ts +221 -0
  171. package/src/dedup/normalize.ts +1 -1
  172. package/src/store/sqlite/game-scores.ts +2 -1
  173. package/src/vector-cortex/conformance/emit.ts +56 -0
  174. package/src/vector-cortex/conformance/manifest.ts +298 -0
  175. package/src/vector-cortex/conformance/runner.ts +165 -0
  176. package/src/vector-cortex/conformance/triadB-reader.ts +110 -0
  177. package/src/vector-cortex/eval/annotations.ts +70 -0
  178. package/src/vector-cortex/eval/metrics.ts +113 -0
  179. package/src/vector-cortex/eval/observer.ts +86 -0
  180. package/src/vector-cortex/eval/persist.ts +73 -0
  181. package/src/vector-cortex/eval/reader.ts +45 -0
  182. package/src/vector-cortex/eval/types.ts +89 -0
  183. package/src/vector-cortex/ledger/adapter.ts +71 -0
  184. package/src/vector-cortex/ledger/compat-journal.ts +234 -0
  185. package/src/vector-cortex/ledger/emit.ts +86 -0
  186. package/src/vector-cortex/ledger/event-codec.ts +79 -0
  187. package/src/vector-cortex/ledger/event-codecB.ts +87 -0
  188. package/src/vector-cortex/ledger/sqlite.ts +265 -0
  189. package/src/vector-cortex/ledger/store.ts +380 -0
  190. package/src/vector-cortex/ledger/types.ts +141 -0
  191. package/src/vector-cortex/ledger/validator.ts +124 -0
  192. package/src/vector-cortex/migrations/effective-cut-v2.ts +149 -0
  193. package/src/vector-cortex/migrations/minhash-v2.ts +263 -0
  194. package/src/vector-cortex/migrations/occurrence-v2.ts +192 -0
  195. package/src/vector-cortex/replay/cut.ts +169 -0
  196. package/src/vector-cortex/replay/emit.ts +89 -0
  197. package/src/vector-cortex/replay/replay.ts +308 -0
  198. package/src/vector-cortex/replay/replayB.ts +97 -0
  199. package/src/vector-cortex/replay/types.ts +145 -0
  200. package/src/vector-cortex/resilience/breaker-core.ts +444 -0
  201. package/src/vector-cortex/resilience/breaker.ts +22 -0
  202. package/src/vector-cortex/resilience/emit.ts +99 -0
  203. package/src/vector-cortex/resilience/spool-core.ts +411 -0
  204. package/src/vector-cortex/resilience/spool.ts +22 -0
  205. package/src/vector-cortex/resilience/types.ts +158 -0
  206. package/extensions/dashboard-client/dist/assets/index-B93cewiX.js.map +0 -1
  207. package/extensions/dashboard-client/dist/assets/index-C_tCbOI0.css +0 -1
@@ -0,0 +1,411 @@
1
+ /**
2
+ * vector-cortex/resilience/spool-core.ts — VC0C durable spool implementation (VC0C).
3
+ *
4
+ * Implementation file behind the `spool.ts` factory barrel. See spool.ts for the
5
+ * public surface; this file owns the TRIAD_RESILIENCE §spool append/fsync/ack
6
+ * protocol.
7
+ *
8
+ * Implements TRIAD_RESILIENCE.md §Spool protocol as the mode-B independent,
9
+ * deterministic, local disk spool that derives directly from authority:
10
+ *
11
+ * - One spool file per session: `spool-<session>.spool` under a state dir.
12
+ * - Header records schema, session, first sequence, and the prior durable
13
+ * contiguous high-water.
14
+ * - Frames are length-prefixed binary records carrying seq + eventId +
15
+ * original bytes + SHA-256 + CRC32C (Castagnoli). A frame is FSYNC'd to disk
16
+ * before the writer acknowledges `SPOOLED`; only a durable ledger commit
17
+ * (after `drain` inserts into authority and the ledger is fsynced) returns
18
+ * `SPOOL_COMMITTED` and advances the contiguous high-water.
19
+ * - `drain` strictly sorts frames by `(seq, eventId)`, rejects gaps/conflicts,
20
+ * inserts idempotently by `(eventId, digest)`, appends an fsynced ack frame,
21
+ * then advances contiguous high-water. A duplicate with the same id+digest is
22
+ * acknowledged (`SPOOL_IDEMPOTENT_ACK`); the same id with a DIFFERENT digest
23
+ * is `SPOOL_MANUAL_HALT`.
24
+ * - Crash before an ack safely replays: on reopen, frames after the last ack
25
+ * are re-drained (the ack frame carries the committed seq).
26
+ * - A derived builder may never read beyond the durable contiguous authority
27
+ * high-water; during an authority outage the high-water freezes (drain cannot
28
+ * commit) even while the spool keeps accepting frames.
29
+ *
30
+ * LOCAL ONLY: filesystem + node:crypto, zero network (PREVENT-PI-004); no `any`
31
+ * (PREVENT-011); synchronous durable writes (fsyncSync) per the spec.
32
+ */
33
+
34
+ import { createHash } from "node:crypto";
35
+ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, writeSync } from "node:fs";
36
+ import { basename, join } from "node:path";
37
+ import type { SpoolDrainVerdict } from "./types.js";
38
+ import type { ResilienceReporter } from "./emit.js";
39
+
40
+ export const SPOOL_SCHEMA = "spool-v1";
41
+ /** Sentinel eventId of an acknowledgement frame (carries the committed seq). */
42
+ export const ACK_EVENT_ID = "__ack__";
43
+ /** Fixed on-disk width of the digest field: "sha256:" + 64 hex chars = 71 bytes. */
44
+ export const DIGEST_FIELD_LEN = "sha256:".length + 64;
45
+
46
+ /** CRC32C (Castagnoli) table — seeded once. */
47
+ const CRC_TABLE: number[] = (() => {
48
+ const t: number[] = new Array(256);
49
+ for (let n = 0; n < 256; n++) {
50
+ let c = n;
51
+ for (let k = 0; k < 8; k++) c = c & 1 ? 0x82f63b78 ^ (c >>> 1) : c >>> 1;
52
+ t[n] = c >>> 0;
53
+ }
54
+ return t;
55
+ })();
56
+
57
+ export function crc32c(bytes: Uint8Array): number {
58
+ let c = 0xffffffff;
59
+ for (let i = 0; i < bytes.length; i++) {
60
+ c = CRC_TABLE[(c ^ (bytes[i] as number)) & 0xff]! ^ (c >>> 8);
61
+ }
62
+ return (c ^ 0xffffffff) >>> 0;
63
+ }
64
+
65
+ export function sha256Hex(bytes: Uint8Array): string {
66
+ return createHash("sha256").update(bytes).digest("hex");
67
+ }
68
+
69
+ export interface SpoolFrame {
70
+ readonly session: string;
71
+ readonly seq: bigint;
72
+ readonly eventId: string;
73
+ readonly digest: string; // sha256:<hex> over original bytes
74
+ readonly bytes: Uint8Array;
75
+ readonly crc32c: number;
76
+ }
77
+
78
+ export interface SpoolOptions {
79
+ /** Directory hosting the spool files (created on demand). */
80
+ readonly dir: string;
81
+ /** True while the authority (mode-A SQLite ledger) is in OUTAGE. */
82
+ readonly authorityOutage?: () => boolean;
83
+ /** Optional reporter: emits `vector_cortex_frontier_frozen` on a real freeze. */
84
+ readonly reporter?: ResilienceReporter;
85
+ }
86
+
87
+ export interface SpoolAppendResult {
88
+ readonly verdict: "SPOOLED";
89
+ readonly seq: bigint;
90
+ }
91
+
92
+ export interface SpoolDrainResult {
93
+ readonly verdict: SpoolDrainVerdict;
94
+ readonly committedSeq: bigint;
95
+ readonly reason?: string;
96
+ }
97
+
98
+ /**
99
+ * The authority insert callback the caller supplies to `drain`. `committed`
100
+ * records a new accepted occurrence; `idempotent` acknowledges a duplicate with
101
+ * the SAME id+digest; `conflict` signals the same id with a DIFFERENT digest —
102
+ * which the spool translates to a MANUAL_HALT (never acknowledged).
103
+ */
104
+ export type AuthorityInsert = (
105
+ session: string,
106
+ seq: bigint,
107
+ eventId: string,
108
+ digest: string,
109
+ bytes: Uint8Array,
110
+ ) => "committed" | "idempotent" | "conflict";
111
+
112
+ interface SpoolHeader {
113
+ readonly schema: string;
114
+ readonly session: string;
115
+ readonly firstSeq: number;
116
+ readonly priorHighWater: string;
117
+ }
118
+
119
+ export interface SessionSpool {
120
+ readonly session: string;
121
+ /** Append+fsync a frame; returns SPOOLED once durable on disk. */
122
+ append(input: { seq: bigint; eventId: string; bytes: Uint8Array; digest?: string }): SpoolAppendResult;
123
+ /** Strict drain into authority; advances contiguous high-water after ack. */
124
+ drain(insert: AuthorityInsert): SpoolDrainResult;
125
+ /** Durable contiguous authority high-water (freezes during authority outage). */
126
+ highWater(): bigint;
127
+ /** Whether the derived frontier is frozen (authority outage). */
128
+ frozen(): boolean;
129
+ /** Mark the derived frontier frozen at the current high-water. */
130
+ freezeFrontier(): bigint;
131
+ }
132
+
133
+ export interface Spool {
134
+ session(session: string): SessionSpool;
135
+ }
136
+
137
+ export function createSpool(opts: SpoolOptions): Spool {
138
+ mkdirSync(opts.dir, { recursive: true });
139
+ const bySession = new Map<string, SessionSpoolImpl>();
140
+ return {
141
+ session(session: string): SessionSpool {
142
+ let s = bySession.get(session);
143
+ if (!s) {
144
+ s = new SessionSpoolImpl(join(opts.dir, `spool-${basename(session)}.spool`), session, opts);
145
+ bySession.set(session, s);
146
+ }
147
+ return s;
148
+ },
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Durably manages one session's spool file. Fully synchronous: every append is
154
+ * fsynced before acknowledging; every committed drain appends+fsyncs an ack
155
+ * frame. Reopen parses the header + frames, stops at a torn trailing frame
156
+ * (crash mid-write => unacknowledged tail), and drains only frames strictly
157
+ * beyond the recovered contiguous high-water — crash before ack safely replays.
158
+ */
159
+ class SessionSpoolImpl implements SessionSpool {
160
+ readonly session: string;
161
+ private readonly file: string;
162
+ private readonly outage: () => boolean;
163
+ private readonly reporter?: ResilienceReporter;
164
+ private header: SpoolHeader;
165
+ private frames: SpoolFrame[] = [];
166
+ private ackedSeq: bigint;
167
+ private frontierFrozen = false;
168
+
169
+ constructor(file: string, session: string, opts: SpoolOptions) {
170
+ this.file = file;
171
+ this.session = session;
172
+ this.outage = opts.authorityOutage ?? (() => false);
173
+ this.reporter = opts.reporter;
174
+ mkdirSync(join(file, ".."), { recursive: true });
175
+ if (existsSync(file)) {
176
+ const parsed = this.readExisting();
177
+ this.header = parsed.header;
178
+ this.frames = parsed.frames;
179
+ this.ackedSeq = parsed.highWater;
180
+ } else {
181
+ this.header = { schema: SPOOL_SCHEMA, session, firstSeq: 1, priorHighWater: "0" };
182
+ this.ackedSeq = 0n;
183
+ this.writeHeader();
184
+ }
185
+ }
186
+
187
+ private writeHeader(): void {
188
+ const fd = openSync(this.file, "a");
189
+ try {
190
+ writeSync(fd, Buffer.from(encodeHeader(this.header), "utf8"));
191
+ fsyncSync(fd);
192
+ } finally {
193
+ closeSync(fd);
194
+ }
195
+ }
196
+
197
+ /** Reopen: parse header + frames; recover the last contiguous ack high-water. */
198
+ private readExisting(): { header: SpoolHeader; frames: SpoolFrame[]; highWater: bigint } {
199
+ const raw = readFileSync(this.file);
200
+ const nl = raw.indexOf(10);
201
+ if (nl < 0) {
202
+ return { header: { schema: SPOOL_SCHEMA, session: this.session, firstSeq: 1, priorHighWater: "0" }, frames: [], highWater: 0n };
203
+ }
204
+ const header = parseHeader(raw.subarray(0, nl).toString("utf8"));
205
+ let highWater = BigInt(header.priorHighWater);
206
+ const frames: SpoolFrame[] = [];
207
+ let off = nl + 1;
208
+ for (;;) {
209
+ if (off >= raw.length) break;
210
+ const f = tryParseFrame(raw.subarray(off));
211
+ if (!f) break; // torn/unacknowledged tail — stop, replay from last ack
212
+ const frame = { ...f, session: this.session };
213
+ if (frame.eventId === ACK_EVENT_ID) {
214
+ highWater = frame.seq; // ack frame carries the committed seq
215
+ } else {
216
+ frames.push(frame);
217
+ }
218
+ const len = raw.readUInt32LE(off);
219
+ off += 4 + len;
220
+ }
221
+ // Only frames strictly beyond the recovered high-water need re-drain.
222
+ const unacked = frames.filter((f) => f.seq > highWater);
223
+ return { header, frames: unacked, highWater };
224
+ }
225
+
226
+ append(input: { seq: bigint; eventId: string; bytes: Uint8Array; digest?: string }): SpoolAppendResult {
227
+ const digest = input.digest ?? `sha256:${sha256Hex(input.bytes)}`;
228
+ const frame: SpoolFrame = {
229
+ session: this.session,
230
+ seq: input.seq,
231
+ eventId: input.eventId,
232
+ digest,
233
+ bytes: input.bytes,
234
+ crc32c: crc32c(input.bytes),
235
+ };
236
+ const framed = encodeFrame(frame);
237
+ // Fsync BEFORE acknowledging SPOOLED.
238
+ const fd = openSync(this.file, "a");
239
+ try {
240
+ writeSync(fd, framed, 0, framed.length);
241
+ fsyncSync(fd);
242
+ } finally {
243
+ closeSync(fd);
244
+ }
245
+ this.frames.push(frame);
246
+ return { verdict: "SPOOLED", seq: input.seq };
247
+ }
248
+
249
+ drain(insert: AuthorityInsert): SpoolDrainResult {
250
+ const tail = this.frames;
251
+ if (tail.length === 0) return { verdict: "SPOOL_COMMITTED", committedSeq: this.ackedSeq };
252
+ // Strictly sort by (seq, eventId). NOTE: we do NOT clear this.frames up front
253
+ // (VC0C-Q04) — the queue is only cleared once every insert has succeeded and
254
+ // the ack is durably appended. If any insert throws (rather than returning a
255
+ // conflict), the unacked tail must be retained for a later retry, not dropped.
256
+ const sorted = [...tail].sort(compareFrames);
257
+ let committed = this.ackedSeq;
258
+ for (const f of sorted) {
259
+ if (f.seq !== committed + 1n) {
260
+ // Gap: requeue everything beyond the last committed seq and halt.
261
+ this.frames = sorted.filter((x) => x.seq > committed);
262
+ return { verdict: "SPOOL_MANUAL_HALT", committedSeq: committed, reason: `TRI_SPOOL_GAP expected ${committed + 1n} got ${f.seq}` };
263
+ }
264
+ let outcome: "committed" | "idempotent" | "conflict";
265
+ try {
266
+ outcome = insert(this.session, f.seq, f.eventId, f.digest, f.bytes);
267
+ } catch {
268
+ // Authority insert THREW (e.g. a transient authority write error). This is
269
+ // NON-FATAL: requeue the unacked tail and halt, mirroring the manual-halt
270
+ // path, instead of propagating (a propagating throw would lose the frames
271
+ // and leave a retry seeing SPOOL_COMMITTED-with-nothing-processed).
272
+ this.frames = sorted.filter((x) => x.seq > committed);
273
+ return { verdict: "SPOOL_MANUAL_HALT", committedSeq: committed, reason: "TRI_SPOOL_INSERT_THROW" };
274
+ }
275
+ if (outcome === "committed" || outcome === "idempotent") {
276
+ committed = f.seq;
277
+ } else {
278
+ this.frames = sorted.filter((x) => x.seq > committed);
279
+ return { verdict: "SPOOL_MANUAL_HALT", committedSeq: committed, reason: "TRI_SPOOL_CONFLICT conflicting digest" };
280
+ }
281
+ }
282
+ // All frames committed: clear the in-memory queue, then durably commit by
283
+ // appending+fsyncing an ack frame and advancing the high-water.
284
+ this.frames = [];
285
+ this.appendAck(committed);
286
+ this.ackedSeq = committed;
287
+ return { verdict: "SPOOL_COMMITTED", committedSeq: committed };
288
+ }
289
+
290
+ private appendAck(seq: bigint): void {
291
+ const frame: SpoolFrame = {
292
+ session: this.session,
293
+ seq,
294
+ eventId: ACK_EVENT_ID,
295
+ digest: `sha256:${sha256Hex(new Uint8Array(0))}`,
296
+ bytes: new Uint8Array(0),
297
+ crc32c: crc32c(new Uint8Array(0)),
298
+ };
299
+ const framed = encodeFrame(frame);
300
+ const fd = openSync(this.file, "a");
301
+ try {
302
+ writeSync(fd, framed, 0, framed.length);
303
+ fsyncSync(fd);
304
+ } finally {
305
+ closeSync(fd);
306
+ }
307
+ }
308
+
309
+ highWater(): bigint {
310
+ // The contiguous authority high-water advances ONLY on a durable ack
311
+ // (appendAck after a successful drain commit). During authority outage drain
312
+ // cannot commit, so ackedSeq naturally freezes here — an explicit outage
313
+ // branch would return the same value and was dead code (VC0C-Q05). The
314
+ // freeze/outage SIGNAL is the `frozen()`/`frontierFrozen` flag, not this
315
+ // scalar; frames appended during outage remain in the unacked tail.
316
+ return this.ackedSeq;
317
+ }
318
+
319
+ frozen(): boolean {
320
+ return this.frontierFrozen || this.outage();
321
+ }
322
+
323
+ freezeFrontier(): bigint {
324
+ const firstFreeze = !this.frontierFrozen && !this.outage();
325
+ this.frontierFrozen = true;
326
+ // Emit vector_cortex_frontier_frozen only on an actual freeze transition
327
+ // (once per outage; flag-gated + non-fatal inside the reporter). No event on
328
+ // an already-frozen or authority-outage-covered frontier. flag OFF => no-op.
329
+ if (firstFreeze) {
330
+ this.reporter?.frontierFrozen({
331
+ session: this.session,
332
+ committedSeq: String(this.ackedSeq),
333
+ frozenHighWater: String(this.ackedSeq),
334
+ });
335
+ }
336
+ return this.ackedSeq;
337
+ }
338
+ }
339
+
340
+ function compareFrames(a: SpoolFrame, b: SpoolFrame): number {
341
+ if (a.seq !== b.seq) return a.seq < b.seq ? -1 : 1;
342
+ return a.eventId < b.eventId ? -1 : a.eventId > b.eventId ? 1 : 0;
343
+ }
344
+
345
+ function encodeHeader(h: SpoolHeader): string {
346
+ return `${JSON.stringify(h)}\n`;
347
+ }
348
+
349
+ function parseHeader(line: string): SpoolHeader {
350
+ const raw = JSON.parse(line) as SpoolHeader;
351
+ if (raw.schema !== SPOOL_SCHEMA) throw new Error("TRI_SPOOL_SCHEMA");
352
+ return raw;
353
+ }
354
+
355
+ /**
356
+ * Encode one binary frame: [u32 len][seq i64][eventId UTF-8 NUL][bytes][NUL][digest][u32 crc32c].
357
+ * The digest field is DIGEST_FIELD_LEN bytes ("sha256:" + 64 hex = 71) — sizing it
358
+ * to fit the FULL digest string is load-bearing for read-side verification
359
+ * (VC0C-Q03): a 64-byte cap silently truncated Buffer.write, so the stored digest
360
+ * could never match the recomputed value and every frame would be rejected on read.
361
+ * body length = 8 + eventId.length + 1 + bytes.length + 1 + DIGEST_FIELD_LEN + 4.
362
+ */
363
+ function encodeFrame(f: SpoolFrame): Buffer {
364
+ const idBuf = Buffer.from(f.eventId, "utf8");
365
+ const bodyLen = 8 + idBuf.length + 1 + f.bytes.length + 1 + DIGEST_FIELD_LEN + 4;
366
+ const body = Buffer.alloc(bodyLen);
367
+ let o = 0;
368
+ body.writeBigInt64LE(BigInt(f.seq), o); o += 8;
369
+ idBuf.copy(body, o); o += idBuf.length;
370
+ body[o] = 0; o += 1;
371
+ Buffer.from(f.bytes).copy(body, o); o += f.bytes.length;
372
+ body[o] = 0; o += 1;
373
+ body.write(f.digest, o, "utf8"); o += DIGEST_FIELD_LEN;
374
+ body.writeUInt32LE(f.crc32c, o); o += 4;
375
+ const lenBuf = Buffer.alloc(4);
376
+ lenBuf.writeUInt32LE(bodyLen, 0);
377
+ return Buffer.concat([lenBuf, body]);
378
+ }
379
+
380
+ /**
381
+ * Parse a length-prefixed frame; returns null for a torn/truncated tail OR a
382
+ * CRYPTOGRAPHIC INTEGRITY MISMATCH (VC0C-Q03). The stored SHA-256 digest and
383
+ * CRC32C are recomputed over the parsed bytes and compared — a same-length bit
384
+ * flip must not survive reopen and be drained/committed. A mismatch is treated
385
+ * like a torn tail: `readExisting` stops there and the frame is NOT accepted —
386
+ * it remains unacknowledged, so replay/requeue preserves it rather than
387
+ * silently committing corrupted bytes.
388
+ */
389
+ function tryParseFrame(buf: Buffer): SpoolFrame | null {
390
+ if (buf.length < 4) return null;
391
+ const len = buf.readUInt32LE(0);
392
+ if (buf.length < 4 + len) return null; // torn
393
+ const body = buf.subarray(4, 4 + len);
394
+ let o = 0;
395
+ const seq = body.readBigInt64LE(o); o += 8;
396
+ const idEnd = body.indexOf(0, o);
397
+ if (idEnd < 0) return null;
398
+ const eventId = body.subarray(o, idEnd).toString("utf8"); o = idEnd + 1;
399
+ const bytesStart = o;
400
+ const nul2 = body.indexOf(0, o);
401
+ if (nul2 < 0) return null;
402
+ const bytes = new Uint8Array(body.subarray(bytesStart, nul2)); o = nul2 + 1;
403
+ const digest = body.subarray(o, o + DIGEST_FIELD_LEN).toString("utf8");
404
+ const storedCrc = body.readUInt32LE(o + DIGEST_FIELD_LEN);
405
+ // Recompute and verify — write-only integrity would let a same-length bit flip
406
+ // drain/commit silently. Return null (treat as torn/corrupt) on mismatch.
407
+ if (digest !== `sha256:${sha256Hex(bytes)}` || storedCrc !== crc32c(bytes)) {
408
+ return null;
409
+ }
410
+ return { session: "", seq, eventId, digest, bytes, crc32c: storedCrc };
411
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * vector-cortex/resilience/spool.ts — VC0C durable spool barrel (VC0C).
3
+ *
4
+ * Thin barrel over the append/fsync/ack spool implementation in `spool-core.ts`
5
+ * (delegate-shell + impl-file split keeps the frequently-touched file small).
6
+ * Re-exports the public `createSpool` factory, the mode-B spool/session types,
7
+ * and the SHA-256 / CRC32C helpers for the safety adapter and tests.
8
+ *
9
+ * Full protocol semantics live in spool-core.ts (TRIAD_RESILIENCE §spool).
10
+ * Local-only, no network (PREVENT-PI-004), no `any` (PREVENT-011).
11
+ */
12
+
13
+ export { createSpool, SPOOL_SCHEMA, ACK_EVENT_ID, crc32c, sha256Hex } from "./spool-core.js";
14
+ export type {
15
+ SpoolFrame,
16
+ SpoolOptions,
17
+ SpoolAppendResult,
18
+ SpoolDrainResult,
19
+ AuthorityInsert,
20
+ SessionSpool,
21
+ Spool,
22
+ } from "./spool-core.js";
@@ -0,0 +1,158 @@
1
+ /**
2
+ * vector-cortex/resilience/types.ts — VC0C breaker/triad contracts (VC0C).
3
+ *
4
+ * Owns `Mode`, `TriadResult<T>`, `BreakerRecord`, `KillDecision`, and the
5
+ * `Breaker` circuit-breaker seam from CONTRACTS.md §TriadResult and breaker
6
+ * seam, plus the durable spool record shapes. Pure type/schema definitions +
7
+ * small pure predicates (no storage, no console, no network, no side effects —
8
+ * PREVENT-PI-004 / PREVENT-011). The live state machine lives in
9
+ * `breaker.ts`/`spool.ts`; the safety adapter selects A→B→C in
10
+ * `extensions/mega-runtime/vector-cortex-safety.ts`.
11
+ *
12
+ * Triad semantics (TRIAD_RESILIENCE.md): A is optimized/learned; B is
13
+ * deterministic/local and derives directly from authority; C is continuity, not
14
+ * semantic completeness — it uses only the exact current transcript and reports
15
+ * that limitation.
16
+ */
17
+
18
+ /** Triad execution modes: A optimized/learned, B deterministic/local, C continuity. */
19
+ export type Mode = "A" | "B" | "C";
20
+
21
+ /** Breaker circuit states (TRIAD_RESILIENCE.md §breaker state machine). */
22
+ export type BreakerState =
23
+ | "CLOSED_A"
24
+ | "OPEN_B"
25
+ | "OPEN_C"
26
+ | "PROBE_B"
27
+ | "PROBE_A"
28
+ | "MANUAL_HALT";
29
+
30
+ /** The type of trip that opened or is being tracked on a window. */
31
+ export type BreakerTripKind = "performance" | "correctness" | "manual";
32
+
33
+ /**
34
+ * A snapshot of the breaker from the perspective of one triad execution. Mirrors
35
+ * the contract shape `breaker: BreakerRecord` on `TriadResult<T>`. All window /
36
+ * cooldown / residence fields are MONOTONIC elapsed milliseconds; wall time
37
+ * appears only on the `updatedAt` record timestamp. The `frozenFrontier` field
38
+ * records whether derived frontiers were frozen by an authority outage.
39
+ */
40
+ export interface BreakerRecord {
41
+ /** Per-subsystem breaker identity. */
42
+ readonly subsystem: string;
43
+ /** Current circuit state. */
44
+ readonly state: BreakerState;
45
+ /** Monotonic start of the current rolling eligibility window (ms). */
46
+ readonly windowStartMs: number;
47
+ /** Attempts observed within the current window. */
48
+ readonly attempts: number;
49
+ /** Failures observed within the current window. */
50
+ readonly failures: number;
51
+ /** Trip kind of the failure that most recently opened the breaker. */
52
+ readonly tripKind: BreakerTripKind;
53
+ /** Monotonic time when the breaker entered its current state (ms). */
54
+ readonly transitionedAtMs: number;
55
+ /** Monotonic time when a current cooldown started (ms), or undefined. */
56
+ readonly cooldownUntilMs?: number;
57
+ /** Recovery-probe count already satisfied in the current probe phase. */
58
+ readonly probeCount: number;
59
+ /** Exponential backoff attempt index (0 before any backoff increment). */
60
+ readonly retryAttempt: number;
61
+ /** Backoff delay currently in force (ms, with deterministic ±10% jitter). */
62
+ readonly retryDelayMs: number;
63
+ /** Whether derived frontiers are frozen by an authority outage. */
64
+ readonly frozenFrontier: boolean;
65
+ /** Reason captured on a manual halt (present only in MANUAL_HALT). */
66
+ readonly manualReason?: string;
67
+ /** Wall-clock ISO timestamp of the last state transition (records only). */
68
+ readonly updatedAt: string;
69
+ /** Approximate p95 latency over the current window (ms), for hysteresis. */
70
+ readonly p95Ms: number;
71
+ /** Failure rate over the current window (0..1), for hysteresis. */
72
+ readonly failureRate: number;
73
+ }
74
+
75
+ /**
76
+ * The discriminated result every critical operation returns (CONTRACTS.md).
77
+ * A successful head carries its output plus the breaker snapshot; a failure
78
+ * carries a code and a retryability flag plus the same breaker snapshot.
79
+ */
80
+ export type TriadResult<T> =
81
+ | {
82
+ ok: true;
83
+ value: T;
84
+ mode: Mode;
85
+ inputDigest: string;
86
+ outputDigest: string;
87
+ algorithmVersion: string;
88
+ latencyMs: number;
89
+ breaker: BreakerRecord;
90
+ }
91
+ | {
92
+ ok: false;
93
+ mode: Mode;
94
+ code: string;
95
+ retryable: boolean;
96
+ breaker: BreakerRecord;
97
+ };
98
+
99
+ /**
100
+ * KillDecision — the VC0C decision to demote before provider invocation.
101
+ * Correctness demotion before the provider call is required in 100% of chaos
102
+ * cases (sprint acceptance). `mode` is the mode actually selected; `demoteTo`
103
+ * is the lower mode the current head demotes to (C is terminal continuity).
104
+ */
105
+ export interface KillDecision {
106
+ readonly session: string;
107
+ readonly subsystem: string;
108
+ readonly reason: string;
109
+ readonly code: string;
110
+ readonly demoteTo: Mode;
111
+ readonly state: BreakerState;
112
+ readonly frozenFrontier: boolean;
113
+ readonly atMs: number;
114
+ }
115
+
116
+ /**
117
+ * The circuit-breaker seam (CONTRACTS.md). `execute` runs the triad A→B→C and
118
+ * returns a `TriadResult<T>`; `recordProbe` advances recovery probes;
119
+ * `manualHalt` requires a reason and `reset` (admin) clears cooldown but never
120
+ * evidence.
121
+ */
122
+ export interface Breaker {
123
+ execute<T>(
124
+ subsystem: string,
125
+ inputDigest: string,
126
+ run: Record<Mode, () => T>,
127
+ validate: (v: T) => boolean,
128
+ ): TriadResult<T>;
129
+ recordProbe(subsystem: string): BreakerRecord;
130
+ manualHalt(reason: string): BreakerRecord;
131
+ }
132
+
133
+ /**
134
+ * Registered TRI conformance ID range (TRI-001..030). The acceptance test reads
135
+ * these rows from the v2 `resilience/` domain and asserts each returns its
136
+ * manifest bytes or exactly its listed failure code. TRI-001..015 are owned by
137
+ * VC0C (window/probe/hysteresis/cooldown transitions); TRI-016..030 span the
138
+ * spool protocol (frame, drain, gap, digest conflict, ack crash, frozen
139
+ * frontier). Mirrors EVT_IDS / CUT_IDS / M3_IDS.
140
+ */
141
+ export const TRI_IDS = [
142
+ "TRI-001", "TRI-002", "TRI-003", "TRI-004", "TRI-005",
143
+ "TRI-006", "TRI-007", "TRI-008", "TRI-009", "TRI-010",
144
+ "TRI-011", "TRI-012", "TRI-013", "TRI-014", "TRI-015",
145
+ "TRI-016", "TRI-017", "TRI-018", "TRI-019", "TRI-020",
146
+ "TRI-021", "TRI-022", "TRI-023", "TRI-024", "TRI-025",
147
+ "TRI-026", "TRI-027", "TRI-028", "TRI-029", "TRI-030",
148
+ ] as const;
149
+
150
+ /**
151
+ * Acknowledgment outcome of a spool frame drain against the durable ledger.
152
+ * `SPOOL_IDEMPOTENT_ACK` acknowledges a duplicate with the same digest; a
153
+ * conflicting digest is `SPOOL_MANUAL_HALT` (never acknowledged).
154
+ */
155
+ export type SpoolDrainVerdict =
156
+ | "SPOOL_COMMITTED"
157
+ | "SPOOL_IDEMPOTENT_ACK"
158
+ | "SPOOL_MANUAL_HALT";