# C2 — Implementation Plan

> 對應 design：[design-C2.md](design-C2.md)
> 級別：中
> 預估工時：BE 3~4 天（分兩批 ship 推薦）

---

## Pre-flight 驗證

```bash
# 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
```

---

## 批次規劃（依 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 可後補。

---

## 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`）：
```python
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

**Commit**：`feat(oscal): C2.1 SspProjectResolver + require_ssp_manager helper + error codes`

---

### T2 — Service 層抽共用 base method（BE）

#### T2.1 — `module_frame_party_service` 抽 `list_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

**Commit**：`refactor(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 保留

**Commit**：`refactor(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`
```python
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

**Commit**：`feat(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 註冊**：
```python
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

**Commit**：`feat(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 註冊**：
```python
api.add_resource(SspSystemCharacteristicRoute, '/ssp/<string:ssp_uid>/system-characteristic')
```

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

**Commit**：`feat(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 註冊**：
```python
api.add_resource(ProjectCurrentSspRoute, '/projects/<string:project_uid>/current-ssp-uid')
```

**Commit**：`feat(project): C2.1 /projects/<uid>/current-ssp-uid helper endpoint`

---

### T7 — Batch 1 Changelog + smoke + commit

**Changelog**：`docs/changelog/YYYY-MM-DD-feat-ssp-scoped-endpoints-batch1.md`

```markdown
---
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 仍正常
```

---

## Batch 2：Excel import + leveraged

### T8 — Service: `ssp_excel_import_app_service` 加 `source_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 流程）

**Commit**：`feat(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 已擴增）

**Commit**：`feat(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 註冊**：
```python
api.add_resource(SspLeveragedListRoute, '/ssp/<string:ssp_uid>/leveraged')
api.add_resource(SspLeveragedDetailRoute, '/ssp/<string:ssp_uid>/leveraged/<string:item_uid>')
```

**Commit**：`feat(ssp-leveraged): C2.2 /ssp/<uid>/leveraged 4 個 endpoint`

---

### T11 — Batch 2 Changelog

```markdown
---
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
```

---

## 測試規格

| 測試 | 內容 |
|------|------|
| 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 仍正常運作 |

---

## 完成標準（DoD）

### Batch 1
- [ ] SspProjectResolver + require_ssp_manager + 6 個 error code
- [ ] 11 個 endpoint 全綠
- [ ] MF service 抽 base method 後既有 MF endpoints 仍正常
- [ ] Changelog + tracker 更新

### Batch 2
- [ ] 9 個 endpoint 全綠
- [ ] Excel import 流程 round-trip 通過
- [ ] Changelog + tracker 更新

---

## 風險 / 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
