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.
- package/dist/config/vector-cortex.js +14 -0
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/api-contracts/vector-cortex-policy.js +12 -0
- package/dist/extensions/dashboard-server/route-dispatch.js +6 -0
- package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-policy.js +45 -0
- package/dist/src/config/vector-cortex.js +14 -0
- package/dist/src/config.js +1 -1
- package/dist/src/vector-cortex/controller/policy-emit.js +56 -0
- package/dist/src/vector-cortex/migrations/pressure-v2-ops.js +166 -0
- package/dist/src/vector-cortex/migrations/pressure-v2-types.js +32 -0
- package/dist/src/vector-cortex/migrations/pressure-v2.js +36 -0
- package/dist/vector-cortex/controller/policy-emit.js +56 -0
- package/dist/vector-cortex/controller/policy.js +158 -0
- package/dist/vector-cortex/controller/shadow.js +117 -0
- package/dist/vector-cortex/controller/types.js +85 -0
- package/dist/vector-cortex/migrations/pressure-v2-ops.js +166 -0
- package/dist/vector-cortex/migrations/pressure-v2-types.js +32 -0
- package/dist/vector-cortex/migrations/pressure-v2.js +36 -0
- package/extensions/dashboard-client/dist/assets/{AreaChart-BOGp2sag.js → AreaChart-ujtGh2ez.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-BOGp2sag.js.map → AreaChart-ujtGh2ez.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-BHgA-QPL.js → BarChart-DNZqvtqh.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-BHgA-QPL.js.map → BarChart-DNZqvtqh.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-6E7lwM74.js → CacheTab-CeCIW1rD.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-6E7lwM74.js.map → CacheTab-CeCIW1rD.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-BEtNmFHz.js → EventsTab-ChohmqJI.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-BEtNmFHz.js.map → EventsTab-ChohmqJI.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-DGxZulRH.js → HealthTab-CKfAooiV.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-DGxZulRH.js.map → HealthTab-CKfAooiV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-C86jppQE.js → MaintenanceTab-D3DxHgM2.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-C86jppQE.js.map → MaintenanceTab-D3DxHgM2.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-J3VVxooZ.js → MemoryMapTab--c-0PBka.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-J3VVxooZ.js.map → MemoryMapTab--c-0PBka.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-yn9AFumo.js → MetricsTab-9UaEB69R.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-yn9AFumo.js.map → MetricsTab-9UaEB69R.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-BMarUiq3.js → OverviewTab-89zNl1qx.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-BMarUiq3.js.map → OverviewTab-89zNl1qx.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DxnzMive.js → ReposTab-CkfNrdxF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DxnzMive.js.map → ReposTab-CkfNrdxF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DXKpDUuC.js → SessionsTab-DA7wqI05.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DXKpDUuC.js.map → SessionsTab-DA7wqI05.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-BctuV-CJ.js → SetupTab-pG4xMI3P.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-BctuV-CJ.js.map → SetupTab-pG4xMI3P.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-D8yOC96S.js → TimeSavedCard-DeQnzSkb.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-D8yOC96S.js.map → TimeSavedCard-DeQnzSkb.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-BBxqGYH-.js → TurnsTab-B6ifHMbg.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-BBxqGYH-.js.map → TurnsTab-B6ifHMbg.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DSz16qtM.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DSz16qtM.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-DdJQSagO.js → WikiTab-awMsQNBY.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-DdJQSagO.js.map → WikiTab-awMsQNBY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-B4Q9h_8Y.js → button-DCDwPy_j.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-B4Q9h_8Y.js.map → button-DCDwPy_j.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-WyR31hUV.js → card-D3KAYtck.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-WyR31hUV.js.map → card-D3KAYtck.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-pTio6Bq3.js → generateCategoricalChart-D_FHNog-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-pTio6Bq3.js.map → generateCategoricalChart-D_FHNog-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-BoLUiNsd.js → index-4MkLsevY.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-BoLUiNsd.js.map → index-4MkLsevY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/index-Difwoltx.css +1 -0
- package/extensions/dashboard-client/dist/assets/{switch-5dieXxoo.js → switch-Cc05xGtg.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-5dieXxoo.js.map → switch-Cc05xGtg.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-BFc-7_LL.js → toggle--QUzWk-Y.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-BFc-7_LL.js.map → toggle--QUzWk-Y.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-BqmKNBAI.js → useSSE-DcAeX67m.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-BqmKNBAI.js.map → useSSE-DcAeX67m.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexPolicyCard.tsx +43 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +2 -0
- package/extensions/dashboard-client/src/tabs/useVectorCortexPoll.ts +5 -0
- package/extensions/dashboard-client/src/types/vector-cortex-vc8.ts +17 -0
- package/extensions/dashboard-client/src/types/vector-cortex.ts +1 -1
- package/extensions/dashboard-server/api-contracts/vector-cortex-policy.ts +36 -0
- package/extensions/dashboard-server/route-dispatch.ts +6 -0
- package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
- package/extensions/dashboard-server/routes-vector-cortex-policy.ts +55 -0
- package/package.json +1 -1
- package/src/config/vector-cortex.ts +15 -0
- package/src/config.ts +1 -0
- package/src/vector-cortex/controller/policy-emit.ts +82 -0
- package/src/vector-cortex/migrations/pressure-v2-ops.ts +204 -0
- package/src/vector-cortex/migrations/pressure-v2-types.ts +83 -0
- package/src/vector-cortex/migrations/pressure-v2.ts +59 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-BTF1AGCJ.js +0 -2
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-BTF1AGCJ.js.map +0 -1
- 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
|
+
}
|
|
@@ -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.
|
|
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
|
@@ -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
|
+
}
|