✓ DONE — 2026-06-10:Phase 2(P2a/P2b/P2a-E)+ P2c(DROP 退役表 + 移除 inert 子系統)已於 DEV 完工並經 user 手測通過。收尾見
2026-06-10-phase2-SUMMARY.md、design.md §9。stg/poc 留獨立遷移弧(未排程)。以下為接手當時的原始 handoff,保留供溯源。
| 項目 | 內容 |
|---|---|
| 緣由 | module_frame 範本內容整併進「樣板 SSP」(is_template=True)。Phase 1(schema + 遷移 + 啟動專案 clone + 資源庫編輯改源)已完工並經 user 在 dev 驗證。本 handoff 交接 Phase 2(匯入/填值改源 + gate 清理 + DROP 舊表)。 |
| branch | feature/oscal-refactor(BE)。不要切 branch。 |
| 影響 repo | BE(主專案)+ jedi-oscal(套件,走 dev path-dep)+ 3 DB 環境(dev 已遷移;stg/poc 待) |
| 接手前必讀 | 本檔 → docs/features/FR-036-2606-module-frame-ssp-merge/design.md(含 §3.0 架構修正)→ implementation-plan.md(P2a/P2b/P2c 精確到 method/行號) |
| 預估時間 | P2a 大(~M5a 規模,3 service 讀+寫);P2b 小;P2c 破壞性、待前置 |
docs/features/FR-036-2606-module-frame-ssp-merge/design.md — 整體設計,特別 §3.0 架構修正(專案 SSP 子表是 AP 投影;樣板 SSP 用 AP-independent key;clone 走 re-point copy() 而非 SspVersioningService)docs/features/FR-036-2606-module-frame-ssp-merge/implementation-plan.md — P2a/P2b/P2c 精確 surface(method + 行號),本期主要照它做app/module_frame/service/ssp_import_template_app_service.py(樣板下載填值,6 個讀 helper)app/oscal/service/ssp_docx_import_app_service.py(_run_mf_default_confirm L1078-1268,docx 匯入確認)app/oscal/service/ssp_excel_import_app_service.py(_apply_write_mf_defaults L1707-1866,excel 匯入確認)app/module_frame/service/module_frame_control_default_service.py(control default → 樣板 SSP 的 pattern)module_frame ──(module_frames.template_ssp_id)──► 樣板 SSP(oscal.ssps, is_template=True, version_no=0)
└ 完整 ssp_* 子表(取代 module_frame_*_defaults)
資源庫編輯(M5a/M5b)──► 寫樣板 SSP 子表
啟動專案(M4)──► copy() 從樣板 SSP clone 內容到專案 SSP(AP 橋接)
新建資源庫(M5c)──► 自動建一份樣板 SSP
guidant_ai_dev @ 192.168.50.188:25432,密碼查 .env DB_SECRET)oscal.ssps.is_template(boolean, default false)+ compliance.module_frames.template_ssp_id(int)已加(migration scripts/sql/2026-06-10-fr036-template-ssp-columns.sql,只套了 dev)。is_template=True 樣板 SSP,內容零丟失(M3 遷移 script scripts/sql/fr036_migrate_defaults_to_template_ssp.py,只跑了 dev)。對照(dev):
mf#276 CMMC L1 → ssp#304
mf#277 CMMC L2 → ssp#305
mf#313 超精簡 → ssp#306(1 ctrl / 12 程序書)
mf#343 艾爾航空 → ssp#307(15 ctrl / 57 AO / 2 元件 / 1 SC)
mf#383 系統安全計畫 → ssp#308(15/54/4 元件/1 LA/1 SC)
mf#388 系統安全計畫6… → ssp#309(14/54/4 元件/14 資產/1 LA/1 SC)
mf#389 測試…ㄅ123 → ssp#310
驗 dev 連結完整:
PGPASSWORD='<查.env DB_SECRET>' psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -c \
"SELECT id, left(name,20) name, template_ssp_id FROM compliance.module_frames WHERE is_delete=0 ORDER BY id;"
# 預期 7 筆都有 template_ssp_id(非 NULL)bug:編輯資源庫 metadata 會把 template_ssp_id 洗成 NULL → 該資源庫所有樣板 SSP endpoint 報 BadRequestError: 此資源庫尚未建立樣板 SSP。
update_with_translation(base_repository_impl.py ~L635)更新非翻譯欄位無 value is not None 防呆(與 base update 不一致),update_module_frame 建的 entity 沒帶 template_ssp_id(=None)→ 覆寫成 NULL。f04ec7c7):update_module_frame 從 old entity 帶 template_ssp_id 進去保留。update_with_translation 的 None-overwrite 會洗掉任何 translatable entity 的 partial-update 未帶欄位。jedi-common 為 pin 版、blast radius 大,本次只做 BE targeted 修。下個 session 若有 jedi-common 改動需求可一併修(加 None 防呆,對齊 base update)。把三個 service 的 *_defaults 讀寫改成樣板 SSP(key=mf.template_ssp_id)。好消息:這三個 service 已注入 jedi-oscal 的 domain service(control_implementation/objective/component/inventory/leveraged/system_characteristic),給專案 SSP 路徑用——re-point 就是把 _mf_*_default 換成那些既有 service + mf.template_ssp_id,entity 轉 ComponentEntity/InventoryItemEntity/LeveragedAuthorizationEntity/SystemCharacteristicEntity(帶 tenant_id + is_active)。
精確 surface(method + 行號)見 implementation-plan.md §P2a A/B/C/D。摘要:
ssp_import_template_app_service 樣板下載填值):6 個讀 helper(_fetch_ref_docs_mf_lookup/_build_ref_docs/_build_sc_from_mf_default/_fetch_mf_default_items/_fetch_mf_inventory_entities/_load_existing_ctrl_map+_load_existing_obj_map)。最簡做法:_populate_filled_data 解析 tssp=mf.template_ssp_id,SC/components/leveraged/inventory 走既有 _*_by_ssp_id helper(已存在);control/AO/refdoc helper 改吃 template_ssp_id。_run_mf_default_confirm L1078-1268):LA/Component/Inventory/SC 四段 DELETE-all + upsert,從 self._mf_*_default 改成 self._leveraged_authorization_domain_service 等(已注入),keyed by template_ssp_id。控制項/AO 寫入(module_frame_control_default_repo/_objective_default_repo)改寫樣板 SSP control_implementations/objectives。觸發條件 L870 一併調整。_apply_write_mf_defaults L1707-1866):同 B 模式;ModuleFrameWriteStrategy.write_excel_control_defaults(控制項/AO,L1619)改寫樣板 SSP。di_containers/oscal/oscal_containers.py 993-996 / 1047-1051(+ excel container)三個 import service 停注入 mf_*_default_domain_service,改注入 jedi-oscal domain service。⚠️
ssp_import_template_app_service的讀 helper(如_load_existing_ctrl_map)fill 與 confirm 共用,改的時候整批一起、跑完 import smoke 確認沒破匯入。
oscal_project_service._setup_ssp_system_implementation 的 _mf_has_defaults gate(~L665,現查 module_frame_component_defaults count)→ 改查 module_frames.template_ssp_id IS NOT NULL;移除舊式「profile 第一個 SSP 當 template」clone 概念(已被樣板 SSP 取代)。app/module_frame/service/module_frame_template_copy_service.py __init__ 移除已不用的 4 個 *_default domain service 注入(M4 後 copy() 全走 raw SQL on 樣板 SSP,這些注入沒人用)。*_defaults 子系統(破壞性、不可逆)module_frame_*_defaults(7 張)+ module_frame_reference_documents/_mappings;移除整個子系統 model/repo/domain service/mapper/entity/query-entity/DTO/route。py_compile + DI import smoke。| 檔案 | 為何 read | 改動 |
|---|---|---|
app/module_frame/service/ssp_import_template_app_service.py |
P2a 填值讀 helper | 6 helper 改讀樣板 SSP |
app/oscal/service/ssp_docx_import_app_service.py |
P2a docx 寫 | _run_mf_default_confirm 改寫樣板 SSP |
app/oscal/service/ssp_excel_import_app_service.py |
P2a excel 寫 | _apply_write_mf_defaults + ModuleFrameWriteStrategy 改寫樣板 SSP |
di_containers/oscal/oscal_containers.py |
P2a DI | 三 import service 改注入 jedi-oscal domain service |
app/project/service/oscal_project_service.py |
P2b gate | _mf_has_defaults 改查 template_ssp_id |
app/module_frame/service/module_frame_template_copy_service.py |
P2b 清理 | __init__ 移除 unused 注入 |
app/module_frame/service/module_frame_control_default_service.py |
M5a 範本(已完工,照抄 pattern) | 不改,當參考 |
跨 repo:jedi-oscal 已注入的 domain service 足夠,P2a/P2b 預期不需再改 jedi-oscal(除非要修 §1.3 的 update_with_translation follow-up)。
cd /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be
git branch --show-current # 應為 feature/oscal-refactor;不對停下問 user
git status --short # pyproject.toml(M) 是 dev path-dep(勿 commit);FR-037 檔是別的 arc
grep -n "jedi-oscal" pyproject.toml # 確認 dev-dependencies 的 jedi-oscal path 那行「未註解」(dev path-dep 生效中)
lsof -i :8000 | grep LISTEN || echo "BE 沒在跑" # 改 BE 後提醒 user 重啟(無 hot reload)
# import smoke(改完 DI 必跑):
python -c "import di_containers.module_frame.module_frame_containers, di_containers.oscal.oscal_containers; print('DI OK')"PW=$(python3 -c "import json;[print(json.loads(l.split('=',1)[1].strip().strip(chr(34)).strip(chr(39)))['rds_master_password']) for l in open('.env') if l.startswith('DB_SECRET=')]")
# 7 個 MF 都有 template_ssp_id:
PGPASSWORD="$PW" psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -tA -c \
"SELECT id||':'||COALESCE(template_ssp_id::text,'NULL') FROM compliance.module_frames WHERE is_delete=0 ORDER BY id;"
# 樣板 SSP 內容在(例 ssp#307 艾爾航空):
PGPASSWORD="$PW" psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -tA -c \
"SELECT 'ctrl='||count(*) FROM oscal.ssp_control_implementations WHERE system_security_plan_id=307;"git checkout/switch 一律不執行);branch 不對停下問 user。pyproject.toml dev-dependencies 的 jedi-oscal path 行已取消註解(dev-only,勿 commit);改套件源碼 BE 重啟即生效。發版(bump + 推 Nexus)等 user 明示,且要等整個 FR-036 完成。python main_*.py)。-am(working tree 有 FR-037 別的 arc 檔 + dev-only pyproject,別掃進來)。feedback_*.md:本 arc 教訓(樣板 SSP 模型 / update_with_translation None-overwrite 雷 / AP-independent key),MEMORY.md 加索引行docs/conversation-history/(redact secret)update_with_translation 全域修:pin 版、blast radius 大;除非 user 要,留 follow-up。docs/features/FR-037-* / fr037_*.py / schema HTML 是它的,別碰。BE(feature/oscal-refactor):
1148d566 docs(FR-036): 整併設計(樣板 SSP 模型)
12228389 feat(FR-036): 地基 schema migration(dev 已套)
5a00b2eb feat(FR-036): M3 資料遷移 script(dev 零丟失)
b82d62e4 feat(FR-036): M4 啟動專案 clone 換源
b7afacdd docs(FR-036): M5 + Phase 2 計畫
9aca4a16 feat(FR-036): M5a 控制項預設改寫樣板 SSP(參考樣板)
fe8859e9 feat(FR-036): M5a AO 預設改寫
8e0913d3 feat(FR-036): M5a leveraged/components/inventory/system_characteristic 改寫
b797fc57 feat(FR-036): M5c 新建 module_frame 建樣板 SSP
222bc8f2 feat(FR-036): M5b 程序書改寫
bdf284f6 feat(FR-036): M5d 匯出/資源讀取改源
f04ec7c7 fix(FR-036): update_module_frame 保留 template_ssp_id(修 metadata 編輯洗掉連結)
6f2e1d84 docs(FR-036): P2a/P2b/P2c 詳細計畫
(+ 本 handoff commit)
注:
eaf6a975是中途進度 doc,已被後續取代。
jedi-oscal(未 push):
4735b84 feat(ssp): is_template 欄位
d7155ec feat(ssp): list/get-by-fields 預設排除樣板 SSP
c62ff62 feat(ssp): add_empty_ssp 支援 is_template / version_no
讀 docs/features/FR-036-2606-module-frame-ssp-merge/handoff/2026-06-10-phase2-handoff.md
了解現況(Phase 1 已完工驗證,接手做 Phase 2)。先跑 §5 pre-flight + §6 verify Phase 1,
再照 implementation-plan §P2a 開始做匯入/填值改源。branch=feature/oscal-refactor,不切 branch、不自動 push。