pi-mega-compact 0.20.6 → 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.
- package/dist/config/vector-cortex.js +12 -0
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/api-contracts/vector-cortex-heal.js +11 -0
- package/dist/extensions/dashboard-server/route-dispatch.js +102 -0
- package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +3 -32
- package/dist/extensions/dashboard-server/routes-rag-settings-types.js +11 -0
- package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +44 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-heal.js +47 -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 -82
- package/dist/src/config/dedup.js +1 -0
- package/dist/src/config/vector-cortex.js +12 -0
- package/dist/src/config.js +1 -1
- package/dist/src/dedup-audit.test-helpers.js +43 -0
- package/dist/src/monitoring.js +9 -0
- package/dist/src/vector-cortex/heal/_acceptance-fixture.js +56 -0
- package/dist/src/vector-cortex/heal/closure-metrics.js +66 -0
- package/dist/src/vector-cortex/heal/closure-opt.js +242 -0
- package/dist/src/vector-cortex/heal/emit.js +69 -0
- package/dist/src/vector-cortex/heal/proof.js +210 -0
- package/dist/src/vector-cortex/heal/types.js +42 -0
- package/dist/src/vectorStore/add-l0.js +108 -0
- package/dist/src/vectorStore/add-l1.js +35 -0
- package/dist/src/vectorStore/add.js +229 -0
- package/dist/src/vectorStore/class.js +15 -291
- package/dist/src/vectorStore/dedup-audit.js +104 -0
- package/dist/src/vectorStore.js +1 -0
- package/dist/vector-cortex/heal/_acceptance-fixture.js +56 -0
- package/dist/vector-cortex/heal/closure-metrics.js +66 -0
- package/dist/vector-cortex/heal/closure-opt.js +242 -0
- package/dist/vector-cortex/heal/emit.js +69 -0
- package/dist/vector-cortex/heal/proof.js +210 -0
- package/dist/vector-cortex/heal/types.js +42 -0
- package/extensions/dashboard-client/dist/assets/{AreaChart-DqTbaSRA.js → AreaChart-5yxaI8IO.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-DqTbaSRA.js.map → AreaChart-5yxaI8IO.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-BfNBeOvF.js → BarChart-BoHhCqH-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-BfNBeOvF.js.map → BarChart-BoHhCqH-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-C9SOMYB5.js → CacheTab-Di6L3CsF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-C9SOMYB5.js.map → CacheTab-Di6L3CsF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-GsMKP10_.js → EventsTab-Cdff3h6s.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-GsMKP10_.js.map → EventsTab-Cdff3h6s.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-DAOCXwRP.js → HealthTab-MthYYVLw.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-DAOCXwRP.js.map → HealthTab-MthYYVLw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BtBeMkB5.js → MaintenanceTab-D9G4ntsG.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BtBeMkB5.js.map → MaintenanceTab-D9G4ntsG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DzndtgkZ.js → MemoryMapTab-CN8Lr5Qf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DzndtgkZ.js.map → MemoryMapTab-CN8Lr5Qf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DyxJ4eb8.js → MetricsTab-CgO32kSo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DyxJ4eb8.js.map → MetricsTab-CgO32kSo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DCiEdVWR.js → OverviewTab-Cb4kR0h1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DCiEdVWR.js.map → OverviewTab-Cb4kR0h1.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-B5SIqVHA.js → ReposTab-Dt1FGnMR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-B5SIqVHA.js.map → ReposTab-Dt1FGnMR.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DLWFJ9xI.js → SessionsTab-D7D2RW6U.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DLWFJ9xI.js.map → SessionsTab-D7D2RW6U.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-CvGXZyej.js → SetupTab-CgV5xCzz.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-CvGXZyej.js.map → SetupTab-CgV5xCzz.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-wZuQGxLS.js → TimeSavedCard-CCNuJjyO.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-wZuQGxLS.js.map → TimeSavedCard-CCNuJjyO.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-B7aOejch.js → TurnsTab-Cv1OCttJ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-B7aOejch.js.map → TurnsTab-Cv1OCttJ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DTabQo6h.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DTabQo6h.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-DhWHjAtt.js → WikiTab-B48QnQrK.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-DhWHjAtt.js.map → WikiTab-B48QnQrK.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-BrC-W87h.js → button-BBQl2MiF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-BrC-W87h.js.map → button-BBQl2MiF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-0rgfRgr3.js → card-BYbefUur.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-0rgfRgr3.js.map → card-BYbefUur.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DRresOQF.js → generateCategoricalChart-swm0YZe0.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DRresOQF.js.map → generateCategoricalChart-swm0YZe0.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-DohmtZWL.js → index-DeIky7xm.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-DohmtZWL.js.map → index-DeIky7xm.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{switch-Bs8g_zB5.js → switch-CctU78ww.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-Bs8g_zB5.js.map → switch-CctU78ww.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-DVVauIo8.js → toggle-BRCNCoiI.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-DVVauIo8.js.map → toggle-BRCNCoiI.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-DxH1PTAR.js → useSSE-WfOiWDH_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-DxH1PTAR.js.map → useSSE-WfOiWDH_.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/VectorCortexClosureCard.tsx +46 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexLedgerCard.tsx +80 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +12 -164
- package/extensions/dashboard-client/src/tabs/VectorCortexTopologyCard.tsx +131 -0
- package/extensions/dashboard-client/src/types/vector-cortex.ts +14 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex-heal.ts +43 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex.ts +2 -0
- package/extensions/dashboard-server/route-dispatch.ts +112 -0
- package/extensions/dashboard-server/routes-rag-settings-helpers.ts +11 -135
- package/extensions/dashboard-server/routes-rag-settings-types.ts +34 -0
- package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +148 -0
- package/extensions/dashboard-server/routes-vector-cortex-heal.ts +56 -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 +3 -86
- package/package.json +1 -1
- package/src/config/dedup.ts +9 -0
- package/src/config/vector-cortex.ts +13 -0
- package/src/config.ts +1 -0
- package/src/dedup-audit.test-helpers.ts +49 -0
- package/src/monitoring.ts +10 -0
- package/src/vector-cortex/heal/_acceptance-fixture.ts +108 -0
- package/src/vector-cortex/heal/closure-metrics.ts +73 -0
- package/src/vector-cortex/heal/closure-opt.ts +266 -0
- package/src/vector-cortex/heal/emit.ts +93 -0
- package/src/vector-cortex/heal/proof.ts +234 -0
- package/src/vector-cortex/heal/types.ts +226 -0
- package/src/vectorStore/add-l0.ts +152 -0
- package/src/vectorStore/add-l1.ts +57 -0
- package/src/vectorStore/add.ts +269 -0
- package/src/vectorStore/class.ts +17 -327
- package/src/vectorStore/dedup-audit.ts +199 -0
- package/src/vectorStore.ts +1 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-eHVt9yDx.js +0 -2
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-eHVt9yDx.js.map +0 -1
|
@@ -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
|
+
}
|