Receipt Schema

An enforcement receipt is a signed record documenting a governance decision. This specification defines the required fields, their semantics, and the signing process.

What are the required fields?

Every receipt must contain these fields. Missing or malformed fields invalidate the receipt during verification.

{
  // Identity
  "receipt_v": "1",                    // Schema version
  "receipt_id": "sha256:abc123...",    // Content-addressed ID
  "run_id": "run_xyz789",              // Parent run identifier

  // Sequencing
  "counter": 42,                       // Monotonic per run_id
  "timestamp": "2024-01-15T10:30:00Z", // Event time (ISO 8601 UTC)

  // Event classification
  "event_type": "ENFORCED",            // What happened

  // Decision details
  "decision": {
    "action": "BLOCK_EXECUTION",       // What action taken
    "reason_code": "DRIFT_DETECTED",   // Why
    "details": "Config hash mismatch"  // Human-readable
  },

  // Policy binding
  "policy": {
    "policy_id": "sha256:def456..."    // Which policy governed
  },

  // Measurement (if applicable)
  "measurement": {
    "composite_hash": "sha256:...",    // Current state hash
    "mismatched_paths": ["config.json"] // What differed
  },

  // Chain linkage
  "chain": {
    "prev_receipt_hash": "sha256:...", // Previous receipt
    "this_receipt_hash": "sha256:..."  // This receipt
  },

  // Signature
  "signer": {
    "public_key": "base64:...",
    "key_id": "a1b2c3d4e5f67890",
    "signature": "base64:..."
  }
}

What event types are defined?

The event_type field classifies what happened. Each type has specific semantics:

POLICY_LOADED

Policy artifact validated and activated at run start

MEASUREMENT_OK

Periodic measurement matched baseline (no drift)

DRIFT_DETECTED

Measurement differed from baseline

ENFORCED

Enforcement action executed (CONTINUE, BLOCK, TERMINATE)

BUNDLE_EXPORTED

Evidence bundle created and exported

CHECKPOINT

Periodic checkpoint with chain summary

How is receipt_id computed?

The receipt_id is a content-addressed hash that uniquely identifies the receipt content.

// Step 1: Prepare content
receipt_for_id = Receipt
  EXCLUDING signer.signature
  EXCLUDING receipt_id

// Step 2: Canonicalize
canonical_bytes = canonicalize(receipt_for_id)

// Step 3: Hash
receipt_id = "sha256:" + HEX(SHA-256(canonical_bytes))

How is this_receipt_hash computed?

The this_receipt_hash is used for chain linkage. It's computed differently from receipt_id:

// Step 1: Prepare content
receipt_for_hash = Receipt
  EXCLUDING signer.signature
  EXCLUDING chain.this_receipt_hash

// Step 2: Canonicalize
canonical_bytes = canonicalize(receipt_for_hash)

// Step 3: Hash
this_receipt_hash = "sha256:" + HEX(SHA-256(canonical_bytes))

How is the signature computed?

The signature covers the complete receipt except the signature itself:

// Step 1: Set receipt_id and this_receipt_hash

// Step 2: Prepare signing content
signing_content = Receipt
  INCLUDING receipt_id
  INCLUDING chain.this_receipt_hash
  EXCLUDING signer.signature

// Step 3: Canonicalize
canonical_bytes = canonicalize(signing_content)

// Step 4: Sign
signer.signature = BASE64(Ed25519_sign(private_key, canonical_bytes))

// Step 5: Set key_id
signer.key_id = HEX(SHA-256(public_key_bytes))[0:16]

What are the timestamp requirements?

Timestamps must follow strict formatting for deterministic verification:

  • Format: ISO 8601 with "Z" suffix (UTC)
  • Precision: Milliseconds recommended, seconds minimum
  • TSA tokens: Optional but recommended for high-assurance deployments
  • Degraded mode: If TSA unavailable, mark timestamp as DEGRADED_LOCAL

Frequently asked questions

Can I add custom fields?

Yes, in the "extensions" object. Custom fields in the root may affect hash computation and break verification in other implementations.

What if measurement fails?

Set measurement.composite_hash to null and include an error in decision.details. The receipt still gets signed and chained, documenting the failure.

Why separate receipt_id and this_receipt_hash?

receipt_id identifies the receipt content. this_receipt_hash is for chain linkage and excludes itself to avoid circular dependency. Both serve verification purposes.