Skip to content

ТЗ — Шина интеграций (cms/integrations-bus)

Слой: 🔵 инфра-модуль · Зрелость доноров: ★ · Донор: — Статус: ТЗ к разработке

Назначение и возможности

Единый слой коннекторов к внешним системам на базе saloonphp/saloon: все внешние модули (cms/integration-1c, cms/integration-crm, платёжные шлюзы и т.п.) работают только через эту шину, не обращаются к внешним API напрямую. Даёт единую точку контроля ретраев, circuit breaker и журнала вызовов.

  • Контракт ConnectorContract — единый интерфейс для всех внешних коннекторов
  • Хранение кредов только как ссылки на .env-переменные, не в БД
  • Очереди для асинхронных вызовов внешних API
  • Ретраи с экспоненциальным backoff на временные ошибки
  • Circuit breaker — временное отключение коннектора после серии сбоев
  • Журнал вызовов (запрос/ответ/статус/время) для диагностики
  • Статусы коннекторов (активен/деградация/отключён) в едином дашборде Filament
  • Rate-limit исходящих вызовов на коннектор

Зависимости и выключение

requires: ядро

Поведение при выключении: коннекторы, зависящие от шины, перестают выполнять вызовы и переходят в режим заглушки (модуль-потребитель сам решает, как деградировать) — данные не теряются, только не синхронизируются с внешней системой.

Стоимость внешних API: платёжные шлюзы и часть CRM-тарифов считают вызовы (лимит в пакете/ месяц). ConnectorContract допускает опциональную декларацию cost_per_call/тарифного лимита провайдера при регистрации (необязательное поле); значение отображается в Filament-дашборде рядом со статистикой вызовов (см. «Компоненты») — виден расход, не только факт сбоя.

Модель данных

ТаблицаКлючевые поляПримечание
cms_integration_connectorsid, code, class, status, credentials_ref, rate_limit_per_minuteреестр коннекторов, ссылки на креды в .env
cms_integration_call_logid, connector_id, request (json), response (json), status_code, duration_ms, created_atжурнал вызовов
cms_integration_circuit_stateid, connector_id, state, opened_at, failure_countсостояние circuit breaker

Заметки: status/state — PHP Enum; request/response — JSONB с касом 'array' (диагностический журнал, GIN не нужен); FK connector_id в дочерних таблицах — constrained() + index(); call_log — журнальная таблица, BRIN по created_at при росте.

ПДн-паспорт: cms_integration_call_log.request/.response может содержать ПДн клиента транзитом (контакты, адрес доставки, платёжные детали, передаваемые во внешнюю CRM/1С/платёжку). Срок хранения — integrations-bus.call_log_retention_days (30 дней). Шина не индексирует журнал по субъекту ПДн (не знает структуру чужого payload) — «выгрузить всё по субъекту»/«забыть по запросу» (152-ФЗ) остаётся ответственностью модуля-потребителя; шина как транзитное хранилище чужих ПДн обязана предоставить хук «найти и анонимизировать записи журнала по заданному предикату» (connector_code + JSON-путь + значение), которым пользуется ядро/потребитель.

Конверт сообщения очереди (versioned envelope): каждое сообщение несёт schema_version — версию формата payload. При смене формата между релизами потребителя старые необработанные сообщения не должны ломать новый обработчик: коннектор обязан читать минимум текущую и предыдущую версию на момент апгрейда (аналогия с semver payload'а модуля, §3 стандарта). Для compacted: true (см. «Фоновая работа») ключ дедупликации — entity_type + entity_id, не включает schema_version: последнее состояние заменяет предыдущее независимо от версии схемы.

Входные и выходные данные

Входы

ИсточникДанные/поляЧем валидируется
Сервис-вызов потребителя (requires: cms/integration-1c, cms/integration-crm, платёжные шлюзы, cms/import по suggests)connector_code, метод/эндпоинт коннектора, payload запроса + schema_version, режим (sync/async), ключ идемпотентности (event_id/entity_id)ConnectorContract — типизированная сигнатура метода коннектора; whitelist зарегистрированных connector_code (незарегистрированный код отклоняется доменным исключением)
Регистрация коннектора (boot провайдера модуля-потребителя)code, class, rate_limit_per_minute, credentials_ref, compacted (bool, опц.), cost_per_call (опц.)класс обязан реализовывать ConnectorContract; credentials_ref проверяется на существование переменной в .env при регистрации (fail-fast, не при первом вызове); compacted включает режим compacted queue для этого типа вызова (см. «Фоновая работа»)
Admin API: сброс circuit breaker (POST .../connectors/{code}/reset)code (path)FormRequest, permission integrations-bus.manage, коннектор обязан существовать (404 иначе)
Admin API: фильтры журнала (GET .../log)filter[connector_code], filter[status_code], sort, cursorFormRequest whitelist (конвенция API ядра)
Ответ внешней системы (через saloon-коннектор)HTTP-статус, тело ответа, время выполненияконнектор классифицирует ответ (успех / временная ошибка / постоянная ошибка) перед возвратом в шину

Выходы

ПотребительДанныеФормат
Модуль-потребитель (sync-вызов)результат вызова коннектораResponse/DTO коннектора (типизированный класс saloon-ответа)
Модуль-потребитель (async-вызов)результат в его собственной callback-джобе, поставленной вместе с задачейpayload по контракту потребителя (шина не заглядывает внутрь)
Filament-дашбордстатусы коннекторов, журнал вызововтаблицы админки
cms/health и подписчики событийConnectorCallFailed, ConnectorCircuitOpened, ConnectorCircuitClosedpayload события (канал 1)
Внешняя система (CRM/1С/шлюз/…)исходящий HTTP-запрос коннектораформат конкретного внешнего API (вне контроля шины)

Whitelist-принцип: вызов с незарегистрированным connector_code, коннектором без реализации ConnectorContract или попытка прямого HTTP к внешней системе в обход шины — отклоняется/запрещён (см. «Назначение и возможности», §11 стандарта).

Настройки (группа integrations-bus)

КлючТипДефолтaffectsPageCacheОписание
integrations-bus.retry_attemptsint3нетЧисло ретраев на временную ошибку
integrations-bus.retry_backoff_secondsint5нетБазовая задержка экспоненциального backoff
integrations-bus.circuit_breaker_thresholdint5нетЧисло сбоев подряд до открытия circuit breaker
integrations-bus.circuit_breaker_cooldown_minutesint10нетВремя до повторной попытки после открытия
integrations-bus.call_log_retention_daysint30нетХранение журнала вызовов
integrations-bus.call_timeout_secondsint15нетТаймаут одного внешнего вызова
integrations-bus.dead_letter_ttl_daysint14нетХранение записей в dead-letter до автоочистки
integrations-bus.allowed_domainsarray[]нетWhitelist доменов для исходящих вызовов (пусто = ограничение только на уровне коннектора)
integrations-bus.max_logged_payload_kbint64нетЛимит размера request/response, логируемого в журнал (см. «Крайние случаи»)
integrations-bus.dead_letter_max_sizeint10000нетМаксимум записей в dead-letter; при достижении старые не теряются молча — помечаются expired по dead_letter_ttl_days с алертом (см. «Крайние случаи»)
integrations-bus.compaction_enabledbooltrueнетОбщий рубильник режима compacted queue (§«Фоновая работа»); коннекторы с compacted: true игнорируют флаг только при явном отключении здесь
integrations-bus.kill_switchboolfalseнетАварийная остановка всех исходящих вызовов шины без выключения модуля; включённые вызовы уходят в dead-letter с error_type=kill_switch (см. «Крайние случаи»)

API

МетодПутьДоступНазначение
GET/api/v1/admin/integrations/connectorsadmin (integrations-bus.view)Список коннекторов и их статусы
GET/api/v1/admin/integrations/logadmin (integrations-bus.view)Журнал вызовов с фильтрами
POST/api/v1/admin/integrations/connectors/{code}/resetadmin (integrations-bus.manage)Сброс circuit breaker вручную

Журнал — keyset-пагинация (не OFFSET).

Конверт ошибок — по конвенции ядра (ревизия ядра 14.07.2026, п.1): {"message": …, "code": "connector_not_found"|"circuit_already_closed"|"validation_failed"|…, "errors": {…}}. connector_not_found — сброс circuit для незарегистрированного code; circuit_already_closed — повторный сброс уже закрытого circuit breaker (идемпотентный, но помечен кодом, не тихий 200).

Компоненты

Filament: дашборд статусов коннекторов, журнал вызовов, расход по cost_per_call там, где коннектор его декларирует (см. «Зависимости и выключение»). Команды: cms:integrations:status --json, cms:integrations:reset-circuit --json.

Демо-сидеры: не применимо — у модуля нет собственных блоков/виджетов, только служебный Filament-дашборд; для playground-скриншотов допустим необязательный сидер с 2-3 фиктивными записями журнала тестового коннектора, в приёмку не входит.

События и обмен

СобытиеКогдаPayload
ConnectorCallFailedвызов внешнего API завершился ошибкойconnector_code, error, attempt
ConnectorCircuitOpenedcircuit breaker открылся после серии сбоевconnector_code, failure_count
ConnectorCircuitClosedcircuit breaker закрылся, коннектор восстановленconnector_code

FilterBus не используется. Provides: сервис-вызов «шина коннекторов» — точка, через которую cms/integration-1c, cms/integration-crm, платёжные шлюзы и cms/import (по suggests) выполняют внешние вызовы, не реализуя собственный HTTP-клиент.

cms/integrations-bus сам является одним из двух внешних каналов обмена (data-exchange.md): внутри CMS модули обращаются к нему через requires (канал 4), наружу он выходит собственным HTTP-транспортом (saloon), минуя каналы 1–5.

Таблица взаимодействий

Сущность/модульКаналНаправлениеЧто происходит
cms/integration-1crequires (канал 4)inвызывает шину для отправки/получения данных 1С через свой коннектор
cms/integration-crmrequires (канал 4)inвызывает шину для операций с CRM через свой коннектор
Платёжные шлюзы (cms/pay-gateways-*)provides-контракт payment-gateway (канал 3) у себя + requires к шине (канал 4)inиспользуют шину как транспортный слой для HTTP-вызовов платёжного провайдера
cms/import (suggests)requires (канал 4, опционально)inиспользует шину вместо собственного HTTP-клиента при импорте из внешних источников
Внешняя система (CRM/1С/шлюз/ERP)внешний канал, не из пяти — HTTP через saloonoutфактический вызов; шина — единственная точка выхода наружу
cms/healthсобытие ConnectorCallFailed/ConnectorCircuitOpened/ConnectorCircuitClosed (канал 1)outагрегирует сбои и отставания в общий health-отчёт
cms/audit (если включён)событие (канал 1)outфиксирует сбои и открытия circuit breaker в аудит-лог
Очередь integrations-busканал 5out (внутри себя)асинхронная обработка вызовов, ретраи, circuit breaker на уровне джобы
Ядро (SettingsStore)сервис-вызов контракта ядраinчтение своей группы настроек integrations-bus (0 запросов на горячем пути, кеш группы)

Фоновая работа

Очередь integrations-bus для всех асинхронных вызовов внешних API; ретраи с backoff и circuit breaker — на уровне джобы. Внешние вызовы — только из очереди, кроме синхронных с явным таймаутом и graceful fallback для потребителей, которым нужен немедленный ответ.

Компакция очереди (compacted queue). По опыту Bitrix (bitrix-rest-lessons §6): offline-события там хранятся не журналом, а последним состоянием (1000 изменений сделки = 1 запись). Для потока «остатки/цены → маркетплейсы» шина реализует режим «compacted queue»: постановка вызова с ключом entity_type + entity_id заменяет предыдущий необработанный вызов с тем же ключом — дубли не накапливаются, к моменту обработки в очереди лежит одна запись с актуальным состоянием.

  • Режим включается на уровне регистрации коннектора/типа вызова: compacted: true в ConnectorContract (см. «Входные и выходные данные»).
  • Для лидов и заказов режим не используется — обычный журнальный: важна каждая транзакция, а не только «последнее состояние».
  • Общий рубильник — integrations-bus.compaction_enabled (дефолт true); при false все вызовы, включая compacted: true, идут журнально — аварийный откат без правки кода потребителей.
  • Ключ компакции entity_type + entity_id не задевает schema_version (см. «Модель данных») — версионирование payload и дедупликация по сущности ортогональны.

Производительность и кеш

Ожидаемые объёмы:

  • типичный сайт: 1–2 интеграции (CRM + один платёжный шлюз) — сотни–первые тысячи вызовов/сутки, пики при ручной синхронизации;
  • крупный сайт: 3–5+ интеграций (1С, CRM, ERP, несколько платёжных шлюзов) — десятки тысяч вызовов/сутки, регулярная пакетная синхронизация остатков/заказов.

Горячие пути и бюджет запросов:

  • постановка вызова в очередь потребителем: чтение состояния circuit breaker (1 запрос по connector_id) + запись в call_log (1 запрос) — не более 2 запросов на вызов, без N+1 при батч-синхронизации (экспорт 1000 заказов в 1С — одна батч-джоба с одним вызовом коннектора на пачку, не 1000 отдельных постановок);
  • Filament-дашборд статусов — один агрегирующий запрос по всем коннекторам с последним состоянием circuit, не N+1 по каждому коннектору.

Критичные индексы:

  • cms_integration_call_log: составной (connector_id, created_at) для журнала конкретного коннектора, индекс по status_code для фильтра «только ошибки», BRIN по created_at при росте;
  • cms_integration_circuit_state: уникальный индекс по connector_id (одно состояние на коннектор), индекс по state для быстрой выборки открытых circuit (используется health-чеком);
  • cms_integration_connectors: уникальный индекс по code.

Что кешируется:

  • реестр коннекторов (code → class, rate_limit_per_minute) — меняется редко (по деплою/включению модуля), тег integrations-bus:registry, инвалидация — при регистрации/изменении коннектора на boot и по SettingChanged группы integrations-bus;
  • маппинги полей коннектора (если коннектор их объявляет) — тег integrations-bus:mapping:<connector_code>.

Что не кешируется: состояние circuit breaker и статусы коннекторов — читаются напрямую из БД (актуальность важнее скорости: путь не публичный, единичные запросы при постановке джобы и в админке, вне бюджета page-cache).

Мониторинг лага очереди: метрика «отставание integrations-bus» — возраст самого старого необработанного сообщения — видна в health-чеке (cms:integrations:status --json) и в Filament-дашборде, порог алерта настраивается на уровне cms/health. Экстремальное отставание (восстановление после долгого простоя внешней системы) — сигнал вручную включить integrations-bus.kill_switch, чтобы не заливать внешнюю систему шквалом устаревших вызовов разом вместо автоматической разблокировки всей накопленной очереди.

Ранбук (симптом → команда):

СимптомДиагностикаДействие
Circuit breaker открытcms:integrations:status --json, причина в журнале (error_type)cms:integrations:reset-circuit --json после устранения причины
Очередь отстаётметрика лага в health-чеке/дашбордевременно повысить число воркеров очереди
Dead-letter растёт/превысил dead_letter_max_sizeжурнал status=failed, разобрать error_typeпереотправка через Filament; рост сверх лимита — алерт «коннектор системно сломан», разобрать причину, не просто переотправлять
Коннектор — массовые 401проверить credentials_ref в .envобновить/перевыпустить токен, не истёкший ли

Безопасность

Креды коннекторов — только ссылки на .env (credentials_ref), секреты в БД/журнале вызовов запрещены (тело запроса/ответа маскирует поля с кредами перед записью).

Векторы и защита:

  • утечка кредов через логи — маскируется не только credentials_ref, но и токены, которые внешняя система возвращает в ответе (например, обновлённый access-токен в заголовке) — маскирующий фильтр применяется и к request, и к response по whitelist чувствительных ключей коннектора, не по зашитому в ядро списку;
  • SSRF при настройке коннектора — базовый URL внешней системы обязан быть зашит в .env/коде коннектора, не в изменяемых настройках settings-store (иначе обладатель integrations-bus.manage мог бы указать внутренний адрес — 169.254.169.254, localhost, приватные сети); если появится настраиваемый URL коннектора — обязателен whitelist доменов (integrations-bus.allowed_domains);
  • подмена/спуфинг ответа внешней системы — коннектор обязан проверять TLS-сертификат (verify не отключается), для критичных операций — валидировать подпись ответа, если протокол внешней системы её предоставляет (входящие вебхуки — отдельно, через cms/webhooks-in).

Rate-limit — на уровне коннектора (rate_limit_per_minute), сброс circuit breaker — только под integrations-bus.manage. ПДн в журнале вызовов — транзитные, ретеншн и хук анонимизации по предикату — см. «Модель данных».

Матрица ролей:

РольДействие
Менеджерпросмотр журнала вызовов и статусов коннекторов (integrations-bus.view)
Редакторнет доступа — интеграции не редакторская зона
Studio/админintegrations-bus.manage: сброс circuit breaker, массовая переотправка dead-letter, регистрация новых коннекторов (через boot модулей-потребителей), управление kill_switch

Права: integrations-bus.view, integrations-bus.manage.

UX-требования

Админ:

  • пустое состояние «нет зарегистрированных коннекторов» → подсказка «коннекторы регистрируются модулями-потребителями (1С, CRM, платёжные шлюзы) — установите и включите нужный модуль»;
  • пустой журнал вызовов → «вызовов ещё не было»;
  • массовое действие: повторная отправка пачки failed/dead-letter сообщений из журнала (чекбоксы + кнопка «переотправить выбранные», под integrations-bus.manage);
  • массовое действие: сброс circuit breaker сразу для нескольких коннекторов;
  • ошибки на человеческом языке: «CRM не отвечает 3-й раз подряд, коннектор временно отключён, следующая попытка через 8 минут» вместо «HTTP 500»; «Ключ доступа не найден в .env: ONEC_API_TOKEN — обратитесь к разработчику» вместо стектрейса;
  • подтверждение необратимых операций: удаление коннектора/интеграции с историей вызовов — модальное окно «журнал N вызовов станет недоступен для этого коннектора»; массовая переотправка dead-letter — доп. подтверждение при количестве выше порога (защита от шторма запросов к внешней системе); включение integrations-bus.kill_switch — отдельное подтверждение «все исходящие вызовы шины будут остановлены, включая другие интеграции» (глобальный эффект, не по одному коннектору).

Посетитель: модуль инфраструктурный, публичного UI не имеет — посетитель взаимодействует с ним только косвенно, через модуль-потребитель (например, задержка синхронизации остатков влияет на доступность товара к заказу); поведение форм при ошибке и скорость отклика — зона ответственности потребителя, не шины.

Крайние случаи и типовые баги

  • ретраи исчерпаны (retry_attempts попыток с экспоненциальным backoff от retry_backoff_seconds) → джоба переходит в dead-letter, событие ConnectorCallFailed с финальным attempt, вызов больше не повторяется автоматически;
  • dead-letter → виден админу с правом integrations-bus.manage в журнале (фильтр по статусу), переотправка — вручную, кнопкой; повторная попытка создаётся с тем же ключом идемпотентности, что и исходный вызов;
  • дубль доставки (at-least-once) → шина не гарантирует «ровно один» вызов при ретрае/повторной обработке джобы; коннектор обязан быть идемпотентен на своей стороне (сверка по external_id/ключу идемпотентности), шина передаёт стабильный ключ (event_id/entity_id) в вызове — но не может обеспечить идемпотентность внешней системы, если та не поддерживает Idempotency-Key;
  • порядок сообщений не гарантирован между разными коннекторами/очередями → потребитель обязан передавать/запрашивать текущее состояние сущности, а не полагаться на последовательность (выгружая заказ в CRM после серии смен статуса — выгружается финальный статус, не хронология);
  • выключение модуля посреди отправки → джобы остаются в очереди integrations-bus, но не обрабатываются (health-чек фиксирует отставание); при включении обработка продолжается с места остановки; если disable длится дольше retry_until джобы — Laravel переводит её в failed_jobs, что эквивалентно dead-letter;
  • отсутствие suggests-модуля / незаведённая интеграция → вызов с незарегистрированным connector_code возвращает доменное исключение «коннектор не зарегистрирован»; шина не решает за потребителя, как деградировать — это его ответственность (см. «Зависимости и выключение»);
  • сбой/таймаут внешнего API → таймаут коннектора считается временной ошибкой → ретрай по backoff; после circuit_breaker_threshold сбоев подряд — circuit открывается, дальнейшие вызовы этого коннектора отклоняются fail-fast (без реального обращения) на время circuit_breaker_cooldown_minutes;
  • пустой payload → валидация тела запроса — ответственность коннектора-реализации (владеет форматом конкретного внешнего API), шина не пытается угадывать структуру и логирует запрос как есть;
  • огромный payload → ⚠️ Противоречие: ТЗ не определяет лимит размера request/response, логируемого в cms_integration_call_log (JSONB без ограничений раздувает журнал и БД при экспорте каталога целиком одним вызовом). Разрешение: настройка integrations-bus.max_logged_payload_kb (дефолт 64), тело сверх лимита обрезается с пометкой truncated: true, сам вызов при этом не блокируется;
  • конфликт двух интеграций, пишущих в одну внешнюю систему (например integration-crm и платёжный шлюз одновременно обновляют один контакт в CRM) → шина не разруливает конфликты доменных данных внешней системы — это ответственность потребителей/самой внешней системы (last-write-wins на её стороне); шина гарантирует только независимую доставку каждого вызова;
  • rate-limit коннектора исчерпан → вызов не отбрасывается, а откладывается в очереди до окна лимита; при синхронном вызове с таймаутом — graceful fallback потребителю (см. «Фоновая работа»), не 500;
  • ротация/отзыв credentials_ref (переменная переименована/удалена в .env без обновления реестра) → ⚠️ Противоречие: ТЗ не различает временные ошибки (сеть, 5xx, таймаут — подлежат ретраю) и постоянные (401/403, невалидные креды — ретрай бессмысленен, только тратит retry_attempts впустую до dead-letter). Разрешение: коннектор классифицирует ошибку (temporary/permanent) в ответе, permanent уходит в dead-letter сразу, минуя полный цикл ретраев; событие ConnectorCallFailed получает поле error_type;
  • массовый пересчёт цен 50k товаров → при compacted: true подписчик получает не 50k доставок, а по одному вызову на товар с последним состоянием на момент обработки (см. «Фоновая работа»); без компакции — шквал устаревших состояний, как в Bitrix-опыте до внедрения offline-очереди;
  • dead-letter превысил integrations-bus.dead_letter_max_size → старые записи не теряются молча: помечаются expired/архивируются по dead_letter_ttl_days с алертом — сигнал, что коннектор системно сломан (не просто временные сбои), требует разбора причины;
  • включён integrations-bus.kill_switch → все новые исходящие вызовы сразу уходят в dead-letter с error_type=kill_switch, без попытки HTTP; действует глобально на все коннекторы (в отличие от circuit breaker — автоматического и точечного); выключение kill-switch не переигрывает накопленный dead-letter автоматически — переотправка отдельным действием под integrations-bus.manage;
  • устаревшая версия схемы сообщения в очереди → сообщение с schema_version ниже текущей не должно ронять консьюмер: коннектор обязан читать минимум текущую и предыдущую версию на момент апгрейда (см. «Модель данных»); схема старше поддерживаемого диапазона — доменное исключение, вызов уходит в dead-letter с error_type=schema_too_old, не роняя обработку остальной очереди.

Донорский код

Донор: — (новая разработка на saloonphp/saloon)

Легаси-импорт: не применим напрямую — шина не хранит доменных данных интеграций (зона cms/integration-1c/cms/integration-crm), переносить с прежней платформы нечего. Исторический журнал вызовов старой интеграции (если был) не мигрируется — чистый журнал при подключении.

Тесты и приёмка

  • [ ] Контрактный тест: коннектор реализует ConnectorContract, вызов проходит через шину, не напрямую
  • [ ] Health-чек модуля отражает статус каждого зарегистрированного коннектора
  • [ ] При выключении модуля потребители не падают, а переходят в отложенный режим
  • [ ] Circuit breaker открывается после integrations-bus.circuit_breaker_threshold сбоев подряд (тест)
  • [ ] Креды не хранятся в БД — только ссылки на .env (тест на утечку в журнале вызовов)
  • [ ] Права integrations-bus.view/.manage разграничивают просмотр и сброс circuit breaker
  • [ ] Ретраи с backoff не создают дублирующих вызовов внешнего API (идемпотентность коннектора)
  • [ ] Исчерпание retry_attempts переводит вызов в dead-letter, а не теряет его молча
  • [ ] Dead-letter доступен для ручной переотправки под integrations-bus.manage, переотправка использует тот же ключ идемпотентности, что и исходный вызов
  • [ ] Порядок доставки намеренно не гарантируется в тестах: два вызова к разным коннекторам, выполненные в произвольном порядке, не влияют на корректность друг друга
  • [ ] Compacted-режим: N быстрых изменений одной сущности (entity_type + entity_id) схлопываются в один вызов на выходе с последним состоянием
  • [ ] Обычный (журнальный) режим для лидов/заказов доставляет каждое событие отдельно, даже при включённом integrations-bus.compaction_enabled
  • [ ] integrations-bus.kill_switch блокирует новые исходящие вызовы (error_type=kill_switch в dead-letter); ранее поставленные в очередь тоже переводятся в dead-letter, не выполняются
  • [ ] Сообщение с устаревшей, но поддерживаемой версией schema_version не роняет обработчик текущей версии; схема старше диапазона — error_type=schema_too_old, без блокировки остальной очереди
  • [ ] Контрактный набор cms-testing пройден, пакет протестирован в testbench-изоляции
  • [ ] Feature-тест на каждый роут модуля; тестовая БД только integrations_bus_test, migrate:fresh запрещён

Внутренняя база знаний студии