# CLAUDE.md Памятка для работы над transcriber. Перед задачей прочитай также [docs/passport.md](docs/passport.md), [docs/architecture.md](docs/architecture.md) и [docs/conventions/](docs/conventions/README.md). Проект ведём по-русски. ## Что это Сервис расшифровки аудио в текст. Принимает запись двумя входами — Telegram-бот и HTTP API, — конвертирует её `ffmpeg` в ogg, отдаёт на отложенное распознавание Yandex SpeechKit и возвращает текст туда, откуда пришла запись. Состояние задач и метаданные файлов лежат в SQLite, файлы — на диске. Чего **не** делает: не редактирует и не пересказывает текст, не хранит записи как архив, не распознаёт речь сам, не заводит учётные записи и не работает с живым потоком. Границу домена целиком держит [docs/passport.md](docs/passport.md). ## Стек 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`. ## Инварианты Что нарушать нельзя. - **Секрет не покидает конфиг.** Токен бота, ключ SpeechKit и пара ключей Object Storage не попадают в git, в лог, в ответ пользователю и в колонку `error_text`. Нарушение необратимо: утёкший ключ отзывают и меняют вручную во всех местах выкладки. **critical** - **Содержимое записи остаётся приватным.** Текст расшифровки, имя файла пользователя и его сообщение в лог не пишутся — только длина и идентификаторы. Нарушение необратимо: строки уже уехали в журнал контейнера. **critical** - **Бот отвечает только тем, кто в белом списке.** Бот проверяет отправителя до любой работы, включая скачивание файла. Нарушение обратимо правкой конфига, но чужие записи к тому моменту уже обработаны за наши деньги. **critical** - **Принятая запись не теряется молча.** Отказ на любом шаге либо оставляет задачу пригодной к повтору, либо переводит её в `failed` и сообщает пользователю. Молчаливый выход из шага без записи в лог и без смены состояния запрещён. Обратимо повторной отправкой, но пользователь об этом не узнает. **major** - **`NoopJobError` — не ошибка.** Значение «задач в этом состоянии нет» не логируется, не считается в метрику и не поднимает уровень. Нарушение даёт запись раз в секунду на каждый воркер. **major** - **Миграция, уехавшая на сервер, не переписывается.** Изменение — только новым файлом. Необратимо: goose считает применённую версию по номеру. **critical** - **Новая колонка правится во всех четырёх местах** репозитория SQLite — `Create`, `Save`, `GetByID`, `FindAndAcquire`. Компилятор расхождение не поймает, а проявится оно как потерянное при сохранении поле. **major** ## Команды ```bash go build ./... # нужен CGO go test ./... go vet ./... gofmt -l . golangci-lint run go run . -c config.toml # флаг -c или --config, по умолчанию config.toml task image # docker-образ; тег и раскладка — docs/architecture.md task gate # весь набор проверок разом ``` Локальный запуск требует `ffmpeg` и `ffprobe` в `PATH` и своего `config.toml` — скопируй `config.dist.toml` и заполни; известные прорехи образца перечислены в [docs/conventions/config.md](docs/conventions/config.md) строками «*Расхождение:*». ## Гейт - **Команда целиком:** `task gate`. База диффа — переменная `BASE`, по умолчанию `origin/master`; переопределяется `task gate BASE=`. - **Где логи шагов:** вывод команды, отдельного файла нет. - **Что означает каждый исход:** ненулевой код любого шага роняет гейт. У `docs.py check`, `tasks.py check` и `openspec.py check` словарь кодов общий: 0 сошлось, 1 дрейф, 2 ошибка употребления, 3 окружение (не корень проекта, каталог не найден), 4 внутренний сбой. - **Что красит безусловно и почему:** отказ сборки, тестов, `go vet`, неотформатированный файл, находка `golangci-lint`, дрейф раскладки документов, дрейф каталога задач, форма `openspec/config.yaml`. Машина проверяет всё перечисленное, и это не обсуждается. Шаг, чей скрипт не найден, краснеет с именем недостающего плагина, а не пропускается молча. - **Чего в гейте намеренно нет и кто тогда обязан это гонять:** - `gitleaks` — висит на pre-commit в `lefthook.yml` и смотрит только индекс коммита. Полную историю никто не проверяет; - согласованность документов между собой и с кодом — её судят агенты, зовёт их скилл `av-dev-docs:healthcheck`, и звать его надо руками; - покрытие изменённых строк не считается ничем. **Гейт на `master` сегодня красный, и это объявленный долг, а не поломка дня.** Известный отказ один: - `golangci-lint run` даёт 4 замечания в существующем коде: два непроверенных `Close` (`adapter/recognizer/yandex/speechkit.go:55`, `main.go:124`) и два сравнения ошибок приведением типа (`controller/worker/worker.go:51`, `service/transcribe.go:394`). Долг записан в [docs/conventions/errors.md](docs/conventions/errors.md), заведён задачей `errors-as-instead-of-typecast`. Новые отказы отличай от этого. Пока он жив, «зелёный гейт» в определении сделанного означает «не добавилось ничего сверх перечисленного». **`go test ./...` больше долгом не считается.** Тесты приёма по HTTP чинены задачей `http-handler-tests-never-green`; красный `go test` теперь означает поломку, и списывать его на наследство нельзя. ## Запреты - **Рабочую БД не трогать.** `data/transcriber.db` на сервере и его копии. Локальная база в `./data/` — своя, её ронять и пересоздавать можно свободно. - **Боевой каталог записей не трогать.** `data/files` на сервере: там лежат голосовые сообщения живых людей. - **Боевым токеном бота не запускаться.** Второй процесс с тем же токеном перехватывает обновления у работающего, и пользователь теряет ответы. - **Yandex Cloud за деньги.** Распознавание и хранение в Object Storage оплачиваются по факту. Прогон на реальных ключах ради проверки кода запрещён — подставляй `internal/adapter/recognizer/memory.go`. - **Выкладку не запускать.** `inv pl -- transcriber` из `pet-project-server` запускает человек. - **`testdata` в проекте нет.** Тесты, которым нужен файл, создают его во временном каталоге и убирают за собой. - **Временное** — `t.TempDir()` в тестах, `/tmp` вне их. В `data/` временное не писать: этот каталог смонтирован на сервере. ## Работа - **Основная ветка:** `master`. Коммиты идут в неё напрямую, веток и PR нет. - **Сообщение коммита** без трейлера `Co-Authored-By`. - **Необратимое** (спрашивается у человека всегда): применённая миграция, формат файла на диске и раскладка `data/files`, публичный контракт HTTP API, имя ключа конфига, любое действие с боевыми данными и с Yandex Cloud, ротация секрета. - **Что считается сломанным** — новый красный шаг гейта, которого не было до твоей правки. Такое чинится прежде любой другой работы. Два объявленных долга из раздела «Гейт» сломанным состоянием **не** считаются, пока их не закрыли задачами. - **Ориентир по размеру порции:** не замерялся. - **Что такое «сделана»:** `task gate` зелёный и критерии приёмки проверены поимённо. ## Язык - Документация, комментарии, сообщения коммитов — русский. - Код и идентификаторы — английский. - Текст, который видит пользователь Telegram, — русский.