# 🟢 START HERE — FR-038 import-ssp 復原：P3 專案 SSP 匯入（驅動目標）+ V1 差異更新整層接 v2

> **給下個 session 的 prompt**：「讀 `docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-15-import-ssp-restore-P3-project-import-START-HERE-handoff.md`，先過「🧭 WHY」+ §0 讀序硬 gate（**gap 文件 `import-ssp-v1-v2-gap.md` 全讀**），能答冷接自檢 4 問再碰 code。跑 §6 pre-flight + §7 verify 釘死缺口邊界，接手把 SSP 檔案匯入從『首次空殼 create』補成『可對既有 SSP 差異更新』，驅動目標 = 讓**專案 SSP（piece 3）**能匯入。」
> 本檔自包含；細節盤點在同資料夾 [`import-ssp-v1-v2-gap.md`](../import-ssp-v1-v2-gap.md)（**§0 必讀**）。狀態 **2026-06-15**。

## 🔖 交接現況（換 session 當下）
| 項目 | 值 |
|------|----|
| **本棒目標** | **P3 = 專案 SSP 檔案匯入**（user 2026-06-15 指定為下個 session 首要）。它與「資源庫重複匯入」卡**同一道牆**：套件 `import_ssp` 只有 create-only、無 merge/diff（= import-ssp 設計的 **P4**）。故本棒 = 補 P4 merge mode + 把 V1 差異更新/比對/寫入整層接 v2 |
| 主專案 branch / HEAD | `feature/oscal-refactor` / 本機含 `18826814`(SSP 匯出) + `40802d4e`(gap 文件)，**未 push**（等 user）|
| 主專案 working tree | 乾淨，只有 `M pyproject.toml`（jedi-oscal-v2 dev path-dep，勿 commit）|
| 套件 branch | `~/Projects/Jedicogy/module/jedi-python-package` `feature/oscal-refactor`；**本棒會動套件**（`import_ssp` 加 merge mode）→ 開工前在 plan 列改動範圍、等 user 點頭（套件異動規範）|
| 跑得起來嗎 | `create_app()` BOOT OK on v2；pytest baseline **47 failed + 50 errors**（零新回歸基準）|
| push / 收尾 / 套件發版 | 全等 user 明示 |

---

## 🧭 WHY：這件事原始需求是什麼（先懂才准碰 code）

**產品需求**：受評公司 / 顧問把既有的 SSP（系統安全計畫書）用 **docx / excel** 匯入系統，且**能對已存在的 SSP 做差異比對後更新**（不是每次砍掉重建）——這是 V1 已出貨的「SSP Update Diff」(FR-011.2, 2026-05-07) 核心體驗：上傳 → 看 parsed vs 現有的逐項差異 → 決定接受/保留 → 寫入；匯入的人員自動比對到系統 user/org。

**FR-038 遷移把這層弄丟了**：V2 只接了「首次空殼 create」（P1 套件核心 + P2 Excel + P3 Docx），**差異更新（P4）刻意 defer**，且 V1 的 diff service / reconciliation / write strategy 全 disable 躺在 repo。結果：
- **專案 SSP 完全不能匯入**（living SSP 經 B2 clone 恆非空 → `import_ssp` create-only guard 直接 412）。← **本棒驅動目標 = 解這個**
- 連**資源庫重複匯入**也不行（第二次匯入範本 SSP 已非空 → 412）。

**目標**：把「差異更新匯入」接回 v2，讓專案 SSP（及資源庫重複匯入）都能用檔案更新。

> **冷接自檢 4 問**（答不出回 §0 讀序）：① 為什麼專案 SSP 現在一匯入就 412、根因在套件哪個 guard？② V1 的「差異更新」體驗包含哪幾件事（差異比對 / 決策 / 人員比對 / 手動指派）、各自哪個檔？③ 補齊要分「套件層」和「主專案層」各做什麼？④ 為什麼說 piece 3（專案匯入）和「資源庫重複匯入」是同一道牆？

---

## §0 接手讀序（按序，1~2 是硬 gate）
1. 本檔「🧭」+ 通讀
2. 🔒 gate：[`import-ssp-v1-v2-gap.md`](../import-ssp-v1-v2-gap.md) **全文**（V1 完整能力 + V2 現況 + 差距總表 + 補齊兩層 + V1/V2 程式碼座標）—— 這是本棒的主參照
3. 🔒 gate：[`import-ssp-design.md`](../import-ssp-design.md) §（P1~P4 定義、Model A/B、資料流）+ api-contract §2（import-ssp 端點）
4. V1 disabled 程式碼（**port 來源，不可直接 wire，會撞 v1/v2 MetaData**）：`app/oscal/service/ssp_docx_diff_service.py`、`domain/oscal/service/reconciliation/`、`domain/oscal/strategy/{ssp,module_frame}_write_strategy.py`、`domain/oscal/parser/ssp_intermediate.py`
5. V2 現成範本（已 v2 化、可抄 pattern）：`app/oscal/service/ssp_control_impl_import_service.py`（SoA 匯入，upsert 對非空 SSP 能用）、`app/oscal/service/export/ssp_v2_content_loader.py`（v2 讀 SSP 全子物件 = diff 的 current 端可借）、套件 `OscalIoService.import_ssp` / `_import_*`

---

## §3 修法計畫（方法，開工前 pre-flight 驗）

### A. 套件層（jedi_oscal_v2）= P4 merge mode 〔動套件，先問 user〕
1. `OscalIoService.import_ssp` 加 `mode='update'`（或 diff/overwrite）：非空 SSP 不再丟錯，改逐子物件 upsert（control-impl / parties / components / characteristics …）。
2. 加 `build_ssp_snapshot(ssp_id)`：把現有 v2 SSP 組成「current 中介快照」（形狀對齊 export / import 的 OSCAL dict）給 diff 比對用。**可借用主專案 `ssp_v2_content_loader` 的讀法**或在套件內實作。
3. 套件單元測：update mode 對非空 SSP 各子樹 upsert + round-trip。

### B. 主專案層（把 V1 差異更新整層接 v2）
1. `ssp_docx_diff_service`：current 端從讀 V1 entity 改讀 v2 snapshot（A.2）；移植 7 類 diff 標注（unchanged/changed/added/gone）+ smart default action。
2. `reconciliation/`（person/org reconciler）：改吃 v2 party + 現行 `user_domain_service` / `org_unit_domain_service`，三層比對（exact/normalized/fuzzy + 已選標籤）回填 matched_user_id / matched_org_unit_id。
3. `ssp_write_strategy`（**專案落點，piece 3 重點**）/ `module_frame_write_strategy`（資源庫落點）：改走 v2 `SspService` 子物件 CRUD（**拔光 V1 entity import**），依 decisions 寫入。
4. confirm 流程：parse → diff（vs snapshot）→ preview 逐項 diff_status → confirm 依 decision 走 update mode 寫入。
5. re-enable route：`ssp_scoped_excel_import_route`（`/ssp/<uid>/excel-import/*`，source_type='ssp'）+ docx import 的 `'ssp'` source_type；EXCLUDE 移除 + create_module 註冊。

### 順位建議
- **先做 A（套件 merge mode）+ 最小 B（write strategy 接 v2 + 不帶 diff 的 update）讓專案 SSP 能匯入更新** → 再疊 diff 預覽 + reconciliation + 手動指派。每段 BOOT + real-DB smoke（用專案 266 / living_ssp_id 1482）+ baseline 零回歸 + 顯式 git add commit。
- 落點優先序（gap §5.4-4）：本棒 user 指定 **P3 專案 SSP 匯入優先**；資源庫重複匯入是 A 完成後的 by-product，順帶驗。

---

## §6 Pre-flight（必跑）
```bash
cd /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be
git branch --show-current                 # feature/oscal-refactor
git status --short                         # 僅 ' M pyproject.toml'
( cd ~/Projects/Jedicogy/module/jedi-python-package && git log --oneline -1 )
set -a; source .env 2>/dev/null; set +a
export GITLAB_API_VERSION=4 GITLAB_URL=http://localhost GITLAB_PRIVATE_TOKEN=dummy GITHUB_PRIVATE_TOKEN=dummy
poetry run python -c "from dotenv import load_dotenv; load_dotenv(); import eventlet; eventlet.monkey_patch(all=False, socket=True); import sys; sys.setrecursionlimit(5000); from core.app_factory import create_app; create_app(); print('BOOT OK')"
poetry run python -m pytest test/ -q -p no:cacheprovider --continue-on-collection-errors 2>&1 | grep -E '^(FAILED|ERROR)' | sort > /tmp/ssprestore_base.txt; wc -l < /tmp/ssprestore_base.txt   # 97
```

## §7 Verify 缺口邊界（必跑，釘死再開工 — 勿憑 gap 文件假設）
1. 套件 `import_ssp` guard 現況：`grep -n "mode\|already has a body\|get_by_ssp" ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/jedi_oscal_v2/app/service/io/oscal_io_service.py`（確認仍 create-only、guard 在 sys_char/sys_impl/control_impl）。
2. **verify V2 現況有沒有殘留 reconciliation / 逐項決策**（gap §5 標「待 verify」）：跑一次 Model A docx/excel confirm（新資源庫），看 parsed→寫入是否真的零比對、零 decision，釘死「缺多少」。
3. 專案 SSP 匯入確認真的 412：對 ssp 1482 跑現行 confirm（source_type 想辦法塞 'ssp' 或直接呼 `import_ssp(target=1482, mode='create')`）→ 應撞 "target SSP already has a body"。
4. V1 disabled 程式碼仍在（port 來源）：`ls app/oscal/service/ssp_docx_diff_service.py domain/oscal/service/reconciliation/ domain/oscal/strategy/`。

---

## §8 行為規範重要提醒
- **不切 branch**（兩 repo 都在 `feature/oscal-refactor`）。
- **本棒會動套件**（`import_ssp` merge mode）→ 開工前 plan 列改動範圍 + 影響其他 consumer，**等 user 點頭**；dev 走 poetry path-dep，**發 Nexus 等整 feature 完 + user 明示**；`pyproject.toml` path-dep 勿 commit。
- **可自行階段性 commit**（顯式 git add、禁 `-am`、各 repo 分開）；**push / 收尾等 user 明示**。
- **絕不把 V1 `jedi_oscal` import 進 boot graph**（撞 v2 MetaData 炸）—— V1 程式碼只能 port、改吃 v2，不可直接 wire。
- 改 BE 後提醒 user 重啟；服務 user 自己起。
- plan 假設先 verify（§7）；不晶晶體。

---

## §9 不在本棒 scope（別順手做）
- **框架維護 2a/2b**（framework version 管理 / catalog-tree / AO / parse-job / catalog live-edit）—— 另一塊待辦，與 import-ssp 無關。
- **legacy dark route 清理**（AP task / AR / profile 等已被 B4/B5/B1 取代的 v1 殘骸）。
- **Wave 2 整弧收尾**（changelog / SUMMARY / memory / Notion）+ 套件發版 —— 等 user 下「收尾」。
- 本棒 commits（含 SSP 匯出 `18826814`、gap 文件 `40802d4e`）的 push。

---

## §10 待 user 決策（gap §6）
- 這塊掛 **FR-038 P4+** 還是另開新 FR？（規模接近 FR-011.2 獨立 feature）
- 人員比對是否仍要三層 fuzzy，或簡化？
- 落點是否兩種都要（專案 + 資源庫重複匯入），或本期只先解專案 SSP？
