# 🐞 Не сносить раздачу, которой владеет другая активная загрузка

- **Тип:** fix
- **Категория:** Ядро продукта
- **Зачем:** удаление старой закрытой задачи уничтожает файлы живой загрузки с тем же инфохэшем; воспроизведено падающим тестом на ревью bulk-delete-page
- **Теги:** goal:state-integrity

`Delete` зовёт `torrents/delete` с `deleteFiles=true` по хешам своей записи и не
спрашивает, не владеет ли этим инфохэшем другая **активная** загрузка. Человек
подтверждает удаление одной записи, а необратимое действие применяется к чужим
живым данным.

Двигает у цели `state-integrity` строку завершения «ни один известный сегодня
путь не оставляет состояние, которое не объясняется историей переходов»: живая
загрузка B уходит в `failed` по пропаже источника, и по её записи не видно, что
файлы снесла чужая операция.

## Воспроизведение

1. Довести задачу A по инфохэшу H до `done`.
2. Добавить тот же релиз заново: `CreateDownloadIfNoActive` ищет владельца
   только среди активных, A терминальна, дедупа нет — заводится активная задача
   B по тому же H. Файлы на диске у A и B общие, qBittorrent дедуплицирует
   раздачу по хешу.
3. Удалить A — со страницы загрузки, из Telegram или со страницы группового
   удаления.

Видно вместо ожидаемого: раздача снесена с файлами, B продолжает считать себя
качающейся и уходит в `failed` после дебаунса. Если B ещё не дошла до раскладки,
копии не остаётся нигде.

Прогнанные оракулы ревью (отчёт —
`openspec/changes/archive/2026-08-10-bulk-delete-page/review/report.md`, находка
2): `TestAdversaryDeletablePageOffersHashOwnedByActiveDownload`,
`TestAdversaryDeleteWipesSourceOfAnotherActiveDownload`.

## Затрагивает

- `Worker.Delete` (`internal/worker/review.go`) — вызов `qbt.Delete`;
- предикат `store.FindActiveByInfohash` — существующий, новый заводить не надо;
- спека `state-reconciliation`, требование «Полное удаление загрузки
  пользователем» — сегодня оно про такую проверку молчит;
- строка выбора и подтверждения на странице группового удаления, если решено
  показывать признак чужого владения;
- qBittorrent `torrents/delete` с `deleteFiles=true` — необратимая операция.

## Критерии приёмки

- Удаление задачи, чьим инфохэшем владеет активная загрузка, отклоняется
  конфликтом и раздачу не сносит (оракул: тест `internal/worker` — задача A в
  `done` и активная B по тому же хешу, `Delete(A)` возвращает `ErrConflict`, у
  подставного клиента qBittorrent ноль вызовов `Delete`).
- Отказ виден человеку причиной, а не общим «внутренняя ошибка» (оракул: тест
  `internal/httpapi` — строка отчёта называет, что инфохэшем владеет активная
  задача).
- Удаление задачи, чей инфохэш никем больше не занят, работает как прежде
  (оракул: существующие тесты `Delete` остаются зелёными).
- Поведение записано дельта-спекой `state-reconciliation` (оракул:
  `openspec validate --strict` и `task gate`).

## Рамки

Необратимое: снос раздачи с файлами. Проверка ставится в ядре, а не в
транспорте — допуск обязан держаться без транспорта. Правило дедупа при приёме
(«владелец ищется среди активных») эта задача не меняет: связывание v1/v2 и
доверие к паре `xt` разбирает `infohash-identity-integrity`.
