httpapi: точки метрики за период отдаются одним запросом

- `GET /api/v1/metrics/{name}?from&to&layer` — ряд точек за период; конверт
  объявляет слой, измеренный род, его применимость к отданному ряду и границу
  окна измерения, а сам ряд собирается из одного слоя, выбранного по охвату
  точек внутри периода
- use-case вынесен в `internal/points`, чтение — одним входом `store.ReadSeries`
  под одной транзакцией; правило выбора слоя остаётся в домене и приходит в
  хранилище колбэком
- `writeJSON` перестал экранировать HTML-символы и перестал глушить отказ
  записи: дословность содержимого точки иначе не удерживается, а оборванное
  тело уходило под видом успешного `200`
This commit is contained in:
av
2026-08-04 18:46:45 +03:00
parent b819b77f62
commit 29ca8d415c
36 changed files with 4721 additions and 58 deletions
+43
View File
@@ -142,6 +142,13 @@
**Перестали проверять сознательно.**
- **Шаг покрытия диффа гейт не красит.** `CLAUDE.md` объявляет, что непокрытая
изменённая строка красит гейт безусловно; `scripts/diff-coverage.py` всегда
возвращает `0`, и шаг печатает `OK` при любом покрытии. То есть «гейт зелёный»
не означает «покрытие диффа полное», и разбор непокрытых строк остаётся
человеку или проходу. Найдено проходом `gate` 2026-08-04, подтверждено
триажем; чинить нельзя мимоходом — починка немедленно красит гейт задачи, в
которой её сделали.
- Прогон живого архива (`task verify:archive`) и свёртка под удерживаемой
блокировкой (`task verify:busy`) в гейт не входят: минута и около 50 секунд
соответственно, плюс данные, которых нет ни на какой другой машине. Гоняет их
@@ -158,6 +165,42 @@
Запись на каждый воспроизведённый дефект, сразу, а не ретроспективно: со
временем теряется не факт, а причина непоймания.
## 2026-08-04 — правило выбора слоя мерило одно, а отбор шёл по другому [пойман]
**Что было.** Правило выбора слоя ответа Read API мерило охват **часами
объектов**, а ряд отбирался **точной меткой точки**. На периоде короче часа
множества расходятся: часовой объект попадает в границы часов запроса, а его
единственная точка в период не попадает. Ответ уходил бы пустым при непустых
данных соседнего слоя — с непустым `layer`, то есть неотличимо от честной
пустоты только по числу точек.
**Почему поймано.** Профиль `design` на предложении, до кода: и `review-specs`,
и `review-rubric` построили один и тот же вход независимо друг от друга
(`from = 10:30`, `to = 10:45`). На готовом коде находка стоила бы переписывания
выборки; на предложении — абзаца.
**Что сделано.** Охват меряется метками точек (`first_ts`/`last_ts` уже лежат в
покрывающем индексе). Класс промоутнут в
`docs/conventions/storage.md` — «предикат выбора источника и предикат отбора
данных используют одну границу»: он повторится всюду, где огрубление ради
полноты выборки соседствует с точным фильтром.
## 2026-08-04 — чекпоинт, заведённый ревью, не существовал бы в проде [пойман]
**Что было.** Враждебный проход построил путь «ответ оборвался по `WriteTimeout`
на середине, а `accessLog` написал `200`»: тело в 13 МиБ доехало на 2.7 МиБ,
клиент получил нечитаемый JSON, лог сообщил успех. Чекпоинт об обрыве завели —
и поставили ему уровень `DEBUG`.
**Почему поймано.** Эксплуатационный проход прочитал **боевой** конфиг
(`config.docker.toml`, `level = "info"`) и показал, что запись уровня `DEBUG`
не проходит фильтр `slog` никогда. То есть находка была закрыта наблюдаемостью,
которой в проде не существует.
**Что сделано.** Уровень поднят до `WARN`. Правило, которое из этого следует:
**уровень нового чекпоинта сверяется с боевым конфигом, а не с тем, что видно в
тестах** — в тестах уровень всегда `DEBUG`.
Реализованные задачи, находки ревью и решения сюда не пишутся: у них есть
коммит, спека и задача. Здесь только промахи конвейера и решения о его составе.