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:
@@ -17,6 +17,18 @@
|
||||
* superseded worker discards its output instead of resurrecting a stale keepsake. The
|
||||
* download follows the current `done` row's `file_path`, never a fixed name.
|
||||
*
|
||||
* A second, narrower window on the SAME class of bug (fixed alongside): a reopen (`open_event`)
|
||||
* landing between a *current* worker's `finalize_job` and its ready-flag flip. `open_event`
|
||||
* clears `export_released_at` + both ready flags but leaves the `export_job` row `done` at the
|
||||
* current seq — so the seq-guarded flip would still match and resurrect `export_zip_ready=TRUE`
|
||||
* on a pre-reopen snapshot, and the next re-release would `if ready { continue }` and skip
|
||||
* regeneration. The flip UPDATEs are therefore additionally anchored on
|
||||
* `export_released_at IS NOT NULL`, making them a no-op once a reopen has landed. The
|
||||
* "completeness" test below exercises the real reopen→re-release worker path end-to-end; the
|
||||
* sub-millisecond finalize↔flip interleave itself isn't deterministically forceable with the
|
||||
* fast fixtures, so that exact window is covered by the SQL guard + code review rather than a
|
||||
* timing-dependent assertion.
|
||||
*
|
||||
* Coverage:
|
||||
* - churn integrity: rapid reopen→re-release yields exactly one INTACT ZIP, no stuck jobs.
|
||||
* - completeness: an upload added during the reopen window IS present in the re-released
|
||||
|
||||
Reference in New Issue
Block a user