pi-mega-compact 0.16.2 → 0.17.0

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 (168) hide show
  1. package/dist/config/vector-cortex.js +81 -0
  2. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry-ext.js +30 -0
  3. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +4 -14
  4. package/dist/extensions/dashboard-server/api-contracts/vector-cortex.js +12 -0
  5. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +10 -0
  6. package/dist/extensions/dashboard-server/routes-vector-cortex.js +292 -0
  7. package/dist/extensions/dashboard-server/routes.js +1 -0
  8. package/dist/extensions/dashboard-server/server.js +9 -1
  9. package/dist/extensions/mega-events/context-handler.js +11 -0
  10. package/dist/extensions/mega-runtime/vector-cortex-ledger.js +131 -0
  11. package/dist/extensions/mega-runtime/vector-cortex-safety.js +100 -0
  12. package/dist/src/config/vector-cortex.js +81 -0
  13. package/dist/src/config.js +6 -0
  14. package/dist/src/httpEmbedder.js +136 -1
  15. package/dist/src/vector-cortex/eval/annotations.js +41 -0
  16. package/dist/src/vector-cortex/eval/metrics.js +86 -0
  17. package/dist/src/vector-cortex/eval/observer.js +61 -0
  18. package/dist/src/vector-cortex/eval/persist.js +67 -0
  19. package/dist/src/vector-cortex/eval/reader.js +29 -0
  20. package/dist/src/vector-cortex/eval/types.js +33 -0
  21. package/dist/src/vector-cortex/ledger/adapter.js +55 -0
  22. package/dist/src/vector-cortex/ledger/compat-journal.js +206 -0
  23. package/dist/src/vector-cortex/ledger/emit.js +53 -0
  24. package/dist/src/vector-cortex/ledger/event-codec.js +72 -0
  25. package/dist/src/vector-cortex/ledger/event-codecB.js +76 -0
  26. package/dist/src/vector-cortex/ledger/sqlite.js +200 -0
  27. package/dist/src/vector-cortex/ledger/store.js +184 -0
  28. package/dist/src/vector-cortex/ledger/types.js +52 -0
  29. package/dist/src/vector-cortex/ledger/validator.js +117 -0
  30. package/dist/src/vector-cortex/migrations/effective-cut-v2.js +103 -0
  31. package/dist/src/vector-cortex/migrations/occurrence-v2.js +139 -0
  32. package/dist/src/vector-cortex/replay/cut.js +123 -0
  33. package/dist/src/vector-cortex/replay/emit.js +58 -0
  34. package/dist/src/vector-cortex/replay/replay.js +260 -0
  35. package/dist/src/vector-cortex/replay/replayB.js +73 -0
  36. package/dist/src/vector-cortex/replay/types.js +52 -0
  37. package/dist/src/vector-cortex/resilience/breaker-core.js +359 -0
  38. package/dist/src/vector-cortex/resilience/breaker.js +12 -0
  39. package/dist/src/vector-cortex/resilience/emit.js +56 -0
  40. package/dist/src/vector-cortex/resilience/spool-core.js +347 -0
  41. package/dist/src/vector-cortex/resilience/spool.js +12 -0
  42. package/dist/src/vector-cortex/resilience/types.js +32 -0
  43. package/dist/vector-cortex/eval/annotations.js +41 -0
  44. package/dist/vector-cortex/eval/metrics.js +86 -0
  45. package/dist/vector-cortex/eval/observer.js +61 -0
  46. package/dist/vector-cortex/eval/persist.js +67 -0
  47. package/dist/vector-cortex/eval/reader.js +29 -0
  48. package/dist/vector-cortex/eval/types.js +33 -0
  49. package/dist/vector-cortex/ledger/adapter.js +55 -0
  50. package/dist/vector-cortex/ledger/compat-journal.js +206 -0
  51. package/dist/vector-cortex/ledger/emit.js +53 -0
  52. package/dist/vector-cortex/ledger/event-codec.js +72 -0
  53. package/dist/vector-cortex/ledger/event-codecB.js +76 -0
  54. package/dist/vector-cortex/ledger/sqlite.js +200 -0
  55. package/dist/vector-cortex/ledger/store.js +184 -0
  56. package/dist/vector-cortex/ledger/types.js +52 -0
  57. package/dist/vector-cortex/ledger/validator.js +117 -0
  58. package/dist/vector-cortex/migrations/effective-cut-v2.js +103 -0
  59. package/dist/vector-cortex/migrations/occurrence-v2.js +139 -0
  60. package/dist/vector-cortex/replay/cut.js +123 -0
  61. package/dist/vector-cortex/replay/emit.js +58 -0
  62. package/dist/vector-cortex/replay/replay.js +260 -0
  63. package/dist/vector-cortex/replay/replayB.js +73 -0
  64. package/dist/vector-cortex/replay/types.js +52 -0
  65. package/dist/vector-cortex/resilience/breaker-core.js +359 -0
  66. package/dist/vector-cortex/resilience/breaker.js +12 -0
  67. package/dist/vector-cortex/resilience/emit.js +56 -0
  68. package/dist/vector-cortex/resilience/spool-core.js +347 -0
  69. package/dist/vector-cortex/resilience/spool.js +12 -0
  70. package/dist/vector-cortex/resilience/types.js +32 -0
  71. package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js → AreaChart-CAnWomhw.js} +2 -2
  72. package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js.map → AreaChart-CAnWomhw.js.map} +1 -1
  73. package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js → BarChart-lZ2-s71k.js} +2 -2
  74. package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js.map → BarChart-lZ2-s71k.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js → CacheTab-BL2Thh66.js} +2 -2
  76. package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js.map → CacheTab-BL2Thh66.js.map} +1 -1
  77. package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js → EventsTab-DEh2Z9fp.js} +2 -2
  78. package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js.map → EventsTab-DEh2Z9fp.js.map} +1 -1
  79. package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js → HealthTab-DTwKclxk.js} +2 -2
  80. package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js.map → HealthTab-DTwKclxk.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js → MaintenanceTab-DDfBEdbX.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js.map → MaintenanceTab-DDfBEdbX.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js → MemoryMapTab-Dgfmg-0Q.js} +2 -2
  84. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js.map → MemoryMapTab-Dgfmg-0Q.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js → MetricsTab-DOcMm0Tx.js} +2 -2
  86. package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js.map → MetricsTab-DOcMm0Tx.js.map} +1 -1
  87. package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js → OverviewTab-DHT5aoHg.js} +2 -2
  88. package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js.map → OverviewTab-DHT5aoHg.js.map} +1 -1
  89. package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js → ReposTab-CGAmtR_c.js} +2 -2
  90. package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js.map → ReposTab-CGAmtR_c.js.map} +1 -1
  91. package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js → SessionsTab-BIgx6iKT.js} +2 -2
  92. package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js.map → SessionsTab-BIgx6iKT.js.map} +1 -1
  93. package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js → SetupTab-ADghKuEI.js} +2 -2
  94. package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js.map → SetupTab-ADghKuEI.js.map} +1 -1
  95. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js → TimeSavedCard-C33UiiOM.js} +2 -2
  96. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js.map → TimeSavedCard-C33UiiOM.js.map} +1 -1
  97. package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js → TurnsTab-DdbL9wPN.js} +2 -2
  98. package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js.map → TurnsTab-DdbL9wPN.js.map} +1 -1
  99. package/extensions/dashboard-client/dist/assets/VectorCortexTab-Cl1pbEdR.js +2 -0
  100. package/extensions/dashboard-client/dist/assets/VectorCortexTab-Cl1pbEdR.js.map +1 -0
  101. package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js → WikiTab-Ch00OFKs.js} +2 -2
  102. package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js.map → WikiTab-Ch00OFKs.js.map} +1 -1
  103. package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js → button-ByeEdo2g.js} +2 -2
  104. package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js.map → button-ByeEdo2g.js.map} +1 -1
  105. package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js → card-DesoLXvc.js} +2 -2
  106. package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js.map → card-DesoLXvc.js.map} +1 -1
  107. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js → generateCategoricalChart-CCAB7Iz4.js} +2 -2
  108. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js.map → generateCategoricalChart-CCAB7Iz4.js.map} +1 -1
  109. package/extensions/dashboard-client/dist/assets/{index-B93cewiX.js → index-B6cgyAPr.js} +34 -29
  110. package/extensions/dashboard-client/dist/assets/index-B6cgyAPr.js.map +1 -0
  111. package/extensions/dashboard-client/dist/assets/index-CfvziyBi.css +1 -0
  112. package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js → switch-DIZC-oe-.js} +2 -2
  113. package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js.map → switch-DIZC-oe-.js.map} +1 -1
  114. package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js → toggle-CIoWaH2e.js} +2 -2
  115. package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js.map → toggle-CIoWaH2e.js.map} +1 -1
  116. package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js → useSSE-Bv4943l1.js} +2 -2
  117. package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js.map → useSSE-Bv4943l1.js.map} +1 -1
  118. package/extensions/dashboard-client/dist/index.html +2 -2
  119. package/extensions/dashboard-client/src/App.tsx +2 -0
  120. package/extensions/dashboard-client/src/api/vector-cortex.ts +54 -0
  121. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +272 -0
  122. package/extensions/dashboard-client/src/tabs/registry.ts +4 -1
  123. package/extensions/dashboard-client/src/types/vector-cortex.ts +84 -0
  124. package/extensions/dashboard-server/api-contracts/endpoints/registry-ext.ts +67 -0
  125. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +4 -21
  126. package/extensions/dashboard-server/api-contracts/index.ts +3 -0
  127. package/extensions/dashboard-server/api-contracts/vector-cortex.ts +136 -0
  128. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +35 -0
  129. package/extensions/dashboard-server/routes-vector-cortex.ts +332 -0
  130. package/extensions/dashboard-server/routes.ts +6 -0
  131. package/extensions/dashboard-server/server.ts +8 -0
  132. package/extensions/mega-events/context-handler.ts +16 -0
  133. package/extensions/mega-runtime/vector-cortex-ledger.ts +165 -0
  134. package/extensions/mega-runtime/vector-cortex-safety.ts +181 -0
  135. package/package.json +2 -1
  136. package/src/config/vector-cortex.ts +87 -0
  137. package/src/config.ts +27 -0
  138. package/src/httpEmbedder.ts +134 -1
  139. package/src/vector-cortex/eval/annotations.ts +70 -0
  140. package/src/vector-cortex/eval/metrics.ts +113 -0
  141. package/src/vector-cortex/eval/observer.ts +86 -0
  142. package/src/vector-cortex/eval/persist.ts +73 -0
  143. package/src/vector-cortex/eval/reader.ts +45 -0
  144. package/src/vector-cortex/eval/types.ts +89 -0
  145. package/src/vector-cortex/ledger/adapter.ts +71 -0
  146. package/src/vector-cortex/ledger/compat-journal.ts +234 -0
  147. package/src/vector-cortex/ledger/emit.ts +86 -0
  148. package/src/vector-cortex/ledger/event-codec.ts +79 -0
  149. package/src/vector-cortex/ledger/event-codecB.ts +87 -0
  150. package/src/vector-cortex/ledger/sqlite.ts +265 -0
  151. package/src/vector-cortex/ledger/store.ts +380 -0
  152. package/src/vector-cortex/ledger/types.ts +141 -0
  153. package/src/vector-cortex/ledger/validator.ts +124 -0
  154. package/src/vector-cortex/migrations/effective-cut-v2.ts +149 -0
  155. package/src/vector-cortex/migrations/occurrence-v2.ts +192 -0
  156. package/src/vector-cortex/replay/cut.ts +169 -0
  157. package/src/vector-cortex/replay/emit.ts +89 -0
  158. package/src/vector-cortex/replay/replay.ts +308 -0
  159. package/src/vector-cortex/replay/replayB.ts +97 -0
  160. package/src/vector-cortex/replay/types.ts +145 -0
  161. package/src/vector-cortex/resilience/breaker-core.ts +444 -0
  162. package/src/vector-cortex/resilience/breaker.ts +22 -0
  163. package/src/vector-cortex/resilience/emit.ts +99 -0
  164. package/src/vector-cortex/resilience/spool-core.ts +411 -0
  165. package/src/vector-cortex/resilience/spool.ts +22 -0
  166. package/src/vector-cortex/resilience/types.ts +158 -0
  167. package/extensions/dashboard-client/dist/assets/index-B93cewiX.js.map +0 -1
  168. package/extensions/dashboard-client/dist/assets/index-C_tCbOI0.css +0 -1
@@ -0,0 +1,380 @@
1
+ /**
2
+ * vector-cortex/ledger/store.ts — VC1B capability-separated ledger contracts.
3
+ *
4
+ * Owns `LedgerReader` / `LedgerWriter` / `LedgerAdmin` and `CompatJournalV1`.
5
+ * The ledger is append-only, seq is monotonic per session, a tool RESULT names
6
+ * exactly one earlier call in the same session, and duplicates are identified by
7
+ * `(eventId, digest)` only (two occurrences may share bytes at distinct seq).
8
+ *
9
+ * Capability gating mirrors the host `asReader/asWriter/asAdmin` pattern: a
10
+ * consumer receives ONLY what it needs. Dashboard GET receives `LedgerReader`;
11
+ * only ingestion receives `LedgerWriter`; only the migration coordinator
12
+ * receives `LedgerAdmin`. This keeps the neutral byte-authority ledger closed to
13
+ * anything but the sanctioned channels (CONTRACTS §Store and migration
14
+ * contracts).
15
+ *
16
+ * Pure type/schema definitions + small pure predicates. No storage, no console,
17
+ * no network, no side effects (PREVENT-PI-004 / PREVENT-011).
18
+ */
19
+
20
+ /**
21
+ * An accepted occurrence in the canonical v2 ledger. This is the durable row
22
+ * shape: the neutral byte authority plus the reference envelope. `digest` is the
23
+ * authoritative `sha256:${string}` of `sourceBytes`; `sourceBytes` round-trips
24
+ * exactly (never reconstructed from normalized text).
25
+ */
26
+ export interface LedgerOccurrence {
27
+ /** The source session the occurrence belongs to (ledger scopes by session). */
28
+ readonly session: string;
29
+ /** Monotonic per-session sequence (never regresses for an accepted row). */
30
+ readonly seq: bigint;
31
+ /** Stable occurrence identity within the session (the EventV2 eventId). */
32
+ readonly eventId: string;
33
+ /** SHA-256 over sourceBytes (authoritative, `sha256:<hex>`). */
34
+ readonly digest: string;
35
+ /** Neutral occurrence kind (policy/user/assistant/tool/message...). */
36
+ readonly kind: string;
37
+ /** On a tool RESULT, references exactly one earlier call in this session. */
38
+ readonly toolCallId?: string;
39
+ /** Authoritative original bytes (byte authority). */
40
+ readonly sourceBytes: Uint8Array;
41
+ }
42
+
43
+ /**
44
+ * Identifier of a tool pair for a RESULT row: names exactly one earlier CALL's
45
+ * (session-local) eventId. Absent on non-result rows.
46
+ */
47
+ export interface LedgerToolRef {
48
+ /** The session-local eventId of the CALL this RESULT closes. */
49
+ readonly callEventId: string;
50
+ }
51
+
52
+ /** Failure codes the writer surfaces on a rejected append (write path). */
53
+ export type LedgerAppendCode = "EVT_TOOL_CALL_MISSING" | "EVT_SEQ_REGRESSION";
54
+
55
+ /** Result of a single append attempt. */
56
+ export type LedgerAppendResult =
57
+ | { ok: true; occurrence: LedgerOccurrence }
58
+ | { ok: false; code: LedgerAppendCode; rejected: LedgerOccurrence };
59
+
60
+ /**
61
+ * Reader capability: read-access only. The dashboard's `GET
62
+ * /api/vector-cortex/ledger` is built on exactly this surface and nothing more —
63
+ * it can read counts/order/digests but never mutate.
64
+ */
65
+ export interface LedgerReader {
66
+ readonly kind: "LedgerReader";
67
+ /** Accept the latest contiguous high-water (durable authority), or 0. */
68
+ highWater(session: string): bigint;
69
+ /** Contiguous accepted occurrences for a session in ascending `(seq,eventId)`. */
70
+ readSession(session: string): readonly LedgerOccurrence[];
71
+ /** Occurrences at or above `fromSeq` (inclusive) for a session. */
72
+ readFrom(session: string, fromSeq: bigint): readonly LedgerOccurrence[];
73
+ /** Count of accepted occurrences for a session. */
74
+ count(session: string): number;
75
+ /** Whether a `(eventId, digest)` pair is already accepted (unique key). */
76
+ hasOccurrence(session: string, eventId: string, digest: string): boolean;
77
+ }
78
+
79
+ /**
80
+ * Writer capability: append-only ingestion. Can create occurrences and advance
81
+ * the per-session seq — but cannot read arbitrary history, run migrations, or
82
+ * touch the compat journal. Enforces monotonic seq and tool-call reference
83
+ * completeness on every append.
84
+ */
85
+ export interface LedgerWriter {
86
+ readonly kind: "LedgerWriter";
87
+ /** Append one occurrence; rejects on seq regression or a dangling tool ref. */
88
+ append(input: {
89
+ readonly session: string;
90
+ readonly seq: bigint;
91
+ readonly eventId: string;
92
+ readonly kind: string;
93
+ readonly toolCallId?: string;
94
+ readonly sourceBytes: Uint8Array;
95
+ readonly digest?: string;
96
+ }): LedgerAppendResult;
97
+ /** Append a batch, all-or-nothing per occurrence (each returns its outcome). */
98
+ appendBatch(inputs: ReadonlyArray<Parameters<LedgerWriter["append"]>[0]>): LedgerAppendResult[];
99
+ }
100
+
101
+ /**
102
+ * Admin capability: maintenance + migration coordination only. The sole surface
103
+ * with access to the compatibility journal switch and to run the M2
104
+ * copy/validate/switch. Never exposed to ingestion or to the dashboard reader.
105
+ */
106
+ export interface LedgerAdmin {
107
+ readonly kind: "LedgerAdmin";
108
+ /** The compatibility journal (downgrade safety). */
109
+ readonly compat: CompatJournalV1;
110
+ /** Prepare + validate + switch the v2 ledger (M2), idempotently. */
111
+ migrateOccurrenceV2(): { ok: boolean; codes: readonly string[] };
112
+ }
113
+
114
+ /**
115
+ * CompatJournalV1 — the downgrade-safety journal. Every accepted v2 append
116
+ * atomically appends a journal record holding the original bytes, IDs, and a
117
+ * legacy projection or an explicit `unrepresentable` marker. The journal records
118
+ * its own lifecycle state (prepared/copied/validated/switched) so M2 is
119
+ * resumable and a stop mid-journal never corrupts v2.
120
+ */
121
+ export interface CompatJournalV1 {
122
+ /** Whether any v2 append has activated the journal (journal present). */
123
+ active(): boolean;
124
+ /**
125
+ * Record a new v2 occurrence into the journal. `legacyProjection` holds the
126
+ * lossless legacy copy, or null when the row has no lossless legacy form — in
127
+ * which case the row is marked `unrepresentable` (e.g. invalid UTF-8 bytes).
128
+ */
129
+ record(input: {
130
+ readonly occurrence: LedgerOccurrence;
131
+ readonly legacyProjection: string | null;
132
+ }): void;
133
+ /** True when a row could not be projected to legacy and is marked. */
134
+ isUnrepresentable(eventId: string, digest: string): boolean;
135
+ /**
136
+ * Prepare a downgrade export: snapshot the journal for the copy/validate/switch
137
+ * lifecycle. Returns the list of unrepresentable rows (EVT/MIG reference).
138
+ */
139
+ prepare(): string[];
140
+ /** Mark the copy phase complete. */
141
+ copied(): void;
142
+ /** Validate the copied legacy export (sequence/digest parity). */
143
+ validate(): { ok: boolean; codes: readonly string[] };
144
+ /** Atomically switch authority to the legacy export (downgrade). */
145
+ switched(): void;
146
+ }
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Factory + capability gating (VC1B)
150
+ // ---------------------------------------------------------------------------
151
+
152
+ import type { DatabaseSync } from "node:sqlite";
153
+ import { VC1B_ENABLED } from "../../config/vector-cortex.js";
154
+ import {
155
+ openOccurrenceStore,
156
+ appendOccurrence,
157
+ ledgerHighWater,
158
+ readSessionOccurrences,
159
+ readFromSeq,
160
+ countOccurrences,
161
+ hasOccurrence,
162
+ } from "./sqlite.js";
163
+ import {
164
+ initCompatJournal,
165
+ createCompatJournal,
166
+ journalPhase,
167
+ } from "./compat-journal.js";
168
+ import {
169
+ m2Copy,
170
+ m2Validate,
171
+ m2Switch,
172
+ type M2Host,
173
+ type LegacyExportRow,
174
+ } from "../migrations/occurrence-v2.js";
175
+
176
+ /** The token that gates closures to a single capability (PREVENT-011-free). */
177
+ const _capability: unique symbol = Symbol("mc-ledger-capability");
178
+
179
+ interface ReaderToken {
180
+ readonly [_capability]: "reader";
181
+ }
182
+ interface WriterToken {
183
+ readonly [_capability]: "writer";
184
+ }
185
+ interface AdminToken {
186
+ readonly [_capability]: "admin";
187
+ }
188
+
189
+ /** A capability-gated ledger handle: access only what you were handed. */
190
+ export interface LedgerHandle {
191
+ readonly reader: () => LedgerReader & ReaderToken;
192
+ readonly writer: () => LedgerWriter & WriterToken;
193
+ readonly admin: () => LedgerAdmin & AdminToken;
194
+ /** Close the underlying DB handle (test/sandbox teardown). */
195
+ readonly close: () => void;
196
+ }
197
+
198
+ /** Optional structured-event emitter (same shape as the other VC seams). */
199
+ export type LedgerEmit = (event: string, fields: Record<string, unknown>) => void;
200
+
201
+ /**
202
+ * Create the capability-separated occurrence-v2 ledger over its OWN isolated
203
+ * SQLite DB. `emit` is optional; occurrence-appended and compat-switch-committed
204
+ * events are only emitted when VC1B_ENABLED() and an emitter is supplied (mode-C
205
+ * parity: flag OFF / no emitter => zero observability writes).
206
+ *
207
+ * Normalization: `stateDir` gives the standard daemon location
208
+ * `<stateDir>/vector-cortex/occurrence-v2.db`; a bare `dbPath` overrides it
209
+ * (tests/rehearsal isolate the ledger). The compat journal shares the same DB
210
+ * so a v2 append + its journal record commit atomically.
211
+ */
212
+ export function createLedgerStore(
213
+ opts: { readonly stateDir: string } | { readonly dbPath: string },
214
+ emit?: LedgerEmit,
215
+ ): LedgerHandle {
216
+ const dbPath =
217
+ "dbPath" in opts
218
+ ? opts.dbPath
219
+ : `${opts.stateDir}/vector-cortex/occurrence-v2.db`;
220
+ const db = openOccurrenceStore(dbPath);
221
+ initCompatJournal(db);
222
+ const compat = createCompatJournal(db);
223
+
224
+ const fire = (event: string, fields: Record<string, unknown>): void => {
225
+ if (!VC1B_ENABLED()) return;
226
+ try {
227
+ emit?.(event, fields);
228
+ } catch {
229
+ /* non-fatal observability — never break the agent loop */
230
+ }
231
+ };
232
+
233
+ const asReader = (): LedgerReader & ReaderToken => ({
234
+ kind: "LedgerReader",
235
+ [_capability]: "reader" as const,
236
+ highWater: (session) => ledgerHighWater(db, session),
237
+ readSession: (session) => readSessionOccurrences(db, session),
238
+ readFrom: (session, fromSeq) => readFromSeq(db, session, fromSeq),
239
+ count: (session) => countOccurrences(db, session),
240
+ hasOccurrence: (session, eventId, digest) =>
241
+ hasOccurrence(db, session, eventId, digest),
242
+ });
243
+
244
+ const asWriter = (): LedgerWriter & WriterToken => ({
245
+ kind: "LedgerWriter",
246
+ [_capability]: "writer" as const,
247
+ append(input) {
248
+ // The occurrence insert and its compat-journal record commit atomically
249
+ // inside one nested savepoint (CONTRACTS §Store: atomically appends).
250
+ db.exec("SAVEPOINT mc_ledger_app");
251
+ try {
252
+ const result = appendOccurrence(db, input);
253
+ if (result.ok) {
254
+ compat.record({
255
+ occurrence: result.occurrence,
256
+ legacyProjection: legacyProjectionOf(result.occurrence),
257
+ });
258
+ }
259
+ if (result.ok) {
260
+ db.exec("RELEASE mc_ledger_app");
261
+ fire("vector_cortex_occurrence_appended", {
262
+ session: result.occurrence.session,
263
+ seq: result.occurrence.seq.toString(),
264
+ eventId: result.occurrence.eventId,
265
+ digest: result.occurrence.digest,
266
+ kind: result.occurrence.kind,
267
+ });
268
+ } else {
269
+ db.exec("ROLLBACK TO mc_ledger_app");
270
+ db.exec("RELEASE mc_ledger_app");
271
+ }
272
+ return result;
273
+ } catch (e) {
274
+ db.exec("ROLLBACK TO mc_ledger_app");
275
+ db.exec("RELEASE mc_ledger_app");
276
+ throw e;
277
+ }
278
+ },
279
+ appendBatch(inputs) {
280
+ const out = [];
281
+ for (const input of inputs) out.push(this.append(input));
282
+ return out;
283
+ },
284
+ });
285
+
286
+ const asAdmin = (): LedgerAdmin & AdminToken => ({
287
+ kind: "LedgerAdmin",
288
+ [_capability]: "admin" as const,
289
+ compat,
290
+ migrateOccurrenceV2() {
291
+ // copy => validate => switch; resumable & idempotent. The M2 host wraps
292
+ // the live DB + journal so every phase transition advances the SAME
293
+ // singleton journal state machine (prepared→copied→validated→switched).
294
+ const unrepresentable = compat.prepare().length;
295
+ const mig = migrateHost(db, compat);
296
+ m2Copy(mig);
297
+ const v = m2Validate(mig);
298
+ if (v.ok) {
299
+ m2Switch(mig);
300
+ fire("vector_cortex_compat_switch_committed", {
301
+ session: "all",
302
+ unrepresentable,
303
+ });
304
+ }
305
+ return { ok: v.ok, codes: v.codes };
306
+ },
307
+ });
308
+
309
+ return { reader: asReader, writer: asWriter, admin: asAdmin, close: () => db.close() };
310
+ }
311
+
312
+ /**
313
+ * Lossless legacy projection for a v2 occurrence: its identity envelope plus
314
+ * base64 source bytes. Returns null (→ journal `unrepresentable` marker) when the
315
+ * source bytes do not form valid UTF-8, which the legacy pipeline cannot round-trip
316
+ * losslessly — those rows are listed rather than silently coerced (MIG-DOWN-003).
317
+ */
318
+ function legacyProjectionOf(occ: LedgerOccurrence): string | null {
319
+ const source = Buffer.from(occ.sourceBytes);
320
+ try {
321
+ new TextDecoder("utf-8", { fatal: true }).decode(source);
322
+ } catch {
323
+ return null;
324
+ }
325
+ return JSON.stringify({
326
+ session: occ.session,
327
+ eventId: occ.eventId,
328
+ kind: occ.kind,
329
+ digest: occ.digest,
330
+ source: source.toString("base64"),
331
+ });
332
+ }
333
+
334
+ /** Build an M2Host over the live ledger DB + journal (for migrateOccurrenceV2). */
335
+ function migrateHost(
336
+ db: DatabaseSync,
337
+ journal: CompatJournalV1,
338
+ ): M2Host {
339
+ let staged: readonly LegacyExportRow[] | null = null;
340
+ return {
341
+ db,
342
+ phase: () => journalPhase(db),
343
+ journalActive: () => journal.active(),
344
+ journalRows: () => journalRowsOf(db),
345
+ writeStagedLegacy: (rows) => {
346
+ staged = rows;
347
+ journal.copied();
348
+ },
349
+ stagedLegacy: () => staged,
350
+ validateStaged: () => journal.validate(),
351
+ switchLegacy: () => journal.switched(),
352
+ };
353
+ }
354
+
355
+ /** Read the journaled occurrences as legacy-export rows. */
356
+ function journalRowsOf(db: DatabaseSync): readonly LegacyExportRow[] {
357
+ const rows = db
358
+ .prepare(
359
+ `SELECT session, seq, event_id, digest, kind, legacy_projection, unrepresentable
360
+ FROM compat_journal_v1 ORDER BY session ASC, seq ASC`,
361
+ )
362
+ .all() as unknown as Array<{
363
+ session: string;
364
+ seq: number;
365
+ event_id: string;
366
+ digest: string;
367
+ kind: string;
368
+ legacy_projection: string | null;
369
+ unrepresentable: number;
370
+ }>;
371
+ return rows.map((r) => ({
372
+ session: r.session,
373
+ seq: BigInt(r.seq),
374
+ eventId: r.event_id,
375
+ digest: r.digest,
376
+ kind: r.kind,
377
+ legacyProjection: r.legacy_projection,
378
+ unrepresentable: r.unrepresentable === 1,
379
+ }));
380
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * vector-cortex/ledger/types.ts — EventV2 / EventCodec byte-authority contract
3
+ * types, validation result codes, and the registered EVT conformance ID range.
4
+ *
5
+ * Owned by VC1A (canonical byte events). Consumes only reviewer-accepted
6
+ * predecessor contracts and [common contracts](../../CONTRACTS.md §EventV2),
7
+ * which are NORMATIVE here. `originalBytes` and its SHA-256 digest are the byte
8
+ * authority; strict UTF-8 classification is never lossy; `canonicalNfc` is a
9
+ * DERIVED comparison/search key only (never identity, digest, or reconstruction).
10
+ *
11
+ * Pure type/schema definitions + a short pure digest/classify predicate runtime
12
+ * (validator codes). No network, no side effects (PREVENT-PI-004 / PREVENT-011).
13
+ */
14
+
15
+ /** SHA-256 digest over the authoritative `originalBytes`, `sha256:<hex>`. */
16
+ export type BytesDigest = `sha256:${string}`;
17
+
18
+ /**
19
+ * EventV2 — the neutral byte-authority ledger occurrence. `schema:"event-v2"`
20
+ * is the discriminant tag of the union. Two events whose bytes differ by NFC
21
+ * normalization are DISTINCT identities (different `originalBytes`, different
22
+ * `bytesDigest`); their `canonicalNfc` (derived) may coincide but never drives
23
+ * equality, hashing, or byte reconstruction.
24
+ */
25
+ export interface EventV2 {
26
+ schema: "event-v2";
27
+ sessionId: string;
28
+ seq: bigint;
29
+ eventId: string;
30
+ role: "policy" | "user" | "assistant" | "tool";
31
+ kind: string;
32
+ /** Authoritative original event bytes (byte authority). */
33
+ originalBytes: Uint8Array;
34
+ /** DigitalObjectIdentifier over originalBytes (authoritative digest). */
35
+ bytesDigest: BytesDigest;
36
+ /**
37
+ * Strict UTF-8 classification (NO lossy replacement). Invalid input is
38
+ * represented only as `{valid:false, base64}`.
39
+ */
40
+ utf8: { valid: true; text: string } | { valid: false; base64: string };
41
+ /** DERIVED NFC comparison/search key, valid UTF-8 only. Never identity. */
42
+ canonicalNfc?: string;
43
+ /** On a tool RESULT, references exactly one earlier CALL in this session. */
44
+ toolCallId?: string;
45
+ /** Wall-clock occurrence timestamp (monotonic for ordering; not the sort key). */
46
+ occurredAtMs: bigint;
47
+ }
48
+
49
+ /** Inputs to the byte-authority encoder. `bytes` is the sole byte source. */
50
+ export interface EventEncodeInput {
51
+ sessionId: string;
52
+ seq: bigint;
53
+ eventId: string;
54
+ role: "policy" | "user" | "assistant" | "tool";
55
+ kind: string;
56
+ bytes: Uint8Array;
57
+ toolCallId?: string;
58
+ occurredAtMs: bigint;
59
+ }
60
+
61
+ /**
62
+ * EventCodec — byte-authority contract (normative, [CONTRACTS §EventV2]).
63
+ * `encode` computes the SHA-256 digest + strict UTF-8 classification + derived
64
+ * NFC; `decode` returns the authoritative `originalBytes` unchanged. Decoding
65
+ * never reconstructs bytes from normalized text — only from originalBytes.
66
+ */
67
+ export interface EventCodec {
68
+ encode(input: EventEncodeInput): EventV2;
69
+ decode(event: EventV2): Uint8Array;
70
+ /**
71
+ * Strict UTF-8 classification: `fatal` decode succeeds, or the raw bytes are
72
+ * reported as `{valid:false, base64}` — never replacement-decode to U+FFFD.
73
+ */
74
+ classifyUtf8(bytes: Uint8Array): { valid: true; text: string } | { valid: false; base64: string };
75
+ }
76
+
77
+ /** Canonical validator failure codes (VC1A). */
78
+ export type ValidationCode =
79
+ /** sha256(originalBytes) !== bytesDigest (authority corruption). */
80
+ | "EVT_DIGEST_MISMATCH"
81
+ /** The stored `utf8` discriminant contradicts a strict re-classification. */
82
+ | "EVT_UTF8_TAG_INVALID"
83
+ /** Duplicate (sessionId, seq, eventId) occurrence. */
84
+ | "EVT_DUPLICATE_ID";
85
+
86
+ /**
87
+ * Deterministic validation result. `ok:false` carries the deduplicated failure
88
+ * codes (fixed priority order) AND the per-occurrence `issues` with real
89
+ * locators (sessionId/seq/eventId) so consumers can identify WHICH event failed.
90
+ */
91
+ export type ValidationResult =
92
+ | { ok: true; ordered: readonly EventV2[] }
93
+ | { ok: false; codes: readonly ValidationCode[]; issues: readonly ValidationIssue[] };
94
+
95
+ /** A single flagged validation issue (code + the offending occurrence locator). */
96
+ export interface ValidationIssue {
97
+ readonly code: ValidationCode;
98
+ readonly sessionId: string;
99
+ readonly seq: bigint;
100
+ readonly eventId: string;
101
+ }
102
+
103
+ /**
104
+ * Registered EVT conformance ID range (EVT-001..030). The acceptance test reads
105
+ * these rows from the v2 manifest and asserts each returns its manifest bytes or
106
+ * exactly its listed failure code. Mirrors CUT_IDS / M3_IDS in replay/types.ts.
107
+ * EVT-001..015 are owned by VC1A (codec/validator); EVT-016..030 are owned by
108
+ * VC1B (occurrence ledger, see ledger/store.ts).
109
+ */
110
+ export const EVT_IDS = [
111
+ "EVT-001",
112
+ "EVT-002",
113
+ "EVT-003",
114
+ "EVT-004",
115
+ "EVT-005",
116
+ "EVT-006",
117
+ "EVT-007",
118
+ "EVT-008",
119
+ "EVT-009",
120
+ "EVT-010",
121
+ "EVT-011",
122
+ "EVT-012",
123
+ "EVT-013",
124
+ "EVT-014",
125
+ "EVT-015",
126
+ "EVT-016",
127
+ "EVT-017",
128
+ "EVT-018",
129
+ "EVT-019",
130
+ "EVT-020",
131
+ "EVT-021",
132
+ "EVT-022",
133
+ "EVT-023",
134
+ "EVT-024",
135
+ "EVT-025",
136
+ "EVT-026",
137
+ "EVT-027",
138
+ "EVT-028",
139
+ "EVT-029",
140
+ "EVT-030",
141
+ ] as const;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * vector-cortex/ledger/validator.ts — EventV2 canonical validation (VC1A).
3
+ *
4
+ * Canonical ordering is `(sessionId, seq, eventId bytewise UTF-8)`: the sort is
5
+ * by the eventId's UTF-8 BYTES (unsigned) — NOT by JS string code-unit order —
6
+ * so multi-byte eventIds sort bytewise. Deterministic failure detection with a
7
+ * fixed priority order:
8
+ * 1. EVT_DIGEST_MISMATCH — sha256(originalBytes) !== bytesDigest
9
+ * 2. EVT_UTF8_TAG_INVALID — stored `utf8` discriminant contradicting a strict
10
+ * fatal re-classification of originalBytes
11
+ * 3. EVT_DUPLICATE_ID — duplicate (sessionId, seq, eventId) occurrence
12
+ *
13
+ * The unique failure injection requirement: flip ONE stored byte while retaining
14
+ * the SHA-256 signature — the digest recomputation diverges and the validator
15
+ * returns EVT_DIGEST_MISMATCH (never a lossy replacement text, which is not
16
+ * produced anywhere in this module).
17
+ *
18
+ * Pure/deterministic — no console, no network, no side effects (PREVENT-PI-004).
19
+ */
20
+
21
+ import nodeCrypto from "node:crypto";
22
+ import { classifyUtf8 } from "./event-codec.js";
23
+ import type { BytesDigest, EventV2, ValidationCode, ValidationIssue, ValidationResult } from "./types.js";
24
+
25
+ /** Mode-A digest recompute (validator uses the codec's canonical digest rule). */
26
+ function digestOf(bytes: Uint8Array): BytesDigest {
27
+ const hex = nodeCrypto.createHash("sha256").update(bytes).digest("hex");
28
+ return `sha256:${hex}`;
29
+ }
30
+
31
+ /** Bytewise UTF-8 comparator for eventId tiebreak (unsigned bytes, not code units). */
32
+ function compareEventIdBytes(a: string, b: string): number {
33
+ const ab = Buffer.from(a, "utf8");
34
+ const bb = Buffer.from(b, "utf8");
35
+ const n = Math.min(ab.length, bb.length);
36
+ for (let i = 0; i < n; i++) {
37
+ if (ab[i] !== bb[i]) return ab[i] < bb[i] ? -1 : 1;
38
+ }
39
+ if (ab.length !== bb.length) return ab.length < bb.length ? -1 : 1;
40
+ return 0;
41
+ }
42
+
43
+ /** Comparator: `(sessionId, seq, eventId bytewise UTF-8)`. */
44
+ export function compareEvents(a: EventV2, b: EventV2): number {
45
+ if (a.sessionId !== b.sessionId) return a.sessionId < b.sessionId ? -1 : 1;
46
+ if (a.seq !== b.seq) return a.seq < b.seq ? -1 : 1;
47
+ return compareEventIdBytes(a.eventId, b.eventId);
48
+ }
49
+
50
+ /** Ascending (sessionId, seq, eventId-bytes) sort. Returns a new array. */
51
+ export function sortEvents(events: readonly EventV2[]): EventV2[] {
52
+ return [...events].sort(compareEvents);
53
+ }
54
+
55
+ /** Recompute the digest and compare to the stored one (authority corruption). */
56
+ function digestMismatch(event: EventV2): boolean {
57
+ return digestOf(event.originalBytes) !== event.bytesDigest;
58
+ }
59
+
60
+ /** The stored utf8 discriminant must match a strict fatal re-classification. */
61
+ function utf8TagMismatch(event: EventV2): boolean {
62
+ const cls = classifyUtf8(event.originalBytes);
63
+ const stored = event.utf8;
64
+ if (stored.valid !== cls.valid) return true;
65
+ if (!stored.valid) {
66
+ // Both invalid: never compare decoded text; only the (valid, base64) shape.
67
+ return false;
68
+ }
69
+ if (!cls.valid) return true; // unreachable given `stored.valid === cls.valid`
70
+ // Both valid: compare the strict-decoded TEXT (no NFC — the tag stores raw text).
71
+ return stored.text !== cls.text;
72
+ }
73
+
74
+ /**
75
+ * Validate a batch of events. Returns `{ok:true, ordered}` when every event
76
+ * passes digest + UTF-8-tag consistency and no duplicate occurrence exists;
77
+ * otherwise `{ok:false, codes}` with the failure codes in fixed priority order,
78
+ * deduplicated, and an explicit issue list for diagnostics.
79
+ */
80
+ export function validateEvents(events: readonly EventV2[]): ValidationResult {
81
+ const issues: ValidationIssue[] = [];
82
+
83
+ // Fixed priority order: DIGEST_MISMATCH, UTF8_TAG_INVALID, DUPLICATE_ID.
84
+ for (const e of events) {
85
+ if (digestMismatch(e)) {
86
+ issues.push({ code: "EVT_DIGEST_MISMATCH", sessionId: e.sessionId, seq: e.seq, eventId: e.eventId });
87
+ }
88
+ }
89
+ for (const e of events) {
90
+ if (utf8TagMismatch(e)) {
91
+ issues.push({ code: "EVT_UTF8_TAG_INVALID", sessionId: e.sessionId, seq: e.seq, eventId: e.eventId });
92
+ }
93
+ }
94
+
95
+ // Unambiguous (sessionId, seq, eventId) occurrence key (seq stringified — a
96
+ // bigint cannot be JSON-serialized directly).
97
+ const key = (e: EventV2): string => JSON.stringify([e.sessionId, e.seq.toString(), e.eventId]);
98
+ const seen = new Set<string>();
99
+ for (const e of sortEvents(events)) {
100
+ const k = key(e);
101
+ if (seen.has(k)) {
102
+ issues.push({ code: "EVT_DUPLICATE_ID", sessionId: e.sessionId, seq: e.seq, eventId: e.eventId });
103
+ }
104
+ seen.add(k);
105
+ }
106
+
107
+ if (issues.length === 0) {
108
+ return { ok: true, ordered: sortEvents(events) };
109
+ }
110
+ // Surface the per-occurrence issues (real locators) alongside the deduped
111
+ // codes so the adapter/observability feed can name WHICH event failed.
112
+ return { ok: false, codes: dedupe(issues.map((i) => i.code)), issues };
113
+ }
114
+
115
+ function dedupe(codes: ValidationCode[]): ValidationCode[] {
116
+ const out: ValidationCode[] = [];
117
+ for (const c of codes) {
118
+ if (!out.includes(c)) out.push(c);
119
+ }
120
+ return out;
121
+ }
122
+
123
+ /** Export the canonical ordering comparator for consumers (replay etc.). */
124
+ export { compareEventIdBytes };