diff --git a/CLAUDE.md b/CLAUDE.md index 04207c7..4f87808 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -65,6 +65,7 @@ - **Многопользовательность (2026-10-03)**: «о ком событие» — конвенция `payload.user_id` (= `sub` gnexus-auth; контракт v1 цел, получателей в конверте нет). Что видит пользователь — решают правила: целевые каналы `user` (личный лог) и `push` (web-push) без цели, адресат из `payload.user_id`; правило без них — событие «только для админа». Привязка «user_id ↔ адрес канала» для push — самопользовательская: браузер подписывается в разделе «Настройки» (docs/06). Остался открытый вопрос №3 по tg chat_id/email opt-in. В OAuth-колбэке токен выдаётся любому аутентифицированному (revoke не-админу больше не нужен). - **Настройки + PWA (2026-10-03)**: редактируемые параметры — реестр `app/settings_registry.py`, дефолт из `.env`, оверрайды в таблице `app_settings` через админку (применяются без перезапуска; docs/06). Секреты (VAPID private, тг-токен, SMTP-пароль) — в БД write-only: записываются через UI, никогда не возвращаются API, читает только воркер; исключение из правила «секреты только в .env», согласовано владельцем. Канал `push` реализован: pywebpush (DER base64url ключи, 404/410 → подписка удаляется), таблица `push_subscriptions`, PWA (manifest + sw.js) в дистрибутиве SPA. - **MCP + архив вместо удаления (2026-10-03)**: embedded MCP-сервер (FastMCP, streamable-http) на `/mcp` того же контейнера api; доступ — статический Bearer `MCP_TOKEN` из `.env` (пусто → /mcp не монтируется). Тулы (31, docs/07) переиспользуют admin-route функции напрямую (та же валидация, автор — синтетический superadmin): источники + **выдача API-ключей клиентам** (plaintext один раз), типы, цели, правила, поток, настройки; `send_test_event` — контрольный прогон правила. Удаление справочников (sources/types/targets/rules) = архив: `deleted_at`, restore-эндпоинты, списки с `include_archived`, create поверх архива → 409 с подсказкой; архивный источник/тип не принимают события (401/422), архивная цель — доставки `skipped` с аудитом; push-подписки — исключение, удаляются физически. docs/07-mcp.md. +- **Клиентские библиотеки (2026-10-04)**: тонкие клиенты (без ретраев, очередей, framework-драйверов), весь повторяемый шаблон внутри: env-конфиг (`SYNAPSE_URL`, `SYNAPSE_API_KEY`, `SYNAPSE_TIMEOUT`, `SYNAPSE_DEFAULT_SOURCE` — имена фиксированы экосистемно), сборка+локальная валидация конверта, типизированные исключения, fire-and-forget `emit`, batch, статус. Репы: `gn-synapse-client-py` (PyPI-имя `gnexus-synapse`, зеркалогаут gnexus-gauth) и `gn-synapse-client-php` (Composer `gnexus/synapse-client`, PSR-4 `GNexus\Synapse\`, PSR-18/PSR-17 инжектный транспорт); установка через git.gnexus.space по тегу v0.1.0. Локальный `ValidationException` с statusCode=null — ошибка кода; `default_source` смягчает 403 анти-спуфинга. docs/05 → «Клиентские библиотеки». ## Инфраструктура diff --git a/README.md b/README.md index 0dda0b6..23949df 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,14 @@ # Дедуп: тот же dedup_key в окне (DEDUP_WINDOW_SECONDS, дефолт 24ч) → id первого. ``` +В сервисах вместо curl — **клиентские библиотеки** (`gn-synapse-client-{py,php}`, +доки в `docs/05-ingestion-api.md` → «Клиентские библиотеки»): + +```python +from gnexus_synapse import SynapseClient # pip install git+.../gn-synapse-client-py.git +SynapseClient().emit("monitoring", "container", "down", payload={"container": "api"}) +``` + ### Проверка воркера ```bash diff --git a/docs/05-ingestion-api.md b/docs/05-ingestion-api.md index a668131..8da170e 100644 --- a/docs/05-ingestion-api.md +++ b/docs/05-ingestion-api.md @@ -61,6 +61,36 @@ Валидация конверта жёсткая и мгновенная, payload — мягкая. Схема «принял мгновенно → разберусь» ломается, если требовать на входе знание всех payload-схем. +## Клиентские библиотеки + +Вместо curl/HTTP-шаблона в каждом сервисе — тонкие клиенты (репозитории `gn-synapse-client-py` +и `gn-synapse-client-php` на git.gnexus.space): env-конфиг, сборка и **локальная** валидация +конверта, типизированные исключения, fire-and-forget `emit`, batch, статус. Env: `SYNAPSE_URL`, +`SYNAPSE_API_KEY`, `SYNAPSE_TIMEOUT`, `SYNAPSE_DEFAULT_SOURCE` (смягчает 403 анти-спуфинга). + +Python (`pip install "git+https://git.gnexus.space/git/root/gn-synapse-client-py.git@v0.1.0"`): + +```python +from gnexus_synapse import SynapseClient +client = SynapseClient() # из env +client.emit("bugtrail", "test", "failed", payload={"user_id": "..."}) +event = client.send("bugtrail", "test", "ping", dedup_key="p-1") +client.status(event.id) +``` + +PHP (composer `gnexus/synapse-client`, PSR-18/PSR-17 транспорт инжектный — Guzzle и т.п.): + +```php +$event = $client->send('bugtrail', 'test', 'failed', 'high', + ['user_id' => $sub], dedupKey: "failed-{$sub}"); +$event = $client->emit(null, 'user', 'password_changed', payload: ['user_id' => $sub]); +``` + +Важно: клиент валидирует конверт локально — его `ValidationException` с +`statusCode = null` значит ошибку кода, а не транспорта; ошибки Synapse приходят +типизированными с HTTP-кодом. Ретраев и локальных очередей у клиента нет — Synapse принимает +с 202 мгновенно. Контракт остаётся в этом документе: curl — нормативный способ проверки. + ## Статус события `GET /api/v1/events/{id}` — только источнику события, чужой id → `404`: