
This page documents the verification format so any third party can independently verify an AuditTrail chain without an account. The verify page implements this spec client-side — no data leaves your browser.
Chain verification format
AuditTrail spec v1 — stable, frozen once shipped.
What a chain is
A chain is an ordered array of CaptureRecord objects. Each record contains the full text of one AI conversation turn (prompt + response), metadata (provider, model, URL, timestamp), and a SHA-256 hash that incorporates the hash of the preceding record. This makes any post-hoc modification of a record detectable without needing a trusted third party for the chain itself.
CaptureRecord shape
This is the TypeScript interface from packages/types/src/index.ts:
interface CaptureRecord {
/** UUID of the capture event */
event_id: string;
/** UUID of the user who made the capture */
user_id: string;
/** AI provider: e.g. "claude.ai", "chatgpt.com", "gemini.google.com" */
provider: string;
/** The user's prompt text */
prompt: string;
/** The AI model's response text */
response: string;
/** Model identifier, if detectable; null otherwise */
model: string | null;
/** URL of the page where the capture occurred */
url: string;
/** ISO-8601 timestamp of when the capture occurred */
captured_at: string;
/** Hash of the preceding record in the chain; null for the first record */
previous_hash: string | null;
/** SHA-256 hash of this record's canonical JSON (see spec below) */
hash: string;
/** Hash algorithm version; currently always 1 */
hash_version: number;
}Hash algorithm
The hash field is not part of the hash input — a field cannot be part of its own hash. All other fields from the interface above are included.
// 1. Select exactly these 10 fields (NOT the "hash" field itself): // captured_at, event_id, hash_version, model, previous_hash, // prompt, provider, response, url, user_id // 2. Sort keys lexicographically by Unicode code point. // 3. Serialize with JSON.stringify — no whitespace, no newlines. // 4. SHA-256 of the UTF-8 encoded canonical JSON string → 64 lowercase hex chars. hash = hex(SHA-256(UTF-8(JSON.stringify(sortedFields))))
Chain rules
- The first record per user has
previous_hash = null. - Every subsequent record has
previous_hashequal to thehashof the immediately preceding record. - Records are ordered by
captured_atascending. Ties broken byevent_idascending (UUID lexicographic). - A chain is valid if and only if: (1) the first record has
previous_hash = null; (2) each subsequent record'sprevious_hashmatches the prior record'shash; (3) the recomputed hash of every record matches its storedhash.
Worked example (3 records)
This 3-record chain is the canonical test fixture. You can paste it into the verify page and confirm it passes.
[
{
"event_id": "550e8400-e29b-41d4-a716-446655440001",
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"provider": "claude.ai",
"prompt": "What are the professional obligations of a lawyer using AI?",
"response": "A lawyer using AI must maintain competence, supervise AI output, and not delegate judgment to the model.",
"model": null,
"url": "https://claude.ai/chat/abc123",
"captured_at": "2026-05-21T01:00:00.000Z",
"hash_version": 1,
"previous_hash": null,
"hash": "684cfa53d3a04c51f57649b4ba537838fdb98776a51d00ca9179a39d6ceaab79"
},
{
"event_id": "550e8400-e29b-41d4-a716-446655440002",
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"provider": "chatgpt.com",
"prompt": "Summarise the duty of candour in Canadian law.",
"response": "The duty of candour requires lawyers to be honest with the court and not mislead through omission.",
"model": "gpt-4o",
"url": "https://chatgpt.com/c/def456",
"captured_at": "2026-05-21T01:05:00.000Z",
"hash_version": 1,
"previous_hash": "684cfa53d3a04c51f57649b4ba537838fdb98776a51d00ca9179a39d6ceaab79",
"hash": "57c659e9bb7596018db431af824ff974352950ef72f9fec9dced3755a9883497"
},
{
"event_id": "550e8400-e29b-41d4-a716-446655440003",
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"provider": "claude.ai",
"prompt": "Draft a confidentiality clause for an NDA.",
"response": "Each party agrees to hold in strict confidence all Confidential Information received from the other party.",
"model": null,
"url": "https://claude.ai/chat/ghi789",
"captured_at": "2026-05-21T01:10:00.000Z",
"hash_version": 1,
"previous_hash": "57c659e9bb7596018db431af824ff974352950ef72f9fec9dced3755a9883497",
"hash": "213fb5299d2e48bff63f2d817df998ba9af96e29499ef63c08e95d0fd6ddc67a"
}
]Verify record 0 manually
The canonical JSON for record 0 (keys sorted, no whitespace):
echo -n '{"captured_at":"2026-05-21T01:00:00.000Z","event_id":"550e8400-e29b-41d4-a716-446655440001","hash_version":1,"model":null,"previous_hash":null,"prompt":"What are the professional obligations of a lawyer using AI?","provider":"claude.ai","response":"A lawyer using AI must maintain competence, supervise AI output, and not delegate judgment to the model.","url":"https://claude.ai/chat/abc123","user_id":"550e8400-e29b-41d4-a716-446655440000"}' | sha256sum
# → 684cfa53d3a04c51f57649b4ba537838fdb98776a51d00ca9179a39d6ceaab79 -hash_version is part of the hash input. If the algorithm changes in a future version, it will get a new version number and a new spec. Old chains remain verifiable against their own spec version. This page documents version 1, which is stable and frozen.