2026-05-22 Session 2 完成後建立。貼以下 ``` 框內容開啟 Session 3。
你正在接手 SSP 在專案內直接編輯 — Phase C Session 3。
## Session 2 完成狀況(已 ship)
### C2 — SSP-scoped endpoints 補齊(19 endpoint 全數 shipped)
| Batch | Commit 範圍 | Endpoint 數 | Changelog |
|-------|------------|------------|-----------|
| C2.1 | `603eabe..b0047cf` | 11 | docs/changelog/2026-05-22-feat-ssp-project-endpoints-c2-1.md |
| C2.2 | `fa8f8dc..df87826` | 8 | docs/changelog/2026-05-22-feat-ssp-project-endpoints-c2-2.md |
完整 commit 序列:
- `603eabe` T1: SspProjectResolver + SspPermissionChecker + 3 個 error code
- `a51883e` T2.1: PartyContextService 抽 base
- `8435f22` T2.2: SspResourcesContextService 抽 base
- `b9ac23c` T3: /ssp/<uid>/parties 4 endpoint
- `4ad2f2f` T4: /ssp/<uid>/ssp-resources 4 endpoint
- `d8e0800` T5: /ssp/<uid>/system-characteristic 2 endpoint
- `b0047cf` T6: /project/<uid>/current-ssp-uid helper
- `6d4d24b` C2.1 changelog + tracker
- `fa8f8dc` T10: /ssp/<uid>/leveraged 4 endpoint
- `6c72bf7` T8: excel_import 加 source_type='ssp'
- `df87826` T9: /ssp/<uid>/excel-import/* 4 endpoint
- `0b386f9` C2.2 changelog + tracker
### 19 個新 endpoint 一覽
GET /ssp/<ssp_uid>/parties POST /ssp/<ssp_uid>/parties PUT /ssp/<ssp_uid>/parties/<party_uid> DELETE /ssp/<ssp_uid>/parties/<party_uid> GET /ssp/<ssp_uid>/ssp-resources POST /ssp/<ssp_uid>/ssp-resources/items PUT /ssp/<ssp_uid>/ssp-resources/items/<item_uid> DELETE /ssp/<ssp_uid>/ssp-resources/items/<item_uid> GET /ssp/<ssp_uid>/system-characteristic PUT /ssp/<ssp_uid>/system-characteristic GET /project/
GET /ssp/<ssp_uid>/leveraged POST /ssp/<ssp_uid>/leveraged PUT /ssp/<ssp_uid>/leveraged/<item_uid> DELETE /ssp/<ssp_uid>/leveraged/<item_uid> POST /ssp/<ssp_uid>/excel-import/upload GET /ssp/<ssp_uid>/excel-import/<parse_uid> DELETE /ssp/<ssp_uid>/excel-import/<parse_uid> POST /ssp/<ssp_uid>/excel-import/<parse_uid>/confirm
### 測試現況
- C2 新 unit test 共 104 個 全綠
- 既有 SSP excel import / excel_parser / MF / party 等 regression 全綠
- 1 個 pre-existing fail `test_write_parties_refreshes_existing_party_in_place` 與本批無關(之前就壞)
## Phase C 整體狀態
| Phase | 主題 | 狀態 |
|-------|------|------|
| C1 | OSCAL SSP 角色 master | ✅ shipped (`381d3ec`) |
| C7 | 06_外部利用服務 sheet | ✅ shipped (`e7f29e3`) |
| C8 | role label 清理 | ✅ shipped (`be1d286`) |
| **C2** | **SSP-scoped endpoints** | **✅ shipped (本期完成)** |
| **C3** | **受評範圍歸屬遷移**(**重型**) | **spec-ready,下一步** |
| C4 | 專案 CRUD 改造(拿掉 audit_systems / devices) | spec-ready,依賴 C3 |
| C5 | FE SSP tab | spec-ready,C2 已就緒可開工 |
| C6 | 移除專案設定 UI 受評範圍 | spec-ready |
## 本 Session 目標(請 user 確認方向)
候選方向(任選或合併):
### 方向 A:開始 C3(重型 refactor)
C3 範圍:廢除 `project_device_mapping` / `project_information_systems`,受評範圍改 derived from SSP items。
開工前必做:
- 讀 `docs/features/FR-011.3-2605-ssp-edit-in-project/design-C3.md`(兩階段 PR1 + PR2)
- 讀 `docs/features/FR-011.3-2605-ssp-edit-in-project/implementation-plan-C3.md`
- 注意:C3 不可獨立 ship — 必須跟 C4 同 PR / 同 release,否則前端會撞空 schema(tracker 反斷層機制 §4)
### 方向 B:開始 C5(FE SSP tab)
C5 可獨立開工,純前端:
- 切到 FE repo:`~/Projects/Billows/Audit-Manager/compliance-manager-fe/`
- 讀 `docs/features/FR-011.3-2605-ssp-edit-in-project/design-C5.md` + `implementation-plan-C5.md`
- 依賴 C2 已 ship 的 19 個 endpoint
### 方向 C:清理 C2 延後項
兩個延後 follow-up:
1. **multi-round SSP `get_one` bug 修補**(既有 issue,非 C2 引入)
- 位置:`app/oscal/service/ssp_excel_import_app_service.py:_resolve_existing_ssp_shell()`
- 問題:用 `get_one(SystemSecurityPlanQueryEntity(profile_id=...))` 在 SSP versioning 多輪場景會撞 multiple match
- 影響:MF-scoped excel update flow 在多輪 SSP 場景失準
- SSP-scoped 路徑(C2.2 T8 ship)已避開此 bug
2. **`PUT /ssp/<uid>/excel-import/<parse_uid>/decisions` partial-save endpoint**
- design-C2.md §4.5 提的第 5 個 excel-import endpoint
- 目前走「decisions 隨 confirm body 一次性傳入」已能滿足 day-1
- 需要:app service 新 `save_decisions` method + parse_job 狀態擴充
## 重要規範變更(**Session 2 期間新增**)
CLAUDE.md 加了**禁止自行切換 branch**:
> 任何 git checkout <branch> / git switch 操作一律不執行,永遠在使用者當下的 current branch 工作。
Session 2 期間發生過一次工作目錄意外切到 phase2 branch 的事件(疑似外部觸發),用 git stash / pop 把 T6 改動搬回 phase3 才順利完成。若再次發生,**不要自己 checkout**,停下問 user。
## 環境 / 規範速查
### Branch
`feature/ssp-import-export-phase3`(目前所在;**Session 3 不要切換**)
### Working tree dirt(與本批無關)
- `pyproject.toml` — dev-only jedi-oscal path dep(按規範不該 commit)
- `docs/conversation-history/2026-05-22/ssp-import-export-phase2/part-*.md` — IDE 開檔導致的非語義變化
- `docs/features/ssp-import-export-phase2.zip` — untracked 從早期 session 留下
### DB 連線
PGPASSWORD='jedi@123!' psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev
### Dev 帳號
- manual:blsadmin / Billows@123!
- pytest:blsit / Billows@123!
### BE log
`log/app.log`(專案根目錄)
### 必讀文件(接手前掃一遍)
1. CLAUDE.md(會自動載入 — 注意新增的「禁止自行切換 branch」規範)
2. `docs/features/FR-011.3-2605-ssp-edit-in-project/README.md`(tracker — 必看 C3 / C5 行的狀態)
3. `docs/features/FR-011.3-2605-ssp-edit-in-project/design-C3.md`(若走方向 A)
4. `docs/features/FR-011.3-2605-ssp-edit-in-project/design-C5.md`(若走方向 B)
## 開工建議流程
1. **先問 user 方向 A / B / C**(不要自己決定 C3 vs C5)
2. 跑 pre-flight:
```bash
# 確認 C2 endpoints 都 register OK
grep -rn "ssp_uid.*string" api/oscal/__init__.py api/grc/__init__.py | wc -l
# 預期:12+(C2.1 11 + C2.2 8 個 SSP-scoped url + current-ssp-uid 在 grc)
# 確認 19 個新 endpoint 都正常 import
python -c "from api.oscal import create_module; create_module()"
接下來的 phase(C3 / C4 / C5)若有 SSP 權限 / project 反查需求,可直接用:
domain/oscal/service/ssp_project_resolver.py
SspProjectResolver.resolve(ssp_uid) → SspProjectContext(ssp, ap, project)NON_EDITABLE_AP_STATUSES = (CLOSED, ARCHIVED)common/middleware/permission/ssp_permission.py
SspPermissionChecker.require_participant(ssp_uid) → 任一 participant 通過SspPermissionChecker.require_manager(ssp_uid) → manager + AP 可編輯app/oscal/service/party_context_service.py — OSCAL party CRUD 接 (context_type, context_id)app/oscal/service/ssp_resources_context_service.py — devices / info_systems CRUD 接 ssp_idapp/oscal/service/ssp_leveraged_context_service.py — leveraged-authorization CRUD 接 ssp_id新 error code(C2 期間加的):
GRC_NOT_PROJECT_MANAGER = GRC_403051GRC_INVALID_OSCAL_ROLE = GRC_400076GRC_SSP_EDIT_AP_NOT_ACTIVE = GRC_412028
---
## 補充:Session 2 期間關鍵決策
寫進 handoff 但 Session 3 可選擇性回顧:
1. <strong>Error code 序號全新編</strong>:plan 提的序號(404010 / 412001 / 400015 / 403005 / 404011 / 404012)皆已被佔 — 改用 404012 等既有 + 新編 403051 / 400076 / 412028
2. <strong>party `role` 維持單值</strong>:與既有 MF API 對等;design 提的 `role_ids[]` 多值延後
3. <strong>`current-ssp-uid` URL 用 `/project/<uid>/` 單數</strong>:沿用 grc routes 既有命名(plan 提 `/projects/<...>/`)
4. <strong>AP 可編輯定義放寬</strong>:`status not in (CLOSED, ARCHIVED)`,比 design 的 `status == 'active'` 寬鬆
5. <strong>C2.2 multi-round bug 隔離</strong>:既有 `_resolve_existing_ssp_shell(mf_uid)` 在多輪 SSP 用 `get_one(profile_id=...)` 撞 multiple match — SSP-scoped 路徑直接用 ssp_uid 避開
6. <strong>leveraged 拆獨立 service</strong>:共用 table 但欄位語意不同(party_uuid + date_authorized + purpose),拆 `SspLeveragedContextService`
7. <strong>`PUT /<parse_uid>/decisions` partial-save 延後</strong>:design 提但目前 service 走「decisions 隨 confirm body 一次性傳入」夠用
8. <strong>借用 `ModuleFrameWriteStrategy` 內部 helpers</strong>:`_upsert_party` / `_upsert_responsible_party` 不綁定 context_type,可跨 context 重用 — SSP path 借來避免複製 50 行 upsert 邏輯