#!/usr/bin/env python3 """queue_append_audit.py -- did every queue-touching commit leave its own tree consistent: its index current, and its pin moved to where the queue's counts went? WHY THIS EXISTS `docs/queue-index.md` is rendered from `WORK-QUEUE.md` and pins it by sha256, so a commit that appends to the queue and does not re-render the index is STALE by construction -- there is no "might be stale" case left to argue. Nothing obliged a commit to re-render, and the ledger measured the cost twice: `f2c3067` and `067afdf` each left the gate red until a later repair commit, the second for 2h37m until `de3dcdc`. The rule the ledger now states is "append and re-render in the SAME commit; `--check` must read rc 0 at the commit it lands in". Enforcing that rule by hand cost the control plane three wrong-slice counts in one week, and its own 15:16Z correction names the cause: a `queue_index --check` run in a working tree at HEAD says NOTHING about a cited revision -- "my first probe loop reported rc 0 'at 067afdf' while grading HEAD's tree". This tool takes the hand out of the loop. Each commit's own tree is materialised with `git archive` and that tree's OWN `tools/queue_index.py` runs inside it, so every row is a measurement of the commit it names and of the gate as it existed there. WHAT IT MEASURES -- one row per commit that touches the queue in the range verdict the INDEX axis: the commit's own tree passes its own `tools/queue_index.py --check` (rc 0) PASS rc 0 STALE --check graded a stale rendered document (rc 1) UNGRADABLE the gate did not exist in that tree (no tools/queue_index.py, or no index document), or the rev's own tool refused with rc 2. A commit that cannot be graded is NOT reported as stale, and is counted separately -- folding the two into one bucket is the exact defect this tool was built after. ERROR any other exit, a crash, or a timeout. rc 1 without the tool's `STALE:` line is a crash, not a fault. pin the PIN axis: the same tree's own `wizard-work/volt/q137-open-set/open_set.py --check`, which compares WORK-QUEUE.md's counts with the PIN_COUNTS that file carries. PASS rc 0, the pin moved with the append DRIFT rc 1, the queue moved and the pin did not UNGRADABLE no open_set.py at that rev (the pin is younger than the queue), or the tool refused with rc 2 ERROR any other exit, a crash, or a timeout The two axes are graded in ONE materialised tree and reported side by side, because neither stands for the other: `56d3853` re-rendered its index and left the pin two headings behind, so its row reads `PASS` on the index and `DRIFT` on the pin. queue_add/del numstat for the queue in that commit. index_changed whether the same commit also changed the index document -- the mechanical shape of the rule, reported BESIDE the verdict and never instead of it: a commit can touch the index and still be STALE, and a commit can leave the index alone and still be current (an append that lands outside every section's extent would be such a case, and the verdict would say so). Precedence is deliberate: the verdict comes from a checkout of the commit, never from its message, and never from a run at HEAD. `--axis` narrows the run to one of the two axes; the default grades both. WHAT IT DOES NOT DO It repairs nothing, decides nothing about whether an UNGRADABLE commit should have had a gate, and adjudicates no queue content -- closure is `tools/queue_claim_check.py`'s verdict. `--guard` is the same measurement restricted to the NEWEST queue-touching commit, which is the shape a tick's `check.sh` can call: rc 0 means the commit about to be pushed left the index current AND the pin moved in its own tree. EXIT CODES (house contract, decided 2026-10-17 at 6ee8fce) 0 ran; nothing graded faulty (UNGRADABLE rows are reported, not faults) 1 ran and graded a fault: at least one STALE or ERROR row 2 could not grade: not a git repository, an unknown revision, no commit in the range touches the queue, or the queue is not tracked at the target rev 3 retired, never returned USAGE python3 queue_append_audit.py --repo /root/troupe-standup python3 queue_append_audit.py --repo . --from 1423c4d --to HEAD --json out.json python3 queue_append_audit.py --repo . --guard # newest queue-touching commit python3 queue_append_audit.py --selftest # 25 cells, a synthetic history """ from __future__ import annotations import argparse import io import json import os import shutil import subprocess import sys import tempfile QUEUE_DEFAULT = "WORK-QUEUE.md" INDEX_DEFAULT = "docs/queue-index.md" GATE_TOOL = "tools/queue_index.py" # The queue's own pin, graded in the SAME materialised tree: `open_set.py # --check` compares WORK-QUEUE.md's counts against the PIN_COUNTS the file # carries. The index axis cannot see it -- `56d3853` rendered its index and left # the pin two headings behind -- so the audit grades both axes, each reported # beside the other and neither standing for the other. PIN_TOOL = "wizard-work/volt/q137-open-set/open_set.py" HERE = os.path.dirname(os.path.abspath(__file__)) FATAL = ("STALE", "ERROR") PIN_FATAL = ("DRIFT", "ERROR") # ---------------------------------------------------------------- git plumbing def git(repo, *args, timeout=120): """(rc, stdout, stderr) -- never a traceback for a git-level refusal.""" try: p = subprocess.run(["git", "-C", repo] + list(args), capture_output=True, timeout=timeout) except FileNotFoundError: return 127, b"", b"git not found on PATH" except subprocess.TimeoutExpired: return 124, b"", ("git %s timed out after %ss" % (args[0], timeout)).encode() return p.returncode, p.stdout, p.stderr def out_text(rc, out, err): s = (out or b"").decode("utf-8", "replace") e = (err or b"").decode("utf-8", "replace") return s, e def is_repo(repo): rc, _, _ = git(repo, "rev-parse", "--git-dir") return rc == 0 def resolve(repo, rev): rc, out, _ = git(repo, "rev-parse", "--verify", "--quiet", rev + "^{commit}") s, _ = out_text(rc, out, b"") return s.strip() if rc == 0 else None def exists(repo, rev, path): rc, _, _ = git(repo, "cat-file", "-e", "%s:%s" % (rev, path)) return rc == 0 def commit_meta(repo, rev): rc, out, _ = git(repo, "show", "-s", "--format=%h%x00%ad%x00%s", "--date=iso", rev) s, _ = out_text(rc, out, b"") parts = s.rstrip("\n").split("\x00") if s else [] while len(parts) < 3: parts.append("") return {"short": parts[0][:7] or rev[:7], "date": parts[1], "subject": parts[2]} def numstat(repo, rev, queue, index): """(queue_add, queue_del, index_changed) for one commit.""" rc, out, _ = git(repo, "show", "--numstat", "--format=", rev, "--", queue, index) s, _ = out_text(rc, out, b"") add = delete = None touched_index = False for line in s.splitlines(): parts = line.split("\t") if len(parts) != 3: continue a, d, path = parts if path == queue: add = int(a) if a.isdigit() else a delete = int(d) if d.isdigit() else d elif path == index: touched_index = True return add, delete, touched_index def materialise(repo, rev, paths, dest): """Extract exactly `paths` from `rev` into `dest`. Returns (ok, error).""" rc, out, err = git(repo, "archive", "--format=tar", rev, "--", *paths) if rc != 0: s, e = out_text(rc, out, err) return False, (e or s or "git archive rc %d" % rc).strip().splitlines()[0] try: p = subprocess.run(["tar", "-xf", "-", "-C", dest], input=out, capture_output=True, timeout=120) except (FileNotFoundError, subprocess.TimeoutExpired) as exc: return False, "could not unpack: %s" % exc if p.returncode != 0: return False, "tar rc %d: %s" % (p.returncode, p.stderr.decode("utf-8", "replace").strip()) return True, "" # ------------------------------------------------------------------- one commit def run_check(cmd, cwd, timeout): """(rc, stdout, stderr, why) -- a timeout is a verdict, never a traceback.""" try: p = subprocess.run(cmd, cwd=cwd, capture_output=True, timeout=timeout) except subprocess.TimeoutExpired: return None, "", "", "--check timed out after %ss" % timeout return (p.returncode, p.stdout.decode("utf-8", "replace"), p.stderr.decode("utf-8", "replace"), "") def grade_index(td, queue, index, timeout): """The index axis, measured in `td`: {verdict, rc, detail}.""" tool = os.path.join(td, GATE_TOOL) cmd = [sys.executable, tool, "--repo", td, "--queue", os.path.join(td, queue), "--check", os.path.join(td, index)] rc, s, e, why = run_check(cmd, td, timeout) if why: return {"verdict": "ERROR", "rc": None, "detail": why} first = (s.strip() or e.strip() or "").splitlines() out = {"rc": rc, "detail": first[0][:180] if first else ""} if rc == 0: out["verdict"] = "PASS" elif rc == 1 and "STALE:" in s: out["verdict"] = "STALE" elif rc == 2: out["verdict"] = "UNGRADABLE" else: # rc 1 with no STALE line is a crash wearing the verdict code; rc 3 # is retired. Neither is a graded fault, and neither is a pass. out["verdict"] = "ERROR" return out def grade_pin(td, timeout): """The pin axis, measured in `td`: {pin, pin_rc, pin_detail}. `open_set.py --check` compares the queue's own counts against the PIN_COUNTS the file carries, so a commit that appends a heading or a record and leaves the pin where it was reads DRIFT here even when its index is current -- the exact shape `56d3853` landed in, which the index axis read PASS. """ tool = os.path.join(td, PIN_TOOL) rc, s, e, why = run_check([sys.executable, tool, "--check"], td, timeout) if why: return {"pin": "ERROR", "pin_rc": None, "pin_detail": why} lines = (s.strip() or e.strip() or "").splitlines() fault = [ln for ln in lines if ln.startswith("FAIL")] out = {"pin_rc": rc, "pin_detail": (fault[0] if fault else (lines[-1] if lines else ""))[:180]} if rc == 0: out["pin"] = "PASS" elif rc == 1: out["pin"] = "DRIFT" elif rc == 2: # the pin tool itself refused: a tree it cannot read is not a drift out["pin"] = "UNGRADABLE" else: out["pin"] = "ERROR" return out def audit_commit(repo, rev, queue, index, timeout, axis="both"): """Grade one commit IN ITS OWN TREE. Never raises. Two axes, graded independently in one materialised tree, because neither stands for the other: a commit can leave the rendered index current and the queue's own pin two headings behind (Q167's landing), and a commit can move the pin while shipping no index at all. """ row = {"rev": rev} row.update(commit_meta(repo, rev)) add, delete, touched_index = numstat(repo, rev, queue, index) row.update({"queue_add": add, "queue_del": delete, "index_changed": touched_index, "rc": None, "detail": "", "pin_rc": None, "pin_detail": ""}) want_index = axis in ("both", "index") want_pin = axis in ("both", "pin") if not exists(repo, rev, queue): missing = "%s does not exist at this rev" % queue row.update(verdict="UNGRADABLE", detail=missing, pin="UNGRADABLE", pin_detail=missing) return row if want_index: # The gate as it existed AT THAT REV, not as it exists today: a revision # where queue_index.py had not been written yet has no gate, and no # claim about its index can be true or false. if not exists(repo, rev, GATE_TOOL): row.update(verdict="UNGRADABLE", detail="no %s at this rev (the gate did not exist yet)" % GATE_TOOL) want_index = False elif not exists(repo, rev, index): row.update(verdict="UNGRADABLE", detail="no %s at this rev (nothing to compare against)" % index) want_index = False if want_pin and not exists(repo, rev, PIN_TOOL): # the pin is younger than the queue: absent is UNGRADABLE, never DRIFT row.update(pin="UNGRADABLE", pin_detail="no %s at this rev" % PIN_TOOL) want_pin = False if not want_index and not want_pin: return row wanted = [p for p in (queue, index, GATE_TOOL, PIN_TOOL) if exists(repo, rev, p)] td = tempfile.mkdtemp(prefix="qaa-") try: ok, why = materialise(repo, rev, wanted, td) if not ok: why = "could not materialise: %s" % why if want_index: row.update(verdict="ERROR", detail=why) if want_pin: row.update(pin="ERROR", pin_detail=why) return row if want_index: row.update(grade_index(td, queue, index, timeout)) if want_pin: row.update(grade_pin(td, timeout)) return row finally: shutil.rmtree(td, ignore_errors=True) # ------------------------------------------------------------------ the range def queue_revs(repo, queue, rev_from, rev_to): args = ["rev-list", "--reverse", rev_to] if rev_from: args = ["rev-list", "--reverse", "%s..%s" % (rev_from, rev_to)] rc, out, err = git(repo, *(args + ["--", queue])) s, e = out_text(rc, out, err) return rc, [h for h in s.split() if h], e def audit(repo, queue, index, rev_from, rev_to, guard=False, timeout=30, all_history=False, axis="both"): """(rc, payload). The only place exit codes are decided. Range rules, in order: --from REV grade exactly REV..TO (REV excluded), graded or not. --all grade every commit that ever touched the queue, including the hundreds that predate the index. Each of those is UNGRADABLE, which is the honest answer and a useless table. (default) begin at the commit that INTRODUCED the index: no commit before it can be graded, and listing 328 rows that all say so buries the 9 that can. The count left out is printed, never silently dropped. """ payload = {"repo": repo, "queue": queue, "index": index, "from": None, "to": None, "guard": bool(guard), "auto_from": False, "pre_gate": 0, "axis": axis, "rows": [], "counts": {}, "pin_counts": {}, "graded": 0, "pin_graded": 0, "rc": None} def _grade_error(msg): """Attach a grade error to the payload and return the (2, payload) verdict. Every precondition failure in this function is the same three-statement shape: set the message, set rc=2, return. Six inline copies were the smell; this helper is the single source of that shape. """ payload["error"] = msg payload["rc"] = 2 return 2, payload if not os.path.isdir(repo): return _grade_error("not a directory: %s" % repo) if not is_repo(repo): return _grade_error("not a git repository: %s" % repo) to_sha = resolve(repo, rev_to) if to_sha is None: return _grade_error("unknown revision: %s" % rev_to) payload["to"] = to_sha from_sha = None if rev_from: from_sha = resolve(repo, rev_from) if from_sha is None: return _grade_error("unknown revision: %s" % rev_from) elif not all_history: rc, out, _ = git(repo, "log", "--diff-filter=A", "--format=%H", "-1", to_sha, "--", index) intro = out_text(rc, out, b"")[0].strip() if intro: # Include the introducing commit itself: it may also carry a queue # append, and a commit that adds both is exactly the shape to grade. rc2, out2, _ = git(repo, "rev-parse", intro + "^") from_sha = out2.decode("utf-8", "replace").strip() if rc2 == 0 else intro payload["auto_from"] = True payload["from"] = from_sha rc, revs, err = queue_revs(repo, queue, from_sha, to_sha) if rc != 0: return _grade_error("git rev-list failed: %s" % ((err.strip().splitlines() or [""])[0])) if guard: revs = revs[-1:] if not revs: where = ("%s..%s" % (rev_from, rev_to) if rev_from else rev_to) return _grade_error("no commit in %s touches %s, so there is no index " "currency to grade" % (where, queue)) if payload["auto_from"] and not guard: rc_n, out_n, _ = git(repo, "rev-list", "--count", to_sha, "--", queue) try: payload["pre_gate"] = max(0, int(out_n.decode().strip()) - len(revs)) except (ValueError, AttributeError): payload["pre_gate"] = 0 rows = [audit_commit(repo, r, queue, index, timeout, axis) for r in revs] counts, pin_counts = {}, {} for r in rows: if "verdict" in r: counts[r["verdict"]] = counts.get(r["verdict"], 0) + 1 if "pin" in r: pin_counts[r["pin"]] = pin_counts.get(r["pin"], 0) + 1 payload["rows"] = rows payload["counts"] = counts payload["pin_counts"] = pin_counts payload["graded"] = sum(counts.get(k, 0) for k in ("PASS", "STALE", "ERROR")) payload["pin_graded"] = sum(pin_counts.get(k, 0) for k in ("PASS", "DRIFT", "ERROR")) index_fault = any(r.get("verdict") in FATAL for r in rows) pin_fault = any(r.get("pin") in PIN_FATAL for r in rows) payload["rc"] = 1 if (index_fault or pin_fault) else 0 return payload["rc"], payload # --------------------------------------------------------------------- report def report(payload, stream=sys.stdout): w = stream.write if payload.get("error"): w("queue_append_audit: %s\n" % payload["error"]) return frm, to = payload.get("from"), payload.get("to") or payload["to"] if payload["guard"]: where = "newest queue-touching commit at or below %s" % (to or "")[:7] elif payload["auto_from"]: where = "%s..%s (auto: from the commit that introduced %s)" % ( (frm or "")[:7], (to or "")[:7], payload["index"]) elif frm: where = "%s..%s" % (frm[:7], (to or "")[:7]) else: where = "%s (all history)" % (to or "")[:7] w("queue_append_audit -- queue: %s index: %s range: %s%s\n" % (payload["queue"], payload["index"], where, " [guard]" if payload["guard"] else "")) if payload.get("pre_gate"): w(" %d earlier commit(s) touch the queue before the gate existed; they " "cannot be graded and are not listed (--all lists them)\n" % payload["pre_gate"]) w(" %d commit(s) touch the queue\n\n" % len(payload["rows"])) for r in payload["rows"]: add, dele = r["queue_add"], r["queue_del"] q = "q %s/%s" % (add if add is not None else "-", dele if dele is not None else "-") note = r.get("detail") or "" if r.get("pin") and r["pin"] != "PASS": note = "%s | pin: %s" % (note, r.get("pin_detail") or r["pin"]) w(" %-10s %-8s %-25s %-9s index:%-3s pin:%-10s %s\n" % (r.get("verdict") or "-", r["short"], r["date"][:25], q, "yes" if r["index_changed"] else "no", r.get("pin") or "-", note)) c = payload["counts"] pc = payload["pin_counts"] w("\nsummary: %d commit(s) -- %s\n" % (len(payload["rows"]), ", ".join("%d %s" % (c[k], k) for k in ("PASS", "STALE", "UNGRADABLE", "ERROR") if k in c) or "nothing graded")) w("pin: %s\n" % (", ".join("%d %s" % (pc[k], k) for k in ("PASS", "DRIFT", "UNGRADABLE", "ERROR") if k in pc) or "not graded")) ungradable = c.get("UNGRADABLE", 0) w("graded: %d of %d index, %d of %d pin%s\n" % (payload["graded"], len(payload["rows"]), payload["pin_graded"], len(payload["rows"]), (" (%d index UNGRADABLE -- reported, NOT counted as stale)" % ungradable) if ungradable else "")) stale = [r["short"] for r in payload["rows"] if r.get("verdict") == "STALE"] if stale: w("stale: %s\n" % " ".join(stale)) drift = [r["short"] for r in payload["rows"] if r.get("pin") == "DRIFT"] if drift: w("drift: %s\n" % " ".join(drift)) bad = [r["short"] for r in payload["rows"] if r.get("verdict") == "ERROR"] pbad = [r["short"] for r in payload["rows"] if r.get("pin") == "ERROR"] if bad: w("error: %s\n" % " ".join(bad)) if pbad: w("pin err: %s\n" % " ".join(pbad)) if payload["rc"] == 0: w("verdict: every graded commit left its own tree consistent -- " "the index current and the pin where the queue put it\n") else: w("verdict: %d commit(s) left their own tree inconsistent -- " "%d index, %d pin -- rc 1\n" % (len(stale) + len(bad) + len(drift) + len(pbad), len(stale) + len(bad), len(drift) + len(pbad))) # ------------------------------------------------------------------- selftest def _sh(cwd, *args): return subprocess.run(args, cwd=cwd, capture_output=True, text=True) PIN_STUB = '''#!/usr/bin/env python3 """A stand-in for wizard-work/volt/q137-open-set/open_set.py's --check: count the queue's headings and compare them with the PIN this commit carries.""" import argparse import sys QUEUE = "WORK-QUEUE.md" PIN = @PIN@ ap = argparse.ArgumentParser() ap.add_argument("--check", action="store_true") ap.parse_args() n = len([ln for ln in open(QUEUE, encoding="utf-8") if ln.startswith("## ")]) print("queue: %d heading(s)" % n) if n != PIN: print("FAIL count headings moved %d -> %d" % (PIN, n)) print("CHECK 1 fault(s)") sys.exit(1) print("CHECK OK: counts match the pin") sys.exit(0) ''' def _fixture(base): """A seven-commit history with one commit of each kind. Returns (repo, revs). A: queue only, no gate tool -> index UNGRADABLE, pin UNGRADABLE B: appends, adds tools/queue_index.py + its render + the pin tool (PIN=1) -> both PASS C: appends to the queue, no re-render -> index STALE, pin PASS D: appends AND re-renders -> both PASS F: appends a HEADING and re-renders, pin left -> index PASS, pin DRIFT G: appends a record and moves the pin to 2 -> both PASS E: touches an unrelated file -> not selected at all """ repo = os.path.join(base, "repo") os.makedirs(repo) _sh(base, "git", "init", "-q", repo) _sh(repo, "git", "config", "user.name", "Selftest") _sh(repo, "git", "config", "user.email", "selftest@invalid") _sh(repo, "git", "config", "commit.gpgsign", "false") queue = os.path.join(repo, "WORK-QUEUE.md") with open(queue, "w", encoding="utf-8") as fh: fh.write("## Q1 -- alpha\n\nStatus: open\n") _sh(repo, "git", "add", "-A") _sh(repo, "git", "commit", "-q", "-m", "A: queue only") pin_dir = os.path.join(repo, os.path.dirname(PIN_TOOL)) os.makedirs(pin_dir) os.makedirs(os.path.join(repo, "tools")) os.makedirs(os.path.join(repo, "docs")) with open(os.path.join(repo, PIN_TOOL), "w", encoding="utf-8") as fh: fh.write(PIN_STUB.replace("@PIN@", "1")) with open(os.path.join(repo, GATE_TOOL), "w", encoding="utf-8") as fh: fh.write(open(os.path.join(HERE, "queue_index.py"), encoding="utf-8").read()) with open(queue, "a", encoding="utf-8") as fh: fh.write("- beta appended\n") _sh(repo, sys.executable, GATE_TOOL, "--repo", ".", "--write", INDEX_DEFAULT) _sh(repo, "git", "add", "-A") _sh(repo, "git", "commit", "-q", "-m", "B: append + gate + pin + index") with open(queue, "a", encoding="utf-8") as fh: fh.write("- gamma appended\n") _sh(repo, "git", "add", "-A") _sh(repo, "git", "commit", "-q", "-m", "C: append, no re-render") with open(queue, "a", encoding="utf-8") as fh: fh.write("- delta appended\n") _sh(repo, sys.executable, GATE_TOOL, "--repo", ".", "--write", INDEX_DEFAULT) _sh(repo, "git", "add", "-A") _sh(repo, "git", "commit", "-q", "-m", "D: append + re-render") # F: the shape Q167 landed in -- the index is re-rendered and current, the # pin is not moved, so the two axes disagree in ONE row. with open(queue, "a", encoding="utf-8") as fh: fh.write("\n## Q2 -- beta\n\nStatus: open\n") _sh(repo, sys.executable, GATE_TOOL, "--repo", ".", "--write", INDEX_DEFAULT) _sh(repo, "git", "add", "-A") _sh(repo, "git", "commit", "-q", "-m", "F: new heading + re-render, pin left") # G: the repair commit -- touches the queue and moves the pin with it. with open(queue, "a", encoding="utf-8") as fh: fh.write("- epsilon appended\n") with open(os.path.join(repo, PIN_TOOL), "w", encoding="utf-8") as fh: fh.write(PIN_STUB.replace("@PIN@", "2")) _sh(repo, sys.executable, GATE_TOOL, "--repo", ".", "--write", INDEX_DEFAULT) _sh(repo, "git", "add", "-A") _sh(repo, "git", "commit", "-q", "-m", "G: append + move the pin") with open(os.path.join(repo, "notes.txt"), "w", encoding="utf-8") as fh: fh.write("unrelated\n") _sh(repo, "git", "add", "-A") _sh(repo, "git", "commit", "-q", "-m", "E: unrelated file") revs = _sh(repo, "git", "rev-list", "--reverse", "HEAD").stdout.split() return repo, revs def selftest(): cells = [] def check(name, got, want): cells.append((name, got == want, got, want)) base = tempfile.mkdtemp(prefix="qaa-selftest-") try: repo, revs = _fixture(base) a, b, c, d, f, g, e = revs rc, p = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, "HEAD", all_history=True) rows = {r["rev"]: r for r in p["rows"]} check("the audit selects the 6 queue-touching commits, not the 7th", len(p["rows"]), 6) check("a commit with no gate tool is UNGRADABLE, not STALE", rows[a]["verdict"], "UNGRADABLE") check("the commit that added the gate and its index is PASS", rows[b]["verdict"], "PASS") check("an append with no re-render is STALE", rows[c]["verdict"], "STALE") check("an append that re-renders in the same commit is PASS", rows[d]["verdict"], "PASS") check("STALE row says the index was not touched", rows[c]["index_changed"], False) check("PASS row says the index was touched", rows[d]["index_changed"], True) check("the stale row's numstat is the append", (rows[c]["queue_add"], rows[c]["queue_del"]), (1, 0)) check("a range with a STALE row exits 1", rc, 1) check("counts keep UNGRADABLE out of the STALE bucket", p["counts"].get("STALE"), 1) check("graded counts PASS+STALE+ERROR only", p["graded"], 5) check("the STALE detail carries the tool's own word", "STALE:" in rows[c]["detail"], True) # -- the pin axis, measured in the same trees ----------------------- check("a commit that predates the pin tool is pin UNGRADABLE", rows[a]["pin"], "UNGRADABLE") check("the commit that shipped the pin reads pin PASS", rows[b]["pin"], "PASS") check("an index-stale commit whose pin matches reads pin PASS", rows[c]["pin"], "PASS") check("a new heading with the pin left behind reads pin DRIFT", rows[f]["pin"], "DRIFT") check("the DRIFT row's INDEX is still PASS -- the axes are independent", rows[f]["verdict"], "PASS") check("the DRIFT row names the count that moved", "count headings moved 1 -> 2" in rows[f]["pin_detail"], True) check("a queue-touching commit that moves the pin reads pin PASS", rows[g]["pin"], "PASS") check("DRIFT is kept out of the pin PASS bucket", p["pin_counts"].get("DRIFT"), 1) check("pin UNGRADABLE is counted, not folded into DRIFT", p["pin_counts"].get("UNGRADABLE"), 1) check("pin_graded counts PASS+DRIFT+ERROR only", p["pin_graded"], 5) check("a DRIFT row alone exits 1", rows[f]["pin_rc"], 1) rc_p, p_p = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, f, guard=True, axis="pin") check("--axis pin grades the pin alone and still exits 1 on DRIFT", (rc_p, p_p["rows"][0]["pin"]), (1, "DRIFT")) check("--axis pin sets no index verdict on the row", "verdict" in p_p["rows"][0], False) check("--axis pin counts no index rows", p_p["counts"], {}) rc_i, p_i = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, c, guard=True, axis="index") check("--axis index reads the same commit STALE on its own axis", (rc_i, p_i["rows"][0]["verdict"], "pin" in p_i["rows"][0]), (1, "STALE", False)) rc_g, p_g = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, "HEAD", guard=True) check("--guard on a clean head exits 0", rc_g, 0) check("--guard reports exactly one commit", len(p_g["rows"]), 1) check("--guard's one row is clean on BOTH axes", (p_g["rows"][0]["verdict"], p_g["rows"][0]["pin"]), ("PASS", "PASS")) rc_s, _ = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, c, guard=True) check("--guard on the stale commit exits 1", rc_s, 1) rc_f, _ = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, f, guard=True) check("--guard on the drift commit exits 1", rc_f, 1) # Default range: begin where the gate does. B introduced the index, so # the default table starts at B's parent and A is counted, not listed. rc_a, p_a = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, "HEAD") check("the default range begins at the commit that introduced the index", (p_a["auto_from"], len(p_a["rows"])), (True, 5)) check("pre-gate commits are counted, not silently dropped", p_a["pre_gate"], 1) check("the default range still grades the stale commit", rc_a, 1) rc_e, p_e = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, d, d) check("an empty range is rc 2, not a clean 0", (rc_e, p_e.get("error") is not None), (2, True)) rc_r, _ = audit(os.path.join(base, "no-such-repo"), QUEUE_DEFAULT, INDEX_DEFAULT, None, "HEAD") check("a missing repo is rc 2", rc_r, 2) rc_b, p_b = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, "no-such-rev") check("an unknown revision is rc 2", (rc_b, p_b.get("error") is not None), (2, True)) rc_q, p_q = audit(repo, "NOT-THE-QUEUE.md", INDEX_DEFAULT, None, "HEAD") check("a queue no commit touches is rc 2", (rc_q, "touches" in (p_q.get("error") or "")), (2, True)) json_path = os.path.join(base, "out.json") buf = io.StringIO() rc_j, p_j = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, "HEAD", all_history=True) with open(json_path, "w", encoding="utf-8") as fh: json.dump(p_j, fh, indent=1) report(p_j, stream=buf) with open(json_path, encoding="utf-8") as fh: loaded = json.load(fh) check("--json round-trips the same 6 rows", len(loaded["rows"]), 6) check("the report names the stale commit by short sha", c[:7] in buf.getvalue(), True) check("the report names the drift commit on its own line", "drift: " + f[:7] in buf.getvalue(), True) check("a report with a fault never prints a clean verdict", "every graded commit" in buf.getvalue(), False) finally: shutil.rmtree(base, ignore_errors=True) bad = [c for c in cells if not c[1]] for name, ok, got, want in bad: print("FAIL %s\n got: %r\n want: %r" % (name, got, want)) print("selftest: %d cells, %d passed, %d failed" % (len(cells), len(cells) - len(bad), len(bad))) return 0 if not bad else 1 # ----------------------------------------------------------------------- main def main(argv=None): ap = argparse.ArgumentParser( description="Did every commit that touched the work queue leave its " "index current IN ITS OWN TREE?") ap.add_argument("--repo", default=".") ap.add_argument("--queue", default=QUEUE_DEFAULT) ap.add_argument("--index", default=INDEX_DEFAULT) ap.add_argument("--from", dest="rev_from", default=None, help="exclusive lower bound; default: the commit that " "introduced the index document") ap.add_argument("--to", dest="rev_to", default="HEAD") ap.add_argument("--all", dest="all_history", action="store_true", help="start at the root of history, listing the pre-gate " "commits as UNGRADABLE rows too") ap.add_argument("--guard", action="store_true", help="grade only the newest queue-touching commit") ap.add_argument("--axis", choices=("both", "index", "pin"), default="both", help="which axis to grade: the rendered index, the queue's " "own PIN_COUNTS, or both (default both)") ap.add_argument("--timeout", type=int, default=30, help="seconds per commit's --check (default 30)") ap.add_argument("--json", metavar="PATH", help="write the rows here") ap.add_argument("--selftest", action="store_true") args = ap.parse_args(argv) if args.selftest: return selftest() rc, payload = audit(args.repo, args.queue, args.index, args.rev_from, args.rev_to, args.guard, args.timeout, args.all_history, args.axis) report(payload) if args.json: try: with open(args.json, "w", encoding="utf-8") as fh: json.dump(payload, fh, indent=1) except OSError as exc: print("cannot write json: %s" % exc, file=sys.stderr) return 2 return rc if __name__ == "__main__": sys.exit(main())