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

POST /api/messenger/chats/{chatId}/conferences — Начать конференцию в чате сделки

Участник Deal-чата поднимает групповой звонок. Создаётся ConferenceSessionEntity (Provisioning → Active), инициатор становится модератором в ростере (call-participant.md), остальным участникам чата уходит приглашение ConferenceInvite с дескриптором входа в медиа-комнату. Дом полей, статусы и инварианты (INV-CONF-*) — conference-session.md.

Назначение

Дать участнику чата сделки (инвестор / агент / менеджер застройщика) собрать видеовстречу по сделке: завести сессию конференции, зарезервировать медиа-комнату, при необходимости включить запись по политике и позвать в неё активных участников Deal-чата. На выходе — карточка конференции с дескриптором входа Room. Owning-сервис — messenger, feature group conferences, сценарий «групповой звонок по сделке».

Metadata

ПолеЗначение
Сервис/контекстmessenger
API areaconferences
Feature groupconferences
Статусdraft
Документdocs/06-services/messenger/api/conferences/start-conference.md

Актор и доступ

ПроверкаЗначение
Actor typeUser — активный участник Deal-чата (стартовый модератор)
Auth policy / Permissionspermission messenger.conferences.use
Scope (RBAC)не применяется — доступ по членству в чате, не по scope
Record-levelактивный ParticipantEntity чата (RemovedAt is null, INV-CHAT-4); гейт группы chat:{ChatId}
Tenant isolationне применяется (кросс-tenant, conference-session.md); срез — по членству в Deal-чате
Auditcreate: ConferenceSessionEntity (Provisioning/Active), метка StartedAt; строка ростера инициатора (Moderator, Joined)

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/messenger/chats/{chatId}/conferences
Success status201 (Created)
Idempotency headerне применяется — повтор ловит guard одной активной конференции на чат (INV-CONF-2): второй Provisioning/ActiveMSG_CONFERENCE_ALREADY_ACTIVE
Correlationсквозной trace через traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ChatIdrouteGuidДа!= Empty; существующий Deal-чат, актор — активный участник (шаги 1, 3)
ActorUserIdidentity claimsGuidДаиз JWT; активный Participant чата
MediaTypebodyCallMediaTypeДаenum строкой Audio/Video (call-session.md); unknown → ошибка валидации
Titlebodystring?Нет≤ 120; по умолчанию — из контекста сделки

Модель ответа ConferenceModel

Проекция ConferenceSessionEntity с ростером и дескриптором входа. Поле Room (тип MediaJoinModel, дом — messenger-hub.md) присутствует только в ответах start / join / media-token и никогда — в get / list.

ПолеТипОбязательностьИсточникMaskingОписание
ConferenceIdGuidДаConferenceSessionEntity.IdИдентификатор конференции (адресация join/leave/end).
ChatIdGuidДа.ChatIdDeal-чат конференции.
DealIdGuid?Нет.DealIdСделка (денорм из чата).
MediaTypeCallMediaTypeДа.MediaTypeAudio/Video.
StatusConferenceStatusДа.StatusProvisioning/Active на успехе старта.
Titlestring?Нет.TitleНазвание встречи.
StartedByUserIdGuidДа.StartedByUserIdИнициатор (стартовый модератор).
StartedAtDateTimeOffsetДа.StartedAtМомент старта.
EndedAtDateTimeOffset?Нет.EndedAtnull для активной.
DurationSecondsint?Нет.DurationSecondsnull до завершения.
RecordingStatusRecordingStatus?НетCallRecordingEntity.StatusСтатус записи, если ведётся (call-recording.md); null — запись не заведена.
RecordingIdGuid?НетCallRecordingEntity.IdИдентификатор записи (если заведена).
ActiveParticipantCountintДа.ActiveParticipantCountАктивных участников на момент ответа.
ParticipantsConferenceParticipantEntry[]Даростер (call-participant.md)На старте — только инициатор (Moderator, Joined).
RoomMediaJoinModel?НетвычисляетсяSensitive: AccessToken/TURN-креды не логируютсяДескриптор входа в комнату; здесь — присутствует.

Вложенная модель ConferenceParticipantEntry

ПолеТипОбязательностьИсточникMaskingОписание
UserIdGuidДаCallParticipantEntity.UserIdУчастник.
RoleParticipantRole?Нет.RoleОрг-роль на момент входа (снимок).
ConferenceRoleConferenceRoleДа.ConferenceRoleModerator/Speaker.
JoinedAtDateTimeOffset?Нет.JoinedAtМомент входа.
LeftAtDateTimeOffset?Нет.LeftAtМомент выхода; null для активного.
DurationSecondsint?Нет.DurationSecondsВремя в конференции; null до выхода.
ScreenShareStateScreenShareStateДа.ScreenShareStateNone/Sharing.
IsActiveboolДаcomputed (State == Joined)true — участник сейчас в комнате.

Предусловия и постусловия

Предусловия. Актор авторизован и имеет messenger.conferences.use; флаг CONFERENCE_CALLS_ENABLED включён. Чат существует, типа Deal и не Closed (INV-CONF-1); актор — его активный участник (INV-CHAT-4). На чат нет другой конференции в статусе Provisioning/Active (INV-CONF-2). Media-стек доступен для резервирования комнаты.

Постусловия. Создана ConferenceSessionEntity (Provisioning → Active, StartedByUserId = actor, StartedAt, MediaType, Room), инициатор — единственная строка ростера (ConferenceRole = Moderator, State = Joined, INV-CONF-6); ActiveParticipantCount = 1. При включённой политике и RECORDINGS_ENABLED заведена CallRecordingEntity (Status = Pending, Trigger = AutoPolicy, SessionKind = Conference). В группу chat:{ChatId} доставлен ConferenceInvite (комната + токен минтится при доставке). Kafka-события старт не публикует. При отказе резервирования комнаты сессия не создаётся (MSG_RECORDING_PROVISION_FAILED), состояние чата не меняется.

Алгоритм

  1. Контекст и доступ. Операцию выполняет активный участник Deal-чата (User); ActorUserId — из JWT; требуется право messenger.conferences.use. Конференции включены флагом CONFERENCE_CALLS_ENABLED, иначе — MSG_CONFERENCES_DISABLED.
  2. Проверка входа. ChatId непустой; MediaType — известное значение enum (Audio/Video), иначе ошибка валидации; Title (если задан) — ≤ 120.
  3. Загрузка чата и доступ. Чат по ChatId; нет → MSG_CHAT_NOT_FOUND. Актор — активный участник чата (INV-CHAT-4), иначе MSG_CHAT_ACCESS_DENIED. Чат должен быть типа Deal и не Closed (INV-CONF-1), иначе MSG_CONFERENCE_CHAT_INVALIDDirect-переписке используется 1:1-звонок).
  4. Guard одной активной. На чат допустима максимум одна конференция в статусе Provisioning/Active (INV-CONF-2, partial unique ix_conf_active); иначе MSG_CONFERENCE_ALREADY_ACTIVE.
  5. Резервирование комнаты. Формируется MediaRoom (детерминированное RoomName из Id + nonce, уникальное среди активных, INV-CONF-5); токены доступа/ICE/SDP не персистятся (INV-CONF-7). Если медиа-стек не поднял комнату — MSG_RECORDING_PROVISION_FAILED (сессия не создаётся).
  6. Решение о записи. Политика записи для конференций сделки (как правило — с записью) определяет авто-запись; при включении заводится CallRecordingEntity (Status = Pending, Trigger = AutoPolicy, SessionKind = Conference, call-recording.md). Запись гейтится флагом RECORDINGS_ENABLED; при выключенном флаге конференция стартует без записи.
  7. Создание сессии. Заводится ConferenceSessionEntity (Provisioning → Active, StartedByUserId = актор, StartedAt, MediaType, Room, Title) и строка ростера инициатора (call-participant.md): State = Joined, ConferenceRole = Moderator (INV-CONF-6); пересчитывается ActiveParticipantCount = 1.
  8. Приглашение. В том же согласованном изменении фиксируется намерение доставки ConferenceInvite (persist-then-push): после коммита realtime-dispatch пушит приглашение в группу chat:{ChatId} и минтит Room.AccessToken в момент доставки. Интеграционных Kafka-событий старт не публикует.
  9. Результат. Карточка ConferenceModel (Status = Active, Room присутствует) → HTTP 201 Created, Location: /api/messenger/conferences/{ConferenceId}.

Диаграмма

Побочные эффекты

ТипДетали
Integration eventsnone — старт конференции Kafka не публикует (conference-session.md)
Realtime push (hub)ConferenceInvitechat:{ChatId} (комната + токен, messenger-hub.md)
Токены доступаRoom.AccessToken/TURN-креды минтятся при доставке приглашения, не хранятся в realtime_outbox/БД, не логируются
External callsрезервирование медиа-комнаты в media-стеке (SFU); при отказе — MSG_RECORDING_PROVISION_FAILED
Cache / projections / searchсессия попадает в partial ix_conf_active (guard одной активной)
Notificationsoffline push «вас зовут на встречу» — вне конференс-операции (потребитель hub/notifications)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400ChatId = Empty, неизвестный MediaType, Title > 120«Проверьте параметры конференции»no retry
MSG_CHAT_NOT_FOUND404чата нет«Чат не найден»no retry
MSG_CHAT_ACCESS_DENIED403актор не активный участник чата«Нет доступа к чату»no retry
MSG_CONFERENCE_CHAT_INVALID409чат не Deal либо Closed (INV-CONF-1)«Конференция доступна только в чате сделки»no retry
MSG_CONFERENCE_ALREADY_ACTIVE409уже есть активная/Provisioning конференция на чат (INV-CONF-2)«Конференция в этом чате уже идёт»retry после завершения
MSG_CONFERENCES_DISABLED503CONFERENCE_CALLS_ENABLED = false«Конференции временно недоступны»retry позже
MSG_RECORDING_PROVISION_FAILED503media-стек не поднял комнату/рекордер«Не удалось запустить конференцию»retry позже

Acceptance Criteria

  • Старт с записью (happy). Дано: актор — активный участник Deal-чата, активной конференции на чат нет, флаги включены. Когда стартует видео-конференцию. Тогда 201 с ConferenceModel (Status = Active, Room присутствует, инициатор — модератор в ростере); участники чата получают ConferenceInvite; заведена запись Pending.
  • Guard одной активной. Дано: на чат уже есть конференция Provisioning/Active. Когда актор стартует вторую. Тогда 409 MSG_CONFERENCE_ALREADY_ACTIVE (INV-CONF-2); дубля не создаётся.
  • Не Deal-чат / закрыт. Дано: чат Direct либо Closed. Когда актор стартует конференцию. Тогда 409 MSG_CONFERENCE_CHAT_INVALIDDirect используется 1:1-звонок).
  • Не участник чата. Дано: актор не активный участник чата. Когда стартует. Тогда 403 MSG_CHAT_ACCESS_DENIED.
  • Media-стек не поднял комнату. Дано: SFU недоступен/не подтвердил комнату. Когда актор стартует. Тогда 503 MSG_RECORDING_PROVISION_FAILED, сессия не создаётся, повтор допустим позже.
  • Запись выключена флагом. Дано: RECORDINGS_ENABLED = false, политика требует запись. Когда актор стартует. Тогда конференция стартует без записи (RecordingStatus = null), а не отказывает.
  • Невалидный вход. Дано: неизвестный MediaType или Title > 120. Когда актор стартует. Тогда 400 VALIDATION_FAILED.
  • Гонка двух стартов. Дано: два параллельных старта на одном чате. Тогда партиал-уникальность ix_conf_active пропускает ровно один; второй — 409 MSG_CONFERENCE_ALREADY_ACTIVE.

Совместимость и наблюдаемость

  • Новые optional поля ответа — additive; смена маршрута/семантики статуса — breaking. Kafka-контракта у старта нет.
  • Logs: conference start requested/created/room-provision-failed (ConferenceId, ChatId, MediaType, WasRecorded; токены — никогда в логи); metrics: messenger_conference_start_total{result}, latency p95; traces — сквозной trace (traceparent); dashboard pin-messenger.

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

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

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