gendesign/tradein-mvp/backend/data/sql/189_account_estimate_usage_nonnegative.sql
lekss361 dce2cd2040
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m52s
Deploy Trade-In / build-backend (push) Successful in 1m0s
Deploy Trade-In / deploy (push) Successful in 6m14s
fix(tradein): откат транзакции в backfill + неотрицательный счётчик квот (#2538)
2026-07-26 22:21:08 +00:00

51 lines
3 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Migration 189: account_estimate_usage.used >= 0 — защита от бонус-хака (negative used)
--
-- WHY:
-- Прежний SQL-runbook хак раздачи бонусных попыток (`UPDATE account_estimate_usage
-- SET used = used - N`) уводил used в отрицательные значения. Migration 185 сбросила
-- это ТОЛЬКО для user2 (used=-35 -> 0, WHERE username = 'user2'). На проде остаётся
-- минимум ещё один затронутый аккаунт тем же классом порчи (praktika, период 2026-06:
-- used=-3 при 42 фактических успешных оценках — расхождение объясняется именно этим
-- хаком, не кодовым багом).
--
-- Аудит app.services.account_quota подтверждает: декремента `used` в текущем коде
-- НЕТ. increment() делает только `used + 1` под предикатом `WHERE used < :lim`
-- (#747, atomic conditional increment) — этот путь не может уйти в минус. Значит
-- источник отрицательных значений исключительно внешний (ручной UPDATE через
-- runbook), а не баг в приложении.
--
-- WHAT:
-- 1. Сброс ВСЕХ оставшихся negative used -> 0 (не только user2, как в 185) —
-- закрывает praktika и любой другой пропущенный аккаунт.
-- 2. CHECK (used >= 0) — защита на уровне схемы: любой будущий ручной UPDATE/хак,
-- уводящий used < 0, теперь падает на уровне БД вместо тихой порчи /quota
-- (GET /quota мог отдать remaining > limit — «Осталось 50 из 15», см. 185).
--
-- IDEMPOTENCY:
-- - UPDATE ... WHERE used < 0 — no-op при повторном прогоне (после первого раза
-- условие больше не матчит).
-- - ADD CONSTRAINT через DO-блок с проверкой pg_constraint — Postgres не
-- поддерживает `ADD CONSTRAINT IF NOT EXISTS` напрямую для CHECK, поэтому
-- оборачиваем в идемпотентную проверку по имени constraint.
--
-- Dependencies: 076_account_estimate_quota.sql (account_estimate_usage),
-- 185_account_quota_overrides.sql (первый частичный сброс, только user2).
BEGIN;
UPDATE account_estimate_usage
SET used = 0, updated_at = now()
WHERE used < 0;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'account_estimate_usage_used_nonnegative'
) THEN
ALTER TABLE account_estimate_usage
ADD CONSTRAINT account_estimate_usage_used_nonnegative CHECK (used >= 0);
END IF;
END $$;
COMMIT;