musubi-sdd 3.10.0 → 5.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 +24 -19
- package/package.json +1 -1
- package/src/agents/agent-loop.js +532 -0
- package/src/agents/agentic/code-generator.js +767 -0
- package/src/agents/agentic/code-reviewer.js +698 -0
- package/src/agents/agentic/index.js +43 -0
- package/src/agents/function-tool.js +432 -0
- package/src/agents/index.js +45 -0
- package/src/agents/schema-generator.js +514 -0
- package/src/analyzers/ast-extractor.js +870 -0
- package/src/analyzers/context-optimizer.js +681 -0
- package/src/analyzers/repository-map.js +692 -0
- package/src/integrations/index.js +7 -1
- package/src/integrations/mcp/index.js +175 -0
- package/src/integrations/mcp/mcp-context-provider.js +472 -0
- package/src/integrations/mcp/mcp-discovery.js +436 -0
- package/src/integrations/mcp/mcp-tool-registry.js +467 -0
- package/src/integrations/mcp-connector.js +818 -0
- package/src/integrations/tool-discovery.js +589 -0
- package/src/managers/index.js +7 -0
- package/src/managers/skill-tools.js +565 -0
- package/src/monitoring/cost-tracker.js +7 -0
- package/src/monitoring/incident-manager.js +10 -0
- package/src/monitoring/observability.js +10 -0
- package/src/monitoring/quality-dashboard.js +491 -0
- package/src/monitoring/release-manager.js +10 -0
- package/src/orchestration/agent-skill-binding.js +655 -0
- package/src/orchestration/error-handler.js +827 -0
- package/src/orchestration/index.js +235 -1
- package/src/orchestration/mcp-tool-adapters.js +896 -0
- package/src/orchestration/reasoning/index.js +58 -0
- package/src/orchestration/reasoning/planning-engine.js +831 -0
- package/src/orchestration/reasoning/reasoning-engine.js +710 -0
- package/src/orchestration/reasoning/self-correction.js +751 -0
- package/src/orchestration/skill-executor.js +665 -0
- package/src/orchestration/skill-registry.js +650 -0
- package/src/orchestration/workflow-examples.js +1072 -0
- package/src/orchestration/workflow-executor.js +779 -0
- package/src/phase4-integration.js +248 -0
- package/src/phase5-integration.js +402 -0
- package/src/steering/steering-auto-update.js +572 -0
- package/src/steering/steering-validator.js +547 -0
- package/src/templates/template-constraints.js +646 -0
- package/src/validators/advanced-validation.js +580 -0
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ErrorHandler - Comprehensive error handling patterns
|
|
3
|
+
* Sprint 3.5: Advanced Workflows
|
|
4
|
+
*
|
|
5
|
+
* Provides:
|
|
6
|
+
* - Error classification and categorization
|
|
7
|
+
* - Recovery strategies
|
|
8
|
+
* - Error aggregation and reporting
|
|
9
|
+
* - Circuit breaker pattern
|
|
10
|
+
* - Graceful degradation
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const EventEmitter = require('events');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Error severity levels
|
|
17
|
+
*/
|
|
18
|
+
const ErrorSeverity = {
|
|
19
|
+
LOW: 'low',
|
|
20
|
+
MEDIUM: 'medium',
|
|
21
|
+
HIGH: 'high',
|
|
22
|
+
CRITICAL: 'critical'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Error categories
|
|
27
|
+
*/
|
|
28
|
+
const ErrorCategory = {
|
|
29
|
+
VALIDATION: 'validation',
|
|
30
|
+
AUTHENTICATION: 'authentication',
|
|
31
|
+
AUTHORIZATION: 'authorization',
|
|
32
|
+
NETWORK: 'network',
|
|
33
|
+
TIMEOUT: 'timeout',
|
|
34
|
+
RATE_LIMIT: 'rate-limit',
|
|
35
|
+
RESOURCE_NOT_FOUND: 'resource-not-found',
|
|
36
|
+
CONFLICT: 'conflict',
|
|
37
|
+
INTERNAL: 'internal',
|
|
38
|
+
EXTERNAL_SERVICE: 'external-service',
|
|
39
|
+
CONFIGURATION: 'configuration',
|
|
40
|
+
USER_INPUT: 'user-input',
|
|
41
|
+
UNKNOWN: 'unknown'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Circuit breaker states
|
|
46
|
+
*/
|
|
47
|
+
const CircuitState = {
|
|
48
|
+
CLOSED: 'closed',
|
|
49
|
+
OPEN: 'open',
|
|
50
|
+
HALF_OPEN: 'half-open'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Enhanced error with metadata
|
|
55
|
+
*/
|
|
56
|
+
class WorkflowError extends Error {
|
|
57
|
+
constructor(message, options = {}) {
|
|
58
|
+
super(message);
|
|
59
|
+
this.name = 'WorkflowError';
|
|
60
|
+
this.code = options.code || 'WORKFLOW_ERROR';
|
|
61
|
+
this.category = options.category || ErrorCategory.UNKNOWN;
|
|
62
|
+
this.severity = options.severity || ErrorSeverity.MEDIUM;
|
|
63
|
+
this.recoverable = options.recoverable !== false;
|
|
64
|
+
this.retryable = options.retryable || false;
|
|
65
|
+
this.context = options.context || {};
|
|
66
|
+
this.cause = options.cause || null;
|
|
67
|
+
this.timestamp = new Date().toISOString();
|
|
68
|
+
this.suggestions = options.suggestions || [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
toJSON() {
|
|
72
|
+
return {
|
|
73
|
+
name: this.name,
|
|
74
|
+
message: this.message,
|
|
75
|
+
code: this.code,
|
|
76
|
+
category: this.category,
|
|
77
|
+
severity: this.severity,
|
|
78
|
+
recoverable: this.recoverable,
|
|
79
|
+
retryable: this.retryable,
|
|
80
|
+
context: this.context,
|
|
81
|
+
timestamp: this.timestamp,
|
|
82
|
+
suggestions: this.suggestions,
|
|
83
|
+
stack: this.stack
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Error classifier - categorizes errors based on patterns
|
|
90
|
+
*/
|
|
91
|
+
class ErrorClassifier {
|
|
92
|
+
constructor() {
|
|
93
|
+
this.patterns = this._initializePatterns();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_initializePatterns() {
|
|
97
|
+
return [
|
|
98
|
+
// Network errors
|
|
99
|
+
{
|
|
100
|
+
category: ErrorCategory.NETWORK,
|
|
101
|
+
severity: ErrorSeverity.MEDIUM,
|
|
102
|
+
retryable: true,
|
|
103
|
+
patterns: [
|
|
104
|
+
/ECONNREFUSED/i,
|
|
105
|
+
/ECONNRESET/i,
|
|
106
|
+
/ENOTFOUND/i,
|
|
107
|
+
/network/i,
|
|
108
|
+
/connection failed/i,
|
|
109
|
+
/socket hang up/i
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
// Timeout errors
|
|
113
|
+
{
|
|
114
|
+
category: ErrorCategory.TIMEOUT,
|
|
115
|
+
severity: ErrorSeverity.MEDIUM,
|
|
116
|
+
retryable: true,
|
|
117
|
+
patterns: [
|
|
118
|
+
/timeout/i,
|
|
119
|
+
/ETIMEDOUT/i,
|
|
120
|
+
/timed out/i,
|
|
121
|
+
/deadline exceeded/i
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
// Rate limit errors
|
|
125
|
+
{
|
|
126
|
+
category: ErrorCategory.RATE_LIMIT,
|
|
127
|
+
severity: ErrorSeverity.LOW,
|
|
128
|
+
retryable: true,
|
|
129
|
+
patterns: [
|
|
130
|
+
/rate limit/i,
|
|
131
|
+
/too many requests/i,
|
|
132
|
+
/429/,
|
|
133
|
+
/throttl/i
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
// Authentication errors
|
|
137
|
+
{
|
|
138
|
+
category: ErrorCategory.AUTHENTICATION,
|
|
139
|
+
severity: ErrorSeverity.HIGH,
|
|
140
|
+
retryable: false,
|
|
141
|
+
patterns: [
|
|
142
|
+
/unauthorized/i,
|
|
143
|
+
/authentication failed/i,
|
|
144
|
+
/invalid token/i,
|
|
145
|
+
/401/,
|
|
146
|
+
/not authenticated/i
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
// Authorization errors
|
|
150
|
+
{
|
|
151
|
+
category: ErrorCategory.AUTHORIZATION,
|
|
152
|
+
severity: ErrorSeverity.HIGH,
|
|
153
|
+
retryable: false,
|
|
154
|
+
patterns: [
|
|
155
|
+
/forbidden/i,
|
|
156
|
+
/access denied/i,
|
|
157
|
+
/permission denied/i,
|
|
158
|
+
/403/,
|
|
159
|
+
/not authorized/i
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
// Resource not found
|
|
163
|
+
{
|
|
164
|
+
category: ErrorCategory.RESOURCE_NOT_FOUND,
|
|
165
|
+
severity: ErrorSeverity.MEDIUM,
|
|
166
|
+
retryable: false,
|
|
167
|
+
patterns: [
|
|
168
|
+
/not found/i,
|
|
169
|
+
/404/,
|
|
170
|
+
/does not exist/i,
|
|
171
|
+
/no such/i
|
|
172
|
+
]
|
|
173
|
+
},
|
|
174
|
+
// Validation errors
|
|
175
|
+
{
|
|
176
|
+
category: ErrorCategory.VALIDATION,
|
|
177
|
+
severity: ErrorSeverity.LOW,
|
|
178
|
+
retryable: false,
|
|
179
|
+
patterns: [
|
|
180
|
+
/validation/i,
|
|
181
|
+
/invalid/i,
|
|
182
|
+
/required field/i,
|
|
183
|
+
/must be/i,
|
|
184
|
+
/expected/i
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
// Conflict errors
|
|
188
|
+
{
|
|
189
|
+
category: ErrorCategory.CONFLICT,
|
|
190
|
+
severity: ErrorSeverity.MEDIUM,
|
|
191
|
+
retryable: false,
|
|
192
|
+
patterns: [
|
|
193
|
+
/conflict/i,
|
|
194
|
+
/already exists/i,
|
|
195
|
+
/duplicate/i,
|
|
196
|
+
/409/
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
// Configuration errors
|
|
200
|
+
{
|
|
201
|
+
category: ErrorCategory.CONFIGURATION,
|
|
202
|
+
severity: ErrorSeverity.HIGH,
|
|
203
|
+
retryable: false,
|
|
204
|
+
patterns: [
|
|
205
|
+
/configuration/i,
|
|
206
|
+
/config/i,
|
|
207
|
+
/missing setting/i,
|
|
208
|
+
/not configured/i
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Classify an error
|
|
216
|
+
*/
|
|
217
|
+
classify(error) {
|
|
218
|
+
const errorString = `${error.message} ${error.code || ''} ${error.name || ''}`;
|
|
219
|
+
|
|
220
|
+
for (const pattern of this.patterns) {
|
|
221
|
+
for (const regex of pattern.patterns) {
|
|
222
|
+
if (regex.test(errorString)) {
|
|
223
|
+
return {
|
|
224
|
+
category: pattern.category,
|
|
225
|
+
severity: pattern.severity,
|
|
226
|
+
retryable: pattern.retryable,
|
|
227
|
+
recoverable: pattern.severity !== ErrorSeverity.CRITICAL
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
category: ErrorCategory.UNKNOWN,
|
|
235
|
+
severity: ErrorSeverity.MEDIUM,
|
|
236
|
+
retryable: false,
|
|
237
|
+
recoverable: true
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Enhance an error with classification metadata
|
|
243
|
+
*/
|
|
244
|
+
enhance(error) {
|
|
245
|
+
if (error instanceof WorkflowError) {
|
|
246
|
+
return error;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const classification = this.classify(error);
|
|
250
|
+
|
|
251
|
+
return new WorkflowError(error.message, {
|
|
252
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
253
|
+
category: classification.category,
|
|
254
|
+
severity: classification.severity,
|
|
255
|
+
retryable: classification.retryable,
|
|
256
|
+
recoverable: classification.recoverable,
|
|
257
|
+
cause: error,
|
|
258
|
+
context: { originalName: error.name }
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Add custom pattern
|
|
264
|
+
*/
|
|
265
|
+
addPattern(pattern) {
|
|
266
|
+
this.patterns.unshift(pattern); // Add to beginning for priority
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Circuit breaker for protecting against cascading failures
|
|
272
|
+
*/
|
|
273
|
+
class CircuitBreaker extends EventEmitter {
|
|
274
|
+
constructor(options = {}) {
|
|
275
|
+
super();
|
|
276
|
+
this.name = options.name || 'default';
|
|
277
|
+
this.failureThreshold = options.failureThreshold || 5;
|
|
278
|
+
this.successThreshold = options.successThreshold || 2;
|
|
279
|
+
this.timeout = options.timeout || 30000; // 30 seconds
|
|
280
|
+
this.halfOpenMaxCalls = options.halfOpenMaxCalls || 1;
|
|
281
|
+
|
|
282
|
+
this.state = CircuitState.CLOSED;
|
|
283
|
+
this.failures = 0;
|
|
284
|
+
this.successes = 0;
|
|
285
|
+
this.lastFailureTime = null;
|
|
286
|
+
this.halfOpenCalls = 0;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Execute function with circuit breaker protection
|
|
291
|
+
*/
|
|
292
|
+
async execute(fn) {
|
|
293
|
+
if (this.state === CircuitState.OPEN) {
|
|
294
|
+
// Check if we should transition to half-open
|
|
295
|
+
if (Date.now() - this.lastFailureTime >= this.timeout) {
|
|
296
|
+
this._transitionTo(CircuitState.HALF_OPEN);
|
|
297
|
+
} else {
|
|
298
|
+
throw new WorkflowError('Circuit breaker is open', {
|
|
299
|
+
code: 'CIRCUIT_OPEN',
|
|
300
|
+
category: ErrorCategory.EXTERNAL_SERVICE,
|
|
301
|
+
severity: ErrorSeverity.HIGH,
|
|
302
|
+
retryable: true,
|
|
303
|
+
context: { circuitName: this.name }
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
309
|
+
if (this.halfOpenCalls >= this.halfOpenMaxCalls) {
|
|
310
|
+
throw new WorkflowError('Circuit breaker half-open limit reached', {
|
|
311
|
+
code: 'CIRCUIT_HALF_OPEN_LIMIT',
|
|
312
|
+
category: ErrorCategory.EXTERNAL_SERVICE,
|
|
313
|
+
severity: ErrorSeverity.MEDIUM,
|
|
314
|
+
retryable: true
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
this.halfOpenCalls++;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
const result = await fn();
|
|
322
|
+
this._onSuccess();
|
|
323
|
+
return result;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
this._onFailure(error);
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
_onSuccess() {
|
|
331
|
+
this.failures = 0;
|
|
332
|
+
|
|
333
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
334
|
+
this.successes++;
|
|
335
|
+
if (this.successes >= this.successThreshold) {
|
|
336
|
+
this._transitionTo(CircuitState.CLOSED);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
_onFailure(error) {
|
|
342
|
+
this.failures++;
|
|
343
|
+
this.lastFailureTime = Date.now();
|
|
344
|
+
this.successes = 0;
|
|
345
|
+
|
|
346
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
347
|
+
this._transitionTo(CircuitState.OPEN);
|
|
348
|
+
} else if (this.failures >= this.failureThreshold) {
|
|
349
|
+
this._transitionTo(CircuitState.OPEN);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
this.emit('failure', { error, failures: this.failures });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
_transitionTo(newState) {
|
|
356
|
+
const oldState = this.state;
|
|
357
|
+
this.state = newState;
|
|
358
|
+
|
|
359
|
+
if (newState === CircuitState.CLOSED) {
|
|
360
|
+
this.failures = 0;
|
|
361
|
+
this.successes = 0;
|
|
362
|
+
} else if (newState === CircuitState.HALF_OPEN) {
|
|
363
|
+
this.halfOpenCalls = 0;
|
|
364
|
+
this.successes = 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
this.emit('state-change', { from: oldState, to: newState });
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
getState() {
|
|
371
|
+
return {
|
|
372
|
+
state: this.state,
|
|
373
|
+
failures: this.failures,
|
|
374
|
+
successes: this.successes,
|
|
375
|
+
lastFailureTime: this.lastFailureTime
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
reset() {
|
|
380
|
+
this._transitionTo(CircuitState.CLOSED);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Error aggregator for collecting and analyzing errors
|
|
386
|
+
*/
|
|
387
|
+
class ErrorAggregator {
|
|
388
|
+
constructor(options = {}) {
|
|
389
|
+
this.maxErrors = options.maxErrors || 1000;
|
|
390
|
+
this.errors = [];
|
|
391
|
+
this.categoryCounts = new Map();
|
|
392
|
+
this.severityCounts = new Map();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Add an error to the aggregator
|
|
397
|
+
*/
|
|
398
|
+
add(error, context = {}) {
|
|
399
|
+
const enhanced = error instanceof WorkflowError ? error :
|
|
400
|
+
new ErrorClassifier().enhance(error);
|
|
401
|
+
|
|
402
|
+
const entry = {
|
|
403
|
+
error: enhanced.toJSON(),
|
|
404
|
+
context,
|
|
405
|
+
timestamp: new Date().toISOString()
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
this.errors.push(entry);
|
|
409
|
+
|
|
410
|
+
// Trim if necessary
|
|
411
|
+
if (this.errors.length > this.maxErrors) {
|
|
412
|
+
this.errors = this.errors.slice(-this.maxErrors);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Update counts
|
|
416
|
+
this._incrementCount(this.categoryCounts, enhanced.category);
|
|
417
|
+
this._incrementCount(this.severityCounts, enhanced.severity);
|
|
418
|
+
|
|
419
|
+
return entry;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
_incrementCount(map, key) {
|
|
423
|
+
map.set(key, (map.get(key) || 0) + 1);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Get error statistics
|
|
428
|
+
*/
|
|
429
|
+
getStats() {
|
|
430
|
+
return {
|
|
431
|
+
totalErrors: this.errors.length,
|
|
432
|
+
byCategory: Object.fromEntries(this.categoryCounts),
|
|
433
|
+
bySeverity: Object.fromEntries(this.severityCounts),
|
|
434
|
+
recentErrors: this.errors.slice(-10),
|
|
435
|
+
mostCommonCategory: this._getMostCommon(this.categoryCounts),
|
|
436
|
+
criticalCount: this.severityCounts.get(ErrorSeverity.CRITICAL) || 0
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
_getMostCommon(map) {
|
|
441
|
+
let maxKey = null;
|
|
442
|
+
let maxCount = 0;
|
|
443
|
+
for (const [key, count] of map) {
|
|
444
|
+
if (count > maxCount) {
|
|
445
|
+
maxKey = key;
|
|
446
|
+
maxCount = count;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return maxKey;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Get errors by category
|
|
454
|
+
*/
|
|
455
|
+
getByCategory(category) {
|
|
456
|
+
return this.errors.filter(e => e.error.category === category);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get errors by severity
|
|
461
|
+
*/
|
|
462
|
+
getBySeverity(severity) {
|
|
463
|
+
return this.errors.filter(e => e.error.severity === severity);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Get retryable errors
|
|
468
|
+
*/
|
|
469
|
+
getRetryable() {
|
|
470
|
+
return this.errors.filter(e => e.error.retryable);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Clear all errors
|
|
475
|
+
*/
|
|
476
|
+
clear() {
|
|
477
|
+
this.errors = [];
|
|
478
|
+
this.categoryCounts.clear();
|
|
479
|
+
this.severityCounts.clear();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Generate error report
|
|
484
|
+
*/
|
|
485
|
+
generateReport() {
|
|
486
|
+
const stats = this.getStats();
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
summary: {
|
|
490
|
+
total: stats.totalErrors,
|
|
491
|
+
critical: stats.criticalCount,
|
|
492
|
+
mostCommonCategory: stats.mostCommonCategory
|
|
493
|
+
},
|
|
494
|
+
breakdown: {
|
|
495
|
+
byCategory: stats.byCategory,
|
|
496
|
+
bySeverity: stats.bySeverity
|
|
497
|
+
},
|
|
498
|
+
recentErrors: stats.recentErrors.map(e => ({
|
|
499
|
+
message: e.error.message,
|
|
500
|
+
category: e.error.category,
|
|
501
|
+
severity: e.error.severity,
|
|
502
|
+
timestamp: e.timestamp
|
|
503
|
+
})),
|
|
504
|
+
recommendations: this._generateRecommendations(stats)
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
_generateRecommendations(stats) {
|
|
509
|
+
const recommendations = [];
|
|
510
|
+
|
|
511
|
+
if (stats.criticalCount > 0) {
|
|
512
|
+
recommendations.push({
|
|
513
|
+
priority: 'high',
|
|
514
|
+
message: `${stats.criticalCount} critical errors require immediate attention`
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const networkErrors = this.categoryCounts.get(ErrorCategory.NETWORK) || 0;
|
|
519
|
+
if (networkErrors > 5) {
|
|
520
|
+
recommendations.push({
|
|
521
|
+
priority: 'medium',
|
|
522
|
+
message: 'Multiple network errors detected. Check connectivity and service availability.'
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const authErrors = this.categoryCounts.get(ErrorCategory.AUTHENTICATION) || 0;
|
|
527
|
+
if (authErrors > 0) {
|
|
528
|
+
recommendations.push({
|
|
529
|
+
priority: 'high',
|
|
530
|
+
message: 'Authentication errors detected. Verify credentials and tokens.'
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const rateLimitErrors = this.categoryCounts.get(ErrorCategory.RATE_LIMIT) || 0;
|
|
535
|
+
if (rateLimitErrors > 3) {
|
|
536
|
+
recommendations.push({
|
|
537
|
+
priority: 'medium',
|
|
538
|
+
message: 'Rate limiting detected. Consider implementing backoff or reducing request frequency.'
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return recommendations;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Graceful degradation manager
|
|
548
|
+
*/
|
|
549
|
+
class GracefulDegradation {
|
|
550
|
+
constructor() {
|
|
551
|
+
this.fallbacks = new Map();
|
|
552
|
+
this.degradedServices = new Set();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Register a fallback for a service
|
|
557
|
+
*/
|
|
558
|
+
registerFallback(serviceName, fallbackFn, options = {}) {
|
|
559
|
+
this.fallbacks.set(serviceName, {
|
|
560
|
+
fn: fallbackFn,
|
|
561
|
+
ttl: options.ttl || 60000, // 1 minute default cache
|
|
562
|
+
lastResult: null,
|
|
563
|
+
lastResultTime: null
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Execute with graceful degradation
|
|
569
|
+
*/
|
|
570
|
+
async execute(serviceName, primaryFn, options = {}) {
|
|
571
|
+
try {
|
|
572
|
+
const result = await primaryFn();
|
|
573
|
+
|
|
574
|
+
// Service recovered
|
|
575
|
+
if (this.degradedServices.has(serviceName)) {
|
|
576
|
+
this.degradedServices.delete(serviceName);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Cache successful result for fallback
|
|
580
|
+
const fallback = this.fallbacks.get(serviceName);
|
|
581
|
+
if (fallback) {
|
|
582
|
+
fallback.lastResult = result;
|
|
583
|
+
fallback.lastResultTime = Date.now();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return { result, degraded: false };
|
|
587
|
+
|
|
588
|
+
} catch (error) {
|
|
589
|
+
const fallback = this.fallbacks.get(serviceName);
|
|
590
|
+
|
|
591
|
+
if (!fallback) {
|
|
592
|
+
throw error;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
this.degradedServices.add(serviceName);
|
|
596
|
+
|
|
597
|
+
// Try cached result first
|
|
598
|
+
if (fallback.lastResult &&
|
|
599
|
+
(Date.now() - fallback.lastResultTime) < fallback.ttl) {
|
|
600
|
+
return {
|
|
601
|
+
result: fallback.lastResult,
|
|
602
|
+
degraded: true,
|
|
603
|
+
source: 'cache',
|
|
604
|
+
error: error.message
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Execute fallback function
|
|
609
|
+
try {
|
|
610
|
+
const fallbackResult = await fallback.fn(error);
|
|
611
|
+
return {
|
|
612
|
+
result: fallbackResult,
|
|
613
|
+
degraded: true,
|
|
614
|
+
source: 'fallback',
|
|
615
|
+
error: error.message
|
|
616
|
+
};
|
|
617
|
+
} catch (fallbackError) {
|
|
618
|
+
// Both primary and fallback failed
|
|
619
|
+
throw new WorkflowError('Primary and fallback both failed', {
|
|
620
|
+
code: 'DEGRADATION_FAILED',
|
|
621
|
+
category: ErrorCategory.INTERNAL,
|
|
622
|
+
severity: ErrorSeverity.CRITICAL,
|
|
623
|
+
cause: error,
|
|
624
|
+
context: {
|
|
625
|
+
serviceName,
|
|
626
|
+
primaryError: error.message,
|
|
627
|
+
fallbackError: fallbackError.message
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Get degraded services
|
|
636
|
+
*/
|
|
637
|
+
getDegradedServices() {
|
|
638
|
+
return [...this.degradedServices];
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Check if service is degraded
|
|
643
|
+
*/
|
|
644
|
+
isDegraded(serviceName) {
|
|
645
|
+
return this.degradedServices.has(serviceName);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Main error handler combining all strategies
|
|
651
|
+
*/
|
|
652
|
+
class ErrorHandler extends EventEmitter {
|
|
653
|
+
constructor(options = {}) {
|
|
654
|
+
super();
|
|
655
|
+
this.classifier = new ErrorClassifier();
|
|
656
|
+
this.aggregator = new ErrorAggregator(options.aggregator);
|
|
657
|
+
this.degradation = new GracefulDegradation();
|
|
658
|
+
this.circuitBreakers = new Map();
|
|
659
|
+
this.globalRetryPolicy = options.retryPolicy || {
|
|
660
|
+
maxRetries: 3,
|
|
661
|
+
backoffMs: 1000,
|
|
662
|
+
backoffMultiplier: 2,
|
|
663
|
+
maxBackoffMs: 30000
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Get or create circuit breaker for a service
|
|
669
|
+
*/
|
|
670
|
+
getCircuitBreaker(serviceName, options = {}) {
|
|
671
|
+
if (!this.circuitBreakers.has(serviceName)) {
|
|
672
|
+
const breaker = new CircuitBreaker({ name: serviceName, ...options });
|
|
673
|
+
breaker.on('state-change', (event) => {
|
|
674
|
+
this.emit('circuit-state-change', { service: serviceName, ...event });
|
|
675
|
+
});
|
|
676
|
+
this.circuitBreakers.set(serviceName, breaker);
|
|
677
|
+
}
|
|
678
|
+
return this.circuitBreakers.get(serviceName);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Handle an error with full error handling pipeline
|
|
683
|
+
*/
|
|
684
|
+
handle(error, context = {}) {
|
|
685
|
+
// Classify and enhance error
|
|
686
|
+
const enhanced = this.classifier.enhance(error);
|
|
687
|
+
|
|
688
|
+
// Add to aggregator
|
|
689
|
+
this.aggregator.add(enhanced, context);
|
|
690
|
+
|
|
691
|
+
// Emit error event
|
|
692
|
+
this.emit('error', { error: enhanced, context });
|
|
693
|
+
|
|
694
|
+
// Log based on severity
|
|
695
|
+
this._logError(enhanced, context);
|
|
696
|
+
|
|
697
|
+
// Return enhanced error with handling suggestions
|
|
698
|
+
return {
|
|
699
|
+
error: enhanced,
|
|
700
|
+
handled: true,
|
|
701
|
+
suggestions: this._getSuggestions(enhanced)
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
_logError(error, context) {
|
|
706
|
+
const logData = {
|
|
707
|
+
message: error.message,
|
|
708
|
+
category: error.category,
|
|
709
|
+
severity: error.severity,
|
|
710
|
+
context: { ...error.context, ...context }
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
switch (error.severity) {
|
|
714
|
+
case ErrorSeverity.CRITICAL:
|
|
715
|
+
console.error('[CRITICAL]', JSON.stringify(logData));
|
|
716
|
+
break;
|
|
717
|
+
case ErrorSeverity.HIGH:
|
|
718
|
+
console.error('[ERROR]', JSON.stringify(logData));
|
|
719
|
+
break;
|
|
720
|
+
case ErrorSeverity.MEDIUM:
|
|
721
|
+
console.warn('[WARN]', JSON.stringify(logData));
|
|
722
|
+
break;
|
|
723
|
+
default:
|
|
724
|
+
console.info('[INFO]', JSON.stringify(logData));
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
_getSuggestions(error) {
|
|
729
|
+
const suggestions = [...(error.suggestions || [])];
|
|
730
|
+
|
|
731
|
+
switch (error.category) {
|
|
732
|
+
case ErrorCategory.NETWORK:
|
|
733
|
+
suggestions.push('Check network connectivity');
|
|
734
|
+
suggestions.push('Verify service endpoints are accessible');
|
|
735
|
+
break;
|
|
736
|
+
case ErrorCategory.AUTHENTICATION:
|
|
737
|
+
suggestions.push('Verify credentials are correct');
|
|
738
|
+
suggestions.push('Check if tokens have expired');
|
|
739
|
+
break;
|
|
740
|
+
case ErrorCategory.RATE_LIMIT:
|
|
741
|
+
suggestions.push('Implement exponential backoff');
|
|
742
|
+
suggestions.push('Consider caching responses');
|
|
743
|
+
break;
|
|
744
|
+
case ErrorCategory.TIMEOUT:
|
|
745
|
+
suggestions.push('Increase timeout values');
|
|
746
|
+
suggestions.push('Consider breaking operation into smaller chunks');
|
|
747
|
+
break;
|
|
748
|
+
case ErrorCategory.CONFIGURATION:
|
|
749
|
+
suggestions.push('Review configuration settings');
|
|
750
|
+
suggestions.push('Check environment variables');
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (error.retryable) {
|
|
755
|
+
suggestions.push('This error may be resolved by retrying');
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return suggestions;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Execute with retry
|
|
763
|
+
*/
|
|
764
|
+
async executeWithRetry(fn, options = {}) {
|
|
765
|
+
const policy = { ...this.globalRetryPolicy, ...options };
|
|
766
|
+
let lastError;
|
|
767
|
+
let currentBackoff = policy.backoffMs;
|
|
768
|
+
|
|
769
|
+
for (let attempt = 0; attempt <= policy.maxRetries; attempt++) {
|
|
770
|
+
try {
|
|
771
|
+
return await fn();
|
|
772
|
+
} catch (error) {
|
|
773
|
+
lastError = error;
|
|
774
|
+
const enhanced = this.classifier.enhance(error);
|
|
775
|
+
|
|
776
|
+
if (!enhanced.retryable || attempt >= policy.maxRetries) {
|
|
777
|
+
await this.handle(enhanced, { attempt, maxRetries: policy.maxRetries });
|
|
778
|
+
throw enhanced;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
this.emit('retry', {
|
|
782
|
+
attempt: attempt + 1,
|
|
783
|
+
maxRetries: policy.maxRetries,
|
|
784
|
+
backoffMs: currentBackoff,
|
|
785
|
+
error: enhanced.message
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
await this._sleep(currentBackoff);
|
|
789
|
+
currentBackoff = Math.min(
|
|
790
|
+
currentBackoff * policy.backoffMultiplier,
|
|
791
|
+
policy.maxBackoffMs
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
throw lastError;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Get error report
|
|
801
|
+
*/
|
|
802
|
+
getReport() {
|
|
803
|
+
return {
|
|
804
|
+
...this.aggregator.generateReport(),
|
|
805
|
+
circuitBreakers: Object.fromEntries(
|
|
806
|
+
[...this.circuitBreakers].map(([name, breaker]) => [name, breaker.getState()])
|
|
807
|
+
),
|
|
808
|
+
degradedServices: this.degradation.getDegradedServices()
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
_sleep(ms) {
|
|
813
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
module.exports = {
|
|
818
|
+
ErrorHandler,
|
|
819
|
+
ErrorClassifier,
|
|
820
|
+
ErrorAggregator,
|
|
821
|
+
CircuitBreaker,
|
|
822
|
+
GracefulDegradation,
|
|
823
|
+
WorkflowError,
|
|
824
|
+
ErrorSeverity,
|
|
825
|
+
ErrorCategory,
|
|
826
|
+
CircuitState
|
|
827
|
+
};
|