Files
ISEP/hooks/tests/sdd-guard-retired.test.sh
T
Leo 7aef9436c9 兩台全給(youlin+geek6688),清單守則從名字改成事實(inkstone/ISEP#115 → comment 5541/5542)
leo 2026-09-01:「youlin 和 geek6688 都全給,因為這兩個都是讓 CC 操作
一個 stage 一個 test prod,不給所有的就是受阻。」覆蓋 comment 5526 的 youlin-only。

- NEEDED 從 2 把變 8 把:youlin 五把(CF token/account id/MCP owner secret/
  stage 帳密兩把)+ geek6688 兩把(出貨 token/account id)+ 機器帳號 Gitea token。
  CLOUDFLARE_ACCOUNT_ID_YOULIN 是 comment 5542 才補進 .env 的,已實查在場。
  CLOUDFLARE_API_TOKEN_leo21c 沒加(leo 只點名那兩台)。
- 新增 B9:leo21c 那把住在頂層 .env,不在 polaris/mira/.env ⇒ B8 抓不到它。
  B9 比的是**值相等**不是名字——把它改名叫 CF_TOKEN_BACKUP 照樣紅,
  而黑名單會放它過去。兩條「該紅」方向都在拋棄式副本上實跑證過。
- 密碼類兩把進清單是給人拿的;credentials-map.md「密碼類憑證的用法限制」照舊:
  AI 不得把密碼打進登入欄位。這條寫在清單旁邊,下一個要加變數的人一定會看到。

順手修掉一個不是我造成的假綠:
- hooks/tests/sdd-guard-retired.test.sh 用了 mapfile,macOS 內建 bash 3.2 沒有這個內建,
  在乾淨的 origin/main 上就是離開碼 1(實驗證過),而 TESTING.md 標著「通過 8」。
  改成 while-read(同 scripts/gitea-labels-sync.sh:56 的前例)後:通過 8 / 失敗 0。

接基底與衝突:
- rebase 到 013a987,main 那 8 筆一筆沒掉。
- TESTING.md 兩邊都新增了 A25 ⇒ main 的 A25(sdd-guard 退役)保留原編號,
  本節改為 A26;狀態表那列跟著改。
- plugin.json 取 main 那邊(version 0.18.0 未動),六個數字在併完的樹上實數:
  61/84/7/7/2/49。
- mistakes.md 兩邊各自 append,兩條都留。

值全程沒有進版控、沒有進對話(只印名字與長度)。版本號待總管定。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 12:04:49 +08:00

159 lines
7.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# sdd-guard 退役的迴歸測試(inkstone/ISEP#91
#
# ── 這支在守什麼 ────────────────────────────────────────────────────
# leo 2026-08-16 在 inkstone/InkStoneCo#40 裁定「**取消 Active SDD**」:
# 任務狀態搬到 Gitea 管,SDD 只記「起初的樣子」。
# 但 `sdd-guard.sh` 是 **fail-closed** 的——
# 動 code 檔時,active SDD 不是「恰好 1 份」→ 擋(0 份也擋)
# 路徑所在的 repo 沒有 3-specs → 也擋
# ⇒ 照裁決把 active SDD 拿掉,會把「動任何 .ts/.py/.go」整個鎖死。
# 這就是那個裁決 11 天沒人敢執行的真正原因。
#
# 所以退役的順序是「**先讓閘退役,再拿掉 active SDD**」,而這支測的是第一步做完了沒有。
#
# 🔴 它測的不是「檔案刪掉了沒有」,而是**那個擋還會不會發生**:
# 把整組 `Write|Edit|MultiEdit` 的 PreToolUse 閘,拿去撞「裁決執行完之後的世界」
# (一個沒有 3-specs 的 repo、以及一個有兩份 active SDD 的 repo),一支都不准擋。
# ⇒ 有人日後用別的檔名把同一個形狀種回來,這支照樣會紅。
#
# 用法:hooks/tests/sdd-guard-retired.test.sh
# 🔴 全程在 TMP 底下建假 repo,跑完自己清;不動任何真 repo、不打網路。
set -u
HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REPO_ROOT="$(cd "$HOOKS_DIR/.." && pwd)"
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0
ok() { echo " ✅ $1"; PASS=$((PASS+1)); }
bad() { echo " ❌ $1"; FAIL=$((FAIL+1)); }
# ── ① 閘本體與它的註冊都不在了 ────────────────────────────────────
echo "── ① 退役:檔案與註冊 ──"
if [ -e "$HOOKS_DIR/sdd-guard.sh" ]; then
bad "hooks/sdd-guard.sh 還在(應已刪除)"
else
ok "hooks/sdd-guard.sh 已刪除"
fi
if grep -q 'sdd-guard' "$HOOKS_DIR/hooks.json"; then
bad "hooks.json 還註冊著 sdd-guard(照裁決做會鎖死所有 code 寫入)"
else
ok "hooks.json 已無 sdd-guard 註冊"
fi
# hooks.json 要仍然是合法 JSON,而且別的閘一支都沒被順手弄掉
# (README:合錯的時候「語法合法、閘卻不見了」,不會有任何東西喊一聲)
REGISTERED=$(python3 - "$HOOKS_DIR/hooks.json" <<'PY'
import json, sys
d = json.load(open(sys.argv[1]))
print(sum(len(g.get("hooks", [])) for ev in d["hooks"].values() for g in ev))
PY
) || REGISTERED=""
if [ -n "$REGISTERED" ]; then
ok "hooks.json 仍是合法 JSON(註冊 $REGISTERED 條)"
else
bad "hooks.json 解析失敗"
fi
# ── ② 真正的驗收:裁決執行完之後,動 code 檔不能被擋 ──────────────
echo "── ② 行為:把「拿掉 active SDD 之後的世界」丟給整組寫檔閘,一支都不准擋 ──"
# 這兩個假 repo 就是舊 sdd-guard 一定會擋下的兩種狀態:
# A. 完全沒有 3-specs(=裁決執行完的樣子,也是 ISEP 自己現在的樣子)
# B. 兩份 status: active(=舊的「單一活性鐵律」違反)
REPO_NO_SDD="$TMP/repo-no-sdd"
mkdir -p "$REPO_NO_SDD/src"
git init -q "$REPO_NO_SDD"
REPO_MULTI="$TMP/repo-multi-active"
mkdir -p "$REPO_MULTI/system-dev/docs/3-specs/a" "$REPO_MULTI/system-dev/docs/3-specs/b" "$REPO_MULTI/src"
git init -q "$REPO_MULTI"
printf -- '---\nstatus: active\n---\n# A\n' > "$REPO_MULTI/system-dev/docs/3-specs/a/design.md"
printf -- '---\nstatus: active\n---\n# B\n' > "$REPO_MULTI/system-dev/docs/3-specs/b/design.md"
# 撈出所有掛在 Write|Edit|MultiEdit 上的 PreToolUse 閘(照 hooks.json 的實況,不寫死清單)
# macOS 內建 bash 是 3.2,沒有 mapfile/readarray——用相容寫法收陣列
# (同 scripts/gitea-labels-sync.sh:56 的前例;2026-09-01 inkstone/ISEP#115 實撞:
# 這支測試在 leo 的 mac 上一次都沒跑成功過,離開碼 1,而 TESTING.md 標著「通過 8」)
WRITE_HOOKS=()
while IFS= read -r _l; do WRITE_HOOKS+=("$_l"); done < <(python3 - "$HOOKS_DIR/hooks.json" <<'PY'
import json, sys
d = json.load(open(sys.argv[1]))
for g in d["hooks"].get("PreToolUse", []):
m = g.get("matcher", "") or ""
if "Write" in m or "Edit" in m:
for h in g.get("hooks", []):
print(h["command"].split("/")[-1])
PY
)
if [ "${#WRITE_HOOKS[@]:-0}" -eq 0 ]; then
bad "撈不到任何 Write|Edit 的 PreToolUse 閘——測試本身失效了,先修這裡"
fi
# 判準:擋下來的理由**是不是 SDD**。
#
# 🔴 這裡刻意不寫成「一支閘都不准擋」——同一組 matcher 上還住著好幾支跟 SDD 無關、
# 而且看 session 狀態決定擋不擋的閘(`subagent-first-guard``history-first-guard`…)。
# 把它們算成失敗,這支測試就會在別人改別的東西時無故變紅,
# 紅久了就沒人看——`docs/TESTING.md` 開頭那句「誤攔比漏擋更該修」對測試一樣成立。
# 所以失敗的定義是:**有閘擋下來,而且它擋的理由指向 SDD/3-specs**。
# 其餘的擋只印出來給人看,不判失敗。
probe() { # probe <說明> <file_path>
local desc="$1" path="$2" sdd_blocked="" other_blocked=""
local payload out rc
payload=$(python3 -c "import json,sys;print(json.dumps({'tool_name':'Write','tool_input':{'file_path':sys.argv[1],'content':'export const x = 1;\n'}}))" "$path")
for name in "${WRITE_HOOKS[@]}"; do
[ -x "$HOOKS_DIR/$name" ] || continue
out=$(printf '%s' "$payload" | "$HOOKS_DIR/$name" 2>&1)
rc=$?
[ "$rc" -eq 2 ] || continue
if printf '%s' "$out" | grep -qE 'SDD|3-specs'; then
sdd_blocked="$sdd_blocked $name"
else
other_blocked="$other_blocked $name"
fi
done
if [ -n "$sdd_blocked" ]; then
bad "$desc —— 仍被 SDD 理由擋下:$sdd_blocked"
else
ok "$desc"
[ -n "$other_blocked" ] && echo " (另有與 SDD 無關的閘擋下,不算失敗:$other_blocked"
fi
}
# 刻意讓 $_rootCLAUDE_PROJECT_DIRpwd)跟這些假 repo 對不上——
# 舊 sdd-guard 在這個情境下走的正是 fail-closed 那條分支。
unset CLAUDE_PROJECT_DIR
cd "$TMP" || exit 1
probe "repo 沒有 3-specs(裁決執行完的樣子)→ 寫 .ts 不被擋" "$REPO_NO_SDD/src/x.ts"
probe "repo 沒有 3-specs → 寫 .py 不被擋" "$REPO_NO_SDD/src/x.py"
probe "兩份 status: active(舊「單一活性」違反)→ 寫 .ts 不被擋" "$REPO_MULTI/src/x.ts"
# ISEP 自己就是「沒有 3-specs 的 repo」——退役前,改自己的 .py 當場被擋。
probe "ISEP 自己的 hooks/lib/*.py → 不被擋(退役前這一格是紅的)" "$REPO_ROOT/hooks/lib/dispatch_parse.py"
# ── ③ 會被載入的那幾份,不能再有人讀到「只准一份 active SDD」 ──────
echo "── ③ 條文:載入面不再宣告單一活性鐵律 ──"
# 只查**會被載入**的那幾份(commands / agents / skills / hooks 腳本)。
# docs/ 與 wiki/ 不查——它們得說得出「退役了什麼」,不然這件事沒有歷史。
STALE=""
while IFS= read -r f; do
grep -qE '只(准|允許)一份|單一活性' "$f" && STALE="$STALE $f"
done < <(find "$REPO_ROOT/commands" "$REPO_ROOT/agents" "$REPO_ROOT/skills" -name '*.md' 2>/dev/null
find "$REPO_ROOT/hooks" -maxdepth 1 -name '*.sh' 2>/dev/null)
if [ -n "$STALE" ]; then
bad "這些載入面還在宣告「只准一份 active SDD」:$STALE"
else
ok "commandsagentsskillshooks 都不再宣告單一活性鐵律"
fi
echo
echo "結果:通過 $PASS 失敗 $FAIL"
[ "$FAIL" -eq 0 ] || exit 1