FR-064 交接現況(STATE)

ARC CLOSED — 2026-08-16 收尾:母案 CM-1188+34 卡全 Done;兩 P0(BUG-1 死透/BUG-2 指紋)真 image 實證修復;188 已用 compose 起含兩 P0 的正式容器。收尾彙整見同資料夾 2026-08-16-FR-064-SUMMARY.md。下方為收尾當下現況快照,保留供回顧。

這份是 living 檔:每棒就地 Edit,只描述「此刻」。歷史脈絡看同資料夾的 FR-064-LOG.md

項目
最後更新 2026-08-16 晚(第 10 棒 CM-1242:BUG-2 修復+A-4 unlock 真循環+socketio 死透全綠,兩個 P0 出貨阻斷皆已解除,待決策者放行部署)
branch BE feature/FR-064 / LC feature/FR-064~/Projects/Billows/Audit-Manager/license_center
母案卡 CM-1188 https://app.notion.com/p/FR-064-3bc346da4cd081b0afecdac6f11df640
Notion 回寫規範 docs/claude/notion-issue-tracker.md/CLI scripts/notion_case.py
接手前必讀 本檔全文 + design.md §2 決策表 + LOG 最後 block
§1

🧭 Arc 定位(WHY)

三階段落地版戰線第二棒(FR-063 編譯 ✅ → FR-064 防竄改【本案,母案 CM-1188】 → FR-065 Installer)。

  • 需求:客戶機上偵測產物被竄改 → 終結服務+鎖定,唯一解鎖=原廠一次性 unlock token。
  • 定位(已對齊):client-side 驗證擋不住決心逆向者,目標「99% 不值得+1% 留蓄意痕跡」,B2B 法律層是真防線;離線必須成立。
  • 機制:build 期逐檔 SHA-256 分層 manifest → LC Ed25519 簽章(復用 FR-062 體系,泛用 sign_payload/verify_payload,判別鍵 payload_type、缺鍵視為 license 保向後相容)→ 啟動 gate 驗章+比對 core/resources → runtime 4h 抽查+業務埋點 → tamper 雙落點(DB 全域單例表+FS 標記)→ 拒啟鎖定。
§2

角色定位

首腦=分析決策/協調;實作全走 runner(開 case 給新 session 為主,機械小修才本體 subagent);驗收一律親驗不信自報。

§3

決策定案

D1–D10 全拍板 2026-08-15,D11 拍板 2026-08-16,完整表在 design.md §2(索引:分層驗證/簽驗共用層/main.py 錨點/唯讀化/雙落點/離線 unlock/邊界承認/4h+jitter 抽查/D9 回報 LC/D10 二次確認/D11 lockdown 殼+forensic 存證+邊界明文)。

§4

§1 當前狀態(2026-08-16 傍晚盤點)

git

repo branch 與 origin commits(FR-064 範圍)
BE feature/FR-064 ahead 3(未 push:7cc7989e11c9502e19a086af 頂部見下表
LC feature/FR-064 同步 頂部 5 支屬 FR-064:651db9c(T-5.1 unlock token 簽發)、7c2974d(T-6.3 tamper 事件接收 D9)、076c06b(CLAUDE.md migration 鐵則+部署四步)、57c6566(T-2.1 manifest 簽章)、6ce339e(T-1.1 sign/verify_payload 層)
FE feature/FR-064 最新 de23772 T-8.2 鎖定頁+interceptor

BE 最新(新→舊):19a086af CM-1242 掛載宿主 /etc/machine-id 修指紋漂移(BUG-2,未 push)/11c9502e7cc7989e CM-1241 BUG-1 終止改依承載送訊號(未 push)/0aaa0269 STATE 用語消歧/4e64ca07 CM-1231 殼內容協商改只看 Accept(UA 嗅探讓 XHR 拿到 HTML 的修正)/adf77882 CM-1236 scripts/README 修正/2d56edb6 CM-1238 落地版 docker-compose 定式(api+socketio 雙容器+pki volume)/4c06ddc8 CM-1236 scripts/ 盤點整理/f929abe7 CM-1237 build_all.sh 一鍵管線/be3868f4 STATE/LOG docs/aeb2bd4424834d73 T-8.3 forensic。更早鏈(T-8.1 殼 520fc5e3、T-5.2 f8133e72、gate/manifest/唯讀化等)見 LOG。

Notion 卡(2026-08-16 傍晚盤點實況)

狀態
CM-1188(母案) Not started(母案收尾時等令收 Done)
CM-1215~1223/1226/1227/1230/1224/1225/1228/1232/1234 修正待驗證(1224/1225/1228/1232/1234 已過首腦親驗 2026-08-16 晚)
CM-1231/1233 修正待驗證(殼+forensic;殼另補內容協商修正 4e64ca07
CM-1236/1237/1238 修正待驗證(scripts 盤點/build_all.sh 管線/compose 定式)
CM-1229 修正待驗證(第 8 棒實機驗收測試=人工實測非自動化測試碼;A-2 FAIL 如實在卡,卡不收 Done)
CM-1239 修正待驗證(解鎖 SOP 雙份 91a3bcd8:原廠側 unlock-sop.md+客戶側手冊,首腦已親驗,等 user 收 Done)
CM-1241 修正待驗證(BUG-1 修復,第 9 棒;真 image 端到端已由第 10 棒 V3-① 順帶證實)
CM-1242 修正待驗證(BUG-2 修復+A-4/socketio 複驗,第 10 棒;兩個 P0 皆解除

DEV DB

integrity_tamper_events 表存在確認 ✅(psql -h 192.168.50.188 -p 25432 -d guidant_ai_devto_regclass 回表名)。

188 實況(2026-08-16 傍晚唯讀查證)

  • /opt/guidant_ai:branch feature/FR-064、HEAD 4e64ca07(與本地同步)
  • 正式容器:guidant-apiguidant-socketioguidant-ai-be:1.14.0、Up (healthy);guidant-api log 有 [INTEGRITY] 產物完整性驗證通過(core+resources 共 1304 檔,version=1.14.0) ✅ gate 真容器實跑通過
  • /srv/guidant/pki/:空(tamper 標記已清)✅
  • image:本機 guidant-ai-be:1.14.0:latest 皆 2026-08-16 15:38 新打;Harbor tag 仍是 08-15 舊版(新 image 未推)
  • /opt/license_center:HEAD 651db9c(與本地同步);systemctl is-active license-center → active
  • FE 部署 bundle:/var/www/html/audit-manager/assets/index-B6KcAQdr.jsINTEGRITY_503001(×2)✅ 鎖定碼已在線上 bundle
  • 附註:/opt/license_center 對 jedi 帳號有 git dubious ownership 警告,查詢需帶 -c safe.directory=/opt/license_center
§5

子需求進度表

子需求 狀態
.1 簽驗共用層 CM-1215/1216 ✅ 驗收過(LC 6ce339e+BE 97bca82e)
.2 build manifest CM-1217/1218 ✅ 驗收過+三次返工(參數改名 6f37019e、.env fallback 4fe7638a,皆已驗)
.3 gate+落點 CM-1219/1220/1221 ✅ 驗收過(eba7f5a0/d4ef0285/607b4b3a)
.4 抽查+埋點 CM-1222/1223 ✅ 驗收過(b4a102b7)
.6 唯讀化 CM-1226 ✅/CM-1227 ✅/CM-1228 修正待驗證 T-6.1/6.2 過(7079ae24/3f338ffc);T-6.3(D9 回報 LC)已做(LC 7c2974d/BE d181fdc6),三情境實測皆過,待 user 複測
.5 unlock 全鏈 CM-1224/1225 兩張皆已做(本棒),修正待驗證——LC 651db9c(後台頁+CLI+migration 49629d9e5d99 已套 DEV)/BE f8133e72(核銷四關+回執回填 DB);六情境活體實測皆過(含以真簽章分別驗指紋關與效期關),待 user 複測
.7 實機驗收 CM-1229+修復棒 CM-1241/1242 修正待驗證——第 8 棒:A-1/A-1b/A-3/A-5 縮短版+B license 迴歸+E orchestrator PASS,A-2 FAIL 抓出 BUG-1;第 9 棒修 BUG-1(C-1 銷案);第 10 棒:A-2/C-2 死透、A-4 unlock 真循環(含 4 條負面案)、socketio 死透、BUG-2 指紋穩定 全數 PASS。餘 24h 長跑(A-5 完整版)與 4h 真窗
.8 D11 lockdown 殼+forensic CM-1231/1232/1233/1234 四張全數已做,皆修正待驗證——T-8.4 文件/T-8.2 FE(de23772,FE repo)/T-8.1 lockdown 殼(520fc5e3)+T-8.3 forensic 擴充(24834d73+aeb2bd44),本棒合做。BE↔︎FE 整鏈端到端(真殼回 503 → 真瀏覽器 → 跳鎖定頁)尚未跑
場外 CM-1230 .env 稽核回補✅(bfbe6cc9/4b2234ac/f681a583/dc5c1090/9508c7f8)
§6

簽章鏈實證(2026-08-16 上午,本任親驗)

188 真 dist 產 manifest(core 1291/resources 13/thirdparty 5492)→ LC 簽章(kid 04b1e65f=STG 鑰)→ BE verify_payload 驗章 PASS → 三層各抽一檔 sha256 與實檔全中。首次端到端全通

§7

🔴 尚未實證清單(接手必知,勿當已完成;第 8 棒更新)

第 7 棒已銷gate 真 dist正式 image 唯讀化真容器 gate殼真容器

第 8 棒已銷殼修正(4e64ca07)後整鏈(真 image BE 側四組協商全對:瀏覽器 UA+JSON Accept 回 INTEGRITY_503001 JSON;FE 側 user 已於第 7 棒實測)/真實已出貨 license 匯入迴歸(三環境三把 kid 真照全過+兩條負控+D2 缺鍵向後相容確認)/T-7.1 五條中的 A-1/A-3/A-5 縮短版殼在 orchestrator 下的行為(unhealthy 但不進重啟迴圈)。

第 9 棒已銷C-1 scheduler 觸發×兩承載(scheduler 路徑不同病,修前修後皆 exit=1 死透)。

第 10 棒已銷(CM-1242)C-2 真產物×gunicorn hook 觸發後服務死透(真 image 1.14.0 實測 Handling signal: quitShutting down: Master→容器 Exited、ping 000,BUG-1 修正端到端成立)/A-2 運行中改檔觸發(同上,hook 正常判定並終止)/A-4 unlock 真容器完整循環(鎖定→LC 簽 token→放檔→restart 解鎖成功+標記清+nonce 入帳+DB unlocked_at 回填,四條負面案他機/過期/錯事件編號/重放全擋)/socketio 承載死透(PID 1+eventlet+APScheduler 條件下 _termination_signals()[]os._exit(1)、容器 Exited(1))/BUG-2 指紋漂移(掛載後跨 restart/換容器皆不變,附不掛載的負控復現)。

仍未實證:

  1. 正式/STG/POC 尚未套用 machine-id 掛載——第 10 棒只改 repo 內 compose 檔與拋棄式容器;正式部署需 docker compose up -d recreate 兩容器才生效,等決策者放行
  2. socketio 真 image 的完整竄改→死透鏈——第 10 棒 V5 驗的是承載條件下真的 _terminate_process();「真 binary 被改→真抽查 job 比對出不符→終止」在 socketio 模式未實跑(8002 不載 REST 故無埋點端點、抽查 4h 且參數寫死不可調)。比對邏輯與 api 共用同一份 runtime_check,風險低但非已驗
  3. scheduler×真 binary×4h 真窗——縮短間隔驗過不等於真窗驗過,由時間自然銷或後續長跑驗
  4. 「≥24h 連續運行跨多輪抽查零誤報」(A-5 完整版)
  5. 宿主無 /etc/machine-id 時 docker 建成目錄的退化行為——屬 docker 已知語意(掛載來源不存在時依目標推斷建立),deployment-env.md 已寫前置檢查,但未在缺檔宿主上實測;同理「machine-id 重生成會讓既有綁機憑證失效」亦為推論(不宜在 188 重生成驗證)
  6. K8s liveness 下殼會 CrashLoop——第 8 棒觀察推得:Docker restart policy 只看 process 退出故安全,但 K8s liveness probe 失敗會 kill+重啟 pod → 鎖定態變 CrashLoop。FR-065 installer 若涵蓋 K8s 部署,需把鎖定態排除在 liveness 之外(目前 compose 落地版不受影響)
§8

✅ 兩個 P0 出貨阻斷皆已修(等 user 複驗)

BUG-1(CM-1241,第 9 棒修,第 10 棒端到端證實)

症狀:真產物×gunicorn 下 hook 觸發 tamper 後服務沒死透——落點都寫了、FATAL 也印了,但 master 存活並 respawn,服務照常回 200。 真因(第 9 棒推翻原假設):不是「master 收到 SIGTERM 走 graceful 沒死」,而是master 根本沒收到——容器內 pgid=1,killpg(1,sig)kill(-1,sig) 是 POSIX 廣播特例、Linux 明文排除 PID 1;且 kill(1,SIGKILL) 對無 handler 的 PID 1 也被核心忽略。 修法_terminate_process()依承載分流——gunicorn worker 指名對 master 送 SIGQUIT(有 handler 收得到、quick shutdown 不 respawn),單 process 承載/容器 PID 1 只 os._exit(1),bare metal 僅 pgid==pid 時 killpg。master pid 由 main.py post_fork 登記。 第 10 棒真 image 端到端證實Handling signal: quitShutting down: Master → 容器 Exited、ping 000 ✅

BUG-2(CM-1242,第 10 棒修)

症狀:容器機器指紋每次重啟就變 → 解鎖憑證重啟即失效(永遠解不了鎖)、license 綁機漂移。 根因:Debian base image 的 /etc/machine-id0 bytes 空檔(容器不跑 systemd 永遠填不上)+/var/lib/dbus/machine-id 不存在 → 指紋退到第三級 uuid.getnode()(容器 MAC,每次 restart 就換)。 修法(選項 A,code 零改動):compose 掛 - /etc/machine-id:/etc/machine-id:ro,指紋穩定錨定宿主機。實測跨 restart/換容器皆不變,附「不掛載」負控復現 bug。 ⚠️ 掛的是檔不是目錄——宿主缺檔時 docker 會建成目錄、指紋靜默退回 MAC,症狀與沒掛一樣;deployment-env.md 已補前置檢查。 綁機回歸:DEV 16 列綁舊指紋但全為歷史照is_current=false),現行照 0 列綁機;STG/POC 皆 null → 無回歸。

相關卡:

§9

待辦地圖

  1. CM-1241 修 BUG-1 ✅(第 9 棒)/CM-1242 修 BUG-2+複驗輪 ✅(第 10 棒,A-4 unlock 真循環+socketio 死透一併銷案)
  2. 🔴 user push 三支未 push commit7cc7989e11c9502e19a086af)→ 188 pull → rebuild image(含 BUG-1 修正的 1.14.0 已在 188 本機,但 BUG-2 是 compose 組態不需 rebuild)
  3. 正式/STG/POC 套用 machine-id 掛載docker compose up -d recreate 兩容器)——等決策者放行,非開發期動作
  4. CM-1239 解鎖 SOP 文件(已完成 91a3bcd8,修正待驗證);CM-1240 FE details 開關已收 Done(29fead0 待 push/部署)
  5. version-bump(現標 1.14.0,含 FR-064 出貨前走 version-bump skill)+母案 CM-1188 收尾(等 user 令收 Done)——兩個 P0 已解除,此步的阻斷條件已消失
  6. 場外:CM-1204 待排程;probe「問卷上傳落點」存量問題待開卡(T-6.2 發現);AGENT_CERT user 另 session;test/ 下 9 支 AI Dashboard 舊測試 collection error(與 scripts/archive 同批,待 user 裁)

⚠️ 本節是建議清單,不是執行授權。接手方讀完盤點完即停,等 user 發令。

竄改測試 SOP(接手複驗用,第 7 棒實戰校正版)

  • 絕不對正式 compose 容器測(read_only 改不了檔;且標記會寫進正式 /srv/guidant/pki,殘留會讓正式容器一直拒啟——本棒實際發生過,復原=rm /srv/guidant/pki/.integrity-tamper+compose 重起)
  • 正確做法:拋棄式可寫容器+-v /tmp/pki-test:/opt/guidant/pki;要看 FE 就讓它佔 8000(先 docker rm -f guidant-api,測完 compose 起回來)
  • 改檔對象必須是 manifest 清單內的檔(resources 層模板檔最方便);清單外新增檔案不觸發(設計如此,log/上傳/DB 都不在驗證範圍)
  • unlock 眉角:fingerprint 抄該容器印的;解鎖前先把檔改回(否則解鎖後立刻再鎖,屬設計);重啟用 restart 不可 rm(rm 換 fingerprint、token 作廢)

第 8 棒補充(真產物實測校正)

  • 要驗 DB 落點必須 -e DB_NAME=guidant_ai_dev 覆寫——拋棄式容器沿用 /srv/guidant/guidant.env 會指向 STG(該庫無 integrity_tamper_events 表)。啟動 gate 本就不寫 DB,所以只驗 boot 拒啟時不必覆寫;驗 runtime/hook 落點時必須。
  • 改運行中的 binary 要用 unlink+renameprintf >> /app/guidant-ai 會被 Text file busy 擋下,正確作法 cp → 改副本 → mv 原檔改名 → mv 副本頂上。用前者會誤以為 binary 天然免疫。
  • Nuitka 產物的埋點實際只驗 binary 一檔_HOTPATH_PRIORITY_PREFIXES 那三個 common/* 前綴在真產物中不存在為獨立檔(全編進 binary),manifest grep 零命中 → 選檔退化成只剩 binary 本體。要觸發 hook 偵測,改的必須是 binary。
  • 觸發 hook 的端點:POST /api/1.0/license/upload(需 root tenant(/1/)admin token,一般租戶 admin 會被 GRC_403060 擋在埋點前)或 SSP 報告匯出。

第 10 棒補充(CM-1242 指紋與 unlock 實戰)

  • 拋棄式容器必須加 -v /etc/machine-id:/etc/machine-id:ro,否則指紋每次 restart 就變、unlock token 必然對不上(BUG-2 本體)。加了之後 188 上的指紋恆為 ff793254d00c364107dda07846e5e90e4d4db79fbdbd2a4259fdf24e4a3da080
  • 查指紋三個出口(同一個值):① 服務正常時 GET /api/1.0/license/activation/machine-code(需登入+DEPLOYMENT_MODE=host,故拋棄式容器要 -e DEPLOYMENT_MODE=host)② 鎖定時看拒啟 log 的 machine_fingerprint: 行,或 lockdown 殼 503 JSON ③ 標記檔 .integrity-tampermachine_fingerprint 欄位。
  • root tenant admin 帳號=admin/密碼同 DEV 慣用blsadmin 是 tenant 102,打埋點端點會被 GRC_403060 擋掉)。
  • 驗「過期」負面案不能用 CLI --valid-hours(有 1–336 下限防呆,擋在簽發端就中止了,核銷的效期關根本沒執行到)——要用 LC 私鑰 sign_payload 直接造「真簽章但 expires_at 在過去」的 token(load_private_key(Path("/opt/license_center/keys/private.pem"), os.environ["LICENSE_CENTER_KEY_PASSPHRASE"].encode()))。
  • 驗「重放」要先手工重建標記檔(解鎖成功後標記已清),把同一個 event_uid 寫回 .integrity-tamper 再放已用過的 token。
  • socketio 承載無法用埋點觸發:8002 不載 REST blueprint(main.py 設計),且抽查 4h 週期參數寫死不可調 → 要驗終止行為得自建 harness 重現承載條件(PID 1+monkey_patch(all=False, socket=True)socketio.runBackgroundScheduler)但 mount 真源碼執行(PYTHONPATH=/src)。
§10

環境紀律

  • 開發只動 DEV;STG/POC 寫入等 user 明示
  • 188 程式碼一律 git pull(先 checkout feature/FR-064——上次事故主因)
  • STG 188 兩服務跑舊產物勿動
  • LC 部署四步(pull → 依賴 → alembic PYTHONPATH=src → restart,見 memory feedback_lc_deploy_four_steps
  • 189 POC 的 LC 落後未對齊