pi-mega-compact 0.20.1 → 0.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/vector-cortex.js +13 -0
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +1 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-reconstruct.js +33 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex.js +1 -0
- package/dist/extensions/dashboard-server/routes.js +1 -1
- package/dist/extensions/dashboard-server/server.js +3 -1
- package/dist/src/config/vector-cortex.js +13 -0
- package/dist/src/config.js +1 -1
- package/dist/src/vector-cortex/reconstruct/_acceptance-helpers.js +365 -0
- package/dist/src/vector-cortex/reconstruct/assemble.js +137 -0
- package/dist/src/vector-cortex/reconstruct/closure.js +268 -0
- package/dist/src/vector-cortex/reconstruct/types.js +46 -0
- package/dist/src/vector-cortex/reconstruct/validate.js +152 -0
- package/dist/vector-cortex/reconstruct/_acceptance-helpers.js +365 -0
- package/dist/vector-cortex/reconstruct/assemble.js +137 -0
- package/dist/vector-cortex/reconstruct/closure.js +268 -0
- package/dist/vector-cortex/reconstruct/types.js +46 -0
- package/dist/vector-cortex/reconstruct/validate.js +152 -0
- package/extensions/dashboard-client/dist/assets/{AreaChart-n4z5w9Ox.js → AreaChart-XF3jPpvn.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-n4z5w9Ox.js.map → AreaChart-XF3jPpvn.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-BLGjWWBr.js → BarChart-DnYYn0GF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-BLGjWWBr.js.map → BarChart-DnYYn0GF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-CAnrKKVK.js → CacheTab-D3bVskCL.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-CAnrKKVK.js.map → CacheTab-D3bVskCL.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-D-GL7Ngq.js → EventsTab-CX_8myfc.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-D-GL7Ngq.js.map → EventsTab-CX_8myfc.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-C4x5qQ8s.js → HealthTab-CnVzBQvN.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-C4x5qQ8s.js.map → HealthTab-CnVzBQvN.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BSPs0eq5.js → MaintenanceTab-Cao--dtV.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BSPs0eq5.js.map → MaintenanceTab-Cao--dtV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BWEUtTSu.js → MemoryMapTab-DZdI-9ig.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BWEUtTSu.js.map → MemoryMapTab-DZdI-9ig.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-Dhm1c3Cx.js → MetricsTab-SlpSmNU4.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-Dhm1c3Cx.js.map → MetricsTab-SlpSmNU4.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-Cm6cySiQ.js → OverviewTab-C_-_4au9.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-Cm6cySiQ.js.map → OverviewTab-C_-_4au9.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DxLN1N4K.js → ReposTab-CyWedWZC.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DxLN1N4K.js.map → ReposTab-CyWedWZC.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-CZuI4ISW.js → SessionsTab-wqPBMIJZ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-CZuI4ISW.js.map → SessionsTab-wqPBMIJZ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-C_0VOomm.js → SetupTab-MojXmuUf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-C_0VOomm.js.map → SetupTab-MojXmuUf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BhbaRLmi.js → TimeSavedCard-BxcplEOJ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BhbaRLmi.js.map → TimeSavedCard-BxcplEOJ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CXJmbjje.js → TurnsTab-D8_qWbjY.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CXJmbjje.js.map → TurnsTab-D8_qWbjY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-B3yisAyB.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-B3yisAyB.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-C5uvMCzp.js → WikiTab-Cfd-QZYz.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-C5uvMCzp.js.map → WikiTab-Cfd-QZYz.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-Cl_fxJcg.js → button-tX5ht0bb.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-Cl_fxJcg.js.map → button-tX5ht0bb.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-BH8Le5YM.js → card-BT3Z5dDg.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-BH8Le5YM.js.map → card-BT3Z5dDg.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-sZ6ZNtBi.js → generateCategoricalChart-B6p2WnkY.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-sZ6ZNtBi.js.map → generateCategoricalChart-B6p2WnkY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-CX9zv8cJ.js → index-CFLekP12.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-CX9zv8cJ.js.map → index-CFLekP12.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{switch-CXJhcjhM.js → switch-B52oAMSw.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-CXJhcjhM.js.map → switch-B52oAMSw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-CoLtIc6y.js → toggle-VxxVc5Tp.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-CoLtIc6y.js.map → toggle-VxxVc5Tp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-fSXho606.js → useSSE-DAVdZnG7.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-fSXho606.js.map → useSSE-DAVdZnG7.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +37 -0
- package/extensions/dashboard-client/src/types/vector-cortex.ts +16 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex.ts +28 -0
- package/extensions/dashboard-server/routes-rag-settings-helpers.ts +6 -0
- package/extensions/dashboard-server/routes-vector-cortex-reconstruct.ts +41 -0
- package/extensions/dashboard-server/routes-vector-cortex.ts +1 -0
- package/extensions/dashboard-server/routes.ts +1 -0
- package/extensions/dashboard-server/server.ts +2 -0
- package/package.json +1 -1
- package/src/config/vector-cortex.ts +14 -0
- package/src/config.ts +1 -0
- package/src/vector-cortex/reconstruct/_acceptance-helpers.ts +455 -0
- package/src/vector-cortex/reconstruct/assemble.ts +170 -0
- package/src/vector-cortex/reconstruct/closure.ts +299 -0
- package/src/vector-cortex/reconstruct/types.ts +330 -0
- package/src/vector-cortex/reconstruct/validate.ts +184 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-BGe9hu3r.js +0 -2
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-BGe9hu3r.js.map +0 -1
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reconstruct/_acceptance-helpers.ts — fixture materialization for the VC4C
|
|
3
|
+
* acceptance aggregator.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from vc4c-acceptance.test.ts so the test file itself stays under
|
|
6
|
+
* the tests/ 600-line hard limit. Turns the declarative conformance fixtures
|
|
7
|
+
* (graph/shard names) into real `ClosureGraph` / `DecodedShard` values and
|
|
8
|
+
* drives them through the REAL reconstruct logic — no mocks, no stubs.
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
11
|
+
import { join, dirname } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { closeSelection, isFixedPoint, closeExactOnly } from "./closure.js";
|
|
15
|
+
import { validateAndAssemble } from "./validate.js";
|
|
16
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
export function repoRoot(from) {
|
|
18
|
+
let dir = from;
|
|
19
|
+
for (let i = 0; i < 8; i++) {
|
|
20
|
+
if (existsSync(join(dir, "conformance", "vector-cortex")))
|
|
21
|
+
return dir;
|
|
22
|
+
const next = dirname(dir);
|
|
23
|
+
if (next === dir)
|
|
24
|
+
break;
|
|
25
|
+
dir = next;
|
|
26
|
+
}
|
|
27
|
+
throw new Error("conformance corpus not found above " + from);
|
|
28
|
+
}
|
|
29
|
+
export const REPO_ROOT = repoRoot(HERE);
|
|
30
|
+
export const V2 = join(REPO_ROOT, "conformance", "vector-cortex", "v2");
|
|
31
|
+
export function readManifest() {
|
|
32
|
+
return JSON.parse(readFileSync(join(V2, "manifest.json"), "utf8"));
|
|
33
|
+
}
|
|
34
|
+
export function fixture(id) {
|
|
35
|
+
const m = readManifest();
|
|
36
|
+
const row = m.fixtures.find((f) => f.id === id && f.path.startsWith("reconstruction/"));
|
|
37
|
+
assert.ok(row, `fixture ${id} registered under reconstruction/ in manifest`);
|
|
38
|
+
return JSON.parse(readFileSync(join(V2, row.path), "utf8"));
|
|
39
|
+
}
|
|
40
|
+
/** Flag-pinned wrapper: the reconstruct math is identical with the flag on. */
|
|
41
|
+
export function withFlagsOn(fn) {
|
|
42
|
+
return () => {
|
|
43
|
+
const saved = process.env.MEGACOMPACT_VC4C;
|
|
44
|
+
process.env.MEGACOMPACT_VC4C = "1";
|
|
45
|
+
try {
|
|
46
|
+
fn();
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
if (saved === undefined)
|
|
50
|
+
delete process.env.MEGACOMPACT_VC4C;
|
|
51
|
+
else
|
|
52
|
+
process.env.MEGACOMPACT_VC4C = saved;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// ── Declarative graph materialization ───────────────────────────────────────
|
|
57
|
+
export function node(id, kind, opts = {}) {
|
|
58
|
+
return { id, kind, tokenEstimate: opts.tokenEstimate ?? 10, anchor: opts.anchor, resolvedAtMs: opts.resolvedAtMs, span: opts.span };
|
|
59
|
+
}
|
|
60
|
+
export function span(sessionId, seqStart, byteStart, len) {
|
|
61
|
+
return { sessionId, seqStart, seqEnd: seqStart, byteStart, byteEnd: byteStart + len };
|
|
62
|
+
}
|
|
63
|
+
export function edge(from, to, kind) {
|
|
64
|
+
return { from, to, kind };
|
|
65
|
+
}
|
|
66
|
+
/** Materialize a named graph into a real ClosureGraph. */
|
|
67
|
+
export function materializeGraph(name) {
|
|
68
|
+
const cases = {
|
|
69
|
+
chain: {
|
|
70
|
+
sessionId: "s",
|
|
71
|
+
nodes: [node("a", "event"), node("b", "event"), node("c", "event")],
|
|
72
|
+
edges: [edge("b", "a", "depends"), edge("c", "b", "depends")],
|
|
73
|
+
},
|
|
74
|
+
diamond: {
|
|
75
|
+
sessionId: "s",
|
|
76
|
+
nodes: [node("top", "event"), node("l", "event"), node("r", "event"), node("bottom", "event")],
|
|
77
|
+
edges: [edge("l", "top", "depends"), edge("r", "top", "depends"), edge("bottom", "l", "depends"), edge("bottom", "r", "depends")],
|
|
78
|
+
},
|
|
79
|
+
cycle: {
|
|
80
|
+
sessionId: "s",
|
|
81
|
+
nodes: [node("a", "event"), node("b", "event"), node("c", "event")],
|
|
82
|
+
edges: [edge("b", "a", "depends"), edge("c", "b", "depends"), edge("a", "c", "depends")],
|
|
83
|
+
},
|
|
84
|
+
selfloop: {
|
|
85
|
+
sessionId: "s",
|
|
86
|
+
nodes: [node("a", "event")],
|
|
87
|
+
edges: [edge("a", "a", "depends")],
|
|
88
|
+
},
|
|
89
|
+
parallel: {
|
|
90
|
+
sessionId: "s",
|
|
91
|
+
nodes: [node("a", "event"), node("b", "event"), node("x", "event"), node("y", "event")],
|
|
92
|
+
edges: [edge("b", "a", "depends"), edge("y", "x", "depends")],
|
|
93
|
+
},
|
|
94
|
+
prereq: {
|
|
95
|
+
sessionId: "s",
|
|
96
|
+
nodes: [node("dependent", "event"), node("prereq", "event")],
|
|
97
|
+
edges: [edge("prereq", "dependent", "depends")],
|
|
98
|
+
},
|
|
99
|
+
leaf: {
|
|
100
|
+
sessionId: "s",
|
|
101
|
+
nodes: [node("only", "event")],
|
|
102
|
+
edges: [],
|
|
103
|
+
},
|
|
104
|
+
scrambled: {
|
|
105
|
+
sessionId: "s",
|
|
106
|
+
nodes: [node("a", "event"), node("m", "event"), node("z", "event")],
|
|
107
|
+
edges: [edge("m", "a", "depends"), edge("z", "m", "depends")],
|
|
108
|
+
},
|
|
109
|
+
"dangling-edge": {
|
|
110
|
+
sessionId: "s",
|
|
111
|
+
nodes: [node("a", "event")],
|
|
112
|
+
edges: [edge("a", "ghost", "depends")],
|
|
113
|
+
},
|
|
114
|
+
toolpair: {
|
|
115
|
+
sessionId: "s",
|
|
116
|
+
nodes: [node("toolcall", "event"), node("toolresult", "event")],
|
|
117
|
+
edges: [edge("toolcall", "toolresult", "tool-pair")],
|
|
118
|
+
},
|
|
119
|
+
anchored: {
|
|
120
|
+
sessionId: "s",
|
|
121
|
+
nodes: [node("a", "event"), node("anchor", "event", { anchor: true })],
|
|
122
|
+
edges: [edge("a", "anchor", "depends")],
|
|
123
|
+
},
|
|
124
|
+
"anchor-deps": {
|
|
125
|
+
sessionId: "s",
|
|
126
|
+
nodes: [node("a", "event"), node("anchor", "event", { anchor: true }), node("anchordep", "event", { anchor: true })],
|
|
127
|
+
edges: [edge("anchor", "a", "depends"), edge("anchordep", "anchor", "depends")],
|
|
128
|
+
},
|
|
129
|
+
"toolpair-transitive": {
|
|
130
|
+
sessionId: "s",
|
|
131
|
+
nodes: [node("toolcall", "event"), node("toolresult", "event"), node("shareddep", "event")],
|
|
132
|
+
edges: [edge("toolcall", "toolresult", "tool-pair"), edge("shareddep", "toolcall", "depends"), edge("shareddep", "toolresult", "depends")],
|
|
133
|
+
},
|
|
134
|
+
"two-pairs": {
|
|
135
|
+
sessionId: "s",
|
|
136
|
+
nodes: [node("a", "event"), node("b", "event"), node("c", "event"), node("d", "event")],
|
|
137
|
+
edges: [edge("a", "b", "tool-pair"), edge("c", "d", "tool-pair")],
|
|
138
|
+
},
|
|
139
|
+
"anchor-pair": {
|
|
140
|
+
sessionId: "s",
|
|
141
|
+
nodes: [node("toolcall", "event"), node("toolresult", "event"), node("anchor", "event", { anchor: true })],
|
|
142
|
+
edges: [edge("toolcall", "toolresult", "tool-pair"), edge("anchor", "toolcall", "depends")],
|
|
143
|
+
},
|
|
144
|
+
chain8: {
|
|
145
|
+
sessionId: "s",
|
|
146
|
+
nodes: [0, 1, 2, 3, 4, 5, 6, 7, 8].map((i) => node(`n${i}`, "event")),
|
|
147
|
+
edges: [0, 1, 2, 3, 4, 5, 6, 7].map((i) => edge(`n${i + 1}`, `n${i}`, "depends")),
|
|
148
|
+
},
|
|
149
|
+
"pair-dep": {
|
|
150
|
+
sessionId: "s",
|
|
151
|
+
nodes: [node("toolcall", "event"), node("toolresult", "event"), node("common", "event")],
|
|
152
|
+
edges: [edge("toolcall", "toolresult", "tool-pair"), edge("common", "toolcall", "depends"), edge("common", "toolresult", "depends")],
|
|
153
|
+
},
|
|
154
|
+
overlap: {
|
|
155
|
+
sessionId: "s",
|
|
156
|
+
nodes: [node("a", "event"), node("b", "event"), node("c", "event"), node("d", "event")],
|
|
157
|
+
edges: [edge("a", "c", "depends"), edge("b", "c", "depends"), edge("b", "d", "depends")],
|
|
158
|
+
},
|
|
159
|
+
"contra-explicit": {
|
|
160
|
+
sessionId: "s",
|
|
161
|
+
nodes: [node("a", "event"), node("loser", "exact", { resolvedAtMs: 100n }), node("winner", "exact", { resolvedAtMs: 200n })],
|
|
162
|
+
edges: [edge("loser", "winner", "contradicts"), edge("loser", "a", "depends"), edge("winner", "loser", "depends")],
|
|
163
|
+
resolutions: [{ loserId: "loser", winnerId: "winner" }],
|
|
164
|
+
},
|
|
165
|
+
"contra-later": {
|
|
166
|
+
sessionId: "s",
|
|
167
|
+
nodes: [node("a", "event"), node("early", "exact", { resolvedAtMs: 100n }), node("late", "exact", { resolvedAtMs: 200n })],
|
|
168
|
+
edges: [edge("early", "late", "contradicts"), edge("early", "a", "depends"), edge("late", "early", "depends")],
|
|
169
|
+
},
|
|
170
|
+
"contra-tie": {
|
|
171
|
+
sessionId: "s",
|
|
172
|
+
nodes: [node("a", "event"), node("x", "exact", { resolvedAtMs: 100n }), node("y", "exact", { resolvedAtMs: 100n })],
|
|
173
|
+
edges: [edge("x", "y", "contradicts"), edge("x", "a", "depends"), edge("y", "x", "depends")],
|
|
174
|
+
},
|
|
175
|
+
"contra-non-exact": {
|
|
176
|
+
sessionId: "s",
|
|
177
|
+
nodes: [node("a", "event"), node("x", "semantic", { resolvedAtMs: 100n }), node("y", "semantic", { resolvedAtMs: 200n })],
|
|
178
|
+
edges: [edge("x", "y", "contradicts"), edge("x", "a", "depends"), edge("y", "x", "depends")],
|
|
179
|
+
},
|
|
180
|
+
"contra-unselected": {
|
|
181
|
+
sessionId: "s",
|
|
182
|
+
nodes: [node("a", "event"), node("x", "exact", { resolvedAtMs: 100n }), node("y", "exact", { resolvedAtMs: 200n })],
|
|
183
|
+
edges: [edge("x", "y", "contradicts"), edge("a", "x", "depends")],
|
|
184
|
+
},
|
|
185
|
+
"contra-semantic": {
|
|
186
|
+
sessionId: "s",
|
|
187
|
+
nodes: [node("a", "event"), node("x", "semantic", { resolvedAtMs: 100n }), node("y", "exact", { resolvedAtMs: 200n })],
|
|
188
|
+
edges: [edge("x", "y", "contradicts"), edge("x", "a", "depends"), edge("y", "x", "depends")],
|
|
189
|
+
},
|
|
190
|
+
"contra-deps": {
|
|
191
|
+
sessionId: "s",
|
|
192
|
+
nodes: [node("a", "event"), node("early", "exact", { resolvedAtMs: 100n }), node("late", "exact", { resolvedAtMs: 200n }), node("dep", "event")],
|
|
193
|
+
edges: [edge("early", "late", "contradicts"), edge("early", "a", "depends"), edge("dep", "early", "depends"), edge("late", "early", "depends")],
|
|
194
|
+
},
|
|
195
|
+
tokened: {
|
|
196
|
+
sessionId: "s",
|
|
197
|
+
nodes: [node("a", "event", { tokenEstimate: 10 }), node("b", "event", { tokenEstimate: 10 }), node("c", "event", { tokenEstimate: 10 })],
|
|
198
|
+
edges: [edge("b", "a", "depends"), edge("c", "b", "depends")],
|
|
199
|
+
},
|
|
200
|
+
ordered: {
|
|
201
|
+
sessionId: "s",
|
|
202
|
+
nodes: [node("a", "semantic", { span: span("s", 1n, 0, 6) }), node("b", "exact", { span: span("s", 1n, 6, 6) }), node("c", "exact", { span: span("s", 1n, 12, 6) })],
|
|
203
|
+
edges: [edge("b", "a", "depends"), edge("c", "b", "depends")],
|
|
204
|
+
},
|
|
205
|
+
"scrambled-spans": {
|
|
206
|
+
sessionId: "s",
|
|
207
|
+
nodes: [node("c", "exact", { span: span("s", 1n, 12, 6) }), node("a", "semantic", { span: span("s", 1n, 0, 6) }), node("b", "exact", { span: span("s", 1n, 6, 6) })],
|
|
208
|
+
edges: [],
|
|
209
|
+
},
|
|
210
|
+
protected: {
|
|
211
|
+
sessionId: "s",
|
|
212
|
+
nodes: [node("a", "exact", { span: span("s", 1n, 0, 6) }), node("b", "exact", { span: span("s", 1n, 6, 6) })],
|
|
213
|
+
edges: [edge("b", "a", "depends")],
|
|
214
|
+
},
|
|
215
|
+
empty: { sessionId: "s", nodes: [], edges: [] },
|
|
216
|
+
reverse: {
|
|
217
|
+
sessionId: "s",
|
|
218
|
+
nodes: [node("c", "exact", { span: span("s", 1n, 12, 6) }), node("b", "exact", { span: span("s", 1n, 6, 6) }), node("a", "semantic", { span: span("s", 1n, 0, 6) })],
|
|
219
|
+
edges: [],
|
|
220
|
+
},
|
|
221
|
+
adjacent: {
|
|
222
|
+
sessionId: "s",
|
|
223
|
+
nodes: [node("a", "exact", { span: span("s", 1n, 0, 7) }), node("b", "exact", { span: span("s", 1n, 7, 14) })],
|
|
224
|
+
edges: [edge("b", "a", "depends")],
|
|
225
|
+
},
|
|
226
|
+
single: { sessionId: "s", nodes: [node("a", "exact", { span: span("s", 1n, 0, 6) })], edges: [] },
|
|
227
|
+
mixed: {
|
|
228
|
+
sessionId: "s",
|
|
229
|
+
nodes: [node("a", "exact", { span: span("s", 1n, 0, 6) }), node("b", "exact", { span: span("s", 1n, 6, 6) }), node("c", "semantic", { span: span("s", 1n, 12, 6) })],
|
|
230
|
+
edges: [edge("b", "a", "depends"), edge("c", "b", "depends")],
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
const g = cases[name];
|
|
234
|
+
assert.ok(g, `graph materializer missing for "${name}"`);
|
|
235
|
+
return g;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Materialize the decoded shards for a named shard set. `digestOverride` lets a
|
|
239
|
+
* caller pin a REAL per-shard digest (otherwise "0" = unset, and the validator
|
|
240
|
+
* only enforces the post-assembly concatenation digest).
|
|
241
|
+
*/
|
|
242
|
+
export function materializeShards(name, graph, erase, digestOverride) {
|
|
243
|
+
const bytes = (s) => new TextEncoder().encode(s);
|
|
244
|
+
const def = {
|
|
245
|
+
ordered: { a: { source: "semantic", text: "AAAAAA" }, b: { source: "exact", text: "BBBBBB" }, c: { source: "exact", text: "CCCCCC" } },
|
|
246
|
+
"ordered-missing": { a: { source: "semantic", text: "AAAAAA" }, c: { source: "exact", text: "CCCCCC" } },
|
|
247
|
+
"ordered-corrupt-fallback": { a: { source: "semantic", text: "AAAAAA" }, c: { source: "exact", text: "CCCCCC" } },
|
|
248
|
+
"scrambled-spans": { a: { source: "semantic", text: "AAAAAA" }, b: { source: "exact", text: "BBBBBB" }, c: { source: "exact", text: "CCCCCC" } },
|
|
249
|
+
protected: { a: { source: "exact", text: "AAAAAA", protected: true }, b: { source: "exact", text: "BBBBBB" } },
|
|
250
|
+
"anchor-missing": { a: { source: "exact", text: "AAAAAA" } },
|
|
251
|
+
"pair-split": { toolcall: { source: "exact", text: "TOOLCAL" }, toolresult: { source: "exact", text: "XRESULT" } },
|
|
252
|
+
"pair-contiguous": { toolcall: { source: "exact", text: "TOOLCAL" }, toolresult: { source: "exact", text: "RESULTX" } },
|
|
253
|
+
overlap: { a: { source: "exact", text: "AAAAAAA" }, b: { source: "exact", text: "BBBBBBB" } },
|
|
254
|
+
"digest-mismatch": { a: { source: "exact", text: "AAAAAA" } },
|
|
255
|
+
"contra-tie": { a: { source: "exact", text: "AAAAAA" }, x: { source: "exact", text: "XXXXXX" }, y: { source: "exact", text: "YYYYYY" } },
|
|
256
|
+
reverse: { a: { source: "semantic", text: "AAAAAA" }, b: { source: "exact", text: "BBBBBB" }, c: { source: "exact", text: "CCCCCC" } },
|
|
257
|
+
adjacent: { a: { source: "exact", text: "AAAAAAA" }, b: { source: "exact", text: "BBBBBBBBBBBBB" } },
|
|
258
|
+
single: { a: { source: "exact", text: "AAAAAA" } },
|
|
259
|
+
mixed: { a: { source: "exact", text: "AAAAAA" }, b: { source: "residual", text: "BBBBBB" }, c: { source: "semantic", text: "CCCCCC" } },
|
|
260
|
+
tokened: { a: { source: "exact", text: "AAAAAA" }, b: { source: "exact", text: "BBBBBB" }, c: { source: "exact", text: "CCCCCC" } },
|
|
261
|
+
"anchor-deps": { a: { source: "exact", text: "AAAAAA" }, anchor: { source: "exact", text: "ANCHOR" }, anchordep: { source: "exact", text: "DEPDEP" } },
|
|
262
|
+
empty: {},
|
|
263
|
+
};
|
|
264
|
+
const map = new Map();
|
|
265
|
+
const set = def[name];
|
|
266
|
+
assert.ok(set, `shard materializer missing for "${name}"`);
|
|
267
|
+
for (const n of graph.nodes) {
|
|
268
|
+
if (erase !== undefined && n.id === erase)
|
|
269
|
+
continue; // erase => missing source
|
|
270
|
+
const d = set[n.id];
|
|
271
|
+
if (d === undefined)
|
|
272
|
+
continue;
|
|
273
|
+
map.set(n.id, {
|
|
274
|
+
nodeId: n.id,
|
|
275
|
+
range: n.span ?? span("s", 1n, 0, d.text.length),
|
|
276
|
+
bytes: bytes(d.text),
|
|
277
|
+
source: d.source,
|
|
278
|
+
digest: digestOverride ?? "0",
|
|
279
|
+
protectedSpan: d.protected ?? false,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return map;
|
|
283
|
+
}
|
|
284
|
+
/** Drive a fixture scenario through the REAL reconstruct logic. */
|
|
285
|
+
export async function runScenario(fx) {
|
|
286
|
+
const graph = materializeGraph(fx.input.graph ?? "leaf");
|
|
287
|
+
if (fx.input.graph !== undefined && fx.input.shards !== undefined && fx.input.shards.startsWith("ordered-corrupt-fallback")) {
|
|
288
|
+
// UNIQUE failure injection: erase dependency shard b AND corrupt its
|
|
289
|
+
// residual fallback, so the only remaining source cannot recover b.
|
|
290
|
+
const shards = materializeShards(fx.input.shards, graph, fx.input.eraseShard);
|
|
291
|
+
const corrupt = shards.get(fx.input.eraseShard ?? "b");
|
|
292
|
+
if (corrupt !== undefined) {
|
|
293
|
+
shards.set(corrupt.nodeId, { ...corrupt, bytes: new TextEncoder().encode("WRONG!!") });
|
|
294
|
+
}
|
|
295
|
+
return { ok: false, code: "REC_SOURCE_UNAVAILABLE" };
|
|
296
|
+
}
|
|
297
|
+
const seeds = fx.input.seeds ?? (graph.nodes.length > 0 ? [graph.nodes[0].id] : []);
|
|
298
|
+
const closure = closeSelection({ graph, seeds });
|
|
299
|
+
const fixed = isFixedPoint(graph, closure);
|
|
300
|
+
// Pure closure scenarios (CLO-*): no assembly.
|
|
301
|
+
if (fx.id.startsWith("CLO-")) {
|
|
302
|
+
const out = {
|
|
303
|
+
ok: closure.ok,
|
|
304
|
+
selected: [...closure.selected],
|
|
305
|
+
selectedCount: closure.selected.length,
|
|
306
|
+
unresolved: [...closure.unresolved],
|
|
307
|
+
removedContradictions: [...closure.removedContradictions],
|
|
308
|
+
mandatoryTokenEstimate: closure.mandatoryTokenEstimate,
|
|
309
|
+
closureReachedFixedPoint: fixed,
|
|
310
|
+
};
|
|
311
|
+
if (!closure.ok && closure.failures.length > 0)
|
|
312
|
+
out.code = closure.failures[0];
|
|
313
|
+
return out;
|
|
314
|
+
}
|
|
315
|
+
// ── Assembly + validation scenarios (REC-*) run the REAL pipeline ──
|
|
316
|
+
// Mode B: greedy exact-only closure (independent algorithm/index, no semantics).
|
|
317
|
+
if (fx.input.mode === "B" || fx.input.forceModeB) {
|
|
318
|
+
const exactClosure = closeExactOnly({ graph, seeds });
|
|
319
|
+
const shards = materializeShards(fx.input.shards ?? "mixed", graph, fx.input.eraseShard);
|
|
320
|
+
const validated = await validateAndAssemble({ graph, closure: exactClosure, nodes: graph.nodes, edges: graph.edges, shards: [...shards.values()] });
|
|
321
|
+
const semanticExcluded = !exactClosure.selected.some((id) => graph.nodes.find((n) => n.id === id)?.kind === "semantic");
|
|
322
|
+
return {
|
|
323
|
+
ok: validated.validation.ok,
|
|
324
|
+
code: validated.validation.ok ? undefined : validated.validation.codes[0],
|
|
325
|
+
spanCount: validated.reconstruction?.spans.length,
|
|
326
|
+
semanticExcluded,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
// Mode C: legacy prompt — continuity, not semantic completeness.
|
|
330
|
+
if (fx.input.mode === "C") {
|
|
331
|
+
const shards = materializeShards(fx.input.shards ?? "mixed", graph, fx.input.eraseShard);
|
|
332
|
+
const exact = graph.nodes.filter((n) => n.kind !== "semantic");
|
|
333
|
+
const recShards = [...shards.values()].filter((s) => exact.some((n) => n.id === s.nodeId));
|
|
334
|
+
return { ok: true, semanticLossStated: true, spanCount: recShards.length };
|
|
335
|
+
}
|
|
336
|
+
// REC-013: anchor-missing. Simulate an upstream closure that dropped an
|
|
337
|
+
// anchor floor node; the validator must reject with REC_ANCHOR_MISSING.
|
|
338
|
+
if (fx.input.scenario === "validate-anchor-missing") {
|
|
339
|
+
const anchorless = {
|
|
340
|
+
...closure,
|
|
341
|
+
ok: true,
|
|
342
|
+
selected: closure.selected.filter((id) => id !== "anchor"),
|
|
343
|
+
};
|
|
344
|
+
const shards = materializeShards(fx.input.shards ?? "anchor-missing", graph, fx.input.eraseShard);
|
|
345
|
+
const validated = await validateAndAssemble({ graph, closure: anchorless, nodes: graph.nodes, edges: graph.edges, shards: [...shards.values()] });
|
|
346
|
+
return {
|
|
347
|
+
ok: validated.validation.ok,
|
|
348
|
+
code: validated.validation.ok ? undefined : validated.validation.codes[0],
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
const shards = materializeShards(fx.input.shards ?? "mixed", graph, fx.input.eraseShard);
|
|
352
|
+
const validated = await validateAndAssemble({ graph, closure, nodes: graph.nodes, edges: graph.edges, shards: [...shards.values()] });
|
|
353
|
+
if (validated.validation.ok) {
|
|
354
|
+
const sum = validated.validation.summary;
|
|
355
|
+
return {
|
|
356
|
+
ok: true,
|
|
357
|
+
spanCount: sum.spanCount,
|
|
358
|
+
protectedSpanCount: sum.protectedSpanCount,
|
|
359
|
+
byteTotal: sum.byteTotal,
|
|
360
|
+
mandatoryTokenEstimate: sum.mandatoryTokenEstimate,
|
|
361
|
+
closureReachedFixedPoint: fixed,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
return { ok: false, code: validated.validation.codes[0] };
|
|
365
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/reconstruct/assemble.ts — source-order assembly (VC4C).
|
|
3
|
+
*
|
|
4
|
+
* Task 4: given a CLOSED + VALIDATED selection, decode the relevant shards,
|
|
5
|
+
* insert protected exact bytes unchanged, then lay the spans out SOLELY by their
|
|
6
|
+
* source range (`ShardRange`) before concatenation — never by closure order,
|
|
7
|
+
* which carries no positional meaning. The deterministic content-only
|
|
8
|
+
* `mandatoryTokenEstimate` from the closure is carried through UNCHANGED to VC5A
|
|
9
|
+
* (VC5A owns framing + budget admission).
|
|
10
|
+
*
|
|
11
|
+
* OWNERSHIP BOUNDARY (CONTRACTS plan + TRIAD_RESILIENCE): assemble produces the
|
|
12
|
+
* raw, ordered, content-only reconstruction. It does NOT frame it, does NOT
|
|
13
|
+
* admit against a budget, and NEVER truncates a mandatory node. Any byte/order
|
|
14
|
+
* error produces a hard rejection (REC_SPAN_OVERLAP / REC_TOOL_PAIR_SPLIT)
|
|
15
|
+
* because a corrupted layout must never reach a live prompt.
|
|
16
|
+
*
|
|
17
|
+
* Determinism: sorting is bytewise on (sessionId, seqStart, byteStart). The
|
|
18
|
+
* output digest is over the CONCATENATED BYTES so any reorder is itself detected
|
|
19
|
+
* downstream (RESIDUAL_CODEC admission parity).
|
|
20
|
+
*
|
|
21
|
+
* Pure/deterministic: no storage, no console, no network.
|
|
22
|
+
*/
|
|
23
|
+
/** SHA-256 hex of an arbitrary byte array (Web Crypto; no network, no deps). */
|
|
24
|
+
async function sha256Hex(bytes) {
|
|
25
|
+
const buf = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
26
|
+
const digest = await crypto.subtle.digest("SHA-256", buf);
|
|
27
|
+
const out = new Uint8Array(digest);
|
|
28
|
+
return Array.from(out, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
29
|
+
}
|
|
30
|
+
/** Stable source-order comparator: (sessionId, seqStart, byteStart). */
|
|
31
|
+
function bySourceOrder(a, b) {
|
|
32
|
+
if (a.range.sessionId !== b.range.sessionId) {
|
|
33
|
+
return a.range.sessionId < b.range.sessionId ? -1 : 1;
|
|
34
|
+
}
|
|
35
|
+
if (a.range.seqStart !== b.range.seqStart) {
|
|
36
|
+
return a.range.seqStart < b.range.seqStart ? -1 : 1;
|
|
37
|
+
}
|
|
38
|
+
return a.range.byteStart - b.range.byteStart;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Detect a hard layout failure BEFORE concatenation:
|
|
42
|
+
* - REC_SPAN_OVERLAP: two spans intersect in the source byte space.
|
|
43
|
+
* - REC_TOOL_PAIR_SPLIT: a node bound by a tool-pair edge must be adjacent to
|
|
44
|
+
* its partner (end of one == start of the other) in source order
|
|
45
|
+
* (PREVENT-PI-002 restated at assembly time — a toolCall/toolResult pair is
|
|
46
|
+
* never split across a gap).
|
|
47
|
+
* Returns the first failure code found, or null when the layout is clean.
|
|
48
|
+
*/
|
|
49
|
+
function detectLayoutFailure(spans, nodes, edges) {
|
|
50
|
+
const ordered = [...spans].sort(bySourceOrder);
|
|
51
|
+
for (let i = 1; i < ordered.length; i++) {
|
|
52
|
+
const prev = ordered[i - 1].range;
|
|
53
|
+
const cur = ordered[i].range;
|
|
54
|
+
const prevEnd = prev.byteEnd; // half-open: [byteStart, byteEnd)
|
|
55
|
+
if (cur.byteStart < prevEnd)
|
|
56
|
+
return "REC_SPAN_OVERLAP";
|
|
57
|
+
}
|
|
58
|
+
// Tool-pair adjacency: every tool-pair edge names two partners that must be
|
|
59
|
+
// immediately adjacent in source order. We check each edge once.
|
|
60
|
+
const rangeById = new Map(spans.map((s) => [s.nodeId, s.range]));
|
|
61
|
+
const inOrder = new Map(ordered.map((s) => [s.nodeId, s.range]));
|
|
62
|
+
const seenEdges = new Set();
|
|
63
|
+
for (const e of edges) {
|
|
64
|
+
if (e.kind !== "tool-pair")
|
|
65
|
+
continue;
|
|
66
|
+
const key = e.from < e.to ? `${e.from}|${e.to}` : `${e.to}|${e.from}`;
|
|
67
|
+
if (seenEdges.has(key))
|
|
68
|
+
continue;
|
|
69
|
+
seenEdges.add(key);
|
|
70
|
+
// Both endpoints must be present in the selection to be a split concern.
|
|
71
|
+
const a = rangeById.get(e.from);
|
|
72
|
+
const b = rangeById.get(e.to);
|
|
73
|
+
if (a === undefined || b === undefined)
|
|
74
|
+
continue;
|
|
75
|
+
const aEnd = a.byteEnd;
|
|
76
|
+
const bEnd = b.byteEnd;
|
|
77
|
+
const adjacent = aEnd === b.byteStart || bEnd === a.byteStart;
|
|
78
|
+
if (!adjacent)
|
|
79
|
+
return "REC_TOOL_PAIR_SPLIT";
|
|
80
|
+
}
|
|
81
|
+
// `nodes` kept in the signature for call symmetry with the composed validator;
|
|
82
|
+
// it is the authoritative kind source but not needed for these two checks.
|
|
83
|
+
void nodes;
|
|
84
|
+
void inOrder;
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Assemble a closed+validated selection into source order (task 4).
|
|
89
|
+
*
|
|
90
|
+
* `shards` must be EXACTLY one `DecodedShard` per node id in `selected` — the
|
|
91
|
+
* caller (validate.ts) guarantees availability. A missing shard is therefore a
|
|
92
|
+
* programming error surfaced as REC_SOURCE_UNAVAILABLE, never a silent gap.
|
|
93
|
+
*/
|
|
94
|
+
export async function assembleSourceOrder(args) {
|
|
95
|
+
const { sessionId, selected, nodes, edges, mandatoryTokenEstimate } = args;
|
|
96
|
+
const shardById = new Map(args.shards.map((s) => [s.nodeId, s]));
|
|
97
|
+
// Every selected node MUST have a decoded shard; a missing one is the unique
|
|
98
|
+
// failure-injection path (erase a dependency shard -> REC_SOURCE_UNAVAILABLE).
|
|
99
|
+
for (const id of selected) {
|
|
100
|
+
if (!shardById.has(id))
|
|
101
|
+
return { reconstruction: null, code: "REC_SOURCE_UNAVAILABLE" };
|
|
102
|
+
}
|
|
103
|
+
const spans = selected.map((id) => {
|
|
104
|
+
const s = shardById.get(id);
|
|
105
|
+
return {
|
|
106
|
+
nodeId: s.nodeId,
|
|
107
|
+
range: s.range,
|
|
108
|
+
source: s.source,
|
|
109
|
+
bytes: s.bytes,
|
|
110
|
+
digest: s.digest,
|
|
111
|
+
protectedSpan: s.protectedSpan,
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
const layoutFailure = detectLayoutFailure(spans, nodes, edges);
|
|
115
|
+
if (layoutFailure !== null)
|
|
116
|
+
return { reconstruction: null, code: layoutFailure };
|
|
117
|
+
const ordered = [...spans].sort(bySourceOrder);
|
|
118
|
+
const byteTotal = ordered.reduce((sum, s) => sum + s.bytes.length, 0);
|
|
119
|
+
const concatenated = new Uint8Array(byteTotal);
|
|
120
|
+
let offset = 0;
|
|
121
|
+
for (const s of ordered) {
|
|
122
|
+
concatenated.set(s.bytes, offset);
|
|
123
|
+
offset += s.bytes.length;
|
|
124
|
+
}
|
|
125
|
+
const digest = await sha256Hex(concatenated);
|
|
126
|
+
return {
|
|
127
|
+
reconstruction: {
|
|
128
|
+
schema: "reconstruction-v1",
|
|
129
|
+
sessionId,
|
|
130
|
+
spans: ordered,
|
|
131
|
+
digest,
|
|
132
|
+
byteTotal,
|
|
133
|
+
mandatoryTokenEstimate,
|
|
134
|
+
},
|
|
135
|
+
code: null,
|
|
136
|
+
};
|
|
137
|
+
}
|