закрыта задача bulk-delete-page, заведены три задачи из урожая ревью
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
# 🐞 Не сносить раздачу, которой владеет другая активная загрузка
|
||||
|
||||
- **Тип:** 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`.
|
||||
Reference in New Issue
Block a user