# FR-038 稽核輪次流程引擎再整合 — 換 session Handoff（2026-06-16）

| 項目 | 內容 |
|------|------|
| 緣由 | 「新增專案 422」開查 → 演成「把 FR-026 BPMN 流程引擎從死掉的舊 AP retarget 到 v2 輪次」大重構；測試中又連帶修了 GRC 專案列表/刪除（Wave 2A dark）|
| BE branch | `feature/oscal-refactor`（**勿切 branch**；發現 branch 不對停下問 user）|
| FE repo | `~/Projects/Billows/Audit-Manager/compliance-manager-fe/`，同名 branch `feature/oscal-refactor` |
| 接手前必讀 | 本文件 §0 讀序（先讀需求 WHY 再碰 code）|
| 預估剩餘 | FE round-flow 大遷移（大，數 hr）+ Task 8b commit + 文件收尾 + stg/poc migration |
| push 狀態 | BE + FE 都有未 push commits → **push 等 user 明示** |

---

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

**大圖**：FR-038 是把 OSCAL 從 jedi_oscal **v1 整碗換成 v2**（Wave 2A 已把 BE boot 切到 jedi_oscal_v2，舊 v1 全剝離）。本棒處理其中一條線：**稽核流程引擎**。

**這條線的需求（user 對話拍板，2026-06-16）**：
- **稽核流程範本 = 專案/輪次的「執行流程」**（規劃→執行→稽核→缺失改善→結案），由 FR-026 用 **BPMN 流程引擎驅動** AP 生命週期（見 `docs/features/FR-026-2605-project-flow-engine-integrate/FINAL-SPEC-v1.0.md`）。
- v2 切換時，這套引擎被**擱在已 dark 的舊 AP 上**（孤兒）。目標：**retarget 到 v2 first-class `compliance.project_audit_rounds`**。
- **每個 audit_round 綁一支自己的稽核流程範本 snapshot**（1:1，跟 round 綁一個 SSP 同道理）。範本掛**輪次**，不是專案。
- v2 多了一關 **AP 填寫**（reviewed-controls/subjects/tasks，人工 author）→ 新增 `ap_authoring` stage（auditor 主責）。
- **建專案 = clone 資源庫三件組 + 順帶建第一輪（initial）並綁所選流程範本**（D6）。建專案頁維持「資源庫 + 稽核流程範本」雙下拉。
- 覆核走 **v2 多輪模型**（`launch_reverify` 另開 close-out 子輪 + `parent_round_id`），**不是** FR-026 的 poam→audit 同輪迴圈。

**canonical 鏈（D5 拍板）**：`planning → ap_authoring → audit → poam → End`
（FR-026 的 `task_execution` 收進 planning；`review` 暫不納）

**stage_code → round.status 對照**（與已 shipped 的 v2 輪次狀態機 1:1，零 migration）：
`planning→planning`、`ap_authoring→audit_planning`、`audit→auditing`、`poam→remediation`、End→`closed`

**決策清單**（design 內有完整理由）：
| # | 決策 |
|---|------|
| D1 | 流程範本綁 `project_audit_round`（非專案）|
| D2 | big-bang 一次到位（不分階段）|
| D3 | ap_authoring main_role = **auditor**（暫定）|
| D4 | 第一輪範本在「建專案」選；後續輪在「建輪次」選 |
| D5 | stage 模型 planning→ap_authoring→audit→poam；task_execution 收進 planning |
| D6 | 建專案順帶建第一輪 initial + 綁範本 |
| 重要實作收斂 | **無 status derivation**：handler 各自寫 status（與所進入 stage 一致）；**poam→End**（非 loop）|

**完整設計**：`docs/features/FR-038-2606-oscal-redesign/round-flow-engine-reintegration-design.md`（⚠️ §4.1b 還寫 derivation，實作改成 no-derivation，文件待收尾同步）

---

## §0 接手讀序（按序，前 2 個是硬 gate）

1. 🔒 **本文件 §「原始需求 WHY」全讀** — 答不出下面自檢題別碰 code
2. 🔒 `round-flow-engine-reintegration-design.md` §1~§4（問題/決策/目標架構）
3. `round-flow-engine-implementation-plan.md`（BE 11 task，已全做）+ `round-flow-engine-fe-plan.md`（FE，Task 1 做完、2-5 未做）
4. 主嫌路徑檔：`app/flow_engine/service/stage_advance_service.py`（已 retarget round_uid）、`app/grc/service/oscal_stage_handlers.py`、`app/grc/service/audit_round_app_service.py`（create_round 綁範本 + 狀態機）
5. GRC 列表/刪除修補：`infra/grc/repository/grc_project_repo_impl.py`（list_projects v2 重建）、`app/grc/service/project_service.py`（delete/batch_delete）

**冷接自檢 4 問（答不出回去讀）**：
1. 為何要把流程引擎 retarget？（→ FR-026 引擎被擱在 v2 已 dark 的 AP 上）
2. 範本綁哪一層、幾比幾？（→ 綁 audit_round，1 round : 1 範本 snapshot : 1 workflow_execution）
3. ap_authoring 是什麼、誰推？（→ AP 填寫，auditor）
4. round.status 怎麼來？（→ handler 自己寫，對應所進入的 stage，**非** derivation）

---

## §1 目前狀態（哪些好了 / 哪些沒）

### ✅ 已完成 + committed（BE，round flow engine core，Tasks 1-10）
| commit | 內容 |
|---|---|
| `1fd604cc` | Task 1 schema：`project_audit_rounds.{flow_template_snapshot_uid,workflow_execution_uid}` + `module_frames.audit_flow_template_uid` |
| `76252985` | Task 2 error codes（GRC_ROUND_NO_WORKFLOW_BINDING / GRC_ROUND_PREP_TASKS_NOT_DONE）|
| `5df81d25` | Task 3 ap_authoring stage_object seed + planning 收斂 |
| `36d022e7` | Task 4 builtin BPMN canonical（完整稽核/自我評估 改鏈；含審核/內部自查 deactivate）|
| `0a132492` | **Tasks 5-10 engine core**：stage_advance retarget round_uid、handlers wrap audit_round、preconditions、DI rewire、stage route round-scoped、poam→End |
| `67ded923` | handler 測試回歸修正 |

### ✅ 已完成 + committed（BE，GRC 列表/刪除 — 測試中發現 Wave 2A dark，連帶修）
| commit | 內容 |
|---|---|
| `b31dc79b` | 修 /projects/list DI（移除 ProjectService 死接線 associations.assessment_plan_task_domain_service）|
| `e88e8d78` | 移除 grc 容器對 7 個 Wave 2A 已移除 oscal v1 provider 的死接線 |
| `c56927c4` | **重建 list_projects（v2）**：查 projects+extensions、軟刪排除、可見性、分頁 → 列表恢復顯示（只回未刪除）|
| `987575a6` | 批次刪除 API（POST /grc/projects/batch-delete）|
| `596d5417` | 修單筆刪除（delete_project 不再走 dark get_project）|

### ✅ 已完成 + committed（FE）
| commit | 內容 |
|---|---|
| `175ca24` | 建專案改送 resource_library_uid + flow_template_uid（修 422，FE Task 1）|
| `9ad582c` | 專案列表批次刪除 UI（多選 + 批次刪除按鈕）|

### ⚠️ 已完成但「未 commit」（BE，Task 8b — 建專案順帶建第一輪 D6）
**留在 working tree，下個 session 要 commit。** 因為這 4 個檔在 session 開始前就有 user in-flight 未 commit 改動，無法用 `git add` 乾淨切開：
- `api/project/serializers/project.py`（加 flow_template_uid、清掉舊 EXCLUDE）
- `api/project/routes/project_route.py`（thread flow_template_uid + delete 傳 is_admin）
- `app/project/service/project_start_app_service.py`（start_project 建 round 1 = `_create_first_round`，回 first_round_uid）
- `di_containers/project/project_containers.py`（注入 audit_round_app_service）
- `test/test_project_start_creates_first_round.py`（新檔，2 tests pass）

**驗證 Task 8b 有效**：DB 已驗 — 建專案 271 後 `project_audit_rounds` 有 round_no=1/initial/planning，`flow_template_snapshot_uid` + `workflow_execution_uid` 非空。

### ❌ 未開始（FE round-flow 大遷移 — 最大剩餘塊）
**FE 整個還是 AP-centric**（route param `apUid`、`apList`、`RoundSwitcherBar`、Banner 掛 `:ap-uid`）。round flow 在 UI **還不能跑**。要做（見 `round-flow-engine-fe-plan.md` Task 2-5 + `audit-round-fe-migration-plan.md`）：
- Banner/stage retarget `apUid`→`roundUid`（`StageService.js` / `useStageInfo.js` / `FlowPhaseBanner.vue`，BE 端點已是 `/project/<uid>/audit-round/<round_uid>/stage/{info,advance}`）
- AP 填寫畫面（`ap_authoring` routed stage，route `/round/:roundUid/ap`）
- round-scoped routes + RoundSwitcher 改吃 audit-rounds
- 掛 Banner 的 view 改傳 roundUid + i18n（precondition reason key 在 FE 渲染）

---

## §2 前次（本 session）踩過的雷，別重蹈

1. **GRC 業務層在 Wave 2A 被大量 dark**：`grc_project_repo_impl` 多個 method `return []`/`None`、grc 容器 provider 接了已移除的 oscal v1 provider（lazy resolve，踩到才炸）。**改 GRC 任何 read/list 前先 grep `# FR-038 2A` dark 標記 + 確認 grc→oscal provider 引用都有定義**（`for p in $(grep -oE 'oscal_container\.[a-z_]+' di_containers/grc/grc_containers.py|sort -u); do ...`）。
2. **RLS 不是 projects 列表的過濾機制**：`compliance.projects` 的 RLS **relrowsecurity=f（沒啟用）**；列表過濾是 **app 層 GRC visibility filter**（user_id/is_admin + participant）。別再往 RLS/tenant context 鑽（我繞了一大圈才發現）。
3. **`data.id` 在 FE 專案列表 = project UID**（serializer `id = obj.uid`），不是整數 id。批次刪除送 `data.id` 即 uid。
4. **sandbox 無法 `create_app()` 完整 boot**（jedi_issue 要 gitlab/github token + dependency_injector deepcopy quirk，**pre-existing**，stash 我的改動後 baseline 一樣炸）。驗 boot 要在真實環境；sandbox 只能 import smoke + 單元測試。

---

## §3 下一棒主要工作 + 風險

### 工作 A：commit Task 8b（小，先做）
4 個 entangled 檔 + 1 test。**先跟 user 確認他 in-flight 的改動要不要一起 commit**，再決定怎麼切（顯式 git add、禁 -am）。

### 工作 B：FE round-flow 大遷移（大）
按 `round-flow-engine-fe-plan.md` Task 2-5。**這是讓 round flow 在 UI 能跑的關鍵**。風險：Banner prop rename ripple 到所有掛 Banner 的 view；需 round-scoped routing 地基（migration plan Step 0）。**開工前讀 FE CLAUDE.md（646 行，跨 repo 鐵則）**。

### 工作 C：BE 收尾文件（等 user 下令）
- design `§4.1b` 改 no-derivation（目前還寫 derivation）
- changelog（本 arc 多主題 batch）
- 把本 handoff 標 ✓ + 寫 SUMMARY
- Notion 任務

---

## §4 刻意 defer / lenient 的東西（標明復原依據，別當「不需要」）

| 項目 | 現況 | 復原依據 |
|------|------|---------|
| `round_prep_tasks_done` precondition | **lenient（一律 ok）** | prep job 是專案層級（`_generate_prep_jobs`），「哪些算本輪 prep」scoping 未定 → 待定後補 job-completion 檢查 |
| `all_poam_closed` / `all_controls_verdict_filled` precondition | **lenient** | v2 POA&M/AR Banner 檢查 follow-up；close_round/finalize 自身仍強制 |
| 4 個直接轉換 route（launch-audit/start-auditing/finalize/close）| **未退役**（no-derivation 下不會壞，只 bypass BPMN）| O2：跟 FE 遷移一起退，統一走 stage/advance |
| builtin「完整稽核流程（含審核）」+「內部自查流程」| **deactivate（is_active=false）** | 含 review（D5 延後）/ 無 audit（不合 ap_authoring 拓撲）→ 補 review-stage / planning→end 形狀後 reactivate |
| 專案列表 AP 進度統計（completion_rate/control 數/task 進度）| **回 0/None** | v1 統計子查詢移除；待 v2 輪次層接上重算 |
| design `§4.1b` derivation 段 | 文件寫 derivation，**code 是 no-derivation** | 收尾時改文件對齊 code |

---

## §5 Migration 狀態（4 支，dev 已套，stg/poc 待遷移）
- `scripts/sql/2026-06-16-fr038-round-flow-binding.sql`（round 兩欄 + module_frames 欄）
- `scripts/sql/2026-06-16-fr038-round-stage-seed.sql`（ap_authoring stage + planning 收斂）
- `scripts/sql/2026-06-16-fr038-builtin-flow-canonical.sql`（builtin BPMN）
- `scripts/sql/2026-06-16-fr038-poam-to-end.sql`（poam→End）
> ⚠️ `schema_migrations` 欄位是 **`filename`/`note`**（不是 `version`，我踩過）。套法：`psql -U cmmgr -p 25432 -d <db> --single-transaction -v ON_ERROR_STOP=1 -f <檔>`。

---

## §6 Pre-flight Command（下個 session 開工必跑）
```bash
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
git branch --show-current          # 應為 feature/oscal-refactor，不對停下問 user
git log --oneline -6 | cat         # 對照 §1 commit 清單
git status --short | grep -vE '^\?\? docs/'   # 確認 Task 8b 4 檔 + test 仍在 working tree
# 取 DB 密碼（cmmgr/cm_app 同組，查 .env DB_SECRET）
PW=$(python3 -c "import json;[print(json.loads(l.split('=',1)[1].strip().strip(chr(34))).get('rds_master_password','')) for l in open('.env') if l.startswith('DB_SECRET=')]")
# 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 -m pytest test/test_oscal_stage_handlers.py test/test_create_round_flow_binding.py test/test_project_start_creates_first_round.py -q
```

## §7 Verify 本期成果（DB，下個 session 確認沒走回頭路）
```sql
-- 建專案有建第一輪 + 綁範本（D6 + create_round 綁定）
SELECT round_no, round_type, status, flow_template_snapshot_uid IS NOT NULL AS has_flow, workflow_execution_uid IS NOT NULL AS has_wf
  FROM compliance.project_audit_rounds ORDER BY id DESC LIMIT 3;
-- canonical stage 鏈
SELECT code, complete_handler_key, precondition_key, allowed_successors FROM compliance.stage_objects WHERE code IN ('planning','ap_authoring','audit','poam') ORDER BY sort;
-- 列表只回未刪除（27 筆）；軟刪 175 筆
SELECT count(*) FILTER (WHERE pe.deleted_at IS NULL) non_deleted, count(*) FILTER (WHERE pe.deleted_at IS NOT NULL) soft_deleted
  FROM compliance.projects p LEFT JOIN compliance.project_extensions pe ON pe.project_id=p.id;
```
> 直接打 API 驗（dev turnstile 收 dummy）：`POST /api/1.0/login {"username":"blsadmin","password":"<查 .env / reference_dev_login>","cf_turnstile_token":"dummy","provider":"password"}` → 取 access_token → `POST /api/1.0/grc/projects/list` 帶 `Authorization: Bearer` + `X-Tenant-Id:102` + `X-Org-Unit-Id:98`。

## §8 行為規範提醒（適用本 arc）
- **不切 branch**；**push 等 user 明示**；跨 repo 各自 commit、**顯式 git add 禁 -am**
- 改 BE service/DI → **提醒 user 重啟 BE**（無 hot reload）；服務都 user 自己起
- 動 FE 前**先讀 FE CLAUDE.md**（646 行）
- 改套件先走 poetry path dep（本期沒動套件）
- 不晶晶體；plan 假設先 verify 才開工
- **收尾類動作等 user 明確下令**（changelog/SUMMARY/design §4.1b 同步/Notion）

## §9 不在本期 scope（別順手做）
- v2 輪次層的 AP 進度統計重算（列表那些 0/None）
- review stage / 含審核 builtin 復原
- 4 直接轉換 route 退役（跟 FE 遷移一起）
- prep-task gate 真實檢查
- stg/poc migration（user 排程）

## §10 給 fresh session 的超短 prompt
```
讀 docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-16-round-flow-reintegration-handoff.md，
先讀「🧭 原始需求 WHY」+ §0 自檢 4 問答得出再開工。本 arc：FR-026 流程引擎已 retarget 到 v2
audit_round（BE Tasks 1-10 done + committed）；GRC 列表/刪除已修。剩：①commit Task 8b（4 entangled
檔，先問 user in-flight 改動）②FE round-flow 大遷移（Banner→round + AP 填寫畫面，FE 還全是 AP-centric）。
跑 §6 pre-flight + §7 verify 確認現況，不切 branch、push 等 user。
```
