FR-104 · CM-1823 · 實查日期 2026-09-16 · HEAD 98d756bb

114 個檔,哪些現在就能清

主專案裡管「流程」的程式分散在兩個地方——flow_engine(流程引擎)與 flow_control(稽核流程控管)。這份文件把兩包 114 個檔逐支查過,判定每一支該歸誰,每一筆判定都附依賴證據。不動任何程式碼;要回答的問題是「哪些現在就能清、哪些要等設計、哪些本來就該留著」。

實查基準 98d756bb 114 檔/14350 行 立即可做 2 項 兩項待裁已裁 2026-09-16
§1

30 秒版

  1. 地貌已經跟半個月前完全不同。 FR-069 第四階段把稽核與任務平台的主體抽成兩個套件(jedi-compliance-audit/jedi-task-platform),主專案這兩包從 226 檔縮到 114 檔。舊設計稿(2026-08-31)的每個數字都已失效,本文取代它。
  2. 判準是依賴方向,不是檔名。 每一支看「它 import 了誰、被誰 import」,判給五類之一:引擎/平台/稽核/主程式編排/還看不準。
  3. 結果:引擎 57 支、平台 19 支、稽核 15 支、主程式編排 12 支、還看不準 0 支,另有 11 支空檔(__init__.py,零行)。
  4. 現在就能清的有兩項(§5),都是低風險、不牽其他模組的。首腦初判的兩支裡,flow_template_app_service 成立,workflow_template_snapshot_service 推翻——它的唯一消費者是稽核套件,決策者 2026-09-16 裁歸稽核套件(§7)。
  5. 要設計才能動的有四項(§6),最大的一塊是 workflow_execution_service(1,321 行)。
  6. 端點一條都不能少。 30 條對外端點(19+11)四層查過,沒有一條是死的。

引用本文的數字前請先重驗。 舊稿失效的教訓正是「它自己就是為了更正上一份的數字而做的實查,半個月後也過期了」。本文所有數字的產生指令列在 §1 末,重跑一次再引用。

本文有兩節刻意帶時序敘述(其餘各節只寫現況):§3「與舊判定的差異」與 §4「已裁示事項現況」——比對舊判定與標註裁示落點正是本文的核心用途,去掉就無從驗收。§1 有兩處端點註明「已註解退役(卡號)」,那是現況陳述加可查證出處。


§2

① 現況盤點

這節在講什麼:兩包現在有多大、對外開了哪些門、誰在走那些門。

實查基準

項目 值
實查日期 2026-09-16
HEAD commit 98d756bb
branch feature/review
工作目錄狀態 clean(git status 無異動)

八層規模

層 檔數 行數
api/flow_engine 17 1,320
app/flow_engine 16 3,041
domain/flow_engine 27 852
infra/flow_engine 20 1,118
flow_engine 小計 80 6,331
api/flow_control 7 1,151
app/flow_control 17 3,995
domain/flow_control 0 0
infra/flow_control 10 2,873
flow_control 小計 34 8,019
合計 114 14,350

domain/flow_control 是空目錄——該層的實體與領域服務已全數在 jedi-compliance-audit 與 jedi-task-platform 兩個套件內。

對外端點 30 條

數端點必須用語法解析,不能用字串比對。 api.add_resource( 常寫成跨行,用 grep -c 會漏抓。本文的數字來自 re.finditer(r'^ api\.add_resource\(', s, re.M) 對兩支 __init__.py 的掃描。

api/flow_engine:19 條啟用,1 條已註解退役

# 路徑 吃哪支 service 前端誰在用
1 GET/PUT /flow-engine/process/comments/<id> workflow_execution_service ProcessDiscussBox.vue
2 POST /flow-engine/task/complete/<id> 同上 JobExecutionDrawer.vue
3 POST /flow-engine/task/revert/<id> 同上 同上
4 GET /flow-engine/process-definition/<uid> 同上 WorkflowSetupEditor.vue
5 GET /flow-engine/task/queue app/readmodel/my_jobs_app_service utils/userUtil.js
6 POST /job-evidences job_evidence_service JobExecutionDrawer.vue/RoundAuditReviewView.vue
7 POST /job-evidence 同上 同上
8 GET/PUT/DELETE /job-evidence/<uid> 同上 同上
9 GET /flow-engine/stage-objects stage_object_app_service(16 行薄殼) FlowTemplateService.js
10–15 /flow-engine/flow-templates(list/create/validate/detail/duplicate/publish/unpublish) flow_template_app_service FlowTemplateService.js
16 GET /project/<p>/audit-round/<r>/stage/info stage_advance_service StageService.js/FlowPhaseBanner.vue
17 POST .../stage/advance 同上 同上
18 POST .../stage/rollback stage_rollback_service 同上
19 GET .../stage/transitions 同上 同上
— PUT /job-execution/task/link — 已註解退役(CM-1498,兩張 mapping 表已 DROP)

api/flow_control:11 條啟用,1 條已註解退役

# 路徑 吃哪支 service 前端誰在用
1 GET /grc/jobs/my/projects/menu project_service MyTasksView.vue
2 POST /grc/projects/list 同上 ProjectListView.vue/DashboardView.vue/ProjectSummaryReportManage.vue
3 POST /grc/projects/batch-delete 同上 ProjectListView.vue
4 GET/PUT/DELETE /grc/project/<uid> 同上 9 個頁面/元件
5 GET /grc/project/<p>/assessment-plans/menu assessment_plan_app_service ImportDocxPage.vue/ProjectAuditorOverview.vue
6 GET/PUT /grc/project/<p>/ap/<ap> 同上 ProjectAuditorOverview.vue
7 GET /grc/project/<p>/ap/<ap>/dashboard 同上 同上
8 POST .../control-group/<g>/control/<c>/assessment-object/<ao>/jobs/list job_service TaskSetupView.vue
9 GET/PUT/DELETE /grc/project/<p>/job/<j> 同上 JobExecutionDrawer.vue/TaskSetupView.vue/RoundAuditReviewView.vue
10 POST /grc/project/<p>/assessment-object/<ao>/jobs 同上 TaskSetupView.vue
11 POST /grc/jobs/my/list 同上 MyTasksView.vue/DashboardView.vue
— GET /grc/projects/menu — 已註解退役(FR-038 dark stub)

30 條全部是活的,四層查法逐條核過:

  1. 前端常數(src/config/api/api.js):8 個 flow-engine 常數+5 個 GRC 常數,每個都至少一個 .vue/.js 檔在用。
  2. 前端路由(src/config/router/index.js):對應頁面皆掛在路由上。
  3. 資料庫選單(public.ui_routes,DEV 唯讀查詢):flow-template-manage/project-list/my-tasks/project-dashboard/audit-manage 皆 enable=1。
  4. 非瀏覽器呼叫者:① common/middleware/license_readonly_mw.py:116 的唯讀租戶白名單收了 /flow-engine/flow-templates/validate——拔掉這條端點會讓唯讀租戶的流程範本驗證靜默失效;② E2E(compliance-manager-test/site-regression/)有 23 條路徑在打,含 stage/advance/stage/rollback/stage/transitions/flow-templates 全鏈。

怎麼重跑

# 八層檔數與行數
for d in api/flow_engine app/flow_engine domain/flow_engine infra/flow_engine \
         api/flow_control app/flow_control domain/flow_control infra/flow_control; do
  n=$(find $d -name '*.py' -not -path '*__pycache__*' 2>/dev/null | wc -l)
  l=$(find $d -name '*.py' -not -path '*__pycache__*' -print0 2>/dev/null | xargs -0 cat | wc -l)
  echo "$d  $n 檔    $l 行"
done

# 端點數(必須用語法解析,不可用 grep -c)
python3 -c "
import re
for f in ['api/flow_engine/__init__.py','api/flow_control/__init__.py']:
    s=open(f).read()
    print(f, 'active:', len(re.findall(r'^    api\.add_resource\(', s, re.M)),
             'disabled:', len(re.findall(r'^\s*#\s*api\.add_resource\(', s, re.M)))
"

# 某支檔的依賴(正向)
grep -n '^from \|^import ' app/flow_engine/service/stage_advance_service.py

# 某支檔被誰引用(反向;排除自己那兩包與快取)
grep -rn 'app.flow_engine.service.stage_advance_service' \
  --exclude-dir=.venv --exclude-dir=__pycache__ --exclude-dir=.git .

# 資料庫選單(DEV,唯讀)
psql -h localhost -p 5432 -U cmmgr -d guidant_ai_dev -At -F' | ' \
  -c "SELECT name, url, enable FROM public.ui_routes WHERE url ILIKE '%flow%' OR url ILIKE '%project%' OR url ILIKE '%task%' ORDER BY url;"

# 軟參照掃描(import 掃不到的稽核關聯;見 §2「domain 層」)
for d in api/flow_engine app/flow_engine domain/flow_engine infra/flow_engine \
         api/flow_control app/flow_control infra/flow_control; do
  find $d -name '*.py' -not -path '*__pycache__*'
done | while read f; do
  n=$(grep -vE "^\s*(from|import) " "$f" | grep -ciE "audit_round|project_audit_rounds|poam|assessment_plan|round_id|ap_task")
  [ "$n" -gt 0 ] && echo "$n    $f"
done | sort -rn

§3

② 逐檔歸屬判定

這節在講什麼:114 支逐一判給五類之一,每一筆附依賴證據。

判準

看依賴方向,不看檔名也不看直覺:

類別 什麼算這一類
🟦 引擎 只依賴 jedi-flow-engine 與自家 */flow_engine/,不碰業務語意
🟩 平台 依賴 jedi-task-platform(任務/專案/指派),與稽核無關
🔴 稽核 依賴 jedi-compliance-audit(輪次/AP/POA&M),或實作稽核專屬規則
🟨 主程式編排 同時依賴三個以上模組、負責把它們接起來——這種本來就該留在主程式
🟡 還看不準 依賴方向與語意方向不一致,兩種判法都說得通

結果總覽

57🟦 引擎/2,619 行

19🟩 平台/4,643 行

15🔴 稽核/2,903 行

12🟨 主程式編排/4,188 行

0🟡 還看不準/0 行

另有 11 支 __init__.py 空檔(合計 7 行,皆為 api//app//infra/ 各層的套件宣告檔),不參與判定。五類相加 103 + 空檔 11 = 114。

行數分布與檔數分布方向相反,這是重點。 引擎檔數最多(57 支)但行數最少(2,619 行)——那 57 支多半是 domain//infra/ 的小檔(entity、mapper、repo impl,平均 46 行)。行數集中在平台與主程式編排(合計 8,831 行,佔 62%),而那正是 §6「要設計才能動」的所在。換句話說:好判的都是小檔,大檔都卡著。

app/flow_engine 六支 service

這六支是整份判定的核心,逐支列出完整證據:

檔 行 判定 import 了誰 被誰 import
workflow_execution_service.py 1,321 🟨 主程式編排 jedi_flow_engine(17 處)/jedi_task_platform(participant 4 支+project)/jedi_survey/jedi_iam/app.associations/app.notification/domain.module_frame/common.authz.workflow di_containers/flow_engine/workflow_excution_containers.py、app/module_frame/service/module_frame_service.py、1 支測試
stage_advance_service.py 702 🔴 稽核 jedi_compliance_audit.RoundStageTransitionEntity(直接 import)/jedi_flow_engine/jedi_task_platform/domain.flow_engine.stage_completion_registry di_containers/flow_control/flow_control_containers.py:667、1 支測試
stage_rollback_service.py 189 🔴 稽核 jedi_flow_engine/jedi_task_platform/common.enum.participant_enum di_containers/flow_control/flow_control_containers.py:367、1 支測試
flow_template_app_service.py 265 🟦 引擎 只有 jedi_common/jedi_flow_engine/common.authz/common.util.audit_nickname/自家 domain.flow_engine di_containers/flow_engine/flow_template_containers.py、1 支測試
job_evidence_service.py 181 🟩 平台 jedi_file_upload/jedi_flow_engine/jedi_survey/自家 domain.flow_engine di_containers/flow_engine/job_evidence_containers.py
workflow_template_snapshot_service.py 94 🔴 稽核(決策者 2026-09-16 裁) 只有 jedi_flow_engine(兩支) di_containers/flow_engine/workflow_excution_containers.py:124;唯一的業務消費者是 jedi_compliance_audit.audit_round_app_service(:306/:308/:321)

stage_rollback_service 的稽核依賴沒有消失,只是換了形式。

派工卡上寫「實查其依賴只剩 jedi-common/jedi-flow-engine/jedi-task-platform,原判證據失效」——那是只看 import 行得到的結論。逐處追下去,稽核依賴還在,只是改由兩條路走:

  1. DI 注入:flow_control_containers.py:370 把 audit_round_domain_service=project_audit_round_domain_service 注進去,service 內用 self._audit_round_domain_service.get_by_uid(round_uid) 取輪次(stage_rollback_service.py:90)。
  2. route 層 import:api/flow_engine/routes/stage_rollback_route.py:19 直接 from jedi_compliance_audit.app.service.audit_round_app_service import AuditRoundAppService。

原判(歸稽核)成立,只是證據要換成上面兩條。這正是「看 import 行不夠」的實例——依賴注入會讓靜態 import 看起來很乾淨。

app/flow_control 17 支

檔 行 判定 證據
project_service.py 789 🟨 主程式編排 同時依賴 jedi_compliance_audit/jedi_task_platform/jedi_iam/app.notification/domain.associations/domain.oscal——六個來源;get_project() 拼 participants/SSP inventory/owner 暱稱
assessment_plan_app_service.py 742 🔴 稽核 jedi_oscal_v2/jedi_task_platform/app.module_frame;處理的是評估計畫(AP)=稽核概念
job_import_service.py 470 🟩 平台 jedi_task_platform/jedi_iam/自家 infra 查詢;做的是任務批次匯入
job_service.py 403 🟩 平台 jedi_task_platform/jedi_detection/jedi_survey/jedi_iam/app.detection_tools;型別專屬邏輯已切成 handler mixin
prep_job_generation_service.py 217 🔴 稽核 jedi_compliance_audit.ao_derivation.AO_PART_NAME/jedi_oscal_v2(兩支 catalog repo);做的是 per-AO 準備期任務生成
job_handlers/survey_handler.py 196 🟩 平台 jedi_survey/jedi_common;問卷型別的任務處理
job_batch_complete_service.py 193 🟩 平台 jedi_task_platform/infra.readmodel/app.flow_engine.workflow_execution_service
job_binding_orphan_cleanup_service.py 174 🟩 平台 只有 jedi_common+自家 infra.flow_control.job_binding_orphan_query
oscal_stage_handlers.py 142 🔴 稽核 只 import domain.flow_engine.stage_completion_registry(實作引擎定義的介面);內容是稽核輪次各階段的完成處理
oscal_stage_preconditions.py 129 🔴 稽核 同上;內容是「POA&M 要全部關閉才能推進」這類稽核前置條件
oscal_stage_rollback_handlers.py 71 🔴 稽核 同上(IStageRollbackHandler)
reverify_inheritance_service.py 73 🔴 稽核 infra.flow_control.reverify_clone_query;覆核輪承襲=稽核概念
dto/job_dto.py 360 🟩 平台 jedi_task_platform/jedi_detection;稽核殘留只剩 3 行(:351–:353 的 ao_code/ao_name/ao_id 讀 row.ap_task_*)
job_handlers/__init__.py 36 🟩 平台 兩支 mixin 的匯出點
__init__.py/dto/__init__.py/service/__init__.py 0 — 空檔

三支 oscal_stage_* 是「介面在引擎、實作在稽核」的正確形狀。

它們只 import domain/flow_engine/service/stage_completion_registry.py 的抽象介面,反過來由 DI container 在啟動時註冊進 registry(flow_control_containers.py:684–:689)。引擎不需要認識稽核,稽核來實作引擎的介面——這是舊稿說的「引擎只留通用的階段推進機制抽象」已經兌現的部分。

api 層 24 支

route 與 serializer 跟著它們呼叫的 service 走:

檔群 檔數 行 判定
api/flow_engine/routes/flow_template_route.py+stage_object_route.py+對應 serializer 4 支 6 274 🟦 引擎
api/flow_engine/routes/job_evidence_route.py+serializer 2 197 🟩 平台
api/flow_engine/routes/flow_engine_route.py+serializer 3 支 4 499 🟨 主程式編排(吃 workflow_execution_service)
api/flow_engine/routes/stage_advance_route.py+stage_rollback_route.py+serializer 2 支 4 251 🔴 稽核(stage_rollback_route.py:19 直接 import AuditRoundAppService)
api/flow_engine/__init__.py 1 102 🟨 主程式編排(blueprint 組裝)
api/flow_control/routes/project_route.py+job_route.py+serializers/job.py 3 912 🟩 平台
api/flow_control/routes/assessment_plan_route.py 1 117 🔴 稽核(import jedi_compliance_audit)
api/flow_control/__init__.py 1 122 🟨 主程式編排(blueprint 組裝+兩支套件 attach)

api/flow_control/__init__.py 值得單獨說明:它建 blueprint、定 URL 前綴,然後讓 jedi-task-platform 與 jedi-compliance-audit 各自 attach 自己那半的 route。完整的 URL 表要看三份(本檔+兩支套件的 routing),這是刻意的設計,不是遺漏。

domain/flow_engine 27 支

全部 🟦 引擎。 這一層乾淨到不需要逐支列表——27 支的 import 只有三種來源:jedi_common(基礎設施)、jedi_flow_engine/jedi_task_platform(套件型別)、自家 domain/flow_engine。零稽核 import。

兩處要點名:

  1. stage_completion_registry.py(177 行) 是引擎定義給稽核實作的介面(見上方 callout),被 6 處引用,其中 flow_template_containers.py:24 註冊成 Singleton。
  2. 兩族共 11 支帶稽核軟參照(見下方 callout)。

軟參照是 import 掃描的盲區——判歸屬時看不到,搬遷時是真的資料關聯。

對 114 支做過一次「排除 import 行之後 grep 稽核詞」的全面掃描(指令見 §1 末),判引擎的檔裡命中 11 支,逐支開檔確認全部是同一形狀:指向稽核表的整數欄位,無外鍵。

欄位 出現在 指向
round_id workflow_execution_control_mapping 一族 4 支(entity/query entity/model/mapper/repo impl) compliance.project_audit_rounds.id(model :44 註解明寫「soft ref,無 FK」)
inherited_from_round_id job_evidence 一族 6 支(entity/dto/model/mapper/serializer) 同上(model :66 註解同樣明寫無 FK;用途是覆核輪自母輪 clone 證據時記來源)

DB 層實查確認無外鍵約束。這是 D17 律②(跨疆界只存識別碼、不建外鍵)的正確用法,不構成程式層依賴——引擎不必認識稽核輪次是什麼,只存一個整數,判引擎成立。

但搬遷時必須寫進套件 README:這兩張表若隨引擎套件走,那兩個欄位就成了跨套件軟參照。第 11 支是 infra/flow_engine/models/stage_object.py,命中的是階段 code 字串(planning/task_execution/audit/poam)——那是碼表值不是參照,無須處理。

infra 層 30 支

infra/flow_engine 20 支全部 🟦 引擎(mapper/model/repo impl 各 5–6 支,跟著自家 entity 走)。兩支被外部引用:models/ext_workflow_execution.py 與 models/job_evidence.py 被 infra/readmodel/tasks/job_batch_complete_query.py 引用。

infra/flow_control 10 支——這裡是舊稿 D-4「讀寫混合」的所在,實查結果與舊稿差距最大:

檔 行 寫入語句 表數 跨 schema 判定
flow_control_job_repo_impl.py 1,084 4 24 compliance/oscal/survey/config/iam 🟩 平台(讀寫混合)
flow_control_project_repo_impl.py 716 0 23 compliance/oscal/public/iam 🟨 主程式編排(純讀跨模組)
task_execution_query.py 292 0 19 compliance/oscal/public/config 🟨 主程式編排(純讀跨模組)
job_export_query.py 251 0 13 compliance/oscal/iam 🟨 主程式編排(純讀跨模組)
reverify_clone_query.py 176 6 10 compliance/public/survey 🔴 稽核(跨模組搬運:複製任務+證據+問卷作答)
job_import_lookup_query.py 149 0 6 iam 🟩 平台(純讀)
job_binding_orphan_query.py 129 1 2 compliance 🟩 平台
task_existence_query.py 76 0 2 — 🟩 平台(純讀)

舊稿 D-4 的四支,現在只剩兩支是讀寫混合。

flow_control_project_repo_impl(舊稿記 1 處寫入)與 task_execution_query(舊稿記 1 處寫入)現在都是 0 處寫入的純讀查詢。flow_control_job_repo_impl 的寫入從 21 處降到 4 處。

這代表 D-4 的建議(「逐支切成兩半:純讀的抽進 readmodel、會寫的留原地」)大部分已經做完了——三支純讀跨模組查詢(flow_control_project_repo_impl/task_execution_query/job_export_query,共 1,259 行)現在符合 infra/readmodel/ 的收錄條件,但還留在原地。這是 §6 的第一項。


§4

③ 與舊判定的差異

這節在講什麼:舊稿判 X、現在判 Y、為什麼改。這是本文的核心產出。

舊稿的判定有一半已經無從比較——它判的對象(camunda_service 2,852 行、bpmn_generator 2,428 行、audit_round_app_service 925 行……)現在不在主專案了。下表只列還有對象可比的條目。

判定改變的

對象 舊判 現判 為什麼改
workflow_execution_service 🟡 還看不準(1,679 行,D-9 說「真正跨模組只有 4 條線,改 port 就歸平台」) 🟨 主程式編排(1,321 行) D-9 說的四條線裡,三條已經隨套件化自然解決(TaskSurveyService→jedi_survey、兩支 participant service→jedi_task_platform、UserService→jedi_iam),但解法不是「改成 port」而是「那些模組本身變成套件了」。剩下的跨模組依賴是 app.notification/app.associations/domain.module_frame——三個仍在主專案的模組。它同時牽三個主專案模組,這正是主程式編排的形狀
workflow_template_snapshot_service 🔴 稽核(理由:snapshot 屬輪次凍結語意) 🔴 稽核(決策者 2026-09-16 裁,見 §7 待裁①) 依賴面只有 jedi_flow_engine 兩支 import,乾淨到不能再乾淨;但唯一的業務消費者是 jedi_compliance_audit.audit_round_app_service。依賴說引擎、用途說稽核
flow_control_project_repo_impl 🟡(720 行/1 處寫入,D-4 待切) 🟨 主程式編排(716 行/0 處寫入) 寫入已經清掉了,現在是純讀跨模組查詢,符合 readmodel 收錄條件
task_execution_query 🟡(277 行/1 處寫入,D-4 待切) 🟨 主程式編排(292 行/0 處寫入) 同上
flow_control_job_repo_impl 🔴 讀寫混合(1,015 行/21 處寫入) 🟩 平台(1,084 行/4 處寫入) 寫入從 21 降到 4;跨的表從 13 增到 24(查詢面變廣,寫入面變窄)
job_dto.py 🟩 平台,但「含 6 處稽核欄位待拆」(D-6) 🟩 平台(稽核殘留 3 行) 6 處剩 3 行(:351–:353 讀 row.ap_task_* 補 AO 欄位)
job_service.py 🟡 還看不準(1,144 行,三種型別混在一支,D-1 建議切三份) 🟩 平台(403 行) D-1 已執行——檢測那份進了 jedi_detection,問卷那份切成 job_handlers/survey_handler.py,剩下的是乾淨的任務 CRUD
project_service.py 🔴 稽核(812 行,但註明「其實看不準」,D-2) 🟨 主程式編排(789 行) 依賴六個來源(兩支套件+jedi_iam+通知+關聯+OSCAL),是把它們接起來的那一層

原判成立、但證據要換的

對象 舊判 現判 證據變化
stage_rollback_service 🔴 稽核(證據:直接 import AuditRoundAppService) 🔴 稽核,原判成立 直接 import 確實不見了,但稽核依賴改走 DI 注入(flow_control_containers.py:370)+route 層 import(stage_rollback_route.py:19)。卡上「原判證據失效」這句只對一半——證據變了,結論沒變
stage_advance_service 🔴 稽核(證據:吃 domain.flow_control.RoundStageTransitionEntity) 🔴 稽核,原判成立 該 entity 隨套件化搬家,現在是 jedi_compliance_audit.domain.entities.round_stage_transition_entity(stage_advance_service.py:43 直接 import)。證據更強了——從「吃同專案的 domain 物件」變成「直接依賴稽核套件」
flow_template_app_service 🟦 引擎 🟦 引擎,原判成立 依賴仍然只有 jedi_flow_engine+自家 domain+共用工具
job_evidence_service 🟩 平台 🟩 平台,原判成立 依賴 jedi_file_upload/jedi_flow_engine/jedi_survey
四條 stage route 🔴 稽核 🔴 稽核,原判成立 stage_rollback_route.py:19 的 AuditRoundAppService import 仍在(只是來源換成套件)
reverify_clone_query 🔴(D-4 例外:「整支在做跨模組搬運,留主程式」) 🔴 稽核 6 處寫入仍在;它服務的 reverify_inheritance_service 是覆核輪承襲,稽核專屬

舊稿判過但對象已不存在的

對象 舊判 現況
camunda_service.py(2,852 行) 🟦 引擎 已刪(CM-1486 退役,561922d4)
bpmn_generator.py(2,428 行) 🟦 引擎 已移入套件 jedi_flow_engine/common/utils/(CM-1492)
bpmn_topology_validator.py(330 行) 🟦 引擎 同上
audit_round_app_service.py(925 行) 🔴 稽核 已移入 jedi_compliance_audit
assessment_result_app_service/ar_import_app_service/poam_*/ap_docx_import_*/各 parser 🔴 稽核 已移入 jedi_compliance_audit
control_service/control_group_service/assessment_object_service(D-8① 三支 dark stub) 待退役 已刪(CM-1486)
app/project/oscal_audit_service.py(409 行,D-8②) 「不動,需另派一棒查證是不是殭屍」 已刪(CM-1494 查證後定讞為殭屍)
auditor_dashboard_query/job_batch_complete_query(D-5 兩支) 待補進 readmodel 已補(infra/readmodel/audit//infra/readmodel/tasks/)
review_service.py(D-7) 🟢 通用,建議帶走 已移入 jedi_compliance_audit/app/service/review_service.py——D-7 的建議(歸通用)被後續執行推翻,實際歸了稽核
jedi-project(399 行,D-2 建議當平台包種子) 待處理 已被吸收進 jedi_task_platform,套件本身不存在了
domain/flow_control(舊稿列為要整包搬遷的四層之一) 待搬 空目錄,該層已全數在兩支套件內

§5

④ 已裁示事項現況

這節在講什麼:決策者 2026-08-31 逐項裁過 D-1~D-9,這裡標註每一項現在還成不成立。

裁決結果是「全數照建議採納」(另有 D-7 補閥「port 超過一張即退回稽核半」、D-8② 另立查證棒)。

項 當時裁的 現況 結論
D-1 job_service.py(1,144 行)切三份:任務 CRUD 歸平台/檢測那份歸檢測插件/問卷那份歸問卷插件 已執行。job_service.py 現 403 行;檢測那份在 jedi_detection.app.service.detection_job_binding_handler;問卷那份是 app/flow_control/service/job_handlers/survey_handler.py(196 行)。兩支以 mixin 形式由 JobService 繼承回來 ✅ 已完成,不必再裁
D-2 project_service.py(812 行)拆兩支:CRUD 併入平台包吸收 jedi-project 當種子/聚合查詢進 readmodel 半成。jedi-project 已被 jedi_task_platform 吸收(套件不存在了),但 project_service.py(789 行)沒有拆——get_project() 的聚合仍在原地,仍依賴六個來源 🟡 未完成部分見 §6②,裁示方向仍適用
D-3 job_import_service.py(464 行)算通用,欄位定義改申報制;若判太早就整支帶走留稽核、第 3 步再議 已執行(走「一行未改吃到申報制」那條路,CM-1490)。現 470 行,依賴 jedi_task_platform/jedi_iam,無稽核依賴 ✅ 已完成
D-4 四支讀寫混合 query 逐支切兩半:純讀進 readmodel、會寫的留原地;reverify_clone_query 例外整支留主程式 大部分已完成。四支中兩支(flow_control_project_repo_impl/task_execution_query)已變純讀,flow_control_job_repo_impl 寫入從 21 降到 4,reverify_clone_query 照裁示留著。但那兩支變純讀的沒有搬進 readmodel 🟡 剩搬遷動作,見 §5②
D-5 auditor_dashboard_query(106 行)與 job_batch_complete_query(271 行)補進 infra/readmodel/ 已執行。兩支現在在 infra/readmodel/audit/auditor_dashboard_query.py 與 infra/readmodel/tasks/job_batch_complete_query.py ✅ 已完成
D-6 job_dto.py 的 6 處稽核欄位:整支當通用帶走,欄位改由插件擴充 多數已消化。稽核殘留從 6 處降到 3 行(:351–:353 的 AO 三欄讀 row.ap_task_*),未改成插件擴充形式 🟡 殘留 3 行,規模已不值得單獨處理;併入 §6②
D-7 review_service.py(123 行)與 review_marks 表算通用,標的用軟參照;補閥「port 超過一張即退回稽核半」 裁示被執行推翻。review_service 現在在 jedi_compliance_audit/app/service/review_service.py,review_mark model 也在該套件(infra/model/review_mark.py),實際歸了稽核。主專案側只剩 infra/readmodel/oscal/ssp_control_implementation_query.py:204 的唯讀查詢 ⚠️ 原裁示已不適用(結果與裁示相反但合理,補閥應已觸發),見 §7 待裁②
D-8① 三支 dark stub(control_service/control_group_service/assessment_object_service,225 行)搬遷前先退役;退役前先驗拔掉 DI 不會炸 已執行(CM-1486,三支檔案已不存在) ✅ 已完成
D-8② oscal_audit_service.py(409 行)不動,跟著稽核走;到底是不是殭屍另派一棒查證 已執行。查證棒 CM-1494 定讞為殭屍,檔案已刪,整個 app/project/service/ 現在只剩 project_start_app_service.py(481 行) ✅ 已完成
D-9 workflow_execution_service.py(1,679 行)算通用歸平台層,四條跨模組線改 port:TaskSurveyService→型別 handler 申報/NotificationService→INotifier port/兩支 participant service→平台指派能力/UserService→身分名冊 port 四條線裡三條已解,但解法不同。TaskSurveyService 現在是 jedi_survey.app.service.task_survey_service、兩支 participant service 在 jedi_task_platform.participant.app.service.*、UserService 在 jedi_iam.app.service.user_service——不是改成 port,而是那些模組整個變成套件了。第四條 NotificationService 仍是主專案的 app.notification.service.notification_service,未改 port。另外新增兩條主專案依賴:app.associations.ProjectAssessmentPlanMappingService 與 domain.module_frame.ActionType 🟡 「真正跨模組只有 4 條線」這個前提已失效——現在是 3 條(notification/associations/module_frame),且都指向主專案模組,見 §7 判定與 §6③

D-7 是九項裡唯一「裁示與執行結果相反」的一項。 裁的是「算通用」,做出來是歸稽核套件。當時補的閥(「port 超過一張即退回稽核半」)應該就是在執行時觸發了。這不算違規——補閥本來就是為這種情況設的——但裁示文字與現況不一致,需要決策者確認是否正式撤銷原裁示(見 §7 待裁②)。


§6

⑤ 可以立刻做的乾淨項

這節在講什麼:依賴單純、搬動不牽其他模組的,逐項列出搬去哪、動幾個檔、怎麼驗。

只有兩項合格。 判準是「說得出依賴證據」——首腦初判的兩支裡一支成立、一支推翻,另外找到一項舊稿沒列的。

① flow_template_app_service 歸引擎 — 成立

判定:🟦 引擎,首腦初判成立。

依賴證據(app/flow_engine/service/flow_template_app_service.py:1–:24):

jedi_common(例外類別/transaction/auth context)
jedi_flow_engine(bpmn_topology_validator、BpmnUtils)
common.authz(assert_scope_writable、viewer_is_platform_admin)
common.code.flow_control_error_code
common.util.audit_nickname(enrich_audit_nickname_objs)
common.exception_error.common_exception(BpmnTopologyError)
app.flow_engine.dto.flow_template_dto(自家)
domain.flow_engine.*(自家:entity 2 支、domain service 2 支)

零稽核依賴、零跨模組依賴。被引用處只有兩個:di_containers/flow_engine/flow_template_containers.py 與 test/test_flow_template_app_service.py。

搬去哪:jedi-flow-engine 套件。

要動幾個檔:這支 service(265 行)+ 它依賴的自家檔案:app/flow_engine/dto/flow_template_dto.py(49)、domain/flow_engine/entity/flow_template_entity.py(21)、flow_template_query_entity.py(12)、repository/flow_template_repo.py(28)、service/flow_template_domain_service.py(33)、infra/flow_engine/mapper/flow_template_mapper.py(44)、models/flow_template.py(29)、repository/flow_template_repo_impl.py(108)+ route 與 serializer(flow_template_route.py 144/serializers/flow_template.py 68)+ DI container 改接線。11 支、801 行。

⚠️ 兩處要注意:① stage_object_* 一族(同樣 11 支、301 行,從 app service 到 route 一條完整的鏈)與 flow template 共用同一個 DI container(di_containers/flow_engine/flow_template_containers.py),是否一起走要先確認——兩族都判引擎,一起搬比分兩次省事——但 flow_template 與 stage_object 共容器,一起搬是 22 支進套件並需發版(首腦估核心程式約 570 行;含 route/serializer/DI 接線則逾 1,100 行),屬套件異動卡不是小工;② common.util.audit_nickname 與 common.authz 是主專案的共用工具,套件化時要走 port 或由套件自帶對應能力。

怎麼驗:① 搬前搬後 flask routes 輸出逐字相同(7 條 flow-templates 端點+1 條 stage-objects);② 守衛測試 python -m pytest test/test_module_boundaries.py 全綠;③ test/test_flow_template_app_service.py 與 test_flow_template_domain_service.py 照跑;④ 手測:流程範本的新增/複製/發布/取消發布/驗證五個動作(FlowTemplateService.js 打的那五條)。⑤ 唯讀租戶要單獨測一次範本驗證——license_readonly_mw.py:116 白名單收了 /flow-engine/flow-templates/validate,搬遷若改動路徑,唯讀租戶會靜默失效。

② 三支純讀跨模組查詢搬進 infra/readmodel/

判定:🟨 主程式編排(跨模組唯讀查詢),舊稿 D-4 未完成的部分。

依賴證據(寫入語句數以 grep -ciE "INSERT INTO|UPDATE [a-z_.]+ SET|DELETE FROM" 計):

檔 行 寫入 跨表 跨 schema
infra/flow_control/repository/flow_control_project_repo_impl.py 716 0 23 compliance/oscal/public/iam
infra/flow_control/repository/task_execution_query.py 292 0 19 compliance/oscal/public/config
infra/flow_control/repository/job_export_query.py 251 0 13 compliance/oscal/iam

三支都是純讀、跨三到四個 schema,形狀與 infra/readmodel/ 裡已有的九支完全一致(audit/auditor_dashboard_query.py、tasks/job_batch_complete_query.py 等)。

更正(首腦驗收實查 2026-09-16):三支裡只有 job_export_query.py 真純讀可搬。 上表的「寫入 0」是用 SQL 關鍵字 grep 得出,漏了 ORM 寫入——flow_control_project_repo_impl.py:698-715 的 soft_delete_project 用 ORM session.flush() 寫入;task_execution_query.py:136 有 UPDATE compliance.workflow_executions。這兩支不符 readmodel 的只讀鐵則,不可搬進 infra/readmodel/。判讀寫要查 session.add/merge/flush,不能只 grep INSERT/UPDATE/DELETE。

搬去哪:infra/readmodel/——依該目錄現行的功能分組,flow_control_project_repo_impl 與 task_execution_query 進 tasks/ 或新開 projects/,job_export_query 進 tasks/。落點需依該目錄 README 的四條規格確認(只讀鐵則/層次紀律/出口必 DTO/port 化注入)。

要動幾個檔:三支檔案本身+di_containers/flow_control/flow_control_containers.py 的註冊改接線+infra/readmodel/*/__init__.py 成員表+兩支測試的 import(test/test_grc_project_repo.py、test/test_task_execution_service.py)。約 8 個檔。

⚠️ flow_control_project_repo_impl 有個前提要先驗:它的名字叫 repo_impl,意味著它實作某個 repository 介面。搬進 readmodel 前要確認它真的只被當查詢用——若有 domain repository 介面指著它,就不是單純搬檔,要先處理介面歸屬。另兩支名字就叫 query,沒有這個疑慮。

怎麼驗:① 守衛測試全綠(test_module_boundaries.py 有針對 readmodel 的規則);② 那兩支測試照跑;③ 端點回應比對——/grc/projects/list、/grc/project/<uid>、/grc/project/<p>/ap/<ap>/jobs/export 搬前搬後同一參數回同一結果;④ 出口型別檢查:readmodel 規格要求「出口必 DTO 不泄 ORM row」,搬進去前要逐支核。

③ workflow_template_snapshot_service — 推翻首腦初判

首腦初判「屬立即可做的乾淨項」,實查推翻。

依賴面確實乾淨到不能再乾淨——全檔只有兩行 import,都是 jedi_flow_engine:

from jedi_flow_engine.app.dto.workflow_template_dto import WorkflowTemplateDTO
from jedi_flow_engine.app.service.workflow_template_service import WorkflowTemplateService

但它的唯一業務消費者是稽核套件。jedi_compliance_audit/app/service/audit_round_app_service.py 三處用它(:306 判 None、:308 呼叫 get_source_template_uid、:321 再判一次),DI 由 flow_control_containers.py:349 注入。主專案側除了 DI 註冊與兩支測試,沒有別的呼叫點。

搬去哪取決於怎麼判,而怎麼判有兩種合理答案(見 §7 待裁①,決策者 2026-09-16 已裁歸稽核套件)。在裁決前搬動它,等於用搬遷動作把判定鎖死——正是舊稿「先分家後打包」原則要避免的。列為待裁項(已裁),不列為立即可做。


§7

⑥ 要設計才能動的

這節在講什麼:卡在哪、需要什麼前置。不給完整方案。

① workflow_execution_service(1,321 行)— 最大的一塊

卡在哪:它同時依賴三個仍在主專案的模組:

依賴 用在哪 為什麼不能直接搬
app.notification.service.NotificationService 任務完成/退回時通知相關人 D-9 裁的是「改走 INotifier port」,port 尚未建
app.associations.service.ProjectAssessmentPlanMappingService 專案與 AP 的對應查詢 associations 模組本身未套件化;且它的三組 mapping service 中兩組的呼叫者只有 DI 註冊(FR-102 已記)
domain.module_frame.code.ActionType 模組框架動作型別 module_frame(10,951 行)是主專案第二大模組,未套件化

需要什麼前置:三條線各自要有歸宿。最小的一步是 NotificationService→INotifier port(D-9 已裁定方向,只是沒做);另兩條要等 associations 與 module_frame 的疆界先定。

附帶:__init__ 有四個參數已無呼叫點但刻意保留(project_service/assessment_plan_service/assessment_plan_task_domain_service/drive_sync_orchestration_service),檔內註解說明「拆掉是 DI 簽名異動,2B 重接時大機率還要用」。動這支時要一併決定這些留不留。

② project_service(789 行)— D-2 未完成的那一半

卡在哪:D-2 裁的是「拆兩支:CRUD 歸平台、聚合進 readmodel」。CRUD 那半的歸宿已經有了(jedi_task_platform 吸收了 jedi-project),但拆的動作沒做。get_project()(:238–:334)仍在拼六個來源的資料:participants、SSP inventory 分類、devices、owner 暱稱、雲端同步狀態、稽核輪次。

需要什麼前置:要先決定聚合那半進 readmodel 之後,剩下的 CRUD 半是「搬進套件」還是「留主專案當接線」。這牽動 §5② 的 flow_control_project_repo_impl 搬遷落點——兩件事要一起想。

附帶:job_dto.py:351–:353 那 3 行稽核殘留(D-6)規模已小到不值得單獨動,併進這一項一起處理。

③ 四條 stage route +兩支 stage service 歸稽核

判定明確(🔴 稽核,原判成立、證據更強),但動不了。

卡在哪:stage_advance_service(702 行)與 stage_rollback_service(189 行)目前在 app/flow_engine/,判定歸稽核,理論上該進 jedi-compliance-audit。但:

  1. 它們實作的是 domain/flow_engine/service/stage_completion_registry.py 定義的介面——那個介面在引擎側,如果兩支 service 進稽核套件,介面也要跟著上移到引擎套件,否則變成「稽核套件 import 主專案的 domain」,方向反了。
  2. 三支 oscal_stage_* handler(342 行)也實作同一組介面,同樣的問題。
  3. URL 路徑 /project/<p>/audit-round/<r>/stage/* 掛在 api/flow_engine/ 的 blueprint 上,路徑一字不能改(前端已在用、E2E 在打)。搬動時要用 attach 模式(api/flow_control/__init__.py 已有先例)。

需要什麼前置:stage_completion_registry 先上移到 jedi-flow-engine,這一步做完上面三件才有得談。

④ api/flow_engine/__init__.py 的 blueprint 組裝

卡在哪:api/flow_control/__init__.py 已經改成「主專案建 blueprint、套件 attach 自己那半」的形狀,但 api/flow_engine/__init__.py 還是傳統的全部自己掛。上面三項任何一項動起來,這支都要跟著改成 attach 模式。

需要什麼前置:無技術障礙,但沒有理由單獨做——它是上面三項的配套動作。


§8

⑦ 決策者已裁的

待裁① workflow_template_snapshot_service(94 行)歸引擎還是歸稽核?

事實:

  • 依賴面:全檔只有兩行 import,都是 jedi_flow_engine。零稽核依賴。
  • 用途面:唯一的業務消費者是 jedi_compliance_audit.audit_round_app_service(三處),做的事是「稽核輪次啟動時,把流程範本凍結一份副本」。
  • 檔內文件自述:「本 service 不依賴 spec1 domain entity,接 raw fields,跨 caller 都能用」「Engine 不知道 master 來源在哪張表……caller 自己詮釋(compliance/module/其他業務皆可)」——它是刻意設計成通用的。
  • 舊稿判稽核(理由:snapshot 屬輪次凍結的語意),本次實查推翻該理由的依賴基礎。

兩案與各自後果:

甲案:歸引擎(進 jedi-flow-engine) 乙案:歸稽核(進 jedi-compliance-audit)
依據 依賴方向;且檔內明文寫著設計成跨 caller 通用 實際唯一用途是稽核輪次凍結
後果 引擎套件多一個「凍結範本副本」的通用能力,未來問卷/檢測若要凍結範本可直接用 稽核套件自足,引擎不必為只有一個用戶的功能負責
風險 若三年內沒有第二個 caller,就是替假想需求付維護成本 若之後問卷/檢測要凍結範本,會複製第二份——正是模組化要避免的
動的檔數 1 支+DI 改接線+1 支測試 同左

我的觀察(不是建議):檔內那段「跨 caller 都能用」是 2026-05 寫的設計意圖,四個月過去仍只有一個 caller。這個事實對兩案都是證據——甲案說「能力已備好等著用」,乙案說「假想需求沒有兌現」。

決策者 2026-09-16 裁:歸稽核套件(乙案)。 理由:四個月只有一個用戶且用途是稽核輪次凍結;將來有第二個 caller 再抽上引擎。

待裁② D-7 原裁示(review_service 歸通用)是否正式撤銷?

事實:

  • 2026-08-31 裁:review_service.py(123 行)與 review_marks 表算通用,標的用軟參照,由插件決定標的是什麼。補閥:「port 超過一張即退回稽核半」。
  • 現況:review_service.py 在 jedi_compliance_audit/app/service/review_service.py,review_mark model 在同套件 infra/model/review_mark.py。實際歸了稽核,與裁示相反。
  • 主專案側只剩 infra/readmodel/oscal/ssp_control_implementation_query.py:204 的 get_review_marks_by_control_ids()(唯讀查詢,由 app/oscal/service/ssp_control_implementation_service.py:707 呼叫)。

這一項不是要重新選邊——執行結果合理(補閥應已觸發),只是裁示文字與現況不一致,留著會讓下一個讀裁決紀錄的人誤判。需要的是一句確認:原裁示撤銷、以「歸稽核」為準,或說明當時補閥觸發的實際理由以便入帳。

決策者 2026-09-16 裁:原裁示撤銷,以歸稽核為準。


§9

⑧ 邊界

這些東西一個字都不能動

  1. 對外 API 的 URL 路徑——30 條端點前端全部在用,E2E 有 23 條在打。判歸屬時路徑維持原樣,只換實作位置。
  2. error code 的字串值——前端 error-code.json 靠字串比對做多語系。改類別名稱可以,改值不行。
  3. 資料庫內容。

本文刻意不碰的範圍:

  • app/project(481 行,只剩 project_start_app_service.py):不在本次範圍。它是 OSCAL/專案/流程引擎三者的接縫,若要判歸屬需連同 OSCAL 疆界一起看。
  • associations/module_frame/notification 三個模組:只在 §6① 記為 workflow_execution_service 的前置,不判它們自己的歸屬。
  • 兩支套件內部(jedi-compliance-audit/jedi-task-platform):本文只看主專案這 114 支。
  • 資料庫 view 歸屬:舊稿 §8 列過四張 view,本次未重驗(不在卡片範圍)。若要引用那份清單,請先重查。
FR-104 · CM-1823 · 引用前請先重驗數字(見 §1 末「怎麼重跑」)