diff --git a/README.md b/README.md index 23949df..d9be5ff 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ signature.py HMAC подпись/проверка вебхуков (s2s + входящие gnexus-auth) worker/ Celery: celery_app, tasks, senders (s2s) alembic/ миграции (env.py читает DATABASE_URL из .env) -docs/ docs/04 — схема БД, docs/05 — контракт Ingestion API, docs/06 — настройки+PWA, docs/07 — MCP, docs/08 — рантбук развёртывания (для ИИ-агента) +docs/ docs/04 — схема БД, docs/05 — контракт Ingestion API, docs/06 — настройки+PWA, docs/07 — MCP, docs/08 — рантбук развёртывания (для ИИ-агента), docs/09 — гайд интеграции сервиса в Synapse frontend/ Vue 3 SPA админки (сборка кладётся в spa_static/ образа) docker/entrypoint.sh режимы api (миграции+uvicorn) / worker (celery) docker-compose.yml api, worker, postgres, redis diff --git a/docs/05-ingestion-api.md b/docs/05-ingestion-api.md index 8da170e..c7e0e82 100644 --- a/docs/05-ingestion-api.md +++ b/docs/05-ingestion-api.md @@ -90,6 +90,7 @@ `statusCode = null` значит ошибку кода, а не транспорта; ошибки Synapse приходят типизированными с HTTP-кодом. Ретраев и локальных очередей у клиента нет — Synapse принимает с 202 мгновенно. Контракт остаётся в этом документе: curl — нормативный способ проверки. +Порядок внедрения сервиса, паттерны отправки и анти-паттерны — `docs/09-integration-guide.md`. ## Статус события diff --git a/docs/09-integration-guide.md b/docs/09-integration-guide.md new file mode 100644 index 0000000..0f47189 --- /dev/null +++ b/docs/09-integration-guide.md @@ -0,0 +1,150 @@ +# 09 · Гайд интеграции: сервис шлёт события в Synapse + +Для разработчика (или ИИ-агента), подключающего сервис экосистемы (bugtrail, +gntodo, smart-home, gnexus-auth, Navi…) к хабу уведомлений. Контракт приёма — +`docs/05-ingestion-api.md`; этот документ — порядок внедрения и паттерны. +Клиентские библиотеки: `gn-synapse-client-py` / `gn-synapse-client-php` +(README последних — полный справочник API). + +## 0. Кто что делает (роли) + +- **Владелец/админ Synapse** (админка или MCP-тулы, docs/07): создаёт + источник, выдаёт API-ключ (plaintext показывается один раз), регистрирует + типы (тройки `source,subject,action`), цели и правила. +- **Сервис** (потребитель): хранит ключ в своём env (и в gnexus-creds), + шлёт события через клиентскую библиотеку, документирует свои тройки. + +Админка — единственный способ конфигурировать Synapse: тип не зарегистрирован → +`422` на каждое событие. Подключение сервиса начинается **не с кода, а с +регистрации**: сначала в Synapse, потом код. + +## 1. Чеклист подключения нового сервиса + +1. Согласовать с владельцем: + - имя источника (`^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$`, 1..64, латиница + строчными) — обычно имя репо/сервиса (`bugtrail`, `gntodo`, `navi-rei`); + - список трой `(subject, action)` — что сервис будет сообщать (см. §4), + с приоритетом по умолчанию каждой тройки; + - кому и куда: целевые каналы (`internal_log`, `push`, `telegram`, + s2s в Navi) и «о ком» — нужен ли `payload.user_id`. +2. Владелец: `source_create` (или §админка → источники) → `key_issue` + → plaintext-ключ **в .env сервиса и gnexus-creds, нигде больше**; + `type_register` для каждой тройки; `rule_create`/`rule_set`. +3. Вставить в env сервиса: + ``` + SYNAPSE_URL=https://synapse.gnexus.space # или :8013 в LAN + SYNAPSE_API_KEY=syn_... + SYNAPSE_DEFAULT_SOURCE=<имя источника> + ``` +4. Код: установить либу, инжектнуть в DI (один инстанс на процесс), + шлать через `emit`/`send`. Быстрая проверка после первого отправа — + админка → поток (лог доставок), или `send_test_event` (MCP). + +## 2. Установка и init + +Python: + +```bash +pip install "git+https://git.gnexus.space/git/root/gn-synapse-client-py.git@v0.1.0" +``` + +```python +from gnexus_synapse import SynapseClient +sync = SynapseClient() # конфиг из SYNAPSE_* +async_client = AsyncSynapseClient() # для FastAPI-контекста +``` + +PHP (composer, repositories vcs — см. README либы): + +```php +$client = new \GNexus\Synapse\SynapseClient( + \GNexus\Synapse\Config\SynapseConfig::fromGlobals(), + new \GuzzleHttp\Client(['timeout' => 10.0]), + new \GuzzleHttp\Psr7\HttpFactory(), + new \GuzzleHttp\Psr7\HttpFactory(), +); +``` + +Правило: **один клиент-инстанс на процесс** (переиспользует http-соединения); +при ручном создании — таймаут обязательно (`SYNAPSE_TIMEOUT` и +`timeoutSeconds` у Guzzle-транспорта), иначе вечный вис при лежащем Synapse. + +## 3. Четыре паттерна отправки (какой когда) + +| Паттерн | Когда | Возврат/падение | +|---|---|---| +| `emit(source?, subject, action, …)` | событие «потери не страшно», основной путь большинства сервисов | `Event\|None`; `SynapseError`/транспорт → warning в лог, не бросаются | +| `send(...)` | событие, чья потеря = инцидент (уведомление о падении монитора, инцидент безопасности) | бросает типизированные исключения (см. README) — у caller'а есть ретри- или эскалационная политика | +| `send_batch([...])` | прогон «сотни мелких» (импорт, массовая рассылка) | `BatchResult.accepted/rejected`; локально битые и серверные отклонения — по индексам, 202 и 422 парсятся одинаково | +| `status(event_id)` | отладка/приёмочный тест нового источника; в бизнес-коде обычно не нужен | `EventStatus` со списком доставок | +| `health()` / `ready()` | readiness-probe сервиса, старт-самоtest | без ключа | + +Конвенции полей: + +- `priority` — `low|normal|high|critical`; critical — только действительно + инциденты (обходит throttle правил). +- `dedup_key` — на «повторяющиеся в окне» события (`ping-<день>`, + `issue-`) чтобы не спамить; **best-effort** (24ч-окно, повтор вернёт id + первого события с `deduplicated=true`) — не строить бизнес-логику на нём. +- `payload.user_id` — непустая строка (uuid gnexus-auth): это «о ком событие» + (личный лог, личный push). Число/пустая → предупреждение, адресные + доставки пойдут `skipped`. +- `ttl_seconds` — забыть событие старше N сек (актуальность), по умолчанию окно + от настроек Synapse. +- `scheduled_at` — резерв контракта, сервер пока не обрабатывает. + +## 4. Как называть тройки (subject/action) + +Тройки регистрируются админом один раз каждая — расширение списка это +правка реестра, а не кода; берите предсказуемую схему: + +- subject — сущность сервиса (`task`, `issue`, `container`, `user`, `auth`); +- action — то, что случилось (`created`, `completed`, `down`, `password_changed`, + `login_failed`); глаголы в прошедшем времени или существительные — не повелительное. + +Шаблон события держите в репо сервиса (или в описании сервиса gnexus-book), +чтобы при регистрации нового типа не догадываться о смысле payload'а. + +## 5. Что уже должно быть в сервисе после интеграции (checklist ревью) + +- [ ] Клиент инжектится, не создается в каждом вызове; таймаут задан. +- [ ] Обычные события — `emit`; критичные — `send` + `try/catch` с политикой + (лог + ручной ретри/эскалация — не автомедицина, Synapse сам ретраит доставки по каналам). +- [ ] Ключ — только в env (и копия в gnexus-creds), не в коде/конфиге репозитория. +- [ ] `dedup_key` не в ролях бизнес-идемпотентности. +- [ ] `payload.user_id` — uuid gnexus-auth (если адресные доставки нужны). +- [ ] Все шлющиеся тройки зарегистрированы (в админке Synapse видно типы; + `422` на проде = забыли тип — добавить `type_register`, не «обходить» в коде). +- [ ] Свои тройки задокументированы (таблица subject × action × priority × + «что в payload»). + +## 6. Старты и приёмка (для агента) + +Быстрая приёмка после первого деплоя интеграции: + +1. В либе клиента — интеграционные смоки: + - py: `examples/plain/smoke.py`, php: `examples/plain-php/smoke.php` + (требуют источник `libtest` и тип; для реального сервиса подставьте своё имя). +2. Через MCP: `source_list` → `key_issue` → `type_register` → + `send_test_event` (прогон правила без кода сервиса) → `deliveries_list`. +3. Отрицательные проверки (дешёвые и полезные): неверное имя источника → + локальная валидация клиента до HTTP; не зарегистрированная тройка → 422; + отправка с чужим/отозванным ключом → 401; чужой event_id → 404 (`status`). + +## 7. Анти-паттерны (каждое из них уже решено на сервере — не повторять в сервисе) + +- Локальные очереди/ретраи отправки — Synapse отвечает 202 мгновенно и сам + ретраит доставки (backoff 30 с → 2 м → 10 м → 30 с, 5 попыток); клиент + ничего этого не делает. +- Опрос `status()` «до delivered» в бизнес-коде — события fire-and-forget; + нужен мониторинг — это дежурные метрики Synapse и админка, не поллинг. +- Формирование routing'а в коде (какой канал, кто получатель) — маршрутизация + живёт правилами админки; сервис знает только «что случилось». +- Своя таблица «доставлено/не доставлено» — Delivery Log у Synapse с полным + статусами и ретраем. +- Валидировать тройку на клиенте и молча глотать в рантайме: клиентская + валидация (`statusCode = null`) — ошибка кода, тесты; `422` сервера — + вопрос регистрации, админ. +- Своя рассылка по пользователям внутри сервиса (email/телеграм из сервиса) — + это и есть замена Synapse; если канала не хватает, расширяется канальный + список Synapse. \ No newline at end of file