{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://openspatials.com/developers/spec/0.5/openspatials-signed-app-profile.schema.json",
  "title": "OpenSpatials app profile, signed form (core version 0.5): a Universal Manifest v0.4 document",
  "description": "Spec: src/spec/core/APP-PROFILE-AND-DIRECTORY.md section 1.5. Served beside the plain profile, which names it with signedProfile: the world writes openspatials-world.json and its signed form openspatials-world.signed.json. \"openspatials\": \"0.5\" beside \"manifestVersion\": \"0.4\". Its subject is the address of the plain profile it signs; its one openspatials:AppProfile facet holds every field of that profile except signedProfile, by the plain profile's rules (openspatials-game-profile.schema.json for a draft profile, openspatials-world-profile.schema.json for a 0.5 one). It is signed (Signature Profile A) by a key the profile itself lists in signingKeys, keyRef <address of the plain profile>#<key id>, for at most 365 days. The signature is optional here: an unsigned signed form loads unverified. Checked by code, not by this schema: the 64 KiB limit and duplicate JSON keys; exactly one app profile facet, whose @id, ref and entity @id equal subject; subject equal to the address the plain profile was read from; the facet's fields equal to the plain profile's; the key, the signature and freshness. Reason codes: unknown-version > unsafe-file > too-large > invalid-file.",
  "type": "object",
  "allOf": [
    {
      "$ref": "#/$defs/noCode"
    },
    {
      "required": [
        "openspatials"
      ]
    }
  ],
  "required": [
    "@context",
    "@id",
    "@type",
    "manifestVersion",
    "subject",
    "issuedAt",
    "expiresAt",
    "facets"
  ],
  "properties": {
    "@context": {
      "description": "The pinned Universal Manifest v0.4 context, as a string (or an array holding it) (OWNERSHIP-AND-UM.md section 2).",
      "$ref": "https://universalmanifest.net/ns/universal-manifest/v0.4/schema.json#/properties/@context"
    },
    "@id": {
      "description": "A new urn:uuid at each signing.",
      "type": "string",
      "minLength": 1,
      "maxLength": 2048
    },
    "@type": {
      "$ref": "https://universalmanifest.net/ns/universal-manifest/v0.4/schema.json#/properties/@type"
    },
    "manifestVersion": {
      "const": "0.4"
    },
    "openspatials": {
      "description": "The core version of the signed form: \"0.5\". The profile's own version is the facet's version field.",
      "type": "string",
      "allOf": [
        {
          "x-openspatials-reason": "unknown-version",
          "const": "0.5"
        }
      ]
    },
    "subject": {
      "$ref": "#/$defs/absoluteUrl"
    },
    "issuedAt": {
      "type": "string",
      "format": "date-time"
    },
    "expiresAt": {
      "type": "string",
      "format": "date-time"
    },
    "consents": {
      "type": "array",
      "maxItems": 256,
      "items": {
        "$ref": "https://universalmanifest.net/ns/universal-manifest/v0.4/schema.json#/$defs/consent"
      }
    },
    "signature": {
      "$ref": "#/$defs/signature"
    },
    "facets": {
      "type": "array",
      "minItems": 1,
      "contains": {
        "properties": {
          "entity": {
            "type": "object",
            "required": [
              "@type"
            ],
            "properties": {
              "@type": {
                "anyOf": [
                  {
                    "enum": [
                      "openspatials:AppProfile",
                      "openspatials:AppProfile"
                    ]
                  },
                  {
                    "type": "array",
                    "contains": {
                      "enum": [
                        "openspatials:AppProfile",
                        "openspatials:AppProfile"
                      ]
                    }
                  }
                ]
              }
            }
          }
        },
        "required": [
          "entity"
        ]
      },
      "items": {
        "allOf": [
          {
            "$ref": "https://universalmanifest.net/ns/universal-manifest/v0.4/schema.json#/$defs/facet"
          },
          {
            "if": {
              "properties": {
                "entity": {
                  "type": "object",
                  "required": [
                    "@type"
                  ],
                  "properties": {
                    "@type": {
                      "anyOf": [
                        {
                          "enum": [
                            "openspatials:AppProfile",
                            "openspatials:AppProfile"
                          ]
                        },
                        {
                          "type": "array",
                          "contains": {
                            "enum": [
                              "openspatials:AppProfile",
                              "openspatials:AppProfile"
                            ]
                          }
                        }
                      ]
                    }
                  }
                }
              },
              "required": [
                "entity"
              ]
            },
            "then": {
              "$ref": "#/$defs/profileFacet"
            }
          }
        ]
      }
    },
    "pointers": {
      "type": "array",
      "items": {
        "$ref": "https://universalmanifest.net/ns/universal-manifest/v0.4/schema.json#/$defs/pointer"
      }
    }
  },
  "$defs": {
    "absoluteUrl": {
      "description": "An absolute http(s) address with no #fragment. The address rules (https to a public host, http only on loopback) are checked at run time (checkUrl).",
      "type": "string",
      "maxLength": 2048,
      "pattern": "^https?://[^\\s#]+$"
    },
    "signature": {
      "description": "Signature Profile A only (OWNERSHIP-AND-UM.md section 2): Ed25519 over the RFC 8785 canonical JSON of the document without its signature. keyRef: <address of the issuing game's profile (openspatials-world.json, or openspatials-world.json)>#<key id>, or a did:key. A document with no signature is read and arrives or loads unverified; a signature of another profile is invalid-file. The signature itself, the key and freshness are checked by code (src/ownership/index.js), not by this schema.",
      "type": "object",
      "required": [
        "algorithm",
        "canonicalization",
        "keyRef",
        "value"
      ],
      "properties": {
        "algorithm": {
          "const": "Ed25519"
        },
        "canonicalization": {
          "const": "JCS-RFC8785"
        },
        "keyRef": {
          "type": "string",
          "minLength": 1,
          "maxLength": 2200,
          "pattern": "^\\S+$"
        },
        "publicKeySpkiB64": {
          "type": "string",
          "minLength": 1,
          "maxLength": 256,
          "pattern": "^[A-Za-z0-9+/_-]+={0,2}$"
        },
        "created": {
          "type": "string",
          "format": "date-time"
        },
        "value": {
          "type": "string",
          "minLength": 1,
          "maxLength": 256,
          "pattern": "^[A-Za-z0-9_-]+={0,2}$"
        }
      }
    },
    "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])"
      }
    },
    "profileFacet": {
      "description": "The openspatials:AppProfile facet: @id, ref and entity @id are the subject (the address of the plain profile; equality checked by code). The entity is the profile: every field of the plain profile except signedProfile, with @id and @type added, checked as a draft profile (openspatials-game-profile.schema.json) or, when its version is not a draft, as a 0.5 world profile (openspatials-world-profile.schema.json).",
      "type": "object",
      "required": [
        "@id",
        "entity"
      ],
      "properties": {
        "@id": {
          "$ref": "#/$defs/absoluteUrl"
        },
        "ref": {
          "$ref": "#/$defs/absoluteUrl"
        },
        "entity": {
          "allOf": [
            {
              "if": {
                "type": "object",
                "allOf": [
                  {
                    "required": [
                      "openspatials"
                    ],
                    "properties": {
                      "openspatials": {
                        "not": {
                          "enum": [
                            "0.1-draft",
                            "0.2-draft"
                          ]
                        }
                      }
                    }
                  }
                ]
              },
              "then": {
                "$ref": "https://openspatials.com/developers/spec/0.5/openspatials-world-profile.schema.json"
              },
              "else": {
                "$ref": "https://openspatials.com/developers/spec/0.5/openspatials-game-profile.schema.json"
              }
            },
            {
              "type": "object",
              "required": [
                "@id",
                "@type"
              ],
              "properties": {
                "@id": {
                  "$ref": "#/$defs/absoluteUrl"
                }
              },
              "not": {
                "required": [
                  "signedProfile"
                ]
              }
            }
          ]
        }
      }
    }
  }
}
