# FR-037 OSCAL v1.2.2 關聯式 Schema 審查報告

| | |
|---|---|
| 日期 | 2026-06-12 |
| 審查對象 | `oscal-catalog-and-roots-schema.sql`（45 表 / 673 欄） |
| 對照來源 | `docs/reference/oscal_model_schemas/*.json`（官方 v1.2.2，8 份）逐表逐欄核對 required 陣列與 properties 全集 |
| 方法 | 6 個平行審查 agent 分段對照 + 主審抽查複驗 + 本機拋棄式 PostgreSQL 16 實跑 + 產品使用面盤點（BE + jedi-oscal） |
| 狀態 | 純審查，**未改任何 DDL**；SECTION A/B 的 COMMENT 已豐富化（見 §7） |

## §0 TL;DR

整體品質高：**45 表的 required ↔ NOT NULL 逐欄核對近乎全對（僅 1 處錯置）**、8 root 無 props/links 正確、resource 有 props 無 links 正確、import FK 的 NOT NULL/nullable 四個全對、AR 版與 POA&M 版 observation/risk/finding 逐欄 diff 為零（共用表有 schema 正當性）。問題集中在：**①若干 coverage 漏接（欄位完全沒有去處，匯入會 silent drop）②一個幽靈欄位 ③「同文件參照→FK」政策有 4 處 JSONB 違例（政策自相矛盾）④多型 nullable FK 全部缺 XOR CHECK**。可執行性已實測通過（PG16 single-transaction 一次建完）。

---

## §1 必填/選填錯置（審查點 1）— 僅 1 處

| 嚴重度 | 表.欄位 | OSCAL 出處 | 該怎樣 | 現在怎樣 |
|---|---|---|---|---|
| **高** | `mappings.source_resource_type` / `target_resource_type` | mapping-resource-reference `required=["type","href"]` | 兩欄 NOT NULL 並標 [必填] | nullable，註解未標必填 |

其餘 44 表全對。特別核對過的易錯點（全部正確）：
- `poam_items.uuid` nullable ✓（poam-item 是八大 model 子物件中少見 uuid 選填者，`required=["description","title"]`）
- v1.2.2 的 `system-characteristics` required 不含 security-sensitivity-level / security-impact-level（1.0 曾必填、已放寬）→ SQL nullable ✓
- `ssp.import_profile_id` / `ap.import_ssp_id` / `ar.import_ap_id` NOT NULL、`poams.import_ssp_id` nullable ✓
- `assessment_risks` 無 remarks 欄 ✓（OSCAL risk 真的沒有 remarks，不是漏）
- `mapping_collections.provenance` NOT NULL ✓（root required 含 provenance）

## §2 props/links 放置（審查點 2）— 全數正確

- 8 root 表皆無 props/links ✓（已從 root 全移除，修正屬實）
- `resources` 有 props、無 links（用 rlinks）✓
- 逐物件對 schema：metadata/role/party/group/control/part/param/SSP 全子樹/CD 全子樹/AP/AR/POA&M/mapping 的 props/links 一一對應，無多放無漏放
- CD 版 control-implementation 有 props/links、無 remarks → SQL 正確；SSP 版 control-implementation 無 props/links → SQL 正確（兩個易錯點都對）

## §3 參照原則（審查點 3）— 原則本身執行正確，但有 4 處 JSONB 違例

FK 化（同 DB 內）與 token/href 保留（跨文件）的大方向全對：`implemented_requirements.control_id`、`statements.statement_id`、`cd_control_implementations.source`、mapping source/target href 保留 text ✓；`by_components.component_id` FK 化 ✓；party-uuid 全軟式 ✓。

但檔頭「同文件參照一律 FK」政策有 4 處自相矛盾（同文件 uuid 參照卻留 JSONB，會 dangling、查詢要剝 JSONB）：

| 嚴重度 | 位置 | 同文件參照目標 |
|---|---|---|
| 中 | `ssp_inventory_items.implemented_components`（component-uuid） | `ssp_components` |
| 中 | `cd_capabilities.incorporates_components`（component-uuid，檔頭政策明文舉過這個例子） | `cd_components` |
| 中 | `ap_tasks.dependencies`（task-uuid）/ `associated_activities`（activity-uuid；其 subjects 子欄是 required，JSONB 內 DB 無法強制） | `ap_tasks` / `ap_assessment_activities` |
| 中 | `poam_items.related_findings/observations/risks` 等 related-* 系列（同文件 uuid；產品現況 compliance.poams 就用 ar_finding_id JOIN，查詢需求真實存在） | `assessment_findings` 等 |

→ 二擇一：關聯化成 link 表，或在檔頭政策段明文補「related-*/dependencies 類多對多參照例外留 JSONB」。

另一漏收欄（參照類）：
- **中** | `assessment_findings` 缺 `implementation_statement_uuid`（finding → SSP statement 的跨文件參照，[0..1]）— 整欄無去處，props/links 也收不住。

## §4 coverage 漏接（官方有、SQL 完全沒去處）

| 嚴重度 | 位置 | 漏接內容 |
|---|---|---|
| 中 | `catalogs` | catalog 層 `params[]`（且行內註解誤示已承接——已隨 S1 修正註解） |
| 中 | `catalog_groups` | group 層 `params[]` 與 `parts[]`（目標框架未用，屬有意識棄守，但無 JSONB 兜底） |
| 中 | `catalog_groups.remarks` | **反向幽靈欄**：OSCAL group 沒有 remarks，寫入的資料匯出時無處可放（待決策移除） |
| 中 | `ssp_system_characteristics` | system-information wrapper 自身的 props/links；authorization-boundary / network-architecture / data-flow 三段各自的 props/links/remarks（共 9 欄，SQL 有自註「暫未展開」，gap 屬實） |
| 中 | `ssp_information_types` | 三軸 impact 的 `adjustment-justification`（FIPS-199 tailoring 關鍵語意欄）+ impact 的 props/links |
| 中 | `assessment_results`（root） | root 層 `local-definitions`（objectives-and-methods / activities）— 沒接也沒列 deferred，是 silent gap |
| 中 | `poams`（root） | root 層 `local-definitions`（components / inventory-items / assessment-assets；FedRAMP POA&M 常用） |
| 中 | `poams.system_id` | OSCAL system-id 是 {id, identifier-type} 結構，只存了 id |
| 中 | `mappings` | `mapping-description` / `matching-rationale` / `confidence-score` / `coverage` / `source-gap-summary` / `target-gap-summary` 六欄＋resource-reference 的 ns/props/links/remarks |
| 低 | `mapping_maps` | `ns`（relationship 的命名空間） |
| 低 | `assessment_plans` | AP local-definitions 自身的 `remarks` 沒人接（components/inventory/users 已明示 deferred） |

## §5 約束強化建議（DB 完整性，非 OSCAL 對錯）

1. **多型 nullable FK 缺 XOR CHECK（4 組，全檔系統性問題）**：`ssp_by_components`（implemented_requirement_id/statement_id）、`cd_control_implementations`（cd_component_id/cd_capability_id）、`assessment_observations/risks/findings`（ar_result_id/poam_id）、`profile_imports`（source_catalog_id/source_profile_id）。建議 `CHECK (num_nonnulls(a, b) = 1)`，否則可孤兒列或雙親列。
2. `profile_imports`：include-all XOR include-controls 是 OSCAL required 語意（anyOf 兩分支），目前兩欄皆可空且無註明。
3. `catalog_control_params`：values 與 select 互斥，可加 CHECK。
4. root 表 `metadata_id` 應 UNIQUE（自稱 1:1 但 DB 不擋共用，resources「經 metadata 定位 root」的前提會失效）。
5. 被參照 token 可加唯一約束：`UNIQUE(catalog_id, control_id)`、`UNIQUE(metadata_id, role_id)`、`UNIQUE(metadata_id, uuid)`（parties/resources）。
6. 冗餘索引 3 條：`idx_ssp_syschar_ssp` / `idx_ssp_sysimpl_ssp` / `idx_ssp_ctrlimpl_ssp`（欄位已 UNIQUE）；`profile_imports.source_profile_id` 漏索引。
7. 註解事實錯誤（部分已隨 S1 修正）：檔頭 L33 mapping required 漏 provenance；`ap_tasks.timing` 三處註解誤寫 within-period、漏 at-frequency；`observation.methods` 枚舉漏 UNKNOWN；`task.type` / `implementation_status_state` 是開放值域（anyOf token），註解寫得像封閉 enum。
8. 檔頭 L37 速查「import-profile/ssp/ap: [href]」對 **profile 的 import** 不成立（v1.2.2 profile import 的 required 是 include-all/include-controls 擇一，href 反而選填）。

## §6 JSONB 折衷評估（審查點 4）

**合理保留**（整包存取、無 WHERE/JOIN）：props/links 全系列、metadata.revisions/locations/document-ids/actions、party 聯絡欄、parameter constraints/guidelines、profile merge/modify、by-component export/inherited/satisfied（反悔條件：要做 leveraged authorization 責任鏈對賬時關聯化）、risk characterizations/remediations/risk-log、observation relevant-evidence（反悔條件：證據反查需求）、task timing/subjects、control-selections、attestations/assessment-log。

**該關聯化或補政策例外**（見 §3 的 4 處違例）：implemented_components、incorporates_components、task dependencies/associated-activities、related-* 系列。

**邊際個案**：`metadata.responsible_parties`（role-id/party-uuids 都指向已拆表的 roles/parties，是天然 join 表；產品已有 ResponsiblePartyEntity link-record 慣例）— 若只在 SSP 層查可留 JSONB；`resources.base64`（大檔建議 bytea 或外部儲存）。

## §7 可執行性（實測）

- 本機拋棄式 PostgreSQL 16：`psql --single-transaction -v ON_ERROR_STOP=1` **一次成功**，45 表 / 673 欄全建、0 表 0 欄漏 COMMENT。保留字（"class"/"end"）、`usage`/`start` 裸用皆無問題。S1 註解豐富化後重跑亦通過。
- dbdiagram.io 匯入**未實測**，已知風險：加引號欄位（"class"/"end"）與 `COMMENT ON` 的解析支援度、`text[]`/`uuid[]` 陣列型別、跨表多型 FK 畫線——建議匯入時若報錯，優先檢查這幾類。
- 註解豐富化進度：**SECTION A+B（9 表 155 條 COMMENT）已完成並驗證**；SECTION C~I（36 表）尚未做（user 暫停）。

## §8 過度設計評估（user 追加審查維度）

產品使用面盤點結論（證據見下）：產品實際用 OSCAL 的廣度約六成（Catalog/Profile/SSP/AP/AR 主幹），深度約三成。對照本設計（前提：**這是刻意的「全 OSCAL、不考慮現行產品」重新設計**，所以「對 OSCAL 忠實」本身不算過度設計），分三層評：

**A. 有真實依據、不算過度**（30 表）：共用核心 4、Catalog 樹 5（parts 是 AO 落點，定案需求）、Profile 2、SSP 主幹（syschar/sysimpl/components/inventory/leveraged/ctrl-impl/impl-req/statements/by-components 9）、AP 4、AR 2、共用三表 3、POA&M 2。

**B. 規格對但「先開表」超前產品需求**（8 表，建議保留設計、實作時可後置）：
- `ssp_system_users` / `ssp_diagrams` / `ssp_information_types`：產品今天沒有對應持久化（users 只有 owner_uid 一欄、diagrams 零使用、information-types 匯入解析後不落 DB）。是 OSCAL SSP 完整性需要，但可列第二波。
- `ssp_statements` 拆到 by-component 雙層：OSCAL 結構要求，保留；但現行產品只做到 control 級陳述。
- `ap_local_objectives` / `ap_assessment_activities` / `ap_tasks`：runtime AO 與任務在產品另有 assessment_plan_tasks + workflow 體系，這三表是 OSCAL 文件層的對應——兩套並存的關係要先想清楚（誰是 source of truth），否則是雙寫負擔。
- `ar_results`：產品 AR 已有自己的三表結構，同樣有兩套並存問題。

**C. 高度 YAGNI 候選（10 表，建議等需求再開）**：
- **Component Definition 整個 SECTION E（5 表）**：產品零使用（用自家 InformationSystem 替代），且無任何 import/export component-definition 的路徑。這是 45 表中最明確的超前設計。
- **Mapping 整個 SECTION I（2 表）+ `mapping_collections` root**：OSCAL 實驗性 model，產品的跨框架對應另有機制（workflow_execution_control_mapping 是流程↔控制項，不是框架↔框架）；handoff §5.4 本就待拍板——建議先不做。
- `poams`（OSCAL 文件 root）+ `poam_items`：產品 POA&M 是扁平 compliance.poams 且與 AP/AR 工作流深度耦合；除非要做 FedRAMP 式 POA&M 文件匯出，OSCAL 文件層可緩。

**層數深度評**：SSP 鏈 `ssps → control_implementations → implemented_requirements → statements → by_components` 五層是 OSCAL 本身的結構，不是自己挖的；但查「某 SSP 的所有 by-components」要 4 個 JOIN。建議比照 `catalog_control_parts.catalog_control_id` 的反正規化手法，在深層表加 `ssp_id` 直達欄（denormalized，註解標明 app 層保證一致）。1:1 wrapper 表（`ssp_system_implementations`、`ssp_control_implementations`、`ap_reviewed_controls`）各只有 2~4 個實質欄位，可以折進父表（ssps 加 6 欄、AP 加 4 欄）省兩次 JOIN——保留拆表的理由只剩「OSCAL 結構一目了然」，屬風格取捨。

若採 C 全砍 + wrapper 合併：45 表 → 約 31 表，OSCAL 主幹保真不受影響。

**產品使用面證據摘要**：CD 零讀寫（InformationSystem 替代，`app/oscal/service/ssp_inventory_items_app_service.py:37`）；mapping 僅 workflow↔control（`infra/grc/repository/workflow_execution_control_mapping_repo_impl.py`）；POA&M 扁平單表（`infra/grc/model/poam_model.py`）；SSP users/diagrams/info-types 無持久化；AR 無 observations/risks/attestations 實裝；catalog parts 僅匯入快照、無動態讀寫；profile merge/modify 零實作；OSCAL 匯出僅 SSP 一條路（`app/oscal/service/export/ssp_export_app_service.py:111-133`）。

## §8.5 v2 產出（2026-06-12 user 拍板後）

User 決策：**只砍 Mapping 全段（3 表），CD 與 POA&M 保留（重構將全面採 OSCAL 模式）；一併做官方對齊修正、不加約束強化**。產出 `oscal-catalog-and-roots-schema-v2.sql`（**42 表 / 650 欄**，原 45/673），已在本機拋棄式 PostgreSQL 16 實跑通過、0 表 0 欄漏 COMMENT。v2 內容：
- 裁剪：`mapping_collections` / `mappings` / `mapping_maps` 與其索引（§1 唯一「高」級 finding 隨之消滅）
- 補欄（21 欄）：catalog/group `params`、group `parts`、syschar 三段＋system-information 的 props/links/remarks（11 欄）、impact `adjustment_justification`×3、finding `implementation_statement_uuid`、AR/POA&M root `local_definitions`、AP `local_definitions_remarks`、POA&M `system_id_identifier_type`
- 移除：`catalog_groups.remarks` 幽靈欄（§4）
- 修正：§5.7/§5.8 列的全部註解錯誤（timing 三態、methods 含 UNKNOWN、開放值域標註、mapping required 速查、profile import href 速查）；多型 FK／互斥語意在 COMMENT 標明「應用層保證」（依拍板不加 CHECK）
- **未處理（留待決策）**：§3 的 4 處 JSONB 參照違例（關聯化 vs 政策補例外）、§5 約束強化、§8 wrapper 合併與 deferred 子結構
- 註解豐富化進度更新：SECTION A+B（S1）與 7 張 root 表（S2 在中止前已完成 SECTION C root 的部分）已豐富化；profile_imports 與 SECTION D~H 仍為原版精簡註解

## §9 待 user 決策（彙整，含 handoff §5 原有項）

1. 8 root 的 `status` 產品欄移除與否（handoff §5.1）
2. assessment-common 共用表方案：**審查支持共用**（兩版定義逐欄 diff 為零），條件是補 XOR CHECK（§5.1）
3. AP deferred 子結構補不補（handoff §5.3）→ 結合 §8.B 的「兩套並存」問題一起決
4. mapping model 做不做（handoff §5.4）→ 審查建議：先不做（§8.C）
5. §3 的 4 處參照違例：關聯化 vs 政策補例外
6. §4 漏接欄位逐項補欄（多數是加 nullable 欄/JSONB，低風險 additive）
7. §8.C 的 YAGNI 範圍裁剪與 wrapper 合併
