Files
avandClaude Opus 4.8 70d8758646 Восстановление зависших загрузок и уведомления о падении (state-reconciliation)
Долгий metaDL больше не убивается агрессивным таймаутом: дефолт
magnet_timeout 30m → 24h (страховочный предохранитель), базис отсчёта —
added_on из qBittorrent, а не created_at (переживает retry/усыновление).

Авто-восстановление: фоновая сверка возвращает в поток задачи, упавшие по
нашей нетерпеливости (magnet_timeout/stalled), когда источник ожил и
продвинулся за условие падения (downloading/completed по статусу торрента);
qbit_error не воскрешается. Конфликт idempotency (infohash занят другой
активной задачей) — оставляем в failed.

Уведомления: любой переход в failed/stuck пингует автора (включая приёмный
qbit_add через ingest), с дебаунсом против спама при флаппинге stalled.
Ручной retry добавлен в веб-UI и Telegram; Retry перецепляется к живому
торренту вместо слепого Add.

Дельта state-reconciliation влита в живые спеки; обновлён workflow.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:51:57 +03:00

5.1 KiB
Raw Permalink Blame History

1. Константы и базис таймаута

  • 1.1 Вынести error_code-строки (magnet_timeout, stalled, qbit_error — в worker; qbit_add — в ingest) в именованные константы; error_code уже читается из store.Download.ErrorCode (миграция не нужна)
  • 1.2 В checkTimeouts считать возраст от torrent.AddedOn (UTC) через хелпер torrentAge (фолбэк на created_at, если added_on нет)
  • 1.3 Поднять дефолт [worker].magnet_timeout до 24h в internal/config/config.go и config.example.toml (с пометкой про страховку)

2. Уведомление о падении

  • 2.1 Добавить EventFailed в NotifyEvent (worker.go)
  • 2.2 В transition слать EventFailed при переходе в StateFailed и StateStuck (неблокирующе, вне w.mu)
  • 2.3 Обработать EventFailed в Telegram (renderFailed + retryKeyboard); httpapi Notifier не реализует — только tgbot

3. Авто-восстановление в сверке

  • 3.1 Восстановление idempotency_key отдельным методом стора НЕ нужно: SetDownloadState сам ставит ключ в infohash для нетерминального состояния
  • 3.2 Реализовать reconcileRecovery (под w.mu, из Poll): задачи в failed/stuck с error_code magnet_timeout/stalled, поиск в byHash
  • 3.3 Воскрешать только при прогрессе торрента (torrentProgressed): magnet_timeout!isMeta; stalled!isStalledDL; не classErrored; целевое состояние из classify (recoveredState: ready → completed, downloading → downloading)
  • 3.4 Конфликт занятого idempotency_key: пре-проверка через FindActiveByInfohash — оставляем в failed + лог
  • 3.5 Тесты (recovery_test.go): метаданные → downloading; готов → completed; всё ещё metaDLfailed; qbit_error/ошибка не воскрешаются; нет источника → failed; конфликт ключа → failed

4. Починка Retry и ручной retry в транспортах

  • 4.1 Worker.Retry: при живом торренте — без повторного Add, только downloading; Add лишь когда раздачи нет
  • 4.2 Тест: retry при живом торренте не делает Add и не падает на ближайшем тике (базис added_on)
  • 4.3 Кнопка retry в веб-UI (internal/httpapi + index.html) для failed/stuck; роут /ui/downloads/{id}/retry; тест TestUIRetry
  • 4.4 Действие retry в Telegram-боте (колбэк retry: + кнопка); тесты TestBot_CallbackRetry, TestBot_NotifyFailed

5. Документация спек

  • 5.1 Обновить docs/specs/workflow.md: magnet_timeout как страховка, базис added_on, уведомление о failed/stuck, восстановление и retry
  • 5.2 openspec validate --strict download-failure-recovery — без ошибок

6. Проверка

  • 6.1 task lint (0 issues) и task test (зелёные)
  • 6.2 Ревью кода (чекпоинт до archive) — 8-угловой multi-agent проход

7. Фиксы по ревью кода

  • 7.1 Конфликт idempotency_key: проверка FindActiveByInfohash распространена на ветку completed (а не только downloading) — иначе constraint-ошибка и зависание в failed с логом каждый тик (тест TestRecoverySkipsConflictOnCompleted)
  • 7.2 Recovery не пишет в error_msg здоровой задачи (передаём "", причину — в лог), иначе заметка светилась бы как ошибка в UI/REST
  • 7.3 Эффективность: reconcileRecovery грузит кандидатов через store.ListRecoverable (SQL-фильтр по error_code), а не вычитывает все failed/stuck каждый тик
  • 7.4 Дебаунс уведомлений о падении (shouldNotifyFail, окно 1h) — мерцающий stalled-торрент не спамит EventFailed (тест TestFailNotifyDebounce)
  • 7.5 Уведомление о падении qbit_add в ingest через closure (SetFailureNotifier, wiring в serve.go), тест TestIngestQbitErrorNotifies
  • 7.6 Конвенция логирования: убран неймспейс-префикс recovery: из msg
  • 7.7 Мелочи: дедуп ветки renderCard, диагностика в torrentAge, time.Unix(...).UTC(), актуализирован комментарий terminalStates