Files
transcriber/tasks/items/orphan-file-on-failed-intake.md
T
av 8739b18a9f tasks: очередь расставлена от базы к деталям
- порядок беклога и роадмапа назначен слоями: проверки, которым можно
  верить → долги входа → владелец записи и контракт API → конвейер под
  тестами → приложение и возможности поверх; у каждого движения записана
  причина;
- заведены восемь задач под пункты «Завершения», которых не закрывала ни
  одна запись, — цель any-audio-source была без задач вовсе;
- у четырёх задач сняты критерии, требовавшие того, что делает задача ниже
  по очереди; исправлены ссылки на несуществующий repo/sqlite и на
  отменённую разведку об очереди.
2026-08-12 20:48:53 +03:00

4.4 KiB

🐞 Убирать записанный файл, когда приём отказал на середине

  • Тип: fix
  • Категория: Очередь — Осиротевший файл — тот же отказ на середине, и чинится тем же местом приёма.
  • Зачем: Отказ чтения метаданных и отказ записи на диск оставляют файл в каталоге хранения без задачи и без учёта: сопоставить его не с чем, удалять приходится руками.
  • Теги: review-2026-08-11, goal:upload-reliability

Приём пишет файл на диск, потом спрашивает у источника метаданных длительность, потом заводит запись в учёте и задачу. Уборка при отказе есть только на последнем шаге: отказ 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 не меняется: имена и формат прежние.

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

  • Отказ чтения метаданных не оставляет файла в каталоге хранения. Оракул — тест на подставном источнике, возвращающем ошибку: каталог пуст после запроса.
  • Отказ записи на диск на середине не оставляет частично записанного файла. Оракул — тест с источником данных, обрывающимся на середине чтения.
  • Уборка сама не заслоняет исходную ошибку: отправитель по-прежнему получает отказ, а причина уезжает в журнал. Оракул — тот же тест, код ответа и запись в перехваченном журнале.

Рамки

Уборка файлов, осиротевших до этой правки, сюда не входит: их надо найти сверкой каталога с учётом, и это отдельная работа с боевыми данными.