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:
parent
3beef2a603
commit
d8fdeaa991
7 changed files with 606 additions and 16 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue