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_sspmode='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 編輯),與本匯入缺口無關。