fix(infra): a failed measurement must never render as a reassuring value

Tier 3, all five the same defect class (Verifiseringsloven ansikt 4): a
broken or uninstrumented query returning a positive-looking null, consumed
as a fact about the world.

F5  coord-count.sh: a mailbox root that does not exist was byte-identical
    to one where nobody has pending mail - zero lines, exit 0, silent
    stderr. Now exit 3 + a named stderr line; an existing-but-empty root
    stays a silent, clean 0. 3 rather than 2 because 2 already means "you
    called me wrong" and this means "the world you named is not there".
F14 coord-count.sh: the header promised exit 0 unconditionally while
    --exclude with no value already exited 2. Contract restated as
    0/2/3 and pinned as a check on the help TEXT.
F6  board.sh: `git status | wc -l` yields 0 lines whether the tree is
    clean or git refused to answer, so a failure printed DRT=0. Now "?",
    and BOTH awk consumers handle it - --plan's free-capacity test
    compares the field as a string against "0" (a "?" coerces to 0 in
    arithmetic and would certify an unmeasured tree as free), and the SUM
    roll-up names what it could not add.
F10 board.sh: a scan root that does not exist was skipped in silence and
    the empty scan exited 0. Bad roots are now named on stderr; exit 3
    only when NO root was scanned. A mix still exits 0 and prints the
    board. Replaces an assertion that encoded this defect as a pass.
F13 pre-state-line-guard.mjs: MAX_LINES is overridable via
    CLAUDE_STATE_MAX_LINES so the boundary is testable without hardcoding
    120 twice. An unusable value denies by name rather than falling back
    to the default - a limit that silently did not take effect is the
    same defect one layer up.

Every design choice mutation-tested; every negative check carries a
known-positive control. Section 11's first cut was vacuously green (wrong
basename + unexported fixture path) - recorded in CLAUDE.md rather than
quietly fixed, and the section now asserts its own ground truth.

Denominator measured, not estimated: coord-inbox.sh:57 and
coord-order-inbox.sh:60/64 carry the same `|| exit 0` shape and are
deliberately left alone (injection path, prose output, must never fail a
SessionStart) - stated in CLAUDE.md as a bounded gap.

Suites: coord 230->242, board 281->300, guard 40->54, route 69, orders
110, npm 11/11. Verified under system bash 3.2, not just Homebrew 5.3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-08-26 12:15:05 +02:00
commit d8fdeaa991
7 changed files with 606 additions and 16 deletions

View file

@ -498,8 +498,13 @@ check "NESTE block of only a multi-line comment reports an empty block" $?
"$BOARD" --help >/dev/null 2>&1; check "--help exits 0" $?
# Unreadable root is a no-op, not a crash.
"$BOARD" --roots "$ROOT/does-not-exist" >/dev/null 2>&1; check "missing root is a clean no-op" $?
# A root that does not exist is NOT a clean no-op - it is an unmeasured scan,
# and it used to be indistinguishable from an empty tree. Pinned in full by
# section 26 below (F10); asserted here only as "it does not crash".
mr_rc=0
"$BOARD" --roots "$ROOT/does-not-exist" >/dev/null 2>&1 || mr_rc=$?
[ "$mr_rc" -eq 3 ]
check "missing root exits nonzero (3) without crashing" $?
# --- 8. The documented next-cost form IS the convention --------------------
# The cost field is free text on purpose: the parser reads to ';' or '-->' so
@ -2283,6 +2288,164 @@ check "known-positive: the process-inspection grep can find a planted pgrep call
/bin/rm -rf "$FLY_ROOT" 2>/dev/null
# --- 25. F6: a git FAILURE must not read as a clean tree -------------------
# `git -C "$d" status --porcelain 2>/dev/null | wc -l` yields 0 lines whether
# the tree is clean or git refused to answer at all, so DRT printed 0 and every
# reader - the table, the SUM, and --plan's free-capacity test - took that as
# "nothing uncommitted here". Verifiseringsloven ansikt 4 on our own tooling.
#
# The fixture is a REAL reachable shape, not a contrived one: a `.git` FILE
# pointing at a worktree parent that no longer exists. Discovery tests `.git`
# with -e precisely so worktrees are found (see board.sh's own comment), so
# this directory IS scanned as a repo and `git status` exits 128 on it.
# Measured before the fixture was written: rc=128, zero lines on stdout.
F6ROOT="$(mktemp -d)"
F6COORD="$(mktemp -d)"
# The known-positive controls come first and are the reason this section can
# fail in both directions: if the fix widened "unknown" to cover healthy repos,
# these go red while every negative check below stays green.
mkrepo "$F6ROOT/f6-clean"
{
echo "# STATE - f6-clean"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=done; blocked-on=-; next-cost=Sonnet 5/low -->"
echo "Ingenting."
} > "$F6ROOT/f6-clean/STATE.md"
git -C "$F6ROOT/f6-clean" add -A >/dev/null 2>&1
git -C "$F6ROOT/f6-clean" -c user.email=t@t -c user.name=t commit -qm init >/dev/null 2>&1
mkrepo "$F6ROOT/f6-dirty"
{
echo "# STATE - f6-dirty"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=done; blocked-on=-; next-cost=Sonnet 5/low -->"
echo "Ingenting."
} > "$F6ROOT/f6-dirty/STATE.md"
git -C "$F6ROOT/f6-dirty" add -A >/dev/null 2>&1
git -C "$F6ROOT/f6-dirty" -c user.email=t@t -c user.name=t commit -qm init >/dev/null 2>&1
echo "uncommitted" > "$F6ROOT/f6-dirty/scratch.txt"
# The defect case: scanned as a repo, but git cannot answer.
mkdir -p "$F6ROOT/f6-broken"
echo "gitdir: $F6ROOT/f6-broken-parent-that-does-not-exist/.git/worktrees/x" \
> "$F6ROOT/f6-broken/.git"
{
echo "# STATE - f6-broken"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=done; blocked-on=-; next-cost=Sonnet 5/low -->"
echo "Ingenting."
} > "$F6ROOT/f6-broken/STATE.md"
# Ground truth for the fixture itself, so a later git version that starts
# answering here fails THIS check rather than silently disarming the section.
git -C "$F6ROOT/f6-broken" status --porcelain >/dev/null 2>&1
[ $? -ne 0 ]
check "F6: fixture ground truth - git really does fail on the broken worktree" $?
F6TBL="$(CLAUDE_COORD_DIR="$F6COORD" "$BOARD" --roots "$F6ROOT" 2>/dev/null)"
# DRT is counted from the RIGHT (NF-3: DRT ALDER SISTE NESTE), never from the
# left: the KOST field is free text and its realistic spelling ("Sonnet 5/low")
# contains a space, so $7 reads FLY instead. Measured, not assumed - the first
# cut of this section passed its clean-tree control by accident that way.
printf '%s' "$F6TBL" | grep -E '^f6-clean[[:space:]]' | awk '{print $(NF-3)}' | grep -qx '0'
check "F6: control - a healthy CLEAN tree still reads DRT 0" $?
printf '%s' "$F6TBL" | grep -E '^f6-dirty[[:space:]]' | awk '{print $(NF-3)}' | grep -qx '1'
check "F6: control - a healthy DIRTY tree still reads DRT 1" $?
printf '%s' "$F6TBL" | grep -E '^f6-broken[[:space:]]' | awk '{print $(NF-3)}' | grep -qx '0'
[ $? -ne 0 ]
check "F6: a git failure does NOT read as DRT 0 (clean tree)" $?
printf '%s' "$F6TBL" | grep -E '^f6-broken[[:space:]]' | awk '{print $(NF-3)}' | grep -qx '?'
check "F6: a git failure reads as ? (not measured), the same token coord-count uses" $?
# The load-bearing consequence. All three fixtures are status=done with no mail
# and no orders, so the ONLY thing separating them is the tree reading. A repo
# whose tree could not be measured is not provably free, and certifying it free
# is how an unmeasured null becomes a fact about the world.
F6PLAN="$(CLAUDE_COORD_DIR="$F6COORD" "$BOARD" --roots "$F6ROOT" --plan 2>/dev/null)"
printf '%s' "$F6PLAN" | grep -q '^ledig=f6-clean '
check "F6: control - the clean done repo IS still reported as free capacity" $?
printf '%s' "$F6PLAN" | grep -q '^ledig=f6-dirty '
[ $? -ne 0 ]
check "F6: control - the dirty done repo is still NOT free capacity" $?
printf '%s' "$F6PLAN" | grep -q '^ledig=f6-broken '
[ $? -ne 0 ]
check "F6: an unmeasurable tree is NOT certified as free capacity" $?
# The SUM line adds field 7 across every repo. An unknown adds 0 there, which
# silently understates the total under a heading the operator reads as counted.
# It must be NAMED, on the same principle --plan already applies to repos with
# no board line: labelled, never silently dropped.
printf '%s' "$F6TBL" | grep -q 'MERK: 1 repo har umaalbart arbeidstre'
check "F6: the SUM roll-up names the repo whose tree could not be measured" $?
printf '%s' "$F6TBL" | grep -qE 'SUM: [0-9]+ uhaandterte innboks-meldinger, 1 ukommiterte filer'
check "F6: control - the SUM still counts the one real uncommitted file" $?
/bin/rm -rf "$F6ROOT" "$F6COORD" 2>/dev/null
# --- 26. F10: a scan root that does not exist is not an empty tree ----------
# `[ -d "$root" ] || continue` skipped a bad root silently, and a scan that
# found no repo at all then exited 0 printing nothing - so a typo in --roots,
# a moved home directory and a genuinely empty tree were one single output.
# This section REPLACES the older "missing root is a clean no-op" assertion,
# which encoded exactly this defect as a passing test.
F10ROOT="$(mktemp -d)"
F10COORD="$(mktemp -d)"
mkrepo "$F10ROOT/f10-repo"
{
echo "# STATE - f10-repo"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=planned; blocked-on=-; next-cost=Sonnet 5/low -->"
echo "Et steg."
} > "$F10ROOT/f10-repo/STATE.md"
# Known-positive control FIRST: a valid root still scans, still exits 0, and -
# critically - still says NOTHING on stderr. A warning on the ordinary path
# would train every reader to ignore the channel the defect case needs.
f10a_err="${TMPDIR:-/tmp}/f10a.err.$$"
f10a_out="$(CLAUDE_COORD_DIR="$F10COORD" "$BOARD" --roots "$F10ROOT" 2>"$f10a_err")"; f10a_rc=$?
[ "$f10a_rc" -eq 0 ] && printf '%s' "$f10a_out" | grep -q 'f10-repo' && [ ! -s "$f10a_err" ]
check "F10: control - a valid root scans, exits 0, and warns about nothing" $?
# An EXISTING but empty root is the genuine "no repos here" answer and must
# stay a silent, clean 0 - it is the reading the defect case used to be
# indistinguishable from.
F10EMPTY="$(mktemp -d)"
f10b_err="${TMPDIR:-/tmp}/f10b.err.$$"
f10b_out="$(CLAUDE_COORD_DIR="$F10COORD" "$BOARD" --roots "$F10EMPTY" 2>"$f10b_err")"; f10b_rc=$?
[ "$f10b_rc" -eq 0 ] && [ -z "$f10b_out" ] && [ ! -s "$f10b_err" ]
check "F10: control - an existing but EMPTY root is a silent, clean 0" $?
# The defect: the root is not there at all.
f10c_err="${TMPDIR:-/tmp}/f10c.err.$$"
f10c_out="$(CLAUDE_COORD_DIR="$F10COORD" "$BOARD" --roots "$F10ROOT/no-such-dir" 2>"$f10c_err")"; f10c_rc=$?
[ "$f10c_rc" -eq 3 ]
check "F10: a scan root that does not exist exits 3, not 0" $?
[ -z "$f10c_out" ]
check "F10: a missing root prints no board (3 is not an empty board)" $?
grep -q 'no-such-dir' "$f10c_err"
check "F10: the missing root is NAMED on stderr, not merely counted" $?
[ "$f10b_rc" -ne "$f10c_rc" ]
check "F10: empty root and missing root no longer report the same status" $?
# A MIX is the case that decides whether this is a warning or a failure: some
# repos really were scanned, so the board is real and must be printed - but the
# reader must still learn that one root contributed nothing because it is not
# there, rather than because it is empty.
f10d_err="${TMPDIR:-/tmp}/f10d.err.$$"
f10d_out="$(CLAUDE_COORD_DIR="$F10COORD" "$BOARD" --roots "$F10ROOT,$F10ROOT/no-such-dir" 2>"$f10d_err")"; f10d_rc=$?
[ "$f10d_rc" -eq 0 ]
check "F10: one good root among bad ones still exits 0 - the board is real" $?
printf '%s' "$f10d_out" | grep -q 'f10-repo'
check "F10: the good root's repos are still scanned when a sibling root is bad" $?
grep -q 'no-such-dir' "$f10d_err"
check "F10: the bad root is still named on stderr in the mixed case" $?
/bin/rm -rf "$F10ROOT" "$F10COORD" "$F10EMPTY" "$f10a_err" "$f10b_err" \
"$f10c_err" "$f10d_err" 2>/dev/null
echo ""
echo "board-selftest: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] || exit 1

View file

@ -313,8 +313,20 @@ $dotentry"
OLD_IFS="$IFS"; IFS=','
set -- $ROOTS
IFS="$OLD_IFS"
# F10: `[ -d "$root" ] || continue` skipped a bad root in silence, and a scan
# that then found nothing exited 0 printing nothing - so a typo in --roots, a
# moved home directory and a genuinely empty tree were one single output.
# Verifiseringsloven ansikt 4: the nevner was never reported, so "no repos"
# could not be told apart from "nothing was looked at". Every bad root is now
# NAMED on stderr; the exit status only changes when NO root was scanned at all
# (below), because a mix still produces a real board that must be printed.
ROOTS_OK=0
for root in "$@"; do
[ -d "$root" ] || continue
if [ ! -d "$root" ]; then
echo "board: scan root does not exist: $root (contributed nothing - not scanned, not empty)" >&2
continue
fi
ROOTS_OK=$((ROOTS_OK + 1))
add_dot_repos "$root"
for entry in "$root"/*; do
[ -d "$entry" ] || continue
@ -334,6 +346,17 @@ $child"
done
done
# Status 3 rather than 2, matching coord-count.sh's F5 fix: 2 already means
# "you called me wrong" everywhere in this script (a flag missing its value, an
# unknown --repo), and this is "the world you named is not there". stdout stays
# empty on purpose - 3 is not an empty board, it is the absence of a scan.
if [ "$ROOTS_OK" -eq 0 ]; then
echo "board: no scan root exists (roots: $ROOTS) - nothing was scanned, and that is NOT an empty board" >&2
exit 3
fi
# Roots existed and were scanned; finding no repo in them is a real, measured
# answer and stays a silent, clean 0.
[ -n "$(printf '%s' "$REPOS" | tr -d '[:space:]')" ] || exit 0
# --- Collect one record per repo -------------------------------------------
@ -346,8 +369,28 @@ printf '%s\n' "$REPOS" | while IFS= read -r d; do
name="$(basename "$d")"
state="$d/STATE.md"
dirty="$(git -C "$d" status --porcelain 2>/dev/null | wc -l | tr -d ' ')"
[ -n "$dirty" ] || dirty=0
# F6: `git status | wc -l` yields 0 lines whether the tree is CLEAN or git
# refused to answer at all, so a failure used to print DRT=0 and every reader
# took it as "nothing uncommitted here" - Verifiseringsloven ansikt 4 on our
# own tooling. It is reachable, not theoretical: discovery tests .git with -e
# so worktrees are found, and a worktree whose parent checkout was deleted
# exits 128 here (measured). The exit status is now the discriminator, and an
# unmeasured tree reads "?" - the same token coord-count.sh already uses for
# an age it could not compute, and for the same fail-safe reason: not
# measured must never render as the reassuring value.
#
# Every shell consumer of this field prints it with %s and is unaffected. The
# two awk consumers are NOT, and both are handled where they read it: the
# free-capacity test (a "?" is not free) and the SUM roll-up (a "?" is named,
# never silently added as 0).
dirty_out="$(git -C "$d" status --porcelain 2>/dev/null)"; dirty_rc=$?
if [ "$dirty_rc" -ne 0 ]; then
dirty="?"
elif [ -z "$dirty_out" ]; then
dirty=0
else
dirty="$(printf '%s\n' "$dirty_out" | wc -l | tr -d ' ')"
fi
inbox=0
if [ -d "$COORD/$name/inbox" ]; then
@ -1363,10 +1406,15 @@ plan() {
awk -F'|' -v OWF="$OWED" '
FILENAME==OWF { ow[$1] = $2 + 0; next }
{
name = $3; status = $4; dirty = $7 + 0; orders = $12 + 0; claimed = $13 + 0
name = $3; status = $4; dirty = $7; orders = $12 + 0; claimed = $13 + 0
owed = (name in ow) ? ow[name] : 0
if (status != "done" && status != "deferred") next
if (owed > 0 || orders > 0 || claimed > 0 || dirty > 0) next
# dirty is compared as a STRING against exactly "0", never as $7 + 0: a
# "?" (git could not answer, see F6 above) coerces to 0 in arithmetic and
# would certify an UNMEASURED tree as free capacity - the precise shape
# of the defect this whole column change exists to close. Only a tree
# measured clean is clean.
if (owed > 0 || orders > 0 || claimed > 0 || dirty != "0") next
print name " (" status ")"
}
' "$OWED" "$RECORDS" | sort > "$lf"
@ -1564,9 +1612,15 @@ tot_in="$(awk -F'|' '{s+=$6} END{print s+0}' "$RECORDS")"
tot_dirty="$(awk -F'|' '{s+=$7} END{print s+0}' "$RECORDS")"
n_mal="$(grep -c 'MALFORMED' "$RECORDS" 2>/dev/null | tr -d ' ')"
n_nofield="$(awk -F'|' '$4=="?"' "$RECORDS" | wc -l | tr -d ' ')"
# tot_dirty above sums field 7, where a "?" contributes 0 - so the total is
# short by an unknown amount whenever git failed anywhere. Labelled rather than
# silently dropped, the same rule --plan already applies to repos with no board
# line: the number stays honest about what it did NOT include.
n_unmeas="$(awk -F'|' '$7=="?"' "$RECORDS" | wc -l | tr -d ' ')"
printf '\nSUM: %s uhaandterte innboks-meldinger, %s ukommiterte filer.\n' "$tot_in" "$tot_dirty"
[ "${n_mal:-0}" -gt 0 ] && printf 'ADVARSEL: %s repo har MALFORMED status-token (utenfor det lukkede settet).\n' "$n_mal"
[ "${n_nofield:-0}" -gt 0 ] && printf 'MERK: %s repo mangler board-linje - status/kost er ukjent (?), NESTE-utdrag brukes.\n' "$n_nofield"
[ "${n_unmeas:-0}" -gt 0 ] && printf 'MERK: %s repo har umaalbart arbeidstre - git svarte ikke, DRT er ? og IKKE med i summen over.\n' "$n_unmeas"
exit 0

View file

@ -35,7 +35,20 @@
# --exclude <mailbox> omit one mailbox (the caller's own, whose inbox is
# already injected in full).
# Env: CLAUDE_COORD_DIR overrides the mailbox root.
# Exit: always 0 - this runs at session start and must never fail one.
# Exit: 0 = counted (zero or more mailboxes have pending mail)
# 2 = usage error, nothing counted
# 3 = mailbox root does not exist, nothing counted
#
# The header used to promise exit 0 unconditionally, on the grounds that this
# runs at session start and must never fail one - and that was false in both
# directions (F14). It exited 2 on a usage error already, and - worse - it exited 0 with zero lines when the mailbox root
# was ABSENT, which is byte-identical to "no mailbox has pending mail" on every
# channel a consumer can read (F5). board.sh consumes this TSV. That is
# Verifiseringsloven ansikt 4: a broken query returning a positive-looking null.
# What the old claim was protecting is kept and made precise: no state OF THE
# MAILBOX can ever produce a nonzero exit - not an empty root, not a malformed
# message, not an unreadable date. Only the caller (2) or a missing root (3)
# can, and both print nothing on stdout, so neither can be mistaken for a count.
# ASCII only, bash 3.2 safe.
set -u
export LC_ALL=C
@ -56,7 +69,15 @@ while [ $# -gt 0 ]; do
esac
done
[ -d "$COORD" ] || exit 0
# Not `|| exit 0`: see the F5 paragraph in the header. Status 3 rather than 2
# because 2 is already "you called me wrong" and this is "the world you named
# is not there" - two different repairs, and a consumer that only ever sees one
# integer cannot tell them apart. stdout stays empty on purpose: 3 is not a
# count of zero, it is the absence of a count.
if [ ! -d "$COORD" ]; then
echo "coord-count: mailbox root does not exist: $COORD (not counted, not zero)" >&2
exit 3
fi
# GNU/BSD date flavor, detected once per run (not per mailbox): BSD date
# rejects --version outright (exit nonzero, "illegal option" - measured on

View file

@ -1159,6 +1159,97 @@ check "F7(f): control - a dot-prefixed target name still delivers" $?
/bin/rm -rf "$F7DIR" 2>/dev/null
# 36. F5 - a missing mailbox ROOT and an empty one are two different facts, and
# coord-count.sh used to report them identically: `[ -d "$COORD" ] || exit 0`,
# zero lines on stdout, exit 0, nothing on stderr. A consumer reading that TSV
# (board.sh does) cannot tell "no mailbox has pending mail" from "the root I
# was pointed at is not there" - Verifiseringsloven ansikt 4 exactly: a broken
# query returning a positive-looking null, consumed as a fact about the world.
# The two are now distinguishable by EXIT STATUS and a stderr line. Status 3 is
# new and deliberately not 2: 2 already means "you called me wrong" (a usage
# error, nothing counted), 3 means "the world you named is not there". Both
# print nothing on stdout, so no consumer can mistake either for a count.
#
# The three cases below are the whole point - they must not collapse into one.
F5DIR="$(mktemp -d)"
# (a) The known-positive control FIRST: an EXISTING root holding real mail must
# still count it. A guard that exits 3 on everything would pass every negative
# check below while having destroyed the script, and this is what proves the
# query can still find.
CLAUDE_COORD_DIR="$F5DIR" "$SEND" --to f5box --from tester --subject s --message m >/dev/null 2>&1
f5a_err="$F5DIR/../f5a.err"
f5a_out="$(CLAUDE_COORD_DIR="$F5DIR" "$COUNT" 2>"$f5a_err")"; f5a_rc=$?
[ "$f5a_rc" -eq 0 ] && printf '%s' "$f5a_out" | grep -q '^f5box 1 1 '
check "F5(a): control - an existing root with mail still counts it and exits 0" $?
# (b) An EXISTING but EMPTY root: the genuine "nobody has pending mail" answer.
# Exit 0, no stdout, and NOTHING on stderr - a warning here would make the
# ordinary case noisy and train every reader to ignore the channel that case
# (c) needs.
F5EMPTY="$(mktemp -d)"
f5b_err="$F5DIR/../f5b.err"
f5b_out="$(CLAUDE_COORD_DIR="$F5EMPTY" "$COUNT" 2>"$f5b_err")"; f5b_rc=$?
[ "$f5b_rc" -eq 0 ] && [ -z "$f5b_out" ] && [ ! -s "$f5b_err" ]
check "F5(b): an existing but empty root is a silent, clean zero (exit 0)" $?
# (c) The defect: a root that does not exist. Pre-fix this was byte-identical
# to (b) on every channel a consumer can read.
f5c_err="$F5DIR/../f5c.err"
f5c_out="$(CLAUDE_COORD_DIR="$F5DIR/no/such/root" "$COUNT" 2>"$f5c_err")"; f5c_rc=$?
[ "$f5c_rc" -eq 3 ]
check "F5(c): a missing mailbox root exits 3, not 0" $?
[ -z "$f5c_out" ]
check "F5(c): a missing root still prints NO count line (3 is not a count)" $?
[ -s "$f5c_err" ] && grep -q 'coord-count' "$f5c_err" && grep -q 'not counted' "$f5c_err"
check "F5(c): a missing root says so on stderr, naming what was not measured" $?
# (d) Ground truth that (b) and (c) really are distinguishable now. This is the
# defect stated as one predicate rather than as three separate assertions: it
# fails if any future change collapses the two readings again, including one
# that keeps both exit codes but drops the stderr line.
[ "$f5b_rc" -ne "$f5c_rc" ]
check "F5(d): empty root and missing root no longer report the same status" $?
/bin/rm -rf "$F5EMPTY" "$F5DIR/../f5a.err" "$F5DIR/../f5b.err" "$F5DIR/../f5c.err" 2>/dev/null
# 37. F14 - the header's exit contract said "always 0" while the script exited
# 2 on `--exclude` with no value, and now exits 3 on a missing root (36 above).
# The header IS the contract: it is what `-h` prints, so a consumer that reads
# it and trusts it is reading a false claim. Pinned as a check on the HELP TEXT
# for the same reason board-selftest pins the FLY legend wording - the text is
# engine behavior, not prose, and a doc line nothing tests is a doc line that
# drifts.
f14_help="$(CLAUDE_COORD_DIR="$F5DIR" "$COUNT" --help 2>/dev/null)"; f14_rc=$?
[ "$f14_rc" -eq 0 ] && [ -n "$f14_help" ] && printf '%s' "$f14_help" | grep -q '^Exit:'
check "F14: control - --help still exits 0 and prints an Exit: contract" $?
# Ground truth for the claim the old header contradicted.
f14b_rc=0
CLAUDE_COORD_DIR="$F5DIR" "$COUNT" --exclude >/dev/null 2>&1 || f14b_rc=$?
[ "$f14b_rc" -eq 2 ]
check "F14: ground truth - --exclude with no value really does exit 2" $?
printf '%s' "$f14_help" | grep -q 'always 0'
[ $? -ne 0 ]
check "F14: the header no longer claims the script always exits 0" $?
printf '%s' "$f14_help" | grep -q '2 *= *usage error'
check "F14: the header documents exit 2 (usage error, nothing counted)" $?
printf '%s' "$f14_help" | grep -q '3 *= *mailbox root'
check "F14: the header documents exit 3 (missing root, nothing counted)" $?
# The reason the "always 0" claim existed at all must survive its removal: the
# session-start path must still not be failable by mailbox STATE. Every exit
# above 0 is a caller/world error, never "this mailbox has awkward contents".
f14c_rc=0
CLAUDE_COORD_DIR="$F5DIR" "$COUNT" --exclude f5box >/dev/null 2>&1 || f14c_rc=$?
[ "$f14c_rc" -eq 0 ]
check "F14: control - a correct call over a real root still exits 0" $?
/bin/rm -rf "$F5DIR" 2>/dev/null
echo "----"
echo "PASS=$PASS FAIL=$FAIL"
[ "$FAIL" -eq 0 ]

View file

@ -565,6 +565,114 @@ run_hook "$P"
[ "$HOOK_EXIT" -eq 0 ]; check "Edit: correcting done -> blocked is allowed with unpushed commits" $?
unset SG_PATH SG_CONTENT
# --- 11. F13: the line limit is overridable, and an unusable override is loud
# The limit was a bare `const MAX_LINES = 120`, so a test of the BOUNDARY had
# no choice but to hardcode 120 in every fixture - which means the tests and
# the code encoded the same number twice, and section 1's boundary fixtures
# would have to be rewritten by hand the next time the operator moves it (they
# already were once, 60 -> 120). CLAUDE_STATE_MAX_LINES makes the boundary
# testable at a cheap value AND gives the operator the same knob
# CLAUDE_COORD_DIR gives them over the mailbox root.
#
# The override is not a bypass claim: this guard has always been escapable by
# writing the file some other way (Bash, an editor), exactly as the sibling
# pathguard is. What it must never do is silently NOT take effect.
# Control first, at the DEFAULT: with no override set, the shipped limit still
# governs. This is what proves the denials below come from the override rather
# than from the guard having become stricter for everyone.
unset CLAUDE_STATE_MAX_LINES
P="$(payload '
const content = "x\n".repeat(6);
process.stdout.write(JSON.stringify({
tool_name: "Write",
tool_input: { file_path: "/tmp/f13/STATE.md", content }
}));
')"
run_hook "$P"
[ "$HOOK_EXIT" -eq 0 ]
check "F13: control - a 6-line new STATE.md is allowed at the default limit" $?
# The override takes effect, in BOTH directions. One assertion alone would not
# do: a broken parse that clamped everything to 0 would deny the 6-line file
# and look like a working override.
CLAUDE_STATE_MAX_LINES=5
export CLAUDE_STATE_MAX_LINES
run_hook "$P"
[ "$HOOK_EXIT" -eq 2 ]
check "F13: an override of 5 denies a 6-line new STATE.md" $?
printf '%s' "$HOOK_STDERR" | grep -q 'max 5'
check "F13: the denial message quotes the OVERRIDDEN limit, not the default" $?
P5="$(payload '
const content = "x\n".repeat(5);
process.stdout.write(JSON.stringify({
tool_name: "Write",
tool_input: { file_path: "/tmp/f13/STATE.md", content }
}));
')"
run_hook "$P5"
[ "$HOOK_EXIT" -eq 0 ]
check "F13: exactly-at-the-override is still allowed (boundary, not off by one)" $?
# The ratchet is a property of the guard, not of the constant, so it must
# survive the override: an already-oversized file can still be edited toward
# compliance. Same rule section 8 pins at the default.
# The file must be named exactly STATE.md and the variable must be exported
# BEFORE node reads it. Both were wrong in this section's first cut, and the
# check went GREEN anyway - the basename gate let the write through without
# measuring a thing, and the fixture file was never created. A vacuous pass
# is the very defect this order is closing, so the fixture asserts its own
# ground truth before the check that depends on it.
mkdir -p "$TMPDIR/f13-ratchet"
export SG_BIG="$TMPDIR/f13-ratchet/STATE.md"
node -e 'require("fs").writeFileSync(process.env.SG_BIG, "y\n".repeat(40))'
[ "$(wc -l < "$SG_BIG" | tr -d ' ')" = "40" ]
check "F13: fixture ground truth - the oversized STATE.md really is 40 lines" $?
P="$(payload '
process.stdout.write(JSON.stringify({
tool_name: "Write",
tool_input: { file_path: process.env.SG_BIG, content: "y\n".repeat(20) }
}));
')"
run_hook "$P"
[ "$HOOK_EXIT" -eq 0 ]
check "F13: the ratchet survives the override (40 -> 20, still over 5, allowed)" $?
# The inverse, so the check above cannot pass by the guard simply never firing
# on this path: GROWING the same oversized file is still denied at 5.
P="$(payload '
process.stdout.write(JSON.stringify({
tool_name: "Write",
tool_input: { file_path: process.env.SG_BIG, content: "y\n".repeat(60) }
}));
')"
run_hook "$P"
[ "$HOOK_EXIT" -eq 2 ]
check "F13: growing that same oversized file is still denied under the override" $?
# An override that cannot be used is DENIED, never silently ignored. A silent
# fallback to 120 is the exact defect class this whole order is closing: the
# caller would believe a limit was in force that never was, and a selftest
# would go green having measured the default while claiming to measure 5.
# Failing here is recoverable in one action (unset the variable) and the
# message says which one.
for bad in "0" "-3" "abc" "" "12.5" "1e3"; do
CLAUDE_STATE_MAX_LINES="$bad"
export CLAUDE_STATE_MAX_LINES
run_hook "$P5"
[ "$HOOK_EXIT" -eq 2 ] && printf '%s' "$HOOK_STDERR" | grep -q 'CLAUDE_STATE_MAX_LINES'
check "F13: an unusable override ('$bad') is refused by name, not ignored" $?
done
# ...and an UNSET variable is not an unusable one. Without this the check above
# would pass against a guard that refused every write on the planet.
unset CLAUDE_STATE_MAX_LINES
run_hook "$P5"
[ "$HOOK_EXIT" -eq 0 ]
check "F13: control - an UNSET override is the normal case, not a refusal" $?
unset SG_BIG
echo ""
echo "state-line-guard-selftest: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] || exit 1