{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://openspatials.com/developers/spec/0.5/openspatials-world-state.schema.json",
  "title": "OpenSpatials world state document, version 0.5",
  "description": "0.5 addition (kit support next). Spec: src/spec/core/WORLD-STATE.md section 2. A world's live state, named from its profile's state field (usually openspatials-state.json beside the profile): its identity, name, revision (the count of its changes) and the time of the last change, the address of its event stream (a WebSocket; openspatials-state-frame.schema.json), the number of visitors (observers are never counted), and its named values, each with the revision and time of the change that set it, so one read gives a value's age. A data point is this document's address with a JSON Pointer fragment, such as #/values/temperature/value. Unknown fields are allowed and ignored. Checked by code, not by this schema: the 64 KiB limit on the bytes before parsing, and at most 256 values. Reason codes: unknown-version > unsafe-file > too-large > invalid-file.",
  "type": "object",
  "allOf": [
    {
      "$ref": "#/$defs/noCode"
    }
  ],
  "required": [
    "openspatials",
    "world",
    "name",
    "revision",
    "time",
    "events",
    "values"
  ],
  "properties": {
    "openspatials": {
      "description": "The format version: \"0.5\".",
      "x-openspatials-reason": "unknown-version",
      "enum": [
        "0.5"
      ]
    },
    "world": {
      "description": "The world's identity: its profile's id, else the address of its profile (WORLDS-WITHOUT-AN-ADDRESS.md section 1).",
      "$ref": "#/$defs/worldIdentity"
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "pattern": "\\S",
      "description": "The world's name, as in its profile. Plain text."
    },
    "revision": {
      "description": "The count of the world's changes: one higher for each change, never reused, kept across restarts.",
      "type": "integer",
      "minimum": 0
    },
    "time": {
      "description": "The world's UTC time of the change that made revision (RFC 3339), or null before the first change.",
      "anyOf": [
        {
          "type": "null"
        },
        {
          "type": "string",
          "format": "date-time"
        }
      ]
    },
    "events": {
      "description": "The event stream, relative to this document or absolute; opened as a WebSocket at the same address (https as wss, http as ws).",
      "$ref": "#/$defs/docUrl"
    },
    "visitors": {
      "description": "How many visitors are in the world now. Observers are never counted.",
      "type": "integer",
      "minimum": 0
    },
    "values": {
      "description": "The world's named values. Names: 1 to 64 letters, digits, '.', '_' or '-', starting with a letter or a digit.",
      "type": "object",
      "propertyNames": {
        "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
      },
      "additionalProperties": {
        "$ref": "#/$defs/value"
      }
    }
  },
  "$defs": {
    "value": {
      "description": "One named value.",
      "type": "object",
      "required": [
        "value",
        "revision",
        "time"
      ],
      "properties": {
        "value": {
          "description": "Any JSON value."
        },
        "revision": {
          "description": "The revision of the change that set it.",
          "type": "integer",
          "minimum": 0
        },
        "time": {
          "description": "The time of that change (RFC 3339). The value's age is the time now less this time.",
          "type": "string",
          "format": "date-time"
        },
        "label": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "pattern": "\\S",
          "description": "The label people see. Plain text."
        },
        "unit": {
          "description": "The unit; a UCUM code (Cel, m, 1) where one exists.",
          "type": "string",
          "minLength": 1,
          "maxLength": 32,
          "pattern": "\\S"
        }
      }
    },
    "worldIdentity": {
      "description": "An absolute https address with no query and no fragment, or http on a loopback host for local testing (WORLDS-WITHOUT-AN-ADDRESS.md section 1). A relative address, a query or a fragment is invalid-file; another scheme, a private network host or user info is unsafe-file.",
      "type": "string",
      "maxLength": 2048,
      "pattern": "^[A-Za-z][A-Za-z0-9+.-]*:[^\\s?#]+$",
      "if": {
        "type": "string",
        "pattern": "^[A-Za-z][A-Za-z0-9+.-]*:"
      },
      "then": {
        "x-openspatials-reason": "unsafe-file",
        "anyOf": [
          {
            "$ref": "#/$defs/httpsUrl"
          },
          {
            "$ref": "#/$defs/loopbackHttpUrl"
          }
        ]
      }
    },
    "docUrl": {
      "description": "An address relative to this profile, or an absolute https address (http on a loopback host for local testing). White space makes it invalid-file. Any other scheme, a private network host, user info or a protocol-relative address is unsafe-file.",
      "type": "string",
      "minLength": 1,
      "maxLength": 2048,
      "pattern": "^\\S+$",
      "if": {
        "type": "string",
        "pattern": "^\\S+$"
      },
      "then": {
        "x-openspatials-reason": "unsafe-file",
        "anyOf": [
          {
            "$ref": "#/$defs/httpsUrl"
          },
          {
            "$ref": "#/$defs/loopbackHttpUrl"
          },
          {
            "type": "string",
            "pattern": "^(?![A-Za-z][A-Za-z0-9+.-]*:)(?![\\\\/][\\\\/])\\S+$"
          }
        ]
      }
    },
    "httpsUrl": {
      "type": "string",
      "pattern": "^https://(?!(?:10|0)\\.\\d)(?!192\\.168\\.)(?!169\\.254\\.)(?!172\\.(?:1[6-9]|2\\d|3[01])\\.)(?!\\[[fF][cdeCDE])[^\\s/?#@\\\\]+(?:[/?#]\\S*)?$"
    },
    "loopbackHttpUrl": {
      "type": "string",
      "pattern": "^http://(?:127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|localhost|\\[::1\\])(?::\\d{1,5})?(?:[/?#]\\S*)?$"
    },
    "noCode": {
      "description": "04 section 1, closed list, at any depth: a key named script, scripts, eval or javascript; a key that is 'on' followed by a capital letter; a string value starting with javascript:, vbscript: or data:text/html (any case, after leading white space).",
      "x-openspatials-reason": "unsafe-file",
      "propertyNames": {
        "not": {
          "pattern": "^(?:script|scripts|eval|javascript|on[A-Z].*)$"
        }
      },
      "additionalProperties": {
        "$ref": "#/$defs/noCode"
      },
      "items": {
        "$ref": "#/$defs/noCode"
      },
      "not": {
        "type": "string",
        "pattern": "^\\s*(?:[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]:|[Vv][Bb][Ss][Cc][Rr][Ii][Pp][Tt]:|[Dd][Aa][Tt][Aa]:[Tt][Ee][Xx][Tt]/[Hh][Tt][Mm][Ll])"
      }
    }
  }
}
