Жизненный цикл: уборка торрента при отмене во время добавления (F3/NIT-13)

Отмена задачи (catched→cancelled) в окно, пока worker вне блокировки выводит
имя (LLM) и делает qbt.Add, оставляла добавленный торрент в qBittorrent без
задачи-владельца: PromoteCatched корректно пропускал переход, но источник уже
качался/сидировал вечно, а усыновить его назад нельзя (хеши принадлежат
отменённой задаче). Спека покрывала переход состояния, но не побочный эффект.

Комбинированная защита в processCatched:
- re-read состояния под w.mu прямо перед qbt.Add — при отмене источник не
  добавляется вовсе (сужает окно гонки);
- свежий листинг перед add подтверждает отсутствие infohash — признак «своего»
  торрента; при сбое листинга/присутствии add не делаем (усыновит следующий тик);
- при отмене в окне после add (промах PromoteCatched, подтверждённый re-read'ом
  state != catched) — уборка добавленного нами торрента qbt.Delete(_, true);
- WARN/ERROR-логи по этому пути с корреляцией по download_id, без секретов.

Гарантия «удаляем только своё»: удаление-с-данными достижимо ТОЛЬКО после
подтверждённого отсутствия infohash перед add, поэтому пред-существующий/чужой
торрент с тем же хешем никогда не сносится (негативный инвариант). Обоснование
по инварианту «источник неприкосновенен» — в design.md изменения.

Дельта — download-tracking (требование «Добавление пойманной загрузки в
qBittorrent»): re-read перед add, подтверждение отсутствия, уборка при отмене,
негативный сценарий. Тесты покрывают все ветки (skip-before-add, cleanup после
add, пред-существующий не удаляется, сбой БД не удаляет, сбой листинга не
добавляет).

Change archived: 2026-07-17-cancel-during-add-cleanup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-17 22:10:21 +03:00
co-authored by Claude Opus 4.8
parent 0354a8c96b
commit 3a00fde058
11 changed files with 792 additions and 30 deletions
@@ -0,0 +1,44 @@
## 1. Код
- [x] 1.1 В `processCatched` (`internal/worker/worker.go`), на обычном пути
добавления, ПОСЛЕ вывода имени и НЕПОСРЕДСТВЕННО перед `qbt.Add`: под `w.mu`
перечитать запись и, если `state != catched`, `add` не делать и загрузку
пропустить (D1)
- [x] 1.2 Там же (вне `w.mu`) добавить свежий листинг присутствия любого из
infohash загрузки в qBittorrent перед `add` (переиспользовать `qbt.Torrents`);
при присутствии — `add` не делать, пропустить (усыновит следующий тик); при
СБОЕ листинга — `add` не делать, `WARN`, повтор на следующем тике (D2, Б1)
- [x] 1.3 При неуспехе `PromoteCatched` после успешного `add` — под `w.mu`
перечитать состояние: только если `state != catched` (подтверждённая отмена) —
удалить добавленный торрент с данными `qbt.Delete(ctx, HashList, true)`; `WARN`
об оставленном/удалённом торренте; `ERROR` при сбое удаления. Если `state ==
catched` (транзиентный сбой БД) или re-read упал — НЕ удалять, `WARN`
«promote failed, will retry» (D3, Б2). Решение по состоянию, не по тексту
ошибки (см. errors.md)
- [x] 1.4 Убедиться, что путь уборки достижим только после подтверждённого
отсутствия перед `add` (негативный инвариант «удаляем только своё»)
## 2. Тесты
- [x] 2.1 Обновить `TestProcessCatchedCancelledDuringAddSkipsPromote`: отмена во
время namer → `qbt.Add` НЕ вызывается (re-read перед add), состояние остаётся
`cancelled` (было: Add вызывался)
- [x] 2.2 Новый тест: отмена в окне ПОСЛЕ `add` (hook на `Add`, ставящий
`cancelled`) → `qbt.Delete` вызван с `deleteFiles=true` по infohash загрузки,
состояние `cancelled`
- [x] 2.3 Новый тест (негативный инвариант): infohash появился в qBittorrent во
время namer (hook на namer) → перед `add` листинг видит присутствие → `qbt.Add`
НЕ вызывается, `qbt.Delete` НЕ вызывается (чужие данные не трогаем)
- [x] 2.4 Новый тест (Б2): сбой `PromoteCatched` при `state == catched`
(транзиентная ошибка БД, без отмены) → торрент НЕ удаляется (`qbt.Delete` не
вызван)
- [x] 2.6 Новый тест (Б1, safety-critical): свежий листинг перед `add` упал
(второй вызов `Torrents`) → `Add`/`Delete` НЕ вызваны, остаётся `catched`
- [x] 2.5 Регрессия: штатный успех (нет отмены) по-прежнему добавляет и
промоутит (`TestProcessCatchedAddsToQbit`); усыновление присутствующего и сбой
листинга тика (`TestProcessCatchedListErrorKeepsCatched`) — без изменений
## 3. Спека
- [x] 3.1 MODIFIED-требование «Добавление пойманной загрузки в qBittorrent» в
`download-tracking`; `openspec validate cancel-during-add-cleanup --strict`