手冊首頁 / 專案與任務 / 稽核計畫填寫頁

稽核計畫填寫頁(AP Authoring)

功能群:稽核執行|階段轉換、AP→AR→POA&M 資料模型、角色基調先讀 功能群總覽

事實基準:2026-07-05 從 FE / BE code 掃出(FR-038 v2 round-based + FR-040 行程鉤稽架構);API schema 逐條對照 api/project/serializers/audit_round.pyapp/grc/service/assessment_plan_app_service.py

1. 功能描述

稽核計畫填寫頁是稽核輪次 audit_planning 階段的主工作區:稽核員(auditor)在此定義本輪的 AP(Assessment Plan,評估計畫)——要查哪些控制項、用什麼行程與查核方法進行、實地會碰到哪些受評對象、由哪些稽核人員執行。編輯對象是輪次的 AP 文件oscal.assessment_plans),其 import_ssp_id 指向 launch-audit 當下凍結的 SSP 快照(見 _overview §4);start-auditing 推進後 AP 凍結、本頁轉唯讀。

主要使用者:稽核員(auditor)、專案經理(manager,可代行)。

角色速覽(完整定義與 manager 全域代行慣例見 _overview §3;本群主角是 auditor):

角色 一句話
auditor 稽核員 — AP 的擁有者與填寫者
manager 專案經理 — 可代行 auditor 的 AP 編輯
viewer 一般參與者 — 只能檢視

1.1 功能總覽(本頁全部功能)

# 功能 說明 位置 詳述
1 行程(task)新增 / 刪除 逐條規劃稽核行程,每條有標題 / 類型(action / milestone)/ 時間 Tab0「行程與方法」面板頭 + 卡片 UC-AP-01
2 查核方法多選 + per-方法查核指引 每條行程勾查核方法(EXAMINE / INTERVIEW / TEST,來自 system_menu AUDIT_METHOD),每個方法可填「看什麼 / 怎麼看」指引 行程卡 Methods MultiSelect + 指引列 UC-AP-01
3 行程鉤稽控制項 每條行程勾這次查哪些控制項(母體 = 完整凍結控制樹) 行程卡 Controls MultiSelect UC-AP-01
4 行程鉤稽受評對象 每條行程勾實地會碰到的資產 / 人員(component / inventory-item / party / user,源自 SSP 快照) 行程卡 Subjects MultiSelect UC-AP-01
5 行程鉤稽參與稽核人員 每條行程指派執行的稽核人員(AP parties) 行程卡 Participants MultiSelect UC-AP-01
6 就地新增稽核人員 在參與人員下拉底部「+新增稽核人員」開 dialog,填姓名 / email / 電話後即建立並帶回當前行程 行程卡 → 新增稽核人員 dialog UC-AP-02
7 儲存行程 「儲存行程」把全部行程全量覆寫寫入(含方法 / 控制項 / 對象 / 人員 / 指引) Tab0 面板頭 UC-AP-01
8 受評控制項總覽(唯讀) 由行程勾選的控制項自動彙整去重,唯讀顯示 Tab1 §6.1
9 受評對象總覽(唯讀) 由行程勾選的對象自動彙整,按類型分組唯讀顯示 Tab2 §6.1
10 稽核紀錄 DOCX 匯入 上傳既有稽核計畫 docx → 解析預覽 → 確認匯入成行程 Header「匯入」按鈕 → dialog UC-AP-03
11 階段推進(FlowPhaseBanner) 顯示 audit_planning 階段點與前置(受評控制項非空),符合條件可推「啟動稽核」 頁面頂部橫幅 UC-AP-04
12 唯讀模式 非 audit_planning 階段(或非 auditor/manager)整頁轉唯讀,表單換成唯讀顯示 全頁 §4

UC(§2)展開本頁所有具實質後端邏輯的功能:行程規劃(1,含 2~5 / 7 的鉤稽與儲存)、就地新增稽核人員(6)、DOCX 匯入(10)、階段推進 / 啟動稽核(11)。受評控制項 / 受評對象兩 tab(8 / 9)是行程選取的唯讀投影(BE _backfill_scope_from_tasks 回填),非獨立編輯;唯讀模式(12)是頁面狀態切換——兩者屬純顯示,見 §4 / §6.1。生成草稿(generate-draft)是啟動稽核時的內部產物、非本頁常態操作(規格見 §6.3)。

2. Use Case

角色速覽(完整定義與 manager 全域代行慣例見 _overview §3;本群主角是 auditor):

角色 一句話
auditor 稽核員 — AP 的擁有者與填寫者
manager 專案經理 — 可代行 auditor 的 AP 編輯
viewer 一般參與者 — 只能檢視

流程圖視覺慣例:菱形 = 判斷、橘底 = 例外/擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。

UC-AP-01 規劃稽核行程並儲存

項目 內容
角色 auditor(manager 可代行)
前置條件 已登入、為專案參與者;輪次狀態 = audit_planning
產出 / 後置條件 oscal.ap_tasks + ap_assessment_activities + ap_task_subjects + ap_task_participants 全量覆寫;受評控制項 / 對象由 _backfill_scope_from_tasks 回填 ap_reviewed_controls / ap_assessment_subjects
ap_uc_01 start gate 輪次 = audit_planning 且使用者是 auditor/manager? start->gate ro 表單換唯讀顯示 (start-auditing 後凍結) gate->ro add 新增行程 填標題/類型/時間 gate->add hook 行程鉤稽: 方法(+per-方法指引) 控制項/受評對象/稽核人員 add->hook save 點「儲存行程」 PUT /ap/{apUid}/tasks(全量覆寫) hook->save perm BE 檢查:auditor/manager +phase = audit_planning? save->perm deny 403 GRC_NOT_AUDITOR /412 GRC_ROUND_PHASE_READONLY perm->deny ok ap_tasks/activities/task link 表更新 _backfill_scope_from_tasks 回填受評範圍 perm->ok
UC-AP-01 流程:audit_planning 才可編輯;儲存為全量覆寫,BE 檢查 auditor + phase guard 硬擋

UC-AP-02 就地新增稽核人員

項目 內容
角色 auditor(manager 可代行)
前置條件 輪次 = audit_planning;在某行程的參與人員下拉觸發
產出 / 後置條件 AP metadata 下新增一筆 oscal.parties(type=person,role=assessor);建立後自動帶回觸發的行程;同名擋下(GRC_AP_PARTY_NAME_EXISTED
ap_uc_02 start open 行程參與人員下拉底部 「+新增稽核人員」開 dialog start->open fill 填姓名(必填)/email/電話 open->fill save 確認 POST /ap/{apUid}/parties fill->save dup BE 檢查:同名稽核人員已存在? save->dup deny 409 GRC_AP_PARTY_NAME_EXISTED dup->deny ok oscal.parties 新增 (掛 AP metadata_id,role=assessor) dup->ok back fetchApParties 重載 自動選入觸發的行程 ok->back
UC-AP-02 流程:下拉底部按鈕 → dialog 填姓名 → 同名檢查 → 建 party → 帶回當前行程

UC-AP-03 稽核紀錄 DOCX 匯入

項目 內容
角色 auditor(manager 可代行)
前置條件 輪次 = audit_planning 且 AP 已存在(canImportDocx = !readonly && !!apUid
產出 / 後置條件 docx 解析成暫存 parse job → 預覽 → confirm 後轉成行程;解析失敗 → 400
ap_uc_03 start gate canImportDocx? (audit_planning 且 AP 存在) start->gate off 匯入按鈕隱藏 gate->off up 上傳 docx POST /ap/{apUid}/docx-imports/parse gate->up perr 解析成功? up->perr bad 400 解析失敗 (格式不符) perr->bad prev 預覽 parse job (線上檢視/修正) perr->prev conf 確認 POST /ap-docx-import/{parseUid}/confirm prev->conf ok 轉成行程寫入 整頁重載 conf->ok
UC-AP-03 流程:上傳 docx → parse job 暫存 → 預覽/修正 → confirm 寫入行程 → 重載頁面

UC-AP-04 推進階段(啟動稽核)

項目 內容
角色 auditor(manager 可代行)
前置條件 輪次 = audit_planning;受評控制項(reviewed-controls)非空——為空則「啟動稽核」按鈕禁用帶原因
產出 / 後置條件 前置 ApReviewedControlsSetCheck 通過 → handler SubmitApOnCompleteHandlerstart_auditing(AP 凍結)→ BPMN 前進、輪次狀態同步;handler 副作用先於 BPMN 推進,同 transaction 失敗全 rollback
ap_uc_04 start pre 前置:受評控制項 (reviewed-controls)非空? start->pre off 「啟動稽核」按鈕禁用 帶原因(去行程勾控制項) pre->off adv 點「啟動稽核」 POST .../stage/advance pre->adv role BE 檢查:auditor(manager 代行)? adv->role deny 無權限 → 擋下 role->deny hdl handler SubmitApOnCompleteHandler 呼 start_auditing(AP 凍結) 副作用先於 BPMN 推進 role->hdl rb handler 失敗 → 同 transaction 全 rollback hdl->rb 失敗 ok BPMN 前進一步 輪次狀態同步、本頁轉唯讀 hdl->ok 成功
UC-AP-04 流程:banner 顯示可推進→BE 檢 auditor+受評控制項非空前置→handler start_auditing 副作用先行、失敗全 rollback→BPMN 前進→輪次狀態同步

3. 權限矩陣

專案層角色基調見 _overview §3。本頁的實際檢查點(FE 與 BE 對齊,phase guard BE 硬擋):

操作 FE 判定 BE 強制 BE 檢查位置
讀 AP / 樹 / 候選對象 參與者即可(JWT) 無額外角色檢查 AssessmentPlanAppService.get_ap_for_round
寫行程 / 受評控制項 / 對象 readonly = !(roundStatus==='audit_planning' && (isAuditor||isProjectManager))(RoundApAuthoringView.vue:78-79) resolve_ap_and_check_auditor + assert_round_phase({audit_planning}) app/grc/service/assessment_plan_app_service.py:190-208
新增稽核人員 同上 readonly 同上 + 同名檢查 同檔 add_ap_party
DOCX 匯入 canImportDocx(612:!readonly && !!apUid 同 phase guard AP docx import service
啟動稽核(推進) banner user_can_advance _check_auditor + 前置 reviewed-controls 非空 oscal_stage_preconditions.py:40-62

本頁 FE / BE 權限對齊良好(與專案規劃頁的三層遞進不一致不同):兩端都用「audit_planning 階段 + auditor/manager」判定,且 BE assert_round_phase 硬擋——過階段直接打 API 也會被 412 擋,不像專案規劃頁 SSP 寫入可繞過(見 _overview §5 坑 2)。

4. 狀態機與前置條件

輪次狀態機見 專案管理群 _overview §2;本群階段轉換見 _overview §2。本頁相關 gate:

條件 效果 出處
roundStatus === 'audit_planning' 且使用者是 auditor/manager readonly=false:三 tab 可編、匯入可用、banner 顯示 RoundApAuthoringView.vue:78-79
其餘狀態或非 auditor/manager readonly=true:表單換唯讀顯示、banner 隱藏 同上(v-if="readonly" 切換)
BE 寫入 phase guard AP 寫入 API 一律 assert_round_phase({audit_planning}),過階段回 412 assessment_plan_app_service.py:206
推進前置:reviewed-controls 非空 為空 → 「啟動稽核」按鈕禁用帶原因 oscal_stage_preconditions.py:40-62
受評控制項 / 對象兩 tab 恆唯讀(行程選取的投影,無獨立儲存) RoundApAuthoringView.vue(derived computed)

5. UI 設計

版面骨架與區塊職責(TabView 三 tab;Tab0 行程卡在 editable / readonly 兩態 v-if 切換;彩色區塊可點跳對應小節錨點,API 僅標代表性):

Header:頁面標題(唯讀時加眼睛徽章)+ 專案名 GET /grc/project/{project_uid} 跳至 §2 Use Case(UC-AP-03 稽核紀錄 DOCX 匯入) 匯入稽核紀錄 ▾ POST /ap/{ap_uid}/docx-imports/parse → §2 UC-AP-03 跳至 §2 Use Case(UC-AP-04 推進階段 / 啟動稽核) FlowPhaseBanner 階段推進:audit_planning 階段點 +「啟動稽核」(前置:受評控制項非空) GET …/audit-round/{round_uid}/stage/info ・ POST …/stage/advance → §2 UC-AP-04(受評控制項為空時按鈕禁用帶原因) [0] 行程與方法(可編輯) [1] 受評控制項(唯讀) [2] 受評對象(唯讀) Tab [0] 行程與方法 — 本頁主工作區(editable / readonly 兩態 v-if 切換) Tab0 面板頭:+新增行程 | 儲存行程(全量覆寫) 跳至 §2 Use Case(UC-AP-01 規劃稽核行程並儲存) 行程(task)卡陣列 — 逐條稽核行程(標題/類型 action·milestone/時間) 每卡 5 個 MultiSelect:查核方法(+per-方法指引)/控制項/受評對象/稽核人員 + 備註 PUT /ap/{ap_uid}/tasks(全量覆寫,連帶回填受評範圍) PUT /ap/{ap_uid}/reviewed-controls ・ PUT …/assessment-subjects → 點我跳 §2 UC-AP-01 參與人員下拉底部「+新增稽核人員」dialog(就地建 party)— POST /ap/{ap_uid}/parties → §2 UC-AP-02 Tab[1] 受評控制項 / Tab[2] 受評對象(唯讀彙整) 由行程勾選內容自動去重彙整(BE _backfill_scope_from_tasks 回填),非獨立編輯 GET /audit-round/{round_uid}/ap(讀 reviewed_controls / subjects) 彩色=可點跳本頁小節錨點(框內標錨點+代表性 API)|灰=本頁描述|虛線框=dialog/依操作才出現的層 API 僅列代表性 2~3 條,完整規格見 §6
稽核計畫填寫頁版面 wireframe:Header+匯入稽核紀錄鈕、FlowPhaseBanner 階段推進、三 tab(Tab0 行程卡陣列+就地新增稽核人員、Tab1/2 受評範圍唯讀彙整)

實機截圖(STG,亮色模式,2026-07-05,帳號 blsadmin,專案「亞○ CMMC L1 合規」/ auditing 輪次):

整頁:行程與方法 tab(本輪已過規劃階段,畫面為唯讀檢視;editable 態需 audit_planning 階段輪次,目前 STG 無此狀態資料)

行程卡特寫:查核方法 / per-方法指引 / 查核控制項 / 受評對象 / 稽核人員 / 備註

受評控制項唯讀彙整 tab:由「行程與方法」勾選內容自動彙整

稽核紀錄 DOCX 匯入 dialog 截圖從缺:該匯入動作僅在 audit_planning(規劃中)階段開放,2026-07-05 盤點 STG 全庫無任何存活專案處於此狀態,暫無真實資料可拍。

狀態呈現:每個儲存動作 per-按鈕 spinner(savingTasks / savingParty);無 socket,儲存後靠前端 state 更新(saveTasks 會依日期重排行程並刷新畫面)。DOCX 匯入成功後整頁重載。方法標籤 i18n key lang.ap_authoring.method_<value>,缺翻譯時顯示原始碼。

6. API 規格

Envelope:成功 {"code": 1, "data": …}、失敗 {"code": 0, "msg": "…"}(見專案根 CLAUDE.md Response Format)。

6.1 總清單(本頁呼叫的全部 endpoint)

分類 Method + Path 說明 完整規格
載入 GET /grc/project/{project_uid} 專案基本資料 + 參與人員(角色判定) GAI-SD-02
載入 GET /audit-round/{round_uid}/ap 本輪 AP 明細(reviewed_controls / tasks / subjects / round_status / import_ssp_uid) §6.2
載入 GET /ssp/{ssp_uid}/control-tree 凍結 SSP 控制樹(行程鉤稽控制項的母體) 見 project-planning §6.2
載入 GET /ssp/{ssp_uid}/parties/components/inventory-items 受評對象候選(人員 / 元件 / 盤點項) GAI-SD-02
載入 GET /system/menu/AUDIT_METHOD 查核方法下拉(EXAMINE / INTERVIEW / TEST…) GAI-SD-02
載入 GET /ap/{ap_uid}/parties 本 AP 稽核人員清單 §6.4
行程 PUT /ap/{ap_uid}/tasks 行程 + 方法 + 鉤稽(全量覆寫;連帶回填受評範圍) §6.3
受評範圍 PUT /ap/{ap_uid}/reviewed-controls 查核控制項(全量覆寫,不可為空) §6.3
受評範圍 PUT /ap/{ap_uid}/assessment-subjects 抽查對象(全量覆寫) §6.3
稽核人員 POST /ap/{ap_uid}/parties 就地新增稽核人員(party) §6.4
匯入 POST /ap/{ap_uid}/docx-imports/parse、GET/POST /ap-docx-import/{parse_uid}/confirm)、DELETE 稽核紀錄 docx 解析 → 預覽 → 確認 GAI-SD-02
生成 POST /audit-round/{round_uid}/ap/generate-draft 從凍結 SSP 重生 AP 草稿(內部 / 重置用) §6.3
階段 GET /project/{project_uid}/audit-round/{round_uid}/stage/info、POST .../stage/advance FlowPhaseBanner 階段資訊 / 推進 見 project-planning §6.5

以下 §6.2~6.4 為核心 endpoint 完整規格(欄位逐條對照 serializer / service 組裝碼,出處標在各段末);標 GAI-SD-02 者屬常規 CRUD,細節見 API 規格書或 Swagger /swagger-ui/

6.2 載入類

[GET] /audit-round/{round_uid}/ap

本輪 AP 明細,頁面據此填三 tab。無 query 參數。Response data

欄位 型別 說明
uid / status string AP 識別 / 生命週期(draft…)
import_ssp_uid string 本 AP 依據的凍結 SSP uid
round_status string 輪次狀態(FE 據此決定 readonly)
reviewed_controls[] array {control_id, included}——查核範圍投影
reviewed_control_count int 查核控制項數
assessment_subjects[] array {uid, subject_type, subject_uuid, title, include, sort_order}
tasks[] array 行程(下表)
created_user / created_at string / datetime 審計欄

Task 節點tasks[] 每項):

欄位 型別 說明
uid string 行程識別
title string 行程標題
type string OSCAL task type(action / milestone)
sort_order int 排序
description string 行程整體備註
timing raw 執行時點(date 字串或 OSCAL timing 物件)
methods[] string[] 查核方法 value
controls[] string[] 這條行程查的 control_id
steps[] array per-方法查核指引,每項 {method, description}
subjects[] array 行程受評對象,每項 {subject_type, subject_uuid, include, title}
participants[] array 行程參與人員,每項 {party_uuid, role_id}

出處:app/grc/service/assessment_plan_app_service.py:268-281(AP detail 組裝)

6.3 AP 寫入類(全量覆寫)

[PUT] /ap/{ap_uid}/tasks

行程 + 方法(全量覆寫:送出的陣列即最終狀態,未列入即刪除)。Request:

欄位 型別 必填 說明
tasks[].title string 行程標題
tasks[].type string OSCAL task type(action(預設)/ milestone
tasks[].timing raw date 字串或 OSCAL timing 物件({on-date:{date}} / {within-date-range} / {at-frequency}
tasks[].methods string[] 查核方法 value(system_menu AUDIT_METHOD)→ 落 activity.props
tasks[].controls string[] 這條行程查的 control_id → activity.related-controls
tasks[].subjects array {subject_type(OneOf: component/inventory-item/location/party/user), subject_uuid, include(預設 true), title}ap_task_subjects
tasks[].participants array {party_uuid(必填), role_id(預設 assessor)}ap_task_participants
tasks[].steps array {method(必填), description}——每方法的查核指引 → activity.steps
tasks[].description string 行程整體備註

副作用_backfill_scope_from_tasksassessment_plan_app_service.py:564-589)把所有行程的 controls / subjects 聯集回填 ap_reviewed_controls / ap_assessment_subjects——故受評控制項 / 對象兩 tab 是行程的投影,不需另存。Response:完整 AP detail(同 §6.2)。出處:serializer SetTasksRequestapi/project/serializers/audit_round.py:91-96,其行項 schema _TaskItem 在 :65-88)、service set_tasksassessment_plan_app_service.py:501-562

[PUT] /ap/{ap_uid}/reviewed-controls

查核控制項全量覆寫。Request(control_ids required、不可為空,為空回 GRC_AP_REVIEWED_CONTROLS_REQUIRED):

{ "control_ids": ["string"] }

Response:

{ "ap_uid": "string", "reviewed_control_count": 0 }

出處:serializer SetReviewedControlsRequestaudit_round.py:19-24)、service set_reviewed_controlsassessment_plan_app_service.py:464-474

[PUT] /ap/{ap_uid}/assessment-subjects

抽查對象全量覆寫。Request:

{
  "subjects": [
    {
      "subject_type": "string",  // OneOf: component / inventory-item / location / party / user
      "subject_uuid": "string",
      "include": true,
      "title": "string"
    }
  ]
}

Response:

{ "ap_uid": "string", "subjects": [] }

出處:serializer SetAssessmentSubjectsRequestaudit_round.py:27-46)、service set_assessment_subjectsassessment_plan_app_service.py:476-499

[POST] /audit-round/{round_uid}/ap/generate-draft

從輪次凍結 SSP 重生 AP 草稿(clone Profile 控制項成 AP tasks/AO)。無 body。前置:輪次有凍結 ssp_id、非 close-out 輪。Response:完整 AP detail。出處:assessment_plan_app_service.py:591-613

6.4 稽核人員類

[GET] /ap/{ap_uid}/parties

Response data(陣列,每項):

欄位 型別 說明
uid string party 識別
name string 姓名
type string person / organization
role string OSCAL responsible-role
email_address string email
telephone_number string 聯絡電話

出處:assessment_plan_app_service.py:633-641

[POST] /ap/{ap_uid}/parties

就地新增稽核人員。Request:

欄位 型別 必填 說明
name string 姓名(同名擋下 GRC_AP_PARTY_NAME_EXISTED
party_type string person(預設)/ organization
role string OSCAL responsible-role(預設 assessor
email_address string email
telephone_number string 聯絡電話

Response:

欄位 型別 說明
uid string party 識別
name string 姓名
type string person / organization
role string OSCAL responsible-role
email_address string email
telephone_number string 聯絡電話

出處:serializer ApPartyAddRequestaudit_round.py:99-110)、service add_ap_partyassessment_plan_app_service.py:643-672

7. 前端檔案地圖(compliance-manager-fe/)

檔案 角色
src/views/project/RoundApAuthoringView.vue 主檢視(約 1,000 行;三 tab、行程卡、鉤稽多選、稽核人員 dialog、匯入觸發)
src/config/router/index.js:818-823 route project-ap-authoring-round(path /round/:roundUid/ap-authoring
src/components/grc/FlowPhaseBanner.vue audit_planning 階段推進橫幅(stage info / advance)
src/components/grc/ap-docx-import/ApDocxImportDialog.vue 稽核紀錄 docx 匯入三步精靈
src/service/SspService.js SSP 候選對象(parties / components / inventory-items)
src/service/BaseService.js axios 封裝(AP tasks / reviewed-controls / subjects / parties 直接呼叫)
src/config/api/api.js 端點常數(AUDIT_ROUND / AP_AUTHORING / SSP_CONTROL_TREE / SYSTEM_MENU …)
src/composables/useStageInfo.js FlowPhaseBanner 的階段資訊 / 前置查詢
src/config/locales/i18n/{zh-tw,en}/ap-authoring.jsonap-docx-import.json 頁面 i18n(含 AUDIT_METHOD 標籤 method_*

8. 後端檔案地圖(本頁核心鏈路)

鏈路 Route App Service 底層
AP 明細 api/project/routes/audit_round_route.py::AuditRoundApRoute(GET /audit-round/{uid}/ap assessment_plan_app_service.py::get_ap_for_round_ap_detail AP / reviewed-controls / tasks / activities / task-subjects / task-participants 各 repo(jedi_oscal_v2)組裝
行程寫入 同檔 ApTasksRoute set_tasks(建 activity + task + link 表,_backfill_scope_from_tasks 回填範圍) AssessmentPlanService.set_tasks(v2)+ ap_task_subjects / ap_task_participants repo
受評範圍 同檔 ApReviewedControlsRoute / ApAssessmentSubjectsRoute set_reviewed_controls / set_assessment_subjects(皆 resolve_ap_and_check_auditor + assert_round_phase v2 AP repo
稽核人員 同檔 ApPartiesRoute list_ap_parties / add_ap_party(掛 AP metadata_id,同名檢查) PartyRepoImploscal.parties,scope=metadata_id)
生成草稿 同檔 AuditRoundApGenerateDraftRoute generate_draft_for_roundcreate_draft_for_snapshot(v2) 凍結 SSP → AP clone
階段推進 api/flow_engine/routes/stage_advance_route.py app/flow_engine/service/stage_advance_service.py + oscal_stage_handlers.py::SubmitApOnCompleteHandler 前置 ApReviewedControlsSetCheckoscal_stage_preconditions.py:40-62)→ start_auditing

DDD 提醒:AP 寫入 API 都照層級走(route 不碰 DB、app service @transactionresolve_ap_and_check_auditor + assert_round_phase 雙檢查)——這是本群 phase-guard 的正確樣板,新增本群寫入 API 照抄。

9. DB

9.1 資料表總清單(本頁讀寫的全部表)

讀/寫 說明 欄位詳述
compliance.project_audit_rounds 稽核輪次主檔(狀態 / assessment_plan_id / 凍結 ssp_id 見 project-planning §9.3
compliance.projects / project_participants 專案主檔 / 參與者角色(權限判定) GAI-SD-03
oscal.system_security_plans 凍結 SSP 快照(控制項 / 資產母體) GAI-SD-03
oscal.assessment_plans AP 文件 root(import_ssp_id → 凍結 SSP) §9.3
oscal.ap_reviewed_controls 查核範圍(1:1,行程回填) §9.3
oscal.ap_tasks 行程(timing / type,全量覆寫) §9.3
oscal.ap_assessment_activities 查核方法(activity:props method / steps / related-controls) §9.3
oscal.ap_assessment_subjects 抽查對象(AP 層彙整) §9.3
oscal.ap_task_subjects 行程層受評對象 link(FR-040) §9.3
oscal.ap_task_participants 行程層參與人員 link(FR-040) §9.3
oscal.metadata 讀/寫 AP 文件 metadata(parties 掛此下) GAI-SD-03
oscal.parties 稽核人員(就地新增;掛 AP metadata_id) §9.3

9.2 ER 圖(本頁讀寫範圍)

ap_er cluster_ap AP(本頁寫入) cluster_meta metadata ssp oscal.system_security_plans 凍結 SSP 快照(唯讀母體) ap oscal.assessment_plans AP root(import_ssp_id) ap->ssp import_ssp_id rc oscal.ap_reviewed_controls 查核範圍(1:1,行程回填) ap->rc 1:1 task oscal.ap_tasks 行程(timing / type) ap->task 1:N act oscal.ap_assessment_activities 查核方法(props method / steps) ap->act 1:N subj oscal.ap_assessment_subjects 抽查對象(AP 層彙整) ap->subj 1:N meta oscal.metadata AP 文件 metadata ap->meta metadata_id task->act associated_activities tsub oscal.ap_task_subjects 行程層對象 link task->tsub task_id tpar oscal.ap_task_participants 行程層人員 link task->tpar task_id tsub->ssp subject_uuid(軟參照) party oscal.parties 稽核人員(就地新增) tpar->party party_uuid(軟參照) meta->party 1:N
AP 寫入鏈:AP root(黃)承接凍結 SSP;行程(task)掛方法 activity + 對象 / 人員 link 表;受評範圍由行程回填

9.3 核心表欄位(取自 DEV DB dump,含 DB comment)

oscal.assessment_plans — AP 文件 root

一份 AP = 「要評估哪份 SSP、評估哪些控制項、用什麼活動與排程」。GRC 專案啟動時即建立並從 Profile clone 控制項。required=[uuid, metadata, import-ssp, reviewed-controls]。

欄位 型別 說明
uuid uuid OSCAL AP 文件識別碼
metadata_id bigint → oscal.metadata(title / version;parties 掛此下)
status varchar(20) 產品生命週期(draft/published/deprecated)
import_ssp_id bigint → oscal.system_security_plans(凍結 SSP,NOT NULL;ERD 實線 anchor)
local_definitions_remarks text local-definitions 補充;objectives → ap_local_objectives、activities → ap_assessment_activities

oscal.ap_reviewed_controls — 查核範圍(1:1)

assessment-plan.reviewed-controls(AP root required):本次評估涵蓋哪些控制項。對 AP root 是 required [1]——建 AP 時必同 transaction 建一筆(應用層保證)。

欄位 型別 說明
assessment_plan_id bigint 1:1 → assessment_plans
control_selections jsonb [必填] select-control-by-id[](include/exclude/matching)
control_objective_selections jsonb 評估目標選取

oscal.ap_tasks — 行程(task)

assessment-plan.tasks[]:評估工作項。required=[title, type, uuid]。

欄位 型別 說明
uuid uuid 自身身分
parent_id bigint|null self-FK;NULL=頂層 task
type varchar(40) milestone / action(開放 token)
title / description text 標題 / 備註
timing jsonb 執行時點({on-date{date}} / {within-date-range} / {at-frequency} 三選一)
associated_activities jsonb 關聯 activity(method 的載體,{activity-uuid}
subjects jsonb OSCAL 兜底;真相在 ap_task_subjects link 表

oscal.ap_assessment_activities — 查核方法(activity)

local-definitions.activities:可重用評估活動。查核方法存 props{name:method, value:EXAMINE…})、per-方法指引存 steps、鉤稽控制項存 related_controls。required=[description, uuid]。

欄位 型別 說明
uuid uuid 自身身分(task.associated_activities 指向)
description text [必填]
steps jsonb 評估步驟(per-方法查核指引)
related_controls jsonb reviewed-controls 子集
props jsonb 查核方法(method name/value)

oscal.ap_assessment_subjects / ap_task_subjects — 抽查對象

ap_assessment_subjects(AP 層彙整)與 ap_task_subjects(FR-040 行程層 link,task.subjects[] 的 SoT)欄位對稱:

欄位 型別 說明
subject_type varchar(40) component / inventory-item / location / party / user
subject_uuid uuid 指向 SSP 快照物件的 uuid(軟參照不建 FK)
include boolean 是否納入查核(抽查 = 只勾其中幾筆)
title text 顯示用快照名(建立時帶入)

oscal.ap_task_participants — 行程參與人員(FR-040)

task.responsible-roles[] 的 SoT。

欄位 型別 說明
task_id bigint → ap_tasks
party_uuid uuid 參與人員 party uuid(軟參照 oscal.parties)
role_id varchar(40) OSCAL role-id(assessor…)

oscal.parties — 稽核人員

metadata.parties[]:以 metadata_id 掛在 AP 文件下。就地新增的稽核人員即寫此表。

欄位 型別 說明
metadata_id bigint → oscal.metadata(AP 的 metadata)
uuid uuid party 全域識別碼(task participants 軟參照)
type varchar(20) person / organization
name text 正式全名
email_addresses text[] email 清單(純量陣列,非 JSONB)
telephone_numbers jsonb 電話清單

10. 頁面邏輯與資料對應

載入時序

ap_load_sequence cluster_par 並行(apUid / sspUid 到手後) mounted onMounted proj GET /grc/project/{id} 專案+參與人員 → 角色判定 mounted->proj ap GET /audit-round/{roundUid}/ap → apUid / sspUid / roundStatus / tasks proj->ap tree GET /ssp/{sspUid}/control-tree 行程鉤稽控制項母體 ap->tree sspUid cand GET /ssp/{sspUid}/parties・components・inventory-items 受評對象候選 ap->cand meth GET /system/menu/AUDIT_METHOD 查核方法下拉 ap->meth aparty GET /ap/{apUid}/parties 稽核人員 ap->aparty apUid ready 頁面就緒(三 tab 填妥) tree->ready cand->ready meth->ready aparty->ready
頁面載入時序:先 project + AP(拿 apUid/sspUid),再並行載樹 / 候選對象 / 方法 / 稽核人員

關鍵欄位對應(畫面 ↔︎ API):

畫面元素 FE state API 欄位
行程卡陣列 tasks[]{title, type, date, methods, controls, subjects, participants, stepGuidance, description} AP tasks[];date ↔︎ timing.on-date.date(帶時區)
per-方法查核指引 tk.stepGuidance[method] task.steps [{method, description}]
受評控制項彙整(Tab1) derivedControlIds(computed,行程 controls 聯集去重) 回填後 = reviewed_controls
受評對象彙整(Tab2) derivedSubjectGroups(computed,按類型分組) 回填後 = assessment_subjects
稽核人員下拉 apParties[] GET /ap/{apUid}/parties
Banner stage/info 回傳 precondition.passed(reviewed-controls 非空)

儲存流程:「儲存行程」= 單一 PUT /ap/{apUid}/tasks 全量覆寫,BE 連帶回填受評範圍;saveTasks 後前端依日期重排行程並更新 state(不重拉)。受評控制項 / 對象兩 tab 是 computed 投影,切過去才看到最新彙整(無自動刷新)。就地新增稽核人員:POST 後 fetchApParties() 重載並自動選入觸發行程。

錯誤對應:BE error envelope → toast;常見碼 GRC_NOT_AUDITOR(403,非稽核員)、GRC_ROUND_PHASE_READONLY(412,過階段)、GRC_AP_REVIEWED_CONTROLS_REQUIRED(412,查核控制項空)、GRC_AP_PARTY_NAME_EXISTED(409,稽核人員同名)。

11. 背景行為與外部依賴

類型 內容
通知 本頁 AP 編輯站內通知(通知在啟動任務執行與階段推進時)
BPMN audit_planning 階段推進(「啟動稽核」)= BPMN 引擎前進;前置 handler SubmitApOnCompleteHandlerstart_auditing;handler 副作用先於 BPMN 推進、同 transaction 失敗全 rollback
Socket 本頁 socket / 輪詢;儲存後靠 state 更新與手動重載
jedi-* 套件 jedi-oscal-v2(AP repo / AssessmentPlanService)、jedi-flow-engine(stage advance)、jedi-auth(JWT)
系統參數 查核方法下拉來自 system_menuAUDIT_METHOD;無 feature flag

12. 邊界情況與已知坑

  1. phase guard BE 硬擋:AP 寫入只在 audit_planning,過階段直接打 API 也回 412(與專案規劃頁 SSP 寫入可繞過不同,見 _overview §5 坑 2
  2. 行程是唯一編輯面,受評範圍是投影:受評控制項 / 對象兩 tab 唯讀,由 _backfill_scope_from_tasks 從行程聯集回填;想加控制項要去行程勾,不是在 Tab1 加
  3. 全量覆寫語意PUT /ap/{apUid}/tasks 送出的陣列即最終狀態——漏帶某行程 = 刪除該行程;reviewed-controls 送 [] 會被 412 擋(不可為空)
  4. timing 正規化:FE 單一 Calendar → 一律送 {on-date:{date: ISO 帶時區}};BE 可能回 within-date-range,FE 讀取時取第一個可用字串(RoundApAuthoringView.vue:490-509)
  5. subject registry 是記憶體 map:行程對象標籤靠 subjectRegistry 快取,候選未預載時可能顯示 UUID 而非名稱
  6. 就地新增稽核人員 vs SSP parties:AP parties(稽核人員,可就地新增)與 SSP parties(受評方人員,唯讀)是兩組;FE 無交叉使用防呆,BE 靠 metadata_id scope 分離
  7. 方法 i18n 缺翻譯顯示原始碼:system_menu 新增方法時 FE 直接顯示,但 lang.ap_authoring.method_<value> 缺 key 前顯示原始 value(如 EXAMINE
  8. engagement 共用 AP:覆核輪(close-out)不另開 AP,launch-reverifyclone_ap_for_round(party uuid 依 name remap);generate-draft 對 close-out 輪擋下
  9. date-only 顯示 08:00 陷阱:行程時間欄位若吐 YYYY-MM-DDTHH:MM:00 無 tz,前端顯示留意(見 專案管理群 _overview 慣例)

13. 開發與驗證

  • 跑起來:BE python main_socketio.py(port 8000,log 在 log/app.log);FE 在 compliance-manager-fe/ 起 Vite dev server。BE 改 service code 後必須重啟(無 hot reload)
  • 測試帳號:dev 環境稽核員角色帳號(密碼見 .env / 部署文件)
  • 導航路徑:登入 → 專案 → 進入 audit_planning 狀態的輪次 → 稽核計畫填寫
  • 前置資料:輪次需已 launch-audit(有凍結 SSP + AP);沒有 AP 先 generate-draft 或走 launch-audit 自動生成
  • E2Ecompliance-manager-test/ repo(Cucumber + Playwright);以 稽核計畫 / ap-authoring 搜尋
  • 相關文件:FR-038(v2 遷移,docs/features/FR-038-2606-oscal-redesign/)、FR-040(行程鉤稽模型);稽核執行頁見 稽核執行頁 / AR 判定;DB / API 全量見 GAI-SD-02/03