- Drop the FIFO from oauthStates: consumed states left entries behind, so
an unrate-limited start/cancel cycle grew the slice without bound.
Evict by oldest expiry instead — the map alone now bounds memory.
- CreateSession runs INSERT + expiry sweep in one transaction.
- slices.Contains replaces a hand-rolled contains; APIBase typo fixed.
- Stale comments and test paths updated; login hover uses --ember-ink.
The browser UI signs in with a Discord authorization code grant
(identify + guilds.members.read) instead of a shared password. Guild
membership is the gate; the owner's Discord ID is the only identity
allowed in while registration is closed. Sessions become rows in a
sessions table with opaque random ids — the cookie carries only the id,
looked up and expiry-checked per request — so deleting a row revokes a
session. HMAC cookie signing, its derived key, and WEB_PASSWORD are
gone, and no replacement signing secret is introduced (ADR-0002).
Discord's API base is configurable (DISCORD_API_BASE); the full flow is
tested through the real router against a local stub, including the
form-encoded token exchange Discord rejects if sent as JSON.