noormme 1.2.0 → 1.2.2
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 +60 -6
- package/dist/cjs/agentic/ActionJournal.d.ts +5 -2
- package/dist/cjs/agentic/ActionJournal.js +13 -5
- package/dist/cjs/agentic/CapabilityManager.d.ts +7 -0
- package/dist/cjs/agentic/CapabilityManager.js +84 -7
- package/dist/cjs/agentic/CognitiveRepository.js +3 -6
- package/dist/cjs/agentic/Cortex.d.ts +4 -0
- package/dist/cjs/agentic/Cortex.js +38 -17
- package/dist/cjs/agentic/EpisodicMemory.d.ts +5 -1
- package/dist/cjs/agentic/EpisodicMemory.js +11 -4
- package/dist/cjs/agentic/PersonaManager.js +37 -31
- package/dist/cjs/agentic/PolicyEnforcer.d.ts +6 -1
- package/dist/cjs/agentic/PolicyEnforcer.js +74 -17
- package/dist/cjs/agentic/ResourceMonitor.d.ts +9 -0
- package/dist/cjs/agentic/ResourceMonitor.js +36 -2
- package/dist/cjs/agentic/SessionManager.js +24 -17
- package/dist/cjs/agentic/VectorIndexer.d.ts +1 -0
- package/dist/cjs/agentic/VectorIndexer.js +26 -17
- package/dist/cjs/agentic/improvement/AblationEngine.d.ts +4 -6
- package/dist/cjs/agentic/improvement/AblationEngine.js +57 -37
- package/dist/cjs/agentic/improvement/ActionRefiner.js +30 -14
- package/dist/cjs/agentic/improvement/ConflictResolver.d.ts +3 -1
- package/dist/cjs/agentic/improvement/ConflictResolver.js +59 -47
- package/dist/cjs/agentic/improvement/CortexJanitor.js +11 -0
- package/dist/cjs/agentic/improvement/CuriosityEngine.d.ts +1 -1
- package/dist/cjs/agentic/improvement/CuriosityEngine.js +48 -21
- package/dist/cjs/agentic/improvement/EvolutionRitual.js +26 -14
- package/dist/cjs/agentic/improvement/EvolutionaryPilot.js +16 -4
- package/dist/cjs/agentic/improvement/GoalArchitect.d.ts +6 -2
- package/dist/cjs/agentic/improvement/GoalArchitect.js +72 -34
- package/dist/cjs/agentic/improvement/GovernanceManager.d.ts +20 -6
- package/dist/cjs/agentic/improvement/GovernanceManager.js +134 -155
- package/dist/cjs/agentic/improvement/HiveLink.d.ts +7 -3
- package/dist/cjs/agentic/improvement/HiveLink.js +135 -113
- package/dist/cjs/agentic/improvement/KnowledgeDistiller.js +43 -35
- package/dist/cjs/agentic/improvement/QuotaManager.d.ts +41 -0
- package/dist/cjs/agentic/improvement/QuotaManager.js +185 -0
- package/dist/cjs/agentic/improvement/RecursiveReasoner.js +50 -25
- package/dist/cjs/agentic/improvement/ReflectionEngine.d.ts +4 -1
- package/dist/cjs/agentic/improvement/ReflectionEngine.js +3 -1
- package/dist/cjs/agentic/improvement/RitualOrchestrator.js +27 -16
- package/dist/cjs/agentic/improvement/RuleEngine.d.ts +1 -1
- package/dist/cjs/agentic/improvement/RuleEngine.js +10 -4
- package/dist/cjs/agentic/improvement/SelfEvolution.js +22 -17
- package/dist/cjs/agentic/improvement/SelfTestRegistry.d.ts +5 -0
- package/dist/cjs/agentic/improvement/SelfTestRegistry.js +129 -109
- package/dist/cjs/agentic/improvement/SkillSynthesizer.d.ts +1 -0
- package/dist/cjs/agentic/improvement/SkillSynthesizer.js +22 -13
- package/dist/cjs/agentic/improvement/StrategicPlanner.d.ts +1 -0
- package/dist/cjs/agentic/improvement/StrategicPlanner.js +26 -19
- package/dist/cjs/agentic/improvement/governance/AuditContext.d.ts +17 -0
- package/dist/cjs/agentic/improvement/governance/AuditContext.js +2 -0
- package/dist/cjs/agentic/improvement/governance/BudgetAuditor.d.ts +4 -0
- package/dist/cjs/agentic/improvement/governance/BudgetAuditor.js +50 -0
- package/dist/cjs/agentic/improvement/governance/EmergenceAuditor.d.ts +4 -0
- package/dist/cjs/agentic/improvement/governance/EmergenceAuditor.js +37 -0
- package/dist/cjs/agentic/improvement/governance/MaintenanceOracle.d.ts +4 -0
- package/dist/cjs/agentic/improvement/governance/MaintenanceOracle.js +67 -0
- package/dist/cjs/agentic/improvement/governance/PerformanceAuditor.d.ts +4 -0
- package/dist/cjs/agentic/improvement/governance/PerformanceAuditor.js +43 -0
- package/dist/cjs/agentic/improvement/governance/PersonaAuditor.d.ts +6 -0
- package/dist/cjs/agentic/improvement/governance/PersonaAuditor.js +74 -0
- package/dist/cjs/agentic/improvement/governance/RemediationEngine.d.ts +5 -0
- package/dist/cjs/agentic/improvement/governance/RemediationEngine.js +43 -0
- package/dist/cjs/agentic/improvement/governance/SkillAuditor.d.ts +5 -0
- package/dist/cjs/agentic/improvement/governance/SkillAuditor.js +52 -0
- package/dist/cjs/agentic/telemetry/CognitiveSynthesizer.d.ts +5 -0
- package/dist/cjs/agentic/telemetry/CognitiveSynthesizer.js +54 -12
- package/dist/cjs/agentic/telemetry/EventHarvester.d.ts +1 -1
- package/dist/cjs/agentic/telemetry/EventHarvester.js +10 -3
- package/dist/cjs/agentic/telemetry/ResearchAlchemist.d.ts +7 -2
- package/dist/cjs/agentic/telemetry/ResearchAlchemist.js +49 -8
- package/dist/cjs/agentic/telemetry/TelemetryOrchestrator.d.ts +4 -1
- package/dist/cjs/agentic/telemetry/TelemetryOrchestrator.js +38 -11
- package/dist/cjs/cli/commands/inspect.js +40 -1
- package/dist/cjs/cli/commands/watch.js +31 -25
- package/dist/cjs/cli/index.js +0 -0
- package/dist/cjs/dialect/sqlite/sqlite-introspector.js +15 -5
- package/dist/cjs/helpers/agent-schema.js +15 -14
- package/dist/cjs/migration/data_migrator.js +4 -4
- package/dist/cjs/migration/schema_differ.js +37 -15
- package/dist/cjs/types/index.d.ts +12 -0
- package/dist/cjs/util/safe-sql-helpers.js +7 -10
- package/dist/esm/agentic/ActionJournal.d.ts +5 -2
- package/dist/esm/agentic/ActionJournal.js +13 -5
- package/dist/esm/agentic/CapabilityManager.d.ts +7 -0
- package/dist/esm/agentic/CapabilityManager.js +84 -7
- package/dist/esm/agentic/CognitiveRepository.js +3 -6
- package/dist/esm/agentic/Cortex.d.ts +4 -0
- package/dist/esm/agentic/Cortex.js +38 -17
- package/dist/esm/agentic/EpisodicMemory.d.ts +5 -1
- package/dist/esm/agentic/EpisodicMemory.js +11 -4
- package/dist/esm/agentic/PersonaManager.js +37 -31
- package/dist/esm/agentic/PolicyEnforcer.d.ts +6 -1
- package/dist/esm/agentic/PolicyEnforcer.js +74 -17
- package/dist/esm/agentic/ResourceMonitor.d.ts +9 -0
- package/dist/esm/agentic/ResourceMonitor.js +36 -2
- package/dist/esm/agentic/SessionManager.js +24 -17
- package/dist/esm/agentic/VectorIndexer.d.ts +1 -0
- package/dist/esm/agentic/VectorIndexer.js +26 -17
- package/dist/esm/agentic/improvement/AblationEngine.d.ts +4 -6
- package/dist/esm/agentic/improvement/AblationEngine.js +57 -37
- package/dist/esm/agentic/improvement/ActionRefiner.js +30 -14
- package/dist/esm/agentic/improvement/ConflictResolver.d.ts +3 -1
- package/dist/esm/agentic/improvement/ConflictResolver.js +59 -47
- package/dist/esm/agentic/improvement/CortexJanitor.js +11 -0
- package/dist/esm/agentic/improvement/CuriosityEngine.d.ts +1 -1
- package/dist/esm/agentic/improvement/CuriosityEngine.js +48 -21
- package/dist/esm/agentic/improvement/EvolutionRitual.js +26 -14
- package/dist/esm/agentic/improvement/EvolutionaryPilot.js +16 -4
- package/dist/esm/agentic/improvement/GoalArchitect.d.ts +6 -2
- package/dist/esm/agentic/improvement/GoalArchitect.js +72 -34
- package/dist/esm/agentic/improvement/GovernanceManager.d.ts +20 -6
- package/dist/esm/agentic/improvement/GovernanceManager.js +134 -155
- package/dist/esm/agentic/improvement/HiveLink.d.ts +7 -3
- package/dist/esm/agentic/improvement/HiveLink.js +135 -113
- package/dist/esm/agentic/improvement/KnowledgeDistiller.js +43 -35
- package/dist/esm/agentic/improvement/QuotaManager.d.ts +41 -0
- package/dist/esm/agentic/improvement/QuotaManager.js +182 -0
- package/dist/esm/agentic/improvement/RecursiveReasoner.js +50 -25
- package/dist/esm/agentic/improvement/ReflectionEngine.d.ts +4 -1
- package/dist/esm/agentic/improvement/ReflectionEngine.js +3 -1
- package/dist/esm/agentic/improvement/RitualOrchestrator.js +27 -16
- package/dist/esm/agentic/improvement/RuleEngine.d.ts +1 -1
- package/dist/esm/agentic/improvement/RuleEngine.js +10 -4
- package/dist/esm/agentic/improvement/SelfEvolution.js +22 -17
- package/dist/esm/agentic/improvement/SelfTestRegistry.d.ts +5 -0
- package/dist/esm/agentic/improvement/SelfTestRegistry.js +129 -109
- package/dist/esm/agentic/improvement/SkillSynthesizer.d.ts +1 -0
- package/dist/esm/agentic/improvement/SkillSynthesizer.js +22 -13
- package/dist/esm/agentic/improvement/StrategicPlanner.d.ts +1 -0
- package/dist/esm/agentic/improvement/StrategicPlanner.js +26 -19
- package/dist/esm/agentic/improvement/governance/AuditContext.d.ts +17 -0
- package/dist/esm/agentic/improvement/governance/AuditContext.js +2 -0
- package/dist/esm/agentic/improvement/governance/BudgetAuditor.d.ts +4 -0
- package/dist/esm/agentic/improvement/governance/BudgetAuditor.js +47 -0
- package/dist/esm/agentic/improvement/governance/EmergenceAuditor.d.ts +4 -0
- package/dist/esm/agentic/improvement/governance/EmergenceAuditor.js +34 -0
- package/dist/esm/agentic/improvement/governance/MaintenanceOracle.d.ts +4 -0
- package/dist/esm/agentic/improvement/governance/MaintenanceOracle.js +64 -0
- package/dist/esm/agentic/improvement/governance/PerformanceAuditor.d.ts +4 -0
- package/dist/esm/agentic/improvement/governance/PerformanceAuditor.js +40 -0
- package/dist/esm/agentic/improvement/governance/PersonaAuditor.d.ts +6 -0
- package/dist/esm/agentic/improvement/governance/PersonaAuditor.js +71 -0
- package/dist/esm/agentic/improvement/governance/RemediationEngine.d.ts +5 -0
- package/dist/esm/agentic/improvement/governance/RemediationEngine.js +40 -0
- package/dist/esm/agentic/improvement/governance/SkillAuditor.d.ts +5 -0
- package/dist/esm/agentic/improvement/governance/SkillAuditor.js +49 -0
- package/dist/esm/agentic/telemetry/CognitiveSynthesizer.d.ts +5 -0
- package/dist/esm/agentic/telemetry/CognitiveSynthesizer.js +54 -12
- package/dist/esm/agentic/telemetry/EventHarvester.d.ts +1 -1
- package/dist/esm/agentic/telemetry/EventHarvester.js +10 -3
- package/dist/esm/agentic/telemetry/ResearchAlchemist.d.ts +7 -2
- package/dist/esm/agentic/telemetry/ResearchAlchemist.js +49 -8
- package/dist/esm/agentic/telemetry/TelemetryOrchestrator.d.ts +4 -1
- package/dist/esm/agentic/telemetry/TelemetryOrchestrator.js +38 -11
- package/dist/esm/cli/commands/inspect.js +40 -1
- package/dist/esm/cli/commands/watch.js +31 -25
- package/dist/esm/dialect/sqlite/sqlite-introspector.js +15 -5
- package/dist/esm/helpers/agent-schema.js +15 -14
- package/dist/esm/migration/data_migrator.js +4 -4
- package/dist/esm/migration/schema_differ.js +37 -15
- package/dist/esm/types/index.d.ts +12 -0
- package/dist/esm/util/safe-sql-helpers.js +7 -10
- package/package.json +44 -40
|
@@ -10,6 +10,7 @@ class PolicyEnforcer {
|
|
|
10
10
|
config;
|
|
11
11
|
policiesTable;
|
|
12
12
|
metricsTable;
|
|
13
|
+
metricCache = new Map();
|
|
13
14
|
constructor(db, config = {}) {
|
|
14
15
|
this.db = db;
|
|
15
16
|
this.config = config;
|
|
@@ -24,11 +25,15 @@ class PolicyEnforcer {
|
|
|
24
25
|
*/
|
|
25
26
|
async definePolicy(name, type, definition, isEnabled = true) {
|
|
26
27
|
return await this.db.transaction().execute(async (trx) => {
|
|
27
|
-
|
|
28
|
+
let query = trx
|
|
28
29
|
.selectFrom(this.policiesTable)
|
|
29
30
|
.select('id')
|
|
30
|
-
.where('name', '=', name)
|
|
31
|
-
|
|
31
|
+
.where('name', '=', name);
|
|
32
|
+
// Audit Phase 16: Exclusive lock for provisioning (Skip for SQLite)
|
|
33
|
+
if (this.db.getExecutor().adapter?.constructor.name !== 'SqliteAdapter') {
|
|
34
|
+
query = query.forUpdate();
|
|
35
|
+
}
|
|
36
|
+
const existing = await query.executeTakeFirst();
|
|
32
37
|
if (existing) {
|
|
33
38
|
const updated = await trx
|
|
34
39
|
.updateTable(this.policiesTable)
|
|
@@ -62,7 +67,19 @@ class PolicyEnforcer {
|
|
|
62
67
|
* Comprehensive policy evaluation against a context value.
|
|
63
68
|
* Supports thresholds, regex patterns, and cumulative budgets.
|
|
64
69
|
*/
|
|
65
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Comprehensive policy evaluation against a context value.
|
|
72
|
+
* Supports thresholds, regex patterns, and cumulative budgets.
|
|
73
|
+
*/
|
|
74
|
+
async checkPolicy(name, value, visited = new Set()) {
|
|
75
|
+
// Audit Pass 6: Re-entrancy / Circular Dependency Detection
|
|
76
|
+
if (visited.has(name)) {
|
|
77
|
+
return {
|
|
78
|
+
allowed: false,
|
|
79
|
+
reason: `Circular policy dependency detected: ${Array.from(visited).join(' -> ')} -> ${name}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
visited.add(name);
|
|
66
83
|
const policy = await this.typedDb
|
|
67
84
|
.selectFrom(this.policiesTable)
|
|
68
85
|
.selectAll()
|
|
@@ -89,18 +106,33 @@ class PolicyEnforcer {
|
|
|
89
106
|
}
|
|
90
107
|
// 2. Pattern Check (String/Regex)
|
|
91
108
|
if (typeof value === 'string' && def.pattern) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
reason: `Value does not match required pattern for policy '${name}'`,
|
|
97
|
-
};
|
|
109
|
+
// PRODUCTION HARDENING: ReDoS Prevention
|
|
110
|
+
// Sanitize regex: check for dangerous nested quantifiers and length
|
|
111
|
+
if (def.pattern.length > 500) {
|
|
112
|
+
return { allowed: false, reason: `Policy '${name}' regex pattern too long (potential ReDoS risk)` };
|
|
98
113
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
const dangerousPatterns = /(\*|\+)\1|\(\.\*\)\*/;
|
|
115
|
+
if (dangerousPatterns.test(def.pattern)) {
|
|
116
|
+
return { allowed: false, reason: `Policy '${name}' contains potentially dangerous ReDoS pattern` };
|
|
117
|
+
}
|
|
118
|
+
const flags = def.flags || 'i';
|
|
119
|
+
try {
|
|
120
|
+
const regex = new RegExp(def.pattern, flags);
|
|
121
|
+
if (def.mustMatch && !regex.test(value)) {
|
|
122
|
+
return {
|
|
123
|
+
allowed: false,
|
|
124
|
+
reason: `Value does not match required pattern for policy '${name}'`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (!def.mustMatch && regex.test(value)) {
|
|
128
|
+
return {
|
|
129
|
+
allowed: false,
|
|
130
|
+
reason: `Value contains forbidden pattern for policy '${name}'`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
return { allowed: false, reason: `Invalid regex in policy '${name}': ${String(e)}` };
|
|
104
136
|
}
|
|
105
137
|
}
|
|
106
138
|
// 3. Budget Check (Cumulative)
|
|
@@ -115,6 +147,16 @@ class PolicyEnforcer {
|
|
|
115
147
|
};
|
|
116
148
|
}
|
|
117
149
|
}
|
|
150
|
+
// 4. Composite Policy Check: Recursive evaluation of dependencies
|
|
151
|
+
// Audit Pass 6: Moved from evaluateContext to checkPolicy for deeper nesting support
|
|
152
|
+
if (def.dependsOn && Array.isArray(def.dependsOn)) {
|
|
153
|
+
for (const depName of def.dependsOn) {
|
|
154
|
+
const result = await this.checkPolicy(depName, value, new Set(visited));
|
|
155
|
+
if (!result.allowed) {
|
|
156
|
+
return { allowed: false, reason: `Composite block: ${name} -> ${result.reason}` };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
118
160
|
return { allowed: true };
|
|
119
161
|
}
|
|
120
162
|
/**
|
|
@@ -154,8 +196,21 @@ class PolicyEnforcer {
|
|
|
154
196
|
return list.map((p) => this.parsePolicy(p));
|
|
155
197
|
}
|
|
156
198
|
async getCumulativeMetric(metricName, period) {
|
|
157
|
-
|
|
199
|
+
const cacheKey = `${metricName}:${period}`;
|
|
200
|
+
const cached = this.metricCache.get(cacheKey);
|
|
158
201
|
const now = new Date();
|
|
202
|
+
const ttl = this.config.policyCacheTTL || 60000;
|
|
203
|
+
if (cached && now.getTime() - cached.timestamp < ttl) {
|
|
204
|
+
return cached.value;
|
|
205
|
+
}
|
|
206
|
+
// Map Hardening: Simple LRU-like eviction to prevent memory leaks
|
|
207
|
+
if (this.metricCache.size > 1000) {
|
|
208
|
+
const firstKey = this.metricCache.keys().next().value;
|
|
209
|
+
if (firstKey !== undefined) {
|
|
210
|
+
this.metricCache.delete(firstKey);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
let cutoff = new Date(0); // beginning of time
|
|
159
214
|
if (period === 'daily') {
|
|
160
215
|
cutoff = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
161
216
|
}
|
|
@@ -168,7 +223,9 @@ class PolicyEnforcer {
|
|
|
168
223
|
.where('metric_name', '=', metricName)
|
|
169
224
|
.where('created_at', '>=', cutoff)
|
|
170
225
|
.executeTakeFirst();
|
|
171
|
-
|
|
226
|
+
const total = Number(result?.total || 0);
|
|
227
|
+
this.metricCache.set(cacheKey, { value: total, timestamp: now.getTime() });
|
|
228
|
+
return total;
|
|
172
229
|
}
|
|
173
230
|
parsePolicy(p) {
|
|
174
231
|
return {
|
|
@@ -28,6 +28,15 @@ export declare class ResourceMonitor {
|
|
|
28
28
|
* Record token usage
|
|
29
29
|
*/
|
|
30
30
|
recordUsage(sessionId: string | number, modelName: string, inputTokens: number, outputTokens: number, cost?: number, agentId?: string, metadata?: Record<string, any>): Promise<ResourceUsage>;
|
|
31
|
+
/**
|
|
32
|
+
* Pre-run quota validation.
|
|
33
|
+
* Blocks operations if persona or swarm limits are breached.
|
|
34
|
+
* Includes cost projection to prevent mid-run budget failure.
|
|
35
|
+
*/
|
|
36
|
+
validateQuota(agentId: string, swarmId?: string, estimatedTokens?: number): Promise<{
|
|
37
|
+
allowed: boolean;
|
|
38
|
+
reason?: string;
|
|
39
|
+
}>;
|
|
31
40
|
/**
|
|
32
41
|
* Get total cost for a session
|
|
33
42
|
*/
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ResourceMonitor = void 0;
|
|
4
|
-
const sql_js_1 = require("../raw-builder/sql.js");
|
|
5
4
|
/**
|
|
6
5
|
* ResourceMonitor tracks token usage and costs across sessions.
|
|
7
6
|
*/
|
|
@@ -38,6 +37,39 @@ class ResourceMonitor {
|
|
|
38
37
|
.executeTakeFirstOrThrow();
|
|
39
38
|
return this.parseUsage(usage);
|
|
40
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Pre-run quota validation.
|
|
42
|
+
* Blocks operations if persona or swarm limits are breached.
|
|
43
|
+
* Includes cost projection to prevent mid-run budget failure.
|
|
44
|
+
*/
|
|
45
|
+
async validateQuota(agentId, swarmId, estimatedTokens = 2000) {
|
|
46
|
+
// 1. Project cost using Persistent Oracle from QuotaManager
|
|
47
|
+
let projection = 0;
|
|
48
|
+
const quotas = this.config.cortex?.quotas;
|
|
49
|
+
if (quotas) {
|
|
50
|
+
// Dynamic Lookup via Oracle sync
|
|
51
|
+
const rate = await quotas.getExchangeRate('USD'); // Oracle baseline
|
|
52
|
+
projection = estimatedTokens * (rate * 0.00001); // Weighted projection
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
projection = estimatedTokens * 0.00002; // Safe fallback
|
|
56
|
+
}
|
|
57
|
+
// 2. Persona Quota Check
|
|
58
|
+
const personaCheck = await this.config.cortex?.quotas.checkQuota('persona', agentId);
|
|
59
|
+
if (personaCheck && !personaCheck.allowed)
|
|
60
|
+
return personaCheck;
|
|
61
|
+
// 3. Swarm Quota Check
|
|
62
|
+
if (swarmId) {
|
|
63
|
+
const swarmCheck = await this.config.cortex?.quotas.checkQuota('swarm', swarmId);
|
|
64
|
+
if (swarmCheck && !swarmCheck.allowed)
|
|
65
|
+
return swarmCheck;
|
|
66
|
+
}
|
|
67
|
+
// 4. Global Quota Check
|
|
68
|
+
const globalCheck = await this.config.cortex?.quotas.checkQuota('global');
|
|
69
|
+
if (globalCheck && !globalCheck.allowed)
|
|
70
|
+
return globalCheck;
|
|
71
|
+
return { allowed: true };
|
|
72
|
+
}
|
|
41
73
|
/**
|
|
42
74
|
* Get total cost for a session
|
|
43
75
|
*/
|
|
@@ -67,7 +99,9 @@ class ResourceMonitor {
|
|
|
67
99
|
.selectFrom(this.resourcesTable)
|
|
68
100
|
.select([
|
|
69
101
|
'model_name',
|
|
70
|
-
(eb) => eb.fn
|
|
102
|
+
(eb) => eb.fn
|
|
103
|
+
.sum(eb('input_tokens', '+', 'output_tokens'))
|
|
104
|
+
.as('totalTokens'),
|
|
71
105
|
(eb) => eb.fn.sum('cost').as('totalCost'),
|
|
72
106
|
])
|
|
73
107
|
.groupBy('model_name')
|
|
@@ -139,6 +139,7 @@ class SessionManager {
|
|
|
139
139
|
.selectAll()
|
|
140
140
|
.where('session_id', '=', sessionId)
|
|
141
141
|
.where('description', '=', description)
|
|
142
|
+
.forUpdate() // Audit Phase 13: Atomic goal lock
|
|
142
143
|
.executeTakeFirst();
|
|
143
144
|
if (existing) {
|
|
144
145
|
const updated = await trx
|
|
@@ -205,23 +206,29 @@ class SessionManager {
|
|
|
205
206
|
* Mark a message as a semantic anchor to prevent it from being pruned
|
|
206
207
|
*/
|
|
207
208
|
async markMessageAsAnchor(messageId) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
209
|
+
// PRODUCTION HARDENING: Atomic Metadata Patching
|
|
210
|
+
// We avoid the Read-Modify-Write race condition by letting the DB handle the merge
|
|
211
|
+
// or by using a strict transaction if the DB doesn't support JSON patching natively.
|
|
212
|
+
const updated = await this.db.transaction().execute(async (trx) => {
|
|
213
|
+
const message = await trx
|
|
214
|
+
.selectFrom(this.messagesTable)
|
|
215
|
+
.select('metadata')
|
|
216
|
+
.where('id', '=', messageId)
|
|
217
|
+
.forUpdate() // Lock the row for the duration of the transaction
|
|
218
|
+
.executeTakeFirstOrThrow();
|
|
219
|
+
const metadata = typeof message.metadata === 'string'
|
|
220
|
+
? JSON.parse(message.metadata)
|
|
221
|
+
: message.metadata || {};
|
|
222
|
+
const updatedMetadata = { ...metadata, anchor: true };
|
|
223
|
+
return await trx
|
|
224
|
+
.updateTable(this.messagesTable)
|
|
225
|
+
.set({
|
|
226
|
+
metadata: JSON.stringify(updatedMetadata),
|
|
227
|
+
})
|
|
228
|
+
.where('id', '=', messageId)
|
|
229
|
+
.returningAll()
|
|
230
|
+
.executeTakeFirstOrThrow();
|
|
231
|
+
});
|
|
225
232
|
return this.parseMessage(updated);
|
|
226
233
|
}
|
|
227
234
|
parseSession(session) {
|
|
@@ -27,6 +27,7 @@ export declare class VectorIndexer {
|
|
|
27
27
|
addMemory(content: string, embedding: number[], sessionId?: string | number, metadata?: Record<string, any>): Promise<AgentMemory>;
|
|
28
28
|
/**
|
|
29
29
|
* Batch add memories
|
|
30
|
+
* Refactored Phase 12: Chunked execution to prevent OOM and long-running locks.
|
|
30
31
|
*/
|
|
31
32
|
addMemories(items: {
|
|
32
33
|
content: string;
|
|
@@ -36,21 +36,28 @@ class VectorIndexer {
|
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* Batch add memories
|
|
39
|
+
* Refactored Phase 12: Chunked execution to prevent OOM and long-running locks.
|
|
39
40
|
*/
|
|
40
41
|
async addMemories(items) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
.
|
|
53
|
-
|
|
42
|
+
const BATCH_SIZE = 50;
|
|
43
|
+
const results = [];
|
|
44
|
+
for (let i = 0; i < items.length; i += BATCH_SIZE) {
|
|
45
|
+
const chunk = items.slice(i, i + BATCH_SIZE);
|
|
46
|
+
const values = chunk.map((item) => ({
|
|
47
|
+
content: item.content,
|
|
48
|
+
embedding: JSON.stringify(item.embedding),
|
|
49
|
+
session_id: item.sessionId || null,
|
|
50
|
+
metadata: item.metadata ? JSON.stringify(item.metadata) : null,
|
|
51
|
+
created_at: new Date(),
|
|
52
|
+
}));
|
|
53
|
+
const memories = await this.typedDb
|
|
54
|
+
.insertInto(this.memoriesTable)
|
|
55
|
+
.values(values)
|
|
56
|
+
.returningAll()
|
|
57
|
+
.execute();
|
|
58
|
+
results.push(...memories.map((m) => this.parseMemory(m)));
|
|
59
|
+
}
|
|
60
|
+
return results;
|
|
54
61
|
}
|
|
55
62
|
/**
|
|
56
63
|
* Search for similar memories using Hybrid Search (Vector + Keyword)
|
|
@@ -86,13 +93,15 @@ class VectorIndexer {
|
|
|
86
93
|
}
|
|
87
94
|
if (vectorResults.length === 0) {
|
|
88
95
|
// Manual Fallback (Cosine Similarity in-memory)
|
|
89
|
-
|
|
96
|
+
// Audit Phase 12: Hard limit on candidate set to protect memory
|
|
97
|
+
let query = this.typedDb
|
|
98
|
+
.selectFrom(this.memoriesTable)
|
|
99
|
+
.selectAll()
|
|
100
|
+
.orderBy('created_at', 'desc')
|
|
101
|
+
.limit(1000);
|
|
90
102
|
if (sessionId) {
|
|
91
103
|
query = query.where('session_id', '=', sessionId);
|
|
92
104
|
}
|
|
93
|
-
if (!sessionId) {
|
|
94
|
-
query = query.orderBy('created_at', 'desc').limit(1000);
|
|
95
|
-
}
|
|
96
105
|
const allMemories = await query.execute();
|
|
97
106
|
const scored = allMemories.map((mem) => {
|
|
98
107
|
const vec = typeof mem.embedding === 'string'
|
|
@@ -16,24 +16,22 @@ export declare class AblationEngine {
|
|
|
16
16
|
private get typedDb();
|
|
17
17
|
/**
|
|
18
18
|
* Identify "Zombies": Items that have never been retrieved/hit and are old.
|
|
19
|
-
* Checks for linked dependencies before deletion.
|
|
20
19
|
*/
|
|
21
20
|
pruneZombies(thresholdDays?: number): Promise<number>;
|
|
22
21
|
/**
|
|
23
|
-
* Monitor Performance
|
|
24
|
-
*
|
|
22
|
+
* Monitor Performance and perform Intelligent Rollbacks.
|
|
23
|
+
* Prioritizes recovery of items with highest historical hit counts.
|
|
25
24
|
*/
|
|
26
25
|
monitorAblationPerformance(): Promise<{
|
|
27
26
|
status: 'stable' | 'degraded';
|
|
28
27
|
recoveredCount: number;
|
|
29
28
|
}>;
|
|
30
29
|
/**
|
|
31
|
-
* Conduct an "Ablation Test": Temporarily disable a knowledge item
|
|
32
|
-
* to see if it impacts reasoning.
|
|
30
|
+
* Conduct an "Ablation Test": Temporarily disable a knowledge item.
|
|
33
31
|
*/
|
|
34
32
|
testAblation(id: string | number): Promise<boolean>;
|
|
35
33
|
/**
|
|
36
34
|
* Restore an ablated knowledge item to its original state.
|
|
37
35
|
*/
|
|
38
|
-
recoverAblatedItem(id: string | number): Promise<boolean>;
|
|
36
|
+
recoverAblatedItem(id: string | number, trx?: any): Promise<boolean>;
|
|
39
37
|
}
|
|
@@ -25,13 +25,13 @@ class AblationEngine {
|
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Identify "Zombies": Items that have never been retrieved/hit and are old.
|
|
28
|
-
* Checks for linked dependencies before deletion.
|
|
29
28
|
*/
|
|
30
29
|
async pruneZombies(thresholdDays = 30) {
|
|
31
30
|
const cutoff = new Date(Date.now() - thresholdDays * 24 * 3600000);
|
|
32
31
|
let totalPruned = 0;
|
|
33
32
|
return await this.db.transaction().execute(async (trx) => {
|
|
34
|
-
// 1. Prune Knowledge (with dependency check)
|
|
33
|
+
// 1. Prune Knowledge (with dependency check and pagination)
|
|
34
|
+
// Audit Phase 9: Paginated selection to prevent OOM
|
|
35
35
|
const knowledgeToPrune = await trx
|
|
36
36
|
.selectFrom(this.knowledgeTable)
|
|
37
37
|
.selectAll()
|
|
@@ -41,16 +41,16 @@ class AblationEngine {
|
|
|
41
41
|
eb('metadata', 'is', null),
|
|
42
42
|
]))
|
|
43
43
|
.where('updated_at', '<', cutoff)
|
|
44
|
-
// Exclude items that are linked
|
|
45
44
|
.where('id', 'not in', (eb) => eb.selectFrom(this.linksTable).select('source_id'))
|
|
46
45
|
.where('id', 'not in', (eb) => eb.selectFrom(this.linksTable).select('target_id'))
|
|
46
|
+
.limit(500) // Audit Phase 9: Batch limit
|
|
47
|
+
.forUpdate() // Audit Phase 9: Lock candidates
|
|
47
48
|
.execute();
|
|
48
49
|
if (knowledgeToPrune.length > 0) {
|
|
49
50
|
const candidates = knowledgeToPrune.map((k) => this.cortex.knowledge['parseKnowledge'](k));
|
|
50
51
|
const idsToDelete = [];
|
|
51
52
|
for (const item of candidates) {
|
|
52
53
|
const fitness = this.cortex.knowledge.calculateFitness(item);
|
|
53
|
-
// Prune if fitness is below threshold (e.g., 0.3)
|
|
54
54
|
if (fitness < 0.3) {
|
|
55
55
|
idsToDelete.push(item.id);
|
|
56
56
|
}
|
|
@@ -63,14 +63,17 @@ class AblationEngine {
|
|
|
63
63
|
totalPruned += Number(result.numDeletedRows || 0);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
// 2. Prune Memories
|
|
66
|
+
// 2. Prune Memories (Paginated)
|
|
67
67
|
const memoriesResult = await trx
|
|
68
68
|
.deleteFrom(this.memoriesTable)
|
|
69
|
+
.where('id', 'in', (eb) => eb.selectFrom(this.memoriesTable)
|
|
70
|
+
.select('id')
|
|
69
71
|
.where('created_at', '<', cutoff)
|
|
70
|
-
.where((
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
.where((eb2) => eb2.or([
|
|
73
|
+
eb2('metadata', 'not like', '%"anchor":true%'),
|
|
74
|
+
eb2('metadata', 'is', null),
|
|
73
75
|
]))
|
|
76
|
+
.limit(1000))
|
|
74
77
|
.executeTakeFirst();
|
|
75
78
|
totalPruned += Number(memoriesResult.numDeletedRows || 0);
|
|
76
79
|
if (totalPruned > 0) {
|
|
@@ -80,34 +83,44 @@ class AblationEngine {
|
|
|
80
83
|
});
|
|
81
84
|
}
|
|
82
85
|
/**
|
|
83
|
-
* Monitor Performance
|
|
84
|
-
*
|
|
86
|
+
* Monitor Performance and perform Intelligent Rollbacks.
|
|
87
|
+
* Prioritizes recovery of items with highest historical hit counts.
|
|
85
88
|
*/
|
|
86
89
|
async monitorAblationPerformance() {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
return await this.db.transaction().execute(async (trx) => {
|
|
91
|
+
const baseline = await this.cortex.metrics.getAverageMetric('success_rate');
|
|
92
|
+
const stats = await this.cortex.metrics.getMetricStats('success_rate');
|
|
93
|
+
// If current average is significantly lower than overall average
|
|
94
|
+
if (stats.count > 10 && stats.avg < baseline * 0.8) {
|
|
95
|
+
console.warn(`[AblationEngine] PERFORMANCE DEGRADATION DETECTED (Avg: ${stats.avg}, Baseline: ${baseline}). Triggering targeted recovery.`);
|
|
96
|
+
// Fetch ablated items, ordered by hit_count descending (prioritize high-value restore)
|
|
97
|
+
const ablatedItems = await trx
|
|
98
|
+
.selectFrom(this.knowledgeTable)
|
|
99
|
+
.select(['id', 'metadata'])
|
|
100
|
+
.where('metadata', 'like', '%"ablation_test":true%')
|
|
101
|
+
.execute();
|
|
102
|
+
// Sort by hit_count in memory for precise weighted recovery
|
|
103
|
+
const sortedItems = ablatedItems.sort((a, b) => {
|
|
104
|
+
const metaA = typeof a.metadata === 'string' ? JSON.parse(a.metadata) : a.metadata || {};
|
|
105
|
+
const metaB = typeof b.metadata === 'string' ? JSON.parse(b.metadata) : b.metadata || {};
|
|
106
|
+
return (metaB.hit_count || 0) - (metaA.hit_count || 0);
|
|
107
|
+
});
|
|
108
|
+
let recoveredCount = 0;
|
|
109
|
+
for (const item of sortedItems) {
|
|
110
|
+
if (await this.recoverAblatedItem(item.id, trx)) {
|
|
111
|
+
recoveredCount++;
|
|
112
|
+
}
|
|
113
|
+
// Only recover until performance stabilizes or we've recovered a reasonable chunk (e.g. 5)
|
|
114
|
+
if (recoveredCount >= 5)
|
|
115
|
+
break;
|
|
102
116
|
}
|
|
117
|
+
return { status: 'degraded', recoveredCount };
|
|
103
118
|
}
|
|
104
|
-
return { status: '
|
|
105
|
-
}
|
|
106
|
-
return { status: 'stable', recoveredCount: 0 };
|
|
119
|
+
return { status: 'stable', recoveredCount: 0 };
|
|
120
|
+
});
|
|
107
121
|
}
|
|
108
122
|
/**
|
|
109
|
-
* Conduct an "Ablation Test": Temporarily disable a knowledge item
|
|
110
|
-
* to see if it impacts reasoning.
|
|
123
|
+
* Conduct an "Ablation Test": Temporarily disable a knowledge item.
|
|
111
124
|
*/
|
|
112
125
|
async testAblation(id) {
|
|
113
126
|
console.log(`[AblationEngine] Conducting ablation test on item ${id}`);
|
|
@@ -116,18 +129,18 @@ class AblationEngine {
|
|
|
116
129
|
.selectFrom(this.knowledgeTable)
|
|
117
130
|
.selectAll()
|
|
118
131
|
.where('id', '=', id)
|
|
132
|
+
.forUpdate() // Audit Phase 9: Atomic lock for test initiation
|
|
119
133
|
.executeTakeFirst());
|
|
120
134
|
if (!item)
|
|
121
135
|
return false;
|
|
122
136
|
const metadata = typeof item.metadata === 'string'
|
|
123
137
|
? JSON.parse(item.metadata)
|
|
124
138
|
: item.metadata || {};
|
|
125
|
-
// 1. Record the experiment in reflections
|
|
126
139
|
await this.cortex.reflections.reflect(item.source_session_id || 'system', 'success', `Ablation experiment initiated for item ${id}`, [
|
|
127
140
|
`Temporary confidence reduction to evaluate reasoning impact.`,
|
|
128
141
|
`Original confidence: ${item.confidence}`,
|
|
142
|
+
`Historical hits: ${metadata.hit_count || 0}`
|
|
129
143
|
]);
|
|
130
|
-
// 2. Perform the ablation
|
|
131
144
|
await trx
|
|
132
145
|
.updateTable(this.knowledgeTable)
|
|
133
146
|
.set({
|
|
@@ -147,12 +160,13 @@ class AblationEngine {
|
|
|
147
160
|
/**
|
|
148
161
|
* Restore an ablated knowledge item to its original state.
|
|
149
162
|
*/
|
|
150
|
-
async recoverAblatedItem(id) {
|
|
151
|
-
|
|
152
|
-
const item = (await
|
|
163
|
+
async recoverAblatedItem(id, trx) {
|
|
164
|
+
const recoveryStep = async (t) => {
|
|
165
|
+
const item = (await t
|
|
153
166
|
.selectFrom(this.knowledgeTable)
|
|
154
167
|
.selectAll()
|
|
155
168
|
.where('id', '=', id)
|
|
169
|
+
.forUpdate() // Audit Phase 9: Atomic lock for restoration
|
|
156
170
|
.executeTakeFirst());
|
|
157
171
|
if (!item)
|
|
158
172
|
return false;
|
|
@@ -165,7 +179,7 @@ class AblationEngine {
|
|
|
165
179
|
delete metadata.ablation_test;
|
|
166
180
|
delete metadata.original_confidence;
|
|
167
181
|
delete metadata.ablated_at;
|
|
168
|
-
await
|
|
182
|
+
await t
|
|
169
183
|
.updateTable(this.knowledgeTable)
|
|
170
184
|
.set({
|
|
171
185
|
confidence: originalConfidence,
|
|
@@ -176,7 +190,13 @@ class AblationEngine {
|
|
|
176
190
|
.execute();
|
|
177
191
|
console.log(`[AblationEngine] Item ${id} recovered. Confidence restored to ${originalConfidence}.`);
|
|
178
192
|
return true;
|
|
179
|
-
}
|
|
193
|
+
};
|
|
194
|
+
if (trx) {
|
|
195
|
+
return await recoveryStep(trx);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
return await this.db.transaction().execute(recoveryStep);
|
|
199
|
+
}
|
|
180
200
|
}
|
|
181
201
|
}
|
|
182
202
|
exports.AblationEngine = AblationEngine;
|
|
@@ -21,7 +21,9 @@ class ActionRefiner {
|
|
|
21
21
|
*/
|
|
22
22
|
async refineActions() {
|
|
23
23
|
const recommendations = [];
|
|
24
|
-
// 1. Find tools with high failure rates
|
|
24
|
+
// 1. Find tools with high failure rates (Last 24h Window)
|
|
25
|
+
// Audit Phase 14: Sliding window to prevent global table scans
|
|
26
|
+
const windowStart = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
25
27
|
const failureStats = (await this.db
|
|
26
28
|
.selectFrom(this.actionsTable)
|
|
27
29
|
.select('tool_name')
|
|
@@ -29,23 +31,27 @@ class ActionRefiner {
|
|
|
29
31
|
.select((eb) => eb.fn
|
|
30
32
|
.sum(eb.case().when('status', '=', 'failure').then(1).else(0).end())
|
|
31
33
|
.as('failures'))
|
|
34
|
+
.where('created_at', '>', windowStart)
|
|
32
35
|
.groupBy('tool_name')
|
|
33
36
|
.execute());
|
|
37
|
+
const failureRateThreshold = this.config.refiner?.failureRateThreshold || 0.3;
|
|
38
|
+
const minActionBatch = this.config.refiner?.minActionBatch || 3;
|
|
34
39
|
for (const stat of failureStats) {
|
|
35
40
|
const failures = Number(stat.failures || 0);
|
|
36
41
|
const total = Number(stat.total || 1);
|
|
37
42
|
const rate = failures / total;
|
|
38
|
-
if (rate >
|
|
43
|
+
if (rate > failureRateThreshold && total > minActionBatch) {
|
|
39
44
|
recommendations.push(`Tool '${stat.tool_name}' has a ${Math.round(rate * 100)}% failure rate. Suggesting automatic reflection rule.`);
|
|
40
45
|
// Automatically propose a rule to reflect on this tool's usage
|
|
41
46
|
await this.proposeReflectionRule(stat.tool_name);
|
|
42
47
|
}
|
|
43
48
|
}
|
|
44
|
-
// 2. Discover missing capabilities based on error patterns
|
|
49
|
+
// 2. Discover missing capabilities based on error patterns (Last 24h)
|
|
45
50
|
const missingCapabilities = (await this.db
|
|
46
51
|
.selectFrom(this.actionsTable)
|
|
47
52
|
.select('tool_name')
|
|
48
53
|
.where('status', '=', 'failure')
|
|
54
|
+
.where('created_at', '>', windowStart)
|
|
49
55
|
.where((eb) => eb.or([
|
|
50
56
|
eb('error', 'like', '%permission denied%'),
|
|
51
57
|
eb('error', 'like', '%unknown tool%'),
|
|
@@ -64,17 +70,27 @@ class ActionRefiner {
|
|
|
64
70
|
* Propose a rule to reflect on a specific tool usage
|
|
65
71
|
*/
|
|
66
72
|
async proposeReflectionRule(toolName) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
// Audit Phase 19: Atomic rule proposal via transaction + existence check
|
|
74
|
+
await this.db.transaction().execute(async (trx) => {
|
|
75
|
+
const rulesTable = this.cortex.config.rulesTable || 'agent_rules';
|
|
76
|
+
const existing = await trx
|
|
77
|
+
.selectFrom(rulesTable)
|
|
78
|
+
.select('id')
|
|
79
|
+
.where('tableName', '=', 'agent_actions')
|
|
80
|
+
.where('operation', '=', 'insert')
|
|
81
|
+
.where('metadata', 'like', `%\"targetTool\":\"${toolName}\"%`)
|
|
82
|
+
.forUpdate() // Lock to prevent concurrent proposals
|
|
83
|
+
.executeTakeFirst();
|
|
84
|
+
if (!existing) {
|
|
85
|
+
console.log(`[ActionRefiner] Proposing reflection rule for tool: ${toolName}`);
|
|
86
|
+
await this.cortex.rules.defineRule('agent_actions', 'insert', 'audit', {
|
|
87
|
+
metadata: {
|
|
88
|
+
targetTool: toolName,
|
|
89
|
+
reason: 'High failure rate detected by ActionRefiner',
|
|
90
|
+
},
|
|
91
|
+
}, trx); // Pass transaction object
|
|
92
|
+
}
|
|
93
|
+
});
|
|
78
94
|
}
|
|
79
95
|
/**
|
|
80
96
|
* Propose an update to capabilities
|
|
@@ -12,7 +12,9 @@ export declare class ConflictResolver {
|
|
|
12
12
|
/**
|
|
13
13
|
* Audit rules for direct conflicts and semantic overlaps.
|
|
14
14
|
*/
|
|
15
|
-
auditRuleConflicts(
|
|
15
|
+
auditRuleConflicts(options?: {
|
|
16
|
+
batchSize?: number;
|
|
17
|
+
}): Promise<string[]>;
|
|
16
18
|
/**
|
|
17
19
|
* Resolve a detected conflict by disabling the older rule.
|
|
18
20
|
* Real implementation: Find all active rules for the conflict, and disable all but the newest one.
|