Files
jellybit/tasks/items/delete-checks-active-infohash-owner.md
T
av 3bce73fc34 раскладка av-dev повышена с канона 12 до версии 5
- три плагина слились в один `av-dev`: служебные `docs/.docs.json` и
  `tasks/.tasks.json` заменены на `.av-dev.toml` в корне, в гейте переехали пути
  трёх скриптов, вызовы скиллов переименованы по всему репозиторию
- тип задачи `goal` и `ROADMAP.md` упразднены: семь целей закрыты с причинами,
  теги сняты, объявлена стадия `support`
- метка `small`/`medium`/`large` снята из процесса — вместо «Триггеров метки» в
  review.md подраздел «Когда звать глубокое ревью»; следом разобран урожай
  doc-consistency: девять фактов сведены к одному дому
2026-09-02 09:55:28 +03:00

4.9 KiB
Raw Blame History

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

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

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.