## MODIFIED Requirements ### Requirement: Приём записи по HTTP Сервис SHALL принимать запись запросом `POST /app/audiorecords` с телом `multipart/form-data` и полем `audio` **только от узнанного отправителя**. Запрос от неузнанного MUST получать код `401`, и по нему MUST не заводиться ни файл, ни аудиозапись. Принятая запись от узнанного отправителя MUST быть сохранена и получить заведённую под неё аудиозапись на рубеже `uploaded`. Приём стоит тем же адресом, что и список записей, и отличается от него только методом: он **заводит аудиозапись**, а не кладёт файл. Прежнее имя называло содержимое запроса, и по нему приём читался как отдельная от записи вещь — хотя запись он и создаёт. Ответ MUST нести **список** заведённых записей и место под признак повторного файла у каждой, даже когда файл в запросе один. Форма согласована один раз и вперёд: приём, отдающий одну запись, пришлось бы переписывать вместе с приёмом нескольких файлов и с распознаванием повтора по содержимому, а экран загрузки — переделывать под вторую форму. Число файлов в запросе при этом остаётся прежним: меняется форма ответа, не число файлов. Элемент списка MUST нести те же поля, что и карточка записи, плюс признак повторного файла полем `duplicate`: две формы одной вещи разошлись бы молча. Состав карточки нормирует capability `archive`. Прежние имена полей ответа — `job_id` и `status` — MUST не употребляться: адрес опроса убран целиком, и идентификатор записи зовётся `id`. Это объявленная ломка публичного контракта: стадия проекта — стройка, на сервере данных нет, а внешней программы на прежнем контракте не существует — своего токена у неё не было. Значение рубежа в ответе MUST принадлежать перечню рубежей конвейера и MUST не перечисляться этой нормой порознь: рубеж объявлен одним дескриптором, и перечисленный здесь второй раз он разошёлся бы с ним молча. Рубеж называет достигнутое, а не предстоящее, и `created` в перечне отсутствует вовсе. Запись сверх потолка размера MUST отвергаться до заведения файла и аудиозаписи, и код с телом такого отказа нормирует capability `archive` наравне с прочими ветвями. Потолок применяется уже сегодня, а ответ на его срабатывание — самый частый отказ у человека на мобильной сети — прежде не был нормирован ничем и уходил телом ограничителя тела, мимо единой формы. Отказ неузнанному наступает **раньше** чтения тела: запись, за которую не заплатит узнанный отправитель, не должна попасть даже в память. Приём не судит о годности записи сам: расширение он берёт из имени файла, а пригодность содержимого узнаёт у источника метаданных. Куда именно ложится принятая запись, приёму не принадлежит: раскладку выбирает хранилище, и нормирует её capability `storage`. Владельцем принятой записи приём SHALL назначать узнанного предъявителя. Обязательность владельца при этом MUST держаться и схемой хранилища: колонка владельца пустого значения не принимает вовсе, и норму эту держит capability `storage`. Проверка в приёме от этого не лишняя — она отвечает отправителю понятным отказом до того, как запись попадёт в память, а схема отвечала бы отказом сохранения после укладки файла. Предъявитель, узнанный без учётной записи пользователя, MUST получать отказ `403` и MUST получать его **до чтения тела** — там же, где стоит отказ неузнанному. Владелец панели, предъявивший собственный токен хранилища, — именно такой случай: узнан он всё же узнан, а записи в коллекции пользователей у него нет, и владельцем записи он стать не может. Код здесь другой, чем у запроса от неузнанного, и это не оплошность: `401` значит «предъяви себя», а предъявитель себя предъявил. Утечки по разнице кодов нет — оба ответа говорят о самом спрашивающем, а не о том, какие записи заведены. Отказ **после** укладки записи потребовал бы убрать уже сохранённый файл, а уборки файлов сервис не умеет вовсе: норма, обязывающая к недостижимому, не пишется. #### Scenario: Запись принята - **GIVEN** источник метаданных читает запись и отдаёт её длительность - **AND** отправитель узнан - **WHEN** программа шлёт `POST /app/audiorecords` с полем `audio` - **THEN** ответ имеет код `201`, а в теле лежит список из одного элемента - **AND** элемент несёт непустой `id`, поле `state` со значением `uploaded` и место под признак повторного файла - **AND** содержимое записи целиком лежит в хранилище одним файлом - **AND** владельцем заведённой аудиозаписи стоит узнанный предъявитель #### Scenario: Узнанный без учётной записи пользователя - **GIVEN** предъявлен собственный токен владельца панели - **WHEN** он шлёт `POST /app/audiorecords` с полем `audio` - **THEN** ответ имеет код `403` - **AND** ни файла, ни аудиозаписи не заводится #### Scenario: Пришедший не узнан - **WHEN** программа шлёт `POST /app/audiorecords` с полем `audio` неузнанной - **THEN** ответ имеет код `401` - **AND** ни файла, ни аудиозаписи не заводится - **AND** тело ответа не несёт данных записи #### Scenario: Поля с записью нет - **GIVEN** отправитель узнан - **WHEN** программа шлёт `POST /app/audiorecords` без поля `audio` - **THEN** ответ имеет код `400` и сообщение об отсутствии записи - **AND** ни файла, ни аудиозаписи не заводится #### Scenario: Размеру записи приём не судья - **GIVEN** источник метаданных читает запись и отдаёт её длительность - **AND** отправитель узнан - **WHEN** программа шлёт запись нулевой длины - **THEN** ответ имеет код `201`: собственного порога по размеру у приёма нет