@zlink-systems/framework 0.29.1 → 0.30.0

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.
Files changed (152) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/Builders.d.ts +1 -2
  3. package/dist/contracts/Configuration/Configs.d.ts +0 -5
  4. package/dist/contracts/Configuration/RegistrationBuilders.js +6 -4
  5. package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -6
  6. package/dist/contracts/Configuration/RegistrationValidators.js +4 -4
  7. package/dist/contracts/Configuration/index.d.ts +1 -1
  8. package/dist/internal.d.ts +1 -0
  9. package/dist/internal.js +3 -1
  10. package/dist/runtime/actors/actor-handoff.d.ts +3 -2
  11. package/dist/runtime/actors/actor-handoff.js +9 -4
  12. package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +3 -1
  13. package/dist/runtime/application-jobs/application-ingress-record-owner.js +6 -1
  14. package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +3 -1
  15. package/dist/runtime/application-jobs/application-job-queue-scope.js +5 -0
  16. package/dist/runtime/application-jobs/contracts.d.ts +5 -1
  17. package/dist/runtime/backend/contracts/index.d.ts +10 -19
  18. package/dist/runtime/backend/mesh-completion-table.d.ts +6 -4
  19. package/dist/runtime/backend/mesh-completion-table.js +28 -18
  20. package/dist/runtime/backend/mesh-dispatch-pump.js +21 -25
  21. package/dist/runtime/backend/node/node-backend-adapter-factory.js +6 -1
  22. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +3 -3
  23. package/dist/runtime/backend/node/node-backend-adapter-support.js +24 -29
  24. package/dist/runtime/backend/node/node-mesh-backend-adapter.d.ts +1 -9
  25. package/dist/runtime/backend/node/node-mesh-backend-adapter.js +1 -1
  26. package/dist/runtime/backend/node/node-raw-binding-port.js +11 -6
  27. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +10 -12
  28. package/dist/runtime/backend/node/node-raw-mesh-backend.js +64 -98
  29. package/dist/runtime/backend/node/node-socket-backend-adapter.js +10 -5
  30. package/dist/runtime/backend/raw-binding-port.d.ts +9 -2
  31. package/dist/runtime/channels/channel-receive-loops.js +3 -3
  32. package/dist/runtime/channels/channel-runtime-lifecycle.js +2 -1
  33. package/dist/runtime/channels/channel-socket-registry.d.ts +10 -3
  34. package/dist/runtime/channels/channel-socket-registry.js +59 -82
  35. package/dist/runtime/diagnostics/message-flow.d.ts +1 -0
  36. package/dist/runtime/diagnostics/message-flow.js +3 -3
  37. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +15 -1
  38. package/dist/runtime/foundation/raw-service-mesh-runtime.js +193 -64
  39. package/dist/runtime/foundation/service-liveness-registry.d.ts +20 -8
  40. package/dist/runtime/foundation/service-liveness-registry.js +57 -44
  41. package/dist/runtime/foundation/service-mailbox.d.ts +3 -0
  42. package/dist/runtime/foundation/service-mailbox.js +2 -1
  43. package/dist/runtime/foundation/service-stateful-runtime.d.ts +7 -6
  44. package/dist/runtime/foundation/service-stateful-runtime.js +34 -23
  45. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +5 -2
  46. package/dist/runtime/foundation/service-stateful-wire-codec.js +43 -61
  47. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -3
  48. package/dist/runtime/foundation/service-topology-registry.js +9 -4
  49. package/dist/runtime/host/actor-packet-relay.js +7 -7
  50. package/dist/runtime/host/actor-runtime-options-factory.js +1 -1
  51. package/dist/runtime/host/application-job-queue.d.ts +11 -4
  52. package/dist/runtime/host/application-job-queue.js +66 -10
  53. package/dist/runtime/host/entry-actor-runtime.js +1 -1
  54. package/dist/runtime/host/index.js +15 -11
  55. package/dist/runtime/host/instance-activation-authority.d.ts +2 -1
  56. package/dist/runtime/host/instance-activation-authority.js +89 -25
  57. package/dist/runtime/host/service-relocation-host-runtime.js +2 -2
  58. package/dist/runtime/host/spot-address-transport.d.ts +1 -0
  59. package/dist/runtime/host/spot-address-transport.js +25 -12
  60. package/dist/runtime/host/spot-runtime-options-factory.js +2 -2
  61. package/dist/runtime/host/user-spot-creation-coordinator.d.ts +1 -0
  62. package/dist/runtime/host/user-spot-creation-coordinator.js +1 -0
  63. package/dist/runtime/locations/domain-store-contract.d.ts +2 -2
  64. package/dist/runtime/locations/in-memory-authority-store.d.ts +4 -2
  65. package/dist/runtime/locations/in-memory-authority-store.js +80 -8
  66. package/dist/runtime/locations/in-memory-location-store.d.ts +2 -2
  67. package/dist/runtime/locations/in-memory-location-store.js +19 -3
  68. package/dist/runtime/locations/internal-location-contracts.d.ts +3 -0
  69. package/dist/runtime/locations/location-store-repository.d.ts +3 -2
  70. package/dist/runtime/locations/location-store-repository.js +165 -22
  71. package/dist/runtime/locations/resolvers.d.ts +2 -1
  72. package/dist/runtime/locations/resolvers.js +10 -1
  73. package/dist/runtime/messaging/submission-result.d.ts +1 -1
  74. package/dist/runtime/messaging/submission-result.js +2 -2
  75. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +2 -0
  76. package/dist/runtime/spots/index.d.ts +4 -3
  77. package/dist/runtime/spots/index.js +14 -14
  78. package/dist/runtime/spots/spot-activation.d.ts +2 -1
  79. package/dist/runtime/spots/spot-activation.js +4 -4
  80. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +2 -1
  81. package/dist/runtime/spots/spot-actor-admission-coordinator.js +11 -10
  82. package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +4 -1
  83. package/dist/runtime/spots/spot-actor-packet-dispatch.js +7 -5
  84. package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
  85. package/dist/runtime/spots/spot-actor-packet-relay-dispatch.js +1 -0
  86. package/dist/runtime/spots/spot-entry-activation.js +3 -1
  87. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +2 -1
  88. package/dist/runtime/spots/spot-node-runtime-manager.js +4 -11
  89. package/dist/runtime/spots/spot-routing-internal.d.ts +6 -0
  90. package/dist/runtime/spots/spot-runtime-ports.d.ts +2 -1
  91. package/dist/runtime/streams/stream-session-runtime.js +1 -1
  92. package/package.json +3 -3
  93. package/src/contracts/Configuration/Builders.ts +1 -2
  94. package/src/contracts/Configuration/Configs.ts +0 -6
  95. package/src/contracts/Configuration/RegistrationBuilders.ts +8 -12
  96. package/src/contracts/Configuration/RegistrationTypes.ts +1 -6
  97. package/src/contracts/Configuration/RegistrationValidators.ts +9 -10
  98. package/src/contracts/Configuration/index.ts +1 -6
  99. package/src/internal.ts +1 -0
  100. package/src/runtime/actors/actor-handoff.ts +13 -4
  101. package/src/runtime/application-jobs/application-ingress-record-owner.ts +12 -2
  102. package/src/runtime/application-jobs/application-job-queue-scope.ts +6 -1
  103. package/src/runtime/application-jobs/contracts.ts +14 -1
  104. package/src/runtime/backend/contracts/index.ts +10 -18
  105. package/src/runtime/backend/mesh-completion-table.ts +36 -19
  106. package/src/runtime/backend/mesh-dispatch-pump.ts +19 -22
  107. package/src/runtime/backend/node/node-backend-adapter-factory.ts +12 -5
  108. package/src/runtime/backend/node/node-backend-adapter-support.ts +31 -29
  109. package/src/runtime/backend/node/node-mesh-backend-adapter.ts +3 -10
  110. package/src/runtime/backend/node/node-raw-binding-port.ts +19 -10
  111. package/src/runtime/backend/node/node-raw-mesh-backend.ts +81 -111
  112. package/src/runtime/backend/node/node-socket-backend-adapter.ts +12 -8
  113. package/src/runtime/backend/raw-binding-port.ts +12 -2
  114. package/src/runtime/channels/channel-receive-loops.ts +3 -3
  115. package/src/runtime/channels/channel-runtime-lifecycle.ts +7 -1
  116. package/src/runtime/channels/channel-socket-registry.ts +84 -130
  117. package/src/runtime/diagnostics/message-flow.ts +1 -1
  118. package/src/runtime/foundation/raw-service-mesh-runtime.ts +268 -70
  119. package/src/runtime/foundation/service-liveness-registry.ts +68 -59
  120. package/src/runtime/foundation/service-mailbox.ts +7 -1
  121. package/src/runtime/foundation/service-stateful-runtime.ts +61 -35
  122. package/src/runtime/foundation/service-stateful-wire-codec.ts +55 -79
  123. package/src/runtime/foundation/service-topology-registry.ts +13 -7
  124. package/src/runtime/host/actor-packet-relay.ts +7 -0
  125. package/src/runtime/host/actor-runtime-options-factory.ts +1 -0
  126. package/src/runtime/host/application-job-queue.ts +77 -10
  127. package/src/runtime/host/entry-actor-runtime.ts +1 -0
  128. package/src/runtime/host/index.ts +24 -5
  129. package/src/runtime/host/instance-activation-authority.ts +132 -27
  130. package/src/runtime/host/service-relocation-host-runtime.ts +2 -0
  131. package/src/runtime/host/spot-address-transport.ts +45 -22
  132. package/src/runtime/host/spot-runtime-options-factory.ts +2 -0
  133. package/src/runtime/host/user-spot-creation-coordinator.ts +8 -0
  134. package/src/runtime/locations/domain-store-contract.ts +6 -2
  135. package/src/runtime/locations/in-memory-authority-store.ts +113 -9
  136. package/src/runtime/locations/in-memory-location-store.ts +25 -3
  137. package/src/runtime/locations/internal-location-contracts.ts +4 -0
  138. package/src/runtime/locations/location-store-repository.ts +220 -15
  139. package/src/runtime/locations/resolvers.ts +25 -2
  140. package/src/runtime/messaging/submission-result.ts +2 -2
  141. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +2 -0
  142. package/src/runtime/spots/index.ts +16 -0
  143. package/src/runtime/spots/spot-activation.ts +5 -0
  144. package/src/runtime/spots/spot-actor-admission-coordinator.ts +13 -0
  145. package/src/runtime/spots/spot-actor-packet-dispatch.ts +10 -0
  146. package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
  147. package/src/runtime/spots/spot-actor-packet-relay-dispatch.ts +1 -0
  148. package/src/runtime/spots/spot-entry-activation.ts +3 -0
  149. package/src/runtime/spots/spot-node-runtime-manager.ts +7 -12
  150. package/src/runtime/spots/spot-routing-internal.ts +14 -0
  151. package/src/runtime/spots/spot-runtime-ports.ts +2 -0
  152. package/src/runtime/streams/stream-session-runtime.ts +1 -1
@@ -7,7 +7,6 @@ import type { ZLinkDispatchOptionsBuilder, ZLinkInboundDispatchOptions } from '.
7
7
  import type { ZLinkLocationOptions, ZLinkLocationStore, ZLinkRelocationStore } from '../Locations';
8
8
  import type { RoutingId, Type } from '../Common';
9
9
  import type { ZLinkWorkerOptions } from './RegistrationTypes';
10
- import type { ZLinkSpotPublisherConfig } from './Configs';
11
10
  import type {
12
11
  ZLinkActorFactoryBuilder,
13
12
  ZLinkInstanceSpotFactoryBuilder,
@@ -120,7 +119,6 @@ export interface ZLinkMeshNodeBuilder {
120
119
  setActivationConcurrency(limit: number): this;
121
120
  setInstanceSpotIdleTimeout(timeoutMs: number): this;
122
121
  configureRouterSocket(): ZLinkMeshNodeSocketConfig;
123
- configureSpotPublisher(): ZLinkSpotPublisherConfig;
124
122
  peerConnections(): ZLinkMeshPeerConnections;
125
123
  setDefaultRequestTimeout(timeoutMs: number): this;
126
124
  objects(): ZLinkMeshObjectRoleBuilder;
@@ -180,6 +178,7 @@ export interface ZLinkFanoutChannelBuilder {
180
178
  routingId(routingId: string): this;
181
179
  setRoutingIdPrefix(prefix: string): this;
182
180
  setNoDrop(noDrop?: boolean): this;
181
+ setSendTimeout(timeoutMs: number): this;
183
182
  enableSubscriber(): this;
184
183
  subscribe(topic: string): this;
185
184
  connect(endpoint: string): this;
@@ -19,9 +19,3 @@ export interface ZLinkOutboundRouteConfig {
19
19
  targetNodeRid: RoutingId;
20
20
  endpoint: string;
21
21
  }
22
-
23
- export interface ZLinkSpotPublisherConfig {
24
- sendHighWaterMark: number;
25
- sendTimeoutMs?: number;
26
- lingerMs?: number;
27
- }
@@ -27,7 +27,6 @@ import type {
27
27
  ZLinkSession,
28
28
  ZLinkSessionFactory,
29
29
  ZLinkSpot,
30
- ZLinkSpotPublisherConfig,
31
30
  ZLinkStreamCompressionBuilder,
32
31
  ZLinkStreamCompressionCodec,
33
32
  ZLinkStreamNodeBuilder,
@@ -53,6 +52,7 @@ import type { ZLinkEndpointConnections } from './Connections';
53
52
  import { requireMessageFlowLogMode, requireTraceSampleRate } from './DiagnosticsValidation';
54
53
  import { normalizeEndpoint } from './EndpointNotation';
55
54
  import { requirePublicFanoutTopic } from './FanoutTopic';
55
+ import { requireValidSendTimeoutMs } from './SendTimeoutValidation';
56
56
  import { DEFAULT_STREAM_NODE_MAX_MESSAGE_SIZE } from './InternalDefaults';
57
57
  import {
58
58
  type ZLinkActorFactoryBuilder,
@@ -519,6 +519,12 @@ class DefaultFanoutChannelBuilder implements ZLinkFanoutChannelBuilder {
519
519
  return this;
520
520
  }
521
521
 
522
+ setSendTimeout(timeoutMs: number): this {
523
+ requireValidSendTimeoutMs(`Fanout channel '${this.name}' send timeout`, timeoutMs);
524
+ this.channel.sendTimeoutMs = timeoutMs;
525
+ return this;
526
+ }
527
+
522
528
  enableSubscriber(endpoint?: string): this {
523
529
  this.selectSubscriberMode(endpoint === undefined ? 'automatic' : 'manual');
524
530
  this.channel.subscriber ??= { manualConnections: [] };
@@ -984,11 +990,6 @@ class DefaultMeshNodeBuilder implements ZLinkMeshNodeBuilder {
984
990
  return this.node.router as ZLinkMeshNodeSocketConfig;
985
991
  }
986
992
 
987
- configureSpotPublisher(): ZLinkSpotPublisherConfig {
988
- this.node.publisherConfig ??= {};
989
- return this.node.publisherConfig as ZLinkSpotPublisherConfig;
990
- }
991
-
992
993
  peerConnections(): ZLinkMeshPeerConnections {
993
994
  this.node.router ??= {};
994
995
  return new DefaultMeshPeerConnections(this.node.router);
@@ -1576,6 +1577,7 @@ interface MutableChannelOptions {
1576
1577
  routingId?: string;
1577
1578
  routingIdPrefix?: string;
1578
1579
  noDrop?: boolean;
1580
+ sendTimeoutMs?: number;
1579
1581
  publisher?: MutablePublisherCapabilityOptions;
1580
1582
  publishHandlers?: ZLinkChannelPublishHandlerRegistration[];
1581
1583
  subscriptions?: string[];
@@ -1607,7 +1609,6 @@ interface MutableClientCapabilityOptions {
1607
1609
  }
1608
1610
 
1609
1611
  interface MutablePublisherCapabilityOptions {
1610
- sendTimeoutMs?: number;
1611
1612
  bind?: string;
1612
1613
  bindHost?: string;
1613
1614
  advertiseHost?: string;
@@ -1668,11 +1669,6 @@ interface MutableSpotNodeOptions {
1668
1669
  routeSendHandlers?: Array<{ packetName: string; handlerType: Type }>;
1669
1670
  routeRequestHandlers?: Array<{ packetName: string; handlerType: Type }>;
1670
1671
  requestTimeoutMs?: number;
1671
- publisherConfig?: {
1672
- sendHighWaterMark?: number;
1673
- sendTimeoutMs?: number;
1674
- lingerMs?: number;
1675
- };
1676
1672
  }
1677
1673
 
1678
1674
  interface MutableObjectFactoryRegistration<T, TImplementation = T, TOptions = unknown> {
@@ -166,6 +166,7 @@ export interface ZLinkChannelOptions {
166
166
  readonly routingId?: string;
167
167
  readonly routingIdPrefix?: string;
168
168
  readonly noDrop?: boolean;
169
+ readonly sendTimeoutMs?: number;
169
170
  readonly requestTimeoutMs?: number;
170
171
  readonly client?: ZLinkClientCapabilityOptions;
171
172
  readonly publisher?: ZLinkPublisherCapabilityOptions;
@@ -196,7 +197,6 @@ export interface ZLinkClientCapabilityOptions {
196
197
  }
197
198
 
198
199
  export interface ZLinkPublisherCapabilityOptions {
199
- readonly sendTimeoutMs?: number;
200
200
  readonly bind?: string;
201
201
  readonly bindHost?: string;
202
202
  readonly advertiseHost?: string;
@@ -297,11 +297,6 @@ export interface ZLinkSpotNodeOptions {
297
297
  readonly routeSendHandlers?: readonly ZLinkRouteMeshSendHandlerRegistration[];
298
298
  readonly routeRequestHandlers?: readonly ZLinkRouteMeshRequestHandlerRegistration[];
299
299
  readonly requestTimeoutMs?: number;
300
- readonly publisherConfig?: {
301
- readonly sendHighWaterMark?: number;
302
- readonly sendTimeoutMs?: number;
303
- readonly lingerMs?: number;
304
- };
305
300
  readonly entrySpotTimerHandlers?: readonly ZLinkEntrySpotTimerHandlerRegistration[];
306
301
  readonly entrySpotPacketHandlers?: readonly ZLinkEntrySpotPacketHandlerRegistration[];
307
302
  readonly entrySpotSubscriptionHandlers?: readonly ZLinkEntrySpotSubscriptionHandlerRegistration[];
@@ -217,9 +217,12 @@ function validateChannelCapabilities(
217
217
  if (channel.noDrop !== undefined && typeof channel.noDrop !== 'boolean') {
218
218
  throw new ZLinkConfigurationException(`Channel '${channelName}' NoDrop must be a boolean.`);
219
219
  }
220
- if (channel.noDrop !== undefined && channel.publisher === undefined) {
220
+ if (
221
+ (channel.noDrop !== undefined || channel.sendTimeoutMs !== undefined) &&
222
+ channel.publisher === undefined
223
+ ) {
221
224
  throw new ZLinkConfigurationException(
222
- `Channel '${channelName}' NoDrop requires a publisher role.`
225
+ `Channel '${channelName}': NoDrop and send timeout require the publisher role.`
223
226
  );
224
227
  }
225
228
  if (channel.server !== undefined) {
@@ -235,11 +238,11 @@ function validateChannelCapabilities(
235
238
  requirePeerWeight(`channel '${channelName}' server weight`, channel.server.weight);
236
239
  requireSocketOptions(`channel '${channelName}' server`, channel.server);
237
240
  }
241
+ requireValidSendTimeoutMs(
242
+ `channel '${channelName}' publisher sendTimeoutMs`,
243
+ channel.sendTimeoutMs
244
+ );
238
245
  if (channel.publisher !== undefined) {
239
- requireValidSendTimeoutMs(
240
- `channel '${channelName}' publisher sendTimeoutMs`,
241
- channel.publisher.sendTimeoutMs
242
- );
243
246
  requireEndpoint(`channel '${channelName}' publisher`, channel.publisher.bind);
244
247
  validateListenerNetworkIdentity(
245
248
  `channel '${channelName}' publisher`,
@@ -345,10 +348,6 @@ function validateSpotNodes(registration: ZLinkFrameworkRegistration): void {
345
348
  `SpotNode '${spotNodeName}' instanceSpotIdleTimeoutMs`,
346
349
  spotNode.instanceSpotIdleTimeoutMs
347
350
  );
348
- requireValidSendTimeoutMs(
349
- `SpotNode '${spotNodeName}' publisher sendTimeoutMs`,
350
- spotNode.publisherConfig?.sendTimeoutMs
351
- );
352
351
  validateSpotNodeFactories(spotNodeName, spotNode);
353
352
  validateSpotNodeTimers(spotNode);
354
353
  if (
@@ -20,12 +20,7 @@ export type {
20
20
  ZLinkStreamSocketConfig,
21
21
  ZLinkStreamNodeBuilder
22
22
  } from './Builders';
23
- export type {
24
- ZLinkOutboundRouteConfig,
25
- ZLinkRouteConfig,
26
- ZLinkSocketConfig,
27
- ZLinkSpotPublisherConfig
28
- } from './Configs';
23
+ export type { ZLinkOutboundRouteConfig, ZLinkRouteConfig, ZLinkSocketConfig } from './Configs';
29
24
  export * from './Connections';
30
25
  export * from './ObjectRoles';
31
26
  export type {
package/src/internal.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /** Test and companion-package integration surface. It is not exported by package.json. */
2
2
  export * from './index';
3
+ export { requireValidSendTimeoutMs } from './contracts/Configuration/SendTimeoutValidation';
3
4
  export * from './runtime/diagnostics';
4
5
  export * from './runtime/host';
5
6
  export * from './runtime/admission';
@@ -3,6 +3,7 @@ import { type ActorRef, type RoutingId, ZLinkFrameworkException } from '../../co
3
3
  import type { Message } from '../../contracts/Common/Message';
4
4
  import { zlinkDefaultLocationOptions } from '../../contracts/Locations/Options';
5
5
  import { releaseApplicationJobPermitForDurableHandoff } from '../application-jobs/application-job-queue-scope';
6
+ import type { ApplicationJobOrigin } from '../application-jobs/contracts';
6
7
  import { ZLinkBufferMessage as RuntimeMessage } from '../backend/runtime-message';
7
8
  import {
8
9
  MessageFollowSuppressionRegistry,
@@ -108,7 +109,8 @@ export interface ZLinkActorHandoffPreparedReplayAdmission {
108
109
  }
109
110
 
110
111
  export type ZLinkActorHandoffReplayPreparation = (
111
- signal: AbortSignal
112
+ signal: AbortSignal,
113
+ origin: ApplicationJobOrigin
112
114
  ) => Promise<ZLinkActorHandoffPreparedReplayAdmission>;
113
115
 
114
116
  export interface ZLinkActorHandoffPrefixAdmission {
@@ -137,6 +139,7 @@ export type ZLinkActorHandoffPrefixQueue = (
137
139
  ) => ZLinkActorHandoffPrefixAdmission;
138
140
 
139
141
  interface PendingPacket {
142
+ readonly origin: ApplicationJobOrigin;
140
143
  readonly packet: ZLinkActorHandoffPacket;
141
144
  readonly resolve?: (value: unknown) => void;
142
145
  readonly reject?: (reason: unknown) => void;
@@ -554,7 +557,7 @@ export class ZLinkActorHandoffCoordinator {
554
557
  try {
555
558
  await waitForHandoffReplayStart(release.start, signal);
556
559
  if (release.preparation !== undefined) {
557
- const next = await release.preparation(signal);
560
+ const next = await release.preparation(signal, pending.origin);
558
561
  if (signal.aborted) {
559
562
  next.cancel();
560
563
  throw signal.reason;
@@ -765,6 +768,7 @@ export class ZLinkActorHandoffCoordinator {
765
768
  }
766
769
 
767
770
  capture(
771
+ origin: ApplicationJobOrigin,
768
772
  actorId: string,
769
773
  parts: readonly Message[],
770
774
  returnResponse = false,
@@ -823,7 +827,7 @@ export class ZLinkActorHandoffCoordinator {
823
827
  this.reportRequestFrame(actorId, packet);
824
828
  }
825
829
  if (!returnResponse) {
826
- handoff.pending.push({ packet });
830
+ handoff.pending.push({ packet, origin });
827
831
  // The durable handoff now owns this record. Return the ingress permit
828
832
  // before waiting for a later replay, which must acquire its own fresh
829
833
  // application-job permit.
@@ -859,7 +863,12 @@ export class ZLinkActorHandoffCoordinator {
859
863
  }, RELOCATION_REPLY_RETENTION_MS);
860
864
  route.deadline.unref();
861
865
  this.replyRoutes.set(source.replyRouteId, route);
862
- handoff.pending.push({ packet: requestPacket, resolve, reject });
866
+ handoff.pending.push({
867
+ packet: requestPacket,
868
+ origin,
869
+ resolve,
870
+ reject
871
+ });
863
872
  });
864
873
  releaseApplicationJobPermitForDurableHandoff();
865
874
  if (handoff.prefixRelease !== undefined) {
@@ -1,4 +1,8 @@
1
- import type { ApplicationJobPermitPort, ApplicationJobQueuePort } from './contracts';
1
+ import type {
2
+ ApplicationJobOrigin,
3
+ ApplicationJobPermitPort,
4
+ ApplicationJobQueuePort
5
+ } from './contracts';
2
6
 
3
7
  export type ApplicationJobRecordDomain = 'application' | 'infrastructure';
4
8
 
@@ -85,6 +89,10 @@ export class ApplicationJobRecordLease implements ApplicationJobPermitPort {
85
89
  private readonly permit: ApplicationJobPermitPort
86
90
  ) {}
87
91
 
92
+ get origin(): ApplicationJobOrigin {
93
+ return this.shared.origin;
94
+ }
95
+
88
96
  markApplicationQueued(): void {
89
97
  // The owner classifies and marks the child atomically when it is created;
90
98
  // downstream dispatch must not mutate queue accounting a second time.
@@ -132,12 +140,14 @@ class SharedIngressRecordState {
132
140
  private jobCount = 0;
133
141
  private initialPermit?: ApplicationJobPermitPort;
134
142
  private recordClosed = false;
143
+ readonly origin: ApplicationJobOrigin;
135
144
 
136
145
  constructor(
137
146
  private readonly queue: ApplicationJobQueuePort,
138
147
  initialPermit: ApplicationJobPermitPort,
139
148
  private readonly record: IngressRecordPort
140
149
  ) {
150
+ this.origin = initialPermit.origin;
141
151
  this.initialPermit = initialPermit;
142
152
  }
143
153
 
@@ -160,7 +170,7 @@ class SharedIngressRecordState {
160
170
  }
161
171
 
162
172
  acquirePermit(signal: AbortSignal): Promise<ApplicationJobPermitPort> {
163
- return this.queue.acquire(signal);
173
+ return this.queue.acquire(signal, this.origin);
164
174
  }
165
175
 
166
176
  createJob(
@@ -1,6 +1,6 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
2
 
3
- import type { ApplicationJobPermitPort } from './contracts';
3
+ import type { ApplicationJobOrigin, ApplicationJobPermitPort } from './contracts';
4
4
 
5
5
  interface ApplicationJobPermitScope {
6
6
  readonly permit: ApplicationJobPermitPort;
@@ -42,6 +42,11 @@ export function hasApplicationJobPermit(): boolean {
42
42
  return applicationJobPermitScope.getStore() !== undefined;
43
43
  }
44
44
 
45
+ /** Keeps the ingress origin when a durable record later reacquires its permit. */
46
+ export function currentApplicationJobOrigin(): ApplicationJobOrigin {
47
+ return applicationJobPermitScope.getStore()!.permit.origin;
48
+ }
49
+
45
50
  /** Re-enters the captured permit scope when a serial executor runs later. */
46
51
  export function bindApplicationJobPermit<TResult, TArgument>(
47
52
  callback: (argument?: TArgument) => Promise<TResult> | TResult
@@ -1,4 +1,7 @@
1
+ export type ApplicationJobOrigin = 'local' | 'remote';
2
+
1
3
  export interface ApplicationJobPermitPort {
4
+ readonly origin: ApplicationJobOrigin;
2
5
  markApplicationQueued(): void;
3
6
  /** Transfers ingress-record cleanup to a detached exact-target handler turn. */
4
7
  detachForHandlerTurn?(): void;
@@ -7,7 +10,17 @@ export interface ApplicationJobPermitPort {
7
10
  }
8
11
 
9
12
  export interface ApplicationJobQueuePort {
10
- acquire(signal?: AbortSignal): Promise<ApplicationJobPermitPort>;
13
+ acquire(
14
+ signal: AbortSignal | undefined,
15
+ origin: ApplicationJobOrigin
16
+ ): Promise<ApplicationJobPermitPort>;
17
+ /** Returns a grant now, or registers one FIFO waiter that later calls resume. */
18
+ acquireOrResume(
19
+ resume: (permit: ApplicationJobPermitPort) => void,
20
+ reject: (reason: unknown) => void,
21
+ signal: AbortSignal | undefined,
22
+ origin: ApplicationJobOrigin
23
+ ): ApplicationJobPermitPort | undefined;
11
24
  registerReceiveFlowTarget?(
12
25
  identity: object,
13
26
  applyState: (state: 'running' | 'paused') => void,
@@ -6,7 +6,7 @@ import type {
6
6
  ZLinkBackendReceived,
7
7
  ZLinkBackendTopicMessage
8
8
  } from '../runtime-values';
9
- import type { CoreHwmBudgetSnapshot } from '@zlink-systems/zlink';
9
+ import type { CoreHwmBudgetSnapshot, ReceiveFlowState } from '@zlink-systems/zlink';
10
10
  import { type ZLinkBackendRecvFlags, type ZLinkBackendSendFlags } from '../runtime-values';
11
11
  export * from '../runtime-values';
12
12
  type MessageLike = ZLinkBackendMessageLike;
@@ -27,6 +27,7 @@ import type {
27
27
  import type { RoutingId } from '../../../contracts';
28
28
  import type { ZLinkCoreHwmOptions } from '../../../contracts/Configuration';
29
29
  import type { ZLinkSubmitResult } from '../../messaging/submission-result';
30
+ import type { ZLinkMeshCompletionHandler } from '../mesh-completion-table';
30
31
  import type { Message } from '../../../contracts/Common/Message';
31
32
  import type {
32
33
  ServiceSpotMessageFollowSeal,
@@ -38,6 +39,7 @@ import type {
38
39
  ServiceActorCreateRecord,
39
40
  ServiceDirectSpotRouteFence,
40
41
  ServiceInstanceRouteFence,
42
+ ServiceInstanceColdActivationTarget,
41
43
  ServiceUserSpotCloseRecord,
42
44
  ServiceUserSpotCreateRecord
43
45
  } from '../../foundation/service-stateful-wire-codec';
@@ -112,26 +114,14 @@ export interface ZLinkBackendMeshNode {
112
114
  selectObjectPlacement(stableType: string): ZLinkBackendObjectPlacement;
113
115
  instanceSpotPlacementTypes?(): readonly string[];
114
116
  sendToMissingInstanceSpot(
115
- target: {
116
- readonly targetNodeRid: string;
117
- readonly targetNodeGeneration: bigint;
118
- readonly targetSpotId: string;
119
- readonly stableType: string;
120
- readonly descriptorVersion: string;
121
- },
117
+ target: ServiceInstanceColdActivationTarget,
122
118
  parts: ZLinkBackendMessageLike | readonly ZLinkBackendMessageLike[],
123
119
  deadlineUnixMs: bigint,
124
120
  sourceSpotId?: string,
125
121
  metadata?: ReadonlyMap<string, string>
126
122
  ): Promise<SubmitResult>;
127
123
  requestToMissingInstanceSpot(
128
- target: {
129
- readonly targetNodeRid: string;
130
- readonly targetNodeGeneration: bigint;
131
- readonly targetSpotId: string;
132
- readonly stableType: string;
133
- readonly descriptorVersion: string;
134
- },
124
+ target: ServiceInstanceColdActivationTarget,
135
125
  parts: ZLinkBackendMessageLike | readonly ZLinkBackendMessageLike[],
136
126
  deadlineUnixMs: bigint,
137
127
  sourceSpotId?: string,
@@ -390,6 +380,7 @@ export interface ZLinkBackendMeshNode {
390
380
  timeoutMs?: number
391
381
  ): MeshOperationId;
392
382
  setReadyHandler(handler: (readyDomains: number) => number): void;
383
+ setCompletionHandler(handler: ZLinkMeshCompletionHandler): void;
393
384
  createReadyBatch(capacity: number): ReadyBatch;
394
385
  createReceiveBatch(messageCapacity: number, partCapacity: number): ReceiveBatch;
395
386
  drainReady(
@@ -476,7 +467,6 @@ export interface ZLinkBackendSocket extends ZLinkBackendObject {
476
467
  readonly lastEndpoint?: string;
477
468
  bind(endpoint: string): void;
478
469
  setChannelName(channelName: string): void;
479
- setReceiveFlowState?(state: 0 | 1): void;
480
470
  dispose(): Promise<void>;
481
471
  }
482
472
 
@@ -486,7 +476,7 @@ export interface ZLinkBackendConnectableSocket extends ZLinkBackendSocket {
486
476
  }
487
477
 
488
478
  export interface ZLinkBackendDealerSocket extends ZLinkBackendConnectableSocket {
489
- setReceiveFlowState(state: 0 | 1): void;
479
+ receiveAdmission?: import('../../foundation/service-liveness-registry').ServiceLivenessConnection;
490
480
  setRoutingId(routingId: RoutingId): void;
491
481
  peerWeight: number;
492
482
  sendHighWaterMark: number;
@@ -515,7 +505,7 @@ export interface ZLinkBackendRequestOperation {
515
505
  }
516
506
 
517
507
  export interface ZLinkBackendRouterSocket extends ZLinkBackendConnectableSocket {
518
- setReceiveFlowState(state: 0 | 1): void;
508
+ setReceiveFlowState(state: ReceiveFlowState): void;
519
509
  /** Enables the native probe handshake for manually configured routes. */
520
510
  setProbe(enabled: boolean): void;
521
511
  peerWeight: number;
@@ -549,6 +539,7 @@ export interface ZLinkBackendRouterSocket extends ZLinkBackendConnectableSocket
549
539
  }
550
540
 
551
541
  export interface ZLinkBackendPublisherSocket extends ZLinkBackendSocket {
542
+ lingerMs: number;
552
543
  sendTimeoutMs: number;
553
544
  sendHighWaterMark: number;
554
545
  noDrop: boolean;
@@ -739,6 +730,7 @@ export interface ZLinkMeshBackendAdapter {
739
730
  readonly trustProfile?: string;
740
731
  readonly applicationJobQueue: import('../../application-jobs/contracts').ApplicationJobQueuePort;
741
732
  readonly applicationJobReceiveFlowFailureSink?: (error: unknown) => void;
733
+ readonly infrastructureTaskRunner?: import('../../spots/spot-actor-join-dispatch').ZLinkDetachedTaskRunner;
742
734
  readonly peerAdmissionSealed?: () => boolean;
743
735
  }
744
736
  ): ZLinkBackendMeshNode;
@@ -27,8 +27,14 @@ interface PendingCompletion {
27
27
  readonly removeAbort?: () => void;
28
28
  }
29
29
 
30
+ export type ZLinkMeshCompletionTerminal = Pick<ReceiveRecord, 'operationId' | 'terminalResult'>;
31
+ export type ZLinkMeshCompletionHandler = (
32
+ terminal: ZLinkMeshCompletionTerminal,
33
+ materialize: () => ReceiveRecord
34
+ ) => void;
35
+
30
36
  export class ZLinkMeshCompletionTable {
31
- private readonly pending = new Map<string, PendingCompletion>();
37
+ private readonly pending = new Map<string | symbol, PendingCompletion>();
32
38
  private disposed = false;
33
39
 
34
40
  constructor(
@@ -36,10 +42,9 @@ export class ZLinkMeshCompletionTable {
36
42
  ) {}
37
43
 
38
44
  /**
39
- * Submits and registers without yielding control to the event loop. Mesh
40
- * completion dispatch is asynchronous, so a completion cannot overtake the
41
- * registration. The table therefore does not need to retain responses that
42
- * arrived before their waiter.
45
+ * Reserves the dispatcher slot before transport submission. The backend
46
+ * observes native terminals through Promises, so identity binding finishes
47
+ * before the terminal callback can run, without an infrastructure pump.
43
48
  */
44
49
  submit(operation: () => MeshOperationId, signal?: AbortSignal): Promise<ZLinkMeshCompletion> {
45
50
  if (this.isDisposed()) {
@@ -50,17 +55,8 @@ export class ZLinkMeshCompletionTable {
50
55
  signal.reason ?? new DOMException('The operation was aborted.', 'AbortError')
51
56
  );
52
57
  }
53
- const operationId = operation();
54
- // The backend submission callback can synchronously re-enter `dispose()`.
55
- // TypeScript's control-flow analysis cannot observe mutation through it.
56
- if (this.disposed) {
57
- return Promise.reject(new Error('Mesh completion table is disposed.'));
58
- }
59
- const key = operationIdentityKey(operationId);
60
- if (this.pending.has(key)) {
61
- return Promise.reject(new Error(`Mesh operation '${key}' is already pending.`));
62
- }
63
58
  return new Promise((resolve, reject) => {
59
+ let key: string | symbol = Symbol();
64
60
  const abort = () => {
65
61
  this.pending.delete(key);
66
62
  reject(signal?.reason ?? new DOMException('The operation was aborted.', 'AbortError'));
@@ -70,12 +66,29 @@ export class ZLinkMeshCompletionTable {
70
66
  return;
71
67
  }
72
68
  signal?.addEventListener('abort', abort, { once: true });
73
- this.pending.set(key, {
69
+ const entry: PendingCompletion = {
74
70
  resolve,
75
71
  reject,
76
72
  removeAbort:
77
73
  signal === undefined ? undefined : () => signal.removeEventListener('abort', abort)
78
- });
74
+ };
75
+ this.pending.set(key, entry);
76
+ try {
77
+ const operationId = operation();
78
+ // dispose/abort can consume the reserved slot during submission.
79
+ if (!this.pending.delete(key)) return;
80
+ key = operationIdentityKey(operationId);
81
+ if (this.pending.has(key)) {
82
+ throw new Error(`Mesh operation '${key}' is already pending.`);
83
+ }
84
+ this.pending.set(key, entry);
85
+ } catch (error) {
86
+ // Remove only this submission's slot; a duplicate identity retains
87
+ // the original waiter.
88
+ if (this.pending.get(key) === entry) this.pending.delete(key);
89
+ entry.removeAbort?.();
90
+ reject(error);
91
+ }
79
92
  });
80
93
  }
81
94
 
@@ -83,7 +96,9 @@ export class ZLinkMeshCompletionTable {
83
96
  return this.disposed;
84
97
  }
85
98
 
86
- complete(record: ReceiveRecord): void {
99
+ complete(record: ReceiveRecord): void;
100
+ complete(record: ZLinkMeshCompletionTerminal, materialize: () => ReceiveRecord): void;
101
+ complete(record: ZLinkMeshCompletionTerminal, materialize?: () => ReceiveRecord): void {
87
102
  const key = operationIdentityKey(record.operationId);
88
103
  const pending = this.pending.get(key);
89
104
  if (pending === undefined) {
@@ -97,7 +112,9 @@ export class ZLinkMeshCompletionTable {
97
112
  this.pending.delete(key);
98
113
  pending.removeAbort?.();
99
114
  try {
100
- pending.resolve(retainCompletion(record));
115
+ pending.resolve(
116
+ retainCompletion(materialize === undefined ? (record as ReceiveRecord) : materialize())
117
+ );
101
118
  } catch (error) {
102
119
  pending.reject(error);
103
120
  }
@@ -253,7 +253,7 @@ export class ZLinkMeshDispatchPump {
253
253
  ): Promise<boolean> {
254
254
  let claimsDrained = 0;
255
255
  try {
256
- for (;;) {
256
+ while (!this.disposed) {
257
257
  if (claimBudget !== undefined && claimsDrained >= claimBudget) {
258
258
  // Lifecycle work has priority, but a bounded turn lets application
259
259
  // work make progress when lifecycle records keep arriving.
@@ -282,19 +282,18 @@ export class ZLinkMeshDispatchPump {
282
282
  );
283
283
  const capacity =
284
284
  owner.ordinaryIngressPreAdmitted === true ? MESH_DISPATCH_RECEIVE_CAPACITY : 1;
285
- for (;;) {
286
- let claimPermit =
287
- owner.terminalCompletion === true ||
288
- owner.ordinaryIngressPreAdmitted === true ||
289
- domain === ReadyDomain.Infrastructure
290
- ? undefined
291
- : await this.acquirePermit();
292
- try {
293
- if (this.capacityStop.signal.aborted) return;
294
- ownerReceiveBatch.reset(capacity);
295
- const received = claim.recvBatch(ownerReceiveBatch, ZLINK_BACKEND_RECV_DONT_WAIT);
296
- if (!received.ok) break;
297
- // This owner keeps its claim while it drains, so its records stay in FIFO order.
285
+ let claimPermit =
286
+ owner.terminalCompletion === true ||
287
+ owner.ordinaryIngressPreAdmitted === true ||
288
+ domain === ReadyDomain.Infrastructure
289
+ ? undefined
290
+ : await this.acquirePermit();
291
+ try {
292
+ if (this.capacityStop.signal.aborted) return;
293
+ ownerReceiveBatch.reset(capacity);
294
+ const received = claim.recvBatch(ownerReceiveBatch, ZLINK_BACKEND_RECV_DONT_WAIT);
295
+ if (received.ok) {
296
+ // The bounded batch stays in claim order until its records finish.
298
297
  try {
299
298
  for (const record of received.records) {
300
299
  if (this.disposed) break;
@@ -327,8 +326,6 @@ export class ZLinkMeshDispatchPump {
327
326
  if (permit === claimPermit) claimPermit = undefined;
328
327
  await runWithApplicationJobPermit(permit, dispatch);
329
328
  }
330
- const yieldTurn = this.yieldIfNeeded();
331
- if (yieldTurn !== undefined) await yieldTurn;
332
329
  }
333
330
  } finally {
334
331
  // Batch ownership includes records whose dispatch never began,
@@ -338,14 +335,16 @@ export class ZLinkMeshDispatchPump {
338
335
  record.releaseRetainedIngress?.();
339
336
  }
340
337
  }
341
- } finally {
342
- claimPermit?.releaseAfterInternalProcessing();
343
338
  }
339
+ } finally {
340
+ claimPermit?.releaseAfterInternalProcessing();
344
341
  }
345
342
  } finally {
346
343
  claim.release();
347
344
  if (receiveBatch === undefined) ownerReceiveBatch?.close();
348
345
  }
346
+ const yieldTurn = this.yieldIfNeeded();
347
+ if (yieldTurn !== undefined) await yieldTurn;
349
348
  };
350
349
  // An application worker drains the one owner it claimed. Infrastructure owners
351
350
  // drain independently, so a suspended owner does not hold the other claims.
@@ -366,10 +365,8 @@ export class ZLinkMeshDispatchPump {
366
365
  this.recordsSinceYield += 1;
367
366
  const yieldTurn = this.yieldIfNeeded();
368
367
  if (yieldTurn !== undefined) await yieldTurn;
369
- if (!drained.hasResidue) {
370
- return false;
371
- }
372
368
  }
369
+ return false;
373
370
  } finally {
374
371
  readyBatch.reset();
375
372
  }
@@ -394,7 +391,7 @@ export class ZLinkMeshDispatchPump {
394
391
 
395
392
  private async acquirePermit(): Promise<ApplicationJobPermitPort | undefined> {
396
393
  try {
397
- return await this.options.applicationJobQueue.acquire(this.capacityStop.signal);
394
+ return await this.options.applicationJobQueue.acquire(this.capacityStop.signal, 'remote');
398
395
  } catch (error) {
399
396
  if (this.disposed || this.capacityStop.signal.aborted) return undefined;
400
397
  throw error;