runseal
Prove which inputs produced a result — and that nothing changed since.
Content-addressed provenance for computational research. Four primitives, no required dependencies, no daemon, no server.
pip install runseal # hashing, merkle, registry
pip install runseal[signing] # adds RSA-PSS artifact signing
Thirty seconds
from runseal import HashGenerator, ArtifactRegistry, Artifact
h = HashGenerator()
training_frame = {"rows": [1, 2, 3]}
config = {"window": 30, "seed": 7}
hashes = dict(
source_hash=h.hash_data_snapshot(training_frame),
code_hash=h.hash_code("src/"),
config_hash=h.hash_config(config),
)
# A run is identified by what went into it, never by when you ran it.
run_id = h.compute_run_id(
data_hash=hashes["source_hash"],
config_hash=hashes["config_hash"],
code_hash=hashes["code_hash"],
env_hash=h.hash_environment(h.get_environment_manifest()),
)
registry = ArtifactRegistry("./provenance")
registry.register(Artifact(id=f"{run_id}/raw", type="dataset", layer="ingest", **hashes))
registry.register(Artifact(id=f"{run_id}/model", type="model", layer="train",
parent=f"{run_id}/raw", metrics={"auc": 0.71}, **hashes))
print(run_id[:32])
print(" -> ".join(a.id.split("/")[-1] for a in registry.get_lineage(f"{run_id}/model")))
print(registry.verify_integrity()["integrity_ok"])
45cd8972f036917e3a89ee3363c7119e
model -> raw
True
Re-run the same pipeline on the same inputs and run_id is byte-identical. Change one config
value, one byte of data, or one line of code, and it is not.
What is in it
HashGenerator |
Content-addressed run IDs from data, config, code and environment. Deterministic across processes and machines. |
MerkleTree |
Append-only log with root verification and per-leaf inclusion proofs. |
ArtifactRegistry |
JSON-backed store with parent lineage, filtered queries and integrity checks. |
SignatureManager |
RSA-PSS signing and verification of artifact records. Optional extra. |
hash_config is order-insensitive, hash_data_snapshot handles dicts, sequences and numpy
arrays by content, and hash_code walks a directory's .py files in sorted order.
What this is not
It is easy to pattern-match this to a pipeline tool. It is not one.
| If you want | Use |
|---|---|
| Data and model versioning with remote storage | DVC |
| Experiment tracking with a UI and a server | MLflow, Weights & Biases |
| Pipeline orchestration | Airflow, Prefect, Dagster |
| A small library that answers "what produced this, and has it changed?" | runseal |
No service to run, no storage backend to configure, nothing to log into. It records and verifies; it does not schedule, execute, or store your data.
Why this exists
Extracted from a private research program where a wrong number that reconciles is more expensive than one that crashes. Writing the test suite surfaced two defects, both fixed here with regression tests named after them.
Merkle proofs verified only for leaf 0. get_proof returned bare sibling hashes with no
record of which side each sibling sat on, while verify_proof always combined
H(current + sibling). Parents are built as H(left + right), so any leaf that was a right child
at any level failed to verify. On a four-leaf tree, three of four valid proofs were rejected.
get_proof now returns (sibling_hash, side) pairs, verify_proof honours the side, and the
odd-leaf self-pairing case is explicit. Covered for trees of 1–33 leaves.
Environment hashes were never stable. get_environment_manifest() embedded time.time(), so
hashing the manifest gave a different digest on every call and compute_run_id produced a
different RUN_ID for an identical environment — defeating the whole point of content addressing.
The manifest is now deterministic, with wall-clock time left to Artifact.created_at where it
belongs. Package enumeration also moved off the deprecated pkg_resources.
Scope and limits
Signing covers artifact identity and the provenance hashes — not mutable metrics or metadata,
which are expected to be annotated after the fact. A test asserts that boundary so it stays
explicit.
The Merkle implementation duplicates the final node on odd levels, the common convention, which carries the known second-preimage ambiguity between a tree of N leaves and certain smaller trees. That is fine for tamper-evidence inside one append-only registry and unsuitable as a general-purpose commitment scheme.
Tests
pip install -e ".[test]"
pytest -q
80 tests. The suite runs with or without cryptography — signing tests skip cleanly, and CI
exercises both paths across Python 3.9–3.13.
Licence
MIT. See CHANGELOG.md and CONTRIBUTING.md.
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 runseal-0.1.0.tar.gz.
File metadata
- Download URL: runseal-0.1.0.tar.gz
- Upload date:
- Size: 21.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2396aa857fb4d471e95411501152d6babfc1a2568870c1c896e517d739b5ecf
|
|
| MD5 |
a25e323492eaec564bcf9935fa940d23
|
|
| BLAKE2b-256 |
104f8f601ca8eb5beeb820b1c59e3950eeafc48f265b31ff0b5e672dd858ecb9
|
Provenance
The following attestation bundles were made for runseal-0.1.0.tar.gz:
Publisher:
publish.yml on charlieyanhx/runseal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
runseal-0.1.0.tar.gz -
Subject digest:
f2396aa857fb4d471e95411501152d6babfc1a2568870c1c896e517d739b5ecf - Sigstore transparency entry: 2704487523
- Sigstore integration time:
-
Permalink:
charlieyanhx/runseal@bf512c839040963d4137320dbd00444fe357a509 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/charlieyanhx
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bf512c839040963d4137320dbd00444fe357a509 -
Trigger Event:
push
-
Statement type:
File details
Details for the file runseal-0.1.0-py3-none-any.whl.
File metadata
- Download URL: runseal-0.1.0-py3-none-any.whl
- Upload date:
- Size: 16.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
51d2183d22427cabcfec81fbd44142833a8cc9bffeaccb39ad9e6044280dc442
|
|
| MD5 |
02230fbbbc87c56a8a5c3c2b4ce7b335
|
|
| BLAKE2b-256 |
8efe17eec7f555a4ff97b7c30f138558fca6c7118be0eb777dc0d8833f0346d8
|
Provenance
The following attestation bundles were made for runseal-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on charlieyanhx/runseal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
runseal-0.1.0-py3-none-any.whl -
Subject digest:
51d2183d22427cabcfec81fbd44142833a8cc9bffeaccb39ad9e6044280dc442 - Sigstore transparency entry: 2704487536
- Sigstore integration time:
-
Permalink:
charlieyanhx/runseal@bf512c839040963d4137320dbd00444fe357a509 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/charlieyanhx
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bf512c839040963d4137320dbd00444fe357a509 -
Trigger Event:
push
-
Statement type: