🟢 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§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 全文(V1 完整能力 + V2 現況 + 差距總表 + 補齊兩層 + V1/V2 程式碼座標)—— 這是本棒的主參照
  3. 🔒 gate: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.pydomain/oscal/service/reconciliation/domain/oscal/strategy/{ssp,module_frame}_write_strategy.pydomain/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_sspmode='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(必跑)

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?