# FR-037 OSCAL 模型 × DB Schema required 欄位完整盤點

> 🗄️ **本 FR 已於 2026-06-14 ARCHIVED。** 盤點後續演變成「全 OSCAL 關聯式 schema 重新設計」（單檔 45→42 表），方向已偏離本文件原本「只盤缺口、供下一輪動表」的定位。User 決定整個重來、不續作 FR-037，新一輪將另開 FR。本 session 成果（v2 schema + 審查報告）保留於本資料夾，收尾說明見 [`handoff/2026-06-14-FR037-ARCHIVE-SUMMARY.md`](handoff/2026-06-14-FR037-ARCHIVE-SUMMARY.md)。

| 項目 | 內容 |
|------|------|
| FR | FR-037 |
| 提出 | 2026-06-10 |
| 狀態 | 🗄️ **ARCHIVED（2026-06-14，整個重來、不續作）** |
| arc 關聯 | relates to FR-028（SSP OSCAL 對齊）/ FR-036（SSP 表族整併同期動表）|
| 影響 repo | jedi-oscal（OSCAL ORM 全在此）+ BE（`compliance.poams`）+ 3 DB 環境 |
| 比對基準 | `docs/reference/oscal_model_schemas/` 5 份官方 JSON schema + NIST 概念頁 vs `jedi-oscal/jedi_oscal/infra/model/` ORM |

> **定位**：這是一份「缺口清單」，不是實作計畫。目的是把 OSCAL 5 個模型的 required 欄位與我們 `oscal.*` 表逐欄位對齊一次，讓未來動表時知道一次要補哪些。實作分期、哪些真要做、做到什麼 conformance 等級，留後續 implementation-plan 決策（見 §7）。

---

## 0. 必填欄位缺失速查（required-only）

必填要分「一定必填」與「條件式必填」——差別決定要不要補。

### Tier A：一定必填、完全沒有（沒表沒欄位）← 真正的硬缺口

| 模型 | 缺的必填 | 必填理由 |
|---|---|---|
| SSP | **information-type 子樹**（title/description；categorization.system；C/I/A impact.base）| system-characteristics 必填 system-information → 必填 information-types[]，一路必填 |
| SSP | **control-implementation.description** | control-implementation 是 root 必填，其 description 必填；無 header 列 |
| AR | **result.description** | result 必填，description 必填；只有 `remarks` |

### Tier B：一定必填、欄位在但約束錯（nullable / 詞彙）← 改 DDL 或值域

| 模型 | 欄位 | 問題 |
|---|---|---|
| SSP | `ssp_system_characteristics.status` | active/archived ≠ OSCAL state（operational/under-development/…）|
| SSP | `ssp_components.status` / `.description` | nullable，OSCAL 必填 |
| AR | `assessment_result_datas.title` / `.started_at` | nullable，OSCAL result.title/start 必填 |
| AR | `assessment_result_findings.title` | nullable，OSCAL 必填 |
| AP | `assessment_plan_tasks.task_type` | 被 manual/hybrid 占用，OSCAL task.type(milestone/action) 等於沒存 |
| Catalog | `catalog_control_parts.prose` | **反向**：我們 NOT NULL 比 OSCAL 嚴，會擋匯入 |

### Tier C：條件式必填（該 assembly 選填，一旦用就必填子欄位）← 看 conformance 目標

| 模型 | 結構 | 一旦使用即必填 |
|---|---|---|
| SSP | by-component | component-uuid / uuid / description / implementation-status.state |
| SSP | system-user + authorized-privilege | user.uuid；title + functions-performed |
| SSP | leveraged-authorization | party_uuid / date_authorized（欄位在但 nullable）|
| SSP | set-parameter / diagram | param-id+values；diagram.uuid |
| AR | finding.target | type / target-id / status / implementation-status.state |
| AR | observation / risk | observation: uuid/description/methods/collected；risk: uuid/title/description/statement/status |
| POA&M | poam-item | title / description |

> 「一定必填且完全缺」只有 **Tier A 3 條 + Tier B 6 條約束**。Tier C 不啟用該功能就不算違規，做不做取決於產品 conformance 目標（見 §7）。

---

## 1. 方法與判讀基準

- **權威來源**：JSON Schema 每個 object 的 `"required": [...]` 陣列就是 OSCAL 硬性 required 欄位。本盤點逐一抽出比對。
- **重要前提**：本系統是 GRC 產品，採 OSCAL 的**關聯式拆解**（不是完整 OSCAL processor）。因此「OSCAL required 但我們沒有」分三種意義，務必區分：
  1. **約束倒置 / 詞彙不符**（欄位在、但比 OSCAL 寬鬆或值域不對）→ 便宜、風險低、應先做。
  2. **整段 assembly 缺**（沒表沒欄位）→ 要新增表，工程量視 assembly 大小。
  3. **OSCAL optional 我們也沒有** → 不列為 required 缺口，僅在「非 required 缺口」註記。
- **嚴重度**：🔴 阻擋有效 OSCAL 匯入/匯出　🟠 required 但可延後/可 mapper 補　🟡 fidelity 損失但不致命　✅ 已到位。

---

## 2. 控制層（Catalog / Profile）— 基本健康

### 2.1 Catalog（`oscal.catalogs → catalog_groups → catalog_controls → parts/parameters`）

所有 OSCAL Catalog required 欄位**都有對應欄位**：

| OSCAL object | required | 我們的欄位 | 狀態 |
|---|---|---|---|
| catalog | uuid, metadata | `catalogs.uid` / `catalogs.metadata_id` | ✅ |
| group | title | `catalog_groups.name` | ✅（name 兼當 id+title，見下註）|
| control | id, title | `catalog_controls.control_id` / `.control_title` | ✅ |
| part | name | `catalog_control_parts.part_name` | ✅ |
| parameter | （無 required）| `catalog_control_parameters` | ✅ |

**唯一要修的 required 級問題（約束倒置）**：

- 🔴 **`catalog_control_parts.prose` 是 `NOT NULL`，但 OSCAL part `prose` 是 optional**。匯入「只有子 part 才有文字、自己無 prose 的結構性 part」會被我們的 NOT NULL 擋下。我們在這裡**比 OSCAL 嚴**，應放寬為 nullable。

非 required 缺口（fidelity，OSCAL optional，本輪不列入硬缺口）：
- `props[]` / `links[]` 在 control/group/part/param 都沒建模（800-53 大量靠 `props` 如 `label`/`sort-id`，目前被擠進 `control_id`/`order_no` 或丟失）。
- 子控制項（control enhancement）與巢狀 part 被攤平（`catalog_controls`/`catalog_control_parts` 無 self-FK）；group 倒是有 `parent_group_id` 可巢狀。
- parameter 的 `constraints`/`guidelines`/`select`/`how-many`/`usage`/`class` 沒建模；`values` 壓成單一 `default_value`。
- OSCAL token `id`（group/part）未存（只有內部隨機 `uid`），靠 OSCAL id 交叉連結（`{{ insert: param }}`、`href="#id"`）無法直接解析。
- group `name` 為 `String(100)`，OSCAL `title` 是 MarkupLine（可超長/含 markup）；現行框架（CMMC/ISO/800-53）group 標題都短，低風險。

> `catalog_control_assessments` 是內部 AP 範本層，**不是 OSCAL Catalog 構件**，不列入 OSCAL 盤點。

### 2.2 Profile（`oscal.profiles → profile_controls`）

我們的 Profile 是「已解析的選取控制項清單」表示法——存了 import/include/exclude 的**結果**，丟掉了**宣告規則**。

| OSCAL object | required | 我們的對應 | 狀態 |
|---|---|---|---|
| profile | uuid, metadata, **imports** | `profiles.uid` / `.metadata_id` / —（imports 被壓成單一 `catalog_id` + `profile_controls` 列）| uuid/metadata ✅；**imports ⚠️** |
| import | include-all **或** include-controls（href **非** required）| `profile_controls.include` 布林（逐控制項）| ⚠️ 語意攤平，無 include-all/matching/with-child-controls |
| merge | flat/as-is/custom（擇一）| — | ❌（OSCAL optional）|
| modify.set-parameters | param-id, values | — | ❌（OSCAL optional，但這是 baseline 注入機構參數值之處）|
| modify.alters | control-id（在 alter 內 required）| — | ❌（OSCAL optional）|

**要修的 required 級問題**：

- 🟠 **`profile.imports` 是 root required，目前無結構表示**。現況 = 單一 `catalog_id`（FK，不可 null，比 OSCAL 嚴——OSCAL `import.href` 非 required）+ 扁平 `profile_controls`。匯出時必須保證 `imports[]` 非空。最小解：把 `catalog_id` + `include=True` 的列合成單一 `import.include-controls.with-ids`；正解：建 `profile_imports` 表支援多 catalog + include-all。

merge / modify / set-parameters / alters / matching pattern 全是 **OSCAL optional**，不列本輪 required 缺口，但這是我們 Profile 表示法最大的功能性落差（只知道選了哪些控制項，不知道怎麼選、怎麼客製）。

---

## 3. 實作層（SSP）— 缺口最深，且 FR-036 正要動這批表

### 3.1 已到位

| OSCAL | 我們的表 | 狀態 |
|---|---|---|
| system-security-plan（uuid/metadata/system-implementation/control-implementation）| `ssps` + 子表 | ✅ |
| system-characteristics（system-name/description）| `ssp_system_characteristics.name` / `.description` | ✅ |
| component（uuid/type/title）| `ssp_components.uid` / `.component_type` / `.title` | ✅ |
| inventory-item（uuid/description）| `ssp_inventory_items.uid` / `.description` | ✅ |
| implemented-requirement（uuid/control-id）| `ssp_control_implementations.uid` / `.control_identifier` | ✅ |
| statement（statement-id/uuid）| `ssp_control_implementation_objectives.statement_identifier` / `.uid` | ✅ |

### 3.2 約束倒置 / 詞彙不符（便宜先做）

| 表.欄位 | 現況 | OSCAL | 嚴重度 |
|---|---|---|---|
| `ssp_components.status` | nullable | component.status required | 🔴 |
| `ssp_components.description` | nullable | required | 🔴 |
| `ssp_leveraged_authorizations.party_uuid` | nullable | leveraged-authorization required | 🔴 |
| `ssp_leveraged_authorizations.date_authorized` | nullable | required | 🔴 |
| `ssp_system_characteristics.status` | 值 `active`/`archived` | status.state ∈ `operational`/`under-development`/`under-major-modification`/`disposition`/`other` | 🔴 詞彙完全不對 |

### 3.3 整段 required 結構缺失（FR-036 動表時最划算一次補）

| 缺的 OSCAL 結構 | required 欄位 | 嚴重度 | 說明 |
|---|---|---|---|
| **information-type + C/I/A impact**（FIPS 199）| system-information.information-types[]（system-characteristics required）；information-type 的 title/description；categorization.system；confidentiality/integrity/availability-impact 各自的 `base` | 🔴 | 完全沒表沒欄位。FIPS-199 分類無法匯入匯出，這是 SSP 最大洞 |
| **security-impact-level**（C/I/A 三軸 objective）| security-objective-confidentiality / -integrity / -availability 三者皆 required | 🔴 | 我們只有單一 `security_sensitivity_level`，無法拆三軸 |
| **by-component**（+ export/provided/responsibility/inherited/satisfied）| by-component 的 component-uuid/uuid/description；巢狀 implementation-status.state | 🔴 | 控制項實作只到 requirement+objective，無元件層分解 → leveraged authorization 的責任繼承鏈（provided/inherited/satisfied）整段無法表達 |
| **system-user + authorized-privilege** | user.uuid；authorized-privilege.title + functions-performed | 🟠 | users[] 整段無表（只有 system-characteristics 上單一 `owner_uid` 純量）|
| **set-parameter** | param-id + values（control-impl / impl-req / by-component 三層）| 🟠 | 參數值完全無法 round-trip |
| **diagrams**（authorization-boundary / network-architecture / data-flow）| diagram.uuid（逐圖）+ 各自 description | 🟡 | 沒 diagram 表；network-architecture / data-flow 整段也缺 |
| **system-id 陣列 + identifier-type** | system-id.id（required）| 🟡 | 現 `system_identifier` 單值、無 identifier-type、非陣列 |

### 3.4 其他註記

- import-profile 存 `profile_id` FK 而非 OSCAL `href` → 內部 OK，匯出靠 mapper 合成 href。
- responsible-role：control-implementation 上是單一自由文字 `responsible_role`，非 role-id 參照、非多值。
- ⚠️ **遷移風險**：`ssp_inventory_item_components`（`OscalSspInventoryItemComponent`）仍 FK 到 legacy 表 `oscal.ssp_system_implementation_items`，與新的 `ssp_inventory_implemented_components` 並存（mid-migration）。動 inventory↔component join 前先確認哪張是 live。

---

## 4. 評估層（AP / AR / POA&M）

### 4.1 Assessment Plan（`oscal.assessment_plans → groups → controls → tasks`）

| OSCAL object.field | required | 我們的欄位 | 狀態 |
|---|---|---|---|
| assessment-plan.uuid / metadata | R | `assessment_plans.uid` / `.metadata_id` | ✅ |
| **import-ssp.href** | R | `assessment_plans.ssp_id`（String(36) stringified int，無 FK）| 🟠 存內部 id 非 href；匯出需 mapper 合成 |
| **reviewed-controls.control-selections** | R | `assessment_plan_controls`（+ groups）逐控制項 `include` 布林 | 🟠 攤平；無 include-all/exclude/statement-ids |
| **task.uuid / title** | R | `assessment_plan_tasks.uid` / `.title` | ✅ |
| **task.type**（milestone/action）| R | `assessment_plan_tasks.task_type` 值為 `manual`/`automated`/`hybrid` | 🔴 **欄位被占用，OSCAL 的 type 等於沒存**；manual/hybrid 應移到 prop |
| assessment-subjects / activity / step / assessment-assets / terms-and-conditions / local-definitions | optional（各自有內部 required）| — | ❌ 皆未建模（OSCAL optional）|

我們的 AP 擴充（非 OSCAL）：`assessment_plan_groups`（鏡射 catalog group，OSCAL AP 無 group 概念）、`assessment_task_controls`（task↔control 直連 M:N，OSCAL 是透過 activity→step→reviewed-controls 間接關聯）。匯出時這兩層無 OSCAL 對應位置。

**AP 本輪 required 缺口**：🔴 `task.type` 詞彙占用；🟠 `import-ssp.href` 型別語意；🟠 `reviewed-controls` 攤平有損。其餘缺的都是 OSCAL AP root optional。

### 4.2 Assessment Results（`oscal.assessment_results → datas → controls → findings/evidences`）

| OSCAL object.field | required | 我們的欄位 | 狀態 |
|---|---|---|---|
| assessment-results.uuid / metadata / results | R | `assessment_results.uid` / `.metadata_id` / `result_datas[]` | ✅ |
| **import-ap.href** | R | `assessment_results.assessment_plan_id`（內部 FK）| 🟠 非 href |
| **result.uuid** | R | `assessment_result_datas.uid` | ✅ |
| **result.title** | R | `assessment_result_datas.title`（**nullable**）| 🔴 OSCAL required 但可空 |
| **result.description** | R | —（只有 `remarks`）| 🔴 沒欄位 |
| **result.start** | R | `assessment_result_datas.started_at`（**nullable**）| 🔴 |
| **reviewed-controls.control-selections** | R | `assessment_result_controls` 逐控制項 + `verdict` | ⚠️ 語意不同：OSCAL 是「選取範圍」，我們存「逐控制項判定」|
| **observation**（uuid/description/methods/collected）| R（在 assembly 內）| —（`assessment_result_evidences` 僅鬆散覆蓋 relevant-evidence，且掛 control 非 observation）| 🔴 整段未建模 |
| **risk**（uuid/title/description/statement/status + characterization/facets/response/risk-log/threat-id）| R（在 assembly 內）| — | 🔴 整段未建模；remediation 僅剩 `poams.remediation_plan` 自由文字 |
| **finding.uuid** | R | `assessment_result_findings.uid` | ✅ |
| **finding.title** | R | `assessment_result_findings.title`（**nullable**）| 🔴 |
| **finding.description** | R | `assessment_result_findings.description`（NN）| ✅ |
| **finding.target**（type/target-id/status + implementation-status.state）| R | — | 🔴 finding 的核心結構整段缺；我們用 `controls.verdict`（pass/fail/partial/na）近似，但掛 control 不掛 finding，且值域 ≠ OSCAL（satisfied/not-satisfied）|
| finding.origins / related-observations / related-risks | optional | — | ❌ |

`severity`/`category`/`ao_uid`/`recommendation`/`confidence` 是我們的擴充欄位（OSCAL finding 原生無 severity，通常放 prop）。

### 4.3 POA&M（`compliance.poams` 扁平單表 vs OSCAL `plan-of-action-and-milestones`）

OSCAL POA&M 是**獨立模型**（非 AR 一部分）：root required `uuid/metadata/poam-items`；poam-item required `uuid/title/description`。

| OSCAL POA&M field | required | `compliance.poams` | 狀態 |
|---|---|---|---|
| root uuid + metadata | R | —（扁平列，無文件根/metadata）| ❌ 無 POA&M 文件概念 |
| poam-item.uuid | R | `uid` | ✅ |
| poam-item.title | R | — | 🔴 無 title 欄 |
| poam-item.description | R | `remediation_plan`（最接近）| 🟠 語意是 remediation 非 item description |
| related-findings | optional | `ar_finding_id`（單一 int）| 🟠 只能接一筆，OSCAL 允許多筆 |
| related-observations / related-risks | optional | — | ❌ |
| origins（負責 actor）| optional | `assignee_uid` | 🟠 |
| milestones / tasks | — | `due_date` / `closed_at` | 🟠 單一日期，無 milestone 清單 |

`status`（open/in_progress/closed）是我們的擴充（OSCAL 用 prop）。

---

## 5. 跨模型共用層（Metadata / Back-matter / 識別子）

Metadata 4 個 root required（title/last-modified/version/oscal-version）**全到位**（`oscal.oscal_metadatas`）；prop/link/role/party/location/responsible-party 的 required 欄位也都有。但有幾個結構性大洞：

| OSCAL 構件 | required | 狀態 | 嚴重度 |
|---|---|---|---|
| **back-matter → resource** | resource.uuid；rlink.href；base64.value；citation.text | ❌ **完全未建模**。root 上的 `document_id` 是「原始檔 blob」FK，與 OSCAL resource 無關 | 🔴 整體最大結構洞——附件/citation/嵌入證據無法 round-trip |
| **hash**（back-matter rlink 內）| value, algorithm | ❌ 未建模（`oscal_documents.hash` 是整檔完整性 hash，非此）| 🟠（依附 back-matter）|
| **document-id**（metadata.document-ids[] / resource.document-ids[]）| identifier | ❌ 未建模 | 🟠 |
| **revision**（metadata.revisions[]）| version（逐筆）| ⚠️ `oscal_metadatas.revision` 是單一 `String(20)` 純量，存不了 revision 歷史清單 | 🟡 |

非 required（OSCAL optional）：party 的 `email-addresses`/`telephone-numbers`/`addresses` 都壓成單一純量（OSCAL 是陣列）；`address` 攤成自由 Text（丟 city/state/postal/country）；`responsible-role` 無專表（與 responsible-party link 混用）；`prop.uuid/group`、`link.text`、`role.short-name`、party `external-ids`/`location-uuids`/`member-of-organizations`、`metadata.actions` 皆未建模。

---

## 6. 主優先矩陣（一次盤點總表）

### 6.1 🔴 約束倒置 / 詞彙不符（欄位已在，最便宜，風險最低）

| # | 表.欄位 | 動作 |
|---|---|---|
| 1 | `catalog_control_parts.prose` | NOT NULL → nullable（我們比 OSCAL 嚴，擋匯入）|
| 2 | `ssp_components.status` / `.description` | nullable → NOT NULL（或 mapper 強制）|
| 3 | `ssp_leveraged_authorizations.party_uuid` / `.date_authorized` | nullable → NOT NULL |
| 4 | `ssp_system_characteristics.status` | 詞彙改 OSCAL state 值域（operational/under-development/…）|
| 5 | `assessment_result_datas.title` / `.started_at` | nullable → NOT NULL（或匯出前強制）|
| 6 | `assessment_result_findings.title` | nullable → NOT NULL |
| 7 | `assessment_plan_tasks.task_type` | OSCAL type（milestone/action）另立欄位；manual/hybrid 移 prop |

### 6.2 🔴/🟠 整段 required 結構缺失（要新增表）

| # | 缺的結構 | 層 | 規模 |
|---|---|---|---|
| 8 | information-type + C/I/A impact（FIPS 199）| SSP | 中（新表 ~2-3 張）|
| 9 | security-impact-level（C/I/A 三軸）| SSP | 小（欄位或小表）|
| 10 | by-component + export/provided/responsibility/inherited/satisfied | SSP | 大（責任繼承鏈一整族）|
| 11 | system-user + authorized-privilege | SSP | 中 |
| 12 | set-parameter（三層）| SSP | 中 |
| 13 | diagrams（auth-boundary/network/data-flow）| SSP | 小 |
| 14 | finding.target + implementation-status | AR | 中（finding 核心）|
| 15 | observation（+ origin/subject/relevant-evidence）| AR | 中 |
| 16 | risk（+ status/characterization/facets/response/risk-log/threat-id）| AR | 大 |
| 17 | result.description | AR | 小（單欄）|
| 18 | back-matter / resource（+ hash / document-id）| 共用 | 大（影響所有模型 round-trip）|
| 19 | POA&M poam-item.title/.description + 文件根 | POA&M | 中 |
| 20 | profile.imports 結構化（多 catalog / include-all）| Profile | 中 |

### 6.3 import 類型欄位 — 解法

三個必填 import 都是 OSCAL **跨文件參照**（href = URI 指向另一份 OSCAL 文件）：

| OSCAL 必填 | 我們存的 | 型別 |
|---|---|---|
| SSP `import-profile.href` | `ssps.profile_id` | int FK → profiles |
| AP `import-ssp.href` | `assessment_plans.ssp_id` | **String(36) 存 stringified int**，無 FK |
| AR `import-ap.href` | `assessment_results.assessment_plan_id` | int FK → assessment_plans |

**關鍵事實**：每張目標表都有 `uid`（UUID v4），所以 `內部 id → 目標.uid` 永遠可解析。據此分兩情境：

- **情境 1｜內部文件互參（我們自己產的 OSCAL）→ 零動表，純 mapper**：匯出時 `profile_id/ssp_id/assessment_plan_id` → 查 `目標.uid` → 合成 `href="#<uuid>"`。內部 round-trip 不需新欄位。
- **情境 2｜匯入外部 OSCAL（target 不在我們 DB）→ 需 string href 欄位**：int FK 存不了「我們沒有的文件」的 href（外部 URL 或對方 uuid）。要保真就得在三個 root 表加 `import_href`（String，存原始 href）+ optional `import_remarks`，否則來源在匯入即丟失。

**順帶技術債**：`assessment_plans.ssp_id` 現存 stringified int（memory `feedback_ap_ssp_id_varchar_stringified_int`）。VARCHAR(36) = UUID 長度，**正好可改存 ssp.uid**，語意直接貼 OSCAL href、匯出免查表——但屬 breaking 資料遷移，單獨評估。

**結論**：import 欄位**有解、不一定要動表**——只求內部 round-trip 走 mapper id→uid（0 動表）；要支援匯入外部文件 / 完整保真才加 `import_href`(+remarks)，並建議 `ssp_id` 改存 uuid。

---

## 7. 待決策（留 implementation-plan）

1. **目標 conformance 等級**：要做到完整 OSCAL round-trip（含 risk/observation/back-matter），還是只求內部資料完整 + 主要 required 欄位？這決定 §6.2 的 #16/#18 這種大結構做不做。
2. **分期**：§6.1（約束/詞彙）可獨立先發一輪低風險 migration；§6.2 的 SSP 群（#8-13）建議與 FR-036 動 SSP 表族**同期**做（同一次動表）；AR 群（#14-17）+ back-matter（#18）規模大，宜獨立 phase。
3. **與 FR-036 的界線**：FR-036 負責「MF defaults 折進樣板 SSP」；本 FR 負責「SSP 等表的 OSCAL 欄位補齊」。兩者都動 `ssp_*`，需協調動表順序避免互踩（建議 FR-036 結構先定，本 FR 的 SSP 欄位補在其上）。
4. **jedi-oscal 發版**：OSCAL ORM 全在 jedi-oscal，補欄位＝改套件 → 走 path-dependency dev、user 明示才發版（規範 `feedback_jedi_package_dev_path_first`）。
5. **三環境零丟失 migration**：新增 NOT NULL 欄位要給 default 或先 nullable 回填再收緊；詞彙改值要資料轉換。dev→stg→poc 漸進。

---

## 8. 附：比對來源檔

- Schema：`docs/reference/oscal_model_schemas/oscal_{catalog,profile,ssp,assessment-plan,assessment-results}_schema.json`
- ORM：`~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal/jedi_oscal/infra/model/{base,catalog,profile,ssp,ap,ar}/`
- POA&M：`compliance.poams`（BE 主專案）
- NIST 概念頁：catalog / profile / ssp / assessment-plan / assessment-results（pages.nist.gov/OSCAL/learn/concepts/layer/）
