The audit trail of a run
Every signed-in run writes a hash-chained, RFC 3161 time-stamped record you can verify yourself. What the chain catches, what it does not, and how to check.
When a signed-in run calls a model or a tool on Ivy, the platform writes each event to an append-only log in which every row carries the hash of the row before it. Edit a row, delete one, or insert one out of order, and the chain breaks at the verifier. You can run that check yourself, on your own chats, from your account.
This page covers what the record contains, how the chain and its time-stamps are built, how to verify a chat, and where the record stops. For how the record maps to FDA 21 CFR Part 11, see 21 CFR Part 11.
What gets recorded
Signed-in runs from the web app, the terminal (ivybloom), curl and MCP all write to the same record:
- Web app and terminal chats write under their chat id.
- curl, MCP and
ivybloom orchestratecalls write under their session id, and the response'srecordfield gives the path to verify it. - Keyless guest calls over curl or orchestrate write nothing durable, so there is nothing to verify.
Each event is a row in the event log: the message, the tool call and its arguments, the tool result or a pointer to it, and the model call that produced the answer.
How the chain is built
- Each row is hashed. The row's payload is canonicalized (JSON Canonicalization Scheme, RFC 8785) and hashed with SHA-256, so the same content always produces the same hash regardless of key order.
- Each row links to the one before it. A row stores its own hash and the hash of the previous row in the same chat's chain. Changing any earlier row changes every hash after it.
- Chains are rolled up into Merkle roots. About every 30 minutes, each chat chain with new events is folded into a Merkle tree (the RFC 6962 construction for new chats), and the root is recorded with the range of rows it covers.
- Each root is time-stamped by a third party. The Merkle root is sent to a public RFC 3161 time-stamping authority, and the signed time-stamp token is stored beside it. The token shows that this exact root, and so every row under it, existed no later than the stamped time.
Verify a chat yourself
Signed in, open /audit/verify and pick a chat. The verifier recomputes every row's hash and every link from the stored payloads and reports one verdict:
| Verdict | Meaning |
|---|---|
verified | Every row recomputed, and every link matched |
verified_partial | The rows read all matched, but part of the record could not be checked |
not_verifiable | Part of the record could not be read, so no verdict was reached. This is never shown as a pass |
broken | A row's hash or link did not match. The response names where the chain broke |
no_record | No chained events exist for this chat |
From code, call GET /api/audit/chat-chain/{chatId} for a chat, or GET /api/audit/session-chain/{sessionId} for a curl, MCP or orchestrate session, with your API key. See the API reference.
What the chain catches, and what it does not
The chain detects modification. It does not prevent it, and it is not access control.
It catches:
- an in-place edit of a recorded event
- a deleted event in the middle of a chain
- an event inserted out of order, or a backfill presented as original
It does not, today:
- Stop a privileged database role from editing rows. The database does not refuse updates to the event log. The chain makes such an edit detectable; it does not make it impossible.
- Cover each event's own timestamp. An event's
created_atis the server's write time and is not part of its hash. The RFC 3161 token proves when a whole segment of the chain existed, not when each event inside it happened. - Check the time-stamp signatures automatically. Time-stamp tokens are collected for every anchored chain, but the scheduled pass does not yet validate their signatures against trusted roots. The tokens are stored and can be checked offline.
- Rebuild the full conversation. The chain covers the event log. Long message bodies and large tool outputs live in the message store and object storage, and the chain holds pointers to them, not hashes of every byte.
- Shred data on erasure. A GDPR erasure request deletes your data table by table. The hash-chained logs are kept for now, pending a ruling on how erasure and record retention interact, and the erasure report says so.
Export a job's record
For a job, GET /api/exports/glp-package?jobId=… returns a ZIP with the raw inputs, the parameters, the audit trail and checksums. GET /api/exports/chain-of-custody returns a JSON or HTML report of every transformation, analysis and access for a job, compound or project.
Related
Was this page helpful?
What a passport is
A passport aggregates several tools into one profile. Here is the model behind it, how it differs from running tools yourself, and when to reach for one.
21 CFR Part 11: what the Ivy record covers
How Ivy's hash-chained, time-stamped audit trail maps to FDA 21 CFR Part 11, clause by clause: what the platform provides today and what your team adds.