# ADR-UI-02: Contract Governance UI **狀態**:Accepted **日期**:2026-05-03(台北) **決策者**:統帥 **範圍**:M3 Contract Dashboard + M4 Contract Editor 的詳細設計 **關聯**:ADR-UI-01(Operator Console 架構)、ADR-112(contract governance)、ADR-113(outbox) --- ## 背景 Contract Dashboard(M3)和 Contract Editor(M4)是 Operator Console 最複雜的模組,需要展示六合約的全貌,並支援 draft → publish → activate 的工作流程。 --- ## M3 — Contract Dashboard ### 頁面設計 **路由**:`/awooop/contracts` **主要資訊**: ``` ┌─────────────────────────────────────────────────────────────┐ │ Contract Dashboard [+ New Draft] │ ├──────────┬──────────────────────┬──────────┬───────────────┤ │ Project │ Contract Family │ Active │ Last Updated │ ├──────────┼──────────────────────┼──────────┼───────────────┤ │ awoooi │ policy_routing │ v2.1 ✅ │ 2026-05-03 │ │ awoooi │ mcp_gateway │ v1.3 ✅ │ 2026-04-20 │ │ awoooi │ agent │ v1.0 ✅ │ 2026-03-15 │ │ awoooi │ project_tenant │ v1.0 ✅ │ 2026-01-10 │ │ awoooi │ runtime_run_state │ v1.1 ✅ │ 2026-04-28 │ │ awoooi │ channel_event │ v1.2 ✅ │ 2026-04-15 │ │ ewoooc │ policy_routing │ ⚠️ None │ — │ └──────────┴──────────────────────┴──────────┴───────────────┘ ``` **狀態 badge**: - `✅ v2.1`:有 active revision,lifecycle_status = active - `⚠️ None`:無 active revision(需要初始設定) - `🔄 Pending Activation`:有 published revision 等待 activate(approval 路徑) - `🔴 Outbox Lag`:outbox 有未送出事件超過 60 秒(ADR-113 告警) ### API 呼叫 ```typescript // GET /v1/platform/projects/{project_id}/contracts // Response: ContractSummary[] interface ContractSummary { project_id: string contract_family: ContractFamily contract_id: string active_revision_id: string | null active_version: string | null lifecycle_status: "active" | "no_active" | "pending_activation" last_updated_at: string has_pending_outbox: boolean // ADR-113 outbox lag indicator } ``` --- ## M4 — Contract Editor ### 頁面設計 **路由**:`/awooop/contracts/[contract_id]/revisions` **頁面佈局**: ``` 左側:Revision List 右側:Revision Detail ┌─────────────────────┐ ┌──────────────────────────────────┐ │ policy_routing/ │ │ Revision: v2.1 (Active) │ │ awoooi-sre │ │ │ │ ─────────────────── │ │ [Body JSON] [Signature] [History]│ │ v2.1 ● Active │ →選擇→ │ │ │ v2.0 Published │ │ { │ │ v1.9 Published │ │ "routing_rules": [...], │ │ v1.8 Published │ │ "model_priority": [...] │ │ v0.1 Draft │ │ } │ │ ─────────────────── │ │ │ │ [+ New Draft] │ │ Signature: ✅ Valid │ └─────────────────────┘ │ Published by: admin@awoooi │ │ Published at: 2026-05-03 14:23 │ │ │ │ [Activate] [Revoke] [Diff vs v2.0]│ └──────────────────────────────────┘ ``` ### 操作按鈕邏輯 | 按鈕 | 可見條件 | 行為 | |------|---------|------| | `[+ New Draft]` | 任何時候 | 建立新 draft revision | | `[Publish]` | status=draft | 呼叫 publish API + 簽章驗證 | | `[Activate]` | status=published | 路徑 A:送 approval;路徑 B:直接 activate | | `[Revoke]` | status=active | 確認對話框 → revoke(回到 no_active 狀態)| | `[Diff vs v2.0]` | status=published or active | 顯示與前一版本的 JSON diff | ### Activation Approval Flow(路徑 A) 對於 `policy_routing`、`mcp_gateway`、`runtime_run_state` 三類 contract(ADR-112 D3 路徑 A): ``` 點擊 [Activate] ↓ 顯示對話框: 「此 contract 需要 approval。 請說明變更原因:[ textarea ] [提交 Approval 請求] [取消]」 ↓ POST /v1/platform/projects/{project_id}/contracts/{contract_id}/activate Body: { "revision_id": "...", "change_reason": "..." } ↓ API 返回:{ "approval_run_id": "...", "status": "WAITING_APPROVAL" } ↓ 頁面顯示:「Approval 請求已送出 → 前往 Approval Queue」 ↓ M7 Approval Queue 顯示待審核項目 ``` ### 版本 Diff 視圖 ```typescript // 使用 react-diff-viewer 或自製 JSON diff import { DiffViewer } from "@/components/awooop/diff-viewer" // 對比兩個 revision 的 body_json ``` --- ## 後果 ### Benefits - Contract Dashboard 一眼看清六合約的 active 狀態 - Outbox lag indicator(ADR-113):若 relay worker 卡住,在 UI 立即可見 - Diff 視圖:activate 前可以比對版本差異,減少誤操作 ### Costs - JSON diff 顯示需要第三方套件或自製(約 100 行) - Activation approval flow 需要與 M7/M8 的 Approval 模組配合 --- ## 驗收標準 - [ ] M3:六合約全部顯示,狀態 badge 正確(Phase 5 整合測試) - [ ] M4:draft → publish → activate 完整流程可操作(Phase 5) - [ ] M4:路徑 A contract activate → Approval Queue 有對應請求(整合測試) - [ ] M4:JSON diff 視圖可比對版本差異(Phase 5) ## 關聯 - ADR-UI-01(Operator Console 整體路由) - ADR-112(contract governance API) - ADR-113(outbox,M3 的 outbox lag indicator)