Приём (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>
5.5 KiB
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_codeqbit_add - AND автор загрузки уведомляется
Requirement: catched не считается пропажей раздачи
Система SHALL исключать состояние catched из проверок «раздача не найдена в
qBittorrent» — как в поллинге активных загрузок, так и в сверке рассинхрона
(state-reconciliation). У пойманной загрузки раздачи в qBittorrent ещё нет по
дизайну, поэтому её отсутствие система SHALL NOT трактовать как рассинхрон,
orphaned или пропажу источника.
Scenario: Отсутствие раздачи у catched — не рассинхрон
- GIVEN загрузка в
catched(раздачи в qBittorrent ещё нет) - WHEN идёт тик поллинга и сверки
- THEN загрузка не считается пропавшей/рассинхронизированной и остаётся в
catched(до добавления воркером или срабатыванияcatch_timeout)