C2 — Implementation Plan

對應 design:design-C2.md 級別:中 預估工時:BE 3~4 天(分兩批 ship 推薦)


§1

Pre-flight 驗證

# 1. 確認 MF endpoints 內部已是 SSP scope(不會弄錯複用模式)
grep -n "scope_type.*ssp\|scope_id=ssp" \
  app/module_frame/service/module_frame_ssp_resources_service.py \
  app/module_frame/service/module_frame_party_service.py | head -10

# 2. 確認 ssp_excel_import_app_service 內 _confirm_*_flow 命名
grep -n "_confirm.*flow\|source_type" \
  app/oscal/service/ssp_excel_import_app_service.py | head -10

# 3. 確認 C1 system_menus seed 已 ship
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -c \
  "SELECT COUNT(*) FROM system_menus WHERE \"group\" = 'ssp_party_role';"
# 預期:9

# 4. 確認 GrcErrorCode 下一個可用序號
grep -E "GRC_4[0-9]+|GRC_403[0-9]+" common/code/grc_error_code.py | head -10

# 5. 確認 ProjectAssessmentPlanMapping 反查路徑
grep -rn "ProjectAssessmentPlanMapping\|project_assessment_plan_mapping" \
  domain/ infra/ --include="*.py" | head -5

§2

批次規劃(依 C2-D3 建議)

  • Batch 1(C2.1):party + ssp-resources + system-characteristic + current-ssp-uid(11 個 endpoint)
  • Batch 2(C2.2):excel-import + leveraged(6 個 endpoint)

C5(FE SSP tab)依賴 Batch 1 即可開始(範圍 + party + system-characteristic),Excel import 可後補。


§3

Batch 1:基礎 SSP 編輯 endpoints

T1 — SspProjectResolver + require_ssp_manager helper(BE)

新檔

  • domain/oscal/service/ssp_project_resolver.py
  • common/middleware/permission/ssp_permission.py

內容:對齊 design-C2.md §3.1

新 error codes(加入 common/code/grc_error_code.py):

GRC_SSP_NOT_FOUND        = ("SSP 不存在",              "GRC_404010")  # 序號 pre-flight 確認
GRC_AP_NOT_ACTIVE        = ("AP 已關閉,無法編輯 SSP",  "GRC_412001")  # 412 第一條
GRC_NOT_PROJECT_MANAGER  = ("僅專案 manager 可編輯",   "GRC_403005")
GRC_SSP_PARTY_NOT_FOUND  = ("SSP 內參與人員不存在",     "GRC_404011")
GRC_SSP_RESOURCE_NOT_FOUND = ("SSP 資源不存在",         "GRC_404012")
GRC_INVALID_OSCAL_ROLE   = ("不合法的 OSCAL role",      "GRC_400015")

Unit test

  • resolver: 拿 ssp_uid → 回 ProjectContext;AP closed 報 412
  • require_ssp_manager: manager 通過、其他角色 reject 403

Commitfeat(oscal): C2.1 SspProjectResolver + require_ssp_manager helper + error codes


T2 — Service 層抽共用 base method(BE)

T2.1 — module_frame_party_servicelist_parties_by_context / create_party_in_context

檔案app/module_frame/service/module_frame_party_service.py

動作

  • 既有 list_parties(mf_uid) 內部已透過 mf 反查 internal SSP id
  • 抽出 _list_parties_by_context(context_type='ssp', context_id=<ssp_id>) 為 base
  • MF version 改 wrapper 呼叫 base
  • 同樣處理 create / update / delete

Commitrefactor(module-frame-party): C2.1 抽共用 SSP context base method

T2.2 — module_frame_ssp_resources_service 同樣抽 base

檔案app/module_frame/service/module_frame_ssp_resources_service.py

動作

  • 既有 list_items / add_item / update_item / delete_item 已是 SSP scope
  • 抽 base method 接 ssp_id(不從 mf_uid 推導)
  • MF wrapper 保留

Commitrefactor(module-frame-ssp-resources): C2.1 抽共用 ssp_id base method


T3 — /ssp/<ssp_uid>/parties 4 個 endpoint(BE)

新檔

  • api/oscal/routes/ssp/ssp_party_route.py
  • api/oscal/serializers/ssp/ssp_party.py
  • app/oscal/service/ssp_party_app_service.py
  • di_containers/oscal/... 對應 wiring

Service:呼叫 T2.1 抽出的 base method,加 require_ssp_manager 驗證

Validation

  • role_ids 內每個值用 system_menu_service.get_menus(group='ssp_party_role') 查白名單,否則 GRC_INVALID_OSCAL_ROLE

URL 註冊api/oscal/__init__.py

api.add_resource(SspPartiesRoute, '/ssp/<string:ssp_uid>/parties')
api.add_resource(SspPartyRoute, '/ssp/<string:ssp_uid>/parties/<string:party_uid>')

Unit test

  • GET 列出 / POST 新增 / PUT 更新 / DELETE 刪除
  • 權限:non-manager POST 報 403
  • AP closed 寫操作報 412
  • Invalid role 報 400

Commitfeat(ssp-party): C2.1 /ssp/<uid>/parties 4 個 endpoint


T4 — /ssp/<ssp_uid>/ssp-resources 4 個 endpoint(BE)

新檔

  • api/oscal/routes/ssp/ssp_resources_route.py
  • api/oscal/serializers/ssp/ssp_resources.py
  • app/oscal/service/ssp_resources_app_service.py
  • DI 對應

Service:呼叫 T2.2 抽出的 base method

URL 註冊

api.add_resource(SspResourcesRoute, '/ssp/<string:ssp_uid>/ssp-resources')
api.add_resource(SspResourceItemsRoute, '/ssp/<string:ssp_uid>/ssp-resources/items')
api.add_resource(SspResourceItemRoute, '/ssp/<string:ssp_uid>/ssp-resources/items/<string:item_uid>')

Unit test:同 T3 pattern

Commitfeat(ssp-resources): C2.1 /ssp/<uid>/ssp-resources 4 個 endpoint


T5 — /ssp/<ssp_uid>/system-characteristic 2 個 endpoint(BE)

新檔

  • api/oscal/routes/ssp/ssp_system_characteristic_route.py
  • api/oscal/serializers/ssp/ssp_system_characteristic.py
  • app/oscal/service/ssp_system_characteristic_app_service.py
  • DI 對應

Service:直接讀寫 OscalSystemSecurityPlanSystemCharacteristic(既有 jedi-oscal model)

URL 註冊

api.add_resource(SspSystemCharacteristicRoute, '/ssp/<string:ssp_uid>/system-characteristic')

Unit test:GET 既有資料、PUT 更新後 GET 看到新值

Commitfeat(ssp-system-characteristic): C2.1 /ssp/<uid>/system-characteristic 2 個 endpoint


T6 — /projects/<project_uid>/current-ssp-uid helper endpoint(BE)

新檔

  • api/grc/routes/project_current_ssp_route.py(或加進既有 project route)
  • 對應 service method

Logic

  • 從 project_uid 找 compliance.projects
  • 找對應 OscalAssessmentPlan with status='active' 的 latest one
  • 找該 AP 對應 ssp.uid
  • 回傳 {ssp_uid, ap_uid, ap_status, is_editable}

URL 註冊

api.add_resource(ProjectCurrentSspRoute, '/projects/<string:project_uid>/current-ssp-uid')

Commitfeat(project): C2.1 /projects/<uid>/current-ssp-uid helper endpoint


T7 — Batch 1 Changelog + smoke + commit

Changelogdocs/changelog/YYYY-MM-DD-feat-ssp-scoped-endpoints-batch1.md

---
type: feat
breaking: false
modules: [oscal, grc]
---

## 需求說明
C2.1:補 11 個 SSP-scoped endpoints — party / ssp-resources /
system-characteristic / current-ssp-uid。支援 C5 SSP tab 編輯 UI。

## 變更範圍
### BE
- 新建 SspProjectResolver + require_ssp_manager
- 6 個新 error code
- 4 個 party endpoint (/ssp/<uid>/parties[/<party_uid>])
- 4 個 ssp-resources endpoint
- 2 個 system-characteristic endpoint
- 1 個 current-ssp-uid helper
- Service 層抽 SSP context base method (party + resources)

## 測試
- 11 個新 endpoint 各有 unit test
- 既有 MF endpoints 透過 wrapper 仍正常

§4

Batch 2:Excel import + leveraged

T8 — Service: ssp_excel_import_app_servicesource_type='ssp' 支援

檔案app/oscal/service/ssp_excel_import_app_service.py

動作

  • 既有 _confirm_update_flow(針對 MF)+ _confirm_superset_flow(針對 framework_version)
  • 新增 _confirm_ssp_update_flow(ssp_uid, parsed_result, ...)
  • 新增 source_type='ssp' 入口處理(preview / parse 流程)

Commitfeat(ssp-excel-import): C2.2 加 source_type='ssp' 支援


T9 — /ssp/<ssp_uid>/excel-import/* 5 個 endpoint(BE)

新檔

  • api/oscal/routes/ssp/ssp_scoped_excel_import_route.py
  • 仿 ssp_excel_import_route.py(既有 MF / FW-version 入口)但 URL 改 /ssp/<uid>/...

URL 註冊:對齊 design-C2.md §5

Service:複用 ssp_excel_import_app_service(T8 已擴增)

Commitfeat(ssp-excel-import): C2.2 /ssp/<uid>/excel-import/* 5 個 endpoint


T10 — /ssp/<ssp_uid>/leveraged 4 個 endpoint(BE)

新檔

  • api/oscal/routes/ssp/ssp_leveraged_route.py
  • app/oscal/service/ssp_leveraged_app_service.py

Service:複用 ssp_resources base method,只是 implementation_type 鎖在 'leveraged-authorization'

URL 註冊

api.add_resource(SspLeveragedListRoute, '/ssp/<string:ssp_uid>/leveraged')
api.add_resource(SspLeveragedDetailRoute, '/ssp/<string:ssp_uid>/leveraged/<string:item_uid>')

Commitfeat(ssp-leveraged): C2.2 /ssp/<uid>/leveraged 4 個 endpoint


T11 — Batch 2 Changelog

---
type: feat
breaking: false
modules: [oscal]
---

## 需求說明
C2.2:補完 SSP-scoped 剩餘 endpoint — Excel import (5) + leveraged (4)

## 變更範圍
### BE
- ssp_excel_import_app_service 加 source_type='ssp'
- 5 個 /ssp/<uid>/excel-import/* endpoint
- 4 個 /ssp/<uid>/leveraged endpoint

§5

測試規格

測試 內容
Unit 每個 endpoint:CRUD path + 權限驗證 + invalid input
Unit SspProjectResolver: 各種 resolve 失敗情境
Unit require_ssp_manager: 4 個角色都試一遍
Integration 建專案 → 啟動 AP → 拿 current-ssp-uid → 用該 uid 加 party → 列出看到
Integration Excel import (SSP scope): 上傳 → preview → confirm → SSP items 增加
Regression 既有 MF endpoints 仍正常運作

§6

完成標準(DoD)

Batch 1

Batch 2


§7

風險 / Rollback

風險 緩解
MF service 抽 base 後 wrapper 邏輯 break 既有 MF endpoint unit test 全跑一次
既有 SSP endpoint URL 撞名 pre-flight grep 確認無重複 route
Excel import scope='ssp' 與 'module_frame' 邏輯差異被忽略 兩條 path 各寫 test case

Rollback:每 batch 獨立 commit,可分別 revert