diff --git a/tools/queue_append_audit.py b/tools/queue_append_audit.py new file mode 100644 index 0000000..ec382b0 --- /dev/null +++ b/tools/queue_append_audit.py @@ -0,0 +1,577 @@ +#!/usr/bin/env python3 +"""queue_append_audit.py -- was the queue's index current IN THE TREE of every +commit that touched the queue? + +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 PASS the commit's own tree passes its own --check (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. + 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. + +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 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 re +import subprocess +import sys +import tempfile + +QUEUE_DEFAULT = "WORK-QUEUE.md" +INDEX_DEFAULT = "docs/queue-index.md" +GATE_TOOL = "tools/queue_index.py" +HERE = os.path.dirname(os.path.abspath(__file__)) +FATAL = ("STALE", "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 audit_commit(repo, rev, queue, index, timeout): + """Grade one commit IN ITS OWN TREE. Never raises.""" + 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": ""}) + + if not exists(repo, rev, queue): + row.update(verdict="UNGRADABLE", + detail="%s does not exist at this rev" % queue) + return row + # 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) + return row + if not exists(repo, rev, index): + row.update(verdict="UNGRADABLE", + detail="no %s at this rev (nothing to compare against)" % index) + return row + + td = tempfile.mkdtemp(prefix="qaa-") + try: + ok, why = materialise(repo, rev, [queue, index, GATE_TOOL], td) + if not ok: + row.update(verdict="ERROR", detail="could not materialise: %s" % why) + return row + 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)] + try: + p = subprocess.run(cmd, cwd=td, capture_output=True, timeout=timeout) + except subprocess.TimeoutExpired: + row.update(verdict="ERROR", detail="--check timed out after %ss" % timeout) + return row + s = p.stdout.decode("utf-8", "replace") + e = p.stderr.decode("utf-8", "replace") + first = (s.strip() or e.strip() or "").splitlines() + row["detail"] = first[0][:180] if first else "" + row["rc"] = p.returncode + if p.returncode == 0: + row["verdict"] = "PASS" + elif p.returncode == 1 and "STALE:" in s: + row["verdict"] = "STALE" + elif p.returncode == 2: + row["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. + row["verdict"] = "ERROR" + return row + finally: + for root, dirs, files in os.walk(td, topdown=False): + for f in files: + try: + os.unlink(os.path.join(root, f)) + except OSError: + pass + for d in dirs: + try: + os.rmdir(os.path.join(root, d)) + except OSError: + pass + try: + os.rmdir(td) + except OSError: + pass + + +# ------------------------------------------------------------------ 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): + """(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, + "rows": [], "counts": {}, "graded": 0, "rc": None} + if not os.path.isdir(repo): + payload["error"] = "not a directory: %s" % repo + payload["rc"] = 2 + return 2, payload + if not is_repo(repo): + payload["error"] = "not a git repository: %s" % repo + payload["rc"] = 2 + return 2, payload + to_sha = resolve(repo, rev_to) + if to_sha is None: + payload["error"] = "unknown revision: %s" % rev_to + payload["rc"] = 2 + return 2, payload + payload["to"] = to_sha + + from_sha = None + if rev_from: + from_sha = resolve(repo, rev_from) + if from_sha is None: + payload["error"] = "unknown revision: %s" % rev_from + payload["rc"] = 2 + return 2, payload + 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: + payload["error"] = "git rev-list failed: %s" % ((err.strip().splitlines() or [""])[0]) + payload["rc"] = 2 + return 2, payload + if guard: + revs = revs[-1:] + if not revs: + where = ("%s..%s" % (rev_from, rev_to) if rev_from else rev_to) + payload["error"] = ("no commit in %s touches %s, so there is no index " + "currency to grade" % (where, queue)) + payload["rc"] = 2 + return 2, payload + + 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) for r in revs] + counts = {} + for r in rows: + counts[r["verdict"]] = counts.get(r["verdict"], 0) + 1 + payload["rows"] = rows + payload["counts"] = counts + payload["graded"] = counts.get("PASS", 0) + counts.get("STALE", 0) + counts.get("ERROR", 0) + payload["rc"] = 1 if any(r["verdict"] in FATAL for r in rows) 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 "-") + w(" %-10s %-8s %-25s %-9s index:%-3s %s\n" + % (r["verdict"], r["short"], r["date"][:25], q, + "yes" if r["index_changed"] else "no", r["detail"])) + c = payload["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")) + ungradable = c.get("UNGRADABLE", 0) + w("graded: %d of %d%s\n" + % (payload["graded"], len(payload["rows"]), + (" (%d could not be graded -- reported, NOT counted as stale)" % ungradable) + if ungradable else "")) + stale = [r["short"] for r in payload["rows"] if r["verdict"] == "STALE"] + if stale: + w("stale: %s\n" % " ".join(stale)) + bad = [r["short"] for r in payload["rows"] if r["verdict"] == "ERROR"] + if bad: + w("error: %s\n" % " ".join(bad)) + if payload["rc"] == 0: + w("verdict: every graded commit left the index current in its own tree\n") + else: + w("verdict: %d commit(s) did NOT leave the index current in their own tree -- " + "rc 1\n" % (len(stale) + len(bad))) + + +# ------------------------------------------------------------------- selftest + +def _sh(cwd, *args): + return subprocess.run(args, cwd=cwd, capture_output=True, text=True) + + +def _fixture(base): + """A five-commit history with one commit of each kind. Returns (repo, revs). + + A: queue only, no gate tool -> UNGRADABLE + B: appends, and adds tools/queue_index.py + its render -> PASS + C: appends to the queue, no re-render -> STALE + D: appends AND re-renders -> 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") + + os.makedirs(os.path.join(repo, "tools")) + os.makedirs(os.path.join(repo, "docs")) + 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 + 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") + + 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, 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 4 queue-touching commits, not the 5th", len(p["rows"]), 4) + 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"], 3) + check("the STALE detail carries the tool's own word", "STALE:" in rows[c]["detail"], True) + + 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) + rc_s, _ = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, c, guard=True) + check("--guard on the stale commit exits 1", rc_s, 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, 3)) + 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 4 rows", len(loaded["rows"]), 4) + check("the report names the stale commit by short sha", c[:7] in buf.getvalue(), True) + check("a report with a fault never prints a clean verdict", + "every graded commit" in buf.getvalue(), False) + finally: + for root, dirs, files in os.walk(base, topdown=False): + for f in files: + try: + os.unlink(os.path.join(root, f)) + except OSError: + pass + for dd in dirs: + try: + os.rmdir(os.path.join(root, dd)) + except OSError: + pass + try: + os.rmdir(base) + except OSError: + pass + + 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("--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) + 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()) diff --git a/tools/queue_index.py b/tools/queue_index.py index 4ceb160..a55c231 100644 --- a/tools/queue_index.py +++ b/tools/queue_index.py @@ -371,6 +371,27 @@ def clip(text, limit): return cut.rstrip(" ,;:—-") + " …" +def nested_prose(nested): + """The 'what it exposes' clause about record-opened ids. + + `build_doc` used to index `nested[0]` unconditionally, so `--write` died + with IndexError -- leaving a zero-byte document and rc 1, the code that + means "graded a fault" -- on any queue whose ids all carry headings (a fresh + queue, a fixture, a partial extract). Found 2026-10-18 by the fixture in + `tools/queue_append_audit.py --selftest`. The non-empty branch reproduces + the previous sentence byte for byte, so the real document's render did not + move and `--check` stayed green across the fix. + """ + if not nested: + return (" No id is opened as a record without a heading of its own, so the " + "table below is empty.") + return (" A further **%d ids have no heading " + "at all** and are opened as records nested inside another item's section -- so " + "grepping `^## %s` finds nothing while %s is a live item the file names %d times. " + "Those are listed in their own table below." + % (len(nested), nested[0]["id"], nested[0]["id"], nested[0]["refs"])) + + def build_doc(payload): lines = [] lines.append("# The work queue's table of contents") @@ -386,14 +407,9 @@ def build_doc(payload): "`tools/queue_claim_check.py`'s, and this document does not overrule it.") lines.append("") lines.append("**What it exposes.** The queue is two-tier. %d sections carry %d ids because " - "%d ids own a second, continuation section. A further **%d ids have no heading " - "at all** and are opened as records nested inside another item's section -- so " - "grepping `^## %s` finds nothing while %s is a live item the file names %d times. " - "Those are listed in their own table below." + "%d ids own a second, continuation section.%s" % (len(payload["sections"]), len(payload["ids_with_section"]), - len(payload["ids_multi_section"]), len(payload["nested"]), - payload["nested"][0]["id"], payload["nested"][0]["id"], - payload["nested"][0]["refs"])) + len(payload["ids_multi_section"]), nested_prose(payload["nested"]))) lines.append("") lines.append("| # | id | lines | bytes | role | title | first `Status:` |") lines.append("|---|---|---|---|---|---|---|") diff --git a/tools/selftest_sweep.py b/tools/selftest_sweep.py index 0efb318..47ee0c2 100644 --- a/tools/selftest_sweep.py +++ b/tools/selftest_sweep.py @@ -156,7 +156,8 @@ READ_PROBES = ( ("landing_check.py", ["--repo", "{nope}"]), ("queue_claim_check.py", ["--queue", "{nope}/q.md"]), ("queue_index.py", ["--queue", "{nope}/q.md", "--check", "{nope}/i.md"]), - ("selftest_sweep.py", ["--repo", "{nope}"]), + ("queue_append_audit.py", ["--repo", "{nope}"]), + ("selftest_sweep.py", ["--repo", "{nope}"]) ) NOT_UTF8_PROBES = ( ("append_only_owners.py", ["{bin}"]), @@ -207,6 +208,8 @@ MALFORMED_PROBES = ( 0, "0 opening records"), ("queue_index.py", "junk-index", ["--queue", "{junkqueue}", "--check", "{junkindex}"], 2, "UNVERIFIABLE"), + ("queue_append_audit.py", "not-a-repo", ["--repo", "{notrepo}"], + 2, "not a git repository"), ("selftest_sweep.py", "not-a-repo", ["--repo", "{notrepo}"], 2, "no tools discovered"), )