手冊首頁 / 專案與任務 / 稽核執行 功能群總覽

稽核執行 功能群總覽

本檔放「本功能群多個頁面共用的知識」:頁面地圖、稽核階段與狀態機、角色權限基調、AP→AR→POA&M 核心資料模型。

各頁面 spec 引用本檔,只寫自己頁面的差異——改共用邏輯時改這裡一份。稽核輪次狀態機本體與轉換圖在 專案管理群 _overview §2(同一個 compliance.project_audit_rounds 實體),本檔不重畫,只補本群相關的階段轉換與資料流。

1. 頁面地圖

稽核執行群承接啟動稽核之後的三個階段主場頁面,加上兩個跨階段的個人任務入口。

頁面 FE route 用途一句話 spec
稽核計畫填寫頁 /project/projects/:id/round/:roundUid/ap-authoring audit_planning 階段:定義 AP(受評控制項 / 行程與查核方法 / 受評對象 / 稽核人員) 稽核計畫填寫頁
稽核執行頁(AR 判定) /project/projects/:id/round/:roundUid/audit auditing 階段:逐控制項判定 met/not_met/pending、記觀察、建系統風險、finalize 稽核執行頁
POA&M 矯正頁 /project/projects/:id/round/:roundUid/poam remediation 階段:整改計畫與里程碑追蹤 ⬜ poam.md
我的任務(受稽方執行) /project/task-manage 跨專案的證據蒐集任務收件匣 ⬜ my-tasks.md
我的稽核 /project/audit-manage 稽核員視角的待辦稽核清單 ⬜ my-audits.md

頁面 × 階段對應(每個稽核輪次階段有一個「主場頁面」):

planning        → 專案規劃頁(專案管理群)        living SSP 現況 + 證據蒐集任務準備
audit_planning  → 稽核計畫填寫頁(本群)          凍結 SSP 快照 → 定義 AP
auditing        → 稽核執行頁 / AR 判定(本群)    逐控制項判定 → 產出 AR
remediation     → POA&M 矯正頁(本群)            not_met 缺失 → 整改追蹤
closed          → 各頁唯讀

進入本群頁面前,輪次已 launch-audit 過(living SSP 已凍結成 project_audit_rounds.ssp_id 快照)。本群所有頁面讀寫的控制項母體都來自凍結快照,不是 living SSP——這是與專案規劃頁最大的差異。

2. 稽核階段與狀態機(本群關心的轉換)

輪次 7 態狀態機與完整轉換圖見 專案管理群 _overview §2。本群關心的是「啟動稽核之後」的三段推進,enforcement 在 app/grc/service/audit_round_app_service.py

轉換 API 誰能按 前置條件 失敗 error code
啟動稽核 launch-audit → audit_planning POST /audit-round/{uid}/launch-audit manager status=planning、living SSP 非空(凍結快照)
開始稽核 start-auditing → auditing FE 實走 POST /project/{uid}/audit-round/{uid}/stage/advance(precondition registry);底層直接 API POST /audit-round/{uid}/start-auditing auditor(manager 可代行) stage/advance 路徑ApReviewedControlsSetCheck 查 reviewed-controls 非空(oscal_stage_preconditions.py:40,precondition 失敗回 i18n reason key flow_engine.precondition.ap_reviewed_controls_required,非 error code);直接 API start_auditing() 只驗 AP 存在audit_round_app_service.py,可繞過 reviewed-controls 檢查) 直接 API 無 AP → GRC_AP_NOT_FOUND。(GRC_AP_REVIEWED_CONTROLS_REQUIRED(412)實為 AP 編輯期 set_reviewed_controls 擋空清單,assessment_plan_app_service.py:470,非本轉換的關卡)
完成判定 finalize → remediation | closed POST /audit-round/{uid}/ar/finalize auditor 無 pending 判定 + 每條 risk 至少關聯 1 finding GRC_VERDICT_INCOMPLETE / GRC_RISK_NO_FINDING_LINKED(412)
結束本輪 close-round → closed POST /audit-round/{uid}/close-round manager POA&M 全部 closed GRC_POAM_NOT_ALL_CLOSED(412)
啟動複驗 launch-reverify(開覆核輪) POST /audit-round/{uid}/launch-reverify auditor(manager 可代行) status=pending_reverify

finalize 的分歧(本群最重要的轉換)ar/finalize 依判定結果決定去向——

  • not_met = 0 → 輪次直接 closed(無缺失,不進整改)
  • not_met > 0 → 自動從 not_met findings 生成 POA&M(PoamService.generate_from_findings),輪次轉 remediation、回填 project_audit_rounds.poam_id

通用 phase guard(本群與專案規劃頁的關鍵差異):本群每個寫入 API 都用 common/util/round_guard.pyassert_round_phase(round, {允許階段}) 硬擋——AP 寫入只在 audit_planning、AR 寫入只在 auditing,過了階段回 GRC_ROUND_PHASE_READONLY(412)。這與專案規劃頁「唯讀只是 FE 鎖、BE upsert 照寫」不同(見 project-planning §12 坑 3);本群的唯讀是 BE 強制的。

3. 角色權限基調

角色是誰

  • auditor(稽核員,本群主角)——填 AP 行程與查核方法、逐控制項判定 met/not_met、記觀察與風險、finalize。「身為 auditor,我要依稽核計畫逐控制項判定合規狀態,記錄觀察與風險,完成後送出這一輪的結果。」
  • manager(專案負責人)——可代行 auditor 的所有判定操作,且獨掌輪次收尾。「身為 manager,我要在 auditor 判定完成、整改都結束後,把這一輪正式結案。」
  • viewer(一般參與者)——檢視稽核進度,無編輯權。

專案層角色compliance.project_participants.role):manager / auditor / viewer。角色定義與 manager 全域代行慣例見 專案管理群 _overview §3。本群的基調轉向 auditor 為主角(專案規劃頁以 manager 為主角):

動作類型 manager auditor viewer
AP 編輯(受評控制項 / 行程 / 對象 / 稽核人員) ✓(代行)
AR 判定(verdict / 觀察 / 風險 / finalize) ✓(代行)
輪次生命週期(launch / close-round)
檢視

BE 檢查點:AP / AR 寫入走 _check_auditor(project_id, user_id)role in ("auditor","manager") 才過,否則 GRC_NOT_AUDITOR);close-round_check_role(..., "manager")FE 判定:兩頁都以 roundStatus === '<本頁階段>' && (isAuditor || isProjectManager) 決定可編輯(RoundApAuthoringView.vue / RoundAuditReviewView.vue),與 BE 對齊。

4. 核心資料模型(AP → AR → POA&M)

本群走的是 OSCAL 資訊流的後半段:Catalog → Profile → SSP → AP → AR → POA&M。三份文件是獨立的 OSCAL root,靠 import-* FK 串接。

ae_core_data_model cluster_compliance compliance(業務層) cluster_ssp oscal — 受評對象 cluster_ap AP 層(稽核計畫填寫頁) cluster_ar AR 層(稽核執行頁) cluster_poam POA&M 層(矯正頁) rounds project_audit_rounds 稽核輪次(ssp_id / assessment_plan_id / ar_result_id / poam_id 回填) ssp_frozen system_security_plans 凍結 SSP 快照(per 輪) rounds->ssp_frozen ssp_id ap assessment_plans AP root(import_ssp_id) rounds->ap assessment_plan_id ar_root assessment_results AR root(import_ap_id) rounds->ar_root ar_result_id poam poams POA&M root rounds->poam poam_id(finalize 回填) ap->ssp_frozen import_ssp_id ap_rc ap_reviewed_controls 查核範圍(1:1) ap->ap_rc 1:1 ap_tasks ap_tasks 行程(timing) ap->ap_tasks 1:N ap_act ap_assessment_activities 查核方法(activity) ap->ap_act 1:N ap_subj ap_assessment_subjects 抽查對象 ap->ap_subj 1:N ap_tasks->ap_act associated_activities ar_root->ap import_ap_id ar_res ar_results 每輪一筆 result ar_root->ar_res 1:N(每輪) findings assessment_findings 逐 AO 判定(target_status_state) ar_res->findings 1:N obs assessment_observations 觀察 ar_res->obs 1:N risks assessment_risks 系統風險(severity 在 JSONB) ar_res->risks 1:N findings->risks finding_risks 多對多 poam_items poam_items 矯正項目 findings->poam_items not_met → generate poam->poam_items 1:N
稽核執行核心資料模型:AP(黃)承接凍結 SSP、AR(綠)承接 AP、POA&M(橘)承接 not_met findings;虛線 = 跨文件 uuid 軟參照

三份文件的職責

文件 root 表 承接自 內容 本群哪頁編輯
AP(評估計畫) oscal.assessment_plans import_ssp_id → 凍結 SSP reviewed-controls(查核範圍)/ tasks(行程+方法)/ assessment-subjects(抽查對象)/ parties(稽核人員) 稽核計畫填寫頁
AR(評估結果) oscal.assessment_resultsoscal.ar_results(每輪一筆 result) import_ap_id → AP findings(逐 AO 判定)/ observations(觀察)/ risks(系統風險) 稽核執行頁
POA&M(矯正計畫) oscal.poamsoscal.poam_items poam_id(round 回填),內容源自 AR 的 not_met findings poam-items / remediations(response)/ milestones POA&M 矯正頁

engagement 模型(本群最重要的概念):一個 engagement = 1 輪主稽核(initial/surveillance)+ N 輪覆核(close-out),共用同一份 AP(OSCAL import_ssp_id 單一)與同一份 AR rootimport_ap_id 單一),但每輪各 append 一筆 oscal.ar_results;覆核輪 launch-reverifyclone 母輪 APclone_ap_for_round,party uuid 依 name remap)成獨立副本。

AO(assessment objective)判定粒度

  • 判定矩陣是 AO 層:每個在範圍控制項的每個 AO = 一筆 finding(target_id = ao_id_obj.N 格式);控制項無 AO part 時 fallback 成 control 層(target_id = control_id
  • UI 與 POA&M 是控制項層:稽核執行頁面板以控制項為單位,POA&M 一控制項一 item
  • 控制項 verdict roll-up:控制項下所有 AO 都 met → 控制項 met;任一 AO not_met → 控制項 not_met
  • AO 衍生邏輯 app/grc/service/ao_derivation.py::derive_ao_pairs(B5 AR 與規劃期 prep-job 共用),AO 識別碼 canonical = _obj.N(見 專案管理群 _overview §5

5. 本群共用的已知坑

  1. 凍結快照,不是 living SSP:本群讀控制項 / 資產一律走 project_audit_rounds.ssp_id(launch-audit 當下拍的快照)。專案規劃頁的 living SSP 之後繼續演進,不影響本輪稽核
  2. phase guard 是 BE 硬擋:AP 只能在 audit_planning 改、AR 只能在 auditing 改,過階段 BE 回 412(不是只有 FE 鎖)——與專案規劃頁相反,別把規劃頁「BE 照寫」的心智模型套過來
  3. finding 判定 AO 層 / 顯示與 POA&M 控制項層:改判定相關邏輯先確認你在哪一層;statsmet/not_met/pending(AO 計數)與 controls_with_not_met(控制項計數)兩套
  4. verdict 值 API ≠ DB,但 target_status_state 直存 4 種 token:API 用 FindingState enum(met/not_met/pending,另有第 4 值 not_applicable);DB oscal.assessment_findings.target_status_state 直接存 4 種 product token——satisfied(met)/ not-satisfied(not_met)/ not-applicable(na,OSCAL 規格無此值、比照 pending 直存 product token)/ pending(未判定)。target_status JSONB 是全狀態的冗餘鏡射(非 pending 專屬),比對以 target_status_state 為準(jedi-oscal-v2 .../domain/service/ar/ar_finding_matrix_service.py
  5. risk severity 存 JSONBoscal.assessment_risks 無 severity 欄,等級存在 characterizations JSONB(稽核員判定),不是列舉欄位
  6. finding ↔︎ risk 多對多oscal.assessment_finding_risks 關聯表 + 雙寫 finding.related_risks[](OSCAL 匯出保真);finalize 前每條 risk 必須至少關聯一條 finding
  7. 一 engagement 一 AP/AR:不要為覆核輪另開全新 AP/AR root;覆核走 clone_ap_for_round + append ar_results。輪次反查走 task_assignees.project_id,不要比對 workflow_execution_uid(那是死路)
  8. 輪次狀態 BPMN 驅動:直接 UPDATE project_audit_rounds.status 會跟 workflow 脫鉤,一律走 API(start-auditing / ar/finalize / close-round