Ревью PR #3363:
1. domclick city sweep: комментарий обещал, что унаследованный при claim
чекпоинт переживёт дрейн «jsonb-мержем» — мержа нет. domclick пишет
counters через app-level scrape_runs (update_heartbeat/mark_done делают
`counters = CAST(:counters AS jsonb)`, полная перезапись), а scheduler
при claim done_buckets не наследует. Дрейн закрывал прогон с ПУСТЫМ
чекпоинтом, резюм пересобирал все шесть корзин. Чтение чекпоинта поднято
выше ранних выходов, дрейн-payload несёт `done_buckets` явно — как уже
делает ban-ветка (except NoProxyAvailableError) той же функции.
2. cian full-load, detail-фаза: второй ранний выход по shutdown_requested()
делал `break` и уходил в обычный mark_done — SERP целый, обогащение
обрезано, а прогон выглядел полным. Теперь тот же sentinel
RuntimeError("shutdown"), что и в _on_bucket: interrupted=1 + done_buckets.
3. yandex/avito full-load проверены: по одному shutdown-сайту в _on_bucket,
detail-фазы нет вовсе — аналогичного дефекта нет.
Тесты: чекпоинт дрейна domclick сверяется ПО ЗНАЧЕНИЮ в обеих записях
(heartbeat + финализатор); дрейн cian в detail-фазе даёт interrupted=1.
После #3330/#3346 резюм берёт 'done' только с counters.interrupted=1, но четыре
функции с живым чекпоинтом done_buckets финализировали дрейн чистым 'done':
cian/yandex/avito full-load (ветка RuntimeError("shutdown")) и domclick city
sweep (дрейн до SERP). Оборванный обход был неотличим от полного, а собранные
бакеты никто не подхватывал — у avito это ещё и бан-бюджет (#3315).
done_buckets в domclick-payload не добавляем: чекпоинт унаследован при claim
(#3074), jsonb-мерж его сохраняет.
Closes#3355