#!/usr/bin/env python3 """checkin_check.py -- the gate on the check-in contract, which nothing measured. WHY THIS EXISTS Two files state the check-in contract and neither is instrumented. `checkins/README.md` (in the tree since `07441fa`'s ancestry, 2026-09-24): "One file per person per day: `YYYY-MM-DD-.md`." `WIZARDS-TABLE.md` -- "The churn contract", item 1: "One check-in in `checkins/YYYY-MM-DD-.md` (Did / Blocked / Next / Ask)." Read against `checkins/` at `07441fa` (69 check-in files, measured from this host on 2026-10-18 with `python3 tools/checkin_check.py --repo . --gaps`), both sentences are false of the archive they describe. Twenty-five of the 69 filenames are not `-.md` (`-01`..`-07` suffixes, `zephyrus-wizards`, `cassian-q5-census`, `cassian-q5-recount`), and those suffixes are not cosmetic: ten (date, handle) days carry 2 to 8 files, the worst being 2026-10-06 for zephyrus at 8. Of the 69, 57 do not carry all four of Did / Blocked / Next / Ask; 12 do. A contract with no instrument is a preference, and this archive has been running on two of them. RULES IMPLEMENTED (stated, not implied) F1 NAME a check-in filename is `-.md`, where is a bare roster name: `[a-z][a-z0-9]*`. Any suffix -- `-02`, `-wizards`, `-q5-census` -- is BAD_NAME. This is the README's sentence, taken literally, because a rule read "generously" (suffixes allowed, same person's day split across files) is not the rule that was written. F2 DUPLICATE two or more files resolving to one (date, handle) pair. The handle is resolved by longest-roster-prefix on a `-` boundary, so `zephyrus-02` resolves to zephyrus and `cassian-q5-census` to cassian -- which is why F2 catches what F1 already flags: the suffix is the mechanism, the duplicate is the damage. F3 UNKNOWN a name that no roster handle prefixes. The roster is read from `WIZARDS-TABLE.md`'s rows and `.mailmap`'s canonical names, so a new wizard needs no edit here; `--roster` overrides it. F4 DATE a date that is not a real calendar date (2026-02-30) or lies in the future against `--today` (UTC). FUTURE is separate from BAD_DATE because the two mean different things: a typo, versus a clock this host cannot reproduce. F5 EMPTY a file with no non-blank line. A filename is a claim that a check-in is there. A1 HEADINGS missing any of Did / Blocked / Next / Ask. ADVISORY, never a fault and never rc 1: the four-heading sentence entered this tree after the archive does, so grading history by it would report 57 of 70 files faulty at `c374a0a` -- noise that buries the 25 name faults. It is printed, counted, and carried in --json, and the summary says which rule it is. A heading inside a fenced block is quoted material, not structure, and does not count (the same fence rule `queue_index.py` applies). A2 GAP a day inside a handle's own span with no file, and days since the newest file against --today. ADVISORY, per the table's own words: "A lane that stops churning is a lane that needs a decision, not silence." A missing day is not a repository fault; it is a fact an operator acts on. EXIT CODES (the repo's contract, `tools/selftest_sweep.py`) 0 ran; nothing to report, or faults reported without --fail 1 ran, and graded at least one F-fault, under --fail 2 could not grade: no checkins dir, no roster, a baseline ledger this host cannot read or parse, or a file this host cannot read as UTF-8. Never 1, never a traceback. BASELINE: GRADING THE WHOLE ARCHIVE WITHOUT KEEPING IT RED A `--since` floor is how this tool stayed adoptable: the faults the archive already carried are the record and are not re-litigated every run. But a floor grades *one side of the archive*, and a floor chosen from the archive's own newest date grades **nothing that is added before that date**. Measured on a clone of `e98a34b` on 2026-10-19: a new `checkins/2026-10-02-volt-b.md` (BAD_NAME) added today is invisible to `--since 2026-10-19` -- `verify.py --fast` printed `checkin_check: 1 files, 0 with a fault` and exited **0** -- while the same tree's whole-archive run exited **1** with that file in the table. A backdated filename is not a hypothetical: it is what a typo in the date half makes. `--baseline PATH` replaces the floor with a ledger. The ledger is one row per *known-faulting file*: `\t`, with `DUPLICATE` as a class like any other. A file's classes are then split into **known** (in the ledger) and **new**; only a new class makes rc 1, so the archive can be graded end to end every run and a fault that lands *anywhere*, on any date, is red the moment it lands. The split is per **class**, not per path: an existing file that gains a class it did not have (a check-in truncated to empty) is new, because the ledger states what was tolerated, not that the path is exempt. Entries that no longer match -- file gone, or class cleared -- are reported as stale and never make rc 1: that is someone repairing the record, which is the point. `--emit-baseline` prints that ledger to stdout and exits 0, so refreshing it is a redirect and the tool keeps no write path of its own. An unreadable or malformed ledger is rc 2, never a silent pass: a baseline that cannot be read cannot certify anything. READ-ONLY: the only write paths are `--json PATH` and stdout. It renames nothing -- the badly named files are the principals' history, and a tool that renamed them would be editing the record to make its own report green. USAGE: python3 checkin_check.py --repo /root/troupe-standup python3 checkin_check.py --repo . --gaps --json /tmp/checkins.json python3 checkin_check.py --repo . --fail --baseline tools/checkin_baseline.tsv python3 checkin_check.py --repo . --emit-baseline > tools/checkin_baseline.tsv python3 checkin_check.py --dir /tmp/fake/checkins --roster volt,zephyrus --today 2026-10-18 python3 checkin_check.py --selftest # 35 cells, names, dates, roster, baseline, refusals """ from __future__ import annotations import argparse import json import os import re import sys from datetime import date, datetime, timedelta, timezone NAME_RE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})-([a-z][a-z0-9]*)\.md$") AMBIENT_RE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})-(.+)\.md$") ROSTER_HANDLE_RE = re.compile(r"^[a-z][a-z0-9]*$") HEADING_RE = re.compile(r"^#{1,6}\s+(.*\S)\s*$") # A fenced block is quoted material, never structure (see headings_missing). FENCE_RE = re.compile(r"^\s*(```|~~~)") # The four headings are matched on the heading's own first word, case-folded. WANT_HEADINGS = ("did", "blocked", "next", "ask") FAULTS = ("BAD_NAME", "UNKNOWN_HANDLE", "BAD_DATE", "FUTURE", "EMPTY") # Every class a baseline ledger may name. DUPLICATE is computed after grading # (it is a property of a *group*), so it lives here rather than in FAULTS -- but # a ledger that cannot name it could not record the worst rows in this archive. BASELINE_CLASSES = frozenset(FAULTS) | {"DUPLICATE"} # Not a check-in, and never silently dropped: the directory's own README is # printed as SKIP so a reader can see it was seen and why it was not graded. SKIP_NAMES = {"README.md"} class CannotGrade(Exception): """Anything that stops the tool from grading: rc 2, one message.""" # --------------------------------------------------------------------- roster def roster_from_table(text: str): """Handles from WIZARDS-TABLE.md's `| Wizard | ... |` rows: first token, lowered. Skips the header row and the `|---|` separator. A row whose first cell is not a bare name contributes nothing -- this reads the roster, it does not guess. """ out = set() in_table = False for line in text.splitlines(): s = line.strip() if not s.startswith("|"): in_table = False continue cells = [c.strip() for c in s.strip("|").split("|")] if not cells or not cells[0]: continue if cells[0].lower() == "wizard": in_table = True continue if not in_table or set(cells[0]) <= set("-: "): continue first = cells[0].split()[0].lower() if cells[0].split() else "" if ROSTER_HANDLE_RE.match(first): out.add(first) return out def roster_from_mailmap(text: str): """Handles from the canonical side of `.mailmap`: `Name other `.""" out = set() for line in text.splitlines(): s = line.strip() if not s or s.startswith("#") or "<" not in s: continue name = s.split("<")[0].strip() # `Zephyrus Sparkborn ` -> the handle is the FIRST name, not the last. first = name.split()[0].lower() if name.split() else "" if ROSTER_HANDLE_RE.match(first): out.add(first) return out def load_roster(repo: str, override: str | None): """(handles, source-string). Raises CannotGrade when there is nothing to read.""" if override: hs = {h.strip().lower() for h in override.split(",") if h.strip()} if not hs: raise CannotGrade("cannot grade: --roster is empty") return hs, "--roster" sources, out = [], set() table = os.path.join(repo, "WIZARDS-TABLE.md") mailmap = os.path.join(repo, ".mailmap") for path, fn, label in ((table, roster_from_table, "WIZARDS-TABLE.md"), (mailmap, roster_from_mailmap, ".mailmap")): if not os.path.exists(path): continue try: with open(path, "r", encoding="utf-8") as fh: got = fn(fh.read()) except (OSError, UnicodeDecodeError) as exc: raise CannotGrade(f"cannot read roster {label}: {exc}") if got: out |= got sources.append(f"{label} ({len(got)})") if not out: raise CannotGrade("cannot grade: no roster " f"(looked for WIZARDS-TABLE.md and .mailmap in {repo})") return out, " + ".join(sources) # --------------------------------------------------------------------- grading def resolve_handle(name: str, roster): """The longest roster handle that is `name` or a `-`-delimited prefix of it.""" best = "" for h in roster: if name == h or name.startswith(h + "-"): if len(h) > len(best): best = h return best or None def parse_date(triple): """'YYYY-MM-DD' -> date, or None when the calendar refuses it.""" try: return date(int(triple[0]), int(triple[1]), int(triple[2])) except ValueError: return None def read_text(path: str) -> str: try: with open(path, "r", encoding="utf-8") as fh: return fh.read() except UnicodeDecodeError as exc: raise CannotGrade(f"cannot read {path}: not UTF-8 ({exc.reason} at byte {exc.start})") except OSError as exc: raise CannotGrade(f"cannot read {path}: {exc}") def headings_missing(text: str): """The four contract words this file does NOT use as a heading. A fenced block is quoted material, not structure: a check-in that shows the contract itself (``` ## Did / ## Blocked / ## Next / ## Ask ```) was graded as carrying all four while carrying none. `queue_index.py` already skips fences for the same reason; this reader now does too. Measured over the 74 files at `331927f`: 0 files change grade (60 of 74 advisory-missing before and after), so the fix is a sound for the quoted-heading form, not a re-reading of the archive. """ seen, in_fence = set(), False for line in text.splitlines(): if FENCE_RE.match(line): in_fence = not in_fence continue if in_fence: continue m = HEADING_RE.match(line) if m: seen.add(m.group(1).split()[0].strip("*_`:").lower()) return [w for w in WANT_HEADINGS if w not in seen] def grade_file(path: str, roster, today: date): """One row. Reads the file only when the name parsed far enough to need it.""" base = os.path.basename(path) rec = {"file": base, "path": path, "handle": None, "date": None, "class": [], "headings_missing": [], "lines": 0} m = AMBIENT_RE.match(base) if not m: rec["class"].append("BAD_NAME") rec["detail"] = "not -.md" return rec rec["date"] = f"{m.group(1)}-{m.group(2)}-{m.group(3)}" name = m.group(4) strict = NAME_RE.match(base) rec["handle"] = resolve_handle(name, roster) if not strict: rec["class"].append("BAD_NAME") if rec["handle"] is None: rec["class"].append("UNKNOWN_HANDLE") d = parse_date((m.group(1), m.group(2), m.group(3))) if d is None: rec["class"].append("BAD_DATE") elif d > today: rec["class"].append("FUTURE") text = read_text(path) rec["lines"] = len(text.splitlines()) if not text.strip(): rec["class"].append("EMPTY") else: rec["headings_missing"] = headings_missing(text) return rec def duplicates(rows): """{(date, handle): [files]} for groups with more than one file.""" groups = {} for r in rows: if r["handle"] and r["date"] and "BAD_DATE" not in r["class"]: groups.setdefault((r["date"], r["handle"]), []).append(r["file"]) return {k: sorted(v) for k, v in groups.items() if len(v) > 1} def gaps(rows, today: date, cap: int = 60): """Per-handle coverage + the missing days inside its own span (not after it).""" by_handle = {} for r in rows: # A future-dated file is a clock fault, not coverage: counting it would # stretch the span to the year 2099 and report 26k "missing" days. if not r["handle"] or r["class"] and ("BAD_DATE" in r["class"] or "FUTURE" in r["class"]): continue by_handle.setdefault(r["handle"], set()).add(r["date"]) out = [] for h in sorted(by_handle): days = sorted(by_handle[h]) first, last = date.fromisoformat(days[0]), date.fromisoformat(days[-1]) missing, run, longest, run_end = [], 0, 0, None cur = first while cur <= last: if cur.isoformat() in by_handle[h]: run = 0 else: missing.append(cur.isoformat()) run += 1 if run > longest: longest, run_end = run, cur cur += timedelta(days=1) since = (today - last).days # `days` and `files` are different numbers: zephyrus has 44 files across # 23 days, and a table that called the second one "files" would be the # exact defect this archive keeps grading other people for. nfiles = sum(1 for r in rows if r["handle"] == h and not ("BAD_DATE" in r["class"] or "FUTURE" in r["class"])) out.append({"handle": h, "files": nfiles, "days": len(by_handle[h]), "first": days[0], "last": days[-1], "days_in_span": (last - first).days + 1, "missing": missing[:cap], "missing_count": len(missing), "longest_gap": longest, "longest_gap_ends": run_end.isoformat() if run_end else None, "days_since_last": since}) return out # --------------------------------------------------------------------- baseline def classes_of(rec): """A row's full class set, duplicates folded in: the ledger's unit.""" return set(rec["class"]) | ({"DUPLICATE"} if rec.get("duplicate") else set()) def emit_baseline(rep): """The ledger as text: one `\\t` row per faulting file. Basenames, not paths: `checkins/` is flat and the ledger has to survive a checkout on another host. Sorted, so regenerating it on an unchanged archive produces an unchanged file and the diff is only what moved. The header is part of the output because the output IS the file: a ledger that has to be explained somewhere else is a ledger nobody can read on its own. """ head = ("# check-in baseline -- the faults this archive already carried when the\n" "# naming rule was written. One row per file: \\t.\n" "# Regenerate: checkin_check.py --repo . --emit-baseline > tools/checkin_baseline.tsv\n" "# A row is a stated tolerance, not an exemption: a file that gains a class,\n" "# and a file that is not listed, both make rc 1 under --fail --baseline.\n") lines = [] for r in sorted(rep["rows"], key=lambda x: x["file"]): cls = sorted(classes_of(r)) if cls: lines.append(f"{r['file']}\t{','.join(cls)}") return head + "\n".join(lines) + ("\n" if lines else "") def load_baseline(path): """Read the ledger -> {filename: set(classes)}. Anything unreadable is rc 2. A ledger is what turns "35 historical faults" from a reason not to grade into a stated tolerance. If it cannot be read, the tool has no idea what was tolerated, so it refuses rather than grading against an empty set (which would read the whole archive as new) or skipping the check (which would read nothing as new). Both of those are the silent-pass class this repo grades. """ try: with open(path, "r", encoding="utf-8") as fh: text = fh.read() except OSError as exc: raise CannotGrade(f"cannot grade: baseline {path}: {exc}") except UnicodeDecodeError as exc: raise CannotGrade(f"cannot grade: baseline {path} is not UTF-8: {exc}") out = {} for n, line in enumerate(text.splitlines(), 1): s = line.rstrip("\r") if not s.strip() or s.lstrip().startswith("#"): continue parts = s.split("\t") if len(parts) != 2: raise CannotGrade(f"cannot grade: baseline {path}:{n} is not " f"\\t: {s!r}") name = parts[0].strip() cls = [c.strip() for c in parts[1].split(",") if c.strip()] bad = [c for c in cls if c not in BASELINE_CLASSES] if not name or not cls or bad: raise CannotGrade(f"cannot grade: baseline {path}:{n} names an " f"empty filename, an empty class list, or an " f"unknown class {bad}: {s!r}") out[name] = set(cls) return out def apply_baseline(rep, ledger, path=""): """Split every row's classes into known and new, and count what is stale.""" current = {r["file"]: classes_of(r) for r in rep["rows"]} known_files = 0 for r in rep["rows"]: cls = current[r["file"]] known = ledger.get(r["file"], set()) new = cls - known r["baseline"] = {"known": sorted(cls & known), "new": sorted(new)} if cls and not new: known_files += 1 stale = sorted(f for f in ledger if f not in current) cleared = sorted(f for f in ledger if f in current and (ledger[f] - current[f])) rep["baseline"] = {"path": path, "entries": len(ledger), "known_files": known_files, "new_faults": sum(1 for r in rep["rows"] if r["baseline"]["new"]), "stale": stale, "cleared": cleared} return rep def graded_faults(rep): """The rows that make rc 1. Without a ledger: every faulting row, exactly as before. With one: only the rows carrying a class the ledger does not name -- the ledger is a stated tolerance, not an exemption, so "known" never silently becomes "fine". """ if rep.get("baseline"): return [r for r in rep["rows"] if r.get("baseline", {}).get("new")] return [r for r in rep["rows"] if r["class"] or r["duplicate"]] # --------------------------------------------------------------------- reporting def banner(text: str) -> str: return f"== {text} ==" def run(path: str, roster, today: date, show_gaps: bool, since: str | None = None): """Everything except printing the file table. Returns the report dict.""" if not os.path.isdir(path): raise CannotGrade(f"cannot read checkins dir {path}: not a directory") try: names = sorted(os.listdir(path)) except OSError as exc: raise CannotGrade(f"cannot read checkins dir {path}: {exc}") files = [os.path.join(path, n) for n in names if n.endswith(".md") and n not in SKIP_NAMES and os.path.isfile(os.path.join(path, n))] skipped = [n for n in names if n in SKIP_NAMES] # --since grades forward only: the archive's 25 historical names are the # record, and a gate that can only ever be red is a gate nobody adopts. excluded_since = 0 if since: keep = [] for p in files: m = AMBIENT_RE.match(os.path.basename(p)) if m and f"{m.group(1)}-{m.group(2)}-{m.group(3)}" >= since: keep.append(p) else: excluded_since += 1 files = keep if not files and not since: raise CannotGrade(f"cannot read checkins dir {path}: no *.md files") rows = [grade_file(p, roster, today) for p in files] dup = duplicates(rows) dup_map = {} for (d, h), fl in dup.items(): for f in fl: dup_map[f] = (d, h, len(fl)) for r in rows: if r["file"] in dup_map: r["duplicate"] = {"date": dup_map[r["file"]][0], "handle": dup_map[r["file"]][1], "group_size": dup_map[r["file"]][2]} else: r["duplicate"] = None fault_rows = [r for r in rows if r["class"] or r["duplicate"]] rep = { "dir": path, "checked": len(files), "today": today.isoformat(), "roster_size": len(roster), "skipped": skipped, "since": since, "excluded_since": excluded_since, "faults": {c: sum(1 for r in rows if c in r["class"]) for c in FAULTS}, "fault_rows": len(fault_rows), "duplicate_groups": [{"date": d, "handle": h, "files": fl} for (d, h), fl in sorted(dup.items())], "advisory": { "missing_headings": sum(1 for r in rows if r["headings_missing"]), "headings_ok": sum(1 for r in rows if not r["headings_missing"] and "EMPTY" not in r["class"]), "note": "A1/A2 are advisory: the four-heading rule postdates the archive", }, "rows": rows, } if show_gaps: rep["gaps"] = gaps(rows, today) return rep def print_report(rep, show_gaps: bool, fail: bool): print(banner(f"{rep['checked']} check-in files in {rep['dir']}")) print(f"roster: {rep['roster_size']} handles; today {rep['today']}") if rep.get("since"): print(f"since {rep['since']}: {rep['checked']} graded, " f"{rep['excluded_since']} older file(s) not graded") for n in rep.get("skipped") or []: print(f" SKIP {n} (the directory's own README: not a check-in)") led = rep.get("baseline") fault_rows = graded_faults(rep) if led: print(f"\nbaseline {led['path']}: {led['entries']} entries, " f"{led['known_files']} file(s) known-faulting, " f"{led['new_faults']} new fault(s), {len(led['stale'])} stale, " f"{len(led['cleared'])} cleared") for f in led["stale"]: print(f" STALE BASELINE {f} (no longer in the directory: the record was repaired)") for f in led["cleared"]: print(f" STALE BASELINE {f} (no longer carries every class the ledger names)") if not fault_rows: print("\nno file violates the filename, date, emptiness or duplicate rule" if not led else "\nno file carries a class the baseline does not tolerate") else: width = max(len(r["file"]) for r in fault_rows) print(f"\n{'file':<{width}} {'verdict':<22} detail") for r in fault_rows: cls = (r["baseline"]["new"] if led else sorted(set(r["class"]) | ({"DUPLICATE"} if r["duplicate"] else set()))) detail = [] if "BAD_NAME" in cls: detail.append("name is not -.md") if "UNKNOWN_HANDLE" in cls: detail.append("no roster handle prefixes it") if "BAD_DATE" in cls: detail.append("not a calendar date") if "FUTURE" in cls: detail.append(f"after today {rep['today']}") if "EMPTY" in cls: detail.append("no non-blank line") if "DUPLICATE" in cls and r["duplicate"]: d = r["duplicate"] detail.append(f"{d['group_size']} files for {d['handle']} on {d['date']}") print(f"{r['file']:<{width}} {','.join(cls):<22} {'; '.join(detail)}") adv = rep["advisory"] print(f"\nadvisory (never rc 1): {adv['missing_headings']} of {rep['checked']} " f"files lack a Did/Blocked/Next/Ask heading") print(" -- A1: the four-heading rule postdates the archive; grade it where it applies") if show_gaps: print(f"\n{'handle':<12} {'files':>5} {'days':>5} {'first':>10} {'last':>10} " f"{'span':>4} {'miss':>4} {'gap':>4} {'idle':>4}") for g in rep["gaps"]: print(f"{g['handle']:<12} {g['files']:>5} {g['days']:>5} {g['first']:>10} " f"{g['last']:>10} {g['days_in_span']:>4} {g['missing_count']:>4} " f"{g['longest_gap']:>4} {g['days_since_last']:>4}") print(" -- A2: a missing day is not a repo fault; it is a decision an operator owes") counts = ", ".join(f"{k} {v}" for k, v in rep["faults"].items() if v) if led: print(f"\ncheckin_check: {rep['checked']} files, {led['new_faults']} new fault(s), " f"{led['known_files']} known-faulting in the baseline" + (f" ({counts}; DUPLICATE groups {len(rep['duplicate_groups'])})" if counts else "") + f", {adv['missing_headings']} advisory") if fail and led["new_faults"]: print(f"FAIL: {led['new_faults']} file(s) carry a class the baseline does not tolerate") return 1 return 0 print(f"\ncheckin_check: {rep['checked']} files, {rep['fault_rows']} with a fault" + (f" ({counts}; DUPLICATE groups {len(rep['duplicate_groups'])})" if counts else "") + f", {adv['missing_headings']} advisory") if fail and rep["fault_rows"]: print(f"FAIL: {rep['fault_rows']} file(s) violate the check-in contract") return 1 return 0 # --------------------------------------------------------------------- selftest def _mk(d: str, name: str, body: str, encoding="utf-8"): p = os.path.join(d, name) with open(p, "w", encoding=encoding, errors="strict") as fh: fh.write(body) return p GOOD = "# 2026-10-17 -- volt\n\n## Did\nlanded x\n\n## Blocked\nnothing\n\n## Next\ny\n\n## Ask\nno\n" BARE = "# 2026-10-17 -- volt\n\nlanded x\n" TABLE = """# The Wizards Table | Wizard | Model | Lane | |---|---|---| | Timmy | Hermes | partner | | Zephyrus Sparkborn | Mistral | archive | | Volt | DeepSeek | workhorse | | not a roster row """ def selftest(): bad = [] def cell(name, ok, detail=""): print(f"{'PASS' if ok else 'FAIL'} {name}" + (f" [{detail}]" if detail and not ok else "")) if not ok: bad.append(name) cell("c1 roster from the table's rows only", roster_from_table(TABLE) == {"timmy", "zephyrus", "volt"}, str(roster_from_table(TABLE))) cell("c2 roster from .mailmap's canonical names", roster_from_mailmap("Volt volt \n" "Zephyrus Sparkborn \n") == {"volt", "zephyrus"}, str(roster_from_mailmap("Volt volt \n"))) cell("c3 comment lines and blank lines in .mailmap are skipped", roster_from_mailmap("# c\n\n") == set()) m4 = NAME_RE.match("2026-10-17-volt.md") cell("c4 the strict name parses", m4 is not None and m4.group(4) == "volt") cell("c5 a -02 suffix is not the strict name", NAME_RE.match("2026-09-24-zephyrus-02.md") is None and AMBIENT_RE.match("2026-09-24-zephyrus-02.md").group(4) == "zephyrus-02") cell("c6 longest-prefix handle resolution", (resolve_handle("zephyrus-02", {"zephyrus", "volt"}) == "zephyrus" and resolve_handle("cassian-q5-census", {"cassian"}) == "cassian" and resolve_handle("gandalf", {"volt"}) is None)) cell("c7 a hyphen boundary is required, not a bare prefix", resolve_handle("voltmeter", {"volt"}) is None) cell("c8 2026-02-30 is not a calendar date", parse_date(("2026", "02", "30")) is None and parse_date(("2026", "02", "28")) is not None) cell("c9 headings are matched by first word, case-folded", headings_missing(GOOD) == [] and headings_missing(BARE) == list(WANT_HEADINGS), f"{headings_missing(GOOD)} / {headings_missing(BARE)}") today = date(2026, 10, 18) with __import__("tempfile").TemporaryDirectory() as td: repo = os.path.join(td, "repo") ck = os.path.join(repo, "checkins") os.makedirs(ck) _mk(repo, "WIZARDS-TABLE.md", TABLE) _mk(ck, "2026-10-17-volt.md", GOOD) _mk(ck, "2026-10-17-zephyrus.md", BARE) _mk(ck, "2026-10-17-zephyrus-02.md", BARE) _mk(ck, "2026-10-16-gandalf.md", GOOD) _mk(ck, "2026-02-30-volt.md", GOOD) _mk(ck, "2099-01-01-volt.md", GOOD) _mk(ck, "2026-10-15-volt.md", "") _mk(ck, "README.md", "# Check-ins\n\nOne file per person per day.\n") roster, src = load_roster(repo, None) cell("c10 roster is the union of table and mailmap, and says so", roster == {"timmy", "zephyrus", "volt"} and "WIZARDS-TABLE.md" in src, f"{roster} / {src}") rep = run(ck, roster, today, True) by = {r["file"]: r for r in rep["rows"]} cell("c11 a conforming complete file has no class", by["2026-10-17-volt.md"]["class"] == [] and by["2026-10-17-volt.md"]["headings_missing"] == []) cell("c12 the -02 file is BAD_NAME and duplicates zephyrus", by["2026-10-17-zephyrus-02.md"]["class"] == ["BAD_NAME"] and by["2026-10-17-zephyrus-02.md"]["duplicate"] is not None, str(by["2026-10-17-zephyrus-02.md"])) cell("c13 an unknown handle is UNKNOWN_HANDLE", by["2026-10-16-gandalf.md"]["class"] == ["UNKNOWN_HANDLE"], str(by["2026-10-16-gandalf.md"]["class"])) cell("c14 a false calendar date is BAD_DATE, not FUTURE", by["2026-02-30-volt.md"]["class"] == ["BAD_DATE"]) cell("c15 a future date is FUTURE", by["2099-01-01-volt.md"]["class"] == ["FUTURE"]) cell("c16 an empty file is EMPTY", by["2026-10-15-volt.md"]["class"] == ["EMPTY"]) cell("c17 the duplicate group carries both files", rep["duplicate_groups"] == [{"date": "2026-10-17", "handle": "zephyrus", "files": ["2026-10-17-zephyrus-02.md", "2026-10-17-zephyrus.md"]}], str(rep["duplicate_groups"])) cell("c18 the advisory counts, and does not enter the fault count", rep["advisory"]["missing_headings"] == 2 and rep["fault_rows"] == 6, f"{rep['advisory']} faults={rep['fault_rows']}") g = {x["handle"]: x for x in rep["gaps"]} cell("c19 the gap axis counts missing days inside a handle's span", g["volt"]["missing_count"] == 1 and g["volt"]["longest_gap"] == 1, str(g.get("volt"))) cell("c27 'files' and 'days' are two columns, not one", g["zephyrus"]["files"] == 2 and g["zephyrus"]["days"] == 1, str(g.get("zephyrus"))) rc_fail = print_report(rep, True, True) cell("c20 --fail exits 1 when a fault exists", rc_fail == 1, str(rc_fail)) # refusals: rc 2, never a traceback, never 1 miss = os.path.join(td, "no-such-dir") rc_missing = main(["--dir", miss, "--roster", "volt"]) cell("c21 a missing checkins dir refuses at rc 2", rc_missing == 2, str(rc_missing)) _mk(ck, "2026-10-14-volt.md", "not utf8") with open(os.path.join(ck, "2026-10-14-volt.md"), "wb") as fh: fh.write(b"\xff\xfe\x00bad\x00\n") rc_bin = main(["--dir", ck, "--roster", "volt,timmy,zephyrus"]) cell("c22 a non-UTF-8 check-in refuses at rc 2", rc_bin == 2, str(rc_bin)) rc_noroster = main(["--dir", ck, "--repo", os.path.join(td, "empty")]) cell("c23 no roster at all refuses at rc 2", rc_noroster == 2, str(rc_noroster)) cell("c24 the directory's README is skipped, not graded as a check-in", rep["skipped"] == ["README.md"] and "README.md" not in [r["file"] for r in rep["rows"]] and rep["checked"] == 7, f"{rep['skipped']} {rep['checked']}") rep_since = run(ck, roster, today, False, since="2026-10-16") cell("c25 --since grades forward only, and says how many it left out", rep_since["checked"] == 5 and rep_since["excluded_since"] == 3 and "2026-10-15-volt.md" not in [r["file"] for r in rep_since["rows"]], f"{rep_since['checked']}/{rep_since['excluded_since']}") cell("c26 a malformed --since refuses at rc 2, never 1", main(["--dir", ck, "--roster", "volt", "--since", "16-10-2026"]) == 2) # The quoted-heading form: a fenced block is material, not structure. Both # cells drive the real reader on inline text, not a fixture line number. quoted = ("# 2026-10-17 -- volt\n\n## Did\nx\n\n## Blocked\nx\n\n" "```\n## Next\ny\n## Ask\nno\n```\n") cell("c28 a heading inside a fenced block is not a heading", headings_missing(quoted) == ["next", "ask"], str(headings_missing(quoted))) cell("c29 an unclosed fence quotes to end of file, and says so", headings_missing("## Did\nx\n```\n## Blocked\n## Next\n## Ask\n") == ["blocked", "next", "ask"]) # c30-c35: the baseline ledger. The floor this tool was run under graded one # side of the archive; these cells pin that a fault landing on ANY date is # still graded, that the tolerance is per class rather than per path, and # that an unreadable ledger refuses instead of certifying. with __import__("tempfile").TemporaryDirectory() as td: import contextlib import io as _io ck = os.path.join(td, "checkins") os.makedirs(ck) _mk(ck, "2026-10-17-volt.md", GOOD) # conforming _mk(ck, "2026-10-14-volt-02.md", GOOD) # BAD_NAME _mk(ck, "2026-10-13-volt-03.md", "") # BAD_NAME, EMPTY _mk(ck, "2026-10-12-volt-04.md", "") # BAD_NAME, EMPTY -- "backdated" btoday = date(2026, 10, 18) broster = {"volt"} def with_ledger(ledger_): r_ = run(ck, broster, btoday, False) apply_baseline(r_, ledger_, "fixture.tsv") with contextlib.redirect_stdout(_io.StringIO()): rc_ = print_report(r_, False, True) return r_, sorted(r["file"] for r in graded_faults(r_)), rc_ everything = {"2026-10-14-volt-02.md": {"BAD_NAME"}, "2026-10-13-volt-03.md": {"BAD_NAME", "EMPTY"}, "2026-10-12-volt-04.md": {"BAD_NAME", "EMPTY"}} _, g_none, rc_none = with_ledger(everything) cell("c30 a ledger naming every class grades the whole archive rc 0", g_none == [] and rc_none == 0, f"graded={g_none} rc={rc_none}") partial = dict(everything) del partial["2026-10-12-volt-04.md"] _, g_new, rc_new = with_ledger(partial) cell("c31 a fault on an older date that the ledger does not name is rc 1", g_new == ["2026-10-12-volt-04.md"] and rc_new == 1, f"graded={g_new} rc={rc_new}") per_class = dict(everything, **{"2026-10-12-volt-04.md": {"BAD_NAME"}}) r_class, g_class, rc_class = with_ledger(per_class) new_here = [r["baseline"]["new"] for r in r_class["rows"] if r["file"] == "2026-10-12-volt-04.md"] cell("c32 the ledger is per class, not per path: a gained class is new", new_here == [["EMPTY"]] and rc_class == 1, f"{new_here} rc={rc_class}") rep_emit = run(ck, broster, btoday, False) bl_path = os.path.join(td, "ledger.tsv") with open(bl_path, "w", encoding="utf-8") as fh: fh.write(emit_baseline(rep_emit)) round_trip = load_baseline(bl_path) _, g_rt, rc_rt = with_ledger(round_trip) cell("c33 emit -> load round-trips, and silences exactly the current archive", g_rt == [] and rc_rt == 0 and round_trip == {r["file"]: classes_of(r) for r in rep_emit["rows"] if classes_of(r)}, f"{round_trip} graded={g_rt}") gone = dict(everything, **{"2026-10-01-volt.md": {"BAD_NAME"}}) r_stale, g_stale, rc_stale = with_ledger(gone) cell("c34 an entry for a file that is gone is stale, never a fault", r_stale["baseline"]["stale"] == ["2026-10-01-volt.md"] and g_stale == [] and rc_stale == 0, str(r_stale["baseline"])) bad_ledgers, why = [], [] for label, body in (("no tab", "2026-10-13-volt-03.md\n"), ("unknown class", "2026-10-13-volt-03.md\tNOT_A_CLASS\n"), ("empty classes", "2026-10-13-volt-03.md\t\n")): p = os.path.join(td, "bad-%s.tsv" % label.replace(" ", "-")) _mk(td, os.path.basename(p), body) try: load_baseline(p) bad_ledgers.append(label) except CannotGrade: why.append(label) try: load_baseline(os.path.join(td, "absent.tsv")) bad_ledgers.append("missing file") except CannotGrade: why.append("missing file") cell("c35 an unreadable, malformed or absent ledger refuses (rc-2 class)", bad_ledgers == [], f"accepted: {bad_ledgers}") print(f"selftest: 35 cells, {35 - len(bad)} passed, {len(bad)} failed") return 1 if bad else 0 # --------------------------------------------------------------------- main def main(argv=None): ap = argparse.ArgumentParser( description="checkin_check.py -- the gate on the check-in filename and date " "contract, shipped with its tests.") ap.add_argument("--repo", default=".", help="repository root (roster is read here)") ap.add_argument("--dir", help="checkins dir (default /checkins)") ap.add_argument("--roster", help="comma-separated handles, instead of the roster files") ap.add_argument("--today", help="YYYY-MM-DD for the future-date rule (default: UTC today)") ap.add_argument("--gaps", action="store_true", help="print per-handle coverage and gaps") ap.add_argument("--since", help="grade only check-ins dated on/after this YYYY-MM-DD " "(the archive's older names are its record)") ap.add_argument("--json", help="write the full report here") ap.add_argument("--baseline", help="known-fault ledger (\\t): " "only classes it does not name make rc 1, so the " "whole archive can be graded every run") ap.add_argument("--emit-baseline", action="store_true", help="print the ledger for the current directory to stdout and exit 0") ap.add_argument("--fail", action="store_true", help="exit 1 when any file has a fault") ap.add_argument("--selftest", action="store_true", help="fixture corpus") a = ap.parse_args(argv) if a.selftest: return selftest() if a.emit_baseline and a.baseline: print("cannot grade: --emit-baseline and --baseline are different jobs", file=sys.stderr) return 2 if a.today: try: today = date.fromisoformat(a.today) except ValueError: print(f"cannot grade: --today is not YYYY-MM-DD: {a.today}", file=sys.stderr) return 2 else: today = datetime.now(timezone.utc).date() repo = os.path.abspath(a.repo) path = os.path.abspath(a.dir) if a.dir else os.path.join(repo, "checkins") since = a.since if since: try: date.fromisoformat(since) except ValueError: print(f"cannot grade: --since is not YYYY-MM-DD: {since}", file=sys.stderr) return 2 try: roster, src = load_roster(repo, a.roster) rep = run(path, roster, today, a.gaps, since=since) if a.baseline: rep = apply_baseline(rep, load_baseline(a.baseline), a.baseline) except CannotGrade as exc: print(str(exc), file=sys.stderr) return 2 rep["roster_source"] = src if a.emit_baseline: sys.stdout.write(emit_baseline(rep)) if a.json: with open(a.json, "w", encoding="utf-8") as fh: json.dump(rep, fh, indent=1, sort_keys=False) return 0 rc = print_report(rep, a.gaps, a.fail) if a.json: with open(a.json, "w", encoding="utf-8") as fh: json.dump(rep, fh, indent=1, sort_keys=False) print(f"wrote {a.json}") return rc if __name__ == "__main__": sys.exit(main())