# Gateway State and Sequence

> 目標：把最容易回歸的 gateway 流程，用順序清單 + Mermaid 狀態圖 / 時序圖固定下來，避免未來只靠對話與印象排錯。

## 實際程式碼再確認

本文件對應下列 live 落點：

- verified email lookup / live check：`gateway/user_verification.py`
- LINE pending/postback/quota fallback：`plugins/platforms/line/adapter.py`
- 長任務 heartbeat 與 non_conversational：`gateway/run.py`
- post-stream 附件 reroute：`gateway/platforms/base.py`
- email attachments：`plugins/platforms/email/adapter.py`
- managed download token / route：`hermes_cli/managed_downloads.py`, `hermes_cli/web_server.py`
- governance/admin API：`hermes_cli/web_server.py`, `gateway/user_verification.py`
- principal profile routing：`gateway/principal_profiles.py`

---

## 1. Binding / handoff 順序清單

### 正確順序
1. 取得 active source（LINE / Telegram / email / api_server）
2. 以 `GatewayUserStore.get_verified_email_for_source(source)` 查 live 綁定
3. 若屬於系統主動交付到綁定信箱：
   - 用 `bound_email_matches_source(...)` 再次確認
4. 若比對失敗：
   - block handoff
   - 記 log
   - 不寄出
5. 若使用者明確指定其他 email：
   - 才可帶 `explicit_user_requested=True`

### Mermaid 狀態圖
```mermaid
stateDiagram-v2
    [*] --> ResolveSource
    ResolveSource --> LookupBoundEmail
    LookupBoundEmail --> MatchBoundEmail: system-initiated handoff
    LookupBoundEmail --> ExplicitOverride: user explicitly named another email
    MatchBoundEmail --> Allowed: exact match
    MatchBoundEmail --> Blocked: mismatch / lookup failed
    ExplicitOverride --> Allowed
    Allowed --> [*]
    Blocked --> [*]
```

---

## 2. LINE 長任務順序清單

### 正確順序
1. 使用者送出 LINE 請求
2. 若 reply token 快到期且任務尚未完成：
   - 發 pending button (`show_response`)
   - cache state = `PENDING`
3. 任務完成後：
   - 若 chat 仍有 pending button outstanding
   - 將內容塞入 cache，state -> `READY`
4. 使用者點 `show_response`
   - 若 `READY`：回傳內容或 fallback notice
   - 若 `PENDING`：回 pending notice
   - 若 `DELIVERED`：回 delivered/fallback replay notice
   - 若 `ERROR`：回錯誤內容

### Mermaid 狀態圖
```mermaid
stateDiagram-v2
    [*] --> PENDING: slow-response button issued
    PENDING --> READY: final content cached
    PENDING --> ERROR: run interrupted / failed
    READY --> DELIVERED: postback reply or push succeeds
    ERROR --> DELIVERED: error text delivered
    DELIVERED --> [*]
```

---

## 3. LINE quota fallback → email 最終交付順序

### 正確順序
1. LINE 準備送 final content
2. reply 失效或 push quota 失敗
3. 進入 `_send_email_quota_fallback(...)`
4. 再查一次 verified email 並做 bound-email guard
5. 組 fallback payload：
   - `kind`
   - `email`
   - `notice`
   - `content`
   - `media_files`
6. final 模式附加 download links
7. 呼叫 email standalone sender
8. 若附件過大：
   - 改單封 link-only 通知
   - payload 補 `attachments_omitted_reason=size_limit`
9. 記錄 final email fallback ownership
10. 後續 post-stream files 走 email，不再回 LINE 原生附件

### Mermaid 時序圖
```mermaid
sequenceDiagram
    participant U as LINE User
    participant L as LINE Adapter
    participant V as GatewayUserStore
    participant E as Email Adapter
    participant D as Managed Downloads

    U->>L: send request
    L->>L: try LINE reply/push
    alt LINE quota / push failure
        L->>V: get_verified_email_for_source(source)
        V-->>L: verified email
        L->>V: bound_email_matches_source(...)
        V-->>L: allow / block
        alt allowed
            L->>D: build_public_download_url(files)
            D-->>L: HTTPS download links
            L->>E: _standalone_send(body, media_files)
            alt attachments within limit
                E-->>L: success with MIME attachments
            else size limit
                E-->>L: too large
                L->>E: resend single email with links only
                E-->>L: success
            end
            L-->>U: replay says result went to email
        else blocked
            L-->>U: do not hand off to email
        end
    end
```

---

## 4. Post-stream 附件 reroute 順序

### 背景
有些最終附件是在 text final 之後才進入 `gateway/platforms/base.py` 的 post-stream attachment path。這是最容易「前面已寄 email，後面又誤走 LINE 附件送出」的地方。

### 正確順序
1. 文字 final 已因 LINE quota fallback 改寄 email
2. `gateway/platforms/base.py` 準備送 post-stream media
3. 檢查：
   - `result.raw_response.kind == email_fallback`
   - 或 `should_route_poststream_files_to_email(chat_id)` 為真
4. 若成立：
   - 呼叫 `send_email_file_batch_fallback(...)`
   - `skip_platform_media_delivery = True`
5. 不再送 LINE 原生檔案

### Mermaid 判斷圖
```mermaid
flowchart TD
    A[post-stream media extracted] --> B{LINE final already fell back to email?}
    B -- no --> C[send native LINE media/document path]
    B -- yes --> D[send_email_file_batch_fallback]
    D --> E{email send success?}
    E -- yes --> F[skip LINE native media delivery]
    E -- no --> G[log failure and keep investigating]
```

---

## 5. Email 檔案交付順序

### 正確順序
1. 準備 plain text body
2. 產生 HTML body
3. 若有 media files：
   - 建 `MIMEMultipart`
   - attach plain + html alternative
   - 逐檔 attach MIMEBase
   - 設好 filename / content-type / disposition
4. 若無 media files：
   - 仍寄 plain + html alternative
5. SMTP send
6. 若附件失敗：
   - 記 warning
   - 由上游 fallback 決定改 link-only 或回報失敗

---

## 6. Managed downloads 順序

### 正確順序
1. 來源檔案存在
2. `stage_public_download(file_path)` 複製到 Hermes managed stage dir
3. `create_signed_download_token(...)`
4. `build_public_download_url(...)`
5. `/downloads/{token}` route 以 token resolve staged file
6. 以 `FileResponse` 對外提供下載

### 風險點
- 如果把 session-only / process-memory token 當公開下載憑證，重啟後會失效
- 如果 base URL 不是正式網域，使用者會拿到錯誤或不可持久的連結

---

## 7. 更新後必核對的流程斷點

每次有以下異動時，至少核對這些節點：

- `GatewayUserStore` schema / lookup 邏輯
- LINE `RequestCache` / `_handle_postback_event`
- `gateway.run` heartbeat / non_conversational metadata
- `gateway.platforms.base` post-stream media routing
- email standalone sender MIME 組裝
- managed downloads signing secret / route

---

## 8. 額度治理順序清單

### 正確順序
1. source 已通過 verified identity / principal lookup
2. gateway run 完成，取得本次 input/output/total token delta
3. `gateway/run.py` 呼叫 `GatewayUserStore.record_usage_for_source(...)`
4. usage 寫入 `principal_usage_daily`
5. admin 後台用 `/api/admin/principal-usage` 查回 usage
6. 若有 quota policy，usage list 需能 join 出：
   - `daily_message_limit`
   - `daily_token_limit`
   - `quota_notes`

### Mermaid 時序圖
```mermaid
sequenceDiagram
    participant U as Verified User Source
    participant G as GatewayRunner
    participant V as GatewayUserStore
    participant A as Admin API

    U->>G: run conversation
    G->>G: compute token/message deltas
    G->>V: record_usage_for_source(source, usage)
    V->>V: upsert principal_usage_daily
    A->>V: list_principal_usage()
    V-->>A: usage + email + quota limits
```

### 重要邊界
- 目前明確看到的是「記帳 / 查帳 / 顯示 quota policy」，不是所有 runtime path 都已做 hard deny。
- 所以更新後若 usage API 正常，不代表 quota enforcement 一定也正常；這兩件事要分開驗證。

---

## 9. 權限治理與管理平台操作順序

### dashboard role 判斷順序
1. request 進入 `hermes_cli/web_server.py`
2. `_require_verification_role(...)` 判斷是否 `auth_required`
3. 若不是 gated auth：直接視為 `admin`
4. 若是 gated auth：
   - 從 session 取 email
   - 查 `GatewayUserStore.get_dashboard_role(email)`
   - 若尚未有任何 role 資料，第一位可落到 bootstrap admin
   - 比對 `viewer/operator/admin` rank
5. 權限不足就 `403 forbidden`

### governance API 操作順序
1. 管理者打 admin endpoint
2. 先經 role gate
3. 再進 `GatewayUserStore`
4. 若是 bind / unbind / block / unblock：
   - 更新 identity / principal 關聯
   - 寫 `admin_audit_logs`
5. 若是 quota / model policy：
   - upsert policy row
6. 若是 audit / principals / identities / usage：
   - 回傳當下 live DB 狀態

### Mermaid 狀態圖
```mermaid
flowchart TD
    A[admin request] --> B{auth_required?}
    B -- no --> C[role=admin]
    B -- yes --> D[read session email]
    D --> E[get_dashboard_role(email)]
    E --> F{enough rank?}
    F -- no --> G[403 forbidden]
    F -- yes --> H[execute governance action]
    H --> I[write/read GatewayUserStore]
    I --> J[audit if mutating]
```

---

## 10. principal profile routing 順序

### 正確順序
1. source 經 verification store 成功解析 principal context
2. `resolve_principal_profile(...)` 取 principal_id
3. 寫/讀 `principal_profile_map.json`
4. 對應到穩定 `principal_<slug>` profile
5. gateway 用該 profile 建 quick key / session context

### 為什麼這段要固定
- 只要 principal profile mapping 漂掉，綁定、模型、工具、session continuity 會一起變得很難排。
- 這就是那種「看起來像 delivery 壞掉，其實是 principal context 漂移」的經典坑。

### 本次盤點現況
- 這段流程對未來架構很重要，而本次已補上 `GatewayConfig.principal_profile_routing` / `principal_profile_map_path` 接線。
- `tests/gateway/test_principal_profile_routing_phase1.py` 已通過，表示 verified source → principal profile → quick key / session namespace 這段至少已有回歸保護。
- 後續若再改 `gateway/config.py`、`gateway/run.py`、`gateway/authz_mixin.py` 或 `gateway/principal_profiles.py`，必須重跑這組測試，避免又退回「identity 有綁、但 session namespace 沒切對」的狀態。
