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 = managerblspan = auditor(覆核輪要判定走 auditor;發起覆核 FE 按鈕目前只給 manager —— 見 §2.4)。

驗證 SQL(密碼查 .env DB_SECRET,host 192.168.50.188:25432 db guidant_ai_dev user cmmgr):

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(每步驟查)

-- 覆核輪 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.pylaunch_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 NULLCAST(: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.vueMyAuditsView.vuelocales/index.jsrouter/index.js(前一弧)

§6 Pre-flight(必跑)

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 現況(確認測試起點乾淨)

-- 應看到 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 行為規範重要提醒

  • 不切 branchpush 等 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 令。