Состояние после 17 доработок (план и итоги каждой лежат в `docs/`): надёжность и безопасность, тесты в контейнере, форматы вывода, разделение процессов, управление ASN/FQDN через API, контейнер приложения, SQLite, `POST /collect`, `GET /addresses/diff`, репозиторий git, закрепление версий, резервные копии и автовосстановление, защита ввода и данных, защита от пустой выдачи, исправления ревью 5-10, защита от пустых копий, наблюдаемость. Предыдущая версия этого файла описывала состояние после восьми доработок. Отчёт ревью: `review-2026-09-21.md` (находки 1-11 закрыты).
Тестовый запуск на копиях боевых файлов с реальным ASN 62041 описан в `summary-real-data-run.md`; **боевое развёртывание в контейнерах на порту 18000 выполнено 2026-09-21** (`summary-production-deploy.md`, работает по расписанию).
- **Закрыты риски:** 1 (нет git), 2 (версии не закреплены), 3 (пустой список после порчи базы).
- **Закрыты наблюдения:** 2 (`/health` не видел сбоев источников) и 5 (сообщение «data saved» без изменений); наблюдения 10 и 11 (структура модулей) переосмыслены, наблюдение 3 ухудшилось, добавлено наблюдение 13.
- **Из «Не сделано» выполнено:** автоматическая резервная копия, diff, повторные запросы к RIPE; `/metrics` исключён из плана решением пользователя.
- **Новое:** восстановление из копии, состояние источников (`/sources`), фильтрация DNS-адресов, защита от пустой выдачи и пустых копий, схема базы версии 3, ревью по графу знаний (11 находок).
| Токен на изменяющие запросы | `X-API-Key` на `POST`/`DELETE`, без токена запись отключена (503); сравнение по байтам, любой неверный ключ - 401. Чтение открыто намеренно. |
| TTL, атомарная запись, защита от порчи | TTL (`ttl_days`), атомарная запись JSON и транзакции SQLite, битые файлы уходят в `*.corrupt-*`. |
| Контейнер приложения | `Dockerfile` (все модули корня, проверка импорта при сборке) и `docker-compose.yml`: два сервиса, том, non-root, read-only, healthcheck. |
| SQLite | Таблица `addresses`, WAL, автоматическая миграция из JSON, транзакции; схема версии 3 (журнал изменений, состояние источников). |
| `POST /collect` | Токен, ответ 202, запрос демону файлом (опрос каждые 5 с), объединение запросов, `503` без живого демона. |
| `GET /addresses/diff?since=` | Журнал изменений на триггерах, курсор или время, `410` за горизонтом, `X-Changes-Cursor` в `/addresses`; строгий разбор `since`. |
| Репозиторий и версии | `git`, `origin`; `requirements` с точными версиями и `constraints.txt` (полный `pip freeze`). |
| Резервные копии и восстановление | Задание `backup` (онлайн-копия, `quick_check`, ротация `backup_keep`); при порче базы восстановление из последней исправной копии; `last_restore` в `/health`. |
| Защита от пустой выдачи | Потеря базы без копий или восстановление из пустой копии при настроенных источниках: метка `db_recreated.json`, `503` на `/addresses` и `/addresses/diff` до нового сбора; пустая база не копируется. |
| Данные DNS и внешний источник | Фильтр глобальных адресов (`allow_non_global_ips`), повторы запросов к RIPEstat с `sourceapp` и потолком `Retry-After`. |
| Наблюдаемость | Состояние каждого источника (таблица `source_status`), блок `sources` и `degraded` в `/health`, `GET /sources`, итоговая строка запуска в логе. |
| Боевое развёртывание | Compose на `192.168.5.9:18000` (доступ открыт для всех, токен в `.env`), шесть ASN и три FQDN, миграция боевых файлов без потерь, плановый запуск проверен; копии на томе (`summary-production-deploy.md`). |
| 2 | ~~Версии в `requirements.txt` не закреплены.~~ Базовый образ `python:3.11-slim` не закреплён по дайджесту (осознанно). | Закрыт |
| 3 | ~~Пустой список после порчи базы.~~ Остаются не покрытые случаи: ручное удаление файла базы (порчи нет, пустая база создаётся без метки) и порча внутри файла (503 при чтении, восстановление вручную). | Закрыт, остаток описан |
| 8 | `POST /collect` без ограничения частоты: повторные запросы во время сбора пропускаются, но защиты от нагрузки на RIPE нет (при этом запросы к RIPEstat теперь идут с повторами). | Не закрыт |
| 9 | Журнал diff и состояние источников: на боевом развёртывании с шестью ASN и расписанием база и ответы малы (десятки КБ, миллисекунды); поведение во времени (сутки и более, TTL, рост журнала) ещё предстоит наблюдать. | Частично закрыт |
| 1 | ~~Боевое развёртывание не выполнено.~~**Выполнено 2026-09-21** (`summary-production-deploy.md`); ниже - исходное описание. Миграция боевых файлов проверена в изолированном стенде на их копиях (`summary-real-data-run.md`: без потерь, сбор AS62041 и восстановление на реальных данных работают). В каталоге проекта нет `ripe.db`; `data.json` (6 ASN) и `fqdn_data.json` (4 имени) остались в старом формате; всё проверено только на копиях. Первый запуск на боевых данных выполнит миграцию сразу до схемы версии 3: `last_seen` старых записей станет равным времени миграции, журнал diff и состояние источников начнутся пустыми (клиентам стартовать с `X-Changes-Cursor`), первые запуски сбора создадут состояние источников. | Закрыто |
| 2 | ~~`/health` не видит сбоев источников.~~ Сбои учитываются по каждому источнику, порог `source_failure_threshold` (по умолчанию 3): при суточном расписании FQDN это три дня до `degraded`. | Закрыто |
| 3 | **Безымянные образы Docker: 21 на хосте.** Часть - от пересборок тестового образа `ripe-tests` при разработке, на хосте есть и чужие (`docker image prune` затронет все). | Ухудшилось (было 3) |
| 4 | **Повреждённый `config.json`** переименовывается при чтении; первый успешный `POST /asns` после этого создаст файл без остальных источников, расписания и `ttl_days`. | Без изменений |
| 5 | ~~Сообщение «data saved» после каждой транзакции.~~ Вместо него итоговая строка запуска. | Закрыто |
| 6 | **Структура README:** разделы 8 (Docker) и 9 (SQLite) в конце, разделы 1-4 описывают ручную установку; за время доработок README вырос до 540 строк. Стоит перестроить: Docker и быстрый старт в начало, ручная установка ниже. | Без изменений |
| 7 | **`google.com`** есть в `fqdn_data.json`, но не входит в конфигурацию: его адрес после миграции истечёт через 90 дней. | Без изменений |
| 9 | **Логи APScheduler о плановых запусках скрыты** (чтобы опрос запросов каждые 5 с не засорял лог); остаются сообщения приложения. | Без изменений |
| 10 | **Общая точка отказа хранилища (по графу):** главные узлы `session()` (22 связи), `get_addresses()` (15), `load_full_config()` (14), `_recover()` (14). Развязка через один тип `StorageError` сохраняется, но `db.py` (544 строки) стал самым крупным модулем и совмещает схему, восстановление, копии, журнал и состояние источников. | Переосмыслено |
| 11 | **Рост модулей и цикл зависимостей:**`db.py` берёт пути и настройки из `cidr_collector.py` отложенным импортом внутри функций; `api_server.py` (381 строка) и `cidr_collector.py` (386) совмещают по несколько ролей. Пока работает, но следующий эндпоинт или задание усугубит. | Ухудшилось |
| 12 | **Граф знаний устарел и не заменяет проверку.** Последнее построение (527 узлов, 1029 связей) было до доработки «наблюдаемость» (код `db.py`, `cidr_collector.py`, `api_server.py`, тесты и документы не отражены); хук обновления после коммита не установлен. Изолированных узлов 36 (описания без связи с кодом), расход токенов на построение не учтён (нули). | Требует обновления |
| 13 | **Порог `degraded` для FQDN:** имя, разрешившееся только в неглобальные адреса (без `allow_non_global_ips`), через три запуска даёт `degraded` (`no_global_addresses`); источник, вернувший 0 адресов корректным ответом, `degraded` не вызывает, виден только в `/sources`. | Новое |
1.~~Боевое развёртывание~~**выполнено** (порт 18000, шесть ASN); далее - наблюдение: 03:00 плановый сбор FQDN, 04:30 первая плановая копия, суточный просмотр `/health` и `/sources`. Исходный порядок запуска: копия `config.json`, `data.json`, `fqdn_data.json` вне каталога проекта и вне тома (миграция переименует оригиналы в `*.migrated-*`, но копия на случай ошибки обязательна). Затем `docker compose up -d` с переносом файлов в том (порядок в README), проверка `/health`, `/sources`, `/addresses` и `/addresses/diff`, первый ручной сбор через `POST /collect`, оценка размера базы и журнала, выбор порога `source_failure_threshold`. Нужен отдельный план с откатом.