edit_upload updated the caption/hashtags and nothing else. A caption lives in the HTML
viewer keepsake (the ZIP holds media only — export.rs), so after release the downloadable
viewer kept showing the OLD caption forever while the live feed showed the new one.
Editing stays allowed while locked/released — like comments and likes, the lock freezes
new uploads only (USER_JOURNEYS §9.3) — so the fix is to regenerate, not forbid: the
edit and an invalidate_and_arm(ViewerOnly) share one transaction (same atomicity as
delete_upload), then start_regen after commit. ViewerOnly carries the finished ZIP forward
untouched since the media didn't change; when the gallery isn't released, invalidate_and_arm
returns None and this is a no-op. Mutation-verified: without it, an edit doesn't bump the
epoch and the caption test fails.
Also (test isolation): the compression worker now carries a generation counter that
TRUNCATE bumps. A worker queued on the concurrency semaphore when a truncate wiped media
would otherwise wake in the NEXT test, fail to find its file, and broadcast
upload-error/upload-deleted into that test's SSE stream — corrupting any test asserting on
toasts or feed. It now abandons itself when the generation moved. No-op in production
(TRUNCATE is the only caller). Export workers were already inert across a truncate (epoch
guard on a fresh-UUID event).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>