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,52 @@
1
+ /**
2
+ * vector-cortex/replay/types.ts — ReplayCutV2 / ReplayReportV2 contract types
3
+ * and the registered CUT / M3 conformance ID ranges.
4
+ *
5
+ * Owned by VC0B (replay correctness). Consumes reviewer-accepted predecessor
6
+ * contracts and common contracts only (EventV2 minimal shape). Pure type/schema
7
+ * definitions — no runtime network, no side effects (PREVENT-PI-004 / PREVENT-011).
8
+ */
9
+ /**
10
+ * Registered CUT conformance ID range (CUT-001..020). The acceptance test
11
+ * reads these rows from the v2 manifest and asserts their expected bytes/results.
12
+ */
13
+ export const CUT_IDS = [
14
+ "CUT-001",
15
+ "CUT-002",
16
+ "CUT-003",
17
+ "CUT-004",
18
+ "CUT-005",
19
+ "CUT-006",
20
+ "CUT-007",
21
+ "CUT-008",
22
+ "CUT-009",
23
+ "CUT-010",
24
+ "CUT-011",
25
+ "CUT-012",
26
+ "CUT-013",
27
+ "CUT-014",
28
+ "CUT-015",
29
+ "CUT-016",
30
+ "CUT-017",
31
+ "CUT-018",
32
+ "CUT-019",
33
+ "CUT-020",
34
+ ];
35
+ /**
36
+ * Registered M3 conformance ID range (M3-001..010) for the effective-cut-v2
37
+ * migration (copy/validate/switch). The migration fixtures assert that crash
38
+ * after M3 copy validation but before pointer switch retains the old cut pointer
39
+ * and resumes idempotently.
40
+ */
41
+ export const M3_IDS = [
42
+ "M3-001",
43
+ "M3-002",
44
+ "M3-003",
45
+ "M3-004",
46
+ "M3-005",
47
+ "M3-006",
48
+ "M3-007",
49
+ "M3-008",
50
+ "M3-009",
51
+ "M3-010",
52
+ ];
@@ -0,0 +1,359 @@
1
+ /**
2
+ * vector-cortex/resilience/breaker-core.ts — VC0C breaker state machine (VC0C).
3
+ *
4
+ * Implementation file behind the `breaker.ts` factory shell. See breaker.ts for
5
+ * the public surface; this file owns the TRIAD_RESILIENCE §breaker state machine.
6
+ *
7
+ * Implements TRIAD_RESILIENCE.md §breaker state machine over the shared
8
+ * constants in src/config/vector-cortex.ts (consumed, NOT redefined — resolves
9
+ * the standing "dead BREAKER_* constants" issue):
10
+ *
11
+ * States: CLOSED_A, OPEN_B, OPEN_C, PROBE_B, PROBE_A, MANUAL_HALT.
12
+ * Rolling window BREAKER_WINDOW_MS; min BREAKER_MIN_ATTEMPTS attempts.
13
+ * Performance trip at BREAKER_PERF_FAILURES failures or failure-rate >=
14
+ * BREAKER_PERF_FAILURE_RATE; correctness trip on the FIRST correctness failure.
15
+ * Cooldown BREAKER_COOLDOWN_MS; BREAKER_PROBE_COUNT consecutive probes succeed
16
+ * to advance; exponential retry BREAKER_RETRY_BASE_MS * 2^attempt capped at
17
+ * BREAKER_RETRY_CAP_MS with deterministic +-10% jitter derived from the
18
+ * subsystem digest; promotion hysteresis needs failure rate <
19
+ * BREAKER_HYSTERESIS_FAILURE_RATE and p95 <= BREAKER_HYSTERESIS_BUDGET_P95_MS;
20
+ * BREAKER_MIN_HEALTHY_RESIDENCE_MS healthy residence before another promotion.
21
+ *
22
+ * Clock: windows/cooldowns use MONOTONIC time (`now()`, injectable and
23
+ * defaulting to performance.now()); wall time appears only in `updatedAt`
24
+ * (records). Backward/forward wall jumps never alter eligibility. Restart
25
+ * reconstructs state from appended breaker events (`onEvent`). Expired cooldown
26
+ * may PROBE, never directly promote. Probe output is never served. Manual halt
27
+ * requires a reason + explicit admin `reset` that clears cooldown but never
28
+ * evidence.
29
+ *
30
+ * Pure in-memory + event-serializable state; no storage, no network
31
+ * (PREVENT-PI-004), no `any` (PREVENT-011).
32
+ */
33
+ import { BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "../../config/vector-cortex.js";
34
+ /** Deterministic +-jitter fraction for a subsystem (0..1) from its digest. */
35
+ function jitterSeed(subsystem) {
36
+ let h = 2166136261;
37
+ for (let i = 0; i < subsystem.length; i++) {
38
+ h ^= subsystem.charCodeAt(i);
39
+ h = Math.imul(h, 16777619);
40
+ }
41
+ return (h >>> 0) / 4294967295; // 0..1
42
+ }
43
+ /** Iterative backoff delay with +-BREAKER_RETRY_JITTER deterministic jitter. */
44
+ export function breakerRetryDelay(subsystem, attempt) {
45
+ const exp = Math.min(BREAKER_RETRY_BASE_MS * 2 ** attempt, BREAKER_RETRY_CAP_MS);
46
+ const frac = jitterSeed(subsystem); // 0..1
47
+ const factor = 1 + (frac * 2 - 1) * BREAKER_RETRY_JITTER; // 0.9..1.1
48
+ return Math.round(exp * factor);
49
+ }
50
+ function percentile(sorted, p) {
51
+ if (sorted.length === 0)
52
+ return 0;
53
+ const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1));
54
+ return sorted[idx];
55
+ }
56
+ export function createBreaker(opts = {}) {
57
+ const now = opts.now ?? (() => performance.now());
58
+ const wallNow = opts.wallNow ?? (() => new Date().toISOString());
59
+ const reporter = opts.reporter;
60
+ const onEvent = opts.onEvent;
61
+ const subs = new Map();
62
+ function get(subsystem) {
63
+ let s = subs.get(subsystem);
64
+ if (!s) {
65
+ s = {
66
+ state: "CLOSED_A",
67
+ transitionedAtMs: now(),
68
+ attempts: [],
69
+ failures: [],
70
+ latencies: [],
71
+ retryAttempt: 0,
72
+ probeCount: 0,
73
+ frozenFrontier: false,
74
+ tripKind: "correctness",
75
+ };
76
+ subs.set(subsystem, s);
77
+ }
78
+ return s;
79
+ }
80
+ function pruneWindow(s, at) {
81
+ const cutoff = at - BREAKER_WINDOW_MS;
82
+ s.attempts = s.attempts.filter((t) => t > cutoff);
83
+ s.failures = s.failures.filter((t) => t > cutoff);
84
+ s.latencies = s.latencies.slice(-s.attempts.length);
85
+ }
86
+ /** Emit a transition with the minimal event surface + optional append. */
87
+ function transition(s, to, event, fields) {
88
+ s.state = to;
89
+ s.transitionedAtMs = now();
90
+ if (onEvent) {
91
+ try {
92
+ onEvent({ type: event, to, ...fields, atMs: s.transitionedAtMs });
93
+ }
94
+ catch {
95
+ /* non-fatal */
96
+ }
97
+ }
98
+ if (reporter) {
99
+ if (event === "breaker_opened") {
100
+ reporter.breakerOpened({
101
+ subsystem: String(fields.subsystem ?? ""),
102
+ fromState: String(fields.fromState ?? ""),
103
+ toState: to,
104
+ code: String(fields.code ?? ""),
105
+ attempts: Number(fields.attempts ?? 0),
106
+ failures: Number(fields.failures ?? 0),
107
+ });
108
+ }
109
+ else if (event === "probe_promoted") {
110
+ reporter.probePromoted({
111
+ subsystem: String(fields.subsystem ?? ""),
112
+ fromState: String(fields.fromState ?? ""),
113
+ toState: to,
114
+ probeCount: Number(fields.probeCount ?? 0),
115
+ retryAttempt: Number(fields.retryAttempt ?? 0),
116
+ });
117
+ }
118
+ }
119
+ }
120
+ function record(subsystem) {
121
+ const s = get(subsystem);
122
+ const at = now();
123
+ pruneWindow(s, at);
124
+ const failureRate = s.attempts.length === 0 ? 0 : s.failures.length / s.attempts.length;
125
+ const sortedLat = [...s.latencies].sort((a, b) => a - b);
126
+ const p95 = percentile(sortedLat, 95);
127
+ const r = {
128
+ subsystem,
129
+ state: s.state,
130
+ windowStartMs: s.attempts.length > 0 ? Math.min(...s.attempts) : at,
131
+ attempts: s.attempts.length,
132
+ failures: s.failures.length,
133
+ tripKind: s.tripKind,
134
+ transitionedAtMs: s.transitionedAtMs,
135
+ cooldownUntilMs: s.cooldownUntilMs,
136
+ probeCount: s.probeCount,
137
+ retryAttempt: s.retryAttempt,
138
+ retryDelayMs: breakerRetryDelay(subsystem, s.retryAttempt),
139
+ frozenFrontier: s.frozenFrontier,
140
+ manualReason: s.manualReason,
141
+ updatedAt: wallNow(),
142
+ p95Ms: p95,
143
+ failureRate,
144
+ };
145
+ return r;
146
+ }
147
+ /** Enter a cooldown + the given open state; returns the open-state name. */
148
+ function openTo(s, subsystem, to, kind, code) {
149
+ const from = s.state;
150
+ s.tripKind = kind;
151
+ if (to === "MANUAL_HALT") {
152
+ s.cooldownUntilMs = undefined;
153
+ s.probeCount = 0;
154
+ transition(s, "MANUAL_HALT", "breaker_opened", { subsystem, fromState: from, code, attempts: s.attempts.length, failures: s.failures.length });
155
+ return "MANUAL_HALT";
156
+ }
157
+ s.cooldownUntilMs = now() + BREAKER_COOLDOWN_MS;
158
+ s.probeCount = 0;
159
+ if (to === "OPEN_B")
160
+ s.healthyResidenceSinceMs = now(); // start healthy residence
161
+ transition(s, to, "breaker_opened", { subsystem, fromState: from, code, attempts: s.attempts.length, failures: s.failures.length });
162
+ return to;
163
+ }
164
+ /** After cooldown, an open state may PROBE (never directly promote). */
165
+ function maybeProbe(s, subsystem, openState) {
166
+ const at = now();
167
+ if (s.state !== openState)
168
+ return;
169
+ if (s.cooldownUntilMs === undefined || at < s.cooldownUntilMs)
170
+ return;
171
+ // Promotion back toward A additionally requires the minimum healthy
172
+ // residence in OPEN_B (invariant: promotion never precedes the 5min
173
+ // residence, so we may only PROBE here, never directly promote).
174
+ if (openState === "OPEN_B") {
175
+ const since = s.healthyResidenceSinceMs ?? at;
176
+ if (at - since < BREAKER_MIN_HEALTHY_RESIDENCE_MS)
177
+ return;
178
+ }
179
+ s.cooldownUntilMs = undefined;
180
+ const to = openState === "OPEN_C" ? "PROBE_B" : "PROBE_A";
181
+ transition(s, to, "probe_promoted", { subsystem, fromState: openState, probeCount: 0, retryAttempt: s.retryAttempt });
182
+ }
183
+ /**
184
+ * Promotion hysteresis: promotion requires window failure rate < 2% and p95
185
+ * latency within budget (TRIAD_RESILIENCE). Without it, a recovery probe that
186
+ * passes but with a poor window must NOT promote.
187
+ */
188
+ function canPromote(s) {
189
+ const failureRate = s.attempts.length === 0 ? 0 : s.failures.length / s.attempts.length;
190
+ if (failureRate >= BREAKER_HYSTERESIS_FAILURE_RATE)
191
+ return false;
192
+ const sortedLat = [...s.latencies].sort((a, b) => a - b);
193
+ return percentile(sortedLat, 95) <= BREAKER_HYSTERESIS_BUDGET_P95_MS;
194
+ }
195
+ /** A successful probe: advance the count, and promote once N AND hysteresis hold. */
196
+ function advanceProbe(s, subsystem) {
197
+ if (s.state !== "PROBE_B" && s.state !== "PROBE_A")
198
+ return;
199
+ s.probeCount += 1;
200
+ if (s.probeCount >= BREAKER_PROBE_COUNT) {
201
+ if (!canPromote(s)) {
202
+ // Probe succeeded but hysteresis not met — stay probing, no promotion.
203
+ s.probeCount = 0;
204
+ return;
205
+ }
206
+ const from = s.state;
207
+ s.probeCount = 0;
208
+ const to = from === "PROBE_B" ? "OPEN_B" : "CLOSED_A";
209
+ transition(s, to, "probe_promoted", { subsystem, fromState: from, probeCount: BREAKER_PROBE_COUNT, retryAttempt: s.retryAttempt });
210
+ if (to === "OPEN_B") {
211
+ s.cooldownUntilMs = now() + BREAKER_COOLDOWN_MS;
212
+ s.healthyResidenceSinceMs = now();
213
+ }
214
+ s.retryAttempt = 0;
215
+ }
216
+ }
217
+ const breaker = {
218
+ modeFor(subsystem) {
219
+ const s = get(subsystem);
220
+ const at = now();
221
+ pruneWindow(s, at);
222
+ if (s.state === "MANUAL_HALT")
223
+ return "C";
224
+ if (s.state === "CLOSED_A")
225
+ return "A";
226
+ // OPEN_B / OPEN_C / PROBE_* — the primary is broken; fall back to B, or C
227
+ // once B itself has tripped.
228
+ if (s.state === "OPEN_C" || s.state === "PROBE_B")
229
+ return "C";
230
+ return "B";
231
+ },
232
+ execute(subsystem, inputDigest, run, validate) {
233
+ const s = get(subsystem);
234
+ const at = now();
235
+ pruneWindow(s, at);
236
+ const mode = breaker.modeFor(subsystem);
237
+ s.attempts.push(at);
238
+ let ok = false;
239
+ let value;
240
+ let errorCode = "";
241
+ try {
242
+ value = run[mode]();
243
+ ok = validate(value);
244
+ if (!ok)
245
+ errorCode = "TRI_OUTPUT_INVALID";
246
+ }
247
+ catch {
248
+ errorCode = "TRI_EXEC_THREW";
249
+ ok = false;
250
+ }
251
+ const latency = now() - at;
252
+ s.latencies.push(latency);
253
+ if (ok) {
254
+ const openState = s.state === "OPEN_C" ? "OPEN_C" : "OPEN_B";
255
+ maybeProbe(s, subsystem, openState);
256
+ advanceProbe(s, subsystem);
257
+ return {
258
+ ok: true,
259
+ value: value,
260
+ mode,
261
+ inputDigest,
262
+ outputDigest: String((value?.digest) ?? ""),
263
+ algorithmVersion: "vc0c-triad-1",
264
+ latencyMs: latency,
265
+ breaker: record(subsystem),
266
+ };
267
+ }
268
+ s.failures.push(at);
269
+ // Probe failure: return to the OPEN state it was probing FROM and increment
270
+ // backoff (TRIAD_RESILIENCE §transitions: "Any probe failure returns to its
271
+ // open state and increments backoff"). A probe failure is NOT a fresh trip —
272
+ // it re-enters the originating open state/cooldown and never serves output.
273
+ // This governs both PROBE_A (probing OPEN_B, mode B) and PROBE_B (probing
274
+ // OPEN_C, mode C); without it a PROBE_A failure would mis-trip to OPEN_C and
275
+ // a PROBE_B failure would short-circuit with no revert and no backoff bump.
276
+ if (s.state === "PROBE_A" || s.state === "PROBE_B") {
277
+ const from = s.state === "PROBE_A" ? "OPEN_B" : "OPEN_C";
278
+ openTo(s, subsystem, from, "performance", errorCode);
279
+ s.retryAttempt += 1;
280
+ return { ok: false, mode, code: errorCode, retryable: true, breaker: record(subsystem) };
281
+ }
282
+ // Trip rules for FRESH (non-probe) failures.
283
+ if (mode === "C") {
284
+ // C failures are continuity-level; they do not advance the breaker but
285
+ // surface as retryable C failures (unchanged transcript).
286
+ return { ok: false, mode, code: errorCode, retryable: true, breaker: record(subsystem) };
287
+ }
288
+ if (mode === "B") {
289
+ // Fresh B failure -> OPEN_C. tripKind mirrors the A path: only a semantic
290
+ // correctness signal (TRI_OUTPUT_INVALID) is "correctness"; a plain
291
+ // TRI_EXEC_THREW perf throw is "performance". (Not `failures.length >=
292
+ // BREAKER_CORRECTNESS_FAILURES` — that is ALWAYS true right after
293
+ // recording this failure, which would tautologically mislabel every
294
+ // B-throw as a correctness trip.)
295
+ openTo(s, subsystem, "OPEN_C", errorCode === "TRI_OUTPUT_INVALID" ? "correctness" : "performance", errorCode);
296
+ s.retryAttempt += 1;
297
+ return { ok: false, mode, code: errorCode, retryable: true, breaker: record(subsystem) };
298
+ }
299
+ // mode === "A" failure: correctness (output validation failed) is
300
+ // ZERO-TOLERANCE and trips on its first occurrence regardless of attempt
301
+ // count (TRIAD_RESILIENCE: "correctness trip on first failure"). The
302
+ // 20-attempt minimum gates only the PERFORMANCE rate trip. A first, isolated
303
+ // failure is a performance-rate signal (rate 1.0), so it needs the window.
304
+ const failureRate = s.failures.length / s.attempts.length;
305
+ const perf = s.failures.length >= BREAKER_PERF_FAILURES || failureRate >= BREAKER_PERF_FAILURE_RATE;
306
+ const correctness = errorCode === "TRI_OUTPUT_INVALID" ||
307
+ (s.failures.length >= BREAKER_CORRECTNESS_FAILURES && failureRate < BREAKER_PERF_FAILURE_RATE);
308
+ const trips = correctness || (s.attempts.length >= BREAKER_MIN_ATTEMPTS && perf);
309
+ if (trips) {
310
+ openTo(s, subsystem, "OPEN_B", correctness ? "correctness" : "performance", errorCode);
311
+ s.retryAttempt += 1;
312
+ }
313
+ return { ok: false, mode, code: errorCode, retryable: true, breaker: record(subsystem) };
314
+ },
315
+ recordProbe(subsystem) {
316
+ const s = get(subsystem);
317
+ const openState = s.state === "OPEN_C" ? "OPEN_C" : "OPEN_B";
318
+ maybeProbe(s, subsystem, openState);
319
+ advanceProbe(s, subsystem);
320
+ return record(subsystem);
321
+ },
322
+ manualHalt(reason) {
323
+ if (!reason)
324
+ return record("provider");
325
+ // Authority/digest/causal corruption degrades EVERY tracked subsystem to
326
+ // MANUAL_HALT (the corruption is global, not per-subsystem). Returns the
327
+ // provider's record. A manual halt requires a reason and an explicit
328
+ // admin reset to clear — evidence is never discarded.
329
+ const targets = subs.size === 0 ? ["provider"] : [...subs.keys()];
330
+ let last = record("provider");
331
+ for (const subsystem of targets) {
332
+ const s = get(subsystem);
333
+ s.manualReason = reason;
334
+ openTo(s, subsystem, "MANUAL_HALT", "manual", "TRI_MANUAL_HALT");
335
+ last = record(subsystem);
336
+ }
337
+ return last;
338
+ },
339
+ snapshot(subsystem) {
340
+ return record(subsystem);
341
+ },
342
+ reset(subsystem) {
343
+ const s = get(subsystem);
344
+ const at = now();
345
+ pruneWindow(s, at);
346
+ // Manual reset clears cooldown / probe state but NEVER evidence (attempts
347
+ // and failures are retained). A MANUAL_HALT unwires to a probe of B.
348
+ s.cooldownUntilMs = undefined;
349
+ s.probeCount = 0;
350
+ s.manualReason = undefined;
351
+ if (s.state === "MANUAL_HALT") {
352
+ s.state = "OPEN_B";
353
+ s.transitionedAtMs = now();
354
+ }
355
+ return record(subsystem);
356
+ },
357
+ };
358
+ return breaker;
359
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * vector-cortex/resilience/breaker.ts — VC0C breaker factory shell (VC0C).
3
+ *
4
+ * Thin barrel over the state-machine implementation in `breaker-core.ts`
5
+ * (delegate-shell + impl-file split keeps the frequently-touched file small).
6
+ * Owns the public `createBreaker` factory and the monotonic-clock/options types,
7
+ * all re-exported for the safety adapter and tests.
8
+ *
9
+ * Full semantics live in breaker-core.ts (TRIAD_RESILIENCE §breaker). Local-only,
10
+ * no network (PREVENT-PI-004), no `any` (PREVENT-011).
11
+ */
12
+ export { createBreaker, breakerRetryDelay, } from "./breaker-core.js";
@@ -0,0 +1,56 @@
1
+ /**
2
+ * vector-cortex/resilience/emit.ts — VC0C resilience emit seam (VC0C).
3
+ *
4
+ * The single structured-event surface for the live safety envelope: the breaker
5
+ * opening/probing and frontier-freeze observability that a live provider path (or
6
+ * dashboard) consumes. It mirrors the VC0B replay emit seam (`replay/emit.ts`)
7
+ * and the VC0A eval observer: the same injected `(event, fields)` callback, the
8
+ * same non-fatal `safe()` best-effort wrapper, and the same per-call
9
+ * `VC0C_ENABLED()` gate. Absent emitter (or flag OFF) degrades to a no-op with
10
+ * byte-identical predecessor behavior (PREVENT-PI-004, structured `ts`+`event`).
11
+ *
12
+ * Carries exactly the three events the sprint names:
13
+ * - vector_cortex_breaker_opened (a breaker tripped to an OPEN state)
14
+ * - vector_cortex_probe_promoted (recovery probes advanced a state)
15
+ * - vector_cortex_frontier_frozen (authority outage froze derived frontiers)
16
+ */
17
+ import { VC0C_ENABLED } from "../../config/vector-cortex.js";
18
+ function safe(fn) {
19
+ return (event, fields) => {
20
+ try {
21
+ fn(event, fields);
22
+ }
23
+ catch {
24
+ /* non-fatal observability — never break the agent loop */
25
+ }
26
+ };
27
+ }
28
+ /**
29
+ * Build a typed reporter over an injected emit callback. Emission is gated on
30
+ * `VC0C_ENABLED()` — flag OFF (or absent emitter) means zero resilience
31
+ * observability writes (mode-C predecessor parity). The flag is read per-call so
32
+ * a live toggle takes effect immediately.
33
+ */
34
+ export function createResilienceReporter(emit) {
35
+ const fire = safe((event, fields) => {
36
+ if (VC0C_ENABLED())
37
+ emit?.(event, fields);
38
+ });
39
+ return {
40
+ breakerOpened(fields) {
41
+ fire("vector_cortex_breaker_opened", fields);
42
+ },
43
+ probePromoted(fields) {
44
+ fire("vector_cortex_probe_promoted", fields);
45
+ },
46
+ frontierFrozen(fields) {
47
+ fire("vector_cortex_frontier_frozen", fields);
48
+ },
49
+ };
50
+ }
51
+ /** Export the event names for consumers that match on them. */
52
+ export const RESILIENCE_EVENTS = [
53
+ "vector_cortex_breaker_opened",
54
+ "vector_cortex_probe_promoted",
55
+ "vector_cortex_frontier_frozen",
56
+ ];