POST /api/messenger/conferences/{conferenceId}/decline — Отклонить приглашение в конференцию
Приглашённый участник отказывается от адресного приглашения в конференцию. Его строка ростера
(call-participant.md) переводится Invited → Declined (терминальная для
этого приглашения; строка сохраняется в истории участия). Дом полей, статусы и инварианты —
conference-session.md.
Назначение
Дать приглашённому (получившему ConferenceInvite в user:{UserId}) корректно отклонить звонок: зафиксировать
Declined и снять «звенящий» экран, уведомив остальных, что он не придёт. Это единственный путь, которым строка
ростера достигает состояния Declined. Медиа-комната и токен приглашённому не выдаются. Owning-сервис —
messenger, feature group conferences, сценарий «групповой звонок по сделке».
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | messenger |
| API area | conferences |
| Feature group | conferences |
| Статус | draft |
| Документ | docs/06-services/messenger/api/conferences/decline-conference.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User — сам приглашённый (actor.UserId == строка ростера, State = Invited) |
| Auth policy / Permissions | permission messenger.conferences.use |
| Scope (RBAC) | не применяется — доступ по членству в чате |
| Record-level | собственная строка ростера актора в статусе Invited; членство в Deal-чате (INV-CHAT-4) |
| Tenant isolation | не применяется (кросс-tenant); срез — по членству |
| Audit | update ростера: Invited → Declined, метка LeftAt |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/messenger/conferences/{conferenceId}/decline |
| Success status | 200 (OK) |
| Idempotency header | не применяется — повтор ловит guard состояния (шаг 4): строка уже не Invited → MSG_CONFERENCE_INVALID_STATE |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
ConferenceId | route | Guid | Да | != Empty; существующая конференция, у актора есть строка ростера Invited (шаги 3–4) | — |
ActorUserId | identity claims | Guid | Да | из JWT; собственная строка ростера актора | — |
Тела запроса нет. Отклонить можно только свою строку приглашения; чужую строку снимает модератор (remove-participant.md).
Модель ответа ConferenceParticipantStateModel
Лёгкий ack без дескриптора входа (Room не выдаётся — приглашение отклонено). Полную карточку с ростером при
необходимости даёт get-conference.md.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
ConferenceId | Guid | Да | ConferenceSessionEntity.Id | — | Конференция, приглашение в которую отклонено. |
UserId | Guid | Да | CallParticipantEntity.UserId | — | Актор (приглашённый). |
State | CallParticipantState | Да | .State | — | Declined на успехе. |
Предусловия и постусловия
Предусловия. Актор авторизован и имеет messenger.conferences.use; активный участник Deal-чата (INV-CHAT-4).
У актора есть строка ростера в статусе Invited (иначе — ошибки шага 4).
Постусловия. Строка ростера актора — State = Declined, LeftAt. Поднят
ConferenceParticipantDeclinedEvent → в той же транзакции записано намерение доставки (persist-then-push): после
коммита realtime-dispatch пушит MediaParticipant(Left) в chat:{ChatId} (участники видят, что приглашённый
отказался). ActiveParticipantCount не меняется (приглашённый и не был активен). Медиа-комната/токен не
выдавались. Kafka-события отклонение не публикует.
Алгоритм
- Контекст и доступ. Операцию выполняет приглашённый (
User);ActorUserId— из JWT; требуется правоmessenger.conferences.use. - Проверка входа.
ConferenceIdнепустой; иначе ошибка валидации. Тела запроса нет. - Загрузка сессии и строки. Конференция по
ConferenceId; нет →MSG_CONFERENCE_NOT_FOUND. Актор — активный участник Deal-чата (INV-CHAT-4); иначеMSG_CHAT_ACCESS_DENIED. Строка ростера(ConferenceId, ActorUserId); нет →MSG_CONFERENCE_PARTICIPANT_NOT_FOUND. - Переход. Требуется
State == Invited; иначеMSG_CONFERENCE_INVALID_STATE(ужеJoined/Left/Declined— отклонять нечего; вошедший выходит через leave-conference.md). При успехе —State = Declined,LeftAt; поднимаетсяConferenceParticipantDeclinedEvent. - Присутствие. В том же согласованном изменении фиксируется намерение push
MediaParticipant(Left)вchat:{ChatId}(persist-then-push) — участники убирают «ожидаем» у отказавшегося. Токен/комната не выдаются. - Результат. Лёгкий ack
ConferenceParticipantStateModel(State = Declined) → HTTP200 OK.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | none — отклонение Kafka не публикует |
| Realtime push (hub) | MediaParticipant(Left) → chat:{ChatId} (messenger-hub.md) |
| Cache / projections / search | ростер (ix_call_participants_active не затрагивается — Invited/Declined не активны) |
| 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 |
MSG_CONFERENCE_PARTICIPANT_NOT_FOUND | 404 | у актора нет строки ростера | «Приглашение не найдено» | no retry |
MSG_CONFERENCE_INVALID_STATE | 409 | строка не Invited (уже Joined/Left/Declined) | «Приглашение уже недействительно» | no retry (перечитать) |
MSG_CONFERENCES_DISABLED | 503 | CONFERENCE_CALLS_ENABLED = false | «Конференции временно недоступны» | retry позже |
Acceptance Criteria
- Отклонение приглашения (happy). Дано: у актора строка ростера
Invitedв конференции. КогдаPOST /decline. Тогда200, строка →Declined(LeftAt),State = Declinedв ответе; остальные получаютMediaParticipant(Left)вchat:{ChatId}; токен/комната не выдаются. - Уже вошёл. Дано: строка актора
Joined. КогдаPOST /decline. Тогда409 MSG_CONFERENCE_INVALID_STATE(вошедший выходит через leave-conference.md, не decline). - Нет строки. Дано: актора нет в ростере (не приглашался). Когда
POST /decline. Тогда404 MSG_CONFERENCE_PARTICIPANT_NOT_FOUND. - Повтор. Дано: строка актора уже
Declined. КогдаPOST /decline. Тогда409 MSG_CONFERENCE_INVALID_STATE(перечитать состояние). - Не участник чата. Дано: актор не активный участник Deal-чата. Когда
POST /decline. Тогда403 MSG_CHAT_ACCESS_DENIED. - Нет конференции. Дано:
ConferenceIdне существует. Тогда404 MSG_CONFERENCE_NOT_FOUND.
Совместимость и наблюдаемость
- Новые optional поля ответа — additive; смена маршрута — breaking. Kafka-контракта нет.
- Logs:
conference decline requested/declined/invalid-state(ConferenceId,ActorUserId); metrics:messenger_conference_decline_total{result}, latency p95; traces — сквозной trace (traceparent); dashboardpin-messenger.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (2): Entity / Aggregate: ConferenceSessionEntity — групповой звонок в чате сделки, Entity: CallParticipantEntity (owned-child ConferenceSessionEntity)
- API (2): POST /api/messenger/conferences/{conferenceId}/invite — Пригласить участников в конференцию, POST /api/messenger/conferences/{conferenceId}/participants/{userId}/remove — Удалить участника из конференции
- calls (1): Жизненный цикл сессии звонка — 1:1 и конференция (messenger)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность (дом полей,
State,ConferenceParticipantDeclinedEvent): call-participant.md; конференция: conference-session.md. - Событие
MediaParticipant: messenger-hub.md. - Соседние операции: invite-participant.md, join-conference.md, leave-conference.md, remove-participant.md; контекст: README.
- Разбор жизненного цикла: session-lifecycle.md.
- ADR: 0062 медиа-стек и запись, 0055 свой мини-мессенджер.