Evidence infrastructure for high-stakes AI. Tamper-evident, hash-chained records for every model inference.
Project description
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.
pip install inferify
Quickstart
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.
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
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.
UPDATEandDELETEabort 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:
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.
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
pip install -e ".[dev]"
pytest -q
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file inferify-0.1.0.tar.gz.
File metadata
- Download URL: inferify-0.1.0.tar.gz
- Upload date:
- Size: 21.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
26309025341e2de0f38df03fe743c76d05c7ef0fbc9c31a48213d917c49b90f2
|
|
| MD5 |
08fb4e63c2bb5b02f3ace706c31f7064
|
|
| BLAKE2b-256 |
0272bbbaaf0b55301089b142ff8aa1cae6d8622b2742fd334f8340f845f7a40f
|
File details
Details for the file inferify-0.1.0-py3-none-any.whl.
File metadata
- Download URL: inferify-0.1.0-py3-none-any.whl
- Upload date:
- Size: 19.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be35b7bfdb263e463176b4b77f514644e8cd0328aea29aeb0cc66f07c766808c
|
|
| MD5 |
1a2ed7bdea35ab6d3ddd76d6267dcfdb
|
|
| BLAKE2b-256 |
7d9b2cd3b9c228a95a38f9b35ab60773e8e0c0f79edaf9d4d93aacd53080b720
|