- adminnotify: MaxUnacknowledgedCriticalLogs global cap exposed as
CriticalLogsCapExceeded — a pre-check helper every insert site pairs with the
atomic fold inside its INSERT (count-then-insert is atomic, closing the
TOCTOU where concurrent inserts could both read a below-cap count).
- jobs/cleanup.go ScanCriticalPaymentLogs: capped at the shared cap, pre-check
skips the scan and logs the suppression.
- scheduling: 1_week_no_pay, 1_month_no_pay, default_hours_changed,
deposit_not_paid_by_deadline and the Square-erasure critical notification all
flood-capped with pre-check + atomic fold (per-booking/per-user dedup kept).
- time-blockers.go CleanupExpiredGiftCards (M4): the expiry SELECT now runs
under FOR UPDATE row locks so the read-expired-then-zero window is atomic —
a concurrent top-up either commits before the SELECT (refreshed last_used_at
drops the card out of the predicate) or blocks until the sweep's tx ends and
revives the zeroed card via its own expiry refresh; the top-up value can
never be destroyed by the sweep.
- flood-cap tests added for 1_week_no_pay; adminnotify unit coverage added.
Adds the scan-critical-payment-logs job (daily 2:45am) that surfaces stale pending payments/till-sales and refunds at the retry cap as admin_notifications with reason='critical_payment_log' — the app has no log/alert pipeline (Gap Backlog T14), so money events that would otherwise sit in un-watched CRITICAL log lines now reach the owner's in-app notification centre. Dedup is NULL-safe (IS NOT DISTINCT FROM) and re-surfaces acknowledged-but-still-unresolved rows. Stopgap until a real alerting pipeline lands.
square_webhook_events is now CREATE TABLE IF NOT EXISTS, registered as the 24th maintenance job (sweep-square-webhook-events, daily 2:30am) pruning rows older than 90 days, and testdb gives a clear docker compose hint when the admin DB connection fails.