Metadata-Version: 2.4
Name: trust-ledger-core
Version: 0.3.0
Summary: Trust Ledger core: append -> hash-chain -> Ed25519-sign -> seal to evidence-pack (trust-ledger/v1).
Author: Duncan Prins
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/duncanprins/trust-ledger
Project-URL: Repository, https://github.com/duncanprins/trust-ledger
Project-URL: Changelog, https://github.com/duncanprins/trust-ledger/blob/main/ledger-core/CHANGELOG.md
Project-URL: Issues, https://github.com/duncanprins/trust-ledger/issues
Keywords: audit,provenance,evidence,ed25519,hash-chain,rfc3161,jcs,ai-act
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Legal Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: System :: Logging
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=42
Provides-Extra: tsa
Requires-Dist: rfc3161-client>=1; extra == "tsa"
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: asn1crypto>=1.5; extra == "test"
Requires-Dist: rfc3161-client>=1; extra == "test"
Dynamic: license-file

# trust-ledger-core

Producer side of Trust Ledger. Append records, hash-chain them, sign with Ed25519, write an
evidence pack (`trust-ledger/v1`, or `/v2` when bound to an audit challenge). This is the
package a producing system embeds — it makes no network calls unless you opt into anchoring.

## Install

```bash
pip install trust-ledger-core
pip install 'trust-ledger-core[tsa]'   # + RFC 3161 timestamp-authority anchoring
```

The distribution is `trust-ledger-core`; the import package is **`ledger_core`**, and that is
what your code says. The prefix exists because PyPI collapses `-`, `_` and `.` before comparing
project names, which puts the plain name on top of an unrelated project.

`[tsa]` pulls in `rfc3161-client`. It's optional because not every producer anchors, and every
dependency here becomes the embedding producer's dependency too.

## Minimal example

```python
from ledger_core.keys import load_or_create_key
from ledger_core.ledger import TrustLedger

key = load_or_create_key("session.ed25519.key")
ledger = TrustLedger(session="demo-session-1", signing_key=key, producer="example-producer")

ledger.emit("inference.local", {"model": "local-7b", "egress_events": 0})

pack_dir = ledger.seal(
    reference_values={"require_sensitive_egress_zero": True},
    out_dir="trust-log",
)
print("sealed to", pack_dir)
```

Without an `anchor=` argument to `seal()`, nothing about the pack changes — it is
byte-identical to a pack sealed before anchoring existed. `verifier` will report such a pack as
`PARTIAL` at best (`anchor_status == "unanchored"`), not `PASS`: see the root README for why.

## Anchoring

```python
from ledger_core.anchors.tsa import TsaAnchor

anchor = TsaAnchor(url="https://freetsa.org/tsr")  # any RFC 3161 TSA
ledger.seal(reference_values=..., out_dir="trust-log", anchor=anchor)
```

`anchor.publish()` is the only network call this package makes. Verification of the resulting
token is entirely offline (see `trust-verify` in the `verifier` package) — that split is a
design property, not an incidental one.

If anchoring fails at seal time (network down), the pack is still written, unanchored. Anchor
it afterwards with the bundled CLI:

```bash
trust-anchor path/to/pack
```

This will not silently re-anchor an already-anchored pack — re-anchoring replaces
proof-about-then with proof-about-now, which is not a repair.

## Audit challenges (`trust-ledger/v2`)

If the relying party sent you a challenge before the execution, pass it to the constructor:

```python
import json
from ledger_core.challenge import AuditChallenge

challenge = AuditChallenge.from_dict(json.load(open("challenge.json")))
ledger = TrustLedger(session, key, producer="gateway", challenge=challenge)
```

The pack becomes `trust-ledger/v2`: its **genesis** hangs off the challenge digest, so the
whole chain is bound to it, and the challenge object travels in the manifest. Without the
argument nothing changes — same genesis, same schema, byte-identical to a v1 pack.

⚠️ **This package cannot mint a challenge, on purpose.** Minting is the relying party's job;
a producer able to mint one can mint the challenge he has already answered. The generator is
`trust-challenge`, shipped with `trust-ledger-verifier`. A pack sealed without a challenge is
`PARTIAL` at best (`challenge_status == "unchallenged"`), for the same reason an unanchored
one is.

## What this package does not decide

`trust-ledger-core` never decides what counts as a valid claim or what a PASS means — that's the
verifier's job, evaluated against reference values, a trust profile and a challenge the
*verifier's caller* supplies. See `verifier/README.md`.
