日期:2026-03-24 狀態:Draft
實作 Phase 4 矯正階段:POA&M CRUD、close-round 觸發覆核、與 Stage 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 直接將 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 |
現有欄位(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 |
-- 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);所有 API 在 AP-scoped 路徑下:/api/1.0/grc/project/<project_uid>/ap/<ap_uid>/...
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 |
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_info 從 ao_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 存取。
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"
}
}POST /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/close-round
無 Request Body。
邏輯:
remediation(若非 remediation,回 412 GRC_AP_NOT_REMEDIATION;已經是 auditing 代表 close-round 已執行過,確保冪等性)assessment_plan_id)closed(若有未 closed 的,回傳 412 + 未完成數量)control_identifier 集合 → 查同一 assessment_result_data(run_no=1)下 control_id 匹配的 assessment_result_controls → 重置 verdict = null、清除 confidence/rationale/remarksauditingResponse:
{
"code": 1,
"data": {
"ap_uid": "ap-uuid",
"status": "auditing",
"reset_count": 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欄位的有效值在各專案間不一致(有些用owner、member、auditor),若硬編碼pm角色會造成限制。Stage 3 先以專案參與者為準,未來可透過 capability 機制細化。
新增至 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延續同一序列。
| 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 放在 app/project/service/oscal_audit_service.py,與 launch_audit、confirm_audit 並列。理由:三者都是 AP 狀態轉換的編排操作,需要協調 AP service、AR service、POA&M repo 等多個依賴,屬於同一個 orchestration 職責。
close-round 將 AP 從 remediation 改回 auditing 後:
POST .../ar/controls/list 查看控制項confirm_auditconfirm_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 != closedstatus = closedPOA&M 由系統自動產生(confirm_audit),不提供 DELETE API。若稽核員刪除了對應的 AR finding,POA&M 會成為孤立紀錄,但不影響流程 — close-round 只檢查 POA&M 狀態是否全部 closed,不回查 finding 是否仍存在。
不新建證據表。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 顯示對應狀態。