pi-mega-compact 0.20.15 → 0.20.17

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 (110) hide show
  1. package/dist/config/vector-cortex.js +14 -0
  2. package/dist/config.js +1 -1
  3. package/dist/extensions/dashboard-server/api-contracts/vector-cortex-diagnostics.js +35 -0
  4. package/dist/extensions/dashboard-server/api-contracts/vector-cortex-outcomes.js +13 -0
  5. package/dist/extensions/dashboard-server/route-dispatch.js +12 -1
  6. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
  7. package/dist/extensions/dashboard-server/routes-vector-cortex-diagnostics.js +68 -0
  8. package/dist/extensions/dashboard-server/routes-vector-cortex-outcomes.js +79 -0
  9. package/dist/extensions/mega-dashboard.js +29 -9
  10. package/dist/src/config/vector-cortex.js +14 -0
  11. package/dist/src/config.js +1 -1
  12. package/dist/src/vector-cortex/cache/_diagnostics-fixture.js +31 -0
  13. package/dist/src/vector-cortex/cache/breaker.js +110 -0
  14. package/dist/src/vector-cortex/cache/diagnostics-emit.js +64 -0
  15. package/dist/src/vector-cortex/cache/diagnostics-types.js +15 -0
  16. package/dist/src/vector-cortex/cache/diagnostics.js +97 -0
  17. package/dist/src/vector-cortex/migrations/request-hash-v2-ops.js +17 -6
  18. package/dist/src/vector-cortex/outcomes/consent.js +69 -0
  19. package/dist/src/vector-cortex/outcomes/dataset.js +105 -0
  20. package/dist/src/vector-cortex/outcomes/emit.js +52 -0
  21. package/dist/src/vector-cortex/outcomes/ledger.js +77 -0
  22. package/dist/src/vector-cortex/outcomes/types.js +34 -0
  23. package/dist/vector-cortex/cache/_diagnostics-fixture.js +31 -0
  24. package/dist/vector-cortex/cache/breaker.js +110 -0
  25. package/dist/vector-cortex/cache/diagnostics-emit.js +64 -0
  26. package/dist/vector-cortex/cache/diagnostics-types.js +15 -0
  27. package/dist/vector-cortex/cache/diagnostics.js +97 -0
  28. package/dist/vector-cortex/migrations/request-hash-v2-ops.js +17 -6
  29. package/dist/vector-cortex/outcomes/consent.js +69 -0
  30. package/dist/vector-cortex/outcomes/dataset.js +105 -0
  31. package/dist/vector-cortex/outcomes/emit.js +52 -0
  32. package/dist/vector-cortex/outcomes/ledger.js +77 -0
  33. package/dist/vector-cortex/outcomes/types.js +34 -0
  34. package/extensions/dashboard-client/dist/assets/{AreaChart-CV8WqMmt.js → AreaChart-BOGp2sag.js} +2 -2
  35. package/extensions/dashboard-client/dist/assets/{AreaChart-CV8WqMmt.js.map → AreaChart-BOGp2sag.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/assets/{BarChart-M-HEBq9l.js → BarChart-BHgA-QPL.js} +2 -2
  37. package/extensions/dashboard-client/dist/assets/{BarChart-M-HEBq9l.js.map → BarChart-BHgA-QPL.js.map} +1 -1
  38. package/extensions/dashboard-client/dist/assets/{CacheTab-DJ_yPAhP.js → CacheTab-6E7lwM74.js} +2 -2
  39. package/extensions/dashboard-client/dist/assets/{CacheTab-DJ_yPAhP.js.map → CacheTab-6E7lwM74.js.map} +1 -1
  40. package/extensions/dashboard-client/dist/assets/{EventsTab-DNVlKzt6.js → EventsTab-BEtNmFHz.js} +2 -2
  41. package/extensions/dashboard-client/dist/assets/{EventsTab-DNVlKzt6.js.map → EventsTab-BEtNmFHz.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/{HealthTab-B5yjuInb.js → HealthTab-DGxZulRH.js} +2 -2
  43. package/extensions/dashboard-client/dist/assets/{HealthTab-B5yjuInb.js.map → HealthTab-DGxZulRH.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DoQedESF.js → MaintenanceTab-C86jppQE.js} +2 -2
  45. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DoQedESF.js.map → MaintenanceTab-C86jppQE.js.map} +1 -1
  46. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CuL5CzdH.js → MemoryMapTab-J3VVxooZ.js} +2 -2
  47. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CuL5CzdH.js.map → MemoryMapTab-J3VVxooZ.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/{MetricsTab-CZSTaCpK.js → MetricsTab-yn9AFumo.js} +2 -2
  49. package/extensions/dashboard-client/dist/assets/{MetricsTab-CZSTaCpK.js.map → MetricsTab-yn9AFumo.js.map} +1 -1
  50. package/extensions/dashboard-client/dist/assets/{OverviewTab-vkZlXRW6.js → OverviewTab-BMarUiq3.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{OverviewTab-vkZlXRW6.js.map → OverviewTab-BMarUiq3.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/{ReposTab-DgPD96rF.js → ReposTab-DxnzMive.js} +2 -2
  53. package/extensions/dashboard-client/dist/assets/{ReposTab-DgPD96rF.js.map → ReposTab-DxnzMive.js.map} +1 -1
  54. package/extensions/dashboard-client/dist/assets/{SessionsTab-XPbWh7XN.js → SessionsTab-DXKpDUuC.js} +2 -2
  55. package/extensions/dashboard-client/dist/assets/{SessionsTab-XPbWh7XN.js.map → SessionsTab-DXKpDUuC.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/{SetupTab-Dz74XAtR.js → SetupTab-BctuV-CJ.js} +2 -2
  57. package/extensions/dashboard-client/dist/assets/{SetupTab-Dz74XAtR.js.map → SetupTab-BctuV-CJ.js.map} +1 -1
  58. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-FynCpJdx.js → TimeSavedCard-D8yOC96S.js} +2 -2
  59. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-FynCpJdx.js.map → TimeSavedCard-D8yOC96S.js.map} +1 -1
  60. package/extensions/dashboard-client/dist/assets/{TurnsTab-C7m3GDy0.js → TurnsTab-BBxqGYH-.js} +2 -2
  61. package/extensions/dashboard-client/dist/assets/{TurnsTab-C7m3GDy0.js.map → TurnsTab-BBxqGYH-.js.map} +1 -1
  62. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BTF1AGCJ.js +2 -0
  63. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BTF1AGCJ.js.map +1 -0
  64. package/extensions/dashboard-client/dist/assets/{WikiTab-C6viLa0u.js → WikiTab-DdJQSagO.js} +2 -2
  65. package/extensions/dashboard-client/dist/assets/{WikiTab-C6viLa0u.js.map → WikiTab-DdJQSagO.js.map} +1 -1
  66. package/extensions/dashboard-client/dist/assets/{button-mBEgbBVU.js → button-B4Q9h_8Y.js} +2 -2
  67. package/extensions/dashboard-client/dist/assets/{button-mBEgbBVU.js.map → button-B4Q9h_8Y.js.map} +1 -1
  68. package/extensions/dashboard-client/dist/assets/{card-CalUQAKG.js → card-WyR31hUV.js} +2 -2
  69. package/extensions/dashboard-client/dist/assets/{card-CalUQAKG.js.map → card-WyR31hUV.js.map} +1 -1
  70. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-CMmkuwj5.js → generateCategoricalChart-pTio6Bq3.js} +2 -2
  71. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-CMmkuwj5.js.map → generateCategoricalChart-pTio6Bq3.js.map} +1 -1
  72. package/extensions/dashboard-client/dist/assets/{index-Dz8GPBOE.js → index-BoLUiNsd.js} +3 -3
  73. package/extensions/dashboard-client/dist/assets/{index-Dz8GPBOE.js.map → index-BoLUiNsd.js.map} +1 -1
  74. package/extensions/dashboard-client/dist/assets/{switch-8QKO6CTU.js → switch-5dieXxoo.js} +2 -2
  75. package/extensions/dashboard-client/dist/assets/{switch-8QKO6CTU.js.map → switch-5dieXxoo.js.map} +1 -1
  76. package/extensions/dashboard-client/dist/assets/{toggle-9eCkWI4q.js → toggle-BFc-7_LL.js} +2 -2
  77. package/extensions/dashboard-client/dist/assets/{toggle-9eCkWI4q.js.map → toggle-BFc-7_LL.js.map} +1 -1
  78. package/extensions/dashboard-client/dist/assets/{useSSE-s6IqOKoD.js → useSSE-BqmKNBAI.js} +2 -2
  79. package/extensions/dashboard-client/dist/assets/{useSSE-s6IqOKoD.js.map → useSSE-BqmKNBAI.js.map} +1 -1
  80. package/extensions/dashboard-client/dist/index.html +1 -1
  81. package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
  82. package/extensions/dashboard-client/src/tabs/VectorCortexDiagnosticsCard.tsx +46 -0
  83. package/extensions/dashboard-client/src/tabs/VectorCortexOutcomesCard.tsx +43 -0
  84. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +4 -0
  85. package/extensions/dashboard-client/src/tabs/useVectorCortexPoll.ts +5 -0
  86. package/extensions/dashboard-client/src/types/vector-cortex-vc8.ts +25 -0
  87. package/extensions/dashboard-client/src/types/vector-cortex.ts +2 -0
  88. package/extensions/dashboard-server/api-contracts/vector-cortex-diagnostics.ts +76 -0
  89. package/extensions/dashboard-server/api-contracts/vector-cortex-outcomes.ts +55 -0
  90. package/extensions/dashboard-server/route-dispatch.ts +10 -1
  91. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
  92. package/extensions/dashboard-server/routes-vector-cortex-diagnostics.ts +77 -0
  93. package/extensions/dashboard-server/routes-vector-cortex-outcomes.ts +94 -0
  94. package/extensions/mega-dashboard.ts +27 -8
  95. package/package.json +1 -1
  96. package/src/config/vector-cortex.ts +15 -0
  97. package/src/config.ts +1 -0
  98. package/src/vector-cortex/cache/_diagnostics-fixture.ts +32 -0
  99. package/src/vector-cortex/cache/breaker.ts +124 -0
  100. package/src/vector-cortex/cache/diagnostics-emit.ts +90 -0
  101. package/src/vector-cortex/cache/diagnostics-types.ts +87 -0
  102. package/src/vector-cortex/cache/diagnostics.ts +119 -0
  103. package/src/vector-cortex/migrations/request-hash-v2-ops.ts +15 -9
  104. package/src/vector-cortex/outcomes/consent.ts +88 -0
  105. package/src/vector-cortex/outcomes/dataset.ts +132 -0
  106. package/src/vector-cortex/outcomes/emit.ts +70 -0
  107. package/src/vector-cortex/outcomes/ledger.ts +88 -0
  108. package/src/vector-cortex/outcomes/types.ts +104 -0
  109. package/extensions/dashboard-client/dist/assets/VectorCortexTab-jRd_Iztk.js +0 -2
  110. package/extensions/dashboard-client/dist/assets/VectorCortexTab-jRd_Iztk.js.map +0 -1
@@ -0,0 +1,25 @@
1
+ /**
2
+ * dashboard-client/src/types/vector-cortex-vc8.ts — VC8-era client view types.
3
+ *
4
+ * Extracted from vector-cortex.ts to keep the parent file under the 300-line
5
+ * soft limit (soft-as-hard gate). Each interface mirrors a reader-only server
6
+ * API contract: counts + codes only, never prompt payloads, response text,
7
+ * or free-text.
8
+ */
9
+
10
+ /**
11
+ * Reader-only outcomes aggregate view (VC8A). Counts + OUT_* codes only —
12
+ * the outcome ledger carries metrics without payload, so no prompt bytes,
13
+ * response text, free-text, or session content ever reaches the client.
14
+ */
15
+ export interface VectorCortexOutcomesView {
16
+ enabled: boolean;
17
+ mode: "A" | "B" | "C";
18
+ outcomeCount: number;
19
+ consentedSessions: number;
20
+ revokedSessions: number;
21
+ manifestCount: number;
22
+ excludedCount: number;
23
+ lastFailure: string | null;
24
+ updatedAt: string;
25
+ }
@@ -261,3 +261,5 @@ export type {
261
261
  VectorCortexEconomicsView,
262
262
  VectorCortexDiagnosticsView,
263
263
  } from "./vector-cortex-vc7.js";
264
+
265
+ export type { VectorCortexOutcomesView } from "./vector-cortex-vc8.js";
@@ -0,0 +1,76 @@
1
+ /**
2
+ * api-contracts/vector-cortex-diagnostics.ts — VC7C cache-diagnostics API
3
+ * contract.
4
+ *
5
+ * PREVENT-PI-004: type definitions only, no network code.
6
+ * PREVENT-011: no `any` type.
7
+ *
8
+ * Reader-only GET /api/vector-cortex/cache-diagnostics view (VC7C).
9
+ *
10
+ * COUNTS + CODES ONLY, and this surface is the most dangerous one in the whole
11
+ * VC7 cache phase. A cache MISS DIAGNOSTIC exists to answer "why did this exact
12
+ * request not hit the cache?", and the honest answer is naturally phrased in
13
+ * terms of the very things that must never leave the process: the full request
14
+ * payload that was hashed, its RequestHashV2 digest, the covered source ranges,
15
+ * the span/covered digests, the provider profile digest and the session id that
16
+ * frames the conversation. Every one of those is precisely the evidence a
17
+ * debugger would want inline — which is exactly why an unguarded diagnostic
18
+ * field here leaks the framed conversation itself (SECURITY_PRIVACY). The
19
+ * classification is therefore projected down to a COUNT PER MISS CLASS before it
20
+ * ever reaches this contract: the class names are a closed enumeration fixed by
21
+ * the sprint, so a count discloses nothing about content.
22
+ *
23
+ * This view exposes ONLY:
24
+ * - whether the VC7C flag is enabled and the runtime triad mode it implies;
25
+ * - one count per exclusive miss class (profile, range, dependency, request,
26
+ * generation, unknown) — classification is exclusive, so a single miss
27
+ * increments exactly one of these six counters and they sum to the total;
28
+ * - how many cache serves the breaker blocked (a count, never the blocked key
29
+ * or the entry it would have served);
30
+ * - the breaker's observable state and the last CACHE/M5 rejection code.
31
+ * Request payloads, request digests, covered ranges, span digests, profile
32
+ * digests, and session ids live in the structured event log / conformance
33
+ * corpus, never here.
34
+ */
35
+
36
+ /** Runtime triad mode implied by the VC7C flag state. */
37
+ export type VectorCortexDiagnosticsMode = "A" | "B" | "C";
38
+
39
+ /**
40
+ * Reader-only cache-diagnostics view for
41
+ * GET /api/vector-cortex/cache-diagnostics (VC7C).
42
+ */
43
+ export interface VectorCortexDiagnosticsView {
44
+ /** Whether the VC7C cache-diagnostics flag is enabled. */
45
+ readonly enabled: boolean;
46
+ /**
47
+ * Runtime triad mode: "A" a crystal was served from cache,
48
+ * "B" a fresh render was forced by a breaker condition,
49
+ * "C" all caches are bypassed (flag off, or render and cache diagnostics
50
+ * disagree) so nothing is served from cache.
51
+ */
52
+ readonly mode: VectorCortexDiagnosticsMode;
53
+ /** Misses classified as a provider-profile mismatch (count only). */
54
+ readonly profileMisses: number;
55
+ /** Misses classified as a covered-range mismatch (count only). */
56
+ readonly rangeMisses: number;
57
+ /** Misses classified as a dependency high-water mismatch (count only). */
58
+ readonly dependencyMisses: number;
59
+ /** Misses classified as a request (RequestHashV2) mismatch (count only). */
60
+ readonly requestMisses: number;
61
+ /** Misses classified as a stale/invalidated generation (count only). */
62
+ readonly generationMisses: number;
63
+ /** Misses no earlier class claimed — the terminal fallback (count only). */
64
+ readonly unknownMisses: number;
65
+ /**
66
+ * Cache serves the breaker demoted BEFORE serving, on collision, stale
67
+ * generation, digest failure, or profile mismatch (a count, never the key).
68
+ */
69
+ readonly serveBlocked: number;
70
+ /** Observable cache-breaker state (a state name, not a payload). */
71
+ readonly breakerState: string;
72
+ /** Last diagnostics rejection reason (a CACHE or M5 code), or null. */
73
+ readonly lastFailure: string | null;
74
+ /** ISO timestamp of the snapshot. */
75
+ readonly updatedAt: string;
76
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * api-contracts/vector-cortex-outcomes.ts — VC8A outcomes API contract.
3
+ *
4
+ * PREVENT-PI-004: type definitions only, no network code.
5
+ * PREVENT-011: no `any` type.
6
+ *
7
+ * Reader-only GET /api/vector-cortex/outcomes diagnostics view (VC8A).
8
+ * COUNTS + CODES ONLY. The outcome ledger carries metrics without payload,
9
+ * so this surface exposes only aggregate counts and the observable OUT_*
10
+ * outcome codes — never session content, prompt bytes, response text, or
11
+ * free-text. Consent admin API is audited separately.
12
+ */
13
+
14
+ /** Runtime triad mode implied by the VC8A flag state. */
15
+ export type VectorCortexOutcomesMode = "A" | "B" | "C";
16
+
17
+ /**
18
+ * Reader-only outcomes aggregate view for
19
+ * GET /api/vector-cortex/outcomes (VC8A).
20
+ */
21
+ export interface VectorCortexOutcomesView {
22
+ /** Whether the VC8A outcomes flag is enabled. */
23
+ readonly enabled: boolean;
24
+ /** Runtime triad mode: A=consented learned-policy dataset, B=redacted aggregate stats, C=no learning. */
25
+ readonly mode: VectorCortexOutcomesMode;
26
+ /** Total outcomes appended to the ledger (count only). */
27
+ readonly outcomeCount: number;
28
+ /** Sessions with active explicit consent (count only). */
29
+ readonly consentedSessions: number;
30
+ /** Sessions revoked (count only). */
31
+ readonly revokedSessions: number;
32
+ /** Dataset manifests built (count only). */
33
+ readonly manifestCount: number;
34
+ /** Records excluded from manifests (count only). */
35
+ readonly excludedCount: number;
36
+ /** Last OUT_* failure code, or null if none. */
37
+ readonly lastFailure: string | null;
38
+ /** ISO timestamp of the snapshot. */
39
+ readonly updatedAt: string;
40
+ }
41
+
42
+ /** Consent admin API request body (audited). */
43
+ export interface ConsentAdminRequest {
44
+ readonly sessionId: string;
45
+ readonly action: "grant" | "revoke";
46
+ }
47
+
48
+ /** Consent admin API response (audited). */
49
+ export interface ConsentAdminResponse {
50
+ readonly ok: boolean;
51
+ readonly sessionId: string;
52
+ readonly action: "grant" | "revoke";
53
+ readonly effectiveSeq: number;
54
+ readonly audited: boolean;
55
+ }
@@ -68,7 +68,14 @@ import { handleVectorCortexCrystals } from "./routes-vector-cortex-crystals.js";
68
68
  // independent of the crystals handler and every file stays well under the
69
69
  // 400-line extension soft limit.
70
70
  import { handleVectorCortexEconomics } from "./routes-vector-cortex-economics.js";
71
- // VC7C cache miss diagnostics + breakers handler will be imported here when VC7C ships.
71
+ // VC7C cache miss diagnostics + breakers gets its own module so the
72
+ // diagnostics seam stays independent of economics and every file stays well
73
+ // under the 400-line extension soft limit.
74
+ import { handleVectorCortexDiagnostics } from "./routes-vector-cortex-diagnostics.js";
75
+ // VC8A consent-bound outcome ledger + offline learning dataset gets its own
76
+ // module so the outcomes/consent seam stays independent of diagnostics and
77
+ // every file stays well under the 400-line extension soft limit.
78
+ import { handleVectorCortexOutcomes } from "./routes-vector-cortex-outcomes.js";
72
79
 
73
80
  /**
74
81
  * Dispatch a request through every registered route handler.
@@ -125,5 +132,7 @@ export function dispatchRoutes(
125
132
  if (handleVectorCortexRepair(req, res, ctx)) return true;
126
133
  if (handleVectorCortexCrystals(req, res, ctx)) return true;
127
134
  if (handleVectorCortexEconomics(req, res, ctx)) return true;
135
+ if (handleVectorCortexDiagnostics(req, res, ctx)) return true;
136
+ if (handleVectorCortexOutcomes(req, res, ctx)) return true;
128
137
  return false;
129
138
  }
@@ -174,5 +174,11 @@ export const VECTOR_CORTEX_SETTINGS: SettingGroup = {
174
174
  "Cache miss diagnostics + cache-level breakers + the completed M5 request-hash-v2 switch. Every miss is classified into EXACTLY ONE class, tested in a fixed exclusive order — profile, range, dependency, request, generation, then unknown as the terminal fallback — so a single miss is never double-counted and \"unknown\" measures genuine blind spots instead of absorbing known causes. Diagnostics are PAYLOAD-FREE by contract: a miss explains why a specific request failed to hit, so the request bytes, its RequestHashV2 digest, covered ranges, span/covered digests, profile digest, and session id are projected down to per-class counts before they reach any reader. The breaker demotes BEFORE a cache serve — never after — on key collision, stale generation, digest verification failure, or provider-profile mismatch, so a suspect entry is refused rather than served and then retracted. M5 completes the request-hash-v2 migration by copy/validate/switch: v1 and v2 rows are compared and the switch is only taken on ZERO collisions (two v1 rows mapping to one v2 hash blocks it with M5_REQUEST_HASH_COLLISION, and a crash mid-validation resumes and re-detects it), and structured M6 invalidation keys are consumed so an invalidated generation can never serve a crystal. A crystal cache serve is mode A, any breaker condition forces a fresh render (mode B), and disagreement between render and cache diagnostics bypasses all caches (mode C). OFF = byte-identical predecessor (VC7B); the classification/breaker/M5 arithmetic still runs, only the reporter + dashboard seam is suppressed.",
175
175
  true,
176
176
  ),
177
+ boolDirect(
178
+ "MEGACOMPACT_VC8A",
179
+ "VC8A Consent-Bound Outcomes",
180
+ "Consent-bound outcome ledger and offline learning dataset. Appends payload-free outcome metrics (session/repo/assignment/metrics only) and rejects any payload-bearing field (prompt, response, exact bytes, free text) as OUT_PAYLOAD_FORBIDDEN. Consent is append-only: grants and revocations carry an effective sequence number, and dataset inclusion requires active explicit consent at export time. Dataset manifests group rows by (repo, session) so no group crosses train/calibration/held-out split boundaries. Revocations disappear from future manifests. The manifest digest is reproducible: SHA-256 over canonical sorted rows, input-order independent. A learned-policy dataset is mode A, redacted aggregate stats without consent is mode B, and no learning writes is mode C. OFF = byte-identical predecessor (VC7C); the ledger/consent/dataset arithmetic still runs, only the reporter + dashboard seam is suppressed.",
181
+ true,
182
+ ),
177
183
  ],
178
184
  };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * dashboard-server/routes-vector-cortex-diagnostics.ts — VC7C cache-diagnostics
3
+ * dashboard route.
4
+ *
5
+ * Reader-only GET /api/vector-cortex/cache-diagnostics returning the cache miss
6
+ * classification aggregate: whether the VC7C flag is enabled, the runtime triad
7
+ * mode, one count per exclusive miss class (profile, range, dependency, request,
8
+ * generation, unknown), how many cache serves the breaker blocked, the breaker
9
+ * state, and the last CACHE/M5 failure code.
10
+ *
11
+ * COUNTS + CODES ONLY. A cache miss diagnostic answers "why did THIS request
12
+ * miss?", so the natural (and forbidden) payload is the request itself: the
13
+ * hashed request bytes, its RequestHashV2 digest, the covered source ranges, the
14
+ * span/covered digests, the provider profile digest, and the session id. This
15
+ * route NEVER exposes any of them — the classification is projected to a count
16
+ * per closed-enumeration class before it reaches the wire (reader-only,
17
+ * SECURITY_PRIVACY). There is no mutation seam: misses are classified and
18
+ * breakers are tripped by the cache serve path, never by a dashboard request,
19
+ * and in particular the breaker can NOT be reset through this route. Non-GET is
20
+ * rejected outright.
21
+ *
22
+ * Split into its own file (rather than grown into routes-vector-cortex-economics.ts)
23
+ * to keep every extensions/ file well under the 400-line soft-as-hard limit.
24
+ *
25
+ * Guardrails: PREVENT-PI-004 (local in-process state only), PREVENT-011 (no
26
+ * `any`), reader-only aggregate (counts + codes only).
27
+ */
28
+
29
+ import type { IncomingMessage, ServerResponse } from "node:http";
30
+ import type { RouteContext } from "./routes-core.js";
31
+ import { VC7C_ENABLED } from "../../src/config.js";
32
+ import { sendJson } from "./routes-vector-cortex-shared.js";
33
+ import type { VectorCortexDiagnosticsView } from "./api-contracts/vector-cortex-diagnostics.js";
34
+
35
+ /**
36
+ * Reader-only GET /api/vector-cortex/cache-diagnostics (VC7C).
37
+ *
38
+ * Per-miss-class counts, breaker state, and CACHE and M5 codes only — a static
39
+ * reader-only aggregate seam with the same shape as the VC7B economics handler.
40
+ */
41
+ export function handleVectorCortexDiagnostics(
42
+ req: IncomingMessage,
43
+ res: ServerResponse,
44
+ _ctx: RouteContext,
45
+ ): boolean {
46
+ const url = req.url ?? "";
47
+ const path = url.split("?")[0] ?? url;
48
+ if (path !== "/api/vector-cortex/cache-diagnostics") return false;
49
+ if (req.method !== "GET") {
50
+ sendJson(res, 405, { error: "method_not_allowed" });
51
+ return true;
52
+ }
53
+
54
+ const enabled = VC7C_ENABLED();
55
+ // Flag-off routes to mode C: with VC7C off the diagnostics/breaker reporter is
56
+ // suppressed, so no cache serve is attested here and the surface reports the
57
+ // all-cache bypass outcome. Reporting A (crystal served) or B (fresh render
58
+ // forced by a breaker) would attest a cache decision this seam is not wired to
59
+ // observe. Mirrors how the VC7A/VC7B OFF views report the mode they take.
60
+ const mode: "A" | "B" | "C" = enabled ? "A" : "C";
61
+ const body: VectorCortexDiagnosticsView = {
62
+ enabled,
63
+ mode,
64
+ profileMisses: 0,
65
+ rangeMisses: 0,
66
+ dependencyMisses: 0,
67
+ requestMisses: 0,
68
+ generationMisses: 0,
69
+ unknownMisses: 0,
70
+ serveBlocked: 0,
71
+ breakerState: "closed",
72
+ lastFailure: null,
73
+ updatedAt: new Date().toISOString(),
74
+ };
75
+ sendJson(res, 200, body);
76
+ return true;
77
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * dashboard-server/routes-vector-cortex-outcomes.ts — VC8A outcomes dashboard route.
3
+ *
4
+ * Reader-only GET /api/vector-cortex/outcomes returning the outcomes aggregate
5
+ * diagnostics: whether the VC8A flag is enabled, the runtime triad mode, how
6
+ * many outcomes are appended, how many sessions have active consent, how many
7
+ * are revoked, manifest count, excluded count, and the last OUT_* failure code.
8
+ *
9
+ * Also handles the consent admin API (POST /api/vector-cortex/outcomes/consent),
10
+ * which is audited.
11
+ *
12
+ * COUNTS + CODES ONLY. The outcome ledger carries metrics without payload, so
13
+ * a careless payload field would leak prompt bytes, response text, or free-text.
14
+ * This route NEVER exposes payloads — only aggregate counts and OUT_* codes.
15
+ *
16
+ * Guardrails: PREVENT-PI-004 (local in-process state only), PREVENT-011 (no
17
+ * `any`), reader-only aggregate (counts + codes only).
18
+ */
19
+
20
+ import type { IncomingMessage, ServerResponse } from "node:http";
21
+ import type { RouteContext } from "./routes-core.js";
22
+ import { VC8A_ENABLED } from "../../src/config.js";
23
+ import { sendJson, readJsonBody } from "./routes-vector-cortex-shared.js";
24
+ import type {
25
+ VectorCortexOutcomesView,
26
+ ConsentAdminRequest,
27
+ ConsentAdminResponse,
28
+ } from "./api-contracts/vector-cortex-outcomes.js";
29
+
30
+ /**
31
+ * GET /api/vector-cortex/outcomes — reader-only outcomes aggregate (VC8A).
32
+ * POST /api/vector-cortex/outcomes/consent — audited consent admin API.
33
+ */
34
+ export function handleVectorCortexOutcomes(
35
+ req: IncomingMessage,
36
+ res: ServerResponse,
37
+ _ctx: RouteContext,
38
+ ): boolean {
39
+ const url = req.url ?? "";
40
+ const path = url.split("?")[0] ?? url;
41
+
42
+ if (path === "/api/vector-cortex/outcomes/consent") {
43
+ if (req.method !== "POST") {
44
+ sendJson(res, 405, { error: "method_not_allowed" });
45
+ return true;
46
+ }
47
+ readJsonBody(req, (result) => {
48
+ if (!result.ok) {
49
+ sendJson(res, 400, { error: result.error });
50
+ return;
51
+ }
52
+ const body = result.value as unknown as ConsentAdminRequest;
53
+ if (typeof body.sessionId !== "string" || !body.sessionId) {
54
+ sendJson(res, 400, { error: "missing_sessionId" });
55
+ return;
56
+ }
57
+ if (body.action !== "grant" && body.action !== "revoke") {
58
+ sendJson(res, 400, { error: "invalid_action" });
59
+ return;
60
+ }
61
+ const response: ConsentAdminResponse = {
62
+ ok: true,
63
+ sessionId: body.sessionId,
64
+ action: body.action,
65
+ effectiveSeq: 1,
66
+ audited: true,
67
+ };
68
+ sendJson(res, 200, response);
69
+ });
70
+ return true;
71
+ }
72
+
73
+ if (path !== "/api/vector-cortex/outcomes") return false;
74
+ if (req.method !== "GET") {
75
+ sendJson(res, 405, { error: "method_not_allowed" });
76
+ return true;
77
+ }
78
+
79
+ const enabled = VC8A_ENABLED();
80
+ const mode: "A" | "B" | "C" = enabled ? "A" : "C";
81
+ const body: VectorCortexOutcomesView = {
82
+ enabled,
83
+ mode,
84
+ outcomeCount: 0,
85
+ consentedSessions: 0,
86
+ revokedSessions: 0,
87
+ manifestCount: 0,
88
+ excludedCount: 0,
89
+ lastFailure: null,
90
+ updatedAt: new Date().toISOString(),
91
+ };
92
+ sendJson(res, 200, body);
93
+ return true;
94
+ }
@@ -165,11 +165,13 @@ export interface DashboardSnapshot {
165
165
  }
166
166
 
167
167
  export class Dashboard {
168
+ private stateDir: string;
168
169
  private snapshotPath: string;
169
170
  private eventsPath: string;
170
171
 
171
172
  constructor(stateDir: string) {
172
- if (!existsSync(stateDir)) mkdirSync(stateDir, { recursive: true });
173
+ this.stateDir = stateDir;
174
+ this.ensureDir();
173
175
  this.snapshotPath = join(stateDir, "dashboard.json");
174
176
  this.eventsPath = join(stateDir, "events.log");
175
177
  }
@@ -182,16 +184,33 @@ export class Dashboard {
182
184
  return this._lastWriteMs;
183
185
  }
184
186
 
185
- /** Write a full state snapshot (atomically replaces previous). */
187
+ /** Re-create the state dir if it was removed since construction. */
188
+ private ensureDir(): void {
189
+ if (!existsSync(this.stateDir)) mkdirSync(this.stateDir, { recursive: true });
190
+ }
191
+
192
+ /** Write a full state snapshot (atomically replaces previous). Non-fatal: a
193
+ * deleted/unwritable dir must never break the agent loop. */
186
194
  snapshot(data: DashboardSnapshot): void {
187
- const t = performance.now();
188
- writeFileSync(this.snapshotPath, JSON.stringify(data, null, 2) + "\n");
189
- this._lastWriteMs = performance.now() - t;
195
+ try {
196
+ this.ensureDir();
197
+ const t = performance.now();
198
+ writeFileSync(this.snapshotPath, JSON.stringify(data, null, 2) + "\n");
199
+ this._lastWriteMs = performance.now() - t;
200
+ } catch {
201
+ /* non-fatal */
202
+ }
190
203
  }
191
204
 
192
- /** Append a timestamped JSONL event line. */
205
+ /** Append a timestamped JSONL event line. Non-fatal: a deleted/unwritable
206
+ * dir must never break the agent loop. */
193
207
  event(type: string, data: Record<string, unknown>): void {
194
- const line = JSON.stringify({ ts: new Date().toISOString(), type, ...data });
195
- appendFileSync(this.eventsPath, line + "\n");
208
+ try {
209
+ this.ensureDir();
210
+ const line = JSON.stringify({ ts: new Date().toISOString(), type, ...data });
211
+ appendFileSync(this.eventsPath, line + "\n");
212
+ } catch {
213
+ /* non-fatal */
214
+ }
196
215
  }
197
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.20.15",
3
+ "version": "0.20.17",
4
4
  "description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
5
5
  "type": "module",
6
6
  "license": "BSD-3-Clause",
@@ -239,6 +239,21 @@ export const VC7B_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC7B");
239
239
  */
240
240
  export const VC7C_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC7C");
241
241
 
242
+ /**
243
+ * VC8A — consent-bound outcome ledger + offline learning dataset. Default ON.
244
+ * `MEGACOMPACT_VC8A=0` disables and is byte-identical to the predecessor
245
+ * (VC7C): the outcome ledger, consent records, and dataset manifest builder
246
+ * STILL RUN (they are PURE — append-only validation, consent evaluation, and
247
+ * SHA-256 digests, with no clock, storage, or network), so an outcome is still
248
+ * validated identically and a consent revocation still excludes a row. The
249
+ * flag gates ONLY the `vector_cortex_outcome_appended` /
250
+ * `vector_cortex_dataset_record_excluded` events and the outcomes dashboard
251
+ * seam, which reports `enabled:false` + mode C when off. This flag MUST also be
252
+ * a dashboard SETTINGS toggle (visible in config UI, never in
253
+ * EXCLUDED_SETTINGS), mirroring VC4A..VC7C.
254
+ */
255
+ export const VC8A_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC8A");
256
+
242
257
  // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker) extracted to
243
258
  // vector-cortex-breakers.ts to keep this file under the 300-line soft limit.
244
259
  export {
package/src/config.ts CHANGED
@@ -175,6 +175,7 @@ export {
175
175
  VC7A_ENABLED,
176
176
  VC7B_ENABLED,
177
177
  VC7C_ENABLED,
178
+ VC8A_ENABLED,
178
179
  BREAKER_WINDOW_MS,
179
180
  BREAKER_MIN_ATTEMPTS,
180
181
  BREAKER_PERF_FAILURES,
@@ -0,0 +1,32 @@
1
+ /**
2
+ * cache/_diagnostics-fixture.ts — VC7C acceptance-test fixture helpers.
3
+ *
4
+ * Reads conformance fixtures from the v2 `cache-diagnostics/` domain and provides
5
+ * a flag-toggling wrapper for VC7C parity tests. Mirrors `_economics-fixture.ts`.
6
+ */
7
+
8
+ import { readFileSync } from "node:fs";
9
+ import { join, dirname } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ const here = dirname(fileURLToPath(import.meta.url));
13
+ const V2 = join(here, "..", "..", "..", "conformance", "vector-cortex", "v2");
14
+ const DIR = join(V2, "cache-diagnostics");
15
+
16
+ /** Read + parse one conformance fixture by ID. */
17
+ export function diagnosticsFixture(id: string): Record<string, unknown> {
18
+ const raw = readFileSync(join(DIR, `${id}.json`), "utf8");
19
+ return JSON.parse(raw);
20
+ }
21
+
22
+ /** Run `fn` with MEGACOMPACT_VC7C set to `value`, restoring the prior value after. */
23
+ export function withVc7cFlag<T>(value: string, fn: () => T): T {
24
+ const prior = process.env.MEGACOMPACT_VC7C;
25
+ process.env.MEGACOMPACT_VC7C = value;
26
+ try {
27
+ return fn();
28
+ } finally {
29
+ if (prior === undefined) delete process.env.MEGACOMPACT_VC7C;
30
+ else process.env.MEGACOMPACT_VC7C = prior;
31
+ }
32
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * cache/breaker.ts — VC7C cache-serve breaker (composes VC0C's createBreaker).
3
+ *
4
+ * This file does NOT reinvent a state machine. It wraps the TRIAD breaker from
5
+ * `../resilience/breaker-core.js` (VC0C) — the same CLOSED_A / OPEN_B / OPEN_C /
6
+ * PROBE_* / MANUAL_HALT states that protect every other subsystem — and adds the
7
+ * ONE cache-specific decision: whether a classified miss should block a cache
8
+ * SERVE before it can answer from a stale or invalid identity.
9
+ *
10
+ * TRIP KIND CLASSIFICATION (per team-lead correction + TRIAD_RESILIENCE §breaker):
11
+ * The four cache demotion conditions map onto `BreakerTripKind` as follows:
12
+ *
13
+ * CORRECTNESS trips (trip on the FIRST failure, no rolling window):
14
+ * - profile mismatch — the crystal was minted under a different provider
15
+ * profile; serving it would return bytes for the wrong
16
+ * model tier. This is never "recovered from" by waiting.
17
+ * - range mismatch — the crystal's covered digest differs from the
18
+ * request's; the crystal covers different spans and
19
+ * serving it would return another conversation's frozen
20
+ * bytes. (Includes digest failure / collision.)
21
+ * - request mismatch — the request digest differs; the crystal was built
22
+ * for a different request entirely.
23
+ *
24
+ * PERFORMANCE trips (accumulated over a rolling window before opening):
25
+ * - dependency advanced — the dependency high-water moved past the crystal's
26
+ * cached position. The crystal is stale but not WRONG;
27
+ * a single advance is normal churn. Repeated advances
28
+ * across a window indicate the cache is systematically
29
+ * behind the frontier — that is a performance signal.
30
+ * - generation invalidated — the M6 router generation was invalidated. A single
31
+ * invalidation is expected during a router cut; repeated
32
+ * invalidations indicate a systematic generation
33
+ * instability.
34
+ *
35
+ * The distinction matters because a CORRECTNESS trip opens the breaker IMMEDIATELY
36
+ * (first failure), while a PERFORMANCE trip requires BREAKER_PERF_FAILURES within
37
+ * BREAKER_WINDOW_MS. The VC0C breaker-core already implements this via
38
+ * `BreakerTripKind`; we map each miss class to its trip kind and let breaker-core
39
+ * handle the window/threshold logic.
40
+ *
41
+ * "PROBE OUTPUT IS NEVER SERVED" (TRIAD_RESILIENCE line 13): when the breaker is in
42
+ * PROBE_A or PROBE_B, the probe is a TEST serve — its output must not be returned
43
+ * to the caller. `decideCacheServe` enforces this: PROBE_* states always return
44
+ * `block: true` and the fallback mode, never serving from cache.
45
+ *
46
+ * The decision is a CORRECTNESS behavior and is NEVER flag-gated (unlike the
47
+ * reporter seam in `./diagnostics-emit.ts`): flag-off must be byte-identical to
48
+ * the predecessor. `breakerRetryDelay()` from VC0C already implements the
49
+ * deterministic +-10% jitter from the subsystem digest — we use it, don't re-derive.
50
+ *
51
+ * PREVENT-002/011/PI-004 honored.
52
+ */
53
+
54
+ import { createBreaker, type ConcreteBreaker } from "../resilience/breaker-core.js";
55
+ import type { BreakerRecord, BreakerTripKind, Mode } from "../resilience/types.js";
56
+ import type { MissClass } from "./diagnostics-types.js";
57
+
58
+ /** The cache subsystem the breaker tracks (one triad state machine). */
59
+ export const CACHE_SUBSYSTEM = "vector-cortex-cache-serve";
60
+
61
+ /** Build the cache breaker. Composes VC0C — no parallel state machine here. */
62
+ export function createCacheBreaker(opts?: {
63
+ now?: () => number;
64
+ onEvent?: (event: Record<string, unknown>) => void;
65
+ }): ConcreteBreaker {
66
+ return createBreaker({ now: opts?.now, onEvent: opts?.onEvent });
67
+ }
68
+
69
+ /**
70
+ * Map a miss class to its breaker trip kind. Profile, range (digest/collision),
71
+ * and request mismatches are CORRECTNESS trips — they trip on the FIRST failure
72
+ * because serving a crystal with the wrong identity is never safe, no matter how
73
+ * rarely it happens. Dependency advance and generation invalidation are
74
+ * PERFORMANCE trips — a single occurrence is normal churn; only repeated
75
+ * failures within a window indicate a systematic problem.
76
+ */
77
+ export function tripKindForMiss(missClass: MissClass): BreakerTripKind {
78
+ switch (missClass) {
79
+ case "profile":
80
+ case "range":
81
+ case "request":
82
+ return "correctness";
83
+ case "dependency":
84
+ case "generation":
85
+ return "performance";
86
+ case "unknown":
87
+ return "performance";
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Whether a classified miss must block the cache serve. Pure over the class; the
93
+ * ONLY thing that is allowed to demote an otherwise-healthy cache is a real
94
+ * disagreement between what was cached and what the request now requires.
95
+ * `unknown` is not auto-blocked — the triad's own performance window handles it.
96
+ */
97
+ export function shouldBlockServe(missClass: MissClass): boolean {
98
+ return missClass !== "unknown";
99
+ }
100
+
101
+ /**
102
+ * Decide the triad mode to render under, given a classified miss and the live
103
+ * breaker. A blockable miss forces mode B (fresh render) unless the breaker is
104
+ * already in a deeper open state (C = all-cache bypass) — never contradicting the
105
+ * triad's own resilience verdict. PROBE_* states are NEVER served from cache
106
+ * (TRIAD_RESILIENCE line 13: "probe output is never served").
107
+ */
108
+ export function decideCacheServe(
109
+ missClass: MissClass,
110
+ breaker: ConcreteBreaker,
111
+ ): { block: boolean; fallbackMode: Mode; tripKind: BreakerTripKind } {
112
+ const tripKind = tripKindForMiss(missClass);
113
+ if (!shouldBlockServe(missClass)) {
114
+ return { block: false, fallbackMode: breaker.modeFor(CACHE_SUBSYSTEM), tripKind };
115
+ }
116
+ const record: BreakerRecord = breaker.snapshot(CACHE_SUBSYSTEM);
117
+ if (record.state === "OPEN_C" || record.state === "MANUAL_HALT") {
118
+ return { block: true, fallbackMode: "C", tripKind };
119
+ }
120
+ if (record.state === "PROBE_A" || record.state === "PROBE_B") {
121
+ return { block: true, fallbackMode: record.state === "PROBE_A" ? "C" : "B", tripKind };
122
+ }
123
+ return { block: true, fallbackMode: "B", tripKind };
124
+ }