From 6125af4bab6b58f84d9db03a8018c766840871e4 Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Sat, 4 Jul 2026 12:58:59 +0700 Subject: [PATCH] =?UTF-8?q?docs:=20=D0=B4=D0=B5=D1=82=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B4=D0=B8=D0=B7=D0=B0=D0=B9?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=A4=D0=B0=D0=B7=D1=8B=203=20(=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D0=B5,=20=D1=83?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F,?= =?UTF-8?q?=20=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=BA=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-07-03-dns-autoresolver-design.md | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/docs/superpowers/specs/2026-07-03-dns-autoresolver-design.md b/docs/superpowers/specs/2026-07-03-dns-autoresolver-design.md index b19746c..f52f31f 100644 --- a/docs/superpowers/specs/2026-07-03-dns-autoresolver-design.md +++ b/docs/superpowers/specs/2026-07-03-dns-autoresolver-design.md @@ -385,3 +385,102 @@ internal/api/middleware.go — RequireAuth (session→userID в контек ### Разбивка Один план `phase2-auth`. + +## Фаза 3 — детализация (расписание · уведомления · метрики) + +Периодические проверки доменов по расписанию, уведомления о смене статуса, Prometheus-метрики. +Планировщик **только проверяет и уведомляет** — apply остаётся ручным (prune-guard из 1A/1B). + +### Решения + +- **Планировщик — встроенный in-process**: goroutine-тикер в `cmd/server`, интервал из БД, `last_run_at` + персистентен (переживает рестарт). Без внешних зависимостей. +- **Гранулярность — на проект**: единый интервал проверки всех доменов проекта. +- **Каналы уведомлений — Telegram + Webhook** (чистый `net/http`, без внешних lib). Telegram — + Bot API `sendMessage`; Webhook — HTTP POST JSON. +- **Уведомление при СМЕНЕ статуса домена** (in_sync ↔ drift ↔ error), не спам каждый тик — + `domains.last_check_status`. +- **Метрики — Prometheus** (`client_golang`, custom registry), `/metrics` **публичный** (scrape), + без секретов. +- **bot_token Telegram шифруется** (тот же `crypto.Cipher`, что provider-секреты); в ответах не отдаётся. + +### БД (миграция 0004) + +``` +CREATE TABLE schedules ( + id uuid PRIMARY KEY, + project_id uuid NOT NULL UNIQUE REFERENCES projects(id) ON DELETE CASCADE, + interval_seconds int NOT NULL DEFAULT 3600, + enabled boolean NOT NULL DEFAULT false, + last_run_at timestamptz, + created_at timestamptz NOT NULL DEFAULT now() +); +CREATE TABLE notification_channels ( + id uuid PRIMARY KEY, + project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + type text NOT NULL, -- telegram | webhook + config jsonb NOT NULL, -- telegram: {chat_id}; webhook: {url} + secret_enc text NOT NULL DEFAULT '', -- telegram: bot_token (шифр); webhook: пусто/подпись + enabled boolean NOT NULL DEFAULT true, + created_at timestamptz NOT NULL DEFAULT now() +); +ALTER TABLE domains ADD COLUMN last_check_status text NOT NULL DEFAULT 'unknown'; -- unknown|in_sync|drift|error +``` + +### Backend + +``` +internal/notify + ├─ notify.go — интерфейс Notifier{Send(ctx, Event) error}, тип Event{Project,Domain,Status,Summary,At} + ├─ telegram.go — TelegramNotifier (Bot API sendMessage, net/http) + ├─ webhook.go — WebhookNotifier (HTTP POST JSON) + └─ dispatch.go — Dispatcher: по каналам проекта шлёт Event через нужный Notifier (расшифровка secret) +internal/scheduler + └─ scheduler.go — Scheduler{Run(ctx)}: тикер → due-проекты (enabled && now-last_run>=interval) + → для каждого домена service.Check → сохранить check_run + обновить last_check_status + → при смене статуса → Dispatcher.Send; обновить last_run_at +internal/metrics + └─ metrics.go — Metrics{registry, counters/histogram/gauge}; Handler() для /metrics +``` + +- **Метрики** (custom `prometheus.Registry`, `promauto`): `dns_ar_checks_total{status}` (counter), + `dns_ar_check_duration_seconds` (histogram), `dns_ar_drift_domains` (gauge — текущее число в дрейфе), + `dns_ar_notifications_total{channel,status}` (counter). Инструментируются scheduler/service/notify. + `/metrics` — `promhttp.HandlerFor(reg, ...)`, публичный. +- **Планировщик**: последовательная обработка проектов (без гонок по проекту), graceful shutdown по + `context`. Ошибка проверки домена → статус `error` + метрика + уведомление. + +### REST API (`/api/v1/projects/{pid}`, под RequireAuth+RequireProjectAccess) + +| Метод/путь | Назначение | +|---|---| +| `GET/PUT /schedule` | получить/задать расписание проекта (interval_seconds, enabled) | +| `POST/GET/DELETE /channels` | CRUD каналов уведомлений (secret на вход, не на выход) | +| `POST /channels/{cid}/test` | тест-отправка уведомления в канал | +| `GET /domains/{did}/history` | история проверок (check_runs) домена | + +### Frontend + +- **SchedulePage** (или блок в проекте): интервал + вкл/выкл. +- **ChannelsPage**: CRUD каналов (Telegram: chat_id + bot_token; Webhook: url), тест-отправка. Секрет только на вход. +- **История проверок** домена (список check_runs с результатом/временем). +- **drift-badge** в списке доменов: `last_check_status` (in_sync=emerald, drift=amber, error=rose, unknown=muted). + +### Инварианты + +- Планировщик не применяет изменения (только check + notify). +- `bot_token`/секреты каналов не в ответах/логах; `/metrics` без секретов и PII (только агрегаты по status/channel). +- Уведомления идемпотентны по смене статуса (нет спама при неизменном дрейфе). +- Всё scoped по проекту (мультитенантность/IDOR из Фазы 2 сохраняется). + +### Тестирование Фазы 3 + +- `notify` — Telegram/Webhook против `httptest` (корректный запрос, обработка ошибки); Dispatcher (выбор канала, расшифровка). +- `scheduler` — мок store/service/notifier, управляемый «тик»: due-выбор, смена статуса → уведомление, идемпотентность (нет уведомления при неизменном статусе), ошибка → error-статус. +- `metrics` — `testutil` (счётчики/гистограмма инкрементируются), `/metrics` отдаёт. +- API — httptest + store (CRUD schedule/channels, secret не в ответе, история). +- Frontend — Vitest (клиент/хуки, drift-badge, формы каналов, тест-отправка). + +### Разбивка + +Один план `phase3-scheduler-notify-metrics`.