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
51 lines
3 KiB
PL/PgSQL
51 lines
3 KiB
PL/PgSQL
-- 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;
|