{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://agenticrail.nz/spec/receipt-schema-v3.json",
  "title": "AgenticRail Pre-Execution Enforcement Receipt — v3",
  "description": "Schema for the cryptographically signed receipt issued by the AgenticRail gate for every pre-execution enforcement decision. slp8_receipt_v3 extends slp8_receipt_v2 with four new enforcement surfaces: HUMAN_OVERRIDE (override decision audit trail), TOOL_DENY (tool provenance gate), AGENT_HANDSHAKE (inter-agent receipt handshake), and SYSTEM_HALT (multi-agent compositionality enforcement). All v2 receipts remain valid — the extension is additive and backwards compatible. Published 2026-05-29.",
  "version": "slp8_receipt_v3",
  "datePublished": "2026-05-29",
  "dateModified": "2026-10-08",
  "changeNote": "2026-07-08: added prev_receipt_hash (nullable, required) — sha256 of the immediately preceding receipt's full canonical JSON, a genuine hash chain that catches an in-place edit to any earlier receipt's content, complementing prev_receipt_id's identifier-only linkage. Same field added to slp8_receipt_v2 at receipt-schema.json; this v3 extension mirrors it. Additive — schema version unchanged (slp8_receipt_v3). The fingerprint field below is unrelated to this change (it references the /spec/surfaces/ overview) and is left as-is. 2026-07-08 (later same day): removed a reference to the Hokianga Spine from the reasons field description — AgenticRail is a sequence-enforcement and accountability layer only, not a language or data sovereignty product; the Hokianga Spine was never wired into the live enforcement engine and is withdrawn. Non-normative errata — no schema structure change. 2026-09-30: added step_order (optional, nullable array of strings), mirroring the same addition to slp8_receipt_v2 at receipt-schema.json. Additive, schema version unchanged (slp8_receipt_v3); not in required, so receipts issued before it remain valid. 2026-10-08: added decision_index (optional, nullable integer, minimum 1), mirroring the same addition to slp8_receipt_v2 at receipt-schema.json. Additive, schema version unchanged (slp8_receipt_v3); not in required, so receipts issued before it remain valid.",
  "extends": "slp8_receipt_v2",
  "author": "Kade Cowper — TUARA KURI LIMITED",
  "contact": "hello@agenticrail.nz",
  "specUrl": "https://agenticrail.nz/spec/surfaces/",
  "copyright": "© 2026 TUARA KURI LIMITED. This document's text is protected by copyright. The schema it describes — and any enforcement mechanism implementing it — may be freely and independently implemented by others; publishing this schema openly is a deliberate choice to support standards engagement and independent verifiability.",
  "fingerprint": "f78767c88d227f5885a123bc35296fc970be02f1096b15bebd899c9403f60671",

  "type": "object",

  "required": [
    "ts_ms",
    "pack_id",
    "version",
    "decision",
    "reasons",
    "executed",
    "sealed",
    "meta",
    "prev_receipt_id",
    "prev_receipt_hash",
    "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.",
      "examples": ["e58338a76d00405d2f3a1b9c4e7f8a2d1c6b3e9f0a4d7c2b5e8f1a3d6c9b2e5"]
    },

    "version": {
      "type": "string",
      "enum": ["slp8_receipt_v2", "slp8_receipt_v3"],
      "description": "Receipt format version. v3 receipts may carry any of the new decision types and new denial codes. v2 receipts remain fully valid — the verification endpoint accepts both."
    },

    "decision": {
      "type": "string",
      "enum": [
        "ALLOW",
        "DENY",
        "HUMAN_OVERRIDE",
        "TOOL_DENY",
        "AGENT_HANDSHAKE",
        "SYSTEM_HALT"
      ],
      "description": "Enforcement decision. v2 values (ALLOW, DENY) retain their original semantics. HALT is NOT a decision and never appears in this field: it is a rejection returned at the boundary before enforcement runs, so no receipt is written at all. Do not confuse it with SYSTEM_HALT, which is a v3 receipt type and IS receipted. v3 adds four new decision types — one per enforcement surface introduced in slp8_receipt_v3. HUMAN_OVERRIDE: a human reviewer approved or rejected an AI decision; receipt records the override event. TOOL_DENY: a tool load was refused by the provenance gate. AGENT_HANDSHAKE: Agent B's gate validated Agent A's sealed chain and authorised the handshake. SYSTEM_HALT: supervisor gate detected compositional failure across multiple agents in a shared namespace."
    },

    "reasons": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "SEQUENCE_VIOLATION",
          "REPLAY_NONCE",
          "SEALED_SEQUENCE",
          "UNKNOWN_STEP",
          "FUNCTION_STEP_MISMATCH",
          "ACTION_NOT_ALLOWED",
          "STALE_TIMESTAMP",
          "ARTIFACT_UNBOUND",
          "TOOL_NOT_REGISTERED",
          "TOOL_SIGNATURE_INVALID",
          "AGENT_HANDSHAKE_FAIL",
          "CHAIN_BREAK",
          "CIRCULAR_AUTH",
          "SPLIT_AUTHORITY"
        ]
      },
      "description": "Denial, halt, or override reason codes. v2 codes carry their original meanings. UNKNOWN_STEP (v2, core; corrected 2026-07-05, supersedes the previously-published but unreachable NO_POLICY_MATCH): step/function value not present in this sequence's own declared step_order — an unrecognized name alone falls through to a permissive generic policy so custom step_order sequences work; only a name absent from the declared step_order is rejected. ARTIFACT_UNBOUND (v2, core): a RECORD_RESULT at boundary did not supply attestation.witnessed_pack_id matching the actual immediately-preceding receipt — the witness must attest to the real prior artifact, not an unverified claim. v3 adds: TOOL_NOT_REGISTERED (tool not present in signed registry), TOOL_SIGNATURE_INVALID (tool manifest does not match registered signature), AGENT_HANDSHAKE_FAIL (Agent A's chain is broken, unsealed, or unverifiable), CHAIN_BREAK / CIRCULAR_AUTH / SPLIT_AUTHORITY (system-level failure types carried on SYSTEM_HALT receipts).",
      "examples": [[], ["ARTIFACT_UNBOUND"], ["TOOL_NOT_REGISTERED"], ["AGENT_HANDSHAKE_FAIL"], ["CIRCULAR_AUTH"]]
    },

    "executed": {
      "type": "boolean",
      "description": "Whether the proposed transition was PERMITTED (the gate's verdict). True for ALLOW, AGENT_HANDSHAKE, and HUMAN_OVERRIDE with outcome=APPROVE. False for DENY, HALT, TOOL_DENY, SYSTEM_HALT, and HUMAN_OVERRIDE with outcome=REJECT. This attests the decision only — it does NOT assert the downstream action was carried out or succeeded; the executor's outcome is not part of the signed receipt (the public API response reports it separately under `execution_submitted` / `result`)."
    },

    "sealed": {
      "type": "boolean",
      "description": "Whether this receipt completes and permanently seals the sequence. v3: SYSTEM_HALT receipts always seal — once a system halts, the namespace is retired."
    },

    "meta": {
      "type": "object",
      "description": "Enforcement context captured at decision time. No field is modified after the receipt is written. v3 receipts add optional surface-specific blocks: override, tool, handshake, system.",
      "required": [
        "model_id",
        "sequence_id",
        "step",
        "function",
        "action_type",
        "policy_map_ids"
      ],
      "properties": {
        "model_id": {
          "type": ["string", "null"],
          "examples": ["MSMD"]
        },
        "sequence_id": {
          "type": ["string", "null"],
          "examples": ["whiro-write-f2646de0f129"]
        },
        "step": {
          "type": ["string", "null"],
          "examples": ["intake", "execution", "settle"]
        },
        "function": {
          "type": ["string", "null"],
          "examples": ["intake"]
        },
        "action_type": {
          "type": ["string", "null"],
          "examples": ["CHECK_STATE", "RECORD_RESULT", "TOOL_LOAD", "OVERRIDE_DECISION", "HANDSHAKE", "SYSTEM_CHECK"]
        },
        "policy_map_ids": {
          "type": "array",
          "items": { "type": "string" }
        },
        "override": {
          "type": ["object", "null"],
          "description": "Present when decision is HUMAN_OVERRIDE. Records the override event independently of the system being overridden.",
          "required": ["reviewer_id", "rationale_hash", "review_duration_ms", "overridden_step", "outcome"],
          "properties": {
            "reviewer_id": {
              "type": "string",
              "description": "Identifier of the human reviewer. Should resolve to a verifiable identity in the operator's directory."
            },
            "rationale_hash": {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$",
              "description": "SHA-256 of the reviewer's documented rationale. Rationale text stored separately; hash bound into the receipt."
            },
            "review_duration_ms": {
              "type": "integer",
              "minimum": 0,
              "description": "Elapsed time between the override request and the override decision. Systematic rubber-stamping (near-zero duration) becomes detectable across the receipt chain."
            },
            "overridden_step": {
              "type": "string",
              "description": "The step name (or pack_id reference) of the AI decision being overridden."
            },
            "outcome": {
              "type": "string",
              "enum": ["APPROVE", "REJECT"],
              "description": "The human's decision. APPROVE: AI step permitted to proceed. REJECT: AI step refused; sequence continues to next step or halts per policy."
            }
          },
          "additionalProperties": false
        },
        "tool": {
          "type": ["object", "null"],
          "description": "Present when decision is TOOL_DENY (or, optionally, on tool load ALLOW receipts). Records the tool provenance check.",
          "required": ["tool_id", "tool_hash", "registry_checked"],
          "properties": {
            "tool_id": {
              "type": "string",
              "description": "Identifier of the tool the agent attempted to load. Includes source server for MCP tools."
            },
            "tool_hash": {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$",
              "description": "SHA-256 of the tool manifest as presented to the gate at load time."
            },
            "registry_checked": {
              "type": "boolean",
              "description": "Whether the gate consulted the signed tool registry. False indicates a registry unavailability — should not occur in production."
            },
            "registry_signature_id": {
              "type": ["string", "null"],
              "description": "Identifier of the signature found in the registry for this tool_id. Null when tool_id is not present in the registry."
            }
          },
          "additionalProperties": false
        },
        "handshake": {
          "type": ["object", "null"],
          "description": "Present when decision is AGENT_HANDSHAKE. Records the cross-agent chain verification.",
          "required": [
            "source_agent_id",
            "source_sequence_id",
            "source_final_receipt_id",
            "source_chain_verified",
            "receiving_agent_id"
          ],
          "properties": {
            "source_agent_id": {
              "type": "string",
              "description": "Identifier of Agent A (the source agent passing instructions)."
            },
            "source_sequence_id": {
              "type": "string",
              "description": "Sequence ID of Agent A's completed chain."
            },
            "source_final_receipt_id": {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$",
              "description": "pack_id of Agent A's sealed terminal receipt. Anchors the handshake to a specific cryptographic point in Agent A's chain."
            },
            "source_chain_verified": {
              "type": "boolean",
              "description": "True if Agent B's gate independently verified Agent A's complete chain from intake to settle, including all signatures and chain links. False triggers AGENT_HANDSHAKE_FAIL."
            },
            "source_chain_break_at": {
              "type": ["string", "null"],
              "pattern": "^[0-9a-f]{64}$|^$",
              "description": "If source_chain_verified is false, the pack_id of the receipt at which chain verification failed. Null on successful handshake."
            },
            "receiving_agent_id": {
              "type": "string",
              "description": "Identifier of Agent B (the receiving agent whose gate issued this receipt)."
            }
          },
          "additionalProperties": false
        },
        "system": {
          "type": ["object", "null"],
          "description": "Present when decision is SYSTEM_HALT. Records the system-level compositional failure detected by the supervisor gate.",
          "required": [
            "system_id",
            "failing_agent_id",
            "failing_sequence_id",
            "failure_type",
            "agents_in_namespace"
          ],
          "properties": {
            "system_id": {
              "type": "string",
              "description": "Shared namespace identifier for the multi-agent system."
            },
            "failing_agent_id": {
              "type": "string",
              "description": "Identifier of the agent whose state triggered the halt."
            },
            "failing_sequence_id": {
              "type": "string",
              "description": "Sequence ID at which the failure was detected."
            },
            "failure_type": {
              "type": "string",
              "enum": ["CHAIN_BREAK", "CIRCULAR_AUTH", "SPLIT_AUTHORITY", "HANDSHAKE_FAIL"],
              "description": "Compositional failure taxonomy. CHAIN_BREAK: an agent's chain failed integrity verification. CIRCULAR_AUTH: agents granted each other authority no single agent held. SPLIT_AUTHORITY: agents received conflicting instructions purporting to come from the same source. HANDSHAKE_FAIL: an unauthorised inter-agent handshake was attempted."
            },
            "chain_break_at_receipt_id": {
              "type": ["string", "null"],
              "pattern": "^[0-9a-f]{64}$|^$",
              "description": "If failure_type is CHAIN_BREAK, the pack_id of the broken link. Null otherwise."
            },
            "agents_in_namespace": {
              "type": "integer",
              "minimum": 1,
              "description": "Count of agents participating in the shared namespace at the time of the halt."
            }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },

    "attestation": {
      "type": ["object", "null"],
      "description": "Optional signed evidence attached to this step. Unchanged from v2.",
      "examples": [null, { "chip_id": "WHIRO-2026-05-29-001" }]
    },

    "payload_hash": {
      "type": ["string", "null"],
      "pattern": "^[0-9a-f]{64}$|^$"
    },

    "prev_receipt_id": {
      "type": ["string", "null"],
      "pattern": "^[0-9a-f]{64}$|^null$",
      "description": "pack_id of the immediately preceding receipt in this sequence — an identifier reference, not a content hash (see prev_receipt_hash for that guarantee). v3: in AGENT_HANDSHAKE receipts, prev_receipt_id is the receiving agent's last receipt — the source agent's chain is referenced via meta.handshake.source_final_receipt_id, not via prev_receipt_id."
    },

    "prev_receipt_hash": {
      "type": ["string", "null"],
      "pattern": "^[0-9a-f]{64}$|^null$",
      "description": "SHA-256 hex digest of the immediately preceding receipt's full canonical JSON (signature included) — not merely its pack_id. Added 2026-07-08. An in-place edit to any earlier receipt changes its hash, breaking every subsequent prev_receipt_hash link even if pack_id references still match. Null for the first step, and null when the resolved anchor predates this field — treated as 'not verifiable', not 'broken'."
    },

    "step_order": {
      "type": ["array", "null"],
      "items": { "type": "string" },
      "description": "The step order this call was evaluated against, exactly as enforced: the caller's declared step_order, normalised (trimmed, lower-cased), or the default eight-step MSMD spine when the caller declared none. Added 2026-09-30. On every ALLOW it is the order the sequence is locked to, because a call presenting any other order is refused with STEP_ORDER_MISMATCH. It makes the declaration part of the signed evidence: a sequence whose declared order leaves a step out, an approval for example, now shows that omission in its receipts, where previously a receipt named only the step that ran. Covered by the signature like every other receipt field. Not part of the pack, so pack_id derivation is unchanged. Absent from receipts issued before this field was introduced; not in required, so those receipts remain valid.",
      "examples": [
        ["verify_identity", "request_approval", "deploy_production"],
        ["intake", "disruption", "instability", "state_read", "internal_driver", "execution", "boundary", "settle"]
      ]
    },

    "decision_index": {
      "type": ["integer", "null"],
      "minimum": 1,
      "description": "The position of this decision among every decision recorded for this sequence, counted from 1 across ALLOW and DENY alike. Added 2026-10-08. The hash chain (prev_receipt_id, prev_receipt_hash) runs through ALLOWED receipts only, so a refused step points back but nothing points to it; without this field a DENY receipt could be removed with no break in any link. With it, a removed receipt of either decision leaves a missing number, and the numbering cannot be closed up without re-signing every later receipt. A missing number means the receipt is absent: never written or removed, which the report does not attempt to tell apart. It cannot show receipts missing after the highest number present. Null when the index could not be reserved; such a receipt cannot be placed, and is not read as a gap. Covered by the signature like every other receipt field. Not part of the pack, so pack_id derivation is unchanged. Absent from receipts issued before this field was introduced; not in required, so those receipts remain valid, and a sequence open across the change is numbered from 1 at its first receipt after it.",
      "examples": [1, 7, null]
    },

    "key_id": {
      "type": "string",
      "examples": ["k1_2026-02-22_01"]
    },

    "signature_alg": {
      "type": ["string", "null"],
      "enum": ["hmac-sha256", "Ed25519", null]
    },

    "signature": {
      "type": ["string", "null"]
    }

  },

  "additionalProperties": false,

  "$defs": {

    "pack": {
      "title": "AgenticRail Enforcement Pack — v3",
      "description": "The enforcement decision object whose SHA-256 canonical hash becomes the pack_id. v3 packs may carry the new decision values and surface-specific meta blocks. pack_id = SHA-256(canonicalJson(pack)).",
      "type": "object",
      "required": ["pack_version", "decision", "reasons", "executed", "meta"],
      "properties": {
        "pack_version": {
          "type": "string",
          "enum": ["slp8_pack_1.0", "slp8_pack_1.1"],
          "description": "slp8_pack_1.0 used by v2 receipts. slp8_pack_1.1 used by v3 receipts carrying surface-specific meta blocks."
        },
        "decision": {
          "type": "string",
          "enum": [
            "ALLOW",
            "DENY",
            "HUMAN_OVERRIDE",
            "TOOL_DENY",
            "AGENT_HANDSHAKE",
            "SYSTEM_HALT"
          ]
        },
        "reasons": {
          "type": "array",
          "items": { "type": "string" }
        },
        "executed": {
          "type": "boolean"
        },
        "meta": {
          "type": "object"
        }
      },
      "additionalProperties": false
    }

  }
}
