Files
jellybit/tasks/items/delete-checks-active-infohash-owner.md
T

4.9 KiB
Raw Blame History

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

  • Тип: 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.