tasks: заведены шесть задач из урожая ревью и переименования образца конфига
- пять из урожая change 2026-08-11-fix-http-handler-tests, тег review-2026-08-11; утечка имени файла в журнал поставлена первой строкой очереди - опечатка transcibe дописана в json-api-for-spa: текст ошибки — часть необратимого контракта, и в одиночку он не правится
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
# 🧹 Переименовать образец конфига в config.example.toml
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Конвенция называет config.dist.toml объявленным расхождением, но тут же пишет это имя как правило — документ противоречит сам себе, а образец расходится с конвенцией.
|
||||
|
||||
Конвенция конфигурации взята из проекта jellybit и **сама называет сегодняшнее
|
||||
имя расхождением**: `docs/conventions/config.md`, строка 7 — «образец называется
|
||||
`config.dist.toml`, а не `config.example.toml`». Но строки 31 и 34 того же
|
||||
документа пишут `config.dist.toml` как правило, с заголовком раздела и всем
|
||||
прочим. Документ противоречит сам себе, и который из двух читать — не выводится.
|
||||
|
||||
Задача закрывает расхождение в пользу конвенции: файл переименовывается, а
|
||||
документ перестаёт спорить сам с собой.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `config.dist.toml` в корне — переименование;
|
||||
- `docs/conventions/config.md` — строка расхождения, заголовок раздела и все
|
||||
упоминания имени;
|
||||
- `CLAUDE.md`, раздел «Команды» — строка про то, что копировать;
|
||||
- `README.md` — команда `cp` и абзац про недостающий ключ;
|
||||
- `docs/review.md`, «Триггеры метки» — упоминание образца;
|
||||
- `docs/conventions/README.md` — строка про самодокументируемый образец;
|
||||
- `tasks/items/external-call-timeouts.md`, `telegram-account-link.md`,
|
||||
`oidc-login.md` — разделы «Затрагивает» ссылаются на имя.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Имени `config.dist.toml` в репозитории не осталось. Оракул —
|
||||
`grep -rn 'config\.dist\.toml' . --exclude-dir=.git` пуст.
|
||||
- Образец лежит под именем `config.example.toml` и по-прежнему не даёт
|
||||
закоммитить реальный конфиг. Оракул — `git ls-files config.example.toml`
|
||||
отдаёт файл, `git check-ignore config.toml` отдаёт `config.toml`.
|
||||
- Конвенция больше не называет имя образца расхождением. Оракул —
|
||||
`grep -n 'config\.example\.toml' docs/conventions/config.md` не находит строки,
|
||||
противопоставляющей одно имя другому (сегодня это строка 7).
|
||||
- Гейт зелёный: битых ссылок правка не оставила. Оракул — `task docs`.
|
||||
|
||||
## Рамки
|
||||
|
||||
Состав полей образца и его комментарии не пересматриваются — задача про имя и
|
||||
про ссылки на него. Прорехи образца, помеченные в конвенции строками
|
||||
«*Расхождение:*», остаются на месте.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# 🧹 Разобрать мелочи http-транспорта
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Маршруты зарегистрированы дважды, и переименование пути в main.go проходит проверки зелёным; обработчик пишет в журнал через стандартный log и дублирует запись, уже сделанную сервисом.
|
||||
- **Теги:** review-2026-08-11
|
||||
|
||||
Два дефекта, найденные ревью change `2026-08-11-fix-http-handler-tests`. Оба в
|
||||
`internal/controller/http`, мерджатся одним заходом.
|
||||
|
||||
**Маршруты зарегистрированы дважды.** `main.go` объявляет пути приёма и опроса,
|
||||
и сборка теста объявляет их же своей копией. Проверки ходят по копии, поэтому
|
||||
переименование пути в `main.go` их не роняет — а публичный контракт HTTP API
|
||||
проект объявил необратимым. Проверено мутацией на прогоне триажа: замена
|
||||
`api.POST("/audio", …)` на `api.POST("/upload", …)` оставляет тесты зелёными.
|
||||
Нашёл проход `review-specs`.
|
||||
|
||||
**Обработчик пишет в журнал мимо `slog` и дублирует сервис.** `log.Printf("Err:
|
||||
%v", err)` — стандартный `log`, тогда как весь проект пишет через `slog`; и эту
|
||||
же ошибку строкой выше уже записал сервис. Типовой узел «Транспорт» в
|
||||
[review.md](../../docs/review.md) прямо требует обратного: «не логирует ошибку,
|
||||
которую уже залогировал доменный слой». Нашёл проход `review-code`.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `internal/controller/http` и `main.go` — регистрация маршрутов приёма и
|
||||
опроса, сегодня продублированная в обоих местах;
|
||||
- `internal/controller/http/transcribe.go` — журнальная строка обработчика;
|
||||
- `internal/controller/http/transcribe_test.go` — сборка теста, держащая свою
|
||||
копию маршрутов.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Пути и методы объявлены в одном месте, и переименование пути роняет проверки.
|
||||
Оракул — мутация: заменить `/api/audio` на `/api/upload`, прогнать
|
||||
`go test ./internal/controller/http/` и увидеть красное.
|
||||
- Обработчик не пишет в журнал через стандартный `log`. Оракул —
|
||||
`grep -rn 'log\.' internal/controller/http/*.go` без импорта `log/slog` пуст.
|
||||
- Об одном отказе приёма в журнале одна запись, а не две. Оракул — прогон
|
||||
отказа с перехваченным журналом: строк об этой ошибке ровно одна.
|
||||
|
||||
## Рамки
|
||||
|
||||
Форма ответа и коды не меняются — этим занимается
|
||||
[json-api-for-spa](json-api-for-spa.md).
|
||||
|
||||
@@ -29,9 +29,10 @@
|
||||
репозиторием, возвращающим ошибку драйвера.
|
||||
- Негодный файл даёт `400` с человекочитаемым текстом, а не `500`. Оракул —
|
||||
тест: файл, который отвергает разбор метаданных.
|
||||
- Тело ошибки одной формы на всех эндпоинтах и не содержит сырого `err.Error()`.
|
||||
Оракул — тест на четырёх ветвях отказа: форма совпадает, текста внутренней
|
||||
ошибки в теле нет.
|
||||
- Тело ошибки одной формы на всех эндпоинтах, не содержит сырого `err.Error()` и
|
||||
опечатки `transcibe`. Оракул — тест на четырёх ветвях отказа (форма совпадает,
|
||||
текста внутренней ошибки в теле нет) плюс пустой `grep -rn 'transcibe'
|
||||
internal/`.
|
||||
- Список записей отдаётся страницами и упорядочен по времени создания. Оракул —
|
||||
тест на выборке больше страницы.
|
||||
- Отображение ошибки живёт в одной функции, и она названа в
|
||||
@@ -41,4 +42,7 @@
|
||||
|
||||
Аутентификацию и владельца не заводим — это `oidc-login` и `record-ownership`;
|
||||
задача про форму контракта. Публичный контракт после мерджа обратной правкой не
|
||||
откатывается.
|
||||
откатывается — поэтому сюда же приехала опечатка `transcibe` в тексте ошибки
|
||||
приёма (нашёл проход `review-specs` на ревью change
|
||||
`2026-08-11-fix-http-handler-tests`): в одиночку текст ошибки менять нельзя,
|
||||
а здесь контракт переписывается целиком.
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# 🧹 Покрыть тестами разбор вывода ffprobe
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Проверки приёма перестали звать настоящий ffprobe 2026-08-11, а своего теста у адаптера метаданных нет: разбор JSON и отличие «программы нет в PATH» от «обработка отказала» не проверяет ничто.
|
||||
- **Теги:** review-2026-08-11
|
||||
|
||||
До 2026-08-11 настоящий `ffprobe` звали проверки приёма — правда, звали так, что
|
||||
он всегда отказывал, то есть проверяли отказ и выдавали его за успех. Теперь
|
||||
приём получает длительность от подставного источника, а у самого адаптера своего
|
||||
теста нет: разбор JSON, перевод длительности из строки в секунды и отличие
|
||||
«программы нет в `PATH`» от «обработка отказала» не проверяет ничто.
|
||||
|
||||
Решение и его цена записаны в
|
||||
[ADR-2026-08-11-stub-adapters-in-tests](../../docs/adr/ADR-2026-08-11-stub-adapters-in-tests.md);
|
||||
дыра названа в [review.md](../../docs/review.md), «Перестали проверять
|
||||
сознательно».
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `internal/adapter/metaviewer/ffmpeg` — новый тестовый файл.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Разбор годного вывода `ffprobe` проверен без запуска самой программы: длина в
|
||||
секундах получается из заданного JSON. Оракул — тест на подставленном выводе.
|
||||
- Отсутствие программы в `PATH` даёт отличимую ошибку, а не ту же, что отказ
|
||||
обработки. Оракул — прогон с `PATH`, не содержащим `ffprobe`: текст ошибки
|
||||
называет именно ненайденную программу.
|
||||
- Вырожденный вывод — пустой, без поля длительности, с нечисловой строкой — не
|
||||
превращается в нулевую длительность молча. Оракул — тест на трёх таких входах,
|
||||
каждый даёт ошибку.
|
||||
|
||||
## Рамки
|
||||
|
||||
Проверка на настоящей записи здесь не заводится: она требует файла в
|
||||
репозитории, а `testdata` в проекте нет. Если такая проверка нужна, это своё
|
||||
решение и своя задача.
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# 🐞 Не писать имя файла пользователя в журнал
|
||||
|
||||
- **Тип:** fix
|
||||
- **Категория:** Очередь — сломано сейчас: каждая принятая запись кладёт имя файла живого пользователя в журнал контейнера, нарушение объявлено критическим и необратимым
|
||||
- **Зачем:** Приём кладёт имя файла, данное пользователем, в журнал контейнера на каждой принятой записи — инвариант приватности объявляет это критическим и необратимым.
|
||||
- **Теги:** review-2026-08-11
|
||||
|
||||
Инвариант «Содержимое записи остаётся приватным» (`CLAUDE.md`) запрещает писать
|
||||
имя файла пользователя в лог: «только длина и идентификаторы». Приём пишет.
|
||||
Строка идёт на **общем** шаге заведения задачи, то есть и для записей из
|
||||
Telegram, и для записей по HTTP.
|
||||
|
||||
Найдено проходом `review-specs` ревью дизайна change
|
||||
`2026-08-11-fix-http-handler-tests`; отчёт триажа —
|
||||
`openspec/changes/archive/2026-08-11-fix-http-handler-tests/review/triage.md`.
|
||||
Правка вынесена сюда решением человека на контрольной точке: задача была про проверки.
|
||||
|
||||
## Воспроизведение
|
||||
|
||||
```
|
||||
grep -n 'file_name' internal/service/transcribe.go
|
||||
```
|
||||
|
||||
Отдаёт `107: "file_name", fileName,` — поле в `s.logger.Info("Creating
|
||||
transcribe job", ...)`. Уровень `Info`, то есть в боевой конфигурации строка
|
||||
пишется. Приходит `fileName` из `CreateJobFromApi` и `CreateJobFromTelegram`
|
||||
без изменений — это то самое имя, которое дал отправитель.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `internal/service/transcribe.go`, функция `createTranscribeJob` — журнальные
|
||||
строки шага заведения;
|
||||
- `docs/conventions/logging.md` — если после правки понадобится строка про то,
|
||||
чем имя заменяется.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Имени, данного отправителем, нет ни в одной журнальной строке приёма. Оракул —
|
||||
прогон приёма с записью, чьё имя содержит опознаваемую строку, и `grep` этой
|
||||
строки по перехваченному журналу: ничего не найдено.
|
||||
- Идентификатор файла, его расширение и длина в журнале остаются: по ним путь
|
||||
записи прослеживается. Оракул — тот же перехваченный журнал, `file_id` и
|
||||
`size` на месте.
|
||||
|
||||
## Рамки
|
||||
|
||||
Прочие журнальные строки проекта здесь не пересматриваются. Полная сверка
|
||||
журнала с запретами — отдельная работа; эта задача закрывает названный
|
||||
инвариантом случай.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# 🐞 Убирать записанный файл, когда приём отказал на середине
|
||||
|
||||
- **Тип:** fix
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Отказ чтения метаданных и отказ записи на диск оставляют файл в каталоге хранения без задачи и без учёта: сопоставить его не с чем, удалять приходится руками.
|
||||
- **Теги:** review-2026-08-11
|
||||
|
||||
Приём пишет файл на диск, потом спрашивает у источника метаданных длительность,
|
||||
потом заводит запись в учёте и задачу. Уборка при отказе есть **только на
|
||||
последнем шаге**: отказ `fileRepo.Create` зовёт `os.Remove`, а отказы записи на
|
||||
диск и чтения метаданных возвращают ошибку, оставляя файл лежать.
|
||||
|
||||
Сопоставить такой файл не с чем: записи в `files` под него нет, задачи нет,
|
||||
имя — случайный идентификатор. На сервере это `data/files`, где лежат голосовые
|
||||
сообщения живых людей, а проект по паспорту «не хранит записи как архив».
|
||||
|
||||
Найдено проходом `review-specs` ревью дизайна change
|
||||
`2026-08-11-fix-http-handler-tests`; отчёт триажа —
|
||||
`openspec/changes/archive/2026-08-11-fix-http-handler-tests/review/triage.md`.
|
||||
Вынесено сюда решением человека на контрольной точке.
|
||||
|
||||
## Воспроизведение
|
||||
|
||||
Асимметрия видна в самом коде:
|
||||
|
||||
```
|
||||
sed -n '110,160p' internal/service/transcribe.go
|
||||
```
|
||||
|
||||
Ветка `fileRepo.Create` (строки 152–157) содержит `os.Remove(storageFilePath)` с
|
||||
комментарием «Удаляем файл если не удалось создать запись в БД». Ветки
|
||||
`io.Copy`, `dst.Close` и `metaviewer.GetInfo` (строки 116–133) возвращают ошибку
|
||||
без уборки, хотя файл к этому моменту уже создан.
|
||||
|
||||
Прогоном: `TestCreateTranscribeJob_MetaViewerFailure` в
|
||||
`internal/controller/http/transcribe_test.go` доводит приём до этой ветки —
|
||||
после него в каталоге хранения лежит файл, а `countJobs` равен нулю.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `internal/service/transcribe.go`, функция `createTranscribeJob` — три ветки
|
||||
отказа между созданием файла и заведением записи в учёте;
|
||||
- `internal/controller/http/transcribe_test.go` — случай отказа метаданных
|
||||
получает проверку на отсутствие файла;
|
||||
- раскладка `data/files` не меняется: имена и формат прежние.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Отказ чтения метаданных не оставляет файла в каталоге хранения. Оракул — тест
|
||||
на подставном источнике, возвращающем ошибку: каталог пуст после запроса.
|
||||
- Отказ записи на диск на середине не оставляет частично записанного файла.
|
||||
Оракул — тест с источником данных, обрывающимся на середине чтения.
|
||||
- Уборка сама не заслоняет исходную ошибку: отправитель по-прежнему получает
|
||||
отказ, а причина уезжает в журнал. Оракул — тот же тест, код ответа и запись в
|
||||
перехваченном журнале.
|
||||
|
||||
## Рамки
|
||||
|
||||
Уборка файлов, осиротевших **до** этой правки, сюда не входит: их надо найти
|
||||
сверкой каталога с учётом, и это отдельная работа с боевыми данными.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# 🧹 Покрыть тестами шаги конвейера и захват задачи
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Очередь
|
||||
- **Зачем:** Тестовых файлов в проекте два, и оба мимо конвейера: потеря ссылки на файл, двойной ответ пользователю и гонка при захвате не поймаются ничем.
|
||||
- **Теги:** review-2026-08-11
|
||||
|
||||
Тестовых файлов в проекте два: приём по HTTP и деление длинного сообщения
|
||||
Telegram. Шаги конвейера — `FindAndRunConversionJob`, `FindAndRunTranscribeJob`,
|
||||
`FindAndRunTranscribeCheckJob` — и воркеры, которые их выполняют, не покрыты ничем.
|
||||
|
||||
Свойства, названные типовым узлом «Шаг конвейера» в
|
||||
[review.md](../../docs/review.md), сегодня не проверяет ни одна команда: что
|
||||
«задач нет» отличается от отказа, что ссылка на файл не теряется, что повтор не
|
||||
плодит лишних записей, что пользователю отвечают ровно один раз. Три воркера
|
||||
читают общий `*sql.DB`, и теста с параллельным доступом нет тоже.
|
||||
|
||||
Найдено проходом `review-autotests` ревью кода change
|
||||
`2026-08-11-fix-http-handler-tests`, вопросом темы `autotests`.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `internal/service` — новые тесты шагов конвейера;
|
||||
- `internal/controller/worker` — тест на одновременную работу трёх воркеров
|
||||
против одной базы;
|
||||
- подстановки: `internal/adapter/recognizer/memory.go` уже есть, конвертеру и
|
||||
источнику метаданных нужны свои.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- «Задач нет» отличается от отказа и не считается ошибкой. Оракул — тест на
|
||||
пустой базе: шаг возвращает `NoopJobError`, метрика отказов не растёт.
|
||||
- Задача, брошенная на середине шага, достаётся снова по истечении захвата и
|
||||
проходит шаг заново, не плодя вторых файлов. Оракул — тест: прервать шаг,
|
||||
отмотать время захвата, прогнать снова, сверить число записей в `files`.
|
||||
- Три воркера против одной базы не выдают одну задачу дважды. Оракул — тест с
|
||||
параллельным прогоном под `-race`.
|
||||
|
||||
## Рамки
|
||||
|
||||
Задачу лучше брать **после** разведки
|
||||
[job-queue-choice](job-queue-choice.md): она может сменить модель очереди
|
||||
целиком, и тесты, написанные под сегодняшний захват, придётся выбросить вместе с
|
||||
ним.
|
||||
|
||||
Reference in New Issue
Block a user