Files
jellybit/openspec/changes/archive/2026-06-30-download-failure-recovery/tasks.md
T
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

77 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. Константы и базис таймаута
- [x] 1.1 Вынести `error_code`-строки (`magnet_timeout`, `stalled`,
`qbit_error` — в `worker`; `qbit_add` — в `ingest`) в именованные константы;
`error_code` уже читается из `store.Download.ErrorCode` (миграция не нужна)
- [x] 1.2 В `checkTimeouts` считать возраст от `torrent.AddedOn` (UTC) через
хелпер `torrentAge` (фолбэк на `created_at`, если `added_on` нет)
- [x] 1.3 Поднять дефолт `[worker].magnet_timeout` до `24h` в
`internal/config/config.go` и `config.example.toml` (с пометкой про страховку)
## 2. Уведомление о падении
- [x] 2.1 Добавить `EventFailed` в `NotifyEvent` (worker.go)
- [x] 2.2 В `transition` слать `EventFailed` при переходе в `StateFailed` и
`StateStuck` (неблокирующе, вне `w.mu`)
- [x] 2.3 Обработать `EventFailed` в Telegram (`renderFailed` + `retryKeyboard`);
httpapi `Notifier` не реализует — только tgbot
## 3. Авто-восстановление в сверке
- [x] 3.1 Восстановление `idempotency_key` отдельным методом стора НЕ нужно:
`SetDownloadState` сам ставит ключ в `infohash` для нетерминального состояния
- [x] 3.2 Реализовать `reconcileRecovery` (под `w.mu`, из `Poll`): задачи в
`failed`/`stuck` с `error_code` `magnet_timeout`/`stalled`, поиск в `byHash`
- [x] 3.3 Воскрешать только при прогрессе торрента (`torrentProgressed`):
`magnet_timeout``!isMeta`; `stalled``!isStalledDL`; не `classErrored`;
целевое состояние из `classify` (`recoveredState`: ready → `completed`,
downloading → `downloading`)
- [x] 3.4 Конфликт занятого `idempotency_key`: пре-проверка через
`FindActiveByInfohash` — оставляем в `failed` + лог
- [x] 3.5 Тесты (`recovery_test.go`): метаданные → `downloading`; готов →
`completed`; всё ещё `metaDL``failed`; `qbit_error`/ошибка не воскрешаются;
нет источника → `failed`; конфликт ключа → `failed`
## 4. Починка Retry и ручной retry в транспортах
- [x] 4.1 `Worker.Retry`: при живом торренте — без повторного `Add`, только
`downloading`; `Add` лишь когда раздачи нет
- [x] 4.2 Тест: retry при живом торренте не делает `Add` и не падает на
ближайшем тике (базис `added_on`)
- [x] 4.3 Кнопка retry в веб-UI (`internal/httpapi` + `index.html`) для
`failed`/`stuck`; роут `/ui/downloads/{id}/retry`; тест `TestUIRetry`
- [x] 4.4 Действие retry в Telegram-боте (колбэк `retry:` + кнопка); тесты
`TestBot_CallbackRetry`, `TestBot_NotifyFailed`
## 5. Документация спек
- [x] 5.1 Обновить `docs/specs/workflow.md`: `magnet_timeout` как страховка,
базис `added_on`, уведомление о `failed`/`stuck`, восстановление и retry
- [x] 5.2 `openspec validate --strict download-failure-recovery` — без ошибок
## 6. Проверка
- [x] 6.1 `task lint` (0 issues) и `task test` (зелёные)
- [x] 6.2 Ревью кода (чекпоинт до archive) — 8-угловой multi-agent проход
## 7. Фиксы по ревью кода
- [x] 7.1 Конфликт `idempotency_key`: проверка `FindActiveByInfohash`
распространена на ветку `completed` (а не только `downloading`) —
иначе constraint-ошибка и зависание в `failed` с логом каждый тик
(тест `TestRecoverySkipsConflictOnCompleted`)
- [x] 7.2 Recovery не пишет в `error_msg` здоровой задачи (передаём `""`,
причину — в лог), иначе заметка светилась бы как ошибка в UI/REST
- [x] 7.3 Эффективность: `reconcileRecovery` грузит кандидатов через
`store.ListRecoverable` (SQL-фильтр по `error_code`), а не вычитывает все
failed/stuck каждый тик
- [x] 7.4 Дебаунс уведомлений о падении (`shouldNotifyFail`, окно 1h) —
мерцающий stalled-торрент не спамит `EventFailed` (тест
`TestFailNotifyDebounce`)
- [x] 7.5 Уведомление о падении `qbit_add` в ingest через closure
(`SetFailureNotifier`, wiring в serve.go), тест
`TestIngestQbitErrorNotifies`
- [x] 7.6 Конвенция логирования: убран неймспейс-префикс `recovery:` из `msg`
- [x] 7.7 Мелочи: дедуп ветки `renderCard`, диагностика в `torrentAge`,
`time.Unix(...).UTC()`, актуализирован комментарий `terminalStates`