- `GET /api/v1/metrics/{name}?from&to&layer` — ряд точек за период; конверт
объявляет слой, измеренный род, его применимость к отданному ряду и границу
окна измерения, а сам ряд собирается из одного слоя, выбранного по охвату
точек внутри периода
- use-case вынесен в `internal/points`, чтение — одним входом `store.ReadSeries`
под одной транзакцией; правило выбора слоя остаётся в домене и приходит в
хранилище колбэком
- `writeJSON` перестал экранировать HTML-символы и перестал глушить отказ
записи: дословность содержимого точки иначе не удерживается, а оборванное
тело уходило под видом успешного `200`
- доменные типы internal/catalog лишились json-тегов и MarshalJSON; типы
metricWire/layerWire/aggregationWire и перевод catalogWire живут в транспорте,
тело отказа тоже получило объявленный тип — байты ответа не изменились
- заведён сторож: обход графа типов ответа утверждает, что домен не доезжает до
сериализации, плюс требование json-тега на полях транспортных структур и
заведомо красные случаи к обоим правилам
- решение с ценой обеих сторон записано в architecture.md и ADR; шаг lint в
гейте получил свой кеш — общий на машину красил прогон находками из чужого
worktree
- рядом с воркером свёртки живёт горутина, раз в минуту разбирающая журнал
пассивным чекпойнтом; «журнал не разбирается» видно строкой владельцу, а не
только по `df`. Признак — пара чисел, а не флаг занятости: тот молчит под
удерживаемым читателем (`busy=0` при 6256 страницах и пяти перенесённых), а
при занятой блокировке отдаёт `-1` вместо ответа, и `-1 >= -1` читалось бы как
«разобрано целиком»
- каталог отвечает `304` на `If-None-Match`, не открывая снимок витрины. Метка
собрана из всего, от чего зависит ответ: версии витрины (`data_version` с
закреплённого соединения плюс поколение — значение локально для соединения и
не переживает переоткрытия), горизонта измерения и области действия ресурса.
Версия снимается до и после сборки: снятая после пометила бы устаревший снимок
свежим номером
- предел и дедлайн ответа отложены в задачу Read API точек вместе с измеренной
ценой первого запроса; попутно починен флаки-тест чужой задачи, искавший
значение точки в сыром буфере записи лога
- род метрики выводится сверкой минутного слоя с часовым: часовое значение
сходится с суммой минутных — накопительная, со средним — мгновенная, иначе
`unknown` и свёртка не предлагается вовсе. На живом архиве (123 доставки,
31 метрика) 7 накопительных, 9 мгновенных, противоречащих часов ноль
- `GET /api/v1/metrics` под токеном чтения отдаёт единицы, слои с границами и
род вместе с основанием измерения; род нигде не хранится — он функция витрины,
а витрина функция журнала, устаревать в нём нечему
- миграция 00009: покрывающий индекс, чтобы каталог отвечал по учётным колонкам,
не разжимая содержимое объектов
- Очередью служит сама таблица: доставка ждёт свёртки в статусе `pending`,
канал несёт только бит «есть работа». Переполнять нечего, падение процесса
очередь не теряет, а подбор `pending` при старте — обычный проход воркера, а
не отдельный код. Классификация исхода общая с пересборкой журнала.
- Исход разбора начал отражать доставку, а не обстоятельства: отмена и
занятость базы статус не меняют (иначе конкуренция за базу выводила бы
доставку из очереди навсегда), паника свёртки больше не валит процесс, а
учёт доставки идёт через транзакцию с повторами.
- Длинный бюджет ответа выдан маршруту приёма, а не всему серверу:
`write_timeout` в Go покрывает и чтение тела, и общий подъём снял бы защиту с
остальных маршрутов.
Триаж свёл 62 сырые находки девяти проходов к 33 причинам: 3 блокера,
4 «сейчас», 2 развилки. Все закрыты регрессионными тестами.
- схема точки сна определяется по самой точке, а не по индексу в исходном
массиве: одна пропущенная точка меняла эпизод и сводку местами
- доставка сворачивается одной транзакцией: частичное состояние было
недетерминированным (восемь прогонов — семь состояний)
- граница размера на распакованном теле: 400 КиБ gzip разворачивались в
400 МиБ мимо max_body_mb
- столкновение — расхождение канонических форм, а не байтов; WARN с
координатами объекта; payload без HTML-экранирования
- выравнивание по местной метке: получасовые зоны уводили часовую выгрузку
в minute
- доставка из одних суточных сводок больше не отвергается целиком
- единицы не переписываются молча; счётчик считает сохранённые точки
- все выходы Fold логируются, исход пишется на переживающем отмену контексте
Четыре развилки вынесены блокерами в беклог.
- internal/fold — свёртка по идентификатору доставки, тело из архива: тот же
код, каким пойдёт пересборка витрины
- приём зовёт свёртку на context.WithoutCancel с собственным дедлайном;
исход разбора на код ответа не влияет
- слой доставки хранится в delivery.derived_layer и наследуется от
ПРЕДШЕСТВУЮЩЕЙ доставки автоматизации: без границы по времени свёртка
переставала быть функцией от префикса журнала (1737 объектов против 1742)
- task verify:archive — сходимость на живом архиве, 99 доставок из 99
- POST /api/v1/ingest: токен, лимит тела, gzip, durable-запись тела в сырой
архив, учёт доставки в SQLite
- код ответа отражает доставку, а не разбор: битый JSON — 400, непонятое
содержимое — 200, данные уже сохранены и доразберутся позже
- сохраняется полный набор заголовков запроса с вычисткой секретов по имени
и по совпадению значения с токеном