- свёртка спрашивает журнал, встречалось ли имя строго раньше по паре (received_at, id), и пишет WARN с атрибутом uncovered_new; повторные молчат. Признак выводится, а не хранится — реестр был бы второй копией факта - добавлена подкоманда `healthlog uncovered`: перечень накопленного, чтение только на чтение, экранированные имена и названные границы носителя - синк документации: ADR о выводе новизны из журнала, две записи в журнал дефектов, два правила промоутом в конвенции, терминал оператора назван адресатом недоверенного входа
21 KiB
ADDED Requirements
Requirement: Первая встреча непокрытой секции порождает событие
Свёртка SHALL писать запись уровня WARN, когда доставка принесла имя
непокрытой секции, которого не было ни в одной доставке, стоящей в журнале
раньше. Имена, признанные новыми, SHALL идти отдельным атрибутом записи —
всегда, независимо от выбранного уровня.
Уровень назван поимённо: WARN — «может стать проблемой, посмотри». ERROR
означал бы сбой, который надо разбирать, а приезд новой секции — штатное событие
внешнего мира.
Событие однократно за всю жизнь имени. Поэтому запись SHALL называть его независимо от того, какие повторяющиеся события (перезаписи точек, запечатанный час, расхождение слоя) случились в той же доставке: замаскировать однократное рутинным значило бы потерять ровно то, ради чего наблюдение заведено.
Отдельной строки лога у события нет: чекпоинт свёртки остаётся единственным.
Scenario: Имя, которого журнал раньше не видел
- GIVEN ни одна прежняя доставка не содержала секции
symptoms - WHEN доставка с секцией
symptomsсвёрнута - THEN запись чекпоинта свёртки имеет уровень
WARN - AND атрибут новых секций содержит ровно
symptoms
Scenario: Новая секция не маскируется рутинным событием
- GIVEN ни одна прежняя доставка не содержала секции
ecg - WHEN доставка с секцией
ecgодновременно перезаписывает точки уже сохранённого часа - THEN запись чекпоинта называет новую секцию, а не перезапись точек
- AND счётчик перезаписей остаётся в атрибутах записи
Requirement: Повторная встреча имени события не порождает
Система SHALL считать виденным всякое имя непокрытой секции, уже встречавшееся в
доставке, стоящей в журнале раньше текущей: в атрибут новых секций оно не
попадает и уровень записи не поднимает. Перечисление непокрытых секций при этом
не меняется: атрибут uncovered SHALL продолжать называть их все.
Отсюда следствие для приёмки: наблюдаемый признак события — атрибут новых секций, а не присутствие имени в записи вообще. Имя непокрытой секции стоит в записи каждой доставки, которая её принесла, и так было до этого изменения.
Scenario: Вторая доставка с тем же именем молчит
- GIVEN доставка с секцией
symptomsуже свёрнута - WHEN следующая доставка приносит ту же секцию
symptoms - THEN атрибут новых секций у второй записи пуст
- AND уровень второй записи рутинный
- AND секция
symptomsостаётся в атрибуте непокрытых секций
Scenario: Новое имя рядом с уже виденным
- GIVEN доставка с секцией
symptomsуже свёрнута - WHEN следующая доставка приносит
symptomsиecg - THEN атрибут новых секций содержит ровно
ecg
Requirement: Новизна выводится из журнала, а не из порядка свёртки
Признак новизны SHALL определяться сравнением с доставками, стоящими в журнале
строго раньше текущей — по паре (received_at, id), как порядок журнала
определён capability пересборки. Собственная учётная запись доставки в сравнение
не входит при любом порядке записи исхода.
Отсюда следует, что признак идемпотентен: повторная свёртка той же доставки даёт тот же исход, а проигрывание полного журнала пересборкой воспроизводит ровно те же события — свёртка по журналу остаётся функцией журнала, а не числа прогонов.
Хранимого реестра встреченных имён это изменение заводить MUST NOT: факт уже лежит в учётной записи доставки, и вторая его копия расходилась бы с первой при пересборке молча. Это решение этого изменения, а не запрет навсегда: потребителю, которому понадобится история непокрытых секций, переживающая удаление тел (ретеншен архива), придётся его пересмотреть — цена названа в требовании о честности перечня.
Запрос новизны SHALL выполняться вне транзакции записи свёртки: он идёт по растущему журналу, а транзакция записи объектов не имеет права держать блокировку дольше, чем позволено конкурирующему приёму.
Scenario: Повторная свёртка той же доставки не меняет исход
- GIVEN доставка с новой секцией свёрнута и событие записано
- WHEN та же доставка свёрнута повторно
- THEN событие повторяется тем же составом имён
Scenario: Доставка, свёрнутая позже более новой, судится по журналу
- GIVEN доставка A стоит в журнале раньше доставки B, и обе содержат
секцию
ecg - WHEN B свёрнута первой, а A — после неё
- THEN A признаёт
ecgновым, потому что раньше неё в журнале этого имени не было
Scenario: Запрос новизны не удерживает блокировку записи
- WHEN доставка с непокрытыми секциями сворачивается
- THEN обращение к журналу за признаком новизны идёт вне транзакции записи объектов
Requirement: Отказ свёртки не глотает событие
Список непокрытых секций переживает отказ разбора, поэтому проверка новизны SHALL идти до ветвления на успех и отказ, а новые имена SHALL попадать в запись об отказе — она уже выше рутинного уровня. Уровень такой записи определяет отказ; событие опознаётся атрибутом новых секций.
Это относится к каждому исходу, который пишет список в учётную запись, а не к одному только отказу разбора: отказ слияния тоже записывает имена, и терять событие ему нельзя ровно по той же причине. Отказы, случившиеся до разбора (нечитаемое тело, паника), список очищают и потому события не теряют.
Иначе событие теряется необратимо: имя записано в учётную запись отказавшей доставки, и следующая доставка сочтёт его виденным.
Исход, отложенный по обстоятельствам (занятость базы, отмена снаружи), — другое дело: он учётной записи не меняет вовсе, список непокрытых секций в базу не попадает, и доставка вернётся следующим проходом. Новые имена в такой записи система называть MUST NOT: событие не потеряно, а повторение его на каждом проходе занятой базы превратило бы однократный признак в дребезг.
Scenario: Отказ слияния при новой секции
- GIVEN ни одна прежняя доставка не содержала секции
ecg - WHEN разбор доставки прошёл, а слияние отказало нетранзиентно
- THEN запись об отказе называет новую секцию
ecg - AND учётная запись доставки сохраняет её в списке непокрытых
Scenario: Доставка с новой секцией и невыводимым слоем
- GIVEN ни одна прежняя доставка не содержала секции
cycleTracking - WHEN доставка с этой секцией не свернулась, потому что слой не вывелся
- THEN запись об отказе называет новую секцию
cycleTracking - AND учётная запись доставки сохраняет её в списке непокрытых
Scenario: Отложенная доставка события не порождает
- WHEN свёртка доставки отложена по занятости базы
- THEN запись об отложенном исходе новых секций не называет
- AND доставка остаётся в очереди
Requirement: Несостоявшаяся сверка не молчит
Отказ самого запроса новизны исходом доставки система объявлять MUST NOT: правила классификации исходов свёртки это изменение не трогает, отменённый контекст и занятая база остаются обстоятельствами, а не свойствами доставки.
Когда доставка дошла до записи исхода по прочим правилам, а сверка не удалась, все её непокрытые имена SHALL считаться новыми, и запись SHALL нести отдельный признак того, что сверка не состоялась, вместе с причиной: занятость базы проходит сама, а испорченное содержимое колонки не пройдёт никогда и будет поднимать признак на каждой доставке — по одному булеву эти случаи неразличимы.
Асимметрия названа: лишняя запись стоит внимания владельца один раз, а промолчавшее событие не восстанавливается ничем, кроме ручного запроса в базу.
Scenario: Сверка не удалась, разбор прошёл
- WHEN запрос новизны отказал, а разбор доставки прошёл
- THEN свёртка доходит до конца и записывает исход
- AND все непокрытые имена доставки объявлены новыми
- AND запись несёт признак несостоявшейся сверки и причину
Requirement: Перечень непокрытых секций отдаётся одной командой
Система SHALL отдавать перечень непокрытых секций, накопленных журналом, отдельной подкомандой — без ручного SQL по рабочей базе. Строка перечня SHALL называть имя секции, число доставок с ним, первую и последнюю встречу (метку журнала и идентификатор доставки); идентификатор нужен, чтобы достать тело из архива.
В перечень SHALL входить доставки всех статусов разбора: список непокрытых секций сохраняется и при отказе, и молчать о таком имени значило бы терять как раз подозрительное.
Порядок строк SHALL быть детерминированным — по имени секции.
Вывод SHALL иметь объявленный предел числа строк, а остаток называться числом: предел разбора в 32 имени действует на одну доставку, а различных имён журнал способен накопить сколько угодно.
База SHALL открываться только на чтение: команда диагностическая, и запуск её при живом сервисе не должен ни мигрировать схему, ни писать. Отказ открытия (базы нет, версия схемы не та) SHALL давать ненулевой код возврата и внятное сообщение — молчаливый пустой перечень неотличим от «ничего не приезжало».
Scenario: Перечень на базе с непокрытыми секциями
- GIVEN журнал содержит доставки с секциями
symptomsиecg - WHEN оператор запускает подкоманду перечня
- THEN вывод содержит обе секции с числом доставок и границами встреч
- AND порядок строк детерминирован
Scenario: Перечень на базе без непокрытых секций
- WHEN ни одна доставка непокрытых секций не приносила
- THEN команда завершается нулевым кодом и говорит, что перечень пуст
Scenario: Базы по указанному пути нет
- WHEN оператор запускает подкоманду с конфигом, указывающим на несуществующую базу
- THEN команда завершается ненулевым кодом и называет причину
Scenario: Имён больше предела вывода
- WHEN различных имён в журнале больше объявленного предела
- THEN вывод содержит предел строк и называет число оставшихся имён
Requirement: Перечень честен относительно своего носителя
Система SHALL называть границы перечня в спеке и в выводе команды, а не обещать
«всё, что поток когда-либо приносил»: перечень и признак новизны производны от
delivery.uncovered_sections — колонки, которая обрезается разбором и
обнуляется пересборкой.
Границы SHALL называться и в выводе команды, а не только в спеке: пустой перечень без них читается как «поток ничего не приносил» — обещание, которого носитель не даёт.
- Имя, вытесненное границей списка (не больше 32 имён на доставку), в колонку не попадает вовсе — ни события, ни строки перечня оно не даст; наблюдаемым остаётся счётчик отброшенных имён, который уже поднимает уровень записи.
- Пересборка обнуляет производные от разбора поля и заполняет их заново только по сохранившимся телам: доставка, тело которой удалено ретеншеном, свой список теряет, поэтому «те же события» пересборка воспроизводит при полном архиве.
- Имя, секцию которого разбор научился покрывать, уходит из колонки при пересвёртке — перечень отвечает о текущем состоянии покрытия, а не об истории.
- Поэтому пересвёртка ранее частично разобранных доставок (правило «покрыли секцию — пересверните») может дать событие о новизне повторно: журнал тот же, а колонка заполняется заново.
Scenario: Пустой перечень не говорит за весь поток
- WHEN в учётных записях журнала непокрытых секций нет
- THEN вывод команды говорит именно это, а не «журнал такого не приносил»
- AND называет границы носителя
Scenario: Учётная запись с неразбираемым списком не роняет ответ
- GIVEN в колонке одной доставки лежит значение, не разбираемое как JSON
- WHEN выполняется сверка новизны или собирается перечень
- THEN ответ строится по остальным записям, а не отказывает целиком
Scenario: Имя вытеснено границей списка
- GIVEN тело доставки содержит 32 незнакомых ключа перед секцией
ecg - WHEN доставка свёрнута
- THEN события о новизне
ecgнет, потому что имя в учётную запись не попало - AND запись несёт счётчик отброшенных имён и уровень
WARN
Requirement: Вывод перечня не доверяет содержимому тела
Печатаемое имя секции SHALL быть экранировано, чтобы управляющие последовательности из тела не влияли на терминал оператора: имя приходит верхнеуровневым ключом чужого тела, длина его ограничена разбором, содержимое — ничем.
Вывод SHALL оставаться свободным от значений точек, имён устройств и любых других данных о здоровье: имя секции — структурный ключ, а не измерение.
Scenario: Имя секции с управляющими символами
- GIVEN учётная запись доставки содержит имя секции с управляющим символом
- WHEN оператор запускает подкоманду перечня
- THEN символ выводится экранированной последовательностью, а не сырым байтом