# FR-038 — SSP 匯入 V1↔V2 能力缺口（gap 分析 + 專屬 session 接手依據）

> 狀態 **2026-06-15**。本檔盤點「V1（舊 jedi_oscal，已出貨的 SSP Update Diff / FR-011.2）」與
> 「V2（jedi_oscal_v2，FR-038 遷移後現況）」在 **SSP 匯入** 上的差距。結論：V2 目前只接了
> 「首次（空殼）建立匯入」的骨架，V1 真正值錢的「差異更新 / 專案落點 / 人員比對 / 手動指派」
> 整層**還沒搬**（V1 程式碼躺在 repo 但被 disable）。缺口夠大，建議**獨立一個 session 處理**。

---

## 0. 為什麼會缺這塊（背景）

FR-038 把 BE 從舊 `jedi_oscal`(V1) 打掉重練成 `jedi_oscal_v2`(V2)。Wave 2A「地基翻轉」時，
所有依賴 V1 的 SSP 匯入業務碼被**乾淨 disable**（route 進 `config/di_modules.py:EXCLUDE_MODULES`、
service chain 仍 import V1 entity → 與 V2 撞同一 SQLAlchemy MetaData，不可 wire）。Wave 2B 的
import-ssp 子弧只重建了**首次建立匯入**（P1 套件寫入核心 + P2 Excel + P3 Docx），**P4（update-diff）
刻意 defer**。本檔即盤點 P4 + 其餘未搬能力 = V1 真正的完整匯入體驗。

---

## 1. V1 完整能力輪廓（已出貨過，現 disabled）

V1 是一套「**差異驅動匯入**」系統（FR-011.2「SSP Update Diff」，2026-05-07 出貨）：

| 維度 | V1 能力 |
|------|---------|
| **格式** | docx **和** excel（各自獨立 route + app service） |
| **建立 + 更新** | 兩者皆有。重複匯入時跑 side-by-side 差異比對（7 類子物件算 unchanged/changed/added/gone）+ smart default action + 逐項決定（per-control + per-AO 雙層）再寫 |
| **差異比對來源** | parsed（匯入檔解析）vs current（既有 SSP/MF 快照），per-field 比對 |
| **落點（2 種）** | 合規資源庫範本 SSP（`source_type='module_frame'`）**和** 專案 SSP（`source_type='ssp'`）|
| **人員自動比對** | person/organization 三層遞進（exact → normalized → fuzzy + 已選標籤），配到系統 `user` / `org_unit`，回填 matched_user_id / matched_org_unit_id |
| **未匹配段落手動指派** | docx 解析提出 unmatched_paragraphs，confirm 時可手動指派到控制項 / AO |
| **寫入子物件（8 類）** | system-characteristics、parties、components、leveraged-authorizations、inventory-items、control-implementation、by-component、control-objectives(AO statements) |
| **多框架** | adapter registry（CMMC / ISO …），`DocxParserCore` + `CmmcSspAdapter` 可擴充 |

### V1 程式碼座標（disabled，可供 port 參照）
| 檔案 | 角色 |
|------|------|
| `app/oscal/service/ssp_docx_diff_service.py` | **差異計算 + 決策註解核心**（7 類 diff_status、smart default、annotate_parse_result）|
| `domain/oscal/service/party_reconciliation_service.py` + `domain/oscal/service/reconciliation/{base,person_reconciler,organization_reconciler}.py` | **人員/組織三層比對** |
| `domain/oscal/strategy/ssp_write_strategy.py` | 專案 SSP 落點寫入（`source_type='ssp'`）|
| `domain/oscal/strategy/module_frame_write_strategy.py` | 資源庫範本 SSP 落點寫入（`source_type='module_frame'`）|
| `domain/oscal/strategy/i_ssp_docx_write_strategy.py` | 寫入 strategy 介面 |
| `domain/oscal/parser/ssp_intermediate.py` | ParsedParty / ParsedComponent / ParsedLeveragedAuthorization … 中介資料模型 |
| `domain/oscal/adapter/cmmc_ssp_adapter.py` | CMMC docx adapter |
| `api/oscal/routes/ssp/ssp_docx_import_route.py` / `ssp_scoped_excel_import_route.py` | V1 route（部分已被 V2 P2/P3 取代，scoped excel 仍 dark）|

---

## 2. V2 現況（P1~P3，已 ship）

| 維度 | V2 現況 |
|------|---------|
| **格式** | docx + excel 皆可 |
| **建立 / 更新** | **只有建立（create-only）**。套件 `OscalIoService.import_ssp` 僅支援 `mode='create'`，且目標 SSP 必須**空殼**（有 system-characteristics / system-implementation / control-implementation 任一 → 丟 ValueError → 412）。**無 diff、無更新** |
| **落點** | **只有合規資源庫**。excel `_VALID_SOURCE_TYPES={"framework_version","module_frame"}`、docx `={"module_frame"}`；**專案 SSP（`'ssp'`）不收** |
| **人員比對** | ⚠️ 待 verify（create 路徑是否保留任何 reconciliation；docx adapter 可能有部分 enrich）|
| **手動指派** | ❌ 無 |
| **逐項決策** | ❌ confirm 只把 parsed 結果直寫（create），無 per-item accept/reject |
| **寫入子物件** | `import_ssp` 核心 4 類：metadata roles/parties、system-characteristics、system-implementation/components、control-implementation/IR（SoA props）。**statements / by-components / leveraged / inventory 不在核心**（另有 FR-032 v2-bundle 路徑）|

### V2 程式碼座標
| 檔案 | 角色 |
|------|------|
| `jedi-oscal-v2 .../app/service/io/oscal_io_service.py:247-320` | `import_ssp`（create-only guard 在 289-298、mode 守門 278）|
| `app/oscal/service/ssp_excel_import_app_service.py` | Excel 匯入 confirm（Model A/B，create）|
| `app/oscal/service/ssp_docx_import_app_service.py` | Docx 匯入 confirm（`_VALID_SOURCE_TYPES`）|
| `app/oscal/service/import_adapter/{excel_to_oscal_ssp,docx_to_oscal_ssp}.py` | parsed → OSCAL ssp dict adapter |

---

## 3. 差距總表（V1 有、V2 P1~P3 還沒有）

| # | 能力 | V1 | V2 現況 | 缺口性質 |
|---|------|----|---------|---------|
| 1 | **既有 SSP 重複匯入 + diff 更新** | ✅ side-by-side + 逐項決定 | ❌ create-only，非空 412 | **最大**。需套件 `import_ssp` merge/diff mode + 主專案 diff service 重接 |
| 2 | **差異預覽後端**（7 類 diff_status / smart default） | ✅ `ssp_docx_diff_service` | ❌ disabled | 主專案 port（current 端改讀 v2 snapshot）|
| 3 | **專案 SSP 落點**（`source_type='ssp'`） | ✅ `ssp_write_strategy` | ❌ 不收 | 需 create+update 都支援專案 SSP（且專案 SSP 恆非空 → 倚賴 #1）|
| 4 | **人員/組織三層自動比對** | ✅ reconciliation/ | ⚠️ 待 verify，多半缺 | 主專案 port（reconciler 改吃 v2 user/org + party）|
| 5 | **未匹配段落手動指派** | ✅ manual_assignment | ❌ | 主專案 port |
| 6 | **per-control + per-AO 細粒度決策** | ✅ 雙層 | ⚠️ 待 verify，多半缺 | 跟 #2 一起 |
| 7 | 寫入子物件涵蓋 | 8 類 | 核心 4 類 | statements/by-components/LA/inventory 補齊（部分有 FR-032 路徑）|

---

## 4. 補齊要做什麼（兩層）

**A. 套件層（jedi_oscal_v2）— 即 import-ssp 設計的 P4：**
- `OscalIoService.import_ssp` 加 `mode='update'`（或 diff/overwrite）：對非空 SSP 不再丟錯，改逐子物件 upsert。
- 加 `build_ssp_snapshot(ssp_id)`（current 端中介快照，給 diff 比對用）。

**B. 主專案層（把 V1 disabled 那套重接 V2）：**
- `ssp_docx_diff_service`：current 端從讀 V1 entity 改讀 V2 snapshot；移植 7 類 diff 標注 + smart default。
- `reconciliation/`：person/org reconciler 改吃 v2 party + 現行 user/org_unit domain service。
- `ssp_write_strategy`（專案落點）/ `module_frame_write_strategy`（資源庫落點）：改走 v2 SspService 子物件 CRUD（不再 import V1 entity）。
- re-enable route：`ssp_scoped_excel_import_route`（專案 excel）+ docx 的 `'ssp'` source_type；confirm 改走 diff/decision 流程。

> 規模參考：接近 FR-011.2「SSP Update Diff」當初的工作量（跨套件 + 主專案 diff/reconcile/write
> strategy + FE 決策 UI）。**因此建議獨立一個 session（或子弧）處理，不要塞進其他收尾。**

---

## 5. 接手 session 的 pre-flight（開工前必驗，勿憑本檔假設）
1. V2 `import_ssp` 現況再讀一次（mode/guard 是否已被別棒動過）。
2. **verify #4/#6**：跑一次 V2 docx/excel confirm（Model A，新資源庫），看 parsed→寫入是否真的零 reconciliation / 零逐項決策，釘死缺口邊界。
3. V1 disabled 程式碼是否仍可當參照（`git log` 確認沒被刪；它們 import V1，**不可直接 wire 進 boot**，只能 port）。
4. 決定落點優先序：先補「資源庫重複匯入 + diff」（#1/#2，多數客戶用）還是「專案 SSP 匯入」（#3，倚賴 #1）。
5. 套件異動需 user 明示（path-dep dev + 發版規範）。

---

## 6. 範圍邊界 / 待 user 決策
- 這塊要掛 **FR-038 P4+** 還是另開新 FR？（規模接近獨立 feature；本檔暫掛 FR-038 import-ssp 線）
- 人員比對是否仍要三層 fuzzy，還是簡化（產品是否還需要 fuzzy 配對）？
- 專案 SSP 匯入是否真的要（vs 維護 tab + SoA 匯入已覆蓋大部分）—— 可能只補「資源庫重複匯入 + diff」即足夠，專案落點視需求再評。

---

## 7. 不在本缺口（已由別棒覆蓋，別混淆）
- **SoA 控制實作 Excel 匯入**（`/ssp/<uid>/control-implementations/import`）— 已 v2 化、是 upsert（對非空 SSP 能用），跟本匯入是不同功能。
- **OSCAL JSON/XML/YAML 匯出**（`/oscal/export/...`）+ **SSP docx/pdf 匯出**（`/ssp/<uid>/export`）— 已 v2 化（Wave2 收尾）。
- **框架維護 v1 route 群（2a/2b）** — 另一塊待辦（framework version 管理 / catalog 編輯），與本匯入缺口無關。
