Follow-up to the adversarial re-review of the audit-fix branch.
MUST-FIX:
- H3: bound aggregate upload RAM. The body limit alone didn't cap concurrency,
so ~N parallel 550MB uploads could OOM the box. Add an Arc<Semaphore>
(UPLOAD_MAX_CONCURRENCY=4) in AppState, acquired at the top of the upload
handler before the multipart body is read, so waiting requests hold only a
connection — peak buffered RAM ≈ 4×550MB. (Matches the CompressionWorker
semaphore pattern; avoids tower's non-default `limit` feature.)
- M8: release_gallery now runs the claim UPDATE + both job INSERTs in one
transaction, so the row lock is held until the jobs exist — closing the
cross-table TOCTOU where two concurrent presses could both spawn workers
racing the same output files. Export temp filenames are now per-run
(Gallery.{uuid}.zip.tmp, viewer_tmp_{event}_{uuid}, Memories.{uuid}.zip.tmp)
so overlapping runs can't truncate each other. Final served names unchanged.
- M11: 'user-banned' was missing from sse.ts KNOWN_EVENTS, so EventSource
silently dropped the frame and the forced-logout handler never fired. Added.
SHOULD-FIX:
- M8-3: re-release is now allowed when nothing is in progress and at least one
job failed (was: only when *every* job failed), so a one-sided failure (zip
done, html failed) is no longer permanently unrecoverable.
- M18: two light-mode contrast spots the earlier sweep missed — the LightboxModal
char-counter class: directives and the account PIN-missing notice.
- M15: the diashow auto-advance is now slowed to a ≥30s floor under
prefers-reduced-motion (WCAG 2.2.2), making the app.css comment accurate.
Verified: cargo build + 6 tests, npm run check (0 errors), and a live smoke test
against Postgres — first release 204, second 400, one-sided-failure re-release
204, no SQL errors; a normal upload still returns 201 through the new permit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>