本卡屬 FR-167(母卡 CM-3241),卡 P4-2,階段 P4 Agent 跟進、批 6。只改 BE(主 repo,Repo 欄空著)。依賴:P1-3a、P3-2c、P1-9a。
主系統升完後自動帶 Agent 升級:建分批表,第一批只推一台,成功才推其餘,失敗其餘暫停;心跳告訴目標批的 Agent 該升到哪版;Agent 從主系統下載包;更新中心可以暫停、繼續、推選定幾台。
分批推進的狀態機+下載端點;版本約束(Agent 不比主系統新)在這裡守
extend:照 _agent_vulndb.py 形狀新檔;core/plugins/remote_agent.py 接 update_extra 一行;端點進 P3-2a 模組
repo:~/Projects/Billows/Audit-Manager/compliance-manager-be
改既有:core/plugins/remote_agent.py(一行)
改既有:app/product_update/service/product_update_app_service.py
改既有:api/product_update/routes/product_update_route.py
新開:scripts/sql/2026-xx-xx-fr167-agent-update-rollouts.sql
新開:core/plugins/_agent_update.py
新開:app/product_update/service/agent_rollout_service.py
新開:api/remote_agent/routes/agent_update_bundle_route.py
細節在 design.md §5 對應段(~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-167-2610-online-update-channel/design.md,開 design.html 看導覽);本段只列本卡要做到的點:
agent_update_rollouts(欄位照 design §5.7;RLS;GRANT)。core/plugins/_agent_update.py 照 _agent_vulndb.py:update_extra(agent) 只對 dispatched 回 {target_version, sha256, size};core/plugins/remote_agent.py 接 update_extra= 一行。agent_rollout_service.py:升級完成事件(status 端點看到進度檔 succeeded 且版號變)→ 建 batch 1 一台(最近心跳最新);收 upgrade_result succeeded → 其餘 dispatched;failed → 其餘 paused;pause/resume/push(agent_ids)(只對落後的台)。版本約束:Agent 主線版號 ≤ 主系統版。api/remote_agent/routes/agent_update_bundle_route.py:GET /agents/update/bundle,資料面 JWT(grep 既有 Agent 下載端點 reuse 守門),Range 續傳,回包+sha header。DEV 8003+pytest:升級完成事件 → rollouts 一筆 batch 1 一台;該台回成功 → 其餘進 batch 2;回失敗 → paused;心跳回 update 只給目標批(用 test client 模擬三台心跳);下載端點走資料面 JWT、回包+sha;無能力點 403;守衛測試綠
測試期(不是你做):這張卡在 190 的真實情境是「三台 Agent:主系統升完先一台成功再其餘;第一台失敗其餘 paused;推選定只對落後的台」,由首腦在測試期操作、決策者看畫面。你只要確保上面的本機驗收全過、程式對這個情境沒有寫死假設。
對照雛形:~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-167-2610-online-update-channel/prototype/index.html(上方「跟著走」)。