# 🐞 Не оставлять задачу в done, когда ссылки сняты, а раздача не снесена - **Тип:** fix - **Категория:** Ядро продукта - **Зачем:** при недоступном qBittorrent задача весь простой соседа показывает done, хотя тайтла в Jellyfin уже нет: сверка падает на первом шаге и до коррекции не доходит - **Теги:** goal:state-integrity `Delete` снимает библиотечные ссылки раньше, чем зовёт `qbt.Delete`. Если сосед недоступен, локальный шаг проходит, внешний падает, и задача остаётся в `done`. Спека рассчитывает, что расхождение живёт «до тика сверки», но `Poll` возвращается на первой же ошибке `qbt.Torrents` и до `reconcileDesync` не доходит вовсе — значит, расхождение живёт весь простой qBittorrent. Двигает у цели `state-integrity` ту же строку завершения — «ни один известный сегодня путь не оставляет состояние, которое не объясняется историей переходов»: `done` держится сколь угодно долго и историей переходов не объясняется. ## Воспроизведение 1. Довести задачу до `done` с существующими библиотечными ссылками. 2. Погасить qBittorrent (в тесте — клиент на несуществующий адрес). 3. Выполнить удаление — поштучно или пачкой. Видно вместо ожидаемого: ссылки сняты, строки `file_link` удалены, раздача цела, место не освобождено, а карточка, список и Telegram показывают `done`. Пока qBittorrent лежит, ни один тик сверки этого не исправит. Замер ревью: отказ приходит за `524.545µs`, то есть пачка успевает пройти по всем выбранным раньше, чем человек поймёт, что сосед недоступен. Отчёт ревью — `openspec/changes/archive/2026-08-10-bulk-delete-page/review/report.md`, находка 4. ## Затрагивает - `Worker.Delete` (`internal/worker/review.go`) — порядок шагов и переход после отказа внешнего шага; - `Worker.Poll` (`internal/worker/worker.go`) — ранний возврат по отказу `qbt.Torrents` перед `reconcileDesync`; - спека `state-reconciliation`, требование «Полное удаление загрузки пользователем» — формулировка «кратковременное рассогласование до тика сверки ожидаемо» сегодня неверна; - граф переходов: `done → target_missing` уже легален, нового состояния не надо. ## Критерии приёмки - После отказа внешнего шага при снятых ссылках задача не остаётся в `done` (оракул: тест `internal/worker` — клиент qBittorrent отказывает, задача оказывается в `target_missing`, а не в `done`). - В `deleted` при этом задача не переходит: место не освобождено (оракул: тот же тест). - Повторное удаление после подъёма соседа идемпотентно дожимает раздачу (оракул: тест — второй вызов при живом клиенте уводит задачу в `deleted`). - Формулировка спеки приведена к тому, что код делает (оракул: `openspec validate --strict` и `task gate`). ## Рамки Порядок шагов менять можно, но снос раздачи раньше снятия ссылок — отдельное решение с ценой: оно убирает необратимый локальный шаг при мёртвом соседе, но меняет поведение при отказе на втором шаге. Ранний возврат `Poll` по отказу `qbt.Torrents` трогать осторожно: на нём стоит вся сверка.