FR-182 · 需求討論稿 v1 · 2026-11-07

Agent 也要有照:比照主系統,先改 License Center

一台 Agent 一張照、綁機器碼、有到期日;沒有功能分級;客戶封閉網路靠離線開通;第一期只做 License Center 端

主系統從 FR-062 起每個租戶都要有一張簽過章的授權照才能用,但 Agent 完全沒有授權檢查——拿到註冊 token 就能裝、能跑、裝幾台都行。決策者 11-07 定調:Agent 要用跟主系統一樣的模式拿照——License Center 簽發、給序號、客戶抄機器碼來開通、拿到綁機的照上傳;Agent 不分功能等級,只分「有照/沒照」;客戶在封閉網路,所以離線開通一定要成立。本稿先把 License Center 端要長什麼樣講清楚,Agent 端與主系統端只定方向。

決策者已裁:比照主系統模式 決策者已裁:Agent 不分功能,只驗有效 決策者已裁:離線開通必做 D1~D8 建議已口頭同意,待看雛形確認 第一期只動 license_center repo

0. 分工概述(30 秒版)

整案一句話:License Center 多一種照叫「Agent 照」,一台 Agent 一張、綁那台機器碼、有到期日、沒有模組清單;員工在 LC 後台簽發拿序號,客戶在 Agent 主控台看到機器碼、把序號+機器碼交回來,員工在 LC 後台離線開通、把照寄回,客戶上傳到 Agent;沒照的 Agent 不向主系統註冊、不跑掃描。

階段 做什麼 產出 完成怎麼判定(決策者檢查法)
P1 License Center(本稿範圍) LC 新模組 agent_licensing:資料表、簽發/開通/換綁/補發/展延、後台五個頁、CLI、兩支機器 API、文件 license_center repo 一版 開 LC 後台 → 「Agent 授權」→ 簽一張(數量 3)→ 看到 3 組序號 → 拿任一序號+隨便一串 64 碼當機器碼走「離線開通」→ 下載到 .license 檔 → CLI agent-verify 驗過
P2 Agent 端 Agent 驗 Agent 照(既有驗章引擎加一種型別)、主控台「授權」頁(顯示機器碼、上傳照、看狀態)、沒照就不註冊不心跳不掃描 evidence-agent 一版 123 的 Agent 刪掉照 → 主控台顯示「尚未開通」+機器碼、主系統 Agent 列表變離線;上傳 P1 簽的照 → 主控台顯示「已授權,到期 2027-xx-xx」、主系統列表恢復上線
P3 主系統端 Agent 列表多一欄「授權到期」(Agent 用既有狀態事件回報);線上開通代打 LC(Agent → 主系統 → LC) BE+FE 一版 主系統「遠端 Agent」頁每台看得到到期日;在 123 Agent 主控台輸入序號按「線上開通」,10 秒內變已授權

依賴一句:P1 獨立可做、現在開卡;P2 要 P1 簽得出照,且要等 FR-181(Agent 長輪詢)與 FR-179(Agent 首次設定頁)的 Agent 卡合回(接點見 §2.4);P3 要 P2 的事件格式。決策者功課:§4 的 D1~D8 看一次雛形後正式拍板。


1. 為什麼要做

1.1 現況:主系統有照,Agent 沒有

主系統(FR-062 起) Agent(現況)
怎麼拿到授權 LC 簽照 → 序號 → 開通綁機器碼 → 上傳照 主系統管理員產一組註冊 token,裝 Agent 時填進去
誰能裝 有照的租戶 拿到 token 的任何人、任何台數
到期 照上有到期日,狀態機:正常→提醒→寬限→唯讀→鎖定 沒有到期概念
驗章 jedi-license-runtime 套件,公鑰編譯進去 已經有:啟動時驗產物清單(common/integrity/verify.py),公鑰同一批、同樣編譯進 binary
機器碼 sha256(machine-id) 已經有:device_uuid = sha256(主機板 product_uuid ∣ machine-id),註冊主系統時就是用它

結論:Agent 已經有驗章引擎、有機器碼、有編譯進去的公鑰,缺的只是「照」這個東西和「沒照怎麼辦」的規則。LC 端則是零——它只認得主系統的照。

1.2 為什麼不把 Agent 台數塞進主系統的照裡

最省事的做法是主系統照的 limits 加一個 max_agents: 5,主系統註冊第 6 台時拒絕。排除理由:

  • Agent 本機沒有東西可驗——它只能信主系統說「你可以」。客戶封閉網路、主系統不通時,Agent 連自己有沒有授權都不知道,離線開通做不出來。
  • 不是「一樣的模式」——決策者要的是 Agent 自己拿機器碼來註冊,跟主系統一樣的體驗。
  • 台數額度綁在主系統照上,客戶加買 Agent 要重簽主系統的照(換照=重新開通一次),麻煩。

所以是獨立一張照,照上記得它屬於哪個客戶、掛在哪張主系統的照底下(可空),LC 總覽看得出「這個客戶買了主系統+3 台 Agent」,到期日各自算。


2. 現況盤點:能複用什麼、缺什麼

2.1 License Center 端

既有能力 位置 本案怎麼用
泛用簽章 sign_payload(payload, key, kid),按 payload_type 分型 core/crypto/engine.py reuse:加常數 PAYLOAD_TYPE_AGENT_LICENSE = "agent_license",Agent 照直接走它簽
驗章 verify_payload(signed, expected_type) 同上 reuse:CLI agent-verify 用
.license armor 包裝 armor_license/dearmor_license 同上 reuse:下載檔格式與主系統照一致
序號產生 new_activation_code()、序號效期 ACTIVATION_CODE_TTL、逾期判斷 modules/licensing/service.py extend:這三個與「照」無關、純序號邏輯,抽到 core/activation_code.py 讓兩個模組共用(不從 licensing 模組 import——鐵律③跨模組只准叫對方 service,而這不是 licensing 的業務)
開通失敗鎖定(來源 IP、15 分鐘 5 次、全站預算) modules/auth/service.py check_activation_lockout reuse:Agent 線上開通端點直接用同一套、同一張 activation_attempts 表
到期狀態推算 compute_status(expires_at, policy) core/status.py extend:Agent 照的到期政策只有兩段(提醒/寬限),加一支 compute_agent_status(expires_at, notify_days, grace_days) 放同檔,不塞 if
私鑰解鎖(env passphrase 優先、表單備援) web/views/issuance.py _unlock_or_flash_redirect extend:搬到 web/unlock_helper.py 兩組 view 共用(原本就只被 issuance 用,搬出來不改行為)
後台登入、CSRF、CSP、log 規範、request_id web/ reuse:新頁照既有規矩掛
Alembic migrations/ reuse:新表一支 revision
四支機器 API 合約凍結 docs/api-contract-baseline.md 不動:Agent 的機器 API 全部是新路徑

2.2 Agent 端(P2 用,本稿只定方向)

既有能力 位置 本案怎麼用
機器碼 device_uuid guidant_agent/platform/fingerprint.py reuse:主控台顯示給客戶抄;LC 存的 machine_fingerprint 就是它
驗章 verify_payload(signed, expected_type) + 編譯進去的 PUBLIC_KEYS common/integrity/ reuse:加 expected_type="agent_license" 就能驗 Agent 照;不裝 jedi-license-runtime(它帶 PG 表、租戶解析、Flask API 一整套,Agent 是單租戶 SQLite,用不上)
主控台(登入、頁面、本機設定) guidant_agent/console/ extend:加「授權」分頁
註冊/心跳背景元件 guidant_agent/sync/app/enroll.py extend:啟動時先問授權狀態,沒照就不起這個元件
Agent 自報狀態事件 agent_status guidant_agent/sync/app/agent_status.py extend:多帶 license 一節(狀態、到期日、照號)

2.3 主系統端(P3 用)

既有能力 位置 本案怎麼用
主系統向 LC 請照(API token、lc-issue) jedi-license-runtime + LC /api/internal/* extend:root 後台加「向 LC 請 Agent 照」(第二期)
主系統代打 LC 線上開通 activate_license_online() reuse 做法:Agent 線上開通由主系統 BE 代打 LC /api/activation/agent/activate,Agent 不直連 LC(FR-181 Agent 只出站、只連主系統)
Agent 列表 遠端 Agent 管理頁 extend:加「授權到期」欄

2.4 與 FR-181(Agent 只出站)、FR-179(安裝精簡)的接點

決策者 11-07 提醒 FR-181 是 Agent 通訊方式大改,要對一次。對完的結論:方向不衝突,P1 零影響;P2 有三個接點要排在那兩案的 Agent 卡之後。

接點 FR-181/FR-179 在做什麼 FR-182 P2 要做什麼 怎麼不撞
「沒照不收指令」的位置 FR-181 把指令通道從 8443 直推改成 Agent 自己長輪詢(新元件 command_pull,跟心跳、設定拉取一樣是 sync 模組的背景元件) 沒照時不註冊、不心跳、不收指令、不掃描 閘門放在「sync 模組所有背景元件起不起」這一層(enroll_for_auth 之前一道判斷),不是逐條通道各加 if。FR-181 多一條長輪詢、以後再多一條什麼,都自動被閘住。閘門不碰 command_pull.py、不碰 registry.py
登記型檔 platform/registry.py FR-181 F-2 是它的 owner(登記 command_pull 元件) 新模組 license 要登記進 registry.MODULES(申報 status_fields 讓狀態事件帶授權欄、申報主控台 blueprint) P2 排在 F-2 合回之後;P2 的卡自己當 registry.py 下一個 owner
主控台未註冊可登入 FR-179 A1:未註冊時放行本機管理員登入與首次設定 API;A2:首次設定頁(填主系統網址、token、註冊) 未開通時主控台可登入、看機器碼、上傳照 同一道「未註冊放行」的門,FR-179 A1 開了 P2 直接用;順序是「授權 → 首次設定(註冊)」:A2 的首次設定頁前面多一步「上傳授權照」,沒照時後面的步驟灰掉。P2 排在 A1/A2 合回之後,首次設定頁的改動在 P2 卡
線上開通的認證 FR-181:Agent 只能出站、所有呼叫帶通行證(註冊後才有) 線上開通是 Agent 發起、同步等主系統代打 LC——出站、同步,形狀合 FR-181 但沒照的 Agent 還沒註冊、沒有通行證,所以主系統的線上開通端點要跟 /agents/register 同一道門:用註冊 token 認證,不是通行證。P3 開卡時寫進去
狀態回報 FR-171 起 Agent 自報狀態走 agent_status 事件,各模組用 registry 申報 status_fields 事件多帶 license 一節 走申報掛點,不改 agent_status.py

FR-181 退役 8443 六個指令 blueprint對本案無關——本案在 8443 上不加任何東西;主控台「授權」頁在 8444。

時序:P1(LC)現在開;P2 等 FR-181 批 2(F-2)與批 5(A-3)、FR-179 批 1(A1)與批 2(A2)合回後再拆卡;P3 等 P2。


3. 設計

3.1 端到端:一台 Agent 怎麼拿到照

%%{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'}}}%%
sequenceDiagram
    participant E as 我們的員工
    participant L as License Center
    participant C as 客戶
    participant A as Agent 主控台
    participant M as 主系統

    Note over E,L: 簽約後
    E->>L: 「Agent 授權」→ 簽發:客戶、訂單、數量 3、天數 365
    L-->>E: 3 張照(未綁機)+ 3 組序號
    E->>C: 交付序號(email 等既有管道)

    rect rgb(238, 242, 243)
    Note over C,A: 路徑 B — 離線開通(第一期)
    C->>A: 裝好 Agent,登入主控台 → 「授權」頁顯示「尚未開通」+機器碼
    C->>E: 序號 + 機器碼
    E->>L: 「Agent 離線開通」:輸入序號+機器碼
    L-->>E: 綁定該機器碼的照(.license)
    E->>C: 回寄照檔
    C->>A: 上傳照
    A->>A: 驗章+核對機器碼+到期日 → 「已授權」
    A->>M: 開始註冊/心跳/回報授權狀態
    end

    rect rgb(226, 240, 241)
    Note over C,L: 路徑 A — 線上開通(第二期,Agent 不直連 LC)
    C->>A: 「授權」頁輸入序號 → 線上開通
    A->>M: 序號+機器碼(走既有出站通道)
    M->>L: 代打 /api/activation/agent/activate
    L-->>M: 綁機的照
    M-->>A: 照
    A->>A: 驗章 → 「已授權」
    end
圖 1 — Agent 照的離線開通(第一期)與線上開通(第二期)

3.2 Agent 照的內容

跟主系統的照同一個信封(Ed25519、format_version 2、armor 後叫 .license),差在 payload:

欄位 值 跟主系統照比
payload_type "agent_license" 新:主系統照沒這個鍵(缺鍵=主系統照,向後相容的支點);Agent 驗章時 expected_type 指定它,主系統的照拿來上傳會被拒
license_id alic- 前綴+12 碼 前綴不同,一眼分得出
customer_code/order_no/issued_to 同主系統 同
parent_license_id 主系統照的 license_id,可空 新:掛在哪張主系統照底下;只是對照用,Agent 不驗它
type formal/trial 少 extension(展延照直接換新照,不另立型態)
issuer/issued_at/starts_at/expires_at 同主系統 同
expiry_policy {"notify_days_before": 30, "grace_days": 14} 簡化:主系統是四段式(提醒/寬限/唯讀/鎖定),Agent 沒有「唯讀」可言,只留提醒與寬限
machine_fingerprint Agent 的 device_uuid(64 碼 hex),開通前空 同(算法不同但 LC 不管算法)
product_major 1 同(FR-167 更新站用,先帶著)
kid 簽章鑰指紋 同
tenant_id plan modules limits 不要 Agent 不分功能、不綁租戶

3.3 LC 資料表:agent_license_issuance

新模組 modules/agent_licensing/,自己一張表,不塞進 license_issuance(那張表 tenant_id/plan_code/modules 必填,塞進去要全改可空、總覽頁與狀態推算到處加「如果是 Agent」分支)。欄位比照 license_issuance 的 append-only 事件帳:

欄位 說明
agent_license_id alic-…,unique
batch_id 同一次簽發 N 張共一個批號,結果頁按批列、打包下載
customer_code/customer_name/order_no 商務識別,同主系統
parent_license_id 可空;存字串、不設 DB 外鍵(跨模組不碰對方的表;簽發表單用 licensing 模組的 service 查清單給下拉)
license_type/product_major 同主系統
starts_at/expires_at/expiry_policy(jsonb) 同主系統,policy 只有兩個數字
activation_code/activation_code_expires_at/activation_code_revoked_at 序號三欄,語意同主系統(token_urlsafe(16) 約 22 碼、30 天效期、補發作廢舊序號)
machine_fingerprint/activated_at/rebind_history 開通狀態,同主系統
issuer/event_type/license_payload/created_at 稽核;event_type ∈ new/activate/rebind/reissue/extend

3.4 LC 後台頁(雛形:prototype/lc-agent-license.html)

導覽列加一項「Agent 授權」,底下五個頁,全部照既有 Tabler 樣式:

頁 路徑 做什麼
清單 /agent-licenses/ 每台一列:客戶、照號、上層主系統照、型態、到期日、推算狀態、機器碼前 12 碼、開通狀態;搜尋客戶代碼/名稱/訂單;篩狀態;列尾下拉:詳情/下載/展延/補發/換綁
簽發 /agent-licenses/issue 客戶代碼、客戶名稱、訂單編號、上層主系統照(下拉,選填,列該客戶現行照)、型態、天數、數量、提醒天數、寬限天數、發照人
簽發結果 /agent-licenses/batch/<batch_id> 這批 N 張:每列照號+序號+下載;「全部下載(zip)」;提醒:序號 30 天內要開通
離線開通 /agent-licenses/activate 序號、客戶機器碼、發照人 → 簽出綁機的照 → 結果頁下載
詳情 /agent-licenses/<id> 明文欄位、事件歷史(簽發/開通/換綁…)、操作:下載(只有現行照)、展延、補發、換綁(換綁人工受理,同主系統政策)

客戶總覽(主系統照那頁)不動,只在客戶時間軸頁加一行「Agent 照 N 台 → 看清單」連過去。

3.5 LC 機器 API(新路徑,不碰凍結的四支)

端點 呼叫端 認證 第一期
POST /api/activation/agent/activate 主系統 BE(代 Agent 打) 無 token,序號+機器碼+既有 IP 鎖定 做(薄殼,跟現有 /activate 同形)
POST /api/internal/agent/issue 主系統 root 後台 X-API-Token 白名單 做(薄殼;主系統端的按鈕是第二期)
GET /api/internal/agent/licenses?customer_code= 主系統 root 後台 同上 做(給主系統查某客戶 Agent 照清單)

回應形狀比照既有:成功 {"license": {…信封…}, "license_id": "alic-…"},失敗 {"error": "…"};驗證順序比照既有「格式 → 鎖定 → 查無 → 已使用 → 序號逾期 → 授權過期 → passphrase」。進 OpenAPI、進 tests/test_api_contract.py 新情境表。

3.6 CLI

agent-issue(同表單欄位,--count N)、agent-activate(序號+機器碼)、agent-verify <檔>、agent-extend、agent-rebind。保底用,跟既有 issue/activate 一樣的形狀。

3.7 Agent 端的規則(P2,雛形:prototype/agent-console-license.html)

照存哪:AGENT_DATA_DIR/license/agent.license(資料目錄,升級不動)。

狀態(Agent 自己算,不問主系統):

狀態 條件 Agent 做什麼 主控台顯示
尚未開通 沒有照、照驗不過、機器碼對不上 不註冊、不心跳、不跑掃描、不收指令;主控台可登入 「尚未開通」+機器碼(可複製)+上傳照
正常 驗過、未到提醒日 全功能 「已授權」+到期日
到期提醒 到期前 notify_days_before 天內 全功能 黃色提示「N 天後到期」
寬限 過期、未超過 grace_days 全功能 紅色提示「已過期,寬限剩 N 天」
已過期 超過寬限 回到「尚未開通」的行為 「授權已過期」+機器碼+上傳照

不擋啟動:啟動閘門(防竄改)維持原樣;授權檢查在閘門之後、起 sync 模組背景元件(註冊、心跳、設定拉取、FR-181 的長輪詢)之前——一道門關掉整個 sync 模組,不逐條通道加 if(§2.4)。擋啟動的話客戶連主控台都進不去、看不到機器碼,開通流程走不下去。上傳照成功後不用重啟:閘門在同一行程內放行、起 sync 元件(照 FR-179 A1 「未註冊 → 首次設定頁註冊」的同一條路)。

機器碼對不上(照是別台的):視同沒照,主控台明講「這張照綁的是另一台機器(前 12 碼 …),請洽原廠換綁」。

時間倒轉:主系統照有時鐘水位(clock_watermark)防把系統時間撥回去。Agent 第一期不做,列在 §7。

3.8 主系統端(P3)

  • Agent 的 agent_status 事件多帶 license: {status, expires_at, license_id};主系統落到 agents 表兩欄(license_status、license_expires_at),遠端 Agent 列表多一欄。
  • 第二期:Agent 主控台「線上開通」→ 走出站通道到主系統 → 主系統 BE 代打 LC → 照回傳 Agent。主系統只是中轉,仍由 Agent 自己驗章。
  • 第二期:root 後台「向 LC 請 Agent 照」(比照 lc-issue)。

4. 待決策

決策者 11-07 已口頭同意下列建議;寫成條列是為了看雛形時再核一次,看完沒意見就當定案。

D1 Agent 照與主系統照的關係:獨立一張、記上層照號

獨立一張照,customer_code 必填、parent_license_id 選填。排除「主系統照加 max_agents」(§1.2)。

定案:獨立照。

D2 LC 資料怎麼存:新模組、新表

modules/agent_licensing/ 自己一張 agent_license_issuance,不改 license_issuance。序號邏輯與私鑰解鎖抽到共用層,不是從 licensing 模組 import。

定案:新模組新表。

D3 照的內容:payload_type: agent_license,沒有模組與方案,到期政策兩段式

§3.2 的欄位表。

定案:照 §3.2。

D4 機器碼:直接用 Agent 的 device_uuid

不另算一套。VM 模板複製會讓兩台機器碼相同、一張照兩台都能用——主系統註冊的撞號偵測已經擋第二台,LC 不處理。

定案:用 device_uuid。

D5 開通路徑:第一期離線;線上開通走 Agent → 主系統 → LC,第二期

LC 第一期順便把線上開通端點做出來(薄殼),主系統之後再接。Agent 永遠不直連 LC。

定案:離線先行,線上端點先做、主系統端第二期。

D6 沒照或過期的 Agent:不擋啟動,但不註冊、不心跳、不掃描

§3.7 的狀態表。寬限期全功能、過了寬限回到「尚未開通」。

定案:照 §3.7。

D7 批量簽發:表單加「數量」,一次產 N 張各自獨立的照與序號

不做「一張照 N 個座位」的共享池——每台一張才能各自換綁、各自到期。N 張共一個 batch_id,結果頁按批列、可打包下載。

定案:數量欄+批號。

D8 Agent 端驗章用哪個引擎(P2 的事,先問方向)

兩條路:①用 Agent 既有的 common/integrity/verify.py(公鑰已編譯進去、verify_payload 已能按型別驗),加一個常數與一支「核對機器碼+算狀態」的小模組;②裝 jedi-license-runtime(主系統在用的那支)。②帶 PG 表、租戶解析、Flask API 一整套,Agent 是單租戶 SQLite 用不上,裝了還要解相依(它釘 jedi-common 1.3.2 以上、Flask 外掛一堆)。

建議:①。狀態機邏輯(提醒/寬限)很小,兩邊各一份可接受;若之後要共用,抽成不帶 DB 的純函式小套件再說。


5. 接入點:reuse/extend/new

要做的事 判定 動哪裡
簽章、armor、驗章 reuse core/crypto/engine.py 加常數 PAYLOAD_TYPE_AGENT_LICENSE
序號產生/效期/逾期判斷 extend(抽共用) 從 modules/licensing/service.py 抽到 core/activation_code.py,licensing 改 import 它(行為不變,既有測試要綠)
私鑰解鎖(env 優先、表單備援) extend(抽共用) web/views/issuance.py 的 _resolve_passphrase/_unlock_or_flash_redirect 搬到 web/unlock_helper.py
開通失敗鎖定 reuse modules/auth/service.py 既有三支
到期狀態推算 extend core/status.py 加 compute_agent_status(),不改 compute_status
Agent 照 dataclass new core/schema.py 加 AgentLicense(與 License 並列,不繼承——欄位差太多)
資料表/repo/service new modules/agent_licensing/{models,repository,service}.py,形狀照 licensing
後台五頁 new web/views/agent_licenses.py + 五支模板;導覽列 base.html 加一項
機器 API 三支 new web/views/agent_activation_api.py、web/views/internal_api.py 不動(已 492 行,新端點另開 internal_agent_api.py);web/api_schemas.py 加 schema;csrf.exempt_machine_apis() 登記
CLI 五支 new cli/agent_commands.py,cli/main.py 一行掛
守衛測試 extend tests/test_architecture_boundaries.py 自動涵蓋新模組;tests/test_api_contract.py 加 Agent 情境表;tests/test_csrf.py 加「請求真的走進 view」
文件站 extend docs/handbook/data-model.md(新表)、license-format.md(Agent 照格式)、operations.md(查案食譜)、api-contract-baseline.md(新端點)

6. 分棒草案(P1,license_center repo)

批 卡 做什麼 改哪些既有檔 新開檔 依賴
1 L1 模組地基 序號與解鎖抽共用、AgentLicense dataclass、payload_type 常數、新模組三檔、Alembic、compute_agent_status、CLI 五支、單元測試;DEV 套 migration core/crypto/engine.py、core/schema.py、core/status.py、modules/licensing/service.py(改 import)、web/views/issuance.py(改 import)、cli/main.py、migrations/env.py core/activation_code.py、web/unlock_helper.py、modules/agent_licensing/*、cli/agent_commands.py、migrations/versions/<rev>_agent_license_issuance.py —
2 L2 後台五頁 清單/簽發/結果/離線開通/詳情+客戶時間軸一行;log 照規範記簽發與下載 web/app.py(掛 blueprint)、web/templates/base.html、web/views/customer_timeline.py、templates/customer_timeline.html web/views/agent_licenses.py、五支模板 L1
2 L3 機器 API 三支 線上開通、內部簽發、內部查詢;schema、csrf exempt、OpenAPI、合約測試 web/api_schemas.py、web/csrf.py、tests/test_api_contract.py、tests/test_csrf.py web/views/agent_activation_api.py、web/views/internal_agent_api.py L1
3 L4 文件與收口 文件站四頁、CLAUDE.md 介面表加三列、DEV 端到端走一次(簽 3 張→離線開通→CLI 驗) docs/handbook/*.md、docs/api-contract-baseline.md、CLAUDE.md — L2、L3

L2 與 L3 都要在 web/app.py 掛 blueprint——登記型共用檔。做法:L1 當 owner,先把兩個空 blueprint 檔建好、在 app.py 掛上(空的也能起),L2/L3 只填自己的檔。這樣 L2、L3 同批。

P2(Agent)與 P3(主系統)等 P1 驗完、D8 定了再拆卡;粗估 Agent 三張(驗章與狀態機/主控台授權頁/狀態事件帶授權)、主系統兩張(列表欄位與事件落庫/線上開通代打)、FE 一張。


7. 不做什麼

  • Agent 功能分級:照上沒有模組清單,永遠只有有效/無效。
  • Agent 直連 LC:線上開通一律經主系統中轉(FR-181 Agent 只出站)。
  • 一張照多台共享:每台一張。
  • 主系統照格式變動:License dataclass、license_issuance 表、四支凍結 API 一字不動。
  • Agent 的唯讀/鎖定四段式:只有提醒與寬限。
  • 時鐘倒轉防護(主系統照有):Agent 第一期不做,列入 P2 的「第二期」候選。
  • 自助換綁:同主系統政策,人工受理。
  • Agent 照當更新站身分票:FR-167 的 update_ticket 是主系統照開通時附簽的;Agent 更新鏈(FR-172)目前不靠票,這期不碰。