bulltrackers-module 1.0.327 → 1.0.328
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.
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @fileoverview Admin API Router
|
|
3
3
|
* Sub-module for system observability, debugging, and visualization.
|
|
4
4
|
* Mounted at /admin within the Generic API.
|
|
5
|
-
* UPDATED: Added
|
|
5
|
+
* UPDATED: Added Stale Task Detection, Root Data Health, and SimHash Inspection.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const express = require('express');
|
|
@@ -526,6 +526,108 @@ const createAdminRouter = (config, dependencies, unifiedCalculations) => {
|
|
|
526
526
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
|
527
527
|
});
|
|
528
528
|
|
|
529
|
+
// =========================================================================
|
|
530
|
+
// NEW DEBUGGING ROUTES (Added 2025-12-19)
|
|
531
|
+
// =========================================================================
|
|
532
|
+
|
|
533
|
+
// --- 11. STALE COMPUTATION DETECTOR ("Zombie Hunter") ---
|
|
534
|
+
router.get('/debug/stale', async (req, res) => {
|
|
535
|
+
const thresholdMins = parseInt(String(req.query.threshold)) || 15;
|
|
536
|
+
const cutoffTime = Date.now() - (thresholdMins * 60 * 1000);
|
|
537
|
+
|
|
538
|
+
try {
|
|
539
|
+
// Scanning collectionGroup 'tasks' is expensive, so we focus on 'IN_PROGRESS'
|
|
540
|
+
// and filter in memory if necessary, or rely on composite index if available.
|
|
541
|
+
const snapshot = await db.collectionGroup('tasks')
|
|
542
|
+
.where('status', '==', 'IN_PROGRESS')
|
|
543
|
+
.get();
|
|
544
|
+
|
|
545
|
+
const zombies = [];
|
|
546
|
+
snapshot.forEach(doc => {
|
|
547
|
+
const data = doc.data();
|
|
548
|
+
|
|
549
|
+
// Determine last activity (Heartbeat > StartedAt > 0)
|
|
550
|
+
const lastActivity = data.telemetry?.lastHeartbeat
|
|
551
|
+
? new Date(data.telemetry.lastHeartbeat).getTime()
|
|
552
|
+
: (data.startedAt ? new Date(data.startedAt).getTime() : 0);
|
|
553
|
+
|
|
554
|
+
if (lastActivity < cutoffTime) {
|
|
555
|
+
zombies.push({
|
|
556
|
+
computation: data.computation || doc.id,
|
|
557
|
+
workerId: data.workerId || 'unknown',
|
|
558
|
+
startedAt: data.startedAt,
|
|
559
|
+
lastHeartbeat: data.telemetry?.lastHeartbeat,
|
|
560
|
+
stagnantMinutes: Math.floor((Date.now() - lastActivity) / 60000),
|
|
561
|
+
path: doc.ref.path // Useful for manual deletion
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// Sort by most stagnant
|
|
567
|
+
zombies.sort((a, b) => b.stagnantMinutes - a.stagnantMinutes);
|
|
568
|
+
|
|
569
|
+
res.json({
|
|
570
|
+
count: zombies.length,
|
|
571
|
+
thresholdMins,
|
|
572
|
+
zombies
|
|
573
|
+
});
|
|
574
|
+
} catch (e) {
|
|
575
|
+
res.status(500).json({ error: e.message });
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// --- 12. ROOT DATA HEALTH CHECK ---
|
|
580
|
+
// Debugs the "Available but marked missing" issue
|
|
581
|
+
router.get('/debug/root-data-health', async (req, res) => {
|
|
582
|
+
const { start, end } = req.query;
|
|
583
|
+
if (!start || !end) return res.status(400).json({ error: "Start and End dates required." });
|
|
584
|
+
|
|
585
|
+
try {
|
|
586
|
+
const startDate = new Date(String(start));
|
|
587
|
+
const endDate = new Date(String(end));
|
|
588
|
+
const dates = [];
|
|
589
|
+
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
|
|
590
|
+
dates.push(d.toISOString().slice(0, 10));
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const report = {};
|
|
594
|
+
const limit = pLimit(10);
|
|
595
|
+
|
|
596
|
+
await Promise.all(dates.map(date => limit(async () => {
|
|
597
|
+
const rootDoc = await db.collection('system_root_data_index').doc(date).get();
|
|
598
|
+
if (!rootDoc.exists) {
|
|
599
|
+
report[date] = { exists: false, status: 'MISSING_INDEX' };
|
|
600
|
+
} else {
|
|
601
|
+
const data = rootDoc.data();
|
|
602
|
+
report[date] = {
|
|
603
|
+
exists: true,
|
|
604
|
+
status: 'OK',
|
|
605
|
+
flags: data.status, // hasHistory, hasPortfolio, etc.
|
|
606
|
+
lastUpdated: data.lastUpdated
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
})));
|
|
610
|
+
|
|
611
|
+
res.json(report);
|
|
612
|
+
} catch (e) {
|
|
613
|
+
res.status(500).json({ error: e.message });
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
// --- 13. SIMHASH INSPECTOR ---
|
|
618
|
+
// Debugs "Why did this run? Code Changed?"
|
|
619
|
+
router.get('/debug/simhash/:hash', async (req, res) => {
|
|
620
|
+
const { hash } = req.params;
|
|
621
|
+
try {
|
|
622
|
+
const doc = await db.collection('system_simhash_registry').doc(hash).get();
|
|
623
|
+
if (!doc.exists) return res.status(404).json({ error: "Hash not found in registry." });
|
|
624
|
+
|
|
625
|
+
res.json(doc.data());
|
|
626
|
+
} catch (e) {
|
|
627
|
+
res.status(500).json({ error: e.message });
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
|
|
529
631
|
return router;
|
|
530
632
|
};
|
|
531
633
|
|