- имя раздачи нормализуется на границе разбора: вырожденное `-` (metainfo.NoName) даёт пустое имя, пробельное схлопывается — сентинел больше не доходит ни до контекста распознавания, ни до source_ref, ни до подсказки вывода имени - контракт «на любом пути ошибки приёма результат нулевой» объявлен в ingest и удерживается структурно; три транспорта перестали обещать идентификатор, которого нет, и коррелируют отказ по request_id - scoped-логгер загрузки ставится до вызова внешнего сервиса в семи командах воркера — записи об отказе qBittorrent и метабаз получили download_id и infohash; граница разбора bencode записана в docs/research
79 lines
7.0 KiB
Markdown
79 lines
7.0 KiB
Markdown
## Why
|
||
|
||
Ревью приёма 2026-07-08 оставило четыре нити, каждая из которых по отдельности
|
||
дёшева, а вместе они портят три разных наблюдаемых поверхности: контекст
|
||
распознавания (грязная строка), комментарии в коде (описывают контракт, которого
|
||
уже нет), журнал (запись о неуспешном добавлении без корреляции) и знание о
|
||
границе чужой библиотеки (не записано нигде). Пока нити открыты, каждый
|
||
следующий проход ревью тратит внимание на то, что уже разобрано.
|
||
|
||
## What Changes
|
||
|
||
- **N1 — вырожденное имя не покидает разборщик.** Раздача может объявить полем
|
||
`name` значение `-` — конвенция «имени нет» (в библиотеке разбора это
|
||
константа `metainfo.NoName`). Сейчас `.torrent`-приём отбрасывает `-` только
|
||
для `source_ref`, а `torrent.Info.Context()` и подсказка имени для `namer`
|
||
берут его как содержательное название. Нормализация переезжает на границу
|
||
разбора: `DisplayName` пустеет прямо в `torrent.Parse`, и три места ниже по
|
||
потоку перестают знать про вырожденное значение. Тем же местом схлопываются
|
||
разделители строк в имени — иначе имя добавляет в построчный контекст строку,
|
||
выглядящую как синтезированный нами факт.
|
||
**Основание нити уточнено ревью предложения:** библиотека `-` не синтезирует
|
||
(`BestName()` на раздаче без имени возвращает пустую строку), поэтому речь о
|
||
разборе объявленного значения, а не о фильтре чужого сентинела; случай
|
||
«раздача без поля `name`» уже отрабатывается верно и получает свой сценарий,
|
||
чтобы впредь не путаться с первым.
|
||
- **N3 — комментарии описывают фактический контракт `Ingest`.** После
|
||
fast-catch-рефактора `Ingest` возвращает нулевой `Result` на **каждом** пути
|
||
ошибки, а комментарии в `httpapi` и `tgbot` до сих пор обещают непустой
|
||
`DownloadID` «при сбое после создания задачи». Контракт закрепляется в
|
||
доке `Ingest`, удерживается структурно (обнуление результата одним `defer`, а
|
||
не аккуратностью каждой ветки), подтверждается тестом и перестаёт
|
||
пересказываться неверно на транспортах. Вызовов приёма три, а не два:
|
||
REST, веб-форма и Telegram.
|
||
- **N4 — запись о неуспешном добавлении в qBittorrent коррелируется.** Клиент
|
||
`qbt` берёт логгер из `ctx` и своего инфохэша не знает (и знать не должен —
|
||
разбор источника живёт в `magnet`/`torrent`). Путь `worker.Retry` не кладёт в
|
||
`ctx` scoped-логгер загрузки, поэтому запись `Fails.` с этого пути уходит без
|
||
`download_id`/`infohash`. Retry приводится к тому же виду, что и `Delete`.
|
||
**Ревью изменения показало, что таких команд не одна, а семь** — те же
|
||
`Relink`, `Rerecognize`, `Refine`, `ChooseCandidate`, `AddManualSource`,
|
||
`SetProviderID` зовут qBittorrent или метабазу до постановки логгера.
|
||
Починены все семь: иначе требование `identity` архивировалось бы ложным.
|
||
- **N5 — граница чужой библиотеки записывается наблюдением.** `anacrolix/torrent`
|
||
аллоцирует объявленную bencode-строку до её чтения, с потолком ~128 MiB.
|
||
Правке не подлежит (чужой код), поэтому исход — запись в `docs/research/` с
|
||
провенансом и замером, а не изменение кода.
|
||
|
||
## Capabilities
|
||
|
||
### New Capabilities
|
||
|
||
Нет — изменение не вводит нового поведения и новых понятий.
|
||
|
||
### Modified Capabilities
|
||
|
||
- `ingest`: приём `.torrent` перестаёт считать вырожденное имя `-`
|
||
содержательным — ни в синтезированном контексте, ни в подсказке имени, ни в
|
||
`source_ref`; плюс фиксируется контракт «на ошибке приёма результат пуст» и
|
||
поимённо называется корреляционный ключ отказа на каждом транспорте.
|
||
- `identity`: корреляция в журнале распространяется на записи о вызовах внешних
|
||
сервисов, сделанных в контексте загрузки, — включая путь retry.
|
||
|
||
## Impact
|
||
|
||
- `internal/torrent` — нормализация `DisplayName` на границе разбора; тест на
|
||
безымянной раздаче.
|
||
- `internal/ingest` — упрощение фильтра `source_ref`, дока и структурная
|
||
гарантия контракта `Result`, тест «на ошибке результат нулевой».
|
||
- `internal/httpapi` (REST `handleAPIAdd` **и** веб-форма `handleUIAdd`),
|
||
`internal/tgbot` — комментарии и передача пустого id в диагностику вместо
|
||
заведомо пустого `res.DownloadID`.
|
||
- `internal/worker` — `Retry` кладёт scoped-логгер загрузки в `ctx`.
|
||
- `internal/qbt` — тест с подставным сервером на поля записи о неуспешном `add`.
|
||
- `docs/research/` — новая записка про границы разбора `.torrent` (предел
|
||
`MaxStrLen` bencode + наблюдение о `BestName`/`NoName`); строка в индексе
|
||
`docs/research/README.md` и полстроки в его вводной.
|
||
- Схема БД, конфиг, API и тексты Telegram не затрагиваются; нумерованных
|
||
артефактов (миграций, ADR) изменение не добавляет.
|