Files
transcriber/tasks/items/failure-verdict-vs-retry.md
T

6.1 KiB

🐞 Различать отказ, который стоит повторить, и приговор записи

  • Тип: fix
  • Категория: Очередь — Сроки ожидания идут вперёд: пока их нет, «медленно отвечает» от «упало» неотличимо, и часть отказов классифицировать нечем.
  • Зачем: Отказ приведения останавливает запись с первой попытки, и предел в пять отказов не работает никогда: разовый сбой ffmpeg останавливает запись приговором, хотя повтор обработал бы её успешно.

Спека pipeline заводит требование «Число отказов ограничивает повторы шага» с пределом в пять, но какие отказы этот предел вообще расходуют, она не решает. На деле шаг делит их сам и делит грубо: отказ конвертации, отказ операции у провайдера и отсутствие файла останавливают запись приговором с первой попытки, а до счётчика доходит только отказ хранилища. Значит нарастающая пауза и пять попыток не работают ровно на тех отказах, которые случаются чаще всего.

Разница наблюдаема: переполненный диск, временно недоступный ffmpeg и разовый сбой у провайдера сегодня неотличимы от негодной записи, и человек получает «попробуйте ещё раз» вместо расшифровки, которую дал бы второй заход.

Найдено ревью задачи record-centric-model 2026-08-14; поведение унаследовано от прежней модели, где отказ переводил задачу в конечное состояние. Само по себе деление на приговор и повтор — решение, а не разведка: разбирать надо перечень отказов, а не внешний мир.

Воспроизведение

  1. Подставить конвертер, отказывающий один раз, и завести запись.
  2. Прогнать шаг конвейера.

Видно: запись остановлена признаком с причиной «приговор шага», число отказов — единица, паузы нет. Ожидалось: запись возвращается в работу с нарастающей паузой и останавливается только на шестом отказе.

То же на шаге отправки: отказ операции у провайдера останавливает запись, не израсходовав ни одной попытки.

Затрагивает

  • openspec/specs/pipeline/spec.md — требование о числе отказов и раздел Purpose, где вопрос назван неразобранным;
  • internal/service/transcribe.gofailStep, scheduleRetry и ветки отказа в шагах приведения, отправки, опроса и завершения;
  • internal/entity/audio_record.go — перечень причин остановки, если делению потребуется новая;
  • docs/architecture.md, таблица внешних зависимостей — столбцы «падает» и «отдаёт мусор» описывают сегодняшнее деление.

Критерии приёмки

  • Разовый отказ приведения возвращает запись в работу, а не останавливает её. Оракул — тест: конвертер отказывает один раз, следующий прогон доводит запись до рубежа приведения.
  • Запись, отказывающая на каждой попытке, всё же останавливается по исчерпании предела. Оракул — тест: конвертер отказывает всегда, запись останавливается на шестом захвате с причиной «отказы исчерпаны», а не с первого.
  • Отказ, о котором известно, что повтор его не устранит, останавливает запись сразу. Оракул — тест: запись без файла источника останавливается уже на первом заходе, и причина остановки — приговор шага.
  • Спека называет, какие отказы расходуют предел, а какие выносят приговор. Оракул — openspec validate --strict на изменении плюс сценарий требования, различающий оба исхода.

Рамки

Перечень отказов, которые считать приговором, ограничить теми, что сервис видит сегодня: негодная запись, отказ операции у провайдера, отсутствие файла. Классификацию отказов внешних сервисов по кодам не заводить — сроки ожидания ставит external-call-timeouts, и до неё «медленно» от «упало» неотличимо. Числа предела и паузы не трогать: они записаны в docs/database.md и решением владельца не пересматривались.