bootstrapper/schemas/service.schema.json
Rama main · réplica de lectura del repositorio original.
{
"$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)."
}
}
}
}
}
}
}
}