建議 model:Opus,effort:medium——動 session_scope 的 is_super 判定,錯了是靜默壞。本卡由 CM-1559 拆出(決策者 2026-09-14 裁「拆兩刀,1.20.0 只做第二刀」),原卡結案。前置:.1a CM-1767(沿用它開的 is_system_context 欄位)。
資料庫隔離靠 session_scope() 在每次查詢前設定「這個人是誰、能看哪些租戶」。現在的判定有一條「tenant_id 是空值(None 或 0)就當超級管理員」——這是從「缺值」推論出最高權限,方向反了。X-Tenant-ID: 0 那條路已被 FR-069.16 擋死、DEV 40 個使用者 tenant_id 都不是 NULL,所以現在打不到,但它是縱深防禦少的一層,而且 jedi-iam 的 signed-token 流程刻意把 tenant_id 設成 None 來「借用」這個洞取得最高權限——洞補了它會壞,所以要一起改。
首腦核對(沿用 CM-1559 第一步盤點 2026-09-06 + 2026-09-14 複查):
is_super = (not user_ctx.tenant_id) or _session_paths_have_root(...)——要拿掉 not user_ctx.tenant_id。_set_minimal_context():docstring 自己寫明「tenant_id=None → is_super」是刻意依賴洞。build_user_context() 與 JWT 路徑同源;查 user 那一句用 jedi_iam/infra/elevated_session.py:38 elevated_readonly_session() 窄提權(只包那一句查詢)。~/Projects/Jedicogy/module/jedi-python-package/jedi-common/jedi_common/session/database/db.py:100-104 is_super 判定(拿掉 not tenant_id)
~/Projects/Jedicogy/module/jedi-python-package/jedi-iam/jedi_iam/authz/signed_token.py:83-100 _set_minimal_context(改填真實身分)
~/Projects/Jedicogy/module/jedi-python-package/jedi-iam/jedi_iam/middleware/context.py:80 build_user_context(現成組裝邏輯,沿用)
~/Projects/Jedicogy/module/jedi-python-package/jedi-iam/jedi_iam/infra/elevated_session.py:38 elevated_readonly_session(窄提權樣板)
~/Projects/Jedicogy/module/jedi-python-package/jedi-iam/jedi_iam/api/routes/otp_route.py:53 OTP QR 端點(改完要驗)
is_system_context 在 jedi-common 有命中。沒有就停下回寫。is_super = user_ctx.is_system_context or _session_paths_have_root(user_ctx.allowed_tenant_paths)。不動 elif is_pg: 無 context 那條(那是第一刀 CM-<第一刀卡號>,延後)。把「無 tenant_id 的系統情境亦為 super_admin」那行過時註解收掉。_set_minimal_context(user_id) 改成:用 elevated_readonly_session() 包一句查 user(by uid),拿到 UserDTO 後呼叫 build_user_context(user, tenant_id_override=None) 設 context。先查再寫:build_user_context 需要 user 含 tenants/org_units,確認 elevated 查法能 load 到;不要另寫第二套組裝。user 不存在 → 401,不要 fallback 成 super。not user_ctx.tenant_id 加回去 → 前兩個測試要紅。