Files
healthlog/openspec/changes/archive/2026-08-04-aktivnaya-proverka-novyh-sekcij/tasks.md
T
av bd5d17b079 первая встреча непокрытой секции стала наблюдаемым событием
- свёртка спрашивает журнал, встречалось ли имя строго раньше по паре
  (received_at, id), и пишет WARN с атрибутом uncovered_new; повторные молчат.
  Признак выводится, а не хранится — реестр был бы второй копией факта
- добавлена подкоманда `healthlog uncovered`: перечень накопленного, чтение
  только на чтение, экранированные имена и названные границы носителя
- синк документации: ADR о выводе новизны из журнала, две записи в журнал
  дефектов, два правила промоутом в конвенции, терминал оператора назван
  адресатом недоверенного входа
2026-08-04 13:39:48 +03:00

13 KiB
Raw Blame History

1. Хранилище: два запроса по существующей колонке

  • 1.1 store.SectionsSeenBefore(ctx, names, before DeliveryRef) (map[string]struct{}, error) — какие из имён встречались в доставках, стоящих в журнале строго раньше before; сравнение парой (received_at, id), чтение через json_each, EXISTS на имя (ранний выход для виденного), вне транзакции записи
  • 1.2 store.UncoveredSections(ctx, limit) — перечень: имя, число доставок, первая и последняя встреча (метка и идентификатор доставки), порядок по имени, предел строк с числом остатка; доставки всех статусов разбора
  • 1.3 Тесты хранилища: имя из более ранней доставки виденное, из более поздней — нет, своя же строка в счёт не идёт, доставки одной секунды разводятся идентификатором; перечень сходится с независимо посчитанным ожиданием; отказавшая доставка в перечень входит; предел вывода срабатывает
  • 1.4 Замер стоимости на синтетическом журнале в ./tmp (~105 тысяч доставок, годовой объём): время запроса для виденного и для нового имени; число печатается, метод замера записывается рядом с результатом

2. Свёртка: признак новизны и эскалация

  • 2.1 Проверка новизны сразу после разбора — до ветвления на успех и отказ, вне транзакции записи; берётся только при непустом списке непокрытых
  • 2.2 Отказ запроса не роняет свёртку и исходом доставки не становится: все имена считаются новыми, признак несостоявшейся сверки идёт в запись
  • 2.3 Новые имена в Stats и в атрибутах чекпоинта; ветвь WARN ставится первой в switch (решение кода — спека нормирует поведение, а не позицию)
  • 2.4 Новые имена доезжают до пути отказа (parseResiduefail) и печатаются его записью; отложенный исход их не называет
  • 2.5 Тесты свёртки: первая встреча даёт WARN с атрибутом новых секций, вторая доставка молчит, новое имя рядом с виденным, новая секция не маскируется перезаписью точек, отказ слоя называет новое имя, отложенный исход не называет, отказ сверки объявляет всё новым и не роняет свёртку

3. Команда перечня

  • 3.1 Подкоманда healthlog uncovered --config <path>: store.OpenForRead, человекочитаемый вывод в stdout, ненулевой код и причина на отказ открытия
  • 3.2 Имя печатается экранированным (%q); пустой перечень — отдельная строка и нулевой код; предел строк с числом остатка
  • 3.3 Регистрация подкоманды в main.go и в шапке пакета
  • 3.4 Тесты команды: перечень на базе с секциями, пустой перечень, имя с управляющим символом, базы по пути нет, имён больше предела

4. Документация

  • 4.1 docs/architecture.md — раздел «Неразобранные секции доставки» дополнен событием, командой и границами носителя
  • 4.2 docs/research/apple-health.md — перечень не виденных живьём секций сверен с множеством покрытых имён в internal/hae поимённо
  • 4.3 README.md / CLAUDE.md — команда названа там, где перечислены прочие подкоманды (если перечислены)

5. Приёмка

  • 5.1 task gate зелёный
  • 5.2 task verify:archive — повторный прогон живого архива даёт то же состояние (правило наблюдения затрагивает путь свёртки)
  • 5.3 Поведенческая верификация: сервис поднят, подсунута доставка с выдуманной секцией — ровно одна запись с атрибутом новых секций; вторая доставка молчит; healthlog uncovered на живой базе

6. Отработка ревью кода (профиль deep)

  • 6.1 Отказ слияния доносит новизну до записи об отказе: он пишет имя в учёт так же, как отказ разбора, и терял событие навсегда (найдено тремя проходами с оракулом). Тест TestFoldОтказСлиянияНазываетНовуюСекцию
  • 6.2 Вывод команды называет границы носителя в обоих исходах; пустая ветвь больше не говорит за весь поток
  • 6.3 Учётная запись с неразбираемым списком не роняет ни сверку, ни перечень: проверка стоит внутри аргумента json_each, а не условием в WHERE (порядок вычисления SQLite не обещает)
  • 6.4 Причина несостоявшейся сверки идёт в запись рядом с признаком
  • 6.5 Сообщение чекпоинта приведено к словарю: delivery folded, new uncovered section
  • 6.6 Число доставок считается count(DISTINCT d.id): обрезка имён по 64 байтам давала дубли внутри одной доставки
  • 6.7 Замер перемерян и исправлен: ранний выход есть только у давно приезжающей секции; у только что появившейся — почти полный проход. Числа живут в одном месте (design.md), код и architecture.md ссылаются
  • 6.8 Сквозной тест команды: перечень сходится с посчитанным по телам архива независимо от кода команды (оракул К3)

Критерии приёмки задачи

Из docs/tasks/items/unseen-sections-check.md; оракулы К1–К3 уточнены после ревью предложения — наблюдаемый признак события есть атрибут новых секций, а не присутствие имени в записи (имя непокрытой секции стоит в записи каждой доставки, которая её принесла, и так было до этого изменения).

  • К1 имя секции, которого разбор раньше не встречал, порождает событие уровня выше рутины — оракул: тест на доставке с выдуманной секцией, ровно одна запись уровня WARN с этим именем в атрибуте новых секций
  • К2 то же имя во второй доставке события больше не порождает — оракул: тот же тест на двух доставках подряд, у второй атрибут новых секций пуст
  • К3 список всего, что поток когда-либо приносил и разбор не покрыл, достаётся одной командой, без ручного SQL — оракул: перечень собран из тел internal/hae/testdata независимо от кода команды и сходится со строками её вывода (имя, число доставок, границы); дешёвой добавкой — сверка с SELECT DISTINCT по delivery.uncovered_sections на живой базе. Живая база проверку не пропустила по своей причине: её схема (версия 8) отстала от бинаря (10) — контейнер не пересобирался, — и OpenForRead строго отказал, как и заказано. Сверка сделана на поднятом сервисе с отдельной базой; на живой базе непокрытых секций сегодня нет вовсе (SELECT DISTINCT даёт пустое множество), так что сверять было бы нечего
  • К4 перечень не виденных живьём секций в docs/research/apple-health.md сходится с множеством покрытых имён в internal/hae — оракул: глазами, сверка двух списков поимённо
  • К5 повторный прогон живого архива даёт то же состояние — оракул: task verify:archive

Приёмочные критерии из рубрики ревью (профиль design)

Рубрика прохода review-rubric, порождённая до чтения предложения. Пункты, которые предложение не закрывало, отработаны правкой спеки и дизайна; здесь они остаются проверяемыми свойствами.

  • Р1 признак «впервые» есть функция префикса журнала: перестановка порядка свёртки не переносит событие с доставки на доставку
  • Р2 узел не читает состояние, которое сам же пишет в этом шаге: собственная строка исключена сравнением, а не порядком записи
  • Р3 идемпотентность и отсутствие второй копии факта: повторная свёртка даёт тот же состав имён, хранимого реестра нет
  • Р4 наблюдательный механизм не роняет поток: отказ сверки не меняет parse_status и не превращает pending в failed
  • Р5 запрос новизны выполняется вне транзакции записи; в транзакции записи обращений к delivery с json_each нет
  • Р6 стоимость названа числом с методом замера (задача 1.4), а не посылкой «список почти всегда пуст»
  • Р7 событие однократно на имя: сто доставок подряд с одним новым именем дают ровно одну запись повышенного уровня; уровень назван поимённо (WARN)
  • Р8 признак и перечень честны относительно носителя: обрезка списка, обнуление пересборкой и уход имени после покрытия названы в спеке
  • Р9 имя секции — недоверенный вход: экранировано на выводе, ограничено по длине разбором, данных о здоровье в выводе нет
  • Р10 CLI читает и только читает: без миграций и записи, расхождение версии схемы — явный отказ
  • Р11 вывод детерминирован, ограничен пределом и отличает пустоту от отказа
  • Р12 оракулы не пришпилены к числу, производному от размера корпуса, и не тавтологичны (К3 перестроен от тел архива)