Description
Problem
Deletion commits the metadata soft-delete first, then deletes backend bytes (internal/server/storage_objects.go:223-230; the shared attachment path does the same in internal/server/description_attachments.go:36-43). Profile/project image replacement silently discards backend-delete failures (internal/server/profile_images.go:265-273).
If S3/GCS/local deletion fails, the API returns 500 after the database change has committed. The object is then filtered from reads, so retrying the same route returns 404 and the backend bytes have no durable retry path. TestHTTPStorageObjectDeleteBackendFailure only asserts the initial 500 and does not verify retryability or final byte removal.
Impact
Transient backend failures create permanent, inaccessible objects, causing storage cost, retention/privacy problems, and misleading client semantics.
Acceptance criteria
- Persist pending backend deletion work transactionally in Postgres (or retain queryable deletion state) and process it idempotently from the Go binary.
- Retry transient failures with bounded backoff and expose/log terminal failures.
- Cover raw objects, issue/project/sprint/context attachments, and profile/project image replacement/removal.
- Make the HTTP response accurately describe the committed logical deletion.
- Add a failure-then-success test proving bytes are eventually removed and no orphan remains.
Sub-issues
0Linked issues
0GitHub
0No branches or pull requests linked.