fix(rereview): close export-flip race + queue-dedup reload regression

Adversarial re-review of the persona-audit + audit-followup rounds (6411747..)
found one HIGH and one MED regression plus LOW gaps. All fixed with coverage.

HIGH — export stale-keepsake resurrected by an open_event race
  A reopen landing in the window between a *current* export worker's finalize_job
  and its ready-flag flip cleared export_released_at + the ready flags but left the
  export_job row `done` at the same release_seq. The seq-guarded flip then still
  matched and re-set export_{zip,html}_ready=TRUE on a pre-reopen snapshot; the next
  re-release read that stale TRUE and skipped regeneration (`if ready { continue }`),
  serving a keepsake missing every upload from the reopen window — the exact data
  loss migration 012 exists to prevent. Both ready-flip UPDATEs are now additionally
  anchored on `export_released_at IS NOT NULL`, so a landed reopen makes the flip a
  no-op and the re-release regenerates cleanly.

MED — queue dedup broke for reloaded items
  loadQueue rebuilt QueueItems from IndexedDB without copying lastModified, which the
  new addToQueue dedup keys on. A file re-selected after a page reload / PWA relaunch
  missed the duplicate check and uploaded twice. Rehydration now carries lastModified
  (extracted to a pure, tested entryToQueueItem helper).

LOW
  - diashow: clear the upload-processed debounce timer in onDestroy (no stray
    post-unmount /feed fetch).
  - USER_JOURNEYS §9.5: document the reconnect-delta ban replay (hidden_user_ids /
    uploads_hidden_at, migration 013), not just the live user-hidden SSE.
  - e2e api-client: drop the misleading hide_uploads param from banUser — the backend
    takes no body and always hides; strip the dead boolean at all call sites.

Tests
  - Extract isReversibleLock (the terminal-403 KEEP-vs-PURGE-blob discriminator) into a
    pure exported helper + unit tests, so the data-loss-critical branch is covered
    without an XHR harness.
  - entryToQueueItem unit tests lock the lastModified-carry regression.
  - Document the export flip-race guard in the reopen/re-release spec (the sub-ms
    finalize↔flip interleave isn't deterministically forceable with fast fixtures;
    covered by the SQL guard + the end-to-end completeness test).

Verified: backend 40 tests, frontend 44 unit tests, svelte-check 0 errors,
e2e 156 passed / 1 skipped on chromium-desktop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
fabi
2026-07-13 21:47:44 +02:00
parent 768e712a26
commit df275bbefa
10 changed files with 166 additions and 33 deletions

View File

@@ -167,7 +167,11 @@ the Host can clean up later).
5. Banning **always hides**: the user's existing uploads are filtered out of the feed for
everyone (`v_feed`, `find_visible_media`, and the export query all enforce
`is_banned = FALSE`), and a live `user-hidden` SSE event evicts their cards from every
open feed + the diashow without a reload.
open feed + the diashow without a reload. A client that was offline/disconnected during
the live event doesn't miss the eviction: `uploads_hidden_at` is stamped at ban time
(migration 013) and the reconnect delta (`GET /feed/delta`) returns the banned users in
`hidden_user_ids`, so the feed and diashow replay the eviction on the next reconnect —
the projector-missed-the-live-push case is exactly why this exists.
## 11. Admin — instance configuration