FR-038 OSCAL GRC 重設計 — 契約層 Design(Wave 0)

範圍:契約層(Wave 0) = DB schema 定稿 + jedi-oscal-v2 套件對外介面 + API spec 骨架。 這是所有平行開發的「前提合約」;本層定稿後,套件(Wave 1)/ BE(Wave 2)/ FE(Wave 3)才能真正平行開工。 上游需求基準:requirement-analysis.md(D1-D7 + Q1-Q4 決策已鎖定)。 撰寫日:2026-06-14


0. 本層交付物

交付物 檔案 狀態
v2 schema base(42 表,FR-037 產出) ../FR-037-.../oscal-catalog-and-roots-schema-v2.sql 既有,沿用
v2 schema delta(FR-038 補表) oscal-v2-deltas.sql 本層產出
套件對外介面 + API skeleton 本文件 §3 / §4 本層產出

建置方式(D1:drop oscal 重建):dev 先 DROP SCHEMA oscal CASCADE → 跑 base → 跑 delta。最終發版時 base+delta merge 成單一 build script。


1. 架構總覽(一張圖看懂契約)

框架層(母版)                資源庫層(公版)              專案層(可編輯)            稽核凍結
─────────────              ─────────────              ─────────────            ─────────
frameworks                 [clone②]                   project_audit_rounds(輪次)
 └ framework_versions ─┐    catalog 副本               ├ living SSP ──[snapshot③]──▶ 凍結 SSP 快照
    └ catalogs ────────┼──▶ profile 副本   ──[clone②]─▶├ AP(per engagement)         (round.ssp_id)
       (control/AO樹)  │    SSP 範本                    ├ AR ─ ar_results[](每輪一筆)
                  [resolve①]                           └ POA&M(FK 指回 AR,不複製)

四個「複製並固定」邊界(PM 教材 §09):① profile resolve → 資源庫 catalog 副本;② 資源庫三件組 → 專案三件組;③ living SSP → 凍結快照(每輪);④ 框架改版 = 新 framework_version。 鐵則:副本存「內容」或指向「永不原地改的版本」,不可只存 id 即時 join 回會變動母表


2. DB Schema 契約

2.1 沿用 base(42 表,七大 OSCAL root)

catalog 樹(catalogs/groups/controls/parts(AO)/params)、profiles(+imports)、component_definitions(+子樹)、system_security_plans(+12 子表)、assessment_plans(+reviewed_controls/local_objectives/activities/tasks)、assessment_results(+ar_results)、poams(+poam_items)、assessment-common(observations/risks/findings)、共用層(metadata/roles/parties/resources)。欄位 1:1 對齊 OSCAL v1.2.2,NOT NULL ⟺ required。

2.2 FR-038 新增 / 修改(delta,見 oscal-v2-deltas.sql

Delta 內容 對應決策
D-1 oscal.frameworks + oscal.framework_versions 兩新表;catalogs.framework_version_id FK base 缺口 G1 / FR-038 §23-26
D-2 compliance.project_audit_rounds(7 態 + parent_round_id + assessment_plan_id + ssp_id + ar_result_id Q4 / D2
D-3 oscal.ap_assessment_subjects(抽查名單關聯化,從 ap_tasks.subjects JSONB 抽出) D3
D-4 oscal.assessment_finding_risks(finding↔︎risk 多對多) Q2
D-4b oscal.assessment_remediations(response/整改計畫)+ oscal.poam_milestones(task/里程碑,每里程碑各別 assignee)— OSCAL risk → remediations[](response) → tasks[] 三層忠實落地 PM2
D-5 oscal.catalog_controls.source_identifier(跨框架溯源佔位) D6

compliance.project_audit_roundsstatus/round_type/parent_round_id⟺close-out 已加 CHECK 約束(Q4 鎖定的值域 DB 層強制)。catalogs.framework_version_id 的 clone 語意已定:副本沿用指向作 provenance、控制項存內容不 join 回母版。

base 既有的 oscal.poams(OSCAL 文件 root)與舊 compliance.poams(扁平產品表)並存語意:本期 POA&M 以 OSCAL poams/poam_items + assessment_risks(remediations/milestones) 為主,舊扁平表的去留在 BE sub-project(Wave 2)決定。

2.3 產品生命週期欄(status)保留決策

base 每個 root 表都有 status varchar(20)(draft/published/deprecated),註解標「待決策」。本設計保留:framework/catalog/profile/ssp 都需要 draft↔︎published 區分(資源庫只能選 published 版本,FR-038 §42)。


3. 稽核 engagement 模型(D2 落地細節,讀 schema 後的關鍵收斂)

讀 base schema 發現一個必須交代的細節:oscal.assessment_plans.import_ssp_id單一 NOT NULL,且 PM 教材 §01 明寫「AP 對著的是那份定版快照,不是還在改的 SSP」。這把 D2 的「1 project 1 AP/AR」精修成「1 engagement 1 AP/AR」

Engagement = 一個 initial(或 surveillance)輪 + 其下所有 close-out 覆核輪。

輪次 round_type parent_round_id AP AR ar_results
#1 首評 initial NULL 新建 AP-1(import-ssp→#1 快照) 新建 AR-1(import-ap→AP-1) 追加 result #1
#2 覆核 close-out 1 沿用 AP-1 沿用 AR-1 追加 result #2(自帶 narrowed reviewed-controls)
#3 再覆核 close-out 2 沿用 AP-1 沿用 AR-1 追加 result #3
#4 年度 surveillance NULL 新建 AP-2(import-ssp→#4 新快照) 新建 AR-2 追加 result #1

為什麼這樣切:

  • 滿足 OSCAL 硬規則:AR.import-ap 單一、AP.import-ssp 單一 NOT NULL、results[] 是官方多輪機制。
  • 滿足 PM 教材兩條看似衝突的話:「AP 對著定版快照」(AP-1.import-ssp→#1 快照,是凍結的)+「在原 AR 後追加第二次」(覆核輪 append result 到 AR-1)。
  • 滿足「覆核重新 snapshot」:覆核輪仍 snapshot 當下 SSP 存 round.ssp_id,該 result 對著新快照判定;AP 的 import-ssp 維持 engagement 起點快照(系統識別 anchor),point-in-time 差異記在 result 層。
  • 年度查核(surveillance)= 新 engagement:SSP 已大幅演進,起新 AP/AR 才合理(避免 import-ssp 指向一年前的舊快照)。

project_audit_rounds.assessment_plan_id + ar_result_id 就是把這個 engagement 模型 wire 起來的兩個 FK。

跨 round 結案連動:覆核輪 result 定版確認母輪 not_met 的 AO 過了 → 沿 parent_round_id 關閉母輪 POA&M → 母輪轉 closed


4. jedi-oscal-v2 套件對外介面契約

4.1 定位

  • 新資料夾 ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/import 名稱 jedi_oscal_v2(dist jedi-oscal-v2)。
  • 套件只管 OSCAL 物件的存取與轉換;業務流程(輪次狀態機、權限、workflow、跨 schema 業務表如 project_audit_rounds)留在主專案。
  • DDD 四層沿用舊套件結構:domain/(entity/repository interface/service)、infra/(model/mapper/repository impl/adapter)、app/(app service/dto)、ports/(parser provider/factory)、common/(enum/code)。
  • 開發期走 poetry path dependency;feature 完成才 bump + 推 Nexus。

4.2 對外 App Service(主專案 import 點)契約

簽章為「契約意圖」,細節 dto 在 Wave 1 各 model sub-project 定稿。所有 method 走 @transaction(由主專案 app service 開 scope,套件 method 為被呼叫端)。

模組 Service 核心 method(契約)
Framework FrameworkService add/get/list/update_frameworkadd_framework_versionlist_versions(framework_uid)publish_version(version_uid)
Catalog CatalogService import_catalog_from_pdf(stream, framework_code, …)get/list_catalogget_control_tree(catalog_uid)list_aos(catalog_control_id)
Profile ProfileService add_profile(imports, …)resolve_profile(profile_uid) → resolved control set(邊界①)、get/list_profile
Snapshot/Clone OscalSnapshotService clone_resource_library(framework_version_uid) → (catalog,profile,ssp) 副本(邊界②)、snapshot_ssp(ssp_id) → frozen ssp_id(邊界③)、deep_clone_ssp(src_ssp_id)
SSP SspService add_empty_sspget/update_ssp、子物件 CRUD(system-characteristics / parties / components / inventory / implemented-requirements / by-components / SoA props)
AP AssessmentPlanService create_ap(import_ssp_id, …)generate_draft_from_ssp(ssp_snapshot_id)(AP 草稿自動生成)、set_reviewed_controlsset_assessment_subjectsset_tasks
AR AssessmentResultService add_ar(import_ap_id)add_result(ar_id, snapshot_ref) → ar_result_idupsert_finding(ar_result_id, ao, state)(AO 全量矩陣)、list_findings(ar_result_id)
Risk AssessmentRiskService add_risk(ar_result_id, …)link_findings(risk_id, finding_ids[])(Q2 多對多)、list_risks(ar_result_id)
POA&M PoamService generate_from_findings(ar_result_id)(不複製,FK 指回)、upsert_remediation(risk_id, …)(response/整改計畫;CMMC 可自動建預設一筆)、add_milestone(remediation_id, assignee_user_id, …)(→ oscal.poam_milestones,D-4b)、list_remediations(risk_id)list_milestones(remediation_id)list_poam_items
Import/Export OscalIoService export_oscal(doc_type, uid, fmt)(JSON/XML/YAML)、import_ssp_docx/excel(落地 v2 schema)

4.3 Parser Adapter(沿用 port 機制)

ports/oscal_parser_factory.get_oscal_parser_adapter(code) → CMMC / ISO / NIST adapter,pdf_parser / excel_parser / convert_to_oscal_catalog_entity。本期把舊 adapter 搬移並對齊新 catalog/group/control/part(AO) 結構(AO 落 catalog_control_parts、method 存 props)。

4.4 主專案 ↔︎ 套件 邊界(哪些留主專案)

  • compliance.project_audit_rounds 狀態機、權限檢查(manager/auditor)、workflow/job 引擎綁定(Q1:綁 SSP 控制項)、跨 schema 業務 mapping → 主專案
  • OSCAL 物件 CRUD / resolve / snapshot / clone / 匯入匯出 → 套件

5. API Spec 骨架(路由清單;各 SA 細節留各 sub-project)

URL 慣例(CLAUDE.md):單筆單數、清單/批次複數、kebab-case。envelope {code,data}

區塊 代表路由 動作 sub-project
合規框架 POST /oscal/frameworks/framework-versions/import/{uid}/publish 框架/版本/匯入/發布 BE-B1
合規資源庫 POST /resource-libraries(選 framework_version → clone)、匯入 SSP docx/excel 三件組建立 BE-B1
專案 POST /projects/start(clone 資源庫三件組;AP/AR 延後) 專案成立 BE-B2
稽核輪次 POST /projects/{uid}/audit-rounds/{round}/launch-audit(snapshot)、/{round}/launch-reverify 輪次 + 狀態轉換 BE-B3
SSP 既有 ssp_* 路由對齊 v2 schema SSP 維護 BE-B3
AP POST /audit-rounds/{round}/ap(草稿生成)、PUT …/reviewed-controls…/subjects…/tasks 稽核計畫(全新) BE-B4 / FE-C1
AR GET …/findings(AO 全量矩陣)、PUT …/finding/{uid}POST …/risksPUT …/risk/{uid}/findings(組風險) 判定 + 風險總結 BE-B5 / FE-C2
POA&M POST …/poam/generatePUT …/poam-item/{uid}POST …/poam-item/{uid}/milestonesPOST …/close-round 改善 + 結案 BE-B5 / FE-C3

6. Error Code 規劃(命名 GRC_<HTTP><序號>

新增族群(序號待 Wave 2 開工前 grep common/code/grc_error_code.py 最大值續編,避免衝突):

  • 輪次:GRC_ROUND_NOT_FOUND(404x)、GRC_ROUND_INVALID_STATUS_TRANSITION(412x)、GRC_ROUND_SSP_NOT_FROZEN(412x)、GRC_REVERIFY_PARENT_NOT_PENDING(412x)
  • AP:GRC_AP_REVIEWED_CONTROLS_REQUIRED(412x)
  • AR:GRC_AR_FINDING_TARGET_REQUIRED(412x)、GRC_AR_RESULT_NOT_LATEST(412x)
  • Risk:GRC_RISK_NO_FINDING_LINKED(412x)

7. 值域與範圍鎖定(CMMC-only)

  • risk status CMMC 允許集open / investigating / remediating / closed。ISO 的 deviation-* / risk_accepted(base COMMENT 列出的)本期不 wire(D5 follow-up),避免 FE 驅動不到。
  • ap_assessment_subjects.subject_type:component / inventory-item / location / party / user。user 是產品擴充(對 SSP system-user),OSCAL 匯出時映射為 party/component 參照。

8. 留給後續 sub-project 細化(不在 Wave 0 契約)

  • 各 model 的 entity/dto/mapper 細節(Wave 1 套件)
  • compliance.poams 扁平表 vs OSCAL poams 的最終取捨(Wave 2)
  • 舊資料遷移(D7,正式環境)
  • FE 元件設計、AO 全量矩陣的摺疊 UI、風險總結畫面(Wave 3)
  • D5 ISO 整合(risk_accepted deviation / L1⊂L2 / SoA 自選排除)— follow-up

D2 表述更正:requirement-analysis §3.3 早期寫「1 project → 1 AP / 1 AR」,已被本文件 §3 精修為「1 AP/AR per engagement」(surveillance 起新 AP/AR)取代。delta 的 project_audit_rounds.assessment_plan_id 是 nullable FK、無 project-unique 約束,正確支援 per-engagement 模型。Wave 2 以本文件 §3 為準。


9. 平行開發 wiring(契約定稿後)

  • Wave 1(套件,~5 agent 平行):A1 catalog+framework+profile / A2 SSP+UI落點 / A3 AP+subjects / A4 AR+risk+POA&M / A5 parser adapter。
  • Wave 2(BE,交疊):B1 框架+資源庫 / B2 專案成立 / B3 SSP+輪次狀態機 / B4 AP 後端 / B5 AR+POA&M+結案。
  • Wave 3(FE,交疊):C1 AP 畫面 / C2 AR 總結層 / C3 POA&M / C4 輪次切換。
  • 規範:subagent 顯式 git add、各 repo 分開 commit、不切 branch、套件走 path dependency。