{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://flowdsl.com/schemas/v1/flowdsl.schema.json",
  "title": "FlowDSL",
  "description": "FlowDSL v1.0.0 — The open specification for describing executable event-driven flow graphs. Nodes define business logic. Edges define delivery semantics. The runtime enforces guarantees.",
  "type": "object",
  "required": ["flowdsl", "info", "flows"],
  "additionalProperties": false,

  "properties": {

    "flowdsl": {
      "type": "string",
      "description": "The FlowDSL specification version this document uses.",
      "enum": ["1.0.0"],
      "examples": ["1.0.0"]
    },

    "info": {
      "$ref": "#/definitions/Info"
    },

    "servers": {
      "type": "object",
      "description": "Runtime server definitions. Keys are server names.",
      "additionalProperties": {
        "$ref": "#/definitions/Server"
      }
    },

    "externalDocs": {
      "$ref": "#/definitions/ExternalDocs"
    },

    "flows": {
      "type": "object",
      "description": "Named flow definitions. Each key is a flow ID.",
      "additionalProperties": {
        "$ref": "#/definitions/Flow"
      },
      "minProperties": 1
    },

    "components": {
      "$ref": "#/definitions/Components"
    }
  },

  "patternProperties": {
    "^x-": {
      "$ref": "#/definitions/Extension"
    }
  },

  "definitions": {

    "Info": {
      "type": "object",
      "description": "Metadata about the FlowDSL document.",
      "required": ["title", "version"],
      "additionalProperties": false,
      "properties": {
        "title": {
          "type": "string",
          "description": "Human-readable title of this flow document.",
          "examples": ["Domain Processing Flows"]
        },
        "version": {
          "type": "string",
          "description": "Version of this flow document (semver recommended).",
          "examples": ["1.0.0"]
        },
        "description": {
          "type": "string",
          "description": "Optional description of what this document covers."
        },
        "contact": {
          "type": "object",
          "properties": {
            "name": { "type": "string" },
            "url": { "type": "string", "format": "uri" },
            "email": { "type": "string", "format": "email" }
          }
        },
        "license": {
          "type": "object",
          "required": ["name"],
          "properties": {
            "name": { "type": "string" },
            "url": { "type": "string", "format": "uri" }
          }
        }
      },
      "patternProperties": {
        "^x-": { "$ref": "#/definitions/Extension" }
      }
    },

    "Server": {
      "type": "object",
      "description": "A runtime server definition.",
      "required": ["url", "protocol"],
      "additionalProperties": false,
      "properties": {
        "url": {
          "type": "string",
          "description": "URL or hostname of the runtime server."
        },
        "protocol": {
          "type": "string",
          "description": "Protocol used by this server.",
          "enum": ["hybrid", "kafka", "redis", "mongo", "http", "grpc"]
        },
        "description": {
          "type": "string"
        },
        "variables": {
          "type": "object",
          "additionalProperties": {
            "type": "object",
            "properties": {
              "default": { "type": "string" },
              "description": { "type": "string" }
            }
          }
        }
      },
      "patternProperties": {
        "^x-": { "$ref": "#/definitions/Extension" }
      }
    },

    "ExternalDocs": {
      "type": "object",
      "description": "Optional links to external specification documents. Declare these only when you want to reference schemas from AsyncAPI or OpenAPI documents using asyncapi#/... or openapi#/... $ref prefixes. FlowDSL documents are fully functional without this field.",
      "additionalProperties": false,
      "properties": {
        "asyncapi": {
          "type": "string",
          "description": "URL or path to the AsyncAPI document. Used to resolve asyncapi#/... $refs.",
          "examples": ["/asyncapi.json", "https://api.example.com/asyncapi.json"]
        },
        "openapi": {
          "type": "string",
          "description": "URL or path to the OpenAPI document.",
          "examples": ["/openapi.json"]
        },
        "description": {
          "type": "string"
        }
      },
      "patternProperties": {
        "^x-": { "$ref": "#/definitions/Extension" }
      }
    },

    "Flow": {
      "type": "object",
      "description": "A named executable flow graph — a coherent processing pipeline.",
      "required": ["entrypoints", "nodes", "edges"],
      "additionalProperties": false,
      "properties": {
        "summary": {
          "type": "string",
          "description": "Short human-readable summary of this flow.",
          "examples": ["Domain drop catch pipeline"]
        },
        "description": {
          "type": "string",
          "description": "Full description of this flow's purpose and behavior."
        },
        "tags": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Optional tags for grouping and filtering in the Studio."
        },
        "entrypoints": {
          "type": "array",
          "description": "One or more entry messages that trigger this flow.",
          "minItems": 1,
          "items": {
            "$ref": "#/definitions/Entrypoint"
          }
        },
        "nodes": {
          "type": "object",
          "description": "Named node instances in this flow. Keys are node IDs used in edges.",
          "additionalProperties": {
            "oneOf": [
              { "$ref": "#/definitions/Node" },
              { "$ref": "#/definitions/Ref" }
            ]
          }
        },
        "edges": {
          "type": "array",
          "description": "Edges connecting nodes. Each edge defines delivery semantics.",
          "items": {
            "$ref": "#/definitions/Edge"
          }
        },
        "defaults": {
          "$ref": "#/definitions/FlowDefaults"
        }
      },
      "patternProperties": {
        "^x-": { "$ref": "#/definitions/Extension" }
      }
    },

    "Entrypoint": {
      "type": "object",
      "description": "A trigger message that starts a flow.",
      "required": ["message"],
      "additionalProperties": false,
      "properties": {
        "message": {
          "$ref": "#/definitions/MessageRef"
        },
        "description": {
          "type": "string"
        },
        "filter": {
          "type": "string",
          "description": "Optional expression to filter which incoming messages trigger this flow.",
          "examples": ["message.payload.priority == 'urgent'"]
        }
      }
    },

    "Node": {
      "type": "object",
      "description": "An executable processing unit. Receives typed input, performs logic, emits typed outputs.",
      "required": ["operationId", "runtime"],
      "additionalProperties": false,
      "properties": {
        "operationId": {
          "type": "string",
          "description": "Unique identifier for this node operation. Used for tracing and registry lookup.",
          "pattern": "^[a-z][a-z0-9_]*$",
          "examples": ["run_domain_rule_filter"]
        },
        "title": {
          "type": "string",
          "description": "Human-readable title for this node.",
          "examples": ["RuleFilterNode"]
        },
        "summary": {
          "type": "string",
          "description": "Short description of what this node does."
        },
        "description": {
          "type": "string"
        },
        "kind": {
          "type": "string",
          "description": "Semantic category of this node.",
          "enum": [
            "source",
            "transform",
            "router",
            "llm",
            "action",
            "checkpoint",
            "publish",
            "terminal",
            "integration",
            "subworkflow"
          ]
        },
        "runtime": {
          "$ref": "#/definitions/NodeRuntime"
        },
        "inputs": {
          "type": "array",
          "description": "Typed input messages this node accepts.",
          "items": {
            "$ref": "#/definitions/NodePort"
          }
        },
        "outputs": {
          "type": "array",
          "description": "Typed output messages this node may emit. Each output can trigger a different edge.",
          "items": {
            "$ref": "#/definitions/NodePort"
          }
        },
        "execution": {
          "$ref": "#/definitions/ExecutionConfig"
        },
        "idempotency": {
          "$ref": "#/definitions/IdempotencyConfig"
        }
      },
      "patternProperties": {
        "^x-": { "$ref": "#/definitions/Extension" }
      }
    },

    "NodeRuntime": {
      "type": "object",
      "description": "Runtime configuration for a node — language, handler, and invocation mode.",
      "required": ["language", "handler"],
      "additionalProperties": false,
      "properties": {
        "language": {
          "type": "string",
          "description": "Implementation language of this node.",
          "enum": ["go", "python", "nodejs"]
        },
        "handler": {
          "type": "string",
          "description": "Fully-qualified handler path or class name.",
          "examples": [
            "app.nodes.domain.RuleFilterNode",
            "nodes/domain/rule_filter.RuleFilterNode"
          ]
        },
        "supports": {
          "type": "array",
          "description": "Communication protocols this node supports. A node may support multiple protocols; the actual protocol used is selected per-edge (connection). proc: direct function call (same-language only). grpc: gRPC + Protobuf (recommended for point-to-point). http: HTTP/JSON (simple external APIs). nats: NATS Core pub/sub or RPC (ultra-low latency). kafka: Kafka distributed log (event sourcing, data pipelines). redis: Redis pub/sub (volatile fast broadcasting). zeromq: ZeroMQ brokerless messaging (custom topologies). rabbitmq: RabbitMQ (complex routing, task queues). websocket: WebSockets (persistent bidirectional streams).",
          "items": {
            "type": "string",
            "enum": ["proc", "grpc", "http", "nats", "kafka", "redis", "zeromq", "rabbitmq", "websocket"]
          },
          "uniqueItems": true,
          "default": ["grpc"]
        },
        "grpc": {
          "type": "object",
          "description": "gRPC configuration. Required when invocation is grpc.",
          "additionalProperties": false,
          "properties": {
            "port": {
              "type": "integer",
              "description": "Port this node's gRPC server listens on.",
              "examples": [50051, 50052, 50053]
            },
            "streaming": {
              "type": "boolean",
              "description": "Whether this node supports InvokeStream RPC. Required for LLM nodes.",
              "default": false
            },
            "maxConcurrentStreams": {
              "type": "integer",
              "description": "Maximum concurrent gRPC streams for this node.",
              "default": 100
            },
            "tls": {
              "type": "boolean",
              "description": "Whether to use TLS for gRPC connection.",
              "default": false
            }
          },
          "required": ["port"]
        },
        "nats": {
          "type": "object",
          "description": "NATS Core configuration. Required when invocation is nats.",
          "additionalProperties": false,
          "properties": {
            "url": {
              "type": "string",
              "description": "NATS server URL.",
              "examples": ["nats://localhost:4222"]
            },
            "subject": {
              "type": "string",
              "description": "NATS subject for this node's messages.",
              "examples": ["flowdsl.nodes.filter"]
            },
            "queueGroup": {
              "type": "string",
              "description": "Queue group for load-balanced consumption."
            }
          },
          "required": ["url", "subject"]
        },
        "redis": {
          "type": "object",
          "description": "Redis Pub/Sub configuration. Required when invocation is redis.",
          "additionalProperties": false,
          "properties": {
            "url": {
              "type": "string",
              "description": "Redis server URL.",
              "examples": ["redis://localhost:6379"]
            },
            "channel": {
              "type": "string",
              "description": "Redis channel or pattern for this node.",
              "examples": ["flowdsl:nodes:filter"]
            }
          },
          "required": ["url", "channel"]
        },
        "zeromq": {
          "type": "object",
          "description": "ZeroMQ configuration. Required when invocation is zeromq.",
          "additionalProperties": false,
          "properties": {
            "address": {
              "type": "string",
              "description": "ZeroMQ bind or connect address.",
              "examples": ["tcp://localhost:5555"]
            },
            "pattern": {
              "type": "string",
              "description": "ZeroMQ socket pattern.",
              "enum": ["pubsub", "pushpull", "reqrep", "pair"],
              "default": "reqrep"
            }
          },
          "required": ["address"]
        },
        "rabbitmq": {
          "type": "object",
          "description": "RabbitMQ configuration. Required when invocation is rabbitmq.",
          "additionalProperties": false,
          "properties": {
            "url": {
              "type": "string",
              "description": "AMQP connection URL.",
              "examples": ["amqp://guest:guest@localhost:5672"]
            },
            "exchange": {
              "type": "string",
              "description": "RabbitMQ exchange name.",
              "examples": ["flowdsl.nodes"]
            },
            "routingKey": {
              "type": "string",
              "description": "Routing key for message publishing."
            },
            "queue": {
              "type": "string",
              "description": "Queue name for consumption."
            }
          },
          "required": ["url"]
        },
        "websocket": {
          "type": "object",
          "description": "WebSocket configuration. Required when invocation is websocket.",
          "additionalProperties": false,
          "properties": {
            "url": {
              "type": "string",
              "description": "WebSocket server URL.",
              "examples": ["ws://localhost:8080/ws"]
            },
            "path": {
              "type": "string",
              "description": "WebSocket endpoint path.",
              "examples": ["/ws/nodes/filter"]
            }
          },
          "required": ["url"]
        },
        "image": {
          "type": "string",
          "description": "Optional container image for isolated node execution.",
          "examples": ["ghcr.io/myorg/domain-nodes:latest"]
        },
        "version": {
          "type": "string",
          "description": "Version of the node implementation.",
          "examples": ["1.2.0"]
        }
      },
    },

    "NodePort": {
      "type": "object",
      "description": "A typed input or output port on a node.",
      "required": ["message"],
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string",
          "description": "Port name. Used in edge 'when' conditions to route specific outputs.",
          "examples": ["RulePassedDomain", "RuleRejectedDomain"]
        },
        "message": {
          "$ref": "#/definitions/MessageRef"
        },
        "description": {
          "type": "string"
        }
      }
    },

    "MessageRef": {
      "type": "object",
      "description": "A reference to a message schema. Resolution order: (1) #/components/events/... — FlowDSL-native event (preferred); (2) #/components/packets/... — raw schema shape; (3) asyncapi#/... or openapi#/... — external import, requires externalDocs. Inline schema is also supported.",
      "additionalProperties": false,
      "properties": {
        "$ref": {
          "type": "string",
          "description": "JSON Reference to a message schema. Prefer local FlowDSL refs. External prefixes asyncapi#/... and openapi#/... are optional integrations requiring externalDocs declaration.",
          "examples": [
            "#/components/events/UserCreated",
            "#/components/packets/RulePassedDomainPacket",
            "asyncapi#/components/messages/DomainExpiredPayload",
            "openapi#/components/schemas/Order"
          ]
        },
        "schema": {
          "type": "object",
          "description": "Inline JSON Schema for the message payload. Use $ref to a named event or packet instead when the schema is reused across multiple ports."
        }
      }
    },

    "EventDefinition": {
      "type": "object",
      "description": "A named, versioned FlowDSL event. Events are the primary way to define typed messages that flow between nodes. Use components.events for all first-class message contracts.",
      "required": ["payload"],
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string",
          "description": "Human-readable event name. Should match the component key in PascalCase.",
          "examples": ["UserCreated", "OrderFulfilled", "DomainDropDetected"]
        },
        "title": {
          "type": "string",
          "description": "Display title shown in Studio and documentation.",
          "examples": ["Domain Drop Detected"]
        },
        "summary": {
          "type": "string",
          "description": "One-line description of when this event is emitted.",
          "examples": ["Emitted when DNS monitoring confirms a domain has dropped (NXDOMAIN)."]
        },
        "description": {
          "type": "string",
          "description": "Full description of the event's semantics and usage."
        },
        "version": {
          "type": "string",
          "description": "Semantic version of this event schema. Increment minor on additive changes, major on breaking.",
          "examples": ["1.0.0", "2.1.0"]
        },
        "entityType": {
          "type": "string",
          "description": "Domain entity type this event belongs to. Used by SDK code generators (e.g. redelay Python SDK) to set entity_type on the generated EventDefinition class. Use snake_case.",
          "examples": ["domain", "favorite", "auction", "user"]
        },
        "action": {
          "type": "string",
          "description": "Event action verb. Used by SDK code generators to set action on the generated EventDefinition class. Use snake_case.",
          "examples": ["drop_detected", "expired", "whois_completed", "features_extracted"]
        },
        "tags": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Tags for grouping events in the Studio schema browser.",
          "examples": [["domain", "dropcatch"], ["auction", "inventory"]]
        },
        "payload": {
          "$ref": "#/definitions/MessageRef",
          "description": "The payload schema for this event. Use an inline schema or reference a packet."
        },
        "examples": {
          "type": "array",
          "description": "Example payload instances for documentation and testing.",
          "items": { "type": "object" }
        }
      },
      "patternProperties": {
        "^x-": { "$ref": "#/definitions/Extension" }
      }
    },

    "ExecutionConfig": {
      "type": "object",
      "description": "Runtime execution parameters for a node.",
      "additionalProperties": false,
      "properties": {
        "timeoutMs": {
          "type": "integer",
          "description": "Maximum execution time in milliseconds before the node is considered failed.",
          "minimum": 1,
          "examples": [10000]
        },
        "concurrency": {
          "type": "integer",
          "description": "Maximum concurrent executions of this node.",
          "minimum": 1,
          "examples": [200]
        },
        "maxRetries": {
          "type": "integer",
          "description": "Maximum number of retry attempts on failure.",
          "minimum": 0,
          "default": 3
        }
      }
    },

    "IdempotencyConfig": {
      "type": "object",
      "description": "Configuration for idempotent side effect tracking.",
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "type": "boolean",
          "default": false
        },
        "keyExpression": {
          "type": "string",
          "description": "Expression to derive idempotency key from input message.",
          "examples": ["input.payload.entityId + ':' + input.payload.action"]
        },
        "ttlSeconds": {
          "type": "integer",
          "description": "How long to retain the idempotency record.",
          "examples": [86400]
        },
        "store": {
          "type": "string",
          "enum": ["mongo", "redis"],
          "default": "mongo"
        }
      }
    },

    "Edge": {
      "type": "object",
      "description": "A directed connection between two nodes, with delivery semantics.",
      "required": ["from", "to", "delivery"],
      "additionalProperties": false,
      "properties": {
        "from": {
          "type": "string",
          "description": "ID of the source node (must match a key in flow.nodes).",
          "examples": ["rule_filter"]
        },
        "to": {
          "type": "string",
          "description": "ID of the destination node (must match a key in flow.nodes).",
          "examples": ["dns_check"]
        },
        "when": {
          "type": "string",
          "description": "Optional condition expression. Edge is only followed when this evaluates to true.",
          "examples": [
            "output.name == 'RulePassedDomain'",
            "output.payload.score > 0.7"
          ]
        },
        "protocol": {
          "type": "string",
          "description": "Communication protocol to use for this edge. Must be a protocol supported by both the source and target nodes.",
          "enum": ["proc", "grpc", "http", "nats", "kafka", "redis", "zeromq", "rabbitmq", "websocket"]
        },
        "delivery": {
          "$ref": "#/definitions/DeliveryPolicy"
        },
        "description": {
          "type": "string"
        }
      },
      "patternProperties": {
        "^x-": { "$ref": "#/definitions/Extension" }
      }
    },

    "DeliveryPolicy": {
      "type": "object",
      "description": "Defines how data is handed off between two nodes. The core of FlowDSL's transport-agnostic design.",
      "required": ["mode"],
      "additionalProperties": false,
      "properties": {
        "mode": {
          "type": "string",
          "description": "Delivery mode for this edge. Determines durability, replay, and latency guarantees.",
          "enum": ["direct", "ephemeral", "checkpoint", "durable", "stream"]
        },
        "backend": {
          "type": "string",
          "description": "Concrete backend for ephemeral mode.",
          "enum": ["redis", "nats", "rabbitmq", "memory"],
          "examples": ["redis"]
        },
        "store": {
          "type": "string",
          "description": "Storage backend for durable and checkpoint modes.",
          "enum": ["mongo", "redis", "postgres"],
          "examples": ["mongo"]
        },
        "batching": {
          "$ref": "#/definitions/BatchingConfig"
        },
        "maxInFlight": {
          "type": "integer",
          "description": "Maximum number of in-flight messages on this edge at any time.",
          "minimum": 1,
          "examples": [10000]
        },
        "retryPolicy": {
          "oneOf": [
            { "$ref": "#/definitions/RetryPolicy" },
            { "$ref": "#/definitions/Ref" }
          ]
        },
        "recovery": {
          "$ref": "#/definitions/RecoveryConfig"
        },
        "stream": {
          "$ref": "#/definitions/StreamConfig"
        },
        "ordering": {
          "type": "string",
          "description": "Message ordering guarantee on this edge.",
          "enum": ["none", "perKey", "strict"],
          "default": "none"
        },
        "priority": {
          "type": "integer",
          "description": "Processing priority for this edge (higher = processed first).",
          "minimum": 0,
          "maximum": 10,
          "default": 5
        }
      },

      "allOf": [
        {
          "if": { "properties": { "mode": { "const": "stream" } } },
          "then": { "required": ["stream"] }
        },
        {
          "if": { "properties": { "mode": { "const": "durable" } } },
          "then": { "required": ["store"] }
        },
        {
          "if": { "properties": { "mode": { "const": "ephemeral" } } },
          "then": { "required": ["backend"] }
        }
      ]
    },

    "BatchingConfig": {
      "type": "object",
      "description": "Batching configuration for edges that support it.",
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "type": "boolean",
          "default": false
        },
        "batchSize": {
          "type": "integer",
          "description": "Number of messages per batch.",
          "minimum": 1,
          "examples": [1000, 500]
        },
        "maxWaitMs": {
          "type": "integer",
          "description": "Maximum time to wait before flushing an incomplete batch.",
          "minimum": 0,
          "examples": [100]
        }
      }
    },

    "RetryPolicy": {
      "type": "object",
      "description": "Retry behavior for a failed edge delivery or node execution.",
      "additionalProperties": false,
      "properties": {
        "maxAttempts": {
          "type": "integer",
          "description": "Maximum total attempts including the first.",
          "minimum": 1,
          "examples": [5]
        },
        "initialDelayMs": {
          "type": "integer",
          "description": "Delay before first retry in milliseconds.",
          "minimum": 0,
          "examples": [1000]
        },
        "backoff": {
          "type": "string",
          "description": "Backoff strategy between retries.",
          "enum": ["fixed", "exponential", "linear"],
          "default": "exponential"
        },
        "maxDelayMs": {
          "type": "integer",
          "description": "Maximum delay between retries in milliseconds.",
          "examples": [60000]
        },
        "deadLetterQueue": {
          "type": "boolean",
          "description": "Whether to route to dead letter queue after exhausting retries.",
          "default": true
        }
      }
    },

    "RecoveryConfig": {
      "type": "object",
      "description": "Recovery configuration for non-durable edges — defines where replay starts.",
      "additionalProperties": false,
      "properties": {
        "replayFrom": {
          "type": "string",
          "description": "Reference to a checkpoint or durable boundary to replay from on restart.",
          "examples": ["checkpoint:domain_ingest", "entrypoint"]
        },
        "strategy": {
          "type": "string",
          "description": "Recovery strategy when the edge has no durable state.",
          "enum": ["replayFromCheckpoint", "replayFromEntrypoint", "skip", "fail"],
          "default": "replayFromCheckpoint"
        }
      }
    },

    "StreamConfig": {
      "type": "object",
      "description": "Configuration for stream delivery mode — publishes to a durable streaming backend.",
      "required": ["bus", "topic"],
      "additionalProperties": false,
      "properties": {
        "bus": {
          "type": "string",
          "description": "The streaming backend to publish to.",
          "enum": ["kafka", "redis", "nats"],
          "examples": ["kafka"]
        },
        "topic": {
          "type": "string",
          "description": "Topic or channel name to publish to.",
          "examples": ["domains.analyzed", "flow.results.ranked"]
        },
        "partitionKey": {
          "type": "string",
          "description": "Expression to derive the partition key from the message payload.",
          "examples": ["payload.domain"]
        }
      }
    },

    "FlowDefaults": {
      "type": "object",
      "description": "Flow-level defaults applied to all nodes and edges unless overridden.",
      "additionalProperties": false,
      "properties": {
        "retryPolicy": {
          "oneOf": [
            { "$ref": "#/definitions/RetryPolicy" },
            { "$ref": "#/definitions/Ref" }
          ]
        },
        "execution": {
          "$ref": "#/definitions/ExecutionConfig"
        }
      }
    },

    "Components": {
      "type": "object",
      "description": "Reusable component definitions referenced via $ref throughout the document.",
      "additionalProperties": false,
      "properties": {
        "events": {
          "type": "object",
          "description": "Named FlowDSL event definitions. The primary way to define typed messages. Reference with #/components/events/MyEvent on node ports and entrypoints. Prefer events over raw packets for all named message contracts.",
          "additionalProperties": { "$ref": "#/definitions/EventDefinition" }
        },
        "packets": {
          "type": "object",
          "description": "Raw reusable JSON Schema shapes. Use for low-level payload types shared across events. Reference with #/components/packets/MyPacket.",
          "additionalProperties": {
            "type": "object",
            "description": "A JSON Schema definition for a packet type."
          }
        },
        "nodes": {
          "type": "object",
          "description": "Reusable node definitions.",
          "additionalProperties": { "$ref": "#/definitions/Node" }
        },
        "edges": {
          "type": "object",
          "description": "Reusable edge definitions.",
          "additionalProperties": { "$ref": "#/definitions/Edge" }
        },
        "policies": {
          "type": "object",
          "description": "Reusable retry policy definitions.",
          "additionalProperties": { "$ref": "#/definitions/RetryPolicy" }
        },
        "schemas": {
          "type": "object",
          "description": "Shared JSON Schema definitions for reuse across packets and events.",
          "additionalProperties": {
            "type": "object"
          }
        },
        "runtimeBindings": {
          "type": "object",
          "description": "Named runtime backend configurations.",
          "additionalProperties": {
            "$ref": "#/definitions/RuntimeBinding"
          }
        }
      },
      "patternProperties": {
        "^x-": { "$ref": "#/definitions/Extension" }
      }
    },

    "RuntimeBinding": {
      "type": "object",
      "description": "A named runtime backend binding (MongoDB collection, Redis stream, Kafka topic config).",
      "required": ["type"],
      "additionalProperties": false,
      "properties": {
        "type": {
          "type": "string",
          "enum": ["mongo", "redis", "kafka"]
        },
        "mongo": {
          "type": "object",
          "properties": {
            "collection": { "type": "string", "examples": ["flow_packets"] },
            "database": { "type": "string" }
          }
        },
        "redis": {
          "type": "object",
          "properties": {
            "stream": { "type": "string", "examples": ["flow.ephemeral.domains"] },
            "keyPrefix": { "type": "string" }
          }
        },
        "kafka": {
          "type": "object",
          "properties": {
            "topic": { "type": "string", "examples": ["domains.analyzed"] },
            "partitions": { "type": "integer", "minimum": 1 },
            "replicationFactor": { "type": "integer", "minimum": 1 }
          }
        }
      }
    },

    "Ref": {
      "type": "object",
      "description": "A JSON Reference to another definition.",
      "required": ["$ref"],
      "additionalProperties": false,
      "properties": {
        "$ref": {
          "type": "string",
          "description": "JSON Reference string.",
          "examples": [
            "#/components/nodes/RuleFilterNode",
            "#/components/policies/llmExpensiveRetry",
            "asyncapi#/components/messages/DomainExpiredPayload"
          ]
        }
      }
    },

    "Extension": {
      "description": "Vendor extension field. Any value is permitted under x-* keys.",
      "oneOf": [
        { "type": "string" },
        { "type": "number" },
        { "type": "boolean" },
        { "type": "object" },
        { "type": "array" },
        { "type": "null" }
      ]
    }
  }
}
