Перейти к основному содержимому

Event Contract: pin.messenger.call-recording-available (CallRecordingAvailableEvent)

Версионируемый контракт интеграционного Kafka-события «запись звонка готова». Consumer-документы ссылаются сюда, не переописывая payload.

Назначение

Фиксирует переход записи звонка/конференции в Available (call-recording.md): бинарь сохранён в file storage, известен стабильный FileId, метаданные заполнены. Публичное, потому что file storage событий не публикует, а «доступность записи» нужна внешним потребителям синхронно недостижимым образом: crm прикрепляет запись к сделке/встрече, notifications уведомляет участников. Это же событие — триггер будущего пайплайна транскрипции/конспектирования (../calls/transcription-roadmap.md). Producer — messenger (ingest-эндпоинт финализации записи, ../api/internal/jibri-recording-ingest.md).

Metadata

ПолеЗначение
Event typepin.messenger.call-recording-available
Topicpin.messenger.call-recording-available.v1
Partitions / key3 / ключ = SessionId (порядок сегментов записи в рамках сессии)
ContractVersion1
Producermessenger
Статусdraft

Событие публикуется атомарно с переходом записи Uploading → Available (транзакционный outbox): пока изменение не зафиксировано, событие не отправляется; после фиксации — доставляется at-least-once.

Payload CallRecordingAvailableEvent

Публичные бизнес-поля готовой записи. Запись бинаря — по FileId (file storage); ссылок/URL в событии нет (потребитель получает пресайн сам). Snapshot ссылается на дом сущности (call-recording.md).

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
RecordingIdGuidДаCallRecordingEntity.IdImmutableИдентификатор записи; бизнес-ключ идемпотентности.
SessionKindSessionKindДа.SessionKindAdditiveDirect / Conference.
SessionIdGuidДа.CallSessionId / .ConferenceSessionIdImmutableСессия-владелец; Kafka-ключ.
ChatIdGuid?Нет.ChatIdImmutableDeal-чат (для конференций); null для 1:1.
DealIdGuid?Нет.DealIdImmutableСделка; маршрутизация в crm; null для 1:1.
TenantIdGuid?Нет.DealTenantIdImmutableDeveloperCompanyId сделки; null для 1:1.
MediaTypeCallMediaTypeДа.MediaTypeAdditiveАудио/видео.
FileIdGuidДа.FileIdImmutableСсылка на бинарь в file storage (без URL).
FormatRecordingFormatДа.FormatAdditiveФормат контейнера (Mp4/Webm/Mkv/Mp3).
DurationSecondsintДа.DurationSecondsДлительность записи (≥ 0).
SizeByteslongДа.SizeBytesРазмер файла (> 0).
TriggerRecordingTriggerДа.TriggerAdditiveAutoPolicy / Manual.
StartedAtDateTimeOffsetДа.StartedAtНачало захвата.
EndedAtDateTimeOffsetДа.StoppedAtКонец захвата.
AvailableAtDateTimeOffsetДа.AvailableAtМомент готовности.

Enum'ы (SessionKind, CallMediaType, RecordingFormat, RecordingTrigger) — из call-recording.md/call-session.md, не дублируются.

Когда публикуется

УсловиеПереход состоянияПубликация
Бинарь выгружен в file storage, задан FileId + метаданныеUploading → Available (call-recording.md INV-REC-2)атомарно со сменой состояния (outbox)

Потребители (consumers)

Consumer-контекстConsumerSide effectsИдемпотентностьЗадержка (SLA)
crmattach-call-recordingПри DealId != null — прикрепить FileId к таймлайну/встрече сделкиRecordingId (first-seen)< 1 мин
notificationsrecording-ready-notifyУведомить участников сессии «запись готова» (in-app/offline)RecordingId< 1 мин

Потребители, которым нужен доступ к файлу, запрашивают пресайн у file storage сами (или ведут пользователя к ../api/recordings/get-recording-download.md) — в событии нет URL/секретов.

Гарантии доставки, порядок и обработка отказов

  • At-least-once + идемпотентность. Доставка хотя бы раз; оба потребителя дедуплицируют по RecordingId (first-seen), повтор не плодит вложение сделки и не шлёт повторное уведомление. Частичный отказ fan-out независим: падение notifications не откатывает attach в crm — редоставка повторяет обоих, дубли гасятся.
  • Порядок относительно conference-ended. Для конференций это событие приходит после conference-ended.md (разные топики/ключи — межтопикового порядка нет). crm, прикрепляя запись, находит уже созданную карточку встречи по RecordingId/ConferenceId; если событие встречи ещё не обработано — attach ретраится (backoff), затем DLQ + алерт. conference-ended.WasRecorded = true — сигнал потребителю ждать это событие; RecordingId из встречи совпадает с RecordingId здесь.
  • Запись не стала доступной. Если выгрузка бинаря в file storage не финализировалась, запись остаётся Uploading/Failed (call-recording.md), фоновый recording-finalize-retry добирает; при окончательном Failed событие не публикуется — потребители не ждут его бессрочно (у встречи WasRecorded = true, но call-recording-available не придёт; расхождение — предмет алерта эксплуатации, не бизнес-ошибка потребителя).
  • Retry/DLQ. Транзиентный сбой потребителя — ретрай по offset; poison после исчерпания попыток → DLQ топика + алерт, offset продвигается (поток других записей не блокируется).

Версионирование и совместимость

  • Версия — один ContractVersion = 1; consumer при Version != ContractVersion логирует предупреждение и продолжает. Версия кодируется суффиксом .v1.
  • Additive-only для minor: новые поля — optional с default, без bump.
  • Breaking — новый ContractVersion + топик .v2 + dual-publish на период миграции.
  • Unknown enum (SessionKind/RecordingFormat/RecordingTrigger) — fallback с предупреждением.

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

ПолеКласс данныхПравило
FileIdInternalinclude (ссылка, не бинарь; доступ к файлу — через пресайн file storage)
RecordingId/SessionId/ChatId/DealId/TenantIdInternalinclude
прочие (MediaType/Format/DurationSeconds/SizeBytes/Trigger/timestamps)Publicinclude

Событие НЕ содержит бинарь, ссылки/URL, токены, ФИО/телефон, транскрипт — только id/enum/bounded-скаляры.

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы