fix(board): consume coord-count's exit status, not just its stdout

F5 gave coord-count.sh a three-status contract (0 counted, 2 usage error,
3 mailbox root absent) so it could SAY the measurement failed instead of
rendering a reassuring value. board.sh threw that signal away: every call
site piped it into awk, and a pipeline reports the LAST stage's status,
so the producer's exit code was discarded at each one. HAVE_COUNT only
ever tested that the sibling FILE exists - a strictly weaker question.

Measured on 0.30.0 with a mailbox root that does not exist: coord-count
printed "not counted, not zero" and exited 3, while --brief answered
"Ingen repo skylder noen et svar i dag." and --plan emitted no advarsel=
key at all. A gate that can fail whose consumer does not listen is a gate
that does not fall.

COUNT_OK is now the measurement's verdict, and COUNT_WHY carries the exact
reason - including the status number - into all five reporting sites. The
two causes stay distinguishable: "the sibling is missing" and "the world
you named is not there" are different repairs. Any nonzero is caught, not
3 specifically, and a partial stdout captured before a failure is
discarded because a half-count also looks measured. --brief's three
separate coord-count invocations collapse to one: with a status to honour,
three runs would mean three statuses to reconcile.

board.sh stays read-only. The table and --plain views still never invoke
coord-count at all, and COUNT_WHY says so rather than claiming a
measurement nobody attempted.

Bounded gap, stated rather than closed: the table's own INN/ORDRE/FLY
columns count with ls and read 0 for every repo when the mailbox root is
absent. Same defect shape, different source - not a coord-count consumer,
so exit 3 cannot reach it, and the order warned by name against silent
widening.

TDD: board-selftest.sh section 27 written first and RED (7 failures)
before board.sh was touched, behind six known-positive controls and a
ground-truth assertion that coord-count really does exit 3 on that input.
Mutation-verified: restoring `if true` in place of the status test turns
exactly those seven red and leaves all six controls green.

Suites under real /bin/bash 3.2: board 314/314 (was 300), coord 242/242,
route 69/69, orders 110/110, guard 54/54.

No version bump: 0.30.0 shipped two days ago, and this adds no flag, no
exit code and no env var - it repairs an existing consumer. Recommend it
rides the next collection rather than minting a release of its own; the
operator decides, and the catalog owns the tag.

Order: 20260826T115026Z-9982513971-from-.claude

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-08-29 07:10:21 +02:00
commit 4369499e4b
3 changed files with 236 additions and 22 deletions

View file

@ -554,7 +554,8 @@ UNBLOCKS="${TMPDIR:-/tmp}/board-unblocks.$$"
OWED="${TMPDIR:-/tmp}/board-owed.$$"
ALLMAIL="${TMPDIR:-/tmp}/board-allmail.$$"
DEADLETTERS="${TMPDIR:-/tmp}/board-deadletters.$$"
trap '/bin/rm -f "$RECORDS" "$UNBLOCKS" "$OWED" "$ALLMAIL" "$DEADLETTERS" 2>/dev/null' EXIT
COUNTRAW="${TMPDIR:-/tmp}/board-countraw.$$"
trap '/bin/rm -f "$RECORDS" "$UNBLOCKS" "$OWED" "$ALLMAIL" "$DEADLETTERS" "$COUNTRAW" 2>/dev/null' EXIT
# --- Owed lookup (name -> messages that actually owe a reply) --------------
# `inbox` (field 6 of RECORDS) is raw pending-file count and stays that way -
@ -581,11 +582,62 @@ trap '/bin/rm -f "$RECORDS" "$UNBLOCKS" "$OWED" "$ALLMAIL" "$DEADLETTERS" 2>/dev
HAVE_COUNT=1
[ -f "$SELFDIR/coord-count.sh" ] || HAVE_COUNT=0
# COUNT_OK is the measurement's own verdict, and it is a STRICTLY stronger
# question than HAVE_COUNT's "does the sibling exist" (order
# 20260826T115026Z-9982513971, .claude, operator decision 2026-08-26).
#
# F5 gave coord-count.sh a three-status contract - 0 counted, 2 usage error, 3
# mailbox root absent - precisely so it could SAY the measurement failed instead
# of rendering a reassuring value. Every call site here used to pipe it straight
# into awk (`bash coord-count.sh 2>/dev/null | awk ...`), and a pipeline reports
# the LAST stage's status, so the producer's exit code was discarded at each
# one. Measured on 0.30.0 against a mailbox root that does not exist:
# coord-count.sh printed "not counted, not zero" and exited 3, while --brief
# answered "Ingen repo skylder noen et svar i dag." and --plan emitted no
# advarsel= key at all. A gate that can fail whose consumer does not listen is a
# gate that does not fall.
#
# The two causes stay DISTINGUISHABLE in $COUNT_WHY rather than collapsing into
# one "unavailable" flag, for the same reason coord-count's own 2 and 3 are
# different statuses: "the sibling is missing" is repaired by restoring a file,
# "the world you named is not there" by fixing CLAUDE_COORD_DIR, and a reader
# handed one message for both cannot tell which repair to make.
#
# Run ONCE into $COUNTRAW, for the three views that consume it. This also
# replaces the three separate invocations --brief used to make (OWED,
# DEADLETTERS, brief_orphans): with a status to honour, three runs would mean
# three statuses to reconcile, and coord-count.sh is read-only, so one reading
# is the same reading.
COUNT_OK=0
COUNT_WHY="coord-count.sh mangler"
: > "$COUNTRAW"
if [ "$HAVE_COUNT" -eq 1 ]; then
if [ "$BRIEF" -eq 1 ] || [ "$PLAN" -eq 1 ] || [ "$INBOX_PLAN" -eq 1 ]; then
bash "$SELFDIR/coord-count.sh" > "$COUNTRAW" 2>/dev/null
count_rc=$?
if [ "$count_rc" -eq 0 ]; then
COUNT_OK=1; COUNT_WHY=""
else
# Discard whatever reached stdout before the failure: a partial count is
# the same defect class one layer down - it looks measured.
: > "$COUNTRAW"
case "$count_rc" in
3) COUNT_WHY="coord-count.sh avsluttet 3 - postkasse-roten finnes ikke" ;;
2) COUNT_WHY="coord-count.sh avsluttet 2 - kall-feil mot coord-count.sh" ;;
*) COUNT_WHY="coord-count.sh avsluttet $count_rc - maalingen feilet" ;;
esac
fi
else
# The table and --plain views never read the mailbox beyond what RECORDS
# already counted, so no measurement is attempted and none is claimed.
COUNT_WHY="coord-count.sh ble ikke kjort for denne visningen"
fi
fi
: > "$OWED"
if { [ "$BRIEF" -eq 1 ] || [ "$PLAN" -eq 1 ]; } && [ "$HAVE_COUNT" -eq 1 ]; then
if { [ "$BRIEF" -eq 1 ] || [ "$PLAN" -eq 1 ]; } && [ "$COUNT_OK" -eq 1 ]; then
ow_tab="$(printf '\t')"
bash "$SELFDIR/coord-count.sh" 2>/dev/null \
| awk -F"$ow_tab" -v OFS='|' '{print $1, $3}' > "$OWED"
awk -F"$ow_tab" -v OFS='|' '{print $1, $3}' "$COUNTRAW" > "$OWED"
fi
# --- Dead-letter lookup (name -> age in days), --brief only ----------------
@ -596,10 +648,9 @@ fi
# 3-day threshold reach $DEADLETTERS, so brief_deadletters() below never has to
# re-parse the raw column or re-apply the threshold itself.
: > "$DEADLETTERS"
if [ "$BRIEF" -eq 1 ] && [ "$HAVE_COUNT" -eq 1 ]; then
if [ "$BRIEF" -eq 1 ] && [ "$COUNT_OK" -eq 1 ]; then
dl_tab="$(printf '\t')"
bash "$SELFDIR/coord-count.sh" 2>/dev/null \
| awk -F"$dl_tab" -v OFS='|' '$4 != "-" && $4+0 >= 3 {print $1, $4}' > "$DEADLETTERS"
awk -F"$dl_tab" -v OFS='|' '$4 != "-" && $4+0 >= 3 {print $1, $4}' "$COUNTRAW" > "$DEADLETTERS"
fi
# --- All-mail lookup (name -> pending, owed), --inbox-plan only ------------
@ -610,10 +661,9 @@ fi
# gated: an extra coord-count.sh subprocess on every table/plain invocation
# would tax a path that never reads it.
: > "$ALLMAIL"
if [ "$INBOX_PLAN" -eq 1 ] && [ "$HAVE_COUNT" -eq 1 ]; then
if [ "$INBOX_PLAN" -eq 1 ] && [ "$COUNT_OK" -eq 1 ]; then
am_tab="$(printf '\t')"
bash "$SELFDIR/coord-count.sh" 2>/dev/null \
| awk -F"$am_tab" -v OFS='|' '{print $1, $2, $3}' > "$ALLMAIL"
awk -F"$am_tab" -v OFS='|' '{print $1, $2, $3}' "$COUNTRAW" > "$ALLMAIL"
fi
# --- Chain-root credit -----------------------------------------------------
@ -746,14 +796,13 @@ brief_cmd() {
# coord-count.sh is the right source and the only safe one: it counts without
# delivering, where coord-inbox.sh would mark broadcasts seen just by looking.
brief_orphans() {
if [ "$HAVE_COUNT" -ne 1 ]; then
if [ "$COUNT_OK" -ne 1 ]; then
echo ""
echo "UTENFOR REPO-SKANNEN: kan ikke sjekke - coord-count.sh mangler."
echo "UTENFOR REPO-SKANNEN: kan ikke sjekke - $COUNT_WHY."
return 0
fi
bo_tab="$(printf '\t')"
bo_out="$(bash "$SELFDIR/coord-count.sh" 2>/dev/null \
| awk -F"$bo_tab" '$2+0>0 {print $1"'"$bo_tab"'"$2}' \
bo_out="$(awk -F"$bo_tab" '$2+0>0 {print $1"'"$bo_tab"'"$2}' "$COUNTRAW" \
| while IFS="$bo_tab" read -r bo_name bo_n; do
[ -n "$bo_name" ] || continue
awk -F'|' -v n="$bo_name" '$3==n {f=1} END{exit !f}' "$RECORDS" \
@ -775,9 +824,9 @@ brief_orphans() {
# claimed-repo in board-selftest.sh section 16 both do). The action half
# (report to sender / retract) is unapproved design and is not built here.
brief_deadletters() {
if [ "$HAVE_COUNT" -ne 1 ]; then
if [ "$COUNT_OK" -ne 1 ]; then
echo ""
echo "ALDRI LEST: kan ikke sjekke - coord-count.sh mangler."
echo "ALDRI LEST: kan ikke sjekke - $COUNT_WHY."
return 0
fi
[ -s "$DEADLETTERS" ] || return 0
@ -814,8 +863,8 @@ brief() {
# debt": $OWED is empty for the same reason it would be on a genuinely
# debt-free day, and continuing into the branch below actively mislabeled a
# reply-owing repo as FYI-only (inbox>0, owed==0 looks identical either way).
if [ "$HAVE_COUNT" -ne 1 ]; then
echo "ADVARSEL: coord-count.sh mangler - kan ikke avgjore hvem som skylder svar."
if [ "$COUNT_OK" -ne 1 ]; then
echo "ADVARSEL: $COUNT_WHY - kan ikke avgjore hvem som skylder svar."
echo "Gjeldstall er IKKE null, bare ikke beregnet. Se ${SELFDIR}/coord-count.sh."
brief_orphans
brief_deadletters
@ -1381,7 +1430,7 @@ plan() {
# intentionally left unchanged (a policy change to a format the operator
# decided has a second consumer elsewhere), but the driver must be able to
# see that the omission happened rather than read a shorter list as complete.
[ "$HAVE_COUNT" -eq 1 ] || printf 'advarsel=coord-count.sh mangler - gjeldsvekting (gruppe 2, inbox:N) er ikke beregnet\n'
[ "$COUNT_OK" -eq 1 ] || printf 'advarsel=%s - gjeldsvekting (gruppe 2, inbox:N) er ikke beregnet\n' "$COUNT_WHY"
# --- Free capacity: NAMED, never turned into a tab ------------------------
# The question the plan could not answer until now, in the operator's own
@ -1546,7 +1595,7 @@ inbox_plan() {
# measured effect was 1 block silently becoming 0, not an empty population
# rendered as such. key=value, not '#', for the same reason as plan()'s
# advarsel= line: the driver consumer drops comment lines by rule.
[ "$HAVE_COUNT" -eq 1 ] || printf 'advarsel=coord-count.sh mangler - populasjonen er ikke beregnet, ikke tom\n'
[ "$COUNT_OK" -eq 1 ] || printf 'advarsel=%s - populasjonen er ikke beregnet, ikke tom\n' "$COUNT_WHY"
ipn=0
while IFS='|' read -r owed pend name cls dir status cost neste; do