# A.1 盤點：檢測、證據、問卷三模組（加資產的設備）端點 × 三段式能力點

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

## 範圍與數量

- **範圍**：設備（device，歸 `asset`）、遠端代理（remote-agent-manage）、檢測工具外掛與執行（plugin）、檢測基準（detection-profile）、授權掃描網段（scan-zone）、雲端整合（cloud_integration）、AI 證據分類（jedi-evidence-classification）、儲存設定（storage-config）與檔案上傳下載（jedi-file-upload）、問卷（survey）。
- **主專案 route 檔 6 支、17 個 method**：`api/cloud_integration/routes/` 4 支（13）、`api/remote_agent/routes/agent_file_route.py`（1）、`api/system_config/routes/system_config_route.py` 中屬 storage-config 的 2 個 Resource（3；該檔其餘端點屬 T-1.4）。
- **外部套件 route 檔 20 支、150 個 method**：jedi-detection 4 支（46：工具外掛與執行 18、基準 18、分類字典 5、掃描網段 5）、jedi-remote-agent 1 支（19）、jedi-asset 設備 2 支（11）、jedi-evidence-classification 3 支（21）、jedi-file-upload 1 支（6）、jedi-survey 9 支（47：設計 29、填答 18）。讀的是 `.venv/lib/python3.11/site-packages/` 內實際執行的版本（jedi-detection 1.2.7、jedi-remote-agent 1.4.0、jedi-asset 1.2.2、jedi-evidence-classification 1.6.2、jedi-file-upload 1.3.4、jedi-survey 1.2.3）。
- **合計 26 支檔、167 個 method**。泛用系統設定端點 `/system/config/*`（jedi-system-core）雖會依 GROUP 守 `storage-config.*`，屬 T-1.4 範圍，本卡只記它的分流規則不逐支列。
- **DEV 查詢時間**：2026-10-06 17:32（本機 `localhost:5432/guidant_ai_dev`）。範圍內舊能力點 30 個（8 種 resource_type）。

## 先看這幾點

1. **檢測三合一、雲端整合、問卷三個加值包都能各自整包落在一個模組**，對照驗證表在文末。唯一跨界的是 AI 證據分類設定借用了 `storage-config.update`（見第 5 點）。
2. **檢測執行（開始、取消、重跑、刪除紀錄）9 個 method 沒有能力點**，只有授權照＋專案任務操作人檢查。它是專案任務的一部分，建議歸 `grc.job`，不放 detection——放 detection 會讓「買了檢測包、但公司角色沒勾 detection」的稽核員在任務頁按開始掃描時 403。見文末定案結論。
3. **遠端代理管理 15 個 method 全部守同一顆 `remote-agent-manage.create`**，連列表都是。原因是套件只給一個 `admin_required` 插槽、不分讀寫（`core/plugins/remote_agent.py:147-168`）。改名時要麼請套件拆成讀寫兩欄，要麼宿主自掛 route；不拆的話新名四個動詞只有一個有作用。
4. **問卷填答端 18 個 method 有 16 個沒有能力點**，只有授權照＋任務指派人／專案成員檢查；另 2 個（改、刪留言）反而要問卷設計權。定案：不另開 `survey.response`，歸 `grc.job.read／update`（見文末定案 2）。
5. **`storage-config.*` 一顆鑰匙開了五扇門**：儲存設定頁、Google 應用程式憑證頁、AI 服務設定頁、支援頁、AI 證據分類設定（套件寫入端借 `storage-config.update`）。拆不拆、歸哪個模組都見文末定案結論，建議見「storage-config 歸屬建議」。
6. **證據分類套件 21 個 method 沒有任何能力點**，全靠專案成員角色（manager／參與者）；它也沒有授權照守門。

## 新名總覽（定案）

| 新資源 | read | create | update | delete | 獨立動詞 | 取代的舊名 |
|---|---|---|---|---|---|---|
| `asset.device` | ✓ | ✓ | ✓ | ✓ | `import`、`export` | `device.*` |
| `detection.remote-agent` | ✓ | ✓ | ✓ | ✓ | — | `remote-agent-manage.*` |
| `detection.plugin` | ✓ | ✓ | ✓ | ✓ | — | `plugin.*` |
| `detection.profile` | ✓ | ✓ | ✓ | ✓ | — | `detection-profile.*` |
| `detection.scan-zone` | ✓ | ✓ | ✓ | ✓ | — | `scan-zone.read/update` |
| `evidence.cloud-integration` | ✓ | ✓ | ✓ | ✓ | — | `cloud_integration.*` |
| `evidence.classification` | ✓ | ✓ | ✓ | ✓ | — | 無（新開） |
| `system.storage-config` | ✓ | ✓ | ✓ | ✓ | — | `storage-config.*` |
| `evidence.classification-profile` | ✓ | ✓ | ✓ | ✓ | — | 借用 `storage-config.update`（拆出） |
| `ai.ai-provider-config` | ✓ | ✓ | ✓ | ✓ | — | 借用 `storage-config.*`（拆出） |
| `survey.survey` | ✓ | ✓ | ✓ | ✓ | `import`、`publish` | `survey.*` |

- **`detection.profile` 不叫 `detection.detection-profile`**：模組名已是 detection，資源段重複前綴只是噪音。`detection.scan-zone`、`detection.remote-agent` 同理把 `-manage` 拿掉（`manage` 是頁名，不是資源）。
- **`evidence.cloud-integration` 用連字號**：舊名 `cloud_integration` 是底線，三段式規定資源段內用連字號。license 鍵仍是 `cloud_integration`（D10 的 `license_module` 欄存原值），授權照不受影響。
- **`survey.survey`** 兩段同名，但資源段要有值；若 T-1.6 合併時傾向 `survey.questionnaire`，主表照換即可。
- **不開 `survey.response`**：填答端歸 `grc.job.read／update`（通則 A，見文末定案 2）。
- **`detection.scan-zone` 補 create／delete**：今天只有 read／update 兩顆，新增與刪除都掛 update。依「固定四個」規則補齊，route 改守對應動詞。

## 主表

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

守門縮寫：**J**＝`jwt_required`；**L(x)**＝授權照守門 `require_license("x")`；**C(x)**＝能力點守門；**PA**＝平台管理員；**成員：**＝專案層角色檢查（`參與者`＝任何成員）。所有路徑前綴 `/api/1.0`。套件 route 檔的路徑以 `jedi_xxx/api/routes/` 起算。

### 設備（asset.device）

jedi-asset 的 route 守門：`auth_required`（宿主注入的 jwt）＋`capability_required(rt.config.device_*_capability)`，能力點名預設值在 `jedi_asset/plugin/contract.py:77-80`。無授權照守門（設備屬基礎包）。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/devices` | POST | `jedi_asset/api/routes/device_route.py:39` | J＋C(device.read) | `asset.device.read` | `device.read` | `DeviceManage`（`/device/device-manage`） | org_unit | 列表 |
| `/devices/menu` | GET | `device_route.py:59` | J＋C(device.read) | 跟呼叫頁面的權限（通則 B） | `device.read` | `ModuleFrameSspDevicesPanel`、`JobExecutionDrawer`（`useDeviceTargetOptions`） | org_unit | 輔助端點 |
| `/device` | POST | `device_route.py:129` | J＋C(device.create) | `asset.device.create` | `device.create` | `DeviceManage` | org_unit | |
| `/device/<uid>` | GET | `device_route.py:72` | J＋C(device.read) | `asset.device.read` | `device.read` | `DeviceManage` | org_unit | |
| `/device/<uid>` | PUT | `device_route.py:82` | J＋C(device.update) | `asset.device.update` | `device.update` | `DeviceManage` | org_unit | |
| `/device/<uid>` | DELETE | `device_route.py:94` | J＋C(device.delete) | `asset.device.delete` | `device.delete` | `DeviceManage` | org_unit | |
| `/device/<uid>/references` | GET | `device_route.py:111` | J＋C(device.read) | `asset.device.read` | `device.read` | `DeviceManage`（刪除前警示） | org_unit | 輔助端點 |
| `/devices/import-template` | GET | `jedi_asset/api/routes/device_import_route.py:50` | J＋C(device.create) | `asset.device.import` | `device.create` | `DeviceImportDialog` | org_unit | 匯入類獨立動詞 |
| `/devices/import/verify` | POST | `device_import_route.py:65` | J＋C(device.create) | `asset.device.import` | `device.create` | `DeviceImportDialog` | org_unit | |
| `/devices/import/confirm` | POST | `device_import_route.py:82` | J＋C(device.create) | `asset.device.import` | `device.create` | `DeviceImportDialog` | org_unit | FE 該元件另判 `hasCap('device.update')`，與 BE 守的 create 不一致 |
| `/devices/export` | POST | `device_import_route.py:102` | J＋C(device.read) | `asset.device.export` | `device.read` | `DeviceExportMenu` | org_unit | 對外送出類獨立動詞；今天有 read 就能匯出 |

主專案另有兩處拿 `device.read` 判斷：`app/module_frame/service/template_lookup_scope.py:16`（沒有就不給設備 IP）、`di_containers/dashboard_apis/device.py:19`（儀表板資料源）。

### 遠端代理（detection.remote-agent）

套件 `jedi_remote_agent/api/routing.py:15-62` 的路由表第三欄宣告每支的認證等級；管理端點（`AUTH_ADMIN`）由宿主的 `admin_required` 套上，內容是 J＋L(remote-agent-manage)＋C(remote-agent-manage.create)（`core/plugins/remote_agent.py:147-168`）。代理呼入的控制面端點驗代理通行證，不帶使用者身分。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/remote-agents` | GET | `jedi_remote_agent/api/routes/remote_agent_route.py:58` | J＋L(remote-agent-manage)＋C(remote-agent-manage.create) | `detection.remote-agent.read` | `remote-agent-manage.create` | `ToolPluginManage`、`StorageConfigForm`、`ProjectPlanningView`（代理下拉） | tenant | 輔助端點（啟用中的代理下拉）；見下方「跨頁下拉」 |
| `/remote-agents` | POST | `remote_agent_route.py:66` | 同上 | `detection.remote-agent.create` | 同上 | `RemoteAgentManage`（`/system/remote-agent-manage`） | tenant | |
| `/remote-agents/all` | GET | `remote_agent_route.py:90` | 同上 | `detection.remote-agent.read` | 同上 | `RemoteAgentManage` | tenant | 管理頁列表（含停用） |
| `/remote-agents/health-check` | POST | `remote_agent_route.py:75` | 同上 | `detection.remote-agent.read` | 同上 | `RemoteAgentManage` | tenant | 只讀狀態，不改資料 |
| `/remote-agents/<uid>/reconcile` | POST | `remote_agent_route.py:82` | 同上 | `detection.remote-agent.update` | 同上 | `RemoteAgentManage` | tenant | 對帳會改雲端錨點狀態 |
| `/remote-agents/<uid>` | GET | `remote_agent_route.py:98` | 同上 | `detection.remote-agent.read` | 同上 | `RemoteAgentManage` | tenant | |
| `/remote-agents/<uid>` | PUT | `remote_agent_route.py:105` | 同上 | `detection.remote-agent.update` | 同上 | `RemoteAgentManage` | tenant | 含啟用／停用 |
| `/remote-agents/<uid>` | DELETE | `remote_agent_route.py:112` | 同上 | `detection.remote-agent.delete` | 同上 | `RemoteAgentManage` | tenant | |
| `/remote-agents/<uid>/revoke` | POST | `remote_agent_route.py:119` | 同上 | `detection.remote-agent.update` | 同上 | `RemoteAgentManage` | tenant | 狀態轉換但非四類獨立動詞之一 |
| `/remote-agents/<uid>/unrevoke` | POST | `remote_agent_route.py:127` | 同上 | `detection.remote-agent.update` | 同上 | `RemoteAgentManage` | tenant | |
| `/remote-agents/<uid>/approve` | POST | `remote_agent_route.py:135` | 同上 | `detection.remote-agent.update` | 同上 | `RemoteAgentManage` | tenant | 見文末定案結論：核准新報到代理是否開 `approve` 獨立動詞（見文末定案結論） |
| `/remote-agents/<uid>/rebind` | POST | `remote_agent_route.py:144` | 同上 | `detection.remote-agent.update` | 同上 | `RemoteAgentManage` | tenant | |
| `/agents/enroll-token` | GET | `remote_agent_route.py:154` | 同上 | `detection.remote-agent.read` | 同上 | `RemoteAgentManage` | tenant | 只回摘要 |
| `/agents/enroll-token` | POST | `remote_agent_route.py:160` | 同上 | `detection.remote-agent.create` | 同上 | `RemoteAgentManage` | tenant | 產生註冊 token，明文只回一次 |
| `/agents/enroll-token/revoke` | POST | `remote_agent_route.py:168` | 同上 | `detection.remote-agent.delete` | 同上 | `RemoteAgentManage` | tenant | 停用所有註冊 token |
| `/agents/register` | POST | `remote_agent_route.py:177` | 註冊 token（`AUTH_ENROLL`） | 豁免 | 無 | 無 UI（代理呼入） | — | 代理用註冊 token 換通行證，刻意不設使用者守門 |
| `/agents/heartbeat` | POST | `remote_agent_route.py:187` | 代理通行證 | 豁免 | 無 | 無 UI | — | 代理呼入 |
| `/agents/tasks/<uid>/ack` | POST | `remote_agent_route.py:194` | 代理通行證 | 豁免 | 無 | 無 UI | — | 代理呼入 |
| `/agents/tasks/<uid>/result` | POST | `remote_agent_route.py:200` | 代理通行證 | 豁免 | 無 | 無 UI | — | 代理呼入 |
| `/agents/files/<uid>` | GET | 主專案 `api/remote_agent/routes/agent_file_route.py:40` | 代理通行證＋任務綁定驗證 | 豁免 | 無 | 無 UI | — | 宿主自掛（`core/plugins/remote_agent.py:224-233`），代理下載任務源碼包 |

套件路由表 15 條路徑、19 個 method（管理 15、代理呼入 4），加主專案宿主自掛 1 支。

**跨頁下拉**：`GET /remote-agents` 被三個非代理管理頁拿來當下拉（工具外掛設定、儲存設定、專案規劃的檢測任務）。今天要 `remote-agent-manage.create` 才拿得到，等於「會設儲存的人一定要有代理建立權」。新名掛 `detection.remote-agent.read` 後，出貨角色範本要確認儲存設定管理人與專案規劃者都帶這顆 read。

### 檢測工具外掛與執行（detection.plugin）

jedi-detection 所有 route 都由 `jedi_detection/api/routing.py:175-192` 的動態子類套上宿主的 `jwt_required`（`core/plugins/detection.py:388`），再加各自的 L 與 C。能力點名預設在 `jedi_detection/plugin/contract.py:32-54`。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/detection-tools` | GET | `jedi_detection/api/routes/detection_tool_route.py:31` | J＋L(plugin) | `detection.plugin.read` ∣ `grc.job.read` | 無 | `ToolPluginManage`（`/plugin/tool-plugin-manage`）、`DetectionProfileManageView`、`JobExecutionDrawer`（只在任務綁工具時） | tenant | 工具目錄（全域字典、不含憑證） |
| `/detection-tools/<uid>` | GET | `detection_tool_route.py:43` | J＋L(plugin) | `detection.plugin.read` | 無 | 同上 | tenant | |
| `/detection-tools/configs` | GET | `detection_tool_route.py:56` | J＋L(plugin) | `detection.plugin.read` | 無 | `ToolPluginManage`、`ProjectPlanningView` | tenant | 租戶工具設定列表 |
| `/detection-tools/configs` | POST | `detection_tool_route.py:70` | J＋L(plugin)＋C(plugin.update) | `detection.plugin.create` | `plugin.update` | `ToolPluginManage` | tenant | 今天建立守 update |
| `/detection-tools/configs/<uid>` | PUT | `detection_tool_route.py:91` | J＋L(plugin)＋C(plugin.update) | `detection.plugin.update` | `plugin.update` | `ToolPluginManage` | tenant | |
| `/detection-tools/configs/<uid>/reset` | POST | `detection_tool_route.py:108` | J＋L(plugin)＋C(plugin.update) | `detection.plugin.update` | `plugin.update` | `ToolPluginManage` | tenant | 清空憑證 |
| `/detection-tools/configs/<uid>/test-connection` | POST | `detection_tool_route.py:132` | J＋L(plugin)＋C(plugin.update) | `detection.plugin.update` | `plugin.update` | `ToolPluginManage` | tenant | 會解密憑證送往主機，必須維持寫入級 |
| `/detection-tools/configs/<uid>/referencing-tasks-count` | GET | `detection_tool_route.py:152` | J＋L(plugin) | `detection.plugin.read` | 無 | `ToolPluginManage` | tenant | 輔助端點 |
| `/detection-tools/<uid>/param-schema` | GET | `detection_tool_route.py:165` | J＋L(plugin) | `grc.job.read` | 無 | `JobExecutionDrawer` | tenant | 輔助端點；任務設置畫面用 |
| `/detection-tools/jobs/<job_uid>/execute` | POST | `detection_tool_route.py:186` | J＋L(plugin)；成員：任務操作人 | `grc.job.update` | 無 | `JobExecutionDrawer` | org_unit | 見文末定案結論：歸 grc.job（見文末定案結論） |
| `/detection-tools/jobs/<job_uid>/cancel` | POST | `detection_tool_route.py:218` | 同上 | `grc.job.update` | 無 | `JobExecutionDrawer` | org_unit | 同上 |
| `/detection-tools/jobs/<job_uid>/executions` | GET | `detection_tool_route.py:354` | J＋L(plugin)；成員：參與者 | `grc.job.read`｜`grc.job-evidence.read` | 無 | `JobExecutionDrawer` | org_unit | 同上 |
| `/detection-tools/execution-groups/<g>/assignments/<a>/rerun` | POST | `detection_tool_route.py:237` | J＋L(plugin)；成員：任務操作人 | `grc.job.update` | 無 | `JobExecutionDrawer` | org_unit | 同上 |
| `/detection-tools/execution-groups/<g>/assignments/<a>/cancel` | POST | `detection_tool_route.py:258` | 同上 | `grc.job.update` | 無 | `JobExecutionDrawer` | org_unit | 同上 |
| `/detection-tools/execution-groups/<g>/assignments/<a>/start-now` | POST | `detection_tool_route.py:279` | 同上 | `grc.job.update` | 無 | `JobExecutionDrawer` | org_unit | 同上 |
| `/detection-tools/execution-groups/<g>/cancel` | POST | `detection_tool_route.py:299` | 同上 | `grc.job.update` | 無 | `JobExecutionDrawer` | org_unit | 同上 |
| `/detection-tools/execution-groups/<g>` | DELETE | `detection_tool_route.py:335` | 同上 | `grc.job.update` | 無 | `JobExecutionDrawer` | org_unit | 只能刪全失敗的群組；清理執行紀錄，不是刪任務 |
| `/detection-tools/executions/<uid>` | DELETE | `detection_tool_route.py:317` | 同上 | `grc.job.update` | 無 | `JobExecutionDrawer` | org_unit | 同上 |

「任務操作人」＝`assert_job_operator`（`jedi_detection/app/service/detection_orchestration_service.py:209,913,969,1061,1090,1159,1189,1532`），列表走 `assert_project_participant`（`:1736`），實作由宿主的流程執行 service 注入。

### 檢測基準（detection.profile）

守門 `_profile_read`／`_create`／`_update`／`_delete` 定義在 `jedi_detection/api/routes/detection_profile_route.py:58-61`。分類字典 4 個寫入端點走平台管理員（全域字典表）。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/detection-tool-profiles/list` | POST | `detection_profile_route.py:141` | J＋L(detection-profile)＋C(read) | `detection.profile.read` | `detection-profile.read` | `DetectionProfileManageView`（`/plugin/detection-profile-manage`） | tenant | |
| `/detection-tool-profiles/menu` | GET | `detection_profile_route.py:170` | 同上（read） | `detection.profile.read` | `detection-profile.read` | `JobExecutionDrawer`（`useDetectionProfileOptions`） | tenant | 輔助端點 |
| `/detection-tool-profiles` | POST | `detection_profile_route.py:191` | 同上（create） | `detection.profile.create` | `detection-profile.create` | `DetectionProfileManageView`、`ProfileFormDialog` | tenant | |
| `/detection-tool-profiles/<uid>` | GET | `detection_profile_route.py:219` | 同上（read） | `detection.profile.read` | `detection-profile.read` | 同上 | tenant | |
| `/detection-tool-profiles/<uid>` | PUT | `detection_profile_route.py:238` | 同上（update） | `detection.profile.update` | `detection-profile.update` | 同上 | tenant | |
| `/detection-tool-profiles/<uid>` | DELETE | `detection_profile_route.py:266` | 同上（delete） | `detection.profile.delete` | `detection-profile.delete` | 同上 | tenant | |
| `/detection-tool-profiles/<uid>/versions` | GET | `detection_profile_route.py:283` | 同上（read） | `detection.profile.read` | `detection-profile.read` | `ProfileVersionTable` | tenant | |
| `/detection-tool-profiles/<uid>/referencing-usage` | GET | `detection_profile_route.py:313` | 同上（read） | `detection.profile.read` | `detection-profile.read` | `useProfileUsage` | tenant | 輔助端點 |
| `/detection-tool-profiles/<uid>/new-version` | POST | `detection_profile_route.py:421` | 同上（create） | `detection.profile.create` | `detection-profile.create` | `ProfileVersionTable` | tenant | 版更算建立新一版 |
| `/detection-tool-profiles/<uid>/fork` | POST | `detection_profile_route.py:450` | 同上（create） | `detection.profile.create` | `detection-profile.create` | `ProfileCopyDialog` | tenant | 跨資源動作歸被產出的資源 |
| `/detection-tool-profiles/<uid>/copy-to-tool` | POST | `detection_profile_route.py:471` | 同上（create） | `detection.profile.create` | `detection-profile.create` | `ProfileCopyDialog` | tenant | 同上 |
| `/detection-tool-profiles/<uid>/deactivate` | PUT | `detection_profile_route.py:495` | 同上（delete） | `detection.profile.delete` | `detection-profile.delete` | `DetectionProfileManageView` | tenant | 停用守 delete（套件設計：停用即「下架」） |
| `/detection-tool-profile-versions/<uid>/controls` | GET | `detection_profile_route.py:519` | 同上（read） | `detection.profile.read` | `detection-profile.read` | `ProfileVersionTable` | tenant | |
| `/detection-tool-profile-versions/<uid>/extraction` | GET | `detection_profile_route.py:540` | 同上（read） | `detection.profile.read` | `detection-profile.read` | 同上 | tenant | |
| `/detection-tool-profile-versions/<uid>/extraction` | POST | `detection_profile_route.py:555` | 同上（create） | `detection.profile.create` | `detection-profile.create` | 同上 | tenant | 手動重抽 |
| `/detection-tool-profile-versions/<uid>/referencing-usage` | GET | `detection_profile_route.py:339` | 同上（read） | `detection.profile.read` | `detection-profile.read` | `useProfileUsage` | tenant | 輔助端點 |
| `/detection-tool-profile-versions/<uid>/source` | PATCH | `detection_profile_route.py:370` | 同上（create） | `detection.profile.create` | `detection-profile.create` | `ProfileSourceDialog` | tenant | 補來源檔 |
| `/detection-tool-profile-versions/<uid>` | DELETE | `detection_profile_route.py:403` | 同上（delete） | `detection.profile.delete` | `detection-profile.delete` | `ProfileVersionTable` | tenant | |
| `/detection-profile-taxonomies` | GET | `jedi_detection/api/routes/detection_profile_taxonomy_route.py:62` | J＋L(detection-profile) | `detection.profile.read` | 無 | `useProfileTaxonomy`（基準表單下拉） | tenant | 輔助端點 |
| `/detection-profile-taxonomies` | POST | `detection_profile_taxonomy_route.py:84` | J＋L＋PA | 平台層（T-1.5） | 無 | `useProfileTaxonomy` | — | 全域字典，平台管理員維護 |
| `/detection-profile-taxonomies/<uid>` | PUT | `detection_profile_taxonomy_route.py:111` | J＋L＋PA | 平台層 | 無 | 同上 | — | |
| `/detection-profile-taxonomies/<uid>` | DELETE | `detection_profile_taxonomy_route.py:135` | J＋L＋PA | 平台層 | 無 | 同上 | — | |
| `/detection-profile-taxonomies/<uid>/referencing-profiles-count` | GET | `detection_profile_taxonomy_route.py:150` | J＋L(detection-profile) | `detection.profile.read` | 無 | 同上 | tenant | 輔助端點 |

`detection_profile_route.py` 18 個、`detection_profile_taxonomy_route.py` 5 個。另外 `core/plugins/_file_ownership.py:224-225` 用 `detection-profile.read／delete` 判基準來源檔的下載與刪除權，改名要一起動。

### 授權掃描網段（detection.scan-zone）

基礎設施豁免：`scan-zone` 在 `common/authz/license.py:107-121` 的 `INFRASTRUCTURE_MODULES`，授權照守門恆放行。仍要歸模組：它是檢測的安全邊界，只有買了檢測包才有意義，歸 detection。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/scan-zones` | GET | `jedi_detection/api/routes/scan_zone_route.py:38` | J＋L(scan-zone，豁免)＋C(scan-zone.read) | `detection.scan-zone.read` | `scan-zone.read` | `ScanZoneManage`（`/system/scan-zone`） | tenant | |
| `/scan-zones` | POST | `scan_zone_route.py:47` | 同上＋C(scan-zone.update) | `detection.scan-zone.create` | `scan-zone.update` | 同上 | tenant | 補四動詞後改守 create |
| `/scan-zones/<uid>` | PUT | `scan_zone_route.py:60` | 同上＋C(update) | `detection.scan-zone.update` | `scan-zone.update` | 同上 | tenant | 含確認 pending 建議 |
| `/scan-zones/<uid>` | DELETE | `scan_zone_route.py:70` | 同上＋C(update) | `detection.scan-zone.delete` | `scan-zone.update` | 同上 | tenant | 補四動詞後改守 delete |
| `/scan-zones/suggest-from-agents` | POST | `scan_zone_route.py:81` | 同上＋C(update) | `detection.scan-zone.create` | `scan-zone.update` | 同上 | tenant | 產生 pending 建議列 |

FE 常數在 `src/config/scanZone.js`（`SCAN_ZONE_READ`／`SCAN_ZONE_UPDATE`），改名要一起動。

### 雲端整合（evidence.cloud-integration）

主專案 `api/cloud_integration/__init__.py` 掛載，前綴 `/api/1.0`。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/integrations/google-drive` | GET | `api/cloud_integration/routes/google_drive_integration_route.py:56` | J＋L(cloud_integration) | `evidence.cloud-integration.read` | 無 | `GoogleDriveIntegrationCard`（`/settings/cloud-integrations`）、`ProjectListView`、`ProjectPlanningView`、`ProjectAuditorOverview` | tenant | 刻意不掛 read（`:49` 註解：專案頁的一般成員要看連線狀態）；見文末定案結論 |
| `/integrations/google-drive` | DELETE | `google_drive_integration_route.py:71` | J＋L＋C(cloud_integration.delete) | `evidence.cloud-integration.delete` | `cloud_integration.delete` | `GoogleDriveIntegrationCard` | tenant | 中斷連線 |
| `/integrations/google-drive/auth-url` | POST | `google_drive_integration_route.py:90` | J＋L＋C(cloud_integration.create) | `evidence.cloud-integration.create` | `cloud_integration.create` | `GoogleDriveIntegrationCard` | tenant | |
| `/integrations/google-drive/callback`（`DRIVE_CALLBACK_ROUTE_PATH`） | GET | `google_drive_integration_route.py:110` | 無 JWT；OAuth `state`＋瀏覽器 nonce cookie 驗證 | 豁免 | 無 | 無 UI（Google 導回） | — | 外部導回端點，刻意不設使用者守門 |
| `/integrations/google-drive/verify-credentials` | POST | `api/cloud_integration/routes/drive_app_credential_verify_route.py:32` | J；service 層 PA＋`storage-config.read`（`app/cloud_integration/service/drive_app_credential_verify_service.py:74-78`） | 隨 storage-config 新名的 read（見文末定案結論） | `storage-config.read` | `GoogleDriveAppConfigForm`（`/system/drive-app-config`） | tenant | 平台憑證設定頁，見 storage-config 段 |
| `/integrations/google-drive/sync-jobs` | GET | `api/cloud_integration/routes/google_drive_sync_route.py:43` | J＋L＋C(cloud_integration.read) | `evidence.cloud-integration.read` | `cloud_integration.read` | `SyncJobHistoryDialog`、`GoogleDriveIntegrationCard` | tenant | |
| `/integrations/google-drive/sync-jobs/<uid>/retry` | POST | `google_drive_sync_route.py:90` | J＋L＋C(cloud_integration.update) | `evidence.cloud-integration.update` | `cloud_integration.update` | `SyncJobHistoryDialog` | tenant | |
| `/integrations/google-drive/projects/<p>/init-folders` | POST | `google_drive_sync_route.py:111` | J＋L；成員：manager（`app/cloud_integration/service/drive_sync_admin_service.py:136`） | `grc.project.update` | 無 | `ProjectCloudIntegrationsPanel` | org_unit | 專案 manager 觸發，非租戶管理員；見文末定案結論 |
| `/integrations/google-drive/projects/<p>/verify-and-repair-folders` | POST | `google_drive_sync_route.py:143` | J＋L；成員：manager | `grc.project.update` | 無 | `ProjectCloudIntegrationsPanel` | org_unit | 同上 |
| `/integrations/google-drive/projects/<p>/folders` | GET | `google_drive_sync_route.py:177` | J＋L＋C(cloud_integration.read) | `evidence.cloud-integration.read` | `cloud_integration.read` | `ProjectCloudIntegrationsPanel`、`ProjectAuditorOverview` | org_unit | 專案頁用，要求租戶級 read——與上兩支的「專案 manager 即可」不一致，見文末定案結論 |
| `/integrations/google-drive/tenant/rebuild-all` | POST | `google_drive_sync_route.py:209` | J＋L＋C(cloud_integration.update) | `evidence.cloud-integration.update` | `cloud_integration.update` | `GoogleDriveIntegrationCard` | tenant | 破壞性操作 |
| `/integrations/google-drive/webhook/register` | POST | `google_drive_sync_route.py:239` | J＋L＋C(cloud_integration.update) | `evidence.cloud-integration.update` | `cloud_integration.update` | `GoogleDriveIntegrationCard` | tenant | |
| `/webhooks/google-drive/<tenant_id>` | POST | `api/cloud_integration/routes/google_drive_webhook_route.py:39` | 無 JWT；`X-Goog-Channel-ID`＋`X-Goog-Channel-Token` 比對 | 豁免 | 無 | 無 UI（Google 推送） | — | **確認刻意不設守門**：Google 呼入、恆回 200；身分靠 channel token 比對 |

`google_drive_integration_route.py` 4 個、`google_drive_sync_route.py` 7 個、`drive_app_credential_verify_route.py` 1 個、`google_drive_webhook_route.py` 1 個。

**`cloud_integration.update` 不是孤兒**：有 3 支 route 守它（retry、rebuild-all、webhook register），FE `GoogleDriveIntegrationCard.vue:219`、`SyncJobHistoryDialog.vue:18` 也讀它，10 個角色持有。它沒出現在 `/settings/cloud-integrations` 選單綁定（該頁只綁 create／delete／read），所以「選單反查」會把它誤判成沒人用。

### AI 證據分類（evidence.classification）

jedi-evidence-classification 所有 route 由動態子類套上宿主 `jwt_required`（`core/plugins/evidence_classification.py:608`），**沒有授權照守門、沒有能力點**（套件 `CAPABILITIES` 為空，`jedi_evidence_classification/plugin/contract.py:25`）。專案層檢查在 service：`_require_participant`／`_require_manager`（`jedi_evidence_classification/app/service/evidence_batch_service.py:1472-1481`），宿主實作為精確查 `participant.role == "manager"`（`core/plugins/evidence_classification.py:72-114`）。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/evidence-batches` | POST | `jedi_evidence_classification/api/routes/evidence_batch_route.py:51` | J；成員：manager（service `:412`） | `evidence.classification.create` | 無 | `AutoClassifyBatch` | org_unit | 建批次 |
| `/evidence-batches` | GET | `evidence_batch_route.py:75` | J；成員：參與者（`:436,440`） | `evidence.classification.read` | 無 | `AutoClassifyList` | org_unit | |
| `/evidence-batches/<b>` | GET | `evidence_batch_route.py:101` | J；成員：參與者（`:462`） | `evidence.classification.read` | 無 | `AutoClassifyBatch` | org_unit | |
| `/evidence-batches/<b>` | DELETE | `evidence_batch_route.py:115` | J；成員：manager（`:732`） | `evidence.classification.delete` | 無 | `AutoClassifyList` | org_unit | |
| `/evidence-batches/<b>/files` | POST | `evidence_batch_route.py:122` | J；成員：manager（`:513`） | `evidence.classification.update` | 無 | `AutoClassifyBatch` | org_unit | 上傳檔進批次 |
| `/evidence-batches/<b>/files/<f>` | DELETE | `evidence_batch_route.py:137` | J；成員：manager（`:542`） | `evidence.classification.update` | 無 | `AutoClassifyBatch` | org_unit | 從批次移出檔案 |
| `/evidence-batches/<b>/seal` | POST | `evidence_batch_route.py:145` | J；成員：manager（`:552`） | `evidence.classification.update` | 無 | `AutoClassifyBatch` | org_unit | 封存批次 |
| `/evidence-batches/<b>/classify` | POST | `evidence_batch_route.py:153` | J；成員：manager（`:996`） | `evidence.classification.update` | 無 | `AutoClassifyBatch` | org_unit | 送 AI 分類（花額度） |
| `/evidence-batches/<b>/archive` | POST | `evidence_batch_route.py:176` | J；成員：manager（`:577`） | `evidence.classification.update` | 無 | `AutoClassifyBatch` | org_unit | 把分類結果寫進任務佐證 |
| `/evidence-batches/<b>/purge` | POST | `evidence_batch_route.py:183` | J；成員：manager（`:669`） | `evidence.classification.delete` | 無 | `AutoClassifyBatch` | org_unit | 清掉暫存檔 |
| `/classification-run/<r>/state` | GET | `evidence_batch_route.py:197` | J；成員：參與者（`:824`） | `evidence.classification.read` | 無 | `useEvidenceClassification` | org_unit | |
| `/classification-run/<r>/state` | PUT | `evidence_batch_route.py:202` | J；成員：manager（`:839`） | `evidence.classification.update` | 無 | 同上 | org_unit | 人工審閱結果 |
| `/classification-run/<r>/file/<f>/preview` | GET | `evidence_batch_route.py:212` | J；成員：參與者（`:885`） | `evidence.classification.read` | 無 | `EvidenceFilePreviewDialog` | org_unit | |
| `/classification-run/<r>/report/validation` | GET | `evidence_batch_route.py:230` | J；成員：參與者（`:909`） | `evidence.classification.read` | 無 | `useEvidenceClassification` | org_unit | |
| `/classification-run/<r>/report/adjudication` | GET | `evidence_batch_route.py:236` | J；成員：參與者（`:920`） | `evidence.classification.read` | 無 | 同上 | org_unit | |
| `/classification-ground-truth` | POST | `jedi_evidence_classification/api/routes/evidence_classification_route.py:19` | J；任一專案的 manager（`evidence_classification_service.py:58`） | `evidence.classification.update` | 無 | 無 UI（FE 無呼叫） | tenant | 評估用正解匯入，FE 沒有入口；見文末定案結論去留 |
| `/classification-profiles` | GET | `jedi_evidence_classification/api/routes/classification_profile_route.py:51` | J；service：`storage-config.update`（`classification_profile_service.py:165`，宿主 `core/plugins/evidence_classification.py:137`） | `evidence.classification.read` | `storage-config.update` | `AiServiceConfigForm`（`/system/ai-service-config`） | tenant | 讀也要 update——今天的設計 |
| `/classification-profiles` | POST | `classification_profile_route.py:65` | 同上 | `evidence.classification.create` | `storage-config.update` | 同上 | tenant | |
| `/classification-profiles/<p>` | PUT | `classification_profile_route.py:72` | 同上 | `evidence.classification.update` | `storage-config.update` | 同上 | tenant | |
| `/classification-profiles/<p>` | DELETE | `classification_profile_route.py:77` | 同上 | `evidence.classification.delete` | `storage-config.update` | 同上 | tenant | |
| `/classification/available-models` | GET | `classification_profile_route.py:84` | J；無其他檢查（service `:120-126` 只取身分） | `evidence.classification.read` | 無 | `AutoClassifyList`、`AutoClassifyBatch` | tenant | 輔助端點 |

`evidence_batch_route.py` 15 個、`classification_profile_route.py` 5 個、`evidence_classification_route.py` 1 個。

**「分類設定」該不該跟批次同一資源**：批次的寫入看專案成員角色、分類設定看租戶管理員，兩者授權對象不同。建議拆成 `evidence.classification`（批次與結果）與 `evidence.classification-profile`（AI 分類設定），列入見文末定案結論。主表暫填前者。

### 儲存設定與檔案（storage-config）

`storage-config.*` 四顆宣告在 `jedi_file_upload/plugin/contract.py:33-37`。route 不直接守它，而是三條路徑間接用：

1. 泛用系統設定端點（`jedi_system_core`，T-1.4 範圍）依 GROUP 分流：`STORAGE_CONFIG` 與 `AI_PROVIDER_CONFIG` 兩組映到 `storage-config.*`（`core/plugins/system_core.py:64,78`）。
2. 主專案兩支專屬端點（下表）。
3. 套件與 service 層借用：AI 分類設定（上一段）、Google 應用程式憑證驗證（雲端整合段）。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/system/config/storage/restore-default` | GET | `api/system_config/routes/system_config_route.py:119` | J；`assert_config_capability("STORAGE_CONFIG","read")` | `<storage-config>.read` | `storage-config.read` | `StorageConfigForm`（`/system/storage-config`） | tenant | 只回出廠快照有無 |
| `/system/config/storage/restore-default` | POST | `system_config_route.py:127` | J；`assert_config_capability("STORAGE_CONFIG","update")` | `<storage-config>.update` | `storage-config.update` | 同上 | tenant | 恢復內建儲存 |
| `/system/config/ai-provider/model-options` | GET | `system_config_route.py:100` | J；`assert_config_capability("AI_PROVIDER_CONFIG","read")` | 見文末定案結論（AI 服務設定歸屬） | `storage-config.read` | `AiServiceConfigForm` | tenant | AI 服務設定頁借用 storage-config |

jedi-file-upload 的檔案端點（不開能力點，歸屬由檔案登記表決定）：

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/file/upload` | POST | `jedi_file_upload/api/routes/upload_file_route.py:88` | J | 不開能力點 | 無 | 全站上傳元件 | — | 上傳本身不判功能權；檔案掛到哪個功能時由該功能守 |
| `/file/upload/<uid>` | DELETE | `upload_file_route.py:113` | J＋檔案歸屬（寫） | 不開能力點 | 無 | 同上 | — | 歸屬判斷委派各功能（`core/plugins/_file_ownership.py`、`core/plugins/file_upload.py`） |
| `/file/uploads` | POST | `upload_file_route.py:124` | J | 不開能力點 | 無 | 同上 | — | 批次上傳 |
| `/file/access-token/<uid>` | GET | `upload_file_route.py:153` | J＋檔案歸屬（讀） | 不開能力點 | 無 | 全站下載／預覽 | — | 換短期下載憑證 |
| `/file/download/<uid>` | GET | `upload_file_route.py:165` | 短期憑證或 JWT＋檔案歸屬（讀） | 不開能力點 | 無 | 同上 | — | 瀏覽器導頁 |
| `/file/pdf-preview/<uid>` | GET | `upload_file_route.py:200` | 同上 | 不開能力點 | 無 | 同上 | — | |

檔案端點不開能力點的理由：一個檔案屬於哪個功能是登記表查出來的，同一支端點服務十幾種檔案，掛任何單一能力點都會讓其他功能的檔案打不開。權限已由「該檔案所屬功能的原本守門」代判。

### 問卷設計（survey.survey）

jedi-survey 的守門 decorator 是宿主注入的 port（`jedi_survey/api/guards.py`）；授權照 `require_license("survey")` 全端點都有，能力點名預設在 `jedi_survey/plugin/contract.py:32-35,89-91`。**讀取端點全部沒有 `survey.read`**，只有授權照。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/survey-folders/menu` | GET | `jedi_survey/api/routes/survey_folder_route.py:33` | J＋L(survey) | `survey.survey.read` | 無 | `survey-v2`（`/survey/manage`） | org_unit | 輔助端點 |
| `/survey-folders` | POST | `survey_folder_route.py:45` | J＋L | `survey.survey.read` | 無 | 同上 | org_unit | 列表 |
| `/survey-folder/<uid>` | GET | `survey_folder_route.py:60` | J＋L | `survey.survey.read` | 無 | 同上 | org_unit | |
| `/survey-folder` | POST | `survey_folder_route.py:73` | J＋L＋C(survey.create) | `survey.survey.create` | `survey.create` | 同上 | org_unit | 資料夾不另開資源 |
| `/survey-folder/<uid>` | PUT | `survey_folder_route.py:90` | J＋L＋C(survey.update) | `survey.survey.update` | `survey.update` | 同上 | org_unit | |
| `/survey-folder/<uid>` | DELETE | `survey_folder_route.py:109` | J＋L＋C(survey.delete) | `survey.survey.delete` | `survey.delete` | 同上 | org_unit | |
| `/surveys/menu` | GET | `jedi_survey/api/routes/survey_route.py:34` | J＋L | `survey.survey.read` | 無 | `SurveyPickerDialog`（任務設置挑問卷） | org_unit | 輔助端點 |
| `/surveys` | POST | `survey_route.py:59` | J＋L | `survey.survey.read` | 無 | `survey-v2` | org_unit | 列表 |
| `/survey/<uid>` | GET | `survey_route.py:88` | J＋L | `survey.survey.read` | 無 | `survey-v2`、`SurveyPreview`、`AuditControlRef`、`RoundAuditReviewView`、`RoundPoamView` | org_unit | 稽核頁也讀問卷本體 |
| `/survey` | POST | `survey_route.py:105` | J＋L＋C(create) | `survey.survey.create` | `survey.create` | `survey-v2` | org_unit | |
| `/survey/<uid>` | PUT | `survey_route.py:133` | J＋L＋C(update) | `survey.survey.update` | `survey.update` | 同上 | org_unit | |
| `/survey/<uid>` | DELETE | `survey_route.py:156` | J＋L＋C(delete) | `survey.survey.delete` | `survey.delete` | 同上 | org_unit | |
| `/survey/excel-import` | POST | `survey_route.py:175` | J＋L＋C(create) | `survey.survey.import` | `survey.create` | `BatchImportDialog` | org_unit | 匯入類獨立動詞 |
| `/survey/import-template/token` | GET | `survey_route.py:201` | J＋L | `survey.survey.import` | 無 | `signedDownloadUtil` | org_unit | 換範本下載憑證 |
| `/survey/import-template` | GET | `survey_route.py:209` | 短期憑證（`signed_token_required`） | 豁免（憑證端點） | 無 | 同上 | — | 憑證由上一支發 |
| `/survey/move` | POST | `survey_route.py:221` | J＋L＋C(create) | `survey.survey.update` | `survey.create` | `survey-v2` | org_unit | 移資料夾是改屬性，今天守 create |
| `/survey/<uid>/clone` | POST | `survey_route.py:242` | J＋L＋C(create) | `survey.survey.create` | `survey.create` | 同上 | org_unit | |
| `/survey/<uid>/release` | PUT | `survey_route.py:265` | J＋L＋C(update) | `survey.survey.publish` | `survey.update` | 同上 | org_unit | 發布＝狀態轉換獨立動詞 |
| `/survey/<uid>/release` | DELETE | `survey_route.py:279` | J＋L＋C(delete) | `survey.survey.publish` | `survey.delete` | 同上 | org_unit | 取消發布；今天守 delete |
| `/survey-question` | POST | `jedi_survey/api/routes/survey_question_route.py:33` | J＋L＋C(create) | `survey.survey.update` | `survey.create` | `survey-v2` | org_unit | 題目、頁面是問卷內容，改它們＝改問卷 |
| `/survey-question/<uid>` | PUT | `survey_question_route.py:62` | J＋L＋C(update) | `survey.survey.update` | `survey.update` | 同上 | org_unit | |
| `/survey-question/<uid>` | DELETE | `survey_question_route.py:98` | J＋L＋C(delete) | `survey.survey.update` | `survey.delete` | 同上 | org_unit | |
| `/survey-questions/reorder` | POST | `survey_question_route.py:113` | J＋L＋C(create) | `survey.survey.update` | `survey.create` | 同上 | org_unit | |
| `/survey-question/move` | POST | `survey_question_route.py:134` | J＋L＋C(create) | `survey.survey.update` | `survey.create` | 同上 | org_unit | |
| `/survey-page` | POST | `jedi_survey/api/routes/survey_page_route.py:33` | J＋L＋C(create) | `survey.survey.update` | `survey.create` | 同上 | org_unit | |
| `/survey-page/<uid>` | PUT | `survey_page_route.py:61` | J＋L＋C(update) | `survey.survey.update` | `survey.update` | 同上 | org_unit | |
| `/survey-page/<uid>` | DELETE | `survey_page_route.py:88` | J＋L＋C(delete) | `survey.survey.update` | `survey.delete` | 同上 | org_unit | |
| `/survey-pages/reorder` | POST | `survey_page_route.py:102` | J＋L＋C(create) | `survey.survey.update` | `survey.create` | 同上 | org_unit | |
| `/survey-page/move` | POST | `survey_page_route.py:123` | J＋L＋C(create) | `survey.survey.update` | `survey.create` | 同上 | org_unit | |

題目與頁面的增刪歸問卷 `update` 是依「其他動作歸 update」規則：使用者在問卷編輯器裡加一題，語意是「編輯這份問卷」，不是「建立一個題目資源」。今天的守法（加題守 create、刪題守 delete）讓「能改問卷但不能刪問卷」的角色連題目都刪不掉，改名時順手校正。

### 問卷填答與任務問卷（歸 grc.job）

填答端（`mount_answer_routes`，`jedi_survey/api/routing.py:73-109`）全部 J＋L(survey)，**沒有能力點**。專案層檢查在 service：`assert_task_survey_reader`（專案參與者）、`assert_task_survey_writer`（被指派人本人或專案 manager）、`assert_task_survey_manager`（專案 manager），定義在 `jedi_survey/app/common/task_survey_guard.py`。

| route 路徑 | HTTP | 程式位置 | 現有守門 | 新三段名 | 現有能力點 | 對應 UI | 範圍層 | 備註 |
|---|---|---|---|---|---|---|---|---|
| `/task-surveys` | POST | `jedi_survey/api/routes/task_survey_route.py:28` | J＋L；成員：參與者（`task_survey_service.py:79`） | `grc.job.read`｜`grc.job-evidence.read`（後者限已完成問卷） | 無 | `JobExecutionDrawer` | org_unit | 列表（必帶 task） |
| `/task-survey/<uid>` | GET | `task_survey_route.py:40` | J＋L；成員：參與者（`:94`） | `grc.job.read`｜`grc.job-evidence.read`（後者限已完成問卷） | 無 | `SurveyPreview` | org_unit | |
| `/task-survey/<uid>` | PUT | `task_survey_route.py:49` | J＋L；成員：manager（`:112`） | `grc.job.update` | 無 | `JobExecutionDrawer` | org_unit | 管理者改任務問卷狀態（含審核轉換）；見文末定案結論 |
| `/task-survey/configured/<uid>` | PUT | `task_survey_route.py:155` | J＋L；成員：manager（`:613`） | `grc.job.update` | 無 | `JobExecutionDrawer` | org_unit | 設定任務要用哪份問卷、要不要審核；屬任務設置 |
| `/task-survey/import-template/<task_uid>` | GET | `task_survey_route.py:65` | J＋L；**無成員檢查** | `grc.job.read` | 無 | `SurveyPreview` | org_unit | **守門缺口**：service `generate_task_survey_question_import_excel_template_data`（`task_survey_service.py:206`）不驗身分，見缺口清單 |
| `/task-survey/import-answers` | POST | `task_survey_route.py:134` | J＋L；成員：manager（`:296`） | `grc.job.update` | 無 | `SurveyPreview` | org_unit | Excel 批次匯入答案；填答端一律歸 grc.job.update，不開 import |
| `/project-survey/answers/<uid>` | GET | `jedi_survey/api/routes/question_answer_route.py:30` | J＋L；成員：參與者（`question_answer_service.py:102`） | `grc.job.read`｜`grc.job-evidence.read`（後者限已完成問卷） | 無 | `SurveyPreview` | org_unit | |
| `/project-survey/answers/<uid>` | PUT | `question_answer_route.py:40` | J＋L；成員：被指派人或 manager（`:120`） | `grc.job.update` | 無 | 同上 | org_unit | |
| `/project-survey/answers/<uid>/patch` | POST | `question_answer_route.py:59` | 同上（`:255`） | `grc.job.update` | 無 | 同上 | org_unit | |
| `/project-survey/answers/<uid>/checkpoint` | POST | `question_answer_route.py:89` | 同上（`:407`） | `grc.job.update` | 無 | 同上 | org_unit | 暫存／提交 |
| `/project-survey/histories/menu/<uid>` | GET | `jedi_survey/api/routes/question_answer_history_route.py:24` | J＋L；成員：參與者（`question_answer_history_service.py:58`） | `grc.job.read`｜`grc.job-evidence.read`（後者限已完成問卷） | 無 | `SurveyPreview` | org_unit | 輔助端點 |
| `/project-survey/histories` | POST | `question_answer_history_route.py:35` | 同上（`:71`） | `grc.job.read`｜`grc.job-evidence.read`（後者限已完成問卷） | 無 | 同上 | org_unit | |
| `/project-survey/history/revert` | POST | `question_answer_history_route.py:47` | J＋L；成員：被指派人或 manager（`:82`） | `grc.job.update` | 無 | 同上 | org_unit | |
| `/project-survey/history-details` | POST | `jedi_survey/api/routes/question_answer_history_detail_route.py:20` | J＋L；成員：參與者（`question_answer_history_detail_service.py:35`） | `grc.job.read`｜`grc.job-evidence.read`（後者限已完成問卷） | 無 | 同上 | org_unit | |
| `/survey-discussions` | POST | `jedi_survey/api/routes/survey_discussion_route.py:25` | J＋L；成員：參與者（`survey_discussion_service.py:47`） | `grc.job.read`｜`grc.job-evidence.read`（後者限已完成問卷） | 無 | `SurveyPreview` | org_unit | 列表 |
| `/survey-discussion` | POST | `survey_discussion_route.py:43` | J＋L；**無成員檢查** | `grc.job.update` | 無 | 同上 | org_unit | **守門缺口**：service `add_discussion`（`survey_discussion_service.py:69`）不驗身分，見缺口清單 |
| `/survey-discussion/<uid>` | PUT | `survey_discussion_route.py:65` | J＋L＋C(survey.update)；作者本人（`jedi_survey/app/service/survey_discussion.py:54`） | `grc.job.update` | `survey.update` | 同上 | org_unit | 改自己的留言卻要問卷設計權 |
| `/survey-discussion/<uid>` | DELETE | `survey_discussion_route.py:85` | J＋L＋C(survey.delete)；作者本人（`:66`） | `grc.job.update` | `survey.delete` | 同上 | org_unit | 同上 |

討論串放在填答這一組：它掛在任務問卷（`ref_id=task_survey_uid`）上，是受訪者與審核者的對話，不是問卷設計。今天改／刪留言要 `survey.update／delete`，等於只會填問卷的人留了言就改不掉。


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

| 舊名 | 主表有 route 守它？ | 新名 | 其他引用處 |
|---|---|---|---|
| `device.create` | 有：建立、匯入三支 | `asset.device.create`／`.import` | 套件 `jedi_asset/plugin/contract.py`；綁 `/device/device-manage`；FE `DeviceManage.vue:69` |
| `device.read` | 有：列表、明細、選單、引用數、匯出 | `asset.device.read`／`.export` | 角色範本 `core/role_templates.py:14`；`app/module_frame/service/template_lookup_scope.py:16`；`di_containers/dashboard_apis/device.py:19`；FE `DeviceManage.vue:70` |
| `device.update` | 有：PUT | `asset.device.update` | FE `DeviceImportDialog.vue:34`（判斷匯入按鈕，與 BE 守 create 不一致） |
| `device.delete` | 有：DELETE | `asset.device.delete` | FE `DeviceManage.vue:68` |
| `remote-agent-manage.create` | 有：15 個管理 method 全守它 | `detection.remote-agent.*`（依主表拆） | `core/plugins/remote_agent.py:167` |
| `remote-agent-manage.read／update／delete` | **無** | `detection.remote-agent.read／update／delete` | 只綁 `/system/remote-agent-manage` 選單；11 個角色持有 |
| `plugin.update` | 有：工具設定建立、修改、重置、測試連線 | `detection.plugin.create／update` | 套件 `jedi_detection/plugin/contract.py:48` |
| `plugin.create／read／delete` | **無** | `detection.plugin.create／read／delete` | 只綁 `/plugin/tool-plugin-manage` 選單；10 個角色持有 |
| `detection-profile.create／read／update／delete` | 有：基準 18 支 | `detection.profile.*` | 套件 contract `:49-52`；角色範本 `:16`（read）；`core/plugins/_file_ownership.py:224-225`；FE `DetectionProfileManageView.vue:69-71`、`ProfileVersionTable.vue:227-228` |
| `scan-zone.read` | 有：GET | `detection.scan-zone.read` | 套件 contract `:53`；FE `config/scanZone.js` |
| `scan-zone.update` | 有：新增、修改、刪除、建議 | `detection.scan-zone.create／update／delete` | 套件 contract `:54`；FE `config/scanZone.js` |
| `cloud_integration.create` | 有：auth-url | `evidence.cloud-integration.create` | `core/host_capabilities.py:38`；FE `GoogleDriveIntegrationCard.vue:218` |
| `cloud_integration.read` | 有：sync-jobs、專案資料夾 | `evidence.cloud-integration.read` | `core/host_capabilities.py:40`；FE 同檔 `:222` |
| `cloud_integration.update` | 有：retry、rebuild-all、webhook register | `evidence.cloud-integration.update` | `core/host_capabilities.py:41`；FE 同檔 `:219`、`SyncJobHistoryDialog.vue:18`；**不在選單綁定**（見雲端整合段） |
| `cloud_integration.delete` | 有：中斷連線 | `evidence.cloud-integration.delete` | `core/host_capabilities.py:39`；FE 同檔 `:220` |
| `storage-config.read` | 有：restore-default GET、model-options、Drive 憑證驗證（service） | 見文末定案結論 | `jedi_file_upload/plugin/contract.py:35`；泛用設定 GROUP 分流 `core/plugins/system_core.py:64,78`；綁 `/system/storage-config`、`/system/drive-app-config`、`/system/ai-service-config`、`/system/support` |
| `storage-config.update` | 有：restore-default POST；AI 分類設定 4 支（service） | 見文末定案結論 | `core/plugins/evidence_classification.py:137`；同上四頁選單 |
| `storage-config.create／delete` | **只經泛用設定端點**（T-1.4 範圍） | 見文末定案結論 | 只綁 `/system/storage-config`；11 個角色持有 |
| `survey.create` | 有：建問卷、資料夾、匯入、搬移、複製、題目與頁面的新增／排序／搬移 | `survey.survey.create／update／import` | 套件 contract `:32`；`common/authz/license.py:44`（範例註解） |
| `survey.read` | **無** | `survey.survey.read` | 綁 `/survey/manage`；角色範本 `:48`；`di_containers/dashboard_apis/survey.py:30,44`；12 個角色持有 |
| `survey.update` | 有：改問卷、資料夾、題目、頁面、發布、改留言 | `survey.survey.update／publish`、`grc.job.update` | 套件 contract `:34` |
| `survey.delete` | 有：刪問卷、資料夾、題目、頁面、取消發布、刪留言 | `survey.survey.delete／update／publish`、`grc.job.update` | 套件 contract `:35` |

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

`remote-agent-manage.read／update／delete`（3）、`plugin.create／read／delete`（3）、`survey.read`（1），共 7 個。全都綁在選單上、角色也持有，**孤兒不等於沒用**：今天它們決定「看不看得到那一頁」，改名後由主表對應 route 掛上。`storage-config.create／delete` 只被泛用設定端點（T-1.4）間接守，不算孤兒。

卡片提到的孤兒 `cloud_integration.update`：**不是孤兒**，3 支 route 守它，只是沒綁選單。

## 加值包 → 模組 對照驗證

| 加值包（`license_module`） | 內含舊 resource_type | 新資源 | 落在模組 | 整包同一模組？ |
|---|---|---|---|---|
| 檢測工具包 | `remote-agent-manage`、`detection-profile`、`plugin` | `detection.remote-agent`、`detection.profile`、`detection.plugin` | detection | ✓ |
| 雲端整合包 | `cloud_integration` | `evidence.cloud-integration` | evidence | ✓ |
| 問卷包 | `survey` | `survey.survey` | survey | ✓ |
| 基礎設施豁免 | `scan-zone` | `detection.scan-zone` | detection | 不在照上；歸 detection 不影響授權判定 |
| 基礎包 | `device` | `asset.device` | asset | 與 `asset.information-system`（T-1.1）同模組 ✓ |
| 基礎包 | `storage-config` | 見文末定案結論 | evidence 或 system | 不是加值包，歸哪都不違反硬規則 |
| （無照鍵） | 證據分類（今天無 resource_type） | `evidence.classification` | evidence | 見文末定案結論：新資源的 `license_module` 填什麼（見文末定案結論） |

注意兩處會讓加值包「看起來」跨界：

1. **檢測執行 9 支若照套件歸屬放 detection**，`grc.job` 的任務操作就有一部分在 detection 模組下，客戶買檢測包後還要另外勾 detection 才能在任務頁跑掃描。建議歸 `grc.job`，授權照守門 L(plugin) 照留——授權照與能力點是兩層，任務端點受檢測包管制不需要它的能力點也在 detection。
2. **填答端不另開能力點**：歸 `grc.job.read／update`；授權照 L(survey) 照留，問卷包的能力點仍整包在 survey（`survey.survey`）。

## storage-config 歸屬建議

**建議歸 `system`，名字 `system.storage-config.*`；同時把借用它的兩頁拆出去。**

- **歸 system 的理由**：它設定的是「檔案存到哪台物件儲存、帳密是什麼」，與 SMTP、LDAP 同屬機房設定。客戶授權時會把它跟其他系統設定一起交給 IT，不會跟「證據審閱」交給同一個人。證據模組管的是證據內容，不是儲存媒介；歸 evidence 會讓「能審閱證據分類的人」跟「能改儲存帳密的人」在矩陣上靠在一起，容易誤勾。
- **今天一顆鑰匙開五扇門**：儲存設定、Google 應用程式憑證（平台層）、AI 服務設定、支援頁、AI 證據分類設定。理由是「不新開一顆、省得補 seed 給每個租戶」（`core/plugins/evidence_classification.py:126-129`、`common/constant/ai_service_config.py:11`）。FR-132 本來就要整批改名與補 seed，正是拆開的時機：
  - AI 服務設定（金鑰、模型）＋ AI 證據分類設定 → 另開資源，歸屬與 T-1.5 的 `ai-quota` 一起裁（同屬「AI 怎麼用」）。
  - Google 應用程式憑證 → 平台層（T-1.5），今天就已要求平台管理員。
  - 支援頁 `/system/support` → 交 T-1.4 確認它實際讀什麼，再決定。
- **反悔條件**：若決策者希望「證據」模組涵蓋所有跟證據檔有關的設定（讓客戶一次授權給文管人員），改成 `evidence.storage-config`，主表只換前綴。

## D12 觀察：候選隔離表有沒有子表 API 繞過父表

D12 候選表本卡相關四張：`public.devices`、`compliance.evidence_batches`、`compliance.detection_executions`、`survey.surveys`。DEV 實查各表都開 RLS；下表看「有 API 直接列子表」時，子表的 RLS 是否也跟著父表收緊。

| 父表 | 子表 | 子表 RLS 形狀（DEV） | 有 API 直接讀子表嗎 | 觀察 |
|---|---|---|---|---|
| `public.devices`（有 `org_unit_id`） | 無直接子表 | — | — | 單表，加部門隔離只動它 |
| `compliance.evidence_batches`（有 `org_unit_id`） | `evidence_batch_files`、`evidence_classification_runs` | **各自獨立用 `tenant_id` 判**，不透過父表（三表 policy 都是 `app_tenant_allowed_for_session(tenant_id)`） | 有：`/evidence-batches/<b>/files`（寫）、`/classification-run/<r>/*`（以 run uid 定址，5 支） | **會繞過**：父表加部門隔離後，拿得到 run uid 的人仍能從 `/classification-run/<r>/state`、`/file/<f>/preview` 讀到子表內容。service 層有專案成員檢查擋著，但 RLS 層要補——子表要嘛自己加 `org_unit_id` 條件（兩張子表都有 `org_unit_id` 欄），要嘛改成 `EXISTS (父表)` |
| `compliance.detection_executions`（有 `org_unit_id`） | `detection_execution_groups`（父子關係反過來：群組是上層） | 兩表各自 `tenant_id` 判 | 有：`/detection-tools/execution-groups/<g>/*`（5 支）以群組 uid 定址 | 同上，群組表也要一起加；兩表都有 `org_unit_id` |
| `survey.surveys`（有 `org_unit_id`） | `survey_pages`、`survey_questions`、`survey_discussions`、`question_answers` | **透過父表 `EXISTS`**（pages → surveys、questions → pages、discussions → surveys、answers → questions → pages） | 有：題目、頁面、討論、答案端點都直接以子表 uid 定址 | **不會繞過**：子表 policy 鏈到 `surveys`，父表加條件自動傳下去 |
| 同上 | `task_surveys`、`question_answer_histories` | `task_surveys` 鏈到 `compliance.workflow_executions`（不是 `surveys`） | 有：`/task-survey/*`、`/project-survey/*` | 任務問卷跟著**專案**隔離，不跟著問卷本體。若 D12 要「看不到別部門的問卷」，這條線不受影響是對的（受訪者要能填別部門出的問卷）；若要「看不到別部門的填答」，要動的是 `workflow_executions` 那條 |

## 定案結論

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

1. **檢測執行 9 支歸 `grc.job`**；開始、取消、重跑歸 `update`，不算 assign。授權照 L(plugin) 照留。
2. **不開 `survey.response`。** 填答端 API 歸 `grc.job.read／update`，授權照 L(survey) 照留；兩支守錯的（改、刪自己留言要求問卷設計權）改 `grc.job.update`。`survey.survey` 只剩問卷設計那組。理由：填問卷的人是任務指派人、在任務頁填，身分由任務決定；授權照與能力點是兩層，硬規則管的是能力點模組，填答不需要自己的能力點，故不違反。
3. **任務問卷設定歸 `grc.job.update`。**
4. **遠端代理核准歸 `update`，不開 approve。**
5. **拆 jedi-remote-agent 套件守門插槽為讀／寫兩個**，宿主 `core/plugins/remote_agent.py` 塞兩顆（讀 `detection.remote-agent.read`、寫對應動詞）；落點 T-2.1 順帶，套件發版等令。決策者裁示：FR-132 屬整體大改，jedi-* 該動就動、不逐條問。
6. **AI 證據分類拆兩個資源**：`evidence.classification`（批次與結果）與 `evidence.classification-profile`（分類設定）；`license_module` 填 `evidence-classification`，視為基礎包。
7. **storage-config 歸屬**：儲存設定 → `system.storage-config.*`；AI 服務設定＋AI 分類設定 → `ai.ai-provider-config.*`；Drive 應用程式憑證 → platform 層；支援頁 → `platform.support.*`（已隨 T-1.4／T-1.5 裁）。
8. **雲端整合三支專案端點歸 `grc.project`**（folders 歸 read、init／repair 歸 update），L(cloud_integration) 照留。
9. **`GET /integrations/google-drive` 掛 `evidence.cloud-integration.read`**，出貨範本一般成員組要帶這顆。
10. **`/classification-ground-truth` 保留**，掛 `evidence.classification.update`（灌人工標註答案給準確率報表用，原廠／顧問導入時用；runbook `docs/api/evidence-classification/ground-truth-operations.md`）。
11. **題目與頁面增刪改歸 `survey.survey.update`。**
12. **取消發布歸 `survey.survey.publish`。**

另：設備「選單」類端點（`/devices/menu` 等被專案／SSP 頁拿來挑選的）新名是「跟呼叫頁面的權限」，不是 `asset.device.read`（design 通則 B）。

## 確認要補的守門缺口

本卡只讀，修正由首腦另開卡。

1. **（併入 T-4.6）`GET /task-survey/import-template/<task_uid>` 不驗身分**：service `generate_task_survey_question_import_excel_template_data`（`jedi_survey/app/service/task_survey_service.py:206`）直接以 task uid 查任務問卷產 Excel，知道 task uid 的登入者（同租戶，RLS 只擋跨租戶）就能拿到題目內容。比照同檔其他讀取端點補 `assert_task_survey_reader`。
2. **（併入 T-4.6）`POST /survey-discussion` 不驗身分**：service `add_discussion`（`jedi_survey/app/service/survey_discussion_service.py:69`）沒有呼叫任何守門，同租戶任何登入者可在任一任務問卷留言。比照同檔 `get_survey_discussions`（`:47`）補 `assert_task_survey_reader`。
3. **（併入 T-4.6）`GET /classification/available-models` 只驗身分**：回本租戶有哪些 AI 供應商已設金鑰；定案掛 `ai.ai-provider-config.read`。
4. **（記錄不併）證據分類套件全系列沒有授權照守門**：若 `evidence.classification` 未來要收進某加值包，宿主要先補 L(x)，否則買不買都能用。

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

- 套件 contract 預設名：`jedi_asset/plugin/contract.py:77-80`（device）、`jedi_detection/plugin/contract.py:32-54`（plugin、detection-profile、scan-zone）、`jedi_survey/plugin/contract.py:32-35,89-91`、`jedi_file_upload/plugin/contract.py:33-37`（storage-config）
- 宿主寫死的能力點：`core/plugins/remote_agent.py:167`、`core/plugins/evidence_classification.py:137`、`core/host_capabilities.py:38-41`、`app/cloud_integration/service/drive_app_credential_verify_service.py:46`、`app/module_frame/service/template_lookup_scope.py:16`、`core/plugins/_file_ownership.py:224-225`、`core/plugins/system_core.py:64,78`（GROUP 分流表）
- route 層字面量：`api/cloud_integration/routes/google_drive_integration_route.py:69,88`、`google_drive_sync_route.py:41,88,175,207,237`
- 角色範本：`core/role_templates.py:14,16,48`
- 儀表板資料源：`di_containers/dashboard_apis/device.py:19`、`survey.py:30,44`
- DEV `route_capabilities`：`/device/device-manage`、`/plugin/detection-profile-manage`、`/plugin/tool-plugin-manage`、`/settings/cloud-integrations`、`/survey/manage`、`/system/remote-agent-manage`、`/system/scan-zone`、`/system/storage-config`、`/system/drive-app-config`、`/system/ai-service-config`、`/system/support`
- FE：`views/device/DeviceManage.vue:68-70`、`DeviceImportDialog.vue:34`、`config/scanZone.js`、`views/detection-profile/DetectionProfileManageView.vue:69-71`、`components/ProfileVersionTable.vue:227-228`、`components/integrations/GoogleDriveIntegrationCard.vue:218-222`、`SyncJobHistoryDialog.vue:18`

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