begin; do $$ declare v_active_owner_count integer; v_candidate_count integer; v_candidate_id uuid; v_owner_role_id uuid; begin -- This migration also belongs to the identity-only db migration ledger. When -- the Supabase RBAC schema is not part of that migration flow, record a safe -- no-op without referencing any missing relation or trigger function. if to_regclass('public.admin_users') is null or to_regclass('public.admin_roles') is null or to_regclass('public.admin_user_roles') is null or to_regclass('identity.users') is null or to_regclass('auth.users') is null or to_regprocedure('public.admin_permission_keys(uuid)') is null or to_regprocedure('public.assert_active_admin_owner_exists()') is null then return; end if; -- Serialize recovery with the last-Owner protection used by the RBAC functions. perform pg_catalog.pg_advisory_xact_lock(1096040772, 1); select count(*) into v_active_owner_count from public.admin_users au join public.admin_user_roles aur on aur.admin_user_id = au.user_id join public.admin_roles ar on ar.id = aur.role_id where au.revoked_at is null and ar.code = 'owner'; -- Once an active Owner exists, role management remains the only authority. if v_active_owner_count > 0 then return; end if; -- Keep the candidate set stable while recovery decides and writes. lock table identity.users, auth.users in share mode; -- A pristine database has no account to recover yet. Any populated system -- without an Owner must have exactly one active identity-admin candidate. if not exists (select 1 from identity.users) and not exists (select 1 from auth.users) then return; end if; select count(*), (array_agg(u.id order by u.id))[1] into v_candidate_count, v_candidate_id from identity.users u join auth.users a on a.id = u.id left join public.admin_users existing_admin on existing_admin.user_id = u.id where (existing_admin.user_id is null or existing_admin.revoked_at is null) and (not u.banned or (u.ban_expires is not null and u.ban_expires <= clock_timestamp())) and exists ( select 1 from unnest(string_to_array(u.role, ',')) as role_part(value) where btrim(role_part.value) = 'admin' ); if v_candidate_count <> 1 then raise exception 'admin_owner_recovery_requires_exactly_one_active_identity_admin: found %', v_candidate_count using errcode = '23514'; end if; select id into v_owner_role_id from public.admin_roles where code = 'owner'; if v_owner_role_id is null then raise exception 'admin_owner_recovery_owner_role_missing' using errcode = '23514'; end if; insert into public.admin_users (user_id, created_by) values (v_candidate_id, v_candidate_id) on conflict on constraint admin_users_pkey do nothing; insert into public.admin_user_roles (admin_user_id, role_id, assigned_by) values (v_candidate_id, v_owner_role_id, v_candidate_id) on conflict do nothing; if not exists ( select 1 from public.admin_users au join public.admin_user_roles aur on aur.admin_user_id = au.user_id where au.user_id = v_candidate_id and au.revoked_at is null and aur.role_id = v_owner_role_id ) then raise exception 'admin_owner_recovery_failed' using errcode = '23514'; end if; end; $$; commit;