emma-hermes/docs/gateway-resilience/gateway-functional-contract.md

13 KiB
Raw Blame History

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_fallbackreplay / 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 中應轉成可點擊的 <a href=...>

4.3 large attachment contract

  • 若附件總大小超過 email 限制:
    • 不應狂寄多封
    • 應改成單封通知 + 下載連結
    • payload 應保留:
      • attachments_omitted_reason = size_limit
      • omitted_filenames
      • download_links
  • 對客下載連結應優先使用 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 <folder_file_id> 必須能展開子檔案清單,供使用者最多選 10 檔。

6.2 打包與敏感檔契約

  • bundle --file-ids ... --requester <email> 必須:
    • 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.pytests/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