services.yaml is the customer-authoritative service catalog. Agents use it to know who owns each service, how it depends on other services, and where the runbook lives. Authoring it is optional in the sense that sureops will discover services from your telemetry without it — but if you’ve connected a knowledge base repo at all, services.yaml is a hard prerequisite for everything else in that repo: ingestion short-circuits with a “no contract” status when services.yaml is missing, so your runbooks are never even listed, let alone indexed, until it exists.
Minimum viable contract
Only name and git_repo are required. Everything else — including owner, runbook, tags, and depends_on above — is optional but recommended.
Field reference
Core fields
tier, type, and lifecycle are not recognized fields. If you include them, the parser ignores them and logs an “unknown field” warning — it will not error, but nothing reads the values. Use tags for a tier-like concept instead.
runbook: only does something if it points at a file inside .sureops/runbooks/ in your knowledge base repo. An external wiki URL (Confluence, Notion, an internal wiki) is stored as metadata but is never fetched — it produces zero grounding context for agents. If you want a runbook’s content to actually inform diagnosis, the content has to live in your KB repo. See Runbooks & Knowledge Base.
Extended fields (optional, additive)
These fields give agents richer context for fix-PR generation and blast-radius analysis. Teams with the simpler flat schema above continue to parse correctly — unknown nested keys produce warnings, not errors.
identifiers — cross-source aliases
Helps sureops match your contract entry with telemetry signals from different sources.
Used by the Fix PR Agent to generate accurate rollback and redeployment actions.
Used by the Fix PR Agent to locate the relevant code path.
source.repo takes precedence over git_repo when both are set — the repository node the Fix PR Agent routes against is keyed on source.repo, falling back to git_repo only when source.repo is absent. In practice, most services only set git_repo; only set source.repo too if the code the Fix PR Agent should target actually lives in a different repo than git_repo identifies.
owners — structured ownership
Richer ownership metadata for routing review requests.
deploy — deployment mechanism
Used to determine how to apply a fix (Helm rollback vs. Kustomize vs. ArgoCD).
Your health-check shape varies by service (readiness/liveness probes, a single port+protocol, etc.), so sureops stores whatever you put here as-is on the service node without interpreting it. There’s no fixed sub-schema — put whatever shape your tooling needs.
chart — top-level Helm chart path
Declared once at the top level of services.yaml (not per-service): the directory inside the source repo where the Helm chart lives. Per-service deploy details (which sub-chart to patch) still live in that service’s deploy.component_path.
Complete example
Ownership is customer-declared only
There is no automatic ownership derivation today. If you omit owner for a service, that service simply has no declared owner in the landscape graph — sureops does not read your CODEOWNERS file to infer one. (A CODEOWNERS-derived owner is a plausible future addition, but it isn’t implemented, and no agent or UI path relies on it.)
If you want ownership tracked, declare it explicitly via owner (simple, human-readable) or the richer owners.team / owners.github_team block (for review-request routing).
Backstage compatibility
Core field names are taken from the Backstage entity model. This is a one-shot import, not continuous sync: sureops’s starter generator can parse a Backstage kind: Component catalog — provided as a URL to a hosted catalog-info.yaml, pasted YAML, or an uploaded file — and use it to seed a generated services.yaml once. After that, services.yaml and catalog-info.yaml are two independent files — editing one does not update the other.
Only Component entities are imported, and only if spec.type is either absent or set to service — a Component with an explicit non-service type (e.g. website, library) is skipped. Each imported one must carry a source-location annotation so sureops can derive git_repo — without it, the importer skips the component entirely:
spec.owner, metadata.tags, metadata.description, and spec.dependsOn map onto the equivalent services.yaml fields; component names that don’t match the OTel naming rule are coerced (lowercased, invalid characters replaced with -). Fields not read by the importer (e.g. spec.lifecycle) are simply dropped from the generated services.yaml — edit the file afterward to add anything the import didn’t capture.
Conflict resolution with auto-discovery
sureops discovers services from your telemetry (OTel service names, Kubernetes labels) before they’re in your contract. But once a service is declared in services.yaml and merged, the node is locked: telemetry can still refresh its “last seen” timestamp, but it can no longer add or change any field on that node — including one you never declared. There’s no fill-in-the-gaps merge after that point; the contract entry becomes the whole record.
Provenance ranking (highest wins): manual > customer_contract > sureops_contract > telemetry > kubernetes_scan > github_scan / aws_scan. Once your services.yaml is merged into your knowledge base repo — whether you wrote it from scratch or merged a generated starter as-is — it ingests as customer_contract, the second-highest tier. (sureops_contract is a related, lower tier reserved for sureops-provisioned facts you haven’t touched, used elsewhere in the landscape graph — see Customer Landscape.)
In practice: declare everything you want present on a contract-declared service, including anything telemetry would otherwise have supplied — for example identifiers.k8s_namespace. It’s the only way a namespace ever lands on a contract-declared service, since telemetry can’t add one after the fact once the service is locked.
Validation rules
name must match OTel service name format: lowercase letters, digits, and hyphens; must start with a letter; unique within the file
git_repo is required and must be <owner>/<repo> shape — no github.com/ prefix, no full URL
depends_on (snake_case — dependsOn produces an “unknown field” warning, same as tier/type/lifecycle) must be a list of strings; references should resolve to other entries in the same file — unknown references produce a warning, not an error
tier, type, and lifecycle are not recognized fields — including them produces an “unknown field” warning, not an error, and nothing reads the value
- Nested blocks (
identifiers, image, source, owners, deploy, health) must be mappings if present
Ingest failures (a hard parse/validation error) show as banner text — “Last contract ingest failed: <reason>” — on the Knowledge Base settings page. Soft warnings (like an unknown-field notice) don’t render there; run a validation pass from the .sureops/ Repo browser (Settings → Knowledge Base → Browse in app) to see the full warnings list.