tribunal-kit 9.2.6 → 9.2.7
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/.agent/ARCHITECTURE.md +2 -2
- package/.agent/history/architecture-intelligence.html +1487 -401
- package/.agent/history/architecture-model.json +9365 -5697
- package/.agent/history/architecture-verification.json +37 -9
- package/.agent/routing_index.json +2069 -3340
- package/.agent/rules/GEMINI.md +1 -1
- package/.agent/scripts/adaptive_execution.js +511 -0
- package/.agent/scripts/architecture_diff.js +303 -260
- package/.agent/scripts/architecture_extractor.js +792 -753
- package/.agent/scripts/architecture_health.js +293 -256
- package/.agent/scripts/architecture_intelligence.js +3100 -0
- package/.agent/scripts/architecture_verifier.js +303 -270
- package/.agent/scripts/architecture_visualizer.js +880 -873
- package/.agent/scripts/blast_radius_engine.js +274 -234
- package/.agent/scripts/capability_evolution.js +453 -0
- package/.agent/scripts/concept_extractor.js +240 -0
- package/.agent/scripts/consideration_engine.js +284 -0
- package/.agent/scripts/coverage_engine.js +102 -0
- package/.agent/scripts/cross_domain_engine.js +1883 -0
- package/.agent/scripts/engineering_memory.js +1313 -0
- package/.agent/scripts/integrity_manifest.js +1 -5
- package/.agent/scripts/judgment_engine.js +790 -0
- package/.agent/scripts/materiality_engine.js +251 -0
- package/.agent/scripts/outcome_engine.js +624 -0
- package/.agent/scripts/outcome_optimization_engine.js +1632 -0
- package/.agent/scripts/simulation_engine.js +1478 -0
- package/.agent/scripts/skill_intelligence.js +1371 -0
- package/.agent/scripts/validation_engine.js +236 -236
- package/.agent/scripts/verification_auditor.js +266 -0
- package/.agent/skill-intelligence/taxonomy/engineering-concepts.json +555 -0
- package/.agent/skill_topic_map.json +301 -341
- package/.agent/skills_inventory.json +4272 -6922
- package/.agent/workflows/arch.md +96 -80
- package/.agent/workflows/skill-intelligence.md +216 -0
- package/CONTRIBUTING.md +2 -2
- package/README.md +24 -24
- package/bin/adapter-install.js +1 -1
- package/package.json +8 -9
- package/plugins/core-plugin.json +1 -1
- package/plugins/gemini/extension.json +1 -1
- package/src/browser/index.js +2 -1
- package/src/browser/launcher.js +41 -1
- package/src/cli.js +3 -1
- package/src/commands/arch.js +261 -172
- package/src/commands/context.js +27 -8
- package/src/commands/learn.js +21 -6
- package/src/commands/memory.js +10 -1
- package/src/commands/native.js +12 -3
- package/src/commands/skill-intelligence.js +1165 -0
- package/src/commands/system1.js +105 -4
- package/src/context/ranker.js +337 -339
- package/src/evolution/engine.js +296 -295
- package/src/synthesis/claim_check_separator.js +294 -271
- package/src/system1/browser_intelligence.js +300 -0
- package/src/system1/constants.js +49 -0
- package/src/system1/decision_engine.js +498 -0
- package/src/system1/evidence_engine.js +602 -0
- package/src/system1/evolution_pipeline.js +581 -0
- package/src/system1/index.d.ts +114 -0
- package/src/system1/index.js +102 -0
- package/src/system1/reviewer_orchestrator.js +477 -0
- package/src/tui/banner.js +1 -1
- package/src/utils/fs.js +27 -0
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* evolution_pipeline.js — System-1 Capability 4: Controlled Self-Evolution Pipeline
|
|
5
|
+
* =================================================================================
|
|
6
|
+
* Governs candidate generation, regression validation, adversarial testing,
|
|
7
|
+
* candidate scoring, Human Gate approval, promotion, and clean rollback.
|
|
8
|
+
*
|
|
9
|
+
* CRITICAL RULE:
|
|
10
|
+
* Self-evolution MUST NEVER directly modify production rules.
|
|
11
|
+
* The system generates and evaluates candidate routing / heuristic improvements only.
|
|
12
|
+
*
|
|
13
|
+
* Pipeline Lifecycle:
|
|
14
|
+
* Observed Outcome -> Learning Signal -> Candidate Rule ->
|
|
15
|
+
* Validation Against Regression Corpus -> Adversarial Tests ->
|
|
16
|
+
* Candidate Score -> Human Approval -> Promotion -> (Optional Rollback)
|
|
17
|
+
*
|
|
18
|
+
* Candidate States:
|
|
19
|
+
* - PROPOSED
|
|
20
|
+
* - VALIDATING
|
|
21
|
+
* - REJECTED
|
|
22
|
+
* - APPROVED
|
|
23
|
+
* - PROMOTED
|
|
24
|
+
* - ROLLED_BACK
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
const path = require('path');
|
|
29
|
+
const crypto = require('crypto');
|
|
30
|
+
const {
|
|
31
|
+
validateEvolutionProposal,
|
|
32
|
+
ALLOWED_TARGETS,
|
|
33
|
+
FORBIDDEN_TARGETS,
|
|
34
|
+
EVIDENCE_HIERARCHY,
|
|
35
|
+
} = require('../evolution/engine');
|
|
36
|
+
|
|
37
|
+
const VALID_SOURCES = new Set([
|
|
38
|
+
'SYSTEM1_CLASSIFICATION',
|
|
39
|
+
'REVIEWER_FINDING',
|
|
40
|
+
'INNER_LOOP',
|
|
41
|
+
'HUMAN_GATE',
|
|
42
|
+
'FP_CORRECTION',
|
|
43
|
+
'FN_CORRECTION',
|
|
44
|
+
'TEST_RUNNER',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
const CANDIDATE_STATES = new Set([
|
|
48
|
+
'PROPOSED',
|
|
49
|
+
'VALIDATING',
|
|
50
|
+
'REJECTED',
|
|
51
|
+
'APPROVED',
|
|
52
|
+
'PROMOTED',
|
|
53
|
+
'ROLLED_BACK',
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
57
|
+
|
|
58
|
+
function validateCandidateId(id) {
|
|
59
|
+
if (typeof id !== 'string' || !/^[a-zA-Z0-9_-]+$/.test(id)) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Invalid candidate ID "${id}". ID must contain only alphanumeric characters, underscores, and dashes.`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function writeFileSyncAtomic(filePath, data, encoding = 'utf8') {
|
|
67
|
+
const dir = path.dirname(filePath);
|
|
68
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
69
|
+
const tmpPath = path.join(
|
|
70
|
+
dir,
|
|
71
|
+
`.${path.basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 6)}.tmp`,
|
|
72
|
+
);
|
|
73
|
+
fs.writeFileSync(tmpPath, data, encoding);
|
|
74
|
+
fs.renameSync(tmpPath, filePath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getEvolutionDir(agentDir = path.join(process.cwd(), '.agent')) {
|
|
78
|
+
return path.join(agentDir, 'evolution');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function acquireEvolutionLock(agentDir) {
|
|
82
|
+
const lockPath = path.join(getEvolutionDir(agentDir), 'evolution.lock');
|
|
83
|
+
const dir = path.dirname(lockPath);
|
|
84
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
85
|
+
const maxRetries = 30;
|
|
86
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
87
|
+
try {
|
|
88
|
+
const fd = fs.openSync(lockPath, 'wx');
|
|
89
|
+
fs.closeSync(fd);
|
|
90
|
+
return lockPath;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (err.code !== 'EEXIST') throw err;
|
|
93
|
+
const waitBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
94
|
+
Atomics.wait(waitBuf, 0, 0, 30);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Check for stale lock (> 15000ms old)
|
|
98
|
+
try {
|
|
99
|
+
const stats = fs.statSync(lockPath);
|
|
100
|
+
if (Date.now() - stats.mtimeMs > 15000) {
|
|
101
|
+
fs.unlinkSync(lockPath);
|
|
102
|
+
const fd = fs.openSync(lockPath, 'wx');
|
|
103
|
+
fs.closeSync(fd);
|
|
104
|
+
return lockPath;
|
|
105
|
+
}
|
|
106
|
+
} catch (_) {}
|
|
107
|
+
throw new Error('Could not acquire concurrency lock for evolution pipeline');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function releaseEvolutionLock(lockPath) {
|
|
111
|
+
try {
|
|
112
|
+
if (lockPath && fs.existsSync(lockPath)) {
|
|
113
|
+
fs.unlinkSync(lockPath);
|
|
114
|
+
}
|
|
115
|
+
} catch (_) {}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function getCandidatesDir(agentDir) {
|
|
119
|
+
return path.join(getEvolutionDir(agentDir), 'candidates');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getRollbacksDir(agentDir) {
|
|
123
|
+
return path.join(getEvolutionDir(agentDir), 'rollbacks');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getActiveEvolutionsPath(agentDir) {
|
|
127
|
+
return path.join(getEvolutionDir(agentDir), 'active_evolutions.json');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Ingests a verified outcome and produces a candidate proposal
|
|
132
|
+
* @param {Object} outcome
|
|
133
|
+
* @param {string} outcome.source - Verified source origin
|
|
134
|
+
* @param {string} outcome.target - Allowed evolution target
|
|
135
|
+
* @param {string} outcome.current_behavior
|
|
136
|
+
* @param {string} outcome.proposed_change
|
|
137
|
+
* @param {string} outcome.evidence - Concrete supporting evidence
|
|
138
|
+
* @param {string} [outcome.evidence_type] - Hierarchy level
|
|
139
|
+
* @param {string} [outcome.risk]
|
|
140
|
+
* @param {Object} [outcome.previous_state] - For rollback
|
|
141
|
+
* @param {string} [candidateId] - Optional explicit ID
|
|
142
|
+
* @returns {Object} Candidate object
|
|
143
|
+
*/
|
|
144
|
+
function createCandidateFromOutcome(outcome = {}, candidateId = null) {
|
|
145
|
+
if (candidateId) {
|
|
146
|
+
validateCandidateId(candidateId);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 1. Verify Outcome Source (reject raw LLM speculation or arbitrary text)
|
|
150
|
+
if (!outcome.source || !VALID_SOURCES.has(outcome.source)) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Invalid learning source "${outcome.source}". Self-evolution strictly rejects unverified speculation. Allowed sources: ${Array.from(VALID_SOURCES).join(', ')}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 2. Reject forbidden targets immediately
|
|
157
|
+
if (outcome.target && FORBIDDEN_TARGETS.has(outcome.target)) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Target "${outcome.target}" is a protected security boundary. Automatic evolution is strictly prohibited.`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!outcome.target || !ALLOWED_TARGETS.has(outcome.target)) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Target "${outcome.target}" is not an allowed evolution target. Allowed: ${Array.from(ALLOWED_TARGETS).join(', ')}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 3. Evidence quality check (Anti-Self-Poisoning)
|
|
170
|
+
const evidenceType = outcome.evidence_type || 'DETERMINISTIC_TELEMETRY';
|
|
171
|
+
const evidenceWeight = EVIDENCE_HIERARCHY[evidenceType] || 0;
|
|
172
|
+
if (evidenceWeight <= EVIDENCE_HIERARCHY.REVIEWER_CLAIM) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Anti-Self-Poisoning violation: Learning signal relies on "${evidenceType}". Only verified outcomes, telemetry, and reproducible patterns can generate candidates.`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const id = candidateId || `EVO-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
|
|
179
|
+
|
|
180
|
+
const candidate = {
|
|
181
|
+
candidate_id: id,
|
|
182
|
+
source: outcome.source,
|
|
183
|
+
target: outcome.target,
|
|
184
|
+
observed_behavior: outcome.current_behavior || outcome.observed_behavior || '',
|
|
185
|
+
proposed_change: outcome.proposed_change,
|
|
186
|
+
supporting_evidence: outcome.evidence || outcome.supporting_evidence || '',
|
|
187
|
+
evidence_type: evidenceType,
|
|
188
|
+
evidence_weight: evidenceWeight,
|
|
189
|
+
risk: ['LOW', 'MEDIUM', 'HIGH'].includes(String(outcome.risk).toUpperCase())
|
|
190
|
+
? String(outcome.risk).toUpperCase()
|
|
191
|
+
: 'MEDIUM',
|
|
192
|
+
tests_passed: [],
|
|
193
|
+
tests_failed: [],
|
|
194
|
+
candidate_score: 0.0,
|
|
195
|
+
confidence: evidenceType === 'VERIFIED_TEST_RESULT' ? 'L1' : 'L2',
|
|
196
|
+
status: 'PROPOSED',
|
|
197
|
+
created_at: new Date().toISOString(),
|
|
198
|
+
approval: null,
|
|
199
|
+
rollback_data: {
|
|
200
|
+
previous_state: outcome.previous_state ?? null,
|
|
201
|
+
target: outcome.target,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const validation = validateEvolutionProposal({
|
|
206
|
+
proposal_id: candidate.candidate_id,
|
|
207
|
+
target: candidate.target,
|
|
208
|
+
current_behavior: candidate.observed_behavior,
|
|
209
|
+
observed_evidence: candidate.supporting_evidence,
|
|
210
|
+
proposed_change:
|
|
211
|
+
typeof candidate.proposed_change === 'string'
|
|
212
|
+
? candidate.proposed_change
|
|
213
|
+
: JSON.stringify(candidate.proposed_change),
|
|
214
|
+
expected_effect: 'Optimized routing / heuristic accuracy backed by verified outcome',
|
|
215
|
+
risk: candidate.risk,
|
|
216
|
+
tests_required: ['regression_suite'],
|
|
217
|
+
regression_requirements: ['no_security_downgrades'],
|
|
218
|
+
evidence_type: candidate.evidence_type,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (!validation.valid) {
|
|
222
|
+
throw new Error(`Candidate proposal failed contract validation: ${validation.reason}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return candidate;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Validates candidate against regression corpus and adversarial test fixtures
|
|
230
|
+
* @param {Object} candidate
|
|
231
|
+
* @param {Array<{ name: string, run: Function, isAdversarial?: boolean }>} testSuite
|
|
232
|
+
* @returns {Promise<Object>} Validation results with candidate score
|
|
233
|
+
*/
|
|
234
|
+
async function validateCandidateAgainstCorpus(candidate, testSuite = []) {
|
|
235
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
236
|
+
throw new Error('Invalid candidate provided for validation');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (candidate.status === 'PROMOTED' || candidate.status === 'ROLLED_BACK') {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`Cannot validate candidate ${candidate.candidate_id} with status "${candidate.status}". Already promoted or rolled back candidates cannot be re-validated.`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
candidate.status = 'VALIDATING';
|
|
246
|
+
const passed = [];
|
|
247
|
+
const failed = [];
|
|
248
|
+
|
|
249
|
+
for (const testCase of testSuite) {
|
|
250
|
+
const testName = testCase.name || 'unnamed-test';
|
|
251
|
+
try {
|
|
252
|
+
const result = await testCase.run(candidate);
|
|
253
|
+
if (result === true || (result && result.passed)) {
|
|
254
|
+
passed.push(testName);
|
|
255
|
+
} else {
|
|
256
|
+
failed.push({
|
|
257
|
+
name: testName,
|
|
258
|
+
reason: result?.reason || 'Test assertion failed',
|
|
259
|
+
isAdversarial: !!testCase.isAdversarial,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
} catch (err) {
|
|
263
|
+
failed.push({
|
|
264
|
+
name: testName,
|
|
265
|
+
reason: err.message,
|
|
266
|
+
isAdversarial: !!testCase.isAdversarial,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
candidate.tests_passed = passed;
|
|
272
|
+
candidate.tests_failed = failed.map(f => f.name);
|
|
273
|
+
|
|
274
|
+
const total = passed.length + failed.length;
|
|
275
|
+
const passRatio = total > 0 ? passed.length / total : 0;
|
|
276
|
+
candidate.candidate_score = parseFloat(passRatio.toFixed(3));
|
|
277
|
+
|
|
278
|
+
// If any test failed, reject candidate
|
|
279
|
+
if (failed.length > 0) {
|
|
280
|
+
candidate.status = 'REJECTED';
|
|
281
|
+
candidate.rejection = {
|
|
282
|
+
reason: `Regression validation failed on ${failed.length} test(s): ${failed.map(f => f.name).join(', ')}`,
|
|
283
|
+
rejected_at: new Date().toISOString(),
|
|
284
|
+
failures: failed,
|
|
285
|
+
};
|
|
286
|
+
} else {
|
|
287
|
+
// Passed all tests -> ready for Human Gate
|
|
288
|
+
candidate.status = 'VALIDATING'; // Remains in validating until human approves
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
valid: failed.length === 0,
|
|
293
|
+
candidate_score: candidate.candidate_score,
|
|
294
|
+
passed_count: passed.length,
|
|
295
|
+
failed_count: failed.length,
|
|
296
|
+
candidate,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Human Gate: Explicitly approves a validated candidate
|
|
302
|
+
* @param {Object} candidate
|
|
303
|
+
* @param {string} approverName
|
|
304
|
+
* @param {string} [agentDir]
|
|
305
|
+
* @returns {Object} Approved candidate
|
|
306
|
+
*/
|
|
307
|
+
function approveCandidate(candidate, approverName, agentDir) {
|
|
308
|
+
if (!approverName || typeof approverName !== 'string') {
|
|
309
|
+
throw new Error('Human approval requires an explicit approver name/signature.');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (candidate.status === 'REJECTED') {
|
|
313
|
+
throw new Error(
|
|
314
|
+
`Cannot approve candidate ${candidate.candidate_id}: candidate was rejected during validation.`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (candidate.status !== 'VALIDATING' && candidate.status !== 'VALIDATED') {
|
|
319
|
+
throw new Error(
|
|
320
|
+
`Cannot approve candidate ${candidate.candidate_id} with status "${candidate.status}". Candidate must undergo regression corpus validation before Human Gate approval.`,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (candidate.tests_failed && candidate.tests_failed.length > 0) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
`Cannot approve candidate ${candidate.candidate_id}: failing regression tests exist.`,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (
|
|
331
|
+
!candidate.target ||
|
|
332
|
+
!ALLOWED_TARGETS.has(candidate.target) ||
|
|
333
|
+
FORBIDDEN_TARGETS.has(candidate.target)
|
|
334
|
+
) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`Target "${candidate.target}" is a protected or invalid evolution target. Human Gate approval rejected.`,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
candidate.status = 'APPROVED';
|
|
341
|
+
candidate.approval = {
|
|
342
|
+
approved_by: approverName,
|
|
343
|
+
approved_at: new Date().toISOString(),
|
|
344
|
+
human_gate_cleared: true,
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
if (agentDir) {
|
|
348
|
+
saveCandidate(candidate, agentDir);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return candidate;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Promotes an APPROVED candidate into active evolutions
|
|
356
|
+
* @param {Object} candidate
|
|
357
|
+
* @param {string} [agentDir]
|
|
358
|
+
* @returns {Object} Promotion summary
|
|
359
|
+
*/
|
|
360
|
+
function promoteCandidate(candidate, agentDir = path.join(process.cwd(), '.agent')) {
|
|
361
|
+
if (candidate.status !== 'APPROVED') {
|
|
362
|
+
throw new Error(
|
|
363
|
+
`Cannot promote candidate ${candidate.candidate_id} with status "${candidate.status}". Only APPROVED candidates that cleared the Human Gate can be promoted.`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
validateCandidateId(candidate.candidate_id);
|
|
368
|
+
if (FORBIDDEN_KEYS.has(candidate.target)) {
|
|
369
|
+
throw new Error(`Dangerous target key "${candidate.target}" rejected.`);
|
|
370
|
+
}
|
|
371
|
+
if (
|
|
372
|
+
!candidate.target ||
|
|
373
|
+
!ALLOWED_TARGETS.has(candidate.target) ||
|
|
374
|
+
FORBIDDEN_TARGETS.has(candidate.target)
|
|
375
|
+
) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`Target "${candidate.target}" is a protected or invalid evolution target. Promotion rejected.`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const rollbacksDir = getRollbacksDir(agentDir);
|
|
382
|
+
const activePath = getActiveEvolutionsPath(agentDir);
|
|
383
|
+
if (!fs.existsSync(rollbacksDir)) fs.mkdirSync(rollbacksDir, { recursive: true });
|
|
384
|
+
|
|
385
|
+
const lock = acquireEvolutionLock(agentDir);
|
|
386
|
+
try {
|
|
387
|
+
// 1. Read existing active evolutions
|
|
388
|
+
let active = {};
|
|
389
|
+
if (fs.existsSync(activePath)) {
|
|
390
|
+
try {
|
|
391
|
+
active = JSON.parse(fs.readFileSync(activePath, 'utf8'));
|
|
392
|
+
} catch (_) {
|
|
393
|
+
active = {};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// 2. Save rollback snapshot
|
|
398
|
+
const rollbackSnapshot = {
|
|
399
|
+
candidate_id: candidate.candidate_id,
|
|
400
|
+
target: candidate.target,
|
|
401
|
+
previous_state: active[candidate.target] ?? candidate.rollback_data?.previous_state ?? null,
|
|
402
|
+
promoted_at: new Date().toISOString(),
|
|
403
|
+
};
|
|
404
|
+
writeFileSyncAtomic(
|
|
405
|
+
path.join(rollbacksDir, `${candidate.candidate_id}.rollback.json`),
|
|
406
|
+
JSON.stringify(rollbackSnapshot, null, 2),
|
|
407
|
+
'utf8',
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
// 3. Promote candidate
|
|
411
|
+
active[candidate.target] = {
|
|
412
|
+
candidate_id: candidate.candidate_id,
|
|
413
|
+
proposed_change: candidate.proposed_change,
|
|
414
|
+
promoted_at: new Date().toISOString(),
|
|
415
|
+
approved_by: candidate.approval?.approved_by || 'Human Gate',
|
|
416
|
+
};
|
|
417
|
+
writeFileSyncAtomic(activePath, JSON.stringify(active, null, 2), 'utf8');
|
|
418
|
+
|
|
419
|
+
candidate.status = 'PROMOTED';
|
|
420
|
+
candidate.promoted_at = new Date().toISOString();
|
|
421
|
+
saveCandidate(candidate, agentDir);
|
|
422
|
+
} finally {
|
|
423
|
+
releaseEvolutionLock(lock);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
promoted: true,
|
|
428
|
+
candidate_id: candidate.candidate_id,
|
|
429
|
+
target: candidate.target,
|
|
430
|
+
rollback_available: true,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Rolls back a PROMOTED candidate cleanly
|
|
436
|
+
* @param {string} candidateId
|
|
437
|
+
* @param {string} [agentDir]
|
|
438
|
+
* @returns {Object} Rollback summary
|
|
439
|
+
*/
|
|
440
|
+
function rollbackCandidate(candidateId, agentDir = path.join(process.cwd(), '.agent')) {
|
|
441
|
+
validateCandidateId(candidateId);
|
|
442
|
+
const rollbacksDir = getRollbacksDir(agentDir);
|
|
443
|
+
const activePath = getActiveEvolutionsPath(agentDir);
|
|
444
|
+
const rollbackPath = path.join(rollbacksDir, `${candidateId}.rollback.json`);
|
|
445
|
+
|
|
446
|
+
if (!fs.existsSync(rollbackPath)) {
|
|
447
|
+
throw new Error(`No rollback snapshot found for candidate "${candidateId}".`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const snapshot = JSON.parse(fs.readFileSync(rollbackPath, 'utf8'));
|
|
451
|
+
if (FORBIDDEN_KEYS.has(snapshot.target)) {
|
|
452
|
+
throw new Error(`Dangerous snapshot target key "${snapshot.target}" rejected.`);
|
|
453
|
+
}
|
|
454
|
+
if (
|
|
455
|
+
!snapshot.target ||
|
|
456
|
+
!ALLOWED_TARGETS.has(snapshot.target) ||
|
|
457
|
+
FORBIDDEN_TARGETS.has(snapshot.target)
|
|
458
|
+
) {
|
|
459
|
+
throw new Error(`Dangerous snapshot target "${snapshot.target}" rejected.`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const lock = acquireEvolutionLock(agentDir);
|
|
463
|
+
try {
|
|
464
|
+
let active = {};
|
|
465
|
+
if (fs.existsSync(activePath)) {
|
|
466
|
+
try {
|
|
467
|
+
active = JSON.parse(fs.readFileSync(activePath, 'utf8'));
|
|
468
|
+
} catch (_) {
|
|
469
|
+
active = {};
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Verify candidate is not already rolled back and is in PROMOTED status
|
|
474
|
+
const candidate = loadCandidate(candidateId, agentDir);
|
|
475
|
+
if (candidate && candidate.status === 'ROLLED_BACK') {
|
|
476
|
+
throw new Error(`Candidate "${candidateId}" has already been rolled back.`);
|
|
477
|
+
}
|
|
478
|
+
if (candidate && candidate.status !== 'PROMOTED') {
|
|
479
|
+
throw new Error(
|
|
480
|
+
`Cannot rollback candidate "${candidateId}" with status "${candidate.status}". Only PROMOTED candidates can be rolled back.`,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Enforce LIFO / Active Candidate check:
|
|
485
|
+
// Only the candidate currently active for the target can be rolled back.
|
|
486
|
+
// Rolling back an inactive or superseded candidate would corrupt newer active evolutions.
|
|
487
|
+
const currentActive = active[snapshot.target];
|
|
488
|
+
if (!currentActive || currentActive.candidate_id !== candidateId) {
|
|
489
|
+
throw new Error(
|
|
490
|
+
`Cannot rollback candidate "${candidateId}" on target "${snapshot.target}": candidate is not currently active (active candidate: "${currentActive?.candidate_id || 'none'}"). Rollback the active candidate first.`,
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Restore previous state or remove if none existed
|
|
495
|
+
if (snapshot.previous_state !== null && snapshot.previous_state !== undefined) {
|
|
496
|
+
active[snapshot.target] = snapshot.previous_state;
|
|
497
|
+
} else {
|
|
498
|
+
delete active[snapshot.target];
|
|
499
|
+
}
|
|
500
|
+
writeFileSyncAtomic(activePath, JSON.stringify(active, null, 2), 'utf8');
|
|
501
|
+
|
|
502
|
+
// Update candidate status to ROLLED_BACK
|
|
503
|
+
if (candidate) {
|
|
504
|
+
candidate.status = 'ROLLED_BACK';
|
|
505
|
+
candidate.rolled_back_at = new Date().toISOString();
|
|
506
|
+
saveCandidate(candidate, agentDir);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Remove single-use snapshot to prevent replay rollback
|
|
510
|
+
try {
|
|
511
|
+
if (fs.existsSync(rollbackPath)) {
|
|
512
|
+
fs.unlinkSync(rollbackPath);
|
|
513
|
+
}
|
|
514
|
+
} catch (_) {}
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
rolled_back: true,
|
|
518
|
+
candidate_id: candidateId,
|
|
519
|
+
target: snapshot.target,
|
|
520
|
+
restored_state: snapshot.previous_state,
|
|
521
|
+
};
|
|
522
|
+
} finally {
|
|
523
|
+
releaseEvolutionLock(lock);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Persistence helpers
|
|
529
|
+
*/
|
|
530
|
+
function saveCandidate(candidate, agentDir = path.join(process.cwd(), '.agent')) {
|
|
531
|
+
validateCandidateId(candidate.candidate_id);
|
|
532
|
+
const dir = getCandidatesDir(agentDir);
|
|
533
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
534
|
+
const filePath = path.join(dir, `${candidate.candidate_id}.json`);
|
|
535
|
+
writeFileSyncAtomic(filePath, JSON.stringify(candidate, null, 2), 'utf8');
|
|
536
|
+
return filePath;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function loadCandidate(candidateId, agentDir = path.join(process.cwd(), '.agent')) {
|
|
540
|
+
validateCandidateId(candidateId);
|
|
541
|
+
const filePath = path.join(getCandidatesDir(agentDir), `${candidateId}.json`);
|
|
542
|
+
if (!fs.existsSync(filePath)) return null;
|
|
543
|
+
try {
|
|
544
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
545
|
+
} catch (_) {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function loadAllCandidates(agentDir = path.join(process.cwd(), '.agent')) {
|
|
551
|
+
const dir = getCandidatesDir(agentDir);
|
|
552
|
+
if (!fs.existsSync(dir)) return [];
|
|
553
|
+
return fs
|
|
554
|
+
.readdirSync(dir)
|
|
555
|
+
.filter(f => f.endsWith('.json'))
|
|
556
|
+
.map(f => {
|
|
557
|
+
try {
|
|
558
|
+
return JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8'));
|
|
559
|
+
} catch (_) {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
})
|
|
563
|
+
.filter(Boolean);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
module.exports = {
|
|
567
|
+
createCandidateFromOutcome,
|
|
568
|
+
validateCandidateAgainstCorpus,
|
|
569
|
+
approveCandidate,
|
|
570
|
+
promoteCandidate,
|
|
571
|
+
rollbackCandidate,
|
|
572
|
+
saveCandidate,
|
|
573
|
+
loadCandidate,
|
|
574
|
+
loadAllCandidates,
|
|
575
|
+
getActiveEvolutionsPath,
|
|
576
|
+
acquireEvolutionLock,
|
|
577
|
+
releaseEvolutionLock,
|
|
578
|
+
writeFileSyncAtomic,
|
|
579
|
+
VALID_SOURCES,
|
|
580
|
+
CANDIDATE_STATES,
|
|
581
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tribunal Kit System-1 Capability Suite Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
EvidenceResult,
|
|
7
|
+
EvidenceItem,
|
|
8
|
+
DecisionPayload,
|
|
9
|
+
ReviewerOrchestrationResult,
|
|
10
|
+
BrowserRequirementResult,
|
|
11
|
+
EvolutionCandidate,
|
|
12
|
+
} from '../index';
|
|
13
|
+
|
|
14
|
+
export class System1Provider {
|
|
15
|
+
constructor();
|
|
16
|
+
isAvailable(): boolean;
|
|
17
|
+
classifyImpact(task: string, files: string[]): Promise<number>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getLayaDir(): string;
|
|
21
|
+
export function getConfigPath(): string;
|
|
22
|
+
|
|
23
|
+
// Capability 1: Evidence Intelligence
|
|
24
|
+
export interface EvidenceOptions {
|
|
25
|
+
task?: string;
|
|
26
|
+
diff?: string;
|
|
27
|
+
files?: string[] | string;
|
|
28
|
+
repoRoot?: string;
|
|
29
|
+
memory?: any[];
|
|
30
|
+
previousFindings?: any[];
|
|
31
|
+
maxItems?: number;
|
|
32
|
+
maxTokens?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function collectAndRankEvidence(options?: EvidenceOptions): EvidenceResult;
|
|
36
|
+
export function extractChangedSymbols(
|
|
37
|
+
diff?: string,
|
|
38
|
+
content?: string,
|
|
39
|
+
): Array<{ name: string; type: string; line?: number }>;
|
|
40
|
+
export function extractBoundedSnippet(
|
|
41
|
+
content: string,
|
|
42
|
+
targetLine?: number | null,
|
|
43
|
+
targetSymbol?: string | null,
|
|
44
|
+
maxLines?: number,
|
|
45
|
+
): string;
|
|
46
|
+
export function redactSecrets(text: string): string;
|
|
47
|
+
|
|
48
|
+
export const TYPE_PRIORITY: Record<string, number>;
|
|
49
|
+
export const SECURITY_BOUNDARIES: RegExp[];
|
|
50
|
+
|
|
51
|
+
// Capability 2: Decision Engine
|
|
52
|
+
export interface DecisionOptions {
|
|
53
|
+
files?: string[];
|
|
54
|
+
lines?: number;
|
|
55
|
+
task?: string;
|
|
56
|
+
diff?: string;
|
|
57
|
+
layaTier?: number | null;
|
|
58
|
+
memory?: any[];
|
|
59
|
+
previousFindings?: any[];
|
|
60
|
+
tierResolver?: (options: any) => number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function evaluateDecision(options?: DecisionOptions): DecisionPayload;
|
|
64
|
+
export function resolveMonotonicImpactTier(options?: any): number;
|
|
65
|
+
export const HIGH_RISK_PATTERNS: RegExp;
|
|
66
|
+
export const HIGH_RISK_EXTENSIONS: RegExp;
|
|
67
|
+
export const TIER_NAMES: Record<number, string>;
|
|
68
|
+
export const ROUTING_NAMES: Record<number, string>;
|
|
69
|
+
|
|
70
|
+
// Capability 3: Reviewer Orchestration
|
|
71
|
+
export function orchestrateReviewers(
|
|
72
|
+
evidenceResult?: any,
|
|
73
|
+
options?: any,
|
|
74
|
+
): ReviewerOrchestrationResult;
|
|
75
|
+
export function detectRequiredDomains(evidenceResult: any): string[];
|
|
76
|
+
export const REVIEWER_CATALOG: Record<string, any>;
|
|
77
|
+
|
|
78
|
+
// Capability 4: Self-Evolution
|
|
79
|
+
export function createCandidateFromOutcome(outcome: any, id?: string): EvolutionCandidate;
|
|
80
|
+
export function validateCandidateAgainstCorpus(
|
|
81
|
+
candidate: EvolutionCandidate,
|
|
82
|
+
testSuites: any[],
|
|
83
|
+
): Promise<{ valid: boolean; results: any[] }>;
|
|
84
|
+
export function approveCandidate(
|
|
85
|
+
candidate: EvolutionCandidate,
|
|
86
|
+
approvedBy: string,
|
|
87
|
+
agentDir?: string,
|
|
88
|
+
): EvolutionCandidate;
|
|
89
|
+
export function promoteCandidate(
|
|
90
|
+
candidate: EvolutionCandidate,
|
|
91
|
+
agentDir?: string,
|
|
92
|
+
): { promoted: boolean; candidate_id: string; target: string };
|
|
93
|
+
export function rollbackCandidate(
|
|
94
|
+
candidateId: string,
|
|
95
|
+
agentDir?: string,
|
|
96
|
+
): { rolled_back: boolean; candidate_id: string; target: string; restored_state: any };
|
|
97
|
+
export function saveCandidate(candidate: EvolutionCandidate, agentDir?: string): string;
|
|
98
|
+
export function loadCandidate(candidateId: string, agentDir?: string): EvolutionCandidate | null;
|
|
99
|
+
export function loadAllCandidates(agentDir?: string): EvolutionCandidate[];
|
|
100
|
+
export function getActiveEvolutionsPath(agentDir?: string): string;
|
|
101
|
+
export function acquireEvolutionLock(agentDir?: string): string;
|
|
102
|
+
export function releaseEvolutionLock(lockPath: string): void;
|
|
103
|
+
export function writeFileSyncAtomic(filePath: string, data: string, encoding?: string): void;
|
|
104
|
+
|
|
105
|
+
export const VALID_SOURCES: string[];
|
|
106
|
+
export const CANDIDATE_STATES: string[];
|
|
107
|
+
|
|
108
|
+
// Capability 5: Browser Intelligence
|
|
109
|
+
export function evaluateBrowserRequirement(
|
|
110
|
+
task?: string,
|
|
111
|
+
changedFiles?: string[],
|
|
112
|
+
diff?: string,
|
|
113
|
+
): BrowserRequirementResult;
|
|
114
|
+
export function extractAffectedRoutes(changedFiles?: string[], diff?: string): string[];
|