Тема
ТЗ — Шина интеграций (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_connectors | id, code, class, status, credentials_ref, rate_limit_per_minute | реестр коннекторов, ссылки на креды в .env |
cms_integration_call_log | id, connector_id, request (json), response (json), status_code, duration_ms, created_at | журнал вызовов |
cms_integration_circuit_state | id, 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, cursor | FormRequest whitelist (конвенция API ядра) |
| Ответ внешней системы (через saloon-коннектор) | HTTP-статус, тело ответа, время выполнения | коннектор классифицирует ответ (успех / временная ошибка / постоянная ошибка) перед возвратом в шину |
Выходы
| Потребитель | Данные | Формат |
|---|---|---|
| Модуль-потребитель (sync-вызов) | результат вызова коннектора | Response/DTO коннектора (типизированный класс saloon-ответа) |
| Модуль-потребитель (async-вызов) | результат в его собственной callback-джобе, поставленной вместе с задачей | payload по контракту потребителя (шина не заглядывает внутрь) |
| Filament-дашборд | статусы коннекторов, журнал вызовов | таблицы админки |
cms/health и подписчики событий | ConnectorCallFailed, ConnectorCircuitOpened, ConnectorCircuitClosed | payload события (канал 1) |
| Внешняя система (CRM/1С/шлюз/…) | исходящий HTTP-запрос коннектора | формат конкретного внешнего API (вне контроля шины) |
Whitelist-принцип: вызов с незарегистрированным connector_code, коннектором без реализации ConnectorContract или попытка прямого HTTP к внешней системе в обход шины — отклоняется/запрещён (см. «Назначение и возможности», §11 стандарта).
Настройки (группа integrations-bus)
| Ключ | Тип | Дефолт | affectsPageCache | Описание |
|---|---|---|---|---|
integrations-bus.retry_attempts | int | 3 | нет | Число ретраев на временную ошибку |
integrations-bus.retry_backoff_seconds | int | 5 | нет | Базовая задержка экспоненциального backoff |
integrations-bus.circuit_breaker_threshold | int | 5 | нет | Число сбоев подряд до открытия circuit breaker |
integrations-bus.circuit_breaker_cooldown_minutes | int | 10 | нет | Время до повторной попытки после открытия |
integrations-bus.call_log_retention_days | int | 30 | нет | Хранение журнала вызовов |
integrations-bus.call_timeout_seconds | int | 15 | нет | Таймаут одного внешнего вызова |
integrations-bus.dead_letter_ttl_days | int | 14 | нет | Хранение записей в dead-letter до автоочистки |
integrations-bus.allowed_domains | array | [] | нет | Whitelist доменов для исходящих вызовов (пусто = ограничение только на уровне коннектора) |
integrations-bus.max_logged_payload_kb | int | 64 | нет | Лимит размера request/response, логируемого в журнал (см. «Крайние случаи») |
integrations-bus.dead_letter_max_size | int | 10000 | нет | Максимум записей в dead-letter; при достижении старые не теряются молча — помечаются expired по dead_letter_ttl_days с алертом (см. «Крайние случаи») |
integrations-bus.compaction_enabled | bool | true | нет | Общий рубильник режима compacted queue (§«Фоновая работа»); коннекторы с compacted: true игнорируют флаг только при явном отключении здесь |
integrations-bus.kill_switch | bool | false | нет | Аварийная остановка всех исходящих вызовов шины без выключения модуля; включённые вызовы уходят в dead-letter с error_type=kill_switch (см. «Крайние случаи») |
API
| Метод | Путь | Доступ | Назначение |
|---|---|---|---|
| GET | /api/v1/admin/integrations/connectors | admin (integrations-bus.view) | Список коннекторов и их статусы |
| GET | /api/v1/admin/integrations/log | admin (integrations-bus.view) | Журнал вызовов с фильтрами |
| POST | /api/v1/admin/integrations/connectors/{code}/reset | admin (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 |
ConnectorCircuitOpened | circuit breaker открылся после серии сбоев | connector_code, failure_count |
ConnectorCircuitClosed | circuit 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-1c | requires (канал 4) | in | вызывает шину для отправки/получения данных 1С через свой коннектор |
cms/integration-crm | requires (канал 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 через saloon | out | фактический вызов; шина — единственная точка выхода наружу |
cms/health | событие ConnectorCallFailed/ConnectorCircuitOpened/ConnectorCircuitClosed (канал 1) | out | агрегирует сбои и отставания в общий health-отчёт |
cms/audit (если включён) | событие (канал 1) | out | фиксирует сбои и открытия circuit breaker в аудит-лог |
Очередь integrations-bus | канал 5 | out (внутри себя) | асинхронная обработка вызовов, ретраи, 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запрещён