@zlink-systems/framework 0.29.0 → 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 (168) 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/actors/actor-runtime-contracts.d.ts +1 -0
  13. package/dist/runtime/actors/index.d.ts +2 -0
  14. package/dist/runtime/actors/index.js +21 -6
  15. package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +3 -1
  16. package/dist/runtime/application-jobs/application-ingress-record-owner.js +6 -1
  17. package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +3 -1
  18. package/dist/runtime/application-jobs/application-job-queue-scope.js +5 -0
  19. package/dist/runtime/application-jobs/contracts.d.ts +5 -1
  20. package/dist/runtime/backend/contracts/index.d.ts +10 -19
  21. package/dist/runtime/backend/mesh-completion-table.d.ts +6 -4
  22. package/dist/runtime/backend/mesh-completion-table.js +28 -18
  23. package/dist/runtime/backend/mesh-dispatch-pump.js +21 -25
  24. package/dist/runtime/backend/node/node-backend-adapter-factory.js +6 -1
  25. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +3 -3
  26. package/dist/runtime/backend/node/node-backend-adapter-support.js +24 -29
  27. package/dist/runtime/backend/node/node-mesh-backend-adapter.d.ts +1 -9
  28. package/dist/runtime/backend/node/node-mesh-backend-adapter.js +1 -1
  29. package/dist/runtime/backend/node/node-raw-binding-port.js +11 -6
  30. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +10 -12
  31. package/dist/runtime/backend/node/node-raw-mesh-backend.js +64 -98
  32. package/dist/runtime/backend/node/node-socket-backend-adapter.js +10 -5
  33. package/dist/runtime/backend/raw-binding-port.d.ts +9 -2
  34. package/dist/runtime/channels/channel-receive-loops.js +3 -3
  35. package/dist/runtime/channels/channel-runtime-lifecycle.js +2 -1
  36. package/dist/runtime/channels/channel-socket-registry.d.ts +10 -3
  37. package/dist/runtime/channels/channel-socket-registry.js +59 -82
  38. package/dist/runtime/diagnostics/message-flow.d.ts +1 -0
  39. package/dist/runtime/diagnostics/message-flow.js +3 -3
  40. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +15 -1
  41. package/dist/runtime/foundation/raw-service-mesh-runtime.js +193 -64
  42. package/dist/runtime/foundation/service-liveness-registry.d.ts +20 -8
  43. package/dist/runtime/foundation/service-liveness-registry.js +57 -44
  44. package/dist/runtime/foundation/service-mailbox.d.ts +3 -0
  45. package/dist/runtime/foundation/service-mailbox.js +2 -1
  46. package/dist/runtime/foundation/service-stateful-runtime.d.ts +7 -6
  47. package/dist/runtime/foundation/service-stateful-runtime.js +60 -41
  48. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +5 -2
  49. package/dist/runtime/foundation/service-stateful-wire-codec.js +43 -61
  50. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -3
  51. package/dist/runtime/foundation/service-topology-registry.js +9 -4
  52. package/dist/runtime/handlers/handler-instance-scope.js +1 -1
  53. package/dist/runtime/host/actor-packet-relay.js +7 -7
  54. package/dist/runtime/host/actor-placement-coordinator.d.ts +2 -1
  55. package/dist/runtime/host/actor-placement-coordinator.js +2 -1
  56. package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -1
  57. package/dist/runtime/host/actor-runtime-options-factory.js +2 -1
  58. package/dist/runtime/host/application-job-queue.d.ts +11 -4
  59. package/dist/runtime/host/application-job-queue.js +66 -10
  60. package/dist/runtime/host/entry-actor-runtime.js +1 -1
  61. package/dist/runtime/host/index.d.ts +1 -1
  62. package/dist/runtime/host/index.js +18 -13
  63. package/dist/runtime/host/instance-activation-authority.d.ts +2 -1
  64. package/dist/runtime/host/instance-activation-authority.js +89 -25
  65. package/dist/runtime/host/service-relocation-host-runtime.js +2 -2
  66. package/dist/runtime/host/spot-address-transport.d.ts +1 -0
  67. package/dist/runtime/host/spot-address-transport.js +25 -12
  68. package/dist/runtime/host/spot-runtime-options-factory.js +2 -2
  69. package/dist/runtime/host/user-spot-creation-coordinator.d.ts +1 -0
  70. package/dist/runtime/host/user-spot-creation-coordinator.js +1 -0
  71. package/dist/runtime/locations/domain-store-contract.d.ts +3 -2
  72. package/dist/runtime/locations/in-memory-authority-store.d.ts +5 -2
  73. package/dist/runtime/locations/in-memory-authority-store.js +107 -14
  74. package/dist/runtime/locations/in-memory-location-store.d.ts +3 -3
  75. package/dist/runtime/locations/in-memory-location-store.js +31 -13
  76. package/dist/runtime/locations/internal-location-contracts.d.ts +8 -0
  77. package/dist/runtime/locations/location-store-repository.d.ts +4 -2
  78. package/dist/runtime/locations/location-store-repository.js +209 -33
  79. package/dist/runtime/locations/resolvers.d.ts +2 -1
  80. package/dist/runtime/locations/resolvers.js +10 -1
  81. package/dist/runtime/messaging/submission-result.d.ts +1 -1
  82. package/dist/runtime/messaging/submission-result.js +2 -2
  83. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +2 -0
  84. package/dist/runtime/spots/index.d.ts +4 -3
  85. package/dist/runtime/spots/index.js +14 -14
  86. package/dist/runtime/spots/spot-activation.d.ts +2 -1
  87. package/dist/runtime/spots/spot-activation.js +4 -4
  88. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +2 -1
  89. package/dist/runtime/spots/spot-actor-admission-coordinator.js +11 -10
  90. package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +4 -1
  91. package/dist/runtime/spots/spot-actor-packet-dispatch.js +7 -5
  92. package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
  93. package/dist/runtime/spots/spot-actor-packet-relay-dispatch.js +1 -0
  94. package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
  95. package/dist/runtime/spots/spot-entry-activation.js +6 -1
  96. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +3 -1
  97. package/dist/runtime/spots/spot-node-runtime-manager.js +10 -11
  98. package/dist/runtime/spots/spot-routing-internal.d.ts +6 -0
  99. package/dist/runtime/spots/spot-runtime-ports.d.ts +2 -1
  100. package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
  101. package/dist/runtime/spots/spot-serial-executor.js +10 -1
  102. package/dist/runtime/streams/stream-session-runtime.js +1 -1
  103. package/package.json +3 -3
  104. package/src/contracts/Configuration/Builders.ts +1 -2
  105. package/src/contracts/Configuration/Configs.ts +0 -6
  106. package/src/contracts/Configuration/RegistrationBuilders.ts +8 -12
  107. package/src/contracts/Configuration/RegistrationTypes.ts +1 -6
  108. package/src/contracts/Configuration/RegistrationValidators.ts +9 -10
  109. package/src/contracts/Configuration/index.ts +1 -6
  110. package/src/internal.ts +1 -0
  111. package/src/runtime/actors/actor-handoff.ts +13 -4
  112. package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
  113. package/src/runtime/actors/index.ts +77 -42
  114. package/src/runtime/application-jobs/application-ingress-record-owner.ts +12 -2
  115. package/src/runtime/application-jobs/application-job-queue-scope.ts +6 -1
  116. package/src/runtime/application-jobs/contracts.ts +14 -1
  117. package/src/runtime/backend/contracts/index.ts +10 -18
  118. package/src/runtime/backend/mesh-completion-table.ts +36 -19
  119. package/src/runtime/backend/mesh-dispatch-pump.ts +19 -22
  120. package/src/runtime/backend/node/node-backend-adapter-factory.ts +12 -5
  121. package/src/runtime/backend/node/node-backend-adapter-support.ts +31 -29
  122. package/src/runtime/backend/node/node-mesh-backend-adapter.ts +3 -10
  123. package/src/runtime/backend/node/node-raw-binding-port.ts +19 -10
  124. package/src/runtime/backend/node/node-raw-mesh-backend.ts +81 -111
  125. package/src/runtime/backend/node/node-socket-backend-adapter.ts +12 -8
  126. package/src/runtime/backend/raw-binding-port.ts +12 -2
  127. package/src/runtime/channels/channel-receive-loops.ts +3 -3
  128. package/src/runtime/channels/channel-runtime-lifecycle.ts +7 -1
  129. package/src/runtime/channels/channel-socket-registry.ts +84 -130
  130. package/src/runtime/diagnostics/message-flow.ts +1 -1
  131. package/src/runtime/foundation/raw-service-mesh-runtime.ts +268 -70
  132. package/src/runtime/foundation/service-liveness-registry.ts +68 -59
  133. package/src/runtime/foundation/service-mailbox.ts +7 -1
  134. package/src/runtime/foundation/service-stateful-runtime.ts +109 -77
  135. package/src/runtime/foundation/service-stateful-wire-codec.ts +55 -79
  136. package/src/runtime/foundation/service-topology-registry.ts +13 -7
  137. package/src/runtime/handlers/handler-instance-scope.ts +2 -1
  138. package/src/runtime/host/actor-packet-relay.ts +7 -0
  139. package/src/runtime/host/actor-placement-coordinator.ts +8 -2
  140. package/src/runtime/host/actor-runtime-options-factory.ts +7 -0
  141. package/src/runtime/host/application-job-queue.ts +77 -10
  142. package/src/runtime/host/entry-actor-runtime.ts +1 -0
  143. package/src/runtime/host/index.ts +30 -7
  144. package/src/runtime/host/instance-activation-authority.ts +132 -27
  145. package/src/runtime/host/service-relocation-host-runtime.ts +2 -0
  146. package/src/runtime/host/spot-address-transport.ts +45 -22
  147. package/src/runtime/host/spot-runtime-options-factory.ts +2 -0
  148. package/src/runtime/host/user-spot-creation-coordinator.ts +8 -0
  149. package/src/runtime/locations/domain-store-contract.ts +6 -2
  150. package/src/runtime/locations/in-memory-authority-store.ts +153 -12
  151. package/src/runtime/locations/in-memory-location-store.ts +39 -13
  152. package/src/runtime/locations/internal-location-contracts.ts +10 -0
  153. package/src/runtime/locations/location-store-repository.ts +295 -36
  154. package/src/runtime/locations/resolvers.ts +25 -2
  155. package/src/runtime/messaging/submission-result.ts +2 -2
  156. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +2 -0
  157. package/src/runtime/spots/index.ts +16 -0
  158. package/src/runtime/spots/spot-activation.ts +5 -0
  159. package/src/runtime/spots/spot-actor-admission-coordinator.ts +13 -0
  160. package/src/runtime/spots/spot-actor-packet-dispatch.ts +10 -0
  161. package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
  162. package/src/runtime/spots/spot-actor-packet-relay-dispatch.ts +1 -0
  163. package/src/runtime/spots/spot-entry-activation.ts +7 -0
  164. package/src/runtime/spots/spot-node-runtime-manager.ts +14 -12
  165. package/src/runtime/spots/spot-routing-internal.ts +14 -0
  166. package/src/runtime/spots/spot-runtime-ports.ts +2 -0
  167. package/src/runtime/spots/spot-serial-executor.ts +10 -4
  168. package/src/runtime/streams/stream-session-runtime.ts +1 -1
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) {
@@ -49,6 +49,10 @@ export interface ZLinkActorManagerOptions {
49
49
  signal?: AbortSignal
50
50
  ) => Promise<ZLinkActorCreateResponse | undefined>;
51
51
  readonly actorDestroyedCleanup?: (actorId: string) => void;
52
+ readonly terminateActorActivation?: (
53
+ actor: ZLinkActor,
54
+ terminal: () => Promise<void>
55
+ ) => Promise<void>;
52
56
  readonly publishActorAuthority?: (
53
57
  actorType: string,
54
58
  actorRef: ActorRef,
@@ -29,6 +29,7 @@ import {
29
29
  wireReplyFailureException
30
30
  } from '../framework-errors-internal';
31
31
  import { disposeLifecycleHandlers } from '../handlers/handler-instance-scope';
32
+ import { currentZLinkActorExecution } from './actor-execution-context';
32
33
  import { encodeFrameworkPayloadMessage } from '../messaging/payload-codec';
33
34
  import { ZLinkActorCreationCoordinator, type ZLinkActorCreateRequest } from './actor-creation';
34
35
  import type { ZLinkActorManagerOptions } from './actor-runtime-contracts';
@@ -217,34 +218,43 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
217
218
  const completions = this.options.nativeActorCompletionTableProvider?.();
218
219
  const entryNodeRid = state.entryNodeRid ?? current.nodeRid;
219
220
  if (node === undefined || completions === undefined) return false;
220
- const destroyTask = state.getOrStartDestroy(entryNodeRid, async (nativeRef) => {
221
- if (nativeRef !== undefined) {
222
- const completion = await completions.submit(() => node.destroyActor(nativeRef, 0), signal);
223
- try {
224
- if (completion.terminalResult !== 0 || completion.failureErrno !== 0) {
225
- // Classify the (terminal, fine) pair via the shared translator
226
- // (spec 32-framework-error-model:83-118, 99-108) instead of
227
- // collapsing every destroy failure to NotFound; genuine
228
- // route-not-found fine codes still yield NotFound through the
229
- // shared table.
230
- throw wireReplyFailureException(
231
- completion.terminalResult,
232
- completion.failureErrno,
233
- `Actor '${actor.actorId}' destroy failed with result '${completion.terminalResult}' and errno '${completion.failureErrno}'.`
234
- );
221
+ const destroyTask = state.getOrStartDestroy(entryNodeRid, (nativeRef) =>
222
+ this.terminateActor(state, async () => {
223
+ if (nativeRef !== undefined) {
224
+ const completion = await completions.submit(
225
+ () => node.destroyActor(nativeRef, 0),
226
+ signal
227
+ );
228
+ try {
229
+ if (completion.terminalResult !== 0 || completion.failureErrno !== 0) {
230
+ // Classify the (terminal, fine) pair via the shared translator
231
+ // (spec 32-framework-error-model:83-118, 99-108) instead of
232
+ // collapsing every destroy failure to NotFound; genuine
233
+ // route-not-found fine codes still yield NotFound through the
234
+ // shared table.
235
+ throw wireReplyFailureException(
236
+ completion.terminalResult,
237
+ completion.failureErrno,
238
+ `Actor '${actor.actorId}' destroy failed with result '${completion.terminalResult}' and errno '${completion.failureErrno}'.`
239
+ );
240
+ }
241
+ } finally {
242
+ closeMeshCompletion(completion);
235
243
  }
236
- } finally {
237
- closeMeshCompletion(completion);
244
+ state.markNativeActorDestroyed(nativeRef);
238
245
  }
239
- state.markNativeActorDestroyed(nativeRef);
240
- }
241
- if (state.actorType !== undefined && state.ownsLocation) {
242
- await this.options.locationLifecycle?.releaseActor(state.actorType, actor.actorId, current);
243
- }
244
- await this.finalizeDestroyedActor(actor.actorId, state);
245
- });
246
+ if (state.actorType !== undefined && state.ownsLocation) {
247
+ await this.options.locationLifecycle?.releaseActor(
248
+ state.actorType,
249
+ actor.actorId,
250
+ current
251
+ );
252
+ }
253
+ await this.finalizeDestroyedActor(actor.actorId, state);
254
+ })
255
+ );
246
256
  try {
247
- await destroyTask;
257
+ await this.awaitActorDestroy(state, destroyTask);
248
258
  } catch (error) {
249
259
  state.clearFailedDestroy(destroyTask);
250
260
  throw error;
@@ -548,6 +558,29 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
548
558
  this.options.metrics?.change(METRIC_NAMES.ActorCount, -1);
549
559
  }
550
560
  }
561
+
562
+ private terminateActor(
563
+ state: ZLinkActorRuntimeState,
564
+ terminal: () => Promise<void>
565
+ ): Promise<void> {
566
+ const actor = state.actor;
567
+ const completion =
568
+ actor === undefined || this.options.terminateActorActivation === undefined
569
+ ? terminal()
570
+ : this.options.terminateActorActivation(actor, terminal);
571
+ return completion;
572
+ }
573
+
574
+ private awaitActorDestroy(
575
+ state: ZLinkActorRuntimeState,
576
+ completion: Promise<void>
577
+ ): Promise<void> {
578
+ if (currentZLinkActorExecution()?.actorId === state.actorId) {
579
+ this.detachedTaskRunner.runDetached('Actor terminal cleanup', () => completion);
580
+ return Promise.resolve();
581
+ }
582
+ return completion;
583
+ }
551
584
  async getOrCreateActor(
552
585
  actorId: string,
553
586
  actorType: string,
@@ -648,25 +681,27 @@ export class DefaultZLinkActorManager implements ZLinkActorManager {
648
681
  `Actor '${actor.context.actorId}' must leave its current SPOT before destroy.`
649
682
  );
650
683
  }
651
- const destroyTask = state.getOrStartDestroy(entryNodeRid, async (actorRef) => {
652
- const destroyedActorRef =
653
- actorRef === undefined ? undefined : toFrameworkActorRef(actorRef, state.meshName ?? '');
654
- if (actorRef !== undefined) {
655
- await node.destroyActor(actorRef, 0, destroySignal);
656
- state.markNativeActorDestroyed(actorRef);
657
- }
658
- if (state.actorType !== undefined && state.ownsLocation) {
659
- await this.options.locationLifecycle?.releaseActor(
660
- state.actorType,
661
- actor.context.actorId,
662
- destroyedActorRef
663
- );
664
- }
665
- await this.finalizeDestroyedActor(actor.context.actorId, state);
666
- });
684
+ const destroyTask = state.getOrStartDestroy(entryNodeRid, (actorRef) =>
685
+ this.terminateActor(state, async () => {
686
+ const destroyedActorRef =
687
+ actorRef === undefined ? undefined : toFrameworkActorRef(actorRef, state.meshName ?? '');
688
+ if (actorRef !== undefined) {
689
+ await node.destroyActor(actorRef, 0, destroySignal);
690
+ state.markNativeActorDestroyed(actorRef);
691
+ }
692
+ if (state.actorType !== undefined && state.ownsLocation) {
693
+ await this.options.locationLifecycle?.releaseActor(
694
+ state.actorType,
695
+ actor.context.actorId,
696
+ destroyedActorRef
697
+ );
698
+ }
699
+ await this.finalizeDestroyedActor(actor.context.actorId, state);
700
+ })
701
+ );
667
702
 
668
703
  try {
669
- await destroyTask;
704
+ await this.awaitActorDestroy(state, destroyTask);
670
705
  } catch (error) {
671
706
  state.clearFailedDestroy(destroyTask);
672
707
  throw error;
@@ -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;
@@ -1,4 +1,9 @@
1
- import type { Context, CoreHwmBudgetSnapshot, TopicMessage } from '@zlink-systems/zlink';
1
+ import type {
2
+ Context,
3
+ CoreHwmBudgetSnapshot,
4
+ ReceiveFlowState,
5
+ TopicMessage
6
+ } from '@zlink-systems/zlink';
2
7
  import type { ZLinkCoreHwmOptions } from '../../../contracts/Configuration';
3
8
  import type {
4
9
  ZLinkBackendAdapterFactory,
@@ -60,10 +65,12 @@ class ZLinkNodeChannelBackendAdapter implements ZLinkChannelBackendAdapter {
60
65
  }
61
66
 
62
67
  createRouterSocket(context: ZLinkBackendContext): ZLinkBackendRouterSocket {
63
- return wrapSocket(
64
- zlink.createRouterSocket(asNodeContext(context)),
65
- true
66
- ) as unknown as ZLinkBackendRouterSocket;
68
+ const socket = zlink.createRouterSocket(asNodeContext(context));
69
+ const adapter = wrapSocket(socket, true);
70
+ adapter.setReceiveFlowState = (state: ReceiveFlowState): void => {
71
+ socket.setReceiveFlowState(state);
72
+ };
73
+ return adapter as unknown as ZLinkBackendRouterSocket;
67
74
  }
68
75
 
69
76
  createPublisherSocket(context: ZLinkBackendContext): ZLinkBackendPublisherSocket {