-- 209_scrape_proxies_disabled_reason.sql -- scrape_proxies.disabled_reason — различить "оператор выключил руками" от -- "пул выключил сам после серии сбоев" (#2610). -- -- WHY: -- #2609 сделало mark_health(ok=True) безусловно ставить enabled=true — чинит -- реальный прод-инцидент (авто-выключенный после серии сбоев узел иначе не -- проверяется больше никогда, транзиентный сбой = вечный приговор). Побочка: -- оператор снимает узел с ротации через PATCH /proxies/{id} (enabled=false) — -- первая же успешная ipify-проба молча возвращает его обратно. Особенно больно, -- когда узел забанен площадкой (Авито): ipify через него ходит чисто (ipify -- никого не банит), значит узел гарантированно вернётся, и оператор снимает -- его снова и снова. -- -- WHAT: -- disabled_reason text, NULL по умолчанию. Семантика: -- NULL — узел не выключен вручную (авто-disable по consecutive_fails — -- тоже NULL здесь; поведение #2609 "авто-выключенный воскресает -- первой успешной пробой" сохраняется без изменений). -- текст — причина ручного выключения (по умолчанию из admin API — -- "manually disabled via admin API", либо то, что передал оператор). -- mark_health(ok=True) НЕ трогает enabled, пока это поле не NULL. -- Сбрасывается в NULL ручным включением (PATCH .../{id} enabled=true). -- -- ВЫБОР text, а не boolean manually_disabled: единственное текстовое поле -- расширяемо под будущую ТРЕТЬЮ причину выключения без новой миграции — #2600 -- п.1 (сигнал бана площадкой до пула) заведёт значение вида 'banned:avito', -- отличимое от 'manual' в логах/админке, и по той же самой колонке (не-NULL = -- не воскрешать по голому ipify) без ALTER TABLE. Boolean такого не даёт — -- потребовал бы либо второй колонки, либо потери "почему" при разборе. -- -- IDEMPOTENCY / SAFETY: -- - BEGIN/COMMIT — атомарно. -- - ADD COLUMN IF NOT EXISTS — повторный прогон no-op (auto-apply strict). -- - Без DEFAULT — существующие строки получают NULL, т.е. "не выключены -- руками" — обратная совместимость: ни одна прод-строка не станет ручной. -- -- Dependencies: 157_scrape_proxies.sql BEGIN; ALTER TABLE scrape_proxies ADD COLUMN IF NOT EXISTS disabled_reason text; COMMENT ON COLUMN scrape_proxies.disabled_reason IS 'NULL = не выключен вручную (mark_health(ok=True) может авто-восстановить ' 'enabled=true, #2609). Текст = причина ручного выключения оператором ' '(PATCH /proxies/{id} enabled=false, admin.py) — ipify-проба узел НЕ ' 'воскрешает, пока поле не NULL. Сбрасывается в NULL ручным включением. ' 'Задел под #2600 п.1 (сигнал бана площадкой до пула) — третья причина ' 'ляжет сюда же без новой миграции, напр. значением вида ''banned:avito''.'; COMMIT;