167 lines
6.0 KiB
Python
167 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Sol lane — daily-ledger triage (corrected, reads both surfaces).
|
|
|
|
The previous generator only read the legacy flat series BASE/<lane>-*.txt,
|
|
whose newest files are 2026-10-16-1446.txt (age ~185h) and so printed all
|
|
seven lanes STALE. Since 2026-10-16 the lanes write inside BASE/<lane>/.
|
|
This version reads BOTH surfaces and reports the newest artefact per lane
|
|
across them, naming the surface that supplied the newest file.
|
|
|
|
Newest artefact per lane:
|
|
- if BASE/<lane>/ holds files, those (newest) win, surface named "subdir"
|
|
- else the legacy flat file wins, surface named "legacy-flat"
|
|
Negative control: a lane with NO files on either surface still reads STALE
|
|
(never inherits another surface's age) and is named "none".
|
|
|
|
Output paths carry the run's own date; no hardcoded dates.
|
|
AGE_CLASSIFY rule: age <= 24h -> ACTIVE; age <= 168h -> WATCH; else STALE.
|
|
"""
|
|
|
|
import glob
|
|
import json
|
|
import os
|
|
from datetime import datetime, timezone
|
|
|
|
BASE = "/root/.hermes/wizard-outputs"
|
|
# Output lives in a dedicated directory, separate from the raw logs the lanes
|
|
# write into BASE/<lane>/, so generation never pollutes a lane's own scan.
|
|
OUT_DIR = os.path.join(BASE, "sol", "_triage")
|
|
os.makedirs(OUT_DIR, exist_ok=True)
|
|
RUN_DATE = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
OUT_JSON = os.path.join(OUT_DIR, "daily-ledger-triage-%s.json" % RUN_DATE)
|
|
OUT_MD = os.path.join(OUT_DIR, "daily-ledger-triage-%s.md" % RUN_DATE)
|
|
|
|
# The seven primary daily-ledger lanes.
|
|
PRIMARY = ["forge", "ledger", "null", "quill", "sphinx", "sol", "spark"]
|
|
# A lane with no files on either surface: negative control, must stay STALE.
|
|
NEGATIVE_CONTROL = "phantom"
|
|
|
|
|
|
def to_utc(ts):
|
|
return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
|
|
def classify(age_sec):
|
|
age_h = age_sec / 3600.0
|
|
if age_h <= 24.0:
|
|
return "ACTIVE"
|
|
if age_h <= 168.0:
|
|
return "WATCH"
|
|
return "STALE"
|
|
|
|
|
|
def surfaces(pid):
|
|
"""Return (flat_files, subdir_files) for a lane.
|
|
|
|
Legacy flat series: BASE/<lane>-YYYY-MM-DD-HHMM.txt at the top of BASE/.
|
|
Modern series: BASE/<lane>/* at the top of BASE/.
|
|
"""
|
|
flat = sorted(glob.glob(os.path.join(BASE, pid + "-*.txt")))
|
|
subdir = os.path.join(BASE, pid)
|
|
sub = []
|
|
if os.path.isdir(subdir):
|
|
sub = sorted(
|
|
os.path.join(subdir, f)
|
|
for f in os.listdir(subdir)
|
|
if os.path.isfile(os.path.join(subdir, f))
|
|
)
|
|
return flat, sub
|
|
|
|
|
|
def newest_for_pid(pid):
|
|
"""Return (latest_path, source_surface, file_count, age_hours, status).
|
|
|
|
source_surface is one of "subdir", "legacy-flat", "none".
|
|
"""
|
|
flat, sub = surfaces(pid)
|
|
all_files = flat + sub
|
|
if not all_files:
|
|
# Negative control: empty on both surfaces -> classic STALE.
|
|
return None, "none", 0, 0.0, "STALE"
|
|
latest = max(all_files, key=os.path.getmtime)
|
|
mtime = os.path.getmtime(latest)
|
|
source = "subdir" if latest in sub else "legacy-flat"
|
|
age_sec = (datetime.now(timezone.utc) - datetime.fromtimestamp(
|
|
mtime, tz=timezone.utc)).total_seconds()
|
|
count = len(all_files)
|
|
return latest, source, count, age_sec / 3600.0, classify(age_sec)
|
|
|
|
|
|
def main():
|
|
now = datetime.now(timezone.utc)
|
|
lanes = []
|
|
for pid in PRIMARY + [NEGATIVE_CONTROL]:
|
|
latest, source, count, age_h, status = newest_for_pid(pid)
|
|
if latest is None:
|
|
lanes.append({
|
|
"id": pid,
|
|
"surface": "none",
|
|
"latest_log": None,
|
|
"latest_mtime_utc": None,
|
|
"daily_log_count": 0,
|
|
"age_hours": 0.0,
|
|
"status": status,
|
|
})
|
|
else:
|
|
lanes.append({
|
|
"id": pid,
|
|
"surface": source,
|
|
"latest_log": os.path.basename(latest),
|
|
"latest_mtime_utc": to_utc(os.path.getmtime(latest)),
|
|
"daily_log_count": count,
|
|
"age_hours": round(age_h, 1),
|
|
"status": status,
|
|
})
|
|
|
|
artifact = {
|
|
"generated_utc": now.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
"run_date": RUN_DATE,
|
|
"classify_rule": "AGE_CLASSIFY: ACTIVE <=24h, WATCH <=168h, else STALE",
|
|
"base": BASE,
|
|
"surfaces": ["legacy-flat", "subdir"],
|
|
"negative_control_lane": NEGATIVE_CONTROL,
|
|
"lanes": lanes,
|
|
}
|
|
with open(OUT_JSON, "w", encoding="utf-8") as fh:
|
|
json.dump(artifact, fh, indent=2)
|
|
fh.write("\n")
|
|
|
|
lines = []
|
|
lines.append("# Daily-ledger triage — sol — %s" % RUN_DATE)
|
|
lines.append("")
|
|
lines.append("Recomputed from raw logs under %s at %s." % (BASE, now.strftime("%Y-%m-%dT%H:%M:%SZ")))
|
|
lines.append("")
|
|
lines.append("| lane | surface | latest log | latest mtime (UTC) | logs | age (h) | status |")
|
|
lines.append("|---|---|---|---|---|---|---|")
|
|
for e in sorted(lanes, key=lambda x: x["id"]):
|
|
lines.append(
|
|
"| %s | %s | %s | %s | %d | %.1f | %s |" % (
|
|
e["id"], e["surface"],
|
|
e["latest_log"] or "—", e["latest_mtime_utc"] or "—",
|
|
e["daily_log_count"], e["age_hours"], e["status"]))
|
|
lines.append("")
|
|
lines.append("## Summary")
|
|
lines.append("")
|
|
counts = {}
|
|
for e in lanes:
|
|
counts[e["status"]] = counts.get(e["status"], 0) + 1
|
|
lines.append("- **%d** lanes total" % len(lanes))
|
|
for s in ("ACTIVE", "WATCH", "STALE"):
|
|
lines.append("- **%d** %s" % (counts.get(s, 0), s))
|
|
lines.append("- **Negative control** `%s`: surface none, STALE (no inheritance)." % NEGATIVE_CONTROL)
|
|
lines.append("")
|
|
with open(OUT_MD, "w", encoding="utf-8") as fh:
|
|
fh.write("\n".join(lines))
|
|
|
|
print("WROTE", OUT_JSON)
|
|
print("WROTE", OUT_MD)
|
|
for e in sorted(lanes, key=lambda x: x["id"]):
|
|
print(" %-6s surface=%-8s %-24s count=%-4d age=%-6.1fh %s"
|
|
% (e["id"], e["surface"],
|
|
e["latest_log"] or "none", e["daily_log_count"],
|
|
e["age_hours"], e["status"]))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|