httpapi: форма провода читающих маршрутов объявлена транспортом
- доменные типы internal/catalog лишились json-тегов и MarshalJSON; типы metricWire/layerWire/aggregationWire и перевод catalogWire живут в транспорте, тело отказа тоже получило объявленный тип — байты ответа не изменились - заведён сторож: обход графа типов ответа утверждает, что домен не доезжает до сериализации, плюс требование json-тега на полях транспортных структур и заведомо красные случаи к обоим правилам - решение с ценой обеих сторон записано в architecture.md и ADR; шаг lint в гейте получил свой кеш — общий на машину красил прогон находками из чужого worktree
This commit is contained in:
+46
-1
@@ -230,7 +230,7 @@ capability**, и здесь стоит ссылка, а не пересказ т
|
||||
| `replay` | проигрывание журнала в витрину: состав, порядок, отчёт | [`reindex`](../openspec/specs/reindex/spec.md) |
|
||||
| `catalog` | каталог разрезов и измерение рода агрегации | [`catalog`](../openspec/specs/catalog/spec.md) |
|
||||
| `store` | SQLite: доставки, часовые объекты, тренировки, записи | [`storage`](../openspec/specs/storage/spec.md) |
|
||||
| `httpapi` | приём и read API | [`ingest`](../openspec/specs/ingest/spec.md), [`catalog`](../openspec/specs/catalog/spec.md) |
|
||||
| `httpapi` | приём, read API и **форма провода** ответов чтения | [`ingest`](../openspec/specs/ingest/spec.md), [`catalog`](../openspec/specs/catalog/spec.md), [`read-api`](../openspec/specs/read-api/spec.md) |
|
||||
|
||||
## Приём
|
||||
|
||||
@@ -1594,6 +1594,51 @@ GET /healthz
|
||||
семантику разбирает клиент по имени метрики. Полная нормализация означала бы,
|
||||
что каждая новая метрика требует правки коллектора, а незнакомая теряется.
|
||||
|
||||
### Форма провода
|
||||
|
||||
**Форму ответа объявляет транспорт, а не домен.** Каждый читающий маршрут
|
||||
`internal/httpapi` держит собственные типы с `json`-тегами и переводит в них
|
||||
доменное значение присваиванием поле в поле; доменные типы (`internal/catalog`
|
||||
и далее) `json`-тегов не несут и до сериализации не доезжают. То же правило
|
||||
покрывает тело отказа. MCP собственной формы не объявляет — адаптер переводит
|
||||
вызовы в те же обработчики.
|
||||
|
||||
Цена названа с обеих сторон, потому что она обратная, а не односторонняя.
|
||||
|
||||
- **Домен = провод** (как было у каталога): формы объявлены один раз, перевода
|
||||
нет, ноль строк на маршрут. Платим тем, что публичный контракт меняется
|
||||
правкой домена **молча** — переименованием поля, разъединением встроенной
|
||||
структуры (плоскость `aggregation` была следствием встраивания `Basis`),
|
||||
появлением внутреннего поля. Ни одна из трёх правок транспорт не трогает.
|
||||
- **Раздельно** (взято): контракт меняется только правкой транспорта, то есть
|
||||
действием. Платим двумя вещами. Форма объявлена дважды — типы и перевод на
|
||||
каждый маршрут. И цена **обратная**: новое поле домена в ответ само не
|
||||
попадёт, его обязан перечислить перевод; поле, не доехавшее до клиента, —
|
||||
такой же дефект, как поле, уехавшее случайно, просто другой.
|
||||
|
||||
Развилку решил факт, а не вкус: провод точек обещан как
|
||||
`{ts, tz_offset, units, values}`, а `store.Point` несёт
|
||||
`{Start, End, OffsetSeconds, Raw}` — эти наборы не совпадают ни одним именем,
|
||||
и доменный тип формой провода там быть не может. Хранилище, кстати, уже живёт
|
||||
по этому правилу: формат `payload` объявлен отдельным неэкспортированным
|
||||
`storedPoint`, а `encodePayload` переводит в него полем в поле.
|
||||
|
||||
Сторожей два, и роли у них разные. **Обход графа типов ответа** (внутренний
|
||||
тест `httpapi`) утверждает, что домен до энкодера не доезжает — отсюда и
|
||||
следует, что переименование поля домена байт не меняет; рядом стоит заведомо
|
||||
красный случай, потому что проверка, доказывающая отсутствие, зелена и будучи
|
||||
сломанной. **Байтовый литерал** на каждую различимую форму ответа — детектор
|
||||
изменения формы: он краснеет в момент правки. Источником истины контракта он
|
||||
не является — им станет рукописная OpenAPI-спека, и сверку с маршрутами внесёт
|
||||
в гейт отдельная задача.
|
||||
|
||||
Разбор чужих решений (домен = провод у `wtf` и Prometheus; раздельно у Gitea,
|
||||
Docker и go-kit; версионирование с конверсией у Kubernetes; отвергнутый
|
||||
`apidiff`, который смены `json`-тега не видит вовсе) —
|
||||
[design.md изменения](../openspec/changes/archive/2026-08-04-forma-provoda-chteniya/design.md).
|
||||
Ссылка markdown-ссылкой намеренно: инлайн-код `docs.py check` не проверяет, а
|
||||
путь угадывался до архивации.
|
||||
|
||||
### MCP
|
||||
|
||||
Поверх Read API — адаптер MCP, чтобы агент подключался без промежуточного
|
||||
|
||||
Reference in New Issue
Block a user