pi-mega-compact 0.20.5 → 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.
Files changed (94) hide show
  1. package/dist/config/vector-cortex.js +12 -0
  2. package/dist/config.js +1 -1
  3. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +1 -0
  4. package/dist/extensions/dashboard-server/routes-vector-cortex-rollout.js +43 -0
  5. package/dist/extensions/dashboard-server/routes-vector-cortex.js +1 -0
  6. package/dist/extensions/dashboard-server/routes.js +1 -1
  7. package/dist/extensions/dashboard-server/server.js +3 -1
  8. package/dist/extensions/mega-runtime/vector-cortex-live.js +105 -0
  9. package/dist/src/config/vector-cortex.js +12 -0
  10. package/dist/src/config.js +1 -1
  11. package/dist/src/vector-cortex/rollout/_acceptance-fixture.js +55 -0
  12. package/dist/src/vector-cortex/rollout/_acceptance-helpers.js +8 -0
  13. package/dist/src/vector-cortex/rollout/_acceptance-scenario.js +70 -0
  14. package/dist/src/vector-cortex/rollout/assign.js +56 -0
  15. package/dist/src/vector-cortex/rollout/emit.js +50 -0
  16. package/dist/src/vector-cortex/rollout/gate.js +75 -0
  17. package/dist/src/vector-cortex/rollout/types.js +39 -0
  18. package/dist/vector-cortex/rollout/_acceptance-fixture.js +55 -0
  19. package/dist/vector-cortex/rollout/_acceptance-helpers.js +8 -0
  20. package/dist/vector-cortex/rollout/_acceptance-scenario.js +70 -0
  21. package/dist/vector-cortex/rollout/assign.js +56 -0
  22. package/dist/vector-cortex/rollout/emit.js +50 -0
  23. package/dist/vector-cortex/rollout/gate.js +75 -0
  24. package/dist/vector-cortex/rollout/types.js +39 -0
  25. package/extensions/dashboard-client/dist/assets/{AreaChart-CGL29Tcl.js → AreaChart-DqTbaSRA.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{AreaChart-CGL29Tcl.js.map → AreaChart-DqTbaSRA.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{BarChart-OxhalIZa.js → BarChart-BfNBeOvF.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{BarChart-OxhalIZa.js.map → BarChart-BfNBeOvF.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{CacheTab-VcMFwL-6.js → CacheTab-C9SOMYB5.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{CacheTab-VcMFwL-6.js.map → CacheTab-C9SOMYB5.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{EventsTab-DePofXDM.js → EventsTab-GsMKP10_.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{EventsTab-DePofXDM.js.map → EventsTab-GsMKP10_.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{HealthTab-nQd1tjSF.js → HealthTab-DAOCXwRP.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{HealthTab-nQd1tjSF.js.map → HealthTab-DAOCXwRP.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-CzvA8-F6.js → MaintenanceTab-BtBeMkB5.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-CzvA8-F6.js.map → MaintenanceTab-BtBeMkB5.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-3lxgWBvU.js → MemoryMapTab-DzndtgkZ.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-3lxgWBvU.js.map → MemoryMapTab-DzndtgkZ.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{MetricsTab-D_FcA6kN.js → MetricsTab-DyxJ4eb8.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{MetricsTab-D_FcA6kN.js.map → MetricsTab-DyxJ4eb8.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{OverviewTab-BU6R4aEs.js → OverviewTab-DCiEdVWR.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{OverviewTab-BU6R4aEs.js.map → OverviewTab-DCiEdVWR.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{ReposTab-DFjy1isz.js → ReposTab-B5SIqVHA.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{ReposTab-DFjy1isz.js.map → ReposTab-B5SIqVHA.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{SessionsTab-UGIzIH25.js → SessionsTab-DLWFJ9xI.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{SessionsTab-UGIzIH25.js.map → SessionsTab-DLWFJ9xI.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{SetupTab-CECrgMKI.js → SetupTab-CvGXZyej.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{SetupTab-CECrgMKI.js.map → SetupTab-CvGXZyej.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-Dyy4SCDk.js → TimeSavedCard-wZuQGxLS.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-Dyy4SCDk.js.map → TimeSavedCard-wZuQGxLS.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/{TurnsTab-B0LPcVs1.js → TurnsTab-B7aOejch.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{TurnsTab-B0LPcVs1.js.map → TurnsTab-B7aOejch.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/VectorCortexTab-eHVt9yDx.js +2 -0
  54. package/extensions/dashboard-client/dist/assets/VectorCortexTab-eHVt9yDx.js.map +1 -0
  55. package/extensions/dashboard-client/dist/assets/{WikiTab-DWk9Qvut.js → WikiTab-DhWHjAtt.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{WikiTab-DWk9Qvut.js.map → WikiTab-DhWHjAtt.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{button-O59wPr5H.js → button-BrC-W87h.js} +2 -2
  58. package/extensions/dashboard-client/dist/assets/{button-O59wPr5H.js.map → button-BrC-W87h.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{card-C-DxXv1-.js → card-0rgfRgr3.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{card-C-DxXv1-.js.map → card-0rgfRgr3.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-Dw5YnE5s.js → generateCategoricalChart-DRresOQF.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-Dw5YnE5s.js.map → generateCategoricalChart-DRresOQF.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{index-i9oaPE0a.js → index-DohmtZWL.js} +3 -3
  64. package/extensions/dashboard-client/dist/assets/{index-i9oaPE0a.js.map → index-DohmtZWL.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/{switch-B69QY3IM.js → switch-Bs8g_zB5.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{switch-B69QY3IM.js.map → switch-Bs8g_zB5.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/{toggle-BJ4Kwggu.js → toggle-DVVauIo8.js} +2 -2
  68. package/extensions/dashboard-client/dist/assets/{toggle-BJ4Kwggu.js.map → toggle-DVVauIo8.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/assets/{useSSE-BpuA8sPd.js → useSSE-DxH1PTAR.js} +2 -2
  70. package/extensions/dashboard-client/dist/assets/{useSSE-BpuA8sPd.js.map → useSSE-DxH1PTAR.js.map} +1 -1
  71. package/extensions/dashboard-client/dist/index.html +1 -1
  72. package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
  73. package/extensions/dashboard-client/src/tabs/VectorCortexRolloutCard.tsx +44 -0
  74. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +8 -0
  75. package/extensions/dashboard-client/src/types/vector-cortex.ts +17 -0
  76. package/extensions/dashboard-server/api-contracts/vector-cortex.ts +32 -1
  77. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +6 -0
  78. package/extensions/dashboard-server/routes-vector-cortex-rollout.ts +52 -0
  79. package/extensions/dashboard-server/routes-vector-cortex.ts +1 -0
  80. package/extensions/dashboard-server/routes.ts +1 -0
  81. package/extensions/dashboard-server/server.ts +2 -0
  82. package/extensions/mega-runtime/vector-cortex-live.ts +165 -0
  83. package/package.json +1 -1
  84. package/src/config/vector-cortex.ts +13 -0
  85. package/src/config.ts +1 -0
  86. package/src/vector-cortex/rollout/_acceptance-fixture.ts +94 -0
  87. package/src/vector-cortex/rollout/_acceptance-helpers.ts +24 -0
  88. package/src/vector-cortex/rollout/_acceptance-scenario.ts +96 -0
  89. package/src/vector-cortex/rollout/assign.ts +67 -0
  90. package/src/vector-cortex/rollout/emit.ts +81 -0
  91. package/src/vector-cortex/rollout/gate.ts +119 -0
  92. package/src/vector-cortex/rollout/types.ts +111 -0
  93. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BWi3u_gp.js +0 -2
  94. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BWi3u_gp.js.map +0 -1
@@ -379,6 +379,12 @@ export const SETTINGS: ReadonlyArray<{
379
379
  "Validated prompt renderer: replays VC5A's stable Kahn order verbatim, preserves exact tool bytes (PREVENT-PI-002), places compacted context via the host before_agent_start prepend seam — never role:system (PREVENT-PI-003) — and SHA-256 hashes the entire canonical outbound request before provider invocation. Unknown provider/model cleanly bypasses to the predecessor prompt path. OFF = byte-identical predecessor (VC5A).",
380
380
  true,
381
381
  ),
382
+ boolDirect(
383
+ "MEGACOMPACT_VC5C",
384
+ "VC5C Live Graduated Rollout",
385
+ "Live graduated rollout: deterministically hashes each session into a stable 10,000-bucket cohort and advances the exposure gate (1/5/25/50/100%) only after a 72h monotonic residency, a powered sample, >=10,000 events, and >=200 sessions — advancing ONE gate at a time. A hard causal/tool/anchor/exact failure freezes promotion and selects the pre-VC path. OFF = byte-identical predecessor (VC5B).",
386
+ true,
387
+ ),
382
388
  ],
383
389
  },
384
390
  {
@@ -0,0 +1,52 @@
1
+ /**
2
+ * dashboard-server/routes-vector-cortex-rollout.ts — VC5C live graduated-rollout
3
+ * dashboard route.
4
+ *
5
+ * Reader-only GET /api/vector-cortex/rollout returning the rollout aggregate:
6
+ * current gate, bucket count, sessions/events counts, and promotion-blocked
7
+ * state. NEVER exposes session payloads, prompt text, or bucket→session mappings
8
+ * (reader-only, SECURITY_PRIVACY). Flag-gated on MEGACOMPACT_VC5C: `enabled:false`
9
+ * when off (byte-identical to the pre-VC5C predecessor).
10
+ *
11
+ * Guardrails: PREVENT-PI-004 (local in-process state only), PREVENT-011 (no
12
+ * `any`), reader-only aggregate (counts + gate state only).
13
+ */
14
+
15
+ import type { IncomingMessage, ServerResponse } from "node:http";
16
+ import type { RouteContext } from "./routes-core.js";
17
+ import { VC5C_ENABLED } from "../../src/config.js";
18
+ import { ROLLOUT_GATES, ROLLOUT_BUCKETS } from "../../src/vector-cortex/rollout/types.js";
19
+ import { sendJson } from "./routes-vector-cortex-shared.js";
20
+ import type { VectorCortexRolloutView } from "./api-contracts/vector-cortex.js";
21
+
22
+ /** Reader-only GET /api/vector-cortex/rollout (VC5C). */
23
+ export function handleVectorCortexRollout(
24
+ req: IncomingMessage,
25
+ res: ServerResponse,
26
+ _ctx: RouteContext,
27
+ ): boolean {
28
+ const url = req.url ?? "";
29
+ const path = url.split("?")[0] ?? url;
30
+ if (path !== "/api/vector-cortex/rollout") return false;
31
+ if (req.method !== "GET") {
32
+ sendJson(res, 405, { error: "method_not_allowed" });
33
+ return true;
34
+ }
35
+
36
+ const enabled = VC5C_ENABLED();
37
+ const gateIndex = 0; // Ephemeral rollout state: no durable gate in this sprint.
38
+ const gatePct = ROLLOUT_GATES[gateIndex]!;
39
+ const body: VectorCortexRolloutView = {
40
+ enabled,
41
+ gateIndex: enabled ? gateIndex : 0,
42
+ gatePct: enabled ? gatePct : 0,
43
+ buckets: ROLLOUT_BUCKETS,
44
+ bucketCount: enabled ? gatePct * 100 : 0,
45
+ events: 0,
46
+ sessions: 0,
47
+ promotionBlocked: false,
48
+ updatedAt: new Date().toISOString(),
49
+ };
50
+ sendJson(res, 200, body);
51
+ return true;
52
+ }
@@ -24,3 +24,4 @@ export { handleVectorCortexResidual } from "./routes-vector-cortex-residual.js";
24
24
  export { handleVectorCortexReconstruct } from "./routes-vector-cortex-reconstruct.js";
25
25
  export { handleVectorCortexPlans } from "./routes-vector-cortex-plans.js";
26
26
  export { handleVectorCortexRender } from "./routes-vector-cortex-render.js";
27
+ export { handleVectorCortexRollout } from "./routes-vector-cortex-rollout.js";
@@ -52,4 +52,5 @@ export {
52
52
  handleVectorCortexReconstruct,
53
53
  handleVectorCortexPlans,
54
54
  handleVectorCortexRender,
55
+ handleVectorCortexRollout,
55
56
  } from "./routes-vector-cortex.js";
@@ -68,6 +68,7 @@ import {
68
68
  handleVectorCortexReconstruct,
69
69
  handleVectorCortexPlans,
70
70
  handleVectorCortexRender,
71
+ handleVectorCortexRollout,
71
72
  handleStatic,
72
73
  } from "./routes.js";
73
74
 
@@ -315,6 +316,7 @@ export async function launchDashboardServer(
315
316
  if (handleVectorCortexReconstruct(req, res, ctx)) return;
316
317
  if (handleVectorCortexPlans(req, res, ctx)) return;
317
318
  if (handleVectorCortexRender(req, res, ctx)) return;
319
+ if (handleVectorCortexRollout(req, res, ctx)) return;
318
320
  handleStatic(req, res, ctx);
319
321
  });
320
322
 
@@ -0,0 +1,165 @@
1
+ /**
2
+ * vector-cortex-live.ts — VC5C live graduated-rollout integration seam.
3
+ *
4
+ * This adapter composes the rollout assign + gate + emit seams into the host
5
+ * provider-invocation path. It runs BEFORE provider invocation and decides, for
6
+ * the current session, which live path is active:
7
+ *
8
+ * - on a hard causal/tool/anchor/exact failure → select mode C (pre-VC prompt
9
+ * path) and FREEZE promotion (promotionBlocked=true) for the remainder of the
10
+ * cooldown/spool/restart/clock period satisfying TRIAD_RESILIENCE;
11
+ * - otherwise → the active gate decides whether the VC path is exposed to this
12
+ * session's bucket.
13
+ *
14
+ * PREVENT-PI-003: the rollout NEVER emits a `role:"system"` message. The VC
15
+ * prompt context is composed into the host `before_agent_start` systemPrompt
16
+ * prepend seam only. PREVENT-PI-004: zero network calls (local in-process state
17
+ * + injected clock only). PREVENT-011: no `any`. Non-fatal: any rollout noise
18
+ * degrades to mode C (pre-VC continuity), never breaking the agent loop.
19
+ *
20
+ * LIVENESS HONESTY (mirrors VC0C-Q01/Q06): the rollout decision state here is
21
+ * PER-PROCESS / IN-MEMORY and EPHEMERAL — there is NO persistent rollout runtime
22
+ * and NO live producer wiring. The gate decision is recomputed from injected
23
+ * evidence each epoch. The assigned/blocked events are emitted for observability
24
+ * only.
25
+ */
26
+
27
+ import { assignSession, bucketInGate, gatePctForIndex } from "../../src/vector-cortex/rollout/assign.js";
28
+ import {
29
+ decideGate,
30
+ selectsPreVcPath,
31
+ type RolloutClock,
32
+ type RolloutEvidence,
33
+ } from "../../src/vector-cortex/rollout/gate.js";
34
+ import { createRolloutReporter, type RolloutReporter } from "../../src/vector-cortex/rollout/emit.js";
35
+ import type { GateIndex, RolloutHardFault } from "../../src/vector-cortex/rollout/types.js";
36
+ import { VC5C_ENABLED } from "../../src/config/vector-cortex.js";
37
+
38
+ /** Host context the live seam needs. */
39
+ export interface VectorCortexLiveContext {
40
+ /** Monotonic clock for windows/cooldowns + gate eligibility (fake-clock testable). */
41
+ readonly clock?: RolloutClock;
42
+ /** Current gate index BEFORE this epoch (0..4). */
43
+ readonly currentGate?: GateIndex;
44
+ /** Injected rollout evidence for the current window. */
45
+ readonly evidence?: RolloutEvidence;
46
+ /** Hard faults observed this epoch (any entry forces C + freezes promotion). */
47
+ readonly hardFaults?: readonly RolloutHardFault[];
48
+ /** Optional emit callback for observability events. */
49
+ readonly emit?: (event: string, fields: Record<string, unknown>) => void;
50
+ }
51
+
52
+ /** The decision the live seam returns for one session epoch. */
53
+ export interface VectorCortexLiveDecision {
54
+ /** Whether the VC path is exposed to this session (true under current gate). */
55
+ readonly vcActive: boolean;
56
+ /** True when a hard failure forced mode C (pre-VC continuity). */
57
+ readonly forcedPreVc: boolean;
58
+ /**
59
+ * The selected triad mode for this epoch. The VC5C rollout seam only ever
60
+ * selects "A" (VC path exposed at the active gate) or "C" (pre-VC fallback:
61
+ * flag off, no evidence/clock, or a hard causal/tool/anchor/exact fault that
62
+ * freezes promotion). The deterministic-greedy triad "B" mode is NOT produced
63
+ * here — this seam is a pure gating/eligibility layer with no live renderer
64
+ * driver; a real mode-B greedy-render fallback belongs to the layer that
65
+ * actually runs renderers (a future sprint), not to this observability seam.
66
+ * Declaring "B" in this union while never producing it would be an overclaim.
67
+ */
68
+ readonly mode: "A" | "C";
69
+ /** The session's stable bucket (0..9999). */
70
+ readonly bucket: number;
71
+ /** The effective gate index after this epoch. */
72
+ readonly gateIndex: GateIndex;
73
+ /** True when promotion is frozen by a hard fault. */
74
+ readonly promotionBlocked: boolean;
75
+ /** The systemPrompt prepend the host `before_agent_start` seam should use. */
76
+ readonly systemPromptPrepend: string;
77
+ }
78
+
79
+ const NO_PREPEND = "";
80
+
81
+ /**
82
+ * Decide the live path for a session epoch. Pure over injected context; no
83
+ * I/O, no network. Flag OFF (or absent evidence/clock) degrades to mode C with
84
+ * the VC path inactive (pre-VC predecessor parity).
85
+ */
86
+ export function decideLivePath(
87
+ sessionId: string,
88
+ ctx: VectorCortexLiveContext,
89
+ ): VectorCortexLiveDecision {
90
+ const reporter: RolloutReporter = createRolloutReporter(ctx.emit);
91
+ const assignment = assignSession(sessionId);
92
+
93
+ if (!VC5C_ENABLED()) {
94
+ // Pre-VC predecessor: no rollout, no VC path. Never emit role:system.
95
+ return {
96
+ vcActive: false,
97
+ forcedPreVc: false,
98
+ mode: "C",
99
+ bucket: assignment.bucket,
100
+ gateIndex: 0,
101
+ promotionBlocked: false,
102
+ systemPromptPrepend: NO_PREPEND,
103
+ };
104
+ }
105
+
106
+ reporter.assigned({
107
+ sessionId,
108
+ bucket: assignment.bucket,
109
+ gateIndex: assignment.gateIndex,
110
+ gatePct: gatePctForIndex(assignment.gateIndex),
111
+ });
112
+
113
+ const hardFaults = ctx.hardFaults ?? [];
114
+ const hardFault = hardFaults.length > 0 ? hardFaults[0]! : undefined;
115
+ const forcedPreVc = selectsPreVcPath(hardFault);
116
+
117
+ if (forcedPreVc && hardFault) {
118
+ reporter.promotionBlocked({
119
+ gateIndex: ctx.currentGate ?? assignment.gateIndex,
120
+ kind: hardFault.kind,
121
+ detail: hardFault.detail,
122
+ });
123
+ // Hard failure → select C (pre-VC path) and freeze promotion.
124
+ return {
125
+ vcActive: false,
126
+ forcedPreVc: true,
127
+ mode: "C",
128
+ bucket: assignment.bucket,
129
+ gateIndex: ctx.currentGate ?? assignment.gateIndex,
130
+ promotionBlocked: true,
131
+ systemPromptPrepend: NO_PREPEND,
132
+ };
133
+ }
134
+
135
+ const clock = ctx.clock;
136
+ const evidence = ctx.evidence;
137
+ if (clock === undefined || evidence === undefined) {
138
+ // No live evidence/clock → pre-VC parity (no promotion, no VC exposure).
139
+ return {
140
+ vcActive: false,
141
+ forcedPreVc: false,
142
+ mode: "C",
143
+ bucket: assignment.bucket,
144
+ gateIndex: assignment.gateIndex,
145
+ promotionBlocked: false,
146
+ systemPromptPrepend: NO_PREPEND,
147
+ };
148
+ }
149
+
150
+ const outcome = decideGate(ctx.currentGate ?? assignment.gateIndex, evidence, clock);
151
+ const vcActive = bucketInGate(assignment.bucket, outcome.gatePct);
152
+
153
+ // PREVENT-PI-003: VC prompt context is composed into the host prepend seam,
154
+ // never as a role:system message. Empty prepend here (the actual prompt text
155
+ // is owned by the renderer seam); this seam only signals eligibility.
156
+ return {
157
+ vcActive,
158
+ forcedPreVc: false,
159
+ mode: vcActive ? "A" : "C",
160
+ bucket: assignment.bucket,
161
+ gateIndex: outcome.gateIndex,
162
+ promotionBlocked: outcome.promotionBlocked,
163
+ systemPromptPrepend: NO_PREPEND,
164
+ };
165
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.20.5",
3
+ "version": "0.20.6",
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",
@@ -201,6 +201,19 @@ export const VC5A_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC5A");
201
201
  */
202
202
  export const VC5B_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC5B");
203
203
 
204
+ /**
205
+ * VC5C — live graduated rollout. Default ON. `MEGACOMPACT_VC5C=0` disables and
206
+ * is byte-identical to the predecessor (VC5B): no session is assigned a stable
207
+ * 10k bucket, no gate-advance decision runs, the `vector_cortex_rollout_assigned`
208
+ * / `vector_cortex_rollout_promotion_blocked` events are never emitted, and the
209
+ * prompt continues to be built by the predecessor VC5B renderer path (its goldens
210
+ * are unchanged). The assign/gate/emit functions are PURE — flag OFF gates the
211
+ * reporter + dashboard seam, never the arithmetic. This flag MUST also be a
212
+ * dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
213
+ * mirroring VC4A/VC4B/VC4C/VC5A/VC5B.
214
+ */
215
+ export const VC5C_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC5C");
216
+
204
217
  // ---------------------------------------------------------------------------
205
218
  // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
206
219
  // Rolled numbers for one 60s window; VC0C consumes these at its breaker seam.
package/src/config.ts CHANGED
@@ -168,6 +168,7 @@ export {
168
168
  VC4C_ENABLED,
169
169
  VC5A_ENABLED,
170
170
  VC5B_ENABLED,
171
+ VC5C_ENABLED,
171
172
  BREAKER_WINDOW_MS,
172
173
  BREAKER_MIN_ATTEMPTS,
173
174
  BREAKER_PERF_FAILURES,
@@ -0,0 +1,94 @@
1
+ /**
2
+ * rollout/_acceptance-fixture.ts — conformance fixture I/O for VC5C rollout
3
+ * acceptance rows.
4
+ *
5
+ * Reads the v2 conformance manifest + the per-sprint rollout fixture files, and
6
+ * exposes the `RolloutFx` shape 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
+
14
+ const HERE = dirname(fileURLToPath(import.meta.url));
15
+
16
+ function repoRoot(from: string): string {
17
+ let dir = from;
18
+ for (let i = 0; i < 8; i++) {
19
+ if (existsSync(join(dir, "conformance", "vector-cortex"))) return dir;
20
+ const next = dirname(dir);
21
+ if (next === dir) 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
+
29
+ export interface ManifestRow {
30
+ id: string;
31
+ path: string;
32
+ algorithm: string;
33
+ expected: string;
34
+ }
35
+ export interface Manifest {
36
+ fixtures: ManifestRow[];
37
+ }
38
+
39
+ export function readManifest(): Manifest {
40
+ return JSON.parse(readFileSync(join(V2, "manifest.json"), "utf8")) as Manifest;
41
+ }
42
+
43
+ function readFixture(id: string, prefix: "rollout"): unknown {
44
+ const m = readManifest();
45
+ const row = m.fixtures.find((f) => f.id === id && f.path.startsWith(`${prefix}/`));
46
+ assert.ok(row, `fixture ${id} registered under ${prefix}/ in manifest`);
47
+ return JSON.parse(readFileSync(join(V2, row.path), "utf8"));
48
+ }
49
+
50
+ export interface RolloutFxEvidence {
51
+ windowStartMs?: number;
52
+ powered?: boolean;
53
+ events?: number;
54
+ sessions?: number;
55
+ hardFaults?: Array<{ kind: string; detail: string }>;
56
+ }
57
+ export interface RolloutFxInput {
58
+ scenario: string;
59
+ sessionId?: string;
60
+ evidence?: RolloutFxEvidence;
61
+ }
62
+ export interface RolloutFxExpected {
63
+ ok: boolean;
64
+ bucket?: number;
65
+ gateIndex?: number;
66
+ promotionBlocked?: boolean;
67
+ selectsPreVc?: boolean;
68
+ }
69
+ export interface RolloutFx {
70
+ id: string;
71
+ schema: string;
72
+ producer: string;
73
+ assertion: string;
74
+ kind: string;
75
+ input: RolloutFxInput;
76
+ expected: RolloutFxExpected;
77
+ }
78
+ export function rolloutFixture(id: string): RolloutFx {
79
+ return readFixture(id, "rollout") as RolloutFx;
80
+ }
81
+
82
+ /** Flag-pinned wrapper: VC5C gated by MEGACOMPACT_VC5C (defaults ON). */
83
+ export function withFlagsOn(fn: () => void): () => void {
84
+ return (): void => {
85
+ const saved = process.env.MEGACOMPACT_VC5C;
86
+ process.env.MEGACOMPACT_VC5C = "1";
87
+ try {
88
+ fn();
89
+ } finally {
90
+ if (saved === undefined) delete process.env.MEGACOMPACT_VC5C;
91
+ else process.env.MEGACOMPACT_VC5C = saved;
92
+ }
93
+ };
94
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * rollout/_acceptance-helpers.ts — pure barrel re-export of the VC5C rollout
3
+ * acceptance helpers (fixture I/O + REAL runner). Mirrors the render family's
4
+ * delegate-shell split so the aggregator stays under the 600-line test hard
5
+ * limit.
6
+ */
7
+
8
+ export {
9
+ REPO_ROOT,
10
+ V2,
11
+ readManifest,
12
+ rolloutFixture,
13
+ withFlagsOn,
14
+ } from "./_acceptance-fixture.js";
15
+ export type {
16
+ Manifest,
17
+ ManifestRow,
18
+ RolloutFx,
19
+ RolloutFxInput,
20
+ RolloutFxExpected,
21
+ RolloutFxEvidence,
22
+ } from "./_acceptance-fixture.js";
23
+ export { runRolloutScenario } from "./_acceptance-scenario.js";
24
+ export type { RolloutRunOutcome } from "./_acceptance-scenario.js";
@@ -0,0 +1,96 @@
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
+
12
+ import { assignSession } from "./assign.js";
13
+ import {
14
+ decideGate,
15
+ type RolloutClock,
16
+ type RolloutEvidence,
17
+ } from "./gate.js";
18
+ import type { GateIndex, RolloutHardFault } from "./types.js";
19
+ import type { RolloutFx } from "./_acceptance-fixture.js";
20
+
21
+ const GATE_MIN_ELAPSED_MS = 72 * 60 * 60 * 1000;
22
+
23
+ export interface RolloutRunOutcome {
24
+ readonly ok: boolean;
25
+ readonly bucket?: number;
26
+ readonly gateIndex?: number;
27
+ readonly promotionBlocked?: boolean;
28
+ readonly selectsPreVc?: boolean;
29
+ }
30
+
31
+ /** Fixed monotonic clock: elapsed = now - windowStart (the only driver). */
32
+ function clockAt(elapsedMs: number, windowStartMs: number): RolloutClock {
33
+ return {
34
+ now: () => windowStartMs + elapsedMs, // monotonic delta is the truth
35
+ wallNow: () => new Date(1_700_000_000_000 + elapsedMs).toISOString(),
36
+ };
37
+ }
38
+
39
+ /** Run a rollout fixture through the REAL assign + gate pipeline. */
40
+ export function runRolloutScenario(fx: RolloutFx): RolloutRunOutcome {
41
+ const scenario = fx.input.scenario;
42
+
43
+ if (scenario === "assign-stable") {
44
+ const a = assignSession(fx.input.sessionId ?? "");
45
+ const b = assignSession(fx.input.sessionId ?? "");
46
+ // Determinism: recomputing yields the identical bucket.
47
+ if (a.bucket !== b.bucket) {
48
+ return { ok: false };
49
+ }
50
+ return { ok: true, bucket: a.bucket };
51
+ }
52
+
53
+ if (scenario === "gate-power" || scenario === "gate-safety") {
54
+ const ev = fx.input.evidence ?? {};
55
+ const windowStartMs = ev.windowStartMs ?? 0;
56
+ const hardFaults = (ev.hardFaults ?? []).map(
57
+ (f): RolloutHardFault => ({ kind: f.kind as RolloutHardFault["kind"], detail: f.detail }),
58
+ );
59
+ const evidence: RolloutEvidence = {
60
+ windowStartMs,
61
+ powered: ev.powered ?? false,
62
+ events: ev.events ?? 0,
63
+ sessions: ev.sessions ?? 0,
64
+ hardFaults,
65
+ };
66
+
67
+ // The spec's unique failure injection: wall-clock jump is irrelevant; only the
68
+ // monotonic elapsed drives eligibility. For the 71h59m row we pass 71h59m; for
69
+ // the wall-jump row we pass the SAME monotonic elapsed (so it must stay blocked);
70
+ // for the >=72h row we pass a full 72h+ monotonic delta (so it advances).
71
+ const start = (fx as RolloutFx & { _monotonicElapsedMs?: number })._monotonicElapsedMs;
72
+ const elapsedMs = start ?? GATE_MIN_ELAPSED_MS; // default: a full 72h window
73
+ const clock = clockAt(elapsedMs, windowStartMs);
74
+
75
+ // gate-safety with a hard fault forces pre-VC + freezes promotion regardless of
76
+ // the monotonic window; the gate index stays at the input's prior gate (0 here).
77
+ if (scenario === "gate-safety") {
78
+ const outcome = decideGate(0 as GateIndex, evidence, clock);
79
+ return {
80
+ ok: true,
81
+ gateIndex: outcome.gateIndex,
82
+ promotionBlocked: outcome.promotionBlocked,
83
+ selectsPreVc: outcome.promotionBlocked,
84
+ };
85
+ }
86
+
87
+ const outcome = decideGate(0 as GateIndex, evidence, clock);
88
+ return {
89
+ ok: true,
90
+ gateIndex: outcome.gateIndex,
91
+ promotionBlocked: outcome.promotionBlocked,
92
+ };
93
+ }
94
+
95
+ throw new Error(`unknown rollout scenario: ${scenario}`);
96
+ }
@@ -0,0 +1,67 @@
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
+
16
+ import {
17
+ ROLLOUT_BUCKETS,
18
+ ROLLOUT_GATES,
19
+ type GateIndex,
20
+ type RolloutAssignmentV1,
21
+ } from "./types.js";
22
+
23
+ /**
24
+ * Deterministic 32-bit FNV-1a over the session id (UTF-8 bytes). Fixed seed
25
+ * (2166136261) — restart-invariant by construction. Returns a bucket in
26
+ * 0..(ROLLOUT_BUCKETS-1).
27
+ */
28
+ export function assignSession(sessionId: string): RolloutAssignmentV1 {
29
+ const enc = new TextEncoder();
30
+ const bytes = enc.encode(sessionId);
31
+ let hash = 0x811c9dc5; // FNV offset basis (32-bit)
32
+ for (let i = 0; i < bytes.length; i++) {
33
+ hash ^= bytes[i]!;
34
+ // hash *= 16777619 (FNV prime) with 32-bit overflow wrap.
35
+ hash = Math.imul(hash, 0x01000193);
36
+ }
37
+ const bucket = (hash >>> 0) % ROLLOUT_BUCKETS;
38
+
39
+ // The bucket's qualifying gate is the HIGHEST gate whose bound it falls under.
40
+ let gateIndex: GateIndex = 0;
41
+ for (let i = 0; i < ROLLOUT_GATES.length; i++) {
42
+ if (bucket < ROLLOUT_GATES[i]! * 100) {
43
+ gateIndex = i as GateIndex;
44
+ break;
45
+ }
46
+ }
47
+
48
+ return {
49
+ schema: "rollout-assignment-v1",
50
+ sessionId,
51
+ bucket,
52
+ gateIndex,
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Whether a bucket qualifies for a given gate percentage. Used by the live seam
58
+ * to decide whether a session's bucket is currently exposed to the VC path.
59
+ */
60
+ export function bucketInGate(bucket: number, gatePct: number): boolean {
61
+ return bucket < gatePct * 100;
62
+ }
63
+
64
+ /** Current gate percentage for a gate index. */
65
+ export function gatePctForIndex(gateIndex: GateIndex): number {
66
+ return ROLLOUT_GATES[gateIndex]!;
67
+ }
@@ -0,0 +1,81 @@
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
+
16
+ import { VC5C_ENABLED } from "../../config/vector-cortex.js";
17
+
18
+ export type RolloutEventName =
19
+ | "vector_cortex_rollout_assigned"
20
+ | "vector_cortex_rollout_promotion_blocked";
21
+
22
+ /** Injected emit callback — same (event, fields) shape as the other seams. */
23
+ export type RolloutEmitter = (event: string, fields: Record<string, unknown>) => void;
24
+
25
+ export interface RolloutAssignedFields {
26
+ readonly sessionId: string;
27
+ readonly bucket: number;
28
+ readonly gateIndex: number;
29
+ readonly gatePct: number;
30
+ }
31
+
32
+ export interface PromotionBlockedFields {
33
+ readonly gateIndex: number;
34
+ readonly kind: string;
35
+ readonly detail: string;
36
+ }
37
+
38
+ /** Typed, best-effort reporter bound to the two rollout event names. */
39
+ export interface RolloutReporter {
40
+ readonly assigned: (fields: RolloutAssignedFields) => void;
41
+ readonly promotionBlocked: (fields: PromotionBlockedFields) => void;
42
+ }
43
+
44
+ function safe(fn: (event: RolloutEventName, fields: Record<string, unknown>) => void): (
45
+ event: RolloutEventName,
46
+ fields: Record<string, unknown>,
47
+ ) => void {
48
+ return (event, fields) => {
49
+ try {
50
+ fn(event, fields);
51
+ } catch {
52
+ /* non-fatal observability — never break the agent loop */
53
+ }
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Build a typed reporter over an injected emit callback. Emission is gated on
59
+ * `VC5C_ENABLED()` — flag OFF (or absent emitter) means zero rollout
60
+ * observability writes (pre-VC predecessor parity). The flag is read per-call so
61
+ * a live toggle takes effect immediately.
62
+ */
63
+ export function createRolloutReporter(emit?: RolloutEmitter): RolloutReporter {
64
+ const fire = safe((event, fields) => {
65
+ if (VC5C_ENABLED()) emit?.(event, fields);
66
+ });
67
+ return {
68
+ assigned(fields) {
69
+ fire("vector_cortex_rollout_assigned", fields as unknown as Record<string, unknown>);
70
+ },
71
+ promotionBlocked(fields) {
72
+ fire("vector_cortex_rollout_promotion_blocked", fields as unknown as Record<string, unknown>);
73
+ },
74
+ };
75
+ }
76
+
77
+ /** Export the event names for consumers that match on them. */
78
+ export const ROLLOUT_EVENT_NAMES: readonly RolloutEventName[] = [
79
+ "vector_cortex_rollout_assigned",
80
+ "vector_cortex_rollout_promotion_blocked",
81
+ ];