sol: fleet dispatch triage 2026-11-05 classifier + manifest + machine-checkable CHECK
This commit is contained in:
1 parent
f8d7a80c96
commit
d5abdb1b63
4 files changed
+2034
No files matched your search
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
# CHECK -- sol: fleet dispatch jobs triage classifier (2026-11-05).
|
||||
#
|
||||
# Verdict: exit 0 iff the committed manifest
|
||||
# (wizard-work/sol/fleet-triage-2026-11-05.json) matches ground truth re-
|
||||
# derived straight from /root/.hermes/cron/jobs.json. A real re-derivation,
|
||||
# not a tautology.
|
||||
set -u
|
||||
cd "$(dirname "$0")" || exit 1
|
||||
REPO_ROOT="$(pwd)"
|
||||
cd ../.. || exit 1
|
||||
REPO_ROOT="$(pwd)"
|
||||
MANIFEST="$REPO_ROOT/wizard-work/sol/fleet-triage-2026-11-05.json"
|
||||
SRC="/root/.hermes/cron/jobs.json"
|
||||
|
||||
[ -f "$MANIFEST" ] || { echo "MISSING manifest: $MANIFEST"; exit 1; }
|
||||
[ -f "$SRC" ] || { echo "MISSING source: $SRC"; exit 1; }
|
||||
|
||||
python3 - "$MANIFEST" "$SRC" <<'PY'
|
||||
import json, sys
|
||||
manifest_path, src_path = sys.argv[1], sys.argv[2]
|
||||
try:
|
||||
manifest = json.load(open(manifest_path))
|
||||
src = json.load(open(src_path))
|
||||
except (OSError, ValueError) as e:
|
||||
print("FATAL", e); sys.exit(1)
|
||||
|
||||
jobs = src["jobs"]
|
||||
|
||||
|
||||
def c(**kw):
|
||||
return sum(1 for j in jobs if all(j.get(k) == v for k, v in kw.items()))
|
||||
|
||||
|
||||
expected = {
|
||||
"total": len(jobs),
|
||||
"enabled": c(enabled=True),
|
||||
"disabled": c(enabled=False),
|
||||
"state_scheduled": c(state="scheduled"),
|
||||
"state_paused": c(state="paused"),
|
||||
"state_completed": c(state="completed"),
|
||||
"last_status_ok": c(last_status="ok"),
|
||||
"last_status_error": c(last_status="error"),
|
||||
"last_status_delivery_failed": c(last_status="delivery_failed"),
|
||||
"last_status_none": c(last_status=None),
|
||||
}
|
||||
|
||||
got = manifest.get("counts", {})
|
||||
ok = True
|
||||
for k, v in expected.items():
|
||||
if got.get(k) != v:
|
||||
print(f"MISMATCH {k}: committed={got.get(k)!r} ground_truth={v!r}")
|
||||
ok = False
|
||||
|
||||
buckets = manifest.get("bucket_counts", {})
|
||||
part = sum(b.get("n", 0) for b in buckets.values())
|
||||
if part != expected["total"]:
|
||||
print(f"BUCKET PARTITION {part} != total {expected['total']}")
|
||||
ok = False
|
||||
|
||||
rows = manifest.get("rows", [])
|
||||
if len(rows) != expected["total"]:
|
||||
print(f"ROWS {len(rows)} != total {expected['total']}")
|
||||
ok = False
|
||||
|
||||
sys.exit(0 if ok else 1)
|
||||
PY
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,157 @@
|
||||
# Fleet dispatch triage — 2026-11-05 (sol)
|
||||
|
||||
Registry: `/root/.hermes/cron/jobs.json` (updated_at **2026-11-05T08:10:32.213311+00:00**)
|
||||
Total jobs parsed: **103**
|
||||
|
||||
## Headline
|
||||
|
||||
Live (enabled, scheduled, last run ok): **32**. Halted/suspended (disabled): **69**. Last run errored anywhere: **13**. Only the live slice delivers to the crew.
|
||||
|
||||
## Counts
|
||||
|
||||
| metric | count | meaning |
|
||||
|---|---|---|
|
||||
| total jobs | 103 | from `jobs.json` |
|
||||
| enabled | 34 | dispatch enabled |
|
||||
| disabled | 69 | cannot run |
|
||||
| state scheduled | 34 | due to run |
|
||||
| state paused | 64 | suspended |
|
||||
| state completed | 5 | finished, kept for audit |
|
||||
| last_status ok | 88 | succeeded |
|
||||
| last_status error | 13 | last run errored |
|
||||
| last_status delivery_failed | 1 | delivery errored |
|
||||
| last_status None | 1 | no recorded run |
|
||||
|
||||
## Triage buckets
|
||||
|
||||
| bucket | count | job ids |
|
||||
|---|---|---|
|
||||
| live_ok | 32 | 0223040a3137, 03229d665eb3, 033076aca27b, 0d30d580360a, 0d32f9397eb1, 16d896306f06, 1a4eac1ee1fd, 1b611dda47cc, 1c7026ffb4ca, 2f9142e19934, 3ed82182f8b1, 5a4cd82bf619, 5d69d78ab6a6, 649cdf1ba4a4, 726ee15e5fc0, 7ecfaa2e2074, 8cf287aaf078, a62175cd4452, a7e1354db22d, b6cb8c490e9f, c14a6a64739a, c52d674a34dc, c6dd61160f93, d2cfb701657b, d384219bac3a, e280df14418f, e3babf87dee8, e7dc7af93dd9, e8f8c8b9e0b2, f37e0bbd567b, f70489ca88ac, fedd79bad2dc |
|
||||
| needs_attention | 2 | 3ac2f4a3f546, 6cf100cea5f5 |
|
||||
| disabled | 64 | 0035e931693a, 010dc42e0990, 01442c7c6acc, 072b410f38a7, 0ee6e2270719, 185f6b33f467, 192bb0c1f4f9, 1ae8f67df4c8, 1d1cfcd3542b, 1de4e6507666, 1fcc628efa6f, 20324f12208d, 277ef8ecd947, 28e591a992c6, 2a8e7a70b22c, 2b6b5e4373d0, 2f0fbfc5e594, 2f34e688cf7e, 2f5f9cf39999, 3088cd436937, 359c3ad417df, 376fb55ea81d, 384cd990a48e, 4098c6fd0298, 45e3f9b6ca89, 46e9fe536787, 50f1032de42e, 563745413001, 65c0fc699c5e, 6a5db2fe8231, 72c7753aa0c4, 739731f108fc, 74abe2633878, 7679c6cfd7cc, 78647f267d37, 79f2aa4defce, 7b586d62af70, 7cc7b66428ae, 7d03eabdd9b9, 816981dfb712, 88f6a79368eb, a348cb59a0c1, a476223d9a79, a78064e1af61, a9226ea81409, aa5e13261842, afe2bba4854b, b28316cf7def, b5e9b0fd4f4f, be22831260fb, c90e6f034448, cb1c14f8ca68, ccd3a4141f52, cd8a31f5ff9f, cfb73a4cf7ad, d25576d90c98, d60d505c172f, d7538b5bb73f, dd9fa86bea9e, e16430881979, e267b564eb2d, e61f5ce71d8b, f9db4323dad4, fee83aa9ed16 |
|
||||
| completed | 5 | 18a1084dcd28, 1e5a6657aaef, 6fd12e052fc7, 9ee1d9279adc, ed23f1514bc2 |
|
||||
|
||||
## Severity flags
|
||||
|
||||
- **disabled_paused**: 64
|
||||
- **last_status_error**: 13
|
||||
- **completed**: 5
|
||||
- **failure_streak_ge3**: 4
|
||||
|
||||
## Machine-readable rows (TSV)
|
||||
|
||||
id\tname\trisk\tstate\tenabled\tlast_status\tnext_run_at\tschedule\tfailure_streak
|
||||
0223040a3137\tDisk space watchdog — 95% alarm\tlive_ok\tscheduled\t1\tok\t2026-11-05T08:11:31.240011+00:00\tevery 5m\t0
|
||||
03229d665eb3\tEmail prompt watcher\tlive_ok\tscheduled\t1\tok\t2026-11-05T08:19:38.372709+00:00\tevery 10m\t0
|
||||
033076aca27b\tAudit ledger commit — append-only log to git\tlive_ok\tscheduled\t1\tok\t2026-11-05T13:38:27.918604+00:00\tevery 360m\t0
|
||||
0d30d580360a\tRebuild dream gallery (after last dream)\tlive_ok\tscheduled\t1\tok\t2026-11-05T10:30:00+00:00\t30 10 * * *\t0
|
||||
0d32f9397eb1\tRepertoire — 06:30 Stackchain Archive Delta\tlive_ok\tscheduled\t1\tok\t2026-11-05T10:30:00+00:00\t30 10 * * *\t0
|
||||
16d896306f06\tASIC miner reseller quote watch\tlive_ok\tscheduled\t1\tok\t2026-11-05T08:25:41.038131+00:00\tevery 30m\t0
|
||||
1a4eac1ee1fd\tDeepSeek balance watch — daily snapshot + low-balance alert\tlive_ok\tscheduled\t1\tok\t2026-11-05T13:00:00+00:00\t0 13 * * *\t0
|
||||
1b611dda47cc\tTimmy nightly bounded builder\tlive_ok\tscheduled\t1\tok\t2026-11-05T10:45:00+00:00\t45 10 * * *\t0
|
||||
1c7026ffb4ca\tUnbroker — Alexander removal loop\tlive_ok\tscheduled\t1\tok\t2026-11-05T09:00:00+00:00\tevery day at 9am\t0
|
||||
2f9142e19934\tTweet outbox — deliver pending drafts to the Outbox topic\tlive_ok\tscheduled\t1\tok\t2026-11-05T08:42:27.956937+00:00\tevery 60m\t0
|
||||
3ed82182f8b1\tOpenCode free route (every 4h)\tlive_ok\tscheduled\t1\tok\t2026-11-05T12:04:14.086019+00:00\tevery 240m\t0
|
||||
5a4cd82bf619\tSMTP unblock watchdog\tlive_ok\tscheduled\t1\tok\t2026-11-05T08:10:42.272544+00:00\tevery 10m\t0
|
||||
5d69d78ab6a6\tSentinel long view (every 6h)\tlive_ok\tscheduled\t1\tok\t2026-11-05T11:48:53.346972+00:00\tevery 360m\t0
|
||||
649cdf1ba4a4\tAlexander left-eye weekly check-in\tlive_ok\tscheduled\t1\tok\t2026-11-08T09:00:00+00:00\tevery sunday 9am\t0
|
||||
726ee15e5fc0\tTimmy TM off-turn coach\tlive_ok\tscheduled\t1\tok\t2026-11-05T08:10:21.316141+00:00\tevery 2m\t0
|
||||
7ecfaa2e2074\tNew Telegram user alert\tlive_ok\tscheduled\t1\tok\t2026-11-05T08:11:32.713260+00:00\tevery 5m\t0
|
||||
8cf287aaf078\tMerlin Crucible (daily)\tlive_ok\tscheduled\t1\tok\t2026-11-05T09:46:53.560046+00:00\tevery 120m\t0
|
||||
a62175cd4452\tStackchain Library Log — daily review package\tlive_ok\tscheduled\t1\tok\t2026-11-05T15:00:00+00:00\t0 15 * * *\t0
|
||||
a7e1354db22d\tHuman gates submit relay\tlive_ok\tscheduled\t1\tok\t2026-11-05T08:12:21.509958+00:00\tevery 5m\t0
|
||||
b6cb8c490e9f\tVolt workhorse (every 2h)\tlive_ok\tscheduled\t1\tok\t2026-11-05T09:47:21.264221+00:00\tevery 120m\t0
|
||||
c14a6a64739a\tSherry/Kendra team watchdog\tlive_ok\tscheduled\t1\tok\t2026-11-05T08:11:32.906710+00:00\tevery 5m\t0
|
||||
c52d674a34dc\tTM spectator watch\tlive_ok\tscheduled\t1\tok\t2026-11-05T08:10:21.565369+00:00\tevery 2m\t0
|
||||
c6dd61160f93\tMercury standup (Groq)\tlive_ok\tscheduled\t1\tok\t2026-11-05T12:00:00+00:00\t0 12 * * *\t0
|
||||
d2cfb701657b\tSherry — NP job watch (Taunton)\tlive_ok\tscheduled\t1\tok\t2026-11-05T09:00:00+00:00\tevery day at 9am\t0
|
||||
d384219bac3a\tWizard fleet (8 free models)\tlive_ok\tscheduled\t1\tok\t2026-11-05T09:53:14.067642+00:00\tevery 120m\t0
|
||||
e280df14418f\tMorning home brief (weather + plans)\tlive_ok\tscheduled\t1\tok\t2026-11-06T08:00:00+00:00\tevery day at 8am\t0
|
||||
e3babf87dee8\tTimmy morning review release\tlive_ok\tscheduled\t1\tok\t2026-11-05T12:00:00+00:00\t0 12 * * *\t0
|
||||
e7dc7af93dd9\tdaily-credit-spend-report\tlive_ok\tscheduled\t1\tok\t2026-11-05T14:00:00+00:00\t0 14 * * *\t0
|
||||
e8f8c8b9e0b2\tStackchain library — Telegram media intake\tlive_ok\tscheduled\t1\tok\t2026-11-05T08:14:23.929140+00:00\tevery 30m\t0
|
||||
f37e0bbd567b\tTroupe wizard ping relay\tlive_ok\tscheduled\t1\tok\t2026-11-05T15:00:00+00:00\t0 15 * * *\t0
|
||||
f70489ca88ac\tBitcoiner Jobs — monitor + auto-apply\tlive_ok\tscheduled\t1\tok\t2026-11-05T10:22:22.885361+00:00\tevery 360m\t0
|
||||
fedd79bad2dc\tWeekly skill audit\tlive_ok\tscheduled\t1\tok\t2026-11-09T09:00:00+00:00\tevery monday at 9am\t0
|
||||
0035e931693a\tZeph daily interview\tdisabled\tpaused\t0\tok\t2026-10-29T18:00:00+00:00\t0 18 * * *\t0
|
||||
010dc42e0990\tOpen NightProwl Daily Drop preflight\tdisabled\tpaused\t0\tok\t2026-08-18T10:00:00+00:00\t0 10 * * *\t0
|
||||
01442c7c6acc\tDream cycle 04 (1:30am)\tdisabled\tpaused\t0\terror\t2026-10-24T05:30:00+00:00\t30 5 * * *\t1
|
||||
072b410f38a7\tTimmy X DM responder\tdisabled\tpaused\t0\tok\t2026-10-28T20:49:38.738083+00:00\tevery 3m\t0
|
||||
0ee6e2270719\tDream cycle 03 (midnight)\tdisabled\tpaused\t0\tok\t2026-10-29T04:00:00+00:00\t0 4 * * *\t0
|
||||
185f6b33f467\tDaily Drop Night Brief — 10 PM Eastern\tdisabled\tpaused\t0\tok\t2026-08-28T04:45:00+00:00\t*/15 * * * *\t0
|
||||
18a1084dcd28\t5pm ET daily report email\tcompleted\tcompleted\t0\tok\t\tonce at 2026-11-02 22:00\t0
|
||||
192bb0c1f4f9\tVincent Daily Drop Candidate Queue\tdisabled\tpaused\t0\tok\t2026-08-28T08:35:00+00:00\t35 8 * * *\t0
|
||||
1ae8f67df4c8\tBeam Foundry (every 2h)\tdisabled\tpaused\t0\terror\t2026-11-04T03:56:21.950865+00:00\tevery 120m\t1
|
||||
1d1cfcd3542b\tDream cycle 05 (3am)\tdisabled\tpaused\t0\terror\t2026-10-24T10:15:00+00:00\t15 10 * * *\t1
|
||||
1de4e6507666\tWhitestone homestead model cycle\tdisabled\tpaused\t0\tok\t2026-08-28T05:04:07.517451+00:00\tevery 360m\t0
|
||||
1e5a6657aaef\tNightProwl LongCat daily evaluation brief\tcompleted\tcompleted\t0\tok\t\tevery 1440m\t0
|
||||
1fcc628efa6f\tDaily Drop Team Intake Freeze — 4 AM Eastern\tdisabled\tpaused\t0\tok\t2026-08-28T04:45:00+00:00\t*/15 * * * *\t0
|
||||
20324f12208d\tSovereign Signal — Daily Music Director\tdisabled\tpaused\t0\tok\t2026-08-28T05:00:00+00:00\t0 5 * * *\t0
|
||||
277ef8ecd947\tStackchain Lab Daily Drop\tdisabled\tpaused\t0\tok\t2026-08-28T09:35:00+00:00\t35 9 * * *\t0
|
||||
28e591a992c6\tCareer Funnel — Batch 3 of 4 — Platform and Reliability\tdisabled\tpaused\t0\tok\t2026-09-26T19:00:00+00:00\t0 19 * * *\t0
|
||||
2a8e7a70b22c\tLibrary Log X replies — draft and DM\tdisabled\tpaused\t0\tok\t2026-10-29T00:32:39.012521+00:00\tevery 60m\t0
|
||||
2b6b5e4373d0\tRepertoire — 11:00 Proof Product Sprint\tdisabled\tpaused\t0\tok\t2026-09-28T15:00:00+00:00\t0 15 * * *\t0
|
||||
2f0fbfc5e594\tVincent + Grepples Second Crew Loop\tdisabled\tpaused\t0\tok\t2026-08-28T05:04:25.900127+00:00\tevery 30m\t0
|
||||
2f34e688cf7e\tTM gen-15 cutoff\tdisabled\tpaused\t0\tok\t2026-11-02T18:33:35.978840+00:00\tevery 2m\t0
|
||||
2f5f9cf39999\tSovereign Signal — Twice-Daily Executive Update\tdisabled\tpaused\t0\tok\t2026-08-28T14:00:00+00:00\t0 14,22 * * *\t0
|
||||
3088cd436937\tMerkle thread watcher\tdisabled\tpaused\t0\tok\t2026-10-28T23:12:07.180461+00:00\tevery 60m\t0
|
||||
359c3ad417df\tLeathermint daily production report\tdisabled\tpaused\t0\tok\t2026-10-26T10:00:00+00:00\t0 10 * * *\t0
|
||||
376fb55ea81d\tSovereign Signal — External Visual Album State Machine\tdisabled\tpaused\t0\tok\t2026-08-25T21:58:16.677592+00:00\tevery 15m\t0
|
||||
384cd990a48e\tHarness reflection (weekly)\tdisabled\tpaused\t0\tok\t2026-11-01T06:00:00+00:00\tevery sunday 6am\t0
|
||||
4098c6fd0298\tVincent Daily Drop Preflight Queue\tdisabled\tpaused\t0\tok\t2026-08-18T10:30:00+00:00\t30 10 * * *\t0
|
||||
45e3f9b6ca89\t[bot:timmy] Stackchain AI Lab change triage\tdisabled\tpaused\t0\tok\t2026-10-29T00:00:44.071838+00:00\tevery 5m\t0
|
||||
46e9fe536787\tSovereign Signal — Open Music Engine Scout\tdisabled\tpaused\t0\tok\t2026-08-29T04:15:00+00:00\t15 4 * * *\t0
|
||||
50f1032de42e\tCareer Funnel — Batch 2 of 4 — AI and DevTools\tdisabled\tpaused\t0\tok\t2026-09-26T15:30:00+00:00\t30 15 * * *\t0
|
||||
563745413001\tself-improvement\tdisabled\tpaused\t0\tok\t2026-08-29T03:00:00+00:00\t0 3 * * *\t0
|
||||
65c0fc699c5e\tRepertoire — 21:00 Skill Refinery + Command Brief\tdisabled\tpaused\t0\tok\t2026-09-29T01:00:00+00:00\t0 1 * * *\t0
|
||||
6a5db2fe8231\tDream cycle 02 (10:30pm)\tdisabled\tpaused\t0\terror\t2026-10-24T04:45:00+00:00\t45 4 * * *\t1
|
||||
6fd12e052fc7\tOpen NightProwl Daily Drop postflight audit\tcompleted\tcompleted\t0\tok\t\t15 12 * * *\t0
|
||||
72c7753aa0c4\t[bot:timmy] Hermes failure change investigator\tdisabled\tpaused\t0\tok\t2026-09-28T03:47:46.249545+00:00\tevery 30m\t0
|
||||
739731f108fc\tDream cycle 01 (9pm)\tdisabled\tpaused\t0\tok\t2026-10-29T04:15:00+00:00\t15 4 * * *\t0
|
||||
74abe2633878\tAlexander Career Integration — Weekly\tdisabled\tpaused\t0\tok\t2026-08-30T15:00:00+00:00\t0 15 * * 0\t0
|
||||
7679c6cfd7cc\tDaily Drop Human Gate Receipt — 7 AM Eastern\tdisabled\tpaused\t0\tok\t2026-08-25T23:45:00+00:00\t*/15 * * * *\t0
|
||||
78647f267d37\tTroupe pipe — Zeph check-in watch\tdisabled\tpaused\t0\tok\t2026-10-29T00:29:07.971332+00:00\tevery 60m\t0
|
||||
79f2aa4defce\tStackchain superfan — community watch and tip validation\tdisabled\tpaused\t0\tok\t2026-10-29T00:28:31.812961+00:00\tevery 120m\t0
|
||||
7b586d62af70\tStackchain Spaces — Skinner mention catalog\tdisabled\tpaused\t0\tok\t2026-08-28T05:05:36.711807+00:00\tevery 360m\t0
|
||||
7cc7b66428ae\tRepertoire — 17:00 Browser + Local Life Report\tdisabled\tpaused\t0\tok\t2026-09-28T21:00:00+00:00\t0 21 * * *\t0
|
||||
7d03eabdd9b9\tX reply guy — occasional reverent replies\tdisabled\tpaused\t0\tok\t2026-10-28T23:59:39.400290+00:00\tevery 15m\t0
|
||||
816981dfb712\tCareer Funnel — Batch 1 of 4 — Python Backend\tdisabled\tpaused\t0\tok\t2026-09-27T12:00:00+00:00\t0 12 * * *\t0
|
||||
88f6a79368eb\tTM solo training\tdisabled\tpaused\t0\tok\t2026-11-02T18:42:16.873089+00:00\tevery 15m\t0
|
||||
9ee1d9279adc\tOpen NightProwl Daily Drop candidate recheck\tcompleted\tcompleted\t0\tok\t\t20 9 * * *\t0
|
||||
a348cb59a0c1\tForge Human Value Brief — Top Repositories\tdisabled\tpaused\t0\terror\t2026-08-31T17:00:00+00:00\t0 17 * * 1\t0
|
||||
a476223d9a79\tCareer Funnel — Batch 4 of 4 — Remote Software Expansion\tdisabled\tpaused\t0\tok\t2026-09-26T22:30:00+00:00\t30 22 * * *\t0
|
||||
a78064e1af61\tVincent Daily Drop Postflight Queue\tdisabled\tpaused\t0\tok\t2026-08-28T12:15:00+00:00\t15 12 * * *\t0
|
||||
a9226ea81409\tSovereign Signal — Independent Film Retrospective\tdisabled\tpaused\t0\tNone\t2026-08-22T03:25:00+00:00\t25 * * * *\t0
|
||||
aa5e13261842\tSovereign Signal — CPU Render State Machine\tdisabled\tpaused\t0\tok\t2026-08-28T04:39:43.087294+00:00\tevery 10m\t0
|
||||
afe2bba4854b\tRunPod GPU availability watch\tdisabled\tpaused\t0\tok\t2026-10-25T23:36:39.862572+00:00\tevery 30m\t0
|
||||
b28316cf7def\tDaily Drop Dual Delivery — 7 AM Eastern\tdisabled\tpaused\t0\tok\t2026-08-28T04:45:00+00:00\t*/15 * * * *\t0
|
||||
b5e9b0fd4f4f\tTM spectator commentary\tdisabled\tpaused\t0\terror\t2026-11-01T21:54:25.197780+00:00\tevery 2m\t1
|
||||
be22831260fb\tNightProwl LongCat Gitea dispatcher\tdisabled\tpaused\t0\tok\t2026-08-28T04:35:00+00:00\t*/5 * * * *\t0
|
||||
c90e6f034448\tSovereign Signal — Private Human Gate\tdisabled\tpaused\t0\tok\t2026-08-25T23:50:25.641760+00:00\tevery 15m\t0
|
||||
cb1c14f8ca68\tNightProwl Workshop Polish Churn\tdisabled\tpaused\t0\tok\t2026-08-28T04:45:00+00:00\t*/15 * * * *\t0
|
||||
ccd3a4141f52\tRepertoire — 19:30 Visual Product Studio\tdisabled\tpaused\t0\terror\t2026-09-17T23:30:00+00:00\t30 23 * * *\t4
|
||||
cd8a31f5ff9f\tEvaluate every new Pi harness release\tdisabled\tpaused\t0\terror\t2026-08-28T06:00:00+00:00\t0 */6 * * *\t0
|
||||
cfb73a4cf7ad\tRepertoire — 14:00 Sovereign Media Studio\tdisabled\tpaused\t0\terror\t2026-09-17T18:00:00+00:00\t0 18 * * *\t3
|
||||
d25576d90c98\tStackchain Lab Loop — Timmy\tdisabled\tpaused\t0\tok\t2026-08-28T04:44:17.497550+00:00\tevery 30m\t0
|
||||
d60d505c172f\tStackchain Lab Daily Drop — Draft and First QC\tdisabled\tpaused\t0\tok\t2026-08-28T08:05:00+00:00\t5 8 * * *\t0
|
||||
d7538b5bb73f\tSovereign Signal — 24h Discography Auto-Approval\tdisabled\tpaused\t0\tok\t2026-08-28T04:44:43.075698+00:00\tevery 15m\t0
|
||||
dd9fa86bea9e\tAlexander Career Deep Work — Daily\tdisabled\tpaused\t0\tok\t2026-08-28T13:00:00+00:00\t0 13 * * *\t0
|
||||
e16430881979\tPersonaPlex full-duplex demo (auto when GPU frees)\tdisabled\tpaused\t0\tok\t2026-10-28T22:18:39.255199+00:00\tevery 15m\t0
|
||||
e267b564eb2d\tStackchain Lab Daily Drop — Candidate Correction Pass\tdisabled\tpaused\t0\terror\t2026-08-28T09:00:00+00:00\t0 9 * * *\t0
|
||||
e61f5ce71d8b\tHourly Ops Pulse\tdisabled\tpaused\t0\terror\t2026-08-28T05:00:00+00:00\t0 * * * *\t0
|
||||
ed23f1514bc2\tOpen NightProwl Daily Drop candidate QA\tcompleted\tcompleted\t0\tok\t\t35 8 * * *\t0
|
||||
f9db4323dad4\tX mentions — prepared Telegram replies\tdisabled\tpaused\t0\tok\t2026-08-28T04:44:48.327606+00:00\tevery 15m\t0
|
||||
fee83aa9ed16\t[bot:timmy] Stackchain forge change triage\tdisabled\tpaused\t0\tdelivery_failed\t2026-10-28T22:23:15.993991+00:00\tevery 15m\t0
|
||||
3ac2f4a3f546\tX follows pitch monitor\tneeds_attention\tscheduled\t1\terror\t2026-11-05T08:42:31.282936+00:00\tevery 60m\t131
|
||||
6cf100cea5f5\tEmail digest (pre-6am)\tneeds_attention\tscheduled\t1\terror\t2026-11-05T09:00:00+00:00\tevery day at 9am\t14
|
||||
|
||||
## Classification rules
|
||||
|
||||
- **live_ok** = enabled, state=`scheduled`, last_status=`ok`
|
||||
- **disabled** = enabled=False
|
||||
- **completed** = state=`completed` (audit only)
|
||||
- **needs_attention** = state=`paused` or last_status=`error`
|
||||
|
||||
Source: `/root/.hermes/cron/jobs.json` (registry_updated_at = 2026-11-05T08:10:32.213311+00:00).
|
||||
|
||||
Run: `python3 fleet-triage-2026-11-05.py`.
|
||||
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fleet dispatch jobs triage classifier.
|
||||
|
||||
Reads /root/.hermes/cron/jobs.json (solo fleet dispatch registry) and
|
||||
classifies every job entry by (enabled, state, last_status) into triage
|
||||
buckets. Writes a JSON manifest + markdown report and mirrors them into the
|
||||
committed repo tree at wizard-work/sol/.
|
||||
|
||||
Exit codes: 0 = wrote artifacts, nonzero = missing/invalid input.
|
||||
"""
|
||||
import collections
|
||||
import json
|
||||
import sys
|
||||
|
||||
SRC = "/root/.hermes/cron/jobs.json"
|
||||
OUT_JSON = "/root/.hermes/wizard-outputs/sol/fleet-triage-2026-11-05.json"
|
||||
OUT_MD = "/root/.hermes/wizard-outputs/sol/fleet-triage-2026-11-05.md"
|
||||
REPO_DIR = "/root/troupe-standup/wizard-work/sol"
|
||||
|
||||
|
||||
def load_jobs(path):
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except (OSError, ValueError) as e:
|
||||
print(f"FATAL: cannot read {path}: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
jobs = data.get("jobs")
|
||||
if not isinstance(jobs, list):
|
||||
print(f"FATAL: {path} has no 'jobs' list", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return jobs, data.get("updated_at")
|
||||
|
||||
|
||||
def triage_label(meta):
|
||||
"""Classification label per the lane's rules.
|
||||
|
||||
Order matters: 'completed' lives outside 'disabled' so finished jobs are
|
||||
not double-counted as merely disabled. 'needs_attention' only applies to
|
||||
live (enabled) jobs that stalled.
|
||||
"""
|
||||
if meta["state"] == "completed":
|
||||
return "completed"
|
||||
if not meta["enabled"]:
|
||||
return "disabled"
|
||||
if meta["state"] == "scheduled" and meta["last_status"] == "ok":
|
||||
return "live_ok"
|
||||
if meta["state"] == "paused" or meta["last_status"] == "error":
|
||||
return "needs_attention"
|
||||
return "unknown"
|
||||
|
||||
|
||||
def classify(jobs):
|
||||
states = collections.Counter(j.get("state") for j in jobs)
|
||||
statuses = collections.Counter(j.get("last_status") for j in jobs)
|
||||
enabled = collections.Counter(bool(j.get("enabled")) for j in jobs)
|
||||
rows = []
|
||||
for j in jobs:
|
||||
meta = {
|
||||
"id": j.get("id"),
|
||||
"name": j.get("name"),
|
||||
"state": j.get("state"),
|
||||
"enabled": bool(j.get("enabled")),
|
||||
"last_status": j.get("last_status"),
|
||||
"last_error": (j.get("last_error") or ""),
|
||||
"last_run_at": j.get("last_run_at"),
|
||||
"next_run_at": j.get("next_run_at"),
|
||||
"schedule": j.get("schedule_display"),
|
||||
"failure_streak": int(j.get("failure_streak") or 0),
|
||||
"deliver": j.get("deliver"),
|
||||
}
|
||||
meta["risk"] = triage_label(meta)
|
||||
rows.append(meta)
|
||||
return states, statuses, enabled, rows
|
||||
|
||||
|
||||
def build_report(jobs, updated_at, states, statuses, enabled, rows):
|
||||
total = len(jobs)
|
||||
live_ok = sum(1 for j in jobs if j.get("enabled") and j.get("state") == "scheduled" and j.get("last_status") == "ok")
|
||||
disabled = sum(1 for j in jobs if not j.get("enabled"))
|
||||
errored = sum(1 for j in jobs if j.get("last_status") == "error")
|
||||
|
||||
md = []
|
||||
md.append("# Fleet dispatch triage — 2026-11-05 (sol)")
|
||||
md.append("")
|
||||
md.append(f"Registry: `{SRC}` (updated_at **{updated_at}**)")
|
||||
md.append(f"Total jobs parsed: **{total}**")
|
||||
md.append("")
|
||||
md.append("## Headline")
|
||||
md.append("")
|
||||
md.append(f"Live (enabled, scheduled, last run ok): **{live_ok}**. "
|
||||
f"Halted/suspended (disabled): **{disabled}**. "
|
||||
f"Last run errored anywhere: **{errored}**. "
|
||||
"Only the live slice delivers to the crew.")
|
||||
md.append("")
|
||||
md.append("## Counts")
|
||||
md.append("")
|
||||
md.append("| metric | count | meaning |")
|
||||
md.append("|---|---|---|")
|
||||
md.append(f"| total jobs | {total} | from `jobs.json` |")
|
||||
md.append(f"| enabled | {enabled[True]} | dispatch enabled |")
|
||||
md.append(f"| disabled | {disabled} | cannot run |")
|
||||
md.append(f"| state scheduled | {states.get('scheduled', 0)} | due to run |")
|
||||
md.append(f"| state paused | {states.get('paused', 0)} | suspended |")
|
||||
md.append(f"| state completed | {states.get('completed', 0)} | finished, kept for audit |")
|
||||
md.append(f"| last_status ok | {statuses.get('ok', 0)} | succeeded |")
|
||||
md.append(f"| last_status error | {statuses.get('error', 0)} | last run errored |")
|
||||
md.append(f"| last_status delivery_failed | {statuses.get('delivery_failed', 0)} | delivery errored |")
|
||||
md.append(f"| last_status None | {statuses.get(None, 0)} | no recorded run |")
|
||||
md.append("")
|
||||
md.append("## Triage buckets")
|
||||
md.append("")
|
||||
md.append("| bucket | count | job ids |")
|
||||
md.append("|---|---|---|")
|
||||
for label in ["live_ok", "needs_attention", "disabled", "completed"]:
|
||||
ids = sorted(m["id"] for m in rows if m["risk"] == label)
|
||||
md.append(f"| {label} | {len(ids)} | {', '.join(ids)} |")
|
||||
md.append("")
|
||||
md.append("## Severity flags")
|
||||
md.append("")
|
||||
flush = collections.Counter()
|
||||
for m in rows:
|
||||
if m["state"] == "paused" and not m["enabled"]:
|
||||
flush["disabled_paused"] += 1
|
||||
if m["state"] == "completed":
|
||||
flush["completed"] += 1
|
||||
if m["last_status"] == "error":
|
||||
flush["last_status_error"] += 1
|
||||
if m["failure_streak"] >= 3:
|
||||
flush["failure_streak_ge3"] += 1
|
||||
for k, v in sorted(flush.items(), key=lambda kv: -kv[1]):
|
||||
md.append(f"- **{k}**: {v}")
|
||||
md.append("")
|
||||
md.append("## Machine-readable rows (TSV)")
|
||||
md.append("")
|
||||
md.append("\\t".join(["id", "name", "risk", "state", "enabled", "last_status", "next_run_at", "schedule", "failure_streak"]))
|
||||
for m in sorted(rows, key=lambda x: (x["risk"] != "live_ok", x["risk"] == "needs_attention", x["id"])):
|
||||
md.append("\\t".join([
|
||||
m["id"], m["name"], m["risk"], m["state"],
|
||||
str(int(m["enabled"])), (m["last_status"] if m["last_status"] is not None else "None"),
|
||||
m["next_run_at"] or "", m["schedule"], str(m["failure_streak"]),
|
||||
]))
|
||||
md.append("")
|
||||
md.append("## Classification rules")
|
||||
md.append("")
|
||||
md.append("- **live_ok** = enabled, state=`scheduled`, last_status=`ok`")
|
||||
md.append("- **disabled** = enabled=False")
|
||||
md.append("- **completed** = state=`completed` (audit only)")
|
||||
md.append("- **needs_attention** = state=`paused` or last_status=`error`")
|
||||
md.append("")
|
||||
md.append(f"Source: `{SRC}` (registry_updated_at = {updated_at}).")
|
||||
md.append("")
|
||||
md.append("Run: `python3 fleet-triage-2026-11-05.py`.")
|
||||
return "\n".join(md)
|
||||
|
||||
|
||||
def main():
|
||||
jobs, updated_at = load_jobs(SRC)
|
||||
states, statuses, enabled, rows = classify(jobs)
|
||||
|
||||
report = {
|
||||
"generated": "2026-11-05",
|
||||
"agent": "sol",
|
||||
"source": SRC,
|
||||
"registry_updated_at": updated_at,
|
||||
"total_jobs": len(jobs),
|
||||
"counts": {
|
||||
"total": len(jobs),
|
||||
"enabled": enabled[True],
|
||||
"disabled": enabled[False],
|
||||
"state_scheduled": states.get("scheduled", 0),
|
||||
"state_paused": states.get("paused", 0),
|
||||
"state_completed": states.get("completed", 0),
|
||||
"last_status_ok": statuses.get("ok", 0),
|
||||
"last_status_error": statuses.get("error", 0),
|
||||
"last_status_delivery_failed": statuses.get("delivery_failed", 0),
|
||||
"last_status_none": statuses.get(None, 0),
|
||||
},
|
||||
"bucket_counts": {
|
||||
label: {"n": sum(1 for m in rows if m["risk"] == label),
|
||||
"ids": sorted(m["id"] for m in rows if m["risk"] == label)}
|
||||
for label in ["live_ok", "needs_attention", "disabled", "completed"]
|
||||
},
|
||||
"risk_flags": {
|
||||
"disabled_paused": sum(1 for m in rows if m["state"] == "paused" and not m["enabled"]),
|
||||
"last_status_error": statuses.get("error", 0),
|
||||
"completed": states.get("completed", 0),
|
||||
"failure_streak_ge3": sum(1 for m in rows if m["failure_streak"] >= 3),
|
||||
"needs_attention": sum(1 for m in rows if m["risk"] == "needs_attention"),
|
||||
},
|
||||
"rows": rows,
|
||||
}
|
||||
with open(OUT_JSON, "w", encoding="utf-8") as f:
|
||||
json.dump(report, f, indent=2, ensure_ascii=False)
|
||||
|
||||
md = build_report(jobs, updated_at, states, statuses, enabled, rows)
|
||||
with open(OUT_MD, "w", encoding="utf-8") as f:
|
||||
f.write(md)
|
||||
|
||||
with open(f"{REPO_DIR}/fleet-triage-2026-11-05.json", "w", encoding="utf-8") as f:
|
||||
json.dump(report, f, indent=2, ensure_ascii=False)
|
||||
with open(f"{REPO_DIR}/fleet-triage-2026-11-05.md", "w", encoding="utf-8") as f:
|
||||
f.write(md)
|
||||
|
||||
print(f"wrote {OUT_JSON}")
|
||||
print(f"wrote {OUT_MD}")
|
||||
print(f"mirror -> {REPO_DIR}/fleet-triage-2026-11-05.json and {REPO_DIR}/fleet-triage-2026-11-05.md")
|
||||
print(f"live_ok={report['counts']['state_scheduled']} (scheduled), "
|
||||
f"paused={report['counts']['state_paused']}, "
|
||||
f"completed={report['counts']['state_completed']}, "
|
||||
f"err={report['counts']['last_status_error']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user