## ADDED Requirements ### Requirement: Ручное закрытие загрузки (стоп-кран) Система SHALL предоставлять пользователю команду **«Закрыть»** (dismiss), доступную из **любого** состояния, кроме `deleted`, во всех транспортах (веб-UI и Telegram, опц. REST). Команда SHALL переводить загрузку в терминальное `cancelled`, убирая её из активного списка/внимания, и SHALL служить универсальным стоп-краном для любой зависшей или спорной загрузки (в т.ч. лишнего дубля-близнеца в `target_missing`, чьи файлы уже разложены другой загрузкой). Для загрузки в `deleted` команда доступна SHALL NOT (состояние строго терминально); в `cancelled` команда SHALL быть идемпотентным no-op. Команда SHALL **только менять статус** и SHALL NOT производить никаких действий с файлами или раздачей: система SHALL NOT вызывать qBittorrent (раздача не снимается, продолжает раздаваться) и SHALL NOT удалять либо создавать хардлинки под `paths.movies`/`series` — в т.ч. из `done`/`orphaned` существующие библиотечные ссылки сознательно остаются на месте. Синхронный source-preflight «Закрыть» выполнять SHALL NOT (источник в действии не участвует). Переход SHALL помечаться `error_code = "user_dismiss"` (человекочитаемая причина — в `error_msg` и логе перехода), отличающим стоп-кран от отклонения на ревью и от удаления. Новый статус для этого система вводить SHALL NOT — переиспользуется существующее терминальное `cancelled` (сверка его не переоценивает). Из `cancelled` пользователю остаётся доступной перепривязка (relink), если он передумает. В интерфейсе команда SHALL размещаться в отдельной «danger zone» (напр. внизу страницы загрузки), обособленно от штатных действий. #### Scenario: Закрытие записи без цели не трогает раздачу - **GIVEN** загрузка в `target_missing`: источник присутствует в qBittorrent, целевых хардлинков нет (напр. её файлы разложены другой загрузкой) - **WHEN** пользователь даёт команду «Закрыть» - **THEN** запись переходит в `cancelled` с `error_code = "user_dismiss"` - **AND** раздача с файлами в qBittorrent не удаляется - **AND** запись пропадает из активного списка #### Scenario: Закрытие done оставляет библиотечные файлы на месте - **GIVEN** загрузка в `done` с существующими библиотечными хардлинками - **WHEN** пользователь даёт команду «Закрыть» - **THEN** запись переходит в `cancelled` с `error_code = "user_dismiss"` - **AND** библиотечные хардлинки не удаляются - **AND** раздача в qBittorrent не снимается #### Scenario: Закрытие зависшей загрузки - **GIVEN** загрузка в `stuck` (или `failed`/`deferred`) - **WHEN** пользователь даёт команду «Закрыть» - **THEN** запись переходит в `cancelled` - **AND** источник в qBittorrent не трогается #### Scenario: «Закрыть» недоступна для deleted - **GIVEN** загрузка в `deleted` - **WHEN** пользователь пытается вызвать «Закрыть» - **THEN** команда недоступна, состояние остаётся `deleted` #### Scenario: Закрытую запись можно привязать заново - **GIVEN** запись, закрытая командой «Закрыть» в `cancelled` - **WHEN** пользователь даёт команду «Привязать заново» - **THEN** запись уходит на перераспознавание с ручным подтверждением (как relink из `cancelled`)