pi-mega-compact 0.20.2 → 0.20.3
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 +13 -0
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +1 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-plans.js +49 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex.js +1 -0
- package/dist/extensions/dashboard-server/routes.js +1 -1
- package/dist/extensions/dashboard-server/server.js +3 -1
- package/dist/src/config/vector-cortex.js +13 -0
- package/dist/src/config.js +1 -1
- package/dist/src/vector-cortex/planner/manifest.js +66 -0
- package/dist/src/vector-cortex/planner/portfolio.js +0 -0
- package/dist/src/vector-cortex/planner/types.js +41 -0
- package/dist/src/vector-cortex/prompt-dag/_acceptance-dag.js +347 -0
- package/dist/src/vector-cortex/prompt-dag/_acceptance-fixture.js +59 -0
- package/dist/src/vector-cortex/prompt-dag/_acceptance-helpers.js +17 -0
- package/dist/src/vector-cortex/prompt-dag/_acceptance-planner.js +153 -0
- package/dist/src/vector-cortex/prompt-dag/_acceptance-shuffle.js +24 -0
- package/dist/src/vector-cortex/prompt-dag/builder.js +171 -0
- package/dist/src/vector-cortex/prompt-dag/types.js +33 -0
- package/dist/src/vector-cortex/prompt-dag/validator.js +0 -0
- package/dist/vector-cortex/planner/manifest.js +66 -0
- package/dist/vector-cortex/planner/portfolio.js +0 -0
- package/dist/vector-cortex/planner/types.js +41 -0
- package/dist/vector-cortex/prompt-dag/_acceptance-dag.js +347 -0
- package/dist/vector-cortex/prompt-dag/_acceptance-fixture.js +59 -0
- package/dist/vector-cortex/prompt-dag/_acceptance-helpers.js +17 -0
- package/dist/vector-cortex/prompt-dag/_acceptance-planner.js +153 -0
- package/dist/vector-cortex/prompt-dag/_acceptance-shuffle.js +24 -0
- package/dist/vector-cortex/prompt-dag/builder.js +171 -0
- package/dist/vector-cortex/prompt-dag/types.js +33 -0
- package/dist/vector-cortex/prompt-dag/validator.js +0 -0
- package/extensions/dashboard-client/dist/assets/{AreaChart-XF3jPpvn.js → AreaChart-Cam9-nhT.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-XF3jPpvn.js.map → AreaChart-Cam9-nhT.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-DnYYn0GF.js → BarChart-DzIY8aM1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-DnYYn0GF.js.map → BarChart-DzIY8aM1.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-D3bVskCL.js → CacheTab-CKIiYsG_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-D3bVskCL.js.map → CacheTab-CKIiYsG_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-CX_8myfc.js → EventsTab-xaaBJLiK.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-CX_8myfc.js.map → EventsTab-xaaBJLiK.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-CnVzBQvN.js → HealthTab-BGtUKOqG.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-CnVzBQvN.js.map → HealthTab-BGtUKOqG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-Cao--dtV.js → MaintenanceTab-ABgQnTZ9.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-Cao--dtV.js.map → MaintenanceTab-ABgQnTZ9.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DZdI-9ig.js → MemoryMapTab-ChR2GAN-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DZdI-9ig.js.map → MemoryMapTab-ChR2GAN-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-SlpSmNU4.js → MetricsTab-DdCLAgcR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-SlpSmNU4.js.map → MetricsTab-DdCLAgcR.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-C_-_4au9.js → OverviewTab-DtemzJt6.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-C_-_4au9.js.map → OverviewTab-DtemzJt6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-CyWedWZC.js → ReposTab-B9mvD9Ep.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-CyWedWZC.js.map → ReposTab-B9mvD9Ep.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-wqPBMIJZ.js → SessionsTab-BcO38XLS.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-wqPBMIJZ.js.map → SessionsTab-BcO38XLS.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-MojXmuUf.js → SetupTab-D_TBNP09.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-MojXmuUf.js.map → SetupTab-D_TBNP09.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BxcplEOJ.js → TimeSavedCard-TjHpbUOo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BxcplEOJ.js.map → TimeSavedCard-TjHpbUOo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-D8_qWbjY.js → TurnsTab-DprxndCF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-D8_qWbjY.js.map → TurnsTab-DprxndCF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-Bym21yVQ.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-Bym21yVQ.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-Cfd-QZYz.js → WikiTab-OKgSo15G.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-Cfd-QZYz.js.map → WikiTab-OKgSo15G.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-tX5ht0bb.js → button-D_Qz4q7L.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-tX5ht0bb.js.map → button-D_Qz4q7L.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-BT3Z5dDg.js → card-CsI6Jm3p.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-BT3Z5dDg.js.map → card-CsI6Jm3p.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-B6p2WnkY.js → generateCategoricalChart-CvHKUdhX.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-B6p2WnkY.js.map → generateCategoricalChart-CvHKUdhX.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-CFLekP12.js → index-0Ye_un22.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-CFLekP12.js.map → index-0Ye_un22.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{switch-B52oAMSw.js → switch-VQEoJl8Q.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-B52oAMSw.js.map → switch-VQEoJl8Q.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-VxxVc5Tp.js → toggle-CAPm4HJo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-VxxVc5Tp.js.map → toggle-CAPm4HJo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-DAVdZnG7.js → useSSE-pxsNTVkh.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-DAVdZnG7.js.map → useSSE-pxsNTVkh.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +35 -0
- package/extensions/dashboard-client/src/types/vector-cortex.ts +20 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex.ts +38 -0
- package/extensions/dashboard-server/routes-rag-settings-helpers.ts +6 -0
- package/extensions/dashboard-server/routes-vector-cortex-plans.ts +58 -0
- package/extensions/dashboard-server/routes-vector-cortex.ts +1 -0
- package/extensions/dashboard-server/routes.ts +1 -0
- package/extensions/dashboard-server/server.ts +2 -0
- package/package.json +1 -1
- package/src/config/vector-cortex.ts +14 -0
- package/src/config.ts +1 -0
- package/src/vector-cortex/planner/manifest.ts +83 -0
- package/src/vector-cortex/planner/portfolio.ts +0 -0
- package/src/vector-cortex/planner/types.ts +190 -0
- package/src/vector-cortex/prompt-dag/_acceptance-dag.ts +392 -0
- package/src/vector-cortex/prompt-dag/_acceptance-fixture.ts +132 -0
- package/src/vector-cortex/prompt-dag/_acceptance-helpers.ts +39 -0
- package/src/vector-cortex/prompt-dag/_acceptance-planner.ts +188 -0
- package/src/vector-cortex/prompt-dag/_acceptance-shuffle.ts +26 -0
- package/src/vector-cortex/prompt-dag/builder.ts +189 -0
- package/src/vector-cortex/prompt-dag/types.ts +151 -0
- package/src/vector-cortex/prompt-dag/validator.ts +0 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-B3yisAyB.js +0 -2
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-B3yisAyB.js.map +0 -1
|
@@ -159,6 +159,19 @@ export const VC4B_ENABLED = () => sprintFlag("MEGACOMPACT_VC4B");
|
|
|
159
159
|
* never in EXCLUDED_SETTINGS), mirroring VC4A/VC4B.
|
|
160
160
|
*/
|
|
161
161
|
export const VC4C_ENABLED = () => sprintFlag("MEGACOMPACT_VC4C");
|
|
162
|
+
/**
|
|
163
|
+
* VC5A — PromptDagV1 + budgeted portfolio planner. Default ON.
|
|
164
|
+
* `MEGACOMPACT_VC5A=0` disables and is byte-identical to the predecessor
|
|
165
|
+
* (VC4C): no prompt DAG is built or validated, no budgeted plan is selected,
|
|
166
|
+
* the `vector_cortex_plan_selected` / `vector_cortex_plan_mandatory_overflow`
|
|
167
|
+
* events are never emitted, and the prompt continues to be built by the
|
|
168
|
+
* predecessor VC4C closure/reconstruction path (its goldens are unchanged).
|
|
169
|
+
* The builder/validator/portfolio functions are PURE — flag OFF gates the
|
|
170
|
+
* reporter + dashboard seam, never the arithmetic. This flag MUST also be a
|
|
171
|
+
* dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
|
|
172
|
+
* mirroring VC4A/VC4B/VC4C.
|
|
173
|
+
*/
|
|
174
|
+
export const VC5A_ENABLED = () => sprintFlag("MEGACOMPACT_VC5A");
|
|
162
175
|
// ---------------------------------------------------------------------------
|
|
163
176
|
// Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
|
|
164
177
|
// 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, 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, 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";
|
|
@@ -149,6 +149,7 @@ export const SETTINGS = [
|
|
|
149
149
|
boolDirect("MEGACOMPACT_VC4A", "VC4A Dual-Tier Shards", "SemanticShardV1/ExactShardV1/ShardManifestV1: partition a session ONLY at complete EventV2 boundaries; exact shards preserve every tool call/result pair, anchor and invalid UTF-8 event as original bytes (pairs never split across exact shards); manifest enforces disjoint sorted ranges + complete protected-span coverage. OFF = mode C, exact anchors/current transcript only, byte-identical predecessor.", true),
|
|
150
150
|
boolDirect("MEGACOMPACT_VC4B", "VC4B Residual Basis Parity", "Residual codec: orthonormal DCT-II basis + int16 block quantization + block-scoped exact correction stream + (9,6) Reed-Solomon parity shards with SHA-256 corruption detection; admission gates on encodedSize <= 95% of exact-compressed size. OFF = mode C, no residual artifact produced, byte-identical predecessor.", true),
|
|
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
|
+
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),
|
|
152
153
|
],
|
|
153
154
|
},
|
|
154
155
|
{
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-server/routes-vector-cortex-plans.ts — VC5A PromptDagV1 + budgeted
|
|
3
|
+
* planner dashboard route.
|
|
4
|
+
*
|
|
5
|
+
* Reader-only GET /api/vector-cortex/plans returning ONLY plan manifests — the
|
|
6
|
+
* registered PromptDagV1 (DAG-001..030) and budgeted-planner (PLN-001..020)
|
|
7
|
+
* identifier counts, plus a reader-only plans array. NEVER exposes session
|
|
8
|
+
* payloads, prompt text, byte spans, or source bytes (reader-only,
|
|
9
|
+
* SECURITY_PRIVACY). Flag-gated on MEGACOMPACT_VC5A: `enabled:false` when off
|
|
10
|
+
* (byte-identical to the pre-VC5A predecessor).
|
|
11
|
+
*
|
|
12
|
+
* The VC5A planner is PURE IN-MEMORY logic in this sprint (it has no durable plan
|
|
13
|
+
* store yet), so the per-run plan outputs are not persisted. The route reports
|
|
14
|
+
* the registered manifest layout truthfully: `enabled` reflects the flag, the
|
|
15
|
+
* dag/planner counts come from the registered conformance ID range, and `plans`
|
|
16
|
+
* is empty until a future sprint persists selected plans. Non-fatal: an internal
|
|
17
|
+
* error degrades to `enabled:false` with empty arrays.
|
|
18
|
+
*
|
|
19
|
+
* Guardrails: PREVENT-PI-004 (local filesystem / in-process state only),
|
|
20
|
+
* PREVENT-011 (no `any`), reader-only aggregate (counts + manifests only).
|
|
21
|
+
*/
|
|
22
|
+
import { VC5A_ENABLED } from "../../src/config.js";
|
|
23
|
+
import { DAG_IDS } from "../../src/vector-cortex/prompt-dag/types.js";
|
|
24
|
+
import { PLN_IDS } from "../../src/vector-cortex/planner/types.js";
|
|
25
|
+
import { sendJson } from "./routes-vector-cortex-shared.js";
|
|
26
|
+
/**
|
|
27
|
+
* Reader-only GET /api/vector-cortex/plans (VC5A).
|
|
28
|
+
*/
|
|
29
|
+
export function handleVectorCortexPlans(req, res, _ctx) {
|
|
30
|
+
const url = req.url ?? "";
|
|
31
|
+
const path = url.split("?")[0] ?? url;
|
|
32
|
+
if (path !== "/api/vector-cortex/plans")
|
|
33
|
+
return false;
|
|
34
|
+
if (req.method !== "GET") {
|
|
35
|
+
// Reader-only path: cannot be "off" without a GET; it is genuinely read-only.
|
|
36
|
+
sendJson(res, 405, { error: "method_not_allowed" });
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
const enabled = VC5A_ENABLED();
|
|
40
|
+
const body = {
|
|
41
|
+
enabled,
|
|
42
|
+
dagCount: enabled ? DAG_IDS.length : 0,
|
|
43
|
+
plannerCount: enabled ? PLN_IDS.length : 0,
|
|
44
|
+
plans: [],
|
|
45
|
+
updatedAt: new Date().toISOString(),
|
|
46
|
+
};
|
|
47
|
+
sendJson(res, 200, body);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
@@ -21,3 +21,4 @@ export { handleVectorCortexQuery } from "./routes-vector-cortex-query.js";
|
|
|
21
21
|
export { handleVectorCortexShards } from "./routes-vector-cortex-shards.js";
|
|
22
22
|
export { handleVectorCortexResidual } from "./routes-vector-cortex-residual.js";
|
|
23
23
|
export { handleVectorCortexReconstruct } from "./routes-vector-cortex-reconstruct.js";
|
|
24
|
+
export { handleVectorCortexPlans } from "./routes-vector-cortex-plans.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, } from "./routes-vector-cortex.js";
|
|
26
|
+
export { handleVectorCortexEvaluation, handleVectorCortexHealth, handleVectorCortexBreakersReset, handleVectorCortexLedger, handleVectorCortexTopology, handleVectorCortexQuery, handleVectorCortexShards, handleVectorCortexResidual, handleVectorCortexReconstruct, handleVectorCortexPlans, } 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, 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, 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
|
|
@@ -273,6 +273,8 @@ export async function launchDashboardServer(stateDir) {
|
|
|
273
273
|
return;
|
|
274
274
|
if (handleVectorCortexReconstruct(req, res, ctx))
|
|
275
275
|
return;
|
|
276
|
+
if (handleVectorCortexPlans(req, res, ctx))
|
|
277
|
+
return;
|
|
276
278
|
handleStatic(req, res, ctx);
|
|
277
279
|
});
|
|
278
280
|
// Bind base + range are env-configurable so tests can use a private,
|
|
@@ -159,6 +159,19 @@ export const VC4B_ENABLED = () => sprintFlag("MEGACOMPACT_VC4B");
|
|
|
159
159
|
* never in EXCLUDED_SETTINGS), mirroring VC4A/VC4B.
|
|
160
160
|
*/
|
|
161
161
|
export const VC4C_ENABLED = () => sprintFlag("MEGACOMPACT_VC4C");
|
|
162
|
+
/**
|
|
163
|
+
* VC5A — PromptDagV1 + budgeted portfolio planner. Default ON.
|
|
164
|
+
* `MEGACOMPACT_VC5A=0` disables and is byte-identical to the predecessor
|
|
165
|
+
* (VC4C): no prompt DAG is built or validated, no budgeted plan is selected,
|
|
166
|
+
* the `vector_cortex_plan_selected` / `vector_cortex_plan_mandatory_overflow`
|
|
167
|
+
* events are never emitted, and the prompt continues to be built by the
|
|
168
|
+
* predecessor VC4C closure/reconstruction path (its goldens are unchanged).
|
|
169
|
+
* The builder/validator/portfolio functions are PURE — flag OFF gates the
|
|
170
|
+
* reporter + dashboard seam, never the arithmetic. This flag MUST also be a
|
|
171
|
+
* dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
|
|
172
|
+
* mirroring VC4A/VC4B/VC4C.
|
|
173
|
+
*/
|
|
174
|
+
export const VC5A_ENABLED = () => sprintFlag("MEGACOMPACT_VC5A");
|
|
162
175
|
// ---------------------------------------------------------------------------
|
|
163
176
|
// Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
|
|
164
177
|
// Rolled numbers for one 60s window; VC0C consumes these at its breaker seam.
|
package/dist/src/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, 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, 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,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/planner/manifest.ts — plan manifest identity + pre-provider
|
|
3
|
+
* revalidation (VC5A).
|
|
4
|
+
*
|
|
5
|
+
* Split from `portfolio.ts` (which owns SELECTION) so each file keeps one
|
|
6
|
+
* concern and stays under the 300-line soft limit: this module owns the plan's
|
|
7
|
+
* IDENTITY and the last gate before a provider call.
|
|
8
|
+
*
|
|
9
|
+
* The manifest digest deliberately covers PER-NODE TOKEN COUNTS, which the DAG
|
|
10
|
+
* digest does not: token counts are a planner input rather than DAG structure.
|
|
11
|
+
* That is exactly what makes the sprint's unique failure injection detectable —
|
|
12
|
+
* mutating a node's token count after planning but before validation changes the
|
|
13
|
+
* recomputed manifest digest, so `validatePlanManifest` returns
|
|
14
|
+
* `PLN_MANIFEST_DIGEST_MISMATCH` and the plan never reaches the provider.
|
|
15
|
+
*
|
|
16
|
+
* Pure/deterministic: no storage, no console, no network (PREVENT-PI-004).
|
|
17
|
+
*/
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
/**
|
|
20
|
+
* Deterministic digest over a plan AND the token counts/utilities it was
|
|
21
|
+
* selected with. Hashing walks `selectedNodeIds` in its stored (sorted) order,
|
|
22
|
+
* so the digest is a pure function of the plan and the candidate facts.
|
|
23
|
+
*
|
|
24
|
+
* A node missing from `candidates` hashes as `-1`, so dropping a candidate is
|
|
25
|
+
* itself a detectable mutation rather than a silently-skipped field.
|
|
26
|
+
*/
|
|
27
|
+
export function planManifestDigest(plan, candidates) {
|
|
28
|
+
const h = createHash("sha256");
|
|
29
|
+
h.update(plan.schema);
|
|
30
|
+
h.update(" ");
|
|
31
|
+
h.update(plan.dagDigest);
|
|
32
|
+
h.update(" ");
|
|
33
|
+
h.update(String(plan.tokenBudget));
|
|
34
|
+
h.update(" ");
|
|
35
|
+
h.update(String(plan.tokenTotal));
|
|
36
|
+
h.update(" ");
|
|
37
|
+
h.update(String(plan.dependencyHighWater));
|
|
38
|
+
const byId = new Map(candidates.map((c) => [c.nodeId, c]));
|
|
39
|
+
for (const id of plan.selectedNodeIds) {
|
|
40
|
+
const c = byId.get(id);
|
|
41
|
+
h.update("");
|
|
42
|
+
h.update(id);
|
|
43
|
+
h.update(" ");
|
|
44
|
+
// Token count is part of the identity — a post-plan mutation breaks it.
|
|
45
|
+
h.update(String(c?.tokenEstimate ?? -1));
|
|
46
|
+
h.update(" ");
|
|
47
|
+
h.update(String(c?.utility ?? -1));
|
|
48
|
+
}
|
|
49
|
+
return h.digest("hex");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Re-validate a plan against the candidates AS THEY STAND NOW, immediately
|
|
53
|
+
* before a provider call. Returns `PLN_MANIFEST_DIGEST_MISMATCH` when the
|
|
54
|
+
* recomputed manifest digest disagrees with the digest pinned at planning time.
|
|
55
|
+
*
|
|
56
|
+
* This is the sprint's unique failure injection: a node token count mutated
|
|
57
|
+
* after planning but before validation is caught here, BEFORE the provider is
|
|
58
|
+
* invoked, rather than producing a prompt whose real cost differs from the
|
|
59
|
+
* admitted budget.
|
|
60
|
+
*/
|
|
61
|
+
export function validatePlanManifest(plan, candidates, pinnedDigest) {
|
|
62
|
+
if (planManifestDigest(plan, candidates) !== pinnedDigest) {
|
|
63
|
+
return { ok: false, code: "PLN_MANIFEST_DIGEST_MISMATCH" };
|
|
64
|
+
}
|
|
65
|
+
return { ok: true };
|
|
66
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/planner/types.ts — `PlanV1` + the budget admission contract
|
|
3
|
+
* (VC5A, task 1).
|
|
4
|
+
*
|
|
5
|
+
* VC5A EXCLUSIVELY OWNS FRAMING + BUDGET ADMISSION (CONTRACTS §plan and
|
|
6
|
+
* closure). VC4C hands over `ClosureResult.mandatoryTokenEstimate`, a
|
|
7
|
+
* CONTENT-ONLY count with no prompt framing, no role tags and no separators, and
|
|
8
|
+
* VC4C never truncates a mandatory node nor reasons about a budget. This module
|
|
9
|
+
* adds the framing cost on top of that content estimate and decides admission:
|
|
10
|
+
*
|
|
11
|
+
* framed(node) = tokenEstimate + framingPerNode
|
|
12
|
+
* mandatoryCost = mandatoryTokenEstimate + framingPerNode * |mandatory|
|
|
13
|
+
* + framingOverhead
|
|
14
|
+
*
|
|
15
|
+
* If `mandatoryCost > tokenBudget` the planner returns
|
|
16
|
+
* `MANDATORY_CLOSURE_OVER_BUDGET` and the adapter demotes to C. Crucially it
|
|
17
|
+
* does so WITHOUT DROPPING EVIDENCE: the mandatory set is returned intact on the
|
|
18
|
+
* failure so the caller can report exactly what did not fit (the sprint bar:
|
|
19
|
+
* "over-budget mandatory closure never drops evidence and always demotes").
|
|
20
|
+
*
|
|
21
|
+
* The framing constants are CONFIGURABLE, never invented magic numbers: a caller
|
|
22
|
+
* supplies the profile it is actually rendering with, and the defaults below are
|
|
23
|
+
* documented as a conservative baseline rather than a measured provider fact.
|
|
24
|
+
*
|
|
25
|
+
* Pure types + registered conformance IDs: no storage, no console, no network
|
|
26
|
+
* (PREVENT-PI-004 / PREVENT-011).
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Conservative default framing baseline. Documented as a BASELINE, not a
|
|
30
|
+
* measured provider constant: a real provider profile (VC5B) overrides it. Kept
|
|
31
|
+
* small and explicit so a default-driven plan is never silently over-optimistic.
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_FRAMING = { perNode: 4, overhead: 8 };
|
|
34
|
+
/**
|
|
35
|
+
* Registered PLN conformance ID range (PLN-001..020). The acceptance aggregator
|
|
36
|
+
* reads these rows from the v2 manifest and asserts each returns its manifest
|
|
37
|
+
* `ok`/`code`.
|
|
38
|
+
*/
|
|
39
|
+
export const PLN_IDS = Array.from({ length: 20 }, (_v, i) => `PLN-${String(i + 1).padStart(3, "0")}`);
|
|
40
|
+
/** Named VC5A planner conformance assertions (the sprint's headline rows). */
|
|
41
|
+
export const PLAN_NAMED_IDS = ["PLN-MANDATORY-002", "PLN-TIE-003"];
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prompt-dag/_acceptance-dag.ts — declarative DAG materialization + scenario
|
|
3
|
+
* driver for VC5A acceptance rows.
|
|
4
|
+
*
|
|
5
|
+
* Turns named graph scenarios into real `PromptDagV1` values and drives them
|
|
6
|
+
* through the REAL build/validate logic. Deterministic helpers (eq/shuffle)
|
|
7
|
+
* live in `_acceptance-shuffle.ts`.
|
|
8
|
+
*/
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { buildPromptDag, compareNodes, compareEdges } from "./builder.js";
|
|
11
|
+
import { validatePromptDag, dagDigest } from "./validator.js";
|
|
12
|
+
import { eq, shuffle } from "./_acceptance-shuffle.js";
|
|
13
|
+
function span(sessionId, startSeq, endSeq, startByte, endByte, digest) {
|
|
14
|
+
return { sessionId, startSeq, endSeq, startByte, endByte, digest: `sha256:${digest}` };
|
|
15
|
+
}
|
|
16
|
+
function node(id, kind, opts = {}) {
|
|
17
|
+
return {
|
|
18
|
+
id,
|
|
19
|
+
kind,
|
|
20
|
+
payloadDigest: opts.payloadDigest ?? `pd:${id}`,
|
|
21
|
+
incompatibleWith: opts.incompatibleWith ?? [],
|
|
22
|
+
...(opts.span !== undefined ? { span: opts.span } : {}),
|
|
23
|
+
...(opts.syntheticOrdinal !== undefined ? { syntheticOrdinal: opts.syntheticOrdinal } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function edge(from, to, kind) {
|
|
27
|
+
return { from, to, kind };
|
|
28
|
+
}
|
|
29
|
+
const D0 = "a".repeat(64);
|
|
30
|
+
const D1 = "b".repeat(64);
|
|
31
|
+
/** Materialize a named graph into a built PromptDagV1 + a validator order. */
|
|
32
|
+
export function materializeDag(name) {
|
|
33
|
+
const cases = {
|
|
34
|
+
linear: {
|
|
35
|
+
sessionId: "s",
|
|
36
|
+
nodes: [
|
|
37
|
+
node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
38
|
+
node("b", "exact", { span: span("s", 2n, 2n, 3, 6, D0) }),
|
|
39
|
+
node("c", "semantic", { span: span("s", 3n, 3n, 6, 9, D0) }),
|
|
40
|
+
],
|
|
41
|
+
edges: [edge("a", "b", "depends"), edge("b", "c", "depends")],
|
|
42
|
+
},
|
|
43
|
+
"mixed-session": {
|
|
44
|
+
sessionId: "s",
|
|
45
|
+
nodes: [
|
|
46
|
+
node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
47
|
+
// A span claiming a foreign session — single-session violation.
|
|
48
|
+
node("b", "exact", { span: span("OTHER", 2n, 2n, 0, 3, D0) }),
|
|
49
|
+
],
|
|
50
|
+
edges: [],
|
|
51
|
+
},
|
|
52
|
+
"duplicate-id": {
|
|
53
|
+
sessionId: "s",
|
|
54
|
+
nodes: [
|
|
55
|
+
node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
56
|
+
node("a", "exact", { span: span("s", 2n, 2n, 0, 3, D1) }),
|
|
57
|
+
],
|
|
58
|
+
edges: [],
|
|
59
|
+
},
|
|
60
|
+
"missing-endpoint": {
|
|
61
|
+
sessionId: "s",
|
|
62
|
+
nodes: [node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) })],
|
|
63
|
+
edges: [edge("a", "ghost", "depends")],
|
|
64
|
+
},
|
|
65
|
+
"invalid-span": {
|
|
66
|
+
sessionId: "s",
|
|
67
|
+
nodes: [node("a", "event", { span: span("s", 3n, 1n, 9, 0, D0) })],
|
|
68
|
+
edges: [],
|
|
69
|
+
},
|
|
70
|
+
"synthetic-span": {
|
|
71
|
+
sessionId: "s",
|
|
72
|
+
nodes: [
|
|
73
|
+
node("a", "synthetic", { syntheticOrdinal: 0, span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
74
|
+
],
|
|
75
|
+
edges: [],
|
|
76
|
+
},
|
|
77
|
+
"digest-conflict": {
|
|
78
|
+
sessionId: "s",
|
|
79
|
+
nodes: [
|
|
80
|
+
node("a", "exact", { span: span("s", 1n, 1n, 0, 6, D0) }),
|
|
81
|
+
node("b", "exact", { span: span("s", 2n, 2n, 3, 9, D1) }),
|
|
82
|
+
],
|
|
83
|
+
edges: [],
|
|
84
|
+
},
|
|
85
|
+
"digest-agree": {
|
|
86
|
+
sessionId: "s",
|
|
87
|
+
nodes: [
|
|
88
|
+
node("a", "exact", { span: span("s", 1n, 1n, 0, 6, D0) }),
|
|
89
|
+
node("b", "exact", { span: span("s", 2n, 2n, 3, 9, D0) }),
|
|
90
|
+
],
|
|
91
|
+
edges: [],
|
|
92
|
+
},
|
|
93
|
+
"unknown-incompatible": {
|
|
94
|
+
sessionId: "s",
|
|
95
|
+
nodes: [
|
|
96
|
+
node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0), incompatibleWith: ["ghost"] }),
|
|
97
|
+
node("b", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
|
|
98
|
+
],
|
|
99
|
+
edges: [],
|
|
100
|
+
},
|
|
101
|
+
"tool-pair-split": {
|
|
102
|
+
sessionId: "s",
|
|
103
|
+
nodes: [node("toolcall", "event", { span: span("s", 1n, 1n, 0, 3, D0) })],
|
|
104
|
+
edges: [edge("toolcall", "toolresult", "tool-pair")],
|
|
105
|
+
},
|
|
106
|
+
cycle: {
|
|
107
|
+
sessionId: "s",
|
|
108
|
+
nodes: [
|
|
109
|
+
node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
110
|
+
node("b", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
|
|
111
|
+
node("c", "event", { span: span("s", 3n, 3n, 0, 3, D0) }),
|
|
112
|
+
],
|
|
113
|
+
edges: [edge("b", "a", "depends"), edge("c", "b", "depends"), edge("a", "c", "depends")],
|
|
114
|
+
},
|
|
115
|
+
selfloop: {
|
|
116
|
+
sessionId: "s",
|
|
117
|
+
nodes: [node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) })],
|
|
118
|
+
edges: [edge("a", "a", "depends")],
|
|
119
|
+
},
|
|
120
|
+
"id-tie": {
|
|
121
|
+
sessionId: "s",
|
|
122
|
+
nodes: [
|
|
123
|
+
node("aa", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
124
|
+
node("bb", "event", { span: span("s", 1n, 1n, 3, 6, D1) }),
|
|
125
|
+
node("cc", "event", { span: span("s", 1n, 1n, 6, 9, D0) }),
|
|
126
|
+
],
|
|
127
|
+
edges: [],
|
|
128
|
+
},
|
|
129
|
+
"seq-before-id": {
|
|
130
|
+
sessionId: "s",
|
|
131
|
+
// Earlier seq sorts first even though IDs sort the other way.
|
|
132
|
+
nodes: [
|
|
133
|
+
node("z", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
134
|
+
node("y", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
|
|
135
|
+
node("x", "event", { span: span("s", 3n, 3n, 0, 3, D0) }),
|
|
136
|
+
],
|
|
137
|
+
edges: [],
|
|
138
|
+
},
|
|
139
|
+
"synthetic-last": {
|
|
140
|
+
sessionId: "s",
|
|
141
|
+
nodes: [
|
|
142
|
+
node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
143
|
+
node("b", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
|
|
144
|
+
node("syn", "synthetic", { syntheticOrdinal: 0 }),
|
|
145
|
+
],
|
|
146
|
+
edges: [],
|
|
147
|
+
},
|
|
148
|
+
"synthetic-ordinal": {
|
|
149
|
+
sessionId: "s",
|
|
150
|
+
nodes: [
|
|
151
|
+
node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
152
|
+
node("s1", "synthetic", { syntheticOrdinal: 1 }),
|
|
153
|
+
node("s2", "synthetic", { syntheticOrdinal: 2 }),
|
|
154
|
+
],
|
|
155
|
+
edges: [],
|
|
156
|
+
},
|
|
157
|
+
diamond: {
|
|
158
|
+
sessionId: "s",
|
|
159
|
+
nodes: [
|
|
160
|
+
node("top", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
161
|
+
node("l", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
|
|
162
|
+
node("r", "event", { span: span("s", 3n, 3n, 0, 3, D0) }),
|
|
163
|
+
node("bottom", "event", { span: span("s", 4n, 4n, 0, 3, D1) }),
|
|
164
|
+
],
|
|
165
|
+
edges: [
|
|
166
|
+
edge("top", "l", "depends"),
|
|
167
|
+
edge("top", "r", "depends"),
|
|
168
|
+
edge("l", "bottom", "depends"),
|
|
169
|
+
edge("r", "bottom", "depends"),
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
contradicts: {
|
|
173
|
+
sessionId: "s",
|
|
174
|
+
nodes: [
|
|
175
|
+
node("a", "exact", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
176
|
+
node("b", "exact", { span: span("s", 2n, 2n, 0, 3, D1) }),
|
|
177
|
+
],
|
|
178
|
+
edges: [edge("a", "b", "contradicts")],
|
|
179
|
+
},
|
|
180
|
+
"contradicts-cycle": {
|
|
181
|
+
sessionId: "s",
|
|
182
|
+
nodes: [
|
|
183
|
+
node("a", "exact", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
184
|
+
node("b", "exact", { span: span("s", 2n, 2n, 0, 3, D1) }),
|
|
185
|
+
],
|
|
186
|
+
// A contradicts edge plus a depends edge: a real cycle would need the
|
|
187
|
+
// depends to form a loop, but here only `a->b` depends, so no cycle.
|
|
188
|
+
edges: [edge("a", "b", "contradicts"), edge("a", "b", "depends")],
|
|
189
|
+
},
|
|
190
|
+
"reversed-precedes": {
|
|
191
|
+
sessionId: "s",
|
|
192
|
+
nodes: [
|
|
193
|
+
node("a", "event", { span: span("s", 2n, 2n, 0, 3, D0) }),
|
|
194
|
+
node("b", "event", { span: span("s", 1n, 1n, 0, 3, D1) }),
|
|
195
|
+
],
|
|
196
|
+
// b's source position is EARLIER than a's, so a->b precedes is backward.
|
|
197
|
+
edges: [edge("a", "b", "precedes")],
|
|
198
|
+
},
|
|
199
|
+
scrambled: {
|
|
200
|
+
sessionId: "s",
|
|
201
|
+
nodes: [
|
|
202
|
+
node("c", "event", { span: span("s", 3n, 3n, 6, 9, D0) }),
|
|
203
|
+
node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
204
|
+
node("b", "event", { span: span("s", 2n, 2n, 3, 6, D1) }),
|
|
205
|
+
],
|
|
206
|
+
edges: [edge("a", "b", "depends"), edge("b", "c", "depends")],
|
|
207
|
+
},
|
|
208
|
+
empty: { sessionId: "s", nodes: [], edges: [] },
|
|
209
|
+
single: {
|
|
210
|
+
sessionId: "s",
|
|
211
|
+
nodes: [node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) })],
|
|
212
|
+
edges: [],
|
|
213
|
+
},
|
|
214
|
+
disconnected: {
|
|
215
|
+
sessionId: "s",
|
|
216
|
+
nodes: [
|
|
217
|
+
node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
218
|
+
node("b", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
|
|
219
|
+
node("x", "event", { span: span("s", 1n, 1n, 3, 6, D0) }),
|
|
220
|
+
node("y", "event", { span: span("s", 2n, 2n, 3, 6, D1) }),
|
|
221
|
+
],
|
|
222
|
+
edges: [],
|
|
223
|
+
},
|
|
224
|
+
toolpair: {
|
|
225
|
+
sessionId: "s",
|
|
226
|
+
nodes: [
|
|
227
|
+
node("toolcall", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
|
|
228
|
+
node("toolresult", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
|
|
229
|
+
],
|
|
230
|
+
edges: [edge("toolcall", "toolresult", "tool-pair")],
|
|
231
|
+
},
|
|
232
|
+
chain8: {
|
|
233
|
+
sessionId: "s",
|
|
234
|
+
nodes: [0, 1, 2, 3, 4, 5, 6, 7, 8].map((i) => node(`n${i}`, "event", { span: span("s", BigInt(i + 1), BigInt(i + 1), 0, 3, D0) })),
|
|
235
|
+
edges: [0, 1, 2, 3, 4, 5, 6, 7].map((i) => edge(`n${i + 1}`, `n${i}`, "depends")),
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
const g = cases[name];
|
|
239
|
+
assert.ok(g, `dag materializer missing for "${name}"`);
|
|
240
|
+
// Give every node a DISTINCT, non-overlapping byte window so the builder's
|
|
241
|
+
// digest-conflict rule (which only fires on genuinely shared bytes) does not
|
|
242
|
+
// misfire on adjacent ordering-test nodes. The two digest-* graphs intentionally
|
|
243
|
+
// KEEP overlapping spans to exercise that rule, so they are excluded here.
|
|
244
|
+
const overlapIntentional = name === "digest-conflict" || name === "digest-agree";
|
|
245
|
+
const nodes = overlapIntentional
|
|
246
|
+
? g.nodes
|
|
247
|
+
: g.nodes.map((n, i) => n.span === undefined
|
|
248
|
+
? n
|
|
249
|
+
: { ...n, span: { ...n.span, startByte: i * 4, endByte: i * 4 + 4 } });
|
|
250
|
+
const built = buildPromptDag({
|
|
251
|
+
sessionId: g.sessionId,
|
|
252
|
+
sourceHighWater: 100n,
|
|
253
|
+
nodes,
|
|
254
|
+
edges: g.edges,
|
|
255
|
+
});
|
|
256
|
+
return {
|
|
257
|
+
buildOk: built.ok,
|
|
258
|
+
buildCodes: built.ok ? [] : built.codes,
|
|
259
|
+
dag: built.ok
|
|
260
|
+
? built.dag
|
|
261
|
+
: {
|
|
262
|
+
schema: "prompt-dag-v1",
|
|
263
|
+
sessionId: g.sessionId,
|
|
264
|
+
sourceHighWater: 100n,
|
|
265
|
+
nodes: [...g.nodes].sort(compareNodes),
|
|
266
|
+
edges: [...g.edges].sort(compareEdges),
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/** Validate a materialized DAG, returning order / failure codes / digest. */
|
|
271
|
+
export function runDagScenario(fx) {
|
|
272
|
+
const { scenario, graph, mutateDigest } = fx.input;
|
|
273
|
+
const base = materializeDag(graph ?? "linear");
|
|
274
|
+
// Structural REJECTION scenarios stop at the builder (no validation). Build
|
|
275
|
+
// ACCEPTANCE scenarios (build-linear / build-digest-agree) succeed and must
|
|
276
|
+
// still be validated for their topological order.
|
|
277
|
+
const structuralReject = [
|
|
278
|
+
"build-mixed-session",
|
|
279
|
+
"build-duplicate-id",
|
|
280
|
+
"build-missing-endpoint",
|
|
281
|
+
"build-invalid-span",
|
|
282
|
+
"build-synthetic-with-span",
|
|
283
|
+
"build-digest-conflict",
|
|
284
|
+
"build-unknown-incompatible",
|
|
285
|
+
"build-tool-pair-split",
|
|
286
|
+
];
|
|
287
|
+
if (structuralReject.includes(scenario)) {
|
|
288
|
+
// Prefer the more specific tool-pair rejection when both it and a generic
|
|
289
|
+
// missing-endpoint code are present (a split tool pair is also missing an
|
|
290
|
+
// endpoint, but the tool-pair split is the precise failure).
|
|
291
|
+
const codes = base.buildCodes;
|
|
292
|
+
const code = codes.includes("DAG_TOOL_PAIR_SPLIT")
|
|
293
|
+
? "DAG_TOOL_PAIR_SPLIT"
|
|
294
|
+
: codes[0];
|
|
295
|
+
return { ok: base.buildOk, code };
|
|
296
|
+
}
|
|
297
|
+
// A built DAG is required for validation scenarios.
|
|
298
|
+
assert.ok(base.buildOk, `${fx.id}: builder must succeed before validation`);
|
|
299
|
+
const dag = base.dag;
|
|
300
|
+
const v = validatePromptDag(dag);
|
|
301
|
+
const digest = dagDigest(dag);
|
|
302
|
+
if (fx.expected.permutationInvariant) {
|
|
303
|
+
// Re-run with a shuffled node/edge array; order + digest must be identical.
|
|
304
|
+
const permNodes = shuffle([...dag.nodes]);
|
|
305
|
+
const permEdges = shuffle([...dag.edges]);
|
|
306
|
+
const perm = buildPromptDag({
|
|
307
|
+
sessionId: dag.sessionId,
|
|
308
|
+
sourceHighWater: dag.sourceHighWater,
|
|
309
|
+
nodes: permNodes,
|
|
310
|
+
edges: permEdges,
|
|
311
|
+
});
|
|
312
|
+
assert.ok(perm.ok, `${fx.id}: permuted build must succeed`);
|
|
313
|
+
const vPerm = validatePromptDag(perm.dag);
|
|
314
|
+
const orderSame = v.ok && vPerm.ok && eq(v.order, vPerm.order);
|
|
315
|
+
const digestSame = dagDigest(perm.dag) === digest;
|
|
316
|
+
return { ok: true, permutationInvariant: orderSame, digestStable: digestSame };
|
|
317
|
+
}
|
|
318
|
+
if (fx.expected.digestStable) {
|
|
319
|
+
const perm = buildPromptDag({
|
|
320
|
+
sessionId: dag.sessionId,
|
|
321
|
+
sourceHighWater: dag.sourceHighWater,
|
|
322
|
+
nodes: shuffle([...dag.nodes]),
|
|
323
|
+
edges: shuffle([...dag.edges]),
|
|
324
|
+
});
|
|
325
|
+
assert.ok(perm.ok);
|
|
326
|
+
return { ok: true, digestStable: dagDigest(perm.dag) === digest };
|
|
327
|
+
}
|
|
328
|
+
if (fx.expected.digestSensitive) {
|
|
329
|
+
const mutated = {
|
|
330
|
+
...dag,
|
|
331
|
+
nodes: dag.nodes.map((n, i) => i === 0 ? { ...n, payloadDigest: `pd:mutated-${n.id}` } : n),
|
|
332
|
+
};
|
|
333
|
+
return { ok: true, digestSensitive: dagDigest(mutated) !== digest };
|
|
334
|
+
}
|
|
335
|
+
if (fx.expected.orderIsTotal) {
|
|
336
|
+
const ok = v.ok && v.order.length === dag.nodes.length && new Set(v.order).size === dag.nodes.length;
|
|
337
|
+
return { ok, orderIsTotal: ok };
|
|
338
|
+
}
|
|
339
|
+
if (fx.expected.orderLength !== undefined) {
|
|
340
|
+
return { ok: v.ok, orderLength: v.ok ? v.order.length : 0 };
|
|
341
|
+
}
|
|
342
|
+
// Plain order / failure-code scenarios.
|
|
343
|
+
if (!v.ok)
|
|
344
|
+
return { ok: false, code: v.codes[0] };
|
|
345
|
+
void mutateDigest;
|
|
346
|
+
return { ok: true, order: [...v.order] };
|
|
347
|
+
}
|