timonel 3.0.0-beta.1 → 3.1.0-beta.1

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 (52) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/README.md +606 -119
  3. package/SECURITY.md +25 -11
  4. package/dist/cli.js +54 -15
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.js +2 -0
  7. package/dist/lib/helm.js +28 -1
  8. package/dist/lib/helmChartWriter.js +27 -8
  9. package/dist/lib/policy/configurationLoader.d.ts +46 -0
  10. package/dist/lib/policy/configurationLoader.js +251 -0
  11. package/dist/lib/policy/errorContextGenerator.d.ts +63 -0
  12. package/dist/lib/policy/errorContextGenerator.js +302 -0
  13. package/dist/lib/policy/errors.d.ts +40 -0
  14. package/dist/lib/policy/errors.js +109 -0
  15. package/dist/lib/policy/index.d.ts +11 -0
  16. package/dist/lib/policy/index.js +10 -0
  17. package/dist/lib/policy/parallelExecutor.d.ts +58 -0
  18. package/dist/lib/policy/parallelExecutor.js +215 -0
  19. package/dist/lib/policy/pluginLoader.d.ts +41 -0
  20. package/dist/lib/policy/pluginLoader.js +220 -0
  21. package/dist/lib/policy/pluginRegistry.d.ts +14 -0
  22. package/dist/lib/policy/pluginRegistry.js +69 -0
  23. package/dist/lib/policy/policyEngine.d.ts +39 -0
  24. package/dist/lib/policy/policyEngine.js +495 -0
  25. package/dist/lib/policy/resultAggregator.d.ts +8 -0
  26. package/dist/lib/policy/resultAggregator.js +138 -0
  27. package/dist/lib/policy/resultFormatter.d.ts +25 -0
  28. package/dist/lib/policy/resultFormatter.js +217 -0
  29. package/dist/lib/policy/types.d.ts +111 -0
  30. package/dist/lib/policy/types.js +1 -0
  31. package/dist/lib/policy/validationCache.d.ts +58 -0
  32. package/dist/lib/policy/validationCache.js +289 -0
  33. package/dist/lib/resources/baseResourceProvider.js +5 -0
  34. package/dist/lib/resources/cloud/aws/awsResources.js +2 -1
  35. package/dist/lib/resources/cloud/aws/karpenterResources.js +16 -2
  36. package/dist/lib/rutter.d.ts +7 -2
  37. package/dist/lib/rutter.js +177 -8
  38. package/dist/lib/security.js +4 -4
  39. package/dist/lib/templates/flexible-subchart.js +18 -7
  40. package/dist/lib/templates/umbrella-chart.js +29 -18
  41. package/dist/lib/umbrellaRutter.d.ts +1 -1
  42. package/dist/lib/umbrellaRutter.js +21 -9
  43. package/dist/lib/utils/envVarsLoader.js +13 -7
  44. package/dist/lib/utils/helmHelpers.js +10 -2
  45. package/dist/lib/utils/helmYamlSerializer.js +19 -9
  46. package/dist/lib/utils/logger.js +54 -39
  47. package/dist/lib/utils/valuesRef.js +9 -0
  48. package/dist/lib/validation/inputValidator.d.ts +26 -0
  49. package/dist/lib/validation/inputValidator.js +176 -0
  50. package/dist/types/index.d.ts +27 -0
  51. package/dist/types/index.js +1 -0
  52. package/package.json +21 -19
@@ -0,0 +1,215 @@
1
+ import { cpus, totalmem } from 'os';
2
+ import { createLogger } from '../utils/logger.js';
3
+ import { ValidationTimeoutError } from './errors.js';
4
+ const UNKNOWN_ERROR_MESSAGE = 'Unknown error';
5
+ const DEFAULT_PARALLEL_OPTIONS = {
6
+ maxConcurrency: Math.max(2, Math.min(8, cpus().length)),
7
+ useWorkerThreads: false,
8
+ pluginTimeout: 5000,
9
+ failFast: false,
10
+ };
11
+ export class ParallelExecutor {
12
+ constructor(options = {}) {
13
+ this.activeExecutions = 0;
14
+ this.executionQueue = [];
15
+ this.options = { ...DEFAULT_PARALLEL_OPTIONS, ...options };
16
+ this.logger = createLogger('parallel-executor');
17
+ this.logger.debug('ParallelExecutor initialized', {
18
+ maxConcurrency: this.options.maxConcurrency,
19
+ useWorkerThreads: this.options.useWorkerThreads,
20
+ pluginTimeout: this.options.pluginTimeout,
21
+ operation: 'parallel_executor_init',
22
+ });
23
+ }
24
+ async executePlugins(plugins, manifests, validationContext) {
25
+ const startTime = Date.now();
26
+ this.logger.info('Starting parallel plugin execution', {
27
+ pluginCount: plugins.length,
28
+ maxConcurrency: this.options.maxConcurrency,
29
+ manifestCount: manifests.length,
30
+ operation: 'parallel_execution_start',
31
+ });
32
+ const sortedPlugins = this.sortPluginsByPriority(plugins);
33
+ const executionTasks = sortedPlugins.map((plugin) => () => this.executePlugin(plugin, manifests, validationContext));
34
+ const results = await this.executeConcurrently(executionTasks);
35
+ const totalExecutionTime = Date.now() - startTime;
36
+ const stats = this.calculateStats(results, totalExecutionTime);
37
+ this.logger.info('Parallel plugin execution completed', {
38
+ pluginCount: plugins.length,
39
+ totalExecutionTime,
40
+ averagePluginTime: stats.averagePluginTime,
41
+ concurrencyUtilization: stats.concurrencyUtilization,
42
+ operation: 'parallel_execution_complete',
43
+ });
44
+ return { results, stats };
45
+ }
46
+ async executePlugin(plugin, manifests, validationContext) {
47
+ const startTime = Date.now();
48
+ const startMemory = process.memoryUsage().heapUsed;
49
+ this.logger.debug('Starting plugin execution', {
50
+ pluginName: plugin.name,
51
+ pluginVersion: plugin.version,
52
+ operation: 'plugin_execution_start',
53
+ });
54
+ try {
55
+ const violations = await this.executeWithTimeout(plugin, manifests, validationContext, this.options.pluginTimeout);
56
+ const executionTime = Date.now() - startTime;
57
+ const endMemory = process.memoryUsage().heapUsed;
58
+ const memoryUsageMB = Math.max(0, endMemory - startMemory) / (1024 * 1024);
59
+ const result = {
60
+ plugin,
61
+ violations,
62
+ executionTime,
63
+ resourceUsage: {
64
+ memoryUsageMB,
65
+ cpuTimeMs: executionTime,
66
+ },
67
+ };
68
+ this.logger.debug('Plugin execution completed successfully', {
69
+ pluginName: plugin.name,
70
+ executionTime,
71
+ violationCount: violations.length,
72
+ memoryUsageMB: memoryUsageMB.toFixed(2),
73
+ operation: 'plugin_execution_success',
74
+ });
75
+ return result;
76
+ }
77
+ catch (error) {
78
+ const executionTime = Date.now() - startTime;
79
+ const endMemory = process.memoryUsage().heapUsed;
80
+ const memoryUsageMB = Math.max(0, endMemory - startMemory) / (1024 * 1024);
81
+ this.logger.warn('Plugin execution failed', {
82
+ pluginName: plugin.name,
83
+ executionTime,
84
+ error: error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE,
85
+ memoryUsageMB: memoryUsageMB.toFixed(2),
86
+ operation: 'plugin_execution_error',
87
+ });
88
+ return {
89
+ plugin,
90
+ violations: [],
91
+ executionTime,
92
+ error: error instanceof Error ? error : new Error(String(error)),
93
+ resourceUsage: {
94
+ memoryUsageMB,
95
+ cpuTimeMs: executionTime,
96
+ },
97
+ };
98
+ }
99
+ }
100
+ async executeWithTimeout(plugin, manifests, validationContext, timeout) {
101
+ const timeoutPromise = new Promise((_, reject) => {
102
+ globalThis.setTimeout(() => {
103
+ reject(new ValidationTimeoutError(plugin.name, timeout));
104
+ }, timeout);
105
+ });
106
+ const validationPromise = plugin.validate(manifests, validationContext);
107
+ return Promise.race([validationPromise, timeoutPromise]);
108
+ }
109
+ async executeConcurrently(tasks) {
110
+ const results = [];
111
+ const executing = [];
112
+ let taskIndex = 0;
113
+ let peakConcurrency = 0;
114
+ const executeNext = async () => {
115
+ if (taskIndex >= tasks.length) {
116
+ return;
117
+ }
118
+ const currentTaskIndex = taskIndex++;
119
+ const task = tasks[currentTaskIndex];
120
+ if (!task) {
121
+ return;
122
+ }
123
+ this.activeExecutions++;
124
+ peakConcurrency = Math.max(peakConcurrency, this.activeExecutions);
125
+ try {
126
+ const result = await task();
127
+ results[currentTaskIndex] = result;
128
+ if (this.options.failFast && result.error) {
129
+ this.logger.debug('Fail-fast triggered, stopping execution', {
130
+ pluginName: result.plugin.name,
131
+ error: result.error.message,
132
+ operation: 'parallel_execution_fail_fast',
133
+ });
134
+ return;
135
+ }
136
+ }
137
+ catch (error) {
138
+ this.logger.error('Unexpected error in parallel execution', {
139
+ taskIndex: currentTaskIndex,
140
+ error: error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE,
141
+ operation: 'parallel_execution_unexpected_error',
142
+ });
143
+ }
144
+ finally {
145
+ this.activeExecutions--;
146
+ }
147
+ if (!this.options.failFast || !results[currentTaskIndex]?.error) {
148
+ await executeNext();
149
+ }
150
+ };
151
+ const maxConcurrency = this.options.maxConcurrency;
152
+ for (let i = 0; i < Math.min(maxConcurrency, tasks.length); i++) {
153
+ executing.push(executeNext());
154
+ }
155
+ await Promise.all(executing);
156
+ this.logger.debug('Concurrent execution completed', {
157
+ totalTasks: tasks.length,
158
+ completedTasks: results.filter((r) => r).length,
159
+ peakConcurrency,
160
+ operation: 'concurrent_execution_complete',
161
+ });
162
+ return results.filter((r) => r);
163
+ }
164
+ sortPluginsByPriority(plugins) {
165
+ if (!this.options.priorityConfig) {
166
+ return [...plugins];
167
+ }
168
+ const { highPriority = [], lowPriority = [] } = this.options.priorityConfig;
169
+ const highPriorityPlugins = plugins.filter((p) => highPriority.includes(p.name));
170
+ const normalPriorityPlugins = plugins.filter((p) => !highPriority.includes(p.name) && !lowPriority.includes(p.name));
171
+ const lowPriorityPlugins = plugins.filter((p) => lowPriority.includes(p.name));
172
+ const sortedPlugins = [...highPriorityPlugins, ...normalPriorityPlugins, ...lowPriorityPlugins];
173
+ this.logger.debug('Plugins sorted by priority', {
174
+ totalPlugins: plugins.length,
175
+ highPriorityCount: highPriorityPlugins.length,
176
+ normalPriorityCount: normalPriorityPlugins.length,
177
+ lowPriorityCount: lowPriorityPlugins.length,
178
+ operation: 'plugin_priority_sort',
179
+ });
180
+ return sortedPlugins;
181
+ }
182
+ calculateStats(results, totalExecutionTime) {
183
+ const executionTimes = results.map((r) => r.executionTime);
184
+ const averagePluginTime = executionTimes.reduce((sum, time) => sum + time, 0) / results.length;
185
+ const maxPluginTime = Math.max(...executionTimes);
186
+ const totalMemoryMB = results.reduce((sum, r) => sum + (r.resourceUsage?.memoryUsageMB || 0), 0);
187
+ const totalCpuTimeMs = results.reduce((sum, r) => sum + (r.resourceUsage?.cpuTimeMs || 0), 0);
188
+ const theoreticalMinTime = Math.max(...executionTimes);
189
+ const concurrencyUtilization = theoreticalMinTime / totalExecutionTime;
190
+ return {
191
+ totalExecutionTime,
192
+ averagePluginTime,
193
+ maxPluginTime,
194
+ parallelPluginCount: results.length,
195
+ concurrencyUtilization: Math.min(1, concurrencyUtilization),
196
+ resourceUsage: {
197
+ totalMemoryMB,
198
+ totalCpuTimeMs,
199
+ peakConcurrentPlugins: Math.min(this.options.maxConcurrency, results.length),
200
+ },
201
+ };
202
+ }
203
+ }
204
+ export function calculateOptimalConcurrency(pluginCount, systemInfo) {
205
+ const cpuCount = systemInfo?.cpuCount || cpus().length;
206
+ const availableMemoryMB = systemInfo?.availableMemoryMB || totalmem() / (1024 * 1024);
207
+ let optimalConcurrency = Math.max(2, Math.min(cpuCount, pluginCount));
208
+ const memoryBasedConcurrency = Math.floor(availableMemoryMB / 100);
209
+ optimalConcurrency = Math.min(optimalConcurrency, memoryBasedConcurrency);
210
+ if (systemInfo?.isContainerized) {
211
+ optimalConcurrency = Math.max(1, Math.floor(optimalConcurrency * 0.75));
212
+ }
213
+ optimalConcurrency = Math.min(optimalConcurrency, 16);
214
+ return optimalConcurrency;
215
+ }
@@ -0,0 +1,41 @@
1
+ import type { PolicyPlugin, PluginMetadata } from './types.js';
2
+ export interface PluginLoaderOptions {
3
+ baseDirectory?: string;
4
+ environment?: string;
5
+ validateCompatibility?: boolean;
6
+ supportedKubernetesVersions?: string[];
7
+ registryUrl?: string;
8
+ loadTimeout?: number;
9
+ }
10
+ export interface PluginDiscoveryResult {
11
+ packageName: string;
12
+ version: string;
13
+ metadata?: PluginMetadata;
14
+ compatible: boolean;
15
+ compatibilityIssues?: string[];
16
+ source: 'npm' | 'local' | 'registry';
17
+ }
18
+ export interface PluginLoadingResult {
19
+ plugin: PolicyPlugin;
20
+ packageName: string;
21
+ source: 'npm' | 'local' | 'registry';
22
+ loadTime: number;
23
+ validated: boolean;
24
+ warnings?: string[];
25
+ }
26
+ export declare class PluginLoader {
27
+ private readonly options;
28
+ private readonly logger;
29
+ private readonly loadedPlugins;
30
+ constructor(options?: PluginLoaderOptions);
31
+ loadFromPackage(packageName: string, version?: string): Promise<PluginLoadingResult>;
32
+ discoverPlugins(searchPattern?: string): Promise<PluginDiscoveryResult[]>;
33
+ loadMultiplePlugins(packages: string[]): Promise<PluginLoadingResult[]>;
34
+ static validatePluginInterface(plugin: unknown, packageName: string): asserts plugin is PolicyPlugin;
35
+ getLoadedPlugins(): PluginLoadingResult[];
36
+ clearCache(): void;
37
+ private loadModuleWithTimeout;
38
+ private validatePluginInterface;
39
+ private checkCompatibility;
40
+ private shouldLoadInEnvironment;
41
+ }
@@ -0,0 +1,220 @@
1
+ import { createLogger } from '../utils/logger.js';
2
+ import { PluginError, PluginRegistrationError } from './errors.js';
3
+ const UNKNOWN_ERROR_MESSAGE = 'Unknown error';
4
+ const DEFAULT_LOADER_OPTIONS = {
5
+ baseDirectory: process.cwd(),
6
+ environment: 'development',
7
+ validateCompatibility: true,
8
+ supportedKubernetesVersions: ['1.25', '1.26', '1.27', '1.28', '1.29'],
9
+ registryUrl: 'https://registry.npmjs.org',
10
+ loadTimeout: 30000,
11
+ };
12
+ export class PluginLoader {
13
+ constructor(options = {}) {
14
+ this.loadedPlugins = new Map();
15
+ this.options = { ...DEFAULT_LOADER_OPTIONS, ...options };
16
+ this.logger = createLogger('policy-plugin-loader');
17
+ this.logger.debug('PluginLoader initialized', {
18
+ options: this.options,
19
+ operation: 'plugin_loader_init',
20
+ });
21
+ }
22
+ async loadFromPackage(packageName, version) {
23
+ const startTime = Date.now();
24
+ this.logger.info('Loading plugin from package', {
25
+ packageName,
26
+ version,
27
+ operation: 'load_from_package',
28
+ });
29
+ try {
30
+ const cacheKey = `${packageName}@${version || 'latest'}`;
31
+ if (this.loadedPlugins.has(cacheKey)) {
32
+ const cached = this.loadedPlugins.get(cacheKey);
33
+ this.logger.debug('Plugin loaded from cache', {
34
+ packageName,
35
+ cacheKey,
36
+ operation: 'load_from_cache',
37
+ });
38
+ return cached;
39
+ }
40
+ const modulePath = version ? `${packageName}@${version}` : packageName;
41
+ const plugin = await this.loadModuleWithTimeout(modulePath);
42
+ this.validatePluginInterface(plugin, packageName);
43
+ const warnings = [];
44
+ if (this.options.validateCompatibility) {
45
+ const compatibilityIssues = this.checkCompatibility(plugin);
46
+ warnings.push(...compatibilityIssues);
47
+ }
48
+ if (!this.shouldLoadInEnvironment(plugin)) {
49
+ throw new PluginError(`Plugin '${packageName}' is not enabled for environment '${this.options.environment}'`, packageName);
50
+ }
51
+ const loadTime = Date.now() - startTime;
52
+ const result = {
53
+ plugin,
54
+ packageName,
55
+ source: 'npm',
56
+ loadTime,
57
+ validated: true,
58
+ ...(warnings.length > 0 && { warnings }),
59
+ };
60
+ this.loadedPlugins.set(cacheKey, result);
61
+ this.logger.info('Plugin loaded successfully', {
62
+ packageName,
63
+ pluginName: plugin.name,
64
+ pluginVersion: plugin.version,
65
+ loadTime,
66
+ hasWarnings: warnings.length > 0,
67
+ operation: 'plugin_loaded',
68
+ });
69
+ return result;
70
+ }
71
+ catch (error) {
72
+ const loadTime = Date.now() - startTime;
73
+ this.logger.error('Plugin loading failed', {
74
+ packageName,
75
+ version,
76
+ loadTime,
77
+ error: error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE,
78
+ operation: 'plugin_load_failed',
79
+ });
80
+ throw new PluginError(`Failed to load plugin from package '${packageName}': ${error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE}`, packageName, { originalError: error, loadTime });
81
+ }
82
+ }
83
+ async discoverPlugins(searchPattern) {
84
+ this.logger.info('Discovering plugins', {
85
+ searchPattern,
86
+ environment: this.options.environment,
87
+ operation: 'discover_plugins',
88
+ });
89
+ const discovered = [];
90
+ try {
91
+ this.logger.info('Plugin discovery completed', {
92
+ discoveredCount: discovered.length,
93
+ operation: 'discovery_complete',
94
+ });
95
+ return discovered;
96
+ }
97
+ catch (error) {
98
+ this.logger.error('Plugin discovery failed', {
99
+ error: error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE,
100
+ operation: 'discovery_failed',
101
+ });
102
+ throw new PluginError(`Plugin discovery failed: ${error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE}`, 'discovery');
103
+ }
104
+ }
105
+ async loadMultiplePlugins(packages) {
106
+ this.logger.info('Loading multiple plugins', {
107
+ packageCount: packages.length,
108
+ packages,
109
+ operation: 'load_multiple',
110
+ });
111
+ const results = [];
112
+ const errors = [];
113
+ const loadPromises = packages.map(async (pkg) => {
114
+ try {
115
+ const [packageName, version] = pkg.includes('@') && !pkg.startsWith('@') ? pkg.split('@') : [pkg, undefined];
116
+ return await this.loadFromPackage(packageName, version);
117
+ }
118
+ catch (error) {
119
+ errors.push({
120
+ package: pkg,
121
+ error: error instanceof Error ? error : new Error(String(error)),
122
+ });
123
+ return null;
124
+ }
125
+ });
126
+ const loadResults = await Promise.all(loadPromises);
127
+ for (const result of loadResults) {
128
+ if (result) {
129
+ results.push(result);
130
+ }
131
+ }
132
+ if (errors.length > 0) {
133
+ this.logger.warn('Some plugins failed to load', {
134
+ successCount: results.length,
135
+ errorCount: errors.length,
136
+ errors: errors.map((e) => ({ package: e.package, error: e.error.message })),
137
+ operation: 'load_multiple_partial_failure',
138
+ });
139
+ }
140
+ this.logger.info('Multiple plugin loading completed', {
141
+ totalPackages: packages.length,
142
+ successCount: results.length,
143
+ errorCount: errors.length,
144
+ operation: 'load_multiple_complete',
145
+ });
146
+ return results;
147
+ }
148
+ static validatePluginInterface(plugin, packageName) {
149
+ if (!plugin || typeof plugin !== 'object') {
150
+ throw new PluginRegistrationError(`Plugin from package '${packageName}' must be an object`, packageName);
151
+ }
152
+ const p = plugin;
153
+ if (!p.name || typeof p.name !== 'string') {
154
+ throw new PluginRegistrationError(`Plugin from package '${packageName}' must have a non-empty string name`, packageName);
155
+ }
156
+ if (!p.version || typeof p.version !== 'string') {
157
+ throw new PluginRegistrationError(`Plugin from package '${packageName}' must have a non-empty string version`, packageName);
158
+ }
159
+ if (typeof p.validate !== 'function') {
160
+ throw new PluginRegistrationError(`Plugin from package '${packageName}' must implement validate method`, packageName);
161
+ }
162
+ if (p.description !== undefined && typeof p.description !== 'string') {
163
+ throw new PluginRegistrationError(`Plugin from package '${packageName}' description must be a string`, packageName);
164
+ }
165
+ if (p.configSchema !== undefined && typeof p.configSchema !== 'object') {
166
+ throw new PluginRegistrationError(`Plugin from package '${packageName}' configSchema must be an object`, packageName);
167
+ }
168
+ if (p.metadata !== undefined && typeof p.metadata !== 'object') {
169
+ throw new PluginRegistrationError(`Plugin from package '${packageName}' metadata must be an object`, packageName);
170
+ }
171
+ }
172
+ getLoadedPlugins() {
173
+ return Array.from(this.loadedPlugins.values());
174
+ }
175
+ clearCache() {
176
+ this.loadedPlugins.clear();
177
+ this.logger.debug('Plugin cache cleared', {
178
+ operation: 'cache_cleared',
179
+ });
180
+ }
181
+ async loadModuleWithTimeout(modulePath) {
182
+ return new Promise((resolve, reject) => {
183
+ const timeout = globalThis.setTimeout(() => {
184
+ reject(new Error(`Plugin loading timed out after ${this.options.loadTimeout}ms`));
185
+ }, this.options.loadTimeout);
186
+ import(modulePath)
187
+ .then((module) => {
188
+ globalThis.clearTimeout(timeout);
189
+ const plugin = module.default || module;
190
+ if (!plugin) {
191
+ reject(new Error('Plugin module does not export a plugin'));
192
+ return;
193
+ }
194
+ resolve(plugin);
195
+ })
196
+ .catch((error) => {
197
+ globalThis.clearTimeout(timeout);
198
+ reject(error);
199
+ });
200
+ });
201
+ }
202
+ validatePluginInterface(plugin, packageName) {
203
+ PluginLoader.validatePluginInterface(plugin, packageName);
204
+ }
205
+ checkCompatibility(plugin) {
206
+ const issues = [];
207
+ if (plugin.metadata?.kubernetesVersions) {
208
+ const supportedVersions = new Set(this.options.supportedKubernetesVersions);
209
+ const pluginVersions = plugin.metadata.kubernetesVersions;
210
+ const hasCompatibleVersion = pluginVersions.some((version) => supportedVersions.has(version));
211
+ if (!hasCompatibleVersion) {
212
+ issues.push(`Plugin requires Kubernetes versions [${pluginVersions.join(', ')}] but only [${this.options.supportedKubernetesVersions.join(', ')}] are supported`);
213
+ }
214
+ }
215
+ return issues;
216
+ }
217
+ shouldLoadInEnvironment(_plugin) {
218
+ return true;
219
+ }
220
+ }
@@ -0,0 +1,14 @@
1
+ import type { PolicyPlugin } from './types.js';
2
+ export declare class PluginRegistry {
3
+ private plugins;
4
+ private pluginConfigs;
5
+ register(plugin: PolicyPlugin, config?: unknown): void;
6
+ getPlugin(name: string): PolicyPlugin | undefined;
7
+ getAllPlugins(): PolicyPlugin[];
8
+ getPluginConfig(pluginName: string): unknown;
9
+ hasPlugin(name: string): boolean;
10
+ getPluginCount(): number;
11
+ unregister(name: string): boolean;
12
+ clear(): void;
13
+ private validatePlugin;
14
+ }
@@ -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
+ }