# FR-038 覆核（reverify）端到端測試 — 換 session Handoff（2026-06-17）

| 項目 | 內容 |
|------|------|
| 緣由 | 接續 FR-038 稽核生命週期測試。控制層輪的 **start auditing → 判定 → 風險 → finalize → POA&M → 結案** 已實測通過；**剩最後一段「覆核（reverify）閉環」未測** |
| 下一棒 | **用 round 26（覆核輪）端到端測覆核**：稽核計畫 → 開始稽核 → 重新判定（這次判**符合**）→ finalize → 確認母輪 round 24 連動 `closed` |
| BE branch | `feature/oscal-refactor`（**勿切 branch**）|
| FE branch | 同名 `feature/oscal-refactor`（`~/Projects/Billows/Audit-Manager/compliance-manager-fe/`）|
| 🔴 狀態 | **三 repo 大量未 commit（working tree）**；本 session 改了一堆 BE/FE，**BE 必須重啟才生效**；未收尾（changelog/SUMMARY/Notion 都沒做，等 user 令）|
| 測試專案 | 專案 277「新專案測試新專案測試」（uid `94046c32-81d3-4bb3-860d-ccb368f74c26`）|
| 接手前必讀 | 本文件全讀 + 前一份 handoff `2026-06-17-final-stages-audit-poam-handoff.md`（稽核+POA&M 背景）|

---

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

**大圖**：FR-038 把 OSCAL v1→v2。本弧把生命週期最後階段（audit / poam / 覆核）遷到 v2 round-scoped，並依 OSCAL 模型重做 UI。

**這一棒要驗的價值**：稽核生命週期是 **開立 → 進行中 → 已矯正 → 已驗證/結案**。「已矯正 ≠ 已驗證」——改善負責人說修好了，要由稽核**覆核確認改善有效**才能真正關閉。系統用 `pending_reverify` + **覆核輪（close-out round）** 實作這個「驗證」步驟（對齊 NIST RMF / ISO 內稽的矯正後追蹤驗證）。

**覆核閉環機制**（OSCAL + 本專案）：
1. 母輪缺失改善完成 → 按結案 → 母輪進 `pending_reverify`（不直接 closed）
2. 在輪次清單對母輪按「發起覆核」→ 開一條 **close-out 覆核輪**（重新凍結當下 SSP 快照、把母輪不符合的控制重新帶入稽核）
3. 覆核輪重新稽核那些控制 → 若已改善判**符合** → finalize
4. 覆核輪若全符合（無 not_met）→ 覆核輪 `closed` 並**連動把母輪也設成 `closed`** → 閉環完成

**本棒任務 = 驗證上面第 2~4 步真的跑得通**（前 1 步已測過）。

---

## §0 接手讀序（先懂需求再動）

1. 🔒 本文件「WHY」全讀 + 答出冷接自檢（見下）
2. 前一份 handoff `2026-06-17-final-stages-audit-poam-handoff.md`（稽核+POA&M 設計決策：判定=控制層、風險鉤 finding、manager override 4 處）
3. §5 本 session 改動清單（知道 working tree 有什麼、為何 BE 要重啟）
4. §6 pre-flight → §7 verify 現況 → §4 開工

**冷接自檢（答不出回去讀）：**
1. 為什麼結案後是 `pending_reverify` 不是 `closed`？→ 因為「已矯正 ≠ 已驗證」，要覆核確認改善有效才能關（OSCAL/RMF 驗證步驟）。
2. 覆核輪（close-out）跟母輪什麼關係？→ `parent_round_id` 指母輪；覆核輪 closed 會連動關母輪。
3. 覆核輪的控制範圍怎麼來？→ `narrowed_control_ids_for`：母輪 not_met 的控制集（覆核只查那幾條）。
4. 判定/POA&M 是 AO 層還控制層？→ **控制層**（1 控制 1 finding 1 poam_item，target_id=控制碼無 `_obj`）。

---

## §1 目前 DB 狀態（測試起點，已驗證）

專案 277 現有兩輪：

| round id | uid | 輪次 | 類型 | 狀態 | parent | workflow 綁定 |
|----------|-----|------|------|------|--------|--------------|
| 24 | `57bdf192-5db7-4875-9b3c-e7143547def8` | 第1輪 | initial | `pending_reverify` | — | ✅ 已綁 |
| 26 | `6664a2d2-e941-491e-95fb-8dd480e11ae3` | 第2輪 | close-out | `audit_planning` | 24 | ✅ 已綁（workflow_execution_uid 有值）|

- **round 26 就是覆核輪**，已正確綁 workflow（本 session 修好 `launch_reverify` 漏綁的 bug 後重新發起的）→ 進它的稽核計畫頁不會再報「無法載入階段資訊」。
- 母輪 24 已 finalize 過、POA&M 全結案、里程碑全 done，所以進了 `pending_reverify`。
- 角色：**blsadmin = manager**、**blspan = auditor**（覆核輪要判定走 auditor；發起覆核 FE 按鈕目前只給 manager —— 見 §2.4）。

驗證 SQL（密碼查 `.env` `DB_SECRET`，host 192.168.50.188:25432 db guidant_ai_dev user cmmgr）：
```sql
SELECT id,uid,round_no,round_type,status,parent_round_id,workflow_execution_uid
  FROM compliance.project_audit_rounds WHERE project_id=277 ORDER BY round_no;
```

---

## §2 本 session 踩過的雷 / 必懂 gotchas（別重蹈）

1. **別用 AO 級舊輪測**：上一輪在 round 23/ar_result 467（AO 級，finding target_id 帶 `_obj.N`）反覆踩坑——3 筆 poam_item、補不到風險、卡死。**控制層輪**才是 1 控制 1 finding 1 poam_item（target_id=控制碼）。round 24/26 都是控制層、乾淨。判別法：finding.target_id **無 `_obj` 後綴**。
2. **PrimeVue DataTable 的 Column 之間不能夾「含 Vue 模板語法的 HTML 註解」**：本 session 用 `<!-- <Column>...{{ }}... </Column> -->` 隱藏欄位，害整個 DataTable 渲染壞掉（跑出 stray `>`、整列不渲染）。要隱藏欄位**直接刪 Column**（要恢復看 git 歷史），別用 HTML 註解包。
3. **launch_reverify 原本漏綁 workflow**：覆核輪建立時沒 clone snapshot + start workflow_execution → stage/info 報「稽核輪次尚未綁定流程」。本 session 已修（建輪後呼叫 `_bind_round_main_workflow`，比照 create_round）。
4. **發起覆核 FE/BE 角色不一致（未修，列 follow-up）**：BE `launch_reverify` 檢查 **auditor**（manager override），但 FE 按鈕只在 `isProjectManager` 顯示（`ProjectApListView.vue` line ~283）。所以 auditor(blspan) 看不到按鈕、要用 manager(blsadmin) 發起。語意上覆核是稽核員動作 → FE 應放寬給 auditor，但本 session 沒改（user 沒要求）。
5. **結案後母輪 → pending_reverify，不是 closed**：這是設計，不是 bug。要 closed 得走完覆核輪。
6. **稽核時間（稽核期間 col_period）欄位已隱藏**：round 的 `start_at`/`end_at` 全專案沒 code 在寫（end_at 完全沒接），所以那欄結構性空白，本 session 已從 `ProjectApListView` 刪該 Column。若要顯示需另接（start_at=開始稽核時寫 / end_at=finalize 時寫），列 follow-up。

---

## §3 下一棒任務：覆核端到端測試

**這是測試任務，不是修 bug。** 目標：驗證覆核閉環跑得通。

### 步驟（用 round 26 覆核輪）
1. 進專案 277 → 輪次清單 → 點 **第 2 輪（覆核輪，audit_planning）**
2. **稽核計畫頁**（應正常載入、不報「無法載入階段資訊」）→ 確認受評控制是母輪 not_met 的那條（`IA.L1-3.5.1`）
3. **開始稽核** → 應建控制層 finding（target_id=`IA.L1-3.5.1` 無 `_obj`）
4. **判定** `IA.L1-3.5.1` 為**符合**（模擬改善已生效）→ 存判定
5. **finalize**：因無 not_met → 覆核輪應直接 `closed`，並**連動母輪 24 → `closed`**
6. 驗 DB：覆核輪 26 `closed`、母輪 24 `closed`

### 驗證 SQL（每步驟查）
```sql
-- 覆核輪 findings（控制層？符合？）
SELECT target_id, target_status_state FROM oscal.assessment_findings
  WHERE ar_result_id = (SELECT ar_result_id FROM compliance.project_audit_rounds WHERE id=26);
-- 兩輪最終狀態（finalize 後應都 closed）
SELECT id,round_no,status FROM compliance.project_audit_rounds WHERE project_id=277 ORDER BY round_no;
```

### ⚠️ 連動關母輪的關鍵風險（已從 code 追出，下一棒實測確認）

追 `app/grc/service/audit_round_app_service.py` 兩條路徑：

- **`finalize_audit`**（line ~390）：`not_met_count == 0` → 把**覆核輪**設 `STATUS_CLOSED`。**但這條沒有 parent 連動邏輯** —— 它只關覆核輪自己。
- **`close_round`**（line ~425 close-out 分支）：`e.status in (remediation, closed)` 才不擋 → 設覆核輪 closed → 若 `parent.status == pending_reverify` → **把母輪設 closed**（連動只在這裡）。

**所以推論（待實測）**：覆核輪全判符合 → finalize 把覆核輪設 closed，**但母輪不會自動 closed**（連動在 close_round，不在 finalize）。要關母輪，得在覆核輪**再按一次「完成本輪/結案」**觸發 close_round（它允許 status==closed 通過 → 跑 parent 連動）。

**疑慮**：FE 對「已 closed 的覆核輪」可能不顯示結案按鈕 → 那 close_round 永遠不會被呼叫 → **母輪卡在 pending_reverify 關不掉**。

**下一棒實測重點**：覆核輪全符合 finalize 後，(a) 母輪有沒有自動 closed？(b) 若沒有，FE 有沒有地方能對覆核輪觸發 close_round？(c) 若兩者皆無 → 這是真 bug，要讓 finalize（覆核輪 not_met=0 時）直接呼叫 parent 連動，或 FE 補觸發點。先用 §3 SQL 看 finalize 後兩輪狀態再決定。

---

## §4 開工順位
1. **請 user 重啟 BE**（本 session 大量 BE 改動在 working tree、未生效）+ FE 整頁重整
2. §6 pre-flight（branch / working tree / import smoke）
3. §7 verify 現況（round 24/26 狀態）
4. §3 步驟跑覆核測試，每步查 DB
5. 若母輪沒連動 closed → 追 §3 推測的 finalize vs close_round 路徑
6. 結果回報 user，**等 user 下令才收尾**（changelog/SUMMARY/Notion）

---

## §5 本 session 改動清單（全在 working tree、未 commit；**勿用 -am，顯式 git add**）

### BE（`~/Projects/Billows/Audit-Manager/compliance-manager-be/`）
本 session 在前一弧基礎上**新增/修改**：
- `app/grc/service/poam_app_service.py` — 里程碑負責人 user_uid→user_id resolve；get_item 重做（回 deficiency + observations + **全部 risks** 各帶 remediations/milestones）；list_items 加 control_id/risk_count/max severity；`_item_control_id`/`_item_findings`/`_item_risks`/`_item_observations`/`_max_severity` helpers
- `app/grc/service/assessment_result_app_service.py` — get_findings 每控制附 observations；`_observation_dto`；新增 `delete_observation`/`update_observation`；judge_finding 觀察改**累加**（原本覆蓋）；list_risks/`_linked_for_risk` 來源 control_id 用 `_ao_to_control_for_ap` 映射（去 `_obj`）
- `app/grc/service/audit_round_app_service.py` — **launch_reverify 補綁 workflow**（核心修正）；launch_reverify 加防重複發起 guard
- `app/grc/service/assessment_plan_app_service.py` — get_ap_for_round 回 `round_status`（FE ap-authoring 唯讀判定用，原本誤用 ap.status 永遠 draft → 全唯讀）
- `infra/grc/repository/auditor_dashboard_query.py` — 我的稽核 SQL `:status IS NULL` → `CAST(:status AS text) IS NULL`（修 AmbiguousParameter 崩潰）
- `api/project/serializers/audit_round.py` — AddMilestone/UpdateMilestone 加 `assignee_user_uid`；新增 `UpdateObservationRequest`
- `api/project/routes/audit_round_route.py` — milestone 路由 wire assignee_user_uid；新增 `AuditRoundObservationRoute`（PUT/DELETE observation）
- `api/project/__init__.py` — 註冊 `AuditRoundObservationRoute`（`/audit-round/<r>/ar/observation/<obs_uid>`）
- `common/code/grc_error_code.py` — 加 `GRC_REVERIFY_ALREADY_LAUNCHED`（GRC_412041）
- （前一弧已在 working tree：auditor_dashboard serializer、stage_advance、di_containers、i_project_audit_round_repo、project_audit_round_domain_service、ssp_project_resolver、project_audit_round_repo_impl、scripts/sql/2026-06-17-ar-assessment-subjects.sql）
> ⚠️ `pyproject.toml` / `docs/features/README.md` **非本弧改動**，勿一起 commit。

### FE（`~/Projects/Billows/Audit-Manager/compliance-manager-fe/`）
- `src/views/project/RoundPoamView.vue` — POA&M 頁依 OSCAL 重做（缺失脈絡→觀察證據→風險(弱點/影響)→各風險矯正措施→里程碑）；里程碑負責人指派下拉；里程碑改「明確儲存按鈕」（原本 select 即存）；里程碑進度標籤（🚩里程碑 x/y / 尚無里程碑）
- `src/views/project/RoundAuditReviewView.vue` — 觀察記錄區重做（清單+編輯+刪除，收合式新增）；判定區加標題/分隔線
- `src/views/project/ProjectApListView.vue` — 發起覆核/啟動稽核按鈕改大顆有字；`reverifyLaunched` 已發起就隱藏發起鈕；**刪除 col_period（稽核時間）Column**
- `src/views/project/ProjectAuditorOverview.vue` — 移除標題列的專案狀態 badge（未開始，沒在更新）
- `src/config/locales/i18n/{zh-tw,en}/round-audit-poam.json`（??新檔）— 觀察/風險/里程碑/POA&M 文案
- `src/config/locales/i18n/{zh-tw,en}/ap-authoring.json`（??新檔）
- `src/config/locales/i18n/{zh-tw,en}/project-ap-list.json` — round_type_close-out 翻譯、reverify_already_launched、發起覆核按鈕
- `RoundApAuthoringView.vue`（??）、`AuditTreeNav.vue`、`MyAuditsView.vue`、`locales/index.js`、`router/index.js`（前一弧）

---

## §6 Pre-flight（必跑）
```bash
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
git branch --show-current                 # 應 feature/oscal-refactor
git status --short | grep -vE '^\?\? '     # 對照 §5
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 api.project, app.grc.service.poam_app_service, app.grc.service.audit_round_app_service; print('OK')"
```
- **請 user 重啟 BE**（service/route/套件改動無 hot reload；服務 user 自己起、Claude 不啟動）
- **FE 整頁重整**；build 驗：`cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe && npm run build:DEV`

---

## §7 Verify 現況（確認測試起點乾淨）
```sql
-- 應看到 round 24(pending_reverify) + round 26(close-out, audit_planning)，兩者 workflow_execution_uid 皆有值
SELECT id,round_no,round_type,status,parent_round_id,workflow_execution_uid
  FROM compliance.project_audit_rounds WHERE project_id=277 ORDER BY round_no;
```
若 round 26 不存在或 workflow_execution_uid 為 NULL → launch_reverify 沒生效（BE 沒重啟到帶修正的版本），停下查。

---

## §8 行為規範重要提醒
- **不切 branch**；**push 等 user 明示**；三 repo 各自 commit、顯式 git add、禁 `-am`
- 改 BE service/route → **提醒 user 重啟**（服務 user 自起、Claude 不啟動 / 不下啟動指令）
- BE 出錯先看 `log/app.log`（先 grep ERROR/Traceback，不問 user）
- 動 FE 前讀 FE CLAUDE.md；不晶晶體；plan 假設先 verify
- **收尾類動作等 user 明確下令**：changelog / SUMMARY / design §11 / Notion / commit 收尾 —— 本 session 全沒做

---

## §9 收尾流程（測試通過 + user 下令後才做）
- changelog（本弧多主題各一份：觀察 CRUD / POA&M OSCAL 重做 / 里程碑指派 / 覆核綁流程修正 / 我的稽核崩潰修正 / UI 整理）
- design.md §11.X reconciliation
- FIXED-SUMMARY
- Notion 任務（先搜尋避免跟 PM 預建重複）
- 套件 jedi-oscal-v2 若有改動的發版（dev 走 path dep，發版等 user）

---

## §10 不在本期 scope（勿順手做）
- 發起覆核 FE 角色放寬給 auditor（§2.4）— 列 follow-up
- 稽核時間 col_period 接資料來源（§2.6）— 列 follow-up
- 風險偏差處理（誤報/接受/調整，OSCAL deviation）— v2 模型未接
- severity OSCAL characterizations facets（目前簡化 `{"severity":...}`）
- 我的稽核 evidence 統計（目前 0/0）
- AO 命名殘留清理（get_findings 回的 `ao_findings`/`ao_id`、judge_finding docstring「單 AO 判定」、控制層輪不需要的 `_derive_ao_map`）
- stg/poc/prod 套 `2026-06-17-ar-assessment-subjects.sql`

---

## §11 本 session commits 清單
**無** —— 本 session 所有 BE/FE 改動**都在 working tree、未 commit、未 push**。前一弧的改動也還在 working tree（從未 commit）。下一棒測試通過後等 user 下令才一起整理 commit。

---

## §12 給 fresh session 的超短 prompt
```
讀 docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-17-reverify-testing-handoff.md，
先答冷接自檢（為何 pending_reverify / 覆核輪與母輪關係 / 覆核控制範圍來源 / 判定=控制層）再開工。
本弧稽核+POA&M 已實測通過，剩「覆核（reverify）閉環」未測。
下一棒：請 user 重啟 BE（本 session 大量 BE 改動在 working tree）→ 用專案 277 的 round 26（覆核輪）
端到端測覆核：稽核計畫→開始稽核→判定符合→finalize→確認母輪 round 24 連動 closed（§3 有推測待 verify：
覆核輪走 finalize 直接 closed 還是要 close_round 才連動關母輪）。每步查 DB（§3/§7 SQL）。
不切 branch、push 等 user、收尾等 user 令。
```
