# FR-038 收尾弧 — 稽核 + POA&M 階段完成 換 session Handoff（2026-06-17）

| 項目 | 內容 |
|------|------|
| 緣由 | 接續 FR-038 round-flow，把生命週期最後兩階段（audit / poam）的 FE 遷到 v2 round-scoped，並補 AP→AR 溯源、風險 CRUD、我的稽核 |
| BE branch | `feature/oscal-refactor`（**勿切 branch**）|
| FE branch | 同名 `feature/oscal-refactor`（`~/Projects/Billows/Audit-Manager/compliance-manager-fe/`）|
| 套件 | `jedi-oscal-v2`（`~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/`，editable `.pth`，改完重啟 BE 生效）|
| 🔴 狀態 | **三 repo 全部未 commit、未 push（working tree）；BE 已於 2026-06-17 以最新改動重啟**（可直接測 POA&M）；收尾文件(changelog/SUMMARY/Notion)尚未做（等 user 令）|
| 下一棒 | **POA&M 階段端到端實測**（finalize → 缺失改善 → 結案），+ 補里程碑負責人指派 |
| 接手前必讀 | 本文件全讀 + `implementation-plan-final-stages.md` |

---

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

**大圖**：FR-038 把 OSCAL v1→v2。本弧處理**生命週期最後兩階段 FE 遷移 + AR 溯源**。OSCAL 評估鏈：Catalog→Profile→SSP→AP→AR→POA&M。

**這弧拍板的關鍵決策**：
1. **判定粒度收斂到「控制層」**（user 2026-06-17 拍板）：每控制一筆 finding（不展開 AO）。理由：跟舊流程一致、CMMC+ISO 通用（ISO 無 AO）、POA&M/覆核 per 控制乾淨、舊三欄 UI 可沿用。AO 細節改放 observation。
2. **manager 全域 override**：manager 可推進/操作所有階段（含 auditor 專屬的 audit/poam）。
3. **AR 溯源比照 OSCAL**：受評對象用 `result.assessment-subjects[]`（新建 child table）；風險鉤稽寫 OSCAL 原生 `finding.related_risks[]`（dual-write）。

**冷接自檢 4 問**（答不出回去讀）：
1. 判定是 AO 層還控制層？→ **控制層**（init_ar_matrix 每控制一筆 finding；舊輪是 AO 級，FE 用聚合相容）。
2. 受評對象/風險鉤稽存哪？→ `oscal.ar_assessment_subjects`（result.assessment-subjects）/ `finding.related_risks`（OSCAL 原生）+ join table `assessment_finding_risks`（查詢索引）。
3. 為何 manager 能做 auditor 的事？→ 全域 override（4 處：stage_advance / AP service / AR service / round 狀態 `_check_role`）。
4. POA&M item 從哪來？→ finalize_audit → `generate_from_findings`（not_met finding → poam item，related_findings 軟引用）。

---

## §0 接手讀序

1. 🔒 本文件「WHY」全讀 + 答出自檢 4 問
2. `docs/features/FR-038-2606-oscal-redesign/implementation-plan-final-stages.md`（本弧 plan）
3. 主嫌路徑檔（見 §5 檔案地圖）
4. **先重啟 BE + FE reload**（§4 pre-flight）再動

---

## §1 目前狀態（已完成，分 verified / 待重啟驗證）

### BE + jedi-oscal-v2（**全部需重啟 BE 生效**；import smoke 已過）
- **進階段三修**：`count_reviewed_controls` len bug、`audit_round_app_service._to_dto` datetime 序列化、`stage_advance_service` manager override（line 110/286）
- **frozen SSP resolver**：`domain/oscal/service/ssp_project_resolver.py` 加 `_resolve_project_id`（living + frozen round 兩鏈）；DI `oscal_containers.py` 注入 `project_audit_round_domain_service`；repo/domain 加 `get_by_ssp_id`
- **AR 溯源**：新表 `oscal.ar_assessment_subjects`（migration `scripts/sql/2026-06-17-ar-assessment-subjects.sql` **已套 DEV**；stg/poc/prod 待辦）+ 套件 6 新檔（model/entity/query/mapper/repo if+impl）+ `assessment_result_service.add_result(assessment_subjects=)` / `list_result_subjects`
- **start_auditing echo**：`init_ar_matrix_for_round` 把 AP 受評對象→AR subjects、AP tasks→`ar_results.assessment_log`；**判定收斂控制層**（`init_finding_matrix(control_ids)`）
- **observation.subjects**：create_observation 收 subjects（route+schema 已通）；get_findings 回 `assessment_subjects`
- **manager override 補齊**：`assessment_plan_app_service._check_auditor`、`assessment_result_app_service._check_auditor`、`audit_round_app_service._check_role` 全放行 manager
- **我的稽核重建**：`infra/grc/repository/auditor_dashboard_query.py` 2A dark→查 v2 audit_round（+ verdict 統計）；serializer 加 `round_uid`
- **風險 CRUD**：AR app service `update_risk`/`delete_risk` + 套件 `assessment_risk_service.update_risk/delete_risk`；route PUT/DELETE `/audit-round/<r>/ar/risk/<risk_uid>` + `UpdateRiskRequest`
- **風險鉤稽 dual-write（方案 A）**：`link_risk_findings`/`delete_risk` 同步寫 OSCAL `finding.related_risks[]`（`_set_finding_related_risk` helper）+ join table；`list_risks` 回 `linked_findings`（finding_uid+control_id）

### FE（**整頁重整生效，不需重啟 BE**）
- **稽核頁** `RoundAuditReviewView.vue`（新）：三欄（沿用 `AuditTreeNav`+`AuditControlRef` + v2 控制層判定面板）；判定 Tab（符合/不符合/待判定 + 說明 + 觀察帶受評對象）+ 風險 Tab；左樹預設全展開 + 判定圓點（met→pass/not_met→fail）；字型放大一級；證據接真實來源（檔案/連結 from job-evidences、問卷 from job detail task_surveys 去重）；**AO 級舊輪用聚合相容**（任一 AO not_met→控制 not_met，判定寫回全部 AO finding）
- **風險卡片**：檢視/編輯合一（標題+等級+描述+來源一次存）、刪除有 confirm、「來源」chips
- **POA&M 頁** `RoundPoamView.vue`（新）：三層（項目→矯正措施→里程碑），**尚未 runtime 測**
- **稽核計畫填寫頁** `RoundApAuthoringView.vue`（本弧+前；受評控制項/對象/行程；唯讀模式 by user）
- **我的稽核** `MyAuditsView.vue`：導 round-scoped
- router 加 `project-audit-review-round` / `project-poam-round`；總覽按鈕 isRoundRoute 派發

---

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

1. **控制層 vs AO 級輪並存**：新輪 = 控制層（1 finding/控制）；**舊輪（如 ar_result 467）是 AO 級**（N finding/控制）。FE 用聚合相容兩者。**測 POA&M 建議用新控制層輪**（舊輪 finalize 會 per-AO 生 POA&M item）。
2. **manager override 在 4 處**，少一處就卡（曾漏 `audit_round_app_service._check_role` → start_auditing 報「不具稽核員角色」）。
3. **多 auditor 閘門名稱不同**：`_check_role`(round) vs `_check_auditor`(AP/AR) vs stage `_resolve_main_roles`。
4. **finding state 兩 token**：`target_status_state` 欄 = OSCAL product token（satisfied/not-satisfied/pending）；`target_status.product_state` JSONB = FindingState 值（met/not_met/pending）。get_findings 的 `state` 用後者；FE 用 met/not_met/pending。`AuditTreeNav` 圓點吃 v1 詞彙(pass/fail) → 需 `toTreeVerdict` 對應。
5. **證據來源**：`/job-evidences?job_execution_uid=` **只回檔案/連結**，問卷在 job detail 的 `task_surveys`（一份問卷多指派=多 task_survey，需去重）。
6. **dual-write 同步**：改 risk↔finding 連結務必同時維護 join table + `finding.related_risks`（已封裝在 link_risk_findings/delete_risk）。
7. **問卷 Dialog/embed 已還原**：曾試過 Dialog+iframe+AppLayout embed，因難讀已 revert → 問卷現在開新分頁。**勿再加回**除非 user 要。

---

## §3 下一棒（POA&M）

### 3.1 端到端實測（主任務）
重啟 BE + 用**新控制層輪**：planning → ap_authoring →（啟動稽核）→ audit（判定不符合 + 風險鉤稽）→（finalize）→ **缺失改善**：
- 查 `round.status=remediation`、`round.poam_id` 有值、`oscal.poam_items` 筆數 = not_met 控制數、`poam_item.related_findings` 指回 finding
- POA&M 頁加矯正措施 + 里程碑 → 里程碑全 done → 結案（close_round 強制 `all_items_closed`）→ `closed`

### 3.2 待補：里程碑「負責人指派」（本弧 defer）
- **為何 defer**：`RoundPoamView.addMilestone` 目前只送 `{title, target_date}`。BE `add_milestone` 的 `assignee_user_id` 是 **int user_id**，但 FE 手上的 participants 是 **user_uid 字串** → 型別不一致，需做 user_uid→user_id 對應（查 users 或讓 BE 收 uid）。
- 做法建議：BE add/update_milestone 改收 `assignee_user_uid`（內部轉 id），FE 用 participants 當候選；或 FE 拿 user_id。

---

## §4 Pre-flight（必跑）

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

---

## §5 檔案地圖（為何 read）

**BE**：`assessment_result_app_service.py`（AR 判定/觀察/風險/溯源核心）、`audit_round_app_service.py`（start_auditing/finalize/close_round/launch_reverify + _check_role）、`ssp_project_resolver.py`（frozen SSP 反查）、`auditor_dashboard_query.py`（我的稽核）、`assessment_plan_app_service.py`（AP 寫入 + _check_auditor）、`stage_advance_service.py`（推進 override）
**套件**：`ar/assessment_result_service.py`（add_result/list_result_subjects）、`ar/assessment_risk_service.py`（update/delete_risk）、`ar/oscal_ar_assessment_subjects*`（新 child table 6 檔）
**FE**：`RoundAuditReviewView.vue`（稽核三欄主檔）、`RoundPoamView.vue`（POA&M）、`MyAuditsView.vue`、`ProjectAuditorOverview.vue`（按鈕派發）、`AuditTreeNav.vue`（expandAll + 圓點）

---

## §6 未 commit 改動清單（我的，三 repo；**勿用 -am，顯式 git add**）

**BE**（M）：`api/grc/serializers/auditor_dashboard.py`、`api/project/__init__.py`、`api/project/routes/audit_round_route.py`、`api/project/serializers/audit_round.py`、`app/flow_engine/service/stage_advance_service.py`、`app/grc/service/{assessment_plan,assessment_result,audit_round}_app_service.py`、`di_containers/oscal/oscal_containers.py`、`domain/grc/repository/i_project_audit_round_repo.py`、`domain/grc/service/project_audit_round_domain_service.py`、`domain/oscal/service/ssp_project_resolver.py`、`infra/grc/repository/{auditor_dashboard_query,project_audit_round_repo_impl}.py`（??）`scripts/sql/2026-06-17-ar-assessment-subjects.sql`
> ⚠️ BE 的 `pyproject.toml` / `docs/features/README.md` / `proposal.html` **不是我改的**（pre-existing / user in-flight）— 勿一起 commit。

**套件**（M）：`jedi_oscal_v2/app/service/ar/assessment_result_service.py`、`.../assessment_risk_service.py`；（??）`jedi_oscal_v2/{domain/entity/ar,domain/repository/ar,infra/mapper/ar,infra/model/ar,infra/repository/ar}/*ar_assessment_subjects*`（6 檔）
> ⚠️ 套件的 `catalog_service.py`、`oscal_io_service.py`、`tests/`、`../jedi-*/CLAUDE.md` **不是我改的** — 勿一起 commit。

**FE**（M）：`src/components/grc/AuditTreeNav.vue`、`src/config/locales/index.js`、`src/config/router/index.js`、`src/views/project/MyAuditsView.vue`、`src/views/project/ProjectAuditorOverview.vue`；（??）`src/views/project/{RoundApAuthoringView,RoundAuditReviewView,RoundPoamView}.vue`、`src/config/locales/i18n/{en,zh-tw}/{ap-authoring,round-audit-poam}.json`

---

## §7 行為規範提醒
- **不切 branch**；**push 等 user 明示**；三 repo 各自 commit、顯式 git add、禁 `-am`
- 改 BE service/repo/套件 → **提醒 user 重啟**（服務 user 自起、Claude 不啟動）
- 動 FE 前讀 FE CLAUDE.md；不晶晶體；plan 假設先 verify
- **收尾類動作等 user 明確下令**：changelog / SUMMARY / design §11 / Notion / commit 收尾 —— 目前都還沒做

---

## §8 不在本期 scope / follow-up（勿順手做，列著）
- **severity OSCAL characterizations facets**（目前簡化 `{"severity":...}`，非 likelihood×impact facets）— user 已知,列 follow-up
- **assessment-assets**（稽核工具/平台）— OSCAL 有,BE 無端點
- **我的稽核 evidence 統計**（目前 0/0）
- **ISO 27001 落地差異 analysis 文件**（user 說最後再補；Notion 已有「稽核生命週期設計評估」討論頁）
- **project_start 重複建 IR root cause**（前弧 follow-up）
- stg/poc/prod 套 `2026-06-17-ar-assessment-subjects.sql`

---

## §9 給 fresh session 的超短 prompt
```
讀 docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-17-final-stages-audit-poam-handoff.md，
先答冷接自檢 4 問（判定=控制層 / 溯源存哪 / manager override 4 處 / POA&M item 來源）再開工。
本弧已完成稽核+POA&M FE(v2 round-scoped)+AR 溯源+風險 CRUD+我的稽核(全在 working tree、未 commit)。
下一棒：請 user 重啟 BE → 用「新控制層輪」端到端實測 POA&M（finalize→缺失改善→結案），
查 round.status/poam_id/poam_items/related_findings；再補里程碑負責人指派（user_uid→user_id 對應，見 §3.2）。
不切 branch、push 等 user、收尾等 user 令。
```
