docs/ по канону 12: паспорт с целью проекта, архитектура сегодняшнего устройства, схема хранилища, модель угроз, конвенции кода, журнал ревью. Конвенции перенесены из jellybit; места, где код им не следует, помечены строкой «Расхождение» как объявленный долг. tasks/ с роадмапом: две достигнутые цели, две запланированные (веб и многопользовательский режим), два направления (все форматы, долгие записи) и пять задач в беклоге. openspec/config.yaml — маршрутизатор с адресами документов, спек пока нет. CLAUDE.md переписан по форме канона: инварианты с severity, семантика гейта, запреты с путями. Taskfile получил task gate.
7.2 KiB
Схема хранилища
СУБД, миграции, правило времени и идентификаторов.
СУБД — 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, «Единые точки проекта».
Переход на PocketBase запланирован, но не начат — здесь описано сегодняшнее состояние. Открытые вопросы перехода — в 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, «Инварианты».
Настройки с числовым значением
| Настройка | Значение | Где |
|---|---|---|
| Срок захвата, конвертация и распознавание | 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 тоже нет — ни одного.