@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
@@ -100,6 +100,8 @@ export type RawServiceIngressHandler = (
100
100
  ) => RawServicePumpResult | undefined | Promise<RawServicePumpResult | undefined>;
101
101
 
102
102
  export interface RawServiceMeshRuntimeOptions {
103
+ readonly onReceiveReady?: () => void;
104
+ readonly infrastructureTaskRunner?: import('../spots/spot-actor-join-dispatch').ZLinkDetachedTaskRunner;
103
105
  readonly onPendingOperationsChanged?: () => void;
104
106
  readonly descriptor: ServiceNodeDescriptor;
105
107
  readonly resolveAdvertisedEndpoint?: (boundEndpoint: string) => string;
@@ -170,6 +172,19 @@ export class RawServiceMeshRuntime {
170
172
  private readonly applicationJobQueue: ApplicationJobQueuePort;
171
173
  private readonly peerAdmissionSealed?: () => boolean;
172
174
  private readonly applicationJobStop = new AbortController();
175
+ private receiveAcquisition?: Promise<ApplicationJobPermitPort>;
176
+ private receivePermit?: ApplicationJobPermitPort;
177
+ private readonly controlSends = new Map<
178
+ string,
179
+ {
180
+ readonly task: Promise<boolean>;
181
+ next?: () => boolean | Promise<boolean>;
182
+ current?: () => boolean;
183
+ }
184
+ >();
185
+ private infrastructureFailure?: { readonly error: unknown };
186
+ private readonly onReceiveReady?: () => void;
187
+ private readonly infrastructureTaskRunner?: RawServiceMeshRuntimeOptions['infrastructureTaskRunner'];
173
188
  private readonly onPeerNotRequired?: RawServiceMeshRuntimeOptions['onPeerNotRequired'];
174
189
  private readonly onPeerDisconnected?: RawServiceMeshRuntimeOptions['onPeerDisconnected'];
175
190
  private readonly onProtocolError?: RawServiceMeshRuntimeOptions['onProtocolError'];
@@ -183,6 +198,8 @@ export class RawServiceMeshRuntime {
183
198
  private closed = false;
184
199
 
185
200
  constructor(options: RawServiceMeshRuntimeOptions) {
201
+ this.onReceiveReady = options.onReceiveReady;
202
+ this.infrastructureTaskRunner = options.infrastructureTaskRunner;
186
203
  this.operations = new OperationRegistry(undefined, options.onPendingOperationsChanged);
187
204
  this.descriptor = options.descriptor;
188
205
  this.topology = new ServiceTopologyRegistry(options.descriptor);
@@ -371,7 +388,7 @@ export class RawServiceMeshRuntime {
371
388
  for (const listener of this.peerConnectionIntentRemoved) listener(nodeRoutingId);
372
389
  }
373
390
 
374
- async announcePeer(nodeRoutingId: string): Promise<boolean> {
391
+ announcePeer(nodeRoutingId: string): Promise<boolean> {
375
392
  const expected = this.expectedPeers.get(nodeRoutingId);
376
393
  const generation = this.selectedRoutes.get(nodeRoutingId);
377
394
  if (
@@ -380,17 +397,27 @@ export class RawServiceMeshRuntime {
380
397
  expected.helloSubmittedGeneration === generation ||
381
398
  this.peerAdmissionSealed?.() === true
382
399
  )
383
- return false;
384
- const accepted = await this.send(nodeRoutingId, [
385
- encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor())
386
- ]);
387
- if (
388
- accepted &&
400
+ return Promise.resolve(false);
401
+ const current = () =>
389
402
  this.expectedPeers.get(nodeRoutingId) === expected &&
390
- this.selectedRoutes.get(nodeRoutingId) === generation
391
- )
392
- expected.helloSubmittedGeneration = generation;
393
- return accepted;
403
+ this.selectedRoutes.get(nodeRoutingId) === generation;
404
+ return Promise.resolve(
405
+ this.startControlSend(
406
+ nodeRoutingId,
407
+ M6aServiceWireCommand.hello,
408
+ () =>
409
+ this.submitControl(
410
+ nodeRoutingId,
411
+ [
412
+ encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor())
413
+ ],
414
+ () => {
415
+ if (current()) expected.helloSubmittedGeneration = generation;
416
+ }
417
+ ),
418
+ current
419
+ )
420
+ );
394
421
  }
395
422
 
396
423
  isPeerRouteReady(nodeRoutingId: string, lifecycleGeneration?: bigint): boolean {
@@ -400,8 +427,7 @@ export class RawServiceMeshRuntime {
400
427
  return (
401
428
  peer !== undefined &&
402
429
  (lifecycleGeneration === undefined ||
403
- peer.descriptor.lifecycleGeneration === lifecycleGeneration) &&
404
- this.liveness.isReady(nodeRoutingId, peer.connectionId)
430
+ peer.descriptor.lifecycleGeneration === lifecycleGeneration)
405
431
  );
406
432
  }
407
433
 
@@ -548,7 +574,7 @@ export class RawServiceMeshRuntime {
548
574
  signal === undefined
549
575
  ? this.applicationJobStop.signal
550
576
  : AbortSignal.any([this.applicationJobStop.signal, signal]);
551
- const permit = await this.applicationJobQueue.acquire(waitSignal);
577
+ const permit = await this.applicationJobQueue.acquire(waitSignal, 'local');
552
578
  return ApplicationIngressRecordOwner.create(this.applicationJobQueue, permit, { close() {} });
553
579
  }
554
580
 
@@ -561,7 +587,13 @@ export class RawServiceMeshRuntime {
561
587
  parts: readonly Uint8Array[],
562
588
  timeoutMs: number
563
589
  ): Promise<readonly Buffer[]> {
564
- return this.requireStarted().request(targetNodeRoutingId, parts, timeoutMs);
590
+ const admission = this.topology.peer(targetNodeRoutingId)?.liveness;
591
+ return this.requireStarted().request(
592
+ targetNodeRoutingId,
593
+ parts,
594
+ timeoutMs,
595
+ admission?.recordReceived
596
+ );
565
597
  }
566
598
 
567
599
  replyService(
@@ -607,6 +639,18 @@ export class RawServiceMeshRuntime {
607
639
  nowMs = performance.now(),
608
640
  observe?: RawServicePumpObserver
609
641
  ): Promise<RawServicePumpResult> {
642
+ const result = await this.receiveOne(nowMs, observe);
643
+ const acquisition = this.receiveAcquisition;
644
+ if (acquisition === undefined) return result;
645
+ // A single receive operation may await its FIFO grant. Infrastructure
646
+ // rounds use receiveOne directly and end while that grant is pending.
647
+ try {
648
+ await acquisition;
649
+ } catch (error) {
650
+ if (this.closed) return 'noData';
651
+ throw error;
652
+ }
653
+ if (this.closed) return 'noData';
610
654
  return this.receiveOne(nowMs, observe);
611
655
  }
612
656
 
@@ -616,6 +660,7 @@ export class RawServiceMeshRuntime {
616
660
 
617
661
  /** Returns whether a receive budget ended before no-data; idle ticks only maintain peers. */
618
662
  async pumpBatch(receiveReady = true, routeReady = false): Promise<boolean> {
663
+ this.observeInfrastructureFailure();
619
664
  if (routeReady) await this.observeSelectedRoutes();
620
665
  const startedAtMs = performance.now();
621
666
  let messages = 0;
@@ -639,8 +684,10 @@ export class RawServiceMeshRuntime {
639
684
  break;
640
685
  }
641
686
  if (!this.closed) {
642
- await this.announceExpectedPeers();
643
- await this.tickLiveness();
687
+ for (const nodeRoutingId of this.expectedPeers.keys()) {
688
+ if (this.topology.peer(nodeRoutingId) === undefined) await this.announcePeer(nodeRoutingId);
689
+ }
690
+ this.tickLiveness();
644
691
  }
645
692
  return receiveReady && messages > 0;
646
693
  }
@@ -650,13 +697,40 @@ export class RawServiceMeshRuntime {
650
697
  observe?: RawServicePumpObserver
651
698
  ): Promise<RawServicePumpResult> {
652
699
  const router = this.requireStarted();
653
- let permit: ApplicationJobPermitPort;
654
- try {
655
- permit = await this.applicationJobQueue.acquire(this.applicationJobStop.signal);
656
- } catch (error) {
657
- if (this.closed || this.applicationJobStop.signal.aborted) return 'noData';
658
- throw error;
700
+ this.observeInfrastructureFailure();
701
+ if (this.receivePermit === undefined && this.receiveAcquisition === undefined) {
702
+ let resolveAcquisition!: (permit: ApplicationJobPermitPort) => void;
703
+ let rejectAcquisition!: (error: unknown) => void;
704
+ const acquisition = new Promise<ApplicationJobPermitPort>((resolve, reject) => {
705
+ resolveAcquisition = resolve;
706
+ rejectAcquisition = reject;
707
+ });
708
+ const permit = this.applicationJobQueue.acquireOrResume(
709
+ (permit) => {
710
+ this.receiveAcquisition = undefined;
711
+ if (this.closed) permit.releaseAfterInternalProcessing();
712
+ else {
713
+ this.receivePermit = permit;
714
+ this.onReceiveReady?.();
715
+ }
716
+ resolveAcquisition(permit);
717
+ },
718
+ rejectAcquisition,
719
+ this.applicationJobStop.signal,
720
+ 'remote'
721
+ );
722
+ if (permit !== undefined) this.receivePermit = permit;
723
+ else {
724
+ this.receiveAcquisition = acquisition;
725
+ acquisition.catch((error) => {
726
+ this.receiveAcquisition = undefined;
727
+ if (!this.applicationJobStop.signal.aborted) this.reportInfrastructureFailure(error);
728
+ });
729
+ }
659
730
  }
731
+ const permit = this.receivePermit;
732
+ this.receivePermit = undefined;
733
+ if (permit === undefined) return 'noData';
660
734
  if (this.closed) {
661
735
  permit.releaseAfterInternalProcessing();
662
736
  return 'noData';
@@ -744,7 +818,7 @@ export class RawServiceMeshRuntime {
744
818
  (received.parts.length === 1 && received.parts[0]!.byteLength === 0)
745
819
  ) {
746
820
  if (this.peerAdmissionSealed?.() === true) return 'dropped';
747
- return (await this.send(received.sourceRid, [
821
+ return (await this.sendControl(received.sourceRid, [
748
822
  encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor())
749
823
  ]))
750
824
  ? 'infrastructure'
@@ -772,17 +846,12 @@ export class RawServiceMeshRuntime {
772
846
  (expected.meshName !== descriptor.meshName ||
773
847
  expected.nodeRoutingId !== descriptor.nodeRoutingId)
774
848
  ) {
775
- await this.send(received.sourceRid, [
849
+ await this.sendControl(received.sourceRid, [
776
850
  encodeReject(enumWireRejectReason('identityMismatch'))
777
851
  ]);
778
852
  return 'infrastructure';
779
853
  }
780
- const result = this.admitPeer(
781
- descriptor,
782
- routeConnectionId(received.routeGeneration),
783
- nowMs,
784
- expected
785
- );
854
+ const result = this.admitPeer(descriptor, received.routeGeneration, nowMs, expected);
786
855
  if (result !== 'admitted' && result !== 'notRequired') {
787
856
  const detail =
788
857
  result === 'invalidDescriptor'
@@ -796,7 +865,7 @@ export class RawServiceMeshRuntime {
796
865
  `RouteMesh admission rejected peer '${received.sourceRid}' as ${result}` +
797
866
  (detail === undefined ? '.' : ` (${detail}).`)
798
867
  );
799
- await this.send(received.sourceRid, [encodeReject(admissionReason(result))]);
868
+ await this.sendControl(received.sourceRid, [encodeReject(admissionReason(result))]);
800
869
  return 'infrastructure';
801
870
  }
802
871
  if (result === 'admitted') this.selectBilateralConnection(descriptor);
@@ -804,7 +873,7 @@ export class RawServiceMeshRuntime {
804
873
  // Hello supplies only the sender's descriptor. Even NotRequired
805
874
  // peers must receive ours before the intent owner closes transport;
806
875
  // submitting a response does not guarantee delivery before close.
807
- await this.send(received.sourceRid, [
876
+ await this.sendControl(received.sourceRid, [
808
877
  encodeRouteMeshAdmission(M6aServiceWireCommand.admit, this.topology.localDescriptor())
809
878
  ]);
810
879
  } else if (result === 'notRequired') {
@@ -819,6 +888,8 @@ export class RawServiceMeshRuntime {
819
888
  }
820
889
  const peer = this.topology.peer(received.sourceRid);
821
890
  if (peer === undefined) return 'protocolError';
891
+ if (peer.liveness !== undefined && peer.liveness.connectionId === received.routeGeneration)
892
+ this.liveness.recordReceived(peer.liveness, nowMs);
822
893
  if (
823
894
  header.command === M6aServiceWireCommand.livenessProbe ||
824
895
  header.command === M6aServiceWireCommand.livenessAck
@@ -828,11 +899,11 @@ export class RawServiceMeshRuntime {
828
899
  if (record.command === M6aServiceWireCommand.livenessProbe) {
829
900
  const ack = this.liveness.acknowledgeProbe(
830
901
  received.sourceRid,
831
- peer.connectionId,
902
+ received.routeGeneration,
832
903
  record.probeId
833
904
  );
834
905
  if (ack === undefined) return 'protocolError';
835
- const sent = await this.send(received.sourceRid, [
906
+ const sent = await this.sendControl(received.sourceRid, [
836
907
  livenessCodec.encodeLivenessRecord({
837
908
  command: M6aServiceWireCommand.livenessAck,
838
909
  probeId: record.probeId
@@ -840,7 +911,12 @@ export class RawServiceMeshRuntime {
840
911
  ]);
841
912
  if (!sent) return 'dropped';
842
913
  } else {
843
- this.liveness.acknowledge(received.sourceRid, peer.connectionId, record.probeId, nowMs);
914
+ this.liveness.acknowledge(
915
+ received.sourceRid,
916
+ received.routeGeneration,
917
+ record.probeId,
918
+ nowMs
919
+ );
844
920
  }
845
921
  return 'infrastructure';
846
922
  }
@@ -950,21 +1026,24 @@ export class RawServiceMeshRuntime {
950
1026
  return this.operations.size;
951
1027
  }
952
1028
 
953
- async tickLiveness(nowMs = performance.now()): Promise<ServiceLivenessTick> {
1029
+ tickLiveness(nowMs = performance.now()): ServiceLivenessTick {
954
1030
  const result = this.liveness.tick(nowMs);
955
1031
  this.requireStarted();
956
1032
  for (const probe of result.probes) {
957
- await this.send(probe.nodeRoutingId, [
1033
+ const parts = [
958
1034
  livenessCodec.encodeLivenessRecord({
959
1035
  command: M6aServiceWireCommand.livenessProbe,
960
1036
  probeId: probe.probeId
961
1037
  })
962
- ]);
1038
+ ];
1039
+ this.startControlSend(probe.nodeRoutingId, M6aServiceWireCommand.livenessProbe, () =>
1040
+ this.submitControl(probe.nodeRoutingId, parts)
1041
+ );
963
1042
  }
964
1043
  for (const nodeRoutingId of result.timedOutNodes) {
965
1044
  const peer = this.topology.peer(nodeRoutingId);
966
1045
  if (peer === undefined) continue;
967
- // Liveness timeout is a semantic peer removal of the admitted route.
1046
+ this.requireStarted().disconnectRid!(nodeRoutingId);
968
1047
  this.removePeer(peer);
969
1048
  }
970
1049
  return result;
@@ -990,7 +1069,7 @@ export class RawServiceMeshRuntime {
990
1069
  const expected = this.expectedPeers.get(nodeRoutingId);
991
1070
  if (expected !== undefined) delete expected.helloSubmittedGeneration;
992
1071
  const peer = this.topology.peer(nodeRoutingId);
993
- if (peer !== undefined && peer.connectionId === routeConnectionId(generation)) {
1072
+ if (peer?.liveness?.connectionId === generation) {
994
1073
  this.removePeer(peer);
995
1074
  }
996
1075
  }
@@ -1008,9 +1087,13 @@ export class RawServiceMeshRuntime {
1008
1087
 
1009
1088
  close(): void {
1010
1089
  if (this.closed) return;
1090
+ this.closed = true;
1011
1091
  this.applicationJobStop.abort(
1012
1092
  new Error('Raw service runtime application job admission stopped.')
1013
1093
  );
1094
+ this.receivePermit?.releaseAfterInternalProcessing();
1095
+ this.receivePermit = undefined;
1096
+ for (const send of this.controlSends.values()) delete send.next;
1014
1097
  this.mailbox.close();
1015
1098
  this.operations.close('Raw service runtime closed.');
1016
1099
  this.serviceIngress = undefined;
@@ -1022,7 +1105,6 @@ export class RawServiceMeshRuntime {
1022
1105
  if (host !== undefined) host.close();
1023
1106
  this.router = undefined;
1024
1107
  this.host = undefined;
1025
- this.closed = true;
1026
1108
  }
1027
1109
 
1028
1110
  private requestToTarget(
@@ -1107,7 +1189,8 @@ export class RawServiceMeshRuntime {
1107
1189
  })().catch((error) => this.operations.fail(pending.id, error));
1108
1190
  return pending;
1109
1191
  }
1110
- if (this.topology.peer(selectedTargetNodeRoutingId) === undefined) {
1192
+ const selectedPeer = this.topology.peer(selectedTargetNodeRoutingId);
1193
+ if (selectedPeer === undefined) {
1111
1194
  // Logical target selection is the requester's decision: an RID that is
1112
1195
  // not an admitted peer is not a target. Whether an admitted target's
1113
1196
  // route can carry the request is Core's REQUEST result.
@@ -1118,11 +1201,14 @@ export class RawServiceMeshRuntime {
1118
1201
  return pending;
1119
1202
  }
1120
1203
  const parts = [header, encodedPayload];
1121
- let router: ZLinkRawRouterPort;
1122
1204
  let request: Promise<readonly Uint8Array[]>;
1123
1205
  try {
1124
- router = this.requireStarted();
1125
- request = router.request(selectedTargetNodeRoutingId, parts, timeoutMs);
1206
+ request = this.requireStarted().request(
1207
+ selectedTargetNodeRoutingId,
1208
+ parts,
1209
+ timeoutMs,
1210
+ selectedPeer.liveness?.recordReceived
1211
+ );
1126
1212
  } catch (error) {
1127
1213
  this.operations.fail(pending.id, error);
1128
1214
  return pending;
@@ -1182,7 +1268,7 @@ export class RawServiceMeshRuntime {
1182
1268
 
1183
1269
  private admitPeer(
1184
1270
  descriptor: ServiceNodeDescriptor,
1185
- connectionId: string,
1271
+ connectionId: string | bigint,
1186
1272
  nowMs: number,
1187
1273
  expected?: {
1188
1274
  readonly endpoint?: string;
@@ -1202,12 +1288,18 @@ export class RawServiceMeshRuntime {
1202
1288
  `RouteMesh peer '${descriptor.nodeRoutingId}' descriptor revision is stale or conflicting.`
1203
1289
  );
1204
1290
  }
1205
- const result = this.topology.admit(descriptor, connectionId, expected);
1206
- if (result === 'admitted') {
1207
- this.liveness.admit(descriptor.nodeRoutingId, connectionId, nowMs);
1208
- this.liveness.requestProbe(descriptor.nodeRoutingId, connectionId, nowMs);
1209
- } else if (result === 'notRequired' && previous !== undefined) {
1210
- this.liveness.disconnect(descriptor.nodeRoutingId, previous.connectionId);
1291
+ const topologyConnectionId =
1292
+ typeof connectionId === 'bigint' ? `route:${connectionId}` : connectionId;
1293
+ const result = this.topology.admit(descriptor, topologyConnectionId, expected, () => {
1294
+ const admission = this.liveness.admit(descriptor.nodeRoutingId, connectionId, nowMs);
1295
+ this.liveness.recordReceived(admission, nowMs);
1296
+ return admission;
1297
+ });
1298
+ if (result === 'notRequired' && previous !== undefined) {
1299
+ this.liveness.disconnect(
1300
+ descriptor.nodeRoutingId,
1301
+ previous.liveness?.connectionId ?? previous.connectionId
1302
+ );
1211
1303
  }
1212
1304
  return result;
1213
1305
  }
@@ -1244,7 +1336,10 @@ export class RawServiceMeshRuntime {
1244
1336
 
1245
1337
  private removePeer(peer: AdmittedServicePeer): void {
1246
1338
  this.topology.disconnect(peer.descriptor.nodeRoutingId, peer.connectionId);
1247
- this.liveness.disconnect(peer.descriptor.nodeRoutingId, peer.connectionId);
1339
+ this.liveness.disconnect(
1340
+ peer.descriptor.nodeRoutingId,
1341
+ peer.liveness?.connectionId ?? peer.connectionId
1342
+ );
1248
1343
  this.onPeerDisconnected?.(
1249
1344
  peer.descriptor.nodeRoutingId,
1250
1345
  peer.descriptor.advertisedEndpoint,
@@ -1257,18 +1352,129 @@ export class RawServiceMeshRuntime {
1257
1352
  return this.router;
1258
1353
  }
1259
1354
 
1260
- private async send(targetNodeRoutingId: string, parts: readonly Uint8Array[]): Promise<boolean> {
1355
+ private async sendControl(
1356
+ targetNodeRoutingId: string,
1357
+ parts: readonly Uint8Array[]
1358
+ ): Promise<boolean> {
1359
+ return this.startControlSend(targetNodeRoutingId, decodeHeader(parts[0]!).command, () =>
1360
+ this.submitControl(targetNodeRoutingId, parts)
1361
+ );
1362
+ }
1363
+
1364
+ private submitControl(
1365
+ target: string,
1366
+ parts: readonly Uint8Array[],
1367
+ accepted?: () => void
1368
+ ): boolean | Promise<boolean> {
1369
+ let submission;
1261
1370
  try {
1262
- await this.requireStarted().send(targetNodeRoutingId, parts);
1263
- return true;
1371
+ submission = this.requireStarted().submitSend(target, parts);
1264
1372
  } catch (error) {
1265
- if (isZLinkBackendResultError(error) && error.operation === 'submit') {
1266
- const terminal = submitToRequestResult(error.result, error.phase);
1267
- if (terminal === RequestResult.NotConnected || terminal === RequestResult.NotFound)
1268
- return false;
1373
+ return this.sendFailure(error);
1374
+ }
1375
+ if (submission.result === SubmitResult.Ok) {
1376
+ accepted?.();
1377
+ return true;
1378
+ }
1379
+ return submission.admitted.then(
1380
+ () => {
1381
+ accepted?.();
1382
+ return true;
1383
+ },
1384
+ (error) => this.sendFailure(error)
1385
+ );
1386
+ }
1387
+
1388
+ private startControlSend(
1389
+ target: string,
1390
+ command: number,
1391
+ attempt: () => boolean | Promise<boolean>,
1392
+ current?: () => boolean
1393
+ ): boolean {
1394
+ const key = JSON.stringify([target, command]);
1395
+ const existing = this.controlSends.get(key);
1396
+ if (
1397
+ existing !== undefined &&
1398
+ (command !== M6aServiceWireCommand.hello || existing.current?.() === true)
1399
+ ) {
1400
+ if (command !== M6aServiceWireCommand.hello) {
1401
+ existing.next = attempt;
1402
+ existing.current = current;
1269
1403
  }
1270
- throw error;
1404
+ return true;
1405
+ }
1406
+ const first = attempt();
1407
+ if (typeof first === 'boolean') return first;
1408
+ const owner = this;
1409
+ const state: {
1410
+ task: Promise<boolean>;
1411
+ next?: () => boolean | Promise<boolean>;
1412
+ current?: () => boolean;
1413
+ } = {
1414
+ current,
1415
+ task: first.then(function drain(accepted): boolean | Promise<boolean> {
1416
+ if (!owner.closed && state.next !== undefined) {
1417
+ const next = state.next;
1418
+ delete state.next;
1419
+ return Promise.resolve(next()).then(drain);
1420
+ }
1421
+ if (owner.controlSends.get(key) === state) owner.controlSends.delete(key);
1422
+ return accepted;
1423
+ })
1424
+ };
1425
+ this.controlSends.set(key, state);
1426
+ state.task.then(
1427
+ () => {},
1428
+ (error) => {
1429
+ if (this.controlSends.get(key) === state) this.controlSends.delete(key);
1430
+ if (this.infrastructureTaskRunner === undefined) this.reportInfrastructureFailure(error);
1431
+ }
1432
+ );
1433
+ this.infrastructureTaskRunner?.runDetached('RouteMesh control SEND', async () => {
1434
+ await state.task;
1435
+ });
1436
+ return true;
1437
+ }
1438
+
1439
+ private reportInfrastructureFailure(error: unknown): void {
1440
+ this.infrastructureFailure = {
1441
+ error:
1442
+ this.infrastructureFailure === undefined
1443
+ ? error
1444
+ : new AggregateError(
1445
+ [this.infrastructureFailure.error, error],
1446
+ 'RouteMesh infrastructure failed.'
1447
+ )
1448
+ };
1449
+ this.onReceiveReady?.();
1450
+ }
1451
+
1452
+ private observeInfrastructureFailure(): void {
1453
+ const failure = this.infrastructureFailure;
1454
+ this.infrastructureFailure = undefined;
1455
+ if (failure !== undefined) throw failure.error;
1456
+ }
1457
+
1458
+ private send(targetNodeRoutingId: string, parts: readonly Uint8Array[]): Promise<boolean> {
1459
+ return this.requireStarted()
1460
+ .send(targetNodeRoutingId, parts)
1461
+ .then(
1462
+ () => {
1463
+ return true;
1464
+ },
1465
+ (error) => {
1466
+ return this.sendFailure(error);
1467
+ }
1468
+ );
1469
+ }
1470
+
1471
+ private sendFailure(error: unknown): false {
1472
+ if (isZLinkBackendResultError(error) && error.operation === 'submit') {
1473
+ const terminal = submitToRequestResult(error.result, error.phase);
1474
+ if (terminal === RequestResult.NotConnected || terminal === RequestResult.NotFound)
1475
+ return false;
1271
1476
  }
1477
+ throw error;
1272
1478
  }
1273
1479
  }
1274
1480
 
@@ -1309,14 +1515,6 @@ function isAlreadyDisconnectedError(error: unknown): boolean {
1309
1515
  return (error as { readonly nativeErrno?: unknown }).nativeErrno === nativeErrnoValues.ENOENT;
1310
1516
  }
1311
1517
 
1312
- /**
1313
- * An admission belongs to one Core selected route. Its connection identity is
1314
- * that route's generation, an opaque token compared only for equality.
1315
- */
1316
- function routeConnectionId(routeGeneration: bigint): string {
1317
- return `route:${routeGeneration.toString()}`;
1318
- }
1319
-
1320
1518
  function describeInvalidAdmissionDescriptor(
1321
1519
  descriptor: ServiceNodeDescriptor,
1322
1520
  expected: ServicePeerAdmissionExpectation | undefined,