Stage 5:啟動專案執行設計規格書

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


§1

一、目標

補齊 Phase 1 → Phase 2 的流程閘門:新建專案後 AP 處於 preparing 狀態,PM 設置完成後明確觸發「啟動專案」,AP 轉為 active,通知第一批任務執行人員。

同時重新命名「新建專案」API,消除 start 命名與「啟動執行」的混淆。


§2

二、設計決策摘要

決策 結論 理由
AP 初始狀態 preparing(新增) 區隔「設置中」與「執行中」,PM 未確認前不通知
啟動前檢查 檢查但可 force bypass 彈性最好,避免遺漏但不阻擋特殊情況
通知範圍 只通知 BPMN 第一個任務的指派人 後續任務由流程推進自然通知
誰可啟動 manager 角色 啟動執行是 PM 的決定
新建專案命名 startcreate 消除與「啟動執行」的歧義
冪等啟動 重複呼叫回傳成功但不重發通知 避免併發或重複操作產生多次通知

§3

三、AP Status 完整生命週期(更新)

preparing(新增)→ active → auditing → remediation → closed
                              ↑           │
                              └───────────┘
狀態 說明 可執行操作
preparing PM 設置專案(SSP、任務指派、Job 建立) 編輯 SSP、建 Job、指派人員。My Jobs 不顯示,complete_job 不可執行
active 內部執行 + 內部審核 執行任務、上傳 evidence、reviewer sign-off、launch-audit
auditing 外部稽核進行中 填 verdict/findings、confirm-audit
remediation 改善計劃執行中 POA&M 管理、close-round
closed 本輪結案 唯讀

§4

四、API 變更

4.1 重新命名:新建專案

項目 Before After
URL POST /api/1.0/oscal-project/start POST /api/1.0/grc/projects/create
Route Class OscalProjectStartRoute ProjectCreateResource
Service method start_oscal_project() create_project()
Serializer OscalProjectStartRequest(api/project/serializers/) ProjectCreateRequestSchema(api/grc/serializers/project.py)
AP 初始 status active preparing

舊 URL(/api/1.0/oscal-project/start)保留不刪,避免舊前端立即壞掉。舊 route 內部呼叫相同 service method。

Request / Response 格式不變。

4.2 新增:啟動專案執行

POST /api/1.0/grc/project/<project_uid>/ap/<ap_uid>/activate

權限: manager 角色(複用現有 _check_manager_role 或新增 manager 角色檢查,403 使用既有 GRC_NOT_MANAGER 或通用 forbidden error)

Request:

{
  "force": false
}

前置條件:

  • AP status = preparing,否則 412
  • 如果 AP 已經是 active(重複呼叫),回傳成功但不重發通知(冪等)

檢查邏輯(force=false 時):

  1. 掃描所有 AP tasks
  2. 對每個 task,檢查是否有對應的 job(透過 assessment_plan_task_workflow_execution_mappingjob_executions
  3. 如果有 task 沒有任何 USER type job → 視為「未設置」
  4. 有未設置 task 且 force=false → 回傳 warning,不啟動

Response(warning 模式,force=false 且有未設置 task): HTTP 200

{
  "code": 1,
  "data": {
    "ap_uid": "xxx",
    "status": "preparing",
    "warning": true,
    "can_activate": true,
    "unassigned_tasks": 3,
    "total_tasks": 15
  }
}

Response(成功啟動): HTTP 200

{
  "code": 1,
  "data": {
    "ap_uid": "xxx",
    "status": "active",
    "notified_count": 5
  }
}

啟動動作:

  1. AP status → active(使用 SELECT ... FOR UPDATE 鎖定 AP row,防止併發問題)
  2. 發送通知(見第五節)
  3. 回傳結果

§5

五、通知邏輯

觸發時機

activate API 成功將 AP status 從 preparing 改為 active 後。

通知對象

只通知 BPMN 流程中第一個任務的指派人員:

  1. 查所有 AP tasks 對應的 workflow_executions(透過 assessment_plan_task_workflow_execution_mapping
  2. 對每個 workflow,找 BPMN 中第一個 type=USERstatus=TODOjob_execution
  3. 透過 task_assignees 找到該 job 的指派人員(user email)
  4. 去重(同一人被指派多個第一任務只通知一次)
  5. NotificationService.send_mail_notification 發送

Email 內容

  • 主旨:[專案名稱] 專案已啟動
  • 內容:專案名稱、被指派的任務數量

通知失敗處理

通知發送失敗不影響 activate 結果(AP 狀態已變更為 active)。記錄 error log,不回滾。

後續任務通知

不在本設計範圍。後續任務由 complete_job 流程推進時,現有 workflow engine 自然通知下一個任務的人員。


§6

六、My Jobs 過濾

變更

My Jobs 資料來源為 SQL View public.vw_user_job_queue(定義於 scripts/sql/view/vw_user_job_queue.sql),目前透過 assessment_plan_tasksassessment_plan_groups 間接關聯 AP,但未直接 JOIN assessment_plans

實作方式

修改 vw_user_job_queue DDL,新增 JOIN oscal.assessment_plans,並在 WHERE 加過濾:

-- 在 FROM 區塊加 JOIN
LEFT JOIN oscal.assessment_plans ap ON g.assessment_plan_id = ap.id

-- 在 WHERE 區塊加過濾
AND (ap.status IS NULL OR ap.status != 'preparing')

使用 IS NULL OR 防護 LEFT JOIN 未匹配的情況。

Migration SQL

需要新增 migration 腳本 scripts/sql/stage5_migration.sql,包含:

  1. 重新建立 vw_user_job_queue view
  2. 重新 GRANT 權限

§7

七、Error Code

新增至 common/code/grc_error_code.py

Code 中文 Error Code
GRC_AP_NOT_PREPARING AP 不在設置階段,無法啟動 GRC_412009

權限檢查(manager 角色)複用現有的 403 error code 模式。


§8

八、跨套件依賴:jedi-oscal

需更版:AssessmentPlanStatus enum

jedi_oscal/common/enum/status_enum.py 需新增 PREPARING

class AssessmentPlanStatus(StrEnum):
    PREPARING = "preparing"   # 新增
    DRAFT = "draft"
    ACTIVE = "active"
    AUDITING = "auditing"
    REMEDIATION = "remediation"
    COMPLETED = "completed"
    ARCHIVED = "archived"
    CLOSED = "closed"

AP entity default 維持 ACTIVE(jedi-oscal 不改 default),由主專案 create_project 傳入 status="preparing" 覆蓋。

jedi-oscal 版本更新

修改 enum 後需重新發版 jedi-oscal 並在主專案 pyproject.toml 更新版本號。


§9

九、DDD 分層架構

修改檔案

檔案 變更
app/project/service/oscal_project_service.py method start_oscal_projectcreate_project;AP 初始 status 改 preparing;新增 activate_project method
api/grc/routes/project_route.py 新增 ProjectCreateResource(從 api/project/ 搬移邏輯);新增 ActivateProjectResource
api/grc/serializers/project.py 新增 ProjectCreateRequestSchema(對應舊 OscalProjectStartRequest);新增 ActivateProjectRequestSchema / ActivateProjectResponseSchema
api/grc/__init__.py 註冊 URL:api.add_resource(ProjectCreateResource, "/projects/create")api.add_resource(ActivateProjectResource, "/project/<project_uid>/ap/<ap_uid>/activate")
common/code/grc_error_code.py +1 error code
scripts/sql/view/vw_user_job_queue.sql 加 JOIN assessment_plans + WHERE 過濾 preparing
scripts/sql/stage5_migration.sql 新增 migration 腳本(重建 view)
di_containers/project/project_containers.py activate_project 需要 NotificationService,確認 DI wiring

不動的部分

項目 說明
api/project/ 模組 保留不刪,舊 URL 繼續可用
OscalProjectService class 名稱 不改,只改 method 名稱
現有 AP 資料 已建立的 AP(status=active)不受影響
api/project/ 其他 route list / detail / update / delete 不搬

§10

十、與其他 Stage 的關係

launch-audit 前置條件

Stage 2 的 launch_audit 檢查 AP status = active,邏輯不變。因為新建專案 AP 初始為 preparing,PM 必須先 activate 才能 launch_audit,自然形成正確的流程順序。

complete_job 保護

preparing 狀態下 My Jobs 不顯示(view 過濾),使用者無法看到或執行任務。即使直接呼叫 API,workflow engine 的 job_execution 仍為 TODO 狀態,不會影響 AP 狀態。

對 Stage 1-4 無破壞性

所有 Stage 1-4 的 API 都要求 AP status 為 active / auditing / remediationpreparing 狀態下這些 API 都會被現有的 status 檢查擋住。