stamp_service.add_stamp checks card.last_stamp_at + cooldown_minutes
before crediting and raises StampCooldownException if too soon. The
parallel points_service.earn_points writes card.last_points_at but
never reads it for enforcement — so cooldown_minutes was silently
ignored for points-based programs.
Mirror the stamps check in points_service.earn_points: after acquiring
the row lock, compare now vs last_points_at + cooldown_minutes and
raise the new PointsCooldownException if the cashier is inside the
window. Add PointsCooldownException alongside StampCooldownException
in exceptions.py with parity wording / error code POINTS_COOLDOWN.
Surfaced during Test 3 step 3.6 — repeated earn-points calls for the
same card kept crediting the customer with no rate limit even though
the program's cooldown_minutes was set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>