# FR-058 協調者中繼交接（第四棒）— FR-058.6 主動掃描雙模式端到端通過（2026-08-01 上午）

> **這是中繼交接不是收尾**。八款工具全數上線；**FR-058.6 SonarQube 主動掃描模式（scan）
> 與既有拉取模式（pull）已雙雙端到端驗收通過**——`detection_executions` 內 sonarqube
> 從 0 筆變 2 筆、皆 `succeeded`，且 SonarQube server 上實際長出 `Guidant-AI-soybean-ui` 專案。
>
> **⚠️ 交接當下有另一條線正在 agent repo 動 `sonarqube.py`**（未 commit，mtime 就是此刻），
> 處理 §4.1 的品質門檻條件顯示問題。**接手第一件事是確認那條線收工了沒**，未收工完全不碰
> agent repo。而且它的調查結論**推翻了本文原先的根因推測**（見 §4.1）。
>
> 前三棒交接文件（**§1 狀態盤點全部已過期**，只讀教訓 / 決策脈絡 / 裁示）：
> - `2026-07-31-fr058-coordinator-handoff-3.md`（第三棒）
> - `2026-07-31-fr058-coordinator-handoff-2.md`（第二棒）
> - `2026-07-31-fr058-coordinator-midarc-handoff.md`（第一棒）

| 項目 | 內容 |
|------|------|
| 本棒角色 | **協調、決策、派工、抽查。不寫 code、不跑測試、不寫文件——全部派 subagent（不帶 model 參數）** |
| Branch | 三 repo 皆 `feature/FR-058`（**不切 branch**） |
| 接手第一件事 | §4.1——確認 agent repo 那條線是否收工（`git status` 有無未 commit 的 `sonarqube.py`） |
| 下一個動作 | 收工後驗收該修正 → 等決策者驗收 FR-058.6 → 等決策者下令才收尾 |
| 預估時間 | 驗收 0.5h；全案收尾（若獲令）2~3h |

---

## 🧭 原始需求 / WHY（不讀會誤解範圍，也會把 FR-058.6 誤判成「推翻前面的決策」）

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

### 工具清單的演變（不讀會搞不清楚為何最終清單跟原話對不上）

| 階段 | 發生什麼 |
|------|---------|
| 起點 | 四項：OpenSCAP for Windows / Nmap / GCB / SonarQube |
| 中途 | 追加 **ZAP**（第五個） |
| 裁示① | **SonarQube 本期不做**（D10 成 tombstone） |
| 調研 | 「OpenSCAP for Windows」**證實不可行**（官方已放棄 Windows 支援），改由 **InSpec / CINC Auditor** 承擔 |
| 裁示② | ZAP 優先上線，其餘分批 |
| 裁示③（07-31 晚） | **SonarQube 重新納入**，立為 **FR-058.5**（pull 拉取快照，D13–D16） |
| 裁示④（07-31 深夜） | 決策者看到實際畫面後指出「**流程不對**」——要平台真的發動掃描 → **FR-058.6 主動掃描模式**（D17–D24） |

**本案要解決兩件事**：① 接入工具（現已八款）；② 補平台結構性缺口（任務層敏感參數——原本
只有租戶層憑證有加密，任務層是明文且 FE 執行紀錄會顯示）。

### 🔑 FR-058.6 的關鍵認知（下一棒最容易誤解的點，務必讀懂）

**FR-058.5（pull 拉取快照）不是做錯了。** D13 當時裁示「connector 不觸發掃描、只讀 CI 已推上去
的結果」，在**當時的前提下是正確的**——那時的輸入模型裡根本沒有「使用者提供源碼」這個東西，
SonarQube Server 又確實**沒有任何觸發掃描的 API**，所以除了讀既有結果別無選擇。

**FR-058.6 是前提變了，不是反覆。** 決策者補上了新輸入（使用者提供 repo URL），平台才得以
在 agent 端打包 sonar-scanner 主動跑一次分析、把結果推上客戶 server 再讀回來。

**兩模式並存（D17）**，同一張工具卡靠 `scan_mode` 參數分流，理由有二：

1. **對「已有成熟 CI 的客戶」，pull 仍是正確且更輕量的答案**——他們的 CI 本來就會推分析結果，
   平台再跑一次是重複做工、浪費資源，還可能因為掃描環境不同產生跟 CI 不一致的數字
2. **C# / VB.NET 技術上永遠只能走 pull**——這兩種語言的 SonarQube 分析器**必須**依附
   MSBuild（`SonarScanner.MSBuild.exe` 要在 build 前後各插一段、吃編譯器輸出）。
   agent 端的通用 sonar-scanner CLI 拿不到編譯期資訊，**不是排程或資源問題，是架構限制**

所以「未來把 pull 收掉、統一走 scan」這個方向**不成立**，不要往那邊想。

---

## §0 接手讀序

1. **本文「🧭 原始需求 / WHY」+ §1 現況 + §4 下一步**（必讀，這是硬 gate）
2. **本文 §3 裁示 + §5 教訓**（必讀，避免重蹈與重開已定案的討論）
3. 第三棒 `2026-07-31-fr058-coordinator-handoff-3.md` 的 **§2（教訓）**——⚠️ 其 §1 狀態盤點已過期
4. 第一 / 二棒的 **教訓 / 決策脈絡 / 裁示** 三節——同樣不讀其 §1
5. `design.md` **§2 決策表 D1–D24**（D13–D16＝FR-058.5 pull；**D17–D24＝FR-058.6 scan**）
   + **§4.7**（主動掃描流程）+ **§5.9**
6. **全案 SUMMARY**：`2026-07-31-fr058-arc-SUMMARY.md`（⚠️ 尚未涵蓋 FR-058.6，收尾時要補）
7. 需細節才開：`handoff/2026-07-31-t63-gate-blocked.md`（gate 停下的紀錄，見 §5.1 教訓）

### 冷接自檢（答不出來回去讀，不要碰任何東西）

1. **同一張 SonarQube 工具卡上的 pull 與 scan 兩個模式，「誰跑掃描」這件事差在哪？
   為什麼平台不能乾脆統一成 scan？**
   提示：SonarQube Server 有沒有觸發掃描的 API？pull 模式的資料是誰產生的？
   統一成 scan 會對哪一類客戶造成什麼負面影響？

2. **為什麼 C# / VB.NET 的專案在這個平台上永遠只能用 pull 模式，而 JavaScript 可以用 scan？
   這是排程限制、授權限制，還是架構限制？**
   提示：想一下這兩類語言的分析器分別在什麼時間點介入、需要拿到什麼東西。

3. **DEV 已套 19 支 fr058 migration，STG / POC 只有 13 支。這是待修的落差還是正常狀態？
   如果現在有人給你一張派工單寫「三環境都套上去」，你該怎麼做？**
   提示：本 arc 有一條血淚換來的鐵律，出事的案例就在 FR-058 自己身上。

---

## §1 當前狀態（2026-08-01 10:26 協調者實查）

### 1.1 三 repo

| Repo | HEAD | 時間 | 相對 main | **未推** | working tree |
|------|------|------|----------|---------|-------------|
| **BE** | `298fef57` T-6.3 階段一檢查未通過紀錄 | 07-31 23:16 | 43+ | **4** | 有 `.claude/skills/big-feature-workflow/SKILL.md` modified + 一批**與本案無關**的 untracked docs（別的 arc 留的）——**都不要 commit、不要清理** |
| **FE** | `c167f26` T-6.3 FE 微調 | **08-01 10:02** | 14 | **1** | 乾淨 |
| **agent** | `074f3b8` bump 0.2.22（已部署 DEV） | **08-01 10:22** | 28 | **2** | ⚠️ **`core/task_executor_connectors/sonarqube.py` 有未 commit 修改**（另一條線正在動，見 §4.1） |

**裁示不變：push 永遠等決策者明示。** origin 上已有的 FR-058 commits 是決策者自己推的，
不要因為「origin 已經有了」就自己補推剩下的。

### 1.2 三環境

| 環境 | fr058 migration | sonarqube 狀態 | agent image |
|------|----------------|---------------|-------------|
| **DEV** | **19** | `available` | **`0.2.22`**（Up 22 分鐘） |
| STG | 13 | `coming_soon` | `0.2.18`（Up 17 小時） |
| POC | 13 | `coming_soon` | `0.2.18`（Up 17 小時） |

**DEV 領先 6 支（`14`–`19`）屬正常狀態，不是待修落差**：

| 檔名 | 內容 |
|------|------|
| `2026-07-31-fr058-14-twgcb-server2022-profile-option.sql` | TWGCB Server 2022 profile 選項 |
| `2026-07-31-fr058-15-detection-tools-description-v2.sql` | 工具描述 v2 |
| `2026-07-31-fr058-16-gcb-profile-option-cleanup.sql` | GCB 下拉清理 |
| `2026-07-31-fr058-17-sonarqube-seed.sql` | SonarQube seed（FR-058.5，param_schema v1） |
| `2026-07-31-fr058-18-twgcb-os-profiles-8-options.sql` | **TWGCB 8 支 OS profile 上架**（本棒） |
| `2026-07-31-fr058-19-sonarqube-scan-mode.sql` | **param_schema v2 雙模式**（FR-058.6，本棒） |

**皆刻意只套 DEV**（見 §3 鐵律 2）。上版時才依序補 STG / POC。

### 1.3 🎉 FR-058.6 兩模式端到端驗收**已通過**（本棒最大進展）

`compliance.detection_executions` 內 sonarqube **從 0 筆變 2 筆，皆 `succeeded`**：

| execution id | agent_task_uid | 模式 | 內容 | started_at |
|---|---|---|---|---|
| **58** | `57b1c937-…78e5` | **scan** | repo `https://github.com/soybeanjs/soybean-ui.git`、suffix `soybean-ui` | 08-01 10:18 |
| **57** | `5a523a72-…9d34` | **pull** | `project_key=guidant-ai-backend` | 08-01 10:12 |

**scan 模式的硬證據**：171 上實際出現 **`Guidant-AI-soybean-ui`** 專案
（打 `api/projects/search` 實查到），證明 D21 的 `Guidant-AI-` 前綴生效、
且掃描結果真的推上客戶 server 了。

⚠️ **這也一併補完了 FR-058.5 尚未做的 UI 驗收**（pull 模式從畫面發任務跑通），
該項原掛在 CM-997。

### 1.4 T-6.1 / T-6.2 的欄位 key 契約（協調者已交叉驗證，**一致**）

A（BE param_schema）/ B（agent connector）並行實作時，唯一的風險點是兩邊各自取名。實查對得上：

| BE `param_schema` v2 欄位 | agent connector 讀取 |
|---|---|
| `scan_mode`（select, required, **預設 `scan`**） | `params.get("scan_mode")`，`_DEFAULT_SCAN_MODE = _SCAN_MODE_SCAN` |
| `repo_url`（text, required, condition=scan） | `params.get("repo_url")` |
| `project_key_suffix`（text, required, condition=scan） | `params.get("project_key_suffix")` |
| `project_key`（text, required, condition=pull） | `params.get("project_key")` |
| `branch`（text, 選填, condition=pull） | `params.get("branch")` |

**param_schema 版本策略也正確**（實查 `config.detection_tool_param_schemas`）：

```
id=8  version=1  is_current=f    ← 保留，沒有原地 UPDATE
id=9  version=2  is_current=t
```

非法 `scan_mode` 值會 raise，已實作（`sonarqube.py:327` `_resolve_scan_mode`
+ `:109` `_VALID_SCAN_MODES`）。前綴防重複（避免 `Guidant-AI-Guidant-AI-x`）在 `:371-379`。

### 1.5 八款工具現況

| id | code | 名稱 | status | 驗收 |
|----|------|------|--------|------|
| 1 | openvas | OpenVAS | available | 既有 |
| 2 | nessus | Nessus | coming_soon | 佔位 |
| 3 | **sonarqube** | SonarQube | **available（僅 DEV）** | ✅ **pull + scan 雙模式端到端通過**（本棒，見 §1.3） |
| 4 | openscap | OpenSCAP | available | 既有（FR-057） |
| 5 | zap | ZAP | available | ✅ 被動/主動通過；**登入後對 SPA ❌**（會打掛共用 ZAP，D12） |
| 6 | inspec | CINC Auditor | available | ✅ 雙 transport 實跑：151 pass 131/fail 60、160 pass 300/fail 472 |
| 7 | nmap | Nmap | available | ✅ 實跑通過，HTML 證據 |
| 8 | gcb | 政府組態基準（GCB） | available | ✅ TWGCB 實跑；**2026-07-31 晚上架 8 支 TWGCB 作業系統類基準**（profile 下拉由 1 個擴為 8 個，**只套 DEV**） |

### 1.6 Notion

母案 **CM-957**（修正待驗證）。相關卡：

| 卡號 | 內容 |
|------|------|
| CM-986~991 | 平台能力與 InSpec 線 |
| **CM-992** | GCB content 產製後續案——**本棒大幅更新**（見 §2.4） |
| CM-993 | 收尾追蹤 |
| **CM-994~997** | FR-058.5 SonarQube（pull） |
| **CM-998~1001** | **FR-058.6 SonarQube 主動掃描**（本棒開卡） |
| CM-984 | ZAP SPA 後續案 |
| CM-985 | 報告呈現優化（全案驗收後才做，**不納入 SonarQube**，見裁示 12） |

> Notion free plan **不支援 SQL 查資料源**，用 `notion-search` + `notion-fetch`。

---

## §2 本棒（第四棒）完成的事

### 2.1 TWGCB 8 支 OS profile 上架（commit `a8046a1e`）

決策者自製的 `twgcb2inspec.py` 轉換器產出 23 支 TWGCB profile，本棒從中挑 **8 支上架**
（Windows 4 + Linux 4），**排除 15 支**（macOS + browser / application / network / cloud）。

**排除理由不是「還沒做」而是「做了也沒用」**：

| 排除類別 | 原因 |
|---------|------|
| browser / application | **可自動檢查項數為 0**——全是人工訪談題，上架只會給出一份全 `skipped` 的報告 |
| network / cloud | 除了自動檢查項少，**目標形態根本接不上現有 transport**——SSH / WinRM 是打「一台有 OS 的主機」，網通設備與雲端租戶不是這個形狀 |
| macOS | 同上，自動檢查覆蓋率不足 |

**同批另含兩件事**：

1. `twgcb-01-011` 是**升級**不是新增（pending 225→150、自動檢查 483→558）
2. 修掉 `system_services.rb` 的**實質 bug**——原本用中文顯示名比對服務，實際 Windows
   服務比對要用服務名（顯示名會因語系不同而失配）
3. 轉換器 `tools/` 一併進版控

### 2.2 agent bump 0.2.21（commit `2aa95ec`）

清掉第三棒留下的驗證用臨時 tag `0.2.20-fr0585-verify`，走正式版號。
（後續 FR-058.6 再 bump 到 `0.2.22`，commit `074f3b8`，已部署 DEV。）

### 2.3 FR-058.6 全流程走完

討論稿（Artifact）→ `design.md` D17–D24 + §4.7 + §5.9 → Notion CM-998~1001
→ T-6.1（BE param_schema v2）/ T-6.2（agent scan 分支）**並行** → T-6.3 驗收通過。

### 2.4 Notion CM-992 大幅更新

原卡的核心假設是「Linux / macOS 幾乎零自動化，GCB content 得從頭產製」。
**決策者自製的 `twgcb2inspec.py` 直接推翻了它**——實際產出 23 支 profile / 4,351 控制項 /
**45% 可自動檢查**。

更新後 CM-992 的剩餘範圍：

1. 待補對照 **2,384 項**
2. 後台 content 管理機制
3. **新浮現的 network / cloud / browser transport 形態**（現有 SSH / WinRM 接不上，
   要接得先想清楚「連到哪裡、用什麼協定」）

---

## §3 已定裁示（不要重新討論）

沿用第三棒的裁示 1–9：

| # | 裁示 |
|---|------|
| 1 | **push 永遠等決策者明示** |
| 2 | 🔴 **開發只動 DEV**，STG / POC 需**當次明確指示**（含 DB 與部署） |
| 3 | **commit 標籤不一致不回頭修**——rebase 改寫歷史風險大於整齊 |
| 4 | **GCB content 產製 + 後台 content 管理機制**列後續案 CM-992，不在 FR-058 |
| 5 | **`config.detection_tools` 沒有 i18n 機制**——先不動（平台級缺口，FR-056 建表時就有） |
| 6 | **CM-985（報告呈現優化）全案驗收後才做** |
| 7 | **SonarQube 為 FR-058.5**（D13–D16）；**主動掃描為 FR-058.6**（D17–D24） |
| 8 | **GCB 報告內的 `Cinc Auditor Report` 標題不處理**——html2 reporter 寫死的，要動 reporter 層 |
| 9 | **SonarQube 不換報表方案**——免費方案都是同樣的「打 API 自組」路線 |

**本棒新增 10–14**：

| # | 裁示 |
|---|------|
| **10** | **`_credentials` at-rest 明文 → 發 58 版時一起改**。⚠️ **不是舊資料、仍在持續產生**（最新一筆 07-31 22:10，59 筆全明文、`is_encrypted=f`）。修法：**複用 `common/util/detection_secret_params.py` 現成的 envelope**（不要新造輪子），改存加密、心跳組裝時才解。會動派工路徑，**四款工具都要回歸** |
| **11** | **121 / 122 的 `AGENT_VERSION` 舊 pin → 跟發版一起處理**（屬顯示用版本字串，不影響功能） |
| **12** | **CM-985 不納入 SonarQube** |
| **13** | **TWGCB 其餘 15 支不上架**——等補完對照；network / cloud / browser 另需先解決 transport 形態 |
| **14** | **GCB profile 下拉不依連線方式過濾**——選 SSH 也會列出 Windows profile，選錯會派工失敗。決策者裁示「**先不動**」，屬體驗問題非功能缺陷 |

---

## §4 下一步

### 4.1 🔴 立即：品質門檻條件顯示問題（**已派工，交接當下正在進行**）

**症狀**：SonarQube 報告的「品質門檻條件」區塊顯示「此專案未設定品質門檻條件。」，
但同一份報告上方「品質門檻結果」卻正確顯示 `OK`。

**⚠️ 交接當下 agent repo 有未 commit 的 `sonarqube.py` 修改（mtime 就是此刻），
那條線正在處理這件事。接手第一件事：**

```bash
git -C ~/Projects/Billows/Audit-Manager/evidence-agent status --short
git -C ~/Projects/Billows/Audit-Manager/evidence-agent log --oneline -3
```

- **有未 commit 檔案** → 那條線還在跑，**完全不碰 agent repo**（唯讀查看可以）
- **已 commit 且乾淨** → 進驗收（見下方「驗收要點」）

#### 🔴 本文原先的根因推測**已被推翻**（這段一定要讀完再動手）

協調者派工時給的推測是「`conditions` 在 `projectStatus` 底下一層，
`_render_gate_conditions` 少剝一層」。**這個推測是錯的。**
實查 `sonarqube.py:_fetch_quality_gate` 的最後一行：

```python
return payload.get("projectStatus") or {}
```

**一直都有剝殼**，取用端讀 `gate["conditions"]` 是對的。

#### 實際成因（該條線對 SonarQube 26.7 實測的結論，協調者已核對程式碼一致）

SonarQube 內建與常見自訂 gate 的條件**幾乎全是 `new_*` 指標**（`new_violations` /
`new_coverage` / …），只評「新程式碼」。而「新程式碼」需要一個**比較基準期（`period`）**——
**專案第一次分析時沒有前一版可比**，於是 `api/qualitygates/project_status` 回
`conditions: []` 且**連 `period` 鍵都不出現**，同時 `status` 仍給 `OK`
（沒有條件可判定＝沒有條件不通過）。

**這正是「狀態有值、條件卻空」的來源。**

實測對照（可自行複現）：

| 專案 | 分析次數 | `conditions` | 實際 gate 定義 |
|---|---|---|---|
| `guidant-ai-backend` | 第 10 次（有 period） | **1 條** | — |
| `Guidant-AI-soybean-ui` | scan 模式首次（無 period） | **0 條** | 套用的 `Sonar way` **實際定義了 4 條** |

複現指令（token 查本機 `~/.zshrc` 的 `SONAR_USER_TOKEN`，**絕不寫進任何檔案**）：

```bash
curl -s -u "$SONAR_USER_TOKEN:" \
  "http://192.168.50.171:9000/api/qualitygates/project_status?projectKey=guidant-ai-backend"
curl -s -u "$SONAR_USER_TOKEN:" \
  "http://192.168.50.171:9000/api/qualitygates/project_status?projectKey=Guidant-AI-soybean-ui"
```

#### 派工時給的三項要求（驗收對照用）

決策者原話是「沒有的話就不要顯示這塊」，但協調者判斷**這不只是顯示偏好**——
若客戶明明設了門檻卻被告知「未設定」，比空白更糟（稽核員會認為受稽單位沒有把關機制）。故要求：

1. 先查清楚真相，不要直接照「藏起來」執行
2. 空狀態改**低調處理**（真的空時整塊不輸出，不要留空表也不要寫死一句錯的話）
3. **「品質門檻結果」那一格一律保留**（在「基本資訊」區）——那是客戶自己 server 的判定結果，
   藏掉等於隱瞞

#### 驗收要點（該線收工後）

- [ ] `_render_gate_conditions` 空清單時回空字串、且組 HTML 時有濾掉空區塊（不留空行）
- [ ] `guidant-ai-backend`（有 period）報告**列得出那 1 條條件**
- [ ] `Guidant-AI-soybean-ui`（無 period）報告**整塊不出現**，但「品質門檻結果」仍在
- [ ] 未把「conditions 為空 ≠ 客戶沒設定」這個認知丟掉（docstring 有記錄，別在後續重構時刪掉）

### 4.2 待決策者驗收 FR-058.6

兩模式已跑通（§1.3），但**決策者尚未親自看過報告與 UI**。等他驗收。

### 4.3 全案收尾（**等決策者下令，一項都還沒做**）

| # | 項目 |
|---|------|
| 1 | SPEC 三頁補 **SonarQube 雙模式** + **GCB 8 支 profile**（`writing-feature-specs` skill，只改 `docs/specs/current/`） |
| 2 | 使用手冊補對應段 |
| 3 | `design.md` §11 對應段標 closed |
| 4 | **全案 SUMMARY 更新**——⚠️ 兩件事：① 補進 FR-058.6；② **「任務層敏感參數已修」必須補範圍界定**（見 §5.3），否則下一棒會誤判 `_credentials` 明文為迴歸 |
| 5 | Notion：**CM-994~1001** 標 Done（FR-058.5 的 994~997 + FR-058.6 的 998~1001）+ 母案 CM-957 更新 |
| 6 | memory：本 arc 教訓濃縮（候選：gate 型 prompt 要寫重試、「沒資料就藏起來」前先驗資料源、多 session 併發 pre-flight 紀律） |
| 7 | commit 收尾文件（**顯式 `git add`**） |

### 4.4 發版時一併處理（已裁示，形成一組上版清單）

| # | 項目 | 來源 |
|---|------|------|
| 1 | **`_credentials` at-rest 明文改加密**（複用 `common/util/detection_secret_params.py`，四款工具都要回歸） | 裁示 10 |
| 2 | **121 / 122 的 `AGENT_VERSION` 舊 pin** | 裁示 11 |
| 3 | **STG / POC 補 migration 14–19（6 支）** + agent `0.2.18` → `0.2.22` | §1.2 |
| 4 | ⚠️ **STG / POC 的 sonarqube 仍是 `coming_soon`**，上版時要一起改 | §1.2 |

> 🔴 以上四項**全部要等決策者當次明確指示**才能碰 STG / POC（環境異動鐵律）。

---

## §5 教訓

### 5.1 gate 型 prompt 要寫「重試」，只寫「停」等於把持續監控做成一次性快照

**發生什麼**：昨晚安排一個 session「半夜 4 點檢查 A（T-6.1）/ B（T-6.2）是否完成，
通過才跑 C（T-6.3 驗收）」。實際上該 session **23:16 就執行了檢查**，當時 T-6.2 尚未 commit
（**23:25 才完成，差 9 分鐘**），於是停在 gate、留下 commit `298fef57` 記錄，
**之後沒有再重試**，整晚空轉。

**停下是正確行為**——協調者明確要求「不通過就停、不准硬跑」，它照做了。
問題出在 **prompt 只寫了「停」，沒寫「等一段時間再檢查一次」**。

**紀律**：寫 gate 型 prompt（「檢查 X 是否完成，通過才做 Y」）時，
**必須同時指定重試策略**——間隔多久、重試幾次、最終仍不通過才停下回報。
只寫「停」，等於把一次性快照當成持續監控用。

### 5.2 「沒資料就藏起來」之前，先確認是不是我們沒讀到

**發生什麼**：決策者看到報告顯示「此專案未設定品質門檻條件」，
直覺反應是「沒有的話就不要顯示這塊」——**這個要求本身完全合理**。
但實際打 API 查證後發現，`guidant-ai-backend` **確實設了條件**（回傳 1 條），
是我們這邊沒有正確呈現。

若直接照「藏起來」執行，**這個問題會被永久掩蓋**，客戶會持續收到錯誤資訊。
（後續查證更發現真相比想像複雜——見 §4.1，空與非空取決於有沒有比較基準期，
不是單純的「有設 / 沒設」。）

**紀律**：收到「這塊沒東西，藏掉吧」這類要求時，先問「**是真的沒有，還是我們沒讀到**」。
用最短路徑驗證資料源（打一次 API / 查一次 DB），再決定是改顯示還是修解析。
**顯示層的要求，不能免除對資料層的查證。**

### 5.3 承襲前三棒仍然有效的教訓（不要重蹈）

| # | 教訓 |
|---|------|
| 1 | **多 session 併發是本 arc 常態**——pre-flight 一律實查 git log / DB / 容器，不要憑交接文件。**交接文件的狀態盤點寫下當刻就開始過期**（本文也不例外）。發現另一條線在同一 repo 作業 → **完全不碰該 repo**（唯讀可以） |
| 2 | **log 裡的 ERROR 不一定是缺陷**——先問「這筆資料是走哪條路徑產生的」，再問「程式有沒有 bug」。判別法：`compliance.agent_tasks.created_user IS NULL` 只可能來自手工 INSERT（API 路徑必填 `curr_user`） |
| 3 | **「已修」的宣稱要問清楚範圍**——FR-058 修的是 `job_execution_detection_tools.tool_params` 內 `secret:true` 欄位，**不含 `_credentials`**（那是 FR-056 既有的租戶憑證派工快照）。第三棒就因此一度誤判為迴歸 |
| 4 | **錯誤分類要問「這個錯誤最早能在哪裡被發現？現在是在那裡被發現的嗎？」**——本 arc 兩次把真因（image 缺 `git`、Ruby 3.4 訊息截斷）誤報成「檢測引擎連線失敗」，兩次都把查修方向帶去憑證與網路 |
| 5 | **協調者的假設要標明是假設、容許被實測推翻**——本棒 §4.1 的根因推測就被推翻了；Ruby 3.4 那次協調者給的三個初始假設全錯 |
| 6 | **subagent 自報完成不等於正確**——驗收要獨立抽查（開檔 / 查 DB / fetch Notion） |

---

## §6 Pre-flight（接手必跑）

```bash
# ① 三 repo 狀態（期望：BE 未推 4 / FE 未推 1 / agent 未推 2）
for d in ~/Projects/Billows/Audit-Manager/compliance-manager-be \
         ~/Projects/Billows/Audit-Manager/compliance-manager-fe \
         ~/Projects/Billows/Audit-Manager/evidence-agent; do
  echo "=== $(basename $d) ==="; git -C "$d" branch --show-current; git -C "$d" log --oneline -1
  echo "ahead-main=$(git -C "$d" rev-list --count main..HEAD) unpushed=$(git -C "$d" rev-list --count origin/feature/FR-058..HEAD 2>/dev/null)"
  git -C "$d" status --short | head -5
done
# ⚠️ agent repo 若 sonarqube.py 仍 modified → §4.1 那條線還在跑，不要碰該 repo

# ② 三環境 DB（密碼查 .env 的 DB_SECRET，勿寫進任何檔案）
export PGPASSWORD=$(python3 -c "import json;from dotenv import dotenv_values;print(json.loads(dotenv_values('$HOME/Projects/Billows/Audit-Manager/compliance-manager-be/.env')['DB_SECRET'])['rds_master_password'])")
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 ==="
  psql -h $h -p 25432 -U cmmgr -d $n -tAc \
    "SELECT code||'='||status FROM config.detection_tools ORDER BY id" | tr '\n' ' '; echo ""
  psql -h $h -p 25432 -U cmmgr -d $n -tAc \
    "SELECT count(*) FROM public.schema_migrations WHERE filename LIKE '%fr058%'"
done
# 期望：DEV 19 支 + sonarqube=available；STG/POC 13 支 + sonarqube=coming_soon（正常，非落差）

# ③ SonarQube 端到端執行紀錄（期望 2 筆，皆 succeeded；⚠️ 欄位是 agent_task_uid 不是 agent_task_id）
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -c \
  "SELECT e.id, e.agent_task_uid, e.status, e.started_at FROM compliance.detection_executions e
   JOIN config.detection_tools d ON d.id=e.detection_tool_id WHERE d.code='sonarqube'
   ORDER BY e.id DESC LIMIT 6"

# ④ param_schema 版本（期望 v1 is_current=f 保留、v2 is_current=t）
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -tAc \
  "SELECT ps.id||' v'||ps.version||' current='||ps.is_current FROM config.detection_tool_param_schemas ps
   JOIN config.detection_tools d ON d.id=ps.detection_tool_id WHERE d.code='sonarqube' ORDER BY ps.id"

# ⑤ 三台 agent（期望 DEV=0.2.22、STG/POC=0.2.18）
for h in 192.168.50.121 192.168.50.122 192.168.50.123; do
  echo -n "$h: "; ssh -o ConnectTimeout=8 jedi@$h \
    "docker ps --filter name=guidant-ai-agent --format '{{.Image}}|{{.Status}}'"
done

# ⑥ BE 服務（port 8000；main_socketio.py 是 socket 8002 不是 API 入口，重啟必先 kill -9）
lsof -i :8000 | head -3
tail -200 ~/Projects/Billows/Audit-Manager/compliance-manager-be/log/app.log | grep -i 'Traceback\|ERROR' | tail -10

# ⑦ agent 測試（必帶 PYTHONPATH=.，repo 缺 conftest.py）
# ⚠️ working tree 有未 commit 檔案時不要跑（另一條線在動）
cd ~/Projects/Billows/Audit-Manager/evidence-agent && PYTHONPATH=. poetry run pytest test/ -q | tail -3
# 基線：0.2.21 當下為 487 passed / 1 skipped；T-6.2（scan 分支）加入後會更高，請實跑確認
```

---

## §7 環境座標

| 項目 | 值 |
|------|-----|
| DEV agent | `192.168.50.123`，容器 `deploy-guidant-ai-agent-1`，deploy 目錄 **`/opt/evidence-agent/deploy`**（⚠️ 不是 `~/deploy`），ssh `jedi`，目前 image **`0.2.22`** |
| STG / POC agent | `192.168.50.122` / `192.168.50.121`，皆 `0.2.18` |
| 測試目標 | `192.168.50.151`（Linux/SSH，`audit-scan` 已設 NOPASSWD sudo）、`192.168.50.160`（Windows Server 2025/WinRM） |
| SonarQube | `http://192.168.50.171:9000`（實查版本 **26.7.0.124771**）。**token 在本機 `~/.zshrc` 的 `SONAR_USER_TOKEN`**——⚠️ 只用變數名，**絕不把值寫進任何檔案** |
| scan 模式驗證用公開 repo | `https://github.com/soybeanjs/soybean-ui.git`（suffix `soybean-ui` → server 上專案 `Guidant-AI-soybean-ui`） |
| DB | DEV / STG `192.168.50.188`、POC `192.168.50.189`，port **25432**，帳號 `cmmgr`（密碼查 `.env` 的 `DB_SECRET`） |
| content 投放 | 主機 `/opt/evidence-agent/deploy/content/` → 容器 `/data/content/`（唯讀掛載） |
| profile 版控 | BE repo `content/detection-profiles/`（**不放 agent repo**——Dockerfile 最後一行 `COPY . .` 會讓它進 image，違反 D7） |

---

## §8 行為規範

- **不切 branch**——永遠在 `feature/FR-058`；branch 不對停下問決策者
- **push 永遠等明示**——即使 origin 上已有部分 commits
- **顯式 `git add` 檔名，禁 `-am`**——BE working tree 有一批無關的 untracked docs 與
  `.claude/skills/big-feature-workflow/SKILL.md` 的既有修改，**都不要 commit、不要清理**
- **收尾等命令**——plan approve ≠ 自動收尾
- **憑證禁入版控**——SonarQube token / DB 密碼 / SSH key 一律只寫變數名或「查 `.env` / `~/.zshrc`」
- **BE 出錯先看 log**：`tail -200 log/app.log | grep -A 30 -i 'Traceback\|ERROR'`
- **改 service / serializer 後 BE 必重啟**（`kill -9` 舊 pid，`use_reloader=False` 不會自動重載）
- **subagent 一律不帶 `model` 參數**（繼承 1M context）；文件類派工必寫「**分段寫檔**」
- **測試一律 headless**
- **任務發佈後最長等 30 秒才開跑**（心跳週期，正常行為不是故障。⚠️ 第三棒文件寫 300 秒是**錯的**）
- **agent image 重建必帶 `--platform linux/amd64`**，build 完 `docker inspect` 確認
- **`docker compose up` 必指定 service 名**（`guidant-ai-agent`），否則連帶 recreate `agent-db` / `nginx`
- 已知現象：本機 `docker image inspect <tag>` 對新 tag 可能報 No such image，用 image ID 可以
  （Docker Desktop containerd 多平台索引行為，非損壞）
- **另一條線在同一 repo 作業時，完全不碰該 repo**（唯讀查看可以）
- 🆕 **Notion 卡的厚度要對齊任務規模**——大型 arc 才需要「卡片自足」；**小批次（≤5 張、雛形）
  卡寫薄 + 留 `design.md` 段落座標即可**。本棒開 CM-998~1001 時套了母 arc 的厚規格，
  開卡耗時與產出價值不成比例（決策者當場提出質疑）

---

## §9 收尾流程（全案驗收通過、**決策者下令才做**）

1. **SPEC 三頁**（`writing-feature-specs` skill，只改 `docs/specs/current/`）——
   補 **SonarQube 雙模式**（pull / scan 差異、各自參數、適用情境）+ **GCB 8 支 profile**
2. 使用手冊補 SonarQube 雙模式段 + GCB profile 段
3. `design.md` §11 對應段標 closed
4. **全案 SUMMARY 更新**（`2026-07-31-fr058-arc-SUMMARY.md`）——
   ⚠️ ① 補進 FR-058.6；② **「任務層敏感參數已修」補範圍界定**（見 §5.3 第 3 條）
5. **Notion：CM-994~1001 標 Done**（FR-058.5 的 994~997 + FR-058.6 的 998~1001）+ 母案 CM-957 更新
6. memory：本 arc 教訓濃縮
7. commit 收尾文件（**顯式 `git add`**）

---

## §10 不在本期 scope

- GCB content 產製 + 後台 content 管理機制（CM-992）
- **TWGCB 其餘 15 支 profile**（裁示 13）
- ZAP 對 SPA 的登入後掃描（CM-984）
- 報告呈現優化（CM-985，全案驗收後；**不納入 SonarQube**）
- `detection_tools` i18n（已裁示不動）
- GCB 報告內的 `Cinc Auditor Report` 標題（裁示 8）
- SonarQube 換報表方案（裁示 9）
- **GCB profile 下拉依連線方式過濾**（裁示 14）
- 🆕 **SonarQube scan 模式的下列能力**（皆已裁示不在本期）：
  - 檔案上傳取源碼（目前只支援公開 repo URL）
  - 私有 repo 認證
  - Java / C-C++ / Scala 主動掃描
  - **C# / VB.NET**——⚠️ 不是「本期不做」而是**技術上永遠只能走 pull**（見 WHY 段）
  - 資源控管（並行掃描數、逾時策略）
  - coverage 紅燈的處理

---

## §11 給 fresh session 的短 prompt

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

交接文件：
docs/features/FR-058-2607-detection-tools-expansion/handoff/2026-08-01-fr058-coordinator-handoff-4.md

先讀該文的「🧭 原始需求 / WHY」+ §0 讀序，讀完回答 §0 的冷接自檢三問，再跑 §6 pre-flight。
⚠️ 前三棒交接文件的 §1 狀態盤點全部已過期，只讀它們的教訓 / 決策脈絡 / 裁示三節。

現況：八款工具全數上線。FR-058.6 SonarQube 主動掃描（scan）與既有拉取（pull）
兩模式已端到端驗收通過——detection_executions 內 sonarqube 2 筆皆 succeeded，
且 171 上實際長出 Guidant-AI-soybean-ui 專案。這也一併補完了 FR-058.5 的 UI 驗收。
三 repo 未推：BE 4 / FE 1 / agent 2——但 push 仍永遠等我明示。

你的下一個動作是 §4.1——確認 agent repo 有沒有未 commit 的 sonarqube.py
（品質門檻條件顯示問題，交接當下另一條線正在處理）。還在跑就完全不碰 agent repo；
已收工則照 §4.1 的驗收要點抽查。
⚠️ §4.1 我原先給的根因推測（少剝一層 projectStatus）**已被實測推翻**，
真因是「新程式碼無比較基準期」，讀完那段再動手，別照舊推測去改。

你的角色是協調、決策、派工、抽查。不寫 code、不跑測試、不寫文件——全部派 subagent
（不帶 model 參數）。收尾類動作等我下令。
🔴 開發只動 DEV，STG/POC 要我當次明確指示才能碰（含 DB 與部署）。
```
