AuditTrail

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

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  -
Versioning. 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.