diff --git a/bin/check-escalation b/bin/check-escalation index 05ff934..bce3cce 100755 --- a/bin/check-escalation +++ b/bin/check-escalation @@ -61,4 +61,28 @@ if [ -f "$PENDING" ]; then done fi +# --- the ratchet must actually ratchet --------------------------------------- +# "This list only shrinks" was asserted in five places and enforced in none. A +# traceability probe falsified it in one run: add an unsectioned skill, append it +# to the list, gate returns 0. So compare against the list as committed at HEAD — +# a convention nothing checks is a convention, and this repo has spent a day on +# what those cost. +if [ -f "$PENDING" ]; then + if ! git -C "$ROOT" rev-parse --git-dir >/dev/null 2>&1; then + echo "WARN: $ROOT is not a git checkout — cannot verify the pending list only shrank" >&2 + elif ! git -C "$ROOT" cat-file -e HEAD:bin/escalation-pending.txt 2>/dev/null; then + : # first commit introducing the file — nothing to compare against yet + else + before=$(git -C "$ROOT" show HEAD:bin/escalation-pending.txt 2>/dev/null \ + | sed 's/#.*//' | tr -d '[:blank:]' | grep -v '^$' | sort -u || true) + now=$(sed 's/#.*//' "$PENDING" | tr -d '[:blank:]' | grep -v '^$' | sort -u || true) + added=$(printf '%s\n' "$now" | grep -vxF "$before" 2>/dev/null || true) + for entry in $added; do + [ -n "$entry" ] || continue + echo "ERROR: '$entry' was added to bin/escalation-pending.txt — the list only shrinks. Write its '## Escalation' section instead of exempting it." >&2 + fail=1 + done + fi +fi + exit $fail diff --git a/tests/check_escalation_test.sh b/tests/check_escalation_test.sh index 642d86a..e33496b 100644 --- a/tests/check_escalation_test.sh +++ b/tests/check_escalation_test.sh @@ -81,5 +81,47 @@ f=$(fixture comments); skill "$f" alpha no printf '# a comment, and a blank line follow\n\nalpha\n' > "$f/bin/escalation-pending.txt" expect 0 'comments and blank lines in the list are ignored' "$f" +# --- the ratchet must actually ratchet --------------------------------------- +# Five places in this repo claimed the pending list "only shrinks" and nothing +# enforced it. A traceability probe falsified that in one run: add an unsectioned +# skill, append it to the list, gate returns 0. A convention stated five times is +# still a convention. +# +# Enforcement compares against the list as committed at HEAD, so the fixtures +# below need a real git repo. + +git_fixture() { + _f="$WORK/$1" + mkdir -p "$_f/skills" "$_f/bin" + ( cd "$_f" && git init -q . && git config user.email t@example.com && git config user.name test ) + skill "$_f" alpha no + printf 'alpha\n' > "$_f/bin/escalation-pending.txt" + ( cd "$_f" && git add -A && git commit -qm baseline ) + printf '%s' "$_f" +} + +f=$(git_fixture ratchet_same) +expect 0 'an unchanged list passes' "$f" + +f=$(git_fixture ratchet_shrink) +skill "$f" alpha yes +: > "$f/bin/escalation-pending.txt" +expect 0 'removing an entry (and writing its section) passes' "$f" + +f=$(git_fixture ratchet_grow) +skill "$f" beta no +printf 'alpha\nbeta\n' > "$f/bin/escalation-pending.txt" +expect 1 'adding a NEW entry fails — the list only shrinks' "$f" 'beta' + +# Outside a git repo the comparison is impossible; that must not be silent. +f=$(fixture ratchet_nogit); skill "$f" alpha no +printf 'alpha\n' > "$f/bin/escalation-pending.txt" +_out=$(sh "$SUT" "$f" 2>&1) +if printf '%s' "$_out" | grep -qi 'cannot verify'; then + pass=$((pass + 1)); printf ' ok an unverifiable ratchet says so rather than passing silently\n' +else + fail=$((fail + 1)); printf ' FAIL an unverifiable ratchet says so rather than passing silently\n%s\n' "$_out" +fi + printf '\ncheck_escalation: %d passed, %d failed\n' "$pass" "$fail" [ "$fail" -eq 0 ]