119 lines
3.9 KiB
Python
119 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""check_live_roster.py — independent re-derivation + artifact cross-check.
|
|
|
|
Exit 0 (CHECK: PASS) only if every row of the artifact TSV matches live
|
|
evidence recomputed from: (a) WIZARDS-TABLE.md roster table, (b) checkins/
|
|
filenames, (c) git log. Any mismatch -> CHECK: FAIL, exit 1.
|
|
|
|
Usage: python3 check_live_roster.py /path/to/live-wizard-roster.tsv
|
|
"""
|
|
import csv
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
REPO = "/root/troupe-standup"
|
|
SRC = os.path.join(REPO, "WIZARDS-TABLE.md")
|
|
CHECKINS = os.path.join(REPO, "checkins")
|
|
|
|
|
|
def load_roster():
|
|
text = open(SRC, encoding="utf-8").read()
|
|
rows, in_table = [], False
|
|
for ln in text.splitlines():
|
|
if ln.startswith("| Wizard | Model"):
|
|
in_table = True
|
|
continue
|
|
if in_table:
|
|
if ln.startswith("**Two faults**"):
|
|
break
|
|
s = ln.strip()
|
|
if s.startswith("|") and not s.startswith("|---"):
|
|
rows.append(s)
|
|
out = []
|
|
for r in rows:
|
|
cells = [c.strip() for c in r.split("|") if c.strip()]
|
|
if len(cells) >= 3 and cells[0]:
|
|
out.append({"Wizard": cells[0], "Model": cells[1], "Lane": cells[2]})
|
|
return out
|
|
|
|
|
|
def recompute_checkin(z):
|
|
key = {"Timmy": "timmy", "Zephyrus Sparkborn": "zephyrus",
|
|
"Cassian Assayborn": "cassian", "Merlin": "merlin",
|
|
"Mercury": "mercury", "Gemini": "gemini", "Argus": "argus",
|
|
"Volt": "volt", "Sentinel": "sentinel", "Beam": "beam"}[z]
|
|
best = None
|
|
for fn in os.listdir(CHECKINS):
|
|
if not fn.endswith(".md"):
|
|
continue
|
|
base = fn[:-3]
|
|
if len(base) > 10 and base[10:11] == "-" and base[11:].startswith(key):
|
|
d = base[:10]
|
|
if best is None or d > best:
|
|
best = d
|
|
return best if best else "— (no check-in)"
|
|
|
|
|
|
def recompute_commit(z):
|
|
# map roster name -> a git author name we can match (case-insensitive prefix)
|
|
git = subprocess.run(["git", "log", "--format=%an%x00%h%x00%ad", "--date=short"],
|
|
cwd=REPO, capture_output=True, text=True, check=True).stdout
|
|
best = {}
|
|
for ln in git.splitlines():
|
|
a, h, d = ln.split("\x00")
|
|
if a not in best: # git log is newest-first: first occurrence = newest
|
|
best[a] = (h, d)
|
|
for name in z, z.lower(), z.title():
|
|
for an in list(best):
|
|
if an.lower() == name.lower() or an.lower().startswith(name.lower()):
|
|
h, d = best[an]
|
|
return h + " " + d
|
|
# fall back to exact roster-name author
|
|
if z in best:
|
|
h, d = best[z]
|
|
return h + " " + d
|
|
return "— —"
|
|
|
|
|
|
def main():
|
|
art = sys.argv[1] if len(sys.argv) > 1 else \
|
|
os.path.join(REPO, "wizard-work/ledger/live-wizard-roster.tsv")
|
|
if not os.path.exists(art):
|
|
print("CHECK: FAIL - artifact missing: %s" % art)
|
|
sys.exit(1)
|
|
|
|
roster = load_roster()
|
|
got = list(csv.DictReader(open(art, newline="", encoding="utf-8"), delimiter="\t"))
|
|
errs = []
|
|
|
|
names = [r["Wizard"] for r in got]
|
|
if len(names) != len(roster):
|
|
errs.append("row count %d != roster %d" % (len(names), len(roster)))
|
|
for r in roster:
|
|
if r["Wizard"] not in names:
|
|
errs.append("missing wizard %r" % r["Wizard"])
|
|
|
|
for r in got:
|
|
z = r["Wizard"]
|
|
want_ci = recompute_checkin(z)
|
|
if r["Latest check-in"] != want_ci:
|
|
errs.append("%s: check-in %r != live %r" % (z, r["Latest check-in"], want_ci))
|
|
want_nc = recompute_commit(z)
|
|
if r["Newest commit"] != want_nc:
|
|
errs.append("%s: commit %r != live %r" % (z, r["Newest commit"], want_nc))
|
|
|
|
if errs:
|
|
print("CHECK: FAIL")
|
|
for e in errs:
|
|
print(" -", e)
|
|
sys.exit(1)
|
|
|
|
print("CHECK: PASS")
|
|
print(" wizards: %d (roster=%d)" % (len(got), len(roster)))
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|