Event Contract: pin.messenger.conference-ended (ConferenceEndedEvent)
Версионируемый контракт интеграционного Kafka-события «конференция сделки завершена». Consumer-документы ссылаются сюда, не переописывая payload.
Назначение
Фиксирует завершение группового звонка в чате сделки (conference-session.md):
факт видеовстречи, её длительность и состав участников. Публичное, потому что конференция всегда привязана к
сделке (Deal-чат) — у события есть внешний потребитель, синхронно недостижимый: crm кладёт карточку в
таймлайн сделки («видеовстреча, 32 мин, 3 участника»), notifications/аналитика используют факт звонка. 1:1-звонки
такого события не имеют (person-to-person, вне сделки). Producer — messenger (завершение конференции
или reaper пустой комнаты).
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.messenger.conference-ended |
| Topic | pin.messenger.conference-ended.v1 |
| Partitions / key | 3 / ключ = DealId (порядок конференций в рамках сделки) |
| ContractVersion | 1 |
| Producer | messenger |
| Статус | draft |
Событие публикуется атомарно с переходом конференции
Active → Ended/Failed(транзакционный outbox): после фиксации — доставляется at-least-once.
Payload ConferenceEndedEvent
Публичные бизнес-поля завершённой конференции. Snapshot ссылается на дом сущности (conference-session.md); ростер — bounded-список без ФИО.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
ConferenceId | Guid | Да | ConferenceSessionEntity.Id | Immutable | Идентификатор конференции; ключ идемпотентности. |
ChatId | Guid | Да | .ChatId | Immutable | Deal-чат конференции. |
DealId | Guid | Да | .DealId | Immutable | Сделка; Kafka-ключ. |
TenantId | Guid | Да | .DealTenantId | Immutable | DeveloperCompanyId сделки. |
MediaType | CallMediaType | Да | .MediaType | Additive | Аудио/видео. |
Status | ConferenceStatus | Да | .Status | Additive | Ended / Failed. |
EndReason | ConferenceEndReason | Да | .EndReason | Additive | Причина завершения. |
StartedByUserId | Guid | Да | .StartedByUserId | Immutable | Инициатор. |
StartedAt | DateTimeOffset | Да | .StartedAt | — | Старт. |
EndedAt | DateTimeOffset | Да | .EndedAt | — | Завершение. |
DurationSeconds | int | Да | .DurationSeconds | — | Длительность (≥ 0). |
PeakParticipantCount | int | Да | .PeakParticipantCount | — | Максимум одновременных участников. |
Participants | ConferenceParticipantSnapshotEntry[] | Да | ростер (call-participant.md) | Additive | Итоговый ростер (bounded). |
WasRecorded | bool | Да | наличие записи | Additive | Велась ли запись. |
RecordingId | Guid? | Нет | CallRecordingEntity.Id | Immutable | Запись (если велась); готовность придёт отдельным событием. |
Вложенная модель ConferenceParticipantSnapshotEntry
| Поле | Тип | Обязательность | Ограничения | Описание |
|---|---|---|---|---|
UserId | Guid | Да | логическая ссылка identity | Участник. |
Role | ParticipantRole? | Нет | enum | Орг-роль на момент участия. |
JoinedAt | DateTimeOffset | Да | — | Вход. |
LeftAt | DateTimeOffset? | Нет | — | Выход. |
DurationSeconds | int? | Нет | >= 0 | Время в конференции. |
Enum'ы (CallMediaType, ConferenceStatus, ConferenceEndReason, ParticipantRole) — из
conference-session.md/call-participant.md, не дублируются.
Когда публикуется
| Условие | Переход состояния | Публикация |
|---|---|---|
| Штатное/аварийное завершение конференции (опустошение / модератор / закрытие чата / reaper / фатальный сбой) | Active → Ended / Active → Failed (conference-session.md INV-CONF-4) | атомарно со сменой состояния (outbox) |
Потребители (consumers)
| Consumer-контекст | Consumer | Side effects | Идемпотентность | Задержка (SLA) |
|---|---|---|---|---|
crm | conference-timeline-entry | Карточка «видеовстреча по сделке» в таймлайне сделки | ConferenceId (first-seen) | < 1 мин |
notifications | conference-ended-notify (опц.) | Уведомление «встреча завершена / вы пропустили» | ConferenceId | < 5 мин |
dev-stats | conference-fact (опц.) | Факт звонка для аналитики застройщика (ClickHouse) | ConferenceId | < 5 мин |
Гарантии доставки, порядок и обработка отказов
- Ровно один факт завершения на конференцию. Публикуется единожды при переходе
Active → Ended/Failed(INV-CONF-4), независимо от инициатора (опустошение / модератор / закрытие чата /conference-room-reaper/ фатальный сбой). At-least-once-редоставка возможна — все потребители дедуплицируют поConferenceId(first-seen); повторная доставка не плодит карточку в таймлайне и не задваивает факт аналитики. - Порядок per-
DealId. Несколько конференций одной сделки упорядочены ключомDealId; между сделками порядка нет. Частичный отказ fan-out (crm/notifications/dev-stats) независим — падение одного не откатывает других. - Запись — отдельным событием.
WasRecorded/RecordingIdфиксируют факт записи, но её готовность приходит позже контрактом call-recording-available.md (после выгрузки в file storage); потребитель, ждущий файл, ориентируется наWasRecorded = trueи сверяетRecordingId. - Retry/DLQ. Транзиентный сбой потребителя — ретрай по offset; poison после исчерпания попыток → DLQ + алерт, offset продвигается.
Версионирование и совместимость
- Версия — один
ContractVersion = 1; consumer при рассогласовании логирует предупреждение и продолжает. Суффикс.v1. - Additive-only для minor; breaking →
ContractVersion = 2+ топик.v2+ dual-publish. - Ростер
Participants— bounded; unknown enum — fallback с предупреждением.
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
Participants[].UserId/StartedByUserId | Internal | include (id, не ФИО) |
ConferenceId/ChatId/DealId/TenantId/RecordingId | Internal | include |
прочие (MediaType/Status/EndReason/timestamps/counts/WasRecorded) | Public | include |
Событие НЕ содержит ФИО/телефонов, тела сообщений, медиа, токенов; ростер — только id/роли/тайминги.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Aggregate: ConferenceSessionEntity — групповой звонок в чате сделки
- API (3): POST /api/messenger/conferences/{conferenceId}/end — Завершить конференцию для всех, POST /api/messenger/conferences/{conferenceId}/leave — Выйти из конференции, SignalR Hub /hubs/messenger — MessengerHub (realtime-контракт)
- Use Cases (1): UC-MSG-006. Групповой звонок-конференция в чате сделки
- События (2): События сервиса messenger: реестр топиков, Event Contract: pin.messenger.call-recording-available (CallRecordingAvailableEvent)
- Фоновые задачи (1): Background Job: ConferenceRoomReaperJob
- adr (1): ADR-0062: Медиа-стек звонков и запись на Jitsi (JVB SFU + coturn + jibri → file storage; JWT из messenger; нативный lib-jitsi-meet)
- calls (4): Жизненный цикл сессии звонка — 1:1 и конференция (messenger), Медиа-звонки и запись на Jitsi — интеграционный гид (messenger), Медиа-звонки на Jitsi: план поставки (playbook аналитик → разработчик), Backend-реализация медиа-звонков: спека → код (messenger)
- registries (1): Реестр событий PIN
Связанные документы
- Источник (операция): ../api/conferences/end-conference.md, ../background/conference-room-reaper.md; сущность: ../domain/conference-session.md.
- Реестр событий: README.md; запись сессии: call-recording-available.md.
- Решения: ADR-0062, ADR-0006 Kafka.