Event Contract: pin.messenger.deal-chat-status-changed (DealChatStatusChangedEvent)
Версионируемый контракт интеграционного события «изменилась готовность чата сделки». Consumer-документы
(crm) ссылаются сюда, не переописывая payload. Событие фиксирует факт перехода провизии Deal-чата
(Provisioning/Ready/Failed) и несёт корреляционные id — crm обновляет проекцию Deal.ChatStatus
без синхронного запроса в messenger. Дом провизии чата — chat.md; проекция
потребителя — deal.md.
Назначение
Оркестрация чат ↔ сделка идёт через события crm (ADR-0055): crm публикует
pin.crm.deal-created.v1, консюмер messenger
(deal-created.md) провизионирует чат и возвращает готовность этим
событием. crm держит лишь проекцию Deal.ChatStatus (Pending — свой default до первого события) —
источник истины чата остаётся в messenger. Событие публичное для корреляции воронки сделки; PII не
несёт (только id + enum-статус).
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.messenger.deal-chat-status-changed |
| Класс события | DealChatStatusChangedEvent |
| Topic | pin.messenger.deal-chat-status-changed.v1 |
| Partitions / key | 3 / key = DealId (порядок смен статуса per-deal) |
| ContractVersion | 1 |
| Producer | messenger: провизия Deal-чата (deal-created.md) + воркер chat-provision-retry |
| Статус | draft |
Событие публикуется атомарно (в одном согласованном изменении) с провизией/синком чата через
транзакционный outbox. Партиционирование по ключу DealId даёт per-deal порядок смен статуса.
Payload DealChatStatusChangedEvent
Корреляционные поля; snapshot состояния переписки не входит. Дом полей чата — chat.md;
дом проекции DealChatStatus — deal.md.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
DealId | Guid | Да | Chat.DealId | Immutable | Сделка (Kafka key). Ключ проекции у crm. |
TenantId | Guid | Да | Chat.DealTenantId | Immutable | DeveloperCompanyId сделки (ADR-0052); crm сверяет с deal.TenantId (fail-closed). |
ChatId | Guid? | Нет (для Ready — да) | Chat.Id | Immutable | Id созданного чата messenger; null при Provisioning/Failed до создания строки чата. |
Status | string (enum DealChatStatus) | Да | ход провизии | Additive only (новые значения) | Provisioning / Ready / Failed (Pending — CRM-side default, продюсером не шлётся). |
FailureReason | string? | Нет (только при Status = Failed) | ошибка провизии | Additive only | Краткая диагностика провала (без PII/стектрейса); crm пишет в лог, не хранит. |
ChangedAt | DateTimeOffset | Да | момент смены | Immutable semantics | Момент смены статуса в messenger (диагностика лага; авторитет порядка — partition key DealId). |
DealId/TenantId иммутабельны; Status/ChatId/FailureReason переиздаются при каждом переходе
провизии.
Enum DealChatStatus
Дом — deal.md («Enum DealChatStatus»). messenger — producer подмножества.
| Значение | Кто эмитит | Описание |
|---|---|---|
Pending | — (CRM default) | До первого события consumer-а; продюсером messenger не публикуется. |
Provisioning | messenger | Начато создание чата/синк состава по deal-created. |
Ready | messenger | Чат создан, участники синхронизированы (ChatId заполнен). |
Failed | messenger | Провизия упала; воркер chat-provision-retry повторит и re-emit. |
Когда публикуется
| Условие | Переход состояния | Согласованность |
|---|---|---|
Начата провизия по deal-created (до создания строки чата) | Pending → Provisioning | приём события и факт начала — в одном согласованном изменении |
| Чат создан и состав синхронизирован (INV-CHAT-2/6) | Provisioning → Ready | чат (+участники) и факт готовности — атомарно |
| Провизия упала (ошибка создания/синка) | → Failed | отметка обработки и факт провала — атомарно |
Воркер chat-provision-retry успешно повторил | Failed → Ready | чат и факт готовности — атомарно |
Идемпотентная повторная доставка deal-created (INV-CHAT-2) не плодит чат и не меняет Ready —
регресс из терминального Ready producer не публикует (у crm Ready поглощающий). Источник провизии
— deal-created.md; статус чата пользователя (Active/Closed) —
ортогонален (дом chat.md).
Потребители (consumers)
| Consumer-контекст | Эффект (событие → что меняется) | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
crm | Проекция Deal.ChatStatus (монотонный rank-guard: Ready терминален) — deal-chat-status.md | дедуп по идентификатору сообщения + монотонный rank-guard | < 30 с |
Событие несёт ChatId только для лога/трейса — crm его не хранит на сделке (ADR-0055).
Гарантии доставки, порядок и обработка отказов
- At-least-once + монотонная проекция. Доставка хотя бы раз;
crmдедуплицирует по идентификатору сообщения и держит монотонный rank-guardProvisioning < Ready,Failed < Ready:Readyпоглощающий, назад не откатывается. ПовторReady(в т.ч. re-emit воркеромchat-provision-retryпослеFailed) — идемпотентный no-op проекции. - Порядок per-
DealId. КлючDealIdдаёт per-deal порядок смен статуса; между сделками порядка нет. - Fail-closed по tenant.
crmсверяетTenantIdсобытия сdeal.TenantIdсвоей сделки; при расхождении событие отбрасывается (не своя сделка) — проекция не обновляется, инцидент логируется. - Unknown
Status— критично. В отличие от прочих топиков сервиса, неизвестное значениеStatusне деградирует тихо:CRM_CHAT_STATUS_UNKNOWN→ DLQ + алерт (готовность чата — часть воронки сделки).
Версионирование и совместимость
ContractVersion = 1; при несовпадении версии входящего события с текущей consumer логирует warning и продолжает (апкаст/миграция схемы не выполняется). Топик кодирует версию суффиксом.v1по конвенции.- Additive-only для minor: новые поля — optional с безопасным default, без bump.
- Breaking (удаление/переименование/смена типа/смысла) — новый
ContractVersion = 2+ новый топикpin.messenger.deal-chat-status-changed.v2+ dual-publish + deadline миграции consumers + sunset.v1. - Unknown enum
Statusу consumer-а —CRM_CHAT_STATUS_UNKNOWN→ DLQ + алерт (не тихий fallback: проекция готовности критична). - Порядок смен per-deal гарантирован ключом
DealId; consumer держит монотонный rank-guard (Readyне регрессирует).
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
DealId, TenantId, ChatId, Status, ChangedAt | Internal | include (корреляция сделки/чата; не публичный контент) |
FailureReason | Internal | include (без PII/стектрейса/секретов; только краткая причина) |
Событие НЕ содержит тел сообщений, ФИО/телефонов участников, IP/UA, secrets, tokens, платёжных данных.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Aggregate: ChatEntity (+ParticipantEntity как owned-child)
- API (1): SignalR Hub /hubs/messenger — MessengerHub (realtime-контракт)
- Use Cases (1): UC-MSG-002. Авто-провизия чата сделки по событию CRM
- События (1): События сервиса messenger: реестр топиков
- Консюмеры (3): Consumer: Deal Chat Provisioning (MessengerDealCreatedConsumer), Consumer: Deal Chat Status (pin-crm-deal-chat-status), Consumer: DevStatsFunnelDealCreatedConsumer
- Фоновые задачи (1): Background Job: ChatProvisionRetryJob
- registries (1): Реестр событий PIN
Связанные документы
- Источник (провизия): deal-created.md,
воркер
chat-provision-retry(README); сущность — chat.md (провизия,Kind = Deal, INV-CHAT-2/6). - Потребитель: deal-chat-status.md; проекция —
deal.md (
DealEntity.ChatStatus, enumDealChatStatus). - Соседние контракты сервиса — реестр событий.
- ADR: 0055 мини-мессенджер, 0052 tenancy, 0056 simplicity-first.