# FR-062 License 控管機制 — LOG（append-only，每棒追加一 block）

---

## Block 1 — 2026-08-08 首腦棒（需求討論 → 設計定稿 → 開卡 → 交接）

**角色**：分析決策首腦（協調者）。實作零行，文件全外包 subagent。

**Commits（BE repo，全部已 push）**：
- `34f09eea` design.md 定稿 + FR 登記 + discussion v2 + 燈箱改動收攏
- `0aee480b` License Center repo 座標 + DB 命名新標準進 CLAUDE.md
- `6e8edb1e` 燈箱功能連帶重 build 既有站
- `cd671edf`~`94b398a6` 八個補收 commit（與本 arc 無關的既有未追蹤檔案，user 確認全收）
- license_center repo：`1cc190c` 初始化基底（已 push）

**決策軌跡（時序）**：
1. 兩路平行探查（repo 現況 + 市場調查）→ 提出 D1–D7 → user 逐項拍板
2. 追加 D8–D15（綁定模型/機器指紋/開通/試用/模組表/通知/過渡/管理位置）
3. discussion v1 產出後 user 連續挑戰九輪 → 九項修訂進 v2：D5 四段開關化、金鑰生命週期（kid 公鑰列表）、簽發端升級 Web 後台、SaaS/Host 開通對照、SaaS 手動停權、補發換綁、Plan 套餐、模組分層（相依實證）、七階段可中斷拆分
4. v2 之後五項最終修正（design.md 為準）：**過渡照整個刪除**（user 點破「產品沒上線何來過渡」，改 migration 直接發正常照）、**自建不採 Keygen**（Rails 棧/無 UI/模型不合）、Plan 存 DB、升級換發立即生效、數字定案
5. 命名定案：repo+DB 都叫 `license_center`；DB 新標準 prod 不帶後綴（user 拍板，未來 guidant_ai 跟進）

**教訓**：
- 過渡照是「既有大量正式客戶」產品的解法，套在未上線產品上是過度設計——被 user 一句「何來過渡期？」點破。**設計時先問「這個機制的服務對象現在存在嗎」**
- 模組相依盤點發現兩個販售前必補技術債：api/grc+api/project 零 capability 守門、GrcJobType enum 寫死——按模組賣不是只做守門，還有「殘缺 UI」處理，套餐切越粗工程越省
- subagent 開卡被中停後，下一支必須先查殘卡再建（本次查了，乾淨）

**推翻了什麼**：D14 過渡照 90 天機制（v2 還在，最終刪除）；D1 純 CLI 起步（升級為初版即做 Web 後台）；「鎖登入」預設段（改為 lockout 預設關、終態唯讀）。

**產出物**：discussion.md v2 / design.md / Notion 25 卡（CM-1112~1136）/ license_center repo 基底 / 主管簡報（桌面）/ 七份派工 prompt（fr062-dispatch-prompts.md）/ CLAUDE.md+memory 更新（License Center 座標、DB 命名新標準）。

**下一步**：依 STATE「執行順序」逐棒發令派工（第 1 棒 = FR-062.1，prompt 已備）。

---

## Block 2 — 2026-08-08~09 第二任首腦棒（全七棒派工驗收 → 開發主線完成）

**角色**：分析決策首腦。實作零行；驗收抽查全部親做（fetch 卡+commit+親跑測試+查 DB）。

**派工軌跡（時序）**：.1 → .2+.6 併派 → T-1.3（v2 打包）→ T-1.4（passphrase+金鑰重產）→ T-6.3（下載+明文顯示）→ T-1.5（.license armor）→ T-0.1（design.md 回寫）→ .3+T-2.4 併棒（.3 曾被 user 中停重派，重派 prompt 加了殘留檢查段）→ .4（Opus 跑）→ T-5.1 → T-6.4（詳情頁+Menu）→ T-7.1 → T-7.3（插做 grace/readonly 信）→ T-7.2（DEV migration）→ T-5.2（交接時在跑）。追加十張卡 CM-1137~1146 全因 user 驗收回饋即時開卡。

**關鍵決策（超出 Block 1 既定範圍的）**：
1. **照檔格式三連改**：v2 打包（user「JSON 內容不想給客戶看」→講清楚簽章≠加密後採壓縮編碼）→ v3 armor（user「裸 json 不專業」→對齊 JetBrains/GitLab/Keygen 的 PEM 風格）→ v2.1 send_email 補旗標。三次都趕在消費者變多前改——「格式改動排在 .3 前/.7 前」的時機判斷是本棒最值錢的排程決策
2. **T-5.2 提前**：user 點破「local 就能模擬雲端驗證」→ 把「開發+驗證」與「公網部署」拆開，延後線變現役棒
3. **.4 抗命案**：實作者查證後拒退役 TENANT_ADMin_EXCLUDED_RESOURCE_TYPES，覆核 DB 實況後接受——衍生 CM-1143 退役案（user 拍板 cruise/resource/report 全案後整鏈移除、dashboard 留、workflow 不動）
4. **通知收件人判定**：三個候選旗標 DB 實查後全殘缺（131 只有 tenant.update capability 收得到）→ 裁三者聯集+root fallback
5. **LC 不導 DDD**：user 問架構，盤了 2,268 行後裁定分層架構+Service Layer 已足，立演進觸發條件而非預先武裝

**教訓**：
- **驗收回饋是需求探勘的主現場**：十張追加卡全部來自 user 實測時的觀察（看不到內容/不能下載/不專業/passphrase 是啥）。首腦價值在把每條即時轉成裁示+卡+prompt，而不是護航原計畫
- **「簽章 vs 加密」「passphrase vs 私鑰」這類概念 user 會反覆確認**——用類比講（印鑑章/保險箱），講到 user 能自己推出下一個問題（「私鑰重發公鑰要換嗎」）才算講懂
- 併行派工要在 prompt 內寫明 repo 劃界（.2/.6 零衝突、T-7.x vs T-6.4 邏輯層/Web 層分界）；被中停的棒重派必加「殘留檢查」開頭段
- 實測資料狀態會被驗收輪汙染（131/152/153 被 T-7.1/7.3 插過短效照）——後續棒的 prompt 素材段改寫「先 SELECT 盤實況再動」而不是給快照
- user 會抓漏：裁決後要主動回填到「產生疑問的那張卡」（CM-1126/1127/1123 補裁決段是被催的）

**推翻了什麼**：Block 1 的「T-5.2 等雲端」（改 local 模擬）；design.md §3「TENANT_ADMIN_EXCLUDED_RESOURCE_TYPES 退役」（改保留，§3 該行仍待改註——**下一棒文件批次要做**）；「LC 發照紀錄=歷史留檔不清」（後依 user 指示清了 7 筆測試垃圾，TRUNCATE RESTART IDENTITY）。

**產出物**：三 repo 開發主線全量 commits（BE 至 `d5e136e3`/FE/LC 至 `2c1eaf2`，全未 push）；migration SOP+護欄 script；Notion CM-1137~1146 十張卡；STATE 全面改寫。

**下一步（給第三任首腦）**：①T-5.2 回報後驗收（負面案例：錯誤序號/已用/斷網 fallback）②派 T-4.5 反灰鋪面（CM-1144，prompt 要點：useLicenseReadonly 已在、參考實作 DeviceManage.vue、逐頁 import 套 writeDisabled、遵守「不能做→disabled 不隱藏」紀律）③催 user 總驗收收 19 張卡④收尾等令（含清 root 測試照、design.md §3 改註、LC CLAUDE.md、spec/手冊/SUMMARY）。

---

## Block 3 — 2026-08-09 T-5.2 線上開通實作 runner（CM-1130）

**角色**：runner（非首腦），直接接手 T-5.2 派工 prompt 執行到完成。

**產出**：三 repo 各一 commit——LC `5a9272e`（新 blueprint `activation.py`，公開 API，序號存在/未用/未過期三查+15分鐘5次失敗鎖定）、BE `6f94cc34`（`activate_license_online()` 代打+完整驗章不信任來源、新端點、`LICENSE_ACTIVATION_SERVER_URL` config）、FE `cb6c29b`（開通頁加線上序號輸入路徑，同頁分隔線區分離線）。

**順手修的既有缺口**：FE `error-code.json`（zh-tw/en）整個 `LICENSE_*` 錯誤碼家族翻譯此前完全缺漏（`.4`/`.7` 落地時就沒補），toast 會顯示原始 i18n key——本棒補齊，非本棒新增碼專屬。

**驗收**：全 local（DEPLOYMENT_MODE=host），瀏覽器實測四案：有效序號一鍵開通成功（LC `license_issuance` 回寫機器指紋+時間+`event_type=activate`）、錯誤序號 404、已用序號 409、關 LC 模擬斷網→引導離線訊息。BE log 逐案對應無誤。

**下一步**：交還首腦驗收＋派 T-4.5（見 Block 2 收尾清單）。

---

## Block 4 — 2026-08-10 首腦棒（62.8/62.9/62.10 三輪追加 → STG/POC 雙環境部署）

**角色**：分析決策首腦。實作與文件大多外包（subagent／開 case），**部署類指令與 DB 操作親做**——部署有順序耦合（keygen ⇄ 公鑰進 code ⇄ BE 重部署）且對 POC 屬 production 級動作，不轉手。

**派工軌跡**：
- **FR-062.8**（角色矩陣 license 維度，承 08-09 驗收回饋）T-8.1~T-8.5 五支，其中 T-8.2 初版被決策者驗收推翻後重做
- **FR-062.9**（手動停權）母案 CM-1172 → T-9.1 BE（CM-1173）→ T-9.2 FE（CM-1174）
- **FR-062.10**（產品後台向 LC 請照）四棒接力：T-1 LC 端（CM-1176）→ T-2 產品 BE（CM-1177）→ T-3 產品 FE（CM-1178）→ T-4 docs（CM-1179）
- **驗收當下追加三卡**：CM-1180／1181／1182 全部源自決策者實測 LC 總覽頁時的觀察（無序號照被誤標未開通、同客戶列散開難讀、資料多了找不到）
- **CM-1183 回歸**：登出後仍打 `license/status` 導致「無效的 token」，兩 commit 修（afterEach 排除未登入態＋登出 reset store；追加一支處理 chatbot 401 時序）
- **部署段（親做）**：STG → POC 依序，每環境走「套 migration → keygen → 公鑰進 BE code → BE 重部署 → LC 起服務 → 建 API token → 請照實測」

**關鍵決策**：
1. **請照走 B 案（產品後台直接向 LC 請照），取代人工搬檔**。原設計圖 4 把 LC 與產品後台畫成同一 participant，實際上 SaaS 續約要 admin 在 LC 簽發下載、再切回產品上傳，兩趟手動。母案列的六項待調查，查證後四項成本近零（既有 `issue_license`／`extend_license` 純函式可直接呼叫、落地漏斗已有四個入口可加第五個、失敗路徑可照抄 `activate_license_online()`），**真正需要拍板的只有認證模型與多環境如何隔離**——把調查做在拍板前，讓決策只剩兩題而不是六題
2. **認證從「shared secret」升級為 token 白名單＋管理頁**（決策者提議）。被排除的是單一共享密鑰：換掉要同時改兩邊、發照紀錄看不出來源、無法單獨撤銷某環境。白名單換到可撤銷（停用不刪列）、可追來源（發照紀錄寫 `issuer=token_name`）、未來多 SaaS 平台接入時統一控管
3. **不做「非 production 只能簽 trial」的強制**。理由：主站管理員即決策者本人，LC 環境未來也分開部署；白名單的目的是管理便利與可追溯，不是防範內部誤用。硬加規則只會在正當需求（POC 要簽正式照做展示）時擋路
4. **三環境獨立簽發鑰**（決策者要求）。被排除的是三環境共用 DEV 鑰（部署最省事，但 dev 私鑰一旦外流即等同正式鑰外流，且發照紀錄無法從照本身分辨來源環境）。代價是每環境多一次 keygen ＋ 一個 BE commit ＋ 一次重部署，接受

**教訓**：
- **passphrase 是私鑰檔本身的解鎖密碼，不是可另行設定的環境變數**。STG 首次 keygen 後在 `.env` 填了一個「想要的」passphrase，結果簽發全部 503——私鑰是用 keygen 當下那組密碼加密的，事後在設定檔改字串不會反向改變檔案。第一把 `ef99f508c74e43fd` 因此作廢重產成 `04b1e65f100c6c8f`。**更糟的是第一版自驗指令漏了 `load_dotenv()`**，於是不論 `.env` 寫什麼都讀不到，把「設定沒載入」誤診成「passphrase 錯」——驗證環境設定的指令，必須先確認它真的載入了設定
- **多環境部署的「認得」是雙向的**：LC 簽的照，要產品 BE 的 `PUBLIC_KEYS` 裡有對應 kid 才收得下。所以每次 keygen 都連動「一個 BE commit ＋ 該環境 BE 重新部署」，順序做錯就是「未知的 kid」。這條在上正式環境時同樣成立——**正式簽發鑰必須先進 `public_keys.py` 才 build 出貨 image**
- **rebind ≠ 重簽**。POC 首發簽出一張 `deployment_mode` 記載錯誤（saas，實際是 host）的照，決策者用 LC 後台的 rebind 想修，但 rebind 的語意是「同一張照換綁定目標」、其餘欄位照抄原照——內容要改只能重簽。功能名稱像什麼、和它實際做什麼，是兩回事
- **預設值會在跨系統邊界靜默生效**。BE 請照時沒帶 `deployment_mode`，LC 端就套自己的預設 `saas`，簽進照裡沒有任何警告。**產品自己最清楚自己是什麼版本，不該讓對方猜**——跨系統呼叫時，凡是「對方有預設值」的欄位都該明確傳，不然預設值就是一個沒人負責的決策
- **既有的錯誤會被新的錯誤蓋住**。修掉 license status 那條 401 之後，chatbot session 清理的 401 才浮現——它是 fire-and-forget 沒 await，一直都在，只是先前被更吵的 toast 蓋著沒人注意。修掉一個症狀後要再看一次，不要假設「剩下的都是新的」

**推翻了什麼**：
- **Block 2/3 的「T-7.2 腳本是既有租戶發照的路徑」**：STG/POC 實際發照改用 FR-062.10 產出的「產品後台向 LC 請照」功能，腳本未動、其 DB 白名單護欄仍只認 DEV。腳本轉為 DEV 演練與離線備援用途
- **STATE 舊的「migration 只套 DEV」**：本棒因決策者當次明示放行，7 支 FR-062 相關 migration 已套進 STG 與 POC。此為**部署動作的一次性放行**，不改變常態鐵律
- **design.md §4.7 圖 4 原「路徑 0」畫法**：LC 與產品 root 後台被畫成同一個 participant，讀起來像內部直通；已於 CM-1179 改為兩個獨立系統經內部 API 交互
- **FR-062.8 T-8.2 初版的「已授予但未生效」打勾設計**：決策者驗收推翻，改為未授權一律反灰空框、差異只留 tooltip（理由：讓勾勾說謊再貼紙條解釋，正是本子需求要消滅的 Jira 型困惑）

**產出物**：三 repo 今日共 37 個 commit（BE 15／FE 13／LC 9，**全部已 push**，HEAD==origin/main）；7 支 FR-062 相關 migration 各套進 STG 與 POC（DEV 早已有）；LC 兩支 migration（001 plans+license_issuance、002 api_tokens）＋三筆 plan seed 進 `license_center_stg` 與 `license_center_poc`；LC 部署手冊 `docs/deployment-guide.html`；LC repo CLAUDE.md；Notion 今日收 Done 18 張（FR-062 主案 7 張含母案 CM-1112、.9 三張、.10 八張）。

**下一步**：收尾文件批次（本檔／STATE／SUMMARY／頁面 spec，三支平行 runner）→ 決策者於 STG root 後台正式發照 → 雲端 LC 公網補驗（CM-1130 清單）→ 全案後 CM-1143 遺留模組退役。

---
