R-87 put back in the register; closed_register_gate.py is the 12th gate (R-405, R-406)
gates / gates (push) Failing after 17s
gates / gates (push) Failing after 17s
Records and process only. No machine contacted. No product code, no version bump, no build, no deploy. R-87 was moved into CLOSED-ITEMS.md by the 2026-08-22 compression sweepef6ac6fwhile its own state cell read "READY - RE-RANKED UP 2026-08-03 (R-86 closed)". R-378 records that sweep moving six still-open rows and restoring them in the same session; this was a seventh it missed. Nine days in the wrong file, with the register's ranking paragraph ranking it fourth and pointing at nothing. Restored verbatim from ef6ac6f^, beside R-95 where it sat before. The predicate is the LEADING VERDICT of the state cell, which is R-378's lesson and decides the answer here. Measured on the file as pushed: an open word anywhere in the state cell convicts 3 of 151 rows, two of them genuinely closed (R-224 and R-260 carry "open"/"OPEN" inside long prose verdicts); the leading verdict convicts exactly 1; the whole row convicts 144. closed_register_gate.py, two rules: no open state word leading a CLOSED-ITEMS.md row's verdict, and no R- id with a row in both registers. Red-proofed both, and negative- controlled against the pushed pre-fix files where it convicts R-87 by name, rc=1; restoring the planted rows leaves the file byte-identical. Registered as the 12th gate in repo_gates.py, --fast, after it was green. Four residual holes in its docstring. R-398 was also in both registers - a deliberate cross-reference stub. Now prose beneath the table rather than a table row, because a row in both files is what rule 2 convicts on. R-406 filed: two unrelated findings in OPEN-ITEMS.md both numbered R-133. The only such collision in either register. Deliberately NOT gated - a within-register duplicate rule would fail on a pre-existing row, and a registered-but-failing gate refuses every push. golden-currency is RED at this commit and was already red atdddcc80- controller v0.230.0 released, newest golden 0.229.0. Pre-existing, not this session's debt. Ceiling R-404 -> R-406.
This commit is contained in:
@@ -1,3 +1,25 @@
|
||||
## closed_register_gate.py v1.0.0 — CLOSED-ITEMS.md holds closed work only (2026-08-31, R-405)
|
||||
|
||||
**One row corrected, one gate added, no product code touched.** R-87 ("the restic tier is never
|
||||
restore-tested") was moved into `CLOSED-ITEMS.md` by the 2026-08-22 compression sweep (`ef6ac6f`)
|
||||
while its own state cell read `READY — RE-RANKED UP 2026-08-03 (R-86 closed)`. R-378 records that
|
||||
sweep moving six still-open rows and restoring them in the same session; **this was a seventh it
|
||||
missed**, and it sat in the wrong file for nine days while `OPEN-ITEMS.md`'s ranking paragraph ranked
|
||||
it fourth and pointed at nothing. The row is back beside R-95, verbatim from `ef6ac6f^`.
|
||||
|
||||
- **The predicate is the LEADING VERDICT of the state cell** — R-378's whole lesson, and it decides
|
||||
the answer here. Measured on the file as pushed: an open word anywhere in the state cell convicts
|
||||
**3 of 151** rows, two of them genuinely closed (R-224, R-260 carry "open"/"OPEN" inside long prose
|
||||
verdicts); the leading verdict convicts exactly **1**; the whole row convicts **144**.
|
||||
- **Two rules only** — no open state word leading a `CLOSED-ITEMS.md` row's verdict, and no `R-`
|
||||
identifier with a row in both registers. Its four residual holes are in its docstring.
|
||||
- **Red-proofed both rules**, and negative-controlled against the pushed pre-fix files, where it
|
||||
convicts R-87 by name and rc=1. Restoring the planted rows leaves the file byte-identical.
|
||||
- **`R-398` was also in both registers** — a deliberate cross-reference stub. It is now prose beneath
|
||||
the table rather than a table row, because a row in both files is what rule 2 convicts on.
|
||||
- Registered as the **12th** gate in `repo_gates.py`, `--fast` (two file reads), **after** it was
|
||||
green.
|
||||
|
||||
## check_skills.py v1.0.0 + five process-domain skills (2026-08-25)
|
||||
|
||||
**Five new skills under `skills/`, one new checker under `scripts/`, no product code touched and
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""closed_register_gate.py — CLOSED-ITEMS.md holds closed work ONLY (R-405, 2026-08-31).
|
||||
|
||||
WHAT IT CONVICTS ON (a FAIL, exit 1), two rules and nothing else:
|
||||
|
||||
RULE 1 — no row in `CLOSED-ITEMS.md` may carry an OPEN state word (READY, OPEN, BLOCKED,
|
||||
WATCHING, WAITING-ON-OPERATOR) as the LEADING VERDICT of its state cell.
|
||||
RULE 2 — no `R-` identifier may have a table row in BOTH `CLOSED-ITEMS.md` and `OPEN-ITEMS.md`.
|
||||
|
||||
WHY IT EXISTS, and the cost that bought it. The 2026-08-22 compression sweep (commit `ef6ac6f`,
|
||||
R-376..R-378) moved finished work out of `OPEN-ITEMS.md`. Its classifier matched a status word
|
||||
ANYWHERE in the row, so rows that were not closed went with it. R-378 caught six of them in the same
|
||||
session — R-123, R-190, R-214, R-264, R-295, R-352 — and restored them verbatim. **It missed a
|
||||
seventh.** R-87 ("the restic tier is never restore-tested") went into the closed file with its own
|
||||
state cell reading `READY — RE-RANKED UP 2026-08-03 (R-86 closed)`: a LEADING verdict of `READY`, and
|
||||
the word `closed` later in the same cell describing a DIFFERENT row. It sat in the wrong file for
|
||||
nine days while `OPEN-ITEMS.md`'s own ranking paragraph ranked it fourth and pointed at nothing.
|
||||
|
||||
READ THE LEADING VERDICT, NOT THE CELL, NOT THE ROW. This is R-378's whole lesson and this gate would
|
||||
be wrong without it. Measured on `CLOSED-ITEMS.md` at 2026-08-31, matching an open word anywhere in
|
||||
the state cell convicts THREE rows; two of them — R-224 and R-260 — are genuinely closed and merely
|
||||
contain the words "open" and "OPEN" inside long prose verdicts. Matching the leading verdict convicts
|
||||
exactly ONE, which is the real one. Matching the whole ROW convicts 144 of 151.
|
||||
|
||||
WHAT THIS GATE CANNOT SEE — the residual holes, named rather than implied:
|
||||
|
||||
1. **A row whose body contains a `|` shifts its own cells and is read in the wrong place.** Two
|
||||
rows do this today (R-309, R-351: a pipe inside inline code). The gate cannot tell a shifted
|
||||
cell from a real one, so a mis-filed row that also contains a pipe escapes. It prints such rows
|
||||
as a WARNING when the leading verdict is not a verdict word AND an open word sits somewhere in
|
||||
the cell, which is the best signal available without a real Markdown parser. A leading verdict
|
||||
that is merely a version string is NOT warned on: this table's `Shipped` column legitimately
|
||||
holds `controller v0.227.0`, and ten warnings per run is how a gate gets ignored.
|
||||
2. **A row with no state cell at all escapes.** Two exist today (R-399, R-400 — two columns where
|
||||
the table declares four). An empty state cell is not an open word, so RULE 1 passes it. They are
|
||||
printed as a WARNING.
|
||||
3. **A closed-sounding verdict that is not true escapes.** `PARTLY CLOSED` leads with no open word.
|
||||
This gate checks where a row FILED, never whether the verdict is honest.
|
||||
4. **A duplicate id WITHIN one register escapes.** `OPEN-ITEMS.md` carries two unrelated findings
|
||||
both numbered R-133 (filed as R-406). Adding that rule would fail the gate on a pre-existing
|
||||
defect, and a registered-but-failing gate refuses every push, so it was deliberately left out.
|
||||
5. Nothing here reads audits, spikes or inventories. A finding that never reaches either register
|
||||
is invisible to this gate, as it is to `one_register_gate.py`.
|
||||
|
||||
Run: python3 scripts/closed_register_gate.py [--fast]
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
CLOSED = os.path.join(ROOT, "documentation", "backlog", "CLOSED-ITEMS.md")
|
||||
OPEN = os.path.join(ROOT, "documentation", "backlog", "OPEN-ITEMS.md")
|
||||
|
||||
# The id keeps its suffix letter: R-88a and R-88b are different rows, and R-209/R-209a are a closed
|
||||
# row and its open successor. Dropping the letter invents duplicates that are not there.
|
||||
ROW = re.compile(r"^\|\s*\*{0,2}(R-\d+[a-z]?)\b")
|
||||
|
||||
# A verdict ends at the first separator. `READY — RE-RANKED UP (R-86 closed)` leads with READY;
|
||||
# `CLOSED 2026-08-06 — ... did not open the sealed bundle` leads with CLOSED.
|
||||
SEPARATOR = re.compile(u"[—–(:,.]|\\s-\\s")
|
||||
OPEN_WORD = re.compile(r"\b(READY|OPEN|BLOCKED|WATCHING|WAITING-ON-OPERATOR)\b", re.I)
|
||||
DONE_WORD = re.compile(r"\b(CLOSED|SHIPPED|FIXED|KILLED|DONE|SUPERSEDED|OBSOLETE|WITHDRAWN|MERGED|"
|
||||
r"DISCHARGED|RULED|MOVED|BANKED|PROVEN-LIVE|EXECUTED)\b", re.I)
|
||||
|
||||
|
||||
def leading_verdict(cell):
|
||||
"""The verdict word(s) before the first separator, with Markdown emphasis stripped."""
|
||||
text = cell.replace("*", "").replace("~", "").replace("`", "").strip()
|
||||
return SEPARATOR.split(text)[0].strip()
|
||||
|
||||
|
||||
def rows(path):
|
||||
"""Yield (line_no, id, state_cell_or_None, line) for every R- row in a pipe table.
|
||||
|
||||
CLOSED-ITEMS.md declares `| ID | Title | Shipped | Evidence |` — four columns, so a well-formed
|
||||
row splits into six fields and the state cell is the third. A row of any other width has no
|
||||
state cell this function is willing to guess at, and says so with None.
|
||||
"""
|
||||
for n, line in enumerate(io.open(path, encoding="utf-8"), 1):
|
||||
line = line.rstrip("\n")
|
||||
m = ROW.match(line)
|
||||
if not m:
|
||||
continue
|
||||
cells = line.split("|")
|
||||
state = cells[3] if len(cells) == 6 else None
|
||||
yield n, m.group(1), state, line
|
||||
|
||||
|
||||
def main():
|
||||
for path in (CLOSED, OPEN):
|
||||
if not os.path.exists(path):
|
||||
print("closed-register gate: %s is missing — FAILURE, never a skip" % path)
|
||||
return 1
|
||||
|
||||
convicted, warnings, checked = [], [], 0
|
||||
for n, rid, state, line in rows(CLOSED):
|
||||
if state is None:
|
||||
warnings.append((n, rid, "row is not four columns — no state cell to read"))
|
||||
continue
|
||||
checked += 1
|
||||
verdict = leading_verdict(state)
|
||||
if OPEN_WORD.search(verdict):
|
||||
convicted.append((n, rid, verdict, "open state word in the leading verdict"))
|
||||
elif not DONE_WORD.search(verdict) and OPEN_WORD.search(state):
|
||||
# Ambiguous and worth a human look: the leading verdict is not a verdict word at all
|
||||
# (so the cells may be shifted by a `|` in the body) AND an open word sits somewhere in
|
||||
# the cell. Deliberately NOT warned on a leading verdict that is merely a version
|
||||
# string — this table's `Shipped` column legitimately holds `controller v0.227.0`, and
|
||||
# ten such warnings per run is how a gate teaches people to stop reading it.
|
||||
warnings.append((n, rid, "leading verdict %r is not a verdict word and an open word "
|
||||
"sits in the cell — the row's cells may be shifted by a `|` "
|
||||
"in its body" % verdict[:60]))
|
||||
|
||||
closed_ids = set(rid for _, rid, _, _ in rows(CLOSED))
|
||||
open_ids = {}
|
||||
for n, rid, _, _ in rows(OPEN):
|
||||
open_ids.setdefault(rid, n)
|
||||
for rid in sorted(closed_ids & set(open_ids), key=lambda r: (int(re.sub(r"\D", "", r)), r)):
|
||||
convicted.append((open_ids[rid], rid, "", "has a row in BOTH registers"))
|
||||
|
||||
print("closed-register gate — %d closed rows with a readable state cell, %d open rows, "
|
||||
"%d convicted, %d warnings" % (checked, len(open_ids), len(convicted), len(warnings)))
|
||||
|
||||
for n, rid, why in warnings:
|
||||
print(" WARNING L%-5d %-7s %s" % (n, rid, why))
|
||||
|
||||
if convicted:
|
||||
print()
|
||||
print("CONVICTED — CLOSED-ITEMS.md is for closed work only:")
|
||||
for n, rid, verdict, why in convicted:
|
||||
print(" L%-5d %-7s %s%s" % (n, rid, why, (" — verdict %r" % verdict) if verdict else ""))
|
||||
print()
|
||||
print("Move the row back into OPEN-ITEMS.md keeping its text, state, owner and rank, or —")
|
||||
print("if it really is finished — write the verdict that says so at the START of its state")
|
||||
print("cell. R-87 sat in the wrong file for nine days because nobody could see it there.")
|
||||
return 1
|
||||
|
||||
print("closed-register gate OK — no open work filed as closed, no id in both registers.")
|
||||
return 0
|
||||
|
||||
|
||||
sys.exit(main())
|
||||
@@ -106,6 +106,11 @@ GATES = [
|
||||
# finding sat in ROADMAP.md for 25 days invisible to every "grep the register" rule and was
|
||||
# rediscovered by an overnight drill. Fast: two file reads.
|
||||
("one-register", os.path.join(SCRIPTS, "one_register_gate.py"), [], True),
|
||||
# R-405 — the 2026-08-22 compression sweep moved R-87 into CLOSED-ITEMS.md while its own state
|
||||
# cell read READY; R-378 caught six siblings in the same session and missed this one, so it sat
|
||||
# in the wrong file for nine days while the ranking paragraph pointed at nothing. Fast: two
|
||||
# file reads.
|
||||
("closed-register", os.path.join(SCRIPTS, "closed_register_gate.py"), [], True),
|
||||
# R-389 — a live finding lived in a REPORT.md observations paragraph and nowhere else, and
|
||||
# REPORT.md is overwritten every session. Fast: stdlib file reads.
|
||||
("observations", os.path.join(SCRIPTS, "observations_gate.py"), [ROOT], True),
|
||||
|
||||
Reference in New Issue
Block a user