手冊首頁 / 證據管理 / 程序書池

程序書池(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_filesssp_reference_documentscontext_type='ssp')新增池關聯;移除只解池關聯,實體檔僅在無其他版本引用(refcount=0)時才刪
docpool_uc_01 start pick 上傳區選檔或拖拉 (多檔,單檔 ≤ 50MB) start->pick upload 逐檔 POST /file/upload(multipart) → 拿 file_uid pick->upload add POST /ssp/{ssp_uid}/document-pool (帶 file_uid 陣列 + 描述) upload->add perm BE 檢查:使用者是專案 manager? 且對應稽核計畫可編輯(planning)? add->perm deny 非 manager → 403 AP 已關閉/封存 → GRC_412028 perm->deny fchk file_uid 存在於 upload_files? perm->fchk ferr GRC_404006 (佐證檔不存在) fchk->ferr write 寫 ssp_reference_documents (context_type='ssp',池本體) fchk->write done fetchDocuments() 重載清單 (無 socket,手動 refetch) write->done remove DELETE .../document-pool/{doc_uid} refcount-aware:跨版共享檔 refcount>0 保留 (只解池、實體檔 refcount=0 才刪) done->remove 之後可移除
UC-DP-01 流程:選檔→逐檔 /file/upload 拿 file_uid→POST document-pool→BE 檢 manager+AP 可編(否則 403/GRC_412028)→file 不存在 GRC_404006→寫 ssp_reference_documents(context=ssp)→重載;移除走 refcount-aware DELETE

3. 權限矩陣

操作 FE 判定 BE 強制 BE 檢查位置
讀池清單 進頁即可 require_participant(SSP → project → 任一 participant) common/middleware/permission/ssp_permission.pySspPermissionChecker.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.pyGRC_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 ② 預覽 Dialog docpool-preview.png

版面骨架與區塊職責(單頁;彩色區塊可點跳本頁小節錨點,API 僅標代表性):

跳至 §2 Use Case(UC-DP-01 上傳程序書到池與移除) Header:程序書池標題 + 檔案計數 +「上傳」鈕(manager+AP 可編時顯示) POST /file/upload → POST /ssp/{ssp_uid}/document-pool(批次加入池) 跳至 §2 Use Case(UC-DP-01 拖拉/點選多檔上傳,單檔 ≤ 50MB) 拖拉上傳區:拖拉或點選多檔(單檔 ≤ 50MB)→ 逐檔上傳取 file_uid → 批次入池 POST /file/upload(multipart,回 file_uid) 檔案列表(每列一支池檔;空池顯示資料夾圖示+提示) 檔名 / 描述 / 大小 / 上傳日期 / 🔗關聯計數 / 👁 ⬇ 🗑 policy_v3.pdf ・ 存取控制程序 ・ 1.2MB ・ 2026-07-01 ・ 🔗 4 ・ 👁 ⬇ 🗑 ir_plan.pdf ・ 事件應變計畫 ・ 640KB ・ 2026-06-28 ・ 🔗 2 ・ 👁 ⬇ 🗑 backup.txt ・ 備份紀錄 ・ 12KB ・ 2026-06-20 ・ 🔗 0 ・ 👁 ⬇ 🗑 前端分頁 Paginator(10 / 25 / 50,documents.slice) GET /ssp/{ssp_uid}/document-pool(含 mapping_count) DELETE …/document-pool/{doc_uid}(refcount-aware) 預覽 Dialog 每列👁 開啟 iframe 內嵌預覽 (PDF / txt) iframe 內容區 GET /file/pdf-preview/{uid} GET /file/download/{uid} 彩色=可點跳本頁小節錨點(框內標錨點+代表性 API)|灰=本頁描述|虛線框=拖拉目標/預覽 Dialog 條件層 API 僅列代表性 2~3 條,完整規格見 §6
程序書池版面 wireframe:標題+檔案計數+上傳鈕、拖拉上傳區、檔案列表(每列 檔名/描述/大小/日期/關聯計數+預覽/下載/刪除)+前端分頁、預覽 Dialog

狀態呈現:載入時 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-64app/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 datatrue。出處: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.pySspDocumentPoolService domain/grc/service/ssp_reference_document_domain_service.pyinfra/grc/repository/ssp_reference_document_repo_impl.py;查詢 ssp_document_pool_query.py
權限 common/middleware/permission/ssp_permission.pySspPermissionChecker + 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 圖(本頁讀寫範圍)

docpool_er upload public.upload_files id (PK) uid file_name / file_ext / size path / storage_type / storage_scope sha256(防竄改錨點) refdoc oscal.ssp_reference_documents id (PK) uid context_type('ssp'=池本體 / 'control_implementation' / 'objective') context_id(ssp_id / 控制實作 id / AO id) file_id → upload_files description refdoc->upload file_id(可 NULL) mapping oscal.ssp_reference_document_mappings id (PK) reference_document_id → ssp_reference_documents(CASCADE) context_type('control_implementation' / 'objective') context_id(控制實作 id / AO id) mapping->refdoc reference_document_id
程序書池讀寫範圍:upload_files(實體檔)← ssp_reference_documents(池/參考關聯,context_type 決定層級)← ssp_reference_document_mappings(控制項/AO 關聯)

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/uploadfile_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. 邊界情況與已知坑

  1. 移除 refcount-awareDELETE 只保證解掉「這一筆池關聯」;實體 upload_files 只有在同一 file_id 無其他版本引用時才真的刪——SSP 有版本快照時,同一檔可能被多版共享,直接刪實體會踩跨版 FK violation。
  2. 關聯計數只讀:本頁 mapping_count 是顯示值,改關聯要去規劃頁 Tab0(UC-PP-05);在本頁刪池檔前若仍有關聯,關聯(mappings)隨 ssp_reference_documents CASCADE 一起消失。
  3. 兩種掛載脈絡:規劃頁 Tab4 的 ssp_uid 是專案 living SSP(受 AP 可編 gate);範本編輯 Dialog 的 ssp_uid 是範本 SSP(無 AP gate)——同一面板、不同前置條件。
  4. 無 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;儲存後端見 儲存設定