程序書池(Document Pool)
功能群:證據管理|儲存後端 / 兩種證據概念 / 核心資料模型先讀 功能群總覽
事實基準:2026-07-06 從 FE / BE code 掃出;表結構對
db_schema.json、endpoint 對routes.json逐條核
1. 功能描述
程序書池是單一 SSP 專屬的參考文件集中區:受評方把「程序書」(作業辦法、政策文件、佐證資料等)上傳到這個池,之後在專案規劃頁的控制項 / AO 面板就能從池裡挑檔關聯過去(關聯動作見 專案規劃頁 UC-PP-05)。本頁只負責池本體的維護——上傳、瀏覽、預覽、下載、移除;把檔關聯到控制項 / AO 是另一個動作,不在本頁。
這是一個可重用面板(DocumentPoolPanel.vue),掛在兩個地方:專案規劃頁
Tab4「程序書管理」(ssp_uid = 該專案
living SSP),以及資源庫範本編輯頁的 Dialog(ssp_uid = 範本
SSP)。因此「誰能編」隨掛載脈絡的 SSP 而定。
主要使用者:專案 manager(寫)、專案任一參與者(讀)。
角色速覽(完整定義見 _overview):
| 角色 | 一句話 |
|---|---|
| manager | 專案經理 — 唯一能上傳 / 移除池檔者(且需對應稽核計畫仍可編輯) |
| 參與者 | auditor / reviewer / viewer — 可瀏覽、預覽、下載,不能寫 |
1.1 功能總覽(本頁全部功能)
| # | 功能 | 說明 | 位置 | 詳述 |
|---|---|---|---|---|
| 1 | 上傳程序書 | 拖拉或點選多檔(單檔 ≤ 50MB)→ 逐檔上傳取得 file_uid → 批次加入池 | Header「上傳」+ 上傳區 | UC-DP-01 |
| 2 | 檔案列表 | 每列顯示檔名 / 描述 / 大小 / 上傳日期 / 關聯計數 | 主區清單 | §5 |
| 3 | 前端分頁 | 池檔以前端切片分頁(10 / 25 / 50) | 清單底部 Paginator | §10 |
| 4 | 關聯計數徽章 | 「🔗 N」顯示該檔已關聯到幾個控制項 / AO(唯讀顯示,關聯動作在規劃頁 Tab0) | 每列 | UC-PP-05(關聯動作) |
| 5 | 預覽 | iframe 內嵌預覽(PDF / txt) | 每列👁 → Dialog | §6.1 |
| 6 | 下載 | 開新視窗下載原始檔 | 每列⬇ | §6.1 |
| 7 | 移除池檔 | 確認後從池移除(refcount-aware:跨版共享的實體檔不會被誤刪) | 每列🗑 | UC-DP-01 |
| 8 | 空狀態 | 池無檔時顯示資料夾圖示 + 提示 | 主區 | §5 |
UC(§2)展開本頁唯一具實質寫入邏輯的路徑 UC-DP-01(上傳 / 移除池檔);瀏覽 / 分頁 / 預覽 / 下載屬純顯示或常規檔案操作見 §5 / §6.1;把檔關聯到控制項 / AO 是規劃頁的動作,見 專案規劃頁 UC-PP-05,本頁只顯示關聯計數不重複展開。
2. Use Case
角色速覽(同 §1):
| 角色 | 一句話 |
|---|---|
| manager | 專案經理 — 唯一能上傳 / 移除池檔者(且需 AP 仍可編輯) |
| 參與者 | auditor / reviewer / viewer — 可讀不可寫 |
流程圖視覺慣例:菱形 = 判斷、橘底 = 例外 / 擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。
UC-DP-01 上傳程序書到池(與移除)
| 項目 | 內容 |
|---|---|
| 角色 | 專案 manager |
| 前置條件 | 已載入 SSP(sspUid
存在);對應稽核計畫仍可編輯(planning) |
| 產出 / 後置條件 | 檔案落
upload_files;ssp_reference_documents(context_type='ssp')新增池關聯;移除只解池關聯,實體檔僅在無其他版本引用(refcount=0)時才刪 |
3. 權限矩陣
| 操作 | FE 判定 | BE 強制 | BE 檢查位置 |
|---|---|---|---|
| 讀池清單 | 進頁即可 | require_participant(SSP → project → 任一
participant) |
common/middleware/permission/ssp_permission.py(SspPermissionChecker.require_participant) |
| 上傳到池 | 父頁 canEdit(manager)才顯示上傳鈕 |
require_manager = manager 角色 + AP 可編輯 |
app/oscal/service/ssp_document_pool_service.py::add_to_pool(:64) |
| 移除池檔 | 同上 | require_manager |
同檔 delete_from_pool(:118) |
⚠️ 讀寫權限都繞 SSP → project → participant 反查(
SspProjectResolver),與專案層project_participants.role一致;本頁不自帶角色欄位。上傳鈕的顯示是 FE 依父頁canEdit控制,BE 仍獨立強制。
4. 狀態機與前置條件
本頁無輪次狀態機,僅承接對應 SSP 的可編輯 gate(與 專案規劃頁 §4 同源):
| 條件 | 效果 | 出處 |
|---|---|---|
AP status = planning |
池可上傳 / 移除 | ssp_permission.py(_require_ap_editable) |
| AP 已關閉 / 封存 | 寫入擋 GRC_412028(此 SSP
對應稽核計畫已關閉或封存,無法編輯) |
common/code/grc_error_code.py(GRC_SSP_EDIT_AP_NOT_ACTIVE) |
| 範本編輯脈絡(module-frame Dialog) | ssp_uid 為範本 SSP,無 AP gate(非專案 SSP) |
SspPermissionChecker(無 project context 時略過 AP
檢查) |
5. UI 設計
📸 實際畫面截圖待補(一律亮色模式拍攝):以 playwright 對 STG 擷取,預計補以下到
assets/img/:① 池整區(上傳區 + 檔案列表 + 關聯計數徽章)docpool-overview.png② 預覽 Dialogdocpool-preview.png。
版面骨架與區塊職責(單頁;彩色區塊可點跳本頁小節錨點,API 僅標代表性):
狀態呈現:載入時 ProgressSpinner;上傳中
uploading 遮罩;刪除採 per-列
loading(deletingUid);無 socket,上傳 /
刪除後靠 fetchDocuments()
手動重載;分頁純前端切片(documents.slice)。
6. API 規格
Envelope:成功 {"status": true, "data": …}、失敗
{"error_code": "…", "msg": "…"}(HTTP 4xx/5xx)。
6.1 總清單(本頁呼叫的全部 endpoint)
| 分類 | Method + Path | 說明 | 完整規格 |
|---|---|---|---|
| 池 | GET /ssp/{ssp_uid}/document-pool |
列出該 SSP 的池檔(含關聯計數) | §6.2 |
| 池 | POST /ssp/{ssp_uid}/document-pool |
批次把已上傳檔加入池 | §6.2 |
| 池 | DELETE /ssp/{ssp_uid}/document-pool/{doc_uid} |
從池移除一筆(refcount-aware) | §6.2 |
| 檔案 | POST /file/upload |
multipart 上傳,回 file_uid(拿去 POST 池) |
GAI-SD-02 |
| 檔案 | GET /file/download/{uid} |
下載原始檔(開新視窗) | GAI-SD-02 |
| 檔案 | GET /file/pdf-preview/{uid} |
預覽用(iframe src) | GAI-SD-02 |
6.2 核心 endpoint
[POST] /ssp/{ssp_uid}/document-pool
把已上傳的檔批次加入池。Request:
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
| documents | array | 是 | 每項 {file_uid, description?};file_uid
須為 /file/upload 回傳、且存在於
upload_files(否則 GRC_404006) |
Response data:新增的池檔 DTO 陣列(欄位同 GET
每項)。出處:api/oscal/routes/ssp/ssp_document_pool_route.py:51-64、app/oscal/service/ssp_document_pool_service.py::add_to_pool:62-87
[GET] /ssp/{ssp_uid}/document-pool
Response data(陣列,每項):
| 欄位 | 型別 | 說明 |
|---|---|---|
| uid | string | 池關聯 uid(ssp_reference_documents.uid;刪除用) |
| file_uid | string | 實體檔 uid(下載 / 預覽用) |
| file_name / file_size | string / int | 檔名 / 位元組數 |
| description | string|null | 使用者輸入描述 |
| mapping_count | int | 已關聯到幾個控制項 / AO(來自
ssp_reference_document_mappings) |
| created_at | datetime | 上傳時間 |
出處:ssp_document_pool_service.py::list_pool、查詢
ssp_document_pool_query.py
[DELETE] /ssp/{ssp_uid}/document-pool/{doc_uid}
移除池關聯。refcount-aware:先刪池 row,再查同一
file_id 是否還有其他 ssp_reference_documents
引用(跨版共享)——只有 refcount = 0 才 best-effort 刪
upload_files 實體檔,避免跨版 FK violation。無
body,Response
data:true。出處:ssp_document_pool_service.py::delete_from_pool:104-142
7. 前端檔案地圖(compliance-manager-fe/)
| 檔案 | 角色 |
|---|---|
src/components/grc/DocumentPoolPanel.vue |
本面板(上傳 / 列表 / 預覽 / 下載 / 刪除 / 前端分頁) |
src/views/project/ProjectPlanningView.vue |
掛載點一:規劃頁 Tab4「程序書管理」 |
src/views/module_frame/ModuleFrameTemplateEditView.vue |
掛載點二:範本編輯 Dialog |
src/service/SspService.js |
getDocumentPool / addToDocumentPool /
removeFromDocumentPool |
src/components/grc/DocumentLinkDialog.vue |
從池挑檔關聯到控制項 / AO(動作屬規劃頁,見 UC-PP-05) |
8. 後端檔案地圖(本頁核心鏈路)
| 鏈路 | Route | App Service | 底層 |
|---|---|---|---|
| 池 CRUD | api/oscal/routes/ssp/ssp_document_pool_route.py |
app/oscal/service/ssp_document_pool_service.py(SspDocumentPoolService) |
domain/grc/service/ssp_reference_document_domain_service.py
→
infra/grc/repository/ssp_reference_document_repo_impl.py;查詢
ssp_document_pool_query.py |
| 權限 | — | common/middleware/permission/ssp_permission.py(SspPermissionChecker
+ SspProjectResolver) |
反查 project_participants |
9. DB
9.1 資料表總清單(本頁讀寫的全部表)
| 表 | 讀寫 | 說明 | 詳述 |
|---|---|---|---|
| oscal.ssp_reference_documents | 讀 / 寫 | 池本體(context_type='ssp')+控制項 / AO 參考檔(其他
context_type) |
§9.3 |
| oscal.ssp_reference_document_mappings | 讀 | 程序書 ↔︎ 控制項 / AO 多對多(本頁只讀來算
mapping_count;寫在規劃頁 UC-PP-05) |
§9.3 |
| public.upload_files | 讀 / 寫 | 實體檔元資料(/file/upload 落地;移除時 refcount=0
才刪) |
§9.3 |
9.2 ER 圖(本頁讀寫範圍)
9.3 核心表欄位(取自 DEV DB dump,含 DB comment)
oscal.ssp_reference_documents — SSP 參考程序書文件關聯
| 欄位 | 型別 | 說明 |
|---|---|---|
| uid | varchar(36) | 池關聯唯一識別 |
| context_type | varchar(30) | 'ssp' = 池本體;'control_implementation' /
'objective' = 控制項 / AO 層參考檔 |
| context_id | int | 對應 ssp_id / 控制實作 id / AO id |
| file_id | int|null | → upload_files.id(可 NULL) |
| description | text|null | 使用者輸入描述 |
oscal.ssp_reference_document_mappings — 程序書 ↔︎ 控制項 / AO 多對多
| 欄位 | 型別 | 說明 |
|---|---|---|
| reference_document_id | int | → ssp_reference_documents.id(ON DELETE CASCADE) |
| context_type | varchar(30) | 'control_implementation' /
'objective' |
| context_id | int | 控制實作 id / AO id;唯一鍵
(reference_document_id, context_type, context_id) |
public.upload_files — 上傳檔案元資料
| 欄位 | 型別 | 說明 |
|---|---|---|
| uid | varchar(50) | 檔案唯一識別碼 UUID |
| file_name / file_ext | varchar(255) / varchar(50) | 原始檔名 / 副檔名 |
| size | int | 檔案大小(位元組) |
| path / storage_type / storage_scope | varchar | 儲存路徑 / 類型(LOCAL / MINIO / REMOTE_AGENT)/ 範圍(system / customer) |
| sha256 | varchar(64) | SHA-256 校驗和(防竄改錨點,見 remote-agent 對帳) |
10. 頁面邏輯與資料對應
載入即 fetchDocuments() → GET document-pool → 存
documents ref → 前端 slice 分頁。
| 畫面元素 | FE state | API 欄位 |
|---|---|---|
| 檔名 / 描述 / 大小 / 日期 | documents[] |
file_name / description /
file_size / created_at |
| 關聯計數徽章 | documents[].mapping_count |
mapping_count |
| 分頁切片 | paginatedDocs = documents.slice(first, first+pageSize) |
— |
儲存流程:上傳 = 逐檔 POST /file/upload
收 file_uid → 一次 POST document-pool 帶陣列 →
成功 fetchDocuments()
重載。錯誤對應:GRC_404006(檔不存在)/
GRC_412028(AP 關閉不可編)→ toast。
11. 背景行為與外部依賴
| 類型 | 內容 |
|---|---|
| socket | 無(上傳 / 刪除後手動 refetch) |
| store | 無(純 component local ref,無 Pinia) |
| 檔案儲存 | upload_files.storage_type 決定落地後端(LOCAL / MINIO /
REMOTE_AGENT,見 儲存設定) |
| 跨頁副作用 | 池檔被關聯到控制項 / AO 後,mapping_count 反映;關聯 /
解除在規劃頁 Tab0(UC-PP-05) |
12. 邊界情況與已知坑
- 移除 refcount-aware:
DELETE只保證解掉「這一筆池關聯」;實體upload_files只有在同一 file_id 無其他版本引用時才真的刪——SSP 有版本快照時,同一檔可能被多版共享,直接刪實體會踩跨版 FK violation。 - 關聯計數只讀:本頁
mapping_count是顯示值,改關聯要去規劃頁 Tab0(UC-PP-05);在本頁刪池檔前若仍有關聯,關聯(mappings)隨ssp_reference_documentsCASCADE 一起消失。 - 兩種掛載脈絡:規劃頁 Tab4 的
ssp_uid是專案 living SSP(受 AP 可編 gate);範本編輯 Dialog 的ssp_uid是範本 SSP(無 AP gate)——同一面板、不同前置條件。 - 無 socket:多人同時編同一池不會即時同步,靠各自 refetch。
13. 開發與驗證
- 跑起來:起 BE(見專案根 README)+ FE dev
server;登入見
.env/ 部署文件測試帳號 - 導航:專案規劃頁 →
Tab4「程序書管理」(
/project/projects/:id/planning?tab=4);或資源庫範本編輯頁的程序書池 Dialog - 前置資料:需一個有 living SSP 的專案(AP = planning);上傳任意 PDF 測預覽 / 下載 / 關聯計數
- 相關:關聯動作見 專案規劃頁 UC-PP-05;儲存後端見 儲存設定