Metadata-Version: 2.4
Name: inferify
Version: 0.1.0
Summary: Evidence infrastructure for high-stakes AI. Tamper-evident, hash-chained records for every model inference.
Author: Inferify
License: Apache-2.0
Project-URL: Homepage, https://inferify.io
Project-URL: Source, https://github.com/inferify/inferify
Keywords: ml,audit,evidence,compliance,model-risk,tamper-evident,mlops
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: yaml
Requires-Dist: PyYAML>=6.0; extra == "yaml"
Provides-Extra: witness
Requires-Dist: cryptography>=42; extra == "witness"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: PyYAML>=6.0; extra == "dev"
Requires-Dist: cryptography>=42; extra == "dev"
Dynamic: license-file

# Inferify

[![tests](https://github.com/inferify/inferify/actions/workflows/test.yml/badge.svg)](https://github.com/inferify/inferify/actions/workflows/test.yml)
[![pypi](https://img.shields.io/pypi/v/inferify.svg)](https://pypi.org/project/inferify/)
[![python](https://img.shields.io/pypi/pyversions/inferify.svg)](https://pypi.org/project/inferify/)

**Evidence infrastructure for high-stakes AI.**

Seal every model decision into a tamper-evident, SHA-256 hash-chained record.
When a decision is questioned months later, the proof already exists, and anyone
can verify it was never altered.

```bash
pip install inferify
```

## Quickstart

```python
import inferify

inferify.configure(store="evidence.db", envelope="envelope.yaml")

verdict = inferify.capture(
    model="novadx-v2.3.1",
    input=xray_512,                  # hashed, never stored
    output={"pneumonia": 0.91},
    input_type="chest_xray_512",
)

if not verdict:                      # falsy when the regime is FLAGGED
    escalate(verdict)                # verdict.reasons tells you why
```

`capture()` returns the regime verdict **synchronously**, because your code needs
it before the decision leaves the system. The record is sealed on a background
writer thread, so hashing and disk IO stay off your inference path.

## The envelope

An envelope encodes the conditions your model was actually validated under.

```yaml
model_versions: [novadx-v2.3.1, novadx-v2.3.2]
input_types:    [chest_xray_512]
confidence_min: 0.75
allowed_labels: [normal, pneumonia, effusion, nodule]
feature_bounds:
  patient_age: [0, 120]
```

Inside the envelope, the record is sealed `VALID`. Outside, it is sealed
`FLAGGED` together with the reasons.

**Flagging never blocks the seal.** A flagged record is a truthful, permanent
record of a risky decision. It has to stay in the chain, otherwise you could not
prove the flag ever happened. `FLAGGED` is a verdict about the model. A broken
chain is a verdict about whoever edited history. They are different things.

## Verifying

```bash
inferify-verify evidence.db            # verify the live store
inferify-verify bundle.json            # verify an exported bundle
inferify-verify bundle.json --json     # machine readable, for CI
```

Exit code is `0` when the chain is intact and `1` when it is not, so this drops
straight into a pipeline.

Verification recomputes every record from genesis. It never trusts a stored hash.
Change one byte of any record and that record fails, and every record after it
fails with it, because each one committed to the hash before it.

```
hash = sha256(prev_hash_ascii + canonical_json(body))
```

`canonical_json` is sorted-key, minimal-separator, UTF-8 JSON. Using canonical
JSON rather than a delimiter join means no user-controlled string can change how
a record is framed.

## What is actually guaranteed

**Guaranteed:**

* Raw inputs are never written to the store. Only a SHA-256 fingerprint. There is
  a test that greps the database file for the raw bytes.
* Records are append-only, enforced by SQLite triggers rather than application
  code. `UPDATE` and `DELETE` abort at the database level.
* Concurrent writers produce a contiguous, linear chain. Verified with 8 threads
  writing 200 records.
* Any edit, reorder, or deletion in an exported bundle is caught, and identified
  down to the exact record id.

**Not guaranteed by the hash chain alone:**

A hash chain proves nobody edited history *without rebuilding the chain*. It does
not stop the party who holds the chain from regenerating it end to end. That
attack verifies perfectly.

The fix is anchoring. Publish a checkpoint hash on a schedule to somewhere the
chain's owner cannot rewrite:

```python
cp, receipt = inferify.publish(store, witness)
```

Once a checkpoint is out in the world, every record sealed before it is frozen,
because a rebuilt chain produces a head that no longer matches the published
checkpoint. `FileWitness` is a development stand-in. A production witness is a
customer-held key, a transparency log, a notary, or any third-party append-only
service. See `tests/test_inferify.py::test_published_checkpoint_detects_a_rebuilt_chain`.

## Measured performance

On a 512x512 uint8 input, single shard, commodity hardware:

| | p50 | p95 | p99 | sustained |
|---|---|---|---|---|
| async writer (default) | 0.69 ms | 1.11 ms | 1.25 ms | ~626 rec/sec |
| sync write, fsync each | 1.69 ms | 2.63 ms | 3.46 ms | ~512 rec/sec |

Input hashing alone is ~0.65 ms, which dominates `capture()`. Reproduce with
`python examples/bench.py`.

Throughput is per shard, because a chain serializes writes by construction. Scale
with `shard=` (one chain per shard, verified independently).

## Anchoring, and why the SDK alone is not enough

A hash chain proves nobody edited history *without rebuilding the chain*. It does
not stop the party who holds the chain from regenerating it end to end. The
rebuilt chain verifies perfectly.

The fix is publishing a checkpoint to a witness the chain's owner does not
control. You cannot self-host the thing that makes your evidence credible against
you.

```python
from inferify.witness import HttpWitness

w = HttpWitness("https://api.inferify.io", api_key="ink_...")
cp, receipt = inferify.publish(store, w)

ok, detail = inferify.check_consistency(store, w.receipts())
```

`HttpWitness` never trusts the server. It recomputes the receipt hash, verifies
the Ed25519 signature against the published public key, and confirms the
checkpoint in the receipt is the one it submitted. `FileWitness` is a local
stand-in with no trust boundary, useful for development.

The witness service lives at https://github.com/inferify/inferify-cloud.

## Status

Version 0.1.0, alpha. Implemented and tested (28 tests): fingerprinting, the
envelope, the append-only hash-chained store, the exporter, the verifier CLI,
and the witness client.

Not built: TypeScript SDK, Merkle batching for high-throughput shards.

## Layout

```
inferify/
  fingerprint.py   deterministic hashing and canonical serialization
  envelope.py      the validated operating envelope and regime verdicts
  store.py         append-only, hash-chained SQLite evidence store
  client.py        capture() and the background writer
  verify.py        chain verification, bundle export, CLI
  anchor.py        checkpoints and external witnesses
```

## Tests

```bash
pip install -e ".[dev]"
pytest -q
```
