Skip to main content

Console client contract

All console methods are generated from every service in backplane.console.v1, including ObsService and AuditService. Imports use @backplane-labs/backplane-api; transport/session ownership is in @backplane-labs/backplane-client; React integration is in @backplane-labs/backplane-react. Packages live in this repository and target GitHub Packages in the backplane-labs organization. Nothing is published to npm by development/build commands.

Authentication and transport​

The host owns one BackplaneClient and passes it through BackplaneProvider. Embedded modules consume useClient(ServiceClient) and never log in, dispose the host client, or create their own root router. Standalone modules receive an explicit author-supplied fixture/runtime. They do not need backplane.

import { create } from '@bufbuild/protobuf';
import { CatalogServiceClient, GetServiceRequestSchema } from '@backplane-labs/backplane-api';
import { BackplaneClient } from '@backplane-labs/backplane-client';

const runtime = new BackplaneClient({ baseURL: `${location.origin}/backplane` });
await runtime.start(); // checks the existing HttpOnly cookie
// The host presents login when runtime.getSnapshot().connection === 'anonymous'.
// await runtime.login(operatorToken); the token is never persisted by the client.
const api = runtime.client(CatalogServiceClient);
// Invoke after connection === 'connected'.
const service = await api.getService(create(GetServiceRequestSchema, { name: 'hello' }), {
signal: new AbortController().signal, timeoutMs: 10_000,
});
console.log(service);
// The host calls runtime.dispose() on application teardown.

baseURL includes the console deployment prefix. HTTP auth endpoints are auth/login, auth/session, auth/logout; WebSocket is ws with subprotocol wsrpc.v1. Requests use same-origin cookies. Cookie contents never enter JS. Use a same-origin development proxy for live backend access. Deployment secrets and OTLP ingest keys are not browser client options.

Confirmed HTTP 401 clears the session. Network failures, 429 and 5xx preserve it and retry with bounded jitter and Retry-After. Failed logout keeps local state so the UI does not falsely claim server-side revocation. Cookie replacement in another tab terminates the old transport/in-flight work on the next session check. WebSocket reconnect never replays a unary request. An ambiguous mutation failure must be resolved using audit/run state before a user retries it.

Connection states: idle → checking → anonymous or connecting → connected; socket failure gives reconnecting/offline, disposal is terminal. Every new socket increments connectionId. Subscribe through useConnection() or the runtime external-store API. A transient error is distinct from authentication loss and must not open a login screen over an existing session.

Values, errors and cancellation​

Every generated method accepts CallOptions: abort signal, deadline, metadata/header/trailer callbacks. Pass an AbortSignal for component lifetime and cancel on query/filter changes. A deadline can expire after a server-side mutation was committed. Server-stream iteration is lazy; always consume it with for await, and close it on unmount. The transport bounds frames to 16 MiB and its receive queues; overload surfaces explicitly rather than growing forever.

Protobuf 64-bit values are bigint; protobuf JSON encodes them as decimal strings. Use toJson/fromJson rather than JSON.stringify on protobuf objects. Obs metric values/timestamps are strings. Native trace JSON must be decoded by the lossless observability adapter; plain JSON.parse may lose integer precision. Rendering a chart may approximate numeric coordinates, but copying/exporting must use the original exact value.

WsStatusError.code is the gRPC numeric status. A successful RPC can still contain validation violations, parse errors, a failed CallResult, rule evaluation error, or dead-letter failed items. These are product results, not transport exceptions. Unknown status codes remain visible to the caller.

Common​

All methods inherit transport cancellation (Canceled 1), deadline (DeadlineExceeded 4), bounded-resource rejection (ResourceExhausted 8), connection loss (Unavailable 14) and authentication failure (Unauthenticated 16). A server handler can also fail with Internal 13. Authorization middleware can report PermissionDenied 7. Only read operations are eligible for automatic retry. See each family below for backend mappings; they are conservative service-wide sets, not a promise every method emits every code. External command wrappers additionally return Unavailable when intent could not be stored (no dispatch), and Aborted 10 when result storage failed (dispatch may have completed). See audit outcomes.

Catalog​

GetService: NotFound 5 for an unknown service. Lists return current registry snapshots. WatchCatalog can coalesce intermediate snapshots for slow consumers.

Session​

Session queries/revocations return Unavailable for database failure, InvalidArgument for a malformed session ID and NotFound for an absent session. Revocation closes the target session's active connections. Login/logout are HTTP endpoints, not generated RPCs. Never log session cookies or operator tokens.

Config​

Unavailable: registry not synchronized. NotFound: service/revision absent. FailedPrecondition: no manifest. Unexpected storage/reconciliation failures map to Internal. Validation failures are structured violations, including save/rollback responses; a nil revision with violations is not success.

Binding​

Unavailable: catalog/executor not synchronized or configured dependency unavailable. FailedPrecondition: execution is disabled by deployment. NotFound: binding/version absent. FailedPrecondition: tombstoned binding. Aborted: base_version is set and the current version is another one (an edit raced another editor; reload and reapply). Malformed run/request identifiers may return InvalidArgument. Unexpected backend failures map to Internal; executor statuses propagate. Validation violations are response fields. Test calls return CallResult.

Rule​

Binding-family statuses also apply to rules. Run commands additionally map invalid UUID/request to InvalidArgument, missing/wrong run to NotFound, and disabled execution to FailedPrecondition, configured-but-unavailable Temporal to Unavailable. Temporal statuses propagate. A test can return validation violations, an evaluation error, matched=false, or a call result; matched=false alone is a successful test with no run.

Wiring​

Unavailable: registry not synchronized. InvalidArgument: a rename without a definition. Validation violations (analysis, a refused rename) are response fields; nothing is saved.

Operations​

Event, workflow, schedule and call services map bad input to InvalidArgument, disabled execution to FailedPrecondition, an unsettled or unavailable configured backend to Unavailable, undeclared/missing subject to NotFound, and invalid operation state to FailedPrecondition. Unexpected failures become Internal. Temporal's recognized status codes propagate, so AlreadyExists, PermissionDenied, ResourceExhausted and other backend codes must remain distinguishable. Redrive/purge can partially succeed; inspect every failed sequence. Cancel is a request to workflow code; terminate ends a run without cleanup. Neither should be retried blindly after connection loss.

Audit​

InvalidArgument: bad filter, range, limit or cursor. Unavailable: database failure. Internal: malformed stored metadata. Full feed, ingest and outbox contract.

Observability​

InvalidArgument: signal/language/query/range/limit invalid. FailedPrecondition: signal or language is not enabled. NotFound: trace absent. ResourceExhausted: concurrency/size/result budget. DeadlineExceeded: query deadline. Unavailable: storage/network failure. Backend messages are sanitized. A successful response may carry truncated, partial and warnings; show those states. Full driver contract.

Errors​

InvalidArgument: bad filter, range, limit, reference or cursor. FailedPrecondition: the log store is not configured. ResourceExhausted: concurrency or size budget. DeadlineExceeded, Unavailable: the log store is slow or down; its messages are not echoed. A response may be partial with warnings; show that state. Full errors contract.

Snapshot watches​

WatchCatalog, WatchConfig, WatchBindings, WatchRules begin with a complete snapshot. Replace the previous value; do not append or interpret index gaps as missing events. useSnapshotWatch restarts on connection changes and transient stream failure, including unexpected EOF. It retains the same query's value as stale while retrying and discards it when the provider/session/query changes. Memoize its open callback. Nonretryable errors stay explicit.

const api = useClient(CatalogServiceClient);
const open = useCallback((signal: AbortSignal) =>
api.watchCatalog(create(WatchCatalogRequestSchema), { signal }), [api]);
const state = useSnapshotWatch(open);
// Render loading, ready, stale-with-value, and terminal error separately.

watchWithRetry is available without React for long-lived read watches. It retries statuses 4, 8 and 14 with abortable capped jitter. It does not retry validation, permission, authentication or cursor-expiry errors. Do not use it for a mutation or a finite stream.

Audit feed​

The audit feed is read by page; a live view repeats the first page on an interval and merges it by record ID over what it shows (new records on top, the rest kept). Older pages follow nextPageCursor with the identical filter.

const api = runtime.client(AuditServiceClient);
const filter = create(AuditFilterSchema, {
start: timestampFromMs(Date.now() - 3_600_000),
conditions: [{ target: { case: 'field', value: AuditField.SERVICE }, op: AuditOperator.IS, values: [fromJson(ValueSchema, 'iam')] }],
});
const page = await api.searchAudit(create(SearchAuditRequestSchema, { filter }), { signal });
const older = page.nextPageCursor
? await api.searchAudit(create(SearchAuditRequestSchema, { filter, pageCursor: page.nextPageCursor }), { signal })
: undefined;

The caller owns filter and signal.

Verification​

make gen updates protobuf clients, the method inventory and callable examples for all methods. yarn typecheck checks those examples. Unit tests cover cookie failures, logout, late responses, no unary replay, 64-bit round trips, stream retry/cancellation/backoff and terminal statuses. make web-check adds browser acceptance against the real Go console and service-owned bundle delivery.

Platform​

PlatformService.GetCapabilities returns configuration, independent of health. Feature names: events, workflows, schedules, bindings, rules, metrics, logs, traces, telemetry, gateway, audit, audit_export. GetInfrastructure reads cached per-replica probes with ok, checked_at, reason and required. Disabled dependencies are absent. Empty checked_at means the initial check is pending, not an observed outage. Reasons are safe codes (checking, unavailable, timeout); connection strings never leave the server. Common authentication/transport errors apply. Health is available while the control plane and its session store can serve requests.

Disabled runtime RPCs return FailedPrecondition; configured but unavailable backends return Unavailable (or a request deadline). Binding/rule definitions remain editable when their execution engine is disabled.

Administrative schedules​

Temporal stores schedules; the SDK only declares and executes workflows. CreateSchedule validates the workflow and its JSON input against the manifest, and accepts cron or a positive interval with a time zone. New schedules skip overlapping executions. GetSchedule returns an opaque revision and normalized Temporal timing. UpdateSchedule requires that revision, keeps pause/note, policies and advanced action settings, and only replaces timing when explicitly supplied. A stale or concurrently rejected update returns Aborted. Reopen the schedule before retrying. The server serializes schedule commands across replicas with a PostgreSQL advisory lock and waits for update confirmation. DeleteSchedule leaves existing workflow runs intact. Listing, reading and deleting work even after the module disappears from the registry. SDK declarations never resurrect deleted schedules or reset an operator's pause.

Mutations have durable audit intent/result. Never automatically retry them after an ambiguous transport failure; inspect the current schedule and audit first. The generated Go/TS clients call the same administrative API and need no Temporal SDK. CLI clients may use the existing authenticated console WebSocket transport.

Schedule listing uses bounded pages (default 20, maximum 50) and filters by service in Temporal. Pass next_page_token to continue with the same service filter; an empty page may still carry a token. Each request has a ten-second backend budget. Definitions remain editable when execution capabilities are disabled; the host passes these capabilities to PlatformExecutionProvider for Wiring controls.