跳轉到

稽核輪次清單(Audit Round List)

功能群:專案管理|狀態機與共用概念先讀 功能群總覽

事實基準:2026-07-05 從 FE / BE code 掃出(FR-038 v2 round-based 架構);endpoint 對照 routes.json,round DTO 對照 audit_round_app_service._to_dto,表結構取自 db_schema.json

變更紀錄

日期 FR 內容
2026-08-29 CM-1421 發起覆核時清掉受重查控制項的審閱勾勾:覆核輪開起來仍帶著母輪已審閱的勾勾,覆核者看到「已打勾」可能直接跳過而沒真的重審。根因不是漏寫程式,是審閱勾勾本來就不分輪次compliance.review_marks 無 round 維度,且開新輪只凍結 SSP 快照、控制項 catalog 不複製,母輪與覆核輪讀到同一筆控制項,勾勾直接穿透)。採最小改不動 schemalaunch_reverify 在算出收斂範圍後、建新輪之前,清掉要重查的那幾條控制項(含其 AO)的審閱標記;不重查的控制項勾勾保留。清除失敗不擋建輪(best-effort)
2026-07-17 site-regression 實測回饋 §3/§12 訂正發起覆核 FE/BE 落差:舊稱「manager 代行慣例下一致」不準確,實為 FE 顯示條件(isProjectManager)比 BE 授權(auditor-or-manager)更嚴格,純 auditor 看不到 UI 入口

1. 功能描述

稽核輪次清單是單一專案下所有稽核輪次的列表:從專案列表點專案名稱進來,看到該專案的每一輪(首次 / 持續監督 / 覆核),每列顯示輪次序號、名稱、類型、七態狀態、建立者與時間;點整列進入該輪的總覽頁。頁面也承載「發起覆核」這個輪次生命週期動作。

「一專案一稽核」模型下,手動新增輪次入口目前以功能旗標隱藏ALLOW_MANUAL_ROUND_CREATE=false)——全新一輪稽核採「開新專案」而非在此加輪次;BE 建輪次端點仍在(見 §12)。

主要使用者:專案 manager(發起覆核)、所有參與者(檢視 / 進入輪次)。

角色速覽(完整定義與 manager 全域代行慣例見 _overview §3):

角色 一句話
manager 專案經理 — 可發起覆核;(新增輪次入口目前隱藏)
auditor 稽核員 — 覆核的 BE 授權角色(manager 可代行)
參與者 檢視清單、點入輪次

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

# 功能 說明 位置 詳述
1 輪次清單 該專案全部輪次,無分頁(一專案輪次數少) 資料表 UC-PAL-01
2 輪次血緣 close-out 子輪名稱下標「↩ 覆核自第 M 輪」 名稱欄 §6.2
3 七態狀態徽章 planning→…→closed,色階對應 狀態欄 _overview §2
4 建立者 / 時間 created_user_name(nickname)/ created_at 建立者欄 §6.2
5 點列進總覽 整列可點 → 該輪總覽頁(round-scoped) 全列 UC-PAL-01
6 發起覆核 pending_reverify 輪且為 PM、未發起過 → 開 close-out 子輪 操作欄 UC-PAL-02
7 已發起覆核標示 已有子輪指向本輪 → 唯讀「已發起覆核」,防重複 操作欄 UC-PAL-02
8 新增輪次 dialog 名稱 / 類型 / 開始日期——預設隱藏(功能旗標關) Header 按鈕(隱藏) §12
9 空狀態 無輪次時資料夾 icon + 提示 資料表 §5

UC(§2)展開 1 / 5(瀏覽並進總覽)與 6(發起覆核)兩條核心路徑;狀態機轉換的完整語意在 _overview §2

2. Use Case

角色速覽(完整定義與 manager 全域代行慣例見 _overview §3):

角色 一句話
manager 專案經理 — 可發起覆核;(新增輪次入口目前隱藏)
auditor 稽核員 — 覆核的 BE 授權角色(manager 可代行)
參與者 檢視清單、點入輪次

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

UC-PAL-01 瀏覽輪次並進入總覽

項目 內容
角色 任一參與者
前置條件 已登入;為專案參與者
產出 / 後置條件 載入該專案全部輪次;點列導向該輪總覽頁

UC-PAL-01 載入:並行取專案詳情與輪次清單,依 status/角色決定操作欄,點列進總覽

UC-PAL-02 發起覆核(launch-reverify)

項目 內容
角色 專案 manager(BE 授權 auditor / manager 代行)
前置條件 母輪 status = pending_reverify;尚未有 close-out 子輪指向它
產出 / 後置條件 複製母輪 AP 成獨立副本、開新 close-out 子輪(status=planning)、綁主工作流;_sync_project_status清除受重查控制項與其 AO 的審閱標記(CM-1421,見 §4.1

UC-PAL-02 流程:FE gate(pending_reverify+PM+未發起)→確認→POST launch-reverify→BE 三重前置(角色/母輪狀態/未重複)→建子輪

3. 權限矩陣

專案層角色基調見 _overview §3。本頁的實際檢查點:

操作 FE 判定 BE 強制 BE 檢查位置
讀清單 參與者即可 專案可見性(GET /grc/project 帶 participants) app/grc/service/project_service.py::get_project
發起覆核 status==='pending_reverify' && isProjectManager && !reverifyLaunched _check_role(auditor,manager 代行)+母輪 pending_reverify +未重複 app/grc/service/audit_round_app_service.py::launch_reverify
新增輪次 isProjectManager && ALLOW_MANUAL_ROUND_CREATE(旗標關 → 隱藏) _check_role(manager)+ round_type ∈ 同檔 create_round(端點仍在)

⚠️ 實測訂正(2026-07-17):舊描述「manager 代行慣例下一致」不準確——BE _check_rolerequired_role="auditor"、manager 只是 override 角色(見 _overview §3 的「manager 全域 override 慣例」:manager 補位讓 auditor 做不到的事也能做,auditor 才是這個動作原本設計授權的角色)。但 ProjectApListView.vue:285 的按鈕顯示條件是 isProjectManager純 auditor(未兼任 manager)完全看不到「發起覆核」按鈕,即使 BE 明確授權 auditor 可觸發此動作。實務上發起覆核目前只有 manager 能從 UI 操作,auditor 若要用只能繞過 FE 直接打 API。這是 FE 顯示條件比 BE 授權更嚴格的落差,非「一致」,收進 §12 坑 8。

4. 狀態機與前置條件

輪次 7 態狀態機見 _overview §2。本頁相關:

條件 效果 出處
任一輪 status != 'closed' hasActiveRound=true:新增輪次鈕(若啟用)禁用 ProjectApListView.vue:41
status === 'pending_reverify' + 未發起 顯示「發起覆核」鈕 同檔 :285
已有子輪 parent_round_uid === 本輪 uid 顯示唯讀「已發起覆核」 reverifyLaunched(:112)
launch-reverify 前置 母輪 pending_reverify(否則 GRC_412037)、未重複(否則 GRC_412041 audit_round_app_service.py

4.1 覆核輪的審閱標記重置(CM-1421)

問題:覆核輪的語意是「上一輪查出不合格的那幾條重新再查」,但控制項打開時仍掛著母輪審閱者打的勾——覆核者看到已打勾可能直接跳過,等於沒重審。

根因不是漏寫程式,而是審閱勾勾本來就不分輪次

  • compliance.review_marks 的欄位只有「哪個專案、哪個控制項、哪個 AO、誰、何時」,沒有輪次維度。FR-014 的規格書明寫「審閱紀錄為整個專案共用」,寫入與讀取兩端也都沒有按輪次過濾——是刻意設計不是遺漏。
  • 關鍵:開新輪時系統只凍結一份 SSP 快照,控制項 catalog 不會跟著複製。故同一專案裡母輪與覆核輪看到的控制項在 DB 是同一筆,勾勾記在那筆控制項上就直接「穿透」到覆核輪。

方案取捨(採最小改,不動 schema)

A. 最小改(採用 B. 加輪次維度
做法 發起覆核時清掉要重查那幾條的勾勾 review_marksround_id,改輪次層語意
動 schema
影響面 只有發起覆核這一條路徑 寫入 / 讀取 / 計數(reviewed_controls_count)四處都要改
既有資料 不必處理 要 backfill 舊勾勾屬於哪一輪——推導不出來(只有審閱時間,只能猜)
母輪歷史 被重查的那幾條勾勾會消失 保留

選 A 的理由:B 等於把「專案層」語意整個改成「輪次層」,成本高很多,而換來的「保留母輪歷史」價值有限——母輪已結案,回看時那條控制項在覆核輪已重查過,舊勾勾反而誤導。真正該留的歷史在 AR findings 與 POA&M 裡,不在審閱勾勾。且 A 與同函式內既有做法一致(收斂 AP 受評範圍、產生覆核輪任務,都用同一份「要重查的控制項清單」當尺)。選 A 不擋死 B——日後真需要輪次層審閱仍可另案評估。

兩個刻意的行為

  1. 只清「要重查的那幾條」,不是全部清光——母輪已判定合格、這輪不重查的控制項,勾勾保留(與收斂受評範圍、產生任務用同一把尺)。控制項底下的 AO 勾勾一起清(控制項要重查,其 AO 自然失效)。
  2. 清不成不擋建輪(best-effort)——依賴沒注入/要重查的清單為空(母輪全合格卻發起覆核的異常情況,此時清了等於全專案歸零)/控制項清單解析斷鏈 → 記 warning log 跳過,不讓建輪失敗。與同函式內產生任務、承襲母輪設定的防禦姿態一致。

既有資料未回溯處理:已發起過覆核的輪次(如 189 上那筆)殘留的舊勾勾不會自動清掉,本次只改行為不補資料。

5. UI 設計

版面骨架與區塊職責(該專案全部輪次的單表清單,pending_reverify 才顯發起覆核操作;彩色區塊可點跳本頁小節錨點,API 僅標代表性):

稽核輪次清單版面 wireframe:Header 返回+(隱藏)新增輪次、六欄輪次表(含子輪血緣/七態徽章)、pending_reverify 發起覆核操作欄、空狀態

實機截圖(STG,亮色模式,2026-07-05,帳號 blsadmin):

輪次清單:單輪次列(輪次/輪次名稱/輪次類型/狀態/建立者/建立時間)

pending_reverify 列「發起覆核」按鈕截圖從缺:2026-07-05 盤點 STG 全庫,目前無任何存活專案的輪次處於 pending_reverify 狀態(也沒有 close-out 血緣的多輪次專案),暫無真實資料可拍;待有此狀態資料再補。

狀態呈現:狀態欄 PrimeVue TagProjectApListView.vue:87ROUND_STATUS_SEVERITY 對應 6 態planning=secondary / audit_planning=info / auditing=warning / remediation=danger / pending_reverify=help / closed=success)。DB project_audit_rounds.status 另有第 7 態 not_started(新建未啟動)——本頁 severity map 未涵蓋(輪次一律從 planning 起,not_started 不出現在此清單;若真出現會以 Tag 預設樣式呈現)。整列 cursor-pointer row-click;無分頁、無 socket。

6. API 規格

Envelope:成功 {"status": true, "data": …}(輪次清單 data.data 為陣列)、失敗 {"error_code": "…", "msg": "…"}

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

分類 Method + Path 說明 完整規格
載入 GET /grc/project/{uid} 專案詳情(名稱 + participants → isProjectManager) GAI-SD-02
載入 POST /projects/{project_uid}/audit-rounds/list 該專案全部輪次 §6.2
生命週期 POST /audit-round/{round_uid}/launch-reverify 發起覆核(開 close-out 子輪) §6.2
(隱藏) POST /projects/{project_uid}/audit-rounds 新增輪次(UI 旗標關;端點仍在) §6.2

6.2 核心 endpoint

[POST] /projects/{project_uid}/audit-rounds/list

無 body。Response data(陣列,每項 = _to_dto):

欄位 型別 說明
uid string 輪次 UID
name string 輪次名稱
round_no int 輪次序號
round_type string initialsurveillanceclose-out
status string 7 態
parent_round_uid string close-out 母輪,血緣用
assessment_plan_id int
ap_uid string
ssp_id int
ssp_uid string
ar_result_id int
poam_id int
flow_template_snapshot_uid string
workflow_execution_uid string
start_at date
end_at date
created_at date
created_user string login_name
created_user_name string nickname

出處:app/grc/service/audit_round_app_service.py:164-191

[POST] /audit-round/{round_uid}/launch-reverify

無 body。開 close-out 子輪。前置:角色 auditor / manager(否則 GRC_403001)、母輪 pending_reverify(否則 GRC_412037)、未重複發起(否則 GRC_412041)。副作用:複製母輪 AP 成獨立副本、建子輪(status=planning、parent_round_id=母輪、round_type=close-out)、綁主工作流、_sync_project_status。出處:app/grc/service/audit_round_app_service.py::launch_reverify

[POST] /projects/{project_uid}/audit-rounds(目前 UI 隱藏)

Request CreateAuditRoundRequest

欄位 型別 必填 說明
name string
round_type string initialsurveillance
start_at date %Y-%m-%d

前置:角色 manager(否則 GRC_403002)、round_type 合法(否則 GRC_412033,覆核請走 launch-reverify)。出處:api/project/serializers/audit_round.py:5-15audit_round_app_service.py::create_round

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

檔案 角色
src/views/project/ProjectApListView.vue 主檢視(輪次表、狀態色階、發起覆核、建輪次 dialog、旗標 ALLOW_MANUAL_ROUND_CREATE
src/config/router/index.js:766-770 route project-ap-list/project/projects/:id
src/service/BaseService.js HTTP 包裝
src/config/api/api.js endpoint 常數(GRC_PROJECT / AUDIT_ROUNDS / AUDIT_ROUND
src/config/locales/i18n/zh-tw/project-ap-list.json 頁面 i18n

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

鏈路 Route App Service 底層
輪次清單 api/project/routes/audit_round_route.py::AuditRoundsListRoute app/grc/service/audit_round_app_service.py::list_rounds_to_dto compliance.project_audit_rounds
專案詳情 api/grc/routes/project_route.py::ProjectDetailResource.get project_service.get_project jedi_project + participant service
發起覆核 api/project/routes/audit_round_route.py::AuditRoundLaunchReverifyRoute audit_round_app_service.launch_reverify_check_role + 前置) AP clone + 新輪 + workflow bind + _sync_project_status
建輪次 api/project/routes/audit_round_route.py::AuditRoundsRoute audit_round_app_service.create_round 同上(第一輪由專案成立時建,見 專案建立

DDD 提醒:輪次寫入照 _check_role + 狀態前置;狀態轉換一律走 API(BPMN 驅動,不直接 UPDATE,見 _overview §5)。

9. DB

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

讀/寫 說明 欄位詳述
compliance.project_audit_rounds 讀 / (發起覆核建子輪) 輪次主檔(清單主體、血緣、狀態) §9.3
compliance.projects / project_participants 專案主檔 / 角色(isProjectManager) GAI-SD-03
public.users created_user(login_name)→ nickname enrich GAI-SD-03
oscal.assessment_plans 讀 / (覆核複製 AP) 覆核時複製母輪 AP 成獨立副本 GAI-SD-03
oscal.system_security_plans 輪次凍結 SSP 指標(DTO 帶出,不在本頁展開) GAI-SD-03

9.2 ER 圖(本頁讀寫範圍)

稽核輪次清單讀寫範圍:黃 = 輪次主檔(self-FK 血緣)、藍 = 專案 / 角色、虛線 = DTO 帶出的凍結指標

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

compliance.project_audit_rounds — 稽核輪次主檔

清單主體。欄位(uid / round_no / name / round_type / status / parent_round_id(close-out self-FK 血緣)/ assessment_plan_id / ssp_id(凍結快照)/ ar_result_id / poam_id / flow_template_snapshot_uid / workflow_execution_uid / created_user / created_at)與完整說明見 規劃頁 §9.3。本頁除發起覆核建新子輪外只讀。

10. 頁面邏輯與資料對應

載入時序:見 §2 UC-PAL-01(並行取專案詳情與輪次清單)。

關鍵欄位對應

畫面元素 FE state API 欄位
輪次序號 round_no(顯示「第 N 輪」)
名稱 + 血緣 parentLabel(row) name + parent_round_uid(查母輪 round_no)
狀態徽章 ROUND_STATUS_SEVERITY[status] status
建立者 created_user_name(fallback created_user
發起覆核鈕顯示 reverifyLaunched(row) 是否有 parent_round_uid === 本輪 uid 的子輪

儲存流程:發起覆核 / 建輪次成功後 fetchRounds() 重查;無本地快取。

錯誤對應:BE error envelope → toast;覆核 GRC_403001 / GRC_412037 / GRC_412041;建輪次 GRC_403002 / GRC_412033

11. 背景行為與外部依賴

類型 內容
狀態同步 發起覆核建未 closed 子輪 → _sync_project_status 連動專案 status
BPMN 子輪綁主工作流(沿用前輪 flow template snapshot)
Socket 本頁 socket / 輪詢;清單靠操作後重查
jedi-* 套件 jedi-oscal-v2(AP clone)、jedi-flow-engine(workflow bind)、jedi-auth(JWT)
系統參數 ALLOW_MANUAL_ROUND_CREATE(FE 常數,非 BE feature flag)目前 false

12. 邊界情況與已知坑

  1. 手動新增輪次 UI 隱藏但 BE 端點仍活ALLOW_MANUAL_ROUND_CREATE=false 只隱藏 FE 按鈕,POST /projects/{uid}/audit-rounds 仍可被直接呼叫。要硬性禁止需在 BE create_round 加守門(目前只有 manager + round_type 檢查);這是「一專案一稽核」政策的可逆設計,翻旗標即恢復入口
  2. 發起覆核防重複靠 FE + BE 雙層:FE 用 reverifyLaunched(清單內有子輪指向本輪)隱藏按鈕;BE 另有 GRC_412041 擋——繞過 FE 直接打 API 仍會被 BE 擋
  3. 「啟動稽核」不在本頁:launch-audit 已搬到輪次內的 FlowPhaseBanner(規劃 / 總覽頁),列表不放捷徑
  4. 無分頁:一專案輪次數少,DataTable 不分頁;若某專案輪次爆量(異常)會全量渲染
  5. 血緣顯示依賴同批資料parentLabel 靠清單內找得到母輪 round_no;母輪若因故不在回傳集內(理論上不會)則不顯示血緣
  6. 狀態機 7 態但色階圖只列 6 態not_started 未在 ROUND_STATUS_SEVERITY,落預設 secondary;實務上列表輪次多已離開 not_started
  7. date-only 顯示start_at / created_at 走本地 toLocaleDateString,留意時區(見專案共用坑)
  8. 發起覆核 FE 顯示比 BE 授權更嚴格,純 auditor 用不到 UI 入口:BE launch_reverify_check_role 要求 ("auditor", "manager"),auditor 是原本設計的授權角色、manager 是全域代行;但 ProjectApListView.vue:285 按鈕顯示條件只有 isProjectManager,未兼任 manager 的純 auditor 完全看不到此按鈕。實務上發起覆核目前僅 manager 可從 UI 觸發,與 BE 意圖(auditor 也該能做)不符。訂正見 §3。修法:FE 按鈕顯示條件應改為「是該專案 manager 或 auditor」,與 BE 授權對齊。

13. 開發與驗證

  • 跑起來:BE python main_socketio.py(port 8000,log 在 log/app.log);FE 在 compliance-manager-fe/ 起 Vite dev server。BE 改 service code 後必須重啟
  • 測試帳號:dev 環境 blsadmin(密碼見 .env / 部署文件)
  • 導航路徑:專案列表 → 點專案名稱(/project/projects/:id
  • 前置資料:專案需已成立(有 ≥1 輪次);驗發起覆核需一輪推進到 pending_reverify
  • E2Ecompliance-manager-test/ repo;以 稽核輪次 / audit-round / reverify 搜尋
  • 相關文件:單輪入口見 專案總覽頁、覆核狀態機 _overview §2、建立首輪見 專案建立;FR-038 docs/features/FR-038-2606-oscal-redesign/