httpapi: форма провода читающих маршрутов объявлена транспортом
- доменные типы internal/catalog лишились json-тегов и MarshalJSON; типы metricWire/layerWire/aggregationWire и перевод catalogWire живут в транспорте, тело отказа тоже получило объявленный тип — байты ответа не изменились - заведён сторож: обход графа типов ответа утверждает, что домен не доезжает до сериализации, плюс требование json-тега на полях транспортных структур и заведомо красные случаи к обоим правилам - решение с ценой обеих сторон записано в architecture.md и ADR; шаг lint в гейте получил свой кеш — общий на машину красил прогон находками из чужого worktree
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
## 1. Домен перестаёт быть формой провода
|
||||
|
||||
- [x] 1.1 `internal/catalog`: снять `json`-теги с `Basis`, `Aggregation`,
|
||||
`LayerRange`, `Metric`; комментарии типов переписать так, чтобы они называли
|
||||
их формой ответа use-case, а не формой ответа HTTP
|
||||
- [x] 1.2 `internal/catalog`: удалить `Style.MarshalJSON`; `String()` оставить —
|
||||
он нужен логам и сообщениям тестов, и провод зовёт его же
|
||||
- [x] 1.3 `internal/catalog/measure_test.go`: `TestStyleСловарь` переезжает с
|
||||
`MarshalJSON` на `String()`, значения словаря те же
|
||||
|
||||
## 2. Форма провода в транспорте
|
||||
|
||||
- [x] 2.1 `internal/httpapi/catalog.go`: типы `metricWire`, `layerWire`,
|
||||
`aggregationWire` с `json`-тегами; поля `aggregation` перечислены **плоско**,
|
||||
а не встраиванием — плоскость перестаёт быть следствием формы домена
|
||||
- [x] 2.2 перевод ровно этой сигнатуры (одна на все документы изменения):
|
||||
`func catalogWire(metrics []catalog.Metric) catalogResponse` — чистая функция
|
||||
без `context`, без хранилища, без часов; род кладётся строкой через
|
||||
`Style.String()`; пустые коллекции — `[]`, отсутствующие метки — `null`
|
||||
- [x] 2.3 `handleMetrics` собирает ответ вызовом `catalogWire`; версия снимка
|
||||
по-прежнему уходит только в `ETag`, в тело не попадает
|
||||
- [x] 2.4 `internal/httpapi/httpapi.go`: тело отказа собирается объявленным
|
||||
типом вместо `map[string]string`; байты те же — `{"error":"…"}`
|
||||
|
||||
## 3. Проверки
|
||||
|
||||
- [x] 3.1 `internal/httpapi/wire_internal_test.go` (внутренний тест пакета):
|
||||
обход графа типов значения ответа — поля, срезы, массивы, ключи и значения
|
||||
карт, указатели, встроенные и неэкспортированные поля; защита от
|
||||
самоссылающегося типа; принадлежность определяется по `PkgPath` с префиксом
|
||||
module path, а не по имени пакета
|
||||
- [x] 3.2 обход применён **таблицей** «маршрут → образец ответа»: каталог, тело
|
||||
отказа. Следующий маршрут добавляет строку
|
||||
- [x] 3.3 **заведомо красный случай**: на фикстуре, содержащей `catalog.Metric`,
|
||||
обход обязан быть красным. Он же ловит устаревший префикс module path
|
||||
- [x] 3.4 исключение задано **по типу** `json.RawMessage`, а не по признаку
|
||||
«есть свой `MarshalJSON`»; кейс с доменным типом, несущим `MarshalJSON`, —
|
||||
красный
|
||||
- [x] 3.5 `TestКаталогОтдаётОжидаемыеБайты` остаётся; литерал не правится ни
|
||||
одним символом, комментарий называет его **детектором изменения формы**, а
|
||||
источником истины — рукописную OpenAPI-спеку (задача `openapi-spec`)
|
||||
- [x] 3.6 `TestКаталогНеизмеренноеОкноОтдаётNull` переписывается с подстрок на
|
||||
**байты целого тела**: это ветка `*time.Time`, где ручной перевод и создаёт
|
||||
риск подставить `0001-01-01` вместо `null`
|
||||
- [x] 3.7 байты пустого каталога (`{"metrics":[]}`) остаются утверждением
|
||||
- [x] 3.8 форма провода утверждается **ровно в одном месте** на форму ответа:
|
||||
второго утверждения той же формы через разобранную структуру или подстроку в
|
||||
пакете нет
|
||||
|
||||
## 4. Документы
|
||||
|
||||
- [x] 4.1 `docs/architecture.md`, раздел «Read API»: подраздел «Форма провода» —
|
||||
решение, **цена обеих сторон**, ссылка на `design.md` изменения за prior art
|
||||
- [x] 4.2 `docs/conventions/testing.md`: правило о механизме — байтовое
|
||||
утверждение на **каждую различимую** форму ответа; структурная проверка,
|
||||
доказывающая отсутствие, несёт заведомо красный случай
|
||||
- [x] 4.3 `openspec validate --strict forma-provoda-chteniya` зелёный
|
||||
|
||||
## 5. Верификация
|
||||
|
||||
- [x] 5.1 `task gate` зелёный
|
||||
- [x] 5.2 поведенческая: сервис поднят на своём порту и своей базе в `./tmp`.
|
||||
Сравниваются **два ответа одного прогона** — снятый с базовой ревизии и
|
||||
снятый с ветки против **одного и того же** файла базы при неработающем
|
||||
приёме. Числа ответа (`points`, `hours`, границы) печатаются, но в
|
||||
утверждении не участвуют: все они производны от корпуса. `ETag` сравним
|
||||
только внутри одного часа — горизонт измерения входит в метку и едет вместе
|
||||
с часами
|
||||
|
||||
## Критерии приёмки задачи
|
||||
|
||||
Из `docs/tasks/items/read-api-wire-format.md`, дословно:
|
||||
|
||||
- [x] решение записано в `docs/architecture.md` с названной ценой обеих сторон,
|
||||
а не только выбранной — оракул: глазами по разделу
|
||||
- [x] переименование поля доменного типа либо не меняет байты ответа, либо
|
||||
меняет их намеренно, и тест утверждает это прямо, а не проверяет непустоту —
|
||||
оракул задачи назван как «тест на переименование»; **исполнимая его форма** —
|
||||
обход графа типов (3.1) плюс заведомо красный случай (3.3) плюс байтовые
|
||||
литералы (3.5–3.7). Прямого «переименуй и посмотри» в Go-тесте не бывает:
|
||||
отказ компиляции собственного пакета тест не наблюдает, а при неудавшейся
|
||||
компиляции байтов не существует вовсе. Подмена оракула названа здесь, а не
|
||||
сделана молча
|
||||
- [x] маршрут каталога приведён к решению, и следующий маршрут копирует
|
||||
образец, а не выбирает заново — оракул: гейт зелёный плюс сверка
|
||||
`httpapi/catalog.go` с записанным решением
|
||||
|
||||
## Приёмочные критерии от ревью предложения (профиль `design`)
|
||||
|
||||
Рубрика прохода `review-rubric`, порождённая **до** чтения кода. Порядок —
|
||||
по важности.
|
||||
|
||||
- [x] Р1 байты ответа не изменились, и это **измерено**: прежний байтовый
|
||||
литерал не правится ни одним символом; поведенческая сверка сравнивает два
|
||||
ответа **одного прогона**, а не ответ с записанным ранее эталоном
|
||||
- [x] Р2 **оба сторожа доказали, что умеют краснеть**: обход графа типов красен
|
||||
на заведомо доменном значении, байтовое утверждение красно при переименовании
|
||||
тега в типе провода
|
||||
- [x] Р3 обход полон по позициям (поле, срез, массив, ключ и значение карты,
|
||||
указатель, встроенное поле, неэкспортированное поле, анонимная вложенная
|
||||
структура), не зацикливается на рекурсивном типе, а «внутренний пакет
|
||||
проекта» определяется по `PkgPath` с префиксом module path, а не по имени
|
||||
пакета; алиас доменного типа обход **не** пропускает
|
||||
- [x] Р4 пути в обход сторожа названы поимённо (`any`/интерфейс, собственный
|
||||
`MarshalJSON`, тип внешней зависимости), а исключение для дословного
|
||||
содержимого сужено **до типа** `json.RawMessage`
|
||||
- [x] Р5 вырожденные значения закреплены байтами: `{"metrics":[]}`, пустые
|
||||
коллекции как `[]`, неизмеренное окно как `null`, `"style":"unknown"`
|
||||
- [x] Р6 ответ детерминирован: форма провода не содержит `map`, два вызова на
|
||||
одном входе дают те же байты
|
||||
- [x] Р7 перевод — **чистая функция от снимка**: без `context`, без хранилища,
|
||||
без часов; тело и валидатор `ETag` выводятся из одного снимка
|
||||
- [x] Р8 ни одно утверждение не пришпилено к числу, производному от размера
|
||||
корпуса, и к ходу часов; метки времени фикстур — литералы
|
||||
- [x] Р9 форма провода утверждается **ровно в одном месте**, и это место
|
||||
названо комментарием
|
||||
- [x] Р10 отказные ответы принадлежат той же объявленной форме провода
|
||||
- [x] Р11 образец масштабируется: помощник применяется таблицей «маршрут →
|
||||
образец ответа», следующий маршрут добавляет строку
|
||||
- [x] Р12 транспорт не выносит значения точек, имена метрик и токен в лог выше
|
||||
`DEBUG` и не вкладывает доменное значение в текст ошибки
|
||||
|
||||
**Посылка поведенческой сверки, названная рядом с ней (Р1, Р8):** оба ответа
|
||||
снимаются в одном прогоне против одного и того же файла базы при неработающем
|
||||
приёме; `ETag` сравним только внутри одного часа — горизонт измерения входит в
|
||||
метку и едет вместе с часами.
|
||||
Reference in New Issue
Block a user