# lawref.canon

*module*

Канонический JSON-emitter (WP-04; SPEC [§208](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#208-canonical-serialization) + errata E-0002).

Правила v1 (кросс-языковой контракт с engines/lawc/law-canon — байтовая идентичность
проверяется в CI):

- UTF-8, без незначащего whitespace;
- все строки (ключи и значения) ОБЯЗАНЫ быть в NFC — иначе CanonError: NFC-
  нормализация — обязанность normalizer-а, emitter только проверяет. Исключений
  нет: исходные байты official text block (SPEC [§13](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/05-part-v-lexis-and-basic-syntax.ru.md#13-кодировка-и-нормализация)), отличные от NFC, несёт
  base64-поле `texts[].exactBytes` ([§28](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/07-part-vii-source-and-document-model.ru.md#28-fragment), errata E-0248), а не строка `text`;
- ключи объектов сортируются по code points (== порядок байтов UTF-8 —
  одинаково в Python и Rust); коллизия ключей после NFC — ошибка;
- JSON-числа допускаются только целые в диапазоне i64 (SPEC [§18](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/05-part-v-lexis-and-basic-syntax.ru.md#18-числа): binary float
  запрещён; Decimal/Rational сериализуются строками в canonical form [§208](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#208-canonical-serialization) —
  форму строки обеспечивает normalizer);
- массивы сохраняют порядок; для document-режима top-level "nodes" сортируется
  по stable ID ([§208](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#208-canonical-serialization): unordered node sets — по stable ID); сортировка semantic
  sets по element hash и commutative operands по semantic hash — обязанность
  normalizer-а ([§207.1](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#2071-formula-normalization)), сюда документы приходят уже нормализованными;
- экранирование строк зафиксировано явно (совпадает в обеих реализациях):
  `"` -> \", `\` -> \\, управляющие < U+0020 — \b \t \n \f \r либо
  \u00xx (строчные hex); остальное — сырой UTF-8.

## lawref.canon.CanonError

*class*

```python
class CanonError(ValueError)
```

Bases: `ValueError`

Нарушение канонических правил [§208](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#208-canonical-serialization).

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

## lawref.canon.canonical_bytes

*function*

```python
def canonical_bytes(value: Any, *, document: bool = True) -> bytes
```

Канонические байты ([§208](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#208-canonical-serialization)). document=True сортирует top-level nodes по ID.

Нормализация записи значения (errata E-0052, E-0213) применяется ВСЕГДА, а
не только к документу: канонические байты обязаны быть каноническими
независимо от производителя, а CLIR приходит и не от компилятора.

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

## lawref.canon.canonical_decimal

*function*

```python
def canonical_decimal(text: str) -> str | None
```

Каноническая форма Decimal [§208](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#208-canonical-serialization) (errata E-0052).

«canonical non-exponent string: `0`, `-12`, `0.125`, `-3.5`; без leading
zeros, trailing fractional zeros и negative zero», и там же нормативный
пример: «source `12.50`, `1.25e3` normalizes соответственно в `12.5`,
`1250`». Зеркало `law_canon::canonical_decimal`; кросс-языковой контракт
G-M1a держит их байтово равными.

None — вход не десятичное число: значение чужого вида (Text, Date,
Rational) обязано пройти невредимым, а не быть испорченным нормализацией.

Считается СТРОКОЙ, а не Decimal/float: float запрещён [§18](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/05-part-v-lexis-and-basic-syntax.ru.md#18-числа), а перенос точки
цифр не меняет.

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

## lawref.canon.canonical_rational

*function*

```python
def canonical_rational(text: str) -> str | None
```

Каноническая форма Rational [§208](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#208-canonical-serialization)/[§35](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/08-part-viii-type-system.ru.md#35-встроенные-типы-данных) (errata E-0213).

[§35](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/08-part-viii-type-system.ru.md#35-встроенные-типы-данных): «в canonical serialization представляется несократимой дробью».
Знак стоит у ЧИСЛИТЕЛЯ, знаменатель строго положителен — той же записью,
какую [§168.1](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/24-part-xxiii-cases-snapshots-evaluation.ru.md#1681-ground-термы-аргументов-и-запись-rational) уже требует от ground-позиции утверждения. `15/100` → `3/20`,
`1/-2` → `-1/2`, `-3/-6` → `1/2`, `0/7` → `0/1`. `5/1` остаётся дробью:
вид значения не зависит от значения ([§58](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/09-part-ix-values-terms-expressions.ru.md#58-arithmetic)), и целое здесь не появляется.

None — вход не дробь [§208](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#208-canonical-serialization) (чужой вид, незаконная лексика E-0203) либо
знаменатель нулевой: такая запись обязана пройти каноном невредимой, чтобы
её отверг читатель литерала своей диагностикой ([§48](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/08-part-viii-type-system.ru.md#48-отсутствие-implicit-conversions), E-0152), а не канон.

Зеркало `law_canon::canonical_rational`; кросс-языковой контракт G-M1a
держит их байтово равными. Носитель — целые Python ([§35](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/08-part-viii-type-system.ru.md#35-встроенные-типы-данных), E-0159: разрядность
`Rational` не ограничена), делитель — `math.gcd` над int.

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