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:
@@ -303,11 +303,19 @@ async fn run_zip_export_inner(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Flip the ready flag only while still current (a re-release between finalize and here
|
||||
// would have cleared it; don't resurrect it for a superseded generation).
|
||||
// Flip the ready flag only while still current AND still released. The `release_seq`
|
||||
// EXISTS check alone is NOT enough: a reopen (`open_event`) landing in the window between
|
||||
// our `finalize_job` above and this UPDATE clears `export_released_at` + both ready flags
|
||||
// but leaves our `export_job` row `done` at this same seq — so EXISTS would still match and
|
||||
// we'd resurrect `export_zip_ready = TRUE` on a keepsake that predates the reopen. The next
|
||||
// re-release would then read that stale TRUE, skip regeneration (`if ready { continue }`),
|
||||
// and serve a snapshot missing every upload added during the reopen window — the exact
|
||||
// stale-keepsake data loss migration 012 exists to prevent. Anchoring on
|
||||
// `export_released_at IS NOT NULL` makes the flip a no-op once a reopen has landed.
|
||||
sqlx::query(
|
||||
"UPDATE event SET export_zip_ready = TRUE
|
||||
WHERE id = $1
|
||||
AND export_released_at IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM export_job
|
||||
WHERE event_id = $1 AND type = 'zip'::export_type
|
||||
AND release_seq = $2 AND status = 'done')",
|
||||
@@ -644,9 +652,12 @@ async fn run_html_export_inner(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Same released-anchored guard as the ZIP flip (see run_zip_export): a reopen between our
|
||||
// finalize and here must not resurrect `export_html_ready` on a pre-reopen snapshot.
|
||||
sqlx::query(
|
||||
"UPDATE event SET export_html_ready = TRUE
|
||||
WHERE id = $1
|
||||
AND export_released_at IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM export_job
|
||||
WHERE event_id = $1 AND type = 'html'::export_type
|
||||
AND release_seq = $2 AND status = 'done')",
|
||||
|
||||
Reference in New Issue
Block a user