SSP 在專案內直接編輯 — 設計分析

日期: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 樣板資料時發現:

  1. 角色語意混亂:SSP 文件需要「系統 owner / AO / 安全官」(OSCAL 文件角色),專案則用「manager / reviewer / auditor / viewer」(工作流角色),現有 UI 把兩者塞進同一個 picker 看起來像同一回事
  2. 受評範圍歸屬錯位compliance.project_device_mapping / compliance.project_information_systems 把範圍綁在 project 層,但實務上範圍會隨 AP 週期變動(年度稽核增減設備)— 語意應該是 per-SSP
  3. 亞航文件大量資料系統內無對應:Table 1(Leveraged FedRAMP)/ Table 2(External Systems)/ external_systems 等 SSP-only metadata 無欄位可存
  4. 專案內無法直接編 SSP:MF 編輯頁可改 SSP 樣板,但專案啟動後想對該專案的 SSP 做客製化修改,目前要回 MF 改(會污染樣板)

決策匯總(已拍板)

議題 決策
編 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_rolevalue 存英文 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

1. 五個資料域的「Tenant ↔︎ MF ↔︎ SSP ↔︎ Project」對應

1.1 參與人員

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

1.2 設備

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

1.3 資訊系統

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

1.4 06_外部利用服務(目前隱藏中)

Table 說明
Tenant ❌ 無對應 entity 系統內無 「FedRAMP authorized service catalog」
MF ❌ 無
SSP oscal.ssp_system_implementation_items (implementation_type='leveraged-authorization', party_uuidoscal_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 還在。要恢復只需兩步:

  1. sheet_definitions.py:259 ALL_SHEETS 加回 SHEET_LEVERAGED
  2. parser.py:70 改回呼叫 sh.parse_leveraged_sheet(...)

1.5 亞航 Table 2(External Systems)

目前完全沒對應 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。


2. SSP 在專案內直接編輯的可行性

2.1 資料層已 scope-aware(核心優勢)

每張 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

2.2 MF 編輯路徑其實也是寫 SSP scope

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% 邏輯可以共用。

2.3 部分 SSP-scoped endpoints 已存在

✅ /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 階段)

2.4 缺什麼

缺的 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 天工。


3. C4 深度影響分析:廢除 project_device_mapping / project_information_systems

3.1 完整 caller 盤點

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-277TODO Phase E.2,從未實作 → 連帶拆掉 TODO

E. Dashboard / AI Dashboard(不影響)

  • 沒找到直接使用,安全

F. SSP versioning(不影響,反而受益)

  • ssp_versioning_service.clone_to_new_version 已包含 system_implementation_items 全 9 欄位
  • 移除 project mapping 表後跨 AP 連續性更乾淨(單一資料源)

3.2 風險評估

風險 嚴重度 緩解
/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 這兩張表,如有要連帶改

4. OSCAL SSP 角色 master data 設計

4.1 採用 system_menus 而非新建 oscal_roles 表

理由

  • system_menus 已是 generic enum lookup table(group/key/value/sort/enable/public)
  • 既有 API /system/menu/<group> 可直接餵 FE dropdown,零自製
  • 新增角色 = INSERT 一行 SQL,不用 deploy code
  • 同樣模式可套到其他 OSCAL enum(system-implementation-type / system-status / sensitivity-level)

4.2 9 個角色 master list(seed data)

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)。

4.3 FE i18n 檔案結構

// 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": "..." }
  }
}

4.4 需要連帶改的程式碼

修改點 動作
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)

5. 「專案規劃」頁加 SSP 編輯入口(UI 設計)

5.1 採用方案 B:tab in 專案規劃頁

專案規劃頁
├── Tab: 概覽 (default)
├── Tab: AP 列表 / 進度
├── Tab: 參與人員
└── Tab: SSP   ← 新加
    └── 下拉選 AP → 顯示該 AP 對應 SSP 的編輯區
        ├── 基本資料(system_characteristic)
        ├── 受評範圍(devices / info_systems / leveraged)
        ├── OSCAL Parties
        ├── 控制項實作(既有 /ssp/<uid>/control-implementations)
        └── 程序書

5.2 Lazy load 策略(解決 user 擔心的「單頁資料太多」)

  • Tab 切換到「SSP」時才呼叫後端
  • AP 切換時才載入該 AP 對應 SSP 的詳細資料
  • 每個子區塊(範圍 / parties / 控制項)獨立 endpoint,分別 fetch
  • 子區塊內可再用 collapse / accordion 預設折疊避免一次渲染全部

5.3 權限門檻

  • 只有 manager 角色看得到 SSP tab 內的編輯按鈕(reviewer/auditor/viewer 可進但 read-only)
  • 後端每個 SSP-scoped write endpoint 都要驗 project_participants.role='manager'

6. 專案建立/編輯流程改造

6.1 建立專案 (POST /oscal-project/start)

現況

  • OscalProjectStartRequest 接受 participants + audit_systems + devices 三 list
  • oscal_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
  • 新流程:建專案 → 立即啟動第一個 AP → auto-create draft SSP → user 進 SSP tab 編範圍

6.2 編輯專案 (PUT /grc/project/)

現況

  • ProjectUpdateRequestSchema 接受 audit_systems + devices
  • update_project Section 3 + Section 5 做替換邏輯

改造

  • ProjectUpdateRequestSchema移除 audit_systems + devices 欄位
  • update_project移除 Section 3 + Section 5 整段
  • 保留 name / description / start_date / end_date / status / owner_uid / participants

6.3 專案 Response (GET /grc/project/)

現況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 不動)

6.4 前端表單調整

  • 建立專案表單:拿掉「受評範圍」section(devices / audit_systems pickers)
  • 編輯專案表單:同上
  • 專案詳情頁 / 規劃頁:「受評範圍」section 從專案層改為 link 到 SSP tab

7. 已棄用 / 待清理項目

項目 處理方式
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 評估,可能廢

8. Phase C 工作清單(細項見 README.md)

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 完整)