Extension seams and supporting types
This page is part of 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 extension-seams-and-supporting-types.md, and llms.txt lists every page.Part of Lattice Public API Reference.
The sections above document the call surface an application drives. The
tables below cover the rest of the public surface of the core package:
the registration helpers, the host-replaceable seams, and the value types
those seams exchange, each with a pointer to the page that documents it in
depth. AddLattice registers the core default of each replaceable seam
below - the access gate, write interceptor, value decoder, envelope codec,
merge observer, membership and tenancy resolvers, tree placement resolver,
tree ownership guard, merge-mode and origin-cluster resolvers, replication
context, WAL record
encoder, WAL provider catalog, cursor registry, and baseline WAL provider -
with TryAdd, so an implementation registered before AddLattice is
kept; an add-on package that owns a seam swaps its own implementation in.
Registration helpers
| Helper | Signature | Purpose |
|---|---|---|
AddLattice |
ISiloBuilder AddLattice(this ISiloBuilder builder, Action<ISiloBuilder, string> configureStorage) |
Registers Lattice and every core seam default; the callback registers the grain-storage provider under the supplied provider name, and that provider must enforce ETags on write. See Setup and The grain storage provider must enforce ETags. |
ConfigureLattice |
ISiloBuilder ConfigureLattice(this ISiloBuilder builder, Action<LatticeOptions> configure) and ConfigureLattice(this ISiloBuilder builder, string treeName, Action<LatticeOptions> configure) |
Options for every tree, or overrides for one tree. See Configuration. |
ConfigureLatticeGrainStorageFencing |
ISiloBuilder ConfigureLatticeGrainStorageFencing(this ISiloBuilder builder, Action<LatticeGrainStorageFencingOptions> configure) |
Configures the start-up probe that checks the grain storage provider enforces ETags: Mode (LatticeGrainStorageFencingMode.Warn by default, Reject, or Disabled) and ProbeTimeout (30 seconds). See The grain storage provider must enforce ETags. |
ConfigureLatticeTagIndexReconciliation |
(this ISiloBuilder builder, Action<LatticeTagIndexReconciliationOptions> configure) and (this ISiloBuilder builder, string indexName, Action<LatticeTagIndexReconciliationOptions> configure) |
Reconciliation options for every tag index, or overrides for one index. See Background reconciliation. |
AddWalStorage |
ISiloBuilder AddWalStorage(this ISiloBuilder builder, Func<IServiceProvider, IWalStorageProvider>? factory = null) |
Registers the baseline IWalStorageProvider. The no-factory form installs InMemoryWalStorageProvider only when no provider is registered yet; a factory replaces whatever is registered, so the host's choice is order-independent with respect to AddLattice. See WAL Storage Providers. |
AddLatticeWalStorageProvider |
ISiloBuilder AddLatticeWalStorageProvider(this ISiloBuilder builder, string key, Func<IServiceProvider, IWalStorageProvider> factory) |
Registers a named provider in the silo's IWalStorageProviderCatalog so WAL partitions can be pinned to it. The reserved default key is rejected, every silo must register the same key set, and re-registering a key is last-call-wins. See Multi-account fan-out. |
AddWalCursorRegistry |
ISiloBuilder AddWalCursorRegistry(this ISiloBuilder builder, Func<IServiceProvider, IWalCursorRegistry>? factory = null) |
See WAL consumer cursors. |
AddLatticeWalGc |
ISiloBuilder AddLatticeWalGc(this ISiloBuilder builder, Func<IServiceProvider, ILatticeWalGc>? factory = null) |
Registers the WAL garbage collector (default LatticeWalGc) and the per-silo scheduler that runs it every LatticeOptions.WalGcInterval. Idempotent. |
AddLatticeRetryPolicy |
ISiloBuilder AddLatticeRetryPolicy(this ISiloBuilder builder, Action<BoundedExponentialRetryPolicyOptions>? configure = null) |
See Idempotency keys and retry policy. |
AddOrMapShape<TKey, TValue> |
ISiloBuilder AddOrMapShape<TKey, TValue>(this ISiloBuilder builder, string treeName) where TKey : notnull where TValue : ICrdt<TValue>, new() |
Registers the (TKey, TValue) shape an OR-Map tree's accessor and receiver-side applier resolve. Registering a different pair for the same tree is a configuration error. |
AddLatticeGrainCallObservation |
ISiloBuilder AddLatticeGrainCallObservation(this ISiloBuilder builder) |
Opt-in silo-wide filter that records outstanding depth and duration for every outgoing grain call, tagged by target grain type. Idempotent. See Metrics. |
AddLatticeViews / ConfigureLatticeView |
LatticeViewsServiceCollectionExtensions |
See Materialised views. |
AddLatticeAtomicAction |
ISiloBuilder AddLatticeAtomicAction(this ISiloBuilder builder, Action<AtomicActionRegistrationBuilder>? configure = null) (LatticeAtomicActionServiceCollectionExtensions) |
Enables the generic atomic-action coordinator and allow-lists its custom handlers. See Atomic actions. |
WAL storage seam (IWalStorageProvider)
Implement IWalStorageProvider to host the write-ahead log on a custom
backend; WAL Storage Providers - Contract
is the full contract. Members with a default implementation are optional
overrides; every member takes a trailing CancellationToken.
| Member | Default implementation | Purpose |
|---|---|---|
Task AppendBatchAsync(string treeId, int shardIndex, IReadOnlyList<WalEntry> entries, CancellationToken) |
None (required) | All-or-nothing append of dense, ascending, caller-assigned offsets. |
Task AppendEncodedBatchAsync(string treeId, int shardIndex, ReadOnlyMemory<ArraySegment<byte>> encodedEntries, ReadOnlyMemory<long> offsets, IWalRecordEncoder encoder, CancellationToken) |
Decodes the segments and delegates to AppendBatchAsync |
Zero-copy append of pre-encoded payloads, with the same atomicity and offset rules. |
IAsyncEnumerable<WalEntry> ReadAsync(string treeId, int shardIndex, long fromOffsetExclusive, int maxEntries, CancellationToken) |
None (required) | Entries above fromOffsetExclusive in ascending offset order, at most maxEntries. |
Task<WalShardEncodedPage> ReadEncodedAsync(string treeId, int shardIndex, long fromOffsetExclusive, int maxEntries, IWalRecordEncoder encoder, CancellationToken) |
Drains ReadAsync and re-encodes each entry |
The same entries as pre-encoded byte segments. |
IAsyncEnumerable<WalEntry> ReadFilteredAsync(string treeId, int shardIndex, long fromOffsetExclusive, long toOffsetInclusive, int maxEntries, WalKeyFilter filter, CancellationToken) |
Drains ReadAsync over the window and applies the filter to the decoded entries - the same result, at the full decode cost |
Filtered replay read for a reader that owns filter. It examines at most maxEntries entries of the window (fromOffsetExclusive, toOffsetInclusive] and yields every entry the filter does not exclude, in full; when the last entry examined is excluded it is yielded routing-only (offset, Kind, and Key exact, every other field default) so the reader still advances past everything that was dropped. maxEntries below 1 throws ArgumentOutOfRangeException. Lets a provider drop foreign records before materialising their payloads. See Filtered replay read. |
Task<long> GetHighestOffsetAsync(string treeId, int shardIndex, CancellationToken) |
None (required) | The monotonic high-water mark of every offset ever assigned (-1 for a never-written shard). A trim must never lower it. |
Task<long> GetLowestOffsetAsync(string treeId, int shardIndex, CancellationToken) |
None (required) | The lowest still-stored offset, or -1 when the shard holds no entries. |
Task TrimAsync(string treeId, int shardIndex, long throughOffsetInclusive, CancellationToken) |
None (required) | Idempotently removes every entry at or below the offset; called by the WAL GC. |
Task EvaluateCompactionAsync(string treeId, int shardIndex, CancellationToken) |
No-op | Lets a log-structured backend reclaim dead bytes on a pass that trimmed nothing; must not change the shard's logical contents. |
Task ReconcileAsync(string treeId, int shardIndex, CancellationToken) |
No-op | Activation-time recovery hook for a backend with a multi-phase commit. |
Task<long> GetRetainedByteSizeAsync(string treeId, int shardIndex, CancellationToken) |
Returns -1 (unsupported) |
Retained logical payload bytes, for storage-usage accounting and the byte-pressure retention policy. |
Task<long> GetPhysicalByteSizeAsync(string treeId, int shardIndex, CancellationToken) |
Returns -1 (unsupported) |
Bytes physically occupied, including framing and trimmed-but-unreclaimed space. |
| Type | Role |
|---|---|
WalEntry |
readonly record struct (Offset, Mutation): the provider-boundary entry, a LatticeMutation tagged with its dense per-shard offset. |
WalShardEncodedPage |
readonly record struct (EncodedEntries, Offsets, HighestOffsetInclusive) returned by ReadEncodedAsync. Transient and not an Orleans wire type. |
WalKeyFilter |
readonly record struct: the keys a WAL reader owns - a half-open ordinal key range (LowKeyInclusive, HighKeyExclusive; null means unbounded on that side) intersected, optionally, with the virtual slots a ShardMap routes to one physical shard (VirtualShardCount, OwnedSlots). Built with WalKeyFilter(lowKeyInclusive, highKeyExclusive) or WalKeyFilter(lowKeyInclusive, highKeyExclusive, shardMap, shardIndex); a shard that owns every slot carries no shard constraint. Owns(key) tests ownership; Excludes(kind, key) is true only for a Set, Delete, or Tombstone record whose key it does not own (range deletes and saga terminals are never excluded); IsUnbounded and HasShardConstraint describe the shape, and the default filter owns every key. |
IWalRecordEncoder / OrleansBinaryWalRecordEncoder |
The single-pass codec for WAL payload bytes (Encode(in WalRecord, IBufferWriter<byte>) plus three Decode overloads) and its default Orleans-binary implementation. See WAL. |
InMemoryWalStorageProvider |
The default provider AddLattice registers: process-local, lost on restart, with native ReadFilteredAsync and byte accounting. |
IWalStorageProviderCatalog |
The silo's named directory of providers: TryGet(key, out provider), Keys, and the reserved DefaultProviderKey (default) naming the baseline provider. |
WAL garbage collection
| Type | Role |
|---|---|
ILatticeWalGc / LatticeWalGc |
Task<LatticeWalGcReport> RunOnceAsync(string treeName, CancellationToken) and its default implementation, which trims each WAL partition through the largest prefix every retention bound allows. See WAL - Trim and GC. |
LatticeWalGcReport |
Diagnostic result of one pass over TreeName: the bounds it evaluated (MinCursor, TtlCeilingHlc, CausalStable, BlockedFloor), ShardsScanned (the WAL partitions whose provider resolved and were visited; a partition whose pinned provider key does not resolve on this silo is skipped and not counted, so 0 means none could be visited rather than an empty WAL), EntriesTrimmed, the byte-pressure fields (ByteCeiling, RetainedBytesBefore / RetainedBytesAfter, LogicalRetainedBytes, BytePressureTriggered, BytePressureOverThreshold, CeilingUnsatisfiable), and why the cursor floor held (CursorFloorState, BlockingConsumerId, BlockingConsumerIds, RetainedBacklog). |
WalGcCursorFloorState |
Whether the consumer-cursor trim branch was usable: Available, NoCursorReported, or BlockedByUnusablePin. |
WalGcBlockingPinState |
The durable-pin state of a consumer the GC singled out as blocking: CheckpointedUncovered, NeverCheckpointed, NoDurableState, Unreadable, Orphaned, or CheckpointedCoverageUnknown. See Metrics. |
TreeWalUsageReport |
The cheap WAL-only storage report (TreeId, WalRetainedBytes, WalPhysicalBytes, Partial, SampledAt) behind ILatticeAdmin.PollWalUsageAsync. |
Access gate and write interception
| Type | Role |
|---|---|
ILatticeAccessGate |
ValueTask<LatticeAccessDecision> AuthorizeAsync(in LatticeAccessRequest request, CancellationToken cancellationToken = default), consulted at the data-plane choke point before a read, write, delete, range, CRDT, or lifecycle operation. The core default allows everything. See Access-gate operation flags. |
LatticeAccessRequest |
TreeId, Operation (a LatticeOperation), Subject (a LatticeSubject), and the optional Key, RangeStart, and RangeEnd. |
LatticeAccessDecision |
Allow(), Deny(reason), or Filtered(predicate, reason?) - an allow with a per-key KeyFilter the enforcement point applies to prune keys the caller may not observe. Exposes Allowed, Reason, and KeyFilter. |
LatticeSubject |
The resolved caller: SubjectId, the transitively expanded GroupIds, and optional Claims; Anonymous and System, whose ids are the AnonymousSubjectId (anonymous) and SystemSubjectId (system) constants, and IsAnonymous. |
ILatticeMembershipContext |
Resolves the ambient LatticeCredential into a LatticeSubject (ResolveCurrentAsync, TryResolveCurrent). The core default is an anonymous fallback; the Membership package contributes the real implementation. |
ILatticeWriteInterceptor |
ValueTask<LatticeWriteDecision> OnWriteAsync(in LatticeWriteRequest request, CancellationToken cancellationToken = default), consulted after the access gate authorizes a write and before the value is appended to the WAL. System-origin writes (replication apply, saga legs, view maintenance) bypass it unless InterceptsSystemOrigin returns true. The core default accepts everything. |
LatticeWriteRequest |
TreeId, Key, Value, Operation, and the optional Ttl. |
LatticeWriteDecision / LatticeWriteDecisionKind |
Accept(), AcceptTransformed(newValue), Reject(reason) (surfaced as LatticeWriteRejectedException), or DeadLetter(reason); read back through Kind, TransformedValue, and Reason. |
LatticeKeyRange |
PrefixUpperBound(prefix): the exclusive upper bound of a prefix scan under ordinal comparison, or null when none exists (an empty prefix or one made only of U+FFFF). |
Value envelopes and merge observation
| Type | Role |
|---|---|
ILatticeValueDecoder |
Read-path seam that strips or upcasts a per-value envelope just before stored bytes are returned to a client (IsActive(treeId), DecodeAsync). The core default is inactive, so the read path is unchanged. |
ILatticeEnvelopeCodec |
The merge-path complement: reports a stored value's schema-version tag (ReadVersion) and strips the version envelope from CRDT fold input (StripForFold) before it is deserialized, and never upcasts (IsActive(treeId)). The core default is inactive. |
ILatticeMergeObserver |
Post-merge hook after a per-key CRDT or LWW merge completes (OnMergedAsync(in LatticeMergeContext ctx, CancellationToken ct)), returning a LatticeMergeOutcome. The core default always accepts. |
LatticeMergeContext |
Key, TreeId, Mode, LocalValue, IncomingValue, MergedValue, LocalVersion, and IncomingVersion. |
LatticeMergeOutcome / MergeOutcomeKind |
Accept(), AcceptTransformed(mergedValue) (LWW only), or AcceptWithEvent(reason), read back through Kind, TransformedValue, and EventReason. There is deliberately no reject: the merge has already been applied. See Schema. |
Tenancy seams
The core package defines the tenancy seams so tenant-aware choke points
need no dependency on the add-on. The core defaults are no-ops - the
context resolver resolves the reserved default tenant and the placement
resolver the default placement - so a cluster without
Orleans.Lattice.Tenancy behaves as
though tenancy did not exist.
| Type | Role |
|---|---|
ITenantContextResolver |
Resolves the caller's active TenantId (ResolveCurrentAsync, TryResolveCurrent). |
ITenantAdmissionController |
Admits or refuses a tenant-scoped operation or tree creation (IsActive, IsAdmittedAsync, IsReadAdmitted, IsTreeCreateAdmittedAsync). |
ITenantEnumerationFilter |
Prunes a tree-id enumeration to the trees a tenant may observe (IsActive, Filter). |
ITenantRegionVisibilityResolver |
Resolves a tenant's per-region standing as a TenantRegionVisibilityMap (IsActive, ResolveAsync). |
ITreePlacementResolver |
Resolves a tree's TreePhysicalPlacement when it is first registered (TryResolveForRegistration, ResolveForRegistrationAsync). |
TenantId |
A DNS-label-like tenant token (Value, MaxLength 63, Default / DefaultId default, IsDefault) with Parse and TryParse. |
TreeOwnership |
A tree's ownership, always re-derived from its id and never stored (IsTenantOwned, IsPlatformOwned, Tenant; built with the static Platform or ForTenant(tenant)). |
TreePhysicalPlacement |
WalProviderKey and the optional PlacementFilter seeded into a new tree's WAL placement; Default. |
TenantRegionVisibility / TenantRegionVisibilityMap / TenantRegionResidencyStatus |
One tenant's standing in a region (IsAllowed, Status, and the derived IsResident and IsVisible), the immutable per-region map of it (Create, TryGet, Count, IsResolved, Empty, Unresolved), and the residency lifecycle (None, Provisioning, Backfilling, Online, Draining, Offline, Removed). |
LatticeTenantTrees |
The reserved t/ structural namespace (SegmentPrefix): Compose, ComposePrefix, IsTenantScoped, TryGetTenant, LocalName, and GetOwner. |
LatticeTenantExtensions |
ResolveEffectiveTreeIdAsync (an extension on ITenantContextResolver) and the two GetLatticeAsync overloads (on IServiceProvider, and on IGrainFactory with an explicit resolver) that resolve an ILattice from a tenant-local tree name. With the core no-op resolver - or with the tenancy add-on registered but no active tenant asserted, which resolves the default tenant - the bare name is returned unchanged, so behaviour is byte-for-byte as before. Under an asserted non-default tenant an unqualified name is scoped into that tenant's t/{tenant}/{name} namespace, and an already-qualified, well-formed t/ id or a _lattice_ system-tree name passes through unchanged (a well-formed foreign t/{other}/{name} is left to the tenancy access gate to adjudicate). The call fails closed with LatticeTenantAccessDeniedException when the asserted tenant fails validation against the caller's own membership, or when, under an asserted tenant and outside system origin, it names a sys- tree or a malformed t/ id that belongs to no tenant. |
LatticeActiveTenantContext / LatticeActiveTenantAssertion |
The ambient active-tenant scope (Current, IsActive, With), and the helper that lifts a caller-asserted tenant off a transport header (default lattice-active-tenant) onto it (Stamp, Resolve, DefaultHeaderName). |
LatticeTenantAdminScope / LatticeTenantAdminAuthorizer |
The platform-wide or delegated per-tenant administration scope (Platform, ForTenant, IsPlatformWide, Tenant, and the TreeScope built from the PlatformScopeId / TenantScopePrefix constants, with ToAdminRequest), and the authorizer that evaluates it as a whole-scope Admin request against the access gate, refusing a key-filtered grant (IsAuthorizedAsync, AuthorizeAsync). |
LatticeTenantLabel |
The single source of the derived tenant metric dimension (TagTenant, ForTree, ForTenant, Resolve, PlatformMeasurement, and the pre-built Platform / Default tags; PlatformTenant _platform_ for platform-owned series and DefaultTenant default). |
Tree ownership guard
The tree registry puts every alias assignment to this seam before it writes anything - a resize's swap, a shadow-cutover restore's cutover or its revert to a previous alias, a schema remediation's cutover, and an administrative set-alias, system-origin maintenance included; removing an alias does not consult it. The core default allows every alias, and the installable apps package replaces it with a guard over its tree ownership ledger. See Ownership-bounded aliasing for the full contract.
| Type | Role |
|---|---|
ITreeOwnershipGuard |
ValueTask<TreeOwnershipDecision> AuthorizeAliasAsync(string logicalTreeId, string physicalTreeId, string? derivedFrom, CancellationToken cancellationToken = default). derivedFrom is the logical tree the target was created for, read from the registry rather than supplied by the caller (null for an independent tree). An in-process service, not a grain; a guard that throws fails the alias change without writing it. |
TreeOwnershipDecision |
In-process readonly struct: Allow(), or Deny(reason) with a non-blank, caller-safe reason, read back through Allowed and Reason. The default value denies. A denial surfaces as LatticeTreeOwnershipDeniedException. |
Replication-facing seams and ambient contexts
| Type | Role |
|---|---|
ILatticeMergeModeResolver |
Resolves a tree's declared LatticeMergeMode at commit time (Resolve(treeId)); null means not replicated. See Replication modes. |
ILatticeOriginClusterIdResolver |
Supplies the local cluster id stamped on a WAL record when the mutation carries no origin (Resolve(treeId)). See WAL - Origin cluster id stamping. |
LatticeVectorClockContext / LatticeHlcOverrideContext |
Ambient scopes (Current, With) that stamp a VersionVector frontier, or a source HLC verbatim, onto the mutations the current logical call authors. |
LatticeReplayAdmissionContext / LatticeReplayAdmissionClass |
Declares a call chain Bulk (BeginBulkScope()) rather than the default Interactive when it must queue for a WAL replay permit, so the per-silo replay queue can bound and prioritise bulk walks. |
CrdtShapeRegistry |
The typed CRDT shapes a tree's writes and receiver-side applies resolve (Register, TryGet), with a global fallback for every closed-shape mode; only OR-Map needs per-tree registration (AddOrMapShape). |
CRDT support types
| Type | Role |
|---|---|
Typed delta records (LwwRegisterDelta, OrSetDelta / OrSetDeltaDot, PnCounterDelta, GCounterDelta, GSetDelta, VersionVectorDelta, MvRegisterDelta, OrMapDelta<TKey, TValue> / OrMapDeltaEntry / OrMapDeltaTombstone, RgaDelta / RgaDeltaNode, OrFlagDelta, RwFlagDelta, RwSetDelta, BoundedRegisterDelta) |
The System.Text.Json-encoded delta payloads a CRDT write carries - the deltaBytes of ApplyCrdtDeltaAsync, and the author delta on a mutation's Delta slot. See Typed CRDT delta records. |
MvRegisterEntry, OrMapEntry<TValue>, RgaNode |
The dot-tagged entries inside MvRegister, OrMap<TKey, TValue>, and Rga. See State Primitives. |
ICrdtProvenanceDecoder / CrdtProvenanceDecoderRegistry |
Turns a CRDT's stored state or author deltas into ordered element-level CrdtMemberChange events (DecodeDeltas, DecodeState) and its live members into CrdtMemberValues (DecodeCurrentValue); the registry resolves the decoder by LatticeMergeMode or shape tag (TryGet), and CrdtProvenanceDecoderRegistry.Default carries one built-in decoder per typed CRDT mode - OrSetProvenanceDecoder, PnCounterProvenanceDecoder, VersionVectorProvenanceDecoder, MvRegisterProvenanceDecoder, OrMapProvenanceDecoder, SequenceProvenanceDecoder, OrFlagProvenanceDecoder, RwFlagProvenanceDecoder, GCounterProvenanceDecoder, GSetProvenanceDecoder, RwSetProvenanceDecoder, MaxRegisterProvenanceDecoder, and MinRegisterProvenanceDecoder, each exposing a static Instance. See State API surfaces. |
CrdtMemberChange / CrdtMemberChangeKind / CrdtMemberValue / CrdtProvenanceDelta |
A decoded Added or Removed event (Element, Kind, ReplicaId, the causal Ordinal, optional WallClock), a live member (Element, ReplicaId, Ordinal), and the in-process (Delta, WallClock) input pair a decoder consumes. |
Change history, events, and diagnostics
| Type | Role |
|---|---|
EntryRevision / EntryHistorySource |
One revision of a key's timeline in an EntryHistoryPage (Hlc, Kind, SourceKey, OriginClusterId, ValuePreview, ValueLength, ValueTruncated, ValueHash, Delta, Mode, RetentionShape, EndKey, VectorClock), and which substrate served the page (None, View, WalWindow). See Change history. |
LatticeTreeEventKind / LatticeEventConstants |
The kinds a LatticeTreeEvent carries (Set, Delete, DeleteRange, SplitCommitted, CompactionTriggered, CompactionCompleted, TreeDeleted, TreeRecovered, TreePurged, SnapshotCompleted, ResizeCompleted, ReshardCompleted, AtomicWriteCompleted), and the StreamNamespace (orleans.lattice.events) events are published on. See Events. |
ShardDiagnosticReport / RecentSplit |
The per-shard entries of a TreeDiagnosticReport (ShardIndex, Depth, RootIsLeaf, LiveKeys, Tombstones, TombstoneRatio, OpsPerSecond, Reads, Writes, HotnessWindow, SplitInProgress, BulkOperationPending, SampleFailed), and one recently committed adaptive split (ShardIndex, AtUtc). The report itself carries TreeId, ShardCount, VirtualShardCount, TotalLiveKeys, TotalTombstones, Shards, RecentSplits, SampledAt, and Deep. See Diagnostics. |
LatticeStorageUsageMetrics |
The process-wide sink behind the storage-usage observable gauges (Publish, PublishWal, PublishOverThreshold, StalenessHorizon). AddLattice registers it. |
LatticeMetrics.WalReplayPermitWaitScope / LatticeMetrics.LeafSplitCompletionScope |
Allocation-free disposable scopes returned by LatticeMetrics.EnterWalReplayPermitWait and EnterLeafSplitCompletion, which feed the in-flight observable gauges. See Metrics. |
Grain storage
| Type | Role |
|---|---|
ILatticeBinaryPersistedState / LatticeGrainStorageSerializer |
The marker for a persisted state type Lattice writes through the Orleans binary serializer instead of the JSON grain-storage serializer, and the serializer AddLattice installs to do it (WritesBinary(stateType), Fallback). Every other state type is delegated, unchanged, to the serializer registered before it, because the JSON path cannot write a large opaque payload without first materialising it as one contiguous string. |
Distributed lock and atomic actions
| Type | Role |
|---|---|
ILatticeLockGrain, LockAcquireRequest, LockLease, LockToken, LockStatus |
The FIFO-fair distributed lock (AcquireAsync, TryAcquireAsync, RenewAsync, ReleaseAsync, GetStatusAsync) and its request (LeaseDuration, MaxWait), lease (Token, ExpiresAt, LeaseDuration), fencing token (FencingToken), and status (IsHeld, CurrentFencingToken, LeaseExpiresAt, QueueDepth) values. See Distributed lock. |
IAtomicActionGrain, AtomicActionPlanBuilder, AtomicActionTreeWriteBuilder, AtomicActionPlan, AtomicActionStep, AtomicActionStepKind, AtomicActionEntry, AtomicActionOutcome, AtomicActionStatus |
The saga coordinator (ExecuteAsync, TryGetOutcomeAsync), the fluent plan builder (Step, TreeWrite with Upsert / Delete, Build), the plan and step shapes (Custom or TreeWrite steps), and the terminal outcome (Committed, Compensated, or CompensationFailed, with FailedStepIndex and FailureMessage). See Atomic actions. |
IAtomicActionHandler, IAtomicActionContext, AtomicActionRegistrationBuilder |
A named, versioned forward / compensate pair (HandlerId, VersionTag, ForwardAsync, CompensateAsync), the context each effect receives (OperationId, Args, GrainFactory, CancellationToken), and the AddHandler registration surface AddLatticeAtomicAction supplies. |
Exception reference
Every public exception type the core package defines. Those deriving from a BCL
exception subclass implement ILatticeDomainFault;
LeafProjectionStaleException also implements ILatticeLeafUnavailable,
the marker for "this leaf cannot be activated right now".
| Exception | Base | Carries | Raised when |
|---|---|---|---|
LatticeAuthorizationDeniedException |
UnauthorizedAccessException |
TreeId, Operation, SubjectId, Reason |
The registered ILatticeAccessGate denies a write, delete, CRDT, atomic, range-delete, bulk-load, lifecycle, or whole-tree read call, or the backup / restore authorization seam. LatticeTenantAdminAuthorizer.AuthorizeAsync raises it when the gate denies a tenant-administration scope or allows only a key-filtered subset of it, and add-on facades (for example telemetry, tenant administration, and app installation) raise it for their own capability denials. With the auth add-on's internal-origin enforcement registered, a direct external-client call to one of a tree's internal shard or leaf grains is refused with it. Nothing is persisted. A denied point or range read reports absence or an empty result instead (see Reading an empty range read under a gate). |
LatticeReservedTreeNamespaceException |
InvalidOperationException |
TreeId |
Any ILattice call is addressed to an internal system tree (the _lattice_ prefix). Outside system origin it is also raised when a data-mutation call (write, delete, CRDT apply, bulk load) names a tree in the sys- namespace, a t/ tenant id the caller's active tenant does not own, or the all-trees authorization sentinel *, and when a SnapshotAsync destination names a _lattice_, sys-, or another tenant's t/ tree. Reads of sys- and t/ trees are not refused. App trees (a/{app}/{tree}) are ordinary trees and are not reserved; the app registry's sys-app- trees fall under sys-. |
LatticeTreeNotRegisteredException |
KeyNotFoundException |
TreeId |
A tree-registry verb that changes an existing tree's entry - a shard-map change, a split's shard-index allocation, a per-tree configuration override, the projection-digest latch, or a WAL placement change - names a tree with no registry entry (never created, or purged). Nothing is created. SetHistoryRetentionAsync and SetPublishEventsEnabledAsync raise it for a purged tree; a tree that was never created is registered by them instead. The API facades report it as not found. See Tree Registry. |
LatticeTenantAccessDeniedException |
Exception |
- | Resolving a tree name through LatticeTenantExtensions fails closed: the caller's asserted active tenant fails validation against the caller's own membership (an anonymous caller can never act as a tenant), or, under an asserted tenant and outside system origin, the name is a sys- tree or a malformed t/ id that belongs to no tenant. The ILattice read and write paths also raise it (the snapshot cursor per page) when an active ITenantAdmissionController returns false for the tenant's read or write; the tenancy add-on's own controller never returns false, and signals a breach with LatticeQuotaExceededException instead. Never raised by the core defaults: the no-op resolver always resolves the default tenant and the no-op admission controller is inactive. |
LatticeTreeOwnershipDeniedException |
Exception |
Reason |
The registered ITreeOwnershipGuard refuses an alias assignment; the alias is not written and no alias-change notification fires. Every assignment consults the guard, system-origin maintenance included - a resize's swap, a shadow-cutover restore's cutover or its revert to a previous alias, a schema remediation's cutover, and an administrative set-alias - so each can meet it, except that a resize swaps its alias after ResizeAsync has returned, so a refused swap is retried and leaves the resize in progress rather than reaching that caller. The core default guard never refuses. Deleting through an alias to a copy this tree does not own raises InvalidOperationException, not this exception (see DeleteTreeAsync). Reason is the guard's caller-safe reason. |
LatticeReplicationModeMismatchException |
InvalidOperationException |
TreeId, DeclaredMode, AttemptedMode |
A write would break the single-shape rule of a tree declared for cross-cluster replication. |
LatticeCrdtShapeNotRegisteredException |
InvalidOperationException |
TreeId |
An OR-Map write targets a tree with no registered (TKey, TValue) shape. Also raised, at any CRDT mode and with an empty TreeId, when a CRDT write reaches a leaf its shard has not yet attached to a tree - a routing or lifecycle race to retry, not a missing registration. |
LatticeIdempotencyKeyMismatchException |
InvalidOperationException |
OperationId |
An operationId is re-submitted with a different key set (cross-tree: tree set or key set). |
LatticeStateWriteFailedException |
Exception |
GrainType, GrainKey, FaultType, Conflict |
An atomic-write saga, a cross-tree atomic-write coordinator, or a WAL materialiser pin shard fails to persist its own durable state. The storage provider's exception is summarised in FaultType and the message rather than carried as the inner exception, so a client that does not reference the provider can still load it; a fault that is not a conflict is translated only when its exception type comes from such a provider assembly, and a BCL or Lattice exception propagates unchanged. Conflict is true when the write lost an optimistic-concurrency (ETag) check - typically a storage SDK retry of a write that had already landed: the grain deactivates so the next call reloads its durable state, and retrying the same operation with the same operation id is safe and neither loses nor double-applies a commit or abort decision. Every ILattice SetManyAtomicAsync / SetManyAtomicWhereAsync overload, and the cross-tree SetManyAtomicAsync / LatticeAtomicWriteBuilder.CommitAsync, re-attaches up to three attempts (after 1 s and 2 s) before surfacing a persistent conflict. |
LatticeWriteRejectedException |
InvalidOperationException |
TreeId, Operation, Key, Reason |
A registered ILatticeWriteInterceptor rejects the value before commit. A dead-letter decision on a single-key write does not raise it (the value is diverted and the call completes normally), but in an atomic batch a dead-letter aborts the whole batch and surfaces here too. |
LatticeQuotaExceededException |
InvalidOperationException |
TreeId, Dimension, Current, Limit, TenantId |
See Admission back-pressure. |
LatticeSaturatedException |
InvalidOperationException |
TreeId, SaturationSource |
See Saturation back-pressure. |
LatticeShuttingDownException |
InvalidOperationException |
- | See Shutdown back-pressure. |
LatticeWriteFencedException |
InvalidOperationException |
TreeId, SagaId |
The tree is write-fenced for a cross-cluster saga such as a restore cutover. Transient. |
LatticeWalQuiescingException |
InvalidOperationException |
- | A WAL partition is quiesced for an administrative placement move. Transient. |
LatticeWalProviderMissingException |
InvalidOperationException |
TreeId, Partition, ProviderKey |
A WAL partition's pinned provider key does not resolve on this silo; the partition fails closed rather than re-routing to the baseline provider. The ILatticeAdmin WAL move verbs also raise it, before touching any log, when a move target key (or, for ReclaimMovedWalSourceAsync, the source key) does not resolve on the serving silo. |
LatticeCursorRegistryPinExhaustedException |
InvalidOperationException |
- | Opening a point-in-time cursor would push a saga decision registry shard the snapshot touches past LatticeOptions.MaxPinnedSagaDecisions; the cursor is not opened, though a snapshot spanning several shards can leave its pin on the shards that accepted it (see Point-in-time cursors). |
LatticeCursorSnapshotExpiredException |
InvalidOperationException |
- | A point-in-time cursor's registry pin expired between steps; open a fresh cursor. |
LatticeSnapshotReplayBudgetExceededException |
InvalidOperationException |
- | Opening a snapshot cursor would materialise more than LatticeOptions.MaxSnapshotReplayEntries baseline rows on its deepest shard. |
LatticeSnapshotExpiredException |
InvalidOperationException |
- | A snapshot cursor's frozen baseline can no longer be loaded; open a fresh cursor. |
LeafProjectionStaleException |
InvalidOperationException |
- | A leaf cannot rebuild its projection at activation: the WAL has been trimmed past its persisted projection checkpoint and no snapshot covers the gap. Every ProjectionRebuildPolicy value surfaces it (no automatic recovery path is integrated), and the cost triggers - a replay gap over MaxLeafReplayEntries or a checkpoint older than LeafProjectionRetention - never raise it. Resolved by an explicit RebuildLeafProjectionAsync, never by a retry. See Projection Rebuild. |
ScanPageStalledException |
TimeoutException |
TreeId, ShardIndex, Operation, Phase, LeavesVisited, TimeoutSeconds, LeafInFlight, ConsecutiveZeroProgressStalls, LeafStranded, StrandedRecoveryApplications |
One shard range-scan page fill exceeded LatticeOptions.MaxScanPageStallDuration with nothing it could bank: before any leaf contributed, or during a snapshot baseline capture or a bounded range delete, which never bank. A fill that had already accumulated rows returns them as a short page instead, and an aggregate walk (counts, diagnostics, storage usage) banks its partial page. The resilient read scans resume it within their stall budget; the resilient range-delete drain propagates it (see Enumeration). |
ShardActivationTimeoutException |
TimeoutException |
TreeId, ShardIndex, TimeoutSeconds |
A shard root's activation-readiness seed exceeded LatticeOptions.ActivationReadyTimeout; entry points that wrap the seed retry up to three attempts before surfacing it. |
LatticeTransactionOutcomeUnavailableException |
TimeoutException |
TreeId, Key, KeyCount, TransactionIds |
A read's result depends on an atomic-write saga whose outcome the transaction registry could not be reached to establish. A point read raises it for its key; a multi-key read (GetManyAsync, the counts, key and entry enumeration) raises it, with a null Key, only when a key it read carried a prepared mutation and only after its own MaxScanRetries retry is exhausted. A registry that answers that the outcome is no longer known makes the key read as absent instead, on every read path. Retryable; never a guessed value. See When the registry cannot be reached. |
LatticeLockConflictException |
Exception |
LockName |
ILatticeLockGrain.RenewAsync is presented a token that no longer holds the lock. |
CompensationFailedException |
Exception |
StepIndex |
An atomic action's compensating effect faulted after its retry budget. See Atomic actions. |
AtomicActionHandlerNotRegisteredException |
Exception |
HandlerId |
An atomic-action plan names a custom handler id the silo never registered; handler resolution fails closed. |