{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Tadoru event envelope (v1)",
  "description": "The OS-independent envelope shared by every event Tadoru 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; always null otherwise."
        }
      }
    },
    "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"],
            "properties": {
              "field_kind": { "$ref": "#/$defs/field_kind" },
              "value_len": {
                "type": ["integer", "null"],
                "minimum": 0,
                "description": "Length of the value after the change. Always recorded; the content itself lives in element.value and is opt-in."
              }
            }
          }
        }
      }
    },
    {
      "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": "Typed characters (coalesced, after redaction). Only when capture.text_content is enabled and kind=text; null otherwise."
              },
              "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" }
            }
          }
        }
      }
    }
  ]
}
