# FR-048 Phase 2a — 授權守門盤點：grc-group（api/grc + api/flow_engine）

> 純事實盤點，NO 判斷軸。branch: `fix/v1.8.0-bugs`。
> 範圍：每個 WRITE method（post/put/delete/patch）+ 敏感 GET（export/download/report）。
> 只列「actively registered」（未被註解）的 route class；被 `# api.add_resource(...)` 註解掉的 DEAD-V1 route 不列。
> URL prefix：`api/grc/__init__.py` blueprint prefix = `/api/1.0/grc`；`api/flow_engine/__init__.py` blueprint prefix = `/api/1.0`。
>
> 「現況守門」判讀基準：route decorator + 追進 app-service method body 找 `ForbiddenError` / role / participant / is_admin 守門。
> 停用守門特別標注：`common/authz/workflow.py:assert_workflow_project_participant()` 函式第 51 行 body 開頭即 `return`（FR-038 v2 暫時停用），實際不守。`app/flow_engine/service/workflow_execution_service.py:133 assert_project_participant()` 是它的 wrapper。

---

## api/grc

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/grc/projects/list` | POST | `api/grc/routes/project_route.py:93` | 是 | `ProjectService.list_projects()` (`app/grc/service/project_service.py:114`) | 僅 JWT | POST 實為分頁查詢(讀)；service 硬帶 `is_admin=False` 只回使用者參與的專案（可見性過濾，非角色守門） |
| `/api/1.0/grc/projects/batch-delete` | POST | `api/grc/routes/project_route.py:245` | 是 | `ProjectService.batch_delete_projects()` (`project_service.py:653`) | 已有: is_admin 或 owner/manager 參與者 @ `project_service.py:679-685`（否則 `ForbiddenError(ErrorCode.PROJECT_NO_PERMISSION)` @:644 for single；batch 同 pattern） | POST 實為批次刪除(寫) |
| `/api/1.0/grc/project/<uid>` | PUT | `api/grc/routes/project_route.py:168` | 是 | `ProjectService.update_project()` (`project_service.py:336`) | 僅 JWT | 僅 `_domain_service.get_project(uid,user_id,is_admin)` 做可見性(非成員回 None→route 回 200 false)；無角色守門。service 內註解明示「授權由 update_project 自身把關（另列 follow-up）」@:454 — 即尚未實作角色 gate |
| `/api/1.0/grc/project/<uid>` | DELETE | `api/grc/routes/project_route.py:221` | 是 | `ProjectService.delete_project()` (`project_service.py:609`) | 已有: is_admin 或 owner/manager 參與者 @ `project_service.py:632-644`（否則 `ForbiddenError(ErrorCode.PROJECT_NO_PERMISSION)`） | — |
| `/api/1.0/grc/project/<project_uid>/ap/<ap_uid>` | PUT | `api/grc/routes/assessment_plan_route.py:55` | 是 | `ProjectService.update_assessment_plan()` (`project_service.py:550`) | 僅 JWT | service body 為 FR-038 2A dark stub（`return None, False`）— 無守門且不做事 |
| `/api/1.0/grc/project/<project_uid>/ap/<ap_uid>` | DELETE | `api/grc/routes/assessment_plan_route.py:93` | 是 | `ProjectService.delete_assessment_plan()` (`project_service.py:567`) | 已有: manager 參與者 @ `project_service.py:572-573`（否則 `ForbiddenError(GRC_NOT_MANAGER)`） | — |
| `/api/1.0/grc/project/<project_uid>/ap/<ap_uid>/control-group/<group_uid>/control/<control_uid>/assessment-object/<ao_uid>/jobs/list` | POST | `api/grc/routes/job_route.py:50` | 是 | `JobService.list_jobs()` (`app/grc/service/job_service.py:105`) | 僅 JWT | POST 實為分頁查詢(讀) |
| `/api/1.0/grc/project/<project_uid>/job/<job_uid>` | PUT | `api/grc/routes/job_route.py:100` | 是 | `JobService.update_job()` (`job_service.py:126`) | 僅 JWT | route 傳 `user_id`/`is_admin` 進 service，但 service body 從未用來守門（無 ForbiddenError） |
| `/api/1.0/grc/project/<project_uid>/job/<job_uid>` | DELETE | `api/grc/routes/job_route.py:142` | 是 | `JobService.delete_job()` (`job_service.py:92`) | 僅 JWT | 無 user_id 參數，無守門 |
| `/api/1.0/grc/project/<project_uid>/assessment-object/<ao_uid>/jobs` | POST | `api/grc/routes/job_route.py:177` | 是 | `JobService.create_job()` (`job_service.py:44`) | 僅 JWT | 無 user_id 參數，無守門 |
| `/api/1.0/grc/jobs/my/list` | POST | `api/grc/routes/job_route.py:224` | 是 | `TaskAssigneeService.list_my_grc_jobs()` | 僅 JWT | POST 實為分頁查詢(讀)；query 以 `user_id=user.id` 自我 scope |
| `/api/1.0/grc/job-executions/<job_execution_uid>/comments` | POST | `api/grc/routes/job_comment_route.py:40` | 是 | `JobCommentService.add_comment()` (`app/grc/service/job_comment_service.py:22`) | 僅 JWT | 無角色/參與者守門 |
| `/api/1.0/grc/job/comment/<comment_uid>` | PUT | `api/grc/routes/job_comment_route.py:68` | 是 | `JobCommentService.update_comment()` (`job_comment_service.py:38`) | 已有: 作者本人 @ `job_comment_service.py:48-49`（`comment.author_id != user_id` → `ForbiddenError(GRC_JOB_COMMENT_FORBIDDEN)`） | 僅 owner-of-record，非專案角色 |
| `/api/1.0/grc/job/comment/<comment_uid>` | DELETE | `api/grc/routes/job_comment_route.py:89` | 是 | `JobCommentService.delete_comment()` (`job_comment_service.py:54`) | 已有: 作者本人 @ `job_comment_service.py:59-60`（同上） | 僅 owner-of-record |
| `/api/1.0/grc/project/<project_uid>/ap/<ap_uid>/jobs/export` | GET | `api/grc/routes/job_import_route.py:35` | 是 | `JobImportService.export_excel()` (`app/grc/service/job_import_service.py:93`) | 僅 JWT | 敏感 GET（匯出 Excel，`send_file` 附件）；無角色守門 |
| `/api/1.0/grc/project/<project_uid>/ap/<ap_uid>/jobs/import` | POST | `api/grc/routes/job_import_route.py:59` | 是 | `JobImportService.validate_import()` (`job_import_service.py:180`) | 僅 JWT | 上傳+驗證，無守門（未寫入） |
| `/api/1.0/grc/project/<project_uid>/ap/<ap_uid>/jobs/import/validate` | POST | `api/grc/routes/job_import_route.py:83` | 是 | `JobImportService.revalidate_items()` (`job_import_service.py:293`) | 僅 JWT | 重新驗證(讀)，無守門 |
| `/api/1.0/grc/project/<project_uid>/ap/<ap_uid>/jobs/import/confirm` | POST | `api/grc/routes/job_import_route.py:105` | 是 | `JobImportService.confirm_import()` (`job_import_service.py:374`) | 已有: manager 參與者 @ `job_import_service.py:379` → `_check_manager_role()` (`:81-90`, 否則 `ForbiddenError(GRC_NOT_MANAGER)`) | 實際寫入路徑，有守門 |
| `/api/1.0/grc/project/<project_uid>/jobs/batch-complete` | POST | `api/grc/routes/job_batch_complete_route.py:27` | 是 | `JobBatchCompleteService.batch_complete()` (`app/grc/service/job_batch_complete_service.py:30`) | 僅 JWT | user_id 僅作 actor 歸屬（留言/通知）；無角色/參與者守門 |
| `/api/1.0/grc/project/<project_uid>/ap/<ap_uid>/control/<control_uid>/review` | POST | `api/grc/routes/review_route.py:18` | 是 | `ReviewService.mark_control()` (`app/grc/service/review_service.py:42`) | 僅 JWT | 無角色/參與者守門 |
| `/api/1.0/grc/project/<project_uid>/ap/<ap_uid>/control/<control_uid>/review` | DELETE | `api/grc/routes/review_route.py:32` | 是 | `ReviewService.unmark_control()` (`review_service.py:54`) | 僅 JWT | 無守門 |
| `/api/1.0/grc/project/<project_uid>/ap/<ap_uid>/control/<control_uid>/ao/<ao_uid>/review` | POST | `api/grc/routes/review_route.py:50` | 是 | `ReviewService.mark_ao()` (`review_service.py:66`) | 僅 JWT | 無守門 |
| `/api/1.0/grc/project/<project_uid>/ap/<ap_uid>/control/<control_uid>/ao/<ao_uid>/review` | DELETE | `api/grc/routes/review_route.py:65` | 是 | `ReviewService.unmark_ao()` (`review_service.py:78`) | 僅 JWT | 無守門 |
| `/api/1.0/grc/project/<project_uid>/start-task-execution` | POST | `api/grc/routes/task_execution_route.py:35` | 是 | `TaskExecutionService.start_task_execution()` (`app/grc/service/task_execution_service.py:60`) | 已有: manager 參與者 @ `task_execution_service.py:61` → `_check_manager()` (`:46-56`, 否則 `ForbiddenError(GRC_NOT_MANAGER)`) | — |
| `/api/1.0/grc/audits/my/list` | POST | `api/grc/routes/auditor_dashboard_route.py:27` | 是 | `AuditorDashboardService.get_my_audit_list()` (`app/grc/service/auditor_dashboard_service.py:12`) | 僅 JWT | POST 實為清單查詢(讀)；以 `user_id=user.id` 自我 scope |

### grc — 未列入的 WRITE / 敏感 GET（DEAD-V1，route 已被註解不註冊，不可達）

- `ArVerdictResource.put` / `ArFindingCreateResource.post` / `ArFindingDetailResource.put`/`delete`（`api/grc/routes/audit_route.py`）— `api/grc/__init__.py:137-160` 整組註解。
- `PoamListResource.post` / `PoamDetailResource.put`（`api/grc/routes/poam_route.py`）— `__init__.py:163-170` 註解。
- `ProjectControlListResource.post` / `ProjectAOListResource.post` / control-group 系列 — `__init__.py:90-113` 註解。
- （AR/POA&M/audit-round 的 live 端點在 `api/project`，非本盤點模組。）

---

## api/flow_engine

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/flow-engine/process/comments/<id>` | PUT | `api/flow_engine/routes/flow_engine_route.py:182` | 是 | `ElementVariableService.update_element_variable()`（jedi_flow_engine 套件，route 直接呼叫；`WorkflowExecutionCommentRoute`） | 僅 JWT | 新增流程留言(寫)；route 直接操作 element_variable，無專案守門 |
| `/api/1.0/flow-engine/task/complete/<id>` | POST | `api/flow_engine/routes/flow_engine_route.py:317` | 是 | `WorkflowExecutionService.complete_job()` (`app/flow_engine/service/workflow_execution_service.py:640`) | 已有(停用): assert_project_participant → `assert_workflow_project_participant` @ `workflow_execution_service.py:649` / `common/authz/workflow.py:51` — 函式開頭 return，實際不守 | 停用期間僅靠 RLS 租戶隔離 |
| `/api/1.0/flow-engine/task/revert/<id>` | POST | `api/flow_engine/routes/flow_engine_route.py:342` | 是 | `WorkflowExecutionService.revert_job()` (`workflow_execution_service.py:813`) | 已有(停用): assert_project_participant @ `workflow_execution_service.py:822` / `common/authz/workflow.py:51` — 函式開頭 return，實際不守；另有 `_assert_round_not_frozen_for_workflow` @:825（狀態前置條件守門，非角色/參與者） | 停用期間僅靠 RLS |
| `/api/1.0/flow-engine/process-definition/<uid>` | GET | `api/flow_engine/routes/flow_engine_route.py:146` | 是 | `WorkflowTemplateService.get_workflow_template_by_uid()`（套件） | 僅 JWT | 讀取（非 export/download/report），非敏感 GET；列此供上下文，`WorkflowExecutionRoute` 僅 GET |
| `/api/1.0/job-execution/task/link` | PUT | `api/flow_engine/routes/flow_engine_route.py:413` | 是 | `WorkflowExecutionService.update_assessment_plan_task_job_link()` (`workflow_execution_service.py:1582`) | 僅 JWT | 更新任務連結(寫)；無角色/參與者守門 |
| `/api/1.0/job-evidences` | POST | `api/flow_engine/routes/job_evidence_route.py:36` | 是 | `JobEvidenceService.add_job_evidence()` (`app/flow_engine/service/job_evidence_service.py:73`) | 僅 JWT | 批次新增證明(寫)；`add_job_evidence` body 無 participant 守門（僅 NotFound 檢查） |
| `/api/1.0/job-evidence` / `/api/1.0/job-evidence/<uid>` | POST | `api/flow_engine/routes/job_evidence_route.py:77` | 是 | `JobEvidenceService.add_job_evidence()` (`job_evidence_service.py:73`) | 僅 JWT | 單筆新增證明(寫)；同上，無 participant 守門 |
| `/api/1.0/job-evidence/<uid>` | PUT | `api/flow_engine/routes/job_evidence_route.py:100` | 是 | `JobEvidenceService.update_job_evidence()` (`job_evidence_service.py:134`) | 已有(停用): `_workflow_execution_service.assert_project_participant()` @ `job_evidence_service.py:141` → `common/authz/workflow.py:51` — 函式開頭 return，實際不守 | 停用期間僅靠 RLS |
| `/api/1.0/job-evidence/<uid>` | DELETE | `api/flow_engine/routes/job_evidence_route.py:119` | 是 | `JobEvidenceService.delete_job_evidence()` (`job_evidence_service.py:151`) | 已有(停用): `_workflow_execution_service.assert_project_participant()` @ `job_evidence_service.py:164` → `common/authz/workflow.py:51` — 函式開頭 return，實際不守 | 停用期間僅靠 RLS |
| `/api/1.0/flow-engine/flow-templates` | POST | `api/flow_engine/routes/flow_template_route.py:36` | 是 (`method_decorators`) | `FlowTemplateAppService.create()` (`app/flow_engine/service/flow_template_app_service.py:67`) | 僅 JWT | 建立範本(寫)；無角色守門 |
| `/api/1.0/flow-engine/flow-templates/validate` | POST | `api/flow_engine/routes/flow_template_route.py:121` | 是 | `FlowTemplateAppService.validate()` (`flow_template_app_service.py:138`) | 僅 JWT | POST 實為純驗證(讀，不寫入) |
| `/api/1.0/flow-engine/flow-templates/<uid>` | PUT | `api/flow_engine/routes/flow_template_route.py:56` | 是 | `FlowTemplateAppService.update()` (`flow_template_app_service.py:85`) | 僅 JWT（授權層）；另有資源守門 `_guard_not_builtin` @:88 / `:182-184`（builtin 唯讀，非使用者角色/參與者） | 更新範本(寫)；無角色/參與者守門 |
| `/api/1.0/flow-engine/flow-templates/<uid>` | DELETE | `api/flow_engine/routes/flow_template_route.py:65` | 是 | `FlowTemplateAppService.soft_delete()` (`flow_template_app_service.py:131`) | 僅 JWT；資源守門 `_guard_not_builtin` @:133（builtin 唯讀） | 軟刪範本(寫) |
| `/api/1.0/flow-engine/flow-templates/<uid>/duplicate` | POST | `api/flow_engine/routes/flow_template_route.py:76` | 是 | `FlowTemplateAppService.duplicate()` (`flow_template_app_service.py:154`) | 僅 JWT | 複製範本(寫)；無角色守門 |
| `/api/1.0/flow-engine/flow-templates/<uid>/publish` | PUT | `api/flow_engine/routes/flow_template_route.py:93` | 是 | `FlowTemplateAppService.publish()` (`flow_template_app_service.py:105`) | 僅 JWT；資源守門 `_guard_not_builtin` @:107 | 發布範本(寫) |
| `/api/1.0/flow-engine/flow-templates/<uid>/unpublish` | PUT | `api/flow_engine/routes/flow_template_route.py:105` | 是 | `FlowTemplateAppService.unpublish()` (`flow_template_app_service.py:119`) | 僅 JWT；資源守門 `_guard_not_builtin` @:121 | 退回草稿(寫) |
| `/api/1.0/project/<project_uid>/audit-round/<round_uid>/stage/advance` | POST | `api/flow_engine/routes/stage_advance_route.py:48` | 是 (`method_decorators`) | `StageAdvanceService.advance_stage()` (`app/flow_engine/service/stage_advance_service.py`) | 已有: role gate @ `stage_advance_service.py:286-293`（`user_role not in main_roles and != MANAGER` → `ForbiddenError(GRC_STAGE_ROLE_FORBIDDEN)`）；force 需 MANAGER @:297-304（否則 `GRC_NOT_MANAGER`） | 推進階段(寫)；有實際角色守門 |

### flow_engine — 未列入的 WRITE（DEAD-V1，route 已被註解不註冊，不可達）

- `MainWorkflowExecutionsRoute.post`（分頁查詢）/ `MainWorkflowExecutionRoute.put`/`delete`（`flow_engine_route.py`）— `api/flow_engine/__init__.py:39-40` 註解。
- `JobCommentsRoute.put`（task/comments）— `__init__.py:43` 註解（process/comments 才 live）。
- `ElementVariableRoute.put`（element-variable）— `__init__.py:47` 註解。
- `WorkflowExecutionsRoute.get`（process-definitions 複數）— `__init__.py:50` 註解。
- `WorkflowExecutionControlMappingsRoute.get` — `__init__.py:55` 註解（且 service `__init__` 缺參數會 500）。

### flow_engine — 純讀取 GET（非 export/download/report，非敏感，未列主表）

- `WorkflowExecutionCommentRoute.get`（process/comments 讀）、`JobEvidencesRoute.get`、`JobEvidenceRoute.get`、`UserJobQueueRoute.get`、`StageObjectsRoute.get`、`FlowTemplatesRoute.get`、`FlowTemplateRoute.get`、`StageInfoResource.get` — 皆 `@jwt_required`，僅 JWT，無敏感匯出語意。
