Files
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

78 lines
5.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. Состояние catched
- [x] 1.1 Добавить `StateCatched State = "catched"` в `internal/store/download.go`;
убедиться, что оно НЕ в `terminalStates` (нетерминальное = активное)
- [x] 1.2 Включить `StateCatched` в `statesInGroup(GroupActive)`; проверить
поиск/фильтры списка
- [x] 1.3 Тест: `catched` активно для дедупа (`CreateDownloadIfNoActive` держит
инвариант ≤1 активная на infohash с участием `catched`)
## 2. Быстрый приём (ingest)
- [x] 2.1 `Ingest`: писать `download` в `StateCatched` с пустым `DisplayName`;
убрать из синхронного пути `namer.DeriveName` и `qbt.Add`; вернуть `Result`
сразу после записи
- [x] 2.2 Убрать из `ingest.Service` ставшие ненужными зависимости/код:
`Namer`, `QBittorrent`, `notifyFailed`/`SetFailureNotifier`, `qbit_add`-путь
падения add (падение теперь у worker'а — п.4)
- [x] 2.3 Обновить тесты ingest: приём создаёт `catched`, не зовёт qBit/namer;
дедуп по активной (в т.ч. `catched`); синтез контекста сохраняется как прежде
- [x] 2.4 Транспорты (`httpapi`, `tgbot`): ответ по `Result` корректен для
`catched` (без правок API); Telegram-текст не показывает сырое `catched`
(дружелюбная формулировка «принято/добавляется»)
## 3. Асинхронный шаг добавления (worker)
- [x] 3.1 Внедрить в worker зависимости `Namer` и `qbt.Add`; добавить в
интерфейс `worker.Store` новый метод записи имени (`SetDisplayName`)
- [x] 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`
- [x] 3.3 Сбой `add`: оставить в `catched` (повтор на следующем тике), не уводить
в терминальное по единичному сбою; логировать (ext.* уже логирует клиент)
- [x] 3.4 Тесты worker: `catched → downloading` при успехе (rename передан,
display_name сохранён, сетевые вызовы вне замка); транзиентный сбой оставляет
`catched` и повторяет; отмена во время add (ре-валидация отбрасывает переход)
## 4. Предохранитель catch_timeout
- [x] 4.1 Конфиг `catch_timeout` (+ дефолт, валидация на старте) — по образцу
`magnet_timeout`; документация конфига
- [x] 4.2 В поллинг-цикле: `catched` старше `catch_timeout` (от создания) →
`failed` (`error_code` `qbit_add`) + уведомление автора
- [x] 4.3 Тест: `catched` за таймаутом → `failed` + notify
## 5. Исключение catched из проверок пропажи
- [x] 5.1 Убедиться (и закрепить тестом), что поллинг активных и сверка
рассинхрона (`state-reconciliation`) не трактуют `catched` как пропажу/
orphaned
- [x] 5.2 Тест на гонку discover ↔ шаг добавления: дубль по infohash не заводится
## 6. Веб-UI
- [x] 6.1 Человекочитаемая подпись бейджа для `catched` (`badgeLabel`), чтобы не
показывать сырое `catched`; `catched` в активной группе списка
- [x] 6.2 Заголовок при пустом `display_name` (фолбек) и отсутствие секции
раздачи/живого прогресса для `catched` без ошибок
- [x] 6.3 Самозавершающийся htmx-поллинг карточки/страницы в `catched`: пока
`catched` — опрашивает фрагмент; по переходе в `downloading` показывает
прогресс/имя без перезагрузки и завершает поллинг фазы (правка live-рендера
для не-`downloading` активной фазы)
- [x] 6.4 Тест httpapi/шаблонов: карточка/страница `catched` рендерится;
фрагмент-поллинг отдаётся для `catched` и завершается после перехода
## 7. Проверка
- [x] 7.1 `task test` и `task lint` зелёные
- [x] 7.2 `openspec validate fast-catch-ingest --strict` проходит
- [x] 7.3 Прогон вручную/через verify: приём отвечает быстро (без LLM в пути),
загрузка проходит `catched → downloading`, карточка обновляется без
перезагрузки
- [x] 7.4 Рантбук отката: снять застрявшие `catched` (`UPDATE download SET
state='failed', error_code='qbit_add' WHERE state='catched'`) — зафиксировать
в описании change/задаче