For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 把 FR-026 BPMN 流程引擎從舊 AP(ap_uid + assessment_plan_extensions)retarget 到 v2 first-class project_audit_rounds,新增 ap_authoring 階段,round.status 改由 BPMN 推導。
Architecture: flow_template master → 建輪次時 clone snapshot → 綁 project_audit_rounds.workflow_execution_uid;stage_advance 以 round_uid 解 workflow context;既有 stage handler 改 wrap audit_round_app_service;round.status 由當前 BPMN UserTask 的 stage_object_code 經對照表推導(§4.1b)。
Tech Stack: Flask + SQLAlchemy + PostgreSQL(DDD),jedi-flow-engine(BPMN),jedi-oscal-v2(AP/AR),pytest。
設計依據: round-flow-engine-reintegration-design.md(§3 決策含 D5 stage 模型 / §4.1 canonical 4 階段鏈 + handler 對應 / §4.1b status 推導)
canonical stage 鏈(D5): planning → ap_authoring → audit → poam → End(task_execution 收進 planning;review 暫不納)。
| # | 任務 | 依賴 | 風險 |
|---|---|---|---|
| 1 | schema:3 新欄位 + entity/mapper + _to_dto |
— | 低 |
| 2 | error code + i18n key | — | 低 |
| 3 | stage_object seed(加 ap_authoring + 改 planning handler/precondition) | 1,2 | 中 |
| 4 | builtin flow_templates BPMN 重 seed(canonical 4 階段) | 3 | 中 |
| 5 | registry interface + stage_advance retarget(ap_uid→round_uid + status 推導) | 1 | 高 |
| 6 | handler 全改 wrap audit_round + 新 submit_ap + 拆 _safe_register | 5 | 高 |
| 7 | precondition retarget + 新 2 個 + count_reviewed_controls | 5 | 中 |
| 8 | create_round 綁 flow snapshot | 1,4 | 中 |
| 8b | 建專案順帶建第一輪(D6)+ serializer | 8 | 中 |
| 9 | round-scoped stage route + 退役舊 ap-scoped route | 6,7 | 中 |
| 10 | DI wiring | 6,7,8,9 | 中 |
| 11 | 整合測試 + boot 驗證 | all | — |
建專案順帶建第一輪(D6,新規格):舊專案資料已清空、不做向後相容;serializer 清掉舊
Meta.unknown=EXCLUDE/ 舊欄位 + 帶resource_library_uid/flow_template_uid,start_project建 round 1 = Task 8b。FE 照新規格建畫面在 FE plan。
_to_dtoFiles: Create scripts/sql/2026-06-16-fr038-round-flow-binding.sql;Modify infra/module_frame/models/module_frame.py、infra/grc/model/project_audit_round.py、infra/grc/mapper/project_audit_round_mapper.py、domain/grc/entities/project_audit_round_entity.py、app/grc/service/audit_round_app_service.py(_to_dto)
-- Date: 2026-06-16 FR-038 round flow binding
ALTER TABLE compliance.module_frames
ADD COLUMN IF NOT EXISTS audit_flow_template_uid VARCHAR(36);
COMMENT ON COLUMN compliance.module_frames.audit_flow_template_uid IS '預設稽核流程範本 uid(→ compliance.flow_templates.uid,soft ref)';
ALTER TABLE compliance.project_audit_rounds
ADD COLUMN IF NOT EXISTS flow_template_snapshot_uid VARCHAR(36),
ADD COLUMN IF NOT EXISTS workflow_execution_uid VARCHAR(36);
COMMENT ON COLUMN compliance.project_audit_rounds.flow_template_snapshot_uid IS '本輪凍結流程範本 snapshot uid(soft ref)';
COMMENT ON COLUMN compliance.project_audit_rounds.workflow_execution_uid IS '本輪 main workflow_execution uid(BPMN 驅動 round 狀態)';
INSERT INTO public.schema_migrations (version) VALUES ('2026-06-16-fr038-round-flow-binding') ON CONFLICT DO NOTHING;git add scripts/sql/2026-06-16-fr038-round-flow-binding.sql infra/grc/model/project_audit_round.py infra/grc/mapper/project_audit_round_mapper.py domain/grc/entities/project_audit_round_entity.py infra/module_frame/models/module_frame.py app/grc/service/audit_round_app_service.py
git commit -m "feat(FR-038): round flow binding 欄位 + 資源庫預設流程範本欄 + _to_dto"Files: Modify common/code/grc_error_code.py、config/translations/zh_Hant_TW/*.po、config/translations/en/*.po(或對應 json,依專案 i18n 載入機制)
GRC_ROUND_NO_WORKFLOW_BINDING = ("GRC_404xxx", "稽核輪次尚未綁定流程")
GRC_AP_REVIEWED_CONTROLS_REQUIRED = ("GRC_412xxx", "請先選定本輪查核控制項")
GRC_ROUND_PREP_TASKS_NOT_DONE = ("GRC_412xxx", "本輪準備任務尚未全部完成")lang.flow_engine.precondition.round_prep_tasks_done = 本輪準備任務尚未全部完成({count} 項待完成)
lang.flow_engine.precondition.ap_reviewed_controls_required = 請先選定本輪查核控制項
git add common/code/grc_error_code.py config/translations/
git commit -m "feat(FR-038): round flow error codes + precondition i18n"Files: Append scripts/sql/2026-06-16-fr038-round-flow-binding.sql
stage_object UPDATE 矩陣(D5 收斂後的最終狀態):
| code | complete_handler_key | precondition_key | allowed_predecessors | allowed_successors | 動作 |
|---|---|---|---|---|---|
planning |
launch_audit(原 activate_project) |
round_prep_tasks_done(原 all_tasks_*) |
[] |
["ap_authoring"] |
UPDATE |
ap_authoring |
submit_ap |
ap_reviewed_controls_set |
["planning"] |
["audit"] |
INSERT(新) |
audit |
confirm_audit |
ar_all_verdicts_filled |
["ap_authoring"] |
["poam"] |
UPDATE predecessors |
poam |
close_round |
poam_all_closed |
["audit"] |
["audit"] |
不動 |
task_execution / review |
— | — | — | — | 保留 table(builtin 不用;未來變體可復用) |
-- Date: 2026-06-16 FR-038 ap_authoring 階段 + planning 收斂
INSERT INTO compliance.stage_objects
(uid, code, name_i18n, kind, route_pattern, complete_button_label_i18n,
complete_handler_key, precondition_key, default_main_roles, can_start, can_end,
allowed_predecessors, allowed_successors, is_builtin, sort)
VALUES
(gen_random_uuid(), 'ap_authoring',
'{"zh_Hant_TW":"稽核計畫填寫","en":"Assessment Plan Authoring"}', 'routed',
'/project/projects/:projectUID/rounds/:roundUID/ap',
'{"zh_Hant_TW":"完成填寫,開始稽核","en":"Submit & Start Auditing"}',
'submit_ap', 'ap_reviewed_controls_set', '["auditor"]', false, false,
'["planning"]', '["audit"]', true, 15)
ON CONFLICT (code) DO NOTHING;
-- planning:handler activate_project→launch_audit;precondition→round_prep_tasks_done;successor→ap_authoring
UPDATE compliance.stage_objects
SET complete_handler_key='launch_audit', precondition_key='round_prep_tasks_done',
allowed_successors='["ap_authoring"]'
WHERE code='planning';
-- audit:predecessor 改 ap_authoring(不再從 task_execution/review 進)
UPDATE compliance.stage_objects
SET allowed_predecessors='["ap_authoring","poam"]'
WHERE code='audit';git add scripts/sql/2026-06-16-fr038-round-flow-binding.sql
git commit -m "feat(FR-038): ap_authoring stage seed + planning 收斂(task_execution 收進 planning)"Files: Append scripts/sql/2026-06-16-fr038-round-flow-binding.sql
目標串: 4 個 builtin(或本期至少 builtin-full-audit)BPMN 改成 Start → planning → ap_authoring → audit → (Gateway: has_findings) → poam → audit / End。每個 UserTask extension 帶 stage_object_code + main_role。先 SELECT bpmn_xml ... WHERE name='builtin-full-audit' 取既有當底,在 planning 與 audit 間插 ap_authoring UserTask,移除 task_execution/review 節點。
git add scripts/sql/2026-06-16-fr038-round-flow-binding.sql
git commit -m "feat(FR-038): builtin flow templates 改 canonical 4 階段(planning→ap_authoring→audit→poam)"Files: Modify domain/flow_engine/service/stage_completion_registry.py、app/flow_engine/service/stage_advance_service.py;Test test/test_stage_advance_round_context.py
def test_resolve_workflow_context_by_round(stage_advance_service, seeded_round_with_workflow):
ctx = stage_advance_service._resolve_workflow_context(seeded_round_with_workflow.uid)
assert ctx["workflow_execution_uid"] == seeded_round_with_workflow.workflow_execution_uid
assert "workflow_template_xml" in ctx
def test_round_status_derived_from_stage(stage_advance_service, seeded_round_at_ap_authoring):
# 推進 ap_authoring 後 round.status 應為 audit_planning(§4.1b 對照)
...def _resolve_workflow_context(self, round_uid: str) -> dict:
rnd = self._audit_round_domain_service.get_by_uid(round_uid)
if rnd is None or not rnd.workflow_execution_uid:
raise NotFound(GrcErrorCode.GRC_ROUND_NO_WORKFLOW_BINDING)
wf = self._workflow_execution_domain_service.get_workflow_execution(
WorkflowExecutionQueryEntity(uid=str(rnd.workflow_execution_uid)))
if wf is None:
raise NotFound(GrcErrorCode.GRC_ROUND_NO_WORKFLOW_BINDING)
return {"round": rnd, "assessment_plan_id": rnd.assessment_plan_id,
"workflow_execution_id": wf.id, "workflow_execution_uid": str(rnd.workflow_execution_uid),
"workflow_status": str(wf.status) if wf.status is not None else None,
"workflow_template_xml": wf.workflow_template_xml, "template_id": wf.template_id}audit_round_domain_service.update(status=...)。audit_round_app_service 的 launch_audit/start_auditing/finalize_audit/close_round:移除 e.status = STATUS_* 寫死(audit_round_app_service.py:201/229/252/254/289/301),保留 status guard(讀推進前值,仍正確)+ 保留業務寫入(ssp_id/assessment_plan_id/ar_result_id/poam_id)。finalize_audit:回傳 gateway 變數 has_findings(res["not_met_count"]>0)供 BPMN 路由(has_findings→poam=remediation / →End=closed);不自己寫 status。git add domain/flow_engine/service/stage_completion_registry.py app/flow_engine/service/stage_advance_service.py test/test_stage_advance_round_context.py
git commit -m "refactor(FR-038): stage 引擎 retarget round_uid + round.status BPMN 推導"Files: Modify app/grc/service/oscal_stage_handlers.py;Test test/test_round_stage_handlers.py
既有 6 handler 原 wrap
app/project/service/oscal_audit_service.py(2A dark no-op)。本任務全部建構子改注入audit_round_app_service、execute(round_uid, …)。activate_projecthandler 退役。
def test_submit_ap_handler_calls_start_auditing(submit_ap_handler, mock_round_service, seeded_round):
submit_ap_handler.execute(round_uid=seeded_round.uid, project_uid="p", user_id=1, curr_user="u", ctx={})
mock_round_service.start_auditing.assert_called_once()(autouse logger patch fixture,mirror test_ssp_excel_import_app_service.py:46)
class SubmitApOnCompleteHandler(IStageCompletionHandler):
def __init__(self, audit_round_app_service): self._rounds = audit_round_app_service
@property
def key(self): return "submit_ap"
def execute(self, round_uid, project_uid, user_id, curr_user, ctx):
return self._rounds.start_auditing(round_uid=round_uid, curr_user=curr_user, curr_user_id=user_id)其餘:launch_audit→self._rounds.launch_audit、confirm_audit→finalize_audit、close_round→close_round、terminal_close→round 標 closed、review_decision 保留(不 wire)。全部 __init__(self, audit_round_app_service)、execute(round_uid, ...)。
git add app/grc/service/oscal_stage_handlers.py test/test_round_stage_handlers.py
git commit -m "feat(FR-038): stage handler 全改 wrap audit_round + 新 submit_ap"Files: Modify app/grc/service/oscal_stage_preconditions.py、app/grc/service/assessment_plan_app_service.py;Test test/test_round_stage_preconditions.py
def count_reviewed_controls(self, ap_id: int) -> int:
return len(self._reviewed_repo.get_by_ap(ap_id) or [])class ApReviewedControlsSetCheck(IStagePreconditionCheck):
def __init__(self, assessment_plan_app_service, audit_round_domain_service):
self._ap, self._rounds = assessment_plan_app_service, audit_round_domain_service
@property
def key(self): return "ap_reviewed_controls_set"
def check(self, round_uid, project_uid, ctx):
rnd = self._rounds.get_by_uid(round_uid)
if not rnd or not rnd.assessment_plan_id:
return PreconditionResult.fail("lang.flow_engine.precondition.ap_reviewed_controls_required")
n = self._ap.count_reviewed_controls(rnd.assessment_plan_id)
return PreconditionResult.ok() if n > 0 else \
PreconditionResult.fail("lang.flow_engine.precondition.ap_reviewed_controls_required", count=n)
# RoundPrepTasksDoneCheck:per-AO 收證據 job 全 JobStatus.COMPLETED 才放行
# linkage:JobExecution 直接以 workflow_execution_id 過濾(workflow_execution_control_mapping
# 無 job_execution_id FK,不可 join 它)。⚠️ 收證據 job 由 _generate_prep_jobs 在「專案成立」建、
# 為專案層級(非 round 綁定)→ scoping「哪些 job 算本輪 prep」於實作時確認:
# 暫定查專案所有 prep WE 的 job 全 COMPLETED;無 prep job(template_uid 未設)→ ok 放行(不硬擋)。git add app/grc/service/oscal_stage_preconditions.py app/grc/service/assessment_plan_app_service.py test/test_round_stage_preconditions.py
git commit -m "feat(FR-038): precondition retarget round + reviewed_controls/prep_tasks 守門 + count_reviewed_controls"Files: Modify app/grc/service/audit_round_app_service.py;Test test/test_create_round_flow_binding.py
範本來源(O1 a+b): 入參 flow_template_uid(選填)> 前一輪 flow_template_snapshot_uid 對應 master > 資源庫 module_frames.audit_flow_template_uid。
def test_create_round_binds_flow_snapshot(round_service, seeded_project_with_resource_library):
r = round_service.create_round(project_uid=..., name="R1", round_type="initial", curr_user="u", curr_user_id=1)
assert r["flow_template_snapshot_uid"] is not None
assert r["workflow_execution_uid"] is not Nonegit add app/grc/service/audit_round_app_service.py test/test_create_round_flow_binding.py
git commit -m "feat(FR-038): create_round clone flow snapshot + 綁 round(O1 a+b 來源)"Files: Modify api/project/serializers/project.py(OscalProjectStartRequest 加回 flow_template_uid)、app/project/service/project_start_app_service.py(start_project 建 round 1);Test test/test_project_start_creates_first_round.py
def test_start_project_creates_initial_round_with_flow(project_start_service, seeded_resource_library):
res = project_start_service.start_project(name="P", resource_library_uid=..., flow_template_uid=<uid>, curr_user="u", curr_user_uid=..., curr_user_id=1)
rounds = audit_round_domain_service.list_by_project(res_project_id)
assert len(rounds) == 1 and rounds[0].round_type == "initial"
assert rounds[0].flow_template_snapshot_uid is not Nonegit add api/project/serializers/project.py app/project/service/project_start_app_service.py test/test_project_start_creates_first_round.py
git commit -m "feat(FR-038): 建專案順帶建第一輪 initial round + 綁所選流程範本(D6)"Files: Modify api/project/routes/audit_round_route.py、api/project/__init__.py、api/flow_engine/__init__.py;Remove api/flow_engine/routes/stage_advance_route.py;Test test/test_audit_round_stage_route.py
git add api/project/routes/audit_round_route.py api/project/__init__.py api/flow_engine/__init__.py test/test_audit_round_stage_route.py
git rm api/flow_engine/routes/stage_advance_route.py
git commit -m "feat(FR-038): round stage route + 退役舊 ap-scoped + 4 直接轉換 route(O2 收斂 stage/advance)"Files: Modify di_containers/grc/grc_containers.py、di_containers/flow_engine/*
git add di_containers/
git commit -m "feat(FR-038): DI wiring round stage handlers/preconditions + 拆 2A dark-skip"round_prep_tasks_done tooltip)public.system_logs 有 605x 推進紀錄寫完 dispatch plan-document-reviewer(spec=round-flow-engine-reintegration-design.md);❌ 修正重審;✅ handoff 執行。