@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
@@ -1,3 +1,12 @@
1
+ import { awaitWithAbort, isAbortError } from '../abort';
2
+ import { decodeChannelHeader } from '../channels/channel-envelope';
3
+ import { ZLinkBufferMessage } from '../backend/runtime-message';
4
+ import {
5
+ dispatchErrorDetails,
6
+ dispatchReasonFromError
7
+ } from '../diagnostics/dispatch-error-details';
8
+ import { encodeBoundSessionCloseCommand } from '../protocol/service_wire_codec.generated';
9
+ import { decodeRoutingId, encodeRoutingIdStorageHex, routingIdsEqual } from '../routing-id';
1
10
  import { ZlinkStreamContentType } from '@zlink-systems/stream-wire';
2
11
  import { AsyncLocalStorage } from 'node:async_hooks';
3
12
  import { ZLinkFrameworkErrorKind, ZLinkFrameworkException } from '../../contracts';
@@ -10,11 +19,11 @@ import {
10
19
  ZLinkDispatchMessageKind,
11
20
  ZLinkRuntimeMessageFlowOutcome as ZLinkMessageFlowOutcome
12
21
  } from '../../contracts/Dispatch/ZLinkDispatchOptions';
13
- import { awaitWithAbort } from '../abort';
22
+ import { requestDurableService } from './durable-service-request';
14
23
  import { shouldCompactBackingArray } from '../admission';
15
24
  import { RequestResult, SubmitResult, isZLinkBackendResultError } from '../backend/runtime-values';
16
25
  import type { ZLinkDispatchErrorReporter } from '../channels/dispatch-error-reporter';
17
- import { flowIfEnabled } from '../diagnostics/message-flow';
26
+ import { flowIfEnabled, traceSessionBindingControlDrop } from '../diagnostics/message-flow';
18
27
  import { captureZLinkExecutionTurn } from '../execution';
19
28
  import {
20
29
  ZLinkFrameworkInternalErrorKind,
@@ -58,6 +67,7 @@ import {
58
67
  ServiceStaleGenerationError,
59
68
  ServiceStatefulRegistry,
60
69
  ServiceTerminalOperationRegistry,
70
+ RELOCATION_REPLY_RETENTION_MS,
61
71
  type ServiceActorRef,
62
72
  type ServiceActorState,
63
73
  type ServiceSessionBinding,
@@ -104,15 +114,19 @@ import {
104
114
  SERVICE_WIRE_COMMAND_OFFSET,
105
115
  SERVICE_WIRE_FLAGS_OFFSET
106
116
  } from './service-wire-binary-primitives';
107
- import { SERVICE_WIRE_MAGIC, SERVICE_WIRE_MAJOR } from './service-wire-constants.generated';
117
+ import {
118
+ SERVICE_WIRE_MAGIC,
119
+ SERVICE_WIRE_MAJOR,
120
+ SERVICE_FRAMEWORK_MULTIPART_CONTENT_TYPE
121
+ } from './service-wire-constants.generated';
108
122
  import {
109
123
  ServiceWireProtocolError,
110
124
  decodeApplicationPayload,
111
125
  decodeApplicationPayloadView,
126
+ decodeMultipartApplicationParts,
112
127
  encodeApplicationPayload,
113
128
  type ServiceApplicationPayload
114
129
  } from './service-wire-m6a-codec';
115
- const STATEFUL_OPERATION_RETRY_TICK_MS = 20;
116
130
 
117
131
  const MAX_NODE_TIMER_DELAY_MS = 2_147_483_647;
118
132
 
@@ -206,6 +220,58 @@ export interface ServiceCanonicalActorJoinCandidate {
206
220
  readonly local: { readonly phase: 'admission'; readonly transferId: string };
207
221
  }
208
222
 
223
+ /**
224
+ * Session–Actor binding §4: the owner ID and owner lease generation of a
225
+ * Session owner identity are the host's `(OwnerId, LeaseGeneration)` published
226
+ * in its MeshNode descriptor.
227
+ */
228
+ export interface ServiceSessionOwnerToken {
229
+ readonly ownerId: string;
230
+ readonly leaseGeneration: bigint;
231
+ }
232
+
233
+ /** The descriptor source of the Session owner identity (§4). */
234
+ export interface ServiceSessionOwnerDescriptors {
235
+ /** This host's published token, or undefined while it has no live descriptor. */
236
+ local(): ServiceSessionOwnerToken | undefined;
237
+ /**
238
+ * The token of the admitted peer descriptor with this exact RID and
239
+ * lifecycle generation that passed the Location runtime §4.1 owner lease
240
+ * check, or undefined when no such descriptor exists.
241
+ */
242
+ peer(nodeRid: string, nodeGeneration: bigint): Promise<ServiceSessionOwnerToken | undefined>;
243
+ }
244
+
245
+ /** The four values of one Session owner identity (§4). */
246
+ export interface ServiceSessionOwnerIdentity {
247
+ readonly nodeRid: string;
248
+ readonly nodeGeneration: bigint;
249
+ readonly ownerId: string;
250
+ readonly leaseGeneration: bigint;
251
+ }
252
+
253
+ /**
254
+ * The receiving Session owner compares the identity carried by command 42,
255
+ * 51 or 54 with its current values exactly (§4).
256
+ */
257
+ export function sessionOwnerFenceMatches(
258
+ local: ServiceSessionOwnerIdentity,
259
+ fence: Pick<
260
+ ServiceRetiredBoundSessionRouteFence,
261
+ | 'sessionOwnerNodeRid'
262
+ | 'sessionOwnerNodeGeneration'
263
+ | 'sessionOwnerId'
264
+ | 'sessionOwnerLeaseGeneration'
265
+ >
266
+ ): boolean {
267
+ return (
268
+ routingIdsEqual(fence.sessionOwnerNodeRid, local.nodeRid) &&
269
+ fence.sessionOwnerNodeGeneration === local.nodeGeneration &&
270
+ fence.sessionOwnerId === local.ownerId &&
271
+ fence.sessionOwnerLeaseGeneration === local.leaseGeneration
272
+ );
273
+ }
274
+
209
275
  export interface ServiceSessionDelivery {
210
276
  readonly binding: ServiceSessionBinding;
211
277
  /**
@@ -213,6 +279,7 @@ export interface ServiceSessionDelivery {
213
279
  * been validated. The stream adapter owns multipart decoding at that point.
214
280
  */
215
281
  readonly deliver: (sessionRid: string, payloadFrame: Uint8Array) => Promise<boolean>;
282
+ readonly close?: () => Promise<void>;
216
283
  /** Enqueues the replacement lifecycle callback on the owning STREAM session. */
217
284
  readonly onBindingReplaced?: (
218
285
  actorId: string,
@@ -247,6 +314,7 @@ export interface ServiceInstanceApplicationLifecycle {
247
314
  isIdleEvicting?(target: ServiceInstanceApplicationIdentity): boolean;
248
315
  beginIdleEviction?(target: ServiceInstanceApplicationIdentity): boolean;
249
316
  materialize(target: ServiceInstanceApplicationIdentity, objectGeneration: bigint): Promise<void>;
317
+ publish?(target: ServiceInstanceApplicationTarget): void;
250
318
  discard(target: ServiceInstanceActivationTarget): Promise<void>;
251
319
  beginTerminal(target: ServiceInstanceApplicationTarget): void;
252
320
  completeTerminal(target: ServiceInstanceApplicationTarget): Promise<boolean>;
@@ -382,7 +450,7 @@ export class ServiceStatefulRuntime {
382
450
 
383
451
  readonly registry: ServiceStatefulRegistry;
384
452
 
385
- private readonly operations: ServiceTerminalOperationRegistry<ServiceStatefulResult>;
453
+ private readonly operations: ServiceTerminalOperationRegistry<ServiceStatefulResult, bigint>;
386
454
  private readonly sessionDeliveries = new Map<string, ServiceSessionDelivery>();
387
455
  /**
388
456
  * A local Actor owner can replace its own session binding before the
@@ -415,6 +483,7 @@ export class ServiceStatefulRuntime {
415
483
  private asyncInstanceAuthority?: ServiceAsyncInstanceActivationAuthority;
416
484
  private instanceApplicationLifecycle?: ServiceInstanceApplicationLifecycle;
417
485
  private userSpotOperationHandler?: ServiceUserSpotOperationHandler;
486
+ private requestSourceAcceptance?: import('../backend/contracts').ZLinkBackendRequestSourceAcceptance;
418
487
  private messageFollowHandler?: (
419
488
  record: import('./service-stateful-wire-codec').ServiceMessageFollowRecord
420
489
  ) => void;
@@ -423,6 +492,7 @@ export class ServiceStatefulRuntime {
423
492
  readonly owner: string;
424
493
  }) => void;
425
494
  private dispatchErrors?: ZLinkDispatchErrorReporter;
495
+ private sessionOwnerDescriptors?: ServiceSessionOwnerDescriptors;
426
496
  private dispatchErrorMeshName?: string;
427
497
  private readonly admittedUserSpotOperations = new Map<
428
498
  string,
@@ -454,7 +524,7 @@ export class ServiceStatefulRuntime {
454
524
  );
455
525
  this.registry = new ServiceStatefulRegistry(nodeRid, nodeGeneration);
456
526
  this.registry.createEntrySpot(nodeRid);
457
- raw.setServiceIngress((record) => this.ingress(record));
527
+ raw.setServiceIngress((record) => this.ingress(record), acceptsStatefulIngress);
458
528
  }
459
529
 
460
530
  setMailboxDropHandler(
@@ -466,6 +536,52 @@ export class ServiceStatefulRuntime {
466
536
  this.mailboxDropHandler = handler;
467
537
  }
468
538
 
539
+ setSessionOwnerDescriptors(source: ServiceSessionOwnerDescriptors): void {
540
+ this.sessionOwnerDescriptors = source;
541
+ }
542
+
543
+ /** §4: this host's Session owner identity, taken from its own descriptor. */
544
+ private localSessionOwner(): ServiceSessionOwnerIdentity | undefined {
545
+ const token = this.sessionOwnerDescriptors?.local();
546
+ return token === undefined
547
+ ? undefined
548
+ : {
549
+ nodeRid: this.nodeRid,
550
+ nodeGeneration: this.nodeGeneration,
551
+ ownerId: token.ownerId,
552
+ leaseGeneration: token.leaseGeneration
553
+ };
554
+ }
555
+
556
+ /**
557
+ * §4: the identity of the Session owner that requested a bind. It is this
558
+ * host's own descriptor, or the admitted peer descriptor of that exact
559
+ * lifecycle that passed the owner lease check. Without one the bind is
560
+ * rejected instead of storing a guessed identity.
561
+ */
562
+ private async sessionOwnerOf(
563
+ nodeRid: string,
564
+ nodeGeneration: bigint
565
+ ): Promise<ServiceSessionOwnerIdentity> {
566
+ const token = routingIdsEqual(nodeRid, this.nodeRid)
567
+ ? nodeGeneration === this.nodeGeneration
568
+ ? this.sessionOwnerDescriptors?.local()
569
+ : undefined
570
+ : await this.sessionOwnerDescriptors?.peer(nodeRid, nodeGeneration);
571
+ if (token === undefined) {
572
+ throw createInternalFrameworkException(
573
+ ZLinkFrameworkInternalErrorKind.RouteNotConnected,
574
+ `Session owner '${nodeRid}' has no live MeshNode descriptor for its lifecycle.`
575
+ );
576
+ }
577
+ return {
578
+ nodeRid,
579
+ nodeGeneration,
580
+ ownerId: token.ownerId,
581
+ leaseGeneration: token.leaseGeneration
582
+ };
583
+ }
584
+
469
585
  setDispatchErrorReporter(reporter: ZLinkDispatchErrorReporter, meshName: string): void {
470
586
  this.dispatchErrors = reporter;
471
587
  this.dispatchErrorMeshName = meshName;
@@ -567,7 +683,7 @@ export class ServiceStatefulRuntime {
567
683
  if (
568
684
  current?.bindingGeneration === bindingGeneration &&
569
685
  current.sessionOwnerNodeRid === sessionNodeRid &&
570
- current.sessionRid === sessionRid
686
+ routingIdsEqual(current.sessionRid, sessionRid)
571
687
  ) {
572
688
  return;
573
689
  }
@@ -1131,6 +1247,78 @@ export class ServiceStatefulRuntime {
1131
1247
  this.messageFollowHandler = handler;
1132
1248
  }
1133
1249
 
1250
+ setRequestSourceAcceptance(
1251
+ acceptance: import('../backend/contracts').ZLinkBackendRequestSourceAcceptance
1252
+ ): void {
1253
+ this.requestSourceAcceptance = acceptance;
1254
+ }
1255
+
1256
+ /**
1257
+ * Location runtime §9.3: the first accepting owner reads the sender's owner values once. A
1258
+ * sender without a live descriptor ends the request Unavailable (Session·Actor binding §4). The
1259
+ * polled descriptors answer without a promise; only a Store read returns one.
1260
+ */
1261
+ private acceptRequestSource(
1262
+ ingress: RawServiceIngressRecord,
1263
+ signal?: AbortSignal
1264
+ ): Promise<void> | undefined {
1265
+ if (ingress.requestSource !== undefined) return undefined;
1266
+ const acceptance = this.requestSourceAcceptance;
1267
+ if (acceptance === undefined) return undefined;
1268
+ const generation =
1269
+ ingress.sourceNodeGeneration ?? this.tryPeerGeneration(ingress.sourceRoutingId);
1270
+ const sourceRouteHex =
1271
+ ingress.sourceRoute === undefined || ingress.sourceRoute.length === 0
1272
+ ? undefined
1273
+ : Buffer.from(ingress.sourceRoute).toString('hex');
1274
+ const sourceRid =
1275
+ sourceRouteHex === undefined
1276
+ ? ingress.sourceRoutingId
1277
+ : decodeRoutingId(ingress.sourceRoutingId, sourceRouteHex);
1278
+ const source =
1279
+ generation === undefined
1280
+ ? undefined
1281
+ : acceptance(
1282
+ sourceRid,
1283
+ generation,
1284
+ sourceRouteHex ?? encodeRoutingIdStorageHex(sourceRid),
1285
+ signal
1286
+ );
1287
+ if (source instanceof Promise) {
1288
+ return source.then(
1289
+ (read) => this.recordRequestSource(ingress, read),
1290
+ (error: unknown) => {
1291
+ if (
1292
+ error instanceof ZLinkFrameworkException &&
1293
+ error.kind === ZLinkFrameworkErrorKind.Unavailable &&
1294
+ this.dispatchErrors?.captureEnabled() === true
1295
+ ) {
1296
+ this.dispatchErrors.report({
1297
+ surface: ZLinkDispatchErrorSurface.Node,
1298
+ messageKind: ZLinkDispatchMessageKind.Request,
1299
+ reason: ZLinkDispatchErrorReason.StaleTarget,
1300
+ action: ZLinkDispatchErrorAction.ReplyError,
1301
+ meshName: this.dispatchErrorMeshName,
1302
+ sourceRid: ingress.sourceRoutingId,
1303
+ error
1304
+ });
1305
+ }
1306
+ throw error;
1307
+ }
1308
+ );
1309
+ }
1310
+ this.recordRequestSource(ingress, source);
1311
+ return undefined;
1312
+ }
1313
+
1314
+ private recordRequestSource(
1315
+ ingress: RawServiceIngressRecord,
1316
+ source: import('../locations/request-source-resolver').ZLinkRequestSourceFence | undefined
1317
+ ): void {
1318
+ if (source === undefined) throw requestSourceUnavailable(ingress.sourceRoutingId);
1319
+ ingress.requestSource = source;
1320
+ }
1321
+
1134
1322
  sealSpotMessageFollowIngress(
1135
1323
  source: ServiceDirectSpotRouteFence
1136
1324
  ): ServiceSpotMessageFollowSeal | undefined {
@@ -1180,11 +1368,15 @@ export class ServiceStatefulRuntime {
1180
1368
 
1181
1369
  async commitSpotMessageFollowIngress(
1182
1370
  seal: ServiceSpotMessageFollowSeal,
1183
- target: ServiceDirectSpotRouteFence,
1371
+ target: ServiceDirectSpotRouteFence | null,
1184
1372
  durationMs: number
1185
1373
  ): Promise<boolean> {
1186
1374
  const state = this.exactSpotMessageFollow(seal);
1187
1375
  if (state === undefined || state.target !== undefined) return false;
1376
+ if (target === null) {
1377
+ this.failExpiredSpotMessageFollow(state);
1378
+ return true;
1379
+ }
1188
1380
  if (!Number.isSafeInteger(durationMs) || durationMs < 0) {
1189
1381
  throw new RangeError('Message Follow duration must be a non-negative safe integer.');
1190
1382
  }
@@ -1326,7 +1518,8 @@ export class ServiceStatefulRuntime {
1326
1518
  sourceSpotId: string,
1327
1519
  requested: ServiceDirectSpotRouteFence,
1328
1520
  payload: ServiceApplicationPayload,
1329
- timeoutMs: number
1521
+ timeoutMs: number,
1522
+ onAdmitted?: () => void
1330
1523
  ): ServiceStatefulPendingOperation {
1331
1524
  const pending = this.operations.register(timeoutMs);
1332
1525
  const target = this.acceptSpotAuthority(requested);
@@ -1346,20 +1539,21 @@ export class ServiceStatefulRuntime {
1346
1539
  target.targetNodeRid,
1347
1540
  [header, encodeApplicationPayload(payload)],
1348
1541
  timeoutMs,
1349
- 'spotRequest'
1542
+ 'spotRequest',
1543
+ undefined,
1544
+ onAdmitted
1350
1545
  );
1351
1546
  return pending;
1352
1547
  }
1353
1548
 
1354
1549
  async sendToActor(
1355
1550
  target: ServiceActorRef,
1356
- _targetNodeGeneration: bigint,
1357
- _authorityOwnerGeneration: bigint,
1358
1551
  payload: ServiceApplicationPayload,
1359
1552
  sourceActor?: ServiceActorRef,
1360
- boundSession?: { readonly sessionRid: string; readonly bindingGeneration: bigint }
1553
+ boundSession?: { readonly sessionRid: string; readonly bindingGeneration: bigint },
1554
+ routeFence?: ServiceActorRouteFence
1361
1555
  ): Promise<number> {
1362
- const route = this.tryActorFence(target);
1556
+ const route = routeFence ?? this.tryActorFence(target);
1363
1557
  if (route === undefined) return SubmitResult.NotFound;
1364
1558
  const header = encodeActorHeader(
1365
1559
  'actorSend',
@@ -1379,15 +1573,16 @@ export class ServiceStatefulRuntime {
1379
1573
 
1380
1574
  requestToActor(
1381
1575
  target: ServiceActorRef,
1382
- _targetNodeGeneration: bigint,
1383
- _authorityOwnerGeneration: bigint,
1384
1576
  payload: ServiceApplicationPayload,
1385
1577
  timeoutMs: number,
1386
1578
  sourceActor?: ServiceActorRef,
1387
- boundSession?: { readonly sessionRid: string; readonly bindingGeneration: bigint }
1579
+ boundSession?: { readonly sessionRid: string; readonly bindingGeneration: bigint },
1580
+ onAdmitted?: () => void,
1581
+ routeFence?: ServiceActorRouteFence
1388
1582
  ): ServiceStatefulPendingOperation {
1389
- const pending = this.operations.register(timeoutMs);
1390
- const route = this.tryActorFence(target);
1583
+ const operationLow = this.nextMessageFollowOperation++;
1584
+ const pending = this.operations.register(timeoutMs, 'registry', operationLow);
1585
+ const route = routeFence ?? this.tryActorFence(target);
1391
1586
  if (route === undefined) {
1392
1587
  this.operations.reply(
1393
1588
  pending.id,
@@ -1406,18 +1601,35 @@ export class ServiceStatefulRuntime {
1406
1601
  ...boundSession,
1407
1602
  sequence: this.nextSessionSequence++
1408
1603
  },
1409
- { high: this.nodeGeneration, low: this.nextMessageFollowOperation++ }
1604
+ { high: this.nodeGeneration, low: operationLow }
1410
1605
  );
1411
1606
  this.submitRequest(
1412
1607
  pending,
1413
1608
  target.nodeRid,
1414
1609
  [header, encodeApplicationPayload(payload)],
1415
1610
  timeoutMs,
1416
- 'actorRequest'
1611
+ 'actorRequest',
1612
+ undefined,
1613
+ onAdmitted
1417
1614
  );
1418
1615
  return pending;
1419
1616
  }
1420
1617
 
1618
+ acceptRelocatedActorTerminal(
1619
+ operation: { readonly high: bigint; readonly low: bigint },
1620
+ replyRouteId: bigint,
1621
+ result: ServiceStatefulResult
1622
+ ): 'terminalReceived' | 'alreadyTerminal' | undefined {
1623
+ if (
1624
+ operation.high !== this.nodeGeneration ||
1625
+ this.operations.context(replyRouteId) !== operation.low
1626
+ )
1627
+ return undefined;
1628
+ return this.operations.reply(replyRouteId, result, RELOCATION_REPLY_RETENTION_MS)
1629
+ ? 'terminalReceived'
1630
+ : 'alreadyTerminal';
1631
+ }
1632
+
1421
1633
  async sendToInstanceSpot(
1422
1634
  route: ServiceInstanceRouteFence,
1423
1635
  payload: ServiceApplicationPayload,
@@ -1499,7 +1711,8 @@ export class ServiceStatefulRuntime {
1499
1711
  timeoutMs: number,
1500
1712
  sourceSpotId?: string,
1501
1713
  metadataFrame?: Uint8Array,
1502
- instanceIntent = false
1714
+ instanceIntent = false,
1715
+ onAdmitted?: () => void
1503
1716
  ): ServiceStatefulPendingOperation {
1504
1717
  const pending = this.operations.register(timeoutMs);
1505
1718
  this.submitRequest(
@@ -1523,7 +1736,9 @@ export class ServiceStatefulRuntime {
1523
1736
  metadataFrame
1524
1737
  ),
1525
1738
  timeoutMs,
1526
- 'instanceSpotRequest'
1739
+ 'instanceSpotRequest',
1740
+ undefined,
1741
+ onAdmitted
1527
1742
  );
1528
1743
  return pending;
1529
1744
  }
@@ -1551,7 +1766,8 @@ export class ServiceStatefulRuntime {
1551
1766
  payloadFrame: Buffer,
1552
1767
  deadlineUnixMs: bigint,
1553
1768
  sourceSpotId?: string,
1554
- metadataFrame?: Uint8Array
1769
+ metadataFrame?: Uint8Array,
1770
+ onAdmitted?: () => void
1555
1771
  ): ServiceStatefulPendingOperation {
1556
1772
  const timeoutMs = remainingDeadlineMs(deadlineUnixMs);
1557
1773
  const pending = this.operations.register(timeoutMs);
@@ -1576,7 +1792,9 @@ export class ServiceStatefulRuntime {
1576
1792
  metadataFrame
1577
1793
  ),
1578
1794
  timeoutMs,
1579
- 'instanceSpotRequest'
1795
+ 'instanceSpotRequest',
1796
+ undefined,
1797
+ onAdmitted
1580
1798
  );
1581
1799
  return pending;
1582
1800
  }
@@ -1747,27 +1965,51 @@ export class ServiceStatefulRuntime {
1747
1965
  timeoutMs: number,
1748
1966
  deliver: ServiceSessionDelivery['deliver'],
1749
1967
  onBindingReplaced?: ServiceSessionDelivery['onBindingReplaced'],
1750
- actorAuthority?: StreamSessionActorAuthorityFence
1968
+ actorAuthority?: StreamSessionActorAuthorityFence,
1969
+ close?: () => Promise<void>
1751
1970
  ): ServiceStatefulPendingOperation {
1752
1971
  const deadlineMs = performance.now() + timeoutMs;
1753
1972
  const pending = this.operations.register(timeoutMs, 'sender');
1754
- const generation = this.nextSessionSequence++;
1973
+ const sessionOwner = this.localSessionOwner();
1974
+ if (sessionOwner === undefined) {
1975
+ this.operations.reply(
1976
+ pending.id,
1977
+ failure(
1978
+ createInternalFrameworkException(
1979
+ ZLinkFrameworkInternalErrorKind.RouteNotConnected,
1980
+ `Session owner '${this.nodeRid}' has no live MeshNode descriptor.`
1981
+ )
1982
+ )
1983
+ );
1984
+ return pending;
1985
+ }
1986
+ const deliveryKey = actorKey(actor);
1987
+ const previousDelivery = this.sessionDeliveries.get(deliveryKey);
1988
+ const previousBinding = previousDelivery?.binding;
1989
+ const generation =
1990
+ previousBinding !== undefined &&
1991
+ sameActorRef(previousBinding.actor, actor) &&
1992
+ previousBinding.sessionRid === sessionRid &&
1993
+ previousBinding.sessionOwnerNodeRid === sessionOwner.nodeRid &&
1994
+ previousBinding.sessionOwnerNodeGeneration === sessionOwner.nodeGeneration
1995
+ ? previousBinding.bindingGeneration
1996
+ : this.nextSessionSequence++;
1755
1997
  const localBinding: ServiceSessionBinding = {
1756
1998
  actor,
1757
1999
  sessionRid,
1758
- sessionOwnerNodeRid: this.nodeRid,
1759
- sessionOwnerNodeGeneration: this.nodeGeneration,
1760
- sessionOwnerId: this.nodeRid,
1761
- sessionOwnerLeaseGeneration: this.nodeGeneration,
2000
+ sessionOwnerNodeRid: sessionOwner.nodeRid,
2001
+ sessionOwnerNodeGeneration: sessionOwner.nodeGeneration,
2002
+ sessionOwnerId: sessionOwner.ownerId,
2003
+ sessionOwnerLeaseGeneration: sessionOwner.leaseGeneration,
1762
2004
  bindingGeneration: generation,
1763
2005
  membershipEpoch: this.registry.actor(actor.actorId)?.membershipEpoch ?? 1n
1764
2006
  };
1765
2007
  const delivery: ServiceSessionDelivery = {
1766
2008
  binding: localBinding,
1767
2009
  deliver,
2010
+ ...(close === undefined ? {} : { close }),
1768
2011
  ...(onBindingReplaced === undefined ? {} : { onBindingReplaced })
1769
2012
  };
1770
- const deliveryKey = actorKey(actor);
1771
2013
  if (actor.nodeRid === this.nodeRid) {
1772
2014
  let installedDelivery: ServiceSessionDelivery | undefined;
1773
2015
  try {
@@ -1791,18 +2033,7 @@ export class ServiceStatefulRuntime {
1791
2033
  });
1792
2034
  return pending;
1793
2035
  }
1794
- const previousDelivery = this.sessionDeliveries.get(deliveryKey);
1795
- const binding = this.registry.bindSession(
1796
- actor,
1797
- sessionRid,
1798
- this.nodeRid,
1799
- this.nodeGeneration,
1800
- this.nodeRid,
1801
- this.nodeGeneration
1802
- );
1803
- if (previousDelivery !== undefined && previous !== undefined) {
1804
- this.retiredSessionDeliveries.set(sessionDeliveryKey(previous), previousDelivery);
1805
- }
2036
+ const binding = this.registry.installSessionBinding(localBinding);
1806
2037
  installedDelivery = { ...delivery, binding };
1807
2038
  this.sessionDeliveries.set(deliveryKey, installedDelivery);
1808
2039
  this.operations.reply(pending.id, {
@@ -1810,7 +2041,10 @@ export class ServiceStatefulRuntime {
1810
2041
  failureCode: 0,
1811
2042
  kindData: undefined
1812
2043
  });
1813
- if (previous !== undefined) {
2044
+ if (previous !== undefined && previous !== binding) {
2045
+ if (previousDelivery !== undefined) {
2046
+ this.retiredSessionDeliveries.set(sessionDeliveryKey(previous), previousDelivery);
2047
+ }
1814
2048
  this.sendBoundSessionReplacement(
1815
2049
  previous,
1816
2050
  actorAuthorityFromRoute(
@@ -1835,7 +2069,6 @@ export class ServiceStatefulRuntime {
1835
2069
  }
1836
2070
  return pending;
1837
2071
  }
1838
- const previousDelivery = this.sessionDeliveries.get(deliveryKey);
1839
2072
  let bindingActorFence: ServiceActorRouteFence;
1840
2073
  let header: Buffer;
1841
2074
  try {
@@ -1883,7 +2116,10 @@ export class ServiceStatefulRuntime {
1883
2116
  // Publish the exact Location authority used by command 38 in the
1884
2117
  // same Session-owner completion turn as the binding route. Actor
1885
2118
  // lookup only carries a peer-lifecycle fallback for owner lease.
1886
- this.rememberActorRoute(bindingActorFence);
2119
+ this.rememberActorRoute({
2120
+ ...bindingActorFence,
2121
+ authorityOwnerGeneration: result.streamBinding.authorityOwnerGeneration
2122
+ });
1887
2123
  this.sessionDeliveries.set(deliveryKey, committed);
1888
2124
  }
1889
2125
  }
@@ -1892,6 +2128,59 @@ export class ServiceStatefulRuntime {
1892
2128
  };
1893
2129
  }
1894
2130
 
2131
+ closeBoundSession(
2132
+ actor: ServiceActorRef,
2133
+ expectedBindingGeneration: bigint,
2134
+ timeoutMs: number
2135
+ ): ServiceStatefulPendingOperation {
2136
+ const pending = this.operations.register(timeoutMs);
2137
+ const binding = this.registry.binding(actor);
2138
+ if (binding === undefined) {
2139
+ this.operations.reply(
2140
+ pending.id,
2141
+ internalFrameworkWireReply(ZLinkFrameworkInternalErrorKind.ActorSessionNotBound)
2142
+ );
2143
+ return pending;
2144
+ }
2145
+ const header = Buffer.from(
2146
+ encodeBoundSessionCloseCommand(
2147
+ {
2148
+ command: 'boundSessionClose',
2149
+ flags: 0,
2150
+ correlation: pending.id,
2151
+ actor: { actorId: actor.actorId, objectGeneration: actor.generation },
2152
+ session: {
2153
+ sessionOwnerNodeRid: Buffer.from(
2154
+ encodeRoutingIdStorageHex(binding.sessionOwnerNodeRid),
2155
+ 'hex'
2156
+ ),
2157
+ sessionOwnerNodeGeneration: binding.sessionOwnerNodeGeneration!,
2158
+ sessionOwnerId: binding.sessionOwnerId!,
2159
+ sessionOwnerLeaseGeneration: binding.sessionOwnerLeaseGeneration!,
2160
+ sessionRid: Buffer.from(encodeRoutingIdStorageHex(binding.sessionRid), 'hex'),
2161
+ retiredBindingGeneration: expectedBindingGeneration
2162
+ }
2163
+ },
2164
+ {}
2165
+ )[0]!
2166
+ );
2167
+ const reply = routingIdsEqual(binding.sessionOwnerNodeRid, this.nodeRid)
2168
+ ? this.requestLocalInfrastructure(header, pending.id, timeoutMs)
2169
+ : this.raw.requestService(binding.sessionOwnerNodeRid, [header], timeoutMs);
2170
+ void reply.then(
2171
+ (parts) =>
2172
+ this.completeRemoteReply(
2173
+ pending,
2174
+ binding.sessionOwnerNodeRid,
2175
+ 'boundSessionClose',
2176
+ undefined,
2177
+ parts
2178
+ ),
2179
+ (error) => this.operations.fail(pending.id, error)
2180
+ );
2181
+ return pending;
2182
+ }
2183
+
1895
2184
  unbindSession(
1896
2185
  sessionRid: string,
1897
2186
  actor: ServiceActorRef,
@@ -1902,7 +2191,7 @@ export class ServiceStatefulRuntime {
1902
2191
  const delivery = this.sessionDeliveries.get(actorKey(actor));
1903
2192
  if (
1904
2193
  delivery === undefined ||
1905
- delivery.binding.sessionRid !== sessionRid ||
2194
+ !routingIdsEqual(delivery.binding.sessionRid, sessionRid) ||
1906
2195
  delivery.binding.bindingGeneration !== expectedBindingGeneration
1907
2196
  ) {
1908
2197
  this.operations.reply(
@@ -1932,7 +2221,7 @@ export class ServiceStatefulRuntime {
1932
2221
  sessionBindings(sessionRid: string): readonly ServiceStreamSessionBinding[] {
1933
2222
  return [...this.sessionDeliveries.values()]
1934
2223
  .map((value) => value.binding)
1935
- .filter((binding) => binding.sessionRid === sessionRid)
2224
+ .filter((binding) => routingIdsEqual(binding.sessionRid, sessionRid))
1936
2225
  .map((binding) => {
1937
2226
  const owner = requireSessionOwnerIdentity(binding);
1938
2227
  return {
@@ -1954,20 +2243,13 @@ export class ServiceStatefulRuntime {
1954
2243
  payload: ServiceApplicationPayload
1955
2244
  ): Promise<number> {
1956
2245
  const delivery = this.sessionDeliveries.get(actorKey(actor));
1957
- if (delivery === undefined || delivery.binding.sessionRid !== sessionRid) {
2246
+ if (delivery === undefined || !routingIdsEqual(delivery.binding.sessionRid, sessionRid)) {
1958
2247
  return SubmitResult.NotFound;
1959
2248
  }
1960
- return this.sendToActor(
1961
- actor,
1962
- this.peerGeneration(actor.nodeRid),
1963
- actor.generation,
1964
- payload,
1965
- undefined,
1966
- {
1967
- sessionRid,
1968
- bindingGeneration: delivery.binding.bindingGeneration
1969
- }
1970
- );
2249
+ return this.sendToActor(actor, payload, undefined, {
2250
+ sessionRid,
2251
+ bindingGeneration: delivery.binding.bindingGeneration
2252
+ });
1971
2253
  }
1972
2254
 
1973
2255
  async sendBoundSession(
@@ -2003,6 +2285,11 @@ export class ServiceStatefulRuntime {
2003
2285
  return this.operations.size;
2004
2286
  }
2005
2287
 
2288
+ /** The next stateful request expiry, as a lower bound. */
2289
+ nextMaintenanceDeadlineMs(): number {
2290
+ return this.operations.nextDeadlineMs();
2291
+ }
2292
+
2006
2293
  close(): void {
2007
2294
  if (this.closed) return;
2008
2295
  this.closed = true;
@@ -2026,32 +2313,13 @@ export class ServiceStatefulRuntime {
2026
2313
  this.spotMessageFollow.clear();
2027
2314
  }
2028
2315
 
2316
+ cancelPendingOperations(reason: unknown): void {
2317
+ this.operations.failAll(reason);
2318
+ }
2319
+
2029
2320
  private async ingress(
2030
2321
  record: RawServiceIngressRecord
2031
2322
  ): Promise<RawServicePumpResult | undefined> {
2032
- // These frozen relocation controls overlap the legacy stateful command
2033
- // number range (30-34). They are bare infrastructure records and must
2034
- // fall through to RawServiceMeshRuntime's relocation ingress instead of
2035
- // being decoded as stateful actor/spot work.
2036
- if (
2037
- record.command === M6bServiceWireCommand.relocationReady ||
2038
- record.command === M6bServiceWireCommand.relocationData ||
2039
- record.command === M6bServiceWireCommand.replyRelay ||
2040
- record.command === M6bServiceWireCommand.relocationCutover
2041
- ) {
2042
- return undefined;
2043
- }
2044
- if (
2045
- record.command < M6bServiceWireCommand.spotSend ||
2046
- (record.command > M6bServiceWireCommand.instanceSpot &&
2047
- record.command !== M6bServiceWireCommand.userSpotCreate &&
2048
- record.command !== M6bServiceWireCommand.userSpotClose &&
2049
- record.command !== M6bServiceWireCommand.actorCreate &&
2050
- record.command !== M6bServiceWireCommand.messageFollow &&
2051
- record.command !== M6bServiceWireCommand.boundSessionReplaced)
2052
- ) {
2053
- return undefined;
2054
- }
2055
2323
  let decoded: ServiceStatefulWireRecord;
2056
2324
  let canonicalActorJoin: ActorJoin28 | undefined;
2057
2325
  try {
@@ -2098,7 +2366,8 @@ export class ServiceStatefulRuntime {
2098
2366
  decoded.kind === 'userSpotClose' ||
2099
2367
  decoded.kind === 'actorCreate' ||
2100
2368
  decoded.kind === 'messageFollow' ||
2101
- decoded.kind === 'boundSessionReplaced';
2369
+ decoded.kind === 'boundSessionReplaced' ||
2370
+ decoded.kind === 'boundSessionClose';
2102
2371
  if (
2103
2372
  record.parts.length < 1 ||
2104
2373
  record.parts.length > (instanceMetadata || statefulMetadata ? 3 : 2) ||
@@ -2143,7 +2412,13 @@ export class ServiceStatefulRuntime {
2143
2412
  decoded.activation === 'missing' &&
2144
2413
  (error instanceof ZLinkFrameworkException || error instanceof ServiceStaleGenerationError)
2145
2414
  ) {
2146
- this.finishMissingInstanceActivationFailure(record, decoded, error);
2415
+ this.finishMissingInstanceActivationFailure(
2416
+ record,
2417
+ decoded,
2418
+ error,
2419
+ undefined,
2420
+ payloadFrame
2421
+ );
2147
2422
  return 'infrastructure';
2148
2423
  }
2149
2424
  const correlation = statefulCorrelation(decoded);
@@ -2275,7 +2550,7 @@ export class ServiceStatefulRuntime {
2275
2550
  record.boundSession.bindingGeneration
2276
2551
  );
2277
2552
  if (
2278
- binding.sessionRid !== record.boundSession.sessionRid ||
2553
+ !routingIdsEqual(binding.sessionRid, record.boundSession.sessionRid) ||
2279
2554
  binding.sessionOwnerNodeRid !== ingress.sourceRoutingId
2280
2555
  ) {
2281
2556
  throw new ServiceStaleGenerationError('binding', record.target.actor.actorId);
@@ -2298,12 +2573,13 @@ export class ServiceStatefulRuntime {
2298
2573
  : { sourceBindingGeneration: record.boundSession.bindingGeneration }),
2299
2574
  targetActor: actor.ref,
2300
2575
  messageFollowOrigin: {
2301
- sourceNodeRid: ingress.sourceRoutingId,
2302
- originalOperation: {
2303
- high: this.peerGeneration(ingress.sourceRoutingId),
2304
- low: record.correlation ?? this.nextMessageFollowOperation++
2576
+ get requestSource() {
2577
+ return ingress.requestSource;
2305
2578
  },
2306
- originalReplyRouteId: ingress.requestSequence ?? 0n
2579
+ target: record.target,
2580
+ sourceNodeRid: ingress.sourceRoutingId,
2581
+ originalOperation: record.operation,
2582
+ originalReplyRouteId: record.correlation ?? 0n
2307
2583
  },
2308
2584
  ...(record.kind === 'actorRequest'
2309
2585
  ? { reply: this.replyPort(ingress, record.correlation) }
@@ -2341,7 +2617,9 @@ export class ServiceStatefulRuntime {
2341
2617
  canonicalActorJoinPayload
2342
2618
  );
2343
2619
  case 'boundSessionBind':
2344
- return this.replySessionBind(ingress, record);
2620
+ return await this.replySessionBind(ingress, record);
2621
+ case 'boundSessionClose':
2622
+ return await this.handleBoundSessionClose(ingress, record);
2345
2623
  case 'boundSessionReplaced':
2346
2624
  return this.handleBoundSessionReplaced(ingress, record);
2347
2625
  case 'boundSessionSend':
@@ -2371,6 +2649,7 @@ export class ServiceStatefulRuntime {
2371
2649
  localReply,
2372
2650
  metadataFrame
2373
2651
  ).finally(() => ownedIngress.applicationJobOwner?.close());
2652
+ ingress.onAdmitted?.();
2374
2653
  return 'infrastructure';
2375
2654
  }
2376
2655
  if (
@@ -2384,6 +2663,7 @@ export class ServiceStatefulRuntime {
2384
2663
  payloadFrame,
2385
2664
  metadataFrame
2386
2665
  ).finally(() => ownedIngress.applicationJobOwner?.close());
2666
+ ingress.onAdmitted?.();
2387
2667
  return 'infrastructure';
2388
2668
  }
2389
2669
  const spot = this.requireInstanceActivation(ingress, record);
@@ -2546,7 +2826,7 @@ export class ServiceStatefulRuntime {
2546
2826
  }
2547
2827
  });
2548
2828
  } catch (error) {
2549
- this.finishMissingInstanceActivationFailure(ingress, record, error, localReply);
2829
+ this.finishMissingInstanceActivationFailure(ingress, record, error, localReply, payloadFrame);
2550
2830
  }
2551
2831
  }
2552
2832
 
@@ -2581,6 +2861,7 @@ export class ServiceStatefulRuntime {
2581
2861
  latestRoute !== undefined && sameInstanceApplicationOwner(latestRoute, record.route)
2582
2862
  ? { ...record, route: latestRoute }
2583
2863
  : record;
2864
+ this.publishCommittedInstanceRoute(intent.instanceType, refreshedRecord.route);
2584
2865
  const spot = this.requireInstanceActivation(ingress, refreshedRecord);
2585
2866
  const admitted = this.enqueueActivatedInstanceSpot(
2586
2867
  ingress,
@@ -2737,7 +3018,8 @@ export class ServiceStatefulRuntime {
2737
3018
  { readonly kind: 'instanceSpot'; readonly activation: 'missing' }
2738
3019
  >,
2739
3020
  error: unknown,
2740
- localReply?: NonNullable<ServiceStatefulMailboxData['reply']>
3021
+ localReply?: NonNullable<ServiceStatefulMailboxData['reply']>,
3022
+ payloadFrame?: Buffer
2741
3023
  ): void {
2742
3024
  if (record.operationKind === 'request') {
2743
3025
  if (record.deadlineUnixMs <= BigInt(Date.now())) return;
@@ -2750,17 +3032,51 @@ export class ServiceStatefulRuntime {
2750
3032
  return;
2751
3033
  }
2752
3034
  const reporter = this.dispatchErrors;
3035
+ const tracePoint = flowIfEnabled(reporter?.flow, ZLinkMessageFlowOutcome.Dropped);
3036
+ if (tracePoint === undefined && reporter?.captureEnabled() !== true) return;
3037
+ const reason = isAbortError(error)
3038
+ ? ZLinkDispatchErrorReason.Shutdown
3039
+ : dispatchReasonFromError(error);
3040
+ // Decode the retained first message only after diagnostics accepts the terminal.
3041
+ // Detached local and remote activation share this owner and its explicit flow.
3042
+ const payload =
3043
+ payloadFrame === undefined ? undefined : decodeApplicationPayloadView(payloadFrame);
3044
+ const header =
3045
+ payload?.contentType === SERVICE_FRAMEWORK_MULTIPART_CONTENT_TYPE
3046
+ ? decodeChannelHeader(
3047
+ decodeMultipartApplicationParts(payloadFrame!, ZLinkBufferMessage.fromOwned),
3048
+ reporter?.flow.flowCreationEnabled()
3049
+ )
3050
+ : undefined;
3051
+ const context = {
3052
+ surface: ZLinkDispatchErrorSurface.InstanceSpot,
3053
+ messageKind: ZLinkDispatchMessageKind.Send,
3054
+ meshName: this.dispatchErrorMeshName,
3055
+ sourceRid: record.sourceNodeRid,
3056
+ spotId: record.target.targetSpotId,
3057
+ instanceSpotType: record.target.stableType,
3058
+ activationState: 'activating' as const,
3059
+ packetName: header?.messageName ?? payload?.packetName,
3060
+ flowId: header?.flowId,
3061
+ flowOrigin: header?.flowOrigin
3062
+ };
3063
+ tracePoint?.trace({
3064
+ ...context,
3065
+ outcome: ZLinkMessageFlowOutcome.Dropped,
3066
+ errorReason:
3067
+ reason === ZLinkDispatchErrorReason.Shutdown
3068
+ ? reason
3069
+ : error instanceof ZLinkFrameworkException &&
3070
+ error.kind === ZLinkFrameworkErrorKind.DeadlineExceeded
3071
+ ? 'activation_timeout'
3072
+ : 'activation_rejected',
3073
+ ...dispatchErrorDetails(error)
3074
+ });
2753
3075
  if (reporter?.captureEnabled() === true) {
2754
3076
  reporter.report({
2755
- surface: ZLinkDispatchErrorSurface.InstanceSpot,
2756
- messageKind: ZLinkDispatchMessageKind.Send,
2757
- reason: ZLinkDispatchErrorReason.StaleTarget,
3077
+ ...context,
3078
+ reason,
2758
3079
  action: ZLinkDispatchErrorAction.Drop,
2759
- meshName: this.dispatchErrorMeshName,
2760
- sourceRid: record.sourceNodeRid,
2761
- spotId: record.target.targetSpotId,
2762
- instanceSpotType: record.target.stableType,
2763
- activationState: 'activating',
2764
3080
  error
2765
3081
  });
2766
3082
  }
@@ -2979,12 +3295,25 @@ export class ServiceStatefulRuntime {
2979
3295
  ): Promise<unknown> {
2980
3296
  const key = record.target.targetSpotId;
2981
3297
  const pending = this.pendingInstanceActivations.get(key);
3298
+ const metric =
3299
+ pending === undefined
3300
+ ? this.dispatchErrors?.metrics?.startInstanceSpotActivation(
3301
+ record.target.targetMeshName,
3302
+ record.target.stableType
3303
+ )
3304
+ : undefined;
2982
3305
  const ready =
2983
3306
  pending ??
2984
- this.runMissingInstanceActivation(record, payloadFrame, metadataFrame).then((activation) => ({
2985
- ...activation,
2986
- stableType: record.target.stableType
2987
- }));
3307
+ this.runMissingInstanceActivation(record, payloadFrame, metadataFrame).then(
3308
+ (activation) => {
3309
+ metric?.complete('ready');
3310
+ return { ...activation, stableType: record.target.stableType };
3311
+ },
3312
+ (error) => {
3313
+ metric?.complete(instanceActivationOutcome(error));
3314
+ throw error;
3315
+ }
3316
+ );
2988
3317
  const operation = ready.then(async (value) => {
2989
3318
  if (pending !== undefined) {
2990
3319
  await this.asyncInstanceAuthority?.read(
@@ -3033,12 +3362,38 @@ export class ServiceStatefulRuntime {
3033
3362
  ): Promise<{
3034
3363
  readonly spot: Pick<ServiceSpotState, 'ref'>;
3035
3364
  readonly route: ServiceInstanceRouteFence;
3365
+ }> {
3366
+ const deadline = operationDeadline(record.deadlineUnixMs);
3367
+ try {
3368
+ deadline.signal.throwIfAborted();
3369
+ return await awaitWithAbort(
3370
+ this.runMissingInstanceActivationCore(record, payloadFrame, metadataFrame, deadline.signal),
3371
+ deadline.signal
3372
+ );
3373
+ } catch (error) {
3374
+ if (isAbortError(error) && deadline.signal.aborted) throw deadline.signal.reason;
3375
+ throw error;
3376
+ } finally {
3377
+ deadline.close();
3378
+ }
3379
+ }
3380
+
3381
+ private async runMissingInstanceActivationCore(
3382
+ record: Extract<
3383
+ ServiceStatefulWireRecord,
3384
+ { readonly kind: 'instanceSpot'; readonly activation: 'missing' }
3385
+ >,
3386
+ payloadFrame: Buffer,
3387
+ metadataFrame: Buffer | undefined,
3388
+ signal: AbortSignal
3389
+ ): Promise<{
3390
+ readonly spot: Pick<ServiceSpotState, 'ref'>;
3391
+ readonly route: ServiceInstanceRouteFence;
3036
3392
  }> {
3037
3393
  const target = record.target;
3038
3394
  if (
3039
3395
  target.targetNodeRid !== this.nodeRid ||
3040
- target.targetNodeGeneration !== this.nodeGeneration ||
3041
- record.deadlineUnixMs < BigInt(Date.now())
3396
+ target.targetNodeGeneration !== this.nodeGeneration
3042
3397
  ) {
3043
3398
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3044
3399
  }
@@ -3150,21 +3505,22 @@ export class ServiceStatefulRuntime {
3150
3505
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3151
3506
  }
3152
3507
  }
3153
- const deadline = operationDeadline(record.deadlineUnixMs);
3154
- let reserved: ServiceInstanceAuthorityReserve;
3155
- try {
3156
- reserved = await authority.reserve(activationRequest, deadline.signal);
3157
- } finally {
3158
- deadline.close();
3159
- }
3160
- if (this.closed || record.deadlineUnixMs < BigInt(Date.now())) {
3508
+ const reserved = await authority.reserve(activationRequest, signal);
3509
+ if (this.closed || signal.aborted) {
3510
+ const error = new ZLinkFrameworkException(
3511
+ this.closed
3512
+ ? ZLinkFrameworkErrorKind.ShuttingDown
3513
+ : ZLinkFrameworkErrorKind.DeadlineExceeded,
3514
+ this.closed ? 'Runtime stopped during Instance activation.' : 'Operation deadline exceeded.'
3515
+ );
3161
3516
  await authority.abort(target, reserved.reservation);
3162
- throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3517
+ throw error;
3163
3518
  }
3164
3519
 
3165
3520
  let activation: { readonly spot: ServiceSpotState; readonly created: boolean };
3166
3521
  try {
3167
3522
  await this.instanceApplicationLifecycle?.materialize(target, reserved.reservation.attempt);
3523
+ signal.throwIfAborted();
3168
3524
  activation = this.activateInstanceSpot(
3169
3525
  target.targetSpotId,
3170
3526
  target.stableType,
@@ -3232,8 +3588,6 @@ export class ServiceStatefulRuntime {
3232
3588
  if (activation.created) this.registry.closeSpot(activation.spot.ref);
3233
3589
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3234
3590
  }
3235
- this.publishCommittedInstanceRoute(target.stableType, route);
3236
-
3237
3591
  const confirmed = await authority.read(target);
3238
3592
  if (confirmed.kind !== 'ready' || !sameInstanceRoute(confirmed.route, route)) {
3239
3593
  if (confirmed.kind === 'ready' && confirmed.route.targetNodeRid !== this.nodeRid) {
@@ -3248,6 +3602,7 @@ export class ServiceStatefulRuntime {
3248
3602
  ) {
3249
3603
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3250
3604
  }
3605
+ this.publishCommittedInstanceRoute(target.stableType, confirmed.route);
3251
3606
  return { spot: current, route: confirmed.route };
3252
3607
  }
3253
3608
 
@@ -3265,6 +3620,9 @@ export class ServiceStatefulRuntime {
3265
3620
  : instanceRouteAsDirectRoute(expectedCurrentRoute);
3266
3621
  this.rememberSpotRoute(directRoute, expectedDirectRoute);
3267
3622
  this.registerInstanceIntent(stableType, route, expectedCurrentRoute);
3623
+ const target = this.instanceSpotApplicationTarget(route.targetSpotId);
3624
+ if (target !== undefined)
3625
+ this.instanceApplicationLifecycle?.publish?.({ targetSpotId: route.targetSpotId, ...target });
3268
3626
  }
3269
3627
 
3270
3628
  private replaceCommittedInstanceRoute(
@@ -3417,55 +3775,52 @@ export class ServiceStatefulRuntime {
3417
3775
  return 'infrastructure';
3418
3776
  }
3419
3777
 
3420
- private replySessionBind(
3778
+ private async replySessionBind(
3421
3779
  ingress: RawServiceIngressRecord,
3422
3780
  record: Extract<ServiceStatefulWireRecord, { readonly kind: 'boundSessionBind' }>
3423
- ): RawServicePumpResult {
3781
+ ): Promise<RawServicePumpResult> {
3424
3782
  try {
3425
3783
  if (record.binding.state === 'active') {
3784
+ const sessionOwner = await this.sessionOwnerOf(
3785
+ ingress.sourceRoutingId,
3786
+ ingress.sourceNodeGeneration ?? this.peerGeneration(ingress.sourceRoutingId)
3787
+ );
3426
3788
  const actor = this.validateActorFence(record.actor);
3427
- const previous = this.registry.binding(actor.ref);
3428
- const sourceGeneration =
3429
- ingress.sourceNodeGeneration ?? this.peerGeneration(ingress.sourceRoutingId);
3430
3789
  const binding = sessionBindingFromWire(
3431
3790
  actor.ref,
3432
3791
  record.sessionRid,
3433
- ingress.sourceRoutingId,
3792
+ sessionOwner.nodeRid,
3434
3793
  record.binding.generation,
3435
3794
  actor.membershipEpoch,
3436
- sourceGeneration,
3437
- ingress.sourceRoutingId,
3438
- sourceGeneration
3795
+ sessionOwner.nodeGeneration,
3796
+ sessionOwner.ownerId,
3797
+ sessionOwner.leaseGeneration
3439
3798
  );
3440
- if (previous !== undefined && sameSessionIdentity(previous, binding)) {
3441
- // Registry publication is the last step of the first binding owner
3442
- // turn, so an observable same-Session binding already has every
3443
- // Actor route projection installed and can complete idempotently.
3444
- this.replyWire(ingress, record.correlation, RequestResult.Ok, 0, undefined, {
3445
- kind: 'streamBind',
3446
- bindingGeneration: previous.bindingGeneration,
3447
- authorityOwnerGeneration: actor.authorityOwnerGeneration
3448
- });
3449
- return 'infrastructure';
3450
- }
3799
+ let completedBinding = binding;
3451
3800
  const accepted = this.enqueueActorBindingControl(
3452
3801
  ingress,
3453
3802
  binding,
3454
3803
  record.actor,
3455
3804
  (terminalResult, failureCode) => {
3456
- let completedBinding = binding;
3457
3805
  let replacedBinding: ServiceSessionBinding | undefined;
3458
3806
  if (terminalResult === RequestResult.Ok) {
3459
3807
  // This callback runs inside the Actor binding control turn,
3460
- // after the Host has installed its Actor-to-session projection.
3808
+ // before the Host copies the accepted Actor-to-session projection.
3461
3809
  // Publish the authority-owned registry view in that same turn
3462
3810
  // before making the terminal reply observable.
3463
3811
  const turnCurrent = this.registry.binding(actor.ref);
3464
- if (turnCurrent !== undefined && sameSessionIdentity(turnCurrent, binding)) {
3465
- completedBinding = turnCurrent;
3466
- } else {
3467
- replacedBinding = turnCurrent;
3468
- this.registry.installSessionBinding(binding);
3812
+ try {
3813
+ completedBinding = this.registry.installSessionBinding(binding);
3814
+ if (turnCurrent !== completedBinding) replacedBinding = turnCurrent;
3815
+ } catch (error) {
3816
+ const result = failure(error);
3817
+ this.replyWire(
3818
+ ingress,
3819
+ record.correlation,
3820
+ result.terminalResult,
3821
+ result.failureCode
3822
+ );
3823
+ return false;
3469
3824
  }
3470
3825
  }
3471
3826
  this.replyWire(
@@ -3496,7 +3851,8 @@ export class ServiceStatefulRuntime {
3496
3851
  );
3497
3852
  }
3498
3853
  return true;
3499
- }
3854
+ },
3855
+ () => completedBinding
3500
3856
  );
3501
3857
  if (!accepted) {
3502
3858
  const result = failure(
@@ -3517,6 +3873,36 @@ export class ServiceStatefulRuntime {
3517
3873
  return 'infrastructure';
3518
3874
  }
3519
3875
 
3876
+ private async handleBoundSessionClose(
3877
+ ingress: RawServiceIngressRecord,
3878
+ record: Extract<ServiceStatefulWireRecord, { readonly kind: 'boundSessionClose' }>
3879
+ ): Promise<RawServicePumpResult> {
3880
+ if (ingress.requestSequence === undefined) return 'protocolError';
3881
+ try {
3882
+ const delivery = this.sessionDeliveries.get(actorKey(record.actor));
3883
+ const sessionOwner = this.localSessionOwner();
3884
+ if (
3885
+ sessionOwner === undefined ||
3886
+ !sessionOwnerFenceMatches(sessionOwner, record.session) ||
3887
+ delivery === undefined ||
3888
+ delivery.binding.actor.generation !== record.actor.generation ||
3889
+ !exactRetiredDelivery(delivery.binding, record.session) ||
3890
+ delivery.close === undefined
3891
+ ) {
3892
+ throw createInternalFrameworkException(
3893
+ ZLinkFrameworkInternalErrorKind.RouteNotConnected,
3894
+ 'The requested session binding is no longer current.'
3895
+ );
3896
+ }
3897
+ await delivery.close();
3898
+ this.replyWire(ingress, record.correlation, RequestResult.Ok, 0);
3899
+ } catch (error) {
3900
+ const result = failure(error);
3901
+ this.replyWire(ingress, record.correlation, result.terminalResult, result.failureCode);
3902
+ }
3903
+ return 'infrastructure';
3904
+ }
3905
+
3520
3906
  /**
3521
3907
  * Handles the replacement notice at the retired session owner. This path
3522
3908
  * intentionally never calls validateActorFence(): the notice is authorized
@@ -3539,12 +3925,25 @@ export class ServiceStatefulRuntime {
3539
3925
  // session that belongs to the new lifecycle.
3540
3926
  return 'infrastructure';
3541
3927
  }
3542
- if (
3543
- record.retiredSession.sessionOwnerNodeRid !== this.nodeRid ||
3544
- record.retiredSession.sessionOwnerNodeGeneration !== this.nodeGeneration ||
3545
- record.retiredSession.sessionOwnerId !== this.nodeRid ||
3546
- record.retiredSession.sessionOwnerLeaseGeneration !== this.nodeGeneration
3547
- ) {
3928
+ const sessionOwner = this.localSessionOwner();
3929
+ if (sessionOwner === undefined) {
3930
+ traceSessionBindingControlDrop(
3931
+ this.dispatchErrors?.flow,
3932
+ ingress.sourceRoutingId,
3933
+ authority.actor.actorId,
3934
+ 'boundSessionReplaced',
3935
+ 'location_unavailable'
3936
+ );
3937
+ return 'infrastructure';
3938
+ }
3939
+ if (!sessionOwnerFenceMatches(sessionOwner, record.retiredSession)) {
3940
+ traceSessionBindingControlDrop(
3941
+ this.dispatchErrors?.flow,
3942
+ ingress.sourceRoutingId,
3943
+ authority.actor.actorId,
3944
+ 'boundSessionReplaced',
3945
+ 'stale_target'
3946
+ );
3548
3947
  return 'infrastructure';
3549
3948
  }
3550
3949
  const actor = authority.actor;
@@ -3571,6 +3970,9 @@ export class ServiceStatefulRuntime {
3571
3970
  : undefined;
3572
3971
  if (delivery === undefined) return 'infrastructure';
3573
3972
  this.appliedReplacementNotices.add(key);
3973
+ if (delivery === current) {
3974
+ this.sessionDeliveries.delete(deliveryKey);
3975
+ }
3574
3976
  delivery.onBindingReplaced?.(actor.actorId, record.retiredSession, actor);
3575
3977
  if (retired !== undefined) {
3576
3978
  this.retiredSessionDeliveries.delete(sessionDeliveryKey(retired.binding));
@@ -3582,14 +3984,20 @@ export class ServiceStatefulRuntime {
3582
3984
  previous: ServiceSessionBinding,
3583
3985
  actorAuthority: ServiceBoundSessionActorAuthority
3584
3986
  ): void {
3585
- const ownerGeneration =
3586
- previous.sessionOwnerNodeGeneration ?? this.tryPeerGeneration(previous.sessionOwnerNodeRid);
3587
- if (ownerGeneration === undefined) return;
3987
+ // Command 51 carries the retired binding's stored identity unchanged; a
3988
+ // binding without one has nothing to notify.
3989
+ if (
3990
+ previous.sessionOwnerNodeGeneration === undefined ||
3991
+ previous.sessionOwnerId === undefined ||
3992
+ previous.sessionOwnerLeaseGeneration === undefined
3993
+ ) {
3994
+ return;
3995
+ }
3588
3996
  const header = encodeBoundSessionReplacedHeader(actorAuthority, {
3589
3997
  sessionOwnerNodeRid: previous.sessionOwnerNodeRid,
3590
- sessionOwnerNodeGeneration: ownerGeneration,
3591
- sessionOwnerId: previous.sessionOwnerId ?? previous.sessionOwnerNodeRid,
3592
- sessionOwnerLeaseGeneration: previous.sessionOwnerLeaseGeneration ?? ownerGeneration,
3998
+ sessionOwnerNodeGeneration: previous.sessionOwnerNodeGeneration,
3999
+ sessionOwnerId: previous.sessionOwnerId,
4000
+ sessionOwnerLeaseGeneration: previous.sessionOwnerLeaseGeneration,
3593
4001
  sessionRid: previous.sessionRid,
3594
4002
  retiredBindingGeneration: previous.bindingGeneration
3595
4003
  });
@@ -3612,11 +4020,9 @@ export class ServiceStatefulRuntime {
3612
4020
  current === undefined ||
3613
4021
  sourceGeneration === undefined ||
3614
4022
  current.bindingGeneration !== record.binding.retiredGeneration ||
3615
- current.sessionRid !== record.sessionRid ||
4023
+ !routingIdsEqual(current.sessionRid, record.sessionRid) ||
3616
4024
  current.sessionOwnerNodeRid !== ingress.sourceRoutingId ||
3617
- current.sessionOwnerNodeGeneration !== sourceGeneration ||
3618
- current.sessionOwnerId !== ingress.sourceRoutingId ||
3619
- current.sessionOwnerLeaseGeneration !== sourceGeneration
4025
+ current.sessionOwnerNodeGeneration !== sourceGeneration
3620
4026
  ) {
3621
4027
  this.replyWire(ingress, record.correlation, RequestResult.Ok, 0);
3622
4028
  return;
@@ -3781,6 +4187,37 @@ export class ServiceStatefulRuntime {
3781
4187
  });
3782
4188
  }
3783
4189
 
4190
+ private prepareRequestSource(
4191
+ ingress: RawServiceIngressRecord,
4192
+ stateful: ServiceStatefulMailboxData,
4193
+ signal: AbortSignal
4194
+ ): boolean | Promise<boolean> {
4195
+ const reject = (error: unknown): false => {
4196
+ if (signal.aborted) return false;
4197
+ if (!(error instanceof ZLinkFrameworkException)) throw error;
4198
+ const result = failure(error);
4199
+ stateful.reply?.(result.terminalResult, result.failureCode);
4200
+ return false;
4201
+ };
4202
+ if (signal.aborted) return false;
4203
+ try {
4204
+ const pending = this.acceptRequestSource(ingress, signal);
4205
+ return pending === undefined
4206
+ ? true
4207
+ : awaitWithAbort(pending, signal).then(() => true, reject);
4208
+ } catch (error) {
4209
+ return reject(error);
4210
+ }
4211
+ }
4212
+
4213
+ prepareDispatch(record: ServiceMailboxRecord, signal: AbortSignal): boolean | Promise<boolean> {
4214
+ return this.prepareRequestSource(
4215
+ record.ingress!,
4216
+ record.stateful as ServiceStatefulMailboxData,
4217
+ signal
4218
+ );
4219
+ }
4220
+
3784
4221
  private enqueueApplicationFrame(
3785
4222
  ingress: RawServiceIngressRecord,
3786
4223
  owner: string,
@@ -3812,6 +4249,10 @@ export class ServiceStatefulRuntime {
3812
4249
  domain,
3813
4250
  parts: [ingress.parts[0]!, payloadFrame],
3814
4251
  sourceRoutingId: ingress.sourceRoutingId,
4252
+ ingress,
4253
+ get requestSource() {
4254
+ return ingress.requestSource;
4255
+ },
3815
4256
  sourceRoute: ingress.sourceRoute,
3816
4257
  ...(ingress.reply === undefined ? {} : { reply: ingress.reply }),
3817
4258
  ...(ingress.requestSequence === undefined
@@ -3819,9 +4260,21 @@ export class ServiceStatefulRuntime {
3819
4260
  : { requestSequence: ingress.requestSequence }),
3820
4261
  ...(stateful.correlation === undefined ? {} : { correlation: stateful.correlation }),
3821
4262
  stateful,
4263
+ sourcePreparationOwner:
4264
+ domain === 'application' &&
4265
+ this.requestSourceAcceptance !== undefined &&
4266
+ (stateful.receiveKind === ReceiveKind.SpotSend ||
4267
+ stateful.receiveKind === ReceiveKind.SpotRequest ||
4268
+ stateful.receiveKind === ReceiveKind.ActorSend ||
4269
+ stateful.receiveKind === ReceiveKind.ActorRequest)
4270
+ ? this
4271
+ : undefined,
3822
4272
  applicationJob
3823
4273
  });
3824
- if (accepted) return domain;
4274
+ if (accepted) {
4275
+ ingress.onAdmitted?.();
4276
+ return domain;
4277
+ }
3825
4278
  applicationJob.close();
3826
4279
  if (stateful.reply !== undefined) {
3827
4280
  const result = failure(
@@ -3957,10 +4410,11 @@ export class ServiceStatefulRuntime {
3957
4410
  ingress: RawServiceIngressRecord,
3958
4411
  binding: ServiceSessionBinding,
3959
4412
  actorAuthority: ServiceBoundSessionActorAuthority,
3960
- reply: NonNullable<ServiceStatefulMailboxData['reply']>
4413
+ reply: NonNullable<ServiceStatefulMailboxData['reply']>,
4414
+ acceptedBinding: () => ServiceSessionBinding
3961
4415
  ): boolean {
3962
4416
  const actor = binding.actor;
3963
- const sessionOwner = requireSessionOwnerIdentity(binding);
4417
+ requireSessionOwnerIdentity(binding);
3964
4418
  const header = Buffer.from([
3965
4419
  SERVICE_WIRE_MAGIC[0],
3966
4420
  SERVICE_WIRE_MAGIC[1],
@@ -3985,12 +4439,27 @@ export class ServiceStatefulRuntime {
3985
4439
  actorNodeGeneration: actorAuthority.targetNodeGeneration,
3986
4440
  authorityOwnerGeneration: actorAuthority.authorityOwnerGeneration,
3987
4441
  ownerLeaseGeneration: actorAuthority.ownerLeaseGeneration,
3988
- bindingGeneration: binding.bindingGeneration,
3989
- sessionNodeRid: binding.sessionOwnerNodeRid as never,
3990
- sessionRid: binding.sessionRid as never,
3991
- sessionOwnerNodeGeneration: sessionOwner.nodeGeneration,
3992
- sessionOwnerId: sessionOwner.ownerId,
3993
- sessionOwnerLeaseGeneration: sessionOwner.leaseGeneration
4442
+ // The Host reads only Session identity fields before `reply` installs the
4443
+ // binding; installation keeps that identity. Generation and owner fields
4444
+ // are read after `reply`, so they always come from the accepted binding.
4445
+ get bindingGeneration() {
4446
+ return acceptedBinding().bindingGeneration;
4447
+ },
4448
+ get sessionNodeRid() {
4449
+ return acceptedBinding().sessionOwnerNodeRid as never;
4450
+ },
4451
+ get sessionRid() {
4452
+ return acceptedBinding().sessionRid as never;
4453
+ },
4454
+ get sessionOwnerNodeGeneration() {
4455
+ return acceptedBinding().sessionOwnerNodeGeneration!;
4456
+ },
4457
+ get sessionOwnerId() {
4458
+ return acceptedBinding().sessionOwnerId!;
4459
+ },
4460
+ get sessionOwnerLeaseGeneration() {
4461
+ return acceptedBinding().sessionOwnerLeaseGeneration!;
4462
+ }
3994
4463
  },
3995
4464
  reply
3996
4465
  } satisfies ServiceStatefulMailboxData,
@@ -4212,64 +4681,15 @@ export class ServiceStatefulRuntime {
4212
4681
  operationKind: 'userSpotCreate' | 'userSpotClose' | 'actorCreate' | 'streamBind' | 'actorJoin',
4213
4682
  deadlineMs: number
4214
4683
  ): Promise<readonly Buffer[]> {
4215
- const stop = new AbortController();
4216
- const unobserve = this.raw.observePeerConnectionIntentRemoved((nodeRoutingId) => {
4217
- if (nodeRoutingId === targetNodeRid) {
4218
- stop.abort(
4219
- createInternalFrameworkException(
4220
- ZLinkFrameworkInternalErrorKind.RouteNotConnected,
4221
- `${operationKind} target node lifecycle ended.`,
4222
- true
4223
- )
4224
- );
4225
- }
4226
- });
4227
- let wasAdmitted = false;
4228
- try {
4229
- // Only durable lifecycle operations replay these frozen frames. Received
4230
- // envelopes leave this loop before decoding, including failure terminals.
4231
- for (;;) {
4232
- this.requireOpen();
4233
- stop.signal.throwIfAborted();
4234
- const remainingMs = Math.ceil(deadlineMs - performance.now());
4235
- if (remainingMs <= 0) throw durableOperationExhausted(operationKind, wasAdmitted);
4236
- try {
4237
- return await awaitWithAbort(
4238
- targetNodeRid === this.nodeRid
4239
- ? this.requestLocalInfrastructure(parts[0]!, correlation, remainingMs)
4240
- : this.raw.requestService(targetNodeRid, parts, remainingMs),
4241
- stop.signal
4242
- );
4243
- } catch (error) {
4244
- stop.signal.throwIfAborted();
4245
- if (!durableRequestCanReplay(error)) throw error;
4246
- if (durableRequestWasAdmitted(error)) wasAdmitted = true;
4247
- const retryDelayMs = Math.min(
4248
- STATEFUL_OPERATION_RETRY_TICK_MS,
4249
- deadlineMs - performance.now()
4250
- );
4251
- if (retryDelayMs <= 0) {
4252
- throw durableOperationExhausted(operationKind, wasAdmitted, error);
4253
- }
4254
- let timer: ReturnType<typeof setTimeout> | undefined;
4255
- try {
4256
- await awaitWithAbort(
4257
- new Promise<void>((resolve) => {
4258
- timer = setTimeout(resolve, retryDelayMs);
4259
- }),
4260
- stop.signal
4261
- );
4262
- } finally {
4263
- if (timer !== undefined) clearTimeout(timer);
4264
- }
4265
- }
4266
- }
4267
- } catch (error) {
4268
- stop.signal.throwIfAborted();
4269
- throw error;
4270
- } finally {
4271
- unobserve();
4684
+ this.requireOpen();
4685
+ if (targetNodeRid === this.nodeRid) {
4686
+ return this.requestLocalInfrastructure(
4687
+ parts[0]!,
4688
+ correlation,
4689
+ Math.ceil(deadlineMs - performance.now())
4690
+ );
4272
4691
  }
4692
+ return requestDurableService(this.raw, targetNodeRid, parts, deadlineMs, operationKind);
4273
4693
  }
4274
4694
 
4275
4695
  private requestLocalInfrastructure(
@@ -4393,7 +4813,8 @@ export class ServiceStatefulRuntime {
4393
4813
  | 'streamBind'
4394
4814
  | 'streamUnbind'
4395
4815
  | 'instanceSpotRequest',
4396
- actor?: ServiceActorRef
4816
+ actor?: ServiceActorRef,
4817
+ onAdmitted?: () => void
4397
4818
  ): void {
4398
4819
  if (targetNodeRid === this.nodeRid) {
4399
4820
  const deadlineUnixMs = BigInt(Date.now() + Math.max(0, timeoutMs));
@@ -4411,6 +4832,7 @@ export class ServiceStatefulRuntime {
4411
4832
  flags: parts[0]![SERVICE_WIRE_FLAGS_OFFSET]!,
4412
4833
  sourceRoutingId: this.nodeRid,
4413
4834
  requestSequence: pending.id,
4835
+ onAdmitted,
4414
4836
  parts,
4415
4837
  applicationJobOwner
4416
4838
  };
@@ -4429,7 +4851,7 @@ export class ServiceStatefulRuntime {
4429
4851
  })().catch((error) => this.operations.reply(pending.id, failure(error)));
4430
4852
  return;
4431
4853
  }
4432
- void this.raw.requestService(targetNodeRid, parts, timeoutMs).then(
4854
+ void this.raw.requestService(targetNodeRid, parts, timeoutMs, onAdmitted).then(
4433
4855
  (reply) => this.completeRemoteReply(pending, targetNodeRid, operationKind, actor, reply),
4434
4856
  (error) => this.operations.fail(pending.id, error)
4435
4857
  );
@@ -4611,6 +5033,15 @@ export class ServiceStatefulRuntime {
4611
5033
  operationKind: OperationKind.ActorRequest,
4612
5034
  correlation: decoded.correlation,
4613
5035
  targetActor: current.ref,
5036
+ messageFollowOrigin: {
5037
+ get requestSource() {
5038
+ return ingress.requestSource;
5039
+ },
5040
+ target: decoded.target,
5041
+ sourceNodeRid: ingress.sourceRoutingId,
5042
+ originalOperation: decoded.operation,
5043
+ originalReplyRouteId: decoded.correlation ?? 0n
5044
+ },
4614
5045
  ...(decoded.sourceActor === undefined
4615
5046
  ? {}
4616
5047
  : { sourceActor: { ...decoded.sourceActor, nodeRid: ingress.sourceRoutingId } }),
@@ -4706,13 +5137,17 @@ export class ServiceStatefulRuntime {
4706
5137
  try {
4707
5138
  const current = this.validateActorFence(decoded.actor);
4708
5139
  if (decoded.binding.state === 'active') {
5140
+ const sessionOwner = await this.sessionOwnerOf(this.nodeRid, this.nodeGeneration);
4709
5141
  this.registry.installSessionBinding(
4710
5142
  sessionBindingFromWire(
4711
5143
  current.ref,
4712
5144
  decoded.sessionRid,
4713
- this.nodeRid,
5145
+ sessionOwner.nodeRid,
4714
5146
  decoded.binding.generation,
4715
- current.membershipEpoch
5147
+ current.membershipEpoch,
5148
+ sessionOwner.nodeGeneration,
5149
+ sessionOwner.ownerId,
5150
+ sessionOwner.leaseGeneration
4716
5151
  )
4717
5152
  );
4718
5153
  } else {
@@ -4761,6 +5196,7 @@ export class ServiceStatefulRuntime {
4761
5196
  | 'actorJoin'
4762
5197
  | 'streamBind'
4763
5198
  | 'streamUnbind'
5199
+ | 'boundSessionClose'
4764
5200
  | 'instanceSpotRequest',
4765
5201
  actor: ServiceActorRef | undefined,
4766
5202
  reply: readonly Buffer[]
@@ -5387,6 +5823,25 @@ function isEntrySpotFence(fence: ServiceSpotRouteFence): boolean {
5387
5823
  );
5388
5824
  }
5389
5825
 
5826
+ function instanceActivationOutcome(error: unknown): string {
5827
+ if (isAbortError(error)) return 'shutdown';
5828
+ if (error instanceof ZLinkFrameworkException) {
5829
+ switch (error.kind) {
5830
+ case ZLinkFrameworkErrorKind.DeadlineExceeded:
5831
+ return 'timed_out';
5832
+ case ZLinkFrameworkErrorKind.ShuttingDown:
5833
+ return 'shutdown';
5834
+ case ZLinkFrameworkErrorKind.AlreadyExists:
5835
+ return 'conflict';
5836
+ case ZLinkFrameworkErrorKind.InvalidOperation:
5837
+ return 'fenced';
5838
+ case ZLinkFrameworkErrorKind.Unavailable:
5839
+ return 'store_failure';
5840
+ }
5841
+ }
5842
+ return 'rejected';
5843
+ }
5844
+
5390
5845
  function operationDeadline(deadlineUnixMs: bigint): {
5391
5846
  readonly signal: AbortSignal;
5392
5847
  close(): void;
@@ -5414,37 +5869,6 @@ function operationReplayExpired(replayExpiresAtMs: number): boolean {
5414
5869
  return performance.now() > replayExpiresAtMs;
5415
5870
  }
5416
5871
 
5417
- function durableRequestWasAdmitted(error: unknown): boolean {
5418
- // Request NotConnected includes Core handover of an admitted request. The
5419
- // submit/request phase, rather than the result number, owns this distinction.
5420
- return isZLinkBackendResultError(error) && error.operation === 'request';
5421
- }
5422
-
5423
- function durableOperationExhausted(
5424
- operationKind: 'userSpotCreate' | 'userSpotClose' | 'actorCreate' | 'streamBind' | 'actorJoin',
5425
- wasAdmitted: boolean,
5426
- cause?: unknown
5427
- ): ZLinkFrameworkException {
5428
- return createInternalFrameworkException(
5429
- wasAdmitted
5430
- ? ZLinkFrameworkInternalErrorKind.DeadlineExceeded
5431
- : ZLinkFrameworkInternalErrorKind.RouteNotConnected,
5432
- `${operationKind} exhausted its end-to-end deadline.`,
5433
- true,
5434
- cause
5435
- );
5436
- }
5437
-
5438
- function durableRequestCanReplay(error: unknown): boolean {
5439
- if (!isZLinkBackendResultError(error)) return false;
5440
- return error.operation === 'request'
5441
- ? error.result === RequestResult.NotConnected || error.result === RequestResult.TimedOut
5442
- : error.result === SubmitResult.NotConnected ||
5443
- error.result === SubmitResult.NotFound ||
5444
- error.result === SubmitResult.NotAdmitted ||
5445
- error.result === SubmitResult.Backpressured;
5446
- }
5447
-
5448
5872
  function remainingDeadlineMs(deadlineUnixMs: bigint): number {
5449
5873
  const remainingMs = deadlineUnixMs - BigInt(Date.now());
5450
5874
  if (remainingMs <= 0n) {
@@ -5482,6 +5906,13 @@ function actorLocation(actor: ServiceActorState): ActorLocation {
5482
5906
  };
5483
5907
  }
5484
5908
 
5909
+ function requestSourceUnavailable(nodeRid: string): ZLinkFrameworkException {
5910
+ return new ZLinkFrameworkException(
5911
+ ZLinkFrameworkErrorKind.Unavailable,
5912
+ `Request source '${nodeRid}' has no live MeshNode descriptor.`
5913
+ );
5914
+ }
5915
+
5485
5916
  function activationRefused(): ZLinkFrameworkException {
5486
5917
  return new ZLinkFrameworkException(
5487
5918
  ZLinkFrameworkErrorKind.Unavailable,
@@ -5499,14 +5930,14 @@ function failure(error: unknown): ServiceStatefulResult {
5499
5930
  return internalFrameworkWireReply(ZLinkFrameworkInternalErrorKind.RequestFailed);
5500
5931
  }
5501
5932
 
5502
- function actorKey(actor: ServiceActorRef): string {
5933
+ function actorKey(actor: Pick<ServiceActorRef, 'actorId' | 'generation'>): string {
5503
5934
  return `${actor.actorId}\0${actor.generation}`;
5504
5935
  }
5505
5936
 
5506
5937
  function sameSessionBinding(left: ServiceSessionBinding, right: ServiceSessionBinding): boolean {
5507
5938
  return (
5508
5939
  sameActorRef(left.actor, right.actor) &&
5509
- left.sessionRid === right.sessionRid &&
5940
+ routingIdsEqual(left.sessionRid, right.sessionRid) &&
5510
5941
  sameSessionOwnerIdentity(left, right) &&
5511
5942
  left.bindingGeneration === right.bindingGeneration
5512
5943
  );
@@ -5515,7 +5946,7 @@ function sameSessionBinding(left: ServiceSessionBinding, right: ServiceSessionBi
5515
5946
  function sameSessionIdentity(left: ServiceSessionBinding, right: ServiceSessionBinding): boolean {
5516
5947
  return (
5517
5948
  sameActorRef(left.actor, right.actor) &&
5518
- left.sessionRid === right.sessionRid &&
5949
+ routingIdsEqual(left.sessionRid, right.sessionRid) &&
5519
5950
  sameSessionOwnerIdentity(left, right)
5520
5951
  );
5521
5952
  }
@@ -5538,9 +5969,7 @@ function sameSessionOwnerIdentity(
5538
5969
  ): boolean {
5539
5970
  return (
5540
5971
  left.sessionOwnerNodeRid === right.sessionOwnerNodeRid &&
5541
- left.sessionOwnerNodeGeneration === right.sessionOwnerNodeGeneration &&
5542
- left.sessionOwnerId === right.sessionOwnerId &&
5543
- left.sessionOwnerLeaseGeneration === right.sessionOwnerLeaseGeneration
5972
+ left.sessionOwnerNodeGeneration === right.sessionOwnerNodeGeneration
5544
5973
  );
5545
5974
  }
5546
5975
 
@@ -5578,7 +6007,7 @@ function sessionDeliveryKey(
5578
6007
  >
5579
6008
  ): string {
5580
6009
  return (
5581
- `${actorKey(binding.actor)}\0${binding.sessionOwnerNodeRid}\0${binding.sessionRid}` +
6010
+ `${actorKey(binding.actor)}\0${binding.sessionOwnerNodeRid}\0${encodeRoutingIdStorageHex(binding.sessionRid)}` +
5582
6011
  `\0${binding.sessionOwnerNodeGeneration ?? ''}` +
5583
6012
  `\0${binding.sessionOwnerId ?? ''}` +
5584
6013
  `\0${binding.sessionOwnerLeaseGeneration ?? ''}` +
@@ -5591,8 +6020,8 @@ function exactRetiredDelivery(
5591
6020
  retired: ServiceRetiredBoundSessionRouteFence
5592
6021
  ): boolean {
5593
6022
  return (
5594
- binding.sessionRid === retired.sessionRid &&
5595
- binding.sessionOwnerNodeRid === retired.sessionOwnerNodeRid &&
6023
+ routingIdsEqual(binding.sessionRid, retired.sessionRid) &&
6024
+ routingIdsEqual(binding.sessionOwnerNodeRid, retired.sessionOwnerNodeRid) &&
5596
6025
  binding.sessionOwnerNodeGeneration === retired.sessionOwnerNodeGeneration &&
5597
6026
  binding.sessionOwnerId === retired.sessionOwnerId &&
5598
6027
  binding.sessionOwnerLeaseGeneration === retired.sessionOwnerLeaseGeneration &&
@@ -5611,7 +6040,7 @@ function replacementNoticeKey(
5611
6040
  `\0${record.retiredSession.sessionOwnerNodeGeneration}` +
5612
6041
  `\0${record.retiredSession.sessionOwnerId}` +
5613
6042
  `\0${record.retiredSession.sessionOwnerLeaseGeneration}` +
5614
- `\0${record.retiredSession.sessionRid}` +
6043
+ `\0${encodeRoutingIdStorageHex(record.retiredSession.sessionRid)}` +
5615
6044
  `\0${record.retiredSession.retiredBindingGeneration}`
5616
6045
  );
5617
6046
  }
@@ -5937,3 +6366,31 @@ export function statefulMailboxData(
5937
6366
  ): ServiceStatefulMailboxData | undefined {
5938
6367
  return record.stateful as ServiceStatefulMailboxData | undefined;
5939
6368
  }
6369
+
6370
+ function acceptsStatefulIngress(command: number): boolean {
6371
+ // These frozen relocation controls overlap the legacy stateful command
6372
+ // number range (30-34). They are bare infrastructure records and must
6373
+ // fall through to RawServiceMeshRuntime's relocation ingress instead of
6374
+ // being decoded as stateful actor/spot work.
6375
+ if (
6376
+ command === M6bServiceWireCommand.relocationReady ||
6377
+ command === M6bServiceWireCommand.relocationData ||
6378
+ command === M6bServiceWireCommand.replyRelay ||
6379
+ command === M6bServiceWireCommand.relocationCutover
6380
+ ) {
6381
+ return false;
6382
+ }
6383
+ if (
6384
+ command < M6bServiceWireCommand.spotSend ||
6385
+ (command > M6bServiceWireCommand.instanceSpot &&
6386
+ command !== M6bServiceWireCommand.userSpotCreate &&
6387
+ command !== M6bServiceWireCommand.userSpotClose &&
6388
+ command !== M6bServiceWireCommand.actorCreate &&
6389
+ command !== M6bServiceWireCommand.messageFollow &&
6390
+ command !== M6bServiceWireCommand.boundSessionReplaced &&
6391
+ command !== M6bServiceWireCommand.boundSessionClose)
6392
+ ) {
6393
+ return false;
6394
+ }
6395
+ return true;
6396
+ }