{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://getzero.dev/contracts/zero.hl_data_plane_divergence.v1.schema.json",
  "title": "HL Data-Plane Divergence Record v1",
  "description": "Per-cycle divergence record produced by zero.data_plane_producer.compute_divergence. Compares the engine's primary HL source (typically cached responses from market_data_service) against an optional fallback source. One record is written to bus/hl_data_plane_divergence.json each periodic cycle; the full history lives at bus/hl_data_plane_divergence_history.jsonl. Consumers: /v2/data-plane-divergence (current), /v2/data-plane-divergence/history (records[]), runtime_safety.py (folds the overall status into RuntimeSafetyState), and the hl-sign refusal path (refuses signing when status=major_drift). Schema name is intentionally distinct from zero.hl_data_plane.v1 — that one describes the provenance block embedded in a replay frame; this one describes the standalone divergence record produced by the periodic comparator.",
  "type": "object",
  "required": ["schema", "checked_at", "primary", "result"],
  "$defs": {
    "data_plane_source": {
      "type": "object",
      "required": ["source_id", "kind", "network", "info_url", "exchange_url", "ws_url"],
      "additionalProperties": false,
      "properties": {
        "source_id": {
          "type": "string",
          "description": "Stable identifier for this endpoint (e.g. zero-hl-primary)."
        },
        "kind": {
          "type": "string",
          "enum": [
            "public_hl_api",
            "foundation_nonvalidating_node",
            "self_hosted_node_api",
            "managed_private_endpoint"
          ]
        },
        "network": {
          "type": "string",
          "enum": ["mainnet", "testnet"]
        },
        "info_url": {"type": "string", "format": "uri"},
        "exchange_url": {"type": "string", "format": "uri"},
        "ws_url": {"type": "string", "format": "uri"}
      }
    },
    "divergence_status": {
      "type": "string",
      "enum": ["match", "minor_drift", "major_drift", "unchecked"]
    },
    "comparison": {
      "type": "object",
      "required": ["field", "status", "reasons", "diff"],
      "additionalProperties": false,
      "properties": {
        "field": {
          "type": "string",
          "description": "Which shape this comparator covered (positions, open_orders, fills, account_value, account_state, fill_count)."
        },
        "status": {"$ref": "#/$defs/divergence_status"},
        "reasons": {
          "type": "array",
          "items": {"type": "string"},
          "description": "Short human-readable explanations of what differed."
        },
        "diff": {
          "type": "object",
          "description": "Structured detail for downstream consumers (UI, replay frame). Shape depends on the comparator."
        }
      }
    },
    "payload_hash": {
      "type": ["string", "null"],
      "pattern": "^sha256:[0-9a-f]{64}$",
      "description": "sha256-prefixed hex of canonical JSON. Format matches data_plane_producer._payload_hash. Null when no payload was captured for that source/shape."
    }
  },
  "properties": {
    "schema": {
      "type": "string",
      "const": "zero.hl_data_plane_divergence.v1"
    },
    "checked_at": {
      "type": ["string", "null"],
      "format": "date-time",
      "description": "ISO-8601 UTC timestamp when the producer ran this comparison. Null only for the canonical no_record fallback before the first producer write."
    },
    "primary": {
      "$ref": "#/$defs/data_plane_source",
      "description": "The engine's primary HL source. Always present — the producer cannot run without it."
    },
    "fallback": {
      "oneOf": [
        {"$ref": "#/$defs/data_plane_source"},
        {"type": "null"}
      ],
      "description": "The fallback source. Null when ZERO_HL_INFO_URL_FALLBACK is not configured — in that case the record is unchecked."
    },
    "result": {
      "type": "object",
      "required": ["status"],
      "additionalProperties": false,
      "properties": {
        "status": {
          "$ref": "#/$defs/divergence_status",
          "description": "Overall verdict folded from all comparisons via aggregate() (severity: major_drift > unchecked > minor_drift > match)."
        },
        "comparisons": {
          "type": "array",
          "items": {"$ref": "#/$defs/comparison"},
          "description": "Per-comparator results. Empty when status=unchecked (no fallback configured or fetch failed before comparator ran)."
        }
      }
    },
    "reason": {
      "type": ["string", "null"],
      "description": "When status=unchecked, names the cause: no_fallback_configured, no_wallet_address, fallback_fetch_failed:<exc-type>. Null when the comparator ran cleanly."
    },
    "payload_hashes": {
      "type": "object",
      "description": "Trust-anchor hashes computed before the comparator ran (#317). Field is OPTIONAL: absent on unchecked records (no fallback to compare against) and absent on snapshots produced before #317 deployed.",
      "additionalProperties": false,
      "properties": {
        "positions": {
          "type": "object",
          "required": ["primary", "fallback"],
          "additionalProperties": false,
          "properties": {
            "primary": {"$ref": "#/$defs/payload_hash"},
            "fallback": {"$ref": "#/$defs/payload_hash"}
          }
        },
        "open_orders": {
          "type": "object",
          "required": ["primary", "fallback"],
          "additionalProperties": false,
          "properties": {
            "primary": {"$ref": "#/$defs/payload_hash"},
            "fallback": {"$ref": "#/$defs/payload_hash"}
          }
        },
        "fills": {
          "type": "object",
          "required": ["primary", "fallback"],
          "additionalProperties": false,
          "properties": {
            "primary": {"$ref": "#/$defs/payload_hash"},
            "fallback": {"$ref": "#/$defs/payload_hash"}
          }
        }
      }
    }
  },
  "additionalProperties": false,
  "examples": [
    {
      "schema": "zero.hl_data_plane_divergence.v1",
      "checked_at": "2026-05-17T12:34:56Z",
      "primary": {
        "source_id": "zero-hl-primary",
        "kind": "public_hl_api",
        "network": "mainnet",
        "info_url": "https://api.hyperliquid.xyz/info",
        "exchange_url": "https://api.hyperliquid.xyz/exchange",
        "ws_url": "wss://api.hyperliquid.xyz/ws"
      },
      "fallback": null,
      "result": {"status": "unchecked", "comparisons": []},
      "reason": "no_fallback_configured"
    },
    {
      "schema": "zero.hl_data_plane_divergence.v1",
      "checked_at": "2026-05-17T12:34:56Z",
      "primary": {
        "source_id": "zero-hl-primary",
        "kind": "public_hl_api",
        "network": "mainnet",
        "info_url": "https://api.hyperliquid.xyz/info",
        "exchange_url": "https://api.hyperliquid.xyz/exchange",
        "ws_url": "wss://api.hyperliquid.xyz/ws"
      },
      "fallback": {
        "source_id": "zero-hl-fallback",
        "kind": "foundation_nonvalidating_node",
        "network": "mainnet",
        "info_url": "https://hl-foundation.example.com/info",
        "exchange_url": "https://hl-foundation.example.com/exchange",
        "ws_url": "wss://hl-foundation.example.com/ws"
      },
      "result": {
        "status": "major_drift",
        "comparisons": [
          {
            "field": "positions",
            "status": "major_drift",
            "reasons": ["ETH: size drift 16.00% (primary=0.50, fallback=0.42)"],
            "diff": {}
          },
          {
            "field": "open_orders",
            "status": "match",
            "reasons": [],
            "diff": {}
          }
        ]
      },
      "reason": null,
      "payload_hashes": {
        "positions": {
          "primary": "sha256:a3f1b2c4d5e6f7081929384a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e8",
          "fallback": "sha256:b4c5d6e7f80a1b2c3d4e5f6071829304a5b6c7d8e9f0a1b2c3d4e5f607182930"
        },
        "open_orders": {
          "primary": "sha256:c5d6e7f80a1b2c3d4e5f6071829304a5b6c7d8e9f0a1b2c3d4e5f60718293041",
          "fallback": "sha256:c5d6e7f80a1b2c3d4e5f6071829304a5b6c7d8e9f0a1b2c3d4e5f60718293041"
        },
        "fills": {
          "primary": null,
          "fallback": null
        }
      }
    }
  ]
}
