5909c2a43a
實撞(2026-09-02,做這張票的路上):KBDB 兩支工具都回 Connection closed,
照 history-first-guard.sh 印出來的那行 `touch /tmp/.kbdb-down` 打完再送同一個編輯,
**被一模一樣地擋第二次,訊息一字不差**。
根因(打出來的,不是推的):
$ touch /tmp/.kbdb-down
$ t=$(cat /tmp/.kbdb-down); echo "[$t]" → []
$ echo $((now - t)) → 1788318047 ≥ 3600
touch 造的是空檔,而那支閘讀的是**檔案內容**當時戳
⇒ 空字串被當成 0 ⇒「距今 17 億秒」⇒ 永遠不新鮮 ⇒ 恆擋。
⇒ 那行逃生門是印出來好看的,**沒有人照著打過一次**。
跟本票 comment 6071(scripts/ticket 沒有 handback 這個動詞)、
inkstone/ISEP#125(worktree 閘的逃生門原文照打 rc=2)是同一句話。
改法:內容不是數字就改用檔案的 mtime——touch 做的正是更新 mtime,
所以訊息那一行從此真的走得通;kbdb-asked-stamp.sh 寫數字的舊格式照樣相容。
路徑加 KBDB_STAMP_DIR 覆寫,只為了讓測試不去動這台機器真正的戳記
(清掉別的 session 的戳記=把閘弄成隨機的)。
沒有把閘弄鬆:過期的戳記照樣不算數(測試第 ⑦ 條守這件事),
文件/新檔/測試檔本來就不擋的三條也各有一格守著。
驗(hooks/tests/history-first-guard.test.sh,10 條,全離線):
修好後 10/10
修好前(路徑隔離) 9/10 —— ④「擋人的換成歷史警察了嗎」紅
⚠️ 第 ③ 條兩版都綠(它只驗離開碼,而過了第 0 道還有歷史警察會擋)
真正分辨得出新舊的是 ④,檔頭寫了怎麼重現這個對照。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
97 lines
6.2 KiB
Bash
Executable File
97 lines
6.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# history-first-guard.sh 的迴歸測試 —— 重點在**逃生門真的走得通**(inkstone/ISEP#122)
|
||
#
|
||
# ── 這支在守什麼 ────────────────────────────────────────────────────
|
||
# 2026-09-02 實撞:KBDB 兩支工具都回 `Connection closed`,照這支閘印出來的那行
|
||
# `touch /tmp/.kbdb-down`
|
||
# 打完再送同一個編輯,**被一模一樣地擋第二次**——因為 `touch` 造的是空檔,
|
||
# 而舊版是 `cat` 檔案內容當時戳,空字串被當成 0 ⇒ 「距今 17 億秒」⇒ 永遠不新鮮。
|
||
#
|
||
# 🔴 一道閘印出來的出路走不通,比沒有出路更糟:人照做了、還是被擋,
|
||
# 下一步就是去繞過這道閘(本 repo 的 ticket-api-bypass-guard.sh 檔頭記過同一課)。
|
||
# 所以這支測試的第一等公民是**逃生門那一條**,不是「擋不擋得住」。
|
||
#
|
||
# 🔴 **這支測試自己怎麼被證明有鑑別力**(不要只看它全綠):
|
||
# 把修好之前那一版拿來跑,第 ④ 條要紅。因為舊版沒有 KBDB_STAMP_DIR,
|
||
# 直接餵它會去讀這台機器真正的 /tmp 戳記而「剛好過關」,所以要先把路徑隔離:
|
||
# git show <修好前的 commit>:hooks/history-first-guard.sh > /tmp/old.sh
|
||
# sed 's#/tmp/\.kbdb-asked#${KBDB_STAMP_DIR:-/tmp}/.kbdb-asked#;
|
||
# s#/tmp/\.kbdb-down#${KBDB_STAMP_DIR:-/tmp}/.kbdb-down#' /tmp/old.sh > /tmp/old-iso.sh
|
||
# bash hooks/tests/history-first-guard.test.sh /tmp/old-iso.sh
|
||
# 2026-09-02 實跑:舊版 9/10(④ 紅),修好後 10/10。
|
||
# ⚠️ 第 ③ 條**兩版都是綠的**——它只驗離開碼 2,而過了第 0 道之後歷史警察照樣會擋。
|
||
# 真正分辨得出來的是 ④「擋人的是誰」。這一格是本支最容易寫成假綠的地方。
|
||
#
|
||
# 🔴 全程用 KBDB_STAMP_DIR 指到 TMP,**不碰這台機器真正的 /tmp/.kbdb-* 戳記**
|
||
# (把別的 session 的戳記清掉=把閘弄成隨機的)。不打網路。
|
||
set -u
|
||
HOOK="${1:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/history-first-guard.sh}"
|
||
TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
|
||
export KBDB_STAMP_DIR="$TMP/stamps"; mkdir -p "$KBDB_STAMP_DIR"
|
||
|
||
PASS=0; FAIL=0; N=0
|
||
|
||
# 造一個「有前科」的 code 檔:放在一個有 git 歷史的假 repo 裡
|
||
REPO="$TMP/repo"; mkdir -p "$REPO"
|
||
( cd "$REPO" && git init -q && git config user.email t@t && git config user.name t \
|
||
&& printf 'echo old\n' > victim.sh && git add . && git commit -qm "第一版" ) >/dev/null 2>&1
|
||
VICTIM="$REPO/victim.sh"
|
||
|
||
# run <檔案> → 印出離開碼(送一份 Edit 的 payload 進去)
|
||
run() {
|
||
printf '{"tool_input":{"file_path":"%s"}}' "$1" \
|
||
| CLAUDE_PROJECT_DIR="$REPO" bash "$HOOK" >/dev/null 2>"$TMP/err"
|
||
printf '%s' "$?"
|
||
}
|
||
|
||
check() { # check <說明> <實際> <期望>
|
||
N=$((N+1))
|
||
if [ "$2" = "$3" ]; then printf ' ✅ %s\n' "$1"; PASS=$((PASS+1))
|
||
else printf ' ❌ %s —— 期望離開碼 %s,實際 %s\n' "$1" "$3" "$2"; sed 's/^/ /' "$TMP/err" | head -6; FAIL=$((FAIL+1)); fi
|
||
}
|
||
|
||
clean_stamps() { rm -f "$KBDB_STAMP_DIR/.kbdb-asked" "$KBDB_STAMP_DIR/.kbdb-down"; rm -f /tmp/.history-guard-*; }
|
||
|
||
echo "── 第 0 道:問過 KBDB 了沒 ──────────────────────────────────"
|
||
clean_stamps
|
||
check "① 沒有任何戳記 → 擋" "$(run "$VICTIM")" 2
|
||
grep -q "KBDB" "$TMP/err" && printf ' ✅ ② 擋下時訊息講得出是哪一道\n' && PASS=$((PASS+1)) || { printf ' ❌ ② 訊息沒提到 KBDB\n'; FAIL=$((FAIL+1)); }; N=$((N+1))
|
||
|
||
echo "── 🔴 逃生門:照訊息打那一行,就要真的過得去 ──────────────────"
|
||
clean_stamps
|
||
touch "$KBDB_STAMP_DIR/.kbdb-down" # ← 訊息裡原文那一行做的事
|
||
check "③ touch 出來的空檔(本票的實撞)→ 要放行過第 0 道" "$(run "$VICTIM")" 2
|
||
# ↑ 期望仍是 2:過了第 0 道之後會被「歷史警察」擋(這個檔真的有前科),
|
||
# 所以要看訊息換人了沒——這是這支測試最容易寫錯的一格。
|
||
grep -q "歷史警察" "$TMP/err" && printf ' ✅ ④ 擋人的換成歷史警察 ⇒ 第 0 道確實被逃生門放過了\n' && PASS=$((PASS+1)) || { printf ' ❌ ④ 還是卡在 KBDB 那一道 ⇒ 逃生門是假的\n'; sed 's/^/ /' "$TMP/err" | head -4; FAIL=$((FAIL+1)); }; N=$((N+1))
|
||
|
||
clean_stamps
|
||
date +%s > "$KBDB_STAMP_DIR/.kbdb-down" # 舊格式(檔案內容是時戳)
|
||
run "$VICTIM" >/dev/null
|
||
grep -q "歷史警察" "$TMP/err" && printf ' ✅ ⑤ 舊格式(內容寫時戳)照樣認得 ⇒ 沒有把既有的弄壞\n' && PASS=$((PASS+1)) || { printf ' ❌ ⑤ 舊格式壞了\n'; FAIL=$((FAIL+1)); }; N=$((N+1))
|
||
|
||
clean_stamps
|
||
date +%s > "$KBDB_STAMP_DIR/.kbdb-asked"
|
||
run "$VICTIM" >/dev/null
|
||
grep -q "歷史警察" "$TMP/err" && printf ' ✅ ⑥ 真的問過 KBDB(kbdb-asked-stamp.sh 寫的戳記)→ 過第 0 道\n' && PASS=$((PASS+1)) || { printf ' ❌ ⑥ 問過了還被擋\n'; FAIL=$((FAIL+1)); }; N=$((N+1))
|
||
|
||
echo "── 過期的戳記不算數(逃生門不能變成永久後門)──────────────────"
|
||
clean_stamps
|
||
touch -t "$(date -v-2H '+%Y%m%d%H%M' 2>/dev/null || date -d '2 hours ago' '+%Y%m%d%H%M')" "$KBDB_STAMP_DIR/.kbdb-down"
|
||
run "$VICTIM" >/dev/null
|
||
grep -q "KBDB" "$TMP/err" && printf ' ✅ ⑦ 兩小時前的空戳記 → 過期,回到擋\n' && PASS=$((PASS+1)) || { printf ' ❌ ⑦ 過期的戳記還在放行 ⇒ 一次 touch 就永久免疫\n'; FAIL=$((FAIL+1)); }; N=$((N+1))
|
||
|
||
echo "── 不該擋的(誤攔比漏擋嚴重)────────────────────────────────"
|
||
clean_stamps
|
||
printf '# doc\n' > "$REPO/readme.md"
|
||
check "⑧ 文件檔(.md)→ 一路放行,連第 0 道都不該碰" "$(run "$REPO/readme.md")" 0
|
||
clean_stamps
|
||
check "⑨ 不存在的新檔 → 放行" "$(run "$REPO/brand-new.sh")" 0
|
||
clean_stamps
|
||
mkdir -p "$REPO/tests"; printf 'echo t\n' > "$REPO/tests/a.sh"
|
||
check "⑩ 測試檔 → 放行" "$(run "$REPO/tests/a.sh")" 0
|
||
|
||
echo
|
||
printf '通過 %s 條,失敗 %s 條(共 %s 條)\n' "$PASS" "$FAIL" "$N"
|
||
[ "$FAIL" = 0 ] || exit 1
|