# Gateway Recovery Playbook

> 目標：當 gateway 更新後再次出現綁定錯亂、長任務異常、LINE quota fallback 失效、email 附件壞掉時，可以不用重翻歷史對話試錯，而是按固定順序定位。

## 0. 先判斷是哪一類故障

### A. 綁定 / 寄錯信
症狀：
- 寄到錯 principal 的 email
- Telegram / LINE / OpenWebUI 對應到錯綁定
- fallback email 被 block 或誤放行

先看：
- `gateway/user_verification.py`
- `plugins/platforms/line/adapter.py`

### B. LINE 長任務 / 查看答案異常
症狀：
- `查看答案` 取不到結果
- 一直顯示還在處理中
- `DELIVERED` 後 replay 不符合實際交付 surface

先看：
- `plugins/platforms/line/adapter.py`
- `RequestCache`
- `_handle_postback_event(...)`

### C. LINE quota fallback / email fallback 異常
症狀：
- LINE 429 後沒寄 email
- 有寄 email 但 replay 還假裝 LINE 已成功
- final 已寄 email，附件卻又跑回 LINE 失敗

先看：
- `plugins/platforms/line/adapter.py`
- `gateway/platforms/base.py`

### D. Email 附件 / 下載連結異常
症狀：
- 沒附件
- 附件沒副檔名
- 下載連結打不開
- 附件過大直接寄失敗

先看：
- `plugins/platforms/email/adapter.py`
- `hermes_cli/managed_downloads.py`
- `hermes_cli/web_server.py`

### E. Shared drive workflow 異常
症狀：
- 搜尋找不到明明存在的檔案/資料夾
- 資料夾展開後沒有列出可選檔案
- bundle 沒產生連結或敏感檔沒加密
- 24h 連結被誤判壞掉，其實只是 HEAD / bot-like client 驗證失真

先看：
- `~/.hermes/scripts/shared_drive_index_query.py`
- `~/.hermes/scripts/shared_drive_lookup.py`
- `~/.hermes/scripts/shared_drive_delivery_bundle.py`
- `~/.hermes/scripts/shared_drive_chat_workflow.py`
- `docs/shared-drive-file-access/shared-drive-file-access-validation-matrix.md`

### F. 額度 / 權限 / 管理後台異常
症狀：
- dashboard role 消失或全部變 viewer
- principal / identity list 不見
- quota policy / model policy 查不到
- principal usage 不再累積

先看：
- `gateway/user_verification.py`
- `hermes_cli/web_server.py`
- `gateway/principal_profiles.py`

---

## 1. 第一層：不要先猜，先跑最小測試

```bash
venv/bin/pytest -q \
  tests/gateway/test_line_plugin.py \
  tests/gateway/test_email.py \
  tests/gateway/test_approval_prompt_redaction.py \
  tests/gateway/test_display_config.py \
  tests/hermes_cli/test_managed_downloads.py \
  tests/hermes_cli/test_web_verification_admin.py \
  tests/gateway/test_principal_profile_routing_phase1.py \
  tests/gateway/test_verified_email_handoff_guard_helpers.py
```

### 判讀原則
- `test_line_plugin.py` 爆：先看 LINE state machine / fallback ownership / file reroute
- `test_email.py` 爆：先看 MIME / HTML / attachment handling
- `test_managed_downloads.py` 爆：先看正式網域 URL / token / route
- `test_approval_prompt_redaction.py` 爆：先看 approval text fallback / redaction seam
- `test_display_config.py` 爆：先看 LINE 是否又被改回 noisy 模式

---

## 2. 第二層：live log 定位

主要看：
- `~/.hermes/logs/gateway.log`

### 綁定問題關鍵字
- `verified-email mismatch`
- `blocked email handoff`
- `get_verified_email_for_source`

### LINE quota fallback 關鍵字
- `LINE: push send failed`
- `LINE Push 額度已滿`
- `email_fallback`
- `email_fallback_pending`
- `Routed LINE attachment delivery to verified email fallback`
- `Skipping LINE media attachment send because quota fallback email already carried`

### Email 附件問題關鍵字
- `Sent multi-attachment email`
- `standalone attachment failed`
- `size limit`
- `Email send failed`

### Managed downloads 關鍵字
- `/downloads/`
- `invalid_download_signature`
- `expired_download_token`
- `not found`

### Shared drive workflow 關鍵字
- `verification`
- `shared-drive`
- `expired_download_token`
- `invalid_download_signature`
- `find_by_file_ids`

### governance / admin 關鍵字
- `forbidden`
- `unauthenticated`
- `dashboard_role_not_persisted`
- `principal_usage_not_persisted`
- `identity_not_found_after_unbind`

---

## 3. 第三層：依故障類型對照修復順序

### 3.1 若是寄錯信 / 綁定錯亂
1. 確認 active source 是哪個平台、哪個 external user id
2. 查 `GatewayUserStore.get_verified_email_for_source(...)`
3. 查 `bound_email_matches_source(...)`
4. 確認呼叫端是否用了 `explicit_user_requested=True`
5. 若沒有使用者明確指定其他 email，卻仍能寄到不同信箱，視為重大回歸

### 3.2 若是 LINE pending / show_response 異常
1. 看 `_pending_buttons` 是否有建立
2. 看 `RequestCache` 狀態是否真的進到 `READY`
3. 看 `_handle_postback_event(...)` 是否把 payload 當成一般文字，而不是 `email_fallback*`
4. 若 final surface 已是 email，replay 仍顯示 generic delivered copy，代表 fallback payload 被覆蓋或遺失

### 3.3 若是 final 已寄 email，但附件又誤走 LINE
1. 看 `gateway/platforms/base.py`
2. 確認 `result.raw_response.kind == email_fallback`
3. 確認 `should_route_poststream_files_to_email(chat_id)` 為真
4. 確認 `send_email_file_batch_fallback(...)` 成功後 `skip_platform_media_delivery = True`

### 3.4 若是附件寄失敗 / 沒副檔名
1. 查 `_standalone_send(...)`
2. 確認 `MIMEMultipart` 是否建立
3. 確認 attachment 的 `Content-Disposition filename=`
4. 確認 `mimetypes.guess_type(...)` 結果與 fallback content-type

### 3.5 若是下載連結壞掉
1. 查 `build_public_download_url(...)`
2. 查 token 是否可 `resolve_signed_download_token(...)`
3. 實際 GET `/downloads/{token}`
4. 若 route 404，先確認：
   - URL 是否正式網域
   - token 是否重啟後仍可驗證
   - staged file 是否存在

### 3.6 若是 dashboard role / quota / usage 異常
1. 先打 `/api/admin/verification/role`
2. 再查 `/api/admin/verification/roles`
3. 再查 `/api/admin/verification/principals`
4. 再查 `/api/admin/quota-policies`、`/api/admin/model-policies`
5. 再查 `/api/admin/principal-usage`
6. 若 usage 為空，但聊天明明有跑：
   - 查 `gateway/run.py` 的 `record_usage_for_source(...)`
   - 確認 source 仍能解析 principal context

### 3.7 若是 shared drive workflow 壞掉
1. 先跑：
```bash
python3 ~/.hermes/scripts/shared_drive_chat_workflow.py search <query>
python3 ~/.hermes/scripts/shared_drive_chat_workflow.py children <folder_file_id>
python3 ~/.hermes/scripts/shared_drive_chat_workflow.py bundle --file-ids <file_id> --requester dk96@bremen.com.tw
```
2. 若 search 沒結果：先查 index 是否重建、`shared_drive_index_query.py status` 是否有資料
3. 若 children 空掉：先查該 `file_id` 是否仍是資料夾、`parent_dir` 是否對得上
4. 若 bundle 失敗：先查 `find_by_file_ids(...)`、staging 目錄、manifest 是否落地
5. 若外部連結被判失敗：
   - 以真實 GET 驗證，不以 HEAD 為準
   - 不把 urllib / Cloudflare 1010 當成最終失敗判準
   - 確認 `verification.ok = true`
6. 若敏感檔沒加密：查 `detect_sensitivity(...)` 與 bundle payload 的 `encrypted` / `password_channel`

### 3.8 若是 principal profile 漂移
1. 查 `GatewayUserStore.get_principal_context(source)` 是否還拿得到 principal_id
2. 查 `gateway/principal_profiles.py::resolve_principal_profile(...)`
3. 查 `principal_profile_map.json` 是否被重建或覆蓋
4. 若 mapping 漂移，先修 principal context，不要先誤判成單純 delivery bug

---

## 4. 更新後固定 smoke checklist

### 綁定
- [ ] LINE 綁定 email 正確
- [ ] Telegram 綁定 email 正確
- [ ] OpenWebUI / api_server 綁定 email 正確
- [ ] system-initiated handoff 不會寄到非 live bound email

### LINE 長任務
- [ ] pending button / wait notice 正常
- [ ] `查看答案` 在 `PENDING/READY/DELIVERED` 各自回應正確
- [ ] heartbeat 文案不是 raw tool progress 噪音

### quota fallback
- [ ] LINE 429 後有 email fallback
- [ ] pending 只寄一次等待通知
- [ ] final 真正寄到 verified email
- [ ] replay 說明仍保留 email fallback 事實

### 權限 / 額度 / 管理後台
- [ ] `/api/admin/verification/role` 回應符合預期角色
- [ ] principals / identities 列表查得到
- [ ] quota policy / model policy 還在
- [ ] principal usage 持續累積
- [ ] audit log 查得到最近治理操作

### email 附件 / 下載連結
- [ ] 小檔案：附件 + 連結
- [ ] 大檔案：只有連結
- [ ] 附件有副檔名
- [ ] 下載連結可點、可下載、網域正確

### shared drive workflow
- [ ] search 可列出候選與 `file_id`
- [ ] children 可展開資料夾並列出最多 10 檔可選項
- [ ] bundle 可產出 24h 連結
- [ ] 敏感檔自動轉加密 zip
- [ ] `verification.ok = true`

### approval
- [ ] approval 信 / 訊息為繁中對客版
- [ ] raw command / token 未外露

---

## 5. 維護建議

### 必做
- 任何改到上述模組的 PR，都在描述中附上：
  - 改了哪條契約
  - 跑了哪些 regression tests
  - 做了哪些 live smoke

### 建議
- 之後可再補一份 `gateway-change-impact-checklist.md`
- 讓每次 update / refactor 前先做 preflight 勾選：
  - 有無碰 principal binding
  - 有無碰 LINE pending/postback
  - 有無碰 email MIME / managed downloads

---

## 一句話原則

**先用契約定位，再用測試驗證，最後才 patch。**

不要再回到「翻對話、猜哪裡壞、邊改邊試」那種高 token、低穩定度修法。
