## MODIFIED Requirements ### Requirement: Корреляция сущностей в логах Записи журнала, относящиеся к сущности, SHALL содержать её id в атрибуте `_id` (`download_id`, `recognition_id`, `batch_id`, …); работа в контексте загрузки ведётся через scoped-логгер с `download_id`. Благодаря глобальной уникальности ULID поиск по значению id (grep/jq) SHALL находить все записи журнала, относящиеся к сущности, независимо от имени поля. Scoped-логгер SHALL передаваться через `context`, а не доклеиваться к каждой записи руками. Отсюда обязанность вызывающего, и она ограничена наблюдаемым исходом: **операция, работающая в контексте загрузки и делающая вызов внешнего сервиса, SHALL положить scoped-логгер этой загрузки в `context` до такого вызова** — включая команды, пришедшие с транспорта, а не только фоновый цикл воркера. Однородность формы у команд, внешних вызовов не делающих, это требование не нормирует: она принадлежит конвенциям кода. Причина в том, что клиент внешнего сервиса своей доменной сущности не знает и знать SHALL NOT — он берёт логгер из `context`. Поэтому вызов внешнего сервиса в контексте загрузки SHALL давать запись с `download_id` и, когда он известен, `infohash`; добавлять клиенту поля-дубликаты доменных идентификаторов ради этого SHALL NOT — источник корреляции один. Перечень клиентов, ведущих записи о внешних вызовах, живёт в `docs/conventions/logging.md` и здесь не дублируется. Telegram-клиент таких записей не ведёт, и уведомление отправляется вне контекста загрузки намеренно (иначе оно умирало бы вместе с тиком) — это требование его не касается. Отдельно это важно там, где внешний сервис не сообщает причину отказа: ответ qBittorrent `Fails.` на добавление раздачи причины не несёт, и единственное, что делает такую запись пригодной для разбора, — корреляция с загрузкой. #### Scenario: Путь загрузки по логам - **GIVEN** загрузка прошла приём, распознавание и раскладку - **WHEN** журнал фильтруется по значению её `id` - **THEN** находятся записи всех этапов (ingest, recognition, file-layout) #### Scenario: Неуспешное добавление в qBittorrent с пути retry - **GIVEN** загрузка в `failed` с известным инфохэшем, для которой оператор запросил retry - **WHEN** qBittorrent отвечает на добавление отказом (`Fails.` либо не-200) - **THEN** запись о вызове внешнего сервиса содержит `download_id` и `infohash` загрузки - **AND** запись находится тем же фильтром по значению id, что и записи фонового пути добавления