Files
BFM-decomp/tools/r22_verify.sh
T
Drew T 30caa67127 fix(r22 guard): record liveness, do not infer it — my mtime heuristic failed BOTH ways
I shipped a guard that used drafting-scratch mtimes as a liveness proxy. It failed
in both possible directions within minutes:

* FALSE PASS: the find included '.run/*wave*', which expanded past ARG_MAX
  ('Argument list too long'). find then matched nothing, the guard PASSED, and I
  ran clean: removed build/, expected/, and the regenerated splat tree (asm/, assets/, include macros, undefined_*_auto.txt). on a live lane — deleting asm/ under five drafting agents. I
  restored it immediately (extract-all 212/212) but that is damage control, not a
  design.
* FALSE PASS, structurally: even with the glob fixed, an agent that THINKS longer
  than the window is indistinguishable from a finished one — the exact flaw I had
  already written into gater_lane's docstring for the verdicts file ('a quiet file
  mtime is deliberately NOT accepted as one') and then rebuilt here anyway.

tools/lane_inflight.py is the fix: liveness is RECORDED, not inferred. The
orchestrator adds a target when it launches the workflow and removes it when the
verdict returns — both actions it already performs, so the ledger cannot drift
without skipping a step that is taken anyway.  exits non-zero when any agent
is live, which IS the guard, and both r22_verify.sh and parallel_gate --r22 now use
it instead of touching the filesystem.

Negative-controlled both directions: refuses with 5 live agents named and their
start times; passes when the ledger is drained.

The lesson worth more than the fix: I had already identified 'a quiet mtime is not
a completion signal' as a defect class, documented it, and then re-implemented it
in a different file. Writing a rule down does not stop you applying its opposite
somewhere else.
2026-08-31 18:59:50 -06:00

45 lines
2.1 KiB
Bash

#!/bin/bash
# R22 clean-fleet verify. EXCLUSIVE BY CONSTRUCTION, and it CLEARS THE DEFERRED-CHECK DEBT.
#
# `make clean` deletes asm/ AND build/. Four separate times in P31 S68 that raced a live lane:
# * a subagent authorised to splice src/800.c -> a FALSE "212 passed, 1 failed" red;
# * three drafting agents reporting "asm/<binary> is MISSING from the tree" mid-draft (one
# survived only by finding an old snapshot under .run/s46 and still returned MATCH -- luck).
# Drafting agents never WRITE src/, which is exactly why a dirty-tree check does not catch them:
# they DEPEND on state this operation destroys. R54 -- a guard that is not running is not a guard,
# so this REFUSES rather than relying on the operator remembering.
#
# `.run/R22_DEBT` is written by `parallel_gate --r22` whenever it SKIPS its own clean-fleet check
# for the same reason. A deferred check that nobody tracks reads as "verified" at session close
# (R32's corrected form), so it is a file, the session checkpoint quotes it, and only a GREEN run
# here deletes it.
set -u
cd /home/musashi/bfm-decomp
if ! python3 tools/lane_inflight.py list > /tmp/.r22_inflight 2>&1 && [ -z "${R22_FORCE:-}" ]; then
echo "R22 REFUSED — drafting agents are LIVE (they read asm/, which make clean deletes):"
sed 's/^/ /' /tmp/.r22_inflight
echo "Drain the lane, or set R22_FORCE=1 if you know every agent is done."
echo "R22 DONE (refused)"
exit 2
fi
echo "R22 START $(date -Is)"
make clean ; echo "CLEAN rc=$?"
make extract-all ; echo "EXTRACT rc=$?"
CHECK_OUT=$(make check-all 2>&1); CHECK_RC=$?
echo "$CHECK_OUT" | tail -40
echo "CHECK rc=$CHECK_RC"
# Clear the debt ONLY on a genuinely green fleet — read the summary line, not the exit code alone,
# because a failed build leaves the PREVIOUS binary on disk and sha1sum then reads green (R53).
if [ "$CHECK_RC" -eq 0 ] && grep -q 'check-all: .*0 failed' <<< "$CHECK_OUT"; then
if [ -f .run/R22_DEBT ]; then
echo "cleared .run/R22_DEBT ($(wc -l < .run/R22_DEBT) deferred check(s)) — fleet verified green"
rm -f .run/R22_DEBT
fi
else
echo "R22 NOT GREEN — .run/R22_DEBT left standing"
fi
echo "R22 DONE $(date -Is)"