Files
jellybit/openspec/changes/archive/2026-08-06-ingest-nits/proposal.md
T
av d081ef1d30 ingest: закрыты мелочи приёма — вырожденное имя, контракт Result, корреляция add
- имя раздачи нормализуется на границе разбора: вырожденное `-`
  (metainfo.NoName) даёт пустое имя, пробельное схлопывается — сентинел больше
  не доходит ни до контекста распознавания, ни до source_ref, ни до подсказки
  вывода имени
- контракт «на любом пути ошибки приёма результат нулевой» объявлен в ingest и
  удерживается структурно; три транспорта перестали обещать идентификатор,
  которого нет, и коррелируют отказ по request_id
- scoped-логгер загрузки ставится до вызова внешнего сервиса в семи командах
  воркера — записи об отказе qBittorrent и метабаз получили download_id
  и infohash; граница разбора bencode записана в docs/research
2026-08-06 18:20:12 +03:00

79 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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) изменение не добавляет.