{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/rhys-looma/usher/spec/map.schema.json",
  "title": "Usher map",
  "description": "One state of one web form: what its fields mean, how to find them, and which actions matter. Structure only, never values.",
  "type": "object",
  "required": ["usher", "origin", "path", "fingerprint", "form", "fields", "actions", "provenance"],
  "additionalProperties": false,
  "properties": {
    "usher": { "const": "0.1" },
    "origin": {
      "type": "string",
      "pattern": "^https?://[^/]+$",
      "description": "Scheme, host and optional port."
    },
    "path": {
      "type": "string",
      "pattern": "^/",
      "description": "Path pattern; ':param' marks a variable segment."
    },
    "fingerprint": { "$ref": "#/$defs/fingerprint" },
    "form": {
      "type": "object",
      "required": ["purpose"],
      "additionalProperties": false,
      "properties": {
        "purpose": { "type": "string", "minLength": 1, "maxLength": 200 },
        "intent": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$",
          "description": "Dotted intent, e.g. quote.car_insurance, booking.table, contact.message."
        },
        "step": { "type": "integer", "minimum": 1 }
      }
    },
    "groups": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["id", "label"],
        "additionalProperties": false,
        "properties": {
          "id": { "$ref": "#/$defs/elementId" },
          "label": { "type": "string" }
        }
      }
    },
    "fields": { "type": "array", "items": { "$ref": "#/$defs/field" } },
    "actions": { "type": "array", "items": { "$ref": "#/$defs/action" } },
    "provenance": {
      "type": "object",
      "required": ["source", "created_at", "verified"],
      "additionalProperties": false,
      "properties": {
        "source": { "enum": ["compiled", "site", "agent"] },
        "compiler": { "type": "string" },
        "created_at": { "type": "string", "format": "date-time" },
        "verified": { "type": "boolean" }
      }
    }
  },
  "$defs": {
    "elementId": { "type": "string", "pattern": "^[eg][0-9]+$" },
    "key": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$",
      "description": "Semantic key from the vocabulary, or custom.<slug>."
    },
    "risk": { "enum": ["safe", "consequential", "needs_user"] },
    "fingerprint": {
      "type": "object",
      "required": ["algo", "digest", "signature"],
      "additionalProperties": false,
      "properties": {
        "algo": { "const": "usher-fp-1" },
        "digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
        "signature": {
          "type": "array",
          "items": { "type": "string", "pattern": "^[0-9a-f]{8,16}$" }
        }
      }
    },
    "locator": {
      "type": "object",
      "required": ["by", "value"],
      "additionalProperties": false,
      "properties": {
        "by": { "enum": ["id", "name", "label", "role", "placeholder", "css"] },
        "value": { "type": "string", "minLength": 1 },
        "name": { "type": "string" }
      },
      "if": { "properties": { "by": { "const": "role" } } },
      "then": { "required": ["name"] }
    },
    "condition": {
      "type": "object",
      "required": ["field", "op"],
      "additionalProperties": false,
      "properties": {
        "field": { "$ref": "#/$defs/key" },
        "op": { "enum": ["equals", "not_equals", "in", "checked", "filled"] },
        "value": {
          "oneOf": [
            { "type": "string" },
            { "type": "array", "items": { "type": "string" } }
          ]
        }
      }
    },
    "field": {
      "type": "object",
      "required": ["id", "key", "label", "control", "required", "risk", "locators"],
      "additionalProperties": false,
      "properties": {
        "id": { "$ref": "#/$defs/elementId" },
        "key": { "$ref": "#/$defs/key" },
        "label": { "type": "string" },
        "control": {
          "enum": ["text", "email", "tel", "number", "date", "select", "radio", "checkbox", "textarea", "file", "password"]
        },
        "required": { "type": "boolean" },
        "risk": { "$ref": "#/$defs/risk" },
        "sensitive": { "type": "boolean" },
        "constraints": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "pattern": { "type": "string" },
            "min": { "type": ["number", "string"] },
            "max": { "type": ["number", "string"] },
            "min_length": { "type": "integer", "minimum": 0 },
            "max_length": { "type": "integer", "minimum": 0 },
            "format": { "type": "string" }
          }
        },
        "options": {
          "type": "object",
          "required": ["items", "total", "truncated"],
          "additionalProperties": false,
          "properties": {
            "items": {
              "type": "array",
              "items": {
                "type": "object",
                "required": ["value", "label"],
                "additionalProperties": false,
                "properties": {
                  "value": { "type": "string" },
                  "label": { "type": "string" }
                }
              }
            },
            "total": { "type": "integer", "minimum": 0 },
            "truncated": { "type": "boolean" }
          }
        },
        "group": { "$ref": "#/$defs/elementId" },
        "shown_if": { "$ref": "#/$defs/condition" },
        "locators": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/locator" } }
      },
      "allOf": [
        {
          "if": { "properties": { "control": { "enum": ["select", "radio"] } } },
          "then": { "required": ["options"] }
        },
        {
          "if": { "properties": { "sensitive": { "const": true } }, "required": ["sensitive"] },
          "then": { "properties": { "risk": { "const": "needs_user" } } }
        }
      ]
    },
    "action": {
      "type": "object",
      "required": ["id", "key", "label", "kind", "risk", "locators"],
      "additionalProperties": false,
      "properties": {
        "id": { "$ref": "#/$defs/elementId" },
        "key": { "$ref": "#/$defs/key" },
        "label": { "type": "string" },
        "kind": { "enum": ["submit", "button", "link"] },
        "risk": { "$ref": "#/$defs/risk" },
        "locators": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/locator" } },
        "leads_to": { "type": "string", "pattern": "^/" }
      }
    }
  }
}
