@zlink-systems/framework 0.30.0 → 0.30.2

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 (456) 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/ZLinkEncodedPayload.d.ts +6 -0
  6. package/dist/contracts/Common/ZLinkEncodedPayload.js +18 -3
  7. package/dist/contracts/Common/ZLinkMessageMetadata.js +11 -5
  8. package/dist/contracts/Common/encoded-payload-storage.d.ts +1 -1
  9. package/dist/contracts/Common/encoded-payload-storage.js +5 -2
  10. package/dist/contracts/Configuration/Builders.d.ts +1 -1
  11. package/dist/contracts/Configuration/RegistrationBuilderPolicy.d.ts +2 -1
  12. package/dist/contracts/Configuration/RegistrationBuilderPolicy.js +5 -1
  13. package/dist/contracts/Configuration/RegistrationBuilders.js +2 -2
  14. package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -0
  15. package/dist/contracts/Dispatch/ZLinkDispatchOptions.d.ts +2 -2
  16. package/dist/contracts/Handlers/Attributes.d.ts +5 -13
  17. package/dist/contracts/Handlers/Attributes.js +7 -11
  18. package/dist/contracts/Handlers/JsonContract.d.ts +2 -46
  19. package/dist/contracts/Handlers/JsonContract.js +4 -153
  20. package/dist/contracts/Locations/Rows.d.ts +2 -0
  21. package/dist/contracts/Spots/Contracts.d.ts +6 -1
  22. package/dist/contracts/Spots/ZLinkSpot.d.ts +3 -3
  23. package/dist/nest-integration.d.ts +1 -1
  24. package/dist/nest-integration.js +2 -1
  25. package/dist/runtime/abort.d.ts +5 -0
  26. package/dist/runtime/abort.js +10 -0
  27. package/dist/runtime/actors/actor-authority-publication.js +3 -2
  28. package/dist/runtime/actors/actor-client.d.ts +1 -2
  29. package/dist/runtime/actors/actor-client.js +52 -57
  30. package/dist/runtime/actors/actor-context.d.ts +2 -0
  31. package/dist/runtime/actors/actor-context.js +46 -9
  32. package/dist/runtime/actors/actor-handoff.d.ts +8 -4
  33. package/dist/runtime/actors/actor-handoff.js +219 -92
  34. package/dist/runtime/actors/actor-join-relocation.d.ts +3 -0
  35. package/dist/runtime/actors/actor-local-native-join.js +13 -12
  36. package/dist/runtime/actors/actor-mailbox.js +27 -1
  37. package/dist/runtime/actors/actor-remote-joiner.js +3 -1
  38. package/dist/runtime/actors/actor-remote-wire.js +15 -0
  39. package/dist/runtime/actors/actor-reply.d.ts +4 -0
  40. package/dist/runtime/actors/actor-reply.js +29 -0
  41. package/dist/runtime/actors/actor-routed-join-transport.d.ts +6 -2
  42. package/dist/runtime/actors/actor-routed-json-request.d.ts +2 -0
  43. package/dist/runtime/actors/actor-runtime-contracts.d.ts +2 -0
  44. package/dist/runtime/actors/actor-runtime-state.js +2 -20
  45. package/dist/runtime/actors/actor-source-transfer.d.ts +1 -17
  46. package/dist/runtime/actors/index.d.ts +8 -3
  47. package/dist/runtime/actors/index.js +55 -27
  48. package/dist/runtime/actors/session-disconnected-wire.d.ts +8 -0
  49. package/dist/runtime/actors/session-disconnected-wire.js +48 -0
  50. package/dist/runtime/actors/spot-actor-dispatch.js +2 -2
  51. package/dist/runtime/admission.d.ts +9 -1
  52. package/dist/runtime/admission.js +56 -23
  53. package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +6 -8
  54. package/dist/runtime/application-jobs/application-ingress-record-owner.js +39 -22
  55. package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +15 -1
  56. package/dist/runtime/application-jobs/application-job-queue-scope.js +61 -20
  57. package/dist/runtime/application-jobs/contracts.d.ts +12 -1
  58. package/dist/runtime/backend/contracts/index.d.ts +37 -12
  59. package/dist/runtime/backend/mesh-actor-session-node-adapter.js +2 -2
  60. package/dist/runtime/backend/mesh-completion-table.d.ts +9 -1
  61. package/dist/runtime/backend/mesh-completion-table.js +154 -49
  62. package/dist/runtime/backend/mesh-dispatch-pump.d.ts +28 -1
  63. package/dist/runtime/backend/mesh-dispatch-pump.js +208 -101
  64. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -2
  65. package/dist/runtime/backend/node/node-backend-adapter-support.js +16 -9
  66. package/dist/runtime/backend/node/node-dealer-channel-client-transport.d.ts +1 -1
  67. package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +5 -2
  68. package/dist/runtime/backend/node/node-raw-binding-port.js +124 -72
  69. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +46 -13
  70. package/dist/runtime/backend/node/node-raw-mesh-backend.js +181 -164
  71. package/dist/runtime/backend/node/node-socket-backend-adapter.d.ts +1 -1
  72. package/dist/runtime/backend/node/node-socket-backend-adapter.js +11 -11
  73. package/dist/runtime/backend/raw-binding-port.d.ts +2 -2
  74. package/dist/runtime/backend/runtime-message.d.ts +1 -0
  75. package/dist/runtime/backend/runtime-message.js +5 -0
  76. package/dist/runtime/backend/runtime-values.d.ts +1 -0
  77. package/dist/runtime/backend/runtime-values.js +2 -1
  78. package/dist/runtime/channels/channel-clients.d.ts +2 -0
  79. package/dist/runtime/channels/channel-clients.js +75 -23
  80. package/dist/runtime/channels/channel-dispatch-pipeline.d.ts +25 -5
  81. package/dist/runtime/channels/channel-dispatch-pipeline.js +111 -56
  82. package/dist/runtime/channels/channel-dispatch-services.d.ts +1 -0
  83. package/dist/runtime/channels/channel-dispatch-services.js +45 -0
  84. package/dist/runtime/channels/channel-dispatchers.d.ts +4 -3
  85. package/dist/runtime/channels/channel-dispatchers.js +62 -54
  86. package/dist/runtime/channels/channel-envelope-inspection.d.ts +1 -2
  87. package/dist/runtime/channels/channel-envelope-inspection.js +1 -16
  88. package/dist/runtime/channels/channel-envelope.d.ts +2 -0
  89. package/dist/runtime/channels/channel-envelope.js +146 -28
  90. package/dist/runtime/channels/channel-outbound-operations.d.ts +2 -2
  91. package/dist/runtime/channels/channel-outbound-operations.js +8 -8
  92. package/dist/runtime/channels/channel-runtime-lifecycle.d.ts +4 -3
  93. package/dist/runtime/channels/channel-runtime-lifecycle.js +19 -24
  94. package/dist/runtime/channels/channel-runtime-manager.d.ts +9 -8
  95. package/dist/runtime/channels/channel-runtime-manager.js +16 -16
  96. package/dist/runtime/channels/channel-transports.d.ts +18 -16
  97. package/dist/runtime/channels/channel-transports.js +77 -58
  98. package/dist/runtime/channels/client-server-location-runtime.js +5 -4
  99. package/dist/runtime/channels/dispatch-error-reporter.d.ts +2 -1
  100. package/dist/runtime/channels/dispatch-error-reporter.js +2 -1
  101. package/dist/runtime/channels/fanout-location-runtime.d.ts +1 -1
  102. package/dist/runtime/channels/fanout-location-runtime.js +3 -3
  103. package/dist/runtime/channels/local-spot-route-transport.d.ts +1 -1
  104. package/dist/runtime/channels/local-spot-route-transport.js +2 -1
  105. package/dist/runtime/channels/router-socket-spot-route-transport.d.ts +2 -2
  106. package/dist/runtime/channels/router-socket-spot-route-transport.js +4 -4
  107. package/dist/runtime/channels/source-spot-router.d.ts +2 -2
  108. package/dist/runtime/channels/source-spot-router.js +4 -4
  109. package/dist/runtime/channels/spot-node-route-transport.d.ts +2 -2
  110. package/dist/runtime/channels/spot-node-route-transport.js +4 -4
  111. package/dist/runtime/channels/spot-route-bridge-transport.d.ts +2 -2
  112. package/dist/runtime/channels/spot-route-bridge-transport.js +4 -4
  113. package/dist/runtime/channels/spot-route-dispatch-strategy.d.ts +5 -4
  114. package/dist/runtime/channels/spot-route-dispatch-strategy.js +18 -18
  115. package/dist/runtime/diagnostics/flow-context.js +19 -7
  116. package/dist/runtime/diagnostics/index.js +4 -9
  117. package/dist/runtime/diagnostics/message-flow.d.ts +8 -0
  118. package/dist/runtime/diagnostics/message-flow.js +35 -5
  119. package/dist/runtime/diagnostics/runtime-metrics.d.ts +2 -2
  120. package/dist/runtime/diagnostics/runtime-metrics.js +10 -8
  121. package/dist/runtime/execution/index.d.ts +4 -2
  122. package/dist/runtime/execution/index.js +33 -10
  123. package/dist/runtime/execution/serial-execution-queue.d.ts +5 -0
  124. package/dist/runtime/execution/serial-execution-queue.js +22 -2
  125. package/dist/runtime/execution/state-lane.d.ts +2 -0
  126. package/dist/runtime/execution/state-lane.js +5 -0
  127. package/dist/runtime/foundation/actor-join-recovery-codec.js +2 -2
  128. package/dist/runtime/foundation/durable-service-request.d.ts +14 -0
  129. package/dist/runtime/foundation/durable-service-request.js +98 -0
  130. package/dist/runtime/foundation/operation-identity.d.ts +1 -0
  131. package/dist/runtime/foundation/operation-identity.js +5 -1
  132. package/dist/runtime/foundation/operation-registry.d.ts +25 -1
  133. package/dist/runtime/foundation/operation-registry.js +86 -18
  134. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +61 -8
  135. package/dist/runtime/foundation/raw-service-mesh-runtime.js +486 -266
  136. package/dist/runtime/foundation/service-liveness-registry.d.ts +12 -1
  137. package/dist/runtime/foundation/service-liveness-registry.js +38 -0
  138. package/dist/runtime/foundation/service-mailbox.d.ts +7 -0
  139. package/dist/runtime/foundation/service-maintenance-runtime.js +1 -6
  140. package/dist/runtime/foundation/service-relocation-object-owner.d.ts +3 -3
  141. package/dist/runtime/foundation/service-relocation-object-owner.js +48 -34
  142. package/dist/runtime/foundation/service-relocation-runtime.d.ts +2 -0
  143. package/dist/runtime/foundation/service-relocation-runtime.js +20 -4
  144. package/dist/runtime/foundation/service-runtime-contracts.d.ts +10 -1
  145. package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +2 -1
  146. package/dist/runtime/foundation/service-stateful-registry.d.ts +5 -4
  147. package/dist/runtime/foundation/service-stateful-registry.js +24 -26
  148. package/dist/runtime/foundation/service-stateful-runtime.d.ts +75 -9
  149. package/dist/runtime/foundation/service-stateful-runtime.js +486 -218
  150. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +14 -6
  151. package/dist/runtime/foundation/service-stateful-wire-codec.js +48 -27
  152. package/dist/runtime/foundation/service-topology-registry.d.ts +12 -2
  153. package/dist/runtime/foundation/service-topology-registry.js +61 -27
  154. package/dist/runtime/foundation/service-wire-binary-primitives.d.ts +6 -1
  155. package/dist/runtime/foundation/service-wire-binary-primitives.js +27 -11
  156. package/dist/runtime/foundation/service-wire-constants.generated.d.ts +1 -0
  157. package/dist/runtime/foundation/service-wire-constants.generated.js +1 -0
  158. package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +13 -3
  159. package/dist/runtime/foundation/service-wire-m6a-codec.js +189 -78
  160. package/dist/runtime/handlers/handler-instance-scope.d.ts +8 -0
  161. package/dist/runtime/handlers/handler-instance-scope.js +54 -11
  162. package/dist/runtime/host/actor-packet-relay.d.ts +6 -4
  163. package/dist/runtime/host/actor-packet-relay.js +59 -72
  164. package/dist/runtime/host/actor-placement-coordinator.js +25 -8
  165. package/dist/runtime/host/actor-runtime-options-factory.js +2 -2
  166. package/dist/runtime/host/actor-transfer-runtime.d.ts +7 -31
  167. package/dist/runtime/host/actor-transfer-runtime.js +85 -331
  168. package/dist/runtime/host/application-job-queue.d.ts +40 -7
  169. package/dist/runtime/host/application-job-queue.js +137 -42
  170. package/dist/runtime/host/entry-actor-runtime.d.ts +1 -0
  171. package/dist/runtime/host/entry-actor-runtime.js +3 -0
  172. package/dist/runtime/host/index.d.ts +12 -2
  173. package/dist/runtime/host/index.js +300 -202
  174. package/dist/runtime/host/location-runtime-owner.js +2 -6
  175. package/dist/runtime/host/mesh-router-resolver.d.ts +4 -0
  176. package/dist/runtime/host/mesh-router-resolver.js +14 -6
  177. package/dist/runtime/host/remote-bound-session-relay.d.ts +2 -4
  178. package/dist/runtime/host/remote-bound-session-relay.js +73 -208
  179. package/dist/runtime/host/route-mesh-runtime.d.ts +2 -2
  180. package/dist/runtime/host/route-mesh-runtime.js +6 -4
  181. package/dist/runtime/host/runtime-shutdown.d.ts +1 -0
  182. package/dist/runtime/host/runtime-shutdown.js +3 -2
  183. package/dist/runtime/host/service-relocation-control.d.ts +1 -0
  184. package/dist/runtime/host/service-relocation-control.js +10 -4
  185. package/dist/runtime/host/service-relocation-host-runtime.d.ts +22 -14
  186. package/dist/runtime/host/service-relocation-host-runtime.js +754 -599
  187. package/dist/runtime/host/spot-address-transport.js +3 -2
  188. package/dist/runtime/host/spot-runtime-options-factory.js +16 -7
  189. package/dist/runtime/host/user-spot-creation-coordinator.js +33 -84
  190. package/dist/runtime/locations/actor-location-claims.d.ts +2 -2
  191. package/dist/runtime/locations/actor-location-claims.js +34 -19
  192. package/dist/runtime/locations/auto-connect-loop.d.ts +2 -0
  193. package/dist/runtime/locations/auto-connect-loop.js +22 -14
  194. package/dist/runtime/locations/auto-connect-reconciler.js +0 -6
  195. package/dist/runtime/locations/auto-connect-types.d.ts +0 -1
  196. package/dist/runtime/locations/descriptor-publication.d.ts +4 -0
  197. package/dist/runtime/locations/descriptor-publication.js +11 -0
  198. package/dist/runtime/locations/domain-store-contract.d.ts +1 -0
  199. package/dist/runtime/locations/in-memory-authority-store.d.ts +7 -1
  200. package/dist/runtime/locations/in-memory-authority-store.js +56 -19
  201. package/dist/runtime/locations/in-memory-location-store.js +1 -10
  202. package/dist/runtime/locations/index.d.ts +1 -0
  203. package/dist/runtime/locations/index.js +1 -0
  204. package/dist/runtime/locations/lease-tracker.js +1 -6
  205. package/dist/runtime/locations/lifecycle.d.ts +1 -2
  206. package/dist/runtime/locations/lifecycle.js +2 -5
  207. package/dist/runtime/locations/location-store-repository.d.ts +51 -1
  208. package/dist/runtime/locations/location-store-repository.js +363 -139
  209. package/dist/runtime/locations/relocation-blob.d.ts +18 -1
  210. package/dist/runtime/locations/relocation-blob.js +52 -1
  211. package/dist/runtime/locations/request-source-resolver.d.ts +37 -0
  212. package/dist/runtime/locations/request-source-resolver.js +80 -0
  213. package/dist/runtime/locations/resolvers.js +1 -1
  214. package/dist/runtime/locations/runtime.d.ts +7 -3
  215. package/dist/runtime/locations/runtime.js +61 -53
  216. package/dist/runtime/messaging/framework-json-v1.d.ts +4 -6
  217. package/dist/runtime/messaging/framework-json-v1.js +38 -294
  218. package/dist/runtime/messaging/packet-name.d.ts +1 -1
  219. package/dist/runtime/messaging/packet-name.js +8 -24
  220. package/dist/runtime/messaging/payload-codec.d.ts +7 -1
  221. package/dist/runtime/messaging/payload-codec.js +7 -6
  222. package/dist/runtime/messaging/request-result.d.ts +2 -0
  223. package/dist/runtime/messaging/request-result.js +57 -0
  224. package/dist/runtime/messaging/submission-result.d.ts +1 -0
  225. package/dist/runtime/messaging/submission-result.js +5 -2
  226. package/dist/runtime/protocol/service_wire_codec.generated.d.ts +34 -1
  227. package/dist/runtime/protocol/service_wire_codec.generated.js +229 -0
  228. package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +16 -1
  229. package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +37 -8
  230. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +7 -2
  231. package/dist/runtime/spots/formal-remote-actor-admission-registry.js +36 -21
  232. package/dist/runtime/spots/index.d.ts +21 -11
  233. package/dist/runtime/spots/index.js +243 -178
  234. package/dist/runtime/spots/spot-activation-registry.d.ts +4 -2
  235. package/dist/runtime/spots/spot-activation-registry.js +21 -11
  236. package/dist/runtime/spots/spot-activation-state.d.ts +1 -1
  237. package/dist/runtime/spots/spot-activation-state.js +7 -7
  238. package/dist/runtime/spots/spot-activation.d.ts +7 -3
  239. package/dist/runtime/spots/spot-activation.js +140 -52
  240. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +1 -0
  241. package/dist/runtime/spots/spot-actor-admission-coordinator.js +13 -22
  242. package/dist/runtime/spots/spot-actor-membership.d.ts +0 -1
  243. package/dist/runtime/spots/spot-actor-membership.js +0 -8
  244. package/dist/runtime/spots/spot-actor-packet-dispatch.js +33 -8
  245. package/dist/runtime/spots/spot-actor-packet-drain.js +5 -33
  246. package/dist/runtime/spots/spot-entry-activation.d.ts +2 -0
  247. package/dist/runtime/spots/spot-entry-activation.js +20 -11
  248. package/dist/runtime/spots/spot-handler-registry.d.ts +7 -4
  249. package/dist/runtime/spots/spot-handler-registry.js +24 -10
  250. package/dist/runtime/spots/spot-manager-internal-contracts.d.ts +2 -2
  251. package/dist/runtime/spots/spot-manager-public.js +2 -2
  252. package/dist/runtime/spots/spot-node-autoconnect.d.ts +2 -1
  253. package/dist/runtime/spots/spot-node-autoconnect.js +22 -42
  254. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +19 -2
  255. package/dist/runtime/spots/spot-node-runtime-manager.js +115 -50
  256. package/dist/runtime/spots/spot-outbound.d.ts +3 -0
  257. package/dist/runtime/spots/spot-outbound.js +27 -18
  258. package/dist/runtime/spots/spot-remote-codec.js +5 -1
  259. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.d.ts +1 -0
  260. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.js +1 -1
  261. package/dist/runtime/spots/spot-runtime-ports.d.ts +1 -1
  262. package/dist/runtime/spots/spot-serial-executor.d.ts +10 -0
  263. package/dist/runtime/spots/spot-serial-executor.js +61 -8
  264. package/dist/runtime/spots/spot-serial-turn-executor.js +19 -2
  265. package/dist/runtime/spots/spot-subscription-dispatch.js +1 -4
  266. package/dist/runtime/spots/spot-timer.d.ts +57 -5
  267. package/dist/runtime/spots/spot-timer.js +288 -103
  268. package/dist/runtime/streams/actor-session-binding-registry.d.ts +42 -11
  269. package/dist/runtime/streams/actor-session-binding-registry.js +165 -48
  270. package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +8 -4
  271. package/dist/runtime/streams/bound-actor-relay-sender.d.ts +2 -1
  272. package/dist/runtime/streams/bound-actor-relay-sender.js +15 -8
  273. package/dist/runtime/streams/bound-session-service.d.ts +1 -1
  274. package/dist/runtime/streams/bound-session-service.js +6 -11
  275. package/dist/runtime/streams/index.d.ts +3 -2
  276. package/dist/runtime/streams/index.js +6 -4
  277. package/dist/runtime/streams/managed-stream.js +3 -2
  278. package/dist/runtime/streams/native-fallback-bound-session.js +8 -73
  279. package/dist/runtime/streams/session-actor-coordinator.d.ts +2 -8
  280. package/dist/runtime/streams/session-actor-coordinator.js +15 -32
  281. package/dist/runtime/streams/stream-binding-runtime-ports.d.ts +3 -10
  282. package/dist/runtime/streams/stream-session-runtime.d.ts +1 -1
  283. package/dist/runtime/streams/stream-session-runtime.js +14 -31
  284. package/dist/runtime/workers/index.js +1 -1
  285. package/package.json +3 -3
  286. package/src/contracts/Actors/ZLinkActorClient.ts +2 -1
  287. package/src/contracts/Channels/Calls.ts +2 -1
  288. package/src/contracts/Common/ZLinkEncodedPayload.ts +24 -4
  289. package/src/contracts/Common/ZLinkMessageMetadata.ts +13 -8
  290. package/src/contracts/Common/encoded-payload-storage.ts +6 -3
  291. package/src/contracts/Configuration/Builders.ts +4 -1
  292. package/src/contracts/Configuration/RegistrationBuilderPolicy.ts +7 -2
  293. package/src/contracts/Configuration/RegistrationBuilders.ts +6 -2
  294. package/src/contracts/Configuration/RegistrationTypes.ts +1 -0
  295. package/src/contracts/Dispatch/ZLinkDispatchOptions.ts +2 -1
  296. package/src/contracts/Handlers/Attributes.ts +14 -21
  297. package/src/contracts/Handlers/JsonContract.ts +8 -216
  298. package/src/contracts/Locations/Rows.ts +2 -0
  299. package/src/contracts/Spots/Contracts.ts +10 -1
  300. package/src/contracts/Spots/ZLinkSpot.ts +3 -3
  301. package/src/nest-integration.ts +1 -0
  302. package/src/runtime/abort.ts +12 -0
  303. package/src/runtime/actors/actor-authority-publication.ts +3 -2
  304. package/src/runtime/actors/actor-client.ts +100 -85
  305. package/src/runtime/actors/actor-context.ts +49 -9
  306. package/src/runtime/actors/actor-handoff.ts +291 -110
  307. package/src/runtime/actors/actor-join-relocation.ts +3 -0
  308. package/src/runtime/actors/actor-local-native-join.ts +21 -26
  309. package/src/runtime/actors/actor-mailbox.ts +40 -2
  310. package/src/runtime/actors/actor-remote-joiner.ts +6 -1
  311. package/src/runtime/actors/actor-remote-wire.ts +17 -0
  312. package/src/runtime/actors/actor-reply.ts +42 -0
  313. package/src/runtime/actors/actor-routed-join-transport.ts +14 -4
  314. package/src/runtime/actors/actor-routed-json-request.ts +10 -2
  315. package/src/runtime/actors/actor-runtime-contracts.ts +2 -0
  316. package/src/runtime/actors/actor-runtime-state.ts +2 -20
  317. package/src/runtime/actors/actor-source-transfer.ts +1 -34
  318. package/src/runtime/actors/index.ts +60 -32
  319. package/src/runtime/actors/session-disconnected-wire.ts +66 -0
  320. package/src/runtime/actors/spot-actor-dispatch.ts +4 -2
  321. package/src/runtime/admission.ts +69 -21
  322. package/src/runtime/application-jobs/application-ingress-record-owner.ts +41 -25
  323. package/src/runtime/application-jobs/application-job-queue-scope.ts +83 -22
  324. package/src/runtime/application-jobs/contracts.ts +16 -1
  325. package/src/runtime/backend/contracts/index.ts +92 -18
  326. package/src/runtime/backend/mesh-actor-session-node-adapter.ts +5 -3
  327. package/src/runtime/backend/mesh-completion-table.ts +160 -57
  328. package/src/runtime/backend/mesh-dispatch-pump.ts +237 -108
  329. package/src/runtime/backend/node/node-backend-adapter-support.ts +17 -8
  330. package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +12 -2
  331. package/src/runtime/backend/node/node-raw-binding-port.ts +148 -85
  332. package/src/runtime/backend/node/node-raw-mesh-backend.ts +315 -201
  333. package/src/runtime/backend/node/node-socket-backend-adapter.ts +26 -11
  334. package/src/runtime/backend/raw-binding-port.ts +7 -2
  335. package/src/runtime/backend/runtime-message.ts +6 -0
  336. package/src/runtime/backend/runtime-values.ts +1 -0
  337. package/src/runtime/channels/channel-clients.ts +132 -58
  338. package/src/runtime/channels/channel-dispatch-pipeline.ts +198 -114
  339. package/src/runtime/channels/channel-dispatch-services.ts +69 -1
  340. package/src/runtime/channels/channel-dispatchers.ts +136 -90
  341. package/src/runtime/channels/channel-envelope-inspection.ts +2 -16
  342. package/src/runtime/channels/channel-envelope.ts +225 -31
  343. package/src/runtime/channels/channel-outbound-operations.ts +22 -8
  344. package/src/runtime/channels/channel-runtime-lifecycle.ts +13 -13
  345. package/src/runtime/channels/channel-runtime-manager.ts +41 -17
  346. package/src/runtime/channels/channel-transports.ts +171 -112
  347. package/src/runtime/channels/client-server-location-runtime.ts +11 -10
  348. package/src/runtime/channels/dispatch-error-reporter.ts +4 -2
  349. package/src/runtime/channels/fanout-location-runtime.ts +17 -9
  350. package/src/runtime/channels/local-spot-route-transport.ts +3 -1
  351. package/src/runtime/channels/router-socket-spot-route-transport.ts +13 -4
  352. package/src/runtime/channels/source-spot-router.ts +8 -4
  353. package/src/runtime/channels/spot-node-route-transport.ts +8 -4
  354. package/src/runtime/channels/spot-route-bridge-transport.ts +6 -4
  355. package/src/runtime/channels/spot-route-dispatch-strategy.ts +47 -18
  356. package/src/runtime/diagnostics/flow-context.ts +20 -7
  357. package/src/runtime/diagnostics/index.ts +1 -7
  358. package/src/runtime/diagnostics/message-flow.ts +47 -9
  359. package/src/runtime/diagnostics/runtime-metrics.ts +12 -10
  360. package/src/runtime/execution/index.ts +40 -14
  361. package/src/runtime/execution/serial-execution-queue.ts +33 -4
  362. package/src/runtime/execution/state-lane.ts +7 -0
  363. package/src/runtime/foundation/actor-join-recovery-codec.ts +2 -2
  364. package/src/runtime/foundation/durable-service-request.ts +137 -0
  365. package/src/runtime/foundation/operation-identity.ts +5 -1
  366. package/src/runtime/foundation/operation-registry.ts +98 -20
  367. package/src/runtime/foundation/raw-service-mesh-runtime.ts +704 -336
  368. package/src/runtime/foundation/service-liveness-registry.ts +39 -0
  369. package/src/runtime/foundation/service-mailbox.ts +8 -0
  370. package/src/runtime/foundation/service-maintenance-runtime.ts +1 -8
  371. package/src/runtime/foundation/service-relocation-object-owner.ts +55 -36
  372. package/src/runtime/foundation/service-relocation-runtime.ts +25 -4
  373. package/src/runtime/foundation/service-runtime-contracts.ts +19 -2
  374. package/src/runtime/foundation/service-session-binding-ingress-port.ts +2 -1
  375. package/src/runtime/foundation/service-stateful-registry.ts +38 -35
  376. package/src/runtime/foundation/service-stateful-runtime.ts +732 -275
  377. package/src/runtime/foundation/service-stateful-wire-codec.ts +49 -16
  378. package/src/runtime/foundation/service-topology-registry.ts +91 -36
  379. package/src/runtime/foundation/service-wire-binary-primitives.ts +35 -12
  380. package/src/runtime/foundation/service-wire-constants.generated.ts +1 -0
  381. package/src/runtime/foundation/service-wire-m6a-codec.ts +231 -76
  382. package/src/runtime/handlers/handler-instance-scope.ts +67 -16
  383. package/src/runtime/host/actor-packet-relay.ts +113 -94
  384. package/src/runtime/host/actor-placement-coordinator.ts +31 -8
  385. package/src/runtime/host/actor-runtime-options-factory.ts +13 -2
  386. package/src/runtime/host/actor-transfer-runtime.ts +104 -430
  387. package/src/runtime/host/application-job-queue.ts +160 -41
  388. package/src/runtime/host/entry-actor-runtime.ts +4 -0
  389. package/src/runtime/host/index.ts +473 -271
  390. package/src/runtime/host/location-runtime-owner.ts +2 -6
  391. package/src/runtime/host/mesh-router-resolver.ts +13 -5
  392. package/src/runtime/host/remote-bound-session-relay.ts +88 -271
  393. package/src/runtime/host/route-mesh-runtime.ts +12 -6
  394. package/src/runtime/host/runtime-shutdown.ts +15 -4
  395. package/src/runtime/host/service-relocation-control.ts +11 -5
  396. package/src/runtime/host/service-relocation-host-runtime.ts +1092 -759
  397. package/src/runtime/host/spot-address-transport.ts +4 -2
  398. package/src/runtime/host/spot-runtime-options-factory.ts +20 -7
  399. package/src/runtime/host/user-spot-creation-coordinator.ts +41 -100
  400. package/src/runtime/locations/actor-location-claims.ts +52 -30
  401. package/src/runtime/locations/auto-connect-loop.ts +22 -14
  402. package/src/runtime/locations/auto-connect-reconciler.ts +0 -6
  403. package/src/runtime/locations/auto-connect-types.ts +0 -1
  404. package/src/runtime/locations/descriptor-publication.ts +7 -0
  405. package/src/runtime/locations/domain-store-contract.ts +5 -0
  406. package/src/runtime/locations/in-memory-authority-store.ts +85 -23
  407. package/src/runtime/locations/in-memory-location-store.ts +1 -12
  408. package/src/runtime/locations/index.ts +1 -0
  409. package/src/runtime/locations/lease-tracker.ts +2 -9
  410. package/src/runtime/locations/lifecycle.ts +8 -26
  411. package/src/runtime/locations/location-store-repository.ts +628 -152
  412. package/src/runtime/locations/relocation-blob.ts +75 -1
  413. package/src/runtime/locations/request-source-resolver.ts +138 -0
  414. package/src/runtime/locations/resolvers.ts +2 -2
  415. package/src/runtime/locations/runtime.ts +98 -64
  416. package/src/runtime/messaging/framework-json-v1.ts +5 -325
  417. package/src/runtime/messaging/packet-name.ts +5 -25
  418. package/src/runtime/messaging/payload-codec.ts +8 -11
  419. package/src/runtime/messaging/request-result.ts +62 -0
  420. package/src/runtime/messaging/submission-result.ts +5 -1
  421. package/src/runtime/protocol/service_wire_codec.generated.ts +218 -5
  422. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +16 -13
  423. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +46 -25
  424. package/src/runtime/spots/index.ts +389 -283
  425. package/src/runtime/spots/spot-activation-registry.ts +25 -13
  426. package/src/runtime/spots/spot-activation-state.ts +12 -19
  427. package/src/runtime/spots/spot-activation.ts +198 -96
  428. package/src/runtime/spots/spot-actor-admission-coordinator.ts +25 -55
  429. package/src/runtime/spots/spot-actor-membership.ts +0 -15
  430. package/src/runtime/spots/spot-actor-packet-dispatch.ts +56 -11
  431. package/src/runtime/spots/spot-actor-packet-drain.ts +9 -52
  432. package/src/runtime/spots/spot-entry-activation.ts +33 -21
  433. package/src/runtime/spots/spot-handler-registry.ts +56 -12
  434. package/src/runtime/spots/spot-manager-internal-contracts.ts +2 -2
  435. package/src/runtime/spots/spot-manager-public.ts +2 -2
  436. package/src/runtime/spots/spot-node-autoconnect.ts +28 -47
  437. package/src/runtime/spots/spot-node-runtime-manager.ts +197 -67
  438. package/src/runtime/spots/spot-outbound.ts +39 -19
  439. package/src/runtime/spots/spot-remote-codec.ts +9 -2
  440. package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +2 -1
  441. package/src/runtime/spots/spot-runtime-ports.ts +1 -1
  442. package/src/runtime/spots/spot-serial-executor.ts +97 -25
  443. package/src/runtime/spots/spot-serial-turn-executor.ts +22 -3
  444. package/src/runtime/spots/spot-subscription-dispatch.ts +1 -4
  445. package/src/runtime/spots/spot-timer.ts +418 -158
  446. package/src/runtime/streams/actor-session-binding-registry.ts +236 -64
  447. package/src/runtime/streams/actor-session-binding-runtime-owner.ts +16 -8
  448. package/src/runtime/streams/bound-actor-relay-sender.ts +23 -9
  449. package/src/runtime/streams/bound-session-service.ts +7 -10
  450. package/src/runtime/streams/index.ts +12 -6
  451. package/src/runtime/streams/managed-stream.ts +4 -2
  452. package/src/runtime/streams/native-fallback-bound-session.ts +14 -103
  453. package/src/runtime/streams/session-actor-coordinator.ts +39 -88
  454. package/src/runtime/streams/stream-binding-runtime-ports.ts +3 -9
  455. package/src/runtime/streams/stream-session-runtime.ts +13 -34
  456. package/src/runtime/workers/index.ts +1 -1
@@ -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,16 @@ 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';
16
- import { OperationRegistry, type PendingOperation } from './operation-registry';
17
+ import { SERVICE_WIRE_PREFIX_SIZE } from './service-wire-binary-primitives';
18
+ import {
19
+ OperationRegistry,
20
+ OperationTimeoutError,
21
+ type OperationObserver,
22
+ type PendingOperation
23
+ } from './operation-registry';
17
24
  import { ServiceLivenessRegistry, type ServiceLivenessTick } from './service-liveness-registry';
18
25
  import { ServiceMailbox, type ServiceMailboxRecord } from './service-mailbox';
19
26
  import {
20
- sameServiceNodeDescriptor,
21
27
  ServiceTopologyRegistry,
22
28
  validateDescriptor,
23
29
  type AdmittedServicePeer,
@@ -67,6 +73,19 @@ export interface RawServiceRequestResult {
67
73
  readonly payload?: ServiceApplicationPayload;
68
74
  }
69
75
 
76
+ /** Receives observed request terminals; the context is the caller's operation kind. */
77
+ export type RawServiceRequestObserver = OperationObserver<RawServiceRequestResult, number>;
78
+
79
+ /**
80
+ * A registered request. An observed remote request has no Promise; its
81
+ * terminal reaches the observer. Local and Promise-contract requests carry
82
+ * the Promise.
83
+ */
84
+ export interface RawServiceObservedRequest {
85
+ readonly id: bigint;
86
+ readonly promise?: Promise<RawServiceRequestResult>;
87
+ }
88
+
70
89
  type RawServiceChannelTargetSelection =
71
90
  | { readonly kind: 'selected'; readonly peer: AdmittedServicePeer }
72
91
  | {
@@ -84,6 +103,9 @@ type RawServiceChannelTargetSelection =
84
103
  type ServiceApplicationPayloadInput = ServiceApplicationPayload | Buffer;
85
104
 
86
105
  export interface RawServiceIngressRecord {
106
+ /** Source identity recorded once by the first accepting owner (Location runtime §9.3). */
107
+ requestSource?: import('../locations/request-source-resolver').ZLinkRequestSourceFence;
108
+ readonly onAdmitted?: () => void;
87
109
  readonly command: number;
88
110
  readonly flags: number;
89
111
  readonly sourceRoutingId: string;
@@ -129,6 +151,7 @@ export interface RawServiceMeshRuntimeOptions {
129
151
  // Framework error code 13 (RequestTargetNotFound) is encoded as 14 on a
130
152
  // RequestResult.NotFound reply. Boundary transport results keep failureCode 0.
131
153
  const RAW_MESH_RECEIVE_RECORD_BUDGET = 64;
154
+ const NODE_SEND_HEADER = encodeNodeSendHeader();
132
155
  const RAW_MESH_RECEIVE_BYTE_BUDGET = 4 * 1024 * 1024;
133
156
  export const RAW_MESH_RECEIVE_TIME_BUDGET_MS = 2;
134
157
 
@@ -140,6 +163,14 @@ const livenessCodec = createServiceWireCodec({
140
163
  commands: M6aServiceWireCommand
141
164
  });
142
165
 
166
+ interface ControlSendState {
167
+ task?: Promise<boolean>;
168
+ next?: () => boolean | Promise<boolean>;
169
+ current?: () => boolean;
170
+ // Admission owns the first HELLO until ADMIT or route termination.
171
+ hello?: Uint8Array;
172
+ }
173
+
143
174
  /**
144
175
  * RouteMesh M6A runtime built only on the public raw binding package.
145
176
  * It owns protocol, admission, mailbox, request and liveness state.
@@ -149,7 +180,7 @@ export class RawServiceMeshRuntime {
149
180
  readonly mailbox: ServiceMailbox;
150
181
  readonly liveness: ServiceLivenessRegistry;
151
182
 
152
- private readonly operations: OperationRegistry<RawServiceRequestResult>;
183
+ private readonly operations: OperationRegistry<RawServiceRequestResult, number>;
153
184
  private readonly expectedPeers = new Map<
154
185
  string,
155
186
  {
@@ -158,11 +189,11 @@ export class RawServiceMeshRuntime {
158
189
  readonly endpoint?: string;
159
190
  readonly securityIdentity?: string;
160
191
  readonly lifecycleGeneration?: bigint;
161
- helloSubmittedGeneration?: bigint;
162
192
  }
163
193
  >();
164
194
  private readonly endpointOnlyPeers = new Set<string>();
165
195
  private readonly peerConnectionIntentRemoved = new Set<(nodeRoutingId: string) => void>();
196
+ private readonly peerRouteChanged = new Set<(nodeRoutingId: string) => void>();
166
197
  /**
167
198
  * The last observed Core selected route of each RID (Core ROUTER §10.1):
168
199
  * RID -> route generation. Only `observeSelectedRoutes` replaces it.
@@ -170,18 +201,13 @@ export class RawServiceMeshRuntime {
170
201
  private selectedRoutes = new Map<string, bigint>();
171
202
  private readonly bindingPort: ZLinkRawBindingPort;
172
203
  private readonly applicationJobQueue: ApplicationJobQueuePort;
204
+ private readonly nodeMailboxOwner: string;
173
205
  private readonly peerAdmissionSealed?: () => boolean;
174
206
  private readonly applicationJobStop = new AbortController();
175
207
  private receiveAcquisition?: Promise<ApplicationJobPermitPort>;
176
208
  private receivePermit?: ApplicationJobPermitPort;
177
- private readonly controlSends = new Map<
178
- string,
179
- {
180
- readonly task: Promise<boolean>;
181
- next?: () => boolean | Promise<boolean>;
182
- current?: () => boolean;
183
- }
184
- >();
209
+ private receiveRotation?: ApplicationJobReceiveRotationPort;
210
+ private readonly controlSends = new Map<string, ControlSendState>();
185
211
  private infrastructureFailure?: { readonly error: unknown };
186
212
  private readonly onReceiveReady?: () => void;
187
213
  private readonly infrastructureTaskRunner?: RawServiceMeshRuntimeOptions['infrastructureTaskRunner'];
@@ -195,6 +221,7 @@ export class RawServiceMeshRuntime {
195
221
  private router?: ZLinkRawRouterPort;
196
222
  private nextCorrelation = 1n;
197
223
  private serviceIngress?: RawServiceIngressHandler;
224
+ private serviceIngressAccepts?: (command: number) => boolean;
198
225
  private closed = false;
199
226
 
200
227
  constructor(options: RawServiceMeshRuntimeOptions) {
@@ -202,6 +229,7 @@ export class RawServiceMeshRuntime {
202
229
  this.infrastructureTaskRunner = options.infrastructureTaskRunner;
203
230
  this.operations = new OperationRegistry(undefined, options.onPendingOperationsChanged);
204
231
  this.descriptor = options.descriptor;
232
+ this.nodeMailboxOwner = `node:${options.descriptor.nodeRoutingId}`;
205
233
  this.topology = new ServiceTopologyRegistry(options.descriptor);
206
234
  this.mailbox = new ServiceMailbox(options.onMailboxReady);
207
235
  this.liveness = new ServiceLivenessRegistry(options.probeIntervalMs, options.peerTimeoutMs);
@@ -331,10 +359,12 @@ export class RawServiceMeshRuntime {
331
359
  if (router.disconnectRid !== undefined) {
332
360
  router.disconnectRid(nodeRoutingId);
333
361
  } else {
334
- router.disconnect(endpoint);
362
+ try {
363
+ router.disconnect(endpoint);
364
+ } catch (error) {
365
+ if (!isAlreadyDisconnectedError(error)) throw error;
366
+ }
335
367
  }
336
- } catch (error) {
337
- if (!isAlreadyDisconnectedError(error)) throw error;
338
368
  } finally {
339
369
  if (current !== undefined) this.removePeer(current);
340
370
  this.topology.forgetNotRequired(nodeRoutingId);
@@ -380,6 +410,65 @@ export class RawServiceMeshRuntime {
380
410
  };
381
411
  }
382
412
 
413
+ observePeerRoute(
414
+ nodeRoutingId: string
415
+ ): import('./durable-service-request').PeerRouteObservation {
416
+ const selected = this.selectedRoutes.get(nodeRoutingId);
417
+ const admitted = this.topology.admittedPeer(nodeRoutingId)?.liveness?.connectionId;
418
+ return {
419
+ generation: selected ?? (typeof admitted === 'bigint' ? admitted : undefined),
420
+ ready: admitted !== undefined && (selected === undefined || admitted === selected)
421
+ };
422
+ }
423
+
424
+ async waitForPeerRoute(
425
+ nodeRoutingId: string,
426
+ lifecycleGeneration: bigint,
427
+ deadlineUnixMs: bigint,
428
+ signal?: AbortSignal
429
+ ): Promise<void> {
430
+ const remaining = deadlineUnixMs - BigInt(Date.now());
431
+ if (remaining <= 0n) throw new OperationTimeoutError(0n);
432
+ const pending = this.operations.register(Number(remaining));
433
+ const check = () => {
434
+ if (this.isPeerRouteReady(nodeRoutingId, lifecycleGeneration)) {
435
+ this.operations.complete(pending.id, { terminalResult: RequestResult.Ok, failureCode: 0 });
436
+ }
437
+ };
438
+ const unsubscribe = this.observePeerRouteChanged((rid) => {
439
+ if (rid === nodeRoutingId) check();
440
+ });
441
+ const abort = () => this.operations.fail(pending.id, signal?.reason);
442
+ signal?.addEventListener('abort', abort, { once: true });
443
+ try {
444
+ if (signal?.aborted === true) abort();
445
+ else check();
446
+ await pending.promise;
447
+ } finally {
448
+ unsubscribe();
449
+ signal?.removeEventListener('abort', abort);
450
+ }
451
+ }
452
+
453
+ get requestStopSignal(): AbortSignal {
454
+ return this.applicationJobStop.signal;
455
+ }
456
+
457
+ observePeerRouteChanged(listener: (nodeRoutingId: string) => void): () => void {
458
+ this.peerRouteChanged.add(listener);
459
+ return () => {
460
+ this.peerRouteChanged.delete(listener);
461
+ };
462
+ }
463
+
464
+ hasSelectedPeerRoute(nodeRoutingId: string): boolean {
465
+ return this.selectedRoutes.has(nodeRoutingId);
466
+ }
467
+
468
+ private notifyPeerRouteChanged(nodeRoutingId: string): void {
469
+ for (const listener of this.peerRouteChanged) listener(nodeRoutingId);
470
+ }
471
+
383
472
  private notifyPeerConnectionIntentRemoved(nodeRoutingId: string): void {
384
473
  // Only the logical owner's removal publishes lifecycle termination.
385
474
  // A physical disconnect alone retains the connection expectation.
@@ -388,42 +477,46 @@ export class RawServiceMeshRuntime {
388
477
  for (const listener of this.peerConnectionIntentRemoved) listener(nodeRoutingId);
389
478
  }
390
479
 
391
- announcePeer(nodeRoutingId: string): Promise<boolean> {
392
- const expected = this.expectedPeers.get(nodeRoutingId);
480
+ announcePeer(nodeRoutingId: string): boolean {
481
+ if (this.closed) return false;
393
482
  const generation = this.selectedRoutes.get(nodeRoutingId);
394
- if (
395
- expected === undefined ||
396
- generation === undefined ||
397
- expected.helloSubmittedGeneration === generation ||
398
- this.peerAdmissionSealed?.() === true
399
- )
400
- return Promise.resolve(false);
401
- const current = () =>
402
- this.expectedPeers.get(nodeRoutingId) === expected &&
403
- this.selectedRoutes.get(nodeRoutingId) === generation;
404
- return Promise.resolve(
405
- this.startControlSend(
406
- nodeRoutingId,
407
- M6aServiceWireCommand.hello,
408
- () =>
409
- this.submitControl(
410
- nodeRoutingId,
411
- [
412
- encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor())
413
- ],
414
- () => {
415
- if (current()) expected.helloSubmittedGeneration = generation;
416
- }
417
- ),
418
- current
419
- )
483
+ if (generation === undefined) return false;
484
+ const key = controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello);
485
+ if (!this.isPeerAnnouncementCurrent(nodeRoutingId, generation)) {
486
+ this.controlSends.delete(key);
487
+ return false;
488
+ }
489
+ const current = () => this.isPeerAnnouncementCurrent(nodeRoutingId, generation);
490
+ const existing = this.controlSends.get(key);
491
+ const hello =
492
+ existing?.current?.() === true
493
+ ? existing.hello!
494
+ : encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor());
495
+ return this.startControlSend(
496
+ nodeRoutingId,
497
+ M6aServiceWireCommand.hello,
498
+ () => this.submitControl(nodeRoutingId, [hello]),
499
+ current,
500
+ hello
501
+ );
502
+ }
503
+
504
+ private isPeerAnnouncementCurrent(nodeRoutingId: string, generation: bigint): boolean {
505
+ const admission = this.topology.routeAdmission(nodeRoutingId);
506
+ return (
507
+ this.selectedRoutes.get(nodeRoutingId) === generation &&
508
+ !(
509
+ admission?.connectionId === routeConnectionId(generation) &&
510
+ admission.remoteAdmitReceived === true
511
+ ) &&
512
+ this.peerAdmissionSealed?.() !== true
420
513
  );
421
514
  }
422
515
 
423
516
  isPeerRouteReady(nodeRoutingId: string, lifecycleGeneration?: bigint): boolean {
424
517
  // Admission is bound to the observed selected route: the route observer
425
518
  // removes the peer when that route ends or is replaced.
426
- const peer = this.topology.peer(nodeRoutingId);
519
+ const peer = this.topology.admittedPeer(nodeRoutingId);
427
520
  return (
428
521
  peer !== undefined &&
429
522
  (lifecycleGeneration === undefined ||
@@ -470,12 +563,18 @@ export class RawServiceMeshRuntime {
470
563
  // placement and channel selectors therefore need the new mutable fields
471
564
  // on that same connection before the next request is selected; waiting for
472
565
  // a reconnect would leave a race where a drained node is selected again.
566
+ await this.publishLocalDescriptor(
567
+ this.topology.peers().map((peer) => peer.descriptor.nodeRoutingId)
568
+ );
569
+ }
570
+
571
+ private async publishLocalDescriptor(targets: readonly string[]): Promise<void> {
473
572
  const update = encodeRouteMeshAdmission(
474
573
  M6aServiceWireCommand.update,
475
574
  this.topology.localDescriptor()
476
575
  );
477
- for (const peer of this.topology.peers()) {
478
- await this.send(peer.descriptor.nodeRoutingId, [update]);
576
+ for (const target of targets) {
577
+ await this.sendService(target, [update]);
479
578
  }
480
579
  }
481
580
 
@@ -484,18 +583,14 @@ export class RawServiceMeshRuntime {
484
583
  }
485
584
 
486
585
  isObjectClientNodeDirectTarget(nodeRoutingId: string): boolean {
487
- const descriptor =
488
- nodeRoutingId === this.descriptor.nodeRoutingId
489
- ? this.topology.localDescriptor()
490
- : this.topology.knownDescriptor(nodeRoutingId);
491
- return descriptor?.objectRole === 'client';
586
+ return this.topology.isObjectClientNodeDirectTarget(nodeRoutingId);
492
587
  }
493
588
 
494
- async sendToNode(
589
+ sendToNode(
495
590
  targetNodeRoutingId: string,
496
591
  payload: ServiceApplicationPayloadInput
497
- ): Promise<boolean> {
498
- return this.send(targetNodeRoutingId, [encodeNodeSendHeader(), this.applicationFrame(payload)]);
592
+ ): boolean | Promise<boolean> {
593
+ return this.send(targetNodeRoutingId, [NODE_SEND_HEADER, this.applicationFrame(payload)]);
499
594
  }
500
595
 
501
596
  async sendToChannel(
@@ -517,30 +612,108 @@ export class RawServiceMeshRuntime {
517
612
  requestToNode(
518
613
  targetNodeRoutingId: string,
519
614
  payload: ServiceApplicationPayloadInput,
520
- timeoutMs: number
521
- ): PendingOperation<RawServiceRequestResult> {
522
- return this.requestToTarget(targetNodeRoutingId, payload, timeoutMs);
615
+ timeoutMs: number,
616
+ onAdmitted?: () => void
617
+ ): PendingOperation<RawServiceRequestResult>;
618
+ requestToNode(
619
+ targetNodeRoutingId: string,
620
+ payload: ServiceApplicationPayloadInput,
621
+ timeoutMs: number,
622
+ onAdmitted: (() => void) | undefined,
623
+ observer: RawServiceRequestObserver,
624
+ operationKind: number
625
+ ): RawServiceObservedRequest;
626
+ requestToNode(
627
+ targetNodeRoutingId: string,
628
+ payload: ServiceApplicationPayloadInput,
629
+ timeoutMs: number,
630
+ onAdmitted?: () => void,
631
+ observer?: RawServiceRequestObserver,
632
+ operationKind?: number
633
+ ): RawServiceObservedRequest {
634
+ return this.requestToTarget(
635
+ targetNodeRoutingId,
636
+ payload,
637
+ timeoutMs,
638
+ undefined,
639
+ onAdmitted,
640
+ observer,
641
+ operationKind
642
+ );
523
643
  }
524
644
 
525
645
  requestToChannel(
526
646
  channelName: string,
527
647
  payload: ServiceApplicationPayloadInput,
528
- timeoutMs: number
529
- ): PendingOperation<RawServiceRequestResult> {
648
+ timeoutMs: number,
649
+ onAdmitted?: () => void
650
+ ): PendingOperation<RawServiceRequestResult>;
651
+ requestToChannel(
652
+ channelName: string,
653
+ payload: ServiceApplicationPayloadInput,
654
+ timeoutMs: number,
655
+ onAdmitted: (() => void) | undefined,
656
+ observer: RawServiceRequestObserver,
657
+ operationKind: number
658
+ ): RawServiceObservedRequest;
659
+ requestToChannel(
660
+ channelName: string,
661
+ payload: ServiceApplicationPayloadInput,
662
+ timeoutMs: number,
663
+ onAdmitted?: () => void,
664
+ observer?: RawServiceRequestObserver,
665
+ operationKind?: number
666
+ ): RawServiceObservedRequest {
530
667
  const selection = this.selectChannelTarget(channelName);
531
668
  if (selection.kind !== 'selected') {
532
- const pending = this.operations.register(timeoutMs);
533
- this.operations.complete(pending.id, selection.requestResult);
669
+ const pending = this.registerRequest(timeoutMs, observer, operationKind);
670
+ this.completeRequest(pending, selection.requestResult);
534
671
  return pending;
535
672
  }
536
673
  return this.requestToTarget(
537
674
  selection.peer.descriptor.nodeRoutingId,
538
675
  payload,
539
676
  timeoutMs,
540
- channelName
677
+ channelName,
678
+ onAdmitted,
679
+ observer,
680
+ operationKind
541
681
  );
542
682
  }
543
683
 
684
+ /**
685
+ * One pending entry per request: an observed request reports its terminal
686
+ * to the caller's observer; any other request keeps the Promise contract.
687
+ */
688
+ private registerRequest(
689
+ timeoutMs: number,
690
+ observer: RawServiceRequestObserver | undefined,
691
+ operationKind: number | undefined
692
+ ): RawServiceObservedRequest {
693
+ return observer === undefined
694
+ ? this.operations.register(timeoutMs)
695
+ : { id: this.operations.registerObserved(timeoutMs, observer, operationKind) };
696
+ }
697
+
698
+ /**
699
+ * An observed caller learns the operation id only when the request call
700
+ * returns, so a terminal decided inside that call is delivered after it.
701
+ */
702
+ private completeRequest(
703
+ pending: RawServiceObservedRequest,
704
+ result: RawServiceRequestResult
705
+ ): void {
706
+ if (pending.promise === undefined)
707
+ queueMicrotask(() => this.operations.complete(pending.id, result));
708
+ else this.operations.complete(pending.id, result);
709
+ }
710
+
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);
715
+ }
716
+
544
717
  private selectChannelTarget(channelName: string): RawServiceChannelTargetSelection {
545
718
  const selected = this.topology.selectChannel(channelName, (peer) =>
546
719
  this.isPeerRouteReady(peer.descriptor.nodeRoutingId)
@@ -562,11 +735,15 @@ export class RawServiceMeshRuntime {
562
735
  };
563
736
  }
564
737
 
565
- setServiceIngress(handler: RawServiceIngressHandler): void {
738
+ setServiceIngress(
739
+ handler: RawServiceIngressHandler,
740
+ accepts?: (command: number) => boolean
741
+ ): void {
566
742
  if (this.serviceIngress !== undefined && this.serviceIngress !== handler) {
567
743
  throw new Error('Raw service ingress is already registered.');
568
744
  }
569
745
  this.serviceIngress = handler;
746
+ this.serviceIngressAccepts = accepts;
570
747
  }
571
748
 
572
749
  async reserveLocalIngress(signal?: AbortSignal): Promise<ApplicationIngressRecordOwner> {
@@ -579,20 +756,27 @@ export class RawServiceMeshRuntime {
579
756
  }
580
757
 
581
758
  async sendService(targetNodeRoutingId: string, parts: readonly Uint8Array[]): Promise<boolean> {
582
- return this.send(targetNodeRoutingId, parts);
759
+ try {
760
+ await this.requireStarted().send(targetNodeRoutingId, parts);
761
+ return true;
762
+ } catch (error) {
763
+ return this.sendFailure(error);
764
+ }
583
765
  }
584
766
 
585
767
  requestService(
586
768
  targetNodeRoutingId: string,
587
769
  parts: readonly Uint8Array[],
588
- timeoutMs: number
770
+ timeoutMs: number,
771
+ onAdmitted?: () => void
589
772
  ): Promise<readonly Buffer[]> {
590
- const admission = this.topology.peer(targetNodeRoutingId)?.liveness;
773
+ const admission = this.topology.admittedPeer(targetNodeRoutingId)?.liveness;
591
774
  return this.requireStarted().request(
592
775
  targetNodeRoutingId,
593
776
  parts,
594
777
  timeoutMs,
595
- admission?.recordReceived
778
+ admission?.recordReceived,
779
+ onAdmitted
596
780
  );
597
781
  }
598
782
 
@@ -668,59 +852,71 @@ export class RawServiceMeshRuntime {
668
852
  const observe: RawServicePumpObserver = (_source, byteCount) => {
669
853
  bytes += byteCount;
670
854
  };
671
- while (receiveReady && messages < RAW_MESH_RECEIVE_RECORD_BUDGET && !this.closed) {
672
- const result = await this.receiveOne(performance.now(), observe);
673
- if (result === 'noData') {
674
- receiveReady = false;
675
- break;
855
+ const rotation = (this.receiveRotation ??= this.applicationJobQueue.createReceiveRotation(
856
+ RAW_MESH_RECEIVE_RECORD_BUDGET,
857
+ 'remote'
858
+ ));
859
+ 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;
867
+ }
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;
676
876
  }
677
- messages += 1;
678
- // Core owns the per-peer fair-queue cursor. The Framework limit bounds
679
- // the entire round, so changing peers does not renew its byte/count budget.
680
- if (
681
- bytes >= RAW_MESH_RECEIVE_BYTE_BUDGET ||
682
- performance.now() - startedAtMs >= RAW_MESH_RECEIVE_TIME_BUDGET_MS
683
- )
684
- break;
877
+ } finally {
878
+ rotation.releaseUnused();
685
879
  }
686
880
  if (!this.closed) {
687
- for (const nodeRoutingId of this.expectedPeers.keys()) {
688
- if (this.topology.peer(nodeRoutingId) === undefined) await this.announcePeer(nodeRoutingId);
689
- }
690
881
  this.tickLiveness();
691
882
  }
692
883
  return receiveReady && messages > 0;
693
884
  }
694
885
 
695
- private async receiveOne(
886
+ private receiveOne(
696
887
  nowMs: number,
697
- observe?: RawServicePumpObserver
698
- ): Promise<RawServicePumpResult> {
888
+ observe?: RawServicePumpObserver,
889
+ rotation?: ApplicationJobReceiveRotationPort
890
+ ): RawServicePumpResult | Promise<RawServicePumpResult> {
699
891
  const router = this.requireStarted();
700
892
  this.observeInfrastructureFailure();
701
- if (this.receivePermit === undefined && this.receiveAcquisition === undefined) {
702
- let resolveAcquisition!: (permit: ApplicationJobPermitPort) => void;
703
- let rejectAcquisition!: (error: unknown) => void;
704
- const acquisition = new Promise<ApplicationJobPermitPort>((resolve, reject) => {
705
- resolveAcquisition = resolve;
706
- rejectAcquisition = reject;
707
- });
708
- const permit = this.applicationJobQueue.acquireOrResume(
893
+ let permit = this.receivePermit;
894
+ this.receivePermit = undefined;
895
+ permit ??= rotation?.take();
896
+ 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(
709
900
  (permit) => {
710
901
  this.receiveAcquisition = undefined;
711
- if (this.closed) permit.releaseAfterInternalProcessing();
712
- else {
902
+ if (this.closed) {
903
+ permit.releaseAfterInternalProcessing();
904
+ permit.releaseStorage?.();
905
+ } else {
713
906
  this.receivePermit = permit;
714
907
  this.onReceiveReady?.();
715
908
  }
716
- resolveAcquisition(permit);
909
+ resolveAcquisition?.(permit);
717
910
  },
718
- rejectAcquisition,
911
+ (error) => rejectAcquisition?.(error),
719
912
  this.applicationJobStop.signal,
720
913
  'remote'
721
914
  );
722
- if (permit !== undefined) this.receivePermit = permit;
723
- else {
915
+ if (permit === undefined) {
916
+ const acquisition = new Promise<ApplicationJobPermitPort>((resolve, reject) => {
917
+ resolveAcquisition = resolve;
918
+ rejectAcquisition = reject;
919
+ });
724
920
  this.receiveAcquisition = acquisition;
725
921
  acquisition.catch((error) => {
726
922
  this.receiveAcquisition = undefined;
@@ -728,11 +924,10 @@ export class RawServiceMeshRuntime {
728
924
  });
729
925
  }
730
926
  }
731
- const permit = this.receivePermit;
732
- this.receivePermit = undefined;
733
927
  if (permit === undefined) return 'noData';
734
928
  if (this.closed) {
735
929
  permit.releaseAfterInternalProcessing();
930
+ permit.releaseStorage?.();
736
931
  return 'noData';
737
932
  }
738
933
  let received: ZLinkRawReceivedRecord | undefined;
@@ -740,10 +935,12 @@ export class RawServiceMeshRuntime {
740
935
  received = router.receive(true);
741
936
  } catch (error) {
742
937
  permit.releaseAfterInternalProcessing();
938
+ permit.releaseStorage?.();
743
939
  throw error;
744
940
  }
745
941
  if (received === undefined) {
746
942
  permit.releaseAfterInternalProcessing();
943
+ permit.releaseStorage?.();
747
944
  return 'noData';
748
945
  }
749
946
  const applicationJobOwner = ApplicationIngressRecordOwner.create(
@@ -751,18 +948,40 @@ export class RawServiceMeshRuntime {
751
948
  permit,
752
949
  received
753
950
  );
951
+ let result: RawServicePumpResult | Promise<RawServicePumpResult>;
754
952
  try {
755
- const result = await this.processReceived(received, nowMs, applicationJobOwner);
756
- if (result === 'protocolError') {
757
- this.reportProtocolError(received);
953
+ result = this.processReceived(received, nowMs, applicationJobOwner);
954
+ } catch (error) {
955
+ applicationJobOwner.close();
956
+ throw error;
957
+ }
958
+ 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
+ )
966
+ : this.finishReceive(received, applicationJobOwner, observe, result);
967
+ }
968
+
969
+ private finishReceive(
970
+ received: ZLinkRawReceivedRecord,
971
+ owner: ApplicationIngressRecordOwner,
972
+ observe: RawServicePumpObserver | undefined,
973
+ result: RawServicePumpResult
974
+ ): RawServicePumpResult {
975
+ try {
976
+ if (result === 'protocolError') this.reportProtocolError(received);
977
+ if (observe !== undefined) {
978
+ let bytes = 0;
979
+ for (const part of received.parts) bytes += part.byteLength;
980
+ observe(received.sourceRid, bytes);
758
981
  }
759
- observe?.(
760
- received.sourceRid,
761
- received.parts.reduce((sum, part) => sum + part.byteLength, 0)
762
- );
763
982
  return result;
764
983
  } finally {
765
- applicationJobOwner.close();
984
+ owner.close();
766
985
  }
767
986
  }
768
987
 
@@ -808,22 +1027,15 @@ export class RawServiceMeshRuntime {
808
1027
  }
809
1028
  }
810
1029
 
811
- private async processReceived(
1030
+ private processReceived(
812
1031
  received: ZLinkRawReceivedRecord,
813
1032
  nowMs: number,
814
1033
  applicationJobOwner: ApplicationIngressRecordOwner
815
- ): Promise<RawServicePumpResult> {
816
- if (
817
- received.parts.length === 0 ||
818
- (received.parts.length === 1 && received.parts[0]!.byteLength === 0)
819
- ) {
820
- if (this.peerAdmissionSealed?.() === true) return 'dropped';
821
- return (await this.sendControl(received.sourceRid, [
822
- encodeRouteMeshAdmission(M6aServiceWireCommand.hello, this.topology.localDescriptor())
823
- ]))
824
- ? 'infrastructure'
825
- : 'dropped';
826
- }
1034
+ ): RawServicePumpResult | Promise<RawServicePumpResult> {
1035
+ const peer = this.topology.admittedPeer(received.sourceRid);
1036
+ const currentSource =
1037
+ peer?.liveness !== undefined && peer.liveness.connectionId === received.routeGeneration;
1038
+ if (received.parts.length === 0) return currentSource ? 'protocolError' : 'dropped';
827
1039
  try {
828
1040
  const header = decodeHeader(received.parts[0]!);
829
1041
  if (header.command === M6aServiceWireCommand.hello && this.peerAdmissionSealed?.() === true) {
@@ -831,107 +1043,187 @@ export class RawServiceMeshRuntime {
831
1043
  }
832
1044
  if (
833
1045
  header.command === M6aServiceWireCommand.hello ||
834
- header.command === M6aServiceWireCommand.admit ||
835
- header.command === M6aServiceWireCommand.update
1046
+ header.command === M6aServiceWireCommand.admit
836
1047
  ) {
837
- if (received.parts.length !== 1) return 'protocolError';
838
- const descriptor = decodeRouteMeshAdmission(
839
- received.parts[0]!,
840
- header.command,
841
- received.sourceRid
1048
+ return this.processAdmission(received, header, nowMs).catch((error) =>
1049
+ this.receiveProtocolFailure(error)
842
1050
  );
843
- const expected = this.expectedPeers.get(received.sourceRid);
844
- if (
845
- expected !== undefined &&
846
- (expected.meshName !== descriptor.meshName ||
847
- expected.nodeRoutingId !== descriptor.nodeRoutingId)
848
- ) {
849
- await this.sendControl(received.sourceRid, [
850
- encodeReject(enumWireRejectReason('identityMismatch'))
851
- ]);
852
- return 'infrastructure';
853
- }
854
- const result = this.admitPeer(descriptor, received.routeGeneration, nowMs, expected);
855
- if (result !== 'admitted' && result !== 'notRequired') {
856
- const detail =
857
- result === 'invalidDescriptor'
858
- ? describeInvalidAdmissionDescriptor(
859
- descriptor,
860
- expected,
861
- this.topology.peer(descriptor.nodeRoutingId)?.descriptor
862
- )
863
- : undefined;
864
- console.warn(
865
- `RouteMesh admission rejected peer '${received.sourceRid}' as ${result}` +
866
- (detail === undefined ? '.' : ` (${detail}).`)
867
- );
868
- await this.sendControl(received.sourceRid, [encodeReject(admissionReason(result))]);
869
- return 'infrastructure';
870
- }
871
- if (result === 'admitted') this.selectBilateralConnection(descriptor);
872
- if (header.command === M6aServiceWireCommand.hello) {
873
- // Hello supplies only the sender's descriptor. Even NotRequired
874
- // peers must receive ours before the intent owner closes transport;
875
- // submitting a response does not guarantee delivery before close.
876
- await this.sendControl(received.sourceRid, [
877
- encodeRouteMeshAdmission(M6aServiceWireCommand.admit, this.topology.localDescriptor())
878
- ]);
879
- } else if (result === 'notRequired') {
880
- this.retireNotRequiredExpectedPeer(received.sourceRid, descriptor.advertisedEndpoint);
881
- }
882
- return 'infrastructure';
883
1051
  }
884
1052
  if (header.command === M6aServiceWireCommand.reject) {
885
1053
  if (received.parts.length !== 1) return 'protocolError';
886
1054
  decodeReject(received.parts[0]!);
887
1055
  return 'infrastructure';
888
1056
  }
889
- const peer = this.topology.peer(received.sourceRid);
890
- if (peer === undefined) return 'protocolError';
891
- if (peer.liveness !== undefined && peer.liveness.connectionId === received.routeGeneration)
892
- this.liveness.recordReceived(peer.liveness, nowMs);
1057
+ if (!currentSource) return 'dropped';
1058
+ this.liveness.recordReceived(peer.liveness, nowMs);
1059
+ if (header.command === M6aServiceWireCommand.update) {
1060
+ return this.processAdmission(received, header, nowMs).catch((error) =>
1061
+ this.receiveProtocolFailure(error)
1062
+ );
1063
+ }
893
1064
  if (
894
1065
  header.command === M6aServiceWireCommand.livenessProbe ||
895
1066
  header.command === M6aServiceWireCommand.livenessAck
896
1067
  ) {
897
- if (received.parts.length !== 1) return 'protocolError';
898
- const record = livenessCodec.decodeLivenessRecord(received.parts[0]!);
899
- if (record.command === M6aServiceWireCommand.livenessProbe) {
900
- const ack = this.liveness.acknowledgeProbe(
901
- received.sourceRid,
902
- received.routeGeneration,
903
- record.probeId
904
- );
905
- if (ack === undefined) return 'protocolError';
906
- const sent = await this.sendControl(received.sourceRid, [
907
- livenessCodec.encodeLivenessRecord({
908
- command: M6aServiceWireCommand.livenessAck,
909
- probeId: record.probeId
910
- })
911
- ]);
912
- if (!sent) return 'dropped';
913
- } else {
914
- this.liveness.acknowledge(
915
- received.sourceRid,
916
- received.routeGeneration,
917
- record.probeId,
918
- nowMs
919
- );
920
- }
921
- return 'infrastructure';
1068
+ return this.processLiveness(received, nowMs).catch((error) =>
1069
+ this.receiveProtocolFailure(error)
1070
+ );
922
1071
  }
923
- const stateful = await this.serviceIngress?.({
924
- command: header.command,
925
- flags: header.flags,
926
- sourceRoutingId: received.sourceRid,
927
- sourceNodeGeneration: peer.descriptor.lifecycleGeneration,
928
- sourceRoute: received.sourceRoute,
929
- ...(received.reply === undefined ? {} : { reply: received.reply }),
930
- ...(received.requestSeq === undefined ? {} : { requestSequence: received.requestSeq }),
931
- parts: received.parts,
932
- applicationJobOwner
933
- });
1072
+ const stateful =
1073
+ this.serviceIngress !== undefined && this.serviceIngressAccepts?.(header.command) !== false
1074
+ ? this.serviceIngress({
1075
+ command: header.command,
1076
+ flags: header.flags,
1077
+ sourceRoutingId: received.sourceRid,
1078
+ sourceNodeGeneration: peer.descriptor.lifecycleGeneration,
1079
+ sourceRoute: received.sourceRoute,
1080
+ ...(received.reply === undefined ? {} : { reply: received.reply }),
1081
+ ...(received.requestSeq === undefined
1082
+ ? {}
1083
+ : { requestSequence: received.requestSeq }),
1084
+ parts: received.parts,
1085
+ applicationJobOwner
1086
+ })
1087
+ : undefined;
1088
+ if (stateful instanceof Promise)
1089
+ return stateful.then(
1090
+ (result) =>
1091
+ result ?? this.processApplicationReceived(received, header, applicationJobOwner),
1092
+ (error) => this.receiveProtocolFailure(error)
1093
+ );
934
1094
  if (stateful !== undefined) return stateful;
1095
+ return this.processApplicationReceived(received, header, applicationJobOwner);
1096
+ } catch (error) {
1097
+ if (!currentSource && error instanceof ServiceWireProtocolError) return 'dropped';
1098
+ return this.receiveProtocolFailure(error);
1099
+ }
1100
+ }
1101
+
1102
+ private receiveProtocolFailure(error: unknown): RawServicePumpResult {
1103
+ if (error instanceof ServiceWireProtocolError) return 'protocolError';
1104
+ throw error;
1105
+ }
1106
+
1107
+ private async processAdmission(
1108
+ received: ZLinkRawReceivedRecord,
1109
+ header: ReturnType<typeof decodeHeader>,
1110
+ nowMs: number
1111
+ ): Promise<RawServicePumpResult> {
1112
+ if (received.parts.length !== 1) return 'protocolError';
1113
+ const descriptor = decodeRouteMeshAdmission(
1114
+ received.parts[0]!,
1115
+ header.command,
1116
+ received.sourceRid
1117
+ );
1118
+ const expected = this.expectedPeers.get(received.sourceRid);
1119
+ if (
1120
+ expected !== undefined &&
1121
+ (expected.meshName !== descriptor.meshName ||
1122
+ expected.nodeRoutingId !== descriptor.nodeRoutingId)
1123
+ ) {
1124
+ await this.sendControl(received.sourceRid, [
1125
+ encodeReject(enumWireRejectReason('identityMismatch'))
1126
+ ]);
1127
+ return 'infrastructure';
1128
+ }
1129
+ const helloKey = controlSendKey(received.sourceRid, M6aServiceWireCommand.hello);
1130
+ const exchange = this.controlSends.get(helloKey);
1131
+ const result = this.admitPeer(
1132
+ descriptor,
1133
+ received.routeGeneration,
1134
+ nowMs,
1135
+ expected,
1136
+ header.command,
1137
+ received.parts[0]!.subarray(SERVICE_WIRE_PREFIX_SIZE)
1138
+ );
1139
+ if (result !== 'admitted' && result !== 'notRequired') {
1140
+ const detail =
1141
+ result === 'invalidDescriptor'
1142
+ ? describeInvalidAdmissionDescriptor(
1143
+ descriptor,
1144
+ expected,
1145
+ this.topology.peer(descriptor.nodeRoutingId)?.descriptor
1146
+ )
1147
+ : undefined;
1148
+ console.warn(
1149
+ `RouteMesh admission rejected peer '${received.sourceRid}' as ${result}` +
1150
+ (detail === undefined ? '.' : ` (${detail}).`)
1151
+ );
1152
+ await this.sendControl(received.sourceRid, [encodeReject(admissionReason(result))]);
1153
+ return 'infrastructure';
1154
+ }
1155
+ if (result === 'admitted') this.selectBilateralConnection(descriptor);
1156
+ if (header.command === M6aServiceWireCommand.hello) {
1157
+ // Hello supplies only the sender's descriptor. Even NotRequired
1158
+ // peers must receive ours before the intent owner closes transport;
1159
+ // submitting a response does not guarantee delivery before close.
1160
+ const local =
1161
+ exchange?.current?.() === true && exchange.hello !== undefined
1162
+ ? decodeRouteMeshAdmission(
1163
+ exchange.hello,
1164
+ M6aServiceWireCommand.hello,
1165
+ this.descriptor.nodeRoutingId
1166
+ )
1167
+ : this.topology.localDescriptor();
1168
+ await this.sendControl(received.sourceRid, [
1169
+ encodeRouteMeshAdmission(M6aServiceWireCommand.admit, local)
1170
+ ]);
1171
+ } else if (header.command === M6aServiceWireCommand.admit) {
1172
+ this.controlSends.delete(helloKey);
1173
+ this.refreshAdmissionPending(nowMs);
1174
+ if (result === 'notRequired') {
1175
+ this.retireNotRequiredExpectedPeer(received.sourceRid, descriptor.advertisedEndpoint);
1176
+ } else if (
1177
+ exchange?.hello !== undefined &&
1178
+ decodeRouteMeshAdmission(
1179
+ exchange.hello,
1180
+ M6aServiceWireCommand.hello,
1181
+ this.descriptor.nodeRoutingId
1182
+ ).descriptorRevision < this.descriptor.descriptorRevision
1183
+ ) {
1184
+ await this.publishLocalDescriptor([received.sourceRid]);
1185
+ }
1186
+ }
1187
+ return 'infrastructure';
1188
+ }
1189
+
1190
+ private async processLiveness(
1191
+ received: ZLinkRawReceivedRecord,
1192
+ nowMs: number
1193
+ ): Promise<RawServicePumpResult> {
1194
+ if (received.parts.length !== 1) return 'protocolError';
1195
+ const record = livenessCodec.decodeLivenessRecord(received.parts[0]!);
1196
+ if (record.command === M6aServiceWireCommand.livenessProbe) {
1197
+ const ack = this.liveness.acknowledgeProbe(
1198
+ received.sourceRid,
1199
+ received.routeGeneration,
1200
+ record.probeId
1201
+ );
1202
+ if (ack === undefined) return 'protocolError';
1203
+ const sent = await this.sendControl(received.sourceRid, [
1204
+ livenessCodec.encodeLivenessRecord({
1205
+ command: M6aServiceWireCommand.livenessAck,
1206
+ probeId: record.probeId
1207
+ })
1208
+ ]);
1209
+ if (!sent) return 'dropped';
1210
+ } else {
1211
+ this.liveness.acknowledge(
1212
+ received.sourceRid,
1213
+ received.routeGeneration,
1214
+ record.probeId,
1215
+ nowMs
1216
+ );
1217
+ }
1218
+ return 'infrastructure';
1219
+ }
1220
+
1221
+ private processApplicationReceived(
1222
+ received: ZLinkRawReceivedRecord,
1223
+ header: ReturnType<typeof decodeHeader>,
1224
+ applicationJobOwner: ApplicationIngressRecordOwner
1225
+ ): RawServicePumpResult | Promise<RawServicePumpResult> {
1226
+ try {
935
1227
  if (
936
1228
  header.flags === 0 &&
937
1229
  // A bare infrastructure control is normally exactly one frame, but a
@@ -949,7 +1241,7 @@ export class RawServiceMeshRuntime {
949
1241
  isBareInfrastructureControl(header.command)
950
1242
  ) {
951
1243
  const accepted = this.mailbox.tryEnqueue({
952
- owner: `node:${this.descriptor.nodeRoutingId}`,
1244
+ owner: this.nodeMailboxOwner,
953
1245
  domain: 'infrastructure',
954
1246
  parts: received.parts,
955
1247
  sourceRoutingId: received.sourceRid,
@@ -962,62 +1254,67 @@ export class RawServiceMeshRuntime {
962
1254
  if (
963
1255
  header.flags !== 0 ||
964
1256
  received.parts.length !== 2 ||
965
- ![
966
- M6aServiceWireCommand.nodeSend,
967
- M6aServiceWireCommand.nodeRequest,
968
- M6aServiceWireCommand.channelSend,
969
- M6aServiceWireCommand.channelRequest
970
- ].includes(header.command as never)
1257
+ !isApplicationEnvelopeCommand(header.command)
971
1258
  ) {
972
1259
  return 'protocolError';
973
1260
  }
974
1261
  let owner: string;
975
1262
  let correlation: bigint | undefined;
976
1263
  if (header.command === M6aServiceWireCommand.nodeSend) {
977
- owner = `node:${this.descriptor.nodeRoutingId}`;
1264
+ owner = this.nodeMailboxOwner;
978
1265
  } else if (header.command === M6aServiceWireCommand.nodeRequest) {
979
- owner = `node:${this.descriptor.nodeRoutingId}`;
980
- correlation = decodeNodeRequestHeader(received.parts[0]!);
1266
+ owner = this.nodeMailboxOwner;
1267
+ correlation = decodeNodeRequestHeader(received.parts[0]!, header);
981
1268
  } else if (header.command === M6aServiceWireCommand.channelSend) {
982
- owner = `channel:${decodeChannelSendHeader(received.parts[0]!)}`;
1269
+ owner = `channel:${decodeChannelSendHeader(received.parts[0]!, header)}`;
983
1270
  } else {
984
- const channel = decodeChannelRequestHeader(received.parts[0]!);
1271
+ const channel = decodeChannelRequestHeader(received.parts[0]!, header);
985
1272
  owner = `channel:${channel.channelName}`;
986
1273
  correlation = channel.correlation;
987
1274
  }
988
- const applicationJob = await applicationJobOwner.acquire('application');
989
- const accepted = this.mailbox.tryEnqueue({
990
- owner,
991
- domain: 'application',
992
- parts: received.parts,
993
- sourceRoutingId: received.sourceRid,
994
- sourceRoute: received.sourceRoute,
995
- ...(received.reply === undefined ? {} : { reply: received.reply }),
996
- requestSequence: received.requestSeq,
997
- ...(correlation === undefined ? {} : { correlation }),
998
- applicationJob
999
- });
1000
- if (accepted) return 'application';
1001
- applicationJob.close();
1002
- if (correlation !== undefined && received.requestSeq !== undefined) {
1003
- this.replyService(
1004
- {
1005
- sourceRoutingId: received.sourceRid,
1006
- sourceRoute: received.sourceRoute,
1007
- requestSequence: received.requestSeq,
1008
- ...(received.reply === undefined ? {} : { reply: received.reply })
1009
- },
1010
- [encodeReplyHeader(correlation, RequestResult.NotConnected, 0)]
1011
- );
1012
- return 'infrastructure';
1013
- }
1014
- return 'dropped';
1275
+ const job = applicationJobOwner.acquire('application');
1276
+ return job instanceof Promise
1277
+ ? job.then((permit) => this.enqueueOrdinaryRecord(received, owner, correlation, permit))
1278
+ : this.enqueueOrdinaryRecord(received, owner, correlation, job);
1015
1279
  } catch (error) {
1016
- if (error instanceof ServiceWireProtocolError) return 'protocolError';
1017
- throw error;
1280
+ return this.receiveProtocolFailure(error);
1018
1281
  }
1019
1282
  }
1020
1283
 
1284
+ private enqueueOrdinaryRecord(
1285
+ received: ZLinkRawReceivedRecord,
1286
+ owner: string,
1287
+ correlation: bigint | undefined,
1288
+ applicationJob: import('../application-jobs/application-ingress-record-owner').ApplicationJobRecordLease
1289
+ ): RawServicePumpResult {
1290
+ const accepted = this.mailbox.tryEnqueue({
1291
+ owner,
1292
+ domain: 'application',
1293
+ parts: received.parts,
1294
+ sourceRoutingId: received.sourceRid,
1295
+ sourceRoute: received.sourceRoute,
1296
+ reply: received.reply,
1297
+ requestSequence: received.requestSeq,
1298
+ correlation,
1299
+ applicationJob
1300
+ });
1301
+ if (accepted) return 'application';
1302
+ applicationJob.close();
1303
+ if (correlation !== undefined && received.requestSeq !== undefined) {
1304
+ this.replyService(
1305
+ {
1306
+ sourceRoutingId: received.sourceRid,
1307
+ sourceRoute: received.sourceRoute,
1308
+ requestSequence: received.requestSeq,
1309
+ ...(received.reply === undefined ? {} : { reply: received.reply })
1310
+ },
1311
+ [encodeReplyHeader(correlation, RequestResult.NotConnected, 0)]
1312
+ );
1313
+ return 'infrastructure';
1314
+ }
1315
+ return 'dropped';
1316
+ }
1317
+
1021
1318
  expireOperations(nowMs: number, turnDeadlineMs: number): number {
1022
1319
  return this.operations.expire(nowMs, turnDeadlineMs);
1023
1320
  }
@@ -1026,9 +1323,42 @@ export class RawServiceMeshRuntime {
1026
1323
  return this.operations.size;
1027
1324
  }
1028
1325
 
1326
+ cancelPendingOperations(reason: unknown): void {
1327
+ this.operations.failAll(reason);
1328
+ }
1329
+
1330
+ /**
1331
+ * The next management deadline of this node (spec 08 §4.2): request expiry,
1332
+ * liveness probe or timeout, or an owned HELLO exchange still awaiting
1333
+ * ADMIT, which keeps the existing announce cadence.
1334
+ */
1335
+ nextMaintenanceDeadlineMs(nowMs: number): number {
1336
+ this.refreshAdmissionPending(nowMs);
1337
+ return Math.min(this.operations.nextDeadlineMs(), this.liveness.nextDeadlineMs());
1338
+ }
1339
+
1340
+ private refreshAdmissionPending(nowMs: number): void {
1341
+ let pending = false;
1342
+ if (!this.closed && this.controlSends.size !== 0) {
1343
+ for (const nodeRoutingId of this.selectedRoutes.keys()) {
1344
+ const exchange = this.controlSends.get(
1345
+ controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello)
1346
+ );
1347
+ if (exchange?.current?.() === true) {
1348
+ pending = true;
1349
+ break;
1350
+ }
1351
+ }
1352
+ }
1353
+ this.liveness.setAdmissionPending(pending, nowMs);
1354
+ }
1355
+
1029
1356
  tickLiveness(nowMs = performance.now()): ServiceLivenessTick {
1357
+ const admissionDue = this.liveness.tickAdmission(nowMs);
1030
1358
  const result = this.liveness.tick(nowMs);
1031
1359
  this.requireStarted();
1360
+ if (admissionDue)
1361
+ for (const nodeRoutingId of this.selectedRoutes.keys()) void this.announcePeer(nodeRoutingId);
1032
1362
  for (const probe of result.probes) {
1033
1363
  const parts = [
1034
1364
  livenessCodec.encodeLivenessRecord({
@@ -1065,9 +1395,8 @@ export class RawServiceMeshRuntime {
1065
1395
  let changes = 0;
1066
1396
  for (const [nodeRoutingId, generation] of this.selectedRoutes) {
1067
1397
  if (observed.get(nodeRoutingId) === generation) continue;
1398
+ this.controlSends.delete(controlSendKey(nodeRoutingId, M6aServiceWireCommand.hello));
1068
1399
  changes++;
1069
- const expected = this.expectedPeers.get(nodeRoutingId);
1070
- if (expected !== undefined) delete expected.helloSubmittedGeneration;
1071
1400
  const peer = this.topology.peer(nodeRoutingId);
1072
1401
  if (peer?.liveness?.connectionId === generation) {
1073
1402
  this.removePeer(peer);
@@ -1080,8 +1409,10 @@ export class RawServiceMeshRuntime {
1080
1409
  selected.push(nodeRoutingId);
1081
1410
  }
1082
1411
  this.selectedRoutes = observed;
1412
+ for (const nodeRoutingId of selected) this.notifyPeerRouteChanged(nodeRoutingId);
1083
1413
  // A new selected route admits only through a new handshake.
1084
1414
  for (const nodeRoutingId of selected) await this.announcePeer(nodeRoutingId);
1415
+ this.refreshAdmissionPending(performance.now());
1085
1416
  return changes;
1086
1417
  }
1087
1418
 
@@ -1092,8 +1423,10 @@ export class RawServiceMeshRuntime {
1092
1423
  new Error('Raw service runtime application job admission stopped.')
1093
1424
  );
1094
1425
  this.receivePermit?.releaseAfterInternalProcessing();
1426
+ this.receivePermit?.releaseStorage?.();
1095
1427
  this.receivePermit = undefined;
1096
1428
  for (const send of this.controlSends.values()) delete send.next;
1429
+ this.controlSends.clear();
1097
1430
  this.mailbox.close();
1098
1431
  this.operations.close('Raw service runtime closed.');
1099
1432
  this.serviceIngress = undefined;
@@ -1111,15 +1444,17 @@ export class RawServiceMeshRuntime {
1111
1444
  targetNodeRoutingId: string,
1112
1445
  payload: ServiceApplicationPayloadInput,
1113
1446
  timeoutMs: number,
1114
- channelName?: string
1115
- ): PendingOperation<RawServiceRequestResult> {
1447
+ channelName?: string,
1448
+ onAdmitted?: () => void,
1449
+ observer?: RawServiceRequestObserver,
1450
+ operationKind?: number
1451
+ ): RawServiceObservedRequest {
1116
1452
  const correlation = this.nextCorrelation++;
1117
1453
  const header =
1118
1454
  channelName === undefined
1119
1455
  ? encodeNodeRequestHeader(correlation)
1120
1456
  : encodeChannelRequestHeader(correlation, channelName);
1121
1457
  const encodedPayload = this.applicationFrame(payload);
1122
- const pending = this.operations.register(timeoutMs);
1123
1458
  let selectedTargetNodeRoutingId = targetNodeRoutingId;
1124
1459
  if (
1125
1460
  selectedTargetNodeRoutingId !== this.descriptor.nodeRoutingId &&
@@ -1147,6 +1482,9 @@ export class RawServiceMeshRuntime {
1147
1482
  }
1148
1483
  }
1149
1484
  if (selectedTargetNodeRoutingId === this.descriptor.nodeRoutingId) {
1485
+ // The local admission path aborts its capacity wait on the terminal
1486
+ // Promise, so a local request always keeps the Promise entry.
1487
+ const pending = this.operations.register(timeoutMs);
1150
1488
  const parts = [header, encodedPayload];
1151
1489
  const capacityStop = new AbortController();
1152
1490
  void pending.promise.finally(() => capacityStop.abort()).catch(() => undefined);
@@ -1182,6 +1520,8 @@ export class RawServiceMeshRuntime {
1182
1520
  terminalResult: RequestResult.NotConnected,
1183
1521
  failureCode: 0
1184
1522
  });
1523
+ } else {
1524
+ onAdmitted?.();
1185
1525
  }
1186
1526
  } finally {
1187
1527
  applicationJobOwner.close();
@@ -1189,12 +1529,13 @@ export class RawServiceMeshRuntime {
1189
1529
  })().catch((error) => this.operations.fail(pending.id, error));
1190
1530
  return pending;
1191
1531
  }
1192
- const selectedPeer = this.topology.peer(selectedTargetNodeRoutingId);
1532
+ const pending = this.registerRequest(timeoutMs, observer, operationKind);
1533
+ const selectedPeer = this.topology.admittedPeer(selectedTargetNodeRoutingId);
1193
1534
  if (selectedPeer === undefined) {
1194
1535
  // Logical target selection is the requester's decision: an RID that is
1195
1536
  // not an admitted peer is not a target. Whether an admitted target's
1196
1537
  // route can carry the request is Core's REQUEST result.
1197
- this.operations.complete(pending.id, {
1538
+ this.completeRequest(pending, {
1198
1539
  terminalResult: RequestResult.NotFound,
1199
1540
  failureCode: REQUEST_TARGET_NOT_FOUND_FAILURE_CODE
1200
1541
  });
@@ -1207,10 +1548,11 @@ export class RawServiceMeshRuntime {
1207
1548
  selectedTargetNodeRoutingId,
1208
1549
  parts,
1209
1550
  timeoutMs,
1210
- selectedPeer.liveness?.recordReceived
1551
+ selectedPeer.liveness?.recordReceived,
1552
+ onAdmitted
1211
1553
  );
1212
1554
  } catch (error) {
1213
- this.operations.fail(pending.id, error);
1555
+ this.failRequest(pending, error);
1214
1556
  return pending;
1215
1557
  }
1216
1558
  void request.then(
@@ -1274,33 +1616,37 @@ export class RawServiceMeshRuntime {
1274
1616
  readonly endpoint?: string;
1275
1617
  readonly securityIdentity?: string;
1276
1618
  readonly lifecycleGeneration?: bigint;
1277
- }
1619
+ },
1620
+ command: number = M6aServiceWireCommand.hello,
1621
+ descriptorBytes?: Uint8Array
1278
1622
  ): PeerAdmissionResult {
1279
1623
  const previous = this.topology.peer(descriptor.nodeRoutingId);
1280
- if (
1281
- previous !== undefined &&
1282
- previous.descriptor.lifecycleGeneration === descriptor.lifecycleGeneration &&
1283
- (descriptor.descriptorRevision < previous.descriptor.descriptorRevision ||
1284
- (descriptor.descriptorRevision === previous.descriptor.descriptorRevision &&
1285
- !sameServiceNodeDescriptor(previous.descriptor, descriptor)))
1286
- ) {
1624
+ const topologyConnectionId =
1625
+ typeof connectionId === 'bigint' ? routeConnectionId(connectionId) : connectionId;
1626
+ const result = this.topology.admit(
1627
+ descriptor,
1628
+ topologyConnectionId,
1629
+ expected,
1630
+ () => {
1631
+ const admission = this.liveness.admit(descriptor.nodeRoutingId, connectionId, nowMs);
1632
+ this.liveness.recordReceived(admission, nowMs);
1633
+ return admission;
1634
+ },
1635
+ command,
1636
+ descriptorBytes
1637
+ );
1638
+ if (result === 'staleDescriptor') {
1287
1639
  throw new ServiceWireProtocolError(
1288
1640
  `RouteMesh peer '${descriptor.nodeRoutingId}' descriptor revision is stale or conflicting.`
1289
1641
  );
1290
1642
  }
1291
- const topologyConnectionId =
1292
- typeof connectionId === 'bigint' ? `route:${connectionId}` : connectionId;
1293
- const result = this.topology.admit(descriptor, topologyConnectionId, expected, () => {
1294
- const admission = this.liveness.admit(descriptor.nodeRoutingId, connectionId, nowMs);
1295
- this.liveness.recordReceived(admission, nowMs);
1296
- return admission;
1297
- });
1298
1643
  if (result === 'notRequired' && previous !== undefined) {
1299
1644
  this.liveness.disconnect(
1300
1645
  descriptor.nodeRoutingId,
1301
1646
  previous.liveness?.connectionId ?? previous.connectionId
1302
1647
  );
1303
1648
  }
1649
+ if (result === 'admitted') this.notifyPeerRouteChanged(descriptor.nodeRoutingId);
1304
1650
  return result;
1305
1651
  }
1306
1652
 
@@ -1361,26 +1707,16 @@ export class RawServiceMeshRuntime {
1361
1707
  );
1362
1708
  }
1363
1709
 
1364
- private submitControl(
1365
- target: string,
1366
- parts: readonly Uint8Array[],
1367
- accepted?: () => void
1368
- ): boolean | Promise<boolean> {
1710
+ private submitControl(target: string, parts: readonly Uint8Array[]): boolean | Promise<boolean> {
1369
1711
  let submission;
1370
1712
  try {
1371
1713
  submission = this.requireStarted().submitSend(target, parts);
1372
1714
  } catch (error) {
1373
1715
  return this.sendFailure(error);
1374
1716
  }
1375
- if (submission.result === SubmitResult.Ok) {
1376
- accepted?.();
1377
- return true;
1378
- }
1717
+ if (submission.result === SubmitResult.Ok) return true;
1379
1718
  return submission.admitted.then(
1380
- () => {
1381
- accepted?.();
1382
- return true;
1383
- },
1719
+ () => true,
1384
1720
  (error) => this.sendFailure(error)
1385
1721
  );
1386
1722
  }
@@ -1389,12 +1725,16 @@ export class RawServiceMeshRuntime {
1389
1725
  target: string,
1390
1726
  command: number,
1391
1727
  attempt: () => boolean | Promise<boolean>,
1392
- current?: () => boolean
1728
+ current?: () => boolean,
1729
+ hello?: Uint8Array
1393
1730
  ): boolean {
1394
- const key = JSON.stringify([target, command]);
1731
+ const key = controlSendKey(target, command);
1732
+ if (command === M6aServiceWireCommand.hello)
1733
+ this.liveness.setAdmissionPending(true, performance.now());
1395
1734
  const existing = this.controlSends.get(key);
1396
1735
  if (
1397
1736
  existing !== undefined &&
1737
+ existing.task !== undefined &&
1398
1738
  (command !== M6aServiceWireCommand.hello || existing.current?.() === true)
1399
1739
  ) {
1400
1740
  if (command !== M6aServiceWireCommand.hello) {
@@ -1404,39 +1744,45 @@ export class RawServiceMeshRuntime {
1404
1744
  return true;
1405
1745
  }
1406
1746
  const first = attempt();
1407
- if (typeof first === 'boolean') return first;
1747
+ if (typeof first === 'boolean') {
1748
+ if (hello !== undefined) this.controlSends.set(key, { current, hello });
1749
+ return first;
1750
+ }
1408
1751
  const owner = this;
1409
- const state: {
1410
- task: Promise<boolean>;
1411
- next?: () => boolean | Promise<boolean>;
1412
- current?: () => boolean;
1413
- } = {
1414
- current,
1415
- task: first.then(function drain(accepted): boolean | Promise<boolean> {
1416
- if (!owner.closed && state.next !== undefined) {
1417
- const next = state.next;
1418
- delete state.next;
1419
- return Promise.resolve(next()).then(drain);
1420
- }
1421
- if (owner.controlSends.get(key) === state) owner.controlSends.delete(key);
1422
- return accepted;
1423
- })
1752
+ const state: ControlSendState = { current, hello };
1753
+ const completeSend = () => {
1754
+ if (owner.controlSends.get(key) !== state) return;
1755
+ if (hello !== undefined && current?.() === true) delete state.task;
1756
+ else owner.controlSends.delete(key);
1424
1757
  };
1758
+ const task = first.then(function drain(accepted): boolean | Promise<boolean> {
1759
+ if (!owner.closed && state.next !== undefined) {
1760
+ const next = state.next;
1761
+ delete state.next;
1762
+ return Promise.resolve(next()).then(drain);
1763
+ }
1764
+ completeSend();
1765
+ return accepted;
1766
+ });
1767
+ state.task = task;
1425
1768
  this.controlSends.set(key, state);
1426
- state.task.then(
1769
+ task.then(
1427
1770
  () => {},
1428
1771
  (error) => {
1429
- if (this.controlSends.get(key) === state) this.controlSends.delete(key);
1430
- if (this.infrastructureTaskRunner === undefined) this.reportInfrastructureFailure(error);
1772
+ completeSend();
1773
+ this.reportInfrastructureFailure(error);
1431
1774
  }
1432
1775
  );
1433
- this.infrastructureTaskRunner?.runDetached('RouteMesh control SEND', async () => {
1434
- await state.task;
1435
- });
1436
1776
  return true;
1437
1777
  }
1438
1778
 
1439
- private reportInfrastructureFailure(error: unknown): void {
1779
+ reportInfrastructureFailure(error: unknown): void {
1780
+ if (this.infrastructureTaskRunner !== undefined) {
1781
+ this.infrastructureTaskRunner.runDetached('RouteMesh infrastructure', async () => {
1782
+ throw error;
1783
+ });
1784
+ return;
1785
+ }
1440
1786
  this.infrastructureFailure = {
1441
1787
  error:
1442
1788
  this.infrastructureFailure === undefined
@@ -1455,17 +1801,20 @@ export class RawServiceMeshRuntime {
1455
1801
  if (failure !== undefined) throw failure.error;
1456
1802
  }
1457
1803
 
1458
- private send(targetNodeRoutingId: string, parts: readonly Uint8Array[]): Promise<boolean> {
1459
- return this.requireStarted()
1460
- .send(targetNodeRoutingId, parts)
1461
- .then(
1462
- () => {
1463
- return true;
1464
- },
1465
- (error) => {
1466
- return this.sendFailure(error);
1467
- }
1804
+ private send(
1805
+ targetNodeRoutingId: string,
1806
+ parts: readonly Uint8Array[]
1807
+ ): boolean | Promise<boolean> {
1808
+ try {
1809
+ const submission = this.requireStarted().submitSend(targetNodeRoutingId, parts);
1810
+ if (submission.result === SubmitResult.Ok) return true;
1811
+ return submission.admitted.then(
1812
+ () => true,
1813
+ (error) => this.sendFailure(error)
1468
1814
  );
1815
+ } catch (error) {
1816
+ return this.sendFailure(error);
1817
+ }
1469
1818
  }
1470
1819
 
1471
1820
  private sendFailure(error: unknown): false {
@@ -1478,25 +1827,44 @@ export class RawServiceMeshRuntime {
1478
1827
  }
1479
1828
  }
1480
1829
 
1830
+ function controlSendKey(target: string, command: number): string {
1831
+ return `${command}:${target}`;
1832
+ }
1833
+
1834
+ function routeConnectionId(generation: bigint): string {
1835
+ return `route:${generation}`;
1836
+ }
1837
+
1481
1838
  /**
1482
1839
  * Relocation and bound-Session controls are service-wire records themselves,
1483
1840
  * not application NodeSend(16) envelopes. Keep this list closed: a bare
1484
1841
  * record outside the frozen control vocabulary remains a protocol error.
1485
1842
  */
1843
+ const BARE_INFRASTRUCTURE_CONTROLS: ReadonlySet<number> = new Set<number>([
1844
+ ServiceWireCommand.relocationReady,
1845
+ ServiceWireCommand.relocationData,
1846
+ ServiceWireCommand.replyRelay,
1847
+ ServiceWireCommand.relocationCutover,
1848
+ ServiceWireCommand.relocationPrepare,
1849
+ ServiceWireCommand.sessionRelocationSeal,
1850
+ ServiceWireCommand.sessionRelocationSealed,
1851
+ ServiceWireCommand.sessionRelocationRoute,
1852
+ ServiceWireCommand.replyRelayAck,
1853
+ ServiceWireCommand.relocationState,
1854
+ ServiceWireCommand.relocationFailed
1855
+ ]);
1856
+
1486
1857
  function isBareInfrastructureControl(command: number): boolean {
1487
- return [
1488
- ServiceWireCommand.relocationReady,
1489
- ServiceWireCommand.relocationData,
1490
- ServiceWireCommand.replyRelay,
1491
- ServiceWireCommand.relocationCutover,
1492
- ServiceWireCommand.relocationPrepare,
1493
- ServiceWireCommand.sessionRelocationSeal,
1494
- ServiceWireCommand.sessionRelocationSealed,
1495
- ServiceWireCommand.sessionRelocationRoute,
1496
- ServiceWireCommand.replyRelayAck,
1497
- ServiceWireCommand.relocationState,
1498
- ServiceWireCommand.relocationFailed
1499
- ].includes(command as never);
1858
+ return BARE_INFRASTRUCTURE_CONTROLS.has(command);
1859
+ }
1860
+
1861
+ function isApplicationEnvelopeCommand(command: number): boolean {
1862
+ return (
1863
+ command === M6aServiceWireCommand.nodeSend ||
1864
+ command === M6aServiceWireCommand.nodeRequest ||
1865
+ command === M6aServiceWireCommand.channelSend ||
1866
+ command === M6aServiceWireCommand.channelRequest
1867
+ );
1500
1868
  }
1501
1869
 
1502
1870
  function protocolCommand(parts: readonly Uint8Array[]): { readonly command?: number } {