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

> 日期：2026-03-25
> 狀態：Draft

---

## 一、目標

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

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

---

## 二、設計決策摘要

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

---

## 三、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` | 本輪結案 | 唯讀 |

---

## 四、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：**
```json
{
  "force": false
}
```

**前置條件：**
- AP status = `preparing`，否則 412
- 如果 AP 已經是 `active`（重複呼叫），回傳成功但不重發通知（冪等）

**檢查邏輯（force=false 時）：**
1. 掃描所有 AP tasks
2. 對每個 task，檢查是否有對應的 job（透過 `assessment_plan_task_workflow_execution_mapping` → `job_executions`）
3. 如果有 task 沒有任何 USER type job → 視為「未設置」
4. 有未設置 task 且 force=false → 回傳 warning，不啟動

**Response（warning 模式，force=false 且有未設置 task）：**
HTTP 200
```json
{
  "code": 1,
  "data": {
    "ap_uid": "xxx",
    "status": "preparing",
    "warning": true,
    "can_activate": true,
    "unassigned_tasks": 3,
    "total_tasks": 15
  }
}
```

**Response（成功啟動）：**
HTTP 200
```json
{
  "code": 1,
  "data": {
    "ap_uid": "xxx",
    "status": "active",
    "notified_count": 5
  }
}
```

**啟動動作：**
1. AP status → `active`（使用 `SELECT ... FOR UPDATE` 鎖定 AP row，防止併發問題）
2. 發送通知（見第五節）
3. 回傳結果

---

## 五、通知邏輯

### 觸發時機

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

### 通知對象

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

1. 查所有 AP tasks 對應的 `workflow_executions`（透過 `assessment_plan_task_workflow_execution_mapping`）
2. 對每個 workflow，找 BPMN 中第一個 `type=USER` 且 `status=TODO` 的 `job_execution`
3. 透過 `task_assignees` 找到該 job 的指派人員（user email）
4. 去重（同一人被指派多個第一任務只通知一次）
5. 用 `NotificationService.send_mail_notification` 發送

### Email 內容

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

### 通知失敗處理

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

### 後續任務通知

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

---

## 六、My Jobs 過濾

### 變更

My Jobs 資料來源為 SQL View `public.vw_user_job_queue`（定義於 `scripts/sql/view/vw_user_job_queue.sql`），目前透過 `assessment_plan_tasks` → `assessment_plan_groups` 間接關聯 AP，但**未直接 JOIN `assessment_plans` 表**。

### 實作方式

修改 `vw_user_job_queue` DDL，新增 JOIN `oscal.assessment_plans`，並在 WHERE 加過濾：

```sql
-- 在 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 權限

---

## 七、Error Code

新增至 `common/code/grc_error_code.py`：

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

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

---

## 八、跨套件依賴：jedi-oscal

### 需更版：`AssessmentPlanStatus` enum

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

```python
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` 更新版本號。

---

## 九、DDD 分層架構

### 修改檔案

| 檔案 | 變更 |
|------|------|
| `app/project/service/oscal_project_service.py` | method `start_oscal_project` → `create_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 不搬 |

---

## 十、與其他 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` / `remediation`，`preparing` 狀態下這些 API 都會被現有的 status 檢查擋住。
