7aef9436c9
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>
159 lines
7.6 KiB
Bash
Executable File
159 lines
7.6 KiB
Bash
Executable File
#!/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
|
||
}
|
||
|
||
# 刻意讓 $_root(CLAUDE_PROJECT_DIR/pwd)跟這些假 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 "commands/agents/skills/hooks 都不再宣告單一活性鐵律"
|
||
fi
|
||
|
||
echo
|
||
echo "結果:通過 $PASS / 失敗 $FAIL"
|
||
[ "$FAIL" -eq 0 ] || exit 1
|