FR-173 · 設計文件 · 2026-11-03 修訂(出包時蓋章、測試包模式)
BE、Agent、FE 各一支活的 pipeline;發版起點是 GitLab 的 Run pipeline;出包時向自架 Vault 要簽名,試完在 GitLab 按「發布」;另有不掃描、不上站的測試包模式
🔴 這份是設計決策,不是現況。 1.29 起走這條路出版,1.28.x 不出正式版。推翻 FR-167 兩條裁示:①「印章私鑰只在決策者 Mac」改為「Vault 簽、Mac 章備用」;②「先給內部機器裝草稿、再蓋章發布」改為「出包時就蓋章、發布只是公開」——客戶端一律拒收沒蓋章的清單,舊順序在內部機器那一步就走不通。update-center 與 License Center 不進管線。
📑 本頁是 v1.0(定稿)。 實作中有設計異動,見 設計 v2.0(定稿)(正式發版、直接部署、測試版發布三條路;正式發版自動發布;內部三台皆可部署與測試升級)。本頁內容保留定稿原樣,不回頭改。
整案一句話:每個會出貨的 repo 一支自己的 CI 對自己負責;發版的人在 GitLab 按「Run pipeline」,CI 跑完測試、編譯、排練、打包,出包時就向自架 Vault 要簽名,把蓋好章的版本登記成只有 190 看得到的草稿;190 試過沒問題,回 GitLab 按「發布」,客戶才看得到。全程不用 Mac。
兩種跑法,同一顆按鈕,差在勾不勾 internal_test:
| 正式包 | 測試包 | |
|---|---|---|
| 給誰 | 客戶 | 只給 190 自己測 |
| 從哪跑 | 受保護的 tag(例 v1.29.0) |
任何 branch |
| 跑什麼 | 測試、編譯、190 升級排練、SBOM+弱掃、推 Harbor、上 R2、登記更新站 | 測試、編譯、打包;跳過排練、SBOM+弱掃、Harbor、R2、更新站 |
| 章 | Vault 正式章 guidant-release |
Vault 測試章 guidant-test(客戶機不認得) |
| 怎麼到 190 | 190 更新中心「立即檢查」→ 更新 | CI 直接 scp 到 190 升級 |
| 主系統約多久(188 實測推估) | 約 50 分 | 約 30 分;配 skip_be(不重編)約 10 分 |
| 階段 | 做什麼 | 卡 | 完成怎麼判定(決策者檢查法) |
|---|---|---|---|
| 決策者功課 | 分三段,各在對應批次前做,見 §5 | — | 各段的檢查指令見 §5 |
| V 簽章 | 蓋章工具接 Vault;更新站收「蓋好章的草稿」、發布端點認 GitLab 身分 | V-1 | 172 簽一份清單,190 驗章過;Mac 私鑰仍能簽(備用) |
| C 三條 CI | FE 自己的 CI;Agent 新 CI;BE 改觸發 FE、出包時蓋章、手動「發布」、測試包模式;jedi 套件 CI | C-3 C-2 C-1 C-4 | 從 tag 跑一次:190 看得到新版、升級成功 → 按「發布」→ 更新站 published、kid 是 Vault 那把;勾 internal_test 跑一次:190 被直接升上去 |
| D 文件 | FINAL-SPEC、三 repo 分工表、仍要人按的步驟、鑰匙輪換 SOP | D-1 | — |
依賴一句:批 1(V-1、C-3)→ 批 2(C-2、C-1)→ 批 3(C-4、D-1),每批前決策者做完那一段功課。
| # | 題目 | 定案 | 被排除的 | 理由 |
|---|---|---|---|---|
| D1 | CI 自動簽章 | 同意;私鑰進金庫、CI 用 OIDC 短效身分要簽名 | 維持 Mac 手簽 | 出差就出不了版;與無人值守方向相反 |
| D2 | 金庫 | 自架 HashiCorp Vault Transit(Community 版),專用 VM 192.168.50.172、apt 裝成 systemd 服務、單節點 Raft;架法見 vault-setup-runbook.md |
Azure Key Vault/AWS KMS(要換 P-256、改三處驗章);update-center Worker Secret(鑰匙程式讀得到,不算不可匯出);GitLab CI 變數(反模式);Cloudflare 無 KMS;GitLab Secrets Manager(要 Premium、只存祕密不開放簽章);OpenBao(功能同、文件少,日後授權有變再換,指令與 API 相同) | Ed25519 不改、鑰匙不可匯出、建置期祕密順便集中;業界共識是 KMS/Vault+OIDC,私鑰不放 CI 變數 |
| D3 | 三 repo CI 分不分 | BE、Agent、FE 各一支;BE 發版 pipeline trigger: FE 下游(strategy: mirror,官方已不建議 depend);Agent 是自己的按鈕,不被 BE 觸發 |
全塞 BE 一支 | 各 repo 對自己負責、FE push 就掃不用等出包 |
| D4 | runner | BE、Agent、FE、jedi 都用 188 shell runner(24 核、concurrent=3);只有 Nuitka 那段用 188 主機鎖串行(resource_group 不跨專案);188 沒裝 node,FE 的 npm 在 node 容器裡跑;jedi 套件另議 |
FE 用 171 上 2024 年留下的 docker runner(#37407054) | 兩條 Nuitka 同跑 linker crash;171 只有 2 核且跑 Nexus、Harbor,FE build 會拖慢它們;188 上 BE CI 本來就在打包 FE,環境現成 |
| D5 | FE 三支舊 CI | 刪(ECR/ECS/ansible 是 SaaS 時代);換 verify(lint、vitest、npm 弱掃)+tag build 推 Harbor | 留著 | 沒人維護、推錯地方 |
| D6 | release note 與最低相容版 | 從 repo 檔讀:BE docs/release_notes/v<版>.md 檔尾、Agent RELEASE_NOTES.md 檔尾、min_agent_version/min_host_version 一行檔 |
GitLab 表單欄位 | 進版控、有 review |
| D7 | update-center、LC | 不進管線,各自部署 | 納入 | 獨立系統,範圍太大 |
| D8 | 客戶主機預設密碼(DB、Redis、Bugsink 每台同組) | 另案:Vault 解不了(客戶內網連不到);正解是裝機時每台隨機、compose secrets: 掛檔、首次登入強制改 |
塞進本案 | 與 CI 無關 |
| D9 | 仍要人按的 | ①打受保護 tag 並在 GitLab 按 Run pipeline;②190 試過後按 pipeline 最後一個手動 job「發布」 | 全自動;推 tag 自動跑 | 「這個 commit 要出貨」是決策;BE CI 一直是人按才跑,維持 |
| D10 | 什麼時候蓋章 | 出包時就蓋:CI 打包後向 Vault 要簽名,登記「蓋好章的草稿」,只有內部先裝機器看得到;發布只把狀態改成公開,不再蓋章 | 先給內部機器裝草稿、試完才蓋章 | 客戶端一律拒收沒蓋章的清單(package_verifier.py),舊順序內部機器裝不上,發布前的「內部先裝成功」檢查永遠過不了 |
| D11 | 誰能按發布 | 更新站多認一種身分:GitLab 每個 job 的 ID token,只收 BE、Agent 兩專案、受保護 ref;決策者本人的 Cloudflare 登入照舊可用(備用) | 發一把長效 CI 通行碼加「發布」權限 | 不留長效祕密;與 Vault 用同一張身分證、同一套信任條件 |
| D12 | 測試包 | 擴充 CI 既有 internal_test:跳排練、SBOM+弱掃、Harbor、R2、更新站;用 Vault 第二把測試章 guidant-test 簽;scp 直升 190 |
另開第三種模式;測試包也上更新站 | 測試不需要的步驟約 20 分;測試章公鑰只裝在 190,包流出去別台(含 STG、POC、客戶機)都裝不了;要測「更新中心看到新版 → 按更新」那段就跑正式模式(草稿只有 190 看得到,本來就安全) |
| D13 | GitLab 免費版 | 拿 Vault 祕密用 id_tokens+script 內登入(secrets: 是付費版) |
升 Premium | 官方教學的手動登入寫法免費版可用,效果相同 |
| D14 | Agent 主控台前端怎麼來 | 比照主系統前端:Agent CI 在 188 把 /opt/guidant-ai-fe 切到同名 branch,先跑 FE sbom_fe.sh 掃該 commit 的 lockfile,再用 FE Dockerfile 的 console-dist 目標 docker build 輸出 dist-console/ |
從 FE pipeline 下載 artifact(免費版 CI_JOB_TOKEN 跨專案下載回 404、needs:project 付費);FE 傳套件庫;長效唯讀通行碼;188 共用資料夾 |
跟主系統前端同一份 Dockerfile、同一個 node 版本、同一份 lockfile;掃描與打包同一個 commit;不多一把祕密 |
%%{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','fontSize':'20px'},'sequence':{'actorFontSize':20,'messageFontSize':19,'noteFontSize':18,'actorMargin':60,'messageMargin':42,'boxMargin':12,'useMaxWidth':false}}}%%
sequenceDiagram
autonumber
actor P as 發版的人
participant GL as GitLab BE pipeline(188 runner)
participant FE as FE pipeline(188 runner)
participant V as Vault(172)
participant H as Harbor/R2
participant US as 更新站
participant T as 190 測試機
P->>GL: 打受保護 tag v1.29.0,Run pipeline
GL->>GL: verify:守衛、pytest、弱掃 lockfile
GL->>FE: trigger(mirror),等 FE 綠
FE-->>GL: FE 映像 tag
GL->>GL: build:Nuitka、BE/init 映像
GL->>T: rehearse:190 清掉重灌舊版、灌 STG dump、用新包升級
GL->>GL: package:SBOM+弱掃放行門、打包
GL->>V: 用 GitLab job 身分登入,讀 R2/Harbor/更新站祕密
GL->>H: 推映像、上傳安裝包
GL->>V: transit/sign guidant-release(canonical 清單)
V-->>GL: 簽名(鑰匙不出 Vault)
GL->>US: register-draft,附蓋好章的清單;草稿只給 190 看
P->>T: 更新中心「立即檢查」→ 更新到 1.29.0
T->>US: 回報升級成功
P->>GL: 按手動 job「發布」
GL->>US: 帶 GitLab job 身分呼叫 publish
US->>US: 核:BE 專案、受保護 tag、190 已回報成功
US-->>GL: published
Note over US: 客戶的「立即檢查」從這一刻看得到 1.29.0
為什麼章在第 10~11 步就蓋:客戶端看到沒蓋章的清單一律拒收,190 要能在第 13 步裝上去,拿到的就必須是蓋好章的版本。發布(第 15~18 步)只是把草稿改成公開。
從 branch 跑會怎樣:Vault 不給正式章(角色只認受保護 ref),pipeline 在第 10 步紅;「發布」那顆 job 也不會出現。故意的:避免從開發分支誤發。
%%{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','fontSize':'20px'},'sequence':{'actorFontSize':20,'messageFontSize':19,'noteFontSize':18,'actorMargin':60,'messageMargin':42,'boxMargin':12,'useMaxWidth':false}}}%%
sequenceDiagram
autonumber
actor P as 開發者
participant GL as GitLab BE pipeline(188 runner)
participant V as Vault(172)
participant T as 190 測試機
P->>GL: 任一 branch,勾 internal_test(可加 skip_be),Run pipeline
GL->>GL: verify:守衛、pytest
GL->>GL: build(skip_be 時沿用上一顆 BE 映像)
GL->>GL: 打包(不跑 SBOM/弱掃)
GL->>V: transit/sign guidant-test
V-->>GL: 測試章簽名
GL->>T: scp 安裝包+清單,install.sh --upgrade --force
T-->>GL: 升級結果
Note over T: 測試章只有 190 認得;包流出去別台都裝不了
%%{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'}}}%%
flowchart LR
subgraph trig["人按"]
T1["BE:Run pipeline(tag 或 branch)"]
T2["Agent:Run pipeline(tag 或 branch)"]
T3["FE/jedi:任意 push"]
end
subgraph be["BE pipeline(188 shell runner)"]
B1["verify"]
B2["trigger FE(mirror)"]
B4["build:Nuitka、BE/init 映像"]
B5["rehearse:190 舊庫升級排練(測試包跳過)"]
B6["package:SBOM 放行門 → 打包 → Harbor → R2(測試包只打包)"]
B6s["sign:Vault 正式章或測試章"]
B6d["正式:register-draft 帶章/測試:scp 升 190"]
B7["publish(手動、只在受保護 tag)"]
B1 --> B2 --> B4 --> B5 --> B6 --> B6s --> B6d --> B7
end
subgraph fe["FE pipeline(188 shell runner)"]
F1["verify:lint、vitest、npm 弱掃"]
F2["build:FE 映像 → Harbor;dist-console artifact"]
end
subgraph ag["Agent pipeline(188 shell runner,與 BE 串行)"]
G1["verify"] --> G2["build:Nuitka、拉 FE dist-console、Agent 映像"] --> G3["rehearse"] --> G4["package+sign+register-draft product=agent"] --> G5["publish(手動)"]
end
T1 --> B1
T2 --> G1
T3 --> F1
B2 -.-> F1
| 項 | 主系統 CI | Agent CI | FE CI | jedi CI |
|---|---|---|---|---|
| runner | 188 shell(id 56998011);Nuitka 段用 188 上的 flock /home/jedi/.guidant-build.lock 排隊 |
188 shell,同一把鎖 | 188 shell(同一台;不用 Nuitka,不進 resource_group,188 一次可同跑 3 個 job) | 待決策者開給套件群組 |
| 觸發 | 人按 Run pipeline | 人按 Run pipeline | push(verify)、BE 觸發(build) | push(test)、BE 觸發(分類器映像) |
| 弱掃 | pip-audit、syft+grype lockfile、trivy+grype 映像、vuln_gate.py;測試包跳映像掃描 |
同左 | syft+grype package-lock |
pip-audit |
| 祕密 | id_tokens → Vault JWT 登入 → KV |
同左 | 同左(Nexus npm 認證) | 同左 |
| 簽章 | 正式:guidant-release;測試:guidant-test |
同左 --product agent |
無 | 無 |
| 人按的 | Run pipeline、發布 | 同左 | 無 | 無 |
Agent 是另一個專案(auditmanager/evidence-agent)、另一顆按鈕,流程與圖 1 相同,蓋同一把正式章(客戶機只認一批公鑰;190 的 Agent 已認得)。和主系統不同的三處:
| 主系統 | Agent | |
|---|---|---|
| 190 怎麼試 | 更新中心升主系統 | 更新中心 Agent 區「推送 Agent」 |
| 離線檔(不出網的客戶從 Agent 主控台上傳) | 無 | 蓋章時一起產 guidant-agent-<版>.offline.tar.gz(包+簽好的清單+.sha256),留在 188 .build/compose/,與主系統安裝包同樣不上 artifacts;不出網的客戶從 188 拿 |
| 打包時另一個簽章 | 無 | Agent 映像內檔案清單送 License Center 簽(sign_agent_manifest.sh,與出版章無關),要 LICENSE_CENTER_API_TOKEN;改從 Vault kv/ci/license-center 拿,188 /opt/guidant-ai-agent/.env 不再放 |
兩條同時按時,後到的在 188 主機鎖(/home/jedi/.guidant-build.lock)排隊,不會兩個 Nuitka 同跑;GitLab 的 resource_group 只在同一專案內有效,擋不了跨專案。主系統這一版要求新 Agent 時,先發 Agent、再發主系統(主系統清單的 min_agent_version 會指到它)。
| 項 | 定案 |
|---|---|
| 鑰匙 | 正式章 guidant-release(已建,kid 05086455d42ca0d6);測試章 guidant-test(待建)。都是 Ed25519、exportable=false、deletion_allowed=false |
| 身分 | GitLab id_tokens(aud=Vault)→ Vault JWT auth 角色。正式角色 release-ci:綁 BE、Agent 兩專案、ref_type=tag、ref_protected=true、user_id=決策者,policy 給正式章簽章+kv/ci/* 讀;測試角色 test-ci:綁兩專案、ref_type=branch、user_id=決策者,policy 給測試章簽章+Nexus、Harbor、License Center 三筆讀;FE 角色 fe-ci 只綁 FE 專案、只讀 Nexus。只有決策者能發版、能跑測試包靠 user_id(按 Run pipeline 的人)擋:GitLab 免費版受保護 tag 只能選角色、不能指定人,五位成員都是 Maintainer 以上。要加人就把他的 user_id 加進兩個角色,重寫角色即可,不改程式、不出版 |
| 稽核 | Vault audit log 每次簽章記 job id、project、ref;D-1 寫進 SOP 怎麼查 |
| 公鑰 | 正式章公鑰在 BE scripts/installer/release-keys/ 與 PROD_KIDS,隨 1.28.0 裝上客戶機;客戶端本來就支援多把 kid,零改動。客戶機要先升到 1.28.x 才認得這把(190 已有;189 POC 還在 1.27.x,要先升)。測試章公鑰只放 190(188 STG、189 POC 不放:任何 branch 都能簽測試章,信任它的機器就等於信任未經審查的程式碼),不進安裝包,只手動複製到 190 的 /etc/guidant/release-keys/ |
| 備用 | Mac 根印章保留第二把 kid;Vault 出事時 publish --signer file 照舊 |
| 輪換 | 加新鑰 → 新 kid 進 PROD_KIDS 出一版 → 舊 kid 標 retiring → 下一版拔;SOP 在 D-1 |
| 建置期祕密 | Nexus npm 認證、R2 金鑰、更新站 CI token、Harbor 帳密、License Center API token(Agent 打包用)進 Vault KV;188 上 ~/.npmrc、.env 內對應鍵刪掉;job 執行時 secrets: 拿、結束即失效 |
188 工作區共用的風險與擋法:BE、Agent CI 都直接在 188 的 /opt/guidant-ai-* 跑(GIT_STRATEGY: none),測試包(任何 branch)與正式包共用同一個工作區、.venv、映像快取。任何 branch 的 pipeline 都可能留下被改過的快取,下一次正式包把它包進去再蓋正式章。擋法(C-1、C-2 的必做項):正式模式不准 skip_be/--reuse-images;.build/image-cache/ 正式模式只讀不寫,讀入後 docker load 比對 image ID,不符就丟掉重匯;.venv 正式模式先 poetry install --sync 再跑。殘餘風險:能推 branch 的人就能讓 188 以 jedi(在 docker 群組,等同 root)跑任意指令——BE、Agent 是人按 Run pipeline 時、FE 是每次 push 時;讀得到 188 上的明文 .env、Harbor 登入,也能動共用快取。目前 BE、Agent、FE 三個專案成員是同一批五位 Maintainer,FE 改成 push 就跑並沒有多開放給新的人。Vault 裡的章與 License Center token 有 user_id 擋住,188 上的明文副本在第二批 CI 驗完後清掉(STATE §3)。要徹底隔離得另開一台只收受保護 ref 的 runner,本案不做,記在 §8;FE 專案日後若加入非 Maintainer 成員,要先把 FE 的 verify 移出 188 或改成人按才跑。
不解的:客戶主機執行期密碼(D8,另案)。
由 handoff/cards.py 產。每批開工前決策者做完 §5 對應那段功課。
| 批 | 卡 | 階段 | repo | 做什麼 | reuse/extend/new | 改既有檔 | 新開檔 | 依賴 | 驗收(小弟本機) | model/effort |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | V-1 | 簽章金庫 | update-center | 出包時就蓋章、GitLab 身分發布:蓋章 CLI 接 Vault Transit(正式章 guidant-release、測試章 guidant-test),新增 sign-draft 子命令讓 CI 登記草稿時帶上簽好的清單(Agent 版同時產離線檔);更新站 Worker 收簽好的草稿、發布端點多認 GitLab 的 job 身分(只限 BE/Agent 專案受保護 tag),發布不再帶章;Mac 私鑰留備用 | extend:cli/keystore.ts 加 VaultSigner(與既有檔案私鑰 signer 同介面);cli/envelope.ts 簽章函式吃 signer;cli/commands.ts 加 sign-draft(--product agent 時 reuse cli/offline.ts 產離線檔)、`publish --signer vault |
file;Worker src/access.ts照verifyAccessJwt 的寫法加 GitLab ID token 驗證、register-draft收signed_manifest+kid、publish` 對 GitLab 身分只改狀態不收章 |
cli/keystore.tscli/envelope.tscli/commands.tscli/README.mdsrc/access.tssrc/routes/admin.tssrc/releases.tstest/ |
cli/vault_signer.tstest/cli/vault_signer.spec.tstest/gitlab_identity.spec.ts |
— | vitest:VaultSigner 對 mock 的 Vault 組出合法簽名、verifyRelease 驗過;file signer 行為不變;缺 Vault 身分明確錯誤不退回 file;register-draft 帶簽好清單後內部先裝機器 check 拿得到 signed_manifest;GitLab ID token 不是 BE/Agent 專案、不是受保護 ref、過期、aud 不對都 403;Cloudflare 本人登入的 publish 路徑照舊可用。用開發帳號對 172 以測試章實簽一份清單並驗過;正式章只讀公鑰、核 kid=05086455d42ca0d6(開發帳號不能簽正式章,正式章實簽在 C-1 由 CI 驗) |
| 1 | C-3 | 三條 CI | FE | FE 自己的 CI:刪三支舊檔(ECR/ECS/ansible),換一支 verify+build:push 跑 lint、vitest、npm 弱掃(syft+grype 對 package-lock);BE 觸發或 tag 時 build FE image 推 Harbor;主控台前端同一支 pipeline build 成 artifact 給 Agent CI 拿 | new(照 BE CI 分段,跑 188 的 guidantai-build shell runner;188 沒裝 node,npm 一律在 node 容器裡跑):弱掃 reuse BE scripts/build/sbom.sh 的 lockfile 掃法搬成 FE repo 自己的 scripts/ci/sbom_fe.sh;Nexus 認證從 Vault KV 拿(job 內用 id_tokens 登入 Vault 再讀 KV;GitLab 免費版不能用 secrets: 關鍵字),不再 GitLab Variable |
.gitlab-ci.yml(重寫) |
scripts/ci/sbom_fe.shscripts/ci/vuln-allowlist.txt |
— | 刪 .gitlab-ci-on-premises.yml、.gitlab-ci-terraform.yml;push feature branch 跑 verify 綠(有白名單外 Critical 會紅);tag 跑 build 推 Harbor guidant-ai-fe:<版>;artifacts 有 dist-console/ |
opus/medium |
| 2 | C-3b | 三條 CI | FE | FE Dockerfile 多一個主控台打包目標 console-dist:docker build 就能產出 Agent 主控台 dist-console/,不必在 Mac 打包(從 C-2 拆出,CM-3480) | extend:既有 Dockerfile 拆出共用 deps stage,主系統與主控台各一個 build stage;不帶 --target 時產物不變 | Dockerfile |
— | — | docker build --target console-dist --output type=local 產出 index.html+assets/(≤2 MB);主系統映像照舊通過 build_image.sh --no-push 檢查 |
opus/medium |
| 2 | C-2 | 三條 CI | evidence-agent | Agent 自己的 CI(新):verify → build(主控台前端從 FE pipeline 拿)→ rehearse → package(SBOM 放行門、build_compose_bundle、Harbor、sign-draft 蓋 Vault 章並產離線檔、登記草稿 product=agent、R2 agent/v1/)→ 手動 publish;License Center 送簽 token 改從 Vault 拿;測試包模式同 C-1;188 shell runner、resource_group 與 BE 串行 | new:照 BE .gitlab-ci.yml 五段與 .sync_repo/.in_repo/.collect 片段;publish_draft.sh 從 BE 複製成 Agent 版加 --product agent(先複製,不跨 repo 共用);vault_login.sh 照 C-1 那支複製;測試包模式沿用既有 --internal-test |
scripts/build/build_all.sh(CI 模式與既有 --internal-test 併用)scripts/build/README.md |
.gitlab-ci.ymlscripts/build/publish_draft.shscripts/build/vault_login.sh |
C-3, C-3b | 正式模式:五段綠、更新站草稿 product=agent 帶 Vault 章、R2 agent/v1/<版>/ 三檔、188 .build/compose/ 有離線檔、按 publish 後 published;188 /opt/guidant-ai-agent/.env 不再需要 LICENSE_CENTER_API_TOKEN;測試包模式:測試章簽、不碰 Harbor/R2/更新站;與 BE pipeline 同時觸發時第二條在 resource_group 排隊不併跑 |
opus/high |
| 2 | C-1 | 三條 CI | BE | BE 發版 pipeline 改版:trigger FE 下游(strategy mirror)、不再在 188 build FE、祕密從 Vault 拿、package 段用 V-1 的 sign-draft 向 Vault 蓋章後登記草稿、最後一個手動 job「發布」用 GitLab 身分叫更新站公開;internal_test 擴成「測試包」模式(跳排練、SBOM/弱掃、Harbor、R2、更新站,測試章簽、scp 直升 190);release note 檔尾自動送更新站;runbook 改寫 | extend:.gitlab-ci.yml 的 build-fe 改 trigger:;package 段加 sign-draft、新 job publish(manual、只在受保護 tag);internal_test input 改成測試包模式(既有開關擴充,不另開);publish_draft.sh 帶簽好清單與 release note 摘要;runbook 步驟 2.2/2.3/4 重寫 |
.gitlab-ci.ymlscripts/build/publish_draft.sh(帶簽好清單+release note 摘要)docs/features/FR-167-2610-online-update-channel/release-runbook.md |
scripts/build/release_notes_tail.shscripts/build/vault_login.sh |
V-1, C-3 | 正式模式(受保護 tag):FE 下游綠才進 build-be;草稿登記時已帶 Vault 章、只有 190 check 看得到;190 升上去後按 publish job,更新站轉 published、kid 是 Vault 正式章;從 branch 跑的 pipeline 要不到正式章、publish job 不出現。測試包模式:不跑排練/SBOM/Harbor/R2/更新站,包用測試章簽、scp 升 190 成功,總時間比正式模式少 20 分以上。188 上 grep -r R2_SECRET ~/.npmrc /opt/*/.env 為 0;runbook 無「Mac 蓋章」步驟(備用章段除外) |
opus/high |
| 3 | C-4 | 三條 CI | jedi 套件 | jedi 套件 CI:push 跑各套件 pytest 與 pip-audit;分類器 image 由 BE 下游 trigger build 推 Harbor(runner 待決策者開給套件群組) | extend:既有 .gitlab-ci.yml(若有)加 job;分類器 image build reuse BE build_classifier_image.sh 搬到套件 repo |
.gitlab-ci.yml |
jedi-evidence-classification/docker/ci.sh |
C-1 | push 跑測試綠;BE tag 觸發時分類器 image 在 Harbor 有新 tag | sonnet/medium |
| 3 | D-1 | 文件 | BE | 文件:補齊獨立文件 docs/system-design/release-pipeline/出版管線總覽.md 八段(全景、機器、服務、祕密、連線、流程、從零重建、出事怎麼辦),含 Vault 鑰匙輪換與備用章啟用 SOP;FR-173 的 FINAL-SPEC 只放指向它的摘要 | new 文件 | docs/system-design/release-pipeline/出版管線總覽.mddocs/system-design/README.md |
docs/features/FR-173-2611-release-pipeline/FINAL-SPEC.md |
C-1, C-2 | 總覽八段沒有「待填」;每台機器、每個服務都實查過(ssh/API 讀回),不照抄 design;FINAL-SPEC 五段齊;grep -n "原本|改判|第 [0-9] 棒" 為 0 |
sonnet/low |
共 7 張、3 批;update-center 1、FE 2、evidence-agent 1、BE 2、jedi 套件 1。
| 什麼時候前 | 做什麼 | 狀態 |
|---|---|---|
| 派批 1 前 | Vault 測試章 guidant-test、開發帳號 release-dev(只能簽測試章)、Mac CA 憑證、update-center .env 四個 VAULT_ 鍵 |
✅ 2026-11-03,首腦實測登入、簽章、越權 403 |
| 派批 1(C-3)前 | Vault JWT 登入(認 gitlab.com)、kv/ci/ 五筆祕密、ci-fe+fe-ci;FE 專案 CI 設定檔路徑清空(原指已刪的舊檔);188 信任 Vault CA |
✅ 2026-11-03,FE pipeline 3022544900 實跑通過 |
| 派批 2 前 | ci-release/ci-test 政策、release-ci/test-ci 角色(綁 user_id);測試章公鑰只放 190;BE、Agent v* 受保護 tag;FE job token 允許清單加 Agent |
✅ 2026-11-03,首腦讀回確認 |
| 第二批 CI 驗完後 | 刪 188 上的明文帳密(/opt/guidant-ai-*/.env 的 License Center token、~/.fr167-r2.env、~/.npmrc)、BE 專案 GitLab 變數的 R2 與 Bugsink;License Center 換新 token 放進 Vault |
⬜ |
| 派批 3 前 | jedi 套件群組(commonservice3)開一個 runner |
⬜ |
| 正式出 1.29 前 | Vault 每日快照+異地備份、防火牆(只放 188、決策者 Mac);189 POC 先升到 1.28.x;部署更新站 Worker 後在後台登記 05086455d42ca0d6 為 primary、Cloudflare Access 放行 GitLab 打發布端點 |
⬜ |
指令逐行寫在 vault-setup-runbook.md 第 14 段。
internal_test 跑 BE:190 被直接升上去;Harbor、R2、更新站都沒有這次的東西。published、kid 是 05086455d42ca0d6;Vault 稽核 log 有一筆 sign。grep -r "R2_SECRET\|_auth=\|LICENSE_CENTER_API_TOKEN" ~/.npmrc /opt/guidant-ai-*/.env 為 0。.build/compose/ 有 guidant-agent-<版>.offline.tar.gz,拿一台不出網的 Agent 從主控台上傳驗過。publish --signer file 照樣能發(備用路徑)。以 FR-171、172 實際節奏推估(工作坊一批 2~3 張平行,一張卡 2~4 小時):
| 段 | 內容 | 預估 |
|---|---|---|
| 批 1 | V-1(蓋章工具+更新站三處,本案最大一張)、C-3 | 半天 |
| 批 2 | C-1、C-2;要真跑 pipeline 才驗得到,一輪 30~50 分,算兩三輪修 | 半天到一天 |
| 批 3 | C-4、D-1 | 2~3 小時 |
| 總驗 | §6 八項,從 tag 跑一次正式、一次測試包 | 半天 |
| 合計 | 約 2 個工作天 |
會拉長的地方:決策者功課沒在批次前做完,小弟會停下等;GitLab 免費版的 artifact 下載方式(C-2)若不通要換做法。
update-center 與 LC 進管線(update-center 本身的部署照舊手動)、BE/Agent 的 Nuitka 塞進 Dockerfile 變純容器 build、Sigstore keyless(客戶離線驗不了、簽章公開)、雲端 KMS(要換演算法)、客戶主機預設密碼(另案)、推 tag 自動跑(維持人按)、正式包專用 runner(只收受保護 ref 的第二台 runner,見 §3 工作區共用的殘餘風險)。