FR-048 Phase 2b-2 交接 prompt(2b 剩餘 + 跨 phase 縫隙收口)

下面整段是給執行 session 的 prompt。前置:Phase 2b 第一波已收(80514dff 等 4 commits)。 本波收完才輪 Phase 2c(收編)。


【接手主題】FR-048 Phase 2b-2 — 完成 2b 交棒單剩餘項(flow_engine 守門復活、participant canonical、4 組端點)+ 2 個跨 phase 縫隙(MF 敏感 GET、D14 補 JWT 批)

必讀:

  1. docs/features/FR-048-2607-unified-auth-guard/endpoint-authz-matrix.md §7.2(2b 交棒單)+ §1 D14 定調 + §3 各模組矩陣
  2. docs/analysis/2026-07-07-unified-auth-guard-design.md §2 軸模型
  3. common/authz/ 現況(project.py / workflow.py / decorators.py)

【branch】fix/v1.8.0-bugs,禁止切 branch。不動 jedi-* 套件。守門一律 common.authz canonical。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 【工作清單(建議照序)】

1. common/authz/project.pyassert_project_participant(前置 canonical)

「任一參與者放行」版(軸③,與 assert_project_manager 同族):participant 為 None → ForbiddenError(GRC_NOT_PROJECT_PARTICIPANT)。查法用 project 層 participant 精確查詢或 role_service(跟 assert_project_manager 同查法即可,manager/auditor/reviewer/viewer 任一角色皆過)。登進 __init__.py 決策表。

2. 掛 4 組「參與者級」端點(矩陣 §7.2)

端點 守門 備註
grc /jobs/export(GET,Excel) 參與者 project 維度從 request 取,resolve 後 assert_project_participant
grc job-execution comments POST 參與者 留言=協作行為;PUT/DELETE 已有 owner 守門不動
project_summary_report create/update/delete/export/history-revert 參與者 spec 明載無角色細分=驗「專案歸屬」;update/delete/export 依 uid,需 domain service 補 report→project 反查(先 grep 有沒有既有反查,禁重複造輪)
subtask_status_history POST/PUT/DELETE 參與者 同上,subtask→project 反查

3. evidence_classification 四支 → 統一軸③ manager(2026-07-07 user 定調)

  • run/statearchive:由 run/folder resolve 專案 → assert_project_manager(與已守的 trigger-classify 同軸同級)。
  • ground-truth importPOST /classification-ground-truth):API 無專案維度(per-tenant),user 定調統一 manager → 實作為「該租戶內至少任一專案的 manager」判定(查 participant 表 role=manager 存在即過;RLS 已鎖租戶範圍)。加 docstring 說明此語意;不改 API 契約。此端點無 FE 頁面(維運 runbook 用),收緊零影響。

4. flow_engine 守門復活(壓軸,量大)

common/authz/workflow.py assert_workflow_project_participant 開頭 return 拔掉,反查鏈改 v2:

  • v2 鏈:workflow_execution → task_assignees.project_id(memory 定調:別比對 workflow_execution_uid;v1 的 AP-task mapping 已 dark 不能用)
  • 判定政策不變:任一參與者放行、fail-closed(查無 mapping 一律拒)、admin 放行、無 context(背景任務)不守
  • 受守端點:task/complete、task/revert、job-evidence PUT/DELETE、加掛裸奔的 job-evidences POST/job-evidence POST、process/comments PUT、task/link PUT
  • grc jobs/batch-complete:每 job 走 flow_engine complete_job 隨之受守;其自身入口再補 project 反查(job_uids 可跨專案→逐 job 驗)
  • 改完必補測試(fail-closed、參與者過、非參與者 403、admin 過);這支曾因 v1 鏈斷把正當使用者全擋(FR-038 教訓),復活前先用 DEV DB 實資料驗反查鏈走得通

5. MF 敏感 GET 掛軸②(P3 縫,D1 定調涵蓋)

api/module_frame/ 下含實際資料的 GET 補 @require_super_admin_route(順序:jwt 外、authz 內):template export/download(module_frame_template_import_route.py:66/94)、control-defaults GET list/detail(module_frame_control_default_route.py:50/75)、excel-template / ssp-import-template / ssp-export 類(見矩陣 3.2)。menu/list/detail 一般讀不掛。掛完登記過渡名單(併入 module_frame.manage)。

6. D14 補 JWT 批(無人認領件收口)

端點 處置
/module-frame/download/template/download/yaml-templatemodule_frame_import_route.py:27/38 @jwt_required()(靜態範例檔,補認證即可)
/survey/import-template 同上
/issue/get_members @jwt_required()
/task-survey/import-template/<task_uid> 先查 FE 呼叫方式:axios(interceptor 帶 bearer)→ 補 JWT;<a href>/window.open → 不能盲加,改列 Phase 4 signed-token(矩陣註記)。有 ffa01a78 前科,不確定就查 FE source

7. 收尾

  • 矩陣逐列銷帳(§7.2 各列 → 已修待驗+commit hash;§7.3 新開 2b-2 落地紀錄)
  • 回歸驗證:baseline worktree 集合 diff(在本波起點 commit 開 worktree 跑 python -m pytest test/ -q --continue-on-collection-errors,comm 比對 FAILED/ERROR 清單,新增失敗必須 0)
  • commit 按主題分批(顯式 add、禁 -am、不 push);一句話 status+手測 checklist,收尾動作等 user 下令

【FE 影響】本波多為 service 層守門(403 對正常流程不可見);flow_engine 復活影響任務完成/證據增刪——手測 checklist 必含「被指派 auditor 完成任務、增刪證據照常」。