本卡屬 FR-182(母卡 CM-3609 https://app.notion.com/p/FR-182-Agent-Agent-P1-License-Center-4-3-410346da4cd0816ea5e2c4757ebc4fbb),P2 Agent 端,卡 P2-1,Stage 4(P2 第一批,接在 P1 的批 1~3 之後;P1 LC 端 CM-3610~3613 已 Done 並上 189)。只改 evidence-agent。依賴:無(同批 P2-1/P2-2/P2-3 平行;P2-2 的 gate 介面以 P2-1 為準,合回後對一次)。

這張卡做什麼(白話)

License Center 那邊(P1,已上 189)已經簽得出「Agent 照」——一台 Agent 一張、綁那台機器碼、有到期日、沒有功能分級。這張卡讓 Agent 認得這張照:開機時讀資料目錄裡的 agent.license,用已經編譯進程式的簽發公鑰驗簽章、確認型別是 agent_license(主系統的照拿來會被拒)、核對照上的機器碼等於本機的 device_uuid、按到期日算狀態(正常/到期提醒/寬限期/已過期)。沒照、照驗不過、照是別台的、過了寬限期:整個 sync 模組不起——不向主系統註冊、不心跳、不拉設定、不收指令(含 FR-181 新做的長輪詢);主控台照常起、能登入看機器碼(上傳照的畫面是 P2-2/P2-3)。閘門放在「起 sync 模組」那一層,一道門關全部,不逐條通道加 if——以後 sync 多一條什麼都自動被閘住。上傳對的照之後不用重啟:閘門在同一個行程內放行、把 sync 起來。決策者定調(design D6、D8):不擋啟動(擋了客戶看不到機器碼,開通流程走不下去);驗章用 Agent 既有的 common/integrity,不裝主系統在用的 jedi-license-runtime。

reuse/extend/new

reuse:common/integrity/verify.py 的 verify_payload(signed, expected_type=…) 與編譯進去的 PUBLIC_KEYS(加常數 PAYLOAD_TYPE_AGENT_LICENSE = "agent_license" 到 verify.py 的型別清單旁);platform/fingerprint.compute_device_uuid;config.data_dir()。new:guidant_agent/license/(照 ARCHITECTURE §3:domain/status.py 純函式四態 valid/notice/grace/expired+LicenseState dataclass;app/store.py 讀寫 AGENT_DATA_DIR/license/agent.license(armor 或 JSON 都收,寫入原子 tmp+rename、0600);app/check.py evaluate(cfg) -> LicenseState:無檔→missing、驗章失敗→invalid(reason)、payload_type 不是 agent_license→invalid(wrong_type)、machine_fingerprint ≠ device_uuid→mismatch(bound_prefix)、其餘算四態;app/install.py install(bytes) -> LicenseState:先 evaluate 新檔內容(不落地),通過才寫檔並覆蓋,失敗保留原檔;__init__.py 申報 status_fields()(本期回空 dict——AGENT_STATUS_KEYS 白名單在 jedi-agent-protocol,P3 才加鍵;先把掛點做好)、register(app, container) 把 LicenseGate 單例放 app.extensions["agent_license"]+根容器 provider)。extend:platform/registry.py MODULES 加 _module("license", status_fields=True) 放 identity 之後 files 之前(登記型,本卡 owner);platform/bootstrap.py _setup_agent_auth 開頭:gate = app.extensions["agent_license"]; if not gate.allows_sync(): logger.warning(...); app.config["AGENT_STATUS"] = "unlicensed"; return——閘門在這一層,setup_agent_auth/enroll_for_auth/registry.py 的 sync 那列一字不動;閘門放行條件=valid/notice/grace。LicenseGate.on_installed 回呼:install 成功且閘門原本關著→同行程呼叫 _setup_agent_auth(app, container, components)+components.start_all()(bootstrap 把這兩個綁進 gate),不用重啟。.claude/rules/security/sync-and-identity.md 的 paths 補 guidant_agent/license/**(守衛 test_security_rules_cover_sources 會紅)。mode=none(demo)不閘。

在哪裡

repo:~/Projects/Billows/Audit-Manager/evidence-agent
改既有:guidant_agent/platform/registry.py(MODULES 加一列)
改既有:guidant_agent/platform/bootstrap.py(_setup_agent_auth 開頭加閘門+把重入綁給 gate)
改既有:common/integrity/verify.py(加 PAYLOAD_TYPE_AGENT_LICENSE 常數)
改既有:.claude/rules/security/sync-and-identity.md(paths 補 license/**)
新開:guidant_agent/license/__init__.py
新開:guidant_agent/license/domain/status.py
新開:guidant_agent/license/app/store.py
新開:guidant_agent/license/app/check.py
新開:guidant_agent/license/app/install.py
新開:guidant_agent/license/app/gate.py
新開:test/test_license_status.py
新開:test/test_license_check.py
新開:test/test_license_gate.py

怎麼做