給下個 session 的 prompt:「讀
docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-15-B3-ssp-maintenance-START-HERE-handoff.md,先過「🧭 WHY」+ §0 讀序硬 gate(api-contract §9 + design §4.2/§4.4 + import-ssp-design),能答冷接自檢 4 問再碰 code。跑 §6 pre-flight + §7 verify(import-ssp + B3 地基),接手 B3 SSP 維護:用已建好的套件SspService子物件 CRUD,把 16 條 disabled route(7 資源庫範本 + 9 專案 SSP)逐一接 v2、re-enable。」 本檔自包含。狀態為 2026-06-15(import-ssp 全弧 ship + B3 套件地基 ship + verified)。
| 項目 | 值 |
|---|---|
| 進度 | import-ssp 全弧 DONE(Excel + docx 匯入資源庫範本 SSP,full v1 parity)+ B3 套件地基 DONE(SspService 子物件 CRUD)。下一棒 = B3 SSP 維護:re-enable 16 route |
| 主專案 branch / HEAD | feature/oscal-refactor / 4e448ce1 |
| 主專案 working tree | 乾淨,只有 M pyproject.toml(jedi-oscal-v2 dev path-dep,照規範勿 commit) |
| 套件 branch / HEAD | ~/Projects/Jedicogy/module/jedi-python-package feature/oscal-refactor / 0196582(領先 origin 3,未 push) |
| ⚠️ 套件未 commit 的 B1 dev 改動(別搞丟/別發版) | 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 on v2;BE python -m pytest test/ = 56 failed + 50 errors(持平 baseline、零新回歸)+ 9 skipped;套件 io_export 10 passed + ssp 44 passed |
| push | BE + 套件都未 push,等 user 明示 |
| 未收尾(user 還沒下收尾命令) | import-ssp + B3 地基的 changelog / analysis / SUMMARY / memory / Notion 全未寫。連更前面 Q1/Wave 2B 的收尾也掛著。下次 user 說「收尾」一起補。 |
FR-038 一句話:把舊「只套 OSCAL 概念、大量自定義欄位」的實作打掉重練成「正式 OSCAL v1.2.2 物件模型 + 三層 clone/snapshot 邊界 + 稽核輪次 first-class」。Wave 2A 已把 BE 從舊 jedi_oscal 全翻到 jedi_oscal_v2、B1~B5 業務全 disable;Wave 2B 在 v2 地基上逐一把業務 re-enable。
三層 + 四邊界:框架母版(catalog/profile) →①resolve→ 資源庫三件組(catalog+profile+SSP 範本) →②clone脫鉤→ 專案副本 →③啟動稽核 snapshot→ 凍結快照。
import-ssp(剛做完的前一棒):合規資源庫的「範本 SSP」原本就有「從 Word/Excel 匯入填答案卷」功能,v2 翻地基後寫入層斷掉。本弧把寫入層接到 v2 OscalIoService.import_ssp,Excel + docx、Model A(更新既有資源庫)+ Model B(新建資源庫)都做完、達 v1 parity(control 實作 by-component 敘述+狀態 / per-AO statements / parties / components / leveraged-authorizations / inventory 全寫)。
B3 SSP 維護(本棒)= 把「匯入填好的 SSP」能在 FE 檢視/編輯:
ModuleFrameTemplateEditView.vue,7 tabs:受評標的 / 參與人員 / 元件 / 資產清冊 / 外部服務 / 控制實作 / 程序書)—— 落點是 compliance.module_frames.template_ssp_id 指的那份 SSP(= import-ssp 灌進去的同一份)。SspService、re-enable route。冷接自檢 4 問(答不出回 §0 讀序):① B3 SSP 維護的落點是哪兩種 SSP、各從哪來?② 為什麼 import-ssp 做完接著就該做 B3(同一份 SSP 的寫 vs 讀/編輯)?③ 套件
SspService這次補了哪些子物件 CRUD、給誰用?④ 為什麼 16 route 不能直接 re-enable、要先重寫 app service?
api-contract.md §9(SSP 維護端點清單 — B3 照此重建 route) + §2(資源庫)design.md §4.2(套件對外 service 契約,含 SspService)/ §4.4(套件 vs 主專案邊界:OSCAL CRUD 在套件、業務在主專案)import-ssp-design.md(兩模型 / 落點 = template_ssp_id / this-system 元件當 by-component 錨點)~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/jedi_oscal_v2/app/service/ssp/ssp_service.py(子物件 CRUD method 清單見 §3)app/module_frame/service/module_frame_components_service.py 等(§5 清單);v1 專案 SSP route 對應 serviceapp/oscal/service/resource_library_app_service.py + import-ssp 的 app/oscal/service/ssp_excel_import_app_service.py(怎麼包 v2 primitive + DI wire + route 重寫 + EXCLUDE 移除 + blueprint 註冊)OscalIoService.import_ssp / export 擴到完整 SSP 子樹(by-components 控制敘述+狀態、per-AO statements、leveraged-authorizations、inventory、information-types;component uuid→id map 解 by-component 軟參照)。import_ssp(oscal_ssp, *, target_ssp_id, curr_user, mode='create'),create-only(非空丟 ValueError)。app/oscal/service/import_adapter/):excel_to_oscal_ssp / docx_to_oscal_ssp + 共用 _common(synthesize OSCAL this-system 元件當 by-component 錨點 / LA 補合成 provider party / inventory / SoA props)。ssp_excel_import_app_service.py / ssp_docx_import_app_service.py 精簡重寫 → confirm delegate import_ssp。Model A(既有資源庫 → template_ssp_id 填)+ Model B(新建資源庫 = ResourceLibraryAppService.create_resource_library 建殼再填)。docx parse 用新 v2_candidate_loader(framework_version → v2 catalog → controls + AO)。api/oscal/__init__.py 重新註冊。0196582)SspService 擴子物件 CRUD(讀寫 import-ssp 灌進去的同一批 v2 子表):
get_system_implementation / get_or_create_system_implementation / get_or_create_control_implementationlist_components_for_ssp / get_component / update_component / delete_component(+既有 add/list)list_inventory_items(ssp_id) / add / get / update / delete_inventory_itemlist_leveraged_authorizations(ssp_id) / add / get / update / delete_leveraged_authorizationlist_implemented_requirements_for_ssp / get / update / delete(+既有 add/list)update_by_component / delete_by_componentlist_parties(metadata_id) / add_party / get / update / delete_partyproject_oscal_v1_v2_cannot_coexist):B3 任何新 code 只准 import jedi_oscal_v2。重寫 app service 前確認它內部不再 import 舊 jedi_oscal,否則 wire 進 boot 撞同名表 MetaData 直接炸。16 route 被 disable 的根因就是 service chain 還 import 舊套件。compliance.module_frames.template_ssp_id 指的 SSP(= import-ssp 填的那份);專案 SSP 維護 → 專案 living SSP(B2 clone 出來)。module_frames 在 compliance schema(不是 public)。oscal schema(system_security_plans 主表 + ssp_* 子表);jedi_common Base DEFAULT_SCHEMA 會讓部分表走 search_path。查表存在性查對 schema(compliance.module_frames vs oscal.ssp_*)。ssp_system_implementations(用 get_or_create_system_implementation);implemented-requirements 掛 ssp_control_implementations(get_or_create_control_implementation);parties 掛 SSP 的 metadata_id(不是 ssp_id)。SspService 的 list_*_for_ssp 已封裝 resolve,直接用。module_frame_control_defaults 等);v2 改成讀寫 template_ssp_id 的 SSP 子物件(MF-defaults 在 v2 退役,範本 SSP 才是 source of truth,memory feedback_mf_defaults_single_source_of_truth)。別照抄 v1 寫 MF-defaults。module_frames tenant-scoped;route 帶 user.tenant_id;無 user context 時 session_scope else 開 super_admin 繞 RLS(smoke 可直接跑)。created_user/updated_user/matched_*_id 要 enrich nickname(CLAUDE.md 審計欄位規範)。python -m pytest;BE boot 補 dummy GITLAB/GITHUB env(見 §6)。目標:16 條 disabled SSP 維護 route 接 v2、re-enable。user 拍板「兩組一起做、直接擴 SspService(地基已完成)」。
16 route(現 EXCLUDE_MODULES,= re-enable 地圖):
api.module_frame.routes.*,7 條):module_frame_system_characteristic / module_frame_components / module_frame_inventory / module_frame_leveraged / module_frame_party / module_frame_ssp_resources / mf_ssp_export。落點 template_ssp_id。api.oscal.routes.ssp.*,9 條):ssp_system_characteristic / ssp_components / ssp_inventory_items / ssp_leveraged / ssp_party / ssp_resources / ssp_control_implementation / ssp_control_impl_import / ssp_document_pool。落點專案 living SSP。ssp_scoped_excel_import_route / ssp_export_route 是匯入/匯出,非本棒,見 §10。)canonical pattern(每條 route,照 B1 / import-ssp 同套):
oscal_container.ssp_service(+ resource_library_app_service 解 template_ssp_id 或 ssp domain 解專案 ssp)→ 用 §1 的子物件 CRUD method 讀寫;拔掉所有 from jedi_oscal... v1 import 與 MF-defaults 寫入。di_containers/oscal/oscal_containers.py 加 provider(mirror ssp_excel_import_app_service 的 wiring)。config/di_modules.py 從 EXCLUDE_MODULES 移除該 route;api/oscal/__init__.py(oscal.ssp.)或 api/module_frame/__init__.py(module_frame.)的 create_module() 重新註冊 blueprint。建議子順序:先做資源庫範本那 7 條(跟 import-ssp 同落點,可直接拿匯入結果驗讀/編輯),再做專案 SSP 9 條。先立一條完整 vertical slice(建議
module_frame_system_characteristic,1:1 子物件最單純)當 canonical,再複製其餘。
ModuleFrameTemplateEditView.vue 打哪個端點 / 期望 response shape。| 檔案 | 為何 |
|---|---|
~/Projects/.../jedi-oscal-v2/.../ssp/ssp_service.py |
B3 地基(子物件 CRUD),B3 app service 都呼它(已完成,讀懂 method) |
app/module_frame/service/module_frame_*_service.py(~7 個) |
資源庫範本編輯 app service,重寫接 v2(v1 寫 MF-defaults → v2 寫 template_ssp_id SSP 子物件) |
app/oscal/routes/ssp/* 對應的 app service |
專案 SSP 維護 app service,同上 |
api/module_frame/routes/* + api/oscal/routes/ssp/* |
route 重寫(Provide 指新 provider) |
di_containers/oscal/oscal_containers.py |
加 B3 app service provider(mirror ssp_excel_import_app_service) |
config/di_modules.py |
從 EXCLUDE_MODULES 逐條移除 |
api/oscal/__init__.py + api/module_frame/__init__.py |
create_module() 重新註冊 blueprint |
~/Projects/.../compliance-manager-fe/src/views/module_frame/ModuleFrameTemplateEditView.vue |
FE 消費端,確認 request/response shape(動 FE 前讀 FE CLAUDE.md) |
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 # 4e448ce1 / 37c1100c / a082c0b2
( cd ~/Projects/Jedicogy/module/jedi-python-package && git log --oneline -1 ) # 0196582
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(持平)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
# 套件:import_ssp/export 全子樹 round-trip + SspService 子物件 CRUD
poetry run python -m pytest ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/tests/io_export/ ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/tests/ssp/ -q -p no:cacheprovider 2>&1 | tail -3 # 10 + 44 passed
# 主專案 adapter 單元測試
poetry run python -m pytest test/test_import_adapter_excel_to_oscal_ssp.py test/test_import_adapter_docx_to_oscal_ssp.py -q -p no:cacheprovider 2>&1 | tail -3
# SspService 新子物件 CRUD method 都在
poetry run python -c "from jedi_oscal_v2.app.service.ssp.ssp_service import SspService; print([m for m in dir(SspService) if 'inventory' in m or 'leveraged' in m or 'party' in m])"feature/oscal-refactor;branch 不對停下問 user)。git add 檔名、禁 -am;各 repo 分開);push / 收尾 / 套件發版等 user 明示。pyproject.toml path-dep 勿 commit;套件 dev 改動 commit 進套件 branch 防搞丟,但不發 Nexus。盤點 commits(BE + 套件)→ changelog(type=feat,B3 各組 + import-ssp 補)→ analysis(this-system 錨點決策 / 完整 parity / MF-defaults 退役)→ SUMMARY → 回頭更新橫向文件(api-contract §9 標完工、design §4.2 SspService 補子物件 CRUD、frontend-overview 若動 payload)→ memory feedback(≥1 條)→ Notion。push 等 user。
本 session 自身的收尾(import-ssp + B3 地基的 changelog / SUMMARY / analysis / memory / Notion)也都還沒做 —— user 還沒下收尾命令,全留著。
GRC_IMPORT_SSP_TEMPLATE_NOT_EMPTY)。差異模式留 P4。ssp_scoped_excel_import_route / ssp_export_route / mf_ssp_export 的匯出半部)—— 跟 B3 維護 CRUD 不同,另議。2026-06-15-B4.2-B5-START-HERE-handoff.md)。pyproject.toml 改回 pin / 收尾文件 —— 整 feature 完 + user 明示。feature/oscal-refactor,未 push;HEAD 4e448ce1)4e448ce1 fix(FR-038): import-ssp P3 docx create — confirm 自建資源庫(對齊 FE 預期)
37c1100c feat(FR-038): import-ssp P3 — Docx 匯入落 v2 範本 SSP(full parity)
a082c0b2 feat(FR-038): import-ssp adapters full v1 parity + docx adapter
b70339b0 feat(FR-038): import-ssp P2 — Excel 匯入兩 source 落 v2 範本 SSP
(更前面 cb091369 起為前一棒 import-ssp 設計/P1。)
~/Projects/Jedicogy/module/jedi-python-package branch feature/oscal-refactor,未 push;HEAD 0196582,領先 origin 3)0196582 feat(oscal-v2): SspService 子物件 CRUD — B3 SSP 維護地基
798a274 feat(oscal-v2): import_ssp/export full SSP subtree — v1 parity
a930bfd feat(oscal-v2): OscalIoService.import_ssp — OSCAL SSP → v2 relational tree (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-B3-ssp-maintenance-START-HERE-handoff.md。
先過「🧭 WHY」+ §0 讀序硬 gate(api-contract §9 + design §4.2/§4.4 + import-ssp-design),
能答冷接自檢 4 問(B3 兩落點 / 為何接著 import-ssp 做 / SspService 補了什麼 / 為何不能直接 re-enable)才往下。
跑 §6 pre-flight(BOOT OK + pytest 56f/50e)+ §7 verify(套件 io_export 10 + ssp 44 passed)。
接手 B3 SSP 維護:用已建好的套件 SspService 子物件 CRUD,把 16 條 disabled route(先 7 資源庫範本、再 9 專案 SSP)
逐一接 v2 — 重寫 app service(拔 v1 import、改讀寫 template_ssp_id 的 SSP 子物件)→ DI wire → route 重寫 →
移出 EXCLUDE_MODULES + 重新註冊 blueprint → real-DB smoke + commit。先立一條 vertical slice 當 canonical 再複製。
每段 smoke + 顯式 git add commit。push/收尾/套件發版等 user 明示;不切 branch;pyproject.toml dev path-dep 勿 commit。
看本檔 + §0 讀序 + 跑 §6/§7 → 能確認現況(import-ssp 全弧 done + B3 套件地基 done + commits)、懂 WHY(FR-038 替換、import-ssp 寫入 / B3 讀編輯同一份 SSP、三層四邊界)、知道 B3 怎麼接(SspService 子物件 CRUD + canonical re-enable pattern + 16 route 地圖)、知道地基陷阱(只接 v2 / 落點 template_ssp_id / 子物件掛點 / MF-defaults 退役)、知道規範界線。不需 user 額外解釋即可開工。