C4 — Implementation Plan

對應 design:design-C4.md 級別:中 預估工時:BE 1 天


§1

Pre-flight 驗證

# 1. 確認 C3 已 ship(C4 嚴格依賴 C3 — Response Schema 已改 derived)
git log --oneline | grep -E "C3|2026.*range-migration" | head -3

# 2. 確認 OscalProjectStartRequest 仍在預期位置
grep -n "class OscalProjectStartRequest\|class ProjectUpdateRequestSchema" \
  api/project/serializers/project.py api/grc/serializers/project.py

# 3. 確認 start_oscal_project Step C 仍在 line 456-461
grep -n "Step C\|project_device_mapping_service" \
  app/project/service/oscal_project_service.py | head -5

# 4. 確認 update_project Section 3/5 仍在 line 367 / 432
grep -n "audit_systems\|devices.*替換\|稽核系統替換\|設備替換" \
  app/grc/service/project_service.py | head -10

# 5. 確認 DI 注入點
grep -rn "project_device_mapping_service\|project_information_system_domain_service" \
  di_containers/ --include="*.py" | head -10

§2

任務清單

T1 — OscalProjectStartRequest 拿掉 audit_systems + devices 欄位(BE)

檔案api/project/serializers/project.py:30

動作

  • 刪除 line 40-43(audit_systems 欄位)
  • 刪除 line 48-51(devices 欄位)
  • 加註解說明 C4 移除背景(指 docs/features/FR-011.3-2605-ssp-edit-in-project/)
  • Meta.unknown = EXCLUDE 維持(向後相容)

對應 DTO 變動

  • StartProjectDTO(或對應 dataclass)有 audit_systems / devices 屬性 → 同步拿掉
  • pre-flight 確認 DTO 位置

Commit messagerefactor(project-api): C4 拿掉 OscalProjectStartRequest 內 audit_systems + devices


T2 — ProjectUpdateRequestSchema 拿掉 audit_systems + devices 欄位(BE)

檔案api/grc/serializers/project.py:77

動作

  • 刪除 line 95-99(audit_systems 欄位)
  • 刪除 line 105-109(devices 欄位)
  • docstring 補說明 C4 改動

Commit messagerefactor(grc-api): C4 拿掉 ProjectUpdateRequestSchema 內 audit_systems + devices


T3 — oscal_project_service.start_oscal_project 移除 Step C(BE)

檔案app/project/service/oscal_project_service.py

動作

  1. 找 Step C(line 456-461 區段)+ 對應 audit_systems INSERT(pre-flight 找確切位置)
  2. 整段刪除
  3. __init__ 拿掉 project_device_mapping_service 參數
  4. self.project_device_mapping_service 屬性 + 所有引用 grep + 移除
  5. 確認 delete_by_project_id(line 1100)也要移除(刪專案 cascade,C3 應已搬到 SSP CASCADE,C4 確認清理)

DI 改動

  • di_containers/project/project_containers.py — 移除 project_device_mapping_service wiring

Commit messagerefactor(oscal-project): C4 移除 start_oscal_project Step C device/audit_systems 寫入邏輯


T4 — GrcProjectService.update_project 移除 Section 3 + 5(BE)

檔案app/grc/service/project_service.py

動作

  1. 找 Section 3(line 367+ 稽核系統替換)+ Section 5(line 432-444 設備替換)
  2. 整段刪除
  3. __init__ 拿掉 project_device_mapping_service + project_information_system_domain_service 參數
  4. self._device_mapping_service + self._pis_domain_service 屬性 + 所有引用 grep + 移除
  5. 注意 line 264 dto.audit_systems 組裝邏輯由 C3 改 derived,C4 不動 (Response 不變)

DI 改動

  • di_containers/grc/grc_containers.py — 移除兩個 service wiring

Commit messagerefactor(grc-project): C4 移除 update_project audit_systems + devices 替換邏輯


T5 — 補強 ParticipantInputSchema role validation(BE,optional)

檔案api/project/serializers/project.py:19

改動前

class ParticipantInputSchema(Schema):
    user_uid = fields.String(required=True)
    role = fields.String(load_default="viewer")

改動後

from marshmallow import validate

class ParticipantInputSchema(Schema):
    user_uid = fields.String(required=True)
    role = fields.String(
        load_default="viewer",
        validate=validate.OneOf(["manager", "reviewer", "auditor", "viewer"]),
    )

Commit messagetweak(project-api): C4 ParticipantInputSchema.role 加 OneOf validation


T6 — Tests 更新(BE)

可能受影響的測試

grep -rln "audit_systems\|devices=\|StartProjectDTO" tests/ | head

預期受影響:

  • tests/test_oscal_project_service.py
  • tests/test_grc_project_service.py
  • tests/test_project_routes.py
  • 任何 fixture 內帶 audit_systems / devices 的測試 case

動作

  • 修 fixture 拿掉這兩個 field
  • 修 assertion 不再 expect device / audit_system 被寫入
  • 加新 test case:建專案後 dto.devices == [] dto.audit_systems == [](依賴 C3 改 derive)

Commit messagetest(project): C4 移除 audit_systems / devices 相關 test fixture + 加 derived empty case


T7 — Changelog

檔案docs/changelog/YYYY-MM-DD-refactor-project-crud-drop-scope.md

---
type: tweak
breaking: true
modules: [project, grc]
---

## 需求說明
C4:拿掉專案建立/編輯 API 內的 audit_systems / devices 欄位。
受評範圍改在 SSP 編輯頁維護(per-AP SSP),見 docs/features/FR-011.3-2605-ssp-edit-in-project/

## 變更範圍
### BE
- `api/project/serializers/project.py`:OscalProjectStartRequest 拿掉 audit_systems + devices
- `api/grc/serializers/project.py`:ProjectUpdateRequestSchema 拿掉 audit_systems + devices
- `app/project/service/oscal_project_service.py`:start_oscal_project Step C 移除
- `app/grc/service/project_service.py`:update_project Section 3+5 移除
- DI containers:拿掉 project_device_mapping_service / project_information_system_domain_service 注入

## 行為差異
| 情境 | 改前 | 改後 |
|------|------|------|
| POST /oscal-project/start 含 devices | 寫入 project_device_mapping | unknown=EXCLUDE 忽略,建專案成功但不寫入 |
| PUT /grc/project/<uid> 含 audit_systems | 替換 mappings | EXCLUDE 忽略 |
| GET /grc/project/<uid> | devices/audit_systems from project tables | 由 C3 改 derived from SSP(C4 不變動 response)|

## Breaking change 警示
- FE 必須同步 deploy 更新表單(C6)
- 既有用 audit_systems / devices field 的 caller 改用 SSP-scoped endpoints(C2)

§3

測試規格

測試 內容
Unit OscalProjectStartRequest 不接受 audit_systems / devices(unknown=EXCLUDE 忽略)
Unit start_oscal_project 不再呼叫 project_device_mapping_service
Unit update_project 不再呼叫 device / pis service
Unit DI container resolve OscalProjectService 不需要 project_device_mapping_service
Integration E2E:POST /oscal-project/start 不含 devices → 建專案 OK + Response devices=[]
Integration E2E:建專案後 → 進 SSP tab 加 device → 再 GET 專案 → Response devices 從 SSP derive 出來

§4

完成標準(DoD)


§5

風險 / Rollback

風險 緩解
FE 沒同步 deploy 仍送 audit_systems / devices unknown=EXCLUDE 容錯 + log warn
既有 caller (gh stat / dashboard) 依賴 Response 內 audit_systems C3 已改 derived,Response shape 不變,caller 透明
DI 解依賴後其他 service 還有引用 pre-flight grep -rn project_device_mapping_service 找全

Rollback:純程式碼改動,git revert 即可。但若已 deploy 且 FE 表單同步更新後,rollback 會撞 FE 不送 fields 但 BE 不接受新 schema 的 mismatch。建議:FE deploy 前先 deploy BE。