# A.1 盤點：oscal 合規框架與 flow 流程設計模組端點 × 三段式能力點

> 卡片 CM-2512（FR-132 T-1.2）。唯讀盤點，不改程式、不改 DB。

## 範圍與數量

- **範圍**：合規框架（框架、版本、catalog 編輯、PDF 匯入）、合規資源庫（`api/module_frame/` 與 `api/oscal/` 下的資源庫端點）、專案 SSP（`api/oscal/routes/ssp/`）、流程範本（`flow_template_route.py`），以及 T-1.1 留下的兩支 `api/flow_engine` 端點（`process-definition`、`stage-objects`）。
- **主專案 route 檔 37 支、161 個 method**：
  - `api/oscal/routes/framework/` 4 支、25 個
  - `api/oscal/routes/` 根目錄 2 支、3 個（資源庫列表／新增、範本 SSP 參照）
  - `api/oscal/routes/ssp/` 13 支、58 個（另有 3 個 Resource 已註解停用：`SspScopedExcelImportRoute` GET／DELETE、`SspScopedExcelImportConfirmRoute` POST，`api/oscal/__init__.py:110-111`，不計入）
  - `api/module_frame/routes/` 15 支、62 個
  - `api/module_frame/routes/` 的 62 個裡含 `/ssp/<uid>/excel-template`（程式住資源庫、資源是 SSP，列在 SSP 段）
  - `api/flow_engine/routes/` 3 支、13 個（流程範本 11、階段物件 1，以及 `flow_engine_route.py` 的流程定義 1 個；同檔其餘 3 個已由 T-1.1 歸 grc）
- **外部套件 route 檔 0 支**：jedi-flow-engine 只有 `api/guards.py`（`runtime()` 取執行期組件，不是守門 decorator，套件自己註明「本套件沒有 route」）；jedi-oscal-v2 沒有 `api/` 目錄。兩支套件提供的守門被主專案用到的：無。
- **DEV 查詢時間**：2026-10-06 17:27（本機 `localhost:5432/guidant_ai_dev`）。範圍內舊能力點 12 個（`compliance-framework.*`、`module-frame.*`、`flow_template.*` 各 4）。`workflow.*` 4 個已由 T-1.1 判刪除，本份不重複。
- **三個 blueprint 都沒有 license 軸**（`require_license` 零命中）。路徑前綴一律 `/api/1.0`，下表省略。

## 先看這幾點

1. **`compliance-framework.*` 四個能力點一支 route 都沒守。** 合規框架 25 個 method 的 route 層只有 `jwt_required`；寫入在 service 層用 `require_platform_admin()`（只限原廠總部租戶的管理員），讀取完全不檢查。四個能力點只綁在 `/compliance-framework/compliance-framework-manage` 選單與角色範本，決定選單看不看得到。
2. **SSP 58 個 method 全部沒有能力點**，只靠 service 層 `SspPermissionChecker`：讀＝專案任一成員，寫＝專案 manager 且當前輪次仍在規劃階段（`common/authz/ssp.py:91-104`、`domain/oscal/service/ssp_project_resolver.py` `is_writable`）。成員檢查是完整的（13 支 service 逐 method 對過，沒有漏掉的），缺的是公司層這一道。
3. **SSP 的控制項樹不只 SSP 頁在用。** `GET /ssp/<uid>/control-tree` 被證據分類、稽核審閱、AP 編寫、稽核員總覽等六個頁面呼叫。`oscal.ssp.read` 一旦掛上守門，**出貨角色範本的一般成員組必須帶它**，否則專案成員打開稽核頁就 403。
4. **資源庫子物件的動詞今天是照 HTTP 動詞配的**：替既有資源庫新增元件要 `module-frame.create`、刪元件要 `module-frame.delete`、匯入預設值到既有資源庫也要 `create`。新規則把子物件寫入一律歸 update（見定案 3），等於行為改變，要在 migration 補配時一併處理。
5. **SSP 與資源庫共用兩支「雙目標匯入」端點**（`/ssp-excel-imports/*`、`/ssp-docx-imports/*`）：同一支 route 依上傳時選的目標，寫進專案 SSP 或資源庫範本。能力點只能在 service 層依目標判，route decorator 表達不了。
6. **流程定義端點不是舊入口。** `GET /flow-engine/process-definition/<uid>` 讀的是查核項目層的 BPMN（`jedi_flow_engine` 的 `workflow_templates`，與流程範本 `flow_templates` 是兩張表），被專案規劃頁與三支資源庫頁面透過 `/workflow/workflow-setup` 打開（FE `config/router/index.js:457-466` 註解明載必須保留）。歸資源庫，見定案 6。

## 新名總覽（建議）

| 新資源 | read | create | update | delete | 獨立動詞 | 取代的舊名 | 平台層 |
|---|---|---|---|---|---|---|---|
| `oscal.framework` | ✓ | ✓ | ✓ | ✓ | `import`（PDF 匯入） | `compliance-framework.*` | create／update／delete／import |
| `oscal.resource-library` | ✓ | ✓ | ✓ | ✓ | 無 | `module-frame.*` | 否 |
| `oscal.ssp` | ✓ | ✓ | ✓ | ✓ | `import`、`export` | 無（新開） | 否 |
| `flow.template` | ✓ | ✓ | ✓ | ✓ | `publish`（發布／取消發布） | `flow_template.*` | 否 |

- 資源名、`flow` 獨立模組、`publish` 都已定案，見文末定案結論。
- `oscal.ssp.create`／`.delete`、`oscal.framework` 以外的部分 create／delete 今天沒有對應端點（SSP 隨建專案產生），照「每個資源固定四個」仍要建出來。

## 主表

欄位：route 路徑 ｜ HTTP ｜ 程式位置 ｜ 現有守門 ｜ 新三段名 ｜ 現有能力點 ｜ 對應 UI ｜ 範圍層 ｜ 備註。

守門縮寫：**J**＝`jwt_required`；**C(x)**＝`require_capability("x")`；**PA**＝service 層 `require_platform_admin()`；**V(x)**＝service 層 `viewer_has_capability("x")`；**ST**＝簽章下載憑證（`signed_token_required`）。SSP 段的專案層守門寫「成員：參與者」或「成員：manager＋可寫」（＝manager 且當前輪次仍在規劃階段）。程式位置的行號是 method 定義行。

### 合規框架（oscal.framework）

框架程式 `api/oscal/routes/framework/`，service 在 `app/oscal/service/framework_*.py`。對應頁 `/compliance-framework/compliance-framework-manage`（`ComplianceFrameworkManage.vue`、`ComplianceFrameworkVersionManage.vue`），catalog 編輯 `FrameworkVersionEditView.vue`，PDF 匯入 `FrameworkImportPage.vue`。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/oscal-frameworks` | POST | `oscal_framework_route.py:49` | J | `oscal.framework.read` | 無 | `ComplianceFrameworkManage` | tenant | 列表 |
| `/oscal-frameworks/menu` | GET | `oscal_framework_route.py:35` | J | `oscal.framework.read` | 無 | `menuStore.fetchComplianceFrameworkMenu`：`ModuleFrame`、`SspDocxImportPage`、`ImportExcelPage`、`DownloadSspBlankTemplateDialog` | tenant | 輔助端點；呼叫者多是資源庫頁，靠一般成員組本來就帶 read |
| `/oscal-framework/<uid>` | GET | `oscal_framework_route.py:68` | J | `oscal.framework.read` | 無 | `ComplianceFrameworkManage`、`ComplianceFrameworkVersionManage` | tenant | |
| `/oscal-framework` | POST | `oscal_framework_route.py:78` | J＋PA | `oscal.framework.create` | 無 | `ComplianceFrameworkManage` | 平台 | |
| `/oscal-framework/<uid>` | PUT | `oscal_framework_route.py:92` | J＋PA | `oscal.framework.update` | 無 | 同上 | 平台 | |
| 同上 | DELETE | `oscal_framework_route.py:101` | J＋PA | `oscal.framework.delete` | 無 | 同上 | 平台 | |
| `/oscal-framework-versions` | POST | `oscal_framework_version_route.py:38` | J | `oscal.framework.read` | 無 | `ComplianceFrameworkVersionManage` | tenant | 版本列表 |
| `/oscal-framework-version/<uid>` | GET | `oscal_framework_version_route.py:57` | J | `oscal.framework.read` | 無 | 同上 | tenant | |
| `/oscal-framework-version` | POST | `oscal_framework_version_route.py:68` | J＋PA | `oscal.framework.update` | 無 | 同上 | 平台 | 新增版本＝改框架（子物件歸 update，定案 3） |
| `/oscal-framework-version/<uid>` | PUT | `oscal_framework_version_route.py:82` | J＋PA | `oscal.framework.update` | 無 | 同上 | 平台 | |
| 同上 | DELETE | `oscal_framework_version_route.py:92` | J＋PA | `oscal.framework.update` | 無 | 同上 | 平台 | 子物件歸 update |
| `/oscal-framework-version/download/oscal/<uid>/token` | GET | `oscal_framework_version_route.py:155` | J | `oscal.framework.read` | 無 | `signedDownloadUtil.js:75`（版本頁「下載 OSCAL」） | tenant | 發短期下載憑證；守門要掛在這支（下載端點本身不帶 bearer）。定案 5 |
| `/oscal-framework-version/download/oscal/<uid>` | GET | `oscal_framework_version_route.py:120` | ST（綁 uid） | `oscal.framework.read` | 無 | 同上 | tenant | 不掛能力點，由發憑證那支守 |
| `/oscal-framework-version/<uid>/catalog-tree` | GET | `framework_version_edit_route.py:34` | J | `oscal.framework.read` | 無 | `FrameworkVersionEditView`、`ComplianceFrameworkVersionManage`、`ModuleFrame`、`EditIncludeControlsDialog` | tenant | 資源庫頁也打（挑控制項） |
| `/oscal-catalog-group/<uid>` | PUT | `framework_version_edit_route.py:50` | J＋PA＋限草稿 | `oscal.framework.update` | 無 | `FrameworkVersionEditView` | 平台 | |
| 同上 | DELETE | `framework_version_edit_route.py:64` | J＋PA＋限草稿且無引用 | `oscal.framework.update` | 無 | 同上 | 平台 | 子物件歸 update |
| `/oscal-catalog-control/<uid>` | PUT | `framework_version_edit_route.py:80` | 同 group | `oscal.framework.update` | 無 | 同上 | 平台 | |
| 同上 | DELETE | `framework_version_edit_route.py:94` | 同 group | `oscal.framework.update` | 無 | 同上 | 平台 | |
| `/oscal-catalog-control-assessment/<uid>` | PUT | `framework_version_edit_route.py:110` | 同 group | `oscal.framework.update` | 無 | 同上 | 平台 | |
| 同上 | DELETE | `framework_version_edit_route.py:124` | 同 group | `oscal.framework.update` | 無 | 同上 | 平台 | |
| `/oscal-framework-parse-jobs` | GET | `framework_parse_job_route.py:39` | J（只列本租戶） | `oscal.framework.read` | 無 | `FrameworkImportService`（框架頁進行中匯入的提示條） | tenant | 輔助端點；給 read 不給 import，避免看框架頁的人被提示條 403 |
| `/oscal-framework-parse-jobs/parse` | POST | `framework_parse_job_route.py:58` | J＋PA | `oscal.framework.import` | 無 | `FrameworkImportPage` | 平台 | 上傳 PDF |
| `/oscal-framework-parse-jobs/<uid>` | GET | `framework_parse_job_route.py:127` | J | `oscal.framework.import` | 無 | `FrameworkImportPage`、`FrameworkVersionEditView` | 平台 | 解析預覽；今天無任何檢查，見「守門缺口」1 |
| 同上 | DELETE | `framework_parse_job_route.py:140` | J＋PA | `oscal.framework.import` | 無 | `FrameworkImportPage` | 平台 | 廢棄解析單 |
| `/oscal-framework-parse-jobs/<uid>/confirm` | POST | `framework_parse_job_route.py:159` | J＋PA | `oscal.framework.import` | 無 | 同上 | 平台 | 寫入 catalog／版本 |

`FrameworkAppService.publish_version`（`app/oscal/service/framework_app_service.py:246`）沒有任何 route 呼叫，不列。

### 合規資源庫（oscal.resource-library）

程式 `api/module_frame/routes/` 與 `api/oscal/routes/` 根目錄兩支。對應頁 `/module-frame/module-frame`（`ModuleFrame.vue`），範本編輯 `ModuleFrameTemplateEditView.vue`（經 `ModuleFrameTemplateService.js`），舊版項目頁 `ModuleFrameItems.vue`。現有守門全部在 route 層，每個 method 都有，無遺漏。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/oscal/resource-libraries/list` | POST | `api/oscal/routes/resource_library_route.py:29` | **J（無能力點）** | `oscal.resource-library.read` | 無 | `ModuleFrame` | tenant | 列表沒守，單筆 GET 卻要 read。見「守門缺口」2 |
| `/oscal/resource-libraries` | POST | `resource_library_route.py:45` | J＋C(module-frame.create) | `oscal.resource-library.create` | `module-frame.create` | `ModuleFrame`、`SspDocxImportPage` | tenant | 從框架版本複製三件組建新資源庫 |
| `/module-frame/<mf_uid>/template-ssp` | GET | `api/oscal/routes/module_frame_template_ssp_route.py:33` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ModuleFrameTemplateEditView` | tenant | 輔助端點，查範本 SSP 編號 |
| `/module-frames/menu` | GET | `module_frame_route.py:28` | J＋C(module-frame.read 或 project.create) | `oscal.resource-library.read` | `module-frame.read`、`project.create` | `ProjectCreateView` | tenant | 輔助端點；`project.create` 放行要改成 `grc.project.create` |
| `/module-frame/<uid>` | GET | `module_frame_route.py:49` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ModuleFrame` | tenant | |
| `/module-frame` | POST | `module_frame_route.py:60` | J＋C(module-frame.create) | `oscal.resource-library.create` | `module-frame.create` | 同上 | tenant | |
| `/module-frame/<uid>` | PUT | `module_frame_route.py:76` | J＋C(module-frame.update) | `oscal.resource-library.update` | `module-frame.update` | 同上 | tenant | |
| 同上 | DELETE | `module_frame_route.py:88` | J＋C(module-frame.delete) | `oscal.resource-library.delete` | `module-frame.delete` | 同上 | tenant | |
| `/module-frame/clone` | POST | `module_frame_route.py:99` | J＋C(module-frame.create) | `oscal.resource-library.create` | `module-frame.create` | `ModuleFrame` | tenant | |
| `/module-frame/item/<uid>` | GET | `module_frame_item_route.py:34` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ModuleFrameItems` | tenant | 查核項目 |
| `/module-frame/item` | POST | `module_frame_item_route.py:51` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 子物件歸 update（行為改變，定案 3） |
| `/module-frame/item/<uid>` | PUT | `module_frame_item_route.py:67` | J＋C(module-frame.update) | `oscal.resource-library.update` | `module-frame.update` | 同上 | tenant | |
| 同上 | DELETE | `module_frame_item_route.py:82` | J＋C(module-frame.delete) | `oscal.resource-library.update` | `module-frame.delete` | 同上 | tenant | 子物件歸 update |
| `/module-frame/item/xml/<uid>` | PUT | `module_frame_item_route.py:101` | J＋C(module-frame.update) | `oscal.resource-library.update` | `module-frame.update` | `WorkflowSetupEditor`（`/workflow/workflow-setup`） | tenant | 存查核項目層 BPMN；讀取端是流程定義那支，見流程段 |
| `/module-frame/import/verify` | POST | `module_frame_import_route.py:26` | J＋C(module-frame.create) | `oscal.resource-library.create` | `module-frame.create` | **無 UI**（FE 常數 `MODULE_FRAME_IMPORT_VERIFY` 沒人引用） | tenant | 檔案建資源庫。疑似死碼，見「守門缺口」6 |
| `/module-frame/import` | POST | `module_frame_import_route.py:48` | J＋C(module-frame.create)；覆蓋既有時 V(module-frame.update)（`module_frame_import_service.py:143`） | `oscal.resource-library.create` | `module-frame.create`、`module-frame.update` | **無 UI** | tenant | 同上 |
| `/module-frame/import/yaml` | POST | `module_frame_import_route.py:79` | J＋C(module-frame.create) | `oscal.resource-library.create` | `module-frame.create` | **無 UI** | tenant | 同上 |
| `/module-frame/<uid>/system-characteristic` | GET | `module_frame_system_characteristic_route.py:31` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ModuleFrameTemplateEditView` | tenant | 範本 SSP 的系統特性 |
| 同上 | PUT | `module_frame_system_characteristic_route.py:48` | J＋C(module-frame.update) | `oscal.resource-library.update` | `module-frame.update` | 同上 | tenant | |
| `/module-frame/<uid>/components` | GET | `module_frame_components_route.py:21` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ComponentsLeveragedInventoryTab` | tenant | |
| 同上 | POST | `module_frame_components_route.py:35` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/components/<item_uid>` | PUT | `module_frame_components_route.py:53` | J＋C(module-frame.update) | `oscal.resource-library.update` | `module-frame.update` | 同上 | tenant | |
| 同上 | DELETE | `module_frame_components_route.py:70` | J＋C(module-frame.delete) | `oscal.resource-library.update` | `module-frame.delete` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/inventory` | GET | `module_frame_inventory_route.py:21` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ComponentsLeveragedInventoryTab` | tenant | |
| 同上 | POST | `module_frame_inventory_route.py:35` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/inventory/<item_uid>` | PUT | `module_frame_inventory_route.py:53` | J＋C(module-frame.update) | `oscal.resource-library.update` | `module-frame.update` | 同上 | tenant | |
| 同上 | DELETE | `module_frame_inventory_route.py:70` | J＋C(module-frame.delete) | `oscal.resource-library.update` | `module-frame.delete` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/leveraged` | GET | `module_frame_leveraged_route.py:32` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ComponentsLeveragedInventoryTab` | tenant | |
| 同上 | POST | `module_frame_leveraged_route.py:46` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/leveraged/<item_uid>` | PUT | `module_frame_leveraged_route.py:66` | J＋C(module-frame.update) | `oscal.resource-library.update` | `module-frame.update` | 同上 | tenant | |
| 同上 | DELETE | `module_frame_leveraged_route.py:83` | J＋C(module-frame.delete) | `oscal.resource-library.update` | `module-frame.delete` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/parties` | GET | `module_frame_party_route.py:29` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ModuleFrameTemplateEditView`（FE 走共用 composable，apiBase 切換） | tenant | |
| 同上 | POST | `module_frame_party_route.py:45` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/parties/<party_uid>` | PUT | `module_frame_party_route.py:67` | J＋C(module-frame.update) | `oscal.resource-library.update` | `module-frame.update` | 同上 | tenant | |
| 同上 | DELETE | `module_frame_party_route.py:86` | J＋C(module-frame.delete) | `oscal.resource-library.update` | `module-frame.delete` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/ssp-resources` | GET | `module_frame_ssp_resources_route.py:36` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ModuleFrameSspInfoSystemsPanel`（apiBase=module-frame） | tenant | 受評標的 |
| `/module-frame/<uid>/ssp-resources/items` | POST | `module_frame_ssp_resources_route.py:54` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/ssp-resources/items/<item_uid>` | PUT | `module_frame_ssp_resources_route.py:74` | J＋C(module-frame.update) | `oscal.resource-library.update` | `module-frame.update` | 同上 | tenant | |
| 同上 | DELETE | `module_frame_ssp_resources_route.py:91` | J＋C(module-frame.delete) | `oscal.resource-library.update` | `module-frame.delete` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/control-defaults` | GET | `module_frame_control_default_route.py:53` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ModuleFrameTemplateEditView` | tenant | 控制項預設值 |
| `/module-frame/<uid>/control-defaults/<ci>` | GET | `module_frame_control_default_route.py:79` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | 同上 | tenant | |
| 同上 | PUT | `module_frame_control_default_route.py:103` | J＋C(module-frame.update) | `oscal.resource-library.update` | `module-frame.update` | 同上 | tenant | upsert |
| 同上 | DELETE | `module_frame_control_default_route.py:130` | J＋C(module-frame.delete) | `oscal.resource-library.update` | `module-frame.delete` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/objective-defaults` | GET | `module_frame_control_objective_default_route.py:53` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | 同上 | tenant | AO 預設值 |
| 同上 | PUT | `module_frame_control_objective_default_route.py:77` | J＋C(module-frame.update) | `oscal.resource-library.update` | `module-frame.update` | 同上 | tenant | upsert |
| `/module-frame/<uid>/objective-defaults/<od_uid>` | GET | `module_frame_control_objective_default_route.py:113` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | 同上 | tenant | |
| 同上 | DELETE | `module_frame_control_objective_default_route.py:138` | J＋C(module-frame.delete) | `oscal.resource-library.update` | `module-frame.delete` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/reference-documents` | GET | `module_frame_reference_document_route.py:64` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | 同上 | tenant | 程序書池 |
| 同上 | POST | `module_frame_reference_document_route.py:87` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/reference-documents/<doc_uid>` | PUT | `module_frame_reference_document_route.py:120` | J＋C(module-frame.update) | `oscal.resource-library.update` | `module-frame.update` | 同上 | tenant | |
| 同上 | DELETE | `module_frame_reference_document_route.py:149` | J＋C(module-frame.delete) | `oscal.resource-library.update` | `module-frame.delete` | 同上 | tenant | 子物件歸 update |
| `/module-frame/<uid>/control-defaults/<ci>/reference-documents` | POST | `module_frame_reference_document_route.py:182` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 程序書掛到控制項 |
| `.../control-defaults/<ci>/reference-documents/<doc_uid>` | DELETE | `module_frame_reference_document_route.py:208` | J＋C(module-frame.delete) | `oscal.resource-library.update` | `module-frame.delete` | 同上 | tenant | |
| `/module-frame/<uid>/objective-defaults/<od_uid>/reference-documents` | POST | `module_frame_reference_document_route.py:244` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 程序書掛到 AO |
| `.../objective-defaults/<od_uid>/reference-documents/<doc_uid>` | DELETE | `module_frame_reference_document_route.py:270` | J＋C(module-frame.delete) | `oscal.resource-library.update` | `module-frame.delete` | 同上 | tenant | |
| `/module-frame/<uid>/control-defaults/export` | GET | `module_frame_template_import_route.py:69` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ModuleFrameTemplateImportDialog` | tenant | 範本內容匯出 Excel，不開 export（定案 4） |
| `/module-frame/<uid>/control-defaults/template/download` | GET | `module_frame_template_import_route.py:98` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | 同上 | tenant | 空白匯入範本 |
| `/module-frame/<uid>/control-defaults/import/template` | POST | `module_frame_template_import_route.py:138` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 匯入第 1 步：驗檔格式。匯入到既有資源庫＝改它（定案 2、3） |
| `/module-frame/<uid>/control-defaults/import/verify` | POST | `module_frame_template_import_route.py:172` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 第 2 步：逐列解析 |
| `/module-frame/<uid>/control-defaults/import/validate` | POST | `module_frame_template_import_route.py:206` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 第 3 步：前端改過再驗 |
| `/module-frame/<uid>/control-defaults/import` | POST | `module_frame_template_import_route.py:240` | J＋C(module-frame.create) | `oscal.resource-library.update` | `module-frame.create` | 同上 | tenant | 第 4 步：寫入 |
| `/module-frame/<uid>/ssp-import-template` | GET | `ssp_import_template_route.py:57` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ImportExcelPage`、`ModuleFrameTemplateEditView` | tenant | 九分頁 SSP 匯入範本（空白或帶入現有內容） |
| `/ssp-import-template` | GET | `ssp_import_template_route.py:142` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `DownloadSspBlankTemplateDialog` | tenant | 依框架版本的空白範本 |
| `/module-frame/<uid>/ssp-export` | GET | `mf_ssp_export_route.py:54` | J＋C(module-frame.read) | `oscal.resource-library.read` | `module-frame.read` | `ModuleFrameTemplateEditView`（`MODULE_FRAME_SSP_DOCUMENT_EXPORT`） | tenant | 範本 SSP 匯出 docx／pdf，不開 export（定案 4） |

### 專案 SSP（oscal.ssp）

程式 `api/oscal/routes/ssp/`，service 在 `app/oscal/service/ssp_*`。13 支全部只有 `jwt_required`，專案層守門逐 method 對過 service（`require_participant`／`require_manager`），沒有漏。FE 經 `SspService.js` 呼叫，使用頁：`ProjectPlanningView`、`SspTabPanel`、`SspBasicSection`、`SspLeveragedSection`、`ComponentsLeveragedInventoryTab`、`DocumentPoolPanel`、`DocumentLinkDialog`、`SspImportDialog`、`RoundApAuthoringView`、`ProjectAuditorOverview`、`DashboardView`（未逐支核）。範圍層一律 org_unit（SSP 跟著專案）。

**子物件不拆資源，寫入一律 `oscal.ssp.update`**（理由見「SSP 是否拆子資源」）。

| route 路徑（前綴 `/ssp/<ssp_uid>`） | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/system-characteristic` | GET | `ssp_system_characteristic_route.py:26` | J；成員：參與者 | `oscal.ssp.read` | 無 | `SspBasicSection`（`useSspSystemCharacteristic`） | org_unit | |
| 同上 | PUT | `ssp_system_characteristic_route.py:41` | J；成員：manager＋可寫 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/components` | GET | `ssp_components_route.py:30` | J；成員：參與者 | `oscal.ssp.read` | 無 | `ComponentsLeveragedInventoryTab` | org_unit | |
| 同上 | POST | `ssp_components_route.py:43` | J；成員：manager＋可寫 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/components/<item_uid>` | PUT | `ssp_components_route.py:61` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| 同上 | DELETE | `ssp_components_route.py:76` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/inventory-items` | GET | `ssp_inventory_items_route.py:29` | J；成員：參與者 | `oscal.ssp.read` | 無 | 同上 | org_unit | |
| 同上 | POST | `ssp_inventory_items_route.py:42` | J；成員：manager＋可寫 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/inventory-items/<item_uid>` | PUT | `ssp_inventory_items_route.py:60` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| 同上 | DELETE | `ssp_inventory_items_route.py:75` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/leveraged` | GET | `ssp_leveraged_route.py:23` | J；成員：參與者 | `oscal.ssp.read` | 無 | `SspLeveragedSection`（`useSspLeveraged`） | org_unit | |
| 同上 | POST | `ssp_leveraged_route.py:36` | J；成員：manager＋可寫 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/leveraged/<item_uid>` | PUT | `ssp_leveraged_route.py:54` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| 同上 | DELETE | `ssp_leveraged_route.py:69` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/parties` | GET | `ssp_party_route.py:24` | J；成員：參與者 | `oscal.ssp.read` | 無 | `useSspParties` | org_unit | |
| 同上 | POST | `ssp_party_route.py:39` | J；成員：manager＋可寫 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/parties/<party_uid>` | PUT | `ssp_party_route.py:59` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| 同上 | DELETE | `ssp_party_route.py:75` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/ssp-resources` | GET | `ssp_resources_route.py:29` | J；成員：參與者 | `oscal.ssp.read` | 無 | `ModuleFrameSspInfoSystemsPanel`（apiBase=ssp） | org_unit | 受評標的 |
| `/ssp-resources/items` | POST | `ssp_resources_route.py:46` | J；成員：manager＋可寫 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/ssp-resources/items/<item_uid>` | PUT | `ssp_resources_route.py:64` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| 同上 | DELETE | `ssp_resources_route.py:79` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/control-tree` | GET | `ssp_control_implementation_route.py:139` | J；成員：參與者 | `oscal.ssp.read` | 無 | `ProjectPlanningView`、`RoundApAuthoringView`、`RoundAuditReviewView`、`ProjectAuditorOverview`、`EvidenceClassificationReview`、`AutoClassifyBatch` | org_unit | **稽核頁共用**，見「先看這幾點」3 |
| `/control-implementations` | GET | `ssp_control_implementation_route.py:109` | J；成員：參與者 | `oscal.ssp.read` | 無 | `SspService`（SoA 列表） | org_unit | |
| `/control-implementation/<ci>` | GET | `ssp_control_implementation_route.py:50` | J；成員：參與者 | `oscal.ssp.read` | 無 | 同上 | org_unit | |
| 同上 | POST | `ssp_control_implementation_route.py:67` | J；成員：manager＋可寫 | `oscal.ssp.update` | 無 | 同上 | org_unit | upsert |
| 同上 | PUT | `ssp_control_implementation_route.py:87` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | upsert |
| `/control-implementation/<ci>/objective/<si>` | POST | `ssp_control_implementation_route.py:156` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| 同上 | PUT | `ssp_control_implementation_route.py:177` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `.../objective/<si>/document/<file_id>` | DELETE | `ssp_control_implementation_route.py:199` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/control-implementation/<ci>/reference-documents` | POST | `ssp_control_implementation_route.py:222` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/control-implementation/<ci>/reference-document/<doc_uid>` | DELETE | `ssp_control_implementation_route.py:243` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `.../objective/<si>/reference-documents` | GET | `ssp_control_implementation_route.py:261` | J；成員：參與者 | `oscal.ssp.read` | 無 | 同上 | org_unit | |
| 同上 | POST | `ssp_control_implementation_route.py:278` | J；成員：manager＋可寫 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `.../objective/<si>/reference-document/<doc_uid>` | DELETE | `ssp_control_implementation_route.py:300` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/document-pool` | GET | `ssp_document_pool_route.py:43` | J；成員：參與者 | `oscal.ssp.read` | 無 | `DocumentPoolPanel` | org_unit | 程序書池 |
| 同上 | POST | `ssp_document_pool_route.py:55` | J；成員：manager＋可寫 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/document-pool/<doc_uid>` | DELETE | `ssp_document_pool_route.py:73` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/control-implementation/<ci>/document-mappings` | GET | `ssp_document_pool_route.py:90` | J；成員：參與者 | `oscal.ssp.read` | 無 | `DocumentLinkDialog` | org_unit | |
| 同上 | POST | `ssp_document_pool_route.py:103` | J；成員：manager＋可寫 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `.../document-mapping/<doc_uid>` | DELETE | `ssp_document_pool_route.py:122` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `.../objective/<si>/document-mappings` | GET | `ssp_document_pool_route.py:140` | J；成員：參與者 | `oscal.ssp.read` | 無 | 同上 | org_unit | |
| 同上 | POST | `ssp_document_pool_route.py:154` | J；成員：manager＋可寫 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `.../objective/<si>/document-mapping/<doc_uid>` | DELETE | `ssp_document_pool_route.py:174` | 同上 | `oscal.ssp.update` | 無 | 同上 | org_unit | |
| `/control-implementations/export` | GET | `ssp_control_impl_import_route.py:34` | J；成員：參與者 | `oscal.ssp.export` | 無 | `SspService.exportExcel`（`SspTabPanel`） | org_unit | SoA 匯出 Excel |
| `/control-implementations/import` | POST | `ssp_control_impl_import_route.py:57` | J；成員：參與者（只驗不寫，`ssp_control_impl_import_service.py:220`） | `oscal.ssp.import` | 無 | `SspImportDialog` | org_unit | 匯入第 1 步：上傳並驗證。整條匯入流程同一個動詞 |
| `/control-implementations/import/validate` | POST | `ssp_control_impl_import_route.py:80` | J；成員：參與者 | `oscal.ssp.import` | 無 | 同上 | org_unit | 第 2 步：前端改過再驗 |
| `/control-implementations/import/confirm` | POST | `ssp_control_impl_import_route.py:101` | J；成員：manager＋可寫 | `oscal.ssp.import` | 無 | 同上 | org_unit | 第 3 步：寫入 |
| `/excel-import/upload` | POST | `ssp_scoped_excel_import_route.py:42` | J；成員：manager＋可寫（`ssp_excel_import_app_service.py:823`） | `oscal.ssp.import` | 無 | `SspTabPanel`（`SspExcelImportService`） | org_unit | 九分頁 Excel 匯入專案 SSP；之後的預覽／確認走下段雙目標端點 |
| `/excel-template` | GET | `api/module_frame/routes/ssp_import_template_route.py:100` | J＋C(module-frame.read)；成員：參與者 | `oscal.ssp.export` | `module-frame.read` | `SspService`（`SspTabPanel`） | org_unit | 程式住在 module_frame 但資源是 SSP。帶入現有內容模式會輸出整份 SSP，歸 export；今天掛 `module-frame.read` 是錯資源 |
| `/export` | GET | `ssp_export_route.py:55` | J；成員：參與者 | `oscal.ssp.export` | 無 | `ProjectPlanningView`（`SSP_DOCUMENT_EXPORT`） | org_unit | 匯出 docx／pdf／odt |

### 雙目標匯入（SSP 或資源庫，依上傳時的目標判）

上傳時選 `source_type`：`ssp`＝專案 SSP；`module_frame`＝既有資源庫；`framework_version`（Excel）或不帶來源（Word「新建」）＝用檔案新建資源庫。能力點必須在 service 層依目標判（今天資源庫兩條已經這樣做：`V(module-frame.create)`、`assert_can_overwrite_resource_library` 查 `V(module-frame.update)`，`app/oscal/service/resource_library_overwrite_guard.py:12-34`）。下表「新三段名」寫成依目標三選一。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/ssp-excel-imports/parse` | POST | `ssp_excel_import_route.py:36` | J；依目標：ssp→成員 manager＋可寫；module_frame→V(module-frame.update)＋租戶可寫；framework_version→V(module-frame.create)（`ssp_excel_import_app_service.py:809-829`） | ssp→`oscal.ssp.import`；module_frame→`oscal.resource-library.update`；framework_version→`oscal.resource-library.create` | `module-frame.create`、`module-frame.update` | `ImportExcelPage`、`SspTabPanel` | 依目標 | 定案 2 |
| `/ssp-excel-import/<parse_uid>` | GET | `ssp_excel_import_route.py:71` | J；ssp→成員：參與者；其他→只比租戶 | 同上依目標 | 無 | `ImportExcelPreviewPage` | 依目標 | 預覽 |
| 同上 | DELETE | `ssp_excel_import_route.py:85` | J；ssp→成員 manager；其他→只比租戶 | 同上依目標 | 無 | 同上 | 依目標 | 廢棄解析單 |
| `/ssp-excel-import/<parse_uid>/confirm` | POST | `ssp_excel_import_route.py:103` | J；ssp→成員 manager＋可寫；module_frame→V(module-frame.update)；**framework_version→只比租戶**（`ssp_excel_import_app_service.py:550` 起） | 同上依目標 | `module-frame.update` | 同上 | 依目標 | 新建資源庫那條確認端沒查能力點，見「守門缺口」3 |
| `/ssp-docx-imports/parse` | POST | `ssp_docx_import_route.py:33` | J；ssp→成員 manager＋可寫；新建→V(module-frame.create)；既有資源庫→V(module-frame.update)（`ssp_docx_import_app_service.py:150-173`） | ssp→`oscal.ssp.import`；既有→`oscal.resource-library.update`；新建→`oscal.resource-library.create` | `module-frame.create`、`module-frame.update` | `SspDocxImportPage` | 依目標 | |
| `/ssp-docx-import/<parse_uid>` | GET | `ssp_docx_import_route.py:64` | J；ssp→成員：參與者；其他→只比租戶 | 同上依目標 | 無 | 同上 | 依目標 | |
| 同上 | DELETE | `ssp_docx_import_route.py:77` | J；ssp→成員 manager；其他→只比租戶 | 同上依目標 | 無 | 同上 | 依目標 | |
| `/ssp-docx-import/<parse_uid>/confirm` | POST | `ssp_docx_import_route.py:96` | J；ssp→成員 manager＋可寫；既有→`resolve_import_confirm_target`（update，或 create＋本人建的空殼）；**新建且未建殼→只比租戶** | 同上依目標 | `module-frame.create`、`module-frame.update` | 同上 | 依目標 | 同 Excel，見「守門缺口」3 |

### 流程範本（flow.template）

程式 `api/flow_engine/routes/flow_template_route.py`，11 個 method 已全掛守門（`:23-32` 定義四個 decorator），錯誤碼用 `GRC_FLOW_TEMPLATE_NO_PERMISSION`。對應頁 `/flow/template-manage`（`FlowTemplateManageView.vue`、`FlowTemplateEditorView.vue`），FE 用 `hasCap('flow_template.create/update/delete')` 控按鈕（`FlowTemplateManageView.vue:27-29`、`FlowTemplateEditorView.vue:50-51`）。範圍層 tenant（`compliance.flow_templates` 只有 `tenant_id`）。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/flow-engine/flow-templates` | GET | `flow_template_route.py:39` | J＋C(flow_template.read 或 project.create) | `flow.template.read` | `flow_template.read`、`project.create` | `FlowTemplateManageView`、`ProjectCreateView` | tenant | 建專案第一步選範本，所以放行 `grc.project.create` 要保留 |
| 同上 | POST | `flow_template_route.py:55` | J＋C(flow_template.create) | `flow.template.create` | `flow_template.create` | `FlowTemplateManageView` | tenant | |
| `/flow-engine/flow-templates/<uid>` | GET | `flow_template_route.py:69` | J＋C(flow_template.read 或 project.create) | `flow.template.read` | `flow_template.read`、`project.create` | `FlowTemplateEditorView`、`FlowTemplatePreviewDialog`（`ProjectCreateView` 內） | tenant | |
| 同上 | PUT | `flow_template_route.py:77` | J＋C(flow_template.update) | `flow.template.update` | `flow_template.update` | `FlowTemplateEditorView` | tenant | |
| 同上 | DELETE | `flow_template_route.py:87` | J＋C(flow_template.delete) | `flow.template.delete` | `flow_template.delete` | `FlowTemplateManageView` | tenant | 軟刪 |
| `/flow-engine/flow-templates/<uid>/duplicate` | POST | `flow_template_route.py:99` | J＋C(flow_template.create) | `flow.template.create` | `flow_template.create` | 同上 | tenant | |
| `/flow-engine/flow-templates/<uid>/publish` | PUT | `flow_template_route.py:117` | J＋C(flow_template.update) | `flow.template.publish` | `flow_template.update` | `FlowTemplateEditorView` | tenant | 定案 7；不開 publish 就歸 update |
| `/flow-engine/flow-templates/<uid>/unpublish` | PUT | `flow_template_route.py:130` | J＋C(flow_template.update) | `flow.template.publish` | `flow_template.update` | 同上 | tenant | 同上 |
| `/flow-engine/flow-templates/validate` | POST | `flow_template_route.py:147` | J＋C(flow_template.update) | `flow.template.update` | `flow_template.update` | 同上 | tenant | 編輯器的檢查，不寫入 |
| `/flow-engine/stage-objects` | GET | `api/flow_engine/routes/stage_object_route.py:16` | J | `flow.template.read` | 無 | `FlowTemplateEditorView`、`FlowTemplatePreviewDialog`（含 `ProjectCreateView`） | tenant | 輔助端點（屬性面板下拉）。建專案預覽也打，守門要比照列表用「read 或 `grc.project.create`」 |

### 查核項目層流程定義（T-1.1 留下）

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/flow-engine/process-definition/<uid>` | GET | `api/flow_engine/routes/flow_engine_route.py:27` | J；**無任何檢查** | 先解析歸屬：專案→`grc.project.read`＋專案成員；資源庫→`oscal.resource-library.read`（定案 6） | 無 | `WorkflowSetupEditor`（`/workflow/workflow-setup`），由 `ProjectPlanningView:1390`、`ModuleFrameItems:103`、`ModuleFrameTemplateEditView:107`、`ModuleFrameDetail:150` 開啟 | tenant | 讀查核項目的 BPMN（`WorkflowTemplateService.get_workflow_template_by_uid`，套件 `jedi_flow_engine/app/service/workflow_template_service.py:46`）；寫入端是 `PUT /module-frame/item/xml`（update）。定案 6 |

## SSP 是否拆子資源

**建議不拆，全部歸 `oscal.ssp`。**

- 若拆，會有：`oscal.ssp`（系統特性）、`oscal.ssp-component`（元件、資產清單、受評標的）、`oscal.ssp-party`（人員與組織）、`oscal.ssp-leveraged`（沿用授權）、`oscal.ssp-control-implementation`（控制實作與 AO 現況）、`oscal.ssp-document`（程序書池與關聯），六個資源各四個基本動詞共 24 個能力點。
- 不拆的理由：
  - 寫入的專案層門檻全部一樣（manager＋當前輪次仍在規劃），沒有任何一塊交給別的角色寫；公司層拆了也表達不出專案層沒有的差異。
  - SSP 頁是一張表單分好幾個分頁，客戶不會只授權「能改人員、不能改元件」。
  - 拆了以後角色漏勾一格，SSP 頁某個分頁的按鈕就 403 且沒有錯誤訊息，正是本 FR 要避免的形狀。
- 不拆時：讀＝`oscal.ssp.read`；所有子物件新增、修改、刪除＝`oscal.ssp.update`；Excel／Word 匯入整條流程＝`oscal.ssp.import`；docx／pdf 匯出、SoA Excel 匯出、帶入內容的匯入範本＝`oscal.ssp.export`。
- **反悔條件**：客戶要求「控制實作現況交給被稽核單位寫、但不能改 SSP 基本資料」時，把控制實作拆成 `oscal.ssp-control-implementation`，其餘不動。這也要專案層守門同時拆（今天控制實作寫入只給 manager）。

## 流程執行歸 flow 或 grc

**建議歸 grc，照 T-1.1 已列的放法。**

- 流程執行（任務提交、退回、待辦、佐證、稽核階段推進與回退）全部在專案底下，守門是專案成員角色，資源是任務與稽核輪次，跟流程範本的設計沒有關係。放在 flow 會變成「有 grc.job 卻要另勾 flow 才能提交任務」。
- flow 模組因此只剩流程範本一個資源，是否保留獨立模組見定案 8。

## 現有能力點反查（DEV 12 個）

| 舊名 | 主表有 route 守它？ | 新名 | 其他引用處 |
|---|---|---|---|
| `compliance-framework.read` | **無** | `oscal.framework.read` | 綁 `/compliance-framework/compliance-framework-manage` 選單；角色範本 `core/role_templates.py:18`（一般成員組）；宣告 `core/host_capabilities.py:32` |
| `compliance-framework.create` | **無**（寫入改由 service 層 `require_platform_admin` 守） | `oscal.framework.create` | 選單同上；`core/host_capabilities.py:30`（`is_platform=True`）；`scripts/sql/2026-06-30-compliance-framework-platform-capability.sql` |
| `compliance-framework.update` | **無**（同上） | `oscal.framework.update` | 選單同上；`core/host_capabilities.py:33` |
| `compliance-framework.delete` | **無**（同上） | `oscal.framework.delete` | 選單同上；`core/host_capabilities.py:31` |
| `module-frame.read` | 有：資源庫讀取全部、範本 SSP 參照、SSP 匯入範本三支、範本匯出 | `oscal.resource-library.read` | 角色範本 :17（一般成員組）；宣告 `core/host_capabilities.py:24`；**`/ssp/<uid>/excel-template` 也掛它，改名時改成 `oscal.ssp.export`** |
| `module-frame.create` | 有：建、複製、子物件新增、匯入 | `oscal.resource-library.create` | service 層：`ssp_excel_import_app_service.py:814`、`ssp_docx_import_app_service.py:168`、`resource_library_overwrite_guard.py:29`；宣告 :22 |
| `module-frame.update` | 有：改、子物件修改、BPMN 儲存 | `oscal.resource-library.update` | service 層：`resource_library_overwrite_guard.py:14,26`、`module_frame_import_service.py:143`；宣告 :25 |
| `module-frame.delete` | 有：刪除、子物件刪除 | `oscal.resource-library.delete` | 宣告 :23 |
| `flow_template.read` | 有：列表、單筆 | `flow.template.read` | 角色範本 :49（唯讀檢視者組）；宣告 `core/host_capabilities.py:62` |
| `flow_template.create` | 有：新增、複製 | `flow.template.create` | FE `FlowTemplateManageView.vue:27`、`FlowTemplateEditorView.vue:50`；宣告 :60 |
| `flow_template.update` | 有：改、發布、取消發布、驗證 | `flow.template.update`（發布另議） | FE `FlowTemplateManageView.vue:28`、`FlowTemplateEditorView.vue:51`；宣告 :63 |
| `flow_template.delete` | 有：刪除 | `flow.template.delete` | FE `FlowTemplateManageView.vue:29`；宣告 :61 |

`workflow.*` 四個也在 DEV 查詢結果內，已由 T-1.1 判刪除（見 `a1-grc.md`「定案結論」2），本份不重複。

### 沒有任何 route 用到的能力點（孤兒）

`compliance-framework.*` 四個。它們決定框架管理頁選單看不看得到，一般成員組也帶 read；但端點不看它：讀取不檢查，寫入看的是「是不是總部租戶的管理員」。改名後主表對應 route 掛上 `oscal.framework.*`，寫入要不要同時保留 `require_platform_admin` 見定案 1。

## 定案結論

決策者 2026-10-06 逐條裁決。

1. **合規框架寫入掛 `oscal.framework.create／update／delete／import`**（`is_platform = true`），service 層 `require_platform_admin()` 保留，兩道都過。
2. **資源庫不開 import**：檔案新建＝create、匯入既有＝update。
3. **子物件寫入一律歸主資源 update**（資源庫、SSP、框架三處）；A.2 補配時查 DEV 有無「有 create／delete 沒 update」的角色。
4. **資源庫與框架匯出歸 read**，只有 SSP 開 `oscal.ssp.export`。
5. **框架版本下載 OSCAL**：`/token` 掛 `oscal.framework.read`，下載端點只驗憑證。
6. **流程定義端點 `GET /flow-engine/process-definition/<uid>`：不用「擇一放行」。** 先解析這張 BPMN 屬於哪個專案或資源庫：屬專案→檢查 `grc.project.read`＋專案成員；屬資源庫→檢查 `oscal.resource-library.read`。修法寫入 T-4.6。理由：先解析資源再判單一權限才是正統做法，擇一是症狀不是設計（design 通則 C）。
7. **開 `flow.template.publish`**，涵蓋發布與取消發布。
8. **flow 維持獨立模組。** 理由：流程範本是全租戶共用的設計資產，改壞影響之後所有新專案，不該隨 `grc.*.*` 萬用字元被專案主管順帶拿到；矩陣多一個單列分組是小代價。T-1.6 看整體矩陣後若不合再翻，只換前綴。
9. **資源名** `oscal.resource-library`、`oscal.framework`。
10. **資源庫範圍層 tenant。**

流程範本列表「`flow_template.read` 或 `project.create`」維持（建專案選範本情境，design 通則 B）。

## 確認要補的守門缺口

本卡只讀，修正另開卡。

1. **（併入 T-4.6）框架解析單詳情沒有任何檢查**：`GET /oscal-framework-parse-jobs/<uid>`（`framework_parse_job_route.py:127`，service `get_detail` 不呼叫 `require_platform_admin`），同租戶任何登入者給 uid 就能看解析結果。因匯入只限總部租戶，實際暴露面只在總部租戶內。
2. **（併入 T-4.6）資源庫列表沒守 read**：`POST /oscal/resource-libraries/list`（`resource_library_route.py:29`）只驗登入，單筆 `GET /module-frame/<uid>` 卻要 `module-frame.read`。沒有 read 的人看不到單筆、卻列得出全部名稱。
3. **（併入 T-4.6）用檔案新建資源庫的確認端不查能力點**：Excel `framework_version` 目標（`ssp_excel_import_app_service.py:550` `_confirm_create_resource_library`）與 Word 新建且尚未建殼的情況，確認時只比租戶。能力點只在上傳那一步查，同租戶另一個沒有建立權的人拿到 parse_uid 就能代為確認。
4. **（併入 T-4.6）流程定義端點沒有任何檢查**：修法見定案 6。
5. **（記錄，A.2 順手處理）SSP 權限檢查器有兩支沒人用的方法**：`SspPermissionChecker.require_read_access`／`require_write_access`（`common/authz/ssp.py:167-213`，支援範本 SSP 的雙模式）全專案零呼叫，13 支 SSP service 都走只認專案 SSP 的 `require_participant`／`require_manager`。範本 SSP 改走 `/module-frame/<uid>/...` 那組端點，所以沒有破口；但這兩支的 `_require_admin_for_template` 用的是帳號層 `is_admin`（檔內自己標了「既有誤用」），日後有人接上要先改。補守門時順手決定刪掉或接上。
6. **（記錄，A.2 順手處理）資源庫舊匯入三支沒有前端呼叫**：`/module-frame/import/verify`、`/module-frame/import`、`/module-frame/import/yaml`（`module_frame_import_route.py`），FE 常數 `MODULE_FRAME_IMPORT_VERIFY`、`MODULE_FRAME_IMPORT` 無人引用，YAML 匯入連常數都沒有。疑似死碼，退役前先查有沒有外部腳本在打。
7. **（記錄）三個 blueprint 都沒有授權模組軸**：合規框架、資源庫、SSP、流程範本都不檢查 `require_license`。這幾項不在 `INFRASTRUCTURE_MODULES`（`common/authz/license.py`），要不要掛、掛哪個模組由授權那條線決定，本份只記錄。

## 改名時要一起動的地方（給下一棒）

- `core/host_capabilities.py:22-25`（`module-frame`）、`:30-33`（`compliance-framework`）、`:60-63`（`flow_template`）；新增 `oscal.ssp.*` 六個與 `oscal.framework.import`
- `core/role_templates.py:17-18`（一般成員組：`module-frame.read`、`compliance-framework.read`）、`:49`（唯讀檢視者：`flow_template.read`）；**一般成員組要加 `oscal.ssp.read`**（控制項樹被稽核頁共用）
- route decorator：`api/module_frame/routes/` 15 支全部、`api/oscal/routes/resource_library_route.py:43`、`module_frame_template_ssp_route.py:31`、`api/flow_engine/routes/flow_template_route.py:23-32`
- 範圍外放行條件：`module_frame_route.py:26` 與 `flow_template_route.py:24` 的 `project.create` → `grc.project.create`（T-1.1 也列了這兩處）
- service 層字串：`app/oscal/service/ssp_excel_import_app_service.py:814`、`ssp_docx_import_app_service.py:168`、`resource_library_overwrite_guard.py:14,26,29`、`app/module_frame/service/module_frame_import_service.py:143`
- DEV `route_capabilities`：`/compliance-framework/compliance-framework-manage`、`/module-frame/module-frame`、`/flow/template-manage` 三頁的綁定
- FE：`FlowTemplateManageView.vue:27-29`、`FlowTemplateEditorView.vue:50-51` 的 `hasCap('flow_template.*')`；資源庫與框架頁目前不讀 `hasCap`
- `common/authz/menu_license_filter.py:15` 註解提到 `compliance-framework`，改名後同步

沿革見 `FR-132-LOG.md` 與 git log。
