pi-mega-compact 0.20.18 → 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 (87) 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/migrations/pressure-v2-ops.js +166 -0
  11. package/dist/src/vector-cortex/migrations/pressure-v2-types.js +32 -0
  12. package/dist/src/vector-cortex/migrations/pressure-v2.js +36 -0
  13. package/dist/vector-cortex/controller/policy-emit.js +56 -0
  14. package/dist/vector-cortex/controller/policy.js +158 -0
  15. package/dist/vector-cortex/controller/shadow.js +117 -0
  16. package/dist/vector-cortex/controller/types.js +85 -0
  17. package/dist/vector-cortex/migrations/pressure-v2-ops.js +166 -0
  18. package/dist/vector-cortex/migrations/pressure-v2-types.js +32 -0
  19. package/dist/vector-cortex/migrations/pressure-v2.js +36 -0
  20. package/extensions/dashboard-client/dist/assets/{AreaChart-BOGp2sag.js → AreaChart-ujtGh2ez.js} +2 -2
  21. package/extensions/dashboard-client/dist/assets/{AreaChart-BOGp2sag.js.map → AreaChart-ujtGh2ez.js.map} +1 -1
  22. package/extensions/dashboard-client/dist/assets/{BarChart-BHgA-QPL.js → BarChart-DNZqvtqh.js} +2 -2
  23. package/extensions/dashboard-client/dist/assets/{BarChart-BHgA-QPL.js.map → BarChart-DNZqvtqh.js.map} +1 -1
  24. package/extensions/dashboard-client/dist/assets/{CacheTab-6E7lwM74.js → CacheTab-CeCIW1rD.js} +2 -2
  25. package/extensions/dashboard-client/dist/assets/{CacheTab-6E7lwM74.js.map → CacheTab-CeCIW1rD.js.map} +1 -1
  26. package/extensions/dashboard-client/dist/assets/{EventsTab-BEtNmFHz.js → EventsTab-ChohmqJI.js} +2 -2
  27. package/extensions/dashboard-client/dist/assets/{EventsTab-BEtNmFHz.js.map → EventsTab-ChohmqJI.js.map} +1 -1
  28. package/extensions/dashboard-client/dist/assets/{HealthTab-DGxZulRH.js → HealthTab-CKfAooiV.js} +2 -2
  29. package/extensions/dashboard-client/dist/assets/{HealthTab-DGxZulRH.js.map → HealthTab-CKfAooiV.js.map} +1 -1
  30. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-C86jppQE.js → MaintenanceTab-D3DxHgM2.js} +2 -2
  31. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-C86jppQE.js.map → MaintenanceTab-D3DxHgM2.js.map} +1 -1
  32. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-J3VVxooZ.js → MemoryMapTab--c-0PBka.js} +2 -2
  33. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-J3VVxooZ.js.map → MemoryMapTab--c-0PBka.js.map} +1 -1
  34. package/extensions/dashboard-client/dist/assets/{MetricsTab-yn9AFumo.js → MetricsTab-9UaEB69R.js} +2 -2
  35. package/extensions/dashboard-client/dist/assets/{MetricsTab-yn9AFumo.js.map → MetricsTab-9UaEB69R.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/assets/{OverviewTab-BMarUiq3.js → OverviewTab-89zNl1qx.js} +2 -2
  37. package/extensions/dashboard-client/dist/assets/{OverviewTab-BMarUiq3.js.map → OverviewTab-89zNl1qx.js.map} +1 -1
  38. package/extensions/dashboard-client/dist/assets/{ReposTab-DxnzMive.js → ReposTab-CkfNrdxF.js} +2 -2
  39. package/extensions/dashboard-client/dist/assets/{ReposTab-DxnzMive.js.map → ReposTab-CkfNrdxF.js.map} +1 -1
  40. package/extensions/dashboard-client/dist/assets/{SessionsTab-DXKpDUuC.js → SessionsTab-DA7wqI05.js} +2 -2
  41. package/extensions/dashboard-client/dist/assets/{SessionsTab-DXKpDUuC.js.map → SessionsTab-DA7wqI05.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/{SetupTab-BctuV-CJ.js → SetupTab-pG4xMI3P.js} +2 -2
  43. package/extensions/dashboard-client/dist/assets/{SetupTab-BctuV-CJ.js.map → SetupTab-pG4xMI3P.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-D8yOC96S.js → TimeSavedCard-DeQnzSkb.js} +2 -2
  45. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-D8yOC96S.js.map → TimeSavedCard-DeQnzSkb.js.map} +1 -1
  46. package/extensions/dashboard-client/dist/assets/{TurnsTab-BBxqGYH-.js → TurnsTab-B6ifHMbg.js} +2 -2
  47. package/extensions/dashboard-client/dist/assets/{TurnsTab-BBxqGYH-.js.map → TurnsTab-B6ifHMbg.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DSz16qtM.js +2 -0
  49. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DSz16qtM.js.map +1 -0
  50. package/extensions/dashboard-client/dist/assets/{WikiTab-DdJQSagO.js → WikiTab-awMsQNBY.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{WikiTab-DdJQSagO.js.map → WikiTab-awMsQNBY.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/{button-B4Q9h_8Y.js → button-DCDwPy_j.js} +2 -2
  53. package/extensions/dashboard-client/dist/assets/{button-B4Q9h_8Y.js.map → button-DCDwPy_j.js.map} +1 -1
  54. package/extensions/dashboard-client/dist/assets/{card-WyR31hUV.js → card-D3KAYtck.js} +2 -2
  55. package/extensions/dashboard-client/dist/assets/{card-WyR31hUV.js.map → card-D3KAYtck.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-pTio6Bq3.js → generateCategoricalChart-D_FHNog-.js} +2 -2
  57. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-pTio6Bq3.js.map → generateCategoricalChart-D_FHNog-.js.map} +1 -1
  58. package/extensions/dashboard-client/dist/assets/{index-BoLUiNsd.js → index-4MkLsevY.js} +3 -3
  59. package/extensions/dashboard-client/dist/assets/{index-BoLUiNsd.js.map → index-4MkLsevY.js.map} +1 -1
  60. package/extensions/dashboard-client/dist/assets/index-Difwoltx.css +1 -0
  61. package/extensions/dashboard-client/dist/assets/{switch-5dieXxoo.js → switch-Cc05xGtg.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{switch-5dieXxoo.js.map → switch-Cc05xGtg.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{toggle-BFc-7_LL.js → toggle--QUzWk-Y.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{toggle-BFc-7_LL.js.map → toggle--QUzWk-Y.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/{useSSE-BqmKNBAI.js → useSSE-DcAeX67m.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{useSSE-BqmKNBAI.js.map → useSSE-DcAeX67m.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/index.html +2 -2
  68. package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
  69. package/extensions/dashboard-client/src/tabs/VectorCortexPolicyCard.tsx +43 -0
  70. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +2 -0
  71. package/extensions/dashboard-client/src/tabs/useVectorCortexPoll.ts +5 -0
  72. package/extensions/dashboard-client/src/types/vector-cortex-vc8.ts +17 -0
  73. package/extensions/dashboard-client/src/types/vector-cortex.ts +1 -1
  74. package/extensions/dashboard-server/api-contracts/vector-cortex-policy.ts +36 -0
  75. package/extensions/dashboard-server/route-dispatch.ts +6 -0
  76. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
  77. package/extensions/dashboard-server/routes-vector-cortex-policy.ts +55 -0
  78. package/package.json +1 -1
  79. package/src/config/vector-cortex.ts +15 -0
  80. package/src/config.ts +1 -0
  81. package/src/vector-cortex/controller/policy-emit.ts +82 -0
  82. package/src/vector-cortex/migrations/pressure-v2-ops.ts +204 -0
  83. package/src/vector-cortex/migrations/pressure-v2-types.ts +83 -0
  84. package/src/vector-cortex/migrations/pressure-v2.ts +59 -0
  85. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BTF1AGCJ.js +0 -2
  86. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BTF1AGCJ.js.map +0 -1
  87. package/extensions/dashboard-client/dist/assets/index-CYpzq9Lm.css +0 -1
@@ -225,6 +225,20 @@ export const VC7C_ENABLED = () => sprintFlag("MEGACOMPACT_VC7C");
225
225
  * EXCLUDED_SETTINGS), mirroring VC4A..VC7C.
226
226
  */
227
227
  export const VC8A_ENABLED = () => sprintFlag("MEGACOMPACT_VC8A");
228
+ /**
229
+ * VC8B — bounded shadow adaptive policy + M7 pressure-v2. Default ON.
230
+ * `MEGACOMPACT_VC8B=0` disables and is byte-identical to the predecessor
231
+ * (VC8A): the policy engine, shadow evaluator, and M7 migration STILL RUN
232
+ * (they are PURE — action validation, budget clamping, pressure canonicalization
233
+ * and copy/validate/switch, with no clock, storage, or network), so a budget is
234
+ * still clamped identically and an unknown pressure label is still rejected. The
235
+ * flag gates ONLY the `vector_cortex_shadow_decision_recorded` /
236
+ * `vector_cortex_policy_action_rejected` events and the policy dashboard seam,
237
+ * which reports `enabled:false` + mode C when off. This flag MUST also be a
238
+ * dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
239
+ * mirroring VC4A..VC8A.
240
+ */
241
+ export const VC8B_ENABLED = () => sprintFlag("MEGACOMPACT_VC8B");
228
242
  // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker) extracted to
229
243
  // vector-cortex-breakers.ts to keep this file under the 300-line soft limit.
230
244
  export { BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./vector-cortex-breakers.js";
package/dist/config.js CHANGED
@@ -114,4 +114,4 @@ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
114
114
  // default ON, `=0`/`_DISABLED` off. Re-exported from src/config/vector-cortex.ts
115
115
  // so root consumers share one source of truth.
116
116
  // ---------------------------------------------------------------------------
117
- export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
117
+ export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, VC8B_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
@@ -0,0 +1,12 @@
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
+ export {};
@@ -28,6 +28,10 @@ import { handleVectorCortexDiagnostics } from "./routes-vector-cortex-diagnostic
28
28
  // module so the outcomes/consent seam stays independent of diagnostics and
29
29
  // every file stays well under the 400-line extension soft limit.
30
30
  import { handleVectorCortexOutcomes } from "./routes-vector-cortex-outcomes.js";
31
+ // VC8B shadow adaptive policy + M7 pressure-v2 gets its own module so the
32
+ // policy/shadow seam stays independent and every file stays well under the
33
+ // 400-line extension soft limit.
34
+ import { handleVectorCortexPolicy } from "./routes-vector-cortex-policy.js";
31
35
  /**
32
36
  * Dispatch a request through every registered route handler.
33
37
  * Returns true if a handler claimed the request (ended the response).
@@ -129,5 +133,7 @@ export function dispatchRoutes(req, res, ctx) {
129
133
  return true;
130
134
  if (handleVectorCortexOutcomes(req, res, ctx))
131
135
  return true;
136
+ if (handleVectorCortexPolicy(req, res, ctx))
137
+ return true;
132
138
  return false;
133
139
  }
@@ -46,5 +46,6 @@ export const VECTOR_CORTEX_SETTINGS = {
46
46
  boolDirect("MEGACOMPACT_VC7B", "VC7B Provider Cache Economics", "Provider cache economics: extends the VC5B provider registry with cache economics (read/write price, TTL, minimum prefix, and a mandatory exclusion fixture id). Answers whether reusing a frozen crystal is actually worth it — a cache WRITE typically costs more than an uncached token, so a cache only pays off once a written prefix is re-read enough times before TTL expiry. Net savings are reported (never clamped at zero, so a losing cache stays visible) and every exclusion MUST name a proving conformance fixture or it is rejected (ECON_EXCLUSION_UNPROVEN). A compiled crystal boundary still reuses the VC7A key unchanged, and a randomized session-level experiment assigns arms by a stable hash so a lost assignment journal re-derives the same arm. OFF = byte-identical predecessor (VC7A); the economics/compiler/experiment arithmetic still runs, only the reporter + dashboard seam is suppressed.", true),
47
47
  boolDirect("MEGACOMPACT_VC7C", "VC7C Cache Diagnostics & Breakers", "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.", true),
48
48
  boolDirect("MEGACOMPACT_VC8A", "VC8A Consent-Bound Outcomes", "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.", true),
49
+ boolDirect("MEGACOMPACT_VC8B", "VC8B Shadow Adaptive Policy", "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.", true),
49
50
  ],
50
51
  };
@@ -0,0 +1,45 @@
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
+ import { VC8B_ENABLED } from "../../src/config.js";
19
+ import { sendJson } from "./routes-vector-cortex-shared.js";
20
+ /** GET /api/vector-cortex/policy — reader-only policy + shadow aggregate (VC8B). */
21
+ export function handleVectorCortexPolicy(req, res, _ctx) {
22
+ const url = req.url ?? "";
23
+ const path = url.split("?")[0] ?? url;
24
+ if (path !== "/api/vector-cortex/policy")
25
+ return false;
26
+ if (req.method !== "GET") {
27
+ sendJson(res, 405, { error: "method_not_allowed" });
28
+ return true;
29
+ }
30
+ const enabled = VC8B_ENABLED();
31
+ const mode = enabled ? "A" : "C";
32
+ const body = {
33
+ enabled,
34
+ mode,
35
+ shadowDecisions: 0,
36
+ clampedDecisions: 0,
37
+ rejectedInputs: 0,
38
+ liveMutations: 0,
39
+ pressureVersion: 1,
40
+ lastFailure: null,
41
+ updatedAt: new Date().toISOString(),
42
+ };
43
+ sendJson(res, 200, body);
44
+ return true;
45
+ }
@@ -225,6 +225,20 @@ export const VC7C_ENABLED = () => sprintFlag("MEGACOMPACT_VC7C");
225
225
  * EXCLUDED_SETTINGS), mirroring VC4A..VC7C.
226
226
  */
227
227
  export const VC8A_ENABLED = () => sprintFlag("MEGACOMPACT_VC8A");
228
+ /**
229
+ * VC8B — bounded shadow adaptive policy + M7 pressure-v2. Default ON.
230
+ * `MEGACOMPACT_VC8B=0` disables and is byte-identical to the predecessor
231
+ * (VC8A): the policy engine, shadow evaluator, and M7 migration STILL RUN
232
+ * (they are PURE — action validation, budget clamping, pressure canonicalization
233
+ * and copy/validate/switch, with no clock, storage, or network), so a budget is
234
+ * still clamped identically and an unknown pressure label is still rejected. The
235
+ * flag gates ONLY the `vector_cortex_shadow_decision_recorded` /
236
+ * `vector_cortex_policy_action_rejected` events and the policy dashboard seam,
237
+ * which reports `enabled:false` + mode C when off. This flag MUST also be a
238
+ * dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
239
+ * mirroring VC4A..VC8A.
240
+ */
241
+ export const VC8B_ENABLED = () => sprintFlag("MEGACOMPACT_VC8B");
228
242
  // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker) extracted to
229
243
  // vector-cortex-breakers.ts to keep this file under the 300-line soft limit.
230
244
  export { BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./vector-cortex-breakers.js";
@@ -114,4 +114,4 @@ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
114
114
  // default ON, `=0`/`_DISABLED` off. Re-exported from src/config/vector-cortex.ts
115
115
  // so root consumers share one source of truth.
116
116
  // ---------------------------------------------------------------------------
117
- export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
117
+ export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, VC8B_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
@@ -0,0 +1,56 @@
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
+ import { VC8B_ENABLED } from "../../config/vector-cortex.js";
20
+ /** Run `fn` only when an emit exists; a reporting failure is never fatal. */
21
+ function safe(emit, fn) {
22
+ if (emit === undefined)
23
+ return;
24
+ try {
25
+ fn(emit);
26
+ }
27
+ catch {
28
+ // Non-fatal: a reporting failure must never break the agent loop.
29
+ }
30
+ }
31
+ /** Report a shadow decision that was recorded. Gated by the flag. */
32
+ export function reportShadowDecisionRecorded(emit, decision) {
33
+ if (!VC8B_ENABLED())
34
+ return;
35
+ safe(emit, (e) => e("vector_cortex_shadow_decision_recorded", {
36
+ ts: undefined,
37
+ event: "vector_cortex_shadow_decision_recorded",
38
+ decisionId: decision.decisionId,
39
+ sessionId: decision.sessionId,
40
+ action: decision.action,
41
+ budget: decision.budget,
42
+ pressure: decision.pressure,
43
+ reason: decision.reason,
44
+ }));
45
+ }
46
+ /** Report a policy action rejected by validation. Gated by the flag. */
47
+ export function reportPolicyActionRejected(emit, rejection) {
48
+ if (!VC8B_ENABLED())
49
+ return;
50
+ safe(emit, (e) => e("vector_cortex_policy_action_rejected", {
51
+ ts: undefined,
52
+ event: "vector_cortex_policy_action_rejected",
53
+ decisionId: rejection.decisionId,
54
+ code: rejection.code,
55
+ }));
56
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * pressure-v2-ops.ts — M7 migration operational logic (copy/validate/switch).
3
+ *
4
+ * Extracted from pressure-v2.ts to keep the parent file under the 300-line
5
+ * soft limit (soft-as-hard gate). Pure logic over an injected M7Host;
6
+ * deterministic + testable; no console. PREVENT-002/011/PI-004 honored.
7
+ *
8
+ * WHY THE LABEL CHECK RE-READS THE HOST AT SWITCH TIME. The sprint's failure
9
+ * injection is explicit: kill the process after the copy phase, insert an
10
+ * unknown legacy pressure row, then resume — and the resumed run must return
11
+ * M7_PRESSURE_UNKNOWN and KEEP THE OLD POINTER. If the switch trusted the
12
+ * verdict of an earlier verify call, the injected row would be invisible and
13
+ * the pointer would flip over a store that still holds an uncanonical label.
14
+ * So `m7Switch` re-reads `v1Rows`/`existingV2`/`activeVersion` and re-validates
15
+ * against that live state. Validation is a precondition; the switch is the only
16
+ * place that proves the hazard is absent *right now*.
17
+ */
18
+ import { createHash } from "node:crypto";
19
+ import { isPressureLevel } from "../controller/policy.js";
20
+ import { M7_FAIL, PRESSURE_LEGACY_VERSION } from "./pressure-v2-types.js";
21
+ /** Length-prefixed framing so the folded fields cannot alias. */
22
+ function field(value) {
23
+ return `${Buffer.byteLength(value, "utf8")}:${value}`;
24
+ }
25
+ /** Row identity is (session, effectiveSeq) — one pressure per sequence point. */
26
+ const identity = (sessionId, effectiveSeq) => `${sessionId}::${effectiveSeq}`;
27
+ /** The authoritative v2 digest: SHA-256 over the length-prefixed fields. */
28
+ export function derivePressureDigest(sessionId, level, effectiveSeq, ts) {
29
+ return createHash("sha256")
30
+ .update(field("pressure-v2") +
31
+ field(sessionId) +
32
+ field(level) +
33
+ field(String(effectiveSeq)) +
34
+ field(ts), "utf8")
35
+ .digest("hex");
36
+ }
37
+ /**
38
+ * Map ONE legacy row to its v2 form. ONLY the canonical five labels map;
39
+ * anything else throws `{ code: M7_PRESSURE_UNKNOWN }`. There is deliberately
40
+ * no fallback branch — a coerced label is a silently misclassified workload.
41
+ */
42
+ export function mapPressureRow(v1) {
43
+ if (!isPressureLevel(v1.label)) {
44
+ throw { code: M7_FAIL.PRESSURE_UNKNOWN };
45
+ }
46
+ return {
47
+ sessionId: v1.sessionId,
48
+ level: v1.label,
49
+ effectiveSeq: v1.effectiveSeq,
50
+ ts: v1.ts,
51
+ digest: derivePressureDigest(v1.sessionId, v1.label, v1.effectiveSeq, v1.ts),
52
+ };
53
+ }
54
+ /** True when every legacy row carries a canonical label. */
55
+ export function allLabelsCanonical(host) {
56
+ return host.v1Rows().every((r) => isPressureLevel(r.label));
57
+ }
58
+ /**
59
+ * COPY: resumable per (session, effectiveSeq). An interrupted run resumes
60
+ * without duplicate rows. An uncanonical label aborts the copy with
61
+ * M7_PRESSURE_UNKNOWN before anything is written.
62
+ */
63
+ export function m7Copy(host) {
64
+ const existing = new Map(host.existingV2().map((r) => [identity(r.sessionId, r.effectiveSeq), r]));
65
+ const wanted = [];
66
+ for (const v1 of host.v1Rows()) {
67
+ // Throws M7_PRESSURE_UNKNOWN for a non-canonical label.
68
+ const fresh = mapPressureRow(v1);
69
+ const stored = existing.get(identity(v1.sessionId, v1.effectiveSeq));
70
+ if (stored !== undefined && stored.digest === fresh.digest)
71
+ continue;
72
+ wanted.push(fresh);
73
+ }
74
+ if (wanted.length > 0)
75
+ host.putV2(wanted);
76
+ return { written: wanted, all: [...host.existingV2()] };
77
+ }
78
+ function dedupe(codes) {
79
+ const out = [];
80
+ for (const c of codes)
81
+ if (!out.includes(c))
82
+ out.push(c);
83
+ return out;
84
+ }
85
+ /**
86
+ * VALIDATE: compare counts and digests between v1 and v2, and re-check every
87
+ * legacy label. Returns codes rather than throwing so a caller sees every
88
+ * problem at once.
89
+ */
90
+ export function m7Verify(host) {
91
+ const codes = [];
92
+ const v1Rows = host.v1Rows();
93
+ const stored = host.existingV2();
94
+ // Label check first: an uncanonical legacy label invalidates the whole run.
95
+ for (const v1 of v1Rows) {
96
+ if (!isPressureLevel(v1.label))
97
+ codes.push(M7_FAIL.PRESSURE_UNKNOWN);
98
+ }
99
+ const counts = new Map();
100
+ for (const r of stored) {
101
+ const id = identity(r.sessionId, r.effectiveSeq);
102
+ counts.set(id, (counts.get(id) ?? 0) + 1);
103
+ }
104
+ const expected = new Set();
105
+ for (const v1 of v1Rows) {
106
+ if (!isPressureLevel(v1.label))
107
+ continue;
108
+ const id = identity(v1.sessionId, v1.effectiveSeq);
109
+ expected.add(id);
110
+ const n = counts.get(id) ?? 0;
111
+ if (n === 0)
112
+ codes.push(M7_FAIL.COPY_PARTIAL);
113
+ if (n > 1)
114
+ codes.push(M7_FAIL.COUNT_MISMATCH);
115
+ }
116
+ // Every stored row must re-derive its own digest and correspond to a v1 row.
117
+ for (const r of stored) {
118
+ const fresh = derivePressureDigest(r.sessionId, r.level, r.effectiveSeq, r.ts);
119
+ if (r.digest !== fresh)
120
+ codes.push(M7_FAIL.DIGEST_MISMATCH);
121
+ if (!expected.has(identity(r.sessionId, r.effectiveSeq))) {
122
+ codes.push(M7_FAIL.COUNT_MISMATCH);
123
+ }
124
+ }
125
+ return { ok: codes.length === 0, codes: dedupe(codes) };
126
+ }
127
+ /**
128
+ * SWITCH: atomically flip the active pointer to v2 — but ONLY after
129
+ * re-validating against freshly-read host state (see the module note on the
130
+ * post-copy failure injection). The pointer is left untouched on any failure.
131
+ */
132
+ export function m7Switch(host) {
133
+ if (host.activeVersion() !== PRESSURE_LEGACY_VERSION) {
134
+ return { ok: false, codes: [M7_FAIL.NOT_ON_LEGACY] };
135
+ }
136
+ // Re-read the live rows: an unknown label injected after the copy is caught
137
+ // here, and the old pointer survives.
138
+ if (!allLabelsCanonical(host)) {
139
+ return { ok: false, codes: [M7_FAIL.PRESSURE_UNKNOWN] };
140
+ }
141
+ const verify = m7Verify(host);
142
+ if (!verify.ok)
143
+ return verify;
144
+ host.switchToV2();
145
+ return { ok: true, codes: [] };
146
+ }
147
+ /**
148
+ * Full M7 migration: copy, then validate + switch. An uncanonical label thrown
149
+ * during the copy is converted to a result code so the caller sees the same
150
+ * shape as a validation failure, and the pointer is left on legacy.
151
+ */
152
+ export function migratePressureV2(host) {
153
+ try {
154
+ m7Copy(host);
155
+ }
156
+ catch (err) {
157
+ const code = typeof err === "object" && err !== null && "code" in err
158
+ ? err.code
159
+ : undefined;
160
+ if (code === M7_FAIL.PRESSURE_UNKNOWN) {
161
+ return { ok: false, codes: [M7_FAIL.PRESSURE_UNKNOWN] };
162
+ }
163
+ throw err;
164
+ }
165
+ return m7Switch(host);
166
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * pressure-v2-types.ts — M7 pressure-v2 migration types.
3
+ *
4
+ * Extracted from pressure-v2.ts to keep the parent file under the 300-line
5
+ * soft limit (soft-as-hard gate). Pure type + constant definitions; no logic.
6
+ * PREVENT-002/011/PI-004 honored.
7
+ */
8
+ /** The active-version value the v2 pointer is switched to. */
9
+ export const PRESSURE_V2_VERSION = 2;
10
+ /** The legacy (predecessor) active version. */
11
+ export const PRESSURE_LEGACY_VERSION = 1;
12
+ /** M7 failure codes. */
13
+ export const M7_FAIL = {
14
+ /** A legacy row carries a label outside the canonical five levels. */
15
+ PRESSURE_UNKNOWN: "M7_PRESSURE_UNKNOWN",
16
+ /** Copied row count does not match the legacy row count. */
17
+ COUNT_MISMATCH: "M7_COUNT_MISMATCH",
18
+ /** A v2 row digest does not re-derive from its own declared fields. */
19
+ DIGEST_MISMATCH: "M7_DIGEST_MISMATCH",
20
+ /** A legacy row has no corresponding v2 row (interrupted copy). */
21
+ COPY_PARTIAL: "M7_COPY_PARTIAL",
22
+ /** The active pointer is not on v1; switching would regress or no-op. */
23
+ NOT_ON_LEGACY: "M7_NOT_ON_LEGACY",
24
+ };
25
+ /**
26
+ * Registered M7 conformance IDs (M7-001..015). The acceptance test reads these
27
+ * rows from the v2 `adaptive-policy/` domain and asserts each returns its
28
+ * manifest bytes or exactly its listed failure code. Mirrors M5_IDS / M6_IDS.
29
+ */
30
+ export const M7_IDS = Array.from({ length: 15 }, (_v, i) => `M7-${String(i + 1).padStart(3, "0")}`);
31
+ /** Named M7 rows surfaced by the conformance corpus. */
32
+ export const M7_NAMED_IDS = ["M7-PRESSURE-003"];
@@ -0,0 +1,36 @@
1
+ /**
2
+ * vector-cortex/migrations/pressure-v2.ts — M7 pressure-v2 migration
3
+ * (COPY + VALIDATE + SWITCH).
4
+ *
5
+ * Delegate-shell: types + constants live in pressure-v2-types.ts and the
6
+ * operational logic (copy/verify/switch/label mapping) lives in
7
+ * pressure-v2-ops.ts. This file re-exports the public surface so callers import
8
+ * from a single module. Split to keep under the 300-line soft limit
9
+ * (soft-as-hard gate).
10
+ *
11
+ * M7 canonicalizes the context-pressure label. The predecessor stored whatever
12
+ * label a producer happened to write; v2 admits EXACTLY five — `low`, `medium`,
13
+ * `high`, `ultra`, `mega` — and rejects everything else as
14
+ * `M7_PRESSURE_UNKNOWN`. The rejection is the feature: coercing an unrecognized
15
+ * legacy label onto the nearest level would silently reclassify a workload, and
16
+ * the direction of that error is unknowable from the label alone.
17
+ *
18
+ * Like M4/M5/M6 it follows the copy/validate/switch contract:
19
+ *
20
+ * - copy: resumable per (session, effectiveSeq) — an interrupted run
21
+ * resumes without duplicate rows or active-pointer drift.
22
+ * - validate: every v1 row has exactly one v2 row, every v2 digest re-derives
23
+ * from its own declared fields, and every legacy label is
24
+ * canonical.
25
+ * - switch: ATOMICALLY flip the active pointer via `host.switchToV2()`, but
26
+ * only after RE-READING host state and re-validating. The sprint's
27
+ * failure injection kills the process after the copy and then
28
+ * inserts an unknown legacy pressure; the resumed run must return
29
+ * `M7_PRESSURE_UNKNOWN` and KEEP THE OLD POINTER. A migration that
30
+ * trusts a stale verification is a migration that corrupts on
31
+ * restart.
32
+ *
33
+ * PREVENT-002/011/PI-004 honored.
34
+ */
35
+ export { PRESSURE_V2_VERSION, PRESSURE_LEGACY_VERSION, M7_FAIL, M7_IDS, M7_NAMED_IDS, } from "./pressure-v2-types.js";
36
+ export { derivePressureDigest, mapPressureRow, allLabelsCanonical, m7Copy, m7Verify, m7Switch, migratePressureV2, } from "./pressure-v2-ops.js";
@@ -0,0 +1,56 @@
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
+ import { VC8B_ENABLED } from "../../config/vector-cortex.js";
20
+ /** Run `fn` only when an emit exists; a reporting failure is never fatal. */
21
+ function safe(emit, fn) {
22
+ if (emit === undefined)
23
+ return;
24
+ try {
25
+ fn(emit);
26
+ }
27
+ catch {
28
+ // Non-fatal: a reporting failure must never break the agent loop.
29
+ }
30
+ }
31
+ /** Report a shadow decision that was recorded. Gated by the flag. */
32
+ export function reportShadowDecisionRecorded(emit, decision) {
33
+ if (!VC8B_ENABLED())
34
+ return;
35
+ safe(emit, (e) => e("vector_cortex_shadow_decision_recorded", {
36
+ ts: undefined,
37
+ event: "vector_cortex_shadow_decision_recorded",
38
+ decisionId: decision.decisionId,
39
+ sessionId: decision.sessionId,
40
+ action: decision.action,
41
+ budget: decision.budget,
42
+ pressure: decision.pressure,
43
+ reason: decision.reason,
44
+ }));
45
+ }
46
+ /** Report a policy action rejected by validation. Gated by the flag. */
47
+ export function reportPolicyActionRejected(emit, rejection) {
48
+ if (!VC8B_ENABLED())
49
+ return;
50
+ safe(emit, (e) => e("vector_cortex_policy_action_rejected", {
51
+ ts: undefined,
52
+ event: "vector_cortex_policy_action_rejected",
53
+ decisionId: rejection.decisionId,
54
+ code: rejection.code,
55
+ }));
56
+ }
@@ -0,0 +1,158 @@
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
+ import { POLICY_ACTIONS, POLICY_DECISION_SCHEMA_V1, POL_ACTION_FORBIDDEN, POL_BUDGET_OUT_OF_BOUNDS, POL_PRESSURE_UNKNOWN, PRESSURE_LEVELS, } from "./types.js";
25
+ /** Construct a policy failure. */
26
+ function fail(code) {
27
+ return { code };
28
+ }
29
+ /** Type guard: is this a canonical pressure level? */
30
+ export function isPressureLevel(label) {
31
+ return PRESSURE_LEVELS.includes(label);
32
+ }
33
+ /** Type guard: is this an allowed policy action? */
34
+ export function isPolicyAction(action) {
35
+ return POLICY_ACTIONS.includes(action);
36
+ }
37
+ /**
38
+ * Validate a pressure label against the canonical five. Throws
39
+ * `{ code: POL_PRESSURE_UNKNOWN }` rather than coercing — see the module note.
40
+ */
41
+ export function validatePressureLabel(label) {
42
+ if (!isPressureLevel(label))
43
+ throw fail(POL_PRESSURE_UNKNOWN);
44
+ return label;
45
+ }
46
+ /**
47
+ * Validate an action against the allowed finite set. Throws
48
+ * `{ code: POL_ACTION_FORBIDDEN }` for anything else.
49
+ */
50
+ export function validateAction(action) {
51
+ if (!isPolicyAction(action))
52
+ throw fail(POL_ACTION_FORBIDDEN);
53
+ return action;
54
+ }
55
+ /**
56
+ * Validate the bound pair itself. A window with min > max, or a non-finite
57
+ * bound, has no correct clamp result, so it is rejected rather than guessed at.
58
+ */
59
+ export function validateBounds(bounds) {
60
+ const { minBudget, maxBudget } = bounds;
61
+ if (!Number.isFinite(minBudget) || !Number.isFinite(maxBudget)) {
62
+ throw fail(POL_BUDGET_OUT_OF_BOUNDS);
63
+ }
64
+ if (minBudget > maxBudget)
65
+ throw fail(POL_BUDGET_OUT_OF_BOUNDS);
66
+ if (minBudget < 0)
67
+ throw fail(POL_BUDGET_OUT_OF_BOUNDS);
68
+ return bounds;
69
+ }
70
+ /**
71
+ * Clamp a budget into `[minBudget, maxBudget]`.
72
+ *
73
+ * A NaN budget clamps to `minBudget`: NaN comparisons are all false, so a naive
74
+ * Math.min/Math.max chain would propagate NaN straight through the "bounded"
75
+ * guarantee. The safest interpretation of an unusable request is the floor.
76
+ */
77
+ export function clampBudget(budget, minBudget, maxBudget) {
78
+ validateBounds({ minBudget, maxBudget });
79
+ if (!Number.isFinite(budget))
80
+ return minBudget;
81
+ if (budget < minBudget)
82
+ return minBudget;
83
+ if (budget > maxBudget)
84
+ return maxBudget;
85
+ return budget;
86
+ }
87
+ /** The multiplier applied to the requested budget at each pressure level. */
88
+ const PRESSURE_FACTOR = {
89
+ low: 1,
90
+ medium: 1,
91
+ high: 0.75,
92
+ ultra: 0.5,
93
+ mega: 0.25,
94
+ };
95
+ /**
96
+ * The action selected at each pressure level. Total over PressureLevel, so the
97
+ * action space cannot grow: `mega` refuses outright, `ultra` defers, `high`
98
+ * dampens, and the quiet levels admit.
99
+ */
100
+ const PRESSURE_ACTION = {
101
+ low: "admit",
102
+ medium: "admit",
103
+ high: "dampen",
104
+ ultra: "defer",
105
+ mega: "reject",
106
+ };
107
+ /** Select the reason code that explains the decision. */
108
+ function reasonFor(pressure, requested, clamped, bounds) {
109
+ if (clamped === bounds.maxBudget && requested > bounds.maxBudget) {
110
+ return "budget_clamped_high";
111
+ }
112
+ if (clamped === bounds.minBudget && requested < bounds.minBudget) {
113
+ return "budget_clamped_low";
114
+ }
115
+ if (pressure === "mega" || pressure === "ultra")
116
+ return "pressure_critical";
117
+ if (pressure === "high")
118
+ return "pressure_elevated";
119
+ return "within_bounds";
120
+ }
121
+ /**
122
+ * Evaluate one policy input into a bounded decision.
123
+ *
124
+ * Order matters: validate the label, validate the window, apply the
125
+ * pressure factor, THEN clamp. Clamping last is what makes the bounded-budget
126
+ * guarantee hold for every action including escalate.
127
+ *
128
+ * Throws `{ code }` on an unknown pressure label or an invalid bound pair.
129
+ */
130
+ export function evaluatePolicy(input) {
131
+ const pressure = validatePressureLabel(input.pressure);
132
+ const bounds = validateBounds(input.bounds);
133
+ const requested = Number.isFinite(input.requestedBudget)
134
+ ? input.requestedBudget
135
+ : bounds.minBudget;
136
+ const adjusted = requested * PRESSURE_FACTOR[pressure];
137
+ const budget = clampBudget(adjusted, bounds.minBudget, bounds.maxBudget);
138
+ return {
139
+ schema: POLICY_DECISION_SCHEMA_V1,
140
+ decisionId: input.decisionId,
141
+ sessionId: input.sessionId,
142
+ action: PRESSURE_ACTION[pressure],
143
+ budget,
144
+ pressure,
145
+ reason: reasonFor(pressure, input.requestedBudget, budget, bounds),
146
+ ts: input.ts,
147
+ };
148
+ }
149
+ /**
150
+ * Assert a decision satisfies the sprint invariant: allowed action AND bounded
151
+ * budget. Used by the acceptance aggregator to check every produced row.
152
+ */
153
+ export function isDecisionWithinBounds(decision, bounds) {
154
+ return (isPolicyAction(decision.action) &&
155
+ Number.isFinite(decision.budget) &&
156
+ decision.budget >= bounds.minBudget &&
157
+ decision.budget <= bounds.maxBudget);
158
+ }