pi-mega-compact 0.20.18 → 0.20.20

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 (119) hide show
  1. package/dist/config/vector-cortex-vc8c.js +31 -0
  2. package/dist/config/vector-cortex.js +19 -0
  3. package/dist/config.js +1 -1
  4. package/dist/extensions/dashboard-server/api-contracts/vector-cortex-platform.js +12 -0
  5. package/dist/extensions/dashboard-server/api-contracts/vector-cortex-policy.js +12 -0
  6. package/dist/extensions/dashboard-server/route-dispatch.js +12 -0
  7. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +2 -0
  8. package/dist/extensions/dashboard-server/routes-vector-cortex-platform.js +42 -0
  9. package/dist/extensions/dashboard-server/routes-vector-cortex-policy.js +45 -0
  10. package/dist/src/config/vector-cortex-vc8c.js +31 -0
  11. package/dist/src/config/vector-cortex.js +19 -0
  12. package/dist/src/config.js +1 -1
  13. package/dist/src/vector-cortex/cache/_diagnostics-fixture.js +79 -5
  14. package/dist/src/vector-cortex/cache/diagnostics.js +17 -8
  15. package/dist/src/vector-cortex/controller/_adaptive-fixture.js +126 -0
  16. package/dist/src/vector-cortex/controller/policy-emit.js +56 -0
  17. package/dist/src/vector-cortex/migrations/pressure-v2-ops.js +166 -0
  18. package/dist/src/vector-cortex/migrations/pressure-v2-types.js +32 -0
  19. package/dist/src/vector-cortex/migrations/pressure-v2.js +36 -0
  20. package/dist/src/vector-cortex/migrations/request-hash-v2-ops.js +23 -7
  21. package/dist/src/vector-cortex/platform/cross-read.js +98 -0
  22. package/dist/src/vector-cortex/platform/emit.js +54 -0
  23. package/dist/src/vector-cortex/platform/select.js +104 -0
  24. package/dist/src/vector-cortex/platform/types.js +57 -0
  25. package/dist/vector-cortex/cache/_diagnostics-fixture.js +79 -5
  26. package/dist/vector-cortex/cache/diagnostics.js +17 -8
  27. package/dist/vector-cortex/controller/_adaptive-fixture.js +126 -0
  28. package/dist/vector-cortex/controller/policy-emit.js +56 -0
  29. package/dist/vector-cortex/controller/policy.js +158 -0
  30. package/dist/vector-cortex/controller/shadow.js +117 -0
  31. package/dist/vector-cortex/controller/types.js +85 -0
  32. package/dist/vector-cortex/migrations/pressure-v2-ops.js +166 -0
  33. package/dist/vector-cortex/migrations/pressure-v2-types.js +32 -0
  34. package/dist/vector-cortex/migrations/pressure-v2.js +36 -0
  35. package/dist/vector-cortex/migrations/request-hash-v2-ops.js +23 -7
  36. package/dist/vector-cortex/platform/cross-read.js +98 -0
  37. package/dist/vector-cortex/platform/emit.js +54 -0
  38. package/dist/vector-cortex/platform/select.js +104 -0
  39. package/dist/vector-cortex/platform/types.js +57 -0
  40. package/extensions/dashboard-client/dist/assets/{AreaChart-BOGp2sag.js → AreaChart-BkfB-LSQ.js} +2 -2
  41. package/extensions/dashboard-client/dist/assets/{AreaChart-BOGp2sag.js.map → AreaChart-BkfB-LSQ.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/{BarChart-BHgA-QPL.js → BarChart-Cs1kogko.js} +2 -2
  43. package/extensions/dashboard-client/dist/assets/{BarChart-BHgA-QPL.js.map → BarChart-Cs1kogko.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/assets/{CacheTab-6E7lwM74.js → CacheTab-BZgaguGr.js} +2 -2
  45. package/extensions/dashboard-client/dist/assets/{CacheTab-6E7lwM74.js.map → CacheTab-BZgaguGr.js.map} +1 -1
  46. package/extensions/dashboard-client/dist/assets/{EventsTab-BEtNmFHz.js → EventsTab-BFbkI_MO.js} +2 -2
  47. package/extensions/dashboard-client/dist/assets/{EventsTab-BEtNmFHz.js.map → EventsTab-BFbkI_MO.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/{HealthTab-DGxZulRH.js → HealthTab-CvlOENCX.js} +2 -2
  49. package/extensions/dashboard-client/dist/assets/{HealthTab-DGxZulRH.js.map → HealthTab-CvlOENCX.js.map} +1 -1
  50. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-C86jppQE.js → MaintenanceTab-CtBuz2qL.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-C86jppQE.js.map → MaintenanceTab-CtBuz2qL.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-J3VVxooZ.js → MemoryMapTab-K4775S4d.js} +2 -2
  53. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-J3VVxooZ.js.map → MemoryMapTab-K4775S4d.js.map} +1 -1
  54. package/extensions/dashboard-client/dist/assets/{MetricsTab-yn9AFumo.js → MetricsTab-DVGcFInz.js} +2 -2
  55. package/extensions/dashboard-client/dist/assets/{MetricsTab-yn9AFumo.js.map → MetricsTab-DVGcFInz.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/{OverviewTab-BMarUiq3.js → OverviewTab-4LmW5ZCl.js} +2 -2
  57. package/extensions/dashboard-client/dist/assets/{OverviewTab-BMarUiq3.js.map → OverviewTab-4LmW5ZCl.js.map} +1 -1
  58. package/extensions/dashboard-client/dist/assets/{ReposTab-DxnzMive.js → ReposTab-StebchgQ.js} +2 -2
  59. package/extensions/dashboard-client/dist/assets/{ReposTab-DxnzMive.js.map → ReposTab-StebchgQ.js.map} +1 -1
  60. package/extensions/dashboard-client/dist/assets/{SessionsTab-DXKpDUuC.js → SessionsTab-C3D_XAkX.js} +2 -2
  61. package/extensions/dashboard-client/dist/assets/{SessionsTab-DXKpDUuC.js.map → SessionsTab-C3D_XAkX.js.map} +1 -1
  62. package/extensions/dashboard-client/dist/assets/{SetupTab-BctuV-CJ.js → SetupTab-BTBGyppF.js} +2 -2
  63. package/extensions/dashboard-client/dist/assets/{SetupTab-BctuV-CJ.js.map → SetupTab-BTBGyppF.js.map} +1 -1
  64. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-D8yOC96S.js → TimeSavedCard-CTPi_AAW.js} +2 -2
  65. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-D8yOC96S.js.map → TimeSavedCard-CTPi_AAW.js.map} +1 -1
  66. package/extensions/dashboard-client/dist/assets/{TurnsTab-BBxqGYH-.js → TurnsTab-DSOGcY5Q.js} +2 -2
  67. package/extensions/dashboard-client/dist/assets/{TurnsTab-BBxqGYH-.js.map → TurnsTab-DSOGcY5Q.js.map} +1 -1
  68. package/extensions/dashboard-client/dist/assets/VectorCortexTab-OMW8_RFf.js +2 -0
  69. package/extensions/dashboard-client/dist/assets/VectorCortexTab-OMW8_RFf.js.map +1 -0
  70. package/extensions/dashboard-client/dist/assets/{WikiTab-DdJQSagO.js → WikiTab-Db-qFDdg.js} +2 -2
  71. package/extensions/dashboard-client/dist/assets/{WikiTab-DdJQSagO.js.map → WikiTab-Db-qFDdg.js.map} +1 -1
  72. package/extensions/dashboard-client/dist/assets/{button-B4Q9h_8Y.js → button-BF4UgBAD.js} +2 -2
  73. package/extensions/dashboard-client/dist/assets/{button-B4Q9h_8Y.js.map → button-BF4UgBAD.js.map} +1 -1
  74. package/extensions/dashboard-client/dist/assets/{card-WyR31hUV.js → card-XmXoYQqB.js} +2 -2
  75. package/extensions/dashboard-client/dist/assets/{card-WyR31hUV.js.map → card-XmXoYQqB.js.map} +1 -1
  76. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-pTio6Bq3.js → generateCategoricalChart-Be8hM1aU.js} +2 -2
  77. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-pTio6Bq3.js.map → generateCategoricalChart-Be8hM1aU.js.map} +1 -1
  78. package/extensions/dashboard-client/dist/assets/index-Difwoltx.css +1 -0
  79. package/extensions/dashboard-client/dist/assets/{index-BoLUiNsd.js → index-NXDgILxv.js} +3 -3
  80. package/extensions/dashboard-client/dist/assets/{index-BoLUiNsd.js.map → index-NXDgILxv.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/assets/{switch-5dieXxoo.js → switch-kUZCrbia.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{switch-5dieXxoo.js.map → switch-kUZCrbia.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/assets/{toggle-BFc-7_LL.js → toggle-DEe5mCwF.js} +2 -2
  84. package/extensions/dashboard-client/dist/assets/{toggle-BFc-7_LL.js.map → toggle-DEe5mCwF.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/{useSSE-BqmKNBAI.js → useSSE-BlCyLqge.js} +2 -2
  86. package/extensions/dashboard-client/dist/assets/{useSSE-BqmKNBAI.js.map → useSSE-BlCyLqge.js.map} +1 -1
  87. package/extensions/dashboard-client/dist/index.html +2 -2
  88. package/extensions/dashboard-client/src/api/vector-cortex.ts +18 -0
  89. package/extensions/dashboard-client/src/tabs/VectorCortexPlatformCard.tsx +42 -0
  90. package/extensions/dashboard-client/src/tabs/VectorCortexPolicyCard.tsx +43 -0
  91. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +4 -0
  92. package/extensions/dashboard-client/src/tabs/useVectorCortexPoll.ts +10 -0
  93. package/extensions/dashboard-client/src/types/vector-cortex-vc8.ts +34 -1
  94. package/extensions/dashboard-client/src/types/vector-cortex.ts +1 -1
  95. package/extensions/dashboard-server/api-contracts/vector-cortex-platform.ts +34 -0
  96. package/extensions/dashboard-server/api-contracts/vector-cortex-policy.ts +36 -0
  97. package/extensions/dashboard-server/route-dispatch.ts +12 -0
  98. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +12 -0
  99. package/extensions/dashboard-server/routes-vector-cortex-platform.ts +52 -0
  100. package/extensions/dashboard-server/routes-vector-cortex-policy.ts +55 -0
  101. package/package.json +1 -1
  102. package/src/config/vector-cortex-vc8c.ts +33 -0
  103. package/src/config/vector-cortex.ts +21 -0
  104. package/src/config.ts +2 -0
  105. package/src/vector-cortex/cache/_diagnostics-fixture.ts +163 -5
  106. package/src/vector-cortex/cache/diagnostics.ts +18 -9
  107. package/src/vector-cortex/controller/_adaptive-fixture.ts +230 -0
  108. package/src/vector-cortex/controller/policy-emit.ts +82 -0
  109. package/src/vector-cortex/migrations/pressure-v2-ops.ts +204 -0
  110. package/src/vector-cortex/migrations/pressure-v2-types.ts +83 -0
  111. package/src/vector-cortex/migrations/pressure-v2.ts +59 -0
  112. package/src/vector-cortex/migrations/request-hash-v2-ops.ts +21 -6
  113. package/src/vector-cortex/platform/cross-read.ts +122 -0
  114. package/src/vector-cortex/platform/emit.ts +77 -0
  115. package/src/vector-cortex/platform/select.ts +131 -0
  116. package/src/vector-cortex/platform/types.ts +169 -0
  117. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BTF1AGCJ.js +0 -2
  118. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BTF1AGCJ.js.map +0 -1
  119. package/extensions/dashboard-client/dist/assets/index-CYpzq9Lm.css +0 -1
@@ -0,0 +1,230 @@
1
+ /**
2
+ * controller/_adaptive-fixture.ts — VC8B conformance fixture I/O + host builders.
3
+ *
4
+ * Reads conformance fixtures from the v2 `adaptive-policy/` domain, decodes
5
+ * them into the REAL production types (`PolicyInput`, `M7Host`) and feeds
6
+ * them verbatim into `evaluatePolicy` / `evaluateShadow` / `migratePressureV2`.
7
+ *
8
+ * No mocks, no stubs, no parallel "test shape": the decoded objects ARE the
9
+ * production types and drive the production functions directly.
10
+ * Mirrors `_diagnostics-fixture.ts` (VC7C) and `_economics-fixture.ts` (VC7B).
11
+ */
12
+
13
+ import { readFileSync, existsSync } from "node:fs";
14
+ import { join, dirname } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+
17
+ import type { PolicyInput } from "./types.js";
18
+ import type {
19
+ M7Host,
20
+ PressureV1Row,
21
+ PressureV2Row,
22
+ } from "../migrations/pressure-v2-types.js";
23
+
24
+ const here = dirname(fileURLToPath(import.meta.url));
25
+
26
+ function repoRoot(from: string): string {
27
+ let dir = from;
28
+ for (let i = 0; i < 8; i++) {
29
+ if (existsSync(join(dir, "conformance", "vector-cortex"))) return dir;
30
+ const next = dirname(dir);
31
+ if (next === dir) break;
32
+ dir = next;
33
+ }
34
+ throw new Error("conformance corpus not found above " + from);
35
+ }
36
+
37
+ const V2 = join(repoRoot(here), "conformance", "vector-cortex", "v2");
38
+ const DIR = join(V2, "adaptive-policy");
39
+
40
+ /** Read + parse one conformance fixture by ID from adaptive-policy/. */
41
+ export function adaptiveFixture(id: string): Record<string, unknown> {
42
+ const raw = readFileSync(join(DIR, `${id}.json`), "utf8");
43
+ return JSON.parse(raw);
44
+ }
45
+
46
+ /** Run `fn` with MEGACOMPACT_VC8B set to `value`, restoring the prior value after. */
47
+ export function withVc8bFlag<T>(value: string, fn: () => T): T {
48
+ const prior = process.env.MEGACOMPACT_VC8B;
49
+ process.env.MEGACOMPACT_VC8B = value;
50
+ try {
51
+ return fn();
52
+ } finally {
53
+ if (prior === undefined) delete process.env.MEGACOMPACT_VC8B;
54
+ else process.env.MEGACOMPACT_VC8B = prior;
55
+ }
56
+ }
57
+
58
+ // ── Policy decision fixtures (POL-001..025 + POL-CLAMP-001) ──────────────────
59
+
60
+ interface PolFxInput {
61
+ decisionId: string;
62
+ sessionId: string;
63
+ pressure: string;
64
+ requestedBudget: number;
65
+ bounds: { minBudget: number; maxBudget: number };
66
+ ts: string;
67
+ alternateRequestedBudget?: number;
68
+ }
69
+
70
+ interface PolFxExpected {
71
+ ok: boolean;
72
+ action?: string;
73
+ budget?: number;
74
+ pressure?: string;
75
+ reason?: string;
76
+ code?: string;
77
+ alternateBudget?: number;
78
+ alternateReason?: string;
79
+ }
80
+
81
+ export interface PolFx {
82
+ id: string;
83
+ assertion: string;
84
+ input: PolFxInput;
85
+ expected: PolFxExpected;
86
+ }
87
+
88
+ /** Read a policy-decision fixture as a typed PolFx. */
89
+ export function polFx(id: string): PolFx {
90
+ return adaptiveFixture(id) as unknown as PolFx;
91
+ }
92
+
93
+ /** Decode the fixture's JSON input into the production PolicyInput. */
94
+ export function toPolicyInput(fx: PolFx): PolicyInput {
95
+ return {
96
+ decisionId: fx.input.decisionId,
97
+ sessionId: fx.input.sessionId,
98
+ pressure: fx.input.pressure,
99
+ requestedBudget: fx.input.requestedBudget,
100
+ bounds: {
101
+ minBudget: fx.input.bounds.minBudget,
102
+ maxBudget: fx.input.bounds.maxBudget,
103
+ },
104
+ ts: fx.input.ts,
105
+ };
106
+ }
107
+
108
+ // ── Shadow fixtures (POL-SHADOW-002) ────────────────────────────────────────
109
+
110
+ interface ShadowFxInput {
111
+ inputs: readonly PolFxInput[];
112
+ promptBytes: string;
113
+ }
114
+
115
+ interface ShadowFxExpected {
116
+ ok: boolean;
117
+ evaluated?: number;
118
+ clamped?: number;
119
+ rejected?: number;
120
+ liveMutations?: number;
121
+ promptUnchanged?: boolean;
122
+ }
123
+
124
+ export interface ShadowFx {
125
+ id: string;
126
+ assertion: string;
127
+ input: ShadowFxInput;
128
+ expected: ShadowFxExpected;
129
+ }
130
+
131
+ /** Read a policy-shadow fixture as a typed ShadowFx. */
132
+ export function shadowFx(id: string): ShadowFx {
133
+ return adaptiveFixture(id) as unknown as ShadowFx;
134
+ }
135
+
136
+ /** Decode shadow input rows into production PolicyInput[]. */
137
+ export function toShadowInputs(fx: ShadowFx): readonly PolicyInput[] {
138
+ return fx.input.inputs.map((i) => ({
139
+ decisionId: i.decisionId,
140
+ sessionId: i.sessionId,
141
+ pressure: i.pressure,
142
+ requestedBudget: i.requestedBudget,
143
+ bounds: {
144
+ minBudget: i.bounds.minBudget,
145
+ maxBudget: i.bounds.maxBudget,
146
+ },
147
+ ts: i.ts,
148
+ }));
149
+ }
150
+
151
+ // ── M7 pressure-v2 fixtures (M7-001..015 + M7-PRESSURE-003) ─────────────────
152
+
153
+ interface M7FxInput {
154
+ scenario: string;
155
+ v1Rows: readonly {
156
+ sessionId: string;
157
+ label: string;
158
+ effectiveSeq: number;
159
+ ts: string;
160
+ }[];
161
+ injectedRow?: {
162
+ sessionId: string;
163
+ label: string;
164
+ effectiveSeq: number;
165
+ ts: string;
166
+ };
167
+ activeVersion?: number;
168
+ }
169
+
170
+ interface M7FxExpected {
171
+ ok: boolean;
172
+ codes?: string[];
173
+ code?: string;
174
+ activeVersionAfter?: number;
175
+ rowCount?: number;
176
+ }
177
+
178
+ export interface M7Fx {
179
+ id: string;
180
+ assertion: string;
181
+ input: M7FxInput;
182
+ expected: M7FxExpected;
183
+ }
184
+
185
+ /** Read an M7 fixture as a typed M7Fx. */
186
+ export function m7Fx(id: string): M7Fx {
187
+ return adaptiveFixture(id) as unknown as M7Fx;
188
+ }
189
+
190
+ /** Build an in-memory M7Host from the fixture input. */
191
+ export function toM7Host(
192
+ fx: M7Fx,
193
+ ): M7Host & { activeVersionAfter: number; readonly v2RowCount: number } {
194
+ const v1Rows: PressureV1Row[] = fx.input.v1Rows.map((r) => ({
195
+ sessionId: r.sessionId,
196
+ label: r.label,
197
+ effectiveSeq: r.effectiveSeq,
198
+ ts: r.ts,
199
+ }));
200
+ // For inject-after-copy, the injected row carries a non-canonical label.
201
+ // migratePressureV2 calls m7Copy first, which throws M7_PRESSURE_UNKNOWN for
202
+ // the bad label — the result is the same: ok:false, pointer on v1.
203
+ if (fx.input.injectedRow) {
204
+ v1Rows.push({
205
+ sessionId: fx.input.injectedRow.sessionId,
206
+ label: fx.input.injectedRow.label,
207
+ effectiveSeq: fx.input.injectedRow.effectiveSeq,
208
+ ts: fx.input.injectedRow.ts,
209
+ });
210
+ }
211
+ const v2Rows: PressureV2Row[] = [];
212
+ let activeVersion = fx.input.activeVersion ?? 1;
213
+
214
+ const host: M7Host & { activeVersionAfter: number; readonly v2RowCount: number } = {
215
+ v1Rows: () => v1Rows,
216
+ existingV2: () => v2Rows,
217
+ putV2: (rows) => v2Rows.push(...rows),
218
+ activeVersion: () => activeVersion,
219
+ switchToV2: () => {
220
+ activeVersion = 2;
221
+ },
222
+ get activeVersionAfter() {
223
+ return activeVersion;
224
+ },
225
+ get v2RowCount() {
226
+ return v2Rows.length;
227
+ },
228
+ };
229
+ return host;
230
+ }
@@ -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,204 @@
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
+
19
+ import { createHash } from "node:crypto";
20
+
21
+ import { isPressureLevel } from "../controller/policy.js";
22
+ import type {
23
+ M7Host,
24
+ M7MigrationCode,
25
+ M7ValidateResult,
26
+ PressureV1Row,
27
+ PressureV2Row,
28
+ } from "./pressure-v2-types.js";
29
+ import { M7_FAIL, PRESSURE_LEGACY_VERSION } from "./pressure-v2-types.js";
30
+
31
+ /** Length-prefixed framing so the folded fields cannot alias. */
32
+ function field(value: string): string {
33
+ return `${Buffer.byteLength(value, "utf8")}:${value}`;
34
+ }
35
+
36
+ /** Row identity is (session, effectiveSeq) — one pressure per sequence point. */
37
+ const identity = (sessionId: string, effectiveSeq: number): string =>
38
+ `${sessionId}::${effectiveSeq}`;
39
+
40
+ /** The authoritative v2 digest: SHA-256 over the length-prefixed fields. */
41
+ export function derivePressureDigest(
42
+ sessionId: string,
43
+ level: string,
44
+ effectiveSeq: number,
45
+ ts: string,
46
+ ): string {
47
+ return createHash("sha256")
48
+ .update(
49
+ field("pressure-v2") +
50
+ field(sessionId) +
51
+ field(level) +
52
+ field(String(effectiveSeq)) +
53
+ field(ts),
54
+ "utf8",
55
+ )
56
+ .digest("hex");
57
+ }
58
+
59
+ /**
60
+ * Map ONE legacy row to its v2 form. ONLY the canonical five labels map;
61
+ * anything else throws `{ code: M7_PRESSURE_UNKNOWN }`. There is deliberately
62
+ * no fallback branch — a coerced label is a silently misclassified workload.
63
+ */
64
+ export function mapPressureRow(v1: PressureV1Row): PressureV2Row {
65
+ if (!isPressureLevel(v1.label)) {
66
+ throw { code: M7_FAIL.PRESSURE_UNKNOWN };
67
+ }
68
+ return {
69
+ sessionId: v1.sessionId,
70
+ level: v1.label,
71
+ effectiveSeq: v1.effectiveSeq,
72
+ ts: v1.ts,
73
+ digest: derivePressureDigest(
74
+ v1.sessionId,
75
+ v1.label,
76
+ v1.effectiveSeq,
77
+ v1.ts,
78
+ ),
79
+ };
80
+ }
81
+
82
+ /** True when every legacy row carries a canonical label. */
83
+ export function allLabelsCanonical(host: M7Host): boolean {
84
+ return host.v1Rows().every((r) => isPressureLevel(r.label));
85
+ }
86
+
87
+ /**
88
+ * COPY: resumable per (session, effectiveSeq). An interrupted run resumes
89
+ * without duplicate rows. An uncanonical label aborts the copy with
90
+ * M7_PRESSURE_UNKNOWN before anything is written.
91
+ */
92
+ export function m7Copy(host: M7Host): {
93
+ readonly written: readonly PressureV2Row[];
94
+ readonly all: readonly PressureV2Row[];
95
+ } {
96
+ const existing = new Map(
97
+ host.existingV2().map((r) => [identity(r.sessionId, r.effectiveSeq), r]),
98
+ );
99
+ const wanted: PressureV2Row[] = [];
100
+ for (const v1 of host.v1Rows()) {
101
+ // Throws M7_PRESSURE_UNKNOWN for a non-canonical label.
102
+ const fresh = mapPressureRow(v1);
103
+ const stored = existing.get(identity(v1.sessionId, v1.effectiveSeq));
104
+ if (stored !== undefined && stored.digest === fresh.digest) continue;
105
+ wanted.push(fresh);
106
+ }
107
+ if (wanted.length > 0) host.putV2(wanted);
108
+ return { written: wanted, all: [...host.existingV2()] };
109
+ }
110
+
111
+ function dedupe(codes: readonly M7MigrationCode[]): M7MigrationCode[] {
112
+ const out: M7MigrationCode[] = [];
113
+ for (const c of codes) if (!out.includes(c)) out.push(c);
114
+ return out;
115
+ }
116
+
117
+ /**
118
+ * VALIDATE: compare counts and digests between v1 and v2, and re-check every
119
+ * legacy label. Returns codes rather than throwing so a caller sees every
120
+ * problem at once.
121
+ */
122
+ export function m7Verify(host: M7Host): M7ValidateResult {
123
+ const codes: M7MigrationCode[] = [];
124
+ const v1Rows = host.v1Rows();
125
+ const stored = host.existingV2();
126
+
127
+ // Label check first: an uncanonical legacy label invalidates the whole run.
128
+ for (const v1 of v1Rows) {
129
+ if (!isPressureLevel(v1.label)) codes.push(M7_FAIL.PRESSURE_UNKNOWN);
130
+ }
131
+
132
+ const counts = new Map<string, number>();
133
+ for (const r of stored) {
134
+ const id = identity(r.sessionId, r.effectiveSeq);
135
+ counts.set(id, (counts.get(id) ?? 0) + 1);
136
+ }
137
+
138
+ const expected = new Set<string>();
139
+ for (const v1 of v1Rows) {
140
+ if (!isPressureLevel(v1.label)) continue;
141
+ const id = identity(v1.sessionId, v1.effectiveSeq);
142
+ expected.add(id);
143
+ const n = counts.get(id) ?? 0;
144
+ if (n === 0) codes.push(M7_FAIL.COPY_PARTIAL);
145
+ if (n > 1) codes.push(M7_FAIL.COUNT_MISMATCH);
146
+ }
147
+
148
+ // Every stored row must re-derive its own digest and correspond to a v1 row.
149
+ for (const r of stored) {
150
+ const fresh = derivePressureDigest(
151
+ r.sessionId,
152
+ r.level,
153
+ r.effectiveSeq,
154
+ r.ts,
155
+ );
156
+ if (r.digest !== fresh) codes.push(M7_FAIL.DIGEST_MISMATCH);
157
+ if (!expected.has(identity(r.sessionId, r.effectiveSeq))) {
158
+ codes.push(M7_FAIL.COUNT_MISMATCH);
159
+ }
160
+ }
161
+
162
+ return { ok: codes.length === 0, codes: dedupe(codes) };
163
+ }
164
+
165
+ /**
166
+ * SWITCH: atomically flip the active pointer to v2 — but ONLY after
167
+ * re-validating against freshly-read host state (see the module note on the
168
+ * post-copy failure injection). The pointer is left untouched on any failure.
169
+ */
170
+ export function m7Switch(host: M7Host): M7ValidateResult {
171
+ if (host.activeVersion() !== PRESSURE_LEGACY_VERSION) {
172
+ return { ok: false, codes: [M7_FAIL.NOT_ON_LEGACY] };
173
+ }
174
+ // Re-read the live rows: an unknown label injected after the copy is caught
175
+ // here, and the old pointer survives.
176
+ if (!allLabelsCanonical(host)) {
177
+ return { ok: false, codes: [M7_FAIL.PRESSURE_UNKNOWN] };
178
+ }
179
+ const verify = m7Verify(host);
180
+ if (!verify.ok) return verify;
181
+ host.switchToV2();
182
+ return { ok: true, codes: [] };
183
+ }
184
+
185
+ /**
186
+ * Full M7 migration: copy, then validate + switch. An uncanonical label thrown
187
+ * during the copy is converted to a result code so the caller sees the same
188
+ * shape as a validation failure, and the pointer is left on legacy.
189
+ */
190
+ export function migratePressureV2(host: M7Host): M7ValidateResult {
191
+ try {
192
+ m7Copy(host);
193
+ } catch (err) {
194
+ const code =
195
+ typeof err === "object" && err !== null && "code" in err
196
+ ? (err as { code: unknown }).code
197
+ : undefined;
198
+ if (code === M7_FAIL.PRESSURE_UNKNOWN) {
199
+ return { ok: false, codes: [M7_FAIL.PRESSURE_UNKNOWN] };
200
+ }
201
+ throw err;
202
+ }
203
+ return m7Switch(host);
204
+ }
@@ -0,0 +1,83 @@
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
+
9
+ /** The active-version value the v2 pointer is switched to. */
10
+ export const PRESSURE_V2_VERSION = 2;
11
+ /** The legacy (predecessor) active version. */
12
+ export const PRESSURE_LEGACY_VERSION = 1;
13
+
14
+ /** M7 failure codes. */
15
+ export const M7_FAIL = {
16
+ /** A legacy row carries a label outside the canonical five levels. */
17
+ PRESSURE_UNKNOWN: "M7_PRESSURE_UNKNOWN",
18
+ /** Copied row count does not match the legacy row count. */
19
+ COUNT_MISMATCH: "M7_COUNT_MISMATCH",
20
+ /** A v2 row digest does not re-derive from its own declared fields. */
21
+ DIGEST_MISMATCH: "M7_DIGEST_MISMATCH",
22
+ /** A legacy row has no corresponding v2 row (interrupted copy). */
23
+ COPY_PARTIAL: "M7_COPY_PARTIAL",
24
+ /** The active pointer is not on v1; switching would regress or no-op. */
25
+ NOT_ON_LEGACY: "M7_NOT_ON_LEGACY",
26
+ } as const;
27
+ export type M7MigrationCode = (typeof M7_FAIL)[keyof typeof M7_FAIL];
28
+
29
+ /**
30
+ * Registered M7 conformance IDs (M7-001..015). The acceptance test reads these
31
+ * rows from the v2 `adaptive-policy/` domain and asserts each returns its
32
+ * manifest bytes or exactly its listed failure code. Mirrors M5_IDS / M6_IDS.
33
+ */
34
+ export const M7_IDS: readonly string[] = Array.from(
35
+ { length: 15 },
36
+ (_v, i) => `M7-${String(i + 1).padStart(3, "0")}`,
37
+ );
38
+
39
+ /** Named M7 rows surfaced by the conformance corpus. */
40
+ export const M7_NAMED_IDS = ["M7-PRESSURE-003"] as const;
41
+
42
+ /**
43
+ * A predecessor (v1) pressure row. `label` is UNTRUSTED: the whole point of
44
+ * the migration is that a legacy store may hold labels outside the canonical
45
+ * five, and those must reject rather than be coerced.
46
+ */
47
+ export interface PressureV1Row {
48
+ readonly sessionId: string;
49
+ readonly label: string;
50
+ readonly effectiveSeq: number;
51
+ readonly ts: string;
52
+ }
53
+
54
+ /** A v2 pressure row: canonical level + a digest over its own fields. */
55
+ export interface PressureV2Row {
56
+ readonly sessionId: string;
57
+ /** One of the canonical five levels — validated, never coerced. */
58
+ readonly level: string;
59
+ readonly effectiveSeq: number;
60
+ readonly ts: string;
61
+ /** SHA-256 over the length-prefixed canonical fields. */
62
+ readonly digest: string;
63
+ }
64
+
65
+ /** The host the migration reads from / writes to (capability-shaped). */
66
+ export interface M7Host {
67
+ /** Every predecessor row being migrated. */
68
+ readonly v1Rows: () => readonly PressureV1Row[];
69
+ /** Persisted v2 rows already written (for resume). */
70
+ readonly existingV2: () => readonly PressureV2Row[];
71
+ /** Idempotent write of v2 rows (never mutates v1 rows). */
72
+ readonly putV2: (rows: readonly PressureV2Row[]) => void;
73
+ /** The currently ACTIVE version (1 = legacy; only v1 -> v2 switching). */
74
+ readonly activeVersion: () => number;
75
+ /** Atomically flip the active pointer to v2. */
76
+ readonly switchToV2: () => void;
77
+ }
78
+
79
+ /** An M7 validation result. */
80
+ export interface M7ValidateResult {
81
+ readonly ok: boolean;
82
+ readonly codes: readonly M7MigrationCode[];
83
+ }
@@ -0,0 +1,59 @@
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
+
36
+ export {
37
+ PRESSURE_V2_VERSION,
38
+ PRESSURE_LEGACY_VERSION,
39
+ M7_FAIL,
40
+ M7_IDS,
41
+ M7_NAMED_IDS,
42
+ } from "./pressure-v2-types.js";
43
+ export type {
44
+ M7MigrationCode,
45
+ PressureV1Row,
46
+ PressureV2Row,
47
+ M7Host,
48
+ M7ValidateResult,
49
+ } from "./pressure-v2-types.js";
50
+
51
+ export {
52
+ derivePressureDigest,
53
+ mapPressureRow,
54
+ allLabelsCanonical,
55
+ m7Copy,
56
+ m7Verify,
57
+ m7Switch,
58
+ migratePressureV2,
59
+ } from "./pressure-v2-ops.js";