FR-155 · Project Role → RBAC — 設計文件 · 2026-10-21

專案角色併入 RBAC:設計定案

角色管理分「系統角色」「專案角色」兩個 tab;參與者指角色不指字串;判定查能力點

專案參與者今天是四個寫死的英文字(manager/reviewer/auditor/viewer),散在三個 repo 約一百多處與流程圖 XML 裡。本案把專案角色做成跟系統角色一樣「可建、可勾」:角色管理頁分兩個 tab,專案角色有自己的一組能力點(proj.*);專案參與者仍每個專案指派,但指的是角色;守門、通知、流程推進、待辦清單全部改成「他在這個專案的角色展開後有沒有這個能力點」。D1–D10 已定案;一版做完,流程編輯器併入第一版。

D1–D10 定案;實作完成待進版 拆分 4 批 × 11 子卡,一版做完 母案 CM-2977(子卡 CM-2978~2988) 跨 BE/FE/jedi_task_platform+5 套件呼叫端/五張表/流程圖 XML

狀態:實作完成、待進版(D1–D10 已拍板,D5/D7 依實作改寫)|日期:2026-10-22|討論稿(含圖與現況細節):discussion.html|雛形:prototype/index.html|母案:CM-2977(子卡 CM-2978~CM-3014)|收口審查:docs/review/2026-10-22-fr155-review.md 前作:FR-132 存取管理(capabilities.assignable_scopes 預留 project)、FR-048 統一授權守門(common/authz/)、FR-069 jedi-iam

§1

變更紀錄

日期 變更 對應
2026-10-21 初版設計定案:D1–D10 全數照討論稿建議拍板;雛形三頁審過;流程編輯器(原第二版)併入第一版,與畫面同批 本檔
2026-10-21 Notion 母卡 CM-2977 + 11 子卡 CM-2978~2988 建卡完成,工作坊四欄已填;等決策者在工作坊派第 1 批 母案 CM-2977
2026-10-22 依實作與決策者裁示改成現況:D5 指派只在專案層;D7 my_capabilities 是專案層精確版;§6.1 能力點 13 個(去 proj.stage.advance、proj.job.execute,加 proj.round.manage);§6.4 設計器主責只收三個;§6.5 補 GET /project-roles?project_uid=、錯誤碼實際編號與 GRC_400185 CM-3014
§2

需求背景與端到端流程

2.1 要解決什麼

缺口 白話 證據
專案角色寫死 四個英文字散在 BE 28 支檔、6 支 jedi 套件、FE 約 30 支;enum 有兩份相同複本 FR-132 inventory/02-project-roles-and-flow.md §1–2;本案 grep
管理員不能自訂專案角色 客戶要「資深稽核員」(稽核+審閱)做不到;角色的能力綁在程式裡 common/enum/participant_enum.py、ParticipantPicker.vue:102-107
流程圖綁角色名 BPMN UserTask main_role 存四值;進行中實例約 4,400 份 XML 快照 stage_advance_service.py:672-679;inventory §3、§5
通知與待辦各自比字串 8 支 SQL 讀模型 pp.role IN (...)、5 處通知收件人各寫一遍 infra/readmodel/audit/*.py、stage_entered_recipients.py
兩套角色沒有關係 系統角色是資料驅動(FR-132),專案角色不是 roles/role_capability_patterns vs 五張參與者表 role varchar

2.2 端到端流程

  1. 管理員在「存取管理 / 角色 / 專案角色」tab 建角色「資深稽核員」,勾 proj.audit.judge、proj.review.approve、proj.report.read。
  2. 專案負責人在「專案 / 設定 / 參與者」把陳美玲加進來,角色下拉選「資深稽核員」,稽核範圍選外稽。
  3. 陳美玲進專案:專案詳情 API 回她的 my_capabilities,前端只顯示那三個能力點對應的按鈕。
  4. 她按「標記已審閱」:後端 assert_project_capability("proj.review.approve", ...) 由下往上找她的參與者指派、展開角色、比對通過。
  5. 批次完成通知:查「本專案有 proj.review.approve 的參與者」,她會收到;「稽核待辦」清單列有 proj.audit.judge 的人,她也在。
  6. 流程走到「稽核」站:該站要求 proj.audit.judge,她可以推進;負責人有 proj.stage.advance-any 也可以。
§3

分工概述(30 秒版)

一句話:先收斂與定清單(B.1),再改表與升級(B.2),改判定與通知與 API(B.3),最後畫面與流程編輯器(B.4)。

批 做什麼(白話) 產出 完成怎麼判定(決策者可親手檢查) 依賴
B.1 收斂與清單 四個字只留一份;守衛擋字面值;專案能力點清單與四個範例角色對照定稿 enum 單一來源+守衛測試;inventory/b1-project-capabilities.md 守衛測試綠,故意加一行 'manager' 會紅;對照表每個專案內動作都有一列 —
B.2 資料地基 roles 加種類與預設負責人旗標;capabilities 加 proj.*;五張參與者表加 project_role_id;升級腳本每租戶建四範例角色並回填;新裝 seed migration、seed、排練紀錄 真實舊庫升級後,任一專案參與者頁每個人角色名與升級前一樣;舊帳號能按的按鈕一樣 B.1
B.3 判定、通知、API 守門四支改收能力點;get_user_role 回角色並能展開;8 支 SQL+5 處通知改同一查法;流程推進讀取換算;專案角色 CRUD/GET /project-roles/專案詳情回 my_capabilities/FE 常數檔 判定層、讀模型、API 建「只能看」的自訂角色指派給某人:他進得了專案、按審核 403、不收審閱通知;勾回審閱立刻能按、會收信 B.2
B.4 畫面與流程編輯器 角色頁兩個 tab+專案角色詳情;參與者下拉讀表;專案內按鈕改讀 my_capabilities;流程編輯器「主要角色」改選能力點 FE 改動+BE 存檔驗證 對照雛形三頁;新增「資深稽核員」指派後該人只看得到三個動作;流程編輯器下拉列的是能力點 B.3

B.1→B.2→B.3→B.4 照順序;B.4 內三張可依卡號串行。

§4

決策定案 D1–D10

# 題目 定案 理由 被排除方案
D1 專案能力點命名 新模組前綴 proj.<資源>.<動詞>,assignable_scopes=['project']、module='proj';13 個(§6.1) 與 grc.*(租戶層「能不能碰這類東西」)一眼分得開;有 grc.audit.approve 的系統角色不會被誤判成每個專案都能審核 沿用 grc.* 靠範圍欄分——畫面與 grep 都會混
D2 出貨範例專案角色 每租戶四個範例(負責人/審核者/稽核員/瀏覽者),可改名、改能力點、可刪;其中一個 is_default_owner=true(建專案建立者自動指派、不可刪、每租戶恰一個);使用中專案數 >0 不可刪 與 FR-132 D6 同精神:保證至少一個角色能管專案,其餘是起點 四個全鎖——客戶要自訂只能複製
D3 參與者表怎麼改 五張表加 project_role_id FK → roles.id(nullable 一版);舊 role 欄並存一版後退役;寫入路徑雙寫(新欄為準、舊欄回填對應範例角色名) 回滾不必動資料,與 FR-132 D2 同手法 直接改 role 欄語意——回滾要動資料
D4 系統管理員進專案 proj.* 只從「這個專案的參與者指派」展開,不從 user_roles 展開;系統 *.*.* 不包含 proj.*;不是參與者就看不到專案(與今天相同) 管理員不會每封通知都收、稽核員清單不會列他;程式上兩個入口各查各的 包含——每種通知都收
D5 群組/控制項層回退 指派只在專案層。子層參與者表(群組、控制項、控制群組、流程)保留 project_role_id 欄,但沒有寫入路徑;回退版守門(控制項→群組→專案找第一筆)仍在,實際一律落到專案層那一列 子層的寫入端點早已退役、DEV 0 筆、前端控制項樹也不送;為它補畫面與回填沒有使用者 補子層指派畫面——沒有需求;拿掉子層欄——回滾要動表
D6 稽核範圍(內稽/外稽) audit_scope 留在參與者列;判定時只對「有 proj.audit.judge」的參與者生效;前端只對有該能力點的角色顯示欄位 不綁角色名、綁能力點 搬到角色——同一人兩專案不同範圍要兩個角色
D7 專案內按鈕前端判定 專案詳情與專案列表回 my_capabilities,是專案層精確版(只看專案層那一列,與後端精確版守門同判法;D5 下子層沒有指派,兩版結果相同);FE 新增 v-can-project="PROJ.X" 指令;權限名由 BE 產常數檔(比照 gen_permission_matrix.py) 回退規則 BE 算一次,FE 不重算 FE 自己展開角色
D8 流程圖 main_role 改存能力點名(例 proj.audit.judge,逗號多值照舊);_resolve_main_roles 讀到舊四值即換算;flow_templates.bpmn_xml 與 stage_objects.default_main_roles 用 migration 改成能力點名;workflow_templates.xml(實例快照)不碰;manager 全域覆寫改成「有 proj.stage.advance-any」;設計器存範本的 main_role 只收 proj.review.approve、proj.audit.judge、proj.stage.advance-any 三個(§6.4) 客戶會改角色名、加角色,角色名不能當程式依據;4,400 份快照不改最安全 存角色 uid(刪角色流程壞);批次改寫 XML
D9 jedi 套件改法 參與者表、enum、serializer 的 owner 是 jedi_task_platform,改在套件;jedi_compliance_audit/jedi_survey/jedi_evidence_classification/jedi_detection/jedi_flow_engine 只改呼叫端參數(角色 tuple → 能力點名);開發走 poetry path dependency,發版等決策者明示 不造第二套真相 主專案 monkeypatch
D10 分版 一版做完(原「流程編輯器放第二版」改判:客戶第一版就能自建角色,編輯器若還只能選四個舊字,自建角色在流程裡沒用) 決策者 2026-10-21 改判 兩版

明確不做:專案角色的部門範圍與起訖日(參與者指派本來就是專案範圍、無時效);拒絕規則;專案角色匯出 JSON;專案角色的萬用字元(只有十幾個能力點,逐個勾比較清楚)。

§5

現況接入點盤點

元件 現況 本案動作 批
common/enum/participant_enum.py+jedi_task_platform/participant/common/participant_enum.py 兩份相同 StrEnum 只留套件那份;主專案改 re-export;守衛測試 test/test_project_role_literal_guard.py 掃 api/ app/ infra/ core/ 的 `'manager' 'reviewer'
common/authz/project.py:28-137 四支守門 allowed_roles tuple 比對 新增 assert_project_capability(cap, role_service, project_id, group_id, control_id)(回退版)與 assert_project_capability_exact(cap, participant_ds, project_id, user_id)(精確版);舊四支留 shim 一版,內部把 allowed_roles 對到能力點(manager→proj.participant.manage、auditor→proj.audit.judge、reviewer→proj.review.approve) B.3
jedi_task_platform participant_role_service.py:34-85 get_user_role 回角色字串 新增 get_user_project_role_id(同回退規則,回 project_role_id)與 get_user_project_capabilities(展開,走 jedi-iam pattern 展開服務+Redis 快取鍵 authz:proj:{tenant}:{catalog_version}:{role_id});get_user_role 保留一版回舊欄 B.3
8 支 infra/readmodel/ SQL(audit_plan_list_query.py:47,53,196,310、closing_report_list_query.py:27,32、auditor_dashboard_query.py:60,157、poam_dashboard_query.py:61、job_due_reminder_query.py:55、job_batch_complete_query.py:239、detection_job_notify_query.py:156、support_end_query.py:52) 字面值 先查「租戶內有能力點 X 的專案角色 id 集合」(新 helper project_role_ids_with_capability(tenant_id, cap),可快取),再 project_role_id = ANY(:ids);查詢形狀不變 B.3
stage_entered_recipients.py、job_batch_complete_query.py:232、detection_job_notify_query.py:150、auditor_dashboard_query.py:58 各自比角色 統一改為「有能力點 X 的參與者」 B.3
project_start_app_service.py:135-146 建立者寫 'manager' 寫該租戶 is_default_owner 角色 B.3
api/project/serializers/project.py:27-29、excel_parser/validators.py:9、jedi_task_platform serializer ×2、jedi_compliance_audit serializers/project.py:77 OneOf 四值 收 project_role_uid,驗存在於該租戶 kind='project' 且啟用;Excel 匯入收角色名、對不到報錯列出 B.3
stage_advance_service.py:148-156,344-355,672-679 main_role 四值 vs user_role _resolve_main_roles 回能力點名(舊四值換算表);判定改 cap in user_caps or "proj.stage.advance-any" in user_caps;force 改要求 proj.stage.advance-any B.3
stage_objects.default_main_roles、flow_templates.bpmn_xml 角色名 migration 改能力點名(XML 用 regexp_replace 只動 name="main_role" 的 value) B.2
jedi-iam api/routing.py roles CRUD 無 kind list 加 kind 篩選;create 收 kind;kind='project' 的角色 pattern 只准 proj.*、必含 proj.project.read;is_default_owner 切換時同租戶其他角色清掉;刪除前查使用中專案數 B.3
專案詳情 API(api/project/) 回 participants 加 my_capabilities: [str];GET /project-roles(租戶內啟用的專案角色,含能力點清單) B.3
scripts/deliverables/gen_permission_matrix.py 產 CAP 常數 加產 PROJ 常數(FE src/config/projectCapabilities.js) B.3
FE views/access/RoleList.vue、RoleDetail.vue、PermissionResourceMatrix.vue 單一列表 兩個 tab(?kind=);專案角色詳情矩陣只列 proj.*、拿掉可見選單預覽、加預設負責人開關 B.4
FE ParticipantPicker.vue:102-115、ProjectParticipantsPanel.vue、ProjectCreateView.vue 四值寫死 下拉讀 GET /project-roles;audit_scope 欄只對含 proj.audit.judge 的角色顯示 B.4
FE 專案頁約 30 處 p.role === 'manager'(ProjectPlanningView.vue 9、ProjectAuditorOverview.vue 6、MyTasksView.vue 2、FlowPhaseBanner.vue 1、ApPlanOverviewTab.vue 2…) 字面值 v-can-project="PROJ.X"+my_capabilities B.4
FE FlowTemplateEditorView.vue:129-134,376-389,594、FlowTemplatePreviewDialog.vue 四值寫死 下拉讀 proj.* 能力點;存檔驗證改能力點名 B.4
5 支套件呼叫端(review_service.py、task_survey_guard.py、evidence_batch_service.py:1476、detection_orchestration_service.py ×8、core/plugins/compliance_audit.py:73-93) allowed_roles=("manager",) 改能力點名參數,透過 adapter 轉發新函式 B.3
§6

詳細設計

6.1 專案能力點清單(D1)

能力點 白話 負責人 審核者 稽核員 瀏覽者
proj.project.read 看專案(所有專案角色必含) ✓ ✓ ✓ ✓
proj.project.update 改專案設定、刪專案 ✓
proj.participant.manage 增刪參與者、指派任務 ✓
proj.job.manage 增刪改任務、批次完成不限指派 ✓
proj.review.approve 標記已審閱、退回 ✓ ✓
proj.audit.judge 稽核判定、AP 編輯、AO 結果 ✓ ✓
proj.stage.advance-any 推進任何階段、強制推進 ✓
proj.round.manage 建立輪次、啟動稽核、結束輪次、各段退回 ✓
proj.poam.manage 改善計畫建立與結案 ✓
proj.report.read 看結案報告、匯出 ✓ ✓
proj.report.manage 建立與發布結案報告 ✓
proj.evidence.manage 證據分類批次操作 ✓
proj.ssp.update SSP 各區塊寫入 ✓

共 13 個。四個範例角色的勾選以 resources/roles/project-roles.json 為準(與 seed migration 由 test/test_role_templates.py 鎖成一致)。「執行任務、上傳佐證、填答」不設能力點:這類動作走「被指派人 ∪ proj.job.manage」(任務被指派人本身就有操作權,不看角色)。「推進自己負責的階段」也不設:可不可以推進由站的 main_role 決定(§6.4)。每個接入點對到哪一個能力點見 inventory/b1-project-capabilities.md。

舊角色→能力點換算表(守門 shim、main_role 讀取換算、get_user_role 相容用同一張):manager→proj.participant.manage(守門語意「是 manager」)/proj.stage.advance-any(流程語意);reviewer→proj.review.approve;auditor→proj.audit.judge;viewer→proj.project.read。

6.2 資料模型(D2、D3)

表 動作
public.roles kind varchar(10) NOT NULL DEFAULT 'system'(CHECK in system/project);is_default_owner boolean NOT NULL DEFAULT false;部分唯一索引 (tenant_id) WHERE kind='project' AND is_default_owner AND is_delete=0
public.capabilities proj.* 13 列:module='proj'、assignable_scopes=['project']、is_platform=false、license_module 空(不受授權照管)、display_name='capability.proj.<資源>.<動詞>'
compliance.project_participants、project_group_participants、project_control_participants、control_group_participants、process_participants 加 project_role_id integer NULL REFERENCES public.roles(id) ON DELETE RESTRICT+索引;role 欄保留一版
compliance.stage_objects.default_main_roles 值改能力點名
compliance.flow_templates.bpmn_xml main_role 值改能力點名(設計器範本);workflow_templates.xml 不碰

升級腳本(T-2.2):對每個 tenants 列建四個 kind='project' 角色(名稱走 i18n key project_role.owner 等,DB 存中文名「負責人/審核者/稽核員/瀏覽者」)+ role_capability_patterns 照 6.1 表;負責人 is_default_owner=true;五張表 UPDATE ... SET project_role_id = (該租戶對應角色) WHERE role = '<舊值>';結尾斷言五張表 project_role_id IS NULL 為 0 列,否則 rollback。新裝:scripts/init/ seed 與 tenant_provisioning_service.py 同樣建四個;回寫時明講「出貨基線待重產」。

6.3 判定層(D4、D5)

  • 兩支展開、兩支守門:回退版 get_user_project_capabilities(project_id, user_id, group_id=None, control_id=None)+assert_project_capability(由下往上找第一筆指派);精確版 get_participant_capabilities+assert_project_capability_exact(只看專案層那一列)。兩版都是 project_role_id → jedi-iam CapabilityExpansionService.expand_project_roles → Redis 快取(鍵 authz:proj:{tenant}:{catalog_version}:{role_id},角色寫入時 invalidate_all 一併清)。非參與者回 None/空集合,守門轉 404;是成員但缺能力點 403。
  • 🔴 正式環境 Redis ACL:docker/production/docker-compose.yml 的 guidant-api/socketio/worker 三個帳號都要開 ~authz:proj:*(與 ~authz:exp:* 同權限)。漏開時 Redis 回 NOPERM,adapter 只把連線錯誤當「快取不可用、改查 DB」,NOPERM 會直接變 500。
  • 舊守門 shim:assert_project_manager/assert_project_role_fallback/assert_project_role 保留一版,內部用換算表把舊角色字對到能力點(manager→proj.participant.manage、reviewer→proj.review.approve、auditor→proj.audit.judge、viewer→proj.project.read)。新程式一律用能力點版。
  • 列表上的「負責人」欄(稽核計畫列表、結案報告列表)看的是「預設負責人」角色,不是看能力點:顯示的是「誰掛名負責」,自建的副負責人有管理能力點但不算掛名。「可不可以指派」仍看 proj.participant.manage。
  • 不從 user_roles 展開 proj.*(D4):CapabilityGuard 的展開服務遇到 assignable_scopes=['project'] 的能力點一律不納入租戶層集合;守衛測試驗 *.*.* 展開結果不含 proj.*。
  • 守門入口維持 common/authz/project.py;決策表(common/authz/__init__.py)軸③改寫為能力點版。
  • audit_scope(D6):resolve_stage_recipients 與 _get_user_role 的稽核性質比對只對 proj.audit.judge in caps 的參與者做。

6.4 流程(D8)

  • _resolve_main_roles → _resolve_main_caps:讀 main_role,每個值若在舊四值換算表就換,否則視為能力點名;回 list[str]。
  • 可推進:caps & main_caps 非空,或 proj.stage.advance-any in caps。force 要 proj.stage.advance-any。
  • stage_entered_notify_service:收件人=有 main_caps 任一的參與者(套 D6)+有 proj.stage.advance-any 的參與者。
  • 進站通知:main_role 空值時退回站預設 stage_objects.default_main_roles,與推進判定共用 stage_entered_recipients.resolve_main_caps。
  • 規劃準備度檢查(「有站沒人負責」軟提醒)用同一套換算;持 proj.stage.advance-any 的人整個不算該站負責人(預設負責人什麼都有,算進去就永遠不提醒)。
  • 設計器只收三個主責:存範本時每個 UserTask main_role 只能是 proj.review.approve、proj.audit.judge、proj.stage.advance-any(app/flow_engine/service/main_role_check.py MAIN_ROLE_ALLOWED),否則 400 GRC_400185 並列出允許值。其他能力點不准選:推進判定只看「持有任一主責」,選了 proj.ssp.update 這類會讓守門失真。
  • 專案流程設定存實例 XML:先把舊四值換算成能力點寫回,再驗「是專案能力點」即可(舊實例可能存了三個以外的值,收嚴會讓它存不回去)。
  • FE 流程編輯器:下拉只列三個,顯示 lang.capability.*;載入舊範本時整張換算;舊範本存了三個以外的值,臨時併入選項並標「不在建議清單」。

6.5 API(D7)

端點 說明
POST /roles(jedi-iam 列表,filters.kind) 預設 system;kind=project 列專案角色,每列回 kind/is_default_owner/in_use_project_count
POST /role、PUT /role/<uid>(既有,收 kind) kind='project' 時只准 proj.* 完整名、自動補 proj.project.read;is_default_owner=true 時同交易先清同租戶其他角色的旗標;專案角色不過授權照守門(proj.* 不是售賣模組)
GET /project-roles 登入者租戶內啟用的專案角色(uid/name/capabilities/is_default_owner),建專案的參與者下拉用;任何登入者可讀
GET /project-roles?project_uid=<uid> 專案所在租戶的專案角色(總部帳號改子租戶專案時用);限專案成員,非成員 404
GET /grc/project/<uid>(既有) 加 my_capabilities;participants 每筆補 project_role_uid/project_role_name
專案列表(既有) 每列加 my_capabilities(整頁一次展開)
PUT /grc/project/<uid>(既有) 改設定要 proj.project.update;送 participants 另要 proj.participant.manage,且每個 project_role_uid 須屬專案所在租戶(否則 404 GRC_404057)
GET /users/me/capabilities(既有) 不含 proj.*

錯誤碼(common/code/flow_control_error_code.py,FE i18n 已同步):

碼 常數 情境
GRC_403071 GRC_PROJECT_CAPABILITY_REQUIRED 是專案成員但角色缺該能力點(呼叫端沒給專屬碼時用;原本有專屬 403 碼的守門保留原碼)
GRC_404057 GRC_PROJECT_ROLE_NOT_FOUND 專案角色不存在、停用,或不屬專案所在租戶
GRC_412078 GRC_DEFAULT_OWNER_REQUIRED 租戶沒有預設負責人(建專案時);預設負責人不可停用
GRC_400183 GRC_PROJECT_ROLE_AUDIT_SCOPE_NOT_ALLOWED 給沒有 proj.audit.judge 的角色設內稽/外稽範圍
GRC_409050 GRC_PROJECT_ROLE_IN_USE 刪除仍有專案在用的專案角色(優先於 412)
GRC_400184 GRC_PROJECT_ROLE_INVALID_PATTERN 專案角色勾了非 proj.* 或萬用字元
GRC_400185 GRC_INVALID_MAIN_ROLE 流程圖 main_role 不在允許清單

6.6 前端(D7)

照雛形三頁:prototype/roles.html(兩個 tab)、prototype/role-detail.html(專案角色詳情)、prototype/participants.html(參與者)。v-can-project 指令實作比照 v-can(src/directives/),資料源是當前專案 store 的 my_capabilities;專案 store 載入專案詳情時一併存。

§7

拆分(4 批 × 11 子卡+收尾 2 批)

下表是原始拆分;驗收後加開第 5 批(CM-3010 合回與套件 bump、CM-3011 BE 裁示收尾、CM-3012 FE 裁示收尾)與第 6 批(CM-3013 出貨基線重產、CM-3014 收口審查與文件)。

依賴鏈:B.1 → B.2 → B.3 → B.4;同批依卡號串行。jedi 套件開發走 poetry path dependency(pyproject.toml:118-124 的註解行),發版等決策者明示;migration 只套 DEV。

編號 卡號 批 做什麼(白話) 改哪些檔 驗收條件 建議 model/effort
T-1.1 CM-2978 1 四個字只留一份:主專案 enum 改 re-export jedi_task_platform;守衛測試擋字面值 common/enum/participant_enum.py、test/test_project_role_literal_guard.py、jedi_task_platform enum 守衛綠;突變(加一行 'manager')會紅 sonnet/medium
T-1.2 CM-2979 1 專案能力點清單定稿:逐一對照 70 處守門、8 支 SQL、5 處通知、6 個流程站、FE 30 處按鈕各對哪個 proj.*;缺的補進清單 inventory/b1-project-capabilities.md 每個接入點都有一列且有能力點;四個範例角色勾選表定稿 opus/high
T-2.1 CM-2980 2 資料表:roles 加欄、capabilities 加 proj.*、五張參與者表加 FK;jedi-iam/jedi_task_platform ORM 同步 scripts/sql/2026-10-xx-fr155-*.sql ×1、jedi-iam infra/models/role.py、jedi_task_platform 五個 model DEV 套用後 \d 看得到欄;capabilities 多 14 列 opus/medium
T-2.2 CM-2981 2 升級腳本與新裝 seed:每租戶建四範例角色、回填 project_role_id、stage_objects/flow_templates 改能力點名;拿真實舊庫排練;tenant_provisioning_service 建四個 migration ×2、scripts/init/ seed、tenant_provisioning_service.py、inventory/b2-upgrade-rehearsal.md 排練後五張表 project_role_id IS NULL 0 列;任一專案參與者頁角色名不變;回報「出貨基線待重產」 opus/high
T-3.1 CM-2982 3 守門改能力點:新兩支守門函式+舊四支 shim;get_user_project_capabilities 展開+快取;D4 守衛;6 套件呼叫端改參數;serializer 收角色 uid;建立者寫預設負責人 common/authz/project.py、__init__.py 決策表、jedi_task_platform participant_role_service.py、5 套件呼叫端、core/plugins/compliance_audit.py、project_start_app_service.py、serializer ×4、error code 「只能看」自訂角色按審核 403;非成員 404;*.*.* 展開不含 proj.*(守衛) opus[1m]/high
T-3.2 CM-2983 3 通知與 SQL 讀模型:project_role_ids_with_capability helper;8 支 SQL 改 project_role_id = ANY;5 處收件人改能力點 infra/readmodel/ 8 支、stage_entered_recipients.py、job_batch_complete_query.py、detection_job_notify_query.py 勾掉審閱能力點批次完成通知不寄;勾回會寄;我的稽核待辦列有 proj.audit.judge 的人 opus/high
T-3.3 CM-2984 3 流程推進讀取換算:_resolve_main_caps、可推進判定、force、進站通知收件人;舊四值與逗號多值測試 stage_advance_service.py、stage_entered_notify_service.py、測試 進行中舊專案推進行為不變;新範本存能力點名可推進 opus/high
T-3.4 CM-2985 3 專案角色 API:roles CRUD 加 kind、pattern 限制、is_default_owner 互斥、刪除前查使用中;GET /project-roles;專案詳情回 my_capabilities;gen_permission_matrix.py 產 PROJ 常數 jedi-iam api/+app/、api/project/、scripts/deliverables/gen_permission_matrix.py、FE src/config/projectCapabilities.js Swagger 打得通;GET /projects/<uid> 有 my_capabilities;常數檔與 DB 一致 opus/medium
T-4.1 CM-2986 4 FE 角色頁兩個 tab+專案角色詳情(照雛形 1、2) FE views/access/RoleList.vue、RoleDetail.vue、components/access/PermissionResourceMatrix.vue、i18n 新增「資深稽核員」勾三個能力點存得起來;預設負責人開關互斥;使用中不可刪 opus/medium
T-4.2 CM-2987 4 FE 參與者下拉讀表+專案內按鈕改 v-can-project(約 30 處)+audit_scope 只對稽核判定角色顯示(照雛形 3) FE ParticipantPicker.vue、ProjectParticipantsPanel.vue、ProjectCreateView.vue、directives/vCanProject.js、專案頁約 10 支 指派「資深稽核員」後進專案只看得到三個動作;grep "role === 'manager'" 專案頁為零 opus[1m]/high
T-4.3 CM-2988 4 FE 流程編輯器「主要角色」改選能力點+BE 存檔驗證 FE FlowTemplateEditorView.vue、FlowTemplatePreviewDialog.vue、BE workflow_xml_sync_service.py 驗證 編輯器下拉列能力點;存舊四值被拒;預覽顯示能力點 i18n sonnet/medium

收口:審查、SPEC 三頁、使用手冊、release note 草稿由 CM-3014 做(報告 docs/review/2026-10-22-fr155-review.md);套件發版與還原 pin、e2e 到 test repo 等決策者令。

上版綁定:① 參與者回填 migration(T-2.2)與判定改查能力點(T-3.x)必須同一版上,只上判定,參與者沒有 project_role_id 會一律 404;② jedi-iam 1.7.0、jedi-task-platform 1.4.0、jedi-compliance-audit 1.4.0、jedi-survey 1.3.0 四支新版必須一起進 image(task-platform 的 adapter 新增必填參數,新套件配舊宿主會 TypeError);③ 正式 compose 的 Redis ACL 要含 ~authz:proj:*。

§8

端到端驗收

  1. 真實舊庫升級:五張表零 NULL;任一專案參與者頁角色名不變;舊帳號按鈕不變;進行中流程推進不變。
  2. 新裝:每租戶四個範例專案角色,負責人為預設;建專案建立者自動負責人。
  3. 自訂角色「資深稽核員」(稽核判定+審閱+看報告):指派後只看得到三個動作;按審核通過、按刪專案 403;收審閱通知與稽核待辦;流程稽核站可推進。
  4. 「只能看」角色:進得了專案、所有寫入 403、不收任何待辦通知。
  5. 系統管理員未加入專案:看不到專案(404);GET /users/me/capabilities 不含 proj.*。
  6. 控制項層指派成「只能看」:該控制項寫入 403,專案層其他控制項照常。
  7. 流程編輯器新建範本 main_role 存能力點名;舊範本開啟顯示換算後的能力點。
  8. 守衛:字面值守衛、*.*.* 不含 proj.*、test/test_module_boundaries.py、RLS 形狀測試全綠。