## Why При повторном приёме торрента, у которого уже есть запись в `target_missing` («разложено, но файлов в библиотеке нет»), рождается загрузка-близнец: дедуп приёма ищет только **активную** задачу по инфохэшу, а `target_missing` терминально — активной нет, заводится новая загрузка, воркер усыновляет присутствующую в qBittorrent раздачу и раскладывает её. В итоге на один торрент две записи (`done` + `target_missing`), причём у осиротевшей единственное действие «Привязать заново» упрётся в уже занятый целевой путь. Пользователю нечем аккуратно закрыть лишнюю запись, не снося при этом раздачу. ## What Changes - **Предотвращение дубля на приёме.** Повторный приём инфохэша, которым владеет запись в `target_missing` или `orphaned`, SHALL привязываться к этой записи (возврат существующей, `Deduplicated`), а не заводить новую загрузку. Критерий дедупа расширяется с «активной» до «активной **или** удерживающей источник ради незакрытого намерения» (`target_missing`/`orphaned`). `done` из дедупа сознательно остаётся размножаемым (повторный приём завершённого = осознанное «хочу заново»). Приём остаётся быстрым: qBittorrent не трогаем, авто-relink не запускаем — пользователю сообщается, что запись существует и её нужно привязать заново. - **Команда «Закрыть» — универсальный стоп-кран.** Добавляется ручная команда, доступная из **любого** состояния (кроме `deleted`) во всех транспортах, переводящая запись в терминальное `cancelled` (с `error_code`-дискриминатором) и убирающая её из активного списка. Команда **только меняет статус**: файлы под `paths.*` не трогает (в т.ч. из `done`/`orphaned` библиотечные хардлинки сознательно остаются на месте) и раздачу в qBittorrent не снимает (в отличие от «Удалить»). Размещается в отдельной danger zone внизу страницы. Так пользователь закрывает лишнего близнеца, а заодно получает страховку для любой зависшей/спорной загрузки. Явно вне scope: авто-схлопывание дублей в фоновой сверке (выбран ручной путь); изменение логики усыновления в воркере; введение нового статуса (переиспользуем `cancelled`, как Delete переиспользует `deleted`); удаление/создание каких-либо файлов или раздач командой «Закрыть». ## Capabilities ### New Capabilities ### Modified Capabilities - `ingest`: критерий дедупликации приёма расширяется — блокирующими повторный приём становятся не только активные, но и `target_missing`/`orphaned` записи (attach вместо создания новой); повторный приём завершённой (`done`) остаётся созданием новой. Модифицируются требования «Дедупликация приёма по любому из хешей» и «Приём источника и заведение загрузки» (терминология «блокирующей» задачи). Требование «Приём из .torrent-файла» текст НЕ правит: оно уже явно делегирует критерий модифицированному требованию через inline-ссылку — оба дедуп-упоминания там наследуют расширенный критерий без риска дрейфа. - `state-reconciliation`: добавляется пользовательская команда «Закрыть» (dismiss) из любого состояния (кроме `deleted`) в терминальное `cancelled`, ничего не делающая с файлами и раздачей; фиксируется её отличие от «Удалить» и новые рёбра перехода `<любое> → cancelled` (в т.ч. из терминальных `done`/`failed`/`reverted`/`target_missing`/`orphaned`). ## Impact - Код: `internal/ingest/ingest.go` (`Ingest`/`attached` — ветка attach для desync-записей, флаг «нужен relink»), `internal/store/download.go` (критерий поиска дедупа: reingest-blocking states = active ∪ `{target_missing, orphaned}`; новое ребро `allowedTransitions` `target_missing → cancelled`; `error_code`-дискриминатор dismiss), `internal/worker/review.go` (обработчик команды «Закрыть» рядом с Delete/Undo/Relink — только setState, без файлов и qBittorrent), веб-UI (danger zone внизу страницы с кнопкой «Закрыть»), `internal/tgbot`. - Данные: новых таблиц/столбцов нет; терминальный набор не меняется (dismiss → существующее `cancelled`). Обновляется граф переходов и, при необходимости, описание статусов в `docs/specs/database.md`. - Инварианты безопасности данных: «Закрыть» источник неприкосновенен — qBittorrent не вызывается, файлы под `paths.downloads`/`movies`/`series` не трогаются.