# 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 記的「連續四次蓋不出戳記」就是這個形狀。 # # ── 再往下一層:`-f` 根本不吃格式參數,所以連離開碼都不可靠 ──────────── # # 2026-08-28 兩個人量這一格,一個量到 `exit=0`、一個量到 `exit=1`。**兩個都是真的**, # 而分歧本身就是最後一塊拼圖:**GNU 的 `-f` 是 `--file-system`,布林旗標、不接格式字串** # ⇒ `%m` 被當成**另一個檔名運算元** ⇒ 離開碼取決於「cwd 裡有沒有一個叫 `%m` 的檔」: # # A. 沒有(一般情況) → exit 1 ⇒ `||` **會**跑 ⇒ 正確的秒數接在垃圾後面 # B. 剛好有 → exit 0 ⇒ `||` **不會**跑 ⇒ 整包連一個數字都沒有 # # 兩種情況下閘的結果一樣:`MT` 都不是純數字,戳記都作廢。(實測 GNU coreutils 9.4) # # 🔴 **這才是該記住的教訓**:舊寫法的 `||` fallback 救不了, # **不是因為它沒跑,而是因為「跑不跑」根本不由這支腳本決定** # ——它由「cwd 裡有沒有某個檔名」決定。 # 一個**行為取決於 cwd 裡有沒有某個檔**的判斷式,不管跑不跑都是壞的。 # # 🔴 所以修法不是「把順序反過來」而已:改成先 `-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" }