# ADR-012: 危險操作治理架構

**狀態**: 提案
**日期**: 2026-03-23
**決策者**: 統帥
**觸發事件**: Telegram Token 連續三次被 logOut 災難

---

## 背景

### 事故摘要

2026-03-23，AWOOOI API 程式碼中的 `logOut` API 呼叫導致 Telegram Bot Token 連續三次被永久失效：

```
時間線:
11:09 - 第一個 Token 被 logOut (舊版 AWOOOI API)
19:31 - 第二個 Token 被 logOut (舊 Pod 未完全終止)
19:39 - 第三個 Token 成功 (AWOOOI API 已停用 Telegram)
```

### 問題根因

| 問題 | 說明 |
|------|------|
| 危險程式碼進入生產 | `logOut` 永久失效 Token，無人攔截 |
| 無 Code Review | 涉及外部服務的危險操作未被審查 |
| 無執行時告警 | 危險操作執行時沒有任何通知 |
| 無審計日誌 | 無法追溯誰/何時執行了什麼 |
| 變更順序錯誤 | 給新 Token 時舊服務還在運行 |

---

## 決策

建立**三層危險操作治理架構**：

1. **預防層** - CI/CD 攔截危險模式
2. **執行層** - 危險操作需人工確認
3. **審計層** - 所有操作記錄可追溯

---

## 架構設計

```
┌─────────────────────────────────────────────────────────────────────┐
│              危險操作治理架構 (Dangerous Operations Governance)       │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  Layer 1: 預防層 (Prevention)                               │   │
│  │  ════════════════════════════                               │   │
│  │                                                             │   │
│  │  CI/CD Pipeline                                             │   │
│  │  ├── 危險模式掃描 (grep logOut, deleteWebhook, etc.)        │   │
│  │  ├── 外部服務 API 白名單檢查                                │   │
│  │  └── PR 標記 (涉及危險操作需額外審核)                       │   │
│  │                                                             │   │
│  │  Code Review                                                │   │
│  │  ├── CODEOWNERS: 危險檔案需 CTO/CISO 審核                   │   │
│  │  └── PR Template: 勾選「是否涉及外部服務」                  │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                              │                                      │
│                              ▼                                      │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  Layer 2: 執行層 (Execution Control)                        │   │
│  │  ═══════════════════════════════════                        │   │
│  │                                                             │   │
│  │  危險操作分級                                               │   │
│  │  ├── Tier 0 (禁止): logOut, revokeToken                    │   │
│  │  ├── Tier 1 (需審批): deleteWebhook, setWebhook            │   │
│  │  ├── Tier 2 (需告警): sendMessage (批量)                   │   │
│  │  └── Tier 3 (自動): getMe, getUpdates                      │   │
│  │                                                             │   │
│  │  執行前檢查                                                 │   │
│  │  ├── 產生簽核卡片 (Tier 1 操作)                             │   │
│  │  ├── 發送 Telegram 告警 (Tier 1-2 操作)                     │   │
│  │  └── 記錄到活躍事件                                         │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                              │                                      │
│                              ▼                                      │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  Layer 3: 審計層 (Audit Trail)                              │   │
│  │  ═════════════════════════════                              │   │
│  │                                                             │   │
│  │  記錄內容                                                   │   │
│  │  ├── 操作類型、時間、執行者                                 │   │
│  │  ├── 影響範圍 (哪些服務/Token)                              │   │
│  │  ├── 執行結果 (成功/失敗)                                   │   │
│  │  └── 關聯的 Incident ID                                     │   │
│  │                                                             │   │
│  │  儲存位置                                                   │   │
│  │  ├── PostgreSQL: audit_logs 表                              │   │
│  │  ├── SignOz: Trace 追蹤                                     │   │
│  │  └── 活躍事件: 顯示在 Dashboard                             │   │
│  │                                                             │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
```

---

## 危險 API 清單

### Telegram API

| API | 分級 | 說明 | 處理方式 |
|-----|------|------|----------|
| `logOut` | Tier 0 (禁止) | 永久失效 Token | CI/CD 攔截，禁止進入生產 |
| `close` | Tier 0 (禁止) | 關閉所有 session | CI/CD 攔截 |
| `deleteWebhook` | Tier 1 (審批) | 可能影響其他服務 | 需簽核卡片 |
| `setWebhook` | Tier 1 (審批) | 可能覆蓋其他設定 | 需簽核卡片 |
| `sendMessage` (批量) | Tier 2 (告警) | 可能 spam 用戶 | 發送告警 |
| `getUpdates` | Tier 3 (自動) | 只能單一實例 | 限定 OpenClaw |
| `getMe` | Tier 3 (自動) | 無副作用 | 允許 |

### K8s API

| 操作 | 分級 | 說明 | 處理方式 |
|------|------|------|----------|
| `kubectl delete namespace` | Tier 0 (禁止) | 刪除整個 namespace | 絕對禁止 |
| `kubectl delete pvc` | Tier 1 (審批) | 刪除持久化資料 | 需簽核 |
| `kubectl rollout restart` | Tier 2 (告警) | 重啟服務 | 發送告警 |
| `kubectl scale` | Tier 2 (告警) | 調整副本數 | 發送告警 |
| `kubectl get` | Tier 3 (自動) | 只讀操作 | 允許 |

### Database

| 操作 | 分級 | 說明 | 處理方式 |
|------|------|------|----------|
| `DROP DATABASE` | Tier 0 (禁止) | 刪除資料庫 | 絕對禁止 |
| `TRUNCATE TABLE` | Tier 1 (審批) | 清空表格 | 需簽核 |
| `DELETE FROM` (無 WHERE) | Tier 1 (審批) | 刪除所有資料 | 需簽核 |
| `ALTER TYPE` | Tier 2 (告警) | 修改 schema | 發送告警 |

---

## 實施計畫

### Phase 1: CI/CD 攔截 (Week 1)

```yaml
# .github/workflows/ci.yml
- name: Scan for Dangerous Operations
  run: |
    echo "🔍 掃描危險操作..."

    # Tier 0: 禁止的操作
    BANNED_PATTERNS="logOut|\.close\(\)|revokeToken|DROP DATABASE|TRUNCATE TABLE"

    if grep -rn --include="*.py" -E "$BANNED_PATTERNS" apps/; then
      echo "❌ 發現 Tier 0 禁止操作！"
      echo "請移除以下危險程式碼後重新提交"
      exit 1
    fi

    # Tier 1: 需要審批的操作
    REVIEW_PATTERNS="deleteWebhook|setWebhook|DELETE FROM"

    if grep -rn --include="*.py" -E "$REVIEW_PATTERNS" apps/; then
      echo "⚠️ 發現 Tier 1 操作，需要 CTO/CISO 審核"
      echo "::warning::此 PR 包含危險操作，請確認已通過審核"
    fi

    echo "✅ 危險操作掃描完成"
```

### Phase 2: CODEOWNERS (Week 1)

```
# .github/CODEOWNERS

# 涉及外部服務的檔案需要額外審核
apps/api/src/services/telegram_*.py @awoooi/cto @awoooi/ciso
apps/api/src/services/executor.py @awoooi/cto @awoooi/ciso
k8s/**/02-network-policy.yaml @awoooi/cio @awoooi/sre

# 危險操作相關
apps/api/src/services/*gateway*.py @awoooi/cto
```

### Phase 3: 執行時控制 (Week 2)

```python
# apps/api/src/core/dangerous_ops.py

from enum import Enum
from typing import Callable, Any
import structlog

logger = structlog.get_logger()

class OperationTier(Enum):
    FORBIDDEN = 0      # 絕對禁止
    REQUIRES_APPROVAL = 1  # 需要簽核
    REQUIRES_ALERT = 2     # 需要告警
    AUTOMATIC = 3          # 自動允許

DANGEROUS_OPERATIONS = {
    "telegram.logOut": OperationTier.FORBIDDEN,
    "telegram.close": OperationTier.FORBIDDEN,
    "telegram.deleteWebhook": OperationTier.REQUIRES_APPROVAL,
    "telegram.setWebhook": OperationTier.REQUIRES_APPROVAL,
    "k8s.deleteNamespace": OperationTier.FORBIDDEN,
    "k8s.deletePVC": OperationTier.REQUIRES_APPROVAL,
    "k8s.rolloutRestart": OperationTier.REQUIRES_ALERT,
}

async def execute_dangerous_operation(
    operation_name: str,
    operation_fn: Callable,
    *args,
    **kwargs
) -> Any:
    """執行危險操作的統一入口"""

    tier = DANGEROUS_OPERATIONS.get(operation_name, OperationTier.AUTOMATIC)

    if tier == OperationTier.FORBIDDEN:
        logger.error(
            "forbidden_operation_blocked",
            operation=operation_name,
        )
        raise PermissionError(f"操作 {operation_name} 被禁止執行")

    if tier == OperationTier.REQUIRES_APPROVAL:
        # 產生簽核卡片，等待 Y/n
        approval = await create_approval_request(
            title=f"危險操作: {operation_name}",
            description=f"即將執行 {operation_name}，需要人工確認",
            tier="Tier 1",
        )
        if not approval.approved:
            raise PermissionError("操作被拒絕")

    if tier in [OperationTier.REQUIRES_APPROVAL, OperationTier.REQUIRES_ALERT]:
        # 發送告警
        await send_telegram_alert(
            f"⚠️ 危險操作執行中: {operation_name}"
        )

    # 記錄審計日誌
    await log_audit_trail(
        operation=operation_name,
        tier=tier.name,
        args=str(args),
        kwargs=str(kwargs),
    )

    # 執行操作
    result = await operation_fn(*args, **kwargs)

    # 記錄結果
    await log_audit_trail(
        operation=operation_name,
        result="success",
    )

    return result
```

### Phase 4: 審計日誌 (Week 2)

```sql
-- 新增審計日誌表
CREATE TABLE IF NOT EXISTS audit_logs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    operation VARCHAR(255) NOT NULL,
    tier VARCHAR(50) NOT NULL,
    actor VARCHAR(255),  -- 執行者 (系統/用戶)
    target VARCHAR(255),  -- 影響目標
    args JSONB,
    result VARCHAR(50),  -- success/failure/blocked
    error_message TEXT,
    incident_id VARCHAR(50),  -- 關聯的 Incident
    trace_id VARCHAR(50),  -- SignOz Trace ID
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_audit_logs_operation ON audit_logs(operation);
CREATE INDEX idx_audit_logs_timestamp ON audit_logs(timestamp);
CREATE INDEX idx_audit_logs_tier ON audit_logs(tier);
```

---

## 變更管理流程

### Token/Secret 更新 SOP

```
┌─────────────────────────────────────────────────────────────────┐
│  Token/Secret 更新 SOP                                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Step 1: 停止所有使用該 Token 的服務                            │
│  ────────────────────────────────────                          │
│  - kubectl scale deployment --replicas=0                       │
│  - docker stop <container>                                     │
│  - 等待所有實例完全終止                                        │
│                                                                 │
│  Step 2: 驗證沒有殘留實例                                       │
│  ────────────────────────────                                  │
│  - kubectl get pods (確認沒有 Running/Terminating)             │
│  - docker ps (確認沒有相關容器)                                │
│                                                                 │
│  Step 3: 取得新 Token                                           │
│  ────────────────────────                                      │
│  - @BotFather → Revoke current token                           │
│  - 複製新 Token                                                │
│                                                                 │
│  Step 4: 更新到唯一的服務                                       │
│  ────────────────────────                                      │
│  - 更新 .env 或 K8s Secret                                     │
│  - 記錄到 memory/reference_telegram_token.md                   │
│                                                                 │
│  Step 5: 啟動服務並驗證                                         │
│  ────────────────────────                                      │
│  - 啟動服務                                                    │
│  - 檢查日誌確認無錯誤                                          │
│  - 測試 Telegram 連線                                          │
│                                                                 │
│  Step 6: 更新文檔                                               │
│  ────────────────                                              │
│  - 更新 memory 相關 MD                                         │
│  - 發送 Telegram 確認訊息                                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

---

## 驗收標準

| 項目 | 狀態 |
|------|------|
| CI/CD 危險模式掃描 | ⬜ |
| CODEOWNERS 危險檔案審核 | ⬜ |
| Tier 0 操作絕對禁止 | ⬜ |
| Tier 1 操作需簽核卡片 | ⬜ |
| Tier 2 操作發送告警 | ⬜ |
| 審計日誌表建立 | ⬜ |
| Token 更新 SOP 文檔 | ✅ |
| Memory 文檔更新 | ✅ |

---

## 附錄: 2026-03-23 事故完整時間線

```
11:09:47 - AWOOOI API 啟動，呼叫 logOut (第一個 Token 失效)
11:09:52 - OpenClaw 嘗試啟動失敗 "Logged out"
...
19:31:16 - 給第二個 Token
19:31:27 - 舊 AWOOOI Pod 呼叫 logOut (第二個 Token 失效)
19:31:31 - OpenClaw 啟動失敗 "Logged out"
19:33:00 - 新 AWOOOI Pod 部署完成 (但 Token 已死)
...
19:35:00 - 清空 AWOOOI API Telegram Token
19:35:30 - 重啟 AWOOOI API (不再碰 Telegram)
19:39:01 - 給第三個 Token
19:39:17 - OpenClaw 啟動成功
19:39:36 - 所有系統健康
```
