Приём: пропажа источника у активной загрузки → failed(source_gone) (MAJOR-3)

Раздача активной (downloading) загрузки, исчезнувшая из qBittorrent (удалил
пользователь/другой клиент), делала задачу вечным зомби: поллинг промахивался
по torrentFor, писал Warn и continue каждый тик — состояние не менялось,
уведомления и телеметрии не было, checkTimeouts без торрента не срабатывал.
Пропажей источника у downloading не владел никто (сверка рассинхрона покрывает
только done/target_missing/orphaned, восстановление — failed/stuck).

Активный цикл Poll теперь применяет тот же дебаунс пропажи источника, что и
сверка рассинхрона (source_miss_count / source_missing_threshold): после порога
подряд идущих промахов задача уходит downloading → failed с distinct error_code
source_gone и уведомлением. До порога транзиентная недоступность qBit
(рестарт демона) задачу не роняет. source_gone восстановлению сверкой не
подлежит (удаление намеренно), но штатно retriable — Retry заново отдаёт
сохранённый источник; Retry сбрасывает source_miss_count, чтобы вернувшаяся
задача получила полное грейс-окно, а не упала снова на ближайшем тике.

Ребро downloading → failed уже было в графе, миграций/полей БД нет. Спека
download-tracking дополнена требованием, диаграмма workflow.md — ребром.
Change downloading-source-gone заархивирован.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-08 18:05:04 +03:00
co-authored by Claude Opus 4.8
parent bfd469bd43
commit 2a5a65f2d5
13 changed files with 524 additions and 21 deletions
@@ -0,0 +1,38 @@
# Tasks — Пропажа источника у активной загрузки
## 1. Код воркера
- [x] 1.1 Ввести `errCodeSourceGone = "source_gone"` в блок кодов ошибок
(`internal/worker/worker.go`) с комментарием: distinct-код, восстановлению
сверкой не подлежит, retriable вручную.
- [x] 1.2 Перестроить активный цикл в `Poll`: при промахе `torrentFor` прогонять
`debounceSource`; при исчерпании порога — `transition downloading → failed`
(`source_gone`); до порога — `continue`; при наличии источника — сброс счётчика
и обычная сверка (`captureInfohashes`/`captureSourceAddedAt`/`reconcile`).
- [x] 1.3 Убедиться, что `debounceSource` вызывается и при наличии источника
(сброс накопленных промахов).
- [x] 1.4 `Retry` сбрасывает `source_miss_count` в 0 (рядом с `SetRetriedAt`),
чтобы retried `source_gone`-задача получила полное грейс-окно, а не падала
сразу (см. design «Сброс source_miss_count при Retry»).
## 2. Тесты
- [x] 2.1 Промах < порога → задача остаётся `downloading`.
- [x] 2.2 Промах ≥ порога → `downloading → failed`/`source_gone` + `EventFailed`.
- [x] 2.3 Возврат раздачи до порога → счётчик сброшен, задача ведётся обычной
сверкой.
- [x] 2.4 `source_gone` не воскрешается `reconcileRecovery` (источник вернулся —
задача остаётся `failed`).
- [x] 2.5 `Retry` задачи `source_gone` сбрасывает `source_miss_count`: после
retry с ещё-не-видимой раздачей задача остаётся `downloading` полный порог
тиков (грейс-окно не съедено).
## 3. Документация
- [x] 3.1 `docs/specs/workflow.md`: добавить ребро `downloading → failed`
(`source_gone`, пропажа источника) в диаграмму и список переходов.
## 4. Верификация
- [x] 4.1 `task test` / `task lint` зелёные.
- [x] 4.2 `openspec validate downloading-source-gone --strict`.