.NET Spot Public Interface¶
A Spot relocation, including an Actor bound to a session, keeps the same
ObjectGeneration. Since relocation itself isn't a physical/logical
disconnect, it doesn't run the Actor disconnect callback.
.NET per-language interface table of contents
1. Spot¶
SpotId is a string with UTF-8 encoded size 1..255 bytes, and is a
logical ID unique across the whole Location Store transaction domain.
Comparison is case-sensitive comparison, with no normalization. A
regular message only takes SpotId and resolves current authority.
SpotRef is the immutable location snapshot used when closing an
incarnation, and doesn't own a runtime resource or local Spot instance.
public enum ZLinkSpotKind
{
Invalid = 0,
Entry = 1,
User = 2,
Instance = 3
}
public readonly record struct SpotRef(
string SpotId,
ulong ObjectGeneration,
string MeshName,
RoutingId NodeRid);
public enum ZLinkSpotCloseReason
{
ExplicitClose = 0,
HostShutdown = 1,
RelocationOut = 2,
IdleEvicted = 3
}
public readonly record struct ZLinkSpotClosingContext(
ZLinkSpotCloseReason Reason,
DateTimeOffset Deadline);
public enum ZLinkSpotRelocationReadyOutcome
{
Continued = 0,
Relocated = 1
}
public readonly record struct ZLinkSpotRelocationReadyCompletion(
ZLinkSpotRelocationReadyOutcome Outcome);
public interface IZLinkSpotRelocationReadyCall
{
void Defer();
}
public interface IZLinkSpot
{
IZLinkSpotContext Context { get; }
void Configure()
{
}
ValueTask<ZLinkSpotCreateResponse> OnCreateAsync(
ZLinkMessage request,
CancellationToken cancellationToken)
{
// the default lifecycle accepts the creation request.
return ValueTask.FromResult(ZLinkSpotCreateResponse.Accept());
}
ValueTask OnInitializeAsync(CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
ValueTask OnClosingAsync(
ZLinkSpotClosingContext context,
CancellationToken cleanupCancellationToken)
{
return ValueTask.CompletedTask;
}
ValueTask OnRelocationReadyCompletedAsync(
ZLinkSpotRelocationReadyCompletion completion,
CancellationToken cancellationToken)
{
// override only when the application needs round-boundary follow-up processing.
return ValueTask.CompletedTask;
}
}
public interface IZLinkInstanceSpot
{
IZLinkInstanceSpotContext Context { get; }
void Configure()
{
}
ValueTask OnInitializeAsync(CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
ValueTask OnClosingAsync(
ZLinkSpotClosingContext context,
CancellationToken cleanupCancellationToken)
{
return ValueTask.CompletedTask;
}
}
public interface IZLinkSpotRelocationAdapter<TSpot>
where TSpot : class
{
ValueTask<byte[]> CaptureAsync(
TSpot spot,
CancellationToken cancellationToken);
ValueTask RestoreAsync(
TSpot spot,
ReadOnlyMemory<byte> payload,
CancellationToken cancellationToken);
}
public readonly record struct ZLinkSpotCreateResponse(
bool Accepted,
ZLinkMessage? Reply)
{
public static ZLinkSpotCreateResponse Accept(ZLinkMessage? reply = null);
public static ZLinkSpotCreateResponse Accept<TReply>(TReply reply);
public static ZLinkSpotCreateResponse Reject(ZLinkMessage? reply = null);
public static ZLinkSpotCreateResponse Reject<TReply>(TReply reply);
}
public interface IZLinkSpotHandlerRegistry : IZLinkActorHandlerRegistry
{
void AddPacket<THandler>() where THandler : class;
void AddSubscribe<THandler>(string channelName, string topic) where THandler : class;
}
public interface IZLinkInstanceSpotHandlerRegistry
{
void AddPacket<THandler>() where THandler : class;
}
public interface IZLinkSpotOutbound
{
IZLinkSpotSendCall SendToSpot<TMessage>(string spotId, TMessage message);
IZLinkSpotRequestCall RequestToSpot<TRequest>(string spotId, TRequest request);
IZLinkPublishCall Publish<TEvent>(
string channelName,
string topic,
TEvent message);
IZLinkSendCall SendToChannel<TMessage>(
string channelName,
TMessage message);
IZLinkRequestCall RequestToChannel<TRequest>(
string channelName,
TRequest request);
}
public interface IZLinkSpotCommonContext
{
string MeshName { get; }
string SpotId { get; }
ulong ObjectGeneration { get; }
RoutingId NodeRid { get; }
IZLinkSpotOutbound Outbound { get; }
ValueTask<IZLinkTimer> AddTimer<THandler>(
string name,
TimeSpan period,
ZLinkTimerOptions? options = null,
CancellationToken cancellationToken = default)
where THandler : class;
IZLinkWorkerCall<TResult> RunCpuWorker<TResult>(
Func<CancellationToken, TResult> work);
IZLinkWorkerCall<TResult> RunIoWorker<TResult>(
Func<CancellationToken, ValueTask<TResult>> work);
}
public interface IZLinkSpotContext : IZLinkSpotCommonContext
{
IZLinkSpotHandlerRegistry Handlers { get; }
IZLinkSpotRelocationReadyCall RelocationReady();
ValueTask LeaveActorAsync(
IZLinkActor actor,
CancellationToken cancellationToken = default);
ValueTask<bool> CloseAsync(
CancellationToken cancellationToken = default);
}
public interface IZLinkInstanceSpotContext : IZLinkSpotCommonContext
{
IZLinkInstanceSpotHandlerRegistry Handlers { get; }
ValueTask<bool> CloseAsync(
CancellationToken cancellationToken = default);
}
public interface IZLinkEntrySpot
{
IZLinkEntrySpotContext Context { get; }
void Configure()
{
}
ValueTask OnInitializeAsync(CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
ValueTask OnClosingAsync(
ZLinkSpotClosingContext context,
CancellationToken cleanupCancellationToken)
{
return ValueTask.CompletedTask;
}
}
public interface IZLinkSpotActorMembershipLifecycle<TActor>
where TActor : IZLinkActor
{
ValueTask OnJoinedActorAsync(
TActor actor,
CancellationToken cancellationToken);
ValueTask OnLeaveActorAsync(
TActor actor,
CancellationToken cancellationToken);
ValueTask OnDisconnectActorAsync(
TActor actor,
CancellationToken cancellationToken)
{
// a Spot that doesn't need to handle disconnects doesn't have to implement this callback.
return ValueTask.CompletedTask;
}
}
public interface IZLinkUserSpotActorLifecycle<TActor>
: IZLinkSpotActorMembershipLifecycle<TActor>
where TActor : IZLinkActor
{
ValueTask<ZLinkSpotActorJoinResult> OnActorJoinAsync(
string actorId,
ZLinkMessage request,
CancellationToken cancellationToken);
}
public interface IZLinkSpot<TActor> : IZLinkSpot, IZLinkUserSpotActorLifecycle<TActor>
where TActor : IZLinkActor;
public readonly record struct ZLinkActorCreateResponse(
bool Accepted,
ZLinkMessage? Reply)
{
public static ZLinkActorCreateResponse Accept(ZLinkMessage? reply = null);
public static ZLinkActorCreateResponse Accept<TReply>(TReply reply);
public static ZLinkActorCreateResponse Reject(ZLinkMessage? reply = null);
public static ZLinkActorCreateResponse Reject<TReply>(TReply reply);
}
public interface IZLinkEntrySpot<TActor>
: IZLinkEntrySpot, IZLinkSpotActorMembershipLifecycle<TActor>
where TActor : IZLinkActor
{
ValueTask<ZLinkActorCreateResponse> OnCreateActorAsync(
TActor actor,
ZLinkMessage createRequest,
CancellationToken cancellationToken)
{
// the default lifecycle approves Actor creation.
return ValueTask.FromResult(ZLinkActorCreateResponse.Accept());
}
}
public readonly record struct ZLinkSpotActorJoinResult(
bool Accepted,
ZLinkMessage? Reply)
{
public static ZLinkSpotActorJoinResult Accept(ZLinkMessage? reply = null);
public static ZLinkSpotActorJoinResult Accept<TReply>(TReply reply);
public static ZLinkSpotActorJoinResult Reject(ZLinkMessage? reply = null);
public static ZLinkSpotActorJoinResult Reject<TReply>(TReply reply);
}
public interface IZLinkEntrySpotContext : IZLinkSpotCommonContext
{
IZLinkSpotHandlerRegistry Handlers { get; }
ValueTask DestroyActorAsync(
IZLinkActor actor,
CancellationToken cancellationToken = default);
}
public interface IZLinkSpotActorSendHandler<TSpot, TActor, in TMessage>
where TSpot : class
where TActor : IZLinkActor
{
ValueTask HandleAsync(
TSpot spot,
TActor actor,
IZLinkMessageContext context,
TMessage message,
CancellationToken cancellationToken);
}
public interface IZLinkSpotActorRequestHandler<TSpot, TActor, in TRequest, TReply>
where TSpot : class
where TActor : IZLinkActor
{
ValueTask<TReply> HandleAsync(
TSpot spot,
TActor actor,
IZLinkMessageContext context,
TRequest request,
CancellationToken cancellationToken);
}
public interface IZLinkEntrySpotActorSendHandler<TEntrySpot, TActor, in TMessage>
where TEntrySpot : class, IZLinkEntrySpot
where TActor : IZLinkActor
{
ValueTask HandleAsync(
TEntrySpot entrySpot,
TActor actor,
IZLinkMessageContext context,
TMessage message,
CancellationToken cancellationToken);
}
public interface IZLinkEntrySpotActorRequestHandler<TEntrySpot, TActor, in TRequest, TReply>
where TEntrySpot : class, IZLinkEntrySpot
where TActor : IZLinkActor
{
ValueTask<TReply> HandleAsync(
TEntrySpot entrySpot,
TActor actor,
IZLinkMessageContext context,
TRequest request,
CancellationToken cancellationToken);
}
The framework creates a Spot's packet/request/subscription/timer handler
once in that Spot's activation DI scope, and reuses it for the duration
of the Spot activation. An Actor send/request handler is created once in
that Actor's separate activation DI scope, and reused for the duration of
the Actor activation. Different Actors of an Entry Spot and a PerActor
User Spot don't share a handler instance or scoped dependency.
The handler type's own DI registration lifetime doesn't change this
rule. The framework owns the handler instance and only resolves
constructor dependencies in that activation scope. A separate handler
lifetime option isn't provided. On Spot/Actor relocation and cross-node
Join, the source handler and scope are cleaned up and re-created in the
target activation. The state that must be recovered is owned by TSpot
or TActor, not a handler field.
ZLinkSpotCloseReason's numeric values are ExplicitClose=0,
HostShutdown=1, RelocationOut=2, IdleEvicted=3. IdleEvicted is an
Instance-Spot-only reason and isn't delivered to Entry Spot or User Spot.
The idle judgment condition and the reactivation rule after cleanup are
owned by
Spot Model §6.2.
Deadline is the closing operation's absolute deadline. The framework
doesn't cancel cleanupCancellationToken before the callback invocation,
and cancels it when the
deadline ends. An already
cancelled handler token isn't reused. Only Entry/User/Instance Spot
receive this callback — a per-Actor closing callback isn't provided.
Host Shutdown runs the callback while Actor membership and the local
instance are valid, and cleans up scope and
authority after completion.
A standalone Actor relocation doesn't close the Entry Spot, so it doesn't
call this callback.
IZLinkSpotRelocationAdapter<TSpot> is registered with
PreserveStateWith<TAdapter>(). It's only called when materializing a
cross-node User/Instance Spot
instance. In a whole User Spot relocation, the
Spot adapter handles the Spot
application payload, and each member Actor's payload is handled
separately by the Actor adapter registered on the Actor factory.
RecreateOnRelocation() doesn't call the adapter and re-creates the
instance with no application state, and DisableRelocation() rejects a
cross-node move before capture.
The Spot adapter's capture and restore can be called at-least-once within
a stable relocation attempt, so they must be retry-safe.
CaptureAsync(...)'s result has no relocation-adapter-specific size cap;
the framework splits the payload into chunks no larger than
RelocationPayloadChunkLimit and transfers them directly over the
source–target ordered mesh connection. Source memory is the restore
origin, and the handoff payload isn't stored in the Relocation Store. An
empty array is valid,
and null is a contract violation. The framework immediately copies the
completed array and doesn't observe subsequent application mutation.
RestoreAsync(...)'s ReadOnlyMemory<byte> is only valid until the
callback completes, so the application must copy it to keep it. A capture
exception restores admission after a durable abort and source
normalization. An instance with a restore exception is discarded, and a
new attempt applies the same immutable payload to a new instance the
factory creates. If the
framework cancels a callback due to the operation deadline, it's
classified as DeadlineExceeded. The framework doesn't guarantee
exactly-once execution of the callback's external side effect.
When maintenance restores an Actor to a different node's Entry Spot, it
first finishes the Actor adapter restore, then commits Location
authority and Entry membership.
Since this isn't an application membership change, it doesn't call the
target's OnJoinedActorAsync(...), the source's
OnLeaveActorAsync(...), or a relocation-dedicated callback. It restores
the accepted journal/queue/Actor timer, commits Location authority/
membership, and then starts Actor message processing. Session–Actor binding §8.2 owns relocation route updates for a bound Session.
A regular application
join toward a User Spot keeps the order: target's
OnActorJoinAsync(...), membership commit, target's
OnJoinedActorAsync(...). An Entry Spot return commits membership with
no admission callback and then calls the target Entry Spot's
OnJoinedActorAsync(...). SpotWide User Spot aggregate move and a
PerActor User Spot's Actor relocation also don't call an application
membership callback.
A PerActor User Spot only allows the RecreateOnRelocation Spot
policy and doesn't register a Spot relocation adapter. Spot fields and a
Spot-level application timer aren't relocation targets. It moves the
Actor independently after first switching the target Spot authority —
ToSpot/Create/Join use Spot authority, and ToActor uses the current
owner per Actor. The target's runtime-private shell uses the same public
SpotId and ObjectGeneration, and isn't exposed to public lookup before
the authority switch. A stale source route is relayed while preserving
operation identity, generation, deadline, correlation, and reply route.
The source-local 1-second window from Actor queue seal to the one-way cutover submit's
success or failure terminal is an
operational goal — exceeding it doesn't cancel or roll back the
relocation.
RelocationReady().Defer() is only valid on a Spot turn where the
SpotWide factory selected the ApplicationSignaled coordination mode.
Defer() registers a relocation boundary right before the next
application turn, after the current handler finishes. The framework
delivers Continued from the source if it didn't move or aborted before
relay-ready was accepted, and Relocated from the target if it moved, to
OnRelocationReadyCompletedAsync(...)'s completion. The default
implementation is a no-op. Held messages and timers aren't run before the
callback completes.
A duplicate Defer() on the default FrameworkManaged, on PerActor, on
Entry/Instance Spot, outside the Spot turn, or in the same turn ends with
a ZLinkFrameworkErrorKind.InvalidOperation error before any queue
mutation. Starting a different framework operation in the same turn
after Defer() is the same error. Since the callback can run again
during process recovery, the override must be retry-safe.
The current-location query for Spot and Actor is performed by the manager using the global ID. A public resolver and runtime handle aren't provided. The owner route and generation update rule follows Spot Address Messaging.
The Spot handler signatures are as follows.
public interface IZLinkSpotPacketHandler<TSpot, in TMessage>
where TSpot : class
{
ValueTask HandleAsync(
TSpot spot,
TMessage message,
CancellationToken cancellationToken);
}
public interface IZLinkSpotRequestHandler<TSpot, in TRequest, TReply>
where TSpot : class
{
ValueTask<TReply> HandleAsync(
TSpot spot,
TRequest request,
CancellationToken cancellationToken);
}
public interface IZLinkSpotSubscriptionHandler<TSpot, in TEvent>
where TSpot : class
{
ValueTask HandleAsync(
TSpot spot,
TEvent message,
ZLinkPublishMessageContext context,
CancellationToken cancellationToken);
}
public interface IZLinkSpotTimerHandler<TSpot>
where TSpot : class
{
ValueTask HandleAsync(
TSpot spot,
ZLinkTimerTick tick,
CancellationToken cancellationToken);
}
public interface IZLinkTimer : IAsyncDisposable
{
bool IsDisposed { get; }
ValueTask CancelAsync();
}
public sealed record ZLinkTimerOptions
{
public ZLinkTimerOverrunPolicy OverrunPolicy { get; init; }
= ZLinkTimerOverrunPolicy.SkipLateTicks;
public int MaxCatchUpTicks { get; init; } = 1;
public bool StopOnUnhandledException { get; init; }
}
public enum ZLinkTimerOverrunPolicy
{
SkipLateTicks = 1,
CatchUpBounded = 2,
DelayNextTick = 3
}
public readonly record struct ZLinkTimerTick(
string Name,
ulong DeliveryIndex,
ulong ScheduledIndex,
TimeSpan Period,
DateTimeOffset ScheduledAt,
DateTimeOffset StartedAt,
TimeSpan ScheduledElapsed,
TimeSpan StartedElapsed,
TimeSpan Delay,
ulong SkippedTicks);
When timer options are omitted, OverrunPolicy defaults to SkipLateTicks
and MaxCatchUpTicks defaults to 1. MaxCatchUpTicks is used and validated
in 1..Int32.MaxValue only when OverrunPolicy == CatchUpBounded. Other
policies do not use or validate this value against that range. This prose does
not change the existing ZLinkTimerOptions public surface.
A Framework timer is a logical registration belonging to the owner
Actor/Spot. On cross-node relocation, the timer name, handler type,
period, ZLinkTimerOptions, scheduling cursor, and the pending tick at
seal time are automatically included in the relocation payload. The
application's relocation adapter doesn't capture/restore the timer or
re-register it on the target. A framework-managed timer resource isn't
included in the payload — it's re-created on the target as a logical
registration. The source doesn't dispatch a new tick after sealing the
queue, and the target only submits the restored pending tick and the
next tick to the owner mailbox
after finishing Restore and authority commit and dispatch admission
opens.
An external client of a Spot uses the following signatures.
public interface IZLinkSpotClient
{
IZLinkSpotSendCall SendToSpot<TMessage>(string spotId, TMessage message);
IZLinkSpotRequestCall RequestToSpot<TRequest>(string spotId, TRequest request);
}
public interface IZLinkSpotSendCall : IZLinkMetadataCall<IZLinkSpotSendCall>
{
IZLinkSpotSendCall InstanceSpot();
IZLinkSpotSendCall InstanceSpot(string instanceSpotType);
IZLinkSpotSendCall InMesh(string meshName);
ValueTask Async(
CancellationToken cancellationToken = default);
}
public interface IZLinkSpotRequestCall : IZLinkMetadataCall<IZLinkSpotRequestCall>
{
IZLinkSpotRequestCall InstanceSpot();
IZLinkSpotRequestCall InstanceSpot(string instanceSpotType);
IZLinkSpotRequestCall InMesh(string meshName);
IZLinkSpotRequestCall Timeout(TimeSpan timeout);
ValueTask<TReply> Async<TReply>(
CancellationToken cancellationToken = default);
ValueTask<TReply> Yield<TReply>(
CancellationToken cancellationToken = default);
}
public enum ZLinkSpotCreateState
{
Existing = 0,
Created = 1,
Rejected = 2
}
public readonly record struct ZLinkSpotCreateResult(
SpotRef Spot,
ZLinkSpotCreateState State,
ZLinkMessage? Reply);
public interface IZLinkSpotManager
{
IZLinkSpotCreateCall Create(string spotType);
IZLinkSpotGetOrCreateCall GetOrCreate(
string spotId,
string spotType);
ValueTask<SpotRef?> FindAsync(
string spotId,
CancellationToken cancellationToken = default);
ValueTask<bool> CloseAsync(
SpotRef spot,
CancellationToken cancellationToken = default);
}
public interface IZLinkSpotCreateCall
{
IZLinkSpotCreateCall InMesh(string meshName);
IZLinkSpotCreateCall Request(ZLinkMessage request);
IZLinkSpotCreateCall Request<TRequest>(TRequest request);
IZLinkSpotCreateCall Timeout(TimeSpan timeout);
ValueTask<ZLinkSpotCreateResult> Async(CancellationToken cancellationToken = default);
ValueTask<ZLinkSpotCreateResult> Yield(CancellationToken cancellationToken = default);
}
public interface IZLinkSpotGetOrCreateCall
{
IZLinkSpotGetOrCreateCall InMesh(string meshName);
IZLinkSpotGetOrCreateCall Request(ZLinkMessage request);
IZLinkSpotGetOrCreateCall Request<TRequest>(TRequest request);
IZLinkSpotGetOrCreateCall Timeout(TimeSpan timeout);
ValueTask<ZLinkSpotCreateResult> Async(CancellationToken cancellationToken = default);
ValueTask<ZLinkSpotCreateResult> Yield(CancellationToken cancellationToken = default);
}
public interface IZLinkSpotPublisherClient
{
IZLinkPublishCall Publish<TEvent>(
string channelName,
string topic,
TEvent message);
}
Entry/User/Instance SpotId is a global string key with UTF-8 encoded
size 1..255 bytes. Stable type is UTF-8 1..255 bytes, compared as a
case-sensitive value comparison with no normalization. SpotRef.ObjectGeneration
is 1..long.MaxValue. MeshName and NodeRid are the route snapshot at
query time and aren't included in the identity key.
IZLinkSpotOutbound and IZLinkSpotClient take the global SpotId and return
IZLinkSpotSendCall or IZLinkSpotRequestCall. Marker overloads are InstanceSpot()
and InstanceSpot(string instanceSpotType); the Mesh input is InMesh(string meshName).
Send Async(...) returns ValueTask; request Async<TReply>(...) and
Yield<TReply>(...) return ValueTask<TReply>.
Spot address messaging §4 owns type/Mesh selection, the creation sequence, and first-message preservation for cold activation. Completion boundaries follow Spot address messaging §5.
InMesh is valid only on a call with the Instance marker; using it without the marker
ends with InvalidOperation. The Instance marker and each option can only be set once.
Send is submitted once with Async, and request with Async<TReply> or Yield<TReply>.
IZLinkInstanceSpot is an actor-free lifecycle interface that doesn't
inherit IZLinkSpot. It can only register a direct packet and timer
handler. If an Actor handler or Logical Multicast subscription is
registered, the framework rejects activation before the Ready commit.
CloseAsync(spotRef) only closes the specified incarnation. If that
incarnation doesn't exist, false; if the generation differs,
InvalidOperation; if in pre-commit seal, Unavailable. If Actor
membership remains on the User Spot, false — it doesn't automatically
leave/destroy the Actor. The framework doesn't find the current ref again
and close a different incarnation.
IZLinkSpotManager only provides User Spot's explicit create/
get-or-create, resolve, and close. The manager doesn't have an
argument to select Spot kind or an Instance Spot create/get-or-create
overload. Instance Spot's creation path is the one explicit
InstanceSpot(...) opt-in on the Spot-dedicated message call. It leaves
IZLinkInstanceSpotContext.CloseAsync() for an Instance Spot
implementation to close its own lifecycle.
User Spot Create and GetOrCreate calls are single-use. Setting the same
option twice is InvalidOperation, and calling terminal Async(...)
twice is InvalidOperation. The InMesh(...) selection and error and
whole-deadline rules are the same as Actor create. Create has the
framework issue a new global Spot ID. GetOrCreate returns a Ready Spot
of the same User Spot stable type as Existing. If Creating, it waits
for the authority change; once Ready, Existing; if it becomes Missing
through cleanup, it competes for a new reservation. A CAS loser doesn't
run a separate factory. If kind or type differs, TypeMismatch; if the
terminal state isn't reached within the deadline, DeadlineExceeded. A
creation request is at most 1 MiB and is kept as an immutable reference
and hash before reservation.
Object lifecycle §3 owns the scope of stored creation intent resumption and the distinction
from steady Ready owner failure.
A public
activation driver, address, handle, resolver, and unbounded list aren't
provided. An operational query is owned by the Location Runtime's paged
query with page size 1..1000 and encoded page at most 4 MiB.
If the cold Instance factory or initialize fails, that call completes with a typed failure. The same call isn't hidden-retried internally, and a public API for manipulating the failure state or recovery procedure isn't provided.
IZLinkSpotPublisherClient.Publish(...) and
IZLinkSpotOutbound.Publish(...) are
Logical Multicast.
Both an external publisher's and a Spot callback's outbound only take
ChannelName and topic. A process-local
ChannelName index selects
the owner MeshNode, and the
caller doesn't additionally pass MeshName.
Each remote target follows the MeshNode ROUTER's send rule, and matching
Spot queues on the same node share immutable message storage. The
configuration surface is owned by
Topology Configuration §5.
Per-target admission/failure results of remote transport and the local
Spot queue aren't returned or aggregated into monitoring. It doesn't wait
for remote Spot queue submission or remote/local handler execution or
completion.