{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "flusso Config",
  "type": "object",
  "required": ["source"],
  "additionalProperties": false,
  "definitions": {
    "pg_identifier": {
      "type": "string",
      "pattern": "^[a-z_][a-z0-9_]*$",
      "maxLength": 63
    },
    "env_or_value": {
      "description": "A literal string value or a reference to an environment variable.",
      "oneOf": [
        {
          "type": "string"
        },
        {
          "type": "object",
          "required": ["env"],
          "additionalProperties": false,
          "properties": {
            "env": {
              "type": "string",
              "description": "Name of the environment variable to read at runtime"
            }
          }
        }
      ]
    },
    "opensearch_sink": {
      "type": "object",
      "title": "OpenSearch",
      "required": ["type", "url"],
      "additionalProperties": false,
      "properties": {
        "type": {
          "type": "string",
          "const": "opensearch"
        },
        "url": {
          "$ref": "#/definitions/env_or_value",
          "description": "Base URL of the OpenSearch cluster, e.g. https://search.example.com:9200"
        },
        "username": {
          "$ref": "#/definitions/env_or_value",
          "description": "HTTP Basic Auth username"
        },
        "password": {
          "$ref": "#/definitions/env_or_value",
          "description": "HTTP Basic Auth password"
        },
        "tls_verify": {
          "type": "boolean",
          "default": true,
          "description": "Verify TLS certificates. Set false only for local development."
        },
        "batch_size": {
          "type": "integer",
          "minimum": 1,
          "default": 1000,
          "description": "Maximum number of documents per bulk request chunk"
        },
        "max_bytes": {
          "type": "integer",
          "minimum": 1,
          "default": 10485760,
          "description": "Maximum uncompressed byte size of a bulk request chunk. Defaults to 10 MiB, within OpenSearch's recommended 5–15 MB bulk range."
        },
        "timeout_secs": {
          "type": "integer",
          "minimum": 1,
          "default": 30,
          "description": "HTTP request timeout in seconds"
        },
        "max_retries": {
          "type": "integer",
          "minimum": 0,
          "default": 3,
          "description": "Number of additional retry attempts on transient failures (exponential backoff)"
        },
        "pipeline": {
          "type": "string",
          "description": "Optional OpenSearch ingest pipeline name to apply on every index operation"
        },
        "number_of_shards": {
          "type": "integer",
          "minimum": 1,
          "default": 1,
          "description": "Primary shards for each created index"
        },
        "number_of_replicas": {
          "type": "integer",
          "minimum": 0,
          "default": 1,
          "description": "Replica shards for each created index"
        },
        "refresh_interval": {
          "type": "string",
          "default": "10s",
          "description": "OpenSearch refresh_interval applied to each index after seeding — the steady-state search-visibility ceiling (e.g. \"10s\", \"1s\", or \"-1\" to disable automatic refresh). flusso forces an immediate refresh whenever the pipeline catches up, so this only bounds staleness while a backlog is draining."
        },
        "text_analysis": {
          "enum": ["builtin", "icu"],
          "default": "builtin",
          "description": "Analysis backend for the flusso_* analyzers. 'icu' requires the analysis-icu plugin installed on every node."
        },
        "auto_subfields": {
          "type": "boolean",
          "default": true,
          "description": "Auto-enrich text/keyword fields with a good analyzer and the keyword/keyword_lowercase/text subfields. A field's explicit mapping always wins."
        }
      }
    },
    "stdout_sink": {
      "type": "object",
      "title": "Stdout",
      "required": ["type"],
      "additionalProperties": false,
      "properties": {
        "type": {
          "type": "string",
          "const": "stdout"
        },
        "pretty": {
          "type": "boolean",
          "default": false,
          "description": "Emit pretty-printed JSON instead of compact NDJSON"
        }
      }
    }
  },
  "properties": {
    "source": {
      "type": "object",
      "required": ["type"],
      "oneOf": [
        {
          "title": "Postgres",
          "additionalProperties": false,
          "required": ["type"],
          "properties": {
            "type": {
              "type": "string",
              "const": "postgres"
            },
            "manage_publication": {
              "type": "boolean",
              "default": true,
              "description": "Whether flusso may auto-create/extend the Postgres publication to cover every table the indexes read (when the source role is privileged enough). Set false to only report coverage gaps and never issue publication DDL."
            },
            "connection_url": {
              "description": "How to connect to the source database. Either a full URL or individual connection parts.",
              "oneOf": [
                {
                  "type": "string",
                  "pattern": "^(postgresql|postgres)://\\S+$",
                  "description": "Full connection URL, e.g. postgresql://user:pass@localhost:5432/mydb"
                },
                {
                  "type": "object",
                  "required": ["env"],
                  "additionalProperties": false,
                  "description": "Read the full connection URL from an environment variable at runtime",
                  "properties": {
                    "env": {
                      "type": "string",
                      "description": "Name of the environment variable to read at runtime"
                    }
                  }
                },
                {
                  "type": "object",
                  "additionalProperties": false,
                  "required": ["database"],
                  "description": "Individual connection parts",
                  "properties": {
                    "host": {
                      "type": "string",
                      "default": "127.0.0.1",
                      "description": "Database host"
                    },
                    "port": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 65535,
                      "default": 5432,
                      "description": "Database port"
                    },
                    "user": {
                      "type": "string",
                      "default": "postgres",
                      "description": "Database user"
                    },
                    "password": {
                      "$ref": "#/definitions/env_or_value",
                      "description": "Database password"
                    },
                    "database": {
                      "type": "string",
                      "description": "Database name"
                    }
                  }
                }
              ]
            }
          }
        }
      ]
    },
    "sinks": {
      "type": "object",
      "description": "Named sink destinations. Each key is the sink name; multiple sinks can be defined.",
      "propertyNames": {
        "$ref": "#/definitions/pg_identifier"
      },
      "additionalProperties": {
        "oneOf": [
          { "$ref": "#/definitions/opensearch_sink" },
          { "$ref": "#/definitions/stdout_sink" }
        ]
      }
    },
    "index": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["name", "schema", "enabled"],
        "additionalProperties": false,
        "properties": {
          "name": {
            "$ref": "#/definitions/pg_identifier",
            "description": "Name of the index"
          },
          "schema": {
            "type": "string",
            "description": "Path to the index schema YAML file starting from the config location",
            "format": "uri-reference",
            "pattern": "^[^/][^:]*\\.ya?ml$"
          },
          "enabled": {
            "type": "boolean"
          },
          "on_error": {
            "type": "string",
            "enum": ["stop", "skip"],
            "description": "Per-index override of the global on_error policy (stop | skip). Omitted inherits the global default."
          }
        }
      }
    },
    "on_error": {
      "type": "string",
      "enum": ["stop", "skip"],
      "default": "stop",
      "description": "What to do when a sink rejects a document at the item level (a mapping conflict, a malformed value): stop the run (default) or skip the document and continue. Override per index under [[index]]."
    },
    "server": {
      "type": "object",
      "additionalProperties": false,
      "description": "Bind addresses for the operational HTTP surfaces. The FLUSSO_* environment variables and CLI flags override these (CLI > env > config).",
      "properties": {
        "public_address": {
          "type": "string",
          "description": "Bind address for the public, read-only surface (/healthz, /readyz, /status, /metrics). E.g. 0.0.0.0:9464."
        },
        "private_address": {
          "type": "string",
          "description": "Bind address for the private, Basic-auth control surface (/indexes, /reindex). E.g. 0.0.0.0:9465."
        }
      }
    }
  }
}
