本卡屬 FR-181(母卡 FR-181 母案(同清單上一張)),卡 A-2,批 5。只改 主 repo。依賴:A-1。
主系統這邊六種指令的「送」那一段換掉:原本是「寫 DB 正本 → 主動打 Agent 8443」,改成「寫 DB 正本 → 敲一下 Redis 的鈴」,掛在那台 Agent 長輪詢上的請求就會醒來自己查表拿走。六種:派工(工單 insert 事件後)、重啟(管理頁按鈕後)、升級(分批轉下發後)、設定變了(授權清冊等 13 類改動後)、立即換證、取消檢測——後兩種原本沒有 DB 正本(只發不記),改成寫進 agent_requests。直推那支 AgentCommandService 整支保留,只在 send() 開頭加開關 AGENT_DIRECT_PUSH_ENABLED 預設 False 擋掉(決策者裁「留著怕未來要開」)。心跳回來那條「補推」改成只敲鈴、仍不夾帶任何東西(11-01 裁示不翻)。連線欄用的 last_command_status 停寫、欄位留著(FE B-3 改二態)。
extend:core/plugins/_agent_commands.py——_push_after_commit/push_restart_after/push_update_soon/redispatch_on_heartbeat 的「送」全部改成 waiter.notify(agent_uid)(正本已在 DB,長輪詢端點自己查);with_command_push 的 _PUSH_AFTER 改成 notify;record_probe_after 刪(連線欄改二態)。_agent_config_notify.notify_agent_after_commit 改寫 agent_requests(kind=config_changed)+notify(取代直推 /config/notify)。_agent_keys.py:118 立即換證改 fire(agent, credentials_renew)。AgentCommandService:整支保留,send() 開頭加 if not settings.AGENT_DIRECT_PUSH_ENABLED: return CommandResult.disabled()(D6 開關,預設 False,config/config.py 加變數);run_now/after_commit 不動。redispatch_on_heartbeat 留一行 notify(心跳回來順手敲一下鈴,仍不夾帶)。last_command_status 停寫(欄位留,B-3 FE 改二態)
repo:~/Projects/Billows/Audit-Manager/compliance-manager-be
改既有:core/plugins/_agent_commands.py
改既有:core/plugins/_agent_config_notify.py 135
改既有:core/plugins/_agent_keys.py 118
改既有:app/remote_agent/service/agent_command_service.py 55(開關)
改既有:config/config.py(AGENT_DIRECT_PUSH_ENABLED)
改既有:test/test_agent_command_service.py
core/plugins/_agent_commands.py:_push_after_commit → waiter.notify(agent_uid)(每個 ref 一次,去重);push_restart_after/push_update_soon → notify(restart_requested 的清除改由套件端點 mark_restart_dispatched 做,這裡不清);redispatch_on_heartbeat → 只 waiter.notify(agent.uid)(心跳回來順手敲鈴,仍不夾帶);record_probe_after/_AFTER_RESPONSE 刪;with_command_push 的 _PUSH_AFTER 值改 notify。檔頭 docstring 改成現況(直推預設關、長輪詢主路徑)。waiter 從 plugin handle 取(A-1 注入的那支)。core/plugins/_agent_config_notify.py notify_agent_after_commit(agent_uid, changed):改 agent_request_service.fire(agent, KIND_CONFIG_CHANGED, {"changed": changed})(去重:同一台 5 秒內已有 waiting 的 config_changed 就不再插)。core/plugins/_agent_keys.py 118 行立即換證:service.send(agent, cmd.CREDENTIALS_RENEW, …) → agent_request_service.fire(agent, KIND_CREDENTIALS_RENEW, {});audit 事件照舊。fire),本卡不碰;確認主系統 DI 有把 agent_request_service 注入給 jedi-detection 的 channel port(A-4 定的介面),缺就在 di_containers/detection_tools/ 加。app/remote_agent/service/agent_command_service.py send() 開頭:if not current_app.config.get("AGENT_DIRECT_PUSH_ENABLED"): return CommandResult.disabled()(CommandResult 加 classmethod disabled(),ok=False、status="disabled");config/config.py 加 AGENT_DIRECT_PUSH_ENABLED = os.getenv(...) == "true" 預設 False;.env sample 與 deployment-env.md 各補一行。_record 遇 disabled 不寫 last_command_*。test/test_agent_command_service.py 既有案例改新語意(send 預設回 disabled、不打 client);突變:拿掉開關判斷 → 一支「預設不呼叫 client」的斷言要紅。grep -rn '\.command(\|\.send(' core/plugins app/remote_agent 只剩開關後那一處。🔴 只動 DEV 主系統;123 Agent 是決策者授權的驗證機,重啟 Agent 前查有無掃描在跑。pytest 既有 test_agent_command_service 改成新語意綠;突變:拿掉 AGENT_DIRECT_PUSH_ENABLED 判斷 → 守衛紅。DEV+123 Agent(F-2 版)實測:派工 3 秒內開跑;管理頁按重啟 → Agent 重啟且旗標清掉、不重複重啟;分批升級轉成下發 → Agent 3 秒內開始下載;改授權清冊 → Agent 3 秒內拉到;按立即換證 → agent_requests 一列 done、Agent log 有 renew;grep -rn 'data_plane_client().command\|\.send(' core/ app/ 只剩開關後那一處
~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-181-2611-agent-outbound-only-channel/design.md §3 決策定案(D1~D8)、§4 端到端流程兩張圖、§5 詳細設計本卡那節、§7 拆分表本卡那列;為什麼這樣定看 discussion.mdcore/plugins/_agent_commands.py(檔頭說明三個觸發點)、app/remote_agent/service/agent_command_service.py、core/plugins/_agent_config_notify.py、core/plugins/_agent_keys.py、infra/upload_file/remote_agent_adapter.py、core/plugins/remote_agent.py(plugin mount 與 handle)