## Phase 0(文件層,全部 Accepted) - ADR-106/107:AwoooP 平台架構 + 儲存策略 - ADR-111~118:Bootstrap → RLS 七項核心 ADR - ADR-119~124:SAGA → Singleton Decomposition 六項 ADR - ADR-UI-01~04:Operator Console 四個 UI ADR ## Phase 1(DB schema + migration) - awooop_phase1_control_plane_2026-05-04.sql:7 張新表 + trigger + RLS - Step 1:三角色(platform_admin/migration BYPASSRLS,awooop_app 受 RLS) - Step 13:GRANT awooop_app 最小權限(7 條) - Step 14:RLS fail-closed,移除 __platform__ 後門 - awooop_phase1_batch1_rls_2026-05-04.sql:高流量四表三步式 ADD COLUMN - awooop_phase1_batch1_backfill.py:SKIP LOCKED 分批回填腳本 - awooop_models.py:7 個 SQLAlchemy 2.x models ## Critic 修正(4 Critical + 3 Major) - C-1:ADD CONSTRAINT IF NOT EXISTS → DO 塊 + pg_constraint 查詢 - C-2:__mapper_args__ 字串 list → primary_key=True on mapped_column - C-3:__platform__ RLS 後門 → 全移除,改用 BYPASSRLS role - C-4:awooop_app role 從未建立 → Step 1 + 7 條 GRANT - M-1:active_pointer_guard SECURITY DEFINER(FORCE RLS 跨租戶保護) - M-2:pg_partman create_parent 加冪等防護 - M-3:immutability trigger 新增身份欄位保護(project_id/family/contract_id) ## Task 1.2 修補 - agent_loader.py:硬編碼 Mac 路徑 → AGENTS_DIR 環境變數 - Dockerfile:補 COPY .claude/agents/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6.3 KiB
6.3 KiB
ADR-UI-01: Operator Console Architecture
狀態:Accepted 日期:2026-05-03(台北) 決策者:統帥 範圍:AwoooP Operator Console 前端整體架構、與現有 apps/web/ 的整合方式 關聯:ADR-106(六合約)、ADR-112(contract governance)、ADR-115(principal mapping)
背景
AwoooP 需要一個 Operator Console(管理員後台),讓平台管理員可以:
- 管理 tenant(project)和 principal 角色
- 查看和管理 contract revisions(六合約的 draft/publish/activate)
- 監控 runs(run state, saga steps, budget usage)
- 執行 approval decisions(human approval for WAITING_APPROVAL runs)
現有 apps/web/ 是 Next.js 14 前端,已有 AI 監控 dashboard。Operator Console 需要在不破壞現有功能的情況下整合。
決策
D1 — 整合方式:子路由擴展(不建新應用)
Operator Console 作為 apps/web/ 的新路由段:
apps/web/src/app/
├── (existing)/ ← 現有頁面(不動)
│ ├── dashboard/
│ ├── incidents/
│ └── ...
└── awooop/ ← 新增 Operator Console
├── layout.tsx ← Console layout(sidebar + auth gate)
├── tenants/ ← Tenant 管理
│ ├── page.tsx
│ └── [project_id]/
│ ├── page.tsx
│ └── principals/
├── contracts/ ← Contract governance
│ ├── page.tsx
│ └── [contract_id]/
│ ├── page.tsx
│ └── revisions/
├── runs/ ← Run 監控
│ ├── page.tsx
│ └── [run_id]/
│ └── page.tsx
└── approvals/ ← Approval decisions
├── page.tsx
└── [run_id]/
└── page.tsx
D2 — Auth Gate(Operator Console 專屬)
Operator Console 需要 platform_subject.roles 中包含 admin 或 approver:
// apps/web/src/app/awooop/layout.tsx
import { redirect } from "next/navigation"
import { getServerSession } from "@/lib/auth"
import { hasOperatorRole } from "@/lib/awooop-auth"
export default async function AwoooPLayout({ children }) {
const session = await getServerSession()
if (!session || !hasOperatorRole(session.user)) {
redirect("/unauthorized")
}
return (
<div className="awooop-operator-layout">
<AwoooPSidebar />
<main>{children}</main>
</div>
)
}
D3 — API Client 分層
apps/web/src/lib/
├── awooop/
│ ├── client.ts ← AwoooP Platform API client(/v1/platform/...)
│ ├── types.ts ← TypeScript types(從 API spec 生成)
│ ├── runs.ts ← Run 相關 API calls
│ ├── contracts.ts ← Contract 相關 API calls
│ ├── tenants.ts ← Tenant 相關 API calls
│ └── approvals.ts ← Approval 相關 API calls
API base URL:
// 從環境變數讀取(禁止 hardcode 內網 IP,feedback_frontend_internal_ip_ban)
const AWOOOP_API_BASE = process.env.NEXT_PUBLIC_AWOOOP_API_URL
// K8s ConfigMap 設置:NEXT_PUBLIC_AWOOOP_API_URL=https://api.awoooi.com
D4 — 8 個 UI 模組(Phase 5 實作)
| 模組 | 路由 | 功能 | ADR 關聯 |
|---|---|---|---|
| M1 Tenant List | /awooop/tenants |
列出所有 project,建立/停用 | ADR-115 |
| M2 Principal Manager | /awooop/tenants/[id]/principals |
角色管理,auto-provision 確認 | ADR-115 |
| M3 Contract Dashboard | /awooop/contracts |
六合約總覽,active revision 狀態 | ADR-106 |
| M4 Contract Editor | /awooop/contracts/[id]/revisions |
draft/publish/activate 操作 | ADR-112 |
| M5 Run Monitor | /awooop/runs |
所有 run 的即時狀態 | ADR-114 |
| M6 Run Detail | /awooop/runs/[run_id] |
saga_steps, budget, trace_id | ADR-119 |
| M7 Approval Queue | /awooop/approvals |
WAITING_APPROVAL run 列表 | ADR-114 |
| M8 Approval Decision | /awooop/approvals/[run_id] |
approve/reject + token 生成 | ADR-116 |
各模組的詳細設計在 ADR-UI-02 / ADR-UI-03 / ADR-UI-04。
D5 — 設計原則
- 禁止 emoji,使用 Lucide icon(feedback_no_emoji_use_icons)
- 全站統一字體/顏色(feedback_design_system_consistency)
- 100% next-intl i18n(feedback_i18n_zero_hardcode)— 所有文字走翻譯 key
- 禁止內網 IP(feedback_frontend_internal_ip_ban)— NEXT_PUBLIC_* 只用公網域名
- Logo SVG 與正式環境一致(feedback_brand_logo_consistency)
D6 — 即時更新(WebSocket / SSE)
Run Monitor(M5)和 Approval Queue(M7)需要即時更新:
// Server-Sent Events(比 WebSocket 更簡單,適合單向推送)
// GET /v1/platform/runs/stream?project_id=awoooi&status=RUNNING,WAITING_APPROVAL
const eventSource = new EventSource(
`/v1/platform/runs/stream?project_id=${projectId}`
)
eventSource.onmessage = (event) => {
const update = JSON.parse(event.data) as RunStateUpdate
updateRunInCache(update)
}
後果
Benefits
- 複用現有 apps/web/ 的 auth、i18n、設計系統(不重造輪子)
- 子路由設計:現有功能不受影響,可獨立部署 Operator Console 路由
- Phase 5 前,
/awooop/*路由可以用 feature flag 隱藏(9 個 feature flag 中的ENABLE_OPERATOR_CONSOLE)
Costs
- 需要在 apps/web/ 建立
awooop/目錄結構(~15 個新檔案) - API client 需要 TypeScript type generation(從 OpenAPI spec)
Risks
- Operator Console 的 auth gate 若有漏洞 → 非管理員可存取敏感操作
- 緩解:auth gate 在 layout.tsx 實作(server-side),不依賴 client-side 隱藏
驗收標準
/awooop路由在apps/web/src/app/建立(Phase 5)- auth gate:非 admin/approver → redirect
/unauthorized(整合測試) NEXT_PUBLIC_AWOOOP_API_URL不含內網 IP(CI lint check)- 8 個模組全部有對應路由(Phase 5 完成)
關聯
- ADR-106(六合約,Contract Dashboard M3/M4 的資料來源)
- ADR-112(contract governance API,M4 的操作路徑)
- ADR-114(run state,M5 M6 的資料來源)
- ADR-115(principal mapping,M1 M2)
- ADR-116(approval token,M8 核心功能)