合規框架 PDF 匯入兩階段化 — API 規格書(SA)

文件版本:1.1 建立日期:2026-05-03 最後更新:2026-05-04(合併 v2.1 迭代) 文件性質:SA — API 介面契約 對應 SDdesign.md


0. 迭代紀錄

日期 版本 變更摘要
2026-05-03 1.0 初版:parse / detail / confirm / list / delete 五端點
2026-05-04 1.1 合併 v2.1 三項迭代:(a) Step 1 parser-only — metadata 三欄改 optional 並可在 confirm 補;(b) 重新上傳 PDF 覆蓋既有版本(parse 帶 target_version_uid,confirm 走更新而非新建);(c) 合規框架版本即時編輯 — 4 個新端點(catalog-tree + group / control / assessment 各 PUT/DELETE)

1. 端點總覽

1.1 Parse Job 系列(兩階段匯入)

# Method URL Route Class 說明
1 GET /api/1.0/oscal-framework-parse-jobs FrameworkParseJobListRoute 列當前 tenant 進行中草稿(給 banner 用)
2 POST /api/1.0/oscal-framework-parse-jobs/parse FrameworkParseJobParseRoute Step 1 — 上傳 PDF + 選 parser;metadata 可選(v1.1 起);可帶 target_version_uid 走覆蓋模式
3 GET /api/1.0/oscal-framework-parse-jobs/<uid> FrameworkParseJobDetailRoute 取單筆 parse_job(含 parsed_result 給預覽)
4 POST /api/1.0/oscal-framework-parse-jobs/<uid>/confirm FrameworkParseJobConfirmRoute Step 2 confirm — 套用 decisions/overrides + metadata;新建或覆蓋既有版本
5 DELETE /api/1.0/oscal-framework-parse-jobs/<uid> FrameworkParseJobDetailRoute 軟刪 parse_job(is_active=false)

1.2 Framework Version 即時編輯(v1.1 新增)

# Method URL Route Class 說明
6 GET /api/1.0/oscal-framework-version/<uid>/catalog-tree FrameworkVersionCatalogTreeRoute 取版本 + groups + controls + assessments + has_references(給編輯頁載入)
7 PUT /api/1.0/oscal-catalog-group/<uid> CatalogGroupRoute patch group 欄位(draft 限定)
8 DELETE /api/1.0/oscal-catalog-group/<uid> CatalogGroupRoute cascade 刪 group + 子 controls + assessments(draft + 無引用)
9 PUT /api/1.0/oscal-catalog-control/<uid> CatalogControlRoute patch control 欄位
10 DELETE /api/1.0/oscal-catalog-control/<uid> CatalogControlRoute cascade 刪 control + 子 assessments
11 PUT /api/1.0/oscal-catalog-control-assessment/<uid> CatalogControlAssessmentRoute patch assessment 欄位
12 DELETE /api/1.0/oscal-catalog-control-assessment/<uid> CatalogControlAssessmentRoute 刪 assessment

端點命名遵循「複數集合 + 單筆 detail」慣例(見 memory)。Parse Job 沿用 /api/1.0/oscal-framework-* prefix;Catalog edit 則使用既有的 OSCAL 單體資源 prefix。


2. API 端點規格

2.1 GET /api/1.0/oscal-framework-parse-jobs — 列當前 tenant 草稿

用途:FE 進入匯入頁面時取得 banner 用的「未完成草稿」清單。

認證:JWT Required

Query Parameters(皆可選):

欄位 型別 說明
status string 過濾 status,不傳預設只列 awaiting_review + failed
is_mine boolean true 只回傳當前使用者建立的;false/不傳回全部 tenant 內的

ResponseFrameworkParseJobListResponseSchema):

{
  "status": true,
  "data": [
    {
      "uid": "uuid",
      "status": "awaiting_review|failed",
      "file_name": "string",
      "target_framework_uid": "uuid",
      "target_framework_name": "string",
      "target_version": "string",
      "target_release_date": "YYYY-MM-DD",
      "is_mine": true,
      "created_user": "string",
      "created_user_name": "string",
      "created_at": "ISO8601",
      "updated_at": "ISO8601",
      "summary": {
        "groups": 6,
        "controls": 110,
        "assessments": 320
      }
    }
  ]
}

summary 欄位由 BE 對 parsed_result 做 count,方便 FE banner 顯示「N 個 groups / M 個 controls」。 target_framework_name 由 BE 對 framework domain service 做 lookup,遵循審計欄位回傳規範(login_name 同時帶 nickname)。


2.2 POST /api/1.0/oscal-framework-parse-jobs/parse — 上傳並解析

用途:Step 1 上傳 PDF + 選 parser。同步解析 PDF(不開背景 job),回傳 parse_uid 與 status。Metadata(version / release_date / publish_status)改為可選 — 預設於 Step 2 確認時填寫;若帶 target_version_uid 則走「重新上傳到既有版本」覆蓋模式。

認證:JWT Required

Content-Typemultipart/form-data

Form fields

欄位 型別 必填 說明
file binary Y PDF binary
json string (JSON) Y metadata payload,見下表

json payload 結構(FrameworkParseRequestSchema):

欄位 型別 必填 說明
target_framework_uid string (uuid) Y 目標 framework
parser_type string Y PDF 解析器;現行 dropdown 值
target_version string N 目標版本字串(v1.1 改可選;改在 confirm 補)
target_release_date string (YYYY-MM-DD) N 發行日期(v1.1 改可選)
target_publish_status string N draft / published / deprecated(v1.1 改可選)
target_parent_version_uid string (uuid) N 若新增子版本
target_version_uid string (uuid) N v1.1 新增 — 若帶值表示「重新上傳覆蓋既有版本」。BE 預檢:版本存在、屬同 framework、publish_status='draft' 且無引用;自動把既有版本的 version / release_date / publish_status 帶進 parse_job 的 target_*(FE 顯示「重新上傳到 vX.Y」badge 用)

ResponseFrameworkParseResponseSchema):

{
  "status": true,
  "data": {
    "uid": "uuid",
    "status": "awaiting_review|failed",
    "error_code": null,
    "error_message": null,
    "summary": {
      "groups": 6,
      "controls": 110,
      "assessments": 320
    }
  }
}

注意:解析失敗時 HTTP 仍回 200,body status 改 failed 並帶 error 欄位 — 由 FE 統一導去 detail 頁面再依 status 渲染(見 design 4.2)。

Error Cases

條件 HTTP error code
target_framework_uid 不存在 / 跨 tenant 404 GRC_FRAMEWORK_NOT_FOUND
target_parent_version_uid 不屬同 framework 400 GRC_FRAMEWORK_PARSE_JOB_INVALID_PARENT
target_version_uid 不存在 / 跨 framework 404 GRC_FRAMEWORK_NOT_FOUND
target_version_uid 對應版本不是 draft 403 GRC_FRAMEWORK_VERSION_NOT_DRAFT
target_version_uid 對應版本已被引用 409 GRC_FRAMEWORK_VERSION_HAS_REFERENCES
檔案缺失 / 非 PDF 400 GRC_FRAMEWORK_PARSE_FILE_INVALID
檔案 > 50 MB 413 (Flask MAX_CONTENT_LENGTH)

v1.1 變更:版本重名檢查((target_framework_uid, target_version) 衝突)從 parse 移到 confirm(因為 parse 階段 version 可能還沒填)。


2.3 GET /api/1.0/oscal-framework-parse-jobs/<uid> — 取單筆

用途:Step 2 預覽頁載入;或從草稿 banner 點繼續。

認證:JWT Required

Path Parametersuid(parse_job uid)

ResponseFrameworkParseJobDetailResponseSchema):

{
  "status": true,
  "data": {
    "uid": "uuid",
    "status": "awaiting_review|failed|completed|expired",
    "file_name": "string",
    "file_size": 123456,
    "parser_type": "string",
    "target_framework_uid": "uuid",
    "target_framework": {
      "uid": "...", "code": "...", "name": "..."
    },
    "target_version": "string|null",
    "target_release_date": "YYYY-MM-DD|null",
    "target_publish_status": "string|null",
    "target_parent_version_uid": "uuid|null",
    "target_version_uid": "uuid|null",
    "file_uid": "string|null",
    "parsed_result": {
      "catalog": {
        "name": "string",
        "description": "string"
      },
      "groups": [
        {
          "uid": "uuid (parse-time generated)",
          "name": "string",
          "description": "string",
          "order_no": 1,
          "parent_group_uid": "uuid|null"
        }
      ],
      "controls": [
        {
          "uid": "uuid",
          "control_id": "AC.L1-3.1.1",
          "control_title": "string",
          "description": "string",
          "guidance": "string",
          "order_no": 1,
          "group_uid": "uuid"
        }
      ],
      "assessments": [
        {
          "uid": "uuid",
          "name": "string",
          "description": "string",
          "order_no": 1,
          "control_uid": "uuid"
        }
      ]
    },
    "error_code": "string|null",
    "error_message": "string|null",
    "result_framework_version_uid": "uuid|null",
    "is_active": true,
    "created_user": "string",
    "created_user_name": "string",
    "updated_user": "string",
    "updated_user_name": "string",
    "created_at": "ISO8601",
    "updated_at": "ISO8601"
  }
}

Error Cases

條件 HTTP error code
uid 不存在或屬其他 tenant 404 GRC_FRAMEWORK_PARSE_JOB_NOT_FOUND
TTL 過期(is_active=false 且 status=expired) 404 同上

2.4 POST /api/1.0/oscal-framework-parse-jobs/<uid>/confirm — 確認匯入

用途:套用 FE 編輯(decisions + overrides),由 BE 寫入 catalog 系列正式表。依 parse_job 是否帶 target_version_uid 分流:

  • 新建模式target_version_uid 為 null):建立新 framework_version;version 重名檢查在此進行
  • 覆蓋模式target_version_uid 有值):保留版本 uid 不變、舊 catalog 標 DEPRECATED、新 catalog 接到同 framework_version、更新 file_uid

認證:JWT Required

Path Parametersuid

Request BodyFrameworkParseJobConfirmRequestSchema):

{
  "target_version": "string (v1.1 新增;新建模式必填)",
  "target_release_date": "YYYY-MM-DD (v1.1 新增;新建模式必填)",
  "target_publish_status": "string (v1.1 新增;新建模式必填)",
  "decisions": [
    { "type": "group", "uid": "uuid", "action": "delete" },
    { "type": "control", "uid": "uuid", "action": "delete" },
    { "type": "assessment", "uid": "uuid", "action": "delete" }
  ],
  "overrides": {
    "groups": [
      {
        "uid": "uuid",
        "name": "string|null",
        "description": "string|null",
        "order_no": 1,
        "parent_group_uid": "uuid|null"
      }
    ],
    "controls": [
      {
        "uid": "uuid",
        "control_id": "string|null",
        "control_title": "string|null",
        "description": "string|null",
        "guidance": "string|null",
        "order_no": 1,
        "group_uid": "uuid"
      }
    ],
    "assessments": [
      {
        "uid": "uuid",
        "name": "string|null",
        "description": "string|null",
        "order_no": 1,
        "control_uid": "uuid"
      }
    ]
  }
}

規則

  • decisions[].action 目前僅支援 delete(未來若開放新增則加 create
  • overrides[*].uid 必須對應到 parsed_result 內、且未被 decisions 標 delete 的條目
  • overrides[*] 內的字串欄位若為 null 表示「不覆蓋」;其他欄位(order_no / parent_group_uid / group_uid / control_uid)必為當前實際值或新值
  • parent_group_uid / group_uid / control_uid 不可指向被 delete 的 uid
  • v1.1 metadata 規則:若 parse 時已填 metadata 三欄則此處可省略;若 parse 時未填則 confirm 必須補。三欄至少要有一處來源否則丟 GRC_FRAMEWORK_PARSE_JOB_METADATA_REQUIRED
  • 覆蓋模式下 metadata 三欄忽略(沿用既有版本)

ResponseFrameworkParseJobConfirmResponseSchema):

{
  "status": true,
  "data": {
    "framework_version_uid": "uuid",
    "framework_uid": "uuid",
    "version": "string",
    "groups_created": 6,
    "controls_created": 108,
    "assessments_created": 312
  }
}

Error Cases

條件 HTTP error code
parse_job 不存在 / TTL 過期 404 GRC_FRAMEWORK_PARSE_JOB_NOT_FOUND
status != awaiting_review 409 GRC_FRAMEWORK_PARSE_JOB_ALREADY_CONFIRMED
Metadata 三欄都沒有來源(v1.1) 400 GRC_FRAMEWORK_PARSE_JOB_METADATA_REQUIRED
套用後剩餘 controls = 0 400 GRC_FRAMEWORK_PARSE_JOB_EMPTY
引用已 delete 的 uid 400 GRC_FRAMEWORK_PARSE_JOB_INVALID_REFERENCE
寫入時 (framework_id, version) 衝突(新建模式) 409 GRC_FRAMEWORK_VERSION_ALREADY_EXISTS
覆蓋模式下版本不再是 draft / 已被引用(race) 403 / 409 GRC_FRAMEWORK_VERSION_NOT_DRAFT / GRC_FRAMEWORK_VERSION_HAS_REFERENCES

2.5 DELETE /api/1.0/oscal-framework-parse-jobs/<uid> — 軟刪 / 捨棄

用途:使用者點「捨棄草稿」或關閉編輯頁時呼叫。

認證:JWT Required

Response{ "status": true, "data": true }

業務邏輯:將 is_active=false / status='expired'真正 delete row。

Error Cases

條件 HTTP error code
parse_job 不存在 404 GRC_FRAMEWORK_PARSE_JOB_NOT_FOUND
status=completed(已成功匯入) 409 GRC_FRAMEWORK_PARSE_JOB_ALREADY_CONFIRMED

2.6 GET /api/1.0/oscal-framework-version/<uid>/catalog-tree — 取版本 + 所有 catalog 條目(v1.1 新增)

用途:合規框架版本即時編輯頁載入用。一次取版本 metadata + 全部 groups / controls / assessments + 引用狀態(has_references)。

Response

{
  "status": true,
  "data": {
    "version_uid": "uuid",
    "version": "string",
    "publish_status": "string",
    "file_uid": "string|null",
    "has_references": false,
    "groups": [ { "uid": "uuid", "name": "...", "description": "...", "order_no": 1, "parent_group_uid": "uuid|null" } ],
    "controls": [ { "uid": "uuid", "control_id": "...", "control_title": "...", "description": "...", "guidance": "...", "order_no": 1, "group_uid": "uuid" } ],
    "assessments": [ { "uid": "uuid", "name": "...", "description": "...", "order_no": 1, "control_uid": "uuid" } ]
  }
}

2.7 PUT/DELETE /api/1.0/oscal-catalog-group/<uid> — Group 單筆編輯(v1.1 新增)

雙軌守門:對應 framework_version 必須 publish_status='draft'has_references=false

  • PUT body{ name, description, order_no, parent_group_uid }(皆 optional,為 null 表示不變)
  • DELETE:cascade 刪 group + 子 controls + 子 assessments;先檢查 has_references 否則 409

2.8 PUT/DELETE /api/1.0/oscal-catalog-control/<uid> — Control 單筆編輯(v1.1 新增)

  • PUT body{ control_id, control_title, description, guidance, order_no, group_uid }
  • DELETE:cascade 刪 control + 子 assessments

2.9 PUT/DELETE /api/1.0/oscal-catalog-control-assessment/<uid> — Assessment 單筆編輯(v1.1 新增)

  • PUT body{ name, description, order_no, control_uid }
  • DELETE:刪 assessment

§2.7~2.9 共通 Error Cases

條件 HTTP error code
對應版本不是 draft 403 GRC_FRAMEWORK_VERSION_NOT_DRAFT
對應版本已被引用(DELETE 時) 409 GRC_FRAMEWORK_VERSION_HAS_REFERENCES
uid 不存在 404 GRC_CATALOG_GROUP_NOT_FOUND / GRC_CATALOG_CONTROL_NOT_FOUND / GRC_CATALOG_CONTROL_ASSESSMENT_NOT_FOUND(沿用既有)

3. 新增 Error Code

# common/code/grc_error_code.py — Framework PDF Import 段
GRC_FRAMEWORK_PARSE_FILE_INVALID            = ("上傳檔案無效,請上傳 PDF 檔案",                       "GRC_400020")
GRC_FRAMEWORK_PARSE_JOB_EMPTY               = ("解析結果為空,無控制項可匯入",                        "GRC_400017")
GRC_FRAMEWORK_PARSE_JOB_INVALID_PARENT      = ("Parent version 無效或跨 framework",                   "GRC_400018")
GRC_FRAMEWORK_PARSE_JOB_INVALID_REFERENCE   = ("Override 引用了被刪除的條目",                         "GRC_400019")
GRC_FRAMEWORK_PARSE_JOB_METADATA_REQUIRED   = ("確認匯入需指定版本 / 發布日期 / 發布狀態",            "GRC_400021")  # v1.1
GRC_FRAMEWORK_NOT_FOUND                     = ("合規框架不存在",                                      "GRC_404026")
GRC_FRAMEWORK_PARSE_JOB_NOT_FOUND           = ("框架解析任務不存在或已過期",                          "GRC_404025")
GRC_FRAMEWORK_VERSION_ALREADY_EXISTS        = ("框架版本已存在(同 framework 下 version 重複)",       "GRC_409006")
GRC_FRAMEWORK_PARSE_JOB_ALREADY_CONFIRMED   = ("解析任務已被確認匯入或已捨棄",                        "GRC_412015")
GRC_FRAMEWORK_PARSE_FAILED                  = ("框架 PDF 解析失敗",                                  "GRC_422004")

# Framework Version 即時編輯(v1.1 新增)
GRC_FRAMEWORK_VERSION_NOT_DRAFT             = ("僅 draft 狀態的版本可編輯",                           "GRC_403030")
GRC_FRAMEWORK_VERSION_HAS_REFERENCES        = ("版本已被合規資源庫引用,禁止刪除控制項 / 群組 / 評估目標", "GRC_409010")

序號接續既有 grc_error_code.py 末段;422 採 SSP doc parser 同模式(FATAL parse error 用 422 區)。


4. 權限 / 認證

  • 所有端點僅檢查 JWT,不額外加角色檢查(RBAC 由 route_capabilities 配置決定誰可呼叫)
  • 新路由 /compliance-framework/import-version 需在 ui_routes 註冊;對應的 BE capabilities 需新增至 route_capabilities(詳見 docs/system-design/permission/... 的 SOP)