@zlink-systems/framework 0.29.1 → 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 (322) 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/Configuration/Builders.d.ts +1 -2
  7. package/dist/contracts/Configuration/Configs.d.ts +0 -5
  8. package/dist/contracts/Configuration/RegistrationBuilders.js +6 -4
  9. package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -6
  10. package/dist/contracts/Configuration/RegistrationValidators.js +4 -4
  11. package/dist/contracts/Configuration/index.d.ts +1 -1
  12. package/dist/contracts/Handlers/Attributes.d.ts +5 -13
  13. package/dist/contracts/Handlers/Attributes.js +7 -11
  14. package/dist/contracts/Handlers/JsonContract.d.ts +2 -46
  15. package/dist/contracts/Handlers/JsonContract.js +4 -153
  16. package/dist/contracts/Spots/Contracts.d.ts +2 -1
  17. package/dist/internal.d.ts +1 -0
  18. package/dist/internal.js +3 -1
  19. package/dist/nest-integration.d.ts +1 -1
  20. package/dist/nest-integration.js +2 -1
  21. package/dist/runtime/actors/actor-client.d.ts +1 -1
  22. package/dist/runtime/actors/actor-client.js +14 -12
  23. package/dist/runtime/actors/actor-handoff.d.ts +3 -2
  24. package/dist/runtime/actors/actor-handoff.js +15 -7
  25. package/dist/runtime/actors/actor-join-relocation.d.ts +1 -0
  26. package/dist/runtime/actors/actor-local-native-join.js +9 -12
  27. package/dist/runtime/actors/actor-remote-wire.js +15 -0
  28. package/dist/runtime/actors/actor-routed-join-transport.d.ts +6 -2
  29. package/dist/runtime/actors/actor-routed-json-request.d.ts +2 -0
  30. package/dist/runtime/actors/index.d.ts +2 -1
  31. package/dist/runtime/actors/index.js +18 -6
  32. package/dist/runtime/actors/spot-actor-dispatch.js +2 -2
  33. package/dist/runtime/admission.d.ts +1 -1
  34. package/dist/runtime/admission.js +15 -5
  35. package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +9 -9
  36. package/dist/runtime/application-jobs/application-ingress-record-owner.js +45 -23
  37. package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +5 -2
  38. package/dist/runtime/application-jobs/application-job-queue-scope.js +41 -21
  39. package/dist/runtime/application-jobs/contracts.d.ts +15 -2
  40. package/dist/runtime/backend/contracts/index.d.ts +27 -27
  41. package/dist/runtime/backend/mesh-completion-table.d.ts +15 -5
  42. package/dist/runtime/backend/mesh-completion-table.js +160 -50
  43. package/dist/runtime/backend/mesh-dispatch-pump.d.ts +3 -0
  44. package/dist/runtime/backend/mesh-dispatch-pump.js +53 -36
  45. package/dist/runtime/backend/node/node-backend-adapter-factory.js +6 -1
  46. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +3 -3
  47. package/dist/runtime/backend/node/node-backend-adapter-support.js +33 -31
  48. package/dist/runtime/backend/node/node-dealer-channel-client-transport.d.ts +1 -1
  49. package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +5 -2
  50. package/dist/runtime/backend/node/node-mesh-backend-adapter.d.ts +1 -9
  51. package/dist/runtime/backend/node/node-mesh-backend-adapter.js +1 -1
  52. package/dist/runtime/backend/node/node-raw-binding-port.js +50 -25
  53. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +22 -16
  54. package/dist/runtime/backend/node/node-raw-mesh-backend.js +133 -149
  55. package/dist/runtime/backend/node/node-socket-backend-adapter.js +15 -10
  56. package/dist/runtime/backend/raw-binding-port.d.ts +10 -3
  57. package/dist/runtime/backend/runtime-message.d.ts +1 -0
  58. package/dist/runtime/backend/runtime-message.js +5 -0
  59. package/dist/runtime/backend/runtime-values.d.ts +1 -0
  60. package/dist/runtime/backend/runtime-values.js +2 -1
  61. package/dist/runtime/channels/channel-clients.d.ts +1 -0
  62. package/dist/runtime/channels/channel-clients.js +58 -21
  63. package/dist/runtime/channels/channel-dispatch-pipeline.d.ts +3 -2
  64. package/dist/runtime/channels/channel-dispatch-pipeline.js +20 -11
  65. package/dist/runtime/channels/channel-dispatch-services.d.ts +1 -0
  66. package/dist/runtime/channels/channel-dispatch-services.js +45 -0
  67. package/dist/runtime/channels/channel-dispatchers.d.ts +1 -1
  68. package/dist/runtime/channels/channel-dispatchers.js +27 -26
  69. package/dist/runtime/channels/channel-envelope-inspection.d.ts +1 -2
  70. package/dist/runtime/channels/channel-envelope-inspection.js +1 -16
  71. package/dist/runtime/channels/channel-envelope.d.ts +1 -0
  72. package/dist/runtime/channels/channel-envelope.js +83 -8
  73. package/dist/runtime/channels/channel-outbound-operations.d.ts +2 -2
  74. package/dist/runtime/channels/channel-outbound-operations.js +8 -8
  75. package/dist/runtime/channels/channel-receive-loops.js +3 -3
  76. package/dist/runtime/channels/channel-runtime-lifecycle.js +2 -1
  77. package/dist/runtime/channels/channel-runtime-manager.d.ts +6 -6
  78. package/dist/runtime/channels/channel-runtime-manager.js +12 -12
  79. package/dist/runtime/channels/channel-socket-registry.d.ts +10 -3
  80. package/dist/runtime/channels/channel-socket-registry.js +59 -82
  81. package/dist/runtime/channels/channel-transports.d.ts +18 -15
  82. package/dist/runtime/channels/channel-transports.js +48 -31
  83. package/dist/runtime/channels/local-spot-route-transport.d.ts +1 -1
  84. package/dist/runtime/channels/local-spot-route-transport.js +2 -1
  85. package/dist/runtime/channels/router-socket-spot-route-transport.d.ts +2 -2
  86. package/dist/runtime/channels/router-socket-spot-route-transport.js +4 -4
  87. package/dist/runtime/channels/source-spot-router.d.ts +2 -2
  88. package/dist/runtime/channels/source-spot-router.js +4 -4
  89. package/dist/runtime/channels/spot-node-route-transport.d.ts +2 -2
  90. package/dist/runtime/channels/spot-node-route-transport.js +4 -4
  91. package/dist/runtime/channels/spot-route-bridge-transport.d.ts +2 -2
  92. package/dist/runtime/channels/spot-route-bridge-transport.js +4 -4
  93. package/dist/runtime/channels/spot-route-dispatch-strategy.d.ts +5 -4
  94. package/dist/runtime/channels/spot-route-dispatch-strategy.js +18 -18
  95. package/dist/runtime/diagnostics/message-flow.d.ts +1 -0
  96. package/dist/runtime/diagnostics/message-flow.js +3 -3
  97. package/dist/runtime/execution/serial-execution-queue.d.ts +1 -0
  98. package/dist/runtime/execution/serial-execution-queue.js +1 -0
  99. package/dist/runtime/foundation/actor-join-recovery-codec.js +2 -2
  100. package/dist/runtime/foundation/durable-service-request.d.ts +14 -0
  101. package/dist/runtime/foundation/durable-service-request.js +98 -0
  102. package/dist/runtime/foundation/operation-identity.d.ts +1 -0
  103. package/dist/runtime/foundation/operation-identity.js +5 -1
  104. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +37 -6
  105. package/dist/runtime/foundation/raw-service-mesh-runtime.js +473 -220
  106. package/dist/runtime/foundation/service-liveness-registry.d.ts +20 -8
  107. package/dist/runtime/foundation/service-liveness-registry.js +57 -44
  108. package/dist/runtime/foundation/service-mailbox.d.ts +3 -0
  109. package/dist/runtime/foundation/service-mailbox.js +2 -1
  110. package/dist/runtime/foundation/service-relocation-object-owner.d.ts +1 -0
  111. package/dist/runtime/foundation/service-relocation-object-owner.js +1 -0
  112. package/dist/runtime/foundation/service-relocation-runtime.d.ts +2 -0
  113. package/dist/runtime/foundation/service-relocation-runtime.js +13 -1
  114. package/dist/runtime/foundation/service-runtime-contracts.d.ts +2 -0
  115. package/dist/runtime/foundation/service-stateful-runtime.d.ts +10 -9
  116. package/dist/runtime/foundation/service-stateful-runtime.js +78 -126
  117. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +5 -7
  118. package/dist/runtime/foundation/service-stateful-wire-codec.js +43 -61
  119. package/dist/runtime/foundation/service-topology-registry.d.ts +16 -4
  120. package/dist/runtime/foundation/service-topology-registry.js +69 -30
  121. package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +3 -3
  122. package/dist/runtime/foundation/service-wire-m6a-codec.js +36 -38
  123. package/dist/runtime/handlers/handler-instance-scope.d.ts +8 -0
  124. package/dist/runtime/handlers/handler-instance-scope.js +54 -11
  125. package/dist/runtime/host/actor-packet-relay.js +7 -7
  126. package/dist/runtime/host/actor-runtime-options-factory.js +2 -2
  127. package/dist/runtime/host/application-job-queue.d.ts +28 -9
  128. package/dist/runtime/host/application-job-queue.js +214 -50
  129. package/dist/runtime/host/entry-actor-runtime.d.ts +1 -0
  130. package/dist/runtime/host/entry-actor-runtime.js +4 -1
  131. package/dist/runtime/host/index.d.ts +7 -1
  132. package/dist/runtime/host/index.js +154 -126
  133. package/dist/runtime/host/instance-activation-authority.d.ts +2 -1
  134. package/dist/runtime/host/instance-activation-authority.js +89 -25
  135. package/dist/runtime/host/remote-bound-session-relay.js +2 -18
  136. package/dist/runtime/host/route-mesh-runtime.js +1 -1
  137. package/dist/runtime/host/service-relocation-control.d.ts +1 -0
  138. package/dist/runtime/host/service-relocation-control.js +10 -4
  139. package/dist/runtime/host/service-relocation-host-runtime.d.ts +6 -8
  140. package/dist/runtime/host/service-relocation-host-runtime.js +215 -243
  141. package/dist/runtime/host/spot-address-transport.d.ts +1 -0
  142. package/dist/runtime/host/spot-address-transport.js +28 -14
  143. package/dist/runtime/host/spot-runtime-options-factory.js +3 -2
  144. package/dist/runtime/host/user-spot-creation-coordinator.d.ts +1 -0
  145. package/dist/runtime/host/user-spot-creation-coordinator.js +1 -0
  146. package/dist/runtime/locations/actor-location-claims.d.ts +2 -2
  147. package/dist/runtime/locations/actor-location-claims.js +34 -19
  148. package/dist/runtime/locations/domain-store-contract.d.ts +2 -2
  149. package/dist/runtime/locations/in-memory-authority-store.d.ts +4 -2
  150. package/dist/runtime/locations/in-memory-authority-store.js +80 -8
  151. package/dist/runtime/locations/in-memory-location-store.d.ts +2 -2
  152. package/dist/runtime/locations/in-memory-location-store.js +19 -3
  153. package/dist/runtime/locations/internal-location-contracts.d.ts +3 -0
  154. package/dist/runtime/locations/lifecycle.d.ts +1 -2
  155. package/dist/runtime/locations/lifecycle.js +2 -5
  156. package/dist/runtime/locations/location-store-repository.d.ts +3 -2
  157. package/dist/runtime/locations/location-store-repository.js +165 -22
  158. package/dist/runtime/locations/resolvers.d.ts +2 -1
  159. package/dist/runtime/locations/resolvers.js +10 -1
  160. package/dist/runtime/locations/runtime.d.ts +1 -1
  161. package/dist/runtime/locations/runtime.js +24 -23
  162. package/dist/runtime/messaging/framework-json-v1.d.ts +4 -6
  163. package/dist/runtime/messaging/framework-json-v1.js +38 -294
  164. package/dist/runtime/messaging/packet-name.d.ts +1 -1
  165. package/dist/runtime/messaging/packet-name.js +8 -24
  166. package/dist/runtime/messaging/request-result.d.ts +2 -0
  167. package/dist/runtime/messaging/request-result.js +54 -0
  168. package/dist/runtime/messaging/submission-result.d.ts +2 -1
  169. package/dist/runtime/messaging/submission-result.js +7 -4
  170. package/dist/runtime/protocol/service_wire_codec.generated.d.ts +15 -0
  171. package/dist/runtime/protocol/service_wire_codec.generated.js +144 -0
  172. package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +9 -0
  173. package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +14 -1
  174. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +9 -2
  175. package/dist/runtime/spots/formal-remote-actor-admission-registry.js +33 -19
  176. package/dist/runtime/spots/index.d.ts +7 -3
  177. package/dist/runtime/spots/index.js +23 -16
  178. package/dist/runtime/spots/spot-activation.d.ts +3 -1
  179. package/dist/runtime/spots/spot-activation.js +21 -12
  180. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +2 -1
  181. package/dist/runtime/spots/spot-actor-admission-coordinator.js +11 -10
  182. package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +4 -1
  183. package/dist/runtime/spots/spot-actor-packet-dispatch.js +7 -5
  184. package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
  185. package/dist/runtime/spots/spot-actor-packet-relay-dispatch.js +1 -0
  186. package/dist/runtime/spots/spot-entry-activation.js +9 -3
  187. package/dist/runtime/spots/spot-handler-registry.d.ts +3 -1
  188. package/dist/runtime/spots/spot-handler-registry.js +18 -8
  189. package/dist/runtime/spots/spot-node-autoconnect.js +4 -3
  190. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +2 -1
  191. package/dist/runtime/spots/spot-node-runtime-manager.js +4 -11
  192. package/dist/runtime/spots/spot-outbound.d.ts +3 -0
  193. package/dist/runtime/spots/spot-outbound.js +24 -15
  194. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.d.ts +1 -0
  195. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.js +1 -1
  196. package/dist/runtime/spots/spot-routing-internal.d.ts +6 -0
  197. package/dist/runtime/spots/spot-runtime-ports.d.ts +3 -1
  198. package/dist/runtime/spots/spot-subscription-dispatch.js +0 -3
  199. package/dist/runtime/spots/spot-timer.js +1 -0
  200. package/dist/runtime/streams/stream-session-runtime.d.ts +1 -1
  201. package/dist/runtime/streams/stream-session-runtime.js +14 -28
  202. package/package.json +3 -3
  203. package/src/contracts/Actors/ZLinkActorClient.ts +2 -1
  204. package/src/contracts/Channels/Calls.ts +2 -1
  205. package/src/contracts/Common/ZLinkMessageMetadata.ts +13 -8
  206. package/src/contracts/Configuration/Builders.ts +1 -2
  207. package/src/contracts/Configuration/Configs.ts +0 -6
  208. package/src/contracts/Configuration/RegistrationBuilders.ts +8 -12
  209. package/src/contracts/Configuration/RegistrationTypes.ts +1 -6
  210. package/src/contracts/Configuration/RegistrationValidators.ts +9 -10
  211. package/src/contracts/Configuration/index.ts +1 -6
  212. package/src/contracts/Handlers/Attributes.ts +14 -21
  213. package/src/contracts/Handlers/JsonContract.ts +8 -216
  214. package/src/contracts/Spots/Contracts.ts +2 -1
  215. package/src/internal.ts +1 -0
  216. package/src/nest-integration.ts +1 -0
  217. package/src/runtime/actors/actor-client.ts +40 -16
  218. package/src/runtime/actors/actor-handoff.ts +22 -8
  219. package/src/runtime/actors/actor-join-relocation.ts +1 -0
  220. package/src/runtime/actors/actor-local-native-join.ts +17 -26
  221. package/src/runtime/actors/actor-remote-wire.ts +17 -0
  222. package/src/runtime/actors/actor-routed-join-transport.ts +14 -4
  223. package/src/runtime/actors/actor-routed-json-request.ts +10 -2
  224. package/src/runtime/actors/index.ts +19 -5
  225. package/src/runtime/actors/spot-actor-dispatch.ts +4 -2
  226. package/src/runtime/admission.ts +18 -5
  227. package/src/runtime/application-jobs/application-ingress-record-owner.ts +53 -27
  228. package/src/runtime/application-jobs/application-job-queue-scope.ts +56 -22
  229. package/src/runtime/application-jobs/contracts.ts +28 -2
  230. package/src/runtime/backend/contracts/index.ts +63 -32
  231. package/src/runtime/backend/mesh-completion-table.ts +175 -59
  232. package/src/runtime/backend/mesh-dispatch-pump.ts +59 -32
  233. package/src/runtime/backend/node/node-backend-adapter-factory.ts +12 -5
  234. package/src/runtime/backend/node/node-backend-adapter-support.ts +42 -31
  235. package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +12 -2
  236. package/src/runtime/backend/node/node-mesh-backend-adapter.ts +3 -10
  237. package/src/runtime/backend/node/node-raw-binding-port.ts +73 -37
  238. package/src/runtime/backend/node/node-raw-mesh-backend.ts +209 -176
  239. package/src/runtime/backend/node/node-socket-backend-adapter.ts +32 -13
  240. package/src/runtime/backend/raw-binding-port.ts +18 -3
  241. package/src/runtime/backend/runtime-message.ts +6 -0
  242. package/src/runtime/backend/runtime-values.ts +1 -0
  243. package/src/runtime/channels/channel-clients.ts +90 -41
  244. package/src/runtime/channels/channel-dispatch-pipeline.ts +69 -34
  245. package/src/runtime/channels/channel-dispatch-services.ts +69 -1
  246. package/src/runtime/channels/channel-dispatchers.ts +29 -27
  247. package/src/runtime/channels/channel-envelope-inspection.ts +2 -16
  248. package/src/runtime/channels/channel-envelope.ts +133 -8
  249. package/src/runtime/channels/channel-outbound-operations.ts +22 -8
  250. package/src/runtime/channels/channel-receive-loops.ts +3 -3
  251. package/src/runtime/channels/channel-runtime-lifecycle.ts +7 -1
  252. package/src/runtime/channels/channel-runtime-manager.ts +29 -12
  253. package/src/runtime/channels/channel-socket-registry.ts +84 -130
  254. package/src/runtime/channels/channel-transports.ts +128 -60
  255. package/src/runtime/channels/local-spot-route-transport.ts +3 -1
  256. package/src/runtime/channels/router-socket-spot-route-transport.ts +13 -4
  257. package/src/runtime/channels/source-spot-router.ts +8 -4
  258. package/src/runtime/channels/spot-node-route-transport.ts +8 -4
  259. package/src/runtime/channels/spot-route-bridge-transport.ts +6 -4
  260. package/src/runtime/channels/spot-route-dispatch-strategy.ts +47 -18
  261. package/src/runtime/diagnostics/message-flow.ts +1 -1
  262. package/src/runtime/execution/serial-execution-queue.ts +2 -0
  263. package/src/runtime/foundation/actor-join-recovery-codec.ts +2 -2
  264. package/src/runtime/foundation/durable-service-request.ts +137 -0
  265. package/src/runtime/foundation/operation-identity.ts +5 -1
  266. package/src/runtime/foundation/raw-service-mesh-runtime.ts +642 -253
  267. package/src/runtime/foundation/service-liveness-registry.ts +68 -59
  268. package/src/runtime/foundation/service-mailbox.ts +7 -1
  269. package/src/runtime/foundation/service-relocation-object-owner.ts +2 -0
  270. package/src/runtime/foundation/service-relocation-runtime.ts +18 -1
  271. package/src/runtime/foundation/service-runtime-contracts.ts +7 -1
  272. package/src/runtime/foundation/service-stateful-runtime.ts +128 -160
  273. package/src/runtime/foundation/service-stateful-wire-codec.ts +55 -84
  274. package/src/runtime/foundation/service-topology-registry.ts +103 -42
  275. package/src/runtime/foundation/service-wire-m6a-codec.ts +57 -38
  276. package/src/runtime/handlers/handler-instance-scope.ts +67 -16
  277. package/src/runtime/host/actor-packet-relay.ts +7 -0
  278. package/src/runtime/host/actor-runtime-options-factory.ts +14 -2
  279. package/src/runtime/host/application-job-queue.ts +249 -48
  280. package/src/runtime/host/entry-actor-runtime.ts +5 -0
  281. package/src/runtime/host/index.ts +224 -160
  282. package/src/runtime/host/instance-activation-authority.ts +132 -27
  283. package/src/runtime/host/remote-bound-session-relay.ts +2 -23
  284. package/src/runtime/host/route-mesh-runtime.ts +1 -1
  285. package/src/runtime/host/service-relocation-control.ts +11 -5
  286. package/src/runtime/host/service-relocation-host-runtime.ts +317 -291
  287. package/src/runtime/host/spot-address-transport.ts +49 -24
  288. package/src/runtime/host/spot-runtime-options-factory.ts +4 -0
  289. package/src/runtime/host/user-spot-creation-coordinator.ts +8 -0
  290. package/src/runtime/locations/actor-location-claims.ts +52 -30
  291. package/src/runtime/locations/domain-store-contract.ts +6 -2
  292. package/src/runtime/locations/in-memory-authority-store.ts +113 -9
  293. package/src/runtime/locations/in-memory-location-store.ts +25 -3
  294. package/src/runtime/locations/internal-location-contracts.ts +4 -0
  295. package/src/runtime/locations/lifecycle.ts +8 -26
  296. package/src/runtime/locations/location-store-repository.ts +220 -15
  297. package/src/runtime/locations/resolvers.ts +25 -2
  298. package/src/runtime/locations/runtime.ts +28 -24
  299. package/src/runtime/messaging/framework-json-v1.ts +5 -325
  300. package/src/runtime/messaging/packet-name.ts +5 -25
  301. package/src/runtime/messaging/request-result.ts +58 -0
  302. package/src/runtime/messaging/submission-result.ts +7 -3
  303. package/src/runtime/protocol/service_wire_codec.generated.ts +131 -1
  304. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +4 -4
  305. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +45 -23
  306. package/src/runtime/spots/index.ts +30 -2
  307. package/src/runtime/spots/spot-activation.ts +33 -10
  308. package/src/runtime/spots/spot-actor-admission-coordinator.ts +13 -0
  309. package/src/runtime/spots/spot-actor-packet-dispatch.ts +10 -0
  310. package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
  311. package/src/runtime/spots/spot-actor-packet-relay-dispatch.ts +1 -0
  312. package/src/runtime/spots/spot-entry-activation.ts +12 -2
  313. package/src/runtime/spots/spot-handler-registry.ts +41 -8
  314. package/src/runtime/spots/spot-node-autoconnect.ts +6 -2
  315. package/src/runtime/spots/spot-node-runtime-manager.ts +7 -12
  316. package/src/runtime/spots/spot-outbound.ts +36 -16
  317. package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +2 -1
  318. package/src/runtime/spots/spot-routing-internal.ts +14 -0
  319. package/src/runtime/spots/spot-runtime-ports.ts +3 -0
  320. package/src/runtime/spots/spot-subscription-dispatch.ts +0 -3
  321. package/src/runtime/spots/spot-timer.ts +5 -1
  322. package/src/runtime/streams/stream-session-runtime.ts +13 -30
@@ -2,6 +2,7 @@ import { constants as osConstants } from 'node:os';
2
2
  import { ApplicationIngressRecordOwner } from '../application-jobs/application-ingress-record-owner';
3
3
  import type {
4
4
  ApplicationJobPermitPort,
5
+ ApplicationJobReceiveRotationPort,
5
6
  ApplicationJobQueuePort
6
7
  } from '../application-jobs/contracts';
7
8
  import type {
@@ -13,11 +14,11 @@ import type {
13
14
  import { submitToRequestResult } from '../messaging/submission-result';
14
15
  import { RequestResult, SubmitResult, isZLinkBackendResultError } from '../backend/runtime-values';
15
16
  import { enumWireRejectReason } from '../protocol/service_wire_codec.generated';
17
+ import { SERVICE_WIRE_PREFIX_SIZE } from './service-wire-binary-primitives';
16
18
  import { OperationRegistry, type PendingOperation } from './operation-registry';
17
19
  import { ServiceLivenessRegistry, type ServiceLivenessTick } from './service-liveness-registry';
18
20
  import { ServiceMailbox, type ServiceMailboxRecord } from './service-mailbox';
19
21
  import {
20
- sameServiceNodeDescriptor,
21
22
  ServiceTopologyRegistry,
22
23
  validateDescriptor,
23
24
  type AdmittedServicePeer,
@@ -84,6 +85,7 @@ type RawServiceChannelTargetSelection =
84
85
  type ServiceApplicationPayloadInput = ServiceApplicationPayload | Buffer;
85
86
 
86
87
  export interface RawServiceIngressRecord {
88
+ readonly onAdmitted?: () => void;
87
89
  readonly command: number;
88
90
  readonly flags: number;
89
91
  readonly sourceRoutingId: string;
@@ -100,6 +102,8 @@ export type RawServiceIngressHandler = (
100
102
  ) => RawServicePumpResult | undefined | Promise<RawServicePumpResult | undefined>;
101
103
 
102
104
  export interface RawServiceMeshRuntimeOptions {
105
+ readonly onReceiveReady?: () => void;
106
+ readonly infrastructureTaskRunner?: import('../spots/spot-actor-join-dispatch').ZLinkDetachedTaskRunner;
103
107
  readonly onPendingOperationsChanged?: () => void;
104
108
  readonly descriptor: ServiceNodeDescriptor;
105
109
  readonly resolveAdvertisedEndpoint?: (boundEndpoint: string) => string;
@@ -127,6 +131,7 @@ export interface RawServiceMeshRuntimeOptions {
127
131
  // Framework error code 13 (RequestTargetNotFound) is encoded as 14 on a
128
132
  // RequestResult.NotFound reply. Boundary transport results keep failureCode 0.
129
133
  const RAW_MESH_RECEIVE_RECORD_BUDGET = 64;
134
+ const NODE_SEND_HEADER = encodeNodeSendHeader();
130
135
  const RAW_MESH_RECEIVE_BYTE_BUDGET = 4 * 1024 * 1024;
131
136
  export const RAW_MESH_RECEIVE_TIME_BUDGET_MS = 2;
132
137
 
@@ -138,6 +143,14 @@ const livenessCodec = createServiceWireCodec({
138
143
  commands: M6aServiceWireCommand
139
144
  });
140
145
 
146
+ interface ControlSendState {
147
+ task?: Promise<boolean>;
148
+ next?: () => boolean | Promise<boolean>;
149
+ current?: () => boolean;
150
+ // Admission owns the first HELLO until ADMIT or route termination.
151
+ hello?: Uint8Array;
152
+ }
153
+
141
154
  /**
142
155
  * RouteMesh M6A runtime built only on the public raw binding package.
143
156
  * It owns protocol, admission, mailbox, request and liveness state.
@@ -156,11 +169,11 @@ export class RawServiceMeshRuntime {
156
169
  readonly endpoint?: string;
157
170
  readonly securityIdentity?: string;
158
171
  readonly lifecycleGeneration?: bigint;
159
- helloSubmittedGeneration?: bigint;
160
172
  }
161
173
  >();
162
174
  private readonly endpointOnlyPeers = new Set<string>();
163
175
  private readonly peerConnectionIntentRemoved = new Set<(nodeRoutingId: string) => void>();
176
+ private readonly peerRouteChanged = new Set<(nodeRoutingId: string) => void>();
164
177
  /**
165
178
  * The last observed Core selected route of each RID (Core ROUTER §10.1):
166
179
  * RID -> route generation. Only `observeSelectedRoutes` replaces it.
@@ -168,8 +181,16 @@ export class RawServiceMeshRuntime {
168
181
  private selectedRoutes = new Map<string, bigint>();
169
182
  private readonly bindingPort: ZLinkRawBindingPort;
170
183
  private readonly applicationJobQueue: ApplicationJobQueuePort;
184
+ private readonly nodeMailboxOwner: string;
171
185
  private readonly peerAdmissionSealed?: () => boolean;
172
186
  private readonly applicationJobStop = new AbortController();
187
+ private receiveAcquisition?: Promise<ApplicationJobPermitPort>;
188
+ private receivePermit?: ApplicationJobPermitPort;
189
+ private receiveRotation?: ApplicationJobReceiveRotationPort;
190
+ private readonly controlSends = new Map<string, ControlSendState>();
191
+ private infrastructureFailure?: { readonly error: unknown };
192
+ private readonly onReceiveReady?: () => void;
193
+ private readonly infrastructureTaskRunner?: RawServiceMeshRuntimeOptions['infrastructureTaskRunner'];
173
194
  private readonly onPeerNotRequired?: RawServiceMeshRuntimeOptions['onPeerNotRequired'];
174
195
  private readonly onPeerDisconnected?: RawServiceMeshRuntimeOptions['onPeerDisconnected'];
175
196
  private readonly onProtocolError?: RawServiceMeshRuntimeOptions['onProtocolError'];
@@ -180,11 +201,15 @@ export class RawServiceMeshRuntime {
180
201
  private router?: ZLinkRawRouterPort;
181
202
  private nextCorrelation = 1n;
182
203
  private serviceIngress?: RawServiceIngressHandler;
204
+ private serviceIngressAccepts?: (command: number) => boolean;
183
205
  private closed = false;
184
206
 
185
207
  constructor(options: RawServiceMeshRuntimeOptions) {
208
+ this.onReceiveReady = options.onReceiveReady;
209
+ this.infrastructureTaskRunner = options.infrastructureTaskRunner;
186
210
  this.operations = new OperationRegistry(undefined, options.onPendingOperationsChanged);
187
211
  this.descriptor = options.descriptor;
212
+ this.nodeMailboxOwner = `node:${options.descriptor.nodeRoutingId}`;
188
213
  this.topology = new ServiceTopologyRegistry(options.descriptor);
189
214
  this.mailbox = new ServiceMailbox(options.onMailboxReady);
190
215
  this.liveness = new ServiceLivenessRegistry(options.probeIntervalMs, options.peerTimeoutMs);
@@ -363,6 +388,36 @@ export class RawServiceMeshRuntime {
363
388
  };
364
389
  }
365
390
 
391
+ observePeerRoute(
392
+ nodeRoutingId: string
393
+ ): import('./durable-service-request').PeerRouteObservation {
394
+ const selected = this.selectedRoutes.get(nodeRoutingId);
395
+ const admitted = this.topology.admittedPeer(nodeRoutingId)?.liveness?.connectionId;
396
+ return {
397
+ generation: selected ?? (typeof admitted === 'bigint' ? admitted : undefined),
398
+ ready: admitted !== undefined && (selected === undefined || admitted === selected)
399
+ };
400
+ }
401
+
402
+ get requestStopSignal(): AbortSignal {
403
+ return this.applicationJobStop.signal;
404
+ }
405
+
406
+ observePeerRouteChanged(listener: (nodeRoutingId: string) => void): () => void {
407
+ this.peerRouteChanged.add(listener);
408
+ return () => {
409
+ this.peerRouteChanged.delete(listener);
410
+ };
411
+ }
412
+
413
+ hasSelectedPeerRoute(nodeRoutingId: string): boolean {
414
+ return this.selectedRoutes.has(nodeRoutingId);
415
+ }
416
+
417
+ private notifyPeerRouteChanged(nodeRoutingId: string): void {
418
+ for (const listener of this.peerRouteChanged) listener(nodeRoutingId);
419
+ }
420
+
366
421
  private notifyPeerConnectionIntentRemoved(nodeRoutingId: string): void {
367
422
  // Only the logical owner's removal publishes lifecycle termination.
368
423
  // A physical disconnect alone retains the connection expectation.
@@ -371,37 +426,49 @@ export class RawServiceMeshRuntime {
371
426
  for (const listener of this.peerConnectionIntentRemoved) listener(nodeRoutingId);
372
427
  }
373
428
 
374
- async announcePeer(nodeRoutingId: string): Promise<boolean> {
375
- const expected = this.expectedPeers.get(nodeRoutingId);
429
+ announcePeer(nodeRoutingId: string): Promise<boolean> {
376
430
  const generation = this.selectedRoutes.get(nodeRoutingId);
377
- if (
378
- expected === undefined ||
379
- generation === undefined ||
380
- expected.helloSubmittedGeneration === generation ||
381
- this.peerAdmissionSealed?.() === true
382
- )
383
- return false;
384
- const accepted = await this.send(nodeRoutingId, [
385
- encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor())
386
- ]);
387
- if (
388
- accepted &&
389
- this.expectedPeers.get(nodeRoutingId) === expected &&
390
- this.selectedRoutes.get(nodeRoutingId) === generation
391
- )
392
- expected.helloSubmittedGeneration = generation;
393
- return accepted;
431
+ if (generation === undefined) return Promise.resolve(false);
432
+ const current = () => {
433
+ const admission = this.topology.routeAdmission(nodeRoutingId);
434
+ return (
435
+ this.selectedRoutes.get(nodeRoutingId) === generation &&
436
+ !(
437
+ admission?.connectionId === routeConnectionId(generation) &&
438
+ admission.remoteAdmitReceived === true
439
+ ) &&
440
+ this.peerAdmissionSealed?.() !== true
441
+ );
442
+ };
443
+ const key = controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello);
444
+ if (!current()) {
445
+ this.controlSends.delete(key);
446
+ return Promise.resolve(false);
447
+ }
448
+ const existing = this.controlSends.get(key);
449
+ const hello =
450
+ existing?.current?.() === true
451
+ ? existing.hello!
452
+ : encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor());
453
+ return Promise.resolve(
454
+ this.startControlSend(
455
+ nodeRoutingId,
456
+ M6aServiceWireCommand.hello,
457
+ () => this.submitControl(nodeRoutingId, [hello]),
458
+ current,
459
+ hello
460
+ )
461
+ );
394
462
  }
395
463
 
396
464
  isPeerRouteReady(nodeRoutingId: string, lifecycleGeneration?: bigint): boolean {
397
465
  // Admission is bound to the observed selected route: the route observer
398
466
  // removes the peer when that route ends or is replaced.
399
- const peer = this.topology.peer(nodeRoutingId);
467
+ const peer = this.topology.admittedPeer(nodeRoutingId);
400
468
  return (
401
469
  peer !== undefined &&
402
470
  (lifecycleGeneration === undefined ||
403
- peer.descriptor.lifecycleGeneration === lifecycleGeneration) &&
404
- this.liveness.isReady(nodeRoutingId, peer.connectionId)
471
+ peer.descriptor.lifecycleGeneration === lifecycleGeneration)
405
472
  );
406
473
  }
407
474
 
@@ -444,12 +511,18 @@ export class RawServiceMeshRuntime {
444
511
  // placement and channel selectors therefore need the new mutable fields
445
512
  // on that same connection before the next request is selected; waiting for
446
513
  // a reconnect would leave a race where a drained node is selected again.
514
+ await this.publishLocalDescriptor(
515
+ this.topology.peers().map((peer) => peer.descriptor.nodeRoutingId)
516
+ );
517
+ }
518
+
519
+ private async publishLocalDescriptor(targets: readonly string[]): Promise<void> {
447
520
  const update = encodeRouteMeshAdmission(
448
521
  M6aServiceWireCommand.update,
449
522
  this.topology.localDescriptor()
450
523
  );
451
- for (const peer of this.topology.peers()) {
452
- await this.send(peer.descriptor.nodeRoutingId, [update]);
524
+ for (const target of targets) {
525
+ await this.sendService(target, [update]);
453
526
  }
454
527
  }
455
528
 
@@ -458,18 +531,14 @@ export class RawServiceMeshRuntime {
458
531
  }
459
532
 
460
533
  isObjectClientNodeDirectTarget(nodeRoutingId: string): boolean {
461
- const descriptor =
462
- nodeRoutingId === this.descriptor.nodeRoutingId
463
- ? this.topology.localDescriptor()
464
- : this.topology.knownDescriptor(nodeRoutingId);
465
- return descriptor?.objectRole === 'client';
534
+ return this.topology.isObjectClientNodeDirectTarget(nodeRoutingId);
466
535
  }
467
536
 
468
- async sendToNode(
537
+ sendToNode(
469
538
  targetNodeRoutingId: string,
470
539
  payload: ServiceApplicationPayloadInput
471
- ): Promise<boolean> {
472
- return this.send(targetNodeRoutingId, [encodeNodeSendHeader(), this.applicationFrame(payload)]);
540
+ ): boolean | Promise<boolean> {
541
+ return this.send(targetNodeRoutingId, [NODE_SEND_HEADER, this.applicationFrame(payload)]);
473
542
  }
474
543
 
475
544
  async sendToChannel(
@@ -491,15 +560,17 @@ export class RawServiceMeshRuntime {
491
560
  requestToNode(
492
561
  targetNodeRoutingId: string,
493
562
  payload: ServiceApplicationPayloadInput,
494
- timeoutMs: number
563
+ timeoutMs: number,
564
+ onAdmitted?: () => void
495
565
  ): PendingOperation<RawServiceRequestResult> {
496
- return this.requestToTarget(targetNodeRoutingId, payload, timeoutMs);
566
+ return this.requestToTarget(targetNodeRoutingId, payload, timeoutMs, undefined, onAdmitted);
497
567
  }
498
568
 
499
569
  requestToChannel(
500
570
  channelName: string,
501
571
  payload: ServiceApplicationPayloadInput,
502
- timeoutMs: number
572
+ timeoutMs: number,
573
+ onAdmitted?: () => void
503
574
  ): PendingOperation<RawServiceRequestResult> {
504
575
  const selection = this.selectChannelTarget(channelName);
505
576
  if (selection.kind !== 'selected') {
@@ -511,7 +582,8 @@ export class RawServiceMeshRuntime {
511
582
  selection.peer.descriptor.nodeRoutingId,
512
583
  payload,
513
584
  timeoutMs,
514
- channelName
585
+ channelName,
586
+ onAdmitted
515
587
  );
516
588
  }
517
589
 
@@ -536,11 +608,15 @@ export class RawServiceMeshRuntime {
536
608
  };
537
609
  }
538
610
 
539
- setServiceIngress(handler: RawServiceIngressHandler): void {
611
+ setServiceIngress(
612
+ handler: RawServiceIngressHandler,
613
+ accepts?: (command: number) => boolean
614
+ ): void {
540
615
  if (this.serviceIngress !== undefined && this.serviceIngress !== handler) {
541
616
  throw new Error('Raw service ingress is already registered.');
542
617
  }
543
618
  this.serviceIngress = handler;
619
+ this.serviceIngressAccepts = accepts;
544
620
  }
545
621
 
546
622
  async reserveLocalIngress(signal?: AbortSignal): Promise<ApplicationIngressRecordOwner> {
@@ -548,20 +624,33 @@ export class RawServiceMeshRuntime {
548
624
  signal === undefined
549
625
  ? this.applicationJobStop.signal
550
626
  : AbortSignal.any([this.applicationJobStop.signal, signal]);
551
- const permit = await this.applicationJobQueue.acquire(waitSignal);
627
+ const permit = await this.applicationJobQueue.acquire(waitSignal, 'local');
552
628
  return ApplicationIngressRecordOwner.create(this.applicationJobQueue, permit, { close() {} });
553
629
  }
554
630
 
555
631
  async sendService(targetNodeRoutingId: string, parts: readonly Uint8Array[]): Promise<boolean> {
556
- return this.send(targetNodeRoutingId, parts);
632
+ try {
633
+ await this.requireStarted().send(targetNodeRoutingId, parts);
634
+ return true;
635
+ } catch (error) {
636
+ return this.sendFailure(error);
637
+ }
557
638
  }
558
639
 
559
640
  requestService(
560
641
  targetNodeRoutingId: string,
561
642
  parts: readonly Uint8Array[],
562
- timeoutMs: number
643
+ timeoutMs: number,
644
+ onAdmitted?: () => void
563
645
  ): Promise<readonly Buffer[]> {
564
- return this.requireStarted().request(targetNodeRoutingId, parts, timeoutMs);
646
+ const admission = this.topology.admittedPeer(targetNodeRoutingId)?.liveness;
647
+ return this.requireStarted().request(
648
+ targetNodeRoutingId,
649
+ parts,
650
+ timeoutMs,
651
+ admission?.recordReceived,
652
+ onAdmitted
653
+ );
565
654
  }
566
655
 
567
656
  replyService(
@@ -607,6 +696,18 @@ export class RawServiceMeshRuntime {
607
696
  nowMs = performance.now(),
608
697
  observe?: RawServicePumpObserver
609
698
  ): Promise<RawServicePumpResult> {
699
+ const result = await this.receiveOne(nowMs, observe);
700
+ const acquisition = this.receiveAcquisition;
701
+ if (acquisition === undefined) return result;
702
+ // A single receive operation may await its FIFO grant. Infrastructure
703
+ // rounds use receiveOne directly and end while that grant is pending.
704
+ try {
705
+ await acquisition;
706
+ } catch (error) {
707
+ if (this.closed) return 'noData';
708
+ throw error;
709
+ }
710
+ if (this.closed) return 'noData';
610
711
  return this.receiveOne(nowMs, observe);
611
712
  }
612
713
 
@@ -616,6 +717,7 @@ export class RawServiceMeshRuntime {
616
717
 
617
718
  /** Returns whether a receive budget ended before no-data; idle ticks only maintain peers. */
618
719
  async pumpBatch(receiveReady = true, routeReady = false): Promise<boolean> {
720
+ this.observeInfrastructureFailure();
619
721
  if (routeReady) await this.observeSelectedRoutes();
620
722
  const startedAtMs = performance.now();
621
723
  let messages = 0;
@@ -623,42 +725,82 @@ export class RawServiceMeshRuntime {
623
725
  const observe: RawServicePumpObserver = (_source, byteCount) => {
624
726
  bytes += byteCount;
625
727
  };
626
- while (receiveReady && messages < RAW_MESH_RECEIVE_RECORD_BUDGET && !this.closed) {
627
- const result = await this.receiveOne(performance.now(), observe);
628
- if (result === 'noData') {
629
- receiveReady = false;
630
- break;
728
+ const rotation = (this.receiveRotation ??= this.applicationJobQueue.createReceiveRotation(
729
+ RAW_MESH_RECEIVE_RECORD_BUDGET,
730
+ 'remote'
731
+ ));
732
+ if (receiveReady) rotation.reserve();
733
+ try {
734
+ while (receiveReady && messages < RAW_MESH_RECEIVE_RECORD_BUDGET && !this.closed) {
735
+ const received = this.receiveOne(performance.now(), observe, rotation);
736
+ const result = received instanceof Promise ? await received : received;
737
+ if (result === 'noData') {
738
+ receiveReady = false;
739
+ break;
740
+ }
741
+ messages += 1;
742
+ // Core owns the per-peer fair-queue cursor. The Framework limit bounds
743
+ // the entire round, so changing peers does not renew its byte/count budget.
744
+ if (
745
+ bytes >= RAW_MESH_RECEIVE_BYTE_BUDGET ||
746
+ performance.now() - startedAtMs >= RAW_MESH_RECEIVE_TIME_BUDGET_MS
747
+ )
748
+ break;
631
749
  }
632
- messages += 1;
633
- // Core owns the per-peer fair-queue cursor. The Framework limit bounds
634
- // the entire round, so changing peers does not renew its byte/count budget.
635
- if (
636
- bytes >= RAW_MESH_RECEIVE_BYTE_BUDGET ||
637
- performance.now() - startedAtMs >= RAW_MESH_RECEIVE_TIME_BUDGET_MS
638
- )
639
- break;
750
+ } finally {
751
+ rotation.releaseUnused();
640
752
  }
641
753
  if (!this.closed) {
642
- await this.announceExpectedPeers();
643
- await this.tickLiveness();
754
+ this.tickLiveness();
644
755
  }
645
756
  return receiveReady && messages > 0;
646
757
  }
647
758
 
648
- private async receiveOne(
759
+ private receiveOne(
649
760
  nowMs: number,
650
- observe?: RawServicePumpObserver
651
- ): Promise<RawServicePumpResult> {
761
+ observe?: RawServicePumpObserver,
762
+ rotation?: ApplicationJobReceiveRotationPort
763
+ ): RawServicePumpResult | Promise<RawServicePumpResult> {
652
764
  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;
765
+ this.observeInfrastructureFailure();
766
+ let permit = this.receivePermit;
767
+ this.receivePermit = undefined;
768
+ permit ??= rotation?.take();
769
+ if (permit === undefined && this.receiveAcquisition === undefined) {
770
+ let resolveAcquisition: ((permit: ApplicationJobPermitPort) => void) | undefined;
771
+ let rejectAcquisition: ((error: unknown) => void) | undefined;
772
+ permit = this.applicationJobQueue.acquireOrResume(
773
+ (permit) => {
774
+ this.receiveAcquisition = undefined;
775
+ if (this.closed) {
776
+ permit.releaseAfterInternalProcessing();
777
+ permit.releaseStorage?.();
778
+ } else {
779
+ this.receivePermit = permit;
780
+ this.onReceiveReady?.();
781
+ }
782
+ resolveAcquisition?.(permit);
783
+ },
784
+ (error) => rejectAcquisition?.(error),
785
+ this.applicationJobStop.signal,
786
+ 'remote'
787
+ );
788
+ if (permit === undefined) {
789
+ const acquisition = new Promise<ApplicationJobPermitPort>((resolve, reject) => {
790
+ resolveAcquisition = resolve;
791
+ rejectAcquisition = reject;
792
+ });
793
+ this.receiveAcquisition = acquisition;
794
+ acquisition.catch((error) => {
795
+ this.receiveAcquisition = undefined;
796
+ if (!this.applicationJobStop.signal.aborted) this.reportInfrastructureFailure(error);
797
+ });
798
+ }
659
799
  }
800
+ if (permit === undefined) return 'noData';
660
801
  if (this.closed) {
661
802
  permit.releaseAfterInternalProcessing();
803
+ permit.releaseStorage?.();
662
804
  return 'noData';
663
805
  }
664
806
  let received: ZLinkRawReceivedRecord | undefined;
@@ -666,10 +808,12 @@ export class RawServiceMeshRuntime {
666
808
  received = router.receive(true);
667
809
  } catch (error) {
668
810
  permit.releaseAfterInternalProcessing();
811
+ permit.releaseStorage?.();
669
812
  throw error;
670
813
  }
671
814
  if (received === undefined) {
672
815
  permit.releaseAfterInternalProcessing();
816
+ permit.releaseStorage?.();
673
817
  return 'noData';
674
818
  }
675
819
  const applicationJobOwner = ApplicationIngressRecordOwner.create(
@@ -677,18 +821,40 @@ export class RawServiceMeshRuntime {
677
821
  permit,
678
822
  received
679
823
  );
824
+ let result: RawServicePumpResult | Promise<RawServicePumpResult>;
680
825
  try {
681
- const result = await this.processReceived(received, nowMs, applicationJobOwner);
682
- if (result === 'protocolError') {
683
- this.reportProtocolError(received);
826
+ result = this.processReceived(received, nowMs, applicationJobOwner);
827
+ } catch (error) {
828
+ applicationJobOwner.close();
829
+ throw error;
830
+ }
831
+ return result instanceof Promise
832
+ ? result.then(
833
+ (value) => this.finishReceive(received, applicationJobOwner, observe, value),
834
+ (error) => {
835
+ applicationJobOwner.close();
836
+ throw error;
837
+ }
838
+ )
839
+ : this.finishReceive(received, applicationJobOwner, observe, result);
840
+ }
841
+
842
+ private finishReceive(
843
+ received: ZLinkRawReceivedRecord,
844
+ owner: ApplicationIngressRecordOwner,
845
+ observe: RawServicePumpObserver | undefined,
846
+ result: RawServicePumpResult
847
+ ): RawServicePumpResult {
848
+ try {
849
+ if (result === 'protocolError') this.reportProtocolError(received);
850
+ if (observe !== undefined) {
851
+ let bytes = 0;
852
+ for (const part of received.parts) bytes += part.byteLength;
853
+ observe(received.sourceRid, bytes);
684
854
  }
685
- observe?.(
686
- received.sourceRid,
687
- received.parts.reduce((sum, part) => sum + part.byteLength, 0)
688
- );
689
855
  return result;
690
856
  } finally {
691
- applicationJobOwner.close();
857
+ owner.close();
692
858
  }
693
859
  }
694
860
 
@@ -734,22 +900,15 @@ export class RawServiceMeshRuntime {
734
900
  }
735
901
  }
736
902
 
737
- private async processReceived(
903
+ private processReceived(
738
904
  received: ZLinkRawReceivedRecord,
739
905
  nowMs: number,
740
906
  applicationJobOwner: ApplicationIngressRecordOwner
741
- ): Promise<RawServicePumpResult> {
742
- if (
743
- received.parts.length === 0 ||
744
- (received.parts.length === 1 && received.parts[0]!.byteLength === 0)
745
- ) {
746
- if (this.peerAdmissionSealed?.() === true) return 'dropped';
747
- return (await this.send(received.sourceRid, [
748
- encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor())
749
- ]))
750
- ? 'infrastructure'
751
- : 'dropped';
752
- }
907
+ ): RawServicePumpResult | Promise<RawServicePumpResult> {
908
+ const peer = this.topology.admittedPeer(received.sourceRid);
909
+ const currentSource =
910
+ peer?.liveness !== undefined && peer.liveness.connectionId === received.routeGeneration;
911
+ if (received.parts.length === 0) return currentSource ? 'protocolError' : 'dropped';
753
912
  try {
754
913
  const header = decodeHeader(received.parts[0]!);
755
914
  if (header.command === M6aServiceWireCommand.hello && this.peerAdmissionSealed?.() === true) {
@@ -757,105 +916,186 @@ export class RawServiceMeshRuntime {
757
916
  }
758
917
  if (
759
918
  header.command === M6aServiceWireCommand.hello ||
760
- header.command === M6aServiceWireCommand.admit ||
761
- header.command === M6aServiceWireCommand.update
919
+ header.command === M6aServiceWireCommand.admit
762
920
  ) {
763
- if (received.parts.length !== 1) return 'protocolError';
764
- const descriptor = decodeRouteMeshAdmission(
765
- received.parts[0]!,
766
- header.command,
767
- received.sourceRid
921
+ return this.processAdmission(received, header, nowMs).catch((error) =>
922
+ this.receiveProtocolFailure(error)
768
923
  );
769
- const expected = this.expectedPeers.get(received.sourceRid);
770
- if (
771
- expected !== undefined &&
772
- (expected.meshName !== descriptor.meshName ||
773
- expected.nodeRoutingId !== descriptor.nodeRoutingId)
774
- ) {
775
- await this.send(received.sourceRid, [
776
- encodeReject(enumWireRejectReason('identityMismatch'))
777
- ]);
778
- return 'infrastructure';
779
- }
780
- const result = this.admitPeer(
781
- descriptor,
782
- routeConnectionId(received.routeGeneration),
783
- nowMs,
784
- expected
785
- );
786
- if (result !== 'admitted' && result !== 'notRequired') {
787
- const detail =
788
- result === 'invalidDescriptor'
789
- ? describeInvalidAdmissionDescriptor(
790
- descriptor,
791
- expected,
792
- this.topology.peer(descriptor.nodeRoutingId)?.descriptor
793
- )
794
- : undefined;
795
- console.warn(
796
- `RouteMesh admission rejected peer '${received.sourceRid}' as ${result}` +
797
- (detail === undefined ? '.' : ` (${detail}).`)
798
- );
799
- await this.send(received.sourceRid, [encodeReject(admissionReason(result))]);
800
- return 'infrastructure';
801
- }
802
- if (result === 'admitted') this.selectBilateralConnection(descriptor);
803
- if (header.command === M6aServiceWireCommand.hello) {
804
- // Hello supplies only the sender's descriptor. Even NotRequired
805
- // peers must receive ours before the intent owner closes transport;
806
- // submitting a response does not guarantee delivery before close.
807
- await this.send(received.sourceRid, [
808
- encodeRouteMeshAdmission(M6aServiceWireCommand.admit, this.topology.localDescriptor())
809
- ]);
810
- } else if (result === 'notRequired') {
811
- this.retireNotRequiredExpectedPeer(received.sourceRid, descriptor.advertisedEndpoint);
812
- }
813
- return 'infrastructure';
814
924
  }
815
925
  if (header.command === M6aServiceWireCommand.reject) {
816
926
  if (received.parts.length !== 1) return 'protocolError';
817
927
  decodeReject(received.parts[0]!);
818
928
  return 'infrastructure';
819
929
  }
820
- const peer = this.topology.peer(received.sourceRid);
821
- if (peer === undefined) return 'protocolError';
930
+ if (!currentSource) return 'dropped';
931
+ this.liveness.recordReceived(peer.liveness, nowMs);
932
+ if (header.command === M6aServiceWireCommand.update) {
933
+ return this.processAdmission(received, header, nowMs).catch((error) =>
934
+ this.receiveProtocolFailure(error)
935
+ );
936
+ }
822
937
  if (
823
938
  header.command === M6aServiceWireCommand.livenessProbe ||
824
939
  header.command === M6aServiceWireCommand.livenessAck
825
940
  ) {
826
- if (received.parts.length !== 1) return 'protocolError';
827
- const record = livenessCodec.decodeLivenessRecord(received.parts[0]!);
828
- if (record.command === M6aServiceWireCommand.livenessProbe) {
829
- const ack = this.liveness.acknowledgeProbe(
830
- received.sourceRid,
831
- peer.connectionId,
832
- record.probeId
833
- );
834
- if (ack === undefined) return 'protocolError';
835
- const sent = await this.send(received.sourceRid, [
836
- livenessCodec.encodeLivenessRecord({
837
- command: M6aServiceWireCommand.livenessAck,
838
- probeId: record.probeId
839
- })
840
- ]);
841
- if (!sent) return 'dropped';
842
- } else {
843
- this.liveness.acknowledge(received.sourceRid, peer.connectionId, record.probeId, nowMs);
844
- }
845
- return 'infrastructure';
941
+ return this.processLiveness(received, nowMs).catch((error) =>
942
+ this.receiveProtocolFailure(error)
943
+ );
846
944
  }
847
- const stateful = await this.serviceIngress?.({
848
- command: header.command,
849
- flags: header.flags,
850
- sourceRoutingId: received.sourceRid,
851
- sourceNodeGeneration: peer.descriptor.lifecycleGeneration,
852
- sourceRoute: received.sourceRoute,
853
- ...(received.reply === undefined ? {} : { reply: received.reply }),
854
- ...(received.requestSeq === undefined ? {} : { requestSequence: received.requestSeq }),
855
- parts: received.parts,
856
- applicationJobOwner
857
- });
945
+ const stateful =
946
+ this.serviceIngress !== undefined && this.serviceIngressAccepts?.(header.command) !== false
947
+ ? this.serviceIngress({
948
+ command: header.command,
949
+ flags: header.flags,
950
+ sourceRoutingId: received.sourceRid,
951
+ sourceNodeGeneration: peer.descriptor.lifecycleGeneration,
952
+ sourceRoute: received.sourceRoute,
953
+ ...(received.reply === undefined ? {} : { reply: received.reply }),
954
+ ...(received.requestSeq === undefined
955
+ ? {}
956
+ : { requestSequence: received.requestSeq }),
957
+ parts: received.parts,
958
+ applicationJobOwner
959
+ })
960
+ : undefined;
961
+ if (stateful instanceof Promise)
962
+ return stateful.then(
963
+ (result) =>
964
+ result ?? this.processApplicationReceived(received, header, applicationJobOwner),
965
+ (error) => this.receiveProtocolFailure(error)
966
+ );
858
967
  if (stateful !== undefined) return stateful;
968
+ return this.processApplicationReceived(received, header, applicationJobOwner);
969
+ } catch (error) {
970
+ if (!currentSource && error instanceof ServiceWireProtocolError) return 'dropped';
971
+ return this.receiveProtocolFailure(error);
972
+ }
973
+ }
974
+
975
+ private receiveProtocolFailure(error: unknown): RawServicePumpResult {
976
+ if (error instanceof ServiceWireProtocolError) return 'protocolError';
977
+ throw error;
978
+ }
979
+
980
+ private async processAdmission(
981
+ received: ZLinkRawReceivedRecord,
982
+ header: ReturnType<typeof decodeHeader>,
983
+ nowMs: number
984
+ ): Promise<RawServicePumpResult> {
985
+ if (received.parts.length !== 1) return 'protocolError';
986
+ const descriptor = decodeRouteMeshAdmission(
987
+ received.parts[0]!,
988
+ header.command,
989
+ received.sourceRid
990
+ );
991
+ const expected = this.expectedPeers.get(received.sourceRid);
992
+ if (
993
+ expected !== undefined &&
994
+ (expected.meshName !== descriptor.meshName ||
995
+ expected.nodeRoutingId !== descriptor.nodeRoutingId)
996
+ ) {
997
+ await this.sendControl(received.sourceRid, [
998
+ encodeReject(enumWireRejectReason('identityMismatch'))
999
+ ]);
1000
+ return 'infrastructure';
1001
+ }
1002
+ const helloKey = controlSendKey(received.sourceRid, M6aServiceWireCommand.hello);
1003
+ const exchange = this.controlSends.get(helloKey);
1004
+ const result = this.admitPeer(
1005
+ descriptor,
1006
+ received.routeGeneration,
1007
+ nowMs,
1008
+ expected,
1009
+ header.command,
1010
+ received.parts[0]!.subarray(SERVICE_WIRE_PREFIX_SIZE)
1011
+ );
1012
+ if (result !== 'admitted' && result !== 'notRequired') {
1013
+ const detail =
1014
+ result === 'invalidDescriptor'
1015
+ ? describeInvalidAdmissionDescriptor(
1016
+ descriptor,
1017
+ expected,
1018
+ this.topology.peer(descriptor.nodeRoutingId)?.descriptor
1019
+ )
1020
+ : undefined;
1021
+ console.warn(
1022
+ `RouteMesh admission rejected peer '${received.sourceRid}' as ${result}` +
1023
+ (detail === undefined ? '.' : ` (${detail}).`)
1024
+ );
1025
+ await this.sendControl(received.sourceRid, [encodeReject(admissionReason(result))]);
1026
+ return 'infrastructure';
1027
+ }
1028
+ if (result === 'admitted') this.selectBilateralConnection(descriptor);
1029
+ if (header.command === M6aServiceWireCommand.hello) {
1030
+ // Hello supplies only the sender's descriptor. Even NotRequired
1031
+ // peers must receive ours before the intent owner closes transport;
1032
+ // submitting a response does not guarantee delivery before close.
1033
+ const local =
1034
+ exchange?.current?.() === true && exchange.hello !== undefined
1035
+ ? decodeRouteMeshAdmission(
1036
+ exchange.hello,
1037
+ M6aServiceWireCommand.hello,
1038
+ this.descriptor.nodeRoutingId
1039
+ )
1040
+ : this.topology.localDescriptor();
1041
+ await this.sendControl(received.sourceRid, [
1042
+ encodeRouteMeshAdmission(M6aServiceWireCommand.admit, local)
1043
+ ]);
1044
+ } else if (header.command === M6aServiceWireCommand.admit) {
1045
+ this.controlSends.delete(helloKey);
1046
+ if (result === 'notRequired') {
1047
+ this.retireNotRequiredExpectedPeer(received.sourceRid, descriptor.advertisedEndpoint);
1048
+ } else if (
1049
+ exchange?.hello !== undefined &&
1050
+ decodeRouteMeshAdmission(
1051
+ exchange.hello,
1052
+ M6aServiceWireCommand.hello,
1053
+ this.descriptor.nodeRoutingId
1054
+ ).descriptorRevision < this.descriptor.descriptorRevision
1055
+ ) {
1056
+ await this.publishLocalDescriptor([received.sourceRid]);
1057
+ }
1058
+ }
1059
+ return 'infrastructure';
1060
+ }
1061
+
1062
+ private async processLiveness(
1063
+ received: ZLinkRawReceivedRecord,
1064
+ nowMs: number
1065
+ ): Promise<RawServicePumpResult> {
1066
+ if (received.parts.length !== 1) return 'protocolError';
1067
+ const record = livenessCodec.decodeLivenessRecord(received.parts[0]!);
1068
+ if (record.command === M6aServiceWireCommand.livenessProbe) {
1069
+ const ack = this.liveness.acknowledgeProbe(
1070
+ received.sourceRid,
1071
+ received.routeGeneration,
1072
+ record.probeId
1073
+ );
1074
+ if (ack === undefined) return 'protocolError';
1075
+ const sent = await this.sendControl(received.sourceRid, [
1076
+ livenessCodec.encodeLivenessRecord({
1077
+ command: M6aServiceWireCommand.livenessAck,
1078
+ probeId: record.probeId
1079
+ })
1080
+ ]);
1081
+ if (!sent) return 'dropped';
1082
+ } else {
1083
+ this.liveness.acknowledge(
1084
+ received.sourceRid,
1085
+ received.routeGeneration,
1086
+ record.probeId,
1087
+ nowMs
1088
+ );
1089
+ }
1090
+ return 'infrastructure';
1091
+ }
1092
+
1093
+ private processApplicationReceived(
1094
+ received: ZLinkRawReceivedRecord,
1095
+ header: ReturnType<typeof decodeHeader>,
1096
+ applicationJobOwner: ApplicationIngressRecordOwner
1097
+ ): RawServicePumpResult | Promise<RawServicePumpResult> {
1098
+ try {
859
1099
  if (
860
1100
  header.flags === 0 &&
861
1101
  // A bare infrastructure control is normally exactly one frame, but a
@@ -873,7 +1113,7 @@ export class RawServiceMeshRuntime {
873
1113
  isBareInfrastructureControl(header.command)
874
1114
  ) {
875
1115
  const accepted = this.mailbox.tryEnqueue({
876
- owner: `node:${this.descriptor.nodeRoutingId}`,
1116
+ owner: this.nodeMailboxOwner,
877
1117
  domain: 'infrastructure',
878
1118
  parts: received.parts,
879
1119
  sourceRoutingId: received.sourceRid,
@@ -898,48 +1138,58 @@ export class RawServiceMeshRuntime {
898
1138
  let owner: string;
899
1139
  let correlation: bigint | undefined;
900
1140
  if (header.command === M6aServiceWireCommand.nodeSend) {
901
- owner = `node:${this.descriptor.nodeRoutingId}`;
1141
+ owner = this.nodeMailboxOwner;
902
1142
  } else if (header.command === M6aServiceWireCommand.nodeRequest) {
903
- owner = `node:${this.descriptor.nodeRoutingId}`;
904
- correlation = decodeNodeRequestHeader(received.parts[0]!);
1143
+ owner = this.nodeMailboxOwner;
1144
+ correlation = decodeNodeRequestHeader(received.parts[0]!, header);
905
1145
  } else if (header.command === M6aServiceWireCommand.channelSend) {
906
- owner = `channel:${decodeChannelSendHeader(received.parts[0]!)}`;
1146
+ owner = `channel:${decodeChannelSendHeader(received.parts[0]!, header)}`;
907
1147
  } else {
908
- const channel = decodeChannelRequestHeader(received.parts[0]!);
1148
+ const channel = decodeChannelRequestHeader(received.parts[0]!, header);
909
1149
  owner = `channel:${channel.channelName}`;
910
1150
  correlation = channel.correlation;
911
1151
  }
912
- const applicationJob = await applicationJobOwner.acquire('application');
913
- const accepted = this.mailbox.tryEnqueue({
914
- owner,
915
- domain: 'application',
916
- parts: received.parts,
917
- sourceRoutingId: received.sourceRid,
918
- sourceRoute: received.sourceRoute,
919
- ...(received.reply === undefined ? {} : { reply: received.reply }),
920
- requestSequence: received.requestSeq,
921
- ...(correlation === undefined ? {} : { correlation }),
922
- applicationJob
923
- });
924
- if (accepted) return 'application';
925
- applicationJob.close();
926
- if (correlation !== undefined && received.requestSeq !== undefined) {
927
- this.replyService(
928
- {
929
- sourceRoutingId: received.sourceRid,
930
- sourceRoute: received.sourceRoute,
931
- requestSequence: received.requestSeq,
932
- ...(received.reply === undefined ? {} : { reply: received.reply })
933
- },
934
- [encodeReplyHeader(correlation, RequestResult.NotConnected, 0)]
935
- );
936
- return 'infrastructure';
937
- }
938
- return 'dropped';
1152
+ const job = applicationJobOwner.acquire('application');
1153
+ return job instanceof Promise
1154
+ ? job.then((permit) => this.enqueueOrdinaryRecord(received, owner, correlation, permit))
1155
+ : this.enqueueOrdinaryRecord(received, owner, correlation, job);
939
1156
  } catch (error) {
940
- if (error instanceof ServiceWireProtocolError) return 'protocolError';
941
- throw error;
1157
+ return this.receiveProtocolFailure(error);
1158
+ }
1159
+ }
1160
+
1161
+ private enqueueOrdinaryRecord(
1162
+ received: ZLinkRawReceivedRecord,
1163
+ owner: string,
1164
+ correlation: bigint | undefined,
1165
+ applicationJob: import('../application-jobs/application-ingress-record-owner').ApplicationJobRecordLease
1166
+ ): RawServicePumpResult {
1167
+ const accepted = this.mailbox.tryEnqueue({
1168
+ owner,
1169
+ domain: 'application',
1170
+ parts: received.parts,
1171
+ sourceRoutingId: received.sourceRid,
1172
+ sourceRoute: received.sourceRoute,
1173
+ ...(received.reply === undefined ? {} : { reply: received.reply }),
1174
+ requestSequence: received.requestSeq,
1175
+ ...(correlation === undefined ? {} : { correlation }),
1176
+ applicationJob
1177
+ });
1178
+ if (accepted) return 'application';
1179
+ applicationJob.close();
1180
+ if (correlation !== undefined && received.requestSeq !== undefined) {
1181
+ this.replyService(
1182
+ {
1183
+ sourceRoutingId: received.sourceRid,
1184
+ sourceRoute: received.sourceRoute,
1185
+ requestSequence: received.requestSeq,
1186
+ ...(received.reply === undefined ? {} : { reply: received.reply })
1187
+ },
1188
+ [encodeReplyHeader(correlation, RequestResult.NotConnected, 0)]
1189
+ );
1190
+ return 'infrastructure';
942
1191
  }
1192
+ return 'dropped';
943
1193
  }
944
1194
 
945
1195
  expireOperations(nowMs: number, turnDeadlineMs: number): number {
@@ -950,21 +1200,25 @@ export class RawServiceMeshRuntime {
950
1200
  return this.operations.size;
951
1201
  }
952
1202
 
953
- async tickLiveness(nowMs = performance.now()): Promise<ServiceLivenessTick> {
1203
+ tickLiveness(nowMs = performance.now()): ServiceLivenessTick {
954
1204
  const result = this.liveness.tick(nowMs);
955
1205
  this.requireStarted();
1206
+ for (const nodeRoutingId of this.selectedRoutes.keys()) void this.announcePeer(nodeRoutingId);
956
1207
  for (const probe of result.probes) {
957
- await this.send(probe.nodeRoutingId, [
1208
+ const parts = [
958
1209
  livenessCodec.encodeLivenessRecord({
959
1210
  command: M6aServiceWireCommand.livenessProbe,
960
1211
  probeId: probe.probeId
961
1212
  })
962
- ]);
1213
+ ];
1214
+ this.startControlSend(probe.nodeRoutingId, M6aServiceWireCommand.livenessProbe, () =>
1215
+ this.submitControl(probe.nodeRoutingId, parts)
1216
+ );
963
1217
  }
964
1218
  for (const nodeRoutingId of result.timedOutNodes) {
965
1219
  const peer = this.topology.peer(nodeRoutingId);
966
1220
  if (peer === undefined) continue;
967
- // Liveness timeout is a semantic peer removal of the admitted route.
1221
+ this.requireStarted().disconnectRid!(nodeRoutingId);
968
1222
  this.removePeer(peer);
969
1223
  }
970
1224
  return result;
@@ -986,11 +1240,10 @@ export class RawServiceMeshRuntime {
986
1240
  let changes = 0;
987
1241
  for (const [nodeRoutingId, generation] of this.selectedRoutes) {
988
1242
  if (observed.get(nodeRoutingId) === generation) continue;
1243
+ this.controlSends.delete(controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello));
989
1244
  changes++;
990
- const expected = this.expectedPeers.get(nodeRoutingId);
991
- if (expected !== undefined) delete expected.helloSubmittedGeneration;
992
1245
  const peer = this.topology.peer(nodeRoutingId);
993
- if (peer !== undefined && peer.connectionId === routeConnectionId(generation)) {
1246
+ if (peer?.liveness?.connectionId === generation) {
994
1247
  this.removePeer(peer);
995
1248
  }
996
1249
  }
@@ -1001,6 +1254,7 @@ export class RawServiceMeshRuntime {
1001
1254
  selected.push(nodeRoutingId);
1002
1255
  }
1003
1256
  this.selectedRoutes = observed;
1257
+ for (const nodeRoutingId of selected) this.notifyPeerRouteChanged(nodeRoutingId);
1004
1258
  // A new selected route admits only through a new handshake.
1005
1259
  for (const nodeRoutingId of selected) await this.announcePeer(nodeRoutingId);
1006
1260
  return changes;
@@ -1008,9 +1262,15 @@ export class RawServiceMeshRuntime {
1008
1262
 
1009
1263
  close(): void {
1010
1264
  if (this.closed) return;
1265
+ this.closed = true;
1011
1266
  this.applicationJobStop.abort(
1012
1267
  new Error('Raw service runtime application job admission stopped.')
1013
1268
  );
1269
+ this.receivePermit?.releaseAfterInternalProcessing();
1270
+ this.receivePermit?.releaseStorage?.();
1271
+ this.receivePermit = undefined;
1272
+ for (const send of this.controlSends.values()) delete send.next;
1273
+ this.controlSends.clear();
1014
1274
  this.mailbox.close();
1015
1275
  this.operations.close('Raw service runtime closed.');
1016
1276
  this.serviceIngress = undefined;
@@ -1022,14 +1282,14 @@ export class RawServiceMeshRuntime {
1022
1282
  if (host !== undefined) host.close();
1023
1283
  this.router = undefined;
1024
1284
  this.host = undefined;
1025
- this.closed = true;
1026
1285
  }
1027
1286
 
1028
1287
  private requestToTarget(
1029
1288
  targetNodeRoutingId: string,
1030
1289
  payload: ServiceApplicationPayloadInput,
1031
1290
  timeoutMs: number,
1032
- channelName?: string
1291
+ channelName?: string,
1292
+ onAdmitted?: () => void
1033
1293
  ): PendingOperation<RawServiceRequestResult> {
1034
1294
  const correlation = this.nextCorrelation++;
1035
1295
  const header =
@@ -1100,6 +1360,8 @@ export class RawServiceMeshRuntime {
1100
1360
  terminalResult: RequestResult.NotConnected,
1101
1361
  failureCode: 0
1102
1362
  });
1363
+ } else {
1364
+ onAdmitted?.();
1103
1365
  }
1104
1366
  } finally {
1105
1367
  applicationJobOwner.close();
@@ -1107,7 +1369,8 @@ export class RawServiceMeshRuntime {
1107
1369
  })().catch((error) => this.operations.fail(pending.id, error));
1108
1370
  return pending;
1109
1371
  }
1110
- if (this.topology.peer(selectedTargetNodeRoutingId) === undefined) {
1372
+ const selectedPeer = this.topology.admittedPeer(selectedTargetNodeRoutingId);
1373
+ if (selectedPeer === undefined) {
1111
1374
  // Logical target selection is the requester's decision: an RID that is
1112
1375
  // not an admitted peer is not a target. Whether an admitted target's
1113
1376
  // route can carry the request is Core's REQUEST result.
@@ -1118,11 +1381,15 @@ export class RawServiceMeshRuntime {
1118
1381
  return pending;
1119
1382
  }
1120
1383
  const parts = [header, encodedPayload];
1121
- let router: ZLinkRawRouterPort;
1122
1384
  let request: Promise<readonly Uint8Array[]>;
1123
1385
  try {
1124
- router = this.requireStarted();
1125
- request = router.request(selectedTargetNodeRoutingId, parts, timeoutMs);
1386
+ request = this.requireStarted().request(
1387
+ selectedTargetNodeRoutingId,
1388
+ parts,
1389
+ timeoutMs,
1390
+ selectedPeer.liveness?.recordReceived,
1391
+ onAdmitted
1392
+ );
1126
1393
  } catch (error) {
1127
1394
  this.operations.fail(pending.id, error);
1128
1395
  return pending;
@@ -1182,33 +1449,43 @@ export class RawServiceMeshRuntime {
1182
1449
 
1183
1450
  private admitPeer(
1184
1451
  descriptor: ServiceNodeDescriptor,
1185
- connectionId: string,
1452
+ connectionId: string | bigint,
1186
1453
  nowMs: number,
1187
1454
  expected?: {
1188
1455
  readonly endpoint?: string;
1189
1456
  readonly securityIdentity?: string;
1190
1457
  readonly lifecycleGeneration?: bigint;
1191
- }
1458
+ },
1459
+ command: number = M6aServiceWireCommand.hello,
1460
+ descriptorBytes?: Uint8Array
1192
1461
  ): PeerAdmissionResult {
1193
1462
  const previous = this.topology.peer(descriptor.nodeRoutingId);
1194
- if (
1195
- previous !== undefined &&
1196
- previous.descriptor.lifecycleGeneration === descriptor.lifecycleGeneration &&
1197
- (descriptor.descriptorRevision < previous.descriptor.descriptorRevision ||
1198
- (descriptor.descriptorRevision === previous.descriptor.descriptorRevision &&
1199
- !sameServiceNodeDescriptor(previous.descriptor, descriptor)))
1200
- ) {
1463
+ const topologyConnectionId =
1464
+ typeof connectionId === 'bigint' ? routeConnectionId(connectionId) : connectionId;
1465
+ const result = this.topology.admit(
1466
+ descriptor,
1467
+ topologyConnectionId,
1468
+ expected,
1469
+ () => {
1470
+ const admission = this.liveness.admit(descriptor.nodeRoutingId, connectionId, nowMs);
1471
+ this.liveness.recordReceived(admission, nowMs);
1472
+ return admission;
1473
+ },
1474
+ command,
1475
+ descriptorBytes
1476
+ );
1477
+ if (result === 'staleDescriptor') {
1201
1478
  throw new ServiceWireProtocolError(
1202
1479
  `RouteMesh peer '${descriptor.nodeRoutingId}' descriptor revision is stale or conflicting.`
1203
1480
  );
1204
1481
  }
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);
1482
+ if (result === 'notRequired' && previous !== undefined) {
1483
+ this.liveness.disconnect(
1484
+ descriptor.nodeRoutingId,
1485
+ previous.liveness?.connectionId ?? previous.connectionId
1486
+ );
1211
1487
  }
1488
+ if (result === 'admitted') this.notifyPeerRouteChanged(descriptor.nodeRoutingId);
1212
1489
  return result;
1213
1490
  }
1214
1491
 
@@ -1244,7 +1521,10 @@ export class RawServiceMeshRuntime {
1244
1521
 
1245
1522
  private removePeer(peer: AdmittedServicePeer): void {
1246
1523
  this.topology.disconnect(peer.descriptor.nodeRoutingId, peer.connectionId);
1247
- this.liveness.disconnect(peer.descriptor.nodeRoutingId, peer.connectionId);
1524
+ this.liveness.disconnect(
1525
+ peer.descriptor.nodeRoutingId,
1526
+ peer.liveness?.connectionId ?? peer.connectionId
1527
+ );
1248
1528
  this.onPeerDisconnected?.(
1249
1529
  peer.descriptor.nodeRoutingId,
1250
1530
  peer.descriptor.advertisedEndpoint,
@@ -1257,19 +1537,136 @@ export class RawServiceMeshRuntime {
1257
1537
  return this.router;
1258
1538
  }
1259
1539
 
1260
- private async send(targetNodeRoutingId: string, parts: readonly Uint8Array[]): Promise<boolean> {
1540
+ private async sendControl(
1541
+ targetNodeRoutingId: string,
1542
+ parts: readonly Uint8Array[]
1543
+ ): Promise<boolean> {
1544
+ return this.startControlSend(targetNodeRoutingId, decodeHeader(parts[0]!).command, () =>
1545
+ this.submitControl(targetNodeRoutingId, parts)
1546
+ );
1547
+ }
1548
+
1549
+ private submitControl(target: string, parts: readonly Uint8Array[]): boolean | Promise<boolean> {
1550
+ let submission;
1261
1551
  try {
1262
- await this.requireStarted().send(targetNodeRoutingId, parts);
1263
- return true;
1552
+ submission = this.requireStarted().submitSend(target, parts);
1264
1553
  } 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;
1554
+ return this.sendFailure(error);
1555
+ }
1556
+ if (submission.result === SubmitResult.Ok) return true;
1557
+ return submission.admitted.then(
1558
+ () => true,
1559
+ (error) => this.sendFailure(error)
1560
+ );
1561
+ }
1562
+
1563
+ private startControlSend(
1564
+ target: string,
1565
+ command: number,
1566
+ attempt: () => boolean | Promise<boolean>,
1567
+ current?: () => boolean,
1568
+ hello?: Uint8Array
1569
+ ): boolean {
1570
+ const key = controlSendKey(target, command);
1571
+ const existing = this.controlSends.get(key);
1572
+ if (
1573
+ existing !== undefined &&
1574
+ existing.task !== undefined &&
1575
+ (command !== M6aServiceWireCommand.hello || existing.current?.() === true)
1576
+ ) {
1577
+ if (command !== M6aServiceWireCommand.hello) {
1578
+ existing.next = attempt;
1579
+ existing.current = current;
1269
1580
  }
1270
- throw error;
1581
+ return true;
1582
+ }
1583
+ const first = attempt();
1584
+ if (typeof first === 'boolean') {
1585
+ if (hello !== undefined) this.controlSends.set(key, { current, hello });
1586
+ return first;
1587
+ }
1588
+ const owner = this;
1589
+ const state: ControlSendState = { current, hello };
1590
+ const completeSend = () => {
1591
+ if (owner.controlSends.get(key) !== state) return;
1592
+ if (hello !== undefined && current?.() === true) delete state.task;
1593
+ else owner.controlSends.delete(key);
1594
+ };
1595
+ const task = first.then(function drain(accepted): boolean | Promise<boolean> {
1596
+ if (!owner.closed && state.next !== undefined) {
1597
+ const next = state.next;
1598
+ delete state.next;
1599
+ return Promise.resolve(next()).then(drain);
1600
+ }
1601
+ completeSend();
1602
+ return accepted;
1603
+ });
1604
+ state.task = task;
1605
+ this.controlSends.set(key, state);
1606
+ task.then(
1607
+ () => {},
1608
+ (error) => {
1609
+ completeSend();
1610
+ if (this.infrastructureTaskRunner === undefined) this.reportInfrastructureFailure(error);
1611
+ }
1612
+ );
1613
+ this.infrastructureTaskRunner?.runDetached('RouteMesh control SEND', async () => {
1614
+ await task;
1615
+ });
1616
+ return true;
1617
+ }
1618
+
1619
+ private reportInfrastructureFailure(error: unknown): void {
1620
+ this.infrastructureFailure = {
1621
+ error:
1622
+ this.infrastructureFailure === undefined
1623
+ ? error
1624
+ : new AggregateError(
1625
+ [this.infrastructureFailure.error, error],
1626
+ 'RouteMesh infrastructure failed.'
1627
+ )
1628
+ };
1629
+ this.onReceiveReady?.();
1630
+ }
1631
+
1632
+ private observeInfrastructureFailure(): void {
1633
+ const failure = this.infrastructureFailure;
1634
+ this.infrastructureFailure = undefined;
1635
+ if (failure !== undefined) throw failure.error;
1636
+ }
1637
+
1638
+ private send(
1639
+ targetNodeRoutingId: string,
1640
+ parts: readonly Uint8Array[]
1641
+ ): boolean | Promise<boolean> {
1642
+ try {
1643
+ const submission = this.requireStarted().submitSend(targetNodeRoutingId, parts);
1644
+ if (submission.result === SubmitResult.Ok) return true;
1645
+ return submission.admitted.then(
1646
+ () => true,
1647
+ (error) => this.sendFailure(error)
1648
+ );
1649
+ } catch (error) {
1650
+ return this.sendFailure(error);
1271
1651
  }
1272
1652
  }
1653
+
1654
+ private sendFailure(error: unknown): false {
1655
+ if (isZLinkBackendResultError(error) && error.operation === 'submit') {
1656
+ const terminal = submitToRequestResult(error.result, error.phase);
1657
+ if (terminal === RequestResult.NotConnected || terminal === RequestResult.NotFound)
1658
+ return false;
1659
+ }
1660
+ throw error;
1661
+ }
1662
+ }
1663
+
1664
+ function controlSendKey(target: string, command: number): string {
1665
+ return JSON.stringify([target, command]);
1666
+ }
1667
+
1668
+ function routeConnectionId(generation: bigint): string {
1669
+ return `route:${generation}`;
1273
1670
  }
1274
1671
 
1275
1672
  /**
@@ -1309,14 +1706,6 @@ function isAlreadyDisconnectedError(error: unknown): boolean {
1309
1706
  return (error as { readonly nativeErrno?: unknown }).nativeErrno === nativeErrnoValues.ENOENT;
1310
1707
  }
1311
1708
 
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
1709
  function describeInvalidAdmissionDescriptor(
1321
1710
  descriptor: ServiceNodeDescriptor,
1322
1711
  expected: ServicePeerAdmissionExpectation | undefined,