Evidence Bundles¶
Every completed validation run can produce an evidence bundle: a portable artifact that documents what was checked, against what workflow contract, with what validator metadata, with what retention policy, and with what cryptographic payload digests.
This page is the current developer-facing reference for the delivered bundle format. The implementation lives in:
validibot/validations/services/evidence.py— builds and persistsmanifest.jsonvalidibot/validations/services/evidence_bundle.py— builds the downloadable bundle tarballvalidibot/validations/views/evidence.py— serves the manifest and bundle download endpointsvalidibot-shared/validibot_shared/evidence/manifest.py— shared Pydantic schema used by producers and verifiers
Current Bundle Format¶
The bundle download is a deterministic .tar.gz built on demand. It is not currently cached to storage.
evidence-<run-id>.tar.gz
├── manifest.json
├── README.txt
└── manifest.sig # only when validibot-pro is installed and the run has a signed credential
manifest.json¶
The canonical JSON evidence manifest for the run. These are the exact bytes stored by RunEvidenceArtifact.manifest_path, copied into the bundle without modification.
The manifest is the structured proof of:
- which run completed;
- which workflow version and contract were used;
- which validator steps and validator digests were used;
- which input schema applied;
- which retention policy applied;
- which SHA-256 payload digests identify the run input and, where retention permits, output.
README.txt¶
A human-readable orientation file generated into the tarball. It explains:
- the run ID and workflow version;
- the manifest schema version;
- the manifest SHA-256;
- how to verify the manifest hash;
- whether
manifest.sigis present; - that raw input and output bytes are not currently included.
manifest.sig¶
Included only when:
validibot-prois installed; and- the run has an
IssuedCredential.
The file is the compact-JWS signed credential bytes from IssuedCredential.credential_jws. It is byte-for-byte identical to the standalone credential.jwt download on the run detail page, but named manifest.sig inside the bundle because it acts as a sidecar attestation for manifest.json.
The credential contains a credentialSubject.validationRun.manifestHash claim. Verifiers recompute SHA-256(manifest.json) and compare it with that claim.
What Is Not In The Current Bundle¶
The current bundle intentionally does not include:
- raw input bytes;
- raw output bytes;
- decoded submission content;
- original execution workspace files;
workflow.json;input/oroutput/directories;- runtime logs;
- signed URLs;
- host storage paths;
- environment variables;
- stack traces.
Raw input/output bundle members and curated logs are future work. For now, payload identity is represented by manifest.json::payload_digests.
Download Endpoints¶
The manifest and bundle are served from validation-run detail routes:
Both endpoints use ValidationRunAccessMixin: if a user can see the run detail page, they can download the evidence artifact.
Manifest Response¶
The manifest endpoint streams manifest.json from storage.
Important response details:
Content-Type: application/jsonContent-Disposition: attachment; filename="manifest.json"Cache-Control: no-store, max-age=0X-Validibot-Manifest-Sha256: <artifact.manifest_hash>X-Validibot-Schema-Version: <artifact.schema_version>
It returns 404 when the run has no generated manifest, when manifest generation failed, when the manifest was purged, or when the artifact row has no stored manifest bytes.
Bundle Response¶
The bundle endpoint builds the tarball in memory from the generated artifact.
Important response details:
Content-Type: application/gzipContent-Disposition: attachment; filename="evidence-<run-id>.tar.gz"Cache-Control: no-store, max-age=0X-Validibot-Manifest-Sha256: <artifact.manifest_hash>X-Validibot-Schema-Version: <artifact.schema_version>
It returns 404 when the run has no RunEvidenceArtifact in GENERATED state or when the stored manifest bytes are unavailable.
Manifest Schema¶
The schema version is validibot.evidence.v1. The schema lives in validibot-shared so external verifiers can parse manifests without importing the Django application.
Public schema symbols:
EvidenceManifest— top-level modelWorkflowContractSnapshot— workflow launch-contract fields at run timeStepValidatorRecord— per-step validator identity and digestsManifestRetentionInfo— retention class and redactions appliedManifestPayloadDigests— input/output SHA-256 digestsSCHEMA_VERSION—"validibot.evidence.v1"
The current manifest shape is top-level, not nested under run, workflow, or submission objects.
{
"schema_version": "validibot.evidence.v1",
"run_id": "4e4de41f-7f63-4af5-8e63-5b69e447f5bb",
"workflow_id": 42,
"workflow_slug": "energyplus-preflight",
"workflow_version": "3",
"org_id": 7,
"executed_at": "2026-05-20T02:11:30+00:00",
"status": "SUCCEEDED",
"source": "API",
"workflow_contract": {
"allowed_file_types": ["json"],
"input_retention": "STORE_30_DAYS",
"output_retention": "STORE_30_DAYS",
"agent_billing_mode": "",
"agent_price_cents": null,
"agent_max_launches_per_hour": null,
"agent_public_discovery": false,
"agent_access_enabled": false
},
"steps": [
{
"step_id": 1001,
"step_order": 1,
"validator_slug": "json-schema",
"validator_version": "1.0.0",
"validator_semantic_digest": "sha256:...",
"validator_backend_image_digest": null
}
],
"input_schema": {
"type": "object"
},
"retention": {
"retention_class": "STORE_30_DAYS",
"redactions_applied": []
},
"payload_digests": {
"input_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"output_envelope_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
}
}
Payload Digest Naming¶
The evidence manifest uses schema-specific digest names:
payload_digests.input_sha256— SHA-256 of the submitted data bytes.payload_digests.output_envelope_sha256— SHA-256 of the canonical output envelope bytes, where retention permits.
The UI may describe the first value as the data hash. Older docs and model comments may use "content hash." In the evidence manifest and bundle, the canonical field name is payload_digests.input_sha256.
The input digest is sourced from Submission.checksum_sha256, which is computed at upload/submit time and preserved when Submission.purge_content() deletes the actual bytes.
The output digest is sourced from ValidationRun.output_hash, populated before the manifest is stamped.
Retention Policy¶
Evidence retention rules are centralized in validibot/validations/services/evidence_retention.py.
Current rules:
payload_digests.input_sha256is included for every retention tier, includingDO_NOT_STORE.payload_digests.output_envelope_sha256is omitted forDO_NOT_STORE.- omitted fields are listed in
retention.redactions_applied.
For DO_NOT_STORE, the manifest still proves which exact input bytes were validated by hash, but the bundle does not include the input bytes and the UI/API must not expose them even if the async reaper has not deleted them yet.
Run Source Attribution¶
The optional source field documents which authenticated route produced the run.
Allowed values:
| Value | Route |
|---|---|
LAUNCH_PAGE |
Browser workflow launch form |
API |
REST API workflow run endpoint |
MCP |
Authenticated MCP helper API |
X402_AGENT |
Cloud-only x402 anonymous-agent route |
CLI |
validibot command-line tool |
SCHEDULE |
Scheduled or automated run |
source must be derived by the authenticated route, never from a caller-controlled request header.
Storage And Database Index¶
Manifest bytes live in configured application storage, not as database blobs.
Current storage path:
The database index is RunEvidenceArtifact, a one-to-one row with ValidationRun.
Important fields:
schema_version— manifest schema string, usuallyvalidibot.evidence.v1manifest_path—FileFieldpointing to the storedmanifest.jsonmanifest_hash— SHA-256 of the canonical JSON bytesretention_class— workflow input retention class used by the manifest builderavailability—GENERATED,PURGED, orFAILEDgeneration_error— populated when availability isFAILEDcached_bundle_path— reserved for future cached bundle exports; current downloads are built on demand and do not populate it
Migration 0044_add_run_evidence_artifact adds this model without backfill. Older completed runs may be manifest-less until re-finalized or explicitly re-stamped.
Generation¶
EvidenceManifestBuilder in validibot/validations/services/evidence.py is the manifest producer.
build(run)returns a schema-validatedEvidenceManifest.serialise(manifest)returns canonical JSON bytes.persist(run, manifest)writes the manifest to storage, computesmanifest_hash, and updates theRunEvidenceArtifactrow.
stamp_evidence_manifest(run) wraps build + persist and catches every exception. Manifest generation is best effort: a failure records availability=FAILED and does not change the validation run result.
Both run-completion paths call the wrapper:
- synchronous step orchestration;
- asynchronous validation callback finalization.
Determinism And Verification¶
The manifest hash is computed over canonical JSON:
sort_keys=Trueseparators=(",", ":")ensure_ascii=True
The bundle tarball is also deterministic:
- tar member names are stable;
- tar member mode, mtime, uid, gid, uname, and gname are normalized;
- gzip
mtimeis fixed to0.
Verification flow for signed bundles:
- Extract
manifest.jsonandmanifest.sig. - Recompute
SHA-256(manifest.json). - Parse
manifest.sigas a compact-JWS verifiable credential. - Verify the JWS against the issuer JWKS.
- Compare the recomputed manifest hash with
credentialSubject.validationRun.manifestHash. - Reject the bundle if the hashes differ.
Community deployments without validibot-pro still produce manifest.json and README.txt, but they do not produce manifest.sig.
Deferred Bundle Members¶
The original evidence architecture described a future richer bundle with members such as:
Those members are not part of the current delivered bundle. Adding them requires a focused retention review because raw inputs, outputs, logs, and file names may carry user content. Until then, the manifest's payload digests are the evidence of payload identity.
Audit Findings¶
audit_workflow_versions emits evidence-manifest findings:
MANIFEST_MISSING— terminal run has no artifact row.MANIFEST_GENERATION_FAILED— artifact exists butavailability=FAILED.
--strict makes warning-level findings return a non-zero exit status.