日期:2026-05-22 觸發來源:SSP Excel 匯入匯出 Phase 2(A+B)測試後發現的範圍歸屬問題 性質:Phase 2 的延伸補強(Phase C),在同一個 branch
feature/ssp-import-export-phase2繼續推進
SSP Excel 匯入匯出 Phase 2(Track A 匯入 + Track B 匯出)全部 ship 後,user 測試對亞航 CMMC SSP 樣板資料時發現:
compliance.project_device_mapping / compliance.project_information_systems 把範圍綁在 project 層,但實務上範圍會隨 AP 週期變動(年度稽核增減設備)— 語意應該是 per-SSP| 議題 | 決策 |
|---|---|
| 編 SSP 的範圍 | C 路線:編「當前 AP 期間對應的 SSP」(per-AP SSP) |
| 誰能編 | 只有 manager 角色(project_participants.role='manager') |
| 編 SSP 後是否 sync 回 MF | 否,SSP 是 snapshot,跟 MF 漂移允許 |
| SSP 角色 vs 專案角色 | 分開:SSP 用 OSCAL 標準角色(9 個),專案維持 ParticipantRole 四角色 |
| OSCAL 角色 master data 存哪 | 複用 system_menus,group=ssp_party_role,value 存英文 key,FE 用 key 查 i18n 顯示中/英文 |
| 受評範圍歸屬遷移路線 | A 路線:全面遷移到 SSP scope,廢除 project_device_mapping / project_information_systems |
| 第 N+1 個 AP 啟動時 SSP 從哪 copy | 從 v_n SSP deep clone(已實作於 SspVersioningService.clone_to_new_version,包含 9 個 OSCAL 擴充欄位) |
| 「專案規劃」頁加 SSP 編輯入口 | 方案 B:新增 SSP tab(不另開 fullpage 編輯頁),子區塊 lazy load 避免單次大量 fetch |
| 層 | Table | 角色欄位 | 角色取值 |
|---|---|---|---|
| Tenant | public.users (jedi-auth) |
— | 純使用者識別 |
| MF(樣板) | oscal_parties (party_type='person', user_id soft ref) + oscal_responsible_parties (context_type='ssp', context_id=NULL or MF 內部 SSP.id) |
role_id |
OSCAL 角色 9 個 |
| SSP(版本快照) | 同上,context_type='system_security_plan', context_id=ssp.id |
role_id |
OSCAL 角色 9 個(從 MF copy) |
| Project(流程) | compliance.project_participants |
role (varchar) |
manager / reviewer / auditor / viewer |
| 層 | Table | 說明 |
|---|---|---|
| Tenant | public.devices (jedi-device) |
設備本體 |
| MF | ❌ 無 | MF 是控制項樣板,不綁設備 |
| SSP | oscal.ssp_system_implementation_items (implementation_type='hardware', device_id soft ref) |
OSCAL inventory-item 鏡像 |
| Project | compliance.project_device_mapping ⚠️ 要廢除 |
改 derived from SSP |
| 層 | Table | 說明 |
|---|---|---|
| Tenant | compliance.information_systems |
系統本體 |
| MF | ❌ 無直接綁定 | — |
| SSP | A. oscal.system_security_plans_system_characteristics (1:1) B. oscal.ssp_system_implementation_items (implementation_type IN ('component','system','subsystem','service','software'), system_characteristic_id soft ref) |
OSCAL 雙層:被描述系統 + 子元件 |
| Project | compliance.project_information_systems ⚠️ 要廢除 |
改 derived from SSP |
| 層 | Table | 說明 |
|---|---|---|
| Tenant | ❌ 無對應 entity | 系統內無 「FedRAMP authorized service catalog」 |
| MF | ❌ 無 | — |
| SSP | oscal.ssp_system_implementation_items (implementation_type='leveraged-authorization', party_uuid → oscal_parties 作為 provider) |
OSCAL leveraged-authorization 鏡像 |
| Project | ❌ 無 | — |
現況:v2.0.0(2026-05-21)06 sheet 已從樣板 ALL_SHEETS 隱藏,parser 也跳過讀(parser.py:70 leveraged=[])。但 DB / write strategy / DI / handler 全 stack 還在。要恢復只需兩步:
sheet_definitions.py:259 ALL_SHEETS 加回 SHEET_LEVERAGEDparser.py:70 改回呼叫 sh.parse_leveraged_sheet(...)目前完全沒對應 entity,系統內無存。OSCAL spec 對應 system-implementation.components with type='interconnection' or 'service',即 ssp_system_implementation_items.implementation_type='component'(純文字 + system_characteristic_id=NULL,因為外部系統不會在 tenant 內有對應 information_system 記錄)。
亞航 Table 2 缺欄位:類別 / Protocol / Security/Auth — 全是 SSP-only metadata。
每張 SSP 相關表都已內建 scope 隔離:
| Table | Scope 欄位 |
|---|---|
oscal.ssp_system_implementation_items |
scope_type + scope_id ('ssp' / 'module_frame') |
oscal.ssp_system_implementations (main) |
同上 |
oscal.oscal_responsible_parties |
context_type + context_id ('ssp' + ssp.id) |
oscal.system_security_plans_system_characteristics |
system_security_plan_id FK |
oscal.ssp_reference_documents |
context_type='ssp' |
→ project clone SSP 後,這些 row 已經是 project 專屬的,改它們不影響 MF 也不影響其他 project。
module_frame_ssp_resources_service.py:204 直接寫:
scope_type="ssp"
scope_id=ssp_id # MF 的內部 SSP等於說 MF 的編輯邏輯,整套搬到 project SSP 只要換 entry 參數從 MF uid → SSP uid 就行。Service 內部 90% 邏輯可以共用。
✅ /ssp/<ssp_uid>/control-implementations (CRUD 控制項實作)
✅ /ssp/<ssp_uid>/.../objective/... (CRUD AO 現況)
✅ /ssp/<ssp_uid>/reference-documents (程序書)
✅ /ssp/<ssp_uid>/document-pool (程序書池)
✅ /ssp/<ssp_uid>/control-implementations/import (docx import per SSP)
✅ /ssp/<ssp_uid>/export (預留給 B 階段)
| 缺的 endpoint | 仿哪個 MF 版本 | 工作量 |
|---|---|---|
GET/POST/PUT/DELETE /ssp/<ssp_uid>/parties |
module_frame_party_service |
小 — service 80% 可複用 |
GET/POST /ssp/<ssp_uid>/ssp-resources (devices/info_systems) |
module_frame_ssp_resources_service |
極小 — 內部已是 SSP scope,換 entry 即可 |
PUT /ssp/<ssp_uid>/ssp-resources/items/<item_uid> |
同上 | 同上 |
PUT /ssp/<ssp_uid>/system-characteristic |
新建 | 中(修 sensitivity / authorization-boundary / system-owner 等) |
GET/POST /ssp/<ssp_uid>/leveraged |
借 ssp_resources | 小 |
POST /ssp/<ssp_uid>/excel-import |
ssp_excel_import_app_service |
中(scope 切換) |
預估純後端 endpoint 補齊:2~3 天工。
A. 核心 CRUD(必須改)
| Caller | 用途 | 改造方向 |
|---|---|---|
app/grc/service/project_service.py:GrcProjectService |
讀寫專案範圍(建立/更新時) - Line 258-281:讀 audit_systems / devices 組 DTO - Line 367-...:audit_systems 替換 - Line 432-444:devices 替換 |
改吃 current SSP 的 system_implementation_items |
app/project/service/oscal_project_service.py |
- Line 456-461 Step C:建專案時 INSERT devices - Line 1100:刪專案 cascade delete |
拿掉 Step C(範圍移交 SSP 編輯頁)+ 拿掉 cascade(搬到 SSP CASCADE 上) |
app/associations/service/project_device_mapping_service.py |
設備 mapping CRUD | 整個服務廢除 |
B. API endpoints(必須處理)
| Endpoint | 用途 | 改造方向 |
|---|---|---|
/project-devices (ProjectDevicesRoute) |
查專案設備清單 | 改成從 current SSP 抓 + 返回相同格式(前端零改) |
/project-device (ProjectDeviceRoute) |
單筆設備 CRUD | 同上 derive,或廢除(改用 SSP 編輯頁) |
/project/job-execution-devices (JobExecutionDevicesRoute) |
工作流任務查設備 | 同上 derive |
/project/job-execution-device |
工作流設備 | 同上 |
C. Request / Response Schema 改造
| Schema | 改動 |
|---|---|
OscalProjectStartRequest (api/project/serializers/project.py:30) |
移除 audit_systems + devices 欄位 |
ProjectUpdateRequestSchema (api/grc/serializers/project.py:77) |
移除 audit_systems + devices 欄位 |
ProjectResponseSchema.audit_systems / .devices |
改 derived from current SSP(API 對外格式不變) |
D. SSP write strategy(不影響)
domain/oscal/strategy/ssp_write_strategy.py:258-277 有 TODO Phase E.2,從未實作 → 連帶拆掉 TODOE. Dashboard / AI Dashboard(不影響)
F. SSP versioning(不影響,反而受益)
ssp_versioning_service.clone_to_new_version 已包含 system_implementation_items 全 9 欄位| 風險 | 嚴重度 | 緩解 |
|---|---|---|
/project-devices API 改成 derived 時 schema 不一致 |
🟡 中 | API 回傳 schema 保持不變,前端零改 |
| 既有專案 migration | 🟡 中 | 寫一次性 SQL:每個專案的 current SSP 內 INSERT 對應 items;對舊資料做 idempotent 補洞 |
oscal_project_service.Step C 改造影響建專案流程 |
🟢 低 | 直接拿掉這段;建專案時不寫設備,等 user 進 SSP 編輯頁加 |
| Domain / Infra 整套程式碼留下會混淆 | 🟢 低 | C 階段完工後一次性 git rm 整個 domain/associations/ / infra/associations/ / app/associations/ stack |
| 多 tenant RLS policy 是否依賴這兩張表 | 🟡 中 | C 開工前 grep -r "project_device_mapping|project_information_systems" scripts/sql/ infra/grc/ 確認 |
| B 階段(匯出)會用到? | 🟡 中 | B 已 ship,需確認匯出邏輯是否 join 這兩張表,如有要連帶改 |
理由:
system_menus 已是 generic enum lookup table(group/key/value/sort/enable/public)/system/menu/<group> 可直接餵 FE dropdown,零自製INSERT INTO system_menus (group, key, value, sort, enable, public) VALUES
('ssp_party_role', 'responsible-organization', 'responsible-organization', 10, 1, 1),
('ssp_party_role', 'system-owner', 'system-owner', 20, 1, 1),
('ssp_party_role', 'system-security-officer', 'system-security-officer', 30, 1, 1),
('ssp_party_role', 'authorizing-official', 'authorizing-official', 40, 1, 1),
('ssp_party_role', 'information-owner', 'information-owner', 50, 1, 1),
('ssp_party_role', 'information-provider', 'information-provider', 60, 1, 1),
('ssp_party_role', 'information-receiver', 'information-receiver', 70, 1, 1),
('ssp_party_role', 'prepared-by', 'prepared-by', 80, 1, 1),
('ssp_party_role', 'prepared-for', 'prepared-for', 90, 1, 1);
value存英文 key(跟key同),FE 拿到後用此 key 查 i18n 顯示中/英文 label。這比 value 存中文更乾淨(不混 i18n 邏輯到 DB)。
// src/config/locales/i18n/zh-tw/oscal-role.json
{
"ssp_party_role": {
"responsible-organization": {
"label": "專案負責單位",
"description": "對系統運作負組織級責任的單位"
},
"system-owner": {
"label": "系統所有者",
"description": "對系統的安全與營運負最終責任"
},
"system-security-officer": {
"label": "系統安全官 (ISSO)",
"description": "負責執行系統安全控制與監控"
},
"authorizing-official": {
"label": "授權機關 (AO)",
"description": "有權正式接受系統運作風險的人員"
},
"information-owner": { "label": "資料所有者", "description": "..." },
"information-provider": { "label": "資料提供者", "description": "..." },
"information-receiver": { "label": "資料接收者 / 窗口", "description": "..." },
"prepared-by": { "label": "文件撰寫者", "description": "..." },
"prepared-for": { "label": "文件對象", "description": "..." }
}
}| 修改點 | 動作 |
|---|---|
sheet_definitions.py:138 Excel 03_參與人員 role |
從 ParticipantRole 四選一 → 改 OSCAL 角色 dropdown(讀 system_menus) |
ssp_docx_generator.py:32 _ROLE_LABEL_MAP |
拿掉 manager/auditor/viewer(不該出現於 SSP 文件),補齊 OSCAL 角色,修 security-officer ↔︎ system-security-officer 對不上 |
cmmc_ssp_adapter.py:49 regex 對應 |
擴增到 9 個角色對應的中文 pattern |
| FE 預覽頁 / template-edit 頁 role picker | 從 hardcoded 4 個 → 改讀 /system/menu/ssp_party_role API |
OscalRole ORM model |
評估留還是廢(目前可能未 seed 也無 caller) |
專案規劃頁
├── Tab: 概覽 (default)
├── Tab: AP 列表 / 進度
├── Tab: 參與人員
└── Tab: SSP ← 新加
└── 下拉選 AP → 顯示該 AP 對應 SSP 的編輯區
├── 基本資料(system_characteristic)
├── 受評範圍(devices / info_systems / leveraged)
├── OSCAL Parties
├── 控制項實作(既有 /ssp/<uid>/control-implementations)
└── 程序書
manager 角色看得到 SSP tab 內的編輯按鈕(reviewer/auditor/viewer 可進但 read-only)project_participants.role='manager'現況:
OscalProjectStartRequest 接受 participants + audit_systems + devices 三 listoscal_project_service.start_oscal_project Step C:寫 device mappings;Step audit_systems:寫 information_system mappings改造:
OscalProjectStartRequest:移除 audit_systems + devices 欄位start_oscal_project:拿掉 Step C 跟對應 audit_systems INSERT現況:
ProjectUpdateRequestSchema 接受 audit_systems + devicesupdate_project Section 3 + Section 5 做替換邏輯改造:
ProjectUpdateRequestSchema:移除 audit_systems + devices 欄位update_project:移除 Section 3 + Section 5 整段現況:ProjectResponseSchema.audit_systems + .devices 從 project_device_mapping / project_information_systems 抓
改造(API schema 不變,內部換資料源):
dto.audit_systems:改 derive from current SSP 的 ssp_system_implementation_items (scope_type='ssp', current_ssp_id, implementation_type IN ('component','system','subsystem','service','software'))dto.devices:改 derive from ssp_system_implementation_items (implementation_type='hardware')→ 前端零改(schema 不動)
| 項目 | 處理方式 |
|---|---|
project_device_mapping table |
C4 完工後 DROP TABLE(含 migration 資料遷移) |
project_information_systems table |
同上 |
domain/associations/ / infra/associations/ / app/associations/ 整個 stack |
C4 完工後 git rm |
ssp_write_strategy.py:258-277 TODO Phase E.2 |
連帶移除 |
_ROLE_LABEL_MAP 內 manager/auditor/viewer |
拿掉(不該出現於 SSP 文件) |
OscalRole ORM model |
評估,可能廢 |
| ID | 子題 | 級別 |
|---|---|---|
| C1 | OSCAL SSP 角色 master data 化 | 中 |
| C2 | SSP-scoped endpoints 補齊(6 個) | 中 |
| C3 | 受評範圍歸屬遷移到 SSP(廢 project_device_mapping / project_information_systems) | 重 |
| C4 | 專案建立/編輯流程改造(拿掉 audit_systems / devices) | 中 |
| C5 | 專案規劃頁加 SSP tab | 中 |
| C6 | 移除專案設定 UI 的「受評範圍」 | 小 |
| C7 | 06_外部利用服務恢復(可選) | 小 |
| C8 | _ROLE_LABEL_MAP 清理 + role id 修正 |
小 |
依賴:C1 + C2 → C3 → C4 → C5/C6(前端依賴後端 endpoint 完整)