FR-038 — 合規資源庫「適用控制項」SoA 頁 v2 遷移 進度 SUMMARY

狀態 2026-06-16。本檔是「FE 逐項套到 v2 BE」arc 中資源庫 §5 決策1(適用控制項改走範本 SSP SoA)這條的進度紀錄。 ⚠️ 本輪所有改動都還沒 commit(working tree;pyproject.toml dev path-dep 不 commit)。這是「進度追蹤」文件,非收尾 — changelog / Notion / 對話歸檔 / 橫向文件都還沒做

§1

🧭 這條 arc 在解什麼

FR-038 把 BE 翻成 jedi_oscal_v2,資源庫範本編輯頁(module-frame/<uid>/template-edit)整套建在舊 jedi_oscal 上、2A 後被 stub/disable。本輪把這頁逐功能接回 v2,原則:換底層、FE 盡量不動、保留原功能。

典範:資源庫 = clone catalog + profile + 範本 SSP 三件組(Model A);「適用控制項」= 編範本 SSP 的 control-implementation(IR)/ AO(statement)SoA。

§2

✅ 已完成且 curl 驗證通過

# 項目 做法 驗證
1 詳情樹顯示(group→control→AO) module_frame detail enrich oscal_profile.include_groups(新 resource_library_app_service.get_profile_control_tree,profile→cloned catalog→parts) ✅ 回 group/control/AO
2 profile id 對映 create 把 include_controls 數字 id → control_id 字串(profile resolve 以 control_id 比對、跨 clone 穩定) ✅ resolved_control_count 正確
3 template-ssp 端點 從 EXCLUDE_MODULES re-enable + 註冊 + 改走 get_template_ssp_ref ✅ 回 {ssp_uid,exists}
4 控制項層可編(control-defaults) service 重寫走 v2 ssp_service(IR.props 存 status/desc/role/origination,繞範本 SSP 無 project 的權限層) ✅ PUT 寫入+讀回+暱稱
5 AO 層可編(objective-defaults) service 重寫走 v2 statement(IR 下 statement.props) ✅ PUT AO 值+讀回
6 AO 初始化基礎 BPMN create 時每 AO 生 BPMN(Camunda7,含 Evidence upload 任務)+ workflow_template_uid 寫 part.props;樹讀回 ✅ 6 AO→6 workflow_template
7 編輯適用控制項(增減) update_module_frame 重寫 + update_applicable_controls(更新 profile with-ids + 移除被拔控制項 IR〔statements CASCADE〕+ 新控制項補 AO BPMN);FE dialog 改吃 catalog-tree + 用 control_id pre-select BE 邏輯已寫;待 FE 實點增減
8 程序書掛載 control/AO module_frame_reference_document_service 改走 v2 ssp_service(context_id = IR.id / statement.id) ✅ FE 實點驗過(2026-06-16)
9 程序書 picker 顯示/reload FE:顯示 fallback file_name、搜尋含 file_name、開啟時 reload pool ✅ FE hot-reload
§3

❌ 未完成 — 批次維護「匯入匯出」(另一塊獨立大工)

template-edit 頁「批次維護」選單全失效,多個不同根因,需獨立 task:

功能 根因
Excel 樣板下載 / 匯出現況 / 匯入現況 DI 炸:module_frame_template_import_service + SspImportTemplateAppService 引用 oscal_container 未定義control_implementation_domain_service(di_containers/module_frame/module_frame_containers.py:203-204, 251-252)→ 需 v2 重寫 + DI
從文件匯入(docx)/ 從 Excel 匯入 ssp_docx_parse_jobs / ssp_excel_parse_jobs 表在 v2 DB 不存在 → 需 DB migration
SSP 文件匯出(docx/pdf/odt) 待確認(log 那兩個錯是 import parse_jobs,非 export 本身)

這塊是 handoff 早標的「control-defaults 匯入匯出依賴 stubbed → 恐死」+ import parse-job 子系統,非本輪改壞。


§4

2026-06-16 續:docx 匯入逐項修復(同日後半段,全部 UNCOMMITTED)

把「從文件匯入(docx)」整條打通到「匯入內容顯示在範本編輯頁」。逐項根因都是 v2 換底層後某條路徑沒跟上。

# 症狀 根因 修法 狀態
A docx/excel 匯入 500 oscal.ssp_docx_parse_jobs / ssp_excel_parse_jobs 表在 v2 DB 不存在(oscal schema DROP 重建時漏掉主專案自有表) migration scripts/sql/2026-06-16-restore-ssp-parse-jobs-v2.sql(建 2 表 + RLS + GRANT),已套 DEV ✅ user 驗過
B 預覽頁整片空白 get_parse_result 把資料包進 parsed_result key,但 FE 讀最上層(v1 是 **parsed_result 攤平) 還原攤平 ✅ user 驗過
C 人員/單位 0 筆 docx 用「修訂模式」填,值在 <w:ins>,python-docx 讀不到;FR-038 P3 重寫 app service 時刪掉了 _normalize_docx_revisions 還原 _normalize_docx_revisions(接受修訂後再解析) ✅ user 驗過
D 新建資源庫「沒勾控制項」誤報 + 建立打到停用端點 FE 抓 v2 永遠 null 的 catalog_control_uid;且 POST /module-frame(add_module_frame 在 v2 已 return None) FE 改抓 control_id + 改打 RESOURCE_LIBRARY_CREATE(對齊 ModuleFrame.vue) ✅ user 驗過
E 評估目標 0 / AO 現況沒帶 parser ao_map 用字母 key (a),v2 candidate.objective_keys 是 part_id ..._obj.1key in ao_map 永不命中 parser _map_ao_snippets:用 catalog 描述開頭 [a] 把 part_id 橋接回字母 ✅ user 驗過
F 發布資源庫「建立專案」灰掉 list dto 漏回 enable(FE disabled: !rowData.enable dto 補 enable ✅ user 驗過
G 匯入內容編輯頁讀不到(控制項/AO 現況空白) 匯入寫 by-component.description(OSCAL 標準),但編輯頁讀 IR.props 自訂 key 採 A:匯入也寫 IR.props implementation-description + statement.props(letter key (a) 對齊 aoLetterKey)。by-component 全遷移列大修正延後(見 analysis doc) ✅ 控制項/AO 現況 user 驗過
H 實作狀態沒帶出來 docx 無「狀態」欄,parser 解不出 → soa_props 沒東西寫 有現況描述者預設 implemented(之後編輯頁可改) ✅ user 初步驗過(2026-06-16)
I 既有資源庫重匯報錯 / 變 append confirm 對既有非空範本送 mode="create" → already-has-a-body 報錯;改 update 後 v3 實體(元件/人員 by title/name)又「只加不刪」累積 Replace:套件加 OscalIoService.clear_ssp_body,confirm 既有路徑改 decision-merge → clear → create ✅ user 初步驗過(2026-06-16)

架構決策(已歸檔)

  • SoA 實作值存放 IR.props vs by-component:本次採 A(維持 IR.props 全系統慣例,FedRAMP SoA pattern);by-component 全 OSCAL-native 遷移列獨立大修正延後(牽動範本+專案編輯頁+匯出+啟動專案+匯入 ~6 service)。決策軌跡 / 反悔條件 / 落點清單見 docs/analysis/2026-06-16-soa-implementation-storage-ir-props-vs-by-component.md

本段改動檔(UNCOMMITTED)

  • BE:app/oscal/service/ssp_docx_import_app_service.py(get_parse_result 攤平 / _normalize_docx_revisions 還原 / confirm Replace)、domain/oscal/parser/docx_parser_core.py(_map_ao_snippets)、app/oscal/service/import_adapter/docx_to_oscal_ssp.py(IR.props+statement.props+letter key+status 預設)、app/oscal/service/resource_library_app_service.py(dto enable)
  • 套件 jedi_oscal_v2(dev path-dep):OscalIoService.clear_ssp_body
  • FE:src/components/grc/ssp-docx-import-v2/SspDocxImportPage.vue
  • migration:scripts/sql/2026-06-16-restore-ssp-parse-jobs-v2.sql(已套 DEV)

仍未做

  • Excel 匯入:同一套寫法,推測有相同問題(IR.props / statement_id / 狀態預設)—— 未驗未修
  • SSP 文件匯出(docx/pdf/odt):未驗
  • by-component 全遷移(大修正,延後,見 analysis + Notion)
  • 收尾未做:commit / changelog / 對話歸檔 / 橫向文件
§5

改動檔清單(全部 UNCOMMITTED)

BE(12)

  • app/oscal/service/resource_library_app_service.py(get_profile_control_tree / get_template_ssp_ref / _init_ao_workflows / update_applicable_controls / create id 對映 + 保留 group/version/frequency/provider / list enrich)
  • app/module_frame/service/module_frame_service.py(detail enrich oscal_profile / update_module_frame 重寫)
  • app/module_frame/service/module_frame_control_default_service.py(重寫走 ssp_service)
  • app/module_frame/service/module_frame_control_objective_default_service.py(重寫走 ssp_service statement)
  • app/module_frame/service/module_frame_reference_document_service.py(mapping context 改 v2 IR/statement)
  • api/oscal/__init__.py(註冊 template-ssp)/ api/oscal/routes/module_frame_template_ssp_route.py(改走 resource_library)/ api/oscal/routes/resource_library_route.py(create 收 group/version/frequency/provider)
  • config/di_modules.py(template-ssp route re-enable)
  • di_containers/containers.py(oscal_container 接 workflow_execution_container)/ di_containers/oscal/oscal_containers.py(workflow_execution_container DependenciesContainer + resource_library 注入 workflow_template_service)/ di_containers/module_frame/module_frame_containers.py(control_default / objective_default / reference_document service 改注 ssp_service)

FE(4)

  • src/config/api/api.js(RESOURCE_LIBRARIES_LIST / RESOURCE_LIBRARY_CREATE / RESOURCE_LIBRARY 常數)
  • src/views/module_frame/ModuleFrame.vue(列表/新增改打 resource-library 端點 + 控制項 picker 改 catalog-tree + 框架欄防 null)
  • src/views/module_frame/ModuleFrameTemplateEditView.vue(程序書 picker 顯示/搜尋/reload)
  • src/components/grc/EditIncludeControlsDialog.vue(catalog-tree + control_id pre-select)
§6

待辦 follow-up

  1. 重啟 BE 驗 #7(編輯適用控制項增減)+ #8(程序書掛載) —— 本輪最後兩項只 curl/邏輯驗到一半。
  2. 批次維護匯入匯出:獨立 task(建 2 張 parse-job 表 migration + 2 service v2 重寫 + ssp-export 驗證)。
  3. 收尾未做:commit / changelog / 橫向文件(fe-apply-comparison.md 的資源庫列該翻成「核心 SoA done / 匯入匯出 pending」)/ Notion。
§7

測試資料(DEV 188/guidant_ai_dev)

本輪建的 verify 庫:verify-soa(57f82ee7) / verify-bpmn(f364552b) / 測試匯入合規資料庫(fb1bc3aa,13 程序書) 等;清理時可一併刪。