#!/usr/bin/env bash # scripts/gate-ok +「戳記真的打得開門」的迴歸測試(inkstone/ISEP#90 ④) # # ══ 這支守的是什麼 ═════════════════════════════════════════════════════ # 三支閘(prod-write/main-and-prod-push/stage-before-prod)都是 # 「擋下來,但**總管看過就可以放行**」的設計。既有的測試只驗了「擋得住」, # **一條都沒有驗過「放得開」**——於是 2026-08-28 才發現: # `stat -f %m` 在 GNU coreutils 上是「檔案系統資訊」,它一邊回非零一邊吐一整段文字, # 把接在後面的秒數污染掉 ⇒ **在 Linux(=每一個雲端 session)上,那三支閘的戳記 # 永遠不會被接受** ⇒ 它們在雲端等於純擋,而閘不會告訴你門是壞的。 # # 所以這支的第一優先是「門打得開」,第二優先是「門沒有因此變寬」。 # # 🔴 這支會真的寫 /tmp 的戳記檔(那是閘寫死的路徑,沒有覆寫的開關)。 # 每一條測完就把它刪掉;跑之前若有真的在等的戳記,會被這支洗掉—— # 請不要在「已經蓋好戳記正要推東西」的當下跑它。 set -u ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # hooks/ REPO="$(cd "$ROOT/.." && pwd)" GATEOK="$REPO/scripts/gate-ok" TMP=$(mktemp -d); trap 'rm -rf "$TMP"; rm -f /tmp/.prod-write-ok /tmp/.main-push-ok /tmp/.stage-verified /tmp/.solo-ok-TESTSID' EXIT export GIT_CONFIG_GLOBAL="$TMP/gitconfig"; : > "$GIT_CONFIG_GLOBAL" export GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t PASS=0; FAIL=0 ok(){ if [ "$2" = "$3" ]; then printf ' ✅ %s\n' "$1"; PASS=$((PASS+1)); else printf ' ❌ %s —— 期望 %s,實得 %s\n' "$1" "$3" "$2"; FAIL=$((FAIL+1)); fi; } mkrepo(){ d="$TMP/$1"; git init -q -b main "$d"; ( cd "$d" && echo a>a && git add a && git commit -qm x ) >/dev/null 2>&1; printf '%s\n' "$d"; } fire_prod(){ printf '{"tool_name":"Bash","tool_input":{"command":"acr push x.yaml"}}' \ | bash "$ROOT/prod-write-guard.sh" >/dev/null 2>&1; echo $?; } fire_push(){ printf '{"tool_name":"Bash","tool_input":{"command":"git push origin main"}}' \ | ( cd "$1" && bash "$ROOT/main-and-prod-push-guard.sh" >/dev/null 2>&1; echo $? ); } rm -f /tmp/.prod-write-ok /tmp/.main-push-ok /tmp/.stage-verified echo "── 最重要:門打得開(Linux 上 stat -f %m 那個 bug 的迴歸)──────" ok "① 沒戳記 → prod-write 擋" "$(fire_prod)" 2 bash "$GATEOK" prod-write >/dev/null ok "② gate-ok prod-write 之後 → 放行" "$(fire_prod)" 0 ok "③ 單次用完即丟:同一枚不能放行第二次" "$(fire_prod)" 2 R1=$(mkrepo r1); R2=$(mkrepo r2) ok "④ 沒戳記 → 推 main 擋" "$(fire_push "$R1")" 2 bash "$GATEOK" main-push "$R1" >/dev/null ok "⑤ gate-ok main-push 之後 → 放行" "$(fire_push "$R1")" 0 echo "── 門沒有因此變寬 ────────────────────────────────────────────" bash "$GATEOK" main-push "$R1" >/dev/null ok "⑥ 替 r1 開的門,r2 走不過(綁 repo 沒鬆)" "$(fire_push "$R2")" 2 rm -f /tmp/.main-push-ok # 過期的戳記不算數:把 mtime 調到 16 分鐘前 bash "$GATEOK" prod-write >/dev/null touch -d '16 minutes ago' /tmp/.prod-write-ok 2>/dev/null || touch -A -001600 /tmp/.prod-write-ok 2>/dev/null ok "⑦ 16 分鐘前的戳記 → 過期,照樣擋" "$(fire_prod)" 2 rm -f /tmp/.prod-write-ok # 空內容的 main-push 戳記=萬用鑰匙,08-12 那次穿透的形狀,不准復活 : > /tmp/.main-push-ok ok "⑧ 空內容的 main-push 戳記 → 不算數" "$(fire_push "$R1")" 2 rm -f /tmp/.main-push-ok echo "── gate-ok 自己:不認得的就要拒絕,別蓋一枚打不開的戳記 ──────" bash "$GATEOK" nonsense >/dev/null 2>&1; ok "⑨ 不認得的閘名 → 離開碼 2" "$?" 2 bash "$GATEOK" solo >/dev/null 2>&1; ok "⑩ solo 沒帶 session id → 離開碼 2" "$?" 2 bash "$GATEOK" main-push "$TMP" >/dev/null 2>&1; ok "⑪ 不是 git repo → 離開碼 2" "$?" 2 ok "⑫ 而且**沒有**留下一枚註定打不開的空戳記" "$([ -f /tmp/.main-push-ok ] && echo yes || echo no)" no bash "$GATEOK" solo TESTSID >/dev/null 2>&1 ok "⑬ solo → 蓋出 /tmp/.solo-ok-" "$([ -f /tmp/.solo-ok-TESTSID ] && echo yes || echo no)" yes bash "$GATEOK" stage-verified >/dev/null 2>&1 ok "⑭ stage-verified → 蓋出 /tmp/.stage-verified" "$([ -f /tmp/.stage-verified ] && echo yes || echo no)" yes echo "── file_mtime 本身:兩個平台的寫法都要答得出純數字 ────────────" . "$ROOT/lib/mtime.sh" touch "$TMP/probe" M=$(file_mtime "$TMP/probe") case "$M" in ''|*[!0-9]*) R=bad ;; *) R=ok ;; esac ok "⑮ file_mtime 回純數字(舊寫法在 Linux 上會回一整段文字)" "$R" ok ok "⑯ 檔案不存在 → 回 0,不是空字串" "$(file_mtime "$TMP/nope")" 0 echo printf '通過 %s 條,失敗 %s 條\n' "$PASS" "$FAIL" [ "$FAIL" = 0 ] || exit 1