FR-050 稽核輪次階段回退(Stage Rollback)— 2026-07-19 SUMMARY

0. 一句話

稽核輪次階段推進的「返回上一階段」功能已在 BE + FE 全鏈完工並在 DEV 手測/冒煙通過;STG migration 已套(2026-07-19 指揮官執行,兩表+GRANT+schema_migrations 對帳綠;POC 未套、累計缺 4 支),E2E 場景四件組派工中(test-case 文件階段),design §6 六個驗收情境目前只做過 API 面/單元測試層級的對應驗證,尚未做完整 live 端到端走查。

1. Commit 帳(BE 三支 + FE 一支)

Commit Repo 內容
c3b3126e BE docs(FR-050): implementation-plan 落地 — 13 task 全鏈規劃文件(migration/domain/rollback handlers×3/StageRollbackService/route/DI/repo)+ 四個 plan-time 裁定 + §4.4 superseded 裁決定案選 B
9ee32a27 BE feat(FR-050): 稽核輪次階段回退 BE 全鏈 — StageRollbackService + 三邊界 rollback handler + 階段歷程時間軸;新表 round_stage_transitions + round_rollback_supersessions;migration 已套 DEV;pytest 新增 31 全綠
9a1216ff BE fix(FR-050): rollback_to_audit_planning 的 from_stage 誤寫 round.status 值"auditing""audit"(stage_objects.code 值域),DEV 資料校正 1 筆 + 測試補值域斷言鎖回歸
141cfcd1 FE feat(FR-050): 稽核輪次階段回退 FE — FlowPhaseBanner 回退入口 + 階段歷程時間軸元件 + i18n + 錯誤碼對照

2. Plan 四項裁定(implementation-plan.md §1)

# 裁定 結論
D1 round_stage_transitions 是否加 tenant_id/org_unit_id/RLS 不加 — 母表 project_audit_rounds 本身無 tenant_id/org_unit_id/RLS(軟隔離由上層 projects 把關),本表沿用同一家族慣例
D2 rollback handler 機制 複用同一個 StageRegistry singleton,加 register_rollback_handler/get_rollback_handler 兩方法 + _rollback_handlers dict,不另開新 registry class
D3 三個回退邊界 v1 開放幾條 三條全開audit_planning→planningauditing→audit_planningremediation→auditing),對應 user 已拍板決策 A
D4 round_stage_transitions 是否含 advance 方向 — advance 路徑補一行落表呼叫(非破壞性 delta,未注入時 no-op)

§4.4 superseded 裁決(跨套件異動守門,選 B)

design §4.4 列出兩個選項讓 user 裁決是否動 jedi-oscal-v2 套件:

  • 選項 A:動套件列舉,jedi-oscal-v2 PublishStatusSUPERSEDED 值——影響全部窮舉比對讀取點,跨套件異動需走發版流程。
  • 選項 B(user 2026-07-19 拍板採用):本專案側新建標記表 compliance.round_rollback_supersessions不動 jedi-oscal-v2 任何檔。SSP/AP/POA&M 三種 entity 統一走本表機制;讀取封裝在 RoundRollbackSupersessionDomainService

理由:superseded 是 FR-050 回退流程的業務概念(天然帶 round_id + reason 脈絡),非 OSCAL 文件生命週期通用狀態,塞進套件 PublishStatus 屬 caller 業務知識下沉,違反套件異動分界原則;選項 A 的 grep 面(3 entity × 全部窮舉比對讀取點)成本不小。

3. 實作期兩個 plan-衝突裁決(開工後才浮現,非 plan 原定範圍)

# 衝突 裁決
1 revert_job 呼叫既有「規劃期才可退回」凍結 gate,與 FR-050 回退邊界本身即發生在 status != planning 時直接衝突(plan §6/§8.3 未預見) BE 層 WorkflowExecutionService.revert_jobbypass_round_frozen_gate 參數(app/flow_engine/service/workflow_execution_service.py:820),StageRollbackService 呼叫時傳 True;既有兩個呼叫點零行為變更(維持 False),test_workflow_execution_service_revert_job_bypass_gate.py 鎖回歸(bypass=False 仍擋)
2 error code 序號接續 plan §5 草稿序號(GRC_412029/GRC_400077/GRC_412030/GRC_400078)在實作時因期間有其他 PR 併入、序號已被佔用,改採實作當下最新序號接續:GRC_412046/GRC_400096/GRC_412047/GRC_400097(見 common/code/grc_error_code.py:338-341

4. from_stage 值域 bug 修正帳(commit 9a1216ff

rollback_to_audit_planningauditing → audit_planning 邊界)落 round_stage_transitions 時,from_stage 誤寫成 round.status"auditing",但欄位 schema COMMENT 規定一律存 stage_objects.code(正確值應為 "audit")——三個 rollback 方法中僅此一處混入錯值,FE 驗收時在 DEV DB 實查抓到(UI 時間軸顯示不一致)。

修正:

  • app/grc/service/audit_round_app_service.py 該行 from_stage"audit"
  • DEV 資料校正:UPDATE 1 筆既有錯誤紀錄
  • 測試補 STAGE_OBJECT_CODES frozenset 值域斷言,鎖三個 rollback 方法的 from_stage/to_stage 一律落在合法 stage_objects.code 集合內——回歸來源是既有測試斷言與 bug 錯得一致(同寫 auditing),此次一併修正測試斷言

5. 驗收狀態

BE

  • pytest 新增測試全綠:test_stage_rollback_service.py(9)+ test_oscal_stage_rollback_handlers.py(7)+ test_workflow_execution_service_revert_job_bypass_gate.py(2)+ test_api_stage_rollback_route_smoke.py(4)+ test_audit_round_app_service.py(9,含新增值域斷言)= 31 passed
  • test_api_stage_rollback_route_smoke.py 涵蓋 3 個手測情境的 API 面對應(route-level smoke,非起 DB 真整合):
    • 情境對應「reason 空 → 400」(schema 層 ValidationError)
    • 情境對應「非 manager → 403」(ForbiddenError 往外傳、route 不吞例外)
    • 情境對應「manager 呼叫成功」envelope 結構
    • GET transitions envelope 結構
  • migration 已套 DEV(兩表 + GRANT + schema_migrations 對帳綠)

FE

手測(DEV round 969ad600),commit message 記錄 6 項全過:

  1. manager 見「返回上一階段」按鈕
  2. 非 manager 隱藏按鈕
  3. closed 輪次無 banner(整個不顯示)
  4. planning 階段無回退按鈕(鏈頭無上一步)
  5. 推進/回退交替操作 5 次,時間軸即時正確刷新
  6. (修正兩個自抓 bug)transitionsStale 旗標機制:陣列非空後不再永不重抓、advance 後也會刷新

E2E

未做。無 test repo 場景派工紀錄。

6. 懸掛事項

項目 現況 備註
STG migration 已套(2026-07-19) DEV/STG 皆套且對帳綠(STG 兩表 + cm_app GRANT + schema_migrations 實查);POC 未套(累計缺 4 支:fr049 快照欄/fr0491/perf indexes/fr050,隨 POC 部署日補),見 docs/claude/sql-migration-conventions.md
E2E 場景 未派工 test repo 尚無 FR-050 對應 feature/step,待排入 site-regression 套件
design §6 驗收情境完整 live 驗證 只做過 API 面 design.md §6 六情境(1. planning↔︎audit_planning 往返含新 snapshot;2. auditing 回退 AR 保留可改;3. 非 manager/reason 空擋;4. closed 無入口;5. 時間軸多次往返正確;6. 零 DELETE 對帳)目前驗證深度:情境 3/4 有 route-level smoke + 單元測試覆蓋較完整;情境 1/2/6 需要真實跑一輪「推進→回退→重推」的完整 live 資料流(含確認重推後產生新 snapshot、AR 保留可編輯、DB 對帳零 DELETE)尚未做,只驗證了 handler 呼叫鏈與 mock 層級行為
§4.4 選項 A 未來反悔條件 若日後 OSCAL 文件生命週期需要通用 superseded 語意(非僅本案業務場景),才需重新評估選項 A(動套件列舉) 目前無觸發跡象

7. 相關文件