控制項/AO 現況編輯與程序書(Implementation Status)
功能群:專案管理|上層見 專案規劃頁(本頁=其 Tab0「控制項實作」右面板的現況編輯、程序書關聯、節點參與人員)
事實基準:2026-07-06 從 FE / BE code 掃出;表結構對
db_schema.json、endpoint 對routes.json、欄位對 serializer + service 逐條核|變更紀錄:2026-07-06 FR-047 從 project-planning.md 拆出
1. 功能描述
本頁是專案規劃頁
Tab0「控制項實作」右面板的三塊核心編輯功能:受評方在左樹選中控制項
/ AO 節點後,於右面板填寫現況說明(實作狀態 + 描述 +
備註)、關聯程序書(控制項層 / AO
層),並可對群組 /
控制項節點指派參與人員(影響三層遞進編輯權)。編輯的是專案
living SSP(見 _overview
§4);launch-audit 凍結後轉唯讀。左樹導覽 /
完成度篩選與進度卡屬 hub 頁級功能,見 規劃頁 §5/§10。
主要使用者:專案 manager、群組 / 控制項管理人、被指派填現況的人員。
角色速覽(完整定義見 _overview §3):
| 角色 | 一句話 |
|---|---|
| manager(三層任一) | 專案 / 群組 / 控制項 manager — 可編此節點下的現況 / 程序書;專案 manager 另可管節點參與人員 |
| 參與者 | auditor / viewer — 可讀不可寫 |
1.1 功能總覽(本頁全部功能)
| # | 功能 | 說明 | 位置 | 詳述 |
|---|---|---|---|---|
| 1 | 控制項 / AO 現況編輯 | 逐項填實作狀態(5 種)+ 現況描述(AO 另有備註),各自獨立儲存(upsert) | Control / AO 面板 | UC-PP-01 |
| 2 | 程序書上傳與關聯 | 上傳或從程序書池挑,關聯到控制項層或 AO 層,可移除 | Control / AO 面板 | UC-PP-05 |
| 3 | 群組 / 控制項層參與人員 | 對群組 / 控制項節點指派管理人員(影響三層編輯權,見 §3) | Group / Control 面板 | UC-PP-08 |
UC(§2)展開三條核心路徑:現況編輯 UC-PP-01、程序書關聯 UC-PP-05、節點參與人員 UC-PP-08。左樹導覽 / 進度卡見 規劃頁。
2. Use Case
角色速覽(同 §1)。流程圖視覺慣例:菱形 = 判斷、橘底 = 例外/擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。
UC-PP-01 填寫控制項 / AO 現況說明
| 項目 | 內容 |
|---|---|
| 角色 | 專案 manager(或群組 / 控制項 manager,見 §3 差異註記) |
| 前置條件 | 已登入;輪次狀態 = planning;為專案參與者 |
| 產出 / 後置條件 | oscal.ssp_implemented_requirements /
ssp_statements 的 props 更新(upsert);SSP 進度卡 %
上升 |
UC-PP-05 上傳程序書並關聯到控制項 / AO
| 項目 | 內容 |
|---|---|
| 角色 | 有編輯權者(專案 / 群組 / 控制項 manager,三層遞進
canEdit;BE require_manager) |
| 前置條件 | 輪次 = planning(FE 唯讀後按鈕隱藏);已選中控制項或 AO 節點 |
| 產出 / 後置條件 | reference-documents 關聯記錄新增於控制項層或 AO
層;document-mappings 反映;移除只解關聯不刪實體檔 |
UC-PP-08 群組 / 控制項層參與人員管理
| 項目 | 內容 |
|---|---|
| 角色 | 具 canManageGroupParticipants /
canManageControlParticipants 者 |
| 前置條件 | 已選中群組或控制項節點 |
| 產出 / 後置條件 | control-group-participant /
project-control-participant 增改刪;直接影響該群組
/ 控制項的三層編輯權(見 §3) |
3. 權限矩陣
| 操作 | FE 判定 | BE 強制 | BE 檢查位置 |
|---|---|---|---|
| 讀現況 / 程序書 | 參與者即可 | require_participant |
app/oscal/service/ssp_control_implementation_service.py:55,80,141 |
| 寫現況 / 程序書 | 三層遞進 canEdit(專案 → 群組 → 控制項
mgr,ProjectPlanningView.vue:663-679) |
require_manager(否則 GRC_403051 / AP
不可編 GRC_412028) |
同檔 :63(SspPermissionChecker) |
| 群組 / 控制項參與人員管理 | canManageGroupParticipants /
canManageControlParticipants(ProjectPlanningView.vue:303-313) |
assert_project_manager()(角色向下回退,不符
GRC_403002) |
common/util/participant_guard.py:16-30 |
⚠️ FE/BE 權限不對齊(見 §12 坑 1):FE 三層遞進允許「群組 / 控制項 manager」編輯,BE
require_manager守門粒度不同——改權限兩端一起看。
4. 狀態機與前置條件
承接輪次 planning gate(見 規劃頁 §4):
| 條件 | 效果 | 出處 |
|---|---|---|
apStatus === 'planning' |
canEditSsp=true:現況 / 程序書可編 |
ProjectPlanningView.vue:75 |
apStatus !== 'planning' |
唯讀(launch-audit 後凍結) | 同上 |
| BE 寫入 phase guard | SSP 寫入本身無 phase 檢查(見 §12 坑 3) | — |
5. UI 設計
版面骨架(Tab0 右面板依左樹選中節點型別切換,三功能區塊 + 代表性 API):
📸 實機截圖(亮色模式):Control 面板見 規劃頁 §5 的
planning-control-panel.png、AO 面板見planning-ao-tasks.png(上半現況區)。
狀態呈現:控制項 / AO /
各程序書各自獨立儲存,per-項目
spinner(sspSaving Set);樹節點帶完成度徽章;無
socket,儲存後前端 state 更新。
6. API 規格
Envelope:成功 {"status": true, "data": …}、失敗
{"error_code": "…", "msg": "…"}。
6.1 總清單
| 分類 | Method + Path | 說明 | 完整規格 |
|---|---|---|---|
| 現況 | GET/PUT /ssp/{sspUid}/control-implementation/{cid} |
控制項現況讀取 / 儲存(upsert) | §6.2 |
| 現況 | PUT .../objective/{stmtId} |
AO 現況儲存(upsert) | §6.2 |
| 程序書 | POST/DELETE .../reference-documents、GET
.../document-mappings(控制項與 AO 兩層) |
程序書關聯增刪查 | §6.3 |
| 程序書 | POST /file/upload |
檔案上傳(upload_type: REFERENCE_DOCUMENT),拿
file_uid 再關聯 |
GAI-SD-02 |
| 參與人員 | POST/PUT/DELETE /control-group-participant |
群組層人員增改刪 | §6.4 |
| 參與人員 | POST/PUT/DELETE /project-control-participant |
控制項層人員增改刪 | §6.4 |
6.2 現況類
FE 右面板表單欄位(選中控制項 → Control 面板;選中
AO → AO 面板,ProjectPlanningView.vue,canEdit
遞進控制 disabled):
| 層 | 欄位 | 型別 | 必填 | 列舉 / 儲存 |
|---|---|---|---|---|
| 控制項 / AO | 實作狀態 implementation_status |
Dropdown | 否 | implemented / partial /
planned / alternative /
not_applicable(5 值) |
| 控制項 / AO | 現況描述 implementation_description |
Textarea | 否 | 實作說明 |
| 控制項 / AO | 備註 remarks |
Textarea | 否 | 補充備註 |
控制項層與各 AO 層各自獨立儲存(各打對應 PUT,per-項目 spinner);儲存走 upsert(紀錄不存在自動建)。
[GET] /ssp/{ssp_uid}/control-implementation/{control_identifier}
Response data:
| 欄位 | 型別 | 說明 |
|---|---|---|
| uid / control_identifier | string | 實作紀錄識別 |
| implementation_status | string|null | implemented / partial /
planned / alternative /
not_applicable |
| implementation_description / remarks | string|null | 現況描述 / 備註 |
| responsible_role / control_origination | string|null | OSCAL 欄位 |
| objectives[] | array | AO
現況:{uid, statement_identifier, implementation_status, implementation_description, remarks, reference_documents[], created_at, updated_at} |
| reference_documents[] | array | 控制項層程序書:{uid, file_id, file_uid, file_name, file_size, description} |
| created_at / updated_at / created_user / updated_user | — | 審計欄 |
出處:api/oscal/serializers/ssp/ssp_control_implementation.py:15-36
[PUT] /ssp/{ssp_uid}/control-implementation/{control_identifier}
Request(全部選填,upsert:紀錄不存在自動建立):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
| implementation_status | string | 否 | 允許值同上 |
| implementation_description | string | 否 | 現況描述 |
| remarks | string | 否 | 備註 |
Response:同 GET。出處:同檔
:38-42(ControlImplementationUpdateRequestSchema);service
require_manager(ssp_control_implementation_service.py:63)→
非 manager GRC_403051 / AP 不可編
GRC_412028。
[PUT] /ssp/{ssp_uid}/control-implementation/{control_identifier}/objective/{statement_identifier}
Request:同上三欄(implementation_status /
implementation_description /
remarks,皆選填)+
reference_documents(array,AO 層程序書)。Response
data:{uid, statement_identifier, implementation_status, implementation_description, remarks, reference_documents[], created_at, updated_at}。出處:api/oscal/serializers/ssp/ssp_control_impl_objective.py:22-27
6.3 程序書類
程序書關聯在控制項層(implemented_requirement)或 AO
層(statement)兩種 context。流程:先
POST /file/upload(upload_type=REFERENCE_DOCUMENT)拿
file_uid,或從程序書池挑既有 → 再
POST …/reference-documents 建立關聯;移除
DELETE
只解關聯不刪實體檔。GET …/document-mappings
查該節點已關聯清單。屬結構相似的關聯 CRUD,逐欄見 GAI-SD-02 /
Swagger。
6.4 群組 / 控制項層參與人員類
對控制群組或單一控制項節點指派管理人員,影響該節點三層遞進編輯權(見 §3)。兩組 endpoint 對稱(單一 path,method 分 POST/PUT/DELETE):
| Method | Path | 說明 |
|---|---|---|
| POST / PUT / DELETE | /control-group-participant |
群組層人員 新增 / 改角色 / 移除 |
| POST / PUT / DELETE | /project-control-participant |
控制項層人員 新增 / 改角色 / 移除 |
[POST / PUT] /control-group-participant ・ /project-control-participant
Request(Create/Update…ParticipantRequest):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
| project_uid / group_uid | string | 否* | 定位(群組層);亦接受 project_id /
group_id |
| control_uid | string | 否* | 僅控制項層;亦接受 control_id |
| user_uid | string | 是 | 被指派使用者 |
| role | string | 是 | manager / reviewer / auditor
/ viewer |
(*uid 與 id 兩組定位擇一帶足即可。)DELETE body 同上但不含
role。出處:serializer
api/participant/serializers/control_group_participant.py:13-36
/ project_control_participant.py:15-44。
權限 / 寫入:皆走
assert_project_manager()(common/util/participant_guard.py:16-30,角色向下回退
控制項→群組→專案,失敗 GRC_403002)。寫入表:群組層
compliance.control_group_participants(PK
project_id+group_id+user_id);控制項層
compliance.project_control_participants(PK
+control_id)。重複新增:群組 GRC_409002 /
控制項 GRC_409003。出處:service
app/participant/service/control_group_participant_service.py:86-156
/ project_control_participant_service.py:65-143。
FE(ParticipantsPanel.vue 指派
Dialog):user_uid(下拉,必填)+
role(Dropdown 4 值,預設 viewer)+
inherited_from(繼承來源
project/control_group/control/null)。API
常數 CONTROL_GROUP_PARTICIPANT /
CONTROL_PARTICIPANT(api.js:114,116)。
7. 前端檔案地圖(compliance-manager-fe/)
| 檔案 | 角色 |
|---|---|
src/views/project/ProjectPlanningView.vue |
主檢視(現況表單、程序書 dialog、節點參與人員 dialog 都在此) |
src/components/grc/ParticipantsPanel.vue |
群組 / 控制項參與人員指派 Dialog |
SspImportDialog / DocumentLinkDialog /
ReferenceDocumentList |
程序書關聯相關 dialog |
src/service/SspService.js |
control-implementation / objective / reference-documents API 包裝 |
8. 後端檔案地圖(本頁核心鏈路)
| 鏈路 | Route | App Service | 底層 |
|---|---|---|---|
| SSP 現況寫入 | api/oscal/routes/ssp/ssp_control_implementation_route.py(SspControlImplementationDetailRoute
/ SspObjectiveDetailRoute) |
ssp_control_implementation_service.py::update_control_implementation
/ update_objective(upsert:IR / statement
不存在自動建) |
jedi_oscal_v2 SspRepoImpl → props JSONB |
| 節點參與人員 | api/participant/ participant routes |
control_group_participant_service.py /
project_control_participant_service.py(assert_project_manager) |
compliance.control_group_participants /
project_control_participants |
DDD 提醒:現況寫入是 upsert 且權限在 service 層(
require_manager);⚠️ SSP 寫入無 phase guard(§12 坑 3),別複製。
9. DB
9.1 資料表總清單(本頁讀寫的全部表)
| 表 | 讀/寫 | 說明 | 詳述 |
|---|---|---|---|
| oscal.ssp_implemented_requirements | 寫 | 控制項現況(props:狀態 / 描述) | §9.3 |
| oscal.ssp_statements | 寫 | AO 現況(props:狀態 / 描述) | §9.3 |
| oscal.ssp_reference_documents | 寫 | 程序書關聯(控制項 / AO 兩層 context) | §9.3 |
| public.upload_files | 寫 | 程序書檔案本體 | GAI-SD-03 |
| compliance.control_group_participants / project_control_participants | 寫 | 群組 / 控制項層參與人員 | §9.3 |
| oscal.ssp_control_implementations | 讀 | SSP 現況的 containment 根 | GAI-SD-03 |
9.2 ER 圖
9.3 核心表欄位(取自 DEV DB dump,含 DB comment)
oscal.ssp_implemented_requirements — 控制項現況
OSCAL
control-implementation.implemented-requirements[]:逐控制項說明系統如何滿足。
| 欄位 | 型別 | 說明 |
|---|---|---|
| control_implementation_id | bigint | → oscal.ssp_control_implementations(containment,CASCADE) |
| control_id | varchar(255) | OSCAL control token(如 ac-2);跨文件參照存 token 不建
FK |
| uuid | uuid | OSCAL 身分 |
| props | jsonb | 實作狀態與現況描述存這裡:implementation-status
/ implementation-description |
| remarks | text | 備註 |
| sort_order | int | 陣列保序 |
oscal.ssp_statements — AO 現況
OSCAL
implemented-requirement.statements[]。結構同上層:implemented_requirement_id(FK,CASCADE)、statement_id(token,如
ac-2_smt.a;本系統 AO 用
_obj.N)、uuid、props(狀態/描述)、remarks、sort_order。
oscal.ssp_reference_documents — 程序書關聯
| 欄位 | 型別 | 說明 |
|---|---|---|
| context_type / context_id | varchar(30) / int | 兩層掛載:控制項層(implemented_requirement)或 AO 層(statement) |
| file_id | int|null | → public.upload_files |
| description | text | 檔案說明 |
compliance.control_group_participants / project_control_participants — 節點參與人員
| 欄位 | 型別 | 說明 |
|---|---|---|
| project_id / group_id | int | 定位(控制項層另有 control_id) |
| user_id | int | 被指派者 |
| role | varchar(50) | manager / reviewer / auditor
/ viewer(DB comment) |
PK:群組層
(project_id, group_id, user_id);控制項層(project_id, group_id, control_id, user_id)。
10. 頁面邏輯與資料對應
| 畫面元素 | FE state | API 欄位 |
|---|---|---|
| 實作狀態下拉 | sspStatuses[id] |
props implementation-status(5 值) |
| 現況描述 | sspDescriptions[id] |
props implementation-description |
| AO 備註 | sspRemarks[aoId] |
statement remarks |
| 樹完成度徽章 | ctrlStatus/ctrlProgress(computed) |
由 sspHasDescription 快取推導 |
儲存流程:各表單獨立儲存(控制項 / AO / 程序書各自
PUT,無整頁 save);儲存後更新本地 state。程序書:先
POST /file/upload 拿 file_uid → 再 POST
關聯。錯誤對應:GRC_403051(非 manager)/
GRC_412028(AP 不可編)/ 參與人員
GRC_403002(非 manager)/
GRC_409002・GRC_409003(重複)→ toast。
11. 背景行為與外部依賴
| 類型 | 內容 |
|---|---|
| socket | 無(儲存後 state 更新) |
| 節點參與人員影響 | 群組 / 控制項 participant 直接改該節點三層遞進
canEdit |
| 程序書 | 檔案本體走 /file/upload →
public.upload_files;關聯只存 file_id 軟參照 |
| jedi-* | jedi-oscal-v2(SSP
repo)、jedi-auth(JWT) |
12. 邊界情況與已知坑
- FE/BE 權限不對齊:FE 用三層遞進(群組/控制項
manager 可編輯),BE SSP 寫入是
require_manager、粒度不同——依 FE 操作可能得到 403,或反之繞過 FE 直接打 API 的權限面比 UI 寬。改權限務必兩端一起改。 - SSP 寫入無 phase
guard:
apStatus != planning的唯讀是 FE 鎖的,BE upsert 照寫。凍結快照後直接打 API 仍會改 living SSP(不會壞快照,但會讓「凍結時的 living 狀態」漂移)。 - 無 socket:多人同時編輯互相看不到;後儲存者覆蓋(props 整包 update)。
- AO 識別碼
_obj.N、現況真相在 props:實作狀態 / 描述存 props 的implementation-status/implementation-description,非獨立欄位;改鍵名要同步 mapper。 - 節點參與人員即時改編輯權:對群組 / 控制項加 manager
會立刻放寬該節點下的
canEdit,移除則收緊——調權限前留意連鎖影響的節點範圍。
13. 開發與驗證
- 跑起來:起 BE + FE;登入見
.env/ 部署文件測試帳號(需該節點 manager) - 導航:規劃頁 Tab0 → 左樹選控制項 / AO → 右面板現況 / 程序書;選群組 / 控制項節點 → 參與人員面板
- 前置資料:AP=planning 的專案(有 living SSP);備幾個 user 測節點參與人員 + 三層編輯權連動
- 相關:任務見 AO 任務配置與啟動;批次匯入見 批次維護;程序書池見 程序書池