#!/usr/bin/env python3 """verify_wizards.py — re-derive the full commit-history extraction from live git and compare every committed artifact (CSV/JSON/MD) against it. Exits 0 only when every check matches exactly. No stubs: every number is re-derived from git.""" import subprocess, sys, json, csv, collections, os REPO = '/root/troupe-standup' OUT = '/root/troupe-standup/wizard-work/ledger' CSV = os.path.join(OUT, '2026-10-25-git-history.csv') JSON = os.path.join(OUT, '2026-10-25-git-history.json') MD = os.path.join(OUT, '2026-10-25-git-history.md') fails = [] def ok(msg): print('PASS: ' + msg) def bad(msg): print('FAIL: ' + msg) fails.append(msg) def git(*args): return subprocess.run(['git', '-C', REPO] + list(args), check=True, capture_output=True, text=True).stdout def file_count(hashfull): out = git('show', '--name-status', '--format=', hashfull) n = 0 for l in out.split('\n'): if l and not l.startswith('only'): n += 1 return n # ---- Live ground truth (re-derived from git, nothing trusted) ---- commits = [] for line in git('log', '--date=iso-strict', '--format=%x1f%H%x1f%h%x1f%an%x1f%ae%x1f%aI%x1f%s').split('\n'): if not line: continue f = line.split('\x1f') assert len(f) == 7, line commits.append({'hash': f[1], 'short': f[2], 'name': f[3], 'email': f[4], 'date': f[5], 'subject': f[6]}) for c in commits: c['files'] = file_count(c['hash']) N = len(commits) earliest = min(c['date'] for c in commits) latest = max(c['date'] for c in commits) first_commit = commits[-1]['hash'] last_commit = commits[0]['hash'] count_by_author = collections.Counter((c['name'], c['email']) for c in commits) census = [{'name': n, 'email': e, 'count': cnt} for (n, e), cnt in sorted(count_by_author.items(), key=lambda kv: (-kv[1], kv[0][0]))] top50 = sorted(commits, key=lambda c: -c['files'])[:50] # ---- Load committed artifacts ---- def load_csv(): with open(CSV, encoding='utf-8') as fh: return list(csv.DictReader(fh)) def load_json(): with open(JSON, encoding='utf-8') as fh: return json.load(fh) def load_md_top50(): rows = [] with open(MD, encoding='utf-8') as fh: for line in fh: if line.startswith('| 1 |') or (line.startswith('| ') and '|' in line): pass # robust: parse any line starting with '| |' import re pat = re.compile(r'^\|\s*(\d+)\s*\|\s*`?([0-9a-f]+)`?\s*\|\s*([^|]+)\|\s*([^|]+)\|\s*([^|]+)\|\s*(\d+)\s*\|') out = [] with open(MD, encoding='utf-8') as fh: for line in fh: m = pat.match(line) if m: out.append({'rank': int(m.group(1)), 'hash': m.group(2), 'author': m.group(3).strip(), 'date': m.group(4).strip(), 'subject': m.group(5).strip(), 'files': int(m.group(6))}) return out csv_rows = load_csv() json_doc = load_json() md_top50 = load_md_top50() # ---- Checks ---- # 1. Counts if len(csv_rows) == N: ok('csv data rows = %d == live commit count %d' % (len(csv_rows), N)) else: bad('csv data rows = %d, expected %d' % (len(csv_rows), N)) if json_doc.get('commits') == N: ok('json commits = %d == live count %d' % (json_doc.get('commits'), N)) else: bad('json commits = %r, expected %d' % (json_doc.get('commits'), N)) # 2. Earliest / latest if earliest == json_doc.get('earliest') == csv_rows[0]['date']: ok('earliest = %s' % earliest) else: bad('earliest declared=%r observed=%r' % (json_doc.get('earliest'), earliest)) if latest == json_doc.get('latest') == csv_rows[-1]['date']: ok('latest = %s' % latest) else: bad('latest declared=%r observed=%r' % (json_doc.get('latest'), latest)) # 3. First / last commits if csv_rows[0]['hash'] == last_commit and csv_rows[-1]['hash'] == first_commit: ok('csv first/last commits match live HEAD/root') else: bad('csv first/last mismatch: %r / %r' % (csv_rows[0]['hash'], csv_rows[-1]['hash'])) if json_doc.get('first_commit') == first_commit and json_doc.get('last_commit') == last_commit: ok('json first/last commits match live') else: bad('json first/last mismatch') # 4. Field-for-field CSV vs live bad_rows = 0 for i, c in enumerate(commits, 1): r = csv_rows[i-1] live = ['%d' % i, c['hash'], c['short'], c['name'], c['email'], c['date'], c['subject'], '%d' % c['files']] if [r.get(k) for k in ('rank','hash','short','name','email','date','subject','files')] != live: bad_rows += 1 if bad_rows <= 3: bad('csv row %d mismatch: %r' % (i, r)) if bad_rows == 0: ok('csv field-for-field matches live derivation for all %d rows' % N) # 5. JSON rows vs live jrows = json_doc.get('rows', []) if len(jrows) == N: ok('json rows = %d' % len(jrows)) else: bad('json rows = %d, expected %d' % (len(jrows), N)) jbad = 0 for i, c in enumerate(commits, 1): j = jrows[i-1] live = {'hash': c['hash'], 'short': c['short'], 'name': c['name'], 'email': c['email'], 'date': c['date'], 'subject': c['subject'], 'files': c['files']} if {k: j.get(k) for k in live} != live: jbad += 1 if jbad <= 3: bad('json row %d mismatch' % i) if jbad == 0: ok('json rows field-for-field match live derivation for all %d rows' % N) # 6. Census if json_doc.get('census') == census: ok('json census matches live derivation (%d authors)' % len(census)) else: bad('json census mismatch') # 7. Top-50 markdown vs live if len(md_top50) == 50: ok('md top-50 rows = %d' % len(md_top50)) else: bad('md top-50 rows = %d, expected 50' % len(md_top50)) mismatch = 0 for live50 in top50: m = next((x for x in md_top50 if x['rank'] == commits.index(live50) + 1), None) # top50 order == md order if not m or m['hash'] != live50['short'] or m['author'] != live50['name'] or m['date'] != live50['date'] or m['files'] != live50['files']: mismatch += 1 if mismatch <= 3: bad('md top-50 row %d mismatch vs live' % live50['rank']) if mismatch == 0: ok('md Top-50 table matches live re-derivation') # 8. Root commit present & files count root = json_doc.get('first_commit') if root and git('rev-parse','-q','--verify', root + '^{commit}').strip(): ok('root commit present: %s' % root) else: bad('root commit missing from json or not a commit') print('') if fails: print('CHECK FAIL: %d check(s) failed.' % len(fails)) sys.exit(1) print('CHECK PASS: git commit-history extraction re-derives cleanly against live git.')