- байтовый порядок канонических форм остался тай-брейком только внутри одной доставки: на живом корпусе он решал 98,8% спорных координат и системно хранил меньшее значение, из-за чего step_count терял род и verify:archive был красным - правило перестало быть коммутативным осознанно, поэтому порядок свёртки приведён к журнальному: проход воркера прекращается на отложенной доставке, а свёртка вне порядка журнала пишет WARN - заведены счётчики PointsHeld и PointsErased — удержание полнотой и единственное направление, в котором правило теряет содержание
46 KiB
reindex Specification
Purpose
Пересборка витрины проигрыванием журнала: import(снапшот) + replay(доставки).
Витрина производна, источник истины — сырой архив тел; значит любое повреждение,
включая ошибку нашего же разбора любой давности, лечится пересборкой, а не
восстановлением из бекапа. Здесь живут состав журнала, порядок проигрывания,
детерминированность, оракул сходимости и граница «что не восстанавливается».
Requirements
Requirement: Пересборка витрины проигрыванием журнала
Система SHALL уметь собрать витрину заново, проиграв журнал целиком:
import(снапшот) + replay(доставки). Стадия снапшота в этой дельте пуста —
пересборка из архива есть вырожденный случай с пустым снапшотом, — и отдельной
операции «пересборка из архива» рядом с импортом экспорта заводить MUST NOT.
Проигрываться SHALL все доставки журнала, а не только те, чей
parse_status говорит о неразобранности. Отбор по учётному статусу сделал бы
результат функцией предыдущего прогона, а не журнала; дешевизну повторного
проигрывания обеспечивает хеш-детектор объекта, а не пропуск доставок.
Пересборка собственного разбора и собственного слияния иметь MUST NOT: она
зовёт тот же код, что и приём, по идентификатору доставки, и тело читает из
архива тем же путём, с тем же пределом размера распакованного тела. Второй путь
разбора разошёлся бы с первым молча, а другой предел означал бы, что тело,
принятое со 200, вечно отказывает на каждой пересборке.
Условие сходимости SHALL называться требованием, а не оговоркой сценария. Правило слияния точек разрешает равную полноту в пользу пришедшей доставки, то есть содержимое витрины есть функция порядка свёртки, а не только множества доставок. Отпечаток пересборки равен отпечатку накопленной приёмом витрины тогда и только тогда, когда живая свёртка шла в порядке журнала. Прежде от порядка зависел только вывод слоя; теперь от него зависят значения точек, то есть цена нарушения выросла и должна быть названа здесь, а не выведена читателем.
Посылка равенства SHALL перечисляться рядом с ним, а не подразумеваться,
потому что оракул, чья посылка не названа, краснеет по причине, к правилу
отношения не имеющей, и краснота становится неотличимой от дефекта. Посылок
три: живая свёртка шла в порядке журнала; в журнале нет доставок, чью свёртку
живой путь провалил, а пересборка проведёт (статус failed); за время
пересборки новых доставок не приезжало.
Из этих трёх посылок прогон пересборки SHALL печатать рядом с отпечатком ту,
которую он измеряет сам, — число failed. Первая посылка прогону
недоступна по построению: записи «свёртка вне порядка журнала» рождаются
только в живом воркере, нигде не хранятся, и пересборке та же спека проверку
прямо запрещает. Она проверяется журналом сервиса, и это сказано здесь, чтобы
следующий автор не приписал к отпечатку константный ноль, выдав его за
подтверждение.
Равенство «пересборка = приём» SHALL проверяться оракулом, а не рассуждением: журнал, содержащий доставки с равнополными столкновениями, проигранный живым путём (фоновый воркер, в том числе с доставкой, отложенной занятостью базы) и путём пересборки, обязан давать один отпечаток витрины.
Место снапшота в порядке журнала этой дельтой не определяется, и это сказано вслух. Под правилом «побеждает пришедшая» исход столкновения снапшота Apple с точкой HAE зависит от того, какую позицию журнала получит доставка импорта: учтённая сегодняшним временем, она перебила бы все равнополные точки, включая досчитанные задним числом. Стадия снапшота сегодня пуста, поэтому вопрос отложен — но решать его SHALL задача импорта, и явно, а не выбором первого автора.
Отчёт пересборки SHALL называть два числа про точки — удержанные правилом полноты против пришедшей доставки и потерявшие содержание в пользу пришедшей — рядом с удержанными версиями сущностей и по той же причине: сходимость отпечатка ни одно из них не проверяет по построению.
Scenario: Пересобранная витрина совпадает с накопленной приёмом
- GIVEN рабочая витрина накоплена тем же разбором, приём во время накопления шёл последовательно, и за время пересборки новых доставок не приезжало
- WHEN журнал проигрывается заново с пустой витрины
- THEN отпечаток пересобранной витрины совпадает с отпечатком накопленной
Scenario: Отложенная занятостью доставка не разводит приём и пересборку
- GIVEN журнал, где одни координаты получают равнополные точки с разными значениями от разных доставок
- AND свёртка одной из доставок откладывается занятостью базы
- WHEN тот же журнал проигрывается живым путём и путём пересборки
- THEN отпечатки витрин совпадают
Scenario: Повторная пересборка ничего не меняет
- WHEN пересборка того же журнала выполняется второй раз
- THEN отпечаток витрины не меняется
Scenario: Разобранная доставка проигрывается наравне с неразобранной
- WHEN в журнале есть доставки со статусом
parsedи со статусомpending - THEN проигрываются обе
Requirement: Порядок проигрывания задаётся журналом
Система SHALL проигрывать доставки строго в порядке (received_at, id), а не
в порядке обхода каталога архива.
Порядок входит в результат: слой доставки без плотных метрик наследуется от предшествующей доставки той же автоматизации, то есть слой есть функция префикса журнала. Обход каталога совпадает с хронологией только по датам каталогов и внутри суток не упорядочивает ничего.
Второй ключ обязателен, а не для красоты: received_at хранится с секундной
точностью, и доставки одной секунды без него шли бы в неопределённом порядке —
а значит два прогона одного журнала могли бы разойтись.
Проигрывание SHALL быть последовательным. Распараллеливать его MUST NOT: слой есть функция префикса журнала, а запись часового объекта — чтение, слияние и запись обратно.
Scenario: Порядок не зависит от раскладки файлов в архиве
- WHEN тела одного журнала лежат в архиве так, что порядок обхода каталога не совпадает с хронологией приёма
- THEN доставка без плотных метрик получает слой предшествующей ей по
received_atдоставки той же автоматизации, а не слой соседа по каталогу
Scenario: Доставки одной секунды упорядочены идентификатором
- WHEN две доставки имеют одинаковый
received_at - THEN порядок между ними задаётся идентификатором и одинаков в каждом прогоне
Requirement: Журналом считается архив, а не таблица доставок
Система SHALL брать состав журнала из сырого архива, сверяя его с учётом
доставок, а не перечислять только строки delivery. Иначе витрина
пересобиралась бы из витрины.
Тело, у которого учётной записи нет, SHALL заводиться заново и проигрываться наравне с остальными: приём кладёт тело на диск раньше строки в базе — обратный порядок дал бы учтённую доставку без данных, — поэтому отказ на вставке строки оставляет тело в архиве без учёта.
Для такого тела идентификатор берётся из имени файла, метка приёма — из времени создания в ULID, приведённого к UTC. Дата каталога меткой служить MUST NOT: она задаёт сутки, а порядок нужен внутри суток. Размер и хеш пересчитываются по распакованному телу — так же, как их считает приём; иначе в тех же колонках появились бы значения другой природы.
Заголовки доставки при этом восстановлены быть не могут — в архиве их нет. Подобранное тело SHALL проигрываться без них, с честной деградацией вывода слоя: наследовать не от чего и подтверждать нечем.
Строка учёта, у которой тела в архиве нет, отказом быть MUST NOT: она станет штатной, когда появится ретеншен архива. Такая строка SHALL считаться и называться в отчёте.
Файл архива, не подходящий под форму тела (чужое расширение, остаток *.tmp
от прерванной записи, имя не разбирается как ULID), SHALL пропускаться и
учитываться счётчиком. Молчаливый пропуск недопустим: тело есть, а в отчёте его
нет. Тело, чей идентификатор уже встретился в другом каталоге, SHALL
пропускаться тем же порядком: вторая запись журнала с тем же ключом сорвала бы
весь прогон, то есть один посторонний файл лишал бы пересборки всё остальное.
Каждый пропуск, повтор и неудачный подбор SHALL оставлять запись в логе с путём файла — путь в архиве это дата и идентификатор, измерений в нём нет. Без неё счётчик в отчёте не на что раскрыть, а отчёт при этом отсылает человека разбираться по логу.
Ошибка чтения каталога архива, включая отсутствие самого корня, SHALL быть отказом всей пересборки, а не пустым журналом. Нечитаемый каталог означает «неизвестно, есть ли там тела», а не «тел нет». Каталог архива пересборка создавать MUST NOT — создание превратило бы запуск не из того каталога в успешный прогон по пустому журналу.
Scenario: Тело без учётной записи подбирается
- WHEN в архиве лежит тело, для которого строки
deliveryнет - THEN доставка заводится заново с идентификатором из имени файла и меткой приёма из ULID в UTC
- AND её размер и хеш посчитаны по распакованному телу
- AND её точки попадают в витрину
Scenario: Учётная запись без тела не роняет пересборку
- WHEN у строки
deliveryнет тела в архиве - THEN пересборка продолжается
- AND факт учитывается счётчиком в отчёте
- AND сама запись переносится в базу назначения, но не сворачивается
Scenario: Файл, не являющийся телом, считается отдельно
- WHEN в архиве лежит файл, чьё имя не разбирается как ULID либо чьё расширение не соответствует форме тела
- THEN он пропускается и учитывается счётчиком, а пересборка продолжается
Scenario: Повтор идентификатора не срывает прогон
- WHEN одно и то же имя тела встречается в двух каталогах суток
- THEN проигрывается первое, второе учитывается счётчиком
- AND пересборка доходит до конца
Scenario: Каталог архива не читается
- WHEN корня архива нет либо подкаталог не читается
- THEN команда завершается ошибкой и витрину не собирает
Requirement: Пересборка не трогает рабочую базу
Система SHALL собирать витрину в отдельный файл базы и MUST NOT записывать в рабочую базу ничего — ни объектов, ни строк учёта, ни миграций схемы.
Пересборка обязана начинаться с пустой витрины: точки из объекта не удаляются никогда, поэтому проигрывание поверх накопленного оставило бы результат прежнего, неверного разбора. Но очистка рабочей витрины необратима и наступает до того, как известно, что пересборка удалась: отказ на середине (битое тело, отменённый контекст, кончившееся место) оставил бы витрину пустой наполовину в состоянии, неотличимом от нормального.
Рабочая база SHALL открываться только для чтения и без наката миграций.
Обычное открытие накатывает миграции безусловно, а миграции меняют и данные (та,
что ввела частичный разбор, переписала parse_status у всех строк) — то есть
штатный путь чтения нарушал бы запрет выше. Хуже: свежий бинарь мигрировал бы
схему под работающим старым сервисом.
Расхождение версии схемы рабочей базы с версией, которую знает бинарь, SHALL быть отказом с указанием обеих версий, а не поводом мигрировать.
Scenario: Рабочая база остаётся нетронутой
- WHEN пересборка отработала успешно
- THEN отпечаток рабочей витрины не изменился
- AND учёт доставок в рабочей базе не изменился
- AND собранная витрина лежит в отдельном файле
Scenario: Отказ посреди пересборки не портит рабочую базу
- WHEN пересборка прерывается на середине журнала
- THEN рабочая витрина и учёт доставок в рабочей базе остаются такими же, какими были
Scenario: Схема рабочей базы старше бинаря
- WHEN версия схемы рабочей базы не совпадает с версией бинаря
- THEN команда завершается ошибкой, называя обе версии
- AND не пишет в рабочую базу ни одной строки
Requirement: Файл назначения и его жизненный цикл
Файл назначения по умолчанию SHALL быть соседом рабочей базы — так подмена остаётся переименованием внутри одной файловой системы.
Тождество файла назначения с рабочей базой SHALL определяться по файлу, а не
по строке пути: путь через .., симлинк или другой префикс монтирования
дают то же тождество при разных строках. Совпадение MUST быть отказом: иначе
проигрывание пошло бы прямо в живую рабочую базу — ровно то, что запрещено выше.
Сборка SHALL идти под временным именем, а переименование в файл назначения быть последним шагом успешного прогона. При любом ином исходе — отказ, отмена, падение — файла по пути назначения появляться MUST NOT, а временный SHALL убираться вместе со спутниками журнала SQLite.
Полусобранная база выглядит как обычная: это ровно то состояние, ради отрицания которого отвергнута очистка рабочей витрины. Обломок по пути назначения ещё и приучил бы обходить защиту от перезаписи флагом принудительности.
Существующий файл назначения перезаписываться молча MUST NOT: это отказ, если человек явно не потребовал перезаписи. Затребованная перезапись SHALL давать ту же витрину, что и сборка в отсутствующий файл, — сборка всегда начинается с пустой витрины, а не дописывается в чужое содержимое.
Scenario: Файл назначения совпадает с рабочей базой
- WHEN файл назначения — тот же файл, что рабочая база, пусть и по другому пути
- THEN команда завершается ошибкой и не пишет ничего
Scenario: Файл назначения уже существует
- WHEN файл назначения существует, а перезапись не затребована явно
- THEN команда завершается ошибкой и существующий файл не трогает
Scenario: Затребованная перезапись даёт ту же витрину
- WHEN пересборка выполняется поверх существующего файла назначения с явно затребованной перезаписью
- THEN отпечаток собранной витрины совпадает с отпечатком сборки того же журнала в отсутствующий файл
Scenario: Прерванная пересборка не оставляет файла назначения
- WHEN пересборка прерывается на середине журнала
- THEN файла по пути назначения не существует
Requirement: База назначения пригодна к подмене
База назначения SHALL нести полноценный учёт доставок, а не только объекты витрины: подменяется файл базы целиком, а не одна таблица.
Состав переноса нормируется явно, потому что колонки delivery двух разных
родов:
факты журнала id, received_at, automation_name, automation_id, aggregation,
period, session_id, bytes, sha256, raw_path, headers
← переносятся дословно
производные parse_status, points, derived_layer, uncovered_sections,
skipped_entities ← начинаются пустыми
Перечень производных полей SHALL пополняться тем же изменением, которое заводит новое поле: он единственное место, где сказано, чему нельзя пережить пересборку, и следующий автор решает по нему. Поле, не внесённое в перечень, однажды перенесут «для полноты учёта».
Факты журнала SHALL переноситься дословно, включая записи, тела которых в архиве уже нет. Заголовки восстановлению не подлежат ничем — в архиве их нет, — и неполный перенос уничтожил бы их первой же подменой, а с ними и вывод слоя для всех доставок, не только подобранных. Запись без тела при этом не сворачивается и в наследовании слоя не участвует: выведенного слоя у неё нет.
Производные от разбора поля MUST начинаться пустыми. Перенос derived_layer
особенно опасен и незаметен: доставка, чей повторный разбор отказал (штатный
исход, когда слой не выводится), сохранила бы слой прежнего разбора, и
следующая доставка той же автоматизации унаследовала бы его. Витрина снова стала
бы функцией предыдущего прогона, а не журнала, причём оба прогона были бы
самосогласованы — проверка «повторная пересборка ничего не меняет» этого не
ловит. Для числа пропущенных сущностей цена та же и хуже: пустота у него значит
«не измерялось», и перенесённое число выдавало бы измерение прежнего разбора за
измерение текущего — а по нему принимается необратимое решение об удалении тела.
Scenario: Учёт переносится полностью
- WHEN пересборка завершилась
- THEN число строк учёта в базе назначения равно числу строк рабочей базы плюс число подобранных тел
- AND заголовки перенесённых доставок совпадают с рабочей базой дословно
Scenario: Слой прошлого разбора в наследование не попадает
- WHEN в рабочей базе у доставок проставлен
derived_layer - THEN отпечаток пересобранной витрины совпадает с отпечатком пересборки того же журнала из учёта без проставленных слоёв
Scenario: Число пропущенных сущностей не переносится из журнала
- WHEN в рабочей базе у доставки проставлено число пропущенных сущностей, а тела этой доставки в архиве уже нет
- THEN в базе назначения её число пропущенных сущностей отсутствует
Requirement: Подмену рабочей базы делает человек
Система SHALL оставлять замену рабочей базы пересобранной человеку и выполнять её сама MUST NOT.
Файл базы держит открытым процесс сервиса, а переименование не касается уже открытого дескриптора: процесс продолжит писать в отвязанный inode, читатели увидят новый файл, и данные разойдутся молча. Документация SQLite называет переименование используемого файла прямой причиной порчи базы. Безопасная подмена требует остановленного сервиса, а остановить его команда не может: сервисом управляет окружение снаружи.
Отчёт SHALL печатать процедуру подмены буквально — команды, а не намёк, — и только тогда, когда прогон признан успешным (см. «Отчёт, оракул и исход команды»).
Scenario: Отчёт называет процедуру подмены
- WHEN прогон признан успешным
- THEN отчёт содержит путь собранного файла и команды подмены
Requirement: Отчёт, оракул и исход команды
Система SHALL завершать пересборку отчётом, который несёт счётчики (проиграно, свёрнуто, отказов по классам, тел без учётной записи, строк без тела, пропущенных файлов, повторов, объектов до и после) и два отпечатка — рабочей витрины и пересобранной, — с прямым ответом, совпали они или нет.
Счётчики «до и после» SHALL покрывать каждую единицу хранения витрины: часовые объекты, тренировки, записи и строки реестра категориальных значений. Отпечаток отвечает «да/нет» за витрину целиком, поэтому единица, которой нет в счётчиках, делает расхождение безадресным: человек увидит «не совпало» при неизменившемся числе объектов и не отличит появление двадцати семи тренировок от пропажи двух. Перечень пополняется тем же изменением, которое заводит единицу хранения: список закрытый, и единица, не внесённая в него, молчит ровно там, где расхождение впервые становится заметным.
Отказы SHALL считаться по классам: слой не выводится, содержимое не разбирается, работа отложена по обстоятельствам, всё прочее. Невыведенный слой есть в каждом журнале и штатен; общий счётчик отправлял бы человека искать дефект там, где его нет. Отдельно называть человеку следует только нештатные отказы.
Отложенная доставка (занятость базы, отмена работы снаружи) SHALL считаться нештатной для пересборки, хотя для фоновой свёртки она штатна: пересборка идёт в свежий файл при единственном писателе, и такая доставка в собранной витрине просто отсутствует — вместе с теми, кто наследовал от неё слой. Классы при этом общие с фоновой свёрткой: второй классификатор разошёлся бы с первым молча.
Число объектов «было и стало» SHALL печататься рядом с отпечатками: отпечатки отвечают «да/нет», а решение о подмене необратимо, и по «да/нет» нельзя судить о направлении расхождения. Именно пара чисел — 1737 против 1742 — поймала прошлый дефект наследования слоя.
Отпечаток здесь оракул, а не украшение: число объектов к правилу разрешения столкновений нечувствительно — на координате всегда ровно одна точка, и правило выбирает, какая, а не сколько. «Объектов столько же» совпало бы и при заведомо сломанном правиле.
Отпечаток рабочей витрины SHALL сниматься до начала проигрывания, а число доставок в рабочей базе — до и после. Ненулевая разница SHALL называться в отчёте, и при ней процедура подмены печататься MUST NOT: доставки, приехавшие за время прогона, есть в рабочей базе и в архиве, но не в собранном файле, и подмена стёрла бы их учёт вместе с заголовками, которых в архиве нет.
Величины, которые не снимались, отчёт печатать MUST NOT. При отмене отпечаток пересобранной витрины и число доставок после прогона не измеряются вовсе — печатать их сравнение значило бы выдать неизмеренное за измеренное, причём в единственном оракуле задачи. Ожидаемые классы расхождения (новые доставки за время прогона, непереносимый признак запечатанного часа, исправленный разбор, покрытая разбором новая секция, появившаяся единица хранения) SHALL называться отдельно от самого факта расхождения.
Класс «покрыта новая секция» назван потому, что первый прогон после такого изменения расходится гарантированно и штатно: витрина обзаводится единицами хранения, которых в рабочей базе нет по построению. Не назвав его, отчёт приучает человека игнорировать расхождение отпечатков — то есть обесценивает оракул ровно тогда, когда по нему принимается необратимое решение. Класс «появилась единица хранения» — та же причина в общем виде: реестр категориальных значений пуст у витрины, свёрнутой прежним бинарём, и первая сверка после выкатки расходится по нему одному, при неизменившихся объектах и сущностях.
Исход команды. Расхождение отпечатков отказом быть MUST NOT: после исправления разбора оно ожидаемо и есть сам смысл пересборки. Отказ отдельной доставки отказом команды тоже MUST NOT быть: доставка, слой которой не выводится, — штатный исход.
Отказом команды SHALL быть: пустой журнал, отсутствие хотя бы одной свёрнутой доставки, отмена и любая ошибка окружения. Пустая витрина совпадает по отпечатку с пустой витриной, поэтому прогон по пустому журналу выглядит идеальной сходимостью — а все умолчания подыгрывают такому запуску: конфига может не быть вовсе, и тогда пути указывают в рабочий каталог процесса. Человек, выполнивший напечатанную процедуру, заменил бы витрину пустой.
Отчёт значений точек, имён метрик, имён устройств и содержимого тел содержать MUST NOT: отпечаток берёт содержимое хешем. Строки реестра при этом счётом, а не перечислением: наблюдённое значение — данные о здоровье наравне со значением точки. Ограничение относится к отчёту в стандартном выводе; лог свёртки живёт по правилам спеки хранения, где координаты столкновения (метрика, слой, час) разрешены явно.
Отчёт идёт в стандартный вывод человеческим текстом. Прогресс длинного прогона SHALL идти в поток ошибок, а не смешиваться с отчётом: прогон на полном архиве молчит минутами, и зависший неотличим от идущего.
Scenario: Отчёт сравнивает отпечатки
- WHEN пересборка завершилась
- THEN отчёт содержит отпечаток рабочей витрины и отпечаток пересобранной
- AND прямо называет, совпали они или нет
- AND называет, изменилось ли число доставок в рабочей базе за время прогона
Scenario: Счётчики покрывают все единицы хранения
- WHEN пересборка завершилась
- THEN отчёт печатает «до и после» отдельно для часовых объектов, тренировок, записей и строк реестра категориальных значений
Scenario: Пустой реестр рабочей витрины назван ожидаемым классом
- WHEN рабочая витрина свёрнута прежним бинарём и строк реестра не имеет, а пересобранная их получила
- THEN отчёт называет «появилась единица хранения» ожидаемым классом расхождения
- AND печатает число строк реестра «до и после»
Scenario: Расхождение отпечатков не является отказом
- WHEN отпечаток пересобранной витрины отличается от рабочей, и при этом хотя бы одна доставка свёрнута
- THEN команда завершается успешно, а расхождение названо в отчёте
Scenario: Пустой журнал — отказ, а не идеальная сходимость
- WHEN в архиве не нашлось ни одного тела
- THEN команда завершается ненулевым кодом
- AND процедуры подмены не печатает
Scenario: Ни одна доставка не свернулась
- WHEN журнал непуст, но свернуть не удалось ни одной доставки
- THEN команда завершается ненулевым кодом
- AND процедуры подмены не печатает
Scenario: Приезд доставок за время прогона отменяет подмену
- WHEN число доставок в рабочей базе за время прогона изменилось
- THEN отчёт называет разницу
- AND процедуры подмены не печатает
Scenario: Отчёт после отмены не сравнивает неизмеренного
- WHEN прогон отменён
- THEN отчёт не содержит ни ответа о совпадении отпечатков, ни разницы числа доставок
Scenario: Рабочей базы нет вовсе
- WHEN файла рабочей базы не существует
- THEN пересборка идёт по одним подобранным телам
- AND отчёт называет, что сверять не с чем и что заголовки доставок не восстанавливаются
Scenario: Отчёт не раскрывает данных о здоровье
- WHEN отчёт напечатан
- THEN он не содержит ни значений точек, ни имён метрик, ни имён устройств, ни наблюдённых категориальных строк
Requirement: Отказ на одной доставке не останавливает пересборку
Система SHALL продолжать проигрывание, когда отдельная доставка не сворачивается (тело не читается, тело больше предела, слой не выводится, содержимое не разбирается), и учитывать такие доставки счётчиком отказов.
Останавливаться на первой нельзя: журнал заведомо содержит доставки, слой которых не выводится, — это штатный исход, а не поломка, и он не должен лишать пересборки остальные тела.
Отмена, наоборот, останавливать проигрывание SHALL: это требование прекратить работу, а не свойство доставки. Источник отмены SHALL быть назван: команду прерывает человек, и без перевода сигнала прерывания в отмену контекста требование к поведению по отмене недостижимо в эксплуатации — процесс умирает мимо всей логики. По отмене команда SHALL напечатать частичный отчёт и завершиться ненулевым кодом.
Scenario: Битое тело не срывает прогон
- WHEN одно из тел архива не распаковывается
- THEN остальные доставки проигрываются
- AND отказ учитывается счётчиком в отчёте
Scenario: Отмена прекращает проигрывание
- WHEN сигнал прерывания приходит посреди журнала
- THEN проигрывание прекращается, печатается частичный отчёт
- AND команда завершается ненулевым кодом
- AND файла по пути назначения не остаётся
Requirement: Отчёт пересборки показывает удержанные версии сущностей
Отчёт пересборки SHALL называть число версий сущностей, удержанных правилом «не теряем содержания», — тем же счётчиком, что ведёт свёртка.
Без него правило слияния сущностей проверить нечем. Сходимость отпечатка его не проверяет по построению: живой приём и пересборка пользуются одним правилом и одинаково сойдутся на одинаково удержанной версии. То есть слишком строгое правило — например, замораживающее тренировку на старой версии из-за исчезнувшего ключа с пустым значением — выглядело бы как идеальная сходимость. Счётчик несравнимых наборов точек выведен в отчёт по ровно той же причине и тем же рассуждением.
Число SHALL печататься всегда, а не только при ненулевом значении: ноль здесь утверждение, а не отсутствие новостей.
Scenario: Удержанная версия видна в отчёте пересборки
- WHEN журнал содержит доставку, приехавшая версия сущности в которой теряет содержание сохранённой
- THEN отчёт пересборки называет число удержанных версий больше нуля