23e4472715
leo 2026-08-27:「開票不查是否有現成的,這是什麼問題?」「不只開票前, 所有新增 gitea 的東西,都要搜尋」。 v1 的 ticket-api-bypass-guard 只認裸字 `grep -qw POST`(大小寫敏感), 只管 issues 端點。2026-08-27 總管一天開 13 張票,v1 一次都沒攔到—— 用的是 `urllib.request.Request(url, data=...)`(靠傳 data= 隱式變 POST, 指令裡從頭到尾沒有 "POST" 三個字)和 `requests.post(...)`(小寫)。 本次改動: - 寫入訊號從單一裸字換成一組結構性訊號(curl 資料類旗標/`.post(`/ `method=post`/urllib 隱式 POST),且大小寫不敏感,同時保留 v1 的裸字比對 - 端點擴大到 milestones/labels/pulls(含 org 層級的 labels),不再只管 issues - 用「resource/數字」特徵先放行帶 ID 的既有資源子路徑(貼留言、改標籤、 合併 PR 等),避免貪婪比對把 `/issues/5/labels` 誤判成頂層 `/labels` 端點 - 擋下 issues 新增時,順手用標題猜關鍵字查一次跨 repo 搜尋,把可能撞到的 舊票(例如 Arcrun#100)直接列進擋下的訊息裡 測試:scripts/test-ticket-api-bypass-guard.sh 24/24 通過(v1 的 13 條 + 本次 新增 11 條,含一條打真實 Gitea 網路重演 Arcrun#100 重複主題偵測)。 plugin.json 0.5.0 → 0.5.1;version-consistency 會在合併打 tag 那一刻才對齊 (release-tag-guard.sh 的既定分工:打 tag 是總管驗過整個 milestone 之後的事)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
259 lines
12 KiB
Markdown
259 lines
12 KiB
Markdown
# 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.json=X.Y.Z,最新 tag=vX.Y.Z,README 沒有自行宣告版本。`
|
||
**失敗**: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、milestone/label/PR、org 端點、以及一條打
|
||
真實 Gitea 網路重演 Arcrun#100 的案例——這台機器的 remote 沒帶憑證時會印
|
||
`⏭️ SKIP`,不算失敗,但也不算驗過)。
|
||
**失敗**:任何一條不符,特別看「不該擋」那幾條——誤攔比漏擋更該修。
|
||
|
||
### 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.yaml:auth: __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/24(2026-08-27,inkstone/ISEP#72) |
|
||
| A5 搜尋跨 repo | 總管 | ✅ |
|
||
| A6 標籤對齊+冪等 | 總管 | ✅ 14 repo,第二次 0/0 |
|
||
| **A9 人閘警察管路** | 總管 | ✅ 14/14(2026-08-26) |
|
||
| **A10 人閘警察準度** | 總管 | ✅ 9/9,連跑三次(2026-08-26),A 群誤攔 0 |
|
||
| **A11 派工單只剩票號** | 總管 | ✅ 19/19(2026-08-27) |
|
||
| **A12 留言身份欄(兩道門)** | 總管 | ✅ 11/11(2026-08-27) |
|
||
| A7 plugin 裝得起來 | 總管 | ✅ |
|
||
| **A8 新 session 閘會觸發** | 總管 | 見本版 release note |
|
||
| **B1–B5 雲端** | **leo** | 還沒跑(機器碰不到 Cloud environment) |
|
||
|
||
**A8 與 B 全綠之前,這個 sprint 的里程碑不准關。**
|