FR-065 · On-premise Installer — 需求討論稿 · 2026-08-16(v1 · D 項大致定案)
FR-063 把 BE 編成了不帶原始碼的 Nuitka image,FR-064 給了它防竄改與授權鎖定。但今天要在一台客戶主機上從零裝出一套可用的系統,仍然只有一份 deployment-env.md 四步手動流程——沒有 DB 初始化、沒有 FE 交付形態、沒有 image 傳遞通路、沒有升級與回滾,每一步都要原廠工程師在場。本案要把「出貨 bundle → 客戶主機 → 可登入的系統 → 後續換版」整條路標準化成客戶可自助執行的 installer。
db-init-inventory.md,其 D1–D10 待裁決清單整合進本稿 §D11)FR-063/064 兩案已把「跑起來之後」的問題解完:image 不帶原始碼、產物唯讀化、防竄改鎖定、license 綁機。還沒解的是「怎麼跑起來」:
docker/production/docker-compose.yml + deployment-env.md §0 的四步手動流程——那是寫給「會 docker、看得懂我們文件的自己人」的,不是給客戶的。GUIDANT_VERSION 重跑 up -d」一句,無 migration 套用、無備份、無回滾語意。目標(從 CM-1189 出發):讓客戶落地安裝標準化——一份出貨 bundle + 一支 installer,客戶自己(或一般 IT 人員照手冊)完成安裝、開通、日後升級,不依賴原廠工程師到場。硬前提是 FR-064 design 明文的部署假設:客戶環境可能完全 air-gapped,一切須離線自洽。
安裝與開通有一個「雞生蛋」結構:license 綁機器指紋(sha256(/etc/machine-id)),但取指紋的唯一現成介面 GET /license/activation/machine-code 需要 JWT 登入——而無照態下系統雖起得來、license 軸以外全 403(/license/ 與 /login 豁免,是設計好的逃生門)。所以旅程必須在裝機當下就把指紋交到客戶手上——D10 精靈定案後主要展示面是精靈頁,installer fingerprint 為保底(D6)。
%%{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: 開通完成,全功能可用
%%{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
以下是撰稿前實際查證的現況(來源:FR-063 deployment-env.md、docker/production/、CM-1207 盤點、FE repo 實檔、FR-064 SUMMARY §9/§10)。
| 元件 | 現況 | 本案動作 |
|---|---|---|
| BE image | guidant-ai-be:1.14.0:ubuntu:22.04 基底(glibc 對齊 build 機)、中文字型/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;檔頭明文警語:獨立 compose project、up -d 必帶 service 名以免 recreate 同 project 的 DB |
擴充為完整 stack(D1/D2),並處理 recreate 風險 |
| 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 是現成同源反代範本(/api/1.0→be:8000、/socket.io→be-socketio:8002 含 WS upgrade),但檔頭明標「限 E2E」;正式部署現況=zip dist scp 手工擺放;build-time 綁死的只剩 VITE_TURNSTILE_SITE_KEY 與 VITE_DEV_MODE(PRD 現值 true,落地要檢討) |
新增 production 容器形態(D2) |
| DB 初始化 | 零。DEV 實況:5 schema/186 表/107 條 RLS policy/156 sequence;cmmgr/cm_app 帳號建立紀錄不在 repo;CM-1207 已產出完整基線盤點+方案 A/B/C 草案(傾向 B 分段腳本+C one-shot init 容器載體) | 本案核心交付(D4) |
| migration/升級 | scripts/sql/manifest.tsv active 138 筆(127 筆 envs=*、11 筆環境限定不屬基線);align_migrations.sh 的 diff+--apply 演算法現成,但 target 寫死內部 IP、密碼讀 repo .env——客戶端要重做連線層並把 SQL+manifest 打進交付物;換版現況只有「改 GUIDANT_VERSION 重跑 up -d」,無備份/回滾/相容性檢查 |
升級流程(D5) |
| license/指紋 | 指紋=sha256(/etc/machine-id)(common/license/machine_fingerprint.py);取指紋唯一介面需 JWT 登入;無照態服務照起但 license 軸外全 403(/license/ 與 /login 豁免);license 檔 PEM armor(.license)貼 FE 頁匯入,BE 不落檔;unlock.token 落 /opt/guidant/pki |
開通旅程(D6);宿主前提 /etc/machine-id 非空(CM-1242 實測) |
| 簽章公鑰 | common/license/public_keys.py 硬編進 image,現有 DEV/STG/POC 三把,無正式 PROD 鑰——FR-064 遺留待辦,出貨前必須新增鑰+重 build image |
D7 |
| 外網依賴 | Turnstile captcha 內網會因連不到 Cloudflare 登入失敗(deployment-env.md §3 標「需評估」未定案);LC 回報是 best-effort 已離線自洽 | D8 |
| image 交付通路 | 完全沒寫。build_image.sh 無 push/save;Harbor 是內部 registry;docs 內 docker save 先例只有 FR-058 agent image | D3 |
| FR-064 出貨遺留 | 未進版(現標 1.14.0);image 未推 Harbor(本機 tag 已打);K8s liveness 下鎖定殼會 CrashLoop(compose 不受影響) | D12 歸屬裁決 |
| 客戶側安裝資產 | 零(唯一素材=compose + deployment-env.md §0 四步手動流程) | 本案主體 |
test repo compliance-manager-test/e2e-env/ 已有一套跑得起來的 8-service 全套 compose(postgres/redis/mailpit/minio/minio-init/be/be-socketio/fe)——它每天在驗證「這組服務能在一個 compose 內良好共處」。user 指示:分析借用、不整抄。逐項拆成可移植與不可抄兩表:
| # | 項目 | 為什麼要借 |
|---|---|---|
| 1 | PG service 形態:postgres:16+顯式 POSTGRES_INITDB_ARGS(--encoding=UTF8 --locale=en_US.utf8)+named volume+pg_isready healthcheck |
不顯式指定 encoding/locale,客戶主機的預設 locale 會滲進 initdb,跨機不可重現 |
| 2 | Redis 必須 ACL user 而非 requirepass | BE config 組的是 redis://<user>:<pass>@host 形式,requirepass 只認 default user 會連不上;--user "$REDIS_USER" on ... "+@all" 段必抄+appendonly no+healthcheck 用 ACL user 實測 ping |
| 3 | 同源 nginx 反代拓撲:/api/1.0→be:8000、/socket.io→be-socketio:8002(必須分開兩 service)、SPA try_files |
與 build:PRD 相對路徑產物正好互補,單一入口不需 per-customer rebuild |
| 4 | nginx resolver 動態解析: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——installer「up 完即可用」的前提 |
| 7 | 一 image 兩服務(RUN_MODE)+YAML anchor |
產品 compose 已有同形態,e2e-env 驗證了與 DB/FE 同 compose 時也成立 |
| 8 | MinIO 選配段形態:minio+mc init job(--ignore-existing 冪等) |
D1 已定 MinIO 選配,bucket 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+憑證掛載(bind ro、私鑰不進 image)是必要設計 |
不是資安加分項——不上 HTTPS(或 localhost 之外裸 HTTP)產品直接壞 |
| # | 項目 | 為什麼不抄 |
|---|---|---|
| 1 | golden dump→TEMPLATE clone 整套 | 產品是空庫 init(CM-1207),不是 restore 測試基準 |
| 2 | pg_dumpall --roles-only 搬 role(連 SCRAM 雜湊) |
產品 init 新建 role+安裝時生成密碼,不搬既有雜湊 |
| 3 | sanitize-golden 全套 | 空庫沒有「洗掉開發資料」的問題 |
| 4 | mailpit 假 SMTP | 產品接客戶真 SMTP |
| 5 | BE E2E image(帶原始碼跑 root dev server) | 產品用 docker/production/Dockerfile Nuitka 產物(不帶原始碼、降權) |
| 6 | ENV=DEVELOP_PREMISE |
產品走正式 config |
| 7 | fe-image 兩層疊 image | 產品單層乾淨 build |
| 8 | 測試帳號/auth-state/排他鎖/污染偵測/奇數 port/e2e-postgres 寫死 service 名 |
全是測試編排產物;產品 service 名用中性名(如 guidant-db) |
結論:e2e-env 驗證了「這組服務能在一個 compose 內良好共處」——借拓撲與機制三條線(服務組成/互連/啟動順序),不借資料來源、image 血統、測試編排三條線。
2026-08-16 批次拍板:D1–D10/D12 全數定案,D11 十項落裁決(8 定案、2 保留——D11.3 與 PM 討論、D11.5 .2 棒前定)。
✅ D1 — 依賴服務形狀(已定案 2026-08-16):PG/Redis compose 全包為預設
現行 compose 定式只有 BE 兩服務,DB/Redis/MinIO 都假設外部已存在。落地客戶(尤其 air-gap 單機)多半沒有現成 PG 16/Redis,要求自備等於把最難的一段推回給客戶——故拍板全包為預設:
DB_HOST/REDIS_HOST 環境變數、移除對應 service 段。installer 不做 --external-db 類參數;參數化外接模式列為未來可選加分項。up -d 不帶 service 名會 recreate 所有容器(含 DB,CLAUDE.md 有案)——傾向 DB/Redis 獨立成另一個 compose project(或至少檔內大字警語+installer 包裝所有 up 指令、永遠帶 service 名),不讓客戶裸打 up -d。✅ D2 — FE production image 與反向代理(已定案 2026-08-16):nginx.onprem.conf+FE production image+單一入口 port
定案:照原建議採納——新增 nginx.onprem.conf+FE production image,進 build 管線、tag 與 BE 同版號;單一入口 port(80/443)由 FE nginx 統一對外。VITE_DEV_MODE 與 VITE_TURNSTILE_SITE_KEY 在實作棒查清語意後定值。
FE 已具備容器化的全部零件:Dockerfile 支援 ARG NGINX_CONF 換 conf、build:PRD 產物走相對路徑(同源即可用,不需 per-customer rebuild)、nginx.e2e.conf 是現成的同源反代範本(含 /socket.io WS upgrade 與長工時 timeout)。缺的只是「產品版」的定案:一支正式 nginx conf(以 e2e 版為底、upstream 指向 compose service 名)+ FE image 進 build 管線與 BE 版號對齊。單一入口 port(80/443)由 FE nginx 統一對外,BE 8000/8002 收進內網。
連帶要檢討兩個 build-time 值:VITE_DEV_MODE(PRD 現值 true——語意與影響面待實作棒查清,落地版直覺上不該開)與 VITE_TURNSTILE_SITE_KEY(現值是 Cloudflare 測試 key,與 D8 連動)。
✅ D3 — image 交付通路與完整性驗證(已定案 2026-08-16):docker save tar bundle 為主+manifest digest 比對必做
定案:照原建議——tar bundle 為主(air-gap 必然);bundle manifest digest 比對進 installer 必做;Harbor pull 文件化為有網客戶的替代路徑;cosign 評估後另議。
air-gap 客戶拉不到 Harbor,通路只剩實體傳遞。選項:(a) docker save tar bundle——BE/FE/PG/Redis 全部 image 打進一份出貨包;(b) Harbor pull(僅限有網路且信任外連的客戶)。
FR-064 D7 已把「image 層驗證」劃入本案:bundle 內附 manifest 列各 tar 的 sha256 digest,installer docker load 後比對,確保客戶手上的 image 與原廠出貨一致(傳遞途中未被替換)。cosign 之類的簽章方案列為評估項,不在 v1 硬性範圍。
✅ D4 — DB init 載體(已定案 2026-08-16):分段 SQL 腳本(B)+one-shot init 容器(C 載體),照 CM-1207 §5.4
定案:照原建議——B+C 載體。DB init=分段 SQL 腳本(基線真相取 DEV pg_dump 整理,參數化注入密碼/命名)+one-shot init 容器為執行載體(跑完即棄,最高權限憑證只存在建庫當下)。init 容器與升級(D5)的 migration 容器共用同一形態。
CM-1207 §5 已完整比較三案:A(單一 3 萬行 init.sql,不建議)、B(分段腳本組 scripts/init/00-cluster … 99-stamp+init.sh)、C(entrypoint 自動 init,服務容器得持有 cmmgr 憑證、違反最小權限)。§5.4 取捨結論:B 為主、C 的 one-shot init-job 容器作為執行載體——installer 產參數(兩組 DB 密碼、admin 初始密碼)→ 起 init 容器跑 init.sh → 成功後才起服務容器(服務只拿 cm_app 憑證)。基線真相取 DEV pg_dump(非 224 支 migration 重放);權限不逐表 GRANT 而是複製 default privileges+event trigger 機制;收尾 stamp schema_migrations baseline marker 讓後續增量 migration 無縫接軌。
✅ D5 — 升級流程與回滾語意(已定案 2026-08-16):四步包成 install.sh --upgrade+回滾=災難逃生
定案:照原建議——升級定式化為四步、包成 install.sh --upgrade;回滾=restore 備份+回舊 tag(明文:災難逃生非常規操作);相容性檢查 v1 最簡版(bundle manifest 標適用起始版號區間,不符即拒跑)。
四步:① pg_dump 全量備份 → ② load 新 image(含 digest 驗證)→ ③ init 容器套增量 migration(align_migrations.sh 的 diff+apply 演算法移植到客戶端形態:連線參數外部注入、SQL+manifest.tsv 隨 bundle 出貨、依 schema_migrations 判斷起點)→ ④ up -d recreate。回滾語意=restore 備份+GUIDANT_VERSION 回舊 tag(明文告知:回滾會丟升級後產生的資料,屬災難逃生不是常規操作)。
✅ D6 — 首次開通的「雞生蛋」(已定案 2026-08-16):指紋主展示面=Web 設定精靈頁+installer fingerprint 保底
定案:指紋主要展示面=Web 設定精靈頁(顯示+複製鈕);installer fingerprint 子命令為保底查詢工具(裝機尾端自動印+可隨時重跑,服務起不來時可用),演算法與 machine_fingerprint.py 一字不差;實作棒需加「兩邊算出同值」的驗證步驟。
取機器指紋的唯一現成介面需 JWT 登入,但簽照又需要指紋——雖然無照態 /login 有豁免(登入後可打 machine-code API),流程仍繞。installer 在裝機尾端直接算指紋(與 BE 同演算法:sha256(/etc/machine-id),宿主上一行 shell 即可),客戶抄指紋走離線通路給原廠簽照,拿到 .license 後進系統貼照開通。
✅ D7 — 正式簽章鑰(PROD kid)(已定案 2026-08-16):劃入本案 .2 棒
定案:PROD 簽章鑰劃入本案 scope(.2 棒,隨 bundle 打包段)——LC 端生成 PROD 鑰對 → public_keys.py 新增 PROD kid → 重 build image;出貨 bundle 一律以含 PROD 鑰的 image 產出。
public_keys.py 現有 DEV/STG/POC 三把,無 PROD 鑰。這是出貨的硬前置:客戶拿到的 image 必須能驗「正式簽發鑰」簽出的 license/unlock token/integrity manifest,否則出貨即「未知的 kid」([[feedback_multi_env_signing_key_pairs_with_public_key_commit]] 四步一組的教訓適用)。
✅ D8 — 外網依賴停用(已定案 2026-08-16):落地版預設停用 Turnstile
定案:落地版預設停用 Turnstile(BE/FE 一對開關,實作棒查清現況後補齊缺的開關);bundle 手冊附「本產品可能外連清單與停用法」。
Turnstile captcha 在內網連不到 Cloudflare 會直接讓登入失敗,deployment-env.md 標「需評估」至今未定案。BE 端 TURNSTILE_SECRET_KEY、FE 端 VITE_TURNSTILE_SITE_KEY 各自的「停用開關」現況(不填是否即停用?有無顯式 flag?)待實作棒查清。其他外網呼叫盤點:LC 在線回報已是 best-effort 離線自洽;AI API/Google Drive 屬功能模組、客戶不啟用即不外連——仍應在實作棒做一次完整外連盤點收進手冊。
✅ D9 — Windows 支援(已定案 2026-08-16):僅出評估文件,不實作不承諾
定案:本案僅出評估文件+前提清單(WSL2 machine-id 行為、Docker Desktop licensing、效能界線),不實作不承諾支援;有真實客戶需求時另開案。
母案卡內文傾向:不做原生 Windows 版,引導客戶裝 Docker Desktop/WSL2 跑同一套 Linux image。技術上可行但有未驗證變數:WSL2 的 /etc/machine-id 穩定性(指紋錨點)、volume 效能、Docker Desktop 授權條款(企業要付費)。
✅ D10 — installer 形式(已定案 2026-08-16,兩次拍板):純 bash(不做 TUI)+首次啟動 Web 設定精靈
user 兩次拍板:第一次定「純 bash install.sh,不做 TUI」;同日續拍採混合式——bash 管基礎設施段+「首次啟動 Web 設定精靈」管應用設定段(業界主流模式:Portainer/GitLab/Nextcloud/Jenkins 式)。
分工定式:
install.sh(bash 純文字):環境檢查 → load image+digest 比對 → DB init(one-shot init 容器:schema+字典 seed,不建 admin)→ 起服務 → 尾端印「請開 http://<主機>/ 完成設定」+一次性 setup token(防搶注,Jenkins 式——避免精靈開放期間被非安裝者搶先建帳號)代價明文:精靈是新的 FE+BE 開發(未初始化態偵測、setup token 驗證、建 admin 的特殊通道——繞過既有權限體系的一次性 API,用後即封),約多一棒工作量;user 拍板值得(體驗優先)。TUI 維持不做;systemd 維持不另立 unit(installer 確保 systemctl enable docker 即可)。
被排除方案:純 shell 印生成密碼+首登強制改密(機制現成但體驗差一階,列 fallback)。
CM-1207 盤點把「拿不準的 seed/schema 項目」列成十項待裁決,原編號 D1–D10 為避免與本稿撞號改編為 D11.1–D11.10。字典類必備 seed(operations/system_menus/ui_routes/capabilities/root tenant+org/Administrator 角色/RUNTIME_CONFIG 等,CM-1207 §3.1)已明確屬出貨集,不在此重列;十項已於 2026-08-16 逐項落 user 裁決——8 項定案、2 項保留待議(D11.3 與 PM 討論/D11.5 .2 棒開工前定):
✅ D11.1 — CMMC 框架 catalog 整族(已定案 2026-08-16):出貨框架只有 CMMC 2.0,走應用層匯入
定案:出貨框架清單只有 CMMC 2.0;初始化資料由 user(決策者)準備。出貨形式採「應用層匯入」——出廠帶 OSCAL JSON 檔、服務起來後走既有匯入流程灌入(與 D11.4 同一模式),DB init 不 seed catalog 資料列。
背景:DEV 的 79 個 catalog 混雜「內建 CMMC」與「開發測試匯入的」,schema 上分不出來——正因如此不走 seed dump 直灌。
✅ D11.2 — 內建 workflow_templates(已定案 2026-08-16,範圍待核對):只帶「預設內建」最小集
定案:只帶「預設內建」的最小集——DEV 的 191 筆(provider=Billows-Official)屬混雜舊資料,非全部出貨;snapshot 類專案運行產物與 tenant 1 的 59 筆不帶。實作棒需與決策者核對確切出貨清單後落地。
⏸ D11.3 — 樣板 SSP/MF defaults 家族(保留——與 PM 討論後另定)
裁示(2026-08-16):先留空——出貨基線不帶樣板 SSP/MF defaults,與 PM 討論後另定範圍。
背景:新客戶初始該有哪些「內建模組框架+樣板 SSP」?DEV 現況分不出產品內建 vs 開發試作。
✅ D11.4 — 檢測 Profile 庫(已定案 2026-08-16):隨 bundle 帶檔+既有匯入機制灌入
定案:採「隨 bundle 帶檔案+服務起來後走既有匯入機制灌入」——DB init 不 seed 資料列,與 D11.1 CMMC 初始化同一模式(單一維護面;避免 seed 列與 storage 檔案脫鉤的壞連結)。
背景:資料列是產品資產(dev-sec 基準+TWGCB 8 支),但 profile 版本的檔案實體在 storage(MinIO/local)——DB seed 灌了列、檔案不在就是壞連結,正是選匯入路線的原因。
⏸ D11.5 — public.labels 35 筆(保留——.2 棒開工前定)
裁示(2026-08-16):先不裁,.2 棒開工前定。傾向:預設只帶前段系統字典(系統回饋/系統操作/選單分類),後段 textbook-/resource- 不帶。
✅ D11.6 — ISSUE_INTEGRATE_CONFIG(已定案 2026-08-16):出廠帶 disabled 空殼
定案:功能對落地客戶開放——出廠帶 disabled 空殼,客戶後台自行啟用接自己的 GitHub/GitLab。
✅ D11.7 — public.alembic_version(已定案 2026-08-16):暫留
定案:此表係舊版 flask-migrate 遺留(user 證實,來源已明)——暫留,基線帶此表、不清除。
✅ D11.8 — root tenant/org/admin 出廠命名(已定案 2026-08-16):由客戶命名
定案:root tenant 顯示名由客戶命名——在 Web 設定精靈輸入(D10 流程內);root id=1 是硬約定不可變,這裡只裁顯示名。DEV 值 Guidant.AI/Guidant.AI Manage/admin 不沿用為出廠固定值。
連動註:若 D13 採納系統殼方案,客戶命名對象改為第一個業務租戶、root 顯示名固定(如 System)。
✅ D11.9 — debug view 與 cm_app_group(已定案 2026-08-16,同日追加裁示):debug view 確認零引用後連 DEV 一併刪除、空群組保留
定案(追加裁示後):debug view 不只「不出貨」——debug_org_units/v_tenant_parent_debug 兩支 確認沒程式在用後,連 DEV 也一併刪掉。實作棒動作:grep 全 codebase 確認零引用 → 開 migration drop 兩支 view(DEV 先套,走既有 sql-migration 流程與 DROP 前安全查核鐵則)→ 出貨基線自然不含。cm_app_group 空群組保留(利未來多 app 帳號,照 CM-1207 傾向)。
✅ D11.10 — 落地版 DB 名(已定案 2026-08-16):統一 guidant_ai
定案:落地版 DB 名統一 guidant_ai——採 2026-08-08 DB 命名新制(<系統>_{dev|stg}、prod 不帶後綴,落地新客戶屬新建置適用新制);installer 的 INIT_DB_NAME 預設值定此。
✅ D12 — FR-064 出貨遺留歸屬(已定案 2026-08-16):①②劃入本案,③不實作但設計預留
FR-064 收口時明文留下三件:① 進版(現標 1.14.0,未走 version-bump);② image 推 Harbor(本機 tag 已打);③ K8s liveness 下鎖定殼會 CrashLoop(compose 落地不受影響,上 K8s 才需把鎖定態排除在 liveness 外)。
定案:①進版(version-bump)與 ②image 推 Harbor 劃入本案出貨動作(出貨 bundle 必然要定版,推 Harbor 是原廠內部歸檔)。③ 不在本案實作,但設計預留——user 明示「要預留 K8s」:design.md 需把「鎖定態排除在 liveness 之外的設計要點」與 cluster 化前提備忘一起記為正式預留節(作為未來 K8s 案的起點),compose 服務契約保持可翻譯性(服務名/port/env 介面清楚)。見文末〈cluster 化前提備忘〉同步標註。
🏢 D13 — root tenant 定位:系統管理殼 vs 可做業務(待公司討論)
議題:落地版 root tenant(id=1)要不要給客戶做業務,還是只當 super admin 系統層?
市面對照(皆為「系統層=管理殼、業務在下層」同構):
我方架構現況:B-2 模型下 root tenant 本來就是系統資源掛載點(平台 capability/系統設定/Administrator 角色),DEV 大家拿 root admin 直接做業務是開發便利非設計意圖。
建議方案:root tenant=super admin 的家,只做系統管理(license 匯入/SMTP/storage/建租戶/系統日誌);業務一律走第一層租戶。精靈流程配合改為:建 super admin(root 層)→ 引導建第一個業務租戶(D11.8 客戶命名的就是它,root 顯示名可固定如 System)→ 建租戶管理員 → 日常用租戶管理員登入。
強制程度兩檔(待公司裁):
首腦建議:v1 軟邊界+精靈強制引導;硬邊界視盤點與 PM 意見排 v2。
連動註記:此裁決影響 .3 棒精靈流程與 D11.8 的落法(客戶命名對象從 root tenant 改為第一個業務租戶)。
粗略切棒,轉 design.md 時細拆。階段重切(user 2026-08-16 指示):先切一個「docker compose 起整個服務」可獨立驗收的地基棒當 .1;原 .1 的 bundle 打包與 PROD 簽章鑰移到 .2。
| 階段 | 內容 | 驗收 |
|---|---|---|
| **FR-065.1 full-stack compose(地基棒) | 產品 compose 全 stack:PG+Redis+BE api/socketio+FE production image(nginx 同源反代單一入口)+MinIO 選配段(D1/D2);FE image build 定案(nginx.onprem.conf+build:PRD);.env 範本;自備 DB/Redis 的註解引導。DB 先用暫行手段(如手動 restore)驗通拓撲,正式空庫 init 屬 .2** |
乾淨機器上 docker compose up -d 全組 healthy、FE 反代登入通 |
| FR-065.2 DB init+installer 殼 | 分段 init 腳本+one-shot init 容器(CM-1207 方案,D4;schema+字典 seed,不建 admin);install.sh(環境檢查/load image/產基礎設施密碼/起 stack/尾端印 setup token 而非 admin 密碼,D10);bundle 打包(docker save+digest manifest,D3);PROD 簽章鑰(D7 定案:LC 生 PROD 鑰對+public_keys.py 新增 kid+重 build image);seed 依 D11 裁決落地(D11.2 清單開工前與決策者核對、D11.5 labels 開工前定;CMMC/檢測 Profile 走應用層匯入不 seed) | 空機一鍵裝到「服務全綠、待精靈設定」 |
| FR-065.3 開通精靈與升級 | Web 設定精靈(FE 精靈頁+BE setup token/建 admin 通道,D10);installer fingerprint 子命令+開通流程整合(D6);--upgrade 四步+回滾(D5);Turnstile 停用落地(D8) |
空機裝完 → 瀏覽器精靈建帳號 → 匯照開通 → 升級 → 回滾端到端 |
| FR-065.4 文件與驗收 | 客戶安裝手冊;乾淨主機端到端真機驗收;Windows 評估文件(D9) | 全鏈真機走一遍 |
依賴關係相應調整:.1 依 D1/D2(均已定案)即可開工;.2 依 D3/D4/D7/D10/D11(均已定案或裁定,餘 D11.2 清單核對與 D11.5 於開工前補定)+.1 完成;.3(含 Web 設定精靈,依 D10 定案)依 .2;.4 收全鏈。D12 的進版與推 Harbor 屬出貨動作,掛在 .2 bundle 打包段前後執行。
installer 環境檢查段的初稿,門檻數值待實作棒定案:
docker compose plugin;systemctl enable docker(開機自起前提)/etc/machine-id 非空:test -s /etc/machine-id 必過(CM-1242 實測:缺檔時 docker 會建成目錄、指紋退回 MAC 且每次重啟漂移;systemd 系開機安裝即有,極精簡系統用 systemd-machine-id-setup 補)。⚠️ machine-id 一旦重生成,既有綁機憑證全部失效——只在「本來就沒有」時補user 問過未來 cluster(K8s/多節點)支援。compose→K8s 的翻譯本身是機械工,真門檻在應用層假設,先記錄不動:
sha256(/etc/machine-id))——多節點下每台指紋不同,license 模型要改(改綁 cluster identity 或浮動授權)。/opt/guidant/pki、unlock.token)——多副本共享或各自持有需重新設計。D12 裁示同步(2026-08-16):user 明示「要預留 K8s」——本案不做 cluster 實作,但 design.md 需把本清單+「鎖定態排除在 liveness 之外的設計要點」(上列第 2 項)記為正式預留節,作為未來 K8s 案的起點;compose 寫成乾淨的服務契約(服務名/port/env 介面清楚)保持可翻譯性。未來有真實需求另開 FR。