{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://tradingai.local/schemas/forecast-provider-v1.schema.json",
  "title": "TradingAI Host-Managed Forecast Provider API v1",
  "description": "Wire shapes for a host-owned forecast provider registry. This schema checks JSON shape only; the host enforces provenance, snapshot identity, bar ordering, output lengths, and quantile ordering.",
  "oneOf": [
    { "$ref": "#/$defs/providerDescriptor" },
    { "$ref": "#/$defs/forecastRequest" },
    { "$ref": "#/$defs/forecastResponse" }
  ],
  "$defs": {
    "apiVersion": {
      "type": "integer",
      "const": 1
    },
    "nonEmptyText": {
      "type": "string",
      "minLength": 1,
      "maxLength": 512,
      "pattern": "^.*\\S.*$"
    },
    "nonNegativeInteger": {
      "type": "integer",
      "minimum": 0
    },
    "epochMillis": {
      "$ref": "#/$defs/nonNegativeInteger",
      "description": "Unix epoch time in milliseconds."
    },
    "finiteNumber": {
      "type": "number",
      "minimum": -1.7976931348623157e308,
      "maximum": 1.7976931348623157e308
    },
    "quantileLevels": {
      "type": "array",
      "const": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
    },
    "closedBar": {
      "type": "object",
      "description": "One confirmed, closed bar from the host-captured snapshot. The host enforces strictly increasing timestamp_ms values across history.",
      "required": ["timestamp_ms", "close"],
      "properties": {
        "timestamp_ms": { "$ref": "#/$defs/epochMillis" },
        "close": { "$ref": "#/$defs/finiteNumber" }
      },
      "additionalProperties": false
    },
    "providerDescriptor": {
      "type": "object",
      "description": "Host-resolved descriptor for a ready provider registration. Identity, model revision, and artifact digest are enriched from the verified installed package and runtime; they are not accepted as provider-authored forecast output.",
      "required": [
        "api_version",
        "extension_id",
        "provider_id",
        "display_name",
        "model_id",
        "model_revision",
        "artifact_digest",
        "price_basis",
        "quantile_levels",
        "supported_timeframes",
        "min_history_bars",
        "max_history_bars",
        "max_horizon",
        "research_only"
      ],
      "properties": {
        "api_version": { "$ref": "#/$defs/apiVersion" },
        "extension_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
        },
        "provider_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$",
          "description": "Provider identifier scoped to extension_id."
        },
        "display_name": { "$ref": "#/$defs/nonEmptyText" },
        "model_id": { "$ref": "#/$defs/nonEmptyText" },
        "model_revision": { "$ref": "#/$defs/nonEmptyText" },
        "artifact_digest": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$",
          "description": "Lowercase SHA-256 digest resolved by the host from the installed package/runtime."
        },
        "price_basis": { "const": "close" },
        "quantile_levels": { "$ref": "#/$defs/quantileLevels" },
        "supported_timeframes": {
          "description": "Canonical chart timeframe identifiers accepted by this provider. A singleton '*' advertises every canonical value supported by the host; otherwise list supported values without '*'.",
          "oneOf": [
            {
              "type": "array",
              "const": ["*"]
            },
            {
              "type": "array",
              "minItems": 1,
              "maxItems": 128,
              "uniqueItems": true,
              "items": { "$ref": "#/$defs/nonEmptyText" },
              "not": { "contains": { "const": "*" } }
            }
          ]
        },
        "min_history_bars": {
          "type": "integer",
          "minimum": 64,
          "maximum": 4096
        },
        "max_history_bars": {
          "type": "integer",
          "minimum": 64,
          "maximum": 4096
        },
        "max_horizon": {
          "type": "integer",
          "minimum": 1,
          "maximum": 256
        },
        "research_only": { "const": true }
      },
      "additionalProperties": false
    },
    "forecastRequest": {
      "type": "object",
      "description": "Host-created inference request bound to an explicit workspace, qualified symbol, and immutable closed-bar snapshot. No active-chart lookup is implied.",
      "required": [
        "api_version",
        "request_id",
        "workspace_id",
        "qualified_symbol",
        "timeframe",
        "execution_generation",
        "data_version",
        "as_of_ms",
        "input_fingerprint_sha256",
        "history",
        "horizon"
      ],
      "properties": {
        "api_version": { "$ref": "#/$defs/apiVersion" },
        "request_id": { "$ref": "#/$defs/nonEmptyText" },
        "workspace_id": { "$ref": "#/$defs/nonEmptyText" },
        "qualified_symbol": { "$ref": "#/$defs/nonEmptyText" },
        "timeframe": { "$ref": "#/$defs/nonEmptyText" },
        "execution_generation": { "$ref": "#/$defs/nonEmptyText" },
        "data_version": { "$ref": "#/$defs/nonNegativeInteger" },
        "as_of_ms": { "$ref": "#/$defs/epochMillis" },
        "input_fingerprint_sha256": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$"
        },
        "history": {
          "type": "array",
          "minItems": 64,
          "maxItems": 4096,
          "items": { "$ref": "#/$defs/closedBar" },
          "description": "Closed bars in strictly increasing timestamp_ms order; ordering is checked by the host."
        },
        "horizon": {
          "type": "integer",
          "minimum": 1,
          "maximum": 256
        }
      },
      "additionalProperties": false
    },
    "forecastResponse": {
      "type": "object",
      "description": "Provider result. The host requires every echoed identity field to match its request and validates exact horizon lengths, finite values, and non-crossing quantiles.",
      "required": [
        "api_version",
        "extension_id",
        "provider_id",
        "model_id",
        "model_revision",
        "artifact_digest",
        "price_basis",
        "request_id",
        "workspace_id",
        "qualified_symbol",
        "timeframe",
        "execution_generation",
        "data_version",
        "as_of_ms",
        "input_fingerprint_sha256",
        "generated_at_ms",
        "point_forecast",
        "quantiles"
      ],
      "properties": {
        "api_version": { "$ref": "#/$defs/apiVersion" },
        "extension_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
        },
        "provider_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
        },
        "model_id": { "$ref": "#/$defs/nonEmptyText" },
        "model_revision": { "$ref": "#/$defs/nonEmptyText" },
        "artifact_digest": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$"
        },
        "price_basis": { "const": "close" },
        "request_id": { "$ref": "#/$defs/nonEmptyText" },
        "workspace_id": { "$ref": "#/$defs/nonEmptyText" },
        "qualified_symbol": { "$ref": "#/$defs/nonEmptyText" },
        "timeframe": { "$ref": "#/$defs/nonEmptyText" },
        "execution_generation": { "$ref": "#/$defs/nonEmptyText" },
        "data_version": { "$ref": "#/$defs/nonNegativeInteger" },
        "as_of_ms": { "$ref": "#/$defs/epochMillis" },
        "input_fingerprint_sha256": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$"
        },
        "generated_at_ms": { "$ref": "#/$defs/epochMillis" },
        "point_forecast": {
          "type": "array",
          "minItems": 1,
          "maxItems": 256,
          "items": { "$ref": "#/$defs/finiteNumber" }
        },
        "quantiles": {
          "type": "array",
          "minItems": 1,
          "maxItems": 256,
          "items": {
            "type": "array",
            "minItems": 9,
            "maxItems": 9,
            "items": { "$ref": "#/$defs/finiteNumber" },
            "description": "Values correspond in order to the fixed descriptor levels 0.1 through 0.9. The host checks ascending order."
          }
        }
      },
      "additionalProperties": false
    }
  }
}
