pi-mega-compact 0.20.70 → 0.20.72

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 (41) hide show
  1. package/dist/extensions/dashboard-server/routes-setup-cortex.js +7 -1
  2. package/dist/extensions/dashboard-server/routes-vector-cortex-crystals.js +45 -30
  3. package/dist/extensions/dashboard-server/routes-vector-cortex-diagnostics.js +50 -29
  4. package/dist/extensions/dashboard-server/routes-vector-cortex-economics.js +33 -20
  5. package/dist/extensions/dashboard-server/routes-vector-cortex-helpers.js +54 -0
  6. package/dist/extensions/dashboard-server/routes-vector-cortex-policy.js +33 -16
  7. package/dist/src/vector-cortex/cache/store.js +25 -2
  8. package/dist/src/vector-cortex/encoder/asset.js +34 -2
  9. package/dist/src/vector-cortex/encoder/router.js +31 -3
  10. package/dist/src/vector-cortex/livewire/livewire-live.js +163 -0
  11. package/dist/src/vector-cortex/livewire/livewire-registry.js +73 -0
  12. package/dist/src/vector-cortex/livewire/livewire-runtime.js +107 -0
  13. package/dist/src/vector-cortex/livewire/livewire-snapshot.js +101 -0
  14. package/dist/src/vector-cortex/livewire/livewire-types.js +19 -0
  15. package/dist/src/vector-cortex/provider/registry.js +44 -12
  16. package/dist/src/vector-cortex/setup-cortex-blockers-compute.js +43 -7
  17. package/dist/vector-cortex/cache/store.js +25 -2
  18. package/dist/vector-cortex/encoder/asset.js +34 -2
  19. package/dist/vector-cortex/encoder/router.js +31 -3
  20. package/dist/vector-cortex/provider/registry.js +44 -12
  21. package/dist/vector-cortex/setup-cortex-blockers-compute.js +43 -7
  22. package/extensions/dashboard-server/routes-setup-cortex.ts +8 -1
  23. package/extensions/dashboard-server/routes-vector-cortex-crystals.ts +46 -30
  24. package/extensions/dashboard-server/routes-vector-cortex-diagnostics.ts +52 -29
  25. package/extensions/dashboard-server/routes-vector-cortex-economics.ts +35 -20
  26. package/extensions/dashboard-server/routes-vector-cortex-helpers.ts +84 -0
  27. package/extensions/dashboard-server/routes-vector-cortex-policy.ts +35 -16
  28. package/package.json +1 -1
  29. package/src/vector-cortex/cache/store.ts +26 -2
  30. package/src/vector-cortex/encoder/asset.ts +46 -3
  31. package/src/vector-cortex/encoder/router.ts +56 -2
  32. package/src/vector-cortex/encoder/types.ts +3 -0
  33. package/src/vector-cortex/livewire/livewire-live.ts +223 -0
  34. package/src/vector-cortex/livewire/livewire-registry.ts +91 -0
  35. package/src/vector-cortex/livewire/livewire-runtime.ts +117 -0
  36. package/src/vector-cortex/livewire/livewire-snapshot.ts +108 -0
  37. package/src/vector-cortex/livewire/livewire-types.ts +84 -0
  38. package/src/vector-cortex/provider/economics.ts +9 -31
  39. package/src/vector-cortex/provider/registry.ts +82 -11
  40. package/src/vector-cortex/provider/types.ts +37 -0
  41. package/src/vector-cortex/setup-cortex-blockers-compute.ts +48 -7
@@ -0,0 +1,223 @@
1
+ /**
2
+ * vector-cortex/livewire/livewire-live.ts — LIVEWIRE in-process live state.
3
+ *
4
+ * Holds the LIVE subsystem objects for ONE stateDir: the VC7A `CrystalStore`,
5
+ * the VC7C cache breaker, the per-miss-class tallies, the VC8B shadow metrics,
6
+ * and the VC7B economics computed bit. This is the object the RUNTIME accumulates
7
+ * into (`recordMiss`, `recordServeBlocked`, `recordShadowRun`, ...) and that the
8
+ * reader-only dashboard routes snapshot out of. Kept separate from the registry
9
+ * (which owns the per-stateDir `Map` + persistence) so neither file crosses the
10
+ * 300-line soft-as-hard gate.
11
+ *
12
+ * AGGREGATE-FIRST, COUNTS ONLY. `snapshotOf` projects the live objects down to
13
+ * the SECURITY_PRIVACY-safe `LivewireSnapshot` (counts + codes + triad mode) that
14
+ * gets persisted and rendered. No session id, digest, range, profile id, bytes,
15
+ * or prompt text can pass through ANY of these fields.
16
+ *
17
+ * PREVENT-PI-004: no network. PREVENT-011: no `any`. Non-fatal: every derive is
18
+ * a pure read.
19
+ */
20
+
21
+ import { CrystalStore } from "../cache/store.js";
22
+ import { createCacheBreaker } from "../cache/breaker.js";
23
+ import type { MissClass } from "../cache/diagnostics-types.js";
24
+ import { validateProfileEconomics } from "../provider/economics.js";
25
+ import { BASE_PROVIDER_PROFILES } from "../provider/registry.js";
26
+ import type {
27
+ LivewireDiagnosticsAggregate,
28
+ LivewireEconomicsAggregate,
29
+ LivewirePolicyAggregate,
30
+ LivewireSnapshot,
31
+ } from "./livewire-types.js";
32
+
33
+ /** One VC7C miss classification tally (in-process, counts only). */
34
+ export interface LivewireDiagnosticsRecord {
35
+ readonly tallies: { [K in MissClass]: number };
36
+ serveBlocked: number;
37
+ /**
38
+ * Observable breaker state (CLOSED_A / OPEN_B / ... / MANUAL_HALT). Tracked as
39
+ * a field so it survives process restart: the runtime syncs it from the live
40
+ * breaker when persisting, and a reader process restores it from the snapshot
41
+ * (a fresh in-process breaker is always CLOSED_A, which would be misleading).
42
+ */
43
+ breakerState: string;
44
+ lastFailure: string | null;
45
+ }
46
+
47
+ /** One VC7B economics record (computed bit + profile tallies + last failure). */
48
+ export interface LivewireEconomicsRecord {
49
+ computed: boolean;
50
+ /** Provider profiles that declare cache economics (static from BASE_PROFILES). */
51
+ profileCount: number;
52
+ /** Exclusions that carry a proving fixture id (static from BASE_PROFILES). */
53
+ provenExclusions: number;
54
+ /** Exclusions rejected for lacking a fixture id (static from BASE_PROFILES). */
55
+ unprovenExclusions: number;
56
+ lastFailure: string | null;
57
+ }
58
+
59
+ /**
60
+ * Static VC7B economics profile tallies derived from the base provider registry.
61
+ * `profileCount` counts profiles that declare economics; `provenExclusions` /
62
+ * `unprovenExclusions` tally the exclusion sets validated against the fixture
63
+ * rule. This is PURE (no storage/clock/network) and identical regardless of the
64
+ * `MEGACOMPACT_VC7B` flag — only the emission/route seam is flag-gated.
65
+ */
66
+ function baseEconomicsTallies(): {
67
+ profileCount: number;
68
+ provenExclusions: number;
69
+ unprovenExclusions: number;
70
+ } {
71
+ let profileCount = 0;
72
+ let provenExclusions = 0;
73
+ let unprovenExclusions = 0;
74
+ for (const bundle of BASE_PROVIDER_PROFILES) {
75
+ const econ = bundle.profile.economics;
76
+ if (econ === null) continue;
77
+ profileCount += 1;
78
+ const codes = validateProfileEconomics(bundle.profile, econ);
79
+ const failed = new Set(codes);
80
+ for (const _ex of bundle.profile.excludedJsonPointers) {
81
+ if (failed.has("ECON_EXCLUSION_UNPROVEN")) {
82
+ unprovenExclusions += 1;
83
+ } else {
84
+ provenExclusions += 1;
85
+ }
86
+ }
87
+ }
88
+ return { profileCount, provenExclusions, unprovenExclusions };
89
+ }
90
+
91
+ /** One VC8B shadow-policy record (metrics + active pressure version). */
92
+ export interface LivewireShadowRecord {
93
+ shadowDecisions: number;
94
+ clampedDecisions: number;
95
+ rejectedInputs: number;
96
+ liveMutations: number;
97
+ pressureVersion: 1 | 2;
98
+ lastFailure: string | null;
99
+ }
100
+
101
+ /** The live, in-process state for one stateDir. */
102
+ export interface LivewireLiveState {
103
+ readonly crystalStore: CrystalStore;
104
+ readonly breaker: ReturnType<typeof createCacheBreaker>;
105
+ readonly diagnostics: LivewireDiagnosticsRecord;
106
+ readonly economics: LivewireEconomicsRecord;
107
+ readonly shadow: LivewireShadowRecord;
108
+ }
109
+
110
+ /** A zeroed per-class tally map. */
111
+ function zeroTallies(): LivewireDiagnosticsRecord["tallies"] {
112
+ return {
113
+ profile: 0,
114
+ range: 0,
115
+ dependency: 0,
116
+ request: 0,
117
+ generation: 0,
118
+ unknown: 0,
119
+ };
120
+ }
121
+
122
+ /** Build a fresh (empty) live state with real subsystem objects. */
123
+ export function createLiveState(): LivewireLiveState {
124
+ const econTallies = baseEconomicsTallies();
125
+ return {
126
+ crystalStore: new CrystalStore(),
127
+ breaker: createCacheBreaker(),
128
+ diagnostics: {
129
+ tallies: zeroTallies(),
130
+ serveBlocked: 0,
131
+ breakerState: "CLOSED_A",
132
+ lastFailure: null,
133
+ },
134
+ economics: {
135
+ computed: false,
136
+ profileCount: econTallies.profileCount,
137
+ provenExclusions: econTallies.provenExclusions,
138
+ unprovenExclusions: econTallies.unprovenExclusions,
139
+ lastFailure: null,
140
+ },
141
+ shadow: {
142
+ shadowDecisions: 0,
143
+ clampedDecisions: 0,
144
+ rejectedInputs: 0,
145
+ liveMutations: 0,
146
+ pressureVersion: 1,
147
+ lastFailure: null,
148
+ },
149
+ };
150
+ }
151
+
152
+ /** Project the live state down to the persisted, reader-only aggregate. */
153
+ export function snapshotOf(state: LivewireLiveState): LivewireSnapshot {
154
+ const crystals = state.crystalStore.stats();
155
+ const diag: LivewireDiagnosticsAggregate = {
156
+ profileMisses: state.diagnostics.tallies.profile,
157
+ rangeMisses: state.diagnostics.tallies.range,
158
+ dependencyMisses: state.diagnostics.tallies.dependency,
159
+ requestMisses: state.diagnostics.tallies.request,
160
+ generationMisses: state.diagnostics.tallies.generation,
161
+ unknownMisses: state.diagnostics.tallies.unknown,
162
+ serveBlocked: state.diagnostics.serveBlocked,
163
+ breakerState: state.diagnostics.breakerState,
164
+ lastFailure: state.diagnostics.lastFailure,
165
+ };
166
+ const econ: LivewireEconomicsAggregate = {
167
+ profileCount: state.economics.profileCount,
168
+ provenExclusions: state.economics.provenExclusions,
169
+ unprovenExclusions: state.economics.unprovenExclusions,
170
+ computed: state.economics.computed,
171
+ lastFailure: state.economics.lastFailure,
172
+ };
173
+ const policy: LivewirePolicyAggregate = {
174
+ shadowDecisions: state.shadow.shadowDecisions,
175
+ clampedDecisions: state.shadow.clampedDecisions,
176
+ rejectedInputs: state.shadow.rejectedInputs,
177
+ liveMutations: state.shadow.liveMutations,
178
+ pressureVersion: state.shadow.pressureVersion,
179
+ lastFailure: state.shadow.lastFailure,
180
+ };
181
+ return {
182
+ schema: "vector-cortex-livewire-v1",
183
+ crystals: {
184
+ mode: crystals.mode,
185
+ crystalCount: crystals.crystalCount,
186
+ totalBytes: crystals.totalBytes,
187
+ hits: crystals.hits,
188
+ misses: crystals.misses,
189
+ hitBytes: crystals.hitBytes,
190
+ writes: crystals.writes,
191
+ duplicateWrites: crystals.duplicateWrites,
192
+ collisions: crystals.collisions,
193
+ },
194
+ diagnostics: diag,
195
+ economics: econ,
196
+ policy,
197
+ };
198
+ }
199
+
200
+ /** Seed a fresh live state's CUMULATIVE counters from a persisted snapshot. */
201
+ export function rehydrateLive(state: LivewireLiveState, snap: LivewireSnapshot): void {
202
+ state.crystalStore.rehydrate(snap.crystals);
203
+ state.diagnostics.tallies.profile = snap.diagnostics.profileMisses;
204
+ state.diagnostics.tallies.range = snap.diagnostics.rangeMisses;
205
+ state.diagnostics.tallies.dependency = snap.diagnostics.dependencyMisses;
206
+ state.diagnostics.tallies.request = snap.diagnostics.requestMisses;
207
+ state.diagnostics.tallies.generation = snap.diagnostics.generationMisses;
208
+ state.diagnostics.tallies.unknown = snap.diagnostics.unknownMisses;
209
+ state.diagnostics.serveBlocked = snap.diagnostics.serveBlocked;
210
+ state.diagnostics.breakerState = snap.diagnostics.breakerState;
211
+ state.diagnostics.lastFailure = snap.diagnostics.lastFailure;
212
+ state.economics.computed = snap.economics.computed;
213
+ state.economics.profileCount = snap.economics.profileCount;
214
+ state.economics.provenExclusions = snap.economics.provenExclusions;
215
+ state.economics.unprovenExclusions = snap.economics.unprovenExclusions;
216
+ state.economics.lastFailure = snap.economics.lastFailure;
217
+ state.shadow.shadowDecisions = snap.policy.shadowDecisions;
218
+ state.shadow.clampedDecisions = snap.policy.clampedDecisions;
219
+ state.shadow.rejectedInputs = snap.policy.rejectedInputs;
220
+ state.shadow.liveMutations = snap.policy.liveMutations;
221
+ state.shadow.pressureVersion = snap.policy.pressureVersion;
222
+ state.shadow.lastFailure = snap.policy.lastFailure;
223
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * vector-cortex/livewire/livewire-registry.ts — per-stateDir LIWIRE registry.
3
+ *
4
+ * The single seam the runtime and the reader-only dashboard routes BOTH access.
5
+ * It owns a `Map<stateDir, LivewireLiveState>` (one live subsystem cluster per
6
+ * repo) and lazily opens a state on first access, rehydrating its cumulative
7
+ * counters from the persisted aggregate snapshot (`livewire-snapshot.ts`) so a
8
+ * freshly-spawned process — e.g. the dashboard server — reports the same counts
9
+ * the runtime has already accumulated.
10
+ *
11
+ * The runtime WRITES through this registry (see `livewire-runtime.ts`): each
12
+ * mutation persists a reduced, count-only snapshot. The routes READ through
13
+ * `livewireOf(stateDir)` and call the pure `snapshotOf`. There is no other path.
14
+ *
15
+ * BEST-EFFORT + NON-FATAL. `openLivewire` never throws: persistence failures are
16
+ * swallowed by the snapshot layer and state is always returned. PREVENT-PI-004
17
+ * (local in-process Map + filesystem only), PREVENT-011 (no `any`).
18
+ */
19
+
20
+ import type { LivewireSnapshot } from "./livewire-types.js";
21
+ import {
22
+ createLiveState,
23
+ rehydrateLive,
24
+ snapshotOf,
25
+ type LivewireLiveState,
26
+ } from "./livewire-live.js";
27
+ import {
28
+ loadLivewireSnapshot,
29
+ saveLivewireSnapshot,
30
+ } from "./livewire-snapshot.js";
31
+
32
+ /** The per-stateDir registry (process-local; a fresh process starts empty). */
33
+ const REGISTRY = new Map<string, LivewireLiveState>();
34
+
35
+ /** Optional structured logger for best-effort failure events. */
36
+ export type LivewireLogger = (line: unknown) => void;
37
+
38
+ let activeLogger: LivewireLogger | undefined;
39
+
40
+ /**
41
+ * Bind the structured logger the snapshot layer uses for its non-fatal write
42
+ * failures. The runtime calls this once at startup with its JSON logger.
43
+ */
44
+ export function setLivewireLogger(logger: LivewireLogger | undefined): void {
45
+ activeLogger = logger;
46
+ }
47
+
48
+ /**
49
+ * Open (or return the cached) live state for a stateDir. Lazy: on first access
50
+ * it rehydrates from the persisted aggregate so a separate dashboard process
51
+ * reflects prior runtime work. Never throws.
52
+ */
53
+ export function livewireOf(stateDir: string): LivewireLiveState {
54
+ const cached = REGISTRY.get(stateDir);
55
+ if (cached !== undefined) return cached;
56
+ const state = createLiveState();
57
+ const snap = loadLivewireSnapshot(stateDir);
58
+ if (snap !== null) rehydrateLive(state, snap);
59
+ REGISTRY.set(stateDir, state);
60
+ return state;
61
+ }
62
+
63
+ /**
64
+ * Persist a state's reduced aggregate (counts + codes only). Best-effort and
65
+ * non-fatal. Called by the runtime after every mutation so the snapshot stays
66
+ * fresh for any reader process.
67
+ */
68
+ export function persistLivewire(state: LivewireLiveState, stateDir: string): void {
69
+ saveLivewireSnapshot(stateDir, snapshotOf(state), activeLogger);
70
+ }
71
+
72
+ /** Persist the live state for a stateDir (convenience over open + persist). */
73
+ export function flushLivewire(stateDir: string): void {
74
+ const state = REGISTRY.get(stateDir);
75
+ if (state === undefined) return;
76
+ persistLivewire(state, stateDir);
77
+ }
78
+
79
+ /**
80
+ * Build the reader aggregate for one stateDir WITHOUT persisting — the reader
81
+ * seam the dashboard routes call. Reads the live state (rehydrated from disk on
82
+ * first access) and projects it to the count-only snapshot.
83
+ */
84
+ export function readLivewireSnapshot(stateDir: string): LivewireSnapshot {
85
+ return snapshotOf(livewireOf(stateDir));
86
+ }
87
+
88
+ /** For tests: drop the registry so a fresh stateDir is fully rehydrated. */
89
+ export function _resetLivewireRegistryForTests(): void {
90
+ REGISTRY.clear();
91
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * vector-cortex/livewire/livewire-runtime.ts — LIVEWIRE runtime accumulation seam.
3
+ *
4
+ * The WRITE side of LIVEWIRE: the runtime calls these methods wherever the
5
+ * corresponding subsystem path actually runs, and each call accumulates into the
6
+ * per-stateDir live state and persists a reduced count-only snapshot (best-effort,
7
+ * non-fatal). This is the seam that turns the pure VC7A/VC7B/VC7C/VC8B arithmetic
8
+ * into LIVE dashboard state.
9
+ *
10
+ * FLAG SEMANTICS (mirrors every other VC writer): these methods run REGARDLESS of
11
+ * the corresponding `MEGACOMPACT_VC*` flag — the arithmetic is never skipped, and
12
+ * a miss is still classified / a write still stored exactly as before. Only the
13
+ * dashboard REPORT seam (`routes-vector-cortex-*.ts`) is flag-gated: with the
14
+ * flag off a route returns the legacy `deferredReason` (byte-identical to the
15
+ * predecessor). The reporter emit seams in `cache/*-emit.ts` / `controller/
16
+ * policy-emit.ts` remain the event-announcement gate.
17
+ *
18
+ * PREVENT-PI-004: no network. PREVENT-011: no `any`. Every write is best-effort.
19
+ */
20
+
21
+ import type { MissClass, MissObservation } from "../cache/diagnostics-types.js";
22
+ import { classifyMiss } from "../cache/diagnostics.js";
23
+ import { shouldBlockServe } from "../cache/breaker.js";
24
+ import type { CrystalV1, CrystalWriteResult } from "../cache/types.js";
25
+ import type { ShadowResult } from "../controller/types.js";
26
+ import { livewireOf, persistLivewire } from "./livewire-registry.js";
27
+
28
+ /**
29
+ * Record a VC7A crystal WRITE attempt (write-once + collision arithmetic). The
30
+ * caller passes the fully-formed `CrystalV1`; the store returns the write result
31
+ * (first-write / idempotent / collision). The dashboard's `stats()` reflects it.
32
+ *
33
+ * @returns the store's write verdict, forwarded so the runtime can act on a
34
+ * collision without re-deriving it.
35
+ */
36
+ export function recordCrystalWrite(stateDir: string, crystal: CrystalV1): CrystalWriteResult {
37
+ const state = livewireOf(stateDir);
38
+ const result = state.crystalStore.write(crystal);
39
+ persistLivewire(state, stateDir);
40
+ return result;
41
+ }
42
+
43
+ /**
44
+ * Record a VC7A crystal READ attempt. Returns the stored crystal (or undefined
45
+ * on a miss / mode C), mirroring `CrystalStore.read` so the cache-serve path can
46
+ * use this seam entirely.
47
+ */
48
+ export function readCrystal(stateDir: string, keyDigest: string): CrystalV1 | undefined {
49
+ const state = livewireOf(stateDir);
50
+ const found = state.crystalStore.read(keyDigest);
51
+ persistLivewire(state, stateDir);
52
+ return found;
53
+ }
54
+
55
+ /**
56
+ * Record a VC7C miss observation: classify it into its exclusive class and tally
57
+ * it. When the class demands the cache serve be demoted BEFORE answering, the
58
+ * `serveBlocked` counter is also incremented. The live breaker state itself is
59
+ * driven by the real cache-serve path (through `breaker.execute`), not here; this
60
+ * seam only observes and tallies.
61
+ *
62
+ * @returns the exclusive class the observation was tallied under.
63
+ */
64
+ export function observeCacheMiss(stateDir: string, observation: MissObservation): MissClass {
65
+ const state = livewireOf(stateDir);
66
+ const missClass = classifyMiss(observation).missClass;
67
+ state.diagnostics.tallies[missClass] += 1;
68
+ if (shouldBlockServe(missClass)) state.diagnostics.serveBlocked += 1;
69
+ persistLivewire(state, stateDir);
70
+ return missClass;
71
+ }
72
+
73
+ /**
74
+ * Record a VC7C cache serve that the breaker demoted BEFORE answering. Talls the
75
+ * `serveBlocked` counter the diagnostics card surfaces.
76
+ */
77
+ export function recordServeBlocked(stateDir: string): void {
78
+ const state = livewireOf(stateDir);
79
+ state.diagnostics.serveBlocked += 1;
80
+ persistLivewire(state, stateDir);
81
+ }
82
+
83
+ /**
84
+ * Record a VC8B shadow evaluation run: accumulate its decision metrics so the
85
+ * policy card reports how many shadow decisions were evaluated / clamped /
86
+ * rejected and how many live mutations the shadow proved (structurally 0).
87
+ */
88
+ export function recordShadowRun(stateDir: string, result: ShadowResult): void {
89
+ const state = livewireOf(stateDir);
90
+ state.shadow.shadowDecisions += result.metrics.evaluated;
91
+ state.shadow.clampedDecisions += result.metrics.clamped;
92
+ state.shadow.rejectedInputs += result.metrics.rejected;
93
+ state.shadow.liveMutations += result.metrics.liveMutations;
94
+ persistLivewire(state, stateDir);
95
+ }
96
+
97
+ /**
98
+ * Set the active M7 pressure version (1 = legacy, 2 = migrated). The runtime
99
+ * calls this after a successful `migratePressureV2` so the policy card reflects
100
+ * the live migration state rather than a hardcoded 1.
101
+ */
102
+ export function setPressureVersion(stateDir: string, version: 1 | 2): void {
103
+ const state = livewireOf(stateDir);
104
+ state.shadow.pressureVersion = version;
105
+ persistLivewire(state, stateDir);
106
+ }
107
+
108
+ /**
109
+ * Mark that VC7B cache economics have actually been computed at runtime (the
110
+ * `computed` bit drives the economics card's `hasData`). The runtime calls this
111
+ * after the first real `computeEconomics` over observed usage.
112
+ */
113
+ export function markEconomicsComputed(stateDir: string): void {
114
+ const state = livewireOf(stateDir);
115
+ state.economics.computed = true;
116
+ persistLivewire(state, stateDir);
117
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * vector-cortex/livewire/livewire-snapshot.ts — LIVEWIRE aggregate persistence.
3
+ *
4
+ * The four LV subsytems are in-process live objects, but the dashboard server may
5
+ * run in a SEPARATE process from the runtime that accumulates the counts. To keep
6
+ * the reader-only routes honest when they run in their own process, this module
7
+ * persists a COUNT-ONLY aggregate snapshot (`vector-cortex-livewire.json`) to the
8
+ * per-repo `stateDir`, and `openLivewire` rehydrates a fresh process from it on
9
+ * first access. This is the same DR-snapshot philosophy as the legacy JSON
10
+ * checkpoints, but reduced to counts + codes (SECURITY_PRIVACY — see these types).
11
+ *
12
+ * BEST-EFFORT + NON-FATAL. A failed write is logged as a structured event and
13
+ * never breaks the agent loop; a missing/unreadable snapshot reads as null and
14
+ * the process starts from zero (matches the non-fatal-stores invariant). Every
15
+ * write is atomic-ish: the JSON is fully serialized to a temp name then renamed
16
+ * so a reader never observes a partial snapshot.
17
+ *
18
+ * PREVENT-PI-004: local filesystem read/write only, no network. PREVENT-011: no
19
+ * `any`.
20
+ */
21
+
22
+ import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from "node:fs";
23
+ import { join } from "node:path";
24
+ import type { LivewireSnapshot } from "./livewire-types.js";
25
+
26
+ /** The snapshot filename in the per-repo stateDir (counts + codes only). */
27
+ export const LIVEWIRE_SNAPSHOT_FILE = "vector-cortex-livewire.json";
28
+
29
+ /** Resolve the snapshot path for a stateDir. */
30
+ export function livewireSnapshotPath(stateDir: string): string {
31
+ return join(stateDir, LIVEWIRE_SNAPSHOT_FILE);
32
+ }
33
+
34
+ /**
35
+ * Best-effort type guard over a parsed snapshot. Rejects anything that is not
36
+ * the exact aggregate shape so a corrupted or stale file cannot poison the
37
+ * rehydrated counters.
38
+ */
39
+ function isSnapshot(value: unknown): value is LivewireSnapshot {
40
+ if (typeof value !== "object" || value === null) return false;
41
+ const s = value as Record<string, unknown>;
42
+ return (
43
+ s.schema === "vector-cortex-livewire-v1" &&
44
+ typeof s.crystals === "object" &&
45
+ s.crystals !== null &&
46
+ typeof s.diagnostics === "object" &&
47
+ s.diagnostics !== null &&
48
+ typeof s.economics === "object" &&
49
+ s.economics !== null &&
50
+ typeof s.policy === "object" &&
51
+ s.policy !== null
52
+ );
53
+ }
54
+
55
+ /**
56
+ * Read the persisted aggregate for a stateDir, or null when absent/unreadable/
57
+ * malformed. Best-effort and non-fatal by construction.
58
+ */
59
+ export function loadLivewireSnapshot(stateDir: string): LivewireSnapshot | null {
60
+ const path = livewireSnapshotPath(stateDir);
61
+ let raw: string;
62
+ try {
63
+ raw = readFileSync(path, "utf-8");
64
+ } catch {
65
+ return null;
66
+ }
67
+ try {
68
+ const parsed: unknown = JSON.parse(raw);
69
+ return isSnapshot(parsed) ? parsed : null;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Best-effort write of the aggregate snapshot (atomic rename). Never throws to
77
+ * the caller: a persistence failure logs a structured event and is swallowed.
78
+ *
79
+ * @param logger optional structured logger `(line: unknown) => void`; when
80
+ * omitted no event is emitted (tests pass `undefined`).
81
+ */
82
+ export function saveLivewireSnapshot(
83
+ stateDir: string,
84
+ snapshot: LivewireSnapshot,
85
+ logger?: (line: unknown) => void,
86
+ ): void {
87
+ const dir = stateDir;
88
+ try {
89
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
90
+ const path = livewireSnapshotPath(dir);
91
+ const tmp = `${path}.tmp`;
92
+ writeFileSync(tmp, JSON.stringify(snapshot), "utf-8");
93
+ renameSync(tmp, path);
94
+ } catch (err) {
95
+ if (logger !== undefined) {
96
+ try {
97
+ logger({
98
+ ts: new Date().toISOString(),
99
+ event: "vector_cortex_livewire_snapshot_write_failed",
100
+ stateDir,
101
+ reason: err instanceof Error ? err.message : String(err),
102
+ });
103
+ } catch {
104
+ // A failing logger must not recurse into another failure.
105
+ }
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * vector-cortex/livewire/livewire-types.ts — LIVEWIRE aggregate snapshot types.
3
+ *
4
+ * LIVEWIRE wires the four complete-but-unwired Vector Cortex subsystems into the
5
+ * runtime so their dashboard routes report LIVE state instead of hardcoded
6
+ * "deferred" zeros. This module is the SINGLE SOURCE OF TRUTH for the persisted
7
+ * aggregate snapshot shape (counts + codes ONLY) and for the per-stateDir
8
+ * live-state records the routes read.
9
+ *
10
+ * SECURITY_PRIVACY: every field here is a count, a code, or a finite triad mode.
11
+ * There is deliberately NO string slot for a session id, a request/crystal
12
+ * digest, a covered range, frozen bytes, a profile id, or ledger content — a
13
+ * crystal IS a frozen rendered prompt, so the card that reports on it must never
14
+ * be able to carry one. The snapshot is the SAME reduced shape, so nothing secret
15
+ * reaches disk either.
16
+ *
17
+ * PREVENT-PI-004: type definitions only, no network code. PREVENT-011: no `any`.
18
+ */
19
+
20
+ /** Triad mode shared by the crystals / diagnostics / economics cards. */
21
+ export type LivewireMode = "A" | "B" | "C";
22
+
23
+ /** VC7A crystal aggregate — mirrors `CrystalStoreStats` one-for-one. */
24
+ export interface LivewireCrystalAggregate {
25
+ readonly mode: LivewireMode;
26
+ readonly crystalCount: number;
27
+ readonly totalBytes: number;
28
+ readonly hits: number;
29
+ readonly misses: number;
30
+ readonly hitBytes: number;
31
+ readonly writes: number;
32
+ readonly duplicateWrites: number;
33
+ readonly collisions: number;
34
+ }
35
+
36
+ /** VC7C per-miss-class tallies + breaker observable state. */
37
+ export interface LivewireDiagnosticsAggregate {
38
+ readonly profileMisses: number;
39
+ readonly rangeMisses: number;
40
+ readonly dependencyMisses: number;
41
+ readonly requestMisses: number;
42
+ readonly generationMisses: number;
43
+ readonly unknownMisses: number;
44
+ readonly serveBlocked: number;
45
+ /** Breaker observable state name (CLOSED_A / OPEN_B / ... / MANUAL_HALT). */
46
+ readonly breakerState: string;
47
+ /** Last CACHE/M5 code, or null. */
48
+ readonly lastFailure: string | null;
49
+ }
50
+
51
+ /** VC7B economics aggregate — static profile tallies + computed bit. */
52
+ export interface LivewireEconomicsAggregate {
53
+ /** Provider profiles that declare cache economics. */
54
+ readonly profileCount: number;
55
+ /** Exclusions that carry a proving fixture id. */
56
+ readonly provenExclusions: number;
57
+ /** Exclusions rejected for lacking a fixture id. */
58
+ readonly unprovenExclusions: number;
59
+ /** True once the runtime has actually run `computeEconomics` at least once. */
60
+ readonly computed: boolean;
61
+ /** Last ECON_* code, or null. */
62
+ readonly lastFailure: string | null;
63
+ }
64
+
65
+ /** VC8B shadow-policy aggregate — maps 1:1 to the policy card fields. */
66
+ export interface LivewirePolicyAggregate {
67
+ readonly shadowDecisions: number;
68
+ readonly clampedDecisions: number;
69
+ readonly rejectedInputs: number;
70
+ readonly liveMutations: number;
71
+ /** Active pressure version (1 legacy / 2 migrated). */
72
+ readonly pressureVersion: 1 | 2;
73
+ /** Last POL_ or M7_ code, or null. */
74
+ readonly lastFailure: string | null;
75
+ }
76
+
77
+ /** The persisted, restart-surviving aggregate for one stateDir. */
78
+ export interface LivewireSnapshot {
79
+ readonly schema: "vector-cortex-livewire-v1";
80
+ readonly crystals: LivewireCrystalAggregate;
81
+ readonly diagnostics: LivewireDiagnosticsAggregate;
82
+ readonly economics: LivewireEconomicsAggregate;
83
+ readonly policy: LivewirePolicyAggregate;
84
+ }
@@ -47,38 +47,16 @@
47
47
  * gates only the reporter/dashboard seam in `../cache/economics-emit.ts`.
48
48
  */
49
49
 
50
- import type { ProviderProfileExclusion, ProviderProfileV1 } from "./types.js";
50
+ import type {
51
+ ProviderEconomicsV1,
52
+ ProviderProfileExclusion,
53
+ ProviderProfileV1,
54
+ } from "./types.js";
51
55
 
52
- /**
53
- * Cache economics attached to a provider profile.
54
- *
55
- * Prices are integer MICRO-UNITS PER TOKEN (1e-6 currency units), so a provider
56
- * charging $3.00 per million input tokens has `basePrice: 3`. Integers keep the
57
- * money path exact; see the file header for why floats are refused.
58
- */
59
- export interface ProviderEconomicsV1 {
60
- readonly schema: "provider-economics-v1";
61
- /** The `ProviderProfileV1.id` these economics belong to. */
62
- readonly profileId: string;
63
- /** Profile version — economics are versioned WITH the profile they price. */
64
- readonly profileVersion: string;
65
- /** Uncached price per token, integer micro-units. The savings baseline. */
66
- readonly basePrice: number;
67
- /** Cache-READ price per token, integer micro-units. Normally < basePrice. */
68
- readonly readPrice: number;
69
- /** Cache-WRITE price per token, integer micro-units. Normally > basePrice. */
70
- readonly writePrice: number;
71
- /** Cache entry lifetime in ms. A prefix older than this cannot be read back. */
72
- readonly ttlMs: number;
73
- /** Minimum cacheable prefix in tokens; a shorter prefix is never cached. */
74
- readonly minPrefix: number;
75
- /**
76
- * Conformance fixture ID proving this profile's exclusion set is safe, or
77
- * `null` when the profile declares NO exclusions (nothing to prove). A profile
78
- * WITH exclusions and a null/blank id is rejected — see `validateEconomics`.
79
- */
80
- readonly exclusionFixtureId: string | null;
81
- }
56
+ // `ProviderEconomicsV1` is defined in `./types.js` so it can ride on
57
+ // `ProviderProfileV1` without a type-only module cycle. Re-exported forward here
58
+ // so every existing consumer import path (`from "./economics.js"`) is unchanged.
59
+ export type { ProviderEconomicsV1 } from "./types.js";
82
60
 
83
61
  /** Observed (or shadow) cache traffic for one economics computation. */
84
62
  export interface CacheUsageV1 {