# План: `GET /addresses/diff?since=` (изменения списка) ## Цель Клиент (роутер, файрвол, скрипт) запрашивает только изменения с момента прошлой синхронизации: что добавилось и что исчезло. Полный список каждый раз не скачивается. ## Дизайн - `GET /addresses/diff?since=<время | курсор>[&type=cidr|fqdn|all][&ip_version=all|4|6]`, чтение открыто (как у `/addresses`). - Ответ: `{"since", "now", "cursor", "added": [...], "removed": [...]}`. Форматы конфигураций и агрегация не поддерживаются (агрегированный diff не аддитивен). - **Журнал изменений** в SQLite: таблица `changes(id, ts, kind, value, action add|del)`. Пишется триггерами на `addresses`, поэтому охватывает все пути удаления (TTL, снятый источник, `purge`) и добавления: - `add`: значение появилось, а у других источников его не было; - `del`: удалена последняя запись значения. Значение, которое есть у нескольких источников, в diff не попадает, пока хотя бы один источник его держит. Первичный импорт из JSON в журнал не пишется. - **Итоговый эффект, а не история:** по каждому значению берётся первое действие после точки `since` (`add` - значения не было, `del` - было) и сверяется с текущим состоянием. Удалено и возвращено в тот же интервал - в diff не попадает. - **Точка отсчёта:** `since` - время ISO 8601 (без часового пояса считается UTC) либо целый курсор из прошлого ответа. **Рекомендуется курсор:** порядковый номер записи журнала не зависит от часов и от длительности транзакции сборщика. Время округляется до миллисекунд, границы включительные (доставка «минимум один раз», повтор безвреден). - **Срок хранения:** `changes_retention_days` в `config.json` (по умолчанию 30, `<= 0` - без очистки). Очистка выполняется в транзакции сбора. Границу («горизонт») хранит таблица `meta`. Если `since` старше горизонта или курсор не из этой базы (больше текущего) - `410 Gone`: клиент забирает полный `/addresses` и продолжает с нового `cursor`. - Ошибки: `400` при неверном `since`; `503` при недоступной базе (общий обработчик). - Чтение diff выполняется в одной read-транзакции (согласованный снимок). ## Изменения 1. `db.py`: `SCHEMA_VERSION = 2` (миграция 1 -> 2: `changes`, `meta`, триггеры; для v0 таблицы создаются после импорта JSON), `prune_changes()`, `get_changes()`. 2. `cidr_collector.py`: `DEFAULT_CHANGES_RETENTION_DAYS`, вызов очистки в обоих `run_collection`. 3. `api_server.py`: эндпоинт `/addresses/diff`, разбор `since`; заголовок `X-Changes-Cursor` в ответах `/addresses` (курсор для первой синхронизации, читается до данных). 4. `README.md`: описание эндпоинта, ключ конфигурации, раздел о хранении. 5. Тесты (2): БД (триггеры: несколько источников, TTL, возврат в тот же интервал, очистка и горизонт); API (400, 410, курсор и время, фильтры). ## Проверка Тесты в контейнере; вручную на копии данных: миграция базы версии 1 -> 2 сохраняет данные, `purge`/TTL порождают `removed`, повторный запрос с `cursor` возвращает пустой diff.