# INV-1: Redis Key Inventory

**版本**：v1.0 初稿
**日期**：2026-05-03（台北）
**範圍**：`apps/api/src/` 全 codebase grep
**用途**：Phase 2 三階段雙寫遷移的完整 Redis key 清單

---

## 使用方式

本 INV 列出所有 Redis key pattern、TTL、用途、寫入點、讀取點、是否硬碼跨兩處。
Phase 2 遷移按批次執行：P0（Critical）→ P1（高風險）→ P2（業務資料）→ P3（低影響）

---

## 1. P0 — Critical（必須優先遷移）

| Key Pattern | 用途 | TTL | 寫入點 | 讀取點 | 問題 | 新 Pattern（Phase 2 後）|
|-------------|------|-----|--------|--------|------|------------------------|
| `ollama:current_primary` | Ollama primary URL 選擇 | 無 | `ollama_auto_recovery.py` (兩處！) | `ollama_failover_manager.py` | 🔴 P0-11：第二定義 at ollama_auto_recovery.py:230；三層 GCP 拓撲無法塞入單一 URL | `platform:ollama:topology`（JSON：primary/secondary/fallback）|
| `ollama:gemini_daily_count:{date}` | Gemini 每日使用計數 | 24h | `ollama_auto_recovery.py` | same | 🔴 無 project 前綴，多 tenant 共用 | `platform:ollama:gemini_daily_count:{date}` |
| `telegram:polling:leader` | Telegram polling pod 鎖 | ~30s | `telegram_gateway.py:55` | same | 🔴 platform_resource，應明確標記 | `platform:telegram:polling:leader` |
| `telegram_silence:{target}` | 告警靜默控制 | 設定值 | `telegram_gateway.py:4079`（用 SILENCE_KEY_PREFIX）| `decision_manager.py:240`（🔴 硬碼 `telegram_silence:{target}`，未 import 常數）| P1-24：兩處定義不一致 | `{project_id}:telegram:silence:{target}` |
| `ai_rate:total_cost:{provider}` | 費用上限控制 | 無 | `ai_rate_limiter.py` | `ai_router.py` | 🔴 無 project 前綴，AWOOOI 費用計數會與 EwoooC 混算 | `{project_id}:ai_rate:total_cost:{provider}` |

## 2. P1 — 高風險（Phase 2 第二批）

| Key Pattern | 用途 | TTL | 寫入點 | 讀取點 | 問題 | 新 Pattern |
|-------------|------|-----|--------|--------|------|------------|
| `approval:{run_id}:*` | approval 審核狀態 | 15min | `multi_sig_redis.py` | same | 🔴 P1-15：無 trace_id；approval_token 未簽章 | `{project_id}:approval:{run_id}:*`（Phase 8 後加 jti）|
| `incident:{incident_id}` | incident working memory | 24h | `incident_service.py` | `incident_service.py:603`（SCAN `incident:*`）| 🔴 P1-18：SCAN 無 prefix 篩選 | `{project_id}:incident:{incident_id}` |
| `consensus:{*}` | consensus engine 狀態 | varies | `consensus_engine.py`（CONSENSUS_PREFIX="consensus:"）| same | 🔴 P0-12：無 project 前綴，multi-tenant 共用 | `{project_id}:consensus:{*}` |
| `playbook:{playbook_id}` | playbook cache | 1h | `playbook_repository.py` | same | 🟠 無 project，但目前 AWOOOI 只有一個 tenant | `{project_id}:playbook:{playbook_id}` |
| `playbook:embedding:index` | embedding index | 無 | `playbook_repository.py` | same | 🟠 無 project | `{project_id}:playbook:embedding:index` |
| `playbook:index:alert:{alert_name}` | alert → playbook 映射 | 1h | `playbook_repository.py` | same | 🟠 無 project | `{project_id}:playbook:index:alert:{alert_name}` |
| `playbook:embedding:{playbook_id}` | embedding vector | 無 | `playbook_repository.py` | same | 🟠 無 project | `{project_id}:playbook:embedding:{playbook_id}` |
| `km:backfill:dlq` | KM backfill dead letter queue | 無 | `km_service.py` | same | 🟠 無 project | `{project_id}:km:backfill:dlq` |

## 3. P2 — 業務資料（Phase 2 第三批）

| Key Pattern | 用途 | TTL | 寫入點 | 讀取點 | 問題 | 新 Pattern |
|-------------|------|-----|--------|--------|------|------------|
| `anomaly:disposition:{alert_id}` | 告警處置決定 | 7d | `anomaly_counter.py:790` | same | 🔴 P1-17：AnomalyCounter 全域單例，6 個 prefix 無 tenant | `{project_id}:anomaly:disposition:{alert_id}` |
| `anomaly:metadata:{alert_id}` | 告警 metadata | 7d | `anomaly_counter.py` | same | 同上 | `{project_id}:anomaly:metadata:{alert_id}` |
| `anomaly:permanent_fix:{alert_id}` | 永久修復紀錄 | 30d | `anomaly_counter.py` | same | 同上 | `{project_id}:anomaly:permanent_fix:{alert_id}` |
| `anomaly:repair_count:{alert_id}` | 修復計數 | 7d | `anomaly_counter.py` | same | 同上 | `{project_id}:anomaly:repair_count:{alert_id}` |
| `anomaly:repair_history:{alert_id}` | 修復歷史 | 30d | `anomaly_counter.py` | same | 同上 | `{project_id}:anomaly:repair_history:{alert_id}` |
| `anomaly:timeline:{alert_id}` | 告警時間線 | 7d | `anomaly_counter.py` | same | 同上 | `{project_id}:anomaly:timeline:{alert_id}` |
| `hermes:session:{chat_id}:{user_id}` | Hermes 對話狀態 | 30min | `hermes/nl_gateway.py:146` | same | 🔴 P1-16：無 project 前綴 | `{project_id}:hermes:session:{chat_id}:{user_id}` |
| `hermes:rl:{chat_id}` | Hermes rate limit | 1min | `hermes/nl_gateway.py:163` | same | 🔴 P1-16 | `{project_id}:hermes:rl:{chat_id}` |
| `hermes:approvers` | Hermes 審核者清單 | 無 | `hermes/nl_gateway.py:7` | same | 🔴 P1-16 | `{project_id}:hermes:approvers` |
| `session:{session_id}` | agent session | varies | `agent_sessions service` | same | 🟠 無 project | `{project_id}:session:{session_id}` |
| `tg_sent:{fingerprint}` | Telegram 去重 dedup | 10min | `telegram_gateway.py` | same | 🟢 ADR-109 已定（dedup 相容性）；注意 EwoooC Telegram 需要不同 fingerprint scope | `{project_id}:tg_sent:{fingerprint}` |
| `telegram:{user_id}:{*}` | Telegram session / callback | varies | `telegram_gateway.py` | same | 🟠 無 project | `{project_id}:telegram:{user_id}:{*}` |

## 4. P3 — 低影響（Phase 2 最後）

| Key Pattern | 用途 | TTL | 問題 | 新 Pattern |
|-------------|------|-----|------|------------|
| `playbook:*`（SCAN）| playbook 全量掃描 | — | 🟠 SCAN 無 prefix 篩選 | 禁止裸 SCAN，改 `SCAN {project_id}:playbook:*` |
| `approval:123:lock`（測試用）| 測試 key | — | 🔵 hardcode 測試字串，清理 | 移除 |

---

## 5. Platform Resource（不按 project 隔離，明確標記）

| Key Pattern | 理由 | 備註 |
|-------------|------|------|
| `platform:telegram:polling:leader` | 全域 polling leader 鎖，不屬於任何 tenant | ADR-111 platform_resource |
| `platform:ollama:topology` | GCP-A/GCP-B/Local 三層路由狀態，所有 tenant 共用 | ADR-111 + ADR-110 |
| `platform:ollama:gemini_daily_count:{date}` | 全 platform Gemini 緊急路由計數 | ADR-111 |

---

## 6. 遷移執行順序（三階段）

```
Phase A（雙寫，30 天觀察）：
  新 key 寫入 + 舊 key 繼續寫入
  讀取仍以舊 key 為主
  監控：舊 key 讀取次數 / 新 key 命中次數

Phase B（雙讀，14 天）：
  新 key 為主，舊 key 為 fallback
  讀不到新 key → 讀舊 key，並回填新 key
  監控：舊 key fallback 率必須 < 1%

Phase C（移除舊 key 寫入）：
  停止寫入舊 key
  舊 key 自然過期
  監控：確認舊 key 0 讀取後才進行
```

---

## 7. 驗收標準

- [ ] 所有 P0 key 完成 Phase A 雙寫
- [ ] `decision_manager.py:240` 改為 import `telegram_gateway.SILENCE_KEY_PREFIX`
- [ ] `ollama:current_primary` 改為 `platform:ollama:topology`（JSON 格式）
- [ ] 無任何裸 `SCAN *` 或 `SCAN prefix:*`（改為 `SCAN {project_id}:prefix:*`）

*最後更新：2026-05-03（台北）*
