Files
jellybit/openspec/changes/archive/2026-07-07-fast-catch-ingest/tasks.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.6 KiB
Raw Blame History

1. Состояние catched

  • 1.1 Добавить StateCatched State = "catched" в internal/store/download.go; убедиться, что оно НЕ в terminalStates (нетерминальное = активное)
  • 1.2 Включить StateCatched в statesInGroup(GroupActive); проверить поиск/фильтры списка
  • 1.3 Тест: catched активно для дедупа (CreateDownloadIfNoActive держит инвариант ≤1 активная на infohash с участием catched)

2. Быстрый приём (ingest)

  • 2.1 Ingest: писать download в StateCatched с пустым DisplayName; убрать из синхронного пути namer.DeriveName и qbt.Add; вернуть Result сразу после записи
  • 2.2 Убрать из ingest.Service ставшие ненужными зависимости/код: Namer, QBittorrent, notifyFailed/SetFailureNotifier, qbit_add-путь падения add (падение теперь у worker'а — п.4)
  • 2.3 Обновить тесты ingest: приём создаёт catched, не зовёт qBit/namer; дедуп по активной (в т.ч. catched); синтез контекста сохраняется как прежде
  • 2.4 Транспорты (httpapi, tgbot): ответ по Result корректен для catched (без правок API); Telegram-текст не показывает сырое catched (дружелюбная формулировка «принято/добавляется»)

3. Асинхронный шаг добавления (worker)

  • 3.1 Внедрить в worker зависимости Namer и qbt.Add; добавить в интерфейс worker.Store новый метод записи имени (SetDisplayName)
  • 3.2 Шаг в поллинг-цикле, сеть ВНЕ w.mu: под замком снять ListDownloadsByState(StateCatched); вне замка для каждой — разобрать d.SourceRef (magnet.Parse) для dn-hint, вывести имя (namer.DeriveName), вызвать qbt.Add(urls=d.SourceRef, category, savepath, rename); снова под замком — ре-валидировать state==catched и записать переход catched → downloading + display_name
  • 3.3 Сбой add: оставить в catched (повтор на следующем тике), не уводить в терминальное по единичному сбою; логировать (ext.* уже логирует клиент)
  • 3.4 Тесты worker: catched → downloading при успехе (rename передан, display_name сохранён, сетевые вызовы вне замка); транзиентный сбой оставляет catched и повторяет; отмена во время add (ре-валидация отбрасывает переход)

4. Предохранитель catch_timeout

  • 4.1 Конфиг catch_timeout (+ дефолт, валидация на старте) — по образцу magnet_timeout; документация конфига
  • 4.2 В поллинг-цикле: catched старше catch_timeout (от создания) → failed (error_code qbit_add) + уведомление автора
  • 4.3 Тест: catched за таймаутом → failed + notify

5. Исключение catched из проверок пропажи

  • 5.1 Убедиться (и закрепить тестом), что поллинг активных и сверка рассинхрона (state-reconciliation) не трактуют catched как пропажу/ orphaned
  • 5.2 Тест на гонку discover ↔ шаг добавления: дубль по infohash не заводится

6. Веб-UI

  • 6.1 Человекочитаемая подпись бейджа для catched (badgeLabel), чтобы не показывать сырое catched; catched в активной группе списка
  • 6.2 Заголовок при пустом display_name (фолбек) и отсутствие секции раздачи/живого прогресса для catched без ошибок
  • 6.3 Самозавершающийся htmx-поллинг карточки/страницы в catched: пока catched — опрашивает фрагмент; по переходе в downloading показывает прогресс/имя без перезагрузки и завершает поллинг фазы (правка live-рендера для не-downloading активной фазы)
  • 6.4 Тест httpapi/шаблонов: карточка/страница catched рендерится; фрагмент-поллинг отдаётся для catched и завершается после перехода

7. Проверка

  • 7.1 task test и task lint зелёные
  • 7.2 openspec validate fast-catch-ingest --strict проходит
  • 7.3 Прогон вручную/через verify: приём отвечает быстро (без LLM в пути), загрузка проходит catched → downloading, карточка обновляется без перезагрузки
  • 7.4 Рантбук отката: снять застрявшие catched (UPDATE download SET state='failed', error_code='qbit_add' WHERE state='catched') — зафиксировать в описании change/задаче