Metadata-Version: 2.4
Name: grundnorm
Version: 0.3.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", "cryptographically_ok", "hash_ok", "view_consistent", "status_re_derivable", "signatures", "quorum", "attestation"}
```

**`v["ok"]` proves cryptographic soundness, not trustworthiness.** True iff the bytes, the view and
every signature check out (1-3 below) — exactly what a third party re-derives from the response alone.
It does **not** establish that the signers are independent institutions, that the record is externally
anchored, or that its meaning is jurist-correct. A demo record signed by one party is `ok: True`. Read
`quorum` and `attestation` before treating a record as authoritative.

## What `verify()` re-derives (no trust required)

1. **Hash**: `sha256(canonicalize(norm["canonical"]["content"]))` equals `norm["seal"]["contentHash"]`,
   where `canonicalize` = JSON with keys sorted recursively (UTF-16 order), no whitespace, UTF-8.
2. **View integrity**: the English `atoms`/`purpose` are exactly what the sealed `canonical.content`
   projects to (`view_consistent`); `canonical.content` is the source of truth.
3. **Signatures**: each `seal.signatures[].signatureHex` is a valid Ed25519 signature by that signer's
   `publicKeyHex` over the UTF-8 bytes of the `seal.contentHash` hex string.
4. **Status** (when a classification witness is present): each atom's status
   (`fixed`/`needs_review`/`for_the_court`) re-executes from the deterministic rule applied to the
   sealed per-atom signals (`status_re_derivable`). A mismatch fails `ok`. `None` = no witness (older
   seals) → not re-derivable, no penalty. Bounded: proves the *rule* was applied to sealed inputs; it
   does **not** prove the deontic decomposition is faithful to the article (that is the jurist gate).

## What it REPORTS but does not prove

- **`quorum`** — a signature count (`valid_signatures`, `has_sovereign`, `meets` = >=2 incl. one sovereign). Not proof of institutional independence.
- **`attestation`** — honest flags the SDK cannot prove: `custody` (`"unverified"` for the demo — one party regardless of pinning), `independence` (as asserted in the record), `ledger` (`"unanchored"` for demo/local seals), `key_provenance` (`"pinned_oob"` when a signer's key matches the SDK's out-of-band snapshot, else `"in_band_response"`), `classification` (`"rule_reexecuted"`, `"not_present"`, or `"rule_unsupported"` when the witness declares a rule version this SDK does not implement).
- **Key pinning (`key_provenance`, per-signature `pinned`)** is **tamper-evidence of a snapshot**, not proof of institutional independence: a single-party demo key is pinned yet `custody` stays `"unverified"`. It is not a key-transparency log.

## 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`.
