補上 stat -f 診斷的最後一層:連離開碼都不可靠(inkstone/ISEP#90 ④)

總管自己驗這一格量到 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 有沒有某個檔的判斷式,不管跑不跑都是壞的。
⇒ 所以修法不能只是「把順序反過來」,**每一步都要驗它是不是純數字**
(lib/mtime.sh 本來就是這樣寫的,現在把理由寫進去了)。

gate-ok 測試補第 ⑰ 條守這一層:cwd 裡有一個叫 `%m` 的檔時,file_mtime 仍要回純數字。
16 → 17 條,TESTING.md 的 A16 一併更新。
This commit is contained in:
claude-code
2026-08-28 00:27:51 +00:00
parent 3a1c3d4969
commit 4322deb23a
4 changed files with 77 additions and 4 deletions
+7 -3
View File
@@ -131,11 +131,11 @@ bash hooks/tests/prod-write-guard.test.sh hooks/prod-write-guard.sh
- 「卡點二」2 條任一紅 ⇒ 「談論它」又被當成「執行它」(同款第八次), - 「卡點二」2 條任一紅 ⇒ 「談論它」又被當成「執行它」(同款第八次),
或是剝了內文之後連真的部署都放行了 或是剝了內文之後連真的部署都放行了
### A16 — 「總管可以放行」那道門真的打得開:16 ### A16 — 「總管可以放行」那道門真的打得開:17
``` ```
bash hooks/tests/gate-ok.test.sh bash hooks/tests/gate-ok.test.sh
``` ```
**該看到**`通過 16 條,失敗 0 條` **該看到**`通過 17 條,失敗 0 條`
**它在守什麼**inkstone/ISEP#90 ④,2026-08-28 實查):三支閘 **它在守什麼**inkstone/ISEP#90 ④,2026-08-28 實查):三支閘
`prod-write-guard``main-and-prod-push-guard``stage-before-prod-guard` `prod-write-guard``main-and-prod-push-guard``stage-before-prod-guard`
@@ -151,6 +151,10 @@ bash hooks/tests/gate-ok.test.sh
08-11、08-12 兩次真的被穿透之後才補上的,不准為了「好放行」而鬆掉 08-11、08-12 兩次真的被穿透之後才補上的,不准為了「好放行」而鬆掉
- ⑫ 紅 ⇒ `gate-ok` 在解不出 repo 時留下了一枚註定打不開的空戳記, - ⑫ 紅 ⇒ `gate-ok` 在解不出 repo 時留下了一枚註定打不開的空戳記,
那會讓人以為門開了(而空戳記本身就是 08-12 那把萬用鑰匙的形狀) 那會讓人以為門開了(而空戳記本身就是 08-12 那把萬用鑰匙的形狀)
- ⑰ 紅 ⇒ 判準又變回「行為取決於 cwd 裡有沒有一個叫 `%m` 的檔」。
這條是這個 bug 的最後一層:**GNU 的 `-f` 是布林旗標,`%m` 被當成另一個檔名運算元**
⇒ 有那個檔就 `exit 0``||` 連跑都不跑。所以修法不能只是「把順序反過來」,
**每一步都要驗它是不是純數字**
⚠️ 這支會真的寫 `/tmp` 的戳記檔(那是閘寫死的路徑)。**不要在「已經蓋好戳記正要推東西」 ⚠️ 這支會真的寫 `/tmp` 的戳記檔(那是閘寫死的路徑)。**不要在「已經蓋好戳記正要推東西」
的當下跑它**——它會把那枚戳記洗掉。 的當下跑它**——它會把那枚戳記洗掉。
@@ -389,7 +393,7 @@ B2(信標那行)/B3(setup 輸出)/B4(閘的訊息)三個畫面
| **A10 人閘警察準度** | 總管 | ✅ 9/9,連跑三次(2026-08-26),A 群誤攔 0 | | **A10 人閘警察準度** | 總管 | ✅ 9/9,連跑三次(2026-08-26),A 群誤攔 0 |
| **A11 派工單只剩票號** | 總管 | ✅ 19/192026-08-27 | | **A11 派工單只剩票號** | 總管 | ✅ 19/192026-08-27 |
| **A12 留言身份欄(兩道門)** | 總管 | ✅ 11/112026-08-27 | | **A12 留言身份欄(兩道門)** | 總管 | ✅ 11/112026-08-27 |
| **A16 放行的門真的打得開** | 總管 | ✅ 16/162026-08-28inkstone/ISEP#90 | | **A16 放行的門真的打得開** | 總管 | ✅ 17/172026-08-28inkstone/ISEP#90 |
| **A17 未推警察不誤攔雲端分支** | 總管 | ✅ 10/102026-08-28inkstone/ISEP#90 | | **A17 未推警察不誤攔雲端分支** | 總管 | ✅ 10/102026-08-28inkstone/ISEP#90 |
| **A18 信標會報雲端接線缺陷** | 總管 | ✅ 14/142026-08-28inkstone/ISEP#90 | | **A18 信標會報雲端接線缺陷** | 總管 | ✅ 14/142026-08-28inkstone/ISEP#90 |
| A7 plugin 裝得起來 | 總管 | ✅ | | A7 plugin 裝得起來 | 總管 | ✅ |
+47
View File
@@ -102,6 +102,53 @@ stat: cannot read file system information for '%m': No such file or directory
`case "$NOW$MT" in *[!0-9]*) return 1` 必然命中 `case "$NOW$MT" in *[!0-9]*) return 1` 必然命中
**在 Linux(=每一個雲端 session)上,那三支閘的戳記永遠不會被接受。** **在 Linux(=每一個雲端 session)上,那三支閘的戳記永遠不會被接受。**
### 再往下一層:`-f` 根本不吃格式參數,所以連離開碼都不可靠
總管 2026-08-28 自己驗這一格時量到 `exit=0`,而我量到 `exit=1`
**兩個都是真的**,而分歧本身就是這個 bug 最後一塊拼圖:
**GNU 的 `-f` 是 `--file-system`,它是布林旗標、不接格式字串**
`%m` 不是格式,它被當成**另一個檔名運算元** ⇒ 離開碼取決於
「當前目錄裡有沒有一個叫 `%m` 的檔」:
```
$ cd /tmp/statprobe && touch .mt
# A. 沒有名為 %m 的檔(一般情況)
$ stat -f %m .mt ; echo "exit=$?"
stat: cannot read file system information for '%m': No such file or directory
File: ".mt" … ← 檔案系統資訊照樣印到 stdout
exit=1 ← `||` **會**跑 ⇒ 正確的秒數接在那堆垃圾後面
# B. 剛好有一個叫 %m 的檔
$ touch '%m' && stat -f %m .mt ; echo "exit=$?"
exit=0 ← `||` **不會**跑 ⇒ 整包連一個數字都沒有
```
(實測環境:`stat (GNU coreutils) 9.4`
**兩種情況下閘的結果一模一樣**——`MT` 都不是純數字,
`case "$NOW$MT" in *[!0-9]*) return 1` 都必然命中,戳記都作廢:
```
情況 A:非數字 ⇒ return 1 ⇒ 戳記作廢
情況 B:非數字 ⇒ return 1 ⇒ 戳記作廢
```
🔴 **這一層才是真正該記住的教訓**:舊寫法的 `||` fallback 之所以救不了,
**不是因為它沒跑,而是因為「跑不跑」根本不由這支腳本決定**
——它由「當前目錄裡有沒有一個叫 `%m` 的檔」決定。
一個**行為取決於 cwd 裡有沒有某個檔名**的判斷式,不管跑不跑都是壞的。
⇒ 所以 `lib/mtime.sh` 的修法不是「把順序反過來」而已,是
**每一步都驗它是不是純數字**:這個 bug 的成因正是「命令失敗了卻還是印了東西」,
**只看離開碼會再被騙一次**
📌 這一格的實害(總管 2026-08-28 原話):「我今天為了發一則通知,
用了兩種方式蓋 `prod-write-ok` 都無效,一度以為是權限問題。」
**閘不會告訴你門是壞的**,所以人會往錯的方向查(權限、classifier、設定),
而根因在閘自己身上。
🔴 **後果**:那三支閘在雲端**等於純擋**。總管照著閘自己印的指示做, 🔴 **後果**:那三支閘在雲端**等於純擋**。總管照著閘自己印的指示做,
做幾次都打不開,**而閘不會告訴他門是壞的**。 做幾次都打不開,**而閘不會告訴他門是壞的**。
+17 -1
View File
@@ -29,7 +29,23 @@
# 總管照著閘自己印的指示做,做幾次都打不開,而閘不會告訴他門是壞的。 # 總管照著閘自己印的指示做,做幾次都打不開,而閘不會告訴他門是壞的。
# inkstone/InkStoneCo#99 記的「連續四次蓋不出戳記」就是這個形狀。 # inkstone/InkStoneCo#99 記的「連續四次蓋不出戳記」就是這個形狀。
# #
# 🔴 順序反過來就好,但不能只靠順序:改成先 `-c %Y`(GNU)再 `-f %m`BSD), # ── 再往下一層:`-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 的成因正是 # 而且**每一步都驗它是不是純數字**——因為這個 bug 的成因正是
# 「命令失敗了卻還是印了東西出來」,只看離開碼會再被騙一次。 # 「命令失敗了卻還是印了東西出來」,只看離開碼會再被騙一次。
file_mtime() { file_mtime() {
+6
View File
@@ -76,6 +76,12 @@ M=$(file_mtime "$TMP/probe")
case "$M" in ''|*[!0-9]*) R=bad ;; *) R=ok ;; esac case "$M" in ''|*[!0-9]*) R=bad ;; *) R=ok ;; esac
ok "⑮ file_mtime 回純數字(舊寫法在 Linux 上會回一整段文字)" "$R" ok ok "⑮ file_mtime 回純數字(舊寫法在 Linux 上會回一整段文字)" "$R" ok
ok "⑯ 檔案不存在 → 回 0,不是空字串" "$(file_mtime "$TMP/nope")" 0 ok "⑯ 檔案不存在 → 回 0,不是空字串" "$(file_mtime "$TMP/nope")" 0
# 🔴 ⑰ 守的是這個 bug 的最後一層:舊寫法的行為**取決於 cwd 裡有沒有一個叫 `%m` 的檔**
# GNU 的 -f 是布林旗標,`%m` 被當成另一個檔名運算元)。有 ⇒ exit 0 ⇒ `||` 連跑都不跑。
# 一個行為取決於 cwd 有沒有某個檔的判斷式,不管跑不跑都是壞的。
M2=$( cd "$TMP" && touch '%m' && file_mtime "$TMP/probe" )
case "$M2" in ''|*[!0-9]*) R2=bad ;; *) R2=ok ;; esac
ok "⑰ cwd 裡有一個叫 %m 的檔 → 仍要回純數字" "$R2" ok
echo echo
printf '通過 %s 條,失敗 %s 條\n' "$PASS" "$FAIL" printf '通過 %s 條,失敗 %s 條\n' "$PASS" "$FAIL"