FR-038 下一棒 START HERE — 稽核計畫填寫(ap_authoring 階段)往後推進

接續 2026-06-17 專案規劃頁 v2 遷移弧(已收口,見 2026-06-17-planning-v2-SUMMARY.md)。 本棒:把稽核生命週期從「規劃(planning)」往後推進到「稽核計畫填寫(ap_authoring)」。


🧭 原始需求 / WHY(先懂這個再動)

稽核輪次生命週期(D5,已 seed)planning → ap_authoring → audit → poam → End。對應 round.status:planning → audit_planning → auditing → remediation → closed。設計依據:round-flow-engine-reintegration-design.md §4.1 / §4.2。

  • planning(已做):補 SSP 現況 + per-AO 收證據任務指派(本弧完成)。完成(按「稽核計畫填寫」鈕)→ handler launch_audit:snapshot living SSP → round.ssp_id(frozen)+ 建 AP 草稿(create_draft_for_snapshot)→ status audit_planning
  • ap_authoring(本棒)稽核員(auditor)規劃這一輪要稽核什麼 —— 選 reviewed-controls(適用/受評控制項子集) + subjects(受評標的)+ tasks。完成(submit)→ handler start_auditing:建 AR(assessment result)+ AO 全量判定矩陣(每個 reviewed control 的每個 AO 一列待判定)→ status auditing
  • audit / poam:之後的棒,不在本次。

為什麼是 auditor:reviewed-controls / subjects / tasks 是「稽核規劃」決策,屬稽核員職責(D3)。

冷接自檢 4 問(答不出回去讀 design §4):

  1. ap_authoring 完成跑哪個 handler、做什麼?(→ submit_apstart_auditing:建 AR + AO 全量矩陣,status→auditing)
  2. ap_authoring 的前置條件?(→ ap_reviewed_controls_set:reviewed-controls 非空)
  3. 推進按鈕從哪取 label?(→ 下一階段 audit.entry_button_label_i18n=「啟動稽核」,已 seed)
  4. AP 綁在哪?(→ round.assessment_plan_id,launch_audit 時 create_draft_for_snapshot 建)

§1 現況:BE 大致就緒,FE 待建

BE(已 wired,alive)

  • stage_object ap_authoring:handler_key=submit_ap、precondition_key=ap_reviewed_controls_set、main_role=auditor、entry label=「稽核計畫填寫」(本弧補)。
  • handler SubmitApOnCompleteHandleraudit_round_app_service.start_auditing(round_uid)(建 AR + AO 矩陣)—— 已註冊(register_stage_hooks_to_registry)。
  • precondition ApReviewedControlsSetCheck(reviewed-controls 非空)—— 已註冊。
  • AP app service:app/grc/service/assessment_plan_app_service.pycreate_draft_for_snapshotset_reviewed_controlsget_ap_for_round 類)。
  • route:ApReviewedControlsRoute = GET/PUT /ap/<ap_uid>/reviewed-controlsapi/project/routes/audit_round_route.py)。
  • ⚠️ subjects / tasks 端點待確認:design §4.2 列了 PUT …/subjects|tasks,但本弧沒查到對應 route → 下一棒 pre-flight grep 確認是否存在,沒有就補 BE

FE(待建)

  • 沒有 ap_authoring viewsrc/views/project/ 只有 ProjectApListView / ProjectAuditorOverview / ProjectPlanningView)。
  • design §4.2:新 RoundApAuthoringView.vue(或併入規劃頁的 routed stage ap_authoring):reviewed-controls 多選 + subjects + tasks,接上述 PUT。
  • 入口:FlowPhaseBanner 在 audit_planning 階段顯示「稽核計畫填寫」routed stage(Banner 已 round-scoped,本弧驗過)。

§2 本棒做法(建議順序)

  1. pre-flight 驗 live 路徑(CLAUDE.md 鐵則):grep 確認 set_reviewed_controls / subjects / tasks 的實際 BE method + route + 是否 alive(非 2A dark)。reviewed-controls 確定有;subjects/tasks 要確認。
  2. FE 稽核計畫填寫頁:比照規劃頁的 round-scoped + control-tree pattern(reviewed-controls 多選可沿用 control-tree 群組→控制項樹)。reviewed-controls 寫 PUT /ap/<apUid>/reviewed-controls
  3. 推進驗證:reviewed-controls 設好 → Banner「啟動稽核」鈕可按(precondition pass)→ submit_ap → start_auditing 建 AR + AO 矩陣 → status auditing。查 DB assessment_results + AO 矩陣列。
  4. (若需)補 subjects / tasks BE 端點 + FE。

§3 pre-flight 指令

cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
git branch --show-current   # feature/oscal-refactor
git log --oneline -3 | cat  # 應見 4e724fea
# AP authoring 端點/服務盤點
grep -rn "set_reviewed_controls\|def.*subjects\|def.*tasks\|create_draft_for_snapshot\|def start_auditing" app/grc/service/assessment_plan_app_service.py app/grc/service/audit_round_app_service.py
grep -rn "reviewed-controls\|/subjects\|/tasks" api/project/ | grep add_resource
# import smoke
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.grc.service.assessment_plan_app_service; print('OK')"

§4 規範提醒

  • 不切 branch;push 等 user;跨 repo 各自 commit、顯式 git add、禁 -am。
  • 改 BE service → 提醒 user 重啟(無 hot reload);服務 user 自己起。
  • 動匯入/敏感管線前 pre-flight;plan 假設先 verify。
  • 收尾類(changelog/SUMMARY/Notion)等 user 明確下令。

§5 本弧未完 follow-up(往後補,非本棒主線)

  • 批次維護「任務 Excel 匯入/匯出」(job_export_query / job_import_lookup 2A dark)—未串通。
  • 雲端整合 Tab —未測。
  • #11 Method A 前向一致(匯入/SoA AO key 對齊 _obj.N)。
  • stg/poc/prod:套本弧 2 migration + 跑 backfill。
  • 規劃頁死碼清理(unused participant handlers / fetchAOJobs)。