Files
jellybit/openspec/changes/archive/2026-07-07-fast-catch-ingest/specs/download-tracking/spec.md
T
avandClaude Opus 4.8 0d263270cb Быстрый приём: сохранение в catched, добавление в qBittorrent — шаг worker'а
Приём (Ingest) стал быстрым: синхронно только парс magnet, синтез контекста из
полей ссылки, атомарный дедуп и запись загрузки в новое состояние `catched` —
ответ клиенту сразу. Медленный вывод имени (LLM) и добавление в qBittorrent
вынесены в асинхронный шаг машины состояний, который двигает worker.

- store: состояние `catched` (нетерминальное, активная группа); атомарный
  переход PromoteCatched (catched → downloading + display_name) с гардом
  state='catched' (ре-валидация после сетевых вызовов вне блокировки)
- ingest: убраны namer/qbt из пути приёма; пишем `catched`, отвечаем сразу
- worker.processCatched: вне w.mu выводит имя и qbt.Add, под w.mu — короткий
  переход; сбой add оставляет catched (ретрай тиком); предохранитель
  catch_timeout → failed(qbit_add)+notify; catched исключён из проверок пропажи
- config: worker.catch_timeout (дефолт 10m)
- веб-UI: бейдж catched, активная группа, самозавершающийся htmx-поллинг
  карточки/страницы до перехода в downloading; Telegram-текст без сырого catched
- OpenSpec: дельты ingest/download-tracking/web-ui влиты в спеки, change
  заархивирован

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 21:29:28 +03:00

5.5 KiB
Raw Blame History

ADDED Requirements

Requirement: Добавление пойманной загрузки в qBittorrent

Worker SHALL периодически (в поллинг-цикле, под единой блокировкой переходов) подхватывать загрузки в состоянии catched и для каждой: вывести отображаемое имя из контекста (см. ingest «Отображаемое имя торрента из контекста»), добавить источник в qBittorrent (категория qbittorrent.category, savepath, rename) и перевести загрузку catched → downloading. Отдельного состояния между catched и downloading быть SHALL NOT — успешный add сразу переводит в downloading (которое и означает «в qBit, возможно metaDL»).

Неуспешный add (qBittorrent недоступен и т.п.) SHALL оставлять загрузку в catched для повторной попытки на следующем тике; переход в терминальное состояние по единичному сбою происходить SHALL NOT (ретраи — естественными тиками поллинга).

Медленные вызовы (вывод имени через LLM, qbt.Add) SHALL выполняться вне блокировки сериализации переходов, чтобы не задерживать команды транспортов и поллинг. Под блокировкой сериализуется только запись перехода catched → downloading (см. «Переходы состояний сериализуются воркером»), с ре-валидацией, что загрузка всё ещё в catched (иначе переход отклоняется — например, при параллельной отмене).

Scenario: Пойманная загрузка добавляется в qBittorrent

  • GIVEN загрузка в состоянии catched
  • WHEN worker обрабатывает тик
  • THEN выводится отображаемое имя, источник добавляется в qBittorrent с нашей категорией и rename
  • AND загрузка переходит в downloading

Scenario: Временный сбой добавления — повтор

  • GIVEN загрузка в catched, qBittorrent временно недоступен
  • WHEN worker пытается добавить источник и add не удался
  • THEN загрузка остаётся в catched
  • AND на следующем тике попытка добавления повторяется

Scenario: Отмена во время добавления

  • GIVEN загрузка в catched, worker выводит имя и добавляет её вне блокировки
  • WHEN параллельно приходит команда отмены (catched → cancelled), а затем worker берёт блокировку для записи перехода
  • THEN ре-валидация видит, что загрузка уже не в catched, и переход в downloading не применяется

Requirement: Предохранитель зависшего catched

Система SHALL переводить загрузку, задержавшуюся в catched дольше catch_timeout (конфигурируемый предохранитель, дефолт консервативный), в failed (error_code qbit_add) и уведомлять автора. Возраст SHALL считать от времени попадания в catched (создания загрузки). Предохранитель — редкий страховочный механизм на случай устойчивой недоступности qBittorrent, а не штатный путь.

Scenario: catched висит дольше таймаута

  • GIVEN загрузка в catched дольше catch_timeout
  • WHEN идёт тик поллинга
  • THEN загрузка переходит в failed с error_code qbit_add
  • AND автор загрузки уведомляется

Requirement: catched не считается пропажей раздачи

Система SHALL исключать состояние catched из проверок «раздача не найдена в qBittorrent» — как в поллинге активных загрузок, так и в сверке рассинхрона (state-reconciliation). У пойманной загрузки раздачи в qBittorrent ещё нет по дизайну, поэтому её отсутствие система SHALL NOT трактовать как рассинхрон, orphaned или пропажу источника.

Scenario: Отсутствие раздачи у catched — не рассинхрон

  • GIVEN загрузка в catched (раздачи в qBittorrent ещё нет)
  • WHEN идёт тик поллинга и сверки
  • THEN загрузка не считается пропавшей/рассинхронизированной и остаётся в catched (до добавления воркером или срабатывания catch_timeout)