thuban 0.3.1 → 0.3.3
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 +137 -102
- package/cli.js +644 -93
- package/package.json +5 -2
- package/packages/scanner/alert-manager.js +398 -398
- package/packages/scanner/code-scanner.js +758 -713
- package/packages/scanner/copy-paste-detector.js +33 -7
- package/packages/scanner/dependency-graph.js +541 -536
- package/packages/scanner/drift-detector.js +423 -423
- package/packages/scanner/file-collector.js +64 -0
- package/packages/scanner/file-watcher.js +323 -323
- package/packages/scanner/ghost-code-detector.js +80 -5
- package/packages/scanner/hallucination-detector.js +189 -19
- package/packages/scanner/health-checker.js +586 -586
- package/packages/scanner/index.js +100 -92
- package/packages/scanner/license-manager.js +15 -2
- package/packages/scanner/master-health-checker.js +513 -513
- package/packages/scanner/monitor-notifier.js +64 -0
- package/packages/scanner/monitor-service.js +103 -0
- package/packages/scanner/monitor-store.js +117 -0
- package/packages/scanner/pre-commit-gate.js +1 -1
- package/packages/scanner/scan-diff.js +50 -0
- package/packages/scanner/scan-runner.js +172 -0
- package/packages/scanner/secret-scanner.js +612 -0
- package/packages/scanner/secret-scanner.test.js +103 -0
- package/packages/scanner/sentinel-core.js +616 -608
- package/packages/scanner/sentinel-knowledge.js +322 -322
- package/packages/scanner/tech-debt-analyzer.js +46 -28
- package/packages/scanner/widget-generator.js +415 -415
|
@@ -1,586 +1,586 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Health Checker
|
|
3
|
-
*
|
|
4
|
-
* @purpose Event-driven HealthChecker component
|
|
5
|
-
* @module sentinel
|
|
6
|
-
* @layer application
|
|
7
|
-
* @imports-from fs, path, events
|
|
8
|
-
* @exports-to sentinel
|
|
9
|
-
* @side-effects File system reads, Console output, Event emission
|
|
10
|
-
* @critical-for sentinel
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Sentinel Health Checker
|
|
15
|
-
*
|
|
16
|
-
* Monitors the health of all Orion systems.
|
|
17
|
-
* Checks: Senate, Citadel, Forge, OCEE, Handoff, Shadow Realm, Knowledge
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
const fs = require('fs');
|
|
21
|
-
const path = require('path');
|
|
22
|
-
const EventEmitter = require('events');
|
|
23
|
-
|
|
24
|
-
class HealthChecker extends EventEmitter {
|
|
25
|
-
constructor(config = {}) {
|
|
26
|
-
super();
|
|
27
|
-
|
|
28
|
-
this.config = {
|
|
29
|
-
rootPath: config.rootPath || process.cwd(),
|
|
30
|
-
serverPath: config.serverPath || path.join(config.rootPath || process.cwd(), 'server'),
|
|
31
|
-
checkTimeout: config.checkTimeout || 5000,
|
|
32
|
-
...config
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// Define system health checks
|
|
36
|
-
this.systemChecks = [
|
|
37
|
-
{
|
|
38
|
-
id: 'senate',
|
|
39
|
-
name: 'Senate',
|
|
40
|
-
description: 'Multi-model orchestration',
|
|
41
|
-
check: () => this._checkSenate()
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
id: 'citadel',
|
|
45
|
-
name: 'Citadel',
|
|
46
|
-
description: '5-layer safety system',
|
|
47
|
-
check: () => this._checkCitadel()
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: 'forge',
|
|
51
|
-
name: 'Forge',
|
|
52
|
-
description: 'Build system',
|
|
53
|
-
check: () => this._checkForge()
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
id: 'ocee',
|
|
57
|
-
name: 'OCEE',
|
|
58
|
-
description: 'Orchestrated continuous execution',
|
|
59
|
-
check: () => this._checkOCEE()
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
id: 'handoff',
|
|
63
|
-
name: 'Handoff',
|
|
64
|
-
description: 'Human-AI collaboration',
|
|
65
|
-
check: () => this._checkHandoff()
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
id: 'shadow_realm',
|
|
69
|
-
name: 'Shadow Realm',
|
|
70
|
-
description: 'Validation sandbox',
|
|
71
|
-
check: () => this._checkShadowRealm()
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
id: 'knowledge',
|
|
75
|
-
name: 'Knowledge Manager',
|
|
76
|
-
description: 'Build tracking and learning',
|
|
77
|
-
check: () => this._checkKnowledge()
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
id: 'server',
|
|
81
|
-
name: 'Express Server',
|
|
82
|
-
description: 'API server health',
|
|
83
|
-
check: () => this._checkServer()
|
|
84
|
-
}
|
|
85
|
-
];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Check health of all systems
|
|
90
|
-
*/
|
|
91
|
-
async checkAll() {
|
|
92
|
-
console.log('[HEALTH-CHECKER] Running full health check...');
|
|
93
|
-
const startTime = Date.now();
|
|
94
|
-
|
|
95
|
-
const result = {
|
|
96
|
-
timestamp: new Date().toISOString(),
|
|
97
|
-
duration: 0,
|
|
98
|
-
overall: 'healthy',
|
|
99
|
-
systems: [],
|
|
100
|
-
healthySystems: [],
|
|
101
|
-
unhealthySystems: [],
|
|
102
|
-
summary: {}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
for (const systemCheck of this.systemChecks) {
|
|
106
|
-
try {
|
|
107
|
-
const checkResult = await Promise.race([
|
|
108
|
-
systemCheck.check(),
|
|
109
|
-
this._timeout(this.config.checkTimeout)
|
|
110
|
-
]);
|
|
111
|
-
|
|
112
|
-
const systemResult = {
|
|
113
|
-
id: systemCheck.id,
|
|
114
|
-
name: systemCheck.name,
|
|
115
|
-
description: systemCheck.description,
|
|
116
|
-
status: checkResult.healthy ? 'healthy' : 'unhealthy',
|
|
117
|
-
healthy: checkResult.healthy,
|
|
118
|
-
details: checkResult.details || {},
|
|
119
|
-
issues: checkResult.issues || [],
|
|
120
|
-
lastChecked: new Date().toISOString()
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
result.systems.push(systemResult);
|
|
124
|
-
|
|
125
|
-
if (systemResult.healthy) {
|
|
126
|
-
result.healthySystems.push(systemCheck.id);
|
|
127
|
-
} else {
|
|
128
|
-
result.unhealthySystems.push(systemCheck.id);
|
|
129
|
-
}
|
|
130
|
-
} catch (error) {
|
|
131
|
-
result.systems.push({
|
|
132
|
-
id: systemCheck.id,
|
|
133
|
-
name: systemCheck.name,
|
|
134
|
-
description: systemCheck.description,
|
|
135
|
-
status: 'error',
|
|
136
|
-
healthy: false,
|
|
137
|
-
error: error.message,
|
|
138
|
-
lastChecked: new Date().toISOString()
|
|
139
|
-
});
|
|
140
|
-
result.unhealthySystems.push(systemCheck.id);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Determine overall health
|
|
145
|
-
if (result.unhealthySystems.length === 0) {
|
|
146
|
-
result.overall = 'healthy';
|
|
147
|
-
} else if (result.unhealthySystems.length <= 2) {
|
|
148
|
-
result.overall = 'degraded';
|
|
149
|
-
} else {
|
|
150
|
-
result.overall = 'unhealthy';
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
result.duration = Date.now() - startTime;
|
|
154
|
-
result.summary = {
|
|
155
|
-
total: this.systemChecks.length,
|
|
156
|
-
healthy: result.healthySystems.length,
|
|
157
|
-
unhealthy: result.unhealthySystems.length
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
console.log(`[HEALTH-CHECKER] Health check complete: ${result.overall} (${result.summary.healthy}/${result.summary.total} systems healthy)`);
|
|
161
|
-
this.emit('checkComplete', result);
|
|
162
|
-
|
|
163
|
-
return result;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Check health of a specific system
|
|
168
|
-
*/
|
|
169
|
-
async checkSystem(systemId) {
|
|
170
|
-
const systemCheck = this.systemChecks.find(s => s.id === systemId);
|
|
171
|
-
if (!systemCheck) {
|
|
172
|
-
return { error: `Unknown system: ${systemId}` };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
const result = await systemCheck.check();
|
|
177
|
-
return {
|
|
178
|
-
id: systemCheck.id,
|
|
179
|
-
name: systemCheck.name,
|
|
180
|
-
...result,
|
|
181
|
-
lastChecked: new Date().toISOString()
|
|
182
|
-
};
|
|
183
|
-
} catch (error) {
|
|
184
|
-
return {
|
|
185
|
-
id: systemCheck.id,
|
|
186
|
-
name: systemCheck.name,
|
|
187
|
-
healthy: false,
|
|
188
|
-
error: error.message,
|
|
189
|
-
lastChecked: new Date().toISOString()
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Get list of available system checks
|
|
196
|
-
*/
|
|
197
|
-
getAvailableChecks() {
|
|
198
|
-
return this.systemChecks.map(s => ({
|
|
199
|
-
id: s.id,
|
|
200
|
-
name: s.name,
|
|
201
|
-
description: s.description
|
|
202
|
-
}));
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Individual system checks
|
|
206
|
-
|
|
207
|
-
async _checkSenate() {
|
|
208
|
-
const result = {
|
|
209
|
-
healthy: true,
|
|
210
|
-
details: {},
|
|
211
|
-
issues: []
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
// Check if senate files exist
|
|
216
|
-
const senateFiles = [
|
|
217
|
-
path.join(this.config.serverPath, 'senate.js'),
|
|
218
|
-
path.join(this.config.serverPath, 'senate-v2.js'),
|
|
219
|
-
path.join(this.config.serverPath, 'data', 'model-registry.json'),
|
|
220
|
-
path.join(this.config.serverPath, 'data', 'senate-weights-v2.json')
|
|
221
|
-
];
|
|
222
|
-
|
|
223
|
-
let filesFound = 0;
|
|
224
|
-
for (const file of senateFiles) {
|
|
225
|
-
if (fs.existsSync(file)) {
|
|
226
|
-
filesFound++;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
result.details.filesFound = filesFound;
|
|
231
|
-
result.details.totalFiles = senateFiles.length;
|
|
232
|
-
|
|
233
|
-
// Check model registry
|
|
234
|
-
const registryPath = path.join(this.config.serverPath, 'data', 'model-registry.json');
|
|
235
|
-
if (fs.existsSync(registryPath)) {
|
|
236
|
-
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
237
|
-
result.details.providersConfigured = Object.keys(registry.providers || {}).length;
|
|
238
|
-
result.details.modelsRegistered = Object.values(registry.providers || {})
|
|
239
|
-
.reduce((sum, p) => sum + (p.models?.length || 0), 0);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (filesFound < 2) {
|
|
243
|
-
result.healthy = false;
|
|
244
|
-
result.issues.push('Core Senate files missing');
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
} catch (error) {
|
|
248
|
-
result.healthy = false;
|
|
249
|
-
result.issues.push(`Senate check failed: ${error.message}`);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return result;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
async _checkCitadel() {
|
|
256
|
-
const result = {
|
|
257
|
-
healthy: true,
|
|
258
|
-
details: {},
|
|
259
|
-
issues: []
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
try {
|
|
263
|
-
// Check if citadel files exist
|
|
264
|
-
const citadelPath = path.join(this.config.rootPath, 'citadel');
|
|
265
|
-
const citadelFiles = [
|
|
266
|
-
'enforcer.js',
|
|
267
|
-
'watchers.js',
|
|
268
|
-
'constitution-loader.js',
|
|
269
|
-
'file-guard.js'
|
|
270
|
-
];
|
|
271
|
-
|
|
272
|
-
let filesFound = 0;
|
|
273
|
-
for (const file of citadelFiles) {
|
|
274
|
-
if (fs.existsSync(path.join(citadelPath, file))) {
|
|
275
|
-
filesFound++;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
result.details.filesFound = filesFound;
|
|
280
|
-
result.details.totalFiles = citadelFiles.length;
|
|
281
|
-
|
|
282
|
-
// Check audit logs
|
|
283
|
-
const auditPath = path.join(citadelPath, 'audit');
|
|
284
|
-
if (fs.existsSync(auditPath)) {
|
|
285
|
-
const auditFiles = fs.readdirSync(auditPath).filter(f => f.endsWith('.json'));
|
|
286
|
-
result.details.auditFilesCount = auditFiles.length;
|
|
287
|
-
|
|
288
|
-
// Check most recent audit
|
|
289
|
-
if (auditFiles.length > 0) {
|
|
290
|
-
const latestAudit = auditFiles.sort().pop();
|
|
291
|
-
const latestPath = path.join(auditPath, latestAudit);
|
|
292
|
-
const auditStat = fs.statSync(latestPath);
|
|
293
|
-
result.details.lastAudit = auditStat.mtime.toISOString();
|
|
294
|
-
|
|
295
|
-
// Warn if no audit in 24 hours
|
|
296
|
-
const hoursSinceAudit = (Date.now() - auditStat.mtime.getTime()) / (1000 * 60 * 60);
|
|
297
|
-
if (hoursSinceAudit > 24) {
|
|
298
|
-
result.issues.push(`No audit activity in ${Math.round(hoursSinceAudit)} hours`);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (filesFound < 3) {
|
|
304
|
-
result.healthy = false;
|
|
305
|
-
result.issues.push('Core Citadel files missing');
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
} catch (error) {
|
|
309
|
-
result.healthy = false;
|
|
310
|
-
result.issues.push(`Citadel check failed: ${error.message}`);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return result;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
async _checkForge() {
|
|
317
|
-
const result = {
|
|
318
|
-
healthy: true,
|
|
319
|
-
details: {},
|
|
320
|
-
issues: []
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
const forgePath = path.join(this.config.rootPath, 'forge');
|
|
325
|
-
const forgeFiles = [
|
|
326
|
-
'forge-core.js',
|
|
327
|
-
'task-decomposer.js',
|
|
328
|
-
'intent-matcher.js',
|
|
329
|
-
'diagnostician-v2.js',
|
|
330
|
-
'prescriber.js',
|
|
331
|
-
'forge-loop.js'
|
|
332
|
-
];
|
|
333
|
-
|
|
334
|
-
let filesFound = 0;
|
|
335
|
-
for (const file of forgeFiles) {
|
|
336
|
-
if (fs.existsSync(path.join(forgePath, file))) {
|
|
337
|
-
filesFound++;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
result.details.filesFound = filesFound;
|
|
342
|
-
result.details.totalFiles = forgeFiles.length;
|
|
343
|
-
result.details.completeness = Math.round((filesFound / forgeFiles.length) * 100) + '%';
|
|
344
|
-
|
|
345
|
-
if (filesFound < 4) {
|
|
346
|
-
result.healthy = false;
|
|
347
|
-
result.issues.push('Core Forge components missing');
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
} catch (error) {
|
|
351
|
-
result.healthy = false;
|
|
352
|
-
result.issues.push(`Forge check failed: ${error.message}`);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return result;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
async _checkOCEE() {
|
|
359
|
-
const result = {
|
|
360
|
-
healthy: true,
|
|
361
|
-
details: {},
|
|
362
|
-
issues: []
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
try {
|
|
366
|
-
const oceePath = path.join(this.config.serverPath, 'ocee');
|
|
367
|
-
const oceeFiles = [
|
|
368
|
-
'ocee-core.js',
|
|
369
|
-
'persistent-task-queue.js',
|
|
370
|
-
'checkpoint-manager.js',
|
|
371
|
-
'build-session-manager.js',
|
|
372
|
-
'build-graph-generator.js'
|
|
373
|
-
];
|
|
374
|
-
|
|
375
|
-
let filesFound = 0;
|
|
376
|
-
for (const file of oceeFiles) {
|
|
377
|
-
if (fs.existsSync(path.join(oceePath, file))) {
|
|
378
|
-
filesFound++;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
result.details.filesFound = filesFound;
|
|
383
|
-
result.details.totalFiles = oceeFiles.length;
|
|
384
|
-
|
|
385
|
-
// Check for active tasks
|
|
386
|
-
const taskQueuePath = path.join(oceePath, 'queue');
|
|
387
|
-
if (fs.existsSync(taskQueuePath)) {
|
|
388
|
-
try {
|
|
389
|
-
const queueFiles = fs.readdirSync(taskQueuePath);
|
|
390
|
-
result.details.pendingTasks = queueFiles.length;
|
|
391
|
-
} catch (e) {
|
|
392
|
-
result.details.pendingTasks = 'unknown';
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (filesFound < 3) {
|
|
397
|
-
result.healthy = false;
|
|
398
|
-
result.issues.push('Core OCEE components missing');
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
} catch (error) {
|
|
402
|
-
result.healthy = false;
|
|
403
|
-
result.issues.push(`OCEE check failed: ${error.message}`);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return result;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
async _checkHandoff() {
|
|
410
|
-
const result = {
|
|
411
|
-
healthy: true,
|
|
412
|
-
details: {},
|
|
413
|
-
issues: []
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
try {
|
|
417
|
-
const handoffPath = path.join(this.config.serverPath, 'handoff');
|
|
418
|
-
const handoffFiles = [
|
|
419
|
-
'index.js',
|
|
420
|
-
'risk-scorer.js',
|
|
421
|
-
'gate-checker.js',
|
|
422
|
-
'mission-state.js',
|
|
423
|
-
'audit-logger.js',
|
|
424
|
-
'evidence-bundle.js',
|
|
425
|
-
'cost-tracker.js'
|
|
426
|
-
];
|
|
427
|
-
|
|
428
|
-
let filesFound = 0;
|
|
429
|
-
for (const file of handoffFiles) {
|
|
430
|
-
if (fs.existsSync(path.join(handoffPath, file))) {
|
|
431
|
-
filesFound++;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
result.details.filesFound = filesFound;
|
|
436
|
-
result.details.totalFiles = handoffFiles.length;
|
|
437
|
-
|
|
438
|
-
if (filesFound < 5) {
|
|
439
|
-
result.healthy = false;
|
|
440
|
-
result.issues.push('Core Handoff components missing');
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
} catch (error) {
|
|
444
|
-
result.healthy = false;
|
|
445
|
-
result.issues.push(`Handoff check failed: ${error.message}`);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
return result;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
async _checkShadowRealm() {
|
|
452
|
-
const result = {
|
|
453
|
-
healthy: true,
|
|
454
|
-
details: {},
|
|
455
|
-
issues: []
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
try {
|
|
459
|
-
const shadowPath = path.join(this.config.rootPath, 'build-engine', 'orion-core');
|
|
460
|
-
const shadowFiles = [
|
|
461
|
-
'shadow-realm-v2.js',
|
|
462
|
-
'foreman.js',
|
|
463
|
-
'healing-loop.js'
|
|
464
|
-
];
|
|
465
|
-
|
|
466
|
-
let filesFound = 0;
|
|
467
|
-
for (const file of shadowFiles) {
|
|
468
|
-
if (fs.existsSync(path.join(shadowPath, file))) {
|
|
469
|
-
filesFound++;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
result.details.filesFound = filesFound;
|
|
474
|
-
result.details.totalFiles = shadowFiles.length;
|
|
475
|
-
|
|
476
|
-
// Test shadow realm validation
|
|
477
|
-
try {
|
|
478
|
-
const shadowRealm = require(path.join(shadowPath, 'shadow-realm-v2.js'));
|
|
479
|
-
const testResult = shadowRealm.validateInSandbox('console.log("test");', 'test.js');
|
|
480
|
-
result.details.validationWorking = testResult.syntaxValid === true;
|
|
481
|
-
} catch (e) {
|
|
482
|
-
result.details.validationWorking = false;
|
|
483
|
-
result.issues.push(`Shadow Realm validation test failed: ${e.message}`);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
if (filesFound < 2) {
|
|
487
|
-
result.healthy = false;
|
|
488
|
-
result.issues.push('Core Shadow Realm components missing');
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
} catch (error) {
|
|
492
|
-
result.healthy = false;
|
|
493
|
-
result.issues.push(`Shadow Realm check failed: ${error.message}`);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
return result;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
async _checkKnowledge() {
|
|
500
|
-
const result = {
|
|
501
|
-
healthy: true,
|
|
502
|
-
details: {},
|
|
503
|
-
issues: []
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
try {
|
|
507
|
-
const knowledgePath = path.join(this.config.rootPath, 'orion-knowledge.json');
|
|
508
|
-
|
|
509
|
-
if (fs.existsSync(knowledgePath)) {
|
|
510
|
-
const knowledge = JSON.parse(fs.readFileSync(knowledgePath, 'utf8'));
|
|
511
|
-
result.details.buildsTracked = knowledge.builds?.length || 0;
|
|
512
|
-
result.details.capabilitiesTracked = knowledge.capabilities?.length || 0;
|
|
513
|
-
|
|
514
|
-
// Check for recent activity
|
|
515
|
-
if (knowledge.builds && knowledge.builds.length > 0) {
|
|
516
|
-
const latestBuild = knowledge.builds[knowledge.builds.length - 1];
|
|
517
|
-
result.details.lastBuild = latestBuild.timestamp || latestBuild.date;
|
|
518
|
-
}
|
|
519
|
-
} else {
|
|
520
|
-
result.healthy = false;
|
|
521
|
-
result.issues.push('orion-knowledge.json not found');
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
} catch (error) {
|
|
525
|
-
result.healthy = false;
|
|
526
|
-
result.issues.push(`Knowledge check failed: ${error.message}`);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
return result;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
async _checkServer() {
|
|
533
|
-
const result = {
|
|
534
|
-
healthy: true,
|
|
535
|
-
details: {},
|
|
536
|
-
issues: []
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
try {
|
|
540
|
-
// Check if server files exist
|
|
541
|
-
const serverFiles = [
|
|
542
|
-
path.join(this.config.serverPath, 'package.json'),
|
|
543
|
-
path.join(this.config.serverPath, 'index.js')
|
|
544
|
-
];
|
|
545
|
-
|
|
546
|
-
let filesFound = 0;
|
|
547
|
-
for (const file of serverFiles) {
|
|
548
|
-
if (fs.existsSync(file)) {
|
|
549
|
-
filesFound++;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
result.details.filesFound = filesFound;
|
|
554
|
-
|
|
555
|
-
// Check package.json for dependencies
|
|
556
|
-
const pkgPath = path.join(this.config.serverPath, 'package.json');
|
|
557
|
-
if (fs.existsSync(pkgPath)) {
|
|
558
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
559
|
-
result.details.dependencies = Object.keys(pkg.dependencies || {}).length;
|
|
560
|
-
result.details.nodeVersion = pkg.engines?.node || 'not specified';
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Check node_modules exists
|
|
564
|
-
const nodeModulesPath = path.join(this.config.serverPath, 'node_modules');
|
|
565
|
-
result.details.nodeModulesInstalled = fs.existsSync(nodeModulesPath);
|
|
566
|
-
|
|
567
|
-
if (!result.details.nodeModulesInstalled) {
|
|
568
|
-
result.issues.push('node_modules not installed - run npm install');
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
} catch (error) {
|
|
572
|
-
result.healthy = false;
|
|
573
|
-
result.issues.push(`Server check failed: ${error.message}`);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
return result;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
_timeout(ms) {
|
|
580
|
-
return new Promise((_, reject) =>
|
|
581
|
-
setTimeout(() => reject(new Error('Health check timeout')), ms)
|
|
582
|
-
);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
module.exports = HealthChecker;
|
|
1
|
+
/**
|
|
2
|
+
* Health Checker
|
|
3
|
+
*
|
|
4
|
+
* @purpose Event-driven HealthChecker component
|
|
5
|
+
* @module sentinel
|
|
6
|
+
* @layer application
|
|
7
|
+
* @imports-from fs, path, events
|
|
8
|
+
* @exports-to sentinel
|
|
9
|
+
* @side-effects File system reads, Console output, Event emission
|
|
10
|
+
* @critical-for sentinel
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Sentinel Health Checker
|
|
15
|
+
*
|
|
16
|
+
* Monitors the health of all Orion systems.
|
|
17
|
+
* Checks: Senate, Citadel, Forge, OCEE, Handoff, Shadow Realm, Knowledge
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const EventEmitter = require('events');
|
|
23
|
+
|
|
24
|
+
class HealthChecker extends EventEmitter {
|
|
25
|
+
constructor(config = {}) {
|
|
26
|
+
super();
|
|
27
|
+
|
|
28
|
+
this.config = {
|
|
29
|
+
rootPath: config.rootPath || process.cwd(),
|
|
30
|
+
serverPath: config.serverPath || path.join(config.rootPath || process.cwd(), 'server'),
|
|
31
|
+
checkTimeout: config.checkTimeout || 5000,
|
|
32
|
+
...config
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Define system health checks
|
|
36
|
+
this.systemChecks = [
|
|
37
|
+
{
|
|
38
|
+
id: 'senate',
|
|
39
|
+
name: 'Senate',
|
|
40
|
+
description: 'Multi-model orchestration',
|
|
41
|
+
check: () => this._checkSenate()
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'citadel',
|
|
45
|
+
name: 'Citadel',
|
|
46
|
+
description: '5-layer safety system',
|
|
47
|
+
check: () => this._checkCitadel()
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'forge',
|
|
51
|
+
name: 'Forge',
|
|
52
|
+
description: 'Build system',
|
|
53
|
+
check: () => this._checkForge()
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'ocee',
|
|
57
|
+
name: 'OCEE',
|
|
58
|
+
description: 'Orchestrated continuous execution',
|
|
59
|
+
check: () => this._checkOCEE()
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'handoff',
|
|
63
|
+
name: 'Handoff',
|
|
64
|
+
description: 'Human-AI collaboration',
|
|
65
|
+
check: () => this._checkHandoff()
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'shadow_realm',
|
|
69
|
+
name: 'Shadow Realm',
|
|
70
|
+
description: 'Validation sandbox',
|
|
71
|
+
check: () => this._checkShadowRealm()
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'knowledge',
|
|
75
|
+
name: 'Knowledge Manager',
|
|
76
|
+
description: 'Build tracking and learning',
|
|
77
|
+
check: () => this._checkKnowledge()
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'server',
|
|
81
|
+
name: 'Express Server',
|
|
82
|
+
description: 'API server health',
|
|
83
|
+
check: () => this._checkServer()
|
|
84
|
+
}
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check health of all systems
|
|
90
|
+
*/
|
|
91
|
+
async checkAll() {
|
|
92
|
+
console.log('[HEALTH-CHECKER] Running full health check...');
|
|
93
|
+
const startTime = Date.now();
|
|
94
|
+
|
|
95
|
+
const result = {
|
|
96
|
+
timestamp: new Date().toISOString(),
|
|
97
|
+
duration: 0,
|
|
98
|
+
overall: 'healthy',
|
|
99
|
+
systems: [],
|
|
100
|
+
healthySystems: [],
|
|
101
|
+
unhealthySystems: [],
|
|
102
|
+
summary: {}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
for (const systemCheck of this.systemChecks) {
|
|
106
|
+
try {
|
|
107
|
+
const checkResult = await Promise.race([
|
|
108
|
+
systemCheck.check(),
|
|
109
|
+
this._timeout(this.config.checkTimeout)
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
const systemResult = {
|
|
113
|
+
id: systemCheck.id,
|
|
114
|
+
name: systemCheck.name,
|
|
115
|
+
description: systemCheck.description,
|
|
116
|
+
status: checkResult.healthy ? 'healthy' : 'unhealthy',
|
|
117
|
+
healthy: checkResult.healthy,
|
|
118
|
+
details: checkResult.details || {},
|
|
119
|
+
issues: checkResult.issues || [],
|
|
120
|
+
lastChecked: new Date().toISOString()
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
result.systems.push(systemResult);
|
|
124
|
+
|
|
125
|
+
if (systemResult.healthy) {
|
|
126
|
+
result.healthySystems.push(systemCheck.id);
|
|
127
|
+
} else {
|
|
128
|
+
result.unhealthySystems.push(systemCheck.id);
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
result.systems.push({
|
|
132
|
+
id: systemCheck.id,
|
|
133
|
+
name: systemCheck.name,
|
|
134
|
+
description: systemCheck.description,
|
|
135
|
+
status: 'error',
|
|
136
|
+
healthy: false,
|
|
137
|
+
error: error.message,
|
|
138
|
+
lastChecked: new Date().toISOString()
|
|
139
|
+
});
|
|
140
|
+
result.unhealthySystems.push(systemCheck.id);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Determine overall health
|
|
145
|
+
if (result.unhealthySystems.length === 0) {
|
|
146
|
+
result.overall = 'healthy';
|
|
147
|
+
} else if (result.unhealthySystems.length <= 2) {
|
|
148
|
+
result.overall = 'degraded';
|
|
149
|
+
} else {
|
|
150
|
+
result.overall = 'unhealthy';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
result.duration = Date.now() - startTime;
|
|
154
|
+
result.summary = {
|
|
155
|
+
total: this.systemChecks.length,
|
|
156
|
+
healthy: result.healthySystems.length,
|
|
157
|
+
unhealthy: result.unhealthySystems.length
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
console.log(`[HEALTH-CHECKER] Health check complete: ${result.overall} (${result.summary.healthy}/${result.summary.total} systems healthy)`);
|
|
161
|
+
this.emit('checkComplete', result);
|
|
162
|
+
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check health of a specific system
|
|
168
|
+
*/
|
|
169
|
+
async checkSystem(systemId) {
|
|
170
|
+
const systemCheck = this.systemChecks.find(s => s.id === systemId);
|
|
171
|
+
if (!systemCheck) {
|
|
172
|
+
return { error: `Unknown system: ${systemId}` };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const result = await systemCheck.check();
|
|
177
|
+
return {
|
|
178
|
+
id: systemCheck.id,
|
|
179
|
+
name: systemCheck.name,
|
|
180
|
+
...result,
|
|
181
|
+
lastChecked: new Date().toISOString()
|
|
182
|
+
};
|
|
183
|
+
} catch (error) {
|
|
184
|
+
return {
|
|
185
|
+
id: systemCheck.id,
|
|
186
|
+
name: systemCheck.name,
|
|
187
|
+
healthy: false,
|
|
188
|
+
error: error.message,
|
|
189
|
+
lastChecked: new Date().toISOString()
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get list of available system checks
|
|
196
|
+
*/
|
|
197
|
+
getAvailableChecks() {
|
|
198
|
+
return this.systemChecks.map(s => ({
|
|
199
|
+
id: s.id,
|
|
200
|
+
name: s.name,
|
|
201
|
+
description: s.description
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Individual system checks
|
|
206
|
+
|
|
207
|
+
async _checkSenate() {
|
|
208
|
+
const result = {
|
|
209
|
+
healthy: true,
|
|
210
|
+
details: {},
|
|
211
|
+
issues: []
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
// Check if senate files exist
|
|
216
|
+
const senateFiles = [
|
|
217
|
+
path.join(this.config.serverPath, 'senate.js'),
|
|
218
|
+
path.join(this.config.serverPath, 'senate-v2.js'),
|
|
219
|
+
path.join(this.config.serverPath, 'data', 'model-registry.json'),
|
|
220
|
+
path.join(this.config.serverPath, 'data', 'senate-weights-v2.json')
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
let filesFound = 0;
|
|
224
|
+
for (const file of senateFiles) {
|
|
225
|
+
if (fs.existsSync(file)) {
|
|
226
|
+
filesFound++;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
result.details.filesFound = filesFound;
|
|
231
|
+
result.details.totalFiles = senateFiles.length;
|
|
232
|
+
|
|
233
|
+
// Check model registry
|
|
234
|
+
const registryPath = path.join(this.config.serverPath, 'data', 'model-registry.json');
|
|
235
|
+
if (fs.existsSync(registryPath)) {
|
|
236
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
237
|
+
result.details.providersConfigured = Object.keys(registry.providers || {}).length;
|
|
238
|
+
result.details.modelsRegistered = Object.values(registry.providers || {})
|
|
239
|
+
.reduce((sum, p) => sum + (p.models?.length || 0), 0);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (filesFound < 2) {
|
|
243
|
+
result.healthy = false;
|
|
244
|
+
result.issues.push('Core Senate files missing');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
} catch (error) {
|
|
248
|
+
result.healthy = false;
|
|
249
|
+
result.issues.push(`Senate check failed: ${error.message}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async _checkCitadel() {
|
|
256
|
+
const result = {
|
|
257
|
+
healthy: true,
|
|
258
|
+
details: {},
|
|
259
|
+
issues: []
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
// Check if citadel files exist
|
|
264
|
+
const citadelPath = path.join(this.config.rootPath, 'citadel');
|
|
265
|
+
const citadelFiles = [
|
|
266
|
+
'enforcer.js',
|
|
267
|
+
'watchers.js',
|
|
268
|
+
'constitution-loader.js',
|
|
269
|
+
'file-guard.js'
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
let filesFound = 0;
|
|
273
|
+
for (const file of citadelFiles) {
|
|
274
|
+
if (fs.existsSync(path.join(citadelPath, file))) {
|
|
275
|
+
filesFound++;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
result.details.filesFound = filesFound;
|
|
280
|
+
result.details.totalFiles = citadelFiles.length;
|
|
281
|
+
|
|
282
|
+
// Check audit logs
|
|
283
|
+
const auditPath = path.join(citadelPath, 'audit');
|
|
284
|
+
if (fs.existsSync(auditPath)) {
|
|
285
|
+
const auditFiles = fs.readdirSync(auditPath).filter(f => f.endsWith('.json'));
|
|
286
|
+
result.details.auditFilesCount = auditFiles.length;
|
|
287
|
+
|
|
288
|
+
// Check most recent audit
|
|
289
|
+
if (auditFiles.length > 0) {
|
|
290
|
+
const latestAudit = auditFiles.sort().pop();
|
|
291
|
+
const latestPath = path.join(auditPath, latestAudit);
|
|
292
|
+
const auditStat = fs.statSync(latestPath);
|
|
293
|
+
result.details.lastAudit = auditStat.mtime.toISOString();
|
|
294
|
+
|
|
295
|
+
// Warn if no audit in 24 hours
|
|
296
|
+
const hoursSinceAudit = (Date.now() - auditStat.mtime.getTime()) / (1000 * 60 * 60);
|
|
297
|
+
if (hoursSinceAudit > 24) {
|
|
298
|
+
result.issues.push(`No audit activity in ${Math.round(hoursSinceAudit)} hours`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (filesFound < 3) {
|
|
304
|
+
result.healthy = false;
|
|
305
|
+
result.issues.push('Core Citadel files missing');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
} catch (error) {
|
|
309
|
+
result.healthy = false;
|
|
310
|
+
result.issues.push(`Citadel check failed: ${error.message}`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async _checkForge() {
|
|
317
|
+
const result = {
|
|
318
|
+
healthy: true,
|
|
319
|
+
details: {},
|
|
320
|
+
issues: []
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const forgePath = path.join(this.config.rootPath, 'forge');
|
|
325
|
+
const forgeFiles = [
|
|
326
|
+
'forge-core.js',
|
|
327
|
+
'task-decomposer.js',
|
|
328
|
+
'intent-matcher.js',
|
|
329
|
+
'diagnostician-v2.js',
|
|
330
|
+
'prescriber.js',
|
|
331
|
+
'forge-loop.js'
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
let filesFound = 0;
|
|
335
|
+
for (const file of forgeFiles) {
|
|
336
|
+
if (fs.existsSync(path.join(forgePath, file))) {
|
|
337
|
+
filesFound++;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
result.details.filesFound = filesFound;
|
|
342
|
+
result.details.totalFiles = forgeFiles.length;
|
|
343
|
+
result.details.completeness = Math.round((filesFound / forgeFiles.length) * 100) + '%';
|
|
344
|
+
|
|
345
|
+
if (filesFound < 4) {
|
|
346
|
+
result.healthy = false;
|
|
347
|
+
result.issues.push('Core Forge components missing');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
} catch (error) {
|
|
351
|
+
result.healthy = false;
|
|
352
|
+
result.issues.push(`Forge check failed: ${error.message}`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return result;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async _checkOCEE() {
|
|
359
|
+
const result = {
|
|
360
|
+
healthy: true,
|
|
361
|
+
details: {},
|
|
362
|
+
issues: []
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
const oceePath = path.join(this.config.serverPath, 'ocee');
|
|
367
|
+
const oceeFiles = [
|
|
368
|
+
'ocee-core.js',
|
|
369
|
+
'persistent-task-queue.js',
|
|
370
|
+
'checkpoint-manager.js',
|
|
371
|
+
'build-session-manager.js',
|
|
372
|
+
'build-graph-generator.js'
|
|
373
|
+
];
|
|
374
|
+
|
|
375
|
+
let filesFound = 0;
|
|
376
|
+
for (const file of oceeFiles) {
|
|
377
|
+
if (fs.existsSync(path.join(oceePath, file))) {
|
|
378
|
+
filesFound++;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
result.details.filesFound = filesFound;
|
|
383
|
+
result.details.totalFiles = oceeFiles.length;
|
|
384
|
+
|
|
385
|
+
// Check for active tasks
|
|
386
|
+
const taskQueuePath = path.join(oceePath, 'queue');
|
|
387
|
+
if (fs.existsSync(taskQueuePath)) {
|
|
388
|
+
try {
|
|
389
|
+
const queueFiles = fs.readdirSync(taskQueuePath);
|
|
390
|
+
result.details.pendingTasks = queueFiles.length;
|
|
391
|
+
} catch (e) {
|
|
392
|
+
result.details.pendingTasks = 'unknown';
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (filesFound < 3) {
|
|
397
|
+
result.healthy = false;
|
|
398
|
+
result.issues.push('Core OCEE components missing');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
} catch (error) {
|
|
402
|
+
result.healthy = false;
|
|
403
|
+
result.issues.push(`OCEE check failed: ${error.message}`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return result;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async _checkHandoff() {
|
|
410
|
+
const result = {
|
|
411
|
+
healthy: true,
|
|
412
|
+
details: {},
|
|
413
|
+
issues: []
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
const handoffPath = path.join(this.config.serverPath, 'handoff');
|
|
418
|
+
const handoffFiles = [
|
|
419
|
+
'index.js',
|
|
420
|
+
'risk-scorer.js',
|
|
421
|
+
'gate-checker.js',
|
|
422
|
+
'mission-state.js',
|
|
423
|
+
'audit-logger.js',
|
|
424
|
+
'evidence-bundle.js',
|
|
425
|
+
'cost-tracker.js'
|
|
426
|
+
];
|
|
427
|
+
|
|
428
|
+
let filesFound = 0;
|
|
429
|
+
for (const file of handoffFiles) {
|
|
430
|
+
if (fs.existsSync(path.join(handoffPath, file))) {
|
|
431
|
+
filesFound++;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
result.details.filesFound = filesFound;
|
|
436
|
+
result.details.totalFiles = handoffFiles.length;
|
|
437
|
+
|
|
438
|
+
if (filesFound < 5) {
|
|
439
|
+
result.healthy = false;
|
|
440
|
+
result.issues.push('Core Handoff components missing');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
} catch (error) {
|
|
444
|
+
result.healthy = false;
|
|
445
|
+
result.issues.push(`Handoff check failed: ${error.message}`);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async _checkShadowRealm() {
|
|
452
|
+
const result = {
|
|
453
|
+
healthy: true,
|
|
454
|
+
details: {},
|
|
455
|
+
issues: []
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
const shadowPath = path.join(this.config.rootPath, 'build-engine', 'orion-core');
|
|
460
|
+
const shadowFiles = [
|
|
461
|
+
'shadow-realm-v2.js',
|
|
462
|
+
'foreman.js',
|
|
463
|
+
'healing-loop.js'
|
|
464
|
+
];
|
|
465
|
+
|
|
466
|
+
let filesFound = 0;
|
|
467
|
+
for (const file of shadowFiles) {
|
|
468
|
+
if (fs.existsSync(path.join(shadowPath, file))) {
|
|
469
|
+
filesFound++;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
result.details.filesFound = filesFound;
|
|
474
|
+
result.details.totalFiles = shadowFiles.length;
|
|
475
|
+
|
|
476
|
+
// Test shadow realm validation
|
|
477
|
+
try {
|
|
478
|
+
const shadowRealm = require(path.join(shadowPath, 'shadow-realm-v2.js'));
|
|
479
|
+
const testResult = shadowRealm.validateInSandbox('console.log("test");', 'test.js');
|
|
480
|
+
result.details.validationWorking = testResult.syntaxValid === true;
|
|
481
|
+
} catch (e) {
|
|
482
|
+
result.details.validationWorking = false;
|
|
483
|
+
result.issues.push(`Shadow Realm validation test failed: ${e.message}`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (filesFound < 2) {
|
|
487
|
+
result.healthy = false;
|
|
488
|
+
result.issues.push('Core Shadow Realm components missing');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
} catch (error) {
|
|
492
|
+
result.healthy = false;
|
|
493
|
+
result.issues.push(`Shadow Realm check failed: ${error.message}`);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async _checkKnowledge() {
|
|
500
|
+
const result = {
|
|
501
|
+
healthy: true,
|
|
502
|
+
details: {},
|
|
503
|
+
issues: []
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
try {
|
|
507
|
+
const knowledgePath = path.join(this.config.rootPath, 'orion-knowledge.json');
|
|
508
|
+
|
|
509
|
+
if (fs.existsSync(knowledgePath)) {
|
|
510
|
+
const knowledge = JSON.parse(fs.readFileSync(knowledgePath, 'utf8'));
|
|
511
|
+
result.details.buildsTracked = knowledge.builds?.length || 0;
|
|
512
|
+
result.details.capabilitiesTracked = knowledge.capabilities?.length || 0;
|
|
513
|
+
|
|
514
|
+
// Check for recent activity
|
|
515
|
+
if (knowledge.builds && knowledge.builds.length > 0) {
|
|
516
|
+
const latestBuild = knowledge.builds[knowledge.builds.length - 1];
|
|
517
|
+
result.details.lastBuild = latestBuild.timestamp || latestBuild.date;
|
|
518
|
+
}
|
|
519
|
+
} else {
|
|
520
|
+
result.healthy = false;
|
|
521
|
+
result.issues.push('orion-knowledge.json not found');
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
} catch (error) {
|
|
525
|
+
result.healthy = false;
|
|
526
|
+
result.issues.push(`Knowledge check failed: ${error.message}`);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return result;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async _checkServer() {
|
|
533
|
+
const result = {
|
|
534
|
+
healthy: true,
|
|
535
|
+
details: {},
|
|
536
|
+
issues: []
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
// Check if server files exist
|
|
541
|
+
const serverFiles = [
|
|
542
|
+
path.join(this.config.serverPath, 'package.json'),
|
|
543
|
+
path.join(this.config.serverPath, 'index.js')
|
|
544
|
+
];
|
|
545
|
+
|
|
546
|
+
let filesFound = 0;
|
|
547
|
+
for (const file of serverFiles) {
|
|
548
|
+
if (fs.existsSync(file)) {
|
|
549
|
+
filesFound++;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
result.details.filesFound = filesFound;
|
|
554
|
+
|
|
555
|
+
// Check package.json for dependencies
|
|
556
|
+
const pkgPath = path.join(this.config.serverPath, 'package.json');
|
|
557
|
+
if (fs.existsSync(pkgPath)) {
|
|
558
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
559
|
+
result.details.dependencies = Object.keys(pkg.dependencies || {}).length;
|
|
560
|
+
result.details.nodeVersion = pkg.engines?.node || 'not specified';
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Check node_modules exists
|
|
564
|
+
const nodeModulesPath = path.join(this.config.serverPath, 'node_modules');
|
|
565
|
+
result.details.nodeModulesInstalled = fs.existsSync(nodeModulesPath);
|
|
566
|
+
|
|
567
|
+
if (!result.details.nodeModulesInstalled) {
|
|
568
|
+
result.issues.push('node_modules not installed - run npm install');
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
} catch (error) {
|
|
572
|
+
result.healthy = false;
|
|
573
|
+
result.issues.push(`Server check failed: ${error.message}`);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return result;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
_timeout(ms) {
|
|
580
|
+
return new Promise((_, reject) =>
|
|
581
|
+
setTimeout(() => reject(new Error('Health check timeout')), ms)
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
module.exports = HealthChecker;
|