本卡屬 FR-181(母卡 FR-181 母案(同清單上一張)),卡 A-3,批 5。只改 evidence-agent。依賴:F-2。
Agent 這邊把 F-2 留空的九種指令接上:換證、取消檢測、測試連線、存帳密、查日誌、證據檔四種(來拿、送上去、送 PDF、刪)。每一種都接既有的處理函式——但這些函式現在多數寫在 route 檔裡(8443 直推端點的 handler),要先抽到 app 層(scan/app/cancel.py、scan/app/probe.py、files/app/blob_ops.py),route 殼改成一行呼叫,長輪詢分派表呼叫同一支,不複製。「要回答的」七種(測試連線、存帳密、取檔、送檔、送 PDF、查日誌)流程固定:拿到先回 taken(讓主系統知道「在做了」不是「沒拿到」)→ 做 → 回 done 帶結果,或 failed 帶原因(主系統立刻讓瀏覽器收到錯誤,不等到逾時)。證據檔「來拿」要驗 sha256,不符就 failed 不落檔;「送上去」用 multipart 帶檔。
extend:command_pull.py 分派表填九個 no-op——credentials_renew→cert_renew.request_now();detection_cancel→scan 既有 cancel 的 app 層函式(從 detection_route.DetectionCancelRoute 抽到 scan/app/cancel.py,route 改呼叫);probe→既有 probe app 函式(同法抽 scan/app/probe.py);credentials_set→scan/app/credential_store 既有存法;query_logs→platform/audit_log.list_logs(cmd_logs 已是薄殼);fetch_file→GET /agents/files/<id> 下載→jedi_file_upload FileUploadService.save→reply done 帶 {file_uid, sha256};send_file/send_pdf→讀本機(pdf 先走既有轉檔)→reply_sender.reply(file=) multipart;delete_file→既有刪除、reply done。要回答的七個 kind 先 taken 再做,做失敗 failed reason=;證據檔四種的 app 層從 files/api/blob_route.py 抽到 files/app/blob_ops.py,route 改薄殼。禁:不碰 registry.py/data_plane_server.py(F-2 已留槽)
repo:~/Projects/Billows/Audit-Manager/evidence-agent
改既有:guidant_agent/sync/app/command_pull.py(填分派表)
改既有:guidant_agent/scan/api/routes/detection_route.py(抽 app 層)
改既有:guidant_agent/scan/api/routes/credential_route.py
改既有:guidant_agent/files/api/blob_route.py(抽 app 層)
新開:guidant_agent/scan/app/cancel.py
新開:guidant_agent/scan/app/probe.py
新開:guidant_agent/files/app/blob_ops.py
新開:tests/sync/test_command_reply_kinds.py
scan/api/routes/detection_route.py 的 cancel/probe 本體 → scan/app/cancel.py::cancel(job_uid) -> dict、scan/app/probe.py::probe(payload) -> dict;scan/api/routes/credential_route.py 本體 → 既有 scan/app/credential_store 已有存法就直接用、沒有就抽 credential_store.set_from_main(payload);files/api/blob_route.py 四操作 → files/app/blob_ops.py(fetch(request_id, name, sha256) -> file_uid:GET FILES_PATH 下載到 tmp、算 sha 不符 raise、FileUploadService.save;send(file_uid) -> path;send_pdf(file_uid) -> path(走既有轉檔);delete(file_uid))。cmd_logs 已是薄殼不動。command_pull.HANDLERS 填九個:credentials_renew→cert_renew.request_now();detection_cancel→cancel.cancel(payload["job_uid"]);probe/credentials_set/query_logs/fetch_file/send_file/send_pdf→包一層 _answer(cmd, fn);delete_file→blob_ops.delete(不回答)。_answer(cmd, fn):reply(cmd.request_id, "taken") → result = fn(cmd.payload) → 回傳是路徑(send_file/send_pdf)→ reply(..., "done", file_path=path) 送完刪本機 tmp;否則 reply(..., "done", result=result);任何例外 → reply(..., "failed", reason=str(exc)[:200]) 並 log。🔴 payload 可能含工具帳密(credentials_set):log 只記 kind 與 request_id。query_logs:audit_log.list_logs(**filters),篩選參數名照 cmd_logs 現在吃的那組(platform/query_args.log_filters),結果形狀照 cmd_logs 現在回的(主系統 B-2 靠這個形狀不改 FE);每次查記本機 6257 事件(沿用 cmd_logs 的做法)。tests/sync/test_command_reply_kinds.py:九個 kind 各一支——taken 先於 handler、done 帶 result/file;handler 拋 → failed 帶 reason;fetch_file sha 不符 → failed 且 FileUploadService.save 未被叫;route 殼(開關開時 POST /detection/cancel 等)仍呼叫同一支 app 函式(patch app 函式斷言被叫)。agent_request_service.ask(agent, KIND_PROBE, {...}) → 2 秒內回 result;ask(KIND_SEND_FILE, {file_uid}) → /tmp/agent-outbound/<id> 出現檔、sha 與 Agent 側一致。pytest:每個 kind 一支:收到→taken reply→handler 被叫→done reply 帶 result;handler 拋 → failed 帶 reason;fetch_file 下載 sha 不符 → failed 不落檔;route 殼(POST /detection/cancel 等)在開關開時仍能呼叫同一支 app 函式(抽層沒抽壞)。grep -n 'def ' files/api/blob_route.py 每支 ≤ 10 行
~/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)jedi-remote-agent/jedi_remote_agent/api/routing.py(端點表與 AUTH_AGENT)、api/routes/(心跳 route 範本)、app/service/agent_data_plane_client.py(直推出口,留著)、app/service/heartbeat_vulndb.py(_CORE_KEYS 心跳守衛);契約 jedi-agent-protocol/jedi_agent_protocol/commands.py、heartbeat.py、events.py