---
title: FR-115 掃描盤點——五支套件的主專案接線：範圍界定與切棒
---

# FR-115 掃描盤點：五支套件的主專案接線（問卷／檢測／日誌／資產／議題）

> 盤點日期 2026-09-23｜盤點卡 CM-2087｜這份文件只盤點、不掃描——切好棒等首腦開卡派工。
> 行數全部是 `wc -l` 逐檔實算（基準：分支 `feature/review`，commit `ba6cf30b4`），每棒段落附驗證指令。

**名詞先講清楚**（給不寫程式的讀者）：

- **套件**：公司自己的共用模組，例如「問卷」整套功能放在 `jedi-survey` 這個套件裡。套件本身已經掃完。
- **接線程式**：主專案裡「把套件接上產品」的那層程式——告訴套件「登入怎麼驗、權限怎麼查、資料從哪個服務拿」。套件自己不決定誰能進門，它把這件事交給接線程式，**接錯了就等於門沒鎖**。
- **DI 容器**（`di_containers/`）：負責「組裝」的檔案，決定每個服務用哪個零件建出來。組錯了不會報錯，只會讓某道檢查靜默失效。
- **守門**：檢查「你有沒有登入」「你有沒有這個功能的權限」「這筆資料是不是你的」的程式碼。

---

## 這批最該先看的幾支

1. **`app/flow_control/service/job_handlers/survey_handler.py`（196 行，W2 棒）——0 個守門關鍵字、而且從來沒有被任何一棒當成掃描目標。** 它負責「幫任務掛上問卷、替問卷拍快照」，前端送來的問卷編號直接拿去建關聯。FR-088 H4 的報告已經點名過「問卷識別碼寫入關聯表時只驗證了這筆資料存在，沒看到客戶歸屬檢查」，當時因為不在範圍內沒追下去。
2. **`infra/flow_control/repository/flow_control_job_repo_impl.py`（1,216 行，W4 棒）——整支沒有被任何一棒掃過。** 它直接拿問卷、設備套件的資料表做查詢拼接（不經套件的服務層），**套件本身的守門在這裡完全不起作用**，能擋的只剩資料庫隔離。
3. **`core/plugins/evidence_classification.py`（718 行，W7 棒）——FR-111 E4 讀過它，但那之後這支檔被改了 496 行新增、26 行刪除。** 它是 AI 金鑰解析鏈的門面，總表 §7 第 23 項點名的「非平台管理員拿不拿得到原廠金鑰」答案一半在這裡。
4. **`core/plugins/detection.py`（362 行，W3 棒）——三道解壓上限的設定在這裡（第 338～340 行）**，而總表第 115 項（規則包被當成程式執行）、第 116 項（網址來源繞過封裝檢查）的上游防線都靠這幾個數字。FR-108 十四棒只把它當「順手讀的參考」，沒有一棒把它列為掃描目標。

**為什麼這幾支要優先看**：這個 arc 反覆證明，**套件本身的守門寫得再好，接線那一層繞過去或接錯，一樣全開**（FR-088 的結論就是「套件 49 檔零新問題，洞全在主專案側」）。上面四支是「整支沒被掃過」或「掃過後大改」，而且都直接經手客戶資料或金鑰。

---

## 為什麼要另開這一批

五支套件（問卷、弱點檢測、日誌、資產、議題追蹤）的**套件本體**都已經掃完（FR-109／108／097／098／081＋101），但每一支掃描報告最後都寫了同一句話：「**主專案接線另棒**」。那一棒一直沒開。

交接文件原本寫「三支小接線合一棒」，首腦 2026-09-23 按 import 實數後發現不成立——光問卷、檢測、資產三支就有三十幾個檔、一萬兩千行。這份盤點就是要回答：**接線程式到底是哪些檔、多少行、怎麼切。**

盤點結論：**真正要掃的是 48 支檔、9,054 行，切成 7 棒**（其中第 7 棒是證據分類的接線，6 檔／1,622 行，要不要併進本批由首腦裁；不併的話是 **42 支、7,432 行、6 棒**）。比首腦按 import 估的 12,400 行少，主因是 import 名單裡有 6 支檔（合計 4,404 行，其中 3 支各破千行）其實已經被 FR-088／FR-113／FR-095／FR-077 當成掃描目標掃過，只是剛好 import 了這幾支套件的東西（見 1.4）。

---

## 1. 範圍界定

### 1.1 起手：三種方法各跑一次

**方法一：誰 import 了這五支套件**

```bash
grep -rlE "(from|import) jedi_(survey|detection|asset|issue|api_log)\b" --include="*.py" \
  api app core di_containers infra domain config common
```

抓到 **35 支**。

🔴 **日誌套件的 import 名是 `jedi_api_log`，不是 `jedi_log`**——套件在 `pyproject.toml` 叫 `jedi-log`，但 Python 程式裡的名字是 `jedi_api_log`（`core/plugins/api_log.py:87-90` 就是這樣 import 的）。所以首腦按 `jedi_log` 搜零命中。改用 `jedi_api_log` 抓到 4 支：`core/plugins/api_log.py`、`di_containers/log/apilog_containers.py`、`common/middleware/app_mw.py`、`infra/support/diag_masking.py`；另外 `main.py:240` 也有一行 `from jedi_api_log.plugin import restart_after_fork`（`main.py` 在根目錄、不在上面的搜尋路徑裡，另外補進來）。

**方法二：從接線檔與 DI 容器反查**

從五支接線檔 `core/plugins/{survey,detection,api_log,asset,issue}.py` 出發，追它們叫用了哪些容器（`lazy("survey_container", …)`、`container.asset_container` 這類寫法），再從 `di_containers/containers.py` 找到每個容器定義在哪支檔。追到的：

| 接線檔 | 叫用的容器／零件 | 容器定義檔 |
|---|---|---|
| `core/plugins/survey.py` | `survey_container`、`task_survey_container`、`question_answer_container`、`identity_container`；零件 `infra/survey/adapters.py` | `di_containers/survey/survey_containers.py`、`di_containers/task_survey/*.py` |
| `core/plugins/detection.py` | `detection_tools_container`、`detection_orchestration_container`、`job_evidence_container`、`remote_agent_container`、`workflow_execution_container`、`identity_container` | `di_containers/detection_tools/*.py`、`di_containers/detection_execution/*.py` |
| `core/plugins/api_log.py` | `api_log_container`；零件 `infra/identity/user_name_resolver.py` | `di_containers/log/apilog_containers.py` |
| `core/plugins/asset.py` | `asset_container`、`identity_container` | `di_containers/asset/asset_containers.py` |
| `core/plugins/issue.py` | `auth_container`、`identity_container`（**不接任何本套件的容器**——服務全由套件自己組） | 無 |

另外還追到兩條「不經 import、用字串指名」的接法：`config/socketio_namespaces.py:48` 用字串 `"jedi_survey.app.handler.fill_survey_socketio_handler"` 註冊問卷即時共編；`di_containers/dashboard_apis/feedback.py:23` 用字串 `"jedi_issue.plugin"` 把意見回饋申報給 AI 儀表板。**這兩支檔 grep import 抓不到**。

**方法三：按目錄名／檔名撈**

```bash
git ls-files '*.py' | grep -E "^(api|app|core|di_containers|infra|domain|config|common)/" \
  | grep -iE "survey|detect|api_?log|log_forward|/log/|asset|device|issue|feedback|information_system"
```

抓到 **37 支**。

### 1.2 交叉比對：只被一種方法抓到的檔

**只有方法三（檔名）抓到、方法一（import）抓不到的 18 支**：

| 檔 | 行 | 為什麼 import 抓不到 | 處置 |
|---|---:|---|---|
| `infra/survey/adapters.py` | 163 | 它是**被套件叫用**的零件（專案角色守門、Redis 線上名單、任務編號換算），自己不 import 套件 | 🔴 **納入 W1**——問卷的專案角色守門就在這支（`ProjectRoleGuardAdapter`） |
| `di_containers/dashboard_apis/survey.py` | 44 | 用容器名字串指到問卷服務，不 import 套件 | 納入 W1（AI 儀表板這條路不經網頁守門，見 3 節） |
| `di_containers/dashboard_apis/device.py` | 21 | 同上，指到資產容器 | 納入 W6 |
| `di_containers/dashboard_apis/feedback.py` | 25 | 同上，用字串 `"jedi_issue.plugin"` | 納入 W6 |
| `app/detection_tools/task_type_declaration.py` | 109 | 檢測任務型別的申報書，只宣告常數 | 納入 W3 |
| `common/code/detection_tools_error_code.py` | 209 | 錯誤碼常數表 | 納入 W3（研究員判斷「錯誤訊息漏資訊」時要對照） |
| `common/code/feedback_code.py` | 5 | 三個字串常數，**全專案只有文件產生腳本引用它**，執行期沒人用 | 納入 W6（5 行成本零；也順便確認它真的是死碼） |
| `infra/readmodel/detection/detection_job_notify_query.py` | 161 | 被 DI 容器 import，本身只 import 主專案的東西 | 納入 W3 |
| `infra/remote_agent/adapter/detection_lifecycle_listener.py` | 37 | 同上 | 納入 W3（FR-077 R3 已掃過，列為接縫、見 1.4） |
| `app/detection_tools/__init__.py` 等 8 支 | 0 | 空的 `__init__.py` | 空檔，見 1.5 |
| `infra/readmodel/detection/__init__.py` | 14 | 只有說明文字 | 不派工，見 1.5 |

（`common/util/system_asset_snapshot.py`、`infra/remote_agent/adapter/detection_task_payload_provider.py` 這類檔名本身帶關鍵字、又 import 套件的，兩種方法都抓得到，不在上下兩張表裡。）

**只有方法一（import）抓到、方法三（檔名）抓不到的 16 支**——這些都是**別的模組的檔案，順便 import 了這五支套件的東西**：

| 檔 | 行 | import 了什麼 | 處置 |
|---|---:|---|---|
| `app/flow_control/service/job_service.py` | 455 | 檢測／問卷的 domain service | 納入 W2（任務建立時掛問卷、掛檢測工具的入口） |
| `app/flow_control/dto/job_dto.py` | 360 | 檢測的「敏感參數遮罩」函式 | 納入 W2（**任務回給前端前要不要遮掉密碼，在這支決定**） |
| `app/flow_control/service/job_handlers/__init__.py` | 36 | 檢測的綁定處理器 | 納入 W2 |
| `app/flow_engine/service/job_evidence_service.py` | 191 | 問卷 domain service | 納入 W2（FR-088 H2 掃過、但之後改了 13 行，見 1.4） |
| `infra/readmodel/tasks/job_batch_complete_query.py` | 284 | 問卷 model | 納入 W2 |
| `infra/flow_control/repository/flow_control_job_repo_impl.py` | 1,216 | 問卷、設備的**資料表 model** | 納入 W4 |
| `infra/flow_control/repository/job_import_lookup_query.py` | 149 | 設備資料表 model | 納入 W4 |
| `infra/readmodel/tasks/job_export_query.py` | 264 | 設備資料表 model | 納入 W4 |
| `common/middleware/app_mw.py` | 297 | 日誌套件的服務 | 納入 W5（每個請求都經過它，寫操作日誌） |
| `infra/support/diag_masking.py` | 198 | 日誌套件的遮罩函式 | 納入 W5 |
| `common/util/profile_extractor/__init__.py` | 12 | 轉手檔（把套件的東西掛回舊路徑） | 納入 W3 |
| `app/flow_engine/service/workflow_execution_service.py` | 1,374 | 問卷 | **排除**，見 1.4 |
| `app/module_frame/service/ssp_import_template_app_service.py` | 1,500 | 資產 | **排除**，見 1.4 |
| `app/oscal/service/ssp_control_implementation_service.py` | 1,037 | 檢測 | **排除**，見 1.4 |
| `app/oscal/service/ssp_resources_context_service.py` | 230 | 資產 | **排除**，見 1.4 |
| `core/plugins/task.py` | 138 | 問卷的任務型別申報 | **排除**，見 1.4 |

**只有方法二（DI 反查）抓到、另兩種都抓不到的 3 支**：

| 檔 | 行 | 為什麼只有 DI 反查抓得到 | 處置 |
|---|---:|---|---|
| `config/socketio_namespaces.py` | 52 | 用**字串**註冊問卷即時共編（`/socket/fill-survey`），不 import | 納入 W1 |
| `infra/identity/user_name_resolver.py` | 85 | 日誌接線把它傳給套件當「帳號換暱稱」零件 | 納入 W5 |
| `core/plugins/_host.py` | 212 | 五支接線共用的守門預設值（`host_defaults()` 決定「登入」和「能力點」各給什麼） | 納入 W1／W3／W6 當接縫檔，見 2 節 |

### 1.3 共用但不屬於本批的檔（查到過、判斷過，列出來避免重查）

- **`di_containers/containers.py`（397 行）**：總組裝表，每支容器在這裡登記一行。只是登記、沒有邏輯。研究員要追「某容器接到哪」可以讀，但不排進任何一棒。
- **`core/plugins/__init__.py`（83 行）、`config/app_modules.py`（146 行）**：插件清單與模組註冊，只有名字沒有邏輯。
- **`app/flow_control/service/job_import_service.py`（470 行）、`app/flow_control/service/job_batch_complete_service.py`（193 行）**：W4／W2 的讀取模型真正的呼叫者。**它們屬於任務平台（FR-095）的地盤**，FR-095 決策者裁定停在三棒、這兩支沒被掃。這批不是任務平台的補掃，所以不納入；但 FR-095 已經點名 `job_batch_complete_service.py:50-56` 預設把呼叫者當管理員（總表第 59 項），W2 研究員讀到時知道有這條就好，不用重報。
- **`api/flow_control/routes/job_route.py`（259 行）**：任務 CRUD 的網址入口。只掛「登入＋授權模組」、沒掛能力點，權限在 `job_service._require_manager` 那層。同樣屬任務平台地盤，W2 可讀不列。
- **`app/system_config/service/guarded_system_config_service.py`（665 行）**：會 import AI 金鑰加密函式，但它屬系統設定模組，FR-096 H1 已掃。
- **`core/plugins/ai_bot.py`、`di_containers/ai_dashboard/ai_dashboard_containers.py`**：也叫用 AI 金鑰解析鏈，但分屬 FR-082／FR-083 已掃的套件。
- **`common/authz/`**：全專案共用守門本體，FR-085 C1 已掃。研究員會追進去讀，屬正常，**不要重報**已知的守門實作問題。

### 1.4 排除清單（已經被別棒當成掃描目標掃過，不重複掃）

| 檔 | 行 | 哪一棒掃過 | 掃過後有沒有改 |
|---|---:|---|---|
| `app/flow_engine/service/workflow_execution_service.py` | 1,374 | FR-088 H4（21 檔範圍的主檔） | 改過（+45／-230，主要是刪死碼與 FR-112 兩處功能），⚠️ 見第 5 節第 3 條 |
| `app/module_frame/service/ssp_import_template_app_service.py` | 1,500 | FR-113 B4（2 檔範圍的主檔） | 2026-09-23 剛掃，無 |
| `app/oscal/service/ssp_control_implementation_service.py` | 1,037 | FR-113 O1 | 無明顯改動 |
| `app/oscal/service/ssp_resources_context_service.py` | 230 | FR-113 O5 | 無明顯改動 |
| `core/plugins/task.py` | 138 | FR-095 H1（35 檔範圍內） | 掃描版本 `739a0a61` 至今零改動 |
| `infra/remote_agent/adapter/detection_task_payload_provider.py` | 125 | FR-077 R3（16 檔範圍內） | 零改動 |
| `infra/remote_agent/adapter/detection_lifecycle_listener.py` | 37 | FR-077 R3（同上） | 零改動——**但仍列進 W3 當接縫檔**（37 行，順著檢測編排讀下去會碰到它） |

合計排除 **6 支、4,404 行**（`detection_lifecycle_listener.py` 不算在內，它重疊帶入 W3）。

**「被提到」不等於「被掃過」**：下面這幾支在舊報告裡出現過檔名，但**只是研究員順手讀的參考、不在該棒的掃描範圍內**，所以**不排除、照樣排進本批**：

- `core/plugins/detection.py`：FR-108 D1／D2-1a／D2-4a／D3-1、FR-113 O3b 都引用過，全是「查證某一點時打開看了一行」。
- `core/plugins/api_log.py`：FR-097 L1 只查了第 185-186 行「轉送端點有掛權限」這一點。
- `core/plugins/asset.py`、`core/plugins/issue.py`：FR-098 A2、FR-101 J1 只引用「`mount_api=True` 在第 N 行」。
- `common/middleware/app_mw.py`：FR-085 C2 在它身上找到 C2-1（高風險，請求原文寫進日誌），但 C2 的範圍是 `jedi-common` 套件、這支是越界；FR-097 L2 也寫明「不在本棒掃描範圍內」。**沒有一棒完整掃過它**。
- `core/plugins/evidence_classification.py`、`app/system_config/service/ai_provider_key_resolver.py`：FR-111 E4 明寫「不在本棒範圍、但結論依賴它們」；FR-095 H1 雖然列了 `evidence_classification.py`，但那是 `739a0a61` 版本，之後這支改了 +496／-26 行，形同新檔。
- `di_containers/detection_tools/detection_orchestration_containers.py`：FR-077 R3 只查了第 119 行「是 Factory 不是 Singleton」。
- `di_containers/dashboard_apis/{survey,device,feedback}.py`：FR-083 D2 範圍含整個 `dashboard_apis/`，**但報告自陳工具只查了 auth／project 兩支，survey／device／feedback 標「⬜ 待補」**；而且 feedback.py 在 FR-083 之後（09-16）改過。這三支當成沒查過。

### 1.5 不列入切棒的空檔（11 支，0 行）

```
app/detection_tools/__init__.py
di_containers/asset/__init__.py
di_containers/detection_execution/__init__.py
di_containers/detection_tools/__init__.py
di_containers/log/__init__.py
di_containers/survey/__init__.py
di_containers/task_survey/__init__.py
infra/survey/__init__.py
di_containers/identity/__init__.py
infra/remote_agent/__init__.py
infra/remote_agent/adapter/__init__.py
```

（前 8 支是三種方法名單裡的空檔；最後三支 `di_containers/identity/__init__.py`、`infra/remote_agent/__init__.py`、`infra/remote_agent/adapter/__init__.py` 不在原始名單裡，是追接縫時碰到的，一併列出。）另外 `infra/readmodel/detection/__init__.py`（14 行）只有說明文字、沒有邏輯，不派工。

### 1.6 最終要掃的範圍

**48 支不重複檔案、9,054 行**（含 W7；不含 W7 是 42 支、7,432 行）。

來源拆解（每一步都實跑過）：

| 步驟 | 檔數 |
|---|---:|
| 方法一（import）35 支 ∪ 方法三（檔名）37 支 | 53 |
| 扣掉 8 支空 `__init__.py` ＋ 1 支純說明檔 `infra/readmodel/detection/__init__.py` | 44 |
| 扣掉 1.4 的 6 支已掃檔（4,404 行） | 38 |
| 加回方法二（DI 反查）與 1.1 另外追到的 4 支：`config/socketio_namespaces.py`、`core/plugins/_host.py`、`main.py`、`infra/identity/user_name_resolver.py` | 42（W1～W6，7,432 行） |
| 加上 W7 證據分類接線 6 支（FR-111 列的 5 支＋`infra/system_config/system_config_root_reader.py`） | **48（9,054 行）** |

`infra/remote_agent/adapter/detection_lifecycle_listener.py`（37 行）是 FR-077 R3 掃過的檔，但沒有列進 1.4 的 6 支排除——它當接縫檔帶入 W3，算在 48 支裡。

驗證指令（已實跑，輸出 `48 支檔、9054 行`；去掉最後 6 行就是 W1～W6 的 `42 支檔、7432 行`）：

```bash
cat <<'EOF' | sort -u | while IFS= read -r f; do echo "$(wc -l < "$f") $f"; done | awk '{s+=$1; c+=1} END {print c" 支檔、"s" 行"}'
core/plugins/survey.py
infra/survey/adapters.py
di_containers/survey/survey_containers.py
di_containers/task_survey/task_survey_containers.py
di_containers/task_survey/question_answer_containers.py
di_containers/dashboard_apis/survey.py
config/socketio_namespaces.py
core/plugins/_host.py
app/flow_control/service/job_handlers/survey_handler.py
app/flow_control/service/job_handlers/__init__.py
app/flow_control/service/job_service.py
app/flow_control/dto/job_dto.py
app/flow_engine/service/job_evidence_service.py
infra/readmodel/tasks/job_batch_complete_query.py
core/plugins/detection.py
di_containers/detection_tools/detection_tools_containers.py
di_containers/detection_tools/detection_orchestration_containers.py
di_containers/detection_execution/detection_execution_containers.py
infra/readmodel/detection/detection_profile_usage_query.py
infra/readmodel/detection/detection_job_notify_query.py
infra/remote_agent/adapter/detection_lifecycle_listener.py
app/detection_tools/task_type_declaration.py
app/detection_tools/dto/__init__.py
app/detection_tools/service/__init__.py
common/util/profile_extractor/__init__.py
common/code/detection_tools_error_code.py
infra/flow_control/repository/flow_control_job_repo_impl.py
infra/flow_control/repository/job_import_lookup_query.py
infra/readmodel/tasks/job_export_query.py
core/plugins/api_log.py
di_containers/log/apilog_containers.py
common/middleware/app_mw.py
infra/support/diag_masking.py
main.py
infra/identity/user_name_resolver.py
core/plugins/asset.py
di_containers/asset/asset_containers.py
di_containers/dashboard_apis/device.py
common/util/system_asset_snapshot.py
core/plugins/issue.py
di_containers/dashboard_apis/feedback.py
common/code/feedback_code.py
core/plugins/evidence_classification.py
di_containers/evidence_classification/evidence_classification_containers.py
app/system_config/service/ai_provider_key_resolver.py
infra/flow_engine/models/job_evidence.py
common/middleware/request_context_mw.py
infra/system_config/system_config_root_reader.py
EOF
```

`core/plugins/_host.py` 在 W1／W3／W6 三棒都列，清單裡只寫一次。**W7 那 6 支（證據分類）要不要併進本批由首腦裁**（見第 5 節第 1 條）。

---

## 2. 切棒（7 棒，每棒 ≤2,000 行、≤30 檔）

切法原則：**一棒一條完整接線路徑**——從「套件怎麼被掛上產品」（`core/plugins/xxx.py`）到「套件的服務怎麼被組起來」（DI 容器）到「主專案哪些地方繞過套件直接用它的資料」，放在同一棒。不按層切，因為接線漏洞就活在「套件以為宿主會守、宿主以為套件會守」那條縫上。

**共用接縫檔 `core/plugins/_host.py`（212 行）重複帶入 W1、W3、W6 三棒**：它的 `host_defaults()` 決定「登入」和「能力點」這兩道門各給套件什麼東西——資產、議題整包吃它，日誌取其中幾項；問卷、檢測自己手寫守門，但用它的 `lazy()`／`di()` 在請求當下才去 DI 容器取服務（「取錯實例」的坑就在這，見該檔註解）。**這三棒各自都要回答「我這支套件拿到的守門和服務是不是跟別人一樣」**，所以每棒都要看得到它。

### W1 — 問卷的接線本體（掛載、零件、組裝、即時共編、AI 儀表板）

問卷套件怎麼被掛上產品：兩個網址群（設計問卷、填答問卷）、一條即時共編 socket、AI 儀表板兩支查詢。

```
core/plugins/survey.py                                        186   ← 接線主檔：守門怎麼給、兩條掛載路徑
infra/survey/adapters.py                                      163   ← 🔴 專案角色守門（ProjectRoleGuardAdapter）、任務編號換算
di_containers/survey/survey_containers.py                     124
di_containers/task_survey/task_survey_containers.py            56
di_containers/task_survey/question_answer_containers.py        97
di_containers/dashboard_apis/survey.py                         44   ← AI 儀表板這條路不經網頁守門
config/socketio_namespaces.py                                  52   ← 即時共編 /socket/fill-survey 用字串註冊
core/plugins/_host.py                                         212   ← 接縫檔，W3／W6 重複帶入
```

共 **8 檔／934 行**。驗證：`git ls-files -- core/plugins/survey.py infra/survey/adapters.py di_containers/survey/survey_containers.py di_containers/task_survey/task_survey_containers.py di_containers/task_survey/question_answer_containers.py di_containers/dashboard_apis/survey.py config/socketio_namespaces.py core/plugins/_host.py | xargs wc -l | tail -1`（預期 8 檔、934 total）

### W2 — 問卷與檢測被「任務」使用的那一面

任務建立、修改時把問卷和檢測工具掛上去；任務回給前端時遮掉敏感參數；任務證據（問卷答案）怎麼讀；批次完成時問卷狀態怎麼查。**這些是主專案「拿套件的東西來用」，不是套件自己的路**——套件守門管不到這裡。

```
app/flow_control/service/job_handlers/survey_handler.py       196   ← 🔴 0 守門、從沒掃過：問卷掛到任務、拍快照
app/flow_control/service/job_handlers/__init__.py              36   ← 看得到檢測綁定處理器從套件 import 回來
app/flow_control/service/job_service.py                       455   ← 任務 CRUD 入口，_require_manager 守門在這
app/flow_control/dto/job_dto.py                               360   ← 🔴 任務回前端前的密碼遮罩
app/flow_engine/service/job_evidence_service.py               191   ← FR-088 H2 掃過，之後改 +13／-3
infra/readmodel/tasks/job_batch_complete_query.py             284   ← 批次完成時查問卷狀態
```

共 **6 檔／1,522 行**。驗證：`git ls-files -- app/flow_control/service/job_handlers/survey_handler.py app/flow_control/service/job_handlers/__init__.py app/flow_control/service/job_service.py app/flow_control/dto/job_dto.py app/flow_engine/service/job_evidence_service.py infra/readmodel/tasks/job_batch_complete_query.py | xargs wc -l | tail -1`（預期 6 檔、1522 total）

**W2 必須在 W1 之後、由同一個 runner 接著跑**：`survey_handler.py` 裡寫進關聯表的問卷，後續是透過 W1 的 `infra/survey/adapters.py`（`TaskUidResolverAdapter`）換算回任務編號給套件用。「問卷掛上任務時有沒有檢查歸屬」在 W2，「掛上去之後套件怎麼憑任務編號決定誰能看」在 W1，兩邊各看一半會出現責任真空。

### W3 — 檢測的接線本體

檢測套件怎麼被掛上產品：守門給什麼、解壓上限多少、證據寫回哪、通知怎麼拼、規則包被誰引用。

```
core/plugins/detection.py                                     362   ← 接線主檔：七個 adapter、三道解壓上限（:338-340）
di_containers/detection_tools/detection_tools_containers.py   204
di_containers/detection_tools/detection_orchestration_containers.py 125
di_containers/detection_execution/detection_execution_containers.py  33
infra/readmodel/detection/detection_profile_usage_query.py    150   ← 規則包「還有沒有人在用」的判定
infra/readmodel/detection/detection_job_notify_query.py       161   ← 通知收件人名單怎麼拼
infra/remote_agent/adapter/detection_lifecycle_listener.py     37   ← 接縫檔（FR-077 R3 掃過、零改動）
app/detection_tools/task_type_declaration.py                  109
app/detection_tools/dto/__init__.py                            26   ← 轉手檔
app/detection_tools/service/__init__.py                        26   ← 轉手檔
common/util/profile_extractor/__init__.py                      12   ← 轉手檔
common/code/detection_tools_error_code.py                     209
core/plugins/_host.py                                         212   ← 接縫檔，重複自 W1
```

共 **13 檔／1,666 行**。驗證：`git ls-files -- core/plugins/detection.py di_containers/detection_tools/detection_tools_containers.py di_containers/detection_tools/detection_orchestration_containers.py di_containers/detection_execution/detection_execution_containers.py infra/readmodel/detection/detection_profile_usage_query.py infra/readmodel/detection/detection_job_notify_query.py infra/remote_agent/adapter/detection_lifecycle_listener.py app/detection_tools/task_type_declaration.py app/detection_tools/dto/__init__.py app/detection_tools/service/__init__.py common/util/profile_extractor/__init__.py common/code/detection_tools_error_code.py core/plugins/_host.py | xargs wc -l | tail -1`（預期 13 檔、1666 total）

⚠️ 行數雖在門檻內，但 `detection_tools_error_code.py`（209 行）幾乎全是中文訊息字串，實際 token 比行數看起來重。**若首腦要壓，可把它拿掉**（W3 變 12 檔／1,457 行），研究員需要時自己查。

### W4 — 任務層直接拿問卷／設備資料表查詢（繞過套件服務層）

這三支**不叫套件的服務、直接 import 套件的資料表 model 下 SQL**。套件的守門在這條路上完全不起作用，能擋的只有資料庫隔離。

```
infra/flow_control/repository/flow_control_job_repo_impl.py  1216   ← 🔴 整支沒掃過；直接查 Survey／Device／TaskSurvey／QuestionAnswer
infra/flow_control/repository/job_import_lookup_query.py      149   ← Excel 匯入任務時查設備
infra/readmodel/tasks/job_export_query.py                     264   ← Excel 匯出任務時拼設備名稱
```

共 **3 檔／1,629 行**。驗證：`git ls-files -- infra/flow_control/repository/flow_control_job_repo_impl.py infra/flow_control/repository/job_import_lookup_query.py infra/readmodel/tasks/job_export_query.py | xargs wc -l | tail -1`（預期 3 檔、1629 total）

**刻意做成單檔大棒**：`flow_control_job_repo_impl.py` 一支就 1,216 行（62KB），是本批最大一支，也是全專案超標檔之一。它的 19 個方法全部 0 守門關鍵字（repo 層本來就不該有守門），研究員要回答的是「每一條查詢有沒有被資料庫隔離罩住、有沒有哪條查詢把別家的問卷／設備拼進來」。硬塞別的檔只會稀釋。

### W5 — 日誌的接線本體（兩半：操作日誌＋日誌轉送）

日誌套件怎麼被掛上產品：操作日誌只給平台管理員看、日誌轉送用能力點分讀寫；每個請求怎麼被寫進操作日誌；錯誤追蹤怎麼遮罩。

```
core/plugins/api_log.py                                       352   ← 接線主檔：兩半守門刻意不共用
di_containers/log/apilog_containers.py                         21
common/middleware/app_mw.py                                   297   ← 🔴 每個請求都經過，寫操作日誌（FR-085 C2-1 越界撿到的高風險就在這）
infra/support/diag_masking.py                                 198   ← 錯誤追蹤的遮罩
main.py                                                       302   ← :240 程序分叉後重啟日誌轉送（多工人模式下的狀態）
infra/identity/user_name_resolver.py                           85   ← 帳號換暱稱零件（日誌、上傳兩支共用）
```

共 **6 檔／1,255 行**。驗證：`git ls-files -- core/plugins/api_log.py di_containers/log/apilog_containers.py common/middleware/app_mw.py infra/support/diag_masking.py main.py infra/identity/user_name_resolver.py | xargs wc -l | tail -1`（預期 6 檔、1255 total）

`main.py` 只有第 240 行附近跟日誌有關，其餘是啟動流程。**若首腦要壓成本可拿掉**（W5 變 5 檔／953 行），在卡片上寫「main.py:230-250 自己開檔看」即可。

### W6 — 資產與議題的接線本體（兩支都是「套件自帶守門、宿主只給零件」的形狀）

資產（設備、資訊系統清冊）與議題（意見回饋）兩支接線都很薄，而且**都是 `**host_defaults()` 整包吃預設守門**（全專案只有資產、公告、議題三支這樣吃），並排看最容易看出「有沒有哪一支少了什麼」。

```
core/plugins/asset.py                                         174   ← 接線主檔；引用計數零件（刪除前檢查有沒有人在用）
di_containers/asset/asset_containers.py                        89
di_containers/dashboard_apis/device.py                         21   ← AI 儀表板查設備
common/util/system_asset_snapshot.py                          129   ← SSP 設備清單快照（主專案直接用設備 domain service）
core/plugins/issue.py                                         195   ← 接線主檔；GitHub／GitLab 整合設定從總部那列讀
di_containers/dashboard_apis/feedback.py                       25   ← AI 儀表板查意見回饋（09-16 改過）
common/code/feedback_code.py                                    5   ← 疑似死碼
core/plugins/_host.py                                         212   ← 接縫檔，重複自 W1
```

共 **8 檔／850 行**。驗證：`git ls-files -- core/plugins/asset.py di_containers/asset/asset_containers.py di_containers/dashboard_apis/device.py common/util/system_asset_snapshot.py core/plugins/issue.py di_containers/dashboard_apis/feedback.py common/code/feedback_code.py core/plugins/_host.py | xargs wc -l | tail -1`（預期 8 檔、850 total）

議題那半幾乎是空的：FR-101 三棒重掃時 J1／J2 已經從套件側追進 `core/plugins/issue.py` 與 `_host.py` 讀過（但沒列為範圍）。這棒的價值在「並排」不在「議題本身」。

### W7 — 證據自動分類的接線（金鑰解析鏈）＊要不要併進本批由首腦裁

總表 §7 第 23 項：金鑰解析鏈「租戶自己的鑰 → 原廠鑰 → 環境變數」的本體在主專案，套件五棒只答得了「套件沒把鑰匙露出去」，答不了「解析鏈守不守」。

```
core/plugins/evidence_classification.py                      718   ← 八個 adapter；:149-203 是 AI 金鑰 adapter
app/system_config/service/ai_provider_key_resolver.py        199   ← 🔴 金鑰解析鏈本體，0 守門關鍵字、9 支函式
di_containers/evidence_classification/evidence_classification_containers.py 198   ← 雲端硬碟 token 綁在這
infra/system_config/system_config_root_reader.py             321   ← 解析鏈讀設定走這支（會關掉資料庫隔離）
infra/flow_engine/models/job_evidence.py                     117   ← 分類結果寫回的表
common/middleware/request_context_mw.py                       69   ← 「現在是誰」的身分脈絡
```

共 **6 檔／1,622 行**。驗證：`git ls-files -- core/plugins/evidence_classification.py app/system_config/service/ai_provider_key_resolver.py di_containers/evidence_classification/evidence_classification_containers.py infra/system_config/system_config_root_reader.py infra/flow_engine/models/job_evidence.py common/middleware/request_context_mw.py | xargs wc -l | tail -1`（預期 6 檔、1622 total）

FR-111 原本寫「5 檔約 1,300 行」，本盤點多加了 `system_config_root_reader.py`（321 行）：解析鏈的第一、二層都呼叫它的 `read_tenant_config_value`（`ai_provider_key_resolver.py:124`），而它會用 `SET LOCAL app.is_super_admin = 't'` 關掉資料庫隔離去讀（FR-096 H1 查到的寫法）。**「會不會讀到別家租戶的鑰」這題只有把兩支放一起看才答得了**。這支 FR-096 H1 掃過、掃描後零改動，屬重疊帶入。

---

## 3. 每棒重點看什麼

**每一棒開工前都先做這四件事**：

**① 算守門落差**。對每支有方法的檔跑：

```bash
grep -c "require_\|authz\|permission\|Forbidden\|Permission" <檔>
grep -c "^    def [a-zA-Z]" <檔>
```

盤點時已先跑一輪（下表）。**守門 0 次的檔不一定有洞**——接線檔的守門多半是「把 decorator 交給套件」而不是自己呼叫，repo／查詢層本來就不該有守門——但要能說清楚「這支為什麼不需要守門、守門在哪一層」。

| 檔 | 守門次數 | 對外方法數 | 棒 |
|---|---:|---:|---|
| `app/flow_control/service/job_handlers/survey_handler.py` | **0** | 0（mixin 私有方法 1 支，約 150 行） | W2 🔴 |
| `app/flow_engine/service/job_evidence_service.py` | **0** | 5 | W2 🔴 |
| `app/flow_control/dto/job_dto.py` | 0 | 11 | W2 |
| `app/flow_control/service/job_service.py` | 5 | 6 | W2 |
| `app/system_config/service/ai_provider_key_resolver.py` | **0** | 9 支模組函式 | W7 🔴 |
| `core/plugins/evidence_classification.py` | 8 | 22＋3 | W7 |
| `core/plugins/asset.py` | **0** | 3＋2 | W6 |
| `core/plugins/issue.py` | **0** | 2＋3 | W6 |
| `core/plugins/survey.py` | 6 | 1＋3 | W1 |
| `core/plugins/detection.py` | 10 | 15＋5 | W3 |
| `core/plugins/api_log.py` | 5 | 5 支模組函式 | W5 |
| `infra/survey/adapters.py` | 2 | 8 | W1 |
| `infra/flow_control/repository/flow_control_job_repo_impl.py` | 0 | 19 | W4（repo 層，預期 0） |

資產、議題兩支接線檔 0 次是因為它們**不自己寫守門、整包吃 `host_defaults()`**——這正是 W6 要驗的：整包吃進去的東西是不是套件真正需要的全部。

**② 找「同一套接線複製五份，某一份漏了」**。五支接線檔都是同一個三段式契約（① port adapter ② 填表 ③ 掛載），並排看：

| 接線檔 | 登入守門 | 能力點守門 | 授權模組守門 | 專案角色守門 | 平台管理員守門 | 給法 |
|---|---|---|---|---|---|---|
| survey | `jwt_required()` | `require_capability` | `require_license` | `ProjectRoleGuardAdapter`（在 `infra/survey/adapters.py`） | — | 逐項手寫 |
| detection | （套件內組） | `CapabilityGuardAdapter` | `LicenseGuardAdapter` | — | `IdentityGuardAdapter`＋`platform_admin_check`＋`scope_writable_check` | 逐項手寫 |
| api_log | `jwt_required()` | 轉送那半有，操作日誌那半**刻意沒有** | — | — | `require_platform_admin_route` | 取 `host_defaults()` 部分 key |
| asset | `host_defaults()` | `host_defaults()` | — | — | — | 整包 `**host_defaults()` |
| issue | `host_defaults()` | `host_defaults()` | — | — | — | 整包 `**host_defaults()` |

**問卷和檢測有授權模組守門（客戶有沒有買這個功能），資產和議題沒有**——W6 要確認這是刻意的（例如資產清冊是基本功能不分方案）還是漏接。

**③ 找「套件以為宿主守、宿主以為套件守」**。接線檔裡常見的註解是「守門由套件做」或「套件會自己補名」——每一處都要追到套件那一側確認真的有做。反過來，套件報告裡寫「守門由宿主注入」的地方，要在這批確認宿主真的注入了、注入的是對的東西。

**④ 特別注意 AI 儀表板那條路**。`di_containers/dashboard_apis/` 申報的查詢**不經過網頁那層的任何守門**（FR-083 D2 已證實 auth 那四支完全裸奔）。本批三支（survey 2 支查詢、device 1 支、feedback 1 支）FR-083 報告自陳「沒查、待補」，每支都要回答：這支查詢的服務方法自己有沒有做權限或歸屬檢查？

### 各棒個別重點

**W1（問卷接線本體）**：
- `infra/survey/adapters.py:43-63` 的 `ProjectRoleGuardAdapter.assert_manager`——它是問卷所有「只有專案管理者能做」的判斷依據。確認它是直接委派 `common.authz.project.assert_project_manager` 還是自己另寫一套；`group_id`／`control_id` 這兩個參數有沒有被用到（沒用到就代表只判專案層、不判控制項層）。
- **套件報告的已知問題（第 89～100 項）是「套件側讀取入口 0/8 守門」**。本棒要確認的是反面：宿主給套件的 `capability_required`、`license_guard` 有沒有給對——套件拿到守門零件卻沒套用是套件的錯（已登記），宿主根本沒給是本棒的錯。
- 即時共編（`config/socketio_namespaces.py:48`）：socketio 模式「一條 REST 都不掛、只寫 `app.extensions`」（`core/plugins/survey.py` 開頭註解）。**socket 連線本身有沒有驗登入**？套件報告第 F7 條說「房間想進哪間就進哪間」，那是套件側；宿主側要看的是 socket 握手時有沒有帶身分。
- `di_containers/dashboard_apis/survey.py` 兩支：`SurveyService.get_surveys`、`SurveyFolderService.get_folders`——空條件時回什麼？會不會回整個客戶的問卷？

**W2（任務使用問卷與檢測）**：
- 🔴 `survey_handler.py` 的 `_reconcile_task_surveys`（第 39 行起）：前端送來的問卷 `uid` 清單直接拿去建任務與問卷的關聯。**有沒有檢查這些問卷是同一家客戶的**？FR-088 H4 已經點名「只驗證了這筆資料存在，沒看到客戶歸屬檢查」、當時沒追。
- 🔴 `job_dto.py` 的敏感參數遮罩：檢測工具的帳密存在任務參數裡，任務回前端前用 `strip_secret_params` 遮掉。**每一條把任務序列化回前端的路是不是都經過這支**？有沒有哪個 DTO 繞過去直接回原始參數？
- `job_evidence_service.py`：FR-088 H2 找到的高風險（任務證明清單不檢查是不是專案成員）在這支第 48 行。掃描後這支改了 +13／-3 行——**確認那條洞還在不在、有沒有被改動影響**，不要重報成新發現。
- `job_service.py` 三個寫入入口都呼叫 `_require_manager`（FR-108 D3-2 已確認）。本棒要看**讀取入口 `get_job`（第 140 行）、`list_jobs`（第 244 行）**有沒有同等檢查——這是本 arc「有人守了一半」最常見的長相（讀的那半沒守）。

**W3（檢測接線本體）**：
- `core/plugins/detection.py:338-340` 三道解壓上限：`getattr(Config, "...", 預設值)`——**Config 沒設時用預設值，預設值合不合理**？（總表第 115／116 項的上游防線）。另外 FR-113 O3b 建議 Excel 那條路直接重用這三個數字，確認它們是設在 detection 專用還是全域。
- `scope_writable_check`（第 181 行）與 `platform_admin_check`（第 170 行）走「模組級設定」而不是每次請求取——**有沒有可能在程序啟動時就被固定成某個人的身分**？
- `CryptoAdapter`（第 129 行）：檢測工具帳密的加解密零件。總表第 85 項（測試連線零守門、可把帳密解密外送）的解密就是走這支——確認它沒有額外的「誰能呼叫解密」限制是設計如此。
- `detection_profile_usage_query.py`：規則包「還有沒有人在用」的判定。總表第 133 項（刪框架不檢查還有沒有人在用）是同款形狀——**如果這支查詢因為資料庫隔離只看得到自己家的引用，平台管理員刪公版規則包時會以為沒人在用**。
- `detection_job_notify_query.py`：通知收件人名單拼了檢測、派工、專案、成員四張表——有沒有可能把別家的人拼進收件人。

**W4（任務層直接查套件資料表）**：
- 19 個方法逐支看：**每條直接 `session.query(Survey)`／`query(Device)` 的查詢，Survey／Device 這兩張表有沒有開資料庫隔離**？（FR-098 報告說設備表有開；問卷表要查。）
- 第 190 行 `session.query(Survey).filter(Survey.uid.in_(ref_ids))`：`ref_ids` 從哪來？如果是從任務參數讀出來、而任務參數又是前端可寫的，就是「用別家的問卷編號拼進自己的任務」的形狀。
- 第 912、1090 行在方法內 import `TaskSurvey`、`QuestionAnswer`——這兩支是在讀填答結果，確認讀的時候有沒有綁定「這個任務」。

**W5（日誌接線本體）**：
- `core/plugins/api_log.py` 自陳「兩半的守門刻意不共用」：操作日誌只給平台管理員、日誌轉送用能力點分讀寫。**總表已登記「一個客戶的管理員可以把全公司的日誌改送到他自己的機器」**——那是套件側能力點設計的問題還是宿主接錯了？本棒要給這個問題一個宿主側的答案。
- `common/middleware/app_mw.py`：FR-085 C2-1（高風險，請求標頭與內容原文寫進 log）、FR-097 L2（匯出公式注入的源頭）都在這支，**全是越界撿到、沒有一棒完整掃過**。本棒把它當正式範圍：`before_request` 在權限檢查之前就寫日誌，寫進去的欄位哪些有遮罩、哪些沒有。已登記的不重報，找有沒有第三條。
- `main.py:240` 程序分叉後重啟日誌轉送：gunicorn 多工人模式下，每個工人各自一份轉送設定，**改了轉送目的地之後其他工人會不會還在往舊的地方送**？
- 總表已登記「兩張日誌表的保存期限沒生效」——確認宿主側有沒有負責啟動清理排程的那一段。

**W6（資產與議題並排）**：
- **授權模組守門**：問卷、檢測都有 `license_guard`，資產、議題沒有。確認 `AssetAdapters`／`IssueAdapters` 根本沒有這個欄位（套件契約就沒要）還是有欄位但宿主沒給。
- `AssetReferenceAdapter.count_references`（`core/plugins/asset.py:73`）：刪設備前檢查「還有沒有人在用」。總表第 81 項（只有修改權限就能達成刪除效果）的形狀——**這支計數查詢受資料庫隔離嗎？隔離下只數得到自己家的引用，會不會低估**？
- `common/util/system_asset_snapshot.py`：SSP 設備清單快照直接用設備 domain service，**不經套件的網頁守門**。FR-098 A2 查過 `ssp_resources_context_service.py` 同款呼叫「走同一套 RLS、沒有繞過」，本棒對這支做同樣確認。
- `core/plugins/issue.py:70` `RootIntegrateConfigProvider`：GitHub／GitLab 權杖從**總部那一列**讀（`read_root_config_value`）。確認任何客戶送意見回饋時，都是用公司的權杖開問題單——這是設計（總表已登記「死碼待裁」），不要重報；但要確認權杖**不會出現在回應或日誌裡**。
- `common/code/feedback_code.py`：確認執行期沒人用，是死碼的話寫進報告給 FR-092 系列清理。

**W7（證據分類金鑰鏈）**：
- 🔴 **非平台管理員拿不拿得到原廠金鑰**（FR-111 卡片重點⑤的另一半）：`ai_provider_key_resolver.py` 的解析順序「租戶自己的 → ROOT 原廠 → 環境變數」，租戶沒設時會退到原廠鑰——**這是「用」原廠鑰，不是「看到」原廠鑰**（CM-1867 裁 D13：鎖改不鎖用）。本棒要確認的是「只有用、沒有看到」：原廠鑰有沒有可能經由回應、錯誤訊息、容器日誌、`container_env()` 流到租戶手上。
- `ai_provider_key_resolver.py:179` 取 `get_user_context()` 決定是哪個租戶——**無登入脈絡時（背景排程、批次任務）回 None，會退到哪一層**？FR-085 C1 已證實「沒有登入身分時資料庫隔離整個關閉」，兩件事疊在一起要看清楚。
- `system_config_root_reader.py` 的 `read_tenant_config_value` 關掉資料庫隔離去讀——傳進去的 `tenant_id` 是誰決定的？有沒有哪條呼叫路徑是前端可控的。
- `evidence_classification_containers.py` 的雲端硬碟 `token_provider`：FR-111 E3 的高風險（舊線預覽端點讀客戶整個硬碟）用的權杖從這裡取，確認新線與舊線是不是同一個 provider。

---

## 4. 切棒推導與風險提示

### 為什麼這樣切

- **問卷拆成 W1（接線本體）和 W2（被任務使用）兩棒**：問卷接線合計約 2,450 行，超過門檻。拆法是按「誰是主角」——W1 是「套件怎麼被掛上來」（主角是套件），W2 是「主專案拿套件的東西去做任務」（主角是任務模組）。兩棒的接縫是「問卷掛上任務」與「套件憑任務編號判權限」這條線，所以要求同一個 runner 連續跑（見 W2）。
- **W2 同時收檢測的 `job_dto.py` 與 `job_handlers/__init__.py`**：這兩支是「任務模組使用檢測」的那一面，跟問卷在任務模組的使用是同一群檔案（都在 `app/flow_control/`），放一起研究員不用換腦。
- **W4 單獨一棒**：三支檔都是「直接拿套件的資料表下 SQL」，同一種風險形狀；而 `flow_control_job_repo_impl.py` 一支就 1,216 行，湊別的會超標。
- **資產與議題合一棒（W6）**：兩支接線都很薄（議題實質只有 `issue.py` 195 行＋一支 25 行申報），而且**是全專案唯二整包吃 `host_defaults()` 的套件接線**（另一支是公告，已掃），並排看正好對照。
- **W7 單獨一棒而非拆進其他棒**：它屬於不同的套件（證據分類），只是總表 §7 第 23 項建議「順便一起盤」。拆進別棒會讓那一棒失焦。

### 給首腦的風險提示

- **本批大多數檔都被別的 arc「路過」過，這是最容易誤判成已掃的地方**。1.4 後半列了 9 支「被引用過但沒被掃過」的檔——FR-108 十四棒有 5 棒引用過 `core/plugins/detection.py`，但沒有一棒把它列進範圍。首腦驗收時看到報告寫「此點 FR-108 D1 已查」，要分清楚是「D1 查過這一點」還是「D1 掃過這支檔」。
- **W2 與 W1 必須同一個 runner 連續跑**（理由見 W2 段）。
- **W4 的結論高度依賴資料庫隔離的現況**：這三支 repo 本來就不做程式層守門，研究員只能回答「每條查詢撞到的表有沒有開隔離」。資料庫隔離的現況要以驗收當下 DEV 實查為準（平行 RLS 線可能在掃描與驗收之間改變事實）。
- **`core/plugins/_host.py` 重複帶入三棒**：三份報告若對它給出不同結論，以最嚴格那份為準，並回頭確認另兩棒是不是漏讀。
- **任務平台（FR-095）剩下的 108 檔不因本批而算「掃過」**。本批納入的 `app/flow_control/` 那幾支，是因為它們 import 了問卷／檢測，不是因為任務平台要補掃。`job_import_service.py`、`job_batch_complete_service.py`、`job_route.py` 仍然沒人掃。

### 逐棒加總核對（已實跑）

```
W1（問卷接線本體）          8 檔    934 行（含 _host.py 212 行）
W2（任務使用問卷與檢測）     6 檔  1,522 行
W3（檢測接線本體）         13 檔  1,666 行（含 _host.py 212 行）
W4（任務層直接查資料表）     3 檔  1,629 行
W5（日誌接線本體）          6 檔  1,255 行
W6（資產與議題並排）         8 檔    850 行（含 _host.py 212 行）
W7（證據分類金鑰鏈）         6 檔  1,622 行
------------------------------------------
逐棒加總（含重複計數）       50 檔  9,478 行
扣掉 _host.py 多算的 2 次（212 × 2 ＝ 424 行）
＝ 48 支不重複檔案、9,054 行
```

跟 1.6 的「48 支、9,054 行」一致。不含 W7：逐棒 44 檔／7,856 行，扣 424 行＝ **42 支、7,432 行**。

---

## 5. 盤點時發現、但需要首腦裁決的事

1. **W7（證據分類金鑰鏈，6 檔／1,622 行）要不要併進本批？** 卡片寫「由首腦裁」。盤點員建議**併**：它跟 W5（日誌）一樣是「主專案接線、套件已掃完」的形狀，而且總表 §7 第 23 項已經掛了很久；多加 `system_config_root_reader.py` 的理由見 W7 段。不併的話本批是 6 棒。
2. **`detection_tools_error_code.py`（209 行，W3）與 `main.py`（302 行，W5）要不要從範圍拿掉？** 兩支都只有一小段跟本批有關。拿掉可以省約 500 行，代價是研究員要自己去查。盤點員傾向**保留**（兩棒都還在門檻內）。
3. **`workflow_execution_service.py`（1,374 行）FR-088 H4 掃過之後改了 +45／-230 行**，其中兩筆是 FR-112 的功能改動（合流閘道等兄弟分支、管理人強制開始任務的新端點 `525989b7a`）。它 import 問卷只是為了一個狀態碼和 `TaskSurveyService`，本批按「已掃」排除。**但 FR-112 新加的「強制開始」端點有沒有人掃過，盤點員沒追到**——這不是本批的範圍問題，是 FR-088 的「掃過後新增端點」問題，列出來讓首腦知道。
4. **`common/code/feedback_code.py`（5 行）疑似死碼**：全專案只有 `docs/api/feedback/generate_docx.py` 引用。放進 W6 讓研究員確認，確認後可轉 FR-092 系列清理，不是資安問題。
