拿一份沒有任何 FR-132 異動的舊庫,走客戶升級的正式路徑(scripts/init/migrate.sh)升到現版,逐角色、逐帳號比對升級前後「能做的事」。新裝環境永遠會過,會出事的是帶著舊資料升級,這份紀錄回答的是:舊客戶升級後,每個人原本做得到的事還做得到嗎?有沒有人多拿到不該拿的?
asset.device.export 給有 device.read 的角色)。grc.audit.read(舊名 audit.read)——一般成員與兩個零權限 E2E 角色因此多了「我的稽核管理」側欄。is_admin 角色全數轉成 *.*.*,沒有未轉的 is_admin 角色。非 root 租戶的管理員展開後 168 顆=全集 208 扣平台層 40 顆,沒有拿到任何平台權限。| 項目 | 打折在哪 | 影響 |
|---|---|---|
| 舊庫來源 | 190 是我們自己的 E2E 安裝版,不是真實客戶庫。基線 1.22.0、BE image 1.25.1,資料是 e2e 跑出來的真流程 | 角色結構只有出貨預設四種+兩個零權限測試角色,沒有客戶自建、權限東拼西湊的角色——CM-2562 的缺口(升級回補腳本仍寫舊表,非全集管理員角色拿不到新權限)在這份庫上驗不到,因為 190 沒有「is_admin 但不是全集」的角色 |
| 升級前選單 | 現版程式連不上升級前的庫(缺 roles.is_system 等新欄位),所以升級前選單不是登入取得,而是用舊版規則(舊表 role_capabilities × route_capabilities 的 ANY/ALL/無門檻,加授權照過濾)在升級前副本上用 SQL 重算 |
已用 190 api_logs 裡唯一一筆真實 /user/web-menu 回應(blsadmin)對過,重算結果 37 個節點完全相同;其餘帳號的「升級前」是重算值 |
| 升級後選單 | 臨時 BE 起在 8010,不起背景排程、Redis 用本機另起的隔離實例(.env 指向的 189 Redis 與 8000 的 DEV BE 共用,展開快取鍵會互相污染) |
選單與權限判定走的是正式程式碼,排程不影響這兩者 |
| 按鈕 | 沒有開瀏覽器逐頁截圖。改比「前端判斷按鈕用的那份權限清單」(/users/me/capabilities 的 names)——升級後每個帳號實際登入取回,轉回舊名後與升級前逐帳號比 |
證明的是「按鈕判斷的依據」前後一致(零遺失),不是像素截圖 |
| blsalex | 密碼與共用測試密碼不同,沒有登入 | 該帳號角色是「一般成員」,同角色另有 9 個帳號已驗 |
| 第 26 支 migration | 排練進行中,另一棒(CM-2535 轉真擋)在同一工作目錄新增了 2026-10-08-fr132-enforce-all-log-only.sql(尚未 commit)。排練庫先套完 25 支做比對,之後把它當第 26 支補套 |
它只改 enforce_mode,套前套後角色 pattern 完全相同;套完後抽測 API 實擋結果與權限集合一致 |
舊庫:190 唯讀 pg_dump(guidant_ai_190_20261008_1548_before_FR-132_rehearsal.dump,md5 a15fde8c08cc3bbaeb66107309337313),restore 到本機 localhost:5432:
| 庫名 | 用途 |
|---|---|
guidant_ai_rehearsal |
升級後(跑過 migrate.sh),保留給首腦抽查 |
guidant_ai_rehearsal_pre |
升級前原樣副本(重算升級前選單用),保留 |
restore 零錯誤。升級前:285 支已套(主線 211+套件 74)、0 支 FR-132;4 個租戶、15 個角色、capabilities 123 顆舊名。
套件 SQL 攤平:本機 jedi 套件是 path 模式,flatten_pkg_migrations.sh 只掃得到 site-packages 內實體安裝的 3 支,攤出來的套件 manifest 不完整、第三輪宣告也會缺。做法是在 scratchpad 建一個假 site-packages(把 path 模式套件的目錄接進來),攤平到 scratchpad 的 SQL 目錄,repo 內的 scripts/sql/packages/ 沒動。攤出 74 支,與 190 已套的套件 migration 檔名集合完全相同;能力點宣告 208 顆。
升級:migrate.sh 先 dry-run(主線待套 25、套件 0、第三輪 208),再 MIGRATE_APPLY=1。過程 NOTICE:
grc.audit.read(CM-2588):5 個角色workspace.feedback.export:0 個角色比對口徑:
role_capabilities 的舊名集合;升級後以 jedi_iam.authz.pattern.match_patterns(判定用的同一支)展開 role_capability_patterns,root 判定與排除清單照正式規則,經 capabilities.legacy_name 轉回舊名比。升級時刪掉的 5 顆(workflow.* 四顆、bulletin.read)從預期集合扣掉。/user/web-menu(側欄)與 /users/me/capabilities(按鈕判斷用的權限清單)。user_roles 升級前後只差 org_unit_id 被清空(fr132-user-roles-tenant-scope-clear-org-unit 的設計),其餘欄位逐列相同。| 租戶 | 選為系統管理員(*.*.*)的角色 |
升級前能力點 | 升級後展開 |
|---|---|---|---|
| 1 Guidant.AI(root) | role 2 Administrator | 123(含 5 顆被刪的舊名) | 208(全集,含平台層) |
| 3 Billows | role 3 System Manager | 103(含 bulletin.read) |
168(全集扣平台 40) |
| 4 E2E_security_tenantA | role 8 System Manager | 103 | 168 |
| 5 E2E_security_tenantOther | role 12 System Manager | 103 | 168 |
未轉的 is_admin 角色:無。 每個租戶只有一個 is_admin 角色,且都等於該租戶全集。
「多出(舊名)」=升級後展開轉回舊名、升級前沒有的;「新拆」=沒有舊名的新權限(升級前不存在,不算遺失也不算越權)。
| 角色 | 租戶 | 升級前 | 遺失 | 多出(舊名) | 新拆 | 判定 |
|---|---|---|---|---|---|---|
| 2 Administrator | 1 | 123 | 0 | 0 | 90 | 一致(*.*.*) |
| 3 System Manager | 3 | 103 | 0 | 0 | 66 | 一致(*.*.*) |
| 8 System Manager | 4 | 103 | 0 | 0 | 66 | 一致(*.*.*) |
| 12 System Manager | 5 | 103 | 0 | 0 | 66 | 一致(*.*.*) |
| 4/9/13 一般成員 | 3/4/5 | 14 | 0 | audit.read |
28 | 多出屬 CM-2588 設計 |
| 5/10/14 稽核人員 | 3/4/5 | 23 | 0 | 0 | 28 | 一致 |
| 6/11/15 唯讀檢視者 | 3/4/5 | 13 | 0 | 0 | 28 | 一致 |
| 31 E2E_fixed_nocap | 3 | 0 | 0 | audit.read |
27 | 多出屬 CM-2588/CM-2556 B 類設計 |
| 32 E2E_fixed_root_nocap | 1 | 0 | 0 | audit.read |
36 | 同上;root 租戶故 B 類含平台層 9 顆 |
新拆權限補到哪些角色(CM-2556 三類):
asset.device.export(來源 asset.device.read)。*.*.*,本來就涵蓋。B 類的意思是「升級前這些端點只驗登入、任何人都做得到」,所以零權限角色升級後拿到 grc.job.delete 之類並不是越權,是把原本就做得到的事寫明。真擋開了之後它們仍做得到,與升級前一致。
零權限角色值得決策者知道的一點:E2E_fixed_nocap 升級前「什麼權限都沒有」,但當時任何登入者都能打那 27 支只驗登入的端點;升級後這件事被寫成明確權限,畫面上看起來像是「多了 27 顆」。客戶若有類似「刻意給空角色」的帳號,升級後在角色頁會第一次看到這些權限,可以自行拿掉。
升級前=舊規則+授權照過濾重算;升級後=臨時 BE 登入取 /user/web-menu。只比側欄頁(內頁不上側欄,兩邊都排除)。
| 帳號 | 租戶 | 前 | 後 | 少了 | 多了 |
|---|---|---|---|---|---|
| admin | 1 | 41 | 42 | /auth/role-manage |
/access/roles、/access/assignments |
| blsadmin、blsit、E2E_fixed_member_a | 3 | 37 | 38 | 同上 | 同上 |
| blsfrank、E2E_fixed_sub_admin、E2E_fixed_sub_pm | 4 | 37 | 38 | 同上 | 同上 |
| E2E_fixed_oth_admin | 5 | 10 | 10 | — | — |
| blspan(稽核人員) | 3 | 15 | 15 | — | — |
| E2E_fixed_readonly_b(唯讀) | 3 | 16 | 16 | — | — |
| blsemp1、blsshino、E2E_fixed_emp_a/emp_b/mail_html/mfa(一般成員) | 3 | 13 | 14 | — | /project/audit-manage |
| E2E_fixed_sub_emp/sub_quota/sub_rpm(一般成員) | 4 | 13 | 14 | — | /project/audit-manage |
| E2E_fixed_nocap | 3 | 3 | 4 | — | /project/audit-manage |
| E2E_fixed_root_nocap | 1 | 3 | 4 | — | /project/audit-manage |
grc.audit.read 給所有會進專案的角色)的直接效果。驗收條件「舊帳號選單不變」在這一項不成立,是預期的不成立。/users/me/capabilities 的 names 轉回舊名後與升級前逐帳號比:21 個帳號遺失皆為 0;多出的舊名只有一般成員與兩個零權限帳號的 audit.read,同上。
轉真擋(第 26 支)套完後抽測 API:一般成員、稽核人員打角色選單與 AI 呼叫紀錄回 403(升級前就沒有這兩顆)、打專案列表與我的任務通過守門(升級前有 project.read);管理員全數通過。
migrate-capability-grants.sql:46,72 仍寫舊表)這份庫驗不到,要等一份有「is_admin 但非全集」角色的真實客戶庫,或在排練庫手造一個這樣的角色再跑一次第三輪。migrate.sh 排練,套件 SQL 不能直接用 repo 內 scripts/sql/packages/(path 模式只攤到 3 支)。build 機上是 wheel 安裝,不受影響。DROP DATABASE guidant_ai_rehearsal; DROP DATABASE guidant_ai_rehearsal_pre;。