- `GET /api/v1/metrics/{name}?from&to&layer` — ряд точек за период; конверт
объявляет слой, измеренный род, его применимость к отданному ряду и границу
окна измерения, а сам ряд собирается из одного слоя, выбранного по охвату
точек внутри периода
- use-case вынесен в `internal/points`, чтение — одним входом `store.ReadSeries`
под одной транзакцией; правило выбора слоя остаётся в домене и приходит в
хранилище колбэком
- `writeJSON` перестал экранировать HTML-символы и перестал глушить отказ
записи: дословность содержимого точки иначе не удерживается, а оборванное
тело уходило под видом успешного `200`
7.3 KiB
Ответ точек несёт измеренный род и его применимость к отданному ряду
- Дата: 2026-08-04
- Источник: openspec/changes/archive/2026-08-04-tochki-metriki-za-period/design.md
Решение
Конверт ответа маршрута точек несёт aggregation объектом
{style, applicable, last_hour}, а не строкой с применённой свёрткой:
style— измеренный род метрики, тот же словарь и то же имя, что у каталога;applicable— применим ли объявленный род к отданному ряду;last_hour— ярлык самого свежего часа окна измерения.
docs/architecture.md до этого изменения обещал "aggregation": "sum" —
строку. Решение её пересматривает: строка называет применённое и молчит об
основании.
Отвергнуто и названо поимённо: поле applied с именем применённой свёртки
(выводится из style и bucket тем же инвариантом; как строка неверно
описывает свёртку мгновенной метрики, у которой по архитектуре «среднее с
min/max рядом»); полное основание каталога (hours, compared, agreeing,
conflicting, first_hour) в конверте точек — второй экземпляр факта, обязанный
сходиться с первым.
Почему
Род есть свойство метрики, а слой — свойство ряда, и их сочетание бывает
опасным. Конверт {"layer": "raw", "style": "cumulative"} законен и штатен:
правило выбора слоя при равном охвате предпочитает самый мелкий. Инвариант
«нижний слой HAE не суммируется никогда» система соблюдает, ничего не складывая,
— но потребитель об инварианте не знает, а сумма по нижнему слою завышает втрое
(находка 34 разведки). Разрыв построен проходом review-rubric на предложении,
до кода:
Конверт
{"layer": "raw", "aggregation": {"style": "cumulative"}}законен, штатен — и он прямо приглашает главного потребителя (агента с ограниченным контекстом) сложить ряд самому. Система при этом свёртки не делает, инвариант формально цел; результат у потребителя завышен, а решение по нему уже принято.
applicable: false — та самая оговорка, которая едет вместе с данными.
last_hour — единственный след замершего окна. Род считается по 48 самым
свежим общим часам, а не по последним 48 часам календаря: выключенная
минутная автоматизация HAE останавливает пополнение общих часов, окно замирает и
продолжает объявлять род.
Литература расколота, и обе стороны названы. Род вместе с данными:
Google Cloud Monitoring объявляет metricKind и valueType в каждом объекте
TimeSeries ответа, а не только в дескрипторе метрики; CloudWatch
GetMetricData кладёт StatusCode (Complete / PartialData) рядом с рядом —
оговорка едет с данными, а не оставляется клиенту на вывод; Home Assistant
statistics_during_period держит start и end в ответе всегда,
независимо от запрошенных types. Род отдельно от данных: Prometheus отдаёт
{resultType, result} без единого слова о типе, а тип живёт в
/api/v1/metadata; Graphite render не объявляет ничего. Второе отвергнуто по
измеримой причине: клиент обязан сделать второй запрос, а до тех пор не
отличает «род известен» от «род не измерен», — и согласованности между двумя
ответами всё равно нет, потому что род есть функция окна, а окно едет с
часами. Принцип HealthKit HKStatistics («род не тот — свёртки нет») взят,
механизм неприменим: у нас стиль источником не объявлен.
Последствия
+Потребитель видит не только число, но и на каком основании его можно сворачивать, без второго запроса и без знания инвариантов проекта.+Форма объявлена до того, как её скопируют свёртка по сетке, порог неполного ведра, тренировки, записи и MCP. После копирования это была бы не развилка, а археология.−Полеapplicableизбыточно по построению: клиент, знающий правило «нижний слой HAE не суммируется», вывел бы его изstyleиlayer. Взято сознательно — правило принадлежит нам, и молчаливо перекладывать его на потребителя дороже, чем поле.−Чтобы разобрать, почему родunknown, придётся спросить каталог: полное основание живёт там в одном экземпляре.−Род в конверте точек и род в каталоге считаются в разные моменты и у клиента, сравнивающего два ответа, могут разойтись. Это свойство измерения, а не дефект; ровно поэтомуlast_hourедет вместе с родом.
Открыто, решает владелец
Машинно-различимый код причины отказа. Тело отказа несёт только
человекочитаемую строку, и агент не отличит «зона не указана» от «слой
незнаком» иначе, чем разбором русского текста. Правило общее для всех маршрутов
и меняет errorWire, то есть и контракт приёма, — сюда не взято.