#!/usr/bin/env python3 # -*- coding: utf-8 -*- """register_shape_gate.py — OPEN-ITEMS.md is a TABLE, and a table has a shape (R-627, 2026-09-22). WHAT IT CONVICTS ON (a FAIL, exit 1), four rules and nothing else: RULE 1 — every register row (`| **R-nnn** | …`) ends with `|`, so it HAS a state cell. RULE 2 — reserved. **There is deliberately no cell-COUNT rule, and that is a measurement, not an omission.** The first draft counted cells and convicted 125 innocent rows: register cells carry literal `|` characters inside their prose and their shell snippets (`owner: CC | …`, `--format '{{.Names}}|{{.Image}}'`), so splitting a row on `|` does not yield its cells. A count that cannot be computed is not a check. RULE 1 catches the thing that actually breaks — a row whose state cell was eaten — because such a row stops ending with a pipe. RULE 3 — no `R-` identifier appears on more than one row. RULE 4 — no blank line sits between two register rows. A blank line ENDS a markdown table, so the rows after it render as a separate table — and, worse, a script that walks "the table" stops there. WHY IT EXISTS, and the cost that bought it. On 2026-09-21 the update night appended measured results to eight existing rows with a regex that matched a row's trailing state cell. On two rows (**R-446** and **R-458**) it consumed the state cell and did not put it back; the cell reappeared as a stray FOURTH cell on a duplicated copy of a different row (**R-626** and **R-625**), and a blank line was left between each pair. The register then reported **317 rows for 315 findings**, two rows carried no state at all, and two findings existed twice with contradictory state cells. **Nothing caught it.** `one_register_gate.py` compares OPEN-ITEMS against ROADMAP; `closed_register_gate.py` forbids an id in BOTH OPEN and CLOSED — neither asks whether the file is a well-formed table, and neither notices an id duplicated WITHIN OPEN-ITEMS. It was found the next morning by a person reading the file. **A register that silently mis-states how many findings exist is the one file in this project that must not be able to do that**, because every standing rule says "grep the register before minting" and every count in every report is read off it. WHAT THIS GATE CANNOT SEE — the residual holes, named rather than implied: 1. **A row whose state cell is WRONG but present.** Shape is not meaning. A row that says READY when it is closed passes here and is `closed_register_gate.py`'s business. 2. **A row whose text was truncated mid-sentence** but still ends with a valid state cell. Only a human or a diff against the previous commit sees that. 3. **The DUE-CHECKS block and the other tables in this file.** This gate reads register rows only — lines that begin `| **R-`. The file's other tables are `due_checks_gate.py`'s business. USAGE python3 scripts/register_shape_gate.py # the real register python3 scripts/register_shape_gate.py # any file, for the red-proof Exit: 0 clean · 1 convicted. """ import re import sys from pathlib import Path ROW = re.compile(r'^\| \*\*(R-\d+)\*\* \|') REG = Path(__file__).resolve().parent.parent / "documentation" / "backlog" / "OPEN-ITEMS.md" def cells(line): """The cells of a markdown table row, as a reader sees them. A row is `| a | b | c |`. Stripping the outer pipes and splitting on `|` gives the cells; a row that forgot its trailing pipe yields one fewer, which is exactly RULE 2's symptom, so RULE 1 and RULE 2 are reported separately rather than collapsed into one confusing message. """ t = line.rstrip() if not t.endswith("|"): return None return t.strip("|").split("|") SEP = re.compile(r'^\|\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|\s*$') def check(path): lines = Path(path).read_text(encoding="utf-8").split("\n") bad = [] seen = {} rows = 0 for i, line in enumerate(lines, start=1): m = ROW.match(line) if not m: continue rows += 1 rid = m.group(1) # RULE 3 first, and ALWAYS — a row that also breaks another rule is still a row, and # skipping it here would under-count the ids and report phantom duplicates. (Measured: the # first draft skipped them and claimed 5 duplicates where there were 2.) if rid in seen: bad.append((i, rid, f"RULE 3 — duplicate: {rid} already has a row at line {seen[rid]}")) else: seen[rid] = i # RULE 1 — the row must end with `|`, i.e. it must still HAVE a state cell if not line.rstrip().endswith("|"): bad.append((i, rid, "RULE 1 — the row does not end with `|`, so it has no state cell. " "This is exactly what an eaten state cell looks like, and what " "R-446, R-458 (2026-09-21) and R-254 (2026-08-08, unnoticed for 45 " "days) each looked like.")) # RULE 4 — look back over blanks to the previous non-blank line j = i - 2 blanks = 0 while j >= 0 and lines[j].strip() == "": blanks += 1 j -= 1 if blanks and j >= 0 and ROW.match(lines[j]): bad.append((i, rid, f"RULE 4 — {blanks} blank line(s) between this row and " f"{ROW.match(lines[j]).group(1)} at line {j+1}. A blank line ends " f"the markdown table.")) return rows, len(seen), bad def main(): path = sys.argv[1] if len(sys.argv) > 1 else REG rows, ids, bad = check(path) print(f"register-shape: {path}") print(f"register-shape: {rows} register row(s), {ids} distinct R- id(s)") if rows != ids: print(f"register-shape: {rows - ids} row(s) are DUPLICATES") if not bad: print("register-shape: OK — every row ends with a state cell, every id is unique, " "and no blank line splits the table") return 0 print(f"register-shape: CONVICTED — {len(bad)} problem(s)") for line_no, rid, why in bad: print(f" line {line_no} {rid}: {why}") return 1 if __name__ == "__main__": sys.exit(main())