Files
pad/internal
xarmian 8cdeeb166b fix(store): orphan GC claim protocol — writer reference stamps + conditional row-first deletes (BUG-2415) (#1129)
* fix(store): orphan GC claim protocol — writer reference stamps + conditional row-first deletes (BUG-2415)

The sweep scanned content for pad-attachment: references, then deleted
the BLOB, then the row — with nothing serializing it against content
writers. A reference committing between scan and reclaim left either a
dangling id or, worse, a surviving row whose bytes were already gone.

Claim protocol:
- attachments.last_referenced_at (dual-dialect migration): every
  content writer that persists a pad-attachment: reference stamps the
  rows INSIDE its own write transaction (stampAttachmentRefsTx), wired
  at the four store chokepoints every surface funnels through —
  CreateItem, the UpdateItem core (item PATCH, collab-snapshot flush,
  version restore, bulk update), CreateComment, UpdateComment (both now
  transactional). Workspace-scoped; covers content AND fields, matching
  AttachmentReferenced's scan surface.
- The sweep's row deletion is now the atomic claim: a conditional
  DELETE re-asserting reclaimable state in the statement itself
  (ClaimNeverAttachedAttachment: unattached + live + no fresh stamp;
  ClaimSoftDeletedAttachment: still deleted + still past grace, so a
  mid-sweep restore survives too). Writer stamp and claim serialize at
  the database; whichever commits first wins and the loser observes it.
- Row BEFORE bytes: the blob is reclaimed only after a successful
  claim, so a surviving row implies surviving bytes — the old order's
  worst failure mode (row without content) is structurally impossible.
- orphanGCRefStaleWindow (15m) is documented as a correctness
  parameter: the stamp only covers references landing after the scan,
  so the window bounds scan-to-claim latency plus a maximally stalled
  writer transaction — not a lease on long-lived references (the LIKE
  scan still guards those).

Sweep-level test pins the filed race (fresh stamp survives sweep, row
AND blob) with a counterfactual arm (aged stamp reclaims); verified
discriminating against a compiling control build of the old sweep
order. Store tests cover every claim predicate leg, stamp wiring on
all four chokepoints, and workspace scoping.

* fixup: codex round 1 — stamp move-override + workspace-import paths, parent-aware variant protection (scan by parent id + claim NOT EXISTS fresh parent stamp), variant test with total-loss control

* fixup: codex round 3 — stamps ordered BEFORE content statements (PG row-lock makes the claim wait out the writer tx), chunked stamp IN-lists, BlobDeleteFailures counter

* fixup: codex round 4 — stamp variants of referenced originals (own-row lock protects concurrently-claimed thumbnails), bounded-duration residual + irrevocability docs
2026-08-17 01:33:18 -04:00
..
2026-03-26 01:52:36 +00:00