{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://getzero.dev/contracts/zero.divergence_refusal_proof.v1.schema.json",
  "title": "DivergenceRefusalProof v1",
  "description": "Cryptographically-fingerprinted bundle attached to an hl-sign refusal that was driven by HL data-plane divergence. Lets an operator (or third-party auditor) verify the claim 'we refused this trade because our primary HL source and the fallback disagreed, by this amount, on these coins, at this timestamp' — without trusting the engine's own log line. Embedded as an extension under decision_replay_frames.frame.tools[] or stored as a standalone bundle keyed by replay_id. The proof_hash field self-seals the bundle so any silent modification is detectable. Closes audit item F21/F22 (https://getzero.dev/contracts/audit-items#F22) when the engine wires the full payload-hash chain.",
  "type": "object",
  "required": [
    "schema",
    "replay_id",
    "decided_at",
    "sources",
    "verdict",
    "action_refused",
    "proof_hash"
  ],
  "$defs": {
    "source_kind": {
      "type": "string",
      "enum": [
        "public_hl_api",
        "foundation_nonvalidating_node",
        "self_hosted_node_api",
        "managed_private_endpoint"
      ],
      "description": "Class of HL endpoint. Matches data_plane.DataPlaneSource.kind and the existing zero.hl_data_plane.v1 contract — these are the exact values the engine emits."
    }
  },
  "properties": {
    "schema": {
      "type": "string",
      "const": "zero.divergence_refusal_proof.v1"
    },
    "replay_id": {
      "type": "string",
      "minLength": 8,
      "description": "Deterministic ID of the parent decision_replay_frames row; matches frame.replay_id."
    },
    "decided_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO-8601 UTC timestamp of the refusal decision. Must equal the parent frame's outcome.occurred_at."
    },
    "sources": {
      "type": "array",
      "minItems": 2,
      "maxItems": 2,
      "description": "The two HL data sources whose disagreement triggered the refusal. Order is significant and ENFORCED via prefixItems: [0].role=='primary', [1].role=='fallback'. Without prefixItems, a producer could swap the slots or duplicate the role and still validate against the same items schema.",
      "prefixItems": [
        {
          "type": "object",
          "required": ["source_id", "kind", "role", "url", "queried_at"],
          "additionalProperties": false,
          "properties": {
            "source_id": {
              "type": "string",
              "description": "Stable identifier for this source (e.g. zero-hl-primary)."
            },
            "kind": {
              "$ref": "#/$defs/source_kind"
            },
            "role": {
              "type": "string",
              "const": "primary",
              "description": "Slot [0] is always the primary source."
            },
            "url": {
              "type": "string",
              "format": "uri",
              "description": "Resolved info-endpoint URL. Useful for replay but not the trust anchor — payload hashes below are."
            },
            "queried_at": {
              "type": "string",
              "format": "date-time"
            },
            "state_age_ms": {
              "type": ["integer", "null"],
              "minimum": 0
            },
            "payload_hash": {
              "type": ["string", "null"],
              "pattern": "^sha256:[0-9a-f]{64}$",
              "description": "Trust anchor: sha256-prefixed hex of canonical JSON (matches engine's data_plane_producer._payload_hash format). Null when no payload was captured."
            }
          }
        },
        {
          "type": "object",
          "required": ["source_id", "kind", "role", "url", "queried_at"],
          "additionalProperties": false,
          "properties": {
            "source_id": {"type": "string"},
            "kind": {"$ref": "#/$defs/source_kind"},
            "role": {
              "type": "string",
              "const": "fallback",
              "description": "Slot [1] is always the fallback source."
            },
            "url": {"type": "string", "format": "uri"},
            "queried_at": {"type": "string", "format": "date-time"},
            "state_age_ms": {"type": ["integer", "null"], "minimum": 0},
            "payload_hash": {
              "type": ["string", "null"],
              "pattern": "^sha256:[0-9a-f]{64}$"
            }
          }
        }
      ],
      "items": false
    },
    "compared_shapes": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": ["positions", "open_orders", "account_value", "account_state", "fill_count"]
      },
      "description": "Which of the divergence comparator's shapes were checked. Each shape's verdict appears in `verdict.per_shape` below.",
      "default": []
    },
    "diff_summary": {
      "type": "array",
      "description": "Per-field disagreements. Each entry names the field that differed and the delta. Bounded to 50 entries to keep the proof compact.",
      "maxItems": 50,
      "items": {
        "type": "object",
        "required": ["shape", "field_path"],
        "additionalProperties": false,
        "properties": {
          "shape": {
            "type": "string",
            "enum": ["positions", "open_orders", "account_value", "account_state", "fill_count"]
          },
          "field_path": {
            "type": "string",
            "description": "Dotted path within the shape (e.g. 'ETH.size_coins', 'orders[oid=12345].limit_px')."
          },
          "primary_value": {},
          "fallback_value": {},
          "delta_abs": {
            "type": ["number", "null"],
            "description": "|primary - fallback| when both are numeric; null for shape mismatches or non-numeric fields."
          },
          "delta_pct": {
            "type": ["number", "null"],
            "description": "|primary - fallback| / max(|primary|, |fallback|, 1e-9). Null for non-numeric fields."
          }
        }
      },
      "default": []
    },
    "verdict": {
      "type": "object",
      "required": ["overall_status"],
      "additionalProperties": false,
      "properties": {
        "overall_status": {
          "type": "string",
          "enum": ["match", "minor_drift", "major_drift", "unchecked"],
          "description": "Mirrors data_plane_divergence.STATUS_*."
        },
        "per_shape": {
          "type": "object",
          "additionalProperties": {
            "type": "string",
            "enum": ["match", "minor_drift", "major_drift", "unchecked"]
          },
          "description": "{shape: status} for each shape in compared_shapes."
        },
        "per_coin_major_drift_coins": {
          "type": "array",
          "items": {"type": "string"},
          "description": "Coins flagged for per-coin major drift across any comparator variant.",
          "default": []
        }
      }
    },
    "action_refused": {
      "type": "object",
      "required": ["scope"],
      "additionalProperties": false,
      "properties": {
        "scope": {
          "type": "string",
          "enum": ["portfolio", "coin"],
          "description": "portfolio: whole hl-sign request refused. coin: just one coin in the request was refused."
        },
        "coins": {
          "type": "array",
          "items": {"type": "string"},
          "description": "When scope=coin, the specific coin(s) refused. Empty when scope=portfolio.",
          "default": []
        },
        "hl_request_kind": {
          "type": "string",
          "enum": ["order", "cancel", "scheduleCancel"],
          "description": "The kind of HL action that was refused."
        }
      }
    },
    "proof_hash": {
      "type": "string",
      "pattern": "^sha256:[0-9a-f]{64}$",
      "description": "Trust anchor: 'sha256:' + hex of canonical JSON of this bundle EXCLUDING this field itself. Format matches engine's data_plane_producer._payload_hash for consistency. Computed via: 'sha256:' + sha256_hex(canonicalize(bundle without proof_hash)). Anyone who reconstructs the same inputs gets the same hash; any silent modification breaks verification."
    }
  },
  "additionalProperties": false,
  "examples": [
    {
      "schema": "zero.divergence_refusal_proof.v1",
      "replay_id": "refusal-hl-abc123def456",
      "decided_at": "2026-05-17T12:34:56Z",
      "sources": [
        {
          "source_id": "zero-hl-primary",
          "kind": "public_hl_api",
          "role": "primary",
          "url": "https://api.hyperliquid.xyz/info",
          "queried_at": "2026-05-17T12:34:55Z",
          "state_age_ms": 1200,
          "payload_hash": "sha256:a3f1b2c4d5e6f7081929384a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e8"
        },
        {
          "source_id": "zero-hl-fallback",
          "kind": "foundation_nonvalidating_node",
          "role": "fallback",
          "url": "https://hl-foundation.example.com/info",
          "queried_at": "2026-05-17T12:34:55Z",
          "state_age_ms": 1100,
          "payload_hash": "sha256:b4c5d6e7f80a1b2c3d4e5f6071829304a5b6c7d8e9f0a1b2c3d4e5f607182930"
        }
      ],
      "compared_shapes": ["positions"],
      "diff_summary": [
        {
          "shape": "positions",
          "field_path": "ETH.size_coins",
          "primary_value": 0.5,
          "fallback_value": 0.42,
          "delta_abs": 0.08,
          "delta_pct": 0.16
        }
      ],
      "verdict": {
        "overall_status": "major_drift",
        "per_shape": {"positions": "major_drift"},
        "per_coin_major_drift_coins": ["ETH"]
      },
      "action_refused": {
        "scope": "coin",
        "coins": ["ETH"],
        "hl_request_kind": "order"
      },
      "proof_hash": "sha256:c5d6e7f80a1b2c3d4e5f6071829304a5b6c7d8e9f0a1b2c3d4e5f60718293041"
    }
  ]
}
