# FR-184 交接現況（STATE）

> **這份是 living 檔**：每棒就地 Edit，只描述「此刻」。歷史脈絡看同資料夾的 `FR-184-LOG.md`。

| 項目 | 值 |
|------|-----|
| 最後更新 | 2026-11-08 19:30（第 3 棒：Terraform Q&A 開始、infra README 補架構圖；接 201 Agent 實測壞，另開 FR-185 母卡 CM-3659 五張子卡） |
| branch | BE `feature/FR-172`；infra `feature/FR-172`（clone 時帶入，不切）；**兩邊都已 push 到 origin**（11-08） |
| 母案卡 | CM-3643 https://app.notion.com/p/FR-184-GCP-VM-Terraform-6-4-411346da4cd0810fa529df7b81d720d1 |
| 下一張要做的卡 | **下一棒＝Terraform Q&A**：決策者要能回答 PM 的問題，首腦陪讀 infra repo、答問題、需要時補導讀。之後才是 P1 → O1 → V1 |
| 卡片狀態 | T1 CM-3644 驗收通過待確認；T2 CM-3645 **工作坊上仍是 In progress**（實際已由首腦做完並回寫，見 §2-8）；F1 CM-3650、T4 CM-3651 **取消**（卡上已寫理由，等決策者在狀態頁按「這張不用做」）；其餘 Not started |
| 接手前必讀 | §0 讀序 |

## 🧭 原始需求 / WHY

- **決策者原話（11-07）**：「假設我今天的主系統想要部署到 GCP，你建議要怎麼做，最好是可以用成自動化部署。我要用 terraform 模式，這樣未來換平台也可以支援。」後續：「一套就好，stg 先不做，因為突然急著要上雲端」、「這是要自己營運的，然後是 POC 階段」。
- **目標**：GCP 台灣開一台 VM，**Terraform 建環境、主產品既有 `install.sh` 裝系統**（程式零改動），自己營運多租戶的雲端版給客戶試用。換雲只換 Terraform 的 module，安裝腳本不動。
- **為什麼不是 K8s**：費用 2～5 倍（實查）、程式要改八處拿掉單機假設、多三週。POC 用不到。決策者聽完「好貴」，定案 VM。
- **本棒位置**：雲端版已上線可用（1.30.1、e2-standard-4），決策者先給客戶試用。**下一棒做 Q&A**：決策者原話「你可能要教我看一下你的 terraform 是怎麼寫的，因為 PM 會問」。導讀已寫好（infra `docs/terraform-walkthrough.md`，含 PM 可能問的九題），下一棒以它為底陪決策者讀、答問題、答不出或文件沒寫到的就查程式碼補進導讀。

## §0 接手讀序

### 0.1 🔒 先懂需求 gate
0. **Q&A 這棒先讀**：infra `~/Projects/Billows/Audit-Manager/guidant-ai-infra/docs/terraform-walkthrough.md`（導讀）全讀，再讀 `modules/platform-gcp/main.tf`、`budget.tf`、`scripts/startup.sh`、`envs/gcp-prod/*.tf` 實際程式——**答問題以程式碼為準，導讀與程式不一致時改導讀**
1. 本檔 🧭 WHY、§2 已定裁示
2. `docs/features/FR-184-2611-gcp-vm-deployment/design.md` 全讀（§3 決策、§5.2 裝機五步、§7 拆分表、§8 驗收、§10 風險）
3. `python3 /Users/chouraymond/Projects/Jedicogy/agent-workshop/scripts/notion_case.py get CM-3646`（T3 卡，卡尾有三段首腦補充：1.30.0、AI 金鑰走 install.conf、序號裝完才貼）
4. `~/Projects/Billows/Audit-Manager/guidant-ai-infra/docs/runbook.md`（機器座標、防火牆驗法）

### 0.2 現況
5. 本檔 §1、§3
6. `FR-184-LOG.md` 最後一個 block

### 0.4 ✅ 冷接自檢
1. 為什麼雲端版第一期跑 `DEPLOYMENT_MODE=host` 而不是 saas？
2. env 與 module 為什麼分兩層？要加 STG、要換 AWS 各改哪裡？
3. 哪三樣東西有 `prevent_destroy`／`deletion_protection`，為什麼是它們？
4. 「VM 被入侵也清不掉備份」是哪一行設定做到的？
5. 為什麼密碼值不經過 Terraform？Terraform 對 Secret Manager 做了什麼、沒做什麼？
6. 換 VM 規格的完整步驟（含 apply 前後要做的兩件事）？
7. 災難復原目前的缺口是什麼？為什麼快照救不回整台？

## §1 當前狀態

**GCP（專案 `guidant-ai-cloud-514012`，帳號 company.jedicotech@gmail.com，gcloud config 名 `guidantai`）**

| 項目 | 狀態 |
|---|---|
| VM `guidant-ai-vm` | 跑著，asia-east1-b，**e2-standard-4（4 核 16 GB）**＋8 GB swap，Ubuntu 24.04，vTPM（11-08 17:05 由 2 核 8 GB 升級，infra `5fe59da`） |
| 固定 IP | **35.194.213.124** |
| 開機腳本 | exit 0：swap、資料碟 `/dev/sdb` bind 到 `/srv/guidant-ai`／`/var/lib/docker`／`/var/lib/containerd`、docker 29.9.3＋compose v5.7.0、Ops Agent active、`/dev/tpm0` |
| 防火牆 | 直打 IP 的 80／443 逾時；5432、20202 被擋；22 只放 `125.228.154.97`（公司線，188 與決策者 Mac 同一個） |
| ssh | `ssh jedi@35.194.213.124`（首腦 Mac 與 188 的公鑰都在） |
| 系統 | **1.30.1**（11-08 13:53 裝 1.30.0、16:45～16:57 更新中心線上升到 1.30.1），9 容器 healthy；Cloudflare Origin 憑證、`UPDATE_SITE_URL`、Turnstile 兩把 key 升級後都保留；安裝紀錄 `/srv/guidant-ai/log/install-20261108-134535.log` |
| Terraform | state 在 `gs://guidant-ai-cloud-514012-tfstate/gcp-prod/`，24 個資源；本機 `envs/gcp-prod/terraform.tfvars`（不入版控）已填真值 |
| 預算告警 | 每月 6,500 TWD（約 200 美元；帳單帳戶是 TWD 計價，預算幣別必須相同），50／90／100％與預測超標寄信，只通知不停機；infra `724e9c0`、`modules/platform-gcp/budget.tf` |

**Secret Manager（18 個，名稱都是 `guidant-*`）**

| secret | 誰放的 | 用途 |
|---|---|---|
| db-admin、db-app、redis、redis-api、redis-socketio、redis-worker | 決策者 | 裝機自帶密碼（12 字元英數） |
| root-admin | 決策者 | `GUIDANT_ROOT_ADMIN_PASSWORD`，非互動安裝必填 |
| anthropic | 決策者 | `GUIDANT_AI_KEY_ANTHROPIC`，install.conf 填，裝機時加密進 DB |
| smtp | 決策者 | Brevo SMTP key，備查；實際由決策者裝完在後台填 |
| turnstile-secret | 決策者 | install.conf 的 `GUIDANT_TURNSTILE_SECRET_KEY` |
| origin-cert、origin-key | 決策者 | Cloudflare Origin 憑證與私鑰，已驗配對、SAN 只有 `guidant-ai.jedicotech.com`、到 2041 |

VM service account 只有前六個的讀取權（Terraform 綁的）。其餘首腦從 Mac 讀出再送上 VM。
**11-08 裝機後**：`redis-api`／`redis-socketio`／`redis-worker` 已用 installer 產的值加新版本（舊版本是決策者給的、裝機沒用到）；另新建 6 個備查（label `source=installer`，VM 不可讀、不在 Terraform 管理）：`guidant-s3-access-key`、`guidant-s3-secret-key`、`guidant-classifier-token`、`guidant-bugsink-db`、`guidant-jwt-secret`、`guidant-app-encryption-key`。`guidant-db-app` 決策者 11-08 換過一版（不可與 db-admin 相同）。

**Cloudflare（jedicotech.com，權杖與 Zone ID 在 infra `.env`，權杖只有 DNS 編輯權）**

- A 紀錄 `guidant-ai.jedicotech.com` → 35.194.213.124，橘雲代理，已解析到 Cloudflare，連網址回 521（正常，系統未裝）
- 全網域 SSL 是「完整」；**`guidant-ai.jedicotech.com` 另有 Configuration Rule 設「嚴格」**（決策者 11-08 設，運算式 `(http.host eq "guidant-ai.jedicotech.com")`）
- Turnstile 小工具「Guidant AI Cloud For GCP」，site key `0x4AAAAAAF3nyyICSRHDlH3k`（公開值）
- Brevo：網域驗證通過、寄件人 `noreply@jedicotech.com` 驗證通過、四筆 DNS 已加

**安裝包**：188 `/opt/guidant-ai-be/.build/bundle/guidant-ai-1.30.0.tar.gz`，11-08 13:09 決策者重出（sha256 `6b0f806f…`），含 Turnstile 改動（開包核過 install.sh／compose／fe 的 nginx 與 entrypoint／Login.js）。**這包有兩個問題**：vulndb 檔是 600（`457f2964a` 已修出包腳本，包本身未重出）、沒帶 `DEFAULT_UPDATE_SITE_URL`（手跑出包沒設這個環境變數；CI 會帶）。VM 上已手動處理，家目錄的包已刪。

**commits**

| repo | commits |
|---|---|
| BE | （已 push）`db989fcc3` 討論稿、`5ac49c456` 設計 v1.0、`0209f3ea0` 網址、`ea6095af0` 開卡、`f7a336878` secret 改讀既有、`5acafb2ce` Turnstile 兩卡、`bf880a7cf` site key、`f16174dcf` 架構圖、`1af8bb821` 架構圖補 Agent、`5a0f17ab0` 交接、`811a9ba44` memory、`a108ab505` Turnstile 有填 key 就開（主產品）、本棒文件 commit |
| FE | （已 push）`f95a85b8` 登入頁問後端決定 Turnstile、前門 CSP 依 site key 放行 |
| infra | （已 push）`8194fb0` 開機腳本補時區與 tpm2-tools、`724e9c0` 預算告警；（新建遠端分支）`7ec4f3a` 骨架、`78e8255` 專案 ID、`6775501` 工作坊登記、`2227c0d` 網址、`224b744` 對齊 8 GB／host、`93e0287`＋`31dc505` T1（小弟，已合回）、`08c67b6` T2 runbook |

## §2 已定裁示（不要重新討論）

1. **VM 不是 K8s**；一套 prod，不做 stg（11-07）
2. **規格 e2-standard-2 8 GB**（首腦建議 16，決策者裁 8，可改 `machine_type` 重 apply 升級）（11-07）
3. **`DEPLOYMENT_MODE=host`**：saas 兩個既有缺口（`common/authz/tenant_hq.py` 總部判定讓郵件／LDAP／安全政策三頁消失、`product_update_flow.py:193` 只認 host 照）另開 FR（11-07）
4. **Turnstile 必開**，是上線條件。做法：主產品改成「兩把都填真 key 才開」，裝機時 `install.conf` 填 `GUIDANT_TURNSTILE_SITE_KEY`／`GUIDANT_TURNSTILE_SECRET_KEY`（寫進 compose `.env`，升級保留）。F1、T4 取消（11-08）。admin 後台設定之後另做（11-08）
5. **DB／Redis 密碼決策者自帶**；AI 與 SMTP 金鑰走後台或 install.conf，不進 `.env`（11-07）
6. **License 開通**：裝完後決策者登入、在開通頁貼序號線上開通，**不預先放序號**（11-07 首腦講錯被糾正）
7. **對外網址 `guidant-ai.jedicotech.com`**；SSL 只對這個主機名稱設嚴格，不動全網域（11-08）
8. **工作坊狀態決策者不動**：T2 被工作坊誤派給小弟後卡住、工作坊暫停；決策者說「那邊狀態我不動，你知道就好」。首腦在 infra 主資料夾親手做完 T2、卡上已回寫。**接手者也不要去動工作坊的狀態**（11-08）
9. **Agent 資安問題（Cloudflare 憑證 90 天換、8443 健康檢查、8444 白名單預設空）交給另一個 session**，本 arc 不處理（11-08）。該 session 已處理 Cloudflare 憑證那題：先在 BE 加「token 不拼指紋」開關（`01fb1289e`），決策者裁撤回（`b0cc9b321`，理由：設定檔開關沒人記得設、1.30.0 已出版不含開關），改由 **Agent 端自己判斷**（CM-3654）：主系統憑證能被系統信任庫驗過（含 Cloudflare 公開憑證）就不比對指紋。**所以雲端版 `guidant.env` 不用加任何指紋開關**。CM-3654 的修正（evidence-agent `16ca2b5`、`bf262cf`）已含在 **Agent 1.7.0** 正式版（11-08 已發），雲端版現在就能接 Agent；判準是「系統信任庫驗得過」不是寫死 Cloudflare，換其他 CDN／公開 CA 都不用改。CM-3654 卡在修正待驗證，待真機接一台 Agent 驗收
10. **雲端版平常升級走更新中心線上更新、只收正式發版**；P1 CI 直接部署仍做，定位救火用（11-08）。雲端版機器指紋不登記進更新站「內部主機清單」、`guidant.env` 不加 `PRODUCT_UPDATE_ALLOW_SAME_VERSION`

## §3 下一步

**🔴 下一個動作：Terraform Q&A**（決策者發令再開始）。首腦陪決策者讀 infra repo，答 PM 會問的問題。

做法建議：
1. 先用導讀 §2 檔案結構、§3 資源表帶一遍全貌（約 10 分鐘），決策者有問題隨時打斷
2. 依決策者想看的部分開實際檔案，對著程式碼講；導讀 §7 是 PM 可能問的九題，可當練習
3. 問到導讀沒寫、或答得不夠好的，查程式碼後補進導讀並 commit（infra repo，可直接 push）
4. 需要的話產一頁給 PM 的簡報版（`writing-technical-docs` skill「系統說明」骨架，或 Artifact 一頁）

Q&A 之後才是：P1（救火用直接部署，批 3，可派小弟）→ O1（備份與災難復原，批 4，**開工前先跟決策者定災難復原做法**）→ V1（驗收，批 5）。

**1.30.1 線上更新實績（11-08 16:45～16:57）**：八步全過（自我替換 → 預檢 → 收單 → 複製資料庫 123 MB／11 秒 → 複本套新版 → 健康檢查 → 換接 → 清理）。健康檢查在 Turnstile 開著時通過（`a108ab505` 的放寬生效）。升完 Turnstile、`UPDATE_SITE_URL`、Origin 憑證都在。

**VM 升規格實績（11-08 17:00～17:06）**：`guidantai stop` → apply（約 2 分 45 秒）→ 開機腳本 exit 0、vTPM 解封成功 → `guidantai start` 9 容器 healthy。指紋不變、License 不用重開。前後對照：VM 內 API 80～130 ms → 17～24 ms；CPU pressure 28% → 4%；可用記憶體 1.7 GB → 9.4 GB。**注意**：停過服務再改規格，重開機後要手動 `guidantai start`（容器停下的狀態會保留）。

**裝機時實際走過的步驟與坑**（下次重建 VM 照這個）：
- 包從 188 直接 scp 到 VM（188 的 known_hosts 已收 VM host key）；家目錄系統碟只有 30 GB，包＋解開約 7 GB，解開後刪壓縮檔
- `install.conf` 在 Mac 從 Secret Manager 組（600，單引號包值），scp 上 VM；裝完 `shred -u`
- 先 `--check-only`；主機時區由開機腳本設 `Asia/Taipei`（infra `8194fb0`）
- Ubuntu 24.04 要 tpm2-tools：已由開機腳本預裝（infra `8194fb0`）；主產品 `--check-only` 也會照實報錯（`b3b21a950`）
- 🔴 vulndb 檔 600 → 第⑥-3 步 Permission denied：`chmod 644` 後 `sudo GUIDANT_REUSE_EXISTING_INSTALL=1 ./install.sh --config install.conf` 重跑（沿用已產的密碼）
- 裝完換 Origin 憑證：`install -m 600 -o 101 -g 101` 放 key、`sudo guidantai restart guidant-fe`
- 🔴 手跑出包沒有 `UPDATE_SITE_URL`：用 `sudo EDITOR=<追加一行的腳本> guidantai config edit guidant.env` 補 `UPDATE_SITE_URL=https://guidantai-update.jedicotech.com`，再 `sudo guidantai start`
- installer 自產的九組回寫 Secret Manager（§1）

之後順序：P1 管線 gcp-prod（救火用，可派小弟，批 3）→ O1 備份告警（批 4，**開工前先跟決策者定災難復原做法**）→ V1 驗收（批 5）。

> ⚠️ 這節是建議清單，不是執行授權。接手方盤點完即停，等決策者發令。

## §4 待辦與風險

- **雲端版接 201 Agent（11-08 18:13 註冊）實測四個問題，另開 FR-185（母卡 CM-3659）**：同步起不來（已重啟解掉，201 現在正常同步）、登記位址是 Docker IP 172.18.0.3 改不了（管理頁離線、「開啟主控台」打不開，等 A-2 修正版再改）、Redis NoPermissionError 三類（CM-3658 補充）、CI 沒跑守衛（M-1）。**雲端 VM 與 201 都沒動設定**，只重啟過 201 的 Agent 容器一次。詳見 `docs/features/FR-185-2611-cloud-agent-first-contact-fixes/README.md`

- **T3 卡 Model 欄空、工作坊恢復時會被誤派**：決策者不動工作坊，接手者也不要動，但要提醒決策者
- **災難復原路徑沒解**：installer 把設定檔加密鑰匙密封進 vTPM；快照只拍資料碟，`/etc/machine-id` 在系統碟；`install.sh` 提示寫「換機器需走 --restore」但沒有這個選項（T1 小弟實查 `install.sh:770～774`）。O1 開工前要跟決策者定
- **已發布的 1.30.0 給新客戶手動安裝會撞 vulndb 600**：包內 19 個檔 600（映像 8、vulndb 1、storage-seed 10），只有 vulndb 是掛給容器 uid 1000 讀才會壞；解包帳號不是 uid 1000 時第⑥-3 步失敗（雲端 VM 的 jedi 是 1001 才撞到；188／189 的 jedi 剛好 1000）。線上更新由 systemd root 解包、保留包內 1000/1000，不受影響。修正 `457f2964a`＋`9086e8ad8`（改接既有 `dist_perms.sh` 整包正規化＋斷言，已 push），**1.30.1 帶上**。暫時解法：chmod 644 後 `GUIDANT_REUSE_EXISTING_INSTALL=1` 重跑
- **tpm2-tools 已修（1.30.1 帶上）**：主產品 `b3b21a950` 讓 `--check-only` 與正式安裝共用 OS 判斷，24.04 上檢查就會照實報錯；infra `8194fb0` 開機腳本預裝 tpm2-tools 並設時區（已 apply、VM 上重跑驗過）。重建 VM 只剩 Origin 憑證與 188 known_hosts 要手動（runbook「重建 VM 後要手動補的」）
- **郵件設定全站共用、「總部」可改**：SMTP 只有一筆（tenant 1，key `*`），所有租戶共用。host 模式下安裝精靈建的 `Billows` 被 `common/authz/tenant_hq.py` 判為總部，其管理員能改全站寄信／LDAP／登入政策。**`Billows` 留給自家用，外部試用客戶另開租戶**；根治在 saas FR
- **SMTP 設定 `has_secret: false` 被存回 DB**：`secret` 欄有值（加密、長度 204）、寄信正常，`has_secret` 是讀取時算的旗標卻被畫面原樣存回。不影響使用，另開小卡
- **雲端租戶的儲存**：每租戶一份 `STORAGE_CONFIG`，新租戶複製 root（SeaweedFS、同一個 bucket `guidant-ai`，檔名時間＋uuid 不撞名，歸屬靠 DB 紀錄＋RLS）；租戶可改指向自己的 Agent（檔留在客戶內網，雲端只留參照）。`compliance.remote_agents` 有 RLS 但沒 FORCE、INSERT policy 無條件（註冊 token 綁租戶，實務上擋得住），交 Agent 資安那條線確認
- **Cloudflare 真實 IP**：FE `83556a06`（CM-3655，另一 session）已讓前門只信 Cloudflare IP 範圍送來的真實 IP，1.30.0 起帶上，未在雲端實測稽核紀錄
- **外部延遲 0.65～1.25 秒主要是網路**：Cloudflare 免費方案把台灣使用者分到 SJC（聖荷西），VM 內只要 17～24 ms。`/assets/*`（檔名帶雜湊）Cloudflare 已快取（預設 4 小時）；`theme-init.js`、`layout/` 主題檔名固定也被快取 4 小時，前端改主題時升級後最多 4 小時不生效。可改前門 nginx 分檔種設 Cache-Control（雜湊檔 1 年 immutable、固定檔名 no-cache），屬產品改動。**決策者 11-08 裁：先不動，看客戶反應**
- **告警確認信**：Google 寄到 raymond.jedicotech@gmail.com，決策者要點，**未確認是否點了**
- **License Center 的 Cloudflare Access 放行 35.194.213.124**：決策者要做，**未確認是否做了**

## §5 Pre-flight

```bash
gcloud config configurations list                       # guidantai 要 active
gcloud billing projects describe guidant-ai-cloud-514012 --format="value(billingEnabled)"   # True
gcloud secrets list --project=guidant-ai-cloud-514012 --format="value(name)" | wc -l       # 18
ssh -o ConnectTimeout=8 jedi@35.194.213.124 'free -g | sed -n 2,3p; df -h /srv/guidant-ai | tail -1; ls /srv/guidant-ai'
curl -s -o /dev/null -w "%{http_code}\n" -m 15 https://guidant-ai.jedicotech.com/          # 200
curl -s -m 15 https://guidant-ai.jedicotech.com/api/1.0/setup/status                      # turnstile.enabled true
ssh jedi@35.194.213.124 'sudo guidantai status | grep -c healthy'                         # 9
cd ~/Projects/Billows/Audit-Manager/guidant-ai-infra/envs/gcp-prod && terraform plan -detailed-exitcode >/dev/null; echo $?   # 0＝無漂移
```

## §6 環境座標

| 東西 | 在哪 |
|---|---|
| infra repo | `~/Projects/Billows/Audit-Manager/guidant-ai-infra`（GitLab `auditmanager/guidant-ai-infra`）；**導讀 `docs/terraform-walkthrough.md`** |
| Terraform 初始化 | `terraform init -backend-config="bucket=guidant-ai-cloud-514012-tfstate"` |
| Cloudflare 權杖 | infra `.env` `CLOUDFLARE_API_TOKEN`、`CLOUDFLARE_ZONE_ID`（600、不入版控） |
| 安裝包 | 188 `/opt/guidant-ai-be/.build/bundle/` |
| installer 原始碼 | BE `scripts/installer/install.sh`、`install.conf.example` |
| 設計站 | BE `docs/features/FR-184-2611-gcp-vm-deployment/`（`index.html` 雙擊可開） |
| 架構圖 | 同上 `diagrams/cloud-architecture.png`，圖源 `cloud_architecture.py`（venv 要裝 diagrams 0.25.1） |
| 工作坊 | `http://127.0.0.1:7777`；工作坊 46 暫停中 |

## §7 行為規範提醒

- infra repo：**runner 只做到 plan**，apply／destroy 首腦或決策者做；plan 出現 destroy／replace 停下
- **祕密不進 repo、不進對話、不進 Notion**；從 Secret Manager 讀值只在記憶體或 600 暫存檔裡流動，用完刪
- `docker compose up -d` **必帶 service 名**
- 首腦親手的卡開卡時直接開成「暫緩」，不靠 Model 留空擋派（memory `feedback_brain_handson_cards_open_as_paused_not_empty_model`）
- 不切 branch；commit 可自行做；push 自 2026-10-31 起常態授權（推自己當下 branch、推前 fetch、不推 main、不 force）

## §8 不在本期 scope

K8s；saas 模式；AWS module；多租戶自助註冊；高可用；Agent 資安（另一個 session）；Cloudflare DNS 收進 Terraform。

## §9 給 fresh session 的短 prompt

```
請接手 FR-184（主系統上 GCP），這一棒要陪我做 Terraform Q&A（PM 會問）。先讀 docs/features/FR-184-2611-gcp-vm-deployment/handoff/FR-184-STATE.md，照 §0 讀序讀完（含 infra repo 的 docs/terraform-walkthrough.md 與實際 .tf 檔）、答 §0.4 冷接自檢，再跑 §5 pre-flight。
讀取與盤點完成後不做任何事——不派工、不跑測試、不改檔、不 ssh 上 VM 動手、不 terraform apply，回報自檢答案與現況後等我下指令才開始作業。
```
