POST /api/messenger/conferences/{conferenceId}/leave — Выйти из конференции
Участник покидает идущую конференцию. Строка ростера (call-participant.md)
переводится в Left (soft — строка сохраняется в истории участия), пересчитывается активный состав. Дом полей и
инварианты — conference-session.md.
Назначение
Дать участнику корректно выйти из конференции: зафиксировать выход и длительность участия, обновить активный
ростер и уведомить остальных. Выход последнего активного участника не завершает сессию мгновенно —
конференция остаётся Active короткое grace-окно, после которого её закрывает
conference-room-reaper (EmptyTimeout). На выходе — актуальная
карточка конференции. Owning-сервис — messenger, feature group conferences.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | messenger |
| API area | conferences |
| Feature group | conferences |
| Статус | draft |
| Документ | docs/06-services/messenger/api/conferences/leave-conference.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User — участник конференции (State = Joined) |
| Auth policy / Permissions | permission messenger.conferences.use |
| Scope (RBAC) | не применяется — доступ по членству в чате |
| Record-level | активная строка ростера актора в конференции; членство в Deal-чате (INV-CHAT-4) |
| Tenant isolation | не применяется (кросс-tenant); срез — по членству |
| Audit | update ростера: → Left, метки LeftAt/LeaveReason (SelfLeft), DurationSeconds |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/messenger/conferences/{conferenceId}/leave |
| Success status | 200 (OK) |
| Idempotency header | не применяется — повтор на уже Left-строке no-op (участник не активен) |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
ConferenceId | route | Guid | Да | != Empty; существующая конференция, актор — участник ростера (шаги 3–4) | — |
ActorUserId | identity claims | Guid | Да | из JWT; строка ростера актора | — |
Тела запроса нет. LeaveReason при вызове клиентом — SelfLeft; выходы по дисконнекту/удалению модератором
фиксируются presence-вебхуком media-стека и end-операцией, не этим методом.
Модель ответа ConferenceModel
Проекция ConferenceSessionEntity с обновлённым ростером (дом полей —
start-conference.md). Поле Room отсутствует (выход не выдаёт токен): актор уже
покинул комнату.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
ConferenceId | Guid | Да | ConferenceSessionEntity.Id | — | Идентификатор конференции. |
Status | ConferenceStatus | Да | .Status | — | Обычно ещё Active (закрытие — за reaper-ом). |
ActiveParticipantCount | int | Да | .ActiveParticipantCount | — | Активных после выхода. |
Participants | ConferenceParticipantEntry[] | Да | ростер (call-participant.md) | — | Актор — с IsActive = false, LeftAt, DurationSeconds. |
Полный состав полей карточки — ConferenceModel в start-conference.md; здесь перечислена
значимая для выхода дельта, Room = null.
Предусловия и постусловия
Предусловия. Актор авторизован и имеет messenger.conferences.use; активный участник Deal-чата (INV-CHAT-4).
Как правило, у актора есть строка ростера в Joined (но отсутствие/Left — допустимый no-op, см. Алгоритм).
Постусловия. Строка ростера актора — State = Left, LeftAt, LeaveReason = SelfLeft,
DurationSeconds = LeftAt − JoinedAt (≥ 0) (INV-CPART-2/3); ActiveParticipantCount пересчитан. Если вышел
модератор и остались активные — роль модератора передана старейшему активному (INV-CONF-6). В канал
chat:{ChatId} отправлен MediaParticipant(Left) (при передаче модерации — ещё ConferenceSignal). Сессия
остаётся Active даже при опустошении: фактическое закрытие пустой комнаты с EmptyTimeout выполняет
conference-room-reaper. Kafka-события выход не публикует.
Алгоритм
- Контекст и доступ. Операцию выполняет участник конференции (
User);ActorUserId— из JWT; требуется правоmessenger.conferences.use. - Проверка входа.
ConferenceIdнепустой; иначе ошибка валидации. - Загрузка сессии. Конференция по
ConferenceId; нет →MSG_CONFERENCE_NOT_FOUND. Актор — активный участник Deal-чата (INV-CHAT-4); иначеMSG_CHAT_ACCESS_DENIED. - Выход из ростера. Строка
(ConferenceId, ActorUserId): еслиJoined→State = Left,LeftAt,LeaveReason = SelfLeft,DurationSeconds = LeftAt − JoinedAt(INV-CPART-2/3); если строки нет или она ужеLeft— no-op (успех, без ошибки). ПересчитываетсяActiveParticipantCount. Если вышел модератор и остаются активные — роль передаётся старейшему активному (INV-CONF-6). - Присутствие. В том же изменении фиксируется намерение push
MediaParticipant(Left)вchat:{ChatId}(persist-then-push), а при передаче модерации —ConferenceSignal(смена модератора). Если активных не осталось — сессия остаётсяActive; фактическое закрытие сEmptyTimeoutвыполняет conference-room-reaper. - Результат. Актуальная карточка
ConferenceModel(безRoom) → HTTP200 OK.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | none — сам выход Kafka не публикует; закрытие пустой комнаты reaper-ом даёт pin.messenger.conference-ended.v1 (conference-ended.md) |
| Realtime push (hub) | MediaParticipant(Left) → chat:{ChatId}; при передаче модерации — ConferenceSignal |
| Cache / projections / search | пересчёт ActiveParticipantCount; ростер (ix_call_participants_active) |
| Notifications | нет |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | ConferenceId = Empty | «Проверьте параметры запроса» | no retry |
MSG_CONFERENCE_NOT_FOUND | 404 | конференции нет | «Конференция не найдена» | no retry |
MSG_CHAT_ACCESS_DENIED | 403 | актор не участник Deal-чата | «Нет доступа к конференции» | no retry |
Повтор выхода и выход при уже терминальной конференции — no-op (200, участник не активен), отдельного кода
состояния не вводится.
Acceptance Criteria
- Штатный выход (happy). Дано: актор
JoinedвActive-конференции, кроме него есть активные. Когда выходит. Тогда200, его строка →LeftсDurationSeconds,ActiveParticipantCountуменьшен; остальные получаютMediaParticipant(Left); сессия остаётсяActive. - Выход модератора. Дано: актор — модератор, есть другие активные. Когда выходит. Тогда роль модератора
передана старейшему активному (INV-CONF-6), отправлен
ConferenceSignalо смене модератора. - Выход последнего. Дано: актор — единственный активный. Когда выходит. Тогда
ActiveParticipantCount = 0, ноStatusостаётсяActive; закрытие сEmptyTimeoutпозже выполнит reaper (не мгновенно). - Повторный выход / нет строки (идемпотентность). Дано: строка актора уже
Leftлибо отсутствует. Когда выходит. Тогда200no-op без ошибки, состояние не меняется, лишнего push нет. - Не участник чата. Дано: актор не активный участник Deal-чата. Когда выходит. Тогда
403 MSG_CHAT_ACCESS_DENIED. - Нет конференции. Дано:
ConferenceIdне существует. Тогда404 MSG_CONFERENCE_NOT_FOUND.
Совместимость и наблюдаемость
- Новые optional поля ответа — additive; смена маршрута — breaking. Kafka-контракта у выхода нет.
- Logs:
conference leave requested/left/moderator-transferred(ConferenceId,ActorUserId,DurationSeconds); metrics:messenger_conference_leave_total{result}, latency p95; traces — сквозной trace (traceparent); dashboardpin-messenger.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Aggregate: ConferenceSessionEntity — групповой звонок в чате сделки
- API (6): GET /api/messenger/conferences/{conferenceId} — Карточка конференции, POST /api/messenger/chats/{chatId}/conferences — Начать конференцию в чате сделки, POST /api/messenger/conferences/{conferenceId}/decline — Отклонить приглашение в конференцию, POST /api/messenger/conferences/{conferenceId}/end — Завершить конференцию для всех, POST /api/messenger/conferences/{conferenceId}/join — Присоединиться к конференции, POST /api/messenger/conferences/{conferenceId}/participants/{userId}/remove — Удалить участника из конференции
- Use Cases (1): UC-MSG-006. Групповой звонок-конференция в чате сделки
- calls (1): Жизненный цикл сессии звонка — 1:1 и конференция (messenger)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность (дом полей,
INV-CONF-6): conference-session.md; ростер (INV-CPART-2/3,ParticipantLeaveReason): call-participant.md. - Авто-завершение пустой комнаты: conference-room-reaper.md; событие завершения: conference-ended.md.
- Соседние операции: start-conference.md, join-conference.md, end-conference.md, get-conference.md; контекст: README.
- ADR: 0062 медиа-стек и запись, 0055 свой мини-мессенджер.