# Чекпоинт 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-<id>` уходит в подсказку вывода имени**, хотя из
  контекста отфильтрована (`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` → «Запреты».
- **Ценность самой постановки** («нужно ли закрывать эти четыре нити») ревью не
  оценивает ни в одном профиле.
