## 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 и
  существующие поля задачи).
