Files
transcriber/openspec/changes/archive/2026-08-22-trusted-header-login/specs/intake/spec.md
T
av 7f33c957e5 вход переехал на доверенный заголовок Authelia вместо собственного OIDC
- пришедшего называет заголовок Remote-User от прокси, и верят ему только с
  адреса из перечня trusted_proxies; своего входа у сервиса не осталось — ни
  корня /auth, ни кук, ни срока сессии, ни секрета клиента в конфиге и в базе
- учётная запись заводится первым обращением: EnsureUser в пакете хранилища,
  шаг схемы 202608220001 с колонкой provider_login и снятыми правилами users
- cmd/oidcstub заменён на cmd/devtools с подкомандой proxy; заодно закрыт
  унаследованный DL3066 — пользователь образа назван числом
2026-08-22 20:24:22 +03:00

9.0 KiB
Raw Blame History

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: собственного порога по размеру у приёма нет