Metadata-Version: 2.1
Name: arxo-codegen
Version: 0.4.0
Summary: CLIR code-generation backends and delivery CLI
License: Apache-2.0
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: jsonschema<5,>=4.26
Requires-Dist: fastjsonschema<3,>=2.22

# arxohq/codegen — самостоятельная кодогенерация модели

Standalone repository: требуется явный валидированный `LAW_CODEGEN_CORE_KIT`;
соседний checkout law-dsl не используется.

Несовместимый выпуск 0.4.0 (этап R, 15.10.2026) принимает ровно `0.2.11` и
`law.core.ir/0.7`; прежние и будущие штампы отвергаются до планирования.
Реестр R.2 пересертифицирован: 1689 текущих векторов, 503 совпадения,
1186 закреплённых отказов/случаев вне профиля; py/ts/go, расхождений и
крахов нет. Приёмка 0.4.0 выполняется на установленном wheel через
`ci/verify.py`; vendor-пины и встроенный native lock обновляются вместе.
Push и теги выполняет владелец; локальная поставка — `candidate`. 51 errata квалифицирована портом, именованным отказом
или границей профиля; старые семантические пины из active registry удалены.
`positions` временно снят у `ts`, `py`, `go`: прямой вычислительный вход
отвечает `POSITIONS_UNAVAILABLE` до создания модели, перенос рангов — W6. Модель с `norm_template` либо нормативной головой
отказывает `NORM_RANKS_UNAVAILABLE` для любого вопроса. Цели больше не
объявляют `norm`; прежние эмиттеры лестницы и эффектов удалены.

Текущий сертифицированный профиль — только `py`, `ts`, `go`. Вторичные
цели и XLSX явно отказывают на `admission:current` до отдельного порта;
наличие их исторических исходников не означает поддержку текущего выпуска.
Одна числовая source-канарейка понижается текущим компилятором в `Number`:
проверяется `NUMBER_UNAVAILABLE`, пять остальных сохраняют mutation proof.

Публичный план — `law-backend-plan/0.20`: `basePlanVersion` выбирает
исполнительный формат 0.7–0.19, `admission.reservedAssertionIds` сохраняет
идентичности не-assertion узлов корня и зависимостей. Возможность
`admission:current` обязательна для текущей поставки py/ts/go. Transport case
с коллизией id получает `LDC-E1382`; typed temporal bounds проверяются до
вывода. `Number` даёт `NUMBER_UNAVAILABLE` до старой сверки типов слотов;
агрегаты в judgment-моделях — `AGGREGATE_JUDGMENT_UNAVAILABLE` (E-0357);
witness-bound backchain — `JUDGMENT_WITNESS_UNAVAILABLE` (E-0189).

Пределы текущей числовой арифметики не заменяются произвольной точностью:
границы i64/i128 дают `NUMERIC_LIMIT_UNAVAILABLE`; компактное ненулевое
десятичное расширение >4096 — `DECIMAL_EXPANSION_UNAVAILABLE` (E-0320).
Нулевые экстремальные exponent/scale нормализуются без огромных степеней
и циклов. Это явные границы среза, а не поддержка всего WP-13.

Быстрый цикл над свежим комплектом, без упаковки wheel:

```sh
python check_conformance.py --core-kit KIT --fixtures FIXTURES --targets ts \
  --vector-glob 'LAY-L0-INST-*' --report instant.json
```

`--vector ID` и `--vector-glob 'PATTERN'` повторяются и объединяются;
опечатка или шаблон без совпадений — ошибка. Отчёт такого запуска `partial`,
пара инертности всегда проверяется целиком. Явный `--outcomes` сохраняет
проверку выбранных пинов, включая отказ при устаревшем хэше входов.
Без него development-запуск сверяет голдены без приёмки полного реестра.

Полная проверка реестра (не обычный TS-цикл):

```sh
python check_conformance.py --core-kit KIT --fixtures FIXTURES --targets py,ts,go \
  --outcomes active-outcomes.json --timeout 180 --report conformance.json
```

Все совпадения и отказы требуют свежих пинов; лишний пин также ошибка.
`recertify_registry.py` закрепляет полный завершённый observation без повторной
сборки: проверяет хэш каждого execution-source и всего fixture payload,
полноту inventory, три цели, отсутствие ошибок и поимённую квалификацию
каждого утраченного совпадения. Частичный отчёт, изменённый источник,
неизвестный исход, crash/divergence и неквалифицированная потеря отвергаются.
В R.2 отдельно квалифицированы все 145 совпадений последней сертификации
и их объединение с прежним harness — 525 ID.

Принято DECISION-0188. Срез слоя **L0**: пакет Law DSL превращается в обычный
исходник на целевом языке, который считает **без движка правил**. Профиль —
`core-executable` (§8.4): ответ и статус, без доказательства, хэшей и
канонического документа.

**Excel:** цель `xlsx` выпускает самостоятельную книгу с формулами по отдельному
ограниченному профилю `excel-calculator/0.1`. [Сборка, настройки и границы](targets/xlsx/README.ru.md).
Это протокол `workbook`: книга пересчитывается в Excel, JSON-раннера у неё нет.

## Устройство

```
CLIR (ir.json)
      │
   plan.py                    ВСЯ семантика среза и ВСЕ отказы; языка не знает
      │
  план (law-backend-plan/0.7) таблицы, правила, соединения, гарды, термы,
      │   либо 0.8 (L1), 0.9  окна действия §31, собственные факты пакета;
      │                       у 0.8 — возможности, страты и фазы §111;
      │                       у 0.9 — агрегаты §59 и замыкания §70;
      │                       0.10 с norm снят до W6;
      │                       у 0.11 — календарные функции §84–§86;
      │                       у 0.12 — Rational §35; у 0.13 — Quantity §49;
      │                       у 0.14 — политика округления §50;
      │                       у 0.15 — Boolean/Natural, границы месяца, `_` §98;
      │                       у 0.16 — датированные приоритеты §92.3 и `prepare`;
      │                       у 0.17 — условный терм §56 и вычисляемый аргумент тела;
      │                       у 0.18 — функции §46, параметры A3, `let`, `match`;
      │                       у 0.19 — канал суждения §47.3
      ├── targets/ts/emit.py → model.ts + api.ts (+ evaluate.ts, apiBase.ts, runtime.ts, runner.ts)
      ├── targets/py/emit.py → model.py + api.py (+ evaluate.py, api_base.py, runtime.py, runner.py)
      ├── targets/go/emit.py → model.go + api.go (+ evaluate.go, apiBase.go, runtime.go, runner.go)
      ├── excel_plan.py → targets/xlsx/emit.py → model.xlsx (профиль Excel, только план 0.7)
      └── targets/{rb,rs,java,cs,kt,erl,ex,lua,swift,php,dart,scala,bsl}/emit.py —
          Ruby, Rust, Java, C#, Kotlin, Erlang, Elixir, Lua, Swift, PHP, Dart,
          Scala, встроенный язык 1С (BSL на OneScript): та же раскладка, свой
          рантайм и раннер, только план 0.7
```

Цель — каталог `targets/<цель>/` с эмиттером и дескриптором в нём же:
`available()` (тулчейн на машине), `MODEL` (путь модели в проекте),
`layout(proj, main)` (рантайм, раннер либо харнесс, файлы сборки языка),
`build(proj)` (команда сборки или `None`), `run(proj, main)` (argv-префикс).
Ворота, поставка и потребитель ветвятся по дескриптору, а не по языку;
`--targets` выбирает цели, по умолчанию `ts,py,go`, `all` в воротах и consumer —
все с протоколом `runner` и доступным тулчейном. Поставка `./law dev backend`
также видит цель `xlsx`; её отдельный профиль проверяет `check_xlsx.py`.

Дескриптор `PROTOCOL = "workbook"` вместо runner предоставляет `artifacts(plan,
options) -> {имя: bytes}` и `PROFILE`. Все артефакты готовятся до записи первого
файла. Паспорт фиксирует фактический профиль и хэши бинарных файлов. Ограничения
Excel и развёртка формул живут в `excel_plan.py`, сериализатор не решает правила.

**Отказ живёт в плане, а не в эмиттере.** Иначе один и тот же пакет означал бы
в разных языках разное: где-то отказ, где-то тихо другой ответ. Незнакомое
объявление, незнакомое поле правила или незнакомая форма терма дают не
«меньше ответа», а ДРУГОЙ ответ — поэтому пропускать нельзя.

**Инертная конструкция — единственное исключение, и оно доказывается, а не
предполагается** (CODEGEN-COVERAGE-PLAN §1.1, W1). Манифест `inert-nodes.json`
перечисляет конструкции, которые присутствуют в пакете, но на ответ
поддержанных видов вопросов не влияют: `source_work` §28, `anchor` §33,
`revision` §32, `entrenchment` §92.3, `factor_vocabulary` и `precedent_decl`
§201, `counterfactual_decl` §186, поле правила `visibility` §23.2. План
пропускает ровно перечисленное (`INERT_NODE_KINDS`, `INERT_RULE_FIELDS`); у
каждой строки — пара векторов `LAY-L0-INERT-<КОНСТРУКЦИЯ>` / `…-BARE` над одной
программой, делом и вопросом, чьи голдены оракула совпадают по `results`,
`positions`, `issues`, `conflicts`, `proofGraph` (manifest и `resultHash`
различаются по построению). `check_conformance` требует обе половины среди
активных векторов, совпадение половины с конструкцией на каждой цели и
равенство документов; строка без свидетеля — отказ ворот (`inert.py`).
`reference` §30.2 в манифест НЕ входит: без применимой на legal_time редакции
оракул опустошает результаты (`SOURCE_RESOLUTION_FAILED`), и это отказ плана.

Проверено на векторе `ERR-E0095-CLOSED-EDITION-RULE`: пока план молча
игнорировал жизненный цикл редакции, порождённый код отвечал `TRUE_ONLY` там,
где голден даёт `NEITHER`. Ловят такие места только векторы.

## Формат плана `law-backend-plan/0.7` и `law-backend-plan/0.8`

Версия у формата есть, поэтому поля перечислены здесь, а не читаются из кода
генератора. Изменение состава полей меняет версию. Каждый эмиттер печатает
закрытый список версий (`SUPPORTED_PLAN*` в `check_plan`) и отвергает любую
другую, как и возможность плана вне своего `CAPABILITIES`: незнакомое поле
плана печаталось бы молча, а это другой ответ, не меньший. Сегодня `ts`,
`py`, `go` печатают версии 0.7–0.19, остальные 13 программных целей и `xlsx` —
только 0.7. Ворота начинают с отрицательной пробы — план версии `999` обязан
быть отвергнут всеми эмиттерами. Проба не ловит смену СМЫСЛА поля под
прежним номером: это держат только векторы.

История: `0.1` — без `params` у таблиц; `0.2` (09.09.2026) — таблицы несут
объявленные типы параметров для проверки аргументов дела §189; `0.3`
(09.09.2026, ночь) — `consts` и `bases` верхнего уровня, вид `Date` в
типах и термах, вид `enum` для членов перечислений §38.
`0.4` (10.09.2026) — арифметика §58: операции `add`, `sub`, `mul`, `div`,
`mod` и функции `min`, `max`, `abs`, `pow`, `round`; пары видов проверяются
планом статически по таблицам §58. `0.5` (14.09.2026) — `keys` у таблиц
(ключи отношений §45.1, раннер печатает `KEY_CONFLICT`) и `filters` у
правил (константа в аргументе атома тела). `0.6` — `api`; `0.7` — окна
редакций §92.3 и мир импортов (DECISION-0236). `0.8` (14.09.2026,
DECISION-0237) — план поражаемого слоя, описан ниже в своём разделе. План
без возможностей по-прежнему `0.7` и байтово тот же: у 343 планов,
построенных до 0.8 (векторы и снимки корпуса), отпечатки совпали.

**Верхний уровень.** `planVersion` — строка версии. `package` — паспорт из
CLIR (`name`, `namespace`, `version`, `semanticHash`); эмиттеры его не
читают, он нужен человеку и будущей поставке. `world` — зависимости мира
(`name`, `namespace`, `version`), `excluded` — id узлов, снятых проекцией
§92.3 на всей оси (информация для человека). `tables`, `rules`, `facts`,
`api` — ниже.

**`tables`** — по одной записи на объявленное отношение: `pred` (URN,
единственный ключ доступа во время исполнения), `name` (последний сегмент,
даёт имя переменной в порождённом коде), `arity`, `params` — объявленные
типы параметров по порядку (URN типа `std` либо типа-сущности). Тип каждого
параметра проверяется планом на входе в срез, а не только у атомов правил:
таблица с параметром вне среза (Rational, Instant, Date) не может хранить
значения дела, и отказ обязан жить в плане, а не всплывать в раннере одной
из целей. `keys` — ключи отношения §45.1 как списки позиций столбцов
(пустой список — ключей нет). Порождённый модуль после насыщения считает
группы строк с одинаковой проекцией на ключ; строки уникальны по ключу
строки, поэтому группа из двух и более строк — различные accepted-кортежи,
и на каждую такую группу раннер печатает один `KEY_CONFLICT` (§196). У
оракула в счёт идут утверждения и strict-выводы — то же множество, что в
таблицах среза. Ответ от конфликта не меняется: evaluator ничего не
выбирает и не удаляет (`LAY-L0-KEYS`).

**`rules`** — по записи на правило, в порядке CLIR:

| поле | что несёт |
|---|---|
| `name` | уникальное имя в порождённом коде: порядковый номер плюс последний сегмент `id` |
| `id` | исходный URN правила; идёт комментарием в порождённый код |
| `window` | `null` либо `{start, end}` — полуинтервал `[start, end)` по оси `legal_time` (§31) |
| `atoms` | что перебирать, по порядку; строка атома `i` связывается с переменной `r{i}` |
| `joins` | равенства повторной переменной: пары `[[строка, столбец, тип], …]` |
| `filters` | константы в аргументах атомов тела: `{row, col, t, term}` — ячейка обязана быть равна терму-литералу (`eq` рантайма, равенство значений) |
| `guards` | отсев: `{op, l, r}`, где `op` — `lt`, `le`, `gt`, `ge`, `eq`, `ne` |
| `head` | что положить: `{pred, args}` |

**`facts`** — собственные утверждения пакета: `{pred, args}`. Кладутся в
таблицы до насыщения.

**Константа в теле.** Оракул сопоставляет неперемённый аргумент атома тела
с ground-атомом по каноническим байтам терма — по значению И типу
(`solver.solve`), а рантайм среза сравнивает `eq` по значению. Поэтому тип
литерала обязан совпасть с объявленным типом слота статически: литерал
Integer в Decimal-слоте у оракула не совпадает ни с одной строкой, а
рантайм по величине ответил бы иначе — это отказ плана «тип аргумента тела
не совпадает с параметром §189». Член перечисления проверяется по
принадлежности слоту, а не по «типу члена»: одно имя члена бывает у
нескольких перечислений пакета (`kz.corpus.land_code`, `FarmHousehold` в
четырёх). Сущности типа не несут, как у оракула. Подстановочный `_` §98
(E-0165) и терм с вычислением в аргументе тела — названные отказы.
Вектор — `ERR-E0052-DECIMAL-CANON`: `price(v0, 110000 USD)` в теле.

**`consts`** — константы §54 со значением: `{id: {type, term}}`. Раннер
разыменовывает `const_ref` в аргументах дела и запроса до исполнения
(§54.1: ниже ссылок нет) и сверяет тип константы с параметром §189 —
только у константы std-типа: оракул разыменовывает константы до сверки, и
константа-сущность становится `entity_ref`, который типа не несёт (до
15.09.2026 раннеры сверяли и её; у `eu.pil.jurisdictions` подтип с именем
`пакет#Тип` не находил базового — 3 ложных NEITHER).
**`bases`** — наследование сущностей `{подтип: базовый}`: константа подтипа
подходит параметру базового типа, как у оракула. Ссылка `const_ref`, не
стоящая в `consts`, — член перечисления §38 либо имя без объявления: оракул
оставляет её в ключе атома как есть, поэтому раннер берёт её значением-
идентичностью (вид `enum`), без проверки типа. Ссылка на функцию или
параметр A3 через `const_ref` — названный отказ плана.

**Чужой тип `пакет#Тип`.** Компилятор понижает `algo.description::Algorithm`
в имя `algo.description#Algorithm`, а не в URN объявления. Вид слота план
берёт у объявления `namespace#Тип`, где namespace — пакета мира с точно этим
именем; строки типов в `params`, `consts` и `bases` не меняются, потому что
оракул сравнивает их строками (`facts._conforms`). Имя без пакета в мире или
без объявления — вне среза. 15.09.2026 это открыло 27 снимков
(`algo.*` 15, `mathlib.*`, `isabelle.order`, `arxo.*`, `eu.pil.jurisdictions`).

**Алиас типа §40 и сущности `law.std.actors` §261 (W2, часть 1, 27.09.2026).**
Объявление `type_decl` с `typeKind: alias` прозрачно на исполнении, как у
оракула (`facts._unalias`, E-0097/E-0098): план ведёт таблицу `aliases` и
берёт имя типа за цепочкой алиасов в сигнатурах, константах, наследовании,
параметрах функций §46, `let` и параметрах A3. Цель алиаса компилятор пишет в
форме `имя.пакета#Тип`; она разрешается через паспорт мира (корень и
импорты), цель без пакета в мире остаётся строкой и уходит в отказ «тип
аргумента вне среза генератора: <цель>». Нормализуются ТОЛЬКО ссылки на
объявленные алиасы: сырые строки `имя.пакета#Тип` (абзац выше) не трогаются.
Типы `Actor`, `NaturalPerson`, `LegalPerson`, `Organization`,
`PublicAuthority`, `Official`, `Court`, `Collective` — сущности (вид `text`,
идентичность), как сущности пакета: `entity_ref` дела типа не несёт, §189 их
не сверяет. `Event` §79 и `Instant` §2.15 остаются отказом до частей 2–3 W2.
Свидетели — `LAY-L0-ALIAS-STD-PARAM`, `LAY-L0-ALIAS-ENTITY-CHAIN`,
`LAY-L0-STD-ACTOR-PARAM`; в корпусе алиасов 1255, из них 1250 с целью в
форме компилятора.

**`LegalContext`, `LegalRole`, `Jurisdiction` и слот `Interval<T>` (W2, релиз 0.1.5).**
Первые три — идентичности того же рода, что Actor: у оракула нет вида значения,
дело подаёт `entity_ref`. Слот `Interval<T>` §37 — непрозрачная идентичность:
литерала такого вида у оракула нет, интервальный терм дела раннер отвергает
поимённо («аргумент вне среза раннера: interval»), литерал-интервал в теле
правила — отказ плана. Свидетели — `LAY-L0-STD-CONTEXT-JURISDICTION`
(совпадение) и `LAY-L0-INTERVAL-SLOT-TERM` (закреплённый отказ раннера).

**Ошибка головы поподстановочная (W5, errata E-0265, релиз 0.1.6).** Ошибка
терма или агрегата головы снимает только свою подстановку: issue — по каждому
коду однажды, остальные подстановки применяются, как у оракула после E-0265.
Агрегат `sum`/`min`/`max`/`only` без политики `empty` в голове больше не отказ
плана (`headAgg`). Статус зависимого результата: NEITHER — статус кода (E-0105),
иная истинность — итог §181.2 (`ruleErrorStatus` раннеров). Свидетели —
`LAY-L0-AGG-HEAD-EMPTY-FIRST`, `LAY-L0-AGG-HEAD-ALL-FILLED`, `ERR-E0265-*`.

**Instant §2.15 (W2, часть 2, 28.09.2026).** Вид значения `inst`: запись
мгновения хранится как подана и служит идентичностью атома (E-0214), а
равенство и порядок берутся по МОМЕНТУ (E-0048): `12:00:00+05:00` и
`07:00:00Z` равны. §82.2 требует ASCII-григорианскую дату 0001–9999, полные часы/минуты/секунды
и обязательное смещение 00:00–23:59. Невалидная запись даёт `TYPE_ERROR`;
дробь произвольной длины сравнивается точно, без усечения. Запись без
смещения также не проходит admission;
Date с Instant не сравниваются (§57, у оракула `DATE_INSTANT_MISMATCH`) —
отказ плана при известных видах и рантайма при неизвестных. Функции §84.1:
`hours_between`/`minutes_between` — полных единиц со знаком, дробь к нулю;
`add_duration(after, n unit)` — целая Quantity секунд/минут/часов/суток,
ответ канонично в UTC без дробных секунд. Литерал Quantity длительности
сворачивается планом в пару (число, единица), как у календарных функций.
Возможность `value:instant` объявлена у py, ts, go; контракт рантайма —
случаи `instant-*`, `hours-between-*`, `minutes-between-*`, `add-duration-*`
в `runtime-cert.json`. Свидетели — `LAY-L0-INST-EQ-MOMENT`, `LAY-L0-INST-ORDER`,
`LAY-L0-INST-HOURS-BETWEEN`, `LAY-L0-INST-MINUTES-BETWEEN`,
`LAY-L0-INST-ADD-DURATION`.

**Event §79 (W2, часть 3, 28.09.2026) — срез идентичности.** Корни `Event` и
`Action` и типы-события пакетов (сущности с базой `urn:law:std#Event`) — слоты
идентичности, как сущности: событие в факте есть `entity_ref` на закреплённый
снимок (§79.1). Конструктор-запись события в факте раннер отвергает
поимённо («аргумент вне среза раннера: record»): его поля читает свёртка
§161.1, а это территория процедур (W7). Свидетели — `LAY-L0-EVENT-REF-IDENTITY`
(совпадение) и `LAY-L0-EVENT-RECORD-VALUE` (закреплённый отказ раннера).

**Порядок проверок в цикле.** Соединение и фильтр печатаются на уровне атома,
который их связывает, а не во внутреннем цикле: план не меняется, ответы и
отказы прежние (`eq` на text, enum, date, dec не отказывает; правило с
соединением над деньгами печатается прежним порядком). Декартово
произведение шести атомов тела `algo.description` без этого шло минутами на
пустом деле.

**`api`** (DECISION-0234) — поверхность модуля, выведенная планом по
правилам `@arxo/law-forms`: `questions` — `pub`-отношения с производящим
правилом, читающим в теле другой предикат; `inputs` — предикаты, достижимые
из вопросов и либо эмпирические, либо без производителя. У записи `pred`,
`name` (уникальное имя функции), `params`, `kinds`; у вопроса ещё `inputs`.
Цель с `API` и `emit_api` печатает по нему файл — устройство и контракт в
разделе «Единый вход и библиотека (DECISION-0423)» ниже. Над каждой функцией —
комментарий из меток отношения и производящих правил с якорями
`@source локатор · редакция` (поля `labels` и `sources` плана); в файле есть
реестры по имени.

## Единый вход и библиотека (DECISION-0423)

**Один вычислительный вход.** У `ts`, `py`, `go` семантика ответа живёт в одном
рукописном модуле цели — `evaluate.py`, `evaluate.ts`, `evaluate.go`: дело и
вопрос в канонической форме §168/§172 (той же, что у векторов и оракула) →
ответ исполняемого профиля. В нём разбор сырых литералов §208, сверка
аргументов дела §189, разворот контекста (правовое время, политика срока,
снимок календаря), насыщение, issues и лестница статусов вопроса (конфликт
эффектов §127 → конфликт ключа E-0242 → запрос судье §47.3 → ошибка терма
E-0105/§181.2). Раннер — транспорт JSON к этому входу, API — адаптер
аргументов к нему же, поэтому API и раннер отвечают одинаково по построению.
До 0.2.0 часть этой семантики жила только в раннере, и API на одном деле
давал другой ответ (Decimal в слоте сущности: раннер `NEITHER`/`TYPE_ERROR`,
API `TRUE_ONLY`/`COMPUTED`).

**Состояние на вызов.** Модель — фабрика: эмиттер оборачивает тело модуля
модели в `new_model()` (Python), `newModel()` (TypeScript, Go); таблицы,
опоры, кандидаты и issues — локальные переменные вызова, правила — замыкания
над ними, тела правил не переписываются. Контекст вычисления: у Python —
`contextvars`, у TypeScript — контекст, который синхронный `evaluate` ставит на
время вызова и снимает в `finally`, у Go — `evalContext` экземпляра модели, чьи
методы в фабрике затеняют одноимённые функции рантайма (`legalTime`,
`add_legal_term` и соседи). Глобального правового времени и мьютекса нет:
вызовы из потоков Python и горутин Go независимы.

**Значения входа — литерал-термы §53.** Конструкторы общей части API
(`api_base.py`, `apiBase.ts`, `apiBase.go`) строят ту же запись, что векторы:
`integer`, `natural`, `decimal`, `money`, `quantity`, `rational`, `text`,
`boolean`, `date`, `instant`, `entity`, `member` (в Go — `IntegerOf`,
`DecimalOf`, … с суффиксом `Of`: имена типов рантайма заняты). Decimal и Money —
из десятичной строки по лексике §208, без float; валюта обязательна. Значение
языка без конструктора переводится только по объявленному типу параметра
(строка в слоте сущности — `entity`, в слоте Text — `text`, целое в слоте
Integer — `integer`); вид, который по типу слота не читается однозначно, —
ошибка формы вызова. Литерал-терм с чужим типом проходит в `evaluate`, и там
сверка §189 даёт `TYPE_ERROR`, как у раннера и оракула.

**Ответ одной формы.** `Answer` у L0 и L1 и у всех трёх целей: `query_id`,
`result_kind` (`PROPOSITION` у вопроса истинности, `COLLECTION` у строк),
`truth_status`, `evaluation_status` (перечень §175), `issues` (коды),
`judgment_requests`, `rows` (литерал-термы). Python — frozen dataclass,
TypeScript — readonly размеченное объединение по `resultKind`, Go — структура.
`rows_*` идёт тем же входом (внутренний вид `rows`, которого протокол раннера не
знает) и отдаёт тот же конверт.

**Ошибки.** Ошибка формы вызова — `InputError`; названный отказ среза —
`Refused` (Python, TypeScript) и `*RefusedError` (Go, `errors.As`), паники на
границе нет; семантический `TYPE_ERROR` — issue и статус ответа. В Go ошибка
перевода аргумента запоминается в факте и возвращается вызовом `Ask*`.

**Функции.** Python: `fact_<вход>`, `negated_<вход>` (L1), `ask_<вопрос>(facts,
on, …, *, query_id=None)`, `rows_<вопрос>`, реестры `QUESTIONS`, `INPUTS`,
`NEGATED`. TypeScript: объекты `facts`, `negated`, `ask`, `rows`, реестры
`questions`, `inputs`, `negatedInputs`. Go: `Fact<Имя>`, `Negated<Имя>`,
`Ask<Имя>`, `Rows<Имя>`, реестры `Questions`, `Inputs`, `Negated`.

**Имена из StableId (0.3.0).** План даёт каждому вопросу и входу нейтральное
имя — локальную часть StableId: ASCII-имя как прежде, буквы других
письменностей сохраняются (NFC), прочие знаки сворачиваются в `_`. Эмиттер
делит имя на слова (по `_` и горбам camelCase с учётом регистра Unicode) и
печатает его по правилу языка: Python — snake_case, TypeScript — camelCase,
Go — PascalCase из одних букв и десятичных цифр (идентификатор Go не
принимает комбинирующих знаков). Совпадения ПОСЛЕ преобразования разводятся
суффиксом (`_2` у Python, `2` у TypeScript и Go) в порядке плана, то есть по
StableId. Реестры по строке и идентификатор вопроса по умолчанию
(`urn:query:api:<имя>`) держат нейтральное имя — оно одинаково у всех целей.
До 0.3.0 кириллическое имя превращалось в подчёркивания: у пакетов `ru-tk-*`
Go-API ломался на одинаковых `FactX`, а TypeScript — на одинаковых именах
таблиц модели; внутренний слаг с буквами вне ASCII теперь получает 8 знаков
SHA-256 локальной части, а повторное совпадение имени таблицы после префикса
пространства имён — названный отказ плана.

**Проверка.** `check_api.py` (`arxo-codegen-check-api`, шаг приёмки
`ci/verify.py`) печатает API-пакеты комплекта на целях с API и исполняет каждое
задание раннером и харнессом API (`targets/<цель>/apicheck.*`, у Go — второй
бинарь под тегом `apicheck`): положительное, с отрицательным фактом L1, с
литералом чужого типа (§189), с синтетическим ключом §45.1 при читающем и
независимом вопросе. Исходы обязаны совпасть у API и раннера каждой цели и у
целей между собой; затем пакет всех заданий с повтором первого исполняется
одним процессом последовательно и параллельно, а канарейка портит статус и
issues в `api_base.py` — расхождение обязано найтись. Харнессам запрещено
достраивать отсутствующие поля ответа. На коде 0.1.6 эта проверка даёт 55
расхождений (45 — литерал чужого типа, 10 — конфликт ключа).

**Терм** — одна из четырёх форм:

| форма | поля |
|---|---|
| ячейка строки | `{k: "var", row, col, t}`, где `t` — `money`, `dec`, `text`, `bool`, `date` или `enum` |
| литерал | `{k: "lit", t: "money", v, cur}`, `{k: "lit", t: "dec", v}`, `{k: "lit", t: "text", v}`, `{k: "lit", t: "date", v}`, `{k: "lit", t: "enum", v}` |
| бинарная операция | `{k: "bin", op, l, r}`, `op` — `add`, `sub`, `mul`, `div`, `mod` |
| вызов функции | `{k: "call", fn, args}`, `fn` — `round`, `min`, `max`, `abs`, `pow`, `div_round`; в плане 0.14 у `round` и `div_round` — `ak`, статические виды аргументов (DECISION-0259) |

**Таблицы видов §58 в срезе** (Integer, Decimal, Money; Rational и Quantity
вне среза). `add`/`sub`/`min`/`max` — только совпадающие виды. `mul` —
скаляр §49: Integer и Decimal масштабируют Money, два Money не
перемножаются. `div` — `Decimal/Decimal` и `Money/Money` одной валюты дают
Decimal, `Money/Decimal` — Money; `Integer/Integer` даёт Rational и
отвергается планом. `mod` и `pow` — только Integer, остаток евклидов,
показатель `0..4096`, результат в 64-битном диапазоне. `abs` сохраняет вид,
`round` над Integer — тождество (E-0110). Пара вне таблицы — отказ плана,
как TYPE_ERROR у оракула. Вид результата, зависящий от ЗНАЧЕНИЯ, — не
10-гладкое частное, `INEXACT_DIVISION`, деление на ноль, предел `pow` —
решает рантайм названным отказом: оракул на тех же входах даёт issue и
`RUNTIME_ERROR`, а не ответ, поэтому отказ здесь честен, а ответ был бы
другим.

Числа во всех литералах — СТРОКИ: десятичная запись не проходит через
двоичную плавающую точку ни в плане, ни в порождённом коде.

## Поражаемый слой L1 (DECISION-0237)

**Возможности.** План сначала определяет, что программе нужно сверх L0:
`defeasible` (сила `defeasible`/`defeater` §104), `negation` (отрицательная
голова, литерал тела или собственный факт §99/§113), `priority`
(`priority_rule` §106), `status:<вид>` (статусные тесты §65 `not_known`,
`unknown`, `refuted`, `monotone`, `supported`). Вызывающий может добавить
возможность сам: ворота просят `negation`, когда дело несёт отрицательный
факт. Пустое множество — прежний план `0.7`. Непустое — план `0.8` с полем
`capabilities` (отсортированный список). Дескриптор цели объявляет
`CAPABILITIES`; `deliver.emit_project` сверяет их ДО записи первого файла и
отказывает поставке названной причиной «цель rb не поддерживает возможности
плана: …» (код 2). Сейчас `0.8` печатают `ts`, `py`, `go`; остальные 13
целей его отвергают — проба ворот это проверяет.

**Поля плана 0.8 сверх 0.7.** У правила: `strength`; у атома — `accept`,
сырые статусы §62 строки, при которых генератор её перечисляет
(`established` — `TRUE_ONLY`, `monotone`/`supported` — `TRUE_ONLY`, `BOTH`);
`checks` — литералы, которые не перебираются, а проверяются над уже
связанными переменными: `{pred, accept, args}`, где `accept` уже учёл
полярность литерала (§63) и вид теста; у головы — `polarity`; `subst` —
значения всех переменных тела, идентичность применения. У факта —
`polarity`. Верхний уровень: `strata.strict` (списки имён правил по рангам
строгого слоя), `strata.defeasible` (по рангам §110), `priorities` (пары
«выше, ниже» транзитивного замыкания §106 по именам правил), `schedule` —
фазы §111: `strict`, `closure` (зарезервирована, `policies: []`), затем на
каждую поражаемую страту `candidates` (с `rules`), `defeat`, `surviving`,
`strict`, `status`. Эмиттер, встретив неизвестную фазу или `closure` с
политиками, отказывает.

**Что решает план (всё структурное).** Генераторы — положительные
`established`/`monotone` литералы тела по порядку; всё прочее — проверки,
и переменная, не связанная генератором, — отказ §190/§113 (отсутствие не
связывает). Ground-тело без генератора допускается. Ранги строгого слоя —
порт `strict.strict_ranks`, поражаемого — порт `defeasible.stratify` с
выравниванием голов обеих полярностей и дефитеров (E-0078/E-0080).
Названные отказы:

| причина | когда |
|---|---|
| `программа не стратифицируема (§110, NON_EXECUTABLE_UNSTRATIFIED)` | цикл предикатного графа с немонотонным ребром; ранги не сходятся |
| `рекурсия с порождением значений арифметикой (DECISION-0237 п. 11)` | голова с `bin`/`call` в той же компоненте, что чтение тела; действует и для L0 |
| `дефитер без обычного кандидата той же полярности (§110 п. 5, NON_EXECUTABLE_STRATUM_BARRIER)` | нет производителя либо его окна редакций §92.3 не покрывают окно дефитера |
| `цикл приоритетов (§106, PRIORITY_CONFLICT)` | цикл безусловных рёбер; отказ, а не совпадение с оракулом |
| `условный приоритет (§117/§118)` | `condition` не `true` либо `priorityKind` не `direct` |
| `датированный приоритет (§92.3)` | окно редакции приоритета не вся ось |
| `поздний производитель (§111, LDC-E4103)` | strict-правило читает статусно (не `monotone`) предикат с поражаемым производителем; strict `not_known`/`unknown`/`refuted` над строгой цепочкой к поражаемому выводу; поражаемое правило читает предикат (или делит голову), строгая цепочка которого завершается не ниже его страты |
| `статусный тест вне среза генератора: conflicted` | `conflicted`, `opposed` — оракул их не исполняет |
| `объявление вне среза генератора: closure_policy/None`, `…: interpretation*`, `…: reference/None` | замыкание §70, прочтения §156, динамическая ссылка §30.2; инертные объявления манифеста `inert-nodes.json` сюда не попадают |

**Модель (одинаково в `py`, `ts`, `go`).** Строки таблиц и параллельно —
их ключи; опоры — биты на ключ строки: strict/asserted `+`/`−` и
surviving-defeasible `+`/`−`. Статус §62 выводится при чтении из битов.
Strict-правило ставит бит своей полярности; поражаемое правило кладёт
кандидата в таблицу кандидатов страты с ключом «имя правила + ключ
подстановки». `defeat` — локальный helper над полным снимком кандидатов:
группа — `(предикат, ключ строки)`, кандидат побеждён strict-битом
комплемента (§107.1), противоположным обычным кандидатом с ребром
приоритета над ним (§107.2) или дефитером той же полярности, над которым у
кандидата ребра нет (§107.3); побеждённые не удаляются по ходу обхода.
Выжившим ставится surviving-бит. Страты печатаются циклами по `schedule`.
Контракт рантайма (восемь функций) и сертификация не менялись.

**Раннер и API.** L1-модель принимает отрицательные факты дела; вопрос
`truth` отвечает статусом §62 атома, отрицательный вопрос зеркалит стороны
§63. `collect` и `rows_*` перечисляют только `TRUE_ONLY`-строки. API
отдаёт статус буквально: у Python `(status, issues)`, у TypeScript
`Answer.status` типа `Status`, у Go `Answer.Status`; отрицательные входы —
`negated_*`, `negated.*`, `Negated<Имя>` и реестры `NEGATED`,
`negatedInputs`, `Negated`. Входы вопроса считаются по чтениям всех правил,
включая дефитеры и проверки; дефитер производителем вопроса не считается.
L0-модели получили тот же интерфейс раннера (`LAYER`, установленные строки,
статус строки, отказ на отрицательный факт) без смены поведения.

**Ворота.** `layer: L1` исполняется наравне с L0; голден без результата у
L1-вектора обязан быть отказом плана с той же причиной (`structural:
<код>` в манифесте: `NON_EXECUTABLE_UNSTRATIFIED`,
`NON_EXECUTABLE_STRATUM_BARRIER`, `LDC-E4101`). Пол `MIN_CHECKED_L1`
отдельный. Фикстуры `verify/ci/gates/backends/l1_fixtures.py`: семь
программ с ответом из прозы, каждая в перестановке правил и фактов и с
канарейкой — той же программой без решающей нормы, чей ответ обязан
отличаться (обе стороны выжили, strict против defeasible, дефитер
преодолён и не преодолён, один выживший среди побеждённых, умолчание §113,
монотонная рекурсия); три структурных контрпримера (default-цикл, поздний
производитель, дефитер без кандидата). API-шаг идёт и на L1-пакетах
`doctrine-ius-privatum`, `ru-zolotaya-rybka` с заданиями, где первый вход
подан отрицательным фактом.

## Агрегаты §59 и замыкания §70 (DECISION-0239)

**Возможности и версия.** `aggregate` — агрегат в теле или голове правила,
`closure` — узел `closure_policy`. План с любой из них — `law-backend-plan/0.9`
(поля 0.8 плюс ниже); без них план остаётся 0.8 или 0.7 байтово (отпечатки
всех построенных до правки планов совпали). Печатают `ts`, `py`, `go`.

**Поля 0.9.** У правила `steps` — порядок тела, как его выбирает `solve`
оракула: `{gen: i}` открывает цикл по атому, `{check: i}` и `{guard: i}`
стоят там, где связаны их переменные; `fallible` — в гарде есть агрегат,
способный дать ошибку значения; `addends` — агрегаты `sum` монотонных гардов
для проверки знака слагаемых. Верхний уровень: `aggregates` — подпланы
`{name, op, params, atoms, joins, filters, checks, guards, steps, element,
distinct, empty}`, где `params` — виды внешних переменных, а терм
`{k: "param", i, t}` читает `i`-ю из них; терм `{k: "agg", fn, args, t}`
вызывает подплан. `closures` — `{name, id, pred, domain, window}`; окно по
дню §2.15 переведено в полуинтервал. Расписание: `strict`, затем на каждый
уровень E-0113 `closure` с `policies` и снова `strict`, затем страты L1.
У фазы `strict` может быть `waiting` — имена strict-правил, которые этот
проход не исполняет: их замыкания ещё не материализованы (errata E-0174).
Поле печатается только непустым, поэтому прочие планы 0.9 байтово прежние.
Эмиттер печатает для каждого набора `waiting` константу страт без этих
правил, а незнакомое поле фазы отвергает.

**Семантика.** Агрегаты `count`, `sum`, `min`, `max`, `only` над comprehension
§52.1: генератор перебирает установленные строки, `distinct` — по ключу
значения, `collect all` — со повторами; `count` даёт Integer, остальные —
вид элемента (Integer, Decimal, Money). `only` — функциональный lookup §51
(errata E-0256): значение, если различное значение ровно одно (различие — по
тому же ключу, что `distinct`, поэтому над `collect all` кратность
безразлична); пусто — `EMPTY_AGGREGATE`, два и более различных —
`CARDINALITY_CONFLICT`. Он всегда фоллибелен: в гарде — ошибка значения G3
ниже, в голове правила — поподстановочная ошибка головы (E-0265, W5; до
релиза 0.1.6 — отказ плана). Политики у `only` нет:
`options` — отказ плана (у оракула `TYPE_ERROR`). План с фоллибельным
агрегатом несёт `backchain`: ошибка агрегата правила-кандидата доезжает до
статуса вопроса `truth` по E-0105 (`NEITHER` + `RUNTIME_ERROR`). Перечисление — ребро `completion`
(+1 в рангах обоих слоёв). Замыкание ставит strict-негатив члену домена
(`TRUE_ONLY`), у которого нет strict/asserted positive-опоры; strict-негатив
по §107.1 побеждает поражаемого кандидата.

**Ожидание замыкания (E-0174).** Strict-правило, тест которого опровергается
негативом замыкания, ждёт материализации этого замыкания. Это `unknown(P)`
любой полярности, `not_known(not P)` и перечисление в генераторе агрегата.
План переносит `rules.closure_waits` оракула: зависимости чтения делятся
по §65, ожидание транзитивно через строгих производителей. Правило ждёт до
уровня своего старшего замыкания. Голый `not P`, `established` и `monotone`
отрицательного литерала не ждут: замыкание делает их тест только истинным.

**Ошибки значения (G3).** `sum`/`min`/`max` без `options.empty` над пустой
коллекцией в гарде: правило копит выводы и при ошибке не применяет ни одного,
модель печатает `EMPTY_AGGREGATE` один раз на правило; отрицательное
слагаемое монотонного гарда `sum` — `MONOTONE_GUARD_NEGATIVE_ADDEND` и
пропуск подстановки. Коды идут в `issues` раннера и API и сверяются с
голденом (`BACKEND_ISSUES`).

**Удержание issue монотонного гарда (errata E-0198) в срез НЕ переносится, и
переносить нечего.** Ядро с 20.09.2026 предъявляет ошибку проверки
МОНОТОННОГО АГРЕГАТНОГО ГАРДА §111 по состоянию НЕПОДВИЖНОЙ ТОЧКИ страты:
ошибка промежуточного прохода, не повторившаяся на последнем, в документ не
попадает. Удержание наблюдаемо ровно там, где коллекция гарда РАСТЁТ внутри
его страты, то есть где generator читает ВЫВОДИМЫЙ предикат, — а это и есть
отказ плана G2 из таблицы ниже. Замер 20.09.2026 живьём на целях `ts,py,go`:
пять векторов errata (`ERR-E0198-GUARD-EARLY-EMPTY-SILENT`, `…-SILENT-02`,
`…-GUARD-FIXPOINT-EMPTY-SPEAKS`, `…-BLOCKS-02`, `…-GUARD-LATE-ERROR-SPEAKS`)
отвергнуты планом с той же причиной, что и семейство `ERR-E0116-*`; шестой,
`ERR-E0198-EMPIRICAL-GUARD-UNCHANGED`, в срезе И СОВПАЛ — у него generator
читает одну эмпирику, коллекция заморожена входом дела, и errata его не
касается по собственной границе. Отказ закреплён дважды: структурной
фикстурой `monotone-guard-derived` и шестью строками манифеста. Поэтому
абзац G3 выше остаётся верным дословно: у среза «правило копит выводы и при
ошибке не применяет ни одного» относится к ЗАКРЫТОЙ коллекции, а растущая в
срез не входит. Появится акт, ради которого G2 откроют, — удержание придётся
переносить тем же изменением, и это условие входа названо здесь, а не в
календаре.

**Вид `empty`-политики §59 (errata E-0195) сверяется в обеих точках.** В
правиле — планом: вид политики обязан совпасть и с видом результата,
выведенным планом из терма элемента, и с ОБЪЯВЛЕННЫМ типом проекции
comprehension §52.1 (`variable.type.name` — тот самый носитель, с которым
сверяется оракул: он не зависит от числа решений generator-а). Расхождение
любого из двух — отказ плана, потому что у оракула тот же вход даёт
`TYPE_ERROR`, то есть ДРУГОЙ ответ, а не меньший. В вопросе `term` — раннером
каждой цели: политика не-`value` и политика чужого вида над ПУСТОЙ коллекцией
дают `TermError TYPE_ERROR` (ответ без значения, `evaluationStatus`
`TYPE_ERROR`, issue `TYPE_ERROR`), совпавший вид и непустая коллекция
отвечают как прежде. Текста issue порождённый модуль не печатает вовсе —
байтовый контракт среза здесь КОД, и он тот же, что у оракула. До правки
20.09.2026 все три цели отвечали на коллекцию `Money` с политикой `Integer`
значением `Integer 0` и `COMPUTED`. Векторы `ERR-E0195-EMPTY-POLICY-*` этого
места не достают: их comprehension несёт СВЯЗКУ, и раннер отказывает раньше
агрегата, — поэтому норму держат три пробы ворот (`EMPTY_POLICY_PROBES` в
`check_backends.py`) на том же пакете и деле без связок: свидетель нормы и две
пробы против переусердствования.

**Отказы 0.9.**

| причина | когда |
|---|---|
| `агрегат вне среза генератора: average` | операция вне `count`/`sum`/`min`/`max`/`only` |
| `агрегат only с options вне среза генератора (§51, errata E-0256)` | у `only` задана политика; у оракула тот же вход — `TYPE_ERROR` |
| `вычисляемый элемент comprehension вне среза генератора (§52.1)` | элемент не переменная и не литерал |
| `монотонный агрегатный гард (E-0116) над выводимым предикатом` | гард, который оракул считает монотонным, читает выводимый предикат (G2); над одними входами — исполняется |
| `вид empty-политики агрегата не совпадает с видом элемента коллекции (§59, errata E-0195)` | вид политики разошёлся с видом результата плана либо с объявленным типом проекции comprehension; у оракула тот же вход — `TYPE_ERROR` |
| `агрегат с ошибкой значения над незавершённым входом` | фоллибельный гард strict-правила читает то, что в его страте ещё растёт |
| `замыкание §70 вне среза генератора (NON_EXECUTABLE_CLOSURE)` | предикат и домен не одной сигнатуры либо домен не `const_ref` |
| `замыкание §70 над поражаемым производителем` | у предиката или домена поражаемый либо поздний производитель (G5) |
| `программа не стратифицируема (§110, …)` | цикл через `completion` или вершину замыкания |

Фикстуры ворот: `count-over-derived`, `empty-sum-issue` (канарейка — те же
статусы без issue), `closure-negative`, `closure-strict-positive`,
`closure-unknown-reader` и `closure-transitive-default` (E-0174: ответ
NEITHER, канарейка без замыкания — TRUE_ONLY); структурные
`completion-cycle`, `monotone-guard-derived`, `closure-over-defeasible`.

## Нормативный runtime — отказ до W6

Текущая семантика норм использует ранги E-0325/E-0328–E-0332/E-0339.
По выбору владельца она переносится в W6. План отвергает наличие шаблона
нормы или нормативной головы с `NORM_RANKS_UNAVAILABLE` для всех вопросов;
прямой `evaluate(..., {kind: "positions"})` — `POSITIONS_UNAVAILABLE` до
создания модели. Hand-built план с `norms` либо возможностью `norm`
отвергается и эмиттером. Прежней лестницы статусов и материализации эффектов
в трёх эмиттерах нет. Обычный truth о цели эффекта эту границу не обходит.

## Календарные функции §84–§86 (DECISION-0245)

**Возможности и версия.** `calendar:days_between`, `calendar:period`
(`add_calendar_period`, `add_legal_term` календарных единиц),
`calendar:business` (`add_business_days`, `add_legal_term` дневных единиц).
План с любой из них — `law-backend-plan/0.11`; 0.7–0.10 байтово прежние
(отпечатки 1656 планов совпали). `days_between` безошибочна над слотами Date и
модели не меняет: пакет только с ней остаётся L0-моделью. Функции с ошибкой
значения требуют накопления выводов и печатаются L1-моделью 0.9. Печатают
`ts`, `py`, `go`; прочие цели отвергают план пробой версии.

**Термы.** `{k: "call", fn, args}`; счётчик — `{k: "qty", v, unit}`, только
литерал Quantity с единицей, допустимой функции (E-0029/E-0109), целый.
Не Date, чужая единица, дробный счётчик — отказ плана (у оракула всегда
TYPE_ERROR). `add_legal_term` §86.1 заменяется одной из двух операций по
единице. Места: гарды (`cal` у гарда), головы (`headFallible` у правила),
границы окон позиций L2 (`{k: "calbound", term}` либо `{k: "calbound",
error}`, когда граница не Date).

**Окружение.** `semanticVersion` CLIR, `deadlinePolicies` (узлы
`deadline_policy` по id), `calendars` — снимки `calendar_snapshot` с
датасетом (покрытие, недельный график, исключения) либо кодом ошибки загрузки:
байты ресурса сверяются с `datasetHash` и схемой `calendar-dataset` при
построении плана; у вектора байты — `calendar.resource.json` рядом. Раннер до
вывода разворачивает профили контекста §89.1, выбирает политику
(`context.deadlinePolicy`: у CLIR 0.1 объект, у 0.2 ссылка на узел) и снимок
(`options.calendarSnapshot`; два снимка без выбора — отказ
`AMBIGUOUS_CALENDAR`). Потребитель закрепляет встроенную политику сценария
узлом программы, как корпусный путь оракула (`kz.engine._pin_deadline_policies`).

**Ошибки.** Рантайм — порт `compute_deadline` и `Calendar` по порядку
проверок; коды `MISSING_POLICY`, `DEADLINE_POLICY_INVALID`,
`MISSING_CALENDAR`, `CALENDAR_OUT_OF_RANGE`, `CALENDAR_DATASET_INVALID`,
`CALENDAR_DATASET_HASH_MISMATCH` — issues модели. Гард: код ловится по
подстановкам, правило на проходе не срабатывает; два разных кода — отказ
рантайма. Голова: ошибка снимает только свою подстановку — issue по каждому
коду однажды, остальные подстановки применяются (errata E-0265, W5; до
релиза 0.1.6 ошибка у части подстановок была отказом рантайма). Граница окна:
issue и граница «нет». Фоллибельное правило обязано читать завершённое ниже
своей страты. Вопрос `truth` с ответом NEITHER получает статус E-0105:
раннер обходит `backchain` (правила с разыменованной головой и литеральными
конъюнктами) от цели, собирает коды ошибок правил-кандидатов и берёт
старший статус (`MISSING_INPUT`, `MISSING_POLICY`, …).

Фикстуры ворот: `business-days-holiday` (канарейка без исключения календаря),
`calendar-month-end` и `window-calendar-bound` (без политики),
`guard-out-of-range`, `days-between-l0`.

## Rational §35/§58 (DECISION-0252)

**Возможность и версия.** `value:rational` получает программа, в которой
Rational может появиться: параметр отношения, литерал или константа Rational,
`Integer/Integer`, операция над Rational, а также значение-зависимое частное
`Decimal/Decimal` и `Money/Money`. Статический вид последнего виден только при
компиляции терма, поэтому `build_plan` строит план дважды: первый проход
отмечает встречу, второй строит `law-backend-plan/0.12` модели L1 0.9. Планы,
где Rational не возникает, байтово прежние. Печатают `ts`, `py`, `go`.

**Виды.** Статический вид терма — `Integer`, `Decimal`, `Money`, `Rational`
либо динамический `Decimal|Rational`. Пара, у которой все исходы у оракула
`TYPE_ERROR` (Rational с Decimal или Money, `Rational/Integer`, `mod` не над
целыми, `round` над Rational), — отказ плана; пара с динамическим операндом
получает вид своих успешных исходов. Сравнение Rational с иным видом — отказ.

**Поля 0.12.** У терма `bin` — `lk`, `rk`: статические виды операндов (`i`,
`d`, `m`, `r`, `n`), по ним рантайм `arith` решает пару §58. Литерал —
`{k: "lit", t: "rat", v: "n/d"}`. У гарда с возможной ошибкой значения — `err`
(механика `cal`), у правила — `headFallible` и `kindChecks: [[позиция, "d"|"r"]]`:
динамический вид в слоте Decimal либо Rational сверяется по значению, чужой
вид — issue `TYPE_ERROR` по ключу (правило, предикат, позиция, вид) и пропуск
подстановки (E-0077). `backchain` — у каждого плана 0.12 (статус E-0105).

**Ошибки.** `DIVISION_BY_ZERO`, `INEXACT_DIVISION`, `TYPE_ERROR` — issues модели
по фазам DECISION-0245 п. 6. Расхождение валют денег не фоллибельно, как и
раньше: `CURRENCY_MISMATCH` — названный отказ раннера. Правило с фоллибельным
термом, читающее незавершённое в своей страте, ошибку не ловит: момент ошибки
у оракула зависел бы от хода фикспойнта, и она становится названным отказом
раннера. Вопрос `term` исполняет раннер сам: ошибка значения — `RUNTIME_ERROR`
с issue у любого плана. Rational в деле и вопросе — только канонической
записью §168.1, иная — отказ раннера.

**Канарейки значений.** Язык фикстур L1 деления и Rational не знает, поэтому
решающие нормы плана 0.12/0.13 держит `VALUE_CANARIES` в `check_backends.py`:
у векторов `LAY-L0-RAT-*`/`LAY-L0-QTY-*` из плана снимается `kindChecks`,
`err`, `headFallible` либо статический вид операнда деления, и модуль цели `py`
обязан разойтись с голденом — иначе ворота падают: норма не видна.

## Quantity §49 v1 и счётчик-связыватель §86.1 (DECISION-0256)

**Возможность и версия.** `value:quantity` получает программа, где Quantity —
значение первого класса: параметр отношения, литерал или константа вне
литерала календарного счётчика, операция над Quantity, счётчик-связыватель.
План — `law-backend-plan/0.13` модели L1 0.9; возможность независима от
`value:rational`, но `Quantity/Quantity` даёт вид `Decimal|Rational`, и такой
план несёт обе. Календарный план со счётчиком-литералом остаётся 0.11.
Сборка — тот же второй проход `build_plan` (`value_caps`).

**Виды и поля.** Статический вид `Quantity` без тега, код операнда `q`.
Литерал — `{k: "lit", t: "qty", v, unit}`. Пары §58 — порт `values.binary`:
пара, у которой все исходы у оракула `TYPE_ERROR` (`Q×Q`, Quantity с Money или
Rational, `Q/Integer`, `Decimal/Q`, `mod`, сравнение с иным видом), — отказ
плана. Счётчик-связыватель календарной функции — `{k: "qtyvar", fn, term}`:
рантайм `counter` проверяет вид, единицу функции и целочисленность; у
`add_legal_term` со связывателем план несёт `calendar:period` и
`calendar:business`, ветвь выбирает единица на деле.

**Ошибки.** Фоллибельны `Q±Q`, сравнение и `min`/`max` двух Quantity, деление:
`DIMENSION_MISMATCH` (разные теги), `TYPE_ERROR` (разные теги в частном — раньше
нуля), `DIVISION_BY_ZERO`, `INEXACT_DIVISION` — issues модели; статус E-0105 и
вопроса `term` у кодов несовместимости видов — `TYPE_ERROR`. Равенство при
сопоставлении фактов и ключах — «не равно» без issue. Расхождение валют денег
не фоллибельно (DECISION-0252) — асимметрия названа в DECISION-0256.

## Политика округления §50: `round` и `div_round` (DECISION-0259)

**Возможности и версия.** `value:div_round` получает программа с любым
`div_round`; `value:round` — программа с `round`, у которого точность или режим
не литерал, режим не `HALF_UP` либо значение динамического вида
`Decimal|Rational`. Любая из них — `law-backend-plan/0.14` модели L1 0.9; сборка —
тот же второй проход `build_plan` (`value_caps`). Программа с одним литеральным
`round(…, n, "HALF_UP")` остаётся прежней версии байтово и печатает прежний
`round`.

**Виды и поля.** У вызова `round` и `div_round` в плане 0.14 — `ak`: статические
виды аргументов (`i`, `d`, `m`, `q`, `n`; `t` — Text режима). Рантайм не
угадывает вид по значению: `round` над Integer — тождество Integer (E-0110).
Отказ плана — там, где у оракула всегда `TYPE_ERROR`: `div_round` вне пар
`Money/Decimal`, `Quantity/Decimal`, `Decimal/Decimal`, `Money/Money`
(E-0036, E-0170), Rational в аргументе, точность не Integer и не Decimal, режим
не Text, литеральная точность дробная, отрицательная или у `div_round` больше
34, литеральный режим вне семи. Динамический вид проверяет рантайм.

**Ошибки.** `TYPE_ERROR`, `CURRENCY_MISMATCH`, `DIVISION_BY_ZERO` — issues модели
по фазам DECISION-0245 п. 6, в порядке проверок оракула. `round` фоллибелен при
нелитеральной политике или динамическом виде, `div_round` — всегда, кроме
ненулевого литерального делителя при литеральной политике и паре не
`Money/Money`. Расхождение валют у `div_round` — issue, у оператора `/` —
прежний отказ (DECISION-0252 п. 4); асимметрия названа в DECISION-0259.
Решающие нормы плана держат канарейки `VALUE_CANARIES` над `LAY-L0-RND-*`:
`err`, `headFallible`, литеральный режим, литеральная точность, `ak`.

## Boolean, Natural, границы месяца и `_` (DECISION-0266–0268)

**Возможности и версия.** `value:boolean`, `value:natural`
(DECISION-0266), `calendar:month_bound` (DECISION-0267), `wildcard`
(DECISION-0268). Любая из них — `law-backend-plan/0.15`; модель не меняется,
и эмиттер узнаёт её по полям, как у 0.11 (`schedule` — L1, `aggregates` —
0.9). Планы без этих возможностей байтово прежние (отпечатки 1825 из 1869
путей; остальные — новые планы либо смена первой причины отказа).

**Boolean.** Литерал `{k: "lit", t: "bool", v}`, ячейка вида `bool`, в
деле — только JSON-булево (иное — названный отказ раннера). Равенство по
значению, порядок не определён: гард с `lt`…`ge` над Boolean или Boolean
против иного вида — отказ плана (у оракула всегда `TYPE_ERROR`,
`LAY-L0-BOOL-ORDER`); арифметика — отказ плана. Ключ строки `b:true` отличен
от Integer 1 и Text `"true"`. Вопрос `term`: сравнение только `eq`/`ne`.

**Natural.** Ячейка вида `dec`, тип-строка `urn:law:std#Natural` в `params`.
Арифметика и статические виды считают Natural как Integer (`_num`), вид
результата — Integer, как у оракула (`Typed integer`). Сверка §189 тип-строк
без нормализации: Integer в Natural-слоте дела — `TYPE_ERROR` и утверждение
не принято (`LAY-L0-NATURAL-SLOT-INTEGER`); Integer-терм в Natural-слот
головы и Natural-ячейка в Integer-слот — отказ плана (E-0077,
`LAY-L0-NATURAL-INTO-INTEGER-SLOT`); переменная Natural-ячейки в Natural-слот
сохраняет запись (`LAY-L0-NATURAL-HEAD-COPY`). Отрицательное значение в деле
— названный отказ раннера.

**Границы месяца §84.2.** `{k: "call", fn: "month_start" | "month_end",
args: [Date]}` — чистая функция над Date без политики §86 и снимка §85
(`values.month_bound_value`), не фоллибельна и накопления 0.9 не требует; не
Date статически или арность не 1 — отказ плана. В вопросе `term` арность и
вид — `TermError TYPE_ERROR` (`ERR-E0084-MONTH-BOUND-ARITY`, `-KIND`).

**`_` §98.** У атома `wild` (столбцы `_`) и `keep`: по столбцу `_` нет ни
фильтра, ни соединения, а на уровне цикла атома эмиттер пропускает строку с
уже виденной проекцией на `keep` — строки, различающиеся только в `_`, дают
одну подстановку (`solver.solve`, E-0165 MANY-ONE-APPLICATION). Множество
создаётся заново на каждой подстановке внешних атомов; полностью связанный
литерал с `_` в теле 0.9 остаётся генератором на своём шаге. `_` под
отрицанием, в статусном тесте не из `GENERATING` и в голове — отказ плана.

## Датированные приоритеты §92.3 и подготовленный контекст (DECISION-0269)

**Возможность и версия.** `priority:dated` получает план, где узел
`priority_rule` датирован якорем на редакцию; версия —
`law-backend-plan/0.16` модели L1. Планы без датированных рёбер байтово
прежние.

**Замыкание с окнами.** У оракула проекция §92.3 снимает неприменимые узлы
приоритета до замыкания (`editions.project_editions`,
`defeasible._priority_closure`), поэтому окно ребра участвует в замыкании:
ось права делится границами окон датированных рёбер на отрезки, на каждом
строится замыкание Уоршелла по активным рёбрам, пара «выше, ниже» получает
объединение отрезков, где достижима (`_priority_windows`). Пары на всей оси
— `priorities`, как прежде; остальные — `datedPriorities: [выше, ниже,
окна]`; у норм-правил L2 — `normPriorities` и `datedNormPriorities`. Цикл на
отрезке — отказ плана с названным отрезком; прерывистое окно исходного ребра
(две и более редакции с разрывом) — отказ. Окно ребра с окнами правил-сторон
не пересекается: путь замыкания через снятое проекцией правило держится по
id, как у оракула (`LAY-L1-PRIORITY-DATED-PATH-EXCLUDED-RULE`).

**Подготовленный контекст.** Модуль печатает `prepare()`: по `legalTime`
дела один раз на исполнение строит активное множество рёбер `PRIO_ACTIVE`
(и `NORM_PRIO_ACTIVE`) из недатированных пар и датированных с окном
`[start, end)`, покрывающим дату; `defeat` и эффекты полномочий читают его.
`saturate()` начинается с `prepare()`, так что раннер и API готовят контекст
сами. Это первый шаг рычага Astra (аналог `prepared.program_view` оракула):
ключ контекста сегодня — дата права, дальше — выбор прочтений и значения
параметров A3.

## Условный терм §56 и вычисляемый аргумент атома тела (DECISION-0270)

**Возможности и версия.** `term:if` (условный терм) и `term:body`
(вычисляемый аргумент атома тела: `binary`, `call`, `if`). Любая — план
`law-backend-plan/0.17` модели L1 0.9 с механикой ошибок значения; планы
без них байтово прежние.

**`if`.** `{k: "if", c, t, e}`, условие — `bool`, `cmp`, `and`, `or`
(`values.is_data_condition`); статусный тест в условии — отказ плана. Ветви
одного статического вида, иначе отказ. Эмиттер печатает ленивую ветвь
(тернарный оператор, условное выражение Python, замыкание Go).

**Вычисляемый аргумент.** Фильтр атома с `pre`: значение считается на уровне
атома ДО открытия его цикла (`solver.solve`), тип обязан совпасть с типом
слота статически, как у константы тела; при `err` — try/catch с кодом по
подстановке и issue правила после прохода, как у гарда. Генератор с таким
аргументом берётся, лишь когда переменные аргумента связаны (pick);
полностью связанный литерал — проверка статуса по ключу с тем же
перехватом. Внутри comprehension агрегата ошибка аргумента — отказ плана.
Обход E-0105 в раннере не заземляет конъюнкт с вычисляемым аргументом.

## Функции §46, параметры A3, `let` и `match` §55.1 (DECISION-0273)

**Возможности и версия.** `function` (вызов функции пакета в терме
правила), `param:a3` (параметр-датасет A3 в вычисляемом терме), `term:let`,
`term:match`. Любая — план `law-backend-plan/0.18` модели L1 0.9 с
механикой ошибок значения; пакет, чьи функции читает только вопрос `term`
(T071), получает план прежней модели версии 0.18 с полем `functions`.
Планы без всего этого байтово прежние. `build_plan` добирает возможности до
неподвижной точки: параметр или функция внутри вычисляемого аргумента тела
видны только проходу с `term:body`.

**Функции.** `functions: [{name, id, params, paramTypes, returns,
returnType, body, fallible?}]`, тело — терм над `{k: "fvar", i}`, вызов —
`{k: "fcall", fn, args}`. Эмиттер печатает `f_<name>` по позиционным
параметрам и таблицу `FUNCTIONS` для раннера (вопросы `term`/`collect`:
арность и виды аргументов — TYPE_ERROR оракула, вид результата —
объявленный). Статически: арность, типы аргументов против параметров, тип
тела против объявленного, рекурсия — отказ плана (у оракула — TYPE_ERROR
и NON_EXECUTABLE_FUNCTION_RECURSION при исполнении). Ошибка значения в
теле — фоллибельность вызова, коды по подстановкам как у 0252; виды
операндов в теле печатаются всегда.

**Параметры A3.** `a3Params: [{id, type, windows: [[start, end, term]]}]`
— план разбирает тело датасета (`a3.lower_parameter`) структурно: цепочка
`if` по окнам дат, вызов функции строки, `Result/Ok [литерал]`, хвост
`MissingParameterValue`; иная форма, ось Instant, тип строки не равный
типу значения — отказ. Терм `{k: "a3", id}`; `prepare()` (подготовленный
контекст 0269) выбирает окно по legalTime один раз на дело, разрыв —
`TermError("MISSING_PARAMETER_VALUE")` по подстановке, issue правила,
вывода нет; обход E-0105 код пропускает — NEITHER/COMPUTED, как у оракула
(молчание права). Параметр читается вызовом над `const_ref`
(`round(MRP, 0, "HALF_UP")`, как в корпусе), в гарде и в аргументе атома
(`pre`/`err`). Голый `const_ref` параметра в голове или атоме — у оракула
позиция идентичности с отложенным вычислением при чтении связанной
переменной; в срезе это названный отказ плана (29 пакетов корпуса).
Параметр в вопросе `term` — отказ раннера.

**`let` и `match`.** `{k: "let", i, v, b}` с переменной `{k: "lvar", i}`
печатается немедленно вызываемой функцией; тип значения обязан совпасть с
объявленным. `{k: "match", s, arms: [{m, v}], x?: false}` — только над
перечислением, образцы — вариант без полей (идентичность члена, E-0067) и
`_`; ветви одного вида; печать — цепочка `eq` по порядку ветвей, хвост —
`TermError("NON_EXHAUSTIVE_MATCH")` (статус NON_EXECUTABLE); неполный
`match` фоллибелен, полный по членам — нет. Образцы с полями, `bind`,
`value` — отказ. Таблица статусов раннера дополнена кодами оракула
(NON_EXHAUSTIVE_MATCH, NON_EXECUTABLE_FUNCTION_RECURSION, UNKNOWN_FUNCTION,
OPAQUE_NON_EXECUTABLE → NON_EXECUTABLE, RESOURCE_LIMIT).

## Канал суждения §47.3 (DECISION-0274)

**Возможность и версия.** `judgment` — программа объявляет отношение
суждения (`symbol_decl` relationKind judgment, `external_decl` канала с
органом и формой запроса). План `law-backend-plan/0.19`; модель не меняется
(L0 остаётся L0): план несёт `judgments` (орган, `requestSchema`),
`backchain` с силой правил и гардами (у L0 — впервые), `backchainConsts`.

**Обход — в рантайме цели**, один код для раннера и публичного API: порт
`whynot._collect_judgments` и соседей над готовым состоянием модели. От
цели без опоры — правила с унифицируемой головой, конъюнкты размечаются под
подстановкой цели (пара опор §62; несвязанная переменная — UNDETERMINED);
правило с заведомо невыполненным конъюнктом мертво (E-0101, кроме
refuted-теста над самим отношением при NEITHER), поражаемое правило, чей
вывод поразил бы сработавший дефитер без приоритета над ним, мертво
(E-0103 — по применениям страт `APPLIED`), дефитер стоящего вывода жив,
лишь если способен его снять (E-0100 — опоры §107.1 битами); неопределённый
судейский конъюнкт — запрос, прочий — вглубь по паре (атом, полярность)
(E-0111); судейский конъюнкт с несвязанными переменными перечисляется по
активному домену (аргументы всех атомов таблиц в порядке §208, кап 8 ДО
фильтра по NEITHER, DECISION-0036 (б)). Носители кроме тела: `valid_when`
полномочия при осуществлении (E-0100), кроме эффекта, запертого иммунитетом
(E-0120, карта `BLOCKED_IMMUNITY` из материализации — она же больше не
выпускает `POWER_INVALID_EXERCISE` при судье), `condition` цели обязанности
в вопросе `positions`. Гард в обходе — мини-вычислитель рантайма над
литералами §53 (переменные, литералы, константы §54, арифметика §58); иной
терм — UNDETERMINED (правило живо, как у оракула при исключении). Лестница
§175: конфликт эффектов, затем судья, затем ошибка терма E-0105.

**Ответ судьи §47.3.1.** Утверждение с объектом `judgment` принимается при
совпадении органа с декларацией и формы payload с `requestSchema`
(виды `_TYPE_KINDS`); иначе issue `JUDGMENT_AUTHORITY_MISMATCH` /
`JUDGMENT_PAYLOAD_MISMATCH` и никакой опоры. Ворота сверяют
`judgmentRequests` с голденом кроме `proof`; `Answer` L1-API несёт
`evaluationStatus` и `judgmentRequests`.

## Редакции и мир импортов (DECISION-0236)

Жизненный цикл редакции §31 не доходит до эмиттеров: план считает
применимость редакции во всех точках излома её событий (порт
`applicability.edition_state`) и сворачивает в окна `[start, end)`; окно
правила — пересечение `effective` с объединением окон датирующих редакций
(достаточно одной применимой). Якорь, не разрешающийся в датированную
редакцию, и граница не дневной гранулярности узел не датируют; пустое окно —
узел в `excluded`; несколько окон — названный отказ. Датированный факт —
правило с окном над таблицей `always` с одной строкой.

Мир импортов: `build_plan(ir, world)`, где `world` — CLIR зависимостей
(замыкание). `world_index(ROOT)` индексирует снимки по имени пакета,
`resolve_world(ir, index, extra)` строит замыкание; `deliver.py --dep FILE`
подаёт зависимость явно, остальное ищется по снимкам; у вектора мир —
`producer.ir.json` рядом. Узлы с занятым StableId и тем же `contentHash`
пропускаются (копии фактов у потребителя); имя таблицы при коллизии
локальных имён получает префикс пространства имён. Вопросы API — только у
корня.

## Виды вопросов раннера

Вопрос приходит с делом, и план его не видит: вид вопроса — свойство раннера.
Дескриптор цели объявляет `QUERY_KINDS`; ворота исполняют вектор только
тогда, когда его вид поддержан ВСЕМИ активными целями. `ts`, `py`, `go`
исполняют `truth`, `term` и `collect` (DECISION-0234): comprehension §52.1 по
установленным атомам таблиц с гардами, элемент — ячейка или литерал,
`distinct` — множество по каноническим байтам, порядок элементов —
канонические байты §208; агрегаты `count`, `sum`, `min`, `max` §59, пустая
коллекция без `options.empty` — названный отказ `EMPTY_AGGREGATE`; `only` §51
отвечает сам: пусто — `TermError EMPTY_AGGREGATE`, два различных значения
(канонические байты) — `TermError CARDINALITY_CONFLICT`, оба со статусом
`RUNTIME_ERROR`, политика у `only` — `TermError TYPE_ERROR`; политика
не-`value` либо чужого вида над пустой коллекцией — `TermError TYPE_ERROR`
(errata E-0195, вид элемента — из `variable.type.name` comprehension);
арифметика §58 и функции рантайма над результатом. Вид результата раннер
выводит статически теми же таблицами §58, что план: у оракула вид — часть
значения. Запись, поданная делом и сущностью, и Text, — названный отказ:
рантайм их не различает. Ответ `term` — `resultKind: DATA` с литерал-термом
§53, `collect` — `COLLECTION` со списком; ворота сравнивают значение с
голденом оракульским каноном (`lawref.canon`), а не сериализатором цели.

С DECISION-0252 и DECISION-0256 арифметику вопроса `term` раннер исполняет
рантаймом `arith` над точными видами значений, а не таблицей видов: ошибка
значения — ответ без значения со статусом по таблице `queries.query_calc`
(`TYPE_ERROR` для `TYPE_ERROR`, `CURRENCY_MISMATCH`, `DIMENSION_MISMATCH`,
`DATE_INSTANT_MISMATCH`, иначе `RUNTIME_ERROR`). Сравнение `lt`…`ne` в вопросе —
`values.binary`: валюта, единица, затем вид (`TYPE_ERROR`); числовые виды и
Date, ответ — `Boolean`. Литерал Rational операнда вычисляется `ratTerm`
(сократимая запись нормализуется, ERR-E0005); записи дела и вопроса `truth`
— только канонические, `rat`. С DECISION-0259 `round` и `div_round` в вопросе
исполняют `roundMode` и `divRound` с точными видами аргументов: ошибка политики
— ответ без значения со статусом той же таблицы (`CURRENCY_MISMATCH` —
`TYPE_ERROR`, `DIVISION_BY_ZERO` — `RUNTIME_ERROR`); пара вне E-0036/E-0170,
Rational в аргументе, точность не числом и режим не Text — названный отказ.

## Контракт рантайма

Операции ниже ведут себя одинаково во всех целях; всё остальное печатает
эмиттер. Состав контракта цели объявлен в `runtime_cert.py`: 19 базовых
операций (`BASELINE_OPERATIONS`) обязательны для каждой цели и достаточны
для плана 0.7; ещё 13 (`EXTENSION_OPERATIONS`: `arith`, `counter`,
`divRound`, `monthStart`, `monthEnd`, `qty`, `rat`, `ratTerm`, `roundMode`,
`instant`, `hoursBetween`, `minutesBetween`, `addDuration`) — операции видов
значений и функций поздних планов: Rational, Quantity, округление §50,
границы месяца, Instant. Полный набор сегодня несут `ts`, `py`, `go`; новый
целевой язык начинает с базового. Каждый громкий отказ ниже — значение одного
названного типа `Refused` (класс исключения в Python и TypeScript, тип
`panic` в Go); раннер узнаёт только его. Любая другая ошибка — крах.

| функция | обязательство |
|---|---|
| `dec(строка)` | точное десятичное значение произвольной длины (канонической записи плана; сырое поле литерала читают `lit*` — см. «Сырое поле литерала §208») |
| `money(строка, валюта)` | то же плюс валюта |
| `add`, `sub`, `min`, `max` | точно, масштаб — больший из двух; виды и валюты обязаны совпасть, иначе громкий отказ |
| `mul(a, b)` | точное произведение; масштаб результата — сумма масштабов; два Money — громкий отказ |
| `div(a, b)` | точное частное при 10-гладком знаменателе, наименьший точный масштаб; иначе громкий отказ (Money — `INEXACT_DIVISION`); ноль — громкий отказ |
| `mod(a, b)` | евклидов остаток над целыми; ноль и нецелые — громкий отказ |
| `abs`, `pow` | вид сохраняется; `pow` только над целыми с пределами показателя и значения |
| `round(значение, разрядов, режим)` | только названный режим; `HALF_UP` округляет ОТ НУЛЯ; над Integer — тождество |
| `cmp(a, op, b)` | порядок; разные валюты и сравнение денег с безразмерным — громкий отказ |
| `eq(a, b)` | равенство значений, а не записей: `2469` и `2469.00` равны |
| `key(строка)` | канонический ключ строки таблицы для дедупликации; виды различимы: сущность, дата, член перечисления, деньги, число |
| `date(строка)` | календарный день §2.15 в канонической записи ISO; порядок и равенство — по дню, с другими видами не сравнивается (§57) |
| `sym(идентификатор)` | член перечисления §38 или ссылка без объявления: равенство по идентичности, порядок не определён (§57) |
| Boolean в `eq`, `cmp`, `key` | DECISION-0266: значение языка (`boolean`); `eq` — только Boolean того же значения; `cmp` — `eq`/`ne`, иначе громкий отказ «§57: вид Boolean не упорядочен»; ключ `b:true`/`b:false`, отличный от числа и текста; в арифметике — громкий отказ видов |
| `month_start(дата)`, `month_end(дата)` | §84.2 (DECISION-0267): первое и последнее число месяца в канонической записи, високосность по григорианскому правилу; не Date, не ISO-дата, несуществующий день — `TermError TYPE_ERROR`; политика §86 и снимок §85 не читаются |
| `setLegalTime` / `legalTime` | правовое время дела; без него окна §31 не вычисляются |
| `rat(строка)` | Rational §35 (DECISION-0252): только каноническая запись `n/d` §168.1 — несократимая, знаменатель положителен, ноль `0/1`; части — по лексике §208 (errata E-0203); иная запись — громкий отказ, а не сокращение; целые произвольной длины |
| `arith(op, a, b, lk, rk)` | план 0.12: `add`, `sub`, `mul`, `div`, `mod` по таблице §58 `values.binary` над статическими видами операндов `i` Integer, `d` Decimal, `m` Money, `r` Rational, `n` Decimal либо Rational по классу значения; вид, расходящийся со значением, — громкий отказ. `Integer/Integer` и `Rational/Rational` — Rational (целое частное остаётся Rational); `Decimal/Decimal`, `Money/Money` одной валюты — Decimal при 10-гладком частном, иначе Rational; `Money*Rational` в обоих порядках — Money, 10-гладкость после сокращения; ошибка значения — `TermError` с кодом оракула: `DIVISION_BY_ZERO`, `INEXACT_DIVISION`, `CURRENCY_MISMATCH`, `TYPE_ERROR` (Rational с Decimal или Money, `Rational/Integer`, `mod` не над целыми) |
| `ratTerm(строка)` | литерал Rational операнда вопроса `term` — разбор оракула: `n/d` с целыми частями по лексике §208 (errata E-0203: `[+-]?[0-9]+`, без пробела, `_` и не-ASCII цифры), сократимая запись и знак в знаменателе нормализуются; не `n/d` — `TermError TYPE_ERROR`, знаменатель 0 — `DIVISION_BY_ZERO` (ERR-E0005; каноничность §168.1 — у записей дела и вопроса `truth`, там `rat`) |
| `qty(строка, единица)` | Quantity §49 v1 (DECISION-0256): точное десятичное значение и тег единицы — байты записи, без реестра единиц и алиасов; отличен от Money |
| `arith` над `q` | вид `q` — Quantity: `Q±Q`, сравнение, `min`/`max` разных тегов — `TermError DIMENSION_MISMATCH`, Quantity с иным видом — `TYPE_ERROR`; `Integer`/`Decimal` × Q — Q того же тега; `Q×Q`, Q с Money или Rational — `TYPE_ERROR`; `Q/Decimal` — Q, неточно `INEXACT_DIVISION`, ноль `DIVISION_BY_ZERO`; `Q/Q` одного тега — Decimal либо Rational по значению, разных тегов — `TYPE_ERROR` раньше нуля; `Q/Integer`, `Decimal/Q`, `mod` — `TYPE_ERROR` |
| Quantity в `add`, `sub`, `min`, `max`, `abs`, `round`, `cmp`, `eq`, `key` | тег сохраняется; разные теги в `add`/`sub`/`min`/`max`/`cmp` — `DIMENSION_MISMATCH`, Quantity с иным видом — `TYPE_ERROR`, в `eq` — ложь; ключ `q:<тег>:<число>` с экранированием тега, отличный от денег с тем же тегом |
| `counter(величина, функция)` | счётчик-связыватель §86.1: Quantity с единицей, допустимой функции (`add_business_days` — дневные, `add_calendar_period` — календарные, `add_legal_term` — все пять), целый; иначе `TermError TYPE_ERROR`; возвращает число и единицу; знак не проверяет (отрицательный — `DEADLINE_POLICY_INVALID` операции) |
| `add_legal_term(дата, число, единица)` | §86.1: `business_day`/`calendar_day` — `add_business_days`, прочие — `add_calendar_period` |
| `roundMode(значение, точность, режим, vk, pk)` | план 0.14 (DECISION-0259): `round` §50 с проверкой политики в порядке оракула — вид значения (`n` с Rational — `TermError TYPE_ERROR`), точность Integer либо целый Decimal (`pk`; дробная или отрицательная — `TYPE_ERROR`), режим из семи (иначе `TYPE_ERROR`); округление — порт `_round_fraction` на целых; Integer — тождество; точность не короче дробной части — значение то же, без дополнения нулями |
| `divRound(делимое, делитель, точность, режим, ak, bk, pk)` | план 0.14: пары `m/d`, `q/d`, `d/d`, `m/m`, иначе `TYPE_ERROR` → у `m/m` разные валюты `CURRENCY_MISMATCH` → ноль `DIVISION_BY_ZERO` → точность целая `0..34`, иначе `TYPE_ERROR` → режим; частное точное, округление всегда, масштаб результата — точность; `m/d` — Money той же валюты, `q/d` — Quantity той же единицы, `m/m` — Decimal без тега |
| Rational в `add`, `sub`, `min`, `max`, `abs`, `cmp`, `eq`, `key` | с Rational — по значению; Rational с иным видом в `add`/`sub`/`min`/`max`/`cmp` — `TermError TYPE_ERROR`, в `eq` — ложь; ключ `r:n/d`, отличный от числа и денег: `2/1` и Decimal `2` — разные ключи |
| `instant(строка)` | Instant §2.15 (W2, часть 2): запись хранится как подана и служит идентичностью атома (E-0214); `eq` и `cmp` — по моменту (E-0048): `12:00:00+05:00` и `07:00:00Z` равны; ASCII-григорианская дата и обязательное корректное смещение; дробь любой длины сравнивается точно; невалидная запись — `TYPE_ERROR`, с Date не сравнивается (§57) |
| `hoursBetween`, `minutesBetween`, `addDuration` | §84.1: полных часов или минут со знаком, дробь к нулю; `addDuration(мгновение, число, единица)` — секунды, минуты, часы или сутки, ответ канонично в UTC без дробных секунд; запись без смещения отвергается admission |

### Сырое поле литерала — грамматика §208, а не средства языка цели

Errata E-0197, E-0202 и E-0203 (20.09.2026) закрыли у обоих оценщиков один и
тот же дефект: сырое поле `value` литерала §53 читалось конструктором РОДНОГО
языка, и «так делает мой язык» становилось нормой. У целей генератора дефект
был тот же и разошёлся сильнее, потому что языков три, а не два.

**Правило одно.** Поле литерала читается по грамматике §208, названной явно, а
не тем, что примет `int()`, `Decimal()`, `Number()`, `parseFloat`, `strconv`
или `math/big`. Носитель поля проверяется ПЕРВЫМ (`rawLit` / `raw_lit` /
`rawOfMessage`, `rawOfValue`), и только потом — запись:

| вид | канон §208 | иная форма |
|---|---|---|
| `Text`, `Date`, `Instant`, `Rational`, `currency`, `unit` | JSON-строка | `TermError TYPE_ERROR` |
| `Integer` | целое JSON-число знакового 64-битного диапазона | `TermError TYPE_ERROR` |
| `Decimal`, `Money`, `Quantity` | JSON-строка по лексике ниже либо целое JSON-число | `TermError TYPE_ERROR` |
| `Boolean` | JSON-булево | громкий отказ (DECISION-0266) |
| `Natural` | неотрицательное целое JSON-число | громкий отказ (DECISION-0266) |

**Две лексики — дословный порт `lawref/values.py`** (`_DECIMAL_LEXIS`,
`_FRACTION_PART_LEXIS`) и `law-eval/src/values/literal.rs`:

```
десятичное:   ^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$
часть дроби:  ^[+-]?[0-9]+$
```

Форма УЗКАЯ по решению errata E-0203, и это не придирка к алфавиту: сужены
ровно пробельный символ, `_`, не-ASCII цифра и `Infinity`/`NaN` — четыре оси,
у которых радиус по всему дереву нулевой. **Экспонента (`1e2` → `100`),
ведущие и хвостовые нули (`1.50` → `1.5`), знак `+`, `.5`, `5.` и сократимая
дробь (`2/4` → `1/2`) остаются законной записью входа и нормализуются.**
Ужесточать сверх этого нельзя: `ERR-E0203-DECIMAL-EXPONENT`,
`-DECIMAL-CANON-FORMS` и `-RATIONAL-REDUCIBLE` стоят в векторах именно
границей, без которой «запись судится» не отличалось бы от «нормализация
отменена».

В Python якорь обязан быть `\Z`, а не `$`: Python-овый `$` пропускает
хвостовой перевод строки, и `"1.5\n"` прошёл бы. В JavaScript без флага `m` и
в RE2 без `(?m)` `$` уже строг.

**Граница i64 у TypeScript.** `JSON.parse` сводит число к `double`, и целое
знакового 64-битного диапазона теряет знаки НА ЧТЕНИИ: на
`ERR-E0202-CANON-NUMBER-I64-MAX` в ответ уходило `9223372036854776000` — не
меньший ответ, а другой, то есть ровно то, что DECISION-0188 запрещает. Отказа
здесь достаточно не было: `py` и `go` это целое несут точно, а отказ одной цели
при ответе двух других ворота читают расхождением. Радиус перехода мерился и
оказался мал: `Dec.u` в `runtime.ts` УЖЕ `bigint`, и печать результата уже идёт
носителем `{int: "<цифры>"}`. Поэтому чинится только чтение — `RT.parseJson`
разбирает текст, пропуская строки отдельной ветвью, и заменяет целое вне
±2^53 тем же носителем `{int: "<цифры>"}`, который читает и харнесс API.
`Number.MAX_SAFE_INTEGER` нигде не становится нормой: границей нормы остаётся
i64 §208, а 2^53 — лишь порог, за которым нужен точный носитель.

**JSON-число вне канона §208** (float либо целое вне i64) оракул и движок
отвергают ОТКАЗОМ СОБРАТЬ ДОКУМЕНТ — error-объектом §172, а не issue. У
порождённого модуля такого канала нет: профиль `core-executable` документа не
строит. Поэтому тот же вход даёт названный отказ раннера одним текстом у всех
целей: `JSON-число вне канона §208 (только целое знакового 64-битного
диапазона) вне среза раннера`.

**Текст issue модуль не печатает, и контракт здесь — КОД.** В ответе раннера
любого вида (раздел «Контракт раннера») `issues` — список кодов; ворота
сверяют коды (`BACKEND_ISSUES`), а не формулировки. Дословные
сообщения оракула (`term: Integer ожидает целое JSON-число, получено {"a":1}`)
стоят в векторах и в errata, а у модуля тот же случай — код `TYPE_ERROR`.

**Долг, названный числами.** (1) Литералы ПРОГРАММЫ (`ir.json`) читает
`plan.py`, и он остался permissive: 570 литералов `Integer` в векторах
записаны JSON-строкой (`"11"`), тогда как оракул после E-0197 даёт на такой
записи `TYPE_ERROR`. Сегодня это ничего не ломает — ни одно из этих правил в
своём векторе не вычисляется, — но норма у плана и у раннера разная, и это
записано, а не забыто. (2) Сокращение дроби в `plan.py` (ветвь `RAT`)
существует потому, что канон ядра дробь НЕ сокращает, а рантайм цели требует
канона §168.1; владелец решил 20.09.2026, что канон ядра сокращать начнёт —
отдельной errata. **После той errata костыль подлежит снятию тем же
изменением**, и до неё он не трогается.

### Сертификация рантайма

Контракт выше исполняем: `runtime-cert.json` — фикстура вызовов и ожиданий,
записанных ИЗ этого раздела, а не снятых с реализации. Сравнение целей между
собой общую ошибку не находит: коллизия ключа `["a|e:b","c"]` /
`["a","b|e:c"]` жила во всех трёх рантаймах одинаково и теряла второй факт.
Харнесс каждой цели (`targets/<цель>/cert.*`) исполняет фикстуру и печатает
результат, ожиданий он не читает; ворота сверяют каждый случай с ожиданием
до исполнения векторов. Отображение в фикстуре — с явным масштабом
(`2.00 KZT`, не `2 KZT`), поэтому она держит и масштаб результата, а не
только его значение. Литералы: `{dec}`, `{money, cur}`, `{text}`, `{date}`,
`{sym}`, `{row}` — у всех харнессов; `{rat}`, `{qty, unit}`, `{bool}`,
`{instant}` — только у харнессов полного контракта (`COMMON_LITERAL_TAGS` и
`EXTENSION_LITERAL_TAGS` в `runtime_cert.py`); ожидание — строка, булево, `null`,
`{refused: текст}` либо `{termError: код}` (ошибка значения плана 0.12,
DECISION-0252); Rational отображается `n/d`. Новый случай
контракта — строка фикстуры, новая цель — харнесс на 60–120 строк.

Ключ дедупликации экранирует разделитель и экран внутри текста (`|` → `\|`,
`\` → `\\`): длина в префиксе была бы разной у языков (байты, code points,
UTF-16), экранирование — нет.

## Контракт раннера

Раннер принимает `case.json` и `query.json` и передаёт их единому входу
`evaluate` (у `ts`, `py`, `go`; раздел «Единый вход и библиотека»); у прочих
целей раннер сам подаёт факты дела в таблицы, насыщает и отвечает. Три исхода
различаются протоколом выхода, а не одним ненулевым кодом:

| исход | код | stdout |
|---|---|---|
| ответ | 0 | JSON по виду вопроса, таблица ниже |
| названный отказ среза | 3 | `{"refused": текст}` |
| крах | любой другой | что угодно; ворота валят |

Форма ответа зависит от вида вопроса (`QUERY_KINDS` дескриптора). `value` —
литерал-терм §53, `issues` — список кодов:

| вид | ответ |
|---|---|
| `truth` у `ts`, `py`, `go` | `{queryId, truthStatus, evaluationStatus, judgmentRequests, issues}` |
| `truth` у остальных целей | `{queryId, truthStatus, evaluationStatus, issues}` |
| `term` | `{queryId, resultKind: "DATA", value, evaluationStatus, issues}`; при ошибке значения `value` нет, `evaluationStatus` — `TYPE_ERROR` либо `RUNTIME_ERROR` |
| `collect` | `{queryId, resultKind: "COLLECTION", value, evaluationStatus, issues}` |
| `positions` | именованный отказ `POSITIONS_UNAVAILABLE` до W6 |

Текст отказа обязан совпадать между целями побайтово; отказ одной цели при
ответе другой — расхождение. Отказы, зависящие от данных дела, закономерно
возникают при исполнении, но их перечень общий; основные:

| текст | когда |
|---|---|
| `аргумент вне среза раннера: <kind>` | аргумент дела не `value`, не `entity_ref` и не `const_ref` — например `record` (конструктор события) или `interval` |
| `аргумент вне среза раннера: <URN типа>` | значение типа, которого раннер не знает |
| `§113: отрицательный факт вне L0-среза` | утверждение дела с `polarity` ≠ `positive` у модели L0; модель L1 принимает `negative` |
| `вид запроса вне среза раннера: <kind>` | вид вопроса вне `QUERY_KINDS` цели: у `ts`, `py`, `go` — не из `truth`, `term`, `collect`; у остальных целей — любой, кроме `truth` |
| `POSITIONS_UNAVAILABLE` | `positions` временно снят в R до переноса рангов norm runtime в W6; отказ одинаков у библиотеки и раннера трёх целей |
| `ошибка значения вне среза раннера: <код>` | ошибка значения вне вопроса `term` и вне правил модели, например в гарде comprehension вопроса |
| отказы рантайма из таблицы выше | валюты, режим округления, порядок на Text, Date с другим видом, порядок над перечислением, `legalTime` |

**Типы аргументов дела** сверяются с `params` таблицы до преобразования
значений — зеркало `check_argument_types` оракула (§189/§48, errata E-0046):
несогласованное по арности или типу утверждение не принимается, в `issues`
ответа встаёт `TYPE_ERROR`; `entity_ref` типа не несёт и не проверяется;
предикат без сигнатуры принимается и никем не читается. До этой проверки на
`ERR-E0046-CASE-ARG-TYPE` Python и Go отвечали `TRUE_ONLY` против голдена
`NEITHER`, а ворота этого не видели: TypeScript отказывал на Integer первым,
и вектор шёл в «отказ рантайма».

**Коды `issues`** сверяются между целями целиком, а с голденом — по кодам,
которые срез порождает сам: набор `BACKEND_ISSUES` ворот, 18 кодов —
`TYPE_ERROR`, `KEY_CONFLICT` и коды ошибок значения §58, агрегатов §59,
календаря §85/§86, эффектов полномочий §127–§128 и ответа судьи §47.3.1.
Без сверки с голденом `KEY_CONFLICT` был бы невидим воротам: он не меняет
статус, и модуль, молча не считающий ключи, совпадал бы с голденом по
статусу и с соседями по пустому списку. Коды вне набора с голденом не
сверяются, только между целями.

**Аргументы запроса** сверяются с той же сигнатурой, но без `TYPE_ERROR`:
ключ ground-атома у оракула (§208) включает тип значения, поэтому запрос с
`Integer 1` к утверждению `Decimal 1` атома не находит и отвечает `NEITHER`
без issue, а разная запись одного Decimal (`0.5` и `0.50`) — один атом.
Рантайм сравнивает числа по величине и без проверки отвечал бы `TRUE_ONLY`.
Векторы — `LAY-L0-QUERY-ARG-TYPE` и `LAY-L0-QUERY-ARG-SCALE`; несогласованный
по арности или типу запрос просто не совпадает ни с одной строкой.

**Типы головы правила** проверяются планом статически (E-0077): тип слота
тела, литерала, константы или результата `mul`/`round` обязан совпасть с
объявленным типом параметра головы; сущности не проверяются, как у оракула.
Оракул отвергает такой вывод при исполнении, рантайм среза положил бы Integer
в Decimal-слот и ответил по величине. Вектора среза на это нет: исходник с
такой головой отвергает уже `lawc check` (LDC-E2104), а обязательство
оракула держат `ERR-E0077-*` над Quantity, которые план отвергает раньше.

## Запуск

```sh
python3 verify/ci/gates/backends/check_backends.py           # шаг 73/73 полного конвейера
python3 verify/ci/gates/backends/check_backends.py --require ts,py,go     # без тулчейна — отказ
python3 verify/ci/gates/backends/check_backends.py --keep /tmp/backends   # оставить проекты
python3 verify/ci/gates/backends/check_backends.py --write-manifest       # закрепить исходы
python3 engines/backends/targets/go/emit.py <ir.json> model.go            # одна цель вручную
```

Цель Python исполняется всегда: без неё ворота сообщали бы «зелено», не
исполнив ни одного бэкенда. TypeScript требует `node` и `tsc` с типами Node:
собственный проект тулчейна `targets/ts/package.json` (`npm ci` там), запасной
путь — `apps/constructor/node_modules`. Go требует `go` в `PATH`. Отсутствие
тулчейна НАЗЫВАЕТСЯ в отчёте, а не молчит; `--require` делает его отказом.

Ворота идут шагом рабочего профиля (`run_all.sh` → `work_profile.py`) на
каждом прогоне с обязательными `ts,py,go` (`py` при `--python-only`): они
читают только conformance-векторы, корпус им не нужен, и DECISION-0184 это
не нарушает. Workflow профиля ставит node, `npm ci` и go явно. В полном
конвейере — шаг 73/73.

Ворота исполняют каждый вектор ВСЕМИ доступными целями и различают три
исхода по протоколу раннера. Крах любой цели валит ворота. Исход каждого
L0-вектора закреплён в `verify/ci/data/backends-vectors.json`: `match`,
`plan: <причина>`, `query: <вид>`, `golden: результатов <n>`,
`runtime: <текст отказа>`. Закреплённый исход обязан повториться, закреплённый
вектор — существовать, незакреплённое совпадение — ошибка регистрации.
Одно число исполненных векторов позволило бы потерять старый случай и
компенсировать его новым, поэтому пол `MIN_CHECKED` остался второй защитой.

## Поставка порождённого модуля

```sh
./law dev backend corpus/clir/kz-grammar-tasymal.lawir.json --target ts --target go --out DIR
```

`deliver.py` печатает пакет в `DIR/<цель>/`: `model.*`, `runtime.*`,
`runner.*`, у `ts`, `py`, `go` ещё `api.*`, `evaluate.*` и общую часть API (у Go
ещё `go.mod`), `plan.json` и паспорт `law-backend.json`
формата `law.backend-delivery/1`: версия плана, цель, паспорт пакета из
CLIR, семантический хэш §209 самого CLIR (в снимках поле `semanticHash`
бывает нулевым, поэтому считается здесь), профиль и SHA-256 каждого файла.
Паспорт связывает модуль с каноном. Отказ плана — отказ поставки с
названной причиной и кодом 2: «меньше пакета» печатать нельзя. Ворота
печатают проекты той же функцией `emit_project`, так что проверяется ровно
то, что уезжает. Устанавливаемый пакет — следующий раздел.

## Устанавливаемые пакеты (DECISION-0423 §2.7)

```sh
arxo-codegen-package root.lawir.json --target py --name NAME --version 1.0.0 --out DIR
arxo-codegen-package root.lawir.json --target ts --name @scope/name --version 1.0.0 --out DIR
arxo-codegen-package root.lawir.json --target go --name NAME --version 1.0.0 \
  --module example.com/org/name --out DIR
```

`package.py` печатает библиотеку тем же эмиттером, что и поставка, и меняет
только раскладку и импорты:

| цель | раскладка |
|---|---|
| Python | `pyproject.toml`, `src/<импорт>/__init__.py` (реэкспорт `api` и `info()`), `api.py`, `_internal/` (рантайм, модель, `evaluate`, общая часть API — относительные импорты), `py.typed`, `api-map.json` данными пакета |
| TypeScript | `package.json` (`exports`, `types`, `files`, `engines`), `src/index.ts`, `src/api.ts`, `src/internal/`, собранный `dist/` с `.d.ts` — собственным тулчейном цели; Node не нужен рантайму (порядок UTF-8 без `Buffer`) |
| Go | `go.mod` с путём `--module`, код в корневом `package <имя>` (приватность — регистром имён), `EvaluateJSON`, `Info()`, команда `cmd/<имя>-eval` с протоколом раннера |

В каждом пакете — `README.md` с исполняемым примером (факты по входам первого
вопроса и сам вопрос), `api-map.json` формата `law.codegen-api-map/0.1`
(функция → вопрос или вход → StableId → производящие правила → якоря
источника) и паспорт `law-backend.json`: поля поставки плюс `distribution`
(имя, версия, у Go — модуль и пакет), `generator` (версия arxo-codegen),
`coreKit` (SHA-256 манифеста комплекта ядра), `capabilities` плана и `api`
(`abi: law.codegen-api/1`, `valueFormat: law.codegen-value/1`) — провенанс
артефакта, а не доказательство вычисления; тот же текст вшит в пакет и
отдаётся `info()` / `Info()`.

**Значения между канонами.** `to_value`/`from_value` (Python),
`toValue`/`fromValue` (TypeScript), `ToValue`/`FromValue` (Go): текст
`law.codegen-value/1` — канонический JSON литерал-терма §53 (ключи по
порядку, без пробелов, Integer — числом, без экранирования HTML). Один терм
даёт одни байты во всех трёх языках, поэтому значение из модуля одного канона
передаётся в модуль другого или в сервис без знания языка. Раннер и план ворот в пакет не входят. Минимальные
версии тулчейна — константы целей `TOOLCHAIN_MIN` (Python 3.13, Node 20,
Go 1.25), равные версиям проверок CI; тест сверяет их с
`.github/workflows/candidate.yml`. Имя пакета — параметр; для канонов,
которые публикует Arxo, — семья `-lib`: `@arxo/canon-<имя>-lib`,
`arxo-canon-<имя>-lib`, `github.com/arxohq/canon-<имя>-lib` (выбор владельца
28.09.2026); `@arxo/canon-<имя>` без суффикса — пакет самого канона
(DECISION-0349 §8).

**Проверка.** `check_packages.py` (`arxo-codegen-check-packages`, шаг приёмки
`ci/verify.py`): для API-пакетов комплекта печатает пакет каждой цели и ставит
его так, как получит потребитель, — Python каталогом из `src/`, TypeScript
тарболом `npm pack` без сети, Go модулем-потребителем с `replace`; задания
`check_api` исполняются через опубликованную поверхность пакета и обязаны
совпасть с плоским API Python; пример README обязан исполниться; канарейка
портит статус и issues в установленном Python-пакете — расхождение обязано
найтись.

## Обзор корпуса и первый потребитель

`./law dev backend-survey --report FILE` проходит все снимки CLIR (с
миром импортов по снимкам; третий обзор — `SURVEY-2026-09-14-editions.ru.md`,
план 0.7: 778 снимков, строится у 60; четвёртый —
`SURVEY-2026-09-14-l1.ru.md`, план 0.8: строится у 106 — L0 те же 60, L1
46, из них `kz.*` 26; пятый — `SURVEY-2026-09-14-closure-aggregates.ru.md`,
план 0.9: 780 снимков, строится у 123, из них с `aggregate` 10, с `closure` 9;
шестой — `SURVEY-2026-09-15-norms.ru.md`, план 0.10: 802 снимка, строится у
151, с `norm` 24, из них `kz.*` 12)
(`corpus/clir/` и собственные снимки `packs/`) обзорным режимом плана,
который собирает ВСЕ отказы пакета, а не первый. Отчёт 09.09.2026 —
`SURVEY-2026-09-09.ru.md`, второй (план 0.5, 14.09.2026) —
`SURVEY-2026-09-14.ru.md`: 771 снимок, план строится у 50 против 31, из
19 новых пакетов 8 — словари `vocab.*` с ключами, остальные — грамматика,
фикх и три акта КЗ с константами в теле. Первый же обзор дал рычаг: узел `publication`
(провенанс §27.3, из семантического хэша исключён по §28) отвергал 621
пакет; после его пропуска план строится у 31 пакета вместо 5. Два следующих
рычага обзора оказались не тем, чем читались: «константа не найдена»
(193 пакета) почти целиком была членами перечислений и параметрами A3, а
131 «несовпадение типа головы» — ложным срабатыванием статической
типизации, приписывавшей Money любой операции. После плана 0.3 (Date,
константы, перечисления) принятых по-прежнему 31: у пакетов с датами и
перечислениями всегда есть вторая причина. Дальше по пакетам:
`defeasible`/`defeater` (367), объявления вне среза (451: `norm_template`,
`priority_rule`, `closure_policy`, `external_decl`), типы (362: `Quantity`,
`Event`, `Rational`, `Instant`), функции (179) — всё это семантика.

`./law dev backend-consumer <пакет>` поставляет пакет корпуса, понижает его
собственные `.lawtest` компилятором и спрашивает поставленный модуль.
Наборы берутся обнаружением ворот `.lawtest` (`corpus/lawtests/discovery.py`):
у каждой части свой мир (`world`, `concat`) — он же мир плана — и
resolve-контекст §23 для понижения; часть с `merge = "concat"` или снимками
§47.2 — названный пропуск. Сценарии понижаются через `./law engine
lower-test --imports`; файл, который не понижается, — ошибка; если дело несёт
отрицательный факт, план строится с возможностью `negation`. Без имён
пакетов потребитель сверяет все пакеты манифеста
`verify/ci/data/backends-consumers.json` (число сценариев, исходы, статусы;
пол `MIN_CONSUMER_MATCHED`), `--write-manifest` закрепляет названные. Замер
печатается и не закрепляется: страты, правила, пики кандидатов и групп
(`STATS` модели `py`, третий аргумент раннера) и время по целям. 14.09.2026
(DECISION-0237 этап 6): `kz.corpus.stazh_gossluzhby`, `kz.grammar.bas_arip`,
`kz.grammar.kirme_sozder` — 144 из 144 на `ts,py,go`; без дефитеров модуль
меняет 7 ответов, без поражаемых правил — 32. 15.09.2026 (DECISION-0242):
вопрос `positions` сверяется по `result_kind`, `normative_status`,
`evaluation_status` и наблюдениям `position`, `position_status`,
`position_count`, `positions_present` (порт `lawref.lawtest`); `concat`
наборов входит в мир плана. 19 пакетов с позициями — 825 совпадений из 841
сценария, без норм-правил модуль меняет 79 ответов, без эффектов — 16; пол
`MIN_CONSUMER_MATCHED` 336 → 1161.
09.09.2026: 24 принятых пакета со сценариями (грамматика казахского,
`myco.primety`, `stacks.sets`, разрешения mathlib/isabelle), 300 сценариев,
298 совпали, 2 пропущены как `why_not`, расхождений и крахов нет. Это и
есть первый потребитель: правила грамматики с сущностями и Text.

## Замер 09.09.2026 (план 0.2, протокол исходов)

| исход | штук |
|---|---|
| L0-векторов рассмотрено | 340 |
| отказ плана | 100 |
| запрос не `truth` | 189 |
| голден без результата (фатальная диагностика) | 12 |
| исполнено всеми тремя целями | 39 |
| совпало с голденом и между целями | 36 |
| единогласный отказ рантайма | 3 |
| крах, расхождение между целями, расхождение с голденом | 0 |
| сертификация рантайма | 111 случаев × 3 цели |

Против первого замера (25 / 19 / 6): два вектора ушли из «отказа рантайма»
в совпадение (`ERR-E0041`, `ERR-E0046-CASE-ARG-TYPE` — Integer в TypeScript
и проверка типов дела), два — в отказ плана (`T151`, `T155` — параметры
Rational и Instant), один — из совпадения в отказ плана по ключам §45.1
(`LAY-L0-KEYS`), один крах Go на `const_ref` стал названным отказом; два
новых вектора запроса (`LAY-L0-QUERY-ARG-*`) совпали после проверки типов
запроса. Шаг ворот целиком — 12 секунд, включая сертификацию, `tsc` и
24 сборки `go build`.

Замер 14.09.2026 (план 0.5, цели `ts,py,go`): рассмотрено 385, отказ плана
127, запрос не `truth` 201, голден без результата 14, исполнено 43, совпало
40, отказ рантайма 3, расхождений и крахов 0. Из отказа плана в совпадение
ушли `LAY-L0-KEYS` (`KEY_CONFLICT` сверен с голденом) и
`ERR-E0052-DECIMAL-CANON` (константа в теле); пять `ERR-E0165-WILDCARD-*`
сменили причину на названный отказ по `_` §98, шесть векторов с ключами —
на следующую причину (календарные функции, отрицательный литерал).
Канарейки: сдвиг значения фильтра в порождённом коде даёт `NEITHER`, эмиттер
с `key_conflicts` ≡ 0 валит ворота расхождением issues на `LAY-L0-KEYS`.
Десять целей разом не прогонялись (тяжёлый прогон); семь остальных
эмиттеров и раннеров проверены только пробой версии плана.

Замер 14.09.2026, вечер (план 0.6, DECISION-0234, цели `ts,py,go`):
рассмотрено 385, план построен и вид поддержан 240, совпало 49, отказ
рантайма 191 (единицы, границы, календарь, Rational — названные отказы),
расхождений и крахов 0. Девять новых совпадений — `term` и `collect`.
Поставка `kz.grammar.tasymal`: `api.ts` под `tsc --strict`, `api.go` под
`go build`, `api.py` на сценарии `KZ-TAS-01` — `TRUE_ONLY` с фактом,
`NEITHER` без, снова `TRUE_ONLY`: состояние между вызовами не протекает.

Замер 14.09.2026, ночь (план 0.8, DECISION-0237, цели `ts,py,go`, шаг
ворот 2 мин 36 с): L0 — рассмотрено 385, совпало 56 (было 51, все прежние
целы; в совпадение ушли `ERR-E0018-MONOTONE-EVAL`, `T171`, `T002`,
`LAY-L0-NEGATIVE-STRICT-HEAD`, `…-BOTH`), отказ плана 119, отказ рантайма
191; L1 — рассмотрено 44, совпало 24, структурных отказов 3
(`LAY-L1-STATUS-CYCLE`, `T009`, `ERR-E0099-UNLESS-HEAD-UNBOUND`), отказ
плана 17 (агрегаты в гардах 7, прочтения 4, приоритеты 3, квантор,
Rational, поздний производитель); расхождений и крахов 0. API — 48 вопросов
на 5 пакетах; 18 заданий на двух L1-пакетах дополнительно сверены с
оракулом вне ворот — 0 расхождений. `MIN_CHECKED` 51 → 56,
`MIN_CHECKED_L1` = 27, манифест 429 исходов.

Замер 14.09.2026, ночь (план 0.9, DECISION-0239, цели `ts,py,go`): L0 —
совпало 66 (+10 к 56: closure — `LAY-L0-CLOSURE`, `…-BINARY`,
`…-EFFECTIVE-IN/OUT`, `ERR-E0097`, `ERR-E0113-CLOSURE-TWO-LEVELS`; агрегаты —
`LAY-L0-AGGREGATE-DERIVED-STRICT`, `LAY-L0-BODY-AGGREGATE`, `T164`,
`ERR-E0112-COMPLETION-HEAD-AGGREGATE`), L1 — 31 совпадение и 5 структурных
отказов (+7 `T199`–`T230`, остальное — новые векторы E-0172/E-0173 соседней
работы), расхождений и крахов 0; полы 66 и 36, манифест 439.

Замер 15.09.2026 (план 0.10, DECISION-0242, цели `ts,py,go`, шаг ворот
около 3 мин): L0 — совпало 73, L1 — 31 и 5 структурных отказов, L2 —
совпало 11 (`LAY-L2-DUTY`, `…-MAINTENANCE-SHORT-WINDOW`,
`…-MAINTENANCE-OUT-OF-DOMAIN`, `…-CONFLICT-TWO-LIBERTIES-NONE`,
`…-NORM-BODY-AGGREGATE`, `T014`–`T017`, `T032`, `T146`), отказ плана 101, единогласный
отказ рантайма 1 (`LAY-L2-CONFLICT-EXPLICIT-DUTIES`, `incompatible` в
деле), L3 — совпало 9 (`LAY-L3-LIBERTY-*`, `LAY-L3-RECURSION-CLOSURE`,
`T005`, `T018`, `T019`, `T031`, `EVT-NO-PROCEDURE-L1-CONSERVATIVITY`);
расхождений и крахов 0. `T182` сменил причину отказа на конфликт позиций
по ключу §45.1 (K5). Пол `MIN_CHECKED_L2` = 20, манифест 744 исхода.
Структурный голден L1 при отказе плана по форме вне среза (условный терм
головы `ERR-E0176-DEFEASIBLE-SELF-CYCLE`) закрепляется отказом плана;
ошибкой ворот остаётся построенный план и отказ другой структурной
причиной.

Замер 16.09.2026 (план 0.15, DECISION-0266–0268, цели `ts,py,go`, шаг
ворот около 4 мин без конкуренции): рассмотрено L0 507, L1 94; план построен и
вопрос поддержан у 445; L0 — совпало 225, отказ плана 110, отказ рантайма 151;
L1 — 32 и 5 структурных отказов; L2 — 26; L3 — 9; расхождений и крахов 0.
Манифест перепинован целиком — 913 исходов (292 совпадения): до этого 77
совпадений стояли незакреплёнными (в том числе `LAY-L0-RND-*` 0259) и 145
исходов изменились с прежних закреплений, большинство — из 0252/0256/0259,
чей перепин откладывался; из 0266–0268 — `ERR-E0084-MONTH-*` 7 и
`ERR-E0165-WILDCARD-*` 4 в совпадение, 16 новых векторов (14 совпадений, 2
отказа плана по замыслу). Полы `MIN_CHECKED` 82 → 225, `MIN_CHECKED_L1`
36 → 37, `MIN_CHECKED_L2` 21 → 35. Обзор корпуса — `SURVEY-2026-09-16-values.ru.md`:
228 из 826, план 0.15 у 7 пакетов; потребители `bio.mendel` 5/5 и
`eu.social_security.coordination` 50/50 закреплены.

Замер 16.09.2026, вечер (план 0.16, DECISION-0269, цели `ts,py,go`):
рассмотрено L0 507, L1 104; план построен и вопрос поддержан у 454; L1 — 41
совпадение и 5 структурных отказов (+9 `LAY-L1-PRIORITY-DATED-*`, вектор
цикла на отрезке — отказ плана), L0, L2, L3 прежние; расхождений и крахов 0;
манифест 923 исхода, `MIN_CHECKED_L1` 37 → 46. Обзор корпуса —
`SURVEY-2026-09-16-priorities.ru.md`: 242 из 826, план 0.16 у 14 пакетов;
потребители — 12 пакетов закреплены (+250 совпадений, `MIN_CONSUMER_MATCHED`
2181 → 2431), `kz.corpus.kacd_svod_pravil` с 27 названными отказами
`AMBIGUOUS_CALENDAR`.

Замер 16.09.2026, поздний вечер (план 0.18, DECISION-0273, цели `ts,py,go`,
шаг ворот 8 мин 37 с): L0 — совпало 251 (+17: `LAY-L0-A3-PARAM-*`,
`LAY-L0-FN-*`, `LAY-L0-LET-HEAD`, `LAY-L0-MATCH-*`, `T071`, `T158`,
`T304`, `T324`–`T326`, `T351`), отказ плана 103, отказ рантайма 150; L1 —
54 и 5 структурных отказов; L2 — 26; L3 — 9; расхождений и крахов 0;
манифест 943 исхода, `MIN_CHECKED` 234 → 251. Обзор корпуса —
`SURVEY-2026-09-16-functions.ru.md`; потребители и их полы — в журнале
`docs/status/2026-09-16.ru.md`.

Пол храповика `MIN_CHECKED` в воротах: снижение числа исполненных векторов —
регрессия среза, а не «просто меньше». Поднимается вместе с расширением плана.

## Чего стоит целевой язык (замер 10.09.2026, план 0.4)

| цель | эмиттер | рантайм | раннер | харнесс |
|---|---:|---:|---:|---:|
| `ts` | 184 | 251 | 124 | 72 |
| `py` | 145 | 344 | 141 | 97 |
| `go` | 181 | 471 | 238 | 162 |
| `rb` | 149 | 255 | 113 | 73 |
| `rs` | 189 | 448 | 163 | 106 |
| `java` | 156 | 243 | 140 + 148 JSON | 91 |
| `cs` | 175 | 243 | 140 | 90 |
| `kt` | 154 | 196 | 115 + 103 JSON | 67 |
| `erl` | 184 | 238 | 118 | 64 |
| `ex` | 176 | 227 | 115 | 62 |
| `lua` (14.09, план 0.5) | 229 | 541 | 154 + 133 JSON | 82 |
| `swift` (14.09, план 0.5) | 209 | 528 | 124 + 208 JSON | 105 |
| `php` (14.09, план 0.5) | 207 | 660 | 174 + 144 JSON | 84 |
| `dart` (14.09, план 0.5) | 183 | 293 | 142 + 166 JSON | 99 |
| `bsl` (14.09, план 0.6) | 310 | 858 | 184 + 243 JSON | 113 |

Эмиттер почти одинаков между языками. Дорожает рантайм там, где нет
точных целых или они многословны (`math/big` в Go, `num-bigint` в Rust),
и раннер там, где нет JSON в стандартной библиотеке (Java и Kotlin несут
свой разбор, числа в нём сохраняются текстом). Число строк «втрое» не
предсказывается: предсказывают бигинт и JSON.

Что ловит сертификация при переносе, одинаково в разных языках: собственные
`max`, `abs` и `min` рантайма затеняют встроенные (Python, Erlang, Elixir);
общий каталог сборки складывает все модели в один бинарь (Rust);
печать UTF-8 как latin1 (`~s` в Erlang); порождённое имя сталкивается с
именем рантайма (`add` в Go). Ни одно из этого не видно векторам, пока
ответ случайно совпадает.

## Что целевой язык навязывает сам

- Go отвергает неиспользуемую переменную цикла, и эмиттер обязан её гасить;
  у Go одно пространство имён — вставка строки в модели зовётся `addRow`.
- Целые произвольной длины есть не везде: `bigint`, `int`, `math/big`,
  `BigInteger`, `num-bigint` — разные решения одной задачи.
- Порядок обхода обязан быть детерминированным: строки лежат в массивах, а не
  в множествах и словарях с недетерминированным порядком; у Erlang и Elixir
  состояние таблиц чистое и передаётся через правила.
- Rust собирает все проекты в общий `--target-dir`, поэтому крейт и бинарь
  именуются по каталогу вектора; C# запускается как `dotnet app.dll`,
  apphost без `DOTNET_ROOT` не стартует; Kotlin, Erlang и Elixir на этой
  машине поставлены Homebrew и в workflow профиля не входят.
- Lua и Swift не дают ни длинных целых, ни пригодного JSON: у обоих свой
  модуль по основанию 10^7 со знаком (деление в столбик, цифра частного
  двоичным поиском) и свой разбор, где число — исходная запись. В Swift
  разбор нужен потому, что `JSONSerialization` отдаёт числа как NSNumber.
- Lua: массивы с единицы — эмиттер сдвигает номера столбцов и ключей;
  экранов `\uXXXX` в строке нет — литерал печатается экранами `\ddd`; у
  функции не больше 200 локальных имён — правила лежат в массиве функций.
  Отказ — таблица с метатаблицей `Refused` в `error`, крах — любое другое
  значение ошибки. Проверено на Lua 5.5.0; дескриптор принимает 5.3+.
- Swift: код верхнего уровня допустим только в `main.swift` — раскладка
  кладёт раннер или харнесс под этим именем; отказ — `throw`, поэтому каждое
  выражение модели с вызовом рантайма стоит под `try`; внутри `enum RT`
  голые `max`/`min` означают функции рантайма — целые сравниваются через
  `Swift.max`. `swiftc` на проект — десятки секунд, поэтому у цели есть
  `build_many`: сборки всех проектов ворот идут параллельно (одного бинаря
  на все модели не выходит — у каждой тот же `enum Model`). Foundation не
  подключается. Шаг ворот `py,swift` — 644 с при load average около 100.
- PHP: `==` сравнивает строки, похожие на числа, как числа (`"1.0" == "1"`),
  поэтому текст сравнивается только `===`, а границы окна §31 — `strcmp`;
  инициализатор статического свойства допускает только константу — константы
  §54 и собственные факты пакета печатаются функциями; bcmath и gmp — не ядро
  (в образе `php:8.3-cli` их нет), длинные целые свои, как у Lua; `json_decode`
  отдаёт дробную запись как float — числа разбираются записью, строки JSON
  разбирает ядро. Литералы модели — в одинарных кавычках: без интерполяции `$`.
- Dart: у `BigInt` `%` евклидов, а `~/` усекает — остаток с усечением только
  `remainder`; кода верхнего уровня нет — факты пакета в `packageFacts()`;
  `exit()` не дожидается записи stdout, поэтому код выхода ставится через
  `exitCode`; `jsonDecode` превращает длинное целое в double — разбор свой;
  в строковом литерале `$` — интерполяция и экранируется. `dart run file.dart`
  работает без pubspec, сборки нет.
- PHP, Dart и Scala на этой машине не поставлены; проверены в docker через
  обёртки PATH (`php`, `dart`, `scala-cli` → `docker exec` в долгоживущий
  контейнер) и, как Kotlin и Erlang, в workflow профиля не входят. Шаги ворот:
  `py,php` — 21,5 с, `py,dart` — 56 с.
- BSL (встроенный язык 1С) исполняется OneScript (`oscript`), без лицензии
  платформы; решение и пределы — DECISION-0235 (черновик). `Число` у OneScript —
  `.NET decimal` (28–29 значащих цифр), у платформы — 38 цифр, и оба режут
  дробь МОЛЧА (проба OneScript 2.2.0: `1/3` и дробь из 30 знаков — без ошибки),
  громко падает только переполнение целой части. Поэтому длинные целые свои:
  разряды по основанию 10^7 в `Массив`, масштаб рядом; частное целых — только
  через `%`, а `%` усекает к нулю (`-7 % 3 = -1`), евклидов остаток свой.
  Цифры печатаются `Формат(БАЗА + р, "ЧГ=0")`: `Строка()` у платформы
  группирует разряды, `Формат(0)` без `ЧН` — пустая строка. Регистр не
  различается, `И` — ключевое слово, а `pow`, `round`, `min`, `max`, `date`
  методом класса быть не могут, — операции контракта зовутся `op_*`, правила
  `Правило_<name>` со сверкой уникальности без учёта регистра. JSON свой: число
  остаётся записью, `ЗаписьJSON` переносит строки. Модули — классы
  `ПодключитьСценарий`; отказ — исключение с префиксом `law-refused|`,
  необработанное исключение OneScript печатает в stdout с кодом 1 (крах);
  stdout — UTF-8 без BOM при исходниках с BOM. Интерпретатор перебирает
  декартово произведение атомов на порядки медленнее: у `kz.corpus.bin`
  (правило из пяти атомов) дело шло 43 с, поэтому эмиттер выносит литералы из
  циклов и ставит ПРЕФИКС проверок, которые отказать не могут (фильтры и
  соединения не над Money), в цикл, где связаны их строки, — 0,21 с. На этой
  машине `oscript` — релиз 2.2.0 `osx-arm64` без подписи кода (ядро убивает
  его с кодом 137 до ad-hoc `codesign -s -`); в workflow профиля цель не
  входит, как Kotlin, Erlang и Elixir. Шаг ворот `py,bsl` — 11 с.

## Границы среза

Вне плана (на 28.09.2026): условные приоритеты §117/§118, прочтения §152–§157, ограничения
§93.1, динамическая ссылка `reference` §30.2, Magnitude и алгебра единиц
(`magnitude`, `unit_convert`, `unit_exponent`), слой событий §79–§82 сверх
идентичности события (запись события и её поля, свёртка §161.1), судейский
конъюнкт с вычисляемым аргументом и гард с вызовом/`if` в обходе суждения
(UNDETERMINED — безопасная сторона), голый `const_ref`
параметра A3 в голове или атоме (позиция идентичности), параметр A3 по оси
Instant, функции с `record`/`list`/рекурсией и образцы `match` с полями,
кванторы в телах, `average`,
жизненный цикл редакции с прерывистой применимостью, вопросы `why_not` и
`interpretation_analysis`, порядок над перечислением и сравнение членов
разных перечислений (E-0067), порядок над Boolean, монотонный агрегатный
гард над выводимым предикатом (G2) — а с ним и удержание issue такого гарда
до неподвижной точки (errata E-0198): удержание наблюдаемо только на растущей
коллекции, а растущая коллекция в срез не входит (разбор — в G3 выше). Что вошло в срез
позднее первых версий, описано разделами выше: замыкание §70 и агрегаты
(0239), нормы L2 и `positions` (0242), календарь §84–§86 (0245), Rational
(0252), Quantity (0256), `round`/`div_round` (0259), Boolean, Natural,
границы месяца и `_` §98 (0266–0268), датированные приоритеты (0269),
условный терм и вычисляемый аргумент тела (0270), функции §46, параметры
A3, `let` и `match` (0273), канал суждения §47.3 (0274), импорт чужих
пакетов (0236), инертные конструкции манифеста (0406, W1), алиасы типов §40,
std-сущности §261 и идентичности `LegalContext`, `LegalRole`, `Jurisdiction`,
Instant §2.15 и событие как идентичность §79 (W2),
поподстановочная ошибка головы и агрегат в голове без политики `empty`
(E-0265, W5). Каждая
из форм вне плана — названный отказ плана, а не тихий пропуск. Внутри среза: `Date`
как хранимое и сравнимое по дню значение (`LAY-L0-DATE-ORDER-*`),
константы §54 в деле и запросе (`ERR-E0032`), члены перечислений в фактах
и гардах равенства (`ERR-E0067-ENUM-GUARD-*`), константы в атомах тела
(`ERR-E0052-DECIMAL-CANON`), ключи отношений §45.1 с `KEY_CONFLICT`
(`LAY-L0-KEYS`).

Пропускаются молча только узлы провенанса §194, которые по SPEC не меняют ни
одной нормы: `source_publication`, `publication` (§27.3, вне семантического
хэша по §28), `fragment`. `source_work` §28 с 27.09.2026 — инертная
конструкция манифеста со свидетелями (DECISION-0406), а не молчаливый пропуск.

Про ключи отдельно (решение 09.09.2026, история): ключевая проекция даёт
`KEY_CONFLICT` при разных accepted-кортежах, evaluator при этом ничего не
выбирает и не удаляет, так что ответ бэкенда совпадал бы с голденом
(`LAY-L0-KEYS`). Но диагностику срез тогда не порождал, а поле, которое план
молча не читает, — именно тот пропуск, который DECISION-0188 запрещает.
Поэтому ключи до плана 0.5 были отказом: четыре вектора ушли из совпадения и
отказа по типам в отказ по ключам. С плана 0.5 (14.09.2026) модуль считает
`KEY_CONFLICT` сам, и ворота сверяют его с голденом.

Доказательство и байтовый контракт сюда НЕ входят: для профиля
`core-auditable` пришлось бы перенести на каждый целевой язык канонический
порядок §208, семантический хэш §209 и рендер proof graph. Это отдельная
работа и отдельное решение.
