{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://atlas/schemas/service.schema.json", "title": "Service Manifest", "description": "Schema for services//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//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//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 `_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...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)." } } } } } } } }