Replace sync soft-deletes with hard deletes - #184
Conversation
Stop tombstoning synced rows (deleted=true) and DELETE them instead, so deletions are committed immediately rather than relying on the 24h cleanup_sync_tombstones reaper. - markDeletedMessages -> deleteSyncedMessagesFrom: ranged DELETE instead of UPDATE deleted=true (same chunked/bounded logic). - deleteSyncedChat: single ranged DELETE of messages (messages first, then the chat row, so a failure stays retryable) instead of the maxIndex read + chunked soft-delete. - deleteSyncedPersona: DELETE instead of UPDATE deleted=true. Existing DELETE RLS policies already permit this; no schema changes. Read filters on deleted=false are left in place to keep hiding any legacy tombstones until the cron drains them.
faetalize
left a comment
There was a problem hiding this comment.
I found one blocking issue in this PR.
The change switches chat/persona deletes from tombstones to hard deletes, but the pull path still relies on fetched deleted = true rows to propagate removals to other local stores. With the new behavior, a device that already has a local chat/persona copy will never see a remote tombstone after another device deletes it, so the local copy can remain stale and later be resurrected by an edit or sync. Either keep the tombstone contract until there is a replacement reconciliation path, or add explicit local-vs-remote ID reconciliation before hard deleting old rows.
| // ── Delete the chat row ──────────────────────────────────────── | ||
| const { error: chatError } = await supabase | ||
| .from("user_synced_chats") | ||
| .delete() |
There was a problem hiding this comment.
Blocking: hard-deleting the user_synced_chats row removes the tombstone that pullChats() currently depends on. pullChats() only deletes local IndexedDB copies when it fetches a row with deleted === true; if this row is gone, another device with an existing local copy has no deletion signal and can retain or later resurrect the chat. Keep the soft-delete tombstone here, or add a replacement reconciliation path that removes local IDs absent from the remote set before hard deletes are allowed.
| const { error } = await supabase | ||
| .from("user_synced_personas") | ||
| .update({ deleted: true }) | ||
| .delete() |
There was a problem hiding this comment.
Same propagation issue for personas: pullPersonas() only removes a local persona when it receives a remote row marked deleted. Hard-deleting this row means other devices with cached/local persona data never receive that signal. Please preserve the tombstone behavior or introduce explicit local/remote reconciliation for missing persona IDs.
Summary
Sync.service.ts.Related: #182
Validation