Приём (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.6 KiB
5.6 KiB
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_codeqbit_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/задаче