## ADDED Requirements ### Requirement: Приём источника и заведение загрузки Приём SHALL быть единым use-case, общим для всех транспортов (HTTP, Telegram, CLI): по источнику (Ф1 — magnet) и текстовому контексту система SHALL извлечь инфохэши, дедуплицировать по активной задаче, при отсутствии дубля завести загрузку (`download` в состоянии `downloading` + записи `download_infohash`) и отдать источник в qBittorrent (категория `qbittorrent.category`, savepath). Если добавление в qBittorrent не удалось, система SHALL перевести уже заведённую загрузку в `failed` (`error_code` `qbit_add`) и уведомить автора. Заведение загрузки и запись её хешей SHALL выполняться атомарно (см. «Атомарность возврата загрузки в активное состояние»). #### Scenario: Успешный приём magnet - **GIVEN** валидная magnet-ссылка и контекст - **WHEN** вызывается приём - **THEN** создаётся `download` в `downloading` с записями `download_infohash` - **AND** источник отдан в qBittorrent с нашей категорией #### Scenario: Падение добавления в qBittorrent - **GIVEN** заведённую загрузку не удалось добавить в qBittorrent - **WHEN** обрабатывается ошибка добавления - **THEN** загрузка переходит в `failed` с `error_code` `qbit_add` - **AND** автор загрузки уведомляется ### Requirement: Множество инфохэшей загрузки Загрузка SHALL иметь одну или более записей инфохэша (`download_infohash`: `infohash` lowercase hex, `kind` ∈ `v1`|`v2`). При приёме magnet-ссылки SHALL записываться ВСЕ известные из неё хеши — гибридный magnet несёт и btih (v1), и btmh (v2); `kind` определяется по длине hex (40 — `v1`, 64 — `v2`). Когда qBittorrent сообщает для раздачи оба хеша (`infohash_v1`, `infohash_v2`), система SHALL дописывать недостающие записи загрузке; усечённый хеш v2-only раздачи (поле `hash` qBittorrent, 40 hex от v2) записываться SHALL NOT. Сопоставление раздачи qBittorrent с загрузкой (поллинг, discover) SHALL выполняться по любому из известных хешей. Один и тот же infohash MAY принадлежать нескольким загрузкам во времени (повторный приём после терминального состояния), но активной из них MUST быть не более одной. #### Scenario: Гибридный торрент раскрывает оба хеша - **GIVEN** загрузка принята по magnet с v1-хешем - **WHEN** qBittorrent отдаёт раздачу с заполненными `infohash_v1` и `infohash_v2` - **THEN** у загрузки появляются обе записи (`kind` = `v1` и `v2`) #### Scenario: Сопоставление по v2-хешу - **GIVEN** загрузка с записями v1- и v2-хешей - **WHEN** поллинг находит раздачу, совпавшую только по v2-хешу - **THEN** раздача сопоставляется с этой загрузкой ### Requirement: Дедупликация приёма по любому из хешей При приёме система SHALL искать **активную** (нетерминальную) загрузку по любому из известных хешей и, найдя, SHALL возвращать её вместо создания новой. Проверка активности и вставка новой загрузки с её хешами SHALL выполняться атомарно (в одной write-транзакции), поддерживая инвариант «не более одной активной загрузки на infohash». Отдельного снимаемого/ восстанавливаемого ключа идемпотентности в схеме быть SHALL NOT — активность выводится только из `state`. #### Scenario: Повторный приём при активной загрузке - **GIVEN** активная загрузка с infohash `h` - **WHEN** принимается magnet с тем же `h` - **THEN** новая загрузка не создаётся, возвращается существующая #### Scenario: Повторный приём после завершения - **GIVEN** загрузка с infohash `h` в терминальном состоянии (`done`) - **WHEN** принимается magnet с тем же `h` - **THEN** создаётся новая загрузка со своим ULID и записью `h` ### Requirement: Атомарность возврата загрузки в активное состояние Система SHALL атомарно (в одной write-транзакции) проверять на каждом пути, возвращающем загрузку из терминального состояния в активное (ручной retry, воскрешение фоновой сверкой, повторная раскладка/relink) или создающем её (приём, adopt чужой раздачи), что никакая другая активная загрузка не владеет любым из хешей этой, и при владении SHALL отказывать в переходе, сохраняя инвариант «не более одной активной загрузки на infohash». Отказ SHALL происходить до побочных эффектов во внешних системах (повторного добавления торрента в qBittorrent). Та же проверка SHALL применяться к дозаписи хешей загрузке (раскрытие гибридного торрента): хеш, которым владеет другая активная загрузка, дописан быть SHALL NOT. Прямой перевод терминальной загрузки в активное состояние в обход этой проверки SHALL отклоняться хранилищем (механический бэкстоп вместо удалённого unique-индекса). #### Scenario: Retry при занятом хеше - **GIVEN** загрузка #1 в `failed` с хешем `h`, и другая активная загрузка #2 с тем же `h` - **WHEN** пользователь вызывает retry для #1 - **THEN** переход отклоняется с пояснением, #1 остаётся в `failed` - **AND** активной по `h` остаётся #2