Fixes the three findings from the independent release review of promotion PR #98 at head c54af1656c71e62e649e5e4d5ca4f3d4f5033d95. Maestro task: 6819ce33-3715-4bd4-9208-a54a8b5cee02.
MQ-98-01 (MEDIUM): GET /dinners/{id}/recipe-candidates now returns an opaque candidate_id
(a keyed hash of the dinner id and the page URL under a per-process key). The server resolves it
to the page it last offered that dinner, so the page URL - which may carry a token - never
reaches a response or the service-worker cache. An id not offered for that dinner, a raw URL, or
an id from before a restart gets 400 invalid_dinner ("reload the candidates"). Grounding by the
opaque id still attaches the right recipe; a successful ground clears that dinner's offers.
MQ-98-02 (MEDIUM): "Turn on notifications" no longer completes onboarding silently when
notifications are unavailable or denied. The step says plainly that nothing was turned on and
offers "Finish setting up", which completes onboarding. This is the interim state until
Feature 19 wires Web Push (marked with a TODO).
MQ-98-03 (LOW): GroundPendingDinner now clears recipe_url on successful background grounding,
matching the manual grounding path, so a token-bearing pasted link no longer persists in the
database and backups. Duplicate detection by link still works via the recipe's source_url.
No schema change; no new migration; sqlc regenerated for the query change.
Checks: gofmt clean, go vet, go test -race -count=1 ./..., CGO_ENABLED=0 go build ./..., make sqlc-check (no drift), make build, docker build ., and web typecheck + 536 tests + build
all pass. New tests fail against the base head a364839.
Fixes the three findings from the independent release review of promotion PR #98 at head
`c54af1656c71e62e649e5e4d5ca4f3d4f5033d95`. Maestro task: `6819ce33-3715-4bd4-9208-a54a8b5cee02`.
- MQ-98-01 (MEDIUM): `GET /dinners/{id}/recipe-candidates` now returns an opaque `candidate_id`
(a keyed hash of the dinner id and the page URL under a per-process key). The server resolves it
to the page it last offered that dinner, so the page URL - which may carry a token - never
reaches a response or the service-worker cache. An id not offered for that dinner, a raw URL, or
an id from before a restart gets 400 `invalid_dinner` ("reload the candidates"). Grounding by the
opaque id still attaches the right recipe; a successful ground clears that dinner's offers.
- MQ-98-02 (MEDIUM): "Turn on notifications" no longer completes onboarding silently when
notifications are `unavailable` or `denied`. The step says plainly that nothing was turned on and
offers "Finish setting up", which completes onboarding. This is the interim state until
Feature 19 wires Web Push (marked with a TODO).
- MQ-98-03 (LOW): `GroundPendingDinner` now clears `recipe_url` on successful background grounding,
matching the manual grounding path, so a token-bearing pasted link no longer persists in the
database and backups. Duplicate detection by link still works via the recipe's `source_url`.
No schema change; no new migration; sqlc regenerated for the query change.
Checks: gofmt clean, `go vet`, `go test -race -count=1 ./...`, `CGO_ENABLED=0 go build ./...`,
`make sqlc-check` (no drift), `make build`, `docker build .`, and web typecheck + 536 tests + build
- all pass. New tests fail against the base head `a364839`.
Release corrections from the review of promotion pull request #98:
- MQ-98-01: recipe-candidates returns an opaque candidate_id (an HMAC of
the dinner and page under a per-process key) that the server resolves
to the page it last offered that dinner. Candidate URLs, which may
carry a token, no longer reach responses or the service-worker cache.
- MQ-98-02: "Turn on notifications" no longer finishes onboarding
silently when notifications are unavailable (or denied); the step says
plainly that nothing was turned on and offers "Finish setting up".
The placeholder stays until Feature 19 wires Web Push.
- MQ-98-03: GroundPendingDinner clears the pasted recipe_url when
background grounding succeeds, as manual grounding already did.
Maestro task 6819ce33-3715-4bd4-9208-a54a8b5cee02.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make build overwrote the tracked internal/frontend/dist/index.html
placeholder in the previous commit; put it back as it is on dev.
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>
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>
Store each offer set's deadline and check it under offersMu in
candidateURL, so an expired candidate id cannot ground a dinner while its
eviction timer is delayed. The timer still evicts the set from memory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes the three findings from the independent release review of promotion PR #98 at head
c54af1656c71e62e649e5e4d5ca4f3d4f5033d95. Maestro task:6819ce33-3715-4bd4-9208-a54a8b5cee02.GET /dinners/{id}/recipe-candidatesnow returns an opaquecandidate_id(a keyed hash of the dinner id and the page URL under a per-process key). The server resolves it
to the page it last offered that dinner, so the page URL - which may carry a token - never
reaches a response or the service-worker cache. An id not offered for that dinner, a raw URL, or
an id from before a restart gets 400
invalid_dinner("reload the candidates"). Grounding by theopaque id still attaches the right recipe; a successful ground clears that dinner's offers.
notifications are
unavailableordenied. The step says plainly that nothing was turned on andoffers "Finish setting up", which completes onboarding. This is the interim state until
Feature 19 wires Web Push (marked with a TODO).
GroundPendingDinnernow clearsrecipe_urlon successful background grounding,matching the manual grounding path, so a token-bearing pasted link no longer persists in the
database and backups. Duplicate detection by link still works via the recipe's
source_url.No schema change; no new migration; sqlc regenerated for the query change.
Checks: gofmt clean,
go vet,go test -race -count=1 ./...,CGO_ENABLED=0 go build ./...,make sqlc-check(no drift),make build,docker build ., and web typecheck + 536 tests + builda364839.Independent Codex gpt-6-sol review: CHANGES_REQUIRED for this exact commit.
Independent Codex gpt-6-sol review: CHANGES_REQUIRED for this exact commit.
Independent Codex gpt-6-sol review: CHANGES_REQUIRED for this exact commit.
Independent Codex gpt-6-sol review: APPROVED for this exact commit.