Put it in the select policy, then no query can forget it:
create policy "own active projects" on public.projects
for select to authenticated
using ((select auth.uid()) = owner_id and deleted_at is null);Catch: once the row is soft deleted, it's invisible to its owner, so update ... set deleted_at = null for undo finds nothing. Do the undo through a small security definer function that checks ownership and restores it, or add a separate policy that lets owners see their own deleted rows only from a "trash" page (e.g. a view with security_invoker).