Anatomy of an Evidence Bundle: What's Inside the Proof
A GuardSpine evidence bundle is a JSON file that proves what changed, who reviewed it, what rules applied, and why the merge was authorized. Here is every field, explained with real examples.
I hand you a JSON file. You have never used GuardSpine. You have no account, no API key, no connection to our servers. Can you verify that a specific code change was reviewed correctly?
If the answer is no, the governance system failed. That question drove every design decision in the evidence bundle format.
The Top-Level Structure
An evidence bundle is a single JSON object with six fields:
{
"bundle_id": "a3f8c2d1-7b4e-4f2a-9c1d-8e5f3a2b1c4d",
"version": "0.2.1",
"created_at": "2026-03-12T09:15:00Z",
"items": [],
"immutability_proof": {},
"signatures": []
}
bundle_id is a UUID v4, randomly generated at creation time. Not sequential, not derivable from content. This prevents anyone from guessing or predicting bundle identifiers.
version tracks the schema version. A verifier built for v0.2.1 knows exactly which fields exist and how to validate them. Forward compatibility matters when bundles outlive the software that created them — and in compliance, they will.
created_at is always UTC, always ISO 8601. No timezone games. When an auditor in London and a developer in San Francisco look at the same bundle, they see the same timestamp.
The real work happens in the three arrays.
Items: The Evidence Chain
Each entry in items[] is one piece of evidence. A typical bundle for a governed PR contains four to six items:
{
"item_id": "diff-001",
"content_type": "guardspine/diff",
"content": {
"file": "src/auth/middleware.ts",
"before_hash": "sha256:9f86d08...",
"after_hash": "sha256:7c5e3a1...",
"lines_added": 12,
"lines_removed": 3,
"diff_text": "@@ -42,7 +42,16 @@ ..."
},
"content_hash": "sha256:e3b0c44..."
}
The content_type field tells the verifier what kind of evidence this is. Common types include:
guardspine/diff— the exact code change at review timeguardspine/risk-classification— the tier assignment and reasoningguardspine/review— a single reviewer’s assessmentguardspine/policy-check— which rules were evaluated and resultsguardspine/authorization— the merge decision and who made it
Every item has a content_hash computed over the canonical serialization of its content field. This is the anchor. If anyone modifies the content after sealing, the hash breaks.
Here is what a risk classification item looks like:
{
"item_id": "risk-001",
"content_type": "guardspine/risk-classification",
"content": {
"tier": "L2",
"reasoning": "Change modifies authentication middleware. Matches pattern: src/auth/**",
"matched_rules": ["auth-files-elevated", "middleware-requires-review"],
"confidence": 0.94
},
"content_hash": "sha256:b5bb9d8..."
}
And a review item from one of the AI council members:
{
"item_id": "review-claude-001",
"content_type": "guardspine/review",
"content": {
"reviewer_id": "claude-sonnet-4",
"reviewer_type": "ai",
"verdict": "approve_with_findings",
"risk_score": 0.35,
"findings": [
{
"severity": "medium",
"category": "rate-limiting",
"description": "New endpoint lacks rate limiting. Previous implementation had 100 req/min cap.",
"line_range": [47, 52]
}
],
"rules_evaluated": ["security-001", "auth-002", "perf-003"],
"evaluation_time_ms": 2340
},
"content_hash": "sha256:2cf24db..."
}
Notice that the review records which rules were evaluated, not just the outcome. An auditor can verify that the reviewer checked against the right policies, not just that it said “approve.”
The Immutability Proof: Hash Chains
The immutability_proof field ties all items together into a tamper-evident chain:
{
"algorithm": "sha256",
"chain": [
{
"item_id": "diff-001",
"item_hash": "sha256:e3b0c44...",
"chain_hash": "sha256:e3b0c44..."
},
{
"item_id": "risk-001",
"item_hash": "sha256:b5bb9d8...",
"chain_hash": "sha256:a1f2e3d..."
},
{
"item_id": "review-claude-001",
"item_hash": "sha256:2cf24db...",
"chain_hash": "sha256:c4d5e6f..."
}
],
"root_hash": "sha256:f7g8h9i..."
}
Each chain_hash is computed as SHA256(previous_chain_hash + current_item_hash). The first entry’s chain hash equals its item hash. Every subsequent entry depends on every entry before it.
This creates a property that matters enormously for audit: you cannot remove, reorder, or modify any item without breaking the root hash. Insert a fake review? The chain breaks. Delete a finding? The chain breaks. Change the risk classification after the fact? The chain breaks.
The root_hash is the single value that summarizes the entire bundle’s integrity. Verify the root hash, and you know nothing was tampered with.
Signatures: Who Sealed It
The signatures[] array contains cryptographic signatures over the root hash:
{
"signer_id": "guardspine-seal-service",
"algorithm": "ed25519",
"public_key": "MCowBQYDK2VwAyEA...",
"signature": "MEUCIQD...",
"signed_at": "2026-03-12T09:15:01Z"
}
The public key is embedded in the bundle itself. This is intentional. An offline verifier does not need to fetch the key from a server. It can validate the signature using only the data in the JSON file.
Multiple signatures are supported. Your CI system signs it. Your security team signs it. Your compliance tool signs it. Each signature is independently verifiable.
Offline Verification
The guardspine-verify CLI processes a bundle in four steps:
- Recompute every
content_hashfrom the raw content - Rebuild the hash chain from the recomputed hashes
- Compare the rebuilt root hash against the stored root hash
- Validate each signature against the root hash
No network calls. No API tokens. No trust in GuardSpine’s infrastructure. The math either checks out or it does not.
This is the property that makes evidence bundles useful in regulated environments. When an FDA auditor or a SOC 2 assessor reviews your evidence, they do not need to trust your vendor. They can run the verification themselves.
Why This Design
I tested four different evidence formats before settling on this one. The survivors had three properties:
Self-contained. Everything needed for verification lives inside the bundle.
Append-friendly. New items can be added to the chain without invalidating earlier items. This matters for multi-stage reviews where human sign-off happens after AI review.
Schema-versioned. A bundle created today can be verified by a tool built five years from now, because the version field tells the verifier exactly how to interpret the contents.
The format that died hardest was a signature-per-item approach. It was more granular but made offline verification painfully slow for large bundles and created key management headaches. One chain, one root hash, multiple signatures over that root — simpler, faster, equally tamper-evident.
Try It Yourself
The spec is public at guardspine-spec. The verification CLI is free. Grab a sample bundle from the codeguard-action test fixtures and run guardspine-verify against it. Every field I described above will be there, verifiable, with no account required.
If you want to see evidence bundles generated from your own codebase, book a walkthrough at cal.com/davidyoussef/guardspine. I will show you the bundle from your first governed PR.