462ab4a5ff
gates / gates (push) Successful in 29s
THE REPAIR. Last night's append regex ate the state cells of R-446 and R-458, left them as a stray
fourth cell on duplicated copies of R-626 and R-625, and split the table with blank lines. The
register read 317 rows for 315 findings. Both cells restored from the cells that carried them, the
two duplicates deleted, and 15 blank lines that split the register into 12 separate markdown tables
removed. Every row's text is byte-identical afterwards, proven by diff; no row added or removed.
THE RED-PROOF FOUND AN OLDER INSTANCE: R-254 lost its state cell on 2026-08-08 (59527d0) and had
rendered without a State column for 45 days. Its own verdict sentence was the cell; it has it back.
THE GATE. scripts/register_shape_gate.py, gate 14 in repo_gates.py and reached by the pre-push
hook: a row that does not end with `|` (an eaten state cell), a duplicated id, or a blank line
splitting the table. Four decoys in test_gate_decoys.py — three convicting on the exact damage
shapes, one asserting a healthy register still passes. The red-proof corrected the gate twice: a
first draft counted CELLS and convicted 125 innocent rows (register cells carry literal `|` in
prose and shell snippets, so a row cannot be split on `|`), and it skipped malformed rows before
counting ids, reporting 5 duplicates where there were 2. It then immediately caught a blank line
left by this session's own next insert.
R-618's rank now reads P1-HIGH in both its title and its state cell.
HETZNER ANSWERED, AND I FIRST SAID THEY HAD NOT. The operator supplied ticket #2026090103040671.
Q1: with the MAIN account, files and directories can be downloaded from a snapshot (Storage Box
docs govern, not the Storage Share FAQ); a restore reverts the whole box. Q2: --append-only is
enforced by pinning `command="rclone serve restic --stdio --append-only path/to/repo"` to the key
in authorized_keys — so append-only IS expressible on a Storage Box, which is what R-95 was blocked
on. Neither is measured; R-436's due check now asks for the measurement, not the question.
My error is filed as R-628 and is precise: the query was fine — a control returns 201 threads and
reaches SENT and TRASH — and the thread genuinely is not in this mailbox. What was invented was the
step from "absent here" to "Hetzner has not answered". An absent record in one place cannot answer
what someone else did. The rule is now in the Gmail-access memory.
R-629: the drill repo inherited has_actions from the migrate call and mailed the operator 47 CI
failures overnight, into the mailbox that was carrying real off-site alarms. Actions disabled and
verified; 09 §6.5 now makes it a step of creating a drill repo. The 47 mails are the operator's to
clear: subject:"gates FAILED in admin/app-catalog-drill".
Gates: repo_gates.py --fast — all 16 OK, including the new one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
133 lines
6.2 KiB
Python
133 lines
6.2 KiB
Python
#!/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 <file> # 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())
|