Tenant scope 資料模型(B-2 + FR-042 + SMTP/LDAP 平台層)部署 / Migration Runbook

對應 commit BE d9a2ffe8 + jedi-system-config afe2dcd。母案 Notion 38d346da-4cd0-812f-aba6-debcb1e1f632。 dev(188/guidant_ai_dev)已全套並驗。stg / poc / prod 依本檔各跑一次。

0. 前置 / 通則

  • 帳號 cmmgr(系統管理員繞 RLS;cm_app 受 RLS 寫系統層會 silent fail)。密碼查 .env DB_SECRET
  • 每支:psql -h <host> -p 25432 -U cmmgr -d <db> --single-transaction -v ON_ERROR_STOP=1 -f scripts/sql/<檔>
  • 每支檔尾已含 INSERT public.schema_migrations,重跑安全(ON CONFLICT DO NOTHING + 條件式冪等)。
  • root tenant / root org_unit 各環境皆 id=1(雷門 2026-06-29 確認)。
Env host db
stg 192.168.50.188 guidant_ai_stg
poc 192.168.50.189 guidant_ai_poc
prod (查部署文件) (查部署文件)

1. SQL migration(依此順序,scripts/sql/)

依賴:①→② admin 先入 root tenant,roles 遷移的成員檢查才過;⑤→⑥ SMTP 先 null→root 再刪 per-tenant。其餘大致獨立。

# 檔案 作用
1 2026-06-29-b2-admin-root-tenant.sql admin users.tenant_id NULL→1 + user_tenants + user_org_units
2 2026-06-29-b2-admin-root-tenant-roles.sql admin role/user_role global→tenant(1)(修空選單)
3 2026-06-28-fr042-upload-files-add-storage-scope.sql upload_files 加 storage_scope(system/customer)
4 2026-06-29-fr042-tenant-scope-module-frames-flow-templates.sql module_frames 補 scope+RLS;flow_templates builtin→ROOT(1)+is_builtin policy
5 2026-06-29-system-configs-null-to-root.sql 5 筆 null system_configs → ROOT(1) 平台預設
6 2026-06-29-smtp-platform-only-drop-per-tenant.sql 刪所有非 ROOT 的 per-tenant SMTP(平台層共用)
7 2026-06-29-cleanup-orphan-projects-and-test-root-tenant.sql 刪孤兒 projects + 測試 root tenant 134/135
8 2026-06-29-platform-only-smtp-ldap-log-capabilities.sql SMTP/LDAP/操作日誌 capability 改 admin-only(移除租戶角色)
9 2026-06-29-platform-capability-flag-and-guard.sql capabilities 加 is_platform + 標記 4 組;role_capabilities trigger 擋非 root 角色持平台 cap;清非 root 角色 issue cap + per-tenant 回饋整合設定

⚠️ #7(刪測試 root 134/135 + 孤兒 projects)各環境的孤兒/測試租戶不同——套前先各環境自查(SELECT id,name,path FROM tenants WHERE parent_id IS NULL;SELECT count(*) FROM compliance.projects WHERE tenant_id IS NULL;)。條件式(tenant_id IS NULL / id IN (134,135))多環境通用,但確認該環境的 134/135 確實是測試垃圾再跑。 ⚠️ #6/#8 以 tenant_id<>1 / capability id 收斂;各環境若 capability/role id 不同需先核對(dev 上 smtp=74-77 / ldap=78-81 / log=29)。

2. 程式碼 / 套件

  • BE commit d9a2ffe8(+ 後續 commit)部署。
  • jedi-system-config / jedi-auth / jedi-file-upload:dev 走 path-dep(pyproject.toml 未 commit)。stg/poc/prod 需發版:各套件 bump version → push Nexus → 主專案 pyproject 改回 pin 新版 → poetry update <pkg>。(feature 驗收後、雷門明示才發版。)
    • jedi-system-config:STORAGE_CONFIG tenant-scope 確定性讀寫。
    • jedi-auth:capabilities.is_platform + capability/route 矩陣過濾 + tenant menu parent_id。
    • jedi-file-upload:FR-042 storage_scope。
  • ⚠️ stg/poc/prod 在套件發版前,BE 仍 import 舊版套件 → migration 套了但程式行為要等套件發版才一致。建議「套件發版 + migration」同批上。

3. 套用後

  • 重啟 BE(吃套件 + 程式變更,無 hot reload)。
  • admin / 租戶管理員重新登入(重解析 tenant context + capability,選單才更新)。

4. 驗證(每環境套完跑)

-- admin 入 root + 無殘留 null
SELECT id,login_name,tenant_id FROM users WHERE login_name='admin';            -- tenant_id=1
SELECT count(*) FROM users WHERE tenant_id IS NULL AND is_super_admin=true;     -- 0
SELECT scope,tenant_id FROM user_roles ur JOIN users u ON u.id=ur.user_id WHERE u.login_name='admin'; -- tenant/1
-- system_configs / SMTP
SELECT count(*) FROM system_configs WHERE tenant_id IS NULL;                    -- 0
SELECT id,tenant_id FROM system_configs WHERE "group"='SMTP';                   -- 只剩 tenant 1
-- root 收斂
SELECT id,name,path FROM tenants WHERE parent_id IS NULL;                       -- 只剩 root 1
-- module_frames / flow_templates
SELECT scope,count(*) FROM compliance.module_frames GROUP BY scope;
SELECT tenant_id,is_builtin,count(*) FROM compliance.flow_templates GROUP BY 1,2; -- builtin→tenant 1, 0 null

手測:admin 上傳框架進 minio、儲存設定確定性、子租戶寄信走平台 SMTP、租戶系統設定只剩通知設定。

5. schema_migrations 已登記檔名(核對用)

2026-06-28-fr042-upload-files-add-storage-scope / 2026-06-29-b2-admin-root-tenant / -roles / -fr042-tenant-scope-module-frames-flow-templates / -system-configs-null-to-root / -smtp-platform-only-drop-per-tenant / -cleanup-orphan-projects-and-test-root-tenant / -platform-only-smtp-ldap-log-capabilities