@zlink-systems/framework 0.32.1 → 0.32.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,11 @@
1
- import type { ApplicationJobOrigin } from '../application-jobs/contracts';
2
- import type { ZLinkActorRequestTerminal } from '../spots/spot-actor-packet-dispatch';
3
- import type { ZLinkMessageFollowOrigin } from '../foundation/service-runtime-contracts';
4
1
  import type { ActorRef, ZLinkActor } from '../../contracts';
5
- import type { Message } from '../../contracts/Common/Message';
6
2
  import type { ZLinkBackendSpotNode } from '../backend';
7
- import type { DefaultZLinkActorManager, ZLinkRemoteBoundSessionTarget } from '../actors';
3
+ import type { DefaultZLinkActorManager } from '../actors';
8
4
  import type { DefaultZLinkSpotManager, ZLinkSpotNodeRuntimeManager } from '../spots';
9
5
  import type { ZLinkEntryActorRuntime } from '../spots/spot-runtime-ports';
10
6
  import type { ZLinkStreamActorLifecyclePort } from '../streams/stream-binding-runtime-ports';
11
7
  import type { ZLinkBoundSessionRelay } from './bound-session-relay';
8
+ import type { ZLinkActorIngressMembership } from '../spots/spot-actor-ingress-admission';
12
9
  export interface ZLinkEntryActorRuntimeOptions {
13
10
  readonly actorManager: () => DefaultZLinkActorManager | undefined;
14
11
  readonly spotManager: () => DefaultZLinkSpotManager | undefined;
@@ -26,9 +23,6 @@ export declare class ZLinkEntryActorRuntimeService implements ZLinkEntryActorRun
26
23
  releaseMembers(meshName: string): Promise<void>;
27
24
  commitActorTransaction(actor: ZLinkActor, onJoined: () => Promise<void>): Promise<void>;
28
25
  destroyActor(node: ZLinkBackendSpotNode, entryNodeRid: ActorRef['nodeRid'], actor: ZLinkActor, signal?: AbortSignal): Promise<void>;
29
- routePacket(actorId: string, parts: readonly Message[], returnResponse?: boolean, remoteBoundSessionTarget?: ZLinkRemoteBoundSessionTarget, fallbackActorRef?: ActorRef, origin?: ApplicationJobOrigin, requestTerminal?: ZLinkActorRequestTerminal, messageFollowOrigin?: ZLinkMessageFollowOrigin): {
30
- readonly handled: boolean;
31
- readonly response?: unknown;
32
- };
26
+ get actorIngressMembership(): ZLinkActorIngressMembership | undefined;
33
27
  private requireActorManager;
34
28
  }
@@ -61,16 +61,8 @@ class ZLinkEntryActorRuntimeService {
61
61
  async destroyActor(node, entryNodeRid, actor, signal) {
62
62
  await this.requireActorManager().destroyActor(node, entryNodeRid, actor, signal);
63
63
  }
64
- routePacket(actorId, parts, returnResponse, remoteBoundSessionTarget, fallbackActorRef, origin = 'remote', requestTerminal, messageFollowOrigin) {
65
- const spotManager = this.options.spotManager();
66
- const spotId = this.options.actorManager()?.getState(actorId)?.spotId;
67
- if (spotId === undefined || spotManager === undefined) {
68
- return { handled: false };
69
- }
70
- return {
71
- handled: true,
72
- response: spotManager.dispatchRoutedActorPacket(origin, spotId, actorId, parts, returnResponse, remoteBoundSessionTarget, fallbackActorRef, requestTerminal, messageFollowOrigin)
73
- };
64
+ get actorIngressMembership() {
65
+ return this.options.spotManager()?.actorIngressMembership;
74
66
  }
75
67
  requireActorManager() {
76
68
  const manager = this.options.actorManager();
@@ -165,7 +165,8 @@ class ZLinkSpotRuntimeOptionsFactory {
165
165
  : (0, actors_1.toFrameworkActorRef)(state.nativeActorRef, state.meshName ?? '');
166
166
  const localNodeRid = state?.meshName === undefined ? undefined : this.meshNodeRoutingId(state.meshName);
167
167
  if (state?.actor === undefined ||
168
- state.remoteActorPacketTarget !== undefined ||
168
+ (state.remoteActorPacketTarget !== undefined &&
169
+ !(0, routing_id_1.routingIdsEqual)(state.remoteActorPacketTarget.targetNodeRid, localNodeRid)) ||
169
170
  actorRef === undefined ||
170
171
  localNodeRid === undefined ||
171
172
  !(0, routing_id_1.routingIdsEqual)(actorRef.nodeRid, localNodeRid)) {
@@ -1,4 +1,5 @@
1
1
  import { inspectRuntime } from '../internal-observation';
2
+ import type { ZLinkActorDispatchOwner, ZLinkActorIngressMembership } from './spot-actor-ingress-admission';
2
3
  import type { ApplicationJobOrigin } from '../application-jobs/contracts';
3
4
  import type { ActorRef, RoutingId, Type, ZLinkMessageSerializer, ZLinkInstanceSpot, ZLinkActor, ZLinkChannelClient, ZLinkFanoutClient, ZLinkSpot, ZLinkSpotActorJoinResult, ZLinkSpotInfo, ZLinkSpotPublisherClient } from '../../contracts';
4
5
  import type { ZLinkProviderResolver } from '../../contracts/Common/ZLinkProviderResolver';
@@ -73,10 +74,7 @@ export interface ZLinkSpotManagerOptions {
73
74
  readonly releaseActorMembers?: (meshName: string, spotId: RoutingId) => Promise<void>;
74
75
  readonly userSpotExecutionMode?: (meshName: string, spotType: Type<ZLinkSpot>) => import('../../contracts').ZLinkUserSpotExecutionMode;
75
76
  readonly userSpotRelocationCoordinationMode?: (meshName: string, spotType: Type<ZLinkSpot>) => import('../../contracts').ZLinkSpotRelocationCoordinationMode;
76
- readonly actorDispatchOwnerResolver?: (actorId: string) => {
77
- readonly actorRef?: ActorRef;
78
- readonly spotId?: RoutingId;
79
- };
77
+ readonly actorDispatchOwnerResolver?: (actorId: string) => ZLinkActorDispatchOwner;
80
78
  readonly channelClient?: ZLinkChannelClient;
81
79
  readonly fanoutClient?: ZLinkFanoutClient;
82
80
  readonly spotPublisherClient?: ZLinkSpotPublisherClient;
@@ -139,6 +137,7 @@ interface ZLinkTargetSpotCloseOperation {
139
137
  }
140
138
  export declare class DefaultZLinkSpotManager {
141
139
  private readonly options;
140
+ readonly actorIngressMembership: ZLinkActorIngressMembership;
142
141
  [inspectRuntime](): Readonly<{
143
142
  activeActivationCount: number;
144
143
  hasCloseableActivation: (meshName: string, spotId: RoutingId) => boolean;
@@ -225,6 +224,7 @@ export declare class DefaultZLinkSpotManager {
225
224
  cancelActorTransfer(spotId: RoutingId, actorId: string): Promise<void>;
226
225
  notifyJoinedSpotActorDisconnected(spotId: RoutingId, actor: ZLinkActor, signal?: AbortSignal): Promise<boolean>;
227
226
  dispatchRoutedActorPacket(origin: ApplicationJobOrigin, spotId: RoutingId, actorId: string, parts: readonly Message[], returnResponse?: boolean, remoteBoundSessionTarget?: ZLinkRemoteBoundSessionTarget, fallbackActorRef?: ActorRef, requestTerminal?: (response: unknown) => Promise<void> | void, messageFollowOrigin?: ZLinkMessageFollowOrigin): Promise<unknown>;
227
+ private dispatchActorPacketToOwner;
228
228
  /** Resolves the current Actor FIFO owner for both Entry and User membership. */
229
229
  admitSourceActorPacketPrefix(actorId: string, records: readonly ZLinkActorHandoffPrefixRecord[]): ZLinkActorHandoffPrefixAdmission;
230
230
  /** Runs an owner-side lifecycle decision in the Actor's existing execution gate. */
@@ -69,6 +69,7 @@ var spot_manager_public_1 = require("./spot-manager-public");
69
69
  Object.defineProperty(exports, "ZLinkPublicSpotManager", { enumerable: true, get: function () { return spot_manager_public_1.ZLinkPublicSpotManager; } });
70
70
  class DefaultZLinkSpotManager {
71
71
  options;
72
+ actorIngressMembership;
72
73
  [internal_observation_1.inspectRuntime]() {
73
74
  return Object.freeze({
74
75
  activeActivationCount: this.activations.activeActivations().length,
@@ -133,6 +134,10 @@ class DefaultZLinkSpotManager {
133
134
  spotRouteResolver: options.spotRouteResolver,
134
135
  actorTransferRuntime: options.actorTransferRuntime
135
136
  });
137
+ this.actorIngressMembership = {
138
+ resolveOwner: options.actorDispatchOwnerResolver,
139
+ dispatchToOwner: (spotId, delivery) => this.dispatchActorPacketToOwner(spotId, delivery)
140
+ };
136
141
  this.activationLifecycle = new spot_activation_1.ZLinkSpotActivationLifecycle({
137
142
  timerClock,
138
143
  spotTimerHandlers: options.spotTimerHandlers,
@@ -163,6 +168,7 @@ class DefaultZLinkSpotManager {
163
168
  createTopicMessage: options.createTopicMessage,
164
169
  nativeSpotNodeProvider: options.nativeSpotNodeProvider,
165
170
  actorResolver: options.actorResolver,
171
+ actorIngressMembership: this.actorIngressMembership,
166
172
  detachedTaskRunner: options.detachedTaskRunner,
167
173
  actorTransferRuntime: options.actorTransferRuntime,
168
174
  boundSessionRuntime: options.boundSessionRuntime,
@@ -920,6 +926,24 @@ class DefaultZLinkSpotManager {
920
926
  }
921
927
  return this.dispatchActorPacket(origin, activation, actorId, parts, returnResponse, remoteBoundSessionTarget, fallbackActorRef, requestTerminal, messageFollowOrigin);
922
928
  }
929
+ dispatchActorPacketToOwner(spotId, delivery) {
930
+ const args = [
931
+ delivery.actorId,
932
+ delivery.parts,
933
+ delivery.returnResponse,
934
+ delivery.remoteBoundSessionTarget,
935
+ delivery.fallbackActorRef,
936
+ delivery.requestTerminal,
937
+ delivery.messageFollowOrigin
938
+ ];
939
+ if (spotId !== undefined) {
940
+ return this.dispatchRoutedActorPacket(delivery.origin, spotId, ...args);
941
+ }
942
+ if (this.options.dispatchEntryActorPacket === undefined) {
943
+ throw new configuration_1.ZLinkConfigurationException('Actor ingress requires an Entry FIFO port.');
944
+ }
945
+ return this.options.dispatchEntryActorPacket(delivery.origin, ...args);
946
+ }
923
947
  /** @internal Replays a record already owned by the Actor handoff queue. */
924
948
  dispatchRoutedActorPacketDirect(origin, spotId, actorId, parts, returnResponse = false, remoteBoundSessionTarget, fallbackActorRef) {
925
949
  const activation = this.activations.resolveUnique(spotId);
@@ -15,6 +15,7 @@ import { type ZLinkSpotAddressTransport, type ZLinkSpotRoutedTransport } from '.
15
15
  import { type ZLinkTimerClock, type ZLinkTimerRelocationState } from './spot-timer';
16
16
  import type { ZLinkDetachedTaskRunner } from './spot-actor-join-dispatch';
17
17
  import type { ZLinkRouteToActorJoinPrewarm } from './spot-actor-packet-dispatch';
18
+ import type { ZLinkActorIngressMembership } from './spot-actor-ingress-admission';
18
19
  import { ZLinkSpotActivation } from './spot-activation-state';
19
20
  import type { ZLinkSpotLocationClaim } from './spot-location-claim';
20
21
  import type { ZLinkSpotActorHandoffRuntime, ZLinkSpotActorTransferRuntime, ZLinkSpotBoundSessionRuntime } from './spot-runtime-ports';
@@ -49,6 +50,7 @@ export interface ZLinkSpotActivationLifecycleOptions {
49
50
  readonly createTopicMessage?: () => ZLinkBackendTopicMessage;
50
51
  readonly nativeSpotNodeProvider?: (meshName: string) => ZLinkBackendSpotNode | undefined;
51
52
  readonly actorResolver?: (actorId: string) => ZLinkActor | undefined;
53
+ readonly actorIngressMembership?: ZLinkActorIngressMembership;
52
54
  readonly actorTransferRuntime?: ZLinkSpotActorTransferRuntime;
53
55
  readonly boundSessionRuntime?: ZLinkSpotBoundSessionRuntime;
54
56
  readonly actorHandoffRuntime?: ZLinkSpotActorHandoffRuntime;
@@ -8,7 +8,7 @@ import type { ZLinkSpotActivationLifecycleOptions } from './spot-activation';
8
8
  import { ZLinkSpotActivation } from './spot-activation-state';
9
9
  import { ZLinkSpotActorJoinDispatch } from './spot-actor-join-dispatch';
10
10
  import type { ZLinkActorPacketExecution } from './spot-actor-packet-dispatch';
11
- type AdmissionOptions = Pick<ZLinkSpotActivationLifecycleOptions, 'actorHandoffRuntime' | 'admission' | 'actorResolver' | 'actorTransferRuntime' | 'boundSessionRuntime' | 'detachedTaskRunner' | 'dispatchErrors' | 'locationClaim' | 'messageSerializers' | 'nativeSpotNodeProvider' | 'createTopicMessage' | 'providerResolver' | 'runtimeEventPublisher' | 'routeToActorJoinPrewarm'>;
11
+ type AdmissionOptions = Pick<ZLinkSpotActivationLifecycleOptions, 'actorHandoffRuntime' | 'admission' | 'actorResolver' | 'actorIngressMembership' | 'actorTransferRuntime' | 'boundSessionRuntime' | 'detachedTaskRunner' | 'dispatchErrors' | 'locationClaim' | 'messageSerializers' | 'nativeSpotNodeProvider' | 'createTopicMessage' | 'providerResolver' | 'runtimeEventPublisher' | 'routeToActorJoinPrewarm'>;
12
12
  export declare class ZLinkSpotActorAdmissionCoordinator {
13
13
  private readonly options;
14
14
  constructor(options: AdmissionOptions);
@@ -96,7 +96,8 @@ class ZLinkSpotActorAdmissionCoordinator {
96
96
  resolveActor: (targetActorId) => this.options.actorResolver === undefined
97
97
  ? activation.resolveJoinedActor(targetActorId)
98
98
  : this.options.actorResolver(targetActorId),
99
- hasDepartedActor: (targetActorId) => activation.hasDepartedActor(targetActorId),
99
+ membershipSpotId: activation.spotId,
100
+ membership: this.options.actorIngressMembership,
100
101
  onDisconnectActor: (actor) => activation.serial.execute(() => activation.spot.onDisconnectActor?.(actor)),
101
102
  actorResponseSender: this.options.boundSessionRuntime?.sendActorResponse.bind(this.options.boundSessionRuntime),
102
103
  actorErrorSender: this.options.boundSessionRuntime?.sendActorError.bind(this.options.boundSessionRuntime),
@@ -1,4 +1,4 @@
1
- import type { ActorRef, ZLinkActor } from '../../contracts';
1
+ import type { ActorRef, RoutingId, ZLinkActor } from '../../contracts';
2
2
  import type { ZLinkActorPacketDelivery, ZLinkActorRequestTerminal } from './spot-actor-packet-dispatch';
3
3
  import type { ZLinkRemoteBoundSessionTarget } from '../actors';
4
4
  import type { ApplicationJobOrigin } from '../application-jobs/contracts';
@@ -16,10 +16,14 @@ export type ZLinkRouteToActorJoinPrewarm = (actorId: string, objectGeneration: b
16
16
  readonly resolve: (value: unknown) => void;
17
17
  readonly reject: (reason: unknown) => void;
18
18
  }) => 'parked' | 'not-found';
19
- export type ZLinkActorIngressRoute = (delivery: ZLinkActorPacketDelivery) => {
20
- readonly handled: boolean;
21
- readonly response?: unknown;
22
- } | undefined;
19
+ export interface ZLinkActorDispatchOwner {
20
+ readonly actorRef?: ActorRef;
21
+ readonly spotId?: RoutingId;
22
+ }
23
+ export interface ZLinkActorIngressMembership {
24
+ readonly resolveOwner?: (actorId: string) => ZLinkActorDispatchOwner;
25
+ readonly dispatchToOwner?: (spotId: RoutingId | undefined, delivery: ZLinkActorPacketDelivery) => Promise<unknown>;
26
+ }
23
27
  export type ZLinkActorIngressAdmission = {
24
28
  readonly kind: 'local';
25
29
  readonly actor: ZLinkActor;
@@ -33,8 +37,8 @@ export type ZLinkActorIngressAdmission = {
33
37
  /** Fixes one packet owner synchronously at the Actor FIFO head. */
34
38
  export declare function resolveActorIngress(delivery: ZLinkActorPacketDelivery, request: boolean, options: {
35
39
  readonly handoffRuntime?: ZLinkSpotActorHandoffRuntime;
36
- readonly routeBeforeLocal?: ZLinkActorIngressRoute;
40
+ readonly spotId?: RoutingId;
41
+ readonly membership?: ZLinkActorIngressMembership;
37
42
  readonly resolveActor: (actorId: string) => ZLinkActor | undefined;
38
- readonly hasDepartedActor?: (actorId: string) => boolean;
39
43
  readonly routeToActorJoinPrewarm?: ZLinkRouteToActorJoinPrewarm;
40
44
  }): ZLinkActorIngressAdmission;
@@ -2,18 +2,26 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveActorIngress = resolveActorIngress;
4
4
  const actor_message_follow_context_1 = require("../actors/actor-message-follow-context");
5
+ const routing_id_1 = require("../routing-id");
5
6
  const framework_errors_internal_1 = require("../framework-errors-internal");
6
7
  /** Fixes one packet owner synchronously at the Actor FIFO head. */
7
8
  function resolveActorIngress(delivery, request, options) {
8
9
  const handoff = options.handoffRuntime?.capture(delivery.origin, delivery.actorId, delivery.parts, delivery.returnResponse, delivery.remoteBoundSessionTarget, delivery.fallbackActorRef, undefined, delivery.messageFollowOrigin);
9
10
  if (handoff !== undefined)
10
11
  return { kind: 'follow', terminal: handoff };
11
- const route = options.routeBeforeLocal?.(delivery);
12
- if (route?.handled === true)
13
- return { kind: 'follow', terminal: Promise.resolve(route.response) };
14
- const actor = options.hasDepartedActor?.(delivery.actorId) === true
15
- ? undefined
16
- : options.resolveActor(delivery.actorId);
12
+ const membership = options.membership;
13
+ const owner = membership?.resolveOwner?.(delivery.actorId);
14
+ if (owner?.actorRef !== undefined && !(0, routing_id_1.routingIdsEqual)(owner.spotId, options.spotId)) {
15
+ if (membership?.dispatchToOwner === undefined) {
16
+ throw new Error('Actor ingress requires the current membership dispatch port.');
17
+ }
18
+ // Transfer the accepted delivery before releasing the source FIFO claim.
19
+ // Its terminal is awaited outside that claim by dispatchActorPacketInMailbox.
20
+ return { kind: 'follow', terminal: membership.dispatchToOwner(owner.spotId, delivery) };
21
+ }
22
+ const actor = membership?.resolveOwner === undefined || owner?.actorRef !== undefined
23
+ ? options.resolveActor(delivery.actorId)
24
+ : undefined;
17
25
  if (actor !== undefined)
18
26
  return { kind: 'local', actor };
19
27
  if (options.routeToActorJoinPrewarm !== undefined &&
@@ -1,4 +1,4 @@
1
- import { type ZLinkRouteToActorJoinPrewarm, type ZLinkActorIngressRoute } from './spot-actor-ingress-admission';
1
+ import { type ZLinkRouteToActorJoinPrewarm, type ZLinkActorIngressMembership } from './spot-actor-ingress-admission';
2
2
  import type { ZLinkSpotActorHandoffRuntime } from './spot-runtime-ports';
3
3
  import type { ApplicationJobOrigin } from '../application-jobs/contracts';
4
4
  import type { ActorRef, RoutingId, ZLinkActor, ZLinkMessageSerializer, ZLinkSpot } from '../../contracts';
@@ -38,8 +38,8 @@ interface ZLinkSpotActorPacketDispatchOptions {
38
38
  readonly spotId: () => string;
39
39
  readonly registry: ZLinkSpotActorHandlerRegistryRuntime;
40
40
  readonly resolveActor: (actorId: string) => ZLinkActor | undefined;
41
- readonly hasDepartedActor?: (actorId: string) => boolean;
42
- readonly routeBeforeLocal?: ZLinkActorIngressRoute;
41
+ readonly membershipSpotId?: RoutingId;
42
+ readonly membership?: ZLinkActorIngressMembership;
43
43
  readonly onDisconnectActor: (actor: ZLinkActor) => Promise<void>;
44
44
  readonly actorResponseSender?: (actor: ZLinkActor, packetName: string, requestSeq: bigint, response: unknown, replyOptions: ZLinkActorResponseOptions, fallbackBoundSessionTarget?: ZLinkRemoteBoundSessionTarget, fallbackActorRef?: ActorRef, signal?: AbortSignal) => Promise<void> | void;
45
45
  readonly actorErrorSender?: (actorId: string, packetName: string, requestSeq: bigint, error: unknown, metadata: ReadonlyMap<string, string>, fallbackBoundSessionTarget?: ZLinkRemoteBoundSessionTarget, fallbackActorRef?: ActorRef) => Promise<void> | void;
@@ -80,9 +80,9 @@ class ZLinkSpotActorPacketDispatch {
80
80
  : undefined;
81
81
  const admitted = (0, spot_actor_ingress_admission_1.resolveActorIngress)(delivery, messageKind === ZLinkDispatchOptions_1.ZLinkDispatchMessageKind.ActorRequest, {
82
82
  handoffRuntime,
83
- routeBeforeLocal: this.options.routeBeforeLocal,
83
+ spotId: this.options.membershipSpotId,
84
+ membership: this.options.membership,
84
85
  resolveActor: this.options.resolveActor,
85
- hasDepartedActor: this.options.hasDepartedActor,
86
86
  routeToActorJoinPrewarm: this.options.routeToActorJoinPrewarm
87
87
  });
88
88
  if ('terminal' in admitted)
@@ -100,7 +100,7 @@ class ZLinkEntrySpotActivation {
100
100
  spotId: () => String(this.options.nativeSpot.routingId),
101
101
  registry: this.actorHandlers,
102
102
  resolveActor: (targetActorId) => this.options.entryActorRuntime?.resolveActor(targetActorId),
103
- routeBeforeLocal: (delivery) => this.options.entryActorRuntime?.routePacket(delivery.actorId, delivery.parts, delivery.returnResponse, delivery.remoteBoundSessionTarget, delivery.fallbackActorRef, delivery.origin, delivery.requestTerminal, delivery.messageFollowOrigin),
103
+ membership: this.options.entryActorRuntime?.actorIngressMembership,
104
104
  onDisconnectActor: (actor) => this.notifyDisconnectActor(actor),
105
105
  actorResponseSender: this.options.boundSessionRuntime?.sendActorResponse.bind(this.options.boundSessionRuntime),
106
106
  actorErrorSender: this.options.boundSessionRuntime?.sendActorError.bind(this.options.boundSessionRuntime),
@@ -7,15 +7,13 @@ import type { ZLinkActorHandoffDispatch } from '../actors/actor-handoff';
7
7
  import type { ZLinkActorResponseOptions } from './spot-actor-packet-dispatch';
8
8
  import type { ZLinkRemoteActorJoinActor, ZLinkRoutedActorTransferProvider } from './spot-remote-codec';
9
9
  import type { ZLinkBackendSpotNode } from '../backend/contracts';
10
+ import type { ZLinkActorIngressMembership } from './spot-actor-ingress-admission';
10
11
  export interface ZLinkEntryActorRuntime {
12
+ readonly actorIngressMembership?: ZLinkActorIngressMembership;
11
13
  releaseMembers?(meshName: string): Promise<void>;
12
14
  resolveActor(actorId: string): ZLinkActor | undefined;
13
15
  commitActorTransaction(actor: ZLinkActor, onJoined: () => Promise<void>): Promise<void>;
14
16
  destroyActor(node: ZLinkBackendSpotNode, entryNodeRid: RoutingId, actor: ZLinkActor, signal?: AbortSignal): Promise<void>;
15
- routePacket(actorId: string, parts: readonly Message[], returnResponse?: boolean, remoteBoundSessionTarget?: ZLinkRemoteBoundSessionTarget, fallbackActorRef?: ActorRef, origin?: ApplicationJobOrigin, requestTerminal?: import('./spot-actor-packet-dispatch').ZLinkActorRequestTerminal, messageFollowOrigin?: ZLinkMessageFollowOrigin): {
16
- readonly handled: boolean;
17
- readonly response?: unknown;
18
- };
19
17
  }
20
18
  export interface ZLinkSpotActorTransferRuntime {
21
19
  prepareDeferredJoinAccepted(actorId: string, operationId: ZLinkActorJoinOperationId, actorRef: ActorRef, rawReply: Uint8Array, replyContentType?: string): ZLinkDeferredJoinCompletion;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zlink-systems/framework",
3
- "version": "0.32.1",
3
+ "version": "0.32.2",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  "@opentelemetry/api": "^1.9.1",
21
21
  "@opentelemetry/api-logs": "^0.221.0",
22
- "@zlink-systems/stream-wire": "0.32.1",
22
+ "@zlink-systems/stream-wire": "0.32.2",
23
23
  "@zlink-systems/zlink": "1.23.0"
24
24
  }
25
25
  }
@@ -1,15 +1,12 @@
1
- import type { ApplicationJobOrigin } from '../application-jobs/contracts';
2
- import type { ZLinkActorRequestTerminal } from '../spots/spot-actor-packet-dispatch';
3
- import type { ZLinkMessageFollowOrigin } from '../foundation/service-runtime-contracts';
4
1
  import type { ActorRef, ZLinkActor } from '../../contracts';
5
- import type { Message } from '../../contracts/Common/Message';
6
2
  import type { ZLinkBackendActorRef, ZLinkBackendSpotNode } from '../backend';
7
- import type { DefaultZLinkActorManager, ZLinkRemoteBoundSessionTarget } from '../actors';
3
+ import type { DefaultZLinkActorManager } from '../actors';
8
4
  import { ZLinkPostCommitActorBinder } from '../actors/post-commit-actor-binder';
9
5
  import type { DefaultZLinkSpotManager, ZLinkSpotNodeRuntimeManager } from '../spots';
10
6
  import type { ZLinkEntryActorRuntime } from '../spots/spot-runtime-ports';
11
7
  import type { ZLinkStreamActorLifecyclePort } from '../streams/stream-binding-runtime-ports';
12
8
  import type { ZLinkBoundSessionRelay } from './bound-session-relay';
9
+ import type { ZLinkActorIngressMembership } from '../spots/spot-actor-ingress-admission';
13
10
 
14
11
  export interface ZLinkEntryActorRuntimeOptions {
15
12
  readonly actorManager: () => DefaultZLinkActorManager | undefined;
@@ -87,35 +84,8 @@ export class ZLinkEntryActorRuntimeService implements ZLinkEntryActorRuntime {
87
84
  await this.requireActorManager().destroyActor(node, entryNodeRid, actor, signal);
88
85
  }
89
86
 
90
- routePacket(
91
- actorId: string,
92
- parts: readonly Message[],
93
- returnResponse?: boolean,
94
- remoteBoundSessionTarget?: ZLinkRemoteBoundSessionTarget,
95
- fallbackActorRef?: ActorRef,
96
- origin: ApplicationJobOrigin = 'remote',
97
- requestTerminal?: ZLinkActorRequestTerminal,
98
- messageFollowOrigin?: ZLinkMessageFollowOrigin
99
- ): { readonly handled: boolean; readonly response?: unknown } {
100
- const spotManager = this.options.spotManager();
101
- const spotId = this.options.actorManager()?.getState(actorId)?.spotId;
102
- if (spotId === undefined || spotManager === undefined) {
103
- return { handled: false };
104
- }
105
- return {
106
- handled: true,
107
- response: spotManager.dispatchRoutedActorPacket(
108
- origin,
109
- spotId,
110
- actorId,
111
- parts,
112
- returnResponse,
113
- remoteBoundSessionTarget,
114
- fallbackActorRef,
115
- requestTerminal,
116
- messageFollowOrigin
117
- )
118
- };
87
+ get actorIngressMembership(): ZLinkActorIngressMembership | undefined {
88
+ return this.options.spotManager()?.actorIngressMembership;
119
89
  }
120
90
 
121
91
  private requireActorManager(): DefaultZLinkActorManager {
@@ -315,7 +315,8 @@ export class ZLinkSpotRuntimeOptionsFactory {
315
315
  state?.meshName === undefined ? undefined : this.meshNodeRoutingId(state.meshName);
316
316
  if (
317
317
  state?.actor === undefined ||
318
- state.remoteActorPacketTarget !== undefined ||
318
+ (state.remoteActorPacketTarget !== undefined &&
319
+ !routingIdsEqual(state.remoteActorPacketTarget.targetNodeRid, localNodeRid)) ||
319
320
  actorRef === undefined ||
320
321
  localNodeRid === undefined ||
321
322
  !routingIdsEqual(actorRef.nodeRid, localNodeRid)
@@ -4,6 +4,11 @@ import { encodeActorReply } from '../actors/actor-reply';
4
4
  import { boundSessionErrorPayload } from '../streams/bound-session-response-target';
5
5
  import { RELOCATED_REQUEST_OWNED_BY_REQUESTER } from '../actors/actor-handoff';
6
6
  import { routingIdsEqual } from '../routing-id';
7
+ import type { ZLinkActorPacketDelivery } from './spot-actor-packet-dispatch';
8
+ import type {
9
+ ZLinkActorDispatchOwner,
10
+ ZLinkActorIngressMembership
11
+ } from './spot-actor-ingress-admission';
7
12
  import type { ApplicationJobOrigin } from '../application-jobs/contracts';
8
13
  import { dispatchReasonFromError } from '../diagnostics/dispatch-error-details';
9
14
  import type {
@@ -231,10 +236,7 @@ export interface ZLinkSpotManagerOptions {
231
236
  meshName: string,
232
237
  spotType: Type<ZLinkSpot>
233
238
  ) => import('../../contracts').ZLinkSpotRelocationCoordinationMode;
234
- readonly actorDispatchOwnerResolver?: (actorId: string) => {
235
- readonly actorRef?: ActorRef;
236
- readonly spotId?: RoutingId;
237
- };
239
+ readonly actorDispatchOwnerResolver?: (actorId: string) => ZLinkActorDispatchOwner;
238
240
  readonly channelClient?: ZLinkChannelClient;
239
241
  readonly fanoutClient?: ZLinkFanoutClient;
240
242
  readonly spotPublisherClient?: ZLinkSpotPublisherClient;
@@ -337,6 +339,7 @@ interface ZLinkTargetSpotCloseOperation {
337
339
  }
338
340
 
339
341
  export class DefaultZLinkSpotManager {
342
+ readonly actorIngressMembership: ZLinkActorIngressMembership;
340
343
  [inspectRuntime]() {
341
344
  return Object.freeze({
342
345
  activeActivationCount: this.activations.activeActivations().length,
@@ -414,6 +417,10 @@ export class DefaultZLinkSpotManager {
414
417
  spotRouteResolver: options.spotRouteResolver,
415
418
  actorTransferRuntime: options.actorTransferRuntime
416
419
  });
420
+ this.actorIngressMembership = {
421
+ resolveOwner: options.actorDispatchOwnerResolver,
422
+ dispatchToOwner: (spotId, delivery) => this.dispatchActorPacketToOwner(spotId, delivery)
423
+ };
417
424
  this.activationLifecycle = new ZLinkSpotActivationLifecycle({
418
425
  timerClock,
419
426
  spotTimerHandlers: options.spotTimerHandlers,
@@ -444,6 +451,7 @@ export class DefaultZLinkSpotManager {
444
451
  createTopicMessage: options.createTopicMessage,
445
452
  nativeSpotNodeProvider: options.nativeSpotNodeProvider,
446
453
  actorResolver: options.actorResolver,
454
+ actorIngressMembership: this.actorIngressMembership,
447
455
  detachedTaskRunner: options.detachedTaskRunner,
448
456
  actorTransferRuntime: options.actorTransferRuntime,
449
457
  boundSessionRuntime: options.boundSessionRuntime,
@@ -1628,6 +1636,28 @@ export class DefaultZLinkSpotManager {
1628
1636
  );
1629
1637
  }
1630
1638
 
1639
+ private dispatchActorPacketToOwner(
1640
+ spotId: RoutingId | undefined,
1641
+ delivery: ZLinkActorPacketDelivery
1642
+ ): Promise<unknown> {
1643
+ const args = [
1644
+ delivery.actorId,
1645
+ delivery.parts,
1646
+ delivery.returnResponse,
1647
+ delivery.remoteBoundSessionTarget,
1648
+ delivery.fallbackActorRef,
1649
+ delivery.requestTerminal,
1650
+ delivery.messageFollowOrigin
1651
+ ] as const;
1652
+ if (spotId !== undefined) {
1653
+ return this.dispatchRoutedActorPacket(delivery.origin, spotId, ...args);
1654
+ }
1655
+ if (this.options.dispatchEntryActorPacket === undefined) {
1656
+ throw new ZLinkConfigurationException('Actor ingress requires an Entry FIFO port.');
1657
+ }
1658
+ return this.options.dispatchEntryActorPacket(delivery.origin, ...args);
1659
+ }
1660
+
1631
1661
  /** @internal Replays a record already owned by the Actor handoff queue. */
1632
1662
  dispatchRoutedActorPacketDirect(
1633
1663
  origin: ApplicationJobOrigin,
@@ -82,6 +82,7 @@ import type {
82
82
  } from './spot-actor-join-dispatch';
83
83
  import { ZLinkSpotActorAdmissionCoordinator } from './spot-actor-admission-coordinator';
84
84
  import type { ZLinkRouteToActorJoinPrewarm } from './spot-actor-packet-dispatch';
85
+ import type { ZLinkActorIngressMembership } from './spot-actor-ingress-admission';
85
86
  import { ZLinkSpotActivation } from './spot-activation-state';
86
87
  import type { ZLinkSpotLocationClaim } from './spot-location-claim';
87
88
  import type {
@@ -132,6 +133,7 @@ export interface ZLinkSpotActivationLifecycleOptions {
132
133
  readonly createTopicMessage?: () => ZLinkBackendTopicMessage;
133
134
  readonly nativeSpotNodeProvider?: (meshName: string) => ZLinkBackendSpotNode | undefined;
134
135
  readonly actorResolver?: (actorId: string) => ZLinkActor | undefined;
136
+ readonly actorIngressMembership?: ZLinkActorIngressMembership;
135
137
  readonly actorTransferRuntime?: ZLinkSpotActorTransferRuntime;
136
138
  readonly boundSessionRuntime?: ZLinkSpotBoundSessionRuntime;
137
139
  readonly actorHandoffRuntime?: ZLinkSpotActorHandoffRuntime;
@@ -25,6 +25,7 @@ type AdmissionOptions = Pick<
25
25
  | 'actorHandoffRuntime'
26
26
  | 'admission'
27
27
  | 'actorResolver'
28
+ | 'actorIngressMembership'
28
29
  | 'actorTransferRuntime'
29
30
  | 'boundSessionRuntime'
30
31
  | 'detachedTaskRunner'
@@ -225,7 +226,8 @@ export class ZLinkSpotActorAdmissionCoordinator {
225
226
  this.options.actorResolver === undefined
226
227
  ? activation.resolveJoinedActor(targetActorId)
227
228
  : this.options.actorResolver(targetActorId),
228
- hasDepartedActor: (targetActorId) => activation.hasDepartedActor(targetActorId),
229
+ membershipSpotId: activation.spotId,
230
+ membership: this.options.actorIngressMembership,
229
231
  onDisconnectActor: (actor) =>
230
232
  activation.serial.execute(() => activation.spot.onDisconnectActor?.(actor)),
231
233
  actorResponseSender: this.options.boundSessionRuntime?.sendActorResponse.bind(
@@ -1,5 +1,6 @@
1
1
  import { actorMessageFollowContext } from '../actors/actor-message-follow-context';
2
- import type { ActorRef, ZLinkActor } from '../../contracts';
2
+ import type { ActorRef, RoutingId, ZLinkActor } from '../../contracts';
3
+ import { routingIdsEqual } from '../routing-id';
3
4
  import type { Message } from '../../contracts/Common/Message';
4
5
  import type {
5
6
  ZLinkActorPacketDelivery,
@@ -30,9 +31,18 @@ export type ZLinkRouteToActorJoinPrewarm = (
30
31
  }
31
32
  ) => 'parked' | 'not-found';
32
33
 
33
- export type ZLinkActorIngressRoute = (
34
- delivery: ZLinkActorPacketDelivery
35
- ) => { readonly handled: boolean; readonly response?: unknown } | undefined;
34
+ export interface ZLinkActorDispatchOwner {
35
+ readonly actorRef?: ActorRef;
36
+ readonly spotId?: RoutingId;
37
+ }
38
+
39
+ export interface ZLinkActorIngressMembership {
40
+ readonly resolveOwner?: (actorId: string) => ZLinkActorDispatchOwner;
41
+ readonly dispatchToOwner?: (
42
+ spotId: RoutingId | undefined,
43
+ delivery: ZLinkActorPacketDelivery
44
+ ) => Promise<unknown>;
45
+ }
36
46
 
37
47
  export type ZLinkActorIngressAdmission =
38
48
  | { readonly kind: 'local'; readonly actor: ZLinkActor }
@@ -45,9 +55,9 @@ export function resolveActorIngress(
45
55
  request: boolean,
46
56
  options: {
47
57
  readonly handoffRuntime?: ZLinkSpotActorHandoffRuntime;
48
- readonly routeBeforeLocal?: ZLinkActorIngressRoute;
58
+ readonly spotId?: RoutingId;
59
+ readonly membership?: ZLinkActorIngressMembership;
49
60
  readonly resolveActor: (actorId: string) => ZLinkActor | undefined;
50
- readonly hasDepartedActor?: (actorId: string) => boolean;
51
61
  readonly routeToActorJoinPrewarm?: ZLinkRouteToActorJoinPrewarm;
52
62
  }
53
63
  ): ZLinkActorIngressAdmission {
@@ -62,12 +72,20 @@ export function resolveActorIngress(
62
72
  delivery.messageFollowOrigin
63
73
  );
64
74
  if (handoff !== undefined) return { kind: 'follow', terminal: handoff };
65
- const route = options.routeBeforeLocal?.(delivery);
66
- if (route?.handled === true) return { kind: 'follow', terminal: Promise.resolve(route.response) };
75
+ const membership = options.membership;
76
+ const owner = membership?.resolveOwner?.(delivery.actorId);
77
+ if (owner?.actorRef !== undefined && !routingIdsEqual(owner.spotId, options.spotId)) {
78
+ if (membership?.dispatchToOwner === undefined) {
79
+ throw new Error('Actor ingress requires the current membership dispatch port.');
80
+ }
81
+ // Transfer the accepted delivery before releasing the source FIFO claim.
82
+ // Its terminal is awaited outside that claim by dispatchActorPacketInMailbox.
83
+ return { kind: 'follow', terminal: membership.dispatchToOwner(owner.spotId, delivery) };
84
+ }
67
85
  const actor =
68
- options.hasDepartedActor?.(delivery.actorId) === true
69
- ? undefined
70
- : options.resolveActor(delivery.actorId);
86
+ membership?.resolveOwner === undefined || owner?.actorRef !== undefined
87
+ ? options.resolveActor(delivery.actorId)
88
+ : undefined;
71
89
  if (actor !== undefined) return { kind: 'local', actor };
72
90
  if (
73
91
  options.routeToActorJoinPrewarm !== undefined &&
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  resolveActorIngress,
3
3
  type ZLinkRouteToActorJoinPrewarm,
4
- type ZLinkActorIngressRoute
4
+ type ZLinkActorIngressMembership
5
5
  } from './spot-actor-ingress-admission';
6
6
  import type { ZLinkSpotActorHandoffRuntime } from './spot-runtime-ports';
7
7
  import { DefaultZLinkActorContext } from '../actors/actor-context';
@@ -97,8 +97,8 @@ interface ZLinkSpotActorPacketDispatchOptions {
97
97
  readonly spotId: () => string;
98
98
  readonly registry: ZLinkSpotActorHandlerRegistryRuntime;
99
99
  readonly resolveActor: (actorId: string) => ZLinkActor | undefined;
100
- readonly hasDepartedActor?: (actorId: string) => boolean;
101
- readonly routeBeforeLocal?: ZLinkActorIngressRoute;
100
+ readonly membershipSpotId?: RoutingId;
101
+ readonly membership?: ZLinkActorIngressMembership;
102
102
  readonly onDisconnectActor: (actor: ZLinkActor) => Promise<void>;
103
103
  readonly actorResponseSender?: (
104
104
  actor: ZLinkActor,
@@ -226,9 +226,9 @@ export class ZLinkSpotActorPacketDispatch {
226
226
  messageKind === ZLinkDispatchMessageKind.ActorRequest,
227
227
  {
228
228
  handoffRuntime,
229
- routeBeforeLocal: this.options.routeBeforeLocal,
229
+ spotId: this.options.membershipSpotId,
230
+ membership: this.options.membership,
230
231
  resolveActor: this.options.resolveActor,
231
- hasDepartedActor: this.options.hasDepartedActor,
232
232
  routeToActorJoinPrewarm: this.options.routeToActorJoinPrewarm
233
233
  }
234
234
  );
@@ -196,17 +196,7 @@ export class ZLinkEntrySpotActivation {
196
196
  spotId: () => String(this.options.nativeSpot.routingId),
197
197
  registry: this.actorHandlers,
198
198
  resolveActor: (targetActorId) => this.options.entryActorRuntime?.resolveActor(targetActorId),
199
- routeBeforeLocal: (delivery) =>
200
- this.options.entryActorRuntime?.routePacket(
201
- delivery.actorId,
202
- delivery.parts,
203
- delivery.returnResponse,
204
- delivery.remoteBoundSessionTarget,
205
- delivery.fallbackActorRef,
206
- delivery.origin,
207
- delivery.requestTerminal,
208
- delivery.messageFollowOrigin
209
- ),
199
+ membership: this.options.entryActorRuntime?.actorIngressMembership,
210
200
  onDisconnectActor: (actor) => this.notifyDisconnectActor(actor),
211
201
  actorResponseSender: this.options.boundSessionRuntime?.sendActorResponse.bind(
212
202
  this.options.boundSessionRuntime