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

項目 內容
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.proseNOT NULL,但 OSCAL part prose 是 optional。匯入「只有子 part 才有文字、自己無 prose 的結構性 part」會被我們的 NOT NULL 擋下。我們在這裡比 OSCAL 嚴,應放寬為 nullable。

非 required 缺口(fidelity,OSCAL optional,本輪不列入硬缺口):

  • props[] / links[] 在 control/group/part/param 都沒建模(800-53 大量靠 propslabel/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 nameString(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_componentsOscalSspInventoryItemComponent)仍 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.titlenullable 🔴 OSCAL required 但可空
result.description R —(只有 remarks 🔴 沒欄位
result.start R assessment_result_datas.started_atnullable 🔴
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.titlenullable 🔴
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/grouplink.textrole.short-name、party external-ids/location-uuids/member-of-organizationsmetadata.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/)