← back to Nas Setup
TK-11648: negative test drives the REAL backup script via a plist-safe seam
808d7492c7ecd2b78ff650f498142228b1414fe5 · 2026-09-14 01:11:26 -0700 · Steve Abrams
The guard-copy test proved the guard LOGIC but stayed green if the shipped
script's guard was reverted. Add a BACKUP_TEST_MODE seam (SRC/DEST/HENRY/DATA
env overrides, mount-guard relaxed, heartbeat output redirected so it can't
poison the live fleet-health latest.json) and an end-to-end negative test that
runs backup-repos-to-henry.sh itself against a hollow-.git fixture and asserts
it writes NO wrong-repo bundle + reports WARN. Verified the assertion goes RED
when the guard is neutralized (TK-11431 amdt 3).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SnGVNUxnQfxpU1vfZmZFv3
Files touched
M scripts/backup-repos-to-henry.shM scripts/test-backup-identity-guard.sh
Diff
commit 808d7492c7ecd2b78ff650f498142228b1414fe5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Sep 14 01:11:26 2026 -0700
TK-11648: negative test drives the REAL backup script via a plist-safe seam
The guard-copy test proved the guard LOGIC but stayed green if the shipped
script's guard was reverted. Add a BACKUP_TEST_MODE seam (SRC/DEST/HENRY/DATA
env overrides, mount-guard relaxed, heartbeat output redirected so it can't
poison the live fleet-health latest.json) and an end-to-end negative test that
runs backup-repos-to-henry.sh itself against a hollow-.git fixture and asserts
it writes NO wrong-repo bundle + reports WARN. Verified the assertion goes RED
when the guard is neutralized (TK-11431 amdt 3).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SnGVNUxnQfxpU1vfZmZFv3
---
scripts/backup-repos-to-henry.sh | 27 +++++++++++++++++++++------
scripts/test-backup-identity-guard.sh | 24 ++++++++++++++++++++++++
2 files changed, 45 insertions(+), 6 deletions(-)
diff --git a/scripts/backup-repos-to-henry.sh b/scripts/backup-repos-to-henry.sh
index 41c2c0f..0087216 100755
--- a/scripts/backup-repos-to-henry.sh
+++ b/scripts/backup-repos-to-henry.sh
@@ -9,15 +9,26 @@
# half-written corrupt bundle. HARD mount-guard = aborts rather than ever writing to Mac2
# (writing backups onto the box you're backing up is what filled the disk in the first place).
set -uo pipefail
-HENRY=/Volumes/Henry
-DEST="$HENRY/mac2-archive/repo-backups"
-SRC="$HOME/Projects"
+# --- TESTABILITY SEAM (TK-11648) --------------------------------------------------------
+# A negative test must exercise THIS script, not a copy of its guard: a guard-copy test
+# stays green if the shipped guard is later reverted (CLAUDE.md TK-11431 amdt 3 — "this
+# applies to the test harness too"). When BACKUP_TEST_MODE=1, SRC/DEST come from the env
+# and the Henry mount-guard is relaxed so the test drives the real code path against a
+# throwaway fixture. Guarded behind an explicit env flag the launchd plist NEVER sets, so a
+# scheduled run can never silently measure a fixture and become the next false green.
+TEST_MODE="${BACKUP_TEST_MODE:-0}"
+HENRY="${BACKUP_HENRY:-/Volumes/Henry}"
+DEST="${BACKUP_DEST:-$HENRY/mac2-archive/repo-backups}"
+SRC="${BACKUP_SRC:-$HOME/Projects}"
SELF="$(cd "$(dirname "$0")" && pwd)"
-DATA="$SELF/../data"; mkdir -p "$DATA" 2>/dev/null || DATA=/tmp
+# Guard the OUTPUT too: in TEST_MODE the heartbeat goes under the fixture DEST, never the
+# real data/ dir — a seam that writes live latest.json would poison the fleet-health panel.
+if [ "$TEST_MODE" = "1" ]; then DATA="${BACKUP_DATA:-$DEST}"; else DATA="$SELF/../data"; fi
+mkdir -p "$DATA" 2>/dev/null || DATA=/tmp
LATEST="$DATA/repo-backup-latest.json"
# --- HARD GUARD: Henry must be a real mounted volume, or ABORT (never fall back to Mac2) ---
-if [ ! -d "$HENRY" ] || ! mount | grep -q "on $HENRY "; then
+if [ "$TEST_MODE" != "1" ] && { [ ! -d "$HENRY" ] || ! mount | grep -q "on $HENRY "; }; then
printf '{"skill":"repo-backup","verdict":"FAIL","status":"FAIL","reason":"Henry NAS not mounted — aborted (refuse to write backups to Mac2)"}\n' > "$LATEST"
echo "FAIL: Henry not mounted at $HENRY — aborting (will NOT write to Mac2)"; exit 1
fi
@@ -29,7 +40,11 @@ ok=0; fail=0; started=$(date +%s)
# definitions with no off-machine copy at all. Nested skill repos must be bundled
# INDIVIDUALLY: the parent records them as gitlinks, so a parent-only bundle captures a
# pointer and none of their content. (TK-11233 follow-up, 2026-09-10)
-for gd in "$SRC"/*/.git "$HOME/.claude/.git" "$HOME"/.claude/skills/*/.git; do
+srcs=("$SRC"/*/.git)
+# In real runs, ALSO back up ~/.claude and its nested skill repos (not under ~/Projects).
+# In TEST_MODE, scan ONLY the fixture SRC so the negative test stays hermetic.
+[ "$TEST_MODE" != "1" ] && srcs+=("$HOME/.claude/.git" "$HOME"/.claude/skills/*/.git)
+for gd in "${srcs[@]}"; do
[ -d "$gd" ] || continue
repodir="$(dirname "$gd")"
case "$repodir" in
diff --git a/scripts/test-backup-identity-guard.sh b/scripts/test-backup-identity-guard.sh
index cce4284..0e04b98 100755
--- a/scripts/test-backup-identity-guard.sh
+++ b/scripts/test-backup-identity-guard.sh
@@ -47,5 +47,29 @@ r_good="$(guard "$T/good")"
[ "$r_child" = "FAIL" ] && echo "ok : guard FAILs the hollow child (walk-up caught)" || { echo "FAIL: guard did NOT catch the hollow child (got $r_child)"; fails=$((fails+1)); }
[ "$r_good" = "PASS" ] && echo "ok : guard PASSes the valid standalone repo" || { echo "FAIL: guard wrongly rejected a valid repo (got $r_good)"; fails=$((fails+1)); }
+# --- END-TO-END: drive the REAL script, not a copy of its guard ---------------
+# TK-11431 amdt 3 ("applies to the test harness too"): the guard() checks above copy the
+# snippet, so they stay green if the SHIPPED script's guard is reverted. This section runs
+# backup-repos-to-henry.sh itself against a throwaway fixture via its BACKUP_TEST_MODE seam
+# and proves the shipped code goes RED (writes no bundle) for the hollow child while still
+# bundling the valid sibling.
+SCRIPT="$(cd "$(dirname "$0")" && pwd)/backup-repos-to-henry.sh"
+E="$(mktemp -d /tmp/tk11648-e2e-XXXX)"; trap 'rm -rf "$T" "$E"' EXIT
+# fixture SRC is itself a valid repo (mimics ~/Projects meta-repo) so a hollow child walks up to it
+git init -q "$E/src"; ( cd "$E/src" && git -c user.email=t@t -c user.name=t commit -q --allow-empty -m meta )
+git init -q "$E/src/good"; ( cd "$E/src/good" && git -c user.email=t@t -c user.name=t commit -q --allow-empty -m good )
+mkdir -p "$E/src/hollow/.git/refs" "$E/src/hollow/.git/objects" # hollow: no HEAD/config
+BACKUP_TEST_MODE=1 BACKUP_SRC="$E/src" BACKUP_DEST="$E/dest" BACKUP_HENRY="$E/henry" BACKUP_DATA="$E/dest" \
+ bash "$SCRIPT" >"$E/out.txt" 2>&1
+e2e_verdict="$(grep -o '"verdict":"[A-Z]*"' "$E/dest/repo-backup-latest.json" 2>/dev/null | head -1)"
+[ -f "$E/dest/good.bundle" ] && echo "ok : real script bundled the valid sibling (good.bundle written)" || { echo "FAIL: real script did NOT bundle the valid sibling"; fails=$((fails+1)); }
+[ ! -f "$E/dest/hollow.bundle" ] && echo "ok : real script wrote NO hollow.bundle (walk-up mislabel prevented)" || { echo "FAIL: real script wrote hollow.bundle — the WRONG-repo mislabel"; fails=$((fails+1)); }
+echo "$e2e_verdict" | grep -q 'WARN' && echo "ok : real script reported WARN (a repo failed the guard)" || { echo "FAIL: real script verdict was not WARN (got ${e2e_verdict:-<none>})"; fails=$((fails+1)); }
+# prove the seam did NOT touch the live heartbeat
+if [ -f "$SCRIPT" ]; then
+ live="$(cd "$(dirname "$SCRIPT")/.." && pwd)/data/repo-backup-latest.json"
+ [ ! -e "$live" ] || ! grep -q "$E" "$live" 2>/dev/null && echo "ok : live heartbeat untouched by the test seam" || { echo "FAIL: test seam wrote the fixture path into the live heartbeat"; fails=$((fails+1)); }
+fi
+
echo "-------------------------------------------"
[ "$fails" -eq 0 ] && { echo "TK-11648 guard test: PASS"; exit 0; } || { echo "TK-11648 guard test: FAIL ($fails)"; exit 1; }
← 9603008 TK-11648: identity guard so backup never bundles the WRONG r
·
back to Nas Setup
·
(newest)