# Phase 2 部署驗證劇本（ADR-027 真正落地）

> **Date**: 2026-05-03
> **Phase**: Operation Ollama-First v5.0 — Phase 2（A6 debugger）
> **修補項**: B1 / B2 / B3 / B4 / N2 / N3
> **修改檔**: `config.py` / `services/ollama_service.py` / `services/aider_heal_executor.py` / `services/code_review_pipeline_service.py`
> **新檔**: `tests/test_ollama_resolve.py`（13 tests，本機已通過）

---

## 一、部署前 dry-run（本機）

### 1.1 語法檢查

```bash
cd "/Users/ooo/Library/Mobile Documents/com~apple~CloudDocs/momo-pro-system"
python3 -m py_compile config.py services/ollama_service.py \
  services/aider_heal_executor.py services/code_review_pipeline_service.py \
  tests/test_ollama_resolve.py && echo "PYCOMPILE_OK"
```

期望：`PYCOMPILE_OK`（已驗證）

### 1.2 Unit test

```bash
MOMO_ALLOW_INSECURE_CONFIG_FOR_TESTS=true /opt/anaconda3/bin/python3 -m pytest \
  tests/test_ollama_resolve.py \
  tests/test_phase3f_cleanup_contracts.py \
  tests/test_app_startup_contracts.py \
  tests/test_ai_call_logger.py \
  tests/test_code_review_pipeline_security.py \
  tests/test_auto_heal_safety.py -v
```

期望：56 passed（13 新 + 43 既有）。已驗證。

### 1.3 import 一致性

```bash
MOMO_ALLOW_INSECURE_CONFIG_FOR_TESTS=true /opt/anaconda3/bin/python3 -c "
from config import get_ollama_host, get_hermes_url, get_embedding_host
from services.ollama_service import resolve_ollama_host, mark_unhealthy
print('get_ollama_host  =', get_ollama_host())
print('get_hermes_url   =', get_hermes_url())
print('get_embedding_host =', get_embedding_host())
print('resolve_ollama_host=', resolve_ollama_host())
"
```

期望（網路通時）：四行都印 `http://34.21.145.224:11434`（GCP 可達）或 `http://192.168.0.111:11434`（GCP 不可達）。
不可出現 `https://ollama.wooo.work/ollama`（舊寫死 URL）。

---

## 二、部署後驗證（SSH 188）

### 2.1 容器健康

```bash
ssh wooo@192.168.0.110 "ssh ollama@192.168.0.188 \"\
  docker ps --format '{{.Names}} | {{.Status}}' | grep momo-; \
  docker exec momo-pro python3 -c 'from config import get_ollama_host; print(get_ollama_host())' 2>&1\""
```

期望：
- `momo-pro | Up`（重啟後新容器）
- 列印的 host 不是 `https://ollama.wooo.work/ollama`

### 2.2 OllamaHost 解析 log（B3 HTTP probe 驗證）

```bash
ssh wooo@192.168.0.110 "ssh ollama@192.168.0.188 \"\
  docker logs momo-pro --since 10m 2>&1 | grep -E 'OllamaHost' | tail -20\""
```

期望（GCP 可達）：
```
[OllamaHost] GCP 主機可用，使用 Primary: http://34.21.145.224:11434
```

期望（GCP 掛時）：
```
[OllamaHost] GCP 主機無法連線，自動切換 Fallback: http://192.168.0.111:11434
```

罕見（process 卡死，TCP 通但 HTTP 掛）：
```
[OllamaHost] GCP HTTP 探測失敗但 TCP 仍通，疑似 process 卡死：http://34.21.145.224:11434
[OllamaHost] GCP 主機無法連線，自動切換 Fallback: http://192.168.0.111:11434
```

> 第三種日誌是 **Phase 2 修補後才會看見的新觀測能力**，舊版純 TCP 探測不會印。

### 2.3 mark_unhealthy 觸發（B4 驗證）

當 LLM generate 真的失敗時，會看見：
```
[OllamaHost] 主機標記為 unhealthy（30s 跳過）：http://34.21.145.224:11434
```

立刻在下一次任何 ollama 呼叫的 log 看：
```
[OllamaHost] Primary http://34.21.145.224:11434 仍在 unhealthy TTL 內，跳過直接 fallback: http://192.168.0.111:11434
```

### 2.4 AiderHeal OLLAMA_API_BASE 動態化（N2 驗證）

下次 AiderHeal 觸發時 grep：
```bash
ssh wooo@192.168.0.110 "ssh ollama@192.168.0.188 \"\
  docker logs momo-pro --since 30m 2>&1 | grep 'aider_ollama_api_base' | tail -5\""
```

期望：
```
event=aider_ollama_api_base host=http://34.21.145.224:11434
```
（GCP 可達時）或 `host=http://192.168.0.111:11434`（fallback）。
**絕不可** 仍顯示 `http://192.168.0.111:11434` 當 GCP 是可達的。

### 2.5 Code Review provider tag（N3 驗證）

下次 Code Review pipeline 觸發後：
```bash
ssh wooo@192.168.0.110 "ssh ollama@192.168.0.188 \"\
  docker exec momo-postgres psql -U momo -d momo_analytics -c \
    \\\"SELECT caller, provider, meta->>'host' AS host \
       FROM ai_calls \
       WHERE caller = 'code_review_hermes' \
       ORDER BY created_at DESC LIMIT 5;\\\"\""
```

期望（GCP 通時）：
```
caller              | provider    | host
code_review_hermes  | gcp_ollama  | http://34.21.145.224:11434
```

絕不可仍標 `ollama_111` 當 host 是 GCP。

---

## 三、模擬故障驗證（選做）

### 3.1 模擬 GCP 不可達 → 5s 內 fallback

在 188 上臨時封鎖 GCP IP：
```bash
ssh wooo@192.168.0.110 "ssh ollama@192.168.0.188 \"\
  sudo iptables -A OUTPUT -d 34.21.145.224 -j DROP\""
```

立即觸發 sales copy（or 任何 LLM 入口），看 log：
- 第一次呼叫應 timeout（2s 內 _is_reachable 失敗）→ 切 fallback
- 之後 30s 內所有呼叫直接走 fallback
- 30s 後 cache TTL 過期，會重新探測（仍封鎖則繼續 fallback；解封後恢復 GCP）

恢復：
```bash
ssh wooo@192.168.0.110 "ssh ollama@192.168.0.188 \"\
  sudo iptables -D OUTPUT -d 34.21.145.224 -j DROP\""
```

> 此項屬統帥權限，debugger 不執行。

---

## 四、回滾 SOP

若部署後出問題，最快回滾：
```bash
git revert <this-commit-sha>
git push origin main
# 等 Gitea CD 自動部署
```

也可以單獨回退 ollama_service.py：
```bash
git checkout HEAD~1 -- services/ollama_service.py config.py
```
（其他三檔變更可獨立保留）

---

## 五、commit 草稿

```
[V-New] ADR-027 Phase 2：Ollama 主機解析全鏈 lazy + HTTP probe + unhealthy 標記

修補 6 項讓 ADR-027「GCP 優先」真正 100% 落地：
  B1 — config.OLLAMA_HOST 改 lazy resolve（移除寫死 ollama.wooo.work URL）
  B2 — config.EMBEDDING_HOST / HERMES_URL 改 lazy（避免 import-time freeze）
  B3 — _is_reachable 改 HTTP probe (/api/version, 2s timeout)，TCP 改作觀測點
  B4 — 新增 mark_unhealthy()，generate / embedding 失敗時標 30s，cache 失效
  N2 — aider_heal_executor.OLLAMA_API_BASE 改 lazy resolve（每次 execute 重評估）
  N3 — code_review_pipeline_service Hermes scan 改 get_hermes_url() 取代 freeze

新增：tests/test_ollama_resolve.py（13 tests）
變更：config.py / services/ollama_service.py /
      services/aider_heal_executor.py / services/code_review_pipeline_service.py

驗證：56 tests 全綠（13 新 + 43 既有 regression），py_compile 全綠。
驗證劇本：docs/phase2_deploy_verify_20260503.md（給統帥 SSH 188 跑）。
```
