## MODIFIED Requirements

### Requirement: Приём источника из .torrent-файла

Приём SHALL принимать источник в виде **байтов `.torrent`-файла** (наряду с
magnet-ссылкой) — тем же быстрым use-case, общим для транспортов. Получив
непустые байты торрента, система SHALL разобрать их локально (без сети),
извлечь инфохэш(и) и завести загрузку с `source_type = torrent`, после чего
сразу вернуть ответ транспорту (синхронный путь к qBittorrent не обращается —
добавление делает воркер, см. `download-tracking`).

Инфохэши система SHALL извлекать такими, какими их сообщает qBittorrent, чтобы
сопоставление раздач и дедупликация работали: v1-хеш (для v1/гибридного файла)
SHALL вычисляться как SHA1 **исходных** байтов info-словаря (без переэнкода);
v2-хеш (для v2/гибридного файла, BEP52) SHALL извлекаться как 64-hex `infohash_v2`.
Для чистого v2-only файла система SHALL записывать v2-хеш (v1 у него нет).
Извлечение всех известных хешей и дозапись недостающих подчиняются требованию
«Множество инфохэшей загрузки».

Дедупликацию по активной задаче, атомарное заведение (`download` в состоянии
`catched` + записи `download_infohash`) и инвариант «не более одной активной
загрузки на infohash» torrent-приём SHALL проходить тем же атомарным путём, что
и magnet (см. «Приём источника и заведение загрузки», «Дедупликация приёма по
любому из хешей», «Атомарность возврата загрузки в активное состояние»).

Байты `.torrent` система SHALL сохранять персистентно, привязанными к загрузке,
чтобы воркер мог добавить источник в qBittorrent именно файлом (не по magnet):
раздачи закрытых трекеров и торренты без DHT по magnet-хешу метаданные не
получат. Сохранение байтов SHALL выполняться в той же write-транзакции, что и
заведение загрузки; при дедупликации (новая загрузка не создана) байты в общем
случае сохраняться SHALL NOT.

**Исключение — апгрейд пойманной magnet-задачи до torrent.** Если входящий
источник — байты `.torrent`, а дедуп попал на активную загрузку с
`source_type = magnet`, ещё НЕ отданную в qBittorrent (состояние `catched`),
система SHALL в одной write-транзакции сохранить байты `.torrent`,
привязав их к этой загрузке, и сменить её `source_type` на `torrent`. Тем
самым воркер добавит раздачу файлом, а не magnet-хешем (иначе на закрытом
трекере без DHT метаданные не докачаются, а magnet застрянет в metaDL →
failed) — **при условии, что апгрейд лёг до того, как воркер подготовил запрос
на добавление**. Апгрейд, попавший в узкое окно вывода отображаемого имени,
воркер уже не подхватит и добавит magnet: это известное ограничение, названное в
`download-tracking` «Добавление пойманной загрузки в qBittorrent» вместе с
маршрутом восстановления. Апгрейд SHALL применяться ТОЛЬКО пока загрузка в
`catched` (воркер источник ещё не добавил); для уже добавленной (`downloading`
и далее) загрузки смена `source_type` при дедупе выполняться SHALL NOT — её
судьба решается путями retry/сверки, а не приёмом. Апгрейд SHALL быть
best-effort: его неуспех приём не прерывает.

Из полей `.torrent` система SHALL синтезировать контекст распознавания (имя
раздачи, суммарный размер, сигнал по дереву файлов, домен трекера, комментарий)
и **дополнять** им контекст транспорта — тем же правилом слияния, что и синтез
из полей magnet (пользовательский текст первым; при пустом тексте — только
синтез). Обогащённый контекст система SHALL сохранять в `download.Context`.
Синтез SHALL выполняться без сетевых запросов.

`source_ref` у torrent-загрузки SHALL быть человекочитаемым референсом (имя
раздачи или файла), а НЕ адресом добавления: добавление в qBittorrent идёт
байтами, и трактовать `source_ref` как magnet/URL для добавления система SHALL
NOT.

#### Scenario: Быстрый приём .torrent-файла

- **GIVEN** валидные байты `.torrent`-файла и (опц.) текст контекста
- **WHEN** вызывается приём
- **THEN** из файла извлекаются инфохэши и создаётся `download` в состоянии
  `catched` (`source_type = torrent`) с записями `download_infohash`
- **AND** байты файла сохраняются привязанными к загрузке
- **AND** ответ транспорту отдан без обращения к qBittorrent

#### Scenario: Инфохэш из исходных байтов info

- **WHEN** система разбирает v1/гибридный `.torrent`-файл
- **THEN** инфохэш v1 вычисляется как SHA1 исходных байтов info-словаря
- **AND** совпадает с хешем, по которому qBittorrent позже сопоставит раздачу

#### Scenario: v2-only файл записывается под v2-хешем

- **WHEN** система разбирает `.torrent` только с метаданными v2 (без v1)
- **THEN** у загрузки записывается v2-хеш (64-hex), совпадающий с `infohash_v2`
  qBittorrent
- **AND** сопоставление раздачи работает по нему

#### Scenario: Дубль .torrent по активной torrent-задаче

- **GIVEN** уже есть активная (в т.ч. `catched`) загрузка с тем же infohash и
  `source_type = torrent`
- **WHEN** принимается `.torrent` с тем же инфохэшем
- **THEN** новая загрузка не создаётся, возвращается существующая
- **AND** байты торрента повторно не сохраняются (дубль)

#### Scenario: Апгрейд catched-magnet до torrent

- **GIVEN** активная загрузка в `catched` с `source_type = magnet` и хешем `h`
  (magnet-задача ещё не отдана в qBittorrent)
- **WHEN** принимается `.torrent` с тем же инфохэшем `h`
- **THEN** новая загрузка не создаётся, возвращается существующая
- **AND** байты `.torrent` сохраняются привязанными к ней, а её `source_type`
  становится `torrent` — в одной транзакции
- **AND** воркер добавит раздачу файлом (не по magnet), если апгрейд лёг до
  подготовки запроса на добавление — см. `download-tracking` «Добавление
  пойманной загрузки в qBittorrent»

#### Scenario: Апгрейд в окне вывода имени — гарантия добавления файлом не даётся

- **GIVEN** активная загрузка в `catched` с `source_type = magnet`, для которой
  воркер уже подготовил запрос на добавление и выводит отображаемое имя
- **WHEN** принимается `.torrent` с тем же инфохэшем
- **THEN** апгрейд применяется (байты сохранены, `source_type` стал `torrent`)
- **AND** гарантии, что воркер добавит раздачу файлом, приём в этом случае не
  даёт: исход добавления определяет `download-tracking` «Добавление пойманной
  загрузки в qBittorrent», где это окно и его цена описаны

#### Scenario: Magnet-задача уже добавлена — апгрейда нет

- **GIVEN** активная загрузка с `source_type = magnet` уже в `downloading`
  (отдана в qBittorrent)
- **WHEN** принимается `.torrent` с тем же инфохэшем
- **THEN** возвращается существующая загрузка, её `source_type` остаётся
  `magnet`, байты `.torrent` не сохраняются

#### Scenario: Контекст из полей файла

- **WHEN** принят `.torrent` с именем раздачи, деревом файлов и трекерами
- **THEN** в `download.Context` добавляется синтез (имя, размер, сигнал по
  файлам, домен трекера), дополняющий текст транспорта
- **AND** синтез выполнен без сетевых запросов

#### Scenario: Слишком большой .torrent отклоняется

- **WHEN** принимаемый `.torrent`-файл превышает ограничение размера
- **THEN** приём отклоняется с ошибкой, загрузка не создаётся
