хранилище переехало с PocketBase на SQLite со своим каталогом файлов

- база своя: два пула, захват одним UPDATE ... RETURNING, шаги схемы на goose
  под файловым замком, одна миграция начальной схемы вместо семи прежних
- транспорт переписан на net/http: свои слои, свой ограничитель частоты,
  отдача файла с проверкой владельца; панель /_/ и пространство /api/ исчезли
- по находкам ревью: журнал не пишет путь под корнем приложения, ключ бюджета
  читается справа налево, узнавание известного идёт читающим пулом
This commit is contained in:
av
2026-08-23 08:06:04 +03:00
parent 1edf8cb225
commit c9b7765646
118 changed files with 11668 additions and 6679 deletions
@@ -0,0 +1,56 @@
## MODIFIED Requirements
### Requirement: Сырой ответ провайдера сохраняется целиком
Сервис SHALL сохранять ответ распознавателя целиком, в том виде, в каком он
пришёл, и MUST хранить его **отдельным файлом в каталоге данных**, а не колонкой
строки попытки.
Хранится он потому, что **результат операции у провайдера не переспрашивается**:
связь реплики с говорящим сервис строить пока не умеет, и когда научится, архив
пересчитается из сохранённого без повторной оплаты.
Файлом, а не колонкой, — потому что шаг опроса читает строку попытки часто, а
репозиторий читает строку целиком: ответ на многочасовую запись, положенный
колонкой, ехал бы в память при каждом опросе. Где именно этот файл лежит,
нормирует capability `storage`, требование «Файл записи живёт в хранилище»:
третьим файлом в подкаталоге записи, наравне с копиями аудио. Копией аудио он при
этом не считается — их у записи по-прежнему две.
Чтение строки попытки шагом опроса MUST не тянуть за собой сохранённый ответ.
Сохранённый ответ — это полный текст речи, и закрыт он MUST быть наравне с самой
записью. Закрытость MUST держаться **проверкой владельца в обработчике сервиса**:
адреса, которым сохранённый ответ читают снаружи, сервис MUST не заводить вовсе, а
всякий адрес, отдающий содержимое записи, MUST судить владельца связанной
аудиозаписи сам, при каждом обращении. Пометка поля защищённым и правило
просмотра коллекции, которыми закрытость держалась прежде, — механизмы
встроенного хранилища, и их не остаётся; норма от этого не ослабла, а перестала
зависеть от настройки, которую мы не писали.
Путь к файлу сохранённого ответа MUST не попадать ни в журнал, ни в метку
метрики, ни в ответ отправителю.
Норму держит capability `storage`, требование «Содержимое записи закрыто везде,
где лежит»; здесь она названа потому, что попытка распознавания — то место, куда
содержимое приезжает впервые.
#### Scenario: Ответ сохранён и читается позже
- **GIVEN** распознавание завершилось и ответ провайдера получен
- **WHEN** запись доходит до конечного рубежа
- **THEN** сохранённый ответ доступен по строке попытки целиком
#### Scenario: Опрос не тянет сохранённый ответ
- **GIVEN** у попытки распознавания есть сохранённый ответ
- **WHEN** шаг опроса читает строку попытки
- **THEN** сохранённый ответ в память при этом не читается
#### Scenario: Адреса чтения сохранённого ответа у сервиса нет
- **GIVEN** запись принята одним узнанным и прошла распознавание
- **WHEN** другой узнанный ищет адрес, которым читается сохранённый ответ этой
записи
- **THEN** такого адреса у сервиса нет
- **AND** содержимого он не получает