pi-mega-compact 0.20.7 → 0.20.9
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 +25 -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 +104 -0
- package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +2 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-heal.js +86 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex.js +3 -0
- package/dist/extensions/dashboard-server/routes.js +1 -1
- package/dist/extensions/dashboard-server/server.js +3 -82
- package/dist/src/config/vector-cortex.js +25 -0
- package/dist/src/config.js +1 -1
- package/dist/src/vector-cortex/heal/_acceptance-fixture.js +56 -0
- package/dist/src/vector-cortex/heal/_restore-fixture.js +96 -0
- package/dist/src/vector-cortex/heal/_vc6b-helpers.js +35 -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/restore-emit.js +70 -0
- package/dist/src/vector-cortex/heal/restore-readers.js +114 -0
- package/dist/src/vector-cortex/heal/restore-types.js +66 -0
- package/dist/src/vector-cortex/heal/restore.js +140 -0
- package/dist/src/vector-cortex/heal/types.js +43 -0
- package/dist/src/vector-cortex/heal/verify.js +75 -0
- package/dist/vector-cortex/heal/_acceptance-fixture.js +56 -0
- package/dist/vector-cortex/heal/_restore-fixture.js +96 -0
- package/dist/vector-cortex/heal/_vc6b-helpers.js +35 -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/restore-emit.js +70 -0
- package/dist/vector-cortex/heal/restore-readers.js +114 -0
- package/dist/vector-cortex/heal/restore-types.js +66 -0
- package/dist/vector-cortex/heal/restore.js +140 -0
- package/dist/vector-cortex/heal/types.js +43 -0
- package/dist/vector-cortex/heal/verify.js +75 -0
- package/extensions/dashboard-client/dist/assets/{AreaChart-DqTbaSRA.js → AreaChart-fxyq2KaH.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-DqTbaSRA.js.map → AreaChart-fxyq2KaH.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-BfNBeOvF.js → BarChart-D5EqAINP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-BfNBeOvF.js.map → BarChart-D5EqAINP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-C9SOMYB5.js → CacheTab-D4UWbzLo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-C9SOMYB5.js.map → CacheTab-D4UWbzLo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-GsMKP10_.js → EventsTab-BiXquGrX.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-GsMKP10_.js.map → EventsTab-BiXquGrX.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-DAOCXwRP.js → HealthTab-D5QvoTjy.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-DAOCXwRP.js.map → HealthTab-D5QvoTjy.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BtBeMkB5.js → MaintenanceTab-Cy_-CWgM.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BtBeMkB5.js.map → MaintenanceTab-Cy_-CWgM.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DzndtgkZ.js → MemoryMapTab-DrAjgGOR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DzndtgkZ.js.map → MemoryMapTab-DrAjgGOR.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DyxJ4eb8.js → MetricsTab-BvPHcuFN.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DyxJ4eb8.js.map → MetricsTab-BvPHcuFN.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DCiEdVWR.js → OverviewTab-DsbIigOQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DCiEdVWR.js.map → OverviewTab-DsbIigOQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-B5SIqVHA.js → ReposTab-7v_uZX_a.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-B5SIqVHA.js.map → ReposTab-7v_uZX_a.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DLWFJ9xI.js → SessionsTab-BXGrv-6C.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DLWFJ9xI.js.map → SessionsTab-BXGrv-6C.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-CvGXZyej.js → SetupTab-QRUXa65y.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-CvGXZyej.js.map → SetupTab-QRUXa65y.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-wZuQGxLS.js → TimeSavedCard-p327ZZKT.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-wZuQGxLS.js.map → TimeSavedCard-p327ZZKT.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-B7aOejch.js → TurnsTab-BcmBSlik.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-B7aOejch.js.map → TurnsTab-BcmBSlik.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-D2fjKNyT.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-D2fjKNyT.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-DhWHjAtt.js → WikiTab-DLXFMuNy.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-DhWHjAtt.js.map → WikiTab-DLXFMuNy.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-BrC-W87h.js → button-C9CghmRx.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-BrC-W87h.js.map → button-C9CghmRx.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-0rgfRgr3.js → card-DWU-1y3t.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-0rgfRgr3.js.map → card-DWU-1y3t.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DRresOQF.js → generateCategoricalChart-Dayy9dGn.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DRresOQF.js.map → generateCategoricalChart-Dayy9dGn.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-DohmtZWL.js → index-D9PsW3VZ.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-DohmtZWL.js.map → index-D9PsW3VZ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{switch-Bs8g_zB5.js → switch-CnLFAmVT.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-Bs8g_zB5.js.map → switch-CnLFAmVT.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-DVVauIo8.js → toggle-HN5C7_vy.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-DVVauIo8.js.map → toggle-HN5C7_vy.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-DxH1PTAR.js → useSSE-DoJfk3Ce.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-DxH1PTAR.js.map → useSSE-DoJfk3Ce.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/vector-cortex.ts +18 -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/VectorCortexRestoreCard.tsx +42 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +20 -164
- package/extensions/dashboard-client/src/tabs/VectorCortexTopologyCard.tsx +131 -0
- package/extensions/dashboard-client/src/types/vector-cortex.ts +30 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex-heal.ts +73 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex.ts +5 -0
- package/extensions/dashboard-server/route-dispatch.ts +114 -0
- package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +12 -0
- package/extensions/dashboard-server/routes-vector-cortex-heal.ts +103 -0
- package/extensions/dashboard-server/routes-vector-cortex.ts +6 -0
- package/extensions/dashboard-server/routes.ts +2 -0
- package/extensions/dashboard-server/server.ts +3 -86
- package/package.json +1 -1
- package/src/config/vector-cortex.ts +27 -0
- package/src/config.ts +2 -0
- package/src/vector-cortex/heal/_acceptance-fixture.ts +108 -0
- package/src/vector-cortex/heal/_restore-fixture.ts +163 -0
- package/src/vector-cortex/heal/_vc6b-helpers.ts +55 -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/restore-emit.ts +94 -0
- package/src/vector-cortex/heal/restore-readers.ts +145 -0
- package/src/vector-cortex/heal/restore-types.ts +185 -0
- package/src/vector-cortex/heal/restore.ts +165 -0
- package/src/vector-cortex/heal/types.ts +248 -0
- package/src/vector-cortex/heal/verify.ts +93 -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,66 @@
|
|
|
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
|
+
/** Bytewise id comparator — matches `closure-opt.ts` / `reconstruct/closure.ts`. */
|
|
14
|
+
function byBytes(a, b) {
|
|
15
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
16
|
+
}
|
|
17
|
+
/** Anchor id set from a graph (matches `closure-opt.ts`). */
|
|
18
|
+
function anchorIds(graph) {
|
|
19
|
+
return new Set(graph.nodes.filter((n) => n.anchor === true).map((n) => n.id));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Traversal savings as a fraction in [0,1]: how much of the conservative edge
|
|
23
|
+
* walk the optimized plan avoids. A plan with nothing to remove scores 0 (not a
|
|
24
|
+
* failure — a chain graph is already minimal). The sprint's acceptance bar is a
|
|
25
|
+
* >=20% MEDIAN over the corpus, never a per-graph floor.
|
|
26
|
+
*/
|
|
27
|
+
export function traversalSavings(proof) {
|
|
28
|
+
if (proof.conservativeTraversals === 0)
|
|
29
|
+
return 0;
|
|
30
|
+
const saved = proof.conservativeTraversals - proof.optimizedTraversals;
|
|
31
|
+
return saved / proof.conservativeTraversals;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Derive the `RestoreHintV1` list VC6B consumes (the sprint's declared handoff:
|
|
35
|
+
* "VC6B receives missing-source hints and proof").
|
|
36
|
+
*
|
|
37
|
+
* One hint per selected node that a RETAINED edge reaches, naming the cheapest
|
|
38
|
+
* (bytewise-first) retained prerequisite and whether the node is protected.
|
|
39
|
+
* Identity only — never source bytes or prompt text (SECURITY_PRIVACY).
|
|
40
|
+
*/
|
|
41
|
+
export function restoreHints(proof, graph) {
|
|
42
|
+
const anchors = anchorIds(graph);
|
|
43
|
+
const best = new Map();
|
|
44
|
+
for (const row of proof.rows) {
|
|
45
|
+
if (row.decision !== "retained")
|
|
46
|
+
continue;
|
|
47
|
+
const reason = row.reason;
|
|
48
|
+
const existing = best.get(row.to);
|
|
49
|
+
if (existing === undefined || byBytes(row.from, existing.from) < 0) {
|
|
50
|
+
best.set(row.to, { from: row.from, reason });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const hints = [];
|
|
54
|
+
for (const nodeId of [...proof.selected].sort(byBytes)) {
|
|
55
|
+
const hit = best.get(nodeId);
|
|
56
|
+
const isToolPair = proof.retainedEdges.some((e) => e.kind === "tool-pair" && (e.from === nodeId || e.to === nodeId));
|
|
57
|
+
hints.push({
|
|
58
|
+
schema: "restore-hint-v1",
|
|
59
|
+
nodeId,
|
|
60
|
+
viaEdgeFrom: hit?.from,
|
|
61
|
+
protectedNode: anchors.has(nodeId) || isToolPair,
|
|
62
|
+
reason: hit?.reason ?? (anchors.has(nodeId) ? "anchor" : "no-alternate-path"),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return hints;
|
|
66
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
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
|
+
/** Bytewise id comparator — identical to `reconstruct/closure.ts::sortedIds`. */
|
|
38
|
+
function byBytes(a, b) {
|
|
39
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
40
|
+
}
|
|
41
|
+
/** Total order over edges: `from`, then `to`, then `kind` — all bytewise. */
|
|
42
|
+
function compareEdges(x, y) {
|
|
43
|
+
return byBytes(x.from, y.from) || byBytes(x.to, y.to) || byBytes(x.kind, y.kind);
|
|
44
|
+
}
|
|
45
|
+
/** Sorted copy of an edge list (never mutates the caller's array). */
|
|
46
|
+
function sortedEdges(edges) {
|
|
47
|
+
return [...edges].sort(compareEdges);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The requirement adjacency of a plan: for a dependent node, which prerequisites
|
|
51
|
+
* it pulls in. Mirrors `reconstruct/closure.ts::buildRequirements` — `depends`
|
|
52
|
+
* walks backward to the prerequisite, `tool-pair` walks BOTH ways because a pair
|
|
53
|
+
* is atomic. `contradicts` never pulls a node in and is therefore absent here
|
|
54
|
+
* (it is still protected from removal; it simply is not a traversal edge).
|
|
55
|
+
*/
|
|
56
|
+
function requirementAdjacency(edges) {
|
|
57
|
+
const adj = new Map();
|
|
58
|
+
const add = (key, value) => {
|
|
59
|
+
const list = adj.get(key);
|
|
60
|
+
if (list === undefined)
|
|
61
|
+
adj.set(key, [value]);
|
|
62
|
+
else
|
|
63
|
+
list.push(value);
|
|
64
|
+
};
|
|
65
|
+
for (const e of edges) {
|
|
66
|
+
if (e.kind === "depends")
|
|
67
|
+
add(e.to, e.from);
|
|
68
|
+
else if (e.kind === "tool-pair") {
|
|
69
|
+
add(e.to, e.from);
|
|
70
|
+
add(e.from, e.to);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const list of adj.values())
|
|
74
|
+
list.sort(byBytes);
|
|
75
|
+
return adj;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Is `target` reachable from `start` through the requirement adjacency WITHOUT
|
|
79
|
+
* using the direct `start ⇒ target` hop? Returns the witnessing intermediate
|
|
80
|
+
* vertex (the first hop of the detour, bytewise-smallest) or `null`.
|
|
81
|
+
*
|
|
82
|
+
* This is the "alternate path exists" test that licenses a removal. The direct
|
|
83
|
+
* hop is excluded so an edge can never justify its own removal; a cycle simply
|
|
84
|
+
* revisits `seen` and terminates.
|
|
85
|
+
*/
|
|
86
|
+
function alternatePathVia(adj, start, target) {
|
|
87
|
+
// First hops other than the direct one, in bytewise order so the witness is
|
|
88
|
+
// deterministic when several detours exist.
|
|
89
|
+
const firstHops = (adj.get(start) ?? []).filter((n) => n !== target);
|
|
90
|
+
for (const hop of firstHops) {
|
|
91
|
+
// Breadth-first from this hop; `seen` excludes `start` so a detour that
|
|
92
|
+
// loops back through the origin cannot be counted as progress.
|
|
93
|
+
const seen = new Set([start]);
|
|
94
|
+
const worklist = [hop];
|
|
95
|
+
while (worklist.length > 0) {
|
|
96
|
+
worklist.sort(byBytes);
|
|
97
|
+
const current = worklist.shift();
|
|
98
|
+
if (current === undefined)
|
|
99
|
+
break;
|
|
100
|
+
if (current === target)
|
|
101
|
+
return hop;
|
|
102
|
+
if (seen.has(current))
|
|
103
|
+
continue;
|
|
104
|
+
seen.add(current);
|
|
105
|
+
for (const next of adj.get(current) ?? []) {
|
|
106
|
+
if (!seen.has(next))
|
|
107
|
+
worklist.push(next);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Is `target` reachable from `start` using ONLY the edges in `others` (a path of
|
|
115
|
+
* length >= 2, never the direct hop)? Returns the bytewise-smallest first-hop
|
|
116
|
+
* vertex (the witness `via`) or `null`.
|
|
117
|
+
*
|
|
118
|
+
* The transitive-reduction correctness rule: an edge `start ⇒ target` is
|
|
119
|
+
* redundant only if a length>=2 path exists through the OTHER edges. Crucially
|
|
120
|
+
* we EXCLUDE the edge under consideration, so removing one edge never invalidates
|
|
121
|
+
* the witness used to justify removing ANOTHER — a plain single-pass check over
|
|
122
|
+
* the full adjacency over-removes (HEAL-PROTECT-002: a tool pair backs two
|
|
123
|
+
* depends edges that would each look implied by the full graph but only survive
|
|
124
|
+
* together).
|
|
125
|
+
*/
|
|
126
|
+
function alternatePathExcluding(others, start, target) {
|
|
127
|
+
const adj = requirementAdjacency(others);
|
|
128
|
+
return alternatePathVia(adj, start, target);
|
|
129
|
+
}
|
|
130
|
+
/** The set of node ids marked as anchor-floor members in the graph. */
|
|
131
|
+
function anchorIds(graph) {
|
|
132
|
+
const out = new Set();
|
|
133
|
+
for (const n of graph.nodes)
|
|
134
|
+
if (n.anchor === true)
|
|
135
|
+
out.add(n.id);
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Count how many `depends` edges pull in each prerequisite. A prerequisite with
|
|
140
|
+
* exactly one such edge has a SOLE dependency edge: removing it would drop the
|
|
141
|
+
* prerequisite from the closure entirely, so it is protected regardless of any
|
|
142
|
+
* alternate path.
|
|
143
|
+
*/
|
|
144
|
+
function dependsFanIn(edges) {
|
|
145
|
+
const counts = new Map();
|
|
146
|
+
for (const e of edges) {
|
|
147
|
+
if (e.kind !== "depends")
|
|
148
|
+
continue;
|
|
149
|
+
counts.set(e.from, (counts.get(e.from) ?? 0) + 1);
|
|
150
|
+
}
|
|
151
|
+
return counts;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Classify a NON-removable edge, or return `null` when the edge is an ordinary
|
|
155
|
+
* `depends` edge that removal is at least allowed to consider.
|
|
156
|
+
*
|
|
157
|
+
* Order matters and is deliberate: the strongest structural guarantee is
|
|
158
|
+
* reported first, so a tool-pair edge that also touches an anchor is recorded as
|
|
159
|
+
* `tool-pair` and the proof reads as the reason a reviewer would give.
|
|
160
|
+
*/
|
|
161
|
+
function protectedReason(edge, anchors, fanIn) {
|
|
162
|
+
if (edge.kind === "tool-pair")
|
|
163
|
+
return "tool-pair";
|
|
164
|
+
if (edge.kind === "contradicts")
|
|
165
|
+
return "contradiction";
|
|
166
|
+
if (anchors.has(edge.from) || anchors.has(edge.to))
|
|
167
|
+
return "anchor";
|
|
168
|
+
if ((fanIn.get(edge.from) ?? 0) <= 1)
|
|
169
|
+
return "sole-dependency";
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Optimize the already-mandatory closure by transitive reduction (task 2).
|
|
174
|
+
*
|
|
175
|
+
* Only edges INSIDE the closed selection are considered: an edge touching an
|
|
176
|
+
* unselected node is not part of the traversal plan for this closure at all, so
|
|
177
|
+
* it is neither retained nor removed — it is simply out of scope and produces no
|
|
178
|
+
* proof row.
|
|
179
|
+
*
|
|
180
|
+
* `selected` is copied through UNCHANGED. That is the whole safety property: the
|
|
181
|
+
* optimizer is allowed to make the plan cheaper, never to make it smaller.
|
|
182
|
+
*/
|
|
183
|
+
export function optimizeClosure(input) {
|
|
184
|
+
const { graph, conservative } = input;
|
|
185
|
+
const selected = new Set(conservative.selected);
|
|
186
|
+
const anchors = anchorIds(graph);
|
|
187
|
+
// Consider only in-selection edges, in deterministic bytewise order.
|
|
188
|
+
const considered = sortedEdges(graph.edges.filter((e) => selected.has(e.from) && selected.has(e.to)));
|
|
189
|
+
const fanIn = dependsFanIn(considered);
|
|
190
|
+
const rows = [];
|
|
191
|
+
const retained = [];
|
|
192
|
+
const removed = [];
|
|
193
|
+
for (const e of considered) {
|
|
194
|
+
const guard = protectedReason(e, anchors, fanIn);
|
|
195
|
+
if (guard !== null) {
|
|
196
|
+
// Protected: retained unconditionally, EVEN WHEN an alternate path exists
|
|
197
|
+
// (HEAL-PROTECT-002). The reason names the guarantee, not the topology.
|
|
198
|
+
rows.push({ from: e.from, to: e.to, kind: e.kind, decision: "retained", reason: guard });
|
|
199
|
+
retained.push(e);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
// Ordinary `depends` edge: removable iff the requirement it carries is
|
|
203
|
+
// already carried by a longer path `to ⇒ via ⇒ ... ⇒ from` through the OTHER
|
|
204
|
+
// edges (the edge under test is excluded so removals stay mutually valid).
|
|
205
|
+
const others = considered.filter((o) => o !== e);
|
|
206
|
+
const via = alternatePathExcluding(others, e.to, e.from);
|
|
207
|
+
if (via === null) {
|
|
208
|
+
rows.push({
|
|
209
|
+
from: e.from,
|
|
210
|
+
to: e.to,
|
|
211
|
+
kind: e.kind,
|
|
212
|
+
decision: "retained",
|
|
213
|
+
reason: "no-alternate-path",
|
|
214
|
+
});
|
|
215
|
+
retained.push(e);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
rows.push({
|
|
219
|
+
from: e.from,
|
|
220
|
+
to: e.to,
|
|
221
|
+
kind: e.kind,
|
|
222
|
+
decision: "removed",
|
|
223
|
+
reason: "transitively-implied",
|
|
224
|
+
via,
|
|
225
|
+
});
|
|
226
|
+
removed.push(e);
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
schema: "closure-proof-v2",
|
|
230
|
+
sessionId: graph.sessionId,
|
|
231
|
+
selected: [...conservative.selected],
|
|
232
|
+
retainedEdges: sortedEdges(retained),
|
|
233
|
+
removedEdges: sortedEdges(removed),
|
|
234
|
+
rows,
|
|
235
|
+
conservativeTraversals: considered.length,
|
|
236
|
+
optimizedTraversals: retained.length,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// Metrics + VC6B handoff (traversal savings, RestoreHintV1) live in the
|
|
240
|
+
// delegate-shell sibling `closure-metrics.ts` to keep this core reduction
|
|
241
|
+
// algorithm under the 300-line soft limit.
|
|
242
|
+
export { traversalSavings, restoreHints } from "./closure-metrics.js";
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
import { VC6A_ENABLED } from "../../config/vector-cortex.js";
|
|
21
|
+
/**
|
|
22
|
+
* Run `fn` only when an emit is available, swallowing and logging nothing on
|
|
23
|
+
* failure (PRACTICES: non-fatal stores). `fn` receives the emit so callers need
|
|
24
|
+
* not null-check.
|
|
25
|
+
*/
|
|
26
|
+
function safe(emit, fn) {
|
|
27
|
+
if (emit === undefined)
|
|
28
|
+
return;
|
|
29
|
+
try {
|
|
30
|
+
fn(emit);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Non-fatal: a reporting failure must never break the agent loop.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** The event names VC6A emits, exported for the dashboard seam and tests. */
|
|
37
|
+
export const HEAL_EVENT_NAMES = [
|
|
38
|
+
"vector_cortex_closure_optimized",
|
|
39
|
+
"vector_cortex_closure_proof_rejected",
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Report a successful optimization. `MEGACOMPACT_VC6A=0` suppresses the VC6A
|
|
43
|
+
* event (flag-off parity) but does not affect the computation that produced
|
|
44
|
+
* `proof`.
|
|
45
|
+
*/
|
|
46
|
+
export function reportClosureOptimized(emit, payload) {
|
|
47
|
+
if (!VC6A_ENABLED())
|
|
48
|
+
return;
|
|
49
|
+
safe(emit, (e) => e("vector_cortex_closure_optimized", {
|
|
50
|
+
ts: undefined,
|
|
51
|
+
event: "vector_cortex_closure_optimized",
|
|
52
|
+
sessionId: payload.sessionId,
|
|
53
|
+
removedEdges: payload.removed,
|
|
54
|
+
retainedEdges: payload.retained,
|
|
55
|
+
traversalSavings: payload.savings,
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
/** Report a rejected proof. Suppressed under flag-off, like the above. */
|
|
59
|
+
export function reportProofRejected(emit, payload) {
|
|
60
|
+
if (!VC6A_ENABLED())
|
|
61
|
+
return;
|
|
62
|
+
safe(emit, (e) => e("vector_cortex_closure_proof_rejected", {
|
|
63
|
+
ts: undefined,
|
|
64
|
+
event: "vector_cortex_closure_proof_rejected",
|
|
65
|
+
sessionId: payload.sessionId,
|
|
66
|
+
reason: payload.reason,
|
|
67
|
+
fallbackMode: payload.mode,
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/heal/proof.ts — VC6A proof verifier (task 4).
|
|
3
|
+
*
|
|
4
|
+
* The optimizer (`closure-opt.ts`) is PURE and cheap, but a proof is cheap to
|
|
5
|
+
* LIE about: a buggy or tampered producer could ship a proof that removes an
|
|
6
|
+
* edge the conservative closure actually needs. The verifier does not trust the
|
|
7
|
+
* proof — it REPLAYS it against the conservative oracle from scratch.
|
|
8
|
+
*
|
|
9
|
+
* REPLAY. From the conservative `ClosureResult` we rebuild the requirement
|
|
10
|
+
* adjacency (`to ⇒ from` for `depends`, both directions for `tool-pair`) over
|
|
11
|
+
* the in-selection edges — exactly the relation `closure-opt` reduced over.
|
|
12
|
+
* For every row the proof claims to REMOVE, the verifier independently confirms
|
|
13
|
+
* the same alternate path exists. For every row it claims to RETAIN, it confirms
|
|
14
|
+
* the protection reason holds (or that no alternate path exists). Any row that
|
|
15
|
+
* fails its own predicate is a witness violation.
|
|
16
|
+
*
|
|
17
|
+
* SELECTED-SET DIVERGENCE (the cardinal sin). The proof must contain the SAME
|
|
18
|
+
* selected set as the conservative oracle, byte for byte. Closure optimization
|
|
19
|
+
* is allowed to make the plan cheaper, never smaller — so if the proof's
|
|
20
|
+
* `selected` differs from the oracle's, that is `HEAL_PROOF_SET_MISMATCH` and the
|
|
21
|
+
* repaired triad must fall back to mode B (the conservative closure) and
|
|
22
|
+
* ultimately C (VC5C's legacy prompt) if even that fails.
|
|
23
|
+
*
|
|
24
|
+
* PURE / DETERMINISTIC / LOCAL, like everything in `heal/`: no storage, no
|
|
25
|
+
* console, no clock, no network (PREVENT-PI-004 / PREVENT-011). The verifier
|
|
26
|
+
* reads only types and the two ClosureEdge/ClosureGraph/ClosureResult shapes.
|
|
27
|
+
*/
|
|
28
|
+
/** Bytewise id comparator — matches `closure-opt.ts` / `reconstruct/closure.ts`. */
|
|
29
|
+
function byBytes(a, b) {
|
|
30
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
31
|
+
}
|
|
32
|
+
/** Requirement adjacency over an edge list, identical to `closure-opt`. */
|
|
33
|
+
function requirementAdjacency(edges) {
|
|
34
|
+
const adj = new Map();
|
|
35
|
+
const add = (key, value) => {
|
|
36
|
+
const list = adj.get(key);
|
|
37
|
+
if (list === undefined)
|
|
38
|
+
adj.set(key, [value]);
|
|
39
|
+
else
|
|
40
|
+
list.push(value);
|
|
41
|
+
};
|
|
42
|
+
for (const row of edges) {
|
|
43
|
+
if (row.kind === "contradicts")
|
|
44
|
+
continue; // not a traversal edge
|
|
45
|
+
if (row.kind === "depends")
|
|
46
|
+
add(row.to, row.from);
|
|
47
|
+
else if (row.kind === "tool-pair") {
|
|
48
|
+
add(row.to, row.from);
|
|
49
|
+
add(row.from, row.to);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const list of adj.values())
|
|
53
|
+
list.sort(byBytes);
|
|
54
|
+
return adj;
|
|
55
|
+
}
|
|
56
|
+
/** Does a detour `start ⇒ via ⇒ ... ⇒ target` exist, excluding the direct hop? */
|
|
57
|
+
function alternatePathVia(adj, start, target) {
|
|
58
|
+
for (const hop of (adj.get(start) ?? []).filter((n) => n !== target)) {
|
|
59
|
+
const seen = new Set([start]);
|
|
60
|
+
const worklist = [hop];
|
|
61
|
+
while (worklist.length > 0) {
|
|
62
|
+
worklist.sort(byBytes);
|
|
63
|
+
const current = worklist.shift();
|
|
64
|
+
if (current === undefined)
|
|
65
|
+
break;
|
|
66
|
+
if (current === target)
|
|
67
|
+
return hop;
|
|
68
|
+
if (seen.has(current))
|
|
69
|
+
continue;
|
|
70
|
+
seen.add(current);
|
|
71
|
+
for (const next of adj.get(current) ?? []) {
|
|
72
|
+
if (!seen.has(next))
|
|
73
|
+
worklist.push(next);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Verify a `ClosureProofV2` produced for an already-closed graph.
|
|
81
|
+
*
|
|
82
|
+
* Returns `{ ok: true, selected }` when the replay confirms the optimized plan is
|
|
83
|
+
* provably equivalent to the conservative closure — mode A may be used. On any
|
|
84
|
+
* divergence it returns `{ ok: false, codes }` listing every failure code; the
|
|
85
|
+
* caller routes to mode B (conservative) and, if that also fails, mode C (legacy,
|
|
86
|
+
* with `semanticLossStated`). The verifier is idempotent — replaying the same
|
|
87
|
+
* proof twice yields the same verdict, which `vc6a-acceptance.test.ts` asserts.
|
|
88
|
+
*/
|
|
89
|
+
export function verifyProof(proof, conservative, graph) {
|
|
90
|
+
const codes = [];
|
|
91
|
+
const inSelection = new Set(conservative.selected);
|
|
92
|
+
const anchors = new Set((graph?.nodes ?? []).filter((n) => n.anchor === true).map((n) => n.id));
|
|
93
|
+
// 1. Selected-set divergence is the hard gate (task 4: HEAL_PROOF_SET_MISMATCH).
|
|
94
|
+
const oracleSelected = [...conservative.selected].sort(byBytes);
|
|
95
|
+
const proofSelected = [...proof.selected].sort(byBytes);
|
|
96
|
+
const selectedMatch = oracleSelected.length === proofSelected.length &&
|
|
97
|
+
oracleSelected.every((id, i) => id === proofSelected[i]);
|
|
98
|
+
if (!selectedMatch)
|
|
99
|
+
codes.push("HEAL_PROOF_SET_MISMATCH");
|
|
100
|
+
// 2. Re-check each row against its own predicate. For a removal, the witness
|
|
101
|
+
// is validated against the OTHER rows (the reduced graph), so a removal
|
|
102
|
+
// never justifies another's witness — this catches both a missing-witness
|
|
103
|
+
// removal (HEAL-PROOF-003) and a protected edge that was removed
|
|
104
|
+
// (HEAL_PROOF_PROTECTED_REMOVED).
|
|
105
|
+
for (const row of proof.rows) {
|
|
106
|
+
// Out-of-scope rows (touch an unselected node) should never have been
|
|
107
|
+
// emitted; treat as a witness violation so a malformed proof fails loudly.
|
|
108
|
+
if (!inSelection.has(row.from) || !inSelection.has(row.to)) {
|
|
109
|
+
codes.push("HEAL_PROOF_WITNESS_INVALID");
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (row.decision === "removed") {
|
|
113
|
+
// A removal MUST carry a witness and the witness MUST still validate
|
|
114
|
+
// against the OTHER rows (the reduced graph) — never the row itself, so a
|
|
115
|
+
// removal never justifies another's witness (matches the optimizer's
|
|
116
|
+
// exclusion rule).
|
|
117
|
+
if (row.via === undefined) {
|
|
118
|
+
codes.push("HEAL_PROOF_WITNESS_INVALID");
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const others = proof.rows.filter((r) => r !== row);
|
|
122
|
+
const witness = alternatePathVia(requirementAdjacency(others), row.to, row.from);
|
|
123
|
+
if (witness === null) {
|
|
124
|
+
codes.push("HEAL_PROOF_WITNESS_INVALID");
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (anchors.has(row.from) || anchors.has(row.to)) {
|
|
128
|
+
// Topology says removable, but an anchor edge is NEVER removable.
|
|
129
|
+
codes.push("HEAL_PROOF_PROTECTED_REMOVED");
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (row.kind === "tool-pair" || row.kind === "contradicts") {
|
|
133
|
+
// Both are protected regardless of topology.
|
|
134
|
+
codes.push("HEAL_PROOF_PROTECTED_REMOVED");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// A retained plain depends edge that claims `no-alternate-path` but
|
|
139
|
+
// actually HAS one (through the other rows) is an internal inconsistency:
|
|
140
|
+
// the optimizer kept a removable edge without removing it. The proof is
|
|
141
|
+
// internally contradictory, so it fails replay.
|
|
142
|
+
if (row.kind === "depends" && row.reason === "no-alternate-path") {
|
|
143
|
+
const others = proof.rows.filter((r) => r !== row);
|
|
144
|
+
const hasPath = alternatePathVia(requirementAdjacency(others), row.to, row.from);
|
|
145
|
+
if (hasPath !== null)
|
|
146
|
+
codes.push("HEAL_PROOF_WITNESS_INVALID");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// 3. Completeness: every in-selection edge the optimizer considered must appear
|
|
151
|
+
// in the proof. A dropped row (the unique-injection test drops one) surfaces
|
|
152
|
+
// as HEAL_PROOF_INCOMPLETE even if the remaining rows replay fine — the proof
|
|
153
|
+
// must account for EVERY considered edge. `conservativeTraversals` is the
|
|
154
|
+
// optimizer's own record of how many edges it fed through, so `rows.length`
|
|
155
|
+
// must equal it exactly.
|
|
156
|
+
if (proof.rows.length < proof.conservativeTraversals) {
|
|
157
|
+
codes.push("HEAL_PROOF_INCOMPLETE");
|
|
158
|
+
}
|
|
159
|
+
if (codes.length === 0) {
|
|
160
|
+
return { ok: true, selected: proofSelected };
|
|
161
|
+
}
|
|
162
|
+
return { ok: false, codes: [...new Set(codes)] };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Drive the A/B/C resilient triad for one closure (task 5 wiring; VC6A exposes
|
|
166
|
+
* the decision; the actual selection/budget work is VC4C/VC5A respectively).
|
|
167
|
+
*
|
|
168
|
+
* - A: the optimized proof verified cleanly → ship the optimized plan.
|
|
169
|
+
* - B: proof rejected → fall back to the conservative VC4C closure (no opt).
|
|
170
|
+
* - C: B also failed (should not happen for a sound conservative closure) →
|
|
171
|
+
* state the legacy prompt path with `semanticLossStated`.
|
|
172
|
+
*
|
|
173
|
+
* This is PURE: it returns the verdict and which mode to use; it does not emit,
|
|
174
|
+
* store, or touch the network. The emit seam (`emit.ts`) consumes the outcome.
|
|
175
|
+
*/
|
|
176
|
+
export function selectHealMode(proof, conservative, graph) {
|
|
177
|
+
const verification = verifyProof(proof, conservative, graph);
|
|
178
|
+
if (verification.ok) {
|
|
179
|
+
return {
|
|
180
|
+
mode: "A",
|
|
181
|
+
proof,
|
|
182
|
+
selected: [...proof.selected],
|
|
183
|
+
codes: [],
|
|
184
|
+
semanticLossStated: false,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
// Mode B: conservative closure is sound by construction (it is the oracle the
|
|
188
|
+
// proof was measured against). Use it directly.
|
|
189
|
+
return {
|
|
190
|
+
mode: "B",
|
|
191
|
+
proof: null,
|
|
192
|
+
selected: [...conservative.selected],
|
|
193
|
+
codes: verification.codes,
|
|
194
|
+
semanticLossStated: false,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Convenience for the rare B-failure path: if even the conservative oracle's
|
|
199
|
+
* selection cannot be honored, state the legacy prompt and MARK semantic loss.
|
|
200
|
+
* Mode C is the last resort and the only place semantic loss is ever admitted.
|
|
201
|
+
*/
|
|
202
|
+
export function legacyFallback(conservative, reason) {
|
|
203
|
+
return {
|
|
204
|
+
mode: "C",
|
|
205
|
+
proof: null,
|
|
206
|
+
selected: [...conservative.selected],
|
|
207
|
+
codes: [reason, "HEAL_CLOSURE_REJECTED"],
|
|
208
|
+
semanticLossStated: true,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/heal/restore-emit.ts — VC6B event reporter seam.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `./emit.ts`: a thin `safe()` wrapper around an optional injected
|
|
5
|
+
* `emit` (unit tests pass `undefined` and stay pure), and the two event names the
|
|
6
|
+
* sprint spec requires verbatim:
|
|
7
|
+
* - `vector_cortex_source_restored` — a restore batch completed.
|
|
8
|
+
* - `vector_cortex_restore_digest_rejected` — a source failed its digest check.
|
|
9
|
+
*
|
|
10
|
+
* FLAG SEMANTICS. `restoreSources` / `verifyRestored` are PURE arithmetic and run
|
|
11
|
+
* REGARDLESS of `MEGACOMPACT_VC6B`. The flag gates ONLY this reporting +
|
|
12
|
+
* dashboard seam: with the flag off we still restore and still verify, we just do
|
|
13
|
+
* not announce it under the VC6B event namespace. That is what makes flag-off
|
|
14
|
+
* byte-identical to VC6A — the computation is never skipped, only the emission.
|
|
15
|
+
*
|
|
16
|
+
* PAYLOAD DISCIPLINE. These events carry COUNTS and MODES only — never restored
|
|
17
|
+
* bytes, never node text, never a digest of user content. The exact ledger is not
|
|
18
|
+
* diagnostic data (SECURITY_PRIVACY), and a restoration event is exactly the
|
|
19
|
+
* place where an unguarded `payload` field would leak the entire transcript into
|
|
20
|
+
* a log file.
|
|
21
|
+
*
|
|
22
|
+
* No console, no storage, no network (PREVENT-PI-004 / PREVENT-011). Every line
|
|
23
|
+
* is a structured JSON event with `ts` + `event`.
|
|
24
|
+
*/
|
|
25
|
+
import { VC6B_ENABLED } from "../../config/vector-cortex.js";
|
|
26
|
+
/** Run `fn` only when an emit exists; a reporting failure is never fatal. */
|
|
27
|
+
function safe(emit, fn) {
|
|
28
|
+
if (emit === undefined)
|
|
29
|
+
return;
|
|
30
|
+
try {
|
|
31
|
+
fn(emit);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Non-fatal: a reporting failure must never break the agent loop.
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** The event names VC6B emits, exported for the dashboard seam and tests. */
|
|
38
|
+
export const RESTORE_EVENT_NAMES = [
|
|
39
|
+
"vector_cortex_source_restored",
|
|
40
|
+
"vector_cortex_restore_digest_rejected",
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Report a completed restoration. Counts and mode only — enough to see whether
|
|
44
|
+
* the shard index is serving reads (mode A) or every span is falling through to
|
|
45
|
+
* a ledger scan (mode B), without disclosing what was restored.
|
|
46
|
+
*/
|
|
47
|
+
export function reportSourceRestored(emit, payload) {
|
|
48
|
+
if (!VC6B_ENABLED())
|
|
49
|
+
return;
|
|
50
|
+
safe(emit, (e) => e("vector_cortex_source_restored", {
|
|
51
|
+
ts: undefined,
|
|
52
|
+
event: "vector_cortex_source_restored",
|
|
53
|
+
sessionId: payload.sessionId,
|
|
54
|
+
restoredCount: payload.restoredCount,
|
|
55
|
+
missingCount: payload.missingCount,
|
|
56
|
+
mode: payload.mode,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
/** Report a rejected source (digest/limit/range). Suppressed under flag-off. */
|
|
60
|
+
export function reportRestoreDigestRejected(emit, payload) {
|
|
61
|
+
if (!VC6B_ENABLED())
|
|
62
|
+
return;
|
|
63
|
+
safe(emit, (e) => e("vector_cortex_restore_digest_rejected", {
|
|
64
|
+
ts: undefined,
|
|
65
|
+
event: "vector_cortex_restore_digest_rejected",
|
|
66
|
+
sessionId: payload.sessionId,
|
|
67
|
+
code: payload.code,
|
|
68
|
+
mode: payload.mode,
|
|
69
|
+
}));
|
|
70
|
+
}
|