Auditable, version-pinned provenance for AI-produced values — zero dependencies.
Project description
attestable
Make it structurally impossible to ship an AI-produced value without a confidence score and a version-pinned, resolvable citation.
attestable is a tiny, zero-dependency Python library for auditable provenance on model output. It gives you four things that compose:
Derived[T]— a value that cannot be constructed without a confidence in[0, 1]and at least one valid source anchor. Provenance travels with the value, by construction, everywhere it goes.- An anchor grammar — a frozen citation URI that pins the source version, so re-chunking or re-embedding a newer revision never invalidates a citation stored against an older one. This is the one correctness property hand-rolled citation schemes get wrong.
- Resolvers + dangling detection — resolve anchors back to source text, and batch-check a corpus for citations that no longer point anywhere.
- Attestations — deterministic, tamper-evident (optionally HMAC-signed) digests that prove a value/confidence/citation triple has not been altered.
Why
Auditors, regulators, and reviewers reject "the model said so." When an LLM extracts a field, answers a question, or flags a finding, three things must be true for that output to survive scrutiny:
- It carries a confidence you can threshold on.
- It carries a pointer to the exact source it came from.
- That pointer still works after you re-index the corpus.
Most stacks bolt this on as an afterthought and get (3) wrong: citations are stored as raw offsets or chunk ids that break the moment a document is re-chunked. attestable makes (1) and (2) a type-level requirement and makes (3) correct by pinning the source version in the citation itself.
It is deliberately small, dependency-free, offline-capable, and framework-agnostic — it composes with your extraction/RAG stack rather than replacing it.
Install
pip install attestable.py
The PyPI distribution is named
attestable.py(the plainattestablename is an unrelated, unmaintained package). The import name is unaffected:import attestable.
Requires Python 3.10+. No third-party runtime dependencies.
Quick start
from attestable import Derived, span_anchor, attest, verify, InMemoryResolver, parse_anchor
# 1. Cite the exact characters — pinned to version 3 of the source.
anchor = span_anchor("document", "invoice-abc", version=3, chunk_id="p2", start=40, end=52)
# 2. Wrap the value. This raises unless confidence is in [0,1] and provenance is a valid anchor.
total = Derived(value="2026-01-15", confidence=0.94, provenance=[anchor])
# 3. Attest it (keyed = forge-resistant).
token = attest(total, key="rotate-me")
assert verify(total, token, key="rotate-me")
# 4. Tampering is detected.
assert not verify(total.with_value("2026-02-02"), token, key="rotate-me")
# 5. Resolve back to source, and detect citations that no longer point anywhere.
resolver = InMemoryResolver()
resolver.put("document", "invoice-abc", 3, "… issued on 2026-01-15 …")
assert resolver.resolve(parse_anchor(anchor)) is not None
Run the full tour: python examples/quickstart.py.
The anchor grammar
attest://<type>/<lineage_id>@<version>
attest://<type>/<lineage_id>@<version>#chunk:<chunk_id>
attest://<type>/<lineage_id>@<version>#span:<chunk_id>:<start>-<end>
attest://<type>/<lineage_id>@<version>#field:<json_pointer>
| Part | Meaning |
|---|---|
type |
lowercase entity type, e.g. document or document.sop |
lineage_id |
identity that is stable across versions of the source |
version |
the specific integer version cited |
chunk_id |
opaque, stable id of a chunk/paragraph |
start/end |
character range within a chunk (start <= end) |
json_pointer |
RFC-6901 pointer into a structured record |
parse_anchor() is total: it returns a fully-typed ParsedAnchor or raises AnchorSyntaxError — never a half-populated result. The scheme (attest) is fixed per deployment; use Grammar("your-scheme") to change it.
Integrating with your store
Implement the Resolver protocol over wherever your source text actually lives:
from attestable import Resolver, ResolvedSource, ParsedAnchor
class SqliteResolver:
def resolve(self, anchor: ParsedAnchor) -> ResolvedSource | None:
row = db.get(anchor.entity_type, anchor.lineage_id, anchor.version)
if row is None:
return None # -> reported by find_dangling()
text = row.text[anchor.start:anchor.end] if anchor.fragment == "span" else row.text
return ResolvedSource(anchor=anchor.to_anchor(), text=text)
Then find_dangling(anchors, resolver) and dangling_in(derived_values, resolver) give you a corpus-wide integrity check to run after every re-ingest.
Documentation
- Architecture & design decisions
- API reference
- FAQ & troubleshooting
- Changelog
- Contributing · Security policy · Code of conduct
License
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 attestable_py-0.1.1.tar.gz.
File metadata
- Download URL: attestable_py-0.1.1.tar.gz
- Upload date:
- Size: 19.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5bc539dc648cd6add713ca4b16354265200b8ce1f85ed83b70152bbc181c23a0
|
|
| MD5 |
a853d87724a3dd5e8ca38fec83c78f1d
|
|
| BLAKE2b-256 |
370d2cc2cde16dff52bf3bb4bc8cb949b5124114671a97cfe5d5e5e997812d59
|
Provenance
The following attestation bundles were made for attestable_py-0.1.1.tar.gz:
Publisher:
release.yml on Vibecoder012/attestable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
attestable_py-0.1.1.tar.gz -
Subject digest:
5bc539dc648cd6add713ca4b16354265200b8ce1f85ed83b70152bbc181c23a0 - Sigstore transparency entry: 2107008628
- Sigstore integration time:
-
Permalink:
Vibecoder012/attestable@2a5908dc010503b1dd55faed1281172b5a59bbbe -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/Vibecoder012
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2a5908dc010503b1dd55faed1281172b5a59bbbe -
Trigger Event:
push
-
Statement type:
File details
Details for the file attestable_py-0.1.1-py3-none-any.whl.
File metadata
- Download URL: attestable_py-0.1.1-py3-none-any.whl
- Upload date:
- Size: 18.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0bf375dbe8ac9240c3e5c965243d5cfd554d34624d0096e0b6daa5419e10cf63
|
|
| MD5 |
c6448df9eaaf6938d343ecd4e18a922b
|
|
| BLAKE2b-256 |
27630f59eb20c3088820c87275af5078a2b3ef39a9deff793e6f9363fb6689e5
|
Provenance
The following attestation bundles were made for attestable_py-0.1.1-py3-none-any.whl:
Publisher:
release.yml on Vibecoder012/attestable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
attestable_py-0.1.1-py3-none-any.whl -
Subject digest:
0bf375dbe8ac9240c3e5c965243d5cfd554d34624d0096e0b6daa5419e10cf63 - Sigstore transparency entry: 2107008695
- Sigstore integration time:
-
Permalink:
Vibecoder012/attestable@2a5908dc010503b1dd55faed1281172b5a59bbbe -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/Vibecoder012
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2a5908dc010503b1dd55faed1281172b5a59bbbe -
Trigger Event:
push
-
Statement type: