Все три detail-backfill'а (avito/yandex/domclick) финализировались одним
mark_done — прогон, который сделал N попыток и не обогатил ни одного
объявления, отчитывался успехом. На проде это 78 прогонов из 158:
domclick 24/30 — 494 попытки, 0 обогащено, 63 блока, 431 fail;
avito 23/76 — в т.ч. 5 прогонов по 1500-1600 попыток с нулём;
yandex 31/52 — все ровно attempted=5 failed=5 (упор в брейкер).
Существующие алерты этот класс не ловили: _alert_if_consecutive_failures
считает только failed/banned, а _alert_if_consecutive_zero_results смотрит
total_seen, которого в counters backfill'ов нет вовсе (всегда 0 → стрик
никогда не прерывается → анти-спам молчит после первого срабатывания).
Поэтому 30 пустых прогонов Домклика и прошли незамеченными.
Один общий финализатор runs.mark_backfill_finished на три задачи (правка в
одной точке, а не в трёх копиях), правила по образцу #2657 для
domclick_city_sweep:
attempted=0 → done (честная пустота, кандидатов не было);
блоки + обрыв/ноль → banned (external constraint, не наш баг; тот же
статус, что #2642 дал cian/yandex);
ноль результата без блоков → failed (это уже наша поломка);
иначе → done (в т.ч. частичный прогон).
`gone` (404 у avito) считается результатом наравне с enriched: прогон,
подтвердивший снятие объявлений, работу сделал.
Причина пишется в scrape_runs.error с числами прогона, а не пустой строкой.
Побочный эффект честности: три подряд banned/failed поднимают уже
существующий Sentry-алерт — ровно тот сигнал, которого не хватало.
Корневая причина нуля у Домклика — внешняя и уже разобрана в #2636/#2645
(детект QRATOR-маркеров) + куки test-аккаунта протухли 2026-08-03
(domclick_session_cookies.expires_at_estimate), обновление ручное. Расписание
не трогаем: брейкер обрывает прогон после 3 блоков за ~150 с, а миграция кук
не перезальёт.
Refs #2674