🟢 START HERE — FR-038 Wave 2B:B3 SSP 維護(import-ssp 全弧 done + B3 地基 done,接手 16 route)

給下個 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)。

🔖 交接現況(2026-06-15 換 session 當下)

項目
進度 import-ssp 全弧 DONE(Excel + docx 匯入資源庫範本 SSP,full v1 parity)+ B3 套件地基 DONESspService 子物件 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.pyM framework_service.py?? tests/catalog/test_catalog_service_import.pydev 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 說「收尾」一起補。

🧭 WHY:這整件事要幹嘛 + B3 在大圖位置(先懂才准碰 code)

FR-038 一句話:把舊「只套 OSCAL 概念、大量自定義欄位」的實作打掉重練成「正式 OSCAL v1.2.2 物件模型 + 三層 clone/snapshot 邊界 + 稽核輪次 first-class」。Wave 2A 已把 BE 從舊 jedi_oscal 全翻到 jedi_oscal_v2、B1~B5 業務全 disableWave 2B 在 v2 地基上逐一把業務 re-enable

三層 + 四邊界:框架母版(catalog/profile) →①resolve→ 資源庫三件組(catalog+profile+SSP 範本) →②clone脫鉤→ 專案副本 →③啟動稽核 snapshot→ 凍結快照。

import-ssp(剛做完的前一棒):合規資源庫的「範本 SSP」原本就有「從 Word/Excel 匯入填答案卷」功能,v2 翻地基後寫入層斷掉。本弧把寫入層接到 v2 OscalIoService.import_sspExcel + docx、Model A(更新既有資源庫)+ Model B(新建資源庫)都做完、達 v1 parity(control 實作 by-component 敘述+狀態 / per-AO statements / parties / components / leveraged-authorizations / inventory 全寫)。

B3 SSP 維護(本棒)= 把「匯入填好的 SSP」能在 FE 檢視/編輯

  • 資源庫範本編輯(FE ModuleFrameTemplateEditView.vue,7 tabs:受評標的 / 參與人員 / 元件 / 資產清冊 / 外部服務 / 控制實作 / 程序書)—— 落點是 compliance.module_frames.template_ssp_id 指的那份 SSP(= import-ssp 灌進去的同一份)。
  • 專案 SSP 維護(B2 clone 出來的專案 living SSP,同類子物件 + control_implementation / 程序書池)。
  • 這 16 條 route 在 2A 全被 disable(service chain 還 import 舊 jedi_oscal)。B3 = 把它們的 app service 重寫接 v2 SspService、re-enable route

冷接自檢 4 問(答不出回 §0 讀序):① B3 SSP 維護的落點是哪兩種 SSP、各從哪來?② 為什麼 import-ssp 做完接著就該做 B3(同一份 SSP 的寫 vs 讀/編輯)?③ 套件 SspService 這次補了哪些子物件 CRUD、給誰用?④ 為什麼 16 route 不能直接 re-enable、要先重寫 app service?


§0 接手讀序(按序,1~3 是硬 gate)

  1. 本檔「🧭 WHY」+ 通讀本檔
  2. 🔒 gate:api-contract.md §9(SSP 維護端點清單 — B3 照此重建 route) + §2(資源庫)
  3. 🔒 gate:design.md §4.2(套件對外 service 契約,含 SspService)/ §4.4(套件 vs 主專案邊界:OSCAL CRUD 在套件、業務在主專案)
  4. context(前一棒,已完成):import-ssp-design.md(兩模型 / 落點 = template_ssp_id / this-system 元件當 by-component 錨點)
  5. B3 地基成品(套件,已 ship)~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/jedi_oscal_v2/app/service/ssp/ssp_service.py(子物件 CRUD method 清單見 §3)
  6. v1 待重寫的 MF app service(B3 要改):app/module_frame/service/module_frame_components_service.py 等(§5 清單);v1 專案 SSP route 對應 service
  7. canonical re-enable pattern 範本:B1 的 app/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 註冊)

§1 現況:import-ssp 全弧 + B3 地基都做了什麼(2026-06-15,全 verified)

import-ssp 全弧 SHIPPED(BE 4 commit + 套件 2 commit)

  • 套件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)。
  • adapter(主專案 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)。
  • app servicessp_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)。
  • route:excel/docx import route 移出 EXCLUDE_MODULES + api/oscal/__init__.py 重新註冊。
  • verify:real-DB smoke(Excel/docx/create-path)全過、零新回歸。

B3 套件地基 SHIPPED(套件 0196582

SspService 擴子物件 CRUD(讀寫 import-ssp 灌進去的同一批 v2 子表):

  • SI/CI:get_system_implementation / get_or_create_system_implementation / get_or_create_control_implementation
  • components:list_components_for_ssp / get_component / update_component / delete_component(+既有 add/list)
  • inventory:list_inventory_items(ssp_id) / add / get / update / delete_inventory_item
  • leveraged:list_leveraged_authorizations(ssp_id) / add / get / update / delete_leveraged_authorization
  • implemented-requirements:list_implemented_requirements_for_ssp / get / update / delete(+既有 add/list)
  • by-components:update_by_component / delete_by_component
  • parties(metadata-scoped):list_parties(metadata_id) / add_party / get / update / delete_party
  • verify:package ssp+io 44 passed、live CRUD smoke 全過。

§2 ⚠️ 會讓你做錯的陷阱 / 已驗地基

  1. v1/v2 不可共存(memory project_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 舊套件。
  2. 落點:資源庫範本編輯 → compliance.module_frames.template_ssp_id 指的 SSP(= import-ssp 填的那份);專案 SSP 維護 → 專案 living SSP(B2 clone 出來)。module_framescompliance schema(不是 public)。
  3. v2 SSP 子表 schema 落 oscal schemasystem_security_plans 主表 + ssp_* 子表);jedi_common Base DEFAULT_SCHEMA 會讓部分表走 search_path。查表存在性查對 schema(compliance.module_frames vs oscal.ssp_*)。
  4. 子物件掛點:components/inventory/leveraged 掛 ssp_system_implementations(用 get_or_create_system_implementation);implemented-requirements 掛 ssp_control_implementationsget_or_create_control_implementation);parties 掛 SSP 的 metadata_id(不是 ssp_id)。SspService 的 list_*_for_ssp 已封裝 resolve,直接用。
  5. v1 MF app service 寫的是 MF-defaults 表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。
  6. RLSmodule_frames tenant-scoped;route 帶 user.tenant_id;無 user context 時 session_scope else 開 super_admin 繞 RLS(smoke 可直接跑)。
  7. 審計欄位:response 帶 created_user/updated_user/matched_*_id 要 enrich nickname(CLAUDE.md 審計欄位規範)。
  8. pytest 用 python -m pytest;BE boot 補 dummy GITLAB/GITHUB env(見 §6)。
  9. plan 假設先 verify:開工前讀 route 檔 + v1 service 實際 import/method,不憑本檔摘要刻。

§3 B3 計畫(方法,開工前 pre-flight 驗)

目標: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。
  • 專案 SSP(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 同套)

  1. 重寫 app service → v2:注入 oscal_container.ssp_service(+ resource_library_app_service 解 template_ssp_id 或 ssp domain 解專案 ssp)→ 用 §1 的子物件 CRUD method 讀寫;拔掉所有 from jedi_oscal... v1 import 與 MF-defaults 寫入。
  2. DI wiredi_containers/oscal/oscal_containers.py 加 provider(mirror ssp_excel_import_app_service 的 wiring)。
  3. route 重寫:route 只解 request / 呼 app service / 序列化;Provide 路徑指新 provider。
  4. re-enableconfig/di_modules.py 從 EXCLUDE_MODULES 移除該 route;api/oscal/__init__.py(oscal.ssp.)或 api/module_frame/__init__.py(module_frame.)的 create_module() 重新註冊 blueprint。
  5. boot + real-DB smoke(用 import-ssp 填好的真範本 SSP,如 mf 412 / template_ssp_id 1481)+ pytest 零回歸 + 顯式 git add commit

建議子順序:先做資源庫範本那 7 條(跟 import-ssp 同落點,可直接拿匯入結果驗讀/編輯),再做專案 SSP 9 條。先立一條完整 vertical slice(建議 module_frame_system_characteristic,1:1 子物件最單純)當 canonical,再複製其餘。


§4 開工順位(每段 smoke + 顯式 git add commit)

  1. pre-flight:跑 §6 + §7(確認 import-ssp + B3 地基在、BE boot、pytest baseline)。
  2. 讀 live:選定第一條 route(建議 module_frame_system_characteristic)→ 讀其 route 檔 + v1 app service(看它讀寫什麼、v1 import 哪些)+ FE ModuleFrameTemplateEditView.vue 打哪個端點 / 期望 response shape。
  3. 第一條 vertical slice:重寫 app service → DI wire → route → re-enable → boot + smoke + commit(立 canonical pattern)。
  4. 複製其餘 6 條資源庫範本 route。
  5. 9 條專案 SSP route(落專案 living SSP;control_implementation / document_pool 較複雜,留後段)。
  6. 每組 BOOT OK + pytest 不多於 baseline 紅 + commit。

§5 該讀 / 預期改動的檔案

檔案 為何
~/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)

§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'
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(持平)

§7 Verify import-ssp + B3 地基確實 close(必跑,可複製貼)

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])"

§8 行為規範重要提醒(適用本棒)

  • 不切 branch(兩 repo 都在 feature/oscal-refactor;branch 不對停下問 user)。
  • 可自行階段性 commit(顯式 git add 檔名、-am;各 repo 分開);push / 收尾 / 套件發版等 user 明示
  • pyproject.toml path-dep 勿 commit;套件 dev 改動 commit 進套件 branch 防搞丟,但不發 Nexus
  • 改 BE 後提醒 user 重啟 BE(無 hot reload);服務 user 自己起。
  • 動 FE 前先讀 FE CLAUDE.md(本棒理論上 FE 已 v2 不用動,但若要碰先讀)。
  • B3 只接 v2:重寫 app service 前確認拔乾淨 v1 import(§2-1)。
  • 遇到架構/資料模型取捨、scope 邊界 → 停下問 user

§9 收尾流程(整 B3 arc 做完 + user 明示才做)

盤點 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 還沒下收尾命令,全留著。


§10 不在本期 scope(別順手做)

  • P4 update-diff(資源庫範本 SSP 重複匯入做差異更新)—— import-ssp 目前 create-only(非空丟 412 GRC_IMPORT_SSP_TEMPLATE_NOT_EMPTY)。差異模式留 P4。
  • 專案 SSP 匯入 / 匯出ssp_scoped_excel_import_route / ssp_export_route / mf_ssp_export 的匯出半部)—— 跟 B3 維護 CRUD 不同,另議。
  • B4.2(AP 編輯)/ B5(AR/風險/POA&M/結案覆核) —— B3 之後才接(見 2026-06-15-B4.2-B5-START-HERE-handoff.md)。
  • 租戶連結(parties→使用者 / inventory→device 配對)—— 已釐清範本落點不需要(原樣存即可)。
  • 套件發 Nexus / pyproject.toml 改回 pin / 收尾文件 —— 整 feature 完 + user 明示。

§11 本 session commits

主專案(branch 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)
  • 套件另有 B1 未 commit dev 改動:M catalog_service.pyM framework_service.py?? tests/catalog/test_catalog_service_import.py(別搞丟、別發版)。

§12 給 fresh 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 問(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 額外解釋即可開工。