M1–M4 已完成並 commit(見 design.md / changelog)。本檔聚焦尚未做的 M5 與 Phase 2。 盤點結論:採 (b) 重用 jedi-oscal 既有 SSP 子表 domain service(不重造輪子、合 DDD)。所有需要的 provider 都已在
di_containers/oscal/oscal_containers.py註冊。
| 樣板 SSP 子表 | jedi-oscal domain service | 關鍵方法 | BE provider(oscal_containers.py) |
|---|---|---|---|
| ssp_control_implementations | ControlImplementationDomainService |
get_by_ssp_and_identifier(ssp_id, control_identifier)、get_all_by_ssp_id(ssp_id)、add/update/delete_by_id |
control_implementation_domain_service:367 |
| ssp_control_implementation_objectives | ControlImplementationObjectiveDomainService |
get_one(filter)、get_all、add/update/delete_by_id | control_implementation_objective_domain_service:372 |
| ssp_components | ComponentDomainService |
get_all/get_one/add/update/delete | component_domain_service:600 |
| ssp_inventory_items | InventoryItemDomainService |
get_all/get_one/add/update/delete | inventory_item_domain_service:605 |
| ssp_leveraged_authorizations | LeveragedAuthorizationDomainService |
get_all/get_one/add/update/delete | leveraged_authorization_domain_service:595 |
| ssp_system_characteristics | SystemCharacteristicDomainService |
get_by_ssp_id(ssp_id)、add/update |
system_characteristic_domain_service:352 |
| 程序書池 + mapping | ssp_document_pool_query(SspDocumentPoolQuery) |
list_mappings(context_type, context_id)、list_pool(ssp_id)、add/remove_mapping |
ssp_document_pool_query:503 (Singleton) |
共通 helper:module_frame_domain_service.verify_module_frame_exists_by_uid(uid) 回傳的 ModuleFrameEntity 已帶 template_ssp_id(M1 mapper 已補)。各 MF app service 解析 template_ssp_id 後用上表 domain service 讀寫。
control_identifier;objective 用 statement_identifier(ccai uid / letter)。不可用 AP task id(樣板沒有 AP)。SspDocumentPoolService 用 AP control id / AP task id 當 context_id(line 104 註解)。ssp_control_implementations.id / ssp_control_implementation_objectives.id。SspDocumentPoolService;要用 ssp_document_pool_query.list_mappings('control_implementation', 樣板 control_impl.id) 這種「樣板 row id」當 context_id。ModuleFrame*Dto.from_entity() 是 duck-typed,jedi-oscal 的 SSP 子表 entity 欄位名相容(control_identifier / implementation_status / … 都在),可直接餵。is_template=False 預設排除(M2):讀樣板要用 get_by_id / get_by_uid 或顯式條件,別走會被排除的 list; 或直接用 ssp_id(=template_ssp_id)查子表(子表查詢不經 ssp 主表的 is_template filter)。9aca4a16(control)/8e0913d3(其餘 4)/objective。c62ff62、BE b797fc57。oscal.ssp_reference_documents 無 title 欄位(只有 description)→ MF 程序書 title 對應 file_name(與專案 SSP 一致,M3 已只搬 description)。ssp_reference_document_domain_service 只有 list_by_context/add/delete_by_uid,缺 update + get_by_uid → 需補這兩個 method(+ repo)。ssp_document_pool_query.add_mappings/remove_mapping/list_mappings,context_id = 樣板自己的 control_impl.id(用 control_implementation_domain_service.get_by_ssp_and_identifier 解)/ objective.id。module_frame_reference_document_service.py(pool CRUD + control/objective attach/detach)。export/ssp_mf_content_loader.py(SSP 匯出主路徑):control/AO/refdoc 走 M5a/M5b service(已 re-point)、inventory 改用 M5a inventory service。module_frame_ssp_resources_service._resolve_ssp_id:改用 mf.template_ssp_id(不再 get_one(profile_id))。ssp_import_template_app_service(樣板下載填值 _populate_filled_data)—— 此 service 的 controls/ref_docs/inventory/SC/components 讀取 helper 與 Phase 2 的 import-confirm 路徑共用、且自行注入 *_default domain service。只 re-point fill 半邊會切碎共用 helper、風險波及 import path(Phase 1 *_defaults 仍在)。故與 Phase 2 import 改源一起做。Phase 1 期間下載填值仍讀 *_defaults(M3 snapshot;M5a 編輯後在此 service 顯示為遷移初始值,屬已知 Phase-1 限制)。每個 service 同一套改法(以 module_frame_control_default_service.py 為樣板,先做它驗證後複製):
__init__:把注入的 module_frame_*_default_domain_service 換成對應 jedi-oscal SSP 子表 domain service。mf = verify_module_frame_exists_by_uid(uid) → template_ssp_id = mf.template_ssp_id; 若 None 拋 BadRequestError(理論上不會:M3 已設、MF-create 會設,見 M5c)。list_all → get_all_by_ssp_id(template_ssp_id);get_one → get_by_ssp_and_identifier(...); upsert → get → 有則 update、無則 add(entity 帶 system_security_plan_id=template_ssp_id); delete → get → delete_by_id。_enrich_nicknames 不變。oscal_container.*;control/objective 在 module_frame_containers.py、其餘 4 個在 oscal_containers.py, 後者天生可直接 reference)。逐 service:control_default → control_objective_default → components → inventory → leveraged → system_characteristic。 每個改完跑 BE + 在資源庫 UI 實測該頁籤編輯/顯示,再做下一個。
module_frame_reference_document_service 寫入從 module_frame_reference_documents 改成 ssp_reference_documents(context_type='ssp', context_id=template_ssp_id),用 grc ssp_reference_document_domain_service(已有 list_by_context/add/delete_by_uid)。ssp_document_pool_query,但 context_id 用樣板 row id (control → 樣板 ssp_control_implementations.id;AO → 樣板 ssp_control_implementation_objectives.id), 不是 AP id。需要一支「resolve 樣板 control_impl/objective id by (template_ssp_id, identifier)」helper (control_impl_domain_service.get_by_ssp_and_identifier 已有;objective 用 get_one(filter))。_enrich_reference_documents 一併改用此路徑(context_id=樣板 row id)。module_frame_service.py 建立 module_frame 後,呼叫 ssp_service.add_empty_ssp(..., is_template=True) (需先在 jedi-oscal add_empty_ssp 加 is_template: bool=False 參數並傳入 entity)建出空樣板 SSP, 設 module_frames.template_ssp_id。否則新建 MF 無樣板 SSP,M5a 編輯會無處可寫。 (匯入建 MF 的 module_frame_import_service / docx 預選流程同樣要補。)
app/oscal/service/export/ssp_mf_content_loader.py、ssp_export_model.py:讀 *_defaults 改讀樣板 SSP 子表。app/module_frame/service/ssp_import_template_app_service.py 的 _populate_filled_data(MF-scoped 填值)改讀樣板 SSP。module_frame_ssp_resources_service.py:97、ssp_import_template_app_service.py:1002/1075 等 get_one(SystemSecurityPlanQueryEntity(profile_id=...)) 想拿樣板的,改傳 is_template=True(M2 預設排除)。三個 service 各自獨立、無共用 confirm orchestrator。讀(填值)與寫(匯入確認)在
ssp_import_template_app_service內共用部分 helper(如_load_existing_ctrl_map), 改的時候要整批一起、避免切半。共通 mapping 比照 M5a / M3(*_default欄位 →ssp_*欄位, key =mf.template_ssp_id;components/inventory/leveraged 帶 tenant_id + is_active)。
A. ssp_import_template_app_service.py(樣板下載填值,只讀不寫)— 6 個讀 helper 改讀樣板 SSP:
_fetch_ref_docs_mf_lookup() L740 + _build_ref_docs() L1415 → 樣板 SSP ssp_reference_documents(context_type='ssp', context_id=template_ssp_id)_build_sc_from_mf_default() L1011 → system_characteristic_domain_service.get_by_ssp_id(template_ssp_id)(已有 _build_system_characteristic_from_ssp)_fetch_mf_default_items() L959 → 樣板 SSP components + leveraged(已有 _fetch_items_by_ssp_id)_fetch_mf_inventory_entities() L933 → 樣板 SSP inventory(已有 _fetch_inventory_items_by_ssp_id / _fetch_system_assets_by_ssp_id)_load_existing_ctrl_map() L1389 + _load_existing_obj_map() L1410 → 樣板 SSP control_implementations / objectives(keyed by control_identifier /(control_identifier, statement_identifier))_populate_filled_data 解析 tssp=mf.template_ssp_id,SC/components/leveraged/inventory 直接走既有 _*_by_ssp_id helper;control/AO/refdoc helper 改吃 template_ssp_id。B. ssp_docx_import_app_service._run_mf_default_confirm() L1078-1268(匯入確認,DELETE-all + upsert)
mf_*_default_domain_service 改成 jedi-oscal domain service(keyed by template_ssp_id),entity 轉 ComponentEntity/InventoryItemEntity/LeveragedAuthorizationEntity/SystemCharacteristicEntity(帶 tenant_id + is_active)if source_type=='module_frame' and (mf_*_default ...) 一併調整module_frame_control_default_repo / _objective_default_repo L53-54)改寫樣板 SSP control_implementations / objectivesC. ssp_excel_import_app_service._apply_write_mf_defaults() L1707-1866(同 B 模式)
ModuleFrameWriteStrategy.write_excel_control_defaults(控制項/AO,L1619)改寫樣板 SSPD. DI(oscal_containers 993-996 / 1047-1051 / excel container):三個 import service 停止注入 mf_*_default_domain_service,改注入 jedi-oscal domain service(component/inventory/leveraged/system_characteristic/control_implementation/objective)。
_setup_ssp_system_implementation 的 _mf_has_defaults gate(oscal_project_service ~L665)改查 module_frames.template_ssp_id IS NOT NULL;移除舊式「profile 第一個 SSP 當 template」clone。ModuleFrameTemplateCopyService.__init__ 移除已不用的 4 個 *_default domain service 注入。*_defaults 子系統(破壞性、不可逆,安全底線)module_frame_*_defaults(7 張)+ module_frame_reference_documents/_mappings;移除整個子系統 model/repo/domain service/mapper/entity/query-entity/DTO/route。template_ssp_id 有值 → 編輯 → 啟動專案帶入。