fix(check): make the escalation ratchet actually ratchet
"That list only shrinks" was asserted in the commit message, the script header, the pending file's own header, the test header, and the issue comment. It was enforced nowhere. A traceability probe falsified it in one run: extract the commit to a temp tree, add a skill with no `## Escalation` section, append its name to bin/escalation-pending.txt — gate returns 0. The list could grow silently and forever. "Self-cleaning" was true; "only shrinks" was a social convention wearing a mechanism's clothes, and this repo has spent a day on what those cost. The gate now diffs the pending list against the version committed at HEAD and fails on any added entry, naming it. Where the comparison is impossible — not a git checkout — it says so rather than passing silently, because a ratchet that cannot be verified must not look like one that was. Four new cases: unchanged list, shrinking list, growing list, and the unverifiable case. Refs #37 Bump-Type: patch
This commit is contained in:
@@ -61,4 +61,28 @@ if [ -f "$PENDING" ]; then
|
|||||||
done
|
done
|
||||||
fi
|
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
|
exit $fail
|
||||||
|
|||||||
@@ -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"
|
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"
|
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"
|
printf '\ncheck_escalation: %d passed, %d failed\n' "$pass" "$fail"
|
||||||
[ "$fail" -eq 0 ]
|
[ "$fail" -eq 0 ]
|
||||||
|
|||||||
Reference in New Issue
Block a user