# FR-048 Phase 2a — Authorization-Guard Inventory：project-group

> 純事實盤點（無判定軸）。涵蓋模組：`api/project`、`api/project_summary_report`、`api/subtask_status_history`。
> Blueprint url_prefix 皆為 `/api/1.0`。所有 route 均掛 `@jwt_required()`。
> 守門追蹤到 app service 層（含 helper `_check_role` / `_check_auditor` / `_check_manager` / `resolve_*_check_*`）。
>
> 註：`api/project/routes/job_execution_device_route.py`（JobExecutionDevices/Device/Bulk/ViewQuery）在 `api/project/__init__.py:91-94` 全數註解停用（FR-038 DEAD-V1，無 live FE 呼叫），**未註冊、無 live URL**，不列入下表。

## api/project

Route files：`project_route.py`、`audit_round_route.py`、`ap_docx_import_route.py`、`ar_import_route.py`。

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/oscal-project/start` | POST | `project_route.py:25` | 是 | `ProjectStartAppService.start_project` | 僅 JWT | 專案成立；任何登入者可建，建立者 fallback 補 role=manager（`project_start_app_service.py:199`）。無 owner/participant 前置守門 |
| `/api/1.0/projects/<project_uid>/audit-rounds` | POST | `audit_round_route.py:50` | 是 | `AuditRoundAppService.create_round` | 已有: `_check_role(...,"manager")` @ `audit_round_app_service.py:214`（helper 定義 `:99`） | 建輪次 |
| `/api/1.0/projects/<project_uid>/audit-rounds/list` | POST | `audit_round_route.py:65` | 是 | `AuditRoundAppService.list_rounds` | 僅 JWT | POST 實為列表查詢(讀)；`list_rounds` @ `:195` 無角色守門 |
| `/api/1.0/audit-round/<round_uid>` | GET | `audit_round_route.py:75` | 是 | `AuditRoundAppService.get_round` | 僅 JWT | 讀取 detail；`get_round` @ `:202` 無角色守門 |
| `/api/1.0/audit-round/<round_uid>/launch-audit` | POST | `audit_round_route.py:84` | 是 | `AuditRoundAppService.launch_audit` | 已有: `_check_role(...,"manager")` @ `audit_round_app_service.py:358` | 狀態轉換 |
| `/api/1.0/audit-round/<round_uid>/start-auditing` | POST | `audit_round_route.py:96` | 是 | `AuditRoundAppService.start_auditing` | 已有: `_check_role(...,"auditor")` @ `audit_round_app_service.py:400` | 狀態轉換；manager override |
| `/api/1.0/audit-round/<round_uid>/close-round` | POST | `audit_round_route.py:108` | 是 | `AuditRoundAppService.close_round` | 已有: `_check_role(...,"manager")` @ `audit_round_app_service.py:489` | 狀態轉換 |
| `/api/1.0/audit-round/<round_uid>/launch-reverify` | POST | `audit_round_route.py:120` | 是 | `AuditRoundAppService.launch_reverify` | 已有: `_check_role(parent.project_id,...,"auditor")` @ `audit_round_app_service.py:534` | 狀態轉換；manager override |
| `/api/1.0/audit-round/<round_uid>/ap` | GET | `audit_round_route.py:134` | 是 | `AssessmentPlanAppService.get_ap_for_round` | 僅 JWT | 讀取；`get_ap_for_round` @ `:441` 無角色守門 |
| `/api/1.0/audit-round/<round_uid>/ap/generate-draft` | POST | `audit_round_route.py:144` | 是 | `AssessmentPlanAppService.generate_draft_for_round` | 已有: `_check_auditor(...)` @ `assessment_plan_app_service.py:602`（helper `:230`） | 重生 AP 草稿 |
| `/api/1.0/ap/<ap_uid>/reviewed-controls` | PUT | `audit_round_route.py:157` | 是 | `AssessmentPlanAppService.set_reviewed_controls` | 已有: `resolve_ap_and_check_auditor(...)` @ `assessment_plan_app_service.py:468`（`:190`→`_check_auditor :207`） | |
| `/api/1.0/ap/<ap_uid>/assessment-subjects` | PUT | `audit_round_route.py:171` | 是 | `AssessmentPlanAppService.set_assessment_subjects` | 已有: `resolve_ap_and_check_auditor(...)` @ `assessment_plan_app_service.py:480` | |
| `/api/1.0/ap/<ap_uid>/tasks` | PUT | `audit_round_route.py:185` | 是 | `AssessmentPlanAppService.set_tasks` | 已有: `resolve_ap_and_check_auditor(...)` @ `assessment_plan_app_service.py:511` | |
| `/api/1.0/ap/<ap_uid>/parties` | GET | `audit_round_route.py:200` | 是 | `AssessmentPlanAppService.list_ap_parties` | 僅 JWT | 讀取；`list_ap_parties` @ `:634` 無角色守門 |
| `/api/1.0/ap/<ap_uid>/parties` | POST | `audit_round_route.py:208` | 是 | `AssessmentPlanAppService.add_ap_party` | 已有: `resolve_ap_and_check_auditor(...)` @ `assessment_plan_app_service.py:648` | 就地新增稽核人員（寫入） |
| `/api/1.0/ap/<ap_uid>/docx-imports/parse` | POST | `ap_docx_import_route.py:40` | 是 | `ApDocxImportAppService.upload_and_parse` | 已有: `resolve_ap_and_check_auditor(...)` @ `ap_docx_import_app_service.py:72` | docx 上傳+解析 |
| `/api/1.0/ap-docx-import/<parse_uid>` | GET | `ap_docx_import_route.py:63` | 是 | `ApDocxImportAppService.get_parse_result` | 已有: `resolve_ap_and_check_participant(...)` @ `ap_docx_import_app_service.py:147`（`:210`→`GRC_NOT_PROJECT_PARTICIPANT :227`） | 讀取 parse 預覽（participant gate，較寬） |
| `/api/1.0/ap-docx-import/<parse_uid>` | DELETE | `ap_docx_import_route.py:78` | 是 | `ApDocxImportAppService.discard_parse` | 已有: `resolve_ap_and_check_participant(...)` @ `ap_docx_import_app_service.py:175` | soft delete（participant gate）；另 `:256` tenant+is_admin 隔離 |
| `/api/1.0/ap-docx-import/<parse_uid>/confirm` | POST | `ap_docx_import_route.py:98` | 是 | `ApDocxImportAppService.confirm_import` | 已有: `resolve_ap_and_check_auditor(...)` @ `ap_docx_import_app_service.py:189` | 確認匯入（寫入，auditor gate） |
| `/api/1.0/audit-round/<round_uid>/ar-imports/parse` | POST | `ar_import_route.py:40` | 是 | `ArImportAppService.upload_and_parse` | 已有: `resolve_round_for_import(...)` @ `ar_import_app_service.py:74`（`AssessmentResultAppService.resolve_round_for_import`→`_check_auditor :147`） | xlsx 上傳+解析 |
| `/api/1.0/ar-import/<parse_uid>` | GET | `ar_import_route.py:63` | 是 | `ArImportAppService.get_parse_result` | 已有: `resolve_round_for_import(...)` @ `ar_import_app_service.py:185` | 讀取 parse 預覽（auditor gate）；另 `:570` tenant+is_admin 隔離 |
| `/api/1.0/ar-import/<parse_uid>` | DELETE | `ar_import_route.py:78` | 是 | `ArImportAppService.discard_parse` | 已有: `resolve_round_for_import(...)` @ `ar_import_app_service.py:216` | soft delete（auditor gate） |
| `/api/1.0/ar-import/<parse_uid>/confirm` | POST | `ar_import_route.py:98` | 是 | `ArImportAppService.confirm_import` | 已有: `resolve_round_for_import(...)` @ `ar_import_app_service.py:235` | 確認匯入（寫入，auditor gate） |
| `/api/1.0/audit-round/<round_uid>/ar/findings` | GET | `audit_round_route.py:222` | 是 | `AssessmentResultAppService.get_findings` | 僅 JWT | 讀取；`get_findings` @ `:385` 無角色守門 |
| `/api/1.0/audit-round/<round_uid>/ar/finding/<finding_uid>` | PUT | `audit_round_route.py:232` | 是 | `AssessmentResultAppService.judge_finding` | 已有: `_check_auditor(...)` @ `assessment_result_app_service.py:521`（helper `:111`） | AO 判定 |
| `/api/1.0/audit-round/<round_uid>/ar/observations` | POST | `audit_round_route.py:248` | 是 | `AssessmentResultAppService.create_observation` | 已有: `_check_auditor(...)` @ `assessment_result_app_service.py:556` | |
| `/api/1.0/audit-round/<round_uid>/ar/observation/<obs_uid>` | PUT | `audit_round_route.py:265` | 是 | `AssessmentResultAppService.update_observation` | 已有: `_check_auditor(...)` @ `assessment_result_app_service.py:583` | |
| `/api/1.0/audit-round/<round_uid>/ar/observation/<obs_uid>` | DELETE | `audit_round_route.py:279` | 是 | `AssessmentResultAppService.delete_observation` | 已有: `_check_auditor(...)` @ `assessment_result_app_service.py:605` | |
| `/api/1.0/audit-round/<round_uid>/ar/risks` | GET | `audit_round_route.py:292` | 是 | `AssessmentResultAppService.list_risks` | 僅 JWT | 讀取；`list_risks` @ `:688` 無角色守門 |
| `/api/1.0/audit-round/<round_uid>/ar/risks` | POST | `audit_round_route.py:300` | 是 | `AssessmentResultAppService.add_risk` | 已有: `_check_auditor(...)` @ `assessment_result_app_service.py:678` | 建風險（寫入） |
| `/api/1.0/audit-round/<round_uid>/ar/finalize` | POST | `audit_round_route.py:316` | 是 | `AuditRoundAppService.finalize_audit` | 已有: `_check_role(...,"auditor")` @ `audit_round_app_service.py:431` | 狀態轉換；manager override |
| `/api/1.0/audit-round/<round_uid>/ar/risk/<risk_uid>` | PUT | `audit_round_route.py:329` | 是 | `AssessmentResultAppService.update_risk` | 已有: `_check_auditor(...)` @ `assessment_result_app_service.py:738` | |
| `/api/1.0/audit-round/<round_uid>/ar/risk/<risk_uid>` | DELETE | `audit_round_route.py:343` | 是 | `AssessmentResultAppService.delete_risk` | 已有: `_check_auditor(...)` @ `assessment_result_app_service.py:752` | |
| `/api/1.0/audit-round/<round_uid>/ar/risk/<risk_uid>/findings` | PUT | `audit_round_route.py:357` | 是 | `AssessmentResultAppService.link_risk_findings` | 已有: `_check_auditor(...)` @ `assessment_result_app_service.py:767` | 關聯 finding↔risk |
| `/api/1.0/audit-round/<round_uid>/poam-items` | GET | `audit_round_route.py:372` | 是 | `PoamAppService.list_items` | 僅 JWT | 讀取；`list_items` @ `:249` 無角色守門 |
| `/api/1.0/audit-round/<round_uid>/poam-item/<item_uid>` | GET | `audit_round_route.py:381` | 是 | `PoamAppService.get_item` | 僅 JWT | 讀取；`get_item` @ `:274` 無角色守門 |
| `/api/1.0/audit-round/<round_uid>/ar/risk/<risk_uid>/remediations` | POST | `audit_round_route.py:391` | 是 | `PoamAppService.add_remediation` | 已有: `_check_manager(...)` @ `poam_app_service.py:318`（helper `:91`） | 建整改計畫 |
| `/api/1.0/audit-round/<round_uid>/remediation/<remediation_uid>/milestones` | POST | `audit_round_route.py:406` | 是 | `PoamAppService.add_milestone` | 已有: `_check_manager(...)` @ `poam_app_service.py:339` | |
| `/api/1.0/audit-round/<round_uid>/poam-milestone/<milestone_uid>` | PUT | `audit_round_route.py:425` | 是 | `PoamAppService.update_milestone` | 已有: `_check_manager(...)` @ `poam_app_service.py:354` | |
| `/api/1.0/audit-round/<round_uid>/poam-milestone/<milestone_uid>` | DELETE | `audit_round_route.py:436` | 是 | `PoamAppService.delete_milestone` | 已有: `_check_manager(...)` @ `poam_app_service.py:375` | |
| `/api/1.0/audit-round/<round_uid>/remediation/<remediation_uid>` | DELETE | `audit_round_route.py:449` | 是 | `PoamAppService.delete_remediation` | 已有: `_check_manager(...)` @ `poam_app_service.py:388` | |

## api/project_summary_report

Route files：`project_summary_report_route.py`、`project_summary_report_history_route.py`。
兩支 service（`ProjectSummaryReportService`、`ProjectSummaryReportHistoryService`）**全檔無任何授權守門**（無 ForbiddenError / 無 role / participant / is_admin 檢查）。

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/project-summary-reports` | POST | `project_summary_report_route.py:36` | 是 | `ProjectSummaryReportService.get_project_summary_reports_and_pager` | 僅 JWT | POST 實為分頁查詢(讀) |
| `/api/1.0/project-summary-report` | POST | `project_summary_report_route.py:56` | 是 | `ProjectSummaryReportService.create_project_summary_report` | 僅 JWT | 新增（寫入）；service `:74` 僅解析 `project_uid→project_id`，**未檢查 caller 是否為該專案 participant/manager/owner**（project-ownership-not-checked，即已知裸區） |
| `/api/1.0/project-summary-report/<uid>` | GET | `project_summary_report_route.py:76` | 是 | `ProjectSummaryReportService.get_project_summary_report` | 僅 JWT | 讀取 detail |
| `/api/1.0/project-summary-report/<uid>` | PUT | `project_summary_report_route.py:87` | 是 | `ProjectSummaryReportService.update_project_summary_report` | 僅 JWT | 編輯（寫入）；service `:103` 僅依 uid 更新，無 ownership 檢查 |
| `/api/1.0/project-summary-report/<uid>` | DELETE | `project_summary_report_route.py:107` | 是 | `ProjectSummaryReportService.delete_project_summary_report` | 僅 JWT | 刪除（寫入）；service `:128` 僅依 uid 刪，無 ownership 檢查 |
| `/api/1.0/project-summary-report/export/<uid>` | GET | `project_summary_report_route.py:118` | 是 | `ProjectSummaryReportService.get_project_summary_report_by_uid` | 僅 JWT | 敏感 GET（匯出 PDF，weasyprint）；`:66` 無 ownership 檢查 |
| `/api/1.0/project-summary-report/histories` | POST | `project_summary_report_history_route.py:28` | 是 | `ProjectSummaryReportHistoryService.get_project_summary_report_histories` | 僅 JWT | POST 實為列表查詢(讀) |
| `/api/1.0/project-summary-report/history/<uid>` | GET | `project_summary_report_history_route.py:44` | 是 | `ProjectSummaryReportHistoryService.get_project_summary_report_history_by_uid` | 僅 JWT | 讀取 detail |
| `/api/1.0/project-summary-report/history-revert` | POST | `project_summary_report_history_route.py:58` | 是 | `ProjectSummaryReportHistoryService.revert_data_from_cruise_project_report_history` | 僅 JWT | 還原歷史（寫入）；service `:35` 依 report_uid/history_uid 還原，無 ownership 檢查 |

## api/subtask_status_history

Route file：`subtask_status_history_route.py`。
`SubtaskStatusHistoryService` **全檔無任何授權守門**（無 ForbiddenError / 無 role / participant / is_admin 檢查）。
註：`SubtaskStatusHistoriesRoute` 用 `def get` 但註冊在 `/subtask-status-histories/<task_uid>`，body 帶 filter（列表讀取）。

| Route path | Method | Route file:line | @jwt_required? | 呼叫的 service method | 現況守門 | 備註 |
|---|---|---|---|---|---|---|
| `/api/1.0/subtask-status-histories/<task_uid>` | GET | `subtask_status_history_route.py:26` | 是 | `SubtaskStatusHistoryService.get_subtask_status_histories` | 僅 JWT | 列表讀取 by task_uid |
| `/api/1.0/subtask-status-history` / `/subtask-status-history/<uid>` | GET | `subtask_status_history_route.py:39` | 是 | `SubtaskStatusHistoryService.get_subtask_status_history` | 僅 JWT | 讀取 detail by uid |
| `/api/1.0/subtask-status-history` | POST | `subtask_status_history_route.py:49` | 是 | `SubtaskStatusHistoryService.add_subtask_status_history` | 僅 JWT | 新增（寫入）；`:40` 無角色/ownership 守門 |
| `/api/1.0/subtask-status-history/<uid>` | PUT | `subtask_status_history_route.py:60` | 是 | `SubtaskStatusHistoryService.update_subtask_status_history` | 僅 JWT | 修改（寫入）；`:53` 無角色/ownership 守門 |
| `/api/1.0/subtask-status-history/<uid>` | DELETE | `subtask_status_history_route.py:70` | 是 | `SubtaskStatusHistoryService.delete_subtask_status_history` | 僅 JWT | 刪除（寫入）；`:67` 無角色/ownership 守門 |
