@zlink-systems/framework 0.30.3 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (398) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Actors/ZLinkActor.d.ts +2 -0
  3. package/dist/contracts/Actors/ZLinkSessionBindingIdentity.d.ts +6 -0
  4. package/dist/contracts/Actors/ZLinkSessionBindingIdentity.js +2 -0
  5. package/dist/contracts/Actors/index.d.ts +1 -0
  6. package/dist/contracts/Actors/index.js +1 -0
  7. package/dist/contracts/Common/CoreTypes.d.ts +1 -0
  8. package/dist/contracts/Common/HandlerMessageType.d.ts +8 -0
  9. package/dist/contracts/Common/HandlerMessageType.js +41 -0
  10. package/dist/contracts/Common/ZLinkEncodedPayload.d.ts +1 -1
  11. package/dist/contracts/Common/ZLinkEncodedPayload.js +12 -20
  12. package/dist/contracts/Common/ZLinkMessage.js +11 -7
  13. package/dist/contracts/Common/encoded-payload-storage.d.ts +0 -2
  14. package/dist/contracts/Common/encoded-payload-storage.js +4 -16
  15. package/dist/contracts/Common/index.d.ts +1 -1
  16. package/dist/contracts/Common/payload-slab.d.ts +4 -0
  17. package/dist/contracts/Common/payload-slab.js +30 -0
  18. package/dist/contracts/Configuration/RegistrationBuilders.js +15 -9
  19. package/dist/contracts/Configuration/RegistrationTypes.d.ts +19 -0
  20. package/dist/contracts/Handlers/Contexts.d.ts +1 -0
  21. package/dist/contracts/Locations/Authority.d.ts +2 -0
  22. package/dist/internal.d.ts +9 -0
  23. package/dist/internal.js +23 -1
  24. package/dist/nest-integration.d.ts +1 -0
  25. package/dist/nest-integration.js +3 -1
  26. package/dist/runtime/actors/actor-client.d.ts +6 -2
  27. package/dist/runtime/actors/actor-client.js +120 -82
  28. package/dist/runtime/actors/actor-context.js +16 -5
  29. package/dist/runtime/actors/actor-execution-context.js +2 -2
  30. package/dist/runtime/actors/actor-handoff.d.ts +12 -1
  31. package/dist/runtime/actors/actor-handoff.js +79 -34
  32. package/dist/runtime/actors/actor-join-deferred-scope.js +6 -6
  33. package/dist/runtime/actors/actor-local-native-join.d.ts +2 -2
  34. package/dist/runtime/actors/actor-local-native-join.js +19 -17
  35. package/dist/runtime/actors/actor-mailbox.d.ts +1 -1
  36. package/dist/runtime/actors/actor-remote-joiner.d.ts +2 -2
  37. package/dist/runtime/actors/actor-remote-joiner.js +4 -4
  38. package/dist/runtime/actors/actor-routed-join-transport.d.ts +9 -3
  39. package/dist/runtime/actors/actor-routed-json-request.d.ts +3 -0
  40. package/dist/runtime/actors/actor-runtime-contracts.d.ts +2 -2
  41. package/dist/runtime/actors/actor-runtime-state.d.ts +1 -1
  42. package/dist/runtime/actors/spot-actor-dispatch.d.ts +9 -5
  43. package/dist/runtime/actors/spot-actor-dispatch.js +60 -38
  44. package/dist/runtime/admission.d.ts +6 -4
  45. package/dist/runtime/admission.js +21 -10
  46. package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +1 -0
  47. package/dist/runtime/application-jobs/application-ingress-record-owner.js +3 -0
  48. package/dist/runtime/backend/contracts/index.d.ts +26 -10
  49. package/dist/runtime/backend/mesh-completion-table.d.ts +7 -1
  50. package/dist/runtime/backend/mesh-completion-table.js +84 -20
  51. package/dist/runtime/backend/mesh-dispatch-pump.d.ts +19 -6
  52. package/dist/runtime/backend/mesh-dispatch-pump.js +246 -147
  53. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +3 -2
  54. package/dist/runtime/backend/node/node-backend-adapter-support.js +27 -19
  55. package/dist/runtime/backend/node/node-dealer-channel-client-transport.d.ts +5 -2
  56. package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +70 -13
  57. package/dist/runtime/backend/node/node-event-loop-poller.d.ts +2 -2
  58. package/dist/runtime/backend/node/node-event-loop-poller.js +4 -3
  59. package/dist/runtime/backend/node/node-mesh-backend-adapter.js +5 -1
  60. package/dist/runtime/backend/node/node-raw-binding-port.d.ts +7 -2
  61. package/dist/runtime/backend/node/node-raw-binding-port.js +99 -63
  62. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +34 -16
  63. package/dist/runtime/backend/node/node-raw-mesh-backend.js +210 -106
  64. package/dist/runtime/backend/node/node-socket-backend-adapter.js +20 -6
  65. package/dist/runtime/backend/raw-binding-port.d.ts +6 -3
  66. package/dist/runtime/channels/channel-clients.js +3 -3
  67. package/dist/runtime/channels/channel-dispatch-pipeline.d.ts +11 -7
  68. package/dist/runtime/channels/channel-dispatch-pipeline.js +40 -22
  69. package/dist/runtime/channels/channel-dispatch-services.d.ts +3 -1
  70. package/dist/runtime/channels/channel-dispatch-services.js +7 -6
  71. package/dist/runtime/channels/channel-dispatchers.d.ts +13 -6
  72. package/dist/runtime/channels/channel-dispatchers.js +57 -36
  73. package/dist/runtime/channels/channel-envelope.d.ts +2 -2
  74. package/dist/runtime/channels/channel-envelope.js +609 -83
  75. package/dist/runtime/channels/channel-multipart.d.ts +1 -0
  76. package/dist/runtime/channels/channel-multipart.js +10 -6
  77. package/dist/runtime/channels/channel-outbound-operations.d.ts +6 -9
  78. package/dist/runtime/channels/channel-outbound-operations.js +107 -105
  79. package/dist/runtime/channels/channel-receive-loops.d.ts +9 -7
  80. package/dist/runtime/channels/channel-receive-loops.js +126 -90
  81. package/dist/runtime/channels/channel-runtime-lifecycle.d.ts +23 -6
  82. package/dist/runtime/channels/channel-runtime-lifecycle.js +47 -38
  83. package/dist/runtime/channels/channel-runtime-manager.d.ts +13 -6
  84. package/dist/runtime/channels/channel-runtime-manager.js +32 -14
  85. package/dist/runtime/channels/channel-socket-registry.d.ts +28 -4
  86. package/dist/runtime/channels/channel-socket-registry.js +9 -1
  87. package/dist/runtime/channels/channel-transports.d.ts +23 -16
  88. package/dist/runtime/channels/channel-transports.js +139 -119
  89. package/dist/runtime/channels/dispatch-error-reporter.d.ts +4 -4
  90. package/dist/runtime/channels/dispatch-error-reporter.js +5 -5
  91. package/dist/runtime/channels/fanout-location-runtime.d.ts +1 -1
  92. package/dist/runtime/channels/local-spot-route-transport.d.ts +5 -2
  93. package/dist/runtime/channels/local-spot-route-transport.js +15 -18
  94. package/dist/runtime/channels/router-socket-spot-route-transport.d.ts +6 -3
  95. package/dist/runtime/channels/router-socket-spot-route-transport.js +33 -19
  96. package/dist/runtime/channels/source-spot-router.d.ts +6 -3
  97. package/dist/runtime/channels/source-spot-router.js +34 -23
  98. package/dist/runtime/channels/spot-node-route-transport.d.ts +6 -3
  99. package/dist/runtime/channels/spot-node-route-transport.js +23 -17
  100. package/dist/runtime/channels/spot-route-bridge-transport.d.ts +6 -3
  101. package/dist/runtime/channels/spot-route-bridge-transport.js +38 -24
  102. package/dist/runtime/channels/spot-route-dispatch-strategy.d.ts +8 -4
  103. package/dist/runtime/channels/spot-route-dispatch-strategy.js +36 -23
  104. package/dist/runtime/diagnostics/dispatch-error-details.js +9 -3
  105. package/dist/runtime/diagnostics/message-flow-bootstrap.d.ts +1 -0
  106. package/dist/runtime/diagnostics/message-flow-bootstrap.js +8 -0
  107. package/dist/runtime/diagnostics/message-flow-delivery.d.ts +47 -0
  108. package/dist/runtime/diagnostics/message-flow-delivery.js +149 -0
  109. package/dist/runtime/diagnostics/message-flow-record.d.ts +50 -0
  110. package/dist/runtime/diagnostics/message-flow-record.js +228 -0
  111. package/dist/runtime/diagnostics/message-flow-worker.d.ts +1 -0
  112. package/dist/runtime/diagnostics/message-flow-worker.js +118 -0
  113. package/dist/runtime/diagnostics/message-flow.d.ts +7 -19
  114. package/dist/runtime/diagnostics/message-flow.js +42 -290
  115. package/dist/runtime/diagnostics/runtime-metrics.d.ts +4 -3
  116. package/dist/runtime/diagnostics/runtime-metrics.js +8 -4
  117. package/dist/runtime/execution/index.d.ts +1 -0
  118. package/dist/runtime/execution/index.js +6 -3
  119. package/dist/runtime/execution/serial-execution-queue.d.ts +26 -13
  120. package/dist/runtime/execution/serial-execution-queue.js +75 -71
  121. package/dist/runtime/foundation/durable-service-request.d.ts +12 -3
  122. package/dist/runtime/foundation/durable-service-request.js +158 -73
  123. package/dist/runtime/foundation/operation-registry.d.ts +33 -3
  124. package/dist/runtime/foundation/operation-registry.js +272 -33
  125. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +62 -23
  126. package/dist/runtime/foundation/raw-service-mesh-runtime.js +373 -194
  127. package/dist/runtime/foundation/request-runtime.d.ts +17 -0
  128. package/dist/runtime/foundation/request-runtime.js +163 -0
  129. package/dist/runtime/foundation/service-discovery-registry.d.ts +4 -0
  130. package/dist/runtime/foundation/service-discovery-registry.js +6 -0
  131. package/dist/runtime/foundation/service-liveness-registry.d.ts +1 -1
  132. package/dist/runtime/foundation/service-mailbox.d.ts +4 -2
  133. package/dist/runtime/foundation/service-mailbox.js +4 -1
  134. package/dist/runtime/foundation/service-relocation-object-owner.d.ts +2 -2
  135. package/dist/runtime/foundation/service-relocation-object-owner.js +12 -5
  136. package/dist/runtime/foundation/service-runtime-contracts.d.ts +7 -2
  137. package/dist/runtime/foundation/service-stateful-registry.d.ts +6 -2
  138. package/dist/runtime/foundation/service-stateful-registry.js +38 -17
  139. package/dist/runtime/foundation/service-stateful-runtime.d.ts +91 -26
  140. package/dist/runtime/foundation/service-stateful-runtime.js +245 -120
  141. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +3 -0
  142. package/dist/runtime/foundation/service-stateful-wire-codec.js +15 -0
  143. package/dist/runtime/foundation/service-topology-registry.d.ts +2 -0
  144. package/dist/runtime/foundation/service-topology-registry.js +13 -3
  145. package/dist/runtime/foundation/service-wire-constants.generated.d.ts +1 -0
  146. package/dist/runtime/foundation/service-wire-constants.generated.js +12 -0
  147. package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +5 -1
  148. package/dist/runtime/foundation/service-wire-m6a-codec.js +50 -15
  149. package/dist/runtime/host/actor-packet-relay.d.ts +5 -6
  150. package/dist/runtime/host/actor-packet-relay.js +58 -57
  151. package/dist/runtime/host/actor-placement-coordinator.d.ts +1 -1
  152. package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -0
  153. package/dist/runtime/host/actor-runtime-options-factory.js +3 -2
  154. package/dist/runtime/host/actor-transfer-runtime.d.ts +3 -3
  155. package/dist/runtime/host/application-job-queue.d.ts +7 -5
  156. package/dist/runtime/host/application-job-queue.js +33 -21
  157. package/dist/runtime/host/bound-session-relay.js +0 -1
  158. package/dist/runtime/host/channel-runtime-options-factory.d.ts +2 -0
  159. package/dist/runtime/host/channel-runtime-options-factory.js +1 -0
  160. package/dist/runtime/host/entry-actor-runtime.d.ts +5 -2
  161. package/dist/runtime/host/entry-actor-runtime.js +2 -2
  162. package/dist/runtime/host/index.d.ts +106 -37
  163. package/dist/runtime/host/index.js +303 -151
  164. package/dist/runtime/host/location-runtime-owner.d.ts +2 -2
  165. package/dist/runtime/host/location-runtime-owner.js +1 -1
  166. package/dist/runtime/host/service-relocation-host-runtime.d.ts +195 -45
  167. package/dist/runtime/host/service-relocation-host-runtime.js +257 -144
  168. package/dist/runtime/host/spot-address-transport.d.ts +3 -0
  169. package/dist/runtime/host/spot-address-transport.js +73 -76
  170. package/dist/runtime/host/spot-runtime-options-factory.js +2 -0
  171. package/dist/runtime/host/user-spot-creation-coordinator.d.ts +3 -1
  172. package/dist/runtime/host/user-spot-creation-coordinator.js +49 -21
  173. package/dist/runtime/internal-observation.d.ts +1 -0
  174. package/dist/runtime/internal-observation.js +5 -0
  175. package/dist/runtime/locations/actor-location-claims.d.ts +1 -1
  176. package/dist/runtime/locations/actor-location-claims.js +15 -6
  177. package/dist/runtime/locations/auto-connect-loop.d.ts +0 -1
  178. package/dist/runtime/locations/auto-connect-loop.js +25 -19
  179. package/dist/runtime/locations/auto-connect-planner.js +2 -5
  180. package/dist/runtime/locations/auto-connect-reconciler.d.ts +1 -0
  181. package/dist/runtime/locations/auto-connect-reconciler.js +35 -23
  182. package/dist/runtime/locations/in-memory-authority-store.d.ts +1 -0
  183. package/dist/runtime/locations/in-memory-authority-store.js +15 -5
  184. package/dist/runtime/locations/in-memory-location-store.d.ts +0 -2
  185. package/dist/runtime/locations/in-memory-location-store.js +1 -38
  186. package/dist/runtime/locations/lifecycle.d.ts +1 -1
  187. package/dist/runtime/locations/lifecycle.js +2 -2
  188. package/dist/runtime/locations/location-store-repository.d.ts +0 -1
  189. package/dist/runtime/locations/location-store-repository.js +163 -246
  190. package/dist/runtime/locations/resolvers.d.ts +8 -1
  191. package/dist/runtime/locations/resolvers.js +16 -2
  192. package/dist/runtime/locations/runtime.d.ts +3 -3
  193. package/dist/runtime/locations/runtime.js +12 -16
  194. package/dist/runtime/messaging/creation-payload-codec.js +8 -7
  195. package/dist/runtime/messaging/framework-json-v1.d.ts +1 -1
  196. package/dist/runtime/messaging/framework-json-v1.js +6 -2
  197. package/dist/runtime/messaging/payload-codec.js +21 -35
  198. package/dist/runtime/messaging/request-deadline.d.ts +4 -2
  199. package/dist/runtime/messaging/request-deadline.js +40 -25
  200. package/dist/runtime/messaging/request-result.d.ts +14 -1
  201. package/dist/runtime/messaging/request-result.js +74 -13
  202. package/dist/runtime/protocol/service_wire_codec.generated.d.ts +4 -0
  203. package/dist/runtime/protocol/service_wire_codec.generated.js +17 -7
  204. package/dist/runtime/spots/index.d.ts +28 -7
  205. package/dist/runtime/spots/index.js +31 -6
  206. package/dist/runtime/spots/spot-activation-registry.d.ts +3 -2
  207. package/dist/runtime/spots/spot-activation-registry.js +13 -8
  208. package/dist/runtime/spots/spot-activation-state.d.ts +2 -0
  209. package/dist/runtime/spots/spot-activation-state.js +1 -0
  210. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +9 -4
  211. package/dist/runtime/spots/spot-actor-admission-coordinator.js +33 -12
  212. package/dist/runtime/spots/spot-actor-ingress-admission.d.ts +40 -0
  213. package/dist/runtime/spots/spot-actor-ingress-admission.js +78 -0
  214. package/dist/runtime/spots/spot-actor-membership.js +3 -3
  215. package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +16 -43
  216. package/dist/runtime/spots/spot-actor-packet-dispatch.js +56 -113
  217. package/dist/runtime/spots/spot-actor-packet-drain.d.ts +1 -0
  218. package/dist/runtime/spots/spot-actor-packet-drain.js +12 -5
  219. package/dist/runtime/spots/spot-entry-activation.d.ts +4 -3
  220. package/dist/runtime/spots/spot-entry-activation.js +7 -12
  221. package/dist/runtime/spots/spot-handler-registry.d.ts +5 -1
  222. package/dist/runtime/spots/spot-handler-registry.js +31 -15
  223. package/dist/runtime/spots/spot-manager-public.js +3 -2
  224. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +11 -6
  225. package/dist/runtime/spots/spot-node-runtime-manager.js +6 -1
  226. package/dist/runtime/spots/spot-outbound.d.ts +8 -2
  227. package/dist/runtime/spots/spot-outbound.js +5 -5
  228. package/dist/runtime/spots/spot-route-packet-dispatch.js +13 -8
  229. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.d.ts +3 -3
  230. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.js +9 -5
  231. package/dist/runtime/spots/spot-runtime-ports.d.ts +2 -2
  232. package/dist/runtime/spots/spot-serial-executor.d.ts +2 -1
  233. package/dist/runtime/spots/spot-serial-turn-executor.js +20 -26
  234. package/dist/runtime/spots/spot-subscription-dispatch.js +5 -2
  235. package/dist/runtime/spots/spot-timer.d.ts +1 -1
  236. package/dist/runtime/streams/actor-session-binding-registry.d.ts +11 -3
  237. package/dist/runtime/streams/actor-session-binding-registry.js +28 -0
  238. package/dist/runtime/streams/bound-actor-relay-sender.d.ts +2 -2
  239. package/dist/runtime/streams/bound-actor-relay-sender.js +3 -4
  240. package/dist/runtime/streams/bound-session-service.js +6 -7
  241. package/dist/runtime/streams/index.d.ts +3 -3
  242. package/dist/runtime/streams/index.js +22 -2
  243. package/dist/runtime/streams/managed-stream.js +2 -1
  244. package/dist/runtime/streams/session-calls.d.ts +1 -1
  245. package/dist/runtime/streams/session-calls.js +3 -9
  246. package/dist/runtime/streams/session-context.d.ts +2 -2
  247. package/dist/runtime/streams/session-context.js +2 -2
  248. package/dist/runtime/streams/stream-session-runtime.d.ts +1 -0
  249. package/dist/runtime/streams/stream-session-runtime.js +64 -33
  250. package/package.json +3 -3
  251. package/src/contracts/Actors/ZLinkActor.ts +6 -0
  252. package/src/contracts/Actors/ZLinkSessionBindingIdentity.ts +7 -0
  253. package/src/contracts/Actors/index.ts +1 -0
  254. package/src/contracts/Common/CoreTypes.ts +1 -0
  255. package/src/contracts/Common/HandlerMessageType.ts +50 -0
  256. package/src/contracts/Common/ZLinkEncodedPayload.ts +13 -25
  257. package/src/contracts/Common/ZLinkMessage.ts +11 -7
  258. package/src/contracts/Common/encoded-payload-storage.ts +4 -16
  259. package/src/contracts/Common/index.ts +1 -1
  260. package/src/contracts/Common/payload-slab.ts +29 -0
  261. package/src/contracts/Configuration/RegistrationBuilders.ts +29 -15
  262. package/src/contracts/Configuration/RegistrationTypes.ts +19 -0
  263. package/src/contracts/Handlers/Contexts.ts +1 -0
  264. package/src/contracts/Locations/Authority.ts +7 -1
  265. package/src/internal.ts +17 -0
  266. package/src/nest-integration.ts +1 -0
  267. package/src/runtime/actors/actor-client.ts +257 -163
  268. package/src/runtime/actors/actor-context.ts +26 -14
  269. package/src/runtime/actors/actor-execution-context.ts +2 -2
  270. package/src/runtime/actors/actor-handoff.ts +86 -51
  271. package/src/runtime/actors/actor-join-deferred-scope.ts +6 -6
  272. package/src/runtime/actors/actor-local-native-join.ts +38 -17
  273. package/src/runtime/actors/actor-mailbox.ts +1 -1
  274. package/src/runtime/actors/actor-remote-joiner.ts +8 -4
  275. package/src/runtime/actors/actor-routed-join-transport.ts +13 -5
  276. package/src/runtime/actors/actor-routed-json-request.ts +3 -0
  277. package/src/runtime/actors/actor-runtime-contracts.ts +4 -2
  278. package/src/runtime/actors/actor-runtime-state.ts +1 -1
  279. package/src/runtime/actors/spot-actor-dispatch.ts +70 -68
  280. package/src/runtime/admission.ts +42 -15
  281. package/src/runtime/application-jobs/application-ingress-record-owner.ts +4 -0
  282. package/src/runtime/backend/contracts/index.ts +77 -10
  283. package/src/runtime/backend/mesh-completion-table.ts +106 -15
  284. package/src/runtime/backend/mesh-dispatch-pump.ts +294 -146
  285. package/src/runtime/backend/node/node-backend-adapter-support.ts +42 -32
  286. package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +91 -34
  287. package/src/runtime/backend/node/node-event-loop-poller.ts +14 -5
  288. package/src/runtime/backend/node/node-mesh-backend-adapter.ts +5 -1
  289. package/src/runtime/backend/node/node-raw-binding-port.ts +180 -69
  290. package/src/runtime/backend/node/node-raw-mesh-backend.ts +386 -157
  291. package/src/runtime/backend/node/node-socket-backend-adapter.ts +56 -11
  292. package/src/runtime/backend/raw-binding-port.ts +13 -5
  293. package/src/runtime/channels/channel-clients.ts +26 -11
  294. package/src/runtime/channels/channel-dispatch-pipeline.ts +77 -65
  295. package/src/runtime/channels/channel-dispatch-services.ts +7 -12
  296. package/src/runtime/channels/channel-dispatchers.ts +93 -39
  297. package/src/runtime/channels/channel-envelope.ts +680 -144
  298. package/src/runtime/channels/channel-multipart.ts +17 -13
  299. package/src/runtime/channels/channel-outbound-operations.ts +204 -178
  300. package/src/runtime/channels/channel-receive-loops.ts +160 -107
  301. package/src/runtime/channels/channel-runtime-lifecycle.ts +64 -59
  302. package/src/runtime/channels/channel-runtime-manager.ts +74 -27
  303. package/src/runtime/channels/channel-socket-registry.ts +20 -5
  304. package/src/runtime/channels/channel-transports.ts +414 -287
  305. package/src/runtime/channels/dispatch-error-reporter.ts +4 -9
  306. package/src/runtime/channels/local-spot-route-transport.ts +27 -29
  307. package/src/runtime/channels/router-socket-spot-route-transport.ts +63 -32
  308. package/src/runtime/channels/source-spot-router.ts +75 -38
  309. package/src/runtime/channels/spot-node-route-transport.ts +67 -37
  310. package/src/runtime/channels/spot-route-bridge-transport.ts +62 -30
  311. package/src/runtime/channels/spot-route-dispatch-strategy.ts +105 -34
  312. package/src/runtime/diagnostics/dispatch-error-details.ts +10 -2
  313. package/src/runtime/diagnostics/message-flow-bootstrap.ts +6 -0
  314. package/src/runtime/diagnostics/message-flow-delivery.ts +171 -0
  315. package/src/runtime/diagnostics/message-flow-record.ts +295 -0
  316. package/src/runtime/diagnostics/message-flow-worker.ts +146 -0
  317. package/src/runtime/diagnostics/message-flow.ts +49 -362
  318. package/src/runtime/diagnostics/runtime-metrics.ts +10 -4
  319. package/src/runtime/execution/index.ts +6 -0
  320. package/src/runtime/execution/serial-execution-queue.ts +94 -96
  321. package/src/runtime/foundation/durable-service-request.ts +212 -89
  322. package/src/runtime/foundation/operation-registry.ts +287 -38
  323. package/src/runtime/foundation/raw-service-mesh-runtime.ts +617 -292
  324. package/src/runtime/foundation/request-runtime.ts +227 -0
  325. package/src/runtime/foundation/service-discovery-registry.ts +7 -0
  326. package/src/runtime/foundation/service-liveness-registry.ts +1 -1
  327. package/src/runtime/foundation/service-mailbox.ts +7 -2
  328. package/src/runtime/foundation/service-relocation-object-owner.ts +21 -6
  329. package/src/runtime/foundation/service-runtime-contracts.ts +12 -2
  330. package/src/runtime/foundation/service-stateful-registry.ts +52 -19
  331. package/src/runtime/foundation/service-stateful-runtime.ts +365 -198
  332. package/src/runtime/foundation/service-stateful-wire-codec.ts +18 -0
  333. package/src/runtime/foundation/service-topology-registry.ts +21 -3
  334. package/src/runtime/foundation/service-wire-constants.generated.ts +10 -0
  335. package/src/runtime/foundation/service-wire-m6a-codec.ts +69 -17
  336. package/src/runtime/host/actor-packet-relay.ts +160 -156
  337. package/src/runtime/host/actor-placement-coordinator.ts +1 -1
  338. package/src/runtime/host/actor-runtime-options-factory.ts +7 -2
  339. package/src/runtime/host/actor-transfer-runtime.ts +3 -3
  340. package/src/runtime/host/application-job-queue.ts +56 -27
  341. package/src/runtime/host/bound-session-relay.ts +0 -1
  342. package/src/runtime/host/channel-runtime-options-factory.ts +3 -0
  343. package/src/runtime/host/entry-actor-runtime.ts +14 -6
  344. package/src/runtime/host/index.ts +388 -113
  345. package/src/runtime/host/location-runtime-owner.ts +3 -3
  346. package/src/runtime/host/service-relocation-host-runtime.ts +380 -252
  347. package/src/runtime/host/spot-address-transport.ts +110 -95
  348. package/src/runtime/host/spot-runtime-options-factory.ts +3 -0
  349. package/src/runtime/host/user-spot-creation-coordinator.ts +69 -22
  350. package/src/runtime/internal-observation.ts +2 -0
  351. package/src/runtime/locations/actor-location-claims.ts +42 -16
  352. package/src/runtime/locations/auto-connect-loop.ts +24 -16
  353. package/src/runtime/locations/auto-connect-planner.ts +2 -5
  354. package/src/runtime/locations/auto-connect-reconciler.ts +37 -24
  355. package/src/runtime/locations/in-memory-authority-store.ts +27 -7
  356. package/src/runtime/locations/in-memory-location-store.ts +1 -74
  357. package/src/runtime/locations/lifecycle.ts +4 -2
  358. package/src/runtime/locations/location-store-repository.ts +228 -308
  359. package/src/runtime/locations/resolvers.ts +20 -8
  360. package/src/runtime/locations/runtime.ts +16 -28
  361. package/src/runtime/messaging/creation-payload-codec.ts +12 -10
  362. package/src/runtime/messaging/framework-json-v1.ts +10 -1
  363. package/src/runtime/messaging/payload-codec.ts +50 -75
  364. package/src/runtime/messaging/request-deadline.ts +47 -39
  365. package/src/runtime/messaging/request-result.ts +88 -17
  366. package/src/runtime/protocol/service_wire_codec.generated.ts +18 -8
  367. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
  368. package/src/runtime/spots/index.ts +57 -10
  369. package/src/runtime/spots/spot-activation-registry.ts +12 -8
  370. package/src/runtime/spots/spot-activation-state.ts +2 -0
  371. package/src/runtime/spots/spot-actor-admission-coordinator.ts +62 -37
  372. package/src/runtime/spots/spot-actor-ingress-admission.ts +157 -0
  373. package/src/runtime/spots/spot-actor-membership.ts +8 -5
  374. package/src/runtime/spots/spot-actor-packet-dispatch.ts +122 -206
  375. package/src/runtime/spots/spot-actor-packet-drain.ts +21 -6
  376. package/src/runtime/spots/spot-entry-activation.ts +25 -35
  377. package/src/runtime/spots/spot-handler-registry.ts +72 -15
  378. package/src/runtime/spots/spot-manager-public.ts +4 -2
  379. package/src/runtime/spots/spot-node-runtime-manager.ts +16 -7
  380. package/src/runtime/spots/spot-outbound.ts +26 -10
  381. package/src/runtime/spots/spot-route-packet-dispatch.ts +18 -8
  382. package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +14 -8
  383. package/src/runtime/spots/spot-runtime-ports.ts +5 -2
  384. package/src/runtime/spots/spot-serial-executor.ts +1 -1
  385. package/src/runtime/spots/spot-serial-turn-executor.ts +25 -40
  386. package/src/runtime/spots/spot-subscription-dispatch.ts +9 -2
  387. package/src/runtime/spots/spot-timer.ts +1 -1
  388. package/src/runtime/streams/actor-session-binding-registry.ts +46 -3
  389. package/src/runtime/streams/bound-actor-relay-sender.ts +16 -5
  390. package/src/runtime/streams/bound-session-service.ts +10 -7
  391. package/src/runtime/streams/index.ts +30 -4
  392. package/src/runtime/streams/managed-stream.ts +2 -1
  393. package/src/runtime/streams/session-calls.ts +9 -14
  394. package/src/runtime/streams/session-context.ts +6 -11
  395. package/src/runtime/streams/stream-session-runtime.ts +79 -40
  396. package/dist/runtime/spots/spot-lifecycle-metrics.d.ts +0 -8
  397. package/dist/runtime/spots/spot-lifecycle-metrics.js +0 -17
  398. package/src/runtime/spots/spot-lifecycle-metrics.ts +0 -16
@@ -1,3 +1,13 @@
1
+ import {
2
+ createInternalFrameworkException,
3
+ ZLinkFrameworkInternalErrorKind
4
+ } from '../framework-errors-internal';
5
+ import type { ConnectionAttempt } from '@zlink-systems/zlink';
6
+ import { admitServiceRequest } from './durable-service-request';
7
+ import { remainingRequestTimeout, requestTerminalError } from '../messaging/request-deadline';
8
+ import type { OperationHandle } from './operation-registry';
9
+ import type { RequestRuntime } from './request-runtime';
10
+ import { inspectRuntime } from '../internal-observation';
1
11
  import { constants as osConstants } from 'node:os';
2
12
  import { ApplicationIngressRecordOwner } from '../application-jobs/application-ingress-record-owner';
3
13
  import type {
@@ -12,6 +22,7 @@ import type {
12
22
  ZLinkRawRouterPort
13
23
  } from '../backend/raw-binding-port';
14
24
  import { submitToRequestResult } from '../messaging/submission-result';
25
+ import { copyPayloadBytes } from '../../contracts/Common/payload-slab';
15
26
  import { RequestResult, SubmitResult, isZLinkBackendResultError } from '../backend/runtime-values';
16
27
  import { enumWireRejectReason } from '../protocol/service_wire_codec.generated';
17
28
  import { SERVICE_WIRE_PREFIX_SIZE } from './service-wire-binary-primitives';
@@ -36,9 +47,11 @@ import {
36
47
  SERVICE_WIRE_MAGIC,
37
48
  SERVICE_WIRE_MAJOR,
38
49
  ServiceWireCommand,
39
- ServiceWireFrameworkErrorCode
50
+ ServiceWireFrameworkErrorCode,
51
+ recoverRequestCorrelation
40
52
  } from './service-wire-constants.generated';
41
53
  import {
54
+ decodeApplicationPayload,
42
55
  decodeApplicationPayloadView,
43
56
  decodeChannelRequestHeader,
44
57
  decodeChannelSendHeader,
@@ -81,10 +94,7 @@ export type RawServiceRequestObserver = OperationObserver<RawServiceRequestResul
81
94
  * terminal reaches the observer. Local and Promise-contract requests carry
82
95
  * the Promise.
83
96
  */
84
- export interface RawServiceObservedRequest {
85
- readonly id: bigint;
86
- readonly promise?: Promise<RawServiceRequestResult>;
87
- }
97
+ export interface RawServiceObservedRequest extends OperationHandle<RawServiceRequestResult> {}
88
98
 
89
99
  type RawServiceChannelTargetSelection =
90
100
  | { readonly kind: 'selected'; readonly peer: AdmittedServicePeer }
@@ -112,7 +122,7 @@ export interface RawServiceIngressRecord {
112
122
  readonly sourceNodeGeneration?: bigint;
113
123
  readonly sourceRoute?: Uint8Array;
114
124
  readonly requestSequence?: bigint;
115
- readonly reply?: (parts: readonly Uint8Array[]) => void;
125
+ readonly reply?: ServiceMailboxRecord['reply'];
116
126
  readonly parts: readonly Buffer[];
117
127
  readonly applicationJobOwner?: ApplicationIngressRecordOwner;
118
128
  }
@@ -122,6 +132,7 @@ export type RawServiceIngressHandler = (
122
132
  ) => RawServicePumpResult | undefined | Promise<RawServicePumpResult | undefined>;
123
133
 
124
134
  export interface RawServiceMeshRuntimeOptions {
135
+ readonly requests?: RequestRuntime;
125
136
  readonly onReceiveReady?: () => void;
126
137
  readonly infrastructureTaskRunner?: import('../spots/spot-actor-join-dispatch').ZLinkDetachedTaskRunner;
127
138
  readonly onPendingOperationsChanged?: () => void;
@@ -166,6 +177,7 @@ const livenessCodec = createServiceWireCodec({
166
177
  interface ControlSendState {
167
178
  task?: Promise<boolean>;
168
179
  next?: () => boolean | Promise<boolean>;
180
+ // HELLO send eligibility; admission suspension does not end the exchange.
169
181
  current?: () => boolean;
170
182
  // Admission owns the first HELLO until ADMIT or route termination.
171
183
  hello?: Uint8Array;
@@ -176,12 +188,26 @@ interface ControlSendState {
176
188
  * It owns protocol, admission, mailbox, request and liveness state.
177
189
  */
178
190
  export class RawServiceMeshRuntime {
191
+ [inspectRuntime](nodeRoutingId?: string) {
192
+ return Object.freeze({
193
+ pendingControlSends: this.controlSends.size,
194
+ selectedRouteGeneration:
195
+ nodeRoutingId === undefined ? undefined : this.selectedRoutes.get(nodeRoutingId),
196
+ peerRouteObservers: this.peerRouteChanged.size,
197
+ peerConnectionIntentObservers: this.peerConnectionIntentRemoved.size,
198
+ expectedPeers: this.expectedPeers.size
199
+ });
200
+ }
201
+
179
202
  readonly topology: ServiceTopologyRegistry;
180
203
  readonly mailbox: ServiceMailbox;
181
204
  readonly liveness: ServiceLivenessRegistry;
182
205
 
206
+ private readonly requestOwner: RequestRuntime | undefined;
207
+ private readonly releaseOperations: () => void;
208
+
183
209
  private readonly operations: OperationRegistry<RawServiceRequestResult, number>;
184
- private readonly expectedPeers = new Map<
210
+ readonly expectedPeers = new Map<
185
211
  string,
186
212
  {
187
213
  readonly meshName: string;
@@ -198,7 +224,8 @@ export class RawServiceMeshRuntime {
198
224
  * The last observed Core selected route of each RID (Core ROUTER §10.1):
199
225
  * RID -> route generation. Only `observeSelectedRoutes` replaces it.
200
226
  */
201
- private selectedRoutes = new Map<string, bigint>();
227
+ selectedRoutes = new Map<string, bigint>();
228
+ private connectionAttempts: readonly ConnectionAttempt[] = [];
202
229
  private readonly bindingPort: ZLinkRawBindingPort;
203
230
  private readonly applicationJobQueue: ApplicationJobQueuePort;
204
231
  private readonly nodeMailboxOwner: string;
@@ -207,7 +234,7 @@ export class RawServiceMeshRuntime {
207
234
  private receiveAcquisition?: Promise<ApplicationJobPermitPort>;
208
235
  private receivePermit?: ApplicationJobPermitPort;
209
236
  private receiveRotation?: ApplicationJobReceiveRotationPort;
210
- private readonly controlSends = new Map<string, ControlSendState>();
237
+ readonly controlSends = new Map<string, ControlSendState>();
211
238
  private infrastructureFailure?: { readonly error: unknown };
212
239
  private readonly onReceiveReady?: () => void;
213
240
  private readonly infrastructureTaskRunner?: RawServiceMeshRuntimeOptions['infrastructureTaskRunner'];
@@ -218,16 +245,25 @@ export class RawServiceMeshRuntime {
218
245
  private readonly resolveAdvertisedEndpoint?: RawServiceMeshRuntimeOptions['resolveAdvertisedEndpoint'];
219
246
  private descriptor: ServiceNodeDescriptor;
220
247
  private host?: ZLinkRawHostPort;
221
- private router?: ZLinkRawRouterPort;
248
+ router?: ZLinkRawRouterPort;
222
249
  private nextCorrelation = 1n;
223
250
  private serviceIngress?: RawServiceIngressHandler;
224
251
  private serviceIngressAccepts?: (command: number) => boolean;
225
252
  private closed = false;
226
253
 
227
254
  constructor(options: RawServiceMeshRuntimeOptions) {
255
+ this.requestOwner = options.requests;
228
256
  this.onReceiveReady = options.onReceiveReady;
229
257
  this.infrastructureTaskRunner = options.infrastructureTaskRunner;
230
- this.operations = new OperationRegistry(undefined, options.onPendingOperationsChanged);
258
+ this.operations =
259
+ options.requests?.operations<RawServiceRequestResult, number>() ??
260
+ new OperationRegistry(undefined, options.onPendingOperationsChanged, (reason) =>
261
+ requestTerminalError('Request', reason)
262
+ );
263
+ this.releaseOperations =
264
+ options.requests === undefined
265
+ ? () => this.operations.close('Raw service runtime closed.')
266
+ : () => {};
231
267
  this.descriptor = options.descriptor;
232
268
  this.nodeMailboxOwner = `node:${options.descriptor.nodeRoutingId}`;
233
269
  this.topology = new ServiceTopologyRegistry(options.descriptor);
@@ -293,6 +329,7 @@ export class RawServiceMeshRuntime {
293
329
  lifecycleGeneration: expected.lifecycleGeneration
294
330
  })
295
331
  );
332
+ this.notifyPeerRouteChanged(expected.nodeRoutingId);
296
333
  }
297
334
 
298
335
  connectPeerByRoutingId(
@@ -312,6 +349,7 @@ export class RawServiceMeshRuntime {
312
349
  lifecycleGeneration
313
350
  })
314
351
  );
352
+ this.notifyPeerRouteChanged(nodeRoutingId);
315
353
  }
316
354
 
317
355
  expectPeerByRoutingId(
@@ -330,6 +368,7 @@ export class RawServiceMeshRuntime {
330
368
  lifecycleGeneration
331
369
  })
332
370
  );
371
+ this.notifyPeerRouteChanged(nodeRoutingId);
333
372
  }
334
373
 
335
374
  connectPeerEndpoint(endpoint: string): void {
@@ -414,13 +453,27 @@ export class RawServiceMeshRuntime {
414
453
  nodeRoutingId: string
415
454
  ): import('./durable-service-request').PeerRouteObservation {
416
455
  const selected = this.selectedRoutes.get(nodeRoutingId);
417
- const admitted = this.topology.admittedPeer(nodeRoutingId)?.liveness?.connectionId;
456
+ const peer = this.topology.admittedPeer(nodeRoutingId);
457
+ const expected = this.expectedPeers.get(nodeRoutingId);
458
+ const admitted = peer?.liveness?.connectionId;
418
459
  return {
419
460
  generation: selected ?? (typeof admitted === 'bigint' ? admitted : undefined),
420
- ready: admitted !== undefined && (selected === undefined || admitted === selected)
461
+ ready: peer?.remoteAdmitReceived === true,
462
+ selected: selected !== undefined,
463
+ lifecycleGeneration: expected?.lifecycleGeneration ?? peer?.descriptor.lifecycleGeneration,
464
+ endpoint: expected?.endpoint ?? peer?.descriptor.advertisedEndpoint,
465
+ // The immutable socket snapshot is borrowed; only waiting admission filters it.
466
+ connectionAttempts: this.connectionAttempts
421
467
  };
422
468
  }
423
469
 
470
+ peerLifecycleGeneration(nodeRoutingId: string): bigint | undefined {
471
+ return (
472
+ this.expectedPeers.get(nodeRoutingId)?.lifecycleGeneration ??
473
+ this.topology.admittedPeer(nodeRoutingId)?.descriptor.lifecycleGeneration
474
+ );
475
+ }
476
+
424
477
  async waitForPeerRoute(
425
478
  nodeRoutingId: string,
426
479
  lifecycleGeneration: bigint,
@@ -432,13 +485,13 @@ export class RawServiceMeshRuntime {
432
485
  const pending = this.operations.register(Number(remaining));
433
486
  const check = () => {
434
487
  if (this.isPeerRouteReady(nodeRoutingId, lifecycleGeneration)) {
435
- this.operations.complete(pending.id, { terminalResult: RequestResult.Ok, failureCode: 0 });
488
+ pending.reply({ terminalResult: RequestResult.Ok, failureCode: 0 });
436
489
  }
437
490
  };
438
491
  const unsubscribe = this.observePeerRouteChanged((rid) => {
439
492
  if (rid === nodeRoutingId) check();
440
493
  });
441
- const abort = () => this.operations.fail(pending.id, signal?.reason);
494
+ const abort = () => pending.replyFailure(signal?.reason);
442
495
  signal?.addEventListener('abort', abort, { once: true });
443
496
  try {
444
497
  if (signal?.aborted === true) abort();
@@ -483,15 +536,19 @@ export class RawServiceMeshRuntime {
483
536
  if (generation === undefined) return false;
484
537
  const key = controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello);
485
538
  if (!this.isPeerAnnouncementCurrent(nodeRoutingId, generation)) {
486
- this.controlSends.delete(key);
487
539
  return false;
488
540
  }
489
- const current = () => this.isPeerAnnouncementCurrent(nodeRoutingId, generation);
541
+ const current = () =>
542
+ this.isPeerAnnouncementCurrent(nodeRoutingId, generation) &&
543
+ this.peerAdmissionSealed?.() !== true;
490
544
  const existing = this.controlSends.get(key);
545
+ if (!current()) {
546
+ if (existing === undefined) this.controlSends.set(key, { current });
547
+ return false;
548
+ }
491
549
  const hello =
492
- existing?.current?.() === true
493
- ? existing.hello!
494
- : encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor());
550
+ existing?.hello ??
551
+ encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor());
495
552
  return this.startControlSend(
496
553
  nodeRoutingId,
497
554
  M6aServiceWireCommand.hello,
@@ -502,21 +559,16 @@ export class RawServiceMeshRuntime {
502
559
  }
503
560
 
504
561
  private isPeerAnnouncementCurrent(nodeRoutingId: string, generation: bigint): boolean {
505
- const admission = this.topology.routeAdmission(nodeRoutingId);
506
562
  return (
507
563
  this.selectedRoutes.get(nodeRoutingId) === generation &&
508
- !(
509
- admission?.connectionId === routeConnectionId(generation) &&
510
- admission.remoteAdmitReceived === true
511
- ) &&
512
- this.peerAdmissionSealed?.() !== true
564
+ !this.topology.hasRemoteAdmit(nodeRoutingId, routeConnectionId(generation))
513
565
  );
514
566
  }
515
567
 
516
568
  isPeerRouteReady(nodeRoutingId: string, lifecycleGeneration?: bigint): boolean {
517
569
  // Admission is bound to the observed selected route: the route observer
518
570
  // removes the peer when that route ends or is replaced.
519
- const peer = this.topology.admittedPeer(nodeRoutingId);
571
+ const peer = this.topology.readyPeer(nodeRoutingId);
520
572
  return (
521
573
  peer !== undefined &&
522
574
  (lifecycleGeneration === undefined ||
@@ -593,20 +645,21 @@ export class RawServiceMeshRuntime {
593
645
  return this.send(targetNodeRoutingId, [NODE_SEND_HEADER, this.applicationFrame(payload)]);
594
646
  }
595
647
 
596
- async sendToChannel(
648
+ sendToChannel(
597
649
  channelName: string,
598
650
  payload: ServiceApplicationPayloadInput
599
- ): Promise<SubmitResult> {
651
+ ): SubmitResult | Promise<SubmitResult> {
600
652
  const selection = this.selectChannelTarget(channelName);
601
653
  if (selection.kind !== 'selected') return selection.submitResult;
602
654
  const selected = selection.peer;
603
655
  const applicationFrame = this.applicationFrame(payload);
604
- return (await this.send(selected.descriptor.nodeRoutingId, [
656
+ const submitted = this.send(selected.descriptor.nodeRoutingId, [
605
657
  encodeChannelSendHeader(channelName),
606
658
  applicationFrame
607
- ]))
608
- ? SubmitResult.Ok
609
- : SubmitResult.NotConnected;
659
+ ]);
660
+ return typeof submitted === 'boolean'
661
+ ? channelSendSubmitResult(submitted)
662
+ : submitted.then(channelSendSubmitResult);
610
663
  }
611
664
 
612
665
  requestToNode(
@@ -621,7 +674,8 @@ export class RawServiceMeshRuntime {
621
674
  timeoutMs: number,
622
675
  onAdmitted: (() => void) | undefined,
623
676
  observer: RawServiceRequestObserver,
624
- operationKind: number
677
+ operationKind: number,
678
+ requestOperation?: PendingOperation<unknown>
625
679
  ): RawServiceObservedRequest;
626
680
  requestToNode(
627
681
  targetNodeRoutingId: string,
@@ -629,7 +683,8 @@ export class RawServiceMeshRuntime {
629
683
  timeoutMs: number,
630
684
  onAdmitted?: () => void,
631
685
  observer?: RawServiceRequestObserver,
632
- operationKind?: number
686
+ operationKind?: number,
687
+ requestOperation?: PendingOperation<unknown>
633
688
  ): RawServiceObservedRequest {
634
689
  return this.requestToTarget(
635
690
  targetNodeRoutingId,
@@ -638,7 +693,8 @@ export class RawServiceMeshRuntime {
638
693
  undefined,
639
694
  onAdmitted,
640
695
  observer,
641
- operationKind
696
+ operationKind,
697
+ requestOperation
642
698
  );
643
699
  }
644
700
 
@@ -654,7 +710,8 @@ export class RawServiceMeshRuntime {
654
710
  timeoutMs: number,
655
711
  onAdmitted: (() => void) | undefined,
656
712
  observer: RawServiceRequestObserver,
657
- operationKind: number
713
+ operationKind: number,
714
+ requestOperation?: PendingOperation<unknown>
658
715
  ): RawServiceObservedRequest;
659
716
  requestToChannel(
660
717
  channelName: string,
@@ -662,12 +719,18 @@ export class RawServiceMeshRuntime {
662
719
  timeoutMs: number,
663
720
  onAdmitted?: () => void,
664
721
  observer?: RawServiceRequestObserver,
665
- operationKind?: number
722
+ operationKind?: number,
723
+ requestOperation?: PendingOperation<unknown>
666
724
  ): RawServiceObservedRequest {
667
725
  const selection = this.selectChannelTarget(channelName);
668
726
  if (selection.kind !== 'selected') {
669
- const pending = this.registerRequest(timeoutMs, observer, operationKind);
670
- this.completeRequest(pending, selection.requestResult);
727
+ const pending = this.registerRequest(timeoutMs, observer, operationKind, requestOperation);
728
+ this.completeRequest(
729
+ pending,
730
+ selection.requestResult,
731
+ requestOperation === undefined ? undefined : observer,
732
+ operationKind
733
+ );
671
734
  return pending;
672
735
  }
673
736
  return this.requestToTarget(
@@ -681,6 +744,16 @@ export class RawServiceMeshRuntime {
681
744
  );
682
745
  }
683
746
 
747
+ beginInfrastructureRequest(
748
+ timeoutMs: number,
749
+ signal?: AbortSignal,
750
+ absoluteDeadlineMs = performance.now() + timeoutMs
751
+ ): PendingOperation<readonly Uint8Array[]> {
752
+ const pending = this.operations.register(timeoutMs, 'registry', undefined, absoluteDeadlineMs);
753
+ pending.setSignal(signal);
754
+ return pending as unknown as PendingOperation<readonly Uint8Array[]>;
755
+ }
756
+
684
757
  /**
685
758
  * One pending entry per request: an observed request reports its terminal
686
759
  * to the caller's observer; any other request keeps the Promise contract.
@@ -688,11 +761,19 @@ export class RawServiceMeshRuntime {
688
761
  private registerRequest(
689
762
  timeoutMs: number,
690
763
  observer: RawServiceRequestObserver | undefined,
691
- operationKind: number | undefined
764
+ operationKind: number | undefined,
765
+ requestOperation?: PendingOperation<unknown>
692
766
  ): RawServiceObservedRequest {
767
+ if (requestOperation !== undefined) {
768
+ return requestOperation as PendingOperation<RawServiceRequestResult>;
769
+ }
693
770
  return observer === undefined
694
771
  ? this.operations.register(timeoutMs)
695
- : { id: this.operations.registerObserved(timeoutMs, observer, operationKind) };
772
+ : this.observedRequest(this.operations.registerObserved(timeoutMs, observer, operationKind));
773
+ }
774
+
775
+ private observedRequest(id: bigint): RawServiceObservedRequest {
776
+ return this.operations.handle(id);
696
777
  }
697
778
 
698
779
  /**
@@ -701,23 +782,35 @@ export class RawServiceMeshRuntime {
701
782
  */
702
783
  private completeRequest(
703
784
  pending: RawServiceObservedRequest,
704
- result: RawServiceRequestResult
785
+ result: RawServiceRequestResult,
786
+ observer?: RawServiceRequestObserver,
787
+ operationKind?: number
705
788
  ): void {
706
- if (pending.promise === undefined)
707
- queueMicrotask(() => this.operations.complete(pending.id, result));
708
- else this.operations.complete(pending.id, result);
789
+ if (observer !== undefined) {
790
+ queueMicrotask(() => observer.operationCompleted(pending.id, result, operationKind));
791
+ return;
792
+ }
793
+ if (pending.promise === undefined || pending.hasObserver())
794
+ queueMicrotask(() => pending.reply(result));
795
+ else pending.reply(result);
709
796
  }
710
797
 
711
- private failRequest(pending: RawServiceObservedRequest, error: unknown): void {
712
- if (pending.promise === undefined)
713
- queueMicrotask(() => this.operations.fail(pending.id, error));
714
- else this.operations.fail(pending.id, error);
798
+ private failRequest(
799
+ pending: RawServiceObservedRequest,
800
+ error: unknown,
801
+ observer?: RawServiceRequestObserver,
802
+ operationKind?: number
803
+ ): void {
804
+ if (observer !== undefined) {
805
+ queueMicrotask(() => observer.operationFailed(pending.id, error, operationKind));
806
+ return;
807
+ }
808
+ if (pending.promise === undefined) queueMicrotask(() => pending.replyFailure(error));
809
+ else pending.replyFailure(error);
715
810
  }
716
811
 
717
812
  private selectChannelTarget(channelName: string): RawServiceChannelTargetSelection {
718
- const selected = this.topology.selectChannel(channelName, (peer) =>
719
- this.isPeerRouteReady(peer.descriptor.nodeRoutingId)
720
- );
813
+ const selected = this.topology.selectChannel(channelName);
721
814
  if (selected !== undefined) return { kind: 'selected', peer: selected };
722
815
  return this.topology.hasKnownChannelTarget(channelName)
723
816
  ? {
@@ -768,16 +861,64 @@ export class RawServiceMeshRuntime {
768
861
  targetNodeRoutingId: string,
769
862
  parts: readonly Uint8Array[],
770
863
  timeoutMs: number,
771
- onAdmitted?: () => void
772
- ): Promise<readonly Buffer[]> {
864
+ onAdmitted?: () => void,
865
+ existing?: Pick<PendingOperation<unknown>, 'id' | 'deadlineMs'>,
866
+ receiver?: undefined,
867
+ onSubmitted?: () => void
868
+ ): Promise<readonly Buffer[]>;
869
+ requestService(
870
+ targetNodeRoutingId: string,
871
+ parts: readonly Uint8Array[],
872
+ timeoutMs: number,
873
+ onAdmitted: (() => void) | undefined,
874
+ existing: Pick<PendingOperation<unknown>, 'id' | 'deadlineMs'> | undefined,
875
+ receiver: import('./operation-registry').OperationObserver<readonly Buffer[], unknown>
876
+ ): void;
877
+ requestService(
878
+ targetNodeRoutingId: string,
879
+ parts: readonly Uint8Array[],
880
+ timeoutMs: number,
881
+ onAdmitted?: () => void,
882
+ existing?: Pick<PendingOperation<unknown>, 'id' | 'deadlineMs'>,
883
+ receiver?: import('./operation-registry').OperationObserver<readonly Buffer[], unknown>,
884
+ onSubmitted?: () => void
885
+ ): Promise<readonly Buffer[]> | void {
886
+ if (receiver !== undefined) {
887
+ try {
888
+ this.requireStarted().request(
889
+ targetNodeRoutingId,
890
+ parts,
891
+ timeoutMs,
892
+ (reply) => receiver.operationCompleted(existing?.id ?? 0n, reply, undefined),
893
+ (error) => receiver.operationFailed(existing?.id ?? 0n, error, undefined),
894
+ this.topology.admittedPeer(targetNodeRoutingId)?.liveness?.recordReceived,
895
+ onAdmitted,
896
+ existing
897
+ );
898
+ } catch (error) {
899
+ receiver.operationFailed(existing?.id ?? 0n, error, undefined);
900
+ }
901
+ return;
902
+ }
773
903
  const admission = this.topology.admittedPeer(targetNodeRoutingId)?.liveness;
774
- return this.requireStarted().request(
775
- targetNodeRoutingId,
776
- parts,
777
- timeoutMs,
778
- admission?.recordReceived,
779
- onAdmitted
780
- );
904
+ try {
905
+ const reply = this.requireStarted().request(
906
+ targetNodeRoutingId,
907
+ parts,
908
+ timeoutMs,
909
+ (reply) => reply,
910
+ (error) => {
911
+ throw error;
912
+ },
913
+ admission?.recordReceived,
914
+ onAdmitted,
915
+ existing
916
+ );
917
+ onSubmitted?.();
918
+ return reply;
919
+ } catch (error) {
920
+ return Promise.reject(error);
921
+ }
781
922
  }
782
923
 
783
924
  replyService(
@@ -785,7 +926,7 @@ export class RawServiceMeshRuntime {
785
926
  RawServiceIngressRecord,
786
927
  'sourceRoutingId' | 'sourceRoute' | 'requestSequence' | 'reply'
787
928
  >,
788
- parts: readonly Uint8Array[]
929
+ parts: readonly (Uint8Array | string)[]
789
930
  ): void {
790
931
  if (record.requestSequence === undefined) {
791
932
  throw new TypeError('Service reply requires a request sequence.');
@@ -813,10 +954,8 @@ export class RawServiceMeshRuntime {
813
954
  if (request.reply === undefined || request.correlation === undefined) {
814
955
  throw new TypeError('Reply requires a request mailbox record.');
815
956
  }
816
- request.reply([
817
- encodeReplyHeader(request.correlation, terminalResult, failureCode),
818
- ...(terminalResult === 0 ? [this.applicationFrame(payload)] : [])
819
- ]);
957
+ const header = encodeReplyHeader(request.correlation, terminalResult, failureCode);
958
+ request.reply(terminalResult === 0 ? [header, this.applicationFrame(payload)] : [header]);
820
959
  }
821
960
 
822
961
  async pumpOne(
@@ -838,14 +977,48 @@ export class RawServiceMeshRuntime {
838
977
  return this.receiveOne(nowMs, observe);
839
978
  }
840
979
 
841
- setReadableHandler(handler: (receiveReady: boolean, routeReady: boolean) => void): void {
980
+ setReadableHandler(
981
+ handler: (receiveReady: boolean, routeReady: boolean, connectReady: boolean) => void
982
+ ): void {
842
983
  this.requireStarted().setReadableHandler(handler);
843
984
  }
844
985
 
845
986
  /** Returns whether a receive budget ended before no-data; idle ticks only maintain peers. */
846
- async pumpBatch(receiveReady = true, routeReady = false): Promise<boolean> {
847
- this.observeInfrastructureFailure();
987
+ pumpBatch(
988
+ receiveReady = true,
989
+ routeReady = false,
990
+ connectReady = false
991
+ ): boolean | Promise<boolean> {
992
+ try {
993
+ this.observeInfrastructureFailure();
994
+ if (routeReady || connectReady) {
995
+ return this.receiveBatchAfterObservations(receiveReady, routeReady, connectReady);
996
+ }
997
+ return this.receiveBatch(receiveReady);
998
+ } catch (error) {
999
+ return Promise.reject(error);
1000
+ }
1001
+ }
1002
+
1003
+ private async receiveBatchAfterObservations(
1004
+ receiveReady: boolean,
1005
+ routeReady: boolean,
1006
+ connectReady: boolean
1007
+ ): Promise<boolean> {
1008
+ // Both facts are applied before admission observers run in this socket turn.
1009
+ if (connectReady) this.connectionAttempts = this.requireStarted().connectionAttemptsSnapshot();
848
1010
  if (routeReady) await this.observeSelectedRoutes();
1011
+ if (connectReady) {
1012
+ for (const target of this.expectedPeers.keys()) this.notifyPeerRouteChanged(target);
1013
+ for (const peer of this.topology.peers()) {
1014
+ if (!this.expectedPeers.has(peer.descriptor.nodeRoutingId))
1015
+ this.notifyPeerRouteChanged(peer.descriptor.nodeRoutingId);
1016
+ }
1017
+ }
1018
+ return this.receiveBatch(receiveReady);
1019
+ }
1020
+
1021
+ private receiveBatch(receiveReady: boolean): boolean | Promise<boolean> {
849
1022
  const startedAtMs = performance.now();
850
1023
  let messages = 0;
851
1024
  let bytes = 0;
@@ -857,30 +1030,49 @@ export class RawServiceMeshRuntime {
857
1030
  'remote'
858
1031
  ));
859
1032
  if (receiveReady) rotation.reserve();
860
- try {
861
- while (receiveReady && messages < RAW_MESH_RECEIVE_RECORD_BUDGET && !this.closed) {
862
- const received = this.receiveOne(performance.now(), observe, rotation);
863
- const result = received instanceof Promise ? await received : received;
864
- if (result === 'noData') {
865
- receiveReady = false;
866
- break;
1033
+ // The same loop owns both immediate receives and a suspended receive's
1034
+ // continuation; only the latter needs a Promise and retains this frame.
1035
+ const drain = (received?: RawServicePumpResult): boolean | Promise<boolean> => {
1036
+ try {
1037
+ while (
1038
+ received !== undefined ||
1039
+ (receiveReady && messages < RAW_MESH_RECEIVE_RECORD_BUDGET && !this.closed)
1040
+ ) {
1041
+ if (received === undefined) {
1042
+ const pending = this.receiveOne(performance.now(), observe, rotation);
1043
+ if (pending instanceof Promise) {
1044
+ return pending.then(drain, (error) => {
1045
+ rotation.releaseUnused();
1046
+ throw error;
1047
+ });
1048
+ }
1049
+ received = pending;
1050
+ }
1051
+ if (received === 'noData') {
1052
+ receiveReady = false;
1053
+ break;
1054
+ }
1055
+ messages += 1;
1056
+ received = undefined;
1057
+ // Core owns the per-peer fair-queue cursor. The Framework limit bounds
1058
+ // the entire round, so changing peers does not renew its byte/count budget.
1059
+ if (
1060
+ bytes >= RAW_MESH_RECEIVE_BYTE_BUDGET ||
1061
+ performance.now() - startedAtMs >= RAW_MESH_RECEIVE_TIME_BUDGET_MS
1062
+ )
1063
+ break;
867
1064
  }
868
- messages += 1;
869
- // Core owns the per-peer fair-queue cursor. The Framework limit bounds
870
- // the entire round, so changing peers does not renew its byte/count budget.
871
- if (
872
- bytes >= RAW_MESH_RECEIVE_BYTE_BUDGET ||
873
- performance.now() - startedAtMs >= RAW_MESH_RECEIVE_TIME_BUDGET_MS
874
- )
875
- break;
1065
+ } catch (error) {
1066
+ rotation.releaseUnused();
1067
+ throw error;
876
1068
  }
877
- } finally {
878
1069
  rotation.releaseUnused();
879
- }
880
- if (!this.closed) {
881
- this.tickLiveness();
882
- }
883
- return receiveReady && messages > 0;
1070
+ if (!this.closed) {
1071
+ this.tickLiveness();
1072
+ }
1073
+ return receiveReady && messages > 0;
1074
+ };
1075
+ return drain();
884
1076
  }
885
1077
 
886
1078
  private receiveOne(
@@ -894,35 +1086,7 @@ export class RawServiceMeshRuntime {
894
1086
  this.receivePermit = undefined;
895
1087
  permit ??= rotation?.take();
896
1088
  if (permit === undefined && this.receiveAcquisition === undefined) {
897
- let resolveAcquisition: ((permit: ApplicationJobPermitPort) => void) | undefined;
898
- let rejectAcquisition: ((error: unknown) => void) | undefined;
899
- permit = this.applicationJobQueue.acquireOrResume(
900
- (permit) => {
901
- this.receiveAcquisition = undefined;
902
- if (this.closed) {
903
- permit.releaseAfterInternalProcessing();
904
- permit.releaseStorage?.();
905
- } else {
906
- this.receivePermit = permit;
907
- this.onReceiveReady?.();
908
- }
909
- resolveAcquisition?.(permit);
910
- },
911
- (error) => rejectAcquisition?.(error),
912
- this.applicationJobStop.signal,
913
- 'remote'
914
- );
915
- if (permit === undefined) {
916
- const acquisition = new Promise<ApplicationJobPermitPort>((resolve, reject) => {
917
- resolveAcquisition = resolve;
918
- rejectAcquisition = reject;
919
- });
920
- this.receiveAcquisition = acquisition;
921
- acquisition.catch((error) => {
922
- this.receiveAcquisition = undefined;
923
- if (!this.applicationJobStop.signal.aborted) this.reportInfrastructureFailure(error);
924
- });
925
- }
1089
+ permit = this.acquireReceivePermit();
926
1090
  }
927
1091
  if (permit === undefined) return 'noData';
928
1092
  if (this.closed) {
@@ -956,16 +1120,58 @@ export class RawServiceMeshRuntime {
956
1120
  throw error;
957
1121
  }
958
1122
  return result instanceof Promise
959
- ? result.then(
960
- (value) => this.finishReceive(received, applicationJobOwner, observe, value),
961
- (error) => {
962
- applicationJobOwner.close();
963
- throw error;
964
- }
965
- )
1123
+ ? this.finishPendingReceive(result, received, applicationJobOwner, observe)
966
1124
  : this.finishReceive(received, applicationJobOwner, observe, result);
967
1125
  }
968
1126
 
1127
+ private acquireReceivePermit(): ApplicationJobPermitPort | undefined {
1128
+ let resolveAcquisition: ((permit: ApplicationJobPermitPort) => void) | undefined;
1129
+ let rejectAcquisition: ((error: unknown) => void) | undefined;
1130
+ const permit = this.applicationJobQueue.acquireOrResume(
1131
+ (permit) => {
1132
+ this.receiveAcquisition = undefined;
1133
+ if (this.closed) {
1134
+ permit.releaseAfterInternalProcessing();
1135
+ permit.releaseStorage?.();
1136
+ } else {
1137
+ this.receivePermit = permit;
1138
+ this.onReceiveReady?.();
1139
+ }
1140
+ resolveAcquisition?.(permit);
1141
+ },
1142
+ (error) => rejectAcquisition?.(error),
1143
+ this.applicationJobStop.signal,
1144
+ 'remote'
1145
+ );
1146
+ if (permit === undefined) {
1147
+ const acquisition = new Promise<ApplicationJobPermitPort>((resolve, reject) => {
1148
+ resolveAcquisition = resolve;
1149
+ rejectAcquisition = reject;
1150
+ });
1151
+ this.receiveAcquisition = acquisition;
1152
+ acquisition.catch((error) => {
1153
+ this.receiveAcquisition = undefined;
1154
+ if (!this.applicationJobStop.signal.aborted) this.reportInfrastructureFailure(error);
1155
+ });
1156
+ }
1157
+ return permit;
1158
+ }
1159
+
1160
+ private finishPendingReceive(
1161
+ result: Promise<RawServicePumpResult>,
1162
+ received: ZLinkRawReceivedRecord,
1163
+ owner: ApplicationIngressRecordOwner,
1164
+ observe: RawServicePumpObserver | undefined
1165
+ ): Promise<RawServicePumpResult> {
1166
+ return result.then(
1167
+ (value) => this.finishReceive(received, owner, observe, value),
1168
+ (error) => {
1169
+ owner.close();
1170
+ throw error;
1171
+ }
1172
+ );
1173
+ }
1174
+
969
1175
  private finishReceive(
970
1176
  received: ZLinkRawReceivedRecord,
971
1177
  owner: ApplicationIngressRecordOwner,
@@ -976,7 +1182,8 @@ export class RawServiceMeshRuntime {
976
1182
  if (result === 'protocolError') this.reportProtocolError(received);
977
1183
  if (observe !== undefined) {
978
1184
  let bytes = 0;
979
- for (const part of received.parts) bytes += part.byteLength;
1185
+ for (let index = 0; index < received.parts.length; index += 1)
1186
+ bytes += received.parts[index]!.byteLength;
980
1187
  observe(received.sourceRid, bytes);
981
1188
  }
982
1189
  return result;
@@ -985,7 +1192,7 @@ export class RawServiceMeshRuntime {
985
1192
  }
986
1193
  }
987
1194
 
988
- private reportProtocolError(
1195
+ reportProtocolError(
989
1196
  received: import('../backend/node/node-raw-binding-port').ZLinkRawReceivedRecord
990
1197
  ): void {
991
1198
  const request = received.requestSeq !== undefined;
@@ -1002,32 +1209,19 @@ export class RawServiceMeshRuntime {
1002
1209
  received: import('../backend/node/node-raw-binding-port').ZLinkRawReceivedRecord
1003
1210
  ): boolean {
1004
1211
  if (received.requestSeq === undefined || received.parts.length === 0) return false;
1005
- try {
1006
- const header = decodeHeader(received.parts[0]!);
1007
- const correlation =
1008
- header.command === M6aServiceWireCommand.nodeRequest
1009
- ? decodeNodeRequestHeader(received.parts[0]!)
1010
- : header.command === M6aServiceWireCommand.channelRequest
1011
- ? decodeChannelRequestHeader(received.parts[0]!).correlation
1012
- : undefined;
1013
- if (correlation === undefined) return false;
1014
- const reply = [
1015
- encodeReplyHeader(
1016
- correlation,
1017
- RequestResult.ProtocolError,
1018
- ServiceWireFrameworkErrorCode.requestProtocolError
1019
- )
1020
- ];
1021
- if (received.reply === undefined) return false;
1022
- received.reply(reply);
1023
- return true;
1024
- } catch (error) {
1025
- if (error instanceof ServiceWireProtocolError) return false;
1026
- throw error;
1027
- }
1212
+ const correlation = recoverRequestCorrelation(received.parts[0]!);
1213
+ if (correlation === undefined || received.reply === undefined) return false;
1214
+ received.reply([
1215
+ encodeReplyHeader(
1216
+ correlation,
1217
+ RequestResult.ProtocolError,
1218
+ ServiceWireFrameworkErrorCode.requestProtocolError
1219
+ )
1220
+ ]);
1221
+ return true;
1028
1222
  }
1029
1223
 
1030
- private processReceived(
1224
+ processReceived(
1031
1225
  received: ZLinkRawReceivedRecord,
1032
1226
  nowMs: number,
1033
1227
  applicationJobOwner: ApplicationIngressRecordOwner
@@ -1045,9 +1239,7 @@ export class RawServiceMeshRuntime {
1045
1239
  header.command === M6aServiceWireCommand.hello ||
1046
1240
  header.command === M6aServiceWireCommand.admit
1047
1241
  ) {
1048
- return this.processAdmission(received, header, nowMs).catch((error) =>
1049
- this.receiveProtocolFailure(error)
1050
- );
1242
+ return this.finishPendingProtocolReceive(this.processAdmission(received, header, nowMs));
1051
1243
  }
1052
1244
  if (header.command === M6aServiceWireCommand.reject) {
1053
1245
  if (received.parts.length !== 1) return 'protocolError';
@@ -1057,17 +1249,13 @@ export class RawServiceMeshRuntime {
1057
1249
  if (!currentSource) return 'dropped';
1058
1250
  this.liveness.recordReceived(peer.liveness, nowMs);
1059
1251
  if (header.command === M6aServiceWireCommand.update) {
1060
- return this.processAdmission(received, header, nowMs).catch((error) =>
1061
- this.receiveProtocolFailure(error)
1062
- );
1252
+ return this.finishPendingProtocolReceive(this.processAdmission(received, header, nowMs));
1063
1253
  }
1064
1254
  if (
1065
1255
  header.command === M6aServiceWireCommand.livenessProbe ||
1066
1256
  header.command === M6aServiceWireCommand.livenessAck
1067
1257
  ) {
1068
- return this.processLiveness(received, nowMs).catch((error) =>
1069
- this.receiveProtocolFailure(error)
1070
- );
1258
+ return this.finishPendingProtocolReceive(this.processLiveness(received, nowMs));
1071
1259
  }
1072
1260
  const stateful =
1073
1261
  this.serviceIngress !== undefined && this.serviceIngressAccepts?.(header.command) !== false
@@ -1086,11 +1274,7 @@ export class RawServiceMeshRuntime {
1086
1274
  })
1087
1275
  : undefined;
1088
1276
  if (stateful instanceof Promise)
1089
- return stateful.then(
1090
- (result) =>
1091
- result ?? this.processApplicationReceived(received, header, applicationJobOwner),
1092
- (error) => this.receiveProtocolFailure(error)
1093
- );
1277
+ return this.finishPendingStatefulReceive(stateful, received, header, applicationJobOwner);
1094
1278
  if (stateful !== undefined) return stateful;
1095
1279
  return this.processApplicationReceived(received, header, applicationJobOwner);
1096
1280
  } catch (error) {
@@ -1099,6 +1283,24 @@ export class RawServiceMeshRuntime {
1099
1283
  }
1100
1284
  }
1101
1285
 
1286
+ private finishPendingProtocolReceive(
1287
+ pending: Promise<RawServicePumpResult>
1288
+ ): Promise<RawServicePumpResult> {
1289
+ return pending.catch((error) => this.receiveProtocolFailure(error));
1290
+ }
1291
+
1292
+ private finishPendingStatefulReceive(
1293
+ pending: Promise<RawServicePumpResult | undefined>,
1294
+ received: ZLinkRawReceivedRecord,
1295
+ header: ReturnType<typeof decodeHeader>,
1296
+ owner: ApplicationIngressRecordOwner
1297
+ ): Promise<RawServicePumpResult> {
1298
+ return pending.then(
1299
+ (result) => result ?? this.processApplicationReceived(received, header, owner),
1300
+ (error) => this.receiveProtocolFailure(error)
1301
+ );
1302
+ }
1303
+
1102
1304
  private receiveProtocolFailure(error: unknown): RawServicePumpResult {
1103
1305
  if (error instanceof ServiceWireProtocolError) return 'protocolError';
1104
1306
  throw error;
@@ -1170,7 +1372,7 @@ export class RawServiceMeshRuntime {
1170
1372
  ]);
1171
1373
  } else if (header.command === M6aServiceWireCommand.admit) {
1172
1374
  this.controlSends.delete(helloKey);
1173
- this.refreshAdmissionPending(nowMs);
1375
+ this.progressPeerAdmission(nowMs);
1174
1376
  if (result === 'notRequired') {
1175
1377
  this.retireNotRequiredExpectedPeer(received.sourceRid, descriptor.advertisedEndpoint);
1176
1378
  } else if (
@@ -1274,13 +1476,26 @@ export class RawServiceMeshRuntime {
1274
1476
  }
1275
1477
  const job = applicationJobOwner.acquire('application');
1276
1478
  return job instanceof Promise
1277
- ? job.then((permit) => this.enqueueOrdinaryRecord(received, owner, correlation, permit))
1479
+ ? this.enqueuePendingOrdinaryRecord(job, received, owner, correlation)
1278
1480
  : this.enqueueOrdinaryRecord(received, owner, correlation, job);
1279
1481
  } catch (error) {
1280
1482
  return this.receiveProtocolFailure(error);
1281
1483
  }
1282
1484
  }
1283
1485
 
1486
+ private enqueuePendingOrdinaryRecord(
1487
+ pending: Promise<
1488
+ import('../application-jobs/application-ingress-record-owner').ApplicationJobRecordLease
1489
+ >,
1490
+ received: ZLinkRawReceivedRecord,
1491
+ owner: string,
1492
+ correlation: bigint | undefined
1493
+ ): Promise<RawServicePumpResult> {
1494
+ return pending.then((permit) =>
1495
+ this.enqueueOrdinaryRecord(received, owner, correlation, permit)
1496
+ );
1497
+ }
1498
+
1284
1499
  private enqueueOrdinaryRecord(
1285
1500
  received: ZLinkRawReceivedRecord,
1286
1501
  owner: string,
@@ -1316,7 +1531,9 @@ export class RawServiceMeshRuntime {
1316
1531
  }
1317
1532
 
1318
1533
  expireOperations(nowMs: number, turnDeadlineMs: number): number {
1319
- return this.operations.expire(nowMs, turnDeadlineMs);
1534
+ return this.requestOwner === undefined && nowMs >= this.operations.nextDeadlineMs()
1535
+ ? this.operations.expire(nowMs, turnDeadlineMs)
1536
+ : 0;
1320
1537
  }
1321
1538
 
1322
1539
  get pendingOperationCount(): number {
@@ -1333,20 +1550,24 @@ export class RawServiceMeshRuntime {
1333
1550
  * ADMIT, which keeps the existing announce cadence.
1334
1551
  */
1335
1552
  nextMaintenanceDeadlineMs(nowMs: number): number {
1336
- this.refreshAdmissionPending(nowMs);
1337
- return Math.min(this.operations.nextDeadlineMs(), this.liveness.nextDeadlineMs());
1553
+ this.progressPeerAdmission(nowMs);
1554
+ return Math.min(
1555
+ this.requestOwner === undefined ? this.operations.nextDeadlineMs() : Infinity,
1556
+ this.liveness.nextDeadlineMs()
1557
+ );
1338
1558
  }
1339
1559
 
1340
- private refreshAdmissionPending(nowMs: number): void {
1560
+ private progressPeerAdmission(nowMs: number): void {
1341
1561
  let pending = false;
1342
1562
  if (!this.closed && this.controlSends.size !== 0) {
1343
1563
  for (const nodeRoutingId of this.selectedRoutes.keys()) {
1344
- const exchange = this.controlSends.get(
1345
- controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello)
1346
- );
1564
+ const key = controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello);
1565
+ const exchange = this.controlSends.get(key);
1566
+ // An unsent HELLO intent may resume after the host admission seal opens.
1567
+ // Only retransmission of submitted bytes waits for the management cadence.
1347
1568
  if (exchange?.current?.() === true) {
1569
+ if (exchange.hello === undefined) this.announcePeer(nodeRoutingId);
1348
1570
  pending = true;
1349
- break;
1350
1571
  }
1351
1572
  }
1352
1573
  }
@@ -1354,9 +1575,12 @@ export class RawServiceMeshRuntime {
1354
1575
  }
1355
1576
 
1356
1577
  tickLiveness(nowMs = performance.now()): ServiceLivenessTick {
1578
+ this.progressPeerAdmission(nowMs);
1357
1579
  const admissionDue = this.liveness.tickAdmission(nowMs);
1358
1580
  const result = this.liveness.tick(nowMs);
1359
1581
  this.requireStarted();
1582
+ if (!admissionDue && result.probes.length === 0 && result.timedOutNodes.length === 0)
1583
+ return result;
1360
1584
  if (admissionDue)
1361
1585
  for (const nodeRoutingId of this.selectedRoutes.keys()) void this.announcePeer(nodeRoutingId);
1362
1586
  for (const probe of result.probes) {
@@ -1370,12 +1594,24 @@ export class RawServiceMeshRuntime {
1370
1594
  this.submitControl(probe.nodeRoutingId, parts)
1371
1595
  );
1372
1596
  }
1597
+ const failures: unknown[] = [];
1373
1598
  for (const nodeRoutingId of result.timedOutNodes) {
1374
1599
  const peer = this.topology.peer(nodeRoutingId);
1375
1600
  if (peer === undefined) continue;
1376
- this.requireStarted().disconnectRid!(nodeRoutingId);
1377
- this.removePeer(peer);
1601
+ try {
1602
+ this.removePeer(peer, () => {
1603
+ try {
1604
+ this.requireStarted().disconnectRid!(nodeRoutingId);
1605
+ } catch (error) {
1606
+ failures.push(error);
1607
+ }
1608
+ });
1609
+ } catch (error) {
1610
+ failures.push(error);
1611
+ }
1378
1612
  }
1613
+ if (failures.length === 1) throw failures[0];
1614
+ if (failures.length > 1) throw new AggregateError(failures, 'Liveness peer cleanup failed.');
1379
1615
  return result;
1380
1616
  }
1381
1617
 
@@ -1410,34 +1646,44 @@ export class RawServiceMeshRuntime {
1410
1646
  }
1411
1647
  this.selectedRoutes = observed;
1412
1648
  for (const nodeRoutingId of selected) this.notifyPeerRouteChanged(nodeRoutingId);
1413
- // A new selected route admits only through a new handshake.
1649
+ // Selected routes begin admission before waiting for the management cadence.
1414
1650
  for (const nodeRoutingId of selected) await this.announcePeer(nodeRoutingId);
1415
- this.refreshAdmissionPending(performance.now());
1651
+ this.progressPeerAdmission(performance.now());
1416
1652
  return changes;
1417
1653
  }
1418
1654
 
1419
1655
  close(): void {
1420
- if (this.closed) return;
1421
- this.closed = true;
1422
- this.applicationJobStop.abort(
1423
- new Error('Raw service runtime application job admission stopped.')
1424
- );
1425
- this.receivePermit?.releaseAfterInternalProcessing();
1426
- this.receivePermit?.releaseStorage?.();
1427
- this.receivePermit = undefined;
1428
- for (const send of this.controlSends.values()) delete send.next;
1429
- this.controlSends.clear();
1430
- this.mailbox.close();
1431
- this.operations.close('Raw service runtime closed.');
1432
- this.serviceIngress = undefined;
1433
- const router = this.router;
1434
- if (router !== undefined) {
1435
- this.applicationJobQueue.unregisterReceiveFlowTarget?.(router);
1436
- }
1437
1656
  const host = this.host;
1438
- if (host !== undefined) host.close();
1439
- this.router = undefined;
1657
+ if (this.closed && host === undefined) return;
1440
1658
  this.host = undefined;
1659
+ try {
1660
+ if (!this.closed) {
1661
+ this.closed = true;
1662
+ this.applicationJobStop.abort(
1663
+ createInternalFrameworkException(
1664
+ ZLinkFrameworkInternalErrorKind.RuntimeShutdown,
1665
+ 'Raw service runtime application job admission stopped.'
1666
+ )
1667
+ );
1668
+ this.receivePermit?.releaseAfterInternalProcessing();
1669
+ this.receivePermit?.releaseStorage?.();
1670
+ this.receivePermit = undefined;
1671
+ for (const send of this.controlSends.values()) delete send.next;
1672
+ this.controlSends.clear();
1673
+ this.mailbox.close();
1674
+ this.releaseOperations();
1675
+ this.serviceIngress = undefined;
1676
+ const router = this.router;
1677
+ if (router !== undefined) {
1678
+ this.applicationJobQueue.unregisterReceiveFlowTarget?.(router);
1679
+ }
1680
+ }
1681
+ if (host !== undefined) host.close();
1682
+ this.router = undefined;
1683
+ } catch (error) {
1684
+ this.host = host;
1685
+ throw error;
1686
+ }
1441
1687
  }
1442
1688
 
1443
1689
  private requestToTarget(
@@ -1447,14 +1693,15 @@ export class RawServiceMeshRuntime {
1447
1693
  channelName?: string,
1448
1694
  onAdmitted?: () => void,
1449
1695
  observer?: RawServiceRequestObserver,
1450
- operationKind?: number
1696
+ operationKind?: number,
1697
+ requestOperation?: PendingOperation<unknown>
1451
1698
  ): RawServiceObservedRequest {
1452
1699
  const correlation = this.nextCorrelation++;
1453
1700
  const header =
1454
1701
  channelName === undefined
1455
1702
  ? encodeNodeRequestHeader(correlation)
1456
1703
  : encodeChannelRequestHeader(correlation, channelName);
1457
- const encodedPayload = this.applicationFrame(payload);
1704
+ let encodedPayload = this.applicationFrame(payload);
1458
1705
  let selectedTargetNodeRoutingId = targetNodeRoutingId;
1459
1706
  if (
1460
1707
  selectedTargetNodeRoutingId !== this.descriptor.nodeRoutingId &&
@@ -1482,21 +1729,30 @@ export class RawServiceMeshRuntime {
1482
1729
  }
1483
1730
  }
1484
1731
  if (selectedTargetNodeRoutingId === this.descriptor.nodeRoutingId) {
1732
+ encodedPayload = copyPayloadBytes(encodedPayload);
1485
1733
  // The local admission path aborts its capacity wait on the terminal
1486
1734
  // Promise, so a local request always keeps the Promise entry.
1487
- const pending = this.operations.register(timeoutMs);
1735
+ const pending =
1736
+ requestOperation === undefined
1737
+ ? this.operations.register(timeoutMs)
1738
+ : (this.registerRequest(
1739
+ timeoutMs,
1740
+ observer,
1741
+ operationKind,
1742
+ requestOperation
1743
+ ) as PendingOperation<RawServiceRequestResult>);
1488
1744
  const parts = [header, encodedPayload];
1489
1745
  const capacityStop = new AbortController();
1490
1746
  void pending.promise.finally(() => capacityStop.abort()).catch(() => undefined);
1491
1747
  void (async () => {
1492
1748
  const applicationJobOwner = await this.reserveLocalIngress(capacityStop.signal);
1493
1749
  try {
1494
- if (!this.operations.isPending(pending.id)) return;
1750
+ if (!pending.isPending()) return;
1495
1751
  const applicationJob = await applicationJobOwner.acquire(
1496
1752
  'application',
1497
1753
  capacityStop.signal
1498
1754
  );
1499
- if (!this.operations.isPending(pending.id)) {
1755
+ if (!pending.isPending()) {
1500
1756
  applicationJob.close();
1501
1757
  return;
1502
1758
  }
@@ -1507,91 +1763,150 @@ export class RawServiceMeshRuntime {
1507
1763
  sourceRoutingId: this.descriptor.nodeRoutingId,
1508
1764
  correlation,
1509
1765
  localReply: (terminalResult, failureCode, reply) =>
1510
- this.operations.complete(pending.id, {
1511
- terminalResult,
1512
- failureCode,
1513
- ...(reply === undefined ? {} : { payload: reply })
1514
- }),
1766
+ this.completeRequest(
1767
+ pending,
1768
+ {
1769
+ terminalResult,
1770
+ failureCode,
1771
+ ...(reply === undefined ? {} : { payload: reply })
1772
+ },
1773
+ requestOperation === undefined ? undefined : observer,
1774
+ operationKind
1775
+ ),
1515
1776
  applicationJob
1516
1777
  });
1517
1778
  if (!accepted) {
1518
1779
  applicationJob.close();
1519
- this.operations.complete(pending.id, {
1520
- terminalResult: RequestResult.NotConnected,
1521
- failureCode: 0
1522
- });
1780
+ this.completeRequest(
1781
+ pending,
1782
+ {
1783
+ terminalResult: RequestResult.NotConnected,
1784
+ failureCode: 0
1785
+ },
1786
+ requestOperation === undefined ? undefined : observer,
1787
+ operationKind
1788
+ );
1523
1789
  } else {
1524
1790
  onAdmitted?.();
1525
1791
  }
1526
1792
  } finally {
1527
1793
  applicationJobOwner.close();
1528
1794
  }
1529
- })().catch((error) => this.operations.fail(pending.id, error));
1795
+ })().catch((error) =>
1796
+ this.failRequest(
1797
+ pending,
1798
+ error,
1799
+ requestOperation === undefined ? undefined : observer,
1800
+ operationKind
1801
+ )
1802
+ );
1530
1803
  return pending;
1531
1804
  }
1532
- const pending = this.registerRequest(timeoutMs, observer, operationKind);
1533
- const selectedPeer = this.topology.admittedPeer(selectedTargetNodeRoutingId);
1534
- if (selectedPeer === undefined) {
1535
- // Logical target selection is the requester's decision: an RID that is
1536
- // not an admitted peer is not a target. Whether an admitted target's
1537
- // route can carry the request is Core's REQUEST result.
1538
- this.completeRequest(pending, {
1539
- terminalResult: RequestResult.NotFound,
1540
- failureCode: REQUEST_TARGET_NOT_FOUND_FAILURE_CODE
1541
- });
1805
+ const pending = this.registerRequest(timeoutMs, observer, operationKind, requestOperation);
1806
+ const selectedPeer = this.topology.readyPeer(selectedTargetNodeRoutingId);
1807
+ if (
1808
+ selectedPeer === undefined &&
1809
+ !this.expectedPeers.has(selectedTargetNodeRoutingId) &&
1810
+ this.topology.peer(selectedTargetNodeRoutingId) === undefined
1811
+ ) {
1812
+ this.completeRequest(
1813
+ pending,
1814
+ {
1815
+ terminalResult: RequestResult.NotFound,
1816
+ failureCode: REQUEST_TARGET_NOT_FOUND_FAILURE_CODE
1817
+ },
1818
+ requestOperation === undefined ? undefined : observer,
1819
+ operationKind
1820
+ );
1542
1821
  return pending;
1543
1822
  }
1544
1823
  const parts = [header, encodedPayload];
1545
- let request: Promise<readonly Uint8Array[]>;
1546
- try {
1547
- request = this.requireStarted().request(
1548
- selectedTargetNodeRoutingId,
1549
- parts,
1550
- timeoutMs,
1551
- selectedPeer.liveness?.recordReceived,
1552
- onAdmitted
1824
+ const completed = (replyParts: readonly Buffer[]) => {
1825
+ try {
1826
+ if (replyParts.length < 1 || replyParts.length > 2) {
1827
+ throw new ServiceWireProtocolError('Invalid reply parts.');
1828
+ }
1829
+ const reply = decodeReplyHeader(replyParts[0]!);
1830
+ if (reply.correlation !== correlation)
1831
+ throw new ServiceWireProtocolError('Reply correlation mismatch.');
1832
+ if (reply.tail.byteLength !== 0) {
1833
+ throw new ServiceWireProtocolError(
1834
+ 'Generic node/channel reply carries an operation-specific tail.'
1835
+ );
1836
+ }
1837
+ if (reply.terminalResult === 0 && replyParts.length !== 2) {
1838
+ throw new ServiceWireProtocolError('Successful reply omits its payload.');
1839
+ }
1840
+ if (reply.terminalResult !== 0 && replyParts.length !== 1) {
1841
+ throw new ServiceWireProtocolError('Failed reply carries a payload.');
1842
+ }
1843
+ const result: RawServiceRequestResult = {
1844
+ terminalResult: reply.terminalResult,
1845
+ failureCode: reply.failureCode
1846
+ };
1847
+ const value =
1848
+ reply.terminalResult === 0
1849
+ ? { ...result, payload: decodeApplicationPayloadView(replyParts[1]!) }
1850
+ : result;
1851
+ if (requestOperation !== undefined && observer !== undefined)
1852
+ observer.operationCompleted(pending.id, value, operationKind);
1853
+ else pending.reply(value);
1854
+ } catch (error) {
1855
+ this.failRequest(
1856
+ pending,
1857
+ error,
1858
+ requestOperation === undefined ? undefined : observer,
1859
+ operationKind
1860
+ );
1861
+ }
1862
+ };
1863
+ const failed = (error: unknown) =>
1864
+ this.failRequest(
1865
+ pending,
1866
+ error,
1867
+ requestOperation === undefined ? undefined : observer,
1868
+ operationKind
1553
1869
  );
1554
- } catch (error) {
1555
- this.failRequest(pending, error);
1870
+ if (selectedPeer !== undefined) {
1871
+ try {
1872
+ this.requireStarted().request(
1873
+ selectedTargetNodeRoutingId,
1874
+ parts,
1875
+ remainingRequestTimeout('Request', pending.deadlineMs)!,
1876
+ completed,
1877
+ failed,
1878
+ selectedPeer.liveness?.recordReceived,
1879
+ onAdmitted,
1880
+ pending
1881
+ );
1882
+ } catch (error) {
1883
+ failed(error);
1884
+ }
1556
1885
  return pending;
1557
1886
  }
1558
- void request.then(
1559
- (replyParts) => {
1560
- try {
1561
- if (replyParts.length < 1 || replyParts.length > 2) {
1562
- throw new ServiceWireProtocolError('Invalid reply parts.');
1563
- }
1564
- const reply = decodeReplyHeader(replyParts[0]!);
1565
- if (reply.correlation !== correlation)
1566
- throw new ServiceWireProtocolError('Reply correlation mismatch.');
1567
- if (reply.tail.byteLength !== 0) {
1568
- throw new ServiceWireProtocolError(
1569
- 'Generic node/channel reply carries an operation-specific tail.'
1570
- );
1571
- }
1572
- if (reply.terminalResult === 0 && replyParts.length !== 2) {
1573
- throw new ServiceWireProtocolError('Successful reply omits its payload.');
1574
- }
1575
- if (reply.terminalResult !== 0 && replyParts.length !== 1) {
1576
- throw new ServiceWireProtocolError('Failed reply carries a payload.');
1577
- }
1578
- const result: RawServiceRequestResult = {
1579
- terminalResult: reply.terminalResult,
1580
- failureCode: reply.failureCode
1581
- };
1582
- this.operations.complete(
1583
- pending.id,
1584
- reply.terminalResult === 0
1585
- ? { ...result, payload: decodeApplicationPayloadView(replyParts[1]!) }
1586
- : result
1587
- );
1588
- } catch (error) {
1589
- this.operations.fail(pending.id, error);
1590
- }
1887
+ admitServiceRequest(
1888
+ this,
1889
+ selectedTargetNodeRoutingId,
1890
+ pending,
1891
+ (remaining, finished) => {
1892
+ this.requireStarted().request(
1893
+ selectedTargetNodeRoutingId,
1894
+ parts,
1895
+ remaining,
1896
+ (reply) => {
1897
+ finished();
1898
+ completed(reply);
1899
+ },
1900
+ (error) => {
1901
+ finished();
1902
+ failed(error);
1903
+ },
1904
+ this.topology.admittedPeer(selectedTargetNodeRoutingId)?.liveness?.recordReceived,
1905
+ onAdmitted,
1906
+ pending
1907
+ );
1591
1908
  },
1592
- (error) => {
1593
- this.operations.fail(pending.id, error);
1594
- }
1909
+ failed
1595
1910
  );
1596
1911
  return pending;
1597
1912
  }
@@ -1605,10 +1920,10 @@ export class RawServiceMeshRuntime {
1605
1920
  }
1606
1921
 
1607
1922
  private applicationPayload(payload: ServiceApplicationPayloadInput): ServiceApplicationPayload {
1608
- return Buffer.isBuffer(payload) ? decodeApplicationPayloadView(payload) : payload;
1923
+ return Buffer.isBuffer(payload) ? decodeApplicationPayload(payload) : payload;
1609
1924
  }
1610
1925
 
1611
- private admitPeer(
1926
+ admitPeer(
1612
1927
  descriptor: ServiceNodeDescriptor,
1613
1928
  connectionId: string | bigint,
1614
1929
  nowMs: number,
@@ -1620,7 +1935,7 @@ export class RawServiceMeshRuntime {
1620
1935
  command: number = M6aServiceWireCommand.hello,
1621
1936
  descriptorBytes?: Uint8Array
1622
1937
  ): PeerAdmissionResult {
1623
- const previous = this.topology.peer(descriptor.nodeRoutingId);
1938
+ const previous = this.topology.admittedPeer(descriptor.nodeRoutingId);
1624
1939
  const topologyConnectionId =
1625
1940
  typeof connectionId === 'bigint' ? routeConnectionId(connectionId) : connectionId;
1626
1941
  const result = this.topology.admit(
@@ -1646,7 +1961,14 @@ export class RawServiceMeshRuntime {
1646
1961
  previous.liveness?.connectionId ?? previous.connectionId
1647
1962
  );
1648
1963
  }
1649
- if (result === 'admitted') this.notifyPeerRouteChanged(descriptor.nodeRoutingId);
1964
+ if (
1965
+ result === 'admitted' &&
1966
+ (previous?.connectionId !== topologyConnectionId ||
1967
+ previous.descriptor.lifecycleGeneration !== descriptor.lifecycleGeneration ||
1968
+ previous.remoteAdmitReceived !==
1969
+ this.topology.admittedPeer(descriptor.nodeRoutingId)?.remoteAdmitReceived)
1970
+ )
1971
+ this.notifyPeerRouteChanged(descriptor.nodeRoutingId);
1650
1972
  return result;
1651
1973
  }
1652
1974
 
@@ -1680,17 +2002,22 @@ export class RawServiceMeshRuntime {
1680
2002
  this.endpointOnlyPeers.delete(endpoint);
1681
2003
  }
1682
2004
 
1683
- private removePeer(peer: AdmittedServicePeer): void {
2005
+ private removePeer(peer: AdmittedServicePeer, disconnectPhysical?: () => void): void {
1684
2006
  this.topology.disconnect(peer.descriptor.nodeRoutingId, peer.connectionId);
1685
2007
  this.liveness.disconnect(
1686
2008
  peer.descriptor.nodeRoutingId,
1687
2009
  peer.liveness?.connectionId ?? peer.connectionId
1688
2010
  );
1689
- this.onPeerDisconnected?.(
1690
- peer.descriptor.nodeRoutingId,
1691
- peer.descriptor.advertisedEndpoint,
1692
- peer.descriptor.lifecycleGeneration
1693
- );
2011
+ try {
2012
+ disconnectPhysical?.();
2013
+ } finally {
2014
+ // The logical admission has ended even when the physical disconnect reports a failure.
2015
+ this.onPeerDisconnected?.(
2016
+ peer.descriptor.nodeRoutingId,
2017
+ peer.descriptor.advertisedEndpoint,
2018
+ peer.descriptor.lifecycleGeneration
2019
+ );
2020
+ }
1694
2021
  }
1695
2022
 
1696
2023
  private requireStarted(): ZLinkRawRouterPort {
@@ -1698,10 +2025,7 @@ export class RawServiceMeshRuntime {
1698
2025
  return this.router;
1699
2026
  }
1700
2027
 
1701
- private async sendControl(
1702
- targetNodeRoutingId: string,
1703
- parts: readonly Uint8Array[]
1704
- ): Promise<boolean> {
2028
+ async sendControl(targetNodeRoutingId: string, parts: readonly Uint8Array[]): Promise<boolean> {
1705
2029
  return this.startControlSend(targetNodeRoutingId, decodeHeader(parts[0]!).command, () =>
1706
2030
  this.submitControl(targetNodeRoutingId, parts)
1707
2031
  );
@@ -1721,7 +2045,7 @@ export class RawServiceMeshRuntime {
1721
2045
  );
1722
2046
  }
1723
2047
 
1724
- private startControlSend(
2048
+ startControlSend(
1725
2049
  target: string,
1726
2050
  command: number,
1727
2051
  attempt: () => boolean | Promise<boolean>,
@@ -1732,11 +2056,7 @@ export class RawServiceMeshRuntime {
1732
2056
  if (command === M6aServiceWireCommand.hello)
1733
2057
  this.liveness.setAdmissionPending(true, performance.now());
1734
2058
  const existing = this.controlSends.get(key);
1735
- if (
1736
- existing !== undefined &&
1737
- existing.task !== undefined &&
1738
- (command !== M6aServiceWireCommand.hello || existing.current?.() === true)
1739
- ) {
2059
+ if (existing?.task !== undefined) {
1740
2060
  if (command !== M6aServiceWireCommand.hello) {
1741
2061
  existing.next = attempt;
1742
2062
  existing.current = current;
@@ -1752,7 +2072,7 @@ export class RawServiceMeshRuntime {
1752
2072
  const state: ControlSendState = { current, hello };
1753
2073
  const completeSend = () => {
1754
2074
  if (owner.controlSends.get(key) !== state) return;
1755
- if (hello !== undefined && current?.() === true) delete state.task;
2075
+ if (hello !== undefined) delete state.task;
1756
2076
  else owner.controlSends.delete(key);
1757
2077
  };
1758
2078
  const task = first.then(function drain(accepted): boolean | Promise<boolean> {
@@ -1805,6 +2125,7 @@ export class RawServiceMeshRuntime {
1805
2125
  targetNodeRoutingId: string,
1806
2126
  parts: readonly Uint8Array[]
1807
2127
  ): boolean | Promise<boolean> {
2128
+ if (!this.isPeerRouteReady(targetNodeRoutingId)) return false;
1808
2129
  try {
1809
2130
  const submission = this.requireStarted().submitSend(targetNodeRoutingId, parts);
1810
2131
  if (submission.result === SubmitResult.Ok) return true;
@@ -1827,6 +2148,10 @@ export class RawServiceMeshRuntime {
1827
2148
  }
1828
2149
  }
1829
2150
 
2151
+ function channelSendSubmitResult(accepted: boolean): SubmitResult {
2152
+ return accepted ? SubmitResult.Ok : SubmitResult.NotConnected;
2153
+ }
2154
+
1830
2155
  function controlSendKey(target: string, command: number): string {
1831
2156
  return `${command}:${target}`;
1832
2157
  }