Files
pad/internal/store
xarmian 7680919dbd fix(store): re-stamp attachment refs on item/document restore so a racing GC claim is refused (BUG-2629) (#1155)
An attachment referenced only from soft-deleted content was reclaimable by
the orphan GC: AttachmentReferenced scans LIVE rows only, so the archived
reference is invisible to the sweep, and past the grace period the claim
reclaims the blob. On restore the reference is live again and dangles.

The reachable case is a never-attached upload (item_id NULL) referenced from
a document — documents have no document_id column, so such a row is
necessarily never-attached and the ClaimNeverAttachedAttachment predicate
applies with nothing else standing in its way — or from an item's content
where the attachment was uploaded unattached.

RestoreItem and RestoreDocument now call stampAttachmentRefsTx before
clearing deleted_at, inside the restoring transaction, per that helper's
ORDERING contract: the stamp row-locks the attachment, so a GC claim racing
the restore blocks until commit and re-evaluates last_referenced_at against
the fresh stamp — refusing. RestoreItem stamps content + fields;
RestoreDocument (previously a bare Exec) is wrapped in a transaction that
reads the soft-deleted content + workspace and stamps content.

This is prevention only: a blob already reclaimed before the restore is gone
(the claim is irrevocable by design), and the restore-time stamp matches
zero rows. Surfacing an already-dangling reference to the user on restore is
tracked separately as IDEA-2646.

Regression tests archive content holding the only reference, age the stamp
past the claim's stale window, restore, then run the claim directly (the
sweep's live scan would protect now-live content and pass for the wrong
reason). All three legs fail on unfixed code.

Claude-Session: https://claude.ai/code/session_017jD6t1zjxGSq47SQpZfp1V
2026-08-18 14:43:13 -04:00
..