@trentapps/manager-protocol 1.3.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/LICENSE +21 -0
- package/README.md +639 -0
- package/dist/analyzers/ArchitectureDetector.d.ts +44 -0
- package/dist/analyzers/ArchitectureDetector.d.ts.map +1 -0
- package/dist/analyzers/ArchitectureDetector.js +218 -0
- package/dist/analyzers/ArchitectureDetector.js.map +1 -0
- package/dist/analyzers/CSSAnalyzer.d.ts +284 -0
- package/dist/analyzers/CSSAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/CSSAnalyzer.js +1180 -0
- package/dist/analyzers/CSSAnalyzer.js.map +1 -0
- package/dist/analyzers/index.d.ts +5 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +5 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +174 -0
- package/dist/cli.js.map +1 -0
- package/dist/design-system/index.d.ts +6 -0
- package/dist/design-system/index.d.ts.map +1 -0
- package/dist/design-system/index.js +6 -0
- package/dist/design-system/index.js.map +1 -0
- package/dist/design-system/tokens.d.ts +106 -0
- package/dist/design-system/tokens.d.ts.map +1 -0
- package/dist/design-system/tokens.js +554 -0
- package/dist/design-system/tokens.js.map +1 -0
- package/dist/engine/AuditLogger.d.ts +506 -0
- package/dist/engine/AuditLogger.d.ts.map +1 -0
- package/dist/engine/AuditLogger.js +1491 -0
- package/dist/engine/AuditLogger.js.map +1 -0
- package/dist/engine/GitHubApprovalManager.d.ts +123 -0
- package/dist/engine/GitHubApprovalManager.d.ts.map +1 -0
- package/dist/engine/GitHubApprovalManager.js +347 -0
- package/dist/engine/GitHubApprovalManager.js.map +1 -0
- package/dist/engine/GitHubClient.d.ts +183 -0
- package/dist/engine/GitHubClient.d.ts.map +1 -0
- package/dist/engine/GitHubClient.js +411 -0
- package/dist/engine/GitHubClient.js.map +1 -0
- package/dist/engine/RateLimiter.d.ts +81 -0
- package/dist/engine/RateLimiter.d.ts.map +1 -0
- package/dist/engine/RateLimiter.js +215 -0
- package/dist/engine/RateLimiter.js.map +1 -0
- package/dist/engine/RuleDependencyAnalyzer.d.ts +73 -0
- package/dist/engine/RuleDependencyAnalyzer.d.ts.map +1 -0
- package/dist/engine/RuleDependencyAnalyzer.js +475 -0
- package/dist/engine/RuleDependencyAnalyzer.js.map +1 -0
- package/dist/engine/RulesEngine.d.ts +176 -0
- package/dist/engine/RulesEngine.d.ts.map +1 -0
- package/dist/engine/RulesEngine.js +705 -0
- package/dist/engine/RulesEngine.js.map +1 -0
- package/dist/engine/TaskManager.d.ts +174 -0
- package/dist/engine/TaskManager.d.ts.map +1 -0
- package/dist/engine/TaskManager.js +663 -0
- package/dist/engine/TaskManager.js.map +1 -0
- package/dist/engine/index.d.ts +11 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +13 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/architecture.d.ts +9 -0
- package/dist/rules/architecture.d.ts.map +1 -0
- package/dist/rules/architecture.js +322 -0
- package/dist/rules/architecture.js.map +1 -0
- package/dist/rules/azure.d.ts +7 -0
- package/dist/rules/azure.d.ts.map +1 -0
- package/dist/rules/azure.js +136 -0
- package/dist/rules/azure.js.map +1 -0
- package/dist/rules/compliance.d.ts +9 -0
- package/dist/rules/compliance.d.ts.map +1 -0
- package/dist/rules/compliance.js +286 -0
- package/dist/rules/compliance.js.map +1 -0
- package/dist/rules/condition-optimizer.d.ts +151 -0
- package/dist/rules/condition-optimizer.d.ts.map +1 -0
- package/dist/rules/condition-optimizer.js +479 -0
- package/dist/rules/condition-optimizer.js.map +1 -0
- package/dist/rules/css.d.ts +10 -0
- package/dist/rules/css.d.ts.map +1 -0
- package/dist/rules/css.js +1777 -0
- package/dist/rules/css.js.map +1 -0
- package/dist/rules/field-standards.d.ts +1172 -0
- package/dist/rules/field-standards.d.ts.map +1 -0
- package/dist/rules/field-standards.js +908 -0
- package/dist/rules/field-standards.js.map +1 -0
- package/dist/rules/flask.d.ts +7 -0
- package/dist/rules/flask.d.ts.map +1 -0
- package/dist/rules/flask.js +142 -0
- package/dist/rules/flask.js.map +1 -0
- package/dist/rules/index.d.ts +827 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +556 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/ml-ai.d.ts +7 -0
- package/dist/rules/ml-ai.d.ts.map +1 -0
- package/dist/rules/ml-ai.js +148 -0
- package/dist/rules/ml-ai.js.map +1 -0
- package/dist/rules/operational.d.ts +9 -0
- package/dist/rules/operational.d.ts.map +1 -0
- package/dist/rules/operational.js +318 -0
- package/dist/rules/operational.js.map +1 -0
- package/dist/rules/patterns.d.ts +568 -0
- package/dist/rules/patterns.d.ts.map +1 -0
- package/dist/rules/patterns.js +1359 -0
- package/dist/rules/patterns.js.map +1 -0
- package/dist/rules/security.d.ts +9 -0
- package/dist/rules/security.d.ts.map +1 -0
- package/dist/rules/security.js +848 -0
- package/dist/rules/security.js.map +1 -0
- package/dist/rules/shared-patterns.d.ts +268 -0
- package/dist/rules/shared-patterns.d.ts.map +1 -0
- package/dist/rules/shared-patterns.js +556 -0
- package/dist/rules/shared-patterns.js.map +1 -0
- package/dist/rules/storage.d.ts +13 -0
- package/dist/rules/storage.d.ts.map +1 -0
- package/dist/rules/storage.js +672 -0
- package/dist/rules/storage.js.map +1 -0
- package/dist/rules/stripe.d.ts +7 -0
- package/dist/rules/stripe.d.ts.map +1 -0
- package/dist/rules/stripe.js +133 -0
- package/dist/rules/stripe.js.map +1 -0
- package/dist/rules/testing.d.ts +7 -0
- package/dist/rules/testing.d.ts.map +1 -0
- package/dist/rules/testing.js +135 -0
- package/dist/rules/testing.js.map +1 -0
- package/dist/rules/ux.d.ts +9 -0
- package/dist/rules/ux.d.ts.map +1 -0
- package/dist/rules/ux.js +280 -0
- package/dist/rules/ux.js.map +1 -0
- package/dist/rules/websocket.d.ts +7 -0
- package/dist/rules/websocket.d.ts.map +1 -0
- package/dist/rules/websocket.js +128 -0
- package/dist/rules/websocket.js.map +1 -0
- package/dist/server.d.ts +43 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1967 -0
- package/dist/server.js.map +1 -0
- package/dist/supervisor/AgentSupervisor.d.ts +195 -0
- package/dist/supervisor/AgentSupervisor.d.ts.map +1 -0
- package/dist/supervisor/AgentSupervisor.js +569 -0
- package/dist/supervisor/AgentSupervisor.js.map +1 -0
- package/dist/supervisor/ManagedServerRegistry.d.ts +185 -0
- package/dist/supervisor/ManagedServerRegistry.d.ts.map +1 -0
- package/dist/supervisor/ManagedServerRegistry.js +729 -0
- package/dist/supervisor/ManagedServerRegistry.js.map +1 -0
- package/dist/supervisor/ProjectTracker.d.ts +210 -0
- package/dist/supervisor/ProjectTracker.d.ts.map +1 -0
- package/dist/supervisor/ProjectTracker.js +709 -0
- package/dist/supervisor/ProjectTracker.js.map +1 -0
- package/dist/supervisor/index.d.ts +6 -0
- package/dist/supervisor/index.d.ts.map +1 -0
- package/dist/supervisor/index.js +6 -0
- package/dist/supervisor/index.js.map +1 -0
- package/dist/testing/index.d.ts +11 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +12 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/rule-tester.d.ts +217 -0
- package/dist/testing/rule-tester.d.ts.map +1 -0
- package/dist/testing/rule-tester.examples.d.ts +57 -0
- package/dist/testing/rule-tester.examples.d.ts.map +1 -0
- package/dist/testing/rule-tester.examples.js +375 -0
- package/dist/testing/rule-tester.examples.js.map +1 -0
- package/dist/testing/rule-tester.js +381 -0
- package/dist/testing/rule-tester.js.map +1 -0
- package/dist/testing/rule-validator.d.ts +141 -0
- package/dist/testing/rule-validator.d.ts.map +1 -0
- package/dist/testing/rule-validator.js +640 -0
- package/dist/testing/rule-validator.js.map +1 -0
- package/dist/types/index.d.ts +1282 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +386 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/errors.d.ts +86 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +171 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/rate-limiting.d.ts +268 -0
- package/dist/utils/rate-limiting.d.ts.map +1 -0
- package/dist/utils/rate-limiting.js +403 -0
- package/dist/utils/rate-limiting.js.map +1 -0
- package/dist/utils/shared.d.ts +306 -0
- package/dist/utils/shared.d.ts.map +1 -0
- package/dist/utils/shared.js +464 -0
- package/dist/utils/shared.js.map +1 -0
- package/dist/utils/shell.d.ts +22 -0
- package/dist/utils/shell.d.ts.map +1 -0
- package/dist/utils/shell.js +29 -0
- package/dist/utils/shell.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Agent Supervisor - Rules Engine Core
|
|
3
|
+
*
|
|
4
|
+
* The heart of the governance system that evaluates actions against business rules.
|
|
5
|
+
*/
|
|
6
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
7
|
+
import { RuleDependencyAnalyzer } from './RuleDependencyAnalyzer.js';
|
|
8
|
+
/**
|
|
9
|
+
* Task #34: Condition evaluation cost weights for optimization
|
|
10
|
+
* Lower numbers = faster operations, should be evaluated first
|
|
11
|
+
*/
|
|
12
|
+
const CONDITION_COST_WEIGHTS = {
|
|
13
|
+
exists: 1,
|
|
14
|
+
not_exists: 1,
|
|
15
|
+
equals: 2,
|
|
16
|
+
not_equals: 2,
|
|
17
|
+
in: 3,
|
|
18
|
+
not_in: 3,
|
|
19
|
+
greater_than: 4,
|
|
20
|
+
less_than: 4,
|
|
21
|
+
contains: 5,
|
|
22
|
+
not_contains: 5,
|
|
23
|
+
matches_regex: 10, // Expensive - evaluate last
|
|
24
|
+
custom: 8 // Custom evaluators can be expensive
|
|
25
|
+
};
|
|
26
|
+
export class RulesEngine {
|
|
27
|
+
rules = new Map();
|
|
28
|
+
customEvaluators = new Map();
|
|
29
|
+
// Task #53: Cache for compiled evaluators - keyed by evaluator name/string representation
|
|
30
|
+
evaluatorCache = new Map();
|
|
31
|
+
// Task #34: Cache for compiled RegExp objects - keyed by pattern string
|
|
32
|
+
regexCache = new Map();
|
|
33
|
+
// Task #34: Cache for optimized 'in' operator Sets - keyed by JSON.stringify of value array
|
|
34
|
+
inOperatorSetCache = new Map();
|
|
35
|
+
// Task #37: Dependency analyzer for rule ordering
|
|
36
|
+
dependencyAnalyzer;
|
|
37
|
+
// Task #37: Cache for dependency-sorted execution order
|
|
38
|
+
executionOrderCache = null;
|
|
39
|
+
// Task #37: Whether to respect rule dependencies in evaluation order
|
|
40
|
+
respectDependencies = true;
|
|
41
|
+
constructor(options) {
|
|
42
|
+
this.dependencyAnalyzer = new RuleDependencyAnalyzer();
|
|
43
|
+
this.respectDependencies = options?.respectDependencies ?? true;
|
|
44
|
+
this.registerDefaultEvaluators();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Register a business rule
|
|
48
|
+
* Task #53: Clear evaluator cache when rules change to ensure fresh compilation
|
|
49
|
+
*/
|
|
50
|
+
registerRule(rule) {
|
|
51
|
+
this.rules.set(rule.id, rule);
|
|
52
|
+
this.clearEvaluatorCache();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Register multiple business rules
|
|
56
|
+
*/
|
|
57
|
+
registerRules(rules) {
|
|
58
|
+
for (const rule of rules) {
|
|
59
|
+
this.registerRule(rule);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Unregister a rule by ID
|
|
64
|
+
* Task #53: Clear evaluator cache when rules change
|
|
65
|
+
*/
|
|
66
|
+
unregisterRule(ruleId) {
|
|
67
|
+
const result = this.rules.delete(ruleId);
|
|
68
|
+
if (result) {
|
|
69
|
+
this.clearEvaluatorCache();
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Task #53: Clear the evaluator cache
|
|
75
|
+
* Task #34: Also clear regex and 'in' operator caches
|
|
76
|
+
* Task #37: Also clear execution order cache
|
|
77
|
+
* Called when rules are modified to ensure fresh compilation
|
|
78
|
+
*/
|
|
79
|
+
clearEvaluatorCache() {
|
|
80
|
+
this.evaluatorCache.clear();
|
|
81
|
+
this.regexCache.clear();
|
|
82
|
+
this.inOperatorSetCache.clear();
|
|
83
|
+
this.executionOrderCache = null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get all registered rules
|
|
87
|
+
*/
|
|
88
|
+
getRules() {
|
|
89
|
+
return Array.from(this.rules.values());
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get enabled rules sorted by priority (higher priority first)
|
|
93
|
+
* Task #39: In strict mode, filters out deprecated rules
|
|
94
|
+
* Task #37: When respectDependencies is true, returns rules in dependency-aware order
|
|
95
|
+
*/
|
|
96
|
+
getActiveRules(strictMode = false) {
|
|
97
|
+
const enabledRules = this.getRules()
|
|
98
|
+
.filter(rule => rule.enabled)
|
|
99
|
+
.filter(rule => !strictMode || !rule.deprecated);
|
|
100
|
+
// Task #37: Return rules in dependency order if enabled
|
|
101
|
+
if (this.respectDependencies) {
|
|
102
|
+
return this.getDependencySortedRules(enabledRules);
|
|
103
|
+
}
|
|
104
|
+
// Default: sort by priority (higher priority first)
|
|
105
|
+
return enabledRules.sort((a, b) => b.priority - a.priority);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Task #37: Get rules sorted by dependency order (dependencies first, then by priority)
|
|
109
|
+
*/
|
|
110
|
+
getDependencySortedRules(rules) {
|
|
111
|
+
// Use cached execution order if available
|
|
112
|
+
if (this.executionOrderCache) {
|
|
113
|
+
const ruleMap = new Map();
|
|
114
|
+
for (const rule of rules) {
|
|
115
|
+
ruleMap.set(rule.id, rule);
|
|
116
|
+
}
|
|
117
|
+
const orderedRules = [];
|
|
118
|
+
for (const ruleId of this.executionOrderCache) {
|
|
119
|
+
const rule = ruleMap.get(ruleId);
|
|
120
|
+
if (rule) {
|
|
121
|
+
orderedRules.push(rule);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Add any rules not in cache (shouldn't happen, but safety)
|
|
125
|
+
const cachedIds = new Set(this.executionOrderCache);
|
|
126
|
+
for (const rule of rules) {
|
|
127
|
+
if (!cachedIds.has(rule.id)) {
|
|
128
|
+
orderedRules.push(rule);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return orderedRules;
|
|
132
|
+
}
|
|
133
|
+
// Calculate and cache execution order
|
|
134
|
+
const sortedRules = this.dependencyAnalyzer.getSortedRulesForExecution(rules);
|
|
135
|
+
this.executionOrderCache = sortedRules.map(r => r.id);
|
|
136
|
+
return sortedRules;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Task #39: Get all deprecated rules
|
|
140
|
+
*/
|
|
141
|
+
getDeprecatedRules() {
|
|
142
|
+
return this.getRules().filter(rule => rule.deprecated === true);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Task #39: Analyze rules and suggest migrations for deprecated rules
|
|
146
|
+
* Returns migration suggestions with replacement rule information
|
|
147
|
+
*/
|
|
148
|
+
migrateRules(rules) {
|
|
149
|
+
const migrations = [];
|
|
150
|
+
for (const rule of rules) {
|
|
151
|
+
if (rule.deprecated) {
|
|
152
|
+
const replacementRule = rule.replacedBy ? this.rules.get(rule.replacedBy) : undefined;
|
|
153
|
+
let suggestion = `Rule '${rule.name}' (${rule.id}) is deprecated.`;
|
|
154
|
+
if (rule.deprecatedMessage) {
|
|
155
|
+
suggestion += ` ${rule.deprecatedMessage}`;
|
|
156
|
+
}
|
|
157
|
+
if (rule.replacedBy) {
|
|
158
|
+
if (replacementRule) {
|
|
159
|
+
suggestion += ` Migrate to '${replacementRule.name}' (${rule.replacedBy}).`;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
suggestion += ` Recommended replacement: ${rule.replacedBy} (not found in current rules).`;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
migrations.push({
|
|
166
|
+
ruleId: rule.id,
|
|
167
|
+
ruleName: rule.name,
|
|
168
|
+
deprecatedMessage: rule.deprecatedMessage,
|
|
169
|
+
replacedBy: rule.replacedBy,
|
|
170
|
+
replacementRule,
|
|
171
|
+
suggestion
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return migrations;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Task #39: Check if a rule is compatible with the current supervisor version
|
|
179
|
+
*/
|
|
180
|
+
isRuleCompatible(rule, currentVersion) {
|
|
181
|
+
if (!rule.minVersion) {
|
|
182
|
+
return true; // No version requirement
|
|
183
|
+
}
|
|
184
|
+
return this.compareVersions(currentVersion, rule.minVersion) >= 0;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Task #37: Validate all rule dependencies
|
|
188
|
+
* Returns validation result with errors and warnings
|
|
189
|
+
*/
|
|
190
|
+
validateDependencies() {
|
|
191
|
+
return this.dependencyAnalyzer.validateDependencies(this.getRules());
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Task #37: Get the dependency analyzer for advanced analysis
|
|
195
|
+
*/
|
|
196
|
+
getDependencyAnalyzer() {
|
|
197
|
+
return this.dependencyAnalyzer;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Task #37: Enable or disable dependency-aware rule ordering
|
|
201
|
+
*/
|
|
202
|
+
setRespectDependencies(enabled) {
|
|
203
|
+
if (this.respectDependencies !== enabled) {
|
|
204
|
+
this.respectDependencies = enabled;
|
|
205
|
+
this.executionOrderCache = null; // Clear cache when toggling
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Task #37: Check if dependency-aware ordering is enabled
|
|
210
|
+
*/
|
|
211
|
+
isRespectingDependencies() {
|
|
212
|
+
return this.respectDependencies;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Task #37: Get rules that depend on a specific rule
|
|
216
|
+
*/
|
|
217
|
+
getRuleDependents(ruleId) {
|
|
218
|
+
return this.dependencyAnalyzer.getDependents(ruleId, this.getRules());
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Task #37: Get rules that a specific rule depends on
|
|
222
|
+
*/
|
|
223
|
+
getRuleDependencies(ruleId) {
|
|
224
|
+
return this.dependencyAnalyzer.getDependencies(ruleId, this.getRules());
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Task #37: Get all rules that would be affected if a rule is disabled
|
|
228
|
+
*/
|
|
229
|
+
getRulesAffectedByDisabling(ruleId) {
|
|
230
|
+
return this.dependencyAnalyzer.getAffectedByDisabling(ruleId, this.getRules());
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Task #39: Compare semantic versions
|
|
234
|
+
* Returns: 1 if a > b, -1 if a < b, 0 if equal
|
|
235
|
+
*/
|
|
236
|
+
compareVersions(a, b) {
|
|
237
|
+
const partsA = a.split('.').map(Number);
|
|
238
|
+
const partsB = b.split('.').map(Number);
|
|
239
|
+
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
240
|
+
const numA = partsA[i] || 0;
|
|
241
|
+
const numB = partsB[i] || 0;
|
|
242
|
+
if (numA > numB)
|
|
243
|
+
return 1;
|
|
244
|
+
if (numA < numB)
|
|
245
|
+
return -1;
|
|
246
|
+
}
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Task #39: Warn about deprecated rule usage
|
|
251
|
+
*/
|
|
252
|
+
warnDeprecatedRule(rule) {
|
|
253
|
+
let message = `[DEPRECATED] Rule '${rule.name}' (${rule.id}) is deprecated.`;
|
|
254
|
+
if (rule.deprecatedMessage) {
|
|
255
|
+
message += ` ${rule.deprecatedMessage}`;
|
|
256
|
+
}
|
|
257
|
+
if (rule.replacedBy) {
|
|
258
|
+
message += ` Consider migrating to: ${rule.replacedBy}`;
|
|
259
|
+
}
|
|
260
|
+
console.warn(message);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Register a custom condition evaluator
|
|
264
|
+
* Task #53: Clear evaluator cache when custom evaluators change
|
|
265
|
+
*/
|
|
266
|
+
registerCustomEvaluator(name, evaluator) {
|
|
267
|
+
this.customEvaluators.set(name, evaluator);
|
|
268
|
+
this.clearEvaluatorCache();
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Evaluate an agent action against all active rules
|
|
272
|
+
*/
|
|
273
|
+
evaluateAction(action, context) {
|
|
274
|
+
const actionId = action.id || uuidv4();
|
|
275
|
+
const violations = [];
|
|
276
|
+
const warnings = [];
|
|
277
|
+
const appliedRules = [];
|
|
278
|
+
let totalRiskWeight = 0;
|
|
279
|
+
let requiresHumanApproval = false;
|
|
280
|
+
let approvalReason;
|
|
281
|
+
let isDenied = false;
|
|
282
|
+
let isRateLimited = false;
|
|
283
|
+
// Build evaluation context
|
|
284
|
+
const evalContext = this.buildEvaluationContext(action, context);
|
|
285
|
+
// Get active rules and evaluate
|
|
286
|
+
const activeRules = this.getActiveRules();
|
|
287
|
+
// Task #57: Track total priority weight for normalization
|
|
288
|
+
let totalPriorityWeight = 0;
|
|
289
|
+
let weightedRiskSum = 0;
|
|
290
|
+
for (const rule of activeRules) {
|
|
291
|
+
const ruleMatches = this.evaluateRuleConditions(rule, evalContext);
|
|
292
|
+
if (ruleMatches) {
|
|
293
|
+
appliedRules.push(rule.id);
|
|
294
|
+
// Task #39: Warn when deprecated rules are triggered
|
|
295
|
+
if (rule.deprecated) {
|
|
296
|
+
this.warnDeprecatedRule(rule);
|
|
297
|
+
}
|
|
298
|
+
// Task #57: Weight risk by rule priority (0-1000 scale)
|
|
299
|
+
// Higher priority rules have more impact on final score
|
|
300
|
+
const priorityMultiplier = (rule.priority + 100) / 100; // Ensure minimum weight of 1
|
|
301
|
+
const weightedRisk = rule.riskWeight * priorityMultiplier;
|
|
302
|
+
weightedRiskSum += weightedRisk;
|
|
303
|
+
totalPriorityWeight += priorityMultiplier;
|
|
304
|
+
totalRiskWeight += rule.riskWeight;
|
|
305
|
+
// Process rule actions
|
|
306
|
+
for (const ruleAction of rule.actions) {
|
|
307
|
+
switch (ruleAction.type) {
|
|
308
|
+
case 'deny':
|
|
309
|
+
isDenied = true;
|
|
310
|
+
violations.push({
|
|
311
|
+
ruleId: rule.id,
|
|
312
|
+
ruleName: rule.name,
|
|
313
|
+
severity: this.getRuleSeverity(rule),
|
|
314
|
+
message: ruleAction.message || `Action denied by rule: ${rule.name}`,
|
|
315
|
+
recommendation: this.getRecommendation(rule, ruleAction)
|
|
316
|
+
});
|
|
317
|
+
break;
|
|
318
|
+
case 'require_approval':
|
|
319
|
+
requiresHumanApproval = true;
|
|
320
|
+
approvalReason = ruleAction.message || `Requires approval due to rule: ${rule.name}`;
|
|
321
|
+
break;
|
|
322
|
+
case 'warn':
|
|
323
|
+
warnings.push(ruleAction.message || `Warning from rule: ${rule.name}`);
|
|
324
|
+
break;
|
|
325
|
+
case 'rate_limit':
|
|
326
|
+
isRateLimited = true;
|
|
327
|
+
break;
|
|
328
|
+
case 'escalate':
|
|
329
|
+
requiresHumanApproval = true;
|
|
330
|
+
approvalReason = `Escalated: ${ruleAction.message || rule.name}`;
|
|
331
|
+
break;
|
|
332
|
+
default:
|
|
333
|
+
// Allow, log, transform, notify - don't affect approval status
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// Task #57: Calculate risk score using priority-weighted average (0-100)
|
|
340
|
+
// If no rules matched, use raw total; otherwise use weighted calculation
|
|
341
|
+
const riskScore = totalPriorityWeight > 0
|
|
342
|
+
? Math.min(100, weightedRiskSum / totalPriorityWeight)
|
|
343
|
+
: Math.min(100, totalRiskWeight);
|
|
344
|
+
const riskLevel = this.calculateRiskLevel(riskScore);
|
|
345
|
+
// Determine final status
|
|
346
|
+
let status;
|
|
347
|
+
if (isDenied) {
|
|
348
|
+
status = 'denied';
|
|
349
|
+
}
|
|
350
|
+
else if (isRateLimited) {
|
|
351
|
+
status = 'rate_limited';
|
|
352
|
+
}
|
|
353
|
+
else if (requiresHumanApproval) {
|
|
354
|
+
status = 'pending_approval';
|
|
355
|
+
}
|
|
356
|
+
else if (warnings.length > 0) {
|
|
357
|
+
status = 'requires_review';
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
status = 'approved';
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
actionId,
|
|
364
|
+
status,
|
|
365
|
+
riskScore,
|
|
366
|
+
riskLevel,
|
|
367
|
+
allowed: !isDenied && !isRateLimited,
|
|
368
|
+
violations,
|
|
369
|
+
warnings,
|
|
370
|
+
appliedRules,
|
|
371
|
+
requiresHumanApproval,
|
|
372
|
+
approvalReason,
|
|
373
|
+
rateLimitInfo: isRateLimited ? { limited: true } : undefined,
|
|
374
|
+
evaluatedAt: new Date().toISOString(),
|
|
375
|
+
metadata: {
|
|
376
|
+
action: action.name,
|
|
377
|
+
category: action.category,
|
|
378
|
+
rulesEvaluated: activeRules.length
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Apply business rules to a context and return recommendations
|
|
384
|
+
*/
|
|
385
|
+
applyBusinessRules(context) {
|
|
386
|
+
const contextId = uuidv4();
|
|
387
|
+
const rulesApplied = [];
|
|
388
|
+
const recommendations = [];
|
|
389
|
+
const constraints = [];
|
|
390
|
+
let totalRiskScore = 0;
|
|
391
|
+
const evalContext = context.customAttributes || {};
|
|
392
|
+
const activeRules = this.getActiveRules();
|
|
393
|
+
for (const rule of activeRules) {
|
|
394
|
+
const matched = this.evaluateRuleConditions(rule, {
|
|
395
|
+
...evalContext,
|
|
396
|
+
environment: context.environment,
|
|
397
|
+
userRole: context.userRole,
|
|
398
|
+
dataClassification: context.dataClassification,
|
|
399
|
+
department: context.department
|
|
400
|
+
});
|
|
401
|
+
rulesApplied.push({
|
|
402
|
+
ruleId: rule.id,
|
|
403
|
+
ruleName: rule.name,
|
|
404
|
+
matched,
|
|
405
|
+
actions: matched ? rule.actions : []
|
|
406
|
+
});
|
|
407
|
+
if (matched) {
|
|
408
|
+
totalRiskScore += rule.riskWeight;
|
|
409
|
+
// Generate recommendations and constraints
|
|
410
|
+
for (const action of rule.actions) {
|
|
411
|
+
if (action.type === 'deny') {
|
|
412
|
+
constraints.push({
|
|
413
|
+
type: 'prohibition',
|
|
414
|
+
description: action.message || `Prohibited by ${rule.name}`,
|
|
415
|
+
enforced: true
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
else if (action.type === 'require_approval') {
|
|
419
|
+
constraints.push({
|
|
420
|
+
type: 'approval_required',
|
|
421
|
+
description: action.message || `Requires approval per ${rule.name}`,
|
|
422
|
+
enforced: true
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
else if (action.type === 'warn') {
|
|
426
|
+
recommendations.push(action.message || `Consider: ${rule.name}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
contextId,
|
|
433
|
+
rulesApplied,
|
|
434
|
+
aggregateRiskScore: Math.min(100, totalRiskScore),
|
|
435
|
+
recommendations,
|
|
436
|
+
constraints,
|
|
437
|
+
processedAt: new Date().toISOString()
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Build evaluation context from action and business context
|
|
442
|
+
*/
|
|
443
|
+
buildEvaluationContext(action, context) {
|
|
444
|
+
return {
|
|
445
|
+
// Action fields
|
|
446
|
+
actionName: action.name,
|
|
447
|
+
actionCategory: action.category,
|
|
448
|
+
actionDescription: action.description,
|
|
449
|
+
...action.parameters,
|
|
450
|
+
...action.metadata,
|
|
451
|
+
// Context fields
|
|
452
|
+
environment: context?.environment,
|
|
453
|
+
agentId: context?.agentId || action.agentId,
|
|
454
|
+
agentType: context?.agentType,
|
|
455
|
+
userId: context?.userId,
|
|
456
|
+
userRole: context?.userRole,
|
|
457
|
+
sessionId: context?.sessionId || action.sessionId,
|
|
458
|
+
organizationId: context?.organizationId,
|
|
459
|
+
department: context?.department,
|
|
460
|
+
costCenter: context?.costCenter,
|
|
461
|
+
dataClassification: context?.dataClassification,
|
|
462
|
+
complianceFrameworks: context?.complianceFrameworks,
|
|
463
|
+
...context?.customAttributes
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Task #34: Sort conditions by evaluation cost for optimal short-circuit evaluation
|
|
468
|
+
* Cheaper operations (exists, equals) go first, expensive ones (regex, custom) last
|
|
469
|
+
*/
|
|
470
|
+
sortConditionsByEvaluationCost(conditions) {
|
|
471
|
+
return [...conditions].sort((a, b) => {
|
|
472
|
+
const costA = CONDITION_COST_WEIGHTS[a.operator] ?? 5;
|
|
473
|
+
const costB = CONDITION_COST_WEIGHTS[b.operator] ?? 5;
|
|
474
|
+
return costA - costB;
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Evaluate if all/any conditions of a rule match
|
|
479
|
+
* Task #34: Optimized with condition sorting for short-circuit evaluation
|
|
480
|
+
*/
|
|
481
|
+
evaluateRuleConditions(rule, context) {
|
|
482
|
+
if (rule.conditions.length === 0) {
|
|
483
|
+
return true; // No conditions means always match
|
|
484
|
+
}
|
|
485
|
+
// Task #34: Sort conditions by evaluation cost for optimal short-circuit behavior
|
|
486
|
+
const sortedConditions = this.sortConditionsByEvaluationCost(rule.conditions);
|
|
487
|
+
if (rule.conditionLogic === 'any') {
|
|
488
|
+
// For 'any' logic, evaluate until first true (cheaper conditions first)
|
|
489
|
+
for (const condition of sortedConditions) {
|
|
490
|
+
if (this.evaluateCondition(condition, context)) {
|
|
491
|
+
return true; // Short-circuit: found a match
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
// For 'all' logic (default), evaluate until first false (cheaper conditions first)
|
|
497
|
+
for (const condition of sortedConditions) {
|
|
498
|
+
if (!this.evaluateCondition(condition, context)) {
|
|
499
|
+
return false; // Short-circuit: found a non-match
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Task #34: Get cached RegExp or compile and cache it
|
|
506
|
+
*/
|
|
507
|
+
getCachedRegex(pattern) {
|
|
508
|
+
let regex = this.regexCache.get(pattern);
|
|
509
|
+
if (!regex) {
|
|
510
|
+
try {
|
|
511
|
+
regex = new RegExp(pattern);
|
|
512
|
+
this.regexCache.set(pattern, regex);
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return regex;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Task #34: Get cached Set for 'in' operator or create and cache it
|
|
522
|
+
* Using Set provides O(1) lookup instead of O(n) array.includes()
|
|
523
|
+
*/
|
|
524
|
+
getCachedInOperatorSet(values) {
|
|
525
|
+
const cacheKey = JSON.stringify(values);
|
|
526
|
+
let valueSet = this.inOperatorSetCache.get(cacheKey);
|
|
527
|
+
if (!valueSet) {
|
|
528
|
+
valueSet = new Set(values);
|
|
529
|
+
this.inOperatorSetCache.set(cacheKey, valueSet);
|
|
530
|
+
}
|
|
531
|
+
return valueSet;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Evaluate a single condition
|
|
535
|
+
* Task #34: Optimized with regex caching and Set-based 'in' operator
|
|
536
|
+
*/
|
|
537
|
+
evaluateCondition(condition, context) {
|
|
538
|
+
const fieldValue = this.getNestedValue(context, condition.field);
|
|
539
|
+
switch (condition.operator) {
|
|
540
|
+
case 'equals':
|
|
541
|
+
return fieldValue === condition.value;
|
|
542
|
+
case 'not_equals':
|
|
543
|
+
return fieldValue !== condition.value;
|
|
544
|
+
case 'contains':
|
|
545
|
+
if (typeof fieldValue === 'string' && typeof condition.value === 'string') {
|
|
546
|
+
return fieldValue.includes(condition.value);
|
|
547
|
+
}
|
|
548
|
+
if (Array.isArray(fieldValue)) {
|
|
549
|
+
return fieldValue.includes(condition.value);
|
|
550
|
+
}
|
|
551
|
+
return false;
|
|
552
|
+
case 'not_contains':
|
|
553
|
+
if (typeof fieldValue === 'string' && typeof condition.value === 'string') {
|
|
554
|
+
return !fieldValue.includes(condition.value);
|
|
555
|
+
}
|
|
556
|
+
if (Array.isArray(fieldValue)) {
|
|
557
|
+
return !fieldValue.includes(condition.value);
|
|
558
|
+
}
|
|
559
|
+
return true;
|
|
560
|
+
case 'greater_than':
|
|
561
|
+
return typeof fieldValue === 'number' && typeof condition.value === 'number'
|
|
562
|
+
&& fieldValue > condition.value;
|
|
563
|
+
case 'less_than':
|
|
564
|
+
return typeof fieldValue === 'number' && typeof condition.value === 'number'
|
|
565
|
+
&& fieldValue < condition.value;
|
|
566
|
+
case 'in':
|
|
567
|
+
// Task #34: Use Set-based lookup for O(1) performance on large value arrays
|
|
568
|
+
if (Array.isArray(condition.value)) {
|
|
569
|
+
const valueSet = this.getCachedInOperatorSet(condition.value);
|
|
570
|
+
return valueSet.has(fieldValue);
|
|
571
|
+
}
|
|
572
|
+
return false;
|
|
573
|
+
case 'not_in':
|
|
574
|
+
// Task #34: Use Set-based lookup for O(1) performance on large value arrays
|
|
575
|
+
if (Array.isArray(condition.value)) {
|
|
576
|
+
const valueSet = this.getCachedInOperatorSet(condition.value);
|
|
577
|
+
return !valueSet.has(fieldValue);
|
|
578
|
+
}
|
|
579
|
+
return true;
|
|
580
|
+
case 'matches_regex':
|
|
581
|
+
// Task #34: Use cached regex compilation to avoid repeated new RegExp() calls
|
|
582
|
+
if (typeof fieldValue === 'string' && typeof condition.value === 'string') {
|
|
583
|
+
const regex = this.getCachedRegex(condition.value);
|
|
584
|
+
return regex ? regex.test(fieldValue) : false;
|
|
585
|
+
}
|
|
586
|
+
return false;
|
|
587
|
+
case 'exists':
|
|
588
|
+
return fieldValue !== undefined && fieldValue !== null;
|
|
589
|
+
case 'not_exists':
|
|
590
|
+
return fieldValue === undefined || fieldValue === null;
|
|
591
|
+
case 'custom':
|
|
592
|
+
if (condition.customEvaluator) {
|
|
593
|
+
// Task #53: Check cache first for compiled evaluator
|
|
594
|
+
const cacheKey = condition.customEvaluator;
|
|
595
|
+
let evaluator = this.evaluatorCache.get(cacheKey);
|
|
596
|
+
if (!evaluator) {
|
|
597
|
+
evaluator = this.customEvaluators.get(condition.customEvaluator);
|
|
598
|
+
if (evaluator) {
|
|
599
|
+
// Cache the evaluator for future use
|
|
600
|
+
this.evaluatorCache.set(cacheKey, evaluator);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (evaluator) {
|
|
604
|
+
// Task #56: Add try-catch around custom evaluator execution
|
|
605
|
+
try {
|
|
606
|
+
return evaluator(context, condition);
|
|
607
|
+
}
|
|
608
|
+
catch (error) {
|
|
609
|
+
// Log error and treat failed evaluator as non-matching condition
|
|
610
|
+
console.error(`Custom evaluator '${condition.customEvaluator}' threw an exception:`, error instanceof Error ? error.message : String(error));
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return false;
|
|
616
|
+
default:
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Get nested value from object using dot notation
|
|
622
|
+
*/
|
|
623
|
+
getNestedValue(obj, path) {
|
|
624
|
+
return path.split('.').reduce((current, key) => {
|
|
625
|
+
if (current && typeof current === 'object' && key in current) {
|
|
626
|
+
return current[key];
|
|
627
|
+
}
|
|
628
|
+
return undefined;
|
|
629
|
+
}, obj);
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Calculate risk level from score
|
|
633
|
+
*/
|
|
634
|
+
calculateRiskLevel(score) {
|
|
635
|
+
if (score >= 80)
|
|
636
|
+
return 'critical';
|
|
637
|
+
if (score >= 60)
|
|
638
|
+
return 'high';
|
|
639
|
+
if (score >= 40)
|
|
640
|
+
return 'medium';
|
|
641
|
+
if (score >= 20)
|
|
642
|
+
return 'low';
|
|
643
|
+
return 'minimal';
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Get severity level from a rule
|
|
647
|
+
*/
|
|
648
|
+
getRuleSeverity(rule) {
|
|
649
|
+
if (rule.riskWeight >= 40)
|
|
650
|
+
return 'critical';
|
|
651
|
+
if (rule.riskWeight >= 30)
|
|
652
|
+
return 'high';
|
|
653
|
+
if (rule.riskWeight >= 20)
|
|
654
|
+
return 'medium';
|
|
655
|
+
if (rule.riskWeight >= 10)
|
|
656
|
+
return 'low';
|
|
657
|
+
return 'minimal';
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Generate recommendation based on rule and action
|
|
661
|
+
*/
|
|
662
|
+
getRecommendation(rule, action) {
|
|
663
|
+
const baseRecs = {
|
|
664
|
+
compliance: 'Review compliance requirements and ensure proper authorization',
|
|
665
|
+
security: 'Consult with security team before proceeding',
|
|
666
|
+
operational: 'Follow standard operating procedures',
|
|
667
|
+
financial: 'Obtain financial approval before proceeding',
|
|
668
|
+
ux: 'Review UX guidelines and user impact',
|
|
669
|
+
architecture: 'Consult architecture review board',
|
|
670
|
+
data_governance: 'Ensure data handling complies with policies',
|
|
671
|
+
rate_limit: 'Reduce request frequency or request limit increase',
|
|
672
|
+
custom: 'Review custom policy requirements'
|
|
673
|
+
};
|
|
674
|
+
return action.params?.recommendation
|
|
675
|
+
|| baseRecs[rule.type]
|
|
676
|
+
|| 'Review policy and obtain necessary approvals';
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Register default custom evaluators
|
|
680
|
+
*/
|
|
681
|
+
registerDefaultEvaluators() {
|
|
682
|
+
// Time-based evaluator
|
|
683
|
+
this.registerCustomEvaluator('businessHours', (_context) => {
|
|
684
|
+
const hour = new Date().getHours();
|
|
685
|
+
return hour >= 9 && hour < 17;
|
|
686
|
+
});
|
|
687
|
+
// Weekday evaluator
|
|
688
|
+
this.registerCustomEvaluator('weekday', (_context) => {
|
|
689
|
+
const day = new Date().getDay();
|
|
690
|
+
return day >= 1 && day <= 5;
|
|
691
|
+
});
|
|
692
|
+
// Production environment evaluator
|
|
693
|
+
this.registerCustomEvaluator('isProduction', (context) => {
|
|
694
|
+
return context.environment === 'production';
|
|
695
|
+
});
|
|
696
|
+
// High privilege evaluator
|
|
697
|
+
this.registerCustomEvaluator('isHighPrivilege', (context) => {
|
|
698
|
+
const highPrivRoles = ['admin', 'superuser', 'root', 'system'];
|
|
699
|
+
return highPrivRoles.includes(context.userRole);
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
// Export singleton instance
|
|
704
|
+
export const rulesEngine = new RulesEngine();
|
|
705
|
+
//# sourceMappingURL=RulesEngine.js.map
|