## ADDED Requirements ### Requirement: Полное удаление загрузки пользователем Система SHALL предоставлять пользователю команду **«Удалить»** (delete), доступную из состояний `done`, `orphaned` и `target_missing` во всех транспортах (веб-UI и Telegram, опц. REST). Команда SHALL снимать **обе** стороны загрузки — целевые библиотечные хардлинки И раздачу с файлами в qBittorrent — и переводить задачу в терминальное `deleted`. Из прочих состояний команда доступна SHALL NOT. Снятие цели SHALL идти по механике снятия ссылок последнего батча (как в `Undo`: `superseded` пропускаются как забранные другой загрузкой), но **отдельным путём с выключенным** гардом последней копии — не переиспользуя guarded-`Undo`: в отличие от `Undo`, delete SHALL снимать целевую ссылку, даже если она — последняя копия данных (`nlink <= 1`). Это осознанный выход за инвариант «источник неприкосновенен», поэтому delete SHALL требовать явного **подтверждения** пользователя перед выполнением и SHALL NOT срабатывать по одиночному клику/тапу. Снятие цели SHALL затрагивать только собственные ссылки загрузки строго под `paths.movies`/`series`; файлы источника под `paths.downloads` система сама трогать SHALL NOT — их удаляет qBittorrent по вызову API с `deleteFiles=true`. В отличие от прочих команд, требующих источника, delete синхронный source-preflight выполнять SHALL NOT и под требование «Принудительная проверка источника/цели перед действием» не подпадает: цель delete — снять источник, поэтому его отсутствие трактуется как уже снятая сторона, а не как повод привести состояние сверкой и отказать. Удаление SHALL быть идемпотентным к отсутствующей стороне: в `orphaned` (нет источника) отсутствие раздачи в qBittorrent ошибкой считаться SHALL NOT; в `target_missing` (нет цели) пустой список живых ссылок обрабатывается как «нечего снимать». Если qBittorrent вернул ошибку при удалении присутствующей раздачи, система в `deleted` переходить SHALL NOT (не заявляем освобождение места, которого не произошло), SHALL сообщить пользователю причину отказа (это не `ErrConflict`, а ошибка внешнего сервиса — транслируется как таковая), и повторный delete идемпотентно дожимает удаление, опираясь на оставшийся `done` либо приведённый сверкой к реальности `target_missing` (кратковременное рассогласование до тика сверки ожидаемо). Инициатора перехода в `deleted` система SHALL отличать от фоновой сверки: пользовательское удаление SHALL помечаться `error_code = "user_delete"` (сверка кладёт `"reconcile"`), человекочитаемую причину — в `error_msg` и лог перехода. Новый статус для этого система вводить SHALL NOT — переиспользуется существующее терминальное `deleted` (сверка его не переоценивает, см. требование о `deleted`). #### Scenario: Удаление из done снимает обе стороны и освобождает место - **GIVEN** задача в `done`: раздача присутствует в qBittorrent, её библиотечные хардлинки существуют - **WHEN** пользователь подтверждает «Удалить» - **THEN** библиотечные ссылки последнего батча снимаются - **AND** раздача с файлами удаляется из qBittorrent (`deleteFiles=true`) - **AND** задача переходит в `deleted` с `error_code = "user_delete"` #### Scenario: Удаление из orphaned снимает последнюю копию осознанно - **GIVEN** задача в `orphaned`: источник пропал, библиотечный хардлинк остался единственной копией данных (`nlink <= 1`) - **WHEN** пользователь подтверждает «Удалить» - **THEN** библиотечная ссылка снимается несмотря на то, что она последняя копия (гард последней копии выключен, в отличие от `Undo`) - **AND** отсутствие раздачи в qBittorrent ошибкой не считается - **AND** задача переходит в `deleted` #### Scenario: Удаление из target_missing сносит остаточную раздачу - **GIVEN** задача в `target_missing`: источник присутствует, цель уже удалена вручную - **WHEN** пользователь подтверждает «Удалить» - **THEN** снятие цели идемпотентно (живых ссылок нет) - **AND** раздача с файлами удаляется из qBittorrent - **AND** задача переходит в `deleted` #### Scenario: Удаление требует подтверждения - **GIVEN** задача в `done` - **WHEN** пользователь инициирует «Удалить», но не подтверждает действие - **THEN** ни ссылки, ни раздача не удаляются, состояние остаётся `done` #### Scenario: Удаление недоступно из прочих состояний - **GIVEN** задача в `review` (или ином состоянии вне `done`/`orphaned`/ `target_missing`) - **WHEN** приходит команда «Удалить» - **THEN** команда отклоняется с конфликтом, состояние не меняется #### Scenario: Ошибка qBittorrent не метит deleted ложно - **GIVEN** задача в `done`, раздача присутствует, но qBittorrent вернул ошибку на удаление - **WHEN** пользователь подтверждает «Удалить» - **THEN** задача в `deleted` не переходит (место не освобождено) - **AND** пользователю сообщается причина отказа (ошибка qBittorrent, не тихий успех) - **AND** повторный delete идемпотентно дожимает удаление