## MODIFIED Requirements ### Requirement: Файл отдаётся ссылкой Сервис SHALL отдавать файл записи ссылкой, которую строит хранилище по самой записи, **и только узнанному отправителю**. Поле файла MUST быть помечено защищённым: без этого ссылка открывает запись любому, кто её знает, и знание ссылки становится правом. Отданный файл MUST совпадать с принятым по длине. Одной пометки мало: защищённый файл судится **коротким токеном файла**, который узнанный отправитель берёт у хранилища, — и правилом просмотра коллекции. Правило MUST пускать только владельца файла: незаданное означает «только владелец панели», и тогда файла не получит и узнанный, а прежнее «всякий узнанный» отдавало чужое аудио тому, кто знает идентификатор записи. Токен файла хранилище выдаёт **на предъявителя**, а не на файл, и о файле при выдаче не спрашивает. Значит владельца судит переход по ссылке, а не выдача токена: отказ наступает там, и требовать его от выдачи значит требовать механизма, которого нет. Отсюда порядок для потребителя: узнавание → токен файла → ссылка с этим токеном. Адрес выдачи токена лежит в пространстве хранилища, и узнавание по заголовку MUST на нём работать — иначе файл записи недостижим для браузера вовсе. Одного заголовка при этом мало: без токена ссылка файла не отдаёт, и это свойство хранилища, а не недосмотр. Конвейер расшифровки этим не затронут: он читает файл из файловой системы хранилища, а не по ссылке. Ссылка на несуществующую запись MUST отвечать отказом, а не пустым файлом. **Ссылка сама по себе и есть право пройти по ней**, и потому она MUST не попадать ни в журнал, ни в метку метрики, ни в ответ отправителю. Имя, под которым файл лёг в хранилище, из журнала выводимо быть не должно: журнал уезжает в собранные логи, откуда строку не убрать, и оттуда ссылка на чужую запись работала бы бессрочно. Защищённое поле сужает это право, но не отменяет запрета: право пройти теперь требует ещё и узнавания, а строка журнала со ссылкой по-прежнему собирала бы половину ключа. Отсюда требование к отказам: сообщение об отказе хранилища MUST не выходить за пределы хранилища дословно. Отказ чтения и отказ укладки называют ключ файла целиком, а отказ выгрузки во внешнее хранилище — полный адрес объекта; и то и другое кончается в журнале и собирает ссылку не хуже успешного пути. Что именно журнал приёма пишет ради прослеживаемости, нормирует capability `intake`. #### Scenario: Файл забирают по ссылке - **GIVEN** запись принята и её файл лежит в хранилище - **AND** забирающий узнан и взял токен файла - **WHEN** ссылку на файл запрашивают с этим токеном - **THEN** приходит тот же файл, и его длина совпадает с длиной принятого #### Scenario: Неузнанному файл не отдаётся - **GIVEN** запись принята и её файл лежит в хранилище - **WHEN** ссылку на файл запрашивают неузнанным - **THEN** приходит отказ, а содержимого записи в ответе нет #### Scenario: Токен файла выдаётся узнанному по заголовку - **GIVEN** запрос идёт с доверенного адреса с заголовком `Remote-User` - **WHEN** он просит у хранилища токен файла - **THEN** токен выдаётся #### Scenario: Конвейер читает файл без узнавания - **GIVEN** запись принята и ждёт расшифровки - **WHEN** шаг конвейера берётся за неё - **THEN** файл читается из файловой системы хранилища и шаг проходит #### Scenario: Ссылка ведёт в никуда - **WHEN** запрашивают ссылку на запись, которой нет - **THEN** приходит отказ, а не пустой ответ #### Scenario: По журналу ссылку не собрать - **GIVEN** запись принята и прошла конвейер - **WHEN** читают журнал сервиса целиком - **THEN** имени, под которым файл лёг в хранилище, в нём нет #### Scenario: Отказ чтения файла не называет его ключ - **GIVEN** файл записи не читается из хранилища - **WHEN** шаг конвейера берётся за эту запись и отказывает - **THEN** отказ называет запись её идентификатором и не несёт имени файла ### Requirement: Файл записи сужается владельцем наравне с задачей Хранилище SHALL держать владельца и у файла записи — той же связью с учётной записью, — и правило просмотра файлов MUST пускать к файлу только его владельца. Владелец файла MUST назначаться при приёме, из узнанного предъявителя, а колонка файла MUST не допускать пустого значения наравне с колонкой записи. Прежде пустое значение оставалось у файлов, заведённых конвейером для записи без владельца; таких записей больше не заводится, и разное правило у записи и у её файла читалось бы как недосмотр. Файл, заведённый шагом конвейера, — приведённую копию заводит именно он — MUST получать владельца своей записи. Иного источника владельца у файла нет, и шаг, оставивший его пустым, упрётся в отказ сохранения: запись накопит отказы и остановится признаком на первом же приведении. Ссылки на файлы у записи две — на принятую копию и на приведённую, — и обе живут до конца, но владелец файла MUST по-прежнему лежать своей колонкой, а не выводиться через запись: файл переживает свою запись, и заведённый шагом до сохранения записи он остаётся с владельцем и без ссылки. Отказ наступает **на переходе по ссылке**, а не на выдаче токена файла: токен хранилище выдаёт на предъявителя, а не на файл, и о файле при выдаче не спрашивает вовсе. Требовать отказа при выдаче значит требовать механизма, которого нет, — а проверка, написанная под такое требование, зеленела бы, не касаясь пути, по которому аудио и уходит. #### Scenario: Чужой файл не отдаётся - **GIVEN** запись принята одним узнанным - **WHEN** другой узнанный идёт по ссылке на файл этой записи со своим токеном - **THEN** содержимого он не получает #### Scenario: Свой файл отдаётся - **GIVEN** человек принял запись - **WHEN** он идёт по ссылке на файл своей записи со своим токеном - **THEN** содержимое отдаётся #### Scenario: Файл без владельца не сохраняется - **GIVEN** сервис поднят - **WHEN** файл записи пытаются сохранить с пустым владельцем - **THEN** хранилище его не сохраняет #### Scenario: Приведённая копия получает владельца записи - **GIVEN** запись с владельцем дошла до приведения - **WHEN** шаг заводит приведённую копию файла - **THEN** владельцем копии стоит владелец записи - **AND** шаг завершается без отказа