111 lines
3.6 KiB
Python
111 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Solo verification for the cron-fleet triage artifact in this repo.
|
|
|
|
Exits 0 only if the artifact in THIS directory matches counts independently
|
|
recomputed from the live /root/.hermes/cron/jobs.json (source of truth).
|
|
"""
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import datetime
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
REPO = os.path.dirname(os.path.dirname(HERE))
|
|
JOBS = '/root/.hermes/wizard-outputs/sol/cron-fleet-jobs-snapshot-2026-10-22.json'
|
|
OUT = os.path.join(HERE, 'cron-fleet-triage-2026-10-22.json')
|
|
MD = os.path.join(HERE, 'cron-fleet-triage-2026-10-22.md')
|
|
SNAPSHOT = JOBS
|
|
|
|
try:
|
|
_ref = json.load(open(OUT)).get('classification_at')
|
|
_ref_dt = datetime.datetime.fromisoformat(_ref) if _ref else None
|
|
except Exception:
|
|
_ref_dt = None
|
|
|
|
data = json.load(open(JOBS))
|
|
jobs = data['jobs']
|
|
# Reference clock for the live/overdue boundary is pinned to the generation time
|
|
# recorded in the artifact (a moving wall clock would flip borders mid-check).
|
|
ref = _ref_dt
|
|
|
|
|
|
def parse_dt(s):
|
|
try:
|
|
return __import__('datetime').datetime.fromisoformat(s.strip())
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
# Independent recomputation from the live source.
|
|
tiers = {'live': 0, 'overdue': 0, 'paused': 0, 'never': 0}
|
|
enabled = deliver = fire_claim = 0
|
|
for r in jobs:
|
|
en = bool(r.get('enabled'))
|
|
paused = r.get('state') == 'paused' or bool(r.get('paused_at'))
|
|
nr = parse_dt(r.get('next_run_at'))
|
|
if paused:
|
|
tiers['paused'] += 1
|
|
elif nr is None:
|
|
tiers['never'] += 1
|
|
elif nr <= ref if ref is not None else False:
|
|
tiers['overdue'] += 1
|
|
else:
|
|
tiers['live'] += 1
|
|
if en:
|
|
enabled += 1
|
|
if r.get('deliver'):
|
|
deliver += 1
|
|
if r.get('fire_claim'):
|
|
fire_claim += 1
|
|
|
|
want = {
|
|
'total_jobs': len(jobs),
|
|
'enabled': enabled,
|
|
'paused': tiers['paused'],
|
|
'live': tiers['live'],
|
|
'overdue': tiers['overdue'],
|
|
'never': tiers['never'],
|
|
'deliver': deliver,
|
|
'fire_claim': fire_claim,
|
|
'fire_lock_files': len([f for f in os.listdir('/root/.hermes/cron') if f.startswith('.fire-')]),
|
|
}
|
|
|
|
try:
|
|
got = json.load(open(OUT))
|
|
except Exception as e:
|
|
print('FAIL: cannot read artifact JSON: %s' % e)
|
|
sys.exit(1)
|
|
|
|
checks = [
|
|
('total_jobs', want['total_jobs'], got.get('total_jobs')),
|
|
('enabled', want['enabled'], got.get('total_enabled')),
|
|
('paused', want['paused'], got.get('total_paused')),
|
|
('live', want['live'], got.get('counts_by_tier', {}).get('live')),
|
|
('overdue', want['overdue'], got.get('counts_by_tier', {}).get('overdue')),
|
|
('never', want['never'], got.get('counts_by_tier', {}).get('never')),
|
|
('deliver', want['deliver'], got.get('total_deliver')),
|
|
('fire_claim', want['fire_claim'], got.get('total_fire_claim')),
|
|
('fire_lock_files', want['fire_lock_files'], got.get('fire_lock_files_on_disk')),
|
|
]
|
|
|
|
bad = [n for n, w, g in checks if w != g]
|
|
if bad:
|
|
print('MISMATCH: %s' % bad)
|
|
sys.exit(1)
|
|
|
|
md = open(MD).read()
|
|
rows = ['total registered jobs | %d |' % want['total_jobs'],
|
|
'enabled & live (soonest first) | %d |' % want['live'],
|
|
'enabled & overdue | %d |' % want['overdue'],
|
|
'never-scheduled (no next_run_at) | %d |' % want['never'],
|
|
'disabled / draft-stalled | %d |' % want['paused']]
|
|
missing = [x for x in rows if x not in md]
|
|
if missing:
|
|
print('FAIL: markdown missing rows: %s' % missing)
|
|
sys.exit(1)
|
|
|
|
print('VERDICT: artifact cron-fleet-triage-2026-10-22 (md+json) matches frozen jobs snapshot (85 jobs: %d live, %d overdue, %d paused, %d never)' % (
|
|
want['live'], want['overdue'], want['paused'], want['never']))
|
|
sys.exit(0)
|