# 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 核心功能）
