## 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>
174 lines
6.3 KiB
Markdown
174 lines
6.3 KiB
Markdown
# 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(管理員後台),讓平台管理員可以:
|
||
1. 管理 tenant(project)和 principal 角色
|
||
2. 查看和管理 contract revisions(六合約的 draft/publish/activate)
|
||
3. 監控 runs(run state, saga steps, budget usage)
|
||
4. 執行 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`:
|
||
|
||
```typescript
|
||
// 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**:
|
||
```typescript
|
||
// 從環境變數讀取(禁止 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 — 設計原則
|
||
|
||
1. **禁止 emoji,使用 Lucide icon**(feedback_no_emoji_use_icons)
|
||
2. **全站統一字體/顏色**(feedback_design_system_consistency)
|
||
3. **100% next-intl i18n**(feedback_i18n_zero_hardcode)— 所有文字走翻譯 key
|
||
4. **禁止內網 IP**(feedback_frontend_internal_ip_ban)— NEXT_PUBLIC_* 只用公網域名
|
||
5. **Logo SVG 與正式環境一致**(feedback_brand_logo_consistency)
|
||
|
||
### D6 — 即時更新(WebSocket / SSE)
|
||
|
||
Run Monitor(M5)和 Approval Queue(M7)需要即時更新:
|
||
|
||
```typescript
|
||
// 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 核心功能)
|