pi-mega-compact 0.20.1 → 0.20.3
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 +26 -0
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +2 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-plans.js +49 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-reconstruct.js +33 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex.js +2 -0
- package/dist/extensions/dashboard-server/routes.js +1 -1
- package/dist/extensions/dashboard-server/server.js +5 -1
- package/dist/src/config/vector-cortex.js +26 -0
- package/dist/src/config.js +1 -1
- package/dist/src/vector-cortex/planner/manifest.js +66 -0
- package/dist/src/vector-cortex/planner/portfolio.js +0 -0
- package/dist/src/vector-cortex/planner/types.js +41 -0
- package/dist/src/vector-cortex/prompt-dag/_acceptance-dag.js +347 -0
- package/dist/src/vector-cortex/prompt-dag/_acceptance-fixture.js +59 -0
- package/dist/src/vector-cortex/prompt-dag/_acceptance-helpers.js +17 -0
- package/dist/src/vector-cortex/prompt-dag/_acceptance-planner.js +153 -0
- package/dist/src/vector-cortex/prompt-dag/_acceptance-shuffle.js +24 -0
- package/dist/src/vector-cortex/prompt-dag/builder.js +171 -0
- package/dist/src/vector-cortex/prompt-dag/types.js +33 -0
- package/dist/src/vector-cortex/prompt-dag/validator.js +0 -0
- package/dist/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/planner/manifest.js +66 -0
- package/dist/vector-cortex/planner/portfolio.js +0 -0
- package/dist/vector-cortex/planner/types.js +41 -0
- package/dist/vector-cortex/prompt-dag/_acceptance-dag.js +347 -0
- package/dist/vector-cortex/prompt-dag/_acceptance-fixture.js +59 -0
- package/dist/vector-cortex/prompt-dag/_acceptance-helpers.js +17 -0
- package/dist/vector-cortex/prompt-dag/_acceptance-planner.js +153 -0
- package/dist/vector-cortex/prompt-dag/_acceptance-shuffle.js +24 -0
- package/dist/vector-cortex/prompt-dag/builder.js +171 -0
- package/dist/vector-cortex/prompt-dag/types.js +33 -0
- package/dist/vector-cortex/prompt-dag/validator.js +0 -0
- package/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-Cam9-nhT.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-n4z5w9Ox.js.map → AreaChart-Cam9-nhT.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-BLGjWWBr.js → BarChart-DzIY8aM1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-BLGjWWBr.js.map → BarChart-DzIY8aM1.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-CAnrKKVK.js → CacheTab-CKIiYsG_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-CAnrKKVK.js.map → CacheTab-CKIiYsG_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-D-GL7Ngq.js → EventsTab-xaaBJLiK.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-D-GL7Ngq.js.map → EventsTab-xaaBJLiK.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-C4x5qQ8s.js → HealthTab-BGtUKOqG.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-C4x5qQ8s.js.map → HealthTab-BGtUKOqG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BSPs0eq5.js → MaintenanceTab-ABgQnTZ9.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BSPs0eq5.js.map → MaintenanceTab-ABgQnTZ9.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BWEUtTSu.js → MemoryMapTab-ChR2GAN-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BWEUtTSu.js.map → MemoryMapTab-ChR2GAN-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-Dhm1c3Cx.js → MetricsTab-DdCLAgcR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-Dhm1c3Cx.js.map → MetricsTab-DdCLAgcR.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-Cm6cySiQ.js → OverviewTab-DtemzJt6.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-Cm6cySiQ.js.map → OverviewTab-DtemzJt6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DxLN1N4K.js → ReposTab-B9mvD9Ep.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DxLN1N4K.js.map → ReposTab-B9mvD9Ep.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-CZuI4ISW.js → SessionsTab-BcO38XLS.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-CZuI4ISW.js.map → SessionsTab-BcO38XLS.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-C_0VOomm.js → SetupTab-D_TBNP09.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-C_0VOomm.js.map → SetupTab-D_TBNP09.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BhbaRLmi.js → TimeSavedCard-TjHpbUOo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BhbaRLmi.js.map → TimeSavedCard-TjHpbUOo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CXJmbjje.js → TurnsTab-DprxndCF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CXJmbjje.js.map → TurnsTab-DprxndCF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-Bym21yVQ.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-Bym21yVQ.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-C5uvMCzp.js → WikiTab-OKgSo15G.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-C5uvMCzp.js.map → WikiTab-OKgSo15G.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-Cl_fxJcg.js → button-D_Qz4q7L.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-Cl_fxJcg.js.map → button-D_Qz4q7L.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-BH8Le5YM.js → card-CsI6Jm3p.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-BH8Le5YM.js.map → card-CsI6Jm3p.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-sZ6ZNtBi.js → generateCategoricalChart-CvHKUdhX.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-sZ6ZNtBi.js.map → generateCategoricalChart-CvHKUdhX.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-CX9zv8cJ.js → index-0Ye_un22.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-CX9zv8cJ.js.map → index-0Ye_un22.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{switch-CXJhcjhM.js → switch-VQEoJl8Q.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-CXJhcjhM.js.map → switch-VQEoJl8Q.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-CoLtIc6y.js → toggle-CAPm4HJo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-CoLtIc6y.js.map → toggle-CAPm4HJo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-fSXho606.js → useSSE-pxsNTVkh.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-fSXho606.js.map → useSSE-pxsNTVkh.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/vector-cortex.ts +18 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +72 -0
- package/extensions/dashboard-client/src/types/vector-cortex.ts +36 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex.ts +66 -0
- package/extensions/dashboard-server/routes-rag-settings-helpers.ts +12 -0
- package/extensions/dashboard-server/routes-vector-cortex-plans.ts +58 -0
- package/extensions/dashboard-server/routes-vector-cortex-reconstruct.ts +41 -0
- package/extensions/dashboard-server/routes-vector-cortex.ts +2 -0
- package/extensions/dashboard-server/routes.ts +2 -0
- package/extensions/dashboard-server/server.ts +4 -0
- package/package.json +1 -1
- package/src/config/vector-cortex.ts +28 -0
- package/src/config.ts +2 -0
- package/src/vector-cortex/planner/manifest.ts +83 -0
- package/src/vector-cortex/planner/portfolio.ts +0 -0
- package/src/vector-cortex/planner/types.ts +190 -0
- package/src/vector-cortex/prompt-dag/_acceptance-dag.ts +392 -0
- package/src/vector-cortex/prompt-dag/_acceptance-fixture.ts +132 -0
- package/src/vector-cortex/prompt-dag/_acceptance-helpers.ts +39 -0
- package/src/vector-cortex/prompt-dag/_acceptance-planner.ts +188 -0
- package/src/vector-cortex/prompt-dag/_acceptance-shuffle.ts +26 -0
- package/src/vector-cortex/prompt-dag/builder.ts +189 -0
- package/src/vector-cortex/prompt-dag/types.ts +151 -0
- package/src/vector-cortex/prompt-dag/validator.ts +0 -0
- package/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,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/prompt-dag/builder.ts — single-session PromptDagV1 construction
|
|
3
|
+
* (VC5A, task 2).
|
|
4
|
+
*
|
|
5
|
+
* The builder is the STRUCTURAL gate: it rejects a graph that could never be
|
|
6
|
+
* planned safely, before any ordering or selection is attempted. Rejections are
|
|
7
|
+
* hard and total (the sprint bar: "cycles/incompatibilities rejected 100%"):
|
|
8
|
+
*
|
|
9
|
+
* - `DAG_MIXED_SESSION` — a span whose sessionId differs from the DAG's.
|
|
10
|
+
* Cross-session evidence MUST be a synthetic
|
|
11
|
+
* node citing a separately validated manifest,
|
|
12
|
+
* never a foreign span (CONTRACTS §PromptDagV1).
|
|
13
|
+
* - `DAG_DUPLICATE_ID` — two nodes sharing an id.
|
|
14
|
+
* - `DAG_MISSING_ENDPOINT` — an edge naming an absent node.
|
|
15
|
+
* - `DAG_INVALID_SPAN` — reversed/negative range, or a synthetic node
|
|
16
|
+
* carrying a span (synthetics have no source
|
|
17
|
+
* position by definition).
|
|
18
|
+
* - `DAG_SPAN_DIGEST_CONFLICT` — two overlapping spans pinning DIFFERENT
|
|
19
|
+
* digests for the same bytes: a corrupted or
|
|
20
|
+
* forged source claim, not a legal overlap.
|
|
21
|
+
* - `DAG_UNKNOWN_INCOMPATIBLE` — `incompatibleWith` naming an absent node.
|
|
22
|
+
* - `DAG_TOOL_PAIR_SPLIT` — a tool-pair edge whose endpoints are not both
|
|
23
|
+
* present (PREVENT-PI-002).
|
|
24
|
+
*
|
|
25
|
+
* Cycle and reversed-`precedes` detection belong to the VALIDATOR, which owns
|
|
26
|
+
* the topological order; the builder only guarantees the graph is structurally
|
|
27
|
+
* well-formed and single-session.
|
|
28
|
+
*
|
|
29
|
+
* Determinism: nodes and edges are emitted in a canonical sorted order so the
|
|
30
|
+
* same inputs always produce byte-identical DAG bytes, whatever order the caller
|
|
31
|
+
* supplied them in. Pure: no storage, no console, no network (PREVENT-PI-004).
|
|
32
|
+
*/
|
|
33
|
+
/** Bytewise string comparison — the universal deterministic tie-break. */
|
|
34
|
+
function byBytes(a, b) {
|
|
35
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Canonical node order: source position first (`startSeq`, then `startByte`),
|
|
39
|
+
* synthetics last (they have no span), then `syntheticOrdinal`, then id bytes.
|
|
40
|
+
* This mirrors the validator's queue key so the emitted DAG and its topological
|
|
41
|
+
* order agree on what "earlier" means.
|
|
42
|
+
*/
|
|
43
|
+
export function compareNodes(a, b) {
|
|
44
|
+
const aSeq = a.span?.startSeq;
|
|
45
|
+
const bSeq = b.span?.startSeq;
|
|
46
|
+
if (aSeq !== undefined && bSeq !== undefined) {
|
|
47
|
+
if (aSeq !== bSeq)
|
|
48
|
+
return aSeq < bSeq ? -1 : 1;
|
|
49
|
+
}
|
|
50
|
+
else if (aSeq !== undefined) {
|
|
51
|
+
return -1; // spanned nodes precede synthetics
|
|
52
|
+
}
|
|
53
|
+
else if (bSeq !== undefined) {
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
// Contract key: (startSeq, syntheticOrdinal, id bytes) — byte offset is NOT a
|
|
57
|
+
// tie-break, so the emitted DAG and its topological order agree exactly.
|
|
58
|
+
const aOrd = a.syntheticOrdinal ?? 0;
|
|
59
|
+
const bOrd = b.syntheticOrdinal ?? 0;
|
|
60
|
+
if (aOrd !== bOrd)
|
|
61
|
+
return aOrd < bOrd ? -1 : 1;
|
|
62
|
+
return byBytes(a.id, b.id);
|
|
63
|
+
}
|
|
64
|
+
/** Canonical edge order: from, then to, then kind — a total order. */
|
|
65
|
+
export function compareEdges(a, b) {
|
|
66
|
+
const f = byBytes(a.from, b.from);
|
|
67
|
+
if (f !== 0)
|
|
68
|
+
return f;
|
|
69
|
+
const t = byBytes(a.to, b.to);
|
|
70
|
+
if (t !== 0)
|
|
71
|
+
return t;
|
|
72
|
+
return byBytes(a.kind, b.kind);
|
|
73
|
+
}
|
|
74
|
+
/** Whether two half-open byte ranges in the same session intersect. */
|
|
75
|
+
function spansOverlap(a, b) {
|
|
76
|
+
if (a.sessionId !== b.sessionId)
|
|
77
|
+
return false;
|
|
78
|
+
return a.startByte < b.endByte && b.startByte < a.endByte;
|
|
79
|
+
}
|
|
80
|
+
/** A span is well-formed when both its seq and byte ranges are non-decreasing. */
|
|
81
|
+
function spanIsValid(span) {
|
|
82
|
+
if (span.startSeq > span.endSeq)
|
|
83
|
+
return false;
|
|
84
|
+
if (span.startByte < 0 || span.endByte < 0)
|
|
85
|
+
return false;
|
|
86
|
+
return span.startByte <= span.endByte;
|
|
87
|
+
}
|
|
88
|
+
/** Push a failure code once (codes are a SET, reported in discovery order). */
|
|
89
|
+
function pushCode(codes, code) {
|
|
90
|
+
if (!codes.includes(code))
|
|
91
|
+
codes.push(code);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Build a canonical single-session `PromptDagV1`, or reject with the exact
|
|
95
|
+
* structural failure codes (task 2).
|
|
96
|
+
*
|
|
97
|
+
* Every check runs before returning so a caller sees ALL structural problems at
|
|
98
|
+
* once rather than fixing them one round-trip at a time; the codes array is
|
|
99
|
+
* deduplicated and ordered by discovery.
|
|
100
|
+
*/
|
|
101
|
+
export function buildPromptDag(input) {
|
|
102
|
+
const codes = [];
|
|
103
|
+
// ── Node identity + span validity + single-session enforcement ─────────────
|
|
104
|
+
const byId = new Map();
|
|
105
|
+
for (const n of input.nodes) {
|
|
106
|
+
if (byId.has(n.id))
|
|
107
|
+
pushCode(codes, "DAG_DUPLICATE_ID");
|
|
108
|
+
else
|
|
109
|
+
byId.set(n.id, n);
|
|
110
|
+
if (n.kind === "synthetic") {
|
|
111
|
+
// A synthetic node has no source position by definition; carrying a span
|
|
112
|
+
// would make its ordering ambiguous against real source nodes.
|
|
113
|
+
if (n.span !== undefined)
|
|
114
|
+
pushCode(codes, "DAG_INVALID_SPAN");
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (n.span === undefined)
|
|
118
|
+
continue; // a spanless non-synthetic orders last
|
|
119
|
+
if (!spanIsValid(n.span))
|
|
120
|
+
pushCode(codes, "DAG_INVALID_SPAN");
|
|
121
|
+
// Single-session: cross-session evidence must be a synthetic citation.
|
|
122
|
+
if (n.span.sessionId !== input.sessionId)
|
|
123
|
+
pushCode(codes, "DAG_MIXED_SESSION");
|
|
124
|
+
}
|
|
125
|
+
// ── Overlapping spans must agree on the digest of the shared bytes ─────────
|
|
126
|
+
const spanned = input.nodes.filter((n) => n.span !== undefined);
|
|
127
|
+
for (let i = 0; i < spanned.length; i++) {
|
|
128
|
+
for (let j = i + 1; j < spanned.length; j++) {
|
|
129
|
+
const a = spanned[i];
|
|
130
|
+
const b = spanned[j];
|
|
131
|
+
if (!spansOverlap(a.span, b.span))
|
|
132
|
+
continue;
|
|
133
|
+
if (a.span.digest !== b.span.digest) {
|
|
134
|
+
pushCode(codes, "DAG_SPAN_DIGEST_CONFLICT");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// ── Incompatibility targets must exist ────────────────────────────────────
|
|
139
|
+
for (const n of input.nodes) {
|
|
140
|
+
for (const other of n.incompatibleWith) {
|
|
141
|
+
if (!byId.has(other))
|
|
142
|
+
pushCode(codes, "DAG_UNKNOWN_INCOMPATIBLE");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// ── Edge endpoints must exist; tool pairs must be whole ───────────────────
|
|
146
|
+
for (const e of input.edges) {
|
|
147
|
+
const hasFrom = byId.has(e.from);
|
|
148
|
+
const hasTo = byId.has(e.to);
|
|
149
|
+
if (!hasFrom || !hasTo) {
|
|
150
|
+
pushCode(codes, "DAG_MISSING_ENDPOINT");
|
|
151
|
+
// A tool pair missing an endpoint is ALSO a split pair (PREVENT-PI-002):
|
|
152
|
+
// the atomic unit cannot be represented, let alone selected whole.
|
|
153
|
+
if (e.kind === "tool-pair")
|
|
154
|
+
pushCode(codes, "DAG_TOOL_PAIR_SPLIT");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (codes.length > 0)
|
|
158
|
+
return { ok: false, codes };
|
|
159
|
+
const nodes = [...input.nodes].sort(compareNodes);
|
|
160
|
+
const edges = [...input.edges].sort(compareEdges);
|
|
161
|
+
return {
|
|
162
|
+
ok: true,
|
|
163
|
+
dag: {
|
|
164
|
+
schema: "prompt-dag-v1",
|
|
165
|
+
sessionId: input.sessionId,
|
|
166
|
+
sourceHighWater: input.sourceHighWater,
|
|
167
|
+
nodes,
|
|
168
|
+
edges,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/prompt-dag/types.ts — the exact single-session `PromptDagV1`
|
|
3
|
+
* schema (VC5A, task 1).
|
|
4
|
+
*
|
|
5
|
+
* A PromptDagV1 is the STRUCTURE a plan is selected over: nodes are prompt
|
|
6
|
+
* candidates (events, exact spans, semantic renderings, synthetic citations) and
|
|
7
|
+
* edges are the ordering/atomicity/exclusivity constraints among them. It is
|
|
8
|
+
* strictly SINGLE-SESSION (CONTRACTS §PromptDagV1): every node span MUST carry
|
|
9
|
+
* `dag.sessionId`; cross-session evidence is represented by a SYNTHETIC node
|
|
10
|
+
* whose payload cites a separately validated source manifest, never by a foreign
|
|
11
|
+
* span.
|
|
12
|
+
*
|
|
13
|
+
* Ordering is a total order by construction so the same DAG always yields the
|
|
14
|
+
* same Kahn sequence:
|
|
15
|
+
*
|
|
16
|
+
* - event/exact/semantic nodes order by their source span (`startSeq`, then
|
|
17
|
+
* `startByte`);
|
|
18
|
+
* - synthetic nodes have NO span and sort after every prerequisite, then by
|
|
19
|
+
* `syntheticOrdinal`, then by node-ID bytes;
|
|
20
|
+
* - the zero-indegree queue key is `(span.startSeq ?? MAX, syntheticOrdinal ??
|
|
21
|
+
* 0, id bytes)` — never object/map iteration order.
|
|
22
|
+
*
|
|
23
|
+
* Pure types + registered conformance IDs: no storage, no console, no network
|
|
24
|
+
* (PREVENT-PI-004 / PREVENT-011).
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Registered DAG conformance ID range (DAG-001..030). The acceptance aggregator
|
|
28
|
+
* reads these rows from the v2 manifest and asserts each returns its manifest
|
|
29
|
+
* `ok`/`code`.
|
|
30
|
+
*/
|
|
31
|
+
export const DAG_IDS = Array.from({ length: 30 }, (_v, i) => `DAG-${String(i + 1).padStart(3, "0")}`);
|
|
32
|
+
/** Named VC5A DAG conformance assertion (the sprint's headline DAG row). */
|
|
33
|
+
export const DAG_NAMED_IDS = ["DAG-CYCLE-001"];
|
|
Binary file
|
|
@@ -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
|
+
}
|