# FR-064 交接 LOG（append-only）

> 每棒收工 append 一個 block，寫完永不改。發現舊 block 有誤 → 在新 block「推翻了什麼」欄更正。

## 第 1 棒 — 2026-08-15 晚 ～ 2026-08-16 午 — 首任首腦（協調者）

**commits**: BE `eba7f5a0`..`4fe7638a` 共 16 支（含 design 7ceb7188 前置）；LC `6ce339e`/`57c6566`/`076c06b`。兩 repo 皆已 push 與 origin 同步。
**Notion**: CM-1208~1229 卡樹建立；CM-1215~1223/1226/1227/1230 → 修正待驗證；CM-1224/1225/1228/1229 未派；CM-1188 母案續掛。

**做了什麼**:
- 接手戰線 → CM-1207 驗收收 Done → big-feature-workflow 全程（探脈×2 → 討論稿 90828118 → D1–D10 拍板 → design.md 7ceb7188 → 22 張卡樹 CM-1208~1229）
- 五波 runner 派工與驗收（.1/.2/.3/.4/.6ab 全過，含夜間授權自主兩棒）
- 188 build 事故鏈排除（branch 未切／LC 三連炸／token 未進 shell）
- 簽章鏈首次全通實證（真 dist manifest → LC 簽 → BE 驗 → 抽檔 sha256 全中）

**決策**: D1–D10（詳 design.md §2）。D8 由 45min 改 4h+jitter（user 裁示，容錯靠 D10 二次確認與 jitter 防節奏）；D9/D10 討論中新增；unlock 走離線信封、棄 activation_code 線上模型。manifest 缺席行為＝dist 強制/源碼跳過（runner 自定，**user 未明示追認——接手注意**）。

**教訓**:
1. 「本機測試綠」≠「部署機會產出」——188 停舊 branch 讓 build 跑舊腳本，驗收漏了「碼到得了現場」這環，被 user 指正「呼嚨」；未實證清單要集中列給 user 看，不散落各棒回報。
2. env 變數自創撞名（LC_API_TOKEN vs 既有 LICENSE_CENTER_API_TOKEN）＋錯誤預設 port——已固化 CLAUDE.md 鐵則（f8d1632b）。
3. bash 腳本不讀 .env——user token 填了卻被判未設定，已修 fallback（4fe7638a）。
4. LC 部署三連炸（依賴／unit 過時 exit0／缺 migration）→ 四步 SOP 固化（memory＋LC CLAUDE.md 076c06b）。
5. 稽核判準「套件內有 getenv ≠ 產品要填」（GITLAB 誤收案例）。

**推翻了什麼**: C 棒自創參數名（6f37019e 廢除）；.env 稽核首輪誤收 9 支（4b2234ac 移除）。

**在途**: user 正重跑 188 build（本次起自動送簽）；AGENT_CERT_VALID_DAYS 已處置（9508c7f8）但其他 env 項 user 另 session 處理中。

**留給下一棒**: build 完先做真產物集中驗證（smoke＋gate 真驗＋改檔拒啟），再派 .5/.6.3。

---

## 第 2 棒（T-6.3／CM-1228）— 2026-08-16 午後 — runner

**commits**: LC `7c2974d`（tamper 事件接收端點＋migration＋tamper_report 模組）；BE `d181fdc6`（best-effort 回報模組＋兩處掛點）。兩邊皆未 push。

**做了什麼**:
- LC：`POST /api/internal/report-tamper`（走 62.10 internal API 形狀），新表 `tamper_events`，migration `4328af2b0ece` 已套 DEV。
- BE：`common/integrity/lc_report.py`（stdlib-only、3s timeout、全域 except），掛進 `startup_gate._record_and_refuse` 與 `runtime_check._respond_to_tamper`。
- 三情境實測（非模擬）：源碼模式模擬 tamper → LC dev 收到並落 DB（清除測試資料）；LC 位址不通 → 3.01s 內返回無 traceback；未設 token → 0.0003s 靜默跳過。
- 回歸：BE 50 支 integrity 測試全綠；LC 360 支通過（另 2 支失敗屬其他並行 session 進行中改動，非本卡）。
- CM-1228 回寫「修正待驗證」＋白話補充。

**教訓**:
1. **同一 LC checkout 有另一 session 並行動 `modules/integrity/`**（FR-064.5 T-5.1 unlock token 簽發）——非 worktree，即時撞了 `models.py`/`repository.py` 兩次被覆寫。發現後把本卡模組改放 `modules/tamper_report/`，未動對方任何檔案。**後續派工若可能撞路徑，建議指定 worktree 或明確劃分模組命名**。
2. Alembic autogenerate 在多 session 併發改 model 時會把「尚未落地的別人的表」一併抓進來——已手動裁剪 migration 只留自己的 `tamper_events`。

**推翻了什麼**: 無。

**留給下一棒**: .5 unlock 全鏈（CM-1224/1225）看起來已有另一 session 在跑（`modules/integrity/`、`unlock.py`、`common/integrity/unlock.py` 皆已出現改動但未經本棒驗證，接手者需自行盤點其進度）。T-6.3 完成後 .6 子需求全數修正待驗證，可推進 .7 E2E 收口前的整體 push 齊備檢查。

---

## 第 3 棒（T-8.4／CM-1234）— 2026-08-16 晚 — 文件 runner（D11 落地）

**commits**: BE（單支，純文件），git add 顯式列檔，訊息帶 CM-1234。

**做了什麼**:
- 落地 2026-08-16 拍板的 D11 三件事到 design.md：①鎖定溝通改 B 方案（lockdown 殼，取代純 exit）②forensic 證據規格擴充（mtime/ctime/owner/size＋依來源分級觸發脈絡）③邊界明文＋契約措辭建議（「是誰改的」應用層不可知，條款綁「完整性驗證失敗」而非「查明行為人」）
- design.md：§4.4 整段改寫（4.4.1–4.4.4）；§2 補 D11 決策列（含被排除的 A 純 exit／C 半套方案與理由）；§5 新增 FR-064.8 段（T-8.1–T-8.4，標注依賴關係）；檔頭 lede／變更紀錄補一行
- deployment-env.md（FR-063 資料夾，本案適用落點——找不到 FR-064 自己的部署文件，沿用 FR-063 落地版部署文件）新增 §9「客戶端主機層留證建議」：auditd 監控規則範例、log 外送建議、邊界明文；原 §9 交付前檢查清單順移為 §10
- 跑 doc-site-build 重 build FR-064 站，成功

**決策**: 無新決策，純落地既定拍板內容，不加料。

**教訓**: 無。

**推翻了什麼**: 無。

**留給下一棒**: T-8.1（lockdown 殼實作）／T-8.3（forensic 擴充）依賴 CM-1224/1225/1228 主鏈程式碼完成後才能開工；T-8.2（FE 認錯誤碼跳轉）契約已定可先行派工。

---

## 第 4 棒（T-8.2／CM-1232）— 2026-08-16 晚 — FE runner

**commits**: FE repo（compliance-manager-fe，branch feature/FR-064）單支 `de23772`，git add 顯式列檔，訊息帶 CM-1232。未 push。

**做了什麼**:
- `src/config/axios/axios-interceptors.js`：response interceptor 新增 `INTEGRITY_503001` 分支（形狀對齊既有 `LICENSE_403001` 靜默分支）——不 emit `axios-error`（不 toast）、把 BE lockdown 殼回應 `data.machine_fingerprint`/`tamper_event_id`/`detected_at` 透過 query 帶進 `router.push({name:'system-locked', query:{fp,eid,at}})`；模組級旗標防同一瞬間多隻請求重複導向。
- `src/config/router/index.js` 新增 `/system-locked` route（不掛 `AppLayout`、無 `requiresAuth`——被鎖時可能拿不到 token）。
- 新頁 `src/views/default/SystemLocked.vue`：視覺沿用 `NotFound.vue` 品牌光暈骨架，顯示「系統已鎖定」＋聯繫原廠 hint-bar＋三值（機器指紋／事件編號／偵測時間，前兩者帶複製按鈕，複製邏輯抄 `LicenseActivation.vue` 的 `copyMachineCode` 形狀）；query 缺值時 fallback「請洽系統管理員查閱伺服器紀錄」；頁面不發任何 API。
- 新 i18n 檔 `system-locked.json`（en/zh-tw，`system_locked` namespace），已在 `config/locales/index.js` 註冊；`error-code.json`（en/zh-tw）補 `INTEGRITY_503001` 條目（雖不 toast，仍照 BE error code 同步 FE i18n 慣例補齊）。

**決策**: 無新決策，純照派工 prompt 定死契約實作。

**驗收（dev server + chrome devtools MCP 實測）**:
1. 直接 GET `/system-locked?fp=...&eid=...&at=...`——三值正確渲染，複製按鈕點擊成功彈「已複製到剪貼簿」toast，console 無 error。
2. 直接 GET `/system-locked`（無 query）——正確落 fallback 文案。
3. 直接呼叫 axios 已註冊的 response interceptor rejected handler、餵入模擬 BE lockdown 殼回應（`error_code: INTEGRITY_503001`）的 fake error——確認未 emit `axios-error`（無 toast）且 router 正確跳轉並帶對三個 query 值，證實「BE 回 503 → FE 認碼 → 跳鎖定頁」整條路徑成立。
4. 登入頁 smoke（渲染、無 console error）確認未影響既有流程。

**教訓**: 無。

**推翻了什麼**: 無。

**留給下一棒**: T-8.1（lockdown 殼實作，BE）／T-8.3（forensic 擴充）仍待 CM-1224/1225/1228 主鏈程式碼完成後才能開工；T-8.2 完成後，一旦 T-8.1 殼落地即可整鏈端到端驗（真殼回 503 → 真瀏覽器打真 API → 跳鎖定頁）。

---
## 第 5 棒（T-5.1＋T-5.2／CM-1224＋CM-1225）— 2026-08-16 晚 — unlock 全鏈 runner

**commits**: LC `651db9c`（CM-1224）／BE `f8133e72`（CM-1225）。兩 repo 皆 branch `feature/FR-064`，顯式 git add，未 push。

**做了什麼**:

*LC 側（T-5.1）*——新 `modules/integrity/`（models/repository/service）＋SSR 後台頁 `/unlock` ＋ CLI `issue-unlock-token` 保底＋ migration `49629d9e5d99`（表 `unlock_token_issuance`，**只套 DEV**）。payload 依 design §4.3 定死，走 `6ce339e` 的泛用 `sign_payload`（無第二套簽章實作）。效期預設 72h（1–336h）。service 層守門（三入口共享）：指紋 64 hex、event_id `evt-` 前綴、效期範圍。下載檔名固定 `unlock.token`、**不套 v3 armor**（armor 標頭寫 GUIDANT LICENSE，會讓客戶誤當授權照上傳）。log 依安全鐵律：指紋只記 `fingerprint_digest` 前 12 碼、nonce 不記。

*BE 側（T-5.2）*——新 `common/integrity/unlock.py`（同套件約束：只 stdlib＋無依賴純函式）。核銷四關（驗章→指紋→event_id→nonce）＋效期關，掛進 `startup_gate.run_startup_gate()` 的 FS 標記分支。成功則清標記＋記 nonce＋刪 token 檔＋留回執，**繼續往下跑 manifest 比對**（不 return）。DB 側 `unlocked_at`/`unlock_nonce` 由 `sync_unlock_receipt_to_db()` ＋ `repo.mark_unlocked()` 回填，掛在 scheduler 首輪同一 tick。

**決策（三項，皆屬派工單留給實作棒的形式選擇）**:
1. **LC 入口選 SSR 後台頁，不做 internal API**——要解鎖的機器正處於拒啟狀態，跑不起來也就打不了 API；行為者是坐在瀏覽器前接客戶電話的內部人員，形狀同「離線開通受理」。CLI 為 air-gapped 保底（同 issue／sign-manifest 雙軌）。**沒有第三軌 API**：沒有呼叫端的端點只是多開攻擊面。
2. **unlock 另立 `modules/integrity/` 不塞 licensing**——unlock token 不是授權照（無 Plan／訂單／展延／開通綁定），塞進去只會讓那模組變雜物間；共用的「同私鑰、同 sign_payload」本就在 `core/crypto`。
3. **DB 回填走「回執檔」而非從 nonce 帳末筆推導**——標記清除後 `event_uid` 就沒了，而那正是 UPDATE 的 WHERE 依據。回執寫不進去不阻斷解鎖（解鎖已成立，純紀錄失敗不該否定已成立的授權行為）。

**幾個刻意取捨（避免下一棒「好心修掉」）**:
- **nonce 帳毀損取寬鬆側**（回空清單）：取嚴格側會讓一次檔案損毀就再也解不開只能重灌；寬鬆側最多讓某張已用 token 再用一次，仍受效期／指紋／事件三關拘束。**與標記檔「毀損即視同鎖定」方向相反是刻意的**。
- **標記毀損時一律不放行解鎖**：否則「先把標記改亂碼、再拿任一張舊 token」就成了繞法。
- **nonce 寫不進去時不放行**（記不住＝失去防重放），但訊息明確標示為環境權限問題，避免現場誤判成 token 有問題而白跑一輪重簽。
- **四種失敗訊息各自不同且含處置建議**：含糊訊息會讓現場把「指紋抄錯」誤判成「原廠簽錯了」。

**驗收（全部用 LC 真 DEV 鑰簽出的 token，非模擬信封）**:
1. 正確 token → 核銷成功、標記清除、nonce 入帳、token 檔刪除、回執產生 ✅
2. 重放同 nonce → 拒 ✅　3. 事件編號不符 → 拒 ✅
4. **另簽一張 `fingerprint=b*64` 的真 token** → 指紋關擋下 ✅（不是靠改檔破壞簽章矇混——那只驗得到驗章關）
5. **回溯 issued_at 簽一張真的過期 token** → 效期關擋下 ✅
6. 未放 token → 靜默拒啟不噪音 ✅
7. DB 回填（真 DEV DB）：FS→DB inserted=True → 回執回填 updated=True → 回執清除 → 重複回填 False（`unlocked_at IS NULL` 生效）→ 無回執 no-op；psql 查得兩欄正確落列，**測試列已清除** ✅
8. LC 後台頁走完整 HTTP 流程（登入→表單→簽發→結果頁→下載）簽出的檔，直接放進標記目錄被 BE 核銷成功——**跨 repo 端到端** ✅

測試：LC 17 條（全站 378 綠）／BE 16 條（license+integrity 相關 120 綠）。**突變驗證**：BE 四關逐一停用各紅對應那一條、還原復綠（每關獨立可證）；LC `tamper_event_id` 組錯→2 紅、預設效期 72→71→1 紅。

**教訓**:
- **自證式斷言抓不到東西**：`assert expires - issued == timedelta(hours=DEFAULT_VALID_HOURS)` 拿常數比自己，把 72 改成 71 測試照樣綠。改成字面 72 才有牙齒——突變測試就是為了逼出這種。
- **「用改壞的檔案驗某一關」是假驗證**：改 payload 會同時破壞簽章，於是四關全被第一關攔下，看起來全過、實際只驗到驗章。指紋關與效期關各補簽一張**真的**才算數。
- **`base.html` 加導覽項會連帶打斷測試**：`test_csrf` / `test_login_lockout` 的最小 app fixture 逐一 stub 了每個 nav endpoint，新增一項就要同步補，否則 `url_for` BuildError。
- `load_dotenv()` 是相對**呼叫檔**解析而非 cwd——臨時驗證腳本放 scratchpad 時要帶絕對路徑。

**推翻了什麼**: 無。design §4.3 的契約與派工單裁定（FS 通道、四關、nonce 落 FS、DB 補寫比照 T-3.3）全部照做，未偏離。

**留給下一棒**:
- **.5 只在源碼模式驗過**（`GUIDANT_TAMPER_MARKER_DIR` 指臨時目錄、手造標記檔）。真 Nuitka dist 的完整重啟循環（gate 拒啟 → 放 token → 重啟解鎖 → 檔案未還原立刻再鎖）**未跑**，建議併入「真產物集中驗證」一起做。
- DB 回填走的是直接呼叫 service（真 DEV DB），**非經 scheduler tick 實跑**。
- LC migration 只套 DEV；STG/POC 上版時 `alembic upgrade head` 會一併帶 `49629d9e5d99` 與平行棒的 `4328af2b0ece`。
- 平行棒（CM-1228 T-6.3）同時段在 LC 動工，本棒的 `migrations/env.py` 一行 import 被掃進其 `7c2974d`——內容正確，僅歸屬提醒。
- 零新增環境變數（兩側皆沿用既有），`.env.sample` / `deployment-env.md` 無須異動。

---

## 第 6 棒（T-8.1＋T-8.3／CM-1231＋CM-1233）— 2026-08-16 晚 — .8 第二波 runner（合棒）

**commits**: BE `520fc5e3`（CM-1231 lockdown 殼）／`24834d73`（CM-1233 forensic 擴充）／`aeb2bd44`（CM-1233 補 boot 的 app.log 尾段，review 後補）。branch `feature/FR-064`，顯式 git add，未 push。LC 側零改動（見下）。

**做了什麼**:

*T-8.1 lockdown 殼*——新 `common/integrity/lockdown.py`（stdlib `http.server`，無 Flask/DI/DB、零寫入面）。任何路徑任何方法回 HTTP 503；內容協商：`Accept: text/html` 或瀏覽器 UA → 繁中鎖定頁，其餘 → JSON envelope（`code:0`／`error_code:INTEGRITY_503001`／`data` 三欄），契約照 FE `de23772` 定死未動。`startup_gate._refuse()` 收斂全部拒啟路徑：印完整 log → 起殼 → 殼起不來才 `SystemExit`。runtime 抽查／埋點維持既有「寫落點＋`os._exit`」不變。

*T-8.3 forensic*——新 `common/integrity/forensic.py`。`collect_file_metadata`（mtime/ctime/owner/size，個別 stat 失敗記 `error` 不阻斷）＋`collect_trigger_context` 依來源分級（boot `app.log` 尾段／scheduler 活躍 session 快照／hook 當下帳號＋IP）。三落點吃同一份 detail 物件（一處建構三處同步）。

**決策（實作棒的形式選擇）**:
1. **殼要放 CORS**——派工單沒提，但不放行 preflight 的話跨源 FE 收到的是 network error 而非 `INTEGRITY_503001`，「FE 認碼跳鎖定頁」整條契約會斷。殼本來就沒有機密與寫入面，`Allow-Origin: *` 不放寬任何能力。
2. **活躍 session 資料源選 `public.api_logs`**——HTTP middleware 全自動寫入，是唯一「不必額外埋點就有帳號＋IP＋時間」的現成來源。不為一份旁證另建活躍 session 表。窗口 30 分鐘（抽查週期 4h，太長會把早已離線的人算進來）。排除 `user_name` 為空字串的未登入流量（那不是 session）。
3. **不符檔案的「路徑清單」與「人類可讀描述」分開回傳**（`_GateFailure.mismatched_paths`／`_check_files` 回 tuple）——從描述字串反解路徑會依賴訊息格式，訊息一改 forensic 就靜默失準。

**確認不需要動的兩件事（派工單列為「若…則…」的分支，實查後都不成立）**:
- **DB 無須 migration**：`integrity_tamper_events.detail` 本來就是 JSONB（psql `\d` 確認）。
- **LC 端無須調整**：`report-tamper` 的 `detail` 收 `ma.fields.Raw`、schema `unknown=EXCLUDE`，實打真 LC dev 回 200 且新欄位完整落庫。

**驗收（全部實跑，非模擬簽章）**:
1. 用 LC DEV 真私鑰簽的 manifest（kid `2ce3bb59a6f5a2ce`）：改檔 → 拒啟起殼；`curl /` 與任意路徑得三欄 JSON、`Accept: text/html` 得 HTML 頁（三值渲染正確）、`OPTIONS` 得 204＋preflight 標頭 ✅
2. FS 標記存在（檔案已還原、只剩標記）重啟 → 同樣起殼，三欄取自標記 ✅
3. **完整解鎖循環**：以真私鑰簽 unlock token → 放進標記目錄重啟 → 核銷成功、標記清除、**殼不起**、gate 往下跑完 manifest 比對、正常啟動（exit 0）✅
4. forensic 三落點：FS 標記含四欄 metadata；FS→DB 補同步後 psql 查得 detail 完整；D9 payload 先經 mock 端點原樣落檔確認擴充有送出，再以真 LC dev 打通（回 200，`tamper_events.detail` 查得新欄位）✅
5. 三種來源分級各自實跑：boot（`log_tail` 取回 50 行，最後一行與 `tail -1 log/app.log` 逐字相同）／scheduler（接真 DEV DB 得具名快照 `Billows Admin`＋IP＋時間；無 DB 時落 `unavailable` 不 raise）／hook（真 Flask request context＋真 `UserContextDTO`，得帳號與 XFF 首段 IP）✅
6. socketio 模式：`RUN_MODE=socketio SOCKET_PORT=18102` → 殼綁 18102、api port 空著 ✅
7. 測試：新增 22（lockdown）＋18（forensic）條，integrity＋license＋manifest 共 **295 綠**。**突變驗證**：lockdown 六項（error_code／狀態碼／CORS 標頭／port 分流／內容協商／起不來回傳值）、forensic 六項（mtime 取值／owner 欄位／分級條件／XFF 首段／stat 容錯／上限）、log tail 四項（boot 不含 log_tail／不 seek 整檔讀／失敗回空清單／50 行上限失效）、wiring 三項（gate 不掛 forensic／runtime 不掛 metadata／gate 拒啟不起殼），逐一驗紅後還原。

8. **ddd-compliance-reviewer 掃過本棒 diff**：PASS，唯一 Warning 即上述 log tail 落差，已補（`aeb2bd44`）。其餘（套件 stdlib-only 約束、lazy import＋全域 except、無憑證誤入、`INTEGRITY_503001` 不走 `common/code/` 是 D11 明文的合理例外）皆判合規。

**測試資料已清除**：DEV `integrity_tamper_events`、LC dev `tamper_events` 與臨時 api_token 皆已刪，count 回 0（已驗）。

**教訓**:
1. **「測試全綠」可能是環境巧合**：首次跑 gate 測試 0.57s 全綠，實情是本機 BE 佔著 port 8000 讓殼起不來、退回 SystemExit——port 空著時整份 suite 會被 `serve_forever()` 卡死。凡是新引入「會阻塞的東西」，要先問「為什麼它沒卡住」而不是「綠了就好」。
2. **只攔不斷言＝白攔**：把 `enter_lockdown` monkeypatch 掉之後，wiring 突變（整行拿掉起殼）測試照樣全綠。fixture 改成記錄呼叫參數並斷言「拒啟必起殼＋三欄傳對」才有牙齒。
3. **`load_dotenv()` 相對呼叫檔解析**（第 5 棒已記過，本棒又踩）：scratchpad 驗證腳本讀不到 repo 的 `.env`，engine 靜默用預設值連 localhost:5432，錯誤訊息指向 socket 而非設定。臨時腳本一律帶絕對路徑。
4. **合棒要先想 commit 邊界**：兩張卡的改動落在同一批檔案，第一次 commit 時把 forensic 的 hunk 一起 add 進 T-8.1。未 push，`reset --soft` 後拆成「T-8.1 只含殼」「T-8.3 只含 forensic」兩支，各自單獨可編譯可測。合棒時應在動手前就規劃好切分點。
5. **派工單的簡述 ≠ 規格，要回 design 逐條對**：派工單 T-8.3 寫 boot 分級「僅檔案 metadata（無 session 可查）」，照著做就漏了 design §4.4.2 表格明列的「＋`app.log` 尾段」。被 ddd-compliance-reviewer 抓到才補（`aeb2bd44`）。派工單是導引，design 才是驗收基準。
6. **突變測試也會寫出假的**：驗「大檔只讀檔尾」時第一版斷言「最後 50 行對不對」——把 `seek` 整段拿掉照樣全綠（整檔讀進來再切最後 50 行結果一樣）。這條測的是記憶體行為，就得斷言實際讀進的 bytes 數。突變沒紅時要先懷疑斷言，不是慶幸。

**推翻了什麼**: 無。派工單裁定（殼只在 gate 拒啟路徑起、runtime 維持 exit）與 design §4.4 一致，未偏離。

**留給下一棒**:
- 本棒全在**源碼模式**（`GUIDANT_INTEGRITY_FORCE=1`＋臨時 dist）驗過，**真 Nuitka dist 與真容器未跑**——併入「真產物集中驗證」。
- **BE↔FE 整鏈端到端未跑**（真殼回 503 → 真瀏覽器打真 API → FE 跳鎖定頁）。T-8.1/T-8.2 兩端都已落地，條件成立。
- 殼在真部署承載下的邊界：容器 healthcheck／LB 打到殼會拿 503（預期，但未觀察 orchestrator 反應）；若正式環境有殘留 process 佔 port，殼起不來會退回 exit（不放行，但也沒有鎖定頁）。
- 零新增環境變數（`PORT`／`SOCKET_PORT`／`RUN_MODE` 皆既有），`.env.sample`／`deployment-env.md` 無須異動。

---
## 第 7 棒 — 2026-08-16 午～傍晚 — 第二任首腦（協調者，驗收＋D11 拍板＋部署定式）

**commits**: BE `e1190f86`（locale）／`4e64ca07`（殼協商修）／`2d56edb6`（compose）／`f929abe7`（build_all）／`4c06ddc8`（scripts 盤點）＋docs 若干，皆已 push。LC/FE 無新 commit（第 2~5 棒的已入）。
**Notion**: CM-1231~1234 驗收完（修正待驗證）；新開 CM-1236（scripts 盤點✅）/1237（build_all✅）/1238（compose✅）/1239（解鎖 SOP，未派）。

**做了什麼**:
- 真產物集中驗證首通：188 真 dist smoke 七項＋gate 真驗章（1304 檔）＋改檔拒啟三段（改→拒／改回仍拒／清標記恢復）
- 抓到兩個出貨阻斷 bug 並修復驗證：①ASCII locale 下 gate 印中文炸 UnicodeEncodeError（e1190f86：stdout errors=replace＋image 釘 C.UTF-8）②殼內容協商 UA 嗅探讓 XHR 拿 HTML、FE 解不出 error_code 不跳鎖定頁（4e64ca07：只看 Accept）——後者是 user 真瀏覽器實測抓到的
- D11 拍板（lockdown 殼＋forensic 存證＋行為人不可知邊界）→ 四棒派出並親驗全過（詳第 2~5 棒 block）
- 部署定式化：docker-compose（含 pki volume 補洞——原 run 指令漏掛，rm 容器會洗掉鎖定標記）＋build_all.sh 一鍵管線；188 已用 compose 起正式容器（read_only 生效，root 也寫不進 /app）
- 188 實環境驗過：正式容器 gate 通過、D4 唯讀、locale C.UTF-8、pki 掛載；user 實測竄改→鎖定→FE（抓到殼 bug）→清標記復原
- LC 部署四步走完（188 STG：unlock 簽發＋tamper 接收，migration 至 head 49629d9e5d99）

**決策**:
- D11（lockdown 殼 B 方案）＋forensic 證據規格＋契約措辭建議（綁「完整性驗證失敗」不綁「查明行為人」）——design §2/§4.4 已落
- unlock 核銷通道＝FS token 檔（放標記同目錄），不走 HTTP（被鎖時服務起不來）
- 排程決策：T-8.1/8.3 等主鏈完成後合棒（同檔衝突）；T-8.2 FE 契約定死先行——實證有效
- 選 model 按任務認知形狀三軸（裁量/整合/錯誤隱蔽），不再一律最高階——已落 memory

**教訓**:
1. 內容協商不能看 UA——XHR 的 UA 恆為瀏覽器，零鑑別力；活體驗證只 curl 過 JSON 路徑沒模擬「瀏覽器 UA＋JSON Accept」組合，讓 bug 溜到 user 面前
2. compose 的 read_only 讓「容器內改檔測竄改」不可行——測竄改一律用拋棄式可寫容器＋隔離 pki 目錄（/tmp/pki-test），別打正式落點；user 誤打正式落點後標記殘留 /srv/guidant/pki，復原＝清標記＋compose 重起
3. push 是 user 專屬動作（Claude 無權限，能力問題非授權問題）——已落 memory
4. deployment run 指令漏掛 VOLUME 宣告的 pki——Dockerfile VOLUME 不等於部署有掛，匿名 volume 會讓鎖定被 docker rm 洗掉

**推翻了什麼**: 殼的 UA 嗅探分支（520fc5e3 引入、4e64ca07 廢除）；STATE 第 5 棒 Notion 盤點表過期列（5084eeea 更正）。

**在途**: user 正在 188 rebuild（HEAD 4e64ca07，碼正確）——build 完換容器後，「FE 鎖定頁真瀏覽器整鏈」即可複驗。

**留給下一棒（收尾棒）**:
1. 殼修正後的 FE 整鏈複驗（rebuild 完成後：臨時可寫容器佔 8000 → 改檔 → FE 應跳鎖定頁）
2. CM-1229 E2E 五條＋license 迴歸＋4h 抽查真承載（未實證清單見 STATE）
3. CM-1239 解鎖 SOP 文件（卡已開未派）
4. version-bump（含 FR-064 出貨前）＋母案收尾（等 user 令收 Done）

---

## 第 8 棒（T-7.1／CM-1229）— 2026-08-16 傍晚 — E2E 收口 runner

**commits**: 無程式碼異動（本棒是實測棒）；docs 本 block＋STATE 更新（帶 CM-1229）。
**Notion**: CM-1229 → 修正待驗證（A/B/E 成果成立，A-2 FAIL 如實在卡、A-4/C-1/24h 留複驗輪，卡不收 Done）；**新開 CM-1241**（BUG-1 修復棒，調查與證據已轉載）。

**做了什麼**（真 image 1.14.0／HEAD 4e64ca07，全程拋棄式可寫容器＋`/tmp/pki-test` 隔離落點，正式 compose 容器與 `/srv/guidant/pki` 未受影響）:
- **🔴 抓到 BUG-1（P0 出貨阻斷，本棒最大產出）**：真產物×gunicorn 下 **hook 觸發 tamper 後服務沒死透**——FATAL 印了、FS/DB 落點都寫了、fingerprint/event_id 正確，但 master 存活並 respawn，服務照常回 200；同一竄改重現 3 次（3 event／3 FATAL）服務都活著，容器全程 healthy、RestartCount=0。根因定位 `runtime_check._terminate_process()`：容器內 PID1＝gunicorn master、worker 與 master 同 PGID=1（killpg 確實送達），但 master 本就 catch SIGTERM（`/proc/1/status SigCgt=0x108314e07`，bit15 已設）走自己的 shutdown handler，實測反而 respawn。**交 CM-1241 修，本棒不修**（驗的人不修）。
- A-1 改檔拒啟 PASS（改 manifest 內 `.mo` 一 byte → 拒啟＋列不符檔＋fingerprint/event_id；`.integrity-tamper` 5928B 含 T-8.3 forensic 三段：file_metadata／trigger_context.source=boot／log_tail）
- **A-1b 殼協商修正（4e64ca07）BE 側複驗 PASS**——瀏覽器 UA＋`Accept: application/json` 回 `503 application/json` 帶 `INTEGRITY_503001`＋三欄 data（**正是修正前錯回 HTML 的組合**）；`Accept: text/html` 回鎖定頁
- A-3 重啟被拒 PASS（restart 直接認標記不再逐檔比對；**`docker rm -f` 重建容器→仍拒啟、同一 event_uid**，FS 落點跨容器生命週期生效）
- A-5 縮短版 PASS（乾淨容器 gate 1304 檔通過＋多輪 API 操作零誤報；24h 保留未實證）
- **B License 迴歸 PASS**：三環境三把 kid（DEV `2ce3bb59`／STG `04b1e65f`／POC `f3b562a6`）真實已簽發照全部驗章通過（皆止於商務層 400006/409002），**兩條負控**（簽章改 1 byte／payload zlib 解壓改內容再壓回）皆 `LICENSE_400002` → 證明不是「什麼都放行」。四張真照 payload 皆**無 `payload_type` 鍵**，走「缺鍵視為 license」路徑 → **D2 抽層對已出貨 license 零破壞**
- **E orchestrator 觀察完成**：鎖定殼被判 unhealthy（failing=4）但 **Docker restart policy 只看 process 退出不看 health → 不進重啟迴圈**，殼穩定停在鎖定態回 503（符合預期）

**決策**:
- BUG-1 開新卡 CM-1241 交修復 runner，**驗的人不修、修的人另驗**（首腦裁定）
- C-1（源碼模式縮短間隔×兩承載）**併入 CM-1241 開工第一步**——它回答「scheduler 路徑是否同病」，是決定修法範圍的關鍵輸入，不在本棒跑
- A-4 unlock 真循環延到 BUG-1 修完的複驗輪（unlock 的意義是「解除鎖定」，而 BUG-1 動搖的正是「鎖定是否成立」這個前提）
- B 的 license 素材從三環境 DB 唯讀 SELECT 取真照打 DEV 容器——比自簽假照更有迴歸價值，且不違反環境紀律（只讀）

**教訓**:
1. **docstring 的終止假設沒被任何測試驗過**——`_terminate_process()` 檔頭洋洋灑灑寫明「killpg 讓 master 與所有 worker 同組收到終止……只 `os._exit` 的話 worker 死了 master 會再 respawn」，把失敗模式都預想到了，卻沒有任何測試在**真承載**上驗證這個假設；既有測試 monkeypatch 掉 `_terminate_process` 只驗落點副作用，等於把最關鍵的一環整個跳過。寫得越詳盡的設計註解越容易讓人以為「已經想清楚＝已經成立」。
2. Nuitka 產物的埋點選檔會**退化成只驗 binary**——`_HOTPATH_PRIORITY_PREFIXES`（`common/license/`、`common/integrity/`、`common/authz/`）在真產物中不存在為獨立檔（全編進 binary），manifest grep 三個前綴皆零命中。埋點實際只驗 binary 一檔，與源碼模式行為差很大，設計時的「個位數檔案」預期在真產物是「1 檔」。
3. 運行中的 binary 用 `printf >>` 改不動（`Text file busy`），但 **unlink+rename 可以**（Linux 語意）——測真產物竄改要用「複製→改→mv 換掉」，否則會誤以為 binary 天然免疫。
4. 拋棄式容器沿用 `guidant.env` 會指向 **STG DB**（該庫無 `integrity_tamper_events` 表，符合環境紀律）——要驗 DB 落點必須 `-e DB_NAME=guidant_ai_dev` 覆寫；啟動 gate 本就不寫 DB，所以 A-1 不受影響，但 C-2 需要。
5. **K8s 下 E 的結論會反過來**：liveness probe 失敗會 kill+重啟 pod，鎖定態變 CrashLoop。compose 落地版目前不受影響，FR-065 若上 K8s 需把鎖定態排除在 liveness 之外。

**推翻了什麼**: 「運行中偵測→服務立即終止」在真產物 gunicorn 下**不成立**（design §4.6／D8 的承諾、`_terminate_process` docstring 的三種承載死法宣稱，至少 api+gunicorn 這條是錯的）；STATE 原「抽查真承載未驗」一項的隱含前提（以為只差時間窗，實為機制本身有洞）。

**留給下一棒**:
1. **CM-1241 修 BUG-1**（開工第一步跑 C-1 定範圍；修完需複驗 C-2 死透／C-1 兩承載／A-4 unlock 真循環）
2. CM-1239 解鎖 SOP 文件（卡已開未派）
3. version-bump＋母案 CM-1188 收尾（**BUG-1 未修不得出貨**）

---

## 第 9 棒（CM-1241）— 2026-08-16 — BUG-1 P0 修復 runner

**commits**: `7cc7989e` fix(CM-1241) tamper 終止改依承載送訊號（`common/integrity/runtime_check.py`＋`main.py`＋`test/test_integrity_runtime_check.py`）。未 push（等 user）。
**Notion**: CM-1241 → 修正待驗證（含根因更正、修法、各承載證據、未實測假設）。

**做了什麼**:
- **先跑 C-1 定範圍（首腦指定工序）**，但改用**容器化 harness** 而非 188 源碼環境：真 gunicorn 26 pre-fork、PID1=master、呼叫**真的** `_terminate_process`，比改 `SPOT_CHECK_INTERVAL_SECONDS` 等 60s 更快且不佔 188。**C-1 結論：scheduler 路徑不同病**——master 執行緒自己呼叫時 `os._exit(1)` 直接讓 PID 1 退出，容器 exit=1 死透（修前修後皆然）。
- **🔴 推翻卡上的根因假設（本棒最大產出）**：卡與 docstring 都寫「killpg 送達了 master，但 master catch SIGTERM 走 graceful handler 沒死」。**實測：master 根本沒收到訊號。** 容器內 pgid=1，而 `killpg(1,sig)` ＝ `kill(-1,sig)` 是 POSIX **廣播特例**，Linux 明文排除 PID 1。真 image 1.14.0 內 perl 探針（image 無 python）：`killpg(-1,TERM)` **送達對象數=0**／`kill(1,QUIT)` 送達=1 handler 有跑／`kill(1,KILL)` 回傳 1 但 **PID1 仍存活**（核心忽略無 handler 的訊號）。
- **連帶推翻卡上的候選修法**：「SIGTERM 改 SIGKILL（不可 catch 必死）」對 PID 1 **無效**，改下去會從「不死」變成「還是不死且更難查」。
- 修法：`_terminate_process` 改**依承載分流**——gunicorn worker 指名對 master 送 **SIGQUIT**（有 handler 故 PID1 收得到；quick shutdown 直接 halt 不回主迴圈故不 respawn）；容器 PID 1 與非 leader 不送訊號只 `os._exit(1)`；bare metal 僅在 `pgid == pid`（自己是 group leader）才 killpg，保住 shell 不被波及。master pid 由 `main.py` post_fork 呼叫 `register_gunicorn_master(server.pid)` **登記**而非猜。送訊號決策抽 `_termination_signals()` 純函式以便測試。

**驗證**（真 image 根因實證 ✅／harness 行為實證 ✅／真 image 端到端 ⏳ 待 push）:
| 項目 | 結果 |
|---|---|
| 真 image 1.14.0 三種送法可達性 | killpg=0 對象／kill(1,QUIT)=1 且 handler 跑／kill(1,KILL) PID1 存活 |
| harness hook 路徑（修前） | 復現：worker 死、master respawn、ping **200** |
| harness hook 路徑（修後） | master `Handling signal: quit` → `Shutting down: Master` → 容器 exited、ping **000** |
| C-1 scheduler（修前／後） | 皆 exit=1 死透（非同病） |
| C-1 Flask dev server 單 process | 死透 exit=1 |
| bare metal | 子進程被 killpg 收掉、**呼叫端 shell 存活**（/proc 掃描確認） |
| 負控（拿掉 post_fork 登記） | 舊病完整復現 ping 200 → 證明修正承重 |
| 單元測試 | +6 支；**三次突變**（SIGQUIT→SIGTERM／移除 PID1 守門／移除 reparent 守門）各自翻紅、還原全綠 |
| 迴歸 | integrity 全套 **133 passed**；乾淨容器多輪請求零誤殺 |

**決策**:
- C-1 改用容器 harness 而非 188 源碼環境＋縮短間隔：同樣回答「scheduler 是否同病」，但**更貼近真相**（真 PID1／真 pre-fork，源碼模式在 shell 下 pgid≠1 反而測不到本 bug 的關鍵條件），且不佔 188、不需臨時改寫死參數。
- 選 SIGQUIT 不選 SIGTERM：後者走 graceful 要多等 `graceful_timeout`；不選 SIGKILL：對 PID1 無效（實測）。
- master pid 用 post_fork 登記不用 `getppid()` 猜：猜錯時 bare metal 會把使用者 shell 當 master 殺掉。

**教訓**:
1. **「訊號送出去」≠「送到了」**——`os.killpg` 不 raise 不代表有對象收到（本例回 ESRCH 或 0 對象）。診斷訊號問題要驗**接收端**有沒有收到，不能只看發送端 syscall 成功。上一棒讀 `/proc/1/status` 看到 `SigCgt` bit15 已設，正確地證明了「master 有 handler」，卻被誤讀成「master 收到了訊號並用 handler 處理」——**有能力接收 ≠ 實際收到**。
2. **PID 1 在容器內是特殊公民**：`kill(-1)` 廣播排除它、沒註冊 handler 的訊號（含 SIGKILL）核心直接忽略。任何「送訊號讓服務死」的設計，在容器內必須針對 PID 1 單獨驗證。
3. 承接上一棒教訓 1 的下一層：docstring 假設沒測會錯，而**卡片上「已定位的根因」同樣是假說**——本棒若照卡直接改 SIGKILL 就會交出一個看似合理、實則無效的修正。派工卡的根因段要當**待驗假說**讀（[[feedback_upstream_hint_is_hypothesis_not_conclusion]]）。
4. 真 image 無 python，但**有 perl**——要在出貨產物內做系統層探針時 perl 是可用的後路。

**推翻了什麼**: CM-1229／CM-1241 卡上的根因（「master 收到 SIGTERM 但走 graceful handler 沒死」）與候選修法（SIGKILL）；`_terminate_process` 原 docstring 三種承載死法的整段敘述（已改寫並保留錯誤假設的說明，避免後人重蹈）。C-1 待答項銷案：**scheduler 路徑不同病**。

**留給下一棒**:
1. **真 image 端到端複驗（唯一未完項）**：需 user push `7cc7989e` → 188 pull → rebuild image → 重現 CM-1229 C-2 步驟（unlink+rename 換 binary → 打 `/api/1.0/license/upload`）→ 應**服務死透**（master+worker 全滅、容器 exit、不 respawn）→ restart 被 gate 攔、殼回 503
2. A-4 unlock 真循環（BUG-1 修完前提已恢復，可接著跑）
3. CM-1239 解鎖 SOP 文件；version-bump＋母案 CM-1188 收尾

---

## 第 10 棒（CM-1242）— 2026-08-16 — BUG-2 P0 修復＋A-4／socketio 複驗 runner

**commits**: `19a086af` fix(CM-1242) 掛載宿主 `/etc/machine-id`（`docker/production/docker-compose.yml`＋`deployment-env.md`）。未 push（等 user）。
**Notion**: CM-1242 → 修正待驗證（含修法／綁機回歸評估／V1–V5 證據／未實測假設 6 條）。

**做了什麼**:
- **修法選項 A 落地，code 零改動**：compose `x-guidant-common` volumes 加 `- /etc/machine-id:/etc/machine-id:ro`（anchor 共用，api+socketio 兩 service 同時生效）。**先驗掛載是否足夠再決定要不要動 code——足夠，故 `machine_fingerprint.py` 一行未改**（三級 fallback 邏輯本身沒錯，錯的是部署沒把宿主 machine-id 帶進容器）。
- `deployment-env.md` 同步：三處 `docker run` fallback 各補一行、§4 掛載表六→七列、新增「宿主必須有 machine-id」前置檢查段（含 `test -s` 檢查與「machine-id 重生成會讓既有綁機憑證全失效」警語）。
- **既有 license 綁機回歸評估（唯讀 SELECT，未改資料）**：DEV 25 列中 16 列綁舊指紋（`4a837148…`×15＝舊測試容器 MAC、`f2ef9354…`×1＝**開發者本機 MAC** 推導值，已用 `sha256("mac:"+getnode())` 比對確認），但**全部 `is_current=false`**；三張現行照皆未綁機。STG 3 列／POC 1 列皆 null。**結論：無回歸，不需資料遷移**（真要處理也該是向 LC 重簽，改 DB 會讓照內簽章與 DB 值不一致）。

**驗證**（188 拋棄式容器；🔴 正式容器與 `/srv/guidant/pki` 全程未碰）:
| 項目 | 結果 |
|---|---|
| V1 跨 `docker restart` | MAC `e2:5c:9a…`→換，指紋 `ff793254…a3da080` **不變** |
| V2 跨 `docker rm`+重 run | MAC `a2:83:dc:f7:ba:78`（全新），指紋 **仍相同** |
| 錨點驗證 | `sha256(宿主 machine-id d478e208…)` ＝ `ff793254…` ✅ |
| **負控（不掛載）** | restart 前後 `67c6dd9d…`→`63572f6d…`，**bug 完整復現**（證明修正承重） |
| V3-① 觸發 | unlink+rename 換 binary→打 `/license/upload`→**服務死透**（`Handling signal: quit`→`Shutting down: Master`、容器 Exited、ping 000）← **順帶端到端證實 CM-1241 BUG-1 修正在真 image 1.14.0 上成立**（第 9 棒留的唯一未完項銷案） |
| V3-② 鎖定態 | restart→gate 攔、lockdown 殼 503、訊息含 fingerprint+event_id |
| V3-③ 負面案 **4 條** | 他機／過期／錯事件編號／重放 **全擋**，四種訊息各異 |
| V3-④ 正解鎖 | 核銷成功、標記清、nonce 入帳、token 用完即刪、ping 200；**receipt→scheduler→DB 回填鏈完整**（DB 實查 `unlocked_at` 非空、`unlock_nonce=fe6e3cbc40d1…`） |
| V4 迴歸 | 乾淨容器 healthy 無誤鎖；integrity+license 指紋測試 **131 passed** |
| V5 socketio 死透 | PID 1／eventlet／APScheduler 條件下 `_termination_signals()` 回 `[]`、走 `os._exit(1)`、容器 **Exited(1)**、ping 000 |

**決策**:
- **能不改 code 就不改**：先實測「掛載是否足夠」再決定，結果足夠。改 `machine_fingerprint.py` 的空檔 fallback 屬另一議題（建議做成「打包模式落到第三級時警示」而非拒啟——拒啟會讓沒有 machine-id 的極簡宿主完全裝不起來），不在本卡範圍。
- **過期 token 用 LC 私鑰直簽而非 CLI**：CLI `--valid-hours` 有 1–336 下限（本身是正確的防呆），改用 `sign_payload` 直接造「真簽章但 `expires_at` 在過去」的 token——這樣驗到的是**核銷的效期關**，不是「簽發端擋住了」，兩者是不同的東西。
- **V5 用 harness 而非真 image**：socketio 模式不載 REST blueprint（`main.py` 設計），8002 上沒有埋點端點；抽查 4h 週期且參數寫死（D8 刻意不開放 env）。故重現承載條件（容器 PID 1＋`monkey_patch(all=False, socket=True)`＋`socketio.run`＋`BackgroundScheduler`）但**執行 mount 進來的真源碼**（`PYTHONPATH=/src`→`/opt/guidant_ai`），非複製品。承接第 9 棒同一手法。

**教訓**:
1. **「容器內有這個檔」不等於「這個檔有內容」**——`/etc/machine-id` 在 Debian base image 是 **0 bytes 佔位檔**，`os.path.exists()` 為真、`open().read().strip()` 為空。三級 fallback 的 `if value:` 寫得完全正確，卻因為第一級「存在但空」而靜默降級到最不穩的第三級。檢查「來源可用性」時，存在性與有效性要分開驗。
2. **設計註解寫對了假設，不代表部署實現了它**——`machine_fingerprint.py` docstring 白紙黑字寫「正式 Host 版部署預期跑在有 `/etc/machine-id` 的 Linux 容器宿主上」，這句話**是對的**（宿主確實有），但沒有人把宿主的檔帶進容器。這與第 8 棒教訓 1（docstring 假設沒被測過）同源：假設寫得越清楚越容易讓人以為它已成立。
3. **掛「檔」的陷阱**：`-v /etc/machine-id:/etc/machine-id:ro` 在宿主缺檔時 docker 會**建成目錄**，容器讀檔失敗→靜默退回 MAC→**症狀與完全沒掛一模一樣**。凡是掛單一檔案的 bind mount，文件都要附「宿主須先存在」的前置檢查。
4. **綁機類欄位查回歸要看 `is_current`**——`tenant_licenses` 是 replace 制，歷史列會累積一堆舊指紋，只看「有幾列綁了指紋」會嚇死自己（16 列！），實際受影響的只有現行列（0 列）。
5. **驗負面案要驗到「該關」而不是「任何一關」**——過期案若用 CLI 的 `--valid-hours -1`，擋下來的是簽發端參數防呆，核銷的效期關根本沒被執行到。負面案要確保失敗發生在你想驗的那一關。

**推翻了什麼**: 無推翻（卡上根因與修法選項 A 皆經實測成立，是少見的「卡寫對了」）。銷案兩項：第 9 棒留的「真 image 端到端複驗」（V3-① 順帶完成）、STATE 未實證清單第 1 項的 ③A-4 unlock 真循環＋socketio 承載。

**留給下一棒**:
1. **正式／STG／POC 套用 machine-id 掛載**（需 `docker compose up -d` recreate 兩容器）——**等決策者放行**，本棒只改 repo 檔
2. STATE 未實證清單餘項：scheduler×真 binary×4h 真窗、≥24h 長跑（A-5 完整版）、K8s liveness 下殼會 CrashLoop（FR-065 議題）
3. socketio 真 image 完整竄改→死透鏈（低風險，比對邏輯與 api 共用同一份 `runtime_check`，但未實跑）
4. CM-1239／CM-1240 等 user 收 Done；version-bump＋母案 CM-1188 收尾（**BUG-1／BUG-2 皆已修，出貨阻斷解除**）

## 第 11 棒 — 2026-08-16 傍晚～夜 — 第三任首腦（收尾棒）

**commits**: BE `29fead0`(FE CM-1240 details 開關，FE repo)／`19a086af`(BUG-2 fix)／`7cc7989e`(BUG-1 fix)＋docs 若干，皆已 push 三 repo 與 origin 同步。

**做了什麼**:
- 接手驗收：FE 鎖定頁真瀏覽器截圖複驗（user 親見）；CM-1240 開卡→FE 指紋/時間預設隱藏（?details=1 才顯示，離線 unlock 鏈不斷）親驗收 Done
- 派 CM-1229 實機驗收棒 → A/B/E＋A-1/A-3/A-5 縮短版全過、B license 三環境三 kid 迴歸過，**抓到 BUG-1 P0**（hook 觸發不死透）
- CM-1229 卡名消歧（「E2E」→「實機驗收測試（人工實測非自動化碼）」）＋補白話段（user 反映看不懂術語）
- 派 CM-1241 修 BUG-1 → runner 更正根因（非「master 收訊號不理」而是 killpg 打不到 PID 1）→ 首腦親驗實碼＋自跑 integrity 157 passed
- CM-1241 複驗輪本體親跑：真 image C-2 死透（log 見 master 收 SIGQUIT→Shutting down 不 respawn）＋接力鏈（restart→gate 攔→殼 503）；跑 A-4 unlock 時**抓到 BUG-2 P0**（指紋 restart 就變）
- 開 CM-1242（卡內含完整執行指令，派工只給卡號）→ runner 修（compose 掛宿主 machine-id，code 不動）→ 首腦親驗：親算 sha256(宿主 machine-id) 對上容器指紋、親查 config.tenant_licenses 綁機無回歸、188 host 模式容器 restart 前後指紋不變
- user 重啟 188 → 首腦驗正式部署：HEAD 66f6dea8、image 含兩 P0、掛 machine-id、gate 1304 檔、指紋穩定
- 收尾：34 卡全 Done、SUMMARY、兩條 P0 memory、STATE/LOG 收尾標頭、母案回寫

**決策**:
- 派工模式改「工作內容全寫進 Notion 卡、派工只給卡號」（user 明示不要重複貼長 prompt）
- 卡名寫「做什麼」不寫代號、術語表前放白話段（user 反映看不懂）
- 不進版（現標 1.14.0）——FR-064 後面還有 FR-065，里程碑確定才進版
- CM-1242 把 A-4 unlock＋socketio 死透併入（指紋修好才跑得完 unlock）

**教訓**:
1. killpg 打不到容器 PID 1（POSIX 廣播特例排除 init）——已落 [[feedback_killpg_cannot_signal_container_pid1]]
2. 容器 /etc/machine-id 空檔→指紋退回隨機 MAC 每次重啟就變——已落 [[feedback_container_machine_id_empty_fallback_mac_drifts]]
3. 「實機驗收/end-to-end」與「自動化 E2E 測試碼」是兩回事，卡名用縮寫會誤導 PM——收尾把 CM-1229 改名並補白話
4. 首腦親跑複驗（非全外包）在本棒兩度直接抓到 P0——C-2/A-4 是首腦本體跑的，BUG-2 就是這樣現形；驗收「親驗不信自報」延伸到「關鍵鏈自己走一遍」

**推翻了什麼**: BUG-1 原卡假設「master 收 SIGTERM 走 graceful 沒死」（實為根本沒收到）；`_terminate_process` 原 docstring 的 killpg 宣稱（已改寫）；「docker restart 不換 fingerprint」的 SOP 前提（僅宿主有穩定 machine-id 時成立，容器需掛載）。

**留給下一棒**: FR-064 已收口。續戰線＝FR-065 installer（FE image/compose）；出貨前待辦見 SUMMARY §10（進版／推 Harbor／正式 PUBLIC_KEYS 鑰／K8s liveness 排除鎖定態）。
