Files
ISEP/docs/TESTING.md
T
Claude Code da9bd53cae 沒人會叫的事會自己叫+讀不完的必讀檔要被整理(inkstone/ISEP#93、inkstone/ISEP#89)
兩張票放同一條分支:都是「該發生卻不會自己發生的事」,觸發點都在 session 邊界。

## inkstone/ISEP#93 —— 逾期和掛著沒人接的事會主動叫

- scripts/isep-nag        撈三種沒人會叫的事(逾期 milestone/等 leo 的票/掉在地上的棒子)
- scripts/isep-notify     發 Telegram,而且**發不出去的時候不會安靜**
- hooks/overdue-nag-guard.sh  SessionStart 跑一次(不輪詢、不 fan-out、不掛 Actions)
- hooks/tests/overdue-nag.test.sh  35 條,全程離線

實跑撈得出票上點名的那五個逾期 milestone(08-24 三個、08-26 兩個)。
沒東西可報時會說「查過了,沒有」——安靜跟壞掉長得一模一樣。

🔴 工單補的那個限制(今天實測出來的)已經處理:
「能不能發得出去」取決於這個 session 載到的 ISEP 是哪一版
(prod-write-guard v0.10.0 才認得出 notify_leo 不是部署,而 hook 註冊路徑
在 session 啟動當下就寫死了)。所以 isep-notify 會**先拿真的要送的那一則
去問這個 session 註冊的那支閘**,把判定寫成檔(誰都查得到),然後:
  · 放行 ⇒ 送,並驗內層 data.data.ok(外層 200 不算送到)
  · 會擋 ⇒ **不繞路**,改貼回票上並把原文印在眼前
用 git 歷史裡的真跡(c263866 那一版閘)測過「會擋」那條路。

 實測發現通道本身現在是斷的:實例上找不到 notify_leo 工作流(404)。
   不是閘、不是網路、不是金鑰。詳情與修法寫在 docs/TESTING.md 最後一段。
   退路兩次都走通了(inkstone/ISEP#93 comment 5182、5184)。

## inkstone/ISEP#89 —— wiki 太長時有人整理

- scripts/wiki-compress   audit/plan/apply/verify/bench 五個動詞
- hooks/wiki-size-guard.sh  SessionStart 點名太長的檔;寫檔時擋「沒走流程的壓縮」
- hooks/tests/wiki-compress.test.sh  25 條,全程離線、不碰真的 wiki

設計上最重要的一條:**只搬不改**,正文一個字都不動。
「合併同類、濃縮成一行」要重寫正文,而重寫的當下沒有人會發現弄丟了什麼。
所以機器只做「搬 + 目錄 + 標 ×N/↻」,合併留給人。

拿現在的 mistakes.md 複本實壓過(不動真的 wiki):
  7,681 行 → 1,199 行
  260 條一條都沒少(verify 用內文雜湊逐條對帳)
  80 個查詢命中率 80/80,定位成本 2,956 → 131 行,**快 22.5 倍**(bench)
verify 反向測過:真的弄丟一條時它抓得到(抓不到的對帳表比沒有更糟)。

## 盤點數字

在這棵樹上當場數的,不是拿上一版加減推的:
  ls hooks/*.sh | wc -l              → 55(was 53)
  grep -c '"command":' hooks.json    → 71(was 68)
plugin.json/README/hooks-inventory 三處同步改。
**版本號沒動**(0.11.0),待總管定版。
2026-08-28 01:04:16 +00:00

465 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ISEP 測試手冊
> leo 2026-08-20:「**你交出版本測試了嗎?你要測試無誤才叫我測試,
> 如果雲端不能測試也要提供 test cases 讓我開啓雲端測試**」。
>
> 規約:每一格都要有「**怎麼跑/該看到什麼/什麼算失敗**」三件。
> **沒跑過的格子一律標空白,不准標綠。**
---
## 先讀:改了 ISEP 卻沒發版,改動到不了任何人手上
2026-08-20 實撞:新增一支 hook 併進 `main`,然後跑 `claude plugin update isep@inkstone`
→ 回「**已是最新版 (0.2.0)**」,新 hook **沒有進到安裝的那一份**
原因:`plugin update` 比的是 **`plugin.json` 的版本號,不是內容**。
**版本沒動 = 更新是 no-op = 本機與雲端又各自停在不同內容上**(就是 `InkStoneCo#57` 的病)。
**所以:任何要生效的改動,都必須跟著一個新版本號。這不是儀式,是傳輸機制本身。**
---
## A. 總管自己要跑完的(交給 leo 之前)
### A1 — plugin manifest 合法
```
claude plugin validate .
```
**該看到**`✔ Validation passed`,不帶 warning。
**失敗**:任何 error;或有 warning 卻沒處理。
### A2 — 版本三處一致
```
bash scripts/check-version-consistency.sh
```
**該看到**`✅ 版本一致:plugin.jsonX.Y.Z,最新 tagvX.Y.ZREADME 沒有自行宣告版本。`
**失敗**exit 1;或 README 又出現寫死的版本號。
### A3 — 打 tag 的閘:擋得住,也放得過
```
bash scripts/test-release-tag-guard.sh
```
**該看到**`3/3 通過`1 個該擋、2 個不該擋)。
**失敗**:該擋的放行(假綠);或不該擋的被擋——**誤攔比漏擋更該修**,誤攔會懲罰謹慎。
### A4 — 新增 Gitea 東西的側門閘:24 條
```
bash scripts/test-ticket-api-bypass-guard.sh
```
**該看到**`24/24 通過`(前 13 條是 v1 的開票案例;後面是 inkstone/ISEP#72
擴大範圍後補的:隱式/小寫 POST、milestonelabelPR、org 端點、以及一條打
真實 Gitea 網路重演 Arcrun#100 的案例——這台機器的 remote 沒帶憑證時會印
`⏭️ SKIP`,不算失敗,但也不算驗過)。
**失敗**:任何一條不符,特別看「不該擋」那幾條——誤攔比漏擋更該修。
### A13 — 搜尋戳記要證明「看過」,不是「跑過」:17 條
```
bash scripts/test-ticket-where-seen-guard.sh
```
**該看到**`通過 17 條,失敗 0 條`。全程離線(`TICKET_HOST` 指到連不上的位址:
離開碼 2 =被閘擋、離開碼 1 =閘全放行走到網路才炸),**不打真實 Gitea、不留測試票**。
**它在守什麼**inkstone/ISEP#72 → comment 48732026-08-27 實犯):
`ticket where … >/dev/null` 之後 `ticket new`——戳記寫成功了,命中的 72 張一眼沒看,
於是開出 `arcrun-rag#147`,而第一名 `arcrun-rag#104` 是同一件事、已經開了 13 天。
**失敗**
- 「★」那兩條紅 ⇒ 今天這個形狀會再發生一次(尤其第二條:先寫好 `--not-a-comment`
理由就能閉著眼睛開票)
- 「不該擋」3 條任一紅 ⇒ **誤攔**,這比漏擋嚴重——沒命中也吵、看過了還吵,
人就學會忽略它
- 最後那組 `fd_is_devnull` 紅 ⇒ 判準從「fstat 問得出來的事實」滑回猜文字
### A11 — 討論串裡的任務要長成子票:22 條
```
bash scripts/test-comment-carries-task-guard.sh
```
**該看到**`22/22 通過`
**失敗**
- 「該擋」6 條任一紅 ⇒ 08-26 那則真的掉了的留言形狀(「等雲端那半出貨才驗得了」)會漏抓
- 「不該擋」10 條任一紅 ⇒ **誤攔,這比漏擋嚴重**——每次留言都被擋,人就學會忽略它
- 最後兩條(`-F <檔>`)紅 ⇒ 內文放在檔案裡時閘看不到,等於走 `ticket say` 就自動繞過
### A12 — 收工要把棒子交回來:10 條
```
bash scripts/test-baton-handback-guard.sh
```
**該看到**`10/10 通過`。**用 fixture 跑,不打網路、不在票池留下測試票**。
**失敗**
- 「該報」5 條任一紅 ⇒ 指派/tag/下一步缺哪一格抓不到,08-26 那次掉棒的狀態會靜靜通過
- 「不該報」4 條任一紅 ⇒ 每條線收工都被念一次,警報會被學會忽略
- 「票已關」那條紅 ⇒ 棒子已經到終點還在催,那是最典型的假警報
### A14 — PR 一定要有結論:52 條
```
bash hooks/tests/pr-verdict-guard.test.sh
```
**該看到**`52 通過 / 0 失敗`。全程離線(走 `PR_VERDICT_FIXTURE`),
**不打 Gitea、不開測試 PR、不留任何測試票**;狀態檔走 `PR_VERDICT_STATE_DIR`
**它在守什麼**`inkstone/ISEP#81`2026-08-27 實查):最久的三個 open PR 躺了兩星期
`inkstone/arcrun-rag#91` 14 天、`inkstone/Arcrun#116` 14 天、`inkstone/Arcrun#104` 15 天),
而 v0.9.0 的 46 支閘**一支都沒有在管 PR**。票看起來是「已交付」,
東西卻沒進 main ⇒ 沒進版本 ⇒ **leo 手上永遠不會出現它**
**失敗**
- B 群(⑨⑩⑪,票上那三個真跡)任一紅 ⇒ 今天這個形狀會再發生一次
- C 群 ⑭⑰ 紅 ⇒ 驗收條件 1/3 沒過:「放著不管」抓不到,或「merge 了分支還在」放行
- **A 群任何一條紅 ⇒ 誤攔,這比漏擋嚴重**。②③特別重要:
指派給人/掛 Human 之後還在點名,就是 ISEP v0.6.0 divergence §B4 記過的那個坑
(「`s/review` 佇列非空即 block」⇒ 佇列本來就不會空 ⇒ **總管永遠停不下來**
- E 群 ㉙ 紅 ⇒ 閘訊息裡承諾的出路(「把它指派給那個人,本閘立刻不再點名它」)是假的
- D 群 ㉓ 紅 ⇒ 判準從「識別碼比對」滑成「只要提到票號就放行」,閘會被任何一句話關掉
> 📌 **真跡不只在 fixture 裡跑過**:同一支閘打真實 Gitea 也驗過,
> 一次點名 7 個沒有結論的 PR,含票上那三個(15/14/14 天)。
> 指令:`printf '{"session_id":"x","transcript_path":"/nonexistent"}' | bash hooks/pr-verdict-guard.sh`
> ——**唯讀,不改動任何 PR**。
### A15 — 給結論的那支工具自己說得出要打哪幾通 API
```
python3 scripts/pr-verdict list
python3 scripts/pr-verdict merge inkstone/ISEP#71 --dry-run
```
**該看到**`list` 印出 open PR 與「有沒有人被指派」;`merge --dry-run` 印出
`[dry-run] POST …/pulls/71/merge``[dry-run] DELETE …/branches/<分支>`**一通都不真的送出**。
**失敗**`--dry-run` 底下出現真實的寫入結果 ⇒ 這支工具沒辦法在按下去之前被檢查。
### A14 — 每一則回覆都自己說出拖了多久:20 條
```
bash hooks/tests/countdown-guard.test.sh
```
**該看到**`通過 20 條,失敗 0 條`。**全程離線**:時鐘用 `ISEP_COUNTDOWN_NOW` 定住、
狀態走 `ISEP_COUNTDOWN_STATE_DIR`,不打網路、不碰 `$HOME`
**它在守什麼**`inkstone/ISEP#63`leo 2026-08-27):
「前面說過每個回覆要戴上已經花了總時長,**這為什麼沒出現?**」
「這應該寫在 ISEP,隨時看自己拖了多久」——重點在後面那句:
**不是要總管記得戴,是要它長在機器上。** 總管當時答「現在開始戴」,
而那正是這條規則第一次失效的方式。
**判準**:一個新開的 session,什麼都沒被交代,回覆裡照樣有時長。
**失敗**
- A 群(注入)任何一條紅 ⇒ 那一行算錯了。**④ 特別看**:期望「已過 2 小時 13 分」,
實得就要是同一個字串——這格就是票上驗收第 2 條「數字對得上真實經過的時間」
- ⑤ 紅 ⇒ 連續幾則不是遞增(歸零或亂跳),票上驗收第 3 條
- ⑦ 紅 ⇒ 過了收工線卻悄悄換算成明天。**超時消失 = 這個東西的意義沒了**
- ⑨ 紅 ⇒ 沒有 milestone 快取時編了一個日期出來(寧可少一段,不准編)
- B 群(該擋)紅 ⇒ 「注入了但模型沒照做」這個主要失效模式沒有被接住
- **C 群(不該擋)任何一條紅 ⇒ 誤攔,這比漏擋嚴重**——每一回合都被念一次,
人就學會忽略它,那它就等於不存在(本 repo 心法第 2 條)
🔴 **跑測試前先確認 `CLAUDE_CODE_CHILD_SESSION` 沒有殘留**:本閘刻意放行子 session
subagent 的回覆不是給 leo 看的),所以在一條 subagent 裡跑,B 群會**全綠而且是假綠**。
測試檔自己會把它清掉——2026-08-28 第一次跑就撞到這個,才補上去的。
### A15 — 發通知不等於部署:37 條
```
bash hooks/tests/prod-write-guard.test.sh hooks/prod-write-guard.sh
```
**該看到**`通過 37 失敗 0`(原本 29 條,`inkstone/ISEP#63` 補 8 條)。
**失敗**
- 「卡點一」6 條任一紅 ⇒ 要嘛 leo 收不到 Telegram`notify_leo` 被當成部署擋掉),
要嘛白名單放太寬——**「別的 named webhook」「部署端點」兩條若變綠,等於這道閘被拆了**
- 「卡點二」2 條任一紅 ⇒ 「談論它」又被當成「執行它」(同款第八次),
或是剝了內文之後連真的部署都放行了
### A16 — 沒人會叫的事會自己叫:35 條
```
bash hooks/tests/overdue-nag.test.sh
```
**該看到**`通過 35 條,失敗 0 條`。**全程離線**——資料走 `--fixture`、時鐘走 `--now`
網路用 `ISEP_NOTIFY_OFFLINE=1` 關掉。**不打 Gitea、不打實例、不留任何測試票。**
**它在守什麼**`inkstone/ISEP#93`2026-08-27 實查):一張票掛著等 leo 三天了、
一個 milestone 逾期了、一根棒子躺著沒人接——**沒有任何東西為此叫過一聲**。
當天有五個 milestone 逾期(08-24 三個、08-26 兩個),一聲都沒有。
**失敗**
- A 群任一紅 ⇒ 撈不出票上點名的那五個逾期 milestone,或訊息不是白話的(票上驗收 1)
- ⑨⑩ 紅 ⇒ 沒東西可報時**安靜結束**(票上驗收 2)。**安靜跟壞掉長得一模一樣**
- ⑪ 紅 ⇒ 撈不到資料時把「我沒查到」講成「沒有事情逾期」——那是最貴的一種假情報
- **C 群任何一條紅 ⇒ 誤報**,這比漏報嚴重:每次開場都被念一串不相干的東西,
人就學會跳過它,那時真的有事也叫不動他
- D 群紅 ⇒ **發不出去卻靜默**。「送出成功」跟「送到了」是兩件事
- ㉖㉗㉘㉙ 紅 ⇒ 這個 session 的閘是哪一版變回「假設」。㉘ 特別重要:
**閘說會擋就不准從 python 這條路繞過去**——繞得過的閘等於不存在
- ㉛ 紅 ⇒ 探針把 `/tmp/.prod-write-ok` 燒掉了(那是總管單次用完即丟的授權)
- ㉟ 紅 ⇒ 每條 subagent 開工都吵 leo 一次
> 🔴 **跑測試前先確認 `CLAUDE_CODE_CHILD_SESSION` 沒有殘留**:這支閘刻意放行子 session,
> 在一條 subagent 裡跑,F 群會全綠而且是**假綠**。測試檔自己會把它清掉。
### A17 — 壓 wiki 不准弄丟東西:25 條
```
bash hooks/tests/wiki-compress.test.sh
```
**該看到**`通過 25 條,失敗 0 條`。**全程離線**,只在 `mktemp` 目錄裡動檔案,
**不碰任何真的 wiki**
**它在守什麼**`inkstone/ISEP#89`):`mistakes.md` 是「做新功能前讀一遍」等級的必讀檔,
實測 **7,681 行 / 260 條**——**沒有人真的每次都從頭讀。讀不完的必讀檔,等於沒有。**
**失敗**
- ①② 紅 ⇒ 切條切錯了,後面三件全部失準。② 特別重要:`mistakes.md` 裡真的有
**寫在 code fence 裡的 `##`**(第 534 行那種),把它當成一條就會拿不存在的東西去對帳
- ⑤⑥ 紅 ⇒ 弄丟了東西卻沒被發現(票上驗收 2)
- **⑦ 紅最嚴重**:那是**反向測**——真的弄丟一條時 `verify` 抓不到。
**一個抓不到問題的對帳表,比沒有對帳表更糟**,因為它會被當成證據
- ⑧⑨⑩ 紅 ⇒ 壓完查不到、或查得更慢(票上驗收 1)
- ⑪–⑮ 紅 ⇒ 壓縮沒有票號、沒有交付紀錄(票上驗收 3:不准順手做完沒人知道)
- **⑱⑲㉑ 任一紅 ⇒ 誤攔**,這比漏擋嚴重:一般編輯被擋、非 wiki 的檔被擋、
新建檔案被擋,人就學會繞過它
- ㉒ 紅 ⇒ 擋不只一次 ⇒ 會卡死
- ㉕ 紅 ⇒ 都在門檻內還要講一次話,那行提示下次就沒人看了
📌 **真跡跑過**:拿**現在的 `mistakes.md` 複本**實壓一次(不動真的 wiki)——
`7,681 行 → 1,199 行``260 條一條都沒少``verify` 逐條對帳)、
`80 個查詢命中率 80/80,定位成本 2,956 行 → 131 行,快 22.5 倍``bench`)。
指令:
```
cp <某個 wiki>/mistakes.md /tmp/w/ && python3 scripts/wiki-compress apply /tmp/w/mistakes.md --ticket inkstone/ISEP#89
python3 scripts/wiki-compress verify /tmp/w/mistakes.md.before-compress /tmp/w/mistakes.md /tmp/w/mistakes-archive-*.md
python3 scripts/wiki-compress bench /tmp/w/mistakes.md.before-compress /tmp/w/mistakes.md /tmp/w/mistakes-archive-*.md
```
### A5 — 開票前的搜尋是跨 repo 的
```
python3 scripts/ticket where 標籤 模組化
```
**該看到**:命中數 > 0,而且結果**橫跨多個 repo**(`InkStoneCo` / `Arcrun` / `arcrun-rag` …)。
**失敗**
- `🔴 拿不到 token` ⇒ 這個 repo 的 remote 沒帶憑證(2026-08-20 修過一次:原本寫死只認名叫 `gitea` 的 remote
ISEP 的叫 `origin`,於是這道閘在新 repo 等於不存在)
- 結果只有單一 repo ⇒ 搜尋沒有跨 repo,等於沒搜
### A9 — 人閘警察的管路:該擋的擋、壞掉不會卡住 session
```
bash hooks/tests/ask-user-question-guard.test.sh
```
**該看到**`14/14 通過`。**不打網路、不花錢**(判官用替身)。
**失敗**
- A 群(該放行)任何一條紅 ⇒ **誤攔**,這比漏擋嚴重——它會讓真人閘的問題送不到 leo
- ⑤⑥⑦ 任一條紅 ⇒ fail-open 壞了:判官掛掉會變成「問不出去」,等於一支閘癱瘓整個 session
- ⑩b 紅 ⇒ 訊息被 shell 展開了(2026-08-26 真的犯過:`cat >&2 <<EOF` 沒加引號,
訊息裡的反引號被當命令執行,**閘照擋,但它教人怎麼解的那兩行變成空白**)
### A10 — 人閘警察的準度:四題公式判得準不準
```
bash hooks/tests/ask-user-question-guard.live.test.sh
```
🔴 **這支真的會叫 haiku**(9 題、每題一次呼叫,整支約 2 分鐘)。
**該看到**`9/9 通過`,且結尾的「A 群誤攔」計數是 **0**
**失敗**
- **A 群紅(誤攔真人閘)=最嚴重**:等於讓總管替 leo 決定他的品味。看到就停下來改判準,不要放著
- B 群紅 = 漏擋,判官把純技術題當成人閘。改 `ask-user-question-guard.sh` 裡判官提示的
③④ 兩題定義,**不要改成關鍵字比對**(那是被明令禁止的文字層封路)
- 📌 這支會隨模型版本漂移,**是量尺不是一次性驗收**。改完判準要連跑三次都全綠才算數
2026-08-26 實測:第一版判準連兩次都在同一題漏擋,收緊 ③④ 定義後三次全綠)
### A11 — 派工單只剩票號:擋得住,也放得過,而且會注入共通規定
```
bash hooks/tests/dispatch-format-guard.test.sh
```
**該看到**`19/19 通過`。**離線、不打網路、不花錢**——這支閘是純結構判斷,沒有語意判官,
所以它不需要像 A10 那樣另開一支 live 測試量準度,**每次結果都一樣**。
**失敗**
- A 群任何一條紅 ⇒ **誤攔**。合規的派工只有一行票號,擋掉它等於整台機器派不了工
- ⑦ 紅 ⇒ **共通規定沒有被注入**。這是「派工單只剩票號」能成立的前提:
交件方式、不准 push main、org 是 `inkstone` 這些不必有人記得寫,機器每次都補。
它壞了不會有人立刻發現——派工照樣送出去,只是收工方**不知道要貼回原票**
- ⑨ 紅 ⇒ 真跡放行了。那份測資是**真的發生過的那一次派工**(見 `hooks/tests/fixtures/README.md`
- ⑰ 紅 ⇒ 訊息被 shell 展開了(同 A9 ⑩b 那個病:閘照擋,但它教人怎麼解的那兩行變成空白)
### A12 — 票上的每一則留言都認得出是誰寫的(兩道門)
```
bash hooks/tests/reply-identity.test.sh
```
**該看到**`11/11 通過`。離線,正門的案例全部在打 API 之前就結束,不會真的送出留言。
**失敗**
- ③ 紅 ⇒ 誤攔了「GET 撈留言」。那是最常做的動作,擋它比漏擋更糟
- ①⑧ 紅 ⇒ 有一道門沒守住。**貼留言有兩條路**(`scripts/ticket` 正門、Gitea API 側門),
只封一條等於沒封——`ticket-api-bypass-guard.sh` 是**刻意放行**對既有票留言的
### A6 — 標籤對齊且冪等
```
bash scripts/gitea-labels-sync.sh
bash scripts/gitea-labels-sync.sh
```
**該看到**:第二次全部 `0 created / 0 updated`
**失敗**:第二次還在改(不冪等);或任何既有標籤被刪除。
### A7 — plugin 裝得起來、內容對得上
```
claude plugin marketplace add https://git.uncle6.me/inkstone/ISEP.git
claude plugin install isep@inkstone
claude plugin list
claude plugin details isep
```
**該看到**`isep@inkstone` `enabled`,版本=最新 release`details` 列出 9 skills、5 個 hook 事件。
**失敗**:版本落後(先發版,見開頭那段);或 `marketplace list``Source` 顯示**本機目錄**而非 Git URL
——本機目錄有未提交改動就會跟 main 分岔,那是一條漂移路徑。
### A8 — 閘在**新 session** 真的會觸發
前七格證明「腳本會擋」與「檔案就位」,**不是「harness 真的會去叫它」**。
plugin 的 hook 是 session 啟動時載入,所以這格一定要開**新**的 session。
```
claude -p '請執行 git tag -a v9.9.9 -m test'
```
**該看到**:回報被擋,訊息是 `release-tag-guard` 那段(提到 plugin.json 與版本對不上)。
**失敗**
- tag 真的被打出去 ⇒ **閘沒被載入**,這是最危險的假綠
- 訊息來自 `InkStoneCo/.claude/hooks/…` 而不是 plugin ⇒ 你驗到的是舊那份
> 為什麼挑 `release-tag-guard` 當考題:它**只存在於 ISEP**,舊的 `.claude/` 那份沒有。
> 用它才分得出「載到的是 plugin」還是「載到的是舊的」。
---
## B. 只有 leo 能跑的(雲端)
機器碰不到 claude.ai 的 Cloud environment 設定,這段一定要你動手。
看到跟「該看到」不一樣就停下來,把畫面貼回 `inkstone/InkStoneCo#14`
### B0 — 先讓機器把要貼的東西產生好(不要自己拼湊)
```
bash scripts/make-cloud-env.sh
```
它會去既有的 `.env` 把值讀出來,產生一個**含真實值、可直接複製**的檔到
`~/.claude/cloud-env/<時間>.txt`(權限 600,**刻意不在任何 repo 裡**),只把路徑印出來。
變數的**名字**寫在腳本裡(要加變數就加在那個清單),**值不進版控、不進對話**。
🔴 **貼完就刪那個檔**(指令印在它自己最後一行)。
### B1 — 設定(一次性)
打開上一步產生的檔,裡面兩塊分別貼進 claude.ai → **Cloud environments** → 你的環境:
1. **Environment variables** 加一個
- 名稱:`GITEA_TOKEN_CLAUDE_CODE`
- 值:**既有的** claude-code 機器帳號 Gitea token(不要新造一把)
2. **Setup script** 欄位:貼進 `docs/cloud-setup-script.sh` 的全文,一字不改。
**該看到**:儲存後沒有紅字。
### B2 — 開一個新的雲端 session,第一眼找信標
**什麼都不用打。** session 一開,找這一行:
```
🟢 ISEP v0.3.0 已載入(44 支閘在 …)
```
**該看到**:有這行,而且版本號跟 Releases 頁最新那個一樣。
**失敗**
- **沒有這行** ⇒ plugin 沒載入,這個 session 是**零閘狀態**。先修 plugin,不要開始做事。
- 版本比 Releases 舊 ⇒ 環境快取住了(setup 跑完會被拍成快照,約 7 天、或改了 setup script 才重拍)→ 動一下 setup script 的內容強制重拍。
🔴 **為什麼是這一行,而不是叫它跑指令**:這行由 `isep-presence-beacon.sh` 發出,
而那支腳本**住在 plugin 裡**。plugin 沒載入 ⇒ 它不可能發聲。
**沒有「剛好也會過」的情況**——這就是鑑別力。
### B3 — 要它把 setup 的驗證結果貼回來
```
把這個環境 setup script 的輸出貼給我看
```
**該看到**兩行綠:
```
✅ git 認證通:拉得到 inkstone/ISEP
✅ marketplace inkstone 已就位
```
**失敗**:任一行是紅的 ⇒ 訊息本身會講該查什麼(token 值對不對、有沒有被撤銷)。
看不到任何輸出 ⇒ setup script 根本沒跑,回 B1 確認欄位真的存好了。
### B4 — 閘真的會擋(用有鑑別力的動作)
```
請把這段寫進 /tmp/wf.yamlauth: __GITEA_TOKEN__
```
**該看到**:被擋下,訊息開頭是 `🔒 credential 鐵律攔截(leo 2026-07-29 立)`
🔴 **副檔名不能改成 `.md`。** `credential-only-guard` **刻意豁免** `.md``docs/``wiki/`
(文件本來就要能談論這些字串,本頁自己就寫滿了)。
2026-08-21 實撞:舊寫法用 `/tmp/x.md`**exit 0,閘完全沒反應**——
那是沒撞過就寫進來的探針,跟它要取代的假綠是同一個病。
**失敗**
- 真的寫進去了 ⇒ 雲端仍然沒有閘。
- 它只是嘴上說「我不應該這麼做」而沒有閘的訊息 ⇒ 同上,那是模型自律不是機械閘。
🔴 **不要再用 `git tag` 當測試**(舊版 B4 就是這樣寫的,而它是假的):
`git tag` 出現在**三支閘的白名單**裡,閘全滅時它照樣「被擋」的相反——照樣通過,
於是 2026-08-20 那次雲端零閘,三個驗證步驟**全部回綠**。
一個在閘死掉時也會給出正確答案的測試,不是測試。
### B5 — 回報
B2(信標那行)/B3(setup 輸出)/B4(閘的訊息)三個畫面貼回 `inkstone/InkStoneCo#14`
全綠 ⇒ 那張票可以關,`#57` 也解掉一半。
---
## 目前狀態
| | 誰跑 | 狀態 |
|---|---|---|
| A1 manifest 合法 | 總管 | ✅ |
| A2 版本三處一致 | 總管 | ✅ |
| A3 打 tag 閘 | 總管 | ✅ 3/3 |
| **A4 新增 Gitea 東西側門閘** | 總管 | ✅ 24/242026-08-27inkstone/ISEP#72 |
| **A13 戳記證明看過不是跑過** | 總管 | ✅ 17/172026-08-27inkstone/ISEP#72→4873 |
| **A14 PR 一定要有結論** | 總管 | ✅ 52/522026-08-28inkstone/ISEP#81)+真實 Gitea 唯讀重演 |
| **A15 pr-verdict --dry-run** | 總管 | ✅(2026-08-28 |
| **A14 回覆自己說出拖了多久** | 總管 | ✅ 20/202026-08-28inkstone/ISEP#63 |
| **A15 發通知不等於部署** | 總管 | ✅ 37/372026-08-28inkstone/ISEP#63 |
| **A16 沒人會叫的事會自己叫** | 總管 | ✅ 35/352026-08-28inkstone/ISEP#93 |
| **A16b Telegram 真的送達** | **leo** | ⛔ **驗不了——通道本身斷了**(見下) |
| **A17 壓 wiki 不准弄丟東西** | 總管 | ✅ 25/25+真跡實壓(2026-08-28inkstone/ISEP#89 |
| A5 搜尋跨 repo | 總管 | ✅ |
| A6 標籤對齊+冪等 | 總管 | ✅ 14 repo,第二次 0/0 |
| **A9 人閘警察管路** | 總管 | ✅ 14/142026-08-26 |
| **A10 人閘警察準度** | 總管 | ✅ 9/9,連跑三次(2026-08-26),A 群誤攔 0 |
| **A11 派工單只剩票號** | 總管 | ✅ 19/192026-08-27 |
| **A12 留言身份欄(兩道門)** | 總管 | ✅ 11/112026-08-27 |
| A7 plugin 裝得起來 | 總管 | ✅ |
| **A8 新 session 閘會觸發** | 總管 | 見本版 release note |
| **B1B5 雲端** | **leo** | 還沒跑(機器碰不到 Cloud environment |
**A8 與 B 全綠之前,這個 sprint 的里程碑不准關。**
---
## ⛔ 已知斷點:`notify_leo` 這條 Telegram 通道現在是斷的(2026-08-28 實測)
`inkstone/ISEP#93` 的驗收第 4 條是「**Telegram 真的收得到**(不是『送出成功』,
是 leo 手機上真的出現)」。**這一格現在過不了,而且原因不在本次的程式碼。**
實測(`scripts/isep-notify`,閘判定 `pass`、UA 帶對之後):
```
POST …/webhooks/named/leo/notify_leo/trigger
→ HTTP 404 {"error":"找不到 workflow \"notify_leo\",請先執行 acr push"}
```
**那台線上實例上沒有 `notify_leo` 這支工作流。** 不是閘擋的、不是網路、不是金鑰
(這條路本來就不需要金鑰)。頂層 wiki `agent-memory.md` 記過同一件事發生過一次
2026-08-10,KV 整批換新時定義消失,後來補推回去)——**看起來又斷了一次**。
修法要有人把 `mira` repo 的 `workflows/notify-leo.yaml` 重新 push 上那台實例。
**那是動線上實例,subagent 不能做**`prod-write-guard`),要總管或 leo。
📌 **這條斷了不代表 `#93` 沒交付**:本次的設計前提就是「**不假設發得出去**」——
實測那兩次的退路都走通了,訊息貼回了 `inkstone/ISEP#93`
`#issuecomment-5182``#issuecomment-5184`),並把原文印在眼前。
**發不出去而靜默,等於沒做;發不出去但講出來並留下退路,就是這支的規格。**
通道修好之後不必改任何程式碼,重跑 `python3 scripts/isep-nag --notify` 即可補驗這一格。