@mhingston5/lasso 0.1.0
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/README.md +707 -0
- package/docs/agent-wrangling.png +0 -0
- package/package.json +26 -0
- package/src/capabilities/matcher.ts +25 -0
- package/src/capabilities/registry.ts +103 -0
- package/src/capabilities/types.ts +15 -0
- package/src/cir/lower.ts +253 -0
- package/src/cir/optimize.ts +251 -0
- package/src/cir/types.ts +131 -0
- package/src/cir/validate.ts +265 -0
- package/src/compiler/compile.ts +601 -0
- package/src/compiler/feedback.ts +471 -0
- package/src/compiler/runtime-helpers.ts +455 -0
- package/src/composition/chain.ts +58 -0
- package/src/composition/conditional.ts +76 -0
- package/src/composition/parallel.ts +75 -0
- package/src/composition/types.ts +105 -0
- package/src/environment/analyzer.ts +56 -0
- package/src/environment/discovery.ts +179 -0
- package/src/environment/types.ts +68 -0
- package/src/failures/classifiers.ts +134 -0
- package/src/failures/generator.ts +421 -0
- package/src/failures/map-reference-failures.ts +23 -0
- package/src/failures/ontology.ts +210 -0
- package/src/failures/recovery.ts +214 -0
- package/src/failures/types.ts +14 -0
- package/src/index.ts +67 -0
- package/src/memory/advisor.ts +132 -0
- package/src/memory/extractor.ts +166 -0
- package/src/memory/store.ts +107 -0
- package/src/memory/types.ts +53 -0
- package/src/metaharness/engine.ts +256 -0
- package/src/metaharness/predictor.ts +168 -0
- package/src/metaharness/types.ts +40 -0
- package/src/mutation/derive.ts +308 -0
- package/src/mutation/diff.ts +52 -0
- package/src/mutation/engine.ts +256 -0
- package/src/mutation/types.ts +84 -0
- package/src/pi/command-input.ts +209 -0
- package/src/pi/commands.ts +351 -0
- package/src/pi/extension.ts +16 -0
- package/src/planner/synthesize.ts +83 -0
- package/src/planner/template-rules.ts +183 -0
- package/src/planner/types.ts +42 -0
- package/src/reference/catalog.ts +128 -0
- package/src/reference/patch-validation-strategies.ts +170 -0
- package/src/reference/patch-validation.ts +174 -0
- package/src/reference/pr-review-merge.ts +155 -0
- package/src/reference/strategies.ts +126 -0
- package/src/reference/types.ts +33 -0
- package/src/replanner/risk-rules.ts +161 -0
- package/src/replanner/runtime.ts +308 -0
- package/src/replanner/synthesize.ts +619 -0
- package/src/replanner/types.ts +73 -0
- package/src/spec/schema.ts +254 -0
- package/src/spec/types.ts +319 -0
- package/src/spec/validate.ts +296 -0
- package/src/state/snapshots.ts +43 -0
- package/src/state/types.ts +12 -0
- package/src/synthesis/graph-builder.ts +267 -0
- package/src/synthesis/harness-builder.ts +113 -0
- package/src/synthesis/intent-ir.ts +63 -0
- package/src/synthesis/policy-builder.ts +320 -0
- package/src/synthesis/risk-analyzer.ts +182 -0
- package/src/synthesis/skill-parser.ts +441 -0
- package/src/verification/engine.ts +230 -0
- package/src/versioning/file-store.ts +103 -0
- package/src/versioning/history.ts +43 -0
- package/src/versioning/store.ts +16 -0
- package/src/versioning/types.ts +31 -0
- package/test/capabilities/matcher.test.ts +67 -0
- package/test/capabilities/registry.test.ts +136 -0
- package/test/capabilities/synthesis.test.ts +264 -0
- package/test/cir/lower.test.ts +417 -0
- package/test/cir/optimize.test.ts +266 -0
- package/test/cir/validate.test.ts +368 -0
- package/test/compiler/adaptive-runtime.test.ts +157 -0
- package/test/compiler/compile.test.ts +1198 -0
- package/test/compiler/feedback.test.ts +784 -0
- package/test/compiler/guardrails.test.ts +191 -0
- package/test/compiler/trace.test.ts +404 -0
- package/test/composition/chain.test.ts +328 -0
- package/test/composition/conditional.test.ts +241 -0
- package/test/composition/parallel.test.ts +215 -0
- package/test/environment/analyzer.test.ts +204 -0
- package/test/environment/discovery.test.ts +149 -0
- package/test/failures/classifiers.test.ts +287 -0
- package/test/failures/generator.test.ts +203 -0
- package/test/failures/ontology.test.ts +439 -0
- package/test/failures/recovery.test.ts +300 -0
- package/test/helpers/createFixtureRepo.ts +84 -0
- package/test/helpers/createPatchValidationFixture.ts +144 -0
- package/test/helpers/runCompiledWorkflow.ts +208 -0
- package/test/memory/advisor.test.ts +332 -0
- package/test/memory/extractor.test.ts +295 -0
- package/test/memory/store.test.ts +244 -0
- package/test/metaharness/engine.test.ts +575 -0
- package/test/metaharness/predictor.test.ts +436 -0
- package/test/mutation/derive-failure.test.ts +209 -0
- package/test/mutation/engine.test.ts +622 -0
- package/test/package-smoke.test.ts +29 -0
- package/test/pi/command-input.test.ts +153 -0
- package/test/pi/commands.test.ts +623 -0
- package/test/planner/classify-template.test.ts +32 -0
- package/test/planner/synthesize.test.ts +901 -0
- package/test/reference/PatchValidation.failures.test.ts +137 -0
- package/test/reference/PatchValidation.test.ts +326 -0
- package/test/reference/PrReviewMerge.failures.test.ts +121 -0
- package/test/reference/PrReviewMerge.test.ts +55 -0
- package/test/reference/catalog-open.test.ts +70 -0
- package/test/replanner/runtime.test.ts +207 -0
- package/test/replanner/synthesize.test.ts +303 -0
- package/test/spec/validate.test.ts +1056 -0
- package/test/state/snapshots.test.ts +264 -0
- package/test/synthesis/custom-workflow.test.ts +264 -0
- package/test/synthesis/graph-builder.test.ts +370 -0
- package/test/synthesis/harness-builder.test.ts +128 -0
- package/test/synthesis/policy-builder.test.ts +149 -0
- package/test/synthesis/risk-analyzer.test.ts +230 -0
- package/test/synthesis/skill-parser.test.ts +796 -0
- package/test/verification/engine.test.ts +509 -0
- package/test/versioning/history.test.ts +144 -0
- package/test/versioning/store.test.ts +254 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { validateCirWorkflow } from "../../src/cir/validate.js";
|
|
3
|
+
import type { CirWorkflow } from "../../src/cir/types.js";
|
|
4
|
+
|
|
5
|
+
function createValidCirWorkflow(): CirWorkflow {
|
|
6
|
+
return {
|
|
7
|
+
name: "manual-cir",
|
|
8
|
+
entryNodeId: "start",
|
|
9
|
+
nodes: [
|
|
10
|
+
{
|
|
11
|
+
id: "start",
|
|
12
|
+
kind: "tool",
|
|
13
|
+
source: {
|
|
14
|
+
specNodeId: "start",
|
|
15
|
+
specNodeKind: "tool",
|
|
16
|
+
specPath: "graph.nodes[0]"
|
|
17
|
+
},
|
|
18
|
+
action: {
|
|
19
|
+
tool: "echo",
|
|
20
|
+
args: ["start"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "check",
|
|
25
|
+
kind: "condition",
|
|
26
|
+
source: {
|
|
27
|
+
specNodeId: "check",
|
|
28
|
+
specNodeKind: "condition",
|
|
29
|
+
specPath: "graph.nodes[1]"
|
|
30
|
+
},
|
|
31
|
+
action: {
|
|
32
|
+
conditionExpr: "result.ok"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "approve",
|
|
37
|
+
kind: "human",
|
|
38
|
+
source: {
|
|
39
|
+
specNodeId: "approve",
|
|
40
|
+
specNodeKind: "human",
|
|
41
|
+
specPath: "graph.nodes[2]"
|
|
42
|
+
},
|
|
43
|
+
action: {
|
|
44
|
+
prompt: "Approve?",
|
|
45
|
+
interactionType: "approval"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "verify",
|
|
50
|
+
kind: "tool",
|
|
51
|
+
source: {
|
|
52
|
+
specNodeId: "verify",
|
|
53
|
+
specNodeKind: "tool",
|
|
54
|
+
specPath: "graph.nodes[3]"
|
|
55
|
+
},
|
|
56
|
+
action: {
|
|
57
|
+
tool: "npm",
|
|
58
|
+
args: ["test"]
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "join",
|
|
63
|
+
kind: "merge",
|
|
64
|
+
source: {
|
|
65
|
+
specNodeId: "join",
|
|
66
|
+
specNodeKind: "merge",
|
|
67
|
+
specPath: "graph.nodes[4]"
|
|
68
|
+
},
|
|
69
|
+
action: {
|
|
70
|
+
join: {
|
|
71
|
+
waitFor: ["approve", "verify"],
|
|
72
|
+
strategy: "all"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "finish",
|
|
78
|
+
kind: "subworkflow",
|
|
79
|
+
source: {
|
|
80
|
+
specNodeId: "finish",
|
|
81
|
+
specNodeKind: "subworkflow",
|
|
82
|
+
specPath: "graph.nodes[5]"
|
|
83
|
+
},
|
|
84
|
+
action: {
|
|
85
|
+
specRef: "finish"
|
|
86
|
+
},
|
|
87
|
+
terminal: true
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "reject",
|
|
91
|
+
kind: "subworkflow",
|
|
92
|
+
source: {
|
|
93
|
+
specNodeId: "reject",
|
|
94
|
+
specNodeKind: "subworkflow",
|
|
95
|
+
specPath: "graph.nodes[6]"
|
|
96
|
+
},
|
|
97
|
+
action: {
|
|
98
|
+
specRef: "reject"
|
|
99
|
+
},
|
|
100
|
+
terminal: true
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
transitions: [
|
|
104
|
+
{
|
|
105
|
+
from: "start",
|
|
106
|
+
to: "check",
|
|
107
|
+
when: "success",
|
|
108
|
+
source: {
|
|
109
|
+
kind: "graph-edge",
|
|
110
|
+
specPath: "graph.edges[0]"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
from: "start",
|
|
115
|
+
to: "verify",
|
|
116
|
+
when: "success",
|
|
117
|
+
source: {
|
|
118
|
+
kind: "graph-edge",
|
|
119
|
+
specPath: "graph.edges[1]"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
from: "check",
|
|
124
|
+
to: "approve",
|
|
125
|
+
when: "condition-true",
|
|
126
|
+
source: {
|
|
127
|
+
kind: "condition-then",
|
|
128
|
+
specNodeId: "check",
|
|
129
|
+
specPath: "graph.nodes[1].thenNodeId"
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
from: "check",
|
|
134
|
+
to: "reject",
|
|
135
|
+
when: "condition-false",
|
|
136
|
+
source: {
|
|
137
|
+
kind: "condition-else",
|
|
138
|
+
specNodeId: "check",
|
|
139
|
+
specPath: "graph.nodes[1].elseNodeId"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
from: "approve",
|
|
144
|
+
to: "join",
|
|
145
|
+
when: "success",
|
|
146
|
+
source: {
|
|
147
|
+
kind: "graph-edge",
|
|
148
|
+
specPath: "graph.edges[2]"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
from: "verify",
|
|
153
|
+
to: "join",
|
|
154
|
+
when: "success",
|
|
155
|
+
source: {
|
|
156
|
+
kind: "graph-edge",
|
|
157
|
+
specPath: "graph.edges[3]"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
from: "join",
|
|
162
|
+
to: "finish",
|
|
163
|
+
when: "success",
|
|
164
|
+
source: {
|
|
165
|
+
kind: "graph-edge",
|
|
166
|
+
specPath: "graph.edges[4]"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function cloneWorkflow(workflow: CirWorkflow): CirWorkflow {
|
|
174
|
+
return structuredClone(workflow);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
describe("validateCirWorkflow", () => {
|
|
178
|
+
it("accepts a well-formed CIR workflow", () => {
|
|
179
|
+
const result = validateCirWorkflow(createValidCirWorkflow());
|
|
180
|
+
|
|
181
|
+
expect(result.valid).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("rejects dangling transitions", () => {
|
|
185
|
+
const workflow = cloneWorkflow(createValidCirWorkflow());
|
|
186
|
+
workflow.transitions[0] = {
|
|
187
|
+
...workflow.transitions[0],
|
|
188
|
+
to: "missing-node"
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const result = validateCirWorkflow(workflow);
|
|
192
|
+
|
|
193
|
+
expect(result.valid).toBe(false);
|
|
194
|
+
if (!result.valid) {
|
|
195
|
+
expect(result.errors.some(error => error.includes("missing-node"))).toBe(true);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("allows unreachable verifier nodes when referenced by verification hooks", () => {
|
|
200
|
+
const workflow = cloneWorkflow(createValidCirWorkflow());
|
|
201
|
+
workflow.nodes.push({
|
|
202
|
+
id: "orphan-check",
|
|
203
|
+
kind: "tool",
|
|
204
|
+
source: {
|
|
205
|
+
specNodeId: "orphan-check",
|
|
206
|
+
specNodeKind: "tool",
|
|
207
|
+
specPath: "graph.nodes[99]"
|
|
208
|
+
},
|
|
209
|
+
action: {
|
|
210
|
+
tool: "echo",
|
|
211
|
+
args: ["orphan"]
|
|
212
|
+
},
|
|
213
|
+
terminal: true
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const startNode = workflow.nodes.find(node => node.id === "start");
|
|
217
|
+
if (!startNode || startNode.kind !== "tool") {
|
|
218
|
+
throw new Error("start node is missing");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
startNode.verification = [
|
|
222
|
+
{
|
|
223
|
+
kind: "tool",
|
|
224
|
+
checkNodeId: "orphan-check",
|
|
225
|
+
onFail: "block"
|
|
226
|
+
}
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
const result = validateCirWorkflow(workflow);
|
|
230
|
+
|
|
231
|
+
expect(result.valid).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("rejects non-terminal nodes with no follow-on transition", () => {
|
|
235
|
+
const workflow = cloneWorkflow(createValidCirWorkflow());
|
|
236
|
+
workflow.transitions = workflow.transitions.filter(transition => !(transition.from === "join" && transition.to === "finish"));
|
|
237
|
+
|
|
238
|
+
const result = validateCirWorkflow(workflow);
|
|
239
|
+
|
|
240
|
+
expect(result.valid).toBe(false);
|
|
241
|
+
if (!result.valid) {
|
|
242
|
+
expect(result.errors.some(error => error.includes("join") && error.includes("outgoing"))).toBe(true);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("rejects condition nodes that branch to themselves", () => {
|
|
247
|
+
const workflow = cloneWorkflow(createValidCirWorkflow());
|
|
248
|
+
workflow.transitions = workflow.transitions.map(transition =>
|
|
249
|
+
transition.from === "check" && transition.when === "condition-true"
|
|
250
|
+
? {
|
|
251
|
+
...transition,
|
|
252
|
+
to: "check"
|
|
253
|
+
}
|
|
254
|
+
: transition,
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const result = validateCirWorkflow(workflow);
|
|
258
|
+
|
|
259
|
+
expect(result.valid).toBe(false);
|
|
260
|
+
if (!result.valid) {
|
|
261
|
+
expect(result.errors.some(error => error.includes("Condition node check cannot branch to itself"))).toBe(true);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("rejects retry metadata on unsupported node kinds", () => {
|
|
266
|
+
const workflow = cloneWorkflow(createValidCirWorkflow());
|
|
267
|
+
const approveNode = workflow.nodes.find(node => node.id === "approve");
|
|
268
|
+
if (!approveNode || approveNode.kind !== "human") {
|
|
269
|
+
throw new Error("approve node is missing");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
approveNode.retry = {
|
|
273
|
+
maxAttempts: 2,
|
|
274
|
+
backoff: "linear"
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const result = validateCirWorkflow(workflow);
|
|
278
|
+
|
|
279
|
+
expect(result.valid).toBe(false);
|
|
280
|
+
if (!result.valid) {
|
|
281
|
+
expect(result.errors.some(error => error.includes("retry") && error.includes("approve"))).toBe(true);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("rejects merge nodes fed by non-success transitions", () => {
|
|
286
|
+
const workflow = cloneWorkflow(createValidCirWorkflow());
|
|
287
|
+
workflow.transitions = workflow.transitions.map(transition =>
|
|
288
|
+
transition.from === "approve" && transition.to === "join"
|
|
289
|
+
? {
|
|
290
|
+
...transition,
|
|
291
|
+
when: "condition-true"
|
|
292
|
+
}
|
|
293
|
+
: transition,
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const result = validateCirWorkflow(workflow);
|
|
297
|
+
|
|
298
|
+
expect(result.valid).toBe(false);
|
|
299
|
+
if (!result.valid) {
|
|
300
|
+
expect(
|
|
301
|
+
result.errors.some(
|
|
302
|
+
error => error.includes("Merge node join can only receive success transitions") && error.includes("approve"),
|
|
303
|
+
),
|
|
304
|
+
).toBe(true);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("rejects verification hooks attached to nodes without post-node outputs", () => {
|
|
309
|
+
const workflow = cloneWorkflow(createValidCirWorkflow());
|
|
310
|
+
const joinNode = workflow.nodes.find(node => node.id === "join");
|
|
311
|
+
if (!joinNode || joinNode.kind !== "merge") {
|
|
312
|
+
throw new Error("join node is missing");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
joinNode.verification = [
|
|
316
|
+
{
|
|
317
|
+
checkNodeId: "verify",
|
|
318
|
+
onFail: "block"
|
|
319
|
+
}
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
const result = validateCirWorkflow(workflow);
|
|
323
|
+
|
|
324
|
+
expect(result.valid).toBe(false);
|
|
325
|
+
if (!result.valid) {
|
|
326
|
+
expect(result.errors.some(error => error.includes("verification") && error.includes("join"))).toBe(true);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("rejects verification hooks that target unsupported verifier node kinds", () => {
|
|
331
|
+
const workflow = cloneWorkflow(createValidCirWorkflow());
|
|
332
|
+
const startNode = workflow.nodes.find(node => node.id === "start");
|
|
333
|
+
if (!startNode || startNode.kind !== "tool") {
|
|
334
|
+
throw new Error("start node is missing");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
startNode.verification = [
|
|
338
|
+
{
|
|
339
|
+
checkNodeId: "check",
|
|
340
|
+
onFail: "block"
|
|
341
|
+
}
|
|
342
|
+
];
|
|
343
|
+
|
|
344
|
+
const result = validateCirWorkflow(workflow);
|
|
345
|
+
|
|
346
|
+
expect(result.valid).toBe(false);
|
|
347
|
+
if (!result.valid) {
|
|
348
|
+
expect(result.errors.some(error => error.includes("check") && error.toLowerCase().includes("verification"))).toBe(true);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("rejects merge joins whose waitFor set does not match incoming transitions", () => {
|
|
353
|
+
const workflow = cloneWorkflow(createValidCirWorkflow());
|
|
354
|
+
const joinNode = workflow.nodes.find(node => node.id === "join");
|
|
355
|
+
if (!joinNode || joinNode.kind !== "merge") {
|
|
356
|
+
throw new Error("join node is missing");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
joinNode.action.join.waitFor = ["approve", "start"];
|
|
360
|
+
|
|
361
|
+
const result = validateCirWorkflow(workflow);
|
|
362
|
+
|
|
363
|
+
expect(result.valid).toBe(false);
|
|
364
|
+
if (!result.valid) {
|
|
365
|
+
expect(result.errors.some(error => error.includes("waitFor") && error.includes("start"))).toBe(true);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
+
import { compileHarnessSpec } from "../../src/compiler/compile.js";
|
|
3
|
+
import { buildReferenceHarnessSpec } from "../../src/reference/catalog.js";
|
|
4
|
+
import { prepareInitialAdaptiveInput, unwrapAdaptiveInput } from "../../src/replanner/runtime.js";
|
|
5
|
+
import { runCompiledWorkflow } from "../helpers/runCompiledWorkflow.js";
|
|
6
|
+
import { createPatchValidationFixture, type PatchValidationFixture } from "../helpers/createPatchValidationFixture.js";
|
|
7
|
+
import type { ReferenceWorkflowRequest } from "../../src/reference/catalog.js";
|
|
8
|
+
|
|
9
|
+
describe("adaptive runtime integration", () => {
|
|
10
|
+
const fixtures: PatchValidationFixture[] = [];
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
for (const fixture of fixtures) {
|
|
14
|
+
fixture.cleanup();
|
|
15
|
+
}
|
|
16
|
+
fixtures.length = 0;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should trigger continueAsNew for high-risk validated-fix and escalate to approvalRequired", async () => {
|
|
20
|
+
const fixture = createPatchValidationFixture({ candidateKind: "patchFile" });
|
|
21
|
+
fixtures.push(fixture);
|
|
22
|
+
|
|
23
|
+
const request: ReferenceWorkflowRequest = {
|
|
24
|
+
workflow: "patch-validation",
|
|
25
|
+
input: fixture.bundle,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const initialSpec = buildReferenceHarnessSpec(request);
|
|
29
|
+
const compiled = compileHarnessSpec(initialSpec);
|
|
30
|
+
const adaptiveInput = prepareInitialAdaptiveInput(request, initialSpec, {});
|
|
31
|
+
|
|
32
|
+
const result = await runCompiledWorkflow(
|
|
33
|
+
compiled,
|
|
34
|
+
adaptiveInput as any,
|
|
35
|
+
{
|
|
36
|
+
llmResult: { approved: true },
|
|
37
|
+
humanResponse: { approved: true },
|
|
38
|
+
maxContinuations: 2,
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(result.adaptiveMetadata).toBeDefined();
|
|
43
|
+
expect(result.adaptiveMetadata?.currentVersion.version).toBe(2);
|
|
44
|
+
expect(result.adaptiveMetadata?.currentVersion.parentVersion).toBe(1);
|
|
45
|
+
expect(result.adaptiveMetadata?.currentVersion.reason).toMatch(/escalation|risk/);
|
|
46
|
+
expect(result.adaptiveMetadata?.currentRequest.workflow).toBe("patch-validation");
|
|
47
|
+
expect(result.adaptiveMetadata?.currentRequest.input.approvalRequired).toBe(true);
|
|
48
|
+
expect(result.lineage).toHaveLength(1);
|
|
49
|
+
expect(result.lineage?.[0].version).toBe(1);
|
|
50
|
+
expect(result.lineage?.[0].terminalNodeId).toBe("validated-fix");
|
|
51
|
+
|
|
52
|
+
const adaptiveMetadata = result.adaptiveMetadata!;
|
|
53
|
+
const v2Spec = adaptiveMetadata.currentVersion.spec;
|
|
54
|
+
|
|
55
|
+
const v2GraphNode = v2Spec.graph.nodes.find(n => n.kind === "human");
|
|
56
|
+
expect(v2GraphNode).toBeDefined();
|
|
57
|
+
expect(v2GraphNode?.kind).toBe("human");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should stop after validated-fix with low risk and no escalation", async () => {
|
|
61
|
+
const fixture = createPatchValidationFixture({ candidateKind: "branch" });
|
|
62
|
+
fixtures.push(fixture);
|
|
63
|
+
|
|
64
|
+
const request: ReferenceWorkflowRequest = {
|
|
65
|
+
workflow: "patch-validation",
|
|
66
|
+
input: fixture.bundle,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const initialSpec = buildReferenceHarnessSpec(request);
|
|
70
|
+
const compiled = compileHarnessSpec(initialSpec);
|
|
71
|
+
const adaptiveInput = prepareInitialAdaptiveInput(request, initialSpec, {});
|
|
72
|
+
|
|
73
|
+
const result = await runCompiledWorkflow(
|
|
74
|
+
compiled,
|
|
75
|
+
adaptiveInput as any,
|
|
76
|
+
{
|
|
77
|
+
llmResult: { approved: true },
|
|
78
|
+
humanResponse: { approved: true },
|
|
79
|
+
maxContinuations: 0,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(result.adaptiveMetadata).toBeDefined();
|
|
84
|
+
expect(result.adaptiveMetadata?.currentVersion.version).toBe(1);
|
|
85
|
+
expect(result.lineage).toHaveLength(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should carry lineage metadata through result", async () => {
|
|
89
|
+
const fixture = createPatchValidationFixture({ candidateKind: "patchFile" });
|
|
90
|
+
fixtures.push(fixture);
|
|
91
|
+
|
|
92
|
+
const request: ReferenceWorkflowRequest = {
|
|
93
|
+
workflow: "patch-validation",
|
|
94
|
+
input: fixture.bundle,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const initialSpec = buildReferenceHarnessSpec(request);
|
|
98
|
+
const compiled = compileHarnessSpec(initialSpec);
|
|
99
|
+
const adaptiveInput = prepareInitialAdaptiveInput(request, initialSpec, {});
|
|
100
|
+
|
|
101
|
+
const result = await runCompiledWorkflow(
|
|
102
|
+
compiled,
|
|
103
|
+
adaptiveInput as any,
|
|
104
|
+
{
|
|
105
|
+
llmResult: { approved: true },
|
|
106
|
+
humanResponse: { approved: true },
|
|
107
|
+
maxContinuations: 1,
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(result.lineage).toBeDefined();
|
|
112
|
+
expect(result.lineage).toHaveLength(1);
|
|
113
|
+
|
|
114
|
+
const lineageEntry = result.lineage![0];
|
|
115
|
+
expect(lineageEntry.version).toBe(1);
|
|
116
|
+
expect(lineageEntry.terminalNodeId).toBe("validated-fix");
|
|
117
|
+
expect(lineageEntry.outputs).toBeDefined();
|
|
118
|
+
expect(lineageEntry.nodeResults).toBeDefined();
|
|
119
|
+
expect(lineageEntry.metrics).toBeDefined();
|
|
120
|
+
expect(lineageEntry.trace).toBeDefined();
|
|
121
|
+
expect(lineageEntry.completedAt).toBeGreaterThan(0);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should work without adaptive envelope for custom specs", async () => {
|
|
125
|
+
const fixture = createPatchValidationFixture();
|
|
126
|
+
fixtures.push(fixture);
|
|
127
|
+
|
|
128
|
+
const customSpec = {
|
|
129
|
+
name: "simple-test",
|
|
130
|
+
graph: {
|
|
131
|
+
entryNodeId: "start",
|
|
132
|
+
nodes: [
|
|
133
|
+
{
|
|
134
|
+
kind: "tool" as const,
|
|
135
|
+
id: "start",
|
|
136
|
+
label: "Start",
|
|
137
|
+
tool: "bash",
|
|
138
|
+
args: ["-c", "echo test"],
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
edges: [],
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const compiled = compileHarnessSpec(customSpec);
|
|
146
|
+
|
|
147
|
+
const result = await runCompiledWorkflow(
|
|
148
|
+
compiled,
|
|
149
|
+
{ repoPath: fixture.bundle.repoPath },
|
|
150
|
+
{},
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(result.status).toBe("completed");
|
|
154
|
+
expect(result.adaptiveMetadata).toBeUndefined();
|
|
155
|
+
expect(result.lineage).toBeUndefined();
|
|
156
|
+
});
|
|
157
|
+
});
|