Orleans.Lattice.Api.Telemetry.Grpc
This page documents Orleans.Lattice.Api.Telemetry.Grpc 9.9.0, in the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at README.md, and llms.txt lists every page.The gRPC binding for Orleans.Lattice.Api.Telemetry.
It exposes the telemetry facade to a remote head - notably the Explorer, which
reaches the cluster over this binding, cannot enforce tenant scoping locally, and
so must be served by a routable, server-scoped endpoint.
Reference closure
This package references only the shared contract package,
Orleans.Lattice.Api.Abstractions. It does not reference the facade
implementation, and it reaches no MCP package transitively. That closure is
asserted rather than assumed: the test suite walks the whole ProjectReference
graph, sweeps transitive PackageReference ids, and inspects the emitted
assembly references.
It also asserts that the client's reachable public surface contains no Orleans grain interface, so a head that consumes this binding never takes a dependency on the cluster's internal grain contracts.
Service
Service name orleans.lattice.api.telemetry, three unary RPCs:
| RPC | Request -> Response | Notes |
|---|---|---|
GetCatalog |
TelemetryCatalogRequest -> TelemetryQueryCatalog |
What this deployment offers. |
Query |
TelemetryQueryRequest -> TelemetryQueryResponse |
Carries the contract's own messages unchanged. |
GetAuthScheme |
AuthSchemeAdvertisementRequest -> AuthSchemeAdvertisement |
Unauthenticated; exempt from the interceptor so a client can discover how to authenticate. |
Query deliberately reuses the contract's own request and response types rather
than defining binding-specific ones. That is a deliberate constraint: with no
binding-owned query message, the wire can never grow a free-text query field or a
second tenant assertion, so the facade's "name a query id, never supply PromQL"
guarantee holds at the transport too.
Hosting
A host composes two packages: the facade, and this binding. They are registered
separately and deliberately cannot be demonstrated in one compiled snippet -
this package does not reference the facade, which is the closure described
above, so a sample compiled against the binding alone cannot name
AddLatticeTelemetryApi().
First register the facade, from
Orleans.Lattice.Api.Telemetry:
builder.Services.AddLatticeTelemetryApi();
Then the binding, which is what this package provides:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Orleans.Lattice.Api.Telemetry.Grpc;
var builder = WebApplication.CreateBuilder();
// RequireAuthorization defaults to true; the default authorizer denies.
builder.Services.AddLatticeTelemetryApiGrpc();
var app = builder.Build();
app.MapLatticeTelemetryApiGrpc();
AddLatticeTelemetryApi() is idempotent, so ordering between the two is not
load-bearing. AddLatticeTelemetryApiGrpc() is not idempotent in one respect: every
other registration it makes is a TryAdd, but each call appends the authorization
interceptor to the gRPC pipeline again, so call it once.
The binding resolves on a host with only ILatticeTelemetry registered - no
access gate, no membership context, no tenant-context resolver. A constructor
guard pins that, so adding a hidden dependency later fails loudly rather than
silently raising the bar for every host.
Client
LatticeTelemetryApiGrpcClient.Create(CallInvoker, IServiceProvider) builds the
client over a caller-supplied CallInvoker and a service provider with Orleans
serialization registered (AddSerializer()), so its marshallers match the
server's. It exposes the three RPCs as GetCatalogAsync,
QueryAsync(TelemetryQueryRequest), and GetAuthSchemeAsync, and carries no
transport policy of its own: address, TLS, retries, deadlines, and call
credentials live on the CallInvoker / GrpcChannel. It forwards the visibility
the caller requests and returns the facade's pinned Scope unchanged - render
that scope, never the one that was asked for.
Authorization
| Seam | Default | Purpose |
|---|---|---|
ILatticeTelemetryApiAuthorizer |
DenyTelemetryApiAuthorizer |
Fail-closed. Registered with TryAdd, so a host must deliberately replace it - with a custom authorizer, or with the opt-in AllowAllTelemetryApiAuthorizer behind a separate authentication boundary. Permitting a call never widens what the facade scopes the caller to. |
ILatticeTelemetryApiCredentialBridge |
header-based | Carries an opaque caller credential onto the ambient context for the duration of the call. |
ILatticeTelemetryApiAuthSchemeSource |
options-based | Backs the unauthenticated GetAuthScheme probe. |
The interceptor is scoped to this service's method prefix, exempts
GetAuthScheme, and maps an unrecognised method to LatticeTelemetryApiOperation.Unknown
so a deny-by-default policy refuses it rather than falling through. The authorizer
receives a LatticeTelemetryApiAuthorizationContext carrying the underlying
ServerCallContext (Call), the LatticeTelemetryApiOperation (GetCatalog, Query,
or Unknown), and a TargetId that is the requested query id for Query and null
for GetCatalog.
Server options (LatticeTelemetryApiGrpcOptions)
| Property | Type | Default | Meaning |
|---|---|---|---|
RequireAuthorization |
bool |
true |
Whether the interceptor enforces ILatticeTelemetryApiAuthorizer on every inbound call. Set false only when an outer authentication boundary already guards the endpoint. |
CredentialHeaderName |
string |
authorization |
Request header carrying the caller's credential token, bridged into the ambient Lattice credential. The default header bridge reads it on every GetCatalog and Query call, whether or not the Orleans.Lattice.Auth add-on is registered; the facade's own gate resolves the caller's subject from it. |
CredentialScheme |
string |
Bearer |
Scheme stamped on the bridged credential; a matching case-insensitive prefix on the header value is stripped. |
ActiveTenantHeaderName |
string |
lattice-active-tenant |
Request header carrying the tenant the caller is acting as, lifted onto the ambient active-tenant context per call. Only carried: it is re-validated downstream and the facade derives the effective tenant server-side, so it can never widen a caller's scope. |
AdvertisedAuthSchemes |
IList<AuthSchemeDescriptor> |
empty | Auth schemes the unauthenticated GetAuthScheme RPC advertises, in preference order; public configuration only. |
The binding derives no tenant
It relays. RequestedVisibility and RequestedTenantId are forwarded verbatim,
and the facade's resulting Scope is returned untouched - including
WasDowngraded and IsCrossTenant, so a client can tell that it received less
than it asked for.
A reflection guard fails the build if any member named *ResolveTenant*,
*DeriveTenant*, *EffectiveTenant* or *DefaultTenant* ever appears in this
assembly. The authorizer's target id is the query id, never a wire-supplied
tenant.
Status mapping
| Exception | Status | Why |
|---|---|---|
TelemetryQueryNotFoundException |
NotFound |
Caller error. Unknown and unoffered stay indistinguishable. |
TelemetryQueryBoundsException |
OutOfRange |
Well-formed request; the guardrails refuse the window. |
TelemetryBackendException |
Unavailable |
Not the caller's fault - the retryable-with-backoff code. |
LatticeAuthorizationDeniedException, LatticeTenantAccessDeniedException |
PermissionDenied |
A denial, not an internal fault. |
ArgumentException |
InvalidArgument |
|
OperationCanceledException |
Cancelled |
|
| anything else | Internal |
The original message is suppressed. |
Two deliberate non-translations, both tested: an unconfigured backend arriving as
NotFound stays NotFound and is not upgraded to Unavailable, and a
capability denial never collapses into NotFound. A catalogue that offered
nothing cannot then refuse a query for a different-looking reason.
The backend fault's detail is not forwarded. Its message embeds the underlying transport fault, which routinely carries the backend host and port; this facade is routable and its callers are untrusted heads, so the real reason is logged server-side and the caller receives a fixed detail naming only the query id it already supplied.
Wire aliases
Serializable types in this package use the reserved oitlg. alias prefix, which
is disjoint from the contract's oitl. set; the constants live in the public
GrpcTelemetryTypeAliases class. Four aliases only - TelemetryCatalogRequest,
AuthSchemeAdvertisementRequest, AuthSchemeDescriptor, and
AuthSchemeAdvertisement - so the binding adds nothing else to the wire. Aliases are
wire format: never rename or remove one.
See also
Orleans.Lattice.Api.Telemetry- the facade this binding exposes.