Components
On this page
This page describes the module boundaries and package layout. The language is Go, per ADR 0003.
Relay is one process with a small number of internal boundaries. The boundaries exist so that the parts most likely to change, the wire protocol and the HTTP contract, can change without dragging the rest with them.
The shape
Executors connect outbound over WebSockets. Clients, the dashboard, and a metrics scraper connect inbound over HTTP. Between them sit a connection hub and a router; behind them sits Relay's own small Postgres database.
flowchart LR
subgraph Inbound["Executors"]
E1["Executor 1"]
E2["Executor 2"]
E3["Executor N"]
end
subgraph RelayCore["Relay Core"]
GW["Gateway (WS)"]
HUB["Hub (Correlation)"]
RTR["Router (Local / Peer)"]
API["HTTP API"]
end
subgraph Consumers["Control and Monitoring"]
CLI["Control Client"]
DASH["Dashboard"]
METRICS["Metrics Scraper"]
end
subgraph Storage["Relay Postgres"]
DB[("Relay Postgres\n• Liveness and recovery\n• Ownership lookup\n• Operational store")]
end
E1 -->|ws| GW
E2 -->|ws| GW
E3 -->|ws| GW
GW --> HUB
HUB --> RTR
RTR --> API
API -->|http| CLI
API -->|http| DASH
API -->|http| METRICS
HUB -.-> DB
RTR -.-> DB
API -.-> DB
Modules
Protocol (internal/protocol). Types and a codec for the executor
WebSocket protocol, with no input or output of its own. Written from the
protocol specification rather than lifted from an SDK, and versioned.
Hub (internal/hub). Accepts connections, authenticates them, registers
the executor, and multiplexes requests and responses over each connection by
request identifier. It handles pings, deadlines, out-of-order replies, and
unsolicited messages.
Liveness (internal/liveness). The executor state machine and its timers,
plus recovery dispatch and confirmation. See recovery.
Router (internal/router). Given an organisation and an application,
picks a healthy executor. If that executor's connection is owned by another instance, the
request is forwarded to that instance over HTTP with a signed internal header.
API (internal/api). The HTTP server, generated from the vendored OpenAPI
document by oapi-codegen, bound to the standard library's router. The
handlers are deliberately thin: validate, authorise, then either read the
store or dispatch through the router. No hand-written JSON, so the contract
cannot drift from the specification.
Store (internal/store). Relay's own schema, migrations, and typed
queries, generated by sqlc over pgx. It holds
organisations, applications, API keys, executors, instances, alert rules,
audit entries, and metric samples. It holds no workflow data.
Auth (internal/auth). API keys, hashed at rest and scoped to
applications and permissions, and OIDC token validation for the operations
that require it.
Metrics (internal/metrics). An OpenMetrics endpoint over executor and
request counters.
Dashboard (served by internal/dashboard). The web interface,
talking only to the HTTP API. It has no database access, ever. In accordance
with project terminology conventions, Relay's web interface is named the
dashboard; Console refers strictly to the upstream vendor product.
Rules the boundaries encode
- Relay's database is small and its own. Workflow data is fetched from
executors per request and is not cached. When an application configures a
data-plane connection,
internal/dataplaneprovides fallback reads via the official SDK client if no healthy executor is available. - Every executor-served request carries a deadline, and failure maps onto the status codes the published specification defines.
- One executor connection has exactly one owning instance. Ownership and its lease live in the database, so an instance that dies releases its executors by lease expiry rather than by cleanup.
- Correlation is by request identifier, and the hub tolerates messages it did not ask for.