Files
jellybit/openspec/changes/archive/2026-08-06-ingest-nits/review/checkpoint-1-design.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

138 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Чекпоинт 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` → «Запреты».
- **Ценность самой постановки** («нужно ли закрывать эти четыре нити») ревью не
оценивает ни в одном профиле.