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

76 lines
5.9 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.
## Why
Загрузка magnet'ом ушла в терминальный `failed`/`magnet_timeout` по
wall-clock таймауту (возраст от `created_at`, ~1ч), хотя qBittorrent просто
долго тянул метаданные (медленные трекеры / мало пиров). Метаданные в итоге
пришли, торрент жив и качается, но задача застряла в терминальном состоянии
без выхода — восстановить её нельзя. Вдобавок падение происходит молча (нет
уведомления автору), а единственный путь возврата `Worker.Retry` баговый
(не сбрасывает базис времени → задача мгновенно снова падает) и доступен
только через REST, но не из веб-UI и Telegram.
## What Changes
- **Терпеливость к `metaDL`.** Перестаём убивать долгий magnet агрессивным
таймаутом. Дефолт `[worker].magnet_timeout` поднимается до `24h` — это
редкий страховочный предохранитель, а не рабочий механизм. Настоящие
провалы определяются по статусам ошибок qBittorrent (`error`/`missingFiles`
`qbit_error`), а не по wall-clock. *(У qBittorrent нет статуса «magnet
мёртв» — зависший magnet вечно висит в `metaDL`, поэтому единственный
сигнал на этот кейс — большой страховочный таймаут.)*
- **Базис таймаута — от факта, а не от `created_at`.** Возраст для
`magnet_timeout`/`stalled` считаем от времени добавления торрента в
qBittorrent (`added_on`), а не от создания записи. Это чинит неверный
отсчёт для усыновлённых раздач и устраняет мгновенное повторное падение
после возврата в `downloading`.
- **Уведомление о любом падении.** Переход в `failed` (любой `error_code`)
и `stuck` уведомляет автора загрузки через `notifier` (раньше уведомления
слались только для `review`/`done`/`orphaned`/`target_missing`).
- **Авто-восстановление из `failed`/`stuck`.** Фоновая сверка
(state-reconciliation) замечает, что у задачи в восстановимом
`failed`/`stuck` (наша нетерпеливость: `magnet_timeout`, `stalled`)
источник в qBittorrent жив и продвинулся, и возвращает задачу в поток
(`downloading` либо `completed` по статусу торрента). Пользовательские
и реальные провалы (`qbit_error`, `reverted`, `cancelled`) сверка не
воскрешает.
- **Ручной retry из UI и Telegram.** Кнопка повторной попытки добавляется в
веб-UI и Telegram-бот (раньше — только Cancel; retry был только в REST).
`Worker.Retry` чинится: перецепляется к уже живому торренту вместо слепого
повторного `Add`, базис таймаута сбрасывается.
## Capabilities
### New Capabilities
Нет. Уведомления о падении и семантика таймаута относятся к жизненному циклу
загрузки, который пока живёт в `docs/specs/workflow.md` (ещё не мигрирован в
OpenSpec); заводить отдельную capability `notifications` сейчас —
преждевременное дробление.
### Modified Capabilities
- `state-reconciliation`: восстановимые `failed`/`stuck` (`magnet_timeout`,
`stalled`) перестают быть «неприкосновенными» для сверки и подлежат
авто-восстановлению при живом продвинувшемся источнике; добавляется
требование о восстановлении и о доступности ручного retry. `qbit_error`,
`reverted`, `cancelled`, `deleted` остаются вне восстановления.
## Impact
- **Спеки:** дельта `state-reconciliation`; обновление графа переходов и
семантики таймаута/уведомлений в `docs/specs/workflow.md` (источник истины
по жизненному циклу до миграции).
- **Конфиг:** дефолт `[worker].magnet_timeout``24h`
(`internal/config/config.go`), `config.example.toml`.
- **Код:** `internal/worker/worker.go``transition` (уведомление о
failed/stuck), `checkTimeouts` (базис от `added_on`), `reconcile`/
`reconcileDesync` (воскрешение из failed/stuck), `Retry` (перецепление +
сброс базиса); новый `Event` падения и его обработка в `notifier`/
`tgbot`/`httpapi`; кнопка retry в `internal/httpapi` и `internal/tgbot`;
вынос строки `"magnet_timeout"` (и смежных `error_code`) в именованные
константы рядом с состояниями.
- **qBittorrent-клиент:** возможно потребуется поле `added_on` в
`qbt.Torrent` (если ещё не читается).
- **Миграции БД:** не ожидаются (восстановление опирается на состояние qBit и
существующие поля задачи).