Files
kyleandClaude Opus 5 e3f37da232 Hand over the M3a plan: 22 tasks, their files, and the check record
Task files, the files they copy in (byte-identical to the reference on
m3a-ref), each area's check record, and a README with the per-task
table of what each check exposed. The handoff note is done with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 23:45:43 -07:00

5.2 KiB

M3a task 21: the runbook gate check

Branch: m3a (run git switch m3a; git status --short must be empty, otherwise stop) Commit subject: Check that every runbook pointer has an entry

Goal

Every message for a fail-closed state ends with see docs/runbook.md#<anchor>. A new gate script fails if the code names an entry that docs/runbook.md does not have.

Files

  • Copy: scripts/test-gate-scripts.sh, and Makefile from docs/plans/M3a/files/Makefile-task21 (files/Makefile is task 22's, with a line that needs a test this task does not have yet)
  • Create: scripts/check-runbook.sh
  • Modify: docs/implementer-log.md

You never edit docs/runbook.md. If the script finds a missing entry in the real tree, the pointer in the code is wrong, or the entry is missing: stop and report which.

What the script does

sh scripts/check-runbook.sh [ROOT], ROOT defaulting to ., in POSIX sh like the other scripts in scripts/. Read scripts/check-lines.sh first for the style.

  1. A pointer is the text docs/runbook.md# followed by zero or more characters of [A-Za-z0-9_-], anywhere in a *.rs file under ROOT/crates. Files under any target/ directory are ignored (find ... -type d -name target -prune -o -type f -name '*.rs' -print). Test files count. A line can hold more than one pointer, and each counts.
  2. The pointer's anchor is what follows the #. Its entry is a line of ROOT/docs/runbook.md that is exactly ## <anchor>: the whole line, nothing before or after it (grep -q -x -F -e "## $anchor"). ### grants-invalid, ## grants-invalid and more, a mention in prose and ## Grants-Invalid are not the entry for grants-invalid.
  3. Exit status 0 if every anchor has its entry. Otherwise 1.

Every way it can fail, each with a message on stderr that starts with check-runbook::

Condition Why
ROOT/crates is not a directory nothing to check: fail closed
ROOT/docs/runbook.md is not a file nothing to check against
find, awk, sort or mktemp fails, or a file cannot be read a check that cannot do its job fails
grep exits with a status other than 0 or 1 1 means "no such line"; 2 means grep itself failed
no pointer is found anywhere the harness has fail-closed states, so the search is broken
an anchor is empty, as in docs/runbook.md#{anchor} or docs/runbook.md#<anchor> the script cannot read an anchor that is not written out
an anchor has no entry the point of the script

Rules from earlier reviews, which the self-test checks:

  • Report every problem, not only the first. Go through all the anchors, print a message for each one that is missing, and exit 1 at the end. Do not exit inside the loop over anchors. For each missing anchor, name the files that point to it.
  • Never hide an error. No 2>/dev/null, no || true. Test each command's exit status.
  • A while read loop at the end of a pipeline runs in a subshell, and an exit or a variable set inside it is lost. Write the list to a file made with mktemp -d and read it with while IFS= read -r line; do ...; done < "$file". Remove the directory with a trap ... EXIT.
  • grep -o is not POSIX. Take the pointers out of a line with awk:
awk '{
    s = $0
    while (match(s, /docs\/runbook\.md#[A-Za-z0-9_-]*/)) {
        printf "%s\t%s\n", substr(s, RSTART + 16, RLENGTH - 16), FILENAME
        s = substr(s, RSTART + RLENGTH)
    }
}' "$f"

docs/runbook.md# is 16 characters, so this prints the anchor, a tab, and the file.

The Makefile

The copied Makefile runs sh scripts/check-runbook.sh in gate, after check-dep-docs.sh.

Steps

  • 1. Copy. git switch m3a, then cp docs/plans/M3a/files/scripts/test-gate-scripts.sh scripts/ && cp docs/plans/M3a/files/Makefile-task21 Makefile
  • 2. See the self-test fail. sh scripts/test-gate-scripts.sh. Expected: failures that name check-runbook.sh cases, and exit status 1.
  • 3. Write scripts/check-runbook.sh.
  • 4. See the self-test pass. sh scripts/test-gate-scripts.sh. Expected: test-gate-scripts: ok.
  • 5. Prove the self-test has teeth. Change -x to nothing in your grep line and run the self-test: it must fail with "the entry is the whole line, at level two". Change it back. Then put exit 1 where a missing anchor is reported and run it: it must fail with "both missing entries and their files are reported". Change it back. Write both results in the log's Notes.
  • 6. Run it on the real tree. sh scripts/check-runbook.sh; echo $?. Expected: no output and 0. If it names a missing entry, stop and report: do not edit the runbook, the pointer or a copied test.
  • 7. Run the gate. make gate. Expected last line: gate: ok.
  • 8. Log and commit. git add scripts/check-runbook.sh scripts/test-gate-scripts.sh Makefile docs/implementer-log.md && git commit

Done when

  • sh scripts/test-gate-scripts.sh prints test-gate-scripts: ok; sh scripts/check-runbook.sh exits 0 on the repository; make gate prints gate: ok.

Stop and report if

  • Step 6 reports a missing entry or an empty anchor.
  • sh on this machine lacks something the task relies on (mktemp -d, awk's match).