# План: защита от восстановления из пустой копии (находка 11) Источник: трассировка `_recover()` по графу знаний и проверка запуском (см. `docs/review-2026-09-21.md`, находка 11). ## Проблема `_recover` ставит на место повреждённой базы самую свежую исправную копию, но не смотрит на её содержимое. Если копия пуста (ночное задание `backup` сработало до первого сбора или во время его сбоев), а источники в `config.json` настроены, то после порчи базы API отвечает `200 []` без метки: та же угроза, что в находке 1 (потребитель может принять пустой список за истину), только через восстановление. Подтверждено запуском: копия пустой базы -> порча -> `/addresses` даёт `200 []`, метки нет. ## Дизайн - **Признак «пусто несмотря на источники»:** `db.empty_despite_sources(conn)`: в базе нет ни одного значения, а в `config.json` настроен хотя бы один ASN или FQDN. Сначала проверяется число значений (конфиг не читается, если данные есть); нечитаемый конфиг при пустой базе считается «пусто» (блокировка по умолчанию). Критерий намеренно общий, а не по типам: копия, где есть ASN, но нет адресов FQDN (например, все адреса отфильтрованы как неглобальные), не должна вечно держать выдачу в ожидании. - **Восстановление:** после успешного `_restore_backup` при `empty_despite_sources` пишется та же метка `db_recreated.json` (с полем `reason`). Дальше работает уже существующая защита: `/addresses` и `/addresses/diff` дают `503` с `Retry-After`, `/health` показывает `degraded` и `db_recreated`, сборщик снимает метку, когда данные собраны. Восстановление копии с данными метку не ставит. - **Задание `backup`:** пустую базу при настроенных источниках не копирует: `backup_database` возвращает `None` (не ошибка: статус задания остаётся успешным, в лог пишется сообщение), ротация не выполняется, существующие копии не затрагиваются. Так пустые копии не вытесняют хорошие за `backup_keep` дней и не появляются вовсе. Пустая база без источников копируется как раньше. ## Изменения 1. `db.py`: `empty_despite_sources`; `recreated_pending` использует общий разбор конфигурации; метка ставится в `_recover` (общая функция записи метки для обеих веток); `backup_database` возвращает `None` для пустой базы при источниках. 2. `collector_daemon.py`: `run_backup` обрабатывает `None` (сообщение в лог). 3. `README.md`: разделы о резервной копии и автовосстановлении. 4. `docs/review-2026-09-21.md`: находка 11; итоги в `docs/summary-empty-backup-guard.md`. 5. Тесты (1 новый, всего 28): пустая база при настроенных источниках копию не создаёт, копия с данными восстанавливается без метки, пустая копия (снятая, когда источников не было) при появлении источников даёт метку и `503`-состояние. ## Не входит Тип-специфичное ожидание при восстановлении (см. выше); копии вне сервера; удаление уже существующих пустых копий (при необходимости - вручную из `backups/`). ## Проверка Тесты в контейнере; повтор запуска, воспроизводившего проблему: восстановление из пустой копии при настроенном источнике теперь даёт `503`, метка есть, после появления данных выдача возобновляется; копия с данными - без метки. ## Откат Убрать проверку в `_recover` и `backup_database` (возврат к предыдущему коммиту); схема данных не затрагивается, метку можно удалить вручную.