Files
silo-server/internal
Quick104andClaude Fable 5 80397a5eb3 fix(downloads): enforce artifact links with an FK and make eviction leak-proof
Remediates review findings on the eviction fence:

- The EXISTS/NOT EXISTS fences read independent snapshots under READ
  COMMITTED and take no conflicting locks, so a concurrent evict/link
  pair can both commit (reproduced empirically in both interleavings),
  leaving a ready download pointing at a deleted artifact. New
  downloads_artifact_id_fkey (ON DELETE RESTRICT) is the enforcement
  layer: RI triggers take FOR KEY SHARE and re-check on a fresh snapshot.
  The fences stay as fast-path filters; 23503 maps to ErrArtifactEvicted
  on insert/replace and reads as 'not evictable' on delete. Terminal
  transitions null artifact_id so dead rows cannot pin artifacts; the
  migration heals legacy dangling/terminal links before the constraint.
- Row-first eviction could leak bytes when the unlink failed after the
  row was gone (no local orphan queue, no reaper; remote leaked when the
  node delete and orphan enqueue both failed). Cleanup intents are now
  recorded durably BEFORE the row delete — a new
  download_artifact_local_orphans queue mirrors the remote one — and a
  failed enqueue skips the candidate so the row survives for retry.
- Output paths now include the artifact id, so a replacement row can
  never claim an evicted row's path; the OutputPathInUse probe (an
  unindexed seq scan with a TOCTOU window) is gone.
- The failed-artifact hygiene sweep is fenced (DeleteIfEvictable):
  Ensure requeues failed rows, so the unfenced delete could remove an
  artifact a fresh download had just linked, stranding it in preparing.
- ReplaceManagedEntry drops the non-atomic artifact probe; zero rows now
  unambiguously means revision/identity conflict and self-heals via
  GetManagedEntry. The replace retry refreshes its row snapshot so the
  second attempt's revision fence can succeed.
- Reuse/cleanups: shared withEnsuredArtifact retry helper, downloadColumns
  interpolated into all insert sites, execInsertDownload shared by
  Create/CreateBatch, EnsureDevice hoisted out of the retry loop,
  HasActiveLink and unconditional DeleteArtifact deleted, distinct log
  messages for the two sweep failure paths.

DB-backed tests cover the FK both directions, the requeued-artifact sweep
fence, local orphan round-trip, terminal unpinning, revision-conflict
self-healing, and a 100-round concurrent evict-vs-link race asserting no
dangling links.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:32:11 -04:00
..
2026-05-22 23:26:56 -04:00
2026-05-22 23:26:56 -04:00
2026-05-22 23:26:56 -04:00
2026-05-22 23:26:56 -04:00
2026-05-22 23:26:56 -04:00
2026-05-22 23:26:56 -04:00