# FR-038 Round Flow FE 遷移 — 換 session Handoff（2026-06-17）

| 項目 | 內容 |
|------|------|
| 緣由 | 接續 round-flow 再整合：把專案生命週期 FE 從舊 AP-centric 引擎逐頁遷到 v2 audit_round + living/round SSP |
| BE branch | `feature/oscal-refactor`（**勿切 branch**；branch 不對停下問 user）|
| FE branch | 同名 `feature/oscal-refactor`（`~/Projects/Billows/Audit-Manager/compliance-manager-fe/`）|
| 下一棒主任務 | **專案規劃頁 `ProjectPlanningView` 資料沒帶出來** → 比照總覽遷到 v2 SSP control-tree |
| push 狀態 | BE + FE 都有未 push commits → **push 等 user 明示** |
| 驗證狀態 | 本弧全部已 user 手測通過（含 per-AO 任務 / `get_job` drawer，2026-06-17）|

---

## 🧭 原始需求 / WHY（先懂這個再動 code）

**大圖**：FR-038 把 OSCAL 從 jedi_oscal v1 整碗換 v2。本弧處理 **專案生命週期 FE 逐頁從舊 AP 引擎遷到 v2 audit_round**。GRC 業務層在 Wave 2A 被大量 dark，這弧一頁一頁把資料源接回 v2。

**這條線的需求（user 對話拍板）**：
- 專案總覽 / 規劃頁的「控制項 + 現況說明 + 任務」資料源，依**是否進入稽核階段**切換：
  - **未進稽核階段（planning / 規劃）→ 從「主專案 living SSP」拉資料**（建專案 clone 的脫鉤公版，錨在 `compliance.project_extensions.living_ssp_id`）。
  - **進入稽核階段後 → 從「該 round 的 frozen SSP」拉資料 + 編輯（如果可以）**（`launch_audit` 時 snapshot living SSP → `project_audit_rounds.ssp_id`，邊界③）。
- 此切換是 OSCAL/NIST 800-53A 的 Prepare（規劃補 SSP + 收證據）vs Assess（稽核）分界。設計依據見 `round-flow-engine-reintegration-design.md`（D5 stage 模型 / 邊界③ snapshot / status 對照表）。

**SSP-source 切換在 FE 怎麼自然落地（關鍵）**：
總覽 / 規劃頁的 `sspUid` computed 都是
```
sspUid = currentRound.ssp_uid  ??  project.ssp.id  ??  project.ssp_uid
```
- **planning 輪**：`round.ssp_id` 為 NULL（snapshot 還沒做）→ fallback 到 `project.ssp.id`（= living SSP uuid，BE `get_project` 已回，見下）→ **自動拉 living SSP**。
- **launch_audit 後**：`round.ssp_id` 有值（frozen snapshot）→ `currentRound.ssp_uid` 有值 → **自動拉該 round frozen SSP**。
> 所以「同一段 sspUid 邏輯」就涵蓋兩階段，不需 if/else 判 stage。規劃頁照抄總覽即可。

**冷接自檢 4 問（答不出回去讀設計）**：
1. planning 階段控制項 / 現況說明從哪拉？（→ 主專案 living SSP，`project_extensions.living_ssp_id`）
2. 何時切換成 round 的 SSP？（→ `launch_audit` snapshot 後，`round.ssp_id` 有值）
3. FE 怎麼自動切換、不用判 stage？（→ `sspUid = round.ssp_uid ?? project.ssp.id`，planning 輪 round.ssp_uid 為 null 自動 fallback）
4. 規劃頁該照誰實作？（→ 剛遷好的 `ProjectAuditorOverview`，§3 參考實作）

---

## §0 接手讀序（按序）

1. 🔒 本文件「原始需求 WHY」全讀
2. `round-flow-engine-reintegration-design.md` §1~§4（D5 stage / 邊界③ / status 對照）
3. §3 參考實作：`ProjectAuditorOverview.vue` 的 round-scoped 遷移（已完成，照它做規劃頁）
4. §1 本 session 完成清單 + §4 踩過的雷

---

## §1 本 session 完成（BE + FE，已 commit）

### 1.1 GRC 列表 / 刪除 / 建專案修補
| commit | 內容 |
|---|---|
| `49aca1ab` | 批次刪除掛掉 — `batch_delete_projects` 補 `@transaction` |
| `9a617180` | 建專案順帶建第一輪 initial + 綁流程範本（D6, Task 8b）|
| `901d173`(FE) | 建專案後導到 round-scoped 總覽（`project-auditor-overview-round` + `first_round_uid`）|

### 1.2 輪次列表（ProjectApListView）改撈 audit_round
| commit | 內容 |
|---|---|
| `e200b01`(FE) | `/project/projects/:id` 列表來源 AP→`POST /projects/<id>/audit-rounds/list`；建輪次 / launch-audit / launch-reverify；整列可點進總覽 |

### 1.3 專案總覽 round-scoped + 控制項樹 + 現況說明（**參考實作**）
| commit | 內容 |
|---|---|
| `03fb83bf` | `get_project_by_uid` 從 dark 重建（v2）— 修總覽「找不到專案」；**回 living SSP uuid**（`project_extensions.living_ssp_id` → `project.ssp.id`）|
| `fbebdee`(FE) | 總覽入口吃 `roundUid`（route `project-auditor-overview-round` = `/project/projects/:id/round/:roundUid`）；Banner retarget round-scoped（`StageService`/`FlowPhaseBanner` 改 `/audit-round/<roundUid>/stage/{info,advance}`）|
| `98b33fc3` | 新 BE 端點 `GET /ssp/<ssp_uid>/control-tree`：v2 catalog（群組→控制項→AO part）+ SSP 現況說明（implemented_requirements + statements）組樹；防重複 IR（偏好有描述那筆）|
| `c014197`(FE) | 總覽 `fetchGroups` 改吃 `/ssp/<sspUid>/control-tree`、`fetchControlDetail` 從已載入控制項取 |

### 1.4 per-AO 專屬流程 + AO 任務 + get_job（對齊 V1 Step 8）✅ 已驗（2026-06-17）
| commit | 內容 |
|---|---|
| `d8c1d4fe` | `project_start._generate_prep_jobs` 重寫：每 AO 用自己的 `workflow-template-uid`（資源庫 `_init_ao_workflows` 寫進 cloned catalog part.props）**clone 一份定版**（`source_template_uid` 指原始），回填 part.props 指向 cloned uid；`get_control_tree` 補 AO jobs（cloned wf uid → workflow_executions → job_executions）；`get_job` 從 dark 重建（最小 GrcJobEntity）+ route None→404 guard |

> ✅ user 手測通過（重啟 BE + 建新專案 → AO 顯示「收證據」任務 + 點任務 drawer 正常開）。
> 既有舊專案（舊單一 template、part.props 未回填）不會顯示 per-AO 任務 → 測試一律用**新**專案。

---

## §2 下一棒主任務：專案規劃頁 `ProjectPlanningView` 資料沒帶出來

**症狀**：`/project/projects/<id>/ap/<apUid>/planning` 整頁資料沒帶出來（控制項 / 現況說明 / SSP）。

**root cause（同總覽舊版）**：`ProjectPlanningView.vue` 仍是 AP-centric：
- 讀 `route.params.apUid` + `apSegment = /ap/<apUid>`，打**舊 dark 的** `/grc/.../control-groups` 引擎 → 空。
- `sspUid` computed 已是對的 pattern（`currentAp.ssp_uid ?? project.ssp?.id ?? project.ssp_uid`），但 `project.ssp` 要 BE `get_project` 回（已修，`03fb83bf`）+ 入口要改吃 roundUid。

**做法 = 照 §3 參考實作（總覽剛遷好的那套）**：
1. **route / 入口**：規劃頁加 round-scoped route（`/project/projects/:id/round/:roundUid/planning`，名 `project-planning-round`），或讓現有 view 同時吃 `route.params.roundUid`（總覽用 `isRoundRoute` + `selectedApUid = roundUid ?? apUid` 雙吃法，見 `ProjectAuditorOverview.vue:44`）。總覽 / 列表的「規劃」按鈕導過去帶 roundUid。
2. **控制項 / 現況說明**：把規劃頁抓控制項的地方從 `/grc/.../control-groups` 改吃 `GET /ssp/<sspUid>/control-tree`（總覽 `fetchGroups` 範本，`ProjectAuditorOverview.vue` 內）。SSP 現況說明編輯沿用既有 `SspService`（`/ssp/<uid>/control-implementation/...`，是活的、可寫）。
3. **sspUid 自動切換**：`sspUid` computed 保持 `round.ssp_uid ?? project.ssp.id`——planning 輪自動拉 living SSP、launch_audit 後自動拉 round frozen SSP（見 WHY 節）。**編輯**：living SSP 階段可寫（`/ssp/<living_uid>/control-implementation` PUT，Upsert 活的）；round frozen 階段「如果可以」可寫（frozen 是否唯讀依設計，design §4.2 deep_clone_ssp 是可寫副本，但稽核階段是否鎖定待 user 確認）。

**注意（同總覽踩過）**：
- 覆核狀態 / 任務指派 = AR 層 / 指派 UI（FE Wave 3）概念，planning 規劃頁不一定要。
- 規劃頁若也掛 `<FlowPhaseBanner>`，改傳 `:round-uid`（Banner 已支援 roundUid 優先、apUid deprecated fallback）。

---

## §3 參考實作（照這個做規劃頁）

**`ProjectAuditorOverview.vue`（FE，剛遷好）** 是規劃頁的範本，關鍵段：
- `ProjectAuditorOverview.vue:44` — `isRoundRoute` + `selectedApUid = roundUid ?? apUid` 雙吃入口
- `ProjectAuditorOverview.vue` `fetchApList()` — round route 時改撈 `audit-rounds/list` 並 map 成 header/dropdown shape
- `ProjectAuditorOverview.vue` `fetchGroups()` — 改吃 `GET /ssp/<sspUid>/control-tree`，populate groups + controlsMap + `syncControlMaps`
- `sspUid` computed — `selectedAp.ssp_uid ?? project.ssp?.id ?? project.ssp_uid`

**BE 端點（都活的）**：
- `GET /ssp/<ssp_uid>/control-tree` — 群組→控制項→AO（含現況說明 + AO 任務）
- `GET/PUT /ssp/<ssp_uid>/control-implementation/<control_identifier>` — 單控制項現況（讀/Upsert）
- `PUT /ssp/<ssp_uid>/control-implementation/<ctrl>/objective/<stmt>` — AO 層現況
- `GET /grc/project/<uid>`（`ProjectResponseSchema`）回 `project.ssp = {id: living_ssp_uuid}`

---

## §4 本 session 踩過的雷（別重蹈）

1. **GRC 業務層 Wave 2A 大量 dark**：`get_project_by_uid` / `get_job` / `grc_job_repo` 多處 `return None`/`[]`（標 `FR-038 2A`）。改 GRC read 前先 grep dark 標記，dark 的就比照 `list_projects` 重建（查 v2 表 + 套可見性 + enrich）。
2. **重複 IR**：`project_start` 同時「clone 範本 SSP 帶描述 IR」+「`_init_ssp_control_implementations` 建空白 IR」→ 每控制項兩筆 IR（一有描述、一空白）。讀取路徑要**偏好有描述那筆**（`get_control_tree` / `_ir_for_control` 已做）。**root cause（project_start 重複建 IR）待獨立修 + 既有資料清理**——見 §6 follow-up。
3. **per-AO 流程 scope 不靠 schema**：prep job 的 `workflow_execution_control_mappings` 只有 `control_id + ao_part_id`（無 project_id），跨專案同 catalog 會撞。改用「per-project cloned template 專案唯一」當 scope（part.props.workflow-template-uid 回填 cloned uid → 該 template 的 workflow_execution 即本專案的）。
4. **flow_engine 表在 `compliance` schema**（`workflow_templates` / `workflow_executions` / `job_executions` / `task_assignees`）。
5. **`api/oscal/__init__.py` 與 user in-flight 糾纏**：該檔同時有「我的 SspControlTreeRoute」+「user 的 ModuleFrameTemplateSspResource」→ commit 時用 `git apply --cached` 切自己的 hunk（互動式 `git add -p` 在此環境不可用）。
6. **BE 無 hot reload**：改 service/repo 後提醒 user 重啟（user 自己起服務）。

---

## §5 Pre-flight + Verify（下個 session 開工必跑）

```bash
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
git branch --show-current                    # 應 feature/oscal-refactor
git log --oneline -8 | cat                   # 對照 §1 commit 清單
git status --short | grep -vE '^\?\? docs/'  # 應只剩 user in-flight（api/oscal/__init__.py、module_frame/* 等）
# import smoke（sandbox 無法完整 boot）
set -a; source .env; set +a; export GITLAB_API_VERSION=4 GITLAB_URL=x GITLAB_PRIVATE_TOKEN=x GITHUB_PRIVATE_TOKEN=x
.venv/bin/python -c "import app.oscal.service.ssp_control_implementation_service, app.project.service.project_start_app_service; print('import OK')"
```
Verify 本期成果（重啟 BE 後，建新專案 → 進 round 總覽）：
- 控制項導航列出群組 + 控制項數（非 0 群組）
- 點控制項 → 右側 AO 清單 + 現況說明（點擊不消失）
- 展開 AO → 「收證據」任務（TODO）→ 點任務 drawer 開不報錯
- BE log：`generate_prep_jobs ... jobs=N cloned_templates=N (per-AO 專屬流程，對齊 V1 Step 8)`

---

## §6 已知 follow-up（本期未做，獨立處理）
- **project_start 重複建 IR root cause**：clone 範本 SSP（帶 IR）+ `_init_ssp_control_implementations`（建空白 IR）兩邊都建 → 每控制項兩筆 IR。需改 project_start 擇一 + 寫既有專案空白重複 IR 清理 SQL。（本期只在讀取路徑 robust）
- **round frozen SSP 稽核階段是否唯讀**：design §4.2 deep_clone_ssp 是可寫副本，但「進稽核後 round SSP 可否編輯」待 user 拍板。
- **AO 任務指派 UI**（FE Wave 3）：prep job 目前全未指派；指派 + task_assignees 顯示待做。
- **stg/poc migration**：本弧 BE 改動多為 code（無新 migration）；之前 4 支 round-flow migration 仍待 stg/poc 套。

---

## §7 行為規範提醒
- **不切 branch**；**push 等 user 明示**；跨 repo 各自 commit、**顯式 git add 禁 -am**
- 改 BE service/DI → **提醒 user 重啟**（無 hot reload）；服務 user 自己起
- 動 FE 前讀 FE CLAUDE.md；不晶晶體；plan 假設先 verify 才開工
- 收尾類動作（changelog / SUMMARY / design §11 / Notion）等 user 明確下令

## §8 給 fresh session 的超短 prompt
```
讀 docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-17-round-flow-fe-migration-handoff.md，
先讀「🧭 原始需求 WHY」+ §0 自檢 4 問答得出再開工。本弧：專案生命週期 FE 逐頁從舊 AP 引擎
遷到 v2 audit_round + living/round SSP（總覽已遷好，§3 參考實作）。下一棒：專案規劃頁
ProjectPlanningView 資料沒帶出來 → 比照總覽改吃 /ssp/<sspUid>/control-tree + roundUid 入口；
sspUid = round.ssp_uid ?? project.ssp.id 自動切換 living/round SSP（planning 拉主專案 SSP、
launch_audit 後拉 round frozen SSP）。跑 §5 pre-flight。不切 branch、push 等 user。
```
