3e83a4b4f3
三支閘(prod-write-guard/main-and-prod-push-guard/stage-before-prod-guard)
判斷戳記新不新,都寫成這一行:
MT=$(stat -f %m "$STAMP" 2>/dev/null || stat -c %Y "$STAMP" 2>/dev/null || echo 0)
macOS(BSD stat)上 `-f %m` 就是 mtime,對的。
**GNU coreutils 的 `-f` 是「顯示檔案系統資訊」**,而且它一邊回非零、
一邊往 stdout 吐一整段區塊 ⇒ `||` 接上的秒數被那段文字污染
⇒ 下一行的 `case "$NOW$MT" in *[!0-9]*) return 1` 必然命中
⇒ **在 Linux 上那三支閘的戳記永遠不會被接受。**
後果不是少一個便利功能:那三支閘都是「擋下來、但總管看過就能放行」,
而放行那道門在雲端打不開 ⇒ **它們在雲端等於純擋**,
總管照著閘自己印的指示做,做幾次都打不開,閘也不會告訴他門是壞的
(inkstone/InkStoneCo#99 那次「連續四次蓋不出戳記」就是這個形狀)。
· hooks/lib/mtime.sh:先 `-c %Y`(GNU)再 `-f %m`(BSD),**每一步都驗是不是純數字**
——這個 bug 的成因正是「命令失敗了卻還是印了東西」,只看離開碼會再被騙一次。
四支用到的檔各自帶一份 inline fallback:這道門不能因為少一個檔案就再關上一次。
· scripts/gate-ok:把散在各閘訊息裡的七種逃生口收斂成一個名字、一種形狀
(`gate-ok <閘名> [參數]`)。原因是逃生口原本是「臨時湊出來的 Bash 指令」,
而臨時湊的指令沒有穩定形狀可以事先放行——settings.json 的 allow 只能逐條完全比對,
多一個 `&&`、換一個 session id 就落在規則外。它蓋的戳記與閘原本認的完全同一個檔、
同一種語意(單次、綁 repo、綁 session、有效期都沒動),換掉的只有「怎麼蓋」。
測試 hooks/tests/gate-ok.test.sh:16/16。既有的三支測試(29/16/10)不受影響。
拿 main 跑同一份:②⑤(門打得開)紅、⑥⑦⑧(門沒變寬)全綠
⇒ 這支補的正是既有測試從來沒驗過的那半邊。
42 lines
2.4 KiB
Bash
42 lines
2.4 KiB
Bash
# hooks/lib/mtime.sh — 「這個檔幾點被動的」,跨 macOS/Linux 都問得出來。
|
||
# 不是獨立掛的閘(沒進 hooks.json),給那幾支用 /tmp 戳記的閘 `source` 用。
|
||
#
|
||
# ══ 為什麼要有這支(inkstone/ISEP#90 ④,2026-08-28 實查)═══════════════
|
||
#
|
||
# 三支閘(prod-write-guard、main-and-prod-push-guard、stage-before-prod-guard)
|
||
# 的戳記檢查都寫成這一行:
|
||
#
|
||
# MT=$(stat -f %m "$STAMP" 2>/dev/null || stat -c %Y "$STAMP" 2>/dev/null || echo 0)
|
||
#
|
||
# 在 macOS(BSD stat)上它是對的:`-f %m` 就是 mtime。
|
||
# 在 **Linux(GNU coreutils)上 `-f` 是「顯示檔案系統資訊」**,而且它
|
||
# **一邊回非零、一邊往 stdout 吐一整段檔案系統的區塊**:
|
||
#
|
||
# $ stat -f %m /tmp/.probe
|
||
# stat: cannot read file system information for '%m': No such file or directory ← stderr
|
||
# File: "/tmp/.probe" ← stdout
|
||
# ID: 0 Namelen: 255 Type: ext2/ext3
|
||
# …
|
||
#
|
||
# ⇒ `2>/dev/null` 把錯誤訊息吃掉、`||` 接著跑 `stat -c %Y` 把正確的秒數**接在那堆垃圾後面**
|
||
# ⇒ `MT` 變成「一段多行文字+一個數字」
|
||
# ⇒ 閘下一行的 `case "$NOW$MT" in *[!0-9]*) return 1` 一定命中
|
||
# ⇒ **在 Linux 上,那三支閘的戳記永遠不會被接受。**
|
||
#
|
||
# 🔴 這件事的後果不是「少一個便利功能」:
|
||
# 那三支閘都是「擋下來,但**總管看過就可以放行**」的設計。
|
||
# 放行的那道門在雲端(Linux)打不開 ⇒ **它們在雲端等於純擋**,
|
||
# 總管照著閘自己印的指示做,做幾次都打不開,而閘不會告訴他門是壞的。
|
||
# inkstone/InkStoneCo#99 記的「連續四次蓋不出戳記」就是這個形狀。
|
||
#
|
||
# 🔴 順序反過來就好,但不能只靠順序:改成先 `-c %Y`(GNU)再 `-f %m`(BSD),
|
||
# 而且**每一步都驗它是不是純數字**——因為這個 bug 的成因正是
|
||
# 「命令失敗了卻還是印了東西出來」,只看離開碼會再被騙一次。
|
||
file_mtime() {
|
||
_fm=$(stat -c %Y "$1" 2>/dev/null || true)
|
||
case "${_fm:-}" in ''|*[!0-9]*) _fm=$(stat -f %m "$1" 2>/dev/null || true) ;; esac
|
||
case "${_fm:-}" in ''|*[!0-9]*) _fm=$(python3 -c 'import os,sys; print(int(os.path.getmtime(sys.argv[1])))' "$1" 2>/dev/null || true) ;; esac
|
||
case "${_fm:-}" in ''|*[!0-9]*) _fm=0 ;; esac
|
||
printf '%s' "$_fm"
|
||
}
|