@zlink-systems/framework 0.30.1 → 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 (340) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Common/ZLinkEncodedPayload.d.ts +6 -0
  3. package/dist/contracts/Common/ZLinkEncodedPayload.js +18 -3
  4. package/dist/contracts/Common/encoded-payload-storage.d.ts +1 -1
  5. package/dist/contracts/Common/encoded-payload-storage.js +5 -2
  6. package/dist/contracts/Configuration/Builders.d.ts +1 -1
  7. package/dist/contracts/Configuration/RegistrationBuilderPolicy.d.ts +2 -1
  8. package/dist/contracts/Configuration/RegistrationBuilderPolicy.js +5 -1
  9. package/dist/contracts/Configuration/RegistrationBuilders.js +2 -2
  10. package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -0
  11. package/dist/contracts/Dispatch/ZLinkDispatchOptions.d.ts +2 -2
  12. package/dist/contracts/Locations/Rows.d.ts +2 -0
  13. package/dist/contracts/Spots/Contracts.d.ts +4 -0
  14. package/dist/contracts/Spots/ZLinkSpot.d.ts +3 -3
  15. package/dist/runtime/abort.d.ts +5 -0
  16. package/dist/runtime/abort.js +10 -0
  17. package/dist/runtime/actors/actor-authority-publication.js +3 -2
  18. package/dist/runtime/actors/actor-client.d.ts +0 -1
  19. package/dist/runtime/actors/actor-client.js +38 -45
  20. package/dist/runtime/actors/actor-context.d.ts +2 -0
  21. package/dist/runtime/actors/actor-context.js +46 -9
  22. package/dist/runtime/actors/actor-handoff.d.ts +7 -3
  23. package/dist/runtime/actors/actor-handoff.js +212 -88
  24. package/dist/runtime/actors/actor-join-relocation.d.ts +2 -0
  25. package/dist/runtime/actors/actor-local-native-join.js +4 -0
  26. package/dist/runtime/actors/actor-mailbox.js +27 -1
  27. package/dist/runtime/actors/actor-remote-joiner.js +3 -1
  28. package/dist/runtime/actors/actor-reply.d.ts +4 -0
  29. package/dist/runtime/actors/actor-reply.js +29 -0
  30. package/dist/runtime/actors/actor-runtime-contracts.d.ts +2 -0
  31. package/dist/runtime/actors/actor-runtime-state.js +2 -20
  32. package/dist/runtime/actors/actor-source-transfer.d.ts +1 -17
  33. package/dist/runtime/actors/index.d.ts +6 -2
  34. package/dist/runtime/actors/index.js +37 -21
  35. package/dist/runtime/actors/session-disconnected-wire.d.ts +8 -0
  36. package/dist/runtime/actors/session-disconnected-wire.js +48 -0
  37. package/dist/runtime/admission.d.ts +9 -1
  38. package/dist/runtime/admission.js +55 -32
  39. package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +13 -0
  40. package/dist/runtime/application-jobs/application-job-queue-scope.js +27 -1
  41. package/dist/runtime/application-jobs/contracts.d.ts +2 -0
  42. package/dist/runtime/backend/contracts/index.d.ts +19 -3
  43. package/dist/runtime/backend/mesh-actor-session-node-adapter.js +2 -2
  44. package/dist/runtime/backend/mesh-completion-table.js +5 -0
  45. package/dist/runtime/backend/mesh-dispatch-pump.d.ts +26 -2
  46. package/dist/runtime/backend/mesh-dispatch-pump.js +193 -107
  47. package/dist/runtime/backend/node/node-raw-binding-port.js +90 -58
  48. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +33 -8
  49. package/dist/runtime/backend/node/node-raw-mesh-backend.js +142 -143
  50. package/dist/runtime/backend/node/node-socket-backend-adapter.d.ts +1 -1
  51. package/dist/runtime/backend/node/node-socket-backend-adapter.js +5 -5
  52. package/dist/runtime/channels/channel-clients.d.ts +1 -0
  53. package/dist/runtime/channels/channel-clients.js +20 -5
  54. package/dist/runtime/channels/channel-dispatch-pipeline.d.ts +22 -3
  55. package/dist/runtime/channels/channel-dispatch-pipeline.js +103 -57
  56. package/dist/runtime/channels/channel-dispatchers.d.ts +3 -2
  57. package/dist/runtime/channels/channel-dispatchers.js +44 -37
  58. package/dist/runtime/channels/channel-envelope.d.ts +1 -0
  59. package/dist/runtime/channels/channel-envelope.js +74 -31
  60. package/dist/runtime/channels/channel-runtime-lifecycle.d.ts +4 -3
  61. package/dist/runtime/channels/channel-runtime-lifecycle.js +19 -24
  62. package/dist/runtime/channels/channel-runtime-manager.d.ts +3 -2
  63. package/dist/runtime/channels/channel-runtime-manager.js +4 -4
  64. package/dist/runtime/channels/channel-transports.d.ts +0 -1
  65. package/dist/runtime/channels/channel-transports.js +35 -33
  66. package/dist/runtime/channels/client-server-location-runtime.js +5 -4
  67. package/dist/runtime/channels/dispatch-error-reporter.d.ts +2 -1
  68. package/dist/runtime/channels/dispatch-error-reporter.js +2 -1
  69. package/dist/runtime/channels/fanout-location-runtime.d.ts +1 -1
  70. package/dist/runtime/channels/fanout-location-runtime.js +3 -3
  71. package/dist/runtime/diagnostics/flow-context.js +19 -7
  72. package/dist/runtime/diagnostics/index.js +4 -9
  73. package/dist/runtime/diagnostics/message-flow.d.ts +8 -0
  74. package/dist/runtime/diagnostics/message-flow.js +35 -5
  75. package/dist/runtime/diagnostics/runtime-metrics.d.ts +2 -2
  76. package/dist/runtime/diagnostics/runtime-metrics.js +10 -8
  77. package/dist/runtime/execution/index.d.ts +4 -2
  78. package/dist/runtime/execution/index.js +33 -10
  79. package/dist/runtime/execution/serial-execution-queue.d.ts +4 -0
  80. package/dist/runtime/execution/serial-execution-queue.js +21 -2
  81. package/dist/runtime/execution/state-lane.d.ts +2 -0
  82. package/dist/runtime/execution/state-lane.js +5 -0
  83. package/dist/runtime/foundation/operation-registry.d.ts +25 -1
  84. package/dist/runtime/foundation/operation-registry.js +86 -18
  85. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +39 -3
  86. package/dist/runtime/foundation/raw-service-mesh-runtime.js +151 -55
  87. package/dist/runtime/foundation/service-liveness-registry.d.ts +12 -1
  88. package/dist/runtime/foundation/service-liveness-registry.js +38 -0
  89. package/dist/runtime/foundation/service-mailbox.d.ts +7 -0
  90. package/dist/runtime/foundation/service-maintenance-runtime.js +1 -6
  91. package/dist/runtime/foundation/service-relocation-object-owner.d.ts +2 -3
  92. package/dist/runtime/foundation/service-relocation-object-owner.js +48 -35
  93. package/dist/runtime/foundation/service-relocation-runtime.js +7 -3
  94. package/dist/runtime/foundation/service-runtime-contracts.d.ts +8 -1
  95. package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +2 -1
  96. package/dist/runtime/foundation/service-stateful-registry.d.ts +5 -4
  97. package/dist/runtime/foundation/service-stateful-registry.js +24 -26
  98. package/dist/runtime/foundation/service-stateful-runtime.d.ts +72 -6
  99. package/dist/runtime/foundation/service-stateful-runtime.js +444 -117
  100. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +14 -1
  101. package/dist/runtime/foundation/service-stateful-wire-codec.js +48 -27
  102. package/dist/runtime/foundation/service-wire-binary-primitives.d.ts +6 -1
  103. package/dist/runtime/foundation/service-wire-binary-primitives.js +27 -11
  104. package/dist/runtime/foundation/service-wire-constants.generated.d.ts +1 -0
  105. package/dist/runtime/foundation/service-wire-constants.generated.js +1 -0
  106. package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +10 -0
  107. package/dist/runtime/foundation/service-wire-m6a-codec.js +166 -53
  108. package/dist/runtime/host/actor-packet-relay.d.ts +6 -4
  109. package/dist/runtime/host/actor-packet-relay.js +59 -72
  110. package/dist/runtime/host/actor-placement-coordinator.js +25 -8
  111. package/dist/runtime/host/actor-transfer-runtime.d.ts +7 -31
  112. package/dist/runtime/host/actor-transfer-runtime.js +85 -331
  113. package/dist/runtime/host/application-job-queue.d.ts +24 -3
  114. package/dist/runtime/host/application-job-queue.js +28 -41
  115. package/dist/runtime/host/index.d.ts +5 -1
  116. package/dist/runtime/host/index.js +171 -97
  117. package/dist/runtime/host/location-runtime-owner.js +2 -6
  118. package/dist/runtime/host/mesh-router-resolver.d.ts +4 -0
  119. package/dist/runtime/host/mesh-router-resolver.js +14 -6
  120. package/dist/runtime/host/remote-bound-session-relay.d.ts +2 -4
  121. package/dist/runtime/host/remote-bound-session-relay.js +71 -190
  122. package/dist/runtime/host/route-mesh-runtime.d.ts +2 -2
  123. package/dist/runtime/host/route-mesh-runtime.js +5 -3
  124. package/dist/runtime/host/runtime-shutdown.d.ts +1 -0
  125. package/dist/runtime/host/runtime-shutdown.js +3 -2
  126. package/dist/runtime/host/service-relocation-host-runtime.d.ts +17 -7
  127. package/dist/runtime/host/service-relocation-host-runtime.js +550 -367
  128. package/dist/runtime/host/spot-runtime-options-factory.js +15 -7
  129. package/dist/runtime/host/user-spot-creation-coordinator.js +33 -84
  130. package/dist/runtime/locations/auto-connect-loop.d.ts +2 -0
  131. package/dist/runtime/locations/auto-connect-loop.js +22 -14
  132. package/dist/runtime/locations/auto-connect-reconciler.js +0 -6
  133. package/dist/runtime/locations/auto-connect-types.d.ts +0 -1
  134. package/dist/runtime/locations/descriptor-publication.d.ts +4 -0
  135. package/dist/runtime/locations/descriptor-publication.js +11 -0
  136. package/dist/runtime/locations/domain-store-contract.d.ts +1 -0
  137. package/dist/runtime/locations/in-memory-authority-store.d.ts +7 -1
  138. package/dist/runtime/locations/in-memory-authority-store.js +56 -19
  139. package/dist/runtime/locations/in-memory-location-store.js +1 -10
  140. package/dist/runtime/locations/index.d.ts +1 -0
  141. package/dist/runtime/locations/index.js +1 -0
  142. package/dist/runtime/locations/lease-tracker.js +1 -6
  143. package/dist/runtime/locations/location-store-repository.d.ts +51 -1
  144. package/dist/runtime/locations/location-store-repository.js +363 -139
  145. package/dist/runtime/locations/relocation-blob.d.ts +18 -1
  146. package/dist/runtime/locations/relocation-blob.js +52 -1
  147. package/dist/runtime/locations/request-source-resolver.d.ts +37 -0
  148. package/dist/runtime/locations/request-source-resolver.js +80 -0
  149. package/dist/runtime/locations/resolvers.js +1 -1
  150. package/dist/runtime/locations/runtime.d.ts +6 -2
  151. package/dist/runtime/locations/runtime.js +37 -30
  152. package/dist/runtime/messaging/payload-codec.d.ts +7 -1
  153. package/dist/runtime/messaging/payload-codec.js +7 -6
  154. package/dist/runtime/messaging/request-result.js +29 -26
  155. package/dist/runtime/protocol/service_wire_codec.generated.d.ts +19 -1
  156. package/dist/runtime/protocol/service_wire_codec.generated.js +85 -0
  157. package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +7 -1
  158. package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +23 -7
  159. package/dist/runtime/spots/formal-remote-actor-admission-registry.js +3 -2
  160. package/dist/runtime/spots/index.d.ts +18 -11
  161. package/dist/runtime/spots/index.js +234 -176
  162. package/dist/runtime/spots/spot-activation-registry.d.ts +4 -2
  163. package/dist/runtime/spots/spot-activation-registry.js +21 -11
  164. package/dist/runtime/spots/spot-activation-state.d.ts +1 -1
  165. package/dist/runtime/spots/spot-activation-state.js +7 -7
  166. package/dist/runtime/spots/spot-activation.d.ts +6 -3
  167. package/dist/runtime/spots/spot-activation.js +123 -44
  168. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +1 -0
  169. package/dist/runtime/spots/spot-actor-admission-coordinator.js +13 -22
  170. package/dist/runtime/spots/spot-actor-membership.d.ts +0 -1
  171. package/dist/runtime/spots/spot-actor-membership.js +0 -8
  172. package/dist/runtime/spots/spot-actor-packet-dispatch.js +33 -8
  173. package/dist/runtime/spots/spot-actor-packet-drain.js +5 -33
  174. package/dist/runtime/spots/spot-entry-activation.d.ts +2 -0
  175. package/dist/runtime/spots/spot-entry-activation.js +14 -9
  176. package/dist/runtime/spots/spot-handler-registry.d.ts +4 -3
  177. package/dist/runtime/spots/spot-handler-registry.js +6 -2
  178. package/dist/runtime/spots/spot-manager-internal-contracts.d.ts +2 -2
  179. package/dist/runtime/spots/spot-manager-public.js +2 -2
  180. package/dist/runtime/spots/spot-node-autoconnect.d.ts +2 -1
  181. package/dist/runtime/spots/spot-node-autoconnect.js +18 -39
  182. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +19 -2
  183. package/dist/runtime/spots/spot-node-runtime-manager.js +115 -50
  184. package/dist/runtime/spots/spot-outbound.js +3 -3
  185. package/dist/runtime/spots/spot-remote-codec.js +5 -1
  186. package/dist/runtime/spots/spot-runtime-ports.d.ts +0 -1
  187. package/dist/runtime/spots/spot-serial-executor.d.ts +10 -0
  188. package/dist/runtime/spots/spot-serial-executor.js +61 -8
  189. package/dist/runtime/spots/spot-serial-turn-executor.js +19 -2
  190. package/dist/runtime/spots/spot-subscription-dispatch.js +1 -1
  191. package/dist/runtime/spots/spot-timer.d.ts +57 -5
  192. package/dist/runtime/spots/spot-timer.js +287 -103
  193. package/dist/runtime/streams/actor-session-binding-registry.d.ts +42 -11
  194. package/dist/runtime/streams/actor-session-binding-registry.js +165 -48
  195. package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +8 -4
  196. package/dist/runtime/streams/bound-actor-relay-sender.d.ts +2 -1
  197. package/dist/runtime/streams/bound-actor-relay-sender.js +15 -8
  198. package/dist/runtime/streams/bound-session-service.d.ts +1 -1
  199. package/dist/runtime/streams/bound-session-service.js +6 -11
  200. package/dist/runtime/streams/index.d.ts +3 -2
  201. package/dist/runtime/streams/index.js +6 -4
  202. package/dist/runtime/streams/managed-stream.js +3 -2
  203. package/dist/runtime/streams/native-fallback-bound-session.js +8 -73
  204. package/dist/runtime/streams/session-actor-coordinator.d.ts +2 -8
  205. package/dist/runtime/streams/session-actor-coordinator.js +15 -32
  206. package/dist/runtime/streams/stream-binding-runtime-ports.d.ts +3 -10
  207. package/dist/runtime/streams/stream-session-runtime.js +1 -4
  208. package/dist/runtime/workers/index.js +1 -1
  209. package/package.json +3 -3
  210. package/src/contracts/Common/ZLinkEncodedPayload.ts +24 -4
  211. package/src/contracts/Common/encoded-payload-storage.ts +6 -3
  212. package/src/contracts/Configuration/Builders.ts +4 -1
  213. package/src/contracts/Configuration/RegistrationBuilderPolicy.ts +7 -2
  214. package/src/contracts/Configuration/RegistrationBuilders.ts +6 -2
  215. package/src/contracts/Configuration/RegistrationTypes.ts +1 -0
  216. package/src/contracts/Dispatch/ZLinkDispatchOptions.ts +2 -1
  217. package/src/contracts/Locations/Rows.ts +2 -0
  218. package/src/contracts/Spots/Contracts.ts +8 -0
  219. package/src/contracts/Spots/ZLinkSpot.ts +3 -3
  220. package/src/runtime/abort.ts +12 -0
  221. package/src/runtime/actors/actor-authority-publication.ts +3 -2
  222. package/src/runtime/actors/actor-client.ts +61 -70
  223. package/src/runtime/actors/actor-context.ts +49 -9
  224. package/src/runtime/actors/actor-handoff.ts +282 -106
  225. package/src/runtime/actors/actor-join-relocation.ts +2 -0
  226. package/src/runtime/actors/actor-local-native-join.ts +4 -0
  227. package/src/runtime/actors/actor-mailbox.ts +40 -2
  228. package/src/runtime/actors/actor-remote-joiner.ts +6 -1
  229. package/src/runtime/actors/actor-reply.ts +42 -0
  230. package/src/runtime/actors/actor-runtime-contracts.ts +2 -0
  231. package/src/runtime/actors/actor-runtime-state.ts +2 -20
  232. package/src/runtime/actors/actor-source-transfer.ts +1 -34
  233. package/src/runtime/actors/index.ts +41 -27
  234. package/src/runtime/actors/session-disconnected-wire.ts +66 -0
  235. package/src/runtime/admission.ts +69 -34
  236. package/src/runtime/application-jobs/application-job-queue-scope.ts +33 -1
  237. package/src/runtime/application-jobs/contracts.ts +2 -0
  238. package/src/runtime/backend/contracts/index.ts +39 -4
  239. package/src/runtime/backend/mesh-actor-session-node-adapter.ts +5 -3
  240. package/src/runtime/backend/mesh-completion-table.ts +4 -0
  241. package/src/runtime/backend/mesh-dispatch-pump.ts +223 -124
  242. package/src/runtime/backend/node/node-raw-binding-port.ts +106 -70
  243. package/src/runtime/backend/node/node-raw-mesh-backend.ts +221 -170
  244. package/src/runtime/backend/node/node-socket-backend-adapter.ts +5 -5
  245. package/src/runtime/channels/channel-clients.ts +49 -24
  246. package/src/runtime/channels/channel-dispatch-pipeline.ts +173 -124
  247. package/src/runtime/channels/channel-dispatchers.ts +116 -72
  248. package/src/runtime/channels/channel-envelope.ts +105 -36
  249. package/src/runtime/channels/channel-runtime-lifecycle.ts +13 -13
  250. package/src/runtime/channels/channel-runtime-manager.ts +12 -5
  251. package/src/runtime/channels/channel-transports.ts +49 -58
  252. package/src/runtime/channels/client-server-location-runtime.ts +11 -10
  253. package/src/runtime/channels/dispatch-error-reporter.ts +4 -2
  254. package/src/runtime/channels/fanout-location-runtime.ts +17 -9
  255. package/src/runtime/diagnostics/flow-context.ts +20 -7
  256. package/src/runtime/diagnostics/index.ts +1 -7
  257. package/src/runtime/diagnostics/message-flow.ts +47 -9
  258. package/src/runtime/diagnostics/runtime-metrics.ts +12 -10
  259. package/src/runtime/execution/index.ts +40 -14
  260. package/src/runtime/execution/serial-execution-queue.ts +31 -4
  261. package/src/runtime/execution/state-lane.ts +7 -0
  262. package/src/runtime/foundation/operation-registry.ts +98 -20
  263. package/src/runtime/foundation/raw-service-mesh-runtime.ts +244 -67
  264. package/src/runtime/foundation/service-liveness-registry.ts +39 -0
  265. package/src/runtime/foundation/service-mailbox.ts +8 -0
  266. package/src/runtime/foundation/service-maintenance-runtime.ts +1 -8
  267. package/src/runtime/foundation/service-relocation-object-owner.ts +54 -37
  268. package/src/runtime/foundation/service-relocation-runtime.ts +7 -3
  269. package/src/runtime/foundation/service-runtime-contracts.ts +12 -1
  270. package/src/runtime/foundation/service-session-binding-ingress-port.ts +2 -1
  271. package/src/runtime/foundation/service-stateful-registry.ts +38 -35
  272. package/src/runtime/foundation/service-stateful-runtime.ts +667 -152
  273. package/src/runtime/foundation/service-stateful-wire-codec.ts +49 -11
  274. package/src/runtime/foundation/service-wire-binary-primitives.ts +35 -12
  275. package/src/runtime/foundation/service-wire-constants.generated.ts +1 -0
  276. package/src/runtime/foundation/service-wire-m6a-codec.ts +187 -51
  277. package/src/runtime/host/actor-packet-relay.ts +113 -94
  278. package/src/runtime/host/actor-placement-coordinator.ts +31 -8
  279. package/src/runtime/host/actor-transfer-runtime.ts +104 -430
  280. package/src/runtime/host/application-job-queue.ts +29 -44
  281. package/src/runtime/host/index.ts +286 -129
  282. package/src/runtime/host/location-runtime-owner.ts +2 -6
  283. package/src/runtime/host/mesh-router-resolver.ts +13 -5
  284. package/src/runtime/host/remote-bound-session-relay.ts +86 -248
  285. package/src/runtime/host/route-mesh-runtime.ts +11 -5
  286. package/src/runtime/host/runtime-shutdown.ts +15 -4
  287. package/src/runtime/host/service-relocation-host-runtime.ts +784 -475
  288. package/src/runtime/host/spot-runtime-options-factory.ts +18 -7
  289. package/src/runtime/host/user-spot-creation-coordinator.ts +41 -100
  290. package/src/runtime/locations/auto-connect-loop.ts +22 -14
  291. package/src/runtime/locations/auto-connect-reconciler.ts +0 -6
  292. package/src/runtime/locations/auto-connect-types.ts +0 -1
  293. package/src/runtime/locations/descriptor-publication.ts +7 -0
  294. package/src/runtime/locations/domain-store-contract.ts +5 -0
  295. package/src/runtime/locations/in-memory-authority-store.ts +85 -23
  296. package/src/runtime/locations/in-memory-location-store.ts +1 -12
  297. package/src/runtime/locations/index.ts +1 -0
  298. package/src/runtime/locations/lease-tracker.ts +2 -9
  299. package/src/runtime/locations/location-store-repository.ts +628 -152
  300. package/src/runtime/locations/relocation-blob.ts +75 -1
  301. package/src/runtime/locations/request-source-resolver.ts +138 -0
  302. package/src/runtime/locations/resolvers.ts +2 -2
  303. package/src/runtime/locations/runtime.ts +70 -40
  304. package/src/runtime/messaging/payload-codec.ts +8 -11
  305. package/src/runtime/messaging/request-result.ts +33 -29
  306. package/src/runtime/protocol/service_wire_codec.generated.ts +87 -4
  307. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +13 -10
  308. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +3 -2
  309. package/src/runtime/spots/index.ts +375 -281
  310. package/src/runtime/spots/spot-activation-registry.ts +25 -13
  311. package/src/runtime/spots/spot-activation-state.ts +12 -19
  312. package/src/runtime/spots/spot-activation.ts +170 -86
  313. package/src/runtime/spots/spot-actor-admission-coordinator.ts +25 -55
  314. package/src/runtime/spots/spot-actor-membership.ts +0 -15
  315. package/src/runtime/spots/spot-actor-packet-dispatch.ts +56 -11
  316. package/src/runtime/spots/spot-actor-packet-drain.ts +9 -52
  317. package/src/runtime/spots/spot-entry-activation.ts +24 -19
  318. package/src/runtime/spots/spot-handler-registry.ts +15 -4
  319. package/src/runtime/spots/spot-manager-internal-contracts.ts +2 -2
  320. package/src/runtime/spots/spot-manager-public.ts +2 -2
  321. package/src/runtime/spots/spot-node-autoconnect.ts +22 -45
  322. package/src/runtime/spots/spot-node-runtime-manager.ts +197 -67
  323. package/src/runtime/spots/spot-outbound.ts +3 -3
  324. package/src/runtime/spots/spot-remote-codec.ts +9 -2
  325. package/src/runtime/spots/spot-runtime-ports.ts +0 -1
  326. package/src/runtime/spots/spot-serial-executor.ts +97 -25
  327. package/src/runtime/spots/spot-serial-turn-executor.ts +22 -3
  328. package/src/runtime/spots/spot-subscription-dispatch.ts +1 -1
  329. package/src/runtime/spots/spot-timer.ts +413 -157
  330. package/src/runtime/streams/actor-session-binding-registry.ts +236 -64
  331. package/src/runtime/streams/actor-session-binding-runtime-owner.ts +16 -8
  332. package/src/runtime/streams/bound-actor-relay-sender.ts +23 -9
  333. package/src/runtime/streams/bound-session-service.ts +7 -10
  334. package/src/runtime/streams/index.ts +12 -6
  335. package/src/runtime/streams/managed-stream.ts +4 -2
  336. package/src/runtime/streams/native-fallback-bound-session.ts +14 -103
  337. package/src/runtime/streams/session-actor-coordinator.ts +39 -88
  338. package/src/runtime/streams/stream-binding-runtime-ports.ts +3 -9
  339. package/src/runtime/streams/stream-session-runtime.ts +1 -5
  340. 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';
@@ -14,7 +23,7 @@ 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,11 +114,16 @@ 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';
@@ -205,6 +220,58 @@ export interface ServiceCanonicalActorJoinCandidate {
205
220
  readonly local: { readonly phase: 'admission'; readonly transferId: string };
206
221
  }
207
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
+
208
275
  export interface ServiceSessionDelivery {
209
276
  readonly binding: ServiceSessionBinding;
210
277
  /**
@@ -212,6 +279,7 @@ export interface ServiceSessionDelivery {
212
279
  * been validated. The stream adapter owns multipart decoding at that point.
213
280
  */
214
281
  readonly deliver: (sessionRid: string, payloadFrame: Uint8Array) => Promise<boolean>;
282
+ readonly close?: () => Promise<void>;
215
283
  /** Enqueues the replacement lifecycle callback on the owning STREAM session. */
216
284
  readonly onBindingReplaced?: (
217
285
  actorId: string,
@@ -246,6 +314,7 @@ export interface ServiceInstanceApplicationLifecycle {
246
314
  isIdleEvicting?(target: ServiceInstanceApplicationIdentity): boolean;
247
315
  beginIdleEviction?(target: ServiceInstanceApplicationIdentity): boolean;
248
316
  materialize(target: ServiceInstanceApplicationIdentity, objectGeneration: bigint): Promise<void>;
317
+ publish?(target: ServiceInstanceApplicationTarget): void;
249
318
  discard(target: ServiceInstanceActivationTarget): Promise<void>;
250
319
  beginTerminal(target: ServiceInstanceApplicationTarget): void;
251
320
  completeTerminal(target: ServiceInstanceApplicationTarget): Promise<boolean>;
@@ -381,7 +450,7 @@ export class ServiceStatefulRuntime {
381
450
 
382
451
  readonly registry: ServiceStatefulRegistry;
383
452
 
384
- private readonly operations: ServiceTerminalOperationRegistry<ServiceStatefulResult>;
453
+ private readonly operations: ServiceTerminalOperationRegistry<ServiceStatefulResult, bigint>;
385
454
  private readonly sessionDeliveries = new Map<string, ServiceSessionDelivery>();
386
455
  /**
387
456
  * A local Actor owner can replace its own session binding before the
@@ -414,6 +483,7 @@ export class ServiceStatefulRuntime {
414
483
  private asyncInstanceAuthority?: ServiceAsyncInstanceActivationAuthority;
415
484
  private instanceApplicationLifecycle?: ServiceInstanceApplicationLifecycle;
416
485
  private userSpotOperationHandler?: ServiceUserSpotOperationHandler;
486
+ private requestSourceAcceptance?: import('../backend/contracts').ZLinkBackendRequestSourceAcceptance;
417
487
  private messageFollowHandler?: (
418
488
  record: import('./service-stateful-wire-codec').ServiceMessageFollowRecord
419
489
  ) => void;
@@ -422,6 +492,7 @@ export class ServiceStatefulRuntime {
422
492
  readonly owner: string;
423
493
  }) => void;
424
494
  private dispatchErrors?: ZLinkDispatchErrorReporter;
495
+ private sessionOwnerDescriptors?: ServiceSessionOwnerDescriptors;
425
496
  private dispatchErrorMeshName?: string;
426
497
  private readonly admittedUserSpotOperations = new Map<
427
498
  string,
@@ -465,6 +536,52 @@ export class ServiceStatefulRuntime {
465
536
  this.mailboxDropHandler = handler;
466
537
  }
467
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
+
468
585
  setDispatchErrorReporter(reporter: ZLinkDispatchErrorReporter, meshName: string): void {
469
586
  this.dispatchErrors = reporter;
470
587
  this.dispatchErrorMeshName = meshName;
@@ -566,7 +683,7 @@ export class ServiceStatefulRuntime {
566
683
  if (
567
684
  current?.bindingGeneration === bindingGeneration &&
568
685
  current.sessionOwnerNodeRid === sessionNodeRid &&
569
- current.sessionRid === sessionRid
686
+ routingIdsEqual(current.sessionRid, sessionRid)
570
687
  ) {
571
688
  return;
572
689
  }
@@ -1130,6 +1247,78 @@ export class ServiceStatefulRuntime {
1130
1247
  this.messageFollowHandler = handler;
1131
1248
  }
1132
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
+
1133
1322
  sealSpotMessageFollowIngress(
1134
1323
  source: ServiceDirectSpotRouteFence
1135
1324
  ): ServiceSpotMessageFollowSeal | undefined {
@@ -1179,11 +1368,15 @@ export class ServiceStatefulRuntime {
1179
1368
 
1180
1369
  async commitSpotMessageFollowIngress(
1181
1370
  seal: ServiceSpotMessageFollowSeal,
1182
- target: ServiceDirectSpotRouteFence,
1371
+ target: ServiceDirectSpotRouteFence | null,
1183
1372
  durationMs: number
1184
1373
  ): Promise<boolean> {
1185
1374
  const state = this.exactSpotMessageFollow(seal);
1186
1375
  if (state === undefined || state.target !== undefined) return false;
1376
+ if (target === null) {
1377
+ this.failExpiredSpotMessageFollow(state);
1378
+ return true;
1379
+ }
1187
1380
  if (!Number.isSafeInteger(durationMs) || durationMs < 0) {
1188
1381
  throw new RangeError('Message Follow duration must be a non-negative safe integer.');
1189
1382
  }
@@ -1355,13 +1548,12 @@ export class ServiceStatefulRuntime {
1355
1548
 
1356
1549
  async sendToActor(
1357
1550
  target: ServiceActorRef,
1358
- _targetNodeGeneration: bigint,
1359
- _authorityOwnerGeneration: bigint,
1360
1551
  payload: ServiceApplicationPayload,
1361
1552
  sourceActor?: ServiceActorRef,
1362
- boundSession?: { readonly sessionRid: string; readonly bindingGeneration: bigint }
1553
+ boundSession?: { readonly sessionRid: string; readonly bindingGeneration: bigint },
1554
+ routeFence?: ServiceActorRouteFence
1363
1555
  ): Promise<number> {
1364
- const route = this.tryActorFence(target);
1556
+ const route = routeFence ?? this.tryActorFence(target);
1365
1557
  if (route === undefined) return SubmitResult.NotFound;
1366
1558
  const header = encodeActorHeader(
1367
1559
  'actorSend',
@@ -1381,16 +1573,16 @@ export class ServiceStatefulRuntime {
1381
1573
 
1382
1574
  requestToActor(
1383
1575
  target: ServiceActorRef,
1384
- _targetNodeGeneration: bigint,
1385
- _authorityOwnerGeneration: bigint,
1386
1576
  payload: ServiceApplicationPayload,
1387
1577
  timeoutMs: number,
1388
1578
  sourceActor?: ServiceActorRef,
1389
1579
  boundSession?: { readonly sessionRid: string; readonly bindingGeneration: bigint },
1390
- onAdmitted?: () => void
1580
+ onAdmitted?: () => void,
1581
+ routeFence?: ServiceActorRouteFence
1391
1582
  ): ServiceStatefulPendingOperation {
1392
- const pending = this.operations.register(timeoutMs);
1393
- 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);
1394
1586
  if (route === undefined) {
1395
1587
  this.operations.reply(
1396
1588
  pending.id,
@@ -1409,7 +1601,7 @@ export class ServiceStatefulRuntime {
1409
1601
  ...boundSession,
1410
1602
  sequence: this.nextSessionSequence++
1411
1603
  },
1412
- { high: this.nodeGeneration, low: this.nextMessageFollowOperation++ }
1604
+ { high: this.nodeGeneration, low: operationLow }
1413
1605
  );
1414
1606
  this.submitRequest(
1415
1607
  pending,
@@ -1423,6 +1615,21 @@ export class ServiceStatefulRuntime {
1423
1615
  return pending;
1424
1616
  }
1425
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
+
1426
1633
  async sendToInstanceSpot(
1427
1634
  route: ServiceInstanceRouteFence,
1428
1635
  payload: ServiceApplicationPayload,
@@ -1758,27 +1965,51 @@ export class ServiceStatefulRuntime {
1758
1965
  timeoutMs: number,
1759
1966
  deliver: ServiceSessionDelivery['deliver'],
1760
1967
  onBindingReplaced?: ServiceSessionDelivery['onBindingReplaced'],
1761
- actorAuthority?: StreamSessionActorAuthorityFence
1968
+ actorAuthority?: StreamSessionActorAuthorityFence,
1969
+ close?: () => Promise<void>
1762
1970
  ): ServiceStatefulPendingOperation {
1763
1971
  const deadlineMs = performance.now() + timeoutMs;
1764
1972
  const pending = this.operations.register(timeoutMs, 'sender');
1765
- 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++;
1766
1997
  const localBinding: ServiceSessionBinding = {
1767
1998
  actor,
1768
1999
  sessionRid,
1769
- sessionOwnerNodeRid: this.nodeRid,
1770
- sessionOwnerNodeGeneration: this.nodeGeneration,
1771
- sessionOwnerId: this.nodeRid,
1772
- sessionOwnerLeaseGeneration: this.nodeGeneration,
2000
+ sessionOwnerNodeRid: sessionOwner.nodeRid,
2001
+ sessionOwnerNodeGeneration: sessionOwner.nodeGeneration,
2002
+ sessionOwnerId: sessionOwner.ownerId,
2003
+ sessionOwnerLeaseGeneration: sessionOwner.leaseGeneration,
1773
2004
  bindingGeneration: generation,
1774
2005
  membershipEpoch: this.registry.actor(actor.actorId)?.membershipEpoch ?? 1n
1775
2006
  };
1776
2007
  const delivery: ServiceSessionDelivery = {
1777
2008
  binding: localBinding,
1778
2009
  deliver,
2010
+ ...(close === undefined ? {} : { close }),
1779
2011
  ...(onBindingReplaced === undefined ? {} : { onBindingReplaced })
1780
2012
  };
1781
- const deliveryKey = actorKey(actor);
1782
2013
  if (actor.nodeRid === this.nodeRid) {
1783
2014
  let installedDelivery: ServiceSessionDelivery | undefined;
1784
2015
  try {
@@ -1802,18 +2033,7 @@ export class ServiceStatefulRuntime {
1802
2033
  });
1803
2034
  return pending;
1804
2035
  }
1805
- const previousDelivery = this.sessionDeliveries.get(deliveryKey);
1806
- const binding = this.registry.bindSession(
1807
- actor,
1808
- sessionRid,
1809
- this.nodeRid,
1810
- this.nodeGeneration,
1811
- this.nodeRid,
1812
- this.nodeGeneration
1813
- );
1814
- if (previousDelivery !== undefined && previous !== undefined) {
1815
- this.retiredSessionDeliveries.set(sessionDeliveryKey(previous), previousDelivery);
1816
- }
2036
+ const binding = this.registry.installSessionBinding(localBinding);
1817
2037
  installedDelivery = { ...delivery, binding };
1818
2038
  this.sessionDeliveries.set(deliveryKey, installedDelivery);
1819
2039
  this.operations.reply(pending.id, {
@@ -1821,7 +2041,10 @@ export class ServiceStatefulRuntime {
1821
2041
  failureCode: 0,
1822
2042
  kindData: undefined
1823
2043
  });
1824
- if (previous !== undefined) {
2044
+ if (previous !== undefined && previous !== binding) {
2045
+ if (previousDelivery !== undefined) {
2046
+ this.retiredSessionDeliveries.set(sessionDeliveryKey(previous), previousDelivery);
2047
+ }
1825
2048
  this.sendBoundSessionReplacement(
1826
2049
  previous,
1827
2050
  actorAuthorityFromRoute(
@@ -1846,7 +2069,6 @@ export class ServiceStatefulRuntime {
1846
2069
  }
1847
2070
  return pending;
1848
2071
  }
1849
- const previousDelivery = this.sessionDeliveries.get(deliveryKey);
1850
2072
  let bindingActorFence: ServiceActorRouteFence;
1851
2073
  let header: Buffer;
1852
2074
  try {
@@ -1894,7 +2116,10 @@ export class ServiceStatefulRuntime {
1894
2116
  // Publish the exact Location authority used by command 38 in the
1895
2117
  // same Session-owner completion turn as the binding route. Actor
1896
2118
  // lookup only carries a peer-lifecycle fallback for owner lease.
1897
- this.rememberActorRoute(bindingActorFence);
2119
+ this.rememberActorRoute({
2120
+ ...bindingActorFence,
2121
+ authorityOwnerGeneration: result.streamBinding.authorityOwnerGeneration
2122
+ });
1898
2123
  this.sessionDeliveries.set(deliveryKey, committed);
1899
2124
  }
1900
2125
  }
@@ -1903,6 +2128,59 @@ export class ServiceStatefulRuntime {
1903
2128
  };
1904
2129
  }
1905
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
+
1906
2184
  unbindSession(
1907
2185
  sessionRid: string,
1908
2186
  actor: ServiceActorRef,
@@ -1913,7 +2191,7 @@ export class ServiceStatefulRuntime {
1913
2191
  const delivery = this.sessionDeliveries.get(actorKey(actor));
1914
2192
  if (
1915
2193
  delivery === undefined ||
1916
- delivery.binding.sessionRid !== sessionRid ||
2194
+ !routingIdsEqual(delivery.binding.sessionRid, sessionRid) ||
1917
2195
  delivery.binding.bindingGeneration !== expectedBindingGeneration
1918
2196
  ) {
1919
2197
  this.operations.reply(
@@ -1943,7 +2221,7 @@ export class ServiceStatefulRuntime {
1943
2221
  sessionBindings(sessionRid: string): readonly ServiceStreamSessionBinding[] {
1944
2222
  return [...this.sessionDeliveries.values()]
1945
2223
  .map((value) => value.binding)
1946
- .filter((binding) => binding.sessionRid === sessionRid)
2224
+ .filter((binding) => routingIdsEqual(binding.sessionRid, sessionRid))
1947
2225
  .map((binding) => {
1948
2226
  const owner = requireSessionOwnerIdentity(binding);
1949
2227
  return {
@@ -1965,20 +2243,13 @@ export class ServiceStatefulRuntime {
1965
2243
  payload: ServiceApplicationPayload
1966
2244
  ): Promise<number> {
1967
2245
  const delivery = this.sessionDeliveries.get(actorKey(actor));
1968
- if (delivery === undefined || delivery.binding.sessionRid !== sessionRid) {
2246
+ if (delivery === undefined || !routingIdsEqual(delivery.binding.sessionRid, sessionRid)) {
1969
2247
  return SubmitResult.NotFound;
1970
2248
  }
1971
- return this.sendToActor(
1972
- actor,
1973
- this.peerGeneration(actor.nodeRid),
1974
- actor.generation,
1975
- payload,
1976
- undefined,
1977
- {
1978
- sessionRid,
1979
- bindingGeneration: delivery.binding.bindingGeneration
1980
- }
1981
- );
2249
+ return this.sendToActor(actor, payload, undefined, {
2250
+ sessionRid,
2251
+ bindingGeneration: delivery.binding.bindingGeneration
2252
+ });
1982
2253
  }
1983
2254
 
1984
2255
  async sendBoundSession(
@@ -2014,6 +2285,11 @@ export class ServiceStatefulRuntime {
2014
2285
  return this.operations.size;
2015
2286
  }
2016
2287
 
2288
+ /** The next stateful request expiry, as a lower bound. */
2289
+ nextMaintenanceDeadlineMs(): number {
2290
+ return this.operations.nextDeadlineMs();
2291
+ }
2292
+
2017
2293
  close(): void {
2018
2294
  if (this.closed) return;
2019
2295
  this.closed = true;
@@ -2037,6 +2313,10 @@ export class ServiceStatefulRuntime {
2037
2313
  this.spotMessageFollow.clear();
2038
2314
  }
2039
2315
 
2316
+ cancelPendingOperations(reason: unknown): void {
2317
+ this.operations.failAll(reason);
2318
+ }
2319
+
2040
2320
  private async ingress(
2041
2321
  record: RawServiceIngressRecord
2042
2322
  ): Promise<RawServicePumpResult | undefined> {
@@ -2086,7 +2366,8 @@ export class ServiceStatefulRuntime {
2086
2366
  decoded.kind === 'userSpotClose' ||
2087
2367
  decoded.kind === 'actorCreate' ||
2088
2368
  decoded.kind === 'messageFollow' ||
2089
- decoded.kind === 'boundSessionReplaced';
2369
+ decoded.kind === 'boundSessionReplaced' ||
2370
+ decoded.kind === 'boundSessionClose';
2090
2371
  if (
2091
2372
  record.parts.length < 1 ||
2092
2373
  record.parts.length > (instanceMetadata || statefulMetadata ? 3 : 2) ||
@@ -2131,7 +2412,13 @@ export class ServiceStatefulRuntime {
2131
2412
  decoded.activation === 'missing' &&
2132
2413
  (error instanceof ZLinkFrameworkException || error instanceof ServiceStaleGenerationError)
2133
2414
  ) {
2134
- this.finishMissingInstanceActivationFailure(record, decoded, error);
2415
+ this.finishMissingInstanceActivationFailure(
2416
+ record,
2417
+ decoded,
2418
+ error,
2419
+ undefined,
2420
+ payloadFrame
2421
+ );
2135
2422
  return 'infrastructure';
2136
2423
  }
2137
2424
  const correlation = statefulCorrelation(decoded);
@@ -2263,7 +2550,7 @@ export class ServiceStatefulRuntime {
2263
2550
  record.boundSession.bindingGeneration
2264
2551
  );
2265
2552
  if (
2266
- binding.sessionRid !== record.boundSession.sessionRid ||
2553
+ !routingIdsEqual(binding.sessionRid, record.boundSession.sessionRid) ||
2267
2554
  binding.sessionOwnerNodeRid !== ingress.sourceRoutingId
2268
2555
  ) {
2269
2556
  throw new ServiceStaleGenerationError('binding', record.target.actor.actorId);
@@ -2286,12 +2573,13 @@ export class ServiceStatefulRuntime {
2286
2573
  : { sourceBindingGeneration: record.boundSession.bindingGeneration }),
2287
2574
  targetActor: actor.ref,
2288
2575
  messageFollowOrigin: {
2289
- sourceNodeRid: ingress.sourceRoutingId,
2290
- originalOperation: {
2291
- high: this.peerGeneration(ingress.sourceRoutingId),
2292
- low: record.correlation ?? this.nextMessageFollowOperation++
2576
+ get requestSource() {
2577
+ return ingress.requestSource;
2293
2578
  },
2294
- originalReplyRouteId: ingress.requestSequence ?? 0n
2579
+ target: record.target,
2580
+ sourceNodeRid: ingress.sourceRoutingId,
2581
+ originalOperation: record.operation,
2582
+ originalReplyRouteId: record.correlation ?? 0n
2295
2583
  },
2296
2584
  ...(record.kind === 'actorRequest'
2297
2585
  ? { reply: this.replyPort(ingress, record.correlation) }
@@ -2329,7 +2617,9 @@ export class ServiceStatefulRuntime {
2329
2617
  canonicalActorJoinPayload
2330
2618
  );
2331
2619
  case 'boundSessionBind':
2332
- return this.replySessionBind(ingress, record);
2620
+ return await this.replySessionBind(ingress, record);
2621
+ case 'boundSessionClose':
2622
+ return await this.handleBoundSessionClose(ingress, record);
2333
2623
  case 'boundSessionReplaced':
2334
2624
  return this.handleBoundSessionReplaced(ingress, record);
2335
2625
  case 'boundSessionSend':
@@ -2536,7 +2826,7 @@ export class ServiceStatefulRuntime {
2536
2826
  }
2537
2827
  });
2538
2828
  } catch (error) {
2539
- this.finishMissingInstanceActivationFailure(ingress, record, error, localReply);
2829
+ this.finishMissingInstanceActivationFailure(ingress, record, error, localReply, payloadFrame);
2540
2830
  }
2541
2831
  }
2542
2832
 
@@ -2571,6 +2861,7 @@ export class ServiceStatefulRuntime {
2571
2861
  latestRoute !== undefined && sameInstanceApplicationOwner(latestRoute, record.route)
2572
2862
  ? { ...record, route: latestRoute }
2573
2863
  : record;
2864
+ this.publishCommittedInstanceRoute(intent.instanceType, refreshedRecord.route);
2574
2865
  const spot = this.requireInstanceActivation(ingress, refreshedRecord);
2575
2866
  const admitted = this.enqueueActivatedInstanceSpot(
2576
2867
  ingress,
@@ -2727,7 +3018,8 @@ export class ServiceStatefulRuntime {
2727
3018
  { readonly kind: 'instanceSpot'; readonly activation: 'missing' }
2728
3019
  >,
2729
3020
  error: unknown,
2730
- localReply?: NonNullable<ServiceStatefulMailboxData['reply']>
3021
+ localReply?: NonNullable<ServiceStatefulMailboxData['reply']>,
3022
+ payloadFrame?: Buffer
2731
3023
  ): void {
2732
3024
  if (record.operationKind === 'request') {
2733
3025
  if (record.deadlineUnixMs <= BigInt(Date.now())) return;
@@ -2740,17 +3032,51 @@ export class ServiceStatefulRuntime {
2740
3032
  return;
2741
3033
  }
2742
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
+ });
2743
3075
  if (reporter?.captureEnabled() === true) {
2744
3076
  reporter.report({
2745
- surface: ZLinkDispatchErrorSurface.InstanceSpot,
2746
- messageKind: ZLinkDispatchMessageKind.Send,
2747
- reason: ZLinkDispatchErrorReason.StaleTarget,
3077
+ ...context,
3078
+ reason,
2748
3079
  action: ZLinkDispatchErrorAction.Drop,
2749
- meshName: this.dispatchErrorMeshName,
2750
- sourceRid: record.sourceNodeRid,
2751
- spotId: record.target.targetSpotId,
2752
- instanceSpotType: record.target.stableType,
2753
- activationState: 'activating',
2754
3080
  error
2755
3081
  });
2756
3082
  }
@@ -2969,12 +3295,25 @@ export class ServiceStatefulRuntime {
2969
3295
  ): Promise<unknown> {
2970
3296
  const key = record.target.targetSpotId;
2971
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;
2972
3305
  const ready =
2973
3306
  pending ??
2974
- this.runMissingInstanceActivation(record, payloadFrame, metadataFrame).then((activation) => ({
2975
- ...activation,
2976
- stableType: record.target.stableType
2977
- }));
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
+ );
2978
3317
  const operation = ready.then(async (value) => {
2979
3318
  if (pending !== undefined) {
2980
3319
  await this.asyncInstanceAuthority?.read(
@@ -3023,12 +3362,38 @@ export class ServiceStatefulRuntime {
3023
3362
  ): Promise<{
3024
3363
  readonly spot: Pick<ServiceSpotState, 'ref'>;
3025
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;
3026
3392
  }> {
3027
3393
  const target = record.target;
3028
3394
  if (
3029
3395
  target.targetNodeRid !== this.nodeRid ||
3030
- target.targetNodeGeneration !== this.nodeGeneration ||
3031
- record.deadlineUnixMs < BigInt(Date.now())
3396
+ target.targetNodeGeneration !== this.nodeGeneration
3032
3397
  ) {
3033
3398
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3034
3399
  }
@@ -3140,21 +3505,22 @@ export class ServiceStatefulRuntime {
3140
3505
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3141
3506
  }
3142
3507
  }
3143
- const deadline = operationDeadline(record.deadlineUnixMs);
3144
- let reserved: ServiceInstanceAuthorityReserve;
3145
- try {
3146
- reserved = await authority.reserve(activationRequest, deadline.signal);
3147
- } finally {
3148
- deadline.close();
3149
- }
3150
- 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
+ );
3151
3516
  await authority.abort(target, reserved.reservation);
3152
- throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3517
+ throw error;
3153
3518
  }
3154
3519
 
3155
3520
  let activation: { readonly spot: ServiceSpotState; readonly created: boolean };
3156
3521
  try {
3157
3522
  await this.instanceApplicationLifecycle?.materialize(target, reserved.reservation.attempt);
3523
+ signal.throwIfAborted();
3158
3524
  activation = this.activateInstanceSpot(
3159
3525
  target.targetSpotId,
3160
3526
  target.stableType,
@@ -3222,8 +3588,6 @@ export class ServiceStatefulRuntime {
3222
3588
  if (activation.created) this.registry.closeSpot(activation.spot.ref);
3223
3589
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3224
3590
  }
3225
- this.publishCommittedInstanceRoute(target.stableType, route);
3226
-
3227
3591
  const confirmed = await authority.read(target);
3228
3592
  if (confirmed.kind !== 'ready' || !sameInstanceRoute(confirmed.route, route)) {
3229
3593
  if (confirmed.kind === 'ready' && confirmed.route.targetNodeRid !== this.nodeRid) {
@@ -3238,6 +3602,7 @@ export class ServiceStatefulRuntime {
3238
3602
  ) {
3239
3603
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3240
3604
  }
3605
+ this.publishCommittedInstanceRoute(target.stableType, confirmed.route);
3241
3606
  return { spot: current, route: confirmed.route };
3242
3607
  }
3243
3608
 
@@ -3255,6 +3620,9 @@ export class ServiceStatefulRuntime {
3255
3620
  : instanceRouteAsDirectRoute(expectedCurrentRoute);
3256
3621
  this.rememberSpotRoute(directRoute, expectedDirectRoute);
3257
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 });
3258
3626
  }
3259
3627
 
3260
3628
  private replaceCommittedInstanceRoute(
@@ -3407,55 +3775,52 @@ export class ServiceStatefulRuntime {
3407
3775
  return 'infrastructure';
3408
3776
  }
3409
3777
 
3410
- private replySessionBind(
3778
+ private async replySessionBind(
3411
3779
  ingress: RawServiceIngressRecord,
3412
3780
  record: Extract<ServiceStatefulWireRecord, { readonly kind: 'boundSessionBind' }>
3413
- ): RawServicePumpResult {
3781
+ ): Promise<RawServicePumpResult> {
3414
3782
  try {
3415
3783
  if (record.binding.state === 'active') {
3784
+ const sessionOwner = await this.sessionOwnerOf(
3785
+ ingress.sourceRoutingId,
3786
+ ingress.sourceNodeGeneration ?? this.peerGeneration(ingress.sourceRoutingId)
3787
+ );
3416
3788
  const actor = this.validateActorFence(record.actor);
3417
- const previous = this.registry.binding(actor.ref);
3418
- const sourceGeneration =
3419
- ingress.sourceNodeGeneration ?? this.peerGeneration(ingress.sourceRoutingId);
3420
3789
  const binding = sessionBindingFromWire(
3421
3790
  actor.ref,
3422
3791
  record.sessionRid,
3423
- ingress.sourceRoutingId,
3792
+ sessionOwner.nodeRid,
3424
3793
  record.binding.generation,
3425
3794
  actor.membershipEpoch,
3426
- sourceGeneration,
3427
- ingress.sourceRoutingId,
3428
- sourceGeneration
3795
+ sessionOwner.nodeGeneration,
3796
+ sessionOwner.ownerId,
3797
+ sessionOwner.leaseGeneration
3429
3798
  );
3430
- if (previous !== undefined && sameSessionIdentity(previous, binding)) {
3431
- // Registry publication is the last step of the first binding owner
3432
- // turn, so an observable same-Session binding already has every
3433
- // Actor route projection installed and can complete idempotently.
3434
- this.replyWire(ingress, record.correlation, RequestResult.Ok, 0, undefined, {
3435
- kind: 'streamBind',
3436
- bindingGeneration: previous.bindingGeneration,
3437
- authorityOwnerGeneration: actor.authorityOwnerGeneration
3438
- });
3439
- return 'infrastructure';
3440
- }
3799
+ let completedBinding = binding;
3441
3800
  const accepted = this.enqueueActorBindingControl(
3442
3801
  ingress,
3443
3802
  binding,
3444
3803
  record.actor,
3445
3804
  (terminalResult, failureCode) => {
3446
- let completedBinding = binding;
3447
3805
  let replacedBinding: ServiceSessionBinding | undefined;
3448
3806
  if (terminalResult === RequestResult.Ok) {
3449
3807
  // This callback runs inside the Actor binding control turn,
3450
- // after the Host has installed its Actor-to-session projection.
3808
+ // before the Host copies the accepted Actor-to-session projection.
3451
3809
  // Publish the authority-owned registry view in that same turn
3452
3810
  // before making the terminal reply observable.
3453
3811
  const turnCurrent = this.registry.binding(actor.ref);
3454
- if (turnCurrent !== undefined && sameSessionIdentity(turnCurrent, binding)) {
3455
- completedBinding = turnCurrent;
3456
- } else {
3457
- replacedBinding = turnCurrent;
3458
- 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;
3459
3824
  }
3460
3825
  }
3461
3826
  this.replyWire(
@@ -3486,7 +3851,8 @@ export class ServiceStatefulRuntime {
3486
3851
  );
3487
3852
  }
3488
3853
  return true;
3489
- }
3854
+ },
3855
+ () => completedBinding
3490
3856
  );
3491
3857
  if (!accepted) {
3492
3858
  const result = failure(
@@ -3507,6 +3873,36 @@ export class ServiceStatefulRuntime {
3507
3873
  return 'infrastructure';
3508
3874
  }
3509
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
+
3510
3906
  /**
3511
3907
  * Handles the replacement notice at the retired session owner. This path
3512
3908
  * intentionally never calls validateActorFence(): the notice is authorized
@@ -3529,12 +3925,25 @@ export class ServiceStatefulRuntime {
3529
3925
  // session that belongs to the new lifecycle.
3530
3926
  return 'infrastructure';
3531
3927
  }
3532
- if (
3533
- record.retiredSession.sessionOwnerNodeRid !== this.nodeRid ||
3534
- record.retiredSession.sessionOwnerNodeGeneration !== this.nodeGeneration ||
3535
- record.retiredSession.sessionOwnerId !== this.nodeRid ||
3536
- record.retiredSession.sessionOwnerLeaseGeneration !== this.nodeGeneration
3537
- ) {
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
+ );
3538
3947
  return 'infrastructure';
3539
3948
  }
3540
3949
  const actor = authority.actor;
@@ -3561,6 +3970,9 @@ export class ServiceStatefulRuntime {
3561
3970
  : undefined;
3562
3971
  if (delivery === undefined) return 'infrastructure';
3563
3972
  this.appliedReplacementNotices.add(key);
3973
+ if (delivery === current) {
3974
+ this.sessionDeliveries.delete(deliveryKey);
3975
+ }
3564
3976
  delivery.onBindingReplaced?.(actor.actorId, record.retiredSession, actor);
3565
3977
  if (retired !== undefined) {
3566
3978
  this.retiredSessionDeliveries.delete(sessionDeliveryKey(retired.binding));
@@ -3572,14 +3984,20 @@ export class ServiceStatefulRuntime {
3572
3984
  previous: ServiceSessionBinding,
3573
3985
  actorAuthority: ServiceBoundSessionActorAuthority
3574
3986
  ): void {
3575
- const ownerGeneration =
3576
- previous.sessionOwnerNodeGeneration ?? this.tryPeerGeneration(previous.sessionOwnerNodeRid);
3577
- 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
+ }
3578
3996
  const header = encodeBoundSessionReplacedHeader(actorAuthority, {
3579
3997
  sessionOwnerNodeRid: previous.sessionOwnerNodeRid,
3580
- sessionOwnerNodeGeneration: ownerGeneration,
3581
- sessionOwnerId: previous.sessionOwnerId ?? previous.sessionOwnerNodeRid,
3582
- sessionOwnerLeaseGeneration: previous.sessionOwnerLeaseGeneration ?? ownerGeneration,
3998
+ sessionOwnerNodeGeneration: previous.sessionOwnerNodeGeneration,
3999
+ sessionOwnerId: previous.sessionOwnerId,
4000
+ sessionOwnerLeaseGeneration: previous.sessionOwnerLeaseGeneration,
3583
4001
  sessionRid: previous.sessionRid,
3584
4002
  retiredBindingGeneration: previous.bindingGeneration
3585
4003
  });
@@ -3602,11 +4020,9 @@ export class ServiceStatefulRuntime {
3602
4020
  current === undefined ||
3603
4021
  sourceGeneration === undefined ||
3604
4022
  current.bindingGeneration !== record.binding.retiredGeneration ||
3605
- current.sessionRid !== record.sessionRid ||
4023
+ !routingIdsEqual(current.sessionRid, record.sessionRid) ||
3606
4024
  current.sessionOwnerNodeRid !== ingress.sourceRoutingId ||
3607
- current.sessionOwnerNodeGeneration !== sourceGeneration ||
3608
- current.sessionOwnerId !== ingress.sourceRoutingId ||
3609
- current.sessionOwnerLeaseGeneration !== sourceGeneration
4025
+ current.sessionOwnerNodeGeneration !== sourceGeneration
3610
4026
  ) {
3611
4027
  this.replyWire(ingress, record.correlation, RequestResult.Ok, 0);
3612
4028
  return;
@@ -3771,6 +4187,37 @@ export class ServiceStatefulRuntime {
3771
4187
  });
3772
4188
  }
3773
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
+
3774
4221
  private enqueueApplicationFrame(
3775
4222
  ingress: RawServiceIngressRecord,
3776
4223
  owner: string,
@@ -3802,6 +4249,10 @@ export class ServiceStatefulRuntime {
3802
4249
  domain,
3803
4250
  parts: [ingress.parts[0]!, payloadFrame],
3804
4251
  sourceRoutingId: ingress.sourceRoutingId,
4252
+ ingress,
4253
+ get requestSource() {
4254
+ return ingress.requestSource;
4255
+ },
3805
4256
  sourceRoute: ingress.sourceRoute,
3806
4257
  ...(ingress.reply === undefined ? {} : { reply: ingress.reply }),
3807
4258
  ...(ingress.requestSequence === undefined
@@ -3809,6 +4260,15 @@ export class ServiceStatefulRuntime {
3809
4260
  : { requestSequence: ingress.requestSequence }),
3810
4261
  ...(stateful.correlation === undefined ? {} : { correlation: stateful.correlation }),
3811
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,
3812
4272
  applicationJob
3813
4273
  });
3814
4274
  if (accepted) {
@@ -3950,10 +4410,11 @@ export class ServiceStatefulRuntime {
3950
4410
  ingress: RawServiceIngressRecord,
3951
4411
  binding: ServiceSessionBinding,
3952
4412
  actorAuthority: ServiceBoundSessionActorAuthority,
3953
- reply: NonNullable<ServiceStatefulMailboxData['reply']>
4413
+ reply: NonNullable<ServiceStatefulMailboxData['reply']>,
4414
+ acceptedBinding: () => ServiceSessionBinding
3954
4415
  ): boolean {
3955
4416
  const actor = binding.actor;
3956
- const sessionOwner = requireSessionOwnerIdentity(binding);
4417
+ requireSessionOwnerIdentity(binding);
3957
4418
  const header = Buffer.from([
3958
4419
  SERVICE_WIRE_MAGIC[0],
3959
4420
  SERVICE_WIRE_MAGIC[1],
@@ -3978,12 +4439,27 @@ export class ServiceStatefulRuntime {
3978
4439
  actorNodeGeneration: actorAuthority.targetNodeGeneration,
3979
4440
  authorityOwnerGeneration: actorAuthority.authorityOwnerGeneration,
3980
4441
  ownerLeaseGeneration: actorAuthority.ownerLeaseGeneration,
3981
- bindingGeneration: binding.bindingGeneration,
3982
- sessionNodeRid: binding.sessionOwnerNodeRid as never,
3983
- sessionRid: binding.sessionRid as never,
3984
- sessionOwnerNodeGeneration: sessionOwner.nodeGeneration,
3985
- sessionOwnerId: sessionOwner.ownerId,
3986
- 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
+ }
3987
4463
  },
3988
4464
  reply
3989
4465
  } satisfies ServiceStatefulMailboxData,
@@ -4557,6 +5033,15 @@ export class ServiceStatefulRuntime {
4557
5033
  operationKind: OperationKind.ActorRequest,
4558
5034
  correlation: decoded.correlation,
4559
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
+ },
4560
5045
  ...(decoded.sourceActor === undefined
4561
5046
  ? {}
4562
5047
  : { sourceActor: { ...decoded.sourceActor, nodeRid: ingress.sourceRoutingId } }),
@@ -4652,13 +5137,17 @@ export class ServiceStatefulRuntime {
4652
5137
  try {
4653
5138
  const current = this.validateActorFence(decoded.actor);
4654
5139
  if (decoded.binding.state === 'active') {
5140
+ const sessionOwner = await this.sessionOwnerOf(this.nodeRid, this.nodeGeneration);
4655
5141
  this.registry.installSessionBinding(
4656
5142
  sessionBindingFromWire(
4657
5143
  current.ref,
4658
5144
  decoded.sessionRid,
4659
- this.nodeRid,
5145
+ sessionOwner.nodeRid,
4660
5146
  decoded.binding.generation,
4661
- current.membershipEpoch
5147
+ current.membershipEpoch,
5148
+ sessionOwner.nodeGeneration,
5149
+ sessionOwner.ownerId,
5150
+ sessionOwner.leaseGeneration
4662
5151
  )
4663
5152
  );
4664
5153
  } else {
@@ -4707,6 +5196,7 @@ export class ServiceStatefulRuntime {
4707
5196
  | 'actorJoin'
4708
5197
  | 'streamBind'
4709
5198
  | 'streamUnbind'
5199
+ | 'boundSessionClose'
4710
5200
  | 'instanceSpotRequest',
4711
5201
  actor: ServiceActorRef | undefined,
4712
5202
  reply: readonly Buffer[]
@@ -5333,6 +5823,25 @@ function isEntrySpotFence(fence: ServiceSpotRouteFence): boolean {
5333
5823
  );
5334
5824
  }
5335
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
+
5336
5845
  function operationDeadline(deadlineUnixMs: bigint): {
5337
5846
  readonly signal: AbortSignal;
5338
5847
  close(): void;
@@ -5397,6 +5906,13 @@ function actorLocation(actor: ServiceActorState): ActorLocation {
5397
5906
  };
5398
5907
  }
5399
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
+
5400
5916
  function activationRefused(): ZLinkFrameworkException {
5401
5917
  return new ZLinkFrameworkException(
5402
5918
  ZLinkFrameworkErrorKind.Unavailable,
@@ -5414,14 +5930,14 @@ function failure(error: unknown): ServiceStatefulResult {
5414
5930
  return internalFrameworkWireReply(ZLinkFrameworkInternalErrorKind.RequestFailed);
5415
5931
  }
5416
5932
 
5417
- function actorKey(actor: ServiceActorRef): string {
5933
+ function actorKey(actor: Pick<ServiceActorRef, 'actorId' | 'generation'>): string {
5418
5934
  return `${actor.actorId}\0${actor.generation}`;
5419
5935
  }
5420
5936
 
5421
5937
  function sameSessionBinding(left: ServiceSessionBinding, right: ServiceSessionBinding): boolean {
5422
5938
  return (
5423
5939
  sameActorRef(left.actor, right.actor) &&
5424
- left.sessionRid === right.sessionRid &&
5940
+ routingIdsEqual(left.sessionRid, right.sessionRid) &&
5425
5941
  sameSessionOwnerIdentity(left, right) &&
5426
5942
  left.bindingGeneration === right.bindingGeneration
5427
5943
  );
@@ -5430,7 +5946,7 @@ function sameSessionBinding(left: ServiceSessionBinding, right: ServiceSessionBi
5430
5946
  function sameSessionIdentity(left: ServiceSessionBinding, right: ServiceSessionBinding): boolean {
5431
5947
  return (
5432
5948
  sameActorRef(left.actor, right.actor) &&
5433
- left.sessionRid === right.sessionRid &&
5949
+ routingIdsEqual(left.sessionRid, right.sessionRid) &&
5434
5950
  sameSessionOwnerIdentity(left, right)
5435
5951
  );
5436
5952
  }
@@ -5453,9 +5969,7 @@ function sameSessionOwnerIdentity(
5453
5969
  ): boolean {
5454
5970
  return (
5455
5971
  left.sessionOwnerNodeRid === right.sessionOwnerNodeRid &&
5456
- left.sessionOwnerNodeGeneration === right.sessionOwnerNodeGeneration &&
5457
- left.sessionOwnerId === right.sessionOwnerId &&
5458
- left.sessionOwnerLeaseGeneration === right.sessionOwnerLeaseGeneration
5972
+ left.sessionOwnerNodeGeneration === right.sessionOwnerNodeGeneration
5459
5973
  );
5460
5974
  }
5461
5975
 
@@ -5493,7 +6007,7 @@ function sessionDeliveryKey(
5493
6007
  >
5494
6008
  ): string {
5495
6009
  return (
5496
- `${actorKey(binding.actor)}\0${binding.sessionOwnerNodeRid}\0${binding.sessionRid}` +
6010
+ `${actorKey(binding.actor)}\0${binding.sessionOwnerNodeRid}\0${encodeRoutingIdStorageHex(binding.sessionRid)}` +
5497
6011
  `\0${binding.sessionOwnerNodeGeneration ?? ''}` +
5498
6012
  `\0${binding.sessionOwnerId ?? ''}` +
5499
6013
  `\0${binding.sessionOwnerLeaseGeneration ?? ''}` +
@@ -5506,8 +6020,8 @@ function exactRetiredDelivery(
5506
6020
  retired: ServiceRetiredBoundSessionRouteFence
5507
6021
  ): boolean {
5508
6022
  return (
5509
- binding.sessionRid === retired.sessionRid &&
5510
- binding.sessionOwnerNodeRid === retired.sessionOwnerNodeRid &&
6023
+ routingIdsEqual(binding.sessionRid, retired.sessionRid) &&
6024
+ routingIdsEqual(binding.sessionOwnerNodeRid, retired.sessionOwnerNodeRid) &&
5511
6025
  binding.sessionOwnerNodeGeneration === retired.sessionOwnerNodeGeneration &&
5512
6026
  binding.sessionOwnerId === retired.sessionOwnerId &&
5513
6027
  binding.sessionOwnerLeaseGeneration === retired.sessionOwnerLeaseGeneration &&
@@ -5526,7 +6040,7 @@ function replacementNoticeKey(
5526
6040
  `\0${record.retiredSession.sessionOwnerNodeGeneration}` +
5527
6041
  `\0${record.retiredSession.sessionOwnerId}` +
5528
6042
  `\0${record.retiredSession.sessionOwnerLeaseGeneration}` +
5529
- `\0${record.retiredSession.sessionRid}` +
6043
+ `\0${encodeRoutingIdStorageHex(record.retiredSession.sessionRid)}` +
5530
6044
  `\0${record.retiredSession.retiredBindingGeneration}`
5531
6045
  );
5532
6046
  }
@@ -5873,7 +6387,8 @@ function acceptsStatefulIngress(command: number): boolean {
5873
6387
  command !== M6bServiceWireCommand.userSpotClose &&
5874
6388
  command !== M6bServiceWireCommand.actorCreate &&
5875
6389
  command !== M6bServiceWireCommand.messageFollow &&
5876
- command !== M6bServiceWireCommand.boundSessionReplaced)
6390
+ command !== M6bServiceWireCommand.boundSessionReplaced &&
6391
+ command !== M6bServiceWireCommand.boundSessionClose)
5877
6392
  ) {
5878
6393
  return false;
5879
6394
  }