@zlink-systems/framework 0.30.0 → 0.30.1

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 (245) hide show
  1. package/LICENSE +150 -68
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/contracts/Actors/ZLinkActorClient.d.ts +2 -1
  4. package/dist/contracts/Channels/Calls.d.ts +2 -1
  5. package/dist/contracts/Common/ZLinkMessageMetadata.js +11 -5
  6. package/dist/contracts/Handlers/Attributes.d.ts +5 -13
  7. package/dist/contracts/Handlers/Attributes.js +7 -11
  8. package/dist/contracts/Handlers/JsonContract.d.ts +2 -46
  9. package/dist/contracts/Handlers/JsonContract.js +4 -153
  10. package/dist/contracts/Spots/Contracts.d.ts +2 -1
  11. package/dist/nest-integration.d.ts +1 -1
  12. package/dist/nest-integration.js +2 -1
  13. package/dist/runtime/actors/actor-client.d.ts +1 -1
  14. package/dist/runtime/actors/actor-client.js +14 -12
  15. package/dist/runtime/actors/actor-handoff.d.ts +1 -1
  16. package/dist/runtime/actors/actor-handoff.js +7 -4
  17. package/dist/runtime/actors/actor-join-relocation.d.ts +1 -0
  18. package/dist/runtime/actors/actor-local-native-join.js +9 -12
  19. package/dist/runtime/actors/actor-remote-wire.js +15 -0
  20. package/dist/runtime/actors/actor-routed-join-transport.d.ts +6 -2
  21. package/dist/runtime/actors/actor-routed-json-request.d.ts +2 -0
  22. package/dist/runtime/actors/index.d.ts +2 -1
  23. package/dist/runtime/actors/index.js +18 -6
  24. package/dist/runtime/actors/spot-actor-dispatch.js +2 -2
  25. package/dist/runtime/admission.d.ts +1 -1
  26. package/dist/runtime/admission.js +15 -5
  27. package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +6 -8
  28. package/dist/runtime/application-jobs/application-ingress-record-owner.js +39 -22
  29. package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +2 -1
  30. package/dist/runtime/application-jobs/application-job-queue-scope.js +37 -22
  31. package/dist/runtime/application-jobs/contracts.d.ts +10 -1
  32. package/dist/runtime/backend/contracts/index.d.ts +18 -9
  33. package/dist/runtime/backend/mesh-completion-table.d.ts +9 -1
  34. package/dist/runtime/backend/mesh-completion-table.js +149 -49
  35. package/dist/runtime/backend/mesh-dispatch-pump.d.ts +3 -0
  36. package/dist/runtime/backend/mesh-dispatch-pump.js +32 -11
  37. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -2
  38. package/dist/runtime/backend/node/node-backend-adapter-support.js +16 -9
  39. package/dist/runtime/backend/node/node-dealer-channel-client-transport.d.ts +1 -1
  40. package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +5 -2
  41. package/dist/runtime/backend/node/node-raw-binding-port.js +42 -22
  42. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +13 -5
  43. package/dist/runtime/backend/node/node-raw-mesh-backend.js +69 -51
  44. package/dist/runtime/backend/node/node-socket-backend-adapter.js +6 -6
  45. package/dist/runtime/backend/raw-binding-port.d.ts +2 -2
  46. package/dist/runtime/backend/runtime-message.d.ts +1 -0
  47. package/dist/runtime/backend/runtime-message.js +5 -0
  48. package/dist/runtime/backend/runtime-values.d.ts +1 -0
  49. package/dist/runtime/backend/runtime-values.js +2 -1
  50. package/dist/runtime/channels/channel-clients.d.ts +1 -0
  51. package/dist/runtime/channels/channel-clients.js +58 -21
  52. package/dist/runtime/channels/channel-dispatch-pipeline.d.ts +3 -2
  53. package/dist/runtime/channels/channel-dispatch-pipeline.js +20 -11
  54. package/dist/runtime/channels/channel-dispatch-services.d.ts +1 -0
  55. package/dist/runtime/channels/channel-dispatch-services.js +45 -0
  56. package/dist/runtime/channels/channel-dispatchers.d.ts +1 -1
  57. package/dist/runtime/channels/channel-dispatchers.js +27 -26
  58. package/dist/runtime/channels/channel-envelope-inspection.d.ts +1 -2
  59. package/dist/runtime/channels/channel-envelope-inspection.js +1 -16
  60. package/dist/runtime/channels/channel-envelope.d.ts +1 -0
  61. package/dist/runtime/channels/channel-envelope.js +83 -8
  62. package/dist/runtime/channels/channel-outbound-operations.d.ts +2 -2
  63. package/dist/runtime/channels/channel-outbound-operations.js +8 -8
  64. package/dist/runtime/channels/channel-runtime-manager.d.ts +6 -6
  65. package/dist/runtime/channels/channel-runtime-manager.js +12 -12
  66. package/dist/runtime/channels/channel-transports.d.ts +18 -15
  67. package/dist/runtime/channels/channel-transports.js +48 -31
  68. package/dist/runtime/channels/local-spot-route-transport.d.ts +1 -1
  69. package/dist/runtime/channels/local-spot-route-transport.js +2 -1
  70. package/dist/runtime/channels/router-socket-spot-route-transport.d.ts +2 -2
  71. package/dist/runtime/channels/router-socket-spot-route-transport.js +4 -4
  72. package/dist/runtime/channels/source-spot-router.d.ts +2 -2
  73. package/dist/runtime/channels/source-spot-router.js +4 -4
  74. package/dist/runtime/channels/spot-node-route-transport.d.ts +2 -2
  75. package/dist/runtime/channels/spot-node-route-transport.js +4 -4
  76. package/dist/runtime/channels/spot-route-bridge-transport.d.ts +2 -2
  77. package/dist/runtime/channels/spot-route-bridge-transport.js +4 -4
  78. package/dist/runtime/channels/spot-route-dispatch-strategy.d.ts +5 -4
  79. package/dist/runtime/channels/spot-route-dispatch-strategy.js +18 -18
  80. package/dist/runtime/execution/serial-execution-queue.d.ts +1 -0
  81. package/dist/runtime/execution/serial-execution-queue.js +1 -0
  82. package/dist/runtime/foundation/actor-join-recovery-codec.js +2 -2
  83. package/dist/runtime/foundation/durable-service-request.d.ts +14 -0
  84. package/dist/runtime/foundation/durable-service-request.js +98 -0
  85. package/dist/runtime/foundation/operation-identity.d.ts +1 -0
  86. package/dist/runtime/foundation/operation-identity.js +5 -1
  87. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +22 -5
  88. package/dist/runtime/foundation/raw-service-mesh-runtime.js +356 -232
  89. package/dist/runtime/foundation/service-relocation-object-owner.d.ts +1 -0
  90. package/dist/runtime/foundation/service-relocation-object-owner.js +1 -0
  91. package/dist/runtime/foundation/service-relocation-runtime.d.ts +2 -0
  92. package/dist/runtime/foundation/service-relocation-runtime.js +13 -1
  93. package/dist/runtime/foundation/service-runtime-contracts.d.ts +2 -0
  94. package/dist/runtime/foundation/service-stateful-runtime.d.ts +4 -4
  95. package/dist/runtime/foundation/service-stateful-runtime.js +44 -103
  96. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -5
  97. package/dist/runtime/foundation/service-topology-registry.d.ts +12 -2
  98. package/dist/runtime/foundation/service-topology-registry.js +61 -27
  99. package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +3 -3
  100. package/dist/runtime/foundation/service-wire-m6a-codec.js +36 -38
  101. package/dist/runtime/handlers/handler-instance-scope.d.ts +8 -0
  102. package/dist/runtime/handlers/handler-instance-scope.js +54 -11
  103. package/dist/runtime/host/actor-runtime-options-factory.js +2 -2
  104. package/dist/runtime/host/application-job-queue.d.ts +19 -7
  105. package/dist/runtime/host/application-job-queue.js +150 -42
  106. package/dist/runtime/host/entry-actor-runtime.d.ts +1 -0
  107. package/dist/runtime/host/entry-actor-runtime.js +3 -0
  108. package/dist/runtime/host/index.d.ts +7 -1
  109. package/dist/runtime/host/index.js +142 -118
  110. package/dist/runtime/host/remote-bound-session-relay.js +2 -18
  111. package/dist/runtime/host/route-mesh-runtime.js +1 -1
  112. package/dist/runtime/host/service-relocation-control.d.ts +1 -0
  113. package/dist/runtime/host/service-relocation-control.js +10 -4
  114. package/dist/runtime/host/service-relocation-host-runtime.d.ts +6 -8
  115. package/dist/runtime/host/service-relocation-host-runtime.js +213 -241
  116. package/dist/runtime/host/spot-address-transport.js +3 -2
  117. package/dist/runtime/host/spot-runtime-options-factory.js +1 -0
  118. package/dist/runtime/locations/actor-location-claims.d.ts +2 -2
  119. package/dist/runtime/locations/actor-location-claims.js +34 -19
  120. package/dist/runtime/locations/lifecycle.d.ts +1 -2
  121. package/dist/runtime/locations/lifecycle.js +2 -5
  122. package/dist/runtime/locations/runtime.d.ts +1 -1
  123. package/dist/runtime/locations/runtime.js +24 -23
  124. package/dist/runtime/messaging/framework-json-v1.d.ts +4 -6
  125. package/dist/runtime/messaging/framework-json-v1.js +38 -294
  126. package/dist/runtime/messaging/packet-name.d.ts +1 -1
  127. package/dist/runtime/messaging/packet-name.js +8 -24
  128. package/dist/runtime/messaging/request-result.d.ts +2 -0
  129. package/dist/runtime/messaging/request-result.js +54 -0
  130. package/dist/runtime/messaging/submission-result.d.ts +1 -0
  131. package/dist/runtime/messaging/submission-result.js +5 -2
  132. package/dist/runtime/protocol/service_wire_codec.generated.d.ts +15 -0
  133. package/dist/runtime/protocol/service_wire_codec.generated.js +144 -0
  134. package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +9 -0
  135. package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +14 -1
  136. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +7 -2
  137. package/dist/runtime/spots/formal-remote-actor-admission-registry.js +33 -19
  138. package/dist/runtime/spots/index.d.ts +3 -0
  139. package/dist/runtime/spots/index.js +9 -2
  140. package/dist/runtime/spots/spot-activation.d.ts +1 -0
  141. package/dist/runtime/spots/spot-activation.js +17 -8
  142. package/dist/runtime/spots/spot-entry-activation.js +6 -2
  143. package/dist/runtime/spots/spot-handler-registry.d.ts +3 -1
  144. package/dist/runtime/spots/spot-handler-registry.js +18 -8
  145. package/dist/runtime/spots/spot-node-autoconnect.js +4 -3
  146. package/dist/runtime/spots/spot-outbound.d.ts +3 -0
  147. package/dist/runtime/spots/spot-outbound.js +24 -15
  148. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.d.ts +1 -0
  149. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.js +1 -1
  150. package/dist/runtime/spots/spot-runtime-ports.d.ts +1 -0
  151. package/dist/runtime/spots/spot-subscription-dispatch.js +0 -3
  152. package/dist/runtime/spots/spot-timer.js +1 -0
  153. package/dist/runtime/streams/stream-session-runtime.d.ts +1 -1
  154. package/dist/runtime/streams/stream-session-runtime.js +13 -27
  155. package/package.json +2 -2
  156. package/src/contracts/Actors/ZLinkActorClient.ts +2 -1
  157. package/src/contracts/Channels/Calls.ts +2 -1
  158. package/src/contracts/Common/ZLinkMessageMetadata.ts +13 -8
  159. package/src/contracts/Handlers/Attributes.ts +14 -21
  160. package/src/contracts/Handlers/JsonContract.ts +8 -216
  161. package/src/contracts/Spots/Contracts.ts +2 -1
  162. package/src/nest-integration.ts +1 -0
  163. package/src/runtime/actors/actor-client.ts +40 -16
  164. package/src/runtime/actors/actor-handoff.ts +9 -4
  165. package/src/runtime/actors/actor-join-relocation.ts +1 -0
  166. package/src/runtime/actors/actor-local-native-join.ts +17 -26
  167. package/src/runtime/actors/actor-remote-wire.ts +17 -0
  168. package/src/runtime/actors/actor-routed-join-transport.ts +14 -4
  169. package/src/runtime/actors/actor-routed-json-request.ts +10 -2
  170. package/src/runtime/actors/index.ts +19 -5
  171. package/src/runtime/actors/spot-actor-dispatch.ts +4 -2
  172. package/src/runtime/admission.ts +18 -5
  173. package/src/runtime/application-jobs/application-ingress-record-owner.ts +41 -25
  174. package/src/runtime/application-jobs/application-job-queue-scope.ts +51 -22
  175. package/src/runtime/application-jobs/contracts.ts +14 -1
  176. package/src/runtime/backend/contracts/index.ts +53 -14
  177. package/src/runtime/backend/mesh-completion-table.ts +156 -57
  178. package/src/runtime/backend/mesh-dispatch-pump.ts +40 -10
  179. package/src/runtime/backend/node/node-backend-adapter-support.ts +17 -8
  180. package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +12 -2
  181. package/src/runtime/backend/node/node-raw-binding-port.ts +57 -30
  182. package/src/runtime/backend/node/node-raw-mesh-backend.ts +128 -65
  183. package/src/runtime/backend/node/node-socket-backend-adapter.ts +21 -6
  184. package/src/runtime/backend/raw-binding-port.ts +7 -2
  185. package/src/runtime/backend/runtime-message.ts +6 -0
  186. package/src/runtime/backend/runtime-values.ts +1 -0
  187. package/src/runtime/channels/channel-clients.ts +90 -41
  188. package/src/runtime/channels/channel-dispatch-pipeline.ts +69 -34
  189. package/src/runtime/channels/channel-dispatch-services.ts +69 -1
  190. package/src/runtime/channels/channel-dispatchers.ts +29 -27
  191. package/src/runtime/channels/channel-envelope-inspection.ts +2 -16
  192. package/src/runtime/channels/channel-envelope.ts +133 -8
  193. package/src/runtime/channels/channel-outbound-operations.ts +22 -8
  194. package/src/runtime/channels/channel-runtime-manager.ts +29 -12
  195. package/src/runtime/channels/channel-transports.ts +128 -60
  196. package/src/runtime/channels/local-spot-route-transport.ts +3 -1
  197. package/src/runtime/channels/router-socket-spot-route-transport.ts +13 -4
  198. package/src/runtime/channels/source-spot-router.ts +8 -4
  199. package/src/runtime/channels/spot-node-route-transport.ts +8 -4
  200. package/src/runtime/channels/spot-route-bridge-transport.ts +6 -4
  201. package/src/runtime/channels/spot-route-dispatch-strategy.ts +47 -18
  202. package/src/runtime/execution/serial-execution-queue.ts +2 -0
  203. package/src/runtime/foundation/actor-join-recovery-codec.ts +2 -2
  204. package/src/runtime/foundation/durable-service-request.ts +137 -0
  205. package/src/runtime/foundation/operation-identity.ts +5 -1
  206. package/src/runtime/foundation/raw-service-mesh-runtime.ts +484 -293
  207. package/src/runtime/foundation/service-relocation-object-owner.ts +2 -0
  208. package/src/runtime/foundation/service-relocation-runtime.ts +18 -1
  209. package/src/runtime/foundation/service-runtime-contracts.ts +7 -1
  210. package/src/runtime/foundation/service-stateful-runtime.ts +67 -125
  211. package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -5
  212. package/src/runtime/foundation/service-topology-registry.ts +91 -36
  213. package/src/runtime/foundation/service-wire-m6a-codec.ts +57 -38
  214. package/src/runtime/handlers/handler-instance-scope.ts +67 -16
  215. package/src/runtime/host/actor-runtime-options-factory.ts +13 -2
  216. package/src/runtime/host/application-job-queue.ts +175 -41
  217. package/src/runtime/host/entry-actor-runtime.ts +4 -0
  218. package/src/runtime/host/index.ts +201 -156
  219. package/src/runtime/host/remote-bound-session-relay.ts +2 -23
  220. package/src/runtime/host/route-mesh-runtime.ts +1 -1
  221. package/src/runtime/host/service-relocation-control.ts +11 -5
  222. package/src/runtime/host/service-relocation-host-runtime.ts +315 -291
  223. package/src/runtime/host/spot-address-transport.ts +4 -2
  224. package/src/runtime/host/spot-runtime-options-factory.ts +2 -0
  225. package/src/runtime/locations/actor-location-claims.ts +52 -30
  226. package/src/runtime/locations/lifecycle.ts +8 -26
  227. package/src/runtime/locations/runtime.ts +28 -24
  228. package/src/runtime/messaging/framework-json-v1.ts +5 -325
  229. package/src/runtime/messaging/packet-name.ts +5 -25
  230. package/src/runtime/messaging/request-result.ts +58 -0
  231. package/src/runtime/messaging/submission-result.ts +5 -1
  232. package/src/runtime/protocol/service_wire_codec.generated.ts +131 -1
  233. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +4 -4
  234. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +43 -23
  235. package/src/runtime/spots/index.ts +14 -2
  236. package/src/runtime/spots/spot-activation.ts +28 -10
  237. package/src/runtime/spots/spot-entry-activation.ts +9 -2
  238. package/src/runtime/spots/spot-handler-registry.ts +41 -8
  239. package/src/runtime/spots/spot-node-autoconnect.ts +6 -2
  240. package/src/runtime/spots/spot-outbound.ts +36 -16
  241. package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +2 -1
  242. package/src/runtime/spots/spot-runtime-ports.ts +1 -0
  243. package/src/runtime/spots/spot-subscription-dispatch.ts +0 -3
  244. package/src/runtime/spots/spot-timer.ts +5 -1
  245. package/src/runtime/streams/stream-session-runtime.ts +12 -29
@@ -6,6 +6,7 @@ const application_ingress_record_owner_1 = require("../application-jobs/applicat
6
6
  const submission_result_1 = require("../messaging/submission-result");
7
7
  const runtime_values_1 = require("../backend/runtime-values");
8
8
  const service_wire_codec_generated_1 = require("../protocol/service_wire_codec.generated");
9
+ const service_wire_binary_primitives_1 = require("./service-wire-binary-primitives");
9
10
  const operation_registry_1 = require("./operation-registry");
10
11
  const service_liveness_registry_1 = require("./service-liveness-registry");
11
12
  const service_mailbox_1 = require("./service-mailbox");
@@ -17,6 +18,7 @@ const nativeErrnoValues = node_os_1.constants.errno;
17
18
  // Framework error code 13 (RequestTargetNotFound) is encoded as 14 on a
18
19
  // RequestResult.NotFound reply. Boundary transport results keep failureCode 0.
19
20
  const RAW_MESH_RECEIVE_RECORD_BUDGET = 64;
21
+ const NODE_SEND_HEADER = (0, service_wire_m6a_codec_1.encodeNodeSendHeader)();
20
22
  const RAW_MESH_RECEIVE_BYTE_BUDGET = 4 * 1024 * 1024;
21
23
  exports.RAW_MESH_RECEIVE_TIME_BUDGET_MS = 2;
22
24
  const REQUEST_TARGET_NOT_FOUND_FAILURE_CODE = service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.requestTargetNotFound;
@@ -37,6 +39,7 @@ class RawServiceMeshRuntime {
37
39
  expectedPeers = new Map();
38
40
  endpointOnlyPeers = new Set();
39
41
  peerConnectionIntentRemoved = new Set();
42
+ peerRouteChanged = new Set();
40
43
  /**
41
44
  * The last observed Core selected route of each RID (Core ROUTER §10.1):
42
45
  * RID -> route generation. Only `observeSelectedRoutes` replaces it.
@@ -44,10 +47,12 @@ class RawServiceMeshRuntime {
44
47
  selectedRoutes = new Map();
45
48
  bindingPort;
46
49
  applicationJobQueue;
50
+ nodeMailboxOwner;
47
51
  peerAdmissionSealed;
48
52
  applicationJobStop = new AbortController();
49
53
  receiveAcquisition;
50
54
  receivePermit;
55
+ receiveRotation;
51
56
  controlSends = new Map();
52
57
  infrastructureFailure;
53
58
  onReceiveReady;
@@ -62,12 +67,14 @@ class RawServiceMeshRuntime {
62
67
  router;
63
68
  nextCorrelation = 1n;
64
69
  serviceIngress;
70
+ serviceIngressAccepts;
65
71
  closed = false;
66
72
  constructor(options) {
67
73
  this.onReceiveReady = options.onReceiveReady;
68
74
  this.infrastructureTaskRunner = options.infrastructureTaskRunner;
69
75
  this.operations = new operation_registry_1.OperationRegistry(undefined, options.onPendingOperationsChanged);
70
76
  this.descriptor = options.descriptor;
77
+ this.nodeMailboxOwner = `node:${options.descriptor.nodeRoutingId}`;
71
78
  this.topology = new service_topology_registry_1.ServiceTopologyRegistry(options.descriptor);
72
79
  this.mailbox = new service_mailbox_1.ServiceMailbox(options.onMailboxReady);
73
80
  this.liveness = new service_liveness_registry_1.ServiceLivenessRegistry(options.probeIntervalMs, options.peerTimeoutMs);
@@ -222,6 +229,30 @@ class RawServiceMeshRuntime {
222
229
  this.peerConnectionIntentRemoved.delete(listener);
223
230
  };
224
231
  }
232
+ observePeerRoute(nodeRoutingId) {
233
+ const selected = this.selectedRoutes.get(nodeRoutingId);
234
+ const admitted = this.topology.admittedPeer(nodeRoutingId)?.liveness?.connectionId;
235
+ return {
236
+ generation: selected ?? (typeof admitted === 'bigint' ? admitted : undefined),
237
+ ready: admitted !== undefined && (selected === undefined || admitted === selected)
238
+ };
239
+ }
240
+ get requestStopSignal() {
241
+ return this.applicationJobStop.signal;
242
+ }
243
+ observePeerRouteChanged(listener) {
244
+ this.peerRouteChanged.add(listener);
245
+ return () => {
246
+ this.peerRouteChanged.delete(listener);
247
+ };
248
+ }
249
+ hasSelectedPeerRoute(nodeRoutingId) {
250
+ return this.selectedRoutes.has(nodeRoutingId);
251
+ }
252
+ notifyPeerRouteChanged(nodeRoutingId) {
253
+ for (const listener of this.peerRouteChanged)
254
+ listener(nodeRoutingId);
255
+ }
225
256
  notifyPeerConnectionIntentRemoved(nodeRoutingId) {
226
257
  // Only the logical owner's removal publishes lifecycle termination.
227
258
  // A physical disconnect alone retains the connection expectation.
@@ -231,26 +262,31 @@ class RawServiceMeshRuntime {
231
262
  listener(nodeRoutingId);
232
263
  }
233
264
  announcePeer(nodeRoutingId) {
234
- const expected = this.expectedPeers.get(nodeRoutingId);
235
265
  const generation = this.selectedRoutes.get(nodeRoutingId);
236
- if (expected === undefined ||
237
- generation === undefined ||
238
- expected.helloSubmittedGeneration === generation ||
239
- this.peerAdmissionSealed?.() === true)
266
+ if (generation === undefined)
267
+ return Promise.resolve(false);
268
+ const current = () => {
269
+ const admission = this.topology.routeAdmission(nodeRoutingId);
270
+ return (this.selectedRoutes.get(nodeRoutingId) === generation &&
271
+ !(admission?.connectionId === routeConnectionId(generation) &&
272
+ admission.remoteAdmitReceived === true) &&
273
+ this.peerAdmissionSealed?.() !== true);
274
+ };
275
+ const key = controlSendKey(nodeRoutingId, service_wire_m6a_codec_1.M6aServiceWireCommand.hello);
276
+ if (!current()) {
277
+ this.controlSends.delete(key);
240
278
  return Promise.resolve(false);
241
- const current = () => this.expectedPeers.get(nodeRoutingId) === expected &&
242
- this.selectedRoutes.get(nodeRoutingId) === generation;
243
- return Promise.resolve(this.startControlSend(nodeRoutingId, service_wire_m6a_codec_1.M6aServiceWireCommand.hello, () => this.submitControl(nodeRoutingId, [
244
- (0, service_wire_m6a_codec_1.encodeRouteMeshAdmission)(service_wire_m6a_codec_1.M6aServiceWireCommand.hello, this.topology.localDescriptor())
245
- ], () => {
246
- if (current())
247
- expected.helloSubmittedGeneration = generation;
248
- }), current));
279
+ }
280
+ const existing = this.controlSends.get(key);
281
+ const hello = existing?.current?.() === true
282
+ ? existing.hello
283
+ : (0, service_wire_m6a_codec_1.encodeRouteMeshAdmission)(service_wire_m6a_codec_1.M6aServiceWireCommand.hello, this.topology.localDescriptor());
284
+ return Promise.resolve(this.startControlSend(nodeRoutingId, service_wire_m6a_codec_1.M6aServiceWireCommand.hello, () => this.submitControl(nodeRoutingId, [hello]), current, hello));
249
285
  }
250
286
  isPeerRouteReady(nodeRoutingId, lifecycleGeneration) {
251
287
  // Admission is bound to the observed selected route: the route observer
252
288
  // removes the peer when that route ends or is replaced.
253
- const peer = this.topology.peer(nodeRoutingId);
289
+ const peer = this.topology.admittedPeer(nodeRoutingId);
254
290
  return (peer !== undefined &&
255
291
  (lifecycleGeneration === undefined ||
256
292
  peer.descriptor.lifecycleGeneration === lifecycleGeneration));
@@ -287,22 +323,22 @@ class RawServiceMeshRuntime {
287
323
  // placement and channel selectors therefore need the new mutable fields
288
324
  // on that same connection before the next request is selected; waiting for
289
325
  // a reconnect would leave a race where a drained node is selected again.
326
+ await this.publishLocalDescriptor(this.topology.peers().map((peer) => peer.descriptor.nodeRoutingId));
327
+ }
328
+ async publishLocalDescriptor(targets) {
290
329
  const update = (0, service_wire_m6a_codec_1.encodeRouteMeshAdmission)(service_wire_m6a_codec_1.M6aServiceWireCommand.update, this.topology.localDescriptor());
291
- for (const peer of this.topology.peers()) {
292
- await this.send(peer.descriptor.nodeRoutingId, [update]);
330
+ for (const target of targets) {
331
+ await this.sendService(target, [update]);
293
332
  }
294
333
  }
295
334
  replaceDiscoveredNotRequired(descriptors) {
296
335
  this.topology.replaceDiscoveredNotRequired(descriptors);
297
336
  }
298
337
  isObjectClientNodeDirectTarget(nodeRoutingId) {
299
- const descriptor = nodeRoutingId === this.descriptor.nodeRoutingId
300
- ? this.topology.localDescriptor()
301
- : this.topology.knownDescriptor(nodeRoutingId);
302
- return descriptor?.objectRole === 'client';
338
+ return this.topology.isObjectClientNodeDirectTarget(nodeRoutingId);
303
339
  }
304
- async sendToNode(targetNodeRoutingId, payload) {
305
- return this.send(targetNodeRoutingId, [(0, service_wire_m6a_codec_1.encodeNodeSendHeader)(), this.applicationFrame(payload)]);
340
+ sendToNode(targetNodeRoutingId, payload) {
341
+ return this.send(targetNodeRoutingId, [NODE_SEND_HEADER, this.applicationFrame(payload)]);
306
342
  }
307
343
  async sendToChannel(channelName, payload) {
308
344
  const selection = this.selectChannelTarget(channelName);
@@ -317,17 +353,17 @@ class RawServiceMeshRuntime {
317
353
  ? runtime_values_1.SubmitResult.Ok
318
354
  : runtime_values_1.SubmitResult.NotConnected;
319
355
  }
320
- requestToNode(targetNodeRoutingId, payload, timeoutMs) {
321
- return this.requestToTarget(targetNodeRoutingId, payload, timeoutMs);
356
+ requestToNode(targetNodeRoutingId, payload, timeoutMs, onAdmitted) {
357
+ return this.requestToTarget(targetNodeRoutingId, payload, timeoutMs, undefined, onAdmitted);
322
358
  }
323
- requestToChannel(channelName, payload, timeoutMs) {
359
+ requestToChannel(channelName, payload, timeoutMs, onAdmitted) {
324
360
  const selection = this.selectChannelTarget(channelName);
325
361
  if (selection.kind !== 'selected') {
326
362
  const pending = this.operations.register(timeoutMs);
327
363
  this.operations.complete(pending.id, selection.requestResult);
328
364
  return pending;
329
365
  }
330
- return this.requestToTarget(selection.peer.descriptor.nodeRoutingId, payload, timeoutMs, channelName);
366
+ return this.requestToTarget(selection.peer.descriptor.nodeRoutingId, payload, timeoutMs, channelName, onAdmitted);
331
367
  }
332
368
  selectChannelTarget(channelName) {
333
369
  const selected = this.topology.selectChannel(channelName, (peer) => this.isPeerRouteReady(peer.descriptor.nodeRoutingId));
@@ -348,11 +384,12 @@ class RawServiceMeshRuntime {
348
384
  }
349
385
  };
350
386
  }
351
- setServiceIngress(handler) {
387
+ setServiceIngress(handler, accepts) {
352
388
  if (this.serviceIngress !== undefined && this.serviceIngress !== handler) {
353
389
  throw new Error('Raw service ingress is already registered.');
354
390
  }
355
391
  this.serviceIngress = handler;
392
+ this.serviceIngressAccepts = accepts;
356
393
  }
357
394
  async reserveLocalIngress(signal) {
358
395
  const waitSignal = signal === undefined
@@ -362,11 +399,17 @@ class RawServiceMeshRuntime {
362
399
  return application_ingress_record_owner_1.ApplicationIngressRecordOwner.create(this.applicationJobQueue, permit, { close() { } });
363
400
  }
364
401
  async sendService(targetNodeRoutingId, parts) {
365
- return this.send(targetNodeRoutingId, parts);
402
+ try {
403
+ await this.requireStarted().send(targetNodeRoutingId, parts);
404
+ return true;
405
+ }
406
+ catch (error) {
407
+ return this.sendFailure(error);
408
+ }
366
409
  }
367
- requestService(targetNodeRoutingId, parts, timeoutMs) {
368
- const admission = this.topology.peer(targetNodeRoutingId)?.liveness;
369
- return this.requireStarted().request(targetNodeRoutingId, parts, timeoutMs, admission?.recordReceived);
410
+ requestService(targetNodeRoutingId, parts, timeoutMs, onAdmitted) {
411
+ const admission = this.topology.admittedPeer(targetNodeRoutingId)?.liveness;
412
+ return this.requireStarted().request(targetNodeRoutingId, parts, timeoutMs, admission?.recordReceived, onAdmitted);
370
413
  }
371
414
  replyService(record, parts) {
372
415
  if (record.requestSequence === undefined) {
@@ -423,51 +466,59 @@ class RawServiceMeshRuntime {
423
466
  const observe = (_source, byteCount) => {
424
467
  bytes += byteCount;
425
468
  };
426
- while (receiveReady && messages < RAW_MESH_RECEIVE_RECORD_BUDGET && !this.closed) {
427
- const result = await this.receiveOne(performance.now(), observe);
428
- if (result === 'noData') {
429
- receiveReady = false;
430
- break;
469
+ const rotation = (this.receiveRotation ??= this.applicationJobQueue.createReceiveRotation(RAW_MESH_RECEIVE_RECORD_BUDGET, 'remote'));
470
+ if (receiveReady)
471
+ rotation.reserve();
472
+ try {
473
+ while (receiveReady && messages < RAW_MESH_RECEIVE_RECORD_BUDGET && !this.closed) {
474
+ const received = this.receiveOne(performance.now(), observe, rotation);
475
+ const result = received instanceof Promise ? await received : received;
476
+ if (result === 'noData') {
477
+ receiveReady = false;
478
+ break;
479
+ }
480
+ messages += 1;
481
+ // Core owns the per-peer fair-queue cursor. The Framework limit bounds
482
+ // the entire round, so changing peers does not renew its byte/count budget.
483
+ if (bytes >= RAW_MESH_RECEIVE_BYTE_BUDGET ||
484
+ performance.now() - startedAtMs >= exports.RAW_MESH_RECEIVE_TIME_BUDGET_MS)
485
+ break;
431
486
  }
432
- messages += 1;
433
- // Core owns the per-peer fair-queue cursor. The Framework limit bounds
434
- // the entire round, so changing peers does not renew its byte/count budget.
435
- if (bytes >= RAW_MESH_RECEIVE_BYTE_BUDGET ||
436
- performance.now() - startedAtMs >= exports.RAW_MESH_RECEIVE_TIME_BUDGET_MS)
437
- break;
487
+ }
488
+ finally {
489
+ rotation.releaseUnused();
438
490
  }
439
491
  if (!this.closed) {
440
- for (const nodeRoutingId of this.expectedPeers.keys()) {
441
- if (this.topology.peer(nodeRoutingId) === undefined)
442
- await this.announcePeer(nodeRoutingId);
443
- }
444
492
  this.tickLiveness();
445
493
  }
446
494
  return receiveReady && messages > 0;
447
495
  }
448
- async receiveOne(nowMs, observe) {
496
+ receiveOne(nowMs, observe, rotation) {
449
497
  const router = this.requireStarted();
450
498
  this.observeInfrastructureFailure();
451
- if (this.receivePermit === undefined && this.receiveAcquisition === undefined) {
499
+ let permit = this.receivePermit;
500
+ this.receivePermit = undefined;
501
+ permit ??= rotation?.take();
502
+ if (permit === undefined && this.receiveAcquisition === undefined) {
452
503
  let resolveAcquisition;
453
504
  let rejectAcquisition;
454
- const acquisition = new Promise((resolve, reject) => {
455
- resolveAcquisition = resolve;
456
- rejectAcquisition = reject;
457
- });
458
- const permit = this.applicationJobQueue.acquireOrResume((permit) => {
505
+ permit = this.applicationJobQueue.acquireOrResume((permit) => {
459
506
  this.receiveAcquisition = undefined;
460
- if (this.closed)
507
+ if (this.closed) {
461
508
  permit.releaseAfterInternalProcessing();
509
+ permit.releaseStorage?.();
510
+ }
462
511
  else {
463
512
  this.receivePermit = permit;
464
513
  this.onReceiveReady?.();
465
514
  }
466
- resolveAcquisition(permit);
467
- }, rejectAcquisition, this.applicationJobStop.signal, 'remote');
468
- if (permit !== undefined)
469
- this.receivePermit = permit;
470
- else {
515
+ resolveAcquisition?.(permit);
516
+ }, (error) => rejectAcquisition?.(error), this.applicationJobStop.signal, 'remote');
517
+ if (permit === undefined) {
518
+ const acquisition = new Promise((resolve, reject) => {
519
+ resolveAcquisition = resolve;
520
+ rejectAcquisition = reject;
521
+ });
471
522
  this.receiveAcquisition = acquisition;
472
523
  acquisition.catch((error) => {
473
524
  this.receiveAcquisition = undefined;
@@ -476,12 +527,11 @@ class RawServiceMeshRuntime {
476
527
  });
477
528
  }
478
529
  }
479
- const permit = this.receivePermit;
480
- this.receivePermit = undefined;
481
530
  if (permit === undefined)
482
531
  return 'noData';
483
532
  if (this.closed) {
484
533
  permit.releaseAfterInternalProcessing();
534
+ permit.releaseStorage?.();
485
535
  return 'noData';
486
536
  }
487
537
  let received;
@@ -490,23 +540,44 @@ class RawServiceMeshRuntime {
490
540
  }
491
541
  catch (error) {
492
542
  permit.releaseAfterInternalProcessing();
543
+ permit.releaseStorage?.();
493
544
  throw error;
494
545
  }
495
546
  if (received === undefined) {
496
547
  permit.releaseAfterInternalProcessing();
548
+ permit.releaseStorage?.();
497
549
  return 'noData';
498
550
  }
499
551
  const applicationJobOwner = application_ingress_record_owner_1.ApplicationIngressRecordOwner.create(this.applicationJobQueue, permit, received);
552
+ let result;
500
553
  try {
501
- const result = await this.processReceived(received, nowMs, applicationJobOwner);
502
- if (result === 'protocolError') {
554
+ result = this.processReceived(received, nowMs, applicationJobOwner);
555
+ }
556
+ catch (error) {
557
+ applicationJobOwner.close();
558
+ throw error;
559
+ }
560
+ return result instanceof Promise
561
+ ? result.then((value) => this.finishReceive(received, applicationJobOwner, observe, value), (error) => {
562
+ applicationJobOwner.close();
563
+ throw error;
564
+ })
565
+ : this.finishReceive(received, applicationJobOwner, observe, result);
566
+ }
567
+ finishReceive(received, owner, observe, result) {
568
+ try {
569
+ if (result === 'protocolError')
503
570
  this.reportProtocolError(received);
571
+ if (observe !== undefined) {
572
+ let bytes = 0;
573
+ for (const part of received.parts)
574
+ bytes += part.byteLength;
575
+ observe(received.sourceRid, bytes);
504
576
  }
505
- observe?.(received.sourceRid, received.parts.reduce((sum, part) => sum + part.byteLength, 0));
506
577
  return result;
507
578
  }
508
579
  finally {
509
- applicationJobOwner.close();
580
+ owner.close();
510
581
  }
511
582
  }
512
583
  reportProtocolError(received) {
@@ -545,61 +616,19 @@ class RawServiceMeshRuntime {
545
616
  throw error;
546
617
  }
547
618
  }
548
- async processReceived(received, nowMs, applicationJobOwner) {
549
- if (received.parts.length === 0 ||
550
- (received.parts.length === 1 && received.parts[0].byteLength === 0)) {
551
- if (this.peerAdmissionSealed?.() === true)
552
- return 'dropped';
553
- return (await this.sendControl(received.sourceRid, [
554
- (0, service_wire_m6a_codec_1.encodeRouteMeshAdmission)(service_wire_m6a_codec_1.M6aServiceWireCommand.hello, this.topology.localDescriptor())
555
- ]))
556
- ? 'infrastructure'
557
- : 'dropped';
558
- }
619
+ processReceived(received, nowMs, applicationJobOwner) {
620
+ const peer = this.topology.admittedPeer(received.sourceRid);
621
+ const currentSource = peer?.liveness !== undefined && peer.liveness.connectionId === received.routeGeneration;
622
+ if (received.parts.length === 0)
623
+ return currentSource ? 'protocolError' : 'dropped';
559
624
  try {
560
625
  const header = (0, service_wire_m6a_codec_1.decodeHeader)(received.parts[0]);
561
626
  if (header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.hello && this.peerAdmissionSealed?.() === true) {
562
627
  return 'dropped';
563
628
  }
564
629
  if (header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.hello ||
565
- header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.admit ||
566
- header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.update) {
567
- if (received.parts.length !== 1)
568
- return 'protocolError';
569
- const descriptor = (0, service_wire_m6a_codec_1.decodeRouteMeshAdmission)(received.parts[0], header.command, received.sourceRid);
570
- const expected = this.expectedPeers.get(received.sourceRid);
571
- if (expected !== undefined &&
572
- (expected.meshName !== descriptor.meshName ||
573
- expected.nodeRoutingId !== descriptor.nodeRoutingId)) {
574
- await this.sendControl(received.sourceRid, [
575
- (0, service_wire_m6a_codec_1.encodeReject)((0, service_wire_codec_generated_1.enumWireRejectReason)('identityMismatch'))
576
- ]);
577
- return 'infrastructure';
578
- }
579
- const result = this.admitPeer(descriptor, received.routeGeneration, nowMs, expected);
580
- if (result !== 'admitted' && result !== 'notRequired') {
581
- const detail = result === 'invalidDescriptor'
582
- ? describeInvalidAdmissionDescriptor(descriptor, expected, this.topology.peer(descriptor.nodeRoutingId)?.descriptor)
583
- : undefined;
584
- console.warn(`RouteMesh admission rejected peer '${received.sourceRid}' as ${result}` +
585
- (detail === undefined ? '.' : ` (${detail}).`));
586
- await this.sendControl(received.sourceRid, [(0, service_wire_m6a_codec_1.encodeReject)(admissionReason(result))]);
587
- return 'infrastructure';
588
- }
589
- if (result === 'admitted')
590
- this.selectBilateralConnection(descriptor);
591
- if (header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.hello) {
592
- // Hello supplies only the sender's descriptor. Even NotRequired
593
- // peers must receive ours before the intent owner closes transport;
594
- // submitting a response does not guarantee delivery before close.
595
- await this.sendControl(received.sourceRid, [
596
- (0, service_wire_m6a_codec_1.encodeRouteMeshAdmission)(service_wire_m6a_codec_1.M6aServiceWireCommand.admit, this.topology.localDescriptor())
597
- ]);
598
- }
599
- else if (result === 'notRequired') {
600
- this.retireNotRequiredExpectedPeer(received.sourceRid, descriptor.advertisedEndpoint);
601
- }
602
- return 'infrastructure';
630
+ header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.admit) {
631
+ return this.processAdmission(received, header, nowMs).catch((error) => this.receiveProtocolFailure(error));
603
632
  }
604
633
  if (header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.reject) {
605
634
  if (received.parts.length !== 1)
@@ -607,47 +636,122 @@ class RawServiceMeshRuntime {
607
636
  (0, service_wire_m6a_codec_1.decodeReject)(received.parts[0]);
608
637
  return 'infrastructure';
609
638
  }
610
- const peer = this.topology.peer(received.sourceRid);
611
- if (peer === undefined)
612
- return 'protocolError';
613
- if (peer.liveness !== undefined && peer.liveness.connectionId === received.routeGeneration)
614
- this.liveness.recordReceived(peer.liveness, nowMs);
639
+ if (!currentSource)
640
+ return 'dropped';
641
+ this.liveness.recordReceived(peer.liveness, nowMs);
642
+ if (header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.update) {
643
+ return this.processAdmission(received, header, nowMs).catch((error) => this.receiveProtocolFailure(error));
644
+ }
615
645
  if (header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.livenessProbe ||
616
646
  header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.livenessAck) {
617
- if (received.parts.length !== 1)
618
- return 'protocolError';
619
- const record = livenessCodec.decodeLivenessRecord(received.parts[0]);
620
- if (record.command === service_wire_m6a_codec_1.M6aServiceWireCommand.livenessProbe) {
621
- const ack = this.liveness.acknowledgeProbe(received.sourceRid, received.routeGeneration, record.probeId);
622
- if (ack === undefined)
623
- return 'protocolError';
624
- const sent = await this.sendControl(received.sourceRid, [
625
- livenessCodec.encodeLivenessRecord({
626
- command: service_wire_m6a_codec_1.M6aServiceWireCommand.livenessAck,
627
- probeId: record.probeId
628
- })
629
- ]);
630
- if (!sent)
631
- return 'dropped';
632
- }
633
- else {
634
- this.liveness.acknowledge(received.sourceRid, received.routeGeneration, record.probeId, nowMs);
635
- }
636
- return 'infrastructure';
647
+ return this.processLiveness(received, nowMs).catch((error) => this.receiveProtocolFailure(error));
637
648
  }
638
- const stateful = await this.serviceIngress?.({
639
- command: header.command,
640
- flags: header.flags,
641
- sourceRoutingId: received.sourceRid,
642
- sourceNodeGeneration: peer.descriptor.lifecycleGeneration,
643
- sourceRoute: received.sourceRoute,
644
- ...(received.reply === undefined ? {} : { reply: received.reply }),
645
- ...(received.requestSeq === undefined ? {} : { requestSequence: received.requestSeq }),
646
- parts: received.parts,
647
- applicationJobOwner
648
- });
649
+ const stateful = this.serviceIngress !== undefined && this.serviceIngressAccepts?.(header.command) !== false
650
+ ? this.serviceIngress({
651
+ command: header.command,
652
+ flags: header.flags,
653
+ sourceRoutingId: received.sourceRid,
654
+ sourceNodeGeneration: peer.descriptor.lifecycleGeneration,
655
+ sourceRoute: received.sourceRoute,
656
+ ...(received.reply === undefined ? {} : { reply: received.reply }),
657
+ ...(received.requestSeq === undefined
658
+ ? {}
659
+ : { requestSequence: received.requestSeq }),
660
+ parts: received.parts,
661
+ applicationJobOwner
662
+ })
663
+ : undefined;
664
+ if (stateful instanceof Promise)
665
+ return stateful.then((result) => result ?? this.processApplicationReceived(received, header, applicationJobOwner), (error) => this.receiveProtocolFailure(error));
649
666
  if (stateful !== undefined)
650
667
  return stateful;
668
+ return this.processApplicationReceived(received, header, applicationJobOwner);
669
+ }
670
+ catch (error) {
671
+ if (!currentSource && error instanceof service_wire_m6a_codec_1.ServiceWireProtocolError)
672
+ return 'dropped';
673
+ return this.receiveProtocolFailure(error);
674
+ }
675
+ }
676
+ receiveProtocolFailure(error) {
677
+ if (error instanceof service_wire_m6a_codec_1.ServiceWireProtocolError)
678
+ return 'protocolError';
679
+ throw error;
680
+ }
681
+ async processAdmission(received, header, nowMs) {
682
+ if (received.parts.length !== 1)
683
+ return 'protocolError';
684
+ const descriptor = (0, service_wire_m6a_codec_1.decodeRouteMeshAdmission)(received.parts[0], header.command, received.sourceRid);
685
+ const expected = this.expectedPeers.get(received.sourceRid);
686
+ if (expected !== undefined &&
687
+ (expected.meshName !== descriptor.meshName ||
688
+ expected.nodeRoutingId !== descriptor.nodeRoutingId)) {
689
+ await this.sendControl(received.sourceRid, [
690
+ (0, service_wire_m6a_codec_1.encodeReject)((0, service_wire_codec_generated_1.enumWireRejectReason)('identityMismatch'))
691
+ ]);
692
+ return 'infrastructure';
693
+ }
694
+ const helloKey = controlSendKey(received.sourceRid, service_wire_m6a_codec_1.M6aServiceWireCommand.hello);
695
+ const exchange = this.controlSends.get(helloKey);
696
+ const result = this.admitPeer(descriptor, received.routeGeneration, nowMs, expected, header.command, received.parts[0].subarray(service_wire_binary_primitives_1.SERVICE_WIRE_PREFIX_SIZE));
697
+ if (result !== 'admitted' && result !== 'notRequired') {
698
+ const detail = result === 'invalidDescriptor'
699
+ ? describeInvalidAdmissionDescriptor(descriptor, expected, this.topology.peer(descriptor.nodeRoutingId)?.descriptor)
700
+ : undefined;
701
+ console.warn(`RouteMesh admission rejected peer '${received.sourceRid}' as ${result}` +
702
+ (detail === undefined ? '.' : ` (${detail}).`));
703
+ await this.sendControl(received.sourceRid, [(0, service_wire_m6a_codec_1.encodeReject)(admissionReason(result))]);
704
+ return 'infrastructure';
705
+ }
706
+ if (result === 'admitted')
707
+ this.selectBilateralConnection(descriptor);
708
+ if (header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.hello) {
709
+ // Hello supplies only the sender's descriptor. Even NotRequired
710
+ // peers must receive ours before the intent owner closes transport;
711
+ // submitting a response does not guarantee delivery before close.
712
+ const local = exchange?.current?.() === true && exchange.hello !== undefined
713
+ ? (0, service_wire_m6a_codec_1.decodeRouteMeshAdmission)(exchange.hello, service_wire_m6a_codec_1.M6aServiceWireCommand.hello, this.descriptor.nodeRoutingId)
714
+ : this.topology.localDescriptor();
715
+ await this.sendControl(received.sourceRid, [
716
+ (0, service_wire_m6a_codec_1.encodeRouteMeshAdmission)(service_wire_m6a_codec_1.M6aServiceWireCommand.admit, local)
717
+ ]);
718
+ }
719
+ else if (header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.admit) {
720
+ this.controlSends.delete(helloKey);
721
+ if (result === 'notRequired') {
722
+ this.retireNotRequiredExpectedPeer(received.sourceRid, descriptor.advertisedEndpoint);
723
+ }
724
+ else if (exchange?.hello !== undefined &&
725
+ (0, service_wire_m6a_codec_1.decodeRouteMeshAdmission)(exchange.hello, service_wire_m6a_codec_1.M6aServiceWireCommand.hello, this.descriptor.nodeRoutingId).descriptorRevision < this.descriptor.descriptorRevision) {
726
+ await this.publishLocalDescriptor([received.sourceRid]);
727
+ }
728
+ }
729
+ return 'infrastructure';
730
+ }
731
+ async processLiveness(received, nowMs) {
732
+ if (received.parts.length !== 1)
733
+ return 'protocolError';
734
+ const record = livenessCodec.decodeLivenessRecord(received.parts[0]);
735
+ if (record.command === service_wire_m6a_codec_1.M6aServiceWireCommand.livenessProbe) {
736
+ const ack = this.liveness.acknowledgeProbe(received.sourceRid, received.routeGeneration, record.probeId);
737
+ if (ack === undefined)
738
+ return 'protocolError';
739
+ const sent = await this.sendControl(received.sourceRid, [
740
+ livenessCodec.encodeLivenessRecord({
741
+ command: service_wire_m6a_codec_1.M6aServiceWireCommand.livenessAck,
742
+ probeId: record.probeId
743
+ })
744
+ ]);
745
+ if (!sent)
746
+ return 'dropped';
747
+ }
748
+ else {
749
+ this.liveness.acknowledge(received.sourceRid, received.routeGeneration, record.probeId, nowMs);
750
+ }
751
+ return 'infrastructure';
752
+ }
753
+ processApplicationReceived(received, header, applicationJobOwner) {
754
+ try {
651
755
  if (header.flags === 0 &&
652
756
  // A bare infrastructure control is normally exactly one frame, but a
653
757
  // relocation Prepare now rides its bound-session journal as
@@ -663,7 +767,7 @@ class RawServiceMeshRuntime {
663
767
  received.parts.length >= 1 &&
664
768
  isBareInfrastructureControl(header.command)) {
665
769
  const accepted = this.mailbox.tryEnqueue({
666
- owner: `node:${this.descriptor.nodeRoutingId}`,
770
+ owner: this.nodeMailboxOwner,
667
771
  domain: 'infrastructure',
668
772
  parts: received.parts,
669
773
  sourceRoutingId: received.sourceRid,
@@ -686,51 +790,54 @@ class RawServiceMeshRuntime {
686
790
  let owner;
687
791
  let correlation;
688
792
  if (header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.nodeSend) {
689
- owner = `node:${this.descriptor.nodeRoutingId}`;
793
+ owner = this.nodeMailboxOwner;
690
794
  }
691
795
  else if (header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.nodeRequest) {
692
- owner = `node:${this.descriptor.nodeRoutingId}`;
693
- correlation = (0, service_wire_m6a_codec_1.decodeNodeRequestHeader)(received.parts[0]);
796
+ owner = this.nodeMailboxOwner;
797
+ correlation = (0, service_wire_m6a_codec_1.decodeNodeRequestHeader)(received.parts[0], header);
694
798
  }
695
799
  else if (header.command === service_wire_m6a_codec_1.M6aServiceWireCommand.channelSend) {
696
- owner = `channel:${(0, service_wire_m6a_codec_1.decodeChannelSendHeader)(received.parts[0])}`;
800
+ owner = `channel:${(0, service_wire_m6a_codec_1.decodeChannelSendHeader)(received.parts[0], header)}`;
697
801
  }
698
802
  else {
699
- const channel = (0, service_wire_m6a_codec_1.decodeChannelRequestHeader)(received.parts[0]);
803
+ const channel = (0, service_wire_m6a_codec_1.decodeChannelRequestHeader)(received.parts[0], header);
700
804
  owner = `channel:${channel.channelName}`;
701
805
  correlation = channel.correlation;
702
806
  }
703
- const applicationJob = await applicationJobOwner.acquire('application');
704
- const accepted = this.mailbox.tryEnqueue({
705
- owner,
706
- domain: 'application',
707
- parts: received.parts,
807
+ const job = applicationJobOwner.acquire('application');
808
+ return job instanceof Promise
809
+ ? job.then((permit) => this.enqueueOrdinaryRecord(received, owner, correlation, permit))
810
+ : this.enqueueOrdinaryRecord(received, owner, correlation, job);
811
+ }
812
+ catch (error) {
813
+ return this.receiveProtocolFailure(error);
814
+ }
815
+ }
816
+ enqueueOrdinaryRecord(received, owner, correlation, applicationJob) {
817
+ const accepted = this.mailbox.tryEnqueue({
818
+ owner,
819
+ domain: 'application',
820
+ parts: received.parts,
821
+ sourceRoutingId: received.sourceRid,
822
+ sourceRoute: received.sourceRoute,
823
+ ...(received.reply === undefined ? {} : { reply: received.reply }),
824
+ requestSequence: received.requestSeq,
825
+ ...(correlation === undefined ? {} : { correlation }),
826
+ applicationJob
827
+ });
828
+ if (accepted)
829
+ return 'application';
830
+ applicationJob.close();
831
+ if (correlation !== undefined && received.requestSeq !== undefined) {
832
+ this.replyService({
708
833
  sourceRoutingId: received.sourceRid,
709
834
  sourceRoute: received.sourceRoute,
710
- ...(received.reply === undefined ? {} : { reply: received.reply }),
711
835
  requestSequence: received.requestSeq,
712
- ...(correlation === undefined ? {} : { correlation }),
713
- applicationJob
714
- });
715
- if (accepted)
716
- return 'application';
717
- applicationJob.close();
718
- if (correlation !== undefined && received.requestSeq !== undefined) {
719
- this.replyService({
720
- sourceRoutingId: received.sourceRid,
721
- sourceRoute: received.sourceRoute,
722
- requestSequence: received.requestSeq,
723
- ...(received.reply === undefined ? {} : { reply: received.reply })
724
- }, [(0, service_wire_m6a_codec_1.encodeReplyHeader)(correlation, runtime_values_1.RequestResult.NotConnected, 0)]);
725
- return 'infrastructure';
726
- }
727
- return 'dropped';
728
- }
729
- catch (error) {
730
- if (error instanceof service_wire_m6a_codec_1.ServiceWireProtocolError)
731
- return 'protocolError';
732
- throw error;
836
+ ...(received.reply === undefined ? {} : { reply: received.reply })
837
+ }, [(0, service_wire_m6a_codec_1.encodeReplyHeader)(correlation, runtime_values_1.RequestResult.NotConnected, 0)]);
838
+ return 'infrastructure';
733
839
  }
840
+ return 'dropped';
734
841
  }
735
842
  expireOperations(nowMs, turnDeadlineMs) {
736
843
  return this.operations.expire(nowMs, turnDeadlineMs);
@@ -741,6 +848,8 @@ class RawServiceMeshRuntime {
741
848
  tickLiveness(nowMs = performance.now()) {
742
849
  const result = this.liveness.tick(nowMs);
743
850
  this.requireStarted();
851
+ for (const nodeRoutingId of this.selectedRoutes.keys())
852
+ void this.announcePeer(nodeRoutingId);
744
853
  for (const probe of result.probes) {
745
854
  const parts = [
746
855
  livenessCodec.encodeLivenessRecord({
@@ -777,10 +886,8 @@ class RawServiceMeshRuntime {
777
886
  for (const [nodeRoutingId, generation] of this.selectedRoutes) {
778
887
  if (observed.get(nodeRoutingId) === generation)
779
888
  continue;
889
+ this.controlSends.delete(controlSendKey(nodeRoutingId, service_wire_m6a_codec_1.M6aServiceWireCommand.hello));
780
890
  changes++;
781
- const expected = this.expectedPeers.get(nodeRoutingId);
782
- if (expected !== undefined)
783
- delete expected.helloSubmittedGeneration;
784
891
  const peer = this.topology.peer(nodeRoutingId);
785
892
  if (peer?.liveness?.connectionId === generation) {
786
893
  this.removePeer(peer);
@@ -795,6 +902,8 @@ class RawServiceMeshRuntime {
795
902
  selected.push(nodeRoutingId);
796
903
  }
797
904
  this.selectedRoutes = observed;
905
+ for (const nodeRoutingId of selected)
906
+ this.notifyPeerRouteChanged(nodeRoutingId);
798
907
  // A new selected route admits only through a new handshake.
799
908
  for (const nodeRoutingId of selected)
800
909
  await this.announcePeer(nodeRoutingId);
@@ -806,9 +915,11 @@ class RawServiceMeshRuntime {
806
915
  this.closed = true;
807
916
  this.applicationJobStop.abort(new Error('Raw service runtime application job admission stopped.'));
808
917
  this.receivePermit?.releaseAfterInternalProcessing();
918
+ this.receivePermit?.releaseStorage?.();
809
919
  this.receivePermit = undefined;
810
920
  for (const send of this.controlSends.values())
811
921
  delete send.next;
922
+ this.controlSends.clear();
812
923
  this.mailbox.close();
813
924
  this.operations.close('Raw service runtime closed.');
814
925
  this.serviceIngress = undefined;
@@ -822,7 +933,7 @@ class RawServiceMeshRuntime {
822
933
  this.router = undefined;
823
934
  this.host = undefined;
824
935
  }
825
- requestToTarget(targetNodeRoutingId, payload, timeoutMs, channelName) {
936
+ requestToTarget(targetNodeRoutingId, payload, timeoutMs, channelName, onAdmitted) {
826
937
  const correlation = this.nextCorrelation++;
827
938
  const header = channelName === undefined
828
939
  ? (0, service_wire_m6a_codec_1.encodeNodeRequestHeader)(correlation)
@@ -888,6 +999,9 @@ class RawServiceMeshRuntime {
888
999
  failureCode: 0
889
1000
  });
890
1001
  }
1002
+ else {
1003
+ onAdmitted?.();
1004
+ }
891
1005
  }
892
1006
  finally {
893
1007
  applicationJobOwner.close();
@@ -895,7 +1009,7 @@ class RawServiceMeshRuntime {
895
1009
  })().catch((error) => this.operations.fail(pending.id, error));
896
1010
  return pending;
897
1011
  }
898
- const selectedPeer = this.topology.peer(selectedTargetNodeRoutingId);
1012
+ const selectedPeer = this.topology.admittedPeer(selectedTargetNodeRoutingId);
899
1013
  if (selectedPeer === undefined) {
900
1014
  // Logical target selection is the requester's decision: an RID that is
901
1015
  // not an admitted peer is not a target. Whether an admitted target's
@@ -909,7 +1023,7 @@ class RawServiceMeshRuntime {
909
1023
  const parts = [header, encodedPayload];
910
1024
  let request;
911
1025
  try {
912
- request = this.requireStarted().request(selectedTargetNodeRoutingId, parts, timeoutMs, selectedPeer.liveness?.recordReceived);
1026
+ request = this.requireStarted().request(selectedTargetNodeRoutingId, parts, timeoutMs, selectedPeer.liveness?.recordReceived, onAdmitted);
913
1027
  }
914
1028
  catch (error) {
915
1029
  this.operations.fail(pending.id, error);
@@ -958,24 +1072,22 @@ class RawServiceMeshRuntime {
958
1072
  applicationPayload(payload) {
959
1073
  return Buffer.isBuffer(payload) ? (0, service_wire_m6a_codec_1.decodeApplicationPayloadView)(payload) : payload;
960
1074
  }
961
- admitPeer(descriptor, connectionId, nowMs, expected) {
1075
+ admitPeer(descriptor, connectionId, nowMs, expected, command = service_wire_m6a_codec_1.M6aServiceWireCommand.hello, descriptorBytes) {
962
1076
  const previous = this.topology.peer(descriptor.nodeRoutingId);
963
- if (previous !== undefined &&
964
- previous.descriptor.lifecycleGeneration === descriptor.lifecycleGeneration &&
965
- (descriptor.descriptorRevision < previous.descriptor.descriptorRevision ||
966
- (descriptor.descriptorRevision === previous.descriptor.descriptorRevision &&
967
- !(0, service_topology_registry_1.sameServiceNodeDescriptor)(previous.descriptor, descriptor)))) {
968
- throw new service_wire_m6a_codec_1.ServiceWireProtocolError(`RouteMesh peer '${descriptor.nodeRoutingId}' descriptor revision is stale or conflicting.`);
969
- }
970
- const topologyConnectionId = typeof connectionId === 'bigint' ? `route:${connectionId}` : connectionId;
1077
+ const topologyConnectionId = typeof connectionId === 'bigint' ? routeConnectionId(connectionId) : connectionId;
971
1078
  const result = this.topology.admit(descriptor, topologyConnectionId, expected, () => {
972
1079
  const admission = this.liveness.admit(descriptor.nodeRoutingId, connectionId, nowMs);
973
1080
  this.liveness.recordReceived(admission, nowMs);
974
1081
  return admission;
975
- });
1082
+ }, command, descriptorBytes);
1083
+ if (result === 'staleDescriptor') {
1084
+ throw new service_wire_m6a_codec_1.ServiceWireProtocolError(`RouteMesh peer '${descriptor.nodeRoutingId}' descriptor revision is stale or conflicting.`);
1085
+ }
976
1086
  if (result === 'notRequired' && previous !== undefined) {
977
1087
  this.liveness.disconnect(descriptor.nodeRoutingId, previous.liveness?.connectionId ?? previous.connectionId);
978
1088
  }
1089
+ if (result === 'admitted')
1090
+ this.notifyPeerRouteChanged(descriptor.nodeRoutingId);
979
1091
  return result;
980
1092
  }
981
1093
  retireNotRequiredExpectedPeer(nodeRoutingId, advertisedEndpoint) {
@@ -1020,7 +1132,7 @@ class RawServiceMeshRuntime {
1020
1132
  async sendControl(targetNodeRoutingId, parts) {
1021
1133
  return this.startControlSend(targetNodeRoutingId, (0, service_wire_m6a_codec_1.decodeHeader)(parts[0]).command, () => this.submitControl(targetNodeRoutingId, parts));
1022
1134
  }
1023
- submitControl(target, parts, accepted) {
1135
+ submitControl(target, parts) {
1024
1136
  let submission;
1025
1137
  try {
1026
1138
  submission = this.requireStarted().submitSend(target, parts);
@@ -1028,19 +1140,15 @@ class RawServiceMeshRuntime {
1028
1140
  catch (error) {
1029
1141
  return this.sendFailure(error);
1030
1142
  }
1031
- if (submission.result === runtime_values_1.SubmitResult.Ok) {
1032
- accepted?.();
1143
+ if (submission.result === runtime_values_1.SubmitResult.Ok)
1033
1144
  return true;
1034
- }
1035
- return submission.admitted.then(() => {
1036
- accepted?.();
1037
- return true;
1038
- }, (error) => this.sendFailure(error));
1145
+ return submission.admitted.then(() => true, (error) => this.sendFailure(error));
1039
1146
  }
1040
- startControlSend(target, command, attempt, current) {
1041
- const key = JSON.stringify([target, command]);
1147
+ startControlSend(target, command, attempt, current, hello) {
1148
+ const key = controlSendKey(target, command);
1042
1149
  const existing = this.controlSends.get(key);
1043
1150
  if (existing !== undefined &&
1151
+ existing.task !== undefined &&
1044
1152
  (command !== service_wire_m6a_codec_1.M6aServiceWireCommand.hello || existing.current?.() === true)) {
1045
1153
  if (command !== service_wire_m6a_codec_1.M6aServiceWireCommand.hello) {
1046
1154
  existing.next = attempt;
@@ -1049,31 +1157,39 @@ class RawServiceMeshRuntime {
1049
1157
  return true;
1050
1158
  }
1051
1159
  const first = attempt();
1052
- if (typeof first === 'boolean')
1160
+ if (typeof first === 'boolean') {
1161
+ if (hello !== undefined)
1162
+ this.controlSends.set(key, { current, hello });
1053
1163
  return first;
1164
+ }
1054
1165
  const owner = this;
1055
- const state = {
1056
- current,
1057
- task: first.then(function drain(accepted) {
1058
- if (!owner.closed && state.next !== undefined) {
1059
- const next = state.next;
1060
- delete state.next;
1061
- return Promise.resolve(next()).then(drain);
1062
- }
1063
- if (owner.controlSends.get(key) === state)
1064
- owner.controlSends.delete(key);
1065
- return accepted;
1066
- })
1166
+ const state = { current, hello };
1167
+ const completeSend = () => {
1168
+ if (owner.controlSends.get(key) !== state)
1169
+ return;
1170
+ if (hello !== undefined && current?.() === true)
1171
+ delete state.task;
1172
+ else
1173
+ owner.controlSends.delete(key);
1067
1174
  };
1175
+ const task = first.then(function drain(accepted) {
1176
+ if (!owner.closed && state.next !== undefined) {
1177
+ const next = state.next;
1178
+ delete state.next;
1179
+ return Promise.resolve(next()).then(drain);
1180
+ }
1181
+ completeSend();
1182
+ return accepted;
1183
+ });
1184
+ state.task = task;
1068
1185
  this.controlSends.set(key, state);
1069
- state.task.then(() => { }, (error) => {
1070
- if (this.controlSends.get(key) === state)
1071
- this.controlSends.delete(key);
1186
+ task.then(() => { }, (error) => {
1187
+ completeSend();
1072
1188
  if (this.infrastructureTaskRunner === undefined)
1073
1189
  this.reportInfrastructureFailure(error);
1074
1190
  });
1075
1191
  this.infrastructureTaskRunner?.runDetached('RouteMesh control SEND', async () => {
1076
- await state.task;
1192
+ await task;
1077
1193
  });
1078
1194
  return true;
1079
1195
  }
@@ -1092,13 +1208,15 @@ class RawServiceMeshRuntime {
1092
1208
  throw failure.error;
1093
1209
  }
1094
1210
  send(targetNodeRoutingId, parts) {
1095
- return this.requireStarted()
1096
- .send(targetNodeRoutingId, parts)
1097
- .then(() => {
1098
- return true;
1099
- }, (error) => {
1211
+ try {
1212
+ const submission = this.requireStarted().submitSend(targetNodeRoutingId, parts);
1213
+ if (submission.result === runtime_values_1.SubmitResult.Ok)
1214
+ return true;
1215
+ return submission.admitted.then(() => true, (error) => this.sendFailure(error));
1216
+ }
1217
+ catch (error) {
1100
1218
  return this.sendFailure(error);
1101
- });
1219
+ }
1102
1220
  }
1103
1221
  sendFailure(error) {
1104
1222
  if ((0, runtime_values_1.isZLinkBackendResultError)(error) && error.operation === 'submit') {
@@ -1110,6 +1228,12 @@ class RawServiceMeshRuntime {
1110
1228
  }
1111
1229
  }
1112
1230
  exports.RawServiceMeshRuntime = RawServiceMeshRuntime;
1231
+ function controlSendKey(target, command) {
1232
+ return JSON.stringify([target, command]);
1233
+ }
1234
+ function routeConnectionId(generation) {
1235
+ return `route:${generation}`;
1236
+ }
1113
1237
  /**
1114
1238
  * Relocation and bound-Session controls are service-wire records themselves,
1115
1239
  * not application NodeSend(16) envelopes. Keep this list closed: a bare