diff --git a/.cursor/rules/engineering.mdc b/.cursor/rules/engineering.mdc new file mode 100644 index 0000000..c797580 --- /dev/null +++ b/.cursor/rules/engineering.mdc @@ -0,0 +1,241 @@ +--- +description: Инженерные правила EvoBGP — Go, API, migrations, общие стандарты +alwaysApply: true +--- + +# Engineering Guidelines — EvoBGP + +Специализированные правила: **Web UI** → `.cursor/rules/web-shadcn.mdc`; **BIRD2 / BGP / IP** → `.cursor/rules/networking-bird.mdc`. + +Карта репозитория: `AGENTS.md`, `docs/architecture.md`. HTTP-контракт: `docs/openapi.yaml`. + +--- + +## Architecture + +**ARCH-01** | MUST | Новая persistence-логика — метод `store.Backend` + реализации в `repository` и `store.Memory`; SQL не в `httpapi`. +*Rationale:* единая абстракция данных. +*Проверка:* grep SQL в `internal/httpapi` — отсутствие; review. + +**ARCH-02** | MUST | HTTP-маршруты только в `internal/httpapi`; регистрация через `http.ServeMux` с паттернами `METHOD /v1/...`. +*Rationale:* один слой REST. +*Проверка:* маршруты только в `routes.go`, `routes_crud.go`. + +**ARCH-03** | MUST | Долгие операции (refresh, apply, rollback) — `jobs.Registry`; ответ `202` + `job_id` где задано OpenAPI. +*Rationale:* не блокировать HTTP worker. +*Проверка:* OpenAPI + handlers. + +**ARCH-04** | MUST | Кросс-процессные воркеры — HTTP к API или общая БД; не shared memory (кроме `evobgp-all`). +*Rationale:* `jobs.Registry` in-process only. +*Проверка:* `docs/architecture.md`; review. + +**ARCH-05** | MUST | `birdfmt` не импортирует `httpapi` / `store`. +*Rationale:* направление зависимостей вниз. +*Проверка:* `go list -deps` / review imports. + +**ARCH-06** | MUST | Точки входа `cmd/*` — тонкий `main`: config, wiring, signal/shutdown; без бизнес-логики. +*Rationale:* тестируемость `internal/`. +*Проверка:* review `main.go`. + +**ARCH-07** | MUST | Инициализация store + jobs для API и воркеров — `httpapi.BootstrapWorkers`. +*Rationale:* единый wiring. +*Проверка:* `bootstrap.go`. + +**ARCH-08** | MUST | Ingest+render префиксов — `internal/pipeline`; генерация BIRD-текста — `internal/birdfmt`. +*Rationale:* разделение data plane / control plane. +*Проверка:* review пакетов. + +**ARCH-09** | NEVER | Прямой доступ handler'ов к `pgxpool` для CRUD; только `store.Backend`. +*Rationale:* абстракция бэкенда. +*Проверка:* review `httpapi`. + +**ARCH-10** | MUST | Подпись бандлов — `internal/bundle` + `internal/signing`; не дублировать crypto в handlers. +*Rationale:* единая криптография артефактов. +*Проверка:* review. + +--- + +## Code Style + +**STYLE-01** | MUST | Go-код после `gofmt`; перед PR — `go vet ./...`. +*Rationale:* единый стиль. +*Проверка:* CI job `go`. + +**STYLE-02** | MUST | Экспортируемые типы/функции публичных пакетов — godoc-комментарий. +*Rationale:* навигация по API пакетов. +*Проверка:* review. + +**STYLE-03** | NEVER | `panic` в `internal/*` вне `init` и тестов. +*Rationale:* предсказуемые ошибки. +*Проверка:* grep `panic(`. + +**STYLE-04** | MUST | Ошибки пакетов с префиксом (`birdfmt:`, `db:`, `httpapi:`) и `%w` при оборачивании. +*Rationale:* трассировка. +*Проверка:* review. + +**STYLE-05** | MUST | HTTP-ошибки — `writeProblem` / `writeJSON` (`application/problem+json` для 4xx/5xx). +*Rationale:* RFC 9457, OpenAPI. +*Проверка:* `problem.go`. + +**STYLE-06** | MUST | JSON полей HTTP DTO согласованы с `docs/openapi.yaml`. +*Rationale:* контракт API. +*Проверка:* OpenAPI diff + review. + +--- + +## Dependency Management + +**DEP-01** | MUST | Go-зависимости — через `go get` / `go.mod`; версия Go как в `go.mod` и CI (1.24). +*Проверка:* `go.mod`, `.gitea/workflows/ci.yaml`. + +**DEP-02** | NEVER | Vendor-копирование без явного решения в репозитории. +*Проверка:* review. + +**DEP-03** | MUST | Миграции схемы — пары `.up.sql`/`.down.sql` для **postgres** и **sqlite**, синхронная нумерация. +*Проверка:* `migrations/postgres/`, `migrations/sqlite/`. + +**DEP-04** | MUST | Web UI-библиотеки — только экосистема shadcn-svelte/bits-ui (см. `web-shadcn.mdc`). +*Проверка:* `web/package.json` review. + +--- + +## Error Handling + +**ERR-01** | MUST | HTTP 5xx — без сырого `err.Error()` клиенту; detail через `writeProblem`. +*Проверка:* review handlers. + +**ERR-02** | MUST | Публичные функции I/O — `(T, error)`; `errors.Is`/`errors.As` для sentinel. +*Проверка:* review. + +**ERR-03** | MUST | `context.Context` — первый аргумент для I/O; таймауты на внешние HTTP (CDN, DoH, RIPEstat). +*Проверка:* `pipeline`, `bootstrap.go`. + +--- + +## Testing + +**TEST-01** | MUST | Перед PR: `go test ./... -race -count=1`. +*Проверка:* CI job `go`. + +**TEST-02** | MUST | Табличные тесты — эталон для `birdfmt`, `pipeline`, `bundle`. +*Проверка:* `*_test.go`. + +**TEST-03** | MUST | Новые BIRD-сценарии в `internal/birdfmt/testdata/scenarios/*/bird.conf` + `bird -p`. +*Проверка:* CI job `bird2`. + +**TEST-04** | MUST | Изменения `web/` — локально `npm run check` и `npm run lint` (CI web пока не в scope). +*Проверка:* локальные команды. + +**TEST-05** | MUST | Изменения OpenAPI — `npx @redocly/cli lint docs/openapi.yaml`. +*Проверка:* CI job `openapi`. + +--- + +## Documentation + +**DOC-01** | MUST | Пользовательская документация в `docs/` — на русском. +*Проверка:* review. + +**DOC-02** | MUST | Изменение HTTP API: сначала `docs/openapi.yaml`, затем handlers/store, затем `docs/api.md` при необходимости. +*Проверка:* PR diff order. + +**DOC-03** | MUST | Расхождение spec/код — явно в `docs/access.md` или description операции OpenAPI. +*Проверка:* review (напр. `X-Tenant-Id`). + +**DOC-04** | NEVER | Дублировать длинные фрагменты OpenAPI в комментариях; ссылка на путь/operationId. +*Проверка:* review. + +--- + +## Naming Conventions + +**NAME-01** | MUST | Бинарники: `cmd/evobgp-/main.go`. +*Проверка:* `cmd/`. + +**NAME-02** | MUST | Env-переменные: префикс `EVOBGP_`. +*Проверка:* `internal/config`, `docs/access.md`. + +**NAME-03** | MUST | Job kinds — константы в `internal/jobs/worker.go` (`module_refresh`, …). +*Проверка:* grep `Kind`. + +**NAME-04** | MUST | ID сущностей — UUID-строки; tenant только из API-ключа (auth context), не из недокументированных заголовков. +*Проверка:* `auth.go`, `docs/access.md`. + +--- + +## Performance + +**PERF-01** | MUST | Списки API — пагинация `cursor` + `limit`; не unbounded выборки в handlers. +*Проверка:* OpenAPI + store methods. + +**PERF-02** | MUST | CDN HTTP — переиспользуемый `http.Client` с таймаутом (45s в bootstrap). +*Проверка:* `bootstrap.go`. + +**PERF-03** | MUST | Новые метрики — `internal/observability`, экспорт `/metrics`. +*Проверка:* review. + +--- + +## Security + +**SEC-01** | NEVER | Секреты, API-ключи, токены в Git. +*Проверка:* review; `docs/access.md`. + +**SEC-02** | NEVER | `EVOBGP_DEV_INSECURE=1` в production. +*Проверка:* ops review. + +**SEC-03** | MUST | Роль `node` — только node API; CRUD — `403` для `node`. +*Проверка:* `auth.go`, handlers. + +**SEC-04** | MUST | apply/rollback/settings — operator (или выше по `roleLevel`). +*Проверка:* review handlers. + +**SEC-05** | MUST | Бандл на ноде — `verify-bundle` перед `apply-bundle`. +*Проверка:* `docs/access.md`. + +**SEC-06** | MUST | CORS — явный whitelist `EVOBGP_CORS_ORIGINS`. +*Проверка:* `cors.go`. + +--- + +## Documentation Sync Rules + +| Область | Источник | +|---------|----------| +| Go / net/http | https://go.dev/doc/ | +| pgx v5 | https://pkg.go.dev/github.com/jackc/pgx/v5 | +| OpenAPI / problem+json | `docs/openapi.yaml`, RFC 9457 | +| Svelte / Kit | https://svelte.dev/docs , https://kit.svelte.dev/docs | +| shadcn-svelte | https://shadcn-svelte.com/docs | +| BIRD 2 | https://bird.network.cz/?get_doc | +| Prometheus Go | https://pkg.go.dev/github.com/prometheus/client_golang | + +**DOC-SYNC-01** | MUST | Новый API библиотеки — сверка версии в `go.mod`/`package.json` с официальной документацией. +**DOC-SYNC-02** | NEVER | Устаревшие примеры (Svelte 4 `export let`, deprecated pgx). +**DOC-SYNC-03** | MUST | Конфликт docs: **OpenAPI (HTTP)** → **код** → обзорные `docs/`; `.cursor/plans/` не контракт. +**DOC-SYNC-04** | MUST | Сомнения по Svelte — Svelte MCP / `npm run check`. +**DOC-SYNC-05** | MUST | BIRD — официальная документация BIRD2 + `networking-bird.mdc` + `go test ./internal/birdfmt/...`. + +Приоритет при сомнениях — **официальные источники**, не блоги и не «память модели». + +--- + +## Enforcement Strategy + +**CI (Gitea):** OpenAPI lint; `go vet`, `go test -race`, build `cmd/*`; `bird -p` на scenarios; Docker bake на push. + +**Локально перед PR:** + +```powershell +go vet ./... +go test ./... -race -count=1 +npx @redocly/cli lint docs/openapi.yaml +# web: cd web; npm run check; npm run lint +# birdfmt: go test ./internal/birdfmt/... -count=1 +``` + +**Рекомендуется (не внедрено):** CI job `web`; `.golangci.yml`; pre-commit gofmt/prettier. + +**Только code review:** слои SQL; роли; idempotency; OpenAPI bodies; secrets в compose. + +**Известные ограничения:** `X-Tenant-Id` в OpenAPI не реализован в handlers; `jobs.Registry` не shared между процессами API и отдельными воркерами без HTTP. diff --git a/.cursor/rules/networking-bird.mdc b/.cursor/rules/networking-bird.mdc new file mode 100644 index 0000000..3426e7d --- /dev/null +++ b/.cursor/rules/networking-bird.mdc @@ -0,0 +1,219 @@ +--- +description: EvoBGP — BIRD2, BGP policy, IP/CIDR, маршрутизация, валидация конфигов +globs: + - internal/birdfmt/** + - internal/birddeploy/** + - internal/pipeline/** + - deploy/bird/** + - "**/testdata/scenarios/**" +alwaysApply: false +--- + +# Networking & Routing Rules + +Общие правила: `.cursor/rules/engineering.mdc`. Пакет BIRD: `internal/birdfmt/doc.go`. + +## Single source of truth + +| Домен | SSOT | +|-------|------| +| Префиксы для анонса | Модули tenant → materialized snapshot → ревизия (`store`, `pipeline`, `CreateRenderRevision`) | +| Текст BIRD-фрагментов | `internal/birdfmt` — только генератор `evobgp_*.conf` | +| Каркас `bird.conf` | Operator skeleton + `include` (`StandardIncludeFragments`) | +| Параметры BIRD tenant | Global settings: `bird_router_id`, `bird_local_asn`, … (`docs/manual.md`) | +| BGP peers | `BGPPeer` + `ParsePeerNeighbor` | + +**BIRD2 docs:** https://bird.network.cz/?get_doc + +--- + +## A. BIRD2 Configuration Rules + +**BIRD-01** | MUST | Имена фрагментов — константы `Fragment*` (`evobgp_prefixes_v4.conf`, …); include `bird.d/`. +*Rationale:* совместимость deploy. +*Проверка:* `layout.go`; `go test ./internal/birdfmt/...` + +**BIRD-02** | MUST | Порядок include: filters v4/v6 → BGP template → prefixes v4/v6 → peers (`StandardIncludeFragments`). +*Rationale:* фильтры до ссылок. +*Проверка:* scenario `standard_layout`; `bird -p` + +**BIRD-03** | MUST | `bird.conf` (router id, device, direct, includes) отдельно от `bird.d/evobgp_*` (политика, префиксы). +*Rationale:* атомарная подмена фрагментов. +*Проверка:* review layout. + +**BIRD-04** | MUST | Новая логика фильтрации — функция в `birdfmt` + unit-тест; NEVER дублировать filter-блок в два `.conf` вручную. +*Rationale:* DRY v4/v6. +*Проверка:* `filter_test.go`; review. + +**BIRD-05** | MUST | Filter: префикс `evobgp_`, суффикс `_v4`/`_v6`; templates `bgp_template` / `bgp_template_v6`. +*Проверка:* `bgp.go`, `layout.go`. + +**BIRD-06** | MUST | `router id` — валидный IPv4 dotted-quad (`RenderMainBirdConf`). +*Проверка:* `birdfmt` error; `bird -p` + +**BIRD-07** | MUST | Communities — `RouteCommunityAttrs`; синтаксис `((a,b))` в `add()`, не `add(65000,1)`. +*Проверка:* `community_bird_test.go` + +**BIRD-08** | SHOULD | Комментарии `#` в preamble; в фрагментах — revision/module id где уместно. +*Проверка:* review. + +**BIRD-09** | NEVER | Ручное редактирование `evobgp_*.conf` на ноде без ревизии/бандла в control plane. +*Rationale:* drift. +*Проверка:* `evobgp-deploy` logs. + +**BIRD-10** | MUST | Новый scenario: `testdata/scenarios//bird.conf` (+ `bird.d/`) проходит `bird -p`. +*Проверка:* CI `bird2` + +**BIRD-11** | MUST | Только BIRD **2.x**, не 1.6. +*Проверка:* `bird --version` + +**BIRD-12** | MUST | Apply: staging → `bird -p` → atomic swap → `birdc configure`; NEVER configure без parse check. +*Проверка:* `birdctl.go`; `birddeploy` review. + +--- + +## B. Routing Policy Rules + +**RTE-01** | MUST | BGP template: `import none` на каждый AFI, если задача явно не добавляет import policy. +*Rationale:* deny-by-default. +*Проверка:* `bgp.go` + +**RTE-02** | MUST | Export filter завершается `reject;`. +*Проверка:* `filter.go`; `filter_export` scenario. + +**RTE-03** | MUST | Анонс только префиксов materialized revision; NEVER static `route` вне pipeline/birdfmt без operator approval. +*Проверка:* store snapshot; review. + +**RTE-04** | NEVER | Анонс `0.0.0.0/0`, `::/0`, RFC1918/ULA, loopback, link-local, multicast, reserved без documented exception. +*Проверка:* ingest validation; review. + +**RTE-05** | NEVER | Префиксы чужих ASN без авторизации (IRR/RPKI — ops review). +*Проверка:* ops review. + +**RTE-06** | MUST | Отдельные export filters IPv4 и IPv6. +*Проверка:* `RenderExportFilterIPv4/IPv6` + +**RTE-07** | SHOULD | AS_PATH match — ASN из tenant modules; дедуп `filterUniqueASNs`. +*Проверка:* unit tests. + +**RTE-08** | MUST | Peer import/export overrides — расширение `birdfmt`, не ad-hoc в operator `bird.conf`. +*Проверка:* review. + +**RTE-09** | SHOULD | hold 90s / keepalive 30s — менять только с обоснованием в settings/docs. +*Проверка:* review. + +**RTE-10** | MUST | После apply — post-check `birdc`/метрики (`mergeBirdPostApplyMeta`, `EVOBGP_BIRDC_SOCKET`). +*Проверка:* jobs meta; observability poller. + +--- + +## C. IP Addressing Rules + +**IP-01** | MUST | CIDR в Go — `netip.Prefix`; строка — `Masked().String()`. +*Проверка:* `pipeline/parse.go` + +**IP-02** | MUST | IPv4/IPv6 — отдельные fragments/filters. +*Проверка:* `layout.go` + +**IP-03** | MUST | BGP neighbor — `ParsePeerNeighbor`; `/32`/`/128` нормализуются в host addr. +*Проверка:* `peer_neighbor_test.go` + +**IP-04** | NEVER | Hardcoded production CIDR вне testdata/fixtures. +*Проверка:* grep; review. + +**IP-05** | MUST | Fixtures/docs — TEST-NET (RFC 5737, 3849): `203.0.113.0/24`, `198.51.100.0/24`, `2001:db8::/32`. +*Проверка:* scenarios. + +**IP-06** | MUST | Domain→IP: host routes `/32`, `/128` via `ipToHostPrefix`; invalid отбрасывается. +*Проверка:* `collect_parallel.go` + +**IP-07** | MUST | IP range entries — валидный CIDR на границе API/store. +*Проверка:* httpapi; review. + +**IP-08** | SHOULD | Множество префиксов — без дубликатов; агрегация — policy decision. +*Проверка:* snapshot dedup. + +**IP-09** | MUST | `router id`, `local as` — из tenant settings, не hardcode в callers `birdfmt`. +*Проверка:* `docs/manual.md` + +**IP-10** | NEVER | Reserved blocks (`0.0.0.0/8`, `127.0.0.0/8`, `169.254.0.0/16`, `224.0.0.0/4`, IPv6 analogs) в materialized prefixes. +*Проверка:* review; future validator. + +--- + +## D. Internet Compliance + +**COMP-01** | SHOULD | Policy changes — сверка RFC 4271, 4760, 7454 (обзор). +*Проверка:* review. + +**COMP-02** | MUST | No excessive deaggregation без need. +*Проверка:* review prefix list. + +**COMP-03** | MUST | Standard community 0..65535; large — schema store. +*Проверка:* `community_bird_test.go` + +**COMP-04** | NEVER | AS_PATH/MED manipulation без product spec. +*Проверка:* review output. + +**COMP-05** | SHOULD | Revision/job meta: source, counts, samples (`revisionLogEntry`). +*Проверка:* jobs. + +**COMP-06** | MUST | Node: `evobgp-node verify-bundle` перед apply. +*Проверка:* `docs/access.md` + +**COMP-07** | SHOULD | `bird_local_asn` = `local as` в templates/peers. +*Проверка:* settings + birdfmt. + +**COMP-08** | NEVER | Default route leak без named `default-originate` policy. +*Проверка:* export filter review. + +--- + +## Validation & Observability + +**VAL-01** | MUST | Перед деплоем: `bird -c -p` (main + includes). +*Проверка:* CI `bird2`; `ParseCheck` + +**VAL-02** | MUST | Preview ревизии read-only; apply — jobs/apply/bundle only. +*Проверка:* OpenAPI + httpapi. + +**VAL-03** | MUST | Изменения birdfmt — `go test ./internal/birdfmt/...`. +*Проверка:* CI `go` + +**VAL-04** | SHOULD | Dry-run apply где API поддерживает `dry_run`. +*Проверка:* routes.go. + +**VAL-05** | MUST | `/metrics` Prometheus; `EVOBGP_BIRDC_SOCKET` — BGP session poller. +*Проверка:* `observability` + +**VAL-06** | MUST | Логировать job_id, revision_id, speaker_id на apply/refresh/rollback. +*Проверка:* jobs API. + +**VAL-07** | SHOULD | После apply — `birdc show protocols` / parsers в `birdfmt`. +*Проверка:* `protocols.go` + +**VAL-08** | MUST | Мониторить drift: `last_applied_revision_id` vs published (`evobgp-deploy`). +*Проверка:* deploy worker. + +--- + +## Documentation Sync + +**DOC-SYNC-05** | MUST | BIRD — https://bird.network.cz/?get_doc +**DOC-SYNC-08** | MUST | BGP policy — RFC 4271, 4760, 7454 + BIRD docs + `birdfmt` +**DOC-SYNC-09** | MUST | CIDR — https://pkg.go.dev/net/netip ; примеры — RFC 5737, 3849 + +--- + +## Enforcement + +```powershell +go test ./internal/birdfmt/... ./internal/pipeline/... -count=1 +bird -c internal/birdfmt/testdata/scenarios//bird.conf -p +``` + +**PR checklist `birdfmt/**`, `pipeline/**`, `deploy/bird/**`:** +- [ ] filter + unit-test + `bird -p` +- [ ] нет дубли filter logic v4/v6 без общей Go-функции +- [ ] префиксы только store/revision path +- [ ] `import none` на templates сохранён diff --git a/.cursor/rules/web-shadcn.mdc b/.cursor/rules/web-shadcn.mdc new file mode 100644 index 0000000..bdab97c --- /dev/null +++ b/.cursor/rules/web-shadcn.mdc @@ -0,0 +1,109 @@ +--- +description: EvoBGP WebUI — shadcn-svelte, Svelte 5, слои ui/core|patterns|app +globs: + - web/** +alwaysApply: false +--- + +# Web UI — shadcn-svelte + +**Источник правды:** https://shadcn-svelte.com/docs (не React shadcn/ui, не Legacy Docs). + +Общие правила Go/API: `.cursor/rules/engineering.mdc`. Локальная карта: `web/README.md`. + +## Слои UI + +| Слой | Путь | Назначение | +|------|------|------------| +| Примитивы | `src/lib/ui/core/` | shadcn-svelte (только CLI `add`) | +| Паттерны | `src/lib/ui/patterns/` | FormField, AppDataTable, ConfirmDialog, EmptyState | +| App chrome | `src/lib/ui/app/` | Layout, PageHeader, `notify` | +| Legacy | `src/lib/components/ui/` | Re-export; **не добавлять новые файлы** | + +Тема: `src/routes/layout.css`, `src/lib/ui/app/tokens.md`. CLI из `web/`: `npx shadcn-svelte@latest add -y -o`. + +--- + +## Правила + +**WEB-01** | MUST | Перед новым UI — проверить https://shadcn-svelte.com/docs/components; использовать компонент, не HTML+CSS с нуля. +*Rationale:* Open Code + единый дизайн. +*Проверка:* review; нет голых `