# 🟢 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`](../requirement-analysis.md)（§0~§4）+ [`design.md`](../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. 🔒 **gate**：[`requirement-analysis.md`](../requirement-analysis.md) **§0~§4 全讀**（§0 一句話總結、§1 舊 6 問題、§2 三層+四邊界、§3.3 輪次 7 態狀態機、§4.x 新舊業務流程對照、§4.3b Q1 job 綁定）
3. 🔒 **gate**：[`design.md`](../design.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`](../api-contract.md) §10（My Jobs 綁定點後端改）+ §1~§8（B1~B5 端點 — 2A 全 disable，**2B 照此重建，所以 2A disable 時要對齊這裡的端點清單**）
5. [`wave2-migration-plan.md`](../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 DB**：`oscal` 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` / `prompts`、`domain.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_oscal` 與 `jedi_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 之前** — `.env` 沒 `GITLAB_*`/`GITHUB_PRIVATE_TOKEN`，`jedi_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/oscal`、`app/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_framework`、`add_framework_version`、`list_versions`、`publish_version`
- Catalog: `get/list_catalog`、`get_control_tree`、`list_aos(catalog_control_id)`（**AO 已搬到 `catalog_control_parts`**）
- Profile: `add_profile`、`resolve_profile`（邊界①）
- Snapshot: `clone_resource_library`（邊界②）、`snapshot_ssp`（邊界③）、`deep_clone_ssp`
- SSP: `add_empty_ssp`、`get/update_ssp` + 子物件 CRUD
- AP: `create_ap(import_ssp_id)`、`generate_draft(ssp_id)`、`set_reviewed_controls`、`set_assessment_subjects`、`set_tasks`
- AR: `add_ar(import_ap_id)`、`add_result`、`init_finding_matrix`、`upsert_finding`、`list_findings`（AO 全量矩陣）
- Risk: `add_risk`、`link_findings`、`list_risks`
- POA&M: `generate_from_findings`、`upsert_remediation`、`upsert_milestone`、`list_*`
- 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 名全變（`OscalSystemSecurityPlan`→`OscalSsp`、`OscalAssessmentPlan`→`OscalAp`、`AssessmentPlanEntity`→`ApEntity` …）。
- 舊有 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.py`：`register_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 驗證指令（必跑，可複製貼）

```bash
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 額外解釋即可正確開工。**
