🟢 START HERE — FR-038 Wave 2A(修正版)冷接交接:gut-and-disable

✓ FIXED — 2026-06-14 收尾完成(三綠燈已驗)

本波 2A 已執行完畢create_app() BOOT OK on v2 + python -m pytest test/ 106 紅 ≤ baseline 117(zero new regression)+ My Jobs view 回 131 列。

  • 收尾彙整見 changelog docs/changelog/2026-06-14-tweak-fr038-wave2a-oscal-v2-foundation-flip.md + 2B handoff docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-14-2B-START-HERE-handoff.md
  • §5.4 disable 清單已填最終版(見下)。接手 2B 者看 2B handoff,不要重跑本波。
  • 比 handoff 原估大很多:實際是把整個 live GRC 產品的舊 OSCAL 資料地基剝離(~56 leaf 檔 + 9 container),GRC 全 dark 到 2B;27(實 9 檔 55 nodes)test 轉 skip。已徵得 user 同意照原 scope 全切。

給下個 session 的 prompt:「讀 docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-14-2A-corrected-gut-and-disable-handoff.md,跑完 §6 pre-flight 確認現況,接手執行修正版 2A。」 本檔自包含 —— 看完 + 跑完 §6,就能正確開工,不需 user 再解釋。所有狀態為 2026-06-14 verified(現場跑指令,非假設)。 本檔取代 2026-06-14-WAVE2-START-HERE-handoff.md 對 2A 的描述(該檔把 2.0a 框成「~40 檔 import-rename」,經驗證是錯的,見 §2)。

項目
緣由 FR-038 OSCAL 重設計 Wave 2A = 主專案 BE 從舊 jedi_oscal 翻到 jedi_oscal_v2地基翻轉(不是業務實作)
branch(兩 repo) feature/oscal-refactor(已 push;main origin 5129f57f、套件 origin 3096f1f
本棒目標(綠燈) create_app() 能 boot 在 v2 上 + python -m pytest test/ 不比 baseline 多紅 + My Jobs view 出資料
範圍 只 BE,只到 boot+綠。B1~B5 業務流程在 2A 乾淨 disable,留 2B/2C
接手前必讀 本檔 → §0 讀序

🧭 開工前必懂:FR-038 原始需求要幹嘛(讀完這節 + §0 讀序的需求文件,能用一句話複述目標,才准碰 code)

2A 只是地基翻轉,但你必須先懂它服務的那個大目標 —— 否則會像「只改 import」一樣做錯方向。 本節是自包含的 WHY;細節去讀 requirement-analysis.md(§0~§4)+ design.md(§3)。

一句話:把現在「只套 OSCAL 概念、底層大量自定義欄位、拿 AP 兼當稽核輪次」的舊實作,打掉重練成「正式 OSCAL v1.2.2 物件模型落地、三層 clone/snapshot 邊界清楚、稽核輪次獨立成 first-class」的專業 OSCAL GRC 系統。

為什麼要重練(舊實作 6 大結構性問題,requirement §1)

  1. SSP/AP 大量自定義欄位偏離 OSCAL → 無法無損匯出標準 OSCAL、跟外部工具不互通。
  2. 用 1 個 AP 兼當整個稽核 arc、多輪靠 run_no 疊加 → 輪次語意藏在 AR 子表、二次稽核/覆核難建模(user 明指「這設計不對」)。
  3. catalog 階層 + AO 落地不標準(AO 不是用 OSCAL part 表達)。
  4. Profile 只存 include 旗標 + runtime join 回會變動的母表 → 母版改動會抽換進行中的工作。
  5. 三層 clone 邊界不乾淨(MF defaults 與 template SSP 兩套並存)→「單一真相來源」不成立。
  6. AR 只有逐條 verdict、AP 幾乎不存在、無系統風險總結 → 稽核生命週期 Phase 3(外部稽核員寫 AR/findings/risks)斷掉,而這正是市場空白、本次核心價值

目標新模型(你切 2A 是在為這個鋪地基)

  • 三層 + 四個 clone/snapshot 邊界:框架母版(catalog/profile) →①resolve→ 資源庫三件組(catalog+profile+ssp) →②clone脫鉤→ 專案副本(★單一真相) →③啟動稽核 snapshot→ 凍結 SSP 快照。鐵則:副本存內容或指版本化母版,不可只存 id 即時 join 回會變動的母表
  • 稽核輪次 first-class = compliance.project_audit_rounds(7 態狀態機),取代「AP 當輪次」。engagement 模型:initial/surveillance 起新 AP+AR;close-out 覆核沿用母輪 AP+AR、append ar_results(自帶 narrowed reviewed-controls);parent_round_id 串血緣。
  • Phase 3 補齊:AP 全新(草稿自 SSP 快照生成 + reviewed-controls/抽查名單/行程方法)、AR 下沉到 AO 全量判定矩陣(每個 in-scope AO 一筆 met/not_met/pending)+ 稽核員手動組 風險總結(多對多 link findings)、POA&M 整改三層(remediation→milestone→assignee)+ 結案/覆核。
  • 套件 vs 主專案邊界(design §4.4):jedi_oscal_v2 只管 OSCAL 物件 CRUD/resolve/snapshot/clone/匯入匯出;輪次狀態機、權限(manager/auditor)、workflow/job 綁定(Q1:綁 SSP 控制項而非 AP task)、project_audit_rounds 留主專案
  • 任務執行/My Jobs(Q1):受評公司「準備 SSP/收證據」的工作,job 綁專案 SSP 控制項、專案成立就建,與 AP/輪次無關 —— 這就是 2A 要重寫 vw_user_job_queue 的原因(舊 view 綁 AP task,概念已換)。

這套東西怎麼分波:Wave 1(套件,已完成 194 綠) → Wave 2 = B1~B5 主專案 BE 把上述業務模型落地 → Wave 3(FE)。2A = Wave 2 的地基翻轉:讓 BE 從舊套件翻到 v2 能 boot,B1~B5 業務先 disable,2B/2C 才真正照上面的新模型實作。**所以 2A 不寫任何業務邏輯,但你切的時候要知道每個被 disable 的東西,2B 會照哪個需求(api-contract §1~§8 對應)重建。**

冷接自檢:讀完本節 + requirement §0~§4 + design §3,你應能回答:① 為何打掉重練(舊 6 問題)② 新模型三層邊界與輪次 engagement 長怎樣 ③ 2A 在這張大圖的位置(地基,非業務)④ My Jobs 為何要重寫。答不出來 → 回去讀,別碰 code。


⛔ 本棒範圍:gut-and-disable 到綠燈就停(別做 B1~B5 業務)

2A 不是做 framework/資源庫/專案成立/SSP/AP/AR/POA&M 的業務邏輯(那是 B1~B5 = 2B/2C)。2A 只做三件事:

  1. OscalContainer 砍舊 provider、wire v2 primitives(2B 要站的地基)。
  2. 未遷的 OSCAL 業務面乾淨 disable(routes + 其 provider + 6 個互注 container 的 oscal 引用),並在本檔 §5.4 維護精確 disable 清單給 2B。
  3. My Jobs(vw_user_job_queue)重寫對新「SSP 控制項」綁定(Q1)—— 這是 2A 唯一必須真的能動的 OSCAL 相鄰流程。

為什麼 2A 必須一次切完才有綠點:見 §2 陷阱 1(新舊套件無法共存)。沒有 booting 中間態,要嘛全舊、要嘛全新,無法分批 commit。本棒請用滿格 context budget 一口氣切到 create_app() boot。


§0 接手讀序(按序,不要跳;第 1~3 是「先懂需求」硬 gate,沒讀完不准碰 code

過去 fresh agent 接手「一知半解」的根因 = 只讀機械步驟、沒讀原始需求。本檔特意把需求理解放在最前面當 gate。

  1. 本檔「🧭 開工前必懂」節(自包含 WHY)+ 通讀本檔(現況/陷阱/做法/步驟/清單)
  2. 🔒 gaterequirement-analysis.md §0~§4 全讀(§0 一句話總結、§1 舊 6 問題、§2 三層+四邊界、§3.3 輪次 7 態狀態機、§4.x 新舊業務流程對照、§4.3b Q1 job 綁定)
  3. 🔒 gatedesign.md §3(engagement 模型:initial/surveillance/close-out 與 AP/AR/ar_results 對應)§4.2(v2 套件對外 service 簽章契約)§4.4(套件 vs 主專案邊界)
    • 讀完第 2~3 後,先用「🧭」節末的冷接自檢 4 問自測;答不出來回去讀,別碰 code。
  4. api-contract.md §10(My Jobs 綁定點後端改)+ §1~§8(B1~B5 端點 — 2A 全 disable,2B 照此重建,所以 2A disable 時要對齊這裡的端點清單
  5. wave2-migration-plan.md(Wave 2 phase 拆解;2.0a 的「import-rename」框架已被本檔取代
  6. 本檔 §5 的各清單(boot 必經面 / v2 wiring map / disable 清單 / My Jobs)

§1 現況(2026-06-14 verified,現場跑指令)

  • branch feature/oscal-refactor,兩 repo 與 origin 同步;working tree 僅 M pyproject.toml(dev path-dep,刻意保留勿 commit)。
  • 本 session 唯一落地的 commit-able 變更 = 一條 memory project_oscal_v1_v2_cannot_coexist.md(+ MEMORY.md 一行索引);OscalContainer 等程式碼未動(曾試加 v2 block 觸發 collision 後已還原乾淨)。
  • jedi_oscal_v2 套件~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/,194 tests 全綠,已 push。對外 14 app service + 3 domain service,全部自建 repo(repo or RepoImpl()),可零參數 wire
  • jedi_oscal 0.0.22 仍裝著、import 仍能過;主專案 109 個 source 檔仍 import 舊 jedi_oscal、0 檔用 v2。
  • dev DBoscal schema 48 表(base 42 + delta 6)、compliance.project_audit_rounds 在(7 態 CHECK + 4 FK)。新 schema + 舊 code 不一致 → OSCAL runtime 現在是壞的(預期中間態,不是 bug)。
  • BE 現狀(舊 code)其實 boot 得起來(見 §6 指令)—— OSCAL 只是 runtime 壞,不是 import 壞。2A 是「邊翻邊保持能 boot」的終點翻轉,不是把死掉的 BE 救活。

pytest baseline(python -m pytest test/ --continue-on-collection-errors

  • 518 passed, 67 failed, 50 errors + 9 個 collection-abort 檔。
  • 這些紅大多是既有、與 OSCAL 無關的技術債:9 個 collection-abort 是 stale test 指向不存在 module(app.ai_dashboard.service.compute_engine / metadata_builder / promptsdomain.module_frame.entity.module_frame_control_default_entity);50 errors 多為 AI dashboard / flow template / google drive fixture。
  • 綠燈定義:2A 改完後 python -m pytest test/ 不得比此 baseline 紅。只有 3 個 test 檔 import 舊 jedi_oscal(boot 的測試障礙很小):
    • test_api_catalog_control_assessment_route.py
    • test_module_frame_template_import_service.py
    • test_oscal_project_service_template_copy_integration.py

§2 ⚠️ 會讓你做錯的陷阱(這些是本 session 踩過 / 驗過的,務必先看)

  1. 【最關鍵】新舊套件無法在同一 process 共存 — 舊 jedi_oscaljedi_oscal_v2 的 ORM model 撞在 jedi_common 同一個 declarative Base 的 MetaData 上,兩邊都註冊 oscal.catalogs 等同名表。一旦同 process 同時 import 到兩邊 model → sqlalchemy.exc.InvalidRequestError: Table 'oscal.catalogs' is already defined後果:不能「新舊並列 additive 共存遷移」;big-bang 全切是被強制的;無 booting 中間態(半切必倒),所以一次切完才有可 commit 的綠點。extend_existing=True 不可用(會 merge 兩套不同欄位的表 → 汙染)。記憶 project_oscal_v1_v2_cannot_coexist
  2. 【方向】2A 是 gut-and-disable,不是 stub-everything — 本 session 曾想「用 flat compat 模組把 154 個舊符號全 stub、109 檔統一改指 compat」讓它 import-resolve,這是錯的:會把本該接真 v2 的東西(framework/catalog/profile,B1 只是 API 對齊)也 stub 成假的、留一坨 dead weight 給 2B 反拆、抹掉「Type A 接真 v2 / Type B-C disable」分界。正解:v2 primitives 接真的、未遷業務面乾淨 disable。
  3. wire() 急切 import 全部 route + 解析全部 Provide[]config/di_modules.py:get_di_wire_modules() 自動掃 api/**/routes/*_route.py 全 wire。所以任一 OSCAL route 的 top-level import 碰到已移除符號、或 Provide[Containers.oscal_container.<已移除 provider>],都會在 create_app() boot 時炸。disable route 要從「不被 wire / 不 import 舊符號」下手(見 §5.4)。另 create_app() line ~158 register_stage_hooks_to_registry 會存取 project_container.oscal_audit_service,該鏈也是 boot 必經。
  4. 本機環境缺 gitlab/github 參數,會擋在 OSCAL 之前.envGITLAB_*/GITHUB_PRIVATE_TOKENjedi_issue 在 import 時建 gitlab/github client 會炸(gitlab.vNone.objects / Auth.Token assert)。驗 boot 必須補 dummy env(見 §6)。這不是 OSCAL 問題,不要去修 jedi_issue。
  5. boot 驗證要用真入口前置 — 沒有 eventlet.monkey_patch + sys.setrecursionlimit(5000) 會誤觸 NonCopyableArgumentError(system_config DI deepcopy)假象。§6 指令已含前置。
  6. pytest 要用 python -m pytest test/ — 純 pytest 缺專案根在 sys.path → 全 No module named 'app'testpaths 在 pyproject 指 tests/(另一個目錄),要測的是 test/(單數,60 檔)。
  7. 套件方法簽章以實際為準 — §5.2 列的 v2 service 簽章是 2026-06-14 verified,但開工前仍 pre-flight 複讀(計畫到開工有時差)。

§3 修正後的 2A 做法(gut-and-disable)

目標終點create_app() 在 v2 上 boot + pytest 不多紅 + My Jobs 出資料。B1~B5 業務面在 2A 全 disable。

切的順序(一次做完,因無 booting 中間態):

A. OscalContainer 重寫
   - 移除全部 from jedi_oscal.* import + 舊 provider
   - wire v2 primitives(§5.2 map,零參數 Factory/Singleton)
   - 外部引用的舊 provider 名(§5.1):能對應 v2 的改 v2;不能的 → 連同其 consumer 一起 disable
B. 6 個互注 container(associations / project_participant / module_frame /
   workflow_execution / project / task_assignee / grc)
   - 移除/repoint 對 oscal_container 舊 provider 的引用(§5.1 第一份清單)
   - 引用已移除 provider 的 consumer service(B1~B5 業務)→ disable 該 provider + 其 route
C. OSCAL routes(api/oscal/routes/* + api/grc、api/module_frame、api/project 內 OSCAL 相關)
   - B1~B5 業務 route:disable(見 §5.4 三種 disable 手法)
   - 確保 wire() 掃到的每個 route 不 import 已移除符號、不 Provide 已移除 provider
D. My Jobs:重寫 vw_user_job_queue + ORM mirror + repo(§5.5)
E. 3 個 import jedi_oscal 的 test 檔(§1):disable / skip / 改 v2
F. boot loop(§6)→ create_app OK;pytest(§1)→ 不多紅
G. 維護 §5.4 disable 清單(2B 照此逐一 re-enable 重建)

判斷「接真 v2」vs「disable」的準則

  • 該 provider/route 對應 B1 框架/資源庫的純 OSCAL CRUD(framework/catalog/profile/ssp 基本讀寫)且 v2 有對應 service → 可考慮接真 v2(但仍可先 disable,2A 不要求 B1 能動,求 boot+綠最省力)。
  • 對應 B2~B5 業務流程(專案成立 clone、輪次狀態機、AP 草稿、AR 矩陣、POA&M)→ 一律 disable,2B/2C 實作。
  • 最省力到綠:2A 可把整個 OSCAL 業務 route 面 disable,只留 v2 primitives 在 container 當地基 + My Jobs 能動。B1 的「接真 v2」也可留給 2B(2A 不強制)。

§4 開工順位(步驟)

  1. 跑 §6 pre-flight,確認現況與本檔一致(branch / 兩 repo pushed / 套件可 import / 109 舊 import / DB schema / 舊 code 現在 boot 得起來)。
  2. 讀 §0 讀序 2~6。
  3. 依 §3 A→G 一次切到 boot。每改一塊跑 §6 boot 指令看錯往前推(boot loop)。
  4. boot OK 後跑 §1 pytest baseline 比對,不多紅。
  5. 驗 My Jobs view 出資料。
  6. 到綠燈停,給 user 一句話 status + 手測 checklist,收尾文件留 working tree 不 commit(§8 / §9)。push / changelog / Notion 等 user 明示。

§5 清單(self-contained,照這些切)

§5.1 boot 必經面:其他 container / route 引用的舊 oscal provider(這些不處理就 boot 不了)

其他 container 從 oscal_container 拉的 provider(移除這些 provider 前必先 repoint/移除引用端)

assessment_plan_service(9) assessment_plan_task_domain_service(6)
assessment_plan_group_domain_service(4) assessment_plan_control_domain_service(4)
system_characteristic_domain_service(3) ssp_domain_service(3)
control_implementation_objective_domain_service(3) control_implementation_domain_service(3)
component_domain_service(3) assessment_result_service(3)
ssp_service(2) ssp_document_pool_query(2) oscal_responsible_party_domain_service(2)
oscal_party_domain_service(2) leveraged_authorization_domain_service(2)
inventory_item_domain_service(2) assessment_result_finding_domain_service(2)
assessment_result_data_domain_service(2) assessment_result_control_domain_service(2)
assessment_plan_domain_service(2)
system_implementation_main_domain_service(1) ssp_versioning_service(1) ssp_project_resolver(1)
profile_service(1) oscal_framework_version_service(1) oscal_framework_service(1)
metadata_domain_service(1) catalog_service(1) catalog_group_domain_service(1)
catalog_control_domain_service(1) assessment_task_control_domain_service(1)

(重跑:grep -rhoE "oscal_container\.[a-z_]+" di_containers/ --include="*.py" | sort | uniq -c | sort -rn

route 層 Provide[Containers.oscal_container.<x>] 引用的 provider(disable route 前要處理):

ssp_control_implementation_service oscal_framework_service ssp_document_pool_service
ssp_excel_import_app_service oscal_framework_version_service framework_version_edit_service
framework_parse_job_service assessment_plan_task_service ssp_resources_app_service
ssp_permission_checker ssp_party_app_service ssp_leveraged_app_service
ssp_inventory_items_app_service ssp_docx_import_app_service ssp_control_impl_import_service
ssp_components_app_service module_frame_ssp_resources_service module_frame_party_service
module_frame_leveraged_service module_frame_inventory_service module_frame_components_service
ssp_system_characteristic_app_service ssp_export_app_service oscal_import_service
module_frame_system_characteristic_service assessment_result_service profile_service
module_frame_template_ssp_app_service catalog_control_assessment_service

(重跑:grep -rhoE "oscal_container\.[a-z_]+" api/ --include="*.py" | sort | uniq -c | sort -rn

註:上面大多是主專案 app service(在 app/oscalapp/module_frame),不是 jedi_oscal —— 它們內部 import 舊 jedi_oscal domain service,是 B1~B5 業務碼,2A disable

§5.2 v2 wiring map(接真 v2 用,全部零參數,2026-06-14 verified class 名 + 簽章)

v2 import path class
jedi_oscal_v2.app.service.framework.framework_service FrameworkService
jedi_oscal_v2.app.service.catalog.catalog_service CatalogService
jedi_oscal_v2.app.service.profile.profile_service ProfileService
jedi_oscal_v2.app.service.ssp.ssp_service SspService
jedi_oscal_v2.app.service.ssp.ssp_clone_service SspCloneService
jedi_oscal_v2.app.service.ap.assessment_plan_service AssessmentPlanService
jedi_oscal_v2.app.service.ar.assessment_result_service AssessmentResultService
jedi_oscal_v2.app.service.ar.assessment_risk_service AssessmentRiskService
jedi_oscal_v2.app.service.poam.poam_service PoamService
jedi_oscal_v2.app.service.poam.remediation_service RemediationService
jedi_oscal_v2.app.service.snapshot.oscal_snapshot_service OscalSnapshotService
jedi_oscal_v2.app.service.snapshot.metadata_clone_service MetadataCloneService
jedi_oscal_v2.app.service.snapshot.oscal_clone_service OscalCloneService
jedi_oscal_v2.app.service.io.oscal_io_service OscalIoService
jedi_oscal_v2.domain.service.ap.ap_draft_service ApDraftService
jedi_oscal_v2.domain.service.profile.profile_resolution_service ProfileResolutionService
jedi_oscal_v2.domain.service.ar.ar_finding_matrix_service ArFindingMatrixService
jedi_oscal_v2.ports.oscal_parser_factory get_oscal_parser_adapter(code)

全部可 providers.Factory(V2XxxService) 零參數(service __init__repo=None 自建)。repo 若 2B 要單獨注入再加(path jedi_oscal_v2.infra.repository.<domain>.<name>_repo_impl)。

核心 method 契約(design.md §4.2,簽章 pre-flight 複驗):

  • Framework: add/get/list/update_frameworkadd_framework_versionlist_versionspublish_version
  • Catalog: get/list_catalogget_control_treelist_aos(catalog_control_id)AO 已搬到 catalog_control_parts
  • Profile: add_profileresolve_profile(邊界①)
  • Snapshot: clone_resource_library(邊界②)、snapshot_ssp(邊界③)、deep_clone_ssp
  • SSP: add_empty_sspget/update_ssp + 子物件 CRUD
  • AP: create_ap(import_ssp_id)generate_draft(ssp_id)set_reviewed_controlsset_assessment_subjectsset_tasks
  • AR: add_ar(import_ap_id)add_resultinit_finding_matrixupsert_findinglist_findings(AO 全量矩陣)
  • Risk: add_risklink_findingslist_risks
  • POA&M: generate_from_findingsupsert_remediationupsert_milestonelist_*
  • IO: export_oscal(doc_type, uid)

§5.3 舊→新結構落差(為何很多不是 import-swap 而是 disable)

v2 收掉了舊套件的 domain-service 層(舊 ~30 個 → v2 只剩 3 個 domain service),且結構改:

  • domain.services.*(control_implementation / system_characteristic / party / leveraged / component / inventory / catalog_control_assessment / ap_task / ar_data …)→ v2 無對應,consumer 改吃 repo 或 disable。
  • AO:舊 catalog_control_assessments → v2 catalog_control_parts
  • AP task:舊 assessment_plan_tasks → v2 ap_tasks(結構不同)。
  • AR data:舊 assessment_result_datas沒了(改 ar_results + finding 矩陣)。
  • entity/model class 名全變(OscalSystemSecurityPlanOscalSspOscalAssessmentPlanOscalApAssessmentPlanEntityApEntity …)。
  • 舊有 154 個被 import 的 symbol(重跑見本 session 紀錄);多數無乾淨 v2 對應 → 其 consumer 是 B1~B5 業務 → disable。

§5.4 disable 清單(2A 維護,2B 照此 re-enable)— 開工時逐項填

三種 disable 手法(擇省力):(a) config/di_modules.py EXCLUDE_MODULES 加該 route module(最乾淨,route 不被 wire);(b) route 檔 body 清空只留 blueprint 殼 + 移除舊 import;(c) container provider 直接刪/註解 + 移除引用端。

規則:每 disable 一個 route/provider/service,在此表記一行(模組路徑 / 手法 / 對應 2B phase B?)讓 2B 精準復原。

✅ 最終 disable 清單(2026-06-14 收尾填,2B 照此 re-enable)

手法代號:(a)=EXCLUDE_MODULES 不被 wire + create_module 停註冊 blueprint;(b)=route 檔層;(c)=container provider 刪/改 Object(None);(d)=leaf service/repo/model stub 成 import-clean。

A. Route 全 disable(30 條,手法 a)→ 2B-B1/B2/B3/B4/B5

  • config/di_modules.py EXCLUDE_MODULES 加 30 條(23 api.oscal.* + 7 api.module_frame.*,清單見該檔)。
  • api/oscal/__init__.py create_module() 整檔簡化成空 blueprint(所有 oscal route)。2B 逐一重建 route 後恢復。
  • api/module_frame/__init__.py create_module() 移除 7 條 import + add_resource:party / ssp_resources / system_characteristic / leveraged / components / inventory / mf_ssp_export。

B. OscalContainer(手法 c)→ 2B 全部 re-wire

  • di_containers/oscal/oscal_containers.py 整檔換成 18 個 v2 primitive。舊 ~80 provider 全移除,2B 依 design §4.2 契約把業務 app service 重新接 v2。

C. 互注 container 內舊 oscal provider/注入(手法 c)→ 2B re-wire

  • associations_containers.py:移除 AP-task repo/domain-service/service(5 provider)+ workflow-exec-mapping service 的 assessment_plan_task_service 注入。
  • workflow_excution_containers.py:移除 oscal_control_mapping repo/domain-service + workflow_execution_service 的 assessment_plan_service / assessment_plan_task_domain_service 注入。
  • module_frame_containers.py:catalog/profile/ssp/framework/AP/control-impl 全系列 provider 改 Object(None)
  • grc_containers.pyregister_stage_hooks_to_registry 改 try/except(dark handler 跳過)。stage handler/precondition provider 仍 reference 已移除的 oscal_container.X(lazy,resolve 時失敗被 skip)→ 2B re-wire 後恢復註冊。

D. Leaf service/repo/model stub(手法 d)→ 2B 重建內部邏輯

區塊 檔案 處置
GRC infra repo infra/grc/repository/ 17 檔(grc_job/dashboard/control/control_group/audit/project/review/task_setup/assessment_object repo + auditor_dashboard/job_batch_complete/job_export/job_import_lookup/oscal_audit/ssp_document_pool query + assessment_plan_extension/poam repo) import-clean,query 方法回空
GRC service app/grc/service/:oscal_stage_handlers / oscal_stage_preconditions / poam_service / project_current_ssp_service / project_service / assessment_object_service stub(precondition 回 ok、其餘空回傳)
Project service app/project/service/:oscal_project_service / oscal_audit_service / project_system_info_service stub
OSCAL service app/oscal/service/ssp_versioning_service + domain/oscal/service/ssp_project_resolver stub / resolve 拋 NotFound
Module frame app/module_frame/:dto + service(control_default / control_objective_default / import / reference_document / template_import / ssp_import_template / module_frame_service)+ infra/module_frame/repository/module_frame_repo_impl stub
Associations app/associations/dto/ 3 dto + app/associations/service/assessment_plan_task_workflow_execution_mapping_service(AP-status method no-op)+ infra/associations/mapper/ 5 mapper + infra/associations/repository/profile_assessment_workflow_repo_impl stub
ORM model relationship infra/associations/model/:assessment_plan_task_workflow_execution_mapping / assessment_plan_task_workflow_mapping / project_assessment_plan_mapping / project_system_characteristic / profile_assessment_workflow_mapping(後者 string-based) 移除對舊 OSCAL model 的 relationship,保留 FK 欄位
Flow engine infra/flow_engine/models/workflow_execution_control_mapping(移 relationship)+ app/flow_engine/service/workflow_execution_control_mapping_service(回空)+ app/flow_engine/service/workflow_execution_service(AP 觸點 no-op、live 流程保留) stub / no-op
Participant(live 保留可動) app/participant/service/:control_group / project_control / project_group / project_participant(AP/SSP 觸點 stub)+ task_assignee(batch_add stub,My Jobs 改走 view) AP-coupled 方法 stub,其餘照舊

E. My Jobs view(手法:SQL 重寫)→ 2B 接 v2 SSP 控制項

  • scripts/sql/view/vw_user_job_queue.sql:去掉 4 張舊 AP 表 join,control_/group_/ap_task_* 填 NULL。2B 把 control_* 接到 oscal.ssp_control_implementations(Q1)。

F. Test skip(9 檔,module-level)→ 2B re-enable 對應業務後移除 skip

test_ssp_versioning_service / test_oscal_stage_preconditions / test_oscal_project_party_clone_dedup / test_module_frame_template_import_service / test_api_catalog_control_assessment_route / test_oscal_project_service_template_copy_integration / test_oscal_project_service_flow_template / test_fr032_excel_system_assets / test_assessment_plan_extension_repo_impl。

§5.5 My Jobs:vw_user_job_queue 重寫(S4,2A 必須能動)

  • 檔案:scripts/sql/view/vw_user_job_queue.sql(view)+ infra/participant/model/vw_user_job_queue.py(ORM mirror)+ infra/participant/repository/task_assignee_repo_impl.py(consumer,line ~163 query(VwUserJobQueue))。
  • 不能 replay:現行 view join 了 4 張已不存在的舊 AP 表:
    LEFT JOIN oscal.assessment_plan_task_workflow_execution_mapping c
    LEFT JOIN oscal.assessment_plan_controls f ON a.control_id = f.id
    LEFT JOIN oscal.assessment_plan_groups e   ON f.group_id = e.id
    LEFT JOIN oscal.assessment_plan_tasks g    ON c.assessment_plan_task_id = g.id
  • 重寫方向(Q1 / requirement §4.3b):job 綁「專案 SSP 控制項」非 AP task。view 改對新來源 = compliance.job_executions + workflow_execution + task_assignees + 專案 SSP 控制項(oscal.ssp_control_implementations / catalog control)。ORM mirror 欄位(ap_task_* / group_* / control_* / project_* / user_* / job_)盡量維持讓 repo + FE 不動;ap_task_ 可暫填 NULL 或改 map 到 SSP 控制項語意。
  • migration 走 cmmgr + --single-transaction -v ON_ERROR_STOP=1,view 重建不需 GRANT(繼承),但若新建表記得 GRANT cm_app + INSERT public.schema_migrations
  • 驗:SELECT count(*) FROM public.vw_user_job_queue WHERE user_id=<測試用戶> 出資料 + My Jobs API smoke。

§6 Pre-flight + Boot 驗證指令(必跑,可複製貼)

cd /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be
# 1. branch + 兩 repo + working tree
git branch --show-current                 # feature/oscal-refactor
git status --short                         # 期望僅 ' M pyproject.toml'
( cd ~/Projects/Jedicogy/module/jedi-python-package && git status -sb | head -1 )
# 2. 套件可 import + 測試綠
poetry run python -c "import jedi_oscal_v2; print('v2 OK')"
poetry run pytest ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/tests -q | tail -1   # 194 passed
# 3. 遷移 surface
grep -rl "from jedi_oscal\b\|import jedi_oscal\b" --include="*.py" api/ app/ domain/ infra/ di_containers/ config/ common/ core/ | wc -l   # 109
grep -rl "jedi_oscal_v2" --include="*.py" api/ app/ domain/ infra/ di_containers/ config/ common/ core/ | wc -l                          # 0(開工前)

# 4. 【關鍵】BE boot 驗證(補 dummy env + 真入口前置,否則撞 jedi_issue / deepcopy 假象)
set -a; source .env; set +a
export GITLAB_API_VERSION=4 GITLAB_URL=http://localhost GITLAB_PRIVATE_TOKEN=dummy GITHUB_PRIVATE_TOKEN=dummy
poetry run python -c "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')"
# 開工前跑這條應印 'BOOT OK'(舊 code 現在能 boot);2A 切完也要這條 'BOOT OK' 才算綠

# 5. pytest baseline(綠燈基準,2A 後不得多紅)
poetry run python -m pytest test/ -q -p no:cacheprovider --continue-on-collection-errors 2>&1 | tail -2
#   baseline: 518 passed, 67 failed, 50 errors(+9 collection-abort 檔,皆既有非 OSCAL 技術債)

# 6. dev DB(密碼查 .env DB_SECRET.rds_master_password,勿落檔)
#   PGPASSWORD=... psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -tA -c "SELECT count(*) FROM information_schema.tables WHERE table_schema='oscal'"  # 48

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

  • 不切 branch(兩 repo 都在 feature/oscal-refactor;branch 不對停下問 user)。
  • 可自行階段性 commit(顯式 git add 檔名、-am;各 repo 分開);但 2A 無 booting 中間態,commit 點只有「boot 在 v2 上」之後
  • push / 收尾(changelog/SUMMARY/Notion/發版)等 user 明示
  • pyproject.toml path-dep 勿 commit(dev-only)。
  • 改 BE 後提醒 user 重啟 BE(無 hot reload);服務 user 自己起,別附啟動指令、別替 user 選 socketio/main_app。
  • 跨 schema FK 字串帶 schema 前綴(記憶 feedback_cross_schema_fk_must_qualify)。
  • SQL migration:cmmgr + --single-transaction -v ON_ERROR_STOP=1,新表 GRANT cm_app,收尾 INSERT schema_migrations。
  • plan 假設先 verify(v2 簽章 / 欄位)才開工。不晶晶體。

§8 收尾流程(boot+綠之後,且 user 下令才做)

  1. boot OK + pytest 不多紅 + My Jobs 出資料 → 給 user 一句話 status + 手測 checklist,
  2. user verify pass + 下令收尾才:填 §5.4 disable 清單最終版 → 寫 changelog(type=tweak/feat 視情況)→ 寫 2B handoff(B1~B5 照 disable 清單逐一重建)→ commit(顯式 add)→ 問 user push。
  3. 收尾不自動:design.md §11、Notion、套件發版皆等 user 明示。

§9 不在本棒 scope(不要順手做)

  • B1~B5 業務邏輯(framework/資源庫/專案成立 clone/SSP 維護/AP/AR/POA&M/輪次狀態機)—— 全 disable,2B/2C 做。
  • 套件發 Nexus(feature 全完成 + user 明示才做);pyproject.toml 改回 pin。
  • FE(Wave 3)。
  • 正式環境 schema 遷移(D7)。
  • 修 jedi_issue 的 gitlab/github env 問題(非 OSCAL、非本期)。
  • ISO/NIST、SSP 深層匯入匯出(D5 follow-up)。

§10 本 session 落地物 + 前期 commits

  • 本 session 唯一 commit-able 變更:memory project_oscal_v1_v2_cannot_coexist.md + MEMORY.md 一行索引(程式碼零變更;OscalContainer 試加 v2 後已還原)。working tree 僅 M pyproject.toml
  • 主專案(feature/oscal-refactor,origin head 5129f57f):dad80aef(契約) 0af2d862(Wave1 收尾) 5129f57f(Wave2 計畫+API契約) + 後續 docs handoff commits(b200ad52 系列)。
  • 套件(origin head 3096f1f):57 commits(Phase0→A5c2)。

§11 給 fresh session 的超短 prompt

讀 docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-14-2A-corrected-gut-and-disable-handoff.md。
先照 §0 讀序「先懂需求」硬 gate:讀 handoff「🧭 開工前必懂」節 + requirement-analysis §0~§4
+ design §3/§4.2/§4.4,能回答冷接自檢 4 問(為何重練/新模型/2A 位置/My Jobs 為何重寫)才往下。
然後跑 §6 pre-flight(含 BOOT OK + pytest baseline),接手執行修正版 2A:
gut OscalContainer 到 v2 primitives + 乾淨 disable B1~B5 業務面 + 重寫 My Jobs view,
一次切到 create_app() BOOT OK + pytest 不多紅 + My Jobs 出資料 就停。
B1~B5 業務邏輯不做(disable 留 2B)。push / 收尾 / Notion 等我明示。

冷接可行性自檢 ✅

下個 session 只看本檔 + §0 讀序 + 跑 §6 → 能確認現況(含 BE 現在 boot 得起來、collision 約束)、知道 2A 是 gut-and-disable 非 stub-everything、知道 v2 wiring map、知道 boot 必經面與 disable 手法、知道 My Jobs 重寫方向、知道綠燈定義與規範界線。不需 user 額外解釋即可正確開工。