---
title: FR-116 掃描盤點——jedi-compliance-audit 套件本體＋主專案接線：範圍界定與切棒
---

# FR-116 掃描盤點：jedi-compliance-audit（稽核輪次、稽核結果、改善計畫、程序書文件池）

> 盤點日期 2026-09-23｜盤點卡 CM-2089｜這份文件只盤點、不掃描——切好棒等首腦開卡派工。
> 行數全部是 `wc -l` 逐檔實算。基準：BE `feature/review` commit `2b595c976`；jedi monorepo `feature/review` commit `eafc7ae5`（`jedi-compliance-audit/` 工作區乾淨）。每棒段落附兩側各自的驗證指令。

名詞先講清楚（讀者是 PM 與新手工程師）：

- **套件**：`jedi-compliance-audit`，放在另一個 repo（`~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit/`）的一包程式，管「一輪稽核從開始到結案」。
- **主專案**：本 repo（BE）。套件本身不直接對外，要靠主專案把它接上網址、接上權限檢查。
- **守門**：檢查「你有沒有登入」「你有沒有這個功能的權限」「這筆資料是不是你的」的程式碼。
- **範圍條件**：資料庫查詢時除了「編號是多少」之外，有沒有再加上「而且要屬於某份計畫／某個專案／某家客戶」。只有編號、沒有範圍，就是總表第 118／119 項那種「檢查甲、刪乙」的洞。
- **資料庫隔離（RLS）**：資料庫這一層自己擋「你只能看到你們公司的資料」。程式層漏檢查時，它是最後一道防線。

---

## 這批最該先看的四件事

1. **四張資料表沒有第二道防線**。`round_stage_transitions`（階段歷程）、`round_rollback_supersessions`（回退標記）、`ssp_reference_documents`（程序書）、`ssp_reference_document_mappings`（程序書掛在哪個控制項）四張表：**沒有客戶欄位、資料庫隔離沒開、一條規則都沒有**（出貨版與 DEV 都一樣）。程式層只要漏一處歸屬檢查，資料就一路通到底。另外，稽核結果與改善計畫實際存在 `oscal.assessment_*`／`oscal.poam*` 八張表，**也全部沒開隔離**（DEV 實查）。
2. **稽核輪次的三支「讀取」沒有守門**。`audit_round_app_service.py` 12 支對外方法中，9 支寫入都有「你在這個專案是什麼角色」的檢查，但 `list_rounds`、`get_round`、`list_stage_transitions` 三支讀取一行都沒有。第三支已是總表第 52 項（FR-088 H1 報過），**前兩支沒有任何報告提過**，而且外面只掛了「有登入」「客戶有買這個功能」兩道門。這是 C2a 的頭號問題。
3. **稽核結果與改善計畫的「讀取」只查輪次存不存在**。`get_findings`（整張判定矩陣）、`list_risks`、`list_items`（改善計畫清單）、`get_item` 四支，寫入同類都有角色檢查，讀取只有 `_require_round…` 這種「輪次查得到就放行」的檢查。**沒有任何報告提過**。這是 C3 的頭號問題。
4. **任務設定樹那支（C1b）**：網址帶專案編號＋稽核計畫編號，但程式只拿後者去查，而且它會依序把這個編號當成「專案編號／輪次編號／稽核計畫編號」三種去猜；整條路只有「有登入」「客戶有買」兩道門，**沒有看到任何「你是不是這個專案的人」的檢查**。回傳的是整棵控制項樹＋每項任務的指派人姓名。

**為什麼這幾件要先看**：總表第 118／119／129 項已經證實這支套件的程式形狀是「只認編號、不問歸屬」，而上面四件是同一個形狀在別的功能上又出現——而且底下的資料表沒有隔離可以兜底。

> ⚠️ 以上是盤點時開檔看到的「疑點」，**不是掃描結論**。有沒有別的機制擋住（例如前端拿不到編號、別的中介層），要等掃描棒追完呼叫鏈才能下定論。

---

## 1. 範圍界定

### 1.1 套件側

```bash
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit
git ls-files -- '*.py' | grep -v '^tests/' | xargs wc -l | tail -1     # 169 支、13,045 行
```

**正式程式 169 支／13,045 行**（另有 tests 7 支／820 行不掃）。

⚠️ **卡片寫的「176 支／13,865 行」是誤植**：把 tests 7 支 820 行也算進去了。以本文件為準。

169 支的去向：

| 分類 | 檔數 | 行數 | 說明 |
|---|---:|---:|---|
| 排進掃描棒 | 110 | 9,577 | 見第 2 節（去重後） |
| 死碼區，不派工 | 39 | 2,781 | 見 1.4 |
| 錯誤碼常數表 `common/error_code.py` | 1 | 483 | 純常數，不派工 |
| `harness/dev_app.py` | 1 | 203 | 套件獨立測試用的假掛載（註解自陳「service 全是假的、不連 DB」），`pyproject.toml` 只打包 `jedi_compliance_audit/`，**不進出貨包**，不派工 |
| 空檔（0～1 行 `__init__.py`） | 18 | 1 | 不派工 |
| **合計** | **169** | **13,045** | |

另有兩支**不是 `.py`、但必須一起看**的檔，已併入 C2a 與 C5 的重點：
`jedi_compliance_audit/migrations/001-compliance-audit-ui-routes.sql`（19 行）、`002-compliance-audit-rls.sql`（92 行，稽核輪次與複核註記兩張表的隔離規則就在這支）。

### 1.2 主專案側

三種方法交叉比對（前一棒做過，本棒在 HEAD `2b595c976` 重跑確認數字不變）：

```bash
# ① import grep
grep -rlE "(from|import) jedi_compliance_audit" --include='*.py' api app core di_containers infra domain config common | xargs wc -l | tail -1
#   → 28 檔、11,557 行
# ② 從 core/plugins/compliance_audit.py 與 DI 容器反查（套件的 service 被哪些 provider 建、被誰注入）
# ③ 全文字串 grep（例如 "audit_service"、"flow_control_container" 這類用字串指名的接法）
```

**28 檔／11,557 行**，三種方法結果一致。

另外**用 ②③ 反查多抓到 3 支 import 抓不到的檔**：前兩支納入 C1b，第三支納入 C2a。

| 檔 | 行數 | 為什麼 import 抓不到 | 為什麼要納入 |
|---|---:|---|---|
| `app/readmodel/service/auditor_dashboard_service.py` | 14 | 套件路由用字串 `runtime().service("auditor_dashboard_service")` 取服務，實際物件是主專案 DI 建的 | 套件的「稽核人員待辦清單」路由背後**真正做事的是主專案這兩支**，不看它們等於沒看這條路 |
| `infra/readmodel/audit/auditor_dashboard_query.py` | 118 | 同上 | 同上；查詢條件是 `pp.user_id = :uid`，要確認沒有別的路徑能換人查 |
| `api/project/serializers/audit_round.py` | 239 | 只在註解提到套件的服務名，沒有 import | 是 `audit_round_route.py` 32 個端點的請求／回應格式，路由少了它就看不到收什麼欄位 |

### 1.3 排除清單（已被別的掃描棒列為範圍，不重複掃）

每一支都回頭對過該棒的卡片或報告裡的檔案清單。

| 檔 | 行數 | 哪一棒掃過 |
|---|---:|---|
| `app/oscal/service/ssp_control_implementation_service.py` | 1,037 | FR-113 O1 |
| `app/oscal/service/ssp_document_pool_service.py` | 313 | FR-113 O6（第 118／119／129 項就在這支） |
| `app/oscal/service/ssp_control_impl_import_service.py` | 416 | FR-113 O4 |
| `app/module_frame/service/module_frame_reference_document_service.py` | 257 | FR-113 B2 |
| `app/module_frame/service/ssp_import_template_app_service.py` | 1,500 | FR-113 B4 |
| `di_containers/module_frame/module_frame_containers.py` | 260 | FR-113 B1 |
| `app/project/service/project_start_app_service.py` | 481 | FR-095 H1 |
| `app/flow_control/service/project_service.py` | 789 | FR-095 H1（並排在 FR-095 H2 CM-1783，尚未派） |
| `infra/flow_control/repository/flow_control_project_repo_impl.py` | 716 | FR-095 H1 |
| `di_containers/flow_control/flow_control_containers.py` | 753 | FR-095 H1 |
| `di_containers/project/project_containers.py` | 70 | FR-095 H1 |
| `di_containers/flow_engine/workflow_excution_containers.py` | 155 | FR-095 H1（FR-088 H4 也掃過） |
| `api/flow_engine/routes/stage_rollback_route.py` | 67 | FR-088 H1 |
| `app/flow_engine/service/stage_advance_service.py` | 702 | FR-088 H1 |
| `infra/readmodel/audit/flow_control_dashboard_repo_impl.py` | 254 | FR-095 H2（CM-1783，已開卡、尚未派） |
| `infra/flow_control/repository/flow_control_job_repo_impl.py` | 1,216 | FR-115 W4（CM-2093，已開卡、尚未派） |
| `infra/readmodel/tasks/job_export_query.py` | 264 | FR-115 W4（CM-2093，已開卡、尚未派） |
| **合計** | **9,000** | 17 檔 |

⚠️ **交接說明裡寫「已掃過」的 `api/flow_control/routes/project_route.py`，查證後不成立**：它只在 FR-088 H3 報告裡被**引用當範例**（「同 repo 讀取端點掛能力點的做法可抄這支」），不在任何一棒的掃描範圍內。本盤點改判「沒查過」，排進 C1c。

⚠️ **`api/flow_control/__init__.py` 在 FR-095 H2 卡片範圍內，但那張卡還沒派**。它是本套件掛上網址的唯一入口（`attach()` 在這裡被呼叫），放進 C1 當接縫檔；H2 若先派，C1 可以只讀不報。

⚠️ 交接說明提到的兩支懸案（`infra/oscal/repository/ssp_catalog_title_query.py`、`di_containers/oscal/oscal_containers.py`）**維持前一棒判斷：只在 FR-113 O9 清單上出現過、O9 本體沒派、拆出的 O9a／O9b 也不含它們**，當作沒查過，排進 C5。

### 1.4 死碼區：39 支／2,781 行，不派工（但需要首腦知道）

這一區是**舊版資料模型留下來、現在已經打不到的程式**。盤點時逐條確認過「打不打得到」：

| 群組 | 檔數 | 行數 | 為什麼打不到 |
|---|---:|---:|---|
| `audit_service.py`＋其 domain／repo／mapper／DTO | 7 | 836 | DI 有建這個服務、`core/plugins/compliance_audit.py:118` 也登記給套件，**但套件五條路由只取 review／task_setup／current-ssp／auditor_dashboard 四個服務**（`routing.py` 逐條對過），全 codebase（兩個 repo）沒有任何呼叫者。底下 repo 的 13 個方法**全部寫死回空值**（`flow_control_audit_repo_impl.py:34-77`） |
| `poam_service.py`＋其 domain／repo／mapper／model／DTO | 8 | 522 | 同上：DI 有、登記有、沒有任何呼叫者。repo 的 `list_poams`／`get_poam` 寫死回空（`poam_repo_impl.py:36-52`），所以 `update_poam` 的第一步 `get_poam` 永遠回 None → 404，**打不到後面那句「只用編號更新」**（`poam_repo_impl.py:55-56`） |
| 控制項／控制群組／評估物件三組 repo＋DTO＋entity | 22 | 1,355 | DI 有建 domain service（`flow_control_containers.py:385-406`），**但沒有任何服務注入它們**；三支 repo 全部方法寫死回空（檔頭自陳「依附舊 OSCAL 資料模型，v2 切換後停用待重建」） |
| `oscal_audit_query.py` | 1 | 68 | DI 注入給 `AllTasksAssignedCheck`，但那個檢查的 `check()` 直接 `return ok()`，從不呼叫它；本身 7 個方法也全部寫死回空 |
| `round_stage_transition_query_entity.py` | 1 | 8 | 零引用 |

**判斷**：死碼不掃。但有兩點要首腦知道：
- 交接說明第 1、2 點懷疑的 `poam_service.py`／`audit_service.py`「零守門」**確認打不到，不成立為發現**；`poam_repo_impl.update_poam` 那句「只用編號」的查詢**也是死碼**，列在第 4 節對照表但標「死碼」。
- 死碼區的 DI 登記還在（`core/plugins/compliance_audit.py:118,120` 與 `flow_control_containers.py:523-556`）。**哪天有人把它接回路由，零守門的服務會直接上線**。建議併入 FR-092（死碼清理）的後續，把這 39 支連同 DI 登記一起刪——這是清理建議，不是資安發現。

### 1.5 最終要掃的範圍

| | 檔數 | 行數 |
|---|---:|---:|
| 套件側 | 110 | 9,577 |
| 主專案側（28 − 排除 17 ＋ 反查補入 3） | 14 | 2,678 |
| **合計（去重）** | **124** | **12,255** |

```bash
# 套件側：預期 110 檔、9,577 行（把第 2 節各棒套件清單合併去重）
# 主專案側：預期 14 檔、2,678 行
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git ls-files -- \
  core/plugins/compliance_audit.py api/flow_control/__init__.py \
  app/readmodel/service/auditor_dashboard_service.py infra/readmodel/audit/auditor_dashboard_query.py \
  api/flow_control/routes/project_route.py api/flow_control/routes/assessment_plan_route.py \
  api/project/routes/audit_round_route.py api/project/serializers/audit_round.py \
  app/flow_control/service/prep_job_generation_service.py \
  api/project/routes/ap_docx_import_route.py api/project/routes/ar_import_route.py \
  app/oscal/dto/ssp/ssp_reference_document_dto.py di_containers/oscal/oscal_containers.py \
  infra/oscal/repository/ssp_catalog_title_query.py | xargs wc -l | tail -1
```

---

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

切法原則：**按業務功能縱切**——同一個功能的「主專案網址入口 → 套件服務 → 套件 domain → 套件 repo → 資料表 model」放同一棒。權限漏洞活在「兩側接縫」上：只給套件或只給主專案，兩邊都看不出來。

**接縫檔**（刻意在兩棒重複列）：`audit_round_app_service.py`（C2a／C2b）、`audit_round_route.py`（C2a／C3）、`api/guards.py`（C1／C1b）、`common/guard.py`（C1／C3）、`import_adapter/` 兩支（C4a／C4b）。

| 棒 | 做什麼 | 檔數 | 行數 | 套件／主專案 |
|---|---|---:|---:|---|
| C1 | 接線總裝＋審閱簽核 | 22 | 1,739 | 20／2 |
| C1b | 任務設定樹、目前 SSP、稽核人員待辦清單 | 21 | 1,404 | 19／2 |
| C1c | 專案清單／詳細與稽核計畫選單的主專案路由 | 11 | 1,255 | 9／2 |
| C2a | 🔴 稽核輪次主體（開輪、啟動、結案、讀取） | 10 | 1,939 | 8／2 |
| C2b | 階段歷程、回退標記、規劃完成度檢查 | 15 | 1,612 | 14／1 |
| C3 | 🔴 稽核結果（判定、觀察、風險）與改善計畫 | 6 | 1,934 | 5／1 |
| C4a | 稽核計畫 Word 匯入 | 14 | 1,271 | 13／1 |
| C4b | 稽核結果 Excel 匯入 | 19 | 1,585 | 18／1 |
| C5 | 程序書文件池的資料層＋兩支懸案檔 | 12 | 1,213 | 9／3 |
| **逐棒加總（含接縫重複）** | | **130** | **13,952** | |

逐棒加總扣掉接縫重複（`audit_round_app_service.py` 925、`audit_round_route.py` 508、`api/guards.py` 125、`common/guard.py` 76、`method_keywords.py` 35、`registry_base.py` 28，共 6 支次、1,697 行）＝ **124 檔、12,255 行**，與 1.5 一致。

### 交接時的卡點：C2 怎麼拆

前一棒把「稽核輪次主體＋階段歷程＋回退標記＋規劃檢查＋路由」放在同一棒，2,454 行超標。本棒先實算 `audit_round_app_service.py` 裡碰「歷程／回退」的比例：**只有 7 行呼叫**（`:804`、`:806`、`:857` 寫回退標記，`:813`、`:836`、`:863` 寫歷程，`:877` 讀歷程），全部集中在三支 `rollback_to_*` 與 `list_stage_transitions`。歷程與回退不是主體邏輯，是**附掛的紀錄表**。

所以拆法是：
- **C2a 輪次主體**：服務本體＋輪次那張表的 domain／repo＋主專案的輪次路由與 serializer。
- **C2b 附掛紀錄與前置檢查**：服務本體（接縫，重複）＋歷程、回退兩組 domain／repo／model＋規劃完成度檢查＋主專案的證據蒐集任務產生器。

另外兩個調整讓 C2a 壓進 2,000 行：
- 「專案延伸欄位」那組 6 支（living SSP、負責人）不是輪次專屬，實際使用者是「目前 SSP」與「任務設定樹」→ 移到 C1b。
- `common/guard.py` 在 C1、C3 都有，C2a 不再重複（研究員追呼叫可越界讀）。

⚠️ **不用擔心兩棒各自膨脹**：接縫檔只有 `audit_round_app_service.py` 一支（925 行），C2a 1,939、C2b 1,612 都在門檻內。

### C1 — 接線總裝＋審閱簽核

**這一棒要回答**：套件拿到的三道守門是不是主專案真的那三道；審閱簽核的角色檢查「零件沒裝就跳過」這個寫法，現在是不是每一處都有裝。

```
套件側（cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit）
   24 jedi_compliance_audit/__init__.py
   37 jedi_compliance_audit/api/__init__.py
  125 jedi_compliance_audit/api/guards.py                          ← 路由層守門（接縫，C1b 也帶）
   56 jedi_compliance_audit/api/routing.py                         ← 套件自帶的 5 條網址
  124 jedi_compliance_audit/plugin/__init__.py
   92 jedi_compliance_audit/plugin/assembly.py                     ← 「守門沒接好就拒絕掛載」在這
  124 jedi_compliance_audit/plugin/contract.py
   41 jedi_compliance_audit/plugin/runtime.py
   64 jedi_compliance_audit/plugin/wiring.py                       ← 把守門綁進服務層
   39 jedi_compliance_audit/domain/ports.py
   76 jedi_compliance_audit/common/guard.py                        ← 服務層守門（接縫，C3 也帶）
   53 jedi_compliance_audit/api/routes/review_route.py
   12 jedi_compliance_audit/api/serializers/review.py
  129 jedi_compliance_audit/app/service/review_service.py          ← 🔴「零件沒裝就跳過」寫法
   32 jedi_compliance_audit/app/dto/review_dto.py
   52 jedi_compliance_audit/domain/entities/flow_control_review_entity.py
   44 jedi_compliance_audit/domain/repository/i_flow_control_review_repo.py
   40 jedi_compliance_audit/domain/service/flow_control_review_domain_service.py
  238 jedi_compliance_audit/infra/repository/flow_control_review_repo_impl.py
   17 jedi_compliance_audit/infra/model/review_mark.py
主專案側（cd ~/Projects/Billows/Audit-Manager/compliance-manager-be）
  185 core/plugins/compliance_audit.py                             ← 三道守門實際給什麼
  135 api/flow_control/__init__.py                                  ← attach() 呼叫點
```
共 **22 檔／1,739 行**（套件 20／1,419＋主專案 2／320）。

驗證：
```bash
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit && git ls-files -- jedi_compliance_audit/__init__.py jedi_compliance_audit/api/__init__.py jedi_compliance_audit/api/guards.py jedi_compliance_audit/api/routing.py jedi_compliance_audit/plugin/__init__.py jedi_compliance_audit/plugin/assembly.py jedi_compliance_audit/plugin/contract.py jedi_compliance_audit/plugin/runtime.py jedi_compliance_audit/plugin/wiring.py jedi_compliance_audit/domain/ports.py jedi_compliance_audit/common/guard.py jedi_compliance_audit/api/routes/review_route.py jedi_compliance_audit/api/serializers/review.py jedi_compliance_audit/app/service/review_service.py jedi_compliance_audit/app/dto/review_dto.py jedi_compliance_audit/domain/entities/flow_control_review_entity.py jedi_compliance_audit/domain/repository/i_flow_control_review_repo.py jedi_compliance_audit/domain/service/flow_control_review_domain_service.py jedi_compliance_audit/infra/repository/flow_control_review_repo_impl.py jedi_compliance_audit/infra/model/review_mark.py | xargs wc -l | tail -1   # 20 檔、1419
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git ls-files -- core/plugins/compliance_audit.py api/flow_control/__init__.py | xargs wc -l | tail -1   # 2 檔、320
```

### C1b — 任務設定樹、目前 SSP、稽核人員待辦清單

**這一棒要回答**：套件自帶的三條「只讀」網址背後，有沒有任何一道「你是不是這個專案的人」的檢查。

```
套件側
  125 jedi_compliance_audit/api/guards.py                          ← 接縫（C1 也帶）
   32 jedi_compliance_audit/api/routes/task_setup_route.py         ← 只掛登入＋商務授權
   57 jedi_compliance_audit/api/serializers/task_setup.py
   29 jedi_compliance_audit/app/service/task_setup_service.py      ← 🔴 0 守門
   97 jedi_compliance_audit/app/dto/task_setup_dto.py
   64 jedi_compliance_audit/domain/entities/flow_control_task_setup_entity.py
   11 jedi_compliance_audit/domain/repository/i_flow_control_task_setup_repo.py
   10 jedi_compliance_audit/domain/service/flow_control_task_setup_domain_service.py
  397 jedi_compliance_audit/infra/repository/flow_control_task_setup_repo_impl.py  ← 🔴 編號三猜
   35 jedi_compliance_audit/api/routes/project_current_ssp_route.py
   69 jedi_compliance_audit/app/service/project_current_ssp_service.py  ← 🔴 0 守門
   37 jedi_compliance_audit/api/routes/auditor_dashboard_route.py
   50 jedi_compliance_audit/api/serializers/auditor_dashboard.py
   19 jedi_compliance_audit/domain/entities/project_extension_entity.py
   12 jedi_compliance_audit/domain/entities/project_extension_query_entity.py
   26 jedi_compliance_audit/domain/repository/i_project_extension_repo.py
   46 jedi_compliance_audit/domain/service/project_extension_domain_service.py
  127 jedi_compliance_audit/infra/repository/project_extension_repo_impl.py
   29 jedi_compliance_audit/infra/mapper/project_extension_mapper.py
主專案側
   14 app/readmodel/service/auditor_dashboard_service.py            ← 反查補入
  118 infra/readmodel/audit/auditor_dashboard_query.py              ← 反查補入
```
共 **21 檔／1,404 行**（套件 19／1,272＋主專案 2／132）。

驗證：
```bash
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit && git ls-files -- jedi_compliance_audit/api/guards.py jedi_compliance_audit/api/routes/task_setup_route.py jedi_compliance_audit/api/serializers/task_setup.py jedi_compliance_audit/app/service/task_setup_service.py jedi_compliance_audit/app/dto/task_setup_dto.py jedi_compliance_audit/domain/entities/flow_control_task_setup_entity.py jedi_compliance_audit/domain/repository/i_flow_control_task_setup_repo.py jedi_compliance_audit/domain/service/flow_control_task_setup_domain_service.py jedi_compliance_audit/infra/repository/flow_control_task_setup_repo_impl.py jedi_compliance_audit/api/routes/project_current_ssp_route.py jedi_compliance_audit/app/service/project_current_ssp_service.py jedi_compliance_audit/api/routes/auditor_dashboard_route.py jedi_compliance_audit/api/serializers/auditor_dashboard.py jedi_compliance_audit/domain/entities/project_extension_entity.py jedi_compliance_audit/domain/entities/project_extension_query_entity.py jedi_compliance_audit/domain/repository/i_project_extension_repo.py jedi_compliance_audit/domain/service/project_extension_domain_service.py jedi_compliance_audit/infra/repository/project_extension_repo_impl.py jedi_compliance_audit/infra/mapper/project_extension_mapper.py | xargs wc -l | tail -1   # 19 檔、1272
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git ls-files -- app/readmodel/service/auditor_dashboard_service.py infra/readmodel/audit/auditor_dashboard_query.py | xargs wc -l | tail -1   # 2 檔、132
```

### C1c — 專案清單／詳細與稽核計畫選單的主專案路由

**這一棒要回答**：主專案這兩支路由檔從沒被掃過（`project_route.py` 只被引用當範例、`assessment_plan_route.py` 只被 FR-095 H1 順帶查了一支選單）。背後的服務 `project_service.py` 已掃過，本棒只看**路由層有沒有漏掛能力點、有沒有把網址上的編號不經比對就往下傳**。

```
套件側
  248 jedi_compliance_audit/api/serializers/project.py
   54 jedi_compliance_audit/api/serializers/assessment_plan.py
  157 jedi_compliance_audit/app/dto/project_dto.py
   50 jedi_compliance_audit/app/dto/assessment_plan_dto.py
   58 jedi_compliance_audit/domain/entities/flow_control_project_entity.py
   15 jedi_compliance_audit/domain/entities/flow_control_project_query_entity.py
  109 jedi_compliance_audit/domain/repository/i_flow_control_project_repo.py
   59 jedi_compliance_audit/domain/service/flow_control_project_domain_service.py
  115 jedi_compliance_audit/infra/mapper/flow_control_project_mapper.py
主專案側
  273 api/flow_control/routes/project_route.py
  117 api/flow_control/routes/assessment_plan_route.py
```
共 **11 檔／1,255 行**（套件 9／865＋主專案 2／390）。

驗證：
```bash
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit && git ls-files -- jedi_compliance_audit/api/serializers/project.py jedi_compliance_audit/api/serializers/assessment_plan.py jedi_compliance_audit/app/dto/project_dto.py jedi_compliance_audit/app/dto/assessment_plan_dto.py jedi_compliance_audit/domain/entities/flow_control_project_entity.py jedi_compliance_audit/domain/entities/flow_control_project_query_entity.py jedi_compliance_audit/domain/repository/i_flow_control_project_repo.py jedi_compliance_audit/domain/service/flow_control_project_domain_service.py jedi_compliance_audit/infra/mapper/flow_control_project_mapper.py | xargs wc -l | tail -1   # 9 檔、865
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git ls-files -- api/flow_control/routes/project_route.py api/flow_control/routes/assessment_plan_route.py | xargs wc -l | tail -1   # 2 檔、390
```

### C2a — 🔴 稽核輪次主體

**這一棒要回答**：開輪、啟動稽核、開始稽核、結案、覆核輪、三種回退，每一支的角色檢查對不對；**兩支讀取（輪次清單、單筆輪次）為什麼沒有角色檢查**。

```
套件側
  925 jedi_compliance_audit/app/service/audit_round_app_service.py  ← 接縫（C2b 也帶）；12 支對外方法
   29 jedi_compliance_audit/domain/entities/project_audit_round_entity.py
   17 jedi_compliance_audit/domain/entities/project_audit_round_query_entity.py
   30 jedi_compliance_audit/domain/repository/i_project_audit_round_repo.py
   37 jedi_compliance_audit/domain/service/project_audit_round_domain_service.py
   77 jedi_compliance_audit/infra/repository/project_audit_round_repo_impl.py  ← get_by_uid 只用編號
   34 jedi_compliance_audit/infra/mapper/project_audit_round_mapper.py
   43 jedi_compliance_audit/infra/model/project_audit_round.py
主專案側
  508 api/project/routes/audit_round_route.py                       ← 接縫（C3 也帶）；32 個端點
  239 api/project/serializers/audit_round.py
```
共 **10 檔／1,939 行**（套件 8／1,192＋主專案 2／747）。
附帶讀：`jedi_compliance_audit/migrations/002-compliance-audit-rls.sql`（92 行，輪次表的隔離規則）。

驗證：
```bash
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit && git ls-files -- jedi_compliance_audit/app/service/audit_round_app_service.py jedi_compliance_audit/domain/entities/project_audit_round_entity.py jedi_compliance_audit/domain/entities/project_audit_round_query_entity.py jedi_compliance_audit/domain/repository/i_project_audit_round_repo.py jedi_compliance_audit/domain/service/project_audit_round_domain_service.py jedi_compliance_audit/infra/repository/project_audit_round_repo_impl.py jedi_compliance_audit/infra/mapper/project_audit_round_mapper.py jedi_compliance_audit/infra/model/project_audit_round.py | xargs wc -l | tail -1   # 8 檔、1192
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git ls-files -- api/project/routes/audit_round_route.py api/project/serializers/audit_round.py | xargs wc -l | tail -1   # 2 檔、747
```

### C2b — 階段歷程、回退標記、規劃完成度檢查

**這一棒要回答**：歷程與回退兩張表（隔離全關）的讀寫，呼叫端有沒有先確認「這個輪次是你的」；規劃完成度檢查在查詢失敗時「放行」的寫法會不會被利用。

```
套件側
  925 jedi_compliance_audit/app/service/audit_round_app_service.py  ← 接縫（C2a 也帶）
   19 jedi_compliance_audit/domain/entities/round_stage_transition_entity.py
   12 jedi_compliance_audit/domain/repository/i_round_stage_transition_repo.py
   17 jedi_compliance_audit/domain/service/round_stage_transition_domain_service.py
   36 jedi_compliance_audit/infra/repository/round_stage_transition_repo_impl.py
   35 jedi_compliance_audit/infra/mapper/round_stage_transition_mapper.py
   25 jedi_compliance_audit/infra/model/round_stage_transition.py
   20 jedi_compliance_audit/domain/entities/round_rollback_supersession_entity.py
   21 jedi_compliance_audit/domain/repository/i_round_rollback_supersession_repo.py
   37 jedi_compliance_audit/domain/service/round_rollback_supersession_domain_service.py
   55 jedi_compliance_audit/infra/repository/round_rollback_supersession_repo_impl.py
   33 jedi_compliance_audit/infra/mapper/round_rollback_supersession_mapper.py
   23 jedi_compliance_audit/infra/model/round_rollback_supersession.py
  128 jedi_compliance_audit/app/service/planning_readiness_checker.py  ← 兩處查詢失敗就放行
主專案側
  226 app/flow_control/service/prep_job_generation_service.py       ← 覆核輪產生證據蒐集任務
```
共 **15 檔／1,612 行**（套件 14／1,386＋主專案 1／226）。

驗證：
```bash
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit && git ls-files -- jedi_compliance_audit/app/service/audit_round_app_service.py jedi_compliance_audit/domain/entities/round_stage_transition_entity.py jedi_compliance_audit/domain/repository/i_round_stage_transition_repo.py jedi_compliance_audit/domain/service/round_stage_transition_domain_service.py jedi_compliance_audit/infra/repository/round_stage_transition_repo_impl.py jedi_compliance_audit/infra/mapper/round_stage_transition_mapper.py jedi_compliance_audit/infra/model/round_stage_transition.py jedi_compliance_audit/domain/entities/round_rollback_supersession_entity.py jedi_compliance_audit/domain/repository/i_round_rollback_supersession_repo.py jedi_compliance_audit/domain/service/round_rollback_supersession_domain_service.py jedi_compliance_audit/infra/repository/round_rollback_supersession_repo_impl.py jedi_compliance_audit/infra/mapper/round_rollback_supersession_mapper.py jedi_compliance_audit/infra/model/round_rollback_supersession.py jedi_compliance_audit/app/service/planning_readiness_checker.py | xargs wc -l | tail -1   # 14 檔、1386
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git ls-files -- app/flow_control/service/prep_job_generation_service.py | xargs wc -l | tail -1   # 1 檔、226
```

### C3 — 🔴 稽核結果（判定、觀察、風險）與改善計畫

**這一棒要回答**：判定矩陣、風險清單、改善計畫清單與單筆這四支讀取為什麼只查「輪次存不存在」；寫入類刪除時，被刪的那筆有沒有確實屬於網址上那一輪。

```
套件側
  828 jedi_compliance_audit/app/service/assessment_result_app_service.py  ← 16 支對外方法
  434 jedi_compliance_audit/app/service/poam_app_service.py              ← 8 支對外方法
   42 jedi_compliance_audit/app/service/ao_derivation.py
   76 jedi_compliance_audit/common/guard.py                              ← 接縫（C1 也帶）
   46 jedi_compliance_audit/common/round_guard.py
主專案側
  508 api/project/routes/audit_round_route.py                           ← 接縫（C2a 也帶）
```
共 **6 檔／1,934 行**（套件 5／1,426＋主專案 1／508）。

**刻意做成大檔少數棒**：兩支服務合計 1,262 行、24 支對外方法，而且直接呼叫 `jedi-oscal-v2` 套件十幾支 repo（稽核結果、改善計畫的資料實際存在 `oscal.*` 表）。研究員要越界讀 oscal-v2 的 repo 看查詢條件——那是必要的，但**不報 oscal-v2 本身的問題**（歸 FR-113／M11）。

驗證：
```bash
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit && git ls-files -- jedi_compliance_audit/app/service/assessment_result_app_service.py jedi_compliance_audit/app/service/poam_app_service.py jedi_compliance_audit/app/service/ao_derivation.py jedi_compliance_audit/common/guard.py jedi_compliance_audit/common/round_guard.py | xargs wc -l | tail -1   # 5 檔、1426
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git ls-files -- api/project/routes/audit_round_route.py | xargs wc -l | tail -1   # 1 檔、508
```

### C4a — 稽核計畫 Word 匯入

**這一棒要回答**：上傳 → 解析 → 預覽 → 確認四步，每一步是不是都有「專案角色」＋「這份解析工作屬於你們公司」兩道檢查；上傳的 Word 檔解析時有沒有吃檔案的風險。

```
套件側
  375 jedi_compliance_audit/app/service/ap_docx_import_app_service.py
   23 jedi_compliance_audit/app/service/ap_report_parser/__init__.py
  382 jedi_compliance_audit/app/service/ap_report_parser/airasia_cmmc_l1_v1.py  ← Word 解析器
   43 jedi_compliance_audit/app/service/ap_report_parser/base.py
   26 jedi_compliance_audit/app/service/ap_report_parser/registry.py
   35 jedi_compliance_audit/app/service/import_adapter/method_keywords.py  ← 接縫（C4b 也帶）
   28 jedi_compliance_audit/app/service/import_adapter/registry_base.py    ← 接縫（C4b 也帶）
   39 jedi_compliance_audit/domain/entities/ap_docx_parse_job_entity.py
   25 jedi_compliance_audit/domain/repository/i_ap_docx_parse_job_repo.py
   64 jedi_compliance_audit/domain/service/ap_docx_parse_job_domain_service.py
   54 jedi_compliance_audit/infra/repository/ap_docx_parse_job_repo_impl.py
   33 jedi_compliance_audit/infra/mapper/ap_docx_parse_job_mapper.py
   28 jedi_compliance_audit/infra/model/ap_docx_parse_job.py
主專案側
  116 api/project/routes/ap_docx_import_route.py
```
共 **14 檔／1,271 行**（套件 13／1,155＋主專案 1／116）。

驗證：
```bash
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit && git ls-files -- jedi_compliance_audit/app/service/ap_docx_import_app_service.py jedi_compliance_audit/app/service/ap_report_parser/__init__.py jedi_compliance_audit/app/service/ap_report_parser/airasia_cmmc_l1_v1.py jedi_compliance_audit/app/service/ap_report_parser/base.py jedi_compliance_audit/app/service/ap_report_parser/registry.py jedi_compliance_audit/app/service/import_adapter/method_keywords.py jedi_compliance_audit/app/service/import_adapter/registry_base.py jedi_compliance_audit/domain/entities/ap_docx_parse_job_entity.py jedi_compliance_audit/domain/repository/i_ap_docx_parse_job_repo.py jedi_compliance_audit/domain/service/ap_docx_parse_job_domain_service.py jedi_compliance_audit/infra/repository/ap_docx_parse_job_repo_impl.py jedi_compliance_audit/infra/mapper/ap_docx_parse_job_mapper.py jedi_compliance_audit/infra/model/ap_docx_parse_job.py | xargs wc -l | tail -1   # 13 檔、1155
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git ls-files -- api/project/routes/ap_docx_import_route.py | xargs wc -l | tail -1   # 1 檔、116
```

### C4b — 稽核結果 Excel 匯入

**這一棒要回答**：同 C4a 四步；另外 Excel 匯入會「自動配對證據」——配對時撈的證據範圍有沒有可能撈到別的輪次、別的客戶。

```
套件側
  610 jedi_compliance_audit/app/service/ar_import_app_service.py
   23 jedi_compliance_audit/app/service/ar_report_parser/__init__.py
  195 jedi_compliance_audit/app/service/ar_report_parser/airasia_cmmc_l1_ar_v1.py  ← Excel 解析器
   43 jedi_compliance_audit/app/service/ar_report_parser/base.py
   22 jedi_compliance_audit/app/service/ar_report_parser/registry.py
   37 jedi_compliance_audit/app/service/ar_framework_profile/cmmc_l1.py
   17 jedi_compliance_audit/app/service/ar_import/aggregation.py
   33 jedi_compliance_audit/app/service/ar_import/ao_alignment.py
  107 jedi_compliance_audit/app/service/ar_import/evidence_matcher.py
   35 jedi_compliance_audit/app/service/import_adapter/method_keywords.py  ← 接縫
   28 jedi_compliance_audit/app/service/import_adapter/registry_base.py    ← 接縫
   40 jedi_compliance_audit/domain/entities/ar_xlsx_parse_job_entity.py
   29 jedi_compliance_audit/domain/repository/i_ar_xlsx_parse_job_repo.py
   70 jedi_compliance_audit/domain/service/ar_xlsx_parse_job_domain_service.py
   65 jedi_compliance_audit/infra/repository/ar_xlsx_parse_job_repo_impl.py
   34 jedi_compliance_audit/infra/mapper/ar_xlsx_parse_job_mapper.py
   28 jedi_compliance_audit/infra/model/ar_xlsx_parse_job.py
   53 jedi_compliance_audit/infra/repository/wf_control_mapping_lookup_query.py  ← 🔴 輪次可不帶
主專案側
  116 api/project/routes/ar_import_route.py
```
共 **19 檔／1,585 行**（套件 18／1,469＋主專案 1／116）。

驗證：
```bash
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit && git ls-files -- jedi_compliance_audit/app/service/ar_import_app_service.py jedi_compliance_audit/app/service/ar_report_parser/__init__.py jedi_compliance_audit/app/service/ar_report_parser/airasia_cmmc_l1_ar_v1.py jedi_compliance_audit/app/service/ar_report_parser/base.py jedi_compliance_audit/app/service/ar_report_parser/registry.py jedi_compliance_audit/app/service/ar_framework_profile/cmmc_l1.py jedi_compliance_audit/app/service/ar_import/aggregation.py jedi_compliance_audit/app/service/ar_import/ao_alignment.py jedi_compliance_audit/app/service/ar_import/evidence_matcher.py jedi_compliance_audit/app/service/import_adapter/method_keywords.py jedi_compliance_audit/app/service/import_adapter/registry_base.py jedi_compliance_audit/domain/entities/ar_xlsx_parse_job_entity.py jedi_compliance_audit/domain/repository/i_ar_xlsx_parse_job_repo.py jedi_compliance_audit/domain/service/ar_xlsx_parse_job_domain_service.py jedi_compliance_audit/infra/repository/ar_xlsx_parse_job_repo_impl.py jedi_compliance_audit/infra/mapper/ar_xlsx_parse_job_mapper.py jedi_compliance_audit/infra/model/ar_xlsx_parse_job.py jedi_compliance_audit/infra/repository/wf_control_mapping_lookup_query.py | xargs wc -l | tail -1   # 18 檔、1469
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git ls-files -- api/project/routes/ar_import_route.py | xargs wc -l | tail -1   # 1 檔、116
```

### C5 — 程序書文件池的資料層＋兩支懸案檔

**這一棒要回答**：第 118／119／129 項的根因在這一層（**已知，不重報**）；本棒要找的是**同一個 repo 裡還沒被報過的其他方法**有沒有同樣「只認編號」的形狀，以及 DI 組裝有沒有把文件池接到不該接的地方。

```
套件側
   22 jedi_compliance_audit/domain/entities/ssp_reference_document_entity.py
   31 jedi_compliance_audit/domain/repository/i_ssp_reference_document_repo.py
   46 jedi_compliance_audit/domain/repository/i_ssp_document_pool_query.py
   27 jedi_compliance_audit/domain/service/ssp_reference_document_domain_service.py
   83 jedi_compliance_audit/infra/repository/ssp_reference_document_repo_impl.py   ← 第 118／119 根因
  263 jedi_compliance_audit/infra/repository/ssp_document_pool_query.py          ← 第 129 根因
   27 jedi_compliance_audit/infra/mapper/ssp_reference_document_mapper.py
   43 jedi_compliance_audit/infra/model/ssp_reference_document.py
   40 jedi_compliance_audit/infra/model/ssp_reference_document_mapping.py
主專案側
   29 app/oscal/dto/ssp/ssp_reference_document_dto.py
  509 di_containers/oscal/oscal_containers.py                        ← 懸案檔（O9 只列不掃）
   93 infra/oscal/repository/ssp_catalog_title_query.py              ← 懸案檔（O9 只列不掃）
```
共 **12 檔／1,213 行**（套件 9／582＋主專案 3／631）。
附帶讀：兩張程序書表在 `scripts/init/02-schema.sql` 的建表段（沒有隔離規則，確認用）。

驗證：
```bash
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit && git ls-files -- jedi_compliance_audit/domain/entities/ssp_reference_document_entity.py jedi_compliance_audit/domain/repository/i_ssp_reference_document_repo.py jedi_compliance_audit/domain/repository/i_ssp_document_pool_query.py jedi_compliance_audit/domain/service/ssp_reference_document_domain_service.py jedi_compliance_audit/infra/repository/ssp_reference_document_repo_impl.py jedi_compliance_audit/infra/repository/ssp_document_pool_query.py jedi_compliance_audit/infra/mapper/ssp_reference_document_mapper.py jedi_compliance_audit/infra/model/ssp_reference_document.py jedi_compliance_audit/infra/model/ssp_reference_document_mapping.py | xargs wc -l | tail -1   # 9 檔、582
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git ls-files -- app/oscal/dto/ssp/ssp_reference_document_dto.py di_containers/oscal/oscal_containers.py infra/oscal/repository/ssp_catalog_title_query.py | xargs wc -l | tail -1   # 3 檔、631
```

---

## 3. 每棒重點看什麼

### 每一棒開工前都先做的三件事

**① 算「守門次數」對「對外方法數」**：
```bash
grep -c "require_\|authz\|permission\|Forbidden\|Permission\|assert_project\|_check_role\|_check_auditor\|_check_manager\|_check_participant" <檔>
grep -c "^    def [a-zA-Z]" <檔>
```
守門 0 次不一定有洞（repo 層本來就不該有守門），但要說得出「守門在哪一層」。盤點時先跑了一輪，**服務層與路由層 0 命中的**：

| 檔 | 守門 | 對外方法 | 在哪棒 | 盤點時看到的 |
|---|---:|---:|---|---|
| `app/service/task_setup_service.py` | 🔴 0 | 1 | C1b | 路由只掛登入＋商務授權，往下到 repo 也沒看到角色檢查 |
| `app/service/project_current_ssp_service.py` | 🔴 0 | 1 | C1b | 同上；回傳專案目前 SSP 的編號，那個編號是其他 SSP 端點的鑰匙 |
| `app/readmodel/service/auditor_dashboard_service.py`（主專案） | 0 | 1 | C1b | 查詢條件是「呼叫者本人」（`pp.user_id = :uid`，`uid` 由伺服器端取），預期不是洞，確認即可 |
| `app/service/planning_readiness_checker.py` | 2（都是註解字樣） | 1 | C2b | 不是網址入口，是推進前的軟提醒；兩處「查詢失敗就放行」（`:70`、`:85`）|
| `app/flow_control/service/prep_job_generation_service.py`（主專案） | 0 | 1 | C2b | 被 `audit_round_app_service.launch_reverify` 呼叫，上游有角色檢查；確認沒有別的呼叫端 |

**逐方法看**（盤點時用腳本逐支對過，列出「對外方法裡一行守門都沒有」的）：

| 檔 | 沒守門的對外方法 | 備註 |
|---|---|---|
| `audit_round_app_service.py` | 🔴 `list_rounds`、`get_round`、`list_stage_transitions` | 第三支＝總表第 52 項，前兩支沒人報過 |
| `assessment_result_app_service.py` | `init_ar_matrix_for_round`、`narrowed_control_ids_for`、`narrowed_control_ids_from_parent`、`finalize_assessment` | 四支都是「被別的服務在交易中呼叫」的內部方法（docstring 自陳），確認沒有網址直接打到即可 |
| `assessment_result_app_service.py` | 🔴 `get_findings`、`list_risks` | 只有 `_require_round_ar_result(..., writable=False)`＝只查輪次存不存在 |
| `assessment_result_app_service.py` | `get_ao_counts_by_control` | 同上形狀，但唯一呼叫端是 `ar_import_app_service.py:167`（上游已驗角色），不是網址入口 |
| `poam_app_service.py` | 🔴 `list_items`、`get_item` | 只有 `_require_round_poam(..., writable=False)`＝只查輪次存不存在 |
| `poam_app_service.py` | `all_items_closed` | 內部方法（結案時呼叫） |
| `ar_import_app_service.py` | `upload_and_parse` 本身 0 行 | **但它第一行呼叫 `self._ar.resolve_round_for_import()`，那一支有完整角色檢查**，不是洞 |

**② 找「檢查甲、動乙」的形狀**（第 118／119 項那種）：守門檢查的是網址上的輪次，被改／被刪的卻是另外傳進來的一個編號。看每一支 `delete_*`／`update_*` 裡「拿編號找東西」那一步，有沒有限定在「網址上那一輪」底下找。盤點抽看了 `delete_observation`、`delete_milestone`、`delete_remediation`：**都是先用輪次的 `ar_result_id`／`poam_id` 限定範圍再找**（例如 `_resolve_observation(r.ar_result_id, obs_uid)`），形狀是對的；其餘方法要掃描棒逐支確認。

**③ 每一支讀取類也要問「憑什麼給你看」**：總表第 52 項已經證實「讀取漏守門、寫入有守門」是這一塊的慣性。

### 各棒個別重點

**C1（接線總裝＋審閱）**
- `review_service.py:43` `_require_manager` 開頭寫著「`participant_role_service` 或 `project_domain_service` 任一沒注入就直接 return」——**零件沒裝就跳過檢查**。現在主專案 DI 有裝（`flow_control_containers.py:500-507` 兩個都有傳），但這個寫法與總表 M10 第 8 條「漏裝一條、整支靜默全開」完全同形。要答：①現在是不是每個建 `ReviewService` 的地方都有傳；②`plugin/assembly.py:_assert_wiring` 的「沒接好就拒絕掛載」只檢查三道路由守門，**管不管得到服務層這兩個零件**。
- `flow_control_review_repo_impl.py:25-78` `_resolve_control_ids` 用網址上的專案編號查，**`ap_uid` 參數完全沒用到**（docstring 自陳）。確認審閱標記寫進去的 `project_id` 就是剛剛檢查過角色的那個專案。
- `core/plugins/compliance_audit.py` 三支守門 adapter 有沒有吞例外、有沒有把「查不到角色」轉成放行（FR-095 H1 對 `participant.py` 做過同樣的查核，可參照）。

**C1b（三條只讀網址）**
- 🔴 `task_setup_route.py:27-31`：網址是 `/project/<project_uid>/ap/<ap_uid>/task-setup/tree`，**`project_uid` 收了沒用**，只把 `ap_uid` 往下傳；`flow_control_task_setup_repo_impl.py:247-290` `_resolve_ssp` 依序把這個編號當「專案編號→輪次編號→稽核計畫編號」三種去猜。整條路**沒看到**「你是不是這個專案的人」的檢查。回傳整棵控制項樹＋每項任務的指派人 uid 與暱稱（`:125-141`）。跨客戶靠 `compliance.projects` 的隔離擋（專案表有開），**同客戶跨專案要掃描確認**。
- 🔴 `project_current_ssp_service.py:34-60`：拿網址上的專案編號就回該專案 living SSP 的編號，沒有角色檢查。SSP 編號是其他 SSP 端點的輸入——要確認那些端點各自有守門（FR-113 O1 的 `SspPermissionChecker`），**有的話這支只是洩漏編號、嚴重度低**。
- `project_extension_repo_impl.py:58-88` `get_one_by_fields` 用 `living_ssp_id`／`owner_id` 查專案——查主表 `compliance.projects`（有隔離），確認即可。

**C1c（主專案兩支路由）**
- `assessment_plan_route.py`：三個端點（選單、詳細更新、儀表板）都**只掛登入＋商務授權、沒有能力點**。選單那支已是 FR-095 H1-3（總表第 66 項），**不重報**；要看的是另外兩支——`AssessmentPlanDetailResource.put`（`:55-75`，會改資料）與 `ApDashboardResource.get`（`:105-116`，FR-095 H1 報告順帶提到「也不驗 `ap_uid` 屬不屬於 `project_uid`」，但沒列成獨立發現）。
- `project_route.py`：`MyAssignedProjectsResource`（`:71-81`）沒掛能力點，但查的是「呼叫者本人被指派的專案」，預期不是洞，確認即可；其餘端點都掛了 `project.read/update/delete`。
- `api/serializers/project.py:41-67` 清單查詢的篩選條件**全部選填**（見第 6 節），送空條件會回什麼範圍要確認。

**C2a（輪次主體）🔴**
- 🔴 `audit_round_app_service.py:211-223` `list_rounds`、`get_round`：**0 行守門**。路由 `audit_round_route.py:69-87` 只掛 `@jwt_required()`＋`@require_license("audit")`。`project_audit_rounds` 表 DEV 已開隔離（EXISTS 繞專案表），**跨客戶應該被擋、同客戶跨專案不會**——掃描要實證這個判斷。總表沒有這兩支。
- 同服務其他 9 支寫入都有 `_check_role`，但**角色檢查用的是「從輪次查出來的專案」**（`e.project_id`），不是網址上的專案——這是對的做法，確認每支都是。
- `create_round` 用網址上的 `project_uid` 解析專案再查角色（`:226-230`），對的；確認 `name`／`round_type` 沒有別的注入面。
- `002-compliance-audit-rls.sql`：輪次表的隔離規則是「看得到專案才看得到輪次」。確認出貨版有套上（`scripts/sql/packages/manifest.tsv:16` 有登記，但 `02-schema.sql` 靜態快照**顯示輪次表沒開隔離**——見第 5 節差異說明）。

**C2b（歷程、回退、規劃檢查）**
- `round_stage_transition_repo_impl.py:29-33`、`round_rollback_supersession_repo_impl.py:48-52` 的 `list_by_round(round_id)` 只用數字輪次編號過濾，兩張表**隔離全關**。唯一讀歷程的呼叫端是 `list_stage_transitions`（＝第 52 項，已知）；回退標記目前**只有寫入呼叫端**（`:901` `_mark_superseded`），讀取的 `list_by_round`／`list_by_entity_ids`／`is_superseded` 盤點時沒找到呼叫者——確認是否死碼。
- `planning_readiness_checker.py:67-78`、`:82-90`：兩處查詢失敗就「不警告、放行」（註解自陳 fail-open）。它只是軟提醒、使用者本來就可以按確認放行，**預期不是安全問題**，但要確認不會被當成「權限檢查」用。
- `prep_job_generation_service.py`：覆核輪批次建立證據蒐集任務，確認建出來的任務歸屬（專案、輪次）是由伺服器端決定。

**C3（稽核結果與改善計畫）🔴**
- 🔴 `assessment_result_app_service.py:392`（`get_findings`）、`:695`（`list_risks`）與 `poam_app_service.py:246`（`list_items`）、`:271`（`get_item`）：**讀取只查輪次存不存在**。回傳的是整張稽核判定矩陣（每個控制項合不合格、理由）、系統風險、改善計畫與負責人——這一塊最敏感的內容。底下的 `oscal.assessment_*`／`oscal.poam*` 八張表 **DEV 隔離全關**，跨客戶沒有資料庫這層擋。**總表沒有這幾支**。
- 所有寫入：角色檢查（`_check_auditor`／`_check_manager`）用的專案是「從輪次查出來的」，對的；再逐支確認「拿 uid 找東西」都有限定在該輪底下（第 3 節②）。
- `judge_finding`、`link_risk_findings` 收的是 finding／risk 編號清單——確認每一個編號都驗證屬於本輪。
- `common/round_guard.py` 只管「過了階段就唯讀」，**不是權限檢查**；確認沒有地方把它當成權限檢查用。

**C4a／C4b（兩條匯入）**
- 兩支服務的 `_require_job`（`ap_docx_import_app_service.py:251`、`ar_import_app_service.py:569`）有檢查「解析工作是不是你們公司的」（`job.tenant_id != user_context.tenant_id`），而且每一步都**再**用解析工作上記的輪次／計畫編號重查角色——形狀是對的。確認 `user_context.is_admin` 放行的是平台管理員、不是租戶管理員。
- 🔴 C4b：`wf_control_mapping_lookup_query.py:30-53` `get_wf_ids_by_control_ids` 的 `round_id` 可以不帶，不帶就只用控制項代號查全表；那張表 **隔離關、沒有客戶欄位**，控制項代號（如 `AC-2`）又是所有客戶共用的字串。盤點時追到唯一呼叫端 `ar_import_app_service.py:175-177` **有帶 `round_id`**（從已驗證過的輪次取），`round_id` 只有在輪次物件本身是 None 時才會是 None——掃描要確認這條路徑到不到得了。
- C4b：`_build_evidence_pool`（`:341`）、`_get_allowed_wf_ids_by_control`（`:424`）兩處「查詢失敗就回空、不擋解析」（註解自陳 best-effort）。確認失敗時是「少給證據」而不是「給全部證據」。
- 解析器吃使用者上傳的檔（Word／Excel）：檔案大小上限、壓縮炸彈、公式注入（總表已有 B6 `generator.py` 公式注入的先例）。

**C5（文件池資料層）**
- **第 118／119／129 項的根因就在這兩支 repo，已知，不重報**：`ssp_reference_document_repo_impl.py:43-75`（`get_by_uid`／`update`／`delete_by_uid` 只用編號）、`ssp_document_pool_query.py:199-209`（`resolve_doc_uids_to_ids` 只用編號）。
- 要找的是**同檔還沒被報過的**：`add_mappings`（`:144-183`）直接用呼叫端給的 `doc_ids` 建關聯、不驗文件屬於哪份 SSP——它的輸入正是第 129 項那支 `resolve_doc_uids_to_ids` 的輸出，**是同一個洞的後半段、不另計**；`list_mappings`／`remove_mapping`／`get_mapping_doc_names` 只用 `context_type + context_id` 查，確認 `context_id` 都由伺服器端解析。
- `di_containers/oscal/oscal_containers.py`：文件池的 DI 組裝（`:102-106` import、與 `module_frame_containers.py` 各建一份）。確認兩邊建出來的是同一套、沒有一邊少注入守門零件（FR-113 O9b 證實過「DI 漏接讓守門靜默失效」）。
- `ssp_catalog_title_query.py`：控制項標題查詢，唯讀、被 SSP 匯入匯出用。確認查詢有帶 SSP／catalog 範圍，不是全庫撈。

---

## 4. repo 方法範圍條件對照表

「範圍條件」＝除了編號之外，查詢有沒有再加上「而且要屬於某份計畫／某個專案／某家客戶」。只列會讀或改單筆、多筆資料的方法；寫死回空值的 stub 另列。

### 4.1 🔴 只有編號、沒有範圍（第 118／119／129 項同一形狀）

| 方法 | 位置 | 查詢條件 | 表的隔離 | 狀態 |
|---|---|---|---|---|
| `SspReferenceDocumentRepoImpl.delete_by_uid` | `ssp_reference_document_repo_impl.py:66-76` | 只有 `uid` | 🔴 關 | **第 118／119 項根因，重現、不另計** |
| `SspReferenceDocumentRepoImpl.update` | 同檔 `:51-64` | 只有 `uid` | 🔴 關 | 同上形狀；呼叫端在已掃的 O6／B2 範圍，**重現、不另計**（C5 確認有沒有第三個呼叫端） |
| `SspReferenceDocumentRepoImpl.get_by_uid` | 同檔 `:43-49` | 只有 `uid` | 🔴 關 | 同上 |
| `SspDocumentPoolQuery.resolve_doc_uids_to_ids` | `ssp_document_pool_query.py:199-209` | 只有 `uid IN (...)` | 🔴 關 | **第 129 項根因，重現、不另計** |
| `SspDocumentPoolQuery.add_mappings` | 同檔 `:144-183` | 不驗 `doc_ids` 屬於誰 | 🔴 關 | 第 129 項後半段，不另計 |
| `ProjectAuditRoundRepoImpl.get_by_uid` | `project_audit_round_repo_impl.py:29-31` | 只有 `uid` | 開（繞專案表） | 🟡 C2a／C3：多支讀取只靠它＝「輪次查得到就放行」。跨客戶有隔離擋，同客戶跨專案沒擋 |
| `RoundStageTransitionRepoImpl.list_by_round` | `round_stage_transition_repo_impl.py:29-33` | 只有 `round_id`（數字） | 🔴 關 | 呼叫端＝第 52 項，已知 |
| `RoundRollbackSupersessionRepoImpl.list_by_round`／`list_by_entity_ids` | `round_rollback_supersession_repo_impl.py:35-52` | 只有 `round_id`／`entity_type+ids` | 🔴 關 | 🟡 盤點時沒找到讀取呼叫端，C2b 確認是否死碼 |
| `WfControlMappingLookupQuery.get_wf_ids_by_control_ids` | `wf_control_mapping_lookup_query.py:30-53` | 控制項代號；`round_id` 可不帶 | 🔴 關 | 🟡 唯一呼叫端有帶 `round_id`，C4b 確認不帶的路徑到不到得了 |
| `ArXlsxParseJobRepoImpl.get_completed_by_source` | `ar_xlsx_parse_job_repo_impl.py:56-65` | 只有 `source_uid`（輪次編號） | 開（有客戶欄） | 跨客戶被隔離擋；呼叫端先驗過角色，預期不是洞 |
| `Ap/ArParseJobRepoImpl.update`／`deactivate` | `ap_docx_parse_job_repo_impl.py:31-54`、`ar_…:31-54` | 只有 `id`／`uid` | 開（有客戶欄） | 呼叫端 `_require_job` 先驗客戶，預期不是洞 |
| `PoamRepoImpl.update_poam` | `poam_repo_impl.py:55-66` | 只有 `uid` | 開 | ⚪ **死碼**（見 1.4），不成立 |

### 4.2 有範圍條件

| 方法 | 位置 | 範圍 |
|---|---|---|
| `FlowControlReviewRepoImpl.add_review`／`remove_review`／`_get_reviewers` | `flow_control_review_repo_impl.py:104-200` | `project_id`（由網址專案編號解析）＋控制項＋使用者 |
| `FlowControlReviewRepoImpl.clear_marks_for_controls` | 同檔 `:202-238` | `project_id`＋`catalog_id` |
| `ProjectAuditRoundRepoImpl.list_by_project`／`max_round_no` | `project_audit_round_repo_impl.py:63-77` | `project_id` |
| `ProjectAuditRoundRepoImpl.get_by_assessment_plan_id`／`get_by_ssp_id` | 同檔 `:33-61` | 只有計畫／SSP 數字編號（內部呼叫，數字由伺服器端取得） |
| `ProjectExtensionRepoImpl.*` | `project_extension_repo_impl.py:58-127` | 查專案主表（有隔離） |
| `SspDocumentPoolQuery.list_pool`／`clone_pool_docs`／`get_pool_name_to_uid_map` | `ssp_document_pool_query.py:25-110`、`:211-233` | `ssp_id` |
| `SspDocumentPoolQuery.list_mappings`／`remove_mapping`／`get_mapping_doc_names` | 同檔 `:112-142`、`:185-197`、`:235-253` | `context_type`＋`context_id`（C5 確認 `context_id` 來源） |
| `FlowControlTaskSetupRepoImpl._resolve_ssp` | `flow_control_task_setup_repo_impl.py:247-290` | 用編號猜三種身分；查專案表（有隔離）＋輪次表（有隔離）＋`oscal.assessment_plans` |

### 4.3 寫死回空值的 stub（不是範圍問題，但回空不代表查無資料）

`flow_control_audit_repo_impl.py`（13 個方法）、`flow_control_control_repo_impl.py`（3）、`flow_control_control_group_repo_impl.py`（2）、`flow_control_assessment_object_repo_impl.py`（1）、`oscal_audit_query.py`（7）、`poam_repo_impl.py` 的 `list_poams`／`get_poam`、`ssp_document_pool_query.py` 的 `get_ap_task_id_by_uid`／`get_ap_control_id_by_identifier`。**全部在死碼區或沒有呼叫者**。

**標紅統計**：4.1 表 12 列中，**真正「只有編號、底下表又沒隔離、呼叫端也沒補」而且還活著的，都是第 118／119／129 項已知的那 5 支**；新懷疑點 4 支（輪次 `get_by_uid` 被讀取類依賴、回退標記讀取、`round_id` 可不帶、parse job 兩支）待掃描確認；死碼 1 支不成立。

---

## 5. 資料庫隔離對照表

對照來源：①出貨版 `scripts/init/02-schema.sql`（靜態快照）；②DEV 實查（唯讀 SELECT，**2026-09-23 21:06**，`cm_app` 帳號，本棒重查、與前一棒 20:15 的結果一致）；③套件自帶 migration `002-compliance-audit-rls.sql`。

### 5.1 套件自己的資料表

| 表 | 客戶欄位 | 出貨 02-schema | DEV | 誰的 | 白話 |
|---|---|---|---|---|---|
| `compliance.project_audit_rounds`（稽核輪次） | 無 | ⚠️ 關（0 條） | 開（4 條，繞專案表） | 套件 002 | 看得到專案才看得到輪次 |
| `compliance.review_marks`（審閱標記） | 無 | ⚠️ 關（0 條） | 開（4 條，繞專案表） | 套件 002 | 同上 |
| `compliance.poams`（舊改善計畫表） | 有 | 開（4 條） | 開（4 條） | | 死碼區在用的舊表 |
| `oscal.ap_docx_parse_jobs`（Word 解析工作） | 有 | 開（1 條） | 開（1 條） | | |
| `oscal.ar_xlsx_parse_jobs`（Excel 解析工作） | 有 | 開（1 條） | 開（1 條） | | |
| 🔴 `compliance.round_stage_transitions`（階段歷程） | **無** | **關** | **關** | | 操作者帳號、退回理由全文 |
| 🔴 `compliance.round_rollback_supersessions`（回退標記） | **無** | **關** | **關** | | |
| 🔴 `oscal.ssp_reference_documents`（程序書） | **無** | **關** | **關** | | 第 118／119 項的表 |
| 🔴 `oscal.ssp_reference_document_mappings`（程序書掛在哪） | **無** | **關** | **關** | | 第 129 項的表 |

### 5.2 套件讀寫、但不是它建的表

| 表 | 客戶欄位 | DEV 隔離 | 誰在用 |
|---|---|---|---|
| `compliance.projects` | 有 | 開（4 條） | 幾乎所有路徑的歸屬根源 |
| 🔴 `public.workflow_execution_control_mapping` | 無（有 `round_id`，可為 NULL） | **關** | C4b 證據配對 |
| 🔴 `oscal.assessment_results`／`assessment_findings`／`assessment_observations`／`assessment_risks`／`assessment_remediations` | — | **關**（DEV 21:21 實查） | C3 稽核結果的實際存放處 |
| 🔴 `oscal.poams`／`poam_items`／`poam_milestones` | — | **關**（DEV 21:21 實查） | C3 改善計畫的實際存放處 |

（`oscal.*` 那批表屬 jedi-oscal-v2，總表已登記「整個 oscal 區只有五張工作紀錄表開了隔離」，不重報；列在這裡是為了讓 C3 研究員知道**讀取漏守門時沒有第二道防線**。）

### 5.3 白話結論

- **9 張表裡有 4 張完全沒有防線**（沒有客戶欄位、沒開隔離、沒有規則）：歷程、回退標記、程序書、程序書掛載。程式層只要漏一處歸屬檢查，資料就一路通到底——**第 118／119／129／52 項就是這樣發生的**。
- 稽核結果與改善計畫本體（`oscal.assessment_*`／`oscal.poam*`）也全部沒開隔離。所以 C3 那四支「只查輪次存不存在」的讀取，如果成立，**跨客戶也擋不住**（只要拿得到輪次編號）。
- ⚠️ **出貨版與 DEV 不一致**：輪次與審閱標記兩張表，DEV 已開隔離，但 `02-schema.sql` 靜態快照裡是關的。原因是隔離規則在套件 migration `002`（CM-1800，2026-09-14），出貨時由 `flatten_pkg_migrations.sh` 攤平進安裝包、登記在 `scripts/sql/packages/manifest.tsv:16`，**`02-schema.sql` 本身沒重產**。新客戶裝機會不會套到 002，取決於安裝腳本有沒有跑那份攤平清單——**這是出貨基線的問題、不是本次掃描的範圍**，列給首腦知道（見第 7 節）。

---

## 6. 查詢條件全部選填的請求格式（「空白查詢回全表」形狀）

| 請求格式 | 位置 | 選填欄位 | 對應網址 | 盤點判斷 |
|---|---|---|---|---|
| `AuditorDashboardRequestSchema`＋`FiltersSchema` | 套件 `api/serializers/auditor_dashboard.py:5-18` | `status`、`keyword` 全選填 | `POST /grc/audits/my/list` | 查詢一律帶「呼叫者本人」（伺服器端取），空條件＝回自己的全部，**預期不是洞** |
| `ProjectListRequestSchema`＋`FiltersSchema` | 套件 `api/serializers/project.py:41-67` | 全部篩選選填 | `POST /grc/projects/list`（C1c） | 背後 `project_service.list_projects` 已在 FR-095 H1 掃過；C1c 只確認路由層有掛 `project.read` |

主專案 `api/project/serializers/audit_round.py` 的請求格式都是**寫入用**、關鍵欄位都有 `required=True`，沒有查詢型的全選填格式。

---

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

1. **死碼區 39 支／2,781 行要不要交給 FR-092 刪**：DI 登記還在，哪天被接回路由就是零守門上線（見 1.4）。這是清理建議、不是資安發現，要不要開卡由首腦決定。
2. **`api/flow_control/__init__.py` 與 FR-095 H2（CM-1783，尚未派）重疊**：本盤點放進 C1 當接縫檔。若 H2 先派，C1 的研究員讀但不報；若 C1 先派，H2 那邊比照。要不要乾脆從其中一邊拿掉，由首腦決定。
3. **出貨 `02-schema.sql` 與 DEV 的隔離狀態不一致**（5.3 最後一點）：這是出貨基線問題，不歸本批掃描。要不要另外查「新裝客戶到底有沒有套到套件 migration 002」，由首腦決定。
4. **派工順序建議**：C3 → C2a → C1b（三個 🔴 讀取疑點最集中、底下的表沒有隔離）→ C5 → C1 → C4b → C2b → C4a → C1c。C2a 與 C2b 共用 925 行的接縫檔，**同一個 runner 連做**比較省；C4a／C4b 同理。

---

## 附：與交接說明的差異

- 套件數字：卡片「176 支／13,865 行」→ 實數 **169 支／13,045 行**（卡片含 tests）。
- `api/flow_control/routes/project_route.py`：交接列為「FR-088 H3 已掃」→ 查證只是**被引用當範例**，改判沒查過，排進 C1c。
- `poam_service.py`／`audit_service.py`：交接列為「待確認可達性」→ **確認打不到**（1.4），不成立為發現。
- `task_setup_service.py`：交接說「只讀了一半」→ 本棒讀完 `_resolve_ssp` 與完整呼叫鏈，**沒看到任何角色比對**，列為 C1b 頭號疑點。
- `get_wf_ids_by_control_ids` 的 `round_id` 可不帶：交接列為「要查哪些呼叫端會傳 None」→ 唯一呼叫端有帶，只有輪次物件本身是 None 時才會不帶，列 C4b 確認。
- 切棒：交接 C1～C5 六棒（C2 超標 2,454 行）→ 本棒改成 **9 棒**：C2 拆成 C2a／C2b，C1 拆成 C1／C1b／C1c（交接版 C1 的 27 檔也要重新對齊，因為補入了反查的 2 支主專案檔與 `project_route.py`）。
