первая встреча непокрытой секции стала наблюдаемым событием

- свёртка спрашивает журнал, встречалось ли имя строго раньше по паре
  (received_at, id), и пишет WARN с атрибутом uncovered_new; повторные молчат.
  Признак выводится, а не хранится — реестр был бы второй копией факта
- добавлена подкоманда `healthlog uncovered`: перечень накопленного, чтение
  только на чтение, экранированные имена и названные границы носителя
- синк документации: ADR о выводе новизны из журнала, две записи в журнал
  дефектов, два правила промоутом в конвенции, терминал оператора назван
  адресатом недоверенного входа
This commit is contained in:
av
2026-08-04 13:39:48 +03:00
parent 2130763d3c
commit bd5d17b079
27 changed files with 3125 additions and 9 deletions
+35 -1
View File
@@ -226,7 +226,7 @@ capability**, и здесь стоит ссылка, а не пересказ т
| `archive` | сырой архив: запись тела, чтение для reindex, ретеншен | [`storage`](../openspec/specs/storage/spec.md) |
| `hae` | разбор формата HAE, канонизация, хеш содержимого | [`parsing`](../openspec/specs/parsing/spec.md) |
| `ingest` | use-case приёма, общий для HTTP и CLI `import` | [`ingest`](../openspec/specs/ingest/spec.md) |
| `fold` | свёртка одной доставки в часовые объекты | [`storage`](../openspec/specs/storage/spec.md) |
| `fold` | свёртка одной доставки в часовые объекты | [`storage`](../openspec/specs/storage/spec.md), [`uncovered-sections`](../openspec/specs/uncovered-sections/spec.md) |
| `replay` | проигрывание журнала в витрину: состав, порядок, отчёт | [`reindex`](../openspec/specs/reindex/spec.md) |
| `catalog` | каталог разрезов и измерение рода агрегации | [`catalog`](../openspec/specs/catalog/spec.md) |
| `store` | SQLite: доставки, часовые объекты, тренировки, записи | [`storage`](../openspec/specs/storage/spec.md) |
@@ -355,6 +355,40 @@ capability**, и здесь стоит ссылка, а не пересказ т
`partial` — не отклонение, а установившееся состояние, поэтому уровень лога от
него не растёт. Постоянный `WARN` каждые пять минут обесценил бы уровень.
**Первая встреча имени — другое дело**
([`uncovered-sections`](../openspec/specs/uncovered-sections/spec.md)). Момент, когда поток принёс секцию,
которой раньше не было, фиксировался колонкой, но не наблюдался ничем: увидеть
его мог только тот, кто догадается заглянуть в базу. Теперь свёртка спрашивает
журнал, встречалось ли имя в доставках **строго раньше** этой (пара
`(received_at, id)`, запросом вне транзакции записи), и первая встреча даёт
`WARN` с именами отдельным атрибутом `uncovered_new`. Повторные молчат. Признак
выводится, а не хранится: реестр был бы второй копией факта, обязанной сходиться
с колонкой при каждой пересборке. Отсюда же идемпотентность — проигрывание
полного журнала повторяет ровно те же события.
Событие переживает **отказ** свёртки: список непокрытых секций переживает его
(доставка с невыводимым слоем всё равно пишет имена), и смолчать значило бы
потерять событие навсегда — следующая доставка сочла бы имя виденным. А
отложенный по обстоятельствам исход событий не даёт: учётной записи он не
меняет, доставка вернётся следующим проходом.
Перечень накопленного отдаёт `healthlog uncovered` — имя, число доставок,
первая и последняя встреча, чтением только на чтение и с экранированием имён
(ключ приходит из чужого тела). Границы у перечня три, и они названы, а не
замолчаны: имя, вытесненное границей списка в 32 имени, в колонку не попадает
вовсе; пересборка обнуляет колонку и заполняет её заново только по сохранившимся
телам; а имя, секцию которого разбор научился покрывать, уходит из колонки при
пересвёртке — то есть перечень отвечает о текущем состоянии покрытия, а не об
истории.
Цена сверки измерена на синтетическом журнале годового объёма; числа и метод
живут в одном месте — `design.md` изменения `aktivnaya-proverka-novyh-sekcij`,
решение 3, — и здесь не дублируются. Правило из замера: ранний выход есть только
у секции, приезжающей давно (строки просматриваются от старых к новым); у только
что появившейся секции проход идёт почти по всему журналу на каждой доставке,
пока её не покроет отдельная задача. Имён больше одного спрашиваются одним
запросом — тридцать два запроса подряд стоили секунду с лишним на доставку.
**Правило для будущих задач: покрыли секцию — пересверните.** Список это снимок
покрытия на момент свёртки; доставки, свёрнутые до того, как секция стала
покрытой, останутся `partial` со старым списком, и ретеншен будет вечно щадить