внутренняя модель перестроена вокруг аудиозаписи

- audiorecords вместо transcribe_jobs: приложения (texts, structures,
  recognitions, record_events, topics) живут своими коллекциями, ссылки на
  исходник и на приведённую копию перестали переставляться
- рубеж называет достигнутое, отказ стал признаком остановки с причиной, а
  сторожей стало двое: число отказов и время в рубеже
- воркеры потеряли специализацию, их число задаётся [pipeline] workers, шаг
  выбирается по рубежу, а захват отдаёт идентификатор и признак захвата
This commit is contained in:
av
2026-08-14 20:20:33 +03:00
parent d079f03350
commit 1576d06735
84 changed files with 8973 additions and 2865 deletions
+36 -7
View File
@@ -112,7 +112,17 @@ Telegram отправителю.
списком; `filepath.Ext` режет по последней точке и не пропускает разделитель
каталогов, но это единственное, что стоит между входом и именем файла.
- **Ключ объекта в Object Storage** — то же имя файла, то есть UUID с
расширением. Бакет один на все записи, префикса по пользователю нет.
расширением. Бакет один на все записи, префикса по пользователю нет. С
2026-08-14 копия там файлом записи не считается: она существует лишь потому,
что провайдер читает аудио по адресу, и её ключ живёт в строке попытки
распознавания.
- **Вторая раскладка файла на диске** появилась 2026-08-14 вместе с сохранённым
ответом провайдера: `data/storage/<recognitions>/<попытка>/<имя>.payload`. Имя
задаёт сервис, как и у аудио. Содержимое там — **полный текст речи**, а не
метаданные, поэтому поле помечено защищённым, правило просмотра коллекции
оставлено пустым, и ссылка на вложение подпадает под тот же запрет, что и
ссылка на аудио: в журнал она не пишется. Проверено прогоном: без сессии, с
чужим и со своим токеном файла ссылка отвечает «не найдено».
- **Ссылка на файл** — `/api/files/<коллекция>/<запись>/<имя>`. Поле файла
помечено защищённым задачей `oidc-login` 2026-08-12: пройти по ссылке теперь
можно только с коротким токеном файла, который выдаётся по сессии, и запрос
@@ -121,12 +131,14 @@ Telegram отправителю.
остаётся: **имя файла в хранилище в журнал не пишется**
— иначе строка журнала вместе с идентификатором записи собирала бы ссылку
целиком и работала бы бессрочно. В журнал идёт расширение своим полем.
- **Идентификатор задачи** — 15 знаков, выдаёт хранилище. Он же единственное,
- **Идентификатор записи** — 15 знаков, выдаёт хранилище. Он же единственное,
что защищает `GET /api/status/:id`.
- **Поверхность самого хранилища.** Вместе с переводом наружу выходят
`/api/collections/...`, `/api/logs`, `/api/backups`, `/api/settings`,
`/api/crons` и панель `/_/`. Правила доступа коллекций оставлены пустыми, то
есть доступны они только владельцу панели; коды, снятые прогоном, —
есть доступны они только владельцу панели, — и коллекции, заведённые
2026-08-14, тоже: содержимое записи отдаёт собственный адрес сервиса, а не
поверхность хранилища. Коды, снятые прогоном, —
[database.md](database.md), «Коллекции», норма —
[storage](../openspec/specs/storage/spec.md), «Наружу хранилище отдаёт только
то, что заказано».
@@ -210,7 +222,13 @@ Telegram отправителю.
## Что чувствительнее чего
1. **Содержимое записей и расшифровок.** Голосовые сообщения — личная переписка;
это самое чувствительное, что здесь есть.
это самое чувствительное, что здесь есть. С 2026-08-14 оно живёт не одной
колонкой, а шестью коллекциями: сама запись (заголовок и краткое описание),
`texts` (расшифровка и вычитанный текст), `structures` (реплики со временем),
`recognitions` (**сырой ответ провайдера вложением — полный текст речи**),
`record_events` (журнал событий, содержимого не несёт) и `topics` (словарь
тем человека). Всякая новая коллекция, куда содержимое переезжает, закрывается
наравне с записью — норму держит спека `storage`.
2. **Токен бота Telegram.** Даёт полный доступ к боту и к перепискам с ним.
3. **Ключи Yandex Cloud**`speech_kit_api_key` и пара ключей Object Storage.
Утечка оплачивается деньгами и доступом к бакету.
@@ -336,6 +354,17 @@ Telegram отправителю.
вовсе. С 2026-08-11 это уже не недосмотр, а следствие решения хранить
бессрочно, и тем же днём заведена задача `delete-record`: своя запись
убирается вместе с файлом, объектом в Object Storage и всеми уровнями текста.
Пока она не сделана, единственный способ убрать запись — руками в базе и в
каталоге на сервере. Учёт расхода удалению не подлежит по решению человека:
деньги потрачены, а строки потребления текста не содержат.
Учёт расхода удалению не подлежит по решению человека: деньги потрачены, а
строки потребления текста не содержат.
**Руками запись сегодня не удаляется, и прежняя строка об этом была неверна.**
Проверено прогоном 2026-08-14: содержимое живёт в коллекциях, перечисленных
выше («Что чувствительнее чего»), связи приложений с записью обязательны и
каскада не имеют, поэтому удаление самой
строки записи отвергается хранилищем, а удаление её файлов проходит молча.
Владелец, выполнивший прежнюю процедуру, стирает аудио и **оставляет полный
текст речи** — расшифровку, разбивку по репликам и сырой ответ провайдера
файлом на диске. Порядок, которым запись убирается на самом деле: сперва
строки приложений — журнал событий, попытка распознавания вместе с её
вложением, структура, тексты, — потом сама запись, потом её файлы. До
`delete-record` это единственный способ, и он ручной целиком.