{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.heylua.ai/schemas/logs/1.0",
  "title": "Lua log drain batch",
  "description": "One HTTP request's worth of agent logs, delivered by a Lua log drain. A batch carries exactly one resource: records from three agents in one flush become three batches, never one batch with a mixed resource. Within this major version changes are additive only, so a consumer must accept unknown properties, unknown eventName values and unknown severityNumber values rather than reject them.",
  "type": "object",
  "required": ["schemaUrl", "batchId", "resource", "records"],
  "properties": {
    "schemaUrl": {
      "description": "Always this schema's URL for this major version. Also sent as the X-Lua-Schema header.",
      "const": "https://docs.heylua.ai/schemas/logs/1.0"
    },
    "batchId": {
      "description": "A ULID identifying one HTTP request, also sent as the X-Lua-Batch-Id header. Always present: quote it in a support request and the delivery can be found. Consumers deduplicate on records[].id, never on the batch id: a retried record is re-sent under a new batch id.",
      "type": "string"
    },
    "resource": { "$ref": "#/$defs/resource" },
    "records": {
      "description": "The records in this batch, all describing the same resource.",
      "type": "array",
      "items": { "$ref": "#/$defs/record" }
    }
  },
  "$defs": {
    "resource": {
      "title": "Resource",
      "description": "The agent, environment and version every record in the batch came from. OpenTelemetry semantic conventions where one exists, prefixed lua. where one does not.",
      "type": "object",
      "required": [
        "service.name",
        "service.namespace",
        "service.instance.id",
        "deployment.environment.name",
        "lua.org.id",
        "lua.agent.id"
      ],
      "properties": {
        "service.name": {
          "description": "The agent's name.",
          "type": "string"
        },
        "service.namespace": {
          "description": "The organization id.",
          "type": "string"
        },
        "service.instance.id": {
          "description": "The agent id.",
          "type": "string"
        },
        "service.version": {
          "description": "The agent version that served.",
          "type": "string"
        },
        "deployment.environment.name": {
          "description": "Which environment served the traffic.",
          "enum": ["production", "sandbox"]
        },
        "gen_ai.agent.id": {
          "description": "The agent id, under the GenAI semantic convention.",
          "type": "string"
        },
        "gen_ai.agent.name": {
          "description": "The agent's name, under the GenAI semantic convention.",
          "type": "string"
        },
        "cloud.region": {
          "description": "The region the delivery left from, for example eu-west-1.",
          "type": "string"
        },
        "lua.org.id": {
          "description": "The organization id. The tenancy key: assert it on every record you ingest.",
          "type": "string"
        },
        "lua.agent.id": {
          "description": "The agent id.",
          "type": "string"
        }
      }
    },
    "record": {
      "title": "Record",
      "description": "One log entry, shaped after the OpenTelemetry log data model.",
      "type": "object",
      "required": ["id", "timestamp", "observedTimestamp", "eventName", "severityNumber", "severityText", "attributes"],
      "properties": {
        "id": {
          "description": "The log entry's id. The deduplication key: delivery is at-least-once.",
          "type": "string"
        },
        "timestamp": {
          "description": "RFC 3339. When the thing happened.",
          "type": "string",
          "format": "date-time"
        },
        "observedTimestamp": {
          "description": "RFC 3339. When the platform picked the entry up for delivery.",
          "type": "string",
          "format": "date-time"
        },
        "eventName": {
          "description": "lua.<source>.<suffix>. Never contains an id: a failing Shopify MCP tool is lua.mcp.error, not lua.mcp_shopify.error. New values may appear within this major version, so switch with a default branch.",
          "type": "string",
          "pattern": "^lua\\.[a-z_-]+\\.[a-z]+$"
        },
        "severityNumber": {
          "description": "The OpenTelemetry severity number. Today one of 5, 9, 13 or 17; compare with >= against those boundaries rather than == so a future value does not break ingest.",
          "type": "integer",
          "minimum": 1,
          "maximum": 24
        },
        "severityText": {
          "description": "The severity name matching severityNumber.",
          "enum": ["DEBUG", "INFO", "WARN", "ERROR"]
        },
        "body": {
          "description": "The log message, scrubbed. Absent on a content record when the drain does not include content, in which case lua.content.withheld is true.",
          "type": "string"
        },
        "traceId": {
          "description": "W3C trace id, 32 lowercase hex characters. Absent when the turn carried no trace context.",
          "type": "string",
          "pattern": "^[0-9a-f]{32}$"
        },
        "spanId": {
          "description": "W3C span id, 16 lowercase hex characters.",
          "type": "string",
          "pattern": "^[0-9a-f]{16}$"
        },
        "attributes": {
          "description": "Flat by design: every value is a string, a number or a boolean, never nested. A missing attribute is never an error and never an empty string.",
          "type": "object",
          "additionalProperties": { "$ref": "#/$defs/attributeValue" }
        }
      }
    },
    "attributeValue": {
      "description": "Any attribute value. Scalars only, so destinations that index attributes can take the whole object without a flattening step.",
      "type": ["string", "number", "boolean"]
    }
  }
}
