`SCRAPER_RECREATE` истинно и на infra-правках (compose / workflow / deploy/**),
а те почти всегда собирают тот же образ по кэшу: digest не меняется, `up -d`
выходит no-op — и за него платили пятиминутным ожиданием слива scrape_runs,
прерывая сбор. Теперь после `docker compose pull` (порядок важен: до pull'а под
:latest ещё старый образ) сравниваем ID подтянутого образа с тем, на котором
бежит tradein-scraper. Совпало — печатаем «пересоздавать нечего» и идём дальше
без drain'а; не совпало или контейнера/тега нет — drain как раньше.
Заодно закрыт ложный startup-reap: чекпоинт и reap завязаны на тот же признак и
больше не выполняются, когда recreate'а не было. Иначе прогон, переживший
таймаут drain'а в НЕ пересозданном контейнере, помечался бы 'cancelled',
продолжая работать.
scraper остаётся в $SERVICES в обоих случаях — при совпавшем образе `up -d`
no-op, но правка самого compose (env/лимиты сервиса) так всё же доезжает.
Сверка образов: «контейнера нет» и «контейнер отстал» теперь разные сообщения —
это разные аварии и чинятся по-разному; отсутствие tradein-backend (эталона)
тоже отдельная строка.
Refs #2679
Почему: `tradein-backend`, `tradein-scraper` и `tradein-tgbot` — один образ
gendesign-tradein-backend, но пересоздание скрапера было привязано к allowlist'у
путей («файлы, которые исполняет планировщик»). Список перечислял только то, что
вспомнили: 2026-07-02 (#2188) на нём погорел fias-dedup — починили ДОБАВЛЕНИЕМ
путей; 2026-08-05 (#2675) тот же механизм выстрелил снова на
house_imv_backfill.py / product_handlers.py. За июнь-август 48% (193 из 402)
backend-мержей не попадали ни в один путь списка.
Что сделано вместо очередного пополнения списка:
- фильтр `scraper` удалён; признак пересоздания = `if:` джобы build-backend
(backend || infra || workflow_dispatch), т.е. «образ мог пересобраться»;
- добавлена сверка image ID backend/scraper/tgbot после health-checks: при
расхождении деплой падает ДО записи .tradein-deployed-sha, а не отчитывается
успехом. Следующий прогон возьмёт ту же базу и пересоберёт накопленное.
«Phase 0»-компромисс (infra не трогает скрапер, чтобы не убить многочасовой
прогон) снят: с #1951 перед recreate'ом идёт graceful drain + startup-reap,
а `compose up -d` на неизменившемся образе — no-op.
Цена по факту (43 прод-деплоя 31.07-05.08): деплой со скрапером median 113s /
mean 146s против 76s / 74s без него; средний деплой вырастет примерно на 40s,
худший случай — до +5 мин (потолок drain'а при длинном full-load'е).
Refs #2679