## 1. Схема - [x] 1.1 Миграция `00009_bucket_catalog.sql` — покрывающий индекс `bucket(metric, layer, hour_utc, first_ts, last_ts, points, units)` - [x] 1.2 Обновить `docs/database.md`: индекс и зачем он ## 2. Хранилище - [x] 2.1 `store.ReadCatalog(ctx, store.CatalogWindow)` — весь вход каталога **одной транзакцией чтения**: разрезы всех метрик, общие часы пары слоёв по каждой метрике, объекты окна обоих слоёв - [x] 2.2 Разрезы — одним запросом `metric, layer, units, min(first_ts), max(last_ts), sum(points)`, группировка вместе с единицами (расхождение видно, а не выбирается молча) - [x] 2.3 Общие часы — самые свежие часы с объектами обоих слоёв, от свежих к старым, не больше `window` - [x] 2.4 Объекты окна — **пакетом**, один запрос на метрику на оба слоя (`hour_utc IN (…)`), а не по объекту за раз - [x] 2.5 Тест: разрезы и общие часы отвечают по индексу (`EXPLAIN QUERY PLAN` не содержит обхода таблицы) - [x] 2.6 Тест: число обращений к базе на метрику не зависит от размера окна - [x] 2.7 Тест: `CommonHours` отдаёт не больше `window` и именно свежие часы ## 3. Значение точки - [x] 3.1 `hae.PointValue(raw)` — число точки: `qty`, при его отсутствии `Avg`; разбор через `*json.Number`, **ноль — значение**, отсутствие ключа и `null` — нет значения, нечисловое и не влезающее в `float64` (`ErrRange`, `±Inf`) — нет значения - [x] 3.2 В док-комментарии сказать, что словарь пустоты `canon` сюда не применяется, и почему - [x] 3.3 Тесты на реальных формах точки из `testdata`: `heart_rate` с `Min`/`Avg`/`Max` без `qty`, `{"qty":0}`, точка без числового поля, `1e400` ## 4. Измерение рода - [x] 4.1 Пакет `internal/catalog`: тип рода (`cumulative`/`instant`/`unknown`, пустое значение невыразимо) и основание измерения (`hours`/`compared`/`agreeing`/`conflicting`/границы окна) - [x] 4.2 Именованная константа допуска `1e-9` с измеренной ценой в комментарии и **один** предикат `close(a, b)`, которым выражены все три сравнения - [x] 4.3 Пригодность часа: ровно одна точка со значением у часового объекта, её метка совпадает с началом часа, ≥2 точек со значением у минутного, сумма отличима от среднего - [x] 4.4 Сумма минутных значений считается в порядке возрастания метки - [x] 4.5 Правило метрики: ≥3 согласных и 0 противоречащих, иначе `unknown` - [x] 4.6 Окно 48 самых свежих общих часов - [x] 4.7 Сборка каталога: разрезы из снимка + род из измерения; строки слоя, разошедшиеся единицами, схлопываются в один элемент, множество единиц метрики отсортировано - [x] 4.8 Чекпоинт `WARN` при `conflicting > 0`: имя метрики и числа основания, без значений точек - [x] 4.9 Тесты: накопительная, мгновенная, нулевой час, двухточечный часовой объект, одноточечный минутный, невыровненная часовая метка, противоречие, единственный слой, только слой `day`, история длиннее окна, разошедшиеся единицы, идемпотентность двух вызовов ## 5. HTTP - [x] 5.1 Проверка токена **одна на оба контура**, параметризованная списком; схема строгая (`Bearer`), пустой список = выключено - [x] 5.2 Предупреждение на старте о выключенной проверке чтения - [x] 5.3 Редакция сохраняемых заголовков доставки чистит токены **обоих** контуров - [x] 5.4 `GET /api/v1/metrics` — форма ответа из дизайна: `style`, `hours`, `compared`, `agreeing`, `conflicting`, `first_hour`, `last_hour`; срезы пустые, а не nil; границы окна — указатели; порядок детерминирован - [x] 5.5 Тесты: 401 без токена, 401 с токеном приёма, 401 с голым значением без схемы, отдача при выключенной проверке, пустая витрина — **сравнением байтов** ответа с литералом - [x] 5.6 `config.example.toml` и `config.docker.toml`: `read_tokens` перестал быть заделом на будущее, цена пустого списка названа комментарием ## 6. Проверка на живом архиве - [x] 6.1 Прогон измерения в `internal/replay/archive_test.go`: свойства, а не числа — конфликтующих свидетельств ноль; накопительные и мгновенные метрики разошлись по родам; ни одна метрика не измерена по слою `raw` - [x] 6.2 Печать измеренного рода по метрикам и стоимости каталога в `t.Logf` - [x] 6.3 `task verify:archive` зелёный, отпечаток витрины не изменился ## 7. Документация и беклог - [x] 7.1 `docs/architecture.md`: метод измерения, окно, порог, допуск, почему род не хранится, почему родов два, а не четыре, где нужен `xFilesFactor` и какой у него подвох с полярностью - [x] 7.2 `docs/architecture.md`: форма каталога приведена к реализованной - [x] 7.3 `docs/local-research.md`: находка с результатом измерения на живом корпусе - [x] 7.4 Блокер «тай-брейк при равной полноте точек» в беклог, с вариантами, ценой и рекомендацией - [x] 7.5 Пометка в `docs/tasks/items/read-api-points.md`: порог неполного ведра, его полярность и предел размера ответа решаются там - [x] 7.6 Пометка в `docs/tasks/items/token-and-secret-management.md`: контуров теперь два - [x] 7.7 Убрать задачу из беклога, обновить индекс ## 8. Дозакрыто по ревью кода - [x] 8.0 Горизонт окна: часы позже `now + час` в сверку не входят, данные из будущего пишутся `WARN` - [x] 8.0 Единицы обеих сторон обязаны совпасть — иначе уверенный ложный род - [x] 8.0 Содержимое разжимается только у часов, прошедших отбор по учётным колонкам - [x] 8.0 Имя метрики в логе обрезано, множество единиц ограничено потолком - [x] 8.0 Метрика с пустым именем показывается, а не выбрасывается сентинелом - [x] 8.0 Отмена снаружи не пишется как сбой сервиса - [x] 8.0 Байтовый тест непустого ответа и повтора запроса ## 9. Приёмочные критерии (рубрика ревью предложения) - [x] 9.1 Вердикт — чистая функция состояния витрины и окна: не зависит от порядка строк SQL, порядка точек в объекте и момента вызова - [x] 9.2 Допуск назван величиной, один предикат на все сравнения, поведение около нуля объявлено - [x] 9.3 Вырожденные свидетельства исключены явно, кворум назван числом, ниже кворума исход — `unknown`, а не умолчание - [x] 9.4 `unknown` — исход первого класса, и его причины различимы клиентом без второго запроса - [x] 9.5 Измерение ничего не пишет и не кешируется скрытно - [x] 9.6 Стоимость ответа ограничена сверху и по числу запросов, и по числу прочитанных страниц; не растёт вместе с историей - [x] 9.7 HTTP-контракт полон: только `GET`, пустая витрина — 200 с пустым списком, авторизация до работы, токены и значения здоровья не в логах выше `DEBUG` - [x] 9.8 Ответ самоописателен: словарь слоёв тот же, что везде; границы объявляют, что метят; расхождение единиц показано, а не выбрано молча - [x] 9.9 Каталог отдаёт наблюдаемое, а не досчитанное; деталь хранения наружу не протекает - [x] 9.10 Нижний слой в сверке не участвует; `source` в измерение не входит - [x] 9.11 Смена вердикта наблюдаема чекпоинтом - [x] 9.12 Правило часа и правило метрики тестируются без БД; на живом архиве проверяются свойства, а не числа