Skip to content

Audit Bundles

An ExaminerBundle packages a routing decision, the bitemporal evidence that informed it, and the policy version that was in effect, sealed with a SHA-256 content hash that travels with the artifact and is rechecked on arrival.

compliance For: governance & audit

How it works

  1. Gather the material. A routing decision, the bitemporal evidence store it cited, and the PolicyRegistry it routed against.

  2. Build the bundle. ExaminerBundle.build seals the decision, the referenced evidence, and the policy as-of the decision into a content-hashed artifact.

  3. Verify integrity. verify() recomputes the hash and raises BundleIntegrityError if a byte changed.

  4. Transport and re-verify. to_json / from_json move it anywhere; the recipient re-runs verify() to trust it independently.

flowchart LR
    A["AgentRouter.route<br/>AgentRoutingDecision"] --> D["ExaminerBundle.build"]
    B["InMemoryBitemporalStore<br/>evidence_refs"] --> D
    C["PolicyRegistry<br/>policy as-of decision"] --> D
    D --> E["content_hash<br/>(SHA-256)"]
    E --> F["to_json / from_json"]
    F --> G["verify()<br/>raises on tampering"]

Install

Terminal window
pip install briefcase-ai[compliance]
from briefcase.compliance import ExaminerBundle, BundleIntegrityError

Build a Bundle End to End

This ties together an AgentRouter decision, an InMemoryBitemporalStore of evidence, and a PolicyRegistry, then seals and verifies the result.

from datetime import datetime, timezone
from briefcase.bitemporal import BitemporalRecord, InMemoryBitemporalStore
from briefcase.routing.policy import (
PolicyRegistry,
PolicyVersion,
PolicyRule,
AgentRouter,
)
from briefcase.compliance import ExaminerBundle, BundleIntegrityError
# 1. Evidence: a bitemporal store of the facts that inform routing.
evidence = InMemoryBitemporalStore()
tier_record = BitemporalRecord.new(
key="ticket:tier",
valid_time=datetime(2026, 5, 1, tzinfo=timezone.utc),
value="gold",
source="crm",
)
evidence.append(tier_record)
# 2. Policy: a versioned routing policy in a bitemporal-backed registry.
registry = PolicyRegistry()
policy = PolicyVersion(
policy_id="support_triage",
version="2026.05.01",
rules=[
PolicyRule(
rule_id="gold-to-specialist",
condition={"tier": "gold"},
choice="specialist_queue",
rationale="gold-tier tickets route to specialist agents",
),
],
default_choice="general_queue",
)
registry.publish(policy, valid_from=datetime(2026, 5, 1, tzinfo=timezone.utc))
# 3. Decision: route a request, citing the evidence that informed it.
router = AgentRouter(
registry,
use_case="support_triage",
policy_id="support_triage",
)
decision = router.route(
{"tier": "gold"},
evidence_refs=[tier_record.record_id],
)
print(decision.selected) # specialist_queue
# 4. Bundle: seal decision + evidence + policy with a SHA-256 content hash.
bundle = ExaminerBundle.build(decision, evidence, registry)
bundle.verify() # passes — internally consistent
print(bundle.content_hash[:20]) # sha256:...

build looks up the policy as-of the decision (decision.decided_at by default, overridable with as_of_transaction_time=) and pulls exactly the evidence records named in decision.evidence_refs. If a referenced record is missing from the store, build raises BundleIntegrityError. Evidence is sorted deterministically so the hash is stable.

Transport and Verify

Serialize to JSON, send it anywhere, re-import, and re-check the hash. This is what makes a bundle tamper-evident in transit: the recipient re-verifies on their own machine, and any byte changed since the build breaks the content hash, so silent edits cannot pass.

payload = bundle.to_json(indent=2)
restored = ExaminerBundle.from_json(payload)
restored.verify() # ok — hash recomputed from contents matches

Detect Tampering

The hash covers the decision, policy, evidence, and the as-of timestamp. Change any of them and verify() raises.

restored.evidence[0]["value"] = "platinum"
try:
restored.verify()
except BundleIntegrityError as exc:
print("tamper detected:", exc)

Key Classes

SymbolWhy it matters
ExaminerBundle.build(decision, evidence_store, policy_registry, *, as_of_transaction_time=None, metadata=None)Seals the decision, its evidence, and the as-of policy into one artifact.
ExaminerBundle.verify()Recomputes the hash; raises BundleIntegrityError on any change.
ExaminerBundle.to_json() / from_json()Move the bundle between systems without losing the integrity check.
ExaminerBundle.content_hashSHA-256 digest over the bundle contents. Record it outside the bundle to make it an anchor.
BundleIntegrityErrorRaised by build (missing evidence) and verify (hash mismatch).

Bundles are built from AgentRoutingDecision records (see Versioned Routing Policy) and BitemporalRecord evidence (see Bitemporal Storage).

Sign a bundle with KMS

The compliance-kms extra adds SignedExaminerBundle for AWS KMS signatures. Verification requires a caller-pinned expected_key_id and algorithm. It never trusts the key ID serialized inside an untrusted bundle.

signed.verify_signature(
kms_client=kms,
expected_key_id="arn:aws:kms:us-east-1:123456789012:key/approved-key",
expected_algorithm="RSASSA_PSS_SHA_256",
)

Use Integrity and Signing for local Ed25519 signatures or hash-chain continuity across multiple bundles.

Limits

An unsigned bundle hash is unkeyed. Anchor content_hash outside the bundle, sign it locally, or use a caller-pinned KMS key. Otherwise it proves only self-consistency.

Only the evidence you cited is sealed. build pulls exactly the records named in decision.evidence_refs. Facts the decision relied on but did not cite are not in the bundle, and nothing checks that the citation list is complete. A bundle is as honest as the evidence_refs your routing code passed.

Bundles are built from routing decisions. ExaminerBundle.build takes an AgentRoutingDecision, not a DecisionSnapshot or a @capture record. Sealing a captured decision means routing it through AgentRouter first.

A missing evidence record fails the build, not the verify. If a record_id in evidence_refs is absent from the store, build raises BundleIntegrityError immediately. That is the check working, and it means a bundle cannot be built from a store that has been pruned.

The bundle is a snapshot of an interpretation. It seals the policy as-of decision.decided_at. If you later discover the policy registry itself was wrong at that time, every bundle built against it is faithfully wrong.

API reference

briefcase.compliance has the full ExaminerBundle surface.

Where this fits