本卡屬 FR-182(母卡 CM-3609 https://app.notion.com/p/FR-182-Agent-Agent-P1-License-Center-4-3-410346da4cd0816ea5e2c4757ebc4fbb),P3 主系統端,卡 P3-2,Stage 5(P1 LC 端已 Done 上 188/189;P2 Agent 端 CM-3621~3623 Stage 4)。只改 主 repo(BE)。依賴:無(P3-1/P3-2 同批平行;P3-2 用到 P3-1 的函式先照契約名寫,合回後對一次)。

這張卡做什麼(白話)

兩件事。①主系統收到 Agent 的授權狀態事件,寫進 remote_agents 新加的兩欄(P3-1 的函式,加一行呼叫)。②線上開通:客戶在 Agent 主控台輸入序號按一下,Agent 不能直連 License Center(FR-181:Agent 只連主系統),所以主系統開一支端點代打 LC——Agent 把「註冊 token+序號+機器碼」送來,主系統驗 token 有效(只驗不扣次數——這不是註冊)、照 token 查出租戶、帶序號+機器碼打 LC 的 /api/activation/agent/activate、把 LC 回的照原封不動回給 Agent;主系統不驗章、不存照、log 不記照內容(照是 Agent 的,Agent 自己驗)。為什麼用註冊 token 不用通行證:沒照的 Agent 還沒註冊、沒有通行證,這是它拿到照之前唯一的身分。LC 回的各種錯(序號錯/已用/逾期/鎖定/LC 連不上)對映成主系統自己的 error code 讓 Agent 顯示人話。開發期主系統要吃到 P3-1 改的套件:走 path dependency,那兩行不 commit。

reuse/extend/new

extend:core/plugins/_agent_status.py status_values 加一行 values.update(license_values_from_payload(known, agent, now))(P3-1 的函式;本卡同批平行,先照契約名寫,181 合回前 import 失敗就 try/except ImportError 回 {} 並回寫);pyproject.toml 開發期改 jedi-agent-protocol/jedi-remote-agent 兩行為 path dependency(指 ~/Projects/Jedicogy/module/jedi-python-package/<套件>,develop=true),poetry update jedi-agent-protocol jedi-remote-agent——這兩行與 poetry.lock 不 commit(git update-index --assume-unchanged pyproject.toml poetry.lock,回寫寫明;P3-4 還原釘版);主線 migration 不寫(套件 020 由主系統 installer 的套件 migration 機制套,照 017~019 先例;DEV 手套)。new:api/remote_agent/routes/agent_license_route.py AgentLicenseActivateRoute.post——守門:沒有 agent_pass_required(沒照的 Agent 還沒註冊沒通行證),改驗 body 的 enroll_token:hash_token(token) → 套件 remote_agent_enroll_token_domain_service.get_enabled_by_hash(hash)(只驗不消耗,register 才 consume_enroll_token;查無/停用/過期 → 401 FILE_AGENT_TOKEN_INVALID 沿用既有碼);租戶只認 token 查出來的那個(照 agent_enrollment_service.register 兩段式,payload 帶什麼都不採信),LC 代打在該租戶機器身分下做+activation_code+machine_fingerprint(64 hex 格式驗);body 上限 4 KB;IP 限流照 /agents/register 的(有就 reuse);呼叫 new app/remote_agent/service/agent_license_activation_service.py activate(code, fingerprint) -> dict:httpx.post(f"{LICENSE_ACTIVATION_SERVER_URL}/api/activation/agent/activate", json=…, timeout=10)——錯誤對映照 jedi-license-runtime tenant_license_admin_service._call_lc:連不到→GRC_AGENT_LICENSE_LC_UNREACHABLE、400→把 LC 的 error 原句帶回(序號逾期/授權過期)、404→GRC_AGENT_LICENSE_CODE_NOT_FOUND、409→GRC_AGENT_LICENSE_CODE_USED、429→GRC_AGENT_LICENSE_LOCKED、401/503→GRC_AGENT_LICENSE_LC_UNAVAILABLE;成功回 {"license": <LC 信封原樣>},主系統不驗章、不落地、不記照內容(Agent 自己驗;log 只記 agent_fingerprint_digest+序號摘要+結果);core/plugins/remote_agent.py mount_api=False 那段 api.add_resource(AgentLicenseActivateRoute, "/agents/license/activate")(登記型,本卡 owner);

error code 五個進 common/code/grc_error_code.py(登記型,本卡 owner;FE error-code.json 同步兩語);DI:service 進 di_containers/remote_agent/(181 A-1 動過已合回,可加)。.env 不加新變數(LICENSE_ACTIVATION_SERVER_URL 既有)。

在哪裡

repo:~/Projects/Billows/Audit-Manager/compliance-manager-be
改既有:core/plugins/_agent_status.py
改既有:core/plugins/remote_agent.py(add_resource 一行)
改既有:common/code/grc_error_code.py
改既有:di_containers/remote_agent/remote_agent_containers.py
改既有:pyproject.toml(開發期 path,不 commit)
改既有:poetry.lock(不 commit)
改既有:(FE)src/config/locales/i18n/zh-tw/error-code.json、en/error-code.json(五碼;FE repo 另 commit——一張卡一 repo,本卡回寫列出要加的五碼由首腦補)
新開:api/remote_agent/routes/agent_license_route.py
新開:app/remote_agent/service/agent_license_activation_service.py
新開:test/test_agent_license_activation.py

怎麼做

驗收(首腦照這個驗)

pytest test/test_agent_license_activation.py:token 無效 401;格式錯 400;mock LC 各 status → 對應 GRC 碼;成功回信封且 DB tenant_licenses/remote_agents 零寫入(突變:把「不落地」拿掉寫一列 → 紅);test/test_module_boundaries.py 綠。DEV 實打:BE 起(path dependency 版)→ 產一把註冊 token → curl -XPOST :8000/api/1.0/agents/license/activate -d '{enroll_token, activation_code:<LC DEV 簽的>, machine_fingerprint:<64hex>}' 回 200 帶 license,拿去 LC agent-verify 過;同序號再打 409;錯 token 401 先於其他;_agent_status 收 {license_status: valid, license_expires_at: …} 事件後 remote_agents 兩欄有值、塞 license_status: hacked 不寫且 log warning

必讀