{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://emissar.ai/specs/schemas/resolve-v0.1.schema.json",
  "title": "Emissar Resolve v0.1 record",
  "description": "Emissar Resolve v0.1, a draft for comment. The root schema validates one Resolve record (https://emissar.ai/specs/resolve/v0.1 section 6). $defs/LookupResponse validates a positive lookup body and $defs/Problem a negative or error body (section 8).",
  "$ref": "#/$defs/Record",
  "$defs": {
    "Timestamp": {
      "description": "RFC 3339 date-time in UTC with a Z suffix.",
      "type": "string",
      "format": "date-time",
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?Z$"
    },
    "TelIdentifier": {
      "type": "object",
      "properties": {
        "type": { "const": "tel" },
        "value": {
          "description": "Canonical E.164 form: '+', then the country code and national significant number, digits only, at most 15 digits (section 5.1).",
          "type": "string",
          "pattern": "^\\+[1-9][0-9]{3,14}$"
        }
      },
      "required": ["type", "value"],
      "additionalProperties": false
    },
    "DomainIdentifier": {
      "type": "object",
      "properties": {
        "type": { "const": "domain" },
        "value": {
          "description": "Canonical form: lowercase A-labels, no trailing dot, at least two labels, each 1-63 LDH characters, at most 253 characters (section 5.2).",
          "type": "string",
          "maxLength": 253,
          "pattern": "^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"
        },
        "display": {
          "description": "Unicode (U-label) form for display when the domain contains A-labels. Never used for matching.",
          "type": "string",
          "maxLength": 253
        }
      },
      "required": ["type", "value"],
      "additionalProperties": false
    },
    "BrandIdentifier": {
      "type": "object",
      "properties": {
        "type": { "const": "brand" },
        "value": {
          "description": "Brand key (section 5.3): NFKC case-folded, legal-form suffixes removed, only letters, marks and digits kept. No whitespace or ASCII punctuation.",
          "type": "string",
          "minLength": 2,
          "maxLength": 64,
          "pattern": "^[^\\s!-/:-@\\[-`{-~]+$"
        },
        "country": {
          "description": "ISO 3166-1 alpha-2 code, uppercase. Brand identifiers are always scoped to one country.",
          "type": "string",
          "pattern": "^[A-Z]{2}$"
        },
        "display": {
          "description": "The brand as the holder writes it, for display only.",
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        }
      },
      "required": ["type", "value", "country"],
      "additionalProperties": false
    },
    "Identifier": {
      "oneOf": [
        { "$ref": "#/$defs/TelIdentifier" },
        { "$ref": "#/$defs/DomainIdentifier" },
        { "$ref": "#/$defs/BrandIdentifier" }
      ]
    },
    "Level": {
      "description": "Verification level, weakest first (section 7.1).",
      "enum": ["observed", "verified-control", "verified-organization"]
    },
    "Method": {
      "description": "How control of the identifier was shown (section 7).",
      "enum": [
        "crawl-well-known",
        "dns-txt",
        "card-token",
        "sms-otp",
        "voice-otp",
        "stir-delegate-cert",
        "trademark-registration",
        "mark-certificate"
      ]
    },
    "Evidence": {
      "description": "Public summary of one check. Never contains a secret, a one-time code, a person's name or contact details.",
      "type": "object",
      "properties": {
        "kind": {
          "enum": [
            "crawl",
            "dns-txt",
            "card-token",
            "card-listing",
            "sms-otp",
            "voice-otp",
            "stir-delegate-cert",
            "reassigned-numbers-check",
            "business-registry",
            "lei",
            "trademark-registration",
            "mark-certificate"
          ]
        },
        "authority": {
          "description": "Who holds the underlying record, e.g. 'USPTO', 'CIPO', 'Corporations Canada', 'GLEIF'.",
          "type": "string",
          "maxLength": 100
        },
        "ref": {
          "description": "Public reference: crawl id, DNS owner name, registration or entity number.",
          "type": "string",
          "maxLength": 200
        },
        "checkedAt": { "$ref": "#/$defs/Timestamp" }
      },
      "required": ["kind", "checkedAt"],
      "additionalProperties": false
    },
    "Verification": {
      "type": "object",
      "properties": {
        "level": { "$ref": "#/$defs/Level" },
        "method": { "$ref": "#/$defs/Method" },
        "lastVerifiedAt": { "$ref": "#/$defs/Timestamp" },
        "expiresAt": { "$ref": "#/$defs/Timestamp" },
        "evidence": {
          "type": "array",
          "minItems": 1,
          "maxItems": 20,
          "items": { "$ref": "#/$defs/Evidence" }
        }
      },
      "required": ["level", "method", "lastVerifiedAt", "expiresAt", "evidence"],
      "additionalProperties": false
    },
    "Organization": {
      "description": "Present only at level verified-organization. Legal name and registration of the holder, from an authoritative register.",
      "type": "object",
      "properties": {
        "name": { "type": "string", "minLength": 1, "maxLength": 200 },
        "country": { "type": "string", "pattern": "^[A-Z]{2}$" },
        "registry": {
          "type": "object",
          "properties": {
            "authority": { "type": "string", "maxLength": 100 },
            "id": { "type": "string", "maxLength": 100 }
          },
          "required": ["authority", "id"],
          "additionalProperties": false
        }
      },
      "required": ["name", "country"],
      "additionalProperties": false
    },
    "Record": {
      "type": "object",
      "properties": {
        "id": {
          "description": "Stable record id.",
          "type": "string",
          "pattern": "^rsv_[a-z2-7]{20}$"
        },
        "identifier": { "$ref": "#/$defs/Identifier" },
        "cardUrl": {
          "description": "HTTPS URL of the Agent Card the identifier resolves to.",
          "type": "string",
          "format": "uri",
          "maxLength": 500,
          "pattern": "^https://[^\\s/@]+/[^\\s]*$"
        },
        "status": {
          "description": "active, or disputed (returned with a warning; section 10). Suspended and expired records are never returned.",
          "enum": ["active", "disputed"]
        },
        "source": {
          "description": "crawl: seeded from a public Agent Card (section 9). claim: created by a claimant who proved control (section 7).",
          "enum": ["crawl", "claim"]
        },
        "verification": { "$ref": "#/$defs/Verification" },
        "organization": { "$ref": "#/$defs/Organization" },
        "updatedAt": { "$ref": "#/$defs/Timestamp" }
      },
      "required": ["id", "identifier", "cardUrl", "status", "source", "verification", "updatedAt"],
      "additionalProperties": false,
      "allOf": [
        {
          "description": "Seeded records are observed, domain-only, method crawl-well-known, and carry no organization.",
          "if": { "properties": { "source": { "const": "crawl" } }, "required": ["source"] },
          "then": {
            "properties": {
              "identifier": { "$ref": "#/$defs/DomainIdentifier" },
              "verification": {
                "properties": {
                  "level": { "const": "observed" },
                  "method": { "const": "crawl-well-known" }
                }
              }
            },
            "not": { "required": ["organization"] }
          }
        },
        {
          "description": "Only seeded records may be observed.",
          "if": {
            "properties": { "verification": { "properties": { "level": { "const": "observed" } } } }
          },
          "then": { "properties": { "source": { "const": "crawl" } } }
        },
        {
          "description": "Telephone records exist only at verified-organization in v0.1 (section 12.2), with a telephone method.",
          "if": { "properties": { "identifier": { "properties": { "type": { "const": "tel" } } } } },
          "then": {
            "properties": {
              "verification": {
                "properties": {
                  "level": { "const": "verified-organization" },
                  "method": { "enum": ["sms-otp", "voice-otp", "stir-delegate-cert"] }
                }
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "identifier": { "properties": { "type": { "const": "domain" } } },
              "source": { "const": "claim" }
            }
          },
          "then": {
            "properties": {
              "verification": { "properties": { "method": { "enum": ["dns-txt", "card-token"] } } }
            }
          }
        },
        {
          "description": "Brand records exist only at verified-organization, backed by a trademark registration or a mark certificate.",
          "if": { "properties": { "identifier": { "properties": { "type": { "const": "brand" } } } } },
          "then": {
            "properties": {
              "verification": {
                "properties": {
                  "level": { "const": "verified-organization" },
                  "method": { "enum": ["trademark-registration", "mark-certificate"] }
                }
              }
            }
          }
        },
        {
          "description": "verified-organization requires the organization object.",
          "if": {
            "properties": {
              "verification": { "properties": { "level": { "const": "verified-organization" } } }
            }
          },
          "then": { "required": ["organization"] },
          "else": { "not": { "required": ["organization"] } }
        }
      ]
    },
    "Query": {
      "description": "The canonical identifier that was looked up.",
      "type": "object",
      "properties": {
        "type": { "enum": ["tel", "domain", "brand"] },
        "value": { "type": "string", "minLength": 1, "maxLength": 253 },
        "country": { "type": "string", "pattern": "^[A-Z]{2}$" }
      },
      "required": ["type", "value"],
      "additionalProperties": false
    },
    "LookupResponse": {
      "description": "Body of a 200 response.",
      "type": "object",
      "properties": {
        "resolve": { "const": "0.1" },
        "query": { "$ref": "#/$defs/Query" },
        "answer": { "const": "found" },
        "records": {
          "type": "array",
          "minItems": 1,
          "maxItems": 20,
          "items": { "$ref": "#/$defs/Record" }
        }
      },
      "required": ["resolve", "query", "answer", "records"],
      "additionalProperties": false
    },
    "Problem": {
      "description": "RFC 9457 problem details (application/problem+json) for 400, 404, 429 and 5xx. A 404 with answer 'not-found' is the negative answer.",
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "https://emissar.ai/specs/resolve/v0.1#not-found",
            "https://emissar.ai/specs/resolve/v0.1#invalid-identifier",
            "https://emissar.ai/specs/resolve/v0.1#rate-limited",
            "https://emissar.ai/specs/resolve/v0.1#unavailable"
          ]
        },
        "title": { "type": "string", "maxLength": 200 },
        "status": { "enum": [400, 404, 429, 503] },
        "detail": { "type": "string", "maxLength": 500 },
        "resolve": { "const": "0.1" },
        "answer": { "const": "not-found" },
        "query": { "$ref": "#/$defs/Query" }
      },
      "required": ["type", "title", "status", "resolve"],
      "additionalProperties": false,
      "allOf": [
        {
          "if": { "properties": { "status": { "const": 404 } }, "required": ["status"] },
          "then": { "required": ["answer", "query"] },
          "else": { "not": { "required": ["answer"] } }
        }
      ]
    }
  }
}
