Соответствие, которое умеет отказывать

Один разумный тест отделяет онтологию от схемы с подписями: может ли артефакт что-нибудь запретить? Онтология без аксиом, без валидации, без архитектурного замысла - это глоссарий, нарисованный графом. Он описывает, но не может отвергнуть. Если Vercy - только это, критика попадает в цель.

Поэтому страница отвечает на тест прямо: да, и вот демонстрация, которую вы можете запустить сами. Соответствие в Vercy принудительное, а не декоративное. Существует программа, которая читает модель, признаёт её невалидной и завершается с ненулевым кодом, называя нарушенное правило. Ниже - какая именно программа, какое именно правило и один разобранный отказ.

Соответствие - это шлагбаум, а не описание

Живой реестр, опубликованный на github.com/ver-cy/registry, - это работающий экземпляр профиля ELMM. Путь записи в него - pull request, а шлагбаум на этом пути - непрерывная интеграция: ci/run.py прогоняет семь проверок в режиме fail-closed по порядку, и одна красная проверка блокирует слияние. Fail-closed означает, что по умолчанию - отказ: модель допускается, только если пережила каждую проверку, а не допускается до тех пор, пока кто-нибудь не возразит.

Семь проверок, каждая завершается ненулевым кодом с названным нормативным правилом на первом же нарушении:

Это не обещание поведения, это само поведение. Шлагбаум - это код, и код публичен.

Проверка целостности графа, правило за правилом

check_graph - место, где происходит большая часть структурных отказов. Она обеспечивает шесть правил, у каждого свой диагностический вывод и свой идентификатор ELMM:

  1. Один узел на одну главную идентичность (ELMM-I17): никакие две записи не делят один идентификатор реестра.
  2. Ссылочная целостность (ELMM-I19): каждый конец ребра, from и to, - это идентификатор зарегистрированной записи.
  3. Покрытие экспортом (ELMM-I11, ELMM-I19): каждый вид, названный ребром, присутствует в exports целевой записи. Граф статически вычисляется по одним лишь записям, поэтому ссылка на вид, который цель не экспортирует, отвергается ещё до разрешения.
  4. Согласие объявленного минимума (ELMM-I11): там, где requires записи называет ту же цель и тот же вид, что и ребро, declared_min ребра равен этому min_version. При выборе минимальной версии ребро является единственным входом резолвера, поэтому запись и ребро обязаны совпадать.
  5. Изоляция ядра (ELMM-I18): ни одно ребро composes не выходит из узла ядра. Ядро ничем не дирижирует: его знание о моделях приходит из реестра, а не из исходящих рёбер.
  6. Ацикличность (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 реестра и в валидаторах моделей. И то и другое публично.