docs(docs): describe durable jobs, telemetry, and bundle limits

Зафиксированы очередь job, семантика метрик, лимиты бандла и исходящих запросов, а также чеклист продакшена для пула соединений.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
DenozordecandCursor committed 2026-11-02 17:24:18 +07:00
1 parent bda45d633e
commit 240d83fc61
15 files changed
+839 -17

No files matched your search

+460
View File
@@ -0,0 +1,460 @@
# EvoBGP · План развития backend
> **Дата:** 26 октября 2026 · **Основа анализа:** checkout `924f111`
> **Фокус:** достоверная статистика, надёжное применение BGP-конфигураций, устойчивые фоновые задачи, экономное использование VPS.
## 1. Главный вывод
EvoBGP уже имеет содержательное ядро: сбор префиксов из AS/CDN/доменов/ручных списков, материализованные снимки, агрегацию CIDR, ревизии, подписанные бандлы, управление BIRD, удалённые агенты, PostgreSQL-мониторинг и обслуживание БД. Основная задача следующей итерации — укрепить гарантии между этими частями и сделать наблюдаемое состояние достоверным.
**Рекомендуемый порядок:** исправление статусов и выполнения задач → единый сбор телеметрии → снижение фоновой нагрузки → новые операторские функции.
**Обязательное условие:** обновление control plane не должно требовать одновременного обновления удалённых спикеров. Уже развёрнутые старые агенты/CLI должны продолжать получать, проверять и применять совместимые бандлы. Новые возможности включаются по capabilities конкретной ноды; ограничения наблюдаемости показываются явно. Политика перехода и проверки — B08.
| Приоритет | Значение | Правило исполнения |
|---|---|---|
| **P0** | Неверное подтверждение сетевого изменения, небезопасное применение | Выполнить до расширения автоматического deploy |
| **P1** | Потеря/повтор задач, недостоверная статистика, ошибки изоляции и защиты | Ближайший инженерный этап |
| **P2** | Производительность, эксплуатация, полезные функции | После стабилизации зависимостей |
| **P3** | Расширенные возможности | По подтверждённой потребности |
### Границы исследования
Первоначальный анализ исходников, миграций, Compose и CI выполнен тремя субагентами **Astra**. Реализация ведётся субагентами **Sol / medium**, проверка — **Astra**; фактический прогресс отмечен ниже. Production, реальные RSS/CPU/IO, размер опубликованных образов и нагрузка операторов не измерялись. Docker CLI в локальной среде отсутствует. Поэтому проценты экономии и гарантированная вместимость VPS здесь не заявляются.
Найденные при анализе устаревшие описания очереди и Svelte UI в `docs/architecture.md` и `docs/overview.md` исправлены на этапе 0. План учитывает фактическую реализацию и подключённый **ReUI PRO**.
## 2. Что уже есть и что следует сохранить
### Статус реализации · 26.10.2026
**Этап 0 «Проверяемая основа» и первая очередь runtime-исправлений.** Реализация: Sol / medium; независимая проверка: Astra. Старый формат запроса node/sync и формат бандла сохраняются. Полный этап 0 остаётся открытым до стендовых проверок ниже.
- [x] Добавлен CI job `postgres-integration`: временная PostgreSQL 16, последовательное применение миграций и repository tests с `-race`; проверяется успех каждого из 23 обязательных тестов, skip/отсутствие теста не даёт зелёный результат.
- [x] Добавлены тесты повторного применения миграций, конкурентного SQL claim, сохранения строки job при повторном открытии pool и idempotency row; исправлен невалидный tenant fixture в существующем CRUD-тесте.
- [x] Добавлены воспроизведения двух дефектов live-status: stale Established и ложный healthy без poll. После исправления S01 tag `backend_repro` удалён; проверки включены в обязательный набор.
- [x] Зафиксирован локальный baseline совместимости: исторические исходники `v1.0.0`, минимальные node/sync/health wire contracts, независимая проверка подписи/хешей и замороженный signed bundle текущего builder. Подробности: [совместимость](backend-compatibility.md).
- [x] Добавлен read-only PowerShell collector ресурcных snapshots с whitelist полей и offline проверками multi-container stats, отсутствия утечки env и неполного результата: [инструкция](backend-baseline.md).
- [x] Уточнены architecture/overview: гибридная Registry + PostgreSQL очередь с ограничениями, реальный tenant-refresh endpoint и текущий React/ReUI UI.
- [x] Исправлен локальный Go lint runner: проверяет пакеты текущего модуля вместо посторонних вложенных worktree и возвращает ошибки vet/linter.
- [ ] Подтвердить новый integration job реальным запуском CI/PostgreSQL. Локальный запуск без DSN компилирует тесты, но DB-проверки пропускаются; это не интеграционный успех.
- [ ] Воспроизвести взаимодействие локального Registry и DB claim при ожидании дольше grace, повторном idempotency key и настоящем restart worker/jobs API. SQL-level тесты выше не доказывают эти гарантии.
- [ ] Инвентаризировать реально установленные версии/digests speakers и прогнать матрицу на старых binaries/images. Source-derived контракт и текущая fixture не заменяют эту матрицу.
- [ ] Снять effective Compose и реальные CPU/RAM/IO, выполнить workload/24h soak на целевом VPS; локально Docker отсутствует.
Результаты локальной валидации и ограничения среды приведены в [backend-baseline.md](backend-baseline.md).
**Первая очередь runtime: B01 / B02 / S01 / B08.** Отметки ниже относятся к реализованным частям, а не к закрытию пунктов целиком.
- [x] B01/B08: bare HTTP 2xx означает доставку без подтверждения (`unconfirmed`); applied обновляется только после явного ACK нужной опубликованной ревизии либо успешного локального apply. Формат запроса старому агенту сохранён.
- [x] B01: результат job содержит результаты по speakers и счётчики published/applied/unconfirmed/failed; ошибки доставки больше не превращаются в общий успешный apply.
- [x] S01: неопрошенное состояние и отсутствующие счётчики возвращаются как unknown/null; сохранённый Established не подменяет текущее наблюдение. Добавлены quality/source/timestamps и проверки назначенного speaker.
- [x] B02: реализованы атомарное переключение `bird.conf` внутри mounted ActiveDir, межпроцессная блокировка, проверяемые поколения и rollback с отдельным context. Astra проверила исправления inline/commented include bypass; переносимые тесты прошли.
- [x] B01: отмена останавливает публикацию следующим speakers, игнорирует поздний ACK и сохраняет частичный результат со статусом cancelled; добавлены регрессии отмены dispatch и legacy empty 204.
- [ ] B02: выполнить Linux fault/process-lock тесты и реальный `bird -p` CI; проверить configure/recovery с настоящим daemon, ENOSPC и аварийное завершение между фазами. Cross-compilation и fake birdc не закрывают эти проверки.
- [x] S01/B01 UI: качество и время наблюдения, last-known подсказки, unknown вместо ложного down/0, результаты неподтверждённого deploy. Исправлены знаменатель deployment, disabled peers и явный unhealthy; Astra проверила. Typecheck/lint/build — exit 0, Vitest — 9/9. Визуальный browser review ещё не выполнен; полный F01 этим не закрыт.
- [x] B01: добавлены persistent progress по speakers и атомарные lease/generation проверки публикации/ACK; `/jobs/{job_id}/report` читает сохранённые результаты после перезапуска, включая связанные recovery jobs. Проверены восстановление отчёта, приоритет собственного прогресса recovery и tenant isolation.
- [x] B01: bounded reconciliation сохраняет порядок исходных намерений, дедуплицирует recovery и ограничивает его тремя раундами. Health observation token отклоняет запоздалые результаты; timeout/отсутствующее поле не восстанавливает устаревший статус. Memory tests порядка/retry budget прошли; SQL-регрессии добавлены в CI.
- [ ] B01: выполнить PostgreSQL restart/fencing/health gate и стенд legacy agent. Двухминутная reservation после неоднозначного ответа 2.56.2 ограничивает повторы, но не доказывает остановку удалённой операции; exactly-once и состояние BIRD не заявляются.
- [x] B08 executables: обязательная legacy-версия **2.56.2**; её pinned source-built CLI прошёл pull/auth/signature против текущего CP, агент serve — текущий dispatch/ACK/health/protocols с подписанным bundle и fake BIRD. Добавлена CI-матрица 2.56.2 и дополнительного CLI 1.0.0; реальный BIRD этим не проверен.
- [ ] B08: реальные image digests 2.56.2, agent/BIRD apply/rollback и смешанный парк по-прежнему требуют стендовой проверки; source-built CLI не закрывает всю матрицу.
- [x] B05 transport/proxy: CDN URL проверяется при dial/redirect, DNS-ответ проверяется целиком, соединение выполняется с проверенным IP; CDN proxy отключён. Приватный node transport отделён. Forwarded-заголовки доверены только `EVOBGP_TRUSTED_PROXY_CIDRS`; auth limiter ограничен 4096 активными IP без вытеснения действующих счётчиков. Astra проверила; полный Go test и lint прошли.
- [x] B05 CDN body: ingest/preview отклоняют распакованный ответ больше 8 MiB вместо обрезания; ограничено чтение error-body при retry. Проверены точный предел/+1, gzip и реальный TLS redirect с контролируемым DNS на private target.
- [x] B05 CSV: превышение 8 MiB, 10000 строк данных или 64 полей в записи возвращает 413 до создания записей. Убрано тихое обрезание, ограничено усиление памяти CSV-парсером, отмена останавливает последующие записи. Тесты exact/+1, неизвестной длины, корректного префикса перед превышением и quoted/multiline полей прошли.
- [x] B05 bundle: ограничены compressed/expanded/logical bytes, payload, manifest, число файлов и пути; закрыты sparse tar, gzip trailing expansion, дубликаты/неподписанные файлы и выход через ссылки. HTTP retry читает не более 64 KiB error-body. Astra проверила; bundle/nodecli/httpclient/pipeline tests и повторные legacy 2.56.2 tests прошли. [Пределы и настройки](bundle-limits.md).
- [x] B05 HTTP: общий предел входного тела 8 MiB проверяется после auth до любых handler side effects; до 4 body-запросов одновременно с удержанием памяти до завершения handler. Добавлены локальные tenant/global concurrency и minute quotas preview/lookup, 429 + CORS-visible Retry-After. Refresh/retry сохраняют replay и shared queue quotas; legacy GET не занимают body slots. Astra review и focused HTTP tests прошли.
- [x] B05 внешние ответы: RIPE 32/4 MiB и DoH 2/1 MiB читаются полностью с проверкой +1, включая gzip; введены default deadlines без ослабления caller/profile deadline. После явного разрешения пользователя восстановлены внутренние DoH/RIPE через отдельный pool; CDN остаётся защищённым и переиспользует соединения. Bootstrap private-DoH и TLS connection-reuse tests прошли; [политика](outbound-policy-decision.md).
- [x] B05/B06 circuit cache: не более 1024 hostname, canonical keys, отказ до dial при заполнении, idle expiry закрытых записей и удаление Prometheus label set. Активные/open записи не вытесняются. Тесты capacity, aliases, lease/ref lifecycle и metric cleanup прошли.
- [ ] B05: выполнить symlink extraction regression в Linux CI (локально Windows пропускает из-за привилегий) и стендовые проверки нагрузки. Локальные квоты не доказывают экономию RSS или общую кластерную квоту; старые правила redirects/proxy для DoH сохранены по разрешённой политике.
- [x] S06 основа: добавлены явные inventory/stored метрики, сохранены старые имена и raw labels. При ошибке чтения БД выдаётся collection_success=0 без ложных нулевых агрегатов; добавлены alert и [описание семантики](metrics-semantics.md).
- [ ] S06: desired/published/confirmed applied/observed live группы и общий статус обязательных размещений ещё не завершены.
- [x] S02 реализация: persistent snapshots, bounded collector с lease/fencing, независимые BGP/health, local authority и atomic discoveries; GET не опрашивает ноды. Coalesced POST refresh подключён к UI. `expires_at`, ошибки GET и offline скрывают устаревшие значения, включая memoized графики. Astra не обнаружила блокеров; 30 web tests, typecheck/lint/build и scoped backend tests прошли. Протокол 2.56.2 сохранён, source-built binary smoke пройден.
- [x] S02 PostgreSQL: lease/fencing, discovery rollback и coalesced refresh исполнены на временном PostgreSQL 16.15; все 23 DB/repository tests пройдены без skip (состояние до manifest migration 000037).
- [ ] S02 release gate: Linux/race и реальный mixed-speaker/BIRD стенд остаются проверками B08.
- [x] B04 миграции: закреплённое соединение, advisory lock до создания schema_migrations, SQL+version в одной транзакции, bounded cleanup и отмена ожидания. Проверка SQL закрывает comment/dollar-identifier/строковые обходы transaction control; Astra повторно проверила.
- [x] B04 acquisition: отменяемое ожидание и явные ошибки перенесены в атомарный snapshot RMW; отдельные lock sessions заменены transaction advisory lock на том же соединении, что выполняет запись.
- [x] B04 scheduler scope: reference Compose использует trusted DB enqueue; HTTP-режим ограничен явным tenant. Проверены два tenants, idempotency, system-tenant exclusion и отмена до работы; Astra review и полный Go suite прошли. ListTenantIDs/ListModules ещё требуют context, поэтому общий deadline пока не ограничивает все DB reads.
- [x] B04 snapshot I/O/RMW: context и ошибки чтения/записи проходят в pipeline/lookup; snapshot read-modify-write и module hash атомарны на одном соединении. Memory concurrency/rollback и обработка partial reads проверены; полный Go test, vet/lint и независимый Astra review прошли.
- [ ] B04 manifest: фиксировать единую версию входов tenant-refresh и не допускать публикации результата по устаревшей конфигурации. PostgreSQL connection-loss/rollback/lock-contention tests прошли на изолированной БД; новые manifest-сценарии потребуют отдельного прогона.
- [x] B04 PostgreSQL до 000036: 23 обязательных DB/repository tests прошли на изолированном PostgreSQL 16.15 под Windows, без skips; race не запускался. [Протокол проверки](backend-baseline.md).
- [ ] B04 PostgreSQL 000037+: после manifest изменений повторить SQL runtime gate; отдельно выполнить Linux/race CI.
- [x] B03: единый durable enqueue/claim, lease/heartbeat/fencing, shared jobs API, квоты и graceful shutdown; replay исходного запроса предшествует динамическому подбору модулей и rate limit. Scoped Go tests прошли, Astra выявленные ошибки replay/seed/метрик устранены.
- [x] B03: при одинаковом CreatedAt выбор и список задач используют стабильные RequestSequence/ID. Добавлена регрессия с 32 одинаковыми timestamps; jobs/store прошли 20 повторов, независимая проверка Astra — 50 повторов regression. Это не обещание порядка завершения параллельных workers или snapshot-пагинации.
- [ ] B03: выполнить SQL/race/restart gate и закрыть разрыв между CRUD и enqueue transactional outbox. Заголовок enqueue-failed делает ошибку видимой, но не устраняет crash gap.
- [x] D02/D04 конфигурация: NATS вынесен в optional профиль, Prometheus получил named volume/retention, deploy worker переведён в static runtime, Go build context сужен, зеркало передаёт digest и обновляется явно без фиксированных пауз. Существующие Docker log caps сохранены. Static review Astra и YAML-проверки прошли; [перенос истории Prometheus](docker-resources.md) описан.
- [x] D02 CLI flags: агент принимает флаги до/после subcommand и отклоняет неизвестные/лишние аргументы watch/serve. В трёх Compose watch-командах флаги идут перед subcommand для совместимости с 2.56.2; argv-тесты и YAML-проверка прошли.
- [ ] D02/D04 runtime: Docker build/config engine, реальные замеры и перенос данных ещё не выполнены. Watch hash-gating, host log replay/rotation, согласование BIRD и SBOM остаются следующими частями Docker-этапа.
| Подсистема | Существующая реализация | Направление развития |
|---|---|---|
| Источники префиксов | `AS_PREFIXES`, `CDN_CIDRS`, `DOMAINS`, `IP_RANGES`; кэши, conditional HTTP, снимки | Контроль свежести, происхождения и качества данных |
| Pipeline | Batch-запись снимков, input hash, агрегация CIDR, ревизии и diff | Согласованный снимок tenant, отмена, лимиты ресурсов |
| BIRD / узлы | Parse-check, LKG, подписанные бандлы, pull/apply, dispatch | Подтверждённый applied, сериализация и надёжное восстановление |
| Jobs | Локальный Registry, ограничение активных workers, PostgreSQL audit/claim/reclaim | Одна авторитетная очередь, lease, идемпотентность, чтение после restart |
| Наблюдаемость | Prometheus, live BGP polling, PostgreSQL snapshots, runtime logs | Фоновый collector, история BGP, качество измерений |
| Доступ | API keys, роли, JWT permissions, tenant context | Проверка доверенных прокси, URL transport policy, интеграционные проверки изоляции |
| Эксплуатация | Maintenance policies, dry-run, pruning, аудит обслуживания | Предсказуемые бюджеты, backup/restore, контроль деградации |
| UI | React, shadcn/ui, ReUI PRO, Frame kit | Использовать существующие блоки для новых backend-возможностей |
Не нужно заново создавать очередь, CDN-кэш, revision diff, rollback, lookup, maintenance или мониторинг PostgreSQL: они уже существуют. Нужно усилить их контракт и завершить недостающие части.
## 3. План улучшения backend
### B01 · Достоверный жизненный цикл deploy — P0
**Основание:** `internal/jobs/worker.go:493` вызывает `SetLastAppliedRevision` до удалённого dispatch. Ошибка dispatch сохраняется в metadata, однако job завершается `Succeed` (`:545`, `:573`). Это позволяет считать удалённую ревизию применённой без подтверждения ноды.
**Работы:**
1. Разделить `desired_revision`, `published_revision`, `dispatched_revision`, `applied_revision`, `verified_revision`.
2. Ввести deployment и результаты по каждому speaker: `pending → dispatched → applying → applied → verified`, отдельно `failed`, `unknown`, `timed_out`.
3. Обновлять `applied_revision` только по явному аутентифицированному сообщению ноды о конкретной revision ID. Для новых агентов дополнительно проверять deployment ID/generation и хеш фактической конфигурации; хеш обязателен для уровня `verified`, но не становится новым обязательным полем старого протокола. Для legacy-агента сохранять уровень подтверждения `agent_reported`; без явного revision ID оставлять результат `unconfirmed`, продолжая совместимую доставку. Старое подтверждение не должно перезаписывать новое поколение deploy (см. B08).
4. Dispatch трактовать как доставку запроса. Для недоступной ноды сохранять desired state и повторять доставку с ограниченным backoff.
5. В результате массовой операции показывать частичный успех и ошибки по узлам. Не переопределять молча семантику существующего `202`: обновить OpenAPI и UI согласованно.
**Приёмка:** недоступная нода никогда не получает новый `applied`; потеря ответа и повторный dispatch не применяют один и тот же deployment дважды; после перезапуска CP корректно восстанавливает прогресс; 1 ошибка из 5 узлов явно видна в результате.
### B02 · Безопасное применение BIRD и восстановление — P0
**Основание:** `internal/birddeploy/apply.go:69–97` игнорирует ошибки создания LKG, меняет файлы по одному, а восстановление после ошибки использует исходный context. При отменённом context повторный configure также может не выполниться. Локальная функция не содержит блокировки конкурирующих apply.
**Работы:** сериализовать операции на один BIRD instance, включая несколько процессов; использовать отдельный staging на deployment; проверять полный набор файлов и реальные include paths; выполнять переключение согласованного дерева на одном filesystem; не продолжать без пригодного LKG; удалять исключённые из новой ревизии фрагменты; выполнять rollback с отдельным ограниченным recovery-context; сохранять отдельно исходную ошибку и результат восстановления.
**Приёмка:** тесты двух конкурентных apply, отмены, заполненного диска, ошибки parse/configure и падения между фазами; после каждого сценария BIRD использует целиком старую или целиком новую проверенную конфигурацию. Статус `restored` выдаётся только после подтверждённого восстановления.
### B03 · Довести PostgreSQL-очередь до надёжного контракта — P1
**Основание:** `internal/jobs/job.go:351–410`, `internal/jobs/durable.go:62–131`, `internal/repository/job_audit.go:23–70`. Enqueue запускает локального worker; сохранение в БД — best-effort. Повтор idempotency key создаёт новый UUID локально, а SQL conflict сохраняет старый ID строки. Ошибки SQL игнорируются. Reclaim ориентируется на возраст running, без полноценного протокола lease.
**Работы:**
1. Для PostgreSQL сделать `INSERT/claim` авторитетным источником выполнения; HTTP `202` возвращать после успешной фиксации задания. Memory-режим оставить явным режимом разработки.
2. Определить семантику idempotency: одинаковый ключ и payload возвращают одну операцию в пределах retention; другой payload с тем же ключом — конфликт; намеренный повтор — новый ключ.
3. Добавить `owner_id`, `lease_until`, heartbeat, номер попытки и fencing token. Claim выполнять только при наличии свободной worker capacity.
4. Реализовать ограниченные retries для временных ошибок, terminal failure и управляемый повтор оператором. Модель доставки — at-least-once с идемпотентными эффектами, без обещания exactly-once.
5. Ограничить размер ожидающей очереди и число заданий tenant. Текущий semaphore ограничивает активных workers, но не число ожидающих goroutine.
6. List/get/cancel/report читать из общего хранилища: сейчас `routes.go:870–944` обращается к локальному Registry.
7. При shutdown прекратить claim, завершить либо отпустить lease и закрыть БД после остановки workers.
**Приёмка:** две API-реплики, restart и повтор запроса дают один логический job; живой долгий worker не перехватывается; сбой записи в БД не даёт ложного `202`; job виден и отменяется через любую реплику; нагрузочный burst не вызывает неограниченный рост памяти.
`SKIP LOCKED` подходит для конкурирующих потребителей очереди, но сам по себе не решает lease и идемпотентность: [официальная документация PostgreSQL](https://www.postgresql.org/docs/18/sql-select.html#SQL-FOR-UPDATE-SHARE).
### B04 · Миграции, блокировки и tenant isolation — P1
**Основание:** `internal/db/apply.go:34–50` выполняет проверку версии, SQL и запись версии отдельно; миграции запускаются при открытии pool. В `internal/repository/module_snapshot.go:177` используется advisory lock с background-context. Standalone scheduler обходит tenants, но HTTP refresh отправляет с одним bearer (`internal/scheduler/run.go:70–116`).
**Работы:**
- Один владелец миграций: dedicated migrate step либо advisory lock на закреплённом соединении; транзакция на поддерживаемую миграцию, явный отдельный путь для non-transactional DDL; проверка совместимости schema при readiness.
- Передавать context через store/repository I/O; lock acquisition обязан иметь timeout и возвращать ошибку, а не продолжать без блокировки.
- Сделать tenant-refresh согласованным: сериализация render на tenant и фиксация версий входных снимков. Начать с транзакционного manifest входных версий, затем оптимизировать.
- Для scheduler выбрать явную модель: доверенный DB worker либо строго ограниченная service identity с tenant-scoped запросами. Не использовать пользовательский bearer как неявный доступ ко всем tenants.
- Проверить tenant scope для jobs, revisions, node acknowledgements, monitoring и maintenance. Глобальный мониторинг БД должен быть доступен только соответствующей административной роли.
**Приёмка:** параллельный старт API/workers не ломает миграции; неудачная миграция не записана как успешная; отмена операции прерывает ожидание lock; тесты минимум с двумя tenants доказывают отсутствие смешивания данных.
### B05 · Защита исходящих запросов и ограничение нагрузки — P1
**Основание:** CDN URL уже проверяется, но DNS precheck с кэшем (`internal/pipeline/cdn_url.go:96–132`) отделён от фактического соединения, а общий HTTP transport не проверяет redirect/dial (`internal/httpclient/httpclient.go:18–25`). `clientIP` доверяет X-Forwarded-For (`routes_audit.go:134`), а rate limiter не удаляет старые IP-ключи (`ratelimit.go:36–60`). Это подтверждённые пробелы защиты; эксплуатация в текущей сети не проверялась.
**Работы:** проверять целевые адреса при соединении и каждом redirect; определить поведение при HTTP proxy; разделить публичные CDN/DoH и разрешённые приватные адреса агентов; ограничить размеры скачивания/распаковки/CSV и длительность операций; добавить квоты дорогостоящих refresh/preview/lookup; доверять forwarded headers только от настроенных proxy; ограничить и очищать rate-limit cache.
**Приёмка:** тесты redirect на private address, смены DNS-ответа, IPv4-mapped IPv6, oversized/decompression payload; корректный доступ к явно разрешённому private agent; поддельный XFF не обходит лимит; cardinality IP не увеличивает память без границы.
### B06 · Ускорить lookup, API и pipeline на больших наборах — P2
**Основание:** `internal/lookup/lookup.go:193` перебирает модули и snapshots; для домена поиск повторяется по каждому resolved IP (`:173–185`). Ошибка DNS сейчас не отражена отдельно от отсутствия IP-совпадений. Размер выигрыша требует benchmark.
**Работы:**
- Добавить repository-операцию membership lookup с tenant predicate и индексом по сетевым префиксам; выбрать PostgreSQL `inet/cidr` и индекс по результатам EXPLAIN. Для memory backend — компактный префиксный индекс, если профиль оправдает сложность.
- Batch lookup всех resolved IP; лимиты/пагинация совпадений; `resolution_status` и используемый resolver; единая семантика с DoH pipeline.
- Для list endpoints — пагинация в SQL, а не после загрузки всей коллекции; summary DTO без тяжёлых snapshots и BIRD-конфига.
- Профилировать allocations, COPY/merge, render и bundle; уменьшать копирование больших срезов и повторную сериализацию по конкретным pprof/benchmark данным.
- Повтор внешних HTTP: учитывать 429/Retry-After, jitter и общий deadline; сохранять существующий circuit breaker, добавив наблюдаемость и ограничение его кэша.
- Не менять все store-интерфейсы за один раз: начать с горячих I/O путей и cancellable методов.
**Приёмка:** baseline и сравнение на 10k/100k/1M префиксов в подходящем профиле; одинаковый результат IPv4/IPv6 до и после; отсутствие full snapshot scan в DB lookup; API latency не растёт пропорционально числу открытых браузеров.
### B07 · Интеграционные проверки и эксплуатационная готовность — P1/P2
**Основание:** CI запускает `go test -race`, но в `.gitea/workflows/quality.yaml` нет отдельного PostgreSQL service/DSN для repository tests. Эти тесты вызывают `t.Skip` без `EVOBGP_TEST_DATABASE_URL` (`internal/repository/backend_test.go:17`). Таким образом зелёный общий Go job не доказывает работу PostgreSQL-сценариев.
**Работы:** добавить обязательный integration job с временной PostgreSQL и применением миграций; отдельные сценарии durable queue, cancel, two-tenant, restart, prune и concurrent migration; стенд CP + два агента + BIRD для apply/rollback. Добавить backup/restore runbook и периодическое восстановление в изолированное окружение: БД, signing key, node credentials, конфигурации и последние рабочие ревизии. Согласовать RPO/RTO по реальным требованиям.
OpenTelemetry сейчас является stub (`internal/observability/otel.go:11`). Сначала завершить структурированные логи и request/job/deployment correlation IDs; полноценный tracing включать опционально с sampling, когда он нужен для расследований.
**Приёмка:** CI явно подтверждает исполнение DB-тестов, а не skip; аварийный сценарий воспроизводим; восстановленный стенд проверяет подписи и применяет известную ревизию; все изменения проходят существующие Go/OpenAPI/BIRD quality gates.
### B08 · Обратная совместимость и постепенное обновление спикеров — P1, обязательный release gate
**Цель:** новый CP обслуживает смешанный парк старых и новых speakers. Отсутствие нового telemetry/ACK API не должно лишать старый speaker возможности работать по существующему pull/sync протоколу. Совместимость доставки и полнота подтверждения — отдельные характеристики.
**Основание в текущем коде:** `internal/nodedispatch/dispatch.go` уже читает `applied_revision_id` из sync-ответа, а `health.go` — `last_applied_revision_id` и `last_sync_at`. При этом sync-клиент подставляет запрошенную revision ID, если ответ её не содержит. Такая подстановка не является подтверждением ноды и должна быть устранена без отказа от legacy dispatch. Текущий формат бандла описан в `internal/bundle/pack.go` и проверяется в `verify.go`.
#### Контракт совместимости
| Поверхность | Что сохранить | Как вводить новое |
|---|---|---|
| Node API control plane | Существующие `/v1/speakers/{id}/revisions/latest`, `/bundle/{revision_id}`, получение signing public key и используемые механизмы auth | Новые обязательные поля/семантика — через согласованную capability или отдельную версию API; старые запросы остаются допустимыми |
| Agent API | Текущие sync/health запросы и известные поля ответа | Не отправлять новые поля старым strict-decoders; расширенный ACK и telemetry — только после определения возможностей |
| Signed bundle | Для legacy сохранить tar.gz, layout файлов, manifest, Ed25519/SHA-256 и проверку доверенного ключа | Новый формат выдавать только совместимому клиенту; legacy-вариант создавать и подписывать отдельно, не менять уже подписанные байты |
| BIRD-конфигурация | Синтаксис и capabilities реально установленной на speaker версии | Рендерить по профилю совместимости узла; новую директиву не отправлять неподдерживающему BIRD |
| Credentials / TLS | Действующие ключи доступа, сохранённый signing key и проверка TLS | Ротация через отдельный переход с периодом перекрытия там, где протокол его поддерживает; не обходить подпись/auth ради совместимости |
| UI / monitoring | Старые узлы остаются управляемыми и видимыми | Явные `legacy`, `unsupported`, `unconfirmed`; отсутствие новой метрики не означает неисправность BGP |
#### Определение возможностей и fallback
1. Перед изменением протокола инвентаризировать реальные версии/дигесты `evobgp-agent`, `evobgp-node`, BIRD и режимы работы: agent serve, pull по расписанию/CLI, иные используемые варианты. Зафиксировать поддерживаемый legacy baseline, а не предполагать, что все ноды равны текущему checkout.
2. Новые агенты объявляют protocol version и capabilities, например `deployment_ack`, `config_hash`, `telemetry_v2`, `bundle_v2`. Конкретные имена закрепить в OpenAPI. Возможности важнее сравнения строк версии.
3. Отсутствие capability metadata в корректном старом ответе либо документированный `404/405` capability endpoint означает legacy. Timeout, `401/403`, TLS error и некорректный ответ означают ошибку связи/доступа; они не должны автоматически запускать downgrade или обход проверки безопасности.
4. Для legacy использовать существующие request/response shapes и signed bundle. Если старый агент явно сообщает совпадающую revision ID, записывать `agent_reported`, но не утверждать проверку config hash. Голый `2xx` подтверждает только запрос; без подтверждения ревизии показывать `dispatched/unconfirmed`. Если старый health сообщает revision ID без времени наблюдения, показывать ограниченную свежесть, не выдавая его за ACK нового deployment.
5. Для нод без generation-aware ACK сериализовать deploy на speaker, не допускать неоднозначных перекрывающихся операций. Устаревший ответ не меняет текущий desired state; неоднозначность остаётся `unconfirmed` до новой проверки.
6. Старые pull-only узлы продолжают получать published revision без обязательного push/callback. Отсутствие ACK не вызывает бесконечный повтор apply: bounded retry относится к доставке, наблюдение — к reconciliation с backoff.
7. Новые health-gated rollout/auto-rollback допускают только узлы с достаточным уровнем подтверждения. Legacy-узел можно обновить прежним способом с явным ограничением автоматической верификации; его не блокируют глобально из-за отсутствующей новой функции.
#### Порядок перехода
- **Сначала CP:** добавить совместимые адаптеры, schema expansion и распознавание возможностей; новые флаги включать по speaker/cohort. Старый путь доставки остаётся рабочим.
- **Затем canary-агент:** проверить новый протокол на одной ноде, затем обновлять остальные поочерёдно. Смешанный парк — поддерживаемое рабочее состояние, а не только короткий момент установки.
- **Откат:** проверить новый CP со старым агентом и новый агент с предыдущим поддерживаемым CP. Сохранить старый digest, совместимый bundle generation и предыдущий протокол. Миграции БД — expand/contract: удаление/переименование полей откладывается за окно rollback; обратная совместимость схемы проверяется запуском предыдущего CP на обновлённой схеме.
- **Срок поддержки:** поддерживать инвентаризированные развёрнутые legacy-версии в рамках этого плана. Точный minimum supported version и окно deprecation зафиксировать после инвентаризации. Удаление legacy-пути — отдельное решение владельца проекта после миграции нод; не отключать его автоматически при первом новом релизе. Для опасного неподдерживаемого релиза — адресный план обновления, без бессрочного сохранения уязвимого поведения.
**Приёмка:** integration matrix использует реальные сохранённые старые binaries/images и golden bundles, а не только mocks текущего агента. Обязательные пары: новый CP + самый старый поддерживаемый агент/CLI; новый CP + текущий агент; предыдущий CP + новый агент; смешанный парк. Проверить pull/sync, подпись, auth, parse/apply/rollback, отсутствующие capabilities/ACK, reconnect после offline и поздний ответ. Обновление только CP не требует изменений на legacy-ноде и не вызывает BGP flap; недоступные новые функции явно обозначены. Проверка совместимости входит в CI/release gate каждого изменения node API, bundle, auth и BIRD render.
## 4. Сбор статистики: целевая модель
### S01 · Отделить текущее состояние от последнего известного — P1
**Основание:** `internal/httpapi/peers_live.go:275–283` не заменяет сохранённый session state, если live-проверка не нашла состояние. В `speakers_live.go:117–126` отсутствующий результат превращается в пустой view, а `:24–48` считает отсутствие ошибки успешным polling.
Для каждого измерения хранить `observed_at`, `received_at`, `last_success_at`, `source`, `quality`, `error_code`. Состояния качества: `ok`, `stale`, `unreachable`, `unknown`, `disabled`. Отдельно хранить последнее подтверждённое BGP state. Неизвестное значение — `null`/gap, а не ноль и не зелёный статус.
**Приёмка:** выключение агента переводит данные в stale/unreachable; удалённая сессия не остаётся Established; не настроенный collector не считается успешным; UI показывает возраст измерения и причину отсутствия данных.
### S02 · Перенести polling из чтения API в фоновый collector — P1
Исходно live-опрос был привязан к HTTP-чтению и короткому BGP cache, а health опрашивался в `collectSpeakerLiveStatus`. В реализации S02 эти пути удалены: GET читает persistent snapshots, collector выполняет ограниченные фоновые опросы.
**Целевая цепочка:**
```mermaid
flowchart LR
N[Agent / BIRD] --> C[Collector: jitter, limits, deadlines]
C --> L[Последнее состояние и качество]
C --> E[История событий]
C --> M[Prometheus metrics]
L --> API[Read API]
E --> API
API --> UI[ReUI PRO: dashboard и диагностика]
```
Начальный интервал — 15–30 секунд как проверяемая настройка, не гарантированный SLA. Добавить ограничение параллельных опросов, jitter, backoff для недоступных узлов и единственного владельца опроса в нескольких CP-репликах. Обычный GET отдаёт snapshot сразу. Кнопка «Обновить» ставит объединяемый запрос, не создаёт отдельный fan-out на каждого клиента.
**Приёмка:** при переходе с 1 на 20 UI-клиентов число poll к одному speaker практически неизменно; просроченные данные отмечаются явно; недоступный speaker не задерживает всю страницу; очередь collector ограничена.
### S03 · История BGP и событий — P2
Добавить историю переходов BGP state, длительности недоступности, flap count, времени последнего established, prefix counts по AFI/SAFI там, где их надёжно отдаёт BIRD. Отделить BGP FSM от состояния самого агента и транспорта управления.
Рекомендуемое разделение: PostgreSQL — события, deployment, аудит и ограниченные агрегаты; Prometheus — временные ряды с ограниченной cardinality. На минимальном VPS разрешить режим только событий и последнего snapshot. Не записывать каждый неизменившийся poll как полноценное событие.
Начальные retention-параметры для проверки: детальные события 30 дней, пятиминутные агрегаты 90 дней; это настраиваемые предложения, не требование к любому VPS. Оценивать объём по `число рядов × 86400 / интервал × дни`, затем измерять фактический размер с индексами/WAL. Partitioning вводить при подтверждённом объёме, не автоматически.
**Приёмка:** разрывы данных видны на графике; restart collector не создаёт ложный flap; повторные события дедуплицируются; очистка истории ограничена по времени/пакету; экспорт не блокирует ingest.
### S04 · Статистика качества источников и pipeline — P1/P2
Для каждого источника: время последней попытки и успеха, latency, HTTP status, ETag/cache hit, valid/invalid/duplicate prefix counts, возраст использованных данных, число последовательных ошибок. Для каждой ревизии — manifest источников и их версий, raw/aggregated counts, добавленные/удалённые префиксы, предупреждения о stale fallback.
Настроить пороги изменений: внезапный пустой ответ или массовое удаление префиксов переводят ревизию в `needs_review`; ручное исключение оставляет audit trail. Порог зависит от модуля, а не от одной глобальной константы.
**Приёмка:** оператор видит, почему данные не обновились; временная ошибка upstream не выдаётся за новый успешный ingest; опасное сокращение списка не публикуется автоматически без установленной политики.
### S05 · Правильная аналитика PostgreSQL и алерты — P2
Не смешивать накопительные статистики PostgreSQL с показателями за окно. Для rate/delta учитывать `stats_reset`, временной интервал и исчезновение query ID. В корреляции показывать только реально измеренные точки: текущий `internal/pgmonitor/correlation.go` объединяет историю jobs с одной текущей точкой cache hit, а `RecordCorrelationSnapshot` — заглушка.
Расширить существующие Prometheus alerts на потерю телеметрии, stale источники, очередь/возраст jobs, lease failure, deployment drift и ошибки БД. Предусмотреть окно обслуживания, задержку срабатывания, дедупликацию и восстановление. Правило «нет Established» отдельно от «не удалось опросить BIRD».
Не использовать job ID, revision ID, полный URL, домен или префикс как неограниченные labels. Это соответствует рекомендациям [Prometheus по cardinality](https://next.prometheus.io/docs/practices/naming/). Детали хранить в событиях и логах.
**Приёмка:** outage collector вызывает отдельный alert; графики не рисуют отсутствующие значения как нули; периодические snapshots не дублируются каждой API-репликой; количество рядов имеет проверяемый бюджет.
### S06 · Разделить смысл метрик — P1
`materialized_prefixes_max/sum` агрегируют сохранённые ревизии (`internal/repository/postgres.go:78–89`), а `bgp_peer_sessions` берёт сохранённый `meta_json.session_state` (`:100–116`). Локальная метрика Established не описывает все remote speakers. Это разные показатели: их нельзя объединять в один KPI «маршруты сейчас» или «сеть здорова».
Добавить явные группы **inventory / desired / published / confirmed applied / observed live**; для multi-speaker считать отдельно «есть Established хотя бы где-то» и «здоровы все обязательные размещения». Общий статус — healthy/degraded/down/unknown по задокументированной политике. При смене метрик сохранить переходный период и обновить dashboards/alerts. Ошибка DB-агрегации должна иметь collection error, а не выглядеть достоверным нулём.
**Приёмка:** добавление старой ревизии в историю не увеличивает KPI текущих маршрутов; outage одного обязательного speaker даёт degraded даже при Established на другом; fresh live и сохранённая конфигурация явно различаются в API.
## 5. Отдельный план улучшения и добавления функций
| ID / приоритет | Функция | Польза и объём MVP | Зависимости / критерий готовности |
|---|---|---|---|
| F01 · P1 | **Центр состояния сети** | Пиры, speakers, stale источники, deployment drift и очередь проблем на одном экране | S01–S02, B01; каждый статус имеет источник и возраст |
| F02 · P1 | **Паспорт ревизии** | Продолжение существующих preview/diff: происхождение данных, impact по узлам, warnings и подтверждения apply | B01, S04; можно объяснить каждое существенное изменение |
| F03 · P2 | **Управляемая раскатка** | Canary speaker, этапы rollout, лимит параллелизма, pause/resume, окно обслуживания | B01–B03/B08; ошибка canary останавливает продвижение; legacy-путь доступен отдельно от автоматической верификации |
| F04 · P2 | **История инцидентов и уведомления** | Flap, outage, stale, deploy failure; acknowledge, silence, webhook и один выбранный канал уведомлений | S03–S05; дедупликация, recovery notification и повтор доставки |
| F05 · P2 | **Объяснение принадлежности IP/домена** | Развитие existing lookup: источник → модуль → community → ревизия → desired/applied speaker | B06, F02; DNS failure отделён от «совпадений нет» |
| F06 · P2 | **Проверка источника до включения** | Развитие CDN preview: тип ответа, нормализация, ошибки строк, ожидаемый diff, тест расписания | S04, B05; тест не меняет опубликованную ревизию |
| F07 · P2 | **Backup и восстановление из панели** | Статус последних backup/restore drill, экспорт manifest, запуск разрешённой backup job | B03/B07; секреты не попадают в обычный экспорт UI |
| F08 · P2 | **Диагностический пакет** | Версии CP/agent/BIRD, job/deploy timeline, redacted logs, config hashes | B07; токены и приватные ключи исключены; лимит размера |
| F09 · P3 | **RPKI и защитные BGP-политики** | Warning-only проверка origin, max-prefix guard, затем опциональное применение политики | Отдельный дизайн и BIRD-тесты; отсутствие валидатора не меняет маршрутизацию молча |
| F10 · P3 | **Трафик по интерфейсам/потокам** | Если нужен именно трафик: SNMP/agent counters для bps/pps; NetFlow/IPFIX — отдельный сервис с sampling/retention | Отдельный бюджет и потребность; BGP counters нельзя выдавать за объём пользовательского трафика |
**ReUI PRO уже подключён.** Новые экраны строить на существующем `surface: frame` и `apps/web/src/components/reui-kit/`: `OpsDashboard` для F01, `ResourcePage` для событий/источников, `DetailPanel` для ревизии, `SettingsShell` для политик. Сохранять DataGrid/Filters и семантические Badge. Показывать stale/unknown/partial failure, а не только success/error. Переустановка UI-библиотеки или редизайн shell не нужны. Реализация UI должна следовать [контракту проекта](ui-design-contract.md) и обязательным typecheck/lint/build.
## 6. Отдельный план оптимизации Docker и ресурсов хоста
> **Меньший образ** сокращает загрузку, распаковку и место на диске. **Меньше RAM/CPU/IO** достигается также сокращением процессов, работы collectors, повторных configure, логирования и конкуренции. Эти результаты измеряются отдельно.
### Что уже оптимизировано
В `deploy/docker/gobinary/Dockerfile:26` уже используются multi-stage, `CGO_ENABLED=0`, `-trimpath`, `-s -w`; scheduler/ingest/render используют distroless targets. BuildKit registry cache и общая Go/web сборка присутствуют в `docker-bake.hcl`. `.dockerignore` исключает тяжёлые лишние каталоги. nginx уже работает с одним worker; Docker stdout rotation настроена. Повторное добавление этих механизмов не даст заявляемого нового выигрыша.
### D00 · Измеримый baseline — P1, до изменения лимитов
Зафиксировать image digests, effective Compose, vCPU/RAM, filesystem/cgroup, число tenants/modules/prefixes/speakers/peers. Собрать следующие сценарии на отдельном стенде:
| Сценарий | Что измерять | Зачем |
|---|---|---|
| Idle 15–30 минут | CPU, RSS/cgroup memory, SQL connections, число poll/configure, bytes logs | Найти постоянную лишнюю работу |
| Refresh → render → apply | Длительность, peak memory, allocations/GC, DB waits, BIRD parse/configure | Не оптимизировать idle ценой пикового OOM |
| Несколько refresh + UI | p95/p99 API, queue wait, CPU throttling, IO PSI | Проверить backpressure |
| Недоступные upstream/agents | Retries, goroutines, stale age, рост очереди | Убедиться в ограниченности деградации |
| Restart и 24h soak | Recovery, leaks, WAL/log growth, OOM/restarts, BGP flaps | Проверить эксплуатационный результат |
| Cold/warm image build и pull | Compressed bytes, unique/shared layers, build/pull/extract/start time | Отделить CI, сеть, диск и runtime |
Использовать effective `docker compose config`, `docker inspect` для фактических cgroup limits, `docker stats`, `docker system df -v`, cgroup memory/events/cpu.stat, PostgreSQL statistics и BIRD counters. `shm_size` — допустимая ёмкость, не автоматически занятая RAM. Shared layers не считать несколько раз.
### D01 · Исправить ресурсную модель профилей — P1
`docker-compose.microvps-full.yaml:9` обещает около 800 MiB суммы лимитов. С учётом наследуемого edge получается **1032M**: all 280 + PostgreSQL 256 + BIRD 96 + agent 48 + NATS 48 + web 48 + Prometheus 128 + Traefik 128. Это сумма верхних границ, **не измеренная RAM**, но заявленного резерва для ОС на 1 GiB VPS она не обеспечивает.
Production example и remote-speaker Compose не имеют аналогичных CPU/RAM caps. Подготовить согласованную матрицу ресурсов:
| Профиль | Предлагаемый состав | Политика |
|---|---|---|
| **microvps-core** — новый вариант | all + PostgreSQL + нужные BIRD/agent/edge; UI по потребности | Минимум фоновых процессов; внешний scrape либо ограниченный локальный мониторинг |
| **microvps-full** | Core + UI + локальная история/мониторинг | Допуск на 1 GiB только после workload-теста; если бюджет не сходится — честно поднять минимальные требования |
| **reference** | API и отдельные workers | Сохранить изоляцию процессов, но суммировать pools и concurrency |
| **remote-speaker** | BIRD + agent serve + защищённый endpoint | Отдельный бюджет на маршруты, reconfigure и reconnect; CP workload сюда не переносить |
Оставлять измеренный запас ОС/Docker и на пики BIRD/subprocesses. Не ужимать BIRD лимиты до появления flap/OOM. Согласовать значения с реальным cgroup, а не только YAML. Основа: [Docker resource constraints](https://docs.docker.com/engine/containers/resource_constraints/).
### D02 · Сократить лишние процессы и повторяющуюся работу — P1/P2
| Изменение | Основание | Ожидаемый эффект | Проверка |
|---|---|---|---|
| Вынести **NATS** в optional профиль | `internal/broker/connect.go:12–18` только логирует URL; Compose запускает сервис и зависимости | Удаление лишнего процесса и его RAM/IO в минимальном профиле | Внутренних потребителей нет; перед production проверить внешних клиентов; удалить зависимости/env согласованно |
| Change-driven или hash-gated **BIRD configure** | `cmd/evobgp-agent/main.go:86–94` безусловно configure в watch-цикле, обычно каждые 30s | Меньше работы при неизменной конфигурации | Реальный change применяется; restart/manual change покрываются reconciliation; учитывать, что remote serve — другой режим |
| Проверить CLI flags | `flag.Parse` до positional subcommand, Compose передаёт флаги после `watch` | Настройки interval/socket реально действуют | Тест argv для watch/serve; неверный flag не игнорируется молча |
| Ограничить **дублирование логов** | Production log collector делает `docker logs -f` в host-файлы; reconnect без cursor может повторять историю | Меньше disk IO и роста host logs | Cursor/rotation, dedup replay; Runtime Logs API продолжает работать |
| Использовать существующий cleanup осознанно | `internal/runtimelogs/policy.go:32–37` default disabled | Быстрый контроль накопления | Проверить политику/retention; cron threshold не выдавать за жёсткий byte cap |
### D03 · Настроить Go, PostgreSQL и мониторинг как общий бюджет — P2
- На текущем Go 1.24 провести A/B с `GOMAXPROCS` по CPU quota и `GOMEMLIMIT` с запасом для не-Go памяти и subprocesses. `GOMEMLIMIT` — мягкий лимит runtime, не полный RSS контейнера. Отдельно оценить обновление toolchain, где изменено container-aware поведение; не смешивать обновление версии с первыми correctness fixes. См. [Go GC guide](https://go.dev/doc/gc-guide) и [Go 1.25 runtime](https://go.dev/doc/go1.25#runtime).
- Настроить уже существующие `EVOBGP_DB_MAX_CONNS/MIN_CONNS` (`internal/db/open.go:28–36`) по роли. Сумма pools reference-профиля должна укладываться в PostgreSQL max_connections. `work_mem` оценивать с учётом числа одновременно выполняемых операций, а не как единственный буфер процесса.
- Ограничить общий job concurrency и отдельные бюджеты сетевого ingest/render/deploy/maintenance. Maintenance не должен вытеснять live collection и подтверждение deploy.
- Для Prometheus добавить явный named data volume, retention по времени и размеру с запасом для WAL; проверить recreate без потери истории. В текущем Compose mount `/prometheus` явно не закреплён. Для microvps проверить scrape 30–60s вместо 15s либо external scrape; оценить влияние на detection latency. Сокращение retention не означает пропорционального сокращения head RAM. См. [Prometheus storage](https://prometheus.io/docs/prometheus/latest/storage/).
### D04 · Уменьшить образы и ускорить сборку — P2
1. **Кандидат на облегчение deploy worker.** Target `evobgp-deploy` наследует birdc runtime (`docker-bake.hcl:238–240`), хотя `internal/deploy/run.go` выполняет наблюдение drift. Проверить startup/dependency paths и перевести в уже существующий static runtime, если BIRD tools действительно не вызываются. Считать unique-layer disk отдельно от размера одного image.
2. **Согласовать BIRD toolchain.** API/all/node используют сборку BIRD 2.14 на Debian, daemon/agent — Ubuntu package (`gobinary/Dockerfile:50–58`, `bird2/Dockerfile:4–12`). Сверить реальные версии/capabilities и parse-check на тех же конфигурациях. Затем унифицировать версию/источник и минимальные runtime libraries для новых образов, сохранив render/validation profile старых удалённых BIRD по B08. Обновление CP image не требует синхронного обновления всех speakers. Не переносить динамический BIRD в scratch без зависимостей.
3. **Сузить Go build inputs.** Текущая общая сборка видит root context. Проверить инвалидирование при web-only изменении; отделить Go sources/go.mod/go.sum/migrations и необходимые embed assets. Сохранить общую компиляцию targets, если benchmark не докажет пользу другого подхода. Проверить cold ephemeral builder: layer cache и cache mounts — разные механизмы. См. [Docker cache optimization](https://docs.docker.com/build/cache/optimize/).
4. **Обновляемые base digests.** Mirror script пропускает существующий tag и содержит пять фиксированных `sleep 8` (`mirror-base-images.sh:46–69`). Сделать контролируемый digest refresh и задержки только при retry/rate limit. Это улучшение CI/воспроизводимости, не экономия RAM приложения.
5. **Проверки поставки.** Фиксировать digest, проверять зависимости и базовые образы, генерировать SBOM при сборке; не включать tooling сборки в runtime. Не применять UPX и случайную смену libc ради красивого image-size числа без benchmark и совместимости.
### Приёмка Docker-этапа
До/после используют одинаковый workload и аппаратный профиль. Уменьшение compressed image size, unique disk, steady/peak memory, CPU и logs/day отражается раздельно. Обязательные ограничения: нет новых OOM/restarts и BGP flaps; наборы prefix/community совпадают; подпись, apply и rollback работают; p95/p99 и время refresh не ухудшены сверх согласованного допуска (начальная гипотеза — 5%, уточнить по шуму стенда).
Rollout: лаборатория → canary → остальные узлы; immutable tag/digest и предыдущий рабочий digest готовы. Пересоздавать только изменяемые сервисы с сохранением volumes. Экономия места не должна включать удаление рабочих данных или истории без retention-политики.
## 7. Очерёдность реализации и оценка объёма
Оценки ниже — предварительные **человеко-дни** одного опытного инженера, включая тесты и документацию. Это не календарное обещание. После первых воспроизведённых сценариев уточнить оценки; заложить резерв 25–35% на совместимость, миграции и fault injection. Работы могут идти параллельно только при разделении областей и соблюдении зависимостей.
| Этап | Результат | Задачи | Оценка |
|---|---|---|---|
| **0 · Проверяемая основа** | PostgreSQL integration CI, воспроизведения проблем, baseline ресурсов, инвентаризация legacy | B07, D00, начало B08, актуализация architecture | 3–5 дней |
| **1 · Доверие и защита** | Честные live/applied статусы, безопасный BIRD recovery, закрытие URL/proxy gaps | S01/S06, минимальный B01, B02, B05 | 8–14 дней |
| **2 · Устойчивое исполнение** | Общая durable очередь и jobs API, миграции/locks, multi-tenant scheduler | B03–B04, завершение B01 | 10–16 дней |
| **3 · Статистика и ресурсный бюджет** | Независимый collector, история, alerts, измеренная Docker-конфигурация | S02–S05, D01–D04, B06 | 10–18 дней |
| **4 · Операторские функции** | Dashboard, паспорт ревизии, rollout, incidents | F01–F04/F06, по очереди | 8–14 дней |
**Основной объём:** ориентировочно 39–67 человеко-дней до резерва. F05/F07/F08 и особенно F09/F10 оценивать отдельно после уточнения глубины; они не должны задерживать correctness и эксплуатационные исправления. Наличие ReUI PRO сокращает работу над типовыми экранами, но не заменяет backend-контракты и проверки отказов.
**Дополнение по совместимости B08:** предварительно ещё 4–8 человеко-дней на adapters, version matrix и тесты реальных legacy-артефактов; итого 43–75 человеко-дней до резерва. Уточнить после инвентаризации. B08 выполняется сквозным потоком этапов 0–3 и обязателен до выпуска изменённых протоколов B01/S02 или BIRD toolchain D04.
### Первая итерация: конкретный backlog
1. Добавить PostgreSQL integration job и regression tests: повтор idempotency key, queued > grace при двух workers, restart/history, stale Established, непроведённый poll.
2. Исправить модель live-status; добавить freshness/quality в OpenAPI и UI.
3. Прекратить преждевременную запись remote `applied`; показывать pending/unknown до ACK.
4. Сериализовать BIRD apply и сделать ошибки LKG/recovery явными.
5. Исправить transport-level CDN URL policy и trusted-proxy обработку.
6. Снять baseline effective Compose/RAM/CPU/IO, подтвердить бюджет microvps; подготовить optional NATS и bounded logs изменения.
7. Зафиксировать legacy baseline и golden bundles; добавить проверки «обновили только CP — старый speaker продолжает pull/sync/apply». Выполнить до выпуска изменений подтверждения apply и telemetry.
На первой итерации допустимо временно ограничить конкурентный deploy и число API-writers, пока lease/fencing не готовы. Это должно быть явным ограничением конфигурации и runbook, а не скрытой надеждой на один процесс.
### Зависимости, которые нельзя пропускать
```mermaid
flowchart TD
A[PG integration и воспроизведение отказов] --> B[Миграции и transactional enqueue]
B --> C[Lease / fencing / durable jobs API]
C --> D[Надёжный deployment workflow]
E[Сериализация BIRD и recovery] --> D
D --> F[Canary / автоматический rollout]
G[Честные статусы и timestamps] --> H[Фоновый collector]
H --> I[История / alerts / операторская аналитика]
J[Baseline ресурсов] --> K[Docker и runtime tuning]
H --> K
```
## 8. Измеримые результаты и правила готовности
| Направление | Контрольная проверка |
|---|---|
| Достоверность | Unknown/stale не отображаются как Established/healthy; applied всегда имеет подтверждение |
| Очередь | При DB outage запрос не принят ложно; restart восстанавливает задания; lease и эффект защищены от повторов |
| Сеть | Конкурентный apply и сбой rollback не скрываются; old/new поколения не смешиваются |
| Статистика | Число UI клиентов не определяет частоту polling; история имеет coverage/gaps и ограниченный retention |
| Производительность | Сохранён baseline; p95/p99, peak memory и GC сравниваются на фиксированном наборе данных |
| VPS | Effective limits проверены, достаточный запас памяти подтверждён пиковым workload и 24h soak |
| Доступ | Два tenant, несколько ролей, node ACK, redirect и forged forwarded headers покрыты тестами |
| Восстановление | Успешно выполнен restore drill со схемой БД, ключами и проверкой бандла |
| Контракт | OpenAPI обновлён до реализации; docs соответствуют реальному startup и режимам работы |
| Обратная совместимость | Новый CP обслуживает поддерживаемые старые speakers; смешанный парк проходит version matrix; отсутствие новых ACK/метрик не ломает доставку и не создаёт ложный verified |
Для Go-изменений — `gofmt`, `go vet ./...`, `scripts/lint-go.ps1`, `go test ./... -race -count=1`; для API — OpenAPI lint/codegen; для BIRD — scenarios и `bird -p`; для UI — обязательные typecheck/lint/build и релевантные тесты. Фактически выполненные проверки приведены в [backend-baseline.md](backend-baseline.md); наличие команды в плане не означает её успешный прогон.
## 9. Решения, которые стоит отложить до измерений
- Переход на микросервисы, Kafka/NATS/Redis как обязательную инфраструктуру: текущая PostgreSQL-очередь сначала должна получить корректные гарантии.
- Выделенный time-series кластер: сначала посчитать ряды, частоту и retention; для малого профиля PostgreSQL events + Prometheus достаточно как стартовая архитектура.
- Переписывание Go backend или замена PostgreSQL: аудит не выявил оснований для такого масштаба изменений.
- Автоматический rollback по одному неуспешному poll: сначала нужны quality/coverage, hysteresis и проверенный recovery.
- RPKI enforcement и сбор NetFlow: отдельный продуктовый этап с сетевым тестированием и самостоятельным ресурсным бюджетом.
## 10. Навигация по источникам проекта
| Область | Основные файлы |
|---|---|
| Контракт API / доступ | [openapi.yaml](openapi.yaml), [access.md](access.md), [permissions.go](../internal/httpapi/permissions.go) |
| Queue / persistence | [job.go](../internal/jobs/job.go), [durable.go](../internal/jobs/durable.go), [job_audit.go](../internal/repository/job_audit.go), [bootstrap.go](../internal/httpapi/bootstrap.go) |
| Deploy / recovery | [worker.go](../internal/jobs/worker.go), [apply.go](../internal/birddeploy/apply.go), [nodedispatch](../internal/nodedispatch/) |
| BGP live | [peers_live.go](../internal/httpapi/peers_live.go), [speakers_live.go](../internal/httpapi/speakers_live.go), [bgp_sessions.go](../internal/birdfmt/bgp_sessions.go) |
| Метрики / PostgreSQL | [metrics.go](../internal/observability/metrics.go), [pgmonitor](../internal/pgmonitor/), [alerts.yml](../deploy/prometheus/alerts.yml) |
| Источники / lookup | [pipeline](../internal/pipeline/), [lookup.go](../internal/lookup/lookup.go), [httpclient.go](../internal/httpclient/httpclient.go) |
| Docker / CI | [docker-bake.hcl](../deploy/docker/docker-bake.hcl), [Compose](../deploy/compose/docker-compose.yaml), [microvps override](../deploy/compose/docker-compose.microvps-full.yaml), [quality.yaml](../.gitea/workflows/quality.yaml) |
Номера строк в тексте относятся к исследованному checkout; при реализации ориентироваться также на имена функций. Приоритеты — инженерная оценка по последствиям возможного отказа, а не утверждение о произошедших production-инцидентах.