tasks: спроектирован контракт приложения и переставлена голова очереди

- json-api-for-spa: адреса приложения уехали в своё пространство /app/,
  приём стал POST /app/audiorecords, опрос /api/status/:id убран, заведены
  список, карточка, текст, /app/me и /app/config; имя файла отправителя
  легло своей колонкой рядом с заголовком
- три действия над записью — правка заголовка, возврат в работу и журнал
  событий — собраны задачей audiorecord-actions
- голова очереди: контракт, каркас, экран загрузки, список, действия
- в прежних задачах поправлены адреса, рубежи конвейера и остатки Telegram
This commit is contained in:
av
2026-08-15 09:06:11 +03:00
parent d88e56efcb
commit 79ff12548f
16 changed files with 351 additions and 115 deletions
+17 -16
View File
@@ -4,38 +4,39 @@
- **Категория:** Очередь — Дедупликация ищет совпадение в пределах пользователя — то есть после владельца записи, и экономит деньги с первого дня приложения.
- **Зачем:** Один и тот же файл, отправленный дважды, распознаётся дважды и оплачивается дважды: приём не смотрит на содержимое вовсе.
Повторная отправка того же файла возвращает прежнюю запись вместо второй задачи.
Повторная отправка того же файла возвращает прежнюю запись вместо второй.
Совпадение ищется **в пределах одного пользователя**: чужая расшифровка по
совпадению хеш-суммы не отдаётся и о её существовании отправитель не узнаёт.
## Затрагивает
- `TranscribeService.createTranscribeJob` — единая точка приёма, через неё идут
оба входа;
- `TranscribeService.createRecord` — единственный путь, которым запись
появляется в хранилище;
- таблица `files`: колонка хеш-суммы, её миграция и индекс по паре «владелец,
хеш-сумма»;
- контракт `POST /api/audio`: ответ на повторный файл;
- ответ бота на повторно присланное голосовое;
- контракт `POST /app/audiorecords`: признак повторного файла в ответе — место
под него согласовано задачей `json-api-for-spa`;
- `docs/database.md` — представление данных.
## Критерии приёмки
- Повторная отправка того же файла тем же пользователем возвращает
идентификатор прежней задачи, второй задачи в базе не появляется. Оракул —
тест приёма: два вызова одним содержимым, в репозитории одна задача.
- Тот же файл от другого пользователя заводит свою задачу и своё распознавание.
Оракул — тест приёма с двумя владельцами: две задачи, тексты не разделяются.
идентификатор прежней записи с признаком повтора, второй записи в базе не
появляется. Оракул — тест приёма: два вызова одним содержимым, в репозитории
одна запись, во втором ответе признак повтора.
- Тот же файл от другого пользователя заводит свою запись и своё распознавание.
Оракул — тест приёма с двумя владельцами: две записи, тексты не разделяются.
- Повторный файл не остаётся вторым экземпляром в каталоге хранения. Оракул —
тест: после второго вызова в каталоге один файл.
- Незавершённая задача тоже узнаётся: повторная отправка отдаёт её состояние, а
не заводит соседнюю. Оракул — тест на задаче в состоянии `created`.
- Незавершённая запись тоже узнаётся: повторная отправка отдаёт её рубеж, а не
заводит соседнюю. Оракул — тест на записи с рубежом `uploaded`.
## Рамки
Владелец записи приходит из `record-ownership` — до неё дедупликация опирается
на того владельца, который уже есть. Хеш-сумма считается на сервере: подсчёт на
Владелец записи обязателен с 2026-08-14 — его держит схема хранилища, — и
дедупликация опирается на него. Хеш-сумма считается на сервере: подсчёт на
стороне приложения относится к `chunked-upload-choice`. Колонка добавляется во
всех четырёх местах пакета хранилища `internal/adapter/repo/pocketbase`
`applyToRecord`, `recordToJob`, `acquireColumns`, `acquiredRow` с её `toJob`, —
плюс шаг схемы (инвариант `CLAUDE.md`).
всех местах пакета хранилища `internal/adapter/repo/pocketbase`, которые
перечисляет инвариант `CLAUDE.md` «Колонки записи правятся в двух местах», плюс
шаг схемы.