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.
Files changed (120) hide show
  1. package/dist/config/vector-cortex.js +26 -0
  2. package/dist/config.js +1 -1
  3. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +2 -0
  4. package/dist/extensions/dashboard-server/routes-vector-cortex-plans.js +49 -0
  5. package/dist/extensions/dashboard-server/routes-vector-cortex-reconstruct.js +33 -0
  6. package/dist/extensions/dashboard-server/routes-vector-cortex.js +2 -0
  7. package/dist/extensions/dashboard-server/routes.js +1 -1
  8. package/dist/extensions/dashboard-server/server.js +5 -1
  9. package/dist/src/config/vector-cortex.js +26 -0
  10. package/dist/src/config.js +1 -1
  11. package/dist/src/vector-cortex/planner/manifest.js +66 -0
  12. package/dist/src/vector-cortex/planner/portfolio.js +0 -0
  13. package/dist/src/vector-cortex/planner/types.js +41 -0
  14. package/dist/src/vector-cortex/prompt-dag/_acceptance-dag.js +347 -0
  15. package/dist/src/vector-cortex/prompt-dag/_acceptance-fixture.js +59 -0
  16. package/dist/src/vector-cortex/prompt-dag/_acceptance-helpers.js +17 -0
  17. package/dist/src/vector-cortex/prompt-dag/_acceptance-planner.js +153 -0
  18. package/dist/src/vector-cortex/prompt-dag/_acceptance-shuffle.js +24 -0
  19. package/dist/src/vector-cortex/prompt-dag/builder.js +171 -0
  20. package/dist/src/vector-cortex/prompt-dag/types.js +33 -0
  21. package/dist/src/vector-cortex/prompt-dag/validator.js +0 -0
  22. package/dist/src/vector-cortex/reconstruct/_acceptance-helpers.js +365 -0
  23. package/dist/src/vector-cortex/reconstruct/assemble.js +137 -0
  24. package/dist/src/vector-cortex/reconstruct/closure.js +268 -0
  25. package/dist/src/vector-cortex/reconstruct/types.js +46 -0
  26. package/dist/src/vector-cortex/reconstruct/validate.js +152 -0
  27. package/dist/vector-cortex/planner/manifest.js +66 -0
  28. package/dist/vector-cortex/planner/portfolio.js +0 -0
  29. package/dist/vector-cortex/planner/types.js +41 -0
  30. package/dist/vector-cortex/prompt-dag/_acceptance-dag.js +347 -0
  31. package/dist/vector-cortex/prompt-dag/_acceptance-fixture.js +59 -0
  32. package/dist/vector-cortex/prompt-dag/_acceptance-helpers.js +17 -0
  33. package/dist/vector-cortex/prompt-dag/_acceptance-planner.js +153 -0
  34. package/dist/vector-cortex/prompt-dag/_acceptance-shuffle.js +24 -0
  35. package/dist/vector-cortex/prompt-dag/builder.js +171 -0
  36. package/dist/vector-cortex/prompt-dag/types.js +33 -0
  37. package/dist/vector-cortex/prompt-dag/validator.js +0 -0
  38. package/dist/vector-cortex/reconstruct/_acceptance-helpers.js +365 -0
  39. package/dist/vector-cortex/reconstruct/assemble.js +137 -0
  40. package/dist/vector-cortex/reconstruct/closure.js +268 -0
  41. package/dist/vector-cortex/reconstruct/types.js +46 -0
  42. package/dist/vector-cortex/reconstruct/validate.js +152 -0
  43. package/extensions/dashboard-client/dist/assets/{AreaChart-n4z5w9Ox.js → AreaChart-Cam9-nhT.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{AreaChart-n4z5w9Ox.js.map → AreaChart-Cam9-nhT.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{BarChart-BLGjWWBr.js → BarChart-DzIY8aM1.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{BarChart-BLGjWWBr.js.map → BarChart-DzIY8aM1.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{CacheTab-CAnrKKVK.js → CacheTab-CKIiYsG_.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{CacheTab-CAnrKKVK.js.map → CacheTab-CKIiYsG_.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/{EventsTab-D-GL7Ngq.js → EventsTab-xaaBJLiK.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{EventsTab-D-GL7Ngq.js.map → EventsTab-xaaBJLiK.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/{HealthTab-C4x5qQ8s.js → HealthTab-BGtUKOqG.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{HealthTab-C4x5qQ8s.js.map → HealthTab-BGtUKOqG.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BSPs0eq5.js → MaintenanceTab-ABgQnTZ9.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BSPs0eq5.js.map → MaintenanceTab-ABgQnTZ9.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BWEUtTSu.js → MemoryMapTab-ChR2GAN-.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BWEUtTSu.js.map → MemoryMapTab-ChR2GAN-.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{MetricsTab-Dhm1c3Cx.js → MetricsTab-DdCLAgcR.js} +2 -2
  58. package/extensions/dashboard-client/dist/assets/{MetricsTab-Dhm1c3Cx.js.map → MetricsTab-DdCLAgcR.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{OverviewTab-Cm6cySiQ.js → OverviewTab-DtemzJt6.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{OverviewTab-Cm6cySiQ.js.map → OverviewTab-DtemzJt6.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{ReposTab-DxLN1N4K.js → ReposTab-B9mvD9Ep.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{ReposTab-DxLN1N4K.js.map → ReposTab-B9mvD9Ep.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{SessionsTab-CZuI4ISW.js → SessionsTab-BcO38XLS.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{SessionsTab-CZuI4ISW.js.map → SessionsTab-BcO38XLS.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/{SetupTab-C_0VOomm.js → SetupTab-D_TBNP09.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{SetupTab-C_0VOomm.js.map → SetupTab-D_TBNP09.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BhbaRLmi.js → TimeSavedCard-TjHpbUOo.js} +2 -2
  68. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BhbaRLmi.js.map → TimeSavedCard-TjHpbUOo.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/assets/{TurnsTab-CXJmbjje.js → TurnsTab-DprxndCF.js} +2 -2
  70. package/extensions/dashboard-client/dist/assets/{TurnsTab-CXJmbjje.js.map → TurnsTab-DprxndCF.js.map} +1 -1
  71. package/extensions/dashboard-client/dist/assets/VectorCortexTab-Bym21yVQ.js +2 -0
  72. package/extensions/dashboard-client/dist/assets/VectorCortexTab-Bym21yVQ.js.map +1 -0
  73. package/extensions/dashboard-client/dist/assets/{WikiTab-C5uvMCzp.js → WikiTab-OKgSo15G.js} +2 -2
  74. package/extensions/dashboard-client/dist/assets/{WikiTab-C5uvMCzp.js.map → WikiTab-OKgSo15G.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/assets/{button-Cl_fxJcg.js → button-D_Qz4q7L.js} +2 -2
  76. package/extensions/dashboard-client/dist/assets/{button-Cl_fxJcg.js.map → button-D_Qz4q7L.js.map} +1 -1
  77. package/extensions/dashboard-client/dist/assets/{card-BH8Le5YM.js → card-CsI6Jm3p.js} +2 -2
  78. package/extensions/dashboard-client/dist/assets/{card-BH8Le5YM.js.map → card-CsI6Jm3p.js.map} +1 -1
  79. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-sZ6ZNtBi.js → generateCategoricalChart-CvHKUdhX.js} +2 -2
  80. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-sZ6ZNtBi.js.map → generateCategoricalChart-CvHKUdhX.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/assets/{index-CX9zv8cJ.js → index-0Ye_un22.js} +3 -3
  82. package/extensions/dashboard-client/dist/assets/{index-CX9zv8cJ.js.map → index-0Ye_un22.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/assets/{switch-CXJhcjhM.js → switch-VQEoJl8Q.js} +2 -2
  84. package/extensions/dashboard-client/dist/assets/{switch-CXJhcjhM.js.map → switch-VQEoJl8Q.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/{toggle-CoLtIc6y.js → toggle-CAPm4HJo.js} +2 -2
  86. package/extensions/dashboard-client/dist/assets/{toggle-CoLtIc6y.js.map → toggle-CAPm4HJo.js.map} +1 -1
  87. package/extensions/dashboard-client/dist/assets/{useSSE-fSXho606.js → useSSE-pxsNTVkh.js} +2 -2
  88. package/extensions/dashboard-client/dist/assets/{useSSE-fSXho606.js.map → useSSE-pxsNTVkh.js.map} +1 -1
  89. package/extensions/dashboard-client/dist/index.html +1 -1
  90. package/extensions/dashboard-client/src/api/vector-cortex.ts +18 -0
  91. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +72 -0
  92. package/extensions/dashboard-client/src/types/vector-cortex.ts +36 -0
  93. package/extensions/dashboard-server/api-contracts/vector-cortex.ts +66 -0
  94. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +12 -0
  95. package/extensions/dashboard-server/routes-vector-cortex-plans.ts +58 -0
  96. package/extensions/dashboard-server/routes-vector-cortex-reconstruct.ts +41 -0
  97. package/extensions/dashboard-server/routes-vector-cortex.ts +2 -0
  98. package/extensions/dashboard-server/routes.ts +2 -0
  99. package/extensions/dashboard-server/server.ts +4 -0
  100. package/package.json +1 -1
  101. package/src/config/vector-cortex.ts +28 -0
  102. package/src/config.ts +2 -0
  103. package/src/vector-cortex/planner/manifest.ts +83 -0
  104. package/src/vector-cortex/planner/portfolio.ts +0 -0
  105. package/src/vector-cortex/planner/types.ts +190 -0
  106. package/src/vector-cortex/prompt-dag/_acceptance-dag.ts +392 -0
  107. package/src/vector-cortex/prompt-dag/_acceptance-fixture.ts +132 -0
  108. package/src/vector-cortex/prompt-dag/_acceptance-helpers.ts +39 -0
  109. package/src/vector-cortex/prompt-dag/_acceptance-planner.ts +188 -0
  110. package/src/vector-cortex/prompt-dag/_acceptance-shuffle.ts +26 -0
  111. package/src/vector-cortex/prompt-dag/builder.ts +189 -0
  112. package/src/vector-cortex/prompt-dag/types.ts +151 -0
  113. package/src/vector-cortex/prompt-dag/validator.ts +0 -0
  114. package/src/vector-cortex/reconstruct/_acceptance-helpers.ts +455 -0
  115. package/src/vector-cortex/reconstruct/assemble.ts +170 -0
  116. package/src/vector-cortex/reconstruct/closure.ts +299 -0
  117. package/src/vector-cortex/reconstruct/types.ts +330 -0
  118. package/src/vector-cortex/reconstruct/validate.ts +184 -0
  119. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BGe9hu3r.js +0 -2
  120. 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"];
@@ -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
+ }