# Чекпоинт 1 — ревью предложения, профиль `design` - **Change:** `ingest-nits` - **База диффа:** `master` (`5c79fdfffe9e2ddc78b7ebf9f4cdc3aa83560b2b`); кода на ветке на момент прогона не было. - **Профиль:** `design`. **Режим прогона:** `по графу` (все три прохода одной волной — граф профиля плоский, машину не держит никто). - **Дата:** 2026-08-06. ## Запущенные проходы и исход | Проход | Исход | | --- | --- | | `review-specs` | 6 находок (2 `major`, 4 `minor`) + 5 границ спеки | | `review-rubric` (фаза 1) | рубрика из 12 свойств, 8 находок (2 `major`, 6 `minor`), 5 promote-кандидатов | | `review-architecture` | 2 находки (обе `major`) + 3 пункта «дешевле переделать до мерджа» | Триаж на этом чекпоинте не запускается — так устроен профиль: находок единицы, каждая либо правит спеку, либо становится записанным вопросом. Сведение сделал оркестратор пайплайна. ## Что отработано инлайн Дедуплицировано по причине; в скобках — кто нашёл. 1. **Фактическое основание N1 было неверным** (`specs`, `major`, оракул — прогон крафт-входов через `metainfo.Load`). Библиотека сентинел `-` не синтезирует: `BestName()` на раздаче без имени возвращает пустую строку, а `NoName` присваивается только при авторинге. Значит `-` доходит до нас лишь от раздачи, объявившей его полем `name`. Следствия: критерий приёмки постановки выполнялся **до** изменения; случай «раздача без `name`» не был покрыт вовсе; неверный комментарий в `internal/torrent/torrent_test.go:224` и есть источник исходной ошибки ревью 2026-07-08. → требование, дизайн и предложение переписаны по наблюдению; два входа разведены сценариями и тестами; комментарий теста правится. 2. **Требование `identity` заявляло больше, чем change делает** (`architecture` и `specs`, оба `major`): обязанность формулировалась как «до первого вызова, способного что-либо записать», и поимённо называла `cancel`/`dismiss`, которые change сознательно не трогает. Нормативная спека стала бы ложной в момент архивации. → обязанность сужена до **вызова внешнего сервиса**. 3. **`request_id` вменялся всем транспортам, а существует только на HTTP** (`architecture` `major`, `specs` `minor`, `rubric` `major`). → требование называет ключ поимённо по транспортам; для Telegram зафиксировано отсутствие ключа как сегодняшнее состояние, вопрос записан в `design.md` → Open Questions. Регрессии нет: `res.DownloadID` уже сегодня всегда пуст. 4. **Telegram числился среди `ext.*`-клиентов, которых у него нет** (`specs`, `minor`). → убран; перечень клиентов не дублируется, а отдан конвенции. 5. **Третий вызов приёма (`handleUIAdd`, веб-форма) отсутствовал в задачах** (`specs`, `minor`). → добавлен пункт 2.4; иначе требование было бы выполнено на две трети. 6. **Заявленная однородность с фильтром magnet-заглушек ложна** (`specs`, `minor`): у magnet заглушка `dn` фильтруется только в контексте, а в подсказку имени уходит. → различие названо явно, распространение на magnet объявлено отдельным изменением. 7. **Спека утверждала абсолют «ниже по потоку сентинел не встречается», а путь файла его выпускает** (`rubric`, `minor`). → нормализуемые поля перечислены поимённо, про пути файлов сказано, почему они не нормализуются. 8. **Имя с переводом строки подменяет строки построчного контекста** (`rubric`, `major`). → нормализация расширена на разделители строк и краевые пробелы тем же `oneLine`, что уже применён к комментарию. В требовании прямо сказано, что защитой от инъекции в промпт это **не** является: пользовательский текст многострочен по замыслу. 9. **Контракт «результат пуст» держался перечнем веток** (`rubric`, `minor`). → удерживается структурно: именованный возврат + один `defer`; тест сравнивает результат с нулевым значением целиком. 10. **У третьего потребителя `DisplayName` (подсказка namer) не было оракула** (`rubric`, `minor`). → добавлен пункт 1.5, тест в `internal/worker`. 11. **Тест записи о `Fails.` проверял только наличие полей** (`rubric`, `minor`). → добавлена негативная половина: `passkey` отправленного magnet в записи не встречается. Инвариант «секреты не в логи» — `major`. 12. **Сценарий требовал `infohash` там, где тело требования смягчало до «когда известен»** (`specs`, граница спеки). → в GIVEN добавлено «с известным инфохэшем». 13. **Каталог `docs/research/` тихо расширялся с чужих данных на чужой код** (`architecture`). → вводная README расширяется, записка получает условие устаревания. ## Записанные вопросы Оба — в `design.md` → Open Questions, оба с вариантами, ценой и рекомендацией. 1. Нужен ли Telegram-транспорту собственный корреляционный ключ у отказа приёма. Рекомендация — поднять запись отказа разбора до `INFO` с `infohash` отдельной задачей, а не заводить второй канал корреляции. 2. Приводить ли `Cancel`/`Dismiss` к форме `ctx = w.scoped(…)`. Рекомендация — отдельной задачей-гигиеной, когда в них появится первый внешний вызов. ## Урожай (заведение задач — не работа пайплайна) - **Заглушка `dn` вида `*-topic-` уходит в подсказку вывода имени**, хотя из контекста отфильтрована (`worker.sourceAddParts` → `magnet.Parse` → `DisplayName`, `internal/worker/worker.go:626`). Симметрично N1, но на magnet-ветке. Правка требует изменения действующего требования «Синтез контекста распознавания из полей magnet» (сценарий «Строки-факты не становятся отображаемым именем»), поэтому в границы этой задачи не входит. Оракул: тест на подсказку для голого magnet с заглушкой. Провенанс: `specs`, change `ingest-nits`. - **`infohash` в записи о внешнем вызове снимается со снимка загрузки, а не с того, с чем вызов ушёл** (`rubric`, `low`). Сегодня значения совпадают; расхождение возможно у загрузки с несколькими хешами (v1+v2). Класс совпадает с журналом 2026-08-06 (признак снят с одной сущности, действие применено к другой), но стоит здесь только разбора, не данных. - **Пустой `source_ref` у torrent-загрузки** возможен: `.torrent` с именем `-` и без имени присланного файла. Действующее требование велит `source_ref` быть человекочитаемым референсом, но случая «нет ни того, ни другого» не описывает. Дыра не новая, но изменение проходит ровно по ней. Провенанс: `specs`, границы спеки. - **Отказ разбора источника пишется на `DEBUG`** (`internal/ingest/ingest.go`), то есть на боевом `INFO` не пишется вовсе — для Telegram-пути это означает, что у отказа приёма нет ни ключа у пользователя, ни записи в журнале. Связано с записанным вопросом 1. - **Promote candidates** (`rubric`): (а) «scoped-логгер загрузки строится один раз, на входе публичной команды воркера» → `docs/conventions/logging.md` плюс механизация тестом-перебором; (б) «нормализация недоверенного имени на границе — это вырожденные значения, краевые пробелы и разделители строк» → `docs/review.md`, «Парсер недоверенного входа»; (в) «у каждого транспорта назван свой корреляционный ключ публичного канала» → `docs/conventions/errors.md`; (г) «тест клиента внешнего сервиса проверяет и отсутствие секретосодержащих значений» → `docs/conventions/logging.md`. ## Границы покрытия - **Гейт на этом чекпоинте не запускался** — кода на ветке не было; это устройство профиля `design`, а не пропуск. - **Триаж не запускался** — в профиле `design` его нет по построению; сведение и дедупликацию находок сделал оркестратор пайплайна, то есть **тот же, кто писал предложение**. Разведённости приёмщика и исполнителя на этом чекпоинте нет. - **Не запускались** проходы `code`, `adversary`, `ops`, `reimpl` — они не входят в профиль `design` и работают после apply (чекпоинт 2). - **Замер аллокаций bencode** на этом чекпоинте не воспроизводился ни одним проходом: `specs` проверил только провенанс ссылок по исходникам библиотеки. Сам замер — задача 4.1, его исход проверяет чекпоинт 2. - **Ни один проход не открывал живой qBittorrent, LLM и метабазы** — запрещено `CLAUDE.md` → «Запреты». - **Ценность самой постановки** («нужно ли закрывать эти четыре нити») ревью не оценивает ни в одном профиле.