---
title: FR-069.9 疆界地圖 — 全套件庫去向盤點
---

# FR-069.9 疆界地圖——26 支套件＋主專案候選模組的完整去向

> 狀態：**分析完成，待決策者拍板**｜建立日期：2026-08-30｜對應卡：CM-1446（FR-069.9）
> 母案：FR-069（design.md §7「疆界盤點案」）｜**純分析案，未動任何程式碼**
> 本文為第四階段（P6／P9–P13）與第三階段（老套件補殼）的**開工門檻**——地圖未拍板前，該兩階段不排棒。

## 變更紀錄

| 日期 | 變更 | 對應 |
|------|------|------|
| 2026-08-30 | 初版：六組平行盤查＋首腦交叉驗證，產出去向表與 D10–D16 待拍板清單 | CM-1446 |

---

## 0. 三十秒版（先看這段）

**盤了 26 支套件＋15 個主專案候選模組，結論是「合併 3 組、退役 3 支、下架評估 1 支、其餘獨立補殼」，套件數從 26 收斂到約 15–17 支。**

五組候選疆界的驗證結果**只有兩組成立**，這正是先盤點再動工的價值——若照原假設直接開工，有三組會白做：

| 原假設 | 驗證結果 |
|---|---|
| ① 檔案疆界（file-upload＋resource-store） | ❌ **假設錯**。兩支零耦合、領域不同；真相是 resource-store 整支死碼該退役 |
| ② 問卷疆界（survey＋task_survey） | ✅ **成立且證據最硬**（跨半 DB 外鍵） |
| ③ 檢測疆界（device＋detection_*） | 見 §3 ③ |
| ④ OSCAL 疆界 | ⚠️ **部分成立**：oscal 應用層併入 v2，但 module_frame 不併；且「三角」的第三邊抓錯了 |
| ⑤ 追蹤疆界（issue＋bulletin＋feedback） | ❌ **證偽**。互相 import＝0、整合範式相反、租戶模型相反 |

**兩項退役查證**：jedi-oscal v1 → **可退役**；jedi-log → **不可整支退役，但應拆兩半**（一半活著、一半是「載入即炸」的不可用碼）。

**本案順帶挖出四個 P0 級問題**（詳 §5），其中兩個是會炸的地雷、兩個是出貨污染。

---

## 1. 方法與證據標準

照 D9（jedi-iam 案）的同一套五判準，六組平行盤查後由本案彙整者**逐項交叉驗證**（不採信分組自報，關鍵結論一律親跑指令覆核）：

| # | 判準 | 判讀方式 |
|---|------|---------|
| ① | 實際耦合 | 跨套件 import **具體到符號**＋**穿透層級**（碰 app/domain 介面＝淺；碰 infra／ORM model＝深，深穿透＝「一個能力被切成兩個目錄」的鐵證） |
| ② | 發版歷史 | 各支有無**獨立**的發版節奏 |
| ③ | 主專案膠水 | A 產品特定（合理留宿主）／B 通用流程（任何產品都要重抄＝切錯線的稅）／C 純重複 |
| ④ | 死碼盤點 | 零呼叫功能鏈、零筆資料的表 |
| ⑤ | pyproject 依賴健檢 | 虛掛依賴、測試依賴錯放主依賴 |

### 三個會導致反向結論的陷阱（本案實際踩到，記錄供後續棒次沿用）

1. **`pg_stat_user_tables.n_live_tup` 對分割表母表一律回報 0**。本案彙整者第一輪即被騙——pg_stat 說 `api_logs`／`system_logs` 是 0 列，實際 `count(*)` 是 **102,205** 與 **615,122** 列。若照 pg_stat 判讀，會得出「jedi-log 是死碼可退役」的完全相反結論。**零筆判定一律用 `count(*)`。**
2. **DB 是多 schema 的**（`public` 58／`oscal` 58／`compliance` 46／`survey` 15／`config` 13 張表）。用 `public.<table>` 查 survey 或 detection 的表會得到「relation does not exist」而誤判成死碼。
3. **dist 名 ≠ import 名**。`jedi-log` 底下沒有任何 `jedi_log` 模組，實際是 `jedi_api_log` 與 `jedi_system_log` 兩個 package。用 `jedi_log` 掃會零命中而做出假的退役結論。全 26 支中**只有這一支**有此落差。

### 發版歷史的判讀更正

monorepo 有兩支 **CVE 全掃 commit**（`0bdadde`、`c6188f7`）各一次動 18 支套件的 pyproject。**這不是「同批升版＝獨立生命從未兌現」的證據**，只是資安批次補丁——它對 18 支一視同仁，若當成親緣證據會推出「全部 18 支都該合併」的荒謬結論。多數套件顯示「最後異動 2026-07-25」即由此造成的假象。**判準②一律只看 feature/fix 驅動的 release。**

---

## 2. 最終疆界地圖（26 支套件去向表）

圖例：🟢 獨立補殼（第三階段照常）｜🔵 併入某疆界｜🔴 退役／下架｜⚪ 已定案（D9，不在本案範圍）

| # | 套件 | 規模 | 主專案 import | 去向 | 依據摘要 |
|---|------|------|------|------|---------|
| 1 | jedi-common | 105檔/5,104行 | **821** | 🟢 獨立（地基） | 全生態地基。第二階段續瘦身；`system_logs` canonical 寫入者在此 |
| 2 | jedi-auth | 164檔/10,783行 | 181 | ⚪ → jedi-iam | D9 已拍板 |
| 3 | jedi-login | 90檔/4,361行 | 26 | ⚪ → jedi-iam | D9（→auth 30 處 import，深穿透 infra） |
| 4 | jedi-mfa | 53檔/2,283行 | 10 | ⚪ → jedi-iam | D9 |
| 5 | jedi-captcha | 35檔/1,922行 | 5 | ⚪ → jedi-iam（降 turnstile 模組） | D9 |
| 6 | jedi-department | 41檔/2,164行 | 0 | ⚪ 🔴 下架 | D9 已判（與 org_units 重複、無多租戶） |
| 7 | jedi-oscal（v1） | 452檔/24,672行 | **0（12 處全註解）** | 🔴 **退役** | 四 repo 零可執行引用、無套件依賴、無 DB 表、`poetry.lock` 已無其蹤 → §3 ④ |
| 8 | jedi-oscal-v2 | 445檔/26,321行 | **165** | 🔵 **OSCAL 疆界核心**（吸收 oscal 應用層通用件） | → §3 ④ |
| 9 | jedi-survey | 98檔/10,928行 | 51 | 🔵 **問卷疆界核心**（吸收 task_survey 作答層） | → §3 ② |
| 10 | jedi-flow-engine | 133檔/10,201行 | **163** | 🟢 獨立補殼 | 主專案第 4 熱依賴；與 flow_control 的關係屬終局選項 |
| 11 | jedi-file-upload | 69檔/3,948行 | 37 | 🟢 獨立補殼（升級為檔案疆界） | **有真實第二消費者**（jedi-issue pin 並用 5 符號）＋有獨立發版節奏 → §3 ① |
| 12 | jedi-resource-store | 37檔/1,594行 | **0** | 🔴 **退役** | 程式碼零 import、`public.resources` 表四環境皆不存在、路由 2026-02 已刪 → §3 ① |
| 13 | jedi-issue | 149檔/5,596行 | 20 | 🟢 獨立（但**58% 死碼待清**） | 追蹤疆界證偽 → §3 ⑤ |
| 14 | jedi-bulletin | 42檔/1,338行 | 3 | 🔴 **下架評估**（比照 department） | 套件 408 行 vs 主專案重寫 406 行；DEV 1 列／POC 0 列 → §3 ⑤ |
| 15 | jedi-log | 77檔/3,473行 | 8 | 🔵 **拆兩半**：`jedi_api_log` 併入 log 疆界；`jedi_system_log` 🔴 **刪除** | api_log 活著（102,205 列）；system_log **import 即炸** → §3 ⑤ |
| 16 | jedi-log-forwarding | 37檔/3,281行 | 4 | 🔵 log 疆界 | 與 api_log 職責互補（一個管落地、一個管轉發） |
| 17 | jedi-device | 41檔/2,882行 | 21 | 見 §3 ③ | — |
| 18 | jedi-notification | 44檔/3,745行 | 12 | 🟢 獨立補殼 | D9 已驗證「維持獨立」（12 消費點多與身分無關） |
| 19 | jedi-system-config | 46檔/3,625行 | 28 | 見 §3 ⑥ | — |
| 20 | jedi-system-menu | 45檔/2,446行 | 4 | 見 §3 ⑥ | — |
| 21 | jedi-project | 26檔/**399行** | **75** | 見 §3 ⑥ | ⚠️ 399 行卻被引 75 次、**21 處穿透 infra** |
| 22 | jedi-information-system | 29檔/903行 | 9 | 見 §3 ⑥ | FR-032 G 抽取範本 |
| 23 | jedi-ai-bot | 15檔/1,043行 | 2 | 🟢 已具 D6/D7 契約 | 第一階段 P1 新抽 |
| 24 | jedi-integrity | 29檔/4,721行 | 10 | 🟢 已具 D6/D7 契約 | 第一階段 P2 |
| 25 | jedi-remote-agent | 70檔/3,757行 | 28 | 🟢 已具 D6/D7 契約 | 第一階段 P4 |
| 26 | jedi-license-runtime | 49檔/4,266行 | 9 | 🟢 已具 D6/D7 契約 | 第一階段 P5 |

---

## 3. 五組候選疆界的驗證結果

### ① 檔案疆界 — ❌ 假設不成立（兩支零耦合），真相是一支該退役

**結論：不合併。`jedi-resource-store` 退役；`jedi-file-upload` 獨立保留並吸收主專案通用膠水。信心度：高。**

原假設把兩者放同組，推測是基於名字都像在講檔案。實測全錯：

| 面向 | jedi-file-upload | jedi-resource-store |
|---|---|---|
| 管什麼 | binary 檔案實體＋多後端儲存（local/minio/seaweedfs） | 一張 `link TEXT` 欄——**書籤／連結庫**，帶 publish/hidden/start_date 等內容發佈語意 |
| 有沒有檔案 | 有，2,672 筆實體檔 | **沒有任何檔案**，只有 URL 字串 |
| 資料表 | `upload_files`（2,672 列，三條 FK 被引用） | `public.resources`——**四環境全部不存在，連出貨基線 `02-schema.sql` 都沒有** |
| 外部依賴 | jedi-common＋minio SDK（**不碰任何業務套件**） | jedi-common＋**jedi-auth 深穿透**（`infra/models/resource.py:11` 直吃 `User` ORM 建 FK） |
| 真正的疆界歸屬 | 檔案 | 若真要留，是內容發佈／身分族，**不是檔案族** |

**雙向 import 皆為 0**，連 `generate_uuid()` 都是各自複製（全庫 17 支套件各抄一份，canonical 早在 `jedi_common/utils/common_utils.py:7`）。

#### jedi-resource-store 退役——四項證據齊備

1. **程式碼零 import**：四 repo 掃描 code-level 命中 **0**；全庫唯一殘留是主專案 `pyproject.toml:67` 那一行 pin。無任何套件依賴它（`poetry.lock` 內零反向依賴）。
2. **表從未被建立**：DEV／基線庫／STG／POC 四環境 `to_regclass('public.resources')` 皆為 NULL。
3. **路由已刪**：`api/resource/` 於 commit `5c5de8d7`（2026-02-14）移除。
4. **已列管**：`docs/release_notes/v1.14.0.md:152` 記載「CM-1143 遺留模組整鏈退役（cruise-project／resource／report＋jedi-resource-store）：Not started」。

> **⚠️ 一項本案內部的判定更正（記錄以免後人重蹈）**：彙整者中途曾誤判「`oscal.resources` 0 列就是 resource-store 的表」，經①組實查推翻並由彙整者覆核確認**分組正確、彙整者錯**。全庫有兩處宣告 `__tablename__ = "resources"`：resource-store 的**未宣告 schema（解析到 `public`）**，jedi-oscal-v2 的明寫 `{"schema": "oscal"}`，欄位集合幾乎無交集（後者是 `citation`／`rlinks`／`base64`／`metadata_id`，即 OSCAL `back-matter.resources[]`）。**同名不同物**。
> 修正後的結論**更強**：不是「表 0 列」而是「表從未存在」⇒ 退役是純粹的「刪 pin＋刪目錄」，**零資料風險，且不需把表的處置交給 OSCAL 疆界**。
> 教訓：判斷表歸屬要看**欄位與 schema 宣告**，不能看表名。

**退役的附帶收益**：resource-store 宣告 `jedi-auth>=0.1.5` 且 3 處 import 它。提前移除＝**第 2.5 階段 jedi-iam 合併時少一個要跟著改的下游**。

**建議做法**：**併入既有 CM-1143 退役案**，不另開卡（避免兩張卡管同一件事）；但在 CM-1143 補一條註記——本案已完成套件端四項查證，該支可**無條件、零風險**移除，不必等 cruise-project／report 的分析結果。若 CM-1143 長期不啟動，這行 pin 可在 FR-069 任一棒順手帶走。

#### jedi-file-upload 獨立保留——它是唯一有「第二消費者」的套件

- **真實外部消費者**：`jedi-issue/pyproject.toml:19` pin 它並實際使用 5 個符號（`local_issue_attachment.py:4-8`）。
- **有獨立發版節奏**：排除 CVE 批掃後仍有 **6 次單獨發版**（0.0.15／0.0.17／0.0.18／0.0.21／0.0.22 等）。
- **膠水量化**（主專案 1,019 行）：A 產品特定 ~500（`remote_agent_adapter.py` 273 行等，**留主專案正確**）／**B 通用流程 ~280**（該上移）／C 純重複 14 處（`file_upload_service=...` 在 8 個 container 逐字重複）。

**B 類該上移的證據最強**：`ManagedFileUploadService` 的 docstring 自承「**取代 jedi_file_upload 原本從 env vars 讀取的邏輯**」——套件的設定來源模型主專案完全沒用、整支覆寫；而「依檔案自記的 `storage_type` 挑對後端讀回」這 117 行核心語意套件根本沒有。**疆界切在錯的地方：套件保留了它做不好的（設定解析），卻沒承接它該做的（多後端讀取路由）。** 旁證：jedi-issue 已在為同一缺口付稅（`local_issue_attachment.py:28` 必須 `file_upload_service or FileUploadService(...)`，主專案得顯式注射才不會退化成 local）。

**P0-① 架構債查證結果：已修完**。`api/uploadfile`／`app/uploadfile` 等四目錄皆不存在，且 `test/test_module_boundaries.py:92-96` 有守衛測試焊死舊拼法不得復活。唯一殘留是 `docs/api/uploadfile/` 文件目錄名（純文件，可順手更名）。

### ② 問卷疆界 — ✅ 成立，且是全案證據最硬的一組

**結論：task_survey 併入 jedi-survey，P11 從「抽新套件」改寫為「合併」。信心度：高。**

task_survey 不是「疊在 survey 上的一層」，而是 **jedi-survey 被切掉的下半身**。四項互相獨立的證據一致指向同一結論：

**證據 1：跨半身的 DB 實體外鍵（最硬，單獨即足以定案）**

live DB 實查 `pg_constraint`，兩條真實 FK 橫跨「套件的表」與「主專案的表」：
```
survey.question_answers                → survey.survey_questions
survey.question_answer_history_details → survey.survey_questions
```
**套件邊界不可能靠 DB 外鍵縫合。** 若照原 P11 抽成獨立的 `jedi-task-survey`，這兩條就變成「套件 A 的表對套件 B 的表下外鍵」——安裝順序、migration 順序、卸載全部綁死，本質上就不是兩個套件。而 jedi-survey 全庫**零 migration**（`.sql` 只有測試用 initdb），`survey.surveys` 的 migration 全在主專案 `scripts/sql/`——**套件的表由宿主維護**，這就是同疆界的定義。

**證據 2：DB schema 的切法是刻意設計（實查 migration 原始碼確認）**

`scripts/sql/migrate_schemas.sql`（2026-03-21）**同一支腳本內**做了分流判斷：
```
-- 2. jedi-survey 核心表搬遷 → surveys / survey_pages / survey_questions ... → survey schema
-- 4. answer 相關表搬遷      → task_surveys / question_answers / ...        → survey schema
-- 5. job_evidences 搬至 compliance schema                                   → compliance schema
```
設計者當下對每張表逐一判斷，把 `job_evidences` 明確送去 `compliance`，卻把五張作答表**與問卷核心表並列在同一節、放進同一個 `survey` schema`。當時心中的疆界就是「問卷＝設計＋作答」。

**證據 3：穿透方向單一——上半身乾淨、下半身帶著所有耦合**

task_survey → jedi_survey 共 13 條 import，最深的 5 條直取 **ORM model 與 RepoImpl**（`infra/task_survey/models/task_survey.py:11` 吃 `Survey`、`question_answer.py:7` 吃 `SurveyQuestion`、DI 直取 `SurveyPageRepoImpl`/`SurveyQuestionRepoImpl`）。反向 jedi-survey 對 task 概念**零認知**，只依賴 jedi_common，是乾淨葉節點。

這正是「被切一刀」的形狀：若是兩個真疆界，耦合應雙向或上半身有明確 port；現況是**上半身根本不知道下半身存在，卻被下半身用 ORM 與 FK 直接抓住**。

**證據 4：套件公開 API 已被宿主業務語意侵入**

`jedi-survey/.../survey_snapshot_domain_service.py:36` 的公開簽名是 `create_snapshot(source_survey_uid, project_uid)`，`:86` 用 `f"__snapshots__{project_uid}"` 造資料夾名。**`project_uid` 是主專案概念，早已寫進套件公開 API 與資料落地格式**——所謂「乾淨的上游」在源碼層面不成立。

**膠水量化（4,514 行）：A 產品特定僅 14%、B 通用流程 61%。** 作答的 upsert 三路徑、歷史版本與還原、作答狀態機（未填→編輯→待審→補充→完成）、多人即時共編 socket——**全部零稽核語意**，任何「問卷要指派給人作答」的產品都得重抄。這直接推翻「作答層是產品特定所以該留主專案」的可能結論。

**發版歷史**：排除 CVE 批掃後 2026 年 8 支實質 commit，**5/8 由作答層拉動**（FR-049／049.1 顯示條件 4 支＋snapshot 機制 1 支）。FR-049.1 是最強一支——**一個功能跨兩個 repo 分兩處實作**，且消費端得對自己剛加的欄位寫防禦：
```python
# app/task_survey/service/question_answer_service.py:41
dc = getattr(q, "display_condition", None)   # 對自家剛加的欄位寫 getattr 防禦
```

**被排除的選項——維持原 P11 抽獨立套件：技術上不可行。** 要斷開必須拆掉兩條 DB 實體 FK、改成應用層維護參照完整性，同時把 `TaskSurvey.survey` 的 association_proxy（`survey_uid`/`survey_name`/`survey_version_no`，被 DTO 與通知信直接使用）全改批次 enrich。**代價是拆掉 DB 幫你保證的完整性，換來兩個永遠要對版的套件**——而對版壓力已被 FR-049.1 實證。

**反悔條件**：出現「只要問卷編輯器、不要作答」（如純表單設計器產品）的真實消費者。目前零外部消費者，不成立。

### ③ 檢測疆界 — ❌ 假設被推翻（device 不屬檢測）

**結論：device **不**併入檢測疆界。P9 內容物去掉 device、加上散在 `common/` 的檢測領域碼。形態＝插件，不服務化。信心度：高。**

待驗假設「設備管理本就是為檢測服務」被硬證據推翻：

**證據 1：程式碼層零耦合。** 掃 detection_tools（99 檔）＋detection_execution（30 檔）全部 157 檔，對 `jedi_device` 的 import **＝0**。反向 jedi-device 也不認得任何檢測概念。唯一含 "device" 字樣的 6 處全是同名不同義（`agent.device_fingerprint` 屬 remote-agent、`network_device` 是分類 slug）。

**證據 2：資料層鐵證（本組最硬的一項）。** 實查 `job_execution_devices` 3,012 列的任務型態分佈：
```
general 3,009 列 ｜ survey 3 列 ｜ detection_tool  0 列
（對照：job_executions 有 detection_tool 型 21 列）
```
**檢測型任務從未綁定過任何 device。** 檢測的掃描目標走 JSONB `scan_targets.hosts` 手打字串（裸 IP／CIDR／repo key），與 `devices` 表無 FK、無任何 code 路徑相連。

**證據 3：device 的真實消費者全在資產域。** 21 處 import 分佈於 `system_asset_snapshot`(8)、`ssp_import_template_app_service`(7)、`job_execution_device_mapping_service`(6)、`ssp_inventory_items`／`ssp_resources_context`(各 2)——**清一色是資產盤點／SSP／範本匯入，零檢測**。發版歷史也一致：jedi-device 近兩次真實開發分別由**資產盤點（FR-032 F）**與**主專案審計欄位規範**驅動，**零筆由檢測需求驅動**。

**證據 4：商業模型也不同疆界。** `tenant_licenses` 實查——檢測三個 module key（`plugin`／`detection-profile`／`remote-agent-manage`）各只有 6/25 租戶開通，而 `device` 是 **25/25 全開**。檢測是加購模組、device 是標配。

#### 形態分流（P9）：插件，不服務化

| 三問 | 實查回答 |
|---|---|
| 壞了宿主整停還是可降級？ | **可降級**。檢測是 job 的一種型態（21 列 vs general 11,012 列），掛了只是那些工單卡住，稽核／SSP／問卷／證據照跑 |
| 需要自己的資料主權嗎？ | **不需要，且已證明不可能**。檢測結果終點是宿主的 `compliance.job_evidences` 證據池與宿主 Minio；8 張檢測表有 6 張掛宿主 RLS |
| 同一套實例服務多產品嗎？ | **不會**。多實例分擔的角色**已由 jedi-remote-agent 承擔**（真正跑掃描的是客戶端 agent），雲端側只是「派工＋收單＋轉證據」的編排層 |

**加碼證據**：`DetectionOrchestrationService` 對宿主 workflow 有 9 處 `assert_project_participant(...)`＋1 處 `complete_job()`。服務化後這些全變成回打宿主的網路 RPC——把「同一 transaction 內的授權＋狀態轉換」拆成跨網路兩段，換來分散式一致性問題，收益為零。

⇒ **建議把 design.md §7 P9 那句「開工前先過服務化評估」改成結論式敘述，省掉開工前那一輪。**

#### 對 P9 的兩點修正（design.md 目前低估）

1. **漏計 `common/` 內的檢測領域碼約 1,567 行**：`detection_tools_error_code.py`(209)、`detection_assignment_params.py`(173)、`detection_secret_params.py`(154)、`detection_profile_archive.py`(348)、`scan_target_spec.py`(307)、`profile_extractor/`(188) 等。抽套件不搬這些，等於「套件在主專案裡留了一半領域碼」。
2. **「grc 對它 7 條反向依賴」條數對、工程量嚴重低估**：非測試 incoming 確為 7 行 import，但 `app/flow_control/service/job_service.py` 全檔 1,144 行中**約 785 行（69%）散在 17 個方法裡處理檢測**（單 `_replace_detection_tool_agent_assignments` 就 169 行）。「import 名不變就不用改」對那 7 條成立，但**這 785 行的去向（搬進套件＋`IJobBinding` port，或留 grc）是 P9 最大的單一設計題，design.md 目前沒提**。

**device 自身的處置**：改列入資產域評估（與 module_frame 的 inventory 段、`oscal.ssp_inventory_items` 的 27 列 `ref-type: device` soft-ref 一起看）；套件本身走**第三階段輕量升級**即可（把 `api/device/` 196 行收回套件、清 7 支錯誤依賴、修 `devive_*.py` 檔名 typo），不必等疆界拍板。

### ④ OSCAL 疆界 — ⚠️ 部分成立，且「三角」的第三邊抓錯了

**結論：oscal 應用層的通用件併入 jedi-oscal-v2；module_frame **不**併入、另立候選；P13 真正的攔路虎是 `flow_control` 不是 module_frame。信心度：中高。**

**耦合實況（實查，與 design.md 的敘述不符）**：主專案對 `jedi_oscal_v2` 的 import 按模組分佈——
```
oscal 77 ｜ flow_control 62 ｜ module_frame 18 ｜ project 8 ｜ associations 0
```
**flow_control 的 62 條是 module_frame 的 3.4 倍。** P13 若只處理 module_frame，做完仍有 62 條橫跨 flow_control ↔ v2，疆界沒收乾淨。

**穿透深度是本組最重要的發現**：**77 條 import 直取 `jedi_oscal_v2.infra.repository.*RepoImpl`**（主專案 app service 自己 new 套件的 RepoImpl，繞過套件 app service 層）。最嚴重三處：`assessment_result_app_service.py:36-64` 一口氣拉 13 支、`assessment_plan_app_service.py:37-55` 拉 9 支、`poam_app_service.py:24-30` 拉 7 支。而 v2 自己的 `AssessmentPlanService`／`PoamService`／`AssessmentResultService` **就擺在那沒人用**。

⇒ **P13 真正的技術債是「v2 的 app service 層被判定為不夠用而遭繞過」，不是「應用層該不該進套件」。** 建議 P13 開工前先派一棒查證：v2 service 的粒度是否為單文件 CRUD，而主專案要的是跨 AP/AR/POA&M/catalog 的交易編排？若是，正確做法是**在 v2 內補一層編排 service**，讓主專案從 13 支 RepoImpl 降為 1–2 支 service，而不是把 1,000 行的應用層整包搬進套件。

**依賴方向：design.md 的建議要反過來。** design.md §7 建議「grc → oscal 單向」，實查現況是：
```
oscal → flow_control  11 條  ｜  flow_control → oscal  1 條
```
**主導方向是 oscal → flow_control，與建議相反**（要達成 design 的目標得反轉 11 條而非 1 條）。且其中 6 條關於 `ssp_reference_document*`，**兩條是 DDD 違規**：
- `infra/oscal/repository/ssp_catalog_title_query.py:29` — `from app.flow_control.service.ao_derivation import AO_PART_NAME`（**infra 層 import app 層**）
- `infra/oscal/clone/ssp_versioning_cloner_impl.py:19-20` — 直接 import `infra.flow_control.model.ssp_reference_document{,_mapping}` 兩個 ORM model

⇒ **建議改為「oscal → flow_control 單向」**（oscal 是被流程消費的資料層、flow_control 是流程編排層，讓資料層依賴流程層是反的）。**前置決策：先拍板 `ssp_reference_document*` 兩張表的歸屬**——它們現在住 flow_control，卻被 oscal 的 cloner 直接操作。這比 design.md 寫的「拍板依賴方向」更具體可執行。

**module_frame 不併入 v2 的三個理由**：① 概念不屬 OSCAL 標準（OSCAL 規範裡沒有 module frame）；② 自有兩張表在 **`compliance` schema**（`module_frames` 156 列／`module_frames_trans` 110 列），不在 `oscal` schema；③ 依賴方向是 **oscal → module_frame（15:2）**——module_frame 不是 oscal 的下游，是**上游的範本供應者**。但它對 flow_engine(4)／participant(2) 也有依賴，獨立成套件成本不低 ⇒ **建議 P13 先不動 module_frame，標為「第二支候選、第四階段後期評估」**。

#### jedi-oscal v1 退役查證：✅ 可退役（信心度：高）

| 證據 | 結果 |
|---|---|
| BE 真 import | **0**（`grep -rnE "^\s*(from\|import)\s+jedi_oscal[^_]"` 回 0 筆）；字串命中 12 處**全是註解**，內容一致為「FR-038 2A: jedi_oscal imports removed」 |
| FE／test repo | 0 可執行引用（命中全在 changelog、對話紀錄、註解） |
| 其他套件依賴 | **0**（全 26 支 pyproject 無人宣告 v1） |
| `pyproject.toml` | 無 `jedi-oscal` pin，只有 `jedi-oscal-v2==2.2.3` |
| `poetry.lock` | **v1 不在 lock 內** |
| DB 表 | v1-only 的 27 個表名在 DEV／基線庫**一個都不存在** |
| 最後異動 | 2026-06-18 後靜止 |

**v1/v2 不共存的機制已查實**：兩者共用同一個 `jedi_common` 的 `Base`，且有 **13 個同名表**同在 `oscal` schema——同一 MetaData 註冊同名表即 `InvalidRequestError`。memory 記載的「全切被強制」是真的，**機制是 MetaData 撞名而非人為約定**。

**附帶收益**：v1 宣告了 `pymupdf`（AGPL，即 2026-08 授權盤點的唯一紅燈 → CM-1235），v2 已不依賴它——**退役可順帶消掉一個 AGPL 依賴來源**。

**建議做法**：Nexus 標 deprecated ＋ monorepo 目錄歸檔（不刪 git 歷史），採「先標 deprecated、觀察一個版本週期再歸檔」而非直接刪（唯一理論缺口是未查 Nexus 是否有本地四 repo 以外的下載者）。**主專案端另需 `poetry install --sync` 清 `.venv` 殘留**（見 §5 P0-3）。

**FR-038 2B 查證結果（順帶回答）**：**路由面大致完成**——`api/oscal/__init__.py` live 61 條 vs 註解 6 條；`api/module_frame/__init__.py` live 43 vs 註解 2，且無任何 orphan route 模組。那 8 條註解掉的多是已判定的 DEAD-V1 清理（標記明確：「FE 無 caller」）。**殘留集中在三支殭屍 service ＋其死測試**（見 §5）。

### ⑤ 追蹤疆界 — ❌ 證偽（三者各自獨立）

**結論：不存在「追蹤疆界」。三者各自獨立；唯一有數據支持的動作是 jedi-bulletin 下架評估。信心度：高。**

「都在追蹤某種待辦／訊息」是**命名層的錯覺**，五個判準沒有任何一項支持合併：

| 反證 | 數據 |
|---|---|
| **互相 import ＝ 0** | jedi-issue 與 jedi-bulletin 的 import 剖面**無任何交集項**（除所有套件都吃的 jedi_common） |
| **主專案整合範式相反** | bulletin 是**繼承式擴充**（3 個 import 全是 `as BaseXxx` 再 subclass）；issue 是**委派式呼叫**（當黑盒，18 處 method call）。同疆界的東西不會需要兩種相反的整合方式 |
| **租戶模型相反** | jedi-issue 的 **7 張表全部無 `tenant_id`、全部沒開 RLS**；bulletin 與 feedback 則是 tenant-scoped ＋ RLS 開啟。這與 D9 判 jedi-department 下架的判準是同一條 |
| **演化節奏無關聯** | 近 12 個月功能性 commit：bulletin 1 次、log 1 次、issue 5 次，且改動主題完全不相干 |
| **體積不同量級** | jedi-issue 純套件碼 2,879 行 vs jedi-bulletin **408 行**——這是「都很小」的真相，不是同疆界 |

**jedi-bulletin 下架評估的理由（比照 D9 的 jedi-department）**：套件純碼 408 行，而**主專案為了用它重寫了 406 行**（repo 97 vs 套件 71、domain service 47 vs 42、mapper 34 vs 26 等，實查逐檔比對）——**被包裝的東西比包裝紙還小**。真正在用的只有 3 個 base class，套件的 repo／domain service／mapper／query entity 全被主專案的版本取代。資料面：`bulletins` DEV **1 列**、POC 0 列。**套件化在此是淨負值**：多一個發版對版負擔，換來 3 個 base class。

**gitlab/github 整合線的判定**：不是「與公告語意遠」的問題，而是**它根本沒在跑**——DEV 與 POC 雙環境的 `ISSUE_INTEGRATE_CONFIG` 皆 `enable:false` ＋ 空憑證，`feedback_issues` 的 gitlab/github uid 全 NULL，`_is_gitlab_enable()`／`_is_github_enable()` 恆回 False。**約 1,191 行套件碼從未執行過。**

**jedi-issue 死碼比例 58%（全案最高）**：`project_member` 全鏈 472 行（四 repo 零引用）＋`member` 鏈約 700 行（唯一入口 `/issue/get_members` FE 零呼叫、`members`／`role_members` 表 DEV 與 POC 皆 0 列）＋gitlab/github 1,191 行。**2,879 行套件碼中約 1,663 行從未被執行過。**

> gitlab/github 是「已廢棄」還是「留給未來客戶」屬**產品決策**，程式碼判不了——列為待拍板（D14）。

#### jedi-log 退役查證：❌ 不可整支退役，但應**拆兩半**

這是本案最容易做錯的一題（三個陷阱在此交會：import 名落差、分割表 pg_stat 假 0、dist 名誤導）。

| 子套件 | 判定 | 證據 |
|---|---|---|
| **`jedi_api_log`** | ✅ **活著，保留** | 主專案 **8 條真實 import**（`common/middleware/app_mw.py:5-8` 掛在 middleware 上，每個 request 都寫）；`api_logs` DEV **102,205 列**、POC 27,084 列 |
| **`jedi_system_log`** | 🔴 **刪除——不是死碼，是「載入即炸」的不可用碼** | 見下 |

**`jedi_system_log` 的 P0 級問題（本案彙整者在 BE 的 .venv 內親自實測確認）**：

`jedi-common` 與 `jedi-log` **各自宣告了一個 `__tablename__ = "system_logs"` 的 model，且掛在同一個 `Base.metadata` 上**。實測：
```python
import jedi_common.logger.db_log.system_log.infra.models.system_log   # OK
import jedi_system_log.infra.models.system_log
# → InvalidRequestError: Table 'system_logs' is already defined for this MetaData instance.
```
**任何人只要 import `jedi_system_log`，在 jedi-common 已載入的情況下（也就是必然）就會炸。** 它不是「暫時沒人用」，是「用不了」。

且 615,122 列**全部由 jedi-common 那支寫的**：`path`／`func_name`／`line_no` 三欄只存在於 jedi-common 的 model，實查 615,122 列**全部帶值** ⇒ 100% 由 `jedi_common...DBLogHandler` 寫入，**`jedi_system_log` 對這張表寫過 0 列**。jedi-log 那份 model 已漂移成 9 欄、不符 live schema 的 12 欄。

**真正該收斂的是「jedi-common 的 db_log ↔ jedi-log 的 system_log」**（同表兩份 model，其中一份還會讓另一份炸），而不是原本假設的「jedi-log ↔ log-forwarding」。合理的 log 疆界終局是三塊各歸其位：

| 角色 | 現況 | 建議 |
|---|---|---|
| 系統 log 落地 | jedi-common `logger/db_log/`（canonical，615k 列的實際寫入者）＋ jedi-log `jedi_system_log`（**死＋炸**） | 留 jedi-common，**刪 jedi_system_log** |
| API 存取 log 落地 | jedi-log `jedi_api_log`（活） | 保留 |
| log 轉發出去 | jedi-log-forwarding | 保留 |

三者互相在原始碼裡反覆提及對方的表（log-forwarding 的 `plugin.py:106,111`、`common/code.py:14` 都在講 `system_logs`）——**一個套件反覆提到另一個套件的表，就是疆界訊號**。建議在第三階段補殼時三者同組討論。

**稽核埋點是否重疊？** 實查 615,122 列中 `[AUDIT:` 開頭僅 **1,353 列（0.22%）**。`common/util/audit_log.py`（主專案已是 shim）→ `jedi_common.utils.audit_log.audit()`（決定訊息長相）→ `DBLogHandler`（決定怎麼落地）是**一條鏈而非三份重複**，jedi-common 檔頭已明說「互補而非重疊，故並存不合併」——實測同意。

### ⑥ 殘餘 12 支掃描 — 排除四個假候選，挖出一個真問題

**結論：10 支確認獨立成立、1 支疆界待決（jedi-project）、0 支退役。** 本組對「26→15」的收斂貢獻小，價值在**排除假候選**，避免第三階段花時間合併不該合的東西。

**四個假候選全部不成立**：

| 候選 | 判定 | 決定性證據 |
|---|---|---|
| system-config ＋ system-menu | ❌ **語意相反** | 結構像雙胞胎，但 `system_configs`（20 列）**有 tenant_id ＋ RLS=t**，是**租戶機密設定**；`system_menus`（74 列）**無 tenant_id ＋ RLS=f**、`(group,key)` 全域唯一，是**全站共用列舉字典**（導覽選單另有 `ui_routes` 55 列）。合併會把租戶機密與全域字典放進同一租戶模型，是退步 |
| remote-agent ＋ log-forwarding | ❌ 零交集 | log-forwarding 是**進程內 logging handler 鏈**（跑在雲端 BE 進程內）；remote-agent 是**機器身分與任務派送**（mTLS／RS256 JWT／心跳）。「log-forwarding 是 agent 的一個能力」不成立 |
| integrity ＋ license-runtime | ⚠️ 不合併，但有繞行要修 | integrity 的 `ISignatureVerifier`／`IMachineFingerprint` **實作由宿主從 `jedi_license_runtime` 注入**——驗章能力繞了宿主一圈。合併能消掉這圈，但會把「檔案完整性」綁進「授權」。**建議改把兩支純密碼學原語下沉 jedi-common**，繞行自然消失 |
| notification 維持獨立 | ✅ D9 判斷成立 | 14 個消費點確認與身分無關；D9 說的「mfa 發信改 `INotifier` port」只需動 `jedi-mfa/.../email_service.py` 一個檔 |

**🔴 真問題：`jedi-project` 是「被掏空的核心概念」（本組最重發現）**

不是「太小該併掉」，而是**一個疆界被切成兩半，主專案那半有 24,640 行**：

| 面向 | 數據 |
|---|---|
| 套件體量 | 26 檔 / **399 行**（無 tests、無 harness） |
| 主專案引用 | **75 處 / 53 檔**（第 5 熱依賴） |
| **infra 穿透** | **21 處**（ORM model 15／mapper 4／repo_impl 2） |
| 主專案對應業務量 | `flow_control` 236 檔 / **24,640 行**（套件的 62 倍） |
| 被其他套件依賴 | **0** |
| 兩份實體並存 | 套件 `ProjectEntity` **11 欄** vs 主專案 `FlowControlProjectEntity` **40+ 欄**（docstring 自陳整合了 projects＋extensions＋OSCAL AP 計算欄位） |
| 表的歸屬 | `compliance.projects` 211 列，掛在它上面的 **7 張 FK 表全在主專案**，套件一張都沒有 |

**穿透原因不是「套件 domain service 能力不足」，是疆界切錯**——兩類用途都不是 domain service 能提供的：① JOIN 需要 ORM class 本身（13 處，例如 `Project` outerjoin `ProjectExtension` 再依 15 個衍生欄位排序，那需要套件認得主專案的表）；② SQLAlchemy `relationship()` 需要 model class（4 處）。

**建議：短期凍結、標記「疆界待決」、不補殼**。399 行空殼補 D6/D7 五層插件殼是純浪費（殼比內容物大）；而現在吸收回主專案會**主動關掉 design.md §7 終局選項「project 平台化」的門**——`compliance.projects` 與 `ProjectStatus` 是 jedi 生態的共同錨點。長期路徑與第二階段天然接軌：`project_extensions` 收進主表＋JSONB 容器後，套件的 `Project` model 就長出主專案要的欄位，**21 處穿透中至少 13 處的 JOIN 需求會自動消失**。

> 此條與 D9 的 login→auth **形狀相同但結論相反**：login 案是「兩支套件同疆界→合併」；project 案是「套件與宿主同疆界，但宿主那半的歸屬是商業決策」。差別在對造方是套件還是主專案。

**jedi-flow-engine 未做完整判定**：它有 40 處 infra 穿透、形狀與 jedi-project 同類，但**套件本身有 5,465 行實體**（非空殼），故不列同級問題。判它需要 flow-engine(12,765 行)＋flow_control(24,640 行)＋project 三方分析——**建議另立一棒專做「流程疆界」**，它與 jedi-project 是同一個結。

---

## 4. 對第三、四階段棒次表的改寫建議

### 第四階段（P6／P9–P13）

| 原棒次 | 改寫建議 | 理由 |
|---|---|---|
| **P9 jedi-detection**（detection_tools＋detection_execution＋**device**） | **device 移出**；**加入 `common/` 檢測領域碼約 1,567 行**；形態改為結論式「插件，不做服務化評估」；**新增設計決策題**：`job_service.py` 785 行檢測碼的去向 | device 與檢測零耦合、零資料關聯（§3 ③） |
| **P11 jedi-task-survey**（抽新套件） | **改寫為「問卷疆界合併」**——task_survey 併入 jedi-survey | 跨半身 DB 外鍵使「抽成獨立套件」技術上不可行（§3 ②） |
| **P13 oscal 三角拆解** | **三角的第三邊從 module_frame 改為 `flow_control`**（62 條 vs 18 條）；module_frame 降為「第二支候選、後期評估」；依賴方向建議**反轉為 oscal → flow_control 單向**；**前置決策改為「拍板 `ssp_reference_document*` 兩表歸屬」**；**新增前置查證棒**：v2 app service 為何被 77 條 RepoImpl 繞過 | §3 ④ |
| **P12 jedi-participant** | **維持不變，仍建議先做** | 解核心環的鑰匙；且 P11 排在它之後可省一輪 port 設計 |
| P6／P10 | 維持不變 | 本案未發現反證 |
| 「併入四階段評估」的 feedback | **維持第四階段，但修正判準**：design.md 說的「斷 2 條 system_config 線」實為**空掛注入可直接刪**（注入後從未呼叫）；真正的阻礙是 `jedi_auth.infra.models.User` 的 infra 穿透 ⇒ **應排在第 2.5 階段 jedi-iam 之後** | §3 ⑤ |
| — | **新增：「流程疆界」分析棒**（flow-engine ＋ flow_control ＋ jedi-project 三方） | jedi-project 與 flow-engine 是同一個結（§3 ⑥） |

**建議執行順序**：P12（鑰匙）→ P11（問卷合併）→ P9（檢測）→ P10／P6 → P13（OSCAL，最重且需前置查證）。

### 第三階段（老套件補殼）——補殼名單以本地圖為準

**不補殼的**（避免「補完又扔」）：jedi-resource-store（退役）、jedi-oscal v1（退役）、`jedi_system_log`（刪除）、jedi-bulletin（下架評估中）、jedi-project（疆界待決）。

**建議首棒**：**jedi-information-system**——903 行、9 個消費點、已有 `IUserLookup` dependency inversion 與守衛測試，是全庫依賴最乾淨的一支（唯一零虛掛依賴的老套件），補殼成本最低，適合驗證第三階段 SOP。

**補殼順帶收益**：老套件的虛掛依賴會**被 standalone harness 自動抓出來**（裝不全就跑不起來）——**harness 就是虛掛依賴的照妖鏡**，不需另開清理棒。實證：五支 P1–P5 新套件（皆有 harness）**零虛掛**，六支老套件全部有虛掛。

---

## 5. 順手發現——四個 P0 級問題

> 皆為本案分析副產品，**均已由彙整者親自覆核**。建議獨立開卡，不必等疆界拍板。

| # | 問題 | 證據 | 風險 |
|---|---|---|---|
| **P0-1** | **`jedi_system_log` 載入即炸** | 在 BE `.venv` 實測：先 import jedi-common 的 system_log（必然發生）後再 import `jedi_system_log` → `InvalidRequestError: Table 'system_logs' is already defined` | 上膛的槍。任何人「看到有這套件就拿來用」，第一行 import 就 500。且若有人加 `extend_existing=True` 硬繞，會得到一個**缺 3 欄的 model 靜默覆蓋正確的那個** |
| **P0-2** | **`jedi-issue` 漏宣告 `jedi-common`** | `pyproject.toml` 相依清單無 jedi-common，源碼卻 **import 它 28 次** | **單獨 `pip install jedi-issue` 必炸**。目前不暴露只因主專案剛好也裝了 jedi-common。這正是 FR-069「套件可獨立安裝」目標的直接反例，第三階段補殼首個會紅的 |
| **P0-3** | **`.venv` 殘留 jedi-oscal v1** | `.venv/.../jedi_oscal` 與 `jedi_oscal-0.0.23.dist-info` 實體仍在，但**不在 `poetry.lock`**（`poetry update` 未清） | 目前不炸（DI 走明文 allowlist 非萬用掃描），但它與 v2 有 **13 個同名表同 schema 同 Base**——任何誤 import 立即 `InvalidRequestError`。建議 `poetry install --sync` |
| **P0-4** | **6 張孤兒表隨 installer 出貨到每個客戶環境** | `component_definitions`＋`cd_capabilities`／`cd_components`／`cd_control_implementations`／`cd_implemented_requirements`／`cd_statements`——**全庫零 ORM 擁有者**（`grep __tablename__` 零命中）、**六張全 0 列**，只活在 `scripts/init/02-schema.sql` | 出貨污染。是 OSCAL Component Definition 的預留表，從未實作 |

### 其他可清理項（非 P0）

| 類別 | 內容 |
|---|---|
| **死碼** | 三支殭屍 app service 595 行（`oscal_audit_service.py` 409／`ssp_versioning_service.py` 151／`project_system_info_service.py` 35，method 全回 `None`／`{}`，外部 caller 皆 0）＋ **1,236 行測試在測空殼**（`test_ssp_versioning_service.py` 檔頭是模組層 `pytest.skip`，26 個 test 從未執行）；`task_survey_ref_items` 全鏈 219 行（DB 0 列，概念已被 in-memory dict 取代）；`job_execution_device_mapping` 全鏈 772 行（路由已註解、四 repo 零引用、DB 0 列，但 DI 仍 live-wired）；device 監控子能力 21 行＋2 表（**零實作的預留**：無 ORM、無 repo、無 service、無任何 INSERT 路徑）；問卷答案 Excel 匯入鏈 342 行（FE 唯一呼叫點在 `_archived/`）；`jedi_common/utils/gen_comment.py` 84 行（四 repo 零引用，且是**全生態 `openai` 依賴的唯一理由**——刪它可讓 26 支套件少扛一個 LLM SDK）；`jedi_common/session/database/base_model.py` 19 行（design.md 已定案待刪） |
| **靜默失效** | `SurveyPageDomainService` **DI 漂移**——`survey_containers.py:49-54` 注入 `survey_repo`、`question_answer_containers.py:50-54` 漏注入，而套件側 `verify_survey_exist()` 寫的是無 else 的 `if self.survey_repo:` ⇒ 走後者取得的實例**靜默不驗、回 None**。目前未爆（該路徑只用 `get_pages`），屬上膛的槍 |
| **零呼叫者的完整實作** | `archive_workflow_execution()`（`workflow_execution_service.py:1303`）完整實作了 4 支歷史表歸檔，但**全域零呼叫者**（唯一非定義命中是 2026-04 對話裡被註解掉的一行）；對應 `hi_*` 四張表全 0 列，而活表 `workflow_executions` 有 11,004 列。**需 user 裁決**：歸檔要接上（誰呼叫？專案結案時？）還是連表帶碼退役 |
| **依賴衛生** | **測試依賴錯放主依賴 20/26 支**（非 D9 估的 5 支）——乾淨的只有 ai-bot／integrity／license-runtime／log-forwarding／remote-agent（皆有正確 dev group）＋information-system（無測試）。主專案 `pyproject.toml:113-118` 已記載此問題會讓 `--without dev` 仍裝進 build 機 venv。**虛掛依賴集中在六支老套件**，最嚴重是 **jedi-project 11 項全虛掛**（399 行 CRUD 套件宣告了 pandas／pdfplumber／openpyxl／tqdm／ftfy，且 pytest 重複宣告兩次）。**反向漏宣告**：jedi-notification 用了 `requests` 未宣告、jedi-file-upload 用了 `werkzeug`／`urllib3` 未宣告（standalone harness 會炸） |
| **測試腐化** | jedi-file-upload `tests/unittest` **63 passed / 16 errors**（測試停留在 factory 重構前的舊 signature，且 patch 一個模組層不存在的符號）；`tests/architecture` 5 檔 978 行**從未執行**（缺 `pytestarch` 宣告，collect 即失敗）；jedi-resource-store 931 行測試**全綠地測著一個零消費者的殭屍**——「綠燈不代表活著」的樣本 |
| **命名混淆** | `FileUploadService` vs `UploadFileService` 並存（僅語序不同、職責無法從名字分辨，route 同時注入兩支）；`jedi_system_log/app/service/system_menu_service.py` **檔名與內容不符**（class 是 `SystemLogService`，是從 jedi-system-menu 複製後沒改檔名的殘跡）；`generate_uuid()` **全庫 17 支套件各抄一份**（canonical 早在 `jedi_common/utils/common_utils.py:7`） |
| **潛在 500** | `ai_dashboard` API registry 兩筆 device 條目（`api_registry.py:244,254`）指向 `associations_containers.py` 內**不存在的 provider**；因 lambda 延遲求值，app 起得來，但 AI 儀表板選到這兩支就炸 |
| **RLS 缺口（建議併入既有 CM-1445）** | jedi-issue **7 張表全無 `tenant_id` 且無 RLS**（`labels` 35 列全租戶共享）——與 CM-1445 追蹤的三張表**性質不同**（那三張是「有欄位沒開 RLS」，這七張是「連欄位都沒有」）；`compliance.job_execution_devices` 是租戶資料關聯表卻無 RLS；`upload_files` 無 RLS（靠 path/storage_type 間接隔離，程式碼有明文「繞 RLS 借同型設定」的 fallback） |
| **版控污染** | `jedi-device` 把 `dist/`（8 個舊版 whl）、`coverage_unittest/`、`testarch_report/` 進了版控；`jedi-device/README.md` 8 行且「目錄結構」段是空的 code block（D7-② 接入 README 標配完全缺席） |
| **文件與事實不符** | `docs/spec-site/current/user-manual/developer-setup-guide.md:338` 稱 jedi-resource-store 為「資源儲存抽象」（實為連結庫，且表從未存在）；`tests/DEPENDENCIES.md:23,308` 描述其 `resources` 表與 fixture 寫法；`docs/api/uploadfile/` 目錄名殘留舊拼法（程式碼已於 P0-① 收斂為 `upload_file`） |

---

## 6. 待決策點（D10–D16，供決策者拍板）

> 編號接續 design.md §3 的 D1–D9。**拍板後才改寫第三、四階段棒次表**。

| # | 決策點 | 建議 | 信心 | 若反悔的條件 |
|---|---|---|---|---|
| **D10** | **問卷疆界合併**：task_survey 併入 jedi-survey，P11 從「抽新套件」改為「合併」 | **同意合併**。跨半身 DB 外鍵使原方案技術上不可行；A 類產品特定僅 14% | 高 | 出現「只要問卷編輯器、不要作答」的真實消費者（如純表單設計器產品） |
| **D11** | **檢測疆界**：device **不**併入；P9 改為「detection_* ＋ common/ 檢測碼」；形態＝插件 | **同意**。零 import、零資料關聯（檢測型任務綁 device 0 列）、商業模型也不同（device 25/25 租戶標配 vs 檢測 6/25 加購） | 高 | 未來檢測改為「掃描目標必須來自資產主檔」的產品設計 |
| **D12** | **OSCAL 疆界**：oscal 應用層通用件併入 v2；module_frame 另立；P13 第三邊改為 flow_control；依賴方向反轉為 oscal → flow_control 單向 | **同意，但 P13 開工前先派一棒查證**「v2 app service 為何被 77 條 RepoImpl 繞過」——答案決定是「搬應用層進套件」還是「在 v2 內補編排層」 | 中高 | 查證結果顯示 v2 service 粒度其實夠用（則繞過屬歷史積習，該修的是主專案） |
| **D13** | **三支退役／刪除**：jedi-oscal v1（退役）、jedi-resource-store（併入 CM-1143）、`jedi_system_log`（刪除） | **同意三支**。證據鏈皆齊備，其中 `jedi_system_log` 建議**優先處理**（是會炸的碼，不只是死碼） | 高 | v1／resource-store：Nexus 出現四 repo 以外的下載者（**未查證項**，故建議「先標 deprecated、觀察一版」而非直接刪） |
| **D14** | **jedi-bulletin 下架評估**（比照 D9 的 jedi-department） | **建議下架**，但屬產品決策：套件 408 行 vs 主專案重寫 406 行、DEV 1 列 POC 0 列 | 中 | 公告功能列入未來產品路線（則該做的是把主專案那 406 行收回套件，而非下架） |
| **D15** | **jedi-project 疆界待決**：短期凍結不補殼，長期併入「專案疆界」而非吸收回主專案 | **同意凍結**。現在吸收會關掉 §7 終局選項的門；且第二階段 ext 收斂會自動消掉 13 處穿透 | 中高 | 商業決策明確放棄平台化（則改走「吸收回主專案、jedi-project 退役」，399 行搬回去是一天的工） |
| **D16** | **jedi-issue 死碼去留**（58% 從未執行）：gitlab/github 整合 1,191 行 ＋ member／project_member 鏈約 1,172 行 | **需 user 拍板**——「已廢棄」還是「留給未來客戶」是產品決策，程式碼判不了。技術面已證明：雙環境 `enable:false`＋空憑證＋零筆資料 | — | — |

### 本案未查證事項（誠實列出，供拍板時權衡）

1. **Nexus 是否有本地四 repo 以外的消費者**——三項退役結論的共同理論缺口。已確認「jedi-* 僅 Guidant AI 產品線消費」（design.md §3 D9 前置條件，2026-08-30 user 確認），但**未查 Nexus 下載紀錄**。故退役建議採「先標 deprecated、觀察一個版本週期」。
2. **STG／POC 的資料分佈多數未查**——遵守環境紀律，除 ①⑤ 兩組有補查 POC 外，其餘只查 DEV 與基線庫。不影響任何**程式碼層**結論（零 import／零耦合與資料量無關），但涉及「表有無資料」的死碼判定，STG/POC 未逐一覆核。
3. **A/B/C 膠水行數為逐檔判讀後的估算**，非逐行標註。相對比例可靠（如問卷 B 類 61% vs A 類 14% 差距 4 倍、OSCAL 通用件約 3,000 行），**絕對數字誤差可達 ±30%**，可用於棒次規模估算、**不宜當驗收基準**。
4. **`job_service.py` 785 行檢測碼「搬 vs 留」未拆解**——只做行數盤點，未逐方法判斷哪些依賴宿主 job entity。這是 P9 開工前該做的第二層分析。
5. **`archive_workflow_execution` 零呼叫者是靜態 grep 結論**——若有透過反射／排程設定／BPMN listener 動態調用，grep 抓不到。
6. **jedi-flow-engine 的疆界未做完整判定**——需 flow-engine＋flow_control＋project 三方分析，超出本案範圍，建議另立一棒。
7. **`detection_profiles` 內容庫服務化**（FR-059/060，11 主檔／4,841 控制項列）有「中央維運公版、各站台拉取」的天然形狀，但**未評估商業可行性**（是否有中央維運需求、客戶站台是否允許外連）。標為第四階段之後的獨立評估項，**不該塞進 P9**。

---

## 7. 願景達成度

design.md 原定願景是「26 支收斂為約 12–15 支輪廓清楚的疆界套件」。本案盤點後的實際收斂路徑：

```
現況 26 支
  −4  身分族合併（D9：auth＋login＋mfa＋captcha → jedi-iam）
  −1  jedi-department 下架（D9）
  −3  本案退役（jedi-oscal v1、jedi-resource-store、jedi_system_log 子套件）
  −1  jedi-bulletin 下架（D14，待拍板）
  ±0  問卷／OSCAL 疆界是「吸收主專案模組」，套件數不減但輪廓變清楚
  ────
  ≈ 17 支（若 D14 不下架則 18 支）
```

**未達 12–15 支，但這是正確的結果**——本案排除了四個假候選（system-config＋menu、remote-agent＋log-forwarding、integrity＋license-runtime、追蹤三支），若硬湊數字去合併它們，會把租戶機密與全域字典塞進同一租戶模型、把檔案完整性綁進授權，是**用架構退步換數字好看**。

真正的收斂價值不在套件數，而在：
- **三組疆界重劃**（問卷吸收作答層、OSCAL 吸收應用層通用件、檢測吸收 common/ 領域碼）讓「安裝即有完整能力」成立；
- **四個 P0 級地雷**在動核心抽取前被拆除；
- **約 6,000 行死碼**（jedi-issue 1,663／oscal 殭屍 595＋死測試 1,236／job_execution_device_mapping 772／task_survey_ref_items 219／Excel 匯入鏈 342／gen_comment 84 等）在搬家前先清掉——**搬死碼進套件是純虧**。
