Newer
Older
gn-synapse / docs / 09-integration-guide.md

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:

pip install "git+https://git.gnexus.space/git/root/gn-synapse-client-py.git@v0.1.0"
from gnexus_synapse import SynapseClient
sync = SynapseClient()                      # конфиг из SYNAPSE_*
async_client = AsyncSynapseClient()         # для FastAPI-контекста

PHP (composer, repositories vcs — см. README либы):

$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-<id>) чтобы не спамить; 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. Приём s2s-доставки — тот же SDK

Большинство сервисов-приёмников и шлют события (§3) — поэтому приёмная половина живёт в том же SDK, без второй зависимости. Одна точка входа verify_webhook() проверяет подпись (одна схема на всю экосистему — зеркало WebhookSignature.php gnexus-auth и app/signature.py) и распарсивает конверт:

Python:

from gnexus_synapse import verify_webhook

@router.post("/webhooks/synapse")
async def synapse_webhook(request: Request):
    env = verify_webhook(await request.body(), request.headers, S2S_SECRET)
    # env["event_id"], env["subject"], env["action"], env["payload"], ...

PHP:

use GNexus\Synapse\Webhook\WebhookVerifier;
$envelope = WebhookVerifier::verify($r->getContent(), $r->headers->all(), $secret);

Всё, что не прошло (нет подписи, чужой секрет, replay — тело старше 300 с, не-JSON), ловится одним SynapseWebhookError / InvalidWebhookException. Endpoint обязан возвращать 2xx быстро (первая попытка — сразу при маршрутизации; провал → ретраи Synapse 30 с → 2 м → 10 м → 30 м) и быть идемпотентным или не бояться повторов. Секрет per-target — S2S_SECRET_<token_ref> в env приёмника (+ gnexus-creds); в Synapse цель создаётся с token_ref (админка/MCP target_create). Заголовки-контекст: X-Gnexus-Event-Type = <source>.<subject>.<action>, X-Synapse-Source. Полная спецификация подписи — docs/05 → «Доставка s2s».

8. Анти-паттерны (каждое из них уже решено на сервере — не повторять в сервисе)

  • Локальные очереди/ретраи отправки — Synapse отвечает 202 мгновенно и сам ретраит доставки (backoff 30 с → 2 м → 10 м → 30 м, 5 попыток); клиент ничего этого не делает.
  • Опрос status() «до delivered» в бизнес-коде — события fire-and-forget; нужен мониторинг — это дежурные метрики Synapse и админка, не поллинг.
  • Формирование routing'а в коде (какой канал, кто получатель) — маршрутизация живёт правилами админки; сервис знает только «что случилось».
  • Своя таблица «доставлено/не доставлено» — Delivery Log у Synapse с полным статусами и ретраем.
  • Валидировать тройку на клиенте и молча глотать в рантайме: клиентская валидация (statusCode = null) — ошибка кода, тесты; 422 сервера — вопрос регистрации, админ.
  • Своя рассылка по пользователям внутри сервиса (email/телеграм из сервиса) — это и есть замена Synapse; если канала не хватает, расширяется канальный список Synapse.