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.
- package/dist/config/vector-cortex.js +81 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints/registry-ext.js +30 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +4 -14
- package/dist/extensions/dashboard-server/api-contracts/vector-cortex.js +12 -0
- package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +10 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex.js +292 -0
- package/dist/extensions/dashboard-server/routes.js +1 -0
- package/dist/extensions/dashboard-server/server.js +9 -1
- package/dist/extensions/mega-events/context-handler.js +11 -0
- package/dist/extensions/mega-runtime/vector-cortex-ledger.js +131 -0
- package/dist/extensions/mega-runtime/vector-cortex-safety.js +100 -0
- package/dist/src/config/vector-cortex.js +81 -0
- package/dist/src/config.js +6 -0
- package/dist/src/vector-cortex/eval/annotations.js +41 -0
- package/dist/src/vector-cortex/eval/metrics.js +86 -0
- package/dist/src/vector-cortex/eval/observer.js +61 -0
- package/dist/src/vector-cortex/eval/persist.js +67 -0
- package/dist/src/vector-cortex/eval/reader.js +29 -0
- package/dist/src/vector-cortex/eval/types.js +33 -0
- package/dist/src/vector-cortex/ledger/adapter.js +55 -0
- package/dist/src/vector-cortex/ledger/compat-journal.js +206 -0
- package/dist/src/vector-cortex/ledger/emit.js +53 -0
- package/dist/src/vector-cortex/ledger/event-codec.js +72 -0
- package/dist/src/vector-cortex/ledger/event-codecB.js +76 -0
- package/dist/src/vector-cortex/ledger/sqlite.js +200 -0
- package/dist/src/vector-cortex/ledger/store.js +184 -0
- package/dist/src/vector-cortex/ledger/types.js +52 -0
- package/dist/src/vector-cortex/ledger/validator.js +117 -0
- package/dist/src/vector-cortex/migrations/effective-cut-v2.js +103 -0
- package/dist/src/vector-cortex/migrations/occurrence-v2.js +139 -0
- package/dist/src/vector-cortex/replay/cut.js +123 -0
- package/dist/src/vector-cortex/replay/emit.js +58 -0
- package/dist/src/vector-cortex/replay/replay.js +260 -0
- package/dist/src/vector-cortex/replay/replayB.js +73 -0
- package/dist/src/vector-cortex/replay/types.js +52 -0
- package/dist/src/vector-cortex/resilience/breaker-core.js +359 -0
- package/dist/src/vector-cortex/resilience/breaker.js +12 -0
- package/dist/src/vector-cortex/resilience/emit.js +56 -0
- package/dist/src/vector-cortex/resilience/spool-core.js +347 -0
- package/dist/src/vector-cortex/resilience/spool.js +12 -0
- package/dist/src/vector-cortex/resilience/types.js +32 -0
- package/dist/vector-cortex/eval/annotations.js +41 -0
- package/dist/vector-cortex/eval/metrics.js +86 -0
- package/dist/vector-cortex/eval/observer.js +61 -0
- package/dist/vector-cortex/eval/persist.js +67 -0
- package/dist/vector-cortex/eval/reader.js +29 -0
- package/dist/vector-cortex/eval/types.js +33 -0
- package/dist/vector-cortex/ledger/adapter.js +55 -0
- package/dist/vector-cortex/ledger/compat-journal.js +206 -0
- package/dist/vector-cortex/ledger/emit.js +53 -0
- package/dist/vector-cortex/ledger/event-codec.js +72 -0
- package/dist/vector-cortex/ledger/event-codecB.js +76 -0
- package/dist/vector-cortex/ledger/sqlite.js +200 -0
- package/dist/vector-cortex/ledger/store.js +184 -0
- package/dist/vector-cortex/ledger/types.js +52 -0
- package/dist/vector-cortex/ledger/validator.js +117 -0
- package/dist/vector-cortex/migrations/effective-cut-v2.js +103 -0
- package/dist/vector-cortex/migrations/occurrence-v2.js +139 -0
- package/dist/vector-cortex/replay/cut.js +123 -0
- package/dist/vector-cortex/replay/emit.js +58 -0
- package/dist/vector-cortex/replay/replay.js +260 -0
- package/dist/vector-cortex/replay/replayB.js +73 -0
- package/dist/vector-cortex/replay/types.js +52 -0
- package/dist/vector-cortex/resilience/breaker-core.js +359 -0
- package/dist/vector-cortex/resilience/breaker.js +12 -0
- package/dist/vector-cortex/resilience/emit.js +56 -0
- package/dist/vector-cortex/resilience/spool-core.js +347 -0
- package/dist/vector-cortex/resilience/spool.js +12 -0
- package/dist/vector-cortex/resilience/types.js +32 -0
- package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js → AreaChart-CAnWomhw.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js.map → AreaChart-CAnWomhw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js → BarChart-lZ2-s71k.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js.map → BarChart-lZ2-s71k.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js → CacheTab-BL2Thh66.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js.map → CacheTab-BL2Thh66.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js → EventsTab-DEh2Z9fp.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js.map → EventsTab-DEh2Z9fp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js → HealthTab-DTwKclxk.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js.map → HealthTab-DTwKclxk.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js → MaintenanceTab-DDfBEdbX.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js.map → MaintenanceTab-DDfBEdbX.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js → MemoryMapTab-Dgfmg-0Q.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js.map → MemoryMapTab-Dgfmg-0Q.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js → MetricsTab-DOcMm0Tx.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js.map → MetricsTab-DOcMm0Tx.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js → OverviewTab-DHT5aoHg.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js.map → OverviewTab-DHT5aoHg.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js → ReposTab-CGAmtR_c.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js.map → ReposTab-CGAmtR_c.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js → SessionsTab-BIgx6iKT.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js.map → SessionsTab-BIgx6iKT.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js → SetupTab-ADghKuEI.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js.map → SetupTab-ADghKuEI.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js → TimeSavedCard-C33UiiOM.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js.map → TimeSavedCard-C33UiiOM.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js → TurnsTab-DdbL9wPN.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js.map → TurnsTab-DdbL9wPN.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-Cl1pbEdR.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-Cl1pbEdR.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js → WikiTab-Ch00OFKs.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js.map → WikiTab-Ch00OFKs.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js → button-ByeEdo2g.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js.map → button-ByeEdo2g.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js → card-DesoLXvc.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js.map → card-DesoLXvc.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js → generateCategoricalChart-CCAB7Iz4.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js.map → generateCategoricalChart-CCAB7Iz4.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-B93cewiX.js → index-B6cgyAPr.js} +34 -29
- package/extensions/dashboard-client/dist/assets/index-B6cgyAPr.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/index-CfvziyBi.css +1 -0
- package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js → switch-DIZC-oe-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js.map → switch-DIZC-oe-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js → toggle-CIoWaH2e.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js.map → toggle-CIoWaH2e.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js → useSSE-Bv4943l1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js.map → useSSE-Bv4943l1.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/src/App.tsx +2 -0
- package/extensions/dashboard-client/src/api/vector-cortex.ts +54 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +272 -0
- package/extensions/dashboard-client/src/tabs/registry.ts +4 -1
- package/extensions/dashboard-client/src/types/vector-cortex.ts +84 -0
- package/extensions/dashboard-server/api-contracts/endpoints/registry-ext.ts +67 -0
- package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +4 -21
- package/extensions/dashboard-server/api-contracts/index.ts +3 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex.ts +136 -0
- package/extensions/dashboard-server/routes-rag-settings-helpers.ts +35 -0
- package/extensions/dashboard-server/routes-vector-cortex.ts +332 -0
- package/extensions/dashboard-server/routes.ts +6 -0
- package/extensions/dashboard-server/server.ts +8 -0
- package/extensions/mega-events/context-handler.ts +16 -0
- package/extensions/mega-runtime/vector-cortex-ledger.ts +165 -0
- package/extensions/mega-runtime/vector-cortex-safety.ts +181 -0
- package/package.json +2 -1
- package/src/config/vector-cortex.ts +87 -0
- package/src/config.ts +27 -0
- package/src/vector-cortex/eval/annotations.ts +70 -0
- package/src/vector-cortex/eval/metrics.ts +113 -0
- package/src/vector-cortex/eval/observer.ts +86 -0
- package/src/vector-cortex/eval/persist.ts +73 -0
- package/src/vector-cortex/eval/reader.ts +45 -0
- package/src/vector-cortex/eval/types.ts +89 -0
- package/src/vector-cortex/ledger/adapter.ts +71 -0
- package/src/vector-cortex/ledger/compat-journal.ts +234 -0
- package/src/vector-cortex/ledger/emit.ts +86 -0
- package/src/vector-cortex/ledger/event-codec.ts +79 -0
- package/src/vector-cortex/ledger/event-codecB.ts +87 -0
- package/src/vector-cortex/ledger/sqlite.ts +265 -0
- package/src/vector-cortex/ledger/store.ts +380 -0
- package/src/vector-cortex/ledger/types.ts +141 -0
- package/src/vector-cortex/ledger/validator.ts +124 -0
- package/src/vector-cortex/migrations/effective-cut-v2.ts +149 -0
- package/src/vector-cortex/migrations/occurrence-v2.ts +192 -0
- package/src/vector-cortex/replay/cut.ts +169 -0
- package/src/vector-cortex/replay/emit.ts +89 -0
- package/src/vector-cortex/replay/replay.ts +308 -0
- package/src/vector-cortex/replay/replayB.ts +97 -0
- package/src/vector-cortex/replay/types.ts +145 -0
- package/src/vector-cortex/resilience/breaker-core.ts +444 -0
- package/src/vector-cortex/resilience/breaker.ts +22 -0
- package/src/vector-cortex/resilience/emit.ts +99 -0
- package/src/vector-cortex/resilience/spool-core.ts +411 -0
- package/src/vector-cortex/resilience/spool.ts +22 -0
- package/src/vector-cortex/resilience/types.ts +158 -0
- package/extensions/dashboard-client/dist/assets/index-B93cewiX.js.map +0 -1
- 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
|
+
];
|