timonel 3.0.0 → 3.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 (40) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +432 -1
  3. package/SECURITY.md +2 -2
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.js +2 -0
  6. package/dist/lib/policy/configurationLoader.d.ts +46 -0
  7. package/dist/lib/policy/configurationLoader.js +251 -0
  8. package/dist/lib/policy/errorContextGenerator.d.ts +63 -0
  9. package/dist/lib/policy/errorContextGenerator.js +302 -0
  10. package/dist/lib/policy/errors.d.ts +40 -0
  11. package/dist/lib/policy/errors.js +109 -0
  12. package/dist/lib/policy/index.d.ts +11 -0
  13. package/dist/lib/policy/index.js +10 -0
  14. package/dist/lib/policy/parallelExecutor.d.ts +58 -0
  15. package/dist/lib/policy/parallelExecutor.js +215 -0
  16. package/dist/lib/policy/pluginLoader.d.ts +41 -0
  17. package/dist/lib/policy/pluginLoader.js +220 -0
  18. package/dist/lib/policy/pluginRegistry.d.ts +14 -0
  19. package/dist/lib/policy/pluginRegistry.js +69 -0
  20. package/dist/lib/policy/policyEngine.d.ts +39 -0
  21. package/dist/lib/policy/policyEngine.js +495 -0
  22. package/dist/lib/policy/resultAggregator.d.ts +8 -0
  23. package/dist/lib/policy/resultAggregator.js +138 -0
  24. package/dist/lib/policy/resultFormatter.d.ts +25 -0
  25. package/dist/lib/policy/resultFormatter.js +217 -0
  26. package/dist/lib/policy/types.d.ts +111 -0
  27. package/dist/lib/policy/types.js +1 -0
  28. package/dist/lib/policy/validationCache.d.ts +58 -0
  29. package/dist/lib/policy/validationCache.js +289 -0
  30. package/dist/lib/rutter.d.ts +7 -2
  31. package/dist/lib/rutter.js +177 -8
  32. package/dist/lib/templates/flexible-subchart.js +4 -2
  33. package/dist/lib/templates/umbrella-chart.js +8 -8
  34. package/dist/lib/umbrellaRutter.d.ts +1 -1
  35. package/dist/lib/umbrellaRutter.js +2 -2
  36. package/dist/lib/validation/inputValidator.d.ts +26 -0
  37. package/dist/lib/validation/inputValidator.js +176 -0
  38. package/dist/types/index.d.ts +27 -0
  39. package/dist/types/index.js +1 -0
  40. package/package.json +22 -20
@@ -0,0 +1,69 @@
1
+ import { PluginRegistrationError } from './errors.js';
2
+ export class PluginRegistry {
3
+ constructor() {
4
+ this.plugins = new Map();
5
+ this.pluginConfigs = new Map();
6
+ }
7
+ register(plugin, config) {
8
+ this.validatePlugin(plugin);
9
+ if (this.plugins.has(plugin.name)) {
10
+ throw new PluginRegistrationError(`Plugin with name '${plugin.name}' is already registered`, plugin.name);
11
+ }
12
+ this.plugins.set(plugin.name, plugin);
13
+ if (config !== undefined) {
14
+ this.pluginConfigs.set(plugin.name, config);
15
+ }
16
+ }
17
+ getPlugin(name) {
18
+ return this.plugins.get(name);
19
+ }
20
+ getAllPlugins() {
21
+ return Array.from(this.plugins.values());
22
+ }
23
+ getPluginConfig(pluginName) {
24
+ return this.pluginConfigs.get(pluginName);
25
+ }
26
+ hasPlugin(name) {
27
+ return this.plugins.has(name);
28
+ }
29
+ getPluginCount() {
30
+ return this.plugins.size;
31
+ }
32
+ unregister(name) {
33
+ const removed = this.plugins.delete(name);
34
+ if (removed) {
35
+ this.pluginConfigs.delete(name);
36
+ }
37
+ return removed;
38
+ }
39
+ clear() {
40
+ this.plugins.clear();
41
+ this.pluginConfigs.clear();
42
+ }
43
+ validatePlugin(plugin) {
44
+ if (!plugin) {
45
+ throw new PluginRegistrationError('Plugin cannot be null or undefined');
46
+ }
47
+ if (typeof plugin !== 'object') {
48
+ throw new PluginRegistrationError('Plugin must be an object');
49
+ }
50
+ if (!plugin.name || typeof plugin.name !== 'string') {
51
+ throw new PluginRegistrationError('Plugin must have a non-empty string name');
52
+ }
53
+ if (!plugin.version || typeof plugin.version !== 'string') {
54
+ throw new PluginRegistrationError('Plugin must have a non-empty string version', plugin.name);
55
+ }
56
+ if (typeof plugin.validate !== 'function') {
57
+ throw new PluginRegistrationError('Plugin must implement validate method', plugin.name);
58
+ }
59
+ if (plugin.description !== undefined && typeof plugin.description !== 'string') {
60
+ throw new PluginRegistrationError('Plugin description must be a string', plugin.name);
61
+ }
62
+ if (plugin.configSchema !== undefined && typeof plugin.configSchema !== 'object') {
63
+ throw new PluginRegistrationError('Plugin configSchema must be an object', plugin.name);
64
+ }
65
+ if (plugin.metadata !== undefined && typeof plugin.metadata !== 'object') {
66
+ throw new PluginRegistrationError('Plugin metadata must be an object', plugin.name);
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,39 @@
1
+ import type { ChartMetadata } from '../rutter.js';
2
+ import type { PolicyEngine as IPolicyEngine, PolicyPlugin, PolicyEngineOptions, PolicyResult, PolicyViolation, PolicyWarning, ValidationContext } from './types.js';
3
+ export declare class PolicyEngine implements IPolicyEngine {
4
+ private readonly registry;
5
+ private options;
6
+ private readonly logger;
7
+ private readonly errorContextGenerator;
8
+ private readonly inputValidator;
9
+ private _configurationLoader?;
10
+ private _cache?;
11
+ private _parallelExecutor?;
12
+ constructor(options?: PolicyEngineOptions);
13
+ private get configurationLoader();
14
+ private get cache();
15
+ private get parallelExecutor();
16
+ use(plugin: PolicyPlugin): Promise<PolicyEngine>;
17
+ validate(manifests: unknown[], chartMetadata: ChartMetadata): Promise<PolicyResult>;
18
+ configure(options: PolicyEngineOptions): PolicyEngine;
19
+ formatResult(result: PolicyResult): string;
20
+ generateErrorContext(violation: PolicyViolation | PolicyWarning, validationContext?: ValidationContext, allViolations?: (PolicyViolation | PolicyWarning)[], executionTime?: number): import("./errorContextGenerator.js").ErrorContext;
21
+ generateErrorReport(violation: PolicyViolation | PolicyWarning, validationContext?: ValidationContext, allViolations?: (PolicyViolation | PolicyWarning)[], executionTime?: number): string;
22
+ getCacheStats(): import("./validationCache.js").CacheStats;
23
+ invalidateCache(criteria: {
24
+ manifestHash?: string;
25
+ pluginHash?: string;
26
+ olderThan?: number;
27
+ all?: boolean;
28
+ }): number;
29
+ clearCacheStats(): void;
30
+ private executeSequential;
31
+ private executeParallelOptimized;
32
+ private executePlugin;
33
+ private executePluginAttempt;
34
+ private shouldRetryError;
35
+ private sleep;
36
+ private createTimeoutPromise;
37
+ private handlePluginError;
38
+ private generateErrorSuggestion;
39
+ }
@@ -0,0 +1,495 @@
1
+ import { createLogger } from '../utils/logger.js';
2
+ import { InputValidator } from '../validation/inputValidator.js';
3
+ import { PluginRegistry } from './pluginRegistry.js';
4
+ import { PolicyEngineError, ValidationTimeoutError, PluginError, PluginRetryExhaustedError, PolicyValidationError, } from './errors.js';
5
+ import { generateResultSummary } from './resultAggregator.js';
6
+ import { DefaultResultFormatter } from './resultFormatter.js';
7
+ import { ErrorContextGenerator } from './errorContextGenerator.js';
8
+ import { ConfigurationLoader } from './configurationLoader.js';
9
+ import { ValidationCache, generateManifestHash, generatePluginHash } from './validationCache.js';
10
+ import { ParallelExecutor, calculateOptimalConcurrency, } from './parallelExecutor.js';
11
+ const UNKNOWN_ERROR_MESSAGE = 'Unknown error';
12
+ const DEFAULT_OPTIONS = {
13
+ timeout: 5000,
14
+ parallel: false,
15
+ failFast: false,
16
+ gracefulDegradation: true,
17
+ };
18
+ const DEFAULT_RETRY_CONFIG = {
19
+ maxAttempts: 3,
20
+ baseDelay: 1000,
21
+ backoffMultiplier: 2,
22
+ maxDelay: 10000,
23
+ retryOnTimeout: true,
24
+ retryOnPluginError: false,
25
+ };
26
+ const OPERATIONS = {
27
+ PLUGIN_REGISTRATION: 'plugin_registration',
28
+ VALIDATION_START: 'validation_start',
29
+ VALIDATION_COMPLETE: 'validation_complete',
30
+ PLUGIN_EXECUTION_ERROR: 'plugin_execution_error_isolated',
31
+ PLUGIN_EXECUTION_RETRY: 'plugin_execution_retry',
32
+ };
33
+ export class PolicyEngine {
34
+ constructor(options = {}) {
35
+ this.registry = new PluginRegistry();
36
+ this.options = { ...DEFAULT_OPTIONS, ...options };
37
+ this.logger = createLogger('policy-engine');
38
+ this.errorContextGenerator = new ErrorContextGenerator();
39
+ this.inputValidator = new InputValidator(options.validationOptions);
40
+ this.logger.debug('PolicyEngine initialized with lazy loading', {
41
+ options: this.options,
42
+ cacheEnabled: !!options.cacheOptions,
43
+ parallelEnabled: !!options.parallel,
44
+ operation: 'policy_engine_init',
45
+ });
46
+ }
47
+ get configurationLoader() {
48
+ if (!this._configurationLoader) {
49
+ this._configurationLoader = new ConfigurationLoader(this.options.configurationLoader);
50
+ this.logger.debug('ConfigurationLoader initialized lazily', {
51
+ operation: 'lazy_init_config_loader',
52
+ });
53
+ }
54
+ return this._configurationLoader;
55
+ }
56
+ get cache() {
57
+ if (!this._cache) {
58
+ this._cache = new ValidationCache(this.options.cacheOptions);
59
+ this.logger.debug('ValidationCache initialized lazily', {
60
+ cacheEnabled: !!this.options.cacheOptions,
61
+ operation: 'lazy_init_cache',
62
+ });
63
+ }
64
+ return this._cache;
65
+ }
66
+ get parallelExecutor() {
67
+ if (!this._parallelExecutor) {
68
+ const parallelOptions = {
69
+ maxConcurrency: this.options.parallelOptions?.maxConcurrency || calculateOptimalConcurrency(10),
70
+ pluginTimeout: this.options.timeout || DEFAULT_OPTIONS.timeout,
71
+ failFast: this.options.failFast || DEFAULT_OPTIONS.failFast,
72
+ ...this.options.parallelOptions,
73
+ };
74
+ this._parallelExecutor = new ParallelExecutor(parallelOptions);
75
+ this.logger.debug('ParallelExecutor initialized lazily', {
76
+ maxConcurrency: parallelOptions.maxConcurrency,
77
+ operation: 'lazy_init_parallel_executor',
78
+ });
79
+ }
80
+ return this._parallelExecutor;
81
+ }
82
+ async use(plugin) {
83
+ try {
84
+ if (!plugin || typeof plugin !== 'object') {
85
+ throw new PolicyValidationError('Plugin must be a non-null object');
86
+ }
87
+ if (!plugin.name || typeof plugin.name !== 'string') {
88
+ throw new PolicyValidationError('Plugin name must be a non-empty string');
89
+ }
90
+ if (!plugin.version || typeof plugin.version !== 'string') {
91
+ throw new PolicyValidationError('Plugin version must be a non-empty string');
92
+ }
93
+ if (typeof plugin.validate !== 'function') {
94
+ throw new PolicyValidationError('Plugin must have a validate method');
95
+ }
96
+ if (plugin.name.length > 100) {
97
+ throw new PolicyValidationError('Plugin name exceeds maximum length (100 characters)');
98
+ }
99
+ const sanitizedName = plugin.name
100
+ .replace(/[<>"'&]/g, '')
101
+ .trim();
102
+ if (sanitizedName !== plugin.name) {
103
+ this.logger.warn('Plugin name was sanitized', {
104
+ original: plugin.name,
105
+ sanitized: sanitizedName,
106
+ operation: 'plugin_name_sanitization',
107
+ });
108
+ }
109
+ const pluginConfiguration = await this.configurationLoader.loadPluginConfiguration(plugin, this.options.environment, this.options.pluginConfig?.[plugin.name]);
110
+ this.registry.register(plugin, pluginConfiguration.config);
111
+ this.logger.info('Plugin registered successfully', {
112
+ pluginName: plugin.name,
113
+ pluginVersion: plugin.version,
114
+ hasConfig: Object.keys(pluginConfiguration.config).length > 0,
115
+ configValidated: pluginConfiguration.validated,
116
+ configSources: pluginConfiguration.entries.map((e) => e.source),
117
+ operation: OPERATIONS.PLUGIN_REGISTRATION,
118
+ });
119
+ if (pluginConfiguration.validationErrors) {
120
+ this.logger.warn('Plugin configuration validation warnings', {
121
+ pluginName: plugin.name,
122
+ errors: pluginConfiguration.validationErrors,
123
+ operation: 'plugin_config_validation_warnings',
124
+ });
125
+ }
126
+ return this;
127
+ }
128
+ catch (error) {
129
+ this.logger.error('Plugin registration failed', {
130
+ pluginName: plugin?.name || 'unknown',
131
+ error: error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE,
132
+ operation: 'plugin_registration_error',
133
+ });
134
+ throw error;
135
+ }
136
+ }
137
+ async validate(manifests, chartMetadata) {
138
+ const startTime = Date.now();
139
+ if (!Array.isArray(manifests)) {
140
+ throw new PolicyValidationError('Manifests must be an array');
141
+ }
142
+ if (manifests.length > 1000) {
143
+ throw new PolicyValidationError('Too many manifests provided (maximum: 1000)');
144
+ }
145
+ manifests.forEach((manifest, index) => {
146
+ if (manifest === null || manifest === undefined) {
147
+ throw new PolicyValidationError(`Manifest at index ${index} cannot be null or undefined`);
148
+ }
149
+ const manifestStr = JSON.stringify(manifest);
150
+ if (manifestStr.length > 100000) {
151
+ throw new PolicyValidationError(`Manifest at index ${index} exceeds size limit (100KB)`);
152
+ }
153
+ });
154
+ if (!chartMetadata || typeof chartMetadata !== 'object') {
155
+ throw new PolicyValidationError('Chart metadata must be a non-null object');
156
+ }
157
+ const plugins = this.registry.getAllPlugins();
158
+ this.logger.info('Starting policy validation', {
159
+ manifestCount: manifests.length,
160
+ pluginCount: plugins.length,
161
+ operation: OPERATIONS.VALIDATION_START,
162
+ });
163
+ if (plugins.length === 0) {
164
+ const metadata = {
165
+ executionTime: Date.now() - startTime,
166
+ pluginCount: 0,
167
+ manifestCount: manifests.length,
168
+ startTime,
169
+ };
170
+ this.logger.debug('No plugins registered, validation successful', {
171
+ metadata,
172
+ operation: 'validation_no_plugins',
173
+ });
174
+ return {
175
+ valid: true,
176
+ violations: [],
177
+ warnings: [],
178
+ metadata,
179
+ summary: {
180
+ violationsBySeverity: { error: 0, warning: 0, info: 0 },
181
+ violationsByPlugin: {},
182
+ topViolationTypes: [],
183
+ },
184
+ };
185
+ }
186
+ const manifestHash = generateManifestHash(manifests);
187
+ const pluginNames = plugins.map((p) => p.name);
188
+ const pluginConfigs = Object.fromEntries(pluginNames.map((name) => [name, this.registry.getPluginConfig(name)]));
189
+ const pluginHash = generatePluginHash(pluginNames, pluginConfigs);
190
+ const cachedResult = this.cache.get(manifestHash, pluginHash);
191
+ if (cachedResult) {
192
+ this.logger.info('Returning cached validation result', {
193
+ manifestCount: manifests.length,
194
+ pluginCount: plugins.length,
195
+ cacheAge: Date.now() - (cachedResult.metadata.startTime || 0),
196
+ operation: 'validation_cache_hit',
197
+ });
198
+ return cachedResult;
199
+ }
200
+ const violations = [];
201
+ const warnings = [];
202
+ try {
203
+ if (this.options.parallel) {
204
+ await this.executeParallelOptimized(plugins, manifests, violations, warnings, chartMetadata);
205
+ }
206
+ else {
207
+ await this.executeSequential(plugins, manifests, violations, warnings, chartMetadata);
208
+ }
209
+ }
210
+ catch (error) {
211
+ const executionTime = Date.now() - startTime;
212
+ this.logger.error('Policy validation failed', {
213
+ error: error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE,
214
+ executionTime,
215
+ operation: 'validation_error',
216
+ });
217
+ throw new PolicyEngineError(`Validation failed: ${error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE}`);
218
+ }
219
+ const executionTime = Date.now() - startTime;
220
+ const metadata = {
221
+ executionTime,
222
+ pluginCount: plugins.length,
223
+ manifestCount: manifests.length,
224
+ startTime,
225
+ };
226
+ const result = {
227
+ valid: violations.length === 0,
228
+ violations,
229
+ warnings,
230
+ metadata,
231
+ summary: generateResultSummary(violations, warnings, plugins.map((p) => p.name)),
232
+ };
233
+ this.cache.set(manifestHash, pluginHash, result);
234
+ this.logger.info('Policy validation completed', {
235
+ valid: result.valid,
236
+ violationCount: violations.length,
237
+ warningCount: warnings.length,
238
+ executionTime,
239
+ cached: true,
240
+ operation: 'validation_complete',
241
+ });
242
+ return result;
243
+ }
244
+ configure(options) {
245
+ this.options = { ...this.options, ...options };
246
+ this.logger.debug('PolicyEngine configuration updated', {
247
+ options: this.options,
248
+ operation: 'policy_engine_configure',
249
+ });
250
+ return this;
251
+ }
252
+ formatResult(result) {
253
+ const formatter = this.options.formatter || new DefaultResultFormatter();
254
+ return formatter.format(result);
255
+ }
256
+ generateErrorContext(violation, validationContext, allViolations, executionTime) {
257
+ return this.errorContextGenerator.generateContext(violation, validationContext, allViolations, executionTime);
258
+ }
259
+ generateErrorReport(violation, validationContext, allViolations, executionTime) {
260
+ return this.errorContextGenerator.generateErrorReport(violation, validationContext, allViolations, executionTime);
261
+ }
262
+ getCacheStats() {
263
+ return this.cache.getStats();
264
+ }
265
+ invalidateCache(criteria) {
266
+ return this.cache.invalidate(criteria);
267
+ }
268
+ clearCacheStats() {
269
+ this.cache.clearStats();
270
+ }
271
+ async executeSequential(plugins, manifests, violations, warnings, chartMetadata) {
272
+ for (const plugin of plugins) {
273
+ try {
274
+ const pluginViolations = await this.executePlugin(plugin, manifests, chartMetadata);
275
+ for (const violation of pluginViolations) {
276
+ if (violation.severity === 'error') {
277
+ violations.push(violation);
278
+ }
279
+ else {
280
+ warnings.push(violation);
281
+ }
282
+ }
283
+ if (this.options.failFast && violations.length > 0) {
284
+ this.logger.debug('Fail fast enabled, stopping validation', {
285
+ pluginName: plugin.name,
286
+ violationCount: violations.length,
287
+ operation: 'validation_fail_fast',
288
+ });
289
+ break;
290
+ }
291
+ }
292
+ catch (error) {
293
+ if (this.options.gracefulDegradation) {
294
+ this.handlePluginError(plugin, error, violations);
295
+ }
296
+ else {
297
+ throw error;
298
+ }
299
+ }
300
+ }
301
+ }
302
+ async executeParallelOptimized(plugins, manifests, violations, warnings, chartMetadata) {
303
+ const validationContext = {
304
+ chart: chartMetadata,
305
+ environment: this.options.environment || 'development',
306
+ logger: this.logger,
307
+ };
308
+ const { results, stats } = await this.parallelExecutor.executePlugins(plugins, manifests, validationContext);
309
+ this.logger.info('Parallel execution completed with statistics', {
310
+ pluginCount: plugins.length,
311
+ totalExecutionTime: stats.totalExecutionTime,
312
+ averagePluginTime: stats.averagePluginTime,
313
+ concurrencyUtilization: stats.concurrencyUtilization,
314
+ peakConcurrentPlugins: stats.resourceUsage.peakConcurrentPlugins,
315
+ totalMemoryMB: stats.resourceUsage.totalMemoryMB,
316
+ operation: 'parallel_execution_stats',
317
+ });
318
+ for (const result of results) {
319
+ if (result.error) {
320
+ if (this.options.gracefulDegradation) {
321
+ this.handlePluginError(result.plugin, result.error, violations);
322
+ }
323
+ else {
324
+ throw result.error;
325
+ }
326
+ }
327
+ else {
328
+ for (const violation of result.violations) {
329
+ if (violation.severity === 'error') {
330
+ violations.push(violation);
331
+ }
332
+ else {
333
+ warnings.push(violation);
334
+ }
335
+ }
336
+ }
337
+ if (this.options.failFast && violations.length > 0) {
338
+ this.logger.debug('Fail fast enabled, stopping validation', {
339
+ pluginName: result.plugin.name,
340
+ violationCount: violations.length,
341
+ operation: 'validation_fail_fast',
342
+ });
343
+ break;
344
+ }
345
+ }
346
+ }
347
+ async executePlugin(plugin, manifests, chartMetadata) {
348
+ const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...this.options.retryConfig };
349
+ let lastError = null;
350
+ for (let attempt = 1; attempt <= retryConfig.maxAttempts; attempt++) {
351
+ try {
352
+ return await this.executePluginAttempt(plugin, manifests, attempt, chartMetadata);
353
+ }
354
+ catch (error) {
355
+ lastError = error instanceof Error ? error : new Error(String(error));
356
+ this.logger.warn('Plugin execution attempt failed', {
357
+ pluginName: plugin.name,
358
+ attempt,
359
+ maxAttempts: retryConfig.maxAttempts,
360
+ error: lastError.message,
361
+ operation: 'plugin_execution_retry',
362
+ });
363
+ const shouldRetry = this.shouldRetryError(lastError, retryConfig);
364
+ if (!shouldRetry || attempt === retryConfig.maxAttempts) {
365
+ break;
366
+ }
367
+ const delay = Math.min(retryConfig.baseDelay * Math.pow(retryConfig.backoffMultiplier, attempt - 1), retryConfig.maxDelay);
368
+ this.logger.debug('Retrying plugin execution after delay', {
369
+ pluginName: plugin.name,
370
+ attempt: attempt + 1,
371
+ delay,
372
+ operation: 'plugin_execution_retry_delay',
373
+ });
374
+ await this.sleep(delay);
375
+ }
376
+ }
377
+ const finalError = lastError ||
378
+ new Error(`Plugin '${plugin.name}' failed with unknown error after ${retryConfig.maxAttempts} attempts`);
379
+ throw new PluginRetryExhaustedError(`Plugin '${plugin.name}' failed after ${retryConfig.maxAttempts} attempts`, plugin.name, retryConfig.maxAttempts, finalError);
380
+ }
381
+ async executePluginAttempt(plugin, manifests, attempt, chartMetadata) {
382
+ const timeout = this.options.timeout || DEFAULT_OPTIONS.timeout;
383
+ this.logger.debug('Executing plugin attempt', {
384
+ pluginName: plugin.name,
385
+ attempt,
386
+ timeout,
387
+ operation: 'plugin_execution_attempt_start',
388
+ });
389
+ const validationContext = {
390
+ chart: chartMetadata,
391
+ config: this.registry.getPluginConfig(plugin.name),
392
+ environment: this.options.environment || 'development',
393
+ logger: this.logger,
394
+ };
395
+ const timeoutPromise = this.createTimeoutPromise(timeout, plugin.name);
396
+ const validationPromise = plugin.validate(manifests, validationContext);
397
+ try {
398
+ const result = await Promise.race([validationPromise, timeoutPromise]);
399
+ this.logger.debug('Plugin execution attempt completed', {
400
+ pluginName: plugin.name,
401
+ attempt,
402
+ violationCount: result.length,
403
+ operation: 'plugin_execution_attempt_complete',
404
+ });
405
+ return result;
406
+ }
407
+ catch (error) {
408
+ if (error instanceof ValidationTimeoutError) {
409
+ this.logger.warn('Plugin execution attempt timed out', {
410
+ pluginName: plugin.name,
411
+ attempt,
412
+ timeout,
413
+ operation: 'plugin_execution_attempt_timeout',
414
+ });
415
+ }
416
+ else {
417
+ this.logger.warn('Plugin execution attempt failed', {
418
+ pluginName: plugin.name,
419
+ attempt,
420
+ error: error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE,
421
+ operation: 'plugin_execution_attempt_error',
422
+ });
423
+ }
424
+ throw error;
425
+ }
426
+ }
427
+ shouldRetryError(error, retryConfig) {
428
+ if (error instanceof ValidationTimeoutError) {
429
+ return retryConfig.retryOnTimeout;
430
+ }
431
+ if (error instanceof PluginError) {
432
+ return retryConfig.retryOnPluginError;
433
+ }
434
+ return retryConfig.retryOnPluginError;
435
+ }
436
+ sleep(ms) {
437
+ return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
438
+ }
439
+ createTimeoutPromise(timeout, pluginName) {
440
+ return new Promise((_, reject) => {
441
+ globalThis.setTimeout(() => {
442
+ reject(new ValidationTimeoutError(pluginName, timeout));
443
+ }, timeout);
444
+ });
445
+ }
446
+ handlePluginError(plugin, error, violations) {
447
+ const errorMessage = error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE;
448
+ const errorStack = error instanceof Error ? error.stack : String(error);
449
+ this.logger.warn('Plugin execution failed with error isolation', {
450
+ pluginName: plugin.name,
451
+ pluginVersion: plugin.version,
452
+ error: errorMessage,
453
+ errorType: error instanceof Error ? error.constructor.name : typeof error,
454
+ operation: 'plugin_execution_error_isolated',
455
+ });
456
+ const errorContext = {
457
+ error: errorStack,
458
+ pluginVersion: plugin.version,
459
+ pluginMetadata: plugin.metadata,
460
+ timestamp: new Date().toISOString(),
461
+ errorType: error instanceof Error ? error.constructor.name : typeof error,
462
+ isolated: true,
463
+ };
464
+ const errorViolation = {
465
+ plugin: plugin.name,
466
+ severity: 'error',
467
+ message: `Plugin execution failed: ${errorMessage}`,
468
+ resourcePath: 'plugin-execution',
469
+ field: 'validate',
470
+ suggestion: this.generateErrorSuggestion(error, plugin),
471
+ context: errorContext,
472
+ };
473
+ violations.push(errorViolation);
474
+ this.logger.debug('Plugin error context generated', {
475
+ pluginName: plugin.name,
476
+ errorContext,
477
+ operation: 'plugin_error_context',
478
+ });
479
+ }
480
+ generateErrorSuggestion(error, plugin) {
481
+ if (error instanceof ValidationTimeoutError) {
482
+ return `Consider increasing the timeout value or optimizing the plugin '${plugin.name}' for better performance.`;
483
+ }
484
+ if (error instanceof PluginRetryExhaustedError) {
485
+ return `Plugin '${plugin.name}' failed after multiple retry attempts. Check plugin configuration and dependencies.`;
486
+ }
487
+ if (error instanceof TypeError) {
488
+ return `Plugin '${plugin.name}' encountered a type error. Verify the plugin implementation and input validation.`;
489
+ }
490
+ if (error instanceof ReferenceError) {
491
+ return `Plugin '${plugin.name}' referenced an undefined variable or function. Check plugin dependencies and imports.`;
492
+ }
493
+ return `Review plugin '${plugin.name}' implementation and ensure it handles all edge cases properly.`;
494
+ }
495
+ }
@@ -0,0 +1,8 @@
1
+ import type { PolicyResult, PolicyViolation, PolicyWarning, ResultSummary } from './types.js';
2
+ export declare function aggregateResults(results: PolicyResult[]): PolicyResult;
3
+ export declare function generateResultSummary(violations: PolicyViolation[], warnings: PolicyWarning[], allPlugins?: string[]): ResultSummary;
4
+ export declare function filterViolationsBySeverity(violations: PolicyViolation[], severity: 'error' | 'warning' | 'info'): PolicyViolation[];
5
+ export declare function groupViolationsByPlugin(violations: PolicyViolation[]): Record<string, PolicyViolation[]>;
6
+ export declare function sortViolationsBySeverity(violations: PolicyViolation[]): PolicyViolation[];
7
+ export declare function createEmptyResult(): PolicyResult;
8
+ export declare function mergeViolationContexts(contexts: Array<Record<string, unknown> | undefined>): Record<string, unknown>;