pi-mega-compact 0.16.3 → 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 (166) 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/vector-cortex/eval/annotations.js +41 -0
  15. package/dist/src/vector-cortex/eval/metrics.js +86 -0
  16. package/dist/src/vector-cortex/eval/observer.js +61 -0
  17. package/dist/src/vector-cortex/eval/persist.js +67 -0
  18. package/dist/src/vector-cortex/eval/reader.js +29 -0
  19. package/dist/src/vector-cortex/eval/types.js +33 -0
  20. package/dist/src/vector-cortex/ledger/adapter.js +55 -0
  21. package/dist/src/vector-cortex/ledger/compat-journal.js +206 -0
  22. package/dist/src/vector-cortex/ledger/emit.js +53 -0
  23. package/dist/src/vector-cortex/ledger/event-codec.js +72 -0
  24. package/dist/src/vector-cortex/ledger/event-codecB.js +76 -0
  25. package/dist/src/vector-cortex/ledger/sqlite.js +200 -0
  26. package/dist/src/vector-cortex/ledger/store.js +184 -0
  27. package/dist/src/vector-cortex/ledger/types.js +52 -0
  28. package/dist/src/vector-cortex/ledger/validator.js +117 -0
  29. package/dist/src/vector-cortex/migrations/effective-cut-v2.js +103 -0
  30. package/dist/src/vector-cortex/migrations/occurrence-v2.js +139 -0
  31. package/dist/src/vector-cortex/replay/cut.js +123 -0
  32. package/dist/src/vector-cortex/replay/emit.js +58 -0
  33. package/dist/src/vector-cortex/replay/replay.js +260 -0
  34. package/dist/src/vector-cortex/replay/replayB.js +73 -0
  35. package/dist/src/vector-cortex/replay/types.js +52 -0
  36. package/dist/src/vector-cortex/resilience/breaker-core.js +359 -0
  37. package/dist/src/vector-cortex/resilience/breaker.js +12 -0
  38. package/dist/src/vector-cortex/resilience/emit.js +56 -0
  39. package/dist/src/vector-cortex/resilience/spool-core.js +347 -0
  40. package/dist/src/vector-cortex/resilience/spool.js +12 -0
  41. package/dist/src/vector-cortex/resilience/types.js +32 -0
  42. package/dist/vector-cortex/eval/annotations.js +41 -0
  43. package/dist/vector-cortex/eval/metrics.js +86 -0
  44. package/dist/vector-cortex/eval/observer.js +61 -0
  45. package/dist/vector-cortex/eval/persist.js +67 -0
  46. package/dist/vector-cortex/eval/reader.js +29 -0
  47. package/dist/vector-cortex/eval/types.js +33 -0
  48. package/dist/vector-cortex/ledger/adapter.js +55 -0
  49. package/dist/vector-cortex/ledger/compat-journal.js +206 -0
  50. package/dist/vector-cortex/ledger/emit.js +53 -0
  51. package/dist/vector-cortex/ledger/event-codec.js +72 -0
  52. package/dist/vector-cortex/ledger/event-codecB.js +76 -0
  53. package/dist/vector-cortex/ledger/sqlite.js +200 -0
  54. package/dist/vector-cortex/ledger/store.js +184 -0
  55. package/dist/vector-cortex/ledger/types.js +52 -0
  56. package/dist/vector-cortex/ledger/validator.js +117 -0
  57. package/dist/vector-cortex/migrations/effective-cut-v2.js +103 -0
  58. package/dist/vector-cortex/migrations/occurrence-v2.js +139 -0
  59. package/dist/vector-cortex/replay/cut.js +123 -0
  60. package/dist/vector-cortex/replay/emit.js +58 -0
  61. package/dist/vector-cortex/replay/replay.js +260 -0
  62. package/dist/vector-cortex/replay/replayB.js +73 -0
  63. package/dist/vector-cortex/replay/types.js +52 -0
  64. package/dist/vector-cortex/resilience/breaker-core.js +359 -0
  65. package/dist/vector-cortex/resilience/breaker.js +12 -0
  66. package/dist/vector-cortex/resilience/emit.js +56 -0
  67. package/dist/vector-cortex/resilience/spool-core.js +347 -0
  68. package/dist/vector-cortex/resilience/spool.js +12 -0
  69. package/dist/vector-cortex/resilience/types.js +32 -0
  70. package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js → AreaChart-CAnWomhw.js} +2 -2
  71. package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js.map → AreaChart-CAnWomhw.js.map} +1 -1
  72. package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js → BarChart-lZ2-s71k.js} +2 -2
  73. package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js.map → BarChart-lZ2-s71k.js.map} +1 -1
  74. package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js → CacheTab-BL2Thh66.js} +2 -2
  75. package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js.map → CacheTab-BL2Thh66.js.map} +1 -1
  76. package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js → EventsTab-DEh2Z9fp.js} +2 -2
  77. package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js.map → EventsTab-DEh2Z9fp.js.map} +1 -1
  78. package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js → HealthTab-DTwKclxk.js} +2 -2
  79. package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js.map → HealthTab-DTwKclxk.js.map} +1 -1
  80. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js → MaintenanceTab-DDfBEdbX.js} +2 -2
  81. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js.map → MaintenanceTab-DDfBEdbX.js.map} +1 -1
  82. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js → MemoryMapTab-Dgfmg-0Q.js} +2 -2
  83. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js.map → MemoryMapTab-Dgfmg-0Q.js.map} +1 -1
  84. package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js → MetricsTab-DOcMm0Tx.js} +2 -2
  85. package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js.map → MetricsTab-DOcMm0Tx.js.map} +1 -1
  86. package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js → OverviewTab-DHT5aoHg.js} +2 -2
  87. package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js.map → OverviewTab-DHT5aoHg.js.map} +1 -1
  88. package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js → ReposTab-CGAmtR_c.js} +2 -2
  89. package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js.map → ReposTab-CGAmtR_c.js.map} +1 -1
  90. package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js → SessionsTab-BIgx6iKT.js} +2 -2
  91. package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js.map → SessionsTab-BIgx6iKT.js.map} +1 -1
  92. package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js → SetupTab-ADghKuEI.js} +2 -2
  93. package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js.map → SetupTab-ADghKuEI.js.map} +1 -1
  94. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js → TimeSavedCard-C33UiiOM.js} +2 -2
  95. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js.map → TimeSavedCard-C33UiiOM.js.map} +1 -1
  96. package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js → TurnsTab-DdbL9wPN.js} +2 -2
  97. package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js.map → TurnsTab-DdbL9wPN.js.map} +1 -1
  98. package/extensions/dashboard-client/dist/assets/VectorCortexTab-Cl1pbEdR.js +2 -0
  99. package/extensions/dashboard-client/dist/assets/VectorCortexTab-Cl1pbEdR.js.map +1 -0
  100. package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js → WikiTab-Ch00OFKs.js} +2 -2
  101. package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js.map → WikiTab-Ch00OFKs.js.map} +1 -1
  102. package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js → button-ByeEdo2g.js} +2 -2
  103. package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js.map → button-ByeEdo2g.js.map} +1 -1
  104. package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js → card-DesoLXvc.js} +2 -2
  105. package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js.map → card-DesoLXvc.js.map} +1 -1
  106. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js → generateCategoricalChart-CCAB7Iz4.js} +2 -2
  107. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js.map → generateCategoricalChart-CCAB7Iz4.js.map} +1 -1
  108. package/extensions/dashboard-client/dist/assets/{index-B93cewiX.js → index-B6cgyAPr.js} +34 -29
  109. package/extensions/dashboard-client/dist/assets/index-B6cgyAPr.js.map +1 -0
  110. package/extensions/dashboard-client/dist/assets/index-CfvziyBi.css +1 -0
  111. package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js → switch-DIZC-oe-.js} +2 -2
  112. package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js.map → switch-DIZC-oe-.js.map} +1 -1
  113. package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js → toggle-CIoWaH2e.js} +2 -2
  114. package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js.map → toggle-CIoWaH2e.js.map} +1 -1
  115. package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js → useSSE-Bv4943l1.js} +2 -2
  116. package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js.map → useSSE-Bv4943l1.js.map} +1 -1
  117. package/extensions/dashboard-client/dist/index.html +2 -2
  118. package/extensions/dashboard-client/src/App.tsx +2 -0
  119. package/extensions/dashboard-client/src/api/vector-cortex.ts +54 -0
  120. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +272 -0
  121. package/extensions/dashboard-client/src/tabs/registry.ts +4 -1
  122. package/extensions/dashboard-client/src/types/vector-cortex.ts +84 -0
  123. package/extensions/dashboard-server/api-contracts/endpoints/registry-ext.ts +67 -0
  124. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +4 -21
  125. package/extensions/dashboard-server/api-contracts/index.ts +3 -0
  126. package/extensions/dashboard-server/api-contracts/vector-cortex.ts +136 -0
  127. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +35 -0
  128. package/extensions/dashboard-server/routes-vector-cortex.ts +332 -0
  129. package/extensions/dashboard-server/routes.ts +6 -0
  130. package/extensions/dashboard-server/server.ts +8 -0
  131. package/extensions/mega-events/context-handler.ts +16 -0
  132. package/extensions/mega-runtime/vector-cortex-ledger.ts +165 -0
  133. package/extensions/mega-runtime/vector-cortex-safety.ts +181 -0
  134. package/package.json +2 -1
  135. package/src/config/vector-cortex.ts +87 -0
  136. package/src/config.ts +27 -0
  137. package/src/vector-cortex/eval/annotations.ts +70 -0
  138. package/src/vector-cortex/eval/metrics.ts +113 -0
  139. package/src/vector-cortex/eval/observer.ts +86 -0
  140. package/src/vector-cortex/eval/persist.ts +73 -0
  141. package/src/vector-cortex/eval/reader.ts +45 -0
  142. package/src/vector-cortex/eval/types.ts +89 -0
  143. package/src/vector-cortex/ledger/adapter.ts +71 -0
  144. package/src/vector-cortex/ledger/compat-journal.ts +234 -0
  145. package/src/vector-cortex/ledger/emit.ts +86 -0
  146. package/src/vector-cortex/ledger/event-codec.ts +79 -0
  147. package/src/vector-cortex/ledger/event-codecB.ts +87 -0
  148. package/src/vector-cortex/ledger/sqlite.ts +265 -0
  149. package/src/vector-cortex/ledger/store.ts +380 -0
  150. package/src/vector-cortex/ledger/types.ts +141 -0
  151. package/src/vector-cortex/ledger/validator.ts +124 -0
  152. package/src/vector-cortex/migrations/effective-cut-v2.ts +149 -0
  153. package/src/vector-cortex/migrations/occurrence-v2.ts +192 -0
  154. package/src/vector-cortex/replay/cut.ts +169 -0
  155. package/src/vector-cortex/replay/emit.ts +89 -0
  156. package/src/vector-cortex/replay/replay.ts +308 -0
  157. package/src/vector-cortex/replay/replayB.ts +97 -0
  158. package/src/vector-cortex/replay/types.ts +145 -0
  159. package/src/vector-cortex/resilience/breaker-core.ts +444 -0
  160. package/src/vector-cortex/resilience/breaker.ts +22 -0
  161. package/src/vector-cortex/resilience/emit.ts +99 -0
  162. package/src/vector-cortex/resilience/spool-core.ts +411 -0
  163. package/src/vector-cortex/resilience/spool.ts +22 -0
  164. package/src/vector-cortex/resilience/types.ts +158 -0
  165. package/extensions/dashboard-client/dist/assets/index-B93cewiX.js.map +0 -1
  166. package/extensions/dashboard-client/dist/assets/index-C_tCbOI0.css +0 -1
@@ -0,0 +1,45 @@
1
+ /**
2
+ * vector-cortex/eval/reader.ts — evaluation reader (aggregate-only).
3
+ *
4
+ * Reader-only capability: `summarizeEvalRows` collapses a set of MetricEventV1
5
+ * rows into an aggregate summary for the dashboard `GET /api/vector-cortex/
6
+ * evaluation` endpoint. No writable capability — the observer writes via its
7
+ * own path; this reader never mutates. Predecessor (flag-off / mode C) emits
8
+ * an empty summary byte-identical to "no data".
9
+ *
10
+ * Pure + local, zero network (PREVENT-PI-004).
11
+ */
12
+
13
+ import { buildMetrics } from "./metrics.js";
14
+ import type { MetricEventV1 } from "./types.js";
15
+
16
+ export interface EvalSummary {
17
+ /** Samples aggregated. */
18
+ readonly samples: number;
19
+ /** Per-mode distribution (A/B/C). */
20
+ readonly byMode: Readonly<{ A: number; B: number; C: number }>;
21
+ /** Fixed latency histogram with its inclusive edges + separate overflow. */
22
+ readonly histogram: {
23
+ readonly edges: readonly number[];
24
+ readonly cells: readonly number[];
25
+ readonly overflow: number;
26
+ readonly total: number;
27
+ };
28
+ }
29
+
30
+ /** Aggregate rows into a reader-only summary (histogram + per-mode counts). */
31
+ export function summarizeEvalRows(rows: readonly MetricEventV1[]): EvalSummary {
32
+ const byMode = { A: 0, B: 0, C: 0 };
33
+ for (const r of rows) byMode[r.mode] += 1;
34
+ const metrics = buildMetrics(rows as MetricEventV1[]);
35
+ return {
36
+ samples: rows.length,
37
+ byMode,
38
+ histogram: {
39
+ edges: metrics.histogram.edges,
40
+ cells: metrics.histogram.cells,
41
+ overflow: metrics.histogram.overflow,
42
+ total: metrics.histogram.total,
43
+ },
44
+ };
45
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * vector-cortex/eval/types.ts — MetricEventV1 / AnnotationV1 contract types
3
+ * and the registered EVAL conformance ID range (EVAL-001..010).
4
+ *
5
+ * Owned by VC0A (baseline observability). Consumes reviewer-accepted predecessor
6
+ * contracts and common contracts only. These are pure type/schema definitions —
7
+ * no runtime network, no side effects (PREVENT-PI-004 / PREVENT-011).
8
+ */
9
+
10
+ /**
11
+ * A single structured evaluation-sample observation.
12
+ *
13
+ * Ordering contract: canonical JSONL metric order is `(session, seq, event)`,
14
+ * i.e. stable sort by session, then integer seq, then event-name byte order.
15
+ */
16
+ export interface MetricEventV1 {
17
+ /** Session identifier (opaque). */
18
+ readonly session: string;
19
+ /** Monotonic per-session sequence number (must not decrease within a session). */
20
+ readonly seq: number;
21
+ /** Event/measurement name (deterministic tiebreak within equal seq). */
22
+ readonly event: string;
23
+ /** Numeric value of the sample in `unit`. */
24
+ readonly value: number;
25
+ /** Measurement unit — must be a known unit (see UNITS); rejects unknowns. */
26
+ readonly unit: string;
27
+ /** Triad observer mode that produced this sample: "A" | "B" | "C". */
28
+ readonly mode: "A" | "B" | "C";
29
+ }
30
+
31
+ /** Units the evaluator understands. Unknown units reject as EVAL_UNIT_UNKNOWN. */
32
+ export const UNITS = ["ms", "bytes", "count", "ratio"] as const;
33
+
34
+ /** A single non-monotonic or unknown-unit rejection result. */
35
+ export type EvalReject =
36
+ | { readonly code: "EVAL_ORDER_INVALID"; readonly seq: number; readonly session: string }
37
+ | { readonly code: "EVAL_UNIT_UNKNOWN"; readonly unit: string }
38
+ | { readonly code: "EVAL_JSONL_TRUNCATED" };
39
+
40
+ /**
41
+ * Histogram bucket edges for latency, in milliseconds, inclusive on both
42
+ * boundaries (EVAL-BUCKET-001): values at exactly 1ms and 250ms land in
43
+ * those buckets. Overflow (value > largest edge) is kept separate.
44
+ */
45
+ export const LATENCY_BUCKETS = [1, 5, 10, 25, 50, 100, 250] as const;
46
+
47
+ /**
48
+ * AnnotationV1 redaction metadata. Before JSONL serialization, payload bytes,
49
+ * prompts, and exact ledger text are replaced by digest/count metadata so the
50
+ * exact content never appears in the serialized stream (EVAL-REDACT-002).
51
+ */
52
+ export interface AnnotationV1 {
53
+ /** Annotation identifier (items are addressed via the corpus manifest). */
54
+ readonly itemId: string;
55
+ /** Redaction journal of every payload/prompt/ledger-text field redacted. */
56
+ readonly redactions: ReadonlyArray<{
57
+ /** Field name in the original annotation that was redacted. */
58
+ readonly field: string;
59
+ /** SHA-256 digest of the redacted bytes (standard base64; decodes to the
60
+ * same hash as the conformance fixture's `digestHex` — see annotations.test). */
61
+ readonly digest: string;
62
+ /** Byte length of the redacted content, when known. */
63
+ readonly bytes: number;
64
+ /** Redaction reason: "payload" | "prompt" | "ledger" (all are digested). */
65
+ readonly kind: "payload" | "prompt" | "ledger";
66
+ }>;
67
+ /** Count of redacted occurrences across the annotation. */
68
+ readonly redactedCount: number;
69
+ }
70
+
71
+ /**
72
+ * Registered EVAL conformance ID range (EVAL-001..010). The acceptance test
73
+ * reads these rows from the v2 manifest and asserts their expected bytes/results.
74
+ * Registered BEFORE evaluation logic so the manifest/fixture seam is stable.
75
+ */
76
+ export const EVAL_IDS = [
77
+ "EVAL-001",
78
+ "EVAL-002",
79
+ "EVAL-003",
80
+ "EVAL-004",
81
+ "EVAL-005",
82
+ "EVAL-006",
83
+ "EVAL-007",
84
+ "EVAL-008",
85
+ "EVAL-009",
86
+ "EVAL-010",
87
+ ] as const;
88
+
89
+ export type EvalId = (typeof EVAL_IDS)[number];
@@ -0,0 +1,71 @@
1
+ /**
2
+ * vector-cortex/ledger/adapter.ts — EventV2 ledger adapter (VC1A, task 5).
3
+ *
4
+ * A thin, pi-agnostic adapter combining the Mode-A codec, the canonical
5
+ * validator, and the VC1A emit seam so `vector_cortex_event_decoded` and
6
+ * `vector_cortex_event_validation_failed` are emitted by REAL code paths (not a
7
+ * hand-invoked reporter), gated by `VC1A_ENABLED()` inside `createLedgerReporter`.
8
+ *
9
+ * The live producer (wiring this into `extensions/mega-compact.ts` /
10
+ * `src/engine.ts`) is deferred to VC1C/VC1B (mirroring VC0B-I08); this sprint
11
+ * ships the seam + adapter as the clean single surface, with the acceptance
12
+ * aggregator exercising the real gated-emission path.
13
+ *
14
+ * Pure orchestration — no storage, no network, no side effects (PREVENT-PI-004).
15
+ */
16
+
17
+ import { createEventCodec } from "./event-codec.js";
18
+ import { validateEvents } from "./validator.js";
19
+ import { createLedgerReporter, type LedgerReporter } from "./emit.js";
20
+ import type { EventCodec, EventEncodeInput, EventV2, ValidationResult } from "./types.js";
21
+
22
+ export interface LedgerAdapter {
23
+ /** Encode raw bytes into a V2 occurrence (digest + strict UTF-8 + NFC derived). */
24
+ readonly encode: (input: EventEncodeInput) => EventV2;
25
+ /** Byte-decode an occurrence, emitting `vector_cortex_event_decoded`. */
26
+ readonly decode: (event: EventV2) => Uint8Array;
27
+ /** Canonical-validate a batch, emitting `..._validation_failed` on failure. */
28
+ readonly validate: (events: readonly EventV2[]) => ValidationResult;
29
+ /** The underlying codec (for tests that need a bare codec). */
30
+ readonly codec: EventCodec;
31
+ /** The underlying reporter (flag OFF emits nothing). */
32
+ readonly reporter: LedgerReporter;
33
+ }
34
+
35
+ /** Build the ledger adapter over an injected emit callback (optional). */
36
+ export function createLedgerAdapter(emit?: (event: string, fields: Record<string, unknown>) => void): LedgerAdapter {
37
+ const codec = createEventCodec();
38
+ const reporter = createLedgerReporter(emit);
39
+ return {
40
+ encode: (input) => codec.encode(input),
41
+ decode(event) {
42
+ const bytes = codec.decode(event);
43
+ reporter.eventDecoded({
44
+ session: event.sessionId,
45
+ seq: event.seq.toString(),
46
+ eventId: event.eventId,
47
+ bytes: bytes.length,
48
+ utf8Valid: event.utf8.valid,
49
+ });
50
+ return bytes;
51
+ },
52
+ validate(events) {
53
+ const result = validateEvents(events);
54
+ if (!result.ok) {
55
+ // Emit one event per failing occurrence with its REAL locator — a
56
+ // consumer of the feed can identify WHICH event failed (VC1A-I05).
57
+ for (const issue of result.issues) {
58
+ reporter.validationFailed({
59
+ session: issue.sessionId,
60
+ seq: issue.seq.toString(),
61
+ eventId: issue.eventId,
62
+ code: issue.code,
63
+ });
64
+ }
65
+ }
66
+ return result;
67
+ },
68
+ codec,
69
+ reporter,
70
+ };
71
+ }
@@ -0,0 +1,234 @@
1
+ /**
2
+ * vector-cortex/ledger/compat-journal.ts — CompatJournalV1 (VC1B, task 4).
3
+ *
4
+ * The downgrade-safety journal. From v2 activation every accepted v2 append
5
+ * atomically appends a record holding the original bytes, IDs, and a legacy
6
+ * projection or an explicit `unrepresentable` marker. The journal tracks its own
7
+ * lifetime state (prepared → copied → validated → switched) so the M2
8
+ * downgrade is resumable: a crash mid-journal never corrupts v2, and a restart
9
+ * resumes idempotently (the unique failure-injection contract).
10
+ *
11
+ * `active()` is true once ANY v2 append has been journaled — an old binary may
12
+ * only open the exported legacy copy, never v2 directly, once the journal is
13
+ * active (CONTRACTS §Store). Unrepresentable rows (e.g. invalid UTF-8 bytes have
14
+ * no lossless legacy projection) are listed on prepare and carried through the
15
+ * copy/validate/switch lifecycle.
16
+ *
17
+ * Durable in the same isolated occurrence-v2 SQLite DB as the ledger rows
18
+ * (append-only provenance: journal records are never UPDATE'd; only the
19
+ * singleton state row advances phase). PREVENT-002 parameterized; PREVENT-011
20
+ * no `any`; PREVENT-PI-004 local filesystem only; no console.log.
21
+ */
22
+
23
+ import { DatabaseSync } from "node:sqlite";
24
+ import { createHash } from "node:crypto";
25
+ import type { CompatJournalV1 } from "./store.js";
26
+
27
+ /** Journal lifecycle phase (append-only; the singleton state row advances). */
28
+ export type JournalPhase = "prepared" | "copied" | "validated" | "switched";
29
+
30
+ /** Downgrade-phase validation failure codes (registered as conformance rows). */
31
+ export const MIG_DOWN_FAIL = {
32
+ NOT_ACTIVE: "MIG_DOWN_NOT_ACTIVE",
33
+ PHASE_UNREACHED: "MIG_DOWN_PHASE_UNREACHED",
34
+ SEQ_REGRESSION: "MIG_DOWN_SEQ_REGRESSION",
35
+ DIGEST_MISMATCH: "MIG_DOWN_DIGEST_MISMATCH",
36
+ SWITCH_UNSAFE: "MIG_DOWN_SWITCH_UNSAFE",
37
+ } as const;
38
+ export type MigDownCode = (typeof MIG_DOWN_FAIL)[keyof typeof MIG_DOWN_FAIL];
39
+
40
+ const SCHEMA = `
41
+ CREATE TABLE IF NOT EXISTS compat_journal_v1 (
42
+ session TEXT NOT NULL,
43
+ seq INTEGER NOT NULL,
44
+ event_id TEXT NOT NULL,
45
+ digest TEXT NOT NULL,
46
+ kind TEXT NOT NULL,
47
+ legacy_projection TEXT,
48
+ unrepresentable INTEGER NOT NULL DEFAULT 0,
49
+ PRIMARY KEY (session, seq)
50
+ ) STRICT;
51
+
52
+ CREATE TABLE IF NOT EXISTS compat_journal_state (
53
+ id INTEGER PRIMARY KEY CHECK (id = 1),
54
+ phase TEXT NOT NULL
55
+ ) STRICT;
56
+ `;
57
+
58
+ interface JournalRow {
59
+ session: string;
60
+ seq: number;
61
+ event_id: string;
62
+ digest: string;
63
+ kind: string;
64
+ legacy_projection: string | null;
65
+ unrepresentable: number;
66
+ }
67
+
68
+ /** Ensure the journal tables exist and seed the singleton phase row. */
69
+ export function initCompatJournal(db: DatabaseSync): void {
70
+ db.exec(SCHEMA);
71
+ db.prepare(
72
+ `INSERT OR IGNORE INTO compat_journal_state (id, phase) VALUES (1, 'prepared')`,
73
+ ).run();
74
+ }
75
+
76
+ function phaseOf(db: DatabaseSync): JournalPhase {
77
+ const row = db
78
+ .prepare(`SELECT phase FROM compat_journal_state WHERE id = 1`)
79
+ .get() as { phase: JournalPhase } | undefined;
80
+ return row?.phase ?? "prepared";
81
+ }
82
+
83
+ /** Read the durable journal phase (for the M2 migration host seam). */
84
+ export function journalPhase(db: DatabaseSync): JournalPhase {
85
+ return phaseOf(db);
86
+ }
87
+
88
+ function setPhase(db: DatabaseSync, phase: JournalPhase): void {
89
+ db.prepare(`UPDATE compat_journal_state SET phase = @phase WHERE id = 1`).run({
90
+ "@phase": phase,
91
+ });
92
+ }
93
+
94
+ /**
95
+ * Verify a recorded digest by RECOMPUTING sha256 over the base64 source embedded
96
+ * in the legacy projection and comparing it to the recorded digest (Q03: real
97
+ * digest parity, not a `sha256:` prefix check). Returns false when the projection
98
+ * is absent or malformed, or the digest does not match the recomputed hash.
99
+ */
100
+ export function verifyLegacyDigest(
101
+ digest: string,
102
+ legacyProjection: string | null,
103
+ ): boolean {
104
+ if (legacyProjection === null) return false;
105
+ let rec: unknown;
106
+ try {
107
+ rec = JSON.parse(legacyProjection);
108
+ } catch {
109
+ return false;
110
+ }
111
+ const source = (rec as { source?: unknown }).source;
112
+ if (typeof source !== "string") return false;
113
+ const recomputed = `sha256:${createHash("sha256").update(Buffer.from(source, "base64")).digest("hex")}`;
114
+ return recomputed === digest;
115
+ }
116
+
117
+ /**
118
+ * Build the CompatJournalV1 over the isolated occurrence-v2 DB. Call
119
+ * `initCompatJournal` once before use. All record writes are append-only and
120
+ * best-effort/non-fatal so the agent loop is never broken (PREVENT-PI-004).
121
+ */
122
+ export function createCompatJournal(db: DatabaseSync): CompatJournalV1 {
123
+ return {
124
+ active(): boolean {
125
+ try {
126
+ const row = db
127
+ .prepare(`SELECT 1 AS hit FROM compat_journal_v1 LIMIT 1`)
128
+ .get() as { hit: number } | undefined;
129
+ return row !== undefined;
130
+ } catch {
131
+ return false;
132
+ }
133
+ },
134
+ record(input): void {
135
+ const { occurrence, legacyProjection } = input;
136
+ try {
137
+ const unrep = legacyProjection === null;
138
+ db.prepare(
139
+ `INSERT INTO compat_journal_v1
140
+ (session, seq, event_id, digest, kind, legacy_projection, unrepresentable)
141
+ VALUES (@session, @seq, @event_id, @digest, @kind, @legacy, @unrep)
142
+ ON CONFLICT(session, seq) DO NOTHING`,
143
+ ).run({
144
+ "@session": occurrence.session,
145
+ "@seq": Number(occurrence.seq),
146
+ "@event_id": occurrence.eventId,
147
+ "@digest": occurrence.digest,
148
+ "@kind": occurrence.kind,
149
+ // Q06: an unrepresentable row stores NO projection (legacy_projection
150
+ // stays NULL); we never write a base64 body we cannot round-trip.
151
+ "@legacy": legacyProjection,
152
+ "@unrep": unrep ? 1 : 0,
153
+ });
154
+ } catch {
155
+ /* non-fatal journal write — never break the agent loop */
156
+ }
157
+ },
158
+ isUnrepresentable(eventId, digest): boolean {
159
+ try {
160
+ const row = db
161
+ .prepare(
162
+ `SELECT unrepresentable AS u FROM compat_journal_v1
163
+ WHERE event_id = @event_id AND digest = @digest
164
+ ORDER BY seq DESC LIMIT 1`,
165
+ )
166
+ .get({ "@event_id": eventId, "@digest": digest }) as { u: number } | undefined;
167
+ return row?.u === 1;
168
+ } catch {
169
+ return false;
170
+ }
171
+ },
172
+ prepare(): string[] {
173
+ const rows = db
174
+ .prepare(
175
+ `SELECT session, seq, event_id, digest, kind, legacy_projection, unrepresentable
176
+ FROM compat_journal_v1 ORDER BY session ASC, seq ASC`,
177
+ )
178
+ .all() as unknown as JournalRow[];
179
+ setPhase(db, "prepared");
180
+ return rows.filter((r) => r.unrepresentable === 1).map((r) => r.event_id);
181
+ },
182
+ copied(): void {
183
+ setPhase(db, "copied");
184
+ },
185
+ validate(): { ok: boolean; codes: readonly MigDownCode[] } {
186
+ const codes: MigDownCode[] = [];
187
+ const phase = phaseOf(db);
188
+ if (!isRepresentableState(phase, "copied")) codes.push(MIG_DOWN_FAIL.PHASE_UNREACHED);
189
+ if (!this.active()) codes.push(MIG_DOWN_FAIL.NOT_ACTIVE);
190
+ // Seq must be strictly increasing within a session (no regression).
191
+ const prev = new Map<string, number>();
192
+ const rows = db
193
+ .prepare(
194
+ `SELECT session, seq FROM compat_journal_v1 ORDER BY session ASC, seq ASC`,
195
+ )
196
+ .all() as unknown as Array<{ session: string; seq: number }>;
197
+ for (const r of rows) {
198
+ const last = prev.get(r.session);
199
+ if (last !== undefined && r.seq <= last) codes.push(MIG_DOWN_FAIL.SEQ_REGRESSION);
200
+ prev.set(r.session, r.seq);
201
+ }
202
+ // Digests must verify: recompute sha256 over the stored source and compare
203
+ // to the recorded digest (Q03 — real parity, not just a `sha256:` prefix).
204
+ for (const r of db
205
+ .prepare(
206
+ `SELECT digest, legacy_projection FROM compat_journal_v1 WHERE unrepresentable = 0`,
207
+ )
208
+ .all() as unknown as Array<{ digest: string; legacy_projection: string | null }>) {
209
+ if (r.legacy_projection === null) continue;
210
+ if (!verifyLegacyDigest(r.digest, r.legacy_projection)) {
211
+ codes.push(MIG_DOWN_FAIL.DIGEST_MISMATCH);
212
+ }
213
+ }
214
+ const ok = codes.length === 0;
215
+ if (ok) setPhase(db, "validated");
216
+ return { ok, codes: dedupe(codes) };
217
+ },
218
+ switched(): void {
219
+ if (this.active()) setPhase(db, "switched");
220
+ },
221
+ };
222
+ }
223
+
224
+ /** A phase has reached at least `required` in the prepare→…→switched order. */
225
+ function isRepresentableState(phase: JournalPhase, required: JournalPhase): boolean {
226
+ const order: readonly JournalPhase[] = ["prepared", "copied", "validated", "switched"];
227
+ return order.indexOf(phase) >= order.indexOf(required);
228
+ }
229
+
230
+ function dedupe(codes: MigDownCode[]): MigDownCode[] {
231
+ const out: MigDownCode[] = [];
232
+ for (const c of codes) if (!out.includes(c)) out.push(c);
233
+ return out;
234
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * vector-cortex/ledger/emit.ts — VC1A ledger emit seam.
3
+ *
4
+ * The single structured-event surface for the EventV2 ledger path. It mirrors
5
+ * the VC0B replay emit seam (`src/vector-cortex/replay/emit.ts`): the same
6
+ * non-fatal, structured-JSON `ts`+`event` contract (`src/log.ts` LogEntry),
7
+ * delivered through an injected emit callback so it stays pi-agnostic,
8
+ * deterministically testable, and network-free (PREVENT-PI-004).
9
+ *
10
+ * Carries only the two EventV2 events:
11
+ * - vector_cortex_event_decoded (a V2 occurrence was byte-decoded)
12
+ * - vector_cortex_event_validation_failed (a V2 conformity check failed)
13
+ *
14
+ * Flag gating: this is VC1A's single real consumer of `VC1A_ENABLED()`. When the
15
+ * flag is OFF the reporter emits NOTHING (zero observability writes) — the
16
+ * VC1A mode-C byte-identical behavior — while the codec/validator still operate.
17
+ * The flag is read per-call so a live toggle takes effect immediately.
18
+ */
19
+
20
+ import { VC1A_ENABLED } from "../../config/vector-cortex.js";
21
+
22
+ export type LedgerEventName =
23
+ | "vector_cortex_event_decoded"
24
+ | "vector_cortex_event_validation_failed";
25
+
26
+ /** Injected emit callback — same (event, fields) shape as the eval/replay seams. */
27
+ export type LedgerEmitter = (event: string, fields: Record<string, unknown>) => void;
28
+
29
+ export interface LedgerEventDecodedFields {
30
+ readonly session: string;
31
+ readonly seq: string;
32
+ readonly eventId: string;
33
+ readonly bytes: number;
34
+ readonly utf8Valid: boolean;
35
+ }
36
+
37
+ export interface LedgerValidationFailedFields {
38
+ readonly session: string;
39
+ readonly seq: string;
40
+ readonly eventId: string;
41
+ readonly code: string;
42
+ }
43
+
44
+ /** Typed, best-effort reporter bound to the two ledger event names. */
45
+ export interface LedgerReporter {
46
+ readonly eventDecoded: (fields: LedgerEventDecodedFields) => void;
47
+ readonly validationFailed: (fields: LedgerValidationFailedFields) => void;
48
+ }
49
+
50
+ function safe(fn: (event: LedgerEventName, fields: Record<string, unknown>) => void): (
51
+ event: LedgerEventName,
52
+ fields: Record<string, unknown>,
53
+ ) => void {
54
+ return (event, fields) => {
55
+ try {
56
+ fn(event, fields);
57
+ } catch {
58
+ /* non-fatal observability — never break the agent loop */
59
+ }
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Build a typed reporter over an injected emit callback. Emission is gated on
65
+ * `VC1A_ENABLED()` — flag OFF means zero ledger observability writes (mode-C
66
+ * parity). `emit` is optional: absent (or flag off) degrades to a no-op.
67
+ */
68
+ export function createLedgerReporter(emit?: LedgerEmitter): LedgerReporter {
69
+ const fire = safe((event, fields) => {
70
+ if (VC1A_ENABLED()) emit?.(event, fields);
71
+ });
72
+ return {
73
+ eventDecoded(fields) {
74
+ fire("vector_cortex_event_decoded", fields as unknown as Record<string, unknown>);
75
+ },
76
+ validationFailed(fields) {
77
+ fire("vector_cortex_event_validation_failed", fields as unknown as Record<string, unknown>);
78
+ },
79
+ };
80
+ }
81
+
82
+ /** Export the event names for consumers that match on them. */
83
+ export const LEDGER_EVENTS: readonly LedgerEventName[] = [
84
+ "vector_cortex_event_decoded",
85
+ "vector_cortex_event_validation_failed",
86
+ ] as const;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * vector-cortex/ledger/event-codec.ts — EventV2 byte-authority codec, Mode A
3
+ * (VC1A, TRIAD_RESILIENCE).
4
+ *
5
+ * A retains `originalBytes` + a SHA-256 `bytesDigest` and classifies strict
6
+ * UTF-8 (success) vs invalid-byte content WITHOUT replacement decoding:
7
+ * `TextDecoder(..., {fatal:true})` either yields text or throws — invalid input
8
+ * is represented only as `{valid:false, base64}` and is NEVER lossy-replaced.
9
+ * `canonicalNfc` is computed ONLY as a derived field for valid UTF-8 and is
10
+ * NEVER used for identity, digest, or byte reconstruction — `decode` returns the
11
+ * authoritative `originalBytes` exactly.
12
+ *
13
+ * Mode B (`./event-codecB.ts`) is a genuinely independent implementation of the
14
+ * same byte rule; A and B share no subroutine and must agree byte-for-byte.
15
+ *
16
+ * Pure/deterministic — no console, no network, no side effects (PREVENT-PI-004).
17
+ */
18
+
19
+ import nodeCrypto from "node:crypto";
20
+ import type { BytesDigest, EventCodec, EventEncodeInput, EventV2 } from "./types.js";
21
+
22
+ /** Mode-A internal SHA-256 helper (NOT shared with mode B). */
23
+ function digestSha256A(bytes: Uint8Array): BytesDigest {
24
+ const hex = nodeCrypto.createHash("sha256").update(bytes).digest("hex");
25
+ return `sha256:${hex}`;
26
+ }
27
+
28
+ /** Mode-A internal base64 (NOT shared with mode B). */
29
+ function base64EncodeA(bytes: Uint8Array): string {
30
+ return Buffer.from(bytes).toString("base64");
31
+ }
32
+
33
+ /**
34
+ * Strict UTF-8 classification with FATAL decoding (never replaces invalid bytes
35
+ * with U+FFFD). Returns the decoded text, or `{valid:false, base64}`.
36
+ */
37
+ export function classifyUtf8(bytes: Uint8Array): { valid: true; text: string } | { valid: false; base64: string } {
38
+ try {
39
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
40
+ return { valid: true, text };
41
+ } catch {
42
+ return { valid: false, base64: base64EncodeA(bytes) };
43
+ }
44
+ }
45
+
46
+ /** Build the Mode-A byte-authority codec. */
47
+ export function createEventCodec(): EventCodec {
48
+ return {
49
+ encode(input: EventEncodeInput): EventV2 {
50
+ const bytes = input.bytes;
51
+ const bytesDigest = digestSha256A(bytes);
52
+ const utf8 = classifyUtf8(bytes);
53
+ const event: EventV2 = {
54
+ schema: "event-v2",
55
+ sessionId: input.sessionId,
56
+ seq: input.seq,
57
+ eventId: input.eventId,
58
+ role: input.role,
59
+ kind: input.kind,
60
+ originalBytes: bytes,
61
+ bytesDigest,
62
+ utf8,
63
+ occurredAtMs: input.occurredAtMs,
64
+ };
65
+ if (input.toolCallId !== undefined) event.toolCallId = input.toolCallId;
66
+ // canonicalNfc is a DERIVED search key — valid UTF-8 only, never identity.
67
+ if (utf8.valid) event.canonicalNfc = utf8.text.normalize("NFC");
68
+ return event;
69
+ },
70
+
71
+ decode(event: EventV2): Uint8Array {
72
+ return event.originalBytes;
73
+ },
74
+
75
+ classifyUtf8,
76
+ };
77
+ }
78
+
79
+ export type { BytesDigest, EventV2 };