{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://cladone.com/schemas/usage-event.schema.json",
  "title": "Cladone Gate One usage event",
  "description": "Flat usage record emitted to out-of-process extension plugins. No tokens, cookies, raw query strings, email or username: the user is represented by an opaque subjectId (HMAC).",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schemaVersion",
    "type",
    "ts",
    "instanceId",
    "epoch"
  ],
  "properties": {
    "schemaVersion": {
      "type": "integer",
      "const": 1,
      "description": "Event schema version. Always 1 for this contract."
    },
    "type": {
      "type": "string",
      "enum": [
        "request",
        "auth",
        "error",
        "billing",
        "audit"
      ],
      "description": "Event family. Only subscribed types are produced."
    },
    "ts": {
      "type": "string",
      "format": "date-time",
      "description": "Event time, RFC 3339 UTC."
    },
    "instanceId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 256,
      "description": "Stable Gate One node identifier (one WAL per node and type)."
    },
    "epoch": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "description": "Producer epoch, changed on restart/migration so consumers can detect a gap."
    },
    "tenant": {
      "type": "string",
      "maxLength": 128,
      "description": "Auth provider id or logical tenant; empty for shared/public routes."
    },
    "subjectId": {
      "type": "string",
      "pattern": "^v[0-9]+\\.[A-Za-z0-9_-]+$",
      "description": "Opaque deployment-scoped user identifier: versioned HMAC. Never an email or a raw IdP sub."
    },
    "route": {
      "type": "string",
      "maxLength": 512,
      "description": "Normalized route pattern, never the raw URL/query."
    },
    "host": {
      "type": "string",
      "maxLength": 256,
      "description": "Request host, without port. Routes are keyed by (host, path): this disambiguates the same path served on several hosts."
    },
    "method": {
      "type": "string",
      "maxLength": 16,
      "description": "HTTP method for request/error events."
    },
    "status": {
      "type": "integer",
      "minimum": 0,
      "maximum": 599,
      "description": "HTTP status for request/error events."
    },
    "durationMs": {
      "type": "integer",
      "minimum": 0,
      "description": "Backend or auth duration in milliseconds."
    },
    "bytes": {
      "type": "integer",
      "minimum": 0,
      "description": "Response/request size in bytes, as observed by the gateway."
    },
    "authResult": {
      "type": "string",
      "enum": [
        "ok",
        "anonymous",
        "denied",
        "unavailable",
        "failed"
      ],
      "description": "Authentication outcome for the request or auth action."
    },
    "action": {
      "type": "string",
      "maxLength": 64,
      "description": "Auth action: login, login_failed, logout, backchannel_logout, revocation. Audit action: reload, reload_failed, plugin_load."
    },
    "detail": {
      "type": "string",
      "maxLength": 256,
      "description": "Free-text context for audit events (plugin name, reload error). Never PII."
    },
    "attrs": {
      "type": "object",
      "maxProperties": 8,
      "propertyNames": {
        "maxLength": 32
      },
      "additionalProperties": {
        "type": "string",
        "maxLength": 256
      },
      "description": "Bounded billing attributes captured from allow-listed query/body fields (or backend hints). Sensitive names are skipped; values are capped."
    },
    "attrsTruncated": {
      "type": "boolean",
      "description": "True when attrs were capped (too many keys, or a key/value/total exceeded the limit)."
    },
    "sessionId": {
      "type": "string",
      "maxLength": 128,
      "description": "Opaque RP session id for auth events."
    },
    "mcpTool": {
      "type": "string",
      "maxLength": 256,
      "description": "MCP tool or operation name, when the event comes from an MCP route."
    },
    "units": {
      "type": "integer",
      "minimum": 0,
      "description": "Billable/metered units for billing events."
    },
    "unitType": {
      "type": "string",
      "enum": [
        "request",
        "token",
        "byte",
        "mcp_call"
      ],
      "description": "Unit kind for billing events."
    },
    "requestId": {
      "type": "string",
      "maxLength": 128,
      "description": "Gateway request id, for correlation with logs and traces."
    },
    "idempotencyKey": {
      "type": "string",
      "maxLength": 128,
      "description": "Caller-provided idempotency key, copied only for billing."
    }
  }
}
