gendesign/tradein-mvp/backend/data/sql/189_account_estimate_usage_nonnegative.sql
bot-backend 9c59586be6
All checks were successful
CI / changes (pull_request) Successful in 8s
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m57s
fix(tradein/backfill): rollback on listings save failure + nonnegative used quota
1. cian_history_backfill.py listings block was missing the same
   db.rollback() the houses block already has: save_detail_enrichment()
   runs several unprotected db.execute() and only commits at the end.
   A single bad row (e.g. a non-standard Cian change_time hitting
   CAST(:ct AS timestamptz)) leaves the session in a failed-transaction
   state, and every subsequent listing in the batch (up to 49) then
   fails with PendingRollbackError -- one real failure looked like N
   independent ones in the logs.

2. account_estimate_usage.used had no floor. Prod audit: praktika
   2026-06 shows used=-3 against 42 real estimates. Migration 185
   already fixed this class of bug for user2 (negative used from a
   retired SQL-runbook bonus hack) but only reset that one username.
   No decrement path exists anywhere in app.services.account_quota --
   increment() only ever does used+1 under a WHERE used < lim guard
   (#747) -- so the minus is external (manual UPDATE), not an app bug.
   Migration 189 resets all remaining negative used rows and adds
   CHECK (used >= 0) so a future manual UPDATE can't reintroduce it.
2026-07-27 00:30:52 +03: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;