# lawref.hashing

*module*

Semantic hash, StableId и канонический lockfile (WP-05; SPEC [§209](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#209-theory-hash-и-artifact-hash), [§14](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/05-part-v-lexis-and-basic-syntax.ru.md#14-идентификаторы), [§11](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/04-part-iv-project-and-files.ru.md#11-lawlock)).

Семантический фильтр v1 (кросс-языковой контракт с engines/lawc/law-canon):

- рекурсивно удаляются ключи `sourceSpan` ([§202](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#202-common-node-fields): исключён из semantic content
  hash), `annotations` ([§209](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#209-theory-hash-и-artifact-hash): non-semantic annotations вне хэша; annotation с
  `semantic = true` возможна только у imported feature profile — профилей пока
  нет, при их появлении фильтр станет выборочным) и `labels` ([§209](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#209-theory-hash-и-artifact-hash) ред. E-0010:
  презентационный слой — перевод и переформулировка метки не меняют семантику;
  официальный текст меткой не является и остаётся в хэше как `texts` фрагмента);
- у элементов `parameters` в 0.2-форме (ParameterDecl: есть id и объектный
  type) удаляется `name` — non-semantic metadata по DECISION-0001 п.4;
- на верхнем уровне удаляются `semanticHash` (нельзя хэшировать сам себя) и
  `metadata` ([§209](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#209-theory-hash-и-artifact-hash): local source paths и прочие несемантические сведения);
- `contentHash` узлов удаляется как derived-значение (пересчитывается из
  содержимого; включение derived-хэшей в preimage делало бы формат хрупким).

Всё остальное — включая imports (exact imported package semantic hashes),
features и language semantics version в конверте — входит в hash. Метки
остаются в content hash узла: он считается по содержимому узла (authoring.
content_hash) и в preimage [§209](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#209-theory-hash-и-artifact-hash) не попадает вовсе.

## lawref.hashing.ARTIFACT_EXCLUDED_KEYS

*attribute* · *module attribute*

```python
ARTIFACT_EXCLUDED_KEYS = frozenset({'labels'})
```

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

## lawref.hashing.ARTIFACT_TOP_LEVEL_EXCLUDED_KEYS

*attribute* · *module attribute*

```python
ARTIFACT_TOP_LEVEL_EXCLUDED_KEYS = frozenset({'semanticHash', 'theoryHash', 'artifactHash'})
```

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

## lawref.hashing.NODE_EXCLUDED_KEYS

*attribute* · *module attribute*

```python
NODE_EXCLUDED_KEYS = frozenset({'contentHash'})
```

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

## lawref.hashing.RECURSIVE_EXCLUDED_KEYS

*attribute* · *module attribute*

```python
RECURSIVE_EXCLUDED_KEYS = frozenset({'sourceSpan', 'annotations', 'labels'})
```

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

## lawref.hashing.THEORY_EXCLUDED_KEYS

*attribute* · *module attribute*

```python
THEORY_EXCLUDED_KEYS = frozenset({'sourceSpan', 'annotations', 'labels', 'anchors', 'provenance'})
```

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

## lawref.hashing.TOP_LEVEL_EXCLUDED_KEYS

*attribute* · *module attribute*

```python
TOP_LEVEL_EXCLUDED_KEYS = frozenset({'semanticHash', 'theoryHash', 'artifactHash', 'metadata'})
```

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

## lawref.hashing.anonymous_assert_id

*function*

```python
def anonymous_assert_id(namespace: str, assertion: dict) -> str
```

StableId [§207](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#207-explicit-defaults) для анонимного assert из факта и provenance, не позиции.

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

## lawref.hashing.artifact_hash

*function*

```python
def artifact_hash(document: dict) -> str
```

`sha256:<hex>` идентичности CLIR-артефакта law.core/0.2 ([§209](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#209-theory-hash-и-artifact-hash)).

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

## lawref.hashing.canonical_lockfile_bytes

*function*

```python
def canonical_lockfile_bytes(lock: dict) -> bytes
```

Канонический `law.lock` ([§11](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/04-part-iv-project-and-files.ru.md#11-lawlock)): canonical JSON; generated-at/compiler build
в semantic lockfile отсутствуют по построению. Сортировка semantic-sets по
canonical identity — обязанность resolver-а (WP-31); здесь — байтовая форма.

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

## lawref.hashing.generate_stable_id

*function*

```python
def generate_stable_id(namespace: str, module_path: str, local_name: str) -> str
```

Internal StableId по [§14](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/05-part-v-lexis-and-basic-syntax.ru.md#14-идентификаторы): NFC → namespace + "/.generated/" + percent_encode(module::local).

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

## lawref.hashing.node_semantic_hash

*function*

```python
def node_semantic_hash(node: dict) -> str
```

`sha256:<hex>` семантической проекции ОДНОГО узла ([§209](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#209-theory-hash-и-artifact-hash) + [§202](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#202-common-node-fields)).

Тот же фильтр, что у документа, применённый к узлу: метки (E-0010),
annotations, sourceSpan и derived-хэши вне preimage. Нужен там, где
подписывается отдельная норма, а не пакет целиком — ревью-контур LS [§3.6](https://github.com/arxohq/law/blob/master/spec/LAYERS-SURFACES.ru.md#36-привязка-утверждения):
правка соседней нормы не должна устаревать утверждение этой.

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

## lawref.hashing.percent_encode

*function*

```python
def percent_encode(text: str) -> str
```

Percent-encoding [§14](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/05-part-v-lexis-and-basic-syntax.ru.md#14-идентификаторы): unreserved RFC 3986 — как есть, прочее — %XX (верхний hex) по байтам UTF-8.

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

## lawref.hashing.semantic_hash

*function*

```python
def semantic_hash(document: dict) -> str
```

`sha256:<hex>` над каноническими байтами семантической проекции ([§209](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#209-theory-hash-и-artifact-hash)).

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

## lawref.hashing.snapshot_program_hash

*function*

```python
def snapshot_program_hash(document: dict) -> str
```

Пин внешнего снимка [§47.2](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/08-part-viii-type-system.ru.md#472-snapshot): `semantic_hash` программы без бухгалтерии импортов.

Errata E-0216 (DECISION-0347 [§2](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/01-part-i-audit.ru.md#2-что-необходимо-исправить)). До неё снимок пиннился `programHash`
манифеста [§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), то есть `semantic_hash` ВСЕГО связанного мира вместе с
`imports[].contentHash`. Снимок `kz.corpus.gesv_rules` прожил из-за этого
62 минуты: перевыпуск далёкой транзитивной зависимости менял `contentHash`
импорта, не меняя в мире НИ ОДНОГО узла, и набор решений внешнего
вычислителя объявлялся снятым с чужой программы. Там же вторая цена:
закоммиченный `corpus/clir` и CLIR, собранный из исходников, давали разные
значения при тех же узлах — одного верного не существовало.

Проекция НОРМИРУЕТ поле: `imports` заменяется пустым списком независимо от
того, было ли оно в документе. Всё прочее, что охватывает [§209](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#209-theory-hash-и-artifact-hash), остаётся —
узлы, `package`, `features`, `semanticVersion`, `schemaVersion`, `strata`:
пин по одним лишь `nodes` не различал бы мир 0.2 и мир 0.3 с теми же
узлами, а это разная семантика.

В манифест [§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) это значение НЕ попадает: оно существует ровно для сверки
снимка. Парная реализация — `law-eval/src/document.rs::snapshot_program_hash`.

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

## lawref.hashing.strip_artifact

*function*

```python
def strip_artifact(document: dict) -> dict
```

Проекция `artifactHash` 0.2: CLIR с provenance, без labels/derived hash.

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

## lawref.hashing.strip_non_semantic

*function*

```python
def strip_non_semantic(document: dict) -> dict
```

Семантическая проекция документа (preimage для [§209](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#209-theory-hash-и-artifact-hash)).

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

## lawref.hashing.strip_theory

*function*

```python
def strip_theory(document: dict) -> dict
```

Проекция `theoryHash` 0.2: объявления без presentation/provenance.

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

## lawref.hashing.theory_hash

*function*

```python
def theory_hash(document: dict) -> str
```

`sha256:<hex>` идентичности теории law.core/0.2 ([§209](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/28-part-xxvii-canonical-legal-ir.ru.md#209-theory-hash-и-artifact-hash)).

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