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
@@ -184,6 +184,18 @@ export const VC5A_ENABLED = () => sprintFlag("MEGACOMPACT_VC5A");
184
184
  * in EXCLUDED_SETTINGS), mirroring VC4A/VC4B/VC4C/VC5A.
185
185
  */
186
186
  export const VC5B_ENABLED = () => sprintFlag("MEGACOMPACT_VC5B");
187
+ /**
188
+ * VC5C — live graduated rollout. Default ON. `MEGACOMPACT_VC5C=0` disables and
189
+ * is byte-identical to the predecessor (VC5B): no session is assigned a stable
190
+ * 10k bucket, no gate-advance decision runs, the `vector_cortex_rollout_assigned`
191
+ * / `vector_cortex_rollout_promotion_blocked` events are never emitted, and the
192
+ * prompt continues to be built by the predecessor VC5B renderer path (its goldens
193
+ * are unchanged). The assign/gate/emit functions are PURE — flag OFF gates the
194
+ * reporter + dashboard seam, never the arithmetic. This flag MUST also be a
195
+ * dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
196
+ * mirroring VC4A/VC4B/VC4C/VC5A/VC5B.
197
+ */
198
+ export const VC5C_ENABLED = () => sprintFlag("MEGACOMPACT_VC5C");
187
199
  // ---------------------------------------------------------------------------
188
200
  // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
189
201
  // Rolled numbers for one 60s window; VC0C consumes these at its breaker seam.
package/dist/config.js CHANGED
@@ -114,4 +114,4 @@ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
114
114
  // default ON, `=0`/`_DISABLED` off. Re-exported from src/config/vector-cortex.ts
115
115
  // so root consumers share one source of truth.
116
116
  // ---------------------------------------------------------------------------
117
- export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
117
+ export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
@@ -151,6 +151,7 @@ export const SETTINGS = [
151
151
  boolDirect("MEGACOMPACT_VC4C", "VC4C Reconstruction Fidelity", "Conservative closure + source-order assembly + reconstruction validator: recursively closes dependencies and whole tool pairs to a fixed point, resolves contradictions by retaining the later exact source resolution, assembles spans solely by source range, and rejects missing anchors / split pairs / digest mismatch / unresolved contradiction. Mandatory token estimate is content-only and handed unchanged to VC5A. OFF = mode C, no closure/validator, byte-identical predecessor (VC4B).", true),
152
152
  boolDirect("MEGACOMPACT_VC5A", "VC5A PromptDagV1 + Budgeted Planner", "Single-session DAG (PromptDagV1) + budgeted 0/1 portfolio planner: builds a stable Kahn-ordered DAG, computes the mandatory dependency/tool/anchor closure before optional selection, returns MANDATORY_CLOSURE_OVER_BUDGET with evidence preserved on overflow, and runs a utility-per-token portfolio that never exceeds the remaining budget. Framing is owned here, not in VC4C. OFF = byte-identical predecessor (VC4C).", true),
153
153
  boolDirect("MEGACOMPACT_VC5B", "VC5B Validated Renderer + Provider Profiles", "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).", true),
154
+ boolDirect("MEGACOMPACT_VC5C", "VC5C Live Graduated Rollout", "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).", true),
154
155
  ],
155
156
  },
156
157
  {
@@ -0,0 +1,43 @@
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
+ import { VC5C_ENABLED } from "../../src/config.js";
15
+ import { ROLLOUT_GATES, ROLLOUT_BUCKETS } from "../../src/vector-cortex/rollout/types.js";
16
+ import { sendJson } from "./routes-vector-cortex-shared.js";
17
+ /** Reader-only GET /api/vector-cortex/rollout (VC5C). */
18
+ export function handleVectorCortexRollout(req, res, _ctx) {
19
+ const url = req.url ?? "";
20
+ const path = url.split("?")[0] ?? url;
21
+ if (path !== "/api/vector-cortex/rollout")
22
+ return false;
23
+ if (req.method !== "GET") {
24
+ sendJson(res, 405, { error: "method_not_allowed" });
25
+ return true;
26
+ }
27
+ const enabled = VC5C_ENABLED();
28
+ const gateIndex = 0; // Ephemeral rollout state: no durable gate in this sprint.
29
+ const gatePct = ROLLOUT_GATES[gateIndex];
30
+ const body = {
31
+ enabled,
32
+ gateIndex: enabled ? gateIndex : 0,
33
+ gatePct: enabled ? gatePct : 0,
34
+ buckets: ROLLOUT_BUCKETS,
35
+ bucketCount: enabled ? gatePct * 100 : 0,
36
+ events: 0,
37
+ sessions: 0,
38
+ promotionBlocked: false,
39
+ updatedAt: new Date().toISOString(),
40
+ };
41
+ sendJson(res, 200, body);
42
+ return true;
43
+ }
@@ -23,3 +23,4 @@ export { handleVectorCortexResidual } from "./routes-vector-cortex-residual.js";
23
23
  export { handleVectorCortexReconstruct } from "./routes-vector-cortex-reconstruct.js";
24
24
  export { handleVectorCortexPlans } from "./routes-vector-cortex-plans.js";
25
25
  export { handleVectorCortexRender } from "./routes-vector-cortex-render.js";
26
+ export { handleVectorCortexRollout } from "./routes-vector-cortex-rollout.js";
@@ -23,4 +23,4 @@ export { handleRagSettings } from "./routes-rag-settings.js";
23
23
  export { handleRagMetrics } from "./routes-rag-metrics.js";
24
24
  export { handleModelThresholds } from "./routes-model-thresholds.js";
25
25
  export { handleWiki } from "./routes-wiki.js";
26
- export { handleVectorCortexEvaluation, handleVectorCortexHealth, handleVectorCortexBreakersReset, handleVectorCortexLedger, handleVectorCortexTopology, handleVectorCortexQuery, handleVectorCortexShards, handleVectorCortexResidual, handleVectorCortexReconstruct, handleVectorCortexPlans, handleVectorCortexRender, } from "./routes-vector-cortex.js";
26
+ export { handleVectorCortexEvaluation, handleVectorCortexHealth, handleVectorCortexBreakersReset, handleVectorCortexLedger, handleVectorCortexTopology, handleVectorCortexQuery, handleVectorCortexShards, handleVectorCortexResidual, handleVectorCortexReconstruct, handleVectorCortexPlans, handleVectorCortexRender, handleVectorCortexRollout, } from "./routes-vector-cortex.js";
@@ -14,7 +14,7 @@ import { createRequire } from "node:module";
14
14
  import { NEW_UI } from "../../src/config.js";
15
15
  import { log, setLogPath, setDashboardServerVersion } from "./state.js";
16
16
  import { detectTailscaleIP } from "./tailscale.js";
17
- import { buildRouteContext, handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGameScores, handlePerf, handlePerfSamples, handleAchievements, handleSessions, handleTopics, handleTurns, handleMaintenance, handleProviderCache, handleCacheStripes, handleMemoryStatus, handleSetupStatus, handleSetupDetect, handleSetupConfigure, handleMemoryMap, handleRaptorTree, handleRaptorBuildHistory, handleContextHealth, handleCachePoison, handleHealthSettings, handleEmbedderHealth, handleRagSettings, handleRagMetrics, handleModelThresholds, handleWiki, handleVectorCortexEvaluation, handleVectorCortexHealth, handleVectorCortexBreakersReset, handleVectorCortexLedger, handleVectorCortexTopology, handleVectorCortexQuery, handleVectorCortexShards, handleVectorCortexResidual, handleVectorCortexReconstruct, handleVectorCortexPlans, handleVectorCortexRender, handleStatic, } from "./routes.js";
17
+ import { buildRouteContext, handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGameScores, handlePerf, handlePerfSamples, handleAchievements, handleSessions, handleTopics, handleTurns, handleMaintenance, handleProviderCache, handleCacheStripes, handleMemoryStatus, handleSetupStatus, handleSetupDetect, handleSetupConfigure, handleMemoryMap, handleRaptorTree, handleRaptorBuildHistory, handleContextHealth, handleCachePoison, handleHealthSettings, handleEmbedderHealth, handleRagSettings, handleRagMetrics, handleModelThresholds, handleWiki, handleVectorCortexEvaluation, handleVectorCortexHealth, handleVectorCortexBreakersReset, handleVectorCortexLedger, handleVectorCortexTopology, handleVectorCortexQuery, handleVectorCortexShards, handleVectorCortexResidual, handleVectorCortexReconstruct, handleVectorCortexPlans, handleVectorCortexRender, handleVectorCortexRollout, handleStatic, } from "./routes.js";
18
18
  export async function launchDashboardServer(stateDir) {
19
19
  // Our own package version — exposed at /api/version so the launcher can
20
20
  // detect a stale server (started by an older build) and replace it on
@@ -279,6 +279,8 @@ export async function launchDashboardServer(stateDir) {
279
279
  return;
280
280
  if (handleVectorCortexRender(req, res, ctx))
281
281
  return;
282
+ if (handleVectorCortexRollout(req, res, ctx))
283
+ return;
282
284
  handleStatic(req, res, ctx);
283
285
  });
284
286
  // Bind base + range are env-configurable so tests can use a private,
@@ -0,0 +1,105 @@
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
+ import { assignSession, bucketInGate, gatePctForIndex } from "../../src/vector-cortex/rollout/assign.js";
27
+ import { decideGate, selectsPreVcPath, } from "../../src/vector-cortex/rollout/gate.js";
28
+ import { createRolloutReporter } from "../../src/vector-cortex/rollout/emit.js";
29
+ import { VC5C_ENABLED } from "../../src/config/vector-cortex.js";
30
+ const NO_PREPEND = "";
31
+ /**
32
+ * Decide the live path for a session epoch. Pure over injected context; no
33
+ * I/O, no network. Flag OFF (or absent evidence/clock) degrades to mode C with
34
+ * the VC path inactive (pre-VC predecessor parity).
35
+ */
36
+ export function decideLivePath(sessionId, ctx) {
37
+ const reporter = createRolloutReporter(ctx.emit);
38
+ const assignment = assignSession(sessionId);
39
+ if (!VC5C_ENABLED()) {
40
+ // Pre-VC predecessor: no rollout, no VC path. Never emit role:system.
41
+ return {
42
+ vcActive: false,
43
+ forcedPreVc: false,
44
+ mode: "C",
45
+ bucket: assignment.bucket,
46
+ gateIndex: 0,
47
+ promotionBlocked: false,
48
+ systemPromptPrepend: NO_PREPEND,
49
+ };
50
+ }
51
+ reporter.assigned({
52
+ sessionId,
53
+ bucket: assignment.bucket,
54
+ gateIndex: assignment.gateIndex,
55
+ gatePct: gatePctForIndex(assignment.gateIndex),
56
+ });
57
+ const hardFaults = ctx.hardFaults ?? [];
58
+ const hardFault = hardFaults.length > 0 ? hardFaults[0] : undefined;
59
+ const forcedPreVc = selectsPreVcPath(hardFault);
60
+ if (forcedPreVc && hardFault) {
61
+ reporter.promotionBlocked({
62
+ gateIndex: ctx.currentGate ?? assignment.gateIndex,
63
+ kind: hardFault.kind,
64
+ detail: hardFault.detail,
65
+ });
66
+ // Hard failure → select C (pre-VC path) and freeze promotion.
67
+ return {
68
+ vcActive: false,
69
+ forcedPreVc: true,
70
+ mode: "C",
71
+ bucket: assignment.bucket,
72
+ gateIndex: ctx.currentGate ?? assignment.gateIndex,
73
+ promotionBlocked: true,
74
+ systemPromptPrepend: NO_PREPEND,
75
+ };
76
+ }
77
+ const clock = ctx.clock;
78
+ const evidence = ctx.evidence;
79
+ if (clock === undefined || evidence === undefined) {
80
+ // No live evidence/clock → pre-VC parity (no promotion, no VC exposure).
81
+ return {
82
+ vcActive: false,
83
+ forcedPreVc: false,
84
+ mode: "C",
85
+ bucket: assignment.bucket,
86
+ gateIndex: assignment.gateIndex,
87
+ promotionBlocked: false,
88
+ systemPromptPrepend: NO_PREPEND,
89
+ };
90
+ }
91
+ const outcome = decideGate(ctx.currentGate ?? assignment.gateIndex, evidence, clock);
92
+ const vcActive = bucketInGate(assignment.bucket, outcome.gatePct);
93
+ // PREVENT-PI-003: VC prompt context is composed into the host prepend seam,
94
+ // never as a role:system message. Empty prepend here (the actual prompt text
95
+ // is owned by the renderer seam); this seam only signals eligibility.
96
+ return {
97
+ vcActive,
98
+ forcedPreVc: false,
99
+ mode: vcActive ? "A" : "C",
100
+ bucket: assignment.bucket,
101
+ gateIndex: outcome.gateIndex,
102
+ promotionBlocked: outcome.promotionBlocked,
103
+ systemPromptPrepend: NO_PREPEND,
104
+ };
105
+ }
@@ -184,6 +184,18 @@ export const VC5A_ENABLED = () => sprintFlag("MEGACOMPACT_VC5A");
184
184
  * in EXCLUDED_SETTINGS), mirroring VC4A/VC4B/VC4C/VC5A.
185
185
  */
186
186
  export const VC5B_ENABLED = () => sprintFlag("MEGACOMPACT_VC5B");
187
+ /**
188
+ * VC5C — live graduated rollout. Default ON. `MEGACOMPACT_VC5C=0` disables and
189
+ * is byte-identical to the predecessor (VC5B): no session is assigned a stable
190
+ * 10k bucket, no gate-advance decision runs, the `vector_cortex_rollout_assigned`
191
+ * / `vector_cortex_rollout_promotion_blocked` events are never emitted, and the
192
+ * prompt continues to be built by the predecessor VC5B renderer path (its goldens
193
+ * are unchanged). The assign/gate/emit functions are PURE — flag OFF gates the
194
+ * reporter + dashboard seam, never the arithmetic. This flag MUST also be a
195
+ * dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
196
+ * mirroring VC4A/VC4B/VC4C/VC5A/VC5B.
197
+ */
198
+ export const VC5C_ENABLED = () => sprintFlag("MEGACOMPACT_VC5C");
187
199
  // ---------------------------------------------------------------------------
188
200
  // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
189
201
  // Rolled numbers for one 60s window; VC0C consumes these at its breaker seam.
@@ -114,4 +114,4 @@ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
114
114
  // default ON, `=0`/`_DISABLED` off. Re-exported from src/config/vector-cortex.ts
115
115
  // so root consumers share one source of truth.
116
116
  // ---------------------------------------------------------------------------
117
- export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
117
+ export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
@@ -0,0 +1,55 @@
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
+ 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 rolloutFixture(id) {
38
+ return readFixture(id, "rollout");
39
+ }
40
+ /** Flag-pinned wrapper: VC5C gated by MEGACOMPACT_VC5C (defaults ON). */
41
+ export function withFlagsOn(fn) {
42
+ return () => {
43
+ const saved = process.env.MEGACOMPACT_VC5C;
44
+ process.env.MEGACOMPACT_VC5C = "1";
45
+ try {
46
+ fn();
47
+ }
48
+ finally {
49
+ if (saved === undefined)
50
+ delete process.env.MEGACOMPACT_VC5C;
51
+ else
52
+ process.env.MEGACOMPACT_VC5C = saved;
53
+ }
54
+ };
55
+ }
@@ -0,0 +1,8 @@
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
+ export { REPO_ROOT, V2, readManifest, rolloutFixture, withFlagsOn, } from "./_acceptance-fixture.js";
8
+ export { runRolloutScenario } from "./_acceptance-scenario.js";
@@ -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
+ ];