{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://syspad.io/syspad.schema.json",
  "title": "SysPad diagram (.syspad.json)",
  "description": "Interchange format for SysPad diagrams. Importable at syspad.io via Import. Mirrors packages/ui/src/utils/diagramDSL.ts (DSL version 1.0). The importer is lenient: unknown fields are ignored, missing config falls back to per-component defaults, and dangling or duplicate connections are dropped.",
  "type": "object",
  "required": ["syspad", "components"],
  "additionalProperties": true,
  "properties": {
    "syspad": {
      "description": "DSL version. Current: \"1.0\".",
      "type": "string",
      "const": "1.0"
    },
    "title": {
      "description": "Optional diagram title.",
      "type": "string"
    },
    "components": {
      "description": "The nodes on the canvas.",
      "type": "array",
      "items": { "$ref": "#/definitions/component" }
    },
    "connections": {
      "description": "Directed edges between components. from/to must reference component ids.",
      "type": "array",
      "items": { "$ref": "#/definitions/connection" }
    },
    "groups": {
      "description": "Optional visual groups / VPC boundaries. Purely a view layer; they never change simulation results.",
      "type": "array",
      "items": { "$ref": "#/definitions/group" }
    },
    "flows": {
      "description": "Named request paths (user journeys) laid over the components. Each flow is a weighted sub-graph of steps; every step.node must reference a component id. Consumed by Flow simulation mode.",
      "type": "array",
      "items": { "$ref": "#/definitions/flow" }
    },
    "simulation": {
      "$ref": "#/definitions/simulation"
    }
  },
  "definitions": {
    "componentType": {
      "description": "Component registry id. Determines which service is placed and which config keys apply.",
      "type": "string",
      "enum": [
        "ec2",
        "lambda",
        "ecs-fargate",
        "ecs-ec2",
        "eks",
        "eks-fargate",
        "aws-batch",
        "app-runner",
        "step-functions",
        "mwaa",
        "rds",
        "rds-proxy",
        "aurora",
        "dynamodb",
        "dynamodb-dax",
        "elasticache-redis",
        "elasticache-memcached",
        "memorydb",
        "documentdb",
        "neptune",
        "keyspaces",
        "timestream",
        "redshift",
        "opensearch",
        "alb",
        "nlb",
        "api-gateway",
        "api-gateway-rest",
        "api-gateway-http",
        "api-gateway-websocket",
        "appsync",
        "cloudfront",
        "route53",
        "nat-gateway",
        "internet-gateway",
        "edge-functions",
        "transit-gateway",
        "global-accelerator",
        "vpc-endpoint",
        "direct-connect",
        "gwlb",
        "s3",
        "ebs",
        "efs",
        "fsx",
        "s3-glacier",
        "storage-gateway",
        "sqs",
        "sns",
        "kinesis",
        "kinesis-firehose",
        "msk",
        "amazon-mq",
        "eventbridge",
        "redis-stream",
        "ses",
        "iot-core",
        "eventbridge-scheduler",
        "athena",
        "glue",
        "emr",
        "quicksight",
        "sagemaker",
        "bedrock",
        "waf",
        "cognito",
        "kms",
        "secrets-manager",
        "cloudwatch",
        "kong",
        "redis",
        "cloudflare",
        "stripe",
        "auth0",
        "snowflake",
        "databricks",
        "bigquery",
        "openai",
        "anthropic",
        "mongodb-atlas",
        "planetscale",
        "tiger",
        "redis-cloud",
        "firebase",
        "supabase",
        "vercel",
        "neon",
        "twilio",
        "algolia",
        "datadog",
        "temporal",
        "dagster",
        "dkron",
        "external-service",
        "users",
        "shape-rectangle",
        "shape-circle",
        "shape-diamond",
        "shape-text"
      ]
    },
    "component": {
      "type": "object",
      "required": ["id", "type"],
      "additionalProperties": false,
      "properties": {
        "id": {
          "description": "Unique id within the diagram. Referenced by connections, groups.members, and simulation.entry.",
          "type": "string"
        },
        "type": { "$ref": "#/definitions/componentType" },
        "label": {
          "description": "Display name. Falls back to the component's default name if omitted.",
          "type": "string"
        },
        "note": {
          "description": "Optional author note: what this node does in this architecture (documentary only, never affects simulation). Shown on the node and in the config panel, and travels with the diagram.",
          "type": "string"
        },
        "config": {
          "description": "Per-component tunables. Keys depend on type (see docs/SYSPAD_JSON_SCHEMA.md). Any omitted key uses the component default.",
          "type": "object",
          "additionalProperties": {
            "type": ["number", "string", "boolean"]
          }
        },
        "pos": {
          "description": "Canvas position [x, y] in pixels. Optional: omit pos on every component and SysPad auto-arranges the diagram left to right on import. If set, space nodes ~220px apart horizontally to avoid overlap (do not set pos on only some components).",
          "type": "array",
          "items": { "type": "number" },
          "minItems": 2,
          "maxItems": 2
        },
        "group": {
          "description": "Id of the group this component belongs to (must match a groups[].id).",
          "type": "string"
        },
        "systemExpanded": {
          "description": "For orchestration nodes (eks, eks-fargate): render the internal worker/pod view expanded.",
          "type": "boolean"
        }
      }
    },
    "connection": {
      "type": "object",
      "required": ["from", "to"],
      "additionalProperties": false,
      "properties": {
        "from": {
          "description": "Source component id.",
          "type": "string"
        },
        "to": {
          "description": "Target component id.",
          "type": "string"
        },
        "edgeType": {
          "description": "Traffic semantics. 'split' divides the source RPS across its outgoing split-edges (load balancers/routers). 'replicate' sends the full source RPS down each edge (a service calling its cache AND db). 'feedback' is a return/delivery leg (e.g. pub/sub back to the socket tier that published): the topology simulation ignores it entirely - no traffic, no cycle - so real-world loops can be drawn; model its load in a flow (flows may ride feedback edges, often with a calls: 0 passthrough step). If omitted, it is inferred from the source: distributors (alb, nlb, route53, api-gateway-*, kong) split, everything else replicates ('feedback' is never inferred).",
          "type": "string",
          "enum": ["split", "replicate", "feedback"]
        },
        "behavior": {
          "description": "Behaviour axis - how the caller talks to the target. 'sync': the caller waits for the reply, so the target's latency adds to the caller's critical path (the default for plain calls). 'async': fire and forget - the caller moves on; the edge is cut in the critical-path latency, while RPS, utilisation, and cost still flow downstream. 'stream': continuous records through a broker (Kafka/Kinesis-style); simulates like async, but consumers are described in terms of lag rather than request latency. If omitted, it is resolved from the source component: queue/stream/bus sources (asyncBoundary) make their outgoing edges async (stream for record brokers: msk, kinesis, kinesis-firehose, redis-stream), everything else is sync. Usually omit and let the auto-default pick the right value.",
          "type": "string",
          "enum": ["sync", "async", "stream"]
        },
        "protocol": {
          "description": "Protocol axis - what's on the wire, e.g. https, http, grpc, ws, sse, tcp, udp, kafka, amqp, mqtt, sql, redis (free string, so variants like 'http/2' are fine). Informational only - it never affects the simulation. If omitted, it is resolved from the target component's input port.",
          "type": "string"
        },
        "multiplier": {
          "description": "Downstream calls per incoming request along this edge (e.g. 2x a service, 5x a db). Defaults to 1.",
          "type": "number",
          "minimum": 0
        },
        "label": {
          "description": "Optional free-text annotation for this connection: its protocol or intent, e.g. 'gRPC', 'enqueue', 'CDC', 'cron every :30'. Shown as a chip on the edge. Documentation only - it does not affect the simulation. Keep it short (a few words).",
          "type": "string"
        },
        "points": {
          "description": "Optional orthogonal routing waypoints [[x,y], ...] for a hand-drawn edge path. Usually omit - SysPad auto-routes edges, and re-routes everything on auto-layout when positions are omitted. Present mainly so a manually routed diagram round-trips through export/import. Consecutive corners must share an axis.",
          "type": "array",
          "items": {
            "type": "array",
            "items": { "type": "number" },
            "minItems": 2,
            "maxItems": 2
          }
        }
      }
    },
    "group": {
      "type": "object",
      "required": ["id", "label", "collapsed", "pos", "members"],
      "additionalProperties": false,
      "properties": {
        "id": { "type": "string" },
        "label": { "type": "string" },
        "color": {
          "description": "Optional hex colour, e.g. \"#6366f1\".",
          "type": "string"
        },
        "collapsed": {
          "description": "Collapsed (composite card) vs expanded (dashed region).",
          "type": "boolean"
        },
        "pos": {
          "type": "array",
          "items": { "type": "number" },
          "minItems": 2,
          "maxItems": 2
        },
        "members": {
          "description": "Component ids inside this group.",
          "type": "array",
          "items": { "type": "string" }
        },
        "kind": {
          "description": "'group' (default) or 'vpc' (a network boundary).",
          "type": "string",
          "enum": ["group", "vpc"]
        },
        "cidr": {
          "description": "VPC CIDR block, only when kind == 'vpc' (e.g. \"10.0.0.0/16\").",
          "type": "string"
        },
        "parent": {
          "description": "Parent group id when nested (e.g. a group inside a VPC).",
          "type": "string"
        }
      }
    },
    "flowStep": {
      "type": "object",
      "required": ["id", "node"],
      "additionalProperties": false,
      "properties": {
        "id": {
          "description": "Step-local id, distinct from node id so a node can be visited more than once.",
          "type": "string"
        },
        "node": {
          "description": "Id of an existing component this step invokes.",
          "type": "string"
        },
        "calls": {
          "description": "Visit count: times this step calls its node per arrival. Default 1. Amplifies load on this node only, never downstream. An explicit 0 is a zero-load passthrough: the step stays on the path and forwards its arrival, but adds no load or latency - use it when another flow already charged this node for the same work.",
          "type": "number",
          "minimum": 0
        },
        "async": {
          "description": "Fire-and-forget: own enqueue latency counts, downstream subtree drops off the synchronous critical path.",
          "type": "boolean"
        },
        "serviceMs": {
          "description": "Hard override of this step's latency in ms; bypasses the component's own load-scaled latency.",
          "type": "number",
          "minimum": 0
        }
      }
    },
    "flowTransition": {
      "type": "object",
      "required": ["from", "to"],
      "additionalProperties": false,
      "properties": {
        "from": {
          "description": "Source step id.",
          "type": "string"
        },
        "to": {
          "description": "Target step id.",
          "type": "string"
        },
        "kind": {
          "description": "Omitted = sequential. 'branch' = weighted by pct (mutually exclusive arms). 'parallel' = replicate to each arm, rejoin on max latency.",
          "type": "string",
          "enum": ["branch", "parallel"]
        },
        "pct": {
          "description": "Branch only: 0-100. Sibling branch pcts sum to 100.",
          "type": "number",
          "minimum": 0,
          "maximum": 100
        }
      }
    },
    "flow": {
      "type": "object",
      "required": ["id", "trigger", "steps", "transitions"],
      "additionalProperties": false,
      "properties": {
        "id": { "type": "string" },
        "label": {
          "description": "Display name for the flow.",
          "type": "string"
        },
        "trigger": {
          "description": "Where the flow enters, at what rate, and how it is triggered.",
          "type": "object",
          "required": ["node", "rps"],
          "additionalProperties": false,
          "properties": {
            "node": {
              "description": "Entry component id.",
              "type": "string"
            },
            "rps": {
              "description": "The effective rate the steady-state simulation injects, in requests/sec. For a 'cron' or 'event' trigger this is the AVERAGE executions per second (a job firing every 30s is about 0.033).",
              "type": "number",
              "minimum": 0
            },
            "type": {
              "description": "How the flow is triggered. Omitted = 'rps' (steady API traffic). 'cron' = a scheduled job. 'event' = a queue message, webhook, or event notification. Cron/event still simulate at the average 'rps' above; the 'schedule' is documentation shown on the flow.",
              "type": "string",
              "enum": ["rps", "cron", "event"]
            },
            "schedule": {
              "description": "For 'cron': the schedule as a cron expression or rate (e.g. 'cron(0 * * * ? *)', 'every 30s', '@hourly'). For 'event': the event source/description (e.g. 'SQS: orders', 'S3 ObjectCreated'). Documentation only - the simulation uses 'rps'.",
              "type": "string"
            },
            "timezone": {
              "description": "For a 'cron' trigger only: the IANA timezone the schedule is expressed in (e.g. 'UTC', 'America/New_York').",
              "type": "string"
            }
          }
        },
        "steps": {
          "type": "array",
          "items": { "$ref": "#/definitions/flowStep" }
        },
        "transitions": {
          "type": "array",
          "items": { "$ref": "#/definitions/flowTransition" }
        }
      }
    },
    "simulation": {
      "type": "object",
      "description": "Initial simulation settings.",
      "additionalProperties": false,
      "properties": {
        "entry": {
          "description": "Id of the entry component where request traffic enters the graph.",
          "type": "string"
        },
        "rps": {
          "description": "Target requests per second injected at the entry node.",
          "type": "number",
          "minimum": 0
        },
        "payloadKb": {
          "description": "Average request payload size in KB.",
          "type": "number",
          "minimum": 0
        },
        "region": {
          "description": "AWS region for pricing (defaults to us-east-1), e.g. \"us-east-1\", \"eu-west-1\".",
          "type": "string"
        }
      }
    }
  }
}
