Ctrl K
ComunidadGuías…

bootstrapper/schemas/service.schema.json

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://atlas/schemas/service.schema.json",
  "title": "Service Manifest",
  "description": "Schema for services/<name>/service.yml. Each file is the single source of truth for one service family — what previously lived split across docker-compose.yml, .env.example, and bootstrapper/service-configs.yml.",
  "type": "object",
  "additionalProperties": false,
  "required": ["name", "label", "category", "env", "capabilities"],
  "properties": {
    "virtual": {
      "type": "boolean",
      "default": false,
      "description": "If true, this manifest has no compose fragment. Used for logical groupings like cloud-providers (routed through LiteLLM, no container of their own). Virtual manifests MAY omit `containers` and MUST NOT have a services/<name>/compose.yml."
    },
    "name": {
      "type": "string",
      "description": "Folder name under services/. Must match the parent directory name. Kebab-case.",
      "pattern": "^[a-z][a-z0-9-]*[a-z0-9]$"
    },
    "label": {
      "type": "string",
      "description": "Human-readable label shown in the TUI wizard."
    },
    "category": {
      "type": "string",
      "enum": ["infra", "data", "llm", "media", "agents", "apps"],
      "description": "Wizard grouping. infra=gateway/observability; data=DB/cache/storage; llm=LLM gateway/engine; media=multimodal AI (image-gen/STT/TTS/doc/search); agents=programmable AI agents/workflows; apps=user-facing UIs."
    },
    "doc_extras": {
      "type": "object",
      "additionalProperties": false,
      "description": "Optional doc-generation hints. Not consumed at runtime.",
      "properties": {
        "diagram": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "extra_consumers": {
              "type": "array",
              "items": {"type": "string"},
              "description": "Manual escape hatch — service names to draw on the downstream tier in this service's architecture diagram, for wiring not expressible in depends_on (e.g. Kong routing)."
            }
          }
        }
      }
    },
    "data_flow": {
      "type": "object",
      "additionalProperties": false,
      "description": "Runtime data-flow declarations consumed by the diagram resolver. Independent of depends_on (which expresses bootstrap order).",
      "properties": {
        "calls": {
          "type": "array",
          "items": {
            "oneOf": [
              { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
              {
                "type": "object",
                "additionalProperties": false,
                "required": ["target"],
                "properties": {
                  "target": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
                  "status": {
                    "enum": ["current", "optional", "planned"],
                    "description": "current (default): the call happens whenever both services run. optional: it happens only under the condition. planned: documented or intended, not wired yet."
                  },
                  "condition": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "When an optional edge exists: a SOURCE expression (e.g. MCP_SERVERS_SOURCE=container) or a short free-text condition."
                  },
                  "evidence": {
                    "type": "string",
                    "pattern": "^(https://[^\\s]+|[A-Za-z0-9_][A-Za-z0-9_./-]*(:[0-9]+(-[0-9]+)?)?)$",
                    "description": "Where the call is shown: a repo-relative path, path:line, or an https URL. Repo paths must exist."
                  }
                },
                "allOf": [
                  {
                    "if": { "properties": { "status": { "const": "optional" } }, "required": ["status"] },
                    "then": { "required": ["condition"] }
                  },
                  {
                    "if": { "required": ["condition"] },
                    "then": { "required": ["status"], "properties": { "status": { "enum": ["optional", "planned"] } } }
                  }
                ]
              }
            ]
          },
          "uniqueItems": true,
          "description": "Services this one calls at runtime in the request path. Init-time bootstrap calls do NOT count. An entry is either a plain name, which is a current edge, or an object with target plus optional status, condition and evidence. Targets must match either a doc-folder name under services/ or a manifest name under services/, and each target appears once."
        }
      }
    },
    "support": {
      "type": "object",
      "additionalProperties": false,
      "required": ["tier", "evidence", "evidence_revision"],
      "description": "Operator-facing support tier and the evidence it rests on. Selectable is not validated: a family is `stable` only with a cited qualification run.",
      "properties": {
        "tier": {
          "type": "string",
          "enum": ["stable", "experimental", "community", "unsupported"],
          "description": "stable = qualified cold-start/workflow/upgrade evidence at evidence_revision; experimental = selectable, contract declared, not qualified; community = maintained outside the core team; unsupported = kept for reference only."
        },
        "evidence": {
          "type": "string",
          "minLength": 12,
          "pattern": "^(?!.*[\\r\\n\\u000B\\u000C\\u0085\\u2028\\u2029])(?=.*\\S).+$",
          "description": "Single-line pointer to the qualification evidence (issue, PR, report) or an honest statement that none exists yet."
        },
        "evidence_revision": {
          "type": "string",
          "pattern": "^(v[0-9]+\\.[0-9]+\\.[0-9]+|[0-9a-f]{40})$",
          "description": "Release tag or full commit id the evidence was gathered at; staleness is judged against the release record."
        },
        "owner": {
          "type": "string",
          "minLength": 1,
          "description": "Who maintains the family's promise."
        },
        "limitations": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "pattern": "^(?!.*[\\r\\n\\u000B\\u000C\\u0085\\u2028\\u2029])(?=.*\\S).+$"
          },
          "description": "Single-line known limitations that keep the family below stable."
        }
      }
    },
    "capabilities": {
      "type": "array",
      "minItems": 1,
      "description": "Required operator-facing capability and limitation contract. Declaration order is presentation order.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name", "status", "verification", "note"],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "pattern": "^(?!.*[\\r\\n\\u000B\\u000C\\u0085\\u2028\\u2029])(?=.*\\S).+$",
            "description": "Concise, single-line operator-facing capability label."
          },
          "status": {
            "type": "string",
            "enum": ["supported", "partial", "stubbed", "not-supported"],
            "description": "Runtime support status."
          },
          "verification": {
            "type": "string",
            "enum": ["tested", "documented", "untested"],
            "description": "How Atlas verifies the capability claim."
          },
          "note": {
            "type": "string",
            "minLength": 1,
            "pattern": "^(?!.*[\\r\\n\\u000B\\u000C\\u0085\\u2028\\u2029])(?=.*\\S).+$",
            "description": "Single-line explanation of the useful boundary or limitation."
          }
        }
      }
    },
    "docs": {
      "type": "string",
      "minLength": 1,
      "pattern": "^(?!.*[\\r\\n\\u000B\\u000C\\u0085\\u2028\\u2029])(?=.*\\S).+$",
      "description": "Optional repository-relative pointer to the service's Markdown documentation, e.g. services/<name>/README.md. The cross-manifest validator requires this path or a sibling README.md to exist safely unless docs_exception is present."
    },
    "docs_exception": {
      "type": "string",
      "minLength": 30,
      "maxLength": 500,
      "pattern": "^(?!.*[\\u0000-\\u001F\\u007F-\\u009F\\u2028\\u2029])(?=.*\\S).+$",
      "description": "Exceptional reason this manifest has no operator-facing documentation. Use only when neither docs nor a sibling README.md exists. Must be a printable single line with an explicit 'because' clause and at least four substantive rationale words, including at least three distinct terms; Unicode controls and generic or link-only placeholders are rejected."
    },
    "extra_kong_aliases": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*\\.localhost$"
      },
      "description": "Additional *.localhost Kong hostnames this service exposes BEYOND its rows[].alias (e.g. a second route to a different upstream port). Unlike rows[].alias these are not wizard rows and may be multi-label (s3.minio.localhost). They land in Topology.aliases so --setup-hosts wires them; the matching Kong route is emitted by kong_config_generator. Must be unique across all manifests + rows[].alias."
    },
    "containers": {
      "type": "array",
      "minItems": 0,
      "uniqueItems": true,
      "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
      "description": "Every container name in this family's compose.yml. The manifest_validator asserts this matches compose.yml's services keys 1:1. May be empty when `virtual: true`."
    },
    "images": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["var", "default", "container"],
        "properties": {
          "var": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]*$",
            "description": "Env var name that holds the image reference (interpolated as ${VAR} in compose.yml)."
          },
          "default": {
            "type": "string",
            "description": "Default image reference pinned to an exact release tag or immutable sha256 digest (e.g. 'ollama/ollama:0.18.3')."
          },
          "container": {
            "type": "string",
            "description": "Which container in this manifest uses this image. Must appear in `containers`."
          },
          "notes": {
            "type": "string"
          },
          "platform": {
            "type": "string",
            "pattern": "^linux/(amd64|arm64)$",
            "description": "Optional scanner/runtime platform when the image is single-architecture. Omit only for images supported and scanned on both linux/amd64 and linux/arm64."
          }
        }
      }
    },
    "sources": {
      "type": "object",
      "additionalProperties": false,
      "required": ["var", "default", "options"],
      "description": "Optional SOURCE-variant block. Omit for services without source variation (e.g. redis, kong).",
      "properties": {
        "var": {
          "type": "string",
          "pattern": "^[A-Z][A-Z0-9_]*$",
          "description": "SOURCE env var name (e.g. LLM_PROVIDER_SOURCE)."
        },
        "default": {
          "type": "string",
          "description": "Default source id. Must match one of options[].id."
        },
        "auto_prefer": {
          "type": "array",
          "minItems": 1,
          "description": "Ordered preference list driving `<SVC>_SOURCE: auto` resolution. The first entry whose requires_capability holds on the host (and whose option is offered under the active deployment profile) wins. An entry without requires_capability is always eligible — put the terminal fallback (e.g. container-cpu) last. Omit for services that should not support `auto`.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id"],
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9-]*$",
                "description": "A source id. Must match one of options[].id."
              },
              "requires_capability": {
                "enum": ["apple_silicon", "nvidia_gpu", "host_ollama"],
                "description": "Host capability gating this entry (probed by services/host_capabilities.py). Omitted = always eligible."
              }
            }
          }
        },
        "options": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id", "label"],
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9-]*$",
                "description": "Source value (e.g. 'ollama-container-cpu')."
              },
              "label": { "type": "string" },
              "requires": {
                "type": "array",
                "items": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$" },
                "description": "Env vars that MUST be non-empty when this source is selected."
              },
              "profiles": {
                "type": "array",
                "items": { "enum": ["default", "prod"] },
                "uniqueItems": true,
                "description": "Deployment profiles this option is offered in. OMITTED = all profiles. [\"default\"] = dev-only (hidden in the wizard and rejected by the validator under --profile prod); [\"prod\"] = prod-only."
              }
            }
          }
        }
      }
    },
    "env": {
      "type": "array",
      "minItems": 0,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name"],
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]*$",
            "description": "UPPER_SNAKE_CASE env var name. Must be globally unique across all manifests."
          },
          "default": {
            "description": "Default value emitted into .env.example. Scalar only.",
            "anyOf": [
              { "type": "string" },
              { "type": "integer" },
              { "type": "number" },
              { "type": "boolean" }
            ]
          },
          "description": { "type": "string" },
          "auto_managed": {
            "type": "boolean",
            "default": false,
            "description": "If true, the variable is computed at runtime (by bootstrapper/services/service_config.py based on the active source's runtime_sc entry) and is NOT emitted into .env.example with a default. Still documented."
          },
          "secret": {
            "type": "boolean",
            "default": false,
            "description": "If true, the value is never logged in plain text. A non-empty default is still written to .env.example, so use default: \"\" for real secrets."
          },
          "bootstrapper_only": {
            "type": "boolean",
            "default": false,
            "description": "When true, this var is consumed only by the host-side bootstrapper; .env.example generator skips it."
          }
        }
      }
    },
    "depends_on": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "required": {
          "type": "array",
          "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
          "description": "Other service-manifest names this one CANNOT run without. Logical dependency — compose-level depends_on still lives in compose.yml."
        },
        "optional": {
          "type": "array",
          "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" }
        }
      }
    },
    "exports": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name", "consumers"],
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]*$",
            "description": "Env var this manifest produces and other services read."
          },
          "consumers": {
            "type": "array",
            "minItems": 1,
            "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
            "description": "Service-manifest names that consume the exported value."
          }
        }
      },
      "description": "Documents the cross-service env-var contract. Validator asserts each exported name appears in this manifest's env declarations (or is produced by some runtime_sc.<key>.<source>.environment block)."
    },
    "runtime_sc": {
      "type": "object",
      "description": "Per-manifest slice that sc_synthesizer.py merges into the runtime service-config dict (`source_configurable:`): per-source scale, environment, deploy and extra_hosts.",
      "additionalProperties": {
        "type": "object",
        "additionalProperties": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "scale": { "type": "integer" },
            "environment": { "type": "object" },
            "deploy": { "type": "object" },
            "extra_hosts": { "type": "array" }
          }
        }
      }
    },
    "runtime_adaptive": {
      "type": "object",
      "additionalProperties": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "adapts_to": {
            "anyOf": [
              {"type": "string"},
              {"type": "array", "items": {"type": "string"}}
            ]
          },
          "environment_adaptation": {"type": "object"},
          "extra_hosts_adaptation": {"type": ["string", "array"]},
          "failure_mode": {
            "type": "string",
            "description": "Single-sentence description of what happens when this adaptive dep is disabled or unreachable. Surfaced in the Dependencies & Integrations section's Failure mode column."
          }
        }
      },
      "description": "Per-container adaptive-service descriptors. Keyed by container name in containers[]."
    },
    "runtime_deps": {
      "type": "object",
      "description": "Per-manifest slice that sc_synthesizer.py merges into the runtime service-config dict (`service_dependencies:`): launch-time requires, optional and conditional_requires rules.",
      "additionalProperties": {
        "type": "object",
        "properties": {
          "conditional_requires": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["when", "requires"],
              "properties": {
                "when": {
                  "type": "object",
                  "minProperties": 1,
                  "propertyNames": {"pattern": "^[A-Z][A-Z0-9_]*$"},
                  "additionalProperties": {"type": "string"}
                },
                "when_defaults": {
                  "type": "object",
                  "propertyNames": {"pattern": "^[A-Z][A-Z0-9_]*$"},
                  "additionalProperties": {"type": "string"}
                },
                "requires": {
                  "type": "array",
                  "minItems": 1,
                  "uniqueItems": true,
                  "items": {"type": "string", "minLength": 1}
                },
                "auto_resolve": {"type": "boolean"},
                "error_message": {"type": "string", "minLength": 1}
              }
            }
          }
        },
        "additionalProperties": true
      }
    },
    "runtime_dependency_tiers": {
      "type": "object",
      "description": "Globals-only. Informational grouping of containers by tier (data_tier, init_tier, core_services, app_tier). The validator checks each name; nothing reads it at runtime. Compose depends_on orders startup.",
      "additionalProperties": {
        "type": "array",
        "items": { "type": "string" }
      }
    },
    "rows": {
      "type": "array",
      "minItems": 0,
      "description": "Box rows this manifest renders. Most manifests have one; supabase has seven (one per HTTP component). Replaces the legacy _SERVICES/_HOST_ALIAS/DISPLAY_NAME_OVERRIDES/SERVICE_DESCRIPTIONS/LOCALHOST_ENDPOINT_VARS constants.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["display_name", "source_var"],
        "properties": {
          "display_name": {
            "type": "string",
            "description": "Human label shown in the box and wizard prompts."
          },
          "source_var": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]*$",
            "description": "The *_SOURCE env var driving this row's source pick."
          },
          "port_var": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]*$",
            "description": "The env var holding this row's primary display port. Omit for virtual rows."
          },
          "scale_var": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]*$",
            "description": "The *_SCALE env var. Absence => always-on."
          },
          "alias": {
            "type": "string",
            "pattern": "^[a-z]([a-z0-9-]*[a-z0-9])?\\.localhost$",
            "description": "*.localhost hostname Kong virtual-host-routes. Must be unique across all manifests."
          },
          "description": {
            "type": "string",
            "description": "One-line label used as wizard prompt subtitle."
          },
          "localhost_endpoint_var": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]*$",
            "description": "Env var holding the URL when source is a localhost variant."
          },
          "localhost_port_var": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]*$",
            "description": "Env var holding the user-overridable host port for the localhost source variant. Empty for services without an overridable localhost port."
          },
          "secondary_number": {
            "type": "object",
            "additionalProperties": false,
            "required": ["env_var", "label", "default"],
            "description": "Inline secondary numeric input on the row's source prompt. Used by Prometheus (retention days). The Textual wizard mounts a SecondaryNumberInput widget; the value persists to .env on confirm. (Ray/Spark worker-count widgets are built in integration.py, not via this field.)",
            "properties": {
              "env_var": {
                "type": "string",
                "pattern": "^[A-Z][A-Z0-9_]*$",
                "description": "The env var to persist the numeric value to."
              },
              "label": {
                "type": "string",
                "description": "Short label shown next to the input (e.g. 'Worker count', 'Retention (days)')."
              },
              "default": {
                "type": "string",
                "pattern": "^-?[0-9]+$",
                "description": "Default value as a string (the wizard converts to int)."
              },
              "visible_when_source": {
                "type": "array",
                "items": { "type": "string" },
                "description": "Source values that show the input; empty/omitted means always visible."
              },
              "min": {
                "type": "integer",
                "description": "Optional minimum allowed value (inclusive)."
              },
              "max": {
                "type": "integer",
                "description": "Optional maximum allowed value (inclusive)."
              }
            }
          }
        }
      }
    }
  }
}