---
title: "落地版 Installer — 設計文件 (FR-065)"
brand: "Guidant AI · **FR-065** 落地版 Installer"
eyebrow: "FR-065 · On-premise Installer — 設計文件 · 2026-08-16"
h1: "落地版 Installer 設計定案"
lede: "把「出貨 bundle → 客戶主機 → 可登入的系統 → 後續換版」整條路標準化成客戶可自助執行的 installer。本文件承接同資料夾需求討論稿的 D1–D13 定案，轉為正式設計：full-stack compose、FE production image、bundle 交付、DB 從零初始化、install.sh、Web 設定精靈、升級回滾與 K8s 預留。"
chips: [
  {text: "設計定案 2026-08-16", kind: ok},
  {text: "餘 D11.3（PM 議）開放", kind: warn},
  {text: "母案 CM-1189", kind: accent},
  {text: "前作：FR-063 打包 · FR-064 防竄改", kind: accent},
  {text: "拆分：5 階段 26 子任務（19＋T-2.0＋修補 3 卡＋.5 三卡）", kind: plain}
]
footer: "FR-065 · 落地版 Installer — 設計文件 · 2026-08-16 · 母案 CM-1189 · 內容真相源：本資料夾 discussion.md（D1–D13 定案）＋ db-init-inventory.md（CM-1207 DB init 基線盤點）"
---

> 狀態：**設計定案**（D1–D12 已拍板；D13 大半收斂 2026-08-17；餘 D11.3（樣板 SSP，PM 議）開放）｜建立日期：2026-08-16｜母案：[CM-1189 FR-065 Installer](https://app.notion.com/p/FR-065-Installer-3bc346da4cd081c4abf3ce74a28ba384)
> 前作：[FR-063 Nuitka 落地版打包 design](../FR-063-2608-nuitka-packaging/design.md)（image 1.14.0 出貨）／[FR-064 防竄改偵測與授權鎖定 design](../FR-064-2608-tamper-detection/design.md)（34 卡 Done）
> 討論稿（唯一內容真相源，含 e2e-env 借鑑全文與 D 項拍板脈絡）：[`discussion.html`](./discussion.html)｜DB init 細節引用源：[`db-init-inventory.html`](./db-init-inventory.html)（CM-1207）

## 變更紀錄

| 日期 | 變更 | 對應 |
|------|------|------|
| 2026-08-16 | 初版落地：D1–D12 定案轉入，D11 十子項落裁決（8 定案、D11.3/D11.5 保留），D13 待公司討論 | FR-065 母案 CM-1189 |
| 2026-08-17 | 追加 T-2.0（DEV 清理＋序號歸位，CM-1266）入 .2 拆分表；D13 大半收斂（root＝原廠系統殼、root admin 原廠持有）；root admin 密碼永不過期列 FR-065 後另案處理（CM-1267） | 母案 CM-1189 |
| 2026-08-17 | D2 補裁 HTTPS 憑證：install.sh 產 per-install 自簽憑證（Clipboard API secure context 需求）；T-1.2/T-2.5 對應更新 | 母案 CM-1189 |
| 2026-08-17 | 補「分工概述（30 秒版）」節（整案一句話＋四棒一行表＋依賴），置於需求背景之後、決策表之前 | 母案 CM-1189 |
| 2026-08-17 | 實況同步：.1/.2/.3 實作完成＋修補三卡（T-2.5b/T-2.5c/T-3.2b，19→23 卡）；D6 補裁 license 匯入時機（登入後走既有 FR-062 開通頁、不在精靈內）；§6 補 188 真機首測紀錄；追加「出貨前決策清單」節 | 母案 CM-1189 |
| 2026-08-18 | D14 定案：物件儲存 MinIO→SeaweedFS 五點拍板（出廠即啟用／B 案正式 storage type／bundle 帶 image／框架 PDF 隨包出貨／MinIO 選配段移除）；§5 追加 FR-065.5 段（3 子任務，23→26 卡，CM-1275～1278）＋遷移工具 follow-up CM-1279；§7 第 3 題收口 | 母案 CM-1189／子需求 CM-1275 |
| 2026-08-18 | 排程重整：T-4.3 本次範圍跳過（有真實 Windows 需求時再啟動）；.4 前置待收清單成文（CM-1235／CM-1273＋CM-1269 併棒／CM-1275 驗收／CM-1270 真機複驗）；§7 補第 5 項 PyMuPDF AGPL 替換（CM-1235，出貨合規硬前置已排棒）＋剩餘未決盤點；CM-1269 修復方案（就地修復不重匯，四步）回寫該卡 | 母案 CM-1189 |

---

## 1. 需求背景與端到端流程

### 1.1 背景

本案是落地版三階段戰線的第三棒：FR-063 把 BE 編成不帶原始碼的 Nuitka image、FR-064 給了它防竄改與授權鎖定——「跑起來之後」的問題已解完。**還沒解的是「怎麼跑起來」**：

1. **客戶側安裝資產現況＝零**。唯一安裝素材是 `docker/production/docker-compose.yml`＋`deployment-env.md` §0 四步手動流程，寫給自己人不是客戶。
2. **DB 從零建庫沒有機制**。186 張表、107 條 RLS policy、5 個 schema、一票必備 seed，全靠 DEV 這顆活了兩年的庫（CM-1207 已盤點完整清單）。
3. **FE 沒有落地交付形態**。正式部署至今是 zip dist scp 手工擺放。
4. **image 沒有交付通路**。build_image.sh 不含 push／save；Harbor 是內部 registry，air-gapped 客戶碰不到。
5. **升級／回滾整段空白**。換版只有「改 `GUIDANT_VERSION` 重跑 `up -d`」一句。

**目標**：一份出貨 bundle＋一支 installer，客戶自己（或一般 IT 人員照手冊）完成安裝、開通、日後升級，**不依賴原廠工程師到場**。硬前提承 FR-064 部署假設：**客戶環境可能完全 air-gapped，一切須離線自洽**。

### 1.2 首次安裝＋開通流程

安裝與開通有「雞生蛋」結構：license 綁機器指紋（`sha256(/etc/machine-id)`），但取指紋的唯一現成介面需 JWT 登入——所以旅程必須在裝機當下就把指紋交到客戶手上：主展示面是 Web 設定精靈頁（D10），`installer fingerprint` 為保底（D6）。

```{.mermaid cap="圖 1 — 首次安裝與開通端到端時序（含原廠簽照往返）"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
sequenceDiagram
    participant V as 原廠（build＋LC 簽發站）
    participant C as 客戶安裝者
    participant H as 客戶主機
    participant S as Guidant AI 服務

    V->>C: 出貨 bundle（install.sh＋image tars＋bundle manifest＋SQL/seed＋手冊）
    C->>H: 解開 bundle，執行 install.sh
    H->>H: 環境檢查（docker/compose 版本、/etc/machine-id 非空、磁碟/記憶體）
    H->>H: docker load 各 image ＋ 比對 bundle manifest digest（D3）
    H->>H: 產生基礎設施密碼組（cmmgr/cm_app）＋ 寫 guidant.env
    H->>H: DB init（one-shot init 容器跑分段腳本：schema＋字典 seed，不建 admin，D4）
    H->>S: docker compose up -d（PG/Redis/BE api/BE socketio/FE，D1/D2）
    S-->>H: healthcheck 全綠
    H-->>C: 印出：「請開 http://<主機>/ 完成設定」＋ 一次性 setup token（D10）
    C->>S: 開站進 Web 設定精靈：貼 setup token → 建 admin 帳號（密碼自設）
    S-->>C: 精靈顯示機器指紋（sha256 machine-id，D6）＋ license 匯入引導
    C->>V: 抄指紋（離線通路：mail／電話）申請簽照
    V->>V: LC 簽發站簽 license（綁指紋）
    V-->>C: 回傳 .license 檔（PEM armor）
    C->>S: 在精靈匯入 license（復用 FR-062 開通頁）→ 完成安裝進登入頁
    S-->>C: 開通完成，全功能可用
```

### 1.3 升級流程

```{.mermaid cap="圖 2 — 換版升級時序（含備份與回滾語意，D5）"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
sequenceDiagram
    participant V as 原廠
    participant C as 客戶安裝者
    participant H as 客戶主機
    participant D as PostgreSQL

    V->>C: 升級 bundle（新版 image tars＋增量 migration＋manifest）
    C->>H: 執行 install.sh --upgrade
    H->>D: ① pg_dump 全量備份（落地到指定備份目錄）
    H->>H: ② docker load 新 image ＋ digest 驗證
    H->>D: ③ one-shot init 容器套增量 migration（依 schema_migrations 判斷起點，align_migrations 演算法移植）
    H->>H: ④ 改 GUIDANT_VERSION → docker compose up -d（自動 recreate）
    H-->>C: healthcheck 全綠，升級完成
    Note over C,D: 回滾＝restore 備份 ＋ GUIDANT_VERSION 回舊 tag 重跑 up -d
```

---

## 分工概述（30 秒版）

整案一句話：把前兩案的產物（編譯好的 image＋防竄改機制）包成「客戶拿到一包東西，自己裝得起來、開得了通、升得了版」的落地安裝方案。

| 棒 | 做什麼（一句話） | 產出 |
|----|----------------|------|
| **.1 整組服務一鍵起**（5 卡） | compose 從「只有後端兩服務」擴成全家桶——DB、Redis、後端×2、前端（反向代理單一入口），乾淨機器 `up -d` 全起 | 產品版 docker-compose＋前端正式 image＋.env 範本 |
| **.2 建庫＋安裝腳本＋出貨包**（7 卡） | 清乾淨 DEV 做空庫基準 → 建庫腳本 → install.sh（檢查環境/載入 image/建庫/起服務）→ 打成 tar 出貨包（含正式簽章鑰） | install.sh＋DB 初始化機制＋出貨 bundle |
| **.3 開通精靈＋升級**（5 卡） | 客戶裝完開瀏覽器走設定精靈（建租戶與管理員、抄機器指紋、匯授權開通）＋ `--upgrade` 換版（備份→換 image→補 schema→重起） | Web 設定精靈（前後端）＋升級/回滾流程 |
| **.5 SeaweedFS 物件儲存替換**（3 卡，2026-08-18 追加） | 出貨包物件儲存從 MinIO 選配改為 SeaweedFS 出廠即啟用（正式 storage type＋compose 常駐＋框架 PDF 隨包出貨） | jedi 套件 storage type＋compose/install.sh 改造＋bundle 擴充 |
| **.4 手冊＋真機驗收**（3 卡） | 客戶安裝手冊＋乾淨機器全鏈真走一遍（裝→開通→升級→回滾） | 手冊＋驗收紀錄 |

依賴：.1→.2→.3→**.5**→.4 直線。決策者功課：.2 前裁 D11.2/D11.5＋備 CMMC 初始化資料、.3 前公司議 root 介面硬擋與否。

---

## 2. 決策定案（D1–D13）

> 2026-08-16 批次拍板：D1–D10／D12 全數定案；D11 十子項 8 定案 2 保留；D13 待公司討論。定案一字內容以討論稿 callout 原文為準，本表為設計文件形狀的收斂。

| # | 一句話定案 | 理由摘要 | 被排除方案 |
|---|-----------|---------|-----------|
| **D1** | 依賴服務形狀：**PG／Redis compose 全包為預設**，image 隨 bundle 出貨；客戶自備服務時自行修改 compose 切外部（檔內註解引導：改 `DB_HOST`／`REDIS_HOST`、移除對應 service 段）；MinIO 選配不預設（storage 預設 local）；recreate 風險防護進實作設計（傾向 DB/Redis 獨立 compose project，或大字警語＋installer 包裝所有 up 指令永遠帶 service 名） | air-gap 單機客戶是主場景，要求自備等於把最難的一段推回給客戶；`up -d` 不帶 service 名會 recreate 同 project 的 DB（CLAUDE.md 有案） | installer 做 `--external-db` 類參數（列未來可選加分項） |
| **D2** | FE production image 與反向代理：**`nginx.onprem.conf`＋FE production image＋單一入口 port（80/443）**，進 build 管線、tag 與 BE 同版號；BE 8000/8002 收進內網。**HTTPS 憑證（2026-08-17 補裁）：install.sh 裝機當下 openssl 產 per-install 自簽憑證**（CN/SAN 填客戶主機名/IP，落 volume 掛 nginx），80→443 轉址；動機不只安全——精靈指紋複製鈕走 Clipboard API 需 secure context，http://<IP>/ 下直接不動作；手冊寫明瀏覽器警告屬正常＋換客戶正式憑證步驟（覆蓋同路徑＋restart fe） | FE 已具備容器化全部零件（Dockerfile `ARG NGINX_CONF`、`build:PRD` 相對路徑同源即可用、`nginx.e2e.conf` 現成反代範本），缺的只是產品版定案；`VITE_DEV_MODE` 與 `VITE_TURNSTILE_SITE_KEY` 實作棒查清語意後定值 | 沿用 zip dist scp 手工擺放；bundle 內帶共用自簽憑證（所有客戶同一把私鑰＝沒加密）；只跑 80 留 HTTPS 給客戶自理（複製鈕失效） |
| **D3** | image 交付通路：**docker save tar bundle 為主＋bundle manifest digest 比對進 installer 必做**；Harbor pull 文件化為有網客戶的替代路徑 | air-gap 客戶拉不到 Harbor，通路只剩實體傳遞；FR-064 D7 已把 image 層驗證劃入本案——確保客戶手上的 image 與原廠出貨一致 | cosign 簽章方案（列評估項，不在 v1 硬性範圍） |
| **D4** | DB init 載體：**分段 SQL 腳本（B）＋one-shot init 容器（C 載體）**，照 CM-1207 §5.4；基線真相取 DEV pg_dump；init 容器與升級 migration 容器共用同一形態 | 最高權限憑證只存在建庫當下（服務只拿 cm_app 憑證）；每段可獨立 review／重跑、參數注入面窄 | A（單一 3 萬行 init.sql，難維護、cluster 層塞不進同一支）；C 純 entrypoint 自動 init（服務容器須持有 cmmgr 憑證，違反最小權限） |
| **D5** | 升級與回滾：**四步包成 `install.sh --upgrade`**（備份→load＋digest→套增量 migration→up -d）；回滾＝restore 備份＋回舊 tag，**明文為災難逃生非常規操作**；相容性檢查 v1 最簡版（bundle manifest 標適用起始版號區間，不符即拒跑） | `align_migrations.sh` 的 diff＋apply 演算法現成，移植到客戶端形態即可 | 無備份直升；自動回滾機制（v1 不做） |
| **D6** | 首次開通雞生蛋：**指紋主展示面＝Web 設定精靈頁（顯示＋複製鈕）**；`installer fingerprint` 子命令為保底（裝機尾端自動印＋可隨時重跑，服務起不來時可用），演算法與 `machine_fingerprint.py` 一字不差，實作棒需驗「兩邊算出同值」。**2026-08-17 補裁（CM-1259 偏離確認）：license 匯入時機定案「裝完後走 Web、不在精靈內」——登入後走既有 FR-062 開通頁匯入，精靈收在顯示指紋** | 取指紋唯一現成介面需 JWT 登入，但簽照又需要指紋；宿主上一行 shell 即可算出同值 | 只靠登入後打 machine-code API（流程繞） |
| **D7** | 正式簽章鑰（PROD kid）：**劃入本案 .2 棒**——LC 端生成 PROD 鑰對→`public_keys.py` 新增 PROD kid→重 build image；出貨 bundle 一律以含 PROD 鑰的 image 產出 | `public_keys.py` 現有 DEV/STG/POC 三把、無 PROD 鑰，出貨即「未知的 kid」；四步一組教訓適用 | 延到出貨時再補（硬前置不可延） |
| **D8** | 外網依賴：**落地版預設停用 Turnstile**（BE/FE 一對開關，實作棒查清現況後補齊缺的開關）；bundle 手冊附「本產品可能外連清單與停用法」 | Turnstile 內網連不到 Cloudflare 會直接讓登入失敗；LC 在線回報已 best-effort 離線自洽；AI API／Drive 屬功能模組不啟用即不外連——實作棒仍做一次完整外連盤點 | 維持「需評估」懸置 |
| **D9** | Windows 支援：**僅出評估文件＋前提清單，不實作不承諾** | WSL2 `/etc/machine-id` 穩定性、volume 效能、Docker Desktop 企業授權條款皆未驗證；有真實客戶需求時另開案 | 原生 Windows 版 |
| **D10** | installer 形式：**純 bash `install.sh`（不做 TUI）＋首次啟動 Web 設定精靈**（混合式，Portainer／GitLab／Nextcloud／Jenkins 式）。bash 管基礎設施段（不建 admin，尾端印一次性 setup token 防搶注）；精靈管應用設定段（建 admin 密碼客戶自設不經終端留痕）；SMTP／storage 裝完後台慢慢設；systemd 不另立 unit（installer 確保 `systemctl enable docker` 即可） | 業界主流模式，體驗優先（user 拍板值得多一棒工作量） | TUI；純 shell 印生成密碼＋首登強制改密（機制現成但體驗差一階，列 fallback） |
| **D11** | seed 出貨範圍：十子項見下表（8 定案、2 保留） | 承接 CM-1207 §3.4 待裁決清單 | — |
| **D12** | FR-064 出貨遺留歸屬：**①進版（version-bump）與②image 推 Harbor 劃入本案出貨動作**（掛 .2 bundle 打包段前後）；**③K8s liveness 鎖定殼 CrashLoop 不在本案實作、但設計預留**（見 §4.8） | 出貨 bundle 必然要定版；user 明示「要預留 K8s」——compose 服務契約保持可翻譯性 | 把 K8s 適配一起做（未來另開 FR） |
| **D13** | 🔶 **大半定案（2026-08-17）**：root tenant＝**原廠管理的系統殼**——root admin 帳號密碼由原廠持有管理，客戶不碰；Web 設定精靈**不建 root admin**（DB init seed 建、密碼裝機生成交原廠），精靈建的是「客戶第一個業務租戶＋租戶管理員」。**殘項收案（2026-08-17 決策者）：v1 先不做介面硬擋，照現狀出一版，之後有問題再調**。連帶裁示：root admin 密碼永不過期（豁免 90 天政策）＝**FR-065 後另案處理**，卡號 CM-1267 | 市面同構（Keycloak master realm／GitLab Admin Area／Atlassian DC 皆「系統層＝管理殼」）；我方 B-2 模型下 root 本就是系統資源掛載點，DEV 拿 root admin 做業務是開發便利非設計意圖 | Payload CMS 式「super admin 無租戶歸屬」（與 B-2 架構相反，不可學形狀） |
| **D14** | ✅ **定案（2026-08-18）物件儲存 MinIO→SeaweedFS 五點**：①**出廠即啟用**——SeaweedFS 進產品 compose 常駐服務，root tenant 與預設 STORAGE_CONFIG seed 皆指向它，客戶檔案第一天就進物件儲存（不再選配）；②**B 案（語意乾淨版）**——jedi-file-upload 加 `SEAWEEDFS` 正式 storage type（adapter 繼承 MinIO 版，S3 gateway 已實測全 API 通過），`minio` type 保留供客戶自備 S3 相容儲存切換；③**bundle 帶 seaweedfs:3.99 image**（官方現成，~100MB）；④**框架 PDF（系統檔）隨 bundle 出貨**——基線產製時在 SeaweedFS 上匯 CMMC→bucket 匯出 storage-seed/ 打進 bundle→install.sh 原 key 灌回（不經 BE、不改資料列、冪等）；⑤**MinIO compose 選配段移除**＋手冊補「更換儲存後端」警語章＋遷移工具開 follow-up（CM-1279）不擋 .4。落地＝FR-065.5 三卡（§5） | MinIO 社群版閉源化；SeaweedFS S3 gateway 與既有 minio SDK adapter 全相容（CM-1268 實測，`docs/analysis/2026-08-17-seaweedfs-evaluation-and-dev-deployment.md`）；system-scope 檔讀取走當下 root STORAGE_CONFIG，客戶切換後系統檔立即全斷——故框架 PDF 必須連檔出貨且手冊必須警語 | 沿用 MinIO（閉源化風險）；A 案零改碼（`minio` 標籤裝 SeaweedFS，出貨產品長期語意債）；維持選配（切換遷移支線複雜） |

### 2.1 D11 子項裁決表（seed 出貨範圍，承接 CM-1207 §3.4）

字典類必備 seed（operations／system_menus／ui_routes／capabilities／root tenant+org／Administrator 角色／RUNTIME_CONFIG 等，CM-1207 §3.1）已明確屬出貨集，不在此重列。

| # | 一句話定案 | 備註 |
|---|-----------|------|
| **D11.1** | ✅ **改版定案（2026-08-17）**：框架只出 CMMC 2.0，**由原廠於出貨基線庫匯好**——決策者本人在清完的 `guidant_ai` 上走正規 PDF 匯入 L1/L2，匯入自動產生 AO 級範本一併入基線；bundle 不帶框架檔、客戶不需自行匯入 | 原案「bundle 帶 OSCAL JSON 客戶自匯」（多一個客戶側出錯環節；「DEV 分不出內建」顧慮在乾淨基線庫不存在）；seed dump 直灌 catalog 資料列 |
| **D11.2** | ✅ **全數定案（2026-08-17）**：流程範本實查兩張表——① `flow_templates`（主流程）只帶 id 1 完整稽核流程＋id 3 自我評估稽核（兩筆 inactive 內建砍掉）；② `workflow_templates`（AO 級 BPMN）**清空不 seed**——經程式碼驗證（`_init_ao_workflows()`）它是匯入 catalog 時自動產生的衍生資料，與 D11.1 同模式；T-2.2 驗收補「空庫匯入 CMMC catalog 自動產 AO 範本跑通」 | seed 照抄 DEV 任一包（59/186 皆歷次匯入測試殘留）；人工維護 AO 範本對照表 |
| **D11.3** | ⏸ **保留**：樣板 SSP／MF defaults 家族——裁示先留空，出貨基線不帶，**與 PM 討論後另定範圍** | DEV 現況分不出產品內建 vs 開發試作 |
| **D11.4** | 🔄 **2026-08-21 決策者推翻，改比照 D11.1「原廠匯好進基線」**（CM-1322）：原案「隨 bundle 帶檔＋管理員 UI 逐支匯入」作廢，理由「不可能讓管理員做，這要預設裝好就有，要比照合規框架」。現行＝原廠在基線庫走正規 API 匯好（8 TWGCB＋2 dev-sec，解析全 succeeded），**三者同時進基線**：profile 資料列＋upload_files 列（`05-seed-catalog.sql`）＋檔案實體（`detection-profiles/storage-seed.map` → build 時重建進 storage-seed）。客戶裝完即有 10 套可用基準，零手動步驟。id 已重排為 1..N 連續（決策者要求）。<br>連帶：CINC 解析器**不進 BE image**（省 ~270MB）——內建基準已解析完成不需它；客戶自傳新基準檔的進階場景改由文件交代 compose override 唯讀掛載 `/opt/cinc-auditor`（手冊 §10.3） | 原案顧慮的「seed 灌列、檔案不在＝壞連結」由三者同時進基線解決（框架 PDF 已走通同模式）；本次落地已驗證 8 支檔 MD5 與 seed 資料列 checksum 逐一吻合 |
| **D11.5** | ✅ **定案（2026-08-17）**：FEEDBACK_TYPE 3 筆照帶；FUNCTION 群**不沿用舊清單**，改以 `ui_routes` `enable=1` 且有 url 的頁面 `name` 重新產生（排除 group-* 分類節點，約 33 筆＋others 兜底），seed 腳本從 ui_routes SELECT 產生不硬編；id 1~4 中文舊殘留不帶 | 沿用 DEV 35 筆（textbook-*/resource-*/cruise-* 為舊產品線殘留） |
| **D11.6** | ✅ `ISSUE_INTEGRATE_CONFIG` 出廠帶 disabled 空殼，客戶後台自行啟用接自己的 GitHub／GitLab | 功能對落地客戶開放 |
| **D11.7** | ✅ `public.alembic_version` **暫留**（基線帶此表、不清除） | 舊版 flask-migrate 遺留，user 證實來源已明 |
| **D11.8** | ✅ root tenant 顯示名**由客戶命名**（Web 設定精靈輸入）；root id=1 是硬約定不可變，只裁顯示名；DEV 值不沿用為出廠固定值 | 連動：若 D13 採系統殼方案，命名對象改為第一個業務租戶、root 顯示名固定（如 System） |
| **D11.9** | ✅ debug view（`debug_org_units`／`v_tenant_parent_debug`）確認零引用後**連 DEV 一併刪除**（grep 全 codebase→開 migration drop、走 sql-migration 流程與 DROP 前安全查核鐵則→出貨基線自然不含）；`cm_app_group` 空群組**保留** | 空群組利未來多 app 帳號 |
| **D11.10** | ✅ 落地版 DB 名統一 **`guidant_ai`**（2026-08-08 DB 命名新制，落地新客戶屬新建置適用新制）；installer `INIT_DB_NAME` 預設值定此 | — |

---

## 3. 現況接入點盤點

### 3.1 元件 × 現況 × 本案動作

| 元件 | 現況 | 本案動作 |
|------|------|---------|
| **BE image** | `guidant-ai-be:1.14.0`：ubuntu:22.04 基底、中文字型／LibreOffice／WeasyPrint 原生庫已進 image、產物 root:root＋entrypoint setpriv 降權 uid 1000、不帶 python/原始碼 | 直接沿用，不動 |
| **BE build 管線** | `scripts/build/`：build_all.sh→smoke→build_image.sh；integrity manifest 簽章在 build_release.sh Step 4 尾端送 LC 簽 | 擴充：FE image build（D2）＋bundle 打包段（D3） |
| **compose 定式** | `docker/production/docker-compose.yml` 僅 guidant-api(8000)＋guidant-socketio(8002)；read_only＋tmpfs；7 個掛載（static/log/home/tmp/pki/machine-id/agent 憑證）；無 DB/Redis/MinIO/FE；檔頭警語：`up -d` 必帶 service 名 | 擴充為完整 stack（D1/D2），處理 recreate 風險；既有定式全數保留（§4.1） |
| **FE 交付** | 有 Dockerfile（node build→nginx:alpine，`ARG NGINX_CONF` 可換 conf）；`build:PRD` 走相對路徑（`VITE_API_URL=/api/1.0`、WS 同源）不需 per-customer rebuild；`nginx.e2e.conf` 是現成同源反代範本但限 E2E；正式部署＝zip scp 手工；build-time 綁死僅剩 `VITE_TURNSTILE_SITE_KEY` 與 `VITE_DEV_MODE`（PRD 現值 `true`，落地要檢討） | 新增 production 容器形態（D2，§4.2） |
| **DB 初始化** | 零。DEV 實況 5 schema／186 表／107 條 RLS policy／156 sequence；cmmgr/cm_app 建立紀錄不在 repo；CM-1207 已產出完整基線盤點＋方案草案 | 本案核心交付（D4，§4.4） |
| **migration／升級** | `manifest.tsv` active 138 筆（127 筆 envs=`*`、11 筆環境限定不屬基線）；`align_migrations.sh` diff＋apply 演算法現成但 target 寫死內部 IP、密碼讀 repo `.env`；換版現況無備份/回滾/相容性檢查 | 升級流程（D5，§4.7） |
| **license／指紋** | 指紋＝`sha256(/etc/machine-id)`；取指紋唯一介面需 JWT 登入；無照態服務照起但 license 軸外全 403（`/license/` 與 `/login` 豁免）；license 檔 PEM armor 貼 FE 頁匯入；unlock.token 落 `/opt/guidant/pki` | 開通旅程（D6，§4.6）；宿主前提 `/etc/machine-id` 非空（CM-1242 實測） |
| **簽章公鑰** | `public_keys.py` 硬編進 image，現有 DEV/STG/POC 三把、**無 PROD 鑰** | D7（§4.3） |
| **外網依賴** | Turnstile 內網連不到 Cloudflare 登入失敗（「需評估」未定案）；LC 回報 best-effort 已離線自洽 | D8 |
| **image 交付通路** | 完全沒寫；docker save 先例只有 FR-058 agent image | D3（§4.3） |
| **FR-064 出貨遺留** | 未進版（現標 1.14.0）；image 未推 Harbor；K8s liveness 下鎖定殼會 CrashLoop（compose 不受影響） | D12（進版＋推 Harbor 掛 .2；K8s 預留 §4.8） |
| **客戶側安裝資產** | 零（compose＋deployment-env.md §0 四步手動流程） | 本案主體 |

### 3.2 e2e-env 借鑑要點

test repo `compliance-manager-test/e2e-env/` 的 8-service 全套 compose 每天在驗證「這組服務能在一個 compose 內良好共處」。user 指示：**分析借用、不整抄**——借**拓撲與機制**三條線（服務組成／互連／啟動順序），不借**資料來源、image 血統、測試編排**三條線。可移植 12 項：

| # | 借什麼 | 為什麼 |
|---|--------|--------|
| 1 | PG service 形態：postgres:16＋顯式 `POSTGRES_INITDB_ARGS`（`--encoding=UTF8 --locale=en_US.utf8`）＋named volume＋`pg_isready` healthcheck | 不顯式指定，客戶主機預設 locale 滲進 initdb，跨機不可重現 |
| 2 | Redis 必須 **ACL user 而非 requirepass**（`--user "$REDIS_USER" on ... "+@all"` 段必抄＋`appendonly no`＋healthcheck 用 ACL user 實測 ping） | BE config 組 `redis://<user>:<pass>@host` 形式，requirepass 只認 default user 會連不上 |
| 3 | 同源 nginx 反代拓撲：`/api/1.0`→be:8000、`/socket.io`→be-socketio:8002（**必須分開兩 service**）、SPA `try_files` | 與 `build:PRD` 相對路徑產物互補，單一入口不需 per-customer rebuild |
| 4 | nginx `resolver 127.0.0.11 valid=10s`＋變數 upstream 動態解析 | 不寫的話 BE restart 換 IP 後 nginx 快取舊 IP 全站 502——客戶自己重啟服務是常態 |
| 5 | 反代調校：`client_max_body_size 0`、API `read_timeout 600s`＋buffering off、socket.io 3600s＋WS upgrade | 大檔上傳與長工時匯出（SSP docx/PDF）會踩預設 timeout |
| 6 | `depends_on condition: service_healthy` 啟動順序鏈 | DB/Redis ready 才起 BE、BE healthy 才起 FE——「up 完即可用」的前提 |
| 7 | 一 image 兩服務（`RUN_MODE`）＋YAML anchor | 產品 compose 已有同形態，e2e-env 驗證了與 DB/FE 同 compose 也成立 |
| 8 | MinIO 選配段形態：minio＋mc init job（`--ignore-existing` 冪等） | D1 已定 MinIO 選配，冪等 init job 形態直接可用 |
| 9 | 零硬編憑證＋`.env.example` 規範（只列 key 與用途） | 與本案「密碼由 installer 產生、憑證不入版控」一致 |
| 10 | BE 必要 env 踩坑清單：`ENABLE_MULTI_TENANT` 必須顯式 `"true"`（缺了 RLS 靜默擋、查無資料無錯誤）；`DRIVE_TOKEN_ENCRYPTION_KEY` 啟動即檢；`DETECTION_TOOL_ENCRYPTION_KEY` 是另一支（錯誤訊息會誤導）；`DB_PORT` 顯式 5432（BaseConfig 硬編 25432） | 每一條都是 e2e-env 實踩過的啟動失敗／靜默故障，.env 範本照單全收 |
| 11 | 容器內 vs 對外 port 分離（BE 連 `service:5432`，對外映射另計） | 客戶主機 port 衝突時只改對外映射，服務互連不動 |
| 12 | **HTTPS 是功能必要不是加分**：FE aiChatStore 用 `crypto.randomUUID()`，非 secure context 下為 undefined 且錯誤被吞→登入卡死零訊息 | 443＋憑證掛載是必要設計——不上 HTTPS（localhost 之外裸 HTTP）產品直接壞 |

不可抄清單（golden dump／roles 搬遷／sanitize／mailpit／E2E image／`ENV=DEVELOP_PREMISE`／兩層疊 image／測試編排產物）詳見討論稿〈e2e-env 借鑑分析〉。

---

## 4. 詳細設計

### 4.1 產品 compose 全 stack（D1／D2）

**服務拓撲**（service 名用中性名，不沿用 e2e 的 `e2e-postgres` 類寫死名）：

| service | image | 角色 | 對外 port |
|---------|-------|------|-----------|
| `guidant-db` | postgres:16 | 主資料庫；顯式 `POSTGRES_INITDB_ARGS`（encoding/locale）＋named volume＋`pg_isready` healthcheck | 無（內網 5432） |
| `guidant-redis` | redis | ACL user 形式啟動（非 requirepass）＋`appendonly no`＋ACL user 實測 ping healthcheck | 無 |
| `guidant-api` | guidant-ai-be | REST API（`RUN_MODE` 預設）；沿用既有定式 | 無（內網 8000） |
| `guidant-socketio` | guidant-ai-be | SocketIO（`RUN_MODE=socketio`）；與 api 同 image YAML anchor | 無（內網 8002） |
| `guidant-fe` | guidant-ai-fe | nginx：SPA 靜態＋同源反代，**唯一對外入口** | 80/443 |
| `guidant-minio`＋`guidant-minio-init` | minio／mc | **選配段**（storage 預設 local 不啟用）；mc init job `--ignore-existing` 冪等建 bucket | 無 |

設計要點：

- **與既有 BE compose 定式的關係**：`docker/production/docker-compose.yml` 的 7 個掛載（static/log/home/tmp/pki/machine-id/agent 憑證）、`read_only`＋tmpfs、宿主 `/etc/machine-id` 掛載（CM-1242 指紋修復）**全數保留**，本案是擴充不是重寫。
- **啟動順序**：`depends_on condition: service_healthy` 鏈——DB/Redis healthy→BE 兩服務→FE。
- **nginx.onprem.conf**（以 `nginx.e2e.conf` 為底、upstream 指向 compose service 名）：同源反代 `/api/1.0`→`guidant-api:8000`、`/socket.io`→`guidant-socketio:8002`（含 WS upgrade）；`resolver 127.0.0.11 valid=10s`＋變數 upstream；`client_max_body_size 0`、API read_timeout 600s＋buffering off、socket.io 3600s；SPA `try_files`。
- **HTTPS 443＋憑證掛載是必要設計**（bind ro、私鑰不進 image）：非 secure context 下 `crypto.randomUUID()` 為 undefined、登入卡死零訊息——不是資安加分項，是功能必要（§3.2 第 12 項）。
- **自備 DB/Redis 引導**：compose 內寫註解——改 `DB_HOST`／`REDIS_HOST` 環境變數、移除對應 service 段；installer 不做參數化外接模式（D1）。
- **recreate 風險防護**：傾向 DB/Redis 獨立成另一個 compose project（或至少檔內大字警語＋installer 包裝所有 up 指令永遠帶 service 名），不讓客戶裸打 `up -d`。
- **port 分離**：服務互連走 service 名＋容器內 port，對外映射另計——客戶主機 port 衝突只改映射。
- **.env 範本**：零硬編憑證，只列 key 與用途；§3.2 第 10 項踩坑清單照單全收（`ENABLE_MULTI_TENANT="true"` 顯式、兩支加密 key、`DB_PORT=5432` 顯式）。

### 4.2 FE production image（D2）

- 新增 `nginx.onprem.conf`；Dockerfile 既有 `ARG NGINX_CONF` 換入；build 用 `build:PRD`（相對路徑產物，同源即可用，**不需 per-customer rebuild**）。
- **進 build 管線**：FE image build 併入 `scripts/build/` 管線，tag 與 BE 同版號（`guidant-ai-fe:<X.Y.Z>`）。
- **實作棒查清事項**：`VITE_DEV_MODE`（PRD 現值 `true`——語意與影響面查清後定值，落地版直覺上不該開）與 `VITE_TURNSTILE_SITE_KEY`（現值 Cloudflare 測試 key，與 D8 停用連動）。

### 4.3 bundle 打包與交付（D3／D7）

- **bundle 內容**：install.sh＋全部 image 的 `docker save` tar（BE/FE/PG/Redis，MinIO 隨選配）＋bundle manifest（各 tar sha256 digest＋適用起始版號區間）＋SQL init 腳本與 seed 檔（框架與檢測 Profile 於 2026-08-21 起皆走「原廠匯好進基線」，資料列在 04/05、檔案實體走 storage-seed；D11.1／D11.4 改版）＋manifest.tsv＋客戶手冊。
- **完整性驗證**：installer `docker load` 後逐一比對 manifest digest，不符即拒裝——確保客戶手上的 image 與原廠出貨一致（FR-064 D7 劃入項）。cosign 列評估項不在 v1。
- **Harbor pull 替代路徑**：僅文件化，限有網且信任外連的客戶。
- **PROD 簽章鑰四步**（D7，出貨硬前置）：LC 端生成 PROD 鑰對→`public_keys.py` 新增 PROD kid→重 build image→出貨 bundle 一律以含 PROD 鑰的 image 產出。連帶 D12：進版（version-bump）與 image 推 Harbor 掛在 bundle 打包段前後執行。

### 4.4 DB init（D4／D11）

引用源：[`db-init-inventory.md`](./db-init-inventory.html) §5（方案比較與共同前提全文）。

- **分段腳本組**（`scripts/init/`）：`00-cluster.sql`（CREATE ROLE cmmgr 降規版／cm_app／cm_app_group、CREATE DATABASE，連 postgres 庫）→`01-extensions`（uuid-ossp、pg_trgm）→`02-schema`（DEV pg_dump --schema-only 整理：去 DEV 殘留表、去歷史月分區）→`03-grants`（default privileges ×5 schema＋event trigger `trg_auto_grant_cm_app`，**不逐表 GRANT 400 行**，逐項掃描僅作驗收）→`04-seed-core`→`05-seed-catalog`→`06-admin`（本案 D10 下**不在 init 建 admin**，改由精靈通道；段位保留給精靈 BE 寫入的契約參照）→`99-stamp`＋`init.sh`。
- **執行載體＝one-shot init 容器**：installer 產參數（兩組 DB 密碼）→起 init 容器跑 init.sh→成功後才起服務容器（服務只拿 cm_app 憑證，cmmgr 憑證只存在建庫當下）。與升級 migration 容器共用同一形態。
- **基線真相＝DEV pg_dump**，不是 224 支 migration 重放（歷史串互相覆蓋、混雜環境限定檔）。
- **seed 範圍照 D11 裁決**：字典類必備 seed（CM-1207 §3.1 清單）＋root tenant/org **id=1 硬約定**（顯式指定 id＋setval sequence）＋Administrator 角色＋RUNTIME_CONFIG＋2 支 builtin BPMN（D11.2 定案 id 1/3，兩筆 inactive 內建已砍；CM-1322 查證確認）＋stage_objects 六筆階段字典（CM-1322 補漏）；**CMMC 2.0 與檢測 Profile 皆走「原廠匯好進基線」——資料列確實要 seed**（D11.1 於 2026-08-17、D11.4 於 2026-08-21 先後改版；早期「不 seed、走應用層匯入」的敘述已作廢）；debug view 連 DEV 刪（D11.9，先行 migration）；DB 名 `guidant_ai`（D11.10）；D11.2 清單核對、D11.3/D11.5 依開放項時程補定。
- **冪等**：seed 用自然鍵 `ON CONFLICT DO NOTHING`；schema 段以 `schema_migrations` baseline marker 判斷跳過；失敗語意＝整庫重建（init 場景可接受）。
- **收尾 stamp**：`schema_migrations` 登記 `__init_baseline_v<版號>__` marker＋補登已收斂的 active migration 檔名；`schema_version` 補基線版號——讓後續增量 migration 與三環境 diff 工具無縫接軌，基線之後的新 migration 照舊制走。

### 4.5 install.sh（D10）

**薄終端原則**：bash 只管基礎設施段，應用設定全部讓給 Web 精靈；不做 TUI。

- **環境檢查清單**（宿主前提，門檻數值實作棒定案）：
  - OS：Linux x86_64（glibc ≥ 2.35——BE binary 在 Ubuntu 22.04 編出，基底鏡像自帶 glibc，此條實際約束的是 docker 本身）
  - Docker Engine（最低版本待定，需支援 compose plugin v2）＋`docker compose` plugin；`systemctl enable docker`（開機自起前提）
  - **`/etc/machine-id` 非空**：`test -s /etc/machine-id` 必過（CM-1242 實測：缺檔時 docker 會建成目錄、指紋退回 MAC 每次重啟漂移；極精簡系統用 `systemd-machine-id-setup` 補）。⚠️ machine-id 一旦重生成，既有綁機憑證全部失效——只在「本來就沒有」時補
  - 磁碟／記憶體／CPU：門檻待定（image tars＋DB volume＋上傳檔＋log＋pg_dump 備份；PG＋Redis＋gunicorn 4 workers＋socketio＋nginx 全包最低規格，實測定）
  - 時區：主機 TZ 與部署一致（image 內建 Asia/Taipei，跨時區覆寫但不可各自為政）
  - 網路：無外網前提可運作（air-gap 自洽）；對內開放單一入口 80/443
- **流程步驟**：環境檢查→`docker load`＋digest 比對（不符拒裝）→產生基礎設施密碼組（cmmgr/cm_app 分別產生，不沿用 DEV 同密碼慣例）＋寫 `guidant.env`→DB init（one-shot init 容器：schema＋字典 seed，**不建 admin**）→`docker compose up -d`→healthcheck 全綠。
- **尾端印出**：「請開 `http://<主機>/` 完成設定」＋**一次性 setup token**（Jenkins 式防搶注——避免精靈開放期間被非安裝者搶先建帳號）。不印 admin 密碼（密碼由客戶在精靈自設，不經終端留痕）。
- **`--config` 非互動模式**：參數由設定檔提供，供自動化／重跑場景。
- **`installer fingerprint` 子命令**：見 §4.6。

### 4.6 Web 設定精靈（D6／D10）

- **未初始化偵測**：首次開站偵測「無 admin」自動進入精靈；已初始化則正常登入頁。
- **setup token 驗證**：精靈第一步貼 install.sh 印出的一次性 token，驗過才放行後續步驟。
- **建 admin 一次性通道**：繞過既有權限體系的特殊 API，**用後即封**——admin 建立成功後通道永久關閉。密碼客戶自設（走既有密碼政策驗證）。
- **機器指紋顯示＋複製鈕**：精靈頁顯示 `sha256(/etc/machine-id)` 指紋——這是指紋的**主展示面**（D6）；客戶抄指紋走離線通路（mail／電話）向原廠申請簽照。
- **license 匯入引導**：復用 FR-062 開通頁——拿到 `.license` 檔（PEM armor）貼上匯入→完成安裝進登入頁。
- **保底工具**：`installer fingerprint` 子命令，演算法與 `machine_fingerprint.py` **一字不差**；裝機尾端自動印＋可隨時重跑（服務起不來時可用）；實作棒需加「installer 與 BE 兩邊算出同值」的驗證步驟。
- **root tenant 命名段（🔶 2026-08-17 裁示更新）**：D13 大半收斂採**系統殼方案**——精靈**不建 root admin**（root admin 由 DB init seed 建、原廠持有），此段改為「**建第一個業務租戶**」由客戶命名、root 顯示名固定（如 System），並加「建租戶管理員→日常用租戶管理員登入」引導（setup token 通道建立對象＝業務租戶管理員）。殘餘開放僅「root 層介面硬擋業務與否」（公司議，不阻精靈實作）。
- SMTP／storage 等不進精靈必填，裝完後台設定頁慢慢設。

### 4.7 升級與回滾（D5）

四步包成 `install.sh --upgrade`：

1. **`pg_dump` 全量備份**（落地到指定備份目錄）
2. **`docker load` 新 image＋digest 驗證**
3. **one-shot init 容器套增量 migration**——`align_migrations.sh` 的 diff＋`--apply` 演算法移植到客戶端形態：連線參數外部注入（現版寫死內部 IP、密碼讀 repo `.env`，客戶端重做連線層）、SQL＋manifest.tsv 隨 bundle 出貨、依 `schema_migrations` 判斷起點
4. **改 `GUIDANT_VERSION`→`docker compose up -d`**（自動 recreate）

- **相容性檢查 v1 最簡版**：bundle manifest 標「適用起始版號區間」，目前版本不在區間內即拒跑。
- **回滾語意**：restore 備份＋`GUIDANT_VERSION` 回舊 tag 重跑 `up -d`。**明文告知：回滾會丟升級後產生的資料，屬災難逃生不是常規操作**。

### 4.8 K8s 預留設計要點（D12 裁示：本案不實作，正式預留節）

user 明示「要預留 K8s」——本案不做 cluster 實作，但以下記為未來 K8s 案的起點；compose 寫成乾淨服務契約（服務名／port／env 介面清楚）保持可翻譯性。

**鎖定態排除 liveness 的設計要點**：FR-064 已知，K8s liveness 下鎖定殼會 CrashLoop（compose 落地不受影響）——上 K8s 時需把「授權鎖定態」排除在 liveness 判定之外（鎖定＝業務拒服務但行程存活，不可被 kubelet 視為 unhealthy 重啟）。

**cluster 化前提五條**（compose→K8s 翻譯是機械工，真門檻在應用層假設，先記錄不動）：

1. **機器指紋綁單機**（`sha256(/etc/machine-id)`）——多節點下每台指紋不同，license 模型要改（改綁 cluster identity 或浮動授權）。
2. **鎖定殼 vs liveness**：如上，鎖定態排除在 liveness 判定外。
3. **tamper 偵測的 FS 落點**（`/opt/guidant/pki`、unlock.token）——多副本共享或各自持有需重新設計。
4. **storage 需切 MinIO/S3**：local storage 綁單機磁碟，多副本必然外部化。
5. **socketio 多副本需 sticky session**（或改 message-queue adapter）。

未來有真實需求另開 FR。

---

## 5. 拆分（5 子需求 × 26 子任務——原 19＋T-2.0＋修補三卡 T-2.5b/T-2.5c/T-3.2b＋.5 三卡）

> 實況（2026-08-17）：**.1／.2／.3 全數實作完成**（含追補卡，T-2.5c 進行中除外）；.4 三卡未開工。2026-08-18 追加 **.5（SeaweedFS 物件儲存替換，D14）**，插在 .3 之後、.4 之前。

依賴鏈：**.1（地基）→ .2（DB init＋installer 殼＋bundle＋PROD 鑰）→ .3（精靈與升級）→ .5（SeaweedFS 替換）→ .4（文件與真機驗收，T-4.1/T-4.2 須含 .5 內容）**。.1 依 D1/D2（均已定案）即可開工。開放項影響：**D11.2 清單核對與 D11.5 → T-2.2（.2 開工前定）**；**D11.3 → T-2.2 seed 範圍（現裁「留空」可先行，與 PM 議定後若補範圍另起增量）**；**D13 → T-3.2 精靈流程（.3 棒實作前公司裁決）**。D12 的進版與推 Harbor 屬出貨動作，掛 T-2.6 前後執行。

### FR-065.1 full-stack compose（地基棒）— 依賴：無（D1/D2 已定案）

DB 先用暫行手段（如手動 restore）驗通拓撲，正式空庫 init 屬 .2。

| # | 子任務 | 驗收 | 依賴 |
|---|--------|------|------|
| T-1.1 | 產品 compose 全 stack 骨架：`guidant-db`（postgres:16 顯式 initdb args＋healthcheck）＋`guidant-redis`（ACL user 形式）＋BE api/socketio（既有定式 7 掛載/read_only/machine-id 全保留）＋depends_on healthcheck 鏈＋自備 DB/Redis 註解引導＋recreate 風險防護 | 乾淨機器 `docker compose up -d` 全組 healthy | —（✅ done 2026-08-17） |
| T-1.2 | `nginx.onprem.conf`：同源反代（/api/1.0、/socket.io 含 WS upgrade）＋resolver 動態解析＋timeout 調校＋SPA try_files＋443 憑證掛載（**掛載路徑約定＋80→443 轉址**；憑證產生在 T-2.5，本卡開發驗證可自簽一張） | 反代全路由通；BE restart 後不 502；**自簽憑證 HTTPS 下登入通**（secure context 驗證） | T-1.1（✅ done 2026-08-17） |
| T-1.3 | FE production image：Dockerfile `ARG NGINX_CONF` 換入 onprem conf＋`build:PRD`；查清 `VITE_DEV_MODE`／`VITE_TURNSTILE_SITE_KEY` 語意並定值 | image build 過、同源登入通；兩個 build-time 值有定案紀錄 | T-1.2（✅ done 2026-08-17） |
| T-1.4 | MinIO 選配段＋mc init job（`--ignore-existing` 冪等） | 啟用選配段時 bucket 自動建成、重跑不炸 | T-1.1（✅ done 2026-08-17） |
| T-1.5 | `.env` 範本（零硬編憑證、踩坑清單照單全收）＋暫行 DB（手動 restore）驗通全鏈 | 照範本填值、乾淨機器 FE 反代登入通 | T-1.1～T-1.3（✅ done 2026-08-17） |

### FR-065.2 DB init＋installer 殼＋bundle＋PROD 鑰 — 依賴：FR-065.1（D3/D4/D7/D10/D11）

| # | 子任務 | 驗收 | 依賴 |
|---|--------|------|------|
| T-2.0 | **DEV 資料庫清理非系統資料＋序號歸位（出貨基線前置，CM-1266）**：照 db-init-inventory.md §3.3 清測試租戶 102/131/152/153/158 整族＋業務資料全族＋三張垃圾表 drop＋NOTIFY_CONFIG 真實 secret 筆；保留 §3.1 字典與 root id=1；清完 reset sequences（腳本化逐 schema 掃 sequence 對 max(id)）；SQL 落 scripts/sql/ 標 dev 專屬；🔴 只動 DEV、清理前先 pg_dump 備份 | DEV 只餘系統資料；sequence 歸位；BE 起服務登入正常；基線 dump 可從此產出 | —（.2 最前置，T-2.1 的輸入）（✅ done 2026-08-17） |
| T-2.1 | 分段 init 腳本 `00-cluster…99-stamp`＋init.sh：DEV pg_dump 整理（去殘留表/歷史分區）、default privileges＋event trigger、RLS 107 條建齊、冪等判斷、收尾 stamp | 空庫跑完＝schema 全建、cm_app 權限掃描零缺口（CM-1207 附錄語句）、baseline marker 落庫 | —（✅ done 2026-08-17） |
| T-2.2 | seed 落地依 D11：字典類 core seed＋root tenant/org id=1＋builtin BPMN；**開工前與決策者核對 D11.2 清單、定 D11.5 labels**；D11.3 現裁留空；CMMC／檢測 Profile 檔案打進 bundle（不 seed 資料列） | seed 冪等重跑不重複；出貨集與裁決一致 | T-2.1（✅ done 2026-08-17） |
| T-2.3 | D11.9 前置 migration：grep 全 codebase 確認 debug view 零引用→drop 兩支 view（DEV 先套，走 sql-migration 流程） | DEV 已 drop、基線 dump 自然不含 | —（✅ done 2026-08-17） |
| T-2.4 | one-shot init 容器（執行載體，與升級 migration 容器同形態）：installer 產參數→起容器跑 init.sh→成功才起服務 | 服務容器全程只持 cm_app 憑證；init 失敗語意＝整庫重建可重來 | T-2.1（✅ done 2026-08-17） |
| T-2.5 | install.sh：環境檢查清單→load＋digest 比對→產雙密碼寫 guidant.env→**openssl 產 per-install 自簽憑證**（CN/SAN 填主機名/IP，落 T-1.2 約定路徑）→DB init→起 stack→尾端印 setup token（不建 admin）；`--config` 非互動 | 空機一鍵裝到「服務全綠、待精靈設定」；digest 不符拒裝 | T-2.2/T-2.4、T-1.*（✅ done 2026-08-17） |
| T-2.6 | bundle 打包（docker save 全 image＋manifest digest＋SQL/seed/手冊骨架）＋Harbor pull 替代路徑文件化＋**PROD 簽章鑰四步**（LC 生鑰對→public_keys.py 新增 kid→重 build→出貨 image 含 PROD 鑰）；D12 進版與推 Harbor 掛此段前後 | bundle 可完整離線裝機；PROD kid 驗章通過；版號已 bump、image 已歸檔 Harbor | T-2.5（✅ done 2026-08-17） |

### FR-065.3 開通精靈與升級 — 依賴：FR-065.2（D5/D6/D8/D10；D13 於本棒前裁決）

| # | 子任務 | 驗收 | 依賴 |
|---|--------|------|------|
| T-3.1 | BE：未初始化態偵測＋setup token 驗證＋建 admin 一次性通道（用後即封）＋精靈用指紋顯示介面 | token 錯誤拒入；admin 建立後通道永久封閉；重放不可 | —（✅ done 2026-08-17） |
| T-3.2 | FE：設定精靈頁（token→建 admin→指紋顯示＋複製→license 匯入引導復用 FR-062 開通頁→進登入頁）＋root tenant 命名段（**依 D13 裁決落法**：命名 root 或改建第一個業務租戶） | 空機裝完→瀏覽器精靈建帳號→匯照開通全鏈通 | T-3.1（✅ done 2026-08-17） |
| T-3.3 | `installer fingerprint` 子命令：演算法與 `machine_fingerprint.py` 一字不差＋兩邊同值驗證步驟 | installer 與 BE 算出同一指紋；服務未起時仍可查 | —（✅ done 2026-08-17） |
| T-3.4 | `install.sh --upgrade` 四步＋align_migrations 演算法移植（連線外注、bundle 帶 SQL/manifest、schema_migrations 判起點）＋相容性檢查 v1＋回滾語意（restore＋回 tag，災難逃生明文） | 舊版→新版升級通；區間不符拒跑；回滾後系統可用 | T-2.6（✅ done 2026-08-17） |
| T-3.5 | Turnstile 停用落地（BE/FE 一對開關，查清現況補齊缺的）＋完整外連盤點收進手冊素材 | air-gap 下登入不失敗；外連清單成文 | —（✅ done 2026-08-17） |

### FR-065.4 文件與真機驗收 — 依賴：FR-065.3

> **.4 前置待收（2026-08-18 排程重整）**：CM-1235（PyMuPDF AGPL 替換，出貨合規硬前置）／CM-1273＋CM-1269（install log 落檔＋系統檔修復，併棒）／CM-1275 驗收／CM-1270 真機複驗。

| # | 子任務 | 驗收 | 依賴 |
|---|--------|------|------|
| T-4.1 | 客戶安裝手冊：安裝／開通／升級／回滾／宿主前提／外連清單與停用法／自備 DB-Redis 指引 | 一般 IT 人員照手冊可自助完成 | T-3.* |
| T-4.2 | 乾淨主機端到端真機驗收：§6 全鏈場景＋負面案 | 全鏈真機走一遍、負面案全數如預期拒絕 | T-4.1 |
| T-4.3 | ⏸ 本次範圍跳過（2026-08-18 裁示，有真實 Windows 需求時再啟動）——Windows 評估文件（D9）：WSL2 machine-id 行為、Docker Desktop licensing、效能界線——不實作不承諾 | 評估文件＋前提清單成文 | — |

### FR-065.5 物件儲存替換——SeaweedFS 出廠即啟用（D14，2026-08-18 追加）— 依賴：FR-065.3；.4 的 T-4.1/T-4.2 依賴本段

子需求卡：**CM-1275**（https://app.notion.com/p/FR-065-5-SeaweedFS-PDF-3c0346da4cd081468eede5e4e735d994）

| # | 卡 | 子任務 | 驗收 | 依賴 |
|---|----|--------|------|------|
| T-5.1 | CM-1276 | jedi-file-upload 加 `SEAWEEDFS` 正式 storage type（adapter 繼承 MinIO 版、factory 註冊、DTO）＋主專案 `_build_adapter` 分支＋順修 `delete_files_by_uids` bug（字串 list→`DeleteObject` list，含突變驗證）；poetry path dependency 開發、發版等 user 明示 | 對 SeaweedFS put/get/單刪/批刪 roundtrip 通過；既有 minio type 不變 | — |
| T-5.2 | CM-1277 | compose 移除 MinIO 選配段、加 `guidant-seaweedfs` 常駐服務（seaweedfs:3.99 單機 server＋S3 gateway、healthcheck、s3.json 掛載、不對外開 port）；install.sh 產 S3 憑證＋bucket 初始化＋storage-seed/ 原 key 灌回（冪等）；基線 SQL 5d 段改 root STORAGE_CONFIG 指向內建 SeaweedFS（key 由 install.sh 裝機寫入） | 乾淨機器裝完五＋一服務全 healthy、上傳下載通、storage-seed 灌回後框架 PDF key 皆存在 | T-5.1 |
| T-5.3 | CM-1278 | build_bundle.sh 加 seaweedfs image（save＋digest）＋storage-seed/ 打包段；基線產製 SOP 補「臨時 SeaweedFS→匯 CMMC→bucket 匯出」流程；手冊「更換儲存後端」警語素材（系統檔切換即斷為重點）交 T-4.1 | bundle 內容物齊；SOP 照走可產出含框架 PDF 的完整 bundle | T-5.1／T-5.2 |

> 派棒註（2026-08-18）：**CM-1280（T-2.5e compose log rotation）併入本棒排最前**（同動 compose 檔避免互踩）——執行序 CM-1280 → T-5.1 → T-5.2 → T-5.3。

follow-up（不擋 .4、出貨後另做）：**CM-1279** 儲存後端遷移工具——掃 `upload_files` 異型列（含 system-scope 最優先）→讀舊寫新→checksum 過才 UPDATE→冪等續跑（同 CM-1267 性質）。

### 追補卡（實測後追加，2026-08-17）

188 真機首測（見 §6）撞出的問題與決策者追加需求，成三張追補卡：

| # | 卡 | 內容 | 狀態 |
|---|----|------|------|
| T-2.5b | CM-1270 | install.sh 修補四件：①開頭即要求 sudo（root 化後目錄/擁有者腳本自設，零手動）②既有安裝偵測（資料目錄含 guidant.env／certs 即停下警告不覆寫）③資料目錄改名 `/srv/guidant-ai`（與家族命名對齊，188 現役 `/srv/guidant` 不動）④追加維運子命令 start/stop/restart/status/logs/uninstall（各自帶 --env-file、uninstall 需輸入固定確認字串且不刪 image） | ✅ done 2026-08-17 |
| T-2.5c | CM-1271 | 憑證管理三件：`credentials` 查看匯出（--export 供登錄密碼保險庫）／`rotate-credentials` 輪換 DB 兩組＋Redis 密碼（JWT 與加密金鑰不納入）／`--config` 選填自帶密碼（留空＝自產，互動模式不變） | 🔶 進行中 |
| T-3.2b | CM-1272 | 開通成功後自動重抓功能選單——授權資訊不在 JWT 內，開通完自動重抓選單即時從 3 項變 28 項、不必重登；重抓失敗退回「重新登入」按鈕引導 | ✅ done 2026-08-17 |

---

## 6. 端到端驗收

乾淨機器全鏈場景：

1. **裝**：全新 Linux 主機解開 bundle 執行 install.sh→環境檢查過→load＋digest 驗證過→DB init 完成→`up -d` 全組 healthy→印出精靈網址＋setup token
2. **精靈**：瀏覽器開站自動進精靈→貼 token→建 admin（密碼自設）→看到機器指紋並複製
3. **開通**：指紋送原廠簽照（模擬離線往返）→精靈匯入 `.license`→進登入頁→登入後全功能可用（license 軸外不再 403）
4. **升級**：舊版裝機後套升級 bundle `--upgrade`→備份→migration→recreate→healthcheck 全綠、資料保留
5. **回滾**：restore 備份＋回舊 tag→系統回到升級前狀態可用

負面案：

- **digest 不符拒裝**：竄改任一 image tar→installer 比對失敗、明確報錯、不進入 load 後續步驟
- **setup token 錯誤**：精靈貼錯 token 拒入；admin 建立後一次性通道重放被拒
- **machine-id 空檔偵測**：`/etc/machine-id` 缺檔或空檔→環境檢查即擋下（不進入安裝，避免指紋漂移事故）
- **升級版號區間不符**：目標主機版本不在 bundle 適用區間→`--upgrade` 拒跑

### 2026-08-17 188 真機首測紀錄

真 bundle（`--skip-prod-key-check` 測試包）於 188 走通全鏈：install.sh 裝機→Web 精靈建租戶與 admin→機器指紋（installer 與 BE 兩邊算出同值）→LC STG 簽照（issue→activate 離線開通）→匯照開通→功能可用。首測撞出的問題已成三張追補卡（T-2.5b/T-2.5c/T-3.2b，見 §5「追補卡」節）。

---

## 7. 出貨前決策清單（帶公司議，T-4.1 前定案）

2026-08-17 首腦登記（CM-1189 母卡），決策者帶公司議，T-4.1 手冊成文前需定案：

1. ✅ **AI 功能：維持開放（2026-08-18 定案）**——底線不變（**原廠 token 絕不進 bundle**，T-2.6 出貨守門），token 於交付/裝機後配置（guidant.env／後台設定），T-4.1 手冊照此寫。後續選項（AI Key 客戶自管維護功能／初期 token 用量限制）**先不做、不入本次範圍**。
2. ✅ **Google Drive OAuth：先沿用 Demo（測試模式）憑證出貨（2026-08-18 定案）**——正式 OAuth App 申請完成後再替換，不擋出貨。air-gap 客戶模組不啟用即不外連（T-3.5 已盤點）。
3. ✅ **物件儲存 MinIO→SeaweedFS：已定案收口（2026-08-18）**——五點拍板見 **D14**，工程落地開卡 **FR-065.5**（CM-1275～1278）＋遷移工具 follow-up CM-1279（研究前置 CM-1268）。
4. **三件出貨動作**（既有）：PROD 簽章鑰四步（D7）／進版（version-bump）／image 推 Harbor（D12）。
5. ✅ **PyMuPDF AGPL 替換（CM-1235）＝出貨合規硬前置，已排棒（2026-08-18）**——非決策題，屬待收工程項，列此供 T-4.1 前追蹤。

以上皆為 .4 前置。**剩餘未決（2026-08-18 盤點）：第 1 題 AI 功能／第 2 題 Drive OAuth／第 4 題三件出貨動作**（第 3 題已收口、第 5 項已排棒）。
