# lawref.scenario

*module*

Shared scenario execution and edition projection ([§31](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/07-part-vii-source-and-document-model.ru.md#31-lifecycle), [§92](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/13-part-xiii-context-scope-applicability.ru.md#92-edition-applicability), [§168](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/24-part-xxiii-cases-snapshots-evaluation.ru.md#168-case-file)).

Independent Python implementation, also consumed by private impact tools.
Historical error names are retained for compatibility with internal callers.

## lawref.scenario.ImpactError

*class*

```python
class ImpactError(ValueError)
```

Bases: `ValueError`

Отказ входа отчёта: дата проекции не есть `LegalTime` ([§2.15](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/01-part-i-audit.ru.md#215-legal_time-date-было-слишком-узким)/[§82](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/12-part-xii-events-actions-temporal-model.ru.md#82-temporal-axes)).

[View source](https://github.com-arxohq/arxo-io/law/blob/2edc2b92ce22b52e03f4081d2769a58229684379/engines/lawref/lawref/scenario.py#L16-L17)

## lawref.scenario.ImpactWorldError

*class*

```python
class ImpactWorldError(ImpactError)
```

Bases: `lawref.scenario.ImpactError`

Отказ мира отчёта О-2 (DECISION-0193): программа предъявлена не целиком,
с лишним пакетом или на байтах, разошедшихся с закреплёнными. Код хоста
(FFI) — `IMPACT_WORLD`; текст отказа у CLI и FFI один.

[View source](https://github.com-arxohq/arxo-io/law/blob/2edc2b92ce22b52e03f4081d2769a58229684379/engines/lawref/lawref/scenario.py#L19-L24)

### lawref.scenario.ImpactWorldError.code

*attribute* · *class attribute* · *instance attribute*

```python
code = 'IMPACT_WORLD'
```

[View source](https://github.com-arxohq/arxo-io/law/blob/2edc2b92ce22b52e03f4081d2769a58229684379/engines/lawref/lawref/scenario.py#L24-L24)

## lawref.scenario.edition_projection

*function*

```python
def edition_projection(document: dict, legal_time: str, scope: frozenset[str] | None = None) -> tuple[dict, dict]
```

Состояние корпуса as-of `legal_time`: (проекция, мета).

`scope` (DECISION-0193) — id узлов, о которых говорит МЕТА; проекция
при этом снимает узлы всего документа. Связанный мир проецируется
целиком — `editionsAsPresented` снимает условие 3 [§92](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/13-part-xiii-context-scope-applicability.ru.md#92-edition-applicability) со всех узлов
программы, и неспроецированная зависимость осталась бы живой на любую
дату, — а датировка, снятые узлы, состояния редакций и повисшие ссылки
отчёта называются по корню; о зависимостях — отдельной метой того же
вида. Без `scope` мета — обо всём документе, как прежде.

Нормативный узел снимается, если ВСЕ его разрешившиеся якоря ведут в
редакции, неприменимые на момент (машина D1 — `applicability.edition_state`
по lifecycle [§31](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/07-part-vii-source-and-document-model.ru.md#31-lifecycle)); достаточно одной применимой, чтобы узел остался.
Узел без якорей — вне датировки, остаётся. Якорь, не разрешившийся ни во
фрагмент, ни в редакцию, узел не датирует и называется в мете поимённо
(`unresolvedAnchors`) — принцип [§249](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/33-part-xxxii-interoperability.ru.md#249-legalruleml-adapter): не угадываем, а сообщаем.

Заякоренный нормативный узел, у которого не разрешился НИ ОДИН якорь,
остаётся применимым на любую дату — то есть выпадает из ответа целиком.
Мета считает такие узлы отдельно (`undatedNodes` поимённо,
`anchoredNodeCount` — знаменатель): именно по ним `verdict` отличает
«право не менялось» от «датировать было нечем».

Повисшие ссылки проекция не ЧИНИТ, но ДИАГНОСТИРУЕТ (остаток v1 закрыт
27.08.2026): выживший priority_rule [§116](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/17-part-xvi-priority-declarations.ru.md#116-direct-priority), чьи higher/lower ведут в
снятый узел, называется в мете поимённо (`danglingReferences`) — вход
движку отдаётся как есть, что с висом делать — решает читатель отчёта
([§249](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/33-part-xxxii-interoperability.ru.md#249-legalruleml-adapter): не угадываем, а сообщаем). Автопочинка была бы семантикой в
инструменте: что означает приоритет без одной из сторон — вопрос [§116](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/17-part-xvi-priority-declarations.ru.md#116-direct-priority),
а не оркестрации.

[View source](https://github.com-arxohq/arxo-io/law/blob/2edc2b92ce22b52e03f4081d2769a58229684379/engines/lawref/lawref/scenario.py#L93-L208)

## lawref.scenario.link_presented_world

*function*

```python
def link_presented_world(document: dict, world: list[dict] | None, executing: bool) -> tuple[dict, frozenset[str] | None, list[dict]]
```

Программа отчёта О-2 (DECISION-0193): (программа, id узлов корня, состав
мира). Без предъявленного мира и без исполнения — сам документ, `None` и
пустой состав: байты отчёта прежние.

Программа исполнения — корень и его закреплённое замыкание ([§23](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/06-part-vi-package-and-module-system.ru.md#23-import), [§24](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/06-part-vi-package-and-module-system.ru.md#24-visibility);
DECISION-0170: мир W = замыкание импортов C). До 11.09.2026 отчёт исполнял
один CLIR как программу целиком: у `us-liver-status` расчёт MELD [§9.1](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/04-part-iv-project-and-files.ru.md#9-стандартная-структура-проекта).D
без `unit_decl` зависимости `units.si` ([§49.2](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/08-part-viii-type-system.ru.md#492-объявление-единиц-errata-e-0136-e-0150)) падал в UNIT_UNKNOWN, и
отчёт печатал SEMANTIC-CHANGE с «изменившимся» контрольным делом —
утверждал изменение, которого в праве нет. Поэтому неполный мир при
исполнении сценариев — отказ, а не исход. Без сценариев исполнять нечего
(MCP `law_editions` спрашивает одну ось редакций корня), и один документ
остаётся допустимым входом.

Порядок отказов — от формы входа к байтам: документ без имени → дубль →
непредъявленный пакет замыкания → лишний пакет → связывание [§23](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/06-part-vi-package-and-module-system.ru.md#23-import) (пины
E-0123, коллизии StableId [§14](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/05-part-v-lexis-and-basic-syntax.ru.md#14-идентификаторы)) → версия семантики [§168.4](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/24-part-xxiii-cases-snapshots-evaluation.ru.md#1684-пакет-дело-и-операция-ask-decision-0165). Замыкание
строится обходом `imports` CLIR — ровно C [§24](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/06-part-vi-package-and-module-system.ru.md#24-visibility); подтверждающих импортов
прелюдии `law.std.*` в `imports` CLIR нет (замер корпуса 11.09.2026).

[View source](https://github.com-arxohq/arxo-io/law/blob/2edc2b92ce22b52e03f4081d2769a58229684379/engines/lawref/lawref/scenario.py#L262-L344)

## lawref.scenario.projection_rule_loss

*function*

```python
def projection_rule_loss(document: dict, meta: dict) -> dict | None
```

Что проекция [§31](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/07-part-vii-source-and-document-model.ru.md#31-lifecycle)/D1 сняла с даты: {lost, total, anchored, editions,
examples} — или None, если не сняла ничего.

ЗАЧЕМ ЭТО ВООБЩЕ ЕСТЬ. Инструменты слоя О спрашивают право НА ДАТУ, то
есть считают по проекции, а не по всему пакету. Правило, снятое с даты, в
ответе неотличимо от правила, которому не хватило факта: разбор блокеров
[§185](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/26-part-xxv-proof-graph-and-explanations.ru.md#185-why-not) честно печатает «не закрыто: sogovor_qalyn_mal», а поиск лазеек
честно печатает «путей не найдено» — и читатель делает ровно неверный
вывод, что дело чего-то не доказало, а лазеек нет. Инструмент при этом
ЗНАЕТ причину: `edition_projection` возвращает вторым значением состояния
редакций и список снятых узлов.

Замерено на `kz-tuystyq` (process-run): из 93 правил на ЛЮБУЮ дату
остаётся 5 — те, что компилятор породил из самой `procedure` и потому не
заякорил; все 88 заякоренных сняты, потому что редакция `TUYSTYQ_RU` не
объявляет ни `adopted`, ни `in_force` (обычай не вводили в действие
датой, и пакет объясняет это в 06-sources.law). На
`vory.corpus.ponyatiya` (loophole) снимаются ВСЕ 28 правил — там ни одна
из семи редакций не несёт lifecycle вовсе.

Считаются ПРАВИЛА, а не узлы вообще: снятый фрагмент или symbol_decl на
вывод не влияет, и мешать их в одно число значило бы пугать шумом.

`anchored` — знаменатель для вопроса «осталось ли ХОТЬ ЧТО-ТО, что
вообще можно датировать». Правило без якорей вне датировки по построению
([§31](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/07-part-vii-source-and-document-model.ru.md#31-lifecycle): узел без якорей остаётся применимым на любую дату), и его выживание
не говорит о праве акта ничего: у `intl-istanbul-draft-2022` снято 136
правил из 149 — то есть ВСЕ 136 заякоренных, а уцелевшие 13 порождены
компилятором из `procedure` и якорей не несут. Считать такую проекцию
«частичной потерей» значило бы пропустить ровно тот случай, ради которого
счёт и заведён.

[View source](https://github.com-arxohq/arxo-io/law/blob/2edc2b92ce22b52e03f4081d2769a58229684379/engines/lawref/lawref/scenario.py#L210-L257)

## lawref.scenario.run_scenario

*function*

```python
def run_scenario(document: dict, scenario: dict, extra_options: dict | None = None, editions_as_presented: str | None = None) -> tuple[str, dict]
```

Исполнить сценарий над документом: ('result', doc) | ('error', {...}).

Контракт отказов — тот же, что у `run_vector`: pre-evaluation исключение
(календарь, лимиты) становится значением, а не падением инструмента, — Иначе
сценарий, роняющий ОДНУ из редакций, обрушил бы весь отчёт вместо того,
чтобы показать расхождение исходов.

`extra_options` — ВНЕШНИЕ РЕСУРСЫ вызывающего (сегодня: байты календарного
набора [§85](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/12-part-xii-events-actions-temporal-model.ru.md#85-calendar-snapshot)), а не вход семантики. Они кладутся мимо `case`, потому что
`caseHash` [§170](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/24-part-xxiii-cases-snapshots-evaluation.ru.md#170-evaluation-input-manifest-и-execution-receipt) канонизирует дело целиком, а байты в JSON не канонизируются
вовсе: положить их в `case.options` значит уронить КАЖДЫЙ вызов CanonError.
Пары «id набора» довольно, и она в деле уже есть — `options.calendarSnapshot`.

`editions_as_presented` ([§92](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/13-part-xiii-context-scope-applicability.ru.md#92-edition-applicability), errata E-0167) — дата, на которую состав
редакций `document` уже спроецирован ВЫЗЫВАЮЩИМ: при ней `evaluate` не
исполняет условие 3 [§92](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/13-part-xiii-context-scope-applicability.ru.md#92-edition-applicability) по дате права дела. Без неё сценарий над
ПРОЕКЦИЕЙ исполнялся бы по пересечению двух проекций — на дату проекции и
на дату дела; см. `build_edition_report`.

[View source](https://github.com-arxohq/arxo-io/law/blob/2edc2b92ce22b52e03f4081d2769a58229684379/engines/lawref/lawref/scenario.py#L26-L59)
