-- Keep a client upload key CLAIMED when the deletion was a host takedown. -- -- Migration 026 narrowed `upload_client_upload_id_key` to live rows so that a guest who deletes -- their own photo and whose queue later retries gets a fresh upload instead of a permanent 409. -- That rationale reasoned only about the GUEST deleting. `deleted_at` is also set by -- `host_delete_upload`, and for that case the same rule undoes a moderation decision: -- -- 1. Guest uploads. The row commits and the photo appears in the feed, but the response is lost -- on the way back (the flaky-wifi case this whole feature exists for), so the phone keeps the -- queue item. -- 2. The host sees the photo and takes it down. `deleted_at` is stamped, the keepsake epoch is -- bumped, and the archive is rebuilt without it. -- 3. Ten minutes later the phone reconnects and retries. The key is no longer claimed, the -- INSERT succeeds, and the photo is BACK — in the feed, in the next keepsake, under a NEW -- uuid that matches nothing in the host's moderation history, with nothing logged to say a -- takedown was undone. -- -- So the key stays claimed for a host takedown and is released only for a guest's own delete. The -- retry then resolves to the duplicate path and is refused, which is the correct answer: the photo -- was deliberately removed, and re-sending the bytes must not bring it back. ALTER TABLE upload ADD COLUMN taken_down_by_host BOOLEAN NOT NULL DEFAULT FALSE; -- KEEP THE PREDICATE IN LOCKSTEP WITH `Upload::create`'s ON CONFLICT clause (models/upload.rs). -- A drift between the two is not a compile error here — queries are checked at runtime — it is a -- 500 on every upload that carries a key, i.e. on exactly the retries this index exists to serve. DROP INDEX IF EXISTS upload_client_upload_id_key; CREATE UNIQUE INDEX upload_client_upload_id_key ON upload (client_upload_id) WHERE client_upload_id IS NOT NULL AND (deleted_at IS NULL OR taken_down_by_host);