4.9 KiB
🐞 Не сносить раздачу, которой владеет другая активная загрузка
- Тип: fix
- Категория: Ядро продукта
- Зачем: удаление старой закрытой задачи уничтожает файлы живой загрузки с тем же инфохэшем; воспроизведено падающим тестом на ревью bulk-delete-page
- Теги: goal:state-integrity
Delete зовёт torrents/delete с deleteFiles=true по хешам своей записи и не
спрашивает, не владеет ли этим инфохэшем другая активная загрузка. Человек
подтверждает удаление одной записи, а необратимое действие применяется к чужим
живым данным.
Двигает у цели state-integrity строку завершения «ни один известный сегодня
путь не оставляет состояние, которое не объясняется историей переходов»: живая
загрузка B уходит в failed по пропаже источника, и по её записи не видно, что
файлы снесла чужая операция.
Воспроизведение
- Довести задачу A по инфохэшу H до
done. - Добавить тот же релиз заново:
CreateDownloadIfNoActiveищет владельца только среди активных, A терминальна, дедупа нет — заводится активная задача B по тому же H. Файлы на диске у A и B общие, qBittorrent дедуплицирует раздачу по хешу. - Удалить 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.