# AWOOOI 整體整合架構統合設計

> **文件類型**: 統合架構設計（Single Source of Truth for Integration）
> **優先級**: 🔴 統帥最高指令
> **建立**: 2026-03-29 13:27 (台北)
> **核心命題**: 所有節點必須在同一座大腦的神經網路中協同運作，不允許孤島。

---

## 第一部分：現況誠實盤點（精確）

### 已確認：比稽核報告更樂觀的部分

| 項目 | 稽核報告誤判 | 真實現況 |
|------|------------|---------|
| Worker SIGTERM 處理 | 報告說「缺失」 | ✅ **已實作**（`signal_worker.py:450-455`）— `signal.signal(SIGTERM)` + `shutdown_event` |
| Worker 優雅關機流程 | 報告說「需要實作」| ✅ **已實作**（`stop()` 方法，有心跳機制） |
| SignOz Webhook 路由 | 報告說「未部署」| ✅ **已路由**（`main.py:419`）|

### 已確認：比稽核報告更嚴峻的部分

| 項目 | 稽核報告版本 | 真實缺口 |
|------|------------|---------|
| Worker stop() timeout | 未提及 | ❌ **只有 5 秒**，AI 分析 30-60 秒會被強殺 |
| K8s terminationGracePeriodSeconds | 未提及 | ❌ **未設定**，K8s 預設 30 秒不夠用 |
| ESLint i18n 強制 | 說「CI 攔截」| ❌ **只有 TODO 注解**（`.eslintrc.js:20-22`），未實際安裝 plugin |
| Visual Regression 跨平台 | 說「截圖比對」| ❌ **Mac 與 CI Linux 字體渲染不同**，baseline 不能在 Mac 產生 |
| PostgreSQL HA | 說「Streaming Replication」| ❌ **無切換機制**，主庫掛了需要人工介入 |
| Redis HA | 完全未提及 | ❌ **無 Sentinel**，Redis 單點故障 |
| SSE Event Sourcing | 只設計了事件類型 | ❌ **F5 刷新後 GenUI 卡片全消失** |
| Kali 整合 | Cronjob 被動 | 🟡 層次太低，應升格為 SecurityAgent |

---

## 第二部分：完整整合地圖

### 2.1 系統神經網路拓撲

```
外部事件輸入
┌─────────────────────────────────────────────────────────────────┐
│  Alertmanager :9093  Sentry :9000  SignOz :3301               │
│  GitHub Actions       Kali Scanner              K8s Events     │
└──────────┬──────────────┬──────────────┬──────────────────────┘
           │              │              │
           ▼              ▼              ▼
┌────────────────────────────────────────────────────────────────┐
│               AWOOOI API (K3s :32334)                          │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐  ┌─────────────┐  │
│  │Alertmanager│  │Sentry   │  │SignOz     │  │Kali(未來)   │  │
│  │Webhook  │  │Webhook  │  │Webhook    │  │Webhook      │  │
│  └────┬─────┘  └────┬─────┘  └─────┬─────┘  └──────┬──────┘  │
│       │              │              │                │          │
│       └──────────────┴──────────────┴────────────────┘          │
│                              │                                    │
│                              ▼                                    │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                Signal Worker (Redis XREADGROUP)           │   │
│  │  ← 消費 awoooi:signals stream                            │   │
│  │  → IncidentEngine (聚合 / GraphRAG / 持久化)              │   │
│  └──────────────────────────┬───────────────────────────────┘   │
│                              │                                    │
│                              ▼                                    │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │              OpenClaw (192.168.0.188:8089)                 │   │
│  │  決策引擎: RCA → Blast Radius → Risk → Action             │   │
│  │  工具: kubectl / SSH / Prometheus / SigNoz                │   │
│  └──────┬─────────────────────┬────────────────────────────┘   │
│         │                     │                                   │
│         ▼                     ▼                                   │
│  Telegram Bot          Approval DB (PostgreSQL)                   │
│  統帥通知              審核佇列                                     │
└──────────────────────────────────────────────────────────────────┘
                              │
                    統帥批准 / 拒絕
                              │
                 ┌────────────▼────────────┐
                 │   Auto-Repair Actions   │
                 │ restart/scale/rollback  │
                 └─────────────────────────┘
```

### 2.2 前端整合地圖

```
AWOOOI Web (K3s :32335 / Next.js)
┌─────────────────────────────────────────────────────────────────┐
│                                                                   │
│  ┌── Dashboard (/) ─────────────────────────────────────────┐   │
│  │  AutonomyIndexPanel ← GET /api/v1/stats/autonomy         │   │
│  │  SystemPulseRow     ← GET /api/v1/stats/overview          │   │
│  │  DecisionZone       ← SSE /api/v1/approvals/stream        │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                   │
│  ┌── Omni-Terminal ─────────────────────────────────────────┐   │
│  │  Input Area         → POST /api/v1/terminal/command       │   │
│  │  ThinkingStream     ← SSE /api/v1/terminal/stream/{id}    │   │
│  │  GenUI Renderer     ← event: render_ui                    │   │
│  │  Event Replay       ← Redis List (Last-Event-ID)          │   ← ❌ 缺失
│  └──────────────────────────────────────────────────────────┘   │
│                                                                   │
│  ┌── Knowledge Base (/knowledge-base) ─────────────────────┐   │
│  │  ❌ 空白頁面，缺後端 API                                  │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                   │
│  [深度調查跳脫入口]                                               │
│  → SigNoz: http://192.168.0.188:3301 (新分頁)                   │
│  → Grafana: http://192.168.0.188:3000 (新分頁)                  │
│  → Sentry:  http://192.168.0.110:9000 (新分頁)                  │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘
```

---

## 第三部分：六大缺口的系統整合修復方案

### 缺口 1：Worker terminationGracePeriodSeconds 不足

**問題根源**：`signal_worker.py` 的 `stop()` 等 5 秒，但 AI 分析任務最長 60 秒。K8s 預設 `terminationGracePeriodSeconds: 30`。兩個值都不夠，且彼此沒有對齊。

**與整體整合的依賴**：
- Worker 縮容影響：HPA 縮容時 → K8s 發 SIGTERM → `stop()` 被調用 → 5 秒後強殺
- 上游依賴：Sentry 分析任務、Alertmanager 分析任務都在 Worker background task 中執行
- 下游影響：PostgreSQL 寫入可能不完整（Incident 狀態 Dirty）

**修復：三層數值對齊**

```yaml
# k8s/awoooi-prod/08-deployment-worker.yaml
# 修改 terminationGracePeriodSeconds
spec:
  template:
    spec:
      terminationGracePeriodSeconds: 90  # 給 Worker 足夠時間完成當前任務
      containers:
        - name: awoooi-worker
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 5"]  # 給 K8s 時間發 SIGTERM
```

```python
# apps/api/src/workers/signal_worker.py
# 修改 stop() 的 timeout 與 K8s terminationGracePeriodSeconds 對齊

async def stop(self) -> None:
    if not self._running:
        return
    self._running = False
    if self._task:
        try:
            # 從 5 秒改為 75 秒（比 terminationGracePeriodSeconds=90 少 15 秒緩衝）
            await asyncio.wait_for(self._task, timeout=75.0)
        except TimeoutError:
            logger.warning("signal_worker_stop_timeout_forcekill")
            self._task.cancel()
        except asyncio.CancelledError:
            pass
    logger.info("signal_worker_stopped")
```

**整合驗證指令**：
```bash
# 模擬 K8s 縮容，確認 Worker 優雅關機
kubectl scale deployment awoooi-worker -n awoooi-prod --replicas=0
kubectl logs -n awoooi-prod $(kubectl get pod -n awoooi-prod -l app=awoooi-worker -o name) --tail=20

# 預期看到：
# shutdown_signal_received signal=15
# signal_worker_shutting_down
# signal_worker_shutdown_complete
# （整個流程在 90 秒內完成）
```

---

### 缺口 2：ESLint i18n 強制攔截（eslint-plugin-i18next）

**問題根源**：`.eslintrc.js:20-22` 只有 TODO 注解，未安裝 `eslint-plugin-i18next`。

**與整體整合的依賴**：
- 這是 i18n 清零後的**防護層**：清零是清過去的債，ESLint 是防未來的債
- 需在 `pnpm build` 和 `pnpm lint` CI 步驟中阻擋
- 影響：所有前端開發流程（AI 生成代碼也必須通過）

**修復：安裝並啟用 Plugin**

```bash
# Step 1: 安裝
cd apps/web
pnpm add -D eslint-plugin-i18next
```

```javascript
// apps/web/.eslintrc.js 修改後
module.exports = {
  extends: [
    '@awoooi/eslint-config/react',
    'next/core-web-vitals',
    'plugin:i18next/recommended',   // ← 新增
  ],
  plugins: ['i18next'],              // ← 新增
  parserOptions: {
    project: './tsconfig.json',
    tsconfigRootDir: __dirname,
  },
  rules: {
    '@next/next/no-html-link-for-pages': 'off',
    'no-console': 'off',

    // 🚨 i18n 鐵律：所有 JSX 文字必須透過 t() 函式
    // 違反此規則 = PR 阻擋（error 級別）
    'i18next/no-literal-string': ['error', {
      markupOnly: true,     // 只攔截 JSX 文字節點（非 JS 字串）
      ignoreAttribute: [    // 技術屬性不攔截
        'className', 'id', 'href', 'src', 'type', 'key',
        'data-testid', 'aria-label', 'placeholder'
      ],
    }],

    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
    '@typescript-eslint/consistent-type-imports': 'warn',
    'no-constant-condition': 'warn',
  },
  ignorePatterns: [
    'node_modules', '.next', 'out', 'dist', 'test-results',
    '*.config.js', '*.config.ts',
  ],
}
```

**與 CI 整合（必須加入 cd.yaml）**：

```yaml
# .github/workflows/cd.yaml 在 Build 之前加入 Lint 步驟
- name: 🔍 ESLint i18n 強制檢查
  run: |
    cd apps/web
    pnpm lint
  # 失敗 = 有硬編碼字串 = 直接阻擋部署
```

**⚠️ 重要**：第一次啟用 `eslint-plugin-i18next` 後，**現有的 40+ 違規會立刻全部報錯**。因此必須先完成 i18n 清零，再啟用此 Rule。**正確順序**：
1. i18n 清零（一次性修復 40+ 違規）
2. 安裝 eslint-plugin-i18next（啟用防護）
3. 加入 CI Lint 步驟

---

### 缺口 3：Visual Regression 跨平台渲染問題

**問題根源**：Mac（M1/M2/M3）vs GitHub Actions（Linux Ubuntu）的字體渲染引擎不同（CoreText vs FreeType），導致截圖像素不吻合。

**與整體整合的依賴**：
- Baseline 快照必須統一來源（CI Docker 環境）
- 每次更新 Baseline 必須是可審計的（透過 PR），不能在本機靜默更新

**修復：Docker 強制基準線更新 + threshold 調整**

```json
// apps/web/package.json 新增 scripts
{
  "scripts": {
    "test:visual:update": "docker run --rm -v $(pwd):/work -w /work -p 3000:3000 mcr.microsoft.com/playwright:v1.44.0-jammy pnpm exec playwright test --update-snapshots --project=chromium --grep @visual",
    "test:visual": "pnpm exec playwright test --project=chromium --grep @visual"
  }
}
```

```typescript
// apps/web/playwright.config.ts 修改截圖比對設定
export default defineConfig({
  expect: {
    toHaveScreenshot: {
      threshold: 0.05,        // 允許 5% 差異（吸收跨平台微小差）
      maxDiffPixelRatio: 0.05,
      // 強制使用 CI 環境的字體設定
    },
  },
  use: {
    // 截圖時的視窗大小固定，避免不同螢幕 DPI 差異
    viewport: { width: 1280, height: 720 },
    deviceScaleFactor: 1,    // 強制 1x，避免 Retina 差異
  },
});
```

**強制規範（加入 .awoooi-agent-rules.md 條款）**：

```markdown
## 條款 21：Visual Regression Baseline 更新規範

🚨 絕對禁止在本機 Mac 環境執行 `--update-snapshots`
✅ 更新 Baseline 必須透過以下流程：

1. 在本機執行：`pnpm test:visual:update`（Docker 環境）
2. Docker 生成的 .png 截圖自動存入 tests/e2e/__snapshots__/
3. 提 PR，標注 📸 VISUAL_UPDATE
4. 統帥視覺審核截圖後方可合併
```

---

### 缺口 4：PostgreSQL HA（Patroni / CloudNativePG）

**問題根源**：PostgreSQL 在 .188 上是單一 Docker 容器，K3s 的 Datastore 也依賴它（ADR-033）。資料庫掛掉 = K3s 控制面 + AWOOOI 資料同時失效。

**與整體整合的依賴**：
- PostgreSQL 是 AWOOOI 的 Episodic Memory（Incidents、Approvals、Audit Logs）
- PostgreSQL 也是 K3s 的 HA Datastore（120/121 節點的 K3s 元數據）
- Auto-Repair 對 PostgreSQL 執行 `docker restart` 是**危險的**（可能 Dirty Page）

**修復路線圖（三階段）**：

```
Phase DB-A（1週，低風險）：
  監控補強
  ├── 啟用 PG slow query log (log_min_duration_statement = 2000ms)
  ├── 加入 pg_stat_statements extension 並接入 Prometheus
  └── 關閉 auto_repair.postgres.restart（防止 Dirty Page）

Phase DB-B（1月，中風險）：
  備份策略
  ├── Velero + PostgreSQL Volume Snapshot（已有 Velero，需加 Volume 備份）
  └── 確認 WAL archiving 到 MinIO（WAL-E/WAL-G）

Phase DB-C（Q2，需評估）：
  HA 策略評估
  ├── 方案 A：CloudNativePG（K8s 原生 PostgreSQL Operator）
  │     → 在 K3s 中部署 CloudNativePG，主從自動切換
  ├── 方案 B：Patroni + keepalived（VM 層 HA）
  │     → 在 .188 和備用機上部署 Patroni
  └── 方案 C：Citus（分片，過於複雜，暫不考慮）

  推薦：方案 A (CloudNativePG)，與 K3s 最整合
```

**立即可執行的防護措施**：

```yaml
# k8s/awoooi-prod/manual-remediation/postgres-recovery.yaml
# 建立 PostgreSQL 緊急修復 Playbook（人工操作）

# 事件：PostgreSQL 掛了
# 動作：
# 1. OpenClaw 發告警 + Telegram
# 2. AlterManager 生成 CRITICAL Approval（不自動修復）
# 3. 統帥核准後，執行以下指令：
#    ssh root@192.168.0.188 'docker restart awoooi-postgres'
#    kubectl rollout restart deployment/awoooi-api -n awoooi-prod
#    kubectl rollout restart deployment/awoooi-worker -n awoooi-prod
```

---

### 缺口 5：Redis HA（Sentinel 模式）

**問題根源**：Redis 在 .188 上是單一容器（port 6380），無備援。Redis 同時承載：
- Working Memory（Incident 聚合狀態）
- SSE Terminal Event Store（未來的 Event Source）
- Sentry Dedup Cache（10分鐘去重 TTL）
- Anomaly Counter（ADR-037 核心數據）

**與整體整合的依賴**：
- Redis 掛掉 = Signal Worker 無法消費事件 = 整個告警鏈路中斷
- AOF 啟用對性能有影響，需要評估

**修復路線圖**：

```
Phase Redis-A（立即，0風險）：
  確認 AOF 配置
  ├── ssh root@192.168.0.188 'docker exec awoooi-redis redis-cli CONFIG GET appendonly'
  └── 確認 appendonly yes（否則重啟後 Working Memory 歸零）

Phase Redis-B（1月，中等工程量）：
  Redis Sentinel 部署（在 .110 上部署 Sentinel + Replica）
  ├── .188：Master（現有）
  ├── .110：Replica + Sentinel
  └── OpenClaw 使用 redis-sentinel:// URI，自動發現 Master

  配置變更：
  # AWOOOI API 連線改用 Sentinel
  REDIS_URL=redis-sentinel://sentinel1:26379/awoooi-master
```

**立即可執行的防護措施**：

```bash
# 確認 Redis AOF 狀態
ssh root@192.168.0.188 'docker exec awoooi-redis redis-cli CONFIG GET appendonly; \
  docker exec awoooi-redis redis-cli CONFIG GET appendfsync; \
  docker exec awoooi-redis redis-cli INFO persistence | grep aof'

# 若 appendonly = no，立即啟用（需重啟 Redis）
ssh root@192.168.0.188 'docker exec awoooi-redis redis-cli CONFIG SET appendonly yes'
# 注意：CONFIG SET 是即時生效的，不需要重啟
```

---

### 缺口 6：SSE Event Sourcing（Terminal 狀態不丟失）

**問題根源**：Omni-Terminal 的 SSE 串流是無狀態的，F5 刷新後所有 `render_ui` GenUI 卡片消失。

**與整體整合的依賴**：
- 這是 Agentic Workspace 用戶體驗的底層設施
- 依賴 Redis List 作為 Event Store（如果 Redis 無 AOF，重啟後也丟）
- 必須與 SSE 三種事件類型設計同步建立

**修復：三層機制**

```
Layer 1: 後端 Event Store（Redis List）
  terminal.py → 每個 SSE 事件同步寫入 Redis List
  Key: terminal:events:{command_id}
  TTL: 3600 秒（1小時）

Layer 2: 前端 Reconnect（Last-Event-ID）
  useTerminalSSE → EventSource 自動帶 Last-Event-ID
  後端收到後：從 Redis 撈出錯過的事件 → Replay → 接上即時 Stream

Layer 3: 本地 Zustand 持久化
  useTerminalStore → 用 zustand/middleware/persist 持久化到 sessionStorage
  F5 刷新 → 從 sessionStorage 恢復 GenUI 卡片（UI 層快速恢復）
  同時 → SSE 重連補齊 Server 端新事件
```

**後端實作關鍵代碼**：

```python
# apps/api/src/api/v1/terminal.py 補充 Event Store 機制

import json
from src.core.redis_client import get_redis

async def stream_with_persistence(command_id: str, event_type: str, data: dict):
    """
    SSE 事件輸出 + 同步寫入 Redis Event Store
    確保 F5 刷新後可以 Replay
    """
    redis = get_redis()
    
    event_payload = {
        "type": event_type,
        "data": data,
        "timestamp": now_taipei_iso()
    }
    
    # 寫入 Redis List（RPUSH append to right）
    key = f"terminal:events:{command_id}"
    await redis.rpush(key, json.dumps(event_payload))
    await redis.expire(key, 3600)  # 1 小時後自動清理
    
    # 返回 SSE 格式字串
    return f"id: {redis.llen(key)}\nevent: {event_type}\ndata: {json.dumps(data)}\n\n"


@router.get("/stream/{command_id}/replay")
async def replay_terminal_events(command_id: str, last_event_id: int = 0):
    """
    從指定 ID 開始 Replay 錯過的事件（用於 F5 重連）
    """
    redis = get_redis()
    key = f"terminal:events:{command_id}"
    
    # 取出 last_event_id 之後的所有事件
    events = await redis.lrange(key, last_event_id, -1)
    
    async def generate():
        for i, event_json in enumerate(events):
            event = json.loads(event_json)
            yield f"id: {last_event_id + i + 1}\nevent: {event['type']}\ndata: {json.dumps(event['data'])}\n\n"
    
    return StreamingResponse(generate(), media_type="text/event-stream")
```

---

## 第四部分：整合依賴關係圖（執行順序）

```
┌─────────────────────────────────────────────────────────────────────┐
│                    整合執行優先序                                     │
├──────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  🔴 P0 立即執行（本週，阻塞後續工作）                                  │
│  ┌─────────────────────────────────────────────────────────┐         │
│  │  1. 確認 Redis AOF 狀態（5min）                          │         │
│  │  2. Worker terminationGracePeriodSeconds 修正（1h）      │         │
│  │  3. i18n 清零（4h）← 必須先於 ESLint Plugin 安裝        │         │
│  │  4. ESLint i18n Plugin 安裝並啟用（1h）                  │         │
│  └─────────────────────────────────────────────────────────┘         │
│                              ↓（完成後解鎖）                           │
│                                                                       │
│  🟠 P1 短期（2-3週）                                                  │
│  ┌─────────────────────────────────────────────────────────┐         │
│  │  5. Sentry Comment Token 配置（2h）                      │         │
│  │  6. SignOz 告警規則部署到 .188（2h）                      │         │
│  │  7. Worker HPA YAML 部署（30min）                        │         │
│  │  8. E2E CI Weekly 排程（30min）                          │         │
│  │  9. Visual Regression Docker 基準線建立（2h）            │         │
│  └─────────────────────────────────────────────────────────┘         │
│                              ↓（完成後解鎖）                           │
│                                                                       │
│  🟡 P2 中期（Month 2）                                                │
│  ┌─────────────────────────────────────────────────────────┐         │
│  │  10. Omni-Terminal SSE Event Sourcing（8h）              │         │
│  │  11. Storybook 10 心組件（8h）                           │         │
│  │  12. Nexus AI 自治率 UI（8h）                            │         │
│  │  13. FinOps Dashboard UI（8h）                          │         │
│  │  14. Redis Sentinel 部署（1天）                          │         │
│  └─────────────────────────────────────────────────────────┘         │
│                              ↓（完成後解鎖）                           │
│                                                                       │
│  ⚪ P3 長期（Q2-Q3）                                                   │
│  ┌─────────────────────────────────────────────────────────┐         │
│  │  15. CloudNativePG 評估與導入（2天+）                    │         │
│  │  16. Kali SecurityAgent（MCP Tool 化）                   │         │
│  │  17. Knowledge Base 後端全建                             │         │
│  │  18. Phase 4 視覺靈魂注入                               │         │
│  └─────────────────────────────────────────────────────────┘         │
└─────────────────────────────────────────────────────────────────────┘
```

---

## 第五部分：整合風險矩陣

| 風險 | 可能性 | 影響 | 緩解措施 |
|------|--------|------|---------|
| **ESLint 啟用後大量報錯** | 100%（有 40+ 違規） | CI 完全阻塞 | 先清零再啟用，正確順序執行 |
| **Worker timeout 修改引發 Pod 啟動異常** | 低 | 服務中斷 | 先在 Dev namespace 測試 |
| **Redis AOF 啟用影響性能** | 中 | 延遲微增 | 使用 `appendfsync everysec`（非 `always`）|
| **Visual Regression Docker 第一次 Baseline** | — | 需要 1-2h 產生基準線 | 排在非尖峰時段執行 |
| **PostgreSQL 無 HA 期間主庫故障** | 低 | 完全停機 | 備份策略（Velero）+ Playbook 就位 |
| **SSE Event Sourcing Redis 依賴** | — | Redis 故障時 Event 丟失 | 先解決 Redis AOF，再實作 Event Sourcing |

---

## 第六部分：監控機制與前端的整合設計原則

（承接 `MONITORING_ARCHITECTURE_DEEP_DIVE.md` 的「三義分離原則」）

**整合設計的核心約束**：

```
1. 監控數據 → 後台靜默消化，不直接呈現給統帥
   └─ 99%：Prometheus/SigNoz/Sentry 原始數據
   └─ 1%：AI 無法自動處理 → 浮現為 ApprovalCard

2. 前端不直接查詢 Prometheus/SigNoz
   └─ 所有監控數據透過 AWOOOI API 統一封裝
   └─ API 層：/api/v1/stats/overview, /api/v1/slo, /api/v1/finops

3. 深度調查只能透過「智能跳脫」（新分頁）
   └─ GenUI 卡片提供 ExternalLinks 按鈕
   └─ 絕對禁止 iframe 嵌入 Grafana/SigNoz

4. AI 自治率指數是前端唯一的「監控摘要」入口
   └─ Dashboard (/) 最頂部：AutonomyIndexPanel
   └─ 用一個數字替代所有圖表
```

---

*「整合不是把所有工具接在一起，而是讓所有工具服務同一個大腦。」* 🦞
