# FR-058 協調者中繼交接 — 第一批已完成、第三批全數完成、第二批剩 GCB 三棒（2026-07-31）

> 🔄🔄 **2026-07-31 第三次更新（最新，本段優先於下方兩段）**。本輪的變化：
> - **新增一批平台級能力：互斥憑證組別**（BE `1d23082b` / CM-985 + FE `37967b4`）——`config.detection_tools` 新增 `credential_group_schema` JSONB，測試連線改由使用者明確選一組憑證、設定頁依宣告分區顯示。migration `2026-07-31-fr058-9-*` 三環境已套（實查：僅 inspec 有宣告，其餘六筆 NULL＝行為與現況相同）。詳見**新增的 §1.7**
> - **🟢 實測有實質進展（本輪最大的好消息）**——§5 第 0 項那條「零實跑驗證」的風險**已部分消解**：**DEV agent 已是 `0.2.14` 且 CINC 確實裝進去了**（`/usr/bin/cinc-auditor` 存在，T-2.2 最大的未驗證假設證實正確）、**WinRM 測試連線已對真實 Windows 主機（192.168.50.160）通過**。**SSH 側卡在目標主機的 sudo 權限設定**（環境問題，非 code 問題）。**尚未實跑過 `run()`，只驗到 probe**。詳見**新增的 §1.8**
> - **§2 新增一條教訓（2.7）**：改 serializer / model 後 BE 沒重啟 → 部分功能生效、部分沒有。症狀辨識法見該節
> - **agent `087fa41`：Nmap 證據改存人可讀 HTML**（容器內 xsltproc + 官方 nmap.xsl）——**這是實作 session 自行做的改案，未經派工，需決策者追認**（見 §5 新增項）
> - **§7 新增三件裁示**（7.4 T-3.2 縮成「驗證 + 寫文件」但非無條件、7.5 兩個 content 來源限制開後續卡、7.6 commit 標籤不一致不處理 / push 暫緩）
> - **§5 新增四項待辦**：「目標主機」文案語意模糊、`use_sudo` 不支援 sudo 密碼、Nmap HTML 改案待追認、兩個 content 來源限制
> - **§1.1 註記更正**：舊版說 BE working tree 有 `pyproject.toml` 誤加 `zaproxy`——**實查已不存在**（grep 零命中），該項風險已消失
>
> 🔄 **2026-07-31 稍晚更新**（本文首版 commit 於 `d81067f5`，之後實作 session 又跑完數棒）。本次更新的內容：
> - **第三批（Nmap）全數完成**——T-4.2（BE `16ae4ff0`）+ T-4.3（agent `883e5d1`）都已 commit，第三批零剩餘
> - **第二批 T-2.3 完成**（agent `eb352a7`）——InSpec/CINC `run()` 已寫完；第二批只剩 T-2.4（純 Notion 結案）與 GCB 三棒
> - **agent 已 bump `0.2.14`**（`6f97107`，標註「未出貨，等全案端到端」；DEV 上跑的仍是 `0.2.13`）
> - **🔴 §4 開工順位重大改寫**——決策者已**取消**「先驗收 T-2.2 才放行 T-2.3」這道 gate，改為一路往下跑、驗收全部併到最後。舊版 §4.1 的 T-2.2 gate **已不存在**，不要照著執行
> - **§5 新增本案目前最大的風險**：InSpec（T-2.1〜2.3）與 Nmap（T-4.1〜4.3）兩條線 code 全部完成，但**從未對真實環境跑過任何一次**

| 項目 | 內容 |
|------|------|
| 緣由 | FR-058（檢測工具擴充）的**第一批（ID 解耦 + 任務層敏感參數 + ZAP）已實質完成並實測驗收**；**第三批（Nmap）三棒已全數完成**；**第二批（InSpec/CINC → GCB）的 InSpec 段（T-2.1〜2.3）已完成**，剩 T-2.4 與 GCB 三棒。前一棒協調者 session 的 context 將滿，需冷接續作。 |
| 前一棒 | `docs/features/FR-058-2607-detection-tools-expansion/handoff/2026-07-30-fr058-planning-complete-handoff.md`（規劃完成交接）。**本文是它的續作**——那一份的 §3「三種情境」已走完情境 A，本文接續其後的所有事情。 |
| Branch | 三 repo 皆在 `feature/FR-058`，**全部未 push** |
| **本棒角色** | **協調、決策、派工、抽查。不寫 code、不跑測試。** 執行類（跑測試 / 部署 / 重建 image）與文件類（Notion 回寫 / Artifact republish / spec 撰寫）工作**全部派 subagent**。 |
| 接手前必讀 | 本文 §0 讀序（含硬 gate 與冷接自檢五問） |
| 下一個動作 | **接續 InSpec + Nmap 的合併驗收**（見 §4.1）。**DEV 已建到 `0.2.14`、CINC 已確認裝好、WinRM probe 已通過**；剩下三件卡點：① SSH 側等決策者在目標主機設 NOPASSWD sudo ② FE Dialog 兩欄改版完成後才驗收 ③ **`run()` 從未實跑，才是驗收的主體**。GCB 三棒等這輪過了才做 |
| 預估時間 | 理解現況並接住派工節奏：20–40 分鐘；接續合併驗收（image 已建好，剩實跑 `run()` + Nmap 掃描）：另加 60–100 分鐘 |
| HEAD（2026-07-31 實查） | BE `1d23082b`（領先 17）／FE `8d77d5e`（領先 5）／agent `087fa41`（領先 10）——三 repo 皆 `feature/FR-058`、皆未 push |

---

## 🧭 原始需求 / WHY

### 決策者的原始需求（一字不改）

> 「目前的檢測工具管理 `/plugin/tool-plugin-manage` 還需要增加下面工具——OpenSCAP for Windows 解決方案 / Nmap / GCB / SonarQube。請幫我分析跟規劃，開成新的需求放到 Notion。」

### 需求的演變過程（這是理解本案的關鍵，不讀會誤解範圍）

原始四項工具，經過調研與決策者裁示後**變成另外四項**：

| 階段 | 發生什麼 | 結果 |
|------|----------|------|
| 起點 | 決策者列出四項：OpenSCAP for Windows / Nmap / GCB / SonarQube | 四項 |
| 中途追加 | 決策者追加 **ZAP**（第五個工具） | 五項 |
| 裁示① | **SonarQube 本期不做**（日後另議） | 剩四項 |
| 調研發現 | 「OpenSCAP for Windows」**證實不可行**——OpenSCAP 官方已放棄 Windows 支援（`docs/windows.md` 明文標示 no longer usable，引擎層無 WinRM transport、無 Windows OVAL probe）。改由 **InSpec / CINC Auditor** 承擔此需求 | 工具換人，需求不變 |
| 裁示② | **ZAP 優先上線，其餘分批做**（取代原三線並行規劃） | 分批出貨 |

**最終四工具**：ZAP（DAST 網頁動態掃描）/ InSpec·CINC Auditor（Windows + Linux 組態檢測）/ GCB（政府組態基準 TWGCB，引擎複用 CINC）/ Nmap（網路埠掃描）。

### 大圖定位（本案在哪一棒）

```
FR-056 檢測工具整合平台（v1.11.0 上線）
   └ tool-agnostic 地基：後端對具體工具零知識、前端純 schema 驅動、加工具原則上只要一條 SQL seed
        ↓
FR-057 OpenSCAP SSH connector（v1.12.0 上線，2026-07-30 全案結案）
   └ 第一個真實工具落地：補齊 SSH 連線型態、setup_guide 欄位、多檔證據回收、兩條「靜默假成功」防護通道
        ↓
FR-058（本案）一次規劃四個工具 + 補平台唯一的結構性缺口
```

### 本案要解決的兩件事

**① 接入四個工具**——涵蓋三種連線型態：

| 形態 | 已上線 | 本案新增 |
|------|--------|----------|
| ① 連遠端服務 API | OpenVAS | **ZAP**（FR-058.1） |
| ② 遠端登入目標主機 | OpenSCAP（SSH / Linux） | **InSpec·CINC**（SSH + WinRM，FR-058.2）、**GCB**（複用 .2 引擎，FR-058.3）、**Nmap**（SSH，FR-058.4——2026-07-30 D9 改案後移入此列） |
| ③ agent 本機 CLI | 尚無 | **仍無**——原定 Nmap 走此型態，D9 改案後 `connection_type='CLI'` **至今仍無實際使用者** |

**② 補平台唯一的結構性缺口：任務層敏感參數**（FR-058.0）——現有設計把憑證與參數分兩處放，而**只有憑證那一條有加密**：

| 層級 | 存放位置 | 加密 | 適合放什麼 |
|------|----------|------|-----------|
| 租戶層 | `tenant_detection_tool_configs` | Fernet 加密 | ZAP 服務位址與 API key——全租戶共用、設定一次不動 |
| 任務層 | `job_execution_detection_tools.tool_params` | **明文** | 掃描目標、profile 這類非敏感參數 |
| 任務層敏感 | **原本不存在**（本案補上） | — | 「這次掃 A 網站用 A 的帳密，下次掃 B 網站用 B 的帳密」正好落在這一格 |

若直接把被掃網站的登入帳密塞進 `tool_params`，會發生兩件事：① 帳密明文躺在資料庫；② FE 執行紀錄視窗會顯示任務參數——BE 既有的 secret 剝除機制是針對**租戶層**設定寫的，任務層參數沒有這套保護，**等於帳密直接顯示在畫面上**。這不是 ZAP 專屬問題（GCB 每台主機帳密不同會撞同一格），因此當成**平台級能力**補上。

敏感參數的生命週期：**資料庫為密文、僅在派工傳輸與 agent 執行期間為明文、agent 用完即丟不落地、FE 一律剝除**。此能力已在第一批完成並實測（見 §3.4）。

### 本棒在大圖的位置

第一批已實測交付，第二批與第三批的實作**已派給外部 session 並行進行中**。**本棒是協調者與決策者**——放行 gate、審核 subagent 自主決定、處理跨批協調、產文件。**不是實作者，也不是測試執行者。**

---

## §0 接手讀序

### 0.1 🔒 先懂需求 gate（全讀，讀完才能碰任何東西）

1. **本文「🧭 原始需求 / WHY」全段**——特別是「需求的演變過程」那張表，不讀會搞不清楚為何最終工具清單跟決策者原話對不上
2. **本文 §3（本棒完整經過）全讀**——這是本文的核心。D4 與 D9 兩次改案、三個修掉的 bug、SPA 的營運級風險，全部發生在本棒任內，且都改變了原設計。**只讀前一棒的交接文件會拿到過期的認知**
3. `docs/features/FR-058-2607-detection-tools-expansion/design.md` **§2 決策表 D1–D12 全讀**（512 行檔案，只讀 §2 那一段）——D1–D12 是本案所有取捨的依據。⚠️ **D4 與 D9 已改案、D12 是本棒新增的，內容與前一棒交接時不同**

### 0.2 現況與待辦

4. 本文 §1（當前狀態盤點）→ §2（本棒教訓）→ §4（開工順位）→ §5（已知待辦與風險）
5. Notion 母案 CM-957：`https://app.notion.com/p/3ad346da4cd081859d2ed8b21ca2697c`——末段「🚀 分批出貨計畫」含批次安排與測試紀律

### 0.3 需要細節才開（不要一開始就全讀，會燒 context）

6. 兩份派工文件（要知道兩批各自被交代了什麼，或要回答實作 session 的提問時才開）：
   - `handoff/2026-07-30-fr058-batch2-dispatch.md`（第二批 InSpec/CINC → GCB，約 460 行）
   - `handoff/2026-07-30-fr058-batch3-nmap-dispatch.md`（第三批 Nmap，約 1000 行，含 D9 改案的完整 NPSL 論證）
7. 前一棒交接文件 `handoff/2026-07-30-fr058-planning-complete-handoff.md`——**§6 pre-flight 指令與 §7 前置確認仍然有效可照抄**，但 §1 狀態盤點與 §3 情境判斷已過期（情境 A 已走完）
8. `design.md` §3（現況接入點盤點）/ §4（四工具各自的 param_schema 與 connector 要點）/ §5（拆分與 20 子任務）/ §6（端到端驗收九項）
9. `discussion.html`（1060 行）——⚠️ **內容已多處過期**，見 §5 風險表

### 0.4 ✅ 冷接自檢（五問，答不出來回去讀 §0.1，不要碰任何東西）

1. **本案要解決什麼問題？** 提示：不只是「多加四個工具」，還有一個平台級的結構性缺口，而那個缺口已經在第一批做完了。
2. **第一批的三種掃描模式，各自驗收結果是什麼？為什麼是那個結果？** 提示：其中一種是 ❌，而且失敗的方式比「掃不到東西」嚴重得多。
3. **D4 與 D9 兩次改案分別由什麼觸發？各自的依據是什麼？** 提示：一個是官方 API 的能力邊界（有 issue 編號），一個是授權條款原文（有版本與章節）。兩者都不是「想改就改」。
4. **為什麼 GCB 三棒要等 InSpec 那條線實機驗收過了才做？** 提示：答案有兩層——一層關於 mock 測試蓋不到什麼，一層關於第二批的依賴結構。⚠️ 本題原問的是「為什麼 T-2.2 要驗收過才放行 T-2.3」，**那道 gate 已被決策者取消**（見 §4.1 開頭），但兩層理由原封不動地移到了 GCB 這道新 gate 上。
5. **本棒的角色是什麼？哪三類事情不該自己做？**

> 第 2 題是重點題：若答成「登入後掃描掃出空報告」，代表讀到的是**已被更正前的錯誤記載**，回去讀 §3.3。

---

## §1 當前狀態盤點

> ⚠️ 本節數字為 **2026-07-31 稍晚實查**（`git log` / psql / Notion fetch），已較本文首版（`d81067f5`）更新。**實作 session 若仍在跑，commits 會持續進來**——接手時務必先跑 §6 pre-flight 看實況，不要拿本節當現況。

### 1.1 三 repo git 現況（全部未 push）

> ⚠️ **本表為 2026-07-31 第三次更新的實查值**（舊值 BE 15 / FE 3 / agent 8 已過期）。

| Repo | Branch | 領先 origin | HEAD | 說明 |
|------|--------|------------|------|------|
| **BE** `compliance-manager-be` | `feature/FR-058` | **17** | `1d23082b` feat(cm-985): 互斥憑證組別平台能力（見 §1.7） | working tree 有 `CLAUDE.md` + `.claude/skills/big-feature-workflow/SKILL.md` 兩個 modified，**皆與 FR-058 無關**，不要 commit 進本案 |
| **FE** `compliance-manager-fe` | `feature/FR-058` | **5** | `8d77d5e` fix(fr058): 檢測工具摘要卡分開顯示執行主機與掃描目標 | ⚠️ working tree 有 **`src/views/plugin/ToolPluginManage.vue` 未提交**——那是**進行中的 Dialog 兩欄改版**（§5 新增項），是別的 session 的活，不要動也不要 commit |
| **agent** `evidence-agent` | `feature/FR-058` | **10** | `087fa41` feat(fr058): Nmap 證據改存人可讀 HTML（**未經派工的自主改案，待追認**） | working tree 乾淨 |

> 本輪（前次更新之後）新增：BE **2 個**（`2858b447` 前次文件更新 → `1d23082b` 憑證組別）、FE **2 個**（`37967b4` 憑證分組渲染 → `8d77d5e` 摘要卡 hosts/targets 分離）、agent **2 個**（`2c64a3b` probe transport 端到端釘樁 → `087fa41` Nmap HTML）。
>
> ⚠️ agent 的 HEAD 仍非 bump commit——`pyproject.toml` 版號維持 `0.2.14`，之後進來的三個 commit 都未再 bump（符合「未出貨、等全案端到端」裁示）。**DEV 上實跑的已是 `0.2.14`**（本輪已建並部署，見 §1.5）。

**✅ 舊版風險項已消解**：前一版 §1.1 記載「BE `pyproject.toml` 誤加了 `zaproxy` 相依，待決策者確認是否還原」——**本輪實查該相依已不在 `pyproject.toml`**（`grep zaproxy` 零命中），working tree 也不再有 `pyproject.toml` 的 modified。**§5 第 2 項的風險隨之消失，不需再處理**。

**另有一批與 FR-058 無關的既有 untracked 檔案**（v1.9-bugfix handoff、v1.8.0 交付 docx、`ddd-layer-audit/`、`security-audit-report-2026-07-17.html`、`variants.png` 等）——**那是別的 arc 留下的，不要清理、不歸本棒管**。

### 1.2 三批進度總表

| 批次 | 內容 | 卡號 | 狀態 |
|------|------|------|------|
| **第一批** | ID 解耦（FR-058.X）+ 任務層敏感參數（.0）+ ZAP（.1） | CM-958〜970 | ✅ **實質完成**，已實測驗收（三種掃描模式結果見 §3.4），DEV agent 已部署 `0.2.13` |
| **第二批** | InSpec / CINC（.2）→ GCB（.3） | CM-971〜979 | 🔄 **InSpec 段 code 全數完成**：T-2.1 ✅（`4d5226fa`）、T-2.2 ✅（`dcedb4f`）、T-2.3 ✅（`eb352a7`），三張卡皆「修正待驗證」；**剩 T-2.4（純 Notion 結案 CM-953）+ GCB 三棒（T-3.1 / T-3.2 / T-3.3）未開始** |
| **第三批** | Nmap（.4） | CM-980〜983 | ✅ **三棒全數完成**：T-4.1 ✅（`b9520f7b`）+ `requires_target_host` ✅（`9a15db96` + FE `2fe1ce6`）、T-4.2 ✅（`16ae4ff0`）、T-4.3 ✅（agent `883e5d1` + bump `6f97107`）。**零剩餘，全部待實機驗收** |

**剩餘子任務**：第二批 4 棒（T-2.4 / T-3.1 / T-3.2 / T-3.3）。第三批已無剩餘。**另有一批第三次更新新增的平台級能力（§1.7）與 FE Dialog 改版（§5）不在原 20 棒編號內。**

> ⚠️ **「完成」在此指 code 已 commit + mock 測試綠，不等於已驗證**。
> **2026-07-31 第三次更新的修正**：這句在 InSpec 側**已部分不成立**——DEV 已建到 `0.2.14`、CINC 確認裝好、WinRM probe 已對真實 Windows 主機通過（§1.8）。**但 `run()`（真正的掃描）仍從未跑過一次**，Nmap 整條線也仍零實跑。風險降級但未消除，見 §5 第 0 項與 §4.1。

### 1.3 三環境 `config.detection_tools` 實況（2026-07-31 實查，三環境完全一致）

| id | code | connection_type | status | requires_credentials | requires_target_host |
|----|------|----------------|--------|---------------------|---------------------|
| 1 | openvas | API | available | ✔ | ✘ |
| 2 | nessus | API | coming_soon | ✔ | ✘ |
| 3 | sonarqube | API | coming_soon | ✔ | ✘ |
| 4 | openscap | SSH | available | ✔ | ✔ |
| **5** | **zap** | API | available | ✔ | ✘ |
| **6** | **inspec** | SSH | available | ✔ | ✔ |
| **7** | **nmap** | SSH | available | ✔ | ✔ |

**DEV（188/`guidant_ai_dev`）、STG（188/`guidant_ai_stg`）、POC（189/`guidant_ai_poc`）三環境 id 與旗標完全對齊**，無 drift。

> `nmap` 的 `requires_credentials=TRUE` 正是 D9 改案的落地證據——改案前它應為 FALSE。
> `inspec` 的 `connection_type='SSH'` 是刻意的：該欄位語意實為「要不要問測試主機」，不對使用者顯示，真正的 transport 選擇放在 `param_schema`（見 §7.1）。

### 1.4 已套用的 SQL migration（`scripts/sql/`，**九支**，三環境皆已套）

| 檔案 | 內容 |
|------|------|
| `2026-07-30-fr058-1-zap-seed.sql` | ZAP 工具目錄 + param_schema + setup_guide（T-1.1） |
| `2026-07-30-fr058-2-fix-zap-login-required-flags.sql` | ZAP 登入四欄改條件必填（§3.2 修正②） |
| `2026-07-30-fr058-3-zap-setup-guide-spa-limitation.sql` | setup_guide 補 SPA 限制說明 |
| `2026-07-30-fr058-4-zap-setup-guide-spa-shutdown-correction.sql` | **更正**：SPA 登入掃描會打掛共用 ZAP（§3.3） |
| `2026-07-30-fr058-5-detection-tools-requires-credentials.sql` | `requires_credentials` 宣告欄位（T-4.1，D9 平台能力） |
| `2026-07-30-fr058-6-inspec-seed.sql` | InSpec / CINC 工具目錄（T-2.1） |
| `2026-07-30-fr058-7-detection-tools-requires-target-host.sql` | `requires_target_host` 宣告欄位（解 FE 對 connection_type 的耦合） |
| `2026-07-30-fr058-8-nmap-seed.sql` | Nmap 工具目錄（SSH 型，D9 改案後，T-4.2） |
| **`2026-07-31-fr058-9-detection-tools-credential-group-schema.sql`** | **（第三次更新新增，第九支）** `credential_group_schema` JSONB 宣告欄位 + 回填 InSpec 的憑證組別宣告 + `config_field_schema` 的 `group` / `hint`（§1.7）。**三環境已套並實查**：`inspec` 有宣告、其餘六筆 NULL |

### 1.5 Agent 部署現況（**只有 DEV 是新版，這是刻意的**）

| 主機 | 角色 | 部署版本 | 說明 |
|------|------|---------|------|
| **192.168.50.123** | DEV | **`0.2.14`**（2026-07-31 第三次更新已建並部署） | 含 Nmap connector + InSpec `run()`。**image build 通過、`/usr/bin/cinc-auditor` 實查存在**——T-2.2 最大的未驗證假設已證實（§1.8） |
| 192.168.50.122 | STG | `0.2.11` | **刻意未更新**——決策者裁示 DEV 驗完才輪 121/122，不重複付部署成本 |
| 192.168.50.121 | POC | `0.2.11` | 同上 |

agent 版號沿革：`0.2.11`（FR-057 出貨）→ `0.2.12`（FR-058 第一批：ZAP connector + factory code 解耦）→ **`0.2.13`**（ZAP 主動掃描修正，＝**目前 DEV 實跑版**）→ **`0.2.14`**（`6f97107`，第三批 Nmap connector；版號四處已同步：`pyproject` / `config` / `docker-compose` 兩處 / `rebuild.sh`）。

⚠️ **舊記載「`0.2.14` 只入版控、未出貨、三台都沒有」已過期**——2026-07-31 第三次更新時 **DEV（123）已建好並部署 `0.2.14`**。121 / 122 仍停在 `0.2.11`（刻意，等全案驗收）。`0.2.14` 這個版號同時涵蓋 Nmap connector、InSpec `run()`、probe transport 釘樁與 Nmap HTML 改案（後三者都未再 bump）。

### 1.6 Notion 卡片索引

母案：**CM-957** — `https://app.notion.com/p/3ad346da4cd081859d2ed8b21ca2697c`

| 子需求卡 | 標題 | Notion URL | 子任務卡 |
|---------|------|-----------|---------|
| **CM-958** | FR-058.X `_TOOL_ID_TO_CODE` 解耦（D11） | `https://app.notion.com/p/3ad346da4cd08164b0f9f05776a88562` | CM-959、CM-960 |
| **CM-961** | FR-058.0 任務層敏感參數 | `https://app.notion.com/p/3ad346da4cd0810fae51e7f192d5ad85` | CM-962〜965 |
| **CM-966** | FR-058.1 ZAP | `https://app.notion.com/p/3ad346da4cd081f48410d617dee61ba9` | CM-967〜970 |
| **CM-971** | FR-058.2 InSpec / CINC Auditor | `https://app.notion.com/p/3ad346da4cd0811faa85fd15a38df059` | CM-972〜975 |
| **CM-976** | FR-058.3 GCB | `https://app.notion.com/p/3ad346da4cd08120b9f1cf9b1f976847` | CM-977〜979 |
| **CM-980** | FR-058.4 Nmap | `https://app.notion.com/p/3ad346da4cd081159daae65d4b08c80d` | CM-981〜983 |
| **CM-984** | **（新增）FR-058 後續：ZAP 登入後掃描支援 SPA** | `https://app.notion.com/p/3ad346da4cd08101984bd520657eadd2` | 無（後續另案，Not started） |

> ⚠️ Notion 為 free plan，**不支援 SQL 查詢資料源**（`query-data-sources` 對此 DB 回空陣列）。要查卡片狀態一律用 `notion-search` 找到卡再 `notion-fetch` 開，或直接開母卡看子卡清單。

**子任務卡狀態（2026-07-31 稍晚 fetch 實查）**：

| 卡 | 狀態 | 備註 |
|----|------|------|
| **CM-974**（T-2.3 InSpec `run()`） | **修正待驗證** | 已回寫完整白話說明 + **五條「未經實跑驗證的假設」對照表**（reporter json 輸出位置 / 報告結構 / 退出碼語意 / exec 參數形式 / SIGTERM 行為）。驗收時逐條對照 |
| **CM-982**（T-4.2 Nmap seed） | **修正待驗證** | 已回寫，含三環境套用結果與 live API 實查 |
| **CM-983**（T-4.3 Nmap connector） | **修正待驗證** | 已回寫，含 68 項單元測試 + 12 輪突變測試，並列出四項待端到端驗證 |

> ⚠️ **子需求母卡（CM-971 / CM-976 / CM-980）狀態仍是 `Not started`**——實作 session 只更新自己那張子任務卡，不動母卡。**母卡狀態不代表進度**，看進度要開子任務卡或看本文 §1.2。這是既有慣例，不是遺漏。**母案 CM-957 本身狀態也是 `Not started`**（2026-07-31 fetch 實查），同理不代表進度。

> **新增卡 CM-985**（互斥憑證組別平台能力，見 §1.7）——不在原 20 棒編號內，是實測過程中暴露的平台缺口另開的。

---

### 1.7 🆕 新增的平台級能力：互斥憑證組別（CM-985）

> **2026-07-31 第三次更新新增。BE `1d23082b` + FE `37967b4` + migration `fr058-9`（三環境已套）。**

**這不是 InSpec 專屬修補，是平台級能力。** InSpec / CINC 是平台**第一個「一個工具帶兩組互斥憑證」**的工具（SSH 憑證用於 Linux 目標、WinRM 憑證用於 Windows 目標，使用者通常只填打算掃的那一組），**GCB 會是第二個**——所以做成宣告驅動的平台能力，而非在 code 裡寫 InSpec 特例。

**關掉的兩個缺口**：

| # | 缺口 | 症狀 | 解法 |
|---|------|------|------|
| **①** | **測試連線不知道該用哪組憑證** | BE `test_connection()` 下發的 probe 參數只有 `{"host": ...}`、**不含 transport**；agent connector 的 `_resolve_transport()` 未指定時**預設 SSH**。只填了 WinRM 憑證的使用者按「測試連線」，收到「本任務的連線方式為 SSH（Linux 目標），但未填寫 SSH 稽核帳號」——**訊息本身很清楚，但它回答的是使用者沒問的問題** | 新增 `credential_group_schema` 宣告欄位，**FE 在測試連線對話框讓使用者明確選一組** |
| **②** | **設定頁 11 個欄位平鋪一列**，看起來全部必填 | 使用者看不出「這 6 個一組、那 5 個一組，只填一組就夠」 | `config_field_schema` 逐欄加 `group` + `hint`，FE 依宣告分區渲染 |

**兩個關鍵設計決定（後續有人要改時要知道理由）**：

1. **不做自動偵測，由使用者明確選。** 「看使用者填了哪組就猜」在**兩組都填齊**時無從分辨——而那是真實情境（客戶同時有 Linux 與 Windows 目標）。猜錯產生的正是本案要消滅的誤導訊息。**多問一題的代價遠低於答錯一題。** 未選定時測試連線鈕保持 disabled，不落回預設值；宣告了組別卻沒選 / 選了不存在的 key 一律明確回失敗，**不預設挑第一組，也不記 `last_test_result`**（沒真的測過）。
2. **FE 只送組別 key，probe 參數由 BE 依自己的 DB 宣告組出。** request body 只收 `credential_group`，**FE 不得帶 probe 參數的 key 或 value**——probe 參數會一路流到 agent connector 決定程式路徑（其他工具則用來組檔案路徑），**讓客戶端自由命名參數等於開一個注入面**。FE 側也不硬寫任何協定字面值（`transport` / `SSH` / `WinRM`），全部由後端宣告驅動。

**Schema 形狀**：`config.detection_tools.credential_group_schema` JSONB NULL，`{probe_param_key, prompt_label, groups:[{key, label, description, probe_param_value}]}`。

**為什麼不塞進 `config_field_schema`**：那是一個 JSONB **陣列**，加頂層 metadata 會把形狀從陣列變成物件，**所有讀取端都要跟著改**。

**零影響保證**：`NULL` / 缺席＝該工具無互斥組別，測試連線與設定頁行為**與今日完全相同**。三環境實查：**只有 `inspec` 有宣告，其餘六筆（openvas / nessus / sonarqube / openscap / zap / nmap）全為 NULL**——既有工具零影響。

**agent 側零 code 異動**——`_resolve_transport()` 本來就吃 probe params，缺的只是 BE 沒傳。agent `2c64a3b` 只補了端到端釘樁測試防迴歸。

### 1.8 🟢 實測進展（2026-07-31 第三次更新，本輪最重要的一節）

> **§5 第 0 項那條「InSpec 與 Nmap 兩條線零實跑驗證」的風險，本輪已部分消解。** 但**尚未實跑過真正的掃描（`run()`），只驗到 probe（測試連線）**。

| 項目 | 結果 | 意義 |
|------|------|------|
| **DEV agent 已建到 `0.2.14` 並部署** | ✅ | image build 通過 |
| **CINC 確實裝進 image**（`/usr/bin/cinc-auditor` 實查存在） | ✅ **T-2.2 最大的未驗證假設已證實正確** | Dockerfile 的 CINC 安裝指令**從沒 build 過**、omnitruck 腳本參數與安裝路徑**都是照官方文件推測的**——§5 第 0 項的假設 ① **已消除** |
| **WinRM 測試連線通過**（對 192.168.50.160 Windows 主機） | ✅ | **D6 雙 transport 的 WinRM 半邊已驗證**——連線、認證、憑證解析整條 probe 鏈路可運作 |
| **SSH 測試連線卡在 sudo** | ⚠️ **環境問題，非 code 問題** | 錯誤訊息：`sudo: I'm sorry audit-scan. I'm afraid I can't do that`。**SSH 連線與認證都已通過**，卡在目標主機的 `audit-scan` 帳號**沒有 NOPASSWD sudo 權限**。決策者正在目標主機上設定 |
| **`run()`（真正的掃描）** | ❌ **從未跑過** | **這才是驗收的主體**——CM-974 卡上那五條未驗證假設（reporter json 輸出位置 / 報告結構 / 退出碼語意 / exec 參數形式 / SIGTERM 行為）**全部仍未驗證**。尤其第 2 條「報告結構不符會統計全零但證據照常上傳」，只有實跑才看得出來 |
| **Nmap 整條線** | ❌ **仍零實跑** | connector 與 seed 都完成，但沒對真實網段掃過 |

**接手時的正確認知**：**probe 通過 ≠ 這條線可用。** probe 只驗了「連得上、認證過、引擎在」；`run()` 才驗「profile 跑得動、報告解得開、統計數字對、證據上得去」。**不要因為看到「WinRM 測試連線成功」就把 InSpec 當成驗完了。**

---

## §2 本棒的教訓（可複用，不是流水帳）

### 2.1 subagent 自報完成不等於正確

本棒**每一次**都實際開檔 / 查 DB / fetch Notion 抽查，而且**每次都抓到東西**：漏傳 code 的第二條路徑、required 旗標與 agent 端檢查不一致、SPA 後果記載與實測不符。

**「Notion 卡改成修正待驗證」與「commit 進來了」都只證明有人做了事，不證明做對了。** 抽查的成本遠低於錯誤流到下一批的成本——修正① 那個漏掉的 probe 路徑若沒抽到，會在第二、三批各自的測試連線功能上重現一次。

### 2.2 推測要標明是推測，並容許被實測推翻

`does_not_exist` 那次，主 session 的初始假設是「contextid 不存在」，**被實作 session 的實測推翻了**（真根因是爬取狀態誤判的三層鏈，見 §3.2 修正③）。

**那是對的做法。** 協調者離現場遠、看到的是二手轉述，猜錯很正常；問題只在「有沒有把猜測講成結論」。派工時把假設標明為假設、要求對方以實測為準，比堅持自己的判斷有價值得多。

### 2.3 「錯誤延後到最貴的地方才爆」是本案反覆出現的模式

三個修正都是同一個形狀：

| 問題 | 錯誤原本在哪裡爆 | 修正後在哪裡爆 |
|------|----------------|--------------|
| ZAP 登入四欄 required 不一致 | agent 執行期（派工後 5 分鐘） | FE 表單存檔當下 |
| probe 漏傳 tool code | 測試連線時靜默走 fallback | payload 缺欄位即可查 |
| `does_not_exist` 三層鏈 | 查狀態時回一個看不懂的字串 | `scanAsUser` 回非數字即刻拋錯 |

**每次修正都一併補上「在發生當下就明確報錯」的機制**，不是只把當次症狀壓掉。這個模式在第二批的 T-2.2 也被延續了——connector 的 `probe()` 第 ① 段就是憑證完整性檢查，訊息指名道姓說出缺哪個 transport 的哪一欄（見 §7.3）。

**這是本案最該帶進第二、三批的一條**：審核任何實作時都問一句「這個錯誤最早能在哪裡被發現？現在是在那裡被發現的嗎？」

### 2.4 文件與實測不符時要主動更正，不能留著

SPA 那次，原記載「掃出空報告」與實際「打掛共用 ZAP」的**嚴重度差了一整個等級**——前者是報告品質問題，後者是營運級風險。不改的話會在交付時給客戶錯誤資訊。

**發現既有記載被實測推翻，當下就更正 design.md / setup_guide / Notion 三處**，不要留到收尾一起做——中間任何人讀到那份文件都會拿到錯的認知。

### 2.5 非決策類工作一律派 subagent

本棒自己只做：讀 / 判斷 / 拍板 / 派工 / 抽查。以下全部派出去：

- 跑測試、重建 image、部署 agent、套 migration
- 寫文件（design.md 改案段、派工交接文件、discussion.html 更新）
- Notion 回寫（卡片狀態、「已完成修正」補充說明）
- Artifact republish

**派 subagent 時一律不帶 `model` 參數**（繼承 1M context model）；**文件產出類必須在派工單就寫明「分段寫檔：先 Write 骨架再多次 Edit，不要一次寫完整份」**——本專案已三次遇到 subagent 一次寫大檔時 API 中斷。

### 2.6 昂貴前置只付一次（本棒沿用並強化的紀律）

**重建 agent image + 部署 + 等 300 秒心跳**是整個流程最貴的動作。本棒的做法：

- 第一批驗收**只部署 DEV（123）**，121/122 留到全案驗完再一起更（見 §1.5）
- 第二、三批的端到端驗收**整批延後到全案最後**，做中只做便宜檢查（import / 語法 / mock 單元測試 / SQL 套 DEV 後 SELECT）

**唯一的例外是 T-2.2**——見 §4.1，那是刻意破例，理由是地基風險不對稱。

### 2.7 🆕 「部分功能生效、部分沒有」＝新舊欄位混合的徵兆，先查 BE 有沒有重啟

> **2026-07-31 第三次更新新增。這是本輪排查花掉最多冤枉時間的一次。**

**經過**：§1.7 的憑證分組功能做完後，畫面上**分組標題沒出現**。查了 FE（渲染邏輯正確）、DB（`credential_group_schema` 有值）、migration（三環境都套了）——**全部正常**。

**真根因：BE 行程沒重啟。** `credential_group_schema` 是當天 **10:51** 才加進 serializer 的，而 BE 行程是**前一晚 23:58** 起的。`main_app.py` 設 `use_reloader=False`，**不會自動重載**——跑的還是舊 serializer，API response 裡根本沒有那個欄位。

**症狀特徵非常有辨識度，值得記住**：

| 現象 | 讀哪個欄位 | 舊 serializer 有嗎 |
|------|-----------|-------------------|
| **hint 有顯示** ✅ | `config_field_schema`（既有欄位，本次只加了 `group` / `hint` 兩個 key 進去） | 有 → 照常回 |
| **分組標題沒顯示** ❌ | `credential_group_schema`（**本次新增的欄位**） | 沒有 → response 缺這個 key |

**可複用的判斷法**：**同一個功能「部分生效、部分沒生效」，而兩者的差別剛好是「讀既有欄位 vs 讀新增欄位」——先去看 BE 有沒有重啟，不要先懷疑 FE 或 DB。** 純新功能整個沒反應反而容易想到重啟；這種**半生效**的形狀最會誤導人往下游查。

**紀律**：**改 serializer / ORM model / entity / mapper 之後，BE 一定要重啟**（`kill -9` 舊 pid 再起，orphan pid 會卡 port 8000）。這條在 CLAUDE.md 已有規範，本輪是實際踩到的案例。

---

## §3 本棒完整經過（決策脈絡，讀懂了才接得住）

> 本節是本文最重要的一段。**本棒沒有寫任何一行 code**，做的全是判斷與拍板；但這些判斷改了設計、改了文件、也改了下一批的做法。**冷接時若只看 git log 會完全看不出這些**。

### 3.1 D4 改案：ZAP 證據由 PDF 改 HTML

**觸發點**：agent 側實作 ZAP connector 時，實作 session 回報原定案的 `reports.generate` 走不通——該 API **只把報告檔案寫進 ZAP daemon 主機的磁碟、回傳一個路徑字串**，官方沒有「把檔案內容回傳給呼叫端」的端點（ZAP issue #7821 明確記載）。

**為什麼這是硬阻礙**：D3 已定案 ZAP 走「客戶自備 daemon、agent 只負責連上去」的遠端部署模型。在那個模型下，報告落在 ZAP 主機的檔案系統上，**agent 根本拿不到**。

**拍板**：改走 `core.htmlreport()` + `core.jsonreport()` 雙格式——兩者都回傳 bytes，agent 拿得到。HTML 上傳當證據（檔名 `ZAP掃描報告_<target>_<date>.html`），JSON 只在記憶體解析 summary 不上傳。**精神對齊 OpenVAS connector 既有模式，ZAP 端零設定，D3 部署模型不動。**

**被排除的方案**：

| 方案 | 排除理由 |
|------|---------|
| PDF + 要求客戶開檔案傳輸通道（SMB / SFTP 取 ZAP 主機檔案） | 違反 D3 部署模型——把「只要能連上 API」變成「還要開一條檔案通道」，增加客戶部署負擔與資安面 |
| 檔案傳輸為主、HTML 退回 | 雙路徑的維護與測試成本，換來的是一條罕用的退路，不划算 |

**接受的代價**：HTML 證據**可下載、不可預覽**（證據池的預覽器不吃 HTML）。第一批以此為出貨標準；日後若要預覽，可在 agent 端用既有 LibreOffice 轉 PDF 補上。

**已同步**：design.md D4 + 變更紀錄、discussion.html、Notion CM-957/966/969、Artifact 線上版。

### 3.2 第一批端到端驗收與過程中修掉的三個問題

兩個實作 session 完成後，重建 agent image `0.2.12` 並**只部署 DEV（123）**——決策者裁示 DEV 驗完才輪 121/122，不重複付部署成本（見 §1.5）。驗收過程中發現並修掉三個問題：

#### 修正① probe 漏傳 tool code（BE `082a02d8`）

T-X.1（派工 payload 夾帶 `detection_tool_code`）只改了**心跳派工**那一條路徑，漏了「測試連線」這第二條——雲端直推 agent 的 `agent_probe_client.py`。結果是測試連線時 agent 收不到 code，退回 id fallback。

**通則化教訓**：雲端 → agent 有**三條通道**——① 心跳領工、② probe 直推、③ cancel 直推。**往派工 payload 加欄位時要逐條檢查**，不能只改最顯眼的那一條。

#### 修正② ZAP 登入四欄 required 與 agent 端檢查不一致（BE `2d14ccb7`）

seed 把登入四欄（`login_url` / `login_username` / `login_password` / `logged_in_indicator`）標成 `required: false`，但 agent 執行時把它們**當必填擋**。使用者在 FE 表單留空可以存檔、可以派工，**要等到 agent 跑起來才爆**。

**改法**：改為**條件必填**（靠 `condition` 控制——選了「登入後主動」才變必填）。三環境已套（`fr058-2` migration）。FE 那側同時補了任務參數存檔的必填驗證（FE `8220852`），把錯誤從 agent 執行期提前到表單。

#### 修正③ ZAP 主動掃描 `does_not_exist`（agent `1feb6c7`）

**這是本棒最有價值的一次追查，而且推翻了主 session 自己的初始假設。**

主 session 看到 `does_not_exist` 的第一反應是「contextid 不存在」。實作 session 實測後回報**不是**，真根因是一條三層鏈：

| 層 | 發生什麼 |
|----|---------|
| ① | `ajaxSpider.status` 是**全域狀態、不吃 scan id**。發出爬取指令後、ZAP 還沒把狀態翻成 `running` 的空檔，connector 讀到的是**上一次殘留的 `stopped`**，於是誤判「已經爬完了」 |
| ② | 爬取實際上沒跑，站台樹是空的。此時呼叫 `scanAsUser` 會回 **HTTP 200 + 一個錯誤字串**（不是拋例外、不是非 200） |
| ③ | 那個錯誤字串被當成 scan id 收下，拿去查狀態 → 回 `does_not_exist` |

**修法**：白名單式回傳值驗證——**scan id 只接受純數字**，非數字一律當錯誤即時拋出。

**附帶抓到的第二個 bug**：`setForcedUserModeEnabled(True)` 開了沒關，而那是 **ZAP daemon 的全域開關**，會造成跨任務污染（下一個任務莫名以某個 user 身分發請求）。一併修掉。

修正後 bump `0.2.13` 並重新部署 DEV。

### 3.3 SPA 登入掃描的設計缺口（D12）——本棒發現的最嚴重問題

**第一階段發現（設計缺口）**：實測揭露 `param_schema` 的**傳統表單登入模型對 SPA 天生不成立**，三個原因彼此獨立、必須同時解決才會通：

| # | 現象 | 根本原因 | SPA 需要的做法 |
|---|------|---------|--------------|
| ① | 填了帳密但 ZAP 從未登入成功 | SPA 的登入是 JS 呼叫 API 取 token，頁面上沒有傳統表單提交可供 ZAP 對付 | **json-based 認證** |
| ② | 就算登入成功，後續請求仍是未登入身分 | SPA 多用 JWT 存 localStorage、靠 `Authorization` header 帶；ZAP 預設 session 管理是 cookie-based | **script-based session management** |
| ③ | ZAP 誤判未登入、反覆重跑登入 | `logged_in_indicator` 比對的是 **HTTP 回應的原始內容**，SPA 初始 HTML 是空殼、登入後文字要 JS 渲染才進 DOM | 改用 API 回應特徵等非 DOM 判定 |

**拍板（D12）**：本案不做半套，列後續另案 **Notion CM-984**。理由是**沒有中間地帶**——只做 json 認證不做 session 管理，結果是認證送出去了但 token 帶不進後續請求，掃描結果與完全不做相同，卻已付出工程成本並在 UI 上留下「看起來支援了」的誤導。

**第二階段：後果比原記載嚴重得多（BE `2581b4f6` 更正）**

原文件寫的是「掃描仍會完成、只是涵蓋範圍侷限於登入前頁面」。**這是錯的。** 後續實測發現第 ③ 項的重跑登入是**無限迴圈**，認證失敗持續累積，達到 ZAP 2.17.0 Insights 機制門檻後 **ZAP daemon 主動關閉自己**：

```
Shutting down ZAP due to High Level Insight: HIGH : EXCEEDED_HIGH :
  http://<target> : insight.auth.failure : 100
→ Discard Session → ZAP 2.17.0 terminated. → zap exited with code 2 (restarting)
```

**而 ZAP 是全租戶共用服務**——單一 SPA 登入任務會**連帶打斷同一台 ZAP 上其他正在執行的掃描**（實測同時發生：另一任務在 ZAP 重啟空檔收到 `Connection refused`）。這已不是報告品質問題，而是**營運層級風險**。

**範圍界定（重要，不要誤擴大）**：**僅限登入後掃描模式**。被動與主動模式不建 context、不設認證、不比對特徵字串，不累積認證失敗，碰不到 Insights 門檻。

**連帶處置**：`setup_guide` 從「登入後掃描僅支援傳統表單登入」改為明確的「**請勿對 SPA 使用登入後掃描**」（`fr058-4` migration）；design.md D12 補完整因果鏈與 log 摘錄。

**connector 端自我保護（偵測認證失敗異常累積時主動中止）決策者裁示「只記錄不實作」**——列為待辦，見 §5。

### 3.4 第一批最終驗收結果

| 掃描模式 | 結果 | 說明 |
|---------|------|------|
| **被動掃描** | ✅ 通過 | HTML 證據正常進證據池，summary 有 alert 統計 |
| **主動掃描** | ✅ 通過 | 修正③（`does_not_exist` 三層鏈）後重測通過 |
| **登入後掃描** | ❌ 不適用 | SPA 不支援，且會打掛共用 ZAP（§3.3）。已記錄為 CM-984 後續案 |

**第一批實質完成。** 任務層敏感參數（FR-058.0）的三處剝除、ID 解耦（D11）、既有工具迴歸皆已驗過。

### 3.5 產第二批與第三批派工文件，兩批並行派出

第一批收口後，產出兩份派工交接文件並同時派出：

- `handoff/2026-07-30-fr058-batch2-dispatch.md`（InSpec/CINC → GCB，7 個子任務序列執行）
- `handoff/2026-07-30-fr058-batch3-nmap-dispatch.md`（Nmap，3 個子任務）

兩份都帶了「第一批帶來的新事實」段（factory 已改依 code 取、payload 已夾帶 code、敏感參數平台能力已就緒、D4 教訓通則化），以及跨批共用檔的協調規則。

### 3.6 D9 改案：Nmap 由 CLI 型改 SSH 型

**觸發點**：第三批實作前發現原設計自相矛盾——D9 原定 Nmap 走 `connection_type='CLI'`（agent 本機執行子行程）、**不 bundle 進 image、由客戶自備**。但 **agent 跑在 Docker container 內，看不到主機上安裝的 nmap**（容器有獨立檔案系統）。「不 bundle」與「不能裝進容器」兩個條件互相矛盾，原設計在容器化部署下根本無法成立。

**查證 NPSL 後確認不能 bundle，而且理由比原記載強得多。** 原文件只記了「NPSL 授權」四個字。實際查 NPSL v0.95 §3（`https://svn.nmap.org/nmap/LICENSE`），衍生作品定義明文列舉這一條：

> `Is designed specifically to execute Covered Software and parse the results (as opposed to typical shell or execution-menu apps, which will execute anything you tell them to).`

我們的 connector 正是「專門呼叫 nmap 並解析其 XML 輸出」，**正中此條**。因此 GPL 慣用的「只用子行程呼叫、不連結函式庫所以不算衍生」這個免責論點在 NPSL 底下**不成立**——該條款是特意加來堵掉這個論點的（NPSL 基於 GPLv2 但加了額外限制）。

**但同一條款末段留了出口**：軟體若執行的是「使用者早已安裝在自己系統上的 nmap」，授權方不主張控制（`Licensor does not purport to control through this license any software which does not require the rights granted herein`）。**SSH 型正好落在這個描述裡**——nmap 是客戶裝在自己主機上的，我們只是遠端下指令。

**拍板**：Nmap 改走 `connection_type='SSH'`，與已上線的 OpenSCAP 同構。

**被排除的方案**：

| 方案 | 排除理由 |
|------|---------|
| 掛載主機 binary 進容器（`-v /usr/bin/nmap:...` + 共享函式庫目錄） | nmap 非靜態連結，依賴 libssl / libpcap / liblua 一串共享函式庫；掛主機 lib 進容器是跟 glibc 版本賭運氣，還可能蓋掉容器自身函式庫把 agent 弄壞。架構也綁死 x86_64 |
| 客戶自建一層 image（以我方 image 為 FROM，自行 `apt install nmap`） | 技術與法律上都成立，但客戶每次 agent 升版都要改 Dockerfile 重 build，是持續性負擔；且「誰裝進去的」雖是法律關鍵，技術結果與我方 bundle 完全相同，這種形式上的區隔在交付溝通上彆扭 |
| 購買 Nmap OEM 授權 | **本案不採**，未評估成本。若日後產品策略需要零客戶設定的 nmap 整合，這是唯一能 bundle 的合法途徑，**列為備查** |

**連帶影響**：nmap 改為 `requires_credentials=TRUE`（DB 實查已確認，見 §1.3）；T-4.1 的零憑證放行**平台能力保留**（是平台級能力，只是換掉第一個使用者）；`connection_type='CLI'` **至今仍無實際使用者**；agent connector 範本從「本機子行程」改為對照 `openscap.py` 的 SSH 多目標模式。

已寫進 design.md D9 + 變更紀錄（`8ac863cf`），第三批派工文件亦含完整論證。

---

## §4 開工順位

> 🔴 **本節於 2026-07-31 稍晚重大改寫。** 舊版 §4.1 的「先人工驗收 T-2.2 才放行 T-2.3」**已被決策者取消**——改為一路往下跑、驗收整批併到最後。T-2.3 因此在未驗收 T-2.2 的情況下就已完成（`eb352a7`）。**不要照舊版執行那道 gate**。

### 4.1 🔴 下一個動作：接續 InSpec + Nmap 合併驗收（通過才做 GCB 三棒）

> 🟢 **2026-07-31 第三次更新：這一輪已經開始跑了，不是「尚待執行」。** 已完成的部分見 §1.8——**DEV 已建到 `0.2.14`、CINC 確認裝好、WinRM probe 已通過**。下方的「環境資訊索取表」大部分已取得。

**目前的三個卡點（接手就是處理這三件）**：

| # | 卡點 | 性質 | 處置 |
|---|------|------|------|
| **①** | **SSH 側 probe 卡在 sudo**——`sudo: I'm sorry audit-scan. I'm afraid I can't do that` | **環境設定問題，非 code 問題**（SSH 連線與認證都已通過） | **決策者正在目標主機上設 NOPASSWD sudo**。設好後重測 probe 即可，不需改 code。⚠️ 順帶注意 §5 新增項：connector 的 `use_sudo` **只是布林、不支援 sudo 密碼**，所以只能走 NOPASSWD |
| **②** | **FE 測試連線 Dialog 兩欄改版進行中**（`ToolPluginManage.vue` working tree 未提交） | 進行中的 UI 改版 | **改版完成並 commit 後才做驗收**——否則驗的是中間態。詳見 §5 新增項 |
| **③** | **`run()` 從未實跑** | **這才是驗收的主體** | probe 通過只證明連得上；CM-974 那五條假設全靠 `run()` 才驗得到 |

**若決策者對某項環境資訊尚未提供，接手第一件事就是把它再提一次。**

**為什麼合併成一輪**：InSpec 與 Nmap 的實機驗收共用同一批昂貴前置——重建 agent image（`0.2.14` 同時含兩者）、部署到 DEV、BE 重啟。**兩條線在同一次部署裡就能測完，分兩次做等於把最貴的部分付兩遍**（沿用 §2.6「昂貴前置只付一次」的紀律）。

**為什麼 GCB 三棒要等這輪過了才做**：**GCB（FR-058.3）完全建立在 InSpec 引擎上**（D7：GCB 不另立引擎、不另寫 connector）。InSpec 那條線至今零實跑驗證，地基沒驗就往上蓋三棒，**風險不對稱**——驗一次的成本是一兩小時，地基有錯而三棒要重來的成本是好幾倍。這一層理由與舊版 §4.1 相同，只是 gate 的位置從「T-2.2 之後」移到「InSpec 整條線之後」。

#### 執行前需要決策者提供的環境資訊（沒有這些就跑不了）

> ✅ **多數已取得，本表改為現況對照**（2026-07-31 第三次更新）。

| 需要什麼 | 用途 | 現況 |
|---------|------|------|
| **一台 Linux 主機**（IP + SSH 帳號 / 私鑰或密碼） | InSpec SSH transport 實跑；也可兼作 Nmap 的**執行主機**（需裝 nmap） | ✅ **已有**，SSH 連線與認證已通過。⚠️ **卡在 `audit-scan` 帳號沒有 NOPASSWD sudo**，決策者處理中 |
| **一台 Windows 主機**（IP + WinRM 帳號密碼） | InSpec WinRM transport 實跑 | ✅ **已有並已驗過**——`192.168.50.160`，WinRM probe 通過 |
| **Nmap 掃描目標**（可掃的內部 IP / 網段） | Nmap `run()` 實跑 | ❓ **尚未取得 / 尚未實跑**。⚠️ 只掃自己控制的內部網段，掃描會產生真實網路流量 |

> ⚠️ **憑證模型的澄清（決策者曾誤解，寫在這裡避免重複）**：**一組憑證套用到所有目標主機**——每台目標建同名帳號、放同一把公鑰、同樣的 sudoers 規則；任務的「掃描目標」欄位逗號分隔多台。**這不是跳板模式**：agent **直連每一台**（OpenSCAP 與 InSpec 的 `run()` 都是 `for host in hosts` 逐台連，中間沒有任何中繼主機）。
>
> 「每台主機帳密不同」的情境要走 **FR-058.0 任務層敏感參數**，**本案未做**（D6 定案：預設走租戶層共用一組）。這也是 Tenable 官方《Credentialed Checks on Linux》的業界標準做法，FR-057 母卡已有記載。

#### 驗收要點（依 Notion 卡自報的「未驗證假設」逐條對照）

**InSpec / CINC（CM-973 T-2.2 + CM-974 T-2.3）**：
- ~~**image build 是否過**~~ ✅ **已驗**（§1.8）——`0.2.14` build 通過、`/usr/bin/cinc-auditor` 實查存在。這條原本是全案最大的推測項，**已證實正確**
- container 內 `cinc-auditor` 可執行，且**版本輸出確認是 CINC 而非 Chef InSpec**（授權紅線 D5，不要只看「裝得起來」就過）—— ⚠️ **仍要確認版本字串**，「檔案存在」不等於「是 CINC 不是 Chef」
- 測試連線四情境各回不同訊息（憑證缺漏 / 引擎缺失 / 連不上 / 認證失敗）—— 🟡 **部分已驗**：WinRM 側通過（§1.8）；SSH 側卡 sudo（環境問題）待重測
- 🔴 **`run()` 是本輪驗收的主體，至今零實跑**——下方那條「CM-974 五條假設」全部靠它
- **CM-974 卡上列了五條未實跑驗證的假設，逐條對照**。其中**第 2 條最危險**：報告結構若不符，**統計數字會全零但證據照常上傳**（刻意的容錯）——這條最可能出錯又最不明顯，**驗收時務必看數字對不對，不要看到「有證據進池」就算過**
- `probe()` 不在目標主機留下殘留檔案；`/dev/shm` 暫存檔用完即刪

**Nmap（CM-983 T-4.3）**——這條線是 D9 改案後**全新寫的 SSH 型**，同樣零實跑：
- 手動派工掃真實網段 → XML 報告進證據池、**summary 開放埠數不是 0 / 不是空**
- 執行主機未安裝 nmap 時，測試連線回明確訊息（**不是 exit code 127**）
- 執行中按取消能即時中止，**遠端不留孤兒行程與暫存檔**
- 兩台執行主機其中一台連不上 → 另一台仍成功，失敗台被明確回報（非靜默假成功）
- **檢查 Dockerfile 內確實沒有 nmap**（NPSL 授權紅線）
- 🆕 **證據是人可讀 HTML 而非 XML**——agent `087fa41` 已改（容器內 xsltproc + 官方 nmap.xsl 轉換，XML 僅在記憶體算 summary）。**此改案未經派工、需決策者追認**（§5 新增項）；驗收時確認 ① HTML 在證據池可下載 ② summary 的開放埠統計仍正確（改版後 summary 改吃記憶體中的 XML，是新路徑）

**🆕 併進本輪的第三件事：T-3.2 的 content 來源實跑驗證**（§7.4 裁示）——T-3.2 已縮成「驗證 + 寫文件」，但**不是無條件**：agent image **沒裝 git**，「裸 GitHub URL 走 tarball 還是 git fetcher」從未驗證過。**這件事要在本輪一起實跑掉**，不要等 T-3.1 才發現走不通。

**通過後**：才放行 GCB 三棒（T-3.1 → T-3.2 → T-3.3）。

### 4.2 剩餘四棒與執行順序

**決策者裁示：人工監控，不做鏈式自動接力。** 每一棒完成後由協調者抽查 + 放行下一棒，不設自動觸發。

| 棒 | 內容 | 前置 |
|----|------|------|
| **T-2.4** | 結案 Notion CM-953（純 Notion 動作，無 code） | T-2.3 ✅ 已完成，**隨時可做**，不必等驗收 |
| **T-3.1** | GCB seed（工具目錄 + param_schema + setup_guide） | 建議等 §4.1 驗收過 |
| **T-3.2** | ~~GCB content 外部載入接口~~ → **已縮成「驗證 + 寫文件」**（§7.4 裁示；CINC 原生支援三種來源，不需另做抽象層）。**但實跑驗證併進 §4.1 那一輪**，不是無條件放行 | T-3.1（驗證部分提前到 §4.1） |
| **T-3.3** | Windows 最小 Demo profile | T-3.2 |

GCB 三棒序列不可對調（.3 硬依賴 .2 的 connector）。抽查重點沿用 §2.1〜2.3：實際開檔看、對照卡片驗收條件逐條核、問「這個錯誤最早能在哪裡被發現」。

### 4.3 全案完成後的最終端到端驗收

§4.1 那一輪是**針對 InSpec + Nmap 兩條線的地基驗證**，不取代全案最終驗收。全案（含 GCB）完成後仍要：

- 三環境套齊所有 migration（目前**九支**已完成，見 §1.4——**接手時仍要重查是否有新的 migration 未套**）
- 重建 agent image 到屆時版號（目前 `0.2.14`，GCB 完成後可能再 bump），**部署三台**（121 POC / 122 STG / 123 DEV）
- BE 重啟、FE 部署
- 跑 design.md §6 的端到端驗收九項

**指令可直接照抄前一棒交接文件的 §6 pre-flight 與 §3 情境 A 的 A.1 前置環境準備**（`handoff/2026-07-30-fr058-planning-complete-handoff.md`）——那些指令仍然有效。§4.1 那一輪也用同一批指令，只是部署範圍可先限縮到 DEV 一台。

### 4.4 收尾項（全案驗收通過、決策者下令才做）

| 項目 | 內容 | Repo |
|------|------|------|
| **T-9.1** | 清 `WorkflowSetupEditor.vue` 的硬編死碼工具清單 `toolsMenu = ["Nessus","Nmap","OWASP ZAP","Wireshark"]`（與 `detection_tools` 完全脫鉤；本案加入 ZAP 與 Nmap 後畫面上兩處都出現，其中一處是死的） | FE |
| **T-9.2** | 補 `docs/claude/database-schema.md` 的 config schema 與 detection 三表記載（目前 grep 零命中） | BE |
| agent 部署 | 121 POC / 122 STG 補到最新版（目前停在 `0.2.11`） | 部署 |
| push | **三 repo 全部未 push**，等決策者明示 | 三 repo |
| discussion.html 更新 | 內容已多處過期，見 §5 | BE |

---

## §5 已知待辦與風險

| # | 項目 | 嚴重度 | 說明與處置 |
|---|------|--------|-----------|
| **0** | 🟡 **InSpec 與 Nmap 的實跑驗證——已部分完成，`run()` 仍零實跑**（2026-07-31 第三次更新**降級**，原為「高、零實跑」） | **中高**（原為高） | **✅ 已消除的假設**：**①** Dockerfile 的 CINC 安裝——**image build 通過、`/usr/bin/cinc-auditor` 實查存在**（§1.8），這條原本是全案最大的推測項；WinRM probe 也已對真實主機通過。**🔴 仍未驗證的**：**②** CINC 的 `--reporter json` 輸出結構與 CLI 行為，T-2.3 的解析**全建立在假設上**（CM-974 卡列了五條，第 2 條「報告結構」出錯時會**統計全零但證據照常上傳**，最不明顯）——**`run()` 從未跑過，這五條一條都沒驗到**；**③** Nmap connector 走 SSH 型是 D9 改案後**全新寫的**，同樣沒對真實網段跑過。**處置：§4.1 那一輪繼續跑完**，重點已從「連得上嗎」移到「掃得對嗎」 |
| 1 | **三 repo 全部未 push** | 中 | BE **17** / FE **5** / agent **10** 個 commit 領先 origin（2026-07-31 第三次更新實查）。**決策者已明確裁示：push 暫緩到全案驗收通過**（§7.6）。風險是本機硬碟成為唯一副本——**全部實作成果目前只存在本機** |
| 2 | ~~**BE `pyproject.toml` 多了 `zaproxy` 相依**~~ **已消失** | — | **2026-07-31 第三次更新實查：`pyproject.toml` 已無 `zaproxy`**（grep 零命中），working tree 也不再有該檔的 modified。**本項不需處理** |
| 3 | **`connector` 端 ZAP 自我保護未實作** | 低（已知） | 偵測認證失敗異常累積時主動中止掃描，避免打掛共用 ZAP（§3.3）。**決策者裁示「只記錄不實作」**——目前靠 `setup_guide` 的「請勿對 SPA 使用登入後掃描」防守 |
| 4 | **CM-984（SPA 登入掃描支援）為後續需求** | 低 | Not started。需要「json 認證擴充 + script-based session 管理 + SPA 測試站台」三件事。⚠️ 實作時注意 ZAP script engine：Oracle Nashorn 在 JDK 15 後已移除、新版預設 Graal.js，舊參考碼 `zap_auth.py` 的 `java_script()` 還停在 Nashorn |
| 5 | **DEV 主機 `docker-compose.yml` 第 14 行 fallback 停在 `0.2.8`** | 極低 | 實際版本由 `.env` 的 `AGENT_IMAGE` 決定（實查 DEV `.env` 為 `0.2.13`、實跑也是 `0.2.13`），**無實質影響**。但過時的 fallback 值有誤導風險，日後順手更新即可 |
| 6 | **FE 必填驗證的 falsy 邊界** | 極低（已註記） | helper `compliance-manager-fe/src/utils/detectionFieldValidation.js` 空值判斷用 `!value`，刻意與 `DetectionConfigField.showError` 同源。**目前 param_schema 只有字串型必填欄位，不含 number / boolean，故不會誤殺 `0` / `false`**。⚠️ 日後若出現 `required` 的 number / boolean 欄位就會誤判為缺漏——檔頭第 34–37 行已有註解記載 |
| 7 | **121 / 122 agent 停在 `0.2.11`**（123 DEV **已更新到 `0.2.14`**） | 低（刻意） | 決策者裁示 DEV 驗完才輪，不重複付部署成本。**第三次更新已把 DEV 建到 `0.2.14`**（舊記載「三台都沒有 `0.2.14`」已過期）；121 / 122 等全案驗收時一併更新（§4.3） |
| 8 | **`discussion.html` 內容多處過期** | 低 | 實查：`D12` 零命中、`SPA` 零命中、`requires_target_host` 零命中，`NPSL` 僅 1 處（D9 改案前的舊記載）。即**第一批實測發現與 D9 改案完全未反映**。更新時 ⚠️ **不要動 mermaid 圖首行的 `%%{init:...}%%`**（那是三次踩坑後的解，Artifact 雲端不執行頁面 script，只有內嵌 init 字串有效） |
| 9 | ~~有一個 session-only 的凌晨排程在檢查 T-2.2 進度~~ **已失效** | — | **2026-07-31 稍晚更新：此條已無意義。** 決策者已取消 T-2.2 的驗收 gate（改為一路往下跑、驗收併到最後），T-2.3 也已完成。該排程隨 session 關閉消失，不需處理 |
| 10 | `_pick_agent` 無負載平衡 / `tenant_config_id` 從未被寫入 / 證據關聯靠 `description` 字串前綴 | 低（既有技術債） | **本案不處理**，加工具不會惡化。記在 design.md §3 技術債表 |
| **11** | 🆕 **「目標主機」文案語意模糊——使用者分不清主動方是誰** | **中**（實測當場發生） | **CINC 是 agent 主動連出**（agent 裝引擎 → 連到目標主機執行），**與 OpenSCAP 引擎裝在目標主機的模型相反**。實測時**決策者實際誤填了 agent 自己的 IP（123）而非要掃的目標（151）**——這不是使用者疏忽，是文案沒說清楚主動方。**兩處都要改寫**：① 測試連線對話框的說明文字 ② 錯誤訊息「檢測引擎連線 X 失敗」——**這句聽起來像「引擎在 X 上出問題」**，實際語意是「從 agent 連到 X 失敗」。**尚未修，待排** |
| **12** | 🆕 **FE 測試連線 Dialog 呈現改版進行中（未提交）** | 中（進行中） | 決策者拍板把憑證分組從**垂直堆疊改成左右並排**（Dialog 加寬約 `52rem`、兩欄 grid、窄螢幕 fallback 單欄）。**FE session 進行中**，`compliance-manager-fe` working tree 的 `src/views/plugin/ToolPluginManage.vue` 就是這個。**接手時不要動那個檔、不要 commit**。⚠️ **§4.1 的驗收要等這個改版完成才做**，否則驗的是中間態。**排除了 Tab 方案**，理由記在 §7 之後的小節 |
| **13** | 🆕 **connector 的 `use_sudo` 只是布林，不支援 sudo 密碼** | 低 | 只能走 **NOPASSWD sudo**。**CINC 本身支援 `sudo_password`**，是我們的 schema 沒開這個欄位。企業稽核帳號通常就是設 NOPASSWD（業界標準做法），**故不緊急**；但客戶環境若不允許 NOPASSWD 就會卡住。**本案不做，記錄待日後評估**。⚠️ §1.8 的 SSH probe 卡點正是這個限制的直接後果 |
| **14** | 🆕 **Nmap 證據改存 HTML——未經派工的自主改案，待決策者追認** | 中（流程項） | agent `087fa41`：改存人可讀 HTML（**容器內 xsltproc + 官方 nmap.xsl 轉換**），XML 僅在記憶體算 summary。**技術方向與 D4（ZAP 證據 PDF→HTML）同精神**——證據要人可讀，合理。**但這是實作 session 自行決定做的，協調者沒派過這一棒**。**接手時提請決策者確認是否追認**；若追認，需一併確認 ① 是否要比照 D4 在 design.md 記一筆改案 ② 是否要開 Notion 卡。驗收要點見 §4.1 |
| **15** | 🆕 **GCB content 來源的兩個限制（已裁示開後續卡，不在本案做）** | 低（已裁示） | ① **封閉網路客戶只能用本機路徑**，但**目前沒有把 profile 檔送進 agent container 的機制**；② **私有 repo 無憑證路徑**——`params.profile` 是**非 secret 的純文字欄位**，沒地方放 token。**兩項都不在本案 scope**（§7.5 裁示），**開後續卡記錄**即可 |

---

## §6 Pre-flight Command（必跑）

```bash
# ① 三 repo branch / 領先數 / HEAD（⚠️ 兩批實作 session 仍在跑，數字一定與 §1.1 不同）
for r in compliance-manager-be compliance-manager-fe ../evidence-agent; do
  d=~/Projects/Billows/Audit-Manager/$r
  [ -d "$d" ] || d=~/Projects/Billows/Audit-Manager/evidence-agent
  echo "=== $d ==="
  git -C "$d" branch --show-current
  git -C "$d" rev-list --left-right --count origin/feature/FR-058...HEAD   # 左＝origin 領先、右＝本地領先
  git -C "$d" log --oneline origin/feature/FR-058..HEAD | head -20
  git -C "$d" status --short | head -10
done
# 三者都應為 feature/FR-058；左邊一律 0（沒人 push）

# ② 三環境 detection_tools 對照（確認七筆、三環境 id 與旗標一致）
#    密碼請查 .env 的 DB_SECRET 或部署文件
for db in "192.168.50.188|guidant_ai_dev" "192.168.50.188|guidant_ai_stg" "192.168.50.189|guidant_ai_poc"; do
  h=${db%%|*}; n=${db##*|}
  echo "=== $n @ $h ==="
  psql -h $h -p 25432 -U cmmgr -d $n -c \
    "SELECT id, code, connection_type, status, requires_credentials, requires_target_host
       FROM config.detection_tools ORDER BY id"
done
# 基線（2026-07-31 實查）：1 openvas / 2 nessus / 3 sonarqube / 4 openscap / 5 zap / 6 inspec / 7 nmap
# 🆕 第三次更新加查 credential_group_schema（§1.7）：三環境皆「只有 inspec 非 NULL、其餘六筆 NULL」
#   SELECT id, code, (credential_group_schema IS NOT NULL) AS has_cred_grp FROM config.detection_tools ORDER BY id;

# ③ 已套 migration 清單（對照 §1.4；若有新檔案要確認三環境都套了）
ls ~/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/sql/ | grep fr058

# ④ 三台 agent 部署版本（預期 123=0.2.14、121/122=0.2.11，後兩台刻意未更新）
#    ⚠️ 舊版此處寫 123=0.2.13 已過期——第三次更新時 DEV 已建並部署 0.2.14（§1.5 / §1.8）
for h in 192.168.50.121 192.168.50.122 192.168.50.123; do
  echo "=== $h ==="
  ssh -o ConnectTimeout=6 jedi@$h \
    "docker ps --filter name=guidant-ai-agent --format '{{.Image}}\t{{.Status}}'"
done

# ⑤ BE 服務是否在跑（port 8000）
lsof -i :8000 | head -3
# 沒有 listener 時：
#   cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && nohup python main_app.py > /dev/null 2>&1 &
# ⚠️ main_socketio.py 是 socket（8002），不是 API 入口；重啟必先 kill -9（orphan pid 會卡 port）

# ⑥ BE log 掃一眼
tail -200 ~/Projects/Billows/Audit-Manager/compliance-manager-be/log/app.log \
  | grep -i 'Traceback\|ERROR' | tail -20

# ⑦ agent 測試（⚠️ 必須帶 PYTHONPATH=.，repo 缺 conftest.py）
cd ~/Projects/Billows/Audit-Manager/evidence-agent && PYTHONPATH=. poetry run pytest test/ -q | tail -5
# 基線：T-4.3 完成當下自報 342 passed, 1 skipped；T-2.3 完成當下自報「可收集的 181 項全綠」
# ⚠️ 兩個數字不可比：本機 paramiko / gvm 未安裝時，test_openvas/openscap/nmap/connector_factory
#    四支無法收集（既有環境問題，非本次異動）。裝了才收得到全部 342 項。

# ⑧ 規劃產出物完好（行數會隨改案增長，只確認檔案在）
wc -l ~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-058-2607-detection-tools-expansion/{design.md,discussion.html}
# 2026-07-31 實查：design.md 512 行、discussion.html 1060 行
```

**Notion 查詢注意**：free plan **不支援 SQL 查資料源**（`notion-query-data-sources` 對任務清單 DB 回空陣列）。查卡片一律 `notion-search` 找到再 `notion-fetch` 開，或從母卡 CM-957 的子卡清單進入。

---

## §7 第二、三批的協調裁示（已定，不要重新討論）

> 本節記錄本棒任內對兩批實作 session 提問所做的裁示。**這些已經定案，接手後不要重新討論**，但要知道理由才不會在後續實作中把它推翻。

### 7.1 InSpec 的 `connection_type` 填 `SSH`，transport 選擇放 `param_schema`

**問題**：InSpec 支援 SSH（Linux）與 WinRM（Windows）雙 transport（D6），`detection_tools.connection_type` 只能填一個值，該填什麼？

**裁示**：填 `SSH`。理由是**該欄位的實際語意不是「這個工具怎麼連線」，而是「要不要在 UI 上問使用者測試主機」**——它不對使用者顯示。真正的 transport 選擇放在**任務層的 `param_schema`**（`params.transport` = `ssh` / `winrm`），因為那本來就是任務屬性而非工具屬性：同一租戶的 Linux 與 Windows 目標分屬不同任務（CM-953 已定的「一任務一工具、靠多任務處理異質環境」模型）。

**連帶產物**：這次討論暴露了 FE 在硬判 `connection_type` 來決定要不要顯示「測試主機」輸入框，語意耦合錯誤 → 促成 7.2。

### 7.2 `requires_target_host` 欄位由第三批做，第二批不重複走

**問題**：FE 判斷是否顯示「測試主機」輸入框時硬判 `connection_type === 'SSH'`，語意錯誤且會隨工具增加而失準。要新增一個 `requires_target_host` 宣告欄位。這一棒該給誰？

**裁示**：**給第三批**。理由是第三批的 T-4.1 剛走過 `requires_credentials` 的完整同構鏈（DB migration → ORM model → entity → mapper → serializer → orchestration service → FE），**再走一次同形狀的鏈成本最低**；第二批要從零理解那條鏈，成本反而高。

**已完成**：BE `9a15db96` + FE `2fe1ce6`。三環境已套（`fr058-7` migration），DB 實查已確認欄位存在且值正確（§1.3）。

### 7.3 第二批 T-2.1 的三個自主決定，經審核**全部同意**

實作 session 在 T-2.1（InSpec seed）中自主做了三個決定並回報請示，逐一審核結果：

| # | 自主決定 | 審核 | 理由 |
|---|---------|------|------|
| ① | **不新增 WinRM 值**到 `connection_type` 枚舉 | ✅ 同意 | 與 7.1 一致——該欄位語意是「要不要問測試主機」，WinRM 也要問，加一個值不會讓語意更準，只會多一個要維護的枚舉值 |
| ② | **憑證欄位不做兩層條件顯示** | ✅ 同意 | FE 的 `condition` 是**單層嚴格等值判斷**。硬做兩層會導致「開關關掉時第二層仍依殘留值顯示」——使用者關掉 WinRM 卻仍看到 WinRM 的埠號欄位。這是 FE 實作的硬限制，不是實作偷懶 |
| ③ | **憑證一律非必填**（SSH 一組、WinRM 一組都不強制） | ✅ 同意**，但有配套** | 哪一組是必要的取決於**任務當下選的 transport**，設定當下無從得知。硬性兩組都必填會逼只掃 Linux 的客戶填一組用不到的 Windows 帳密 |

**③ 的配套要求（決策者明確加的，不是實作 session 自己想的）**：

> 缺哪一組憑證，必須由 **T-2.2 的 `probe()` 回明確訊息**（例如「本任務的連線方式為 WinRM，但檢測工具設定中未填寫 WinRM 帳號與密碼，請至檢測工具管理頁補齊」），**不可退化成執行期才失敗**。

這個配套是 ③ 能成立的**前提**——沒有它，「設定時不擋」就等於「執行時才爆」，正是 §2.3 那個反覆出現的壞模式。

**已落實**：T-2.2（`dcedb4f`）的 `probe()` 第 ① 段即為憑證完整性檢查，訊息指名 transport 與欄位。connector 檔頭有完整註解記載此因果。**後續若有人想「簡化」probe 的檢查順序，要知道第 ① 段是不能拿掉的。**

### 7.4 🆕 T-3.2「content 外部載入接口」縮成「驗證 + 寫文件」——**但非無條件**

**背景**：D7 要求「content 不可硬編進 connector 或 agent image，必須可從外部載入」，原本規劃 T-3.2 要做一層載入接口。

**裁示：不做抽象層，縮成「實跑驗證 + 寫文件」。** 理由：**CINC 原生就支援三種 content 來源**——Git URL、本機路徑、Supermarket，而 `params.profile` 是**直接進 `exec` 的 argv**，等於外部載入能力**已經在了**，另做一層抽象只是把 CINC 的能力再包一次。

**⚠️ 但這不是無條件放行——必須實跑證實才算數**：

- **agent image 沒裝 `git`**
- **「裸 GitHub URL 走 tarball fetcher 還是 git fetcher」從未驗證過**——若走 git fetcher，沒有 `git` 就直接失敗

**因此：實跑驗證併進 §4.1 那一輪**（見該節「併進本輪的第三件事」），**不要等 T-3.1 才發現走不通**。驗完再寫文件。

### 7.5 🆕 content 來源的兩個限制：開後續卡記錄，不在本案做

7.4 的驗證會暴露兩個真實限制，**兩者都不在本案 scope**：

| # | 限制 | 為什麼是真限制 |
|---|------|--------------|
| ① | **封閉網路客戶只能用本機路徑**，但**目前沒有送檔進 container 的機制** | 「可以用本機路徑」這句話對封閉網路客戶是空的——檔案根本進不去 |
| ② | **私有 repo 無憑證路徑** | `params.profile` 是**非 secret 的純文字欄位**，沒地方放 token（要放就得走 FR-058.0 任務層敏感參數，那是另一件事） |

**裁示：開後續卡記錄即可，本案不做。** 見 §5 第 15 項。

### 7.6 🆕 commit 標籤不一致不處理；push 暫緩到全案驗收通過

**① commit 標籤不一致**：本案的 commit message 標籤形式並不齊一（有的帶 `cm-xxx`、有的只帶 `fr058`，子任務編號標法也有出入）。**裁示：已 commit 的訊息不改。** 理由：**rebase 改寫歷史的風險大於整齊帶來的收益**——三 repo 都在同一個未 push 的 feature branch 上，改寫會牽動全部。**日後的 commit 盡量對齊，但不回頭修。**

**② push 時機**：**暫緩到全案驗收通過**。這是對「push 永遠等決策者明示」的具體化——不是每一棒完成就 push，而是**全案驗完一次推**。接手時**不要因為「commit 累積很多、怕硬碟壞」就自己 push**（該風險已記在 §5 第 1 項，決策者知情並接受）。

### 7.7 🆕 測試連線 Dialog 為何用「左右並排」而非 Tab

決策者拍板：憑證分組從垂直堆疊改成**左右並排兩欄**（Dialog 加寬約 `52rem`，窄螢幕 fallback 單欄）。

**明確排除了 Tab 方案，理由有二**：

1. **兩組不是互斥選項，而是「填你需要的那些」。** 混合環境的客戶（同時有 Linux 與 Windows 目標）**兩組都會填**。Tab 的視覺語意是「選一個」，**與實際語意相反**。
2. **Tab 會把另一組藏起來，「填了沒」變成不可見。** 使用者無法一眼確認自己填齊了哪些——而這正是 §1.7 缺口 ② 要解決的問題，用 Tab 等於換一種方式再犯一次。

> ⚠️ 注意這與「測試連線時要**選一組**」不衝突：**設定頁**是「填你需要的」（可兩組都填），**測試連線**是「這次要測哪一組」（必須擇一，因為一次 probe 只走一條 transport）。**兩者的語意本來就不同，UI 也就不該用同一種形式。**

---

## §8 行為規範重要提醒

- **不切 branch**：任何 `git checkout <branch>` / `git switch <branch>` **一律不執行**，永遠在 `feature/FR-058` 工作。發現 branch 不對**停下問決策者**，不自己 fix。「為了在正確 branch commit 而切 branch」也不允許
- **push 永遠等決策者明示**，不自動 push（目前三 repo 全未 push）
- **顯式 git add 檔名，禁用 `-am`**——⚠️ 本案**兩批實作 session 並行**，且 BE working tree 有兩個不該一起進去的 modified（`CLAUDE.md` / `pyproject.toml` 的 `zaproxy`，見 §5 第 2 項），誤 add 風險特別高
- **收尾必須等決策者下令**：SPEC / SUMMARY / memory / Notion 母案收尾都是。plan approve ≠ 自動執行收尾
- **憑證禁入版控**：密碼、token、API key 絕不寫進任何會 commit 的檔案（含 SQL migration、docs、handoff）。需要連線資訊時只寫帳號 / host / port / db 名，密碼一律寫「請查 `.env` 或部署文件」
- **BE 出錯先看 log，不問決策者**：`tail -200 log/app.log | grep -A 30 -i 'Traceback\|ERROR'`
- **BE 起服務用 `main_app.py`（port 8000）**，`main_socketio.py` 是 socket（8002）不是 API 入口；重啟必先 `kill -9`
- **文件產出類工作一律派 subagent**，且派工單必寫「**分段寫檔：先 Write 骨架再多次 Edit，不要一次寫完整份**」（本專案已三次遇到一次寫大檔時 API 中斷）
- **執行類工作發出去**：跑測試、跑部署、重建 image 一律派 subagent，本棒不自己執行
- **subagent 一律用 1M context model**，**不帶 `model` 參數**
- **測試一律 headless**
- **任務發佈後最長等 300 秒才會真的開跑**——心跳週期造成的**正常行為，不是故障**。看到「按了沒反應」不要當 bug 追
- **SQL migration 鐵則**：檔頭 `-- Date:`、每個語句加日期註解、新建 table 必加 `GRANT ... TO cm_app` + sequence 權限、收尾必 `INSERT public.schema_migrations`；套用一律 `psql --single-transaction -v ON_ERROR_STOP=1 -f <檔>` 用 `cmmgr`；**三環境（DEV / STG / POC）都要套**
- **跨 repo 各自分開 commit**，commit message 對應各自 repo 的脈絡
- **不要晶晶體**（中英夾雜的語法），**不要用「速贏」**這個詞
- **agent 測試必帶 `PYTHONPATH=.`**（repo 缺 `conftest.py`）
- **重建 agent image 必指定 `--platform linux/amd64`**（開發機是 Mac arm64、目標主機是 Linux amd64），build 完必 `docker inspect ... --format '{{.Os}}/{{.Architecture}}'` 確認

---

## §9 收尾流程（全案完成後，等決策者下令才做）

> ⚠️ 本節列的是「等命令」的動作，**不要主動執行**。第二、三批各自完成不等於全案完成。

**每批完成後的階段性動作**：
1. 回寫該批 Notion 子任務卡狀態（派 subagent）
2. commit（顯式 git add，不 push）
3. 抽查通過後放行下一棒

**全案完成後**（三批都驗收通過、決策者下令才做）：
1. **最終端到端驗收**（§4.3）——三環境 migration 對齊、重建 image 部署三台、跑 design.md §6 九項
2. **收尾兩項未開卡的工作**：T-9.1（清 FE 硬編工具清單）、T-9.2（補 `database-schema.md` 三表記載）
3. **更新 `discussion.html`**——反映 D4 改案、D12（含 SPA 打掛 ZAP 的更正）、D9 改案、`requires_target_host`（見 §5 第 8 項）。⚠️ 不要動 mermaid 的 `%%{init:...}%%` 行
4. **更新 `docs/specs/current/` 對應頁面 spec**（`tool-plugin-manage.md` / `project-task-edit.md` / `my-tasks.md`）+ 檔頭「變更紀錄」加一行——SOP 走 `writing-feature-specs` skill
5. 寫 SUMMARY 到 `docs/features/FR-058-2607-detection-tools-expansion/handoff/`
6. Notion 母案 CM-957 + 六張子需求卡狀態同步；Artifact 線上版 republish
7. memory feedback 沉澱
8. 若要發版 → 走 `version-bump` skill（release note + `pyproject.toml` bump 成對必做 + FE 版號對齊）
9. **三 repo push**（等明示）

---

## §10 不在本期 scope

| 項目 | 說明 |
|------|------|
| **SonarQube** | **本期不做，日後另議**。`detection_tools` 的 `id=3` 佔位列**維持 `status='coming_soon'` 不動** |
| **ZAP 對 SPA 的登入後掃描** | **列為後續另案 CM-984**（D12）。本案的 `setup_guide` 已明白標示「請勿對 SPA 使用登入後掃描」 |
| **ZAP connector 的自我保護** | 偵測認證失敗異常累積主動中止——**決策者裁示只記錄不實作**（§5 第 3 項） |
| **macOS 15（`TWGCB-01-015`）** | **不納入本案**（D7）。基準 2026/6 才發布、官方部署資源頁無機器可讀檔案，現有 connector 未曾處理 macOS |
| **GCB content 產製** | 逐條 TWGCB-ID 轉檢查規則（Windows GPO 產生器 / Linux 人工撰寫）**不在本案**。本案只做引擎 + **一份 Windows 最小 Demo profile** |
| **後台 content 更新機制** | 上傳 / 版更 / 套用的管理介面**不在本案**，日後另案。但 **content 外部載入接口本案就要留**（D7，T-3.2） |
| **Nmap OEM 授權採購** | **本案不採**，未評估成本。若日後產品策略需要零客戶設定的 nmap 整合，這是唯一能 bundle 的合法途徑，**列為備查**（D9 改案，§3.6） |
| **`_pick_agent` 負載平衡** | 既有技術債，本案不處理 |
| **`tenant_config_id` 從未被寫入** | 既有技術債，永遠是 NULL，實際靠 `(tenant_id, detection_tool_id)` UNIQUE 回退查詢。本案不處理 |
| **`connection_type='CLI'` 型態** | D9 改案後**至今仍無實際使用者**。不要為了「讓它有人用」而回頭改 Nmap |

---

## §11 本棒 commits 清單

> 本棒任內三 repo 的全部 commits（**皆未 push**，§7.6 裁示暫緩到全案驗收）。⚠️ 實作 session 若仍在跑，接手時 `git log` 會比本表多。**2026-07-31 第三次更新**（BE +2、FE +2、agent +2）。

### BE `compliance-manager-be`（17 個，HEAD `1d23082b`）

| Commit | 說明 | 歸屬 |
|--------|------|------|
| **`1d23082b`** | **feat(cm-985): 互斥憑證組別平台能力**——測試連線由使用者明確選一組、設定頁分組顯示（§1.7；migration `fr058-9` 三環境已套） | **本輪新增平台能力** |
| **`2858b447`** | docs(fr058): 交接文件更新——第三批全數完成、第二批剩 GCB 三棒、驗收 gate 改為 InSpec+Nmap 合併一輪（＝**本文第二次更新**） | 本棒收尾 |
| `d81067f5` | docs(fr058): 協調者 session 中繼交接文件——**即本文首版** | 本棒收尾 |
| `e1e5c795` | docs(fr058): 補 T-2.2 揭露的架構事實——CINC 裝 agent 本機主動連出，與 OpenSCAP 相反 | 第二批 |
| `16ae4ff0` | feat(cm-982): seed Nmap 工具目錄（SSH 型，D9 改案）（T-4.2） | 第三批 |
| `9a15db96` | feat(cm-981): `detection_tools` 加 `requires_target_host`，解除 FE 對 `connection_type` 的耦合 | 第三批（§7.2 裁示） |
| `8ac863cf` | docs(fr058): **D9 改案**——Nmap 由 CLI 改 SSH 型 + 補 NPSL 授權依據 | 本棒決策（§3.6） |
| `4d5226fa` | feat(cm-972): seed InSpec / CINC Auditor 工具目錄（T-2.1） | 第二批 |
| `b9520f7b` | feat(cm-981): `detection_tools` 加 `requires_credentials` 宣告欄位（T-4.1，D9 平台能力） | 第三批 |
| `2581b4f6` | docs(cm-967): **更正** SPA 登入掃描的實際後果——會打掛共用 ZAP 而非產出空報告 | 本棒決策（§3.3） |
| `f8b0a370` | docs(cm-967): ZAP `setup_guide` 補登入後掃描的 SPA 限制說明 | 本棒決策（§3.3） |
| `2d14ccb7` | fix(cm-967): ZAP 登入四欄改條件必填——錯誤從 agent 執行期提前到 FE 表單 | 驗收修正②（§3.2） |
| `0a9cde3c` | docs(fr058): **D4 改版**同步（ZAP 證據 PDF→HTML）+ 第二批派工交接文件 | 本棒決策（§3.1 / §3.5） |
| `082a02d8` | fix(cm-959): 測試連線 probe payload 補傳 `detection_tool_code` | 驗收修正①（§3.2） |
| `5151fb2c` | feat(cm-959,961-964): 派工帶 tool code + 任務層敏感參數加密鏈（T-X.1 / T-0.1〜0.3） | 第一批（前一棒派出） |
| `8f579c50` | feat(cm-967): seed ZAP 工具目錄 + param_schema + setup_guide（T-1.1） | 第一批 |
| `4b91138b` | docs(fr058): 討論稿補分批出貨計畫 | 前一棒尾聲 |

> 第三批派工文件（`2026-07-30-fr058-batch3-nmap-dispatch.md`）目前為 **untracked**，隨下一次 docs commit 一起進去。

### FE `compliance-manager-fe`（5 個，HEAD `8d77d5e`）

| Commit | 說明 |
|--------|------|
| **`8d77d5e`** | fix(fr058): 檢測工具摘要卡**分開顯示執行主機與掃描目標**——`JobExecutionDrawer` 原本只讀 `params.hosts` 一欄硬標「掃描目標」；openscap/openvas/inspec 這類「登進去掃自己」的工具剛好 `hosts == targets` 沒問題，**nmap 是唯一兩者不同的工具**（hosts＝agent 執行主機、targets＝掃描網段），導致執行主機被誤標成掃描目標 |
| **`37967b4`** | feat(fr058): **憑證分組渲染 + 測試連線由使用者指定憑證組**（§1.7 的 FE 半邊；FE 不硬寫任何協定字面值，全由後端宣告驅動；未選定則測試連線鈕 disabled、每次開窗重設） |
| `2fe1ce6` | refactor(cm-981): 測試主機輸入框改判 `requires_target_host`，不再硬判 `connection_type` |
| `8220852` | fix(fr058): 任務參數存檔補檢測工具參數必填驗證——錯誤從 agent 執行期提前到表單 |
| `992fd35` | feat(cm-965): 任務參數 secret 欄位渲染 + condition 顯示修復（T-0.4） |

### agent `evidence-agent`（10 個，HEAD `087fa41`）

| Commit | 說明 |
|--------|------|
| **`087fa41`** | feat(fr058): **Nmap 證據改存人可讀 HTML**——容器內 xsltproc + 官方 `nmap.xsl` 轉換，XML 僅在記憶體算 summary。⚠️ **未經派工的自主改案，待決策者追認**（§5 第 14 項） |
| **`2c64a3b`** | test(fr058): **probe 路徑 transport 端到端釘樁**——§1.7 平台級缺口的防迴歸測試（**agent 側零 code 異動**，`_resolve_transport()` 本來就吃 probe params，缺的是 BE 沒傳） |
| `eb352a7` | feat(cm-974): InSpec / CINC Auditor `run()`——逐台多檔證據 + JSON summary + 取消/逾時（T-2.3）——🔴 **`run()` 至今零實跑，見 §5 第 0 項** |
| `6f97107` | chore: bump `0.2.14`——FR-058.4 Nmap connector（**未出貨，等全案端到端**；之後的 `eb352a7` 未再 bump，此版號同時涵蓋 Nmap 與 InSpec `run()`） |
| `883e5d1` | feat(cm-983): Nmap connector——SSH 遠端執行 + XML 解析 + probe 四段分類（T-4.3）——**零實跑驗證** |
| `dcedb4f` | feat(cm-973): InSpec / CINC Auditor connector 骨架 + probe（T-2.2）——✅ **Dockerfile 的 CINC 安裝已 build 通過並實查存在**（§1.8，原記載「從沒 build 過」已過期） |
| `8185893` | chore: bump `0.2.13`——ZAP 主動掃描修正出貨版 |
| `1feb6c7` | fix(zap): 主動掃描 `does_not_exist`——錯誤碼偽裝成 scan id 的三層遮蔽鏈（§3.2 修正③） |
| `c775fc8` | chore: bump `0.2.12`——FR-058 第一批出貨版 |
| `500b6c7` | feat(cm-968/969/970): ZAP connector——probe + 三種掃描模式 + 雙格式取報告 |

> 更早的 `f17a311`（factory 改依 tool code 取，T-X.2 / D11）已在 origin，不列入領先清單。

---

## §12 給 fresh session 的超短 prompt

```
接手 FR-058（檢測工具擴充，四工具接入）的協調與決策工作。

完整讀序入口：
docs/features/FR-058-2607-detection-tools-expansion/handoff/2026-07-31-fr058-coordinator-midarc-handoff.md

先讀該文的「🧭 原始需求 / WHY」+ §0 讀序（含 §3 本棒完整經過與 design.md §2 的硬 gate），
讀完先回答 §0.4 的冷接自檢五問，再跑 §6 pre-flight。

現況：第一批（ID 解耦 + 任務層敏感參數 + ZAP）已實測完成；第三批（Nmap）三棒全數完成；
第二批的 InSpec 段（T-2.1~2.3）也全數完成，只剩 T-2.4（純 Notion 結案 CM-953）
與 GCB 三棒（T-3.1/3.2/3.3）。另外本輪加了一批平台級能力「互斥憑證組別」（CM-985，見 §1.7）。

實測進度（§1.8）：DEV agent 已建並部署 0.2.14、CINC 確認裝進 image、
WinRM 測試連線已對真實 Windows 主機（192.168.50.160）通過。
⚠️ 但「真正的掃描」（run()）從沒跑過一次——probe 通過不等於這條線可用。
CM-974 卡上那五條未驗證假設（reporter json 結構、退出碼語意等）一條都沒驗到，
其中「報告結構不符會統計全零但證據照常上傳」最不明顯。Nmap 也仍零實跑。

你的下一個動作是 §4.1——把合併驗收跑完。目前三個卡點：
  ① SSH 側 probe 卡在目標主機 audit-scan 帳號沒有 NOPASSWD sudo（環境問題，我在處理）
  ② FE 測試連線 Dialog 兩欄改版進行中（ToolPluginManage.vue 未提交），改完才驗收
  ③ run() 是驗收主體，還沒開始
另外 T-3.2 的 content 來源實跑驗證要併進這一輪（agent image 沒裝 git，
裸 GitHub URL 走哪個 fetcher 從沒驗過）。通過才放行 GCB 三棒。

⚠️ 兩件過期記載不要照做：舊版「先驗收 T-2.2 才放行 T-2.3」的 gate 已取消；
舊版說 BE pyproject.toml 誤加 zaproxy 待確認——實查已不存在，不需處理。

你的角色是協調、決策、派工、抽查。不寫 code、不跑測試——執行類與文件類工作全派 subagent
（不帶 model 參數，文件類必寫「分段寫檔」）。收尾類動作一律等我下令。
三 repo 皆未 push，我已裁示暫緩到全案驗收通過（§7.6），不要自己 push。
```

---

> 📌 **寫完自檢**：下個 session 只看這一份，能不能答出冷接自檢五問（懂 WHY 與兩次改案）、跑得動 pre-flight、知道下一個動作是**接續 InSpec + Nmap 合併驗收**（且知道 ① 舊的 T-2.2 gate 已取消 ② probe 已通過但 `run()` 從沒跑過 ③ 三個卡點是 sudo / FE 改版 / `run()`）？——本文所有指令 / 路徑 / Notion URL 皆可直接複製貼上，不需回頭問決策者。
>
> 📌 **本文與前一棒的關係**：`2026-07-30-fr058-planning-complete-handoff.md` 的 §6 pre-flight 與 §3 情境 A 的環境準備指令**仍然有效可照抄**；其 §1 狀態盤點與 §3 情境判斷**已過期**（情境 A 已走完）。有衝突時**以本文為準**。
