{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://hyperstitious.art/schemas/connection-profiles.schema.json",
  "title": "Connection Profiles",
  "description": "Single source of truth for LLM/endpoint connection profiles shared across surfaces (assistants, chatlink, the Electron picker, the llm-proxy runtime, and the Solvulator Python runtime). A profile is one record with a two-bit liveness lifecycle: `state.available` (probe succeeded) and `state.tested` (a real inference round-trip succeeded). Magnetic snapping selects on the stronger bit.",
  "type": "object",
  "required": ["version", "profiles"],
  "additionalProperties": false,
  "properties": {
    "version": {
      "description": "Schema-instance version. Bump on breaking changes to the profiles array shape.",
      "type": "integer",
      "minimum": 1
    },
    "profiles": {
      "type": "array",
      "items": { "$ref": "#/$defs/profile" }
    }
  },
  "$defs": {
    "profile": {
      "type": "object",
      "description": "A single connection target plus the contract for proving it is live and the runtime-written record of its current health.",
      "required": ["id", "label", "kind", "target"],
      "additionalProperties": false,
      "properties": {
        "id": {
          "description": "Stable, namespaced identifier. Convention: '<class>.<name>[.<gate>]', e.g. 'ai.coggy', 'ai.hyle.gemini-gate'.",
          "type": "string",
          "pattern": "^[a-z0-9]+(\\.[a-z0-9-]+)*$"
        },
        "label": {
          "description": "Human-facing name shown in the picker.",
          "type": "string",
          "minLength": 1
        },
        "kind": {
          "description": "Transport class. 'endpoint' = directly probeable HTTP base URL. 'gateway' = routes through another profile/proxy. 'legacy' = not wired into the current runtime; always surfaced offline until a real bridge exists.",
          "enum": ["endpoint", "gateway", "legacy"]
        },
        "target": {
          "description": "Where the profile points. For endpoint/gateway this is an HTTP(S) base URL (OpenAI-compatible, ending before '/v1' resources). For legacy it MAY be an empty string until wired.",
          "type": "string"
        },
        "route": {
          "description": "Resolved transport hint written by the runtime (e.g. the concrete upstream a gateway snapped to). Empty until resolved.",
          "type": "string",
          "default": ""
        },
        "probe": {
          "$ref": "#/$defs/probe"
        },
        "models": {
          "description": "Either the literal 'auto' (discover via GET <target>/models) or an explicit allow-list of model ids.",
          "oneOf": [
            { "const": "auto" },
            { "type": "array", "items": { "type": "string" }, "minItems": 1 }
          ],
          "default": "auto"
        },
        "state": {
          "$ref": "#/$defs/state"
        },
        "tags": {
          "description": "Free-form labels for grouping/filtering (e.g. 'local', 'offline', 'paid').",
          "type": "array",
          "items": { "type": "string" },
          "default": []
        },
        "priority": {
          "description": "Lower wins when snapping. Ties broken by state.latency_ms ascending.",
          "type": "integer",
          "default": 100
        },
        "summary": {
          "description": "Optional one-line human description of what this profile is.",
          "type": "string"
        },
        "note": {
          "description": "Optional operator note, typically a precondition for enabling a legacy/offline profile.",
          "type": "string"
        }
      },
      "allOf": [
        {
          "description": "endpoint and gateway profiles must declare a probe so liveness is decidable.",
          "if": { "properties": { "kind": { "enum": ["endpoint", "gateway"] } } },
          "then": { "required": ["probe"] }
        }
      ]
    },
    "probe": {
      "type": "object",
      "description": "How the runtime proves availability. A successful probe sets state.available; it does NOT set state.tested.",
      "required": ["method", "path", "timeout_ms"],
      "additionalProperties": false,
      "properties": {
        "method": { "enum": ["GET", "POST", "HEAD"] },
        "path": {
          "description": "Path appended to target for the liveness check, e.g. '/health' or '/models'.",
          "type": "string"
        },
        "timeout_ms": {
          "description": "Probe must complete within this budget or the profile is treated as unavailable.",
          "type": "integer",
          "minimum": 1
        },
        "expect_status": {
          "description": "HTTP status considered healthy. Defaults to 200.",
          "type": "integer",
          "default": 200
        }
      }
    },
    "state": {
      "type": "object",
      "description": "Runtime-written health. Read-only to authors; the probe/smoke-test runtime owns these fields.",
      "additionalProperties": false,
      "properties": {
        "available": {
          "description": "True iff the probe succeeded within its window. Necessary but NOT sufficient for snapping.",
          "type": "boolean",
          "default": false
        },
        "tested": {
          "description": "True iff a real inference round-trip (POST /v1/chat/completions) succeeded recently. THIS is the bit magnetic snapping gates on.",
          "type": "boolean",
          "default": false
        },
        "last_ok": {
          "description": "ISO-8601 timestamp of the most recent successful test, or null.",
          "type": ["string", "null"],
          "format": "date-time",
          "default": null
        },
        "latency_ms": {
          "description": "Observed latency of the last successful test, used as the tie-breaker. Null if never tested.",
          "type": ["integer", "null"],
          "minimum": 0,
          "default": null
        }
      }
    }
  }
}
