Metadata-Version: 2.4
Name: ujex-audit-chain
Version: 0.2.0
Summary: Hash-chained append-only audit log. Python companion to @ujex/audit-chain (TypeScript). Stdlib-only.
Author-email: Akshay Sarode <akshay@akshaysarode.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://ujex.dev/audit/
Keywords: audit,hash-chain,tamper-evident,sha256,append-only,canonical-json
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Dynamic: license-file

# ujex-audit-chain (Python)

Python companion to [`@ujex/audit-chain`](../audit-chain) (TypeScript).
Hash-chained append-only log. Stdlib only — no external dependencies.

Wire format is identical to the TypeScript package. A chain serialized in
one language verifies in the other.

## Install

```
pip install ujex-audit-chain
```

Requires Python 3.10+.

## Usage

```python
from ujex_audit_chain import Chain, VerifyResult

chain = Chain()
entry = chain.append(
    {"actor": "agent-hello", "action": "postbox.send", "meta": {"to": "alice"}}
)
# entry.seq, entry.timestamp, entry.payloadHash, entry.prevHash, entry.entryHash

result = chain.verify()     # VerifyResult(ok=True, broken_at=None, reason=None)

blob = chain.serialize()    # list[dict] — JSON-safe
restored = Chain.from_entries(blob)
```

## Wire format

```
payloadHash = sha256_hex(canonical_json(payload))
entryHash   = sha256_hex(prevHash || payloadHash || str(timestamp_ms) || str(seq))
```

Canonical JSON = `json.dumps(..., sort_keys=True, separators=(',', ':'),
ensure_ascii=False)`. Hex digests are lowercase, 64 chars.

For full details see the TypeScript package README.

## License

Apache-2.0.
