CM-929 / FR-056.5 中繼交接 — 測試連線改走 Agent(雲端直推 probe)

項目 內容
交接日期 2026-07-27
緣由 中繼交接(context 已長,arc 未完)——CM-929 三輪修正已完成待複測,後面還有任務
Branch(三 repo 同名) feature/scan-plugin-integration不可切 branch
Notion case CM-929 3aa346da-4cd0-8191-9fba-c4fb2474820f,狀態 修正待驗證
現況一句話 CM-929 本體 + 五條驗收回饋(#1~#5)全數實作完成並各自 commit;唯一擋住最終驗收的是「123 那台 agent 尚未重 build」(Claude 無 SSH 權限)
下一棒主要任務 ① 等 user 重 build agent 後複測 ② push 決策 ③ 後續 user 指派的新任務
接手前必讀 本文件 §0 讀序;Notion CM-929 卡(含五條回饋原文 + 三輪回報)

🧭 原始需求 / WHY(先讀這段,不要跳)

這件事要解決什麼問題:檢測工具(OpenVAS)的「測試連線」功能,原本是雲端後端自己httpx.head 直連掃描器。兩個致命錯:

  1. 協定錯 — OpenVAS 走 GMP over TLS(python-gvm),不是 HTTP。且 httpx 要求 http:// 前綴,使用者填「192.168.50.123」直接報 missing protocol。
  2. 架構錯(根本問題) — BE 在雲端 SaaS、OpenVAS 在客戶內網,雲端根本連不到。dev 能通純粹因為 BE 與掃描器恰好同網段,正式環境必然失敗

而真實掃描的 connector 早就在 agent 端(客戶內網,走 python-gvm)——等於「測試連線」跟「真實掃描」是兩套不同的連線邏輯,違反單一真相原則:測得通不代表掃得動,反之亦然。

目標模型(2026-07-27 user 拍板案 1「雲端直推」)

UI 按「測試連線」
  → BE 解密該租戶憑證
  → 挑該租戶有 detection_scan capability 的 agent
  → mTLS POST https://<agent>/detection/probe  { tool 設定, credentials }
  → agent: OpenVasConnector.probe() = TLSConnection + gmp.authenticate(不建 target、不掃描)
  → 同步回 { success, message }(1~3 秒)
  → BE record_test_result + 回 UI

刻意不走任務化(不動 agent_tasks schema、不加 kind 欄位、不縮心跳):任務化鏈路是為長時掃描設計的,probe 是秒級互動操作,走 FR-039 既有的即時 mTLS 通道即可。當初 case 內文原本寫的是任務化方案,因心跳間隔 300 秒(按下去最壞等 5 分鐘)而改案。

本棒在大圖的位置:FR-056(檢測工具整合平台)子需求 56.1~56.4 已全數驗收放行;56.5 是端到端實測時發現的架構缺陷修正,屬 FR-056 收尾前的最後一塊。


§0 接手讀序(照順序,先懂需求再碰 code)

🔒 需求 gate(全讀,擺在機械步驟之前)

  1. 本文件「🧭 原始需求 / WHY」段(上方)
  2. Notion CM-929 卡全文 3aa346da-4cd0-8191-9fba-c4fb2474820f — 含背景 / 目標 / 設計 / 驗收 / 五條驗收手測回饋原文 / 三輪完成回報
  3. docs/features/FR-056-2607-detection-tool-integration/handoff/2026-07-27-fr0565-probe-and-e2e-handoff.md — 前一棒交接(FR-056 整體脈絡、部署現況、累積收尾待辦)

再讀實作 4. app/detection_tools/service/detection_tool_service.py::test_connection — 雲端 probe 入口 5. infra/detection_tools/connector/agent_probe_client.py — 雲端→agent mTLS client(本 arc 新增) 6. ~/Projects/Billows/Audit-Manager/evidence-agent/api/detection/routes/detection_route.py — agent probe 端點(本 arc 新增) 7. ~/Projects/Billows/Audit-Manager/evidence-agent/core/task_executor_connectors/openvas.pyprobe()run() 共用連線邏輯 8. app/grc/service/task_execution_service.py::_auto_dispatch_detection_scans — #5 發佈時自動派掃描

冷接自檢 4 問(答不出來回去讀,別碰 code)

  1. 為什麼「測試連線」不能留在 BE 做?(答不出「客戶內網」= 沒讀懂 WHY)
  2. 為什麼 probe 不走 agent_tasks 任務化?
  3. probe()run() 為何一定要共用 _resolve_connection_settings / _gmp
  4. #5 自動派工為何每筆要包 SAVEPOINT,只用 try/except 會怎樣?

§1 現況:五條驗收回饋全部實作完成

# 內容 Repo Commit 狀態
本體 測試連線改走 agent + 移除 OpenVasProbe BE cadec5a5 ✅ 已 push
本體 agent probe() + /detection/probe 端點 + python-gvm 25.1→27.5 agent 3ed125f ✅ 已 push
本體 FE 顯示 agent 回傳失敗原因 + 兩個實測 bug fix FE 6380818 ✅ 已 push
#1 移除設定 dialog 內測試連線按鈕(誤導性),只留卡片 FE dbea4a5 未 push
#2 probe 失敗訊息去技術化(翻人話 + 未知留原文) agent 6df02e2 未 push
#3 ⛔ 派工憑證 fallback 反查(原本真實掃描必失敗) BE a0d593ab 未 push
#4 MyTasksView detection badge + 完成條件 tooltip FE b56580e 未 push
#5 ⭐ 規劃頁發佈時自動派第一次掃描 BE 538d8da0 未 push

三 repo working tree 皆乾淨(無未 commit 改動)。

順手抓到、原本不在 case 範圍的重大問題

python-gvm 版本不相容:123 的 Greenbone community stack 講 GMP 22.7,agent 原 pin 的 python-gvm 25.1 只認到 22.5,一連上去就 GvmError: unsupported version of GMP這條連真實掃描一起堵死,不只 probe。已 bump 到 >=27.5,<28.0(27.x 起才有 GMPv227),並對 123 的真實掃描器實測 authenticate 成功。


§2 已驗證 / 未驗證(不要把推測當事實)

✅ 已實測驗證

項目 方法 結果
agent probe() 對真實 OpenVAS 本機以新 connector 直連 123(經 socat TLS bridge:9390) 成功登入
probe route 四情境 Flask test_client + 真實憑證打真實掃描器 正確憑證 ✅ / 錯密碼「Authentication failed」/ 連不到「Network is unreachable」/ 未支援工具明確訊息
BE→agent mTLS 通道 BE 起服務打真實 API 2 秒內走到 123 的 agent
agent /health /agent-info 以 BE 憑證 mTLS 直打 200(證明 mTLS 鏈路本身沒問題)
#3 憑證 fallback DEV 真實資料唯讀查(全程 rollback) job 13342 / 13362 兩筆 tenant_config_id=NULL 皆解出完整 base_url/port/username/password;未設定工具回空並記 warning
#5 狀態可見性 DEV 真實資料,壓回 TODO→activate→同 transaction 重查(全程 rollback) 讀得到 PROCESSING ✅(若讀到舊狀態,自動派工會 100% 被狀態檢查擋掉)
DI 循環相依 實際 boot app 後解 service 兩方向都解得出、Singleton 共用同一實例
user 手測測試連線 user 自行操作 DB tenant_detection_tool_configs.last_test_result = success

❌ 尚未驗證(下一棒要做)

唯一缺口:從 UI 完整走一遍。 123 上跑的仍是 0.2.0 舊映像檔,沒有 /detection/probe 端點。

  • 現在從 UI 按測試連線 → 顯示「Agent 回應異常(HTTP 500)」。
  • 這是預期的,不是 bug:已驗證該 agent 對任何未知路徑都回 500(用 /definitely/not/a/route 對照確認),不是 404。
  • 阻塞原因:Claude 無 123 的 SSH 權限(試過 ubuntu / root / guidant / billows 皆 permission denied),無法自行重 build

§3 下一棒開工順位

步驟 1:跑 pre-flight(見 §5)

步驟 2:確認 agent 是否已重 build

# 若回 500 → 還沒重 build;回 200/400 → 已重 build(400 = 缺參數,代表端點存在)
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && source .venv/bin/activate
python - <<'EOF'
import sys; sys.path.insert(0,'.')
from dotenv import load_dotenv; load_dotenv('.env')
import httpx
from common.util.agent_auth.settings import get_agent_auth_settings
from common.util.agent_auth.tls import build_cloud_mtls_context
from common.util.agent_auth import jwt_util
s = get_agent_auth_settings()
FP = "8af59700811b14da49afe4dbfe4c8a3e1e2fccb59d23602a22b2ce0d3d06c520"
tok = jwt_util.mint(agent_uid="f8136df9-c062-45dd-8f39-ba91fc5e6e3a",
                    op="POST /detection/probe", tenant_id=102, bound_fp=FP)
with httpx.Client(verify=build_cloud_mtls_context(s), timeout=20) as c:
    r = c.post("https://192.168.50.123:8443/detection/probe", json={},
               headers={"Authorization": f"Bearer {tok}"})
    print("status:", r.status_code, r.text[:200])
EOF
  • 500 → 還沒重 build。不要自己去 debug probe 邏輯(邏輯已驗證過),提醒 user 在 123 跑 rebuild.sh
  • 400(缺 detection_tool_id)→ 已重 build,可進步驟 3。

步驟 3:五條回饋逐條複測(見 §4 checklist)

步驟 4:push 決策 — 等 user 明示

三 repo 各有未 push commits(見 §1)。push 永遠等 user 明確指示,不自動 push。

步驟 5:user 指派的後續任務


§4 複測 checklist(給 user / 下一棒)

前提:123 的 agent 已重 build(rebuild.sh)。 BE 已重啟、FE 熱更新。

# 操作 預期
本體 檢測工具管理頁卡片按「測試連線」 2 秒內顯示連線成功
#2 故意打錯密碼 顯示「認證失敗,請檢查帳號密碼」,不再有 GvmResponseError 字樣
#2 位址改成不存在 IP(如 192.168.50.199) 「無法連線至檢測工具,請檢查位址與埠號」
#1 開設定 dialog 裡面沒有測試連線按鈕;卡片上那顆還在
#4 「我的任務」頁看檢測工具任務 類型欄顯示紫色盾牌「檢測工具」badge(以前空白)
#4 尚無報告時滑過完成按鈕 tooltip「尚未有成功的掃描報告,請先執行檢測工具掃描」
#5 ⭐ 規劃頁按「開始執行任務」 detection 任務自動出現派工(不必進抽屜按)
#5 再按一次「開始執行任務」 不應對同一任務重複派工(冪等)
#5 混合場景(general + survey + detection 同批) 只有 detection 派掃描,其他不受影響
回歸 抽屜手動「開始執行」按鈕 仍在、仍可用(重掃入口)
回歸 base_url 填「192.168.50.123」不加 http:// 能通(原 case 死因之一)
回歸 port 欄位 顯示 9390 不是 9,390
回歸 已存過密碼的欄位留空存檔 不跳必填紅字

#5 不需等重 build 就能驗(派工是雲端行為):

-- 按下「開始執行任務」後查,應新增列且 _credentials 有值
SELECT id, uid, status, detection_tool_id,
       params->'_credentials' ? 'base_url'  AS has_base_url,
       params->'_credentials' ? 'password'  AS has_password
FROM compliance.agent_tasks ORDER BY id DESC LIMIT 5;

SELECT id, job_execution_uid, status, agent_task_uid
FROM compliance.detection_executions ORDER BY id DESC LIMIT 5;

§5 Pre-flight(必跑)

# 1. 三 repo branch 必須都是 feature/scan-plugin-integration(不對就停下問 user,不要自己切)
for d in compliance-manager-be evidence-agent compliance-manager-fe; do
  printf "%-26s " "$d"; git -C ~/Projects/Billows/Audit-Manager/$d branch --show-current
done

# 2. working tree 應乾淨(除 docs/ 未追蹤檔)
git -C ~/Projects/Billows/Audit-Manager/compliance-manager-be status --short | grep -v '^??'

# 3. BE 是否在跑(port 8000)
lsof -ti:8000 || echo "BE 未啟動 → cd BE && source .venv/bin/activate && nohup python main_app.py >/dev/null 2>&1 &"

# 4. 測試 smoke
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && source .venv/bin/activate
python -m pytest test/test_task_execution_service.py test/test_detection_orchestration.py \
  test/test_detection_tool_connection.py test/test_agent_probe_client.py \
  test/test_remote_agent_capability_filter.py -q | tail -3     # 應 87 passed 那組的子集

cd ~/Projects/Billows/Audit-Manager/evidence-agent && PYTHONPATH=. poetry run pytest test/ -q | tail -3   # 27 passed

⚠️ 全樹 pytest 有 115 個既有失敗(test-isolation 債,前一棒 handoff 已載明)+ 9 個 collection error(ai_dashboard / module_frame 模組)。這些不是本 arc 造成的 —— 本 arc 有做過 before/after 逐項比對,零新增失敗。不要花時間追。


§6 關鍵設計決策(避免下一棒推翻或重蹈)

  1. probe()run() 必須共用連線邏輯_resolve_connection_settings / _gmp)——這是本 case 的核心價值:「測得通 = 掃得動」。若日後有人為 probe 另寫一套連線,等於把已修好的問題重新製造一次。

  2. probe 失敗回 200 + success=false,不回 5xx —— 連不通是「探測結果」不是伺服器錯誤。回 5xx 會讓雲端只看到通用錯誤、使用者無從判斷。

  3. /detection 必須列入 agent 資料面受保護前綴core/data_plane_auth.py_PROTECTED_PREFIXES)—— 這條會收到租戶明文憑證,漏掉等於把「拿憑證連內網掃描器」的能力對外裸奔。已有測試守著(test_detection_path_is_data_plane_protected)。

  4. #3 採 A 案(查詢時 fallback)而非 B 案(綁定時寫入) —— 使用者常「先建任務、後設工具憑證」,綁定當下 config 未必存在。job_execution_detection_tools.tenant_config_id 這個欄位從來沒有人寫入過,是否退役留待 FR-056 收尾討論。

  5. #5 每筆派工包 SAVEPOINT 不是可有可無 —— @transaction 是 reentrant(共用同一 session),start_execution 拋錯會讓外層 transaction 進 aborted 狀態;只用 try/except 的話,後續任務與整批發佈會一起陪葬。

  6. #5 失敗的任務不自動重派 —— 重試是抽屜按鈕的職責。自動重派會讓每按一次發佈就在掃描器疊一份。

  7. DI 循環相依用「宣告後回頭 override」 —— detection_toolsremote_agent 互相需要(前者測連線要找 agent、後者心跳要憑證解密 D9)。已驗證兩方向都解得出且 Singleton 共用。


§7 不在本期 scope(不要順手做)

  • FR-056 總收尾(spec / SUMMARY / 母案 CM-907 收口 / memory)——等 user 下令
  • tenant_config_id 欄位退役 —— 留 FR-056 收尾討論
  • TaskSetupView 補 detection_tool 選項 —— 已查證是孤兒舊頁(兩條路由在 src/ 內無任何元件連入),v2 正式入口 ProjectPlanningView 已完整支援。依回饋指示不盲目加
  • zh-cn 翻譯補齊 —— my-tasks.json 整檔不存在,屬既有整體缺漏,不單點補
  • 全樹 pytest 115 個既有失敗 —— test-isolation 債,另案
  • after_request api_log update failed: cannot adapt type 'dict' —— 既有無關 bug(api_logs 寫入 dict 塞 VARCHAR),洗 log,另案
  • STG / POC 套 migration —— 本 arc 沒有任何 migration(純程式碼改動)

§8 行為規範提醒

  • 不切 branch:三 repo 都在 feature/scan-plugin-integration,branch 不對停下問 user
  • push 等 user 明示:三 repo 皆有未 push commits,不自動 push
  • 收尾等 user 下令:spec / SUMMARY / Notion 母案 / memory 都不自己跑
  • 各 repo 分開 commit、顯式 git add 檔名、禁用 -am
  • 改 BE service 要 kill -9 重啟python main_app.py,port 8000,log 在 log/app.log);FE 熱更新
  • 憑證禁入版控:本 arc 曾用暫存腳本解密 DEV 憑證做驗證,用完已刪;後續若再寫類似腳本,一律放 scratchpad 且用完即刪
  • 跨 repo 先讀目標 repo 的 CLAUDE.md(evidence-agent / FE 各有自己的 repo-local 慣例)

§9 座標

項目
Notion CM-929 3aa346da-4cd0-8191-9fba-c4fb2474820f(狀態:修正待驗證)
Notion 母案 CM-907 3a9346da-4cd0-81a5-be6f-f0ddd6d770c6
任務清單 data source collection://23c346da-4cd0-8041-955e-000bb6976dd2
DEV DB psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev(密碼查 BE .env DB_SECRET
agent 主機 192.168.50.123(ubuntu-lab-03),agent uid f8136df9-c062-45dd-8f39-ba91fc5e6e3a,base_url https://192.168.50.123:8443
agent 指紋 8af59700811b14da49afe4dbfe4c8a3e1e2fccb59d23602a22b2ce0d3d06c520
OpenVAS 123 上 Greenbone community docker stack,GMP 經 socat TLS bridge 對外開 9390(9392 是 GSA Web UI、3000 是 openvasd HTTP API,都不是我們走的)
租戶工具設定 config.tenant_detection_tool_configs id=8(tenant 102, openvas, last_test_result=success
相關 job 13342(auto, PROCESSING)、13362(manual, PROCESSING,已有 detection_executions id=1 running)
BE 起服務 cd BE && source .venv/bin/activate && python main_app.py(8000)
FE dev server 5180(HMR)

§10 給 fresh session 的超短 prompt

接手 CM-929 / FR-056.5(檢測工具「測試連線」改走 Agent)。

1. 先讀 docs/features/FR-056-2607-detection-tool-integration/handoff/2026-07-27-cm929-fr0565-mid-arc-handoff.md
   —— 從「🧭 原始需求 / WHY」讀起,讀完先回答 §0 的冷接自檢 4 問,答不出來回去讀,別碰 code。
2. 跑 §5 pre-flight,再跑 §3 步驟 2 確認 123 的 agent 是否已重 build。
   agent 回 500 = 還沒重 build(預期,非 bug)→ 不要去 debug probe 邏輯,提醒 user 跑 rebuild.sh。
3. 五條回饋(#1~#5)都已實作 commit 完,狀態「修正待驗證」,等複測。
   三 repo 各有未 push commits —— push 等我明示,不要自動 push。

不切 branch、收尾等我下令。