Files
T
avandClaude Opus 4.8 bcc7b2d76b Жизненный цикл: claim-токен распознавания, индексация хешей, retry сломанного торрента
Три мелких фикса из docs/backlog/review-lifecycle-minor.md (ревью Fable
2026-07-08). MINOR-9 (I/O под глобальным w.mu) осознанно waive для
one-user home-сервера — не трогаем.

MINOR-8: claim-токен распознавания. recognizeOne фиксирует updated_at на
момент claim (перечитывая запись после перехода в recognizing), а
finishRecognition коммитит результат, только если токен совпал. Иначе за
время LLM-вызова задачу увели из recognizing и вернули обратно
(cancel → relink revive) — это уже другой эпизод, устаревший результат
отбрасываем, задача остаётся в recognizing для перезапуска поллингом.

NIT-11: lookup-мапы (byHash/live/torrentByInfohash) больше не индексируют
усечённый 40-hex t.Hash v2-only торрентов. Новый хелпер torrentIndexHashes
зеркалит выбор torrentHashes: t.Hash берём только при отсутствии обоих
infohash_v1/v2. Убирает теоретический ложный матч по коллизии длины.

NIT-12: retry живого, но сломанного торрента (error/missingFiles) теперь
отклоняется с подсказкой починить раздачу (recheck) в qBittorrent, вместо
бессмысленной переотдачи источника (сверка тут же вернула бы задачу в
failed). Повторный Add — только когда раздачи в qBittorrent нет. Меняет
спеку state-reconciliation → дельта openspec/changes/2026-07-17-retry-reject-broken-torrent
(не архивировал).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 20:35:14 +03:00

6.2 KiB
Raw Blame History

MODIFIED Requirements

Requirement: Ручной повтор зависшей/упавшей загрузки из транспортов

Система SHALL предоставлять пользователю команду повторной попытки (retry) для задач в failed/stuck из веб-UI и Telegram (не только через REST API). Retry SHALL переводить задачу обратно в downloading, не вызывая её немедленного повторного падения по таймауту: базис отсчёта таймаутов SHALL сбрасываться.

Сброс базиса система SHALL выполнять сохранением времени retry в поле задачи (retried_at, RFC 3339 UTC), которое приподнимает пол ОБОИХ страховочных мер (magnet_timeout по возрасту и stuck_after по простою): отсчёт ведётся от max(базис, retried_at). retried_at SHALL храниться в задаче (не в памяти процесса), чтобы сброс базиса пережил интервал поллинга и рестарт процесса. Благодаря этому даже живой, но давно добавленный либо давно простаивающий торрент после retry SHALL получать свежее окно и на ближайшем тике сверки падать снова SHALL NOT.

Если источник задачи уже жив и ЗДОРОВ в qBittorrent, retry SHALL перецепляться к существующему торренту, а не добавлять источник повторно вслепую. Если же живой торрент в состоянии ошибки qBittorrent (error/missingFiles), retry SHALL отклоняться с понятным пользователю сообщением — починить раздачу (recheck/восстановить файлы) в qBittorrent и повторить. Повторная отдача источника такой торрент не чинит (qBittorrent отверг бы дубль), а простой возврат в downloading тут же снова упал бы qbit_error по сверке (+ дебаунс уведомления) — retry выглядел бы сломанным. При отказе состояние задачи (failed/stuck) система менять SHALL NOT и повторный Add выполнять SHALL NOT.

Повторный Add при retry система SHALL выполнять, только когда раздачи в qBittorrent нет, — по типу источника (source_type), как и добавление пойманной загрузки (см. download-tracking «Добавление пойманной загрузки в qBittorrent»): magnet/url — ссылкой; torrent — сохранёнными байтами .torrent файлом. Для torrent-источника retry БЕЗ живой раздачи система SHALL добавлять раздачу байтами и SHALL NOT активировать задачу в downloading, не добавив её (иначе задача повиснет как «нет в qBittorrent»).

Scenario: Retry упавшей magnet-загрузки из веб-UI

  • GIVEN задача в failed, её торрент жив и здоров в qBittorrent
  • WHEN пользователь нажимает retry в веб-UI
  • THEN задача возвращается в downloading без повторного Add
  • AND не падает снова на ближайшем тике сверки по таймауту

Scenario: Retry живого, но давно простаивающего торрента не падает снова

  • GIVEN задача в stuck/stalled, её торрент жив в qBittorrent, но добавлен давно и данные не двигались дольше stuck_after
  • WHEN пользователь нажимает retry
  • THEN задача возвращается в downloading без повторного Add
  • AND на ближайшем тике сверки НЕ падает снова в stuck (базис сброшен через retried_at)

Scenario: Retry доступен в Telegram

  • WHEN для задачи в failed/stuck пользователь вызывает retry в Telegram-боте
  • THEN задача возвращается в downloading

Scenario: Retry без живого источника добавляет источник заново

  • GIVEN задача в failed, раздачи в qBittorrent нет
  • WHEN пользователь инициирует retry
  • THEN источник добавляется в qBittorrent заново — magnet/url ссылкой, torrent сохранёнными байтами файлом
  • AND задача переходит в downloading

Scenario: Retry сломанного живого торрента отклоняется

  • GIVEN задача в failed, её торрент присутствует в qBittorrent, но в состоянии ошибки (error/missingFiles)
  • WHEN пользователь инициирует retry
  • THEN retry отклоняется с сообщением починить раздачу (recheck) в qBittorrent
  • AND состояние задачи не меняется (остаётся failed), повторный Add не выполняется

Scenario: Retry torrent-загрузки без живого источника

  • GIVEN задача с source_type = torrent в failed, раздачи в qBittorrent нет, байты .torrent сохранены
  • WHEN пользователь инициирует retry
  • THEN сохранённые байты добавляются в qBittorrent файлом
  • AND задача переходит в downloading (не остаётся без раздачи)