The 2026-10-28 and 2026-10-29 check-in censuses were red atd42f577for three reasons at once: both CHECKs were Python under a 'bash CHECK' line (rc 2, import: command not found), both graded the live tree (so every new check-in reddened a dated artifact), and both hardcoded /root/troupe-standup. The 10-28 table also carried a 101st row for checkins/2026-10-27-sentinel.md, a file in no revision. - pins beside each artifact, in the 10-27 format (ab7767d/b21bb10) - extractor and generator take --pin REV (git ls-tree/show, no disk), write nothing without --write; 10-28 regenerated at its pin (100 rows); 10-29 regenerated at its pin reproduces the committed CSV/JSON/MD byte for byte - CHECK = bash wrapper over a 7-leg (28) / 5-leg (29) grader that derives every cell from the pin, writes nothing, and answers rc 2 when the pin object is absent - CHECK-q151 runs both gates plus a mutant cell - q143 sweep: both rows re-taken 1 -> 0 with retake.py, CHECK-q151 added; q137 open-set pin moved with the record
238 lines
9.5 KiB
Python
Executable File
238 lines
9.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Grade the 2026-10-29 check-in signee census against its PINNED revision.
|
|
|
|
WHAT IT GRADES -- five legs, every one re-derived from `git ls-tree` / `git show`
|
|
/ `git log` at the pinned commit, compared against the committed tables. Nothing
|
|
is read from the working tree (so tomorrow's check-in cannot red this artifact)
|
|
and nothing is written (the previous revision's legs ran against `git ls-files`,
|
|
which is the live index, and its row set grew with the archive).
|
|
|
|
1 scope the pin file beside this script: header, sortedness, uniqueness, names
|
|
2 pin the dated name list equals `checkins/` at the pinned commit (SKIP if
|
|
the object is not in this database -- the committed list is then the pin)
|
|
3 csv every cell of the committed CSV against the re-derivation, plus the
|
|
`checkins/README.md` index row and the header order
|
|
4 md the MD's table block, row for row, against the same derivation
|
|
5 json the JSON's rows and `total_checkin_rows`
|
|
|
|
EXIT CODES: 0 every leg passed; 1 a fault (including "the pin is not a usable
|
|
input"); 2 cannot grade at all (no git).
|
|
|
|
Usage: python3 grade.py [--repo DIR] (or `bash CHECK`, which runs this file)
|
|
"""
|
|
import argparse
|
|
import csv
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
PIN_FILE = HERE / "2026-10-29-checkin-census.files"
|
|
TAG = "2026-10-29"
|
|
CSV_PATH = HERE / "checkin-census.csv"
|
|
MD_PATH = HERE / "checkin-census.md"
|
|
JSON_PATH = HERE / "checkin-census.json"
|
|
INDEX_FILE = "checkins/README.md"
|
|
FIELDS = ["file", "date", "signee", "headline", "committer", "kind"]
|
|
NAME_RE = re.compile(r"^(\d{4}-\d{2}-\d{2})-(.+)\.md$")
|
|
PIN_RE = re.compile(r"^# pin checkins@([0-9a-f]{40}) tree=([0-9a-f]{40})$")
|
|
|
|
|
|
class Fault(Exception):
|
|
"""A graded leg failed; the message is the verdict line."""
|
|
|
|
|
|
class CannotGrade(Exception):
|
|
"""The instrument itself cannot run."""
|
|
|
|
|
|
def git(repo, *args):
|
|
try:
|
|
out = subprocess.run(["git", "-C", str(repo)] + list(args),
|
|
capture_output=True, timeout=180)
|
|
except (OSError, subprocess.SubprocessError) as exc:
|
|
raise CannotGrade("git %s: %s" % (args[0], exc))
|
|
if out.returncode != 0:
|
|
raise Fault("git %s failed: %s" % (" ".join(args),
|
|
out.stderr.decode("utf-8", "replace").strip()))
|
|
return out.stdout
|
|
|
|
|
|
def find_repo():
|
|
for cand in (HERE, HERE.parent):
|
|
r = subprocess.run(["git", "-C", str(cand), "rev-parse", "--show-toplevel"],
|
|
capture_output=True, text=True)
|
|
if r.returncode == 0 and r.stdout.strip():
|
|
return Path(r.stdout.strip())
|
|
return HERE.parent.parent.parent
|
|
|
|
|
|
def require_pin(repo, sha):
|
|
"""A revision this clone does not carry cannot grade a census of it."""
|
|
try:
|
|
git(repo, "cat-file", "-e", sha + "^{commit}")
|
|
except Fault:
|
|
raise CannotGrade("pin %s is not in this object database: a census of that "
|
|
"revision cannot be graded without it" % sha[:12])
|
|
|
|
|
|
def read_pin():
|
|
if not PIN_FILE.is_file() or PIN_FILE.stat().st_size == 0:
|
|
raise Fault("SCOPE NOT ESTABLISHED: pinned name list missing or empty: %s"
|
|
% PIN_FILE.name)
|
|
lines = PIN_FILE.read_text(encoding="utf-8").splitlines()
|
|
m = PIN_RE.match(lines[0])
|
|
if not m:
|
|
raise Fault("SCOPE NOT ESTABLISHED: pin header not understood: %r" % lines[0])
|
|
names = lines[1:]
|
|
if not names:
|
|
raise Fault("SCOPE NOT ESTABLISHED: pin names no files")
|
|
bad = [n for n in names if not NAME_RE.match(n)]
|
|
if bad:
|
|
raise Fault("SCOPE NOT ESTABLISHED: %d pinned name(s) are not YYYY-MM-DD-*.md "
|
|
"(first: %r)" % (len(bad), bad[0]))
|
|
if len(set(names)) != len(names):
|
|
raise Fault("SCOPE NOT ESTABLISHED: duplicated name(s) in the pin")
|
|
if sorted(names) != names:
|
|
raise Fault("SCOPE NOT ESTABLISHED: pin names are not sorted (LC_ALL=C)")
|
|
return m.group(1), m.group(2), names
|
|
|
|
|
|
def leg_pin(repo, sha, tree, names):
|
|
real = git(repo, "rev-parse", ("%s:checkins" % sha)).decode().strip()
|
|
if real != tree:
|
|
raise Fault("pin tree %s is not %s:checkins (%s)" % (tree[:12], sha[:7], real[:12]))
|
|
out = git(repo, "ls-tree", "-r", "--name-only", sha, "--", "checkins/").decode("utf-8")
|
|
at_pin = sorted(p[len("checkins/"):] for p in out.splitlines()
|
|
if p.startswith("checkins/") and NAME_RE.match(p[len("checkins/"):]))
|
|
if at_pin != names:
|
|
only_pin = [n for n in at_pin if n not in set(names)]
|
|
only_list = [n for n in names if n not in set(at_pin)]
|
|
raise Fault("pin name list != checkins/ at %s (+%d at pin, +%d in pin): %s %s"
|
|
% (sha[:7], len(only_pin), len(only_list), only_pin[:3], only_list[:3]))
|
|
return "PASS pin: %d dated name(s) == checkins/ tree %s at %s" % (
|
|
len(names), tree[:12], sha[:7])
|
|
|
|
|
|
def derive(repo, sha, names):
|
|
"""Rows in the artifact's order: (date, file), plus the README index row."""
|
|
rows = []
|
|
for name in names:
|
|
path = "checkins/" + name
|
|
m = NAME_RE.match(name)
|
|
signee = re.sub(r"-\d+$", "", m.group(2))
|
|
text = git(repo, "show", "%s:%s" % (sha, path)).decode("utf-8", "replace")
|
|
head = ""
|
|
for line in text.split("\n"):
|
|
if line.startswith("# "):
|
|
head = line[2:].strip()
|
|
break
|
|
who = git(repo, "log", "-1", "--format=%an <%ae>", sha, "--", path).decode().strip()
|
|
rows.append({"file": path, "date": m.group(1), "signee": signee,
|
|
"headline": head, "committer": who or "(no committer)",
|
|
"kind": "checkin"})
|
|
tracked = git(repo, "ls-tree", "-r", "--name-only", sha, "--", "checkins/").decode()
|
|
if INDEX_FILE in tracked.splitlines():
|
|
rows.append({"file": INDEX_FILE, "date": TAG, "signee": "troupe-pipe (index)",
|
|
"headline": "Check-ins index",
|
|
"committer": "(index file; no committer)", "kind": "index"})
|
|
rows.sort(key=lambda r: (r["date"], r["file"]))
|
|
return rows
|
|
|
|
|
|
def leg_csv(rows):
|
|
with open(CSV_PATH, encoding="utf-8", newline="") as fh:
|
|
reader = csv.reader(fh)
|
|
header = next(reader)
|
|
got = list(reader)
|
|
if header != FIELDS:
|
|
raise Fault("CSV header %r != %r" % (header, FIELDS))
|
|
want = [[r[f] for f in FIELDS] for r in rows]
|
|
if len(got) != len(want):
|
|
raise Fault("CSV has %d row(s); the pin derives %d" % (len(got), len(want)))
|
|
for i, (g, w) in enumerate(zip(got, want)):
|
|
if g != w:
|
|
raise Fault("CSV row %d: %r != pin %r" % (i, g, w))
|
|
return "PASS csv: %d row(s) match the pin cell for cell" % len(got)
|
|
|
|
|
|
def md_rows(text):
|
|
out, inside = [], False
|
|
for line in text.splitlines():
|
|
if not line.startswith("|"):
|
|
if inside:
|
|
break
|
|
continue
|
|
cells = [c.strip() for c in line.strip().strip("|").split("|")]
|
|
if not inside:
|
|
inside = cells == FIELDS
|
|
continue
|
|
if set("".join(cells)) <= set("-: "):
|
|
continue
|
|
out.append(cells)
|
|
return out
|
|
|
|
|
|
def leg_md(rows):
|
|
got = md_rows(MD_PATH.read_text(encoding="utf-8"))
|
|
want = [[r[f] for f in FIELDS] for r in rows]
|
|
if got != want:
|
|
bad = next((i for i in range(min(len(got), len(want))) if got[i] != want[i]),
|
|
min(len(got), len(want)))
|
|
raise Fault("MD table: %d row(s) vs pin %d; first difference at row %d "
|
|
"(md %r, pin %r)" % (len(got), len(want), bad,
|
|
got[bad] if bad < len(got) else None,
|
|
want[bad] if bad < len(want) else None))
|
|
return "PASS md: %d row(s) match the pin" % len(got)
|
|
|
|
|
|
def leg_json(rows):
|
|
doc = json.loads(JSON_PATH.read_text(encoding="utf-8"))
|
|
got = [{f: r.get(f) for f in FIELDS} for r in doc.get("rows", [])]
|
|
want = [{f: r[f] for f in FIELDS} for r in rows]
|
|
if got != want:
|
|
bad = next((i for i in range(min(len(got), len(want))) if got[i] != want[i]),
|
|
min(len(got), len(want)))
|
|
raise Fault("JSON rows: %d vs pin %d; first difference at row %d" % (
|
|
len(got), len(want), bad))
|
|
n_dated = len([r for r in rows if r["kind"] == "checkin"])
|
|
if doc.get("total_checkin_rows") != n_dated:
|
|
raise Fault("JSON total_checkin_rows %r != %d derived" % (
|
|
doc.get("total_checkin_rows"), n_dated))
|
|
return "PASS json: %d row(s), total_checkin_rows=%d, match the pin" % (len(got), n_dated)
|
|
|
|
|
|
def main(argv=None):
|
|
ap = argparse.ArgumentParser(description="grade the 2026-10-29 check-in census")
|
|
ap.add_argument("--repo", default=None, help="repository to grade (default: this tree's)")
|
|
args = ap.parse_args(argv)
|
|
repo = Path(args.repo) if args.repo else find_repo()
|
|
|
|
try:
|
|
sha, tree, names = read_pin()
|
|
require_pin(repo, sha)
|
|
print("PASS scope: %d pinned dated file(s), pin %s" % (len(names), sha[:7]))
|
|
lines = [leg_pin(repo, sha, tree, names)]
|
|
rows = derive(repo, sha, names)
|
|
lines.append(leg_csv(rows))
|
|
lines.append(leg_md(rows))
|
|
lines.append(leg_json(rows))
|
|
except Fault as exc:
|
|
print("FAIL: %s" % exc)
|
|
return 1
|
|
except CannotGrade as exc:
|
|
print("CANNOT GRADE: %s" % exc)
|
|
return 2
|
|
|
|
for line in lines:
|
|
print(line)
|
|
print("VERDICT=PASS census-%s rows=%d dated=%d pin=%s"
|
|
% (TAG, len(rows), len(rows) - 1, sha[:7]))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|