# FR-048 Phase 3 交接 prompt（主體層 decorator・軸①②，可與 2b 並行）

> 下面整段是給執行 session 的 prompt。**前置：Phase 1 已 merge、Phase 2a 矩陣已產出且 user 已定調**。與 Phase 2b 並行安全（不同軸、不同檔案面）。

---

【接手主題】FR-048 統一授權守門 Phase 3 — 建主體層 route decorator（platform-admin / super-admin），照矩陣掛上軸①②端點

必讀（照順序）：
1. `docs/features/FR-048-2607-unified-auth-guard/endpoint-authz-matrix.md`（工作清單＝軸①②且 Phase=3 的列）
2. `docs/analysis/2026-07-07-unified-auth-guard-design.md` §2.0 兩層半模型、§3.1 雙軌定調（P1 已拍板）、§7 Phase 3 注意事項
3. `common/authz/` 現況（Phase 1 產物：platform.py / admin.py）

【branch】fix/v1.8.0-bugs，禁止切 branch。**不動 jedi-* 套件**（decorator 掛主專案 route 即可守住套件 service）。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【第一步：建 decorator（新檔 `common/authz/decorators.py`）】

```python
def require_platform_admin_route(fn):
    """Route decorator：非平台（root tenant）管理員 403。委派 common.authz.platform.require_platform_admin()。
    只判主體、不碰資源、不開 session（純 context 判定）。掛在 @jwt_required() 之後（需 user context 已建）。"""

def require_super_admin_route(fn):
    """Route decorator：非帳號層 super admin 403。委派 common.authz.admin.require_super_admin()。
    【過渡性質】Phase 4 capability 軸就緒後逐端點換 @require_capability——掛此 decorator 的端點必須登記過渡名單。"""
```

- functools.wraps 保留 metadata（flask-apispec/swagger 靠它）。
- 例外直接讓 guard 拋 `ForbiddenError`（jedi_common error handler 會接成標準 envelope 403）；**確認 route 層拋出後 handler 有接到**（decorator 在 MethodResource dispatch 內，錯誤處理路徑要實測一支）。
- 順序慣例定死並寫進 docstring：`@jwt_required()` 在外、authz decorator 在內。

【第二步：照矩陣掛端點】

- **插隊件（最優先，矩陣 D14/D15 已定調）**：
  - `GET /schedule-report/preview`（`api/report/`）**無 JWT 且 query 拼路徑＝任意檔讀取**——補 `@jwt_required()` + `@require_super_admin_route` + path 消毒（禁 `..`/絕對路徑，鎖定合法報表目錄）。改前查 FE 呼叫方式（若 window.open 需一併處理，比照 A 族前科）。
  - `POST /send-user-test-mail`（`api/auth/__init__.py:86`，`user_route.py:159`）**直接移除 route**（debug 殘留，hardcoded email）。
- **首發：SEC-002** — `api/log` export/list 掛 `@require_platform_admin_route`（LOG 能力 2026-06-29 migration 已設 platform-only，正解現成；SEC-001 的 JWT 已由 `182bb2f7` 恢復）。
- 其餘照矩陣：全域資源寫入 → platform；租戶級系統設定/功能（SMTP/LDAP/通知/選單/設備/資訊系統/公告/問卷管理…）→ super-admin **過渡**。
- 角色要求一律照矩陣列，不自行判斷；矩陣標「需 user 定調」的跳過。

【順手項（矩陣 §4 C 類）】全站 22 處 inline `ctx.is_admin` / `getattr(ctx,'is_admin')`（cloud_integration / notify_config / flow_engine stage 等）換成 `common.authz` 的 `require_super_admin()` / `viewer_is_super_admin()`——同為帳號層語意、零行為變更，消滅兩個 is_admin 的語意混用面。逐處換前確認原判定確實是帳號層語意（讀 UserContextDTO 的都是）。

【第三步：過渡名單登記（不做 Phase 4 會變永久過渡）】

掛了 `@require_super_admin_route` 的端點，逐支登記到矩陣「Phase 4 待換能力點」欄（或矩陣檔尾新開一段清單），註明建議的能力點名（`<resource>.<action>` 命名照權限 SOP）。

【驗證】
- 每掛一支補測試：無權帳號 403（含 error code 斷言）、有權帳號照舊。
- 覆蓋盤點：矩陣軸①②列全數掛完後，`grep -rn "require_platform_admin_route\|require_super_admin_route" api/` 對帳矩陣，數量要合。
- `pytest test/` 全綠；請 user 重啟 BE 手測 2-3 支（含一支 403 案例）。
- **FE 影響確認**：403 對 FE 是新行為——確認 FE 對這些頁本來就藏按鈕（route_capabilities 可見性），一般使用者實際上打不到；若有頁面 FE 沒藏（矩陣備註欄查）→ 列出來回報 user，別默默上。

【commit】按模組分批（顯式 add、禁 -am、不 push）：`fix(FR-048): <模組> 掛主體層守門（軸①/②）`；decorator 本體單獨一個 commit。收尾等 user 下令。
