A fork-proof audit chain on a single Durable Object.
Steven Martinez · Published July 27, 2026
Logs you administer are not evidence. An auditor’s first question is whether a record could have been altered after the fact, and for any database your own team operates, the honest answer is yes. I’m building OmniStrat around a different primitive: every consequential action. An AI routing decision, a trade, a signed agreement, becomes a receipt that anyone can verify without trusting me. This post is the architecture, including the race condition that shaped it and the places it’s still weak.
Anatomy of a receipt
A receipt commits to four things: what (a payload hash), who (Ed25519 signatures), when (timestamps, externally anchored), and where in history (a hash-chain position). Content hashing starts with canonical JSON, the same bytes on every runtime, or nothing verifies:
function canonicalJson(obj: unknown): string {
if (obj === null || typeof obj !== "object") return JSON.stringify(obj);
if (Array.isArray(obj)) return "[" + obj.map(canonicalJson).join(",") + "]";
const keys = Object.keys(obj).sort();
return "{" + keys.map(k =>
JSON.stringify(k) + ":" + canonicalJson(obj[k])
).join(",") + "}";
}
// payload_hash = SHA-256(canonicalJson(payload))
Signing keys are generated client-side with WebCrypto. The signer’s browser creates an Ed25519 keypair, signs the payload hash, and submits only the public key and signature. My servers verify signatures; they cannot create them. That asymmetry is the entire trust model. I can’t forge a record even if I wanted to, and neither can anyone who compromises me.
The race that forks a chain
Each finalized record incorporates the previous record’s hash, so rewriting history means recomputing every later link. But a hash chain is only as good as its linearity, and my first implementation had a classic flaw: the chain tip lived in a KV key. Two concurrent finalizations could both read the same tip, both compute a “next” hash, and both write. Last write wins, and the loser’s chain hash becomes orphan noise. A verifiable ledger that occasionally forks is worse than no ledger, because it hands a skeptic a genuine inconsistency.
The fix is the most boring possible distributed-systems answer: stop distributing it. All finalizations advance the chain through one global Durable Object instance, which serializes read-and-advance atomically:
// Inside the chain-tip Durable Object, one instance, globally.
const result = await this.state.blockConcurrencyWhile(async () => {
const current = await this.state.storage.get("tip") ?? null;
const prev_chain_hash = current?.chain_hash ?? "0".repeat(64);
const chain_hash = await sha256Hex(canonicalJson({
cert_id, purpose, payload_hash,
parties, // [{ role, pubkey_jwk, signature_b64, signed_at }]
prev_chain_hash, created_at, finalized_at,
}));
await this.state.storage.put("tip", { cert_id, chain_hash });
return { prev_cert_id, prev_chain_hash, chain_hash };
});
A single serialization point sounds like a bottleneck until you do the arithmetic: chain advancement is one hash and one storage write, a Durable Object handles that comfortably at rates far beyond what a receipts workload produces, and every other part of the system. Record storage in KV, immutable finalized blobs in R2, verification reads, scales horizontally around it. If the DO is ever unreachable, finalization falls back to the KV path deliberately: a rare orphan hash is recoverable; a wedged finalize pipeline is an outage. Availability beats purity, and the DO makes the fallback nearly-never.
Nobody has to ask my permission to check
Every finalized record is public JSON at GET /api/attest/verify/<cert_id>, no auth, CORS open. It returns everything verification needs and nothing more: the payload hash, the signatures and public keys, the chain hashes, the timestamps, and a pointer to the previous record so you can walk the ledger backward hop by hop. Content is sealed by default. Disclosed only if the record’s owner opted in, and party emails are masked, because none of that is required for verification: everything commits to the hash.
There’s a page at omnistrat.ai/verify that runs the whole procedure in your browser, re-hash, signature check, chain recompute, backward walk, with no libraries, so view-source is the audit. And because a page I serve deserves no trust either, the verification recipe is printed on it for reproduction with curl and openssl.
Time, from someone who isn’t me
A chain proves order, not wall-clock time. My timestamps are just claims. So the chain tip is periodically anchored with an RFC 3161 timestamp from an external Time-Stamping Authority. Since every link commits to its predecessor, one anchored tip proves the entire ledger existed at that moment. I cannot backdate a record into an already-anchored past without breaking hashes I no longer control.
Where it’s weak, honestly
Three things I’d want a skeptic to push on. First, split-view: between anchors, my server could in principle show different chain states to different verifiers; the anchor cadence bounds the window, but the real fix is publishing anchors to an independent transparency log or multiple TSAs, which is next on the list. Second, canonical JSON is a historically reliable source of bugs; the implementation is deliberately tiny and identical on both sides, but small is not the same as proven. Third, a verified receipt proves integrity and authorship, not truth, it proves nobody rewrote the record, not that the record was right.
The stack, for the curious
Cloudflare Workers for every endpoint, one Durable Object for chain advancement, KV for record state, R2 for immutable finalized blobs, D1 and Queues elsewhere in the platform. No servers, no containers, and the receipts pipeline costs approximately nothing at startup volume. One person can operate all of it. Which is the point: proof infrastructure shouldn’t require a platform team.
A live sample sits on the production chain, re-hash it, check its signature, and walk the ledger in your browser.
Open the verifier →