{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://agenticrail.nz/spec/receipt-schema.json",
  "title": "AgenticRail Pre-Execution Enforcement Receipt",
  "description": "Schema for the cryptographically signed receipt issued by the AgenticRail gate for every pre-execution enforcement decision. Each receipt documents one step in a deterministic sequence: the action proposed, the policy evaluated, the decision reached (ALLOW / DENY / HALT), and the cryptographic proof of integrity. Receipts are chained via prev_receipt_id, forming a tamper-evident audit log for the complete sequence. Published 2026-05-17. Schema version: slp8_receipt_v2.",
  "version": "slp8_receipt_v2",
  "datePublished": "2026-05-17",
  "dateModified": "2026-06-07",
  "changeNote": "2026-06-07: clarified the active signing algorithm (Ed25519, key_id k2_2026-06-07_ed25519) and corrected the signature encoding (Ed25519 is base64, not hex). The receipt structure, required fields, types, and enums are unchanged from the 2026-05-17 publication — this is non-normative errata. The v1.0/v1.1 enforcement-spec fingerprints are at /spec/ ; verification keys at /spec/receipt-public-keys.json.",
  "author": "Kade Cowper — TUARA KURI LIMITED",
  "contact": "hello@agenticrail.nz",
  "specUrl": "https://agenticrail.nz/spec/",

  "type": "object",
  "required": [
    "ts_ms",
    "pack_id",
    "version",
    "decision",
    "reasons",
    "executed",
    "sealed",
    "meta",
    "prev_receipt_id",
    "key_id",
    "signature_alg",
    "signature"
  ],

  "properties": {

    "ts_ms": {
      "type": "integer",
      "description": "Unix timestamp in milliseconds at which the enforcement decision was made. Used for freshness validation: |ts_ms - now| > 300,000 ms triggers STALE_TIMESTAMP denial.",
      "examples": [1747821600000]
    },

    "pack_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$",
      "description": "SHA-256 hex digest of the canonical pack object (alphabetically sorted keys, no extra whitespace). Serves as the receipt's unique identifier and integrity anchor. The report generator re-derives this hash to verify the receipt has not been altered.",
      "examples": ["e58338a76d00405d2f3a1b9c4e7f8a2d1c6b3e9f0a4d7c2b5e8f1a3d6c9b2e5"]
    },

    "version": {
      "type": "string",
      "const": "slp8_receipt_v2",
      "description": "Receipt format version. slp8_receipt_v2 uses canonical JSON (alphabetically sorted keys) for pack_id derivation and for the signature preimage. The receipt format is independent of the signing algorithm: receipts issued from 2026-06-07 are signed Ed25519 (key_id k2_2026-06-07_ed25519); earlier receipts are HMAC-SHA-256 (key_id k1_2026-02-22_01). Gen-1 receipts used JSON.stringify — both are supported by the verification endpoint. Published keys: /spec/receipt-public-keys.json."
    },

    "decision": {
      "type": "string",
      "enum": ["ALLOW", "DENY", "HALT"],
      "description": "Enforcement decision. ALLOW: action is authorised to proceed, receipt is issued. DENY: action is blocked, reasons array is populated. HALT: sequence is terminated immediately regardless of subsequent steps; no recovery path exists.",
      "examples": ["ALLOW"]
    },

    "reasons": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "SEQUENCE_VIOLATION",
          "REPLAY_NONCE",
          "SEALED_SEQUENCE",
          "NO_POLICY_MATCH",
          "FUNCTION_STEP_MISMATCH",
          "ACTION_NOT_ALLOWED",
          "STALE_TIMESTAMP"
        ]
      },
      "description": "Denial or halt reason codes. Empty array when decision is ALLOW. Each code maps to a specific enforcement rule: SEQUENCE_VIOLATION (step submitted out of order), REPLAY_NONCE (nonce already seen — replay attempt blocked), SEALED_SEQUENCE (sequence permanently closed after settle step), NO_POLICY_MATCH (function not in enforcement policy), FUNCTION_STEP_MISMATCH (step and function fields differ), ACTION_NOT_ALLOWED (action_type not permitted at this step), STALE_TIMESTAMP (ts_ms outside 300-second freshness window).",
      "examples": [[], ["REPLAY_NONCE"], ["SEQUENCE_VIOLATION"]]
    },

    "executed": {
      "type": "boolean",
      "description": "Whether the proposed action was permitted to execute. True when decision is ALLOW; false when DENY or HALT. The gate returns this field so downstream systems can confirm authorisation without re-evaluating the decision field.",
      "examples": [true]
    },

    "sealed": {
      "type": "boolean",
      "description": "Whether this receipt completes and permanently seals the sequence. True only on the final step (settle). Once sealed, the sequence_id is retired — no further steps are accepted. Sealing is irreversible: no unsealing mechanism exists.",
      "examples": [false, true]
    },

    "meta": {
      "type": "object",
      "description": "Enforcement context captured at decision time. All fields are immutable after the receipt is written.",
      "required": [
        "model_id",
        "sequence_id",
        "step",
        "function",
        "action_type",
        "policy_map_ids"
      ],
      "properties": {
        "model_id": {
          "type": ["string", "null"],
          "description": "Agent model identifier. Scopes the Durable Object namespace — sequences with different model_ids are isolated from each other.",
          "examples": ["MSMD"]
        },
        "sequence_id": {
          "type": ["string", "null"],
          "description": "Unique identifier for this decision sequence. All receipts sharing a sequence_id form one chained sequence. Must be unique per agent run; reuse constitutes a replay attempt.",
          "examples": ["whiro-write-f2646de0f129"]
        },
        "step": {
          "type": ["string", "null"],
          "description": "The spine step name submitted by the client. Must equal the function field — divergence triggers FUNCTION_STEP_MISMATCH denial.",
          "examples": ["intake", "execution", "settle"]
        },
        "function": {
          "type": ["string", "null"],
          "description": "The enforcement function resolved from the submitted step. Must match one of the eight canonical MSMD spine functions. Unknown values trigger NO_POLICY_MATCH denial.",
          "enum": ["intake", "disruption", "instability", "state_read", "internal_driver", "execution", "boundary", "settle", null],
          "examples": ["intake"]
        },
        "action_type": {
          "type": ["string", "null"],
          "description": "The type of action proposed at this step. Evaluated against the function's allowed_action_types policy. Disallowed values trigger ACTION_NOT_ALLOWED denial.",
          "examples": ["CHECK_STATE", "RECORD_RESULT", "WAIT_FOR_SIGNAL"]
        },
        "policy_map_ids": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Identifiers of the policy maps evaluated for this function. Used by the report generator to verify receipt integrity across policy versions.",
          "examples": [["policy_state_read_unfamiliar_terrain"], ["policy_settle_cycle_close", "policy_settle_evaluation_after_cycle"]]
        }
      },
      "additionalProperties": false
    },

    "attestation": {
      "type": ["object", "null"],
      "description": "Optional signed evidence attached to this step by the client. Arbitrary key-value pairs that the gate signs into the receipt without interpreting. Enables clients to bind external evidence (test results, sensor readings, human approvals) to a specific enforcement decision. The gate excludes attestation from poison detection.",
      "examples": [null, { "chip_id": "WHIRO-2026-05-21-001", "chip_path": "C:\\MSMD_MASTER\\..." }]
    },

    "payload_hash": {
      "type": ["string", "null"],
      "pattern": "^[0-9a-f]{64}$|^$",
      "description": "SHA-256 hex digest of the raw request payload body. Provides an additional integrity link between the enforcement decision and the exact payload that triggered it. Null in some early receipts.",
      "examples": [null, "a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2"]
    },

    "prev_receipt_id": {
      "type": ["string", "null"],
      "pattern": "^[0-9a-f]{64}$|^null$",
      "description": "pack_id of the immediately preceding receipt in this sequence. Forms the chain: each receipt references its predecessor, creating a linked structure where any tampering with an earlier receipt breaks all subsequent chain links. Null for the first step (intake).",
      "examples": [null, "9cc8c77cf469b41dab0511470e94851949d0441ac96e7052c65edbe243d6d32d"]
    },

    "key_id": {
      "type": "string",
      "description": "Identifier of the signing key used to produce the signature field. Enables key rotation: verifiers select the correct public key (Ed25519) or HMAC secret by key_id without parsing the signature itself. Resolve against the published keyring at /spec/receipt-public-keys.json. Current: k2_2026-06-07_ed25519 (Ed25519); legacy: k1_2026-02-22_01 (HMAC).",
      "examples": ["k2_2026-06-07_ed25519", "k1_2026-02-22_01"]
    },

    "signature_alg": {
      "type": ["string", "null"],
      "enum": ["hmac-sha256", "Ed25519", null],
      "description": "Signing algorithm. Ed25519 (active since 2026-06-07): EdDSA signature; verifiable offline by anyone using the published public key, no call back to AgenticRail. hmac-sha256 (legacy): HMAC-SHA-256 — symmetric, so not third-party verifiable; verify via the report endpoint. Both are computed over the canonical JSON of the receipt with the signature field removed (alphabetically sorted keys). Null indicates the receipt was written without a signing key configured — should not occur in production.",
      "examples": ["Ed25519", "hmac-sha256"]
    },

    "signature": {
      "type": ["string", "null"],
      "description": "Cryptographic signature of the receipt. For Ed25519: base64-encoded EdDSA signature (64 bytes → 88 base64 chars). For hmac-sha256: hex-encoded HMAC-SHA-256 digest (64 hex chars). Computed over the canonical JSON of all receipt fields excluding the signature field itself. Ed25519 signatures verify against /spec/receipt-public-keys.json; both are verifiable via the report endpoint.",
      "examples": ["3hJ1m0Qm8eYJ0n2k9xqQ2bFaXyWn0r3sKQ5sI9oG1cV6Yc0a8e2tF7uH4wI3rN1pL5dM2bV0xZ9oH7gW4cT8aQ==", "53241ed9add2ca63b6bc0d6865d297b9203101df788f7924a74e1734caf51b13"]
    }

  },

  "additionalProperties": false,

  "$defs": {

    "pack": {
      "title": "AgenticRail Enforcement Pack",
      "description": "The enforcement decision object whose SHA-256 canonical hash becomes the pack_id. The pack is computed before the receipt is written — it is the minimal authoritative record of the enforcement decision, independent of receipt metadata. pack_id = SHA-256(canonicalJson(pack)).",
      "type": "object",
      "required": ["pack_version", "decision", "reasons", "executed", "meta"],
      "properties": {
        "pack_version": {
          "type": "string",
          "const": "slp8_pack_1.0"
        },
        "decision": {
          "type": "string",
          "enum": ["ALLOW", "DENY", "HALT"]
        },
        "reasons": {
          "type": "array",
          "items": { "type": "string" }
        },
        "executed": {
          "type": "boolean"
        },
        "meta": {
          "type": "object",
          "required": ["model_id", "sequence_id", "step", "function", "action_type", "policy_map_ids"],
          "properties": {
            "model_id":       { "type": ["string", "null"] },
            "sequence_id":    { "type": ["string", "null"] },
            "step":           { "type": ["string", "null"] },
            "function":       { "type": ["string", "null"] },
            "action_type":    { "type": ["string", "null"] },
            "policy_map_ids": { "type": "array", "items": { "type": "string" } }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },

    "request_payload": {
      "title": "AgenticRail Gate Request Payload",
      "description": "The payload submitted by a client to POST /v1/evaluate. Every field is evaluated by the gate; the raw body is hashed to produce receipt.payload_hash.",
      "type": "object",
      "required": ["schema_version", "model_id", "sequence_id", "step", "function", "action_type", "nonce", "ts_ms", "step_order"],
      "properties": {
        "schema_version": {
          "type": "string",
          "const": "1.0"
        },
        "model_id": {
          "type": "string",
          "examples": ["MSMD"]
        },
        "sequence_id": {
          "type": "string",
          "description": "Unique identifier for this agent run. Must be unique per sequence — reuse triggers REPLAY_NONCE or SEQUENCE_VIOLATION."
        },
        "step": {
          "type": "string",
          "enum": ["intake", "disruption", "instability", "state_read", "internal_driver", "execution", "boundary", "settle"],
          "description": "Must equal the function field."
        },
        "function": {
          "type": "string",
          "enum": ["intake", "disruption", "instability", "state_read", "internal_driver", "execution", "boundary", "settle"],
          "description": "Must equal the step field."
        },
        "action_type": {
          "type": "string",
          "description": "Evaluated against the function's allowed_action_types policy."
        },
        "action": {
          "type": "string",
          "description": "Human-readable label for the proposed action. Stored in attestation context; not evaluated by policy."
        },
        "inputs": {
          "type": "object",
          "description": "Arbitrary key-value inputs for the step. Not evaluated by policy; available for attestation and logging."
        },
        "nonce": {
          "type": "string",
          "description": "Unique value per request. The gate rejects any nonce seen before in this sequence — prevents replay attacks. UUID or hex-encoded random bytes recommended."
        },
        "ts_ms": {
          "type": "integer",
          "description": "Client timestamp in Unix milliseconds. Gate enforces |ts_ms - server_now| <= 300,000 ms — prevents delayed replay."
        },
        "step_order": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Complete ordered list of spine steps for this sequence. Must be sent on every request — the gate does not store step_order between calls.",
          "examples": [["intake", "disruption", "instability", "state_read", "internal_driver", "execution", "boundary", "settle"]]
        },
        "attestation": {
          "type": "object",
          "description": "Optional. Arbitrary evidence to be signed into the receipt at this step."
        }
      }
    }

  }
}
