# 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→planning`／`auditing→audit_planning`／`remediation→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` `PublishStatus` 加 `SUPERSEDED` 值——影響全部窮舉比對讀取點，跨套件異動需走發版流程。
- **選項 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_job` 加 `bypass_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_planning`（`auditing → 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. 相關文件

- [design.md](../design.md)（五決策 + 回退邊界表 + 驗收情境草案）
- [implementation-plan.md](../implementation-plan.md)（13 task 全鏈 + 四裁定 + §4.4 執行落點）
- 頁面 spec：[階段推進橫幅（FlowPhaseBanner）](../../../specs/v1.8.0/shared-components/flow-phase-banner.md)（本次同步更新，見其變更紀錄 2026-07-19 一行）
