A race condition in purchaseTickets() causes two symptoms:
Overselling — The availability check (SELECT) and decrement (UPDATE) are separate, non-transactional queries. Concurrent requests all read the same available value, all pass the check, and all decrement — pushing available negative.
Duplicate ticket numbers — Ticket numbers are computed from the same stale snapshot (total - available), so concurrent requests generate identical ranges. No UNIQUE constraint existed to catch this.
SELECT ... FOR UPDATE to lock the event row and serialize concurrent accessUNIQUE (event_id, ticket_number) and CHECK (available >= 0) constraints to init.sqlnpm run reproduce — fires 10 concurrent requests against 32 available tickets to demonstrate both issues.