- каждая запись каталога задач получила тип вместо тега kind: и префикса
заголовка; секция роадмапа «Разработка» стала «Сопровождением», порядок
секций канонический
- поправлены протухшие факты: нереализованные маршруты Read API, MCP и
`healthlog import`, словарь слоёв в инварианте, семантика гейта по покрытию
диффа, периметр перестал дублировать security.md
- замер слияния переведён с находки 49 на находку 54, заполнены Purpose спек
storage и parsing
- роадмап отвечает «что умеет и чего не умеет»: PLAN.md → ROADMAP.md, четыре
канонические секции, достигнутые звенья строками в «Готово», цели
переформулированы возможностями приложения
- задачи: род работы и «Затрагивает» набору спринта, 34 заголовка в форму
действия, «Завершение» целей перечнями со ссылкой из каждой задачи
- вычитка проходами task-form и doc-wording, починены протухшие факты в README,
паспорте и review.md
- `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
- конверт и точки разложены на форму провода, точки за период и условный
запрос; свёртка — на сетку, порог неполного ведра и предел размера ответа;
тренировки и записи разъехались на два независимых маршрута
- openapi-swagger разложена на спеку, гейт против расхождения и Swagger UI —
все три вне набора, вместе с mcp-server
- набор спринта 2026-08-04 — весь HTTP-слой чтения, восемь задач
- цель parsing-and-storage закрыта по своему критерию; незакрываемый остаток
(новые формы от источника, ручные секции задним числом) переехал в тему
parsing-completeness
- цель mcp поглощена целью read-api, переименованной в «Чтение данных
клиентами»: адаптер — последний шаг того же направления, а не своё
- read-api-points разложена на конверт с точками, свёртку по сетке и
тренировки с записями; спринт 2026-08-04 набран пятью задачами
- свёртка спрашивает журнал, встречалось ли имя строго раньше по паре
(received_at, id), и пишет WARN с атрибутом uncovered_new; повторные молчат.
Признак выводится, а не хранится — реестр был бы второй копией факта
- добавлена подкоманда `healthlog uncovered`: перечень накопленного, чтение
только на чтение, экранированные имена и названные границы носителя
- синк документации: ADR о выводе новизны из журнала, две записи в журнал
дефектов, два правила промоутом в конвенции, терминал оператора назван
адресатом недоверенного входа
- пометка — одна строка на диапазон (метрика + слой + период), а не провенанс
на точку: вопрос диапазонный, а поле у точки стоит того объёма, который
устаревание нижнего слоя и приходит экономить
- провенанс на точку отвергнут по цене, а не по ненадобности — различие
записано, чтобы решение пересмотрели при появлении потребителя
- пометку выставляет импорт экспорта, по проверенному прогону; stateOfMind не
получает её никогда — его в экспорте Apple нет
- на master после ff-слияния база диффа равна HEAD, все go-шаги пропускаются,
и вакуумный прогон выглядит зелёным
- тот же прогон с явной базой нашёл красный lint
- CLAUDE.md велит класть черновое и временное в ./tmp, но линтер про это не знал:
диагностическая программа и worktree батча красили гейт
- краснота по причине, не связанной с изменением, приучает не читать красноту
- байтовый порядок канонических форм остался тай-брейком только внутри одной
доставки: на живом корпусе он решал 98,8% спорных координат и системно хранил
меньшее значение, из-за чего step_count терял род и verify:archive был красным
- правило перестало быть коммутативным осознанно, поэтому порядок свёртки
приведён к журнальному: проход воркера прекращается на отложенной доставке,
а свёртка вне порядка журнала пишет WARN
- заведены счётчики PointsHeld и PointsErased — удержание полнотой и
единственное направление, в котором правило теряет содержание
- первый шаг — замер: в 1 022 координатах, где полноту решило превосходство
полей, была ли более полная точка более поздней; от исхода ветвится всё
- «экспорт — источник правды» ограничено двумя рамками: по дате снапшота и по
типам, которых в экспорте нет вовсе (stateOfMind)
- байтовый тай-брейк решает 98,8% спорных координат из 84 978: step_count
потерял род, потому что сохранённая точка выиграла у более поздней
- вариант (б) «брать бо́льшее» снят, взято «пришедшая побеждает сохранённую»;
оно не полурешётка, и задача обязана доказать равенство пересборки приёму
- фазы сна, контекст пульса и имена тренировок попадают в реестр
`category_value` (миграция 00010): строка хранится дословно, выведенный код
лежит рядом отдельной записью, а не полем внутри точки
- словарь и синонимы кодов живут в бинаре (`internal/healthkit`); локаль из
`Accept-Language` сужает поиск, но в ключ реестра не входит — заголовков в
сыром архиве нет
- наблюдение входит в отпечаток витрины, выведенный код — нет: он производная
от словаря, а не от журнала
Миграция по записи «Версия 2» журнала канона: поля Дата и Источник в
docs/adr/template.md жирным, объявлено место под статус (- **Статус:**
заменено на ADR-… либо устарело), та же строка добавлена в «Соглашения»
docs/adr/README.md, docs/.pm.json переведён на canon 2.
Переносить статусы было не из чего: записей ADR в проекте пока нет, каталог
несёт только README и шаблон.
docs.py check зелёный, версия проекта сошлась с версией скрипта.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- открытых вопросов не осталось: три решения владельца доведены до берущегося
вида, по entity-without-parsed-label принято хранить с NULL-меткой после Read API
- unseen-sections-check сжата до остатка — активная проверка появления секции;
разбор невиденных секций из неё вынут, вслепую он не пишется
- спринт под целью parsing-and-storage: categorical-value-dictionary и
unseen-sections-check, обеим написаны критерии приёмки с оракулами
- три задачи с решением от 2026-08-02 сохраняли тег question и непустой раздел
«Вопросы», то есть sprint take отказал бы их взять
- там же названы два числа сессии: ориентир 5–8 задач ничем не замерян, а отбор
по --stale слеп, пока у каталога нет собственной истории правок
49 файлов, миграция сделана командой tasks.py check --fix — той самой, ради
которой в скрипте оставлена читаемость старой формы. Побочно тот же прогон
проставил тег decomposed целям, у которых есть задачи: это его штатная работа.
check после миграции зелёный, индексы согласованы.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Три документа — CLAUDE.md, паспорт и openspec/config.yaml — велели писать
причину отвергнутого решения в architecture.md. По канону дом «почему» это
design.md изменения и промоут в docs/adr/, а architecture.md переезд как раз
опустошает: обоснования шли ровно туда, откуда их вычищают.
Раздел «Процесс» в CLAUDE.md пересказывал шаги пайплайна дословно — тот же
второй дом, что уже вычищен из config.yaml. Осталось три вещи, которые
действительно проектные: автономность, prior art, «поток не останавливается».
config.yaml пересказывал паспорт и инвариант безопасности — стали ссылками.
docs/review.md ссылался на healthlog-review-rubric и healthlog-task-pipeline,
удалённые вместе с проектными копиями. Первое — указание на будущее, поэтому
исправлено на проходы rubric и ops; второе оставлено историей с пометкой.
README.md называл architecture.md домом «принятых решений» и не упоминал
database.md и adr/ вовсе.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- беклог и план переехали в docs/tasks (38 задач, 11 целей), слаги
переименованы с транслита на английские, 85 ссылок поправлены
- conventions.md разобран в docs/conventions/, local-research.md — в
docs/research/, review-journal.md — в docs/review.md с разделом настройки
конвейера; заведены security.md, adr/ и .pm.json
- шаг docs.py check добавлен в task gate; поведение в architecture.md помечено
девятью маркерами долга, database.md получил настройки с числовым значением
- девять агентов healthlog-review-* и скиллы healthlog-{review,task}-pipeline
переехали в плагины av-dev-pipeline и av-dev-pm
- плагины av-dev-git, av-dev-pm, av-dev-pipeline включены в settings.json
- параллельно гоняем только по явной просьбе И с явно названным набором
проходов или стадии; просьба без набора основанием не считается
- обосновывается отступление, а не умолчание
- пару adversary + ops не параллелим даже по просьбе без переспроса:
оба меряют одно железо, испорченный оракул хуже отсутствующего
- параллельный прогон объявляется в отчёте с составом, а замеры из него
идут в границы покрытия как ослабленные
- профиль отвечает «какие проходы», режим — «как их запускать»
- явная просьба владельца — достаточное основание, без обоснований
и переспрашивания; перекрывает эвристики в обе стороны
- главный собственный повод — ожидаемые замеры: adversary и ops меряют
одно железо (блокировка SQLite, куча, рост -wal) и портят числа друг
другу, а находка с испорченным оракулом дороже сэкономленных минут
- декорреляция не меняется ни в каком режиме: проход не видит чужих
находок, «последовательно» ≠ «читает предыдущего»
- ранний выход только при переделке формы изменения, с перезапуском
с нулевой стадии; триаж — только на полном прогоне
- эксперимент против поведения stdlib, драйвера и PRAGMA — обязательный
вопрос 8 у ops, с прецедентом «-1 >= -1» и оговоркой про data_version
- «не изобретаем ли то, что уже есть в библиотеке» — вопрос 1 у architecture,
с перечнем конструкций stdlib
- потеряна поимённая сверка с Effective Go и стайлгайдами: класс обратимый,
но теперь не покрыт вовсе — записано в журнал ревью
- профили: quick 4, standard 6, deep 7–8, design 3
- negative удалён, два его живых вопроса переселены в ops и architecture
- rubric остаётся только в профиле design, reimpl — по триггеру
«новое правило слияния, идентичности или разбора»
- adversary и ops переехали из deep-только в standard: профиль, которым
закрывается большинство задач, гонял четыре самых слабых прохода и не
гонял двух, принёсших почти все находки сессии
- idiom оставлен вопреки первоначальной оценке: он зарабатывает
экспериментами против stdlib и драйвера, а не цитатами из гайдов
- основания и цена решения — в docs/review-journal.md
- рядом с воркером свёртки живёт горутина, раз в минуту разбирающая журнал
пассивным чекпойнтом; «журнал не разбирается» видно строкой владельцу, а не
только по `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: покрывающий индекс, чтобы каталог отвечал по учётным колонкам,
не разжимая содержимое объектов
- порядок журнала при конкурентных приёмах: повторы, но после /stats;
до тех пор живём с записанным в спеке пределом
- откат релиза после наката миграции: копия файла базы перед накатом,
Down-блоки честно названы декорацией для локальной разработки
- Правило покрытия получило второй разряд (условный, как у точек), запрет
вырождения формы и счёт содержательных элементов ряда: скелет из скаляров и
ряд из null больше не затирают маршрут. Победитель внутри доставки стал
функцией множества версий — общим помощником с точками, — а провенанс
поднимается и при совпавшем хеше, иначе отложенная доставка возвращала витрину
к прежнему содержимому.
- Одно поле не того типа больше не уносит сущность, а пропуски видны в учётной
записи доставки (миграция 00008, NULL = «не измерялось»); каноническая форма
считается один раз и вне транзакции; откат бинаря поверх новой схемы отказывает
на старте; текст ошибки разбора не несёт значений из тела.
- Ревью кода профилем deep (девять проходов) нашло две регрессии и обе закрыты:
безусловный второй разряд запирал законный досчёт навсегда, а выбор победителя
был квадратичен по числу присланных версий одного ключа.
- три пропущенных прохода дозапущены, триаж оставил семь причин
- обе развилки разобраны по уже записанным решениям проекта:
второй разряд Covers как у Relate, мягкое чтение полей как у Duration
- уточнена формулировка в architecture.md про цену покрытия секции
- секции `workouts` и `stateOfMind` покрыты разбором: тренировка лежит одной
строкой вместе с маршрутом и внутренними рядами, запись — по ключу `род + id`;
миграция 00007 заводит обе таблицы и возвращает в очередь `partial`-доставки
с этими ключами
- сущность заменяется целиком, но условно: приехавшая побеждает, если не теряет
содержания сохранённой (множество ключей и длины верхнеуровневых массивов), а
при равном содержании выигрывает версия из более поздней доставки ЖУРНАЛА —
«побеждает приехавшая» было бы функцией порядка свёртки, и живая витрина
расходилась бы с пересборкой молча
- отпечаток витрины покрывает тренировки и записи и снимается одним снимком
базы; отчёт `reindex` считает «было и стало» по каждой единице хранения
- предел на размер и число заголовков доставки: спит до деплоя
- сверка живой витрины с пересборкой: отпечатки печатаются, но
сравнивать их некому — известный путь расхождения записан блокером
- Очередью служит сама таблица: доставка ждёт свёртки в статусе `pending`,
канал несёт только бит «есть работа». Переполнять нечего, падение процесса
очередь не теряет, а подбор `pending` при старте — обычный проход воркера, а
не отдельный код. Классификация исхода общая с пересборкой журнала.
- Исход разбора начал отражать доставку, а не обстоятельства: отмена и
занятость базы статус не меняют (иначе конкуренция за базу выводила бы
доставку из очереди навсегда), паника свёртки больше не валит процесс, а
учёт доставки идёт через транзакцию с повторами.
- Длинный бюджет ответа выдан маршруту приёма, а не всему серверу:
`write_timeout` в Go покрывает и чтение тела, и общий подъём снял бы защиту с
остальных маршрутов.
- целостность собранной витрины проверяется отпечатком на открытом
дескрипторе — перед необратимой подменой нужен integrity_check
- пересборка материализует учёт и список путей архива целиком: расход
памяти растёт вместе с журналом
- `healthlog reindex` собирает витрину из журнала (тела архива + учёт
доставок) в ОТДЕЛЬНЫЙ файл базы, строго по `(received_at, id)`; рабочую
базу читает без наката миграций и не трогает вовсе. Подмену делает
человек при остановленном сервисе: переименование поверх открытого
дескриптора портит базу молча.
- Журналом считается архив, а не таблица доставок: тело без учётной записи
заводится заново (метка из ULID, размер и хеш по распакованному телу),
запись без тела переносится, но не сворачивается. Оракул сходимости
встроен — два отпечатка и «объектов было/стало»; пустой журнал успехом не
считается.
- Прогон живого архива переехал на новый пакет: второго проигрывателя
журнала в проекте не осталось, а его утверждение о ключе сна перестало
быть константой, протухающей с каждой доставкой.
- формат пункта блокера жил в двух местах и разошёлся (четыре пункта против трёх, без рекомендации); теперь его описывает индекс беклога, CLAUDE.md ссылается
- три потребителя названы в паспорте поимённо: README звал их по функции, беклог по прозвищу, канонического списка не было нигде
- восстановлена фраза «спрашиваем только про необратимое» в своём абзаце — вставка про prior art её оторвала
- содержимое шагов не перечисляется: список работ жил в плане и в беклоге и расходился с каждой закрытой задачей
- десять пунктов «Отложено» заведены задачами [idea]; два из них (ретеншен, алерт) уже были в беклоге — в плане лежал дубль
- обоснование порядка расписано по шагам: это единственное, чего беклог структурно не вмещает
- числа находок и сценариев заменены общим описанием
- перечни линтеров, шагов гейта и конвенций свёрнуты в ссылку на источник: они дописываются, а копия молча отстаёт