Files
avandClaude Opus 4.8 2a5a65f2d5 Приём: пропажа источника у активной загрузки → 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>
2026-07-08 18:05:04 +03:00

48 lines
3.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.
# Пропажа источника у активной загрузки (MAJOR-3)
## Why
Если раздача исчезает из qBittorrent (пользователь или другой клиент удалил её),
пока задача в `downloading`, задача становится **вечным зомби**. Поллинг активных
загрузок промахивается по `torrentFor`, пишет `Warn "active download not found in
qbittorrent"` и делает `continue` — и так каждый тик, бесконечно. Состояние не
меняется, уведомления нет, телеметрии нет, `checkTimeouts` требует торрент (значит
таймауты-предохранители не срабатывают). Единственный выход — ручной Cancel/Defer.
Дыра: сверка рассинхрона (`state-reconciliation`) покрывает пропажу источника
только для уже разложенных состояний (`done`/`target_missing`/`orphaned`), а
восстановление (`reconcileRecovery`) — только `failed`/`stuck`. Для активного
`downloading` пропажей источника не владеет НИКТО. Сравни: та же пропажа в
`completed`/`recognizing`/`review` деградирует штатно (там источник уже не нужен
или его отсутствие ведёт через матрицу).
## What Changes
- Поллинг активных загрузок SHALL применять **дебаунс пропажи источника** (тот же
`SourceMissCount` / `[worker].source_missing_threshold`, что и сверка
рассинхрона): промах `torrentFor` наращивает счётчик, любое появление раздачи
его сбрасывает.
- После порога подряд идущих промахов задача SHALL переходить `downloading →
failed` с новым отдельным `error_code` `source_gone` и уведомлять автора
(`EventFailed`).
- `source_gone` **восстановлению сверкой не подлежит** (не входит в набор
`reconcileRecovery`): удаление источника из qBittorrent — намеренное действие,
молча воскрешать задачу нельзя. Задача остаётся штатно **retriable**: `Retry`
заново отдаёт источник (у нас сохранены magnet/`.torrent`-байты).
Ребро графа `downloading → failed` уже объявлено — нового ребра не требуется.
Меняется только набор `error_code` и поведение поллинга активных загрузок.
## Capabilities
- `download-tracking` — ADDED: «Пропажа источника у активной загрузки».
## Impact
- Код: `internal/worker/worker.go` (поллинг активных загрузок, новый
`errCodeSourceGone`), тесты воркера.
- Спека: `download-tracking` (новое требование). Диаграмма `docs/specs/workflow.md`
(миррор FSM) — добавить ребро `downloading → failed (source_gone)`.
- Миграции БД нет: `source_miss_count` уже существует, новых полей не вводим.
- Конфиг без изменений: переиспользуем `source_missing_threshold`.