fix(tradein/payments): миграция 234 → 240 — номер снова занят на main

main уехал вперёд за сутки: 234 занял 234_scrape_runs_ban_kind_unknown.sql
(0de22f4b), максимум на main сейчас 239 (235-237 — дыры). max+1=240 безопаснее
дыр; ни один открытый PR номер 235-240 не занимает (сверено по forgejo/main и
всем открытым веткам).

Переименован файл + обновлены все 7 упоминаний "migration 234"
(_manifest_applied.txt, config.py, schemas/trade_in.py,
purge_expired_trade_in_data.py, test_estimate_idor.py, content.ts,
types/trade-in.ts) — правки текстовые, ни один тест не читает миграцию по
имени файла.
This commit is contained in:
bot-backend 2026-08-07 15:31:01 +03:00
parent 48664dfe0e
commit e6591a450a
8 changed files with 16 additions and 12 deletions

View file

@ -836,11 +836,11 @@ class Settings(BaseSettings):
# срок — решение DPO/юриста, не инженера). ENV: TRADE_IN_LEAD_RETENTION_DAYS.
trade_in_lead_retention_days: int = 180
# ── Платный отчёт живёт год (retain_until, migration 234, PR #2754) ─────
# ── Платный отчёт живёт год (retain_until, migration 240, PR #2754) ─────
# trade_in_estimates.retain_until TTL (дни ОТ ОПЛАТЫ) — срок жизни ССЫЛКИ/
# СТРОКИ для оплаченной оценки, независимый от expires_at (актуальность
# расчёта, 24ч, глобальный для ВСЕХ строк). НЕ трогает expires_at — см.
# migration 234 докстринг. Отдельная колонка, а не подъём expires_at:
# migration 240 докстринг. Отдельная колонка, а не подъём expires_at:
# expires_at печатается в PDF/UI как «актуальность расчёта» и одинаков
# для всех строк, поднять его до года = соврать в документе клиента про
# свежесть цифры + нарушить минимизацию ПДн для неоплаченных B2C-адресов.

View file

@ -197,7 +197,7 @@ class AggregatedEstimate(BaseModel):
actual_deals: list[AnalogLot] # реальные продажи last 12 mo
expires_at: datetime
# PR-D1: срок жизни ССЫЛКИ/СТРОКИ (оплаченный доступ), НЕ актуальности
# расчёта — тот остаётся expires_at (не путать, см. migration 234).
# расчёта — тот остаётся expires_at (не путать, см. migration 240).
# NULL = неоплачено (весь текущий трафик, B2B pilots включительно).
retain_until: datetime | None = None
# ── Дополнительные метаданные ──

View file

@ -99,7 +99,7 @@ _DEFAULT_MAX_BATCHES = 20
# означало бы одновременно нарушить минимизацию ПДн по 152-ФЗ и соврать в
# документе клиента про срок актуальности цифры:
# 1. `retain_until IS NULL` — именно IS NULL, НЕ `< NOW()`. Оплаченная
# строка (retain_until IS NOT NULL, migration 234) не удаляется джобой
# строка (retain_until IS NOT NULL, migration 240) не удаляется джобой
# В ПРИНЦИПЕ, пока не поднято ослабление отдельным PR не раньше чем
# через год после первой продажи. `retain_until` ставится сервисным
# кодом платёжного контура (ещё не существует в этом PR) на now() +

View file

@ -1,10 +1,14 @@
-- 234_trade_in_estimates_retain_until.sql
-- 240_trade_in_estimates_retain_until.sql
-- Платёжный контур МЕРЫ, ретеншен (PR #2754): «оплаченное живёт год, purge
-- его не трогает». Владелец продаёт отчёт физлицу за 150 ₽ — отчёт должен
-- жить год на нашей стороне, а не 24ч (см. WHY ниже).
-- Номер сверен и по `forgejo/main`, и по всем открытым PR-веткам на момент
-- написания (последняя занятая — 233_payments.sql) — см. урок в шапке того
-- же файла про то, как коллизия 228/229/231/232 обнаруживается поздно.
-- Номер сверен по `forgejo/main` и всем открытым PR-веткам ДВАЖДЫ: сначала
-- как 234 (последняя занятая на момент ветвления была 233_payments.sql), но
-- main уехал вперёд и 234 занял `234_scrape_runs_ban_kind_unknown.sql`
-- (коммит 0de22f4b) — переименовано в 240 (main max на момент повторной
-- сверки — 239, с дырами 235-237; max+1 безопаснее дыр). Урок пятый за
-- сутки: сверять номер нужно не только перед первым коммитом, а прямо перед
-- пушем/мержем — main не стоит на месте.
--
-- ── WHY ──────────────────────────────────────────────────────────────────────
-- purge_expired_trade_in_data (migration 231, seeded enabled=false) удаляет

View file

@ -231,4 +231,4 @@
# поддержки, #2532/#2533) откладывались до подтверждения, что они осели на
# проде в финальном виде. Они в _schema_migrations — условие выполнено.
233_payments.sql
234_trade_in_estimates_retain_until.sql
240_trade_in_estimates_retain_until.sql

View file

@ -54,7 +54,7 @@ def trade_in_app() -> FastAPI:
def _make_estimate_row(created_by: str | None, retain_until: object = None) -> SimpleNamespace:
"""A trade_in_estimates row with the full column set the endpoints read.
retain_until defaults to None (PR-D1, migration 234) -- unpaid, matches every
retain_until defaults to None (PR-D1, migration 240) -- unpaid, matches every
row that existed before that migration; explicit param lets retention-gate
tests (see test_estimate_retention_gate.py) construct a paid row.
"""

View file

@ -94,7 +94,7 @@ export const PRIVACY_PATH = "/mera-public/privacy";
/**
* Сколько месяцев на нашей стороне хранится ссылка/строка оплаченного отчёта
* после оплаты (`trade_in_estimates.retain_until`, migration 234) НЕ срок
* после оплаты (`trade_in_estimates.retain_until`, migration 240) НЕ срок
* действия самого расчёта (тот отдельный, `expires_at`, часы).
*
* ЕДИНСТВЕННОЕ место, где это число хардкодится на фронте любой другой

View file

@ -172,7 +172,7 @@ export interface AggregatedEstimate {
analogs: AnalogLot[]; // top 5-10
actual_deals: AnalogLot[]; // last 12 mo
expires_at: string; // ISO datetime
// PR-D1: срок жизни ссылки/строки (оплаченный доступ, backend migration 234),
// PR-D1: срок жизни ссылки/строки (оплаченный доступ, backend migration 240),
// НЕ путать с expires_at (актуальность расчёта). null = неоплачено (текущий
// трафик целиком).
retain_until?: string | null; // ISO datetime