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
-
Gather the material. A routing
decision, the bitemporalevidencestore it cited, and thePolicyRegistryit routed against. -
Build the bundle.
ExaminerBundle.buildseals the decision, the referenced evidence, and the policy as-of the decision into a content-hashed artifact. -
Verify integrity.
verify()recomputes the hash and raisesBundleIntegrityErrorif a byte changed. -
Transport and re-verify.
to_json/from_jsonmove it anywhere; the recipient re-runsverify()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
pip install briefcase-ai[compliance]from briefcase.compliance import ExaminerBundle, BundleIntegrityErrorBuild 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, InMemoryBitemporalStorefrom 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 consistentprint(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 matchesDetect 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
| Symbol | Why 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_hash | SHA-256 digest over the bundle contents. Record it outside the bundle to make it an anchor. |
BundleIntegrityError | Raised 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.