Files
ISEP/docs/governance/cloud-wiring.md
T
claude-code 617315e3dd 把四個雲端接線缺陷的診斷寫下來(inkstone/ISEP#90)
docs/governance/cloud-wiring.md:四個缺陷分成兩種病——
①④ 是「閘的判準寫的是地端才成立的假設」,②③ 是「同一個東西有兩份,雲端跑到舊的」。
每一格都附實測證據與修法,並標明還沒關掉的兩格住在哪張票(ISEP#67/真身 repo)。

TESTING.md 補 A14/A15/A16 三格:怎麼跑、該看到什麼、什麼算失敗。
三支閘被擋下的訊息也一併指向 scripts/gate-ok(原本的手打法保留當等價寫法)。
2026-08-28 00:14:01 +00:00

7.1 KiB
Raw Blame History

雲端 session 的閘為什麼跟地端不一樣(inkstone/ISEP#90

leo 的目標一句話:讓雲端 session 跟地端拿到同一組閘、同一組能用的工具。

雲端不是「地端少幾支閘」,是同一批閘在雲端的行為不一樣。四個實例, 每一個都穩定重現,而且每一個都不會自己喊痛——這才是它們活這麼久的原因。


四個缺陷,兩種病

缺陷
未推警察每個雲端 session 都誤攔 判準在雲端不成立
scripts/ticket 在雲端拿不到 token 跑到的是另一份(舊複本)
已刪除的機制在雲端重生 載到的是另一版0.3.9 vs 0.9.0
總管的「我確認過了」出口打不開 判準在雲端不成立(平台差異)

①④ 是同一種病:閘的判準寫的是地端才成立的假設。 ②③ 是同一種病:同一個東西有兩份,而雲端跑到的是舊的那份


① 未推警察:判準是「有沒有 upstream」,而雲端的分支天生沒有

雲端 session 開出來的工作分支沒有 upstream,內容卻等於遠端 main

薄殼 HEAD        = 081c547d90dc2d80692485af083ca1f71f2094f2
GitHub 遠端 main = 081c547d90dc2d80692485af083ca1f71f2094f2   ← 同一顆

⇒ 每個雲端 session、每次收工都被攔一次(2026-08-27 一個 session 五次全是誤報)。

修法:判準改成「遠端有沒有這顆 commit」(git ls-remote)。 本機的 remote-tracking ref 只答得準「有」——08-28 實測那份 origin/main 落後遠端 4 天 ——所以答「沒有」的時候才打網路。三態:有/沒有/問不到;問不到一律不報。

順手補的$CLAUDE_PROJECT_DIR 在雲端是薄殼根,真身在 $TOP/InkStoneCo/。 舊的掃描清單四個路徑在雲端一個都不存在 ⇒ 真身有東西沒推,這支閘一輩子不會知道。 同一支閘在雲端既亂叫、又看不到該看的地方。


scripts/ticket:跑到的是舊複本,不是 plugin 那一份

  • ISEP 的 scripts/ticket 早在 2026-08-20 就修好了(掃所有 remote 環境變數 fallback
  • 但雲端 cwd 是真身,那裡有一份 InkStoneCo/scripts/ticket舊複本, 取 token 邏輯還停在「只認名叫 gitea 的 remote」,而 bootstrap.sh 把 Gitea 設成 origin
  • ⇒ 雲端一律死在「拿不到 gitea token」

🔴 後果比「一支腳本壞了」嚴重scripts/ticket 是開票/留言的正門, 它一壞,人就繞過去直接打 Gitea API——而那正是 ticket-api-bypass-guard.sh 在防的事一道閘把人逼去走它自己禁止的那條路,那道閘就是在製造違規。

ISEP 這半的修法:信標在 session 開頭就點名「專案裡有 ISEP 腳本的舊複本, 而且內容不同」。判準不是檔名一樣,是檔名一樣而內容不同——同步過的複本不吵。 真身那半(把那份複本同步或刪掉)不在 ISEP,要在 inkstone/InkStoneCo 修。


③ 已刪除的機制在雲端重生

ISEP main .claude-plugin/plugin.json  →  0.9.0
雲端實際載入                            →  0.3.9      ← 差 7 個 release

0.3.9 裡還活著兩支在 v0.9.0 已整支刪除的 hook ⇒ .claude/pending-verification/ 被清掉之後又長回來。一個看不見的版本落差,會讓已經刪掉的機制在別人的工作區裡復活。

傳輸那半是 inkstone/ISEP#67(新版到不到得了手上),本票不重複那件。 ISEP 這半能做的是讓它不再看不見:信標匿名讀 ISEP main 的 plugin.json (D20 判準下屬於「讀」,不需開閘),落後就講清楚差幾版、怎麼重拍快照; 同時掃 .claude/ 底下這一份 plugin 的 hooks/scripts 一個字都沒提到的目錄, 點名它們是殘骸。判準是「plugin 現在還認不認得它」,不是關鍵字黑名單 leo 2026-08-17 已證明那條路 8 次誤攔、0 次正確攔截)。


④ 「總管可以放行」的門,在雲端是焊死的

三支閘(prod-write-guardmain-and-prod-push-guardstage-before-prod-guard 都是「擋下來、但總管看過就能放行」。它們判斷戳記新不新都用同一行:

MT=$(stat -f %m "$STAMP" 2>/dev/null || stat -c %Y "$STAMP" 2>/dev/null || echo 0)

macOSBSD stat)上 -f %m 就是 mtime,對的。 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 吃掉錯誤訊息、|| 把正確的秒數接在那堆垃圾後面case "$NOW$MT" in *[!0-9]*) return 1 必然命中 ⇒ 在 Linux(=每一個雲端 session)上,那三支閘的戳記永遠不會被接受。

🔴 後果:那三支閘在雲端等於純擋。總管照著閘自己印的指示做, 做幾次都打不開,而閘不會告訴他門是壞的

為什麼活這麼久:既有的三支測試(29/16/10 條)只驗了「擋得住」, 一條都沒驗過「放得開」。⇒ 這正是「閘的另外一半從來沒被測過」的代價。

修法hooks/lib/mtime.sh —— 先 -c %YGNU)再 -f %mBSD), 每一步都驗它是不是純數字(這個 bug 的成因正是「命令失敗了卻還是印了東西」, 只看離開碼會再被騙一次)。

附帶:逃生口收斂成一個入口 scripts/gate-ok

原本每支閘的門長得都不一樣,而且藏在被擋下的那則訊息裡:

touch /tmp/.prod-write-ok
git rev-parse --show-toplevel > /tmp/.main-push-ok
touch /tmp/.solo-ok-<session_id>
…

兩個後果:記不住(抄錯一個字門就打不開),以及沒有穩定形狀可以事先放行 ——.claude/settings.json 的 allow 只能逐條完全比對(現場真的寫著 Bash(touch /tmp/.prod-write-ok) 這種一行),多一個 &&、換一個 session id 就落在規則之外,然後由權限層自己判斷。

bash "$CLAUDE_PLUGIN_ROOT/scripts/gate-ok" <閘名> [參數]一個名字、一種形狀, 一條前綴規則涵蓋全部,以後新增閘不必再動一次設定。

🔴 它沒有弱化任何一道閘:蓋的是同一個檔、同一種語意——單次用完即丟、綁 repo、 綁 session、有效期全部沒動。換掉的只有「怎麼蓋」。 hooks/tests/gate-ok.test.sh 的 ⑥⑦⑧ 三條就是在守這件事(那三條性質是 08-11、08-12 兩次真的被穿透之後才補上的)。


還沒關掉的那兩格(不屬於 ISEP

缺口 住在哪
雲端載到的版本追上 ISEP main inkstone/ISEP#67
InkStoneCo/scripts/ticket 這份舊複本 inkstone/InkStoneCo(真身)

ISEP 這一側能做的是讓它們不再是看不見的:兩件現在都會在 session 開頭被信標點名。