# FR-038 OSCAL GRC 重設計 — 契約層 Design（Wave 0）

> 範圍：**契約層（Wave 0）** = DB schema 定稿 + jedi-oscal-v2 套件對外介面 + API spec 骨架。
> 這是所有平行開發的「前提合約」；本層定稿後，套件（Wave 1）/ BE（Wave 2）/ FE（Wave 3）才能真正平行開工。
> 上游需求基準：[requirement-analysis.md](requirement-analysis.md)（D1-D7 + Q1-Q4 決策已鎖定）。
> 撰寫日：2026-06-14

---

## 0. 本層交付物

| 交付物 | 檔案 | 狀態 |
|--------|------|------|
| v2 schema base（42 表，FR-037 產出）| [`../FR-037-.../oscal-catalog-and-roots-schema-v2.sql`](../FR-037-2606-oscal-schema-gap-audit/oscal-catalog-and-roots-schema-v2.sql) | 既有，沿用 |
| v2 schema delta（FR-038 補表）| [`oscal-v2-deltas.sql`](oscal-v2-deltas.sql) | 本層產出 |
| 套件對外介面 + API skeleton | 本文件 §3 / §4 | 本層產出 |

> **建置方式（D1：drop oscal 重建）**：dev 先 `DROP SCHEMA oscal CASCADE` → 跑 base → 跑 delta。最終發版時 base+delta merge 成單一 build script。

---

## 1. 架構總覽（一張圖看懂契約）

```
框架層(母版)                資源庫層(公版)              專案層(可編輯)            稽核凍結
─────────────              ─────────────              ─────────────            ─────────
frameworks                 [clone②]                   project_audit_rounds(輪次)
 └ framework_versions ─┐    catalog 副本               ├ living SSP ──[snapshot③]──▶ 凍結 SSP 快照
    └ catalogs ────────┼──▶ profile 副本   ──[clone②]─▶├ AP(per engagement)         (round.ssp_id)
       (control/AO樹)  │    SSP 範本                    ├ AR ─ ar_results[](每輪一筆)
                  [resolve①]                           └ POA&M(FK 指回 AR,不複製)
```

四個「複製並固定」邊界（PM 教材 §09）：① profile resolve → 資源庫 catalog 副本；② 資源庫三件組 → 專案三件組；③ living SSP → 凍結快照（每輪）；④ 框架改版 = 新 framework_version。
**鐵則**：副本存「內容」或指向「永不原地改的版本」，**不可只存 id 即時 join 回會變動母表**。

---

## 2. DB Schema 契約

### 2.1 沿用 base（42 表，七大 OSCAL root）
catalog 樹（catalogs/groups/controls/parts(AO)/params）、profiles(+imports)、component_definitions(+子樹)、system_security_plans(+12 子表)、assessment_plans(+reviewed_controls/local_objectives/activities/tasks)、assessment_results(+ar_results)、poams(+poam_items)、assessment-common(observations/risks/findings)、共用層(metadata/roles/parties/resources)。欄位 1:1 對齊 OSCAL v1.2.2，NOT NULL ⟺ required。

### 2.2 FR-038 新增 / 修改（delta，見 [`oscal-v2-deltas.sql`](oscal-v2-deltas.sql)）

| Delta | 內容 | 對應決策 |
|-------|------|---------|
| **D-1** | `oscal.frameworks` + `oscal.framework_versions` 兩新表；`catalogs.framework_version_id` FK | base 缺口 G1 / FR-038 §23-26 |
| **D-2** | `compliance.project_audit_rounds`（7 態 + `parent_round_id` + `assessment_plan_id` + `ssp_id` + `ar_result_id`）| Q4 / D2 |
| **D-3** | `oscal.ap_assessment_subjects`（抽查名單關聯化，從 ap_tasks.subjects JSONB 抽出）| D3 |
| **D-4** | `oscal.assessment_finding_risks`（finding↔risk 多對多）| Q2 |
| **D-4b** | `oscal.assessment_remediations`（response/整改計畫）+ `oscal.poam_milestones`（task/里程碑，每里程碑各別 assignee）— OSCAL `risk → remediations[](response) → tasks[]` 三層忠實落地 | PM2 |
| **D-5** | `oscal.catalog_controls.source_identifier`（跨框架溯源佔位）| D6 |

> `compliance.project_audit_rounds` 的 `status`/`round_type`/`parent_round_id⟺close-out` 已加 CHECK 約束（Q4 鎖定的值域 DB 層強制）。`catalogs.framework_version_id` 的 clone 語意已定：副本沿用指向作 provenance、控制項存內容不 join 回母版。

> base 既有的 `oscal.poams`（OSCAL 文件 root）與舊 `compliance.poams`（扁平產品表）並存語意：本期 POA&M 以 OSCAL `poams`/`poam_items` + `assessment_risks`(remediations/milestones) 為主，舊扁平表的去留在 BE sub-project（Wave 2）決定。

### 2.3 產品生命週期欄（status）保留決策
base 每個 root 表都有 `status varchar(20)`（draft/published/deprecated），註解標「待決策」。**本設計保留**：framework/catalog/profile/ssp 都需要 draft↔published 區分（資源庫只能選 published 版本，FR-038 §42）。

---

## 3. 稽核 engagement 模型（D2 落地細節，讀 schema 後的關鍵收斂）

讀 base schema 發現一個必須交代的細節：`oscal.assessment_plans.import_ssp_id` 是**單一 NOT NULL**，且 PM 教材 §01 明寫「**AP 對著的是那份定版快照，不是還在改的 SSP**」。這把 D2 的「1 project 1 AP/AR」**精修成「1 engagement 1 AP/AR」**：

**Engagement = 一個 initial（或 surveillance）輪 + 其下所有 close-out 覆核輪。**

| 輪次 | round_type | parent_round_id | AP | AR | ar_results |
|------|-----------|-----------------|----|----|-----------|
| #1 首評 | initial | NULL | **新建 AP-1**（import-ssp→#1 快照）| **新建 AR-1**（import-ap→AP-1）| 追加 result #1 |
| #2 覆核 | close-out | 1 | 沿用 AP-1 | 沿用 AR-1 | 追加 result #2（自帶 narrowed reviewed-controls）|
| #3 再覆核 | close-out | 2 | 沿用 AP-1 | 沿用 AR-1 | 追加 result #3 |
| #4 年度 | surveillance | NULL | **新建 AP-2**（import-ssp→#4 新快照）| **新建 AR-2** | 追加 result #1 |

為什麼這樣切：
- **滿足 OSCAL 硬規則**：AR.import-ap 單一、AP.import-ssp 單一 NOT NULL、results[] 是官方多輪機制。
- **滿足 PM 教材兩條看似衝突的話**：「AP 對著定版快照」（AP-1.import-ssp→#1 快照，是凍結的）+「在原 AR 後追加第二次」（覆核輪 append result 到 AR-1）。
- **滿足「覆核重新 snapshot」**：覆核輪仍 snapshot 當下 SSP 存 `round.ssp_id`，該 result 對著新快照判定；AP 的 import-ssp 維持 engagement 起點快照（系統識別 anchor），point-in-time 差異記在 result 層。
- **年度查核（surveillance）= 新 engagement**：SSP 已大幅演進，起新 AP/AR 才合理（避免 import-ssp 指向一年前的舊快照）。

`project_audit_rounds.assessment_plan_id` + `ar_result_id` 就是把這個 engagement 模型 wire 起來的兩個 FK。

> **跨 round 結案連動**：覆核輪 result 定版確認母輪 not_met 的 AO 過了 → 沿 `parent_round_id` 關閉母輪 POA&M → 母輪轉 `closed`。

---

## 4. jedi-oscal-v2 套件對外介面契約

### 4.1 定位
- 新資料夾 `~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/`，**import 名稱 `jedi_oscal_v2`**（dist `jedi-oscal-v2`）。
- 套件**只管 OSCAL 物件的存取與轉換**；業務流程（輪次狀態機、權限、workflow、跨 schema 業務表如 project_audit_rounds）留在主專案。
- DDD 四層沿用舊套件結構：`domain/`（entity/repository interface/service）、`infra/`（model/mapper/repository impl/adapter）、`app/`（app service/dto）、`ports/`（parser provider/factory）、`common/`（enum/code）。
- 開發期走 poetry path dependency；feature 完成才 bump + 推 Nexus。

### 4.2 對外 App Service（主專案 import 點）契約

> 簽章為「契約意圖」，細節 dto 在 Wave 1 各 model sub-project 定稿。所有 method 走 `@transaction`（由主專案 app service 開 scope，套件 method 為被呼叫端）。

| 模組 | Service | 核心 method（契約） |
|------|---------|--------------------|
| Framework | `FrameworkService` | `add/get/list/update_framework`、`add_framework_version`、`list_versions(framework_uid)`、`publish_version(version_uid)` |
| Catalog | `CatalogService` | `import_catalog_from_pdf(stream, framework_code, …)`、`get/list_catalog`、`get_control_tree(catalog_uid)`、`list_aos(catalog_control_id)` |
| Profile | `ProfileService` | `add_profile(imports, …)`、`resolve_profile(profile_uid) → resolved control set`（邊界①）、`get/list_profile` |
| **Snapshot/Clone** | `OscalSnapshotService` | `clone_resource_library(framework_version_uid) → (catalog,profile,ssp) 副本`（邊界②）、`snapshot_ssp(ssp_id) → frozen ssp_id`（邊界③）、`deep_clone_ssp(src_ssp_id)` |
| SSP | `SspService` | `add_empty_ssp`、`get/update_ssp`、子物件 CRUD（system-characteristics / parties / components / inventory / implemented-requirements / by-components / SoA props）|
| AP | `AssessmentPlanService` | `create_ap(import_ssp_id, …)`、`generate_draft_from_ssp(ssp_snapshot_id)`（AP 草稿自動生成）、`set_reviewed_controls`、`set_assessment_subjects`、`set_tasks` |
| AR | `AssessmentResultService` | `add_ar(import_ap_id)`、`add_result(ar_id, snapshot_ref) → ar_result_id`、`upsert_finding(ar_result_id, ao, state)`（AO 全量矩陣）、`list_findings(ar_result_id)` |
| Risk | `AssessmentRiskService` | `add_risk(ar_result_id, …)`、`link_findings(risk_id, finding_ids[])`（Q2 多對多）、`list_risks(ar_result_id)` |
| POA&M | `PoamService` | `generate_from_findings(ar_result_id)`（不複製，FK 指回）、`upsert_remediation(risk_id, …)`（response/整改計畫；CMMC 可自動建預設一筆）、`add_milestone(remediation_id, assignee_user_id, …)`（→ `oscal.poam_milestones`，D-4b）、`list_remediations(risk_id)`、`list_milestones(remediation_id)`、`list_poam_items` |
| Import/Export | `OscalIoService` | `export_oscal(doc_type, uid, fmt)`（JSON/XML/YAML）、`import_ssp_docx/excel`（落地 v2 schema）|

### 4.3 Parser Adapter（沿用 port 機制）
`ports/oscal_parser_factory.get_oscal_parser_adapter(code)` → CMMC / ISO / NIST adapter，`pdf_parser / excel_parser / convert_to_oscal_catalog_entity`。本期把舊 adapter 搬移並對齊新 catalog/group/control/part(AO) 結構（AO 落 `catalog_control_parts`、method 存 props）。

### 4.4 主專案 ↔ 套件 邊界（哪些留主專案）
- `compliance.project_audit_rounds` 狀態機、權限檢查（manager/auditor）、workflow/job 引擎綁定（Q1：綁 SSP 控制項）、跨 schema 業務 mapping → **主專案**。
- OSCAL 物件 CRUD / resolve / snapshot / clone / 匯入匯出 → **套件**。

---

## 5. API Spec 骨架（路由清單；各 SA 細節留各 sub-project）

> URL 慣例（CLAUDE.md）：單筆單數、清單/批次複數、kebab-case。envelope `{code,data}`。

| 區塊 | 代表路由 | 動作 | sub-project |
|------|---------|------|-------------|
| 合規框架 | `POST /oscal/frameworks`、`/framework-versions/import`、`/{uid}/publish` | 框架/版本/匯入/發布 | BE-B1 |
| 合規資源庫 | `POST /resource-libraries`（選 framework_version → clone）、匯入 SSP docx/excel | 三件組建立 | BE-B1 |
| 專案 | `POST /projects/start`（clone 資源庫三件組；AP/AR 延後）| 專案成立 | BE-B2 |
| 稽核輪次 | `POST /projects/{uid}/audit-rounds`、`/{round}/launch-audit`（snapshot）、`/{round}/launch-reverify` | 輪次 + 狀態轉換 | BE-B3 |
| SSP | 既有 ssp_* 路由對齊 v2 schema | SSP 維護 | BE-B3 |
| **AP** | `POST /audit-rounds/{round}/ap`（草稿生成）、`PUT …/reviewed-controls`、`…/subjects`、`…/tasks` | 稽核計畫（全新）| BE-B4 / FE-C1 |
| **AR** | `GET …/findings`（AO 全量矩陣）、`PUT …/finding/{uid}`、`POST …/risks`、`PUT …/risk/{uid}/findings`（組風險）| 判定 + 風險總結 | BE-B5 / FE-C2 |
| POA&M | `POST …/poam/generate`、`PUT …/poam-item/{uid}`、`POST …/poam-item/{uid}/milestones`、`POST …/close-round` | 改善 + 結案 | BE-B5 / FE-C3 |

---

## 6. Error Code 規劃（命名 `GRC_<HTTP><序號>`）

新增族群（序號待 Wave 2 開工前 grep `common/code/grc_error_code.py` 最大值續編，避免衝突）：
- 輪次：`GRC_ROUND_NOT_FOUND`(404x)、`GRC_ROUND_INVALID_STATUS_TRANSITION`(412x)、`GRC_ROUND_SSP_NOT_FROZEN`(412x)、`GRC_REVERIFY_PARENT_NOT_PENDING`(412x)
- AP：`GRC_AP_REVIEWED_CONTROLS_REQUIRED`(412x)
- AR：`GRC_AR_FINDING_TARGET_REQUIRED`(412x)、`GRC_AR_RESULT_NOT_LATEST`(412x)
- Risk：`GRC_RISK_NO_FINDING_LINKED`(412x)

---

## 7. 值域與範圍鎖定（CMMC-only）
- **risk `status` CMMC 允許集**：`open` / `investigating` / `remediating` / `closed`。ISO 的 `deviation-*` / `risk_accepted`（base COMMENT 列出的）**本期不 wire**（D5 follow-up），避免 FE 驅動不到。
- **`ap_assessment_subjects.subject_type`**：component / inventory-item / location / party / user。`user` 是產品擴充（對 SSP system-user），OSCAL 匯出時映射為 party/component 參照。

## 8. 留給後續 sub-project 細化（不在 Wave 0 契約）
- 各 model 的 entity/dto/mapper 細節（Wave 1 套件）
- 舊 `compliance.poams` 扁平表 vs OSCAL `poams` 的最終取捨（Wave 2）
- 舊資料遷移（D7，正式環境）
- FE 元件設計、AO 全量矩陣的摺疊 UI、風險總結畫面（Wave 3）
- D5 ISO 整合（risk_accepted deviation / L1⊂L2 / SoA 自選排除）— follow-up

> **D2 表述更正**：requirement-analysis §3.3 早期寫「1 project → 1 AP / 1 AR」，已被本文件 §3 精修為「**1 AP/AR per engagement**」（surveillance 起新 AP/AR）取代。delta 的 `project_audit_rounds.assessment_plan_id` 是 nullable FK、無 project-unique 約束，正確支援 per-engagement 模型。Wave 2 以本文件 §3 為準。

---

## 9. 平行開發 wiring（契約定稿後）
- **Wave 1（套件，~5 agent 平行）**：A1 catalog+framework+profile / A2 SSP+UI落點 / A3 AP+subjects / A4 AR+risk+POA&M / A5 parser adapter。
- **Wave 2（BE，交疊）**：B1 框架+資源庫 / B2 專案成立 / B3 SSP+輪次狀態機 / B4 AP 後端 / B5 AR+POA&M+結案。
- **Wave 3（FE，交疊）**：C1 AP 畫面 / C2 AR 總結層 / C3 POA&M / C4 輪次切換。
- 規範：subagent 顯式 git add、各 repo 分開 commit、不切 branch、套件走 path dependency。
