稽核輪次清單(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 不複製,母輪與覆核輪讀到同一筆控制項,勾勾直接穿透)。採最小改不動 schema:launch_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-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) |
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_role的required_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_marks 加 round_id,改輪次層語意 |
| 動 schema | 否 | 是 |
| 影響面 | 只有發起覆核這一條路徑 | 寫入 / 讀取 / 計數(reviewed_controls_count)四處都要改 |
| 既有資料 | 不必處理 | 要 backfill 舊勾勾屬於哪一輪——推導不出來(只有審閱時間,只能猜) |
| 母輪歷史 | 被重查的那幾條勾勾會消失 | 保留 |
選 A 的理由:B 等於把「專案層」語意整個改成「輪次層」,成本高很多,而換來的「保留母輪歷史」價值有限——母輪已結案,回看時那條控制項在覆核輪已重查過,舊勾勾反而誤導。真正該留的歷史在 AR findings 與 POA&M 裡,不在審閱勾勾。且 A 與同函式內既有做法一致(收斂 AP 受評範圍、產生覆核輪任務,都用同一份「要重查的控制項清單」當尺)。選 A 不擋死 B——日後真需要輪次層審閱仍可另案評估。
兩個刻意的行為:
- 只清「要重查的那幾條」,不是全部清光——母輪已判定合格、這輪不重查的控制項,勾勾保留(與收斂受評範圍、產生任務用同一把尺)。控制項底下的 AO 勾勾一起清(控制項要重查,其 AO 自然失效)。
- 清不成不擋建輪(best-effort)——依賴沒注入/要重查的清單為空(母輪全合格卻發起覆核的異常情況,此時清了等於全專案歸零)/控制項清單解析斷鏈 → 記 warning log 跳過,不讓建輪失敗。與同函式內產生任務、承襲母輪設定的防禦姿態一致。
既有資料未回溯處理:已發起過覆核的輪次(如 189 上那筆)殘留的舊勾勾不會自動清掉,本次只改行為不補資料。
5. UI 設計¶
版面骨架與區塊職責(該專案全部輪次的單表清單,pending_reverify 才顯發起覆核操作;彩色區塊可點跳本頁小節錨點,API 僅標代表性):
實機截圖(STG,亮色模式,2026-07-05,帳號 blsadmin):

pending_reverify列「發起覆核」按鈕截圖從缺:2026-07-05 盤點 STG 全庫,目前無任何存活專案的輪次處於pending_reverify狀態(也沒有 close-out 血緣的多輪次專案),暫無真實資料可拍;待有此狀態資料再補。
狀態呈現:狀態欄 PrimeVue Tag,ProjectApListView.vue:87 的 ROUND_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 | initial/surveillance/close-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 | 否 | initial|surveillance |
start_at |
date | 否 | %Y-%m-%d |
前置:角色 manager(否則 GRC_403002)、round_type 合法(否則 GRC_412033,覆核請走 launch-reverify)。出處:api/project/serializers/audit_round.py:5-15、audit_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 圖(本頁讀寫範圍)¶
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. 邊界情況與已知坑¶
- 手動新增輪次 UI 隱藏但 BE 端點仍活:
ALLOW_MANUAL_ROUND_CREATE=false只隱藏 FE 按鈕,POST /projects/{uid}/audit-rounds仍可被直接呼叫。要硬性禁止需在 BEcreate_round加守門(目前只有 manager + round_type 檢查);這是「一專案一稽核」政策的可逆設計,翻旗標即恢復入口 - 發起覆核防重複靠 FE + BE 雙層:FE 用
reverifyLaunched(清單內有子輪指向本輪)隱藏按鈕;BE 另有GRC_412041擋——繞過 FE 直接打 API 仍會被 BE 擋 - 「啟動稽核」不在本頁:launch-audit 已搬到輪次內的 FlowPhaseBanner(規劃 / 總覽頁),列表不放捷徑
- 無分頁:一專案輪次數少,DataTable 不分頁;若某專案輪次爆量(異常)會全量渲染
- 血緣顯示依賴同批資料:
parentLabel靠清單內找得到母輪 round_no;母輪若因故不在回傳集內(理論上不會)則不顯示血緣 - 狀態機 7 態但色階圖只列 6 態:
not_started未在ROUND_STATUS_SEVERITY,落預設 secondary;實務上列表輪次多已離開 not_started - date-only 顯示:
start_at/created_at走本地toLocaleDateString,留意時區(見專案共用坑) - 發起覆核 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 - E2E:
compliance-manager-test/repo;以稽核輪次/audit-round/reverify搜尋 - 相關文件:單輪入口見 專案總覽頁、覆核狀態機 _overview §2、建立首輪見 專案建立;FR-038
docs/features/FR-038-2606-oscal-redesign/