Соответствие, которое умеет отказывать
Один разумный тест отделяет онтологию от схемы с подписями: может ли артефакт что-нибудь запретить? Онтология без аксиом, без валидации, без архитектурного замысла - это глоссарий, нарисованный графом. Он описывает, но не может отвергнуть. Если Vercy - только это, критика попадает в цель.
Поэтому страница отвечает на тест прямо: да, и вот демонстрация, которую вы можете запустить сами. Соответствие в Vercy принудительное, а не декоративное. Существует программа, которая читает модель, признаёт её невалидной и завершается с ненулевым кодом, называя нарушенное правило. Ниже - какая именно программа, какое именно правило и один разобранный отказ.
Соответствие - это шлагбаум, а не описание
Живой реестр, опубликованный на github.com/ver-cy/registry, - это работающий экземпляр профиля ELMM. Путь записи в него - pull request, а шлагбаум на этом пути - непрерывная интеграция: ci/run.py прогоняет семь проверок в режиме fail-closed по порядку, и одна красная проверка блокирует слияние. Fail-closed означает, что по умолчанию - отказ: модель допускается, только если пережила каждую проверку, а не допускается до тех пор, пока кто-нибудь не возразит.
Семь проверок, каждая завершается ненулевым кодом с названным нормативным правилом на первом же нарушении:
check_schema(валидация схемы V0, ELMM-I8, ELMM-I14): каждая запись валидируется по схеме профиля узла реестра, а проекция её upstream-полей - по неизменённой схеме upstream-записи; каждое ребро валидируется по схеме рёбер. Запись с пропущенным обязательным полем, некорректной идентичностью или значением вне контролируемого словаря отвергается здесь.check_graph(целостность графа MMDG): шесть подправил, перечисленных в следующем разделе.check_resolve(детерминизм и воспроизводимость резолвера, ELMM-I23, ELMM-I26, ELMM-I30): резолвер запускается дважды с одним и тем же фиксированным входным временем и обязан выдать побайтово одинаковый результат; выдаваемый пакет контекста и Twin Composition Snapshot обязаны пройти валидацию по своим схемам; а там, где есть зафиксированная фикстура, свежий вывод обязан совпасть с ней побайтово. Резолвер, читающий системные часы, лезущий в сеть или уехавший от своих фикстур, проверку не проходит.check_zero_change(регистрация без изменений ядра, ELMM-I7, ELMM-I44): регистрация новой модели любой роли и любой области обязана затрагивать только данные реестра. Проверка клонирует дерево, добавляет новую модель и ребро, прогоняет шлагбаум, разрешает задачу, доходящую до нового вида, и хешируетschema/иresolver/до и после. Непустой diff ядра - провал соответствия: он означал бы, что допуск потребовал изменения ядра.check_facets(принадлежность фасетам и свежесть индекса, ARCH-017): каждый код индустрии и кластера в записи есть в своём словаре; каждая внешняя Группа отображена; сгенерированный каталог и единый индекс пересобираются побайтово одинаково. Уехавший словарь, неотображённая Группа или правленный руками сгенерированный файл проверку не проходят.check_instantiations(целостность преобразования B6): каждая зафиксированная инстанциация из внешнего во внутреннее пересобирается побайтово одинаково, её запись валидируется по схеме узла, а манифест - по схеме манифеста, и контроль мастерства IC-1 выполняется.check_conventions(соглашения каркаса, S1, S2): схема заголовка артефакта является валидной JSON Schema, readme с описанными соглашениями непустой, и ни одно имя файла вentries/,mmdg/илиschema/не содержит даты. Правило "без даты в имени файла" и соглашения по заголовкам обеспечиваются здесь.
Это не обещание поведения, это само поведение. Шлагбаум - это код, и код публичен.
Проверка целостности графа, правило за правилом
check_graph - место, где происходит большая часть структурных отказов. Она обеспечивает шесть правил, у каждого свой диагностический вывод и свой идентификатор ELMM:
- Один узел на одну главную идентичность (ELMM-I17): никакие две записи не делят один идентификатор реестра.
- Ссылочная целостность (ELMM-I19): каждый конец ребра,
fromиto, - это идентификатор зарегистрированной записи. - Покрытие экспортом (ELMM-I11, ELMM-I19): каждый вид, названный ребром, присутствует в
exportsцелевой записи. Граф статически вычисляется по одним лишь записям, поэтому ссылка на вид, который цель не экспортирует, отвергается ещё до разрешения. - Согласие объявленного минимума (ELMM-I11): там, где
requiresзаписи называет ту же цель и тот же вид, что и ребро,declared_minребра равен этомуmin_version. При выборе минимальной версии ребро является единственным входом резолвера, поэтому запись и ребро обязаны совпадать. - Изоляция ядра (ELMM-I18): ни одно ребро
composesне выходит из узла ядра. Ядро ничем не дирижирует: его знание о моделях приходит из реестра, а не из исходящих рёбер. - Ацикличность (ELMM-I16): подграф
composesявляется направленным ациклическим графом.
Каждое из них само по себе способно сделать зелёное дерево красным.
Один разобранный отказ
Возьмём правило 3, покрытие экспортом, и намеренно его сломаем.
Допустим, единственный управляемый Ландшафт, vercy.plmm, объявляет, что ссылается на вид budget-line, принадлежащий модели орг-единиц vercy.oumm. В mmdg/edges.json автор добавляет:
{
"from": "vercy.plmm",
"to": "vercy.oumm",
"edge_type": "references",
"declared_min": "0.1.0",
"compositional_role": "R4",
"kinds": ["budget-line"]
}
Ребро валидно по схеме: у него есть все обязательные поля, поэтому check_schema его пропускает. Оба конца зарегистрированы, поэтому ссылочная целостность проходит. Оно читается как совершенно корректное утверждение. Но vercy.oumm не экспортирует budget-line. Его экспорты - org-unit, reporting-line, established-position и unit-mandate; budget-line среди них нет. Ребро утверждает зависимость от смысла, который цель никогда не публиковала.
check_graph отказывает. Подпроверка покрытия экспортом обходит каждое ребро, берёт множество exports цели и падает на первом же названном виде, которого там нет. Она печатает полный отсортированный набор экспортов цели, чтобы читатель видел точно, что было опубликовано, а что нет. Прогон завершается ненулевым кодом с такой диагностикой:
check_graph: FAIL
edge #N (vercy.plmm -references-> vercy.oumm) references kind 'budget-line'
which is not in vercy.oumm exports ['established-position', 'org-unit',
'reporting-line', 'unit-mandate'] (ELMM-I11, export coverage)
Слияние заблокировано. Не помечено, не предупреждено, а заблокировано: ci/run.py возвращает ненулевой код выхода, и pull request не может быть влит. Чтобы это починить, автор либо заставляет vercy.oumm действительно экспортировать budget-line - а это настоящее изменение, которое владеющая модель обязана опубликовать и за которое обязана отвечать, - либо перестаёт заявлять ссылку, которой ему никто не давал. Реестр не позволит модели зависеть от смысла, который другая модель не экспортировала. Вот это артефакт запрещает.
Та же форма отказа есть по всему шлагбауму. Циклическая композиция, где Ландшафт композирует модель, которая транзитивно композирует его обратно, отвергается ацикличностью (ELMM-I16). Ребро к несуществующей модели отвергается ссылочной целостностью (ELMM-I19). Минимум в requires, расходящийся с declared_min своего ребра, отвергается согласием объявленного минимума (ELMM-I11), потому что иначе резолвер выбрал бы версию, по которой запись никогда не валидировалась. Регистрация, потребовавшая бы правки ядра, отвергается гарантией регистрации без изменений (ELMM-I7). Имя файла с датой отвергается соглашением об именовании (S1). Резолвер, чьи два прогона разошлись, отвергается детерминизмом (ELMM-I23). В каждом случае правило является причиной, идентификатор стоит в сообщении, а код выхода ненулевой.
Смысл именно в закономерности: каждый отказ называет нормативное правило, на которое модель можно указать, с которым можно спорить и по которому можно чинить. Нет ни молчаливого отказа, ни отказа по вкусу. Нарушь правило, получи код выхода, прочитай идентификатор.
За пределами реестра: валидаторы моделей
Реестр контролирует отношения между моделями. Отдельные модели несут собственные шлагбаумы. Коллективная мета-модель, опубликованная и версионируемая на github.com/ver-cy/collective-meta-model, поставляется с валидатором узла, который прогоняет шлагбаумы от V0 до V2 плюс специфичные для CMM проверки и отказывает на сломанном экземпляре: участник без коллектива, выдача полномочий без выдающего, мандат, не замыкающий контур подотчётности. Регистрационная запись этой модели фиксирует валидацию V2 как свой уровень соответствия, и запись стоит ровно столько, сколько стоит стоящий за ней валидатор. Именно валидатор делает уровень заявлением, а не украшением.
Честная граница
Три вещи надо сказать прямо.
Во-первых, сегодня это соответствие на уровне экземпляров моделей. Проверки отвергают невалидные записи реестра, невалидные рёбра, недетерминированное разрешение и невалидные экземпляры моделей относительно их валидаторов. Они запускаются в CI на пути записи и могут быть запущены кем угодно локально: склонируйте реестр с github.com/ver-cy/registry, выполните python ci/run.py, сломайте правило, посмотрите, как оно падает. Это реально, и этого достаточно, чтобы ответить на тест.
Во-вторых, размещённого публичного сервиса валидации - страницы или endpoint, куда посторонний кидает произвольную модель и получает вердикт, ничего не клонируя, - пока не существует. Это открытый пункт, и он назван. Пока он не выйдет, отказ воспроизводим, но не доступен постороннему в один клик.
В-третьих, читатель, склонировавший реестр ради воспроизведения отказа, обнаружит, что одна запись эталонной реализации всё ещё несёт идентификатор, аффилированный с коммерческим брендом, оставшийся до прохода по нейтральности. К демонстрации выше это отношения не имеет, поскольку она использует только нейтральные идентификаторы vercy.*, но нейтрализация этой записи и её ребра - названный открытый пункт реестра.
Ни одна из этих оговорок не меняет ответа на тест. Пока артефакт не умеет отказывать, он глоссарий. Вот отказ: названная проверка, ненулевой код выхода, нормативное правило и слияние, которое не состоялось. Запустите сами в CI реестра и в валидаторах моделей. И то и другое публично.