From 13e3d21ce2571021774e1f3fca07402deb3bfaa0 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Fri, 31 Jul 2026 12:19:02 +0700 Subject: [PATCH] feat(web): add OpenAPI TypeScript codegen and CI freshness check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Генерируем api.gen.ts из docs/openapi.yaml (openapi-typescript), добавляем scripts/check-openapi-gen.sh в CI. Закрываем решение по X-Tenant-Id как unimplemented; обновляем docs/README под React/ReUI и apps/web. Co-authored-by: Cursor --- .gitea/workflows/ci.yaml | 12 +- apps/web/package.json | 11 +- apps/web/src/types/api.gen.ts | 6580 +++++++++++++++++++++++++++++++++ apps/web/src/types/api.ts | 10 + docs/README.md | 6 +- docs/access.md | 6 +- docs/openapi.yaml | 3 +- pnpm-lock.yaml | 172 +- scripts/check-openapi-gen.sh | 14 + 9 files changed, 6782 insertions(+), 32 deletions(-) create mode 100644 apps/web/src/types/api.gen.ts create mode 100644 scripts/check-openapi-gen.sh diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 913473a..ae00a47 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -150,15 +150,23 @@ jobs: # --------------------------------------------------------------------------- openapi: needs: [changes] - if: needs.changes.outputs.openapi == 'true' + if: needs.changes.outputs.openapi == 'true' || needs.changes.outputs.web == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "22" + - name: Enable pnpm via corepack + run: corepack enable - name: Lint OpenAPI (Redocly) run: npx --yes @redocly/cli@1 lint docs/openapi.yaml + - name: Check OpenAPI→TS codegen is fresh + run: | + set -euxo pipefail + pnpm install --frozen-lockfile + chmod +x scripts/check-openapi-gen.sh + sh scripts/check-openapi-gen.sh # --------------------------------------------------------------------------- web: diff --git a/apps/web/package.json b/apps/web/package.json index bd7f93e..285788a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -9,7 +9,9 @@ "preview": "vite preview", "typecheck": "tsr generate && tsc --noEmit", "lint": "eslint .", - "test": "vitest run" + "test": "vitest run", + "openapi:gen": "openapi-typescript ../../docs/openapi.yaml -o src/types/api.gen.ts", + "openapi:check": "openapi-typescript ../../docs/openapi.yaml -o src/types/api.gen.check.ts && diff -q src/types/api.gen.ts src/types/api.gen.check.ts && rm -f src/types/api.gen.check.ts" }, "dependencies": { "@base-ui/react": "^1.0.0", @@ -40,22 +42,23 @@ "zod": "^3.25.0" }, "devDependencies": { + "@eslint/js": "^9.0.0", "@tailwindcss/vite": "^4.1.0", - "@tanstack/router-plugin": "^1.130.0", "@tanstack/router-cli": "^1.130.0", + "@tanstack/router-plugin": "^1.130.0", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", "eslint": "^9.0.0", - "@eslint/js": "^9.0.0", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.0", "globals": "^15.0.0", - "typescript-eslint": "^8.0.0", "happy-dom": "^18.0.0", + "openapi-typescript": "^7.13.0", "tailwindcss": "^4.1.0", "tw-animate-css": "^1.0.0", "typescript": "^5.9.2", + "typescript-eslint": "^8.0.0", "vite": "^7.3.1", "vitest": "^3.0.0" } diff --git a/apps/web/src/types/api.gen.ts b/apps/web/src/types/api.gen.ts new file mode 100644 index 0000000..7f672fb --- /dev/null +++ b/apps/web/src/types/api.gen.ts @@ -0,0 +1,6580 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/v1/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Liveness + * @description Проверка, что процесс API жив. Не обязана проверять БД или брокер. + */ + get: operations["getHealth"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/ready": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Readiness + * @description Готовность к трафику (БД, брокер сообщений при reference-архитектуре и т.д.). + */ + get: operations["getReady"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/version": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Версия сборки (корневой путь) + * @description Аналог `GET /v1/version`. Публичный маршрут без аутентификации. + * Semver в поле `version` задаётся при сборке Docker-образов из git-тега. + */ + get: operations["getVersionRoot"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/version": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Версия сборки + * @description Semver control-plane и метаданные сборки (`git_sha`, `build_time`). + * Дублирует `GET /version`. + */ + get: operations["getVersion"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/modules": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Список модулей + * @description Модули tenant с опциональными фильтрами по типу и флагу `enabled` (фильтры применяются сервером). + */ + get: operations["listModules"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/lookup": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Проверка IP или домена в списках + * @description Быстрая membership-проверка по tenant: + * + * - **IP** — слой `entry` (`IP_RANGES`, `CIDR.Contains`) и слой `snapshot` + * (все module prefix snapshots, `Prefix.Contains`); + * - **Domain** — слой `entry` (нормализованный FQDN в `DOMAINS`) и слой `snapshot` + * (префиксы `source=domain` у matched DOMAINS-модулей, если snapshot есть); + * затем **live DNS resolve** (A/AAAA через системный резолвер) и проверка + * каждого полученного IP так же, как для IP-запроса (ranges + все snapshots). + * + * Community на матче: `entry.community_id || module.default_community_id` (entry) + * или `PrefixRow.community_id` (snapshot), с join к справочнику communities. + * + * Поля `resolved_ips` / `resolved_ip` заполняются только для domain-запросов + * (после успешного DNS). Ошибка DNS не даёт 5xx: FQDN-слой всё равно возвращается. + */ + get: operations["lookupMembership"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/router-lists/catalog": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Агрегированный каталог для router-lists-ui + * @description Возвращает в одном ответе: + * - модули типов `DOMAINS`, `IP_RANGES`, `AS_PREFIXES`; + * - entries по каждому модулю; + * - справочник community (`id`, `community`, `title`). + */ + get: operations["getRouterListsCatalog"]; + put?: never; + /** + * Создать модуль + * @description Создаёт экземпляр модуля. Поле `type` задаёт вид (`AS_PREFIXES`, `CDN_CIDRS`, `DOMAINS`, `IP_RANGES`). + * `module_id` в других путях - идентификатор экземпляра, не имя типа. + */ + post: operations["createModule"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/modules/{module_id}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + /** Получить модуль */ + get: operations["getModule"]; + put?: never; + post?: never; + /** + * Удалить модуль + * @description Мягкое удаление или перевод в `enabled=false` - конкретное поведение задаётся реализацией. + */ + delete: operations["deleteModule"]; + options?: never; + head?: never; + /** + * Обновить модуль + * @description Частичное обновление (расписание, DoH, приоритет, `enabled` и т.д.). + */ + patch: operations["patchModule"]; + trace?: never; + }; + "/v1/modules/{module_id}/cdn-sources": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + /** Список CDN-источников */ + get: operations["listCdnSources"]; + put?: never; + /** + * Добавить CDN-источник + * @description URL, `source_kind`, опционально `community_id`. + */ + post: operations["createCdnSource"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/modules/{module_id}/cdn-sources/{source_id}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + source_id: components["parameters"]["SourceId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Удалить CDN-источник */ + delete: operations["deleteCdnSource"]; + options?: never; + head?: never; + /** Обновить CDN-источник */ + patch: operations["patchCdnSource"]; + trace?: never; + }; + "/v1/modules/{module_id}/as-entries": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + /** Список AS-записей */ + get: operations["listAsEntries"]; + put?: never; + /** Добавить AS-запись */ + post: operations["createAsEntry"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/modules/{module_id}/as-entries/{entry_id}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + entry_id: components["parameters"]["EntryId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Удалить AS-запись */ + delete: operations["deleteAsEntry"]; + options?: never; + head?: never; + /** Обновить AS-запись */ + patch: operations["patchAsEntry"]; + trace?: never; + }; + "/v1/modules/{module_id}/domain-entries": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + /** + * Список доменных записей + * @description FQDN и привязка к community. + */ + get: operations["listDomainEntries"]; + put?: never; + /** Добавить доменную запись */ + post: operations["createDomainEntry"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/modules/{module_id}/domain-entries/{entry_id}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + entry_id: components["parameters"]["EntryId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Удалить доменную запись */ + delete: operations["deleteDomainEntry"]; + options?: never; + head?: never; + /** Обновить доменную запись */ + patch: operations["patchDomainEntry"]; + trace?: never; + }; + "/v1/modules/{module_id}/ip-range-entries": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + /** + * Список IP-диапазонов + * @description Только для модулей с типом IP_RANGES (статические CIDR и community_id). + */ + get: operations["listIpRangeEntries"]; + put?: never; + /** Добавить IP-диапазон */ + post: operations["createIpRangeEntry"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/modules/{module_id}/ip-range-entries/{entry_id}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + entry_id: components["parameters"]["EntryId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Удалить IP-диапазон */ + delete: operations["deleteIpRangeEntry"]; + options?: never; + head?: never; + /** Обновить IP-диапазон */ + patch: operations["patchIpRangeEntry"]; + trace?: never; + }; + "/v1/modules/{module_id}/refresh": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Запустить ingest + * @description Запуск обновления данных модуля (CDN / DoH / AS в зависимости от типа). + * Итоговая ревизия и фрагменты BIRD строятся **по всем включённым модулям тенанта**: + * обновляемый модуль пересчитывается заново, остальные — по текущему состоянию в БД/источниках, + * так что префиксы ASN, CDN, IP и т.д. суммируются в одном конфиге. + * Для `IP_RANGES` данные обычно только в БД: сервер может вернуть **204** (no-op) или **400**, если refresh не поддерживается - поведение фиксируется в реализации. + * Рекомендуется передавать `Idempotency-Key`. + */ + post: operations["postModuleRefresh"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/modules/{module_id}/entries.csv": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + /** + * Экспорт записей модуля в CSV + * @description Доступно для типов модулей `AS_PREFIXES`, `DOMAINS`, `IP_RANGES`. + * Возвращает CSV с колонками: + * - AS: `asn,community` + * - Домены: `domain,community` + * - IP ranges: `ipRange,community` + */ + get: operations["exportModuleEntriesCsv"]; + put?: never; + /** + * Импорт записей модуля из CSV + * @description Импортирует CSV в модуль типов `AS_PREFIXES`, `DOMAINS`, `IP_RANGES`. + * Поддерживаемые заголовки: + * - `asn,community` + * - `domain,community` + * - `ipRange,community` + * + * В поле `community` можно передавать либо ID community, либо её значение. + */ + post: operations["importModuleEntriesCsv"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/doh-profiles": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Список DoH-профилей */ + get: operations["listDohProfiles"]; + put?: never; + /** + * Создать DoH-профиль + * @description URL и таймауты; секрет - через vault id или отдельный вызов установки секрета. + */ + post: operations["createDohProfile"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/modules/{module_id}/cdn-sources/preview": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Предпросмотр префиксов из CDN-источника + * @description Синхронный запрос: conditional GET к URL (до 8 MiB тела ответа), парсинг plaintext или JSON, возврат до 100 префиксов в `items` (полный счётчик в `total`). Выполняется в HTTP worker; при таймауте клиента используйте короткий URL или меньший payload. + */ + post: operations["previewCdnSource"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/doh-profiles/{id}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["DohProfileId"]; + }; + cookie?: never; + }; + /** + * Получить DoH-профиль + * @description Секрет в ответе не возвращается. + */ + get: operations["getDohProfile"]; + put?: never; + post?: never; + /** + * Удалить DoH-профиль + * @description Допустимо только если профиль не используется модулями. + */ + delete: operations["deleteDohProfile"]; + options?: never; + head?: never; + /** Обновить DoH-профиль */ + patch: operations["patchDohProfile"]; + trace?: never; + }; + "/v1/communities": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Список BGP community */ + get: operations["listCommunities"]; + put?: never; + /** Создать community */ + post: operations["createCommunity"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/communities/{id}/prefixes": { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + limit?: number; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["CommunityId"]; + }; + cookie?: never; + }; + /** + * Префиксы community (latest revision per module) + * @description Уникальные materialized-префиксы с данным community_id + * из последней ревизии каждого модуля tenant. + * Поле `prefixes` — плоский список для клиентов вроде EvoFirewall. + */ + get: operations["listCommunityPrefixes"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/communities/{id}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["CommunityId"]; + }; + cookie?: never; + }; + /** Получить community */ + get: operations["getCommunity"]; + put?: never; + post?: never; + /** + * Удалить community + * @description Только при отсутствии ссылок из других сущностей. + */ + delete: operations["deleteCommunity"]; + options?: never; + head?: never; + /** Обновить community */ + patch: operations["patchCommunity"]; + trace?: never; + }; + "/v1/peers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Список пиров */ + get: operations["listPeers"]; + put?: never; + /** + * Создать пира + * @description Создаёт BGP-пира и инициирует быстрый reconcile пиров (job `peer_reconcile`) без module ingest. После reconcile автоматически запускается apply на спикеры. + */ + post: operations["createPeer"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/peers/discovered": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Список обнаруженных (неодобренных) пиров + * @description Dynamic BGP-сессии (`evobgp_dyn_*`), которых ещё нет в `bgp_peer`. По умолчанию возвращает `status=pending`. При листинге выполняет live-опрос birdc/agent и upsert pending. + */ + get: operations["listDiscoveredPeers"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/peers/discovered/{id}/approve": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Одобрить обнаруженного пира + * @description Создаёт обычный `bgp_peer` из discovery-записи и запускает `peer_reconcile`. + */ + post: operations["approveDiscoveredPeer"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/peers/discovered/{id}/reject": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Отклонить обнаруженного пира + * @description Помечает discovery как rejected; повторно не всплывает при sync. + */ + post: operations["rejectDiscoveredPeer"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/peers/{id}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["PeerId"]; + }; + cookie?: never; + }; + /** Получить пира */ + get: operations["getPeer"]; + put?: never; + post?: never; + /** + * Удалить или отключить пира + * @description Удаление/отключение инициирует быстрый reconcile пиров (job `peer_reconcile`) без module ingest и затем авто-apply на спикеры. + */ + delete: operations["deletePeer"]; + options?: never; + head?: never; + /** + * Обновить пира + * @description Политики (`policies_json`: `local_ipv4`, `local_ipv6`, `local_asn`), neighbor, ASN, привязка к `bgp_speaker_id` или `null` для всех спикеров. Изменение инициирует быстрый reconcile пиров (job `peer_reconcile`) без module ingest и затем авто-apply на спикеры. + */ + patch: operations["patchPeer"]; + trace?: never; + }; + "/v1/bundle/signing-public-key": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Публичный ключ подписи бандлов + * @description Ed25519 public key (base64) для `evobgp-node verify-bundle` / agent sync на реплике. Роль viewer и выше. + */ + get: operations["getBundleSigningPublicKey"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/speakers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Список спикеров + * @description Master / replica, endpoint и связанные поля. + */ + get: operations["listSpeakers"]; + put?: never; + /** + * Зарегистрировать спикер + * @description Реплика, canary и т.д. + */ + post: operations["createSpeaker"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/speakers/{id}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["PeerId"]; + }; + cookie?: never; + }; + /** + * Получить спикер + * @description В т.ч. `last_applied_revision_id`. + */ + get: operations["getSpeaker"]; + put?: never; + post?: never; + /** + * Удалить спикер + * @description Удаляет BGP-спикер. Пиры с `bgp_speaker_id` этого спикера остаются, привязка сбрасывается (ON DELETE SET NULL). + */ + delete: operations["deleteSpeaker"]; + options?: never; + head?: never; + /** Обновить метаданные спикера */ + patch: operations["patchSpeaker"]; + trace?: never; + }; + "/v1/revisions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** История ревизий */ + get: operations["listRevisions"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/revisions/prune-estimate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Оценка очистки ревизий по retention + * @description Считает ревизии и ориентировочный объём данных, которые будут удалены при prune (те же правила, что applyRevisionRetention: последняя ревизия tenant и раскатанные на спикерах сохраняются). + */ + get: operations["getRevisionPruneEstimate"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/revisions/prune": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Очистить старые ревизии (синхронно) + * @description Удаляет ревизии старше cutoff по retention и GC неиспользуемых prefix_snapshot. Operator-only. + */ + post: operations["pruneRevisions"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/revisions/{revision_id}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + revision_id: components["parameters"]["RevisionId"]; + }; + cookie?: never; + }; + /** + * Метаданные ревизии + * @description Хэш, родитель, время, артефакты. + */ + get: operations["getRevision"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/revisions/{revision_id}/prefixes": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + revision_id: components["parameters"]["RevisionId"]; + }; + cookie?: never; + }; + /** Снимок префиксов ревизии */ + get: operations["getRevisionPrefixes"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/revisions/{revision_id}/preview": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + revision_id: components["parameters"]["RevisionId"]; + }; + cookie?: never; + }; + /** + * Превью фрагментов BIRD + * @description Только чтение, без apply. + */ + get: operations["getRevisionPreview"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/revisions/{revision_id}/rollback": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + revision_id: components["parameters"]["RevisionId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Откат через новую ревизию + * @description Создаёт **новую** ревизию с содержимым отката от указанной. Часто возвращает **202** с `Location` на задачу. + * Рекомендуется `Idempotency-Key`. + */ + post: operations["postRevisionRollback"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/revisions/{revision_a}/diff/{revision_b}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + revision_a: components["parameters"]["RevisionA"]; + revision_b: components["parameters"]["RevisionB"]; + }; + cookie?: never; + }; + /** + * Сравнить две ревизии + * @description Формат diff (JSON Patch, табличный и т.д.) определяется реализацией. + */ + get: operations["getRevisionDiff"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/apply": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Применить конфигурацию + * @description Применить целевую ревизию на спикерах (политика по умолчанию или из тела). + * Ответ **202** с `Location` на `/v1/jobs/{job_id}`. **Рекомендуется или обязателен** `Idempotency-Key` по политике продукта. + */ + post: operations["postApply"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/speakers/{id}/apply": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["PeerId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Применить на одном спикере + * @description Canary / точечный deploy. **202** + `Location` на job. + */ + post: operations["postSpeakerApply"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/bird/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Статус локального BIRD на хосте API + * @description Опрос birdc через EVOBGP_BIRDC_SOCKET на процессе API (обычно CP master). На репликах без birdc на CP — birdc_configured=false. + */ + get: operations["getBirdStatus"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/bird/reload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Мягкий reload BIRD + * @description Опциональный явный reload политики, если отделён от apply; иначе может отсутствовать или быть частью apply. + */ + post: operations["postBirdReload"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/jobs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Список задач */ + get: operations["listJobs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/jobs/{job_id}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + job_id: components["parameters"]["JobId"]; + }; + cookie?: never; + }; + /** + * Статус задачи + * @description Прогресс, ошибка, связанные сущности в `meta`. + */ + get: operations["getJob"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/jobs/{job_id}/cancel": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + job_id: components["parameters"]["JobId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Запросить отмену задачи + * @description Best-effort; гарантии зависят от вида задачи. + */ + post: operations["postJobCancel"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/speakers/{speaker_id}/revisions/latest": { + parameters: { + query?: never; + header?: never; + path: { + speaker_id: components["parameters"]["SpeakerId"]; + }; + cookie?: never; + }; + /** + * Последняя опубликованная ревизия для ноды + * @description Вызывается **evobgp-node** с ключом роли `node`. + */ + get: operations["getSpeakerLatestRevision"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/speakers/{speaker_id}/bundle/{revision_id}": { + parameters: { + query?: never; + header?: never; + path: { + speaker_id: components["parameters"]["SpeakerId"]; + revision_id: components["parameters"]["RevisionId"]; + }; + cookie?: never; + }; + /** + * Скачать подписанный бандл + * @description Архив с `manifest.json` и подписью (например Ed25519). Целостность по SHA-256 в манифесте. + * `Content-Type` - `application/octet-stream` или multipart; детали - в реализации. + */ + get: operations["getSpeakerBundle"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/nodes/enroll": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Регистрация ноды + * @description Обмен ключами и привязка к `speaker_id`; детали протокола уточняются отдельно. + */ + post: operations["postNodeEnroll"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/auth/session": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Текущая сессия API-ключа */ + get: operations["getAuthSession"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/api-keys": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Список API-ключей tenant + * @description Только роль **operator**. Секреты не возвращаются. + */ + get: operations["listApiKeys"]; + put?: never; + /** Создать API-ключ */ + post: operations["createApiKey"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/api-keys/{id}": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["ApiKeyId"]; + }; + cookie?: never; + }; + /** Получить метаданные API-ключа */ + get: operations["getApiKey"]; + put?: never; + post?: never; + /** Отозвать API-ключ */ + delete: operations["revokeApiKey"]; + options?: never; + head?: never; + /** Обновить API-ключ */ + patch: operations["patchApiKey"]; + trace?: never; + }; + "/v1/api-keys/{id}/rotate": { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["ApiKeyId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Ротировать секрет API-ключа + * @description Выдаёт новый token; старый перестаёт работать сразу. + */ + post: operations["rotateApiKey"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/monitoring/postgres/overview": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** PostgreSQL overview (instance-level) */ + get: operations["getPostgresOverview"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/monitoring/postgres/queries": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Top queries (pg_stat_statements or snapshot) */ + get: operations["getPostgresQueries"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/monitoring/postgres/locks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Active locks */ + get: operations["getPostgresLocks"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/monitoring/postgres/tables": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Table sizes and scan stats */ + get: operations["getPostgresTables"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/monitoring/postgres/recommendations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Heuristic optimization recommendations */ + get: operations["getPostgresRecommendations"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/monitoring/correlation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Timeline correlation (jobs vs cache hit) */ + get: operations["getMonitoringCorrelation"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/postgres/vacuum": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** VACUUM (async job, operator) */ + post: operations["postPostgresVacuum"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/postgres/vacuum-analyze": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** VACUUM ANALYZE (async job, operator) */ + post: operations["postPostgresVacuumAnalyze"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/postgres/analyze": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** ANALYZE (async job, operator) */ + post: operations["postPostgresAnalyze"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/postgres/reindex": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** REINDEX TABLE (async job, operator) */ + post: operations["postPostgresReindex"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/postgres/cleanup": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Retention cleanup (async job, operator) */ + post: operations["postPostgresCleanup"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/postgres/maintenance/logs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Maintenance audit log */ + get: operations["listPostgresMaintenanceLogs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/maintenance/policies": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List maintenance policies */ + get: operations["listMaintenancePolicies"]; + put?: never; + /** Create maintenance policy */ + post: operations["createMaintenancePolicy"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/maintenance/policies/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get maintenance policy */ + get: operations["getMaintenancePolicy"]; + put?: never; + post?: never; + /** Delete maintenance policy */ + delete: operations["deleteMaintenancePolicy"]; + options?: never; + head?: never; + /** Update maintenance policy */ + patch: operations["patchMaintenancePolicy"]; + trace?: never; + }; + "/v1/maintenance/policies/{id}/hints": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** PostgreSQL hints for policy table */ + get: operations["getMaintenancePolicyHints"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/maintenance/config-audit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Maintenance policy configuration audit log */ + get: operations["listMaintenanceConfigAudit"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/maintenance/run": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Run maintenance policy (async job) */ + post: operations["postMaintenanceRun"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/maintenance/dry-run": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Dry-run maintenance policy (async job) */ + post: operations["postMaintenanceDryRun"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/runtime-logs/files": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Список runtime log-файлов + * @description Список *.log в EVOBGP_RUNTIME_LOGS_DIR (размер и mtime). + * Требуется evobgp-all с примонтированным volume. + */ + get: operations["listRuntimeLogFiles"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/runtime-logs/files/{filename}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Хвост runtime log-файла */ + get: operations["getRuntimeLogTail"]; + put?: never; + post?: never; + /** + * Очистить runtime log-файл + * @description Синхронная очистка (truncate по умолчанию или delete). Запись в cleanup audit. + * Максимальный размер файла для очистки — 512 MiB. Только operator+. + */ + delete: operations["deleteRuntimeLogFile"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/runtime-logs/auto-estimate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Оценка автоочистки runtime log-файлов + * @description Список файлов, которые будут затронуты текущей политикой tenant settings. + * Требует evobgp-all с примонтированным каталогом runtime-logs. + */ + get: operations["estimateRuntimeLogAutoCleanup"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/runtime-logs/auto-run": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Запустить автоочистку runtime log-файлов + * @description Немедленный прогон политики из tenant settings. `dry_run=true` — только оценка без FS-изменений. + * Записи audit с `actor_prefix=auto:scheduler`. + */ + post: operations["runRuntimeLogAutoCleanup"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/runtime-logs/cleanup-audit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Audit очистки runtime log-файлов */ + get: operations["listRuntimeLogCleanupAudit"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/audit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Журнал CRUD audit tenant + * @description Локальный журнал изменений (modules, peers, settings, API keys и т.д.). + * При настроенных `AUTH_PORTAL_URL` + `AUTH_AUDIT_INGEST_SECRET` события также + * отправляются в auth-portal ingest (`source_app=bgp`). + */ + get: operations["listAuditLog"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/settings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Получить настройки + * @description KV (лимиты CDN, feature flags и т.д.). + */ + get: operations["getSettings"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * Частично обновить настройки + * @description Только роль **operator**. + */ + patch: operations["patchSettings"]; + trace?: never; + }; + "/v1/firewall/install-context": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Install context for firewall one-liner (operator) */ + get: operations["getFirewallInstallContext"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/firewall/enroll": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Enroll firewall client (public, X-EvoBGP-Seed) */ + post: operations["firewallEnroll"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/firewall/clients": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List firewall clients */ + get: operations["listFirewallClients"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/firewall/clients/{id}/approve": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Approve pending client */ + post: operations["approveFirewallClient"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/firewall/clients/{id}/revoke": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Reject pending or revoke approved client */ + post: operations["revokeFirewallClient"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/firewall/rules": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List firewall rules */ + get: operations["listFirewallRules"]; + put?: never; + /** Create firewall rule */ + post: operations["createFirewallRule"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/firewall/blocklist": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get evaluated blocklist (firewall client token) */ + get: operations["getFirewallBlocklist"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/firewall/apply-report": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Report last apply status */ + post: operations["firewallApplyReport"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** + * @description UUID v7 или ULID. + * @example 01JQXYZABCDEFGHIJKLMNOPQRS + */ + ResourceId: string; + /** @description Problem Details (RFC 9457). */ + Problem: { + /** + * Format: uri-reference + * @description URI типа проблемы. + */ + type?: string; + title: string; + status: number; + detail?: string; + /** Format: uri-reference */ + instance?: string; + errors?: components["schemas"]["FieldError"][]; + } & { + [key: string]: unknown; + }; + FieldError: { + /** @description Имя поля (или JSON Pointer). */ + field?: string; + /** @description JSON Pointer к значению. */ + pointer?: string; + message?: string; + } & { + [key: string]: unknown; + }; + AsyncJobAccepted: { + job_id: components["schemas"]["ResourceId"]; + /** + * @description Начальный статус задачи в очереди. + * @enum {string} + */ + status: "queued"; + }; + /** @enum {string} */ + ModuleType: "AS_PREFIXES" | "CDN_CIDRS" | "DOMAINS" | "IP_RANGES"; + /** + * @description Политика резолва доменов через DoH при нескольких профилях. + * `primary_only` — только первый профиль; `failover` — по порядку до первого успешного; + * `union` — объединение A/AAAA со всех профилей. + * @default primary_only + * @enum {string} + */ + DohResolverPolicy: "primary_only" | "failover" | "union"; + /** + * @description Статус задачи; перечень может расширяться. + * @enum {string} + */ + JobStatus: "queued" | "running" | "succeeded" | "failed" | "cancelled"; + /** + * @description Стратегия выбора спикеров для apply. + * @enum {string} + */ + ApplyStrategy: "all_speakers"; + Module: { + id: components["schemas"]["ResourceId"]; + type: components["schemas"]["ModuleType"]; + name: string; + enabled: boolean; + priority: number; + /** @description Первый DoH-профиль (legacy); предпочтительно `doh_profile_ids`. */ + doh_profile_id?: string | null; + /** @description Упорядоченный список DoH-профилей для модулей `DOMAINS`. */ + doh_profile_ids?: components["schemas"]["ResourceId"][]; + doh_resolver_policy?: components["schemas"]["DohResolverPolicy"]; + refresh_interval_sec?: number | null; + cron_expr?: string | null; + default_community_id?: string | null; + /** + * Format: date-time + * @description Время последнего успешного обновления данных модуля. + */ + last_refreshed_at?: string | null; + } & { + [key: string]: unknown; + }; + ModuleCreate: { + type: components["schemas"]["ModuleType"]; + name: string; + /** @default true */ + enabled: boolean; + /** @default 0 */ + priority: number; + /** @description Первый DoH-профиль (legacy); предпочтительно `doh_profile_ids`. */ + doh_profile_id?: string | null; + /** @description Упорядоченный список DoH-профилей для модулей `DOMAINS`. */ + doh_profile_ids?: components["schemas"]["ResourceId"][]; + doh_resolver_policy?: components["schemas"]["DohResolverPolicy"]; + refresh_interval_sec?: number | null; + /** + * Format: date-time + * @description Время последнего успешного обновления этого CDN-источника. + */ + last_refreshed_at?: string | null; + cron_expr?: string | null; + default_community_id?: string | null; + }; + ModulePatch: { + name?: string; + enabled?: boolean; + priority?: number; + /** @description Первый DoH-профиль (legacy); предпочтительно `doh_profile_ids`. */ + doh_profile_id?: string | null; + /** @description Упорядоченный список DoH-профилей для модулей `DOMAINS`. */ + doh_profile_ids?: components["schemas"]["ResourceId"][]; + doh_resolver_policy?: components["schemas"]["DohResolverPolicy"]; + refresh_interval_sec?: number | null; + cron_expr?: string | null; + default_community_id?: string | null; + }; + CdnSource: { + id: components["schemas"]["ResourceId"]; + /** Format: uri */ + url: string; + /** @description Формат скачанного списка / парсер. */ + source_kind: string; + /** @description Путь до поля с префиксами для source_kind=json (например data.items[].cidr). */ + prefix_path?: string; + community_id?: string | null; + refresh_interval_sec?: number | null; + }; + CdnSourceCreate: { + /** Format: uri */ + url: string; + source_kind: string; + prefix_path?: string; + community_id?: string | null; + }; + CdnSourcePatch: { + /** Format: uri */ + url?: string; + source_kind?: string; + prefix_path?: string; + community_id?: string | null; + refresh_interval_sec?: number | null; + }; + CdnPreviewRequest: { + /** Format: uri */ + url: string; + source_kind: string; + prefix_path?: string; + }; + CdnPreviewResponse: { + items: string[]; + total: number; + truncated: boolean; + source_url: string; + }; + AsEntry: { + id: components["schemas"]["ResourceId"]; + asn: number; + community_id?: string | null; + } & { + [key: string]: unknown; + }; + AsEntryCreate: { + asn: number; + community_id?: string | null; + } & { + [key: string]: unknown; + }; + AsEntryPatch: { + asn?: number; + community_id?: string | null; + } & { + [key: string]: unknown; + }; + DomainEntry: { + id: components["schemas"]["ResourceId"]; + fqdn: string; + community_id?: string | null; + }; + DomainEntryCreate: { + fqdn: string; + community_id?: string | null; + }; + IpRangeEntry: { + id: components["schemas"]["ResourceId"]; + /** + * @description IPv4/IPv6 CIDR. + * @example 203.0.113.0/24 + */ + prefix: string; + community_id: components["schemas"]["ResourceId"]; + }; + IpRangeEntryCreate: { + prefix: string; + community_id: components["schemas"]["ResourceId"]; + }; + DohProfile: { + id: components["schemas"]["ResourceId"]; + /** @description Отображаемое имя профиля (опционально). */ + name?: string; + /** Format: uri */ + url?: string; + timeout_ms?: number; + /** @description Ссылка на секрет в vault; сырое значение не отдаётся в GET. */ + vault_secret_ref?: string | null; + } & { + [key: string]: unknown; + }; + DohProfileCreate: { + /** @description Отображаемое имя профиля (опционально). */ + name?: string; + /** Format: uri */ + url: string; + timeout_ms?: number; + vault_secret_ref?: string | null; + }; + AuthSession: { + tenant_id: components["schemas"]["ResourceId"]; + /** + * @description apikey — Bearer API key; jwt — portal SSO token. + * @enum {string} + */ + kind: "apikey" | "jwt"; + /** @description API-key role (viewer|editor|operator|node). Empty string for portal JWT sessions. */ + role?: string; + /** @description JWT sub (portal sessions only). */ + user_id?: string; + /** @description JWT email claim (portal sessions only). */ + email?: string; + /** @description JWT permissions (bgp:*); portal sessions only. */ + permissions?: string[]; + /** @description Portal is_admin claim; grants all bgp permissions. */ + is_admin?: boolean; + }; + ApiKey: { + id: components["schemas"]["ResourceId"]; + name: string; + /** @enum {string} */ + role: "viewer" | "editor" | "operator" | "node"; + /** @description Первые 8 символов токена для идентификации в UI. */ + prefix: string; + /** Format: date-time */ + created_at: string; + /** Format: date-time */ + updated_at: string; + /** Format: date-time */ + expires_at?: string | null; + /** Format: date-time */ + revoked_at?: string | null; + /** Format: date-time */ + last_used_at?: string | null; + } & { + [key: string]: unknown; + }; + ApiKeyCreate: { + name: string; + /** @enum {string} */ + role: "viewer" | "editor" | "operator" | "node"; + /** Format: date-time */ + expires_at?: string | null; + }; + ApiKeyPatch: { + name?: string; + /** @enum {string} */ + role?: "viewer" | "editor" | "operator" | "node"; + /** Format: date-time */ + expires_at?: string | null; + }; + ApiKeyCreated: components["schemas"]["ApiKey"] & { + /** @description Полный Bearer-токен; показывается один раз. */ + token: string; + }; + BgpCommunity: { + id: components["schemas"]["ResourceId"]; + /** @description Техническое значение BGP community (строка для BIRD, например 65001:120 или large JSON в value_json). */ + community?: string; + /** @description Человекочитаемое название для UI и фильтров. */ + title?: string; + } & { + [key: string]: unknown; + }; + /** + * @description Определённый тип запроса после нормализации. + * @enum {string} + */ + LookupQueryKind: "ip" | "domain"; + /** + * @description `entry` — сырые IP_RANGES / DOMAINS entries; + * `snapshot` — материализованные префиксы `module_prefix_snapshot`. + * @enum {string} + */ + LookupLayer: "entry" | "snapshot"; + /** + * @description Вид совпадения (entry CIDR, entry FQDN или snapshot prefix). + * @enum {string} + */ + LookupMatchKind: "ip_range" | "domain" | "prefix"; + LookupMatch: { + layer: components["schemas"]["LookupLayer"]; + module_id: components["schemas"]["ResourceId"]; + module_name: string; + module_type: components["schemas"]["ModuleType"]; + match_kind: components["schemas"]["LookupMatchKind"]; + /** @description CIDR, FQDN или prefix, с которым совпал запрос. */ + matched_value: string; + /** @description ID entry (только для layer=entry). */ + entry_id?: string; + /** @description Источник строки snapshot (ip_range, domain, as, cdn, …). */ + source?: string; + community_id?: string | null; + /** @description Техническое значение BGP community. */ + community?: string; + /** @description Человекочитаемое название community. */ + community_title?: string; + /** + * @description IP, полученный DNS-resolve domain-запроса, из-за которого появился этот матч. + * Пусто для прямого IP-запроса и для FQDN entry/snapshot без resolve. + */ + resolved_ip?: string; + }; + LookupResponse: { + /** @description Исходная строка запроса. */ + query: string; + query_kind: components["schemas"]["LookupQueryKind"]; + /** @description Нормализованный IP или FQDN. */ + normalized: string; + /** @description true, если есть хотя бы одно совпадение. */ + matched: boolean; + match_count: number; + matches: components["schemas"]["LookupMatch"][]; + /** @description IP-адреса после live DNS resolve (только для query_kind=domain; A/AAAA). */ + resolved_ips?: string[]; + }; + BgpPeer: { + id: components["schemas"]["ResourceId"]; + /** @description IP соседа. */ + neighbor?: string; + remote_asn?: number; + /** @description `null` - политика для всех спикеров. */ + bgp_speaker_id?: string | null; + /** @description Live (GET /v1/peers?live=1): спикер, на котором сессия Established; опрос CP birdc + agent /v1/agent/bird/protocols. */ + connected_speaker_id?: string | null; + /** @description Человекочитаемая метка ноды из live-опроса. */ + connected_speaker_label?: string; + /** @description Состояние протокола пира на каждой опрошенной ноде. */ + session_on_speakers?: { + speaker_id?: string; + label?: string; + state?: string; + }[]; + /** @description Ноды, где сессия в состоянии Established (один пир может быть на нескольких). */ + established_on_speakers?: { + speaker_id?: string; + label?: string; + state?: string; + }[]; + /** @description true если bgp_speaker_id задан, но на этой ноде нет Established (сессия может быть на других нодах — это не ошибка для tenant-wide пиров). */ + session_mismatch?: boolean; + /** @description JSON-объект (строка). Поля `local_ipv4`, `local_ipv6`, `local_asn` переопределяют глобальные `bird_local_ipv4` / `bird_local_ipv6` / `bird_local_asn` тенанта. Если эффективный локальный адрес или ASN пира отличается от дефолтов тенанта (`bird_local_*`), в блок `protocol bgp … from bgp_template` добавляется строка `local … as …`. */ + policies_json?: string; + } & { + [key: string]: unknown; + }; + BgpSpeaker: { + id: components["schemas"]["ResourceId"]; + /** @description master, replica, canary и т.д. */ + role?: string; + endpoint?: string; + last_applied_revision_id?: string | null; + /** @description Последняя опубликованная на CP ревизия для этого спикера. */ + published_revision_id?: string | null; + /** Format: date-time */ + published_at?: string | null; + /** @description FQDN agent API за Traefik (Address в UI, Remnawave-style). */ + agent_domain?: string; + /** @description IPv4 VPS; default для bird_bgp_source_ipv4. */ + node_ipv4?: string; + /** @description Per-speaker override router id / BGP local (см. pipeline overlay). */ + bird_bgp_source_ipv4?: string; + /** @description ok, error, skipped — последний Panel→Node wake-up. */ + dispatch_status?: string; + /** @description synced, error — состояние sync на реплике. */ + sync_status?: string; + /** Format: date-time */ + last_dispatch_at?: string; + last_dispatch_error?: string; + /** @description Расширяемый объект. Ключи agent_domain, agent_secret (только при создании), agent_port, node_ipv4, bird_bgp_source_ipv4, bird_bgp_source_ipv6. */ + meta_json?: Record; + /** @description При GET /v1/speakers?live=1 — runtime-статус agent и BGP-опроса на ноде. */ + live?: components["schemas"]["SpeakerLiveStatus"]; + } & { + [key: string]: unknown; + }; + /** @description Live runtime snapshot for one speaker (GET /v1/speakers?live=1). */ + SpeakerLiveStatus: { + /** @description Человекочитаемая метка ноды (agent domain или CP master). */ + label?: string; + /** @description true если agent /v1/agent/health успешен (master — local birdc poll). */ + agent_ok?: boolean; + agent_error?: string; + /** Format: date-time */ + agent_last_sync_at?: string; + agent_last_applied_revision_id?: string; + /** @description true если birdc (CP) или GET /v1/agent/bird/protocols (replica) успешен. */ + bgp_poll_ok?: boolean; + bgp_poll_error?: string; + bgp_sessions_total?: number; + bgp_established?: number; + sessions?: components["schemas"]["BgpSessionLive"][]; + } & { + [key: string]: unknown; + }; + BgpSessionLive: { + name?: string; + neighbor?: string; + /** @description Remote ASN from birdc (`Neighbor AS:`). */ + neighbor_as?: number; + /** @description BGP Identifier / Neighbor ID from birdc (`Neighbor ID:`). */ + neighbor_id?: string; + state?: string; + } & { + [key: string]: unknown; + }; + /** @description Метаданные опроса одной ноды в GET /v1/peers?live=1. */ + LiveSpeakerPoll: { + speaker_id?: string; + label?: string; + ok?: boolean; + session_count?: number; + poll_error?: string; + } & { + [key: string]: unknown; + }; + /** @description Instance-level PostgreSQL snapshot (GET /v1/monitoring/postgres/overview). */ + PostgresOverview: { + [key: string]: unknown; + }; + PostgresQueriesResponse: { + /** Format: date-time */ + collected_at?: string; + /** @enum {string} */ + source?: "live" | "snapshot"; + items?: { + [key: string]: unknown; + }[]; + }; + PostgresRecommendations: { + /** Format: date-time */ + collected_at?: string; + items?: { + severity?: string; + code?: string; + title?: string; + detail?: string; + refs?: string[]; + }[]; + }; + PostgresMaintenanceBody: { + table?: string; + /** @default false */ + dry_run: boolean; + /** @description Deprecated; use maintenance policies API. */ + policy?: string; + limit?: number; + }; + MaintenancePolicy: { + id?: components["schemas"]["ResourceId"]; + name: string; + table_name: string; + /** @default true */ + condition: string; + retention_period_sec?: number; + max_rows?: number; + /** @enum {string} */ + vacuum_strategy: "none" | "vacuum" | "analyze" | "vacuum_analyze" | "reindex"; + /** @description Cron expression (5-field, UTC). */ + schedule: string; + /** @default true */ + enabled: boolean; + /** @default false */ + dry_run_enabled: boolean; + /** Format: date-time */ + last_run_at?: string; + last_status?: string; + last_error?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }; + MaintenancePolicyPatch: { + name?: string; + table_name?: string; + condition?: string; + retention_period_sec?: number; + max_rows?: number; + /** @enum {string} */ + vacuum_strategy?: "none" | "vacuum" | "analyze" | "vacuum_analyze" | "reindex"; + schedule?: string; + enabled?: boolean; + dry_run_enabled?: boolean; + }; + MaintenanceRunBody: { + policy_id: components["schemas"]["ResourceId"]; + }; + MaintenancePolicyList: { + items?: components["schemas"]["MaintenancePolicy"][]; + next_cursor?: string; + has_more?: boolean; + }; + /** + * @description truncate — обнулить файл (по умолчанию); delete — удалить файл с диска. + * @enum {string} + */ + RuntimeLogCleanupMode: "truncate" | "delete"; + RuntimeLogFile: { + /** @description Basename файла (*.log) в каталоге runtime-логов. */ + name: string; + /** Format: int64 */ + size_bytes: number; + /** Format: date-time */ + modified_at: string; + }; + RuntimeLogFileList: { + items: components["schemas"]["RuntimeLogFile"][]; + }; + RuntimeLogTail: { + filename: string; + /** @description UTF-8 текст хвоста файла. */ + content: string; + /** @description true если применён лимит bytes/lines. */ + truncated: boolean; + lines_returned: number; + }; + RuntimeLogCleanupResult: { + audit_id: components["schemas"]["ResourceId"]; + filename: string; + action: components["schemas"]["RuntimeLogCleanupMode"]; + /** Format: int64 */ + size_before: number; + /** Format: int64 */ + size_after?: number | null; + }; + RuntimeLogCleanupAudit: { + id: components["schemas"]["ResourceId"]; + tenant_id: components["schemas"]["ResourceId"]; + actor_prefix: string; + filename: string; + action: components["schemas"]["RuntimeLogCleanupMode"]; + /** Format: int64 */ + size_before: number; + /** Format: int64 */ + size_after?: number | null; + detail?: { + [key: string]: unknown; + }; + /** Format: date-time */ + created_at: string; + }; + RuntimeLogCleanupAuditList: { + items?: components["schemas"]["RuntimeLogCleanupAudit"][]; + next_cursor?: string; + has_more?: boolean; + }; + /** @enum {string} */ + AuditSeverity: "info" | "warning" | "critical"; + AuditLogEntry: { + id: components["schemas"]["ResourceId"]; + tenant_id: components["schemas"]["ResourceId"]; + /** @description Stable id for portal ingest deduplication (prefix bgp-). */ + event_id: string; + /** @enum {string} */ + source_app: "bgp"; + /** @description Machine action key (e.g. bgp.module.create). */ + action: string; + severity: components["schemas"]["AuditSeverity"]; + actor_user_id?: string | null; + actor_email?: string | null; + actor_name?: string | null; + actor_api_key_prefix?: string | null; + /** @enum {string|null} */ + target_type?: "app_resource" | null; + target_id?: string | null; + summary: string; + details?: { + [key: string]: unknown; + } | null; + ip?: string | null; + /** Format: date-time */ + created_at: string; + /** Format: date-time */ + portal_pushed_at?: string | null; + }; + AuditLogList: { + items: components["schemas"]["AuditLogEntry"][]; + next_cursor?: string; + has_more?: boolean; + }; + RuntimeLogAutoPolicy: { + enabled?: boolean; + /** Format: int64 */ + max_file_bytes?: number; + /** @description UTC cron (minute hour dom month dow). */ + schedule?: string; + mode?: components["schemas"]["RuntimeLogCleanupMode"]; + }; + RuntimeLogAutoEstimateItem: { + filename: string; + /** Format: int64 */ + size_bytes: number; + would_cleanup: boolean; + /** @description under_threshold, too_large, или текст ошибки. */ + skip_reason?: string; + }; + RuntimeLogAutoEstimate: { + policy?: components["schemas"]["RuntimeLogAutoPolicy"]; + items?: components["schemas"]["RuntimeLogAutoEstimateItem"][]; + would_count?: number; + }; + /** @description dry_run, trigger, policy, cleaned[], skipped[], cleaned_count, skipped_count. */ + RuntimeLogAutoRunResult: { + [key: string]: unknown; + }; + /** @description Статус локального BIRD на хосте API (GET /v1/bird/status). */ + BirdLocalStatus: { + birdc_configured?: boolean; + message?: string; + error?: string; + protocols_excerpt?: string; + bgp_sessions_total?: number; + bgp_established?: number; + healthy?: boolean | null; + } & { + [key: string]: unknown; + }; + BundleSigningPublicKey: { + /** @description Ed25519 public key (base64) для verify-bundle на реплике. */ + public_key_base64: string; + }; + ConfigRevision: { + id: components["schemas"]["ResourceId"]; + parent_revision_id?: string | null; + content_hash?: string; + /** Format: date-time */ + created_at?: string; + } & { + [key: string]: unknown; + }; + RevisionPruneEstimate: { + retention_minutes: number; + /** Format: date-time */ + cutoff_at: string; + revision_count: number; + /** @description Строки prefix_snapshot_row в освобождаемых снимках. */ + prefix_row_count: number; + orphan_snapshot_count: number; + /** + * Format: int64 + * @description Ориентировочный логический объём данных (байты). + */ + bytes_estimate: number; + }; + RevisionPruneResult: { + deleted_revisions: number; + deleted_prefix_snapshots: number; + deleted_prefix_rows: number; + /** Format: int64 */ + bytes_estimate: number; + }; + RevisionPruneRequest: { + /** @description TTL в минутах; если не задан — из revision_retention_minutes tenant settings. */ + retention_minutes?: number; + }; + /** @description Элемент материализованного снимка. Поле prefix обычно содержит CIDR; для модулей AS_PREFIXES допускается ключ вида as:<номер_asn> (не CIDR). */ + PrefixSnapshotItem: { + [key: string]: unknown; + }; + Job: { + job_id: components["schemas"]["ResourceId"]; + kind: string; + status: components["schemas"]["JobStatus"]; + idempotency_key?: string | null; + /** Format: date-time */ + created_at: string; + /** @description ISO 8601 UTC, если задача уже стартовала. */ + started_at?: string | null; + /** @description ISO 8601 UTC по завершении. */ + finished_at?: string | null; + error?: string | null; + meta?: { + [key: string]: unknown; + }; + }; + ApplyRequest: { + revision_id?: components["schemas"]["ResourceId"]; + strategy?: components["schemas"]["ApplyStrategy"]; + /** @default false */ + dry_run: boolean; + }; + VersionInfo: { + /** + * @description Semver сборки (git tag без префикса v). + * @example 1.2.3 + */ + version?: string; + /** + * @deprecated + * @description Alias поля `version` (сохранён для обратной совместимости). + */ + api_version?: string; + git_sha?: string; + /** Format: date-time */ + build_time?: string; + } & { + [key: string]: unknown; + }; + HealthStatus: { + /** @example ok */ + status?: string; + } & { + [key: string]: unknown; + }; + ReadyStatus: { + /** @example ready */ + status?: string; + checks?: { + [key: string]: unknown; + }; + } & { + [key: string]: unknown; + }; + /** + * @description KV настройки tenant/глобальные лимиты и feature flags. + * Параметры BIRD (строки в `global_settings`, JSON-значения — обычно строка или число): + * `bird_router_id`, `bird_local_ipv4`, `bird_local_ipv6`, `bird_local_asn`; + * если задан `bird_bgp_source_ipv4`, он подставляется как BIRD `router id` (перекрывает `bird_router_id`). + * Ключи `bird_bgp_source_ipv4` / `bird_bgp_source_ipv6` в сгенерированном BGP для пиров не используются (оставлены для совместимости API). + * Шаблон BGP в конфиге: `local as ;` без локального IP. + */ + Settings: { + /** @description BIRD `router id` (IPv4 dotted quad), если не задан `bird_bgp_source_ipv4`. */ + bird_router_id?: string; + bird_local_ipv4?: string; + bird_local_ipv6?: string; + bird_local_asn?: number; + /** @description Если задан — используется как BIRD `router id` (IPv4). В блоках `protocol bgp … from bgp_template` строка `source address` не генерируется. */ + bird_bgp_source_ipv4?: string; + /** @description Зарезервировано; в текущей генерации BGP не используется. */ + bird_bgp_source_ipv6?: string; + revision_retention_minutes?: number; + /** @description Автоочистка *.log на evobgp-all по расписанию (только при FS volume). */ + runtime_logs_auto_enabled?: boolean; + /** @description Truncate/delete файлов строго больше порога (MiB). */ + runtime_logs_max_file_mb?: number; + /** @description UTC cron для автоочистки (по умолчанию `0 *\/6 * * *`). */ + runtime_logs_auto_schedule?: string; + runtime_logs_auto_mode?: components["schemas"]["RuntimeLogCleanupMode"]; + /** + * @description Показывать блок «Быстрые действия» на дашборде (UI preference). + * @default true + */ + ui_show_quick_actions: boolean; + } & { + [key: string]: unknown; + }; + /** @description Сравнение двух ревизий. Конкретный формат (JSON Patch, табличный diff и т.д.) задаётся реализацией - контракт может уточняться. */ + RevisionDiff: { + [key: string]: unknown; + }; + /** @description Тело регистрации ноды; протокол обмена ключами уточняется отдельно. */ + NodeEnrollRequest: { + public_key?: string; + speaker_id?: components["schemas"]["ResourceId"]; + } & { + [key: string]: unknown; + }; + /** @description Подтверждение записи enrollment (метаданные спикера обновлены). */ + NodeEnrollResponse: { + /** @example enrolled */ + status?: string; + speaker_id?: components["schemas"]["ResourceId"]; + tenant_id?: components["schemas"]["ResourceId"]; + } & { + [key: string]: unknown; + }; + DohProfilePatch: { + name?: string; + /** Format: uri */ + url?: string; + timeout_ms?: number; + vault_secret_ref?: string | null; + }; + BgpCommunityCreate: { + community: string; + title?: string; + } & { + [key: string]: unknown; + }; + BgpCommunityPatch: { + community?: string; + title?: string; + } & { + [key: string]: unknown; + }; + BgpPeerDiscovery: { + id: components["schemas"]["ResourceId"]; + speaker_id?: string | null; + /** @description BGP Identifier (Neighbor ID / router ID) from birdc. */ + neighbor_id?: string; + /** @description Neighbor IP address. */ + neighbor: string; + remote_asn?: number; + /** @description BIRD protocol name (evobgp_dyn_*). */ + protocol_name?: string; + session_state?: string; + /** @enum {string} */ + status: "pending" | "approved" | "rejected"; + /** Format: date-time */ + first_seen_at?: string; + /** Format: date-time */ + last_seen_at?: string; + approved_peer_id?: string | null; + } & { + [key: string]: unknown; + }; + BgpPeerDiscoveryApprove: { + name?: string; + bgp_speaker_id?: string | null; + enabled?: boolean; + }; + BgpPeerCreate: { + neighbor: string; + remote_asn: number; + bgp_speaker_id?: string | null; + } & { + [key: string]: unknown; + }; + BgpPeerPatch: { + neighbor?: string; + remote_asn?: number; + bgp_speaker_id?: string | null; + } & { + [key: string]: unknown; + }; + BgpSpeakerCreate: { + /** @default replica */ + role: string; + /** @description URL agent или https://AGENT_DOMAIN */ + endpoint: string; + /** @description JSON-объект. Ключи node_ipv4, bird_bgp_source_ipv4 (default = node_ipv4), agent_domain, agent_secret (генерируется при создании если пуст). */ + meta_json?: string; + } & { + [key: string]: unknown; + }; + BgpSpeakerPatch: { + role?: string; + endpoint?: string; + /** @description JSON-объект с ключами agent_domain, node_ipv4, bird_bgp_source_ipv4 и др. */ + meta_json?: string; + } & { + [key: string]: unknown; + }; + LatestRevisionPointer: { + revision_id: components["schemas"]["ResourceId"]; + /** Format: date-time */ + published_at?: string; + }; + FirewallClient: { + id?: components["schemas"]["ResourceId"]; + name?: string; + hostname?: string; + token_prefix?: string; + /** @enum {string} */ + status?: "pending" | "approved" | "revoked"; + /** Format: date-time */ + last_seen_at?: string; + /** Format: date-time */ + last_apply_at?: string; + last_apply_status?: string; + last_apply_prefix_count?: number; + /** + * Format: int64 + * @description Cumulative packets dropped by blocklist rule (from client kernel counter). + */ + last_apply_packets_dropped?: number; + /** + * Format: int64 + * @description Cumulative packets accepted past blocklist chain (nft counter accept rule). + */ + last_apply_packets_accepted?: number; + client_version?: string; + }; + /** @description Контекст для one-liner установки firewall-клиента (только operator). */ + FirewallInstallContext: { + /** @description Значение EVOBGP_BUNDLE_SEED_HEX на control plane. */ + bundle_seed?: string; + bundle_seed_configured?: boolean; + /** Format: uri */ + suggested_cp_url?: string; + /** Format: uri */ + install_sh_url?: string; + }; + FirewallRule: { + id?: components["schemas"]["ResourceId"]; + client_id?: components["schemas"]["ResourceId"]; + priority?: number; + /** @enum {string} */ + action?: "block" | "accept"; + community_id?: components["schemas"]["ResourceId"]; + comment?: string; + }; + FirewallBlocklist: { + client_id?: components["schemas"]["ResourceId"]; + revision_id?: components["schemas"]["ResourceId"]; + prefixes?: string[]; + total?: number; + hash?: string; + }; + }; + responses: { + /** @description Нет или невалидный Bearer-токен. */ + Unauthorized: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Некорректный запрос (пустой или невалидный параметр). */ + BadRequest: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Недостаточно прав для операции. */ + Forbidden: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Ресурс не найден в скоупе tenant. */ + NotFound: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Конфликт состояния (например, удаление DoH-профиля, который ещё используется). */ + Conflict: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Семантическая ошибка запроса или валидации полей. */ + UnprocessableEntity: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Ошибка (см. тело Problem). */ + DefaultProblem: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + parameters: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + TenantId: components["schemas"]["ResourceId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + IdempotencyKey: string; + /** @description Непрозрачный курсор из предыдущего ответа. */ + Cursor: string; + /** @description Размер страницы (по умолчанию 50). */ + Limit: number; + ModuleId: components["schemas"]["ResourceId"]; + SpeakerId: components["schemas"]["ResourceId"]; + RevisionId: components["schemas"]["ResourceId"]; + JobId: components["schemas"]["ResourceId"]; + PeerId: components["schemas"]["ResourceId"]; + DohProfileId: components["schemas"]["ResourceId"]; + CommunityId: components["schemas"]["ResourceId"]; + ApiKeyId: components["schemas"]["ResourceId"]; + SourceId: components["schemas"]["ResourceId"]; + EntryId: components["schemas"]["ResourceId"]; + RevisionA: components["schemas"]["ResourceId"]; + RevisionB: components["schemas"]["ResourceId"]; + ModuleTypeFilter: components["schemas"]["ModuleType"]; + ModuleEnabledFilter: boolean; + SpeakerFilter: components["schemas"]["ResourceId"]; + RevisionModuleFilter: components["schemas"]["ResourceId"]; + JobStatusFilter: components["schemas"]["JobStatus"]; + /** @description Фильтр по виду задачи; точный перечень расширяем. Основные значения: `module_refresh`, `peer_reconcile`, `deploy_apply`, `revision_rollback`, `bird_reload`. */ + JobKindFilter: string; + }; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + getHealth: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Процесс отвечает. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HealthStatus"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getReady: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Сервис готов принимать запросы. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReadyStatus"]; + }; + }; + /** @description Не готов (зависимости недоступны). */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getVersionRoot: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Метаданные сборки. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["VersionInfo"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getVersion: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Метаданные сборки. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["VersionInfo"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listModules: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + type?: components["parameters"]["ModuleTypeFilter"]; + enabled?: components["parameters"]["ModuleEnabledFilter"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Страница модулей. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Module"][]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + lookupMembership: { + parameters: { + query: { + /** @description IP-адрес или FQDN для проверки. */ + q: string; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Результат проверки (в т.ч. matched=false при отсутствии совпадений). */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["LookupResponse"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthorized"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getRouterListsCatalog: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Агрегированные данные для списков UI. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + modules: { + items: components["schemas"]["Module"][]; + }; + domains: { + items: { + module_id: components["schemas"]["ResourceId"]; + entry: components["schemas"]["DomainEntry"]; + }[]; + }; + asns: { + items: { + module_id: components["schemas"]["ResourceId"]; + entry: components["schemas"]["AsEntry"]; + }[]; + }; + ip_ranges: { + items: { + module_id: components["schemas"]["ResourceId"]; + entry: components["schemas"]["IpRangeEntry"]; + }[]; + }; + communities: { + items: components["schemas"]["BgpCommunity"][]; + }; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + createModule: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ModuleCreate"]; + }; + }; + responses: { + /** @description Модуль создан. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Module"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 422: components["responses"]["UnprocessableEntity"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getModule: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Module"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + deleteModule: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Удалено или деактивировано. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + patchModule: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ModulePatch"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Module"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + 422: components["responses"]["UnprocessableEntity"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + listCdnSources: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["CdnSource"][]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + createCdnSource: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CdnSourceCreate"]; + }; + }; + responses: { + /** @description Ресурс создан. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CdnSource"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + 422: components["responses"]["UnprocessableEntity"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + deleteCdnSource: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + source_id: components["parameters"]["SourceId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Удалено. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 401: components["responses"]["Unauthorized"]; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + patchCdnSource: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + source_id: components["parameters"]["SourceId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CdnSourcePatch"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CdnSource"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + listAsEntries: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["AsEntry"][]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + createAsEntry: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AsEntryCreate"]; + }; + }; + responses: { + /** @description Ресурс создан. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsEntry"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + deleteAsEntry: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + entry_id: components["parameters"]["EntryId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Удалено. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + patchAsEntry: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + entry_id: components["parameters"]["EntryId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AsEntryPatch"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsEntry"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + listDomainEntries: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["DomainEntry"][]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + createDomainEntry: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DomainEntryCreate"]; + }; + }; + responses: { + /** @description Ресурс создан. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DomainEntry"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + deleteDomainEntry: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + entry_id: components["parameters"]["EntryId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Удалено. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + patchDomainEntry: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + entry_id: components["parameters"]["EntryId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DomainEntryCreate"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DomainEntry"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + listIpRangeEntries: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["IpRangeEntry"][]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + createIpRangeEntry: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IpRangeEntryCreate"]; + }; + }; + responses: { + /** @description Ресурс создан. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IpRangeEntry"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + deleteIpRangeEntry: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + entry_id: components["parameters"]["EntryId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Удалено. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + patchIpRangeEntry: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + entry_id: components["parameters"]["EntryId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IpRangeEntryCreate"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IpRangeEntry"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + postModuleRefresh: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Задача поставлена в очередь. */ + 202: { + headers: { + /** @description URL задачи `/v1/jobs/{job_id}` */ + Location?: string; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsyncJobAccepted"]; + }; + }; + /** @description Нет операции (например модуль `IP_RANGES` и no-op). */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Тип модуля не поддерживает refresh. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + exportModuleEntriesCsv: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description CSV-файл записей модуля. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/csv": string; + }; + }; + 404: components["responses"]["NotFound"]; + 422: components["responses"]["UnprocessableEntity"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + importModuleEntriesCsv: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "text/csv": string; + }; + }; + responses: { + /** @description Импорт завершён. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + imported: number; + module_type: string; + }; + }; + }; + 404: components["responses"]["NotFound"]; + 422: components["responses"]["UnprocessableEntity"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + listDohProfiles: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["DohProfile"][]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + createDohProfile: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DohProfileCreate"]; + }; + }; + responses: { + /** @description Ресурс создан. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DohProfile"]; + }; + }; + 422: components["responses"]["UnprocessableEntity"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + previewCdnSource: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + module_id: components["parameters"]["ModuleId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CdnPreviewRequest"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CdnPreviewResponse"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + 422: components["responses"]["UnprocessableEntity"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getDohProfile: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["DohProfileId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DohProfile"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + deleteDohProfile: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["DohProfileId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Удалено. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: components["responses"]["NotFound"]; + 409: components["responses"]["Conflict"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + patchDohProfile: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["DohProfileId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DohProfilePatch"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DohProfile"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + listCommunities: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["BgpCommunity"][]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + createCommunity: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BgpCommunityCreate"]; + }; + }; + responses: { + /** @description Ресурс создан. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BgpCommunity"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listCommunityPrefixes: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + limit?: number; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["CommunityId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: { + prefix: string; + source?: string; + }[]; + prefixes?: string[]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getCommunity: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["CommunityId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BgpCommunity"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + deleteCommunity: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["CommunityId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Удалено. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: components["responses"]["NotFound"]; + 409: components["responses"]["Conflict"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + patchCommunity: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["CommunityId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BgpCommunityPatch"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BgpCommunity"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + listPeers: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + speaker_id?: components["parameters"]["SpeakerFilter"]; + /** @description Опрос birdc на CP и GET /v1/agent/bird/protocols на репликах; обогащает session_state и connected_speaker_*. */ + live?: "1"; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["BgpPeer"][]; + next_cursor?: string | null; + has_more: boolean; + /** @description При live=1 — результат опроса каждой ноды (CP birdc + agent protocols). */ + live_speaker_poll?: components["schemas"]["LiveSpeakerPoll"][]; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + createPeer: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BgpPeerCreate"]; + }; + }; + responses: { + /** @description Ресурс создан. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BgpPeer"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listDiscoveredPeers: { + parameters: { + query?: { + status?: "pending" | "approved" | "rejected" | "all"; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["BgpPeerDiscovery"][]; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + approveDiscoveredPeer: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["PeerId"]; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["BgpPeerDiscoveryApprove"]; + }; + }; + responses: { + /** @description Пир создан, discovery → approved. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + peer?: components["schemas"]["BgpPeer"]; + discovery?: components["schemas"]["BgpPeerDiscovery"]; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + rejectDiscoveredPeer: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["PeerId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Discovery → rejected. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BgpPeerDiscovery"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getPeer: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["PeerId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BgpPeer"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + deletePeer: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["PeerId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Удалено / отключено. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + patchPeer: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["PeerId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BgpPeerPatch"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BgpPeer"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getBundleSigningPublicKey: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Ключ для env EVOBGP_BUNDLE_PUBKEY_BASE64 на реплике. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BundleSigningPublicKey"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listSpeakers: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + /** @description Live-опрос agent /v1/agent/health и BGP protocols на репликах; CP — local birdc. Обогащает каждый item полем `live`. */ + live?: "1"; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["BgpSpeaker"][]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + createSpeaker: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BgpSpeakerCreate"]; + }; + }; + responses: { + /** @description Ресурс создан. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BgpSpeaker"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getSpeaker: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["PeerId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BgpSpeaker"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + deleteSpeaker: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["PeerId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Удалено. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + patchSpeaker: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["PeerId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BgpSpeakerPatch"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BgpSpeaker"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + listRevisions: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + module_id?: components["parameters"]["RevisionModuleFilter"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Список ревизий с cursor-пагинацией; опциональный фильтр `module_id`. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ConfigRevision"][]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getRevisionPruneEstimate: { + parameters: { + query?: { + /** @description TTL в минутах; если не задан — из tenant settings (default 30d). */ + retention_minutes?: number; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Оценка. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RevisionPruneEstimate"]; + }; + }; + 422: components["responses"]["UnprocessableEntity"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + pruneRevisions: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["RevisionPruneRequest"]; + }; + }; + responses: { + /** @description Результат очистки. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RevisionPruneResult"]; + }; + }; + 403: components["responses"]["Forbidden"]; + 422: components["responses"]["UnprocessableEntity"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getRevision: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + revision_id: components["parameters"]["RevisionId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ConfigRevision"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getRevisionPrefixes: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + revision_id: components["parameters"]["RevisionId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["PrefixSnapshotItem"][]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getRevisionPreview: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + revision_id: components["parameters"]["RevisionId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Текст или структурированное представление - формат задаётся реализацией. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: unknown; + }; + "text/plain": string; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + postRevisionRollback: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + revision_id: components["parameters"]["RevisionId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Откат поставлен в очередь. */ + 202: { + headers: { + Location?: string; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsyncJobAccepted"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getRevisionDiff: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + revision_a: components["parameters"]["RevisionA"]; + revision_b: components["parameters"]["RevisionB"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RevisionDiff"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + postApply: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["ApplyRequest"]; + }; + }; + responses: { + /** @description Запрос принят, задача поставлена в очередь. */ + 202: { + headers: { + Location?: string; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsyncJobAccepted"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + postSpeakerApply: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["PeerId"]; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["ApplyRequest"]; + }; + }; + responses: { + /** @description Запрос принят, задача поставлена в очередь. */ + 202: { + headers: { + Location?: string; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsyncJobAccepted"]; + }; + }; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getBirdStatus: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BirdLocalStatus"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + postBirdReload: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Задача reload поставлена (если реализовано как async). */ + 202: { + headers: { + Location?: string; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsyncJobAccepted"]; + }; + }; + /** @description Reload выполнен синхронно (если так реализовано). */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Эндпоинт не используется в данной сборке. */ + 501: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listJobs: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + status?: components["parameters"]["JobStatusFilter"]; + /** @description Фильтр по виду задачи; точный перечень расширяем. Основные значения: `module_refresh`, `peer_reconcile`, `deploy_apply`, `revision_rollback`, `bird_reload`. */ + kind?: components["parameters"]["JobKindFilter"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Job"][]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getJob: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + job_id: components["parameters"]["JobId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Job"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + postJobCancel: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + job_id: components["parameters"]["JobId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Запрос отмены принят. */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Job"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getSpeakerLatestRevision: { + parameters: { + query?: never; + header?: never; + path: { + speaker_id: components["parameters"]["SpeakerId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["LatestRevisionPointer"]; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getSpeakerBundle: { + parameters: { + query?: never; + header?: never; + path: { + speaker_id: components["parameters"]["SpeakerId"]; + revision_id: components["parameters"]["RevisionId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Бинарное тело бандла. */ + 200: { + headers: { + "Content-Disposition"?: string; + [name: string]: unknown; + }; + content: { + "application/octet-stream": string; + }; + }; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + postNodeEnroll: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["NodeEnrollRequest"]; + }; + }; + responses: { + /** @description Enrollment записан (node_public_key и время в meta спикера при наличии ключа). */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["NodeEnrollResponse"]; + }; + }; + 422: components["responses"]["UnprocessableEntity"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getAuthSession: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AuthSession"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + listApiKeys: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ApiKey"][]; + next_cursor?: string | null; + has_more: boolean; + }; + }; + }; + 403: components["responses"]["Forbidden"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + createApiKey: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ApiKeyCreate"]; + }; + }; + responses: { + /** @description Ключ создан; token в ответе один раз. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiKeyCreated"]; + }; + }; + 403: components["responses"]["Forbidden"]; + 422: components["responses"]["UnprocessableEntity"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getApiKey: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["parameters"]["ApiKeyId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiKey"]; + }; + }; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + revokeApiKey: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["ApiKeyId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Отозван. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + patchApiKey: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["ApiKeyId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ApiKeyPatch"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiKey"]; + }; + }; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + rotateApiKey: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path: { + id: components["parameters"]["ApiKeyId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiKeyCreated"]; + }; + }; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getPostgresOverview: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PostgresOverview"]; + }; + }; + /** @description PostgreSQL backend не подключён. */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getPostgresQueries: { + parameters: { + query?: { + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PostgresQueriesResponse"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getPostgresLocks: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items?: { + [key: string]: unknown; + }[]; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getPostgresTables: { + parameters: { + query?: { + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items?: { + [key: string]: unknown; + }[]; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getPostgresRecommendations: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PostgresRecommendations"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getMonitoringCorrelation: { + parameters: { + query?: { + /** @description Window in minutes (max 1440). */ + window?: number; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + postPostgresVacuum: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["PostgresMaintenanceBody"]; + }; + }; + responses: { + /** @description Задача поставлена. */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsyncJobAccepted"]; + }; + }; + 403: components["responses"]["Forbidden"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + postPostgresVacuumAnalyze: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["PostgresMaintenanceBody"]; + }; + }; + responses: { + /** @description Задача поставлена. */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsyncJobAccepted"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + postPostgresAnalyze: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["PostgresMaintenanceBody"]; + }; + }; + responses: { + /** @description Задача поставлена. */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsyncJobAccepted"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + postPostgresReindex: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["PostgresMaintenanceBody"]; + }; + }; + responses: { + /** @description Задача поставлена. */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsyncJobAccepted"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + postPostgresCleanup: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PostgresMaintenanceBody"]; + }; + }; + responses: { + /** @description Задача поставлена. */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsyncJobAccepted"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listPostgresMaintenanceLogs: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items?: { + [key: string]: unknown; + }[]; + next_cursor?: string; + has_more?: boolean; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listMaintenancePolicies: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MaintenancePolicyList"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + createMaintenancePolicy: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MaintenancePolicy"]; + }; + }; + responses: { + /** @description Создано. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MaintenancePolicy"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getMaintenancePolicy: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["schemas"]["ResourceId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MaintenancePolicy"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + deleteMaintenancePolicy: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["schemas"]["ResourceId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Удалено. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + patchMaintenancePolicy: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["schemas"]["ResourceId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MaintenancePolicyPatch"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MaintenancePolicy"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getMaintenancePolicyHints: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + id: components["schemas"]["ResourceId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listMaintenanceConfigAudit: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items?: { + [key: string]: unknown; + }[]; + next_cursor?: string; + has_more?: boolean; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + postMaintenanceRun: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MaintenanceRunBody"]; + }; + }; + responses: { + /** @description Задача поставлена. */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsyncJobAccepted"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + postMaintenanceDryRun: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MaintenanceRunBody"]; + }; + }; + responses: { + /** @description Задача поставлена. */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AsyncJobAccepted"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listRuntimeLogFiles: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RuntimeLogFileList"]; + }; + }; + /** @description Runtime logs недоступны (не evobgp-all или каталог не настроен). */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getRuntimeLogTail: { + parameters: { + query?: { + lines?: number; + /** @description Альтернатива lines; при указании обоих — более строгий лимит. */ + bytes?: number; + /** @description Опциональный подстрочный фильтр (после чтения хвоста). */ + grep?: string; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + /** @description Basename файла (без пути). */ + filename: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RuntimeLogTail"]; + }; + }; + 404: components["responses"]["NotFound"]; + /** @description Runtime logs недоступны. */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + deleteRuntimeLogFile: { + parameters: { + query?: { + /** @description По умолчанию truncate. */ + mode?: components["schemas"]["RuntimeLogCleanupMode"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path: { + filename: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Файл очищен. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RuntimeLogCleanupResult"]; + }; + }; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + /** @description Файл превышает лимит 512 MiB. */ + 413: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description Runtime logs недоступны. */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + estimateRuntimeLogAutoCleanup: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RuntimeLogAutoEstimate"]; + }; + }; + /** @description FS API недоступен (не evobgp-all или нет volume). */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + runRuntimeLogAutoCleanup: { + parameters: { + query?: { + dry_run?: boolean; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RuntimeLogAutoRunResult"]; + }; + }; + /** @description FS API недоступен. */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listRuntimeLogCleanupAudit: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RuntimeLogCleanupAuditList"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listAuditLog: { + parameters: { + query?: { + /** @description Непрозрачный курсор из предыдущего ответа. */ + cursor?: components["parameters"]["Cursor"]; + /** @description Размер страницы (по умолчанию 50). */ + limit?: components["parameters"]["Limit"]; + /** @description Filter by action prefix/key (exact match). */ + action?: string; + severity?: components["schemas"]["AuditSeverity"]; + }; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AuditLogList"]; + }; + }; + 400: components["responses"]["BadRequest"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getSettings: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Settings"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + patchSettings: { + parameters: { + query?: never; + header?: { + /** @description Явный tenant (только супер-роли). Без заголовка tenant определяется по API-ключу. **Реализация v1:** заголовок в Go handlers не обрабатывается; tenant только из Bearer-токена (см. docs/access.md). */ + "X-Tenant-Id"?: components["parameters"]["TenantId"]; + /** @description Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта. */ + "Idempotency-Key"?: components["parameters"]["IdempotencyKey"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Settings"]; + }; + }; + responses: { + /** @description Успешно. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Settings"]; + }; + }; + 403: components["responses"]["Forbidden"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + getFirewallInstallContext: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FirewallInstallContext"]; + }; + }; + 403: components["responses"]["Forbidden"]; + default: components["responses"]["DefaultProblem"]; + }; + }; + firewallEnroll: { + parameters: { + query?: never; + header: { + "X-EvoBGP-Seed": string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + name: string; + hostname?: string; + client_token: string; + client_version?: string; + }; + }; + }; + responses: { + /** @description Client created (pending). */ + 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listFirewallClients: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items?: components["schemas"]["FirewallClient"][]; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + approveFirewallClient: { + parameters: { + query?: never; + header?: never; + path: { + id: components["schemas"]["ResourceId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Approved */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FirewallClient"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + revokeFirewallClient: { + parameters: { + query?: never; + header?: never; + path: { + id: components["schemas"]["ResourceId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Revoked */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {string} */ + status?: "revoked"; + }; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + listFirewallRules: { + parameters: { + query?: { + scope?: "tenant" | "client"; + client_id?: components["schemas"]["ResourceId"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + createFirewallRule: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + getFirewallBlocklist: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Blocklist */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FirewallBlocklist"]; + }; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; + firewallApplyReport: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + status?: string; + error?: string; + prefix_count?: number; + ip_count?: number; + /** Format: int64 */ + packets_dropped?: number; + /** Format: int64 */ + packets_accepted?: number; + kernel_method?: string; + source?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + default: components["responses"]["DefaultProblem"]; + }; + }; +} diff --git a/apps/web/src/types/api.ts b/apps/web/src/types/api.ts index 17929f4..89c2841 100644 --- a/apps/web/src/types/api.ts +++ b/apps/web/src/types/api.ts @@ -1,3 +1,13 @@ +/** + * Hand-written UI DTO aliases used by routes/queries. + * Machine SoT from OpenAPI: `./api.gen.ts` (regenerate: `pnpm --filter @evobgp/web run openapi:gen`). + * Prefer aligning new fields with schemas in docs/openapi.yaml + regenerating api.gen.ts. + */ +import type { components } from './api.gen' + +/** Re-export generated OpenAPI components for gradual adoption. */ +export type OpenAPISchemas = components['schemas'] + // ---- Pagination ---- export type Page = { items: T[] diff --git a/docs/README.md b/docs/README.md index 6b7b90a..ccdd0e3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,7 +7,7 @@ - **ИИ-агент / ассистент в репозитории** — [../AGENTS.md](../AGENTS.md): с чего начать чтение, карта `internal/` и `cmd/`, что не тащить в контекст. **Инженерные правила:** [../.cursor/rules/engineering.mdc](../.cursor/rules/engineering.mdc) (общие), [../.cursor/rules/web-shadcn.mdc](../.cursor/rules/web-shadcn.mdc) (Web UI), [../.cursor/rules/networking-bird.mdc](../.cursor/rules/networking-bird.mdc) (BIRD2/BGP/IP). - **Оператор / DevOps** — [quickstart.md](quickstart.md), [architecture.md](architecture.md), [access.md](access.md), [deploy/compose/docker-compose.yaml](../deploy/compose/docker-compose.yaml). - **Разработчик бэкенда или интегратор API** — [api.md](api.md), [access.md](access.md), [openapi.yaml](openapi.yaml), исходники маршрутов в `internal/httpapi/`. -- **Разработчик фронтенда** — [quickstart.md](quickstart.md) (раздел про `web/` и CORS), [api.md](api.md), [../web/README.md](../web/README.md). +- **Разработчик фронтенда** — [quickstart.md](quickstart.md) (раздел про `apps/web/` и CORS), [api.md](api.md), [ui-design-contract.md](ui-design-contract.md), [../apps/web/](../apps/web/). ## Оглавление @@ -22,6 +22,7 @@ | [access.md](access.md) | Выдача доступа: API-ключи, роли, нода, CORS | | [remote-speakers.md](remote-speakers.md) | Удалённые BGP-реплики: Traefik, agent sync, compose | | [releasing.md](releasing.md) | Автоматические релизы, Conventional Commits, CI | +| [ui-design-contract.md](ui-design-contract.md) | UI SoT: ReUI Frame, kit, KPI hybrid | | [openapi.yaml](openapi.yaml) | Источник правды по контракту API | | [OPENAPI-GITEA.md](OPENAPI-GITEA.md) | Как открыть HTML-документацию API (в т.ч. из Gitea) | | [evobgp-api-sketches.md](evobgp-api-sketches.md) | Ранний черновик идей API (контекст, не замена OpenAPI) | @@ -31,7 +32,8 @@ | Файл | Область | |------|---------| | [../.cursor/rules/engineering.mdc](../.cursor/rules/engineering.mdc) | Go, API, migrations, security, enforcement | -| [../.cursor/rules/web-shadcn.mdc](../.cursor/rules/web-shadcn.mdc) | SvelteKit, shadcn-svelte | +| [../.cursor/rules/web-shadcn.mdc](../.cursor/rules/web-shadcn.mdc) | React + TanStack + shadcn/ui + ReUI (`apps/web`, `packages/ui`) | +| [../.cursor/rules/reui-mcp.mdc](../.cursor/rules/reui-mcp.mdc) | ReUI PRO MCP workflow, surface `frame` | | [../.cursor/rules/networking-bird.mdc](../.cursor/rules/networking-bird.mdc) | BIRD2, BGP policy, IP/CIDR | | [../.cursor/rules/conventional-commits.mdc](../.cursor/rules/conventional-commits.mdc) | Conventional Commits (заголовок EN, тело RU) | diff --git a/docs/access.md b/docs/access.md index 73bb3eb..fb52d57 100644 --- a/docs/access.md +++ b/docs/access.md @@ -168,9 +168,11 @@ http://localhost:5173,http://127.0.0.1:5173,https://ui.example.com Разрешённые заголовки включают `Authorization`, `Content-Type`, `Idempotency-Key`, `Accept`, `X-Tenant-Id` (см. `internal/httpapi/cors.go`). -## Заголовок `X-Tenant-Id` (спецификация vs реализация) +## Заголовок `X-Tenant-Id` (решение: не реализован) -В [openapi.yaml](openapi.yaml) описано использование **`X-Tenant-Id`** для супер-ролей при работе от имени разных арендаторов. В **текущем коде** после аутентификации tenant берётся **только из записи API-ключа**; заголовок `X-Tenant-Id` **не переопределяет** tenant в обработчиках. До появления поддержки в коде не рассчитывайте на переключение tenant через этот заголовок. +**Решение (done):** заголовок **`X-Tenant-Id` не переключает tenant** в handlers и **не планируется** без отдельного ADR на супер-роли. +В [openapi.yaml](openapi.yaml) он помечен как reserved / unimplemented; tenant всегда из API-ключа, portal JWT claim или `EVOBGP_PORTAL_TENANT_ID`. +Клиенты **не должны** полагаться на `X-Tenant-Id`. ## Доступ к репозиторию и CI diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 2c1d1e5..1cb0445 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -19,7 +19,8 @@ info: **Роли API key** (матрица): `viewer`, `editor`, `operator`, `node`. Нода использует отдельные пути и ключ с ролью `node`. JWT permissions мапятся на ту же лестницу (`:read`→viewer, `:write`→editor, `:admin`→operator). - Заголовок `X-Tenant-Id` допускается только для супер-ролей (явный tenant); иначе tenant берётся из API-ключа / portal tenant env. + Заголовок `X-Tenant-Id`: **не реализован в handlers** (документировано в docs/access.md). + Tenant всегда из API-ключа / portal JWT claim / `EVOBGP_PORTAL_TENANT_ID`. Спецификация сохраняет заголовок как reserved future; не полагаться на него в клиентах. license: name: Proprietary identifier: LicenseRef-Proprietary diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e4e5d0..5c36856 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,6 +150,9 @@ importers: happy-dom: specifier: ^18.0.0 version: 18.0.1 + openapi-typescript: + specifier: ^7.13.0 + version: 7.13.0(typescript@5.9.3) tailwindcss: specifier: ^4.1.0 version: 4.3.2 @@ -941,6 +944,16 @@ packages: '@types/react': optional: true + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/config@0.22.0': + resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==} + + '@redocly/openapi-core@1.34.18': + resolution: {integrity: sha512-UyKIm0wTPw5BcY7Z2PkbK1Ma260um96LSBWXHrdSMe+ZV0EPMyDfAcUcjjm3qEiGST9OK/1TriekdPCZkn4Q3A==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@reduxjs/toolkit@2.12.0': resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} peerDependencies: @@ -1565,6 +1578,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + agent-base@9.0.0: resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==} engines: {node: '>= 20'} @@ -1583,6 +1600,10 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + ansi-escapes@7.3.0: resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} @@ -1658,6 +1679,9 @@ packages: brace-expansion@1.1.15: resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + brace-expansion@2.1.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} + brace-expansion@5.0.7: resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} engines: {node: 18 || 20 || >=22} @@ -1714,6 +1738,9 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -1780,6 +1807,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -2364,6 +2394,10 @@ packages: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + https-proxy-agent@9.1.0: resolution: {integrity: sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==} engines: {node: '>= 20'} @@ -2510,6 +2544,10 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2797,6 +2835,10 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -2937,6 +2979,12 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + openapi-typescript@7.13.0: + resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==} + hasBin: true + peerDependencies: + typescript: ^5.x + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3076,6 +3124,10 @@ packages: resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==} engines: {node: '>=4'} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + postcss@8.5.16: resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} engines: {node: ^10 || ^12 || >=14} @@ -3433,6 +3485,10 @@ packages: resolution: {integrity: sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==} engines: {node: '>=18'} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -3655,6 +3711,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js-replace@1.0.1: + resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3815,6 +3874,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -3896,7 +3958,7 @@ snapshots: '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3980,7 +4042,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.7 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -4249,7 +4311,7 @@ snapshots: '@eslint/config-array@0.21.2': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -4265,7 +4327,7 @@ snapshots: '@eslint/eslintrc@3.3.5': dependencies: ajv: 6.15.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -4347,7 +4409,7 @@ snapshots: dependencies: '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) dir-glob: 3.0.1 form-data: 4.0.6 fs-extra: 11.3.6 @@ -4577,6 +4639,29 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.1 + + '@redocly/config@0.22.0': {} + + '@redocly/openapi-core@1.34.18(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.22.0 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.3.0 + minimatch: 5.1.9 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + '@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1))(react@19.2.7)': dependencies: '@standard-schema/spec': 1.1.0 @@ -4682,7 +4767,7 @@ snapshots: conventional-changelog-writer: 8.4.0 conventional-commits-filter: 5.0.0 conventional-commits-parser: 6.4.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) import-from-esm: 2.0.0 lodash-es: 4.18.1 micromatch: 4.0.8 @@ -4698,7 +4783,7 @@ snapshots: dependencies: '@semantic-release/error': 4.0.0 aggregate-error: 3.1.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) execa: 9.6.1 lodash-es: 4.18.1 parse-json: 8.3.0 @@ -4714,7 +4799,7 @@ snapshots: '@octokit/plugin-throttling': 11.0.3(@octokit/core@7.0.6) '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) dir-glob: 3.0.1 http-proxy-agent: 9.1.0 https-proxy-agent: 9.1.0 @@ -4755,7 +4840,7 @@ snapshots: conventional-changelog-writer: 8.4.0 conventional-commits-filter: 5.0.0 conventional-commits-parser: 6.4.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) import-from-esm: 2.0.0 lodash-es: 4.18.1 read-package-up: 11.0.0 @@ -5086,7 +5171,7 @@ snapshots: '@typescript-eslint/types': 8.62.1 '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.62.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.4(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: @@ -5096,7 +5181,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@5.9.3) '@typescript-eslint/types': 8.62.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -5115,7 +5200,7 @@ snapshots: '@typescript-eslint/types': 8.62.1 '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3) '@typescript-eslint/utils': 8.62.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.4(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 @@ -5130,7 +5215,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@5.9.3) '@typescript-eslint/types': 8.62.1 '@typescript-eslint/visitor-keys': 8.62.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 10.2.5 semver: 7.8.5 tinyglobby: 0.2.17 @@ -5220,6 +5305,8 @@ snapshots: acorn@8.17.0: {} + agent-base@7.1.4: {} + agent-base@9.0.0: {} aggregate-error@3.1.0: @@ -5246,6 +5333,8 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-colors@4.1.3: {} + ansi-escapes@7.3.0: dependencies: environment: 1.1.0 @@ -5306,6 +5395,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.1.4: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.7: dependencies: balanced-match: 4.0.4 @@ -5368,6 +5461,8 @@ snapshots: chalk@5.6.2: {} + change-case@5.4.4: {} + char-regex@1.0.2: {} check-error@2.1.3: {} @@ -5445,6 +5540,8 @@ snapshots: color-name@1.1.4: {} + colorette@1.4.0: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -5575,9 +5672,11 @@ snapshots: date-fns@4.4.0: {} - debug@4.4.3: + debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 decimal.js-light@2.5.1: {} @@ -5735,7 +5834,7 @@ snapshots: ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -6050,7 +6149,7 @@ snapshots: http-proxy-agent@9.1.0: dependencies: agent-base: 9.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) proxy-agent-negotiate: 1.1.0 transitivePeerDependencies: - kerberos @@ -6061,10 +6160,17 @@ snapshots: quick-lru: 5.1.1 resolve-alpn: 1.2.1 + https-proxy-agent@7.0.6(supports-color@10.2.2): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + https-proxy-agent@9.1.0: dependencies: agent-base: 9.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) proxy-agent-negotiate: 1.1.0 transitivePeerDependencies: - kerberos @@ -6089,7 +6195,7 @@ snapshots: import-from-esm@2.0.0: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) import-meta-resolve: 4.2.0 transitivePeerDependencies: - supports-color @@ -6165,6 +6271,8 @@ snapshots: jiti@2.7.0: {} + js-levenshtein@1.1.6: {} + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -6387,6 +6495,10 @@ snapshots: dependencies: brace-expansion: 1.1.15 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.4 + minimist@1.2.8: {} ms@2.1.3: {} @@ -6452,6 +6564,16 @@ snapshots: dependencies: mimic-fn: 4.0.0 + openapi-typescript@7.13.0(typescript@5.9.3): + dependencies: + '@redocly/openapi-core': 1.34.18(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -6566,6 +6688,8 @@ snapshots: find-up: 2.1.0 load-json-file: 4.0.0 + pluralize@8.0.0: {} + postcss@8.5.16: dependencies: nanoid: 3.3.15 @@ -6796,7 +6920,7 @@ snapshots: '@semantic-release/release-notes-generator': 14.1.1(semantic-release@25.0.5(typescript@5.9.3)) aggregate-error: 5.0.0 cosmiconfig: 9.0.2(typescript@5.9.3) - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) env-ci: 11.2.0 execa: 9.6.1 figures: 6.1.0 @@ -6940,6 +7064,8 @@ snapshots: make-asynchronous: 1.1.0 time-span: 5.1.0 + supports-color@10.2.2: {} + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -7094,6 +7220,8 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js-replace@1.0.1: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -7146,7 +7274,7 @@ snapshots: vite-node@3.2.4(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0): dependencies: cac: 6.7.14 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 7.3.6(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0) @@ -7189,7 +7317,7 @@ snapshots: '@vitest/spy': 3.2.6 '@vitest/utils': 3.2.6 chai: 5.3.3 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) expect-type: 1.4.0 magic-string: 0.30.21 pathe: 2.0.3 @@ -7257,6 +7385,8 @@ snapshots: yallist@3.1.1: {} + yaml-ast-parser@0.0.43: {} + yargs-parser@20.2.9: {} yargs-parser@21.1.1: {} diff --git a/scripts/check-openapi-gen.sh b/scripts/check-openapi-gen.sh new file mode 100644 index 0000000..51b3e90 --- /dev/null +++ b/scripts/check-openapi-gen.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Fail if apps/web/src/types/api.gen.ts is stale vs docs/openapi.yaml. +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT/apps/web" +tmp="$(mktemp)" +trap 'rm -f "$tmp"' EXIT +pnpm exec openapi-typescript ../../docs/openapi.yaml -o "$tmp" +if ! diff -q src/types/api.gen.ts "$tmp" >/dev/null; then + echo "api.gen.ts is stale — run: pnpm --filter @evobgp/web run openapi:gen" >&2 + diff -u src/types/api.gen.ts "$tmp" | head -n 80 >&2 || true + exit 1 +fi +echo "api.gen.ts is up to date"