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 套件側

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 重跑確認數字不變):

# ① 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
# 套件側:預期 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)。

驗證:

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)。

驗證:

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)。

驗證:

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 行,輪次表的隔離規則)。

驗證:

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)。

驗證:

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)。

驗證:

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)。

驗證:

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)。

驗證:

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 的建表段(沒有隔離規則,確認用)。

驗證:

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. 每棒重點看什麼

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

① 算「守門次數」對「對外方法數」:

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)。