Every service in Hangar talks to other services, databases and brokers, and serves an API someone else depends on. Airspace shows those connections as they really are, and keeps track of the API contract each deployed version carries, so you know who a change will reach before it ships.
Who calls this service, what it calls, and the databases and brokers it owns, per environment. A row view for scanning, with a radar graph one toggle away.
The whole fleet's connections, every API with the spec each environment runs and a diff between them, and every connection that disagrees with what was declared.
Each release PR gets an api-compat check. Its verdict, the breaking changes and whether anyone uses them land on the Release Record and the Ops Wall.
Airspace never asks you to draw a diagram by hand. It reads three things Hangar already produces: traffic the platform observes, what your environment's configuration declares and owns, and the API spec signed onto each image.
Hangar already knows what kind of service you run: a Node.js, Spring Boot, Python or Go application is a different Airframe kind. That kind becomes a runtime, the runtime travels with each environment to the Helm chart, and the chart asks the OpenTelemetry Operator to inject the matching agent when the pod starts.
Calls between services, calls into databases and caches, and messages through a broker, each with a rate, a p95 latency and an error count. Traffic arriving from outside appears as user. Services are named <service>-<environment>, so dev and prod never blur together.
Node.js, Java and Python services get an agent automatically. Go services are recognised but not injected, because the Go agent needs a privileged sidecar; they can still send traces with the OpenTelemetry SDK. Every trace is kept, so a quiet connection still shows up.
Observed traffic is compared with what your environment declares. Peers listed in the NetworkPolicy settings count as declared. Components your service owns, such as its PostgreSQL or Redis, come from components[] and need no declaration at all.
| State | Meaning | What to do |
|---|---|---|
| Declared · seen | Declared, and traffic flows. | Nothing. |
| Seen, not declared | Traffic flows, but nothing declares it. | Declare the peer, or remove the call. A NetworkPolicy built from declarations would block it. |
| Declared · no traffic | Declared, but silent in the window. | Check whether the caller still exists. In a quiet environment this can be normal. |
| Via broker | Messages pass through a broker such as RabbitMQ. | Nothing. Shown as a direct publisher-to-consumer edge. |
| Owned | A component the service's own resources create. | Nothing. Known from Crossplane. |
Backstage-style API catalogs read a spec from the main branch, which says what the code will do someday. Hangar signs the spec onto the image at build time instead. Each environment then carries the exact contract of the version it is running, and dev and prod can be compared directly.
api.spec means the service publishes no API. A malformed file or a signing problem is reported, and the build still passes.Put an OpenAPI or AsyncAPI file at the repo root as openapi.yaml. That is the default, so nothing else is needed. Name another path in cicd.yaml if you keep it elsewhere.
api: spec: openapi.yaml # default; YAML or JSON type: openapi # openapi | asyncapi
The spec as JSON, its file hash, its info.title and info.version, and the format version, under the predicate type https://hangar.io/attestations/api-spec/v1. Rebuilding the same image never attaches a second copy. Tower verifies the signature the same way it verifies build provenance and SBOMs, and labels an unverified spec as unverified.
When Glidepath opens a release PR, the api-compat check compares the candidate image's spec with the spec of the image already running in the target environment, not with the previous build. Skipped versions are compared correctly. It verifies both signatures, diffs the two with oasdiff, and looks up which operations are in use there.
| Verdict | When | Blocks the merge? |
|---|---|---|
| pass | No breaking change between the running spec and the candidate. | No |
| warn | Breaking changes found, reported only. | No |
| block | A breaking change to an operation seen in use, with blocking switched on and no owner override. | Yes, when the check is required |
| overridden | An owner accepted the breaking change. Who, when and why are recorded. | No |
| no-spec | The candidate image carries no spec. | No |
| no-baseline | First release, or the running image has no spec yet. | No |
| unsupported | AsyncAPI, which is not diffed yet. | No |
/api-break-ok <reason> on the release PR./retest api-compat-.On the release PR as a comment with the full report. On the Release Record as an API compatibility section. In Airspace as a "breaking change pending" item. On the Ops Wall as a Needs-Attention entry while the PR is open with a warn or block.
Traces show which operation a caller uses, not which response fields it reads. Field-level certainty needs consumer contract tests.
A connection with no traffic in the window may be idle, not gone. Low-traffic environments produce more "no traffic" rows.
Nothing yet checks the spec against the routes a service really serves. Keep the file in step with the code.
Tower's own diff between environments uses a small rule set. The release check's oasdiff report is the authoritative one, and Tower shows it whenever a release PR has run the check.
| Setting | Where | Owner |
|---|---|---|
api.spec, api.type | cicd.yaml in the service repo | Service team |
observability.namespace, .instrumentation | The cluster's cluster-defaults.yaml, rendered by Apron | Platform team |
observability.runtime | Derived from the service's Airframe kind | Automatic |
apiCompat.blockWhenInUse | Glidepath catalog values, per cluster | Platform team |
apiCompat.usage.<cluster>.url | Glidepath catalog values: where the check reads usage | Platform team |
api-compat required | The release guardrail registry | Platform team |