thuban 0.3.3 → 0.3.4
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/dist/LICENSE +21 -0
- package/dist/README.md +185 -0
- package/dist/cli.js +2 -0
- package/dist/packages/scanner/ai-confidence-scorer.js +1 -0
- package/dist/packages/scanner/alert-manager.js +1 -0
- package/dist/packages/scanner/baseline-manager.js +1 -0
- package/dist/packages/scanner/code-scanner.js +1 -0
- package/dist/packages/scanner/codebase-passport.js +1 -0
- package/dist/packages/scanner/copy-paste-detector.js +1 -0
- package/dist/packages/scanner/dependency-graph.js +1 -0
- package/dist/packages/scanner/drift-detector.js +1 -0
- package/dist/packages/scanner/executive-report.js +1 -0
- package/dist/packages/scanner/file-collector.js +1 -0
- package/dist/packages/scanner/file-watcher.js +1 -0
- package/dist/packages/scanner/ghost-code-detector.js +1 -0
- package/dist/packages/scanner/hallucination-detector.js +1 -0
- package/dist/packages/scanner/health-checker.js +1 -0
- package/dist/packages/scanner/html-report.js +1 -0
- package/dist/packages/scanner/index.js +1 -0
- package/dist/packages/scanner/license-manager.js +1 -0
- package/dist/packages/scanner/master-health-checker.js +1 -0
- package/dist/packages/scanner/monitor-notifier.js +1 -0
- package/dist/packages/scanner/monitor-service.js +1 -0
- package/dist/packages/scanner/monitor-store.js +1 -0
- package/dist/packages/scanner/pre-commit-gate.js +1 -0
- package/dist/packages/scanner/scan-diff.js +1 -0
- package/dist/packages/scanner/scan-runner.js +1 -0
- package/dist/packages/scanner/secret-scanner.js +1 -0
- package/dist/packages/scanner/sentinel-core.js +1 -0
- package/dist/packages/scanner/sentinel-knowledge.js +1 -0
- package/dist/packages/scanner/tech-debt-analyzer.js +1 -0
- package/dist/packages/scanner/tech-debt-cost.js +1 -0
- package/dist/packages/scanner/widget-generator.js +1 -0
- package/package.json +12 -7
- package/cli.js +0 -2627
- package/packages/scanner/ai-confidence-scorer.js +0 -260
- package/packages/scanner/alert-manager.js +0 -398
- package/packages/scanner/baseline-manager.js +0 -109
- package/packages/scanner/code-scanner.js +0 -758
- package/packages/scanner/codebase-passport.js +0 -299
- package/packages/scanner/copy-paste-detector.js +0 -276
- package/packages/scanner/dependency-graph.js +0 -541
- package/packages/scanner/drift-detector.js +0 -423
- package/packages/scanner/executive-report.js +0 -774
- package/packages/scanner/file-collector.js +0 -64
- package/packages/scanner/file-watcher.js +0 -323
- package/packages/scanner/ghost-code-detector.js +0 -301
- package/packages/scanner/hallucination-detector.js +0 -822
- package/packages/scanner/health-checker.js +0 -586
- package/packages/scanner/html-report.js +0 -634
- package/packages/scanner/index.js +0 -100
- package/packages/scanner/license-manager.js +0 -331
- package/packages/scanner/master-health-checker.js +0 -513
- package/packages/scanner/monitor-notifier.js +0 -64
- package/packages/scanner/monitor-service.js +0 -103
- package/packages/scanner/monitor-store.js +0 -117
- package/packages/scanner/pre-commit-gate.js +0 -216
- package/packages/scanner/scan-diff.js +0 -50
- package/packages/scanner/scan-runner.js +0 -172
- package/packages/scanner/secret-scanner.js +0 -612
- package/packages/scanner/secret-scanner.test.js +0 -103
- package/packages/scanner/sentinel-core.js +0 -616
- package/packages/scanner/sentinel-knowledge.js +0 -322
- package/packages/scanner/tech-debt-analyzer.js +0 -583
- package/packages/scanner/tech-debt-cost.js +0 -194
- package/packages/scanner/widget-generator.js +0 -415
|
@@ -1,513 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Master Health Checker
|
|
3
|
-
*
|
|
4
|
-
* @purpose Comprehensive health monitoring for all 47 Orion failure points
|
|
5
|
-
* @module sentinel
|
|
6
|
-
* @layer infrastructure
|
|
7
|
-
* @imports-from fs, path, child_process, events
|
|
8
|
-
* @exports-to server/autonomous/health-scheduler.js
|
|
9
|
-
* @side-effects File system reads, Process spawning, Network requests
|
|
10
|
-
* @critical-for System reliability and self-healing
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
const { execSync, exec } = require('child_process');
|
|
16
|
-
const EventEmitter = require('events');
|
|
17
|
-
|
|
18
|
-
// Paths
|
|
19
|
-
const ROOT = path.join(__dirname, '..');
|
|
20
|
-
const SERVER = path.join(ROOT, 'server');
|
|
21
|
-
const MANIFEST_PATH = path.join(SERVER, 'data', 'health-check-manifest.json');
|
|
22
|
-
|
|
23
|
-
class MasterHealthChecker extends EventEmitter {
|
|
24
|
-
constructor(config = {}) {
|
|
25
|
-
super();
|
|
26
|
-
this.config = {
|
|
27
|
-
rootPath: config.rootPath || ROOT,
|
|
28
|
-
serverPath: config.serverPath || SERVER,
|
|
29
|
-
timeout: config.timeout || 5000,
|
|
30
|
-
...config
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
this.manifest = this._loadManifest();
|
|
34
|
-
this.lastResults = null;
|
|
35
|
-
this.issueCount = 0;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
_loadManifest() {
|
|
39
|
-
try {
|
|
40
|
-
if (fs.existsSync(MANIFEST_PATH)) {
|
|
41
|
-
return JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
|
|
42
|
-
}
|
|
43
|
-
} catch (e) {
|
|
44
|
-
console.error('[MASTER-HEALTH] Failed to load manifest:', e.message);
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Run ALL 47 health checks
|
|
51
|
-
*/
|
|
52
|
-
async runFullCheck() {
|
|
53
|
-
console.log('[MASTER-HEALTH] Starting full system health check (47 checkpoints)...');
|
|
54
|
-
const startTime = Date.now();
|
|
55
|
-
|
|
56
|
-
const results = {
|
|
57
|
-
timestamp: new Date().toISOString(),
|
|
58
|
-
duration: 0,
|
|
59
|
-
totalChecks: 0,
|
|
60
|
-
passed: 0,
|
|
61
|
-
failed: 0,
|
|
62
|
-
warnings: 0,
|
|
63
|
-
systems: {},
|
|
64
|
-
criticalIssues: [],
|
|
65
|
-
autoHealAttempted: [],
|
|
66
|
-
humanEscalationNeeded: []
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// Run checks for each system
|
|
70
|
-
const systems = [
|
|
71
|
-
{ id: 'server', fn: () => this._checkServer() },
|
|
72
|
-
{ id: 'senate', fn: () => this._checkSenate() },
|
|
73
|
-
{ id: 'citadel', fn: () => this._checkCitadel() },
|
|
74
|
-
{ id: 'shadowRealm', fn: () => this._checkShadowRealm() },
|
|
75
|
-
{ id: 'forge', fn: () => this._checkForge() },
|
|
76
|
-
{ id: 'ocee', fn: () => this._checkOCEE() },
|
|
77
|
-
{ id: 'memory', fn: () => this._checkMemory() },
|
|
78
|
-
{ id: 'external', fn: () => this._checkExternal() }
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
for (const system of systems) {
|
|
82
|
-
try {
|
|
83
|
-
const systemResult = await system.fn();
|
|
84
|
-
results.systems[system.id] = systemResult;
|
|
85
|
-
|
|
86
|
-
results.totalChecks += systemResult.checks.length;
|
|
87
|
-
results.passed += systemResult.checks.filter(c => c.status === 'PASS').length;
|
|
88
|
-
results.failed += systemResult.checks.filter(c => c.status === 'FAIL').length;
|
|
89
|
-
results.warnings += systemResult.checks.filter(c => c.status === 'WARN').length;
|
|
90
|
-
|
|
91
|
-
// Collect critical issues
|
|
92
|
-
const critical = systemResult.checks.filter(c => c.status === 'FAIL' && c.criticality === 'CRITICAL');
|
|
93
|
-
results.criticalIssues.push(...critical);
|
|
94
|
-
|
|
95
|
-
// Collect escalation needs
|
|
96
|
-
const needsHuman = systemResult.checks.filter(c => c.needsHumanEscalation);
|
|
97
|
-
results.humanEscalationNeeded.push(...needsHuman);
|
|
98
|
-
|
|
99
|
-
} catch (error) {
|
|
100
|
-
results.systems[system.id] = {
|
|
101
|
-
status: 'ERROR',
|
|
102
|
-
error: error.message,
|
|
103
|
-
checks: []
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
results.duration = Date.now() - startTime;
|
|
109
|
-
results.overallStatus = this._calculateOverallStatus(results);
|
|
110
|
-
|
|
111
|
-
this.lastResults = results;
|
|
112
|
-
this.emit('checkComplete', results);
|
|
113
|
-
|
|
114
|
-
console.log(`[MASTER-HEALTH] Complete: ${results.passed}/${results.totalChecks} passed (${results.duration}ms)`);
|
|
115
|
-
|
|
116
|
-
return results;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Run quick check (critical systems only)
|
|
121
|
-
*/
|
|
122
|
-
async runQuickCheck() {
|
|
123
|
-
console.log('[MASTER-HEALTH] Running quick check (critical systems)...');
|
|
124
|
-
|
|
125
|
-
const results = {
|
|
126
|
-
timestamp: new Date().toISOString(),
|
|
127
|
-
type: 'quick',
|
|
128
|
-
checks: []
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// SRV-001: Server running
|
|
132
|
-
results.checks.push(await this._check('SRV-001', 'Server Process', () => {
|
|
133
|
-
return this._fileExists(path.join(this.config.serverPath, 'server.js'));
|
|
134
|
-
}));
|
|
135
|
-
|
|
136
|
-
// SEN-003: Providers responding (simplified)
|
|
137
|
-
results.checks.push(await this._check('SEN-003', 'Model Registry', () => {
|
|
138
|
-
const registry = path.join(this.config.serverPath, 'data', 'model-registry.json');
|
|
139
|
-
if (!fs.existsSync(registry)) return false;
|
|
140
|
-
const data = JSON.parse(fs.readFileSync(registry, 'utf8'));
|
|
141
|
-
return Object.keys(data.providers || {}).length >= 3;
|
|
142
|
-
}));
|
|
143
|
-
|
|
144
|
-
// CIT-001: Constitution loaded
|
|
145
|
-
results.checks.push(await this._check('CIT-001', 'Constitution', () => {
|
|
146
|
-
const constitution = path.join(this.config.rootPath, 'citadel', 'constitution.json');
|
|
147
|
-
return this._fileExists(constitution) && this._isValidJson(constitution);
|
|
148
|
-
}));
|
|
149
|
-
|
|
150
|
-
// CIT-002: File guard active
|
|
151
|
-
results.checks.push(await this._check('CIT-002', 'File Guard', () => {
|
|
152
|
-
return this._fileExists(path.join(this.config.rootPath, 'citadel', 'file-guard.js'));
|
|
153
|
-
}));
|
|
154
|
-
|
|
155
|
-
const passed = results.checks.filter(c => c.status === 'PASS').length;
|
|
156
|
-
console.log(`[MASTER-HEALTH] Quick check: ${passed}/${results.checks.length} passed`);
|
|
157
|
-
|
|
158
|
-
return results;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// ========== SYSTEM CHECKS ==========
|
|
162
|
-
|
|
163
|
-
async _checkServer() {
|
|
164
|
-
const checks = [];
|
|
165
|
-
|
|
166
|
-
// SRV-001: Server process files exist
|
|
167
|
-
checks.push(await this._check('SRV-001', 'Server Files', () => {
|
|
168
|
-
return this._fileExists(path.join(this.config.serverPath, 'server.js')) &&
|
|
169
|
-
this._fileExists(path.join(this.config.serverPath, 'package.json'));
|
|
170
|
-
}, 'CRITICAL'));
|
|
171
|
-
|
|
172
|
-
// SRV-002: Memory (check node_modules size as proxy)
|
|
173
|
-
checks.push(await this._check('SRV-002', 'Node Modules', () => {
|
|
174
|
-
return this._fileExists(path.join(this.config.serverPath, 'node_modules'));
|
|
175
|
-
}, 'HIGH', 'npm install'));
|
|
176
|
-
|
|
177
|
-
// SRV-003: Package.json valid
|
|
178
|
-
checks.push(await this._check('SRV-003', 'Package Valid', () => {
|
|
179
|
-
return this._isValidJson(path.join(this.config.serverPath, 'package.json'));
|
|
180
|
-
}, 'HIGH'));
|
|
181
|
-
|
|
182
|
-
// SRV-004: Data directory exists
|
|
183
|
-
checks.push(await this._check('SRV-004', 'Data Directory', () => {
|
|
184
|
-
return this._dirExists(path.join(this.config.serverPath, 'data'));
|
|
185
|
-
}, 'MEDIUM'));
|
|
186
|
-
|
|
187
|
-
// SRV-005: Environment file or vars
|
|
188
|
-
checks.push(await this._check('SRV-005', 'Environment', () => {
|
|
189
|
-
return this._fileExists(path.join(this.config.rootPath, '.env')) ||
|
|
190
|
-
process.env.ANTHROPIC_API_KEY !== undefined;
|
|
191
|
-
}, 'HIGH'));
|
|
192
|
-
|
|
193
|
-
return { status: this._systemStatus(checks), checks };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async _checkSenate() {
|
|
197
|
-
const checks = [];
|
|
198
|
-
|
|
199
|
-
// SEN-001: Model registry
|
|
200
|
-
checks.push(await this._check('SEN-001', 'Model Registry', () => {
|
|
201
|
-
const file = path.join(this.config.serverPath, 'data', 'model-registry.json');
|
|
202
|
-
return this._isValidJson(file);
|
|
203
|
-
}, 'CRITICAL', 'Restore from snapshot'));
|
|
204
|
-
|
|
205
|
-
// SEN-002: Senate weights
|
|
206
|
-
checks.push(await this._check('SEN-002', 'Senate Weights', () => {
|
|
207
|
-
const file = path.join(this.config.serverPath, 'data', 'senate-weights-v2.json');
|
|
208
|
-
return this._isValidJson(file);
|
|
209
|
-
}, 'HIGH', 'Reset to defaults'));
|
|
210
|
-
|
|
211
|
-
// SEN-003: Providers configured
|
|
212
|
-
checks.push(await this._check('SEN-003', 'Providers Configured', () => {
|
|
213
|
-
const file = path.join(this.config.serverPath, 'data', 'model-registry.json');
|
|
214
|
-
if (!this._isValidJson(file)) return false;
|
|
215
|
-
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
216
|
-
return Object.keys(data.providers || {}).length >= 5;
|
|
217
|
-
}, 'CRITICAL'));
|
|
218
|
-
|
|
219
|
-
// SEN-004: Senate core files
|
|
220
|
-
checks.push(await this._check('SEN-004', 'Senate Core', () => {
|
|
221
|
-
return this._fileExists(path.join(this.config.serverPath, 'senate-v2.js')) ||
|
|
222
|
-
this._fileExists(path.join(this.config.serverPath, 'senate.js'));
|
|
223
|
-
}, 'CRITICAL'));
|
|
224
|
-
|
|
225
|
-
// SEN-005: Response cache not corrupted
|
|
226
|
-
checks.push(await this._check('SEN-005', 'Response Cache', () => {
|
|
227
|
-
const file = path.join(this.config.serverPath, 'data', 'response-cache.json');
|
|
228
|
-
return !this._fileExists(file) || this._isValidJson(file);
|
|
229
|
-
}, 'LOW'));
|
|
230
|
-
|
|
231
|
-
return { status: this._systemStatus(checks), checks };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async _checkCitadel() {
|
|
235
|
-
const checks = [];
|
|
236
|
-
|
|
237
|
-
// CIT-001: Constitution
|
|
238
|
-
checks.push(await this._check('CIT-001', 'Constitution', () => {
|
|
239
|
-
const file = path.join(this.config.rootPath, 'citadel', 'constitution.json');
|
|
240
|
-
return this._isValidJson(file);
|
|
241
|
-
}, 'CRITICAL', 'HALT - Safety rules missing'));
|
|
242
|
-
|
|
243
|
-
// CIT-002: File guard
|
|
244
|
-
checks.push(await this._check('CIT-002', 'File Guard', () => {
|
|
245
|
-
return this._fileExists(path.join(this.config.rootPath, 'citadel', 'file-guard.js'));
|
|
246
|
-
}, 'CRITICAL'));
|
|
247
|
-
|
|
248
|
-
// CIT-003: Enforcer
|
|
249
|
-
checks.push(await this._check('CIT-003', 'Enforcer', () => {
|
|
250
|
-
return this._fileExists(path.join(this.config.rootPath, 'citadel', 'enforcer.js'));
|
|
251
|
-
}, 'CRITICAL'));
|
|
252
|
-
|
|
253
|
-
// CIT-004: Watchers
|
|
254
|
-
checks.push(await this._check('CIT-004', 'Watchers', () => {
|
|
255
|
-
return this._fileExists(path.join(this.config.rootPath, 'citadel', 'watchers.js'));
|
|
256
|
-
}, 'HIGH'));
|
|
257
|
-
|
|
258
|
-
// CIT-005: Audit directory
|
|
259
|
-
checks.push(await this._check('CIT-005', 'Audit Directory', () => {
|
|
260
|
-
return this._dirExists(path.join(this.config.rootPath, 'citadel', 'audit'));
|
|
261
|
-
}, 'MEDIUM', 'Create directory'));
|
|
262
|
-
|
|
263
|
-
return { status: this._systemStatus(checks), checks };
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async _checkShadowRealm() {
|
|
267
|
-
const checks = [];
|
|
268
|
-
|
|
269
|
-
// SHD-001: Shadow realm file
|
|
270
|
-
checks.push(await this._check('SHD-001', 'Shadow Realm V2', () => {
|
|
271
|
-
return this._fileExists(path.join(this.config.rootPath, 'build-engine', 'orion-core', 'shadow-realm-v2.js'));
|
|
272
|
-
}, 'CRITICAL'));
|
|
273
|
-
|
|
274
|
-
// SHD-002: Node available
|
|
275
|
-
checks.push(await this._check('SHD-002', 'Node Binary', () => {
|
|
276
|
-
try {
|
|
277
|
-
execSync('node --version', { stdio: 'pipe' });
|
|
278
|
-
return true;
|
|
279
|
-
} catch { return false; }
|
|
280
|
-
}, 'CRITICAL'));
|
|
281
|
-
|
|
282
|
-
// SHD-003: Temp directory writable
|
|
283
|
-
checks.push(await this._check('SHD-003', 'Temp Writable', () => {
|
|
284
|
-
const os = require('os');
|
|
285
|
-
const testFile = path.join(os.tmpdir(), 'orion-health-test-' + Date.now());
|
|
286
|
-
try {
|
|
287
|
-
fs.writeFileSync(testFile, 'test');
|
|
288
|
-
fs.unlinkSync(testFile);
|
|
289
|
-
return true;
|
|
290
|
-
} catch { return false; }
|
|
291
|
-
}, 'HIGH'));
|
|
292
|
-
|
|
293
|
-
// SHD-004: Healing loop
|
|
294
|
-
checks.push(await this._check('SHD-004', 'Healing Loop', () => {
|
|
295
|
-
return this._fileExists(path.join(this.config.rootPath, 'build-engine', 'orion-core', 'healing-loop.js'));
|
|
296
|
-
}, 'HIGH'));
|
|
297
|
-
|
|
298
|
-
return { status: this._systemStatus(checks), checks };
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async _checkForge() {
|
|
302
|
-
const checks = [];
|
|
303
|
-
const forgePath = path.join(this.config.rootPath, 'forge');
|
|
304
|
-
|
|
305
|
-
// FRG-001: Forge core
|
|
306
|
-
checks.push(await this._check('FRG-001', 'Forge Core', () => {
|
|
307
|
-
return this._fileExists(path.join(forgePath, 'forge-core.js'));
|
|
308
|
-
}, 'HIGH'));
|
|
309
|
-
|
|
310
|
-
// FRG-002: Task decomposer
|
|
311
|
-
checks.push(await this._check('FRG-002', 'Task Decomposer', () => {
|
|
312
|
-
return this._fileExists(path.join(forgePath, 'task-decomposer.js'));
|
|
313
|
-
}, 'MEDIUM'));
|
|
314
|
-
|
|
315
|
-
// FRG-003: Intent matcher
|
|
316
|
-
checks.push(await this._check('FRG-003', 'Intent Matcher', () => {
|
|
317
|
-
return this._fileExists(path.join(forgePath, 'intent-matcher.js'));
|
|
318
|
-
}, 'MEDIUM'));
|
|
319
|
-
|
|
320
|
-
// FRG-004: Diagnostician
|
|
321
|
-
checks.push(await this._check('FRG-004', 'Diagnostician', () => {
|
|
322
|
-
return this._fileExists(path.join(forgePath, 'diagnostician-v2.js')) ||
|
|
323
|
-
this._fileExists(path.join(forgePath, 'diagnostician.js'));
|
|
324
|
-
}, 'MEDIUM'));
|
|
325
|
-
|
|
326
|
-
// FRG-005: Foreman
|
|
327
|
-
checks.push(await this._check('FRG-005', 'Foreman', () => {
|
|
328
|
-
return this._fileExists(path.join(this.config.rootPath, 'build-engine', 'orion-core', 'foreman.js'));
|
|
329
|
-
}, 'HIGH'));
|
|
330
|
-
|
|
331
|
-
return { status: this._systemStatus(checks), checks };
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
async _checkOCEE() {
|
|
335
|
-
const checks = [];
|
|
336
|
-
const oceePath = path.join(this.config.serverPath, 'ocee');
|
|
337
|
-
|
|
338
|
-
// OCE-001: OCEE core
|
|
339
|
-
checks.push(await this._check('OCE-001', 'OCEE Core', () => {
|
|
340
|
-
return this._fileExists(path.join(oceePath, 'ocee-core.js'));
|
|
341
|
-
}, 'HIGH'));
|
|
342
|
-
|
|
343
|
-
// OCE-002: Persistent task queue
|
|
344
|
-
checks.push(await this._check('OCE-002', 'Task Queue', () => {
|
|
345
|
-
return this._fileExists(path.join(oceePath, 'persistent-task-queue.js'));
|
|
346
|
-
}, 'HIGH'));
|
|
347
|
-
|
|
348
|
-
// OCE-003: Checkpoint manager
|
|
349
|
-
checks.push(await this._check('OCE-003', 'Checkpoint Manager', () => {
|
|
350
|
-
return this._fileExists(path.join(oceePath, 'checkpoint-manager.js'));
|
|
351
|
-
}, 'HIGH'));
|
|
352
|
-
|
|
353
|
-
// OCE-004: Build sessions valid
|
|
354
|
-
checks.push(await this._check('OCE-004', 'Build Sessions', () => {
|
|
355
|
-
const file = path.join(this.config.serverPath, 'data', 'build-sessions.json');
|
|
356
|
-
return !this._fileExists(file) || this._isValidJson(file);
|
|
357
|
-
}, 'MEDIUM'));
|
|
358
|
-
|
|
359
|
-
// OCE-005: Executor status
|
|
360
|
-
checks.push(await this._check('OCE-005', 'Executor Status', () => {
|
|
361
|
-
const file = path.join(this.config.serverPath, 'data', 'executor-status.json');
|
|
362
|
-
return this._isValidJson(file);
|
|
363
|
-
}, 'MEDIUM'));
|
|
364
|
-
|
|
365
|
-
return { status: this._systemStatus(checks), checks };
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async _checkMemory() {
|
|
369
|
-
const checks = [];
|
|
370
|
-
|
|
371
|
-
// MEM-001: orion-knowledge.json
|
|
372
|
-
checks.push(await this._check('MEM-001', 'Orion Knowledge', () => {
|
|
373
|
-
return this._isValidJson(path.join(this.config.rootPath, 'orion-knowledge.json'));
|
|
374
|
-
}, 'MEDIUM', 'Reinitialize'));
|
|
375
|
-
|
|
376
|
-
// MEM-002: Memory bank files
|
|
377
|
-
checks.push(await this._check('MEM-002', 'Memory Bank', () => {
|
|
378
|
-
const mbPath = path.join(this.config.rootPath, 'memory-bank');
|
|
379
|
-
return this._fileExists(path.join(mbPath, 'progress.md')) &&
|
|
380
|
-
this._fileExists(path.join(mbPath, 'activeContext.md'));
|
|
381
|
-
}, 'MEDIUM'));
|
|
382
|
-
|
|
383
|
-
// MEM-003: Snapshot manifest
|
|
384
|
-
checks.push(await this._check('MEM-003', 'Snapshot Manifest', () => {
|
|
385
|
-
const file = path.join(this.config.serverPath, '.orion', 'snapshot-manifest.json');
|
|
386
|
-
return !this._fileExists(file) || this._isValidJson(file);
|
|
387
|
-
}, 'MEDIUM'));
|
|
388
|
-
|
|
389
|
-
// MEM-004: Build logs directory
|
|
390
|
-
checks.push(await this._check('MEM-004', 'Build Logs', () => {
|
|
391
|
-
return this._dirExists(path.join(this.config.serverPath, 'data', 'build-logs'));
|
|
392
|
-
}, 'MEDIUM'));
|
|
393
|
-
|
|
394
|
-
// MEM-005: Failure memory service
|
|
395
|
-
checks.push(await this._check('MEM-005', 'Failure Memory', () => {
|
|
396
|
-
return this._fileExists(path.join(this.config.serverPath, 'services', 'failure-memory.js'));
|
|
397
|
-
}, 'MEDIUM'));
|
|
398
|
-
|
|
399
|
-
return { status: this._systemStatus(checks), checks };
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
async _checkExternal() {
|
|
403
|
-
const checks = [];
|
|
404
|
-
|
|
405
|
-
// EXT-001: Zoho tokens (if configured)
|
|
406
|
-
checks.push(await this._check('EXT-001', 'Zoho Tokens', () => {
|
|
407
|
-
const file = path.join(this.config.serverPath, 'data', 'zoho-tokens.json');
|
|
408
|
-
return !this._fileExists(file) || this._isValidJson(file);
|
|
409
|
-
}, 'LOW'));
|
|
410
|
-
|
|
411
|
-
// EXT-002: Cartography maps
|
|
412
|
-
checks.push(await this._check('EXT-002', 'Cartography', () => {
|
|
413
|
-
return this._dirExists(path.join(this.config.rootPath, 'cartography'));
|
|
414
|
-
}, 'LOW'));
|
|
415
|
-
|
|
416
|
-
// EXT-003: Notification config
|
|
417
|
-
checks.push(await this._check('EXT-003', 'Notification Config', () => {
|
|
418
|
-
const file = path.join(this.config.serverPath, 'data', 'notification-config.json');
|
|
419
|
-
return !this._fileExists(file) || this._isValidJson(file);
|
|
420
|
-
}, 'LOW'));
|
|
421
|
-
|
|
422
|
-
// EXT-004: Health history
|
|
423
|
-
checks.push(await this._check('EXT-004', 'Health History', () => {
|
|
424
|
-
const file = path.join(this.config.serverPath, 'data', 'health-history.json');
|
|
425
|
-
return !this._fileExists(file) || this._isValidJson(file);
|
|
426
|
-
}, 'LOW'));
|
|
427
|
-
|
|
428
|
-
return { status: this._systemStatus(checks), checks };
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// ========== HELPERS ==========
|
|
432
|
-
|
|
433
|
-
async _check(id, name, checkFn, criticality = 'MEDIUM', autoHeal = null) {
|
|
434
|
-
const result = {
|
|
435
|
-
id,
|
|
436
|
-
name,
|
|
437
|
-
criticality,
|
|
438
|
-
status: 'UNKNOWN',
|
|
439
|
-
autoHeal,
|
|
440
|
-
needsHumanEscalation: false
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
try {
|
|
444
|
-
const passed = await checkFn();
|
|
445
|
-
result.status = passed ? 'PASS' : 'FAIL';
|
|
446
|
-
|
|
447
|
-
if (!passed && criticality === 'CRITICAL') {
|
|
448
|
-
result.needsHumanEscalation = true;
|
|
449
|
-
}
|
|
450
|
-
} catch (error) {
|
|
451
|
-
result.status = 'ERROR';
|
|
452
|
-
result.error = error.message;
|
|
453
|
-
result.needsHumanEscalation = true;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
return result;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
_fileExists(filePath) {
|
|
460
|
-
try {
|
|
461
|
-
return fs.existsSync(filePath);
|
|
462
|
-
} catch { return false; }
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
_dirExists(dirPath) {
|
|
466
|
-
try {
|
|
467
|
-
return fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory();
|
|
468
|
-
} catch { return false; }
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
_isValidJson(filePath) {
|
|
472
|
-
try {
|
|
473
|
-
if (!fs.existsSync(filePath)) return false;
|
|
474
|
-
JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
475
|
-
return true;
|
|
476
|
-
} catch { return false; }
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
_systemStatus(checks) {
|
|
480
|
-
const failed = checks.filter(c => c.status === 'FAIL' || c.status === 'ERROR');
|
|
481
|
-
const critical = failed.filter(c => c.criticality === 'CRITICAL');
|
|
482
|
-
|
|
483
|
-
if (critical.length > 0) return 'CRITICAL';
|
|
484
|
-
if (failed.length > 0) return 'DEGRADED';
|
|
485
|
-
return 'HEALTHY';
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
_calculateOverallStatus(results) {
|
|
489
|
-
if (results.criticalIssues.length > 0) return 'CRITICAL';
|
|
490
|
-
if (results.failed > 0) return 'DEGRADED';
|
|
491
|
-
if (results.warnings > 0) return 'WARNING';
|
|
492
|
-
return 'HEALTHY';
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Get summary for display
|
|
497
|
-
*/
|
|
498
|
-
getSummary() {
|
|
499
|
-
if (!this.lastResults) return { status: 'NO_DATA', message: 'No health check run yet' };
|
|
500
|
-
|
|
501
|
-
return {
|
|
502
|
-
status: this.lastResults.overallStatus,
|
|
503
|
-
passed: this.lastResults.passed,
|
|
504
|
-
failed: this.lastResults.failed,
|
|
505
|
-
total: this.lastResults.totalChecks,
|
|
506
|
-
criticalIssues: this.lastResults.criticalIssues.length,
|
|
507
|
-
humanActionNeeded: this.lastResults.humanEscalationNeeded.length,
|
|
508
|
-
lastCheck: this.lastResults.timestamp
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
module.exports = MasterHealthChecker;
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
|
|
3
|
-
class MonitorNotifier {
|
|
4
|
-
constructor({ alertManager, store, repoConfig }) {
|
|
5
|
-
this.alertManager = alertManager;
|
|
6
|
-
this.store = store;
|
|
7
|
-
this.repoConfig = repoConfig;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
async notify(diff, run) {
|
|
11
|
-
const channels = this.repoConfig.notification?.channels || ['inbox'];
|
|
12
|
-
const notifications = [];
|
|
13
|
-
|
|
14
|
-
if (!diff.hasNewIssues) {
|
|
15
|
-
return notifications;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const summary = `Found ${diff.added.length} new issue${diff.added.length === 1 ? '' : 's'} in ${this.repoConfig.repoPath}`;
|
|
19
|
-
const payload = {
|
|
20
|
-
timestamp: new Date().toISOString(),
|
|
21
|
-
repoId: this.repoConfig.repoId,
|
|
22
|
-
repoPath: this.repoConfig.repoPath,
|
|
23
|
-
channels,
|
|
24
|
-
summary,
|
|
25
|
-
newIssues: diff.added.slice(0, 10).map(issue => ({
|
|
26
|
-
file: issue.file,
|
|
27
|
-
line: issue.line,
|
|
28
|
-
severity: issue.severity,
|
|
29
|
-
category: issue.category,
|
|
30
|
-
message: issue.message,
|
|
31
|
-
})),
|
|
32
|
-
runTimestamp: run.timestamp,
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
if (channels.includes('inbox')) {
|
|
36
|
-
this.store.appendNotification(payload);
|
|
37
|
-
notifications.push({ channel: 'inbox', delivered: true });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (channels.includes('console') && this.alertManager) {
|
|
41
|
-
await this.alertManager.sendAlert({
|
|
42
|
-
level: 'warning',
|
|
43
|
-
category: 'scheduled-scan',
|
|
44
|
-
title: 'Thuban monitor detected new issues',
|
|
45
|
-
message: summary,
|
|
46
|
-
file: path.join(this.repoConfig.repoPath, '.'),
|
|
47
|
-
details: payload,
|
|
48
|
-
});
|
|
49
|
-
notifications.push({ channel: 'console', delivered: true });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (channels.includes('webhook')) {
|
|
53
|
-
notifications.push({ channel: 'webhook', delivered: false, stub: true });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (channels.includes('email')) {
|
|
57
|
-
notifications.push({ channel: 'email', delivered: false, stub: true });
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return notifications;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
module.exports = MonitorNotifier;
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
|
|
3
|
-
const AlertManager = require('./alert-manager.js');
|
|
4
|
-
const MonitorStore = require('./monitor-store.js');
|
|
5
|
-
const MonitorNotifier = require('./monitor-notifier.js');
|
|
6
|
-
const { runScanPipeline } = require('./scan-runner.js');
|
|
7
|
-
const { diffRuns } = require('./scan-diff.js');
|
|
8
|
-
const { collectFiles } = require('./file-collector.js');
|
|
9
|
-
|
|
10
|
-
function resolveIntervalMs(frequency, intervalMs) {
|
|
11
|
-
if (frequency === 'weekly') return 7 * 24 * 60 * 60 * 1000;
|
|
12
|
-
if (frequency === 'custom') return Math.max(60 * 1000, Number(intervalMs) || 24 * 60 * 60 * 1000);
|
|
13
|
-
return 24 * 60 * 60 * 1000;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
class MonitorService {
|
|
17
|
-
constructor(options = {}) {
|
|
18
|
-
this.store = options.store || new MonitorStore();
|
|
19
|
-
this.timers = new Map();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
configureRepo({ repoPath, frequency = 'daily', intervalMs, notification }) {
|
|
23
|
-
const repoId = this.store.getRepoId(repoPath);
|
|
24
|
-
const effectiveIntervalMs = resolveIntervalMs(frequency, intervalMs);
|
|
25
|
-
const repoConfig = this.store.upsertRepo({
|
|
26
|
-
repoId,
|
|
27
|
-
repoPath,
|
|
28
|
-
frequency,
|
|
29
|
-
intervalMs: effectiveIntervalMs,
|
|
30
|
-
notification,
|
|
31
|
-
nextRunAt: new Date(Date.now() + effectiveIntervalMs).toISOString(),
|
|
32
|
-
});
|
|
33
|
-
return repoConfig;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async runRepoScan(repoConfig) {
|
|
37
|
-
const rootPath = path.resolve(repoConfig.repoPath);
|
|
38
|
-
const files = this._collectFiles(rootPath);
|
|
39
|
-
const startedAt = Date.now();
|
|
40
|
-
const pipeline = await runScanPipeline({ rootPath, files });
|
|
41
|
-
const run = {
|
|
42
|
-
repoId: repoConfig.repoId,
|
|
43
|
-
repoPath: rootPath,
|
|
44
|
-
timestamp: new Date().toISOString(),
|
|
45
|
-
durationMs: Date.now() - startedAt,
|
|
46
|
-
frequency: repoConfig.frequency,
|
|
47
|
-
metrics: pipeline.metrics,
|
|
48
|
-
summary: pipeline.summary,
|
|
49
|
-
issues: pipeline.issues,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const previousRun = this.store.getLatestRun(repoConfig.repoId);
|
|
53
|
-
const diff = diffRuns(previousRun, run);
|
|
54
|
-
run.diff = diff;
|
|
55
|
-
|
|
56
|
-
const runPath = this.store.saveRun(repoConfig.repoId, run);
|
|
57
|
-
const updatedRepo = this.store.upsertRepo({
|
|
58
|
-
...repoConfig,
|
|
59
|
-
lastRunAt: run.timestamp,
|
|
60
|
-
nextRunAt: new Date(Date.now() + repoConfig.intervalMs).toISOString(),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const notifier = new MonitorNotifier({
|
|
64
|
-
alertManager: new AlertManager({ rootPath, consoleOutput: true, fileOutput: true }),
|
|
65
|
-
store: this.store,
|
|
66
|
-
repoConfig: updatedRepo,
|
|
67
|
-
});
|
|
68
|
-
run.notifications = await notifier.notify(diff, run);
|
|
69
|
-
|
|
70
|
-
return { run, runPath, previousRun, diff };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
start(repoConfig, { once = false } = {}) {
|
|
74
|
-
const execute = async () => {
|
|
75
|
-
try {
|
|
76
|
-
await this.runRepoScan(repoConfig);
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.error(`[THUBAN MONITOR] Scheduled scan failed for ${repoConfig.repoPath}: ${error.message}`);
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
execute();
|
|
83
|
-
if (once) return;
|
|
84
|
-
|
|
85
|
-
const timer = setInterval(execute, repoConfig.intervalMs);
|
|
86
|
-
this.timers.set(repoConfig.repoId, timer);
|
|
87
|
-
return timer;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
stopAll() {
|
|
91
|
-
for (const timer of this.timers.values()) clearInterval(timer);
|
|
92
|
-
this.timers.clear();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
_collectFiles(rootPath) {
|
|
96
|
-
return collectFiles(rootPath);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
module.exports = {
|
|
101
|
-
MonitorService,
|
|
102
|
-
resolveIntervalMs,
|
|
103
|
-
};
|