{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Zanei event envelope (v1)",
  "description": "The OS-independent envelope shared by every event Zanei records. One event per line (NDJSON) in raw outputs (query --format jsonl, record, export) and in the events[] array of the MCP query_events result. This file is the single machine-readable contract for all exposure surfaces; the Rust core's schema types are tested against it. Backward-incompatible changes bump `v`; additive changes do not.",
  "type": "object",
  "additionalProperties": false,
  "required": ["v", "id", "ts", "mono_ns", "source", "type", "app", "window", "element", "data", "redaction"],
  "$defs": {
    "field_kind": {
      "type": ["string", "null"],
      "enum": ["text", "search", "url", "email", "number", "other", null],
      "description": "Kind of the focused input field, derived from the accessibility role/subrole. Null when focus is not on a text-like element. Secure text fields are excluded at the collector stage, so no password/secure kind can occur."
    },
    "modifiers": {
      "type": "array",
      "items": { "enum": ["cmd", "shift", "opt", "ctrl", "fn"] },
      "uniqueItems": true,
      "description": "Modifier keys held. Sorted, no duplicates."
    }
  },
  "properties": {
    "v": {
      "const": 1,
      "description": "Envelope schema version."
    },
    "id": {
      "type": "string",
      "pattern": "^evt_[0-9A-HJKMNP-TV-Z]{26}$",
      "description": "Event ID: `evt_` prefix + ULID (Crockford base32). Unique and time-sortable."
    },
    "ts": {
      "type": "string",
      "format": "date-time",
      "description": "Wall-clock timestamp, RFC3339 with millisecond precision."
    },
    "mono_ns": {
      "type": "integer",
      "minimum": 0,
      "description": "Monotonic clock in nanoseconds. Use for reliable ordering even across wall-clock adjustments. Not comparable across daemon restarts."
    },
    "source": {
      "type": "string",
      "pattern": "^[a-z0-9]+\\.[a-z0-9_]+$",
      "description": "Capture backend. v1 (macOS) values: macos.workspace, macos.ax, macos.eventtap, macos.applescript. Future platforms add values without a version bump."
    },
    "type": {
      "type": "string",
      "pattern": "^[a-z0-9]+\\.[a-z0-9_]+$",
      "description": "Event type. v1 taxonomy: app.activate, app.launch, app.terminate, window.focus, window.title, ui.focus, ui.click, ui.value, input.key, input.scroll, browser.navigate, clipboard.copy, clipboard.paste. Deliberately an open set (not an enum): future platforms and versions add types without a version bump, and readers MUST skip unknown types rather than error."
    },
    "app": {
      "type": "object",
      "additionalProperties": false,
      "required": ["name", "bundle_id", "pid"],
      "description": "The application the event originates from.",
      "properties": {
        "name": {
          "type": "string",
          "description": "Display name of the application."
        },
        "bundle_id": {
          "type": ["string", "null"],
          "description": "macOS bundle identifier (e.g. com.google.Chrome). Null for processes without a bundle."
        },
        "pid": {
          "type": ["integer", "null"],
          "description": "Process ID at capture time."
        }
      }
    },
    "window": {
      "type": ["object", "null"],
      "additionalProperties": false,
      "required": ["title", "id"],
      "description": "Window context, when applicable; null otherwise.",
      "properties": {
        "title": {
          "type": ["string", "null"],
          "description": "Window title, after redaction."
        },
        "id": {
          "type": ["integer", "null"],
          "description": "OS window identifier (CGWindowNumber-derived on macOS); daemon-local, null when unavailable."
        }
      }
    },
    "element": {
      "type": ["object", "null"],
      "additionalProperties": false,
      "required": ["role", "title", "value"],
      "description": "UI element context. Non-null only for ui.* events. Secure text fields (password inputs) never appear here.",
      "properties": {
        "role": {
          "type": ["string", "null"],
          "description": "Accessibility role (e.g. AXButton)."
        },
        "title": {
          "type": ["string", "null"],
          "description": "Element title/label."
        },
        "value": {
          "type": ["string", "null"],
          "description": "Element value, after redaction. Present only when capture.text_content is enabled and the role is an allowlisted known-safe non-text control; null for free-text, secure, unknown, and unavailable roles, and always null when capture.text_content is disabled."
        }
      }
    },
    "data": {
      "type": "object",
      "description": "Type-specific payload, pinned per type by the allOf conditions below. Empty object when the type carries no extra data."
    },
    "redaction": {
      "type": "object",
      "additionalProperties": false,
      "required": ["applied", "rules"],
      "description": "Record of privacy processing applied to this event at capture time. Redactors apply to free-text fields only (window.title, element.title, element.value, data.text, data.url, data.tab_title), replacing matches with [REDACTED:<rule>]. Redaction is the second line of defense; the first is not capturing at all (secure fields, incognito, capture-time filters).",
      "properties": {
        "applied": {
          "type": "boolean",
          "description": "Whether any redaction rule modified this event."
        },
        "rules": {
          "type": "array",
          "items": { "type": "string" },
          "description": "IDs of the rules that fired, e.g. email, credit_card, token. Empty when applied is false."
        }
      }
    }
  },
  "allOf": [
    {
      "if": { "properties": { "type": { "const": "app.activate" } } },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["prev_bundle_id"],
            "properties": {
              "prev_bundle_id": {
                "type": ["string", "null"],
                "description": "Bundle ID of the previously frontmost app. Null on the first event after daemon start."
              }
            }
          }
        }
      }
    },
    {
      "if": { "properties": { "type": { "enum": ["app.launch", "app.terminate", "window.focus"] } } },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": false,
            "properties": {},
            "description": "No extra data; context is fully carried by the envelope."
          }
        }
      }
    },
    {
      "if": { "properties": { "type": { "const": "window.title" } } },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["prev_title"],
            "properties": {
              "prev_title": {
                "type": ["string", "null"],
                "description": "Previous window title (after redaction). The new title is in the envelope's window.title."
              }
            }
          }
        }
      }
    },
    {
      "if": { "properties": { "type": { "const": "ui.focus" } } },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["field_kind"],
            "properties": {
              "field_kind": { "$ref": "#/$defs/field_kind" }
            }
          }
        }
      }
    },
    {
      "if": { "properties": { "type": { "const": "ui.click" } } },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["button", "click_count"],
            "properties": {
              "button": { "enum": ["left", "right", "other"] },
              "click_count": {
                "type": "integer",
                "minimum": 1,
                "description": "1 = single click, 2 = double click, ..."
              }
            }
          }
        }
      }
    },
    {
      "if": { "properties": { "type": { "const": "ui.value" } } },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["field_kind", "value_len", "text"],
            "properties": {
              "field_kind": { "$ref": "#/$defs/field_kind" },
              "value_len": {
                "type": ["integer", "null"],
                "minimum": 0,
                "description": "Character count after the change when exposed by Accessibility; null when unavailable. Free-text content never lives in element.value."
              },
              "text": {
                "type": ["string", "null"],
                "description": "Newly added difference after redaction. Present only when capture.text_content is enabled and a keystroke or paste from the same app and focused-element generation has a value-change notification timestamp no more than 3 seconds after input. Authorization is consumed on emission. Null for deletion, app or generation mismatch, expired or missing authorization, unavailable values, and secure input. The complete field value is never recorded."
              }
            }
          }
        }
      }
    },
    {
      "if": { "properties": { "type": { "const": "input.key" } } },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["kind", "modifiers", "count", "combo", "text", "field_kind"],
            "properties": {
              "kind": {
                "enum": ["text", "shortcut", "navigation", "delete", "other"],
                "description": "text = printable typing (coalesced); shortcut = modifier combo; navigation = arrows/page/home/end/tab; delete = backspace/delete; other = esc/F-keys/media."
              },
              "modifiers": { "$ref": "#/$defs/modifiers" },
              "count": {
                "type": "integer",
                "minimum": 1,
                "description": "Number of keystrokes coalesced into this event. Shortcuts never coalesce (always 1)."
              },
              "combo": {
                "type": ["string", "null"],
                "description": "Shortcut string like cmd+s. Always recorded for kind=shortcut (a shortcut is an action, not content); null for other kinds."
              },
              "text": {
                "type": ["string", "null"],
                "description": "Characters produced directly by keystrokes from a keyboard-layout input source (coalesced, after redaction). Only when capture.text_content is enabled and kind=text. Null for keyboard input modes and when the input-source type is unknown or unavailable."
              },
              "field_kind": { "$ref": "#/$defs/field_kind" }
            }
          }
        }
      }
    },
    {
      "if": { "properties": { "type": { "const": "input.scroll" } } },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["direction", "amount", "count"],
            "properties": {
              "direction": { "enum": ["up", "down", "left", "right"] },
              "amount": {
                "type": "number",
                "description": "Total scroll delta coalesced into this event."
              },
              "count": {
                "type": "integer",
                "minimum": 1,
                "description": "Number of scroll events coalesced."
              }
            }
          }
        }
      }
    },
    {
      "if": { "properties": { "type": { "const": "browser.navigate" } } },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["url", "tab_title", "mode", "transition"],
            "properties": {
              "url": {
                "type": "string",
                "format": "uri",
                "description": "Current URL of the active tab (normal windows only), after redaction."
              },
              "tab_title": {
                "type": ["string", "null"],
                "description": "Title of the active tab, after redaction."
              },
              "mode": {
                "const": "normal",
                "description": "Always \"normal\": incognito windows never produce events, so no other value can occur. Kept in the payload as an auditable trace of that guarantee."
              },
              "transition": {
                "enum": ["navigate", "tab_switch", null],
                "description": "navigate = URL changed within the tab; tab_switch = active tab changed; null when indistinguishable."
              }
            }
          }
        }
      }
    },
    {
      "if": { "properties": { "type": { "const": "clipboard.copy" } } },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["content_kind", "size_bytes", "text"],
            "properties": {
              "content_kind": {
                "enum": ["text", "image", "file", "other"],
                "description": "Derived from pasteboard types. Always recorded."
              },
              "size_bytes": {
                "type": ["integer", "null"],
                "minimum": 0,
                "description": "Payload size. Only when capture.text_content is enabled; null otherwise."
              },
              "text": {
                "type": ["string", "null"],
                "description": "Copied text (after redaction). Only when capture.text_content is enabled and content_kind=text; null otherwise."
              }
            }
          }
        }
      }
    },
    {
      "if": { "properties": { "type": { "const": "clipboard.paste" } } },
      "then": {
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["content_kind", "size_bytes", "text", "field_kind"],
            "properties": {
              "content_kind": {
                "enum": ["text", "image", "file", "other"],
                "description": "Derived from pasteboard types. Always recorded."
              },
              "size_bytes": {
                "type": ["integer", "null"],
                "minimum": 0,
                "description": "Payload size. Only when capture.text_content is enabled; null otherwise."
              },
              "text": {
                "type": ["string", "null"],
                "description": "Pasted text (after redaction). Only when capture.text_content is enabled and content_kind=text; null otherwise."
              },
              "field_kind": { "$ref": "#/$defs/field_kind" }
            }
          }
        }
      }
    }
  ]
}
