tasks: заведены шесть задач из урожая ревью и переименования образца конфига
- пять из урожая change 2026-08-11-fix-http-handler-tests, тег review-2026-08-11; утечка имени файла в журнал поставлена первой строкой очереди - опечатка transcibe дописана в json-api-for-spa: текст ошибки — часть необратимого контракта, и в одиночку он не правится
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
## Очередь
|
## Очередь
|
||||||
|
|
||||||
|
- [🐞 Не писать имя файла пользователя в журнал](items/no-user-filename-in-log.md) — Приём кладёт имя файла, данное пользователем, в журнал контейнера на каждой принятой записи — инвариант приватности объявляет это критическим и необратимым.
|
||||||
- [🧹 Сравнивать доменные ошибки через errors.As](items/errors-as-instead-of-typecast.md) — NoopJobError и JobNotFoundError проверяются приведением типа: первая же обёртка %w между слоями сломает проверку молча.
|
- [🧹 Сравнивать доменные ошибки через errors.As](items/errors-as-instead-of-typecast.md) — NoopJobError и JobNotFoundError проверяются приведением типа: первая же обёртка %w между слоями сломает проверку молча.
|
||||||
- [🧹 Задать таймауты обращениям к внешним сервисам](items/external-call-timeouts.md) — Ни у Telegram, ни у Object Storage, ни у SpeechKit нет таймаута: молчащий собеседник держит шаг конвейера до истечения часового захвата.
|
- [🧹 Задать таймауты обращениям к внешним сервисам](items/external-call-timeouts.md) — Ни у Telegram, ни у Object Storage, ни у SpeechKit нет таймаута: молчащий собеседник держит шаг конвейера до истечения часового захвата.
|
||||||
- [🔬 Очередь задач: своя таблица или готовая библиотека](items/job-queue-choice.md) — Очередь написана вручную: захват двумя запросами без транзакции, протухание временем, опрос раз в секунду вхолостую тремя воркерами.
|
- [🔬 Очередь задач: своя таблица или готовая библиотека](items/job-queue-choice.md) — Очередь написана вручную: захват двумя запросами без транзакции, протухание временем, опрос раз в секунду вхолостую тремя воркерами.
|
||||||
@@ -36,3 +37,8 @@
|
|||||||
- [✨ Отправлять готовый текст через apprise и ntfy](items/ntfy-delivery.md) — Пользователь веба узнаёт о готовности только опросом с открытого экрана.
|
- [✨ Отправлять готовый текст через apprise и ntfy](items/ntfy-delivery.md) — Пользователь веба узнаёт о готовности только опросом с открытого экрана.
|
||||||
- [🔬 Стоит ли брать OpenTelemetry вместо голого Prometheus](items/opentelemetry-fit.md) — Метрик одиннадцать штук на пять счётчиков, трассировки нет вовсе: путь одной записи по конвейеру собирается только чтением логов глазами.
|
- [🔬 Стоит ли брать OpenTelemetry вместо голого Prometheus](items/opentelemetry-fit.md) — Метрик одиннадцать штук на пять счётчиков, трассировки нет вовсе: путь одной записи по конвейеру собирается только чтением логов глазами.
|
||||||
- [🔬 Потолки SpeechKit по длине записи и по формату](items/speechkit-limits.md) — Потолок длины записи и перечень принимаемых форматов неизвестны, а цель про долгие записи без них не начинается.
|
- [🔬 Потолки SpeechKit по длине записи и по формату](items/speechkit-limits.md) — Потолок длины записи и перечень принимаемых форматов неизвестны, а цель про долгие записи без них не начинается.
|
||||||
|
- [🐞 Убирать записанный файл, когда приём отказал на середине](items/orphan-file-on-failed-intake.md) — Отказ чтения метаданных и отказ записи на диск оставляют файл в каталоге хранения без задачи и без учёта: сопоставить его не с чем, удалять приходится руками.
|
||||||
|
- [🧹 Покрыть тестами разбор вывода ffprobe](items/metaviewer-adapter-tests.md) — Проверки приёма перестали звать настоящий ffprobe 2026-08-11, а своего теста у адаптера метаданных нет: разбор JSON и отличие «программы нет в PATH» от «обработка отказала» не проверяет ничто.
|
||||||
|
- [🧹 Покрыть тестами шаги конвейера и захват задачи](items/pipeline-step-tests.md) — Тестовых файлов в проекте два, и оба мимо конвейера: потеря ссылки на файл, двойной ответ пользователю и гонка при захвате не поймаются ничем.
|
||||||
|
- [🧹 Разобрать мелочи http-транспорта](items/http-transport-nits.md) — Маршруты зарегистрированы дважды, и переименование пути в main.go проходит проверки зелёным; обработчик пишет в журнал через стандартный log и дублирует запись, уже сделанную сервисом.
|
||||||
|
- [🧹 Переименовать образец конфига в config.example.toml](items/config-example-toml.md) — Конвенция называет config.dist.toml объявленным расхождением, но тут же пишет это имя как правило — документ противоречит сам себе, а образец расходится с конвенцией.
|
||||||
|
|||||||
@@ -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`. Оракул —
|
- Негодный файл даёт `400` с человекочитаемым текстом, а не `500`. Оракул —
|
||||||
тест: файл, который отвергает разбор метаданных.
|
тест: файл, который отвергает разбор метаданных.
|
||||||
- Тело ошибки одной формы на всех эндпоинтах и не содержит сырого `err.Error()`.
|
- Тело ошибки одной формы на всех эндпоинтах, не содержит сырого `err.Error()` и
|
||||||
Оракул — тест на четырёх ветвях отказа: форма совпадает, текста внутренней
|
опечатки `transcibe`. Оракул — тест на четырёх ветвях отказа (форма совпадает,
|
||||||
ошибки в теле нет.
|
текста внутренней ошибки в теле нет) плюс пустой `grep -rn 'transcibe'
|
||||||
|
internal/`.
|
||||||
- Список записей отдаётся страницами и упорядочен по времени создания. Оракул —
|
- Список записей отдаётся страницами и упорядочен по времени создания. Оракул —
|
||||||
тест на выборке больше страницы.
|
тест на выборке больше страницы.
|
||||||
- Отображение ошибки живёт в одной функции, и она названа в
|
- Отображение ошибки живёт в одной функции, и она названа в
|
||||||
@@ -41,4 +42,7 @@
|
|||||||
|
|
||||||
Аутентификацию и владельца не заводим — это `oidc-login` и `record-ownership`;
|
Аутентификацию и владельца не заводим — это `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