{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://thehiveryiq.com/.well-known/schemas/custody-handoff-v1.json",
  "title": "Custody Handoff Receipt, v1",
  "description": "Canonical schema for the Hive custody handoff receipt, canonical type custody.handoff. The instrument records that custody of one artifact moved between two organizations because both sides independently signed a custody.presentation over the same artifact commitment and the same handoff terms. One side asserting a movement produces nothing. When the two presentations do not carry the same terms the receipt records a verdict of custody_not_established, names the field that diverged by field name only, and narrows its disclosure scope to the two presenting parties.",
  "type": "object",
  "required": [
    "receipt_id",
    "payload_sha256",
    "sig_b64u",
    "key_id",
    "algorithm",
    "ts",
    "signed_body"
  ],
  "additionalProperties": false,
  "properties": {
    "receipt_id": {
      "type": "string",
      "pattern": "^r_custody[.]handoff_[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",
        "handoff_id",
        "terms",
        "releasing_party",
        "receiving_party",
        "presentation_relation",
        "sequence",
        "verdict",
        "disclosure_scope",
        "chain",
        "salt_commitment",
        "boundary"
      ],
      "additionalProperties": false,
      "properties": {
        "receipt_type": {
          "type": "string",
          "const": "custody.handoff"
        },
        "schema": {
          "type": "string",
          "const": "r1.0.0"
        },
        "handoff_id": {
          "type": "string",
          "pattern": "^ch_[0-9a-f]{16,64}$"
        },
        "terms": {
          "type": "object",
          "description": "The handoff terms as presented. Every value here is also carried by both presentations, so the terms recorded are the terms the two sides signed rather than terms the issuer wrote afterwards.",
          "required": [
            "artifact_commitment",
            "artifact_digest",
            "purpose_class",
            "retention_policy_id",
            "effective_at"
          ],
          "additionalProperties": false,
          "properties": {
            "artifact_commitment": {
              "type": "string",
              "description": "Salted commitment over the artifact identifier. The salt is disclosed to the verifier in the request and never appears in this body.",
              "pattern": "^[0-9a-f]{64}$"
            },
            "artifact_digest": {
              "type": "string",
              "description": "SHA-256 over the artifact bytes, so a later copy can be compared to the bytes that moved.",
              "pattern": "^[0-9a-f]{64}$"
            },
            "purpose_class": {
              "type": "string",
              "enum": [
                "care_documentation",
                "coding_review",
                "billing_submission",
                "quality_measurement",
                "records_request"
              ]
            },
            "retention_policy_id": {
              "type": "string",
              "pattern": "^rp_[0-9a-z][0-9a-z._-]{0,47}$"
            },
            "effective_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$"
            }
          }
        },
        "releasing_party": {
          "type": "object",
          "required": [
            "party_commitment",
            "presentation_receipt"
          ],
          "additionalProperties": false,
          "properties": {
            "party_commitment": {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$"
            },
            "presentation_receipt": {
              "type": "object",
              "required": [
                "receipt_id",
                "receipt_type",
                "envelope_sha256",
                "issuance_instant"
              ],
              "additionalProperties": false,
              "properties": {
                "receipt_id": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 256
                },
                "receipt_type": {
                  "type": "string",
                  "const": "custody.presentation"
                },
                "envelope_sha256": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{64}$"
                },
                "issuance_instant": {
                  "type": "object",
                  "required": [
                    "utc",
                    "drift_seconds"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "utc": {
                      "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$"
                    },
                    "drift_seconds": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 86400
                    }
                  }
                }
              }
            }
          }
        },
        "receiving_party": {
          "type": "object",
          "required": [
            "party_commitment",
            "presentation_receipt"
          ],
          "additionalProperties": false,
          "properties": {
            "party_commitment": {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$"
            },
            "presentation_receipt": {
              "type": "object",
              "required": [
                "receipt_id",
                "receipt_type",
                "envelope_sha256",
                "issuance_instant"
              ],
              "additionalProperties": false,
              "properties": {
                "receipt_id": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 256
                },
                "receipt_type": {
                  "type": "string",
                  "const": "custody.presentation"
                },
                "envelope_sha256": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{64}$"
                },
                "issuance_instant": {
                  "type": "object",
                  "required": [
                    "utc",
                    "drift_seconds"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "utc": {
                      "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$"
                    },
                    "drift_seconds": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 86400
                    }
                  }
                }
              }
            }
          }
        },
        "presentation_relation": {
          "type": "string",
          "description": "Recomputed relation between the two presentations. Never accepted from a caller.",
          "enum": [
            "both_presentations_agree",
            "presentations_diverge",
            "presentation_missing"
          ]
        },
        "divergent_fields": {
          "type": "array",
          "description": "Field names only, never values. Present only when presentation_relation is presentations_diverge, so a refused handoff says what disagreed without publishing what either side asked for.",
          "minItems": 1,
          "maxItems": 8,
          "items": {
            "type": "string",
            "enum": [
              "artifact_commitment",
              "artifact_digest",
              "purpose_class",
              "retention_policy_id",
              "effective_at"
            ]
          }
        },
        "sequence": {
          "type": "object",
          "required": [
            "released_at",
            "accepted_at",
            "sequence_class"
          ],
          "additionalProperties": false,
          "properties": {
            "released_at": {
              "type": "object",
              "required": [
                "utc",
                "drift_seconds"
              ],
              "additionalProperties": false,
              "properties": {
                "utc": {
                  "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$"
                },
                "drift_seconds": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 86400
                }
              }
            },
            "accepted_at": {
              "type": "object",
              "required": [
                "utc",
                "drift_seconds"
              ],
              "additionalProperties": false,
              "properties": {
                "utc": {
                  "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$"
                },
                "drift_seconds": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 86400
                }
              }
            },
            "sequence_class": {
              "type": "string",
              "description": "Recomputed from the two instants and their drift bounds. An overlap that no drift bound can separate is reported as sequence_indeterminate rather than resolved in either direction.",
              "enum": [
                "acceptance_after_release",
                "acceptance_before_release",
                "sequence_indeterminate"
              ]
            }
          }
        },
        "verdict": {
          "type": "string",
          "description": "Recomputed. custody_transferred requires agreeing presentations, acceptance recorded after release, and a chain class that is not chain_broken.",
          "enum": [
            "custody_transferred",
            "custody_not_established"
          ]
        },
        "disclosure_scope": {
          "type": "string",
          "description": "Recomputed. A refused handoff is scoped to the two presenting parties, so a divergence never becomes a public failure surface.",
          "enum": [
            "chain_visible",
            "two_party_only"
          ]
        },
        "chain": {
          "type": "object",
          "required": [
            "chain_class"
          ],
          "additionalProperties": false,
          "properties": {
            "prior_handoff": {
              "type": "object",
              "description": "Link to the preceding custody.handoff receipt, absent for the first movement of the artifact, so a chain across three or more organizations can be walked receipt by receipt.",
              "required": [
                "receipt_id",
                "receipt_type",
                "envelope_sha256",
                "issuance_instant",
                "artifact_commitment"
              ],
              "additionalProperties": false,
              "properties": {
                "receipt_id": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 256
                },
                "receipt_type": {
                  "type": "string",
                  "const": "custody.handoff"
                },
                "envelope_sha256": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{64}$"
                },
                "issuance_instant": {
                  "type": "object",
                  "required": [
                    "utc",
                    "drift_seconds"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "utc": {
                      "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$"
                    },
                    "drift_seconds": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 86400
                    }
                  }
                },
                "artifact_commitment": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{64}$"
                }
              }
            },
            "chain_class": {
              "type": "string",
              "description": "Recomputed. chain_continuous requires the prior receipt to have transferred the same artifact to the party releasing it here, at or before the release recorded here.",
              "enum": [
                "chain_continuous",
                "chain_origin",
                "chain_broken"
              ]
            }
          }
        },
        "salt_commitment": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$"
        },
        "boundary": {
          "type": "string",
          "minLength": 1,
          "const": "This receipt attests only that two organizations each signed an independent presentation over the same artifact commitment, artifact digest, purpose class, retention policy identifier, and effective instant, that the two presentations were signed under different keys, that the recorded release and acceptance instants stand in the recorded order under their declared drift bounds, and that the recorded presentation relation, divergent field list, sequence class, chain class, verdict, and disclosure scope are exactly the values recomputed from those presentations. A verdict of custody_transferred means only that both sides presented the same terms and that acceptance was recorded after release. It does not attest that the artifact content is accurate, that either organization was authorized to hold the artifact, that the stated purpose is the purpose the artifact was actually used for, that no other copy moved by another route, or that the receiving side did anything with the artifact after accepting it. A verdict of custody_not_established records that the two sides did not present the same terms, and it does not establish that either side acted wrongly. This receipt decides no contractual, statutory, regulatory, evidentiary, or legal consequence, allocates no fault, responsibility, liability, or remedy, and authorizes no action."
        }
      }
    }
  }
}
