給下個 session 的 prompt:「讀
docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-15-import-ssp-P2-START-HERE-handoff.md,先過「🧭 WHY」+ §0 讀序硬 gate 懂 import-ssp 要解決什麼(designimport-ssp-design.md全讀 + api-contract §2),能答冷接自檢 4 問再碰 code。跑 §6 pre-flight + §7 verify P1,接手 P2(Excel 匯入,Model A+B 兩種 source 落 v2)。」 本檔自包含。狀態為 2026-06-15(設計定稿 + spec review 通過 + P1 套件單元測試綠 + BE BOOT/pytest 持平)。
| 項目 | 值 |
|---|---|
| 進度 | import-ssp 設計定稿(import-ssp-design.md,user 校對後補正兩模型)+ P1 套件寫入核心 DONE。下一棒 = P2(Excel 匯入打通,Model A+B) |
| 主專案 branch / HEAD | feature/oscal-refactor / 2dca0f2b |
| 主專案 working tree | 乾淨,只有 M pyproject.toml(jedi-oscal-v2 dev path-dep,照規範勿 commit) |
| 套件 branch / HEAD | ~/Projects/Jedicogy/module/jedi-python-package feature/oscal-refactor / a930bfd(P1,領先 origin 1,未 push) |
| ⚠️ 套件未 commit dev 改動(B1 留下,本 session 沒碰) | M catalog_service.py、M framework_service.py、?? tests/catalog/test_catalog_service_import.py。dev path-dep,BE 重啟即生效;發版等整 feature + user 明示。別搞丟。 |
| 跑得起來嗎 | create_app() BOOT OK;BE python -m pytest test/ = 56 failed + 50 errors(持平 baseline、零新回歸)+ 9 skipped;套件 tests/io_export/ = 10 passed(7 export + 3 import) |
| push | BE + 套件都未 push,等 user 明示 |
| **未收尾(user 還沒下收尾命令) | 本 session 的 Q1(5 增量)+ import-ssp 設計/P1 的 changelog / analysis / SUMMARY / memory / Notion 全都還沒寫**;連前面 Wave 2B(B4.2/B5/匯出/AO-fix)的收尾也還掛著。下次 user 說「收尾」時一起補。 |
一句話:合規資源庫(公版範本)原本就有「從文件/Excel 匯入 SSP」功能(新增資源庫從文件建、編輯資源庫時匯入並差異比對再更新),整套 FE + 解析 + 流程 + 差異比對都還在且 FE 已是 v2。FR-038 把 OSCAL 重寫成 v2 schema 後,這功能的寫入層(confirm 把資料存進 DB)還寫舊 v1 的表 → 被 disable。import-ssp 這個 arc = 只換寫入層,讓它存進 v2 OSCAL 的表,其餘不動。
產品情境(白話):
source_type=module_frame,import 解析該資源庫的 template_ssp_id 填/更新;再匯入時做差異比對(update-diff)。source_type=framework_version,選一個框架版本(提供 catalog)+ 上傳文件 → 建新資源庫(clone catalog→profile→空範本 SSP,= B1 既有 clone_resource_library 能力)→ 再填範本 SSP。每次都建新 = 永遠 create、不需 diff。source_type=ssp = 專案 SSP / Project Planning,範圍外。)FE 已經是 v2、不用動(證據):compliance-manager-fe/src/views/module_frame/ImportExcelPage.vue + ImportDocxPage.vue:mode='create'|'update-mf'、sourceType = isCreate ? 'framework_version' : 'module_frame'(第 95 行)、create 模式有 framework picker。路由 module-frame-import-docx-update / module-frame-import-excel-update,頁面 ssp-docx-import-v2/。FE 打的 BE 端點仍是 /ssp-excel-imports/parse+confirm、/ssp-docx-imports/parse+confirm(帶 source_type),這些端點目前 disabled。
為何被卡(v2 翻地基留下的洞):confirm 的寫入核心 domain/oscal/strategy/ssp_write_strategy.py / module_frame_write_strategy.py 還 import 舊 jedi_oscal v1 entity(ControlImplementationEntity / PartyEntity 等)→ 與 v2 撞同一 SQLAlchemy MetaData → 整條 route 被 config/di_modules.py:EXCLUDE_MODULES 擋掉。P1 已把 v2 寫入機具(OscalIoService.import_ssp)做好,P2/P3 把 confirm 改 delegate 到它、re-enable route。
P2 在大圖位置:P1(套件寫入核心)DONE → P2(Excel,Model A+B) → P3(Docx,A+B)→ P4(update-diff,Model A 再匯入)。
冷接自檢 4 問(答不出回 §0 讀序):① import-ssp 落點是哪份 SSP、為什麼是資源庫範本 SSP 不是專案 SSP?② Model A 與 Model B 差在哪、各自
source_type是什麼?③ 為什麼說「只換寫入層」——哪些重用、哪些重建?④ P1 的OscalIoService.import_ssp涵蓋哪些子樹、為什麼是 create-only?
import-ssp-design.md(全讀 — 兩模型 / source_type 分派 / 架構邊界 D-IS-1~3 / 4 phase / §9 pre-flight 假設)api-contract.md §2(resource-library import-ssp 端點)+ design.md §4.2(OscalIoService)/ §4.4(套件邊界)~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/jedi_oscal_v2/app/service/io/oscal_io_service.py 的 import_ssp + _import_* helpers(P2 confirm 要呼叫它);測試 tests/io_export/test_oscal_import.pyapp/oscal/service/ssp_excel_import_app_service.py(confirm_import → _confirm_superset_flow(framework_version) / _confirm_update_flow(module_frame))app/oscal/service/excel_parser/ 的 ParsedExcel dataclass(types.py)本 session 跨兩個 arc:先做完 Q1(接前一棒),再開 import-ssp(設計 + P1)。
5 增量全完成 + verified(前一棒 handoff 2026-06-15-Q1-prep-jobs-START-HERE-handoff.md 的任務):
workflow_execution_control_mapping 加 ao_part_id(校正:表其實在 public 非 compliance,ORM DEFAULT_SCHEMA=None)+ seed 收證據 template。app/grc/service/ao_derivation.py(B5 + Q1 共用)。project_start_app_service 同步建 per-AO job(direct insert workflow_execution + job + mapping)。vw_user_job_queue rebind control_* + AO。2026-06-15-fr038-q1-workflow-execution-control-mapping.sql、...-q1-seed-collect-evidence-template.sql、...-q1-vw-user-job-queue-rebind.sql。import-ssp-design.md(brainstorm 收斂 + spec review 通過 + user 校對補正兩模型)。a930bfd):OscalIoService.import_ssp(oscal_ssp, *, target_ssp_id, curr_user, mode='create') —— export_oscal('ssp') 的反向,讀 OSCAL ssp dict 寫 v2 SSP 樹。
tests/io_export/test_oscal_import.py:round-trip(import 後 export 比對)+ 非空 guard + 未知 target。io_export 全套 10 passed。DEFAULT_SCHEMA=None → ORM table 不帶 schema → 走 search_path("$user", public)落在 public,不是 compliance。Q1.1 踩過(handoff 誤導查錯 schema)。查表存在性要查對 schema。template_ssp_id=1481 → 0 control_impl / 0 sys_char / 0 sys_impl / 0 parties,只有 metadata 殼)。→ P1 走 insert;首次匯入=create。Model A 再匯入才需 overwrite/diff(P4)。OscalIoService._export_ssp 不 round-trip statements/by-components/LA/inventory/users(看 _implemented_requirement_to_json / _build_system_implementation)。P1 import 鏡像此面,所以也不寫那些。P2 adapter 別硬塞 P1 不支援的子樹,會被丟掉。oscal.catalog_control_parts(name=assessment-objective)、SoA/方法落 props、SSP 12 子表 shape 改了。寫入一律走 P1 import_ssp,別自己拼 v2 entity。ssp_excel_import_route / ssp_docx_import_route / ssp_scoped_excel_import_route 在 EXCLUDE_MODULES。re-enable 前必先把 app service / confirm 路徑裡的 from jedi_oscal... v1 import + v1 write strategy(ssp_write_strategy / module_frame_write_strategy)從匯入路徑拔掉,改 delegate P1。沒拔乾淨 → wire 進去 BE boot 撞 MetaData 直接炸。ParsedExcel 轉成這個 dict 餵 import_ssp。python -m pytest;BE boot 要補 dummy GITLAB/GITHUB env(見 §6)。目標:Excel 匯入兩種 source 都落 v2,confirm 改 delegate P1 import_ssp,re-enable excel route。
方法候選(推測,待 pre-flight verify):
ParsedExcel(controls / parties / system_characteristics / components...)轉成 P1 認的 OSCAL ssp dict(kebab keys,鏡像 export)。SoA → implemented-requirement.props。
ParsedExcel 實際欄位(讀 app/oscal/service/excel_parser/types.py);control-id 是否對得上目標資源庫的 v2 catalog(對不上 → warning 不擋,記 import_warnings)。SspExcelImportAppService._confirm_superset_flow(framework_version=Model B)/ _confirm_update_flow(module_frame=Model A):
{uid} → template_ssp_id → adapter → import_ssp(target_ssp_id, mode='create')。clone_resource_library(= B1 能力,從 framework_version clone catalog→profile→空範本 SSP)拿 new module_frame + template_ssp_id → adapter → import_ssp(...)。ssp_write_strategy / module_frame_write_strategy 在匯入路徑的呼叫。api.oscal.routes.ssp.ssp_excel_import_route 從 EXCLUDE_MODULES 拿掉;DI wire ssp_excel_import_app_service(注入 P1 OscalIoService / clone_resource_library 能力);確認 app service 不再 import v1 entity(boot 不炸)。OscalIoService 要在主專案 DI container 暴露(目前可能只在套件內 export 用過;確認主專案有 provider,沒有就加)。直覺推測(待 verify):P2 主力是「拔 v1 寫入 + 接 adapter + re-enable」,量比想像輕(FE/parser/流程/diff UI 都不動)。但「拔乾淨 v1 import 讓 boot 不炸」可能要逐一追 import chain,是最容易卡的點。
ssp_excel_import_app_service.py 的 confirm 三分派 + excel_parser/types.py 的 ParsedExcel + clone_resource_library 簽章 + OscalIoService 在主專案 DI 現況。ParsedExcel → OSCAL ssp dict(先 Model A 單純路徑跑通)。_confirm_update_flow(A) 先;_confirm_superset_flow(B) 後(多一步 clone 建殼)。oscal.ssp_implemented_requirements 等)→ 量 BOOT OK + pytest 不多紅 → 顯式 git add commit。| 檔案 | 為何 |
|---|---|
~/Projects/.../jedi-oscal-v2/.../app/service/io/oscal_io_service.py |
P1 成品 import_ssp,P2 confirm 要呼叫(已完成,讀懂簽章/涵蓋面) |
app/oscal/service/ssp_excel_import_app_service.py |
P2 改寫 confirm 寫入(_confirm_update_flow / _confirm_superset_flow) |
app/oscal/service/excel_parser/types.py |
ParsedExcel 欄位(adapter 來源) |
新檔 app/oscal/service/...(excel→oscal dict adapter) |
P2 新增 adapter |
config/di_modules.py |
從 EXCLUDE_MODULES 移出 excel import route |
di_containers/oscal/oscal_containers.py |
wire ssp_excel_import_app_service + 暴露 OscalIoService |
domain/oscal/strategy/ssp_write_strategy.py / module_frame_write_strategy.py |
v1 寫入,退役(從匯入路徑移除呼叫;不刪檔,確認無 live consumer 後另開 cleanup) |
cd /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be
git branch --show-current # feature/oscal-refactor
git status --short # 僅 ' M pyproject.toml'
git log --oneline -3 # 2dca0f2b / d6e88221 / dabf1ebc
( cd ~/Projects/Jedicogy/module/jedi-python-package && git log --oneline -1 && git status -sb | head -6 ) # a930bfd P1 + B1 dev 改動還在
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 | tail -1 # 56 failed,...,50 errors(持平)
.env在 shell source 會在 JSON 行報 parse error(無害);python script 一律用load_dotenv()。
cd /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be
export GITLAB_API_VERSION=4 GITLAB_URL=http://localhost GITLAB_PRIVATE_TOKEN=dummy GITHUB_PRIVATE_TOKEN=dummy
# P1 套件單元測試(round-trip + 非空 guard + 未知 target)→ io_export 全套應 10 passed
poetry run python -m pytest ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/tests/io_export/ -q -p no:cacheprovider 2>&1 | tail -3
# import_ssp 存在且簽章正確
poetry run python -c "from jedi_oscal_v2.app.service.io.oscal_io_service import OscalIoService; import inspect; print(inspect.signature(OscalIoService.import_ssp))"feature/oscal-refactor;branch 不對停下問 user)。git add 檔名、禁 -am;各 repo 分開);push / 收尾 / 套件發版等 user 明示。pyproject.toml path-dep 勿 commit;套件 dev 改動 commit 進套件 branch 防搞丟,但不發 Nexus。feedback_preflight_before_touching_pipeline)。--single-transaction -v ON_ERROR_STOP=1,新表 GRANT cm_app、收尾 INSERT schema_migrations;本期 migration 只套 dev。盤點 commits(BE + 套件)→ changelog(type=feat,import-ssp 各 phase)→ analysis(兩模型取捨 / 只換寫入層 / OSCAL-dict 當中介)→ SUMMARY → 回頭更新橫向文件(design §4.2/§4.4 拿掉 import defer 標記、api-contract §2、import-ssp-design 標完工、frontend-overview 若動 payload)→ memory feedback(≥1 條:如「import-ssp 只換寫入層、FE 已 v2」「v1 import route disabled 因 v1 entity import 撞 v2 MetaData」)→ Notion。push 等 user。
本 session 自身的收尾(Q1 + import-ssp 設計/P1 的 changelog / SUMMARY / analysis / memory / Notion)也都還沒做 —— user 還沒下收尾命令,全留著。連更前面 Wave 2B(B4.2/B5/匯出/AO-fix)的收尾也掛著。下次 user 說「收尾」一起補。
source_type=ssp / Project Planning)—— 本 arc 只做資源庫(module_frame + framework_version)。pyproject.toml 改回 pin —— 整 feature 完 + user 明示。feature/oscal-refactor,未 push;HEAD 2dca0f2b)2dca0f2b docs(FR-038): import-ssp spec 補正 — 兩種資源庫匯入模型 + source_type 分派
d6e88221 docs(FR-038): import-ssp 設計定稿(資源庫範本 SSP 匯入,docx+excel 落 v2)
dabf1ebc feat(FR-038): Wave 2B Q1.4 — vw_user_job_queue rebind control_* + AO
394fe2c1 feat(FR-038): Wave 2B Q1.3 — project_start 同步建 per-AO 準備期 job
7cfe3565 fix(FR-038): Q1.1 綁定表改 ALTER public(非 CREATE compliance)
6429ec5f feat(FR-038): Wave 2B Q1.2 — AO 推導抽共用 helper(ao_derivation)
86a4099e feat(FR-038): Wave 2B Q1.1 — 建 workflow_execution_control_mapping + seed 收證據 template
(更前面 c89063db 起為前一棒,見該 commit。)
~/Projects/Jedicogy/module/jedi-python-package branch feature/oscal-refactor,未 push;HEAD a930bfd,領先 origin 1)a930bfd feat(oscal-v2): OscalIoService.import_ssp — OSCAL SSP → v2 relational tree (FR-038 P1)
M catalog_service.py、M framework_service.py、?? tests/catalog/test_catalog_service_import.py(別搞丟、別發版)。讀 docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-15-import-ssp-P2-START-HERE-handoff.md。
先過「🧭 WHY」+ §0 讀序硬 gate(import-ssp-design.md 全讀 + api-contract §2 + design §4.2/§4.4),
能答冷接自檢 4 問(落點為何資源庫範本 SSP / Model A vs B 與其 source_type / 為何「只換寫入層」/ P1 import_ssp 涵蓋面與為何 create-only)才往下。
跑 §6 pre-flight(BOOT OK + pytest 56f/50e)+ §7 verify P1(io_export 10 passed)。
接手 P2:Excel 匯入兩種 source 落 v2 — adapter(ParsedExcel→OSCAL dict) → confirm 改 delegate 套件 import_ssp(拔 v1 write strategy)→ re-enable excel route → 真資料 smoke + commit。
每段 smoke + 顯式 git add commit。push/收尾/套件發版等 user 明示;不切 branch;pyproject.toml dev path-dep 勿 commit。
看本檔 + §0 讀序 + 跑 §6/§7 → 能確認現況(Q1 shipped + import-ssp 設計定稿 + P1 套件寫入核心 done + commits + 套件 dev 改動)、懂 import-ssp WHY(只換寫入層、FE 已 v2、兩模型、為何卡)、知道 P2 怎麼接(adapter + confirm 改寫 + re-enable)、知道地基陷阱(schema 落 public、範本 SSP 空、export MVP 面、拔 v1 import 才不炸)、知道規範界線。不需 user 額外解釋即可開工。