gRPC Contract
This page documents Orleans.Lattice.Api.State 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 grpc-contract.md, and llms.txt lists every page.Orleans.Lattice.Api.State.Grpc is a code-first gRPC binding. There is no hand-written .proto: the service and its methods are defined in C#, and its request / response messages are Orleans-serialized C# records. Most RPCs reuse the facade DTOs directly; RPCs that need a transport-specific envelope wrap the facade arguments or results in binding-owned records. The server binds the methods; the public LatticeStateApiGrpcClient calls them; both sides share identical marshallers, so the wire format stays in lock-step by construction.
Service
The service name on the wire is orleans.lattice.api.state (so each method's full path is /orleans.lattice.api.state/<Rpc>). It exposes unary and server-streaming RPCs for the remotely supported read-only facade operations, plus an unauthenticated GetAuthScheme advertisement RPC. In-process-only summary helpers on the facade are not gRPC RPCs.
| RPC | Kind | Request | Response | Surface |
|---|---|---|---|---|
ListTrees |
unary | CatalogRequest |
TreeCatalogPage |
Discovery |
ListViews |
unary | CatalogRequest |
ViewCatalogPage |
Discovery |
ListTagIndexes |
unary | CatalogRequest |
TagIndexCatalogPage |
Discovery |
ListTagValues |
unary | CatalogRequest |
TagValueCatalogPage |
Discovery |
ListCoveredTrees |
unary | CatalogRequest |
CoveredTreeCatalogPage |
Discovery |
ListIndexTags |
unary | CatalogRequest |
TagValueCatalogPage |
Discovery |
ScanTagMembers |
unary | TagMemberScanRequest |
TagMemberScanPage |
Discovery |
GetTreeStructure |
unary | StructureRequest |
StructureResponse |
Structure |
ScanEntries |
unary | EntryScanRequest |
EntryScanResponse |
Entries |
GetEntry |
unary | EntryGetRequest |
EntryGetResponse |
Entries |
GetEntryHistory |
unary | EntryHistoryRequest |
EntryHistoryResponse |
Change history |
CancelScan |
unary | EntryScanCancelRequest |
EntryScanCancelResponse |
Entries |
GetMetricsSnapshot |
unary | TreeMetricsRequest |
TreeMetricsSnapshot |
Metrics |
GetClusterInfo |
unary | ClusterInfoRequest |
ClusterInfo |
Cluster info |
GetAuthScheme |
unary | AuthSchemeAdvertisementRequest |
AuthSchemeAdvertisement |
Security |
GetDeadLetterCount |
unary | DeadLetterCountRequest |
DeadLetterCountResponse |
Dead letters |
ListDeadLetters |
unary | DeadLetterQueueRequest |
DeadLetterQueuePage |
Dead letters |
ObserveChanges |
server-streaming | StateObserveRequest |
StateChangeNotification |
Change observation |
ObserveMetrics |
server-streaming | TreeMetricsRequest |
TreeMetricsSnapshot |
Metrics |
CancelScan is a best-effort, idempotent cleanup verb: it releases the server-side snapshot cursor named by a scan continuation token, deleting the frozen per-shard baselines it captured when the scan opened, unregistering it from the write-ahead-log cursor registry, and clearing its persisted state promptly, rather than waiting for the cursor's idle TTL to do the same. That registration does not hold back WAL trimming: a snapshot cursor registers at a zero position with no blocked floor, which WAL garbage collection skips, so cancelling reclaims baseline storage and cursor state rather than WAL. A client that abandons a multi-page scan before draining it (refresh, re-filter, navigate away) should call it. Cancelling an empty token, or one that names an unknown, already-drained, or already-closed cursor, is a tolerated no-op; the empty EntryScanCancelResponse is a bare acknowledgement. A cancel naming a materialised-view (view-*) tree is a no-op too: the facade treats a view tree as reserved and returns without closing anything, so an abandoned view scan's cursor is released only by its idle TTL (LatticeOptions.CursorIdleTtl).
GetAuthScheme is the one unauthenticated RPC: it returns the endpoint's advertised authentication schemes (an ordered set of AuthSchemeDescriptor) so a client can discover how to sign in before it holds any credential. Each descriptor carries a required SchemeId (the stable id a client matches to a login provider, for example basic or entra), a DisplayName (empty by default), and Parameters - the public values the provider needs to run the challenge (empty by default). The authorization interceptor exempts this RPC; every other RPC is authorized.
Messages
Every request and response is a [GenerateSerializer] record with a stable [Alias] and sequential [Id]s, exactly like the core library's wire types. Some records are the public facade DTOs from Orleans.Lattice.Api.Abstractions; binding-owned envelopes such as DeadLetterCountRequest / DeadLetterCountResponse wrap scalar facade arguments and results so the wire contract can grow additively. The binding-owned records carry the olag. aliases held in the public GrpcStateTypeAliases class; the facade DTOs keep their ApiStateTypeAliases aliases. The marshallers serialize them with the Orleans binary serializer, so the client and the server must share an Orleans serializer registration (AddSerializer()). The records are public; the service implementation and method binding are internal.
Because the messages are Orleans records rather than protobuf messages, the contract preserves the facade model's fidelity - nullable continuation tokens, TimeSpan sample intervals, predicate trees, and value-length metadata all round-trip without a lossy .proto projection.
The public client
LatticeStateApiGrpcClient is the public typed client for the binding. It wraps a gRPC CallInvoker and exposes one method per RPC:
using Grpc.Net.Client;
using Microsoft.Extensions.DependencyInjection;
using Orleans.Serialization;
var serializerProvider = new ServiceCollection().AddSerializer().BuildServiceProvider();
using var channel = GrpcChannel.ForAddress("https://cluster.example:5001");
var stateClient = LatticeStateApiGrpcClient.Create(channel.CreateCallInvoker(), serializerProvider);
The client carries no transport policy of its own. Address, TLS, retries, deadlines, and call credentials live on the CallInvoker / GrpcChannel the caller supplies; the client only adds the per-RPC marshalling. Unary RPCs return a Task<TResponse>; the streaming RPCs return an IAsyncEnumerable<TResponse> you consume with await foreach.
Server options
LatticeStateApiGrpcOptions is populated by AddLatticeStateApiGrpc(configure) and controls the server-side binding.
| Property | Type | Default | Meaning |
|---|---|---|---|
RequireAuthorization |
bool |
true |
Enforce ILatticeStateApiAuthorizer on inbound protected state-API calls. The default is fail-closed; set to false only behind an outer authentication boundary. |
CredentialHeaderName |
string |
"authorization" |
Request-header (gRPC metadata) name carrying the caller credential token to bridge into the ambient Lattice credential. |
CredentialScheme |
string |
"Bearer" |
Authentication scheme stamped on the bridged LatticeCredential; a matching scheme prefix on the header value is stripped before the token is used. |
ActiveTenantHeaderName |
string |
"lattice-active-tenant" |
Request-header (gRPC metadata) name carrying the caller's asserted active tenant, lifted onto the ambient active-tenant context for the call. The assertion is re-validated against the caller's membership by the tenancy add-on and grants no access; set it to null or empty to disable it. |
AdvertisedAuthSchemes |
IList<AuthSchemeDescriptor> |
Empty List<AuthSchemeDescriptor> |
Public auth-scheme descriptors advertised by the unauthenticated GetAuthScheme RPC, in preference order. |
See Client for the full set of calls and Surfaces for what each request and response carries.