fix(restore): re-upload blob to PDS when it's been GC'd (#1325)
the restore path used to strip audioBlob from the republished record
whenever the PDS had already GC'd the revision's original CID, silently
downgrading the track to audio_storage="r2". plyr.fm's core promise is
that users own their audio on their PDS — dropping the blob ref would
break that promise.
new behavior when PDS returns BlobNotFound on the first publish:
1. fetch the R2 bytes via storage.get_file_data(file_id, file_type)
2. upload them to the user's PDS to mint a fresh blob CID
3. republish the record with the fresh audioBlob ref
4. commit the track with audio_storage="both" + the new CID
fallback chain (rare): if R2 is also missing the bytes, or the PDS
rejects the re-upload (oversize, transient), we keep the old behavior
— republish without audioBlob and downgrade to r2-only. restore still
completes; playback keeps working via audio_url.
verified via smoke test on stg.plyr.fm (track 2202) before the fix:
post-restore PDS record had no audioBlob, DB had audio_storage="r2",
pds_blob_cid=null. with this patch, the restored record carries a
first-class PDS blob ref again.
tests:
- rewrote test_restore_falls_back_when_pds_blob_gc →
test_restore_reuploads_blob_when_pds_gc: asserts the retry record
carries the re-uploaded ref and DB ends with audio_storage="both"
- added test_restore_falls_back_to_r2_when_reupload_also_fails: covers
the R2-miss path (retained fallback behavior)
Co-authored-by: Claude Opus 4 (1M context) <noreply@anthropic.com>
authored by