本卡屬 FR-182(母卡 CM-3609 https://app.notion.com/p/FR-182-Agent-Agent-P1-License-Center-4-3-410346da4cd0816ea5e2c4757ebc4fbb),P3 主系統端,卡 P3-1,Stage 5(P1 LC 端已 Done 上 188/189;P2 Agent 端 CM-3621~3623 Stage 4)。只改 jedi-python-package。依賴:無(P3-1/P3-2 同批平行;P3-2 用到 P3-1 的函式先照契約名寫,合回後對一次)。
P2 讓 Agent 自己認得照、沒照不跑;但主系統那邊還看不到「哪台 Agent 有沒有授權、什麼時候到期」。Agent 本來就會把狀態變動(名稱、工具、儲存…)用 agent_status 事件送給主系統,主系統只收白名單內的鍵、值域內的值。這張卡在白名單加兩個鍵(授權狀態、到期日)、主系統 remote_agents 表加兩欄、寫一支「事件 payload → 要寫的欄位」的小函式(照既有 heartbeat_name.py 的形狀,值域守死——Agent 回報的值不可信)。只改檔、不發版、不動版號:FR-181 正在同兩個套件的另一些檔上改東西(長輪詢契約與端點),版號要等它合回一起進,發版是 P3-4a。主系統那邊接這支函式是 P3-2(同批、不同 repo)。
extend:jedi-agent-protocol events.py AGENT_STATUS_KEYS 加 license_status、license_expires_at(登記型,本卡 owner;181 動的是 commands.py 不同檔);jedi-remote-agent infra/remote_agent/model/remote_agent.py 加 license_status: String(16) null(missing/invalid/mismatch/valid/notice/grace/expired,NULL=Agent 還沒回報過=舊版 Agent)與 license_expires_at: DateTime(tz) null,comment= 寫清楚 NULL 語意;domain entity/query entity/repo impl 對應加欄(照 019 health_flags 怎麼加就怎麼加);api/serializers/remote_agent.py 加兩欄;new:app/service/heartbeat_license.py license_values_from_payload(payload, agent, now) -> dict(照 heartbeat_name.py 形狀:值域白名單七態、license_expires_at 走 _aware 解析、格式不對一律不寫、有變才回);migrations/020-remote-agent-license.sql(照 019:ADD COLUMN IF NOT EXISTS+COMMENT ON,冪等)。主系統(同一張卡順手改、但 repo 是 jedi-packages——不行,一張卡一個 repo)→ 主系統那三行放 P3-2。🔴 不 bump 兩個套件版號、不改 pyproject.toml、不發版:181 同套件還在工作區,版號與發版在 P3-4 收口。測試:protocol tests/test_events.py 鍵在白名單;remote-agent tests/test_heartbeat_license.py(七態各一、壞值不寫、同值不回;突變:拿掉值域白名單 → 塞 hacked 要紅)。
repo:~/Projects/Jedicogy/module/jedi-python-package
改既有:jedi-agent-protocol/jedi_agent_protocol/events.py(AGENT_STATUS_KEYS 加兩鍵)
改既有:jedi-remote-agent/jedi_remote_agent/infra/remote_agent/model/remote_agent.py
改既有:jedi-remote-agent/jedi_remote_agent/domain/remote_agent/entity/remote_agent.py
改既有:jedi-remote-agent/jedi_remote_agent/infra/remote_agent/repository/remote_agent_repo_impl.py(mapper 兩欄)
改既有:jedi-remote-agent/jedi_remote_agent/api/serializers/remote_agent.py
新開:jedi-remote-agent/jedi_remote_agent/app/service/heartbeat_license.py
新開:jedi-remote-agent/jedi_remote_agent/migrations/020-remote-agent-license.sql
新開:jedi-remote-agent/tests/test_heartbeat_license.py
新開:jedi-agent-protocol/tests/test_events_license_keys.py
events.py(AGENT_STATUS_KEYS 與各 *_KEYS 的寫法);jedi-remote-agent app/service/heartbeat_name.py(範本:_aware、長度/值域守門、「有變才回」)、heartbeat_offline.py;infra/remote_agent/model/remote_agent.py 019 加的三欄怎麼寫 comment=;migrations/019-remote-agent-reregister-storm.sql(檔頭格式、冪等寫法);domain/remote_agent/entity/、repo impl 的 mapper;api/serializers/remote_agent.py。🔴 兩個套件在 .worktrees/CM-3595 另有 FR-181 的未合回改動——本卡在自己的工作區做、只碰檔頭列的檔,commands.py/routing.py/remote_agent_service.py/plugin/*/pyproject.toml 一律不碰。events.py:AGENT_STATUS_KEYS 加 "license_status", "license_expires_at";旁邊加 AGENT_LICENSE_STATUSES = frozenset({"missing","invalid","mismatch","valid","notice","grace","expired"})(主系統與 Agent 共用這份值域,不各寫一份)。tests/test_events_license_keys.py:兩鍵在白名單、七態集合。license_status: Mapped[str] = mapped_column(String(16), nullable=True, comment="Agent 自報授權狀態(missing/invalid/mismatch/valid/notice/grace/expired);NULL=未回報(舊版 Agent 或尚未送過狀態事件)")、license_expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=True, comment="Agent 自報授權到期日;NULL 同上")。entity/query entity/repo mapper/serializer 各加兩欄(grep health_flags 看它在哪幾個檔出現,照加)。app/service/heartbeat_license.py:license_values_from_payload(payload: dict, agent, now) -> dict——status = payload.get("license_status"):不是 str 或不在 AGENT_LICENSE_STATUSES → 不寫(log warning 帶 agent.uid);expires_at 走 heartbeat_name._aware(import 它,不複製);兩者各自「與 DB 現值不同才回」;回 dict 只含要改的鍵。migrations/020-remote-agent-license.sql:照 019——檔頭 -- jedi-remote-agent 020:Agent 授權狀態(FR-182 P3-1)、-- Date:、ADD COLUMN IF NOT EXISTS ×2、COMMENT ON ×2。不登記主系統 schema_migrations(套件 migration 走套件機制,照 017~019)。tests/test_heartbeat_license.py:七態各一回對應值;hacked 不寫且 warning;同值不回;expires_at 壞字串不寫;突變:把 AGENT_LICENSE_STATUSES 檢查拿掉 → hacked 那條要紅。兩套件各自 venv 全部 tests 綠。psql(看主系統 .env)-f migrations/020-…sql 兩次都成功;\d compliance.remote_agents 兩欄在。回寫寫明「版號未動、未發版,P3-4a 發」。兩個套件各自 venv pytest 全綠(🔴 env -u VIRTUAL_ENV);grep -n license jedi-remote-agent/pyproject.toml jedi-agent-protocol/pyproject.toml 為 0(版號沒動);migration 020 在 DEV 主系統庫手套兩次(冪等)\d compliance.remote_agents 看到兩欄;AGENT_STATUS_KEYS 含兩鍵;突變紅
~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-182-2611-agent-license/design.md(D6 不擋啟動但不起 sync、D8 用既有 integrity 驗章、§4.2 照 payload、§4.8 Agent 端);討論稿 ~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-182-2611-agent-license/discussion.md §2.2 Agent 端盤點、§2.4 與 FR-181/FR-179 接點、§3.7 狀態表~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-182-2611-agent-license/prototype/agent-console-license.html(七場景;P2-3 對照刻,P2-1/P2-2 看資料要長什麼樣)