Metadata-Version: 2.4
Name: grundnorm
Version: 0.1.0
Summary: Official SDK for the Grundnorm grounded source-of-law truth layer — resolve a legal norm by open identifier (ELI/ECLI) + date and verify its cryptographic seal locally.
Project-URL: Homepage, https://grundnorm.nexusquantum.legal
Project-URL: Repository, https://github.com/djtellado/nexus-grundnorm
Project-URL: Issues, https://github.com/djtellado/nexus-grundnorm/issues
Author-email: Quantum Nexus Ventures FZCO <support@nexusquantum.legal>
License: MIT
License-File: LICENSE
Keywords: ecli,ed25519,eli,grundnorm,law,legal,source-of-law,verifiable
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Requires-Dist: cryptography>=41.0
Description-Content-Type: text/markdown

# grundnorm (Python SDK)

Resolve a legal norm by its **open identifier** (ELI/ECLI) and a **date**, and get back its canonical,
sealed meaning as an **independently verifiable** signed object. The SDK recomputes the content hash and
verifies every Ed25519 signature locally — **you never have to trust the server**.

- Deterministic, zero-LLM read path. Honest `not_found` instead of a guess.
- Point-in-time: `[validFrom, validUntil)`.
- Python 3.9+. One dependency: `cryptography`.

## Install

```bash
pip install grundnorm
```

## Use

```python
from grundnorm import GrundnormClient

# Defaults to the public GDPR demonstrator. For a pilot:
#   GrundnormClient(endpoint="https://.../api/grundnorm/resolve", api_key="nlk_...")
client = GrundnormClient()

r = client.resolve(
    id="http://data.europa.eu/eli/reg/2016/679/art_5",
    jurisdiction="EU",
    at="2026-07-07",   # omit for "today"
)

if r.status == "found":
    print(r.norm["atoms"])          # subject / modality / action / condition / exception / scope + evidence
    print(r.verification["ok"])     # True only if hash recomputes AND all signatures verify
```

`resolve()` verifies the seal by default. Skip with `verify=False`, or verify a stored envelope later:

```python
from grundnorm import verify
v = verify(norm)  # {"ok", "hash_ok", "recomputed_hash", "signatures", "quorum"}
```

## How verification works (no trust required)

1. `sha256(canonicalize(norm["canonical"]["content"]))` must equal `norm["seal"]["contentHash"]`,
   where `canonicalize` = JSON with object keys sorted recursively, no whitespace, hashed as UTF-8.
2. Each `seal.signatures[].signatureHex` is an Ed25519 signature by that signer's `publicKeyHex`
   over the UTF-8 bytes of the `seal.contentHash` hex string.

## Errors

- Nothing sealed for `(id, date)` → returns `NotFound` (a normal outcome).
- Bad key, bad input, or server error → raises `GrundnormError` (`.status`, `.code`).

> Demonstrator note: the demo corpus (GDPR sample) is signed by demo keys, not institutions, and its
> accuracy is not yet jurist-graded. See the project's `DEMO-TRUTHFULNESS.md`.
