agentic-qe 2.5.6 → 2.5.8

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 (210) hide show
  1. package/.claude/agents/n8n/n8n-base-agent.md +376 -0
  2. package/.claude/agents/n8n/n8n-bdd-scenario-tester.md +613 -0
  3. package/.claude/agents/n8n/n8n-chaos-tester.md +654 -0
  4. package/.claude/agents/n8n/n8n-ci-orchestrator.md +850 -0
  5. package/.claude/agents/n8n/n8n-compliance-validator.md +685 -0
  6. package/.claude/agents/n8n/n8n-expression-validator.md +560 -0
  7. package/.claude/agents/n8n/n8n-integration-test.md +602 -0
  8. package/.claude/agents/n8n/n8n-monitoring-validator.md +589 -0
  9. package/.claude/agents/n8n/n8n-node-validator.md +455 -0
  10. package/.claude/agents/n8n/n8n-performance-tester.md +630 -0
  11. package/.claude/agents/n8n/n8n-security-auditor.md +786 -0
  12. package/.claude/agents/n8n/n8n-trigger-test.md +500 -0
  13. package/.claude/agents/n8n/n8n-unit-tester.md +633 -0
  14. package/.claude/agents/n8n/n8n-version-comparator.md +567 -0
  15. package/.claude/agents/n8n/n8n-workflow-executor.md +392 -0
  16. package/.claude/skills/n8n-expression-testing/SKILL.md +434 -0
  17. package/.claude/skills/n8n-integration-testing-patterns/SKILL.md +540 -0
  18. package/.claude/skills/n8n-security-testing/SKILL.md +599 -0
  19. package/.claude/skills/n8n-trigger-testing-strategies/SKILL.md +541 -0
  20. package/.claude/skills/n8n-workflow-testing-fundamentals/SKILL.md +447 -0
  21. package/CHANGELOG.md +127 -0
  22. package/README.md +7 -4
  23. package/dist/agents/BaseAgent.d.ts +142 -0
  24. package/dist/agents/BaseAgent.d.ts.map +1 -1
  25. package/dist/agents/BaseAgent.js +372 -2
  26. package/dist/agents/BaseAgent.js.map +1 -1
  27. package/dist/agents/TestGeneratorAgent.d.ts +5 -0
  28. package/dist/agents/TestGeneratorAgent.d.ts.map +1 -1
  29. package/dist/agents/TestGeneratorAgent.js +38 -0
  30. package/dist/agents/TestGeneratorAgent.js.map +1 -1
  31. package/dist/agents/index.d.ts +1 -1
  32. package/dist/agents/index.d.ts.map +1 -1
  33. package/dist/agents/index.js.map +1 -1
  34. package/dist/agents/n8n/N8nAPIClient.d.ts +121 -0
  35. package/dist/agents/n8n/N8nAPIClient.d.ts.map +1 -0
  36. package/dist/agents/n8n/N8nAPIClient.js +367 -0
  37. package/dist/agents/n8n/N8nAPIClient.js.map +1 -0
  38. package/dist/agents/n8n/N8nAuditPersistence.d.ts +120 -0
  39. package/dist/agents/n8n/N8nAuditPersistence.d.ts.map +1 -0
  40. package/dist/agents/n8n/N8nAuditPersistence.js +473 -0
  41. package/dist/agents/n8n/N8nAuditPersistence.js.map +1 -0
  42. package/dist/agents/n8n/N8nBDDScenarioTesterAgent.d.ts +159 -0
  43. package/dist/agents/n8n/N8nBDDScenarioTesterAgent.d.ts.map +1 -0
  44. package/dist/agents/n8n/N8nBDDScenarioTesterAgent.js +697 -0
  45. package/dist/agents/n8n/N8nBDDScenarioTesterAgent.js.map +1 -0
  46. package/dist/agents/n8n/N8nBaseAgent.d.ts +126 -0
  47. package/dist/agents/n8n/N8nBaseAgent.d.ts.map +1 -0
  48. package/dist/agents/n8n/N8nBaseAgent.js +446 -0
  49. package/dist/agents/n8n/N8nBaseAgent.js.map +1 -0
  50. package/dist/agents/n8n/N8nCIOrchestratorAgent.d.ts +164 -0
  51. package/dist/agents/n8n/N8nCIOrchestratorAgent.d.ts.map +1 -0
  52. package/dist/agents/n8n/N8nCIOrchestratorAgent.js +610 -0
  53. package/dist/agents/n8n/N8nCIOrchestratorAgent.js.map +1 -0
  54. package/dist/agents/n8n/N8nChaosTesterAgent.d.ts +205 -0
  55. package/dist/agents/n8n/N8nChaosTesterAgent.d.ts.map +1 -0
  56. package/dist/agents/n8n/N8nChaosTesterAgent.js +729 -0
  57. package/dist/agents/n8n/N8nChaosTesterAgent.js.map +1 -0
  58. package/dist/agents/n8n/N8nComplianceValidatorAgent.d.ts +228 -0
  59. package/dist/agents/n8n/N8nComplianceValidatorAgent.d.ts.map +1 -0
  60. package/dist/agents/n8n/N8nComplianceValidatorAgent.js +986 -0
  61. package/dist/agents/n8n/N8nComplianceValidatorAgent.js.map +1 -0
  62. package/dist/agents/n8n/N8nContractTesterAgent.d.ts +213 -0
  63. package/dist/agents/n8n/N8nContractTesterAgent.d.ts.map +1 -0
  64. package/dist/agents/n8n/N8nContractTesterAgent.js +989 -0
  65. package/dist/agents/n8n/N8nContractTesterAgent.js.map +1 -0
  66. package/dist/agents/n8n/N8nExpressionValidatorAgent.d.ts +99 -0
  67. package/dist/agents/n8n/N8nExpressionValidatorAgent.d.ts.map +1 -0
  68. package/dist/agents/n8n/N8nExpressionValidatorAgent.js +632 -0
  69. package/dist/agents/n8n/N8nExpressionValidatorAgent.js.map +1 -0
  70. package/dist/agents/n8n/N8nFailureModeTesterAgent.d.ts +238 -0
  71. package/dist/agents/n8n/N8nFailureModeTesterAgent.d.ts.map +1 -0
  72. package/dist/agents/n8n/N8nFailureModeTesterAgent.js +956 -0
  73. package/dist/agents/n8n/N8nFailureModeTesterAgent.js.map +1 -0
  74. package/dist/agents/n8n/N8nIdempotencyTesterAgent.d.ts +242 -0
  75. package/dist/agents/n8n/N8nIdempotencyTesterAgent.d.ts.map +1 -0
  76. package/dist/agents/n8n/N8nIdempotencyTesterAgent.js +992 -0
  77. package/dist/agents/n8n/N8nIdempotencyTesterAgent.js.map +1 -0
  78. package/dist/agents/n8n/N8nIntegrationTestAgent.d.ts +104 -0
  79. package/dist/agents/n8n/N8nIntegrationTestAgent.d.ts.map +1 -0
  80. package/dist/agents/n8n/N8nIntegrationTestAgent.js +653 -0
  81. package/dist/agents/n8n/N8nIntegrationTestAgent.js.map +1 -0
  82. package/dist/agents/n8n/N8nMonitoringValidatorAgent.d.ts +210 -0
  83. package/dist/agents/n8n/N8nMonitoringValidatorAgent.d.ts.map +1 -0
  84. package/dist/agents/n8n/N8nMonitoringValidatorAgent.js +669 -0
  85. package/dist/agents/n8n/N8nMonitoringValidatorAgent.js.map +1 -0
  86. package/dist/agents/n8n/N8nNodeValidatorAgent.d.ts +142 -0
  87. package/dist/agents/n8n/N8nNodeValidatorAgent.d.ts.map +1 -0
  88. package/dist/agents/n8n/N8nNodeValidatorAgent.js +1090 -0
  89. package/dist/agents/n8n/N8nNodeValidatorAgent.js.map +1 -0
  90. package/dist/agents/n8n/N8nPerformanceTesterAgent.d.ts +198 -0
  91. package/dist/agents/n8n/N8nPerformanceTesterAgent.d.ts.map +1 -0
  92. package/dist/agents/n8n/N8nPerformanceTesterAgent.js +653 -0
  93. package/dist/agents/n8n/N8nPerformanceTesterAgent.js.map +1 -0
  94. package/dist/agents/n8n/N8nReplayabilityTesterAgent.d.ts +245 -0
  95. package/dist/agents/n8n/N8nReplayabilityTesterAgent.d.ts.map +1 -0
  96. package/dist/agents/n8n/N8nReplayabilityTesterAgent.js +952 -0
  97. package/dist/agents/n8n/N8nReplayabilityTesterAgent.js.map +1 -0
  98. package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.d.ts +325 -0
  99. package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.d.ts.map +1 -0
  100. package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.js +1187 -0
  101. package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.js.map +1 -0
  102. package/dist/agents/n8n/N8nSecurityAuditorAgent.d.ts +91 -0
  103. package/dist/agents/n8n/N8nSecurityAuditorAgent.d.ts.map +1 -0
  104. package/dist/agents/n8n/N8nSecurityAuditorAgent.js +825 -0
  105. package/dist/agents/n8n/N8nSecurityAuditorAgent.js.map +1 -0
  106. package/dist/agents/n8n/N8nTestHarness.d.ts +131 -0
  107. package/dist/agents/n8n/N8nTestHarness.d.ts.map +1 -0
  108. package/dist/agents/n8n/N8nTestHarness.js +456 -0
  109. package/dist/agents/n8n/N8nTestHarness.js.map +1 -0
  110. package/dist/agents/n8n/N8nTriggerTestAgent.d.ts +119 -0
  111. package/dist/agents/n8n/N8nTriggerTestAgent.d.ts.map +1 -0
  112. package/dist/agents/n8n/N8nTriggerTestAgent.js +652 -0
  113. package/dist/agents/n8n/N8nTriggerTestAgent.js.map +1 -0
  114. package/dist/agents/n8n/N8nUnitTesterAgent.d.ts +130 -0
  115. package/dist/agents/n8n/N8nUnitTesterAgent.d.ts.map +1 -0
  116. package/dist/agents/n8n/N8nUnitTesterAgent.js +522 -0
  117. package/dist/agents/n8n/N8nUnitTesterAgent.js.map +1 -0
  118. package/dist/agents/n8n/N8nVersionComparatorAgent.d.ts +201 -0
  119. package/dist/agents/n8n/N8nVersionComparatorAgent.d.ts.map +1 -0
  120. package/dist/agents/n8n/N8nVersionComparatorAgent.js +645 -0
  121. package/dist/agents/n8n/N8nVersionComparatorAgent.js.map +1 -0
  122. package/dist/agents/n8n/N8nWorkflowExecutorAgent.d.ts +120 -0
  123. package/dist/agents/n8n/N8nWorkflowExecutorAgent.d.ts.map +1 -0
  124. package/dist/agents/n8n/N8nWorkflowExecutorAgent.js +347 -0
  125. package/dist/agents/n8n/N8nWorkflowExecutorAgent.js.map +1 -0
  126. package/dist/agents/n8n/index.d.ts +119 -0
  127. package/dist/agents/n8n/index.d.ts.map +1 -0
  128. package/dist/agents/n8n/index.js +298 -0
  129. package/dist/agents/n8n/index.js.map +1 -0
  130. package/dist/agents/n8n/types.d.ts +486 -0
  131. package/dist/agents/n8n/types.d.ts.map +1 -0
  132. package/dist/agents/n8n/types.js +8 -0
  133. package/dist/agents/n8n/types.js.map +1 -0
  134. package/dist/cli/init/agents.d.ts.map +1 -1
  135. package/dist/cli/init/agents.js +29 -0
  136. package/dist/cli/init/agents.js.map +1 -1
  137. package/dist/cli/init/skills.d.ts.map +1 -1
  138. package/dist/cli/init/skills.js +7 -1
  139. package/dist/cli/init/skills.js.map +1 -1
  140. package/dist/core/memory/HNSWVectorMemory.js +1 -1
  141. package/dist/core/memory/RuVectorPatternStore.d.ts +90 -0
  142. package/dist/core/memory/RuVectorPatternStore.d.ts.map +1 -1
  143. package/dist/core/memory/RuVectorPatternStore.js +209 -0
  144. package/dist/core/memory/RuVectorPatternStore.js.map +1 -1
  145. package/dist/learning/FederatedManager.d.ts +232 -0
  146. package/dist/learning/FederatedManager.d.ts.map +1 -0
  147. package/dist/learning/FederatedManager.js +489 -0
  148. package/dist/learning/FederatedManager.js.map +1 -0
  149. package/dist/learning/HNSWPatternAdapter.d.ts +117 -0
  150. package/dist/learning/HNSWPatternAdapter.d.ts.map +1 -0
  151. package/dist/learning/HNSWPatternAdapter.js +262 -0
  152. package/dist/learning/HNSWPatternAdapter.js.map +1 -0
  153. package/dist/learning/LearningEngine.d.ts +27 -0
  154. package/dist/learning/LearningEngine.d.ts.map +1 -1
  155. package/dist/learning/LearningEngine.js +75 -1
  156. package/dist/learning/LearningEngine.js.map +1 -1
  157. package/dist/learning/PatternCurator.d.ts +217 -0
  158. package/dist/learning/PatternCurator.d.ts.map +1 -0
  159. package/dist/learning/PatternCurator.js +393 -0
  160. package/dist/learning/PatternCurator.js.map +1 -0
  161. package/dist/learning/index.d.ts +6 -0
  162. package/dist/learning/index.d.ts.map +1 -1
  163. package/dist/learning/index.js +16 -1
  164. package/dist/learning/index.js.map +1 -1
  165. package/dist/learning/types.d.ts +4 -0
  166. package/dist/learning/types.d.ts.map +1 -1
  167. package/dist/mcp/server-instructions.d.ts +1 -1
  168. package/dist/mcp/server-instructions.js +1 -1
  169. package/dist/memory/HNSWPatternStore.d.ts +176 -0
  170. package/dist/memory/HNSWPatternStore.d.ts.map +1 -0
  171. package/dist/memory/HNSWPatternStore.js +392 -0
  172. package/dist/memory/HNSWPatternStore.js.map +1 -0
  173. package/dist/memory/index.d.ts +8 -0
  174. package/dist/memory/index.d.ts.map +1 -0
  175. package/dist/memory/index.js +13 -0
  176. package/dist/memory/index.js.map +1 -0
  177. package/dist/providers/HybridRouter.d.ts +85 -4
  178. package/dist/providers/HybridRouter.d.ts.map +1 -1
  179. package/dist/providers/HybridRouter.js +332 -10
  180. package/dist/providers/HybridRouter.js.map +1 -1
  181. package/dist/providers/LLMBaselineTracker.d.ts +120 -0
  182. package/dist/providers/LLMBaselineTracker.d.ts.map +1 -0
  183. package/dist/providers/LLMBaselineTracker.js +305 -0
  184. package/dist/providers/LLMBaselineTracker.js.map +1 -0
  185. package/dist/providers/OpenRouterProvider.d.ts +26 -0
  186. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  187. package/dist/providers/OpenRouterProvider.js +75 -6
  188. package/dist/providers/OpenRouterProvider.js.map +1 -1
  189. package/dist/providers/RuVectorClient.d.ts +259 -0
  190. package/dist/providers/RuVectorClient.d.ts.map +1 -0
  191. package/dist/providers/RuVectorClient.js +416 -0
  192. package/dist/providers/RuVectorClient.js.map +1 -0
  193. package/dist/providers/RuvllmPatternCurator.d.ts +116 -0
  194. package/dist/providers/RuvllmPatternCurator.d.ts.map +1 -0
  195. package/dist/providers/RuvllmPatternCurator.js +323 -0
  196. package/dist/providers/RuvllmPatternCurator.js.map +1 -0
  197. package/dist/providers/RuvllmProvider.d.ts +233 -1
  198. package/dist/providers/RuvllmProvider.d.ts.map +1 -1
  199. package/dist/providers/RuvllmProvider.js +781 -11
  200. package/dist/providers/RuvllmProvider.js.map +1 -1
  201. package/dist/providers/index.d.ts +5 -1
  202. package/dist/providers/index.d.ts.map +1 -1
  203. package/dist/providers/index.js +12 -2
  204. package/dist/providers/index.js.map +1 -1
  205. package/dist/utils/ruvllm-loader.d.ts +98 -1
  206. package/dist/utils/ruvllm-loader.d.ts.map +1 -1
  207. package/dist/utils/ruvllm-loader.js.map +1 -1
  208. package/docs/reference/agents.md +91 -2
  209. package/docs/reference/skills.md +97 -2
  210. package/package.json +2 -2
@@ -0,0 +1,956 @@
1
+ "use strict";
2
+ /**
3
+ * N8nFailureModeTesterAgent
4
+ *
5
+ * Error handling and retry semantics testing for n8n workflows:
6
+ * - Retry/backoff behavior validation
7
+ * - Partial failure handling in loops
8
+ * - Error branch testing
9
+ * - "Continue on fail" behavior
10
+ * - Dead-letter patterns
11
+ * - Timeout handling
12
+ * - Error propagation analysis
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.N8nFailureModeTesterAgent = void 0;
16
+ const N8nBaseAgent_1 = require("./N8nBaseAgent");
17
+ const N8nTestHarness_1 = require("./N8nTestHarness");
18
+ // ============================================================================
19
+ // Error Handling Patterns
20
+ // ============================================================================
21
+ const ERROR_PRONE_NODE_TYPES = [
22
+ 'n8n-nodes-base.httpRequest',
23
+ 'n8n-nodes-base.webhook',
24
+ 'n8n-nodes-base.executeCommand',
25
+ 'n8n-nodes-base.function',
26
+ 'n8n-nodes-base.code',
27
+ 'n8n-nodes-base.postgres',
28
+ 'n8n-nodes-base.mysql',
29
+ 'n8n-nodes-base.mongodb',
30
+ 'n8n-nodes-base.redis',
31
+ 'n8n-nodes-base.ftp',
32
+ 'n8n-nodes-base.ssh',
33
+ 'n8n-nodes-base.awsLambda',
34
+ ];
35
+ const LOOP_NODE_TYPES = [
36
+ 'n8n-nodes-base.splitInBatches',
37
+ 'n8n-nodes-base.loop',
38
+ ];
39
+ // ============================================================================
40
+ // Agent Implementation
41
+ // ============================================================================
42
+ class N8nFailureModeTesterAgent extends N8nBaseAgent_1.N8nBaseAgent {
43
+ constructor(config) {
44
+ const capabilities = [
45
+ {
46
+ name: 'retry-analysis',
47
+ version: '1.0.0',
48
+ description: 'Analyze retry configurations and backoff behavior',
49
+ parameters: {},
50
+ },
51
+ {
52
+ name: 'error-branch-testing',
53
+ version: '1.0.0',
54
+ description: 'Test error branch handling',
55
+ parameters: {},
56
+ },
57
+ {
58
+ name: 'continue-on-fail-analysis',
59
+ version: '1.0.0',
60
+ description: 'Analyze continue-on-fail behavior and risks',
61
+ parameters: {},
62
+ },
63
+ {
64
+ name: 'fault-injection',
65
+ version: '1.0.0',
66
+ description: 'Inject faults to test error handling',
67
+ parameters: {},
68
+ },
69
+ ];
70
+ super({
71
+ ...config,
72
+ type: 'n8n-failure-mode-tester',
73
+ capabilities: [...capabilities, ...(config.capabilities || [])],
74
+ });
75
+ }
76
+ async performTask(task) {
77
+ const failureTask = task;
78
+ if (failureTask.type !== 'failure-mode-test') {
79
+ throw new Error(`Unsupported task type: ${failureTask.type}`);
80
+ }
81
+ return this.testFailureModes(failureTask.target, failureTask.options);
82
+ }
83
+ /**
84
+ * Test failure modes in workflow
85
+ */
86
+ async testFailureModes(workflowId, options, providedWorkflow) {
87
+ const workflow = providedWorkflow || await this.getWorkflow(workflowId);
88
+ const findings = [];
89
+ const recommendations = [];
90
+ // 1. Analyze retry configurations
91
+ const retryAnalysis = this.analyzeRetryConfigurations(workflow);
92
+ findings.push(...this.retryConfigToFindings(retryAnalysis));
93
+ // 2. Analyze error branches
94
+ const errorBranchAnalysis = this.analyzeErrorBranches(workflow);
95
+ findings.push(...this.errorBranchToFindings(errorBranchAnalysis, workflow));
96
+ // 3. Analyze continue-on-fail settings
97
+ const continueOnFailAnalysis = this.analyzeContinueOnFail(workflow);
98
+ findings.push(...this.continueOnFailToFindings(continueOnFailAnalysis));
99
+ // 4. Check for partial failure risks in loops
100
+ if (options?.testPartialFailures !== false) {
101
+ findings.push(...this.checkPartialFailureRisks(workflow));
102
+ }
103
+ // 5. Check for timeout risks
104
+ if (options?.testTimeouts !== false) {
105
+ findings.push(...this.checkTimeoutRisks(workflow));
106
+ }
107
+ // 6. Check for dead-letter patterns
108
+ findings.push(...this.checkDeadLetterPatterns(workflow));
109
+ // 7. Analyze DLQ patterns (enhanced)
110
+ const dlqAnalysis = this.analyzeDLQPatterns(workflow);
111
+ // 8. Execute fault injection tests if requested and not dry run
112
+ let faultInjectionResults;
113
+ if (options?.injectFaults && !options.dryRun) {
114
+ faultInjectionResults = await this.executeFaultInjectionTests(workflowId, options.injectFaults, workflow);
115
+ // Add findings from fault injection results
116
+ for (const faultResult of faultInjectionResults) {
117
+ if (!faultResult.errorHandled && faultResult.executed) {
118
+ findings.push({
119
+ type: 'missing-error-handler',
120
+ severity: 'high',
121
+ node: faultResult.fault.targetNode,
122
+ message: `Fault injection test failed: ${faultResult.fault.faultType} was not handled`,
123
+ details: faultResult.details,
124
+ suggestion: 'Add error handling (retry, error branch, or continue-on-fail) for this node',
125
+ });
126
+ }
127
+ if (faultResult.dataIntegrity === 'corrupted') {
128
+ findings.push({
129
+ type: 'cascade-failure',
130
+ severity: 'critical',
131
+ node: faultResult.fault.targetNode,
132
+ message: `Data corruption detected after ${faultResult.fault.faultType} fault`,
133
+ details: `Error propagated to: ${faultResult.errorPropagation.join(' -> ')}`,
134
+ suggestion: 'Add data validation and rollback mechanisms',
135
+ });
136
+ }
137
+ }
138
+ }
139
+ // Generate recommendations
140
+ recommendations.push(...this.generateRecommendations(findings, retryAnalysis, errorBranchAnalysis, continueOnFailAnalysis));
141
+ recommendations.push(...dlqAnalysis.recommendations);
142
+ // Calculate scores
143
+ const errorHandlingScore = this.calculateErrorHandlingScore(errorBranchAnalysis, retryAnalysis);
144
+ const resilienceScore = this.calculateResilienceScore(findings);
145
+ const score = Math.round((errorHandlingScore + resilienceScore) / 2);
146
+ const result = {
147
+ workflowId: workflow.id || workflowId,
148
+ workflowName: workflow.name,
149
+ testDate: new Date().toISOString(),
150
+ passed: findings.filter(f => f.severity === 'critical' || f.severity === 'high').length === 0,
151
+ score,
152
+ errorHandlingScore,
153
+ resilienceScore,
154
+ findings,
155
+ retryAnalysis,
156
+ errorBranchAnalysis,
157
+ continueOnFailAnalysis,
158
+ recommendations,
159
+ faultInjectionResults,
160
+ dlqAnalysis,
161
+ };
162
+ // Store result
163
+ await this.storeTestResult(`failure-mode-test:${workflowId}`, result);
164
+ // Emit event
165
+ this.emitEvent('failure-mode.test.completed', {
166
+ workflowId,
167
+ passed: result.passed,
168
+ errorHandlingScore,
169
+ resilienceScore,
170
+ findingCount: findings.length,
171
+ });
172
+ return result;
173
+ }
174
+ /**
175
+ * Analyze retry configurations
176
+ */
177
+ analyzeRetryConfigurations(workflow) {
178
+ const nodesWithRetry = [];
179
+ const nodesWithoutRetry = [];
180
+ const retryConfigurations = [];
181
+ for (const node of workflow.nodes) {
182
+ const isErrorProne = ERROR_PRONE_NODE_TYPES.some(t => node.type.includes(t));
183
+ const settings = node.parameters;
184
+ // Check for retry settings
185
+ const hasRetry = settings.options && typeof settings.options === 'object' &&
186
+ ('retry' in settings.options || 'retryOnFail' in settings.options);
187
+ if (hasRetry) {
188
+ nodesWithRetry.push(node.name);
189
+ const retryOptions = (settings.options || {});
190
+ const retrySubOptions = (retryOptions.retry || {});
191
+ const issues = [];
192
+ // Analyze retry configuration
193
+ const maxRetries = ((retryOptions.maxTries ?? retrySubOptions.maxTries) || 3);
194
+ const waitBetween = ((retryOptions.waitBetweenTries ?? retrySubOptions.waitBetweenTries) || 1000);
195
+ if (maxRetries > 10) {
196
+ issues.push('Excessive retry count may cause long execution times');
197
+ }
198
+ if (maxRetries < 2 && isErrorProne) {
199
+ issues.push('Low retry count for error-prone node type');
200
+ }
201
+ if (waitBetween < 500) {
202
+ issues.push('Short wait between retries may trigger rate limits');
203
+ }
204
+ retryConfigurations.push({
205
+ node: node.name,
206
+ maxRetries,
207
+ backoffType: 'fixed', // n8n uses fixed by default
208
+ waitBetween,
209
+ issues,
210
+ });
211
+ }
212
+ else if (isErrorProne) {
213
+ nodesWithoutRetry.push(node.name);
214
+ }
215
+ }
216
+ const errorProneNodes = workflow.nodes.filter(n => ERROR_PRONE_NODE_TYPES.some(t => n.type.includes(t))).length;
217
+ const overallScore = errorProneNodes > 0
218
+ ? Math.round((nodesWithRetry.length / errorProneNodes) * 100)
219
+ : 100;
220
+ return {
221
+ nodesWithRetry,
222
+ nodesWithoutRetry,
223
+ retryConfigurations,
224
+ overallScore,
225
+ };
226
+ }
227
+ /**
228
+ * Analyze error branches
229
+ */
230
+ analyzeErrorBranches(workflow) {
231
+ const nodesWithErrorBranch = [];
232
+ const nodesWithoutErrorBranch = [];
233
+ const errorHandlers = [];
234
+ const orphanedErrorBranches = [];
235
+ for (const node of workflow.nodes) {
236
+ const isErrorProne = ERROR_PRONE_NODE_TYPES.some(t => node.type.includes(t));
237
+ // Check if node has error output connection
238
+ const connections = workflow.connections[node.name];
239
+ const hasErrorBranch = connections?.main && connections.main.length > 1;
240
+ if (hasErrorBranch) {
241
+ nodesWithErrorBranch.push(node.name);
242
+ // Check if error branch leads somewhere
243
+ const errorOutputConnections = connections.main[1];
244
+ if (!errorOutputConnections || errorOutputConnections.length === 0) {
245
+ orphanedErrorBranches.push(node.name);
246
+ }
247
+ else {
248
+ for (const conn of errorOutputConnections) {
249
+ errorHandlers.push({
250
+ triggerNode: node.name,
251
+ handlerNode: conn.node,
252
+ handlesAllErrors: true, // Would need runtime analysis to determine
253
+ });
254
+ }
255
+ }
256
+ }
257
+ else if (isErrorProne) {
258
+ nodesWithoutErrorBranch.push(node.name);
259
+ }
260
+ }
261
+ const errorProneNodes = workflow.nodes.filter(n => ERROR_PRONE_NODE_TYPES.some(t => n.type.includes(t))).length;
262
+ const errorBranchCoverage = errorProneNodes > 0
263
+ ? Math.round((nodesWithErrorBranch.length / errorProneNodes) * 100)
264
+ : 100;
265
+ return {
266
+ nodesWithErrorBranch,
267
+ nodesWithoutErrorBranch,
268
+ errorBranchCoverage,
269
+ orphanedErrorBranches,
270
+ errorHandlers,
271
+ };
272
+ }
273
+ /**
274
+ * Analyze continue-on-fail settings
275
+ */
276
+ analyzeContinueOnFail(workflow) {
277
+ const nodesWithContinueOnFail = [];
278
+ const silentFailureRisks = [];
279
+ const dataIntegrityRisks = [];
280
+ for (const node of workflow.nodes) {
281
+ const settings = node.parameters;
282
+ const hasContinueOnFail = settings.continueOnFail === true ||
283
+ (settings.options && settings.options.continueOnFail === true);
284
+ if (hasContinueOnFail) {
285
+ nodesWithContinueOnFail.push(node.name);
286
+ // Check if there's error handling after this node
287
+ const connections = workflow.connections[node.name];
288
+ const hasErrorHandling = connections?.main && connections.main.length > 1;
289
+ if (!hasErrorHandling) {
290
+ silentFailureRisks.push(node.name);
291
+ }
292
+ // Find downstream nodes that might receive bad data
293
+ const downstream = this.getDownstreamNodes(workflow, node.name);
294
+ if (downstream.length > 0) {
295
+ dataIntegrityRisks.push({
296
+ node: node.name,
297
+ risk: 'Continue on fail may pass incomplete/bad data downstream',
298
+ affectedDownstream: downstream.map(n => n.name),
299
+ });
300
+ }
301
+ }
302
+ }
303
+ return {
304
+ nodesWithContinueOnFail,
305
+ silentFailureRisks,
306
+ dataIntegrityRisks,
307
+ };
308
+ }
309
+ /**
310
+ * Check for partial failure risks in loops
311
+ */
312
+ checkPartialFailureRisks(workflow) {
313
+ const findings = [];
314
+ // Find loop nodes
315
+ const loopNodes = workflow.nodes.filter(n => LOOP_NODE_TYPES.some(t => n.type.includes(t)));
316
+ for (const loopNode of loopNodes) {
317
+ // Check if nodes inside loop have error handling
318
+ const downstream = this.getDownstreamNodes(workflow, loopNode.name);
319
+ const errorProneInLoop = downstream.filter(n => ERROR_PRONE_NODE_TYPES.some(t => n.type.includes(t)));
320
+ for (const errorProneNode of errorProneInLoop) {
321
+ const connections = workflow.connections[errorProneNode.name];
322
+ const hasErrorBranch = connections?.main && connections.main.length > 1;
323
+ if (!hasErrorBranch) {
324
+ findings.push({
325
+ type: 'partial-failure-risk',
326
+ severity: 'high',
327
+ node: errorProneNode.name,
328
+ message: `Error-prone node in loop without error handling`,
329
+ details: `Node "${errorProneNode.name}" is inside loop "${loopNode.name}" but has no error branch. A single item failure could stop the entire loop.`,
330
+ suggestion: 'Add error handling or use "continue on fail" with error logging',
331
+ });
332
+ }
333
+ }
334
+ }
335
+ return findings;
336
+ }
337
+ /**
338
+ * Check for timeout risks
339
+ */
340
+ checkTimeoutRisks(workflow) {
341
+ const findings = [];
342
+ for (const node of workflow.nodes) {
343
+ // Check HTTP nodes for timeout settings
344
+ if (node.type.includes('httpRequest')) {
345
+ const settings = node.parameters;
346
+ const options = settings.options;
347
+ const timeout = options?.timeout;
348
+ if (!timeout) {
349
+ findings.push({
350
+ type: 'timeout-risk',
351
+ severity: 'medium',
352
+ node: node.name,
353
+ message: 'HTTP request without explicit timeout',
354
+ details: 'Missing timeout may cause workflow to hang indefinitely',
355
+ suggestion: 'Set explicit timeout in node options',
356
+ });
357
+ }
358
+ else if (timeout > 300000) { // 5 minutes
359
+ findings.push({
360
+ type: 'timeout-risk',
361
+ severity: 'low',
362
+ node: node.name,
363
+ message: 'HTTP request with very long timeout',
364
+ details: `Timeout set to ${timeout / 1000}s which may cause long hangs`,
365
+ suggestion: 'Consider shorter timeout with retry logic',
366
+ });
367
+ }
368
+ }
369
+ // Check execute command nodes
370
+ if (node.type.includes('executeCommand')) {
371
+ findings.push({
372
+ type: 'timeout-risk',
373
+ severity: 'medium',
374
+ node: node.name,
375
+ message: 'Execute command without timeout control',
376
+ details: 'Shell commands can hang indefinitely',
377
+ suggestion: 'Add timeout to command or use n8n timeout settings',
378
+ });
379
+ }
380
+ }
381
+ return findings;
382
+ }
383
+ /**
384
+ * Check for dead-letter patterns
385
+ */
386
+ checkDeadLetterPatterns(workflow) {
387
+ const findings = [];
388
+ // Check if workflow has any error notification/logging at the end
389
+ const hasErrorWorkflow = workflow.settings?.errorWorkflow;
390
+ const hasErrorNotification = workflow.nodes.some(n => n.type.includes('email') || n.type.includes('slack') || n.type.includes('telegram'));
391
+ // Check for nodes that send to DLQ or error queue
392
+ const hasDLQPattern = workflow.nodes.some(n => {
393
+ const params = JSON.stringify(n.parameters).toLowerCase();
394
+ return params.includes('dlq') || params.includes('dead') || params.includes('error-queue');
395
+ });
396
+ if (!hasErrorWorkflow && !hasErrorNotification && !hasDLQPattern) {
397
+ findings.push({
398
+ type: 'dlq-missing',
399
+ severity: 'medium',
400
+ node: 'workflow',
401
+ message: 'No dead-letter or error notification pattern detected',
402
+ details: 'Failed executions may go unnoticed without error notifications',
403
+ suggestion: 'Add error workflow in workflow settings or add notification node for failures',
404
+ });
405
+ }
406
+ return findings;
407
+ }
408
+ /**
409
+ * Execute fault injection tests
410
+ */
411
+ async executeFaultInjection(workflow, faults) {
412
+ const findings = [];
413
+ // Note: Actual fault injection would require modifying workflow or using n8n's testing features
414
+ // This is a placeholder for the concept
415
+ for (const fault of faults) {
416
+ const targetNode = workflow.nodes.find(n => n.name === fault.targetNode);
417
+ if (!targetNode) {
418
+ findings.push({
419
+ type: 'missing-error-handler',
420
+ severity: 'low',
421
+ node: fault.targetNode,
422
+ message: `Fault injection target "${fault.targetNode}" not found`,
423
+ details: 'Cannot inject fault to non-existent node',
424
+ suggestion: 'Check node name spelling',
425
+ });
426
+ continue;
427
+ }
428
+ // Check if node has error handling for the injected fault type
429
+ const connections = workflow.connections[fault.targetNode];
430
+ const hasErrorBranch = connections?.main && connections.main.length > 1;
431
+ if (!hasErrorBranch) {
432
+ findings.push({
433
+ type: 'missing-error-handler',
434
+ severity: 'high',
435
+ node: fault.targetNode,
436
+ message: `No error handler for ${fault.faultType} fault`,
437
+ details: `If ${fault.faultType} occurs, the workflow will fail without graceful handling`,
438
+ suggestion: 'Add error branch to handle this failure mode',
439
+ });
440
+ }
441
+ }
442
+ return findings;
443
+ }
444
+ /**
445
+ * Convert retry analysis to findings
446
+ */
447
+ retryConfigToFindings(analysis) {
448
+ const findings = [];
449
+ for (const node of analysis.nodesWithoutRetry) {
450
+ findings.push({
451
+ type: 'retry-misconfiguration',
452
+ severity: 'medium',
453
+ node,
454
+ message: 'Error-prone node without retry configuration',
455
+ details: 'Transient failures will cause immediate workflow failure',
456
+ suggestion: 'Add retry settings in node options',
457
+ });
458
+ }
459
+ for (const config of analysis.retryConfigurations) {
460
+ for (const issue of config.issues) {
461
+ findings.push({
462
+ type: 'retry-misconfiguration',
463
+ severity: 'low',
464
+ node: config.node,
465
+ message: issue,
466
+ details: `Current retry config: ${config.maxRetries} retries, ${config.waitBetween}ms wait`,
467
+ suggestion: 'Adjust retry settings based on expected failure modes',
468
+ });
469
+ }
470
+ }
471
+ return findings;
472
+ }
473
+ /**
474
+ * Convert error branch analysis to findings
475
+ */
476
+ errorBranchToFindings(analysis, workflow) {
477
+ const findings = [];
478
+ for (const node of analysis.nodesWithoutErrorBranch) {
479
+ findings.push({
480
+ type: 'missing-error-handler',
481
+ severity: 'medium',
482
+ node,
483
+ message: 'Error-prone node without error branch',
484
+ details: 'Errors will propagate to workflow level without graceful handling',
485
+ suggestion: 'Add error branch to handle failures gracefully',
486
+ });
487
+ }
488
+ for (const orphan of analysis.orphanedErrorBranches) {
489
+ findings.push({
490
+ type: 'missing-error-handler',
491
+ severity: 'low',
492
+ node: orphan,
493
+ message: 'Error branch exists but is not connected',
494
+ details: 'Error output has no target node',
495
+ suggestion: 'Connect error branch to an error handler node',
496
+ });
497
+ }
498
+ return findings;
499
+ }
500
+ /**
501
+ * Convert continue-on-fail analysis to findings
502
+ */
503
+ continueOnFailToFindings(analysis) {
504
+ const findings = [];
505
+ for (const node of analysis.silentFailureRisks) {
506
+ findings.push({
507
+ type: 'silent-failure',
508
+ severity: 'high',
509
+ node,
510
+ message: 'Continue on fail without error tracking',
511
+ details: 'Failures will be silently ignored without logging or notification',
512
+ suggestion: 'Add error logging/notification when using continue on fail',
513
+ });
514
+ }
515
+ for (const risk of analysis.dataIntegrityRisks) {
516
+ findings.push({
517
+ type: 'cascade-failure',
518
+ severity: 'medium',
519
+ node: risk.node,
520
+ message: risk.risk,
521
+ details: `May affect: ${risk.affectedDownstream.join(', ')}`,
522
+ suggestion: 'Add data validation after nodes with continue on fail',
523
+ });
524
+ }
525
+ return findings;
526
+ }
527
+ /**
528
+ * Calculate error handling score
529
+ */
530
+ calculateErrorHandlingScore(errorBranchAnalysis, retryAnalysis) {
531
+ const errorBranchScore = errorBranchAnalysis.errorBranchCoverage;
532
+ const retryScore = retryAnalysis.overallScore;
533
+ return Math.round((errorBranchScore + retryScore) / 2);
534
+ }
535
+ /**
536
+ * Calculate resilience score
537
+ */
538
+ calculateResilienceScore(findings) {
539
+ const criticalCount = findings.filter(f => f.severity === 'critical').length;
540
+ const highCount = findings.filter(f => f.severity === 'high').length;
541
+ const mediumCount = findings.filter(f => f.severity === 'medium').length;
542
+ return Math.max(0, 100 - (criticalCount * 25) - (highCount * 15) - (mediumCount * 5));
543
+ }
544
+ /**
545
+ * Generate recommendations
546
+ */
547
+ generateRecommendations(findings, retryAnalysis, errorBranchAnalysis, continueOnFailAnalysis) {
548
+ const recommendations = [];
549
+ if (retryAnalysis.nodesWithoutRetry.length > 0) {
550
+ recommendations.push(`Add retry configuration to ${retryAnalysis.nodesWithoutRetry.length} error-prone nodes`);
551
+ }
552
+ if (errorBranchAnalysis.errorBranchCoverage < 50) {
553
+ recommendations.push('Error branch coverage is low - add error handling to critical nodes');
554
+ }
555
+ if (continueOnFailAnalysis.silentFailureRisks.length > 0) {
556
+ recommendations.push('Add error logging when using "continue on fail" to avoid silent failures');
557
+ }
558
+ const dlqFindings = findings.filter(f => f.type === 'dlq-missing');
559
+ if (dlqFindings.length > 0) {
560
+ recommendations.push('Consider adding error workflow or notification for failed executions');
561
+ }
562
+ return recommendations;
563
+ }
564
+ // ============================================================================
565
+ // Active Fault Injection Testing
566
+ // ============================================================================
567
+ /**
568
+ * Execute fault injection tests against a live workflow
569
+ * This actually runs the workflow with injected faults to verify error handling
570
+ */
571
+ async executeFaultInjectionTests(workflowId, faults, providedWorkflow) {
572
+ const results = [];
573
+ const workflow = providedWorkflow || await this.getWorkflow(workflowId);
574
+ // If no faults specified, auto-generate faults for error-prone nodes
575
+ const faultsToTest = faults || this.generateDefaultFaults(workflow);
576
+ if (faultsToTest.length === 0) {
577
+ return results;
578
+ }
579
+ // Create test harness
580
+ const harness = new N8nTestHarness_1.N8nTestHarness(this.n8nConfig);
581
+ try {
582
+ for (const fault of faultsToTest) {
583
+ const result = await this.executeSingleFaultTest(harness, workflowId, fault, workflow);
584
+ results.push(result);
585
+ }
586
+ }
587
+ finally {
588
+ await harness.cleanup();
589
+ }
590
+ return results;
591
+ }
592
+ /**
593
+ * Generate default faults for error-prone nodes
594
+ */
595
+ generateDefaultFaults(workflow) {
596
+ const faults = [];
597
+ for (const node of workflow.nodes) {
598
+ const isErrorProne = ERROR_PRONE_NODE_TYPES.some(t => node.type.includes(t));
599
+ if (!isErrorProne)
600
+ continue;
601
+ // Add error fault
602
+ faults.push({
603
+ targetNode: node.name,
604
+ faultType: 'error',
605
+ errorMessage: `Simulated error in ${node.name}`,
606
+ });
607
+ // Add timeout fault for HTTP nodes
608
+ if (node.type.includes('httpRequest')) {
609
+ faults.push({
610
+ targetNode: node.name,
611
+ faultType: 'timeout',
612
+ delay: 5000,
613
+ });
614
+ }
615
+ // Add empty response fault
616
+ faults.push({
617
+ targetNode: node.name,
618
+ faultType: 'empty-response',
619
+ });
620
+ }
621
+ return faults;
622
+ }
623
+ /**
624
+ * Execute a single fault injection test
625
+ */
626
+ async executeSingleFaultTest(harness, workflowId, fault, workflow) {
627
+ const startTime = Date.now();
628
+ // Convert to harness format
629
+ const faultConfig = {
630
+ targetNode: fault.targetNode,
631
+ faultType: fault.faultType === 'rate-limit' ? 'rate-limit' : fault.faultType,
632
+ probability: fault.probability,
633
+ delay: fault.delay,
634
+ errorMessage: fault.errorMessage,
635
+ };
636
+ try {
637
+ const testResult = await harness.executeWithFaults(workflowId, [faultConfig]);
638
+ // Analyze the result
639
+ return this.analyzeFaultTestResult(fault, testResult, workflow, Date.now() - startTime);
640
+ }
641
+ catch (error) {
642
+ return {
643
+ fault,
644
+ executed: false,
645
+ errorHandled: false,
646
+ retryAttempts: 0,
647
+ finalStatus: 'crashed',
648
+ executionTime: Date.now() - startTime,
649
+ errorPropagation: [],
650
+ dataIntegrity: 'corrupted',
651
+ details: `Failed to execute fault test: ${error.message}`,
652
+ };
653
+ }
654
+ }
655
+ /**
656
+ * Analyze the result of a fault injection test
657
+ */
658
+ analyzeFaultTestResult(fault, testResult, workflow, executionTime) {
659
+ const execution = testResult.execution;
660
+ if (!execution) {
661
+ return {
662
+ fault,
663
+ executed: true,
664
+ errorHandled: false,
665
+ retryAttempts: 0,
666
+ finalStatus: testResult.error?.includes('timeout') ? 'timeout' : 'crashed',
667
+ executionTime,
668
+ errorPropagation: [],
669
+ dataIntegrity: 'corrupted',
670
+ details: testResult.error || 'Execution failed with no result',
671
+ };
672
+ }
673
+ // Analyze execution data
674
+ const runData = execution.data?.resultData?.runData || {};
675
+ const errorNodes = this.findErrorNodes(runData);
676
+ const retryAttempts = this.countRetryAttempts(runData, fault.targetNode);
677
+ // Check if error was handled
678
+ const hasErrorBranch = this.checkErrorBranchUsed(workflow, fault.targetNode, runData);
679
+ const hasContinueOnFail = this.checkContinueOnFailUsed(workflow, fault.targetNode, runData);
680
+ const errorHandled = hasErrorBranch || hasContinueOnFail || retryAttempts > 0;
681
+ // Determine final status
682
+ let finalStatus = 'crashed';
683
+ if (execution.finished && !execution.data?.resultData?.error) {
684
+ finalStatus = errorHandled ? 'recovered' : 'failed-gracefully';
685
+ }
686
+ else if (errorHandled) {
687
+ finalStatus = 'failed-gracefully';
688
+ }
689
+ // Check data integrity
690
+ const dataIntegrity = this.assessDataIntegrity(workflow, runData, fault.targetNode);
691
+ // Track error propagation
692
+ const errorPropagation = this.traceErrorPropagation(workflow, fault.targetNode, runData);
693
+ return {
694
+ fault,
695
+ executed: true,
696
+ errorHandled,
697
+ retryAttempts,
698
+ finalStatus,
699
+ executionTime,
700
+ errorPropagation,
701
+ dataIntegrity,
702
+ details: this.generateFaultTestDetails(fault, errorHandled, retryAttempts, finalStatus),
703
+ };
704
+ }
705
+ /**
706
+ * Find nodes that had errors in execution
707
+ */
708
+ findErrorNodes(runData) {
709
+ const errorNodes = [];
710
+ for (const [nodeName, runs] of Object.entries(runData)) {
711
+ const nodeRuns = runs;
712
+ if (nodeRuns.some(run => run.error)) {
713
+ errorNodes.push(nodeName);
714
+ }
715
+ }
716
+ return errorNodes;
717
+ }
718
+ /**
719
+ * Count retry attempts for a node
720
+ */
721
+ countRetryAttempts(runData, nodeName) {
722
+ const nodeRuns = runData[nodeName];
723
+ if (!nodeRuns)
724
+ return 0;
725
+ // Multiple runs of the same node indicate retries
726
+ return Math.max(0, nodeRuns.length - 1);
727
+ }
728
+ /**
729
+ * Check if error branch was used
730
+ */
731
+ checkErrorBranchUsed(workflow, faultNode, runData) {
732
+ const connections = workflow.connections[faultNode];
733
+ if (!connections?.main || connections.main.length < 2)
734
+ return false;
735
+ // Check if error output (index 1) was executed
736
+ const errorOutputConnections = connections.main[1];
737
+ if (!errorOutputConnections)
738
+ return false;
739
+ return errorOutputConnections.some(conn => {
740
+ const connectedNodeRuns = runData[conn.node];
741
+ return connectedNodeRuns && connectedNodeRuns.length > 0;
742
+ });
743
+ }
744
+ /**
745
+ * Check if continue-on-fail was used
746
+ */
747
+ checkContinueOnFailUsed(workflow, faultNode, runData) {
748
+ const node = workflow.nodes.find(n => n.name === faultNode);
749
+ if (!node)
750
+ return false;
751
+ const params = node.parameters;
752
+ const hasContinueOnFail = params.continueOnFail === true ||
753
+ (params.options && params.options.continueOnFail === true);
754
+ if (!hasContinueOnFail)
755
+ return false;
756
+ // Check if downstream nodes executed despite error
757
+ const connections = workflow.connections[faultNode];
758
+ if (!connections?.main?.[0])
759
+ return false;
760
+ return connections.main[0].some(conn => {
761
+ const connectedNodeRuns = runData[conn.node];
762
+ return connectedNodeRuns && connectedNodeRuns.length > 0;
763
+ });
764
+ }
765
+ /**
766
+ * Assess data integrity after fault
767
+ */
768
+ assessDataIntegrity(workflow, runData, faultNode) {
769
+ // Get downstream nodes
770
+ const downstream = this.getDownstreamNodes(workflow, faultNode);
771
+ // Check if any downstream nodes ran
772
+ const downstreamRan = downstream.filter(node => {
773
+ const runs = runData[node.name];
774
+ return runs && runs.length > 0;
775
+ });
776
+ if (downstreamRan.length === 0) {
777
+ // Error stopped propagation - data preserved (nothing bad happened downstream)
778
+ return 'preserved';
779
+ }
780
+ // Check if downstream received valid data
781
+ let hasValidData = true;
782
+ let hasCorruptedData = false;
783
+ for (const node of downstreamRan) {
784
+ const runs = runData[node.name];
785
+ for (const run of runs) {
786
+ if (run.error) {
787
+ hasCorruptedData = true;
788
+ }
789
+ const outputData = run.data?.main?.[0]?.[0]?.json;
790
+ if (outputData === undefined || outputData === null) {
791
+ hasValidData = false;
792
+ }
793
+ }
794
+ }
795
+ if (hasCorruptedData)
796
+ return 'corrupted';
797
+ if (!hasValidData)
798
+ return 'partial-loss';
799
+ return 'preserved';
800
+ }
801
+ /**
802
+ * Trace how error propagated through workflow
803
+ */
804
+ traceErrorPropagation(workflow, faultNode, runData) {
805
+ const propagation = [faultNode];
806
+ const visited = new Set([faultNode]);
807
+ const queue = [faultNode];
808
+ while (queue.length > 0) {
809
+ const current = queue.shift();
810
+ const connections = workflow.connections[current];
811
+ if (!connections?.main)
812
+ continue;
813
+ for (const outputs of connections.main) {
814
+ if (!outputs)
815
+ continue;
816
+ for (const conn of outputs) {
817
+ if (visited.has(conn.node))
818
+ continue;
819
+ visited.add(conn.node);
820
+ // Check if this node was affected by the error
821
+ const runs = runData[conn.node];
822
+ if (runs?.some(r => r.error)) {
823
+ propagation.push(conn.node);
824
+ queue.push(conn.node);
825
+ }
826
+ }
827
+ }
828
+ }
829
+ return propagation;
830
+ }
831
+ /**
832
+ * Generate human-readable details for fault test
833
+ */
834
+ generateFaultTestDetails(fault, errorHandled, retryAttempts, finalStatus) {
835
+ const parts = [];
836
+ parts.push(`Injected ${fault.faultType} fault into "${fault.targetNode}".`);
837
+ if (retryAttempts > 0) {
838
+ parts.push(`Node retried ${retryAttempts} time(s).`);
839
+ }
840
+ if (errorHandled) {
841
+ parts.push('Error was handled by workflow.');
842
+ }
843
+ else {
844
+ parts.push('Error was NOT handled - no error branch or retry configured.');
845
+ }
846
+ parts.push(`Final status: ${finalStatus}.`);
847
+ return parts.join(' ');
848
+ }
849
+ /**
850
+ * Analyze Dead Letter Queue patterns
851
+ */
852
+ analyzeDLQPatterns(workflow) {
853
+ const dlqNodes = [];
854
+ const unhandledFailurePaths = [];
855
+ const recommendations = [];
856
+ // Look for DLQ-like patterns
857
+ for (const node of workflow.nodes) {
858
+ const nameLower = node.name.toLowerCase();
859
+ const typeLower = node.type.toLowerCase();
860
+ // Common DLQ indicators
861
+ if (nameLower.includes('dlq') ||
862
+ nameLower.includes('dead') ||
863
+ nameLower.includes('failed') ||
864
+ nameLower.includes('error-queue') ||
865
+ nameLower.includes('retry-queue')) {
866
+ dlqNodes.push(node.name);
867
+ }
868
+ // Check for error notification patterns
869
+ if ((typeLower.includes('slack') || typeLower.includes('email') || typeLower.includes('telegram')) &&
870
+ (nameLower.includes('error') || nameLower.includes('alert') || nameLower.includes('notify'))) {
871
+ dlqNodes.push(node.name);
872
+ }
873
+ }
874
+ // Find error-prone nodes without error handling leading to DLQ
875
+ for (const node of workflow.nodes) {
876
+ const isErrorProne = ERROR_PRONE_NODE_TYPES.some(t => node.type.includes(t));
877
+ if (!isErrorProne)
878
+ continue;
879
+ const connections = workflow.connections[node.name];
880
+ const hasErrorBranch = connections?.main && connections.main.length > 1;
881
+ if (!hasErrorBranch) {
882
+ unhandledFailurePaths.push(node.name);
883
+ }
884
+ else {
885
+ // Check if error branch leads to DLQ
886
+ const errorOutputs = connections?.main?.[1] || [];
887
+ const leadsToDLQ = errorOutputs.some(conn => dlqNodes.includes(conn.node) ||
888
+ this.eventuallyLeadsTo(workflow, conn.node, dlqNodes));
889
+ if (!leadsToDLQ) {
890
+ unhandledFailurePaths.push(node.name);
891
+ }
892
+ }
893
+ }
894
+ // Determine poison message handling
895
+ let poisonMessageHandling = 'missing';
896
+ if (dlqNodes.length > 0) {
897
+ poisonMessageHandling = unhandledFailurePaths.length === 0 ? 'present' : 'partial';
898
+ }
899
+ // Generate recommendations
900
+ if (dlqNodes.length === 0) {
901
+ recommendations.push('Add a dead-letter queue pattern for failed message handling');
902
+ recommendations.push('Consider adding error notification (Slack, Email) for critical failures');
903
+ }
904
+ if (unhandledFailurePaths.length > 0) {
905
+ recommendations.push(`Add error branches for: ${unhandledFailurePaths.slice(0, 3).join(', ')}${unhandledFailurePaths.length > 3 ? '...' : ''}`);
906
+ }
907
+ return {
908
+ hasDLQPattern: dlqNodes.length > 0,
909
+ dlqNodes,
910
+ unhandledFailurePaths,
911
+ poisonMessageHandling,
912
+ recommendations,
913
+ };
914
+ }
915
+ /**
916
+ * Check if a node eventually leads to any of the target nodes
917
+ */
918
+ eventuallyLeadsTo(workflow, startNode, targetNodes) {
919
+ const visited = new Set();
920
+ const queue = [startNode];
921
+ while (queue.length > 0) {
922
+ const current = queue.shift();
923
+ if (visited.has(current))
924
+ continue;
925
+ visited.add(current);
926
+ if (targetNodes.includes(current)) {
927
+ return true;
928
+ }
929
+ const connections = workflow.connections[current];
930
+ if (!connections?.main)
931
+ continue;
932
+ for (const outputs of connections.main) {
933
+ if (!outputs)
934
+ continue;
935
+ for (const conn of outputs) {
936
+ queue.push(conn.node);
937
+ }
938
+ }
939
+ }
940
+ return false;
941
+ }
942
+ /**
943
+ * Quick failure mode check
944
+ */
945
+ async quickCheck(workflowId) {
946
+ const result = await this.testFailureModes(workflowId, { dryRun: true });
947
+ return {
948
+ resilient: result.passed,
949
+ errorHandlingScore: result.errorHandlingScore,
950
+ criticalIssues: result.findings.filter(f => f.severity === 'critical').length,
951
+ topIssue: result.findings[0]?.message || null,
952
+ };
953
+ }
954
+ }
955
+ exports.N8nFailureModeTesterAgent = N8nFailureModeTesterAgent;
956
+ //# sourceMappingURL=N8nFailureModeTesterAgent.js.map