- имя раздачи нормализуется на границе разбора: вырожденное `-` (metainfo.NoName) даёт пустое имя, пробельное схлопывается — сентинел больше не доходит ни до контекста распознавания, ни до source_ref, ни до подсказки вывода имени - контракт «на любом пути ошибки приёма результат нулевой» объявлен в ingest и удерживается структурно; три транспорта перестали обещать идентификатор, которого нет, и коррелируют отказ по request_id - scoped-логгер загрузки ставится до вызова внешнего сервиса в семи командах воркера — записи об отказе qBittorrent и метабаз получили download_id и infohash; граница разбора bencode записана в docs/research
138 lines
13 KiB
Markdown
138 lines
13 KiB
Markdown
# Чекпоинт 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` → «Запреты».
|
||
- **Ценность самой постановки** («нужно ли закрывать эти четыре нити») ревью не
|
||
оценивает ни в одном профиле.
|