{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://openwop.dev/spec/v1/run-event.schema.json",
  "title": "RunEventDoc",
  "description": "Persisted event document in the run's append-only event log. Source of truth for run state via projection fold. Canonical OpenWOP v1 event envelope.",
  "type": "object",
  "required": ["eventId", "runId", "type", "payload", "timestamp", "sequence"],
  "properties": {
    "eventId": {
      "type": "string",
      "description": "Globally unique ID for this event. Generated by appendAtomic before write; consumers MUST treat as opaque.",
      "minLength": 1,
      "maxLength": 128
    },
    "runId": {
      "type": "string",
      "description": "Run this event belongs to.",
      "minLength": 1,
      "maxLength": 128
    },
    "nodeId": {
      "type": "string",
      "description": "Set for node-scoped events (node.*, approval.*, clarification.*). Lifted to top-level so projection code can key per-node state machines without payload type coercion.",
      "minLength": 1,
      "maxLength": 128
    },
    "type": {
      "description": "Event-type discriminator. RFC 0094 §C: either a protocol-owned type from the authoritative `RunEventType` catalog, or a correctly-prefixed vendor-extension event per `host-extensions.md` §\"Vendor-prefixed namespaces\" (dotted name whose first segment is the vendor's short identifier or reverse-DNS label — e.g. `acme.canvas.published` — and is NOT a protocol-owned/registry-reserved prefix). Aligns the schema with `COMPATIBILITY.md` (\"Clients MUST ignore unknown event types\") and `version-negotiation.md` (readers ignore unknown).",
      "anyOf": [
        { "$ref": "#/$defs/RunEventType" },
        {
          "type": "string",
          "pattern": "^(?!(?:openwop|core|community|vendor|private|local)\\.)[a-z][a-zA-Z0-9_-]*(\\.[a-zA-Z0-9_-]+)+$",
          "description": "Vendor-extension event type: two or more dot-separated segments; the leading segment is the vendor prefix and MUST NOT be one of the protocol-owned / registry-reserved prefixes (`openwop.*`, `core.*`, `community.*`, `vendor.*`, `private.*`, `local.*`) per `host-extensions.md` §\"Canonical prefixes\"."
        }
      ]
    },
    "payload": {
      "description": "Event-type-specific payload. Servers MAY validate against per-type schemas; this top-level schema accepts any JSON value.",
      "type": ["object", "array", "string", "number", "boolean", "null"]
    },
    "timestamp": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp. IO adapters normalize between this canonical form and platform-specific representations (e.g., Firestore Timestamp)."
    },
    "sequence": {
      "type": "integer",
      "minimum": 0,
      "description": "Monotonic per-run sequence. First event is 0; assigned atomically by appendAtomic."
    },
    "schemaVersion": {
      "type": "integer",
      "minimum": 0,
      "description": "Per-event schema version. Bumped when an individual event type's payload contract changes. Distinct from per-run eventLogSchemaVersion (which describes the subcollection contract). Reader behavior is tolerant — unknown future versions fold best-effort."
    },
    "engineVersion": {
      "type": "string",
      "description": "Engine version that produced this event. Used by P4 versioning for compatibility checks."
    },
    "causationId": {
      "type": "string",
      "description": "Optional ID of the event that caused this one (e.g., node.completed caused by approval.received). Lets projections build causal chains without inferring from timing.",
      "minLength": 1,
      "maxLength": 128
    }
  },
  "$comment": "RFC 0094 §G + COMPATIBILITY.md §\"Schema closure\": RunEventDoc is a SERVER-EMITTED shape, so it is open (`additionalProperties: true`) — v1.x hosts may add optional fields additively without breaking schema-validating clients. Client-submitted shapes stay closed at their outermost composition.",
  "additionalProperties": true,
  "$defs": {
    "RunEventType": {
      "type": "string",
      "description": "Discriminator for event payload shape. Dotted naming convention. New variants extend the union — readers MUST NOT throw on unknown types (forward-compat: fold best-effort, ignore unknowns).",
      "enum": [
        "run.started",
        "run.completed",
        "run.failed",
        "run.cancelled",
        "run.resuming",
        "run.paused",
        "run.resumed",
        "run.restored-from-snapshot",
        "run.dead_lettered",
        "node.started",
        "node.completed",
        "node.failed",
        "node.suspended",
        "node.suspend-failed",
        "node.resumed",
        "node.retried",
        "node.skipped",
        "node.cancelled",
        "approval.requested",
        "approval.received",
        "approval.granted",
        "approval.rejected",
        "approval.overridden",
        "clarification.requested",
        "clarification.resolved",
        "interrupt.requested",
        "interrupt.resolved",
        "channel.written",
        "artifact.created",
        "output.chunk",
        "variable.changed",
        "log.appended",
        "version.pinned",
        "workflow.restored",
        "workflow.loopback-limit",
        "workflow.stalled",
        "cap.breached",
        "lease.acquired",
        "lease.renewed",
        "lease.lost",
        "lease.handed-off",
        "replay.diverged",
        "replay.divergedAtRefusal",
        "agent.reasoned",
        "agent.reasoning.delta",
        "provider.usage",
        "prompt.composed",
        "agent.promptResolved",
        "model.capability.substituted",
        "model.capability.insufficient",
        "envelope.retry.attempted",
        "envelope.retry.exhausted",
        "envelope.refusal",
        "envelope.truncated",
        "envelope.nlToFormat.engaged",
        "envelope.recovery.applied",
        "agent.toolCalled",
        "agent.toolReturned",
        "agent.handoff",
        "agent.decided",
        "agent.verified",
        "agent.invocation.started",
        "agent.invocation.completed",
        "eval.started",
        "eval.scored",
        "eval.completed",
        "deployment.promoted",
        "deployment.rolled-back",
        "deployment.canary.adjusted",
        "deployment.state.changed",
        "roster.run.initiated",
        "tool.session.opened",
        "tool.session.closed",
        "egress.decided",
        "trigger.subscription.state.changed",
        "trigger.delivery.attempted",
        "budget.reserved",
        "budget.consumed",
        "budget.threshold.crossed",
        "budget.exhausted",
        "runOrchestrator.decided",
        "node.dispatched",
        "conversation.opened",
        "conversation.exchanged",
        "conversation.closed",
        "memory.compacted",
        "memory.written",
        "agent.memory.consolidated",
        "commitment.fired",
        "workspace.updated",
        "core.workflowChain.event",
        "core.workflowChain.confidence-escalated",
        "connector.authorized",
        "connector.auth_expired",
        "authorization.decided",
        "proposal.created",
        "proposal.activated",
        "goal.evaluated",
        "goal.closed",
        "import.applied"
      ]
    }
  }
}
