pi-mega-compact 0.20.4 → 0.20.6
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 +24 -0
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +2 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-render.js +40 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-rollout.js +43 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex.js +2 -0
- package/dist/extensions/dashboard-server/routes.js +1 -1
- package/dist/extensions/dashboard-server/server.js +5 -1
- package/dist/extensions/mega-context/vector-cortex.js +128 -0
- package/dist/extensions/mega-runtime/vector-cortex-live.js +105 -0
- package/dist/src/config/vector-cortex.js +24 -0
- package/dist/src/config.js +1 -1
- package/dist/src/store/sqlite/global-sessions.js +16 -4
- package/dist/src/vector-cortex/provider/registry.js +112 -0
- package/dist/src/vector-cortex/provider/types.js +30 -0
- package/dist/src/vector-cortex/render/_acceptance-fixture.js +58 -0
- package/dist/src/vector-cortex/render/_acceptance-helpers.js +17 -0
- package/dist/src/vector-cortex/render/_acceptance-provider.js +29 -0
- package/dist/src/vector-cortex/render/_acceptance-scenario.js +259 -0
- package/dist/src/vector-cortex/render/renderer.js +119 -0
- package/dist/src/vector-cortex/render/types.js +29 -0
- package/dist/src/vector-cortex/render/validator.js +90 -0
- package/dist/src/vector-cortex/rollout/_acceptance-fixture.js +55 -0
- package/dist/src/vector-cortex/rollout/_acceptance-helpers.js +8 -0
- package/dist/src/vector-cortex/rollout/_acceptance-scenario.js +70 -0
- package/dist/src/vector-cortex/rollout/assign.js +56 -0
- package/dist/src/vector-cortex/rollout/emit.js +50 -0
- package/dist/src/vector-cortex/rollout/gate.js +75 -0
- package/dist/src/vector-cortex/rollout/types.js +39 -0
- package/dist/vector-cortex/provider/registry.js +112 -0
- package/dist/vector-cortex/provider/types.js +30 -0
- package/dist/vector-cortex/render/_acceptance-fixture.js +58 -0
- package/dist/vector-cortex/render/_acceptance-helpers.js +17 -0
- package/dist/vector-cortex/render/_acceptance-provider.js +29 -0
- package/dist/vector-cortex/render/_acceptance-scenario.js +259 -0
- package/dist/vector-cortex/render/renderer.js +119 -0
- package/dist/vector-cortex/render/types.js +29 -0
- package/dist/vector-cortex/render/validator.js +90 -0
- package/dist/vector-cortex/rollout/_acceptance-fixture.js +55 -0
- package/dist/vector-cortex/rollout/_acceptance-helpers.js +8 -0
- package/dist/vector-cortex/rollout/_acceptance-scenario.js +70 -0
- package/dist/vector-cortex/rollout/assign.js +56 -0
- package/dist/vector-cortex/rollout/emit.js +50 -0
- package/dist/vector-cortex/rollout/gate.js +75 -0
- package/dist/vector-cortex/rollout/types.js +39 -0
- package/extensions/dashboard-client/dist/assets/{AreaChart-Cam9-nhT.js → AreaChart-DqTbaSRA.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-Cam9-nhT.js.map → AreaChart-DqTbaSRA.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-DzIY8aM1.js → BarChart-BfNBeOvF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-DzIY8aM1.js.map → BarChart-BfNBeOvF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-CKIiYsG_.js → CacheTab-C9SOMYB5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-CKIiYsG_.js.map → CacheTab-C9SOMYB5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-xaaBJLiK.js → EventsTab-GsMKP10_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-xaaBJLiK.js.map → EventsTab-GsMKP10_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-BGtUKOqG.js → HealthTab-DAOCXwRP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-BGtUKOqG.js.map → HealthTab-DAOCXwRP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-ABgQnTZ9.js → MaintenanceTab-BtBeMkB5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-ABgQnTZ9.js.map → MaintenanceTab-BtBeMkB5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-ChR2GAN-.js → MemoryMapTab-DzndtgkZ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-ChR2GAN-.js.map → MemoryMapTab-DzndtgkZ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DdCLAgcR.js → MetricsTab-DyxJ4eb8.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DdCLAgcR.js.map → MetricsTab-DyxJ4eb8.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DtemzJt6.js → OverviewTab-DCiEdVWR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DtemzJt6.js.map → OverviewTab-DCiEdVWR.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-B9mvD9Ep.js → ReposTab-B5SIqVHA.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-B9mvD9Ep.js.map → ReposTab-B5SIqVHA.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BcO38XLS.js → SessionsTab-DLWFJ9xI.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BcO38XLS.js.map → SessionsTab-DLWFJ9xI.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-D_TBNP09.js → SetupTab-CvGXZyej.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-D_TBNP09.js.map → SetupTab-CvGXZyej.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-TjHpbUOo.js → TimeSavedCard-wZuQGxLS.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-TjHpbUOo.js.map → TimeSavedCard-wZuQGxLS.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-DprxndCF.js → TurnsTab-B7aOejch.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-DprxndCF.js.map → TurnsTab-B7aOejch.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-eHVt9yDx.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-eHVt9yDx.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-OKgSo15G.js → WikiTab-DhWHjAtt.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-OKgSo15G.js.map → WikiTab-DhWHjAtt.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-D_Qz4q7L.js → button-BrC-W87h.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-D_Qz4q7L.js.map → button-BrC-W87h.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-CsI6Jm3p.js → card-0rgfRgr3.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-CsI6Jm3p.js.map → card-0rgfRgr3.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-CvHKUdhX.js → generateCategoricalChart-DRresOQF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-CvHKUdhX.js.map → generateCategoricalChart-DRresOQF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-0Ye_un22.js → index-DohmtZWL.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-0Ye_un22.js.map → index-DohmtZWL.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{switch-VQEoJl8Q.js → switch-Bs8g_zB5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-VQEoJl8Q.js.map → switch-Bs8g_zB5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-CAPm4HJo.js → toggle-DVVauIo8.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-CAPm4HJo.js.map → toggle-DVVauIo8.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-pxsNTVkh.js → useSSE-DxH1PTAR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-pxsNTVkh.js.map → useSSE-DxH1PTAR.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/vector-cortex.ts +18 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexMetric.tsx +10 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexPlansCard.tsx +39 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexRenderCard.tsx +38 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexRolloutCard.tsx +44 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +19 -38
- package/extensions/dashboard-client/src/types/vector-cortex.ts +26 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex.ts +52 -1
- package/extensions/dashboard-server/routes-rag-settings-helpers.ts +12 -0
- package/extensions/dashboard-server/routes-vector-cortex-render.ts +49 -0
- package/extensions/dashboard-server/routes-vector-cortex-rollout.ts +52 -0
- package/extensions/dashboard-server/routes-vector-cortex.ts +2 -0
- package/extensions/dashboard-server/routes.ts +2 -0
- package/extensions/dashboard-server/server.ts +4 -0
- package/extensions/mega-context/vector-cortex.ts +163 -0
- package/extensions/mega-runtime/vector-cortex-live.ts +165 -0
- package/package.json +1 -1
- package/src/config/vector-cortex.ts +26 -0
- package/src/config.ts +2 -0
- package/src/store/sqlite/global-sessions.ts +14 -2
- package/src/vector-cortex/provider/registry.ts +137 -0
- package/src/vector-cortex/provider/types.ts +163 -0
- package/src/vector-cortex/render/_acceptance-fixture.ts +128 -0
- package/src/vector-cortex/render/_acceptance-helpers.ts +39 -0
- package/src/vector-cortex/render/_acceptance-provider.ts +45 -0
- package/src/vector-cortex/render/_acceptance-scenario.ts +319 -0
- package/src/vector-cortex/render/renderer.ts +142 -0
- package/src/vector-cortex/render/types.ts +174 -0
- package/src/vector-cortex/render/validator.ts +120 -0
- package/src/vector-cortex/rollout/_acceptance-fixture.ts +94 -0
- package/src/vector-cortex/rollout/_acceptance-helpers.ts +24 -0
- package/src/vector-cortex/rollout/_acceptance-scenario.ts +96 -0
- package/src/vector-cortex/rollout/assign.ts +67 -0
- package/src/vector-cortex/rollout/emit.ts +81 -0
- package/src/vector-cortex/rollout/gate.ts +119 -0
- package/src/vector-cortex/rollout/types.ts +111 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-Bym21yVQ.js +0 -2
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-Bym21yVQ.js.map +0 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rollout/_acceptance-scenario.ts — the REAL rollout runner for VC5C acceptance
|
|
3
|
+
* rows. Drives `assignSession` + `decideGate` (no mocks) against the fixture
|
|
4
|
+
* `input` and returns the result fields the fixture's `expected` block pins.
|
|
5
|
+
*
|
|
6
|
+
* Fake clocks: the gate scenario accepts an injected monotonic clock so the
|
|
7
|
+
* spec's unique failure injection (restart at 71h59m with a wall-clock jump +1d
|
|
8
|
+
* but monotonic unchanged) is reproducible. The wall-clock jump is irrelevant to
|
|
9
|
+
* the safety decision because eligibility uses the MONOTONIC delta only.
|
|
10
|
+
*/
|
|
11
|
+
import { assignSession } from "./assign.js";
|
|
12
|
+
import { decideGate, } from "./gate.js";
|
|
13
|
+
const GATE_MIN_ELAPSED_MS = 72 * 60 * 60 * 1000;
|
|
14
|
+
/** Fixed monotonic clock: elapsed = now - windowStart (the only driver). */
|
|
15
|
+
function clockAt(elapsedMs, windowStartMs) {
|
|
16
|
+
return {
|
|
17
|
+
now: () => windowStartMs + elapsedMs, // monotonic delta is the truth
|
|
18
|
+
wallNow: () => new Date(1_700_000_000_000 + elapsedMs).toISOString(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Run a rollout fixture through the REAL assign + gate pipeline. */
|
|
22
|
+
export function runRolloutScenario(fx) {
|
|
23
|
+
const scenario = fx.input.scenario;
|
|
24
|
+
if (scenario === "assign-stable") {
|
|
25
|
+
const a = assignSession(fx.input.sessionId ?? "");
|
|
26
|
+
const b = assignSession(fx.input.sessionId ?? "");
|
|
27
|
+
// Determinism: recomputing yields the identical bucket.
|
|
28
|
+
if (a.bucket !== b.bucket) {
|
|
29
|
+
return { ok: false };
|
|
30
|
+
}
|
|
31
|
+
return { ok: true, bucket: a.bucket };
|
|
32
|
+
}
|
|
33
|
+
if (scenario === "gate-power" || scenario === "gate-safety") {
|
|
34
|
+
const ev = fx.input.evidence ?? {};
|
|
35
|
+
const windowStartMs = ev.windowStartMs ?? 0;
|
|
36
|
+
const hardFaults = (ev.hardFaults ?? []).map((f) => ({ kind: f.kind, detail: f.detail }));
|
|
37
|
+
const evidence = {
|
|
38
|
+
windowStartMs,
|
|
39
|
+
powered: ev.powered ?? false,
|
|
40
|
+
events: ev.events ?? 0,
|
|
41
|
+
sessions: ev.sessions ?? 0,
|
|
42
|
+
hardFaults,
|
|
43
|
+
};
|
|
44
|
+
// The spec's unique failure injection: wall-clock jump is irrelevant; only the
|
|
45
|
+
// monotonic elapsed drives eligibility. For the 71h59m row we pass 71h59m; for
|
|
46
|
+
// the wall-jump row we pass the SAME monotonic elapsed (so it must stay blocked);
|
|
47
|
+
// for the >=72h row we pass a full 72h+ monotonic delta (so it advances).
|
|
48
|
+
const start = fx._monotonicElapsedMs;
|
|
49
|
+
const elapsedMs = start ?? GATE_MIN_ELAPSED_MS; // default: a full 72h window
|
|
50
|
+
const clock = clockAt(elapsedMs, windowStartMs);
|
|
51
|
+
// gate-safety with a hard fault forces pre-VC + freezes promotion regardless of
|
|
52
|
+
// the monotonic window; the gate index stays at the input's prior gate (0 here).
|
|
53
|
+
if (scenario === "gate-safety") {
|
|
54
|
+
const outcome = decideGate(0, evidence, clock);
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
gateIndex: outcome.gateIndex,
|
|
58
|
+
promotionBlocked: outcome.promotionBlocked,
|
|
59
|
+
selectsPreVc: outcome.promotionBlocked,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const outcome = decideGate(0, evidence, clock);
|
|
63
|
+
return {
|
|
64
|
+
ok: true,
|
|
65
|
+
gateIndex: outcome.gateIndex,
|
|
66
|
+
promotionBlocked: outcome.promotionBlocked,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`unknown rollout scenario: ${scenario}`);
|
|
70
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/rollout/assign.ts — deterministic stable-bucket assignment.
|
|
3
|
+
*
|
|
4
|
+
* `assignSession(sessionId)` hashes a session id into one of 10,000 stable
|
|
5
|
+
* buckets (0..9999). The assignment is a PURE function of the session id and
|
|
6
|
+
* NEVER changes across process restart: it uses a fixed-seed FNV-1a over the
|
|
7
|
+
* session id bytes, never Date.now / Math.random (PREVENT-PI-004-adjacent
|
|
8
|
+
* determinism contract; the spec's restart-invariance requirement).
|
|
9
|
+
*
|
|
10
|
+
* A bucket `b` is "in" gate `g` (one of [1,5,25,50,100]) iff
|
|
11
|
+
* `b < g * 100`. So gate 1% covers buckets 0..99, gate 100% covers 0..9999.
|
|
12
|
+
*
|
|
13
|
+
* Pure: no I/O, no clock, no network. PREVENT-011: no `any`.
|
|
14
|
+
*/
|
|
15
|
+
import { ROLLOUT_BUCKETS, ROLLOUT_GATES, } from "./types.js";
|
|
16
|
+
/**
|
|
17
|
+
* Deterministic 32-bit FNV-1a over the session id (UTF-8 bytes). Fixed seed
|
|
18
|
+
* (2166136261) — restart-invariant by construction. Returns a bucket in
|
|
19
|
+
* 0..(ROLLOUT_BUCKETS-1).
|
|
20
|
+
*/
|
|
21
|
+
export function assignSession(sessionId) {
|
|
22
|
+
const enc = new TextEncoder();
|
|
23
|
+
const bytes = enc.encode(sessionId);
|
|
24
|
+
let hash = 0x811c9dc5; // FNV offset basis (32-bit)
|
|
25
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
26
|
+
hash ^= bytes[i];
|
|
27
|
+
// hash *= 16777619 (FNV prime) with 32-bit overflow wrap.
|
|
28
|
+
hash = Math.imul(hash, 0x01000193);
|
|
29
|
+
}
|
|
30
|
+
const bucket = (hash >>> 0) % ROLLOUT_BUCKETS;
|
|
31
|
+
// The bucket's qualifying gate is the HIGHEST gate whose bound it falls under.
|
|
32
|
+
let gateIndex = 0;
|
|
33
|
+
for (let i = 0; i < ROLLOUT_GATES.length; i++) {
|
|
34
|
+
if (bucket < ROLLOUT_GATES[i] * 100) {
|
|
35
|
+
gateIndex = i;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
schema: "rollout-assignment-v1",
|
|
41
|
+
sessionId,
|
|
42
|
+
bucket,
|
|
43
|
+
gateIndex,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Whether a bucket qualifies for a given gate percentage. Used by the live seam
|
|
48
|
+
* to decide whether a session's bucket is currently exposed to the VC path.
|
|
49
|
+
*/
|
|
50
|
+
export function bucketInGate(bucket, gatePct) {
|
|
51
|
+
return bucket < gatePct * 100;
|
|
52
|
+
}
|
|
53
|
+
/** Current gate percentage for a gate index. */
|
|
54
|
+
export function gatePctForIndex(gateIndex) {
|
|
55
|
+
return ROLLOUT_GATES[gateIndex];
|
|
56
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/rollout/emit.ts — VC5C rollout emit seam.
|
|
3
|
+
*
|
|
4
|
+
* The structured-event surface for the live graduated rollout: assignment and
|
|
5
|
+
* promotion-blocked observability. Mirrors the VC0C resilience emit seam and
|
|
6
|
+
* the VC0B replay emit seam: the same injected `(event, fields)` callback, the
|
|
7
|
+
* same non-fatal `safe()` best-effort wrapper, and the same per-call
|
|
8
|
+
* `VC5C_ENABLED()` gate. Absent emitter (or flag OFF) degrades to a no-op with
|
|
9
|
+
* byte-identical predecessor behavior (PREVENT-PI-004, structured `ts`+`event`).
|
|
10
|
+
*
|
|
11
|
+
* Carries exactly the two events the sprint names:
|
|
12
|
+
* - vector_cortex_rollout_assigned (a session was assigned a bucket)
|
|
13
|
+
* - vector_cortex_rollout_promotion_blocked (a hard fault froze promotion)
|
|
14
|
+
*/
|
|
15
|
+
import { VC5C_ENABLED } from "../../config/vector-cortex.js";
|
|
16
|
+
function safe(fn) {
|
|
17
|
+
return (event, fields) => {
|
|
18
|
+
try {
|
|
19
|
+
fn(event, fields);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
/* non-fatal observability — never break the agent loop */
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build a typed reporter over an injected emit callback. Emission is gated on
|
|
28
|
+
* `VC5C_ENABLED()` — flag OFF (or absent emitter) means zero rollout
|
|
29
|
+
* observability writes (pre-VC predecessor parity). The flag is read per-call so
|
|
30
|
+
* a live toggle takes effect immediately.
|
|
31
|
+
*/
|
|
32
|
+
export function createRolloutReporter(emit) {
|
|
33
|
+
const fire = safe((event, fields) => {
|
|
34
|
+
if (VC5C_ENABLED())
|
|
35
|
+
emit?.(event, fields);
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
assigned(fields) {
|
|
39
|
+
fire("vector_cortex_rollout_assigned", fields);
|
|
40
|
+
},
|
|
41
|
+
promotionBlocked(fields) {
|
|
42
|
+
fire("vector_cortex_rollout_promotion_blocked", fields);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Export the event names for consumers that match on them. */
|
|
47
|
+
export const ROLLOUT_EVENT_NAMES = [
|
|
48
|
+
"vector_cortex_rollout_assigned",
|
|
49
|
+
"vector_cortex_rollout_promotion_blocked",
|
|
50
|
+
];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/rollout/gate.ts — monotonic graduated-gate advance decision.
|
|
3
|
+
*
|
|
4
|
+
* A gate advances exactly ONE step only when ALL conjuncts hold:
|
|
5
|
+
* (elapsedWallClock >= 72h) — measured on the MONOTONIC clock, not wall time
|
|
6
|
+
* AND (powered sample available)
|
|
7
|
+
* AND (events >= 10000)
|
|
8
|
+
* AND (sessions >= 200)
|
|
9
|
+
*
|
|
10
|
+
* Promotion is strictly monotonic by one gate step. The unique failure
|
|
11
|
+
* injection (TRIAD_RESILIENCE.md): a wall-clock jump while the monotonic clock
|
|
12
|
+
* is unchanged does NOT advance — because the 72h eligibility is the MONOTONIC
|
|
13
|
+
* delta, not wall time. We therefore take an injected clock seam with SEPARATE
|
|
14
|
+
* monotonic and wall readings and never call Date.now() directly for the safety
|
|
15
|
+
* decision (PREVENT decorum: the host injects a testable fake clock).
|
|
16
|
+
*
|
|
17
|
+
* Pure arithmetic over injected evidence + clock; no I/O, no network.
|
|
18
|
+
* PREVENT-011: no `any`.
|
|
19
|
+
*/
|
|
20
|
+
import { ROLLOUT_GATES, } from "./types.js";
|
|
21
|
+
/** The 72h monotonic residency requirement (milliseconds). */
|
|
22
|
+
export const GATE_MIN_ELAPSED_MS = 72 * 60 * 60 * 1000;
|
|
23
|
+
/** Powered-sample availability gate (minimum events for a one-sided CI read). */
|
|
24
|
+
export const GATE_MIN_EVENTS = 10_000;
|
|
25
|
+
/** Minimum distinct sessions before a gate may advance. */
|
|
26
|
+
export const GATE_MIN_SESSIONS = 200;
|
|
27
|
+
/** Default clock: real monotonic (performance.now) + wall (ISO). */
|
|
28
|
+
export function defaultClock() {
|
|
29
|
+
return {
|
|
30
|
+
now: () => performance.now(),
|
|
31
|
+
wallNow: () => new Date().toISOString(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Decide the next live outcome for a decision epoch. Returns the (possibly
|
|
36
|
+
* advanced) gate index + the full evidence snapshot. Promotion monotonic by one
|
|
37
|
+
* step; a hard fault freezes promotion (promotionBlocked=true) and is reported
|
|
38
|
+
* for C-selection by the live seam.
|
|
39
|
+
*
|
|
40
|
+
* @param currentGate the gate index BEFORE this decision (0..4).
|
|
41
|
+
* @param evidence the observed window evidence.
|
|
42
|
+
* @param clock the injected monotonic/wall clock seam.
|
|
43
|
+
*/
|
|
44
|
+
export function decideGate(currentGate, evidence, clock) {
|
|
45
|
+
const elapsedMs = Math.max(0, clock.now() - evidence.windowStartMs);
|
|
46
|
+
const hardFault = evidence.hardFaults.length > 0;
|
|
47
|
+
const eligible = !hardFault &&
|
|
48
|
+
currentGate < (ROLLOUT_GATES.length - 1) &&
|
|
49
|
+
elapsedMs >= GATE_MIN_ELAPSED_MS &&
|
|
50
|
+
evidence.powered &&
|
|
51
|
+
evidence.events >= GATE_MIN_EVENTS &&
|
|
52
|
+
evidence.sessions >= GATE_MIN_SESSIONS;
|
|
53
|
+
// Monotonic by ONE step only.
|
|
54
|
+
const nextGate = eligible
|
|
55
|
+
? (currentGate + 1)
|
|
56
|
+
: currentGate;
|
|
57
|
+
return {
|
|
58
|
+
schema: "live-outcome-v1",
|
|
59
|
+
gateIndex: nextGate,
|
|
60
|
+
gatePct: ROLLOUT_GATES[nextGate],
|
|
61
|
+
elapsedMs,
|
|
62
|
+
powered: evidence.powered,
|
|
63
|
+
events: evidence.events,
|
|
64
|
+
sessions: evidence.sessions,
|
|
65
|
+
promotionBlocked: hardFault,
|
|
66
|
+
decidedAt: clock.wallNow(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Whether a hard fault should SELECT triad C (pre-VC path). Any hard causal/
|
|
71
|
+
* tool/anchor/exact failure does. Exposed so the live seam can branch cleanly.
|
|
72
|
+
*/
|
|
73
|
+
export function selectsPreVcPath(fault) {
|
|
74
|
+
return fault !== undefined;
|
|
75
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/rollout/types.ts — VC5C live graduated-rollout contracts.
|
|
3
|
+
*
|
|
4
|
+
* A session is deterministically hashed into one of 10,000 stable buckets
|
|
5
|
+
* (0..9999). The current rollout gate is one of 1% / 5% / 25% / 50% / 100%.
|
|
6
|
+
* A bucket is "in" the current gate iff `bucket < gatePct * 100`. Assignment is
|
|
7
|
+
* a PURE function of the session id: it NEVER changes across process restart
|
|
8
|
+
* (no Date.now / Math.random — see `assign.ts`).
|
|
9
|
+
*
|
|
10
|
+
* Pure types + registered conformance IDs: no storage, no console, no network
|
|
11
|
+
* (PREVENT-PI-004 / PREVENT-011).
|
|
12
|
+
*/
|
|
13
|
+
/** Total stable buckets. Reassignment on restart is impossible by construction. */
|
|
14
|
+
export const ROLLOUT_BUCKETS = 10_000;
|
|
15
|
+
/**
|
|
16
|
+
* The five graduated gates, in promotion order. Index = step. Promotion is
|
|
17
|
+
* strictly monotonic by ONE step (never skipping a gate).
|
|
18
|
+
*/
|
|
19
|
+
export const ROLLOUT_GATES = [1, 5, 25, 50, 100];
|
|
20
|
+
/**
|
|
21
|
+
* The two conformance events VC5C emits to the monitoring/events path. Mirrors
|
|
22
|
+
* how prior sprints emit (resilience/emit.ts, render/emit.ts).
|
|
23
|
+
*/
|
|
24
|
+
export const ROLLOUT_EVENTS = [
|
|
25
|
+
"vector_cortex_rollout_assigned",
|
|
26
|
+
"vector_cortex_rollout_promotion_blocked",
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Registered rollout conformance ID range (ROL-001..020). The acceptance
|
|
30
|
+
* aggregator reads these rows from the v2 manifest and asserts each returns its
|
|
31
|
+
* manifest `ok`/`code`.
|
|
32
|
+
*/
|
|
33
|
+
export const ROL_IDS = Array.from({ length: 20 }, (_v, i) => `ROL-${String(i + 1).padStart(3, "0")}`);
|
|
34
|
+
/** Named VC5C rollout conformance assertions (the sprint's headline rows). */
|
|
35
|
+
export const ROL_NAMED_IDS = [
|
|
36
|
+
"ROL-BUCKET-001",
|
|
37
|
+
"ROL-POWER-002",
|
|
38
|
+
"ROL-SAFETY-003",
|
|
39
|
+
];
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/provider/registry.ts — fixture-backed base provider profile
|
|
3
|
+
* registry (VC5B, task 2).
|
|
4
|
+
*
|
|
5
|
+
* The registry resolves a `(provider, model)` pair to a `ProviderProfileBundle`
|
|
6
|
+
* (CONTRACTS §ProviderProfileV1 + role/tool/cache rules). Unknown provider/model
|
|
7
|
+
* returns `PRO_PROFILE_UNKNOWN` and the renderer CLEANLY bypasses vector-cortex
|
|
8
|
+
* rendering (NOT an error — the host uses the predecessor prompt path).
|
|
9
|
+
*
|
|
10
|
+
* Profiles are fixture-backed: each base profile is declared here as a constant
|
|
11
|
+
* and is also pinned as a conformance fixture under `conformance/vector-cortex/v2/
|
|
12
|
+
* provider/` (registered by `scripts/gen-fixtures/provider.mjs`). The registry is
|
|
13
|
+
* the REAL resolver the acceptance suite drives; the fixtures are the committed
|
|
14
|
+
* canonical corpus that proves each profile's cache-identity behavior.
|
|
15
|
+
*
|
|
16
|
+
* Pi-agnostic, dependency-free, zero network (PREVENT-PI-004 / PREVENT-011).
|
|
17
|
+
*/
|
|
18
|
+
import { PRO_PROFILE_UNKNOWN, } from "./types.js";
|
|
19
|
+
/** A registry key is `provider` + `model`. Lookup is exact-match. */
|
|
20
|
+
function key(provider, model) {
|
|
21
|
+
return `${provider}//${model}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The base role rule every VC5B profile shares: the renderer MUST use the host
|
|
25
|
+
* `before_agent_start` prepend seam and MUST NOT inject `role:"system"`
|
|
26
|
+
* (PREVENT-PI-003, critical).
|
|
27
|
+
*/
|
|
28
|
+
const BASE_ROLE = {
|
|
29
|
+
useHostPrependSeam: true,
|
|
30
|
+
forbidSystemRoleInjection: true,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The base tool rule every VC5B profile shares: exact tool bytes preserved
|
|
34
|
+
* (PREVENT-PI-002) and the renderer never reorders away from the validator order.
|
|
35
|
+
*/
|
|
36
|
+
const BASE_TOOL = {
|
|
37
|
+
preserveExactToolBytes: true,
|
|
38
|
+
forbidToolReorder: true,
|
|
39
|
+
};
|
|
40
|
+
/** Build a base cache rule (entire-canonical-request hashing by default). */
|
|
41
|
+
function baseCache(stableFields) {
|
|
42
|
+
return {
|
|
43
|
+
hashMode: "entire-canonical-request",
|
|
44
|
+
stableFields,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Build a contract-profile shape pinned as a conformance fixture too. */
|
|
48
|
+
function profile(id, version, excludedJsonPointers) {
|
|
49
|
+
return {
|
|
50
|
+
schema: "provider-profile-v1",
|
|
51
|
+
id,
|
|
52
|
+
version,
|
|
53
|
+
hashMode: "entire-canonical-request",
|
|
54
|
+
excludedJsonPointers,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The fixture-backed base profiles. Each entry is the REAL bundle the renderer
|
|
59
|
+
* resolves; the parallel conformance fixtures prove the cache-identity behavior.
|
|
60
|
+
*/
|
|
61
|
+
const BASE_PROFILES = [
|
|
62
|
+
{
|
|
63
|
+
profile: profile("anthropic-claude-opus", "v1", []),
|
|
64
|
+
role: BASE_ROLE,
|
|
65
|
+
tool: BASE_TOOL,
|
|
66
|
+
cache: baseCache(["systemPromptPrepend", "tools", "nodes"]),
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
profile: profile("anthropic-claude-sonnet", "v1", []),
|
|
70
|
+
role: BASE_ROLE,
|
|
71
|
+
tool: BASE_TOOL,
|
|
72
|
+
cache: baseCache(["systemPromptPrepend", "tools", "nodes"]),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
profile: profile("openai-gpt", "v1", []),
|
|
76
|
+
role: BASE_ROLE,
|
|
77
|
+
tool: BASE_TOOL,
|
|
78
|
+
cache: baseCache(["systemPromptPrepend", "tools", "nodes"]),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
// A profile that proves a fixture-excluded pointer: a provider whose
|
|
82
|
+
// request-id header cannot affect cache identity. Excluded + versioned.
|
|
83
|
+
profile: profile("google-gemini", "v1", [
|
|
84
|
+
{
|
|
85
|
+
pointer: "/requestId",
|
|
86
|
+
fixtureId: "PRO-EXCLUDE-010",
|
|
87
|
+
proofDigest: "sha256:excluded-request-id-proof",
|
|
88
|
+
},
|
|
89
|
+
]),
|
|
90
|
+
role: BASE_ROLE,
|
|
91
|
+
tool: BASE_TOOL,
|
|
92
|
+
cache: baseCache(["systemPromptPrepend", "tools", "nodes"]),
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
const REGISTRY = new Map(BASE_PROFILES.map((b) => [key(b.profile.id, b.profile.version), b]));
|
|
96
|
+
/**
|
|
97
|
+
* Resolve a `(provider, model)` pair to a `ProviderProfileBundle`. Unknown
|
|
98
|
+
* provider / model / version → a CLEAN bypass (`PRO_PROFILE_UNKNOWN`), never an
|
|
99
|
+
* error. The renderer consults this and, on `ok:false`, selects triad C (the
|
|
100
|
+
* predecessor prompt path).
|
|
101
|
+
*/
|
|
102
|
+
export function resolveProviderProfile(provider, model) {
|
|
103
|
+
const bundle = REGISTRY.get(key(provider, model));
|
|
104
|
+
if (bundle === undefined) {
|
|
105
|
+
return { ok: false, code: PRO_PROFILE_UNKNOWN };
|
|
106
|
+
}
|
|
107
|
+
return { ok: true, bundle };
|
|
108
|
+
}
|
|
109
|
+
/** The fixture-backed base profiles (exposed for tests / dashboard introspection). */
|
|
110
|
+
export const BASE_PROVIDER_PROFILES = BASE_PROFILES;
|
|
111
|
+
/** The set of known `(provider, model)` keys, for conformance registration. */
|
|
112
|
+
export const KNOWN_PROVIDER_KEYS = Array.from(REGISTRY.keys());
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/provider/types.ts — provider profile contract (VC5B, task 1).
|
|
3
|
+
*
|
|
4
|
+
* A `ProviderProfileV1` describes how a given provider/model caches and what the
|
|
5
|
+
* renderer may or may not fold into the outbound prompt without breaking the
|
|
6
|
+
* provider's KV-cache identity (CONTRACTS §ProviderProfileV1 + crystals).
|
|
7
|
+
*
|
|
8
|
+
* The renderer hashes the ENTIRE canonical outbound request by default
|
|
9
|
+
* (`hashMode: "entire-canonical-request"`). A profile may EXCLUDE a field only
|
|
10
|
+
* when a fixture proves that field cannot affect provider cache identity; every
|
|
11
|
+
* exclusion is versioned and listed in the manifest. Unknown provider / profile /
|
|
12
|
+
* version → the renderer bypasses vector-cortex rendering (a clean bypass, NOT an
|
|
13
|
+
* error) and the host falls back to the predecessor prompt path.
|
|
14
|
+
*
|
|
15
|
+
* Pure types + registered conformance IDs: no storage, no console, no network
|
|
16
|
+
* (PREVENT-PI-004 / PREVENT-011).
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The sentinel returned for an unknown provider/model. The renderer sees this as
|
|
20
|
+
* a CLEAN bypass decision, not an error, and emits `vector_cortex_provider_bypassed`.
|
|
21
|
+
*/
|
|
22
|
+
export const PRO_PROFILE_UNKNOWN = "PRO_PROFILE_UNKNOWN";
|
|
23
|
+
/**
|
|
24
|
+
* Registered PRO conformance ID range (PRO-001..015). The acceptance aggregator
|
|
25
|
+
* reads these rows from the v2 manifest and asserts each returns its manifest
|
|
26
|
+
* `ok`/`code` / pinned `requestDigest`.
|
|
27
|
+
*/
|
|
28
|
+
export const PRO_IDS = Array.from({ length: 15 }, (_v, i) => `PRO-${String(i + 1).padStart(3, "0")}`);
|
|
29
|
+
/** Named VC5B provider conformance assertions (the sprint's headline rows). */
|
|
30
|
+
export const PRO_NAMED_IDS = ["PRO-UNKNOWN-003"];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render/_acceptance-fixture.ts — conformance fixture I/O for VC5B render +
|
|
3
|
+
* provider acceptance rows.
|
|
4
|
+
*
|
|
5
|
+
* Reads the v2 conformance manifest + the per-sprint fixture files, and exposes
|
|
6
|
+
* the `RenderFx` / `ProviderFx` shapes the acceptance aggregator drives. No
|
|
7
|
+
* mocks/stubs: the fixtures are the committed canonical corpus.
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
10
|
+
import { join, dirname } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
function repoRoot(from) {
|
|
15
|
+
let dir = from;
|
|
16
|
+
for (let i = 0; i < 8; i++) {
|
|
17
|
+
if (existsSync(join(dir, "conformance", "vector-cortex")))
|
|
18
|
+
return dir;
|
|
19
|
+
const next = dirname(dir);
|
|
20
|
+
if (next === dir)
|
|
21
|
+
break;
|
|
22
|
+
dir = next;
|
|
23
|
+
}
|
|
24
|
+
throw new Error("conformance corpus not found above " + from);
|
|
25
|
+
}
|
|
26
|
+
export const REPO_ROOT = repoRoot(HERE);
|
|
27
|
+
export const V2 = join(REPO_ROOT, "conformance", "vector-cortex", "v2");
|
|
28
|
+
export function readManifest() {
|
|
29
|
+
return JSON.parse(readFileSync(join(V2, "manifest.json"), "utf8"));
|
|
30
|
+
}
|
|
31
|
+
function readFixture(id, prefix) {
|
|
32
|
+
const m = readManifest();
|
|
33
|
+
const row = m.fixtures.find((f) => f.id === id && f.path.startsWith(`${prefix}/`));
|
|
34
|
+
assert.ok(row, `fixture ${id} registered under ${prefix}/ in manifest`);
|
|
35
|
+
return JSON.parse(readFileSync(join(V2, row.path), "utf8"));
|
|
36
|
+
}
|
|
37
|
+
export function renderFixture(id) {
|
|
38
|
+
return readFixture(id, "render");
|
|
39
|
+
}
|
|
40
|
+
export function providerFixture(id) {
|
|
41
|
+
return readFixture(id, "provider");
|
|
42
|
+
}
|
|
43
|
+
/** Flag-pinned wrapper: VC5B gated by MEGACOMPACT_VC5B (defaults ON). */
|
|
44
|
+
export function withFlagsOn(fn) {
|
|
45
|
+
return () => {
|
|
46
|
+
const saved = process.env.MEGACOMPACT_VC5B;
|
|
47
|
+
process.env.MEGACOMPACT_VC5B = "1";
|
|
48
|
+
try {
|
|
49
|
+
fn();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
if (saved === undefined)
|
|
53
|
+
delete process.env.MEGACOMPACT_VC5B;
|
|
54
|
+
else
|
|
55
|
+
process.env.MEGACOMPACT_VC5B = saved;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render/_acceptance-helpers.ts — barrel re-export for the VC5B render + provider
|
|
3
|
+
* acceptance rows.
|
|
4
|
+
*
|
|
5
|
+
* The implementation is split across focused sibling files to keep each under
|
|
6
|
+
* the src/ 500-line hard limit; this file is the single import surface the
|
|
7
|
+
* acceptance aggregator uses:
|
|
8
|
+
* - `_acceptance-fixture.ts` — manifest I/O + RenderFx/ProviderFx shapes + withFlagsOn
|
|
9
|
+
* - `_acceptance-scenario.ts` — declarative graph materialization + runRenderScenario
|
|
10
|
+
* - `_acceptance-provider.ts` — provider-registry runner + KNOWN_KEYS
|
|
11
|
+
*
|
|
12
|
+
* Every function drives the REAL render / validate / provider logic — no mocks,
|
|
13
|
+
* no stubs.
|
|
14
|
+
*/
|
|
15
|
+
export { REPO_ROOT, V2, readManifest, renderFixture, providerFixture, withFlagsOn, } from "./_acceptance-fixture.js";
|
|
16
|
+
export { materializeGraph, runRenderScenario } from "./_acceptance-scenario.js";
|
|
17
|
+
export { runProviderScenario, KNOWN_KEYS } from "./_acceptance-provider.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render/_acceptance-provider.ts — the REAL provider-registry runner for VC5B
|
|
3
|
+
* provider acceptance rows. Drives `resolveProviderProfile` (no mocks).
|
|
4
|
+
*/
|
|
5
|
+
import { resolveProviderProfile, KNOWN_PROVIDER_KEYS } from "../provider/registry.js";
|
|
6
|
+
/** Run a provider fixture through the REAL registry resolver. */
|
|
7
|
+
export function runProviderScenario(fx) {
|
|
8
|
+
const r = resolveProviderProfile(fx.input.provider, fx.input.model);
|
|
9
|
+
if (!r.ok) {
|
|
10
|
+
return { ok: false, code: r.code, bypassClean: true };
|
|
11
|
+
}
|
|
12
|
+
const p = r.bundle.profile;
|
|
13
|
+
const cacheStable = fx.input.scenario === "resolve-excluded-cache" &&
|
|
14
|
+
p.excludedJsonPointers.length === 1 &&
|
|
15
|
+
p.excludedJsonPointers[0].pointer === "/requestId";
|
|
16
|
+
// deterministic: a second identical lookup returns the same profile id.
|
|
17
|
+
const r2 = resolveProviderProfile(fx.input.provider, fx.input.model);
|
|
18
|
+
const deterministic = r2.ok && r2.bundle.profile.id === p.id;
|
|
19
|
+
return {
|
|
20
|
+
ok: true,
|
|
21
|
+
profileId: p.id,
|
|
22
|
+
hashMode: p.hashMode,
|
|
23
|
+
excludedPointers: p.excludedJsonPointers.map((e) => e.pointer),
|
|
24
|
+
cacheStable,
|
|
25
|
+
deterministic,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Exposed for the acceptance registration check. */
|
|
29
|
+
export const KNOWN_KEYS = KNOWN_PROVIDER_KEYS;
|