хранилище, файлы записей и очередь переведены на встроенную PocketBase
- записи, метаданные и файлы съехались под один каталог данных; появилась панель владельца, а gin, goqu, goose и требование CGO ушли - захват задачи стал одним запросом с RETURNING; заведены число попыток, состояние dead и нарастающая пауза вместо признака is_error - имя файла в хранилище задаёт сервис и в журнал не идёт: вместе с идентификатором записи оно собирало бы ссылку на скачивание
This commit is contained in:
@@ -11,8 +11,9 @@
|
||||
|
||||
Сервис расшифровки аудио в текст. Принимает запись двумя входами — Telegram-бот и
|
||||
HTTP API, — конвертирует её `ffmpeg` в ogg, отдаёт на отложенное распознавание
|
||||
Yandex SpeechKit и возвращает текст туда, откуда пришла запись. Состояние задач
|
||||
и метаданные файлов лежат в SQLite, файлы — на диске.
|
||||
Yandex SpeechKit и возвращает текст туда, откуда пришла запись. Состояние задач,
|
||||
метаданные и сами файлы лежат во встроенной PocketBase, и она же даёт владельцу
|
||||
панель администратора.
|
||||
|
||||
Чего **не** делает: сам речь не распознаёт и своих моделей не держит, текст
|
||||
руками не правит и в форматы документов не экспортирует, учётных записей не
|
||||
@@ -23,10 +24,10 @@ Yandex SpeechKit и возвращает текст туда, откуда пр
|
||||
|
||||
## Стек
|
||||
|
||||
Go 1.24 (нужен CGO из-за `mattn/go-sqlite3`), gin, goqu, goose, SQLite,
|
||||
`go-telegram-bot-api`, `aws-sdk-go-v2` для Object Storage, gRPC-клиент Yandex
|
||||
SpeechKit v3, Prometheus, `slog`. Сборка — Taskfile, образ — Docker,
|
||||
выкладка — Ansible из `pet-project-server`.
|
||||
Go 1.25 (CGO не нужен), встроенная PocketBase — хранилище, файлы записей и
|
||||
панель администратора, — `go-telegram-bot-api`, `aws-sdk-go-v2` для Object
|
||||
Storage, gRPC-клиент Yandex SpeechKit v3, Prometheus, `slog`. Сборка —
|
||||
Taskfile, образ — Docker, выкладка — Ansible из `pet-project-server`.
|
||||
|
||||
## Инварианты
|
||||
|
||||
@@ -40,8 +41,9 @@ SpeechKit v3, Prometheus, `slog`. Сборка — Taskfile, образ — Dock
|
||||
пользователя и его сообщение в лог не пишутся — только длина и
|
||||
идентификаторы. Нарушение необратимо: строки уже уехали в журнал контейнера.
|
||||
**critical**
|
||||
*Изъятие:* расширение — хвост после последней точки — в журнал попадает, потому
|
||||
что стоит в собственном имени файла на диске, а путь логируется. Изъятие узкое
|
||||
*Изъятие:* расширение — хвост после последней точки — в журнал попадает
|
||||
собственным полем: по нему прослеживается путь записи. Имени файла в журнале
|
||||
нет вовсе (инвариант ниже). Изъятие узкое
|
||||
и кончается журналом: наружу, меткой метрики, расширение выходит только
|
||||
приведённым к перечню известных форматов. Границу держит спека `intake`,
|
||||
цена — [adr/ADR-2026-08-11-known-format-label.md](docs/adr/ADR-2026-08-11-known-format-label.md),
|
||||
@@ -58,15 +60,28 @@ SpeechKit v3, Prometheus, `slog`. Сборка — Taskfile, образ — Dock
|
||||
логируется, не считается в метрику и не поднимает уровень. Нарушение даёт
|
||||
запись раз в секунду на каждый воркер. **major**
|
||||
- **Миграция, уехавшая на сервер, не переписывается.** Изменение — только новым
|
||||
файлом. Необратимо: goose считает применённую версию по номеру. **critical**
|
||||
- **Новая колонка правится во всех четырёх местах** репозитория SQLite —
|
||||
`Create`, `Save`, `GetByID`, `FindAndAcquire`. Компилятор расхождение не
|
||||
поймает, а проявится оно как потерянное при сохранении поле. **major**
|
||||
файлом шага. Необратимо: хранилище считает применённое по имени файла.
|
||||
**critical**
|
||||
- **Имя файла в хранилище задаёт сервис, а в журнал не идёт.** Умолчание
|
||||
PocketBase строит имя из имени, данного отправителем, — оно не применяется.
|
||||
Само имя — последняя часть ссылки `/api/files/...`, поэтому в журнал пишется
|
||||
расширение, а не имя: строка журнала иначе стала бы бессрочным ключом к чужой
|
||||
записи. **critical**
|
||||
- **Колонки очереди правятся в четырёх местах** пакета хранилища —
|
||||
`applyToRecord`, `recordToJob`, константа `acquireColumns` и структура
|
||||
`acquiredRow` с её `toJob`, — плюс шаг схемы. Компилятор видит два из них.
|
||||
Колонка, забытая в паре `acquireColumns`/`acquiredRow`, приезжает из захвата
|
||||
нулевой, и первый же `Save` пишет этот ноль поверх сохранённого значения:
|
||||
поле теряется **только у задачи, попавшей к воркеру**. **major**
|
||||
- **Результат пишет только держатель захвата.** Шаг, чей захват за время работы
|
||||
достался другому, завершается без записи и без ответа отправителю. Иначе два
|
||||
воркера пишут в одну задачу по очереди, а отправитель получает два ответа.
|
||||
**major**
|
||||
|
||||
## Команды
|
||||
|
||||
```bash
|
||||
go build ./... # нужен CGO
|
||||
go build ./... # CGO не нужен
|
||||
go test ./...
|
||||
go vet ./...
|
||||
gofmt -l .
|
||||
@@ -118,10 +133,10 @@ task gate # весь набор проверок разом
|
||||
|
||||
## Запреты
|
||||
|
||||
- **Рабочую БД не трогать.** `data/transcriber.db` на сервере и его копии.
|
||||
Локальная база в `./data/` — своя, её ронять и пересоздавать можно свободно.
|
||||
- **Боевой каталог записей не трогать.** `data/files` на сервере: там лежат
|
||||
голосовые сообщения живых людей.
|
||||
- **Боевой каталог данных не трогать.** `data/` на сервере целиком: под ним и
|
||||
база (`data/data.db`), и записи живых людей
|
||||
(`data/storage/<коллекция>/<запись>/`). Локальный каталог данных — свой, его
|
||||
ронять и пересоздавать можно свободно.
|
||||
- **Боевым токеном бота не запускаться.** Второй процесс с тем же токеном
|
||||
перехватывает обновления у работающего, и пользователь теряет ответы.
|
||||
- **Yandex Cloud за деньги.** Распознавание и хранение в Object Storage
|
||||
@@ -139,7 +154,7 @@ task gate # весь набор проверок разом
|
||||
- **Основная ветка:** `master`. Коммиты идут в неё напрямую, веток и PR нет.
|
||||
- **Сообщение коммита** без трейлера `Co-Authored-By`.
|
||||
- **Необратимое** (спрашивается у человека всегда): применённая миграция, формат
|
||||
файла на диске и раскладка `data/files`, публичный контракт HTTP API, имя
|
||||
файла на диске и раскладка каталога данных, публичный контракт HTTP API, имя
|
||||
ключа конфига, любое действие с боевыми данными и с Yandex Cloud, ротация
|
||||
секрета.
|
||||
- **Что считается сломанным** — новый красный шаг гейта, которого не было до
|
||||
|
||||
Reference in New Issue
Block a user