From 950c3e19198bacf60954370c5c78b15c03866bec Mon Sep 17 00:00:00 2001 From: richblack Date: Thu, 27 Aug 2026 18:30:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=AC=E8=A9=A6=E4=B8=8D=E5=86=8D=E5=81=BD?= =?UTF-8?q?=E9=80=A0=E3=80=8C=E6=9C=89=E4=B8=80=E7=AD=86=E6=8E=A8=20main?= =?UTF-8?q?=20=E5=9C=A8=E7=AD=89=E7=B8=BD=E7=AE=A1=E8=A3=81=E3=80=8D?= =?UTF-8?q?=EF=BC=88inkstone/ISEP#59=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 現象(實測,不是推論):跑完 main-and-prod-push-guard 那三支測試之後 $ git status --short M hooks/lib/__pycache__/strip_heredoc.cpython-314.pyc M pending-main-push/unnamed--ISEP.md ?? pending-main-push/unnamed--A.md 成因:這支閘擋下推 main 的同時,會把那次請求寫成 `/pending-main-push/<誰>--.md`,而三支測試的測資本來 就全是推 main。於是每跑一次測試,工作區就多/改幾筆**偽造的待裁決**。 兩個後果,後者比較貴: ① `git add -A` 很容易把它們帶進 commit(08-27 那次真的帶進去了,事後才拔掉) ② 總管的迴圈讀那個目錄,讀到的每一筆都該是真的在等他裁—— 測試每跑一次就偽造一筆 ⇒ **下一筆真的請求會混在雜訊裡**。 這跟「永遠在響的警報」是同一個病。 改法(產品程式碼一行都沒動): - 新增 hooks/tests/lib/hook-sandbox.sh:把整個 hooks/ 複製到暫存區再跑複本。 閘算 pending 目錄的位置靠的是 `$0` 的上一層 ⇒ 請求寫進暫存區。 **刻意不在閘上開一個「寫去哪」的環境變數**——那種開關同時是一條把紀錄關掉的路。 - 三支測試改測沙盒複本,並各補兩條斷言: ① repo 的 pending-main-push 一個位元都沒動 ② 沙盒裡**真的有**留下請求(只驗 ① 的話,把留紀錄的功能整個關掉也會綠) - pending-main-push/ 不再進版控(它是本機狀態不是原始碼),只留一份 README 說明規約; 既有的兩筆與那顆被追蹤的 .pyc 一併 `git rm --cached`,檔案留在硬碟上不刪。 實測: - 三支各自 10/10、19/19、13/13(原本 8/17/11,各多兩條新斷言) - 把 `H` 改回真跡重跑 ⇒ 新斷言兩條都紅(11/13)+工作區又髒 ⇒ 這兩條斷言真的抓得到它要抓的東西 - 全套 16 支測試檔跑完 0 失敗,`git status` 沒有多出任何一行 - `claude plugin validate .` ✔ Validation passed 沒有升版:這次只動測試與版控範圍,沒有任何閘的行為改變, 不需要靠新版本號送到任何人手上;請跟著總管下一個 release 一起出去。 Co-Authored-By: Claude Opus 5 --- .gitignore | 9 ++ .../__pycache__/strip_heredoc.cpython-314.pyc | Bin 3357 -> 0 bytes hooks/tests/lib/hook-sandbox.sh | 101 ++++++++++++++++++ ...ain-and-prod-push-guard-cross-repo.test.sh | 19 +++- hooks/tests/main-and-prod-push-guard.test.sh | 16 ++- pending-main-push/README.md | 16 +++ pending-main-push/unnamed--ISEP.md | 43 -------- pending-main-push/unnamed--InkStoneCo.md | 23 ---- scripts/test-main-and-prod-push-guard.sh | 18 +++- 9 files changed, 175 insertions(+), 70 deletions(-) delete mode 100644 hooks/lib/__pycache__/strip_heredoc.cpython-314.pyc create mode 100644 hooks/tests/lib/hook-sandbox.sh create mode 100644 pending-main-push/README.md delete mode 100644 pending-main-push/unnamed--ISEP.md delete mode 100644 pending-main-push/unnamed--InkStoneCo.md diff --git a/.gitignore b/.gitignore index db123e3..58eed0d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,12 @@ cloud-env*.txt # import 共用零件時 python 自己生的,不是原始碼 __pycache__/ *.pyc + +# 推 main 被擋下時留的請求(main-and-prod-push-guard.sh 寫的)。 +# 這是**本機當下的狀態**,不是原始碼:一份檔案的語意是「有一筆推 main 正在等總管裁」。 +# 進版控會有兩個後果(inkstone/ISEP#59 comment 4779 第 ① 條,兩個都實際發生過): +# ① `git add -A` 把它帶進 commit ⇒ 別人 clone 下來就多一筆**別人的**待裁決 +# ② 被追蹤的檔每次被覆寫都變成 ` M` ⇒ `git status` 永遠是髒的 +# (寫成 `目錄/*` 而不是 `目錄/`:後者連目錄本身都排除,下一行的例外就永遠生效不了) +pending-main-push/* +!pending-main-push/README.md diff --git a/hooks/lib/__pycache__/strip_heredoc.cpython-314.pyc b/hooks/lib/__pycache__/strip_heredoc.cpython-314.pyc deleted file mode 100644 index 6d35ecec8de4d94661c46fd5ef90e7bfe26023eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3357 zcmbVO%~Kr574O-d{Tdd5Vz54nlv-d37PBlt#6cfr7ePn@ED0?bQWmf^%rwl1*_laq zuh6Pou1!_SSu81_I4RsiAEYCPJosk`DK#&mWq(& zdFw3a*ef~4sqGgmLW$`agMJxjvzS>rbg@MopU?s9dK~L`P3Hvn+3q^yo=b9*vlBBt zBSU!hH|MnaLWUjtHo>+F^-2!wu8B=gZZ^D<3nCauv0ym6L2b^7D@A4oHEqGtIkq`o zE9ul^=fob%cGvJq8Acg#RtNDU#}X;S+#}0Ps>F}I64&IY;T4Ml zf!O4(4d8V`d?8ZoW?ZKnK)PU$=o_BPspn)@9d85sFqZ)%xNdL>t$a?vZpasUvCXil zbG<=~3yVQZ$>6r<4&u2D4;O8gU2!~Pong<_hKGhf$PSHWhlgIJ&t0DtJdS%k!)FF? z&#ynogtWsbrXFx^=p{ykc1bSl|b9t8c6posxxt@%r*&(ui6g5D@0Hko?@Rz z0hrJX0vdC^$2c)FcmocdQv$AIiu1Qu0WPR)NZkO<7jx-L#nMe+n_zNBFZuvdoNwJ1F5CP{X$AWwaFmcB z5b4;QP~DMC;8kG+wo2eMm;*bsj2%EtGv_72;E;rB0*J6o0MRjsCAt(S?3Q-@zT^o@ z!Fw5ovngY48|#Gkf*)+S*d)w=%O?Fqn-gp57YAU2`IHplJnQ}~3_6dAZdmoR7v2l( z@w%MQ2WZJ5YygI}TP4Qf)?mkT#i3b%6O3S<1NX7v0$G7g0B_tVP5XrDYvi1Uq4;n~ z_$ux_#d>k~d*fFxcH_^mU*?wQp22UA6`FRPV!P;jaGl`#f>s?DJriKn#jaAXSiiPm zfC*P0h&oKWc6aE)g}&@(pJzV0i5K}&f8WjC#qOn#GJQA4vY+bNyM?7jV<>yJu+*Pf z95zHaY=lGv5e-G*+Z%#k1WuiYuU?zwf zMKg#~!b{Z6C_xl3LKO$kDC(RERo%f(x(lHW+^kTbDUg2>K6V^_74%|)V-r%g|K}lWJONbe|aTLAHFh({Kzu$ql;pvEc4A-bzx-m7r@*<1ak}T3Q-%usxFqTg`etwyz^TAMb5Uto>3*nvcu~179o69VgIybXgf_Z7%Sm)0YvNkI%F; z$HLa~KHhv}^a?_L;w|f<`-qbN4}yHuPc`@$djlWZd?K{c1kS)pZRQmVLbQ1`L~&J_ z)BKi&Z)K8(m9XPyR_9}W>T1i+$EPm=8>xML(zB;)2u%kF68lVBsI6Jg?>`y9$K!Y1 zJnJug$H41Udgsfm;-_tz6Nq+6L68p0TcJnxkZ3&MAx zXn~Tw6ey{=OfrZuAHr=Ab%-0J3bS)#`ME-VB8W4hQ^N}4y6+R$3}S24EBQg(k?|o= zZ+W(xNmJo=K}-a67DPd3pxA-p2C5hdlwzRO!wJ_{SP(ObV;6(eD!e(&f)olja)ksj zfCU*j4YE#)nAa#YDTE(77?5Z;49eU1BQz>#CyrX%wT^X+>Q<( zQP5Yt@gQDRY8}U_soJ5o`vh8FU#U*K>>t^lc=YkZkDqir?>PPKspos6VAJ7ROWW4j&9mFH_dfhiJx0aT^*~@1fl-8sy)Mba zn*wV-l2b$wrD7e8!SCANQAVK&Xw{>pq*w+x`5PA#@4!z)xroxgD}Gf|R&@k0w_k)n ziQq5LsGw%(o)OqyD>GvCYbAUG`>*bO!yEfe0cvqS_{^j55{EUssS((%T+75ESCql? z7&mS3N_g%-9)XVV)R=g<5b00AiEw1e39h@HTGVZqz7M04lngW~=!Mq0mD$W}4Q>uT z(~eihU#N*cP5oi&d$r@K+VR)rC*#jL`~InBBvnDOP!KW-1-N91LIIvfg+dUagq{Xj zK?0sTKD^lIVdzN^QkmH0>P3-u-V+nz*z16j7wY-@L;5Qi6N!ZV7JfTXMNxLucNKMS z5GmTfkhYUVt>DsMPJMS|>REcalKLqQN$RJI3Y_JRhSYTR?(^ueAJpU*mv*B_JuY?p E50ceQa{vGU diff --git a/hooks/tests/lib/hook-sandbox.sh b/hooks/tests/lib/hook-sandbox.sh new file mode 100644 index 0000000..57f62f1 --- /dev/null +++ b/hooks/tests/lib/hook-sandbox.sh @@ -0,0 +1,101 @@ +# hooks/tests/lib/hook-sandbox.sh — 讓「會寫檔的閘」在測試時寫到暫存區,不寫進 repo。 +# 不是獨立掛的閘(沒進 hooks.json),給 hooks/tests 與 scripts/test-* `source` 用。 +# +# 🔴 為什麼需要這支(inkstone/ISEP#59 comment 4779 第 ① 條,實測重演過): +# main-and-prod-push-guard.sh 擋下「推 main」的同時,會把那次請求寫成一份 +# `/pending-main-push/<誰>--.md`——**那份檔案的語意是 +# 「有一筆推 main 正在等總管裁」**。而測試的測資本來就全是「推 main」, +# 於是每跑一次測試,工作區就多/改幾筆**偽造的待裁決**: +# +# $ git status --short # 跑完測試之後(修之前的實測) +# M pending-main-push/unnamed--ISEP.md ← 被測試覆寫(它是被追蹤的) +# ?? pending-main-push/unnamed--A.md ← 跨 repo 測試建的臨時 repo +# +# 兩個後果,後者比較貴: +# ① `git add -A` 很容易把它們帶進 commit(08-27 那次真的帶進去了,事後才拔掉) +# ② **永遠在響的警報**:總管的迴圈讀這個目錄,讀到的每一筆都該是真的在等他裁。 +# 測試每跑一次就偽造一筆 ⇒ 下一筆真的請求會混在雜訊裡。 +# +# 做法:把整個 `hooks/` 複製到暫存區再跑那支複本。閘算 `pending-main-push` 的位置 +# 靠的是 `$0` 的上一層,所以複本會寫進暫存區,**產品程式碼一行都不用改** +# (不必為了測試在閘上多開一個「寫去哪」的開關——那種開關會變成關掉紀錄的路)。 +# +# 用法: +# . "$(dirname "$0")/lib/hook-sandbox.sh" +# hook_sandbox "$REAL_HOOK" || exit 1 +# G=$HOOK_SANDBOX_HOOK # 之後一律測 $G +# before=$(hook_sandbox_hostsum) # repo 那份 pending-main-push 的指紋 +# ... 跑測試 ... +# hook_sandbox_assert "$before" # 回 0=乾淨且沙盒裡真的有留下請求 + +# hook_sandbox <真跡 hook 的路徑> → 建好沙盒,設三個變數,回 0/1 +# HOOK_SANDBOX_HOOK 沙盒裡那支同名 hook(之後一律測這支) +# HOOK_SANDBOX 沙盒根(`pending-main-push` 會長在這底下) +# HOOK_SANDBOX_HOST 真跡 repo 的 pending-main-push(拿來比對有沒有被弄髒) +# +# 🔴 **刻意不用 `printf` 把路徑印出來讓呼叫端 `G=$(hook_sandbox …)` 接**—— +# 命令替換跑在子殼裡,函式設的那三個變數**回不到呼叫端**。 +# 寫這支的當下就這樣寫,第一次跑的結果是:`HOOK_SANDBOX` 在呼叫端是空的 +# ⇒ hostsum 兩次都回 `NODIR`(相等)、沙盒的請求數當然是 0 +# ⇒ **第一條斷言變成「拿空的比空的」的假綠**。 +# 所以改成「設變數、不印」,而且 hook_sandbox_assert 開頭會擋空值(見下)。 +hook_sandbox() { + _hs_real=$1 + _hs_hooks=$(CDPATH= cd -- "$(dirname -- "$_hs_real")" && pwd) || return 1 + HOOK_SANDBOX=$(mktemp -d) || return 1 + cp -R "$_hs_hooks" "$HOOK_SANDBOX/hooks" || return 1 + HOOK_SANDBOX_HOST="${_hs_hooks%/hooks}/pending-main-push" + HOOK_SANDBOX_HOOK="$HOOK_SANDBOX/hooks/$(basename "$_hs_real")" + [ -f "$HOOK_SANDBOX_HOOK" ] || return 1 +} + +# hook_sandbox_hostsum → 印出真跡 repo 那份 pending-main-push 的指紋(檔名+內容) +# 目錄不存在時印 NODIR——「本來就沒有」跟「空的」要分得出來。 +hook_sandbox_hostsum() { + if [ -d "${HOOK_SANDBOX_HOST:-/nonexistent}" ]; then + find "$HOOK_SANDBOX_HOST" -type f 2>/dev/null | LC_ALL=C sort | while IFS= read -r _f; do + cksum "$_f" 2>/dev/null || printf 'UNREADABLE %s\n' "$_f" + done + else + printf 'NODIR\n' + fi +} + +# hook_sandbox_assert → 印出兩行結果,全過回 0 +# 🔴 兩件都要驗,只驗前者會養出「把紀錄關掉也算通過」的假綠: +# ① repo 那份 pending-main-push 一個位元都沒動(測試沒有偽造待裁決) +# ② 沙盒那份真的多了請求檔(**留請求這件事本身還在做**) +hook_sandbox_assert() { + _hs_before=$1 + # fail-closed:沙盒沒建起來(或變數沒傳到這裡)時,下面兩條會變成「空的比空的」=假綠。 + if [ -z "${HOOK_SANDBOX:-}" ] || [ -z "${HOOK_SANDBOX_HOST:-}" ]; then + printf ' ❌ %-58s\n' "沙盒變數是空的——這兩條斷言等於沒跑,當失敗處理" + HS_PASS=0; HS_FAIL=2; return 1 + fi + _hs_after=$(hook_sandbox_hostsum) + _hs_rc=0; HS_PASS=0; HS_FAIL=0 + if [ "$_hs_before" = "$_hs_after" ]; then + printf ' ✅ %-58s\n' "repo 的 pending-main-push 沒被測試碰過"; HS_PASS=$((HS_PASS+1)) + else + printf ' ❌ %-58s\n' "repo 的 pending-main-push 被測試寫髒了" + printf ' before/after diff:\n' + printf '%s\n' "$_hs_before" > "$HOOK_SANDBOX/.before" + printf '%s\n' "$_hs_after" > "$HOOK_SANDBOX/.after" + diff "$HOOK_SANDBOX/.before" "$HOOK_SANDBOX/.after" | sed 's/^/ /' + _hs_rc=1; HS_FAIL=$((HS_FAIL+1)) + fi + _hs_n=$(find "$HOOK_SANDBOX/pending-main-push" -type f 2>/dev/null | wc -l | tr -d ' ') + if [ "${_hs_n:-0}" -gt 0 ]; then + printf ' ✅ %-58s(%s 筆)\n' "請求有被留下來,只是留在沙盒裡" "$_hs_n"; HS_PASS=$((HS_PASS+1)) + else + printf ' ❌ %-58s\n' "沙盒裡一筆請求都沒有——留請求的機制可能被關掉了" + _hs_rc=1; HS_FAIL=$((HS_FAIL+1)) + fi + return $_hs_rc +} + +hook_sandbox_cleanup() { + case "${HOOK_SANDBOX:-}" in + /*/*) rm -rf "$HOOK_SANDBOX" ;; # 只刪 mktemp 給的那種深路徑,不接受空值/根目錄 + esac +} diff --git a/hooks/tests/main-and-prod-push-guard-cross-repo.test.sh b/hooks/tests/main-and-prod-push-guard-cross-repo.test.sh index fcbec79..fc5f2b3 100755 --- a/hooks/tests/main-and-prod-push-guard-cross-repo.test.sh +++ b/hooks/tests/main-and-prod-push-guard-cross-repo.test.sh @@ -9,10 +9,21 @@ # 路徑務必給絕對路徑——測試會 cd 進臨時建立的 A/B repo 再呼叫它,相對路徑 # 到那時就對不上了(自己撞過一次:exit=127 command not found)。 set -u -G="$1" +REAL="$1" + +# 🔴 測沙盒裡的複本,不測真跡:這支閘擋下推 main 時會把請求寫進 +# `/pending-main-push/`,而這裡每一條測資都是推 main +# ⇒ 直接測真跡,每跑一次就在 ISEP 的工作區偽造幾筆「還沒裁」 +# (實測留下過 `pending-main-push/unnamed--A.md`——A 是這支自己建的臨時 repo, +# inkstone/ISEP#59 comment 4779 第 ① 條)。收尾會驗兩件(見 lib 檔頭)。 +. "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/lib/hook-sandbox.sh" +hook_sandbox "$REAL" || { echo "❌ 沙盒建不起來"; exit 1; } +G=$HOOK_SANDBOX_HOOK +HOSTSUM_BEFORE=$(hook_sandbox_hostsum) + STAMP=/tmp/.main-push-ok WORK=$(mktemp -d) -trap 'rm -rf "$WORK"; rm -f "$STAMP"' EXIT +trap 'rm -rf "$WORK"; rm -f "$STAMP"; hook_sandbox_cleanup' EXIT for d in A B; do git init -q -b main "$WORK/$d" @@ -101,5 +112,9 @@ touch -t "$(date -v-16M +%Y%m%d%H%M.%S 2>/dev/null || date -d '-16 minutes' +%Y% t "16 分鐘前開的戳記 → 已過期,推 B 應擋" "$A" "cd $B && git push origin HEAD:main" 2 rm -f "$STAMP" +echo "── 測試自己不准弄髒工作區(inkstone/ISEP#59)──" +hook_sandbox_assert "$HOSTSUM_BEFORE" +pass=$((pass+HS_PASS)); fail=$((fail+HS_FAIL)) + echo "────── 通過 $pass / 失敗 $fail" [ "$fail" = 0 ] diff --git a/hooks/tests/main-and-prod-push-guard.test.sh b/hooks/tests/main-and-prod-push-guard.test.sh index 414f59b..86d0442 100755 --- a/hooks/tests/main-and-prod-push-guard.test.sh +++ b/hooks/tests/main-and-prod-push-guard.test.sh @@ -1,7 +1,17 @@ #!/usr/bin/env bash # 八向實測 main-and-prod-push-guard.sh # 放在檔案裡跑,因為測試字串本身會觸發「舊版」那支閘(第五次誤攔)。 -G="$1" # 要測的 hook 路徑 +REAL="$1" # 要測的 hook 路徑(真跡) + +# 🔴 不要直接測真跡:這支閘擋下推 main 的同時會把請求寫進 `/pending-main-push/`, +# 而這裡的測資全是推 main ⇒ 每跑一次就在工作區偽造幾筆「還沒裁」(inkstone/ISEP#59)。 +# 改測沙盒裡的複本,閘照原樣跑,請求寫進暫存區。收尾會驗兩件(見 lib 檔頭)。 +. "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/lib/hook-sandbox.sh" +hook_sandbox "$REAL" || { echo "❌ 沙盒建不起來"; exit 1; } +G=$HOOK_SANDBOX_HOOK +trap 'hook_sandbox_cleanup' EXIT +HOSTSUM_BEFORE=$(hook_sandbox_hostsum) + pass=0; fail=0 t() { # t <說明> <指令> <期望 exit> @@ -25,5 +35,9 @@ t "直接推預設分支" 'git push origin ma'"in" 2 t "HEAD:預設分支" 'git push origin HEAD:ma'"in" 2 t "推 master" 'git push -q origin mas'"ter" 2 +echo "── 測試自己不准弄髒工作區(inkstone/ISEP#59)──" +hook_sandbox_assert "$HOSTSUM_BEFORE" +pass=$((pass+HS_PASS)); fail=$((fail+HS_FAIL)) + echo "────── 通過 $pass / 失敗 $fail" [ "$fail" = "0" ] diff --git a/pending-main-push/README.md b/pending-main-push/README.md new file mode 100644 index 0000000..1662b68 --- /dev/null +++ b/pending-main-push/README.md @@ -0,0 +1,16 @@ +# `pending-main-push/` — 被擋下的推 main 請求(本機狀態,不進版控) + +`hooks/main-and-prod-push-guard.sh` 擋下一次推 main 的同時,會把那次請求寫成 +`<誰>--<哪個 repo>.md` 丟進這個目錄:repo/分支/逐筆 commit/改了哪些檔,**原始資料**。 + +> leo 2026-08-12:「**它會問你的意見,所以每個你叫起來的 subagent 都有名字。**」 +> 閘做不到「同步問總管」(不同行程),做得到的是「**當場擋 + 留下是誰要求的**」。 + +## 規約 + +- **一份檔案 = 一筆還沒裁的請求。** 總管裁完就刪掉它——留著代表還沒裁。 +- **這個目錄的內容不進版控**(`.gitignore` 有一段寫了為什麼),只有本檔在版控裡。 +- **測試不准在這裡留東西。** 推 main 的測資本來就都會觸發這支閘, + 所以那幾支測試改成跑 `hooks/tests/lib/hook-sandbox.sh` 建的複本,請求寫進暫存區; + 每支測完都會驗兩件:這裡沒被碰過、而且沙盒裡**真的有**留下請求 + (只驗前者的話,「把留紀錄的功能關掉」也會綠——那是假綠)。 diff --git a/pending-main-push/unnamed--ISEP.md b/pending-main-push/unnamed--ISEP.md deleted file mode 100644 index 3f1741c..0000000 --- a/pending-main-push/unnamed--ISEP.md +++ /dev/null @@ -1,43 +0,0 @@ -# 推 main 的請求:未署名 - -- repo:/Users/youlinhsieh/Documents/tech_projects/ISEP -- 分支:fix/push-guard-target-not-substring -- 時間:2026-08-21 01:28:51 - -- 它想跑的指令: -``` -git push -q origin master -``` - -## 還沒推上去的 commit(原始資料,不是轉述) - -``` -43c328d Merge pull request 'feat/milestone-must-have-due' (#50) from feat/milestone-must-have-due into main -bcb736e Merge pull request 'fix/worksheet-dedup-by-content' (#49) from fix/worksheet-dedup-by-content into main -6772ca6 每個里程碑都要有真的期限,9999 也擋 -1b55512 待驗工作單改用宣稱內容去重,驗過的不再冒出來 -1920d4c Merge pull request '身為 leo,我要雲端 clone 下來就有閘,我才不用先處理憑證' (#48) from fix/b4-real-probe into main -47ed778 改走「直接複製進薄殼 repo」,並修掉一支會偷跑指令的閘 -03d9782 查了官方文件才發現:setup script 根本讀不到環境變數,而 exit 1 會鎖死 session -36d8e05 認證驗證加 timeout:掛住比失敗更糟 -1356372 B4 的探針我自己沒撞過,實撞後發現它根本不會擋 -d306158 Merge pull request '身為 leo,我要雲端 env 檔能直接產在桌面,我才不用去翻隱藏目錄' (#46) from feat/cloud-env-outdir into main -9099c3f make-cloud-env.sh:輸出路徑可指定,且不再對使用者指定的目錄動權限 -5bceb03 v0.3.1 -4e73b8b Merge pull request '身為 leo,我要雲端驗收步驟在閘死掉時真的變紅,我才不會再被三個綠燈騙一次' (#45) from fix/testing-b-section-discriminating into main -291787e TESTING.md B 段整段換掉——舊版在閘全滅時會回綠 -3a95121 雲端零閘的兩個真因:setup 不自驗+沒有 release 撐版本號 (#44) -daa1674 雲端零閘的兩個真因:setup 從不驗證自己+沒有任何 release 撐版本號 -c48495d Merge pull request 'fix(hooks): sdd-guard.sh 修「解析失敗仍照擋、且訊息洩漏 /nonexistent」' (#42) from fix/sdd-guard-path-resolution into main -8718658 fix(hooks): sdd-guard.sh 修「解析失敗仍照擋、且訊息洩漏 /nonexistent」(InkStoneCo#22) -e6d183d Merge pull request '產生雲端 env 設定給 leo 貼(InkStoneCo#14)' (#41) from feat/cloud-env-generator into main -f855d82 產生雲端 env 設定,不要 leo 自己拼湊(InkStoneCo#14) -``` - -## 改了哪些檔 - -``` -``` - ---- -總管裁完請刪掉這個檔——留著代表「還沒裁」。 diff --git a/pending-main-push/unnamed--InkStoneCo.md b/pending-main-push/unnamed--InkStoneCo.md deleted file mode 100644 index 306bf7d..0000000 --- a/pending-main-push/unnamed--InkStoneCo.md +++ /dev/null @@ -1,23 +0,0 @@ -# 推 main 的請求:未署名 - -- repo:/Users/youlinhsieh/Documents/tech_projects/InkStoneCo -- 分支:main -- 時間:2026-08-21 01:28:23 - -- 它想跑的指令: -``` -git push -q origin master -``` - -## 還沒推上去的 commit(原始資料,不是轉述) - -``` -``` - -## 改了哪些檔 - -``` -``` - ---- -總管裁完請刪掉這個檔——留著代表「還沒裁」。 diff --git a/scripts/test-main-and-prod-push-guard.sh b/scripts/test-main-and-prod-push-guard.sh index f0d58d6..c4726f3 100755 --- a/scripts/test-main-and-prod-push-guard.sh +++ b/scripts/test-main-and-prod-push-guard.sh @@ -4,7 +4,19 @@ # 開頭包一層讀取指令)都不准擋;推自己的分支、含子字串但目標不是 main/master # 的分支(如 fix/custom-domain-setup)也不准擋。 cd "$(dirname "$0")/.." || exit 1 -H=hooks/main-and-prod-push-guard.sh + +# 🔴 測沙盒裡的複本,不測真跡(inkstone/ISEP#59 comment 4779 第 ① 條): +# 這支閘擋下推 main 的同時會把請求寫進 `/pending-main-push/`,那份檔案的 +# 語意是「有一筆推 main 正在等總管裁」。這裡的測資本來就都是推 main +# ⇒ 直接測真跡的話,**每跑一次測試就偽造一筆待裁決**(實測:`unnamed--ISEP.md` +# 每跑一次被覆寫一次,而它當時還是被追蹤的 ⇒ `git status` 永遠是髒的)。 +# cwd 仍然留在 repo 根——戳記比對問的是「人站在哪個 repo」,那件事沒有變。 +. hooks/tests/lib/hook-sandbox.sh +hook_sandbox "$PWD/hooks/main-and-prod-push-guard.sh" || { echo "❌ 沙盒建不起來"; exit 1; } +trap 'hook_sandbox_cleanup' EXIT +HOSTSUM_BEFORE=$(hook_sandbox_hostsum) +H=$HOOK_SANDBOX_HOOK + PASS=0; FAIL=0 run(){ # $1=want $2=cmd printf '%s' "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":$(python3 -c 'import json,sys;print(json.dumps(sys.argv[1]))' "$2")}}" \ @@ -31,6 +43,10 @@ run 0 'grep -n "git push" hooks/main-and-prod-push-guard.sh' echo "── 真違規不能因為這次改動而漏擋:戳記過期/戳記綁錯 repo 一樣要擋 ──" run 2 'git push gitea HEAD:main' # 沒有任何戳記檔時 +echo "── 測試自己不准弄髒工作區(inkstone/ISEP#59)──" +hook_sandbox_assert "$HOSTSUM_BEFORE" +PASS=$((PASS+HS_PASS)); FAIL=$((FAIL+HS_FAIL)) + echo echo "$PASS/$((PASS+FAIL)) 通過" [ "$FAIL" -eq 0 ]