From 000580a8382fe479ec64ae34b35a2c483461040a Mon Sep 17 00:00:00 2001 From: Cassian Assayborn Date: Mon, 2 Nov 2026 15:53:28 -0500 Subject: [PATCH] =?UTF-8?q?cassian:=20Q75=20instrument=20(first=20incremen?= =?UTF-8?q?t)=20=E2=80=94=20read-only=20round-trip=20grade=20of=20ledger's?= =?UTF-8?q?=20two=20wizard-work=20census=20TSVs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wizard-work/cassian/q75_roundtrip.py grades Q75's four rules (field count, path on disk, duplicates, md Totals vs row count), Path(__file__)-relative, exit 0/1/2. Edits nothing of ledger's. --selftest: 4 cells, 4 passed (clean rc 0; injected CHECK body rc 1; deleted row rc 1; missing md rc 2). Committed artifacts at b82b023 (unchanged at HEAD): rc 1 — FIELDS 14 (7 per TSV, lines 13-19), TOTAL 2 (3420 / 3419 rows vs md 3416), ABSENT 1038 (519 per TSV: 381 __pycache__/.pytest_cache, 138 never tracked at b82b023, incl. 'python3 toposort/test_toposort.py'), DUP 0. --- wizard-work/cassian/q75_roundtrip.py | 128 +++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 wizard-work/cassian/q75_roundtrip.py diff --git a/wizard-work/cassian/q75_roundtrip.py b/wizard-work/cassian/q75_roundtrip.py new file mode 100644 index 0000000..ca3f34e --- /dev/null +++ b/wizard-work/cassian/q75_roundtrip.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +"""q75_roundtrip.py -- read-only round-trip grade of ledger's two wizard-work census TSVs (Q75). + +Q75 (WORK-QUEUE.md, ledger lane, opened 2026-10-26 at b82b023) asks that +wizard-work/ledger/wizardwork-file-manifest.tsv and wizardwork-census.tsv +round-trip: every line has the declared field count, every path exists on +disk, no duplicates, and the md summary total equals the TSV's row census. +This file grades exactly those four rules and edits nothing. It is an +instrument for the item, not its repair: the TSVs are ledger's by authorship. + +Faults (each printed as `FAULT : `): + FIELDS a line whose tab-split field count is not 4 + ABSENT a 4-field row whose path (relative to wizard-work/) is not on disk + DUP a path listed on more than one 4-field row + TOTAL the md's `Totals: ... N files` differs from a TSV's 4-field row count + +Paths resolve against the wizard-work/ directory this file sits in +(Path(__file__)-relative), never a host path, so the reading is of the +checkout in hand. Exit contract (tools/): 0 clean, 1 graded a fault, +2 could not grade (an input is missing or unreadable). + + python3 wizard-work/cassian/q75_roundtrip.py # grade the committed files + python3 wizard-work/cassian/q75_roundtrip.py --selftest # 3 fixture cells +""" +import argparse, collections, os, re, sys, tempfile +from pathlib import Path + +WW = Path(__file__).resolve().parent.parent +LEDGER = WW / "ledger" +TSVS = ("wizardwork-file-manifest.tsv", "wizardwork-census.tsv") +MD = "wizardwork-census.md" +TOTAL_RE = re.compile(r"^Totals:.*?(\d+) files\b") + + +def grade(root, tsv_paths, md_path, out=print, limit=5): + faults = collections.Counter() + shown = collections.Counter() + + def fault(cls, where, detail): + faults[cls] += 1 + if shown[cls] < limit: + out(f"FAULT {cls} {where} {detail}") + shown[cls] += 1 + + try: + md_lines = Path(md_path).read_text(encoding="utf-8").splitlines() + except OSError as e: + out(f"cannot read {md_path}: {e}") + return 2 + md_total = None + for ln in md_lines: + m = TOTAL_RE.match(ln) + if m: + md_total = int(m.group(1)) + if md_total is None: + out(f"cannot grade: no `Totals: ... N files` line in {md_path}") + return 2 + for tp in tsv_paths: + try: + lines = Path(tp).read_text(encoding="utf-8").split("\n") + except OSError as e: + out(f"cannot read {tp}: {e}") + return 2 + if lines and lines[-1] == "": + lines.pop() + name = Path(tp).name + rows, seen = 0, collections.Counter() + for i, ln in enumerate(lines, 1): + f = ln.split("\t") + if len(f) != 4: + fault("FIELDS", f"{name}:{i}", f"{len(f)} field(s): {ln[:60]!r}") + continue + rows += 1 + seen[f[0]] += 1 + if seen[f[0]] == 2: + fault("DUP", f"{name}:{i}", repr(f[0])) + if not (Path(root) / f[0]).exists(): + fault("ABSENT", f"{name}:{i}", repr(f[0][:80])) + if rows != md_total: + fault("TOTAL", f"{name}", f"{rows} four-field rows vs md Totals {md_total} files") + out(f"{name}: {len(lines)} lines, {rows} four-field rows, {len(seen)} distinct paths") + summary = ", ".join(f"{k} {v}" for k, v in sorted(faults.items())) or "none" + out(f"faults: {summary}") + return 1 if faults else 0 + + +def selftest(): + cells = [] + with tempfile.TemporaryDirectory() as d: + d = Path(d) + for p in ("a/x.py", "a/y.txt", "b/CHECK"): + (d / p).parent.mkdir(parents=True, exist_ok=True) + (d / p).write_text("x\n") + clean = "a/x.py\t2\tx.py\ta\na/y.txt\t2\ty.txt\ta\nb/CHECK\t2\tCHECK\tb\n" + md = d / "c.md" + md.write_text("Totals: 2 top-level directories, 3 files (1 script).\n") + t = d / "t.tsv" + quiet = lambda s: None + t.write_text(clean) + cells.append(("clean fixture", grade(d, [t], md, quiet), 0)) + # the Q75 shape: a CHECK body spilled into the rows, ending in a bogus path + t.write_text(clean.replace("b/CHECK\t2\tCHECK\tb\n", + "b/CHECK\n#!/bin/bash\nset -e\npython3 b/test_x.py\t2\ttest_x.py\tb\n")) + cells.append(("injected CHECK body (FIELDS+ABSENT+TOTAL)", grade(d, [t], md, quiet), 1)) + t.write_text("\n".join(clean.split("\n")[1:])) + cells.append(("deleted row (TOTAL)", grade(d, [t], md, quiet), 1)) + cells.append(("missing md (could not grade)", grade(d, [t], d / "nope.md", quiet), 2)) + bad = 0 + for name, got, want in cells: + ok = got == want + bad += not ok + print(f"{'PASS' if ok else 'FAIL'} cell: {name}: rc {got} (want {want})") + print(f"selftest: {len(cells)} cells, {len(cells) - bad} passed, {bad} failed") + return 1 if bad else 0 + + +def main(): + ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) + ap.add_argument("--selftest", action="store_true") + ap.add_argument("--limit", type=int, default=5, help="faults printed per class (all are counted)") + a = ap.parse_args() + if a.selftest: + return selftest() + return grade(WW, [LEDGER / t for t in TSVS], LEDGER / MD, limit=a.limit) + + +if __name__ == "__main__": + sys.exit(main())