у записи появился владелец: чужую больше не отдают

- колонка `owner` связью с `users` в обеих коллекциях новым шагом схемы
  `202608140001`; чтение задачи сужено владельцем, и чужая, ничья и
  несуществующая дают один ответ; правило просмотра файлов сужено им же
- приём по HTTP берёт владельца из сессии, а предъявителя без учётной записи
  пользователя отвергает до чтения тела: позже пришлось бы убирать уложенный
  файл, а уборки файлов сервис не умеет. Выборка воркера владельцем не сужается
- удаление учётной записи с записями отвергается стражем, и вешает его сама
  сборка хранилища: сборка, забывшая его позвать, теряла защиту молча
This commit is contained in:
av
2026-08-14 12:18:11 +03:00
parent b7d4660aef
commit 8af8ec2e54
46 changed files with 2520 additions and 101 deletions
+45 -13
View File
@@ -36,8 +36,24 @@ Telegram, дописывает его сюда.
Куда именно ложится принятая запись, приёму не принадлежит: раскладку выбирает
хранилище, и нормирует её capability `storage`.
Владельца у принятой записи приём не заводит: после входа видно ровно то же, что
видно было анонимно.
Владельцем принятой записи приём SHALL назначать предъявителя сессии. Проверка
стоит здесь, а не только в схеме хранилища: колонка владельца допускает пустое
значение ради записей из Telegram, и приём по HTTP — то место, где
обязательность держится.
Предъявитель, чья сессия не даёт учётной записи пользователя, MUST получать
отказ `403` и MUST получать его **до чтения тела** — там же, где стоит отказ по
отсутствию сессии. Сессия владельца панели — именно такой случай: узнан он всё
же узнан, а записи в коллекции пользователей у него нет, и владельцем записи он
стать не может.
Код здесь другой, чем у запроса без сессии, и это не оплошность: `401` значит
«предъяви себя», а предъявитель себя предъявил. Утечки по разнице кодов нет —
оба ответа говорят о самом спрашивающем, а не о том, какие записи заведены.
Отказ **после** укладки записи потребовал бы убрать уже сохранённый файл, а
уборки файлов сервис не умеет вовсе: норма, обязывающая к недостижимому, не
пишется.
#### Scenario: Запись принята
@@ -47,6 +63,14 @@ Telegram, дописывает его сюда.
- **THEN** ответ имеет код `201`, а в теле лежат непустой `job_id` и `status`
со значением `created`
- **AND** содержимое записи целиком лежит в хранилище одним файлом
- **AND** владельцем заведённой задачи стоит предъявитель сессии
#### Scenario: Сессия не даёт учётной записи пользователя
- **GIVEN** предъявлена сессия владельца панели
- **WHEN** он шлёт `POST /api/audio` с полем `audio`
- **THEN** ответ имеет код `403`
- **AND** ни файла, ни задачи не заводится
#### Scenario: Сессии нет
@@ -214,24 +238,24 @@ Telegram, дописывает его сюда.
### Requirement: Опрос готовности задачи
Сервис SHALL отдавать состояние задачи расшифровки по запросу
`GET /api/status/:id` **только узнанному отправителю**. Запрос без сессии MUST
получать код `401`, и тело такого ответа MUST не нести ни состояния задачи, ни
текста расшифровки. Ответ узнанному отправителю MUST нести идентификатор полем
`job_id`, состояние полем `status` и время заведения полем `created_at`, а текст
расшифровки полем `transcription_text`, и это поле MUST отсутствовать в ответе,
пока текста нет: пустая строка на месте отсутствующего текста читается как
«расшифровка пуста».
`GET /api/status/:id` **только её владельцу**. Запрос без сессии MUST получать
код `401`, и тело такого ответа MUST не нести ни состояния задачи, ни текста
расшифровки. Ответ владельцу MUST нести идентификатор полем `job_id`, состояние
полем `status` и время заведения полем `created_at`, а текст расшифровки полем
`transcription_text`, и это поле MUST отсутствовать в ответе, пока текста нет:
пустая строка на месте отсутствующего текста читается как «расшифровка пуста».
Отказ без сессии MUST не зависеть от того, есть такая задача или нет: иначе по
кодам ответа перебирается список заведённых задач.
Выборку по владельцу опрос не сужает: узнанный отправитель видит любую задачу по
её идентификатору ровно как прежде. Сужение придёт отдельной задачей.
Задача, принадлежащая другому, MUST отвечать тем же, чем отвечает неизвестный
идентификатор, — кодом `404` и тем же телом. То же MUST относиться к задаче без
владельца: запись, принятая ботом, по этому адресу не достаётся никому.
#### Scenario: Задача найдена
- **GIVEN** отправитель предъявил сессию
- **WHEN** программа спрашивает состояние заведённой задачи
- **WHEN** он спрашивает состояние своей задачи
- **THEN** ответ имеет код `200` и несёт `job_id`, `status` и `created_at`
#### Scenario: Сессии нет
@@ -246,10 +270,18 @@ Telegram, дописывает его сюда.
состояние по неизвестному идентификатору
- **THEN** оба ответа имеют код `401`
#### Scenario: Чужая задача неотличима от неизвестной
- **GIVEN** задача заведена одним вошедшим
- **WHEN** её состояние спрашивает другой вошедший
- **THEN** ответ имеет код `404` и то же тело, что и ответ по неизвестному
идентификатору
- **AND** тело ответа не несёт ни состояния задачи, ни текста расшифровки
#### Scenario: Расшифровки ещё нет
- **GIVEN** отправитель предъявил сессию
- **WHEN** программа спрашивает состояние задачи, которая ещё не дошла до текста
- **WHEN** он спрашивает состояние своей задачи, которая ещё не дошла до текста
- **THEN** поля `transcription_text` в ответе нет вовсе
#### Scenario: Задачи с таким идентификатором нет