pi-mega-compact 0.20.2 → 0.20.4
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 +29 -22
- 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 +31 -22
- 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
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
|
|
23
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
24
|
+
import type { RouteContext } from "./routes-core.js";
|
|
25
|
+
import { VC5A_ENABLED } from "../../src/config.js";
|
|
26
|
+
import { DAG_IDS } from "../../src/vector-cortex/prompt-dag/types.js";
|
|
27
|
+
import { PLN_IDS } from "../../src/vector-cortex/planner/types.js";
|
|
28
|
+
import { sendJson } from "./routes-vector-cortex-shared.js";
|
|
29
|
+
import type { VectorCortexPlansView } from "./api-contracts/vector-cortex.js";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Reader-only GET /api/vector-cortex/plans (VC5A).
|
|
33
|
+
*/
|
|
34
|
+
export function handleVectorCortexPlans(
|
|
35
|
+
req: IncomingMessage,
|
|
36
|
+
res: ServerResponse,
|
|
37
|
+
_ctx: RouteContext,
|
|
38
|
+
): boolean {
|
|
39
|
+
const url = req.url ?? "";
|
|
40
|
+
const path = url.split("?")[0] ?? url;
|
|
41
|
+
if (path !== "/api/vector-cortex/plans") return false;
|
|
42
|
+
if (req.method !== "GET") {
|
|
43
|
+
// Reader-only path: cannot be "off" without a GET; it is genuinely read-only.
|
|
44
|
+
sendJson(res, 405, { error: "method_not_allowed" });
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const enabled = VC5A_ENABLED();
|
|
49
|
+
const body: VectorCortexPlansView = {
|
|
50
|
+
enabled,
|
|
51
|
+
dagCount: enabled ? DAG_IDS.length : 0,
|
|
52
|
+
plannerCount: enabled ? PLN_IDS.length : 0,
|
|
53
|
+
plans: [],
|
|
54
|
+
updatedAt: new Date().toISOString(),
|
|
55
|
+
};
|
|
56
|
+
sendJson(res, 200, body);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
@@ -22,3 +22,4 @@ export { handleVectorCortexQuery } from "./routes-vector-cortex-query.js";
|
|
|
22
22
|
export { handleVectorCortexShards } from "./routes-vector-cortex-shards.js";
|
|
23
23
|
export { handleVectorCortexResidual } from "./routes-vector-cortex-residual.js";
|
|
24
24
|
export { handleVectorCortexReconstruct } from "./routes-vector-cortex-reconstruct.js";
|
|
25
|
+
export { handleVectorCortexPlans } from "./routes-vector-cortex-plans.js";
|
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
handleVectorCortexShards,
|
|
67
67
|
handleVectorCortexResidual,
|
|
68
68
|
handleVectorCortexReconstruct,
|
|
69
|
+
handleVectorCortexPlans,
|
|
69
70
|
handleStatic,
|
|
70
71
|
} from "./routes.js";
|
|
71
72
|
|
|
@@ -235,15 +236,16 @@ export async function launchDashboardServer(
|
|
|
235
236
|
detectCrossRepoDrift,
|
|
236
237
|
});
|
|
237
238
|
|
|
238
|
-
// Bind host resolution:
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
239
|
+
// Bind host resolution. Default: ALL interfaces (0.0.0.0) so the dashboard
|
|
240
|
+
// is reachable on loopback (localhost — the launcher's health check + the
|
|
241
|
+
// local browser), the tailnet (Tailscale), and any LAN interface. The
|
|
242
|
+
// network-denial gate was removed (the user opted into open access);
|
|
243
|
+
// MEGACOMPACT_DASHBOARD_HOST overrides for anyone who wants to restrict to
|
|
244
|
+
// a single interface. The tailnet IP is still detected so we can surface it
|
|
245
|
+
// as the remote access address. 0.0.0.0 opts into permissive CORS below.
|
|
246
|
+
const host = process.env.MEGACOMPACT_DASHBOARD_HOST ?? "0.0.0.0";
|
|
246
247
|
const allInterfaces = host === "0.0.0.0";
|
|
248
|
+
const tailscaleIP = allInterfaces ? detectTailscaleIP() : null;
|
|
247
249
|
|
|
248
250
|
// guardrails-allow PREVENT-PI-004: optional, user-triggered /dashboard server (tailnet-local via Tailscale mesh or loopback); no remote outbound calls.
|
|
249
251
|
const server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
@@ -310,6 +312,7 @@ export async function launchDashboardServer(
|
|
|
310
312
|
if (handleVectorCortexShards(req, res, ctx)) return;
|
|
311
313
|
if (handleVectorCortexResidual(req, res, ctx)) return;
|
|
312
314
|
if (handleVectorCortexReconstruct(req, res, ctx)) return;
|
|
315
|
+
if (handleVectorCortexPlans(req, res, ctx)) return;
|
|
313
316
|
handleStatic(req, res, ctx);
|
|
314
317
|
});
|
|
315
318
|
|
|
@@ -334,27 +337,33 @@ export async function launchDashboardServer(
|
|
|
334
337
|
});
|
|
335
338
|
|
|
336
339
|
server.listen(port, host, () => {
|
|
337
|
-
//
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
+
// Surface localhost for local reach (the launcher health-checks
|
|
341
|
+
// localhost + the local browser uses it), and — when bound to all
|
|
342
|
+
// interfaces with a tailnet present — also surface the tailnet
|
|
343
|
+
// address for remote devices on the mesh.
|
|
344
|
+
const localUrl = `http://localhost:${port}`; // guardrails-allow PREVENT-PI-004: dashboard URL (loopback/all-interfaces, user-opted open access)
|
|
345
|
+
const tailnetUrl =
|
|
346
|
+
allInterfaces && tailscaleIP ? `http://${tailscaleIP}:${port}` : null; // guardrails-allow PREVENT-PI-004: dashboard URL (tailnet, user-opted open access)
|
|
340
347
|
const url =
|
|
341
348
|
host === "127.0.0.1"
|
|
342
|
-
?
|
|
343
|
-
: `http://${host}:${port}`;
|
|
344
|
-
log("server running", { url, host });
|
|
349
|
+
? localUrl
|
|
350
|
+
: tailnetUrl ?? `http://${host}:${port}`;
|
|
351
|
+
log("server running", { url, host, tailnetUrl });
|
|
345
352
|
// eslint-disable-next-line no-console
|
|
346
|
-
console.log(
|
|
353
|
+
console.log(
|
|
354
|
+
`[mega-compact] dashboard server running: ${url}` +
|
|
355
|
+
(tailnetUrl ? ` (also ${localUrl})` : ""),
|
|
356
|
+
);
|
|
347
357
|
|
|
348
|
-
// v0.8.2: also bind the IPv6 loopback (::1) when
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
// bind ::1 — the tailnet address is what the user reaches. Non-fatal:
|
|
358
|
+
// v0.8.2: also bind the IPv6 loopback (::1) when localhost must work.
|
|
359
|
+
// On many systems `localhost` resolves to ::1 first (see /etc/hosts),
|
|
360
|
+
// so an IPv4-only bind makes the browser hit ::1:port and get refused.
|
|
361
|
+
// Bind ::1 for the explicit loopback bind AND the all-interfaces bind
|
|
362
|
+
// (a browser resolving localhost → ::1 must still connect). Non-fatal:
|
|
354
363
|
// IPv4-only hosts or a ::1 already in use just skip the mirror.
|
|
355
364
|
let v6: ReturnType<typeof createServer> | undefined;
|
|
356
365
|
const v4Handler = server.listeners("request")[0];
|
|
357
|
-
if (host === "127.0.0.1" && v4Handler) {
|
|
366
|
+
if ((host === "127.0.0.1" || allInterfaces) && v4Handler) {
|
|
358
367
|
v6 = createServer((r, s) =>
|
|
359
368
|
(v4Handler as (a: IncomingMessage, b: ServerResponse) => void).call(
|
|
360
369
|
server,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mega-compact",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.4",
|
|
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",
|
|
@@ -174,6 +174,20 @@ export const VC4B_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC4B");
|
|
|
174
174
|
*/
|
|
175
175
|
export const VC4C_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC4C");
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* VC5A — PromptDagV1 + budgeted portfolio planner. Default ON.
|
|
179
|
+
* `MEGACOMPACT_VC5A=0` disables and is byte-identical to the predecessor
|
|
180
|
+
* (VC4C): no prompt DAG is built or validated, no budgeted plan is selected,
|
|
181
|
+
* the `vector_cortex_plan_selected` / `vector_cortex_plan_mandatory_overflow`
|
|
182
|
+
* events are never emitted, and the prompt continues to be built by the
|
|
183
|
+
* predecessor VC4C closure/reconstruction path (its goldens are unchanged).
|
|
184
|
+
* The builder/validator/portfolio functions are PURE — flag OFF gates the
|
|
185
|
+
* reporter + dashboard seam, never the arithmetic. This flag MUST also be a
|
|
186
|
+
* dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
|
|
187
|
+
* mirroring VC4A/VC4B/VC4C.
|
|
188
|
+
*/
|
|
189
|
+
export const VC5A_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC5A");
|
|
190
|
+
|
|
177
191
|
// ---------------------------------------------------------------------------
|
|
178
192
|
// Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
|
|
179
193
|
// Rolled numbers for one 60s window; VC0C consumes these at its breaker seam.
|
package/src/config.ts
CHANGED
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
|
|
21
|
+
import type { PlanCandidate, PlanV1 } from "./types.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Deterministic digest over a plan AND the token counts/utilities it was
|
|
25
|
+
* selected with. Hashing walks `selectedNodeIds` in its stored (sorted) order,
|
|
26
|
+
* so the digest is a pure function of the plan and the candidate facts.
|
|
27
|
+
*
|
|
28
|
+
* A node missing from `candidates` hashes as `-1`, so dropping a candidate is
|
|
29
|
+
* itself a detectable mutation rather than a silently-skipped field.
|
|
30
|
+
*/
|
|
31
|
+
export function planManifestDigest(
|
|
32
|
+
plan: PlanV1,
|
|
33
|
+
candidates: readonly PlanCandidate[],
|
|
34
|
+
): string {
|
|
35
|
+
const h = createHash("sha256");
|
|
36
|
+
h.update(plan.schema);
|
|
37
|
+
h.update(" ");
|
|
38
|
+
h.update(plan.dagDigest);
|
|
39
|
+
h.update(" ");
|
|
40
|
+
h.update(String(plan.tokenBudget));
|
|
41
|
+
h.update(" ");
|
|
42
|
+
h.update(String(plan.tokenTotal));
|
|
43
|
+
h.update(" ");
|
|
44
|
+
h.update(String(plan.dependencyHighWater));
|
|
45
|
+
const byId = new Map(candidates.map((c) => [c.nodeId, c]));
|
|
46
|
+
for (const id of plan.selectedNodeIds) {
|
|
47
|
+
const c = byId.get(id);
|
|
48
|
+
h.update("");
|
|
49
|
+
h.update(id);
|
|
50
|
+
h.update(" ");
|
|
51
|
+
// Token count is part of the identity — a post-plan mutation breaks it.
|
|
52
|
+
h.update(String(c?.tokenEstimate ?? -1));
|
|
53
|
+
h.update(" ");
|
|
54
|
+
h.update(String(c?.utility ?? -1));
|
|
55
|
+
}
|
|
56
|
+
return h.digest("hex");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The verdict of the pre-provider manifest revalidation. */
|
|
60
|
+
export type PlanManifestValidation =
|
|
61
|
+
| { readonly ok: true }
|
|
62
|
+
| { readonly ok: false; readonly code: "PLN_MANIFEST_DIGEST_MISMATCH" };
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Re-validate a plan against the candidates AS THEY STAND NOW, immediately
|
|
66
|
+
* before a provider call. Returns `PLN_MANIFEST_DIGEST_MISMATCH` when the
|
|
67
|
+
* recomputed manifest digest disagrees with the digest pinned at planning time.
|
|
68
|
+
*
|
|
69
|
+
* This is the sprint's unique failure injection: a node token count mutated
|
|
70
|
+
* after planning but before validation is caught here, BEFORE the provider is
|
|
71
|
+
* invoked, rather than producing a prompt whose real cost differs from the
|
|
72
|
+
* admitted budget.
|
|
73
|
+
*/
|
|
74
|
+
export function validatePlanManifest(
|
|
75
|
+
plan: PlanV1,
|
|
76
|
+
candidates: readonly PlanCandidate[],
|
|
77
|
+
pinnedDigest: string,
|
|
78
|
+
): PlanManifestValidation {
|
|
79
|
+
if (planManifestDigest(plan, candidates) !== pinnedDigest) {
|
|
80
|
+
return { ok: false, code: "PLN_MANIFEST_DIGEST_MISMATCH" };
|
|
81
|
+
}
|
|
82
|
+
return { ok: true };
|
|
83
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,190 @@
|
|
|
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
|
+
/**
|
|
30
|
+
* The prompt framing cost model. These are the ONLY places framing enters the
|
|
31
|
+
* budget, keeping the VC4C content-only estimate cleanly separable.
|
|
32
|
+
*
|
|
33
|
+
* `perNode` is the per-node envelope (role tag + separator) a renderer adds
|
|
34
|
+
* around one node's content. `overhead` is the whole-prompt fixed cost (system
|
|
35
|
+
* preamble scaffolding, closing delimiters) charged once.
|
|
36
|
+
*
|
|
37
|
+
* Both DEFAULT to a conservative baseline and are overridable per call, so a
|
|
38
|
+
* provider profile with a measured framing cost (VC5B) supplies its own numbers
|
|
39
|
+
* rather than inheriting a guess.
|
|
40
|
+
*/
|
|
41
|
+
export interface FramingProfile {
|
|
42
|
+
/** Tokens added around each selected node (role tag + separator). */
|
|
43
|
+
readonly perNode: number;
|
|
44
|
+
/** Fixed whole-prompt framing tokens charged once. */
|
|
45
|
+
readonly overhead: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Conservative default framing baseline. Documented as a BASELINE, not a
|
|
50
|
+
* measured provider constant: a real provider profile (VC5B) overrides it. Kept
|
|
51
|
+
* small and explicit so a default-driven plan is never silently over-optimistic.
|
|
52
|
+
*/
|
|
53
|
+
export const DEFAULT_FRAMING: FramingProfile = { perNode: 4, overhead: 8 };
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* One candidate offered to the 0/1 portfolio. `mandatory` candidates are the
|
|
57
|
+
* closed set from VC4C and are admitted before any optional selection; optional
|
|
58
|
+
* candidates compete for the REMAINING budget.
|
|
59
|
+
*
|
|
60
|
+
* `sourceSeq` is the source ordering fact used as the FIRST tie-break after
|
|
61
|
+
* utility-per-token, so two equally efficient candidates resolve by source
|
|
62
|
+
* position (earlier wins) and then by ID bytes — a total, deterministic order.
|
|
63
|
+
*/
|
|
64
|
+
export interface PlanCandidate {
|
|
65
|
+
readonly nodeId: string;
|
|
66
|
+
/** CONTENT-ONLY token estimate (framing is added by the planner). */
|
|
67
|
+
readonly tokenEstimate: number;
|
|
68
|
+
/** Selection value; higher is better. Ratio is `utility / framedTokens`. */
|
|
69
|
+
readonly utility: number;
|
|
70
|
+
/** Source sequence position, the first tie-break after the ratio. */
|
|
71
|
+
readonly sourceSeq: bigint;
|
|
72
|
+
/** True when the candidate is part of the mandatory closure. */
|
|
73
|
+
readonly mandatory: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Why a candidate was left out of the plan (recorded, never silent). */
|
|
77
|
+
export interface PlanOmission {
|
|
78
|
+
readonly nodeId: string;
|
|
79
|
+
readonly reason: "over-budget" | "incompatible" | "zero-utility";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Planner failure codes (registered PLN codes). */
|
|
83
|
+
export type PlanFailureCode =
|
|
84
|
+
/**
|
|
85
|
+
* The mandatory closure alone (with framing) exceeds `tokenBudget`. Evidence
|
|
86
|
+
* is NOT dropped — the mandatory set is returned intact and the adapter
|
|
87
|
+
* demotes to C.
|
|
88
|
+
*/
|
|
89
|
+
| "MANDATORY_CLOSURE_OVER_BUDGET"
|
|
90
|
+
/** A candidate names a node absent from the DAG. */
|
|
91
|
+
| "PLN_UNKNOWN_NODE"
|
|
92
|
+
/** Two selected nodes declare mutual incompatibility. */
|
|
93
|
+
| "PLN_INCOMPATIBLE_SELECTION"
|
|
94
|
+
/** The DAG digest recorded in the plan no longer matches the DAG. */
|
|
95
|
+
| "PLN_MANIFEST_DIGEST_MISMATCH"
|
|
96
|
+
/** The budget itself is invalid (negative or non-finite). */
|
|
97
|
+
| "PLN_INVALID_BUDGET";
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The accepted plan (CONTRACTS §plan and closure). `selectedNodeIds` is sorted
|
|
101
|
+
* for a stable manifest; `tokenTotal` is the FRAMED total and is guaranteed
|
|
102
|
+
* `<= tokenBudget` for every accepted plan.
|
|
103
|
+
*/
|
|
104
|
+
export interface PlanV1 {
|
|
105
|
+
readonly schema: "plan-v1";
|
|
106
|
+
/** Digest of the DAG this plan was selected over (binds plan to structure). */
|
|
107
|
+
readonly dagDigest: string;
|
|
108
|
+
/** The selected node IDs, sorted by ID bytes. */
|
|
109
|
+
readonly selectedNodeIds: readonly string[];
|
|
110
|
+
/** The budget this plan was admitted against. */
|
|
111
|
+
readonly tokenBudget: number;
|
|
112
|
+
/** Framed token total of the selection; always `<= tokenBudget`. */
|
|
113
|
+
readonly tokenTotal: number;
|
|
114
|
+
/** Summed utility of the selection. */
|
|
115
|
+
readonly utilityTotal: number;
|
|
116
|
+
/** The durable authority high-water the plan's evidence depends on. */
|
|
117
|
+
readonly dependencyHighWater: bigint;
|
|
118
|
+
/** Candidates deliberately left out, with the reason. */
|
|
119
|
+
readonly omissions: readonly PlanOmission[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The planner verdict. On failure the mandatory set is preserved so an
|
|
124
|
+
* over-budget closure can be reported WITHOUT dropping evidence.
|
|
125
|
+
*/
|
|
126
|
+
export type PlanResult =
|
|
127
|
+
| { readonly ok: true; readonly plan: PlanV1 }
|
|
128
|
+
| {
|
|
129
|
+
readonly ok: false;
|
|
130
|
+
readonly code: PlanFailureCode;
|
|
131
|
+
/** The intact mandatory node IDs (never dropped on failure). */
|
|
132
|
+
readonly mandatory: readonly string[];
|
|
133
|
+
/** The framed cost of the mandatory set that could not be admitted. */
|
|
134
|
+
readonly mandatoryCost: number;
|
|
135
|
+
/** The budget the mandatory cost was measured against. */
|
|
136
|
+
readonly tokenBudget: number;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The triad mode VC5A selects (TRIAD_RESILIENCE). A/B/C are INDEPENDENT
|
|
141
|
+
* algorithms:
|
|
142
|
+
*
|
|
143
|
+
* A = the 0/1 portfolio optimizer (ratio-ordered admission);
|
|
144
|
+
* B = a stable greedy closed planner, forced by an A exception — it shares no
|
|
145
|
+
* ratio ordering with A and admits strictly in source order;
|
|
146
|
+
* C = the predecessor prompt, forced by mandatory overflow; it states its loss
|
|
147
|
+
* of old semantic context (continuity, NOT completeness).
|
|
148
|
+
*/
|
|
149
|
+
export type PlanMode = "A" | "B" | "C";
|
|
150
|
+
|
|
151
|
+
/** The two structured events the VC5A reporter emits. */
|
|
152
|
+
export type PlanEventName =
|
|
153
|
+
| "vector_cortex_plan_selected"
|
|
154
|
+
| "vector_cortex_plan_mandatory_overflow";
|
|
155
|
+
|
|
156
|
+
/** Injected emit callback — same (event, fields) shape as the other VC seams. */
|
|
157
|
+
export type PlanEmitter = (
|
|
158
|
+
event: PlanEventName,
|
|
159
|
+
fields: Record<string, unknown>,
|
|
160
|
+
) => void;
|
|
161
|
+
|
|
162
|
+
/** Typed, best-effort reporter bound to the two plan event names. */
|
|
163
|
+
export interface PlanReporter {
|
|
164
|
+
readonly planSelected: (fields: Record<string, unknown>) => void;
|
|
165
|
+
readonly planMandatoryOverflow: (fields: Record<string, unknown>) => void;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Aggregate-only plan metrics for the dashboard (counts/tokens only, never
|
|
170
|
+
* prompt text or node payloads).
|
|
171
|
+
*/
|
|
172
|
+
export interface PlanMetricsV1 {
|
|
173
|
+
readonly plansSelected: number;
|
|
174
|
+
readonly mandatoryOverflows: number;
|
|
175
|
+
readonly nodesSelected: number;
|
|
176
|
+
readonly tokenTotal: number;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Registered PLN conformance ID range (PLN-001..020). The acceptance aggregator
|
|
181
|
+
* reads these rows from the v2 manifest and asserts each returns its manifest
|
|
182
|
+
* `ok`/`code`.
|
|
183
|
+
*/
|
|
184
|
+
export const PLN_IDS: readonly string[] = Array.from(
|
|
185
|
+
{ length: 20 },
|
|
186
|
+
(_v, i) => `PLN-${String(i + 1).padStart(3, "0")}`,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
/** Named VC5A planner conformance assertions (the sprint's headline rows). */
|
|
190
|
+
export const PLAN_NAMED_IDS = ["PLN-MANDATORY-002", "PLN-TIE-003"] as const;
|