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

POST/DELETE /internal/jitsi/reservations — Резервирование медиа-комнаты (prosody admission)

Граница интеграции media-стека: prosody mod_reservations спрашивает messenger, можно ли поднять MUC-комнату по имени, а при уничтожении комнаты — уведомляет об этом. Это единственный авторитет допуска комнат: комната существует только если её имя совпадает с RoomName живой сессии PIN (call-session.md / conference-session.md), провизированной бэкендом. Самоподнятых комнат в развёртывании нет.

Граница именования. Это внутренний вебхук от media-стека — имена Jitsi/prosody/mod_reservations здесь предметны и намеренны. Наружу (в api/calls/api/conferences/домене) media-стек описывается нейтрально (SFU, ADR-0062).

Назначение

mod_reservations вызывает POST /internal/jitsi/reservations перед созданием MUC-комнаты и DELETE /internal/jitsi/reservations/{roomName} при её уничтожении. На POST messenger разрешает или запрещает комнату: имя должно точно совпадать с Room.RoomName живой (входибельной) сессии; при разрешении возвращается дескриптор JitsiReservationModel (лимиты, флаг записи), по которому prosody конфигурирует комнату. Запрет → prosody отклоняет создание комнаты. DELETE — сигнал опустошения комнаты: участники помечаются вышедшими, финальное завершение сессии отдаётся reaper-у.

Metadata

ПолеЗначение
Сервис/контекстmessenger
API areainternal (вебхук media-стека)
Feature groupmedia (jitsi-интеграция)
Статусdraft
Документdocs/06-services/messenger/api/internal/jitsi-reservation.md

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

ПроверкаЗначение
Actor typeExternalSystem — prosody mod_reservations (не OIDC-клиент)
Auth policy / Permissionsподписанный вебхук: заголовок X-Pin-Signature = HMAC над сырым телом + X-Pin-Timestamp, окно replay ≤ 5 мин, общий секрет; либо mTLS. Пользовательских прав нет (m2m)
Scope (RBAC)не применяется (доверенная граница инфраструктуры)
Record-levelдоступ по совпадению Name с Room.RoomName конкретной сессии — сам предмет проверки (fail-closed)
Tenant isolationне применяется; сессия резолвится по имени комнаты, не по TenantId
Auditupdate: провизия/завершение сессии по admission — иммутабельные фазовые метки сессии
Trusted boundaryвход подписан/mTLS, но валидируется полностью: подпись → парсинг имени → совпадение RoomName → допустимость состояния

HTTP-контракт

ПолеЗначение
MethodPOST (allow-решение) / DELETE (room destroyed)
RoutePOST /api/messenger/internal/jitsi/reservations ; DELETE /api/messenger/internal/jitsi/reservations/{roomName}
Success statusPOST200 (allow, тело JitsiReservationModel) / 403,409 (deny) ; DELETE200 (ack)
Idempotencyпо RoomName (сессии): повтор POST на входибельной сессии отдаёт тот же дескриптор; повтор DELETE/на терминале — no-op ack
Correlationсквозной trace через traceparent

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

POSTJitsiReservationRequest

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
X-Pin-SignatureheaderstringДаHMAC над сырым телом; несовпадение → MSG_JITSI_SIGNATURE_INVALIDсекрет не логируется
X-Pin-TimestampheaderDateTimeOffsetДаокно replay ≤ 5 мин
NamebodystringДаMUC-localpart комнаты; из него парсится sessionId + nonce; шаблон Room.RoomName; несовпадение → MSG_JITSI_ROOM_UNKNOWNмаршрутный id, не PII
MucRoomJidbodystringДаполный JID комнаты (Name@conference.<domain>)
StartTimebodyDateTimeOffset?Нетзаявленное время старта комнаты (эхо в ответ)
MailOwnerbodystring?Нетвладелец из токена создателя (может отсутствовать)не используется как доступ
RoomParambodystring?Нетдоп. параметры от prosody (игнорируются, если не распознаны)

DELETE — параметр маршрута

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
X-Pin-Signature / X-Pin-TimestampheaderДакак выше
roomNameroutestringДаRoom.RoomName уничтожаемой комнаты; неизвестное имя → тихий 200 ack (комнаты уже нет — нечего закрывать)

Модель ответа JitsiReservationModel (только POST, allow)

Дескриптор допуска комнаты для prosody. Recording — единственное производное решение: включён ли для сессии захват (есть активная запись). Ссылок/токенов/медиа в ответе нет.

ПолеТипОбязательностьИсточникMaskingОписание
IdstringДаsessionId (звонок/конференция)Идентификатор сессии PIN, к которой привязана комната.
NamestringДаRoom.RoomNameПодтверждённое имя комнаты (эхо).
StartTimeDateTimeOffsetДасессия / запросВремя старта комнаты.
Durationint?Нетpolicy-capПредельная длительность комнаты в секундах (после — reaper закрывает).
RecordingboolДаналичие CallRecordingEntity в Pending для сессииНужно ли поднимать jibri (авто-запись по политике).
MaxOccupantsint?НетpolicyЛимит участников комнаты.
LobbyboolДаfalseЛобби выключено: допуск решается бэкендом, не лобби-модерацией.

Алгоритм

  1. Контекст и доступ. Вызывает prosody mod_reservations (ExternalSystem). Сначала проверяется подпись: X-Pin-Signature над сырым телом + свежесть X-Pin-Timestamp (окно ≤ 5 мин); невалидно/просрочено → MSG_JITSI_SIGNATURE_INVALID. Media-стек активен за CALLS_MEDIA_ENABLED (1:1) / CONFERENCE_CALLS_ENABLED (конференции); при выключенном флаге допуск не выдаётся.
  2. Резолв сессии по имени. Из Name парсятся sessionId + nonce (детерминированная схема Room.RoomName). Загружается сессия-владелец: 1:1-звонок (call-session.md) или конференция (conference-session.md). Сессия не найдена или Name не совпадает точно со стором Room.RoomNameMSG_JITSI_ROOM_UNKNOWN (fail-closed: только backend-провизированные комнаты имеют право на существование).
  3. Допустимость состояния. Комната поднимается только для входибельной сессии: конференция в Provisioning/Active, 1:1-звонок в Ringing/Answered. Терминальные/иные статусы → deny (MSG_CONFERENCE_INVALID_STATE / MSG_CALL_INVALID_STATE) — prosody отклоняет создание комнаты.
  4. Решение о записи. Recording = true, если для сессии есть запись в Pending (call-recording.md, INV-REC-7) — по ней prosody активирует jibri; иначе false. Флаг записи решается политикой при старте сессии, здесь только считывается.
  5. Провизия на allow. Для конференции первое успешное резервирование фиксирует admission-подтверждение комнаты: Provisioning → Active (conference-session.md, INV-CONF-4, StartedAt). Повторный POST на уже Active — идемпотентный no-op с тем же дескриптором. Для 1:1 переход состояния не требуется (комната минтится при → Ringing).
  6. Результат POST. Собирается JitsiReservationModel (Id, Name, лимиты, Recording, Lobby=false) → 200 OK. Deny → соответствующий 4xx (prosody не создаёт комнату).
  7. DELETE (room destroyed). По roomName резолвится сессия; активный ростер помечается вышедшим (State = Left, LeaveReason = Disconnected/ConferenceEnded, call-participant.md), LastActivityAt обновляется. Финальное завершение сессии не форсируется здесь — отдаётся conference-room-reaper (grace-окно), чтобы не гонять с немедленным реконнектом/пересозданием комнаты. Неизвестное имя → тихий 200 ack. Всё применяется в одном согласованном изменении.

Диаграмма

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

ТипДетали
Realtime push (hub)конференция Provisioning → Active — доменный ConferenceStartedEvent уже раздал ConferenceInvite при старте; admission сам инвайтов не шлёт
Integration eventsnone напрямую; завершение сессии (после reaper) публикует pin.messenger.conference-ended.v1 из своего пути
External callsnone (это входящий вебхук; допуск/запрет — синхронный ответ prosody)
Cache / projectionsконференция входит/выходит из partial ix_conf_active; ростер — ix_call_participants_active

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
MSG_JITSI_SIGNATURE_INVALID401подпись не сошлась / timestamp вне окна replay«Подпись запроса недействительна»no retry (переподписать)
MSG_JITSI_ROOM_UNKNOWN403Name не резолвится в сессию или не совпадает с Room.RoomName«Комната неизвестна»no retry (prosody отклоняет комнату)
MSG_CONFERENCE_INVALID_STATE409конференция не в Provisioning/Active (терминал)«Конференция недоступна»no retry
MSG_CALL_INVALID_STATE4091:1-звонок не в Ringing/Answered«Звонок недоступен»no retry
VALIDATION_FAILED400пустой/некорректный Name/MucRoomJid«Проверьте параметры запроса»no retry

DELETE на неизвестное/уже удалённое имя — не ошибка (200 ack): цель уже достигнута.

Acceptance Criteria

  • Allow конференции (первое резервирование). Дано конференция в Provisioning с провизированным Room.RoomName, валидная подпись, Name совпал точно. Когда POST. Тогда 200 c JitsiReservationModel (Lobby = false, лимиты, Recording — по наличию Pending-записи), конференция Provisioning → Active (INV-CONF-4, StartedAt).
  • Повторное резервирование. Дано конференция уже Active. Когда повторный POST. Тогда идемпотентный no-op, отдаётся тот же дескриптор.
  • Allow 1:1-звонка. Дано звонок в Ringing/Answered. Когда POST. Тогда 200 c дескриптором, без смены состояния (комната минтится при → Ringing).
  • Deny по имени. Дано Name не резолвится в сессию или не совпал с Room.RoomName. Когда POST. Тогда MSG_JITSI_ROOM_UNKNOWN (403) — prosody не создаёт комнату (самоподнятых комнат нет, fail-closed).
  • Deny по состоянию. Дано конференция в терминальном статусе → MSG_CONFERENCE_INVALID_STATE (409); 1:1 не в Ringing/AnsweredMSG_CALL_INVALID_STATE (409). prosody отклоняет комнату.
  • Флаг записи. Дано для сессии есть Pending-запись (INV-REC-7). Когда allow. Тогда Recording = true → prosody активирует jibri; иначе false.
  • Уничтожение комнаты. Дано существующая сессия. Когда DELETE /{roomName}. Тогда активный ростер → Left (LeaveReason), LastActivityAt обновлён, финал сессии отдаётся reaper, 200 ack.
  • DELETE неизвестной комнаты. Дано имя уже не соответствует живой комнате. Когда DELETE. Тогда тихий 200 ack (цель достигнута).
  • Неверная подпись. Дано подпись не сошлась / timestamp вне окна. Когда запрос. Тогда MSG_JITSI_SIGNATURE_INVALID (401).

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

  • Новые optional-поля JitsiReservationModel (MaxOccupants, Duration) — additive; смена схемы имени комнаты или семантики допуска — breaking (согласованный rollout prosody-модуля). Контракт вебхука версионируется вместе с media-развёртыванием (ADR-0062).
  • Logs: reservation allow/deny (RoomName, sessionId, reason), room destroyed — без токенов/PII; metrics: messenger_jitsi_reservation_total{result,reason}, messenger_jitsi_room_destroyed_total; traces — сквозной traceparent; alert на всплеск deny=room-unknown (рассинхрон имён/попытка самоподнятия комнаты).

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

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

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