pi-mega-compact 0.20.7 → 0.20.8

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 (95) hide show
  1. package/dist/config/vector-cortex.js +12 -0
  2. package/dist/config.js +1 -1
  3. package/dist/extensions/dashboard-server/api-contracts/vector-cortex-heal.js +11 -0
  4. package/dist/extensions/dashboard-server/route-dispatch.js +102 -0
  5. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
  6. package/dist/extensions/dashboard-server/routes-vector-cortex-heal.js +47 -0
  7. package/dist/extensions/dashboard-server/routes-vector-cortex.js +1 -0
  8. package/dist/extensions/dashboard-server/routes.js +1 -1
  9. package/dist/extensions/dashboard-server/server.js +3 -82
  10. package/dist/src/config/vector-cortex.js +12 -0
  11. package/dist/src/config.js +1 -1
  12. package/dist/src/vector-cortex/heal/_acceptance-fixture.js +56 -0
  13. package/dist/src/vector-cortex/heal/closure-metrics.js +66 -0
  14. package/dist/src/vector-cortex/heal/closure-opt.js +242 -0
  15. package/dist/src/vector-cortex/heal/emit.js +69 -0
  16. package/dist/src/vector-cortex/heal/proof.js +210 -0
  17. package/dist/src/vector-cortex/heal/types.js +42 -0
  18. package/dist/vector-cortex/heal/_acceptance-fixture.js +56 -0
  19. package/dist/vector-cortex/heal/closure-metrics.js +66 -0
  20. package/dist/vector-cortex/heal/closure-opt.js +242 -0
  21. package/dist/vector-cortex/heal/emit.js +69 -0
  22. package/dist/vector-cortex/heal/proof.js +210 -0
  23. package/dist/vector-cortex/heal/types.js +42 -0
  24. package/extensions/dashboard-client/dist/assets/{AreaChart-DqTbaSRA.js → AreaChart-5yxaI8IO.js} +2 -2
  25. package/extensions/dashboard-client/dist/assets/{AreaChart-DqTbaSRA.js.map → AreaChart-5yxaI8IO.js.map} +1 -1
  26. package/extensions/dashboard-client/dist/assets/{BarChart-BfNBeOvF.js → BarChart-BoHhCqH-.js} +2 -2
  27. package/extensions/dashboard-client/dist/assets/{BarChart-BfNBeOvF.js.map → BarChart-BoHhCqH-.js.map} +1 -1
  28. package/extensions/dashboard-client/dist/assets/{CacheTab-C9SOMYB5.js → CacheTab-Di6L3CsF.js} +2 -2
  29. package/extensions/dashboard-client/dist/assets/{CacheTab-C9SOMYB5.js.map → CacheTab-Di6L3CsF.js.map} +1 -1
  30. package/extensions/dashboard-client/dist/assets/{EventsTab-GsMKP10_.js → EventsTab-Cdff3h6s.js} +2 -2
  31. package/extensions/dashboard-client/dist/assets/{EventsTab-GsMKP10_.js.map → EventsTab-Cdff3h6s.js.map} +1 -1
  32. package/extensions/dashboard-client/dist/assets/{HealthTab-DAOCXwRP.js → HealthTab-MthYYVLw.js} +2 -2
  33. package/extensions/dashboard-client/dist/assets/{HealthTab-DAOCXwRP.js.map → HealthTab-MthYYVLw.js.map} +1 -1
  34. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BtBeMkB5.js → MaintenanceTab-D9G4ntsG.js} +2 -2
  35. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BtBeMkB5.js.map → MaintenanceTab-D9G4ntsG.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DzndtgkZ.js → MemoryMapTab-CN8Lr5Qf.js} +2 -2
  37. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DzndtgkZ.js.map → MemoryMapTab-CN8Lr5Qf.js.map} +1 -1
  38. package/extensions/dashboard-client/dist/assets/{MetricsTab-DyxJ4eb8.js → MetricsTab-CgO32kSo.js} +2 -2
  39. package/extensions/dashboard-client/dist/assets/{MetricsTab-DyxJ4eb8.js.map → MetricsTab-CgO32kSo.js.map} +1 -1
  40. package/extensions/dashboard-client/dist/assets/{OverviewTab-DCiEdVWR.js → OverviewTab-Cb4kR0h1.js} +2 -2
  41. package/extensions/dashboard-client/dist/assets/{OverviewTab-DCiEdVWR.js.map → OverviewTab-Cb4kR0h1.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/{ReposTab-B5SIqVHA.js → ReposTab-Dt1FGnMR.js} +2 -2
  43. package/extensions/dashboard-client/dist/assets/{ReposTab-B5SIqVHA.js.map → ReposTab-Dt1FGnMR.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/assets/{SessionsTab-DLWFJ9xI.js → SessionsTab-D7D2RW6U.js} +2 -2
  45. package/extensions/dashboard-client/dist/assets/{SessionsTab-DLWFJ9xI.js.map → SessionsTab-D7D2RW6U.js.map} +1 -1
  46. package/extensions/dashboard-client/dist/assets/{SetupTab-CvGXZyej.js → SetupTab-CgV5xCzz.js} +2 -2
  47. package/extensions/dashboard-client/dist/assets/{SetupTab-CvGXZyej.js.map → SetupTab-CgV5xCzz.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-wZuQGxLS.js → TimeSavedCard-CCNuJjyO.js} +2 -2
  49. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-wZuQGxLS.js.map → TimeSavedCard-CCNuJjyO.js.map} +1 -1
  50. package/extensions/dashboard-client/dist/assets/{TurnsTab-B7aOejch.js → TurnsTab-Cv1OCttJ.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{TurnsTab-B7aOejch.js.map → TurnsTab-Cv1OCttJ.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DTabQo6h.js +2 -0
  53. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DTabQo6h.js.map +1 -0
  54. package/extensions/dashboard-client/dist/assets/{WikiTab-DhWHjAtt.js → WikiTab-B48QnQrK.js} +2 -2
  55. package/extensions/dashboard-client/dist/assets/{WikiTab-DhWHjAtt.js.map → WikiTab-B48QnQrK.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/{button-BrC-W87h.js → button-BBQl2MiF.js} +2 -2
  57. package/extensions/dashboard-client/dist/assets/{button-BrC-W87h.js.map → button-BBQl2MiF.js.map} +1 -1
  58. package/extensions/dashboard-client/dist/assets/{card-0rgfRgr3.js → card-BYbefUur.js} +2 -2
  59. package/extensions/dashboard-client/dist/assets/{card-0rgfRgr3.js.map → card-BYbefUur.js.map} +1 -1
  60. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DRresOQF.js → generateCategoricalChart-swm0YZe0.js} +2 -2
  61. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DRresOQF.js.map → generateCategoricalChart-swm0YZe0.js.map} +1 -1
  62. package/extensions/dashboard-client/dist/assets/{index-DohmtZWL.js → index-DeIky7xm.js} +3 -3
  63. package/extensions/dashboard-client/dist/assets/{index-DohmtZWL.js.map → index-DeIky7xm.js.map} +1 -1
  64. package/extensions/dashboard-client/dist/assets/{switch-Bs8g_zB5.js → switch-CctU78ww.js} +2 -2
  65. package/extensions/dashboard-client/dist/assets/{switch-Bs8g_zB5.js.map → switch-CctU78ww.js.map} +1 -1
  66. package/extensions/dashboard-client/dist/assets/{toggle-DVVauIo8.js → toggle-BRCNCoiI.js} +2 -2
  67. package/extensions/dashboard-client/dist/assets/{toggle-DVVauIo8.js.map → toggle-BRCNCoiI.js.map} +1 -1
  68. package/extensions/dashboard-client/dist/assets/{useSSE-DxH1PTAR.js → useSSE-WfOiWDH_.js} +2 -2
  69. package/extensions/dashboard-client/dist/assets/{useSSE-DxH1PTAR.js.map → useSSE-WfOiWDH_.js.map} +1 -1
  70. package/extensions/dashboard-client/dist/index.html +1 -1
  71. package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
  72. package/extensions/dashboard-client/src/tabs/VectorCortexClosureCard.tsx +46 -0
  73. package/extensions/dashboard-client/src/tabs/VectorCortexLedgerCard.tsx +80 -0
  74. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +12 -164
  75. package/extensions/dashboard-client/src/tabs/VectorCortexTopologyCard.tsx +131 -0
  76. package/extensions/dashboard-client/src/types/vector-cortex.ts +14 -0
  77. package/extensions/dashboard-server/api-contracts/vector-cortex-heal.ts +43 -0
  78. package/extensions/dashboard-server/api-contracts/vector-cortex.ts +2 -0
  79. package/extensions/dashboard-server/route-dispatch.ts +112 -0
  80. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
  81. package/extensions/dashboard-server/routes-vector-cortex-heal.ts +56 -0
  82. package/extensions/dashboard-server/routes-vector-cortex.ts +1 -0
  83. package/extensions/dashboard-server/routes.ts +1 -0
  84. package/extensions/dashboard-server/server.ts +3 -86
  85. package/package.json +1 -1
  86. package/src/config/vector-cortex.ts +13 -0
  87. package/src/config.ts +1 -0
  88. package/src/vector-cortex/heal/_acceptance-fixture.ts +108 -0
  89. package/src/vector-cortex/heal/closure-metrics.ts +73 -0
  90. package/src/vector-cortex/heal/closure-opt.ts +266 -0
  91. package/src/vector-cortex/heal/emit.ts +93 -0
  92. package/src/vector-cortex/heal/proof.ts +234 -0
  93. package/src/vector-cortex/heal/types.ts +226 -0
  94. package/extensions/dashboard-client/dist/assets/VectorCortexTab-eHVt9yDx.js +0 -2
  95. package/extensions/dashboard-client/dist/assets/VectorCortexTab-eHVt9yDx.js.map +0 -1
@@ -26,51 +26,8 @@ import { createRequire } from "node:module";
26
26
  import { NEW_UI } from "../../src/config.js";
27
27
  import { log, setLogPath, setDashboardServerVersion } from "./state.js";
28
28
  import { detectTailscaleIP } from "./tailscale.js";
29
- import {
30
- buildRouteContext,
31
- handleIndex,
32
- handleRepoIndex,
33
- handleEvents,
34
- handleGameState,
35
- handleGameScores,
36
- handlePerf,
37
- handlePerfSamples,
38
- handleAchievements,
39
- handleSessions,
40
- handleTopics,
41
- handleTurns,
42
- handleMaintenance,
43
- handleProviderCache,
44
- handleCacheStripes,
45
- handleMemoryStatus,
46
- handleSetupStatus,
47
- handleSetupDetect,
48
- handleSetupConfigure,
49
- handleMemoryMap,
50
- handleRaptorTree,
51
- handleRaptorBuildHistory,
52
- handleContextHealth,
53
- handleCachePoison,
54
- handleHealthSettings,
55
- handleEmbedderHealth,
56
- handleRagSettings,
57
- handleRagMetrics,
58
- handleModelThresholds,
59
- handleWiki,
60
- handleVectorCortexEvaluation,
61
- handleVectorCortexHealth,
62
- handleVectorCortexBreakersReset,
63
- handleVectorCortexLedger,
64
- handleVectorCortexTopology,
65
- handleVectorCortexQuery,
66
- handleVectorCortexShards,
67
- handleVectorCortexResidual,
68
- handleVectorCortexReconstruct,
69
- handleVectorCortexPlans,
70
- handleVectorCortexRender,
71
- handleVectorCortexRollout,
72
- handleStatic,
73
- } from "./routes.js";
29
+ import { buildRouteContext, handleStatic } from "./routes.js";
30
+ import { dispatchRoutes } from "./route-dispatch.js";
74
31
 
75
32
  export async function launchDashboardServer(
76
33
  stateDir: string,
@@ -276,47 +233,7 @@ export async function launchDashboardServer(
276
233
  }
277
234
 
278
235
  // Dispatch — each handler returns true if it ended the response.
279
- if (handleIndex(req, res, ctx)) return;
280
- if (handleRepoIndex(req, res, ctx)) return;
281
- if (handleEvents(req, res, ctx)) return;
282
- if (handleGameState(req, res, ctx)) return;
283
- if (handleGameScores(req, res, ctx)) return;
284
- if (handlePerfSamples(req, res, ctx)) return;
285
- if (handlePerf(req, res, ctx)) return;
286
- if (handleAchievements(req, res, ctx)) return;
287
- if (handleSessions(req, res, ctx)) return;
288
- if (handleTopics(req, res, ctx)) return;
289
- if (handleTurns(req, res, ctx)) return;
290
- if (handleMaintenance(req, res, ctx)) return;
291
- if (handleProviderCache(req, res, ctx)) return;
292
- if (handleMemoryStatus(req, res, ctx)) return;
293
- if (handleCacheStripes(req, res, ctx)) return;
294
- if (handleSetupStatus(req, res, ctx)) return;
295
- if (handleSetupDetect(req, res, ctx)) return;
296
- if (handleSetupConfigure(req, res, ctx)) return;
297
- if (handleMemoryMap(req, res, ctx)) return;
298
- if (handleRaptorTree(req, res, ctx)) return;
299
- if (handleRaptorBuildHistory(req, res, ctx)) return;
300
- if (handleContextHealth(req, res, ctx)) return;
301
- if (handleCachePoison(req, res, ctx)) return;
302
- if (handleHealthSettings(req, res, ctx)) return;
303
- if (handleEmbedderHealth(req, res, ctx)) return;
304
- if (handleRagSettings(req, res, ctx)) return;
305
- if (handleRagMetrics(req, res, ctx)) return;
306
- if (handleModelThresholds(req, res, ctx)) return;
307
- if (handleWiki(req, res, ctx)) return;
308
- if (handleVectorCortexEvaluation(req, res, ctx)) return;
309
- if (handleVectorCortexHealth(req, res, ctx)) return;
310
- if (handleVectorCortexBreakersReset(req, res, ctx)) return;
311
- if (handleVectorCortexLedger(req, res, ctx)) return;
312
- if (handleVectorCortexTopology(req, res, ctx)) return;
313
- if (handleVectorCortexQuery(req, res, ctx)) return;
314
- if (handleVectorCortexShards(req, res, ctx)) return;
315
- if (handleVectorCortexResidual(req, res, ctx)) return;
316
- if (handleVectorCortexReconstruct(req, res, ctx)) return;
317
- if (handleVectorCortexPlans(req, res, ctx)) return;
318
- if (handleVectorCortexRender(req, res, ctx)) return;
319
- if (handleVectorCortexRollout(req, res, ctx)) return;
236
+ if (dispatchRoutes(req, res, ctx)) return;
320
237
  handleStatic(req, res, ctx);
321
238
  });
322
239
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.20.7",
3
+ "version": "0.20.8",
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",
@@ -214,6 +214,19 @@ export const VC5B_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC5B");
214
214
  */
215
215
  export const VC5C_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC5C");
216
216
 
217
+ /**
218
+ * VC6A — advanced closure optimization. Default ON. `MEGACOMPACT_VC6A=0`
219
+ * disables and is byte-identical to the predecessor (VC5C): the `optimizeClosure`
220
+ * transitive-reduction arithmetic STILL RUNS (it is PURE), but the
221
+ * `vector_cortex_closure_optimized` / `vector_cortex_closure_proof_rejected`
222
+ * events are never emitted and the dashboard closure diagnostics seam is
223
+ * suppressed. Flag OFF gates the reporter + dashboard seam, never the arithmetic,
224
+ * so flag-off outbound/predecessor golden bytes match exactly. This flag MUST
225
+ * also be a dashboard SETTINGS toggle (visible in config UI, never in
226
+ * EXCLUDED_SETTINGS), mirroring VC4A/VC4B/VC4C/VC5A/VC5B/VC5C.
227
+ */
228
+ export const VC6A_ENABLED = (): boolean => sprintFlag("MEGACOMPACT_VC6A");
229
+
217
230
  // ---------------------------------------------------------------------------
218
231
  // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
219
232
  // Rolled numbers for one 60s window; VC0C consumes these at its breaker seam.
package/src/config.ts CHANGED
@@ -169,6 +169,7 @@ export {
169
169
  VC5A_ENABLED,
170
170
  VC5B_ENABLED,
171
171
  VC5C_ENABLED,
172
+ VC6A_ENABLED,
172
173
  BREAKER_WINDOW_MS,
173
174
  BREAKER_MIN_ATTEMPTS,
174
175
  BREAKER_PERF_FAILURES,
@@ -0,0 +1,108 @@
1
+ /**
2
+ * heal/_acceptance-fixture.ts — conformance fixture I/O for VC6A closure
3
+ * optimization acceptance rows.
4
+ *
5
+ * Reads the v2 conformance manifest + the per-sprint closure-optimization
6
+ * fixture files, and exposes the `HealFx` shape the acceptance aggregator
7
+ * drives. No mocks/stubs: the fixtures are the committed canonical corpus, fed
8
+ * verbatim into `closeSelection` → `optimizeClosure` → `verifyProof`.
9
+ */
10
+
11
+ import { readFileSync, existsSync } from "node:fs";
12
+ import { join, dirname } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import assert from "node:assert/strict";
15
+
16
+ const HERE = dirname(fileURLToPath(import.meta.url));
17
+
18
+ function repoRoot(from: string): string {
19
+ let dir = from;
20
+ for (let i = 0; i < 8; i++) {
21
+ if (existsSync(join(dir, "conformance", "vector-cortex"))) return dir;
22
+ const next = dirname(dir);
23
+ if (next === dir) break;
24
+ dir = next;
25
+ }
26
+ throw new Error("conformance corpus not found above " + from);
27
+ }
28
+ export const REPO_ROOT = repoRoot(HERE);
29
+ export const V2 = join(REPO_ROOT, "conformance", "vector-cortex", "v2");
30
+
31
+ export interface ManifestRow {
32
+ id: string;
33
+ path: string;
34
+ algorithm: string;
35
+ expected: string;
36
+ }
37
+
38
+ export interface HealFxGraphNode {
39
+ id: string;
40
+ kind: string;
41
+ tokenEstimate: number;
42
+ anchor?: boolean;
43
+ }
44
+ export interface HealFxGraphEdge {
45
+ from: string;
46
+ to: string;
47
+ kind: "depends" | "tool-pair" | "contradicts";
48
+ }
49
+ export interface HealFxGraph {
50
+ sessionId: string;
51
+ nodes: HealFxGraphNode[];
52
+ edges: HealFxGraphEdge[];
53
+ resolutions?: Array<{ loserId: string; winnerId: string }>;
54
+ }
55
+ export interface HealFxInput {
56
+ graph: HealFxGraph;
57
+ scenario: string;
58
+ seeds: string[];
59
+ }
60
+ export interface HealFxExpected {
61
+ ok: boolean;
62
+ code?: string;
63
+ removedEdges: number;
64
+ retainedEdges: number;
65
+ selectedMatch: boolean;
66
+ protectedRetained: number;
67
+ deterministic?: boolean;
68
+ }
69
+ export interface HealFx {
70
+ id: string;
71
+ schema: string;
72
+ producer: string;
73
+ assertion: string;
74
+ kind: string;
75
+ input: HealFxInput;
76
+ expected: HealFxExpected;
77
+ }
78
+
79
+ export function readManifest(): { fixtures: ManifestRow[] } {
80
+ return JSON.parse(readFileSync(join(V2, "manifest.json"), "utf8")) as {
81
+ fixtures: ManifestRow[];
82
+ };
83
+ }
84
+
85
+ function readFixture(id: string, prefix: "closure-optimization"): HealFx {
86
+ const m = readManifest();
87
+ const row = m.fixtures.find((f) => f.id === id && f.path.startsWith(`${prefix}/`));
88
+ assert.ok(row, `fixture ${id} registered under ${prefix}/ in manifest`);
89
+ return JSON.parse(readFileSync(join(V2, row.path), "utf8")) as HealFx;
90
+ }
91
+
92
+ export function healFixture(id: string): HealFx {
93
+ return readFixture(id, "closure-optimization");
94
+ }
95
+
96
+ /** Flag-pinned wrapper: VC6A gated by MEGACOMPACT_VC6A (defaults ON). */
97
+ export function withFlagsOn(fn: () => void): () => void {
98
+ return (): void => {
99
+ const saved = process.env.MEGACOMPACT_VC6A;
100
+ process.env.MEGACOMPACT_VC6A = "1";
101
+ try {
102
+ fn();
103
+ } finally {
104
+ if (saved === undefined) delete process.env.MEGACOMPACT_VC6A;
105
+ else process.env.MEGACOMPACT_VC6A = saved;
106
+ }
107
+ };
108
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * vector-cortex/heal/closure-metrics.ts — VC6A closure proof metrics + handoff.
3
+ *
4
+ * Pure read-only derivations over a `ClosureProofV2`: traversal savings (the
5
+ * sprint's >=20% median acceptance bar) and the `RestoreHintV1` list VC6B
6
+ * consumes. Extracted from `closure-opt.ts` (delegate-shell) to keep the core
7
+ * reduction algorithm under the 300-line soft limit.
8
+ *
9
+ * PURE / DETERMINISTIC / LOCAL: no storage, no clock, no network (PREVENT-PI-004
10
+ * / PREVENT-011). Identity only — never source bytes or prompt text
11
+ * (SECURITY_PRIVACY).
12
+ */
13
+
14
+ import type { ClosureGraph } from "../reconstruct/types.js";
15
+ import type { ClosureProofV2, RestoreHintV1, RetainReason } from "./types.js";
16
+
17
+ /** Bytewise id comparator — matches `closure-opt.ts` / `reconstruct/closure.ts`. */
18
+ function byBytes(a: string, b: string): number {
19
+ return a < b ? -1 : a > b ? 1 : 0;
20
+ }
21
+
22
+ /** Anchor id set from a graph (matches `closure-opt.ts`). */
23
+ function anchorIds(graph: ClosureGraph): Set<string> {
24
+ return new Set(graph.nodes.filter((n) => n.anchor === true).map((n) => n.id));
25
+ }
26
+
27
+ /**
28
+ * Traversal savings as a fraction in [0,1]: how much of the conservative edge
29
+ * walk the optimized plan avoids. A plan with nothing to remove scores 0 (not a
30
+ * failure — a chain graph is already minimal). The sprint's acceptance bar is a
31
+ * >=20% MEDIAN over the corpus, never a per-graph floor.
32
+ */
33
+ export function traversalSavings(proof: ClosureProofV2): number {
34
+ if (proof.conservativeTraversals === 0) return 0;
35
+ const saved = proof.conservativeTraversals - proof.optimizedTraversals;
36
+ return saved / proof.conservativeTraversals;
37
+ }
38
+
39
+ /**
40
+ * Derive the `RestoreHintV1` list VC6B consumes (the sprint's declared handoff:
41
+ * "VC6B receives missing-source hints and proof").
42
+ *
43
+ * One hint per selected node that a RETAINED edge reaches, naming the cheapest
44
+ * (bytewise-first) retained prerequisite and whether the node is protected.
45
+ * Identity only — never source bytes or prompt text (SECURITY_PRIVACY).
46
+ */
47
+ export function restoreHints(proof: ClosureProofV2, graph: ClosureGraph): readonly RestoreHintV1[] {
48
+ const anchors = anchorIds(graph);
49
+ const best = new Map<string, { from: string; reason: RetainReason }>();
50
+ for (const row of proof.rows) {
51
+ if (row.decision !== "retained") continue;
52
+ const reason = row.reason as RetainReason;
53
+ const existing = best.get(row.to);
54
+ if (existing === undefined || byBytes(row.from, existing.from) < 0) {
55
+ best.set(row.to, { from: row.from, reason });
56
+ }
57
+ }
58
+ const hints: RestoreHintV1[] = [];
59
+ for (const nodeId of [...proof.selected].sort(byBytes)) {
60
+ const hit = best.get(nodeId);
61
+ const isToolPair = proof.retainedEdges.some(
62
+ (e) => e.kind === "tool-pair" && (e.from === nodeId || e.to === nodeId),
63
+ );
64
+ hints.push({
65
+ schema: "restore-hint-v1",
66
+ nodeId,
67
+ viaEdgeFrom: hit?.from,
68
+ protectedNode: anchors.has(nodeId) || isToolPair,
69
+ reason: hit?.reason ?? (anchors.has(nodeId) ? "anchor" : "no-alternate-path"),
70
+ });
71
+ }
72
+ return hints;
73
+ }
@@ -0,0 +1,266 @@
1
+ /**
2
+ * vector-cortex/heal/closure-opt.ts — VC6A deterministic transitive reduction
3
+ * over the ALREADY-MANDATORY VC4C closure.
4
+ *
5
+ * WHAT THIS DOES NOT DO: it does not close anything and it does not change which
6
+ * nodes are selected. `closeSelection` (VC4C) has already run and produced the
7
+ * mandatory set; CONTRACTS §plan and closure is explicit that "VC6 only
8
+ * optimizes restoration/self-healing". This module takes that closed set and
9
+ * shrinks the EDGE PLAN used to justify/re-walk it.
10
+ *
11
+ * THE REDUCTION. Requirements flow prerequisite `from` → dependent `to`, and
12
+ * closure walks them BACKWARD (selecting `to` pulls in `from`). So the
13
+ * requirement relation is `to ⇒ from`. If `c ⇒ b` and `b ⇒ a` are both in the
14
+ * plan, then a third edge `c ⇒ a` adds nothing: walking `c` already reaches `a`
15
+ * through `b`. That third edge is transitively implied and is the only kind of
16
+ * edge this module removes.
17
+ *
18
+ * WHAT IS NEVER REMOVED (task 3):
19
+ * - a `tool-pair` edge — the pair is atomic (PREVENT-PI-002);
20
+ * - any edge touching an anchor — the floor is preserved (PREVENT-PI-001);
21
+ * - a `contradicts` edge — the resolver reads it directly, so removing
22
+ * it would change the resolved set;
23
+ * - a SOLE dependency edge — the only edge pulling its prerequisite in.
24
+ * Every considered edge — retained or removed — is recorded in the proof, so the
25
+ * verifier can replay the decision rather than trust it.
26
+ *
27
+ * DETERMINISM. Vertices and edges are sorted by ID BYTES (the same bytewise
28
+ * comparator `reconstruct/closure.ts` uses), the reachability search drains a
29
+ * sorted worklist, and every emitted array is sorted. Two graphs that differ
30
+ * only in input order produce byte-identical proofs.
31
+ *
32
+ * PURITY. No storage, no console, no clock, no network (PREVENT-PI-004 /
33
+ * PREVENT-011). The FLAG DOES NOT GATE THIS ARITHMETIC: `MEGACOMPACT_VC6A=0`
34
+ * gates the reporter + dashboard seam only (see `emit.ts`), exactly as
35
+ * VC5B/VC5C do, so flag-off is byte-identical to the predecessor.
36
+ */
37
+
38
+ import type { ClosureEdge, ClosureGraph, ClosureResult } from "../reconstruct/types.js";
39
+ import type { ClosureProofRow, ClosureProofV2, RetainReason } from "./types.js";
40
+
41
+ /** Bytewise id comparator — identical to `reconstruct/closure.ts::sortedIds`. */
42
+ function byBytes(a: string, b: string): number {
43
+ return a < b ? -1 : a > b ? 1 : 0;
44
+ }
45
+
46
+ /** Total order over edges: `from`, then `to`, then `kind` — all bytewise. */
47
+ function compareEdges(x: ClosureEdge, y: ClosureEdge): number {
48
+ return byBytes(x.from, y.from) || byBytes(x.to, y.to) || byBytes(x.kind, y.kind);
49
+ }
50
+
51
+ /** Sorted copy of an edge list (never mutates the caller's array). */
52
+ function sortedEdges(edges: readonly ClosureEdge[]): ClosureEdge[] {
53
+ return [...edges].sort(compareEdges);
54
+ }
55
+
56
+ /**
57
+ * The requirement adjacency of a plan: for a dependent node, which prerequisites
58
+ * it pulls in. Mirrors `reconstruct/closure.ts::buildRequirements` — `depends`
59
+ * walks backward to the prerequisite, `tool-pair` walks BOTH ways because a pair
60
+ * is atomic. `contradicts` never pulls a node in and is therefore absent here
61
+ * (it is still protected from removal; it simply is not a traversal edge).
62
+ */
63
+ function requirementAdjacency(edges: readonly ClosureEdge[]): Map<string, string[]> {
64
+ const adj = new Map<string, string[]>();
65
+ const add = (key: string, value: string): void => {
66
+ const list = adj.get(key);
67
+ if (list === undefined) adj.set(key, [value]);
68
+ else list.push(value);
69
+ };
70
+ for (const e of edges) {
71
+ if (e.kind === "depends") add(e.to, e.from);
72
+ else if (e.kind === "tool-pair") {
73
+ add(e.to, e.from);
74
+ add(e.from, e.to);
75
+ }
76
+ }
77
+ for (const list of adj.values()) list.sort(byBytes);
78
+ return adj;
79
+ }
80
+
81
+ /**
82
+ * Is `target` reachable from `start` through the requirement adjacency WITHOUT
83
+ * using the direct `start ⇒ target` hop? Returns the witnessing intermediate
84
+ * vertex (the first hop of the detour, bytewise-smallest) or `null`.
85
+ *
86
+ * This is the "alternate path exists" test that licenses a removal. The direct
87
+ * hop is excluded so an edge can never justify its own removal; a cycle simply
88
+ * revisits `seen` and terminates.
89
+ */
90
+ function alternatePathVia(
91
+ adj: ReadonlyMap<string, string[]>,
92
+ start: string,
93
+ target: string,
94
+ ): string | null {
95
+ // First hops other than the direct one, in bytewise order so the witness is
96
+ // deterministic when several detours exist.
97
+ const firstHops = (adj.get(start) ?? []).filter((n) => n !== target);
98
+ for (const hop of firstHops) {
99
+ // Breadth-first from this hop; `seen` excludes `start` so a detour that
100
+ // loops back through the origin cannot be counted as progress.
101
+ const seen = new Set<string>([start]);
102
+ const worklist: string[] = [hop];
103
+ while (worklist.length > 0) {
104
+ worklist.sort(byBytes);
105
+ const current = worklist.shift();
106
+ if (current === undefined) break;
107
+ if (current === target) return hop;
108
+ if (seen.has(current)) continue;
109
+ seen.add(current);
110
+ for (const next of adj.get(current) ?? []) {
111
+ if (!seen.has(next)) worklist.push(next);
112
+ }
113
+ }
114
+ }
115
+ return null;
116
+ }
117
+
118
+ /**
119
+ * Is `target` reachable from `start` using ONLY the edges in `others` (a path of
120
+ * length >= 2, never the direct hop)? Returns the bytewise-smallest first-hop
121
+ * vertex (the witness `via`) or `null`.
122
+ *
123
+ * The transitive-reduction correctness rule: an edge `start ⇒ target` is
124
+ * redundant only if a length>=2 path exists through the OTHER edges. Crucially
125
+ * we EXCLUDE the edge under consideration, so removing one edge never invalidates
126
+ * the witness used to justify removing ANOTHER — a plain single-pass check over
127
+ * the full adjacency over-removes (HEAL-PROTECT-002: a tool pair backs two
128
+ * depends edges that would each look implied by the full graph but only survive
129
+ * together).
130
+ */
131
+ function alternatePathExcluding(
132
+ others: readonly ClosureEdge[],
133
+ start: string,
134
+ target: string,
135
+ ): string | null {
136
+ const adj = requirementAdjacency(others);
137
+ return alternatePathVia(adj, start, target);
138
+ }
139
+
140
+ /** The set of node ids marked as anchor-floor members in the graph. */
141
+ function anchorIds(graph: ClosureGraph): Set<string> {
142
+ const out = new Set<string>();
143
+ for (const n of graph.nodes) if (n.anchor === true) out.add(n.id);
144
+ return out;
145
+ }
146
+
147
+ /**
148
+ * Count how many `depends` edges pull in each prerequisite. A prerequisite with
149
+ * exactly one such edge has a SOLE dependency edge: removing it would drop the
150
+ * prerequisite from the closure entirely, so it is protected regardless of any
151
+ * alternate path.
152
+ */
153
+ function dependsFanIn(edges: readonly ClosureEdge[]): Map<string, number> {
154
+ const counts = new Map<string, number>();
155
+ for (const e of edges) {
156
+ if (e.kind !== "depends") continue;
157
+ counts.set(e.from, (counts.get(e.from) ?? 0) + 1);
158
+ }
159
+ return counts;
160
+ }
161
+
162
+ /**
163
+ * Classify a NON-removable edge, or return `null` when the edge is an ordinary
164
+ * `depends` edge that removal is at least allowed to consider.
165
+ *
166
+ * Order matters and is deliberate: the strongest structural guarantee is
167
+ * reported first, so a tool-pair edge that also touches an anchor is recorded as
168
+ * `tool-pair` and the proof reads as the reason a reviewer would give.
169
+ */
170
+ function protectedReason(
171
+ edge: ClosureEdge,
172
+ anchors: ReadonlySet<string>,
173
+ fanIn: ReadonlyMap<string, number>,
174
+ ): RetainReason | null {
175
+ if (edge.kind === "tool-pair") return "tool-pair";
176
+ if (edge.kind === "contradicts") return "contradiction";
177
+ if (anchors.has(edge.from) || anchors.has(edge.to)) return "anchor";
178
+ if ((fanIn.get(edge.from) ?? 0) <= 1) return "sole-dependency";
179
+ return null;
180
+ }
181
+
182
+ /** Input to one optimization run: the graph plus the conservative VC4C result. */
183
+ export interface ClosureOptInput {
184
+ readonly graph: ClosureGraph;
185
+ /** The ALREADY-COMPUTED conservative closure (VC4C `closeSelection`). */
186
+ readonly conservative: ClosureResult;
187
+ }
188
+
189
+ /**
190
+ * Optimize the already-mandatory closure by transitive reduction (task 2).
191
+ *
192
+ * Only edges INSIDE the closed selection are considered: an edge touching an
193
+ * unselected node is not part of the traversal plan for this closure at all, so
194
+ * it is neither retained nor removed — it is simply out of scope and produces no
195
+ * proof row.
196
+ *
197
+ * `selected` is copied through UNCHANGED. That is the whole safety property: the
198
+ * optimizer is allowed to make the plan cheaper, never to make it smaller.
199
+ */
200
+ export function optimizeClosure(input: ClosureOptInput): ClosureProofV2 {
201
+ const { graph, conservative } = input;
202
+ const selected = new Set(conservative.selected);
203
+ const anchors = anchorIds(graph);
204
+
205
+ // Consider only in-selection edges, in deterministic bytewise order.
206
+ const considered = sortedEdges(
207
+ graph.edges.filter((e) => selected.has(e.from) && selected.has(e.to)),
208
+ );
209
+ const fanIn = dependsFanIn(considered);
210
+
211
+ const rows: ClosureProofRow[] = [];
212
+ const retained: ClosureEdge[] = [];
213
+ const removed: ClosureEdge[] = [];
214
+
215
+ for (const e of considered) {
216
+ const guard = protectedReason(e, anchors, fanIn);
217
+ if (guard !== null) {
218
+ // Protected: retained unconditionally, EVEN WHEN an alternate path exists
219
+ // (HEAL-PROTECT-002). The reason names the guarantee, not the topology.
220
+ rows.push({ from: e.from, to: e.to, kind: e.kind, decision: "retained", reason: guard });
221
+ retained.push(e);
222
+ continue;
223
+ }
224
+ // Ordinary `depends` edge: removable iff the requirement it carries is
225
+ // already carried by a longer path `to ⇒ via ⇒ ... ⇒ from` through the OTHER
226
+ // edges (the edge under test is excluded so removals stay mutually valid).
227
+ const others = considered.filter((o) => o !== e);
228
+ const via = alternatePathExcluding(others, e.to, e.from);
229
+ if (via === null) {
230
+ rows.push({
231
+ from: e.from,
232
+ to: e.to,
233
+ kind: e.kind,
234
+ decision: "retained",
235
+ reason: "no-alternate-path",
236
+ });
237
+ retained.push(e);
238
+ continue;
239
+ }
240
+ rows.push({
241
+ from: e.from,
242
+ to: e.to,
243
+ kind: e.kind,
244
+ decision: "removed",
245
+ reason: "transitively-implied",
246
+ via,
247
+ });
248
+ removed.push(e);
249
+ }
250
+
251
+ return {
252
+ schema: "closure-proof-v2",
253
+ sessionId: graph.sessionId,
254
+ selected: [...conservative.selected],
255
+ retainedEdges: sortedEdges(retained),
256
+ removedEdges: sortedEdges(removed),
257
+ rows,
258
+ conservativeTraversals: considered.length,
259
+ optimizedTraversals: retained.length,
260
+ };
261
+ }
262
+
263
+ // Metrics + VC6B handoff (traversal savings, RestoreHintV1) live in the
264
+ // delegate-shell sibling `closure-metrics.ts` to keep this core reduction
265
+ // algorithm under the 300-line soft limit.
266
+ export { traversalSavings, restoreHints } from "./closure-metrics.js";
@@ -0,0 +1,93 @@
1
+ /**
2
+ * vector-cortex/heal/emit.ts — VC6A event reporter seam (task 5).
3
+ *
4
+ * Mirrors `../rollout/emit.ts`: a thin `safe()` wrapper around an optional
5
+ * injected `emit` (so unit tests stay pure — they pass `undefined` and inspect
6
+ * nothing), and two event names required verbatim by the sprint spec:
7
+ * - `vector_cortex_closure_optimized` — emitted when a proof is produced.
8
+ * - `vector_cortex_closure_proof_rejected` — emitted when verification fails.
9
+ *
10
+ * FLAG SEMANTICS (the invariant VC5B/VC5C established). The optimizer and
11
+ * verifier are PURE arithmetic and run REGARDLESS of the flag. The flag gates
12
+ * ONLY the reporting + dashboard seam: when `MEGACOMPACT_VC6A=0` we still compute
13
+ * the proof (needed for byte-identical parity with the predecessor), we just do
14
+ * not emit the VC6A-namespaced events. This is what makes flag-off byte-identical
15
+ * to VC5C — the arithmetic is never skipped, only the announcement.
16
+ *
17
+ * No console, no storage, no network (PREVENT-PI-004 / PREVENT-011). Every line
18
+ * is a structured JSON event with `ts` + `event`.
19
+ */
20
+
21
+ import { VC6A_ENABLED } from "../../config/vector-cortex.js";
22
+ import type { HealEventName } from "./types.js";
23
+
24
+ /** Optional emit fn injected by the runtime; tests pass `undefined`. */
25
+ export type HealEmit = (name: string, payload: unknown) => void;
26
+
27
+ /**
28
+ * Run `fn` only when an emit is available, swallowing and logging nothing on
29
+ * failure (PRACTICES: non-fatal stores). `fn` receives the emit so callers need
30
+ * not null-check.
31
+ */
32
+ function safe(emit: HealEmit | undefined, fn: (emit: HealEmit) => void): void {
33
+ if (emit === undefined) return;
34
+ try {
35
+ fn(emit);
36
+ } catch {
37
+ // Non-fatal: a reporting failure must never break the agent loop.
38
+ }
39
+ }
40
+
41
+ /** The event names VC6A emits, exported for the dashboard seam and tests. */
42
+ export const HEAL_EVENT_NAMES: readonly HealEventName[] = [
43
+ "vector_cortex_closure_optimized",
44
+ "vector_cortex_closure_proof_rejected",
45
+ ] as const;
46
+
47
+ /**
48
+ * Report a successful optimization. `MEGACOMPACT_VC6A=0` suppresses the VC6A
49
+ * event (flag-off parity) but does not affect the computation that produced
50
+ * `proof`.
51
+ */
52
+ export function reportClosureOptimized(
53
+ emit: HealEmit | undefined,
54
+ payload: {
55
+ readonly sessionId: string;
56
+ readonly removed: number;
57
+ readonly retained: number;
58
+ readonly savings: number;
59
+ },
60
+ ): void {
61
+ if (!VC6A_ENABLED()) return;
62
+ safe(emit, (e) =>
63
+ e("vector_cortex_closure_optimized", {
64
+ ts: undefined,
65
+ event: "vector_cortex_closure_optimized",
66
+ sessionId: payload.sessionId,
67
+ removedEdges: payload.removed,
68
+ retainedEdges: payload.retained,
69
+ traversalSavings: payload.savings,
70
+ }),
71
+ );
72
+ }
73
+
74
+ /** Report a rejected proof. Suppressed under flag-off, like the above. */
75
+ export function reportProofRejected(
76
+ emit: HealEmit | undefined,
77
+ payload: {
78
+ readonly sessionId: string;
79
+ readonly reason: string;
80
+ readonly mode: "B" | "C";
81
+ },
82
+ ): void {
83
+ if (!VC6A_ENABLED()) return;
84
+ safe(emit, (e) =>
85
+ e("vector_cortex_closure_proof_rejected", {
86
+ ts: undefined,
87
+ event: "vector_cortex_closure_proof_rejected",
88
+ sessionId: payload.sessionId,
89
+ reason: payload.reason,
90
+ fallbackMode: payload.mode,
91
+ }),
92
+ );
93
+ }