版本:v1(Phase 3) 建立日期:2026-05-12 依據:
design.mdv2(含 §九 決議) 跨 repo:compliance-manager-be / compliance-manager-fe / compliance-manager-test
Plan 寫好到開工經常 days/weeks,期間 method 改名 / entity 改 shape / error code 序號被佔。實作前先 grep 核對:
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
# 1. OscalAuditService 4 個 method 簽名仍存在
grep -n "def activate_project\|def launch_audit\|def confirm_audit\|def close_round" \
app/project/service/oscal_audit_service.py
# 預期:line 77 / 169 / 323 / 419,簽名 (project_uid, [user_id,] ap_uid, [force,] curr_user)
# 2. WorkflowExecutionService.start_workflow_execution / complete_job 仍存在
grep -n "def start_workflow_execution\|def complete_job" \
app/flow_engine/service/workflow_execution_service.py
# 預期:line 129 / 419
# 3. Stage object schema 已 seed 4 個
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev \
-c "SELECT code, name_zh, complete_handler_key, precondition_key FROM compliance.stage_objects ORDER BY id;"
# 預期:planning / task_execution / audit / poam 4 列
# 4. Spec 2 預留 error code 序號未被佔
grep -E "GRC_412021|GRC_403050|GRC_400050|GRC_404029|GRC_400051|GRC_400052" \
common/code/grc_error_code.py
# 預期:6 個都查不到
# 5. compliance.assessment_plan_extensions 表還沒建
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev \
-c "\d compliance.assessment_plan_extensions"
# 預期:Did not find any relation任一條對不上 → 暫停實作,回 design.md 同步差異後再開工。
compliance.assessment_plan_extensionsstart_oscal_project 流程:建 AP 時同步寫 extension + 啟動 workflow_executionExtAssessmentPlanRepoImplFlowPhaseBanner.vue + useStageInfo composable + 4 個 view 注入 + 4 個推進按鈕拆除Phase A: BE schema + Registry interface
↓
Phase B: BE stage advance API + grc handlers + AP create flow 改造
↓ (BE 可獨立 manual test:用 curl + dev DB)
Phase C: FE Banner 元件 + composable + service
↓
Phase D: FE 4 個 view 注入 + 拆既有按鈕
↓ (BE+FE 跨 repo manual smoke test)
Phase E: E2E BDD scenarios
↓
Phase F: Phase 6 review + changelog + push
並行機會:
檔案:scripts/sql/2026-05-XX-spec2-ap-extensions-and-handler-error-codes.sql
內容:
-- Date: 2026-05-XX
-- Spec 2 階段抽象整合 — AP extension table + RLS
-- 1. assessment_plan_extensions 延伸表 (1:1 of oscal.assessment_plans) (2026-05-XX)
CREATE TABLE compliance.assessment_plan_extensions (
id SERIAL PRIMARY KEY,
assessment_plan_id INTEGER UNIQUE NOT NULL,
workflow_execution_uid UUID,
flow_template_snapshot_uid UUID,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
created_user VARCHAR(50),
updated_user VARCHAR(50),
CONSTRAINT fk_ape_assessment_plan
FOREIGN KEY (assessment_plan_id)
REFERENCES oscal.assessment_plans(id)
ON DELETE CASCADE
);
-- 2. 索引 (2026-05-XX)
CREATE INDEX ix_ape_workflow_execution_uid
ON compliance.assessment_plan_extensions(workflow_execution_uid);
CREATE INDEX ix_ape_flow_template_snapshot_uid
ON compliance.assessment_plan_extensions(flow_template_snapshot_uid);
-- 3. 權限授予(CLAUDE.md 規範)(2026-05-XX)
GRANT SELECT, INSERT, UPDATE, DELETE ON compliance.assessment_plan_extensions TO cm_app;
GRANT USAGE, SELECT ON SEQUENCE compliance.assessment_plan_extensions_id_seq TO cm_app;
-- 4. RLS policy — 跟著 assessment_plans 走(tenant 透過 AP JOIN 過濾) (2026-05-XX)
-- 不開 RLS:本表純為 AP 1:1 延伸欄位,無獨立 tenant 隔離需求;
-- AP 本身在 oscal schema 由 jedi_common.session_scope 透過 GUC 處理 RLS。
-- 若未來需 RLS:可考慮 EXISTS subquery 比對 oscal.assessment_plans 的 metadata。
-- 5. 補既有 AP 的 ext 列(dev/stg 既有資料 backfill) (2026-05-XX)
INSERT INTO compliance.assessment_plan_extensions (assessment_plan_id, created_user, updated_user)
SELECT id, COALESCE(created_user, 'system'), COALESCE(updated_user, 'system')
FROM oscal.assessment_plans
WHERE id NOT IN (SELECT assessment_plan_id FROM compliance.assessment_plan_extensions)
ON CONFLICT (assessment_plan_id) DO NOTHING;Rollback:DROP TABLE compliance.assessment_plan_extensions CASCADE;
部署順序:先跑此 migration → 再部署 BE code(code 引用此表)。
檔案:common/code/grc_error_code.py(追加)
# Spec 2 Stage Integration (2026-05-XX)
GRC_STAGE_PRECONDITION_FAILED = ("階段推進前置條件未滿足", "GRC_412021")
GRC_STAGE_ROLE_FORBIDDEN = ("使用者不具備此階段推進權限", "GRC_403050")
GRC_STAGE_NO_NEXT_NODE = ("流程推進失敗:找不到下一節點", "GRC_400050")
GRC_AP_NO_WORKFLOW_BINDING = ("稽核計畫未綁定流程實例", "GRC_404029")
GRC_STAGE_HANDLER_NOT_REGISTERED = ("stage handler 未註冊", "GRC_400051")
GRC_STAGE_OBJECT_NOT_BOUND = ("BPMN UserTask 未綁定階段物件", "GRC_400052")檔案:domain/flow_engine/service/stage_completion_registry.py(新增)
"""Stage completion registry — flow_engine domain abstraction.
flow_engine 不知道 OSCAL business logic 存在。具體 handler 在 app/grc/ 註冊。
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class PreconditionResult:
passed: bool
reason_i18n_key: Optional[str] = None
context: dict = field(default_factory=dict)
class IStageCompletionHandler(ABC):
"""副作用 handler — stage 推進時呼叫。"""
@property
@abstractmethod
def key(self) -> str:
"""對應 stage_objects.complete_handler_key"""
...
@abstractmethod
def execute(
self,
ap_uid: str,
project_uid: str,
user_id: int,
curr_user: str,
ctx: dict,
) -> dict:
"""執行副作用;ctx 含 force / loopback_decision 等選用參數。
回傳 dict 直接送回 stage_advance API 的 response data。
"""
...
class IStagePreconditionCheck(ABC):
"""前置條件檢查 — stage 推進前呼叫。"""
@property
@abstractmethod
def key(self) -> str:
"""對應 stage_objects.precondition_key"""
...
@abstractmethod
def check(
self, ap_uid: str, project_uid: str, ctx: dict
) -> PreconditionResult:
...
class StageRegistry:
"""Singleton — 啟動時 DI container 註冊 handler / precondition instance。
flow_engine app service 透過此 registry 查 handler,不直接 import grc。
"""
def __init__(self):
self._handlers: dict[str, IStageCompletionHandler] = {}
self._preconditions: dict[str, IStagePreconditionCheck] = {}
def register_handler(self, handler: IStageCompletionHandler) -> None:
self._handlers[handler.key] = handler
def register_precondition(self, check: IStagePreconditionCheck) -> None:
self._preconditions[check.key] = check
def get_handler(self, key: str) -> Optional[IStageCompletionHandler]:
return self._handlers.get(key)
def get_precondition(self, key: str) -> Optional[IStagePreconditionCheck]:
return self._preconditions.get(key)DI wiring:di_containers/flow_engine/flow_engine_containers.py 加:
stage_registry = providers.Singleton(StageRegistry)新增:
domain/grc/entities/assessment_plan_extension_entity.py — Entity + QueryEntitydomain/grc/repository/i_assessment_plan_extension_repo.py — Interfacedomain/grc/service/assessment_plan_extension_domain_service.py — DomainServiceinfra/grc/model/assessment_plan_extension.py — ORM Modelinfra/grc/mapper/assessment_plan_extension_mapper.py — Mapperinfra/grc/repository/assessment_plan_extension_repo_impl.py — Impl(繼承 BaseRepositoryImpl)核心 method:
get_by_ap_id(ap_id: int) -> Optional[Entity]get_by_ap_uid(ap_uid: str) -> Optional[Entity] — via JOINset_workflow_execution_uid(ap_id, workflow_execution_uid, curr_user)set_flow_template_snapshot_uid(ap_id, template_uid, curr_user)add / update / deactivateDI wiring:di_containers/grc/grc_containers.py 加 repo(Singleton)+ domain service(Factory)。
Commit 顆粒:A.1 / A.2 / A.3+A.4 共 3 個 commit(A.3 與 A.4 互相依賴 DI wiring,併一個 commit)
檔案:app/grc/service/oscal_stage_handlers.py
"""OSCAL stage completion handlers — bridge between flow_engine and OSCAL audit lifecycle.
每個 handler key 對應 stage_objects.complete_handler_key,由 DI container 啟動時註冊到 StageRegistry。
"""
from domain.flow_engine.service.stage_completion_registry import IStageCompletionHandler
class PlanningOnCompleteHandler(IStageCompletionHandler):
"""planning stage 推進 → 呼叫 OscalAuditService.activate_project."""
def __init__(self, oscal_audit_service):
self._oscal_audit_service = oscal_audit_service
@property
def key(self) -> str:
return "oscal.planning.activate"
def execute(self, ap_uid, project_uid, user_id, curr_user, ctx) -> dict:
force = ctx.get("force", False)
return self._oscal_audit_service.activate_project(
project_uid=project_uid, ap_uid=ap_uid, user_id=user_id,
force=force, curr_user=curr_user,
)
class TaskExecutionOnCompleteHandler(IStageCompletionHandler):
key = "oscal.task_execution.launch_audit" # 同上 pattern, 略
class AuditOnCompleteHandler(IStageCompletionHandler):
key = "oscal.audit.confirm_audit"
class PoamOnCompleteHandler(IStageCompletionHandler):
key = "oscal.poam.close_round"注意:4 個 handler 不在自己內加 @transaction — 由上層 StageAdvanceService 開 scope。OscalAuditService 各 method 自己有 @transaction,會嵌套。需確認 jedi_common.transaction 是否支援嵌套(既有 confirm_audit 內部也呼叫 close_round,已是嵌套用法,pattern 安全)。
檔案:app/grc/service/oscal_stage_preconditions.py
class TasksThresholdCheck(IStagePreconditionCheck):
"""task_execution → audit 推進前檢查未完成 task 數。
與 launch_audit 內既有 unassigned/incomplete 檢查邏輯 align;
若 force=true 由 caller 在 StageAdvanceService 跳過此 check。
"""
@property
def key(self): return "oscal.tasks_threshold_check"
def check(self, ap_uid, project_uid, ctx):
incomplete = self._oscal_audit_query.get_incomplete_task_count(ap_uid)
if incomplete > 0:
return PreconditionResult(
passed=False,
reason_i18n_key="flow_engine.precondition.tasks_not_complete",
context={"incomplete": incomplete},
)
return PreconditionResult(passed=True)
class AllControlsVerdictedWithFinding(IStagePreconditionCheck):
key = "oscal.all_controls_verdicted_with_finding"
# 對應 GRC_412003 + GRC_412011 既有檢查
class AllPoamClosed(IStagePreconditionCheck):
key = "oscal.all_poam_closed"
# 對應 GRC_412005 既有檢查注意:這些 check 與 OscalAuditService 內既有 raise PreconditionFailedError 重複; 本 spec 把 check 前置 到 stage advance API,提早回給 FE disable 按鈕 + tooltip; 真正寫入時 OscalAuditService 內仍會再 raise(雙保險,避併發 race)。
檔案:app/flow_engine/service/stage_advance_service.py
class StageAdvanceService:
def __init__(
self,
stage_registry,
stage_object_domain_service,
ap_extension_domain_service,
workflow_execution_service,
flow_template_domain_service,
project_participant_domain_service,
grc_project_domain_service,
user_domain_service,
):
...
@transaction
def get_current_stage_info(self, ap_uid: str, user_id: int) -> dict:
"""Banner 用:回傳當前 stage code + name + progress bar + 推進按鈕 metadata.
Response:
{
"current_stage": {
"code": "planning",
"name_i18n": "規劃",
"complete_button_label_i18n": "啟動專案",
"complete_handler_key": "oscal.planning.activate",
"precondition_key": null,
"main_roles": ["manager"]
},
"user_can_advance": true,
"precondition": {"passed": true, "reason_i18n_key": null, "context": {}},
"progress": [
{"code": "planning", "name_i18n": "規劃", "status": "done"},
{"code": "task_execution", "name_i18n": "執行任務", "status": "current"},
{"code": "audit", "name_i18n": "稽核", "status": "pending"},
{"code": "poam", "name_i18n": "缺失改善", "status": "pending"}
],
"ap_status": "active",
"workflow_execution_uid": "<uuid>"
}
"""
# 1. 查 AP extension 拿 workflow_execution_uid + flow_template_snapshot_uid
# 2. 查 workflow_execution 當前 user task → 找到對應 stage_object
# 3. 查 user 的 participant role → 判 user_can_advance
# 4. 跑 precondition check(不 raise,回 PreconditionResult)
# 5. 解 flow_template snapshot BPMN 取出 stage sequence 算 progress
...
@transaction
def advance_stage(self, ap_uid: str, user_id: int, curr_user: str, force: bool = False, ctx: dict = None) -> dict:
"""推進當前 stage:
1. 查 AP extension → workflow_execution
2. 查 current user task → stage_object
3. 主要角色驗證(manager / auditor / reviewer / viewer,依 stage default_main_roles 或 BPMN UserTask 覆寫)
4. precondition.check();若 fail 且 not force:raise PreconditionFailedError(GRC_STAGE_PRECONDITION_FAILED)
5. handler.execute() ← 副作用呼叫 OscalAuditService(包 force / ctx)
6. workflow_execution_service.complete_job() ← 推進 BPMN 到下個節點
7. 若 BPMN 已到 EndEvent → 由 close_round handler 已處理(不額外動)
8. 回傳新 stage info(recursive call get_current_stage_info)
"""
...關鍵設計:
mainRole extension(spec 1 編輯器存的),fallback 到 stage_object.default_main_rolesforce 僅 manager 角色可用(FE 不傳給其他角色 + BE 驗證)ctx 內可帶 loopback_decision (預留,v1 不用)檔案:api/flow_engine/routes/stage_advance_route.py
| Endpoint | Method | Resource |
|---|---|---|
/project/<project_uid>/ap/<ap_uid>/stage/info |
GET | StageInfoResource |
/project/<project_uid>/ap/<ap_uid>/stage/advance |
POST | StageAdvanceResource |
Request schema(advance):
{ "force": false, "ctx": {} }Schema 命名:
api/flow_engine/serializers/stage_advance_request_schema.pyapi/flow_engine/serializers/stage_info_response_schema.pyRBAC capability:
audit_module.read 等既有 capabilitystart_oscal_project 接入 workflow_execution(1 commit)位置:app/project/service/oscal_project_service.py:start_oscal_project (line 143)
新增步驟(Step 22, 在既有 21 步之後):
# Step 22 — 建 AP Extension + 啟動 main workflow_execution
from app.flow_engine.service.flow_template_app_service import ...
from app.flow_engine.service.workflow_execution_service import ...
# 22.1 取 hardcode default 範本(Spec 2):builtin-full-audit
default_template = self._flow_template_app_service.get_by_code("builtin-full-audit")
# 22.2 啟動 main workflow_execution(依範本 BPMN)
main_wf = self._workflow_execution_service.start_workflow_execution(
template_id=default_template.id,
name=f"AP-{ap.uid}-main",
params={"ap_uid": str(ap.uid), "project_uid": project_uid},
created_user=curr_user,
)
# 22.3 寫 AP extension
self._ap_extension_domain_service.add(
AssessmentPlanExtensionEntity(
assessment_plan_id=ap.id,
workflow_execution_uid=main_wf.uid,
flow_template_snapshot_uid=default_template.uid,
created_user=curr_user,
updated_user=curr_user,
)
)try/except:22.2 / 22.3 失敗 → log error 但不 rollback AP 建立(沿用既有 Step 7 的容錯策略);user 看到「workflow 啟動失敗」warning,可手動 retry。
注意:start_workflow_execution 既有實作會建立 task-level workflow execution 給各 AP task;本 Spec 2 加的是 main workflow 給 AP lifecycle,這是獨立 instance(不同 template)。需確認 spec 1 的 workflow_execution table 是否支援這雙層結構 — 預期支援,spec 1 已 seed is_main 欄位。
現況:
domain/flow_engine/repository/i_ext_assessment_plan_repo.py + infra/.../ext_assessment_plan_repo_impl.py 走 raw SQL 直接 UPDATE oscal.assessment_plans.status='closed'FlowExecutionService._on_end_event_reached(spec 1 文件提及)改造:
_on_end_event_reached 改:若 BPMN ctx 含 ap_uid 且當前 last completed UserTask 對應 poam 或 audit(無 poam 路徑)stage,不額外動 AP 狀態 → close_round / confirm_audit 由 stage advance API 在 user 按 Banner 時就已呼叫為什麼:避免「兩條路徑同時改 AP 狀態」造成幽靈轉換;handler 是 single source of truth。
位置:spec 1 已 seed 4 個 stage object,但 complete_handler_key / precondition_key 是 placeholder。本 spec 補實 handler key:
SQL:scripts/sql/2026-05-XX-spec2-update-stage-object-handler-keys.sql
-- Spec 2 — 補 stage_objects 的 handler key 與 precondition key (2026-05-XX)
UPDATE compliance.stage_objects SET complete_handler_key = 'oscal.planning.activate',
precondition_key = NULL WHERE code = 'planning';
UPDATE compliance.stage_objects SET complete_handler_key = 'oscal.task_execution.launch_audit',
precondition_key = 'oscal.tasks_threshold_check' WHERE code = 'task_execution';
UPDATE compliance.stage_objects SET complete_handler_key = 'oscal.audit.confirm_audit',
precondition_key = 'oscal.all_controls_verdicted_with_finding' WHERE code = 'audit';
UPDATE compliance.stage_objects SET complete_handler_key = 'oscal.poam.close_round',
precondition_key = 'oscal.all_poam_closed' WHERE code = 'poam';Commit 顆粒:B.1+B.2 / B.3+B.4 / B.5 / B.6 / B.7 共 5 個 commit
新增:src/service/StageService.js(繼承 BaseService)
getStageInfo(projectUid, apUid)advanceStage(projectUid, apUid, payload)API 常數:src/config/api/api.js 加:
STAGE_INFO: '/project/:projectUid/ap/:apUid/stage/info',
STAGE_ADVANCE: '/project/:projectUid/ap/:apUid/stage/advance',useStageInfo composable(1 commit)新增:src/composables/useStageInfo.js
export function useStageInfo(projectUid, apUid) {
const stageInfo = ref(null)
const loading = ref(false)
const error = ref(null)
async function fetchStageInfo() { ... }
async function advance({ force = false, ctx = {} } = {}) { ... }
return { stageInfo, loading, error, fetchStageInfo, advance }
}FlowPhaseBanner 元件(1 commit)新增:src/components/grc/FlowPhaseBanner.vue
Props:
projectUid: String (required)apUid: String (required)內部結構(對齊 design.md §四):
<div class="flow-phase-banner">
<div class="stage-header">
<span class="stage-name">{{ t(stageInfo.current_stage.name_i18n) }}</span>
</div>
<StageProgressBar :progress="stageInfo.progress" /> <!-- v1 純預覽 -->
<div class="stage-hint">{{ t(stageInfo.current_stage.hint_i18n) }}</div>
<Button
v-if="stageInfo.current_stage && canAdvance"
:disabled="!preconditionPassed"
v-tooltip.left="preconditionTooltip"
@click="onAdvanceClick">
{{ t(stageInfo.current_stage.complete_button_label_i18n) }}
</Button>
<ConfirmDialog ... /> <!-- force override 用 -->
</div>
子元件:src/components/grc/StageProgressBar.vue(純展示 4 dot + label,CSS only)
互動:
force=true 送出fetchStageInfo refresh banner + emit stage-advanced 給父 view(讓父 view refresh 自己的資料)i18n key 設計:
flow_engine.banner.stage_name.<code> ← 從 stage_object.name_i18nflow_engine.banner.button_label.<code> ← 從 stage_object.complete_button_label_i18nflow_engine.precondition.<reason_key> ← 從後端 PreconditionResult.reason_i18n_keyi18n files:src/i18n/zh-tw.js + src/i18n/en.js 加 reason key(4 條:tasks_not_complete / verdict_incomplete / finding_required / poam_not_all_closed)
Commit 顆粒:C.1+C.2+C.3 共 1 個大 commit(內聚性高,難拆)
位置:src/views/grc/ProjectSettingsView.vue 改動:
<FlowPhaseBanner :project-uid="..." :ap-uid="..." />(apUid 從 route param 或 store 取)activateProject API,整個區塊刪除)stage-advanced → router push 或 refresh dashboard位置:src/views/grc/ProjectAuditorOverview.vue 改動:
FlowPhaseBanner位置:src/views/grc/AuditReviewView.vue 改動:
FlowPhaseBanner位置:src/views/grc/PoamView.vue 改動:
FlowPhaseBanner位置:src/views/grc/MyTasksView.vue 改動:無(spec 明確不在此頁掛 Banner,待辦任務由 AP.status + 角色過濾資料)
src/i18n/zh-tw.js / en.js 補 4 個 stage 的 banner 文案docs/claude/frontend-overview.md 加 Banner 元件條目Commit 顆粒:D.1~D.4 各 1 commit + D.6 1 commit = 5 commits
預計 3 個 BDD scenario:
Happy path — 完整 BPMN 推進
前置條件未滿足 — task_execution 階段
角色擋 — auditor 在 planning 嘗試推進
(由 feature-test-planner agent 處理 — 包含 BE pytest + FE component test + BDD step / page object 規劃)
code-reviewer x 2(review BE 跨 5 commits / review FE 跨 5 commits)silent-failure-hunter(特別檢 try/except 區塊:Step 22 容錯邏輯 / handler chain)pr-test-analyzer(test coverage gap)type-design-analyzer(Registry + Interface 設計)docs/changelog/2026-05-XX-feat-stage-integration-be.mdcompliance-manager-fe/docs/changelog/2026-05-XX-feat-stage-integration-fe.mdcompliance-manager-test/docs/changelog/2026-05-XX-feat-stage-integration-e2e.md若實作過程發現偏差(90% 機率會有),補 design.md §十 Reconciliation 段 — 跟 Spec 1 §九 同 pattern。
| 規範 | 落實位置 | 自我檢查 |
|---|---|---|
| Route 不查 DB | api/flow_engine/routes/stage_advance_route.py | ✓ Resource 只 call app service |
App Service @transaction |
StageAdvanceService.get_current_stage_info / advance_stage | ✓ |
Repo session lazy (@property 或繼承 BaseRepositoryImpl) |
AssessmentPlanExtensionRepoImpl | ✓ 繼承 BaseRepositoryImpl |
| 寫入 API 角色檢查 | advance_stage 內 stage main role 驗證 | ✓ raise GRC_STAGE_ROLE_FORBIDDEN |
| 前置條件驗證 | precondition check 在 advance_stage 內 | ✓ raise GRC_STAGE_PRECONDITION_FAILED |
Error code 命名 GRC_<HTTP><3 位序號> |
grc_error_code.py 加 6 條 | ✓ 412021 / 403050 / 400050 / 404029 / 400051 / 400052 |
| SQL migration 日期註解 + GRANT | scripts/sql/2026-05-XX-spec2-*.sql | ✓ |
| 審計欄位 created_user + created_user_name | StageInfoResponseSchema 內如有 user_id 必補 nickname | ✓(v1 response 不含 user_id) |
| RLS | AP extension 不開 RLS(透過 AP 關聯自然隔離) | ✓ 文件已說明 |
| DDD 嚴格規範 | flow_engine domain 不 import grc / oscal | ✓ Registry pattern |
| 跨模組依賴 | DI container wiring | ✓ grc_containers 註冊 handler 到 flow_engine registry |
1. dev DB 跑 scripts/sql/2026-05-XX-spec2-ap-extensions-and-handler-error-codes.sql
2. dev DB 跑 scripts/sql/2026-05-XX-spec2-update-stage-object-handler-keys.sql
3. 重啟 BE(pkill -9 + 重起,CLAUDE.md 規範)
4. FE npm run build:DEV → deploy dist/
5. E2E 跑一次完整 scenarios
DROP TABLE compliance.assessment_plan_extensions CASCADE; + UPDATE stage_objects SET ... = NULL| Risk | 機率 | 影響 | Mitigation |
|---|---|---|---|
start_workflow_execution 雙層(main + task)table 不支援 |
低 | 高 | Pre-flight grep workflow_executions schema 確認 is_main 欄位;若無需另開欄位 |
@transaction 嵌套 commit 邏輯衝突 |
低 | 中 | 既有 confirm_audit→close_round 已嵌套,pattern 安全 |
| Handler chain 中 OscalAuditService raise 後 BPMN advance 已執行 → 資料不一致 | 中 | 高 | 在 stage advance service 內先 handler.execute → 再 complete_job;handler 走 OscalAuditService 既有 @transaction rollback,BPMN 推進在 commit 後 |
Spec 1 builtin BPMN 的 pm_decides_loopback placeholder 在 v1 不被 evaluate → gateway 走錯路徑 |
中 | 中 | Phase B 確認 BPMN engine 對 placeholder default 行為(預期走 default flow=closed) |
| FE Banner 在 task_execution stage(無 routed view 對應)顯示位置 | 低 | 低 | 仍掛在 overview 頁,stage_object.kind=stateful 時 banner 文案改「您正在執行任務階段...」 |
| backfill SQL 把暫時沒對應 AP 的孤兒 ext 列建出來 | 低 | 低 | INSERT ... ON CONFLICT DO NOTHING + WHERE NOT IN,無風險 |
| Phase | 工作量 | 累計 |
|---|---|---|
| Pre-flight + Phase A | 0.5 | 0.5 |
| Phase B | 1.5 | 2.0 |
| Phase C | 1.0 | 3.0 |
| Phase D | 1.0 | 4.0 |
| Phase E(test-plan + scenario) | 1.5 | 5.5 |
| Phase F(review + changelog + push) | 0.5 | 6.0 |
合計:6 人/天(單一 BE session 連續跑可壓到 4 天)
| 預埋項目 | 將在哪個 Spec 啟用 |
|---|---|
assessment_plan_extensions.flow_template_snapshot_uid 欄位(Spec 2 已寫但用 hardcode) |
Spec 3:建 AP wizard 寫入 user 選的範本 uid |
StageAdvanceService.advance_stage 的 ctx.loopback_decision param |
未來 spec:PM Dialog 二擇一 |
| Stage progress bar 的 click handler | 未來 spec:互動式跳轉 |
| Stage object schema 的「kind=stateful」分支 | 已用:task_execution banner 文案差異 |
| 版本 | 日期 | 變更 |
|---|---|---|
| v1 | 2026-05-12 | 初版實作計畫(Phase 3 產出,依 design.md v2 §九 決議) |