FR-062 License 控管機制 — LOG(append-only,每棒追加一 block)


§1

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 已備)。


§2

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)。


§3

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 6f94cc34activate_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 收尾清單)。


§4

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_licenseextend_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_stglicense_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 遺留模組退役。