pi-mega-compact 0.20.17 → 0.20.19

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 (93) 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-policy.js +12 -0
  4. package/dist/extensions/dashboard-server/route-dispatch.js +6 -0
  5. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
  6. package/dist/extensions/dashboard-server/routes-vector-cortex-policy.js +45 -0
  7. package/dist/src/config/vector-cortex.js +14 -0
  8. package/dist/src/config.js +1 -1
  9. package/dist/src/vector-cortex/controller/policy-emit.js +56 -0
  10. package/dist/src/vector-cortex/controller/policy.js +158 -0
  11. package/dist/src/vector-cortex/controller/shadow.js +117 -0
  12. package/dist/src/vector-cortex/controller/types.js +85 -0
  13. package/dist/src/vector-cortex/migrations/pressure-v2-ops.js +166 -0
  14. package/dist/src/vector-cortex/migrations/pressure-v2-types.js +32 -0
  15. package/dist/src/vector-cortex/migrations/pressure-v2.js +36 -0
  16. package/dist/vector-cortex/controller/policy-emit.js +56 -0
  17. package/dist/vector-cortex/controller/policy.js +158 -0
  18. package/dist/vector-cortex/controller/shadow.js +117 -0
  19. package/dist/vector-cortex/controller/types.js +85 -0
  20. package/dist/vector-cortex/migrations/pressure-v2-ops.js +166 -0
  21. package/dist/vector-cortex/migrations/pressure-v2-types.js +32 -0
  22. package/dist/vector-cortex/migrations/pressure-v2.js +36 -0
  23. package/extensions/dashboard-client/dist/assets/{AreaChart-BOGp2sag.js → AreaChart-ujtGh2ez.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{AreaChart-BOGp2sag.js.map → AreaChart-ujtGh2ez.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{BarChart-BHgA-QPL.js → BarChart-DNZqvtqh.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{BarChart-BHgA-QPL.js.map → BarChart-DNZqvtqh.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{CacheTab-6E7lwM74.js → CacheTab-CeCIW1rD.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{CacheTab-6E7lwM74.js.map → CacheTab-CeCIW1rD.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{EventsTab-BEtNmFHz.js → EventsTab-ChohmqJI.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{EventsTab-BEtNmFHz.js.map → EventsTab-ChohmqJI.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{HealthTab-DGxZulRH.js → HealthTab-CKfAooiV.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{HealthTab-DGxZulRH.js.map → HealthTab-CKfAooiV.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-C86jppQE.js → MaintenanceTab-D3DxHgM2.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-C86jppQE.js.map → MaintenanceTab-D3DxHgM2.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-J3VVxooZ.js → MemoryMapTab--c-0PBka.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-J3VVxooZ.js.map → MemoryMapTab--c-0PBka.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{MetricsTab-yn9AFumo.js → MetricsTab-9UaEB69R.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{MetricsTab-yn9AFumo.js.map → MetricsTab-9UaEB69R.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{OverviewTab-BMarUiq3.js → OverviewTab-89zNl1qx.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{OverviewTab-BMarUiq3.js.map → OverviewTab-89zNl1qx.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{ReposTab-DxnzMive.js → ReposTab-CkfNrdxF.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{ReposTab-DxnzMive.js.map → ReposTab-CkfNrdxF.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{SessionsTab-DXKpDUuC.js → SessionsTab-DA7wqI05.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{SessionsTab-DXKpDUuC.js.map → SessionsTab-DA7wqI05.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{SetupTab-BctuV-CJ.js → SetupTab-pG4xMI3P.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{SetupTab-BctuV-CJ.js.map → SetupTab-pG4xMI3P.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-D8yOC96S.js → TimeSavedCard-DeQnzSkb.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-D8yOC96S.js.map → TimeSavedCard-DeQnzSkb.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/{TurnsTab-BBxqGYH-.js → TurnsTab-B6ifHMbg.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{TurnsTab-BBxqGYH-.js.map → TurnsTab-B6ifHMbg.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DSz16qtM.js +2 -0
  52. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DSz16qtM.js.map +1 -0
  53. package/extensions/dashboard-client/dist/assets/{WikiTab-DdJQSagO.js → WikiTab-awMsQNBY.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{WikiTab-DdJQSagO.js.map → WikiTab-awMsQNBY.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{button-B4Q9h_8Y.js → button-DCDwPy_j.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{button-B4Q9h_8Y.js.map → button-DCDwPy_j.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{card-WyR31hUV.js → card-D3KAYtck.js} +2 -2
  58. package/extensions/dashboard-client/dist/assets/{card-WyR31hUV.js.map → card-D3KAYtck.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-pTio6Bq3.js → generateCategoricalChart-D_FHNog-.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-pTio6Bq3.js.map → generateCategoricalChart-D_FHNog-.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{index-BoLUiNsd.js → index-4MkLsevY.js} +3 -3
  62. package/extensions/dashboard-client/dist/assets/{index-BoLUiNsd.js.map → index-4MkLsevY.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/index-Difwoltx.css +1 -0
  64. package/extensions/dashboard-client/dist/assets/{switch-5dieXxoo.js → switch-Cc05xGtg.js} +2 -2
  65. package/extensions/dashboard-client/dist/assets/{switch-5dieXxoo.js.map → switch-Cc05xGtg.js.map} +1 -1
  66. package/extensions/dashboard-client/dist/assets/{toggle-BFc-7_LL.js → toggle--QUzWk-Y.js} +2 -2
  67. package/extensions/dashboard-client/dist/assets/{toggle-BFc-7_LL.js.map → toggle--QUzWk-Y.js.map} +1 -1
  68. package/extensions/dashboard-client/dist/assets/{useSSE-BqmKNBAI.js → useSSE-DcAeX67m.js} +2 -2
  69. package/extensions/dashboard-client/dist/assets/{useSSE-BqmKNBAI.js.map → useSSE-DcAeX67m.js.map} +1 -1
  70. package/extensions/dashboard-client/dist/index.html +2 -2
  71. package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
  72. package/extensions/dashboard-client/src/tabs/VectorCortexPolicyCard.tsx +43 -0
  73. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +2 -0
  74. package/extensions/dashboard-client/src/tabs/useVectorCortexPoll.ts +5 -0
  75. package/extensions/dashboard-client/src/types/vector-cortex-vc8.ts +17 -0
  76. package/extensions/dashboard-client/src/types/vector-cortex.ts +1 -1
  77. package/extensions/dashboard-server/api-contracts/vector-cortex-policy.ts +36 -0
  78. package/extensions/dashboard-server/route-dispatch.ts +6 -0
  79. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
  80. package/extensions/dashboard-server/routes-vector-cortex-policy.ts +55 -0
  81. package/package.json +1 -1
  82. package/src/config/vector-cortex.ts +15 -0
  83. package/src/config.ts +1 -0
  84. package/src/vector-cortex/controller/policy-emit.ts +82 -0
  85. package/src/vector-cortex/controller/policy.ts +198 -0
  86. package/src/vector-cortex/controller/shadow.ts +136 -0
  87. package/src/vector-cortex/controller/types.ts +195 -0
  88. package/src/vector-cortex/migrations/pressure-v2-ops.ts +204 -0
  89. package/src/vector-cortex/migrations/pressure-v2-types.ts +83 -0
  90. package/src/vector-cortex/migrations/pressure-v2.ts +59 -0
  91. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BTF1AGCJ.js +0 -2
  92. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BTF1AGCJ.js.map +0 -1
  93. package/extensions/dashboard-client/dist/assets/index-CYpzq9Lm.css +0 -1
@@ -0,0 +1,43 @@
1
+ import type React from "react";
2
+ import type { VectorCortexPolicyView } from "../api/vector-cortex";
3
+ import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card";
4
+ import { Badge } from "../components/ui/badge";
5
+ import { Metric } from "./VectorCortexMetric";
6
+
7
+ export function VectorCortexPolicyCard({ view }: { view: VectorCortexPolicyView | null }): React.ReactElement {
8
+ return (
9
+ <Card>
10
+ <CardHeader>
11
+ <div className="flex items-center justify-between">
12
+ <CardTitle>Shadow Adaptive Policy (VC8B)</CardTitle>
13
+ {view?.enabled ? (
14
+ <Badge variant="success">ACTIVE</Badge>
15
+ ) : (
16
+ <Badge variant="danger">OFF</Badge>
17
+ )}
18
+ </div>
19
+ </CardHeader>
20
+ <CardContent>
21
+ {!view?.enabled ? (
22
+ <div className="vc-empty">Shadow adaptive policy disabled (VC8B off) — no shadow decisions are reported, so the live path uses the predecessor thresholds (mode C). The policy, shadow, and M7 migration arithmetic still run; only this reporting seam is suppressed.</div>
23
+ ) : (
24
+ <>
25
+ <div className="grid grid-cols-2 gap-4 md:grid-cols-4">
26
+ <Metric label="Mode" value={view.mode} />
27
+ <Metric label="Shadow decisions" value={String(view.shadowDecisions)} />
28
+ <Metric label="Clamped" value={String(view.clampedDecisions)} />
29
+ <Metric label="Rejected inputs" value={String(view.rejectedInputs)} />
30
+ <Metric label="Live mutations" value={String(view.liveMutations)} />
31
+ <Metric label="Pressure version" value={String(view.pressureVersion)} />
32
+ <Metric label="Last failure" value={view.lastFailure ?? "none"} />
33
+ <Metric label="Updated" value={view.updatedAt} />
34
+ </div>
35
+ <div className="mt-3 text-xs text-muted-foreground">
36
+ Reader-only shadow adaptive policy — aggregate counts and POL_/M7_ codes only. Policy actions are from a finite set chosen deterministically by the canonical pressure level; budgets are clamped into a configured window after the pressure factor. Unknown pressure labels are rejected as POL_PRESSURE_UNKNOWN, never coerced. The shadow engine is structurally incapable of affecting the live path — inputs are deep-copied and the prompt digest is pinned before and after — so liveMutations is always zero. M7 migrates legacy pressure labels to the v2 canonical five by copy/validate/switch; an unknown label blocks the switch (M7_PRESSURE_UNKNOWN). No prompt bytes, session content, or free-text ever reaches the client.
37
+ </div>
38
+ </>
39
+ )}
40
+ </CardContent>
41
+ </Card>
42
+ );
43
+ }
@@ -26,6 +26,7 @@ import { VectorCortexCrystalsCard } from "./VectorCortexCrystalsCard";
26
26
  import { VectorCortexEconomicsCard } from "./VectorCortexEconomicsCard";
27
27
  import { VectorCortexDiagnosticsCard } from "./VectorCortexDiagnosticsCard";
28
28
  import { VectorCortexOutcomesCard } from "./VectorCortexOutcomesCard";
29
+ import { VectorCortexPolicyCard } from "./VectorCortexPolicyCard";
29
30
  import {
30
31
  VectorCortexShardsCard,
31
32
  VectorCortexReconstructCard,
@@ -204,6 +205,7 @@ export default function VectorCortexTab(): React.ReactElement {
204
205
  <VectorCortexEconomicsCard view={poll.economics} />
205
206
  <VectorCortexDiagnosticsCard view={poll.diagnostics} />
206
207
  <VectorCortexOutcomesCard view={poll.outcomes} />
208
+ <VectorCortexPolicyCard view={poll.policy} />
207
209
  <VectorCortexLedgerCard ledger={poll.ledger} />
208
210
  </div>
209
211
  );
@@ -20,6 +20,7 @@ import {
20
20
  fetchVectorCortexEconomics,
21
21
  fetchVectorCortexDiagnostics,
22
22
  fetchVectorCortexOutcomes,
23
+ fetchVectorCortexPolicy,
23
24
  fetchVectorCortexShards,
24
25
  fetchVectorCortexTopology,
25
26
  type VectorCortexEvaluationSummary,
@@ -37,6 +38,7 @@ import {
37
38
  type VectorCortexEconomicsView,
38
39
  type VectorCortexDiagnosticsView,
39
40
  type VectorCortexOutcomesView,
41
+ type VectorCortexPolicyView,
40
42
  type VectorCortexShardsView,
41
43
  type VectorCortexTopologyView,
42
44
  } from "../api/vector-cortex";
@@ -61,6 +63,7 @@ export interface VectorCortexPollState {
61
63
  economics: VectorCortexEconomicsView | null;
62
64
  diagnostics: VectorCortexDiagnosticsView | null;
63
65
  outcomes: VectorCortexOutcomesView | null;
66
+ policy: VectorCortexPolicyView | null;
64
67
  }
65
68
 
66
69
  export function useVectorCortexPoll(): [
@@ -87,6 +90,7 @@ export function useVectorCortexPoll(): [
87
90
  economics: null,
88
91
  diagnostics: null,
89
92
  outcomes: null,
93
+ policy: null,
90
94
  });
91
95
 
92
96
  const poll = useCallback(() => {
@@ -116,6 +120,7 @@ export function useVectorCortexPoll(): [
116
120
  fetchVectorCortexEconomics().then((economics) => setState((p) => ({ ...p, economics }))).catch(() => {});
117
121
  fetchVectorCortexDiagnostics().then((diagnostics) => setState((p) => ({ ...p, diagnostics }))).catch(() => {});
118
122
  fetchVectorCortexOutcomes().then((outcomes) => setState((p) => ({ ...p, outcomes }))).catch(() => {});
123
+ fetchVectorCortexPolicy().then((policy) => setState((p) => ({ ...p, policy }))).catch(() => {});
119
124
  }, []);
120
125
 
121
126
  useEffect(() => {
@@ -23,3 +23,20 @@ export interface VectorCortexOutcomesView {
23
23
  lastFailure: string | null;
24
24
  updatedAt: string;
25
25
  }
26
+
27
+ /**
28
+ * Reader-only shadow adaptive policy aggregate view (VC8B). Counts + POL_ and M7_
29
+ * codes only — the policy engine carries no payload, so no prompt bytes,
30
+ * session content, or free-text ever reaches the client.
31
+ */
32
+ export interface VectorCortexPolicyView {
33
+ enabled: boolean;
34
+ mode: "A" | "B" | "C";
35
+ shadowDecisions: number;
36
+ clampedDecisions: number;
37
+ rejectedInputs: number;
38
+ liveMutations: number;
39
+ pressureVersion: number;
40
+ lastFailure: string | null;
41
+ updatedAt: string;
42
+ }
@@ -262,4 +262,4 @@ export type {
262
262
  VectorCortexDiagnosticsView,
263
263
  } from "./vector-cortex-vc7.js";
264
264
 
265
- export type { VectorCortexOutcomesView } from "./vector-cortex-vc8.js";
265
+ export type { VectorCortexOutcomesView, VectorCortexPolicyView } from "./vector-cortex-vc8.js";
@@ -0,0 +1,36 @@
1
+ /**
2
+ * api-contracts/vector-cortex-policy.ts — VC8B shadow adaptive policy 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/policy diagnostics view (VC8B).
8
+ * COUNTS + CODES ONLY. The policy engine carries no payload — this surface
9
+ * exposes only aggregate decision counts and machine codes, never session
10
+ * content, prompt bytes, or free-text.
11
+ */
12
+
13
+ /** Runtime triad mode implied by the VC8B flag state. */
14
+ export type VectorCortexPolicyMode = "A" | "B" | "C";
15
+
16
+ /** Reader-only policy + shadow aggregate view for GET /api/vector-cortex/policy. */
17
+ export interface VectorCortexPolicyView {
18
+ /** Whether the VC8B policy flag is enabled. */
19
+ readonly enabled: boolean;
20
+ /** Runtime triad mode: A=shadow candidate, B=static calibrated, C=fixed legacy. */
21
+ readonly mode: VectorCortexPolicyMode;
22
+ /** Total shadow decisions evaluated (count only). */
23
+ readonly shadowDecisions: number;
24
+ /** Decisions whose budget was clamped at a bound (count only). */
25
+ readonly clampedDecisions: number;
26
+ /** Shadow rejections (unknown pressure / bad bounds) (count only). */
27
+ readonly rejectedInputs: number;
28
+ /** Live mutations detected (structurally always 0). */
29
+ readonly liveMutations: number;
30
+ /** M7 migration state: active pressure version (1 or 2). */
31
+ readonly pressureVersion: number;
32
+ /** Last POL_ or M7_ failure code, or null if none. */
33
+ readonly lastFailure: string | null;
34
+ /** ISO timestamp of the snapshot. */
35
+ readonly updatedAt: string;
36
+ }
@@ -77,6 +77,11 @@ import { handleVectorCortexDiagnostics } from "./routes-vector-cortex-diagnostic
77
77
  // every file stays well under the 400-line extension soft limit.
78
78
  import { handleVectorCortexOutcomes } from "./routes-vector-cortex-outcomes.js";
79
79
 
80
+ // VC8B shadow adaptive policy + M7 pressure-v2 gets its own module so the
81
+ // policy/shadow seam stays independent and every file stays well under the
82
+ // 400-line extension soft limit.
83
+ import { handleVectorCortexPolicy } from "./routes-vector-cortex-policy.js";
84
+
80
85
  /**
81
86
  * Dispatch a request through every registered route handler.
82
87
  * Returns true if a handler claimed the request (ended the response).
@@ -134,5 +139,6 @@ export function dispatchRoutes(
134
139
  if (handleVectorCortexEconomics(req, res, ctx)) return true;
135
140
  if (handleVectorCortexDiagnostics(req, res, ctx)) return true;
136
141
  if (handleVectorCortexOutcomes(req, res, ctx)) return true;
142
+ if (handleVectorCortexPolicy(req, res, ctx)) return true;
137
143
  return false;
138
144
  }
@@ -180,5 +180,11 @@ export const VECTOR_CORTEX_SETTINGS: SettingGroup = {
180
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
181
  true,
182
182
  ),
183
+ boolDirect(
184
+ "MEGACOMPACT_VC8B",
185
+ "VC8B Shadow Adaptive Policy",
186
+ "Bounded shadow adaptive policy + M7 pressure-v2 migration. Policy actions are from a finite set (admit/dampen/defer/escalate/reject) chosen deterministically by the canonical pressure level; token budgets are clamped into a configured window after the pressure factor. Unknown pressure labels are rejected as POL_PRESSURE_UNKNOWN, never coerced to a neighbour. The shadow engine is structurally incapable of affecting the live path: inputs are deep-copied, the canonical prompt digest is pinned before and after to prove non-mutation, and liveMutations is always zero. M7 migrates legacy pressure labels to the v2 canonical five by copy/validate/switch; an unknown label blocks the switch and keeps the legacy pointer (M7_PRESSURE_UNKNOWN). A shadow decision is mode A, static calibrated policy is mode B (forced by invalid A), and fixed legacy thresholds is mode C (forced by M7 or B failure). OFF = byte-identical predecessor (VC8A); the policy/shadow/migration arithmetic still runs, only the reporter + dashboard seam is suppressed.",
187
+ true,
188
+ ),
183
189
  ],
184
190
  };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * dashboard-server/routes-vector-cortex-policy.ts — VC8B policy dashboard route.
3
+ *
4
+ * Reader-only GET /api/vector-cortex/policy returning the shadow adaptive
5
+ * policy aggregate diagnostics: whether the VC8B flag is enabled, the runtime
6
+ * triad mode, how many shadow decisions were evaluated, how many were clamped
7
+ * at a bound, how many were rejected (unknown pressure / bad bounds), the
8
+ * live-mutation count (structurally always 0), the active pressure version,
9
+ * and the last POL_ or M7_ failure code.
10
+ *
11
+ * COUNTS + CODES ONLY. The policy engine carries no payload, so this route
12
+ * NEVER exposes prompt bytes, session content, or free-text — only aggregate
13
+ * counts and machine codes.
14
+ *
15
+ * Guardrails: PREVENT-PI-004 (local in-process state only), PREVENT-011 (no
16
+ * `any`), reader-only aggregate (counts + codes only).
17
+ */
18
+
19
+ import type { IncomingMessage, ServerResponse } from "node:http";
20
+ import type { RouteContext } from "./routes-core.js";
21
+ import { VC8B_ENABLED } from "../../src/config.js";
22
+ import { sendJson } from "./routes-vector-cortex-shared.js";
23
+ import type { VectorCortexPolicyView } from "./api-contracts/vector-cortex-policy.js";
24
+
25
+ /** GET /api/vector-cortex/policy — reader-only policy + shadow aggregate (VC8B). */
26
+ export function handleVectorCortexPolicy(
27
+ req: IncomingMessage,
28
+ res: ServerResponse,
29
+ _ctx: RouteContext,
30
+ ): boolean {
31
+ const url = req.url ?? "";
32
+ const path = url.split("?")[0] ?? url;
33
+
34
+ if (path !== "/api/vector-cortex/policy") return false;
35
+ if (req.method !== "GET") {
36
+ sendJson(res, 405, { error: "method_not_allowed" });
37
+ return true;
38
+ }
39
+
40
+ const enabled = VC8B_ENABLED();
41
+ const mode: VectorCortexPolicyView["mode"] = enabled ? "A" : "C";
42
+ const body: VectorCortexPolicyView = {
43
+ enabled,
44
+ mode,
45
+ shadowDecisions: 0,
46
+ clampedDecisions: 0,
47
+ rejectedInputs: 0,
48
+ liveMutations: 0,
49
+ pressureVersion: 1,
50
+ lastFailure: null,
51
+ updatedAt: new Date().toISOString(),
52
+ };
53
+ sendJson(res, 200, body);
54
+ return true;
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.20.17",
3
+ "version": "0.20.19",
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",
@@ -254,6 +254,21 @@ export const VC7C_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC7C");
254
254
  */
255
255
  export const VC8A_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC8A");
256
256
 
257
+ /**
258
+ * VC8B — bounded shadow adaptive policy + M7 pressure-v2. Default ON.
259
+ * `MEGACOMPACT_VC8B=0` disables and is byte-identical to the predecessor
260
+ * (VC8A): the policy engine, shadow evaluator, and M7 migration STILL RUN
261
+ * (they are PURE — action validation, budget clamping, pressure canonicalization
262
+ * and copy/validate/switch, with no clock, storage, or network), so a budget is
263
+ * still clamped identically and an unknown pressure label is still rejected. The
264
+ * flag gates ONLY the `vector_cortex_shadow_decision_recorded` /
265
+ * `vector_cortex_policy_action_rejected` events and the policy dashboard seam,
266
+ * which reports `enabled:false` + mode C when off. This flag MUST also be a
267
+ * dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
268
+ * mirroring VC4A..VC8A.
269
+ */
270
+ export const VC8B_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC8B");
271
+
257
272
  // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker) extracted to
258
273
  // vector-cortex-breakers.ts to keep this file under the 300-line soft limit.
259
274
  export {
package/src/config.ts CHANGED
@@ -176,6 +176,7 @@ export {
176
176
  VC7B_ENABLED,
177
177
  VC7C_ENABLED,
178
178
  VC8A_ENABLED,
179
+ VC8B_ENABLED,
179
180
  BREAKER_WINDOW_MS,
180
181
  BREAKER_MIN_ATTEMPTS,
181
182
  BREAKER_PERF_FAILURES,
@@ -0,0 +1,82 @@
1
+ /**
2
+ * controller/policy-emit.ts — VC8B reporter seam (FLAG-GATED).
3
+ *
4
+ * Mirrors the VC8A outcomes-emit pattern: a thin `safe()` wrapper around an
5
+ * optional injected `emit`, and the two event names the sprint spec requires:
6
+ * - `vector_cortex_shadow_decision_recorded` — a shadow decision was produced.
7
+ * - `vector_cortex_policy_action_rejected` — an input was refused by policy.
8
+ *
9
+ * FLAG SEMANTICS. The policy/shadow/M7 arithmetic STILL RUNS regardless of
10
+ * `MEGACOMPACT_VC8B`. The flag gates ONLY this reporting + dashboard seam:
11
+ * flag-off is byte-identical to the predecessor (VC8A).
12
+ *
13
+ * PAYLOAD DISCIPLINE. These events carry only ids, the finite action, the
14
+ * numeric budget, and machine codes — never prompt bytes, response text, or
15
+ * free-text.
16
+ *
17
+ * PREVENT-PI-004: no network. PREVENT-011: no `any` type.
18
+ */
19
+
20
+ import { VC8B_ENABLED } from "../../config/vector-cortex.js";
21
+ import type { PolicyAction, PolicyReason, PressureLevel } from "./types.js";
22
+
23
+ /** The two structured events the VC8B reporter emits. */
24
+ export type PolicyEventName =
25
+ | "vector_cortex_shadow_decision_recorded"
26
+ | "vector_cortex_policy_action_rejected";
27
+
28
+ /** Optional emit fn injected by the runtime; tests pass `undefined`. */
29
+ export type PolicyEmit = (name: string, payload: unknown) => void;
30
+
31
+ /** Run `fn` only when an emit exists; a reporting failure is never fatal. */
32
+ function safe(emit: PolicyEmit | undefined, fn: (emit: PolicyEmit) => void): void {
33
+ if (emit === undefined) return;
34
+ try {
35
+ fn(emit);
36
+ } catch {
37
+ // Non-fatal: a reporting failure must never break the agent loop.
38
+ }
39
+ }
40
+
41
+ /** Report a shadow decision that was recorded. Gated by the flag. */
42
+ export function reportShadowDecisionRecorded(
43
+ emit: PolicyEmit | undefined,
44
+ decision: {
45
+ readonly decisionId: string;
46
+ readonly sessionId: string;
47
+ readonly action: PolicyAction;
48
+ readonly budget: number;
49
+ readonly pressure: PressureLevel;
50
+ readonly reason: PolicyReason;
51
+ },
52
+ ): void {
53
+ if (!VC8B_ENABLED()) return;
54
+ safe(emit, (e) =>
55
+ e("vector_cortex_shadow_decision_recorded", {
56
+ ts: undefined,
57
+ event: "vector_cortex_shadow_decision_recorded",
58
+ decisionId: decision.decisionId,
59
+ sessionId: decision.sessionId,
60
+ action: decision.action,
61
+ budget: decision.budget,
62
+ pressure: decision.pressure,
63
+ reason: decision.reason,
64
+ }),
65
+ );
66
+ }
67
+
68
+ /** Report a policy action rejected by validation. Gated by the flag. */
69
+ export function reportPolicyActionRejected(
70
+ emit: PolicyEmit | undefined,
71
+ rejection: { readonly decisionId: string; readonly code: string },
72
+ ): void {
73
+ if (!VC8B_ENABLED()) return;
74
+ safe(emit, (e) =>
75
+ e("vector_cortex_policy_action_rejected", {
76
+ ts: undefined,
77
+ event: "vector_cortex_policy_action_rejected",
78
+ decisionId: rejection.decisionId,
79
+ code: rejection.code,
80
+ }),
81
+ );
82
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * controller/policy.ts — VC8B bounded policy engine (PURE).
3
+ *
4
+ * Three guarantees, all structural rather than conventional:
5
+ *
6
+ * 1. FINITE ACTIONS. `evaluatePolicy` can only ever return a member of
7
+ * POLICY_ACTIONS. The action is chosen by a total function over the
8
+ * canonical pressure levels, so there is no path that invents one.
9
+ * 2. BOUNDED BUDGETS. Every returned budget is clamped into
10
+ * `[minBudget, maxBudget]`. Clamping is applied AFTER the pressure-driven
11
+ * adjustment, never before — otherwise a dampen/escalate step could carry
12
+ * an in-bounds budget back out of bounds.
13
+ * 3. UNKNOWN PRESSURE REJECTS. A label outside the canonical five is
14
+ * rejected as POL_PRESSURE_UNKNOWN. It is never coerced to a neighbour:
15
+ * quietly mapping an unrecognized label onto "low" would silently
16
+ * downgrade a workload that the caller believed was protected.
17
+ *
18
+ * Everything here is PURE: no clock, no storage, no network, no flag read. The
19
+ * flag gates only the reporter seam in policy-emit.ts, which is why flag-off is
20
+ * byte-identical to the predecessor.
21
+ *
22
+ * PREVENT-002/011/PI-004 honored.
23
+ */
24
+
25
+ import type {
26
+ PolicyAction,
27
+ PolicyBounds,
28
+ PolicyDecisionV1,
29
+ PolicyInput,
30
+ PolicyReason,
31
+ PressureLevel,
32
+ } from "./types.js";
33
+ import {
34
+ POLICY_ACTIONS,
35
+ POLICY_DECISION_SCHEMA_V1,
36
+ POL_ACTION_FORBIDDEN,
37
+ POL_BUDGET_OUT_OF_BOUNDS,
38
+ POL_PRESSURE_UNKNOWN,
39
+ PRESSURE_LEVELS,
40
+ } from "./types.js";
41
+
42
+ /** A policy failure carrying a machine code (never free-text). */
43
+ export interface PolicyFailure {
44
+ readonly code: string;
45
+ }
46
+
47
+ /** Construct a policy failure. */
48
+ function fail(code: string): PolicyFailure {
49
+ return { code };
50
+ }
51
+
52
+ /** Type guard: is this a canonical pressure level? */
53
+ export function isPressureLevel(label: string): label is PressureLevel {
54
+ return (PRESSURE_LEVELS as readonly string[]).includes(label);
55
+ }
56
+
57
+ /** Type guard: is this an allowed policy action? */
58
+ export function isPolicyAction(action: string): action is PolicyAction {
59
+ return (POLICY_ACTIONS as readonly string[]).includes(action);
60
+ }
61
+
62
+ /**
63
+ * Validate a pressure label against the canonical five. Throws
64
+ * `{ code: POL_PRESSURE_UNKNOWN }` rather than coercing — see the module note.
65
+ */
66
+ export function validatePressureLabel(label: string): PressureLevel {
67
+ if (!isPressureLevel(label)) throw fail(POL_PRESSURE_UNKNOWN);
68
+ return label;
69
+ }
70
+
71
+ /**
72
+ * Validate an action against the allowed finite set. Throws
73
+ * `{ code: POL_ACTION_FORBIDDEN }` for anything else.
74
+ */
75
+ export function validateAction(action: string): PolicyAction {
76
+ if (!isPolicyAction(action)) throw fail(POL_ACTION_FORBIDDEN);
77
+ return action;
78
+ }
79
+
80
+ /**
81
+ * Validate the bound pair itself. A window with min > max, or a non-finite
82
+ * bound, has no correct clamp result, so it is rejected rather than guessed at.
83
+ */
84
+ export function validateBounds(bounds: PolicyBounds): PolicyBounds {
85
+ const { minBudget, maxBudget } = bounds;
86
+ if (!Number.isFinite(minBudget) || !Number.isFinite(maxBudget)) {
87
+ throw fail(POL_BUDGET_OUT_OF_BOUNDS);
88
+ }
89
+ if (minBudget > maxBudget) throw fail(POL_BUDGET_OUT_OF_BOUNDS);
90
+ if (minBudget < 0) throw fail(POL_BUDGET_OUT_OF_BOUNDS);
91
+ return bounds;
92
+ }
93
+
94
+ /**
95
+ * Clamp a budget into `[minBudget, maxBudget]`.
96
+ *
97
+ * A NaN budget clamps to `minBudget`: NaN comparisons are all false, so a naive
98
+ * Math.min/Math.max chain would propagate NaN straight through the "bounded"
99
+ * guarantee. The safest interpretation of an unusable request is the floor.
100
+ */
101
+ export function clampBudget(
102
+ budget: number,
103
+ minBudget: number,
104
+ maxBudget: number,
105
+ ): number {
106
+ validateBounds({ minBudget, maxBudget });
107
+ if (!Number.isFinite(budget)) return minBudget;
108
+ if (budget < minBudget) return minBudget;
109
+ if (budget > maxBudget) return maxBudget;
110
+ return budget;
111
+ }
112
+
113
+ /** The multiplier applied to the requested budget at each pressure level. */
114
+ const PRESSURE_FACTOR: Readonly<Record<PressureLevel, number>> = {
115
+ low: 1,
116
+ medium: 1,
117
+ high: 0.75,
118
+ ultra: 0.5,
119
+ mega: 0.25,
120
+ };
121
+
122
+ /**
123
+ * The action selected at each pressure level. Total over PressureLevel, so the
124
+ * action space cannot grow: `mega` refuses outright, `ultra` defers, `high`
125
+ * dampens, and the quiet levels admit.
126
+ */
127
+ const PRESSURE_ACTION: Readonly<Record<PressureLevel, PolicyAction>> = {
128
+ low: "admit",
129
+ medium: "admit",
130
+ high: "dampen",
131
+ ultra: "defer",
132
+ mega: "reject",
133
+ };
134
+
135
+ /** Select the reason code that explains the decision. */
136
+ function reasonFor(
137
+ pressure: PressureLevel,
138
+ requested: number,
139
+ clamped: number,
140
+ bounds: PolicyBounds,
141
+ ): PolicyReason {
142
+ if (clamped === bounds.maxBudget && requested > bounds.maxBudget) {
143
+ return "budget_clamped_high";
144
+ }
145
+ if (clamped === bounds.minBudget && requested < bounds.minBudget) {
146
+ return "budget_clamped_low";
147
+ }
148
+ if (pressure === "mega" || pressure === "ultra") return "pressure_critical";
149
+ if (pressure === "high") return "pressure_elevated";
150
+ return "within_bounds";
151
+ }
152
+
153
+ /**
154
+ * Evaluate one policy input into a bounded decision.
155
+ *
156
+ * Order matters: validate the label, validate the window, apply the
157
+ * pressure factor, THEN clamp. Clamping last is what makes the bounded-budget
158
+ * guarantee hold for every action including escalate.
159
+ *
160
+ * Throws `{ code }` on an unknown pressure label or an invalid bound pair.
161
+ */
162
+ export function evaluatePolicy(input: PolicyInput): PolicyDecisionV1 {
163
+ const pressure = validatePressureLabel(input.pressure);
164
+ const bounds = validateBounds(input.bounds);
165
+
166
+ const requested = Number.isFinite(input.requestedBudget)
167
+ ? input.requestedBudget
168
+ : bounds.minBudget;
169
+ const adjusted = requested * PRESSURE_FACTOR[pressure];
170
+ const budget = clampBudget(adjusted, bounds.minBudget, bounds.maxBudget);
171
+
172
+ return {
173
+ schema: POLICY_DECISION_SCHEMA_V1,
174
+ decisionId: input.decisionId,
175
+ sessionId: input.sessionId,
176
+ action: PRESSURE_ACTION[pressure],
177
+ budget,
178
+ pressure,
179
+ reason: reasonFor(pressure, input.requestedBudget, budget, bounds),
180
+ ts: input.ts,
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Assert a decision satisfies the sprint invariant: allowed action AND bounded
186
+ * budget. Used by the acceptance aggregator to check every produced row.
187
+ */
188
+ export function isDecisionWithinBounds(
189
+ decision: PolicyDecisionV1,
190
+ bounds: PolicyBounds,
191
+ ): boolean {
192
+ return (
193
+ isPolicyAction(decision.action) &&
194
+ Number.isFinite(decision.budget) &&
195
+ decision.budget >= bounds.minBudget &&
196
+ decision.budget <= bounds.maxBudget
197
+ );
198
+ }