FR-037 OSCAL 欄位命名對齊(additive)— 實作計畫

項目 內容
目標 讓 table 欄位名直接等於 OSCAL 規範名,後人零 mapping 表即可對應
原則 additive only——保留既有 FK / soft-ref 欄位(內部 join 用),新增 OSCAL 同名欄位(規範對應 + 匯出用)
命名規則 OSCAL 連字號欄位名 → snake_case(import-profileimport_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)

# 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

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

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_identifiercontrol_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.sqlALTER 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——是否納入?