@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.
Files changed (124) hide show
  1. package/README.md +707 -0
  2. package/docs/agent-wrangling.png +0 -0
  3. package/package.json +26 -0
  4. package/src/capabilities/matcher.ts +25 -0
  5. package/src/capabilities/registry.ts +103 -0
  6. package/src/capabilities/types.ts +15 -0
  7. package/src/cir/lower.ts +253 -0
  8. package/src/cir/optimize.ts +251 -0
  9. package/src/cir/types.ts +131 -0
  10. package/src/cir/validate.ts +265 -0
  11. package/src/compiler/compile.ts +601 -0
  12. package/src/compiler/feedback.ts +471 -0
  13. package/src/compiler/runtime-helpers.ts +455 -0
  14. package/src/composition/chain.ts +58 -0
  15. package/src/composition/conditional.ts +76 -0
  16. package/src/composition/parallel.ts +75 -0
  17. package/src/composition/types.ts +105 -0
  18. package/src/environment/analyzer.ts +56 -0
  19. package/src/environment/discovery.ts +179 -0
  20. package/src/environment/types.ts +68 -0
  21. package/src/failures/classifiers.ts +134 -0
  22. package/src/failures/generator.ts +421 -0
  23. package/src/failures/map-reference-failures.ts +23 -0
  24. package/src/failures/ontology.ts +210 -0
  25. package/src/failures/recovery.ts +214 -0
  26. package/src/failures/types.ts +14 -0
  27. package/src/index.ts +67 -0
  28. package/src/memory/advisor.ts +132 -0
  29. package/src/memory/extractor.ts +166 -0
  30. package/src/memory/store.ts +107 -0
  31. package/src/memory/types.ts +53 -0
  32. package/src/metaharness/engine.ts +256 -0
  33. package/src/metaharness/predictor.ts +168 -0
  34. package/src/metaharness/types.ts +40 -0
  35. package/src/mutation/derive.ts +308 -0
  36. package/src/mutation/diff.ts +52 -0
  37. package/src/mutation/engine.ts +256 -0
  38. package/src/mutation/types.ts +84 -0
  39. package/src/pi/command-input.ts +209 -0
  40. package/src/pi/commands.ts +351 -0
  41. package/src/pi/extension.ts +16 -0
  42. package/src/planner/synthesize.ts +83 -0
  43. package/src/planner/template-rules.ts +183 -0
  44. package/src/planner/types.ts +42 -0
  45. package/src/reference/catalog.ts +128 -0
  46. package/src/reference/patch-validation-strategies.ts +170 -0
  47. package/src/reference/patch-validation.ts +174 -0
  48. package/src/reference/pr-review-merge.ts +155 -0
  49. package/src/reference/strategies.ts +126 -0
  50. package/src/reference/types.ts +33 -0
  51. package/src/replanner/risk-rules.ts +161 -0
  52. package/src/replanner/runtime.ts +308 -0
  53. package/src/replanner/synthesize.ts +619 -0
  54. package/src/replanner/types.ts +73 -0
  55. package/src/spec/schema.ts +254 -0
  56. package/src/spec/types.ts +319 -0
  57. package/src/spec/validate.ts +296 -0
  58. package/src/state/snapshots.ts +43 -0
  59. package/src/state/types.ts +12 -0
  60. package/src/synthesis/graph-builder.ts +267 -0
  61. package/src/synthesis/harness-builder.ts +113 -0
  62. package/src/synthesis/intent-ir.ts +63 -0
  63. package/src/synthesis/policy-builder.ts +320 -0
  64. package/src/synthesis/risk-analyzer.ts +182 -0
  65. package/src/synthesis/skill-parser.ts +441 -0
  66. package/src/verification/engine.ts +230 -0
  67. package/src/versioning/file-store.ts +103 -0
  68. package/src/versioning/history.ts +43 -0
  69. package/src/versioning/store.ts +16 -0
  70. package/src/versioning/types.ts +31 -0
  71. package/test/capabilities/matcher.test.ts +67 -0
  72. package/test/capabilities/registry.test.ts +136 -0
  73. package/test/capabilities/synthesis.test.ts +264 -0
  74. package/test/cir/lower.test.ts +417 -0
  75. package/test/cir/optimize.test.ts +266 -0
  76. package/test/cir/validate.test.ts +368 -0
  77. package/test/compiler/adaptive-runtime.test.ts +157 -0
  78. package/test/compiler/compile.test.ts +1198 -0
  79. package/test/compiler/feedback.test.ts +784 -0
  80. package/test/compiler/guardrails.test.ts +191 -0
  81. package/test/compiler/trace.test.ts +404 -0
  82. package/test/composition/chain.test.ts +328 -0
  83. package/test/composition/conditional.test.ts +241 -0
  84. package/test/composition/parallel.test.ts +215 -0
  85. package/test/environment/analyzer.test.ts +204 -0
  86. package/test/environment/discovery.test.ts +149 -0
  87. package/test/failures/classifiers.test.ts +287 -0
  88. package/test/failures/generator.test.ts +203 -0
  89. package/test/failures/ontology.test.ts +439 -0
  90. package/test/failures/recovery.test.ts +300 -0
  91. package/test/helpers/createFixtureRepo.ts +84 -0
  92. package/test/helpers/createPatchValidationFixture.ts +144 -0
  93. package/test/helpers/runCompiledWorkflow.ts +208 -0
  94. package/test/memory/advisor.test.ts +332 -0
  95. package/test/memory/extractor.test.ts +295 -0
  96. package/test/memory/store.test.ts +244 -0
  97. package/test/metaharness/engine.test.ts +575 -0
  98. package/test/metaharness/predictor.test.ts +436 -0
  99. package/test/mutation/derive-failure.test.ts +209 -0
  100. package/test/mutation/engine.test.ts +622 -0
  101. package/test/package-smoke.test.ts +29 -0
  102. package/test/pi/command-input.test.ts +153 -0
  103. package/test/pi/commands.test.ts +623 -0
  104. package/test/planner/classify-template.test.ts +32 -0
  105. package/test/planner/synthesize.test.ts +901 -0
  106. package/test/reference/PatchValidation.failures.test.ts +137 -0
  107. package/test/reference/PatchValidation.test.ts +326 -0
  108. package/test/reference/PrReviewMerge.failures.test.ts +121 -0
  109. package/test/reference/PrReviewMerge.test.ts +55 -0
  110. package/test/reference/catalog-open.test.ts +70 -0
  111. package/test/replanner/runtime.test.ts +207 -0
  112. package/test/replanner/synthesize.test.ts +303 -0
  113. package/test/spec/validate.test.ts +1056 -0
  114. package/test/state/snapshots.test.ts +264 -0
  115. package/test/synthesis/custom-workflow.test.ts +264 -0
  116. package/test/synthesis/graph-builder.test.ts +370 -0
  117. package/test/synthesis/harness-builder.test.ts +128 -0
  118. package/test/synthesis/policy-builder.test.ts +149 -0
  119. package/test/synthesis/risk-analyzer.test.ts +230 -0
  120. package/test/synthesis/skill-parser.test.ts +796 -0
  121. package/test/verification/engine.test.ts +509 -0
  122. package/test/versioning/history.test.ts +144 -0
  123. package/test/versioning/store.test.ts +254 -0
  124. package/vitest.config.ts +9 -0
@@ -0,0 +1,421 @@
1
+ import type { FailureClass } from "./ontology.js";
2
+ import type { EnvironmentModel } from "../environment/types.js";
3
+ import type { HarnessSpec } from "../spec/types.js";
4
+
5
+ export interface FailureMode {
6
+ id: string;
7
+ description: string;
8
+ failureClass: FailureClass;
9
+ probability: "low" | "medium" | "high";
10
+ triggers: string[];
11
+ mitigations: string[];
12
+ recoveryActions: string[];
13
+ }
14
+
15
+ export interface FailureModeGeneration {
16
+ taskDescription: string;
17
+ failureModes: FailureMode[];
18
+ generatedAt: number;
19
+ riskSummary: string;
20
+ }
21
+
22
+ interface PatternRule {
23
+ keywords: RegExp[];
24
+ failureClass: FailureClass;
25
+ description: string;
26
+ probability: "low" | "medium" | "high";
27
+ triggers: string[];
28
+ mitigations: string[];
29
+ recoveryActions: string[];
30
+ }
31
+
32
+ const PATTERN_RULES: PatternRule[] = [
33
+ {
34
+ keywords: [/deploy/i, /release/i, /publish/i],
35
+ failureClass: "auth",
36
+ description: "Authentication failure during deployment",
37
+ probability: "medium",
38
+ triggers: ["Expired deployment credentials", "Missing API token", "Insufficient permissions for target environment"],
39
+ mitigations: ["Verify credentials before deployment", "Use short-lived tokens with refresh"],
40
+ recoveryActions: ["Refresh deployment credentials", "Check token scopes and permissions"],
41
+ },
42
+ {
43
+ keywords: [/deploy/i, /release/i, /publish/i],
44
+ failureClass: "network",
45
+ description: "Network timeout during deployment",
46
+ probability: "medium",
47
+ triggers: ["Target service unreachable", "DNS resolution failure", "Firewall blocking outbound traffic"],
48
+ mitigations: ["Check connectivity before deployment", "Use retry with exponential backoff"],
49
+ recoveryActions: ["Retry deployment with backoff", "Verify target service health"],
50
+ },
51
+ {
52
+ keywords: [/deploy/i, /release/i, /publish/i],
53
+ failureClass: "environment-drift",
54
+ description: "Configuration drift in target environment",
55
+ probability: "medium",
56
+ triggers: ["Environment variables changed", "Infrastructure config differs from expected", "Secrets rotated"],
57
+ mitigations: ["Validate config before deployment", "Use infrastructure-as-code"],
58
+ recoveryActions: ["Sync configuration to expected state", "Verify environment variables"],
59
+ },
60
+ {
61
+ keywords: [/test/i, /spec/i, /check/i],
62
+ failureClass: "resource",
63
+ description: "Test timeout due to resource constraints",
64
+ probability: "medium",
65
+ triggers: ["Test exceeds time limit", "Insufficient memory for test runner", "CPU throttling"],
66
+ mitigations: ["Set appropriate test timeouts", "Allocate sufficient resources"],
67
+ recoveryActions: ["Increase timeout threshold", "Retry flaky tests with isolation"],
68
+ },
69
+ {
70
+ keywords: [/test/i, /spec/i, /check/i],
71
+ failureClass: "semantic",
72
+ description: "Flaky test due to non-deterministic behavior",
73
+ probability: "medium",
74
+ triggers: ["Race conditions in test setup", "Shared mutable state", "Order-dependent test failures"],
75
+ mitigations: ["Isolate test state", "Use deterministic fixtures"],
76
+ recoveryActions: ["Retry with test isolation", "Review test setup and teardown"],
77
+ },
78
+ {
79
+ keywords: [/test/i, /spec/i, /check/i],
80
+ failureClass: "environment-drift",
81
+ description: "Environment mismatch causing test failures",
82
+ probability: "low",
83
+ triggers: ["Missing test dependencies", "Different Node.js version", "OS-specific behavior"],
84
+ mitigations: ["Pin dependency versions", "Use containerized test environment"],
85
+ recoveryActions: ["Install missing dependencies", "Verify runtime version matches expected"],
86
+ },
87
+ {
88
+ keywords: [/build/i, /compile/i, /bundle/i],
89
+ failureClass: "environment-drift",
90
+ description: "Dependency resolution failure during build",
91
+ probability: "medium",
92
+ triggers: ["Package registry unavailable", "Version conflict in dependency tree", "Lockfile out of date"],
93
+ mitigations: ["Use lockfile for reproducible builds", "Cache dependencies locally"],
94
+ recoveryActions: ["Run dependency install with fresh cache", "Verify package registry availability"],
95
+ },
96
+ {
97
+ keywords: [/build/i, /compile/i, /bundle/i],
98
+ failureClass: "resource",
99
+ description: "Disk space exhausted during build",
100
+ probability: "low",
101
+ triggers: ["Build artifacts exceed available disk", "Cache directory grows unbounded", "Temporary files not cleaned"],
102
+ mitigations: ["Monitor disk usage", "Clean build artifacts regularly"],
103
+ recoveryActions: ["Free disk space and retry build", "Clear build cache"],
104
+ },
105
+ {
106
+ keywords: [/build/i, /compile/i, /bundle/i],
107
+ failureClass: "resource",
108
+ description: "Out of memory during build",
109
+ probability: "low",
110
+ triggers: ["Build process exceeds memory limit", "Large bundle size", "Memory leak in build tooling"],
111
+ mitigations: ["Increase Node.js heap size", "Optimize bundle splitting"],
112
+ recoveryActions: ["Increase memory limit (--max-old-space-size)", "Split build into smaller chunks"],
113
+ },
114
+ {
115
+ keywords: [/merge/i, /rebase/i, /integrate/i],
116
+ failureClass: "semantic",
117
+ description: "Merge conflict in source files",
118
+ probability: "high",
119
+ triggers: ["Concurrent changes to same files", "Divergent branch history", "Structural refactoring on both branches"],
120
+ mitigations: ["Rebase frequently", "Communicate changes to shared files"],
121
+ recoveryActions: ["Resolve conflicts manually", "Use merge tool for complex conflicts"],
122
+ },
123
+ {
124
+ keywords: [/merge/i, /rebase/i, /integrate/i],
125
+ failureClass: "semantic",
126
+ description: "Post-merge verification failure",
127
+ probability: "medium",
128
+ triggers: ["Combined changes break tests", "Integration incompatibility", "Semantic conflict"],
129
+ mitigations: ["Run full test suite before merge", "Use CI gates on PR"],
130
+ recoveryActions: ["Revert merge and investigate", "Fix integration issues"],
131
+ },
132
+ {
133
+ keywords: [/database/i, /migrat/i, /schema/i],
134
+ failureClass: "network",
135
+ description: "Database connection timeout",
136
+ probability: "medium",
137
+ triggers: ["Database server unreachable", "Connection pool exhausted", "DNS resolution failure for DB host"],
138
+ mitigations: ["Verify database connectivity", "Use connection pooling with timeouts"],
139
+ recoveryActions: ["Retry with exponential backoff", "Check database server health"],
140
+ },
141
+ {
142
+ keywords: [/database/i, /migrat/i, /schema/i],
143
+ failureClass: "semantic",
144
+ description: "Migration failure due to schema conflict",
145
+ probability: "medium",
146
+ triggers: ["Migration script has syntax error", "Existing data violates new constraints", "Migration already partially applied"],
147
+ mitigations: ["Test migrations on staging first", "Use idempotent migration scripts"],
148
+ recoveryActions: ["Review migration logs", "Rollback to previous schema version"],
149
+ },
150
+ {
151
+ keywords: [/database/i, /migrat/i, /schema/i],
152
+ failureClass: "resource",
153
+ description: "Data corruption risk during migration",
154
+ probability: "low",
155
+ triggers: ["Migration interrupted mid-way", "Insufficient disk for migration", "Concurrent writes during migration"],
156
+ mitigations: ["Take backup before migration", "Schedule maintenance window"],
157
+ recoveryActions: ["Restore from backup", "Run data integrity checks"],
158
+ },
159
+ {
160
+ keywords: [/api/i, /endpoint/i, /request/i, /fetch/i, /call/i],
161
+ failureClass: "resource",
162
+ description: "API rate limit exceeded",
163
+ probability: "medium",
164
+ triggers: ["Too many requests in time window", "Shared rate limit across instances", "Burst traffic pattern"],
165
+ mitigations: ["Implement rate limiting client-side", "Use exponential backoff"],
166
+ recoveryActions: ["Wait and retry with backoff", "Request rate limit increase"],
167
+ },
168
+ {
169
+ keywords: [/api/i, /endpoint/i, /request/i, /fetch/i, /call/i],
170
+ failureClass: "auth",
171
+ description: "API authentication token expired",
172
+ probability: "medium",
173
+ triggers: ["Token TTL exceeded", "Clock skew between client and server", "Token not refreshed before expiry"],
174
+ mitigations: ["Implement token refresh logic", "Use tokens with appropriate TTL"],
175
+ recoveryActions: ["Refresh authentication token", "Re-authenticate with API"],
176
+ },
177
+ {
178
+ keywords: [/api/i, /endpoint/i, /request/i, /fetch/i, /call/i],
179
+ failureClass: "semantic",
180
+ description: "API schema mismatch",
181
+ probability: "low",
182
+ triggers: ["API version changed", "Response format differs from expected", "Breaking change in API contract"],
183
+ mitigations: ["Pin API version", "Validate responses against schema"],
184
+ recoveryActions: ["Check API changelog for breaking changes", "Update client to match new schema"],
185
+ },
186
+ {
187
+ keywords: [/file/i, /copy/i, /write/i, /read/i, /path/i, /directory/i],
188
+ failureClass: "tool",
189
+ description: "File permission denied",
190
+ probability: "medium",
191
+ triggers: ["Insufficient file permissions", "File locked by another process", "Read-only filesystem"],
192
+ mitigations: ["Verify file permissions before operation", "Use appropriate user context"],
193
+ recoveryActions: ["Fix file permissions", "Run with elevated privileges if appropriate"],
194
+ },
195
+ {
196
+ keywords: [/file/i, /copy/i, /write/i, /read/i, /path/i, /directory/i],
197
+ failureClass: "resource",
198
+ description: "Disk full during file operation",
199
+ probability: "low",
200
+ triggers: ["No space left on device", "Large file exceeds available space", "Disk quota exceeded"],
201
+ mitigations: ["Check available disk space", "Clean up temporary files"],
202
+ recoveryActions: ["Free disk space and retry", "Use external storage if available"],
203
+ },
204
+ {
205
+ keywords: [/file/i, /copy/i, /write/i, /read/i, /path/i, /directory/i],
206
+ failureClass: "tool",
207
+ description: "File or path not found",
208
+ probability: "medium",
209
+ triggers: ["Path does not exist", "File was deleted or moved", "Incorrect relative path"],
210
+ mitigations: ["Verify paths before operations", "Use absolute paths when possible"],
211
+ recoveryActions: ["Check path exists", "Correct the file path"],
212
+ },
213
+ ];
214
+
215
+ let idCounter = 0;
216
+
217
+ function generateId(cls: FailureClass, index: number): string {
218
+ return `gen-${cls}-${index}-${Date.now().toString(36)}`;
219
+ }
220
+
221
+ function matchesKeywords(description: string, keywords: RegExp[]): boolean {
222
+ return keywords.some(kw => kw.test(description));
223
+ }
224
+
225
+ function deduplicate(modes: FailureMode[]): FailureMode[] {
226
+ const seen = new Map<string, FailureMode>();
227
+ for (const mode of modes) {
228
+ const key = `${mode.failureClass}:${mode.description}`;
229
+ if (!seen.has(key)) {
230
+ seen.set(key, mode);
231
+ }
232
+ }
233
+ return [...seen.values()];
234
+ }
235
+
236
+ function applyEnvironmentConstraints(
237
+ modes: FailureMode[],
238
+ env: EnvironmentModel,
239
+ ): FailureMode[] {
240
+ const enhanced = [...modes];
241
+ const constraintClasses = new Set<string>();
242
+
243
+ for (const constraint of env.constraints) {
244
+ if (constraint.type === "auth") {
245
+ constraintClasses.add("auth");
246
+ if (!enhanced.some(m => m.failureClass === "auth")) {
247
+ enhanced.push({
248
+ id: generateId("auth", enhanced.length),
249
+ description: `Auth constraint detected: ${constraint.description}`,
250
+ failureClass: "auth",
251
+ probability: constraint.severity === "high" ? "high" : "medium",
252
+ triggers: [constraint.description],
253
+ mitigations: ["Address auth constraint before execution"],
254
+ recoveryActions: ["Resolve authentication issue", "Verify credentials"],
255
+ });
256
+ }
257
+ }
258
+
259
+ if (constraint.type === "network") {
260
+ constraintClasses.add("network");
261
+ if (!enhanced.some(m => m.failureClass === "network")) {
262
+ enhanced.push({
263
+ id: generateId("network", enhanced.length),
264
+ description: `Network constraint detected: ${constraint.description}`,
265
+ failureClass: "network",
266
+ probability: constraint.severity === "high" ? "high" : "medium",
267
+ triggers: [constraint.description],
268
+ mitigations: ["Verify network connectivity before execution"],
269
+ recoveryActions: ["Check network access", "Retry with backoff"],
270
+ });
271
+ }
272
+ }
273
+
274
+ if (constraint.type === "rate-limit") {
275
+ if (!enhanced.some(m => m.description.toLowerCase().includes("rate"))) {
276
+ enhanced.push({
277
+ id: generateId("resource", enhanced.length),
278
+ description: `Rate limit constraint detected: ${constraint.description}`,
279
+ failureClass: "resource",
280
+ probability: constraint.severity === "high" ? "high" : "medium",
281
+ triggers: [constraint.description],
282
+ mitigations: ["Implement client-side rate limiting"],
283
+ recoveryActions: ["Wait and retry with backoff"],
284
+ });
285
+ }
286
+ }
287
+ }
288
+
289
+ for (const resource of env.resources) {
290
+ if (!resource.available) {
291
+ if (!enhanced.some(m => m.failureClass === "resource" && m.description.toLowerCase().includes(resource.type))) {
292
+ enhanced.push({
293
+ id: generateId("resource", enhanced.length),
294
+ description: `Resource unavailable: ${resource.name} (${resource.type})`,
295
+ failureClass: "resource",
296
+ probability: "high",
297
+ triggers: [`${resource.type} resource is not available`],
298
+ mitigations: [`Provision ${resource.name} before execution`],
299
+ recoveryActions: [`Free up ${resource.type} resource`, "Retry after resource cleanup"],
300
+ });
301
+ }
302
+ }
303
+ }
304
+
305
+ for (const auth of env.authState) {
306
+ if (!auth.authenticated) {
307
+ if (!enhanced.some(m => m.failureClass === "auth" && m.description.toLowerCase().includes(auth.system.toLowerCase()))) {
308
+ enhanced.push({
309
+ id: generateId("auth", enhanced.length),
310
+ description: `Not authenticated with ${auth.system}`,
311
+ failureClass: "auth",
312
+ probability: "high",
313
+ triggers: [`${auth.system} authentication missing or expired`],
314
+ mitigations: [`Authenticate with ${auth.system} before execution`],
315
+ recoveryActions: [`Authenticate with ${auth.system}`, "Check credentials"],
316
+ });
317
+ }
318
+ }
319
+ }
320
+
321
+ // Boost probability when env constraints match existing modes
322
+ for (const mode of enhanced) {
323
+ if (mode.probability !== "high") {
324
+ if (mode.failureClass === "auth" && (env.constraints.some(c => c.type === "auth") || env.authState.some(a => !a.authenticated))) {
325
+ mode.probability = "high";
326
+ }
327
+ if (mode.failureClass === "network" && env.constraints.some(c => c.type === "network")) {
328
+ mode.probability = "high";
329
+ }
330
+ if (mode.failureClass === "resource" && env.resources.some(r => !r.available)) {
331
+ mode.probability = "high";
332
+ }
333
+ }
334
+ }
335
+
336
+ return enhanced;
337
+ }
338
+
339
+ function buildRiskSummary(modes: FailureMode[]): string {
340
+ if (modes.length === 0) {
341
+ return "No significant risks identified.";
342
+ }
343
+
344
+ const high = modes.filter(m => m.probability === "high");
345
+ const medium = modes.filter(m => m.probability === "medium");
346
+ const low = modes.filter(m => m.probability === "low");
347
+
348
+ const parts: string[] = [];
349
+
350
+ if (high.length > 0) {
351
+ const classes = [...new Set(high.map(m => m.failureClass))];
352
+ parts.push(`${high.length} high-probability risk(s) in ${classes.join(", ")}`);
353
+ }
354
+ if (medium.length > 0) {
355
+ parts.push(`${medium.length} medium-probability risk(s)`);
356
+ }
357
+ if (low.length > 0) {
358
+ parts.push(`${low.length} low-probability risk(s)`);
359
+ }
360
+
361
+ const classCounts = new Map<FailureClass, number>();
362
+ for (const mode of modes) {
363
+ classCounts.set(mode.failureClass, (classCounts.get(mode.failureClass) ?? 0) + 1);
364
+ }
365
+ const topClasses = [...classCounts.entries()]
366
+ .sort((a, b) => b[1] - a[1])
367
+ .slice(0, 3)
368
+ .map(([cls, count]) => `${cls}(${count})`);
369
+
370
+ return `${parts.join("; ")}. Top categories: ${topClasses.join(", ")}.`;
371
+ }
372
+
373
+ export function generateFailureModes(
374
+ taskDescription: string,
375
+ env?: EnvironmentModel,
376
+ spec?: HarnessSpec,
377
+ ): FailureModeGeneration {
378
+ const matchedModes: FailureMode[] = [];
379
+
380
+ for (const rule of PATTERN_RULES) {
381
+ if (matchesKeywords(taskDescription, rule.keywords)) {
382
+ matchedModes.push({
383
+ id: generateId(rule.failureClass, matchedModes.length),
384
+ description: rule.description,
385
+ failureClass: rule.failureClass,
386
+ probability: rule.probability,
387
+ triggers: [...rule.triggers],
388
+ mitigations: [...rule.mitigations],
389
+ recoveryActions: [...rule.recoveryActions],
390
+ });
391
+ }
392
+ }
393
+
394
+ // For empty/unknown descriptions, add a baseline unknown mode
395
+ if (matchedModes.length === 0) {
396
+ matchedModes.push({
397
+ id: generateId("unknown", 0),
398
+ description: "Unrecognized task pattern — general execution risk",
399
+ failureClass: "unknown",
400
+ probability: "low",
401
+ triggers: ["Task description does not match known patterns"],
402
+ mitigations: ["Review task requirements carefully"],
403
+ recoveryActions: ["Collect diagnostic information", "Escalate if unresolved"],
404
+ });
405
+ }
406
+
407
+ let failureModes = deduplicate(matchedModes);
408
+
409
+ if (env) {
410
+ failureModes = applyEnvironmentConstraints(failureModes, env);
411
+ }
412
+
413
+ const riskSummary = buildRiskSummary(failureModes);
414
+
415
+ return {
416
+ taskDescription,
417
+ failureModes,
418
+ generatedAt: Date.now(),
419
+ riskSummary,
420
+ };
421
+ }
@@ -0,0 +1,23 @@
1
+ import type { FailureRecord } from "./types.js";
2
+
3
+ const WORKFLOW_FAILURE_MAPPING: Record<string, FailureRecord["rootCause"]> = {
4
+ "apply-failed": "dependency_failure",
5
+ "candidate-failed": "invalid_output",
6
+ "reject-human": "human_block",
7
+ };
8
+
9
+ export function mapReferenceFailure(
10
+ domainType: string,
11
+ workflowFailureType: string,
12
+ nodeId: string,
13
+ message: string,
14
+ ): FailureRecord {
15
+ const rootCause = WORKFLOW_FAILURE_MAPPING[workflowFailureType] ?? "unknown";
16
+
17
+ return {
18
+ domainType,
19
+ rootCause,
20
+ nodeId,
21
+ message,
22
+ };
23
+ }
@@ -0,0 +1,210 @@
1
+ import type { FailureRecord } from "./types.js";
2
+ import {
3
+ classifyAuthFailure,
4
+ classifyToolFailure,
5
+ classifyResourceFailure,
6
+ classifySemanticFailure,
7
+ classifyHumanFailure,
8
+ classifyEnvironmentDriftFailure,
9
+ classifyNetworkFailure,
10
+ } from "./classifiers.js";
11
+
12
+ export interface FailureClassification {
13
+ category: "transient" | "permanent";
14
+ retryable: boolean;
15
+ }
16
+
17
+ export function classifyFailureRecord(failure: FailureRecord): FailureClassification {
18
+ switch (failure.rootCause) {
19
+ case "tool_timeout":
20
+ case "rate_limited":
21
+ case "unknown":
22
+ return { category: "transient", retryable: true };
23
+
24
+ case "auth_required":
25
+ case "invalid_output":
26
+ case "dependency_failure":
27
+ case "verification_failed":
28
+ case "human_block":
29
+ return { category: "permanent", retryable: false };
30
+
31
+ default:
32
+ return { category: "transient", retryable: true };
33
+ }
34
+ }
35
+
36
+ export function isRetryableFailure(failure: FailureRecord): boolean {
37
+ return classifyFailureRecord(failure).retryable;
38
+ }
39
+
40
+ export type FailureClass =
41
+ | "auth"
42
+ | "tool"
43
+ | "resource"
44
+ | "semantic"
45
+ | "human"
46
+ | "environment-drift"
47
+ | "network"
48
+ | "unknown";
49
+
50
+ export interface FailureContext {
51
+ nodeId?: string;
52
+ attemptNumber?: number;
53
+ harnessName?: string;
54
+ }
55
+
56
+ export interface FailureSignature {
57
+ class: FailureClass;
58
+ confidence: number;
59
+ evidence: string[];
60
+ suggestedRecovery: string[];
61
+ retryable: boolean;
62
+ requiresHumanIntervention: boolean;
63
+ }
64
+
65
+ const CLASS_RECOVERY_MAP: Record<FailureClass, string[]> = {
66
+ auth: [
67
+ "Refresh or re-obtain authentication credentials",
68
+ "Check token expiry and renew if necessary",
69
+ "Verify API keys or secrets are correctly configured",
70
+ ],
71
+ tool: [
72
+ "Verify tool is installed and available in PATH",
73
+ "Check tool version compatibility",
74
+ "Review tool exit code and stderr for details",
75
+ ],
76
+ resource: [
77
+ "Check available disk space and clean up if necessary",
78
+ "Review memory usage and increase limits if needed",
79
+ "Implement rate limiting or backoff for API calls",
80
+ ],
81
+ semantic: [
82
+ "Review output format and validate against expected schema",
83
+ "Check input data for correctness",
84
+ "Add validation step before processing",
85
+ ],
86
+ human: [
87
+ "Escalate to human operator for review",
88
+ "Reconsider the proposed changes",
89
+ "Provide additional context for human decision",
90
+ ],
91
+ "environment-drift": [
92
+ "Sync environment dependencies to expected versions",
93
+ "Install missing dependencies",
94
+ "Verify configuration matches expected state",
95
+ ],
96
+ network: [
97
+ "Retry the operation with exponential backoff",
98
+ "Check network connectivity and DNS resolution",
99
+ "Verify target service is reachable",
100
+ ],
101
+ unknown: [
102
+ "Collect additional diagnostic information",
103
+ "Review logs for root cause analysis",
104
+ "Escalate to human operator if unresolved",
105
+ ],
106
+ };
107
+
108
+ const CLASS_RETRYABLE_MAP: Record<FailureClass, boolean> = {
109
+ auth: false,
110
+ tool: false,
111
+ resource: true,
112
+ semantic: false,
113
+ human: false,
114
+ "environment-drift": false,
115
+ network: true,
116
+ unknown: false,
117
+ };
118
+
119
+ const CLASS_REQUIRES_HUMAN_MAP: Record<FailureClass, boolean> = {
120
+ auth: true,
121
+ tool: false,
122
+ resource: false,
123
+ semantic: true,
124
+ human: true,
125
+ "environment-drift": true,
126
+ network: false,
127
+ unknown: true,
128
+ };
129
+
130
+ const CLASS_CONFIDENCE_BASE: Record<FailureClass, number> = {
131
+ auth: 0.9,
132
+ tool: 0.85,
133
+ resource: 0.9,
134
+ semantic: 0.85,
135
+ human: 0.85,
136
+ "environment-drift": 0.8,
137
+ network: 0.9,
138
+ unknown: 0.1,
139
+ };
140
+
141
+ export function classifyFailure(
142
+ error: unknown,
143
+ context?: FailureContext,
144
+ ): FailureSignature {
145
+ const authResult = classifyAuthFailure(error);
146
+ if (authResult.matched) {
147
+ return buildSignature("auth", authResult, context);
148
+ }
149
+
150
+ const toolResult = classifyToolFailure(error);
151
+ if (toolResult.matched) {
152
+ return buildSignature("tool", toolResult, context);
153
+ }
154
+
155
+ const resourceResult = classifyResourceFailure(error);
156
+ if (resourceResult.matched) {
157
+ return buildSignature("resource", resourceResult, context);
158
+ }
159
+
160
+ const semanticResult = classifySemanticFailure(error);
161
+ if (semanticResult.matched) {
162
+ return buildSignature("semantic", semanticResult, context);
163
+ }
164
+
165
+ const humanResult = classifyHumanFailure(error);
166
+ if (humanResult.matched) {
167
+ return buildSignature("human", humanResult, context);
168
+ }
169
+
170
+ const envDriftResult = classifyEnvironmentDriftFailure(error);
171
+ if (envDriftResult.matched) {
172
+ return buildSignature("environment-drift", envDriftResult, context);
173
+ }
174
+
175
+ const networkResult = classifyNetworkFailure(error);
176
+ if (networkResult.matched) {
177
+ return buildSignature("network", networkResult, context);
178
+ }
179
+
180
+ return buildSignature("unknown", { matched: false, evidence: [] }, context);
181
+ }
182
+
183
+ function buildSignature(
184
+ cls: FailureClass,
185
+ classifierResult: { matched: boolean; evidence: string[] },
186
+ context?: FailureContext,
187
+ ): FailureSignature {
188
+ const evidence = [...classifierResult.evidence];
189
+
190
+ if (context?.nodeId) {
191
+ evidence.push(`node: ${context.nodeId}`);
192
+ }
193
+
194
+ if (context?.attemptNumber !== undefined) {
195
+ evidence.push(`attempt: ${context.attemptNumber}`);
196
+ }
197
+
198
+ const confidence = classifierResult.matched
199
+ ? CLASS_CONFIDENCE_BASE[cls]
200
+ : CLASS_CONFIDENCE_BASE.unknown;
201
+
202
+ return {
203
+ class: cls,
204
+ confidence,
205
+ evidence,
206
+ suggestedRecovery: CLASS_RECOVERY_MAP[cls],
207
+ retryable: CLASS_RETRYABLE_MAP[cls],
208
+ requiresHumanIntervention: CLASS_REQUIRES_HUMAN_MAP[cls],
209
+ };
210
+ }