# FR-037 OSCAL 欄位命名對齊（additive）— 實作計畫

| 項目 | 內容 |
|------|------|
| 目標 | 讓 table 欄位名直接等於 OSCAL 規範名，後人零 mapping 表即可對應 |
| 原則 | **additive only**——保留既有 FK / soft-ref 欄位（內部 join 用），新增 OSCAL 同名欄位（規範對應 + 匯出用）|
| 命名規則 | OSCAL 連字號欄位名 → snake_case（`import-profile` → `import_profile`）|
| 來源 | 官方 sample `github.com/usnistgov/oscal-content/examples/*/json/`（已爬，欄位名見 §1）|
| 影響 | jedi-oscal（ORM + DDL）+ 3 DB 環境（dev/stg/poc）；POA&M 部分在 BE `compliance.poams` |
| 規範 | 套件異動前 user 點頭、path-dep dev、user 明示才發版；migration 用 cmmgr、加日期註解、INSERT schema_migrations |

> ⚠️ **ORM 欄位與 migration 必須同時上**：SQLAlchemy 查詢帶所有 mapped column，只加 model 不加 DB 欄會讓該表所有查詢炸。故每個欄位 = model 改動 + migration + 套 3 環境，綁定執行。

---

## 1. 官方 sample 實證的 OSCAL 欄位名（爬取結果）

跨文件參照（import 類，本計畫主體）：

| 模型 | OSCAL 欄位 | sample 實值 | 指向 |
|---|---|---|---|
| SSP | `import-profile.href` | `"#b78aa3ec-915d-…"` | **Profile**（經 back-matter resource rlink 到 catalog/profile）|
| AP | `import-ssp.href` | `"../../ssp/xml/ifa_ssp.xml"` | SSP |
| AR | `import-ap.href` | `"../../ap/xml/ifa_assessment-plan.xml"` | AP |
| POA&M | `import-ssp.href` | `"../../ssp/xml/ifa_ssp.xml"` | SSP |

文件內參照（intra-doc，OSCAL 同名 key，供 §3 廣域對齊參考）：

| OSCAL key | 出現處 | 我們現況 |
|---|---|---|
| `component-uuid` | implemented-component / by-component | 未建模 by-component；implemented-component 以 int `component_id` join |
| `party-uuid` / `party-uuids` | leveraged-authorization / responsible-party | `party_uuid` ✅ 已同名 |
| `role-id` / `role-ids` | responsible-role / user | `role_id` ✅ 已同名 |
| `control-id` | implemented-requirement / reviewed-controls | `control_identifier`（SSP）/ `control_id`（AP/AR ✅）|
| `statement-id` | statement | `statement_identifier` |
| `param-id` | set-parameter | 未建模 set-parameter |
| `observation-uuid` / `risk-uuid` / `finding-uuid` | related-* | 未建模 observation/risk；`ar_finding_id`(int) |

> 命名啟示：我們有些欄位**早已與 OSCAL 同名**（`party_uuid` / `role_id` / `control_id`）；落差主要在 import 類（存內部 id）與尚未建模的結構（by-component / observation / risk，屬 FR-037 §0 Tier C）。

---

## 2. 本計畫範圍：import 三欄位（jedi-oscal，additive）

每欄存「被參照文件的 UUID」（非 int id），匯出時合成 `href="#<uuid>"`；既有 FK / soft-ref 保留不動。

### 2.1 `oscal.ssps.import_profile`（user 指定的 worked example）

```python
# jedi_oscal/infra/model/ssp/ssp.py — 新增於 profile_id 之後
import_profile: Mapped[Optional[uuid.UUID]] = mapped_column(
    UUID(as_uuid=True),
    nullable=True,
    comment="OSCAL import-profile.href 指向的 Profile UUID（規範同名欄位；href 合成為 #<uuid>）",
)
```

- **存什麼**：該 SSP 套用的 Profile 的 `uid`（= `profiles.uid`，可由現有 `profile_id` 解析回填）。
- **保留**：`profile_id`（FK，內部 join）。兩者並存——`profile_id` 內部用、`import_profile` 規範/匯出用。
- 回填 migration：`UPDATE oscal.ssps s SET import_profile = p.uid FROM oscal.profiles p WHERE p.id = s.profile_id;`

### 2.2 `oscal.assessment_plans.import_ssp`

```python
import_ssp: Mapped[Optional[uuid.UUID]] = mapped_column(
    UUID(as_uuid=True), nullable=True,
    comment="OSCAL import-ssp.href 指向的 SSP UUID（規範同名欄位）",
)
```
- 存對應 SSP 的 `ssps.uid`。保留 `ssp_id`（現存 stringified int，僅追溯）。
- 回填：`ssp_id` 是 stringified int → `UPDATE … SET import_ssp = s.uid FROM oscal.ssps s WHERE s.id = CAST(ap.ssp_id AS INTEGER)`（注意空值 / 非數字防呆）。

### 2.3 `oscal.assessment_results.import_ap`

```python
import_ap: Mapped[Optional[uuid.UUID]] = mapped_column(
    UUID(as_uuid=True), nullable=True,
    comment="OSCAL import-ap.href 指向的 AP UUID（規範同名欄位）",
)
```
- 存對應 AP 的 `assessment_plans.uid`。保留 `assessment_plan_id`（FK）。
- 回填：`UPDATE … SET import_ap = ap.uid FROM oscal.assessment_plans ap WHERE ap.id = ar.assessment_plan_id`。

### 2.4（選配，BE 主專案）`compliance.poams.import_ssp`

POA&M 在 BE 不在 jedi-oscal，且為扁平 per-finding 表。若要對齊可加 `import_ssp`（SSP uuid）。建議獨立評估，不綁本批。

---

## 3. 廣域對齊（選配，超出本批）

若要連 intra-doc 參照也對齊，多數綁在「尚未建模的結構」上（FR-037 §0 Tier C），應與那批一起做，而非單純改名：

- `component_uuid`：須先有 by-component 表。
- `observation_uuid` / `risk_uuid` / `finding_uuid`：須先有 observation / risk 表（AR）。
- `statement_identifier` → 是否補一個 `statement_id` 同名欄、`control_identifier` → `control_id`：純改名 additive，可做但效益低（語意已清楚）。

---

## 4. 執行步驟（待 user 點頭）

1. jedi-oscal 走 path-dependency dev（BE pyproject 改 path、不 commit）。
2. 改三個 model（§2.1–2.3）新增欄位。
3. 寫 migration `scripts/sql/2026-06-10-oscal-import-ref-columns.sql`：`ALTER TABLE ADD COLUMN` ×3 + 回填 UPDATE + `GRANT`（新欄不需額外 grant，但確認）+ `INSERT schema_migrations`。
4. dev 套 → 驗 → stg → poc（用 cmmgr、`psql --single-transaction -v ON_ERROR_STOP=1`）。
5. BE 重啟（user 自行），驗查詢正常。
6. feature 完成、user 明示才 bump jedi-oscal 版本 + 推 Nexus + BE pin 回正式版。

---

## 5. 待確認

1. **import_profile 語意**：確認存 Profile UUID（非 SSP UUID）。✅ 已照官方 sample 設計。
2. **本批範圍**：只做 §2 的 import 三欄位，還是含 §3 廣域對齊 / §2.4 POA&M？
3. **存 UUID vs 完整 href 字串**：本計畫存 UUID（內部 ref，匯出合成 `#uuid`）。若需支援匯入「外部 OSCAL 文件」（target 不在我們 DB，href 是外部 URL），另需 `import_*_href`(String) 存原始 href——是否納入？
