# ADR-037: Идентификатор комнаты генерирует клиент ## Контекст `docs/crypto.md` вплетает `roomId` в заворачивание ключа комнаты дважды: в `info` вывода `wrapK` и в AAD шифротекста. Ключ заворачивается до запроса — `POST /api/rooms` несёт `keys[]` с готовым шифротекстом. `docs/protocol.md` и `docs/storage.md` при этом отдавали выдачу `roomId` серверу. Создатель комнаты обязан привязать ключ к идентификатору, которого ещё не существует. Обойти это нечем. Ключ себе при создании — не формальность: ADR-018 требует его ровно для других устройств создателя, и без него второе устройство получает комнату с нечитаемым ключом. Эндпоинта, который дослал бы ключ после ответа, в протоколе нет; `POST /api/rooms/{id}/members` меняет `keyId`, то есть делает rekey сразу после создания — лишний круг и комната без действующего ключа в промежутке. ## Решение - `roomId` генерирует клиент: 16 случайных байт base64url — как `deviceId` (ADR-017) и `keyId`. - `POST /api/rooms` принимает `id`. Сервер проверяет форму, как у любого идентификатора, и отвергает занятый — `409 room_conflict`. Клиент берёт новый идентификатор и повторяет, как при `device_conflict`. - Слияния с существующей комнатой нет: повторный `POST` с занятым `id` не присоединяет и не перезаписывает. - Правятся `docs/protocol.md` (тело запроса и перечень кодов) и `docs/storage.md` (комментарий к `rooms.id`). ## Следствия - Заворачивание себе при создании привязано к настоящему `roomId`, и второе устройство создателя читает комнату. - Сервер доверяет клиенту не больше прежнего: он принимает форму идентификатора и отказывает занятому. - `roomId` был и остаётся метаданными — он открыт серверу в любом случае. Угадывание чужого идентификатора ничего не даёт: доступ проверяется по `room_members`, а не по знанию `id`. - Коллизия 128 случайных бит невозможна на практике; `room_conflict` существует ради целостности, а не ради сценария.