FR-132 舊庫升級排練:逐角色、逐帳號比對

舊庫升級排練:逐角色、逐帳號比對

拿一份沒有任何 FR-132 異動的舊庫,走客戶升級的正式路徑(scripts/init/migrate.sh)升到現版,逐角色、逐帳號比對升級前後「能做的事」。新裝環境永遠會過,會出事的是帶著舊資料升級,這份紀錄回答的是:舊客戶升級後,每個人原本做得到的事還做得到嗎?有沒有人多拿到不該拿的?

§1

結論

  • 零遺失:15 個角色、21 個能登入的帳號,升級前持有的每一顆舊權限,升級後都還在(不抽樣,全部逐一比)。
  • 多出來的只有兩類,都是已裁的設計:
    1. CM-2556 補授的新拆權限(B 類 27 顆給所有非管理員角色、A 類 asset.device.export 給有 device.read 的角色)。
    2. CM-2588 補授的 grc.audit.read(舊名 audit.read)——一般成員與兩個零權限 E2E 角色因此多了「我的稽核管理」側欄。
  • 側欄:除上述「我的稽核管理」外,管理員的舊「角色管理」頁換成新「角色」「指派」兩頁;其餘頁面前後完全一致。
  • 系統管理員轉換:4 個 is_admin 角色全數轉成 *.*.*,沒有未轉的 is_admin 角色。非 root 租戶的管理員展開後 168 顆=全集 208 扣平台層 40 顆,沒有拿到任何平台權限。
  • 正式路徑本身:25 支主線 migration 依 manifest 行序一次套完、無錯;套件輪 0 支;第三輪宣告 208 顆。重跑一次待套 0、角色 pattern 不變(冪等)。
  • 沒有踩到順序依賴:manifest 行序就是可用順序。
§2

驗收打折(照實標)

項目 打折在哪 影響
舊庫來源 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 實擋結果與權限集合一致
§3

怎麼做的

舊庫: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:

  • 改名自檢通過:204 個三段式
  • 完整名 pattern 轉入 574 筆;自檢 13 個角色 pattern 展開與舊表完全一致
  • 補授(CM-2556):A 類 9 筆、B 類 306 筆、C 類 0 筆,適用 11 個角色
  • 補授 grc.audit.read(CM-2588):5 個角色
  • 回收 workspace.feedback.export:0 個角色
  • 第三輪:upsert 208、root 新增 0、租戶側新增 0

比對口徑:

  • 角色層:升級前每角色 role_capabilities 的舊名集合;升級後以 jedi_iam.authz.pattern.match_patterns(判定用的同一支)展開 role_capability_patterns,root 判定與排除清單照正式規則,經 capabilities.legacy_name 轉回舊名比。升級時刪掉的 5 顆(workflow.* 四顆、bulletin.read)從預期集合扣掉。
  • 帳號層:臨時 BE 實際登入每個帳號,取 /user/web-menu(側欄)與 /users/me/capabilities(按鈕判斷用的權限清單)。
  • 指派:user_roles 升級前後只差 org_unit_id 被清空(fr132-user-roles-tenant-scope-clear-org-unit 的設計),其餘欄位逐列相同。
§4

每個租戶選到的系統管理員

租戶 選為系統管理員(*.*.*)的角色 升級前能力點 升級後展開
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 角色,且都等於該租戶全集。

§5

逐角色比對(全部 15 個)

「多出(舊名)」=升級後展開轉回舊名、升級前沒有的;「新拆」=沒有舊名的新權限(升級前不存在,不算遺失也不算越權)。

角色 租戶 升級前 遺失 多出(舊名) 新拆 判定
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 三類):

  • 一般成員/稽核人員/唯讀檢視者(9 個):B 類 27 顆+A 類 asset.device.export(來源 asset.device.read)。
  • 兩個零權限 E2E 角色:只有 B 類(27 顆;root 租戶那個多 9 顆平台層,共 36)。
  • C 類(空殼/全新功能只補 is_admin):0 筆——4 個 is_admin 角色都已是 *.*.*,本來就涵蓋。

B 類的意思是「升級前這些端點只驗登入、任何人都做得到」,所以零權限角色升級後拿到 grc.job.delete 之類並不是越權,是把原本就做得到的事寫明。真擋開了之後它們仍做得到,與升級前一致。

零權限角色值得決策者知道的一點:E2E_fixed_nocap 升級前「什麼權限都沒有」,但當時任何登入者都能打那 27 支只驗登入的端點;升級後這件事被寫成明確權限,畫面上看起來像是「多了 27 顆」。客戶若有類似「刻意給空角色」的帳號,升級後在角色頁會第一次看到這些權限,可以自行拿掉。

§6

逐帳號側欄比對

升級前=舊規則+授權照過濾重算;升級後=臨時 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
  • 「角色管理」→「角色」「指派」:A.5 存取管理前端的設計。
  • 一般成員多「我的稽核管理」:CM-2588(決策者 2026-10-08 裁乙,補授 grc.audit.read 給所有會進專案的角色)的直接效果。驗收條件「舊帳號選單不變」在這一項不成立,是預期的不成立。
  • 租戶 5 管理員只有 10 頁:租戶 5 是頂層租戶但沒有授權照,授權過濾(FR-062,早於 FR-132)把需授權模組的頁全濾掉。升級前後相同,不是升級造成。重算升級前選單時漏套授權過濾,這個帳號會看起來「少 27 頁」,是比較口徑的假紅。
§7

逐帳號權限清單比對(按鈕判斷的依據)

/users/me/capabilities 的 names 轉回舊名後與升級前逐帳號比:21 個帳號遺失皆為 0;多出的舊名只有一般成員與兩個零權限帳號的 audit.read,同上。

轉真擋(第 26 支)套完後抽測 API:一般成員、稽核人員打角色選單與 AI 呼叫紀錄回 403(升級前就沒有這兩顆)、打專案列表與我的任務通過守門(升級前有 project.read);管理員全數通過。

§8

給後續的備忘

  • CM-2562(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;。