Skip to content

Kotlin Spot Public Interface

Interface table of contents · Java Spot · Common Spot Contract

Session–Actor binding §8.2 owns relocation route updates for a bound Session.

SpotId is a String of UTF-8 encoded size 1..255 bytes, a logical ID unique across the whole Location Store transaction domain. Comparison is case-sensitive comparison, with no Unicode normalization or case folding applied. A regular Spot send/request only takes SpotId. SpotRef(spotId, objectGeneration, meshName, nodeRid) is an immutable snapshot only used when closing an specified incarnation. objectGeneration is 1..Long.MAX_VALUE, and a decimal string in JSON. User and Instance Spot type is a stable value of UTF-8 1..255 bytes. The Java enum's numeric value is ZLinkSpotKind.INVALID=0, ENTRY=1, USER=2, INSTANCE=3, and Kotlin doesn't use ordinal as the contract value — it uses value(). A creatable-kind enum isn't provided.

ZLinkSpotManager.create(spotType) generates a User Spot ID, and getOrCreate(spotId, spotType) uses a caller-specified User Spot ID. The manager doesn't provide Instance Spot create/get-or-create. Both operations return a Kotlin-only single-use wrapper that preserves inMesh, request, timeout. Terminal await() or yield() is called exactly once. The rules for duplicate option and duplicate terminal, Mesh selection, type conflict, and deadline are the same as the Actor operation. The Entry Spot ID is created by the framework and isn't a public create target.

Instance Spot Cold Activation And The First Message

Spot send/request takes the global SpotId and returns ZLinkKotlinSpotSendCall or ZLinkKotlinSpotRequestCall<TReply>. Marker overloads are instanceSpot() and instanceSpot(stableType: String); the Mesh input is inMesh(meshName: String). Send expresses completion as suspend fun await(): Unit; request uses suspend fun await(): TReply and suspend fun yield(): TReply. Exact declarations appear in Kotlin Source Signature.

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.

Object lifecycle §3 owns the scope of stored creation intent resumption and the distinction from steady Ready owner failure.

Kotlin implements Java's ZLinkSpotRelocationAdapter<TSpot> unchanged. The opaque byte[] appears as ByteArray, and capture and restore return the same CompletionStage as the Java contract. A separate suspending Spot adapter, TState, stateContractId, state class, or ZLinkMessage relocation surface isn't provided. A state-preservation factory uses preserveStateWith(SpotAdapter::class.java), and the factory target and adapter type are validated before socket bind.

A state-preserving whole User Spot relocation uses the Spot adapter for the Spot itself and an Actor adapter for each member Actor. A state-preserving Instance Spot relocation uses the Spot adapter. The adapter isn't called for a same-node operation, disableRelocation(), or recreateOnRelocation(). The capture ByteArray has no relocation-adapter-specific size cap. The Java runtime copies it at completion and splits the payload into chunks no larger than relocationPayloadChunkLimitBytes, transferring 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. The adapter owns the array until completion. Restore receives a fresh defensive copy per call and doesn't keep it after completion. An empty ByteArray is also a valid preserved state. The factory creates a fresh Spot instance per target attempt and doesn't reuse the source or a previous attempt's instance. Restore of the same attempt can be repeated. A capture exception keeps source authority and admission, and a restore exception keeps the target sealed while allowing a retry with the same payload on the same target process. A different target isn't automatically selected. A null stage and null capture payload are contract violations. Host relocation's precommit adapter exception/contract violation is Blocked/StateIncompatible if a deadline hasn't been fixed yet, and Blocked/DeadlineExceeded once the deadline is fixed. Stale attempt cancellation can't commit a terminal result. The callback is at-least-once and can overlap with a stale attempt, so it must be retry-safe.

The Spot closing reason uses Java's ZLinkSpotCloseReason, with values EXPLICIT_CLOSE=0, HOST_SHUTDOWN=1, RELOCATION_OUT=2, IDLE_EVICTED=3. ZLinkSpotClosingContext.deadline is an absolute Instant. The Java lifecycle interface only takes a context and doesn't use a separate Framework cancellation type. The suspending projection cancels the bridge coroutine at the cleanup deadline, and the callback follows coroutine cancellation unchanged. A per-Actor closing callback isn't provided.

Kotlin Source Signature

interface ZLinkSuspendingSpotPacketHandler<TSpot : ZLinkSpot<*>, TMessage> {
 suspend fun handle(spot: TSpot, message: TMessage)
 suspend fun handle(
 spot: TSpot,
 message: TMessage,
 context: ZLinkMessageContext,
 )
}

interface ZLinkSuspendingSpotRequestHandler<TSpot : Any, TRequest, TReply> {
 suspend fun handle(spot: TSpot, request: TRequest): TReply
 suspend fun handle(
 spot: TSpot,
 request: TRequest,
 context: ZLinkMessageContext,
 ): TReply
}

interface ZLinkSuspendingSpotSubscriptionHandler<TSpot : Any, TEvent> {
 suspend fun handle(spot: TSpot, event: TEvent)
 suspend fun handle(
 spot: TSpot,
 event: TEvent,
 context: ZLinkPublishMessageContext,
 )
}

interface ZLinkSuspendingSpotTimerHandler<TSpot : Any> {
 suspend fun handle(spot: TSpot, tick: ZLinkTimerTick)
}

// Relocation restores the logical timer and pending tick as framework payload.

abstract class ZLinkSuspendingSpot<TActor : ZLinkActor> : ZLinkSpot<TActor> {
 abstract val context: ZLinkSpotContext
 final override fun context(): ZLinkSpotContext = context
 protected open suspend fun onCreateSuspending(
 request: ZLinkMessage,
 ): ZLinkSpotCreateResponse
 protected open suspend fun onInitializeSuspending()
 protected open suspend fun onClosingSuspending(
 context: ZLinkSpotClosingContext,
 )
 protected open suspend fun onRelocationReadyCompletedSuspending(
 completion: ZLinkSpotRelocationReadyCompletion,
 )
 protected abstract suspend fun onActorJoinSuspending(
 actorId: String,
 request: ZLinkMessage,
 ): ZLinkSpotActorJoinResult
 protected abstract suspend fun onJoinedActorSuspending(actor: TActor)
 protected abstract suspend fun onLeaveActorSuspending(actor: TActor)
 protected open suspend fun onDisconnectActorSuspending(actor: TActor)
}

abstract class ZLinkSuspendingEntrySpot<TActor : ZLinkActor> :
 ZLinkEntrySpot<TActor> {
 abstract val context: ZLinkEntrySpotContext
 final override fun context(): ZLinkEntrySpotContext = context
 protected open suspend fun onInitializeSuspending()
 protected open suspend fun onClosingSuspending(
 context: ZLinkSpotClosingContext,
 )
 protected open suspend fun onCreateActorSuspending(
 actor: TActor,
 createRequest: ZLinkMessage,
 ): ZLinkActorCreateResponse
 protected abstract suspend fun onJoinedActorSuspending(actor: TActor)
 protected abstract suspend fun onLeaveActorSuspending(actor: TActor)
 protected open suspend fun onDisconnectActorSuspending(actor: TActor)
}

abstract class ZLinkSuspendingInstanceSpot : ZLinkInstanceSpot {
 abstract val context: ZLinkInstanceSpotContext
 final override fun context(): ZLinkInstanceSpotContext = context
 protected open suspend fun onInitializeSuspending()
 protected open suspend fun onClosingSuspending(
 context: ZLinkSpotClosingContext,
 )
}

inline fun <reified THandler : Any> ZLinkSpotHandlerRegistry.addHandler()

interface ZLinkKotlinSpotSendCall {
 fun metadata(key: String, value: String): ZLinkKotlinSpotSendCall
 fun instanceSpot(): ZLinkKotlinSpotSendCall
 fun instanceSpot(stableType: String): ZLinkKotlinSpotSendCall
 fun inMesh(meshName: String): ZLinkKotlinSpotSendCall
 suspend fun await()
}

interface ZLinkKotlinSpotRequestCall<TReply> {
 fun metadata(key: String, value: String): ZLinkKotlinSpotRequestCall<TReply>
 fun instanceSpot(): ZLinkKotlinSpotRequestCall<TReply>
 fun instanceSpot(stableType: String): ZLinkKotlinSpotRequestCall<TReply>
 fun inMesh(meshName: String): ZLinkKotlinSpotRequestCall<TReply>
 fun timeout(timeout: Duration): ZLinkKotlinSpotRequestCall<TReply>
 suspend fun await(): TReply
 suspend fun yield(): TReply
}

interface ZLinkKotlinSpotCreateCall {
 fun inMesh(meshName: String): ZLinkKotlinSpotCreateCall
 fun request(request: Any): ZLinkKotlinSpotCreateCall
 fun timeout(timeout: Duration): ZLinkKotlinSpotCreateCall
 suspend fun await(): ZLinkSpotCreateResult
 suspend fun yield(): ZLinkSpotCreateResult
}

interface ZLinkKotlinSpotManager {
 fun create(spotType: String): ZLinkKotlinSpotCreateCall
 fun getOrCreate(
 spotId: String,
 spotType: String,
 ): ZLinkKotlinSpotCreateCall
}

fun ZLinkKotlinRouteClient.sendToSpot(
 spotId: String,
 message: Any,
): ZLinkKotlinSpotSendCall

inline fun <reified TReply> ZLinkKotlinRouteClient.requestToSpot(
 spotId: String,
 request: Any,
): ZLinkKotlinSpotRequestCall<TReply>

Kotlin reuses Java's ZLinkTimerOptions and ZLinkTimerOverrunPolicy as is. When timer options are omitted, overrunPolicy defaults to SKIP_LATE_TICKS and maxCatchUpTicks defaults to 1. maxCatchUpTicks is used and validated in 1..Int.MAX_VALUE only for CATCH_UP_BOUNDED. Other policies do not use or validate this value against that range. Kotlin adds no timer-option surface.

In User/Instance Spot relocation, the Java runtime includes the logical timer registration, the last completed tick sequence, the next scheduled time, and pending ticks not yet run in the relocation payload. The target restores the logical timer registration, so the application doesn't re-register the timer. Only the currently running suspending timer handler finishes on the source, and the restored tick isn't run before target Ready.

Generated JVM Signature

onCreateActorSuspending returns ZLinkActorCreateResponse, preserved by the JVM bridge onCreateActor as CompletionStage<ZLinkActorCreateResponse>. Spot Model §4.2 owns the common creation-response semantics; Java Spot Interface owns the superinterface declaration.

public final class systems.zlink.framework.kotlin.ZLinkSpotHandlerRegistryExtensionsKt {
 public static final <THandler> void addHandler(systems.zlink.framework.spots.ZLinkSpotHandlerRegistry);
 public static final void addTypedHandler(systems.zlink.framework.spots.ZLinkSpotHandlerRegistry, java.lang.Class<?>);
}
public interface systems.zlink.framework.kotlin.ZLinkSuspendingSpotPacketHandler<TSpot extends systems.zlink.framework.spots.ZLinkSpot<?>, TMessage> {
 public abstract java.lang.Object handle(TSpot, TMessage, kotlin.coroutines.Continuation<? super kotlin.Unit>);
 public abstract java.lang.Object handle(TSpot, TMessage, systems.zlink.framework.messaging.ZLinkMessageContext, kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
public interface systems.zlink.framework.kotlin.ZLinkSuspendingSpotRequestHandler<TSpot, TRequest, TReply> {
 public abstract java.lang.Object handle(TSpot, TRequest, kotlin.coroutines.Continuation<? super TReply>);
 public abstract java.lang.Object handle(TSpot, TRequest, systems.zlink.framework.messaging.ZLinkMessageContext, kotlin.coroutines.Continuation<? super TReply>);
}
public interface systems.zlink.framework.kotlin.ZLinkSuspendingSpotSubscriptionHandler<TSpot, TEvent> {
 public abstract java.lang.Object handle(TSpot, TEvent, kotlin.coroutines.Continuation<? super kotlin.Unit>);
 public abstract java.lang.Object handle(TSpot, TEvent, systems.zlink.framework.messaging.ZLinkPublishMessageContext, kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
public interface systems.zlink.framework.kotlin.ZLinkSuspendingSpotTimerHandler<TSpot> {
 public abstract java.lang.Object handle(TSpot, systems.zlink.framework.spots.ZLinkTimerTick, kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
public abstract class systems.zlink.framework.kotlin.ZLinkSuspendingEntrySpot<TActor extends systems.zlink.framework.actors.ZLinkActor> implements systems.zlink.framework.spots.ZLinkEntrySpot<TActor> {
 public systems.zlink.framework.kotlin.ZLinkSuspendingEntrySpot();
 public abstract systems.zlink.framework.spots.ZLinkEntrySpotContext getContext();
 public final systems.zlink.framework.spots.ZLinkEntrySpotContext context();
 public final java.util.concurrent.CompletionStage<java.lang.Void> onInitialize();
 public final java.util.concurrent.CompletionStage<java.lang.Void> onClosing(systems.zlink.framework.spots.ZLinkSpotClosingContext);
 public final java.util.concurrent.CompletionStage<systems.zlink.framework.spots.ZLinkActorCreateResponse> onCreateActor(TActor, systems.zlink.framework.messaging.ZLinkMessage);
 public final java.util.concurrent.CompletionStage<java.lang.Void> onJoinedActor(TActor);
 public final java.util.concurrent.CompletionStage<java.lang.Void> onLeaveActor(TActor);
 public final java.util.concurrent.CompletionStage<java.lang.Void> onDisconnectActor(TActor);
}
public abstract class systems.zlink.framework.kotlin.ZLinkSuspendingSpot<TActor extends systems.zlink.framework.actors.ZLinkActor> implements systems.zlink.framework.spots.ZLinkSpot<TActor> {
 public systems.zlink.framework.kotlin.ZLinkSuspendingSpot();
 public abstract systems.zlink.framework.spots.ZLinkSpotContext getContext();
 public final systems.zlink.framework.spots.ZLinkSpotContext context();
 public final java.util.concurrent.CompletionStage<systems.zlink.framework.spots.ZLinkSpotCreateResponse> onCreate(systems.zlink.framework.messaging.ZLinkMessage);
 public final java.util.concurrent.CompletionStage<java.lang.Void> onInitialize();
 public final java.util.concurrent.CompletionStage<java.lang.Void> onClosing(systems.zlink.framework.spots.ZLinkSpotClosingContext);
 public final java.util.concurrent.CompletionStage<java.lang.Void> onRelocationReadyCompleted(systems.zlink.framework.spots.ZLinkSpotRelocationReadyCompletion);
 public final java.util.concurrent.CompletionStage<systems.zlink.framework.spots.ZLinkSpotActorJoinResult> onActorJoin(java.lang.String, systems.zlink.framework.messaging.ZLinkMessage);
 public final java.util.concurrent.CompletionStage<java.lang.Void> onJoinedActor(TActor);
 public final java.util.concurrent.CompletionStage<java.lang.Void> onLeaveActor(TActor);
 public final java.util.concurrent.CompletionStage<java.lang.Void> onDisconnectActor(TActor);
}
public abstract class systems.zlink.framework.kotlin.ZLinkSuspendingInstanceSpot implements systems.zlink.framework.spots.ZLinkInstanceSpot {
 public systems.zlink.framework.kotlin.ZLinkSuspendingInstanceSpot();
 public abstract systems.zlink.framework.spots.ZLinkInstanceSpotContext getContext();
 public final systems.zlink.framework.spots.ZLinkInstanceSpotContext context();
 public final java.util.concurrent.CompletionStage<java.lang.Void> onInitialize();
 public final java.util.concurrent.CompletionStage<java.lang.Void> onClosing(systems.zlink.framework.spots.ZLinkSpotClosingContext);
}
public final class systems.zlink.framework.kotlin.ZLinkFrameworkExtensionsKt {
 public static final systems.zlink.framework.kotlin.ZLinkKotlinSpotSendCall sendToSpot(systems.zlink.framework.kotlin.ZLinkKotlinRouteClient, java.lang.String, java.lang.Object);
 public static final <TReply> systems.zlink.framework.kotlin.ZLinkKotlinSpotRequestCall<TReply> requestToSpot(systems.zlink.framework.kotlin.ZLinkKotlinRouteClient, java.lang.String, java.lang.Object);
}
public interface systems.zlink.framework.kotlin.ZLinkKotlinSpotSendCall {
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotSendCall metadata(java.lang.String, java.lang.String);
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotSendCall instanceSpot();
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotSendCall instanceSpot(java.lang.String);
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotSendCall inMesh(java.lang.String);
 public abstract java.lang.Object await(kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
public interface systems.zlink.framework.kotlin.ZLinkKotlinSpotRequestCall<TReply> {
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotRequestCall<TReply> metadata(java.lang.String, java.lang.String);
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotRequestCall<TReply> instanceSpot();
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotRequestCall<TReply> instanceSpot(java.lang.String);
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotRequestCall<TReply> inMesh(java.lang.String);
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotRequestCall<TReply> timeout-LRDsOJo(long);
 public abstract java.lang.Object await(kotlin.coroutines.Continuation<? super TReply>);
 public abstract java.lang.Object yield(kotlin.coroutines.Continuation<? super TReply>);
}
public interface systems.zlink.framework.kotlin.ZLinkKotlinSpotCreateCall {
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotCreateCall inMesh(java.lang.String);
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotCreateCall request(java.lang.Object);
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotCreateCall timeout-LRDsOJo(long);
 public abstract java.lang.Object await(kotlin.coroutines.Continuation<? super systems.zlink.framework.spots.ZLinkSpotCreateResult>);
 public abstract java.lang.Object yield(kotlin.coroutines.Continuation<? super systems.zlink.framework.spots.ZLinkSpotCreateResult>);
}
public interface systems.zlink.framework.kotlin.ZLinkKotlinSpotManager {
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotCreateCall create(java.lang.String);
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSpotCreateCall getOrCreate(java.lang.String, java.lang.String);
}

A Kotlin suspending handler uses the same activation ownership as the Java runtime. A Spot handler is created once per Spot activation, and an Actor handler once per Actor activation — different Actors don't share a handler instance or scoped dependency. A same-node Join keeps the Actor handler, and a cross-node Join and relocation re-create it in the target activation. Coroutine continuation doesn't extend the handler instance lifetime or get included in the relocation payload.

Kotlin doesn't provide an address DTO, process-local handle, resolver, or unbounded directory. The Kotlin-facing route client and manager return a dedicated wrapper that preserves fluent options and single-use state, and doesn't expose the Java call, CompletionStage, or Class<T> to the application. close(SpotRef) handles Missing as false, a generation mismatch as InvalidOperation, and a sealed handoff window as Unavailable, only targeting User Spot. An Instance Spot's self-close uses Java's ZLinkInstanceSpotContext.close() unchanged.

The maintenance target restores the Actor adapter and queue/timer, commits Location authority/membership, and then starts Actor message processing. Session–Actor binding §8.2 owns relocation route updates for a bound Session. Infrastructure relocation doesn't call onJoinedActorSuspending, onLeaveActorSuspending, or a separate relocation callback. Only a regular User Spot application join uses onActorJoinSuspending and onJoinedActorSuspending. A new Actor's first creation only uses onCreateActorSuspending's approval and optional reply, without calling the join/joined callback. Returning from a User Spot to an Entry Spot calls the target's onJoinedActorSuspending and the source's onLeaveActorSuspending. Neither the SpotWide User Spot aggregate nor the PerActor User Spot's Actor relocation calls any of the member's Entry/User Spot membership callbacks.

The default User Spot factory mode is SPOT_WIDE. In this mode, the suspending Spot/Actor/timer/lifecycle callback keeps the User Spot gate during a regular suspension. A member Actor also keeps the Actor FIFO claim together. Only the yield() of a request/worker/Actor/Spot create wrapper returns the gate and, after terminal completion, re-acquires the same gate to run the coroutine continuation. In PER_ACTOR, per-Actor lanes, the Spot direct/lifecycle lane, and per-timer lanes are independent, and suspension only holds that lane's permit. Different Actors and different timers can run concurrently. SPOT_WIDE's Close/relocation/snapshot seals new admission and only proceeds after an all-lane barrier where every active lane, including coroutine continuations, reaches a safe turn boundary. A barrier failure aborts the whole seal of the same generation and restores application admission exactly.

A PER_ACTOR User Spot only allows recreateOnRelocation(). The Spot adapter, Spot fields, and a Spot-level application timer aren't relocation targets. Shared state and schedules that must be kept are placed in an external store the application owns, such as Redis or a database. The framework prepares a stateless shell on the target with the same public Spot ID and ObjectGeneration, and switches Spot authority first. Each Actor is independently relocated, together with its queue/accepted journal/Actor timer, in the order it finishes its current turn. The target shell isn't exposed to public lookup before authority. 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.

If relocationCoordinationMode(...) is omitted in the factory configure callback, it's FRAMEWORK_MANAGED. APPLICATION_SIGNALED is only allowed with SPOT_WIDE. On a Spot turn in this mode, context.relocationReady().defer() registers a boundary right after the current turn. The framework delivers the source's CONTINUED or the target's RELOCATED completion to onRelocationReadyCompletedSuspending(...), whose default implementation is a no-op. Held messages and timers aren't run before the callback completes.

A duplicate defer() on the default mode, PER_ACTOR, Entry/Instance Spot, outside the Spot turn, or in the same turn is INVALID_OPERATION before any queue mutation. Since the callback can run again during recovery, the override must be retry-safe.

Yield is only provided for Channel/Spot/Actor request, I/O/CPU worker, and Actor/Spot create/get-or-create. Outside Entry Spot/Entry Actor/ PER_ACTOR/Node/Channel/the owner context, it completes with InvalidOperation before coroutine suspension, operation submission, queue mutation, and gate return.