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

7.0 KiB
Raw Blame History

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/workerRetry кладёт scoped-логгер загрузки в ctx.
  • internal/qbt — тест с подставным сервером на поля записи о неуспешном add.
  • docs/research/ — новая записка про границы разбора .torrent (предел MaxStrLen bencode + наблюдение о BestName/NoName); строка в индексе docs/research/README.md и полстроки в его вводной.
  • Схема БД, конфиг, API и тексты Telegram не затрагиваются; нумерованных артефактов (миграций, ADR) изменение не добавляет.