# ADR-021: Playbook 更新驗證策略

| 屬性 | 值 |
|------|-----|
| **狀態** | Accepted |
| **建立日期** | 2026-03-26 |
| **決策者** | 首席架構師 |
| **關聯** | Phase 8 P1 修復, leWOOOgo 積木化原則 |

## 背景

Router 層 (`playbooks.py`) 使用 `setattr()` 動態更新 Playbook 欄位，繞過 Pydantic 驗證：

```python
# ❌ 問題代碼 (Router 層)
update_data = request.model_dump(exclude_unset=True)
for field, value in update_data.items():
    setattr(playbook, field, value)  # 無驗證！
```

問題：
1. **資料完整性** - 非法狀態值可直接寫入
2. **違反分層** - Router 層執行業務邏輯
3. **安全風險** - 可修改 `playbook_id`, `success_count` 等欄位

## 決策

將更新邏輯移至 Service 層，實作 `update_with_validation()` 方法。

### 驗證規則

| 規則 | 說明 |
|------|------|
| **禁止修改欄位** | `playbook_id`, `created_at`, `success_count`, `failure_count`, `last_used_at` |
| **狀態轉換** | DRAFT → APPROVED (允許), APPROVED → DEPRECATED (允許), 反向 (禁止) |
| **DEPRECATED 狀態** | 不可修改任何欄位 |

### 實作

```python
# ✅ Service 層 (playbook_service.py)
async def update_with_validation(
    self,
    playbook_id: str,
    update_data: dict,
) -> Playbook | None:
    # 1. 過濾禁止修改的欄位
    # 2. 驗證狀態轉換
    # 3. 應用更新
    # 4. 儲存
```

```python
# ✅ Router 層 (playbooks.py)
updated = await service.update_with_validation(
    playbook_id=playbook_id,
    update_data=request.model_dump(exclude_unset=True),
)
```

## 影響

| 檔案 | 變更 |
|------|------|
| `src/services/playbook_service.py` | 新增 `update_with_validation()` |
| `src/api/v1/playbooks.py` | 改用 Service 方法 |

## 替代方案

| 方案 | 優點 | 缺點 | 決定 |
|------|------|------|------|
| 維持現狀 (setattr) | 無需改動 | 繞過驗證 | ❌ |
| Pydantic validator | 自動驗證 | 無法檢查狀態轉換 | ❌ |
| **Service 層驗證** | 完整控制 | 需新增方法 | ✅ |

## 相關

- ADR-003: leWOOOgo 積木化架構
- Skill 02: leWOOOgo Backend Core
