Stage 2:外部稽核(Phase 3)設計規格

日期:2026-03-24 狀態:Draft — 待確認


§1

一、目標與範圍

目標

實作 GRC 合規管理系統的外部稽核流程(Phase 3),讓稽核員能逐控制項寫入稽核判定(verdict)與發現(findings),並在稽核結束時自動處理矯正流程。

範圍

  • Phase 3 完整流程:launch_audit → 稽核員寫 verdict/findings → confirm_audit
  • confirm_audit 自動產生 POA&M 記錄 + revert 失敗 AO 的 workflow
  • AP status 擴充至 4 值生命週期
  • 不包含:POA&M 前端追蹤頁面 / CRUD API(Stage 3)

設計決策摘要

決策 選擇 原因
範圍 Phase 3 + confirm_audit 自動化 confirm_audit 不產生 POA&M 的話系統沒有落地行動項
權限控管 project_participants.role = "auditor" 用現有機制,不新建表
launch_audit 前置驗證 警告但不阻擋 稽核時程常由外部決定,不應被內部進度卡住
AR run 模型 單一 run(run_no=1) 簡化 API,多 run 需求到 Stage 3 再擴充
confirm_audit revert 粒度 由稽核員在 finding 指定 ao_uid 稽核員最清楚哪個 AO 有問題
confirm_audit 前置驗證 所有控制項必須有 verdict 稽核結果是正式文件,不允許模糊空間
架構 拆兩個 service(編排 + CRUD) oscal_project_service 已過大,職責分離
AR controls 頁面 Flat list + group 資訊,前端建樹 數量可控,flat list 支援分頁/篩選更彈性

§2

二、AP Status 生命週期

Enum 變更(jedi-oscal)

移除 draftcompletedarchived,最終 4 個值:

class AssessmentPlanStatus(StrEnum):
    ACTIVE = "active"           # Phase 1+2:任務指派 + 執行
    AUDITING = "auditing"       # Phase 3:外部稽核
    REMEDIATION = "remediation" # Phase 4:矯正
    CLOSED = "closed"           # 本輪結案

狀態轉換

active → auditing → remediation → closed
              ↘                      ↗
               ──── (全 pass) ──────
觸發 API 條件
active auditing launch_audit AP 必須是 active
auditing closed confirm_audit 全部 verdict = pass 或 na
auditing remediation confirm_audit 任一 verdict = fail 或 partial
remediation auditing launch_audit(Stage 3+4) 矯正後重新稽核
remediation closed close_round(Stage 3) 手動結案

每個轉換 API 檢查當前 status 是否在合法的「從」狀態,不合法拋 PreconditionFailedError


§3

三、API 規格

3.1 launch_audit — 啟動外部稽核

POST /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/launch-audit

權限: auditor 或 project owner

Request body:

{
  "force": false
}

流程:

  1. 驗證 AP status = active
  2. 驗證呼叫者權限
  3. 統計未完成 tasks(assessment_plan_tasks.status != "completed"
  4. 若有未完成 tasks 且 force = false → 回傳 warning,不執行
  5. force = true 或無未完成 tasks → 執行:
    • 查詢該 AP 是否已有 AR(start_oscal_project 會建立空白 AR)
    • 有現有 AR:在該 AR 下建立 assessment_result_datas(run_no=1)+ 初始化 assessment_result_controls(verdict = null)
    • 無現有 AR:建立 assessment_results(含 metadata + document)+ assessment_result_datas(run_no=1)+ 初始化 assessment_result_controls(verdict = null)
    • AP status → auditing

注意: start_oscal_project 已會建立空白 AR(只有 AR 本體,無 run 和 controls),因此 launch_audit 通常是在現有 AR 下新增 run + controls,而非重建 AR。

Warning response(force=false 且有未完成 tasks):

{
  "code": 1,
  "data": {
    "can_launch": true,
    "warning": true,
    "incomplete_tasks": 5,
    "total_tasks": 20
  }
}

Success response:

{
  "code": 1,
  "data": {
    "ar_uid": "...",
    "status": "auditing",
    "incomplete_tasks": 5,
    "total_tasks": 20
  }
}

Service: app/project/service/oscal_audit_service.pylaunch_audit(ap_uid, force, curr_user)


3.2 AR Controls 列表

POST /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/ar/controls/list

權限: 所有專案參與者

Request body: 標準 RequestMetaSchema(pager + sort + filters)

Filters:

欄位 類型 說明
search string 控制項代號或名稱
verdict string pass / fail / partial / na / null(未填)

Response item:

{
  "ar_control_uid": "...",
  "control_id": "AC-1",
  "control_title": "Access Control Policy",
  "verdict": "fail",
  "confidence": 85,
  "findings_count": 2,
  "group_uid": "...",
  "group_name": "Access Control"
}

前端依 group_uid 分組建樹狀導航。

Service: app/grc/service/audit_service.py


3.3 AR Control 詳情

GET /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/ar/control/<ar_control_uid>

權限: 所有專案參與者

Response:

{
  "data": {
    "ar_control_uid": "...",
    "control_id": "AC-1",
    "control_title": "Access Control Policy",
    "description": "...",
    "guidance": "...",
    "verdict": "fail",
    "confidence": 85,
    "rationale": "...",
    "remarks": "...",
    "group_uid": "...",
    "group_name": "Access Control",
    "ssp_implementation": {
      "implementation_status": "partial",
      "implementation_description": "..."
    },
    "findings": [
      {
        "uid": "...",
        "category": "deficiency",
        "severity": "high",
        "title": "...",
        "description": "...",
        "recommendation": "...",
        "ao_uid": "...",
        "ao_name": "..."
      }
    ],
    "assessment_objects": [
      {
        "ao_uid": "...",
        "ao_name": "...",
        "evidences": [
          {
            "uid": "...",
            "evidence_type": "file",
            "description": "...",
            "file_id": 123,
            "reference_url": null
          }
        ]
      }
    ]
  }
}

3.4 寫入 Verdict

PUT /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/ar/control/<ar_control_uid>/verdict

權限: auditor

前置條件: AP status = auditing

Request body:

{
  "verdict": "pass | fail | partial | na",
  "confidence": 85,
  "rationale": "...",
  "remarks": "..."
}

Response: 更新後的 ar_control 資料


3.5 Finding CRUD

新增 Finding:

POST /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/ar/control/<ar_control_uid>/findings

Request body:

{
  "category": "deficiency | observation | recommendation",
  "severity": "low | medium | high | critical",
  "title": "...",
  "description": "...",
  "recommendation": "...",
  "ao_uid": "..."
}

ao_uid 必填 — 指定哪個 AO 需要矯正。

新增 Finding response:

{
  "code": 1,
  "data": {
    "uid": "...",
    "category": "deficiency",
    "severity": "high",
    "title": "...",
    "description": "...",
    "recommendation": "...",
    "ao_uid": "...",
    "ao_name": "..."
  }
}

注意: Stage 2 期間 finding 可自由刪除(AP status = auditing)。Stage 3 實作 POA&M CRUD 後,應加檢查防止刪除已有 POA&M 關聯的 finding。

Finding 詳情 / 更新 / 刪除:

GET    /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/ar/finding/<finding_uid>
PUT    /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/ar/finding/<finding_uid>
DELETE /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/ar/finding/<finding_uid>

權限: auditor(寫入),所有參與者(讀取)

前置條件: AP status = auditing(寫入操作)


3.6 confirm_audit — 確認稽核結案

POST /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/confirm-audit

權限: auditor 或 project owner

前置驗證:

  1. AP status = auditing
  2. 所有 ar_controls 必須有 verdict(非 null)

驗證失敗 response(412):

{
  "code": 0,
  "msg": "以下控制項尚未填寫 verdict",
  "data": {
    "missing_verdicts": [
      {"control_id": "AC-1", "control_title": "Access Control Policy"},
      {"control_id": "AC-2", "control_title": "Account Management"}
    ]
  }
}

執行流程:

  1. 收集所有 ar_controls 的 verdict

  2. 判定路徑:

    • 全部 pass 或 na → 直接結案路徑(步驟 4)
    • 任一 fail 或 partial → 矯正路徑(步驟 3)
  3. 矯正路徑:

    • 設定 assessment_result_datas.completed_at = now()
    • 收集所有 findings 的 ao_uid(去重)
    • 對每個被點名的 AO:
      • ao_uid → assessment_plan_tasks → assessment_plan_task_workflow_execution_mapping → workflow_execution_id
      • 查詢該 workflow 的 job_executions,找到最新的 COMPLETED 或 PROCESSING job 作為 revert 起點
      • 查詢該 workflow BPMN 的第一個 USER job 作為 revert 目標
      • 呼叫 flow-engine revert_job(workflow_uid, current_job_id, first_job_id, ...)
      • 若 workflow 所有 jobs 皆為 TODO,跳過 revert(無需回退)
    • 為每個 finding 建立一筆 compliance.poams(status = open
    • AP status → remediation
  4. 直接結案路徑:

    • 設定 assessment_result_datas.completed_at = now()
    • AP status → closed

Success response:

{
  "code": 1,
  "data": {
    "outcome": "closed | remediation",
    "findings_count": 3,
    "reverted_ao_count": 2,
    "poam_count": 3
  }
}

Service: app/project/service/oscal_audit_service.pyconfirm_audit(ap_uid, curr_user)


§4

四、DDD 分層架構

新增 / 修改檔案

jedi-oscal 套件(需更版):

檔案 變更
common/enum/status_enum.py AssessmentPlanStatus 改為 4 值(移除 draft/completed/archived,新增 auditing/remediation/closed)
infra/model/ar/assessment_result_control.py verdict 欄位改為 nullable=True(launch_audit 初始化時 verdict 為 null)
infra/model/ar/assessment_result_finding.py 新增 ao_uid VARCHAR(36) 欄位(稽核員指定矯正對象)
domain/entity/ar/assessment_result_finding_entity.py 新增 ao_uid 欄位
infra/mapper/ar/assessment_result_finding_mapper.py 映射 ao_uid

jedi-oscal DB migration:

-- verdict 改為 nullable
ALTER TABLE oscal.assessment_result_controls
    ALTER COLUMN verdict DROP NOT NULL;

-- finding 新增 ao_uid
ALTER TABLE oscal.assessment_result_findings
    ADD COLUMN ao_uid VARCHAR(36);

-- 現有 AP status 資料遷移
UPDATE oscal.assessment_plans SET status = 'active' WHERE status = 'draft';
UPDATE oscal.assessment_plans SET status = 'closed' WHERE status IN ('completed', 'archived');

主專案 — 編排層:

檔案 說明
App Service app/project/service/oscal_audit_service.py launch_audit() / confirm_audit()
DI Container di_containers/project/project_containers.py 註冊 oscal_audit_service

主專案 — GRC 稽核 CRUD:

檔案 說明
Route api/grc/routes/audit_route.py 所有稽核相關端點
Serializer api/grc/serializers/audit.py Request / Response schemas
App Service app/grc/service/audit_service.py verdict / finding / ar_controls 查詢
DTO app/grc/dto/audit_dto.py ArControlDto、ArControlDetailDto、ArFindingDto
Domain Service domain/grc/service/grc_audit_domain_service.py 委派 repo
Repo Interface domain/grc/repository/i_grc_audit_repo.py 抽象介面
Repo Impl infra/grc/repository/grc_audit_repo_impl.py SQLAlchemy 查詢
Mapper infra/grc/mapper/grc_audit_mapper.py ORM ↔︎ Entity
Entity domain/grc/entities/grc_audit_entity.py AR control / finding domain entities
Blueprint api/grc/__init__.py 註冊新 route
DI Container di_containers/grc/grc_containers.py 註冊 audit service / repo
Error Code common/code/grc_error_code.py 新增 error codes

主專案 — POA&M 基礎建設:

檔案 說明
Model infra/grc/model/poam_model.py ORM model
Entity domain/grc/entities/poam_entity.py domain entity
Repo Interface domain/grc/repository/i_poam_repo.py 抽象介面(Stage 2 只有 batch create)
Repo Impl infra/grc/repository/poam_repo_impl.py insert 實作
Mapper infra/grc/mapper/poam_mapper.py ORM ↔︎ Entity
Migration scripts/sql/poam_migration.sql DDL
DI Container di_containers/grc/grc_containers.py 註冊 poam repo

新增 Error Codes

# common/code/grc_error_code.py
GRC_NOT_AUDITOR              = ("使用者不具備稽核員角色", "GRC_403001")
GRC_AP_NOT_ACTIVE            = ("稽核計畫狀態不是進行中,無法啟動稽核", "GRC_412001")
GRC_AP_NOT_AUDITING          = ("稽核計畫不在稽核中狀態", "GRC_412002")
GRC_AR_VERDICT_INCOMPLETE    = ("尚有控制項未填寫稽核判定", "GRC_412003")
GRC_AR_NOT_FOUND             = ("稽核結果不存在", "GRC_404016")
GRC_AR_CONTROL_NOT_FOUND     = ("稽核控制項不存在", "GRC_404017")
GRC_AR_FINDING_NOT_FOUND     = ("稽核發現不存在", "GRC_404018")

§5

五、POA&M 資料表

CREATE TABLE compliance.poams (
    id                  SERIAL PRIMARY KEY,
    uid                 VARCHAR(36) NOT NULL DEFAULT gen_random_uuid(),
    assessment_plan_id  INTEGER NOT NULL,
    ar_finding_id       INTEGER NOT NULL,
    control_identifier  VARCHAR(50) NOT NULL,
    ao_uid              VARCHAR(36) NOT NULL,
    status              VARCHAR(30) NOT NULL DEFAULT 'open',
    closed_at           TIMESTAMP,
    tenant_id           INTEGER NOT NULL,
    org_unit_id         INTEGER,
    created_at          TIMESTAMP NOT NULL DEFAULT now(),
    updated_at          TIMESTAMP NOT NULL DEFAULT now(),
    created_user        VARCHAR(50),
    updated_user        VARCHAR(50)
);

CREATE UNIQUE INDEX uq_poams_uid ON compliance.poams (uid);
CREATE INDEX ix_poams_ap_id ON compliance.poams (assessment_plan_id);
CREATE INDEX ix_poams_status ON compliance.poams (status);
CREATE INDEX ix_poams_tenant ON compliance.poams (tenant_id);

Stage 2 只自動建立記錄,不提供前端 CRUD API(Stage 3 實作)。


§6

六、前端整合規格

新增 API 總覽

所有 URL 前綴:/api/1.0/grc

功能 方法 URL 權限
啟動稽核 POST /project/<pid>/ap/<ap_uid>/launch-audit auditor / owner
AR 控制項列表 POST /project/<pid>/ap/<ap_uid>/ar/controls/list 所有參與者
AR 控制項詳情 GET /project/<pid>/ap/<ap_uid>/ar/control/<ar_control_uid> 所有參與者
寫入 Verdict PUT /project/<pid>/ap/<ap_uid>/ar/control/<ar_control_uid>/verdict auditor
新增 Finding POST /project/<pid>/ap/<ap_uid>/ar/control/<ar_control_uid>/findings auditor
Finding 詳情 GET /project/<pid>/ap/<ap_uid>/ar/finding/<finding_uid> 所有參與者
更新 Finding PUT /project/<pid>/ap/<ap_uid>/ar/finding/<finding_uid> auditor
刪除 Finding DELETE /project/<pid>/ap/<ap_uid>/ar/finding/<finding_uid> auditor
確認稽核結案 POST /project/<pid>/ap/<ap_uid>/confirm-audit auditor / owner

AP Status 變更

舊值 新值 說明
draft 移除 合併到 active
active 保留 Phase 1+2
completed 移除 改用 closed
archived 移除 未來再議
auditing(新增) Phase 3
remediation(新增) Phase 4
closed(新增) 結案

前端需更新 AP status 的 badge / 顯示映射。

前端頁面建議

1. 稽核總覽頁

進入條件: AP status = auditing(或 remediation / closed 為唯讀)

UI 結構:

  • 上方:稽核進度統計(已填 verdict / 總數、pass / fail / partial / na 各幾項)
  • 左側導航:依 control group 分組的樹狀結構(從 AR controls list 的 group_uid / group_name 前端分組)
    • 每個 group 可展開,顯示該 group 下的 controls
    • 每個 control 旁顯示 verdict badge(顏色區分)
  • 右側內容區:選中某 control 後顯示審查表單(見下方)

2. 控制項審查頁(右側內容區)

唯讀資訊:

  • 控制項基本資料(code、title、description、guidance)
  • SSP implementation description
  • 該控制項下所有 AO 的 job evidences 列表(來源為 job_evidences,透過 AO → workflow → job 鏈路查詢)

可編輯區域(AP status = auditing 且 role = auditor):

  • Verdict 表單:verdict(下拉)+ confidence(數字)+ rationale + remarks
  • Findings 列表 + 新增按鈕
  • Finding 表單:category(下拉)+ severity(下拉)+ title + description + recommendation + ao_uid(下拉,從該 control 的 AO 列表選)

3. AP status 對 UI 的影響

AP status 稽核總覽頁 Verdict / Finding 操作 launch-audit 按鈕 confirm-audit 按鈕
active 隱藏 不可用 顯示 隱藏
auditing 顯示 可編輯(auditor) 隱藏 顯示(全填完才啟用)
remediation 顯示(唯讀) 不可用 隱藏 隱藏
closed 顯示(唯讀) 不可用 隱藏 隱藏

launch-audit 前端互動流程

點擊「啟動稽核」
  → POST launch-audit { force: false }
  → response.warning == true?
     → 是:顯示確認對話框
           「尚有 {incomplete_tasks}/{total_tasks} 項任務未完成,確定啟動外部稽核?」
           → 確定:POST launch-audit { force: true } → 成功,重新載入 AP 狀態
           → 取消:不動作
     → 否:直接成功,重新載入 AP 狀態

confirm-audit 前端互動流程

點擊「確認結案」
  → POST confirm-audit
  → response.code == 1?
     → outcome == "closed":
        顯示成功訊息「稽核完成,所有控制項通過」
        AP 狀態更新為 closed
     → outcome == "remediation":
        顯示訊息「{findings_count} 項發現需要矯正,{reverted_ao_count} 個評估物件已退回重做」
        AP 狀態更新為 remediation
  → response.code == 0(412)?
     → 顯示「以下控制項尚未填寫 verdict」+ missing_verdicts 列表

Verdict 顯示對照

verdict 中文 英文 Badge 顏色
pass 通過 Pass 綠色
fail 不通過 Fail 紅色
partial 部分通過 Partial 橘色
na 不適用 N/A 灰色
null 未填寫 Pending 淺灰 / 虛線框

Finding Category 顯示對照

category 中文 英文
deficiency 缺失 Deficiency
observation 觀察 Observation
recommendation 建議 Recommendation

Finding Severity 顯示對照

severity 中文 英文 Badge 顏色
critical 嚴重 Critical 紅色
high High 橘色
medium Medium 黃色
low Low 灰色

§7

七、不變的 API

以下 API 不受 Stage 2 影響:

  • Stage 1 所有已遷移的 API(control group / control / AO / job / task-setup / review)
  • 專案列表 / 詳情 / 選單
  • My Jobs / Job 詳情 / Job CRUD
  • Job Comment / Job Evidence
  • SSP 編輯
  • Dashboard
  • 參與者 CRUD

§8

八、前端遷移 Checklist

Stage 2 上線後,前端需要: