Отмена задачи (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>
57 lines
4.4 KiB
Markdown
57 lines
4.4 KiB
Markdown
## Why
|
|
|
|
На шаге добавления пойманной загрузки (`catched`) worker выводит имя (LLM,
|
|
секунды) и вызывает `qbt.Add` **вне** блокировки переходов. Если в это окно
|
|
пользователь отменяет задачу (`catched → cancelled`), запись перехода
|
|
`PromoteCatched` корректно пропускается (гард `state='catched'`), НО источник
|
|
уже добавлен в qBittorrent под нашей категорией. Такой торрент качается/сидирует
|
|
вечно, ест диск, а видимой задачи-владельца нет: усыновить назад его нельзя —
|
|
`adopt` проверяет `ExistsByInfohash` (любое состояние), а хеши уже принадлежат
|
|
отменённой задаче. Спека покрывает переход состояния, но не этот побочный эффект
|
|
(находка ревью F3/NIT-13).
|
|
|
|
## What Changes
|
|
|
|
- **Re-read состояния прямо перед `qbt.Add`** (под блокировкой переходов, после
|
|
медленного вывода имени): если задача уже не в `catched` (отменена) — источник
|
|
в qBittorrent НЕ добавляется вовсе. Сужает окно гонки до промежутка между
|
|
re-read и записью перехода.
|
|
- **Scoped cleanup**: если отмена случилась в оставшемся окне (уже ПОСЛЕ
|
|
успешного `add`, но до записи перехода), worker удаляет только что добавленный
|
|
торрент из qBittorrent **вместе с данными** — уборка собственного мусора.
|
|
- **Гарантия «удаляем только своё»**: удаление-с-данными допустимо ТОЛЬКО для
|
|
торрента, который worker создал именно этим `add`. Признак — подтверждённое
|
|
**отсутствие** infohash в qBittorrent непосредственно перед `add`. Если
|
|
infohash уже присутствовал до нашего `add` (внешний клиент раздаёт тот же
|
|
торрент), worker источник не добавляет и чужие данные не трогает.
|
|
- **WARN-логи** на этом пути (торрент оставлен после отмены → удаляем; факт
|
|
удаления), с корреляцией по `download_id`, без секретов.
|
|
|
|
## Capabilities
|
|
|
|
### New Capabilities
|
|
|
|
Нет.
|
|
|
|
### Modified Capabilities
|
|
|
|
- `download-tracking`: требование «Добавление пойманной загрузки в qBittorrent» —
|
|
добавляется re-read состояния перед `add`, подтверждение отсутствия infohash
|
|
перед `add` как признак «своего» торрента и уборка добавленного торрента при
|
|
отмене в окне после `add`. Сценарий «Отмена во время добавления» уточняется и
|
|
дополняется сценариями уборки и негативного инварианта.
|
|
|
|
## Impact
|
|
|
|
- **Спеки:** дельта `download-tracking` (одно MODIFIED-требование + сценарии).
|
|
- **Код:** `internal/worker/worker.go` — `processCatched` (re-read state и
|
|
проверка присутствия перед `Add`; уборка добавленного при промахе
|
|
`PromoteCatched`). Новых методов `qbt`/`store` не требуется (переиспользуем
|
|
`Torrents`, `Delete`).
|
|
- **Тесты:** `internal/worker/catched_test.go` — обновление сценария отмены во
|
|
время namer (Add не вызывается) + новые: уборка после Add, негативный инвариант
|
|
(пред-существующий торрент не удаляется с данными).
|
|
- **Миграции БД:** нет.
|
|
- **Инвариант «источник неприкосновенен»:** обоснование удаления-с-данными и
|
|
негативная гарантия — в `design.md`.
|