FR-104 · CM-1823 · 實查日期 2026-09-16 · HEAD 98d756bb
主專案裡管「流程」的程式分散在兩個地方——flow_engine(流程引擎)與 flow_control(稽核流程控管)。這份文件把兩包 114 個檔逐支查過,判定每一支該歸誰,每一筆判定都附依賴證據。不動任何程式碼;要回答的問題是「哪些現在就能清、哪些要等設計、哪些本來就該留著」。
jedi-compliance-audit/jedi-task-platform),主專案這兩包從 226 檔縮到 114 檔。舊設計稿(2026-08-31)的每個數字都已失效,本文取代它。__init__.py,零行)。flow_template_app_service 成立,workflow_template_snapshot_service 推翻——它的唯一消費者是稽核套件,決策者 2026-09-16 裁歸稽核套件(§7)。workflow_execution_service(1,321 行)。引用本文的數字前請先重驗。 舊稿失效的教訓正是「它自己就是為了更正上一份的數字而做的實查,半個月後也過期了」。本文所有數字的產生指令列在 §1 末,重跑一次再引用。
本文有兩節刻意帶時序敘述(其餘各節只寫現況):§3「與舊判定的差異」與 §4「已裁示事項現況」——比對舊判定與標註裁示落點正是本文的核心用途,去掉就無從驗收。§1 有兩處端點註明「已註解退役(卡號)」,那是現況陳述加可查證出處。
這節在講什麼:兩包現在有多大、對外開了哪些門、誰在走那些門。
| 項目 | 值 |
|---|---|
| 實查日期 | 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 兩個套件內。
數端點必須用語法解析,不能用字串比對。 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 條全部是活的,四層查法逐條核過:
src/config/api/api.js):8 個 flow-engine 常數+5 個 GRC 常數,每個都至少一個 .vue/.js 檔在用。src/config/router/index.js):對應頁面皆掛在路由上。public.ui_routes,DEV 唯讀查詢):flow-template-manage/project-list/my-tasks/project-dashboard/audit-manage 皆 enable=1。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這節在講什麼: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「要設計才能動」的所在。換句話說:好判的都是小檔,大檔都卡著。
這六支是整份判定的核心,逐支列出完整證據:
| 檔 | 行 | 判定 | 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 行得到的結論。逐處追下去,稽核依賴還在,只是改由兩條路走:
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)。api/flow_engine/routes/stage_rollback_route.py:19 直接 from jedi_compliance_audit.app.service.audit_round_app_service import AuditRoundAppService。原判(歸稽核)成立,只是證據要換成上面兩條。這正是「看 import 行不夠」的實例——依賴注入會讓靜態 import 看起來很乾淨。
| 檔 | 行 | 判定 | 證據 |
|---|---|---|---|
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)。引擎不需要認識稽核,稽核來實作引擎的介面——這是舊稿說的「引擎只留通用的階段推進機制抽象」已經兌現的部分。
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),這是刻意的設計,不是遺漏。
全部 🟦 引擎。 這一層乾淨到不需要逐支列表——27 支的 import 只有三種來源:jedi_common(基礎設施)、jedi_flow_engine/jedi_task_platform(套件型別)、自家 domain/flow_engine。零稽核 import。
兩處要點名:
stage_completion_registry.py(177 行) 是引擎定義給稽核實作的介面(見上方 callout),被 6 處引用,其中 flow_template_containers.py:24 註冊成 Singleton。軟參照是 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/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 的第一項。
這節在講什麼:舊稿判 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(舊稿列為要整包搬遷的四層之一) |
待搬 | 空目錄,該層已全數在兩支套件內 |
這節在講什麼:決策者 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 待裁②)。
這節在講什麼:依賴單純、搬動不牽其他模組的,逐項列出搬去哪、動幾個檔、怎麼驗。
只有兩項合格。 判準是「說得出依賴證據」——首腦初判的兩支裡一支成立、一支推翻,另外找到一項舊稿沒列的。
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 已裁歸稽核套件)。在裁決前搬動它,等於用搬遷動作把判定鎖死——正是舊稿「先分家後打包」原則要避免的。列為待裁項(已裁),不列為立即可做。
這節在講什麼:卡在哪、需要什麼前置。不給完整方案。
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_advance_service(702 行)與 stage_rollback_service(189 行)目前在 app/flow_engine/,判定歸稽核,理論上該進 jedi-compliance-audit。但:
domain/flow_engine/service/stage_completion_registry.py 定義的介面——那個介面在引擎側,如果兩支 service 進稽核套件,介面也要跟著上移到引擎套件,否則變成「稽核套件 import 主專案的 domain」,方向反了。oscal_stage_* handler(342 行)也實作同一組介面,同樣的問題。/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 模式。
需要什麼前置:無技術障礙,但沒有理由單獨做——它是上面三項的配套動作。
workflow_template_snapshot_service(94 行)歸引擎還是歸稽核?事實:
jedi_flow_engine。零稽核依賴。jedi_compliance_audit.audit_round_app_service(三處),做的事是「稽核輪次啟動時,把流程範本凍結一份副本」。兩案與各自後果:
甲案:歸引擎(進 jedi-flow-engine) |
乙案:歸稽核(進 jedi-compliance-audit) |
|
|---|---|---|
| 依據 | 依賴方向;且檔內明文寫著設計成跨 caller 通用 | 實際唯一用途是稽核輪次凍結 |
| 後果 | 引擎套件多一個「凍結範本副本」的通用能力,未來問卷/檢測若要凍結範本可直接用 | 稽核套件自足,引擎不必為只有一個用戶的功能負責 |
| 風險 | 若三年內沒有第二個 caller,就是替假想需求付維護成本 | 若之後問卷/檢測要凍結範本,會複製第二份——正是模組化要避免的 |
| 動的檔數 | 1 支+DI 改接線+1 支測試 | 同左 |
我的觀察(不是建議):檔內那段「跨 caller 都能用」是 2026-05 寫的設計意圖,四個月過去仍只有一個 caller。這個事實對兩案都是證據——甲案說「能力已備好等著用」,乙案說「假想需求沒有兌現」。
決策者 2026-09-16 裁:歸稽核套件(乙案)。 理由:四個月只有一個用戶且用途是稽核輪次凍結;將來有第二個 caller 再抽上引擎。
review_service 歸通用)是否正式撤銷?事實:
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 裁:原裁示撤銷,以歸稽核為準。
這些東西一個字都不能動
error-code.json 靠字串比對做多語系。改類別名稱可以,改值不行。本文刻意不碰的範圍:
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 支。