# Gateway Functional Contract > 目標:把 Hermes gateway 在 **identity 綁定、長任務交付、LINE quota fallback、email 檔案交付、shared drive 下載工作流** 的可接受行為定義成明確契約,未來更新或重構時可直接逐條核對。 ## 實際程式碼再確認 本文件依下列 live 模組與測試整理: - `gateway/user_verification.py` - `gateway/run.py` - `gateway/platforms/base.py` - `plugins/platforms/line/adapter.py` - `plugins/platforms/email/adapter.py` - `hermes_cli/managed_downloads.py` - `hermes_cli/web_server.py` - `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` 這份文件只記錄目前已在程式或測試中有明確落點的行為,不把尚未實作的理想狀態假裝成既有功能。 --- ## 1. Principal / verified email 綁定契約 ### 1.1 authoritative source - verified email 的 live 來源是 `GatewayUserStore`。 - 主要查詢入口:`GatewayUserStore.get_verified_email_for_source(source)`。 - 系統不得只靠對話上下文、記憶摘要、舊 prompt、舊 session 欄位決定要寄到哪個 email。 ### 1.2 handoff 前必做 live confirm - 任何 **系統主動** 把結果寄到使用者綁定信箱的流程,在寄出前都必須重新確認: - active source 的綁定 email - 與即將寄送的 target email - 必須完全一致 - 目前這條 hard guard 已有明確函式: - `GatewayUserStore.bound_email_matches_source(...)` - LINE 側經 `LineAdapter._verified_email_target_if_bound(...)` 使用 ### 1.3 例外規則 - 若是 **使用者明確指定其他 email**,可用 `explicit_user_requested=True` 略過 bound-email equality check。 - 這個例外只適用使用者明講的目的地,不適用系統自行 fallback 或自動推論。 ### 1.4 不可接受行為 - 把別的 principal 的 verified email 當成目前 source 的綁定信箱 - 用 stale session state / context compression 結果直接寄信 - 無 live confirm 就直接把 LINE / Telegram / OpenWebUI 的結果轉寄到 email ### 1.5 principal context 與 profile routing - verified source 除了 email handoff guard,還會產生 principal context: - `GatewayUserStore.get_principal_context(source)` - `gateway/principal_profiles.py` - verified principal 若啟用 `principal_profile_routing`,應穩定解析到同一 principal profile。 - 不可因 update / restart 導致同一 principal 突然丟失 profile mapping,否則會讓該 principal 的後續權限、工具、長任務脈絡一起漂移。 --- ## 2. LINE 長任務契約 ### 2.1 pending / postback state machine - LINE 慢回覆流程使用 `RequestCache` 狀態機: - `PENDING` - `READY` - `DELIVERED` - `ERROR` - `show_response` postback 只在該 cache 路徑中取回結果。 - 若任務還沒完成,`show_response` 必須回 pending 狀態,不得假裝 final 已送達。 ### 2.2 pending button 的語意 - pending button 是「稍後取回答案」機制,不是 final delivery 本身。 - 若 final 結果已轉到 email surface,後續 postback replay 必須保留「已改寄 email」事實,不能回 generic delivered copy。 ### 2.3 長任務 heartbeat - gateway 允許 long-running heartbeat,但 LINE 預設應是 **安靜、泛化、對客版**。 - `tests/gateway/test_display_config.py` 已明確固定: - `tool_progress == off` - `busy_ack_detail == false` - `long_running_notifications == generic` - 不應把 raw tool names、iteration counter、工程英文直接丟給 LINE 使用者,除非明確 opt-in。 ### 2.4 non_conversational 與 bypass - system busy-acks / approval / update 類通知必須以 `non_conversational` metadata 保護,不可被誤收進最終交付內容。 - heartbeat 與 background/system notices 不得誤變成使用者的 final email。 ### 2.5 不可接受行為 - heartbeat 冒充 final answer - `查看答案` 重播時遺失 fallback surface 狀態 - 長任務提示持續噴工程實作細節,讓使用者以為系統異常 --- ## 3. LINE quota fallback → email 契約 ### 3.1 fallback 觸發條件 - LINE push quota / push send 失敗時,應嘗試改走 verified email fallback。 - fallback payload 必須明確標記為: - `email_fallback_pending` - 或 `email_fallback` ### 3.2 pending 與 final 要分開 - pending 階段: - 可寄一次「仍在處理中,完成後會寄到驗證 email」通知 - 應避免重複寄一堆同樣 pending 通知 - final 階段: - 應寄出完整內容 - 並明確告知已改由 email 交付 ### 3.3 fallback ownership transfer - 一旦 final answer 已由 email fallback 接手,後續 post-stream 附件交付也必須沿用同一 email surface。 - 不得又回頭走 LINE 原生檔案送出,造成: - 使用者已收到 email,卻又看到 LINE 附件送失敗 - 或 delivery state 自相矛盾 ### 3.4 replay contract - `DELIVERED` 狀態若 payload 為 `email_fallback`,replay / postback 必須重播 fallback notice,而不是 generic `Already replied` 類文字。 ### 3.5 不可接受行為 - LINE quota 滿了卻靜默失敗 - fallback email 只寄文字,不帶原本該帶的檔案資訊或下載連結 - 使用者之後查詢結果時,看不到「其實已改寄 email」這個事實 --- ## 4. Email 檔案交付契約 ### 4.1 MIME attachment contract - 若有附件且未超限: - email 應為 multipart - 要附真正 MIME attachment - 檔名要保留副檔名 - `Content-Disposition` / `Content-Type` filename 參數要正確 - `tests/gateway/test_email.py` 已固定這個契約 ### 4.2 HTML + plain text contract - email 不只 plain text,也要帶 HTML body - 裸 URL 在 HTML 中應轉成可點擊的 `` ### 4.3 large attachment contract - 若附件總大小超過 email 限制: - 不應狂寄多封 - 應改成單封通知 + 下載連結 - payload 應保留: - `attachments_omitted_reason = size_limit` - `omitted_filenames` - `download_links` ### 4.4 download link contract - 對客下載連結應優先使用 managed downloads 正式網域 - 目前由: - `hermes_cli/managed_downloads.py` - `hermes_cli/web_server.py` 的 `/downloads/{token}` - token 必須可 resolve,且 route 必須真的能回檔案 - `tests/hermes_cli/test_managed_downloads.py` 已固定: - URL shape - route 可下載 - `Content-Disposition` 帶原始副檔名 ### 4.5 不可接受行為 - 說有附件,實際沒有 MIME attachment - 附件有帶出但沒有副檔名 - 下載連結指到錯誤網域或重啟後失效 token - 附件太大寄失敗後,沒有自動切成 link-only --- ## 5. Approval / 對客通知契約 ### 5.1 approval 文案 - approval fallback 文案應為對客繁中,不應把 raw command / security scan 原文直接外露。 - 需要保留「可批准 / 拒絕」操作語意,但不洩漏敏感內容。 ### 5.2 approval redaction seam - `_redact_approval_command(...)` 是 approval prompt 的硬性 secret-egress boundary。 - chat platform path 與 SSE/API path 都必須在送出前實際使用 redacted command。 - `tests/gateway/test_approval_prompt_redaction.py` 已釘住這條契約。 --- ## 6. Shared drive workflow 契約 ### 6.1 查找與選檔契約 - shared drive 對話式工作流目前落在: - `~/.hermes/scripts/shared_drive_index_query.py` - `~/.hermes/scripts/shared_drive_lookup.py` - `~/.hermes/scripts/shared_drive_chat_workflow.py` - `search` 必須回傳可對話使用的候選清單,且每個候選都帶 `file_id`。 - 若命中資料夾,`children ` 必須能展開子檔案清單,供使用者最多選 10 檔。 ### 6.2 打包與敏感檔契約 - `bundle --file-ids ... --requester ` 必須: - 以 `file_id` 解析實際檔案 - 先複製到本機 staging - 產生 24h managed download link - 若命中個資 / 財務或無法可靠判讀內容,必須自動改走加密 zip,並保留第二通道密碼通知資訊。 ### 6.3 驗證契約 - bundle 結果必須帶 `verification` 欄位。 - 成功判準以 **真實 GET** 為準,不以 HEAD 為準。 - 不可把 `urllib` / Cloudflare `1010` 這類 bot-like 驗證誤判直接當成下載路徑壞掉。 ### 6.4 不可接受行為 - search 找得到候選,但沒有 `file_id`,導致無法進入選檔/打包流程 - children 展開不到資料夾內容,卻沒說明是 index 或 parent mapping 問題 - 敏感檔仍走未加密原檔 - 連結其實可用,卻因 HEAD/urllib 驗證誤判為壞掉 --- ## 7. 額度與權限治理契約 ### 6.1 governance store 是 live source of truth - principal、external identity、quota policy、model policy、dashboard role、admin audit log 的 authoritative store 都在 `GatewayUserStore` / `gateway/user_verification.py`。 - schema 至少包含: - `principals` - `external_identities` - `identity_enforcement` - `quota_policies` - `model_policies` - `dashboard_roles` - `principal_usage_daily` - `admin_audit_logs` ### 6.2 dashboard role contract - dashboard verification/admin 角色目前分三級: - `viewer` - `operator` - `admin` - 權限邏輯由 `hermes_cli/web_server.py::_require_verification_role(...)` 控制。 - 在 gated auth 模式下: - `viewer`:可讀 identities / principals / audit / quota / model / usage - `operator`:可 force-bind、unbind、block、unblock、resend verification、寫 quota/model policy - `admin`:可管理 dashboard role 本身 - 在 loopback / 非 auth_required 模式下,server 目前視為 `admin`;這是便利行為,不是未來所有部署都該仰賴的安全模型。 ### 6.3 quota policy contract - quota policy 目前已落地的資料欄位是: - `daily_message_limit` - `daily_token_limit` - `notes` - principal usage 目前已落地的統計欄位是: - `message_count` - `input_tokens` - `output_tokens` - `total_tokens` - `last_model` - `gateway/run.py` 在對話完成後會呼叫 `GatewayUserStore.record_usage_for_source(...)` 寫入 usage。 ### 6.4 quota 的目前邊界 - 目前 repo 內已明確存在的是: - quota policy CRUD - usage record / usage list - API / admin 測試 round-trip - 目前這一版文件 **不把尚未在 live code 看到的硬性 runtime deny / throttle** 假裝成已完整存在。 - 換句話說:目前 governance 比較像「可觀測 + 可設定 + 可對帳」,不是已全面接管所有 gateway runtime 拒絕邏輯。 ### 6.5 model policy contract - 每個 principal 可有: - `allowed_models` - `default_model` - 這些資料目前已可經 admin API round-trip 保存與查詢。 - 若未來要主張「特定 principal 只能用指定模型」,必須再補 runtime enforcement 驗證;沒有測到前,不可把它寫成既成事實。 ### 6.6 admin audit contract - 任何人工治理動作至少應留下 audit: - force bind - unbind - block / unblock - role assignment - quota / model policy 變更(若後續擴充,應一併補 audit) - 目前已確認 `force_bind_identity` 等身份治理動作可經 `/api/admin/verification/audit` 查回。 ### 6.7 不可接受行為 - principal 還在,但 quota / model policy / role 因 update 被洗掉或查不到 - dashboard 可看 identities,卻看不到 usage / quota 對帳 - 系統把「有 quota policy 紀錄」誤當成「runtime 一定已 enforce」 --- ## 7. 目前管理平台功能契約(current admin surface) ### 7.1 已確認存在的管理後台能力 依 `hermes_cli/web_server.py` 與 `tests/hermes_cli/test_web_verification_admin.py`,目前至少已有這些 admin API: - `GET /api/admin/verification/role` - `GET/POST /api/admin/verification/roles` - `GET /api/admin/verification/identities` - `GET /api/admin/verification/principals` - `POST /api/admin/verification/force-bind` - `POST /api/admin/verification/unbind` - `POST /api/admin/verification/block` - `POST /api/admin/verification/unblock` - `POST /api/admin/verification/resend` - `GET /api/admin/verification/audit` - `GET/POST /api/admin/quota-policies` - `GET/POST /api/admin/model-policies` - `GET /api/admin/principal-usage` ### 7.2 這代表「目前管理平台」最少能做什麼 - 查誰綁了哪個平台 identity - 查哪些 identities 被綁到同一 principal - 人工 force-bind / unbind / block / unblock - resend verification email - 指派 dashboard role - 設 quota / model policy - 查每日 principal usage - 查 admin audit log ### 7.3 目前不要過度聲稱的部分 - 我這次沒在 live code 裡看到一個已完成、明確對應上述所有治理 API 的前端 SPA 頁面證據。 - 所以目前比較穩的說法是: - **治理後台後端能力已存在並有測試覆蓋** - **前端顯示/操作面是否完整暴露全部能力,仍需額外做 UI 對照驗證** - 這可以避免未來有人誤以為「API 有 = UI 一定完整可用」。 --- ## 8. 這份契約如何維護 每次修改以下任一區塊,都要同步檢查本文件是否要更新: - `gateway/user_verification.py` - `gateway/run.py` - `gateway/platforms/base.py` - `plugins/platforms/line/adapter.py` - `plugins/platforms/email/adapter.py` - `hermes_cli/managed_downloads.py` - `hermes_cli/web_server.py` - 對應 tests 若改動造成契約變更,應同時更新: 1. 本文件 2. `gateway-state-and-sequence.md` 3. `gateway-regression-matrix.md` 4. 對應 regression tests