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

POST /api/messenger/conferences/{conferenceId}/recording/stop — Остановить запись конференции

Модератор конференции (conference-session.md) вручную останавливает идущую запись (call-recording.md): перевод активной записи Recording → Uploading, финализация — асинхронно через ingest. Ручного старта записи нет — старт задаёт политика (RecordingPolicy); остановка нужна для согласия/комплаенса (участник-модератор может прекратить запись чувствительной части встречи).

Назначение

Дать модератору контроль над записью: прекратить захват до штатного завершения конференции, зафиксировав уже записанное. Захват может стартовать автоматически по политике; право остановить запись — механизм согласия и комплаенса (запись без произвольного продолжения). После остановки файл дописывается и выгружается тем же путём, что при завершении конференции. Owning-сервис — messenger, feature group recordings.

Решение контракта: метод останавливает, но не запускает запись. Старт — RecordingPolicy.ShouldAutoRecord при старте сессии (INV-REC-3); отдельного «start recording» эндпоинта нет, чтобы политика записи оставалась единственным источником решения о старте.

Metadata

ПолеЗначение
Сервис/контекстmessenger
API arearecordings
Feature grouprecordings
Статусdraft
Документdocs/06-services/messenger/api/recordings/stop-recording.md

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

ПроверкаЗначение
Actor typeUser — модератор конференции
Auth policy / Permissionspermission messenger.recordings.manage
Scope (RBAC)не применяется (доступ — по роли в конференции/сделке)
Record-levelмодератор: actor.UserId == StartedByUserId конференции или владелец сделки (Deal.OwnerUserId) её ChatId
Tenant isolationнет (кросс-tenant Deal-чата); изоляция — по роли модератора
Auditupdate: переход записи Recording → Uploading, метка StoppedAt, инициатор остановки

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/messenger/conferences/{conferenceId}/recording/stop
Success status200 (OK)
Idempotency headerне применяется — повтор ловит guard состояния (шаг 5): вторая остановка застаёт Uploading/терминал → MSG_RECORDING_INVALID_STATE
Correlationсквозной trace через traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ConferenceIdrouteGuidДа!= Empty; существующая конференция, у неё активная запись в Recording, актор — модератор (шаги 3–5)
ActorUserIdidentity claimsGuidДаиз JWT; модератор конференции

Тела запроса нет.

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

Проекция CallRecordingEntity (дом — сущность). Дельта: Status = Uploading, проставлен StoppedAt; FileId/SizeBytes/DurationSeconds/Format ещё null (заполнятся при финализации через ingest, → Available). Полное описание полей — get-recording.md.

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

Предусловия. Конференция существует и активна (Active); по политике авто-записи (INV-REC-3) у неё есть активная запись в статусе Recording; актор — модератор конференции (стартер или владелец сделки её ChatId); RECORDINGS_ENABLED = true.

Постусловия (при успехе). Запись переведена Recording → Uploading, проставлен StoppedAt; внешнему рекордеру подан сигнал остановки захвата; в hub-группу chat:{ChatId} доставлен RecordingState(Stopped) — индикатор записи погас у участников; интеграционное событие не публикуется (готовность придёт позже, на ingest). Файл и метаданные (FileId/SizeBytes/DurationSeconds/Format) в этой операции ещё не заполнены — финализация асинхронна. При любом отказе состояние записи не меняется.

Алгоритм

  1. Контекст и доступ. Операцию выполняет модератор (User); ActorUserId — из JWT; требуется право messenger.recordings.manage. Запись включена флагом RECORDINGS_ENABLED, иначе MSG_RECORDINGS_DISABLED.
  2. Проверка входа. ConferenceId непустой; иначе ошибка валидации.
  3. Загрузка. Конференция по ConferenceId; нет → MSG_CONFERENCE_NOT_FOUND. Её активная запись; нет → MSG_RECORDING_NOT_FOUND.
  4. Право. Актор должен быть модератором: ActorUserId == StartedByUserId конференции или владелец сделки её ChatId. Иначе → MSG_CONFERENCE_FORBIDDEN.
  5. Переход. Допустимый источник (INV-REC-2): Status == Recording. Прочие статусы (Pending/Uploading/ терминал) → MSG_RECORDING_INVALID_STATE. При успехе внешнему рекордеру подаётся команда остановки, запись переводится Recording → Uploading, проставляется StoppedAt. Финализация (FileId/метаданные, → Available) выполняется асинхронно ingest-эндпоинтом рекордера (../internal/jibri-recording-ingest.md) — не в этой операции.
  6. Согласованность. Переход → Uploading и намерение push фиксируются атомарно до push (persist-then-push). Конкурентные акторы, способные перевести ту же запись в Uploading, — штатное завершение конференции (end-conference) и conference-room-reaper (сигнал остановки рекордера при закрытии осиротевшей комнаты): побеждает первый переход, проигравший видит на перечитанном состоянии не-RecordingMSG_RECORDING_INVALID_STATE (повтор — no-op). Итоговое состояние сходится к единственному Uploading независимо от того, кто остановил запись.
  7. Доставка сигнала. После сохранения — RecordingState(Stopped) в hub-группу chat:{ChatId} (без PII/URL): индикатор записи гаснет у участников.
  8. Результат. RecordingModel (Status = Uploading, StoppedAt) → HTTP 200 OK.

Диаграмма

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

ТипДетали
Integration eventsnone в этой операции — pin.messenger.call-recording-available.v1 публикует ingest при → Available
Realtime push (hub)RecordingState(Stopped)chat:{ChatId} (non-PII, без URL) — индикатор записи гаснет
External callsвнешний рекордер media-стека: команда остановки захвата (финализация — асинхронно)
Cache / projections / searchзапись выходит из активной (ix_recording_active — по Uploading ещё активна до Available)
Notificationsсистемная строка «запись остановлена» — по политике чата (опционально)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400ConferenceId = Empty«Проверьте параметры запроса»no retry
MSG_CONFERENCE_NOT_FOUND404конференции нет«Конференция не найдена»no retry
MSG_RECORDING_NOT_FOUND404у конференции нет активной записи«Активная запись не найдена»no retry
MSG_CONFERENCE_FORBIDDEN403актор не модератор (не стартер и не владелец сделки)«Недостаточно прав для остановки записи»no retry
MSG_RECORDING_INVALID_STATE409статус записи не Recording (уже Uploading/терминал, INV-REC-2)«Запись уже остановлена»no retry (перечитать)
MSG_RECORDINGS_DISABLED503RECORDINGS_ENABLED = false«Записи временно недоступны»retry позже

Acceptance Criteria

  • Given конференция с активной записью Recording и актор-модератор, When останавливает запись, Then 200, RecordingModel с Status = Uploading и StoppedAt; рекордеру подан сигнал остановки; RecordingState(Stopped) доставлен в chat:{ChatId} — индикатор погас.
  • Given запись уже Uploading/терминал (повтор или гонка с end-conference/reaper), When модератор повторно останавливает, Then 409 MSG_RECORDING_INVALID_STATE (состояние не меняется, no-op).
  • Given актор не модератор (не стартер и не владелец сделки), When останавливает запись, Then 403 MSG_CONFERENCE_FORBIDDEN.
  • Given у конференции нет активной записи, When запрос остановки, Then 404 MSG_RECORDING_NOT_FOUND.
  • Given конференции нет, When запрос, Then 404 MSG_CONFERENCE_NOT_FOUND.
  • Given RECORDINGS_ENABLED = false, When запрос, Then 503 MSG_RECORDINGS_DISABLED.

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

  • Новые optional-поля ответа — additive; смена маршрута/семантики перехода — breaking. Kafka-контракта у операции нет (событие готовности — на ingest).
  • Logs: recording stop requested/stopped/invalid-state (ConferenceId, RecordingId, модератор); metrics: messenger_recording_stop_total{result}, latency p95; correlation через traceparent; PII/URL/бинарь в логи не пишутся.

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

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

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