gendesign/tradein-mvp/backend/app/core
bot-backend b89788ee99 feat(tradein/proxy): прогон знает свой узел, а снятый бан перестаёт стирать историю (#3404)
Выбор оператора мобильного прокси опирался на две ненадёжные опоры.

Первая: `scrape_runs` не знала, через какой узел шёл прогон — колонка `proxy_id`
была только у банов и ротаций. «Какой узел собрал 5 карточек из 21» не выяснялось
ни одним запросом.

Вторая: `clear_source_bans` делала DELETE, а зовётся она после КАЖДОЙ успешной
ротации exit-IP. У #540723 (МегаФон) 23 успешные ротации и ноль строк банов,
у #540722 (Tele2) ротаций почти не было и 7 банов. «7 против 0» читалось как
«Tele2 хуже», хотя в той же мере это «у МегаФона историю стёрли 23 раза».

Теперь:
- `scrape_runs.proxy_id` — последний выданный прогону узел; полная цепочка
  (если узел менялся mid-run) копится в `counters.proxy_ids`. Пишет
  `proxy_pool.attribute_run_proxy` из единственной точки — сразу после выдачи
  лиза в `acquire()`, поэтому curl-путь, браузерный sticky lease и ре-acquire
  при ротации покрыты одинаково. `run_id` доходит до адаптера через ContextVar
  (`scraper_kit.orchestration.run_context`): протокол `ProxyProvider.acquire`
  его не несёт, а `RealProxyProvider` живёт одним объектом на весь планировщик.
  Best-effort: `lock_timeout` 2с и проглоченное исключение — диагностика не
  вправе ронять выдачу прокси или ждать на блокировке строки прогона.
- `clear_source_bans` гасит строку (`banned_until = now()`, `ban_count = 0`,
  `cleared_at`/`cleared_reason`) вместо удаления. Эскалация сохраняется 1:1:
  формула в `mark_banned` берёт ПРЕДЫДУЩИЙ `ban_count` показателем степени, при
  нуле это ровно `SOURCE_BAN_BASE_HOURS` — как после DELETE. Строка доживает до
  штатного purge по `SOURCE_BAN_PURGE_DAYS`.

Для всех читателей `scrape_proxy_source_bans` погашенная строка неотличима от
отсутствующей: acquire, оба guard-подзапроса `mark_banned`, `proxy_egress`
(ранжирование по `ban_count` даёт 0, как у узла без истории), admin `_active_ban` —
все гейтятся по `banned_until > now()`.

Ничего не бэкфиллится: связать прошедшие прогоны с узлами нечем (`leased_by`
исторически = NON_RUN_LEASE_MARKER), врать восстановленным значением нельзя.

Миграция 287. Тесты: 9 новых на обе части (главный — эскалация после гашения даёт
базовые 6ч, а не удвоенные) + 14 существующих переведены с DELETE-семантики на
гашение, включая проверку, что секрет ротации не утекает в новое `cleared_reason`.
Полный прогон бэкенда: 5600 passed, 37 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WHFxVPWoBnSZihkdH1Uou
2026-09-06 13:05:20 +03:00
..
__init__.py feat: add tradein-mvp subproject (Trade-In Estimator под /trade-in) 2026-05-21 00:25:39 +03:00
anon_session.py feat(mera/b2c): анти-абуз для анонимного трафика — этап 2 из 8 (#2546) 2026-08-25 16:27:02 +00:00
auth.py test(tradein): прод-конфигурация ролей в приёмке #3316 + компромисс фолбэка 2026-09-02 14:59:14 +05:00
auth_db.py fix(#3194): hide_parameters=True на всех движках, include_local_variables=False у scheduler 2026-08-30 14:53:41 +05:00
config.py feat(tradein/proxy): прогон знает свой узел, а снятый бан перестаёт стирать историю (#3404) 2026-09-06 13:05:20 +03:00
db.py fix(#3194): hide_parameters=True на всех движках, include_local_variables=False у scheduler 2026-08-30 14:53:41 +05:00
fdw.py chore(tradein/geocoder): удалить Яндекс-геокодер (#2593) 2026-07-31 21:22:09 +03:00
http_errors.py fix(mera/public): Infinity/NaN во входе — 422, и бюджет считает такие запросы 2026-08-30 00:11:56 +05:00
log_scrub.py fix(tradein): маскировать секрет и в параметрах с префиксом (#3154) 2026-09-05 23:03:16 +05:00
password.py fix(tradein/auth): отказ по насыщению — до выборки из БД и с агрегированным следом (#2715) (#2734) 2026-08-06 14:27:26 +00:00
public_request.py fix(mera/b2c): семь дефектов публичного периметра, найденных состязательным ревью 2026-08-16 10:01:47 +03:00
ratelimit.py fix(tradein/payments): тело нотификации не течёт в мониторинг и аудит, повторы банка не отбиваются лимитом 2026-08-07 16:17:59 +03:00
rbac.py fix(rbac): анониму на admin-префиксе отвечать 404, а не 401 2026-09-06 00:51:09 +05:00
request_audit.py fix(tradein/payments): тело нотификации не течёт в мониторинг и аудит, повторы банка не отбиваются лимитом 2026-08-07 16:17:59 +03:00
shutdown.py feat(tradein/scraper): cooperative SIGTERM-drain for graceful deploy (#1182 Phase 2) 2026-06-28 19:52:09 +03:00
version.py feat(tradein): версионирование продукта — единый источник, подвал, PDF, /versions (#2824) 2026-08-10 16:00:54 +00:00