- имя раздачи нормализуется на границе разбора: вырожденное `-` (metainfo.NoName) даёт пустое имя, пробельное схлопывается — сентинел больше не доходит ни до контекста распознавания, ни до source_ref, ни до подсказки вывода имени - контракт «на любом пути ошибки приёма результат нулевой» объявлен в ingest и удерживается структурно; три транспорта перестали обещать идентификатор, которого нет, и коррелируют отказ по request_id - scoped-логгер загрузки ставится до вызова внешнего сервиса в семи командах воркера — записи об отказе qBittorrent и метабаз получили download_id и infohash; граница разбора bencode записана в docs/research
10 KiB
ADDED Requirements
Requirement: Вырожденное имя раздачи не считается именем
Система SHALL нормализовать имя раздачи из .torrent на границе разбора
источника (internal/torrent) и не считать содержательным именем вырожденное
значение - — тот же литерал, которым экосистема обозначает «имени нет»
(в библиотеке разбора он выставлен константой metainfo.NoName и присваивается
при авторинге раздачи из вырожденного пути). Раздача объявляет это значение
сама, полем name; библиотека его не синтезирует, поэтому речь о разборе
объявленного значения, а не о фильтре чужого сентинела.
Раздача, у которой поля name нет вовсе, уже даёт пустое имя — это отдельный
случай, и он нормализации не требует. Оба случая после разбора SHALL быть
неразличимы: имени нет.
Нормализация SHALL применяться к имени раздачи и охватывать три поля, которые из него выводятся:
- строка названия в синтезированном контексте распознавания (см. «Приём источника из .torrent-файла») — за неимением имени строка названия отсутствует;
source_refзагрузки — работает прежний фолбек на имя присланного файла;- подсказка вывода отображаемого имени, которую воркер берёт из метаданных
торрента (см.
download-tracking«Добавление пойманной загрузки в qBittorrent») — подсказка остаётся пустой.
Пути файлов раздачи нормализации не подлежат, и это осознанно. Вырожденное
имя попасть в путь файла может — при пустом наборе сегментов путь берётся
фолбеком из имени раздачи, ненормализованного. Допустимо потому, что путь файла
читается только как число файлов и набор расширений (сигнал по дереву), а
целевые пути раскладки строятся не из него, а из ответа qBittorrent
(см. file-layout). Появится потребитель, читающий путь как путь, —
нормализовать его надо там же, на границе разбора.
Имя раздачи — недоверенный вход, а контекст распознавания читается
построчно. Поэтому нормализация SHALL схлопывать в один пробел любые
последовательности пробельных символов — включая разделители строк — и убирать
краевые, чтобы имя не могло добавить в контекст строку, выглядящую как
синтезированный нами факт. Схлопывание SHALL выполняться до сравнения с
вырожденным значением, иначе имя вида " - " сравнение не пройдёт, а после
схлопывания станет ровно вырожденным и уедет вниз по потоку. Тем же правилом
уже обрабатывается комментарий торрента. Защитой от инъекции в промпт оно
не является и таковой объявляться SHALL NOT: пользовательский текст
контекста многострочен по замыслу и идёт в промпт как есть (см. «Синтез
контекста распознавания из полей magnet»).
Правило не однородно с фильтром заглушек-идентификаторов dn у magnet: там
заглушка вида *-topic-<id> отбрасывается только в контексте, а в подсказку
вывода имени по-прежнему уходит (действующее требование «Синтез контекста
распознавания из полей magnet»). Здесь правило строже, и распространение его на
magnet — отдельное изменение, этим требованием оно не заказано.
Scenario: Раздача объявила имя -
- GIVEN
.torrent, у которого полеnameравно-либо становится равным-после схлопывания пробельного (например" - ") - WHEN система разбирает источник и синтезирует контекст распознавания
- THEN имя раздачи после разбора пусто, строки названия в контексте нет
- AND остальные строки-факты (размер, файлы, трекер, комментарий) синтезируются как обычно
- AND подсказка вывода отображаемого имени пуста
Scenario: Раздача без поля name
- GIVEN
.torrentбез поляname - WHEN система разбирает источник
- THEN имя раздачи пусто — так же, как у раздачи, объявившей
-
Scenario: Вырожденное имя даёт source_ref из имени файла
- GIVEN
.torrentс именем-, присланный файломDune.torrent - WHEN вызывается приём
- THEN
source_refзагрузки —Dune.torrent, а не-
Scenario: Имя с переводом строки не добавляет строк в контекст
- GIVEN
.torrent, имя которого содержит перевод строки и текст, похожий на синтезированный факт - WHEN система синтезирует контекст распознавания
- THEN имя занимает ровно одну строку названия
- AND число строк-фактов в контексте такое же, как у раздачи с обычным именем
Requirement: Результат приёма при ошибке пуст
Приём SHALL возвращать транспорту нулевой результат на любом пути ошибки: идентификатор загрузки, инфохэши, состояние и признак дедупликации в этом случае не публикуются. Причина в том, что быстрый приём не создаёт наблюдаемых последствий раньше, чем становится способен вернуть успех: разбор источника, дедуп-чек и заведение загрузки идут до ответа, а всё, что может отказать после заведения (добавление в qBittorrent, вывод имени), выполняет воркер и на исход приёма не влияет.
Контракт SHALL удерживаться структурно — одним местом обнуления результата при ненулевой ошибке, а не аккуратностью каждой ветки возврата: перечень веток растёт, и именно расхождение перечня с текстом породило исходный дефект.
Транспорты SHALL опираться на этот контракт и SHALL NOT обещать пользователю или журналу идентификатор загрузки, которого нет. Корреляционный ключ публичного отказа приёма зависит от транспорта, и требование называет его поимённо:
- HTTP API и веб-UI —
request_idзапроса (см.docs/conventions/errors.md, «Граница и трансляция»); - Telegram — корреляционного ключа у отказа приёма нет, и требование это
фиксирует как сегодняшнее состояние, а не как цель: диагностика ищется по
записи приёма (
capability=ingest,infohash). Заводить Telegram-транспорту собственный идентификатор запроса это требование SHALL NOT.
Scenario: Невалидный источник
- WHEN приём получает источник, который не разбирается ни как magnet, ни
как
.torrent - THEN возвращается ошибка и нулевой результат (идентификатор загрузки пуст)
Scenario: Сбой хранилища на заведении загрузки
- WHEN приём получает валидный источник, но хранилище отказывает при дедуп-чеке или заведении загрузки
- THEN возвращается ошибка и нулевой результат
Scenario: Отказ приёма на HTTP-границе
- GIVEN приём отказал по любой причине
- WHEN HTTP-транспорт (REST или веб-форма) отвечает пользователю
- THEN ответ несёт
request_idзапроса, а не идентификатор загрузки