tools (volt): checkin_check.py -- a heading inside a fenced block is quoted, not structure

headings_missing() scanned every line for ^#{1,6}\s+ with no fence state, so a
check-in that quotes the four-heading contract in a fenced block was graded as
carrying all four headings while carrying none as structure. queue_index.py has
skipped fences since it was written; the check-in reader never did.

Pre-fix on the quoted fixture: headings_missing == [] (false green).
Post-fix: ['next','ask']. Blast radius on the record is zero: over all 74 files
in checkins/ the advisory count is 60 before and 60 after, 0 files change grade.

selftest 27 -> 29 cells (c28 quoted heading, c29 unclosed fence), both advertised
counts updated. verify.py 3/3 rc 0 (sweep 14 tools, 14 OK, 0 fatal).
This commit is contained in:
timmy committed 2026-10-19 00:22:51 +00:00
1 parent 331927fc99
commit fd84b68b1f
2 files changed
+112 -4

No files matched your search

+34 -4
View File
@@ -45,7 +45,9 @@ RULES IMPLEMENTED (stated, not implied)
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.
--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
@@ -66,7 +68,7 @@ USAGE:
python3 checkin_check.py --repo /root/troupe-standup
python3 checkin_check.py --repo . --gaps --json /tmp/checkins.json
python3 checkin_check.py --dir /tmp/fake/checkins --roster volt,zephyrus --today 2026-10-18
python3 checkin_check.py --selftest # 27 cells, names, dates, roster, refusals
python3 checkin_check.py --selftest # 29 cells, names, dates, roster, refusals
"""
from __future__ import annotations
@@ -81,6 +83,8 @@ 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")
@@ -197,8 +201,23 @@ def read_text(path: str) -> str:
def headings_missing(text: str):
seen = set()
"""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())
@@ -557,7 +576,18 @@ def selftest():
cell("c26 a malformed --since refuses at rc 2, never 1",
main(["--dir", ck, "--roster", "volt", "--since", "16-10-2026"]) == 2)
print(f"selftest: 27 cells, {27 - len(bad)} passed, {len(bad)} failed")
# 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"])
print(f"selftest: 29 cells, {29 - len(bad)} passed, {len(bad)} failed")
return 1 if bad else 0