給下個 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。
| 項目 | 值 |
|---|---|
| 本棒目標 | 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 明示 |
產品需求:受評公司 / 顧問把既有的 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。結果:
import_ssp create-only guard 直接 412)。← 本棒驅動目標 = 解這個目標:把「差異更新匯入」接回 v2,讓專案 SSP(及資源庫重複匯入)都能用檔案更新。
冷接自檢 4 問(答不出回 §0 讀序):① 為什麼專案 SSP 現在一匯入就 412、根因在套件哪個 guard?② V1 的「差異更新」體驗包含哪幾件事(差異比對 / 決策 / 人員比對 / 手動指派)、各自哪個檔?③ 補齊要分「套件層」和「主專案層」各做什麼?④ 為什麼說 piece 3(專案匯入)和「資源庫重複匯入」是同一道牆?
import-ssp-v1-v2-gap.md 全文(V1 完整能力 + V2 現況 + 差距總表 + 補齊兩層 + V1/V2 程式碼座標)—— 這是本棒的主參照import-ssp-design.md §(P1~P4 定義、Model A/B、資料流)+ api-contract §2(import-ssp 端點)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.pyapp/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_*OscalIoService.import_ssp 加 mode='update'(或 diff/overwrite):非空 SSP 不再丟錯,改逐子物件 upsert(control-impl / parties / components / characteristics …)。build_ssp_snapshot(ssp_id):把現有 v2 SSP 組成「current 中介快照」(形狀對齊 export / import 的 OSCAL dict)給 diff 比對用。可借用主專案 ssp_v2_content_loader 的讀法或在套件內實作。ssp_docx_diff_service:current 端從讀 V1 entity 改讀 v2 snapshot(A.2);移植 7 類 diff 標注(unchanged/changed/added/gone)+ smart default action。reconciliation/(person/org reconciler):改吃 v2 party + 現行 user_domain_service / org_unit_domain_service,三層比對(exact/normalized/fuzzy + 已選標籤)回填 matched_user_id / matched_org_unit_id。ssp_write_strategy(專案落點,piece 3 重點)/ module_frame_write_strategy(資源庫落點):改走 v2 SspService 子物件 CRUD(拔光 V1 entity import),依 decisions 寫入。ssp_scoped_excel_import_route(/ssp/<uid>/excel-import/*,source_type='ssp')+ docx import 的 'ssp' source_type;EXCLUDE 移除 + create_module 註冊。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 # 97import_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)。import_ssp(target=1482, mode='create'))→ 應撞 "target SSP already has a body"。ls app/oscal/service/ssp_docx_diff_service.py domain/oscal/service/reconciliation/ domain/oscal/strategy/。feature/oscal-refactor)。import_ssp merge mode)→ 開工前 plan 列改動範圍 + 影響其他 consumer,等 user 點頭;dev 走 poetry path-dep,發 Nexus 等整 feature 完 + user 明示;pyproject.toml path-dep 勿 commit。-am、各 repo 分開);push / 收尾等 user 明示。jedi_oscal import 進 boot graph(撞 v2 MetaData 炸)—— V1 程式碼只能 port、改吃 v2,不可直接 wire。18826814、gap 文件 40802d4e)的 push。