# 2026-08-27 FR-067 檢測多 Agent 分派 — 全案收口 SUMMARY

| 項目 | 內容 |
|------|------|
| Arc | FR-067 檢測多 Agent 分派（含 .7 排程、.8 每列自足、驗收回饋兩輪、v1.16.0 進版出貨、CM-1404 安裝包 P0 根修） |
| 期間 | 2026-08-23 開工 → 2026-08-27 收案（五個實作棒平行/接續＋四個協調者棒） |
| 版本 | **v1.16.0**（BE/FE tag、含 FR-066＋FR-067；agent v0.2.30）；`6d4ea5ee` 定版，SPEC 快照 `docs/specs/v1.16.0/` 已切 |
| Notion | 母案 CM-1350；子需求 CM-1351~1355／1372／1385；子任務＋回饋卡 CM-1357~1405 級距，驗收卡全 Done |
| 出貨 | 189（POC）v1.16.0 全新安裝；Harbor `guidant-ai-*:1.16.0` 六 tag；init image 經 CM-1404 根修重推 |
| 設計 | `../design.md`（D1–D10 決策表＋§5.10 每列自足＋§5.11 目標顯示）；逐棒軌跡 `FR-067-LOG.md` |

## 1. 全案範圍與 WHY

檢測工具平台（FR-056/FR-058）原本一次執行只派**一台** agent，且是隨手挑的——`_pick_agent` 無排序、不驗在線，挑中離線機時工單永遠 pending 沒有回收。客戶落地環境常見多個隔離網段（辦公網／機房網／DMZ），每網段一台 agent，現況 UI 無處指定「這批目標給哪台掃」、執行紀錄也查不到「這份報告誰掃的」。

**目標模型（user 拍板的模型 B）**：任務設置可設多組 assignment（每組＝一台 agent＋一批掃描目標＋排程時間）；按一次「開始執行」展開成 N 筆執行紀錄（維持既有「一筆執行＝一張工單」1:1 不動）＋一層執行群組（group）彙總；各 agent 各領各的單；全部終態才收口（彙總狀態／auto 完成／一封彙總通知）；失敗的組可單組重跑。

## 2. 各棒 commits 清單（四 repo）

### BE（compliance-manager-be，`feature/FR-067` → 已併 main）

| 棒 | commits | 內容 |
|----|---------|------|
| 第一棒 .1 地基 | `9f83973b` | Agent 清單補 capabilities/在線＋修自動挑機（新 canonical `get_dispatchable_agents()`） |
| 第二棒 .2 資料層 | `9bcd02f5`／`69e6906d` | migration 兩新表＋擴欄補索引；DDD 四層＋`compute_group_status` 彙總函式 |
| 第四棒 .4 展開收口 | `e6283532` | 展開＋D3 逐台驗證、列鎖收口＋auto gate＋409 地雷解除、重跑/取消三端點 |
| 第三棒 .3 綁定設置 | `f6e27786` | `agent_assignments` diff-sync＋enrich＋兩條讀取路徑（含 control-tree raw SQL） |
| 第五棒 .5/.7 BE | `0dc088d9` | 執行紀錄 group 化（順修兩處 N+1）＋彙總通知一封＋per-assignment 排程（D10） |
| 第六棒補修 | `b205e07a` | legacy 卡片分組鍵統一（`_execution_card_key()` 兩處共用）＋回歸測試 |
| 驗收第一輪 | `c408814f`／`5ec85bee`／`06e98031`／`8c24e212` | 目標欄依 param_schema 分流、profile 純下拉、測試連線可指定 agent、同 agent 多列＋per 列 profile 覆寫（＋migration 拿掉唯一約束） |
| 驗收續輪＋.8 | `5ca869e7`（內含 T-8.1）等 | transport per 列、CIDR/range 展開（依欄位語意分流）、每列 params 完整化 |
| 顯示回饋 1395/96/97 | `571b5b9e`／`f630282d`／`1be843a7` 等 | 台數 BE 供＋targets 欄位、檢測設定卡逐組分塊、重跑前置收窄到 assignment 線層 |
| 進版＋收尾 | `6d4ea5ee`（v1.16.0）／`9ae28f68`（CM-1402 SPEC）／`b0a468e2` | 進版三件套＋SPEC 四頁補行為＋交接 |
| CM-1404 根修 | `3b795703`／`1a79c3f0`／`de95b831`／`ccb647df`＋`eccc9204` | 基線庫補套→regenerate 產物→build gate→重出 bundle＋裝機驗 SOP；CLAUDE.md DB 清單改版 |

### FE（compliance-manager-fe，`feature/FR-067`）

`996ed22`（.3 Agent 分派 UI）／`cb458a6`（.5 群組卡片＋.7 執行端排程 UX）／`055d9c2`（設定端排程 chip）／`80e1c29`（十支 error code＋App.vue 明細併陳）／`9809d29`・`8633ed6`・`fe10773`・`3a9a213`・`d10ab5a`（第一輪 UX 五波）／`4081e58`（測試連線指定 agent）／`83248dc`（同 agent 多列）／`da33373`（.8 整份 param_schema 渲染）等。

### agent（evidence-agent）

`6fdd541`（stderr 帶出＋Rocky 檔名推 `rl`）／`ae9f4c9`（bump 0.2.29）／re-enroll 指令（0.2.30，CM-1376）。**v0.2.30 tag 已推**。

### test（compliance-manager-test）

CM-1403 半成品（factory 擴充＋`project-task-agent-dispatch.feature`＋traceability）——runner 額度中斷**未 commit**，收案時裁定併入 follow-up（見 §8）。

## 3. 關鍵設計決策（濃縮自 design.md D1–D10＋各棒 LOG）

| 決策 | 定案 | 一句話理由 |
|------|------|-----------|
| **D1 內部模型** | 維持「一筆執行＝一張工單」1:1，往上加 group 層 | 1:1 是回報鏈/報告/FE 渲染的大量隱含依賴，反轉成本高風險大 |
| **D2 assignment 載體** | 新子表（每列有 uid） | 單列 CRUD／按列稽核／兩入口併發不互蓋；JSONB 陣列做不到 |
| **D3 失效語意** | 執行前逐台驗證，任一不過整次 400、一次列全所有不合格機與原因 | 部分派出＝掃描殘缺卻顯示完成，比不派更糟；一次列全避免修一台再撞下一台 |
| **D4 彙總語意** | succeeded／partial_failed／failed／running 四態，單一真相函式 `compute_group_status` 所有入口共用 | 收口、重跑、取消、列表四個入口算同一件事，分散必漂移 |
| **D5 auto 完成** | group 全 succeeded 才 `complete_job`；partial 留人工；收口列鎖只收一次 | 部分網段沒掃到任務不該自動關；順解多筆各自觸發 409 的地雷 |
| **D6 通知** | group 收口發**一封**彙總（每 assignment 一列），三通道同步 | N 台 N 封是轟炸；單台情境體感不變 |
| **D7 執行粒度** | 整組啟動＋單組重跑＋單組/整組取消；彙總以每 assignment 最新一筆計 | 失敗只重掃失敗那台（掃描動輒數十分鐘）；後由 CM-1397 把重跑前置收窄到 assignment 線層 |
| **D8 相容** | **不 backfill 存量**；無 assignment 走 fallback 隱含單組（仍建 group，收口只有一套） | 開發驗證階段資料會重置；fallback 讓簡單場景零設定可用且不再挑到離線機 |
| **D9 地基缺口併入** | remote-agents API 補 capabilities/在線、執行紀錄 enrich agent、test_connection 對齊、`_pick_agent` 濾離線 | 四項都是本功能可用性的前置，拆開徒增協調 |
| **D10 排程** | assignment 加 `scheduled_at`，「group 照常立即建，延後的是 agent 領得到單的時間」——領單查詢加時間過濾，無背景 job | 展開/收口全沿用既有機制，D3 驗證仍在人在場當下做；無 APScheduler tenant context 坑 |
| **.8 每列自足** | 廢「任務層參數＋列覆寫」兩層模型，每列＝完整獨立工作設定（secret 跟列走、零列 fallback 廢掉、複製列＋新列帶預設值） | 混合 OS 各掃各基準下兩層覆寫講不清楚哪列在做什麼；「重用」靠 profile 庫引用對齊業界 |

實作層值得留名的細節決策：diff-sync 配對 uid 優先、`agent_uid` 次之（與 DB 唯一性語意同源）；`agent_assignments` `None`＝不動／`[]`＝清空；軟刪排在新增前（flush 順序）；`scheduled_at` 時區契約收斂 `common/util/scheduled_time.py`（naive 視為 UTC，FE 必帶 offset）；領單過濾另開 `list_dispatchable_for_agent` 不動 FR-058.7 下載授權那支；取消排程短路要同時看 execution scheduled＋工單 pending 兩條件；CIDR/range 展開**依欄位語意分流**（Nmap `hosts` 是執行主機要展開、`targets` 透傳）。

## 4. 行為差異（使用者視角）

- **任務可綁多組 agent＋目標**：任務設置的「Agent 分派」區可加多列（agent 下拉＋掃描目標＋per 列完整參數＋排程），同 agent 可開多列各掃各基準；不設＝自動挑一台**健康的**（不再挑到離線機）。
- **群組化執行紀錄**：一次執行一張群組卡（彙總徽章：綠/黃 partial/紅/執行中/排程中），每台一列帶 agent 名稱與掃描目標；單組重跑、單組取消、整組取消、排程組「立即開始」；重跑歷史收合。
- **一封彙總通知**：整組收口才發一封（每台一列：agent／目標／狀態／發現數／時間／失敗原因），三通道同步；不再 N 台 N 封。
- **per-assignment 排程**：每組可設「幾點才開跑」（排程 chip），時間到 agent 才領單；排程中可取消（不打 agent）或立即開始。
- **失敗訊息帶 stderr**（agent 0.2.29 起），不再空白訊息要上機重現。
- **掃描目標支援 CIDR 與末段範圍**，執行紀錄印原始寫法＋台數。

## 5. 驗收歷程

- **11 張子任務卡**（T-1.1~T-7.2，CM-1357~1374 區段）逐卡協調者驗收通過。
- **端到端驗收兩輪**（188 stack、雙 agent 雙網段）：第一輪六回饋卡（CM-1375 UX 五波／1377 測試連線指定／1378 stderr／1379 同 agent 多列／1380 重布）；續輪四卡（1376 re-enroll／1381 文件／1382 transport／1383 CIDR）；.8 三卡（1386~1388）＋文件二輪（1393）；最後三卡（1395 目標顯示／1396 逐組分塊／1397 重跑放寬）。**全部 user 逐張手測收 Done**。
- **進版鏈四卡**：1398（進版三件套）／1399（出包）／1400（189 主產品 user 親裝）／1401（189 LC 更新）皆 Done。
- **CM-1402**（.6 SPEC 四頁）／**CM-1404**（安裝包根修）收案時修正待驗證；**CM-1403**（E2E）半成品轉 follow-up。

## 6. v1.16.0 出貨與 CM-1404 安裝包事故

**出貨**：v1.16.0＝FR-066＋FR-067（branch 已含 FR-066，merge 一次帶齊）；發版 gate user 明示豁免。bundle 891M＋agent onepack 1.3G（含 seaweedfs）；189 POC 全新安裝（舊 postgres 原地封存，不遷舊資料）；189 LC 同步更新 main。

**P0 事故（CM-1404）**：189 裝出來 DB **缺 8 支 migration**——agent 心跳 500、capabilities 寫不進、檢測功能全殘。**根因**：init image 的 `02-schema/99-stamp` 是「generate 後 commit 進 repo」的產物，8/18 後未 regenerate，出包驗證驗了烙印 commit/Harbor digest/tar 內容卻**沒驗「裝出來的 schema 到 head」**——generate-then-commit 產物天生漂移，build 無 gate 抓不到。**根修**：基線庫補套→regenerate 三支產物→**build gate（stamp vs manifest 比對，突變測試驗過會紅）**→重出 bundle 同版 1.16.0→乾淨環境實裝驗 schema 到 head（136 支），SOP 寫進 `scripts/init/README.md`。

**t20 攔雷**（runner 順手攔下更大的雷）：一次性清理腳本 `t20` 放在 migration 目錄會被當可重跑，重跑會**清光檢測 Profile 庫**——已加「偵測已就位即中止」防呆＋移除與 CM-1322 決策矛盾的段落；未流出（已出貨的 189 實裝驗過 Profile 完好）。

## 7. 教訓彙整（五實作棒＋四協調者棒）

**先查再動手類**
- 「兩處各寫一套」的卡先 grep 是不是早就共用了（.1：FR-056.5 早收斂到 domain service，只需換一支）。
- 協調者開卡的環境/版本斷言**連續三次被 runner 實查推翻**（Nmap hosts 透傳假設／bump 1.15 已發過／CM-1404 基線庫實況相反）——開卡先查現況或明標「假設，實作前查證」；runner 的煞車（停下回報不硬做）是救命機制。
- 設計文件的舉例數字（900s 門檻）≠ 環境實值（DEV 60s×3=180s），驗收拿文件數字對會誤判。

**驗證紀律類**
- 突變測試抓到真漏網（.5 legacy 分組鍵改成常數測試照綠）——「跑綠了」≠「守住了」。
- FE 拿**真實資料**實跑抓到 BE `is_latest` 缺陷（10 筆 legacy 9 筆消失）——單元測試綠 ≠ 真資料形狀對；把 FE helper 抽出來餵真 API 回應跑，比讀 code 想像有效。
- 批次驗證的價值實證（.3 九情境一輪暴露兩坑）；best-effort 端點 FE 要讀回應內容不是只看 200。
- 出包驗證必驗「裝出來的 schema 到 head」，不能只驗烙印（§6）。
- 補套 migration 後 BE 必須重啟——psycopg cached plan 讓症狀與「沒套進去」一模一樣。

**平行作業類**
- 交接雙檔在平行 session 下要當共享檔：先 `git log -- <path>` 再 Edit 併入，不 `cat >` 整檔覆寫（.2 差點蓋掉 .1 的版本）。
- commit 前逐檔認領：`git status` 出現不認得的檔先比 mtime 判定是否平行 session 產物；顯式 add 也擋不住「別人已 stage 的一起進 commit」（協調者 `5ca869e7` 事故）——commit 前必看 staged 區。

**結構性改動類**
- 群組化會「引入」新迴歸不只是新增功能（既有 job 層取消鈕群組化後變成只取消第一台）——要問「哪些原本對的會因此變錯」。
- response 形狀改了要主動 grep FE 有沒有讀；同一分組概念的兩面（分卡 vs 判最新）鍵只能有一份，抽共用函式而非改其中一支。
- 「補上翻譯」不是無害動作：msg 帶動態明細的 error code，`t(code, {default})` 有翻譯就蓋掉 default，明細被吞。

**環境/維運類**
- agent 交付包有兩形態（systemd 原生／compose），排錯先 `systemctl list-units` 確認，別假設。
- endpoint 設定「含不含路徑」的契約要顯示端與接收端對齊（enroll token 照抄必 404）。
- ZAP 掃 SPA 會自噬（ajaxSpider 灌爆佇列連 OpenVAS 陪葬）；ajaxSpider 全域互斥＝同 daemon 必序列。
- 查 DB 先確認查的是哪套 PG（188 有老 25432 與 docker 兩套）；安裝包形態環境 DB 實況以容器內為準。
- 驗證用的寫入要挑自己建的資料或先記原值（.5 改了真任務名稱，走 API 還原）。

## 8. Follow-up 清單

**後續優化池六件（user 2026-08-26 全數定調「後續優化」不開卡）**：
1. **enroll token endpoint 顯示 bug**：`agent_enroll_token_service.py:62` 回含 `/api/1.0` 的原值，使用者照抄必 404（agent 端會再補路徑）。修法二擇一：BE 顯示時剝路徑，或 agent enroll 正規化容忍。
2. **legacy `is_latest` 語意**：legacy 筆（assignment_uid 全 NULL）被算同一條 latest 線；FE 保底已擋住消失問題、D8 資料會重置，影響低。
3. **OpenSCAP content 平台派發**（規模 M，正解方向已定）：沿 FR-059 file 型派發擴到 openscap——Ubuntu 24.04 apt 倉 content 停 0.1.71、26.04 只有 nightly、Rocky 檔名前綴 `rl`，50 台客戶場景逐台手放不可維運。
4. **Windows 無域憑證**：共用帳號模式只對有 AD 環境成立；短/中/長三層方案材料在 LOG 協調者棒 block（短期手冊＋前置腳本／中期 per-分派列憑證組復用 1379 地基／長期 PAM）。
5. **ZAP 排隊重試**：ajaxSpider 全域互斥下並發直接失敗不排隊。
6. **掃描設定下放指派人員**（等 PM）。

**其他 follow-up**：
- **190 E2E 專機未部署 FR-067**——部署後要回歸 `project-task-agent-dispatch.feature`（CM-1403 半成品在 test repo working tree：factory 擴充＋feature＋traceability，斷尾訊息說 C-1 單跑綠、失敗是 CM-1105 類平行負載 flake 非回歸）。
- **既有測案綁死 190 環境座標（另案處理）**：detection-profile-manage steps 硬寫工具 UID、tool-plugin-manage 需 blsemp1 帳號——188 跑必紅 18 條。
- **SPEC 站評估遷 MkDocs**：`render_html.py` 自製輪維護貴；features-site 已有 MkDocs+Material 前例。遷移要盤點：版本凍結快照多版導覽、`link_manual_refs`、Cloudflare webroot。
- **STG（188）是否遷 bundle 形態**未拍板。
- **PROD LC**：正式出版時找一台設備架（已裁示），連 PROD 簽章鑰四步組（[[feedback_multi_env_signing_key_pairs_with_public_key_commit]]）同時做。
- **安裝手冊發布前更新**：兩本客戶手冊（onprem 主產品＋agent）內容停在 1.15.0/0.2.28，發布前對齊版號字串＋多 agent 敘述＋升級章範例，md＋html 兩份重 build。
- **CM-1404 user 複測項**：拿 188 新 bundle 乾淨機實裝→接 agent 驗心跳/capabilities/Profile 10 筆/階段橫幅。
- **FR-068 系統 Log 轉發**：設計定案（D1–D6）＋CM-1389~1392 四卡就緒，user 裁示 FR-067 收完再派——**即本案收口後的下一棒**。
