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,347 @@
1
+ /**
2
+ * prompt-dag/_acceptance-dag.ts — declarative DAG materialization + scenario
3
+ * driver for VC5A acceptance rows.
4
+ *
5
+ * Turns named graph scenarios into real `PromptDagV1` values and drives them
6
+ * through the REAL build/validate logic. Deterministic helpers (eq/shuffle)
7
+ * live in `_acceptance-shuffle.ts`.
8
+ */
9
+ import assert from "node:assert/strict";
10
+ import { buildPromptDag, compareNodes, compareEdges } from "./builder.js";
11
+ import { validatePromptDag, dagDigest } from "./validator.js";
12
+ import { eq, shuffle } from "./_acceptance-shuffle.js";
13
+ function span(sessionId, startSeq, endSeq, startByte, endByte, digest) {
14
+ return { sessionId, startSeq, endSeq, startByte, endByte, digest: `sha256:${digest}` };
15
+ }
16
+ function node(id, kind, opts = {}) {
17
+ return {
18
+ id,
19
+ kind,
20
+ payloadDigest: opts.payloadDigest ?? `pd:${id}`,
21
+ incompatibleWith: opts.incompatibleWith ?? [],
22
+ ...(opts.span !== undefined ? { span: opts.span } : {}),
23
+ ...(opts.syntheticOrdinal !== undefined ? { syntheticOrdinal: opts.syntheticOrdinal } : {}),
24
+ };
25
+ }
26
+ function edge(from, to, kind) {
27
+ return { from, to, kind };
28
+ }
29
+ const D0 = "a".repeat(64);
30
+ const D1 = "b".repeat(64);
31
+ /** Materialize a named graph into a built PromptDagV1 + a validator order. */
32
+ export function materializeDag(name) {
33
+ const cases = {
34
+ linear: {
35
+ sessionId: "s",
36
+ nodes: [
37
+ node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
38
+ node("b", "exact", { span: span("s", 2n, 2n, 3, 6, D0) }),
39
+ node("c", "semantic", { span: span("s", 3n, 3n, 6, 9, D0) }),
40
+ ],
41
+ edges: [edge("a", "b", "depends"), edge("b", "c", "depends")],
42
+ },
43
+ "mixed-session": {
44
+ sessionId: "s",
45
+ nodes: [
46
+ node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
47
+ // A span claiming a foreign session — single-session violation.
48
+ node("b", "exact", { span: span("OTHER", 2n, 2n, 0, 3, D0) }),
49
+ ],
50
+ edges: [],
51
+ },
52
+ "duplicate-id": {
53
+ sessionId: "s",
54
+ nodes: [
55
+ node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
56
+ node("a", "exact", { span: span("s", 2n, 2n, 0, 3, D1) }),
57
+ ],
58
+ edges: [],
59
+ },
60
+ "missing-endpoint": {
61
+ sessionId: "s",
62
+ nodes: [node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) })],
63
+ edges: [edge("a", "ghost", "depends")],
64
+ },
65
+ "invalid-span": {
66
+ sessionId: "s",
67
+ nodes: [node("a", "event", { span: span("s", 3n, 1n, 9, 0, D0) })],
68
+ edges: [],
69
+ },
70
+ "synthetic-span": {
71
+ sessionId: "s",
72
+ nodes: [
73
+ node("a", "synthetic", { syntheticOrdinal: 0, span: span("s", 1n, 1n, 0, 3, D0) }),
74
+ ],
75
+ edges: [],
76
+ },
77
+ "digest-conflict": {
78
+ sessionId: "s",
79
+ nodes: [
80
+ node("a", "exact", { span: span("s", 1n, 1n, 0, 6, D0) }),
81
+ node("b", "exact", { span: span("s", 2n, 2n, 3, 9, D1) }),
82
+ ],
83
+ edges: [],
84
+ },
85
+ "digest-agree": {
86
+ sessionId: "s",
87
+ nodes: [
88
+ node("a", "exact", { span: span("s", 1n, 1n, 0, 6, D0) }),
89
+ node("b", "exact", { span: span("s", 2n, 2n, 3, 9, D0) }),
90
+ ],
91
+ edges: [],
92
+ },
93
+ "unknown-incompatible": {
94
+ sessionId: "s",
95
+ nodes: [
96
+ node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0), incompatibleWith: ["ghost"] }),
97
+ node("b", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
98
+ ],
99
+ edges: [],
100
+ },
101
+ "tool-pair-split": {
102
+ sessionId: "s",
103
+ nodes: [node("toolcall", "event", { span: span("s", 1n, 1n, 0, 3, D0) })],
104
+ edges: [edge("toolcall", "toolresult", "tool-pair")],
105
+ },
106
+ cycle: {
107
+ sessionId: "s",
108
+ nodes: [
109
+ node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
110
+ node("b", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
111
+ node("c", "event", { span: span("s", 3n, 3n, 0, 3, D0) }),
112
+ ],
113
+ edges: [edge("b", "a", "depends"), edge("c", "b", "depends"), edge("a", "c", "depends")],
114
+ },
115
+ selfloop: {
116
+ sessionId: "s",
117
+ nodes: [node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) })],
118
+ edges: [edge("a", "a", "depends")],
119
+ },
120
+ "id-tie": {
121
+ sessionId: "s",
122
+ nodes: [
123
+ node("aa", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
124
+ node("bb", "event", { span: span("s", 1n, 1n, 3, 6, D1) }),
125
+ node("cc", "event", { span: span("s", 1n, 1n, 6, 9, D0) }),
126
+ ],
127
+ edges: [],
128
+ },
129
+ "seq-before-id": {
130
+ sessionId: "s",
131
+ // Earlier seq sorts first even though IDs sort the other way.
132
+ nodes: [
133
+ node("z", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
134
+ node("y", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
135
+ node("x", "event", { span: span("s", 3n, 3n, 0, 3, D0) }),
136
+ ],
137
+ edges: [],
138
+ },
139
+ "synthetic-last": {
140
+ sessionId: "s",
141
+ nodes: [
142
+ node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
143
+ node("b", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
144
+ node("syn", "synthetic", { syntheticOrdinal: 0 }),
145
+ ],
146
+ edges: [],
147
+ },
148
+ "synthetic-ordinal": {
149
+ sessionId: "s",
150
+ nodes: [
151
+ node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
152
+ node("s1", "synthetic", { syntheticOrdinal: 1 }),
153
+ node("s2", "synthetic", { syntheticOrdinal: 2 }),
154
+ ],
155
+ edges: [],
156
+ },
157
+ diamond: {
158
+ sessionId: "s",
159
+ nodes: [
160
+ node("top", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
161
+ node("l", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
162
+ node("r", "event", { span: span("s", 3n, 3n, 0, 3, D0) }),
163
+ node("bottom", "event", { span: span("s", 4n, 4n, 0, 3, D1) }),
164
+ ],
165
+ edges: [
166
+ edge("top", "l", "depends"),
167
+ edge("top", "r", "depends"),
168
+ edge("l", "bottom", "depends"),
169
+ edge("r", "bottom", "depends"),
170
+ ],
171
+ },
172
+ contradicts: {
173
+ sessionId: "s",
174
+ nodes: [
175
+ node("a", "exact", { span: span("s", 1n, 1n, 0, 3, D0) }),
176
+ node("b", "exact", { span: span("s", 2n, 2n, 0, 3, D1) }),
177
+ ],
178
+ edges: [edge("a", "b", "contradicts")],
179
+ },
180
+ "contradicts-cycle": {
181
+ sessionId: "s",
182
+ nodes: [
183
+ node("a", "exact", { span: span("s", 1n, 1n, 0, 3, D0) }),
184
+ node("b", "exact", { span: span("s", 2n, 2n, 0, 3, D1) }),
185
+ ],
186
+ // A contradicts edge plus a depends edge: a real cycle would need the
187
+ // depends to form a loop, but here only `a->b` depends, so no cycle.
188
+ edges: [edge("a", "b", "contradicts"), edge("a", "b", "depends")],
189
+ },
190
+ "reversed-precedes": {
191
+ sessionId: "s",
192
+ nodes: [
193
+ node("a", "event", { span: span("s", 2n, 2n, 0, 3, D0) }),
194
+ node("b", "event", { span: span("s", 1n, 1n, 0, 3, D1) }),
195
+ ],
196
+ // b's source position is EARLIER than a's, so a->b precedes is backward.
197
+ edges: [edge("a", "b", "precedes")],
198
+ },
199
+ scrambled: {
200
+ sessionId: "s",
201
+ nodes: [
202
+ node("c", "event", { span: span("s", 3n, 3n, 6, 9, D0) }),
203
+ node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
204
+ node("b", "event", { span: span("s", 2n, 2n, 3, 6, D1) }),
205
+ ],
206
+ edges: [edge("a", "b", "depends"), edge("b", "c", "depends")],
207
+ },
208
+ empty: { sessionId: "s", nodes: [], edges: [] },
209
+ single: {
210
+ sessionId: "s",
211
+ nodes: [node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) })],
212
+ edges: [],
213
+ },
214
+ disconnected: {
215
+ sessionId: "s",
216
+ nodes: [
217
+ node("a", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
218
+ node("b", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
219
+ node("x", "event", { span: span("s", 1n, 1n, 3, 6, D0) }),
220
+ node("y", "event", { span: span("s", 2n, 2n, 3, 6, D1) }),
221
+ ],
222
+ edges: [],
223
+ },
224
+ toolpair: {
225
+ sessionId: "s",
226
+ nodes: [
227
+ node("toolcall", "event", { span: span("s", 1n, 1n, 0, 3, D0) }),
228
+ node("toolresult", "event", { span: span("s", 2n, 2n, 0, 3, D1) }),
229
+ ],
230
+ edges: [edge("toolcall", "toolresult", "tool-pair")],
231
+ },
232
+ chain8: {
233
+ sessionId: "s",
234
+ nodes: [0, 1, 2, 3, 4, 5, 6, 7, 8].map((i) => node(`n${i}`, "event", { span: span("s", BigInt(i + 1), BigInt(i + 1), 0, 3, D0) })),
235
+ edges: [0, 1, 2, 3, 4, 5, 6, 7].map((i) => edge(`n${i + 1}`, `n${i}`, "depends")),
236
+ },
237
+ };
238
+ const g = cases[name];
239
+ assert.ok(g, `dag materializer missing for "${name}"`);
240
+ // Give every node a DISTINCT, non-overlapping byte window so the builder's
241
+ // digest-conflict rule (which only fires on genuinely shared bytes) does not
242
+ // misfire on adjacent ordering-test nodes. The two digest-* graphs intentionally
243
+ // KEEP overlapping spans to exercise that rule, so they are excluded here.
244
+ const overlapIntentional = name === "digest-conflict" || name === "digest-agree";
245
+ const nodes = overlapIntentional
246
+ ? g.nodes
247
+ : g.nodes.map((n, i) => n.span === undefined
248
+ ? n
249
+ : { ...n, span: { ...n.span, startByte: i * 4, endByte: i * 4 + 4 } });
250
+ const built = buildPromptDag({
251
+ sessionId: g.sessionId,
252
+ sourceHighWater: 100n,
253
+ nodes,
254
+ edges: g.edges,
255
+ });
256
+ return {
257
+ buildOk: built.ok,
258
+ buildCodes: built.ok ? [] : built.codes,
259
+ dag: built.ok
260
+ ? built.dag
261
+ : {
262
+ schema: "prompt-dag-v1",
263
+ sessionId: g.sessionId,
264
+ sourceHighWater: 100n,
265
+ nodes: [...g.nodes].sort(compareNodes),
266
+ edges: [...g.edges].sort(compareEdges),
267
+ },
268
+ };
269
+ }
270
+ /** Validate a materialized DAG, returning order / failure codes / digest. */
271
+ export function runDagScenario(fx) {
272
+ const { scenario, graph, mutateDigest } = fx.input;
273
+ const base = materializeDag(graph ?? "linear");
274
+ // Structural REJECTION scenarios stop at the builder (no validation). Build
275
+ // ACCEPTANCE scenarios (build-linear / build-digest-agree) succeed and must
276
+ // still be validated for their topological order.
277
+ const structuralReject = [
278
+ "build-mixed-session",
279
+ "build-duplicate-id",
280
+ "build-missing-endpoint",
281
+ "build-invalid-span",
282
+ "build-synthetic-with-span",
283
+ "build-digest-conflict",
284
+ "build-unknown-incompatible",
285
+ "build-tool-pair-split",
286
+ ];
287
+ if (structuralReject.includes(scenario)) {
288
+ // Prefer the more specific tool-pair rejection when both it and a generic
289
+ // missing-endpoint code are present (a split tool pair is also missing an
290
+ // endpoint, but the tool-pair split is the precise failure).
291
+ const codes = base.buildCodes;
292
+ const code = codes.includes("DAG_TOOL_PAIR_SPLIT")
293
+ ? "DAG_TOOL_PAIR_SPLIT"
294
+ : codes[0];
295
+ return { ok: base.buildOk, code };
296
+ }
297
+ // A built DAG is required for validation scenarios.
298
+ assert.ok(base.buildOk, `${fx.id}: builder must succeed before validation`);
299
+ const dag = base.dag;
300
+ const v = validatePromptDag(dag);
301
+ const digest = dagDigest(dag);
302
+ if (fx.expected.permutationInvariant) {
303
+ // Re-run with a shuffled node/edge array; order + digest must be identical.
304
+ const permNodes = shuffle([...dag.nodes]);
305
+ const permEdges = shuffle([...dag.edges]);
306
+ const perm = buildPromptDag({
307
+ sessionId: dag.sessionId,
308
+ sourceHighWater: dag.sourceHighWater,
309
+ nodes: permNodes,
310
+ edges: permEdges,
311
+ });
312
+ assert.ok(perm.ok, `${fx.id}: permuted build must succeed`);
313
+ const vPerm = validatePromptDag(perm.dag);
314
+ const orderSame = v.ok && vPerm.ok && eq(v.order, vPerm.order);
315
+ const digestSame = dagDigest(perm.dag) === digest;
316
+ return { ok: true, permutationInvariant: orderSame, digestStable: digestSame };
317
+ }
318
+ if (fx.expected.digestStable) {
319
+ const perm = buildPromptDag({
320
+ sessionId: dag.sessionId,
321
+ sourceHighWater: dag.sourceHighWater,
322
+ nodes: shuffle([...dag.nodes]),
323
+ edges: shuffle([...dag.edges]),
324
+ });
325
+ assert.ok(perm.ok);
326
+ return { ok: true, digestStable: dagDigest(perm.dag) === digest };
327
+ }
328
+ if (fx.expected.digestSensitive) {
329
+ const mutated = {
330
+ ...dag,
331
+ nodes: dag.nodes.map((n, i) => i === 0 ? { ...n, payloadDigest: `pd:mutated-${n.id}` } : n),
332
+ };
333
+ return { ok: true, digestSensitive: dagDigest(mutated) !== digest };
334
+ }
335
+ if (fx.expected.orderIsTotal) {
336
+ const ok = v.ok && v.order.length === dag.nodes.length && new Set(v.order).size === dag.nodes.length;
337
+ return { ok, orderIsTotal: ok };
338
+ }
339
+ if (fx.expected.orderLength !== undefined) {
340
+ return { ok: v.ok, orderLength: v.ok ? v.order.length : 0 };
341
+ }
342
+ // Plain order / failure-code scenarios.
343
+ if (!v.ok)
344
+ return { ok: false, code: v.codes[0] };
345
+ void mutateDigest;
346
+ return { ok: true, order: [...v.order] };
347
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * prompt-dag/_acceptance-fixture.ts — conformance fixture I/O for VC5A DAG +
3
+ * planner acceptance rows.
4
+ *
5
+ * Reads the v2 conformance manifest + the per-sprint fixture files, and exposes
6
+ * the `DagFixture` / `PlnFixture` shapes the acceptance aggregator drives. No
7
+ * mocks/stubs: the fixtures are the committed canonical corpus.
8
+ */
9
+ import { readFileSync, existsSync } from "node:fs";
10
+ import { join, dirname } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import assert from "node:assert/strict";
13
+ const HERE = dirname(fileURLToPath(import.meta.url));
14
+ function repoRoot(from) {
15
+ let dir = from;
16
+ for (let i = 0; i < 8; i++) {
17
+ if (existsSync(join(dir, "conformance", "vector-cortex")))
18
+ return dir;
19
+ const next = dirname(dir);
20
+ if (next === dir)
21
+ break;
22
+ dir = next;
23
+ }
24
+ throw new Error("conformance corpus not found above " + from);
25
+ }
26
+ export const REPO_ROOT = repoRoot(HERE);
27
+ export const V2 = join(REPO_ROOT, "conformance", "vector-cortex", "v2");
28
+ export function readManifest() {
29
+ return JSON.parse(readFileSync(join(V2, "manifest.json"), "utf8"));
30
+ }
31
+ /** Resolve a fixture row by ID, restricting to one of this sprint's two roots. */
32
+ function readFixture(id, prefix) {
33
+ const m = readManifest();
34
+ const row = m.fixtures.find((f) => f.id === id && f.path.startsWith(`${prefix}/`));
35
+ assert.ok(row, `fixture ${id} registered under ${prefix}/ in manifest`);
36
+ return JSON.parse(readFileSync(join(V2, row.path), "utf8"));
37
+ }
38
+ export function dagFixture(id) {
39
+ return readFixture(id, "prompt-dag");
40
+ }
41
+ export function plnFixture(id) {
42
+ return readFixture(id, "planner");
43
+ }
44
+ /** Flag-pinned wrapper: VC5A gated by MEGACOMPACT_VC5A (defaults ON). */
45
+ export function withFlagsOn(fn) {
46
+ return () => {
47
+ const saved = process.env.MEGACOMPACT_VC5A;
48
+ process.env.MEGACOMPACT_VC5A = "1";
49
+ try {
50
+ fn();
51
+ }
52
+ finally {
53
+ if (saved === undefined)
54
+ delete process.env.MEGACOMPACT_VC5A;
55
+ else
56
+ process.env.MEGACOMPACT_VC5A = saved;
57
+ }
58
+ };
59
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * prompt-dag/_acceptance-helpers.ts — barrel re-export for the VC5A DAG +
3
+ * planner acceptance rows.
4
+ *
5
+ * The implementation is split across focused sibling files to keep each under
6
+ * the src/ 500-line hard limit; this file is the single import surface the
7
+ * acceptance aggregator uses:
8
+ * - `_acceptance-fixture.ts` — manifest I/O + DagFixture/PlnFixture shapes + withFlagsOn
9
+ * - `_acceptance-dag.ts` — declarative DAG materialization + runDagScenario
10
+ * - `_acceptance-planner.ts` — candidate-set materialization + runPlannerScenario
11
+ * - `_acceptance-shuffle.ts` — deterministic eq/shuffle helpers
12
+ *
13
+ * Every function drives the REAL prompt-dag / planner logic — no mocks, no stubs.
14
+ */
15
+ export { REPO_ROOT, V2, readManifest, dagFixture, plnFixture, withFlagsOn, } from "./_acceptance-fixture.js";
16
+ export { materializeDag, runDagScenario } from "./_acceptance-dag.js";
17
+ export { materializeCandidates, runPlannerScenario } from "./_acceptance-planner.js";
@@ -0,0 +1,153 @@
1
+ /**
2
+ * prompt-dag/_acceptance-planner.ts — declarative candidate-set materialization
3
+ * + scenario driver for VC5A planner acceptance rows.
4
+ *
5
+ * Turns named candidate-set scenarios into real `PlanCandidate[]` values and
6
+ * drives them through the REAL portfolio planner (planPortfolio). The
7
+ * `mandatoryTokenEstimate` is the VC4C content-only figure = sum of mandatory
8
+ * content tokens (zero-framing fixtures pin this exactly).
9
+ */
10
+ import assert from "node:assert/strict";
11
+ import { planPortfolio, planManifestDigest, validatePlanManifest, } from "../planner/portfolio.js";
12
+ import { DEFAULT_FRAMING } from "../planner/types.js";
13
+ import { shuffle } from "./_acceptance-shuffle.js";
14
+ export function materializeCandidates(name) {
15
+ const sets = {
16
+ // m1=10, m2=20 mandatory → 30 content tokens (zero-framing fixtures pin this).
17
+ "mandatory-only": [
18
+ { nodeId: "m1", tokenEstimate: 10, utility: 100, sourceSeq: 1n, mandatory: true },
19
+ { nodeId: "m2", tokenEstimate: 20, utility: 200, sourceSeq: 2n, mandatory: true },
20
+ ],
21
+ // Two mandatory nodes totalling 101 content tokens → over budget at 100.
22
+ "mandatory-101": [
23
+ { nodeId: "m1", tokenEstimate: 50, utility: 100, sourceSeq: 1n, mandatory: true },
24
+ { nodeId: "m2", tokenEstimate: 51, utility: 200, sourceSeq: 2n, mandatory: true },
25
+ ],
26
+ // Optional candidates: hi=10t/20u (ratio 2.0), mid=10t/10u (ratio 1.0), lo=10t/5u.
27
+ ratio: [
28
+ { nodeId: "hi", tokenEstimate: 10, utility: 20, sourceSeq: 1n, mandatory: false },
29
+ { nodeId: "mid", tokenEstimate: 10, utility: 10, sourceSeq: 2n, mandatory: false },
30
+ { nodeId: "lo", tokenEstimate: 10, utility: 5, sourceSeq: 3n, mandatory: false },
31
+ ],
32
+ // Equal ratios (1.0) → source seq tie-break (early=seq1 wins).
33
+ "tie-seq": [
34
+ { nodeId: "early", tokenEstimate: 10, utility: 10, sourceSeq: 1n, mandatory: false },
35
+ { nodeId: "late", tokenEstimate: 10, utility: 10, sourceSeq: 2n, mandatory: false },
36
+ ],
37
+ // Equal ratios AND equal source seq → lower ID bytes wins.
38
+ "tie-id": [
39
+ { nodeId: "bbb", tokenEstimate: 10, utility: 10, sourceSeq: 1n, mandatory: false },
40
+ { nodeId: "aaa", tokenEstimate: 10, utility: 10, sourceSeq: 1n, mandatory: false },
41
+ ],
42
+ // One indivisible optional node of 10t/10u: 0/1 means it is taken whole.
43
+ indivisible: [
44
+ { nodeId: "one", tokenEstimate: 10, utility: 10, sourceSeq: 1n, mandatory: false },
45
+ ],
46
+ // A zero-utility optional node must be omitted.
47
+ "zero-utility": [
48
+ { nodeId: "zero", tokenEstimate: 5, utility: 0, sourceSeq: 1n, mandatory: false },
49
+ ],
50
+ // An optional node incompatible with a mandatory node is omitted (the planner
51
+ // enforces `incompatiblePairs`; here `bad` is mutually exclusive with `m1`).
52
+ incompatible: [
53
+ { nodeId: "m1", tokenEstimate: 10, utility: 100, sourceSeq: 1n, mandatory: true },
54
+ { nodeId: "bad", tokenEstimate: 5, utility: 50, sourceSeq: 2n, mandatory: false },
55
+ ],
56
+ // Mixed: 2 mandatory + optional candidates exercising closure + selection.
57
+ mixed: [
58
+ { nodeId: "m1", tokenEstimate: 10, utility: 100, sourceSeq: 1n, mandatory: true },
59
+ { nodeId: "m2", tokenEstimate: 20, utility: 200, sourceSeq: 2n, mandatory: true },
60
+ { nodeId: "opt", tokenEstimate: 30, utility: 90, sourceSeq: 3n, mandatory: false },
61
+ ],
62
+ };
63
+ const set = sets[name];
64
+ assert.ok(set, `candidate-set materializer missing for "${name}"`);
65
+ return set;
66
+ }
67
+ function framing(zero) {
68
+ return zero ? { perNode: 0, overhead: 0 } : DEFAULT_FRAMING;
69
+ }
70
+ /**
71
+ * Drive a planner fixture scenario through the REAL portfolio planner. The
72
+ * `mandatoryTokenEstimate` is the VC4C CONTENT-ONLY figure = sum of mandatory
73
+ * content tokens (zero-framing fixtures make that the exact candidate sum).
74
+ */
75
+ export function runPlannerScenario(fx) {
76
+ const { candidates: setName, tokenBudget, zeroFraming, permute, mutateTokensAfterPlan } = fx.input;
77
+ const cands = materializeCandidates(setName);
78
+ // VC4C content-only mandatory estimate = sum of mandatory content tokens.
79
+ const mandatoryTokenEstimate = cands
80
+ .filter((c) => c.mandatory)
81
+ .reduce((s, c) => s + c.tokenEstimate, 0);
82
+ const dagDigest = "deadbeef".repeat(8);
83
+ // The `incompatible` set wires an explicit mutual-exclusion pair (bad ⊥ m1) so
84
+ // the planner's incompatibility gate is exercised end-to-end.
85
+ const incompatiblePairs = setName === "incompatible" ? [["bad", "m1"]] : undefined;
86
+ const input = {
87
+ dagDigest,
88
+ candidates: permute ? shuffle([...cands]) : cands,
89
+ mandatoryTokenEstimate,
90
+ tokenBudget,
91
+ dependencyHighWater: 100n,
92
+ framing: framing(zeroFraming),
93
+ ...(incompatiblePairs ? { incompatiblePairs } : {}),
94
+ };
95
+ const res = planPortfolio(input);
96
+ if (fx.expected.permutationInvariant) {
97
+ const a = planPortfolio(input);
98
+ const b = planPortfolio({ ...input, candidates: shuffle([...cands]) });
99
+ const ids = (ok) => (ok.ok ? [...ok.plan.selectedNodeIds] : []);
100
+ return { ok: true, permutationInvariant: JSON.stringify(ids(a)) === JSON.stringify(ids(b)) };
101
+ }
102
+ if (!res.ok) {
103
+ // Over-budget failure: mandatory set preserved intact, demotes to C.
104
+ const mandatoryIds = cands.filter((c) => c.mandatory).map((c) => c.nodeId).sort();
105
+ const preserved = res.mandatory.slice().sort().join(",") === mandatoryIds.join(",");
106
+ return {
107
+ ok: false,
108
+ code: res.code,
109
+ mandatoryPreserved: preserved,
110
+ demotesToC: res.code === "MANDATORY_CLOSURE_OVER_BUDGET",
111
+ };
112
+ }
113
+ // ── Accepted-plan assertions ──
114
+ const plan = res.plan;
115
+ const selected = [...plan.selectedNodeIds];
116
+ const out = { ok: true, selected, tokenTotal: plan.tokenTotal };
117
+ if (fx.expected.firstSelected !== undefined)
118
+ out.firstSelected = selected[0];
119
+ if (fx.expected.withinBudget)
120
+ out.withinBudget = plan.tokenTotal <= tokenBudget;
121
+ if (fx.expected.noPartialSelection) {
122
+ out.noPartialSelection = cands.every((c) => !c.mandatory || selected.includes(c.nodeId));
123
+ }
124
+ if (fx.expected.planIsClosed) {
125
+ const mandatoryIds = cands.filter((c) => c.mandatory).map((c) => c.nodeId).sort();
126
+ out.planIsClosed = mandatoryIds.every((id) => selected.includes(id));
127
+ }
128
+ if (fx.expected.omittedOverBudget) {
129
+ const omit = plan.omissions.some((o) => o.reason === "over-budget");
130
+ out.omittedOverBudget = omit;
131
+ }
132
+ if (fx.expected.omittedZeroUtility) {
133
+ out.omittedZeroUtility = plan.omissions.some((o) => o.reason === "zero-utility");
134
+ }
135
+ if (fx.expected.omittedIncompatible) {
136
+ out.omittedIncompatible = plan.omissions.some((o) => o.reason === "incompatible");
137
+ }
138
+ // Manifest identity: a post-plan token mutation must fail revalidation.
139
+ if (mutateTokensAfterPlan) {
140
+ const pinned = planManifestDigest(plan, cands);
141
+ const mutated = cands.map((c, i) => (i === 0 ? { ...c, tokenEstimate: c.tokenEstimate + 1 } : c));
142
+ const check = validatePlanManifest(plan, mutated, pinned);
143
+ out.ok = check.ok; // false → plan rejected, never reaches provider
144
+ out.code = check.ok ? undefined : check.code;
145
+ return out;
146
+ }
147
+ if (fx.expected.manifestStable) {
148
+ const pinned = planManifestDigest(plan, cands);
149
+ const check = validatePlanManifest(plan, cands, pinned);
150
+ out.manifestStable = check.ok;
151
+ }
152
+ return out;
153
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * prompt-dag/_acceptance-shuffle.ts — small deterministic helpers shared by the
3
+ * VC5A acceptance DAG/planner materializers.
4
+ */
5
+ /** Array element equality (length + pairwise). */
6
+ export function eq(a, b) {
7
+ return a.length === b.length && a.every((v, i) => v === b[i]);
8
+ }
9
+ /**
10
+ * Fisher–Yates with a FIXED seed so permutation tests are reproducible across
11
+ * runs and machines (no Math.random in tests).
12
+ */
13
+ export function shuffle(arr) {
14
+ const a = [...arr];
15
+ let seed = 0x9e3779b9;
16
+ for (let i = a.length - 1; i > 0; i--) {
17
+ seed = (seed * 1664525 + 1013904223) >>> 0;
18
+ const j = seed % (i + 1);
19
+ const t = a[i];
20
+ a[i] = a[j];
21
+ a[j] = t;
22
+ }
23
+ return a;
24
+ }