FR-038 Wave 2 — 稽核輪次流程引擎再整合(含 AP 填寫階段)

狀態:設計中(2026-06-16) 歸屬:FR-038 OSCAL v2 重設計 Wave 2 增量(跨 B3 輪次狀態機 / B4 AP) 類型:v2 切換的「原有功能再生」—— 把 FR-026 BPMN 流程引擎從舊 AP retarget 到 v2 first-class project_audit_rounds,並加入新的 ap_authoring 階段 決策來源:2026-06-16 設計對話(user 拍板) 連帶:建專案流程照新規格實作(資源庫 + 第一輪流程範本;舊資料已清空、不做向後相容)


0. 為什麼歸在 FR-038(不另開 FR)

FR-038 是 OSCAL V1→V2 抽換底層的 arc,本職有二:(1) 原有功能在 v2 基座上要能運作;(2) 新模型的新流程加入。FR-026 的流程引擎在 v2 切換時被擱在已 dark 的舊 AP 上(孤兒),把它接回 v2 輪次 = 第 (1) 項;加 ap_authoring 階段 = 第 (2) 項。屬 FR-038 Wave 2(B3 輪次狀態機 + B4 AP)的自然延伸,故為本資料夾 sub-design,不佔新整數 FR。


1. 問題陳述

1.1 觸發脈絡

起點是建專案流程在 v2 切換後對不上(FE/BE payload 不一致)。舊專案資料已全部清空 → 不做向後相容,建專案流程直接以本增量新規格實作(資源庫 + 第一輪流程範本,見 D6)。OscalProjectStartRequest 可一併清掉舊 Meta.unknown=EXCLUDE / 舊欄位相容註解。

1.2 根本問題(深一層)

症狀只是表面。真正問題是同一套稽核生命週期狀態機在 v2 並存兩份

① FR-026 流程引擎 ② v2 audit_round 狀態機
載體 stage_objects + flow_templates + stage_advance_service + handler registry + Banner audit_round_app_service 硬編碼 7 態
驅動 BPMN 可設定 寫死轉換
key 舊 AP(ap_uid + assessment_plan_extensions project_audit_rounds(first-class)
現況 程式還在、沒 dark,但綁在 v2 已 dark 的舊 AP 上 → 孤兒 v2 實際在跑的

v2 切換時,把 FR-026 拆掉的「寫死 stage」在輪次層又寫死回去,FR-026 引擎被擱在死掉的 AP 上。稽核流程範本 這條線在 v2 整個掉了。

1.3 為什麼要修(需求面)

「依稽核情境組不同流程」是真需求(FR-026 README 三情境:正式稽核 / 內部自查 / 自我評估,stage 組合不同)。放棄它 = FR-026 整個白做。user 拍板:走完整再整合(big-bang),不逃避。


2. 兩個被混淆的「流程」(先釐清)

稽核流程範本(本增量主角) per-AO 收證據 job
來源 FR-026 專案流程引擎 FR-038 Q1 _generate_prep_jobs
是什麼 輪次的執行流程:規劃→填寫→稽核→改善→結案 單一控制項目標一個收證據 job
粒度 整個輪次 lifecycle(BPMN 驅動 stage 推進) per-assessment-objective
綁定 應綁 project_audit_rounds(本增量) module_frames.template_uid(不動)

module_frames.template_uid(per-AO 收證據)不在本增量範圍,維持現狀。


3. 決策(已拍板)

# 決策 選擇 理由
D1 流程範本綁哪一層 project_audit_round 一專案多輪(initial→surveillance→close-out),每輪各綁/凍結自己的流程;V1 _bind_main_workflow_to_ap 本來就綁輪次(per-AP snapshot),這是回到正確分層
D2 交付策略 big-bang 一次到位 直接把 round 轉換改 BPMN 驅動 + flow_template 綁 round + stage_advance 整合
D3 AP 填寫主責角色 auditor reviewed-controls / subjects / tasks 是「稽核規劃」決策,屬稽核員職責;與現有 start_auditing(auditor 守門)一致
D4 何時選範本 第一輪在「建專案」選;後續輪在「建輪次」選 範本綁 round(D1),於 round 建立當下選;第一輪的建立折進建專案(見 D6),故第一輪範本在建專案頁選
D6 建專案 ↔︎ 第一輪關係 建專案順帶建 round 1(initial)並綁所選流程範本 維持現有建專案 UI(資源庫 + 稽核流程範本雙下拉);一頁完成「專案 + 第一輪」。round 1 進 planning,SSP snapshot 仍等 launch_audit。每 audit_round ↔︎ 一支範本(1:1,與 D1 一致)
D5 stage 模型 / task_execution 去留 planning → ap_authoring → audit → poam;task_execution 收進 planning、review 暫不納 OSCAL/NIST 800-53A:SSP 補完 + 收證據屬 Prepare 期(非 audit 執行),是 planning 的內涵;FR-026 task_execution 命名誤導。PM「確認任務完成才啟動稽核」= planning 的推進 precondition(本輪 per-AO 收證據 job 全完成),不需獨立階段。且此 4 階段 1:1 對齊已 shipped 的 v2 輪次 status(planning/audit_planning/auditing/remediation),零 CHECK migration。review 留作未來 -with-review 變體(FR-026 機制保留可隨時加回)
O1 範本預設來源 **(a)+(b) 資源庫加 audit_flow_template_uid 欄(框架維護者設公版預設)→ 首輪用資源庫預設、後續輪沿用前一輪、皆可覆寫**。動 module_frames schema + 框架維護頁多一欄

4. 目標架構(FR-026 引擎 retarget 到 round)

flow_template (master, 系統層 published)
   │ clone snapshot(建輪次時)
   ▼
project_audit_rounds  ──┬── flow_template_snapshot_uid   (新欄位)
                        └── workflow_execution_uid       (新欄位,running instance)
   │ stage_advance 改用 round_uid 解 context(不再 ap_uid)
   ▼
BPMN UserTask 串(範本定義,可不同情境不同串):
   StartEvent → planning → ap_authoring(新) → audit → (Gateway) → poam → audit / End

4.1 canonical stage 鏈 ↔︎ round status ↔︎ handler 對應(D5 收斂)

4 階段鏈(builtin 全部走這串;review 暫不納,未來變體再加):

planning ──→ ap_authoring ──→ audit ──→ poam ──→ End
BPMN stage_object complete_handler_key round.status(推導) on_complete 包 audit_round_app_service precondition_key main_role
planning(含 FR-026 task_execution) launch_audit planning launch_audit(snapshot SSP 邊界③ + create_draft_for_snapshot 建 AP 草稿) round_prep_tasks_done(本輪 per-AO 收證據 job 全完成;PM gate) manager
**ap_authoring(新) submit_ap audit_planning start_auditing(建 AR + AO 全量矩陣) ap_reviewed_controls_set(reviewed-controls 非空) auditor**
audit confirm_audit auditing finalize_audit(AR 定版 + 有 not_met 生 POA&M) ar_all_verdicts_filled(全 AO 判定完) auditor
poam close_round remediation close_round(→ pending_reverify / closed) poam_all_closed manager
EndEvent 前 terminal UserTask terminal_close closed round 標 closed

handler_key 沿用既有命名launch_audit/confirm_audit/close_round/terminal_close)但改 wrap audit_round_app_service(原 wrap 已 dark 的 OscalAuditService)。activate_project handler 退役(planning 不再對 activate,改對 launch_audit)。review_decision handler 保留但 builtin 不用(未來變體)。

4.1b round.status 推導(取代寫死 7 態)

round.status 不再由 audit_round_app_service 寫死常數,改由 stage_advance 推進成功後依「當前 BPMN UserTask 的 stage_object_code」查下表回填

stage_object_code round.status
planning planning
ap_authoring audit_planning
audit auditing
poam remediation
(terminal / EndEvent) closed
  • 對齊既有 DB CHECK 值域(ck_audit_rounds_status),零 migration
  • audit_round_app_service 各轉換方法移除自身的 e.status = STATUS_* 寫死,status 改由 stage_advance 在 handler 跑完後依當前 stage 統一回填(單一真相源)。
  • 覆核(close-out)走子輪 + parent_round_id(FR-038 既有);pending_reverify / close-out 連動維持 close_round handler 內邏輯。

推進 ↔︎ status 寫入 ordering(單一真相源,避免 race):

  1. stage_advance 解析當前 UserTask(= 當前 stage)→ 跑 precondition → dispatch handler。
  2. handler 呼叫 audit_round_app_service 轉換方法:保留 status guardif e.status != STATUS_X: raise,讀的是「推進前」status,仍正確)+ 保留業務寫入ssp_id / assessment_plan_id / ar_result_id / poam_id);只移除 e.status = STATUS_X
  3. BPMN engine 推進到下一 UserTask;stage_advance 依「新的當前 stage」查 §4.1b map 回填 round.status(status 反映「你現在所在的 stage」)。
  4. audit → closed/remediation 的分叉是 BPMN Gateway,不是純 mapfinalize_audit handler 回傳 gateway 變數 has_findings(= not_met_count>0),BPMN 路由 has_findings→poam(status=remediation) / 否則 →End(status=closed)。poam_id 仍由 finalize_audit 業務寫入。機制同 FR-026 §8 builtin gateway + _next_stage_code peek。

4.2 要動的塊(big-bang 全包)

BE — schema

  • project_audit_roundsflow_template_snapshot_uid + workflow_execution_uid(沿用既有 soft-ref 慣例,不建 ORM relationship;不開新表)。
  • 新增 ap_authoring stage_object seed(i18n / route_pattern / handler_key / precondition_key / main_roles=["auditor"] / allowed_predecessors=["planning"] / allowed_successors=["audit"])。
  • builtin flow_templates 的 BPMN 重 seed:planning 與 audit 之間插入 ap_authoring UserTask。

BE — flow 引擎 retarget(registry interface 簽章 big-bang 一次改全)

  • domain/flow_engine/service/stage_completion_registry.pyIStageCompletionHandler.execute / IStagePreconditionCheck.check 第一參數 ap_uidround_uid
  • app/flow_engine/service/stage_advance_service.py_resolve_workflow_context 改查 project_audit_rounds.workflow_execution_uid(不再走 assessment_plan_extensions);advance_stage / get_current_stage_info 簽章 ap_uidround_uid;內部 ~30 處 ap_uid 引用(log + handler.execute(ap_uid=) 呼叫點 + _run_precondition)全改;DI 加 audit_round_domain_service、移除 assessment_plan_extension_domain_service。推進成功後依 §4.1b 表回填 round.status。
  • app/project/service/oscal_audit_service.py 退場:既有 6 handler 原 wrap 此 service(2A 已 dark no-op,DI 用 _safe_register try/except 靜默跳過)。本增量全部 handler 建構子改注入 audit_round_app_serviceexecute(round_uid, …)、拆掉 _safe_register dark-skip 鷹架(grc_containers.py:468-489)。
    • 改 wrap 的 6 handler:Planning(→launch_audit) / SubmitAp(→start_auditing) / Audit(→finalize_audit) / Poam(→close_round) / TerminalClose(→round closed) / ReviewDecision(保留不 wire)。activate_project handler 退役。
  • precondition 全 retarget:既有 4 個(all_tasks_assigned / all_tasks_completed / all_controls_verdict_filled / all_poam_closedcheck(ap_uid)check(round_uid);新增 round_prep_tasks_done(planning gate)+ ap_reviewed_controls_set。後者用 AssessmentPlanAppService.get_ap_for_round(round_uid) 取 AP,再查 reviewed-controls 數(需新增薄 method count_reviewed_controls,內部 ApReviewedControlsRepoImpl.get_by_ap)。
  • 舊 ap-scoped route 退役api/flow_engine/routes/stage_advance_route.py/project/<uid>/ap/<ap_uid>/stage/*)+ api/flow_engine/__init__.py 註冊一併移除/disable(否則 boot 出壞端點)。
  • create_round:clone flow_template master → snapshot(workflow_template_snapshot_service.clone_master_as_snapshot,mirror app/project/service/oscal_project_service.py:196)→ 起 main workflow_execution → 回填 round 兩新欄位 + _to_dto 加回傳這兩欄。範本來源 O1:入參 > 前輪 snapshot 之 master > 資源庫 audit_flow_template_uid
  • i18n:新 precondition reason key(round_prep_tasks_done / ap_reviewed_controls_set)+ stage 按鈕 label 補 config/translations/ zh_Hant_TW + en。

BE — route

  • GET/POST /project/<project_uid>/audit-round/<round_uid>/stage/{info,advance}(取代 ap-scoped)。
  • 現有 audit_round_routelaunch-audit/start-auditing/close 獨立轉換 route:收斂為 stage/advance 統一入口(O2,傾向統一、獨立 route deprecated)。

BE — 建專案順帶建第一輪(D6,新規格)

  • OscalProjectStartRequest serializer(清掉舊相容):resource_library_uid required + flow_template_uid(= 第一輪範本)+ name/description/start_date/end_date/owner_uid/participants
  • ProjectStartAppService.start_project:clone 資源庫 + 建 project/extension/participants/prep jobs 後,呼叫 audit_round_app_service.create_round(round_type='initial', flow_template_uid=…) 建 round 1(clone flow snapshot + 起 workflow + 綁 round)。未給 flow_template_uid → O1 fallback(資源庫 audit_flow_template_uid)。
  • FE 建專案(新規格):資源庫下拉送 module_frame.uid、保留稽核流程範本下拉送 flow_template_uid

FE

  • FlowPhaseBanner.vue + useStageInfo.js + StageService.js:props/endpoint ap_uidround_uid
  • AP 填寫 stage 畫面(routed stage ap_authoring):reviewed-controls 多選 + subjects + tasks,接 PUT …/reviewed-controls|subjects|tasks
  • ProjectCreateView.vue:照新規格送 resource_library_uid(module_frame.uid)+ 保留流程範本下拉送 flow_template_uid(第一輪範本)。
  • RoundSwitcherBar / ProjectAuditorOverview:Banner 改吃 round context。

5. 受影響檔案地圖(plan 用)

BE

  • infra/grc/model/project_audit_round.py — 加兩欄
  • app/grc/service/audit_round_app_service.py — create_round 綁範本;轉換方法被 handler 包
  • app/flow_engine/service/stage_advance_service.py_resolve_workflow_context / 簽章 retarget
  • domain/flow_engine/service/stage_completion_registry.py — 註冊 submit_ap handler + ap_reviewed_controls_set precondition
  • api/project/routes/audit_round_route.py — 加 round-scoped stage/info + stage/advance
  • api/project/serializers/project.py — v2 shape(已是;FE 對齊即可)
  • scripts/sql/2026-06-1x-fr038-round-flow-binding.sql — round 兩欄 + ap_authoring stage seed + builtin BPMN 重 seed
  • common/code/grc_error_code.py — 新 error code(round 無 workflow binding / AP reviewed-controls 未設)

FE

  • src/components/grc/FlowPhaseBanner.vuesrc/composables/useStageInfo.jssrc/service/StageService.js — retarget round
  • src/views/project/ProjectCreateView.vue — 新規格:資源庫送 module_frame.uid + 保留流程範本下拉(第一輪)
  • src/views/project/RoundApAuthoringView.vue(或併入規劃頁)— AP 填寫
  • src/config/api/api.js — round-scoped stage endpoint 常數

6. 不變式 / 邊界

  • round.status 不再寫死 → 必須與 BPMN stage_object 對應表 100% 一致(DB CHECK 值域 + stage_object code 對齊)。
  • snapshot 三層獨立性沿用 FR-026 §3.4:master 改不影響已啟動輪次。
  • flow_template snapshot 綁 create_round(planning 起算);SSP snapshot 仍在 launch_audit(邊界③)—— 兩個 snapshot 不同時機、不同物件。
  • 權限 / @transaction / DDD 分層照 CLAUDE.md:route 不碰 DB、轉換在 app service、precondition 在 domain。

7. 開放項(plan 前要定)

  • O1 範本預設來源已定 (a)+(b)(2026-06-16)—— module_framesaudit_flow_template_uid 欄;首輪用資源庫預設、後續輪沿用前一輪、皆可覆寫。schema 異動納入本增量。
  • O2 獨立轉換 route 去留已定 — 退役(2026-06-16)。big-bang 後輪次轉換一律走 stage/advance 統一入口audit_round_routelaunch-audit/start-auditing/finalize/close 4 個直接轉換 route disable/移除(否則直呼這些方法會繞過 status 推導 + 撞 status guard,造成 status 不一致)。FE 改走 Banner stage/advance(FE plan)。
  • O3 close-out 覆核輪:覆核子輪(parent_round_id)流程範本沿用母輪還是另選;與 FR-038 B5 launch-reverify 交界。
  • O4 舊 assessment_plan_extensions / ap-scoped stage route:big-bang 後 DROP / disable(比照 DROP 退役表四查)。
  • O5 資料遷移:dev 無正式輪次資料則零遷移;正式環境在跑輪次的 workflow binding 回填(可能 follow-up)。

8. 交付順序

  1. O1 定案 → BE 大重構(schema → 引擎 retarget → handler → route → seed),TDD bite-sized。
  2. FE retarget(Banner → round + AP 填寫畫面 + 建專案照新規格 + 建輪次選範本)。
  3. e2e(compliance-manager-test)。

詳細 bite-sized 步驟見同資料夾 round-flow-engine-implementation-plan.md(BE)+ FE plan。