Arweave is a witness; MLflow is the system of record. The plugin's job isn't to put MLflow data on Arweave — it's to commit to a hash that lets anyone verify "what's in MLflow now matches what was anchored at time T."
The byte-level primitives — RFC 8785 JCS canonicalization, SHA-256, Ed25519
sign/verify, the spec-version registry, the profile-conditional _*
annotation strip — live in the shared
ar-io-proof kernel
(conformance-gated against test-vectors-v1.0). ario_mlflow.proof is the
plugin's adapter: ProofEngine.create_commitment delegates to
ario_proof.sign_envelope, verify_commitment to
ario_proof.verify_envelope. The kernel is the family contract; the plugin
owns key persistence and the mlflow-shaped result dict on top.
Each lifecycle event produces a small signed envelope that goes on Arweave:
{
"spec_version": "ario.mlflow/v1",
"event_id": "uuid",
"event_type": "training_complete | model_registered | prediction",
"subject": {"type": "mlflow_run", "run_id": "..."},
"payload_hash": "SHA-256 of canonical bytes",
"previous_hash": "prior event's payload_hash (or GENESIS)",
"signed_at": "ISO-8601",
"public_key": "Ed25519 public key",
"signature": "Ed25519 signature over canonical(envelope - signature)"
}The envelope is bounded 400–700 bytes. No source data goes on chain.
spec_version pins the envelope shape. Verifiers accept the plugin's
own major (ario.mlflow/v1) and the sister
ar-io-agent daemon's major
(ario.agent/v1) — the two share envelope spec + crypto and verify
each other's records. Envelopes anchored before this field was added
have no spec_version and continue to verify normally (verifiers flag
them as legacy). Envelopes carrying an unknown major fail verification
with reason: "unsupported_spec_version".
The bytes that were hashed live in MLflow as ario/payload.json artifacts:
ario/payload.jsonfor training and registrationario/predictions/<decision_id>/payload.jsonfor inferences
Verifiers download the artifact, re-hash, and compare to the envelope's
payload_hash. They can also re-derive the canonical bytes from a separate
MLflow surface (run params/metrics for training, the ario.payload_json
trace tag for predictions) to detect post-anchoring tampering.
This is the AgentSystems Notary pattern — canonical bytes in caller's existing system of record, commitments on a public chain.
Both the canonical payload and the signed envelope are serialized with RFC 8785 JSON Canonicalization Scheme. Any RFC-8785 verifier in any language reproduces the same bytes — interoperable with Notary, Sigstore, etc.
Events form a DAG, not a strict line. Each event type chains independently:
- Training proofs chain via the registered model's
ario.last_training_hashtag. - Registration proofs chain to the source training run's
ario.training_txtag. - Prediction proofs chain to the model version's
ario.registration_txtag (read atVerifiedModelinit; never written at predict time).
This sidesteps MLflow's lack of a CAS primitive — the high-frequency busy case (predictions) never writes the chain head, eliminating races.
Every verification surface (plugin CLI, third-party verifier, custom integration) runs the same three core checks plus an optional fourth:
| Check | What it proves |
|---|---|
| Proof Found | The pure-commitment envelope was retrieved from ar.io for the given TX ID |
| Record Matches | ario/payload.json in MLflow re-hashes to the envelope's payload_hash, AND re-deriving the canonical bytes from a separate MLflow surface produces the same bytes |
| Signature Confirmed | The envelope's signature verifies against the embedded public key |
| ar.io attestation (optional) | An ar.io gateway operator independently verified the on-chain proof |
Each returns one of three states: PASS, FAIL, or Pending. The first three are decisive for "is this proof intact"; ar.io attestation is an independent third-party witness.
Each event accumulates layers of evidence from independent parties:
- Commitment + Ed25519 signature by the AI system — attests to the event
- Canonical payload in MLflow (
ario/payload.json) — the source bytes that were hashed - Turbo receipt with millisecond timestamp — independent service attests when the proof was submitted
- Arweave block — network consensus confirms permanent storage
- ar.io Verify (on-demand, gateway operator's signature) — independent verification of the anchored data
Three integration points for MLflow users:
ario_mlflow.anchor()insidemlflow.start_run()— training provenanceario_mlflow.ArioMlflowClient— registration + promotion (drop-in formlflow.tracking.MlflowClient)ario_mlflow.VerifiedModel— inference (load-time integrity check + per-prediction anchoring)
Plus standalone dataset anchoring via anchor(dataset=ds), and the CLI:
ar-io-mlflow verify run|model|trace <id> and
ar-io-mlflow audit <model>/<version>.
See the README for usage examples and integration patterns.
A pure-commitment envelope per lifecycle event:
- Training — params, metrics, artifact checksums (re-hashed model artifacts)
- Registration — chains to training; re-hashes artifacts at registration time to catch model swaps
- Prediction — input hash, output hash, OTel trace IDs, model lineage; never the raw input/output values
- Standalone dataset — name, source URI, digest, schema_hash; never row contents
Source data stays in the caller's MLflow. Arweave only holds the ~500-byte commitment per event.
- Privacy — no PII or business data leaves MLflow. Public Arweave only stores commitments.
- Cost — 500 bytes/event fits in Turbo's free-tier file-size threshold; sustainable for high-volume inference.
- Interoperability — JCS + standard SHA-256 + Ed25519 means any verifier in any language can independently check a proof. No "call our API to verify" lock-in.
- Auditor independence — verification reproduces the entire check using only MLflow + an Arweave gateway. No dependency on this plugin's continued existence.