fix(M10): close pair/stream race + demo-block on cancelPairing

Twee P1's uit code-review:

(1) pair/stream race: de findUnique die de pairing-status leest gebeurde vóór
LISTEN actief was. Als de mobiel approvet tussen die query en LISTEN: pg_notify
fired in dat venster gaat verloren (Postgres queuet niet voor abonnees die
nog niet listen) én was de eerder gelezen status stale. De catch-up state-
event emitte dus 'pending' terwijl de DB inmiddels 'approved' was, en de
desktop bleef hangen tot expiry.

Tweede findUnique toegevoegd ná LISTEN actief is: het venster sluit, omdat
elke approve na dat punt via de notify-handler doorkomt. Aanvullend op de
eerdere client-side fix die 'state' events nu ook routeert (commit d6e71f9).

(2) cancelPairing demo-block: cancel was een DB-write zonder demo-guard,
in tegenspraak met de "demo = 403 op writes"-regel. Demo-blokkade
toegevoegd; bestaande test omgedraaid naar 'wordt geblokkeerd, geen DB-write'.

Quality gates: lint 0 errors, tsc clean, vitest 139/139.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-04-27 23:56:21 +02:00
parent d6e71f915c
commit a9616ff122
3 changed files with 22 additions and 11 deletions

View file

@ -152,17 +152,27 @@ export async function GET(
cleanup('pg error')
})
// Initial state — voorkomt race waarbij approve net vóór SSE-open valt
// Initial state — dicht de race tussen pair/start en SSE-open. De
// *eerste* findUnique (voor cookie-validatie) gebeurde vóór LISTEN
// actief was; als de mobiel tussen die query en LISTEN approvet is
// de pg_notify verloren (Postgres queuet niet) én is de eerder
// gelezen status stale. Lees daarom de status hier opnieuw — nu LISTEN
// wel actief is, dus alle approvals na dit punt komen via de notify-
// handler door.
const fresh = await prisma.loginPairing.findUnique({
where: { id: pairingId },
select: { status: true },
})
const currentStatus = fresh?.status ?? pairing.status
enqueue(
`event: state\ndata: ${JSON.stringify({
pairing_id: pairingId,
status: pairing.status,
status: currentStatus,
})}\n\n`,
)
// Pairing was misschien al consumed/cancelled tussen findUnique en LISTEN —
// sluit de stream meteen.
if (TERMINAL_STATUSES.has(pairing.status)) {
await cleanup(`already-${pairing.status}`)
if (TERMINAL_STATUSES.has(currentStatus)) {
await cleanup(`already-${currentStatus}`)
return
}