-- Add per-user recipient/sender columns so conv-keys can be wrapped to a -- user identity instead of a specific device. Old per-device columns stay -- nullable through the transition; a follow-up migration drops them once -- migration telemetry shows >=95% adoption. alter table public.conversation_keys add column if not exists recipient_user_id uuid references auth.users(id) on delete cascade, add column if not exists sender_user_id uuid references auth.users(id) on delete restrict; alter table public.conversation_keys alter column recipient_device_id drop not null, alter column sender_device_id drop not null; create unique index if not exists conversation_keys_user_recipient_uniq on public.conversation_keys(conversation_id, recipient_user_id, key_version) where recipient_user_id is not null; create index if not exists conversation_keys_recipient_user_idx on public.conversation_keys(recipient_user_id); drop policy if exists conversation_keys_select_owner on public.conversation_keys; create policy conversation_keys_select_owner on public.conversation_keys for select to authenticated using ( recipient_user_id = auth.uid() or exists ( select 1 from public.devices d where d.id = recipient_device_id and d.user_id = auth.uid() ) ); drop policy if exists conversation_keys_insert_member on public.conversation_keys; create policy conversation_keys_insert_member on public.conversation_keys for insert to authenticated with check ( exists ( select 1 from public.conversation_members m where m.conversation_id = conversation_keys.conversation_id and m.user_id = auth.uid() and m.accepted = true ) and ( sender_user_id = auth.uid() or exists ( select 1 from public.devices d where d.id = sender_device_id and d.user_id = auth.uid() ) ) and ( (recipient_user_id is not null and exists ( select 1 from public.conversation_members m where m.conversation_id = conversation_keys.conversation_id and m.user_id = recipient_user_id and m.accepted = true )) or (recipient_device_id is not null and exists ( select 1 from public.devices d join public.conversation_members m on m.user_id = d.user_id and m.conversation_id = conversation_keys.conversation_id where d.id = recipient_device_id and m.accepted = true )) ) );