When an auditor asks what your agents did last quarter, the easy answer is a log. The trouble with a log is that it is kept by one side. Intramedial's answer is a ledger bundle: a range of your workspace's audit chain, sealed and signed so that someone who does not trust you, or us, can check it.
Evidence is only useful if everyone reads it the same way. So this post states what a bundle proves, as narrowly as we can, and then lists what it does not.
Three trails, not one#
The plane writes down everything it decides, in three places. They are easy to mix up.
| Trail | What it holds | How you check it |
|---|
| Audit chain | Per-workspace, hash-chained entries in RFC 8785 canonical form, with SHA-256. | Recompute the chain. No key needed. |
| Ledger | The witnessed events of the coordination plane, batched under Merkle roots. Read it with ledger.blame. | The roots in the ledger. |
| Ledger bundles | A range of the audit chain sealed into a Merkle-rooted bundle and signed with Ed25519. | Offline, against the published public keys. |
A bundle seals a range of the audit chain. It does not seal the coordination ledger that ledger.blame reads. They are separate trails, and a statement about one is not a statement about the other.
How the audit chain holds together#
Each entry's hash is the SHA-256 of its canonical core joined to the previous entry's hash. The core is the entry's id, workspace, sequence number, time, event type, domain, actor and payload. The first entry links to a fixed genesis value.
So every entry depends on every entry before it. Edit one, and its hash changes. Remove one, and the next entry's link points at nothing. Reorder two, and both links break. Any of these shows up as a break at a specific sequence number, and anyone can find it with no key at all.
What a hash chain cannot stop on its own is a shorter chain. Cut off the last hundred entries, and what remains still links up perfectly. That is one reason to seal ranges into bundles and keep them.
What a bundle adds#
A bundle takes a range of the chain and does two things.
First, it builds a Merkle tree over the hashes of the records in the range, and records the root. The records travel inside the bundle, so anyone can rebuild the tree and compare.
Second, it signs eleven members with Ed25519: the bundle id, the workspace, the jurisdiction, the scope, the range start and end, the record count, the Merkle root, the first and last record ids, and the sealing time. The signature is over the RFC 8785 canonical form of those members. The key is the one for the workspace's signing jurisdiction, and the bundle names it in signingKeyId.
The public halves of the keys are published at /.well-known/ledger-bundles-keys.json. Checking a bundle needs no account and no call to us.
What a valid bundle proves#
In one sentence:
Intramedial sealed this exact range of this workspace's audit chain, at the time shown, with the key named.
Broken into parts:
- Origin. The signed members were signed by the holder of the private key that matches
signingKeyId. Nobody else, including you, can make a bundle that verifies against that key.
- Integrity. Nothing in the signed core has changed, and the records are exactly the ones the root commits to. Change one character of a record hash, one signed member or the signature, and the check fails.
- A signed time. Intramedial asserts the bundle was sealed at
snapshotAt.
That is a useful thing to be able to show. It fixes a set of records at a moment, and makes any later change visible to anyone who holds the bundle.
What it does not prove#
This is the list to keep next to the bundle.
1. That what the records describe is true. Some facts in the chain are decided by the plane, such as an admission. Some are reported by an agent or a person. The record proves a report was made, not that it was correct.
2. That an agent did what it declared. An admission records an intent and the plane's decision. Whether the deploy or the refund happened is for detection and your own systems to say.
3. That nothing else happened. An agent that never passes through an enforcement point you have connected is not seen, so it is not in the chain. The absence of a record proves nothing.
4. The true time of an event. snapshotAt is our clock, not a third-party timestamp, and it is the time of sealing. Entry times are the time of recording.
5. That the key is ours. A signature proves who holds a key, not whose key it is. Fetch the key set through a channel you trust and pin it.
6. That the key is uncompromised. Bundle signing keys are Worker secrets, set per region inside the service that seals bundles. They are not held in a hardware security module or in AWS KMS. Whoever can deploy or administer that service could cause it to sign. There is no revocation list and no automatic rotation today. The signing keys page states what we would do if a key were compromised.
7. That the bundle is the only one. A bundle is not entered in a public log. We could, in principle, seal two different ranges for the same period. You hold the one you were given.
Where the Bitcoin anchor fits#
A bundle can also carry an OpenTimestamps proof. A daily job is built to batch pending bundle roots into one super-root and submit it to OpenTimestamps. Until a Bitcoin block confirms it, the bundle's otsProof is null. A pending time is never shown as confirmed.
The public /ots/verify route re-executes the proof from the anchored digest and checks that the result equals the Merkle root in the named Bitcoin block's header. A tampered proof that names a real block is rejected as merkle_path_mismatch.
That is a real external bound on time: the digest existed by that block. It also has limits, and the route returns them in notVerified on every valid answer:
- It does not check the block header's proof of work. The block's Merkle root comes from one public Bitcoin API, which you trust for that answer. The offline verifier lets you supply block headers from a source you choose instead.
- It does not enforce a minimum number of confirmations.
- It proves the digest existed, not what the digest stands for.
The anchor never changes the signature verdict. It is a separate, additive proof of time.
How to use a bundle well#
- Verify it yourself when you receive it. Then the check does not rest on our word. Verify a ledger bundle offline shows how.
- Pin the key set you verified against, and note the
kid and the date.
- Keep a copy in a system you control, with its own clock.
- Say what it is. "A signed seal of our audit chain for this range", not "proof that our agents complied".
A bundle is evidence. Whether an auditor, a court or a counterparty accepts it, and for what, is not something a signature decides. The verification docs state the same boundaries, with the failure reasons and a worked test vector.