@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,287 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
classifyAuthFailure,
|
|
4
|
+
classifyToolFailure,
|
|
5
|
+
classifyResourceFailure,
|
|
6
|
+
classifySemanticFailure,
|
|
7
|
+
classifyHumanFailure,
|
|
8
|
+
classifyEnvironmentDriftFailure,
|
|
9
|
+
classifyNetworkFailure,
|
|
10
|
+
} from "../../src/failures/classifiers.js";
|
|
11
|
+
|
|
12
|
+
describe("Failure classifiers", () => {
|
|
13
|
+
describe("classifyAuthFailure", () => {
|
|
14
|
+
it("should match 401 status code", () => {
|
|
15
|
+
const result = classifyAuthFailure(new Error("Request failed with status code 401"));
|
|
16
|
+
expect(result.matched).toBe(true);
|
|
17
|
+
expect(result.evidence).toContain("HTTP 401");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should match 403 status code", () => {
|
|
21
|
+
const result = classifyAuthFailure(new Error("Forbidden: status 403"));
|
|
22
|
+
expect(result.matched).toBe(true);
|
|
23
|
+
expect(result.evidence).toContain("HTTP 403");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should match 'unauthorized' message", () => {
|
|
27
|
+
const result = classifyAuthFailure(new Error("Unauthorized access denied"));
|
|
28
|
+
expect(result.matched).toBe(true);
|
|
29
|
+
expect(result.evidence).toContain("unauthorized");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should match 'authentication required' message", () => {
|
|
33
|
+
const result = classifyAuthFailure(new Error("Authentication required to proceed"));
|
|
34
|
+
expect(result.matched).toBe(true);
|
|
35
|
+
expect(result.evidence).toContain("authentication required");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should match 'token expired' message", () => {
|
|
39
|
+
const result = classifyAuthFailure(new Error("Token expired, please refresh"));
|
|
40
|
+
expect(result.matched).toBe(true);
|
|
41
|
+
expect(result.evidence).toContain("token expired");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should not match unrelated errors", () => {
|
|
45
|
+
const result = classifyAuthFailure(new Error("File not found"));
|
|
46
|
+
expect(result.matched).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should match case-insensitive patterns", () => {
|
|
50
|
+
const result = classifyAuthFailure(new Error("UNAUTHORIZED: access denied"));
|
|
51
|
+
expect(result.matched).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should match string errors", () => {
|
|
55
|
+
const result = classifyAuthFailure("401 Unauthorized");
|
|
56
|
+
expect(result.matched).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("classifyToolFailure", () => {
|
|
61
|
+
it("should match 'command not found'", () => {
|
|
62
|
+
const result = classifyToolFailure(new Error("bash: git: command not found"));
|
|
63
|
+
expect(result.matched).toBe(true);
|
|
64
|
+
expect(result.evidence).toContain("command not found");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should match non-zero exit code", () => {
|
|
68
|
+
const result = classifyToolFailure(new Error("Process exited with code 1"));
|
|
69
|
+
expect(result.matched).toBe(true);
|
|
70
|
+
expect(result.evidence).toContain("exit code 1");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should match stderr patterns", () => {
|
|
74
|
+
const result = classifyToolFailure(
|
|
75
|
+
new Error("stderr: fatal: not a git repository"),
|
|
76
|
+
);
|
|
77
|
+
expect(result.matched).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should not match successful tool execution", () => {
|
|
81
|
+
const result = classifyToolFailure(new Error("Tool completed successfully"));
|
|
82
|
+
expect(result.matched).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should match 'no such file or directory' for tool paths", () => {
|
|
86
|
+
const result = classifyToolFailure(
|
|
87
|
+
new Error("spawn ENOENT: no such file or directory, git"),
|
|
88
|
+
);
|
|
89
|
+
expect(result.matched).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("classifyResourceFailure", () => {
|
|
94
|
+
it("should match disk full", () => {
|
|
95
|
+
const result = classifyResourceFailure(new Error("No space left on device"));
|
|
96
|
+
expect(result.matched).toBe(true);
|
|
97
|
+
expect(result.evidence).toContain("disk full");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should match OOM", () => {
|
|
101
|
+
const result = classifyResourceFailure(
|
|
102
|
+
new Error("JavaScript heap out of memory"),
|
|
103
|
+
);
|
|
104
|
+
expect(result.matched).toBe(true);
|
|
105
|
+
expect(result.evidence).toContain("out of memory");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should match rate limit exceeded", () => {
|
|
109
|
+
const result = classifyResourceFailure(
|
|
110
|
+
new Error("Rate limit exceeded: too many requests"),
|
|
111
|
+
);
|
|
112
|
+
expect(result.matched).toBe(true);
|
|
113
|
+
expect(result.evidence).toContain("rate limit");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should match memory allocation failure", () => {
|
|
117
|
+
const result = classifyResourceFailure(
|
|
118
|
+
new Error("Cannot allocate memory"),
|
|
119
|
+
);
|
|
120
|
+
expect(result.matched).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should not match unrelated errors", () => {
|
|
124
|
+
const result = classifyResourceFailure(new Error("Connection refused"));
|
|
125
|
+
expect(result.matched).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("classifySemanticFailure", () => {
|
|
130
|
+
it("should match assertion failed", () => {
|
|
131
|
+
const result = classifySemanticFailure(
|
|
132
|
+
new Error("Assertion failed: expected 5, got 3"),
|
|
133
|
+
);
|
|
134
|
+
expect(result.matched).toBe(true);
|
|
135
|
+
expect(result.evidence).toContain("assertion failed");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should match unexpected output", () => {
|
|
139
|
+
const result = classifySemanticFailure(
|
|
140
|
+
new Error("Unexpected output: received JSON instead of YAML"),
|
|
141
|
+
);
|
|
142
|
+
expect(result.matched).toBe(true);
|
|
143
|
+
expect(result.evidence).toContain("unexpected output");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should match schema mismatch", () => {
|
|
147
|
+
const result = classifySemanticFailure(
|
|
148
|
+
new Error("Schema validation error: missing required field 'id'"),
|
|
149
|
+
);
|
|
150
|
+
expect(result.matched).toBe(true);
|
|
151
|
+
expect(result.evidence).toContain("schema");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should match type mismatch", () => {
|
|
155
|
+
const result = classifySemanticFailure(
|
|
156
|
+
new Error("Type mismatch: expected string, got number"),
|
|
157
|
+
);
|
|
158
|
+
expect(result.matched).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should not match tool errors", () => {
|
|
162
|
+
const result = classifySemanticFailure(new Error("command not found"));
|
|
163
|
+
expect(result.matched).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("classifyHumanFailure", () => {
|
|
168
|
+
it("should match rejected", () => {
|
|
169
|
+
const result = classifyHumanFailure(
|
|
170
|
+
new Error("Human rejected the proposed changes"),
|
|
171
|
+
);
|
|
172
|
+
expect(result.matched).toBe(true);
|
|
173
|
+
expect(result.evidence).toContain("rejected");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("should match timeout waiting for human", () => {
|
|
177
|
+
const result = classifyHumanFailure(
|
|
178
|
+
new Error("Timed out waiting for human approval"),
|
|
179
|
+
);
|
|
180
|
+
expect(result.matched).toBe(true);
|
|
181
|
+
expect(result.evidence).toContain("human timeout");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should match no response", () => {
|
|
185
|
+
const result = classifyHumanFailure(
|
|
186
|
+
new Error("No response from human operator"),
|
|
187
|
+
);
|
|
188
|
+
expect(result.matched).toBe(true);
|
|
189
|
+
expect(result.evidence).toContain("no response");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should match declined", () => {
|
|
193
|
+
const result = classifyHumanFailure(new Error("Request declined by user"));
|
|
194
|
+
expect(result.matched).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should not match network timeout", () => {
|
|
198
|
+
const result = classifyHumanFailure(
|
|
199
|
+
new Error("Connection timed out after 30s"),
|
|
200
|
+
);
|
|
201
|
+
expect(result.matched).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe("classifyEnvironmentDriftFailure", () => {
|
|
206
|
+
it("should match version mismatch", () => {
|
|
207
|
+
const result = classifyEnvironmentDriftFailure(
|
|
208
|
+
new Error("Node.js version mismatch: expected 18, got 16"),
|
|
209
|
+
);
|
|
210
|
+
expect(result.matched).toBe(true);
|
|
211
|
+
expect(result.evidence).toContain("version mismatch");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("should match missing dependency", () => {
|
|
215
|
+
const result = classifyEnvironmentDriftFailure(
|
|
216
|
+
new Error("Cannot find module 'lodash'"),
|
|
217
|
+
);
|
|
218
|
+
expect(result.matched).toBe(true);
|
|
219
|
+
expect(result.evidence).toContain("missing dependency");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("should match config changed", () => {
|
|
223
|
+
const result = classifyEnvironmentDriftFailure(
|
|
224
|
+
new Error("Configuration changed: API_URL differs from expected"),
|
|
225
|
+
);
|
|
226
|
+
expect(result.matched).toBe(true);
|
|
227
|
+
expect(result.evidence).toContain("config changed");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should match incompatible version", () => {
|
|
231
|
+
const result = classifyEnvironmentDriftFailure(
|
|
232
|
+
new Error("Incompatible Python version: 3.9 required"),
|
|
233
|
+
);
|
|
234
|
+
expect(result.matched).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should not match runtime errors", () => {
|
|
238
|
+
const result = classifyEnvironmentDriftFailure(
|
|
239
|
+
new Error("Assertion failed"),
|
|
240
|
+
);
|
|
241
|
+
expect(result.matched).toBe(false);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe("classifyNetworkFailure", () => {
|
|
246
|
+
it("should match timeout", () => {
|
|
247
|
+
const result = classifyNetworkFailure(
|
|
248
|
+
new Error("Connection timed out after 30000ms"),
|
|
249
|
+
);
|
|
250
|
+
expect(result.matched).toBe(true);
|
|
251
|
+
expect(result.evidence).toContain("timeout");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("should match connection refused", () => {
|
|
255
|
+
const result = classifyNetworkFailure(
|
|
256
|
+
new Error("connect ECONNREFUSED 127.0.0.1:8080"),
|
|
257
|
+
);
|
|
258
|
+
expect(result.matched).toBe(true);
|
|
259
|
+
expect(result.evidence).toContain("connection refused");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should match DNS failure", () => {
|
|
263
|
+
const result = classifyNetworkFailure(
|
|
264
|
+
new Error("getaddrinfo ENOTFOUND api.example.com"),
|
|
265
|
+
);
|
|
266
|
+
expect(result.matched).toBe(true);
|
|
267
|
+
expect(result.evidence).toContain("DNS");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("should match network unreachable", () => {
|
|
271
|
+
const result = classifyNetworkFailure(
|
|
272
|
+
new Error("Network is unreachable"),
|
|
273
|
+
);
|
|
274
|
+
expect(result.matched).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("should match socket hang up", () => {
|
|
278
|
+
const result = classifyNetworkFailure(new Error("socket hang up"));
|
|
279
|
+
expect(result.matched).toBe(true);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("should not match auth errors", () => {
|
|
283
|
+
const result = classifyNetworkFailure(new Error("401 Unauthorized"));
|
|
284
|
+
expect(result.matched).toBe(false);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { generateFailureModes } from "../../src/failures/generator.js";
|
|
3
|
+
import type { EnvironmentModel } from "../../src/environment/types.js";
|
|
4
|
+
|
|
5
|
+
function makeEnv(overrides: Partial<EnvironmentModel> = {}): EnvironmentModel {
|
|
6
|
+
return {
|
|
7
|
+
tools: [],
|
|
8
|
+
resources: [],
|
|
9
|
+
constraints: [],
|
|
10
|
+
authState: [],
|
|
11
|
+
externalSystems: [],
|
|
12
|
+
discoveredAt: Date.now(),
|
|
13
|
+
...overrides,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("generateFailureModes", () => {
|
|
18
|
+
describe("keyword-based generation", () => {
|
|
19
|
+
it("should generate auth/network/config failure modes for 'deploy' tasks", () => {
|
|
20
|
+
const result = generateFailureModes("deploy application to production");
|
|
21
|
+
const classes = result.failureModes.map(f => f.failureClass);
|
|
22
|
+
|
|
23
|
+
expect(classes).toContain("auth");
|
|
24
|
+
expect(classes).toContain("network");
|
|
25
|
+
expect(result.failureModes.some(f => f.description.toLowerCase().includes("config"))).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should generate flaky/timeout/environment failure modes for 'test' tasks", () => {
|
|
29
|
+
const result = generateFailureModes("run integration tests");
|
|
30
|
+
const classes = result.failureModes.map(f => f.failureClass);
|
|
31
|
+
|
|
32
|
+
expect(classes).toContain("resource");
|
|
33
|
+
expect(result.failureModes.some(f => f.description.toLowerCase().includes("flaky") || f.description.toLowerCase().includes("timeout"))).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should generate dependency/disk/OOM failure modes for 'build' tasks", () => {
|
|
37
|
+
const result = generateFailureModes("build the project");
|
|
38
|
+
const classes = result.failureModes.map(f => f.failureClass);
|
|
39
|
+
|
|
40
|
+
expect(classes).toContain("resource");
|
|
41
|
+
expect(result.failureModes.some(f => f.description.toLowerCase().includes("dependency"))).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should generate conflict/verification failure modes for 'merge' tasks", () => {
|
|
45
|
+
const result = generateFailureModes("merge feature branch into main");
|
|
46
|
+
const classes = result.failureModes.map(f => f.failureClass);
|
|
47
|
+
|
|
48
|
+
expect(result.failureModes.some(f => f.description.toLowerCase().includes("conflict"))).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should generate connection/migration failure modes for 'database' tasks", () => {
|
|
52
|
+
const result = generateFailureModes("run database migrations");
|
|
53
|
+
const classes = result.failureModes.map(f => f.failureClass);
|
|
54
|
+
|
|
55
|
+
expect(classes).toContain("network");
|
|
56
|
+
expect(result.failureModes.some(f => f.description.toLowerCase().includes("migration") || f.description.toLowerCase().includes("database"))).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should generate rate-limit/auth/schema failure modes for 'api' tasks", () => {
|
|
60
|
+
const result = generateFailureModes("call external API");
|
|
61
|
+
const classes = result.failureModes.map(f => f.failureClass);
|
|
62
|
+
|
|
63
|
+
expect(classes).toContain("auth");
|
|
64
|
+
expect(result.failureModes.some(f => f.description.toLowerCase().includes("rate") || f.description.toLowerCase().includes("limit"))).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should generate permission/disk/path failure modes for 'file' tasks", () => {
|
|
68
|
+
const result = generateFailureModes("copy files to output directory");
|
|
69
|
+
const classes = result.failureModes.map(f => f.failureClass);
|
|
70
|
+
|
|
71
|
+
expect(result.failureModes.some(f => f.description.toLowerCase().includes("permission") || f.description.toLowerCase().includes("file"))).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("environment constraint integration", () => {
|
|
76
|
+
it("should add auth failure modes when auth constraint detected", () => {
|
|
77
|
+
const env = makeEnv({
|
|
78
|
+
constraints: [{ type: "auth", description: "GitHub token missing", severity: "high" }],
|
|
79
|
+
});
|
|
80
|
+
const result = generateFailureModes("push changes", env);
|
|
81
|
+
|
|
82
|
+
expect(result.failureModes.some(f => f.failureClass === "auth")).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should add network failure modes when network constraint detected", () => {
|
|
86
|
+
const env = makeEnv({
|
|
87
|
+
constraints: [{ type: "network", description: "No outbound access", severity: "high" }],
|
|
88
|
+
});
|
|
89
|
+
const result = generateFailureModes("fetch remote data", env);
|
|
90
|
+
|
|
91
|
+
expect(result.failureModes.some(f => f.failureClass === "network")).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should add resource failure modes when resource constraint detected", () => {
|
|
95
|
+
const env = makeEnv({
|
|
96
|
+
resources: [{ name: "disk", type: "disk", available: false }],
|
|
97
|
+
});
|
|
98
|
+
const result = generateFailureModes("compile project", env);
|
|
99
|
+
|
|
100
|
+
expect(result.failureModes.some(f => f.failureClass === "resource")).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should enhance probability when env constraint matches keyword pattern", () => {
|
|
104
|
+
const env = makeEnv({
|
|
105
|
+
constraints: [{ type: "auth", description: "Token expired", severity: "high" }],
|
|
106
|
+
});
|
|
107
|
+
const result = generateFailureModes("deploy to cloud", env);
|
|
108
|
+
|
|
109
|
+
const authModes = result.failureModes.filter(f => f.failureClass === "auth");
|
|
110
|
+
expect(authModes.some(f => f.probability === "high")).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("risk summary", () => {
|
|
115
|
+
it("should produce a human-readable risk summary", () => {
|
|
116
|
+
const result = generateFailureModes("deploy application");
|
|
117
|
+
|
|
118
|
+
expect(result.riskSummary).toBeTruthy();
|
|
119
|
+
expect(typeof result.riskSummary).toBe("string");
|
|
120
|
+
expect(result.riskSummary.length).toBeGreaterThan(0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should mention high-probability risks in summary", () => {
|
|
124
|
+
const result = generateFailureModes("deploy to production");
|
|
125
|
+
const highProb = result.failureModes.filter(f => f.probability === "high");
|
|
126
|
+
|
|
127
|
+
if (highProb.length > 0) {
|
|
128
|
+
expect(result.riskSummary.toLowerCase()).toContain("high");
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("recovery actions", () => {
|
|
134
|
+
it("should include actionable recovery actions for each failure mode", () => {
|
|
135
|
+
const result = generateFailureModes("deploy application");
|
|
136
|
+
|
|
137
|
+
for (const mode of result.failureModes) {
|
|
138
|
+
expect(mode.recoveryActions.length).toBeGreaterThan(0);
|
|
139
|
+
for (const action of mode.recoveryActions) {
|
|
140
|
+
expect(action.length).toBeGreaterThan(0);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should include triggers for each failure mode", () => {
|
|
146
|
+
const result = generateFailureModes("build project");
|
|
147
|
+
|
|
148
|
+
for (const mode of result.failureModes) {
|
|
149
|
+
expect(mode.triggers.length).toBeGreaterThan(0);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should include mitigations for each failure mode", () => {
|
|
154
|
+
const result = generateFailureModes("test suite");
|
|
155
|
+
|
|
156
|
+
for (const mode of result.failureModes) {
|
|
157
|
+
expect(mode.mitigations.length).toBeGreaterThan(0);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("edge cases", () => {
|
|
163
|
+
it("should return minimal failure modes for empty task description", () => {
|
|
164
|
+
const result = generateFailureModes("");
|
|
165
|
+
|
|
166
|
+
expect(result.failureModes.length).toBeGreaterThan(0);
|
|
167
|
+
expect(result.failureModes.some(f => f.failureClass === "unknown")).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should combine failure modes when multiple keywords present", () => {
|
|
171
|
+
const result = generateFailureModes("deploy and test the API integration");
|
|
172
|
+
const classes = new Set(result.failureModes.map(f => f.failureClass));
|
|
173
|
+
|
|
174
|
+
expect(classes.size).toBeGreaterThanOrEqual(3);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should deduplicate failure modes with same class and similar description", () => {
|
|
178
|
+
const result = generateFailureModes("deploy deploy deploy");
|
|
179
|
+
const descriptions = result.failureModes.map(f => f.description);
|
|
180
|
+
const unique = new Set(descriptions);
|
|
181
|
+
|
|
182
|
+
expect(unique.size).toBe(descriptions.length);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should set generatedAt to a timestamp", () => {
|
|
186
|
+
const result = generateFailureModes("build");
|
|
187
|
+
expect(result.generatedAt).toBeGreaterThan(0);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("should include the task description in the result", () => {
|
|
191
|
+
const result = generateFailureModes("deploy app");
|
|
192
|
+
expect(result.taskDescription).toBe("deploy app");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("should generate unique ids for each failure mode", () => {
|
|
196
|
+
const result = generateFailureModes("deploy and test API");
|
|
197
|
+
const ids = result.failureModes.map(f => f.id);
|
|
198
|
+
const unique = new Set(ids);
|
|
199
|
+
|
|
200
|
+
expect(unique.size).toBe(ids.length);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|