狀態:設計中(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 拍板) 連帶:建專案流程照新規格實作(資源庫 + 第一輪流程範本;舊資料已清空、不做向後相容)
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。
起點是建專案流程在 v2 切換後對不上(FE/BE payload 不一致)。舊專案資料已全部清空 → 不做向後相容,建專案流程直接以本增量新規格實作(資源庫 + 第一輪流程範本,見 D6)。OscalProjectStartRequest 可一併清掉舊 Meta.unknown=EXCLUDE / 舊欄位相容註解。
症狀只是表面。真正問題是同一套稽核生命週期狀態機在 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 整個掉了。
「依稽核情境組不同流程」是真需求(FR-026 README 三情境:正式稽核 / 內部自查 / 自我評估,stage 組合不同)。放棄它 = FR-026 整個白做。user 拍板:走完整再整合(big-bang),不逃避。
| 稽核流程範本(本增量主角) | 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 收證據)不在本增量範圍,維持現狀。
| # | 決策 | 選擇 | 理由 |
|---|---|---|---|
| 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 + 框架維護頁多一欄 |
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 階段鏈(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)但改 wrapaudit_round_app_service(原 wrap 已 dark 的OscalAuditService)。activate_projecthandler 退役(planning 不再對 activate,改對 launch_audit)。review_decisionhandler 保留但 builtin 不用(未來變體)。
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 |
ck_audit_rounds_status),零 migration。audit_round_app_service 各轉換方法移除自身的 e.status = STATUS_* 寫死,status 改由 stage_advance 在 handler 跑完後依當前 stage 統一回填(單一真相源)。parent_round_id(FR-038 既有);pending_reverify / close-out 連動維持 close_round handler 內邏輯。推進 ↔︎ status 寫入 ordering(單一真相源,避免 race):
audit_round_app_service 轉換方法:保留 status guard(if e.status != STATUS_X: raise,讀的是「推進前」status,仍正確)+ 保留業務寫入(ssp_id / assessment_plan_id / ar_result_id / poam_id);只移除 e.status = STATUS_X。round.status(status 反映「你現在所在的 stage」)。finalize_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。BE — schema
project_audit_rounds 加 flow_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"])。ap_authoring UserTask。BE — flow 引擎 retarget(registry interface 簽章 big-bang 一次改全)
domain/flow_engine/service/stage_completion_registry.py:IStageCompletionHandler.execute / IStagePreconditionCheck.check 第一參數 ap_uid → round_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_uid→round_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_service、execute(round_uid, …)、拆掉 _safe_register dark-skip 鷹架(grc_containers.py:468-489)。
Planning(→launch_audit) / 新 SubmitAp(→start_auditing) / Audit(→finalize_audit) / Poam(→close_round) / TerminalClose(→round closed) / ReviewDecision(保留不 wire)。activate_project handler 退役。all_tasks_assigned / all_tasks_completed / all_controls_verdict_filled / all_poam_closed)check(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)。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。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_route 的 launch-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)。module_frame.uid、保留稽核流程範本下拉送 flow_template_uid。FE
FlowPhaseBanner.vue + useStageInfo.js + StageService.js:props/endpoint ap_uid → round_uid。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。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 / 簽章 retargetdomain/flow_engine/service/stage_completion_registry.py — 註冊 submit_ap handler + ap_reviewed_controls_set preconditionapi/project/routes/audit_round_route.py — 加 round-scoped stage/info + stage/advanceapi/project/serializers/project.py — v2 shape(已是;FE 對齊即可)scripts/sql/2026-06-1x-fr038-round-flow-binding.sql — round 兩欄 + ap_authoring stage seed + builtin BPMN 重 seedcommon/code/grc_error_code.py — 新 error code(round 無 workflow binding / AP reviewed-controls 未設)FE
src/components/grc/FlowPhaseBanner.vue、src/composables/useStageInfo.js、src/service/StageService.js — retarget roundsrc/views/project/ProjectCreateView.vue — 新規格:資源庫送 module_frame.uid + 保留流程範本下拉(第一輪)src/views/project/RoundApAuthoringView.vue(或併入規劃頁)— AP 填寫src/config/api/api.js — round-scoped stage endpoint 常數module_frames 加 audit_flow_template_uid 欄;首輪用資源庫預設、後續輪沿用前一輪、皆可覆寫。schema 異動納入本增量。audit_round_route 的 launch-audit/start-auditing/finalize/close 4 個直接轉換 route disable/移除(否則直呼這些方法會繞過 status 推導 + 撞 status guard,造成 status 不一致)。FE 改走 Banner stage/advance(FE plan)。parent_round_id)流程範本沿用母輪還是另選;與 FR-038 B5 launch-reverify 交界。assessment_plan_extensions / ap-scoped stage route:big-bang 後 DROP / disable(比照 DROP 退役表四查)。詳細 bite-sized 步驟見同資料夾
round-flow-engine-implementation-plan.md(BE)+ FE plan。