140 lines
5.2 KiB
Python
Executable File
140 lines
5.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Verifier for repo-2026-11-01-commit-census.tsv.
|
|
|
|
The daily commit-author census is a SNAPSHOT of the repo at revision
|
|
BASELINE (d27c03f0e4651c9a4ee9323830d6d795f0d2b40a), taken before the ledger
|
|
census commit landed. HEAD moves as later commits (including this pipe's own
|
|
ledger commits) are added, so the check must regenerate from BASELINE, not from
|
|
the moving HEAD.
|
|
|
|
Re-derives the 2026-11-01 commit-author census from git at BASELINE and compares
|
|
it against the artifact. Exit code 0 = artifact matches git (verdict correct),
|
|
nonzero = mismatch.
|
|
|
|
Usage: python3 check_2026-11-01-commit-census.py
|
|
"""
|
|
import subprocess
|
|
import sys
|
|
|
|
REPO = "/root/troupe-standup"
|
|
TSV = "/root/.hermes/wizard-outputs/ledger/repo-2026-11-01-commit-census.tsv"
|
|
DATE = "2026-11-01"
|
|
|
|
# Revision the snapshot was taken from. bdfb48a (the ledger census commit) sits
|
|
# on top of this; it must not be part of the data it describes.
|
|
BASELINE = "d27c03f0e4651c9a4ee9323830d6d795f0d2b40a"
|
|
|
|
WIZARD_BY_EMAIL = {
|
|
"timmy@stackchain.ai": "Timmy",
|
|
"timmy@alexanderwhitestone.com": "Timmy",
|
|
"timmy@agents.invalid": "Timmy",
|
|
"timmy@stackchain.invalid": "Timmy",
|
|
"timmy@hermes": "Timmy",
|
|
"timmy@stacktimmy.invalid": "Timmy",
|
|
"timmy@localhost": "Timmy",
|
|
"timmy@alexandrewswhitestone.com": "Timmy",
|
|
"timmy@stachain.ai": "Timmy",
|
|
"zephyrus-sparkborn@agents.invalid": "Zephyrus Sparkborn",
|
|
"zephyrus-sparkborn@users.noreply.forge.alexanderwhitestone.com": "Zephyrus Sparkborn",
|
|
"zephyrus-sparkborn@users.noreply.github.com": "Zephyrus Sparkborn",
|
|
"zephyrus-sparkborn@noreply.local": "Zephyrus Sparkborn",
|
|
"zephyrus@alexanderwhitestone.com": "Zephyrus Sparkborn",
|
|
"cassian@agents.invalid": "Cassian Assayborn",
|
|
"cassian@forge.local": "Cassian Assayborn",
|
|
"volt@agents.invalid": "Volt",
|
|
"volt@troupe.invalid": "Volt",
|
|
"volt@troupe.local": "Volt",
|
|
"volt@stackchain.local": "Volt",
|
|
"volt@alexanderwhitestone.com": "Volt",
|
|
"volt@stackchain.ai": "Volt",
|
|
"ledger@agents.invalid": "Ledger",
|
|
"forge@agents.invalid": "Forge",
|
|
"mercury@agents.invalid": "Mercury",
|
|
"merlin@agents.invalid": "Merlin",
|
|
"sol@agents.invalid": "Sol",
|
|
"quill@agents.invalid": "Quill",
|
|
"sphinx@agents.invalid": "Sphinx",
|
|
"null@agents.invalid": "Null",
|
|
"argus@agents.invalid": "Argus",
|
|
"argus@troupe": "Argus",
|
|
"sentinel@troupe-standup.internal": "Sentinel",
|
|
"beam@agents.invalid": "Beam",
|
|
"troupe-pipe@agents.invalid": "Troupe-pipe",
|
|
"pipe@localhost": "Pipe",
|
|
}
|
|
|
|
def git(*args):
|
|
return subprocess.run(
|
|
["git", "-C", REPO] + list(args), check=True, capture_output=True, text=True
|
|
).stdout
|
|
|
|
def recompute():
|
|
rows = []
|
|
for line in git("log", BASELINE, "--format=%an\t%ae\t%aI\t%H\t%s").splitlines():
|
|
p = line.split("\t")
|
|
if len(p) == 5 and p[2].startswith(DATE + "T"):
|
|
rows.append(p)
|
|
groups = {}
|
|
for name, email, adate, sha, subject in rows:
|
|
wizard = WIZARD_BY_EMAIL.get(email, "Unmapped")
|
|
groups.setdefault(wizard, []).append((adate, sha))
|
|
out = []
|
|
for wizard, recs in groups.items():
|
|
recs.sort()
|
|
out.append({
|
|
"wizard": wizard,
|
|
"commits": len(recs),
|
|
"first_commit": recs[0][0],
|
|
"last_commit": recs[-1][0],
|
|
"latest_sha": recs[-1][1],
|
|
})
|
|
return out
|
|
|
|
def main():
|
|
expected = recompute()
|
|
expected.sort(key=lambda r: (-r["commits"], r["wizard"]))
|
|
|
|
with open(TSV, encoding="utf-8") as f:
|
|
lines = [ln for ln in f.read().splitlines() if ln.strip()]
|
|
header, data = lines[0], lines[1:]
|
|
|
|
problems = []
|
|
if header != "wizard\tcommits\tfirst_commit\tlast_commit\tlatest_sha\tsubjects":
|
|
problems.append(f"header={header!r}")
|
|
|
|
if len(data) != len(expected):
|
|
problems.append(f"row count: artifact={len(data)} git@baseline={len(expected)}")
|
|
|
|
total_expected = sum(r["commits"] for r in expected)
|
|
total_artifact = sum(int(r.split("\t")[1]) for r in data if len(r.split("\t")) == 6)
|
|
if total_expected != total_artifact:
|
|
problems.append(f"total commits: artifact={total_artifact} git@baseline={total_expected}")
|
|
|
|
for i, (art, exp) in enumerate(zip(data, expected)):
|
|
fields = art.split("\t")
|
|
if len(fields) != 6:
|
|
problems.append(f"row {i}: {len(fields)} fields")
|
|
continue
|
|
wizard, commits, first, last, sha, subjects = fields
|
|
checks = [
|
|
(wizard == exp["wizard"], f"row {i} wizard {wizard} != {exp['wizard']}"),
|
|
(int(commits) == exp["commits"], f"row {i} commits {commits} != {exp['commits']}"),
|
|
(first == exp["first_commit"], f"row {i} first {first} != {exp['first_commit']}"),
|
|
(last == exp["last_commit"], f"row {i} last {last} != {exp['last_commit']}"),
|
|
(sha == exp["latest_sha"], f"row {i} sha {sha} != {exp['latest_sha']}"),
|
|
]
|
|
problems += [msg for ok, msg in checks if not ok]
|
|
|
|
if problems:
|
|
print("REPO CENSUS MISMATCH")
|
|
for p in problems:
|
|
print(" -", p)
|
|
sys.exit(1)
|
|
|
|
print(f"REPO CENSUS OK: snapshot at {BASELINE}; {len(expected)} wizard rows, "
|
|
f"{total_expected} commits on {DATE} match git")
|
|
sys.exit(0)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|