Each dinner's offered candidates now carry a time.AfterFunc expiry that
drops them from the service once offerLifetime passes, so token-bearing
candidate URLs are no longer held until some later search sweeps them.
Storing new offers or clearing a dinner's offers stops the old timer; the
timer drops only the set it was armed for. The expiry runs no goroutine
until it fires and touches only the in-memory map.
TestCandidatesExpire now waits for the eviction with no further search
and checks the candidate no longer grounds; TestExpiredCandidatesNotHeld
checks no dinner's expired offers remain held.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Bump the service worker API cache to v3 so activation deletes the v2
cache, whose recipe-candidate responses used the full page URL as
candidate_id; test that activation deletes v1 and v2 and keeps v3.
- Give candidate offers a 30-minute lifetime, drop expired offers when
new ones are stored, and clear a dinner's offers on every hand
grounding (candidate, link or manual) and on archive. A candidate
search still running when its dinner is cleared stores nothing, so a
stale candidate_id gets the same 400 as an unknown one.
- Make the link leak assertions in TestDinnerResponsesHideTokenInLinkPath
and TestCreateRegularNeverNamesFromLink check phrases a ULID cannot
contain, so a dinner id holding "KEY" no longer fails them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>