#  Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
#  SPDX-License-Identifier: Apache-2.0
$schema: "https://json-schema.org/draft-07/schema#"
$id: "infra/v1"
title: "Infra Manifest"
description: >
  Schema for infrastructure manifests (apiVersion: infra/v1). Different ``kind``
  values populate different ``spec`` fields; see InfraManifest in infra_manifest.py.
  InfraBundle composes other infra files via spec.includes (resolved recursively).

type: object
required:
  - apiVersion
  - kind
  - metadata
  - spec
additionalProperties: false


allOf:
  - if:
      properties:
        kind: {const: RuntimeEngine}
      required: [kind]
    then:
      properties:
        spec:
          $ref: "./fragments/runtime-engine.schema.yaml"

properties:
  apiVersion:
    type: string
    const: "infra/v1"

  kind:
    type: string
    enum:
      - InfraBundle
      - InfraMiddleware
      - InfraInterceptor
      - LLMProxy
      - LLMLocal
      - ToolServerRegistry
      - PersonalSecrets
      - Application
      - ToolRegistry
      - ToolProvider
      - Datastore
      - SecretsProvider
      - Infrastructure
      - RuntimeEngine
      - ServiceBundle

  metadata:
    type: object
    additionalProperties: false
    required: [name]
    properties:
      name:
        type: string
        minLength: 1
      description:
        type: string

  spec:
    type: object
    description: >
      Kind-specific infrastructure specification. Connection values belong
      in this document. ``env:VAR_NAME|default`` is an optional override of
      a manifest default (URLs, API bases). ``env:VAR_NAME`` is for secrets
      (empty when unset). Fields ending in ``_env`` (e.g.
      ``api_key_env``, ``password_env``) are secretKeyRef names and must
      be literal env var names, not ``env:`` lookups.
    additionalProperties: true
    allOf:
      - $ref: "./fragments/infra-middleware-params-by-id.schema.yaml"
    properties:
      # ── InfraBundle ─────────────────────────────────────────────────────
      entries:
        type: array
        description: >
          Typed infra slots (Phase 10). Each entry resolves a leaf manifest and
          optional serial pipeline. Preferred over ``includes`` + ``interceptors``.
        items:
          $ref: "./fragments/infra-entry.schema.yaml"

      includes:
        type: array
        description: "Paths or library refs (e.g. standard:openai) merged into one InfraManifest."
        items:
          type: string

      interceptors:
        type: array
        description: >
          Ordered cross-cutting middleware refs or inline entries (outer-first).
        items:
          oneOf:
            - type: string
            - $ref: "./fragments/infra-middleware-inline.schema.yaml"

      runtime_engine:
        $ref: "./fragments/runtime-engine.schema.yaml"

      middleware:
        type: string
        description: "InfraMiddleware/InfraInterceptor: registry id (e.g. llm_cache, fault_inject)."

      applies_to:
        type: array
        description: >
          InfraMiddleware/InfraInterceptor: engine ops this entry wraps.
          Default [LLM_CALL]. Future: TOOL_HTTP, OTEL_EXPORT.
        items:
          type: string
          enum:
            - LLM_CALL
            - TOOL_HTTP
            - OTEL_EXPORT
        default: [LLM_CALL]

      # ── LLMProxy / LLMLocal / Infrastructure ────────────────────────────
      protocol:
        type: string
        description: >
          Wire-protocol plugin kind for the LLMProvider registry
          (``openai`` now; later ``bedrock``, …). Ollama and LiteLLM
          proxies use ``openai``. Default is the library ``llm_provider``
          default (openai). Not ``mock`` or ``cache`` — offline CI uses
          ``llm_cache`` replay (``raise_on_miss``); cache wraps the routed
          protocol plugin.
        default: openai

      proxy:
        type: object
        additionalProperties: true
        properties:
          api_base:
            type: string
            description: >
              OpenAI-compatible base URL. Use ``env:LLM_PROXY_API_BASE|https://…``
              for deploy-time injection (K8s ConfigMap / external secret).
          api_key_env:
            type: string
            default: "OPENAI_API_KEY"
            description: >
              Name of the environment variable holding the API key (secretKeyRef).
              Literal only — do not use ``env:`` syntax here.
          timeout:
            type: number
            description: >
              HTTP request timeout in seconds for LLM API calls. Defaults to 120.
          retry:
            $ref: "./fragments/retry-policy.schema.yaml"
            description: >
              Infra retry for this proxy. Agent ``spec.control.retry.llm``
              overrides when both are set. ``MAS_LLM_HTTP_RETRIES`` is extra
              retries (attempts = retries + 1).

      server:
        type: object
        description: "LLMLocal: OpenAI-compatible endpoint (alias for proxy fields)."
        additionalProperties: true
        properties:
          api_base:
            type: string
          api_key_env:
            type: string
          timeout:
            type: number
            description: >
              HTTP request timeout in seconds for LLM API calls. Defaults to 120.

      models:
        type: object
        additionalProperties: true
        properties:
          allowed:
            type: array
            items:
              type: string
          available:
            type: array
            description: "LLMLocal alias for allowed model list."
            items:
              type: string
          defaults:
            type: object
            properties:
              llm:
                type: string
              embed:
                type: string
          mappings:
            type: object
            additionalProperties:
              type: string
          generation:
            type: object
            additionalProperties: false
            description: >
              LLMProxy/LLMLocal: operator output-token policy for every model
              served by this endpoint. max_tokens / max_completion_tokens /
              on_truncation are defaults below spec.models[]; max_output_tokens
              is a ceiling that bounds every value, including CLI and env
              overrides. Omit the block to send no limit (server default).
            properties:
              max_tokens:
                type: integer
                minimum: 1
              max_completion_tokens:
                type: integer
                minimum: 1
              max_output_tokens:
                type: integer
                minimum: 1
                description: >
                  Hard ceiling. Match the server limit (e.g. vLLM
                  generation-config max_new_tokens).
              on_truncation:
                $ref: "./fragments/llm-truncation.schema.yaml"
              context_window:
                type: integer
                minimum: 1
                description: >
                  Input context-window override for the resolved endpoint. A
                  model-level context_window takes precedence; otherwise the
                  runtime model catalog is used.
              pricing:
                type: object
                additionalProperties: false
                description: >
                  Optional operator pricing override in USD per million tokens.
                  Values replace the model catalog rates for cost estimates.
                properties:
                  input_per_million_tokens:
                    type: number
                    minimum: 0
                  cached_input_per_million_tokens:
                    type: number
                    minimum: 0
                  output_per_million_tokens:
                    type: number
                    minimum: 0

      endpoints:
        description: "Application: map of id → endpoint. LLMProxy: may be a list with name field."
        oneOf:
          - type: object
            additionalProperties:
              type: object
              required: [url, protocol]
              additionalProperties: true
              properties:
                url:
                  type: string
                  format: uri
                protocol:
                  type: string
                expose:
                  type: boolean
                  default: false
                headers:
                  type: object
                  additionalProperties:
                    type: string
                timeout:
                  type: number
                  exclusiveMinimum: 0
          - type: array
            items:
              type: object
              additionalProperties: true

      # ── ToolServerRegistry ─────────────────────────────────────────────────────
      tool_servers:
        type: array
        items:
          $ref: "./fragments/infra-tool-server.schema.yaml"

      # ── ToolRegistry ────────────────────────────────────────────────────
      tools:
        description: "ToolRegistry: logical tool-set id → JSON index path. ToolProvider: semantic tool bindings."
        oneOf:
          - type: array
            items:
              type: object
              required: [id]
              additionalProperties: false
              properties:
                id:
                  type: string
                path:
                  type: string
                description:
                  type: string
          - type: object
            additionalProperties:
              $ref: "./fragments/infra-tool-binding.schema.yaml"

      # ── PersonalSecrets ─────────────────────────────────────────────────
      token_refs:
        type: array
        items:
          type: object
          required: [id, api_key_env]
          additionalProperties: true
          properties:
            id:
              type: string
            api_key_env:
              type: string
            description:
              type: string

      # ── Datastore ───────────────────────────────────────────────────────
      stores:
        type: object
        description: "Named datastore connections (Neo4j, ClickHouse, filesystem, …)."
        additionalProperties:
          type: object
          additionalProperties: true
          properties:
            type:
              type: string
            uri:
              type: string
            host:
              type: string
            port:
              type: integer
            path:
              type: string
            user:
              type: string
            password_env:
              type: string
            database:
              type: string
            description:
              type: string
            artifacts:
              type: array
              description: "Artifact kinds this store handles (otel_traces, events, kg, …)."
              items:
                type: object
                properties:
                  kind:
                    type: string
                  ops:
                    type: array
                    items:
                      type: string
                      enum: [read, write, query]

      # ── ServiceBundle ───────────────────────────────────────────────────
      services:
        type: object
        description: >
          Named processes or compose stacks started by mas-lab
          ``service_start`` / ``service_stop`` (not LLM ``--infra-ref``).
        additionalProperties:
          type: object
          additionalProperties: true
          properties:
            description:
              type: string
            backend:
              type: string
              enum: [process, docker-compose, catalogue]
            command:
              oneOf:
                - type: array
                  items:
                    type: string
                - type: string
            compose_file:
              type: string
            auto_start:
              type: boolean
            env:
              type: object
              additionalProperties: true
            health_check:
              type: object
              additionalProperties: true
              properties:
                url:
                  type: string
                timeout:
                  type: integer
            triggers:
              type: object
              additionalProperties: true

      # ── SecretsProvider ─────────────────────────────────────────────────
      provider:
        type: string
        description: "Secrets backend id (e.g. env, vault)."
      params:
        $ref: "./fragments/infra-middleware-params.schema.yaml"
