check_and_raise (SELECT) and increment (unconditional UPSERT) were 5-40s apart
(the estimate scrapes run between them), so two concurrent /estimate at used=14
both passed the check and both incremented → used=16, pilot exceeds 15/mo.
Make increment atomic-conditional: ON CONFLICT DO UPDATE SET used=used+1
WHERE used < :lim RETURNING used → returns bool. The WHERE applies only to the
DO UPDATE branch, so a fresh insert (used=1) always succeeds; a conflicting row
already at the limit is not updated → RETURNING empty → False. trade_in raises
429 when increment returns False. check_and_raise kept as a fast pre-check.
Tests: 4 new (atomic refuse-at-limit incl. used stays == limit not +2; fresh
insert succeeds; returns bool; unlimited no-op) via an in-memory fake modeling
the conditional UPSERT (CI has no Postgres). pytest -k quota → 36 passed.
Refs #747