Канон документов, каталог задач и OpenSpec
docs/ по канону 12: паспорт с целью проекта, архитектура сегодняшнего устройства, схема хранилища, модель угроз, конвенции кода, журнал ревью. Конвенции перенесены из jellybit; места, где код им не следует, помечены строкой «Расхождение» как объявленный долг. tasks/ с роадмапом: две достигнутые цели, две запланированные (веб и многопользовательский режим), два направления (все форматы, долгие записи) и пять задач в беклоге. openspec/config.yaml — маршрутизатор с адресами документов, спек пока нет. CLAUDE.md переписан по форме канона: инварианты с severity, семантика гейта, запреты с путями. Taskfile получил task gate.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
# Схема хранилища
|
||||
|
||||
СУБД, миграции, правило времени и идентификаторов.
|
||||
|
||||
СУБД — SQLite, драйвер `mattn/go-sqlite3` (нужен CGO). Запросы строит
|
||||
`doug-martin/goqu` с диалектом `sqlite3`. Миграции — `pressly/goose`, каталог
|
||||
`migrations/`, вшит в бинарник через `//go:embed migrations/*.sql` в `main.go` и
|
||||
накатывается при старте. Новый файл достаточно положить в каталог.
|
||||
|
||||
**Идентификаторы** — UUID v4 строкой.
|
||||
|
||||
**Время** — локальная зона процесса, UTC не навязан. Колонки `created_at` и
|
||||
`updated_at` проставляет приложение, а не СУБД; умолчание `CURRENT_TIMESTAMP`
|
||||
стоит только у `files.created_at`.
|
||||
|
||||
Того, что единой точки генерации идентификатора и времени нет, здесь не
|
||||
повторяем: перечень единых точек и их отсутствий держит
|
||||
[architecture.md](architecture.md), «Единые точки проекта».
|
||||
|
||||
Переход на PocketBase запланирован, но не начат — здесь описано сегодняшнее
|
||||
состояние. Открытые вопросы перехода — в
|
||||
[architecture.md](architecture.md), раздел «Открытые вопросы».
|
||||
|
||||
## Таблицы
|
||||
|
||||
### `files`
|
||||
|
||||
Один файл на одну физическую копию: исходник, результат конвертации и копия в
|
||||
Object Storage — три разные записи.
|
||||
|
||||
| Колонка | Тип | Что |
|
||||
| --- | --- | --- |
|
||||
| `id` | TEXT PK | UUID файла |
|
||||
| `storage` | TEXT | `local` или `s3` |
|
||||
| `file_name` | TEXT | Имя в хранилище: UUID с расширением |
|
||||
| `size` | INTEGER | Размер в байтах |
|
||||
| `created_at` | DATETIME | Умолчание `CURRENT_TIMESTAMP` |
|
||||
|
||||
### `transcribe_jobs`
|
||||
|
||||
Задача расшифровки и она же очередь.
|
||||
|
||||
| Колонка | Тип | Что |
|
||||
| --- | --- | --- |
|
||||
| `id` | TEXT PK | UUID задачи |
|
||||
| `state` | TEXT | `created`, `converted`, `transcribe`, `done`, `failed` |
|
||||
| `source` | TEXT | `api`, `telegram`, `unknown`; умолчание `unknown` |
|
||||
| `file_id` | TEXT FK → `files.id` | **Текущий** файл задачи: шаг конвейера переставляет ссылку на свой результат |
|
||||
| `delay_time` | DATETIME | Не брать задачу раньше этого времени |
|
||||
| `acquisition_id` | TEXT | Кто захватил задачу |
|
||||
| `acquire_time` | DATETIME | Когда захватил; по нему считается протухание |
|
||||
| `recognition_op_id` | TEXT | Идентификатор операции в Yandex Cloud |
|
||||
| `transcription_text` | TEXT | Результат распознавания |
|
||||
| `is_error` | BOOLEAN | Задача с `1` из выборки исключена навсегда |
|
||||
| `error_text` | TEXT | Текст ошибки, машинный |
|
||||
| `tg_chat_id` | INTEGER | Куда отправить результат |
|
||||
| `tg_reply_message_id` | INTEGER | С каким сообщением связать |
|
||||
| `created_at`, `updated_at` | DATETIME | Проставляет приложение |
|
||||
|
||||
Индексов, кроме первичных ключей, нет. Выборка воркера идёт полным перебором по
|
||||
`state`, `is_error`, `delay_time` и `acquire_time`.
|
||||
|
||||
## Представление данных
|
||||
|
||||
Чем физически лежит запись и что происходит при чтении и записи.
|
||||
|
||||
- **Расшифровка лежит целиком в колонке `transcription_text`** одной строкой.
|
||||
Запись длиной в час даёт десятки килобайт в одной ячейке; читается она
|
||||
целиком при каждом `GetByID` и при каждом захвате задачи воркером.
|
||||
- **Аудио в базе не лежит.** На диске — каталог `data/files`, плоский, имя файла
|
||||
равно UUID с расширением. Ни файлы, ни объекты в Object Storage не удаляются
|
||||
после завершения задачи: каталог и бакет растут неограниченно.
|
||||
- **Захват задачи — два запроса подряд, не транзакция.** Сперва `UPDATE …
|
||||
WHERE id = (SELECT … LIMIT 1)` проставляет `acquisition_id`, затем отдельный
|
||||
`SELECT … WHERE acquisition_id = ?` читает строку. Репозиторий сверяет число
|
||||
затронутых строк с ожидаемым, но между запросами задачу может перехватить
|
||||
другой воркер с тем же значением — на одном процессе это не наблюдалось.
|
||||
- **Список колонок задан не одним местом** — четырьмя запросами файла
|
||||
`internal/adapter/repo/sqlite/transcript_job_repo.go`. Правило правки всех
|
||||
четырёх и его severity — инвариант в [CLAUDE.md](../CLAUDE.md), «Инварианты».
|
||||
|
||||
## Настройки с числовым значением
|
||||
|
||||
| Настройка | Значение | Где |
|
||||
| --- | --- | --- |
|
||||
| Срок захвата, конвертация и распознавание | 1 час | `service/transcribe.go`, вызовы `findJob` |
|
||||
| Срок захвата, проверка операции | 24 часа | там же |
|
||||
| Пауза воркера между попытками | 1 секунда | `controller/worker/worker.go` |
|
||||
| Задержка перед первой проверкой операции | 10 секунд | `service/transcribe.go` |
|
||||
| Задержка между проверками операции | 5 секунд | там же |
|
||||
| Предел длины сообщения Telegram | 4000 символов | `adapter/telegram/sender.go` |
|
||||
| Память под multipart-загрузку | 32 МиБ | `main.go`, `router.MaxMultipartMemory` |
|
||||
| Таймаут мягкой остановки | 5 секунд | конфиг, `[server] shutdown_timeout` |
|
||||
| Таймаут жёсткой остановки | 20 секунд | конфиг, `[server] force_shutdown_timeout` |
|
||||
| Таймаут обновлений Telegram | 10 секунд | конфиг, `[telegram] update_timeout` |
|
||||
| Качество кодирования vorbis | `-q:a 4` | `adapter/converter/ffmpeg/ffmpeg.go` |
|
||||
|
||||
Чего среди настроек **нет**: режим журналирования SQLite не задан (значение по
|
||||
умолчанию, не WAL), таймаут занятости не задан, размер пула соединений не задан,
|
||||
срока хранения файлов и объектов нет вовсе. Таймаутов у обращений к Telegram, S3 и
|
||||
SpeechKit тоже нет — ни одного.
|
||||
Reference in New Issue
Block a user