Migration 020 (019 reserved per plan) introduces two SECURITY DEFINER RPCs that back the trash drawer's per-row actions: - restore_list(uuid): clears deleted_at; requires caller to be admin or member of the owning collective; rejects if the list is not currently in trash (defensive) - hard_delete_list(uuid): permanently deletes a soft-deleted list; same role guard; rejects if the list is still active Both close the existing UI gap where guest could in principle issue the equivalent UPDATE (the FROM-UPDATE policy is is_active_member, which excludes guest, but the surface area is broader than these two intents — the RPCs make the contract explicit). The lists drawer already exposed the Restore + Delete-for-ever buttons; only the store calls swap from chained PostgREST writes to .rpc() — the UI itself is unchanged. pgTAP 012 (8 assertions) covers existence, restore happy path, non-member rejection, hard-delete happy path, active-list guard. Integration T-10..T-14 (5 vitest) verifies the wire format the store relies on (rpc errors, row state, active-listing visibility). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
3.5 KiB
3.5 KiB