Files
awoooi/docs/HARD_RULES.md
OG T e4bc3ec0ee docs(hard-rules): Prompt-Model 同步鐵律 — LLM Schema Drift 禁令
血的教訓 (2026-04-17): SuggestedAction enum prompt/model 不同步
→ NemoTron 輸出 investigate → Pydantic 爆炸 → 全系統 fallback 待分析

新增強制鐵律:
- 修改 prompts.py 必須同步更新 models/ai.py
- 接收 LLM JSON 的 Model 必須有 validator + fallback
- 禁止靜默死亡(必須 log 具體失敗欄位)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:48:50 +08:00

680 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AWOOOI 絕對禁止規則 (Hard Rules)
> 違反任何一條 = 重大事故
---
## 文件資訊
| 欄位 | 值 |
|------|-----|
| **版本** | v2.0 |
| **建立日期** | 2026-03-20 (台北) |
| **建立者** | Claude Code |
| **最後修改** | 2026-04-16 (台北) |
| **修改者** | Claude Code + ogt (新增孤島開發/主動執行熔斷/自循環工作流/狀態機驗證鐵律) |
### 變更紀錄
| 版本 | 日期 | 執行者 | 變更內容 |
|------|------|--------|----------|
| v1.0 | 2026-03-20 | Claude Code | 初始建立 |
| v1.1 | 2026-03-23 | Claude Code | 新增 No Mock Testing |
| v1.2 | 2026-03-24 | Claude Code | 新增 Deployment Verification |
| v1.3 | 2026-03-25 | Claude Code | 新增 Timezone Taipei |
| v1.4 | 2026-03-25 | Claude Code | 新增 Change Annotation + 文件資訊區塊 |
| v1.5 | 2026-03-26 | Claude Code | 關聯紅區治理 (RED_ZONES.md) |
| v1.6 | 2026-03-30 | Claude Code | 🔴🔴🔴 前端內網 IP 禁令 (瀏覽器權限事故) |
| v1.7 | 2026-04-02 | Claude Code | Phase 24 AI Router 重構規範 (DI/隱私/絞殺者) |
| v1.8 | 2026-04-03 | Claude Code | 🔴🔴🔴 費用變更強制審批 (統帥指示) |
| v1.9 | 2026-04-15 | Claude Code + ogt | 🔴🔴🔴 AI 自主化飛輪 Phase 退出條件鐵律 (ADR-080) |
| v2.0 | 2026-04-16 | Claude Code + ogt | 新增 No Island Coding / 主動執行熔斷機制 / 自循環工作流 / 狀態機驗證鐵律 |
---
## 快速索引
| 主題 | 禁止 | 正確做法 | 詳細規則 |
|------|------|---------|---------|
| CI/CD | `ubuntu-latest` | `self-hosted` | [→ GitHub Billing](#github-billing) |
| Telegram | `logOut()` | 先停後換 | [→ Telegram Token](#telegram-token) |
| 前端 | 硬編碼文字 | `next-intl` | [→ i18n](#i18n) |
| 資料庫 | SQLite | PostgreSQL | [→ DB](#database) |
| CORS | `*` | 白名單 | [→ CORS](#cors) |
| 數據 | 假數據 Demo | 真實 API | [→ No Fake Data](#no-fake-data) |
| 架構 | 刪除 OpenClaw | OpenClaw 是核心 | [→ OpenClaw](#openclaw) |
| Git | `--force` | 正常 push | [→ Git Safety](#git-safety) |
| **測試** | **Mock 測試** | **真實 DB/服務** | [→ No Mock Testing](#no-mock-testing) |
| **API** | **單獨改路徑** | **前後端同步** | [→ API Path Naming](#api-path-naming) |
| **部署** | **假設已部署** | **驗證 Pod 版本** | [→ Deployment Verification](#deployment-verification) |
| **Alertmanager** | **指向 OpenClaw** | **指向 AWOOOI API** | [→ Alertmanager Routing](#alertmanager-routing) |
| **簽核 UI** | **清空內容** | **保留原始內容** | [→ Approval Preserve Content](#approval-preserve-content) |
| **時區** | **UTC/utcnow** | **台北時區 +8** | [→ Timezone Taipei](#timezone-taipei) |
| **變更追蹤** | **無註解** | **人事物+版本+台北時區** | [→ Change Annotation](#change-annotation) |
| **🔴🔴🔴 前端建置** | **內網 IP** | **公網域名** | [→ Frontend Internal IP](#frontend-internal-ip) |
| **AI Router** | **Router import 具體 Provider** | **只依賴 Protocol** | [→ OpenClaw](#openclaw) |
| **🔴🔴🔴 費用變更** | **擅自切換/新增付費 AI Provider** | **先讀憲法第五章,再請統帥批准** | [→ Cost Change Approval](#cost-change-approval) |
| **🔴🔴🔴 AI 飛輪 Phase** | **未過退出條件就宣告完成** | **必須逐條驗收 exit conditions** | [→ AI Phase Exit Conditions](#ai-phase-exit-conditions) |
| **🔴🔴 孤島開發** | **局部最佳化不查 Callers** | **grep 全域掃描後才動手** | [→ No Island Coding](#no-island-coding) |
| **🔴🔴 熔斷機制** | **不確定就停下來問** | **主動做完,爆炸半徑 >3 模組才熔斷** | [→ 主動執行與熔斷](#proactive-execution--circuit-breaker) |
| **🔴🔴 工作流節奏** | **每步都來回報** | **內部自循環,全局單次回報** | [→ 自循環工作流](#self-loop-workflow) |
| **🟡 狀態機驗證** | **不查中間狀態卡死** | **必驗 TTL + Cleanup + Fallback** | [→ State & Flow Validation](#state--flow-validation) |
---
## 🔴🔴🔴 Cost Change Approval — 費用變更強制審批
> **統帥指示 2026-04-03**: 所有涉及費用產生的變更,必須先看憲法規定,再經統帥批准。
### 定義:什麼是「涉及費用的變更」
| 類型 | 範例 | 是否需審批 |
|------|------|-----------|
| 新增/切換付費 AI Provider | 改用 Claude API、新增 GPT-4 | ✅ **必須審批** |
| 增加現有 Provider 呼叫頻率 | 告警觸發改為每分鐘 | ✅ **必須審批** |
| 取消 feature flag 限制 | 關掉 token 上限 | ✅ **必須審批** |
| 新增外部付費 API 整合 | Sentry、Langfuse 付費功能 | ✅ **必須審批** |
| 修改現有 Provider timeout | 45s → 90s延遲但不增費用 | ❌ 不需審批 |
| 純粹程式邏輯優化 | Prompt 精簡、快取策略 | ❌ 不需審批 |
### 強制流程
```
發現需要涉及費用的變更
1. 閱讀憲法第五章 (費用治理)
2. 評估: 月費用影響估算 (USD)
3. 停下來,向統帥說明:
- 為什麼需要這個變更
- 預估費用影響
- 替代方案評估
4. 等待統帥明確批准 ("好" / "同意" / "執行")
5. 執行變更,並在 commit 備註批准日期
```
### 🔴 絕對禁止
```
❌ 未獲批准,擅自切換到新的付費 AI Provider
❌ 未獲批准,增加現有 Provider 的呼叫頻率或 token 上限
❌ 以「暫時測試」為由繞過審批流程
❌ 在沒有費用評估的情況下上線新的 AI 功能
```
### 今日違規案例 (2026-04-03教訓記錄)
> Claude Code 未經批准將 ChatManager 的 OpenClaw 替換為 Gemini Flash
> 理由是「Ollama 卡死」。這是錯誤的 — 應先報告統帥,等待批准,
> 再評估是否切換。費用影響未經評估,架構決策未經統帥核可。
---
## 🔴🔴🔴 Frontend Internal IP
**Memory:** `feedback_docker_nextjs_api_url.md` + `feedback_sentry_local_network.md`
**絕對禁止** 在 CD 建置時使用內網 IP
```yaml
# ❌ 禁止 - 觸發瀏覽器「存取區域網路」權限對話框
--build-arg NEXT_PUBLIC_API_URL=http://192.168.0.125:32334
--build-arg NEXT_PUBLIC_SENTRY_DSN=http://...@192.168.0.110:9000/2
# ✅ 正確 - 使用公網域名
--build-arg NEXT_PUBLIC_API_URL=https://awoooi.wooo.work
```
**為什麼這是 Hard Rule:**
- `NEXT_PUBLIC_*`**build-time** 變數,會寫死到 JS Bundle
- Runtime 設定 (K8s ConfigMap/Secret) 對 Next.js 無效
- 內網 IP 會觸發瀏覽器安全機制,彈出權限對話框
- UX 極差,尤其無痕模式
**2026-03-30 事故回顧:**
CD Pipeline 使用 `http://192.168.0.125:32334` 建置,導致所有 API 請求指向內網 VIP。
---
## GitHub Billing
**Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_github_billing.md`
```yaml
# ❌ 禁止
runs-on: ubuntu-latest
# ✅ 正確
runs-on: self-hosted
```
**原因:** GitHub Actions 帳戶額度限制,必須用 192.168.0.110 的 self-hosted runner。
---
## Telegram Token
**Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_telegram_token_disaster.md`
```python
# ❌ 禁止 - 會導致 Token 永久失效
await bot.log_out()
# ✅ 正確流程
1. 先停止舊 Bot Long Polling
2. 再切換新 Token
```
**原因:** 2026-03-23 災難事件logOut 導致 Token 永久失效。
---
## i18n
**Memory:**
- `feedback_i18n_zero_hardcode.md` - 零容忍硬編碼
- `feedback_i18n_language_strategy.md` - 語言選擇標準
```tsx
// ❌ 禁止硬編碼
<button>Submit</button>
<span>STATE: IDLE</span>
// ✅ 使用 next-intl
<button>{t('common.submit')}</button>
<span>{t('agent.state')}: {t('agent.idle')}</span>
```
| 元素 | 語言 | 範例 |
|------|------|------|
| UI 文字 | 繁體中文 | 「系統穩定」 |
| 技術標識 | 英文 | `P0`, `RESOLVED` |
**原因:** 面向使用者的文字必須繁體中文。
---
## Database
**Memory:** AWOOOI 憲法
```python
# ❌ 禁止
DATABASE_URL = "sqlite:///..."
# ✅ 正確
DATABASE_URL = "postgresql+asyncpg://..."
```
**原因:** SQLite 無法支援生產環境並發。
---
## CORS
**Memory:** AWOOOI 憲法
```python
# ❌ 禁止
CORS_ORIGINS = ["*"]
# ✅ 正確
CORS_ORIGINS = ["https://awoooi.wooo.work", "http://localhost:3000"]
```
**原因:** 安全性要求。
---
## No Fake Data
**Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_no_fake_data.md`
```tsx
// ❌ 禁止
const data = DEMO_DATA
// ✅ 正確
const { data } = useRealAPI()
```
**原因:** 假數據導致用戶無法看到真實系統狀態。
---
## OpenClaw
**Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_architecture_openclaw_core.md`
```
❌ 禁止: 淘汰、取代、或刪除 OpenClaw
✅ 正確: OpenClaw 是 AWOOOI 產品核心,只能增強不能移除
```
**原因:** OpenClaw AI 是產品核心價值。
### Phase 24 AI Router 重構規範 (ADR-052, 2026-04-02)
```
❌ 禁止: 在 AIRouter (router.py) 中 import 具體 Provider 實作類別
✅ 正確: Router 只依賴 AIProvider ProtocolProvider 在 main.py DI 註冊
❌ 禁止: 將 ConsensusEngine (P0/P1) 納入 AIRouter
✅ 正確: ConsensusEngine 是獨立決策層,走 Claude Agent SDK
❌ 禁止: DIAGNOSE/CODE_REVIEW 意圖路由到 cloud Provider
✅ 正確: privacy_level="local" 強制本地 (零信任 ADR-023)
❌ 禁止: 遷移期間刪除 openclaw.py 舊 fallback chain
✅ 正確: USE_AI_ROUTER 絞殺者開關,新舊並存至 Phase B4
```
**Memory:** `project_phase24_ai_router.md`
---
## Git Safety
**Memory:** 防禦性工程
```bash
# ❌ 禁止
git push --force
git reset --hard
git checkout -- .
# ✅ 正確
git push
git revert
```
**原因:** 防止資料遺失。
---
## API Path Naming
**Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_api_path_naming.md`
```python
# ❌ 禁止 - 單獨修改後端路徑
@router.get("/ai-performance") # 改成 /incidents/ai-performance
# 但前端仍調用 /ai-performance → 404
# ✅ 正確 - 前後端同步修改
# 1. 後端: @router.get("/incidents/ai-performance")
# 2. 前端: await fetch('/api/v1/stats/incidents/ai-performance')
# 3. 測試: curl 驗證
```
**原因:** 路徑變更是破壞性變更,必須同時更新前後端。
---
## No Mock Testing
**Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_no_mock_testing.md`
```python
# ❌ 禁止 - 全面禁止 Mock 測試
from unittest.mock import Mock, AsyncMock, MagicMock, patch
mock_service = AsyncMock()
with patch("src.services.xxx", mock_service):
...
# ✅ 正確 - 使用真實資料庫/服務
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.post("/api/v1/xxx", json=payload)
```
**原因:** 統帥 2026-03-24 明確指示「全面禁止」Mock 無法反映真實系統行為。
**允許例外:**
- `patch.object(settings, ...)` 修改配置值(非 Mock 服務)
---
## Deployment Verification
**Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_deployment_verification.md`
```bash
# ❌ 禁止 - 假設 git push 就是部署完成
git push && echo "已部署"
# ✅ 正確 - 必須驗證 Pod 實際運行版本
# 1. 確認 CD workflow 成功
gh run list --workflow=cd.yaml --limit 1 # 必須 ✅ success
# 2. 驗證 Pod 鏡像版本
kubectl get pods -n awoooi-prod -o jsonpath="{.items[*].spec.containers[*].image}"
# 鏡像 tag 必須與最新 commit SHA 匹配
# 3. Health check
curl -f https://api.awoooi.wooo.work/api/v1/health
```
**原因:** 2026-03-24 重大事故:代碼已提交但 CD 連續失敗,正式環境仍運行舊版本,用戶誤以為功能已修復。
---
## Alertmanager Routing
**Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_alertmanager_awoooi_flow.md`
```yaml
# ❌ 禁止 - 指向 OpenClaw (會使用舊 AIOPS 格式)
receivers:
- name: openclaw
webhook_configs:
- url: 'http://192.168.0.188:8088/api/v1/webhook/alertmanager'
# ✅ 正確 - 指向 AWOOOI API (K3s)
receivers:
- name: awoooi
webhook_configs:
- url: 'http://192.168.0.120:32334/api/v1/webhooks/alertmanager'
```
**職責分工:**
| 系統 | 職責 | Telegram 權限 |
|------|------|--------------|
| AWOOOI API (K3s 32334) | 告警處理 + 格式化 + 發送 | ✅ |
| OpenClaw (188:8088) | AI 大腦、LLM 分析 | ❌ |
**原因:** 2026-03-25 災難Claude 錯誤將 Alertmanager 指向 OpenClaw導致 Telegram 發送舊 AIOPS 格式(🤝 [協同警報]),而非 AWOOOI 格式(═══ 🚨 CRITICAL ═══)。
---
## Approval Preserve Content
**Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_approval_preserve_content.md`
```tsx
// ❌ 禁止 - 簽核後清空內容
if (approval.status === 'approved') {
return <div></div> // 原始內容不見了!
}
// ✅ 正確 - 保留原始內容
<Badge></Badge>
<OriginalAlertContent alert={approval} /> {/* 保留! */}
<ExecutionResult result={approval.result} />
```
**原因:** 2026-03-25 統帥指示:簽核後必須保留原始告警內容,用戶需要回顧已處理的告警。
---
## Timezone Taipei
**Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_timezone_taipei.md`
```python
# ❌ 禁止 - UTC 時區
from datetime import UTC
datetime.now(UTC)
datetime.utcnow()
# ✅ 正確 - 台北時區
from src.utils.timezone import now_taipei, now_taipei_iso
now_taipei() # datetime with +08:00
now_taipei_iso() # '2026-03-25T02:08:04+08:00'
```
**原因:** 統帥在台灣,日誌和 Telegram 告警必須顯示台北時間 (UTC+8),方便閱讀無需心算。
---
## Change Annotation
**Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_change_annotation_standard.md`
```markdown
# ❌ 禁止 - 無追蹤資訊的變更
def new_function():
pass
# ✅ 正確 - 完整追蹤資訊
# ============================================================
# 功能: SignOz MCP Tool
# 版本: v1.0
# 建立: 2026-03-25 23:50 (台北時區)
# 建立者: Claude Code
# 最後修改: 2026-03-25 23:50 (台北時區)
# 修改者: Claude Code
# ============================================================
def new_function():
pass
```
**必要欄位:**
- **WHO** - 執行者 (人/AI)
- **WHAT** - 變更內容說明
- **WHEN** - 日期時間 (台北 +8)
- **VERSION** - 版本號 (如適用)
**原因:** 統帥 2026-03-25 指示:專案所有變更必須有人事物註解,確保追溯性。
---
## AI Phase Exit Conditions
> **ADR-080 鐵律 2026-04-15**: 禁止在未通過 Phase N 退出條件前宣告 Phase N 完成。
**Reference:** `docs/adr/ADR-080-ai-autonomy-flywheel-overview.md` / MASTER §5
### 核心規則
```
禁止宣告「Phase N 完成」,除非:
✅ MASTER §5 Phase N 退出條件清單全部打勾
✅ 相關測試通過pytest 綠燈)
✅ 架構師評審 Gate N 已完成ADR-080 §架構師評審框架)
✅ LOGBOOK 已記錄完成項目
```
### 7 Phase 退出條件速查
| Phase | 最關鍵退出條件 | ADR |
|-------|--------------|-----|
| P0 | `feature_flags.py` 建立 + `baseline_snapshot.py` 建立 + HARD_RULES v1.9 | ADR-080 |
| P1 | MCP 呼叫次數/24h > 0EvidenceSnapshot 寫入 DB | ADR-081 |
| P2 | 5 Agent 全部有 unit testCoordinator 熔斷測試通過 | ADR-082 |
| P3 | 學習閉環觸發率 ≥ 99%fire-and-forget bug 消滅 | ADR-083 |
| P4 | 動態基線覆蓋率 ≥ 80%general 告警 < 10% | ADR-084 |
| P5 | Blast Radius check 100% 覆蓋dry-run 強制通過 | ADR-085 |
| P6 | SLO 計算可用;自我降級觸發後不得自動反向升級 | ADR-086 |
### 違規後果
宣告 Phase 完成但退出條件未達到 = 技術債爆炸風險,等同於在不穩定地基上繼續建樓。統帥發現違規 → 立即回滾至上一個已驗收 Phase。
---
---
## 🔴🔴 No Island Coding — 禁止孤島開發
> 任何程式碼修改,必定影響上下游。未經全域檢視的局部最佳化,視為嚴重失職。
### 三條鐵律
| 情境 | 必做動作 |
|------|---------|
| **變更函數簽名** | `grep -rn "函數名" apps/ packages/` 找出所有 Callers一併更新 |
| **變更資料模型 (DB/Pydantic)** | 同步確認 Write 層、Read 層、Redis Cache 序列化/反序列化邏輯一致 |
| **任何局部修改** | 禁止「假設它會動」,必須全域掃描才能動手 |
```bash
# 修改函數前的強制指令
grep -rn "target_function_name" apps/ packages/ --include="*.py"
# 列出所有 Caller逐一確認影響範圍
```
**與積木化的關係:** No Island Coding 是「橫向」掃描找所有呼叫者leWOOOgo 積木化是「縱向」邊界Router 不能越界)。兩者並行,缺一不可。
---
## 🔴🔴🔴 Prompt-Model 同步鐵律 — LLM Schema Drift 禁令
> 血的教訓 (2026-04-17): `prompts.py` 列了 6 個 `suggested_action` 值,`models/ai.py` 只有 4 個。
> NemoTron 輸出 `"investigate"` → Pydantic ValidationError → `analysis_result = None`
> → 全系統 fallback所有 Telegram 卡片顯示「待分析」,持續數週未被察覺。
### 強制鐵律
| 情境 | 必做動作 |
|------|---------|
| **修改 `prompts.py` 的 Enum/格式/欄位** | 同步檢查並更新 `models/ai.py` Pydantic Schema確保兩者完全一致 |
| **新增 LLM 輸出欄位** | 先在 Pydantic 加上 `field_validator``fallback`,再改 Prompt |
| **任何接收 LLM JSON 的 Model** | 必須有 `mode="before"` validator 處理:大小寫/別名/未知值 fallback |
### 三層防護缺一不可
```python
@field_validator("suggested_action", mode="before")
@classmethod
def normalize_suggested_action(cls, v):
if isinstance(v, str):
normalized = v.upper().replace("-", "_")
# 1. 別名映射
alias_map = {"DIAGNOSE": "INVESTIGATE", "MONITOR": "OBSERVE", ...}
normalized = alias_map.get(normalized, normalized)
# 2. 未知值 fallback — 絕不讓 Pydantic 爆炸導致 analysis_result = None
try:
MyEnum(normalized)
return normalized
except ValueError:
return "NO_ACTION" # 安全預設值
return v
```
### 禁止靜默死亡
Pydantic `ValidationError`log 必須明確印出:
- 哪個欄位失敗
- LLM 實際輸出了什麼值
- `pydantic_validation_failed` event已實作於 `openclaw.py`
違反此鐵律 = 破壞生產環境,必須立即 revert。
---
## 🔴🔴 Proactive Execution & Circuit Breaker — 主動執行與熔斷機制
> 整合 `feedback_proactive_execution.md`主動執行2026-04-05與孤島開發停問規則。
> **主動執行為絕對預設,停下問是唯一例外。**
### 預設:主動狂奔
看到任務與規格後,**一口氣實作到底**
```
✅ 自己寫 Code
✅ 自己跑測試
✅ 自己修 Error
✅ 全部跑通後才回報
```
```
❌ 禁止:列出步驟然後問「統帥可不可以開始」
❌ 禁止:把「不確定」當作不寫 Code 的藉口
❌ 禁止:拿中間的 Error 來打斷統帥
```
### 唯一熔斷條件Circuit Breaker
**必須同時滿足以下兩個條件**,才可暫停並請求裁示:
1. 修改的檔案是 **Tier 3 核心紅區**(如 `decision_manager.py``db/base.py`
2. 透過 `grep` 發現這個修改會直接導致 **超過 3 個以上未關聯模組**發生不可預期的 Crash
熔斷時的回報格式:
```
發現連鎖爆炸半徑,涉及 [A, B, C],請求裁示
```
**其餘所有情況,全部自己解決。**
---
## 🔴🔴 Self-Loop Workflow — 自循環工作流與全局回報
> 四步工作流是 AI 的**內部背景作業**,不是與統帥的來回節奏。
### 內部自循環(不對外打斷)
```
[1. 影響評估] → [2. 實作] → [3. 串接驗證]
↓ Error
[4. 自動修復] → 回到 [3. 串接驗證]
```
遇到驗證腳本報錯,**必須自動遞迴修復,不得中斷統帥。**
### 全局單次回報(唯一一次)
當整個任務 100% 通過時,進行唯一一次成果彙報,必須包含:
```
1. 修改了哪些檔案(逐一列出)
2. 最終成功的終端機輸出Console Output
3. 是否有潛在副作用
```
等待統帥批准後,才能執行 `git commit``push`
### 驗證腳本規範 — Live-Fire Only實彈演習
根據 [No Mock Testing](#no-mock-testing) 鐵律,`verify_script.py` **絕對禁止使用任何 Mock 套件**
```python
# ❌ 禁止 — 自己 mock 自己測,驗證毫無意義
from unittest.mock import MagicMock
db = MagicMock()
# ✅ 正確 — 打真實 PostgreSQL / Redis / HTTP
async with get_session() as session:
result = await session.execute(select(MyModel).where(...))
# 驗證完畢後必須 Teardown清理測試資料
await session.execute(delete(MyModel).where(MyModel.id == test_id))
```
---
## 🟡 State & Flow Validation — 狀態機與流程驗證
修改涉及 **async、background jobs、DB 狀態轉換** 的邏輯時,必須驗證以下清單。
### 必查斷點
| 風險類型 | 必查項目 |
|---------|---------|
| **中間狀態卡死** | `PENDING` 是否有機制轉為 `EXPIRED``PROCESSING` 超時後誰負責撿起? |
| **Fire-and-forget 無聲失敗** | 異步任務失敗時Exception 是否被吞掉?有無 dead letter / retry |
| **新增資料實體** | TTL 多長Cleanup Job 是什麼錯誤降級策略Fallback定義了嗎 |
| **生命週期完整性** | 建立 → 處理 → 結案 → 清理,四個階段必須全部有對應的程式碼 |
```python
# 新增資料實體的最低要求
class MyEntity(Base):
expires_at: datetime # TTL 必填
# 對應 Cleanup Job: scheduler/cleanup_expired_entities.py
# 降級策略: 超時 → status = EXPIRED → Telegram 告警
```
---
## 如何新增規則
1. 在此文件新增章節
2. 更新快速索引表
3. 在 Memory 新增對應 `feedback_*.md`
4. 更新 `MEMORY.md` 索引