{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://thehiveryiq.com/.well-known/schemas/routing-receipt-v1.json",
  "title": "Routing Receipt v1, proof-driven routing receipt",
  "description": "Canonical schema for the Hive proof-driven routing receipt, canonical type routing.receipt. Binds a route policy digest declared before candidate selection, an evidence snapshot for every candidate considered, the selected route, deterministic reason codes, and a final selection proof into one Ed25519 signature over a recomputable payload_sha256. The receipt attests that the selection is exactly what the declared policy would produce from the declared evidence snapshot, inside the declared decision window. It does not attest that the policy itself is good, or that the evidence snapshot was collected honestly.",
  "type": "object",
  "required": [
    "receipt_id",
    "payload_sha256",
    "sig_b64u",
    "key_id",
    "algorithm",
    "ts",
    "signed_body"
  ],
  "additionalProperties": false,
  "properties": {
    "receipt_id": {
      "type": "string",
      "pattern": "^r_routing\\.receipt_[0-9]{10,}_[0-9a-f]{12}$"
    },
    "payload_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
    "sig_b64u": { "type": "string", "pattern": "^[A-Za-z0-9_-]{86}$" },
    "key_id": { "type": "string", "pattern": "^did:(hive|web|key):[A-Za-z0-9._:%-]+$" },
    "algorithm": { "type": "string", "const": "Ed25519" },
    "ts": { "type": "integer", "minimum": 1000000000 },
    "signed_body": {
      "type": "object",
      "required": [
        "receipt_type",
        "schema",
        "decision_id",
        "tenant_id",
        "route_policy",
        "decision_window",
        "candidates",
        "selected_route_id",
        "reason_codes",
        "boundary"
      ],
      "additionalProperties": false,
      "properties": {
        "receipt_type": { "type": "string", "const": "routing.receipt" },
        "schema": { "type": "string", "const": "r1.0.0" },
        "decision_id": { "type": "string", "minLength": 1, "maxLength": 256 },
        "tenant_id": { "type": "string", "minLength": 1, "maxLength": 256 },
        "route_policy": {
          "type": "object",
          "description": "The policy this decision is bound to, declared before any candidate was scored.",
          "required": ["policy_id", "policy", "policy_sha256", "declared_at", "selection_rule"],
          "additionalProperties": false,
          "properties": {
            "policy_id": { "type": "string", "minLength": 1, "maxLength": 256 },
            "policy": {
              "type": "object",
              "description": "The actual policy content that policy_sha256 is a digest of, so a verifier can recompute the digest canonically rather than trust a caller supplied value. Contents are opaque beyond selection_rule, which is separately declared and checked below, this object exists to be hashed, not interpreted.",
              "required": ["selection_rule", "rule_params"],
              "additionalProperties": false,
              "properties": {
                "selection_rule": {
                  "type": "string",
                  "enum": ["max_score", "min_score", "first_eligible"]
                },
                "rule_params": {
                  "type": "object",
                  "description": "Opaque parameters for the selection rule, for example score field weights or thresholds. Present so real policy content, not just the rule name, is bound by policy_sha256.",
                  "additionalProperties": true
                }
              }
            },
            "policy_sha256": {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$",
              "description": "Digest over the canonical serialisation of policy. Full verification recomputes this digest from policy and rejects the receipt if it does not match, rather than trusting the caller supplied digest alone."
            },
            "declared_at": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}([.][0-9]{1,9})?Z$",
              "description": "When the policy digest was fixed. Must not follow decision_window.opens_at."
            },
            "selection_rule": {
              "type": "string",
              "enum": ["max_score", "min_score", "first_eligible"],
              "description": "The deterministic rule applied to eligible candidates. max_score and min_score break ties by candidate_id ascending. first_eligible selects the first eligible candidate in candidates order."
            }
          }
        },
        "decision_window": {
          "type": "object",
          "required": ["opens_at", "closes_at"],
          "additionalProperties": false,
          "properties": {
            "opens_at": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}([.][0-9]{1,9})?Z$"
            },
            "closes_at": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}([.][0-9]{1,9})?Z$"
            }
          }
        },
        "candidates": {
          "type": "array",
          "minItems": 1,
          "maxItems": 1024,
          "description": "Every candidate considered, with the evidence snapshot the policy scored it against.",
          "items": {
            "type": "object",
            "required": ["candidate_id", "eligible", "evidence_sha256"],
            "additionalProperties": false,
            "properties": {
              "candidate_id": { "type": "string", "minLength": 1, "maxLength": 256 },
              "eligible": { "type": "boolean" },
              "score": { "type": "number" },
              "evidence_sha256": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$",
                "description": "Digest of the evidence snapshot used to score this candidate. A change to underlying evidence after selection changes this digest and is detectable without disclosing the evidence."
              },
              "ineligible_reason_code": { "type": "string", "minLength": 1, "maxLength": 128 }
            }
          }
        },
        "selected_route_id": { "type": "string", "minLength": 1, "maxLength": 256 },
        "reason_codes": {
          "type": "array",
          "minItems": 1,
          "maxItems": 32,
          "items": { "type": "string", "minLength": 1, "maxLength": 128 }
        },
        "selection_proof": {
          "type": "object",
          "description": "Optional compact proof binding the winning candidate to the policy and the candidate set, for a holder who wants to check selection without recomputing every score.",
          "required": ["candidate_set_sha256"],
          "additionalProperties": false,
          "properties": {
            "candidate_set_sha256": {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$",
              "description": "Digest over the canonical serialisation of the candidates array, binding the final proof to the exact candidate set considered."
            }
          }
        },
        "boundary": {
          "type": "string",
          "const": "This receipt attests that route_policy.policy_sha256 recomputes canonically from route_policy.policy, that route_policy.declared_at does not follow decision_window.opens_at, and that selected_route_id is exactly what route_policy.selection_rule and route_policy.policy would produce from the candidates array as scored against evidence_sha256 for each candidate, inside decision_window. It does not attest that the policy is well designed, that any candidate score reflects real world quality, or that the evidence snapshot behind evidence_sha256 was collected honestly."
        }
      }
    }
  }
}
