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

139 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. Хранилище: два запроса по существующей колонке
- [x] 1.1 `store.SectionsSeenBefore(ctx, names, before DeliveryRef) (map[string]struct{}, error)`
какие из имён встречались в доставках, стоящих в журнале строго раньше
`before`; сравнение парой `(received_at, id)`, чтение через `json_each`,
`EXISTS` на имя (ранний выход для виденного), вне транзакции записи
- [x] 1.2 `store.UncoveredSections(ctx, limit)` — перечень: имя, число доставок,
первая и последняя встреча (метка и идентификатор доставки), порядок по
имени, предел строк с числом остатка; доставки всех статусов разбора
- [x] 1.3 Тесты хранилища: имя из более ранней доставки виденное, из более
поздней — нет, своя же строка в счёт не идёт, доставки одной секунды
разводятся идентификатором; перечень сходится с независимо посчитанным
ожиданием; отказавшая доставка в перечень входит; предел вывода срабатывает
- [x] 1.4 Замер стоимости на **синтетическом** журнале в `./tmp` (~105 тысяч
доставок, годовой объём): время запроса для виденного и для нового имени;
число печатается, метод замера записывается рядом с результатом
## 2. Свёртка: признак новизны и эскалация
- [x] 2.1 Проверка новизны сразу после разбора — до ветвления на успех и отказ,
**вне транзакции записи**; берётся только при непустом списке непокрытых
- [x] 2.2 Отказ запроса не роняет свёртку и исходом доставки не становится: все
имена считаются новыми, признак несостоявшейся сверки идёт в запись
- [x] 2.3 Новые имена в `Stats` и в атрибутах чекпоинта; ветвь `WARN` ставится
первой в `switch` (решение кода — спека нормирует поведение, а не позицию)
- [x] 2.4 Новые имена доезжают до пути отказа (`parseResidue``fail`) и
печатаются его записью; отложенный исход их не называет
- [x] 2.5 Тесты свёртки: первая встреча даёт `WARN` с атрибутом новых секций,
вторая доставка молчит, новое имя рядом с виденным, новая секция не
маскируется перезаписью точек, отказ слоя называет новое имя, отложенный
исход не называет, отказ сверки объявляет всё новым и не роняет свёртку
## 3. Команда перечня
- [x] 3.1 Подкоманда `healthlog uncovered --config <path>`: `store.OpenForRead`,
человекочитаемый вывод в stdout, ненулевой код и причина на отказ открытия
- [x] 3.2 Имя печатается экранированным (`%q`); пустой перечень — отдельная
строка и нулевой код; предел строк с числом остатка
- [x] 3.3 Регистрация подкоманды в `main.go` и в шапке пакета
- [x] 3.4 Тесты команды: перечень на базе с секциями, пустой перечень, имя с
управляющим символом, базы по пути нет, имён больше предела
## 4. Документация
- [x] 4.1 `docs/architecture.md` — раздел «Неразобранные секции доставки»
дополнен событием, командой и границами носителя
- [x] 4.2 `docs/research/apple-health.md` — перечень не виденных живьём секций
сверен с множеством покрытых имён в `internal/hae` поимённо
- [x] 4.3 `README.md` / `CLAUDE.md` — команда названа там, где перечислены
прочие подкоманды (если перечислены)
## 5. Приёмка
- [x] 5.1 `task gate` зелёный
- [x] 5.2 `task verify:archive` — повторный прогон живого архива даёт то же
состояние (правило наблюдения затрагивает путь свёртки)
- [x] 5.3 Поведенческая верификация: сервис поднят, подсунута доставка с
выдуманной секцией — ровно одна запись с атрибутом новых секций; вторая
доставка молчит; `healthlog uncovered` на живой базе
## 6. Отработка ревью кода (профиль `deep`)
- [x] 6.1 Отказ **слияния** доносит новизну до записи об отказе: он пишет имя в
учёт так же, как отказ разбора, и терял событие навсегда (найдено тремя
проходами с оракулом). Тест `TestFoldОтказСлиянияНазываетНовуюСекцию`
- [x] 6.2 Вывод команды называет границы носителя в **обоих** исходах; пустая
ветвь больше не говорит за весь поток
- [x] 6.3 Учётная запись с неразбираемым списком не роняет ни сверку, ни
перечень: проверка стоит внутри аргумента `json_each`, а не условием в
`WHERE` (порядок вычисления SQLite не обещает)
- [x] 6.4 Причина несостоявшейся сверки идёт в запись рядом с признаком
- [x] 6.5 Сообщение чекпоинта приведено к словарю: `delivery folded, new
uncovered section`
- [x] 6.6 Число доставок считается `count(DISTINCT d.id)`: обрезка имён по 64
байтам давала дубли внутри одной доставки
- [x] 6.7 Замер перемерян и исправлен: ранний выход есть только у давно
приезжающей секции; у только что появившейся — почти полный проход.
Числа живут в одном месте (`design.md`), код и `architecture.md` ссылаются
- [x] 6.8 Сквозной тест команды: перечень сходится с посчитанным по **телам**
архива независимо от кода команды (оракул К3)
## Критерии приёмки задачи
Из `docs/tasks/items/unseen-sections-check.md`; оракулы К1–К3 уточнены после
ревью предложения — наблюдаемый признак события есть **атрибут новых секций**, а
не присутствие имени в записи (имя непокрытой секции стоит в записи каждой
доставки, которая её принесла, и так было до этого изменения).
- [x] К1 имя секции, которого разбор раньше не встречал, порождает событие
уровня выше рутины — оракул: тест на доставке с выдуманной секцией, ровно
одна запись уровня `WARN` с этим именем **в атрибуте новых секций**
- [x] К2 то же имя во второй доставке события больше не порождает — оракул: тот
же тест на двух доставках подряд, у второй атрибут новых секций пуст
- [x] К3 список всего, что поток когда-либо приносил и разбор не покрыл,
достаётся одной командой, без ручного SQL — оракул: перечень собран из
**тел** `internal/hae/testdata` независимо от кода команды и сходится со
строками её вывода (имя, число доставок, границы); дешёвой добавкой —
сверка с `SELECT DISTINCT` по `delivery.uncovered_sections` на живой базе.
**Живая база проверку не пропустила по своей причине:** её схема (версия 8)
отстала от бинаря (10) — контейнер не пересобирался, — и `OpenForRead`
строго отказал, как и заказано. Сверка сделана на поднятом сервисе с
отдельной базой; на живой базе непокрытых секций сегодня нет вовсе
(`SELECT DISTINCT` даёт пустое множество), так что сверять было бы нечего
- [x] К4 перечень не виденных живьём секций в `docs/research/apple-health.md`
сходится с множеством покрытых имён в `internal/hae` — оракул: глазами,
сверка двух списков поимённо
- [x] К5 повторный прогон живого архива даёт то же состояние — оракул:
`task verify:archive`
## Приёмочные критерии из рубрики ревью (профиль `design`)
Рубрика прохода `review-rubric`, порождённая до чтения предложения. Пункты,
которые предложение не закрывало, отработаны правкой спеки и дизайна; здесь они
остаются проверяемыми свойствами.
- [x] Р1 признак «впервые» есть функция префикса журнала: перестановка порядка
свёртки не переносит событие с доставки на доставку
- [x] Р2 узел не читает состояние, которое сам же пишет в этом шаге:
собственная строка исключена сравнением, а не порядком записи
- [x] Р3 идемпотентность и отсутствие второй копии факта: повторная свёртка даёт
тот же состав имён, хранимого реестра нет
- [x] Р4 наблюдательный механизм не роняет поток: отказ сверки не меняет
`parse_status` и не превращает `pending` в `failed`
- [x] Р5 запрос новизны выполняется вне транзакции записи; в транзакции записи
обращений к `delivery` с `json_each` нет
- [x] Р6 стоимость названа числом с методом замера (задача 1.4), а не посылкой
«список почти всегда пуст»
- [x] Р7 событие однократно на имя: сто доставок подряд с одним новым именем
дают ровно одну запись повышенного уровня; уровень назван поимённо (`WARN`)
- [x] Р8 признак и перечень честны относительно носителя: обрезка списка,
обнуление пересборкой и уход имени после покрытия названы в спеке
- [x] Р9 имя секции — недоверенный вход: экранировано на выводе, ограничено по
длине разбором, данных о здоровье в выводе нет
- [x] Р10 CLI читает и только читает: без миграций и записи, расхождение версии
схемы — явный отказ
- [x] Р11 вывод детерминирован, ограничен пределом и отличает пустоту от отказа
- [x] Р12 оракулы не пришпилены к числу, производному от размера корпуса, и не
тавтологичны (К3 перестроен от тел архива)