# FR-050 稽核輪次階段回退（Stage Rollback）— Design

> **緣由**：user 2026-07-18 提出——目前專案流程階段推進後無法退回，實務上不切實際（稽核實務本來就迭代：啟動稽核後發現 scope 缺漏、送審後被要求重寫現況說明等）。需求 = **有條件的返回上一步 + 完整留痕**。
> **狀態**：五決策已拍板（§2），設計骨架落定；implementation plan 待開工時做前提查證後撰寫。
> **業界對照**：Qualtrics / ServiceNow GRC / Archer 均有 stage regression（reopen / send back / return to draft），皆綁 justification 必填——本案形態與業界一致。

## 1. 為什麼要做

現行推進（`app/flow_engine/service/stage_advance_service.py:185` `advance_stage`）一次做三件事：業務 handler 副作用（寫 OSCAL state）→ BPMN 節點完成（`complete_main_workflow_job`）→ 輪次 status 前進。三者皆只有前向路徑（狀態機由 DB CHECK 強制值域）。按錯 / 需求變更時唯一出路是整輪作廢重開專案，成本不成比例。

回退最大的反對理由「稽核有效性被質疑」由三條設計合力關掉：closed 不可退 + 不刪資料只作廢標記 + UI 可見時間軸——**回退本身成為稽核軌跡的一部分，不是漏洞**。

## 2. 決策紀錄（user 2026-07-18 全數拍板）

| # | 決策 | 結論 |
|---|------|------|
| 1 | 回退範圍 | **只允許退回相鄰上一步**；跨階段 = 連按多次，每次各留一筆記錄 |
| 2 | closed 輪次 | **不可回退**。已結案要重來走既有模型（開新輪次 / 新專案） |
| 3 | auditing 回退時已填 AR | **保留 + 標記**（不作廢重建；稽核員已填內容不丟失） |
| 4 | 回退權限 | **manager only**（比照 force=true 先例，`stage_advance_service.py:297`） |
| 5 | 記錄形式 | **UI 可見的「階段歷程」時間軸**（誰 / 何時 / 何為 / 為何退），非僅 system_logs |

## 3. 回退邊界與副作用處置（開工前提查證要逐項 live 對）

推進的逆向不是均勻的，每個邊界成本不同（以下座標 2026-07-18 grep 核實）：

| 回退邊界 | 推進時副作用（handler） | 難度 | 副作用處置原則 |
|---|---|---|---|
| audit_planning → planning | `launch_audit`（`audit_round_app_service.py:352`）：snapshot living SSP + clone 程序書池 + 建 AP 草稿 | 低 | frozen SSP / AP 草稿**作廢標記**（superseded，不物理刪）；living SSP 本未動，退回後重推會產生新 snapshot |
| auditing → audit_planning | `start_auditing`：建 AR 全量矩陣 | 中 | AR 已填內容**保留 + 標記**（決策 3）；重推 start_auditing 時的「既有 AR 矩陣」處置為 plan 前提查證重點 |
| remediation → auditing | `finalize_audit`：AR 定版 + not_met 生 POA&M | 高 | POA&M 可能已在整改——作廢標記 + 已有整改紀錄保留；此邊界是否 v1 就開放，plan 時再裁 |
| closed → * | `close_round` | **禁止** | 決策 2 |

**鐵則：回退絕不物理刪資料，一律「作廢/superseded 標記 + 留痕」**——與 FR-049 快照哲學同源（歷史不漂移）。

## 4. 設計骨架

### 4.1 BE

- **新 endpoint**：`POST /project/<uid>/audit-round/<uid>/stage/rollback`，body 必填 `reason`（比照 review reject 必填 comment 的驗證，`stage_advance_service.py:264`）。
- **StageRollbackService**（flow_engine app service，鏡像 StageAdvanceService 結構）：manager 檢查 → 解析當前 stage → 查上一 stage → dispatch rollback handler（stage_registry 加 `rollback_handler_key`，僅對允許回退的邊界註冊）→ BPMN 回退 → 輪次 status 回寫 → 落歷程記錄。
- **BPMN 回退是唯一技術未知數**：`ReviewDecisionHandler` 註解提及「reject 走 BPMN reverse flow」但 builtin 未接——jedi-flow-engine 是否支援重開 COMPLETED job **為 plan 前提查證第一項**；若不支援，fallback＝不倒退 BPMN 節點、以「重建當前 stage 的 job execution」語意實作（對 UI 等價）。
- **稽核埋點**：鏡像 `[AUDIT:STAGE_ADVANCE]` pattern（requested / denied / completed），新 event_code `STAGE_ROLLBACK_*` 進 system_logs。

### 4.2 階段歷程（決策 5 的資料源）

- 新表 `compliance.round_stage_transitions`（round_id、from_stage、to_stage、direction(advance/rollback)、operator、reason(rollback 必填)、created_at）——advance 也同步落（歷程要完整，不能只記 rollback）。
- 既有 advance 路徑補一行落表（非破壞性 delta）。
- 查詢 endpoint：`GET .../stage/transitions` 回時間軸。

### 4.3 FE

- FlowPhaseBanner 加「返回上一階段」入口（manager only 可見）→ dialog 必填原因 → 呼叫 rollback。
- 階段時間軸元件（banner 展開或獨立 tab）：垂直 timeline，advance/rollback 雙色，rollback 顯示 reason。

## 5. Not in scope

- 跨階段一鍵回退（= 連按多次）
- closed 輪次重開（決策 2）
- 回退的雙人複核（v1 manager 單人；未來若客戶要求四眼原則再開）
- remediation → auditing 邊界是否 v1 開放，plan 時裁決

## 6. 驗收情境（草案，plan 時細化）

1. planning 推進到 audit_planning 後回退 → frozen SSP/AP 標 superseded、輪次回 planning、重推產生新 snapshot、時間軸兩筆（advance + rollback 含 reason）。
2. auditing 回退 → 已填 AR 保留可見（帶標記）、回 audit_planning 可改 AP、重推後稽核員接續。
3. 非 manager 按回退 → 403；reason 空 → 400。
4. closed 輪次無回退入口（API 打也擋）。
5. 時間軸完整呈現多次往返（誰/何時/方向/原因），順序正確。
6. 回退不物理刪任何資料（DB 對帳：僅新增標記欄/記錄，零 DELETE）。
