Files
ISEP/hooks/tests/gitea-arm-to-github-armed.test.sh
T
Leo 5d938f5d3a 票上回碼解的保險寫回專案根,出貨線讀得到(inkstone/ISEP#156)
gitea-arm-to-github-armed.sh 原本用腳本自己的上一層決定 .github-armed 寫在哪;
plugin 發佈後那是 ~/.claude/plugins/cache/inkstone/isep/<ver>/,
於是保險寫進快取、印「已解」、ship.mjs 與各閘讀不到(arcrun-rag#202 c7598)。

改寫到 gitea_arm_proj_dir(${CLAUDE_PROJECT_DIR:-$(pwd)}),與
main-and-prod-push-guard/github-contact-guard/stage-before-prod-guard 讀的位置、
以及 check 自己讀 token/待核請求的位置一致。擋下時印的 request 路徑改絕對路徑。

測試:hooks/tests/gitea-arm-to-github-armed.test.sh 18/18;修之前 11 條紅。
TESTING.md 補 A42。版本待總管定。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:58:50 +08:00

128 lines
7.6 KiB
Bash
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
# gitea-arm-to-github-armed.sh 的迴歸測試——**保險寫在哪,讀的人就要在哪讀得到**。
#
# 🔴 它在守什麼(inkstone/ISEP#1562026-09-17 實撞,inkstone/arcrun-rag#202):
# ISEP 以 plugin 形式裝在 `~/.claude/plugins/cache/inkstone/isep/<ver>/`
# 舊版用 `ROOT=$(dirname $0)/..` 決定 `.github-armed` 寫在哪
# ⇒ 保險被寫進 **plugin 快取資料夾**;而出貨線(ship.mjs → d20-guard.mjs
# 與每一支讀它的閘(main-and-prod-push-guardgithub-contact-guardstage-before-prod-guard
# 讀的都是**專案根**`${CLAUDE_PROJECT_DIR:-$(pwd)}`
# ⇒ leo 在票上回了碼、腳本印「✅ 保險已解」,出貨照樣卡住。
# **比單純失敗更糟:它對 leo 說謊。**
#
# 判準是「要求某個東西在場」:保險檔必須出現在讀的人會讀的那個位置,
# 並且**真的被那些讀的人認得**(不是只驗檔案存在)。
#
# 全程離線:`gitea-arm-check.sh` 的核准本身(Leo 真的回覆)必須打真的 Gitea,
# 那部分由 gitea-arm-check.test.sh 與 inkstone/InkStoneCo#34 的人工實測負責。
# 這裡把「核准成功」換成 stub,**只驗核准之後保險落在哪、被不被認得**;
# fail-closed 那條則用**真的** check(無待核請求 → 本地就擋,不碰網路)。
#
# 用法:bash hooks/tests/gitea-arm-to-github-armed.test.sh .
set -u
REPO="$(cd "${1:?用法: $0 <ISEP repo 根目錄>}" && pwd)"
SRC="$REPO/scripts/gitea-arm-to-github-armed.sh"
[ -f "$SRC" ] || { echo "❌ 找不到 $SRC(參數要傳 ISEP repo 根目錄)"; exit 1; }
T=$(mktemp -d)
trap '[ -n "${KEEP:-}" ] && echo "保留 $T" || rm -rf "$T"' EXIT
# ── 造一個長得像 plugin 快取的安裝位置,與一個長得像 InkStoneCo 的專案根 ──
PLUG="$T/.claude/plugins/cache/inkstone/isep/9.9.9"
PROJ="$T/InkStoneCo"
mkdir -p "$PLUG/scripts/lib" "$PROJ" "$T/elsewhere"
cp "$SRC" "$PLUG/scripts/"
cp "$REPO/scripts/lib/gitea-arm-common.sh" "$PLUG/scripts/lib/"
cp "$REPO/scripts/gitea-arm-request.sh" "$PLUG/scripts/" 2>/dev/null
# stub:扮演「leo 已在票上核准」——輸出格式照 gitea-arm-check.sh 成功時的 `ARMED: <mission>`
cat > "$PLUG/scripts/gitea-arm-check.sh" <<'STUB'
#!/bin/sh
[ "${1:-}" = "--consume" ] || { echo "stub: 應該帶 --consume" >&2; exit 1; }
echo "ARMED: 測試出貨 ISEP#156"
STUB
chmod +x "$PLUG/scripts/"*.sh
PASS=0; FAIL=0
ok(){ echo " ✅ $1"; PASS=$((PASS+1)); }
ng(){ echo " ❌ $1"; FAIL=$((FAIL+1)); }
chk(){ if eval "$2"; then ok "$1"; else ng "$1"; fi; }
echo "── ① 從 plugin 路徑執行,CLAUDE_PROJECT_DIR 指向專案根(總管 session 的實際形狀)──"
( cd "$T/elsewhere" && CLAUDE_PROJECT_DIR="$PROJ" sh "$PLUG/scripts/gitea-arm-to-github-armed.sh" ) >"$T/out1" 2>&1
rc=$?
chk "離開碼 0" '[ $rc -eq 0 ]'
chk "★ 專案根出現 .github-armed" '[ -f "$PROJ/.github-armed" ]'
chk "★ plugin 快取資料夾裡沒有 .github-armed09-17 實撞的位置)" '[ ! -e "$PLUG/.github-armed" ]'
chk "cwd 也沒有被寫(不是靠碰巧站對位置)" '[ ! -e "$T/elsewhere/.github-armed" ]'
chk "印出的「提前上保險」路徑就是真正寫下的那份" 'grep -qF "rm $PROJ/.github-armed" "$T/out1"'
chk "第 1 行是未來的到期 epoch" '[ "$(sed -n 1p "$PROJ/.github-armed")" -gt "$(date +%s)" ]'
chk "第 2 行帶任務說明" '[ "$(sed -n 2p "$PROJ/.github-armed")" = "測試出貨 ISEP#156" ]'
echo "── ② 讀的人真的認得這份保險(不是只驗檔案存在)──"
# ②a 出貨線:ship.mjs 的 prod --confirm preflight 呼叫的就是 d20-guard.mjs 的 checkArmed(INKSTONE_ROOT)
D20=""
for c in "${INKSTONE_ROOT:-}" "$REPO/../InkStoneCo" "$REPO/../../InkStoneCo" "$HOME/Documents/tech_projects/InkStoneCo"; do
[ -n "$c" ] && [ -f "$c/products/arcrun-rag/installer/scripts/d20-guard.mjs" ] \
&& { D20="$(cd "$c" && pwd)/products/arcrun-rag/installer/scripts/d20-guard.mjs"; break; }
done
if [ -n "$D20" ] && command -v node >/dev/null 2>&1; then
node --input-type=module -e "
const { checkArmed } = await import(process.argv[1]);
const r = checkArmed(process.argv[2]);
console.log('checkArmed OK mission=' + r.mission);
" "$D20" "$PROJ" >"$T/out2" 2>&1
rc=$?
chk "★ ship.mjs 的 checkArmed(專案根) 認得($(head -1 "$T/out2")" '[ $rc -eq 0 ]'
node --input-type=module -e "
const { checkArmed } = await import(process.argv[1]);
checkArmed(process.argv[2]);
" "$D20" "$PLUG" >/dev/null 2>&1
rc=$?
chk "對照:checkArmed(plugin 快取) 讀不到 ⇒ 上一條有鑑別力" '[ $rc -ne 0 ]'
else
echo " ⏭️ SKIP 找不到 arcrun-rag 的 d20-guard.mjs 或 node(不算失敗,也不算驗過)"
fi
# ②b 閘:同一個位置(`${CLAUDE_PROJECT_DIR:-$(pwd)}/.github-armed`
mk(){ python3 -c "import json,sys;print(json.dumps({'tool_name':'Bash','tool_input':{'command':sys.argv[1]},'cwd':sys.argv[2]}))" "$1" "$T/elsewhere"; }
gate(){ # gate <hook> <cmd> <CLAUDE_PROJECT_DIR> → 回 hook 離開碼
mk "$2" | ( cd "$T/elsewhere" && env -u CLAUDE_CODE_CHILD_SESSION CLAUDE_PROJECT_DIR="$3" bash "$1" ) >/dev/null 2>&1
}
# 閘會寫 github-contact-log/狀態目錄——一律寫進暫存的專案根,不碰真的 InkStoneCo
mkdir -p "$PROJ/system-dev/docs/3-specs/autonomy-dispatch" "$T/empty/system-dev/docs/3-specs/autonomy-dispatch"
WR="npx wrangler deploy"
GH="git push https://github.com/example/example.git main"
gate "$REPO/hooks/main-and-prod-push-guard.sh" "$WR" "$PROJ"; rc=$?
chk "★ main-and-prod-push-guard:保險在專案根 ⇒ wrangler deploy 放行" '[ $rc -ne 2 ]'
gate "$REPO/hooks/main-and-prod-push-guard.sh" "$WR" "$T/empty"; rc=$?
chk "對照:沒有保險 ⇒ 同一條被擋" '[ $rc -eq 2 ]'
gate "$REPO/hooks/github-contact-guard.sh" "$GH" "$PROJ"; rc=$?
chk "★ github-contact-guard:保險在專案根 ⇒ push github 放行" '[ $rc -ne 2 ]'
gate "$REPO/hooks/github-contact-guard.sh" "$GH" "$T/empty"; rc=$?
chk "對照:沒有保險 ⇒ 同一條被擋" '[ $rc -eq 2 ]'
echo "── ③ 沒設 CLAUDE_PROJECT_DIRleo 在終端機)⇒ 跟閘一樣退回 cwd ──"
rm -f "$PROJ/.github-armed"
( cd "$PROJ" && env -u CLAUDE_PROJECT_DIR sh "$PLUG/scripts/gitea-arm-to-github-armed.sh" ) >"$T/out3" 2>&1
rc=$?
chk "離開碼 0" '[ $rc -eq 0 ]'
chk "寫在 cwd(專案根),不在 plugin 快取" '[ -f "$PROJ/.github-armed" ] && [ ! -e "$PLUG/.github-armed" ]'
echo "── ④ fail-closed:沒拿到核准就一個檔都不寫(用真的 check,不碰網路)──"
rm -f "$PROJ/.github-armed" "$PLUG/.github-armed"
cp "$REPO/scripts/gitea-arm-check.sh" "$PLUG/scripts/gitea-arm-check.sh"
printf 'GITEA_TOKEN_CLAUDE_CODE=test-token-not-real\n' > "$PROJ/.env"
( cd "$T/elsewhere" && CLAUDE_PROJECT_DIR="$PROJ" sh "$PLUG/scripts/gitea-arm-to-github-armed.sh" ) >"$T/out4" 2>&1
rc=$?
chk "沒有待核請求 ⇒ 離開碼非 0" '[ $rc -ne 0 ]'
chk "專案根與 plugin 快取都沒有 .github-armed" '[ ! -e "$PROJ/.github-armed" ] && [ ! -e "$PLUG/.github-armed" ]'
# 出路要走得通:訊息印的那支 request 腳本必須是真的存在的絕對路徑
# (相對的 `scripts/…` 會對貼上那個人的 cwd 解析,站在 InkStoneCo 會叫到舊複本——ISEP#112 同一個病)
# 取「本檔自己印的」那一行(⛔ 之後);中文冒號不是空白,所以用「⛔ 之後第一個以 / 開頭的 token」
REQ=$(sed -n '/⛔/,$p' "$T/out4" | grep -oE '[^[:space:]]*gitea-arm-request\.sh' | grep '^/' | head -1)
chk "★ 出路印的是存在的絕對路徑(印出:${REQ:-}" '[ -n "$REQ" ] && [ -f "$REQ" ]'
echo
echo "通過 $PASS 條,失敗 $FAIL 條"
[ "$FAIL" -eq 0 ]