Skip to content

fix(worker): A Live Photo's photo soft-deleted at classification takes its video along (#350) - #371

Merged
bitbiter-dev merged 2 commits into
masterfrom
fix/350-retire-live-photo-video
Oct 29, 2026
Merged

bitbiter-dev merged 2 commits into
masterfrom
fix/350-retire-live-photo-video

Conversation

@bitbiter-dev

Copy link
Copy Markdown
Owner

Closes #350

Summary

A video has one photo (design note, option 2): pairing hands it over, retiring takes it along, and a unique index pins it.

 ClassificationMiddleware (nothing to process)
   LivePhotoPairer.PairAsync
-    heic.PairedAssetId = Resolve(...).Id
+    PairedMovResolver.PairAsync           # the one place a video changes photo
+      stored video → other photos still pointing at it let go (active one logged: now a still photo)
   KnownFileReconciler
-    OverwrittenByCopy / OverwrittenByMove / LoneLivePhotoVideo:
-      row.IsSoftDeleted = true; StageSoftDeleted()
-      (moved row drops PairedAssetId when the path holder points at the same video)
+    StoredContent.RetireAsync(row)       # the one place a row is retired at classification
+      already soft-deleted → nothing, not counted again
+      else soft-delete + count; PairedAssetId → video soft-deleted + counted, same save

 ProcessedContentWriter
-  asset.PairedAssetId = Resolve(...).Id
+  PairedMovResolver.PairAsync

 MissingFileSweep
-  retire active videos of missing OR soft-deleted photos
+  retire active videos of missing photos

 MediaAssets
-  INDEX IX_MediaAssets_PairedAssetId
+  UNIQUE INDEX IX_MediaAssets_PairedAssetId   (migration OneLivePhotoPerVideo)

Evidence

  • Before: cp B.heic A.heic onto Live Photo A → A soft-deleted, A's video active until a complete crawl (never, with an unreadable folder); the test LivePhotoCopiedOverAnother_…RetiresOnlyTheOverwrittenPhoto asserted exactly that.
    After: A and its video soft-deleted in the same save, softDeleted = 2, even when the item never loaded the video (LivePhotosPhotoCopiedOverWithItsVideoUnread); the next crawl leaves both soft-deleted (CopyOverARetiredLivePhotoCrawledAgain).
  • mv B.* A.* onto a Live Photo: softDeleted = 2 (B and its video), not 3. Onto a still photo: B's video stays active, A points at it, B's pointer is null, softDeleted = 1 (pins the pairer running before the reconciler).
  • Integration (IngestionSaverTests, real migrated PostgreSQL): the hand-off of mv B.* A.* onto a still photo saves under the unique index after RetiredFirst, for both id orderings, so EF orders the two UPDATEs itself; no fallback needed.
  • Worker unit suite 986/986; scripts/mutation-changed.sh 100.00% for Core and Worker.

Merge Danger

Door: two-way

The unique index is the only hard-to-reverse part, and only once there is data; nothing is deployed, so it is reversible now.

Blast Radius: ingestion

A path that would make two photos share a video now fails the item's save instead of sharing silently (LostIngestionRace does not match this index). #370 (the video a photo lets go of) is unchanged.

🤖 Generated with Claude Code

bitbiter-dev and others added 2 commits October 29, 2026 21:57
…s its video along (#350)

A video has one photo: pairing hands it over (PairedMovResolver.PairAsync,
used by LivePhotoPairer and ProcessedContentWriter) and a unique index on
PairedAssetId pins it. Retiring at classification goes through
StoredContent.RetireAsync, which soft-deletes the photo's video in the
item's one save and counts each row once. MissingFileSweep no longer
retires the video of a soft-deleted photo, and KnownFileReconciler no
longer drops a moved row's pointer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le out of summaries (#350)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bitbiter-dev
bitbiter-dev merged commit 4b33b29 into master Oct 29, 2026
17 checks passed
@bitbiter-dev
bitbiter-dev deleted the fix/350-retire-live-photo-video branch October 29, 2026 09:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(worker): A Live Photo's photo soft-deleted at classification leaves its video active

1 participant