Stage 3:POA&M 矯正(Phase 4)設計規格書

日期:2026-03-24 狀態:Draft


§1

一、目標

實作 Phase 4 矯正階段:POA&M CRUD、close-round 觸發覆核、與 Stage 2 稽核流程的迴圈銜接。


§2

二、完整流程

confirm_audit 自動產生 POA&M (status=open),AP → remediation
  → PM 填寫改善計畫(remediation_plan / due_date / assignee)→ in_progress
    → 執行人處理被退回的 Job(補件上傳)
      → PM 確認矯正完成 → POA&M → closed(手動)
        → 全部 POA&M closed → PM/Auditor 觸發 close-round
          → AP → auditing,重置相關 AR control verdict = null
            → 稽核員覆核(復用 Stage 2 verdict/finding CRUD)
              → confirm_audit → 全 pass → AP → closed(結案)
              → confirm_audit → 有 fail → 再次 remediation(迴圈)

關鍵:close-round 不呼叫 launch_audit

close-round 直接將 AP 從 remediation 改回 auditing不經過 active、不呼叫 launch_audit。 同一份 assessment_result_datas(run_no=1)和其下的 assessment_result_controls 被複用, 只有與 POA&M 相關的 AR control 的 verdict 被重置為 null。 稽核員在原有的 AR controls 上重新填寫 verdict,然後呼叫 confirm_audit

角色分工

階段 角色 動作
POA&M 產生 系統 confirm_audit 自動從 findings 建立
填寫改善計畫 PM 填 remediation_plan、指派 assignee、設 due_date
補件執行 任務執行人 在被 revert 的 Job 中重新上傳證據
標記矯正完成 PM 手動將 POA&M → closed
觸發覆核 Auditor close-round,AP 回到 auditing
覆核判定 稽核員 復用 Stage 2 verdict/finding CRUD
結案 / 再矯正 稽核員 confirm_audit 決定 closed 或再次 remediation

§3

三、資料模型變更

3.1 compliance.poams 欄位擴充

現有欄位(Stage 2 已建):

欄位 類型 說明
id SERIAL PK
uid VARCHAR(36)
assessment_plan_id INTEGER FK → oscal.assessment_plans.id
ar_finding_id INTEGER FK → oscal.assessment_result_findings.id
control_identifier VARCHAR(50) 控制項編號 snapshot
ao_uid VARCHAR(36) 對應 AP Task UID
status VARCHAR(30) open / in_progress / closed
closed_at TIMESTAMP
tenant_id INTEGER
org_unit_id INTEGER
created_at / updated_at TIMESTAMP
created_user / updated_user VARCHAR(50)

新增欄位:

欄位 類型 Nullable 說明
remediation_plan TEXT YES 矯正方案描述
due_date DATE YES 預計完成日
assignee_uid VARCHAR(36) YES 負責人 user UID

3.2 Migration SQL

-- Stage 3: POA&M Remediation
ALTER TABLE compliance.poams ADD COLUMN IF NOT EXISTS remediation_plan TEXT;
ALTER TABLE compliance.poams ADD COLUMN IF NOT EXISTS due_date DATE;
ALTER TABLE compliance.poams ADD COLUMN IF NOT EXISTS assignee_uid VARCHAR(36);

§4

四、API 設計

所有 API 在 AP-scoped 路徑下:/api/1.0/grc/project/<project_uid>/ap/<ap_uid>/...

4.1 POA&M 分頁列表

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

Request(繼承 RequestMetaSchema):

{
  "pager": { "page": 1, "page_size": 25 },
  "sort": [{ "field": "created_at", "order": "desc" }],
  "filters": {
    "status": "open",
    "search": "AC.L2"
  }
}

Response(繼承 EnvelopeSchema):

{
  "code": 1,
  "data": [
    {
      "uid": "poam-uuid",
      "control_identifier": "AC.L2-3.1.1",
      "ao_uid": "task-uuid",
      "status": "open",
      "finding_title": "存取控制政策未完整",
      "finding_severity": "high",
      "finding_category": "deficiency",
      "remediation_plan": null,
      "due_date": null,
      "assignee_uid": null,
      "assignee_name": null,
      "created_at": "2026-03-24T10:00:00"
    }
  ],
  "meta": { "paging": true, "page": 1, "page_size": 25, "total": 3 }
}

assignee_name 透過 LEFT JOIN users 表(by assignee_uid = users.uid)取得 nickname,在 repo 層查詢時一次性 JOIN,避免 N+1。

Filters:

參數 類型 說明
status String open / in_progress / closed
search String 模糊搜尋 control_identifier / finding_title

4.2 POA&M 詳情

GET /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/poam/<poam_uid>

Response:

{
  "code": 1,
  "data": {
    "uid": "poam-uuid",
    "control_identifier": "AC.L2-3.1.1",
    "ao_uid": "task-uuid",
    "status": "in_progress",
    "remediation_plan": "補充存取控制政策文件並提交審查",
    "due_date": "2026-04-15",
    "assignee_uid": "user-uuid",
    "assignee_name": "王小明",
    "closed_at": null,
    "finding": {
      "uid": "finding-uuid",
      "title": "存取控制政策未完整",
      "category": "deficiency",
      "severity": "high",
      "description": "未能提供完整的存取控制政策文件...",
      "recommendation": "建議補充政策文件..."
    },
    "job_info": {
      "workflow_execution_id": 123,
      "job_uid": "job-uuid",
      "job_status": "PROCESSING"
    },
    "created_at": "2026-03-24T10:00:00",
    "updated_at": "2026-03-24T10:00:00"
  }
}

job_infoao_uid 反查:ao_uid → assessment_plan_tasks.uid → ap_task_wf_execution_mapping → workflow_execution_id → job_executions (type=USER)。取最新一筆 USER job(不限 status),因為被 revert 後 job 可能處於 TODO 或 PROCESSING。若查無結果,job_info 回傳 null

Repo 層查詢時需驗證 POA&M 的 assessment_plan_id 對應的 AP 的 uid 等於 URL 中的 ap_uid,防止跨 AP 存取。

4.3 POA&M 更新

PUT /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/poam/<poam_uid>

Request:

{
  "status": "in_progress",
  "remediation_plan": "補充存取控制政策文件...",
  "due_date": "2026-04-15",
  "assignee_uid": "user-uuid-123"
}

所有欄位皆為 optional,只更新有帶的欄位。

狀態轉換規則:

條件
open in_progress 需同時填寫 remediation_plan(本次 request 帶值,或 DB 中已有值)
open closed 允許(輕微 finding 可直接結案),自動寫入 closed_at
in_progress closed 允許(PM 手動確認),自動寫入 closed_at
closed in_progress 允許(重新開啟),清除 closed_at

驗證邏輯放在 App Service 層(poam_service.py),serializer 層只負責格式驗證。

Response: 回傳更新後的 POA&M 基本欄位(不含 finding / job_info nested object):

{
  "code": 1,
  "data": {
    "uid": "poam-uuid",
    "control_identifier": "AC.L2-3.1.1",
    "ao_uid": "task-uuid",
    "status": "in_progress",
    "remediation_plan": "補充存取控制政策文件...",
    "due_date": "2026-04-15",
    "assignee_uid": "user-uuid-123",
    "assignee_name": "王小明",
    "closed_at": null,
    "created_at": "2026-03-24T10:00:00",
    "updated_at": "2026-03-24T11:30:00"
  }
}

4.4 Close Round

POST /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/close-round

無 Request Body。

邏輯:

  1. 驗證 AP status = remediation(若非 remediation,回 412 GRC_AP_NOT_REMEDIATION;已經是 auditing 代表 close-round 已執行過,確保冪等性)
  2. 查詢該 AP 所有 POA&M(by assessment_plan_id
  3. 驗證全部 POA&M status = closed(若有未 closed 的,回傳 412 + 未完成數量)
  4. 找出有 POA&M 的 AR control:從 POA&M 的 control_identifier 集合 → 查同一 assessment_result_data(run_no=1)下 control_id 匹配的 assessment_result_controls → 重置 verdict = null、清除 confidence/rationale/remarks
  5. AP status → auditing
  6. 稽核員即可復用 Stage 2 的 verdict/finding CRUD 進行覆核

Response:

{
  "code": 1,
  "data": {
    "ap_uid": "ap-uuid",
    "status": "auditing",
    "reset_count": 5
  }
}

§5

五、權限設計

API 允許角色 檢查方式
POA&M 列表 專案參與者 JWT 登入即可(RLS 控制 tenant 隔離)
POA&M 詳情 專案參與者 同上
POA&M 更新 專案參與者 檢查 user 是否為 project_participants 成員(任何 role 皆可),不額外限制角色。業務上由 PM 主導,但系統不強制 role 檢查,保持彈性
close-round auditor 復用 _check_auditor_role

設計考量:現有 project_participants.role 欄位的有效值在各專案間不一致(有些用 ownermemberauditor),若硬編碼 pm 角色會造成限制。Stage 3 先以專案參與者為準,未來可透過 capability 機制細化。


§6

六、Error Codes

新增至 common/code/grc_error_code.py

Code 中文 Error Code
GRC_POAM_NOT_FOUND POA&M 不存在 GRC_404019
GRC_POAM_INVALID_STATUS_TRANSITION POA&M 狀態轉換不合法 GRC_412006
GRC_POAM_REMEDIATION_PLAN_REQUIRED 需填寫矯正方案 GRC_412007
GRC_AP_NOT_REMEDIATION 稽核計畫不在矯正中狀態 GRC_412004
GRC_POAM_NOT_ALL_CLOSED 尚有 POA&M 未結案 GRC_412005

Error code 統一使用 412(PreconditionFailed)表示前置條件不滿足,與現有 GRC_412001~GRC_412003 延續同一序列。


§7

七、DDD 分層架構

新增檔案

Layer 檔案 說明
Migration scripts/sql/stage3_migration.sql ALTER TABLE 加欄位
Domain Service domain/grc/service/grc_poam_domain_service.py 委派 repo
DTO app/grc/dto/poam_dto.py POA&M DTO + FindingDto 複用
App Service app/grc/service/poam_service.py POA&M CRUD + 狀態驗證
Serializer api/grc/serializers/poam.py Request/Response schemas
Route api/grc/routes/poam_route.py 3 個 Resource(List/Detail/Update)

修改檔案

檔案 變更
infra/grc/model/poam_model.py 加 remediation_plan / due_date / assignee_uid
domain/grc/entities/poam_entity.py 加 3 個欄位
infra/grc/mapper/poam_mapper.py 映射新欄位
domain/grc/repository/i_poam_repo.py 新增 list_poams / get_poam / update_poam 方法
infra/grc/repository/poam_repo_impl.py 實作查詢(JOIN finding + users)+ update
common/code/grc_error_code.py 新增 5 個 error codes
app/project/service/oscal_audit_service.py 新增 close_round 方法
di_containers/grc/grc_containers.py 加入 poam_domain_service / poam_service
di_containers/project/project_containers.py oscal_audit_service 加注入 poam_domain_service(如需要)
api/grc/routes/audit_route.py 新增 CloseRoundResource
api/grc/__init__.py 註冊 4 個新 URL

close_round 放置位置

close_round 放在 app/project/service/oscal_audit_service.py,與 launch_auditconfirm_audit 並列。理由:三者都是 AP 狀態轉換的編排操作,需要協調 AP service、AR service、POA&M repo 等多個依賴,屬於同一個 orchestration 職責。


§8

八、與 Stage 2 的銜接

close-round → auditing 的覆核流程

close-round 將 AP 從 remediation 改回 auditing 後:

  1. 稽核員使用 Stage 2 的 POST .../ar/controls/list 查看控制項
  2. 有 POA&M 的控制項 verdict 已被重置為 null,需重新填寫
  3. 沒有 POA&M 的控制項 verdict 保持原值(pass / na)
  4. 稽核員覆核完畢後呼叫 confirm_audit
  5. confirm_audit 邏輯不變:全 pass → closed,有 fail → 再次 remediation + 產生新 POA&M

注意:close-round 不呼叫 launch_audit,不建立新的 assessment_result_datas。同一份 ar_data(run_no=1)被複用,避免 UniqueConstraint 衝突。

迴圈控制

每次 remediation → auditing → confirm_audit 失敗時,會產生新的 POA&M(status=open)。舊的 closed POA&M 保留作為歷史紀錄。POA&M 列表 API 支援 status filter,前端可區分:

  • 當前待處理:status != closed
  • 歷史紀錄:status = closed

POA&M 不支援刪除

POA&M 由系統自動產生(confirm_audit),不提供 DELETE API。若稽核員刪除了對應的 AR finding,POA&M 會成為孤立紀錄,但不影響流程 — close-round 只檢查 POA&M 狀態是否全部 closed,不回查 finding 是否仍存在。


§9

九、證據處理

不新建證據表。POA&M 詳情 API 透過 ao_uid 反查關聯的 Job 資訊:

ao_uid → assessment_plan_tasks.uid
  → ap_task_wf_execution_mapping → workflow_execution_id
    → job_executions (type=USER, 依 id DESC 取最新一筆,不限 status)

前端在 POA&M 詳情頁顯示 Job 連結,點擊跳轉到 Job 頁面處理補件上傳。Job 被 revert 後可能處於 TODO(待處理)或 PROCESSING(進行中),前端依 job_status 顯示對應狀態。