@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,14 @@
1
+ import { encodeActorReply } from '../actors/actor-reply';
2
+ import { ZLinkStreamMessageKind } from '../streams/protocol';
3
+ import {
4
+ decodeApplicationPayloadView,
5
+ encodeMultipartApplicationPayload
6
+ } from '../foundation/service-wire-m6a-codec';
7
+ import {
8
+ SERVICE_FRAMEWORK_MULTIPART_PACKET_NAME,
9
+ SERVICE_FRAMEWORK_MULTIPART_CONTENT_TYPE
10
+ } from '../foundation/service-wire-constants.generated';
11
+ import { OperationTimeoutError } from '../foundation/operation-registry';
1
12
  import { durableRequestCanReplay } from '../foundation/durable-service-request';
2
13
  import { UINT64_MAX, ZlinkStreamContentType } from '@zlink-systems/stream-wire';
3
14
  import { randomBytes } from 'node:crypto';
@@ -19,6 +30,12 @@ import {
19
30
  ZLinkSpotRelocationReadyOutcome,
20
31
  ZLinkUserSpotExecutionMode
21
32
  } from '../../contracts';
33
+ import {
34
+ ZLinkDispatchErrorSurface,
35
+ ZLinkDispatchMessageKind,
36
+ ZLinkRuntimeDispatchErrorAction as ZLinkDispatchErrorAction,
37
+ ZLinkRuntimeMessageFlowOutcome as ZLinkMessageFlowOutcome
38
+ } from '../../contracts/Dispatch/ZLinkDispatchOptions';
22
39
 
23
40
  import type { ZLinkProviderResolver } from '../../contracts/Common/ZLinkProviderResolver';
24
41
  import { zlinkRuntimeDefaultLocationOptions } from '../../contracts/Locations/Options';
@@ -30,7 +47,6 @@ import {
30
47
  rewriteActorAuthorityRoute
31
48
  } from '../actors/actor-authority-publication';
32
49
  import {
33
- replayActorHandoffBacklog,
34
50
  type ZLinkActorHandoffPacket,
35
51
  type ZLinkActorHandoffResult
36
52
  } from '../actors/actor-handoff';
@@ -49,12 +65,19 @@ import { RequestResult, SubmitResult, ZLinkBackendResultError } from '../backend
49
65
  import type { ZLinkFrameworkRegistration } from '../configuration';
50
66
  import {
51
67
  type ZLinkRuntimeEventPublisher,
68
+ type ZLinkRelocationMetricOperation,
52
69
  type ZLinkRuntimeMetrics,
53
70
  isRelocationDebugEnabled,
54
71
  relocationDebug
55
72
  } from '../diagnostics';
56
73
 
57
74
  import { METRIC_NAMES, MILLISECONDS_PER_SECOND } from '../diagnostics/runtime-metrics';
75
+ import {
76
+ traceSessionBindingControlDrop,
77
+ flowIfEnabled,
78
+ type ZLinkMessageFlowTracer
79
+ } from '../diagnostics/message-flow';
80
+ import { dispatchErrorDetails } from '../diagnostics/dispatch-error-details';
58
81
  import { isStructuralGuardEnabled, trackDiagnosticCompletion } from '../execution/state-lane';
59
82
  import {
60
83
  RECREATE_RELOCATION_CONTENT_TYPE,
@@ -160,8 +183,9 @@ import type {
160
183
  } from '../locations/internal-location-contracts';
161
184
  import type { ZLinkTrackedInstanceAuthority } from '../locations/spot-location-claims';
162
185
  import { decodeRoutingId, encodeRoutingIdStorageHex, routingIdsEqual } from '../routing-id';
163
- import type { DefaultZLinkSpotManager, ZLinkSpotNodeRuntimeManager } from '../spots';
186
+ import type { DefaultZLinkSpotManager } from '../spots';
164
187
  import type { ZLinkSpotActivation } from '../spots/spot-activation-state';
188
+ import type { ZLinkSpotRouteTarget } from '../spots/spot-routing-internal';
165
189
  import { createProviderInstance } from '../spots/spot-provider';
166
190
  import { decodeHandoffBacklog } from '../spots/spot-remote-codec';
167
191
  import { committedActorOwnerFence, type ZLinkActorTransferRuntime } from './actor-transfer-runtime';
@@ -219,11 +243,14 @@ const SERVICE_CONTROL_TERMINAL_CAPACITY = 4096;
219
243
  const INITIAL_MEMBERSHIP_EPOCH = 1n;
220
244
 
221
245
  interface ZLinkHostRelocationOptions {
246
+ readonly flowCreationEnabled?: () => boolean;
247
+ readonly messageFlow?: () => ZLinkMessageFlowTracer | undefined;
222
248
  readonly registration: ZLinkFrameworkRegistration;
223
249
  readonly providerResolver?: ZLinkProviderResolver;
224
250
  readonly locationStore: () => ZLinkLocationStore | undefined;
225
251
  readonly currentOwner: () => ZLinkLocationOwnerToken | undefined;
226
252
  readonly targetAdmissionSealed?: () => boolean;
253
+ readonly requestPeerReconcile?: (meshName: string, peerRid: string) => Promise<void>;
227
254
  readonly sourceQuiescenceChanged?: () => void;
228
255
  readonly liveDescriptors: (
229
256
  meshName: string,
@@ -233,16 +260,16 @@ interface ZLinkHostRelocationOptions {
233
260
  readonly meshNode: (meshName: string) => ZLinkBackendMeshNode | undefined;
234
261
  readonly completions: (meshName: string) => ZLinkMeshCompletionTable | undefined;
235
262
  readonly spotManager: () => DefaultZLinkSpotManager | undefined;
236
- readonly spotNodeRuntime: () => ZLinkSpotNodeRuntimeManager | undefined;
237
263
  readonly actorManager: () => DefaultZLinkActorManager | undefined;
238
264
  readonly actorTransfer: ZLinkActorTransferRuntime;
239
265
  readonly boundSessionRelocation?: {
240
266
  receiveSeal(
241
267
  value: ServiceSessionRelocationSeal,
242
- signal?: AbortSignal
243
- ): Promise<ServiceSessionRelocationSealed>;
268
+ signal?: AbortSignal,
269
+ onInstalled?: () => void
270
+ ): Promise<ServiceSessionRelocationSealed | undefined>;
244
271
  receiveRoute(value: ServiceSessionRelocationRoute): Promise<void>;
245
- clear?(): void;
272
+ clear?(): void | Promise<void>;
246
273
  };
247
274
  readonly trackInstanceSpot?: (input: ZLinkTrackedInstanceAuthority) => void;
248
275
  readonly invalidateActorRoute?: (actorId: string) => void;
@@ -264,8 +291,11 @@ interface LocalHidden extends ServiceRelocationHiddenObject {
264
291
  readonly actor?: ZLinkActor;
265
292
  readonly activation?: ZLinkSpotActivation;
266
293
  initialized: boolean;
267
- readonly restoredTimers: ServiceRelocationTimer[];
268
- readonly replayResults: ZLinkActorHandoffResult[];
294
+ replayStart?: Promise<void>;
295
+ onReplayResult?: (
296
+ packet: ZLinkActorHandoffPacket,
297
+ result: ZLinkActorHandoffResult
298
+ ) => Promise<void>;
269
299
  readonly replayPackets: ZLinkActorHandoffPacket[];
270
300
  targetAuthorityOwnerGeneration?: bigint;
271
301
  }
@@ -293,6 +323,12 @@ interface TargetPayloadAssembly {
293
323
  readonly assembly: ZLinkRelocationPayloadAssembly;
294
324
  }
295
325
 
326
+ /** One authority read decides commit and the snapshots eligible for Follow. */
327
+ type SourceAuthorityCommit = {
328
+ readonly kind: 'target';
329
+ readonly followAuthorities: ReadonlyMap<string, ZLinkAuthoritySnapshot>;
330
+ };
331
+
296
332
  interface SourceCutoverWindow {
297
333
  readonly meshName: string;
298
334
  readonly targetNodeRid: RoutingId;
@@ -343,12 +379,14 @@ interface SourceActorJoinProfile {
343
379
  readonly canonicalRecovery?: NonNullable<
344
380
  Parameters<ZLinkActorJoinRelocation['relocateActorJoin']>[0]['canonicalRecovery']
345
381
  >;
382
+ readonly relayReady: () => void;
346
383
  readonly ready: Promise<void>;
347
384
  readonly resolveReady: (failure?: ServiceRelocationPostCommitError) => void;
385
+ retirement?: Promise<void>;
348
386
  }
349
387
 
350
388
  interface PendingRelocationReplyRelay {
351
- readonly ackTargetNodeRid: string;
389
+ readonly ackTargetNodeRid: RoutingId;
352
390
  readonly identityKey: string;
353
391
  readonly request: ServiceMaintenanceReplyRelay;
354
392
  readonly expectedRequestSource: ServiceWireRequestSourceFence;
@@ -370,9 +408,8 @@ interface PendingSessionRelocation {
370
408
 
371
409
  interface TerminalSessionRelocationControl {
372
410
  readonly targetNodeRid: string;
373
- readonly request: ServiceSessionRelocationSeal;
374
411
  readonly requestFingerprint: string;
375
- readonly responseFingerprint: string;
412
+ readonly response: ServiceSessionRelocationSealed;
376
413
  }
377
414
 
378
415
  /** Production host bridge from Retire inventory to remote RouteMesh owners. */
@@ -381,7 +418,6 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
381
418
  private readonly terminalTargets = new BoundedReplayMap<string, string>(
382
419
  RELOCATION_TARGET_TOMBSTONE_LIMIT
383
420
  );
384
- private readonly relocationAuthorityKeys = new Map<string, string>();
385
421
  private readonly targetPrepareOperations = new Map<string, TargetPrepareOperation>();
386
422
  private readonly pendingReplyRelays = new Map<string, PendingRelocationReplyRelay>();
387
423
  private readonly pendingSessionRelocations = new Map<string, PendingSessionRelocation>();
@@ -432,6 +468,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
432
468
  * checksum integrity failure — it must not be reported as DataLost(35).
433
469
  */
434
470
  private disposed = false;
471
+ private readonly targetPreparationStop = new AbortController();
435
472
 
436
473
  constructor(private readonly options: ZLinkHostRelocationOptions) {}
437
474
 
@@ -467,6 +504,10 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
467
504
  else reject(failure.cause);
468
505
  };
469
506
  });
507
+ let acceptRelay!: () => void;
508
+ const relayReady = new Promise<void>((resolve) => {
509
+ acceptRelay = resolve;
510
+ });
470
511
  this.sourceActorJoinProfiles.set(input.relocationId, {
471
512
  actor: input.actor,
472
513
  state: input.state,
@@ -481,43 +522,53 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
481
522
  : RECREATE_RELOCATION_CONTENT_TYPE,
482
523
  completionOperationId: input.completionOperationId,
483
524
  canonicalRecovery: input.canonicalRecovery,
525
+ relayReady: acceptRelay,
484
526
  ready,
485
527
  resolveReady
486
528
  });
487
529
  const membershipEpoch =
488
530
  input.state.spotMembershipEpoch > 0n ? input.state.spotMembershipEpoch : 1n;
489
531
  const spotGeneration = input.target.targetSpotGeneration ?? target.lifecycleGeneration;
490
- try {
491
- await this.relocateStandaloneActor(
492
- input.meshName,
493
- input.state,
494
- target,
495
- target.applicationVersion,
496
- input.signal,
497
- {
498
- spotId: input.target.spotId,
499
- spotKind:
500
- input.target.spotKind === ZLinkSpotKind.Entry
501
- ? ZLinkSpotKind.Entry
502
- : ZLinkSpotKind.User,
503
- spotObjectGeneration: spotGeneration
504
- },
505
- input.relocationId,
506
- true,
507
- input.advertisedReceiveChunkLimitBytes,
508
- input.deadlineAtMs
509
- );
510
- resolveReady();
511
- } catch (error) {
512
- if (error instanceof ServiceRelocationPostCommitError) {
513
- resolveReady(error);
514
- this.completeActorJoinSourceCleanup(input.state.actorId);
515
- } else {
516
- this.sourceActorJoinProfiles.delete(input.relocationId);
517
- throw error;
532
+ const cleanup = (async () => {
533
+ try {
534
+ await this.relocateStandaloneActor(
535
+ input.meshName,
536
+ input.state,
537
+ target,
538
+ target.applicationVersion,
539
+ input.signal,
540
+ {
541
+ ...input.target,
542
+ spotKind:
543
+ input.target.spotKind === ZLinkSpotKind.Entry
544
+ ? ZLinkSpotKind.Entry
545
+ : ZLinkSpotKind.User,
546
+ spotObjectGeneration: spotGeneration
547
+ },
548
+ input.relocationId,
549
+ true,
550
+ input.advertisedReceiveChunkLimitBytes,
551
+ input.deadlineAtMs
552
+ );
553
+ resolveReady();
554
+ } catch (error) {
555
+ if (error instanceof ServiceRelocationPostCommitError) {
556
+ resolveReady(error);
557
+ this.completeActorJoinSourceCleanup(input.state.actorId);
558
+ } else {
559
+ this.sourceActorJoinProfiles.delete(input.relocationId);
560
+ resolveReady();
561
+ throw error;
562
+ }
518
563
  }
519
- }
564
+ })();
565
+ const settledCleanup = Promise.allSettled([cleanup]);
566
+ await Promise.race([relayReady, cleanup]);
520
567
  return {
568
+ sourceCleanup: async () => {
569
+ const [outcome] = await settledCleanup;
570
+ if (outcome.status === 'rejected') throw outcome.reason;
571
+ },
521
572
  actorRef: {
522
573
  actorId: sourceRef.actorId,
523
574
  generation: sourceRef.generation,
@@ -550,8 +601,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
550
601
  const hadSourceWindows = this.sourceCutoverWindows.size !== 0;
551
602
  this.sourceCutoverWindows.clear();
552
603
  if (hadSourceWindows) this.options.sourceQuiescenceChanged?.();
553
- const stopped = new Error('Relocation runtime stopped.');
554
- stopped.name = 'AbortError';
604
+ const stopped = new ServiceRelocationRuntimeStoppedError();
555
605
  this.targetPrepareOperations.clear();
556
606
  for (const pending of this.pendingReplyRelays.values()) {
557
607
  if (pending.timer !== undefined) clearTimeout(pending.timer);
@@ -566,16 +616,27 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
566
616
  this.pendingSessionRelocations.clear();
567
617
  this.terminalSessionRelocations.clear();
568
618
  this.failedSessionRouteSubmits.clear();
569
- this.sourceActorJoinProfiles.clear();
619
+ await this.drainActorJoinSources();
570
620
  this.terminalActorJoinSourceLeaves.clear();
571
- this.options.boundSessionRelocation?.clear?.();
572
- this.relocationAuthorityKeys.clear();
621
+ await this.options.boundSessionRelocation?.clear?.();
573
622
  if (errors.length === 1) throw errors[0];
574
623
  if (errors.length > 1) throw new AggregateError(errors, 'Relocation runtime stop failed.');
575
624
  }
576
625
 
577
626
  /** Host shutdown waits for the existing attempt terminal before releasing its lease. */
578
627
  async drainTargetAttempts(): Promise<void> {
628
+ this.targetPreparationStop.abort(
629
+ new ZLinkFrameworkException(
630
+ ZLinkFrameworkErrorKind.ShuttingDown,
631
+ 'Relocation target is shutting down.'
632
+ )
633
+ );
634
+ for (const pending of this.targetAssemblies.values()) {
635
+ pending.assembly.fail('Relocation target is shutting down.');
636
+ }
637
+ await Promise.allSettled(
638
+ [...this.targetPrepareOperations.values()].map((value) => value.promise)
639
+ );
579
640
  const results = await Promise.allSettled(
580
641
  [...this.targetStages.entries()].map(([stagingId, stage]) => {
581
642
  if (stage.fallback !== undefined) clearTimeout(stage.fallback);
@@ -603,27 +664,58 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
603
664
 
604
665
  /** Spec 28 §4.4: the target owner alone orders shutdown seal and verified cutover. */
605
666
  private targetStageSealed(stage: LocalStage): boolean {
606
- return (
607
- !stage.cutoverReceived && (this.disposed || this.options.targetAdmissionSealed?.() === true)
608
- );
667
+ return !stage.cutoverReceived && this.targetAdmissionSealed();
668
+ }
669
+
670
+ /** The Shutdown admission seal: closed once the target owner starts shutdown. */
671
+ private targetAdmissionSealed(): boolean {
672
+ return this.disposed || this.options.targetAdmissionSealed?.() === true;
609
673
  }
610
674
 
611
675
  completeActorJoinSourceCleanup(actorId: string): void {
612
676
  for (const [relocationId, profile] of this.sourceActorJoinProfiles) {
613
677
  if (profile.state.actorId !== actorId) continue;
614
- this.sourceActorJoinProfiles.delete(relocationId);
615
- this.terminalActorJoinSourceLeaves.remember(relocationId, actorId);
616
- void this.finalizeActorJoinSource(profile);
678
+ void this.retireActorJoinSource(relocationId, profile);
617
679
  }
618
680
  }
619
681
 
620
- private async finalizeActorJoinSource(profile: SourceActorJoinProfile): Promise<void> {
682
+ /** Drains the exact source scopes before their Spot lifecycle owners stop. */
683
+ async drainActorJoinSources(meshName?: string): Promise<void> {
684
+ await Promise.all(
685
+ [...this.sourceActorJoinProfiles].map(([relocationId, profile]) =>
686
+ meshName === undefined || profile.state.meshName === meshName
687
+ ? this.retireActorJoinSource(relocationId, profile)
688
+ : undefined
689
+ )
690
+ );
691
+ }
692
+
693
+ private retireActorJoinSource(
694
+ relocationId: string,
695
+ profile: SourceActorJoinProfile
696
+ ): Promise<void> {
697
+ return (profile.retirement ??= this.finalizeActorJoinSource(profile, relocationId).finally(
698
+ () => {
699
+ if (this.sourceActorJoinProfiles.get(relocationId) === profile) {
700
+ this.sourceActorJoinProfiles.delete(relocationId);
701
+ this.terminalActorJoinSourceLeaves.remember(relocationId, profile.state.actorId);
702
+ }
703
+ }
704
+ ));
705
+ }
706
+
707
+ private async finalizeActorJoinSource(
708
+ profile: SourceActorJoinProfile,
709
+ relocationId: string
710
+ ): Promise<void> {
621
711
  const failures: unknown[] = [];
622
712
  try {
623
713
  await profile.ready;
624
714
  } catch (error) {
625
715
  failures.push(error);
626
716
  }
717
+ // A pre-commit failure removed this attempt and restored the source.
718
+ if (this.sourceActorJoinProfiles.get(relocationId) !== profile) return;
627
719
  try {
628
720
  await this.options.actorTransfer.completeRelocationSourceLeave(
629
721
  profile.actor,
@@ -635,7 +727,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
635
727
  try {
636
728
  await this.options
637
729
  .actorManager()
638
- ?.completeRelocationSource(profile.state.actorId, profile.sourceActorRef);
730
+ ?.completeCoreRelocationSource(profile.state.actorId, profile.state);
639
731
  } catch (error) {
640
732
  failures.push(error);
641
733
  }
@@ -699,6 +791,11 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
699
791
  id: `actor:${state.actorId}`,
700
792
  relocate: async (relocationSignal) => {
701
793
  await relocateShell(relocationSignal);
794
+ const spotAuthority = await requireAuthority(
795
+ this.requireLocationStore(),
796
+ encodeAuthorityKey('user_spot', String(activation.spotId)),
797
+ relocationSignal
798
+ );
702
799
  await this.relocateStandaloneActor(
703
800
  meshName,
704
801
  state,
@@ -706,15 +803,14 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
706
803
  targetApplicationVersion,
707
804
  relocationSignal,
708
805
  {
709
- spotId: activation.spotId,
710
- spotKind: ZLinkSpotKind.User,
711
- spotObjectGeneration: (
712
- await requireAuthority(
713
- this.requireLocationStore(),
714
- encodeAuthorityKey('user_spot', String(activation.spotId)),
715
- relocationSignal
716
- )
717
- ).objectGeneration
806
+ ...committedSpotRoute(
807
+ meshName,
808
+ activation.spotId,
809
+ ZLinkSpotKind.User,
810
+ spotAuthority.allocation.descriptor.rid,
811
+ spotAuthority
812
+ ),
813
+ spotObjectGeneration: spotAuthority.objectGeneration
718
814
  }
719
815
  );
720
816
  }
@@ -822,10 +918,9 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
822
918
  const payload = record.parts[0]!.data();
823
919
  const command = serviceRelocationControlCommand(payload);
824
920
  if (command === undefined) {
825
- if (record.kind !== ReceiveKind.NodeSend) return false;
921
+ if (record.kind !== ReceiveKind.NodeSend || record.parts.length !== 1) return false;
826
922
  const sourceLeave = decodeRemoteActorSourceLeaveTerminal(payload);
827
923
  if (sourceLeave === undefined) return false;
828
- if (record.parts.length !== 1) return false;
829
924
  return this.acceptActorJoinSourceLeave(sourceLeave);
830
925
  }
831
926
  return this.handleControl(meshName, record, payload, command, signal);
@@ -862,18 +957,52 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
862
957
  return true;
863
958
  }
864
959
  if (command === M6bServiceWireCommand.sessionRelocationSeal) {
865
- const request = decodeSessionRelocationSeal(payload);
866
- const response = await this.handleSessionRelocationSeal(
867
- meshName,
868
- request,
869
- record.sourceNodeRid,
870
- signal
871
- );
872
- await this.sendSessionRelocationResponse(
873
- meshName,
874
- record.sourceNodeRid,
875
- encodeSessionRelocationSealed(response)
876
- );
960
+ let request: ServiceSessionRelocationSeal;
961
+ try {
962
+ request = decodeSessionRelocationSeal(payload);
963
+ } catch (error) {
964
+ const outcome = ZLinkMessageFlowOutcome.Error;
965
+ flowIfEnabled(this.options.messageFlow?.(), outcome)?.trace({
966
+ outcome,
967
+ surface: ZLinkDispatchErrorSurface.StreamSession,
968
+ messageKind: ZLinkDispatchMessageKind.Control,
969
+ packetName: 'sessionRelocationSeal',
970
+ sourceRid: record.sourceNodeRid ?? undefined,
971
+ errorReason: 'invalid_frame',
972
+ errorAction: ZLinkDispatchErrorAction.Drop,
973
+ ...dispatchErrorDetails(error)
974
+ });
975
+ throw error;
976
+ }
977
+ let response: ServiceSessionRelocationSealed | undefined;
978
+ try {
979
+ response = await this.handleSessionRelocationSeal(
980
+ meshName,
981
+ request,
982
+ record.sourceNodeRid,
983
+ signal
984
+ );
985
+ } catch (error) {
986
+ traceSessionBindingControlDrop(
987
+ this.options.messageFlow?.(),
988
+ record.sourceNodeRid ?? '',
989
+ request.actor.actor.actorId,
990
+ 'sessionRelocationSeal',
991
+ error instanceof ZLinkFrameworkException &&
992
+ error.kind === ZLinkFrameworkErrorKind.Unavailable
993
+ ? 'location_unavailable'
994
+ : 'stale_target',
995
+ error
996
+ );
997
+ throw error;
998
+ }
999
+ if (response !== undefined) {
1000
+ await this.sendSessionRelocationResponse(
1001
+ meshName,
1002
+ record.sourceNodeRid,
1003
+ encodeSessionRelocationSealed(response)
1004
+ );
1005
+ }
877
1006
  return true;
878
1007
  }
879
1008
  if (command === M6bServiceWireCommand.sessionRelocationRoute) {
@@ -918,7 +1047,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
918
1047
  this.targetAssemblies.get(operationKey)?.fingerprint ??
919
1048
  this.targetStages.get(operationKey)?.offer.prepareFingerprint ??
920
1049
  this.terminalTargets.get(operationKey);
921
- if (request.sourceNodeRid !== String(record.sourceNodeRid)) return true;
1050
+ if (!routingIdsEqual(request.sourceNodeRid, record.sourceNodeRid)) return true;
922
1051
  if (acceptedFingerprint !== undefined && acceptedFingerprint !== fingerprint) {
923
1052
  if (!samePrepareIdentity(acceptedFingerprint, request)) return true;
924
1053
  this.replyPrepare(
@@ -948,7 +1077,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
948
1077
  // finish the restore and the Ready reply asynchronously.
949
1078
  let promise: Promise<ServiceMaintenanceRelocationReady>;
950
1079
  try {
951
- this.registerTargetAssembly(operationKey, request, String(record.sourceNodeRid));
1080
+ this.registerTargetAssembly(operationKey, request, record.sourceNodeRid);
952
1081
  promise = this.handlePrepareControl(
953
1082
  meshName,
954
1083
  operationKey,
@@ -1083,7 +1212,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1083
1212
  if (
1084
1213
  pending === undefined ||
1085
1214
  sourceNodeRid === null ||
1086
- String(sourceNodeRid) !== pending.authenticatedSourceNodeRid ||
1215
+ !routingIdsEqual(sourceNodeRid, pending.authenticatedSourceNodeRid) ||
1087
1216
  request.senderRole !== 'source' ||
1088
1217
  !sameCoordinator(request.coordinator, pending.prepare.coordinator) ||
1089
1218
  !sameRelocationObject(request.object, pending.prepare.object)
@@ -1162,7 +1291,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1162
1291
  // to itself; RouteMesh has no self connection (NotConnected), so the
1163
1292
  // control dispatches through the same inbound handler locally.
1164
1293
  const localRid = this.requireMeshNode(meshName).status().routingId;
1165
- if (String(localRid) === String(targetNodeRid)) {
1294
+ if (routingIdsEqual(localRid, targetNodeRid)) {
1166
1295
  await this.handleSessionRelocationRoute(meshName, request, localRid, signal);
1167
1296
  return;
1168
1297
  }
@@ -1191,7 +1320,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1191
1320
  const key = sessionRelocationPendingKey(request);
1192
1321
  const bytes = encodeSessionRelocationSeal(request);
1193
1322
  const requestFingerprint = bytes.toString('base64');
1194
- const expectedTarget = String(targetNodeRid);
1323
+ const expectedTarget = encodeRoutingIdStorageHex(targetNodeRid);
1195
1324
  const terminal = this.terminalSessionRelocations.get(key);
1196
1325
  if (terminal !== undefined) {
1197
1326
  if (
@@ -1205,6 +1334,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1205
1334
  );
1206
1335
  }
1207
1336
  this.terminalSessionRelocations.touch(key);
1337
+ return Promise.resolve(terminal.response);
1208
1338
  }
1209
1339
  const existing = this.pendingSessionRelocations.get(key);
1210
1340
  if (existing !== undefined) {
@@ -1268,7 +1398,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1268
1398
  // connection (NotConnected); dispatch through the inbound handler
1269
1399
  // locally instead of the wire.
1270
1400
  const localRid = this.requireMeshNode(meshName).status().routingId;
1271
- if (String(localRid) === expectedTarget) {
1401
+ if (encodeRoutingIdStorageHex(localRid) === expectedTarget) {
1272
1402
  try {
1273
1403
  const response = await this.handleSessionRelocationSeal(
1274
1404
  meshName,
@@ -1276,7 +1406,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1276
1406
  localRid,
1277
1407
  signal
1278
1408
  );
1279
- pending.resolve(response);
1409
+ if (response !== undefined) pending.resolve(response);
1280
1410
  } catch (error) {
1281
1411
  pending.reject(error);
1282
1412
  }
@@ -1327,9 +1457,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1327
1457
  finish(() => {
1328
1458
  this.terminalSessionRelocations.remember(key, {
1329
1459
  targetNodeRid: expectedTarget,
1330
- request,
1331
1460
  requestFingerprint,
1332
- responseFingerprint: sessionRelocationResponseFingerprint(response)
1461
+ response
1333
1462
  });
1334
1463
  resolvePromise(response);
1335
1464
  }),
@@ -1375,19 +1504,14 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1375
1504
  if (pending === undefined) {
1376
1505
  const terminal = this.terminalSessionRelocations.get(key);
1377
1506
  if (terminal === undefined) return;
1378
- if (sourceNodeRid === null || String(sourceNodeRid) !== terminal.targetNodeRid) {
1379
- throw new ServiceWireProtocolError(`Session relocation ACK '${key}' source node changed.`);
1380
- }
1381
- validateSessionRelocationResponse(terminal.request, response);
1382
- if (sessionRelocationResponseFingerprint(response) !== terminal.responseFingerprint) {
1383
- throw new ServiceWireProtocolError(
1384
- `Session relocation ACK '${key}' repeated with different bytes.`
1385
- );
1386
- }
1507
+ // Session–Actor binding §8.3: the first command 43 is final for this participant.
1387
1508
  this.terminalSessionRelocations.touch(key);
1388
1509
  return;
1389
1510
  }
1390
- if (sourceNodeRid === null || String(sourceNodeRid) !== pending.targetNodeRid) {
1511
+ if (
1512
+ sourceNodeRid === null ||
1513
+ encodeRoutingIdStorageHex(sourceNodeRid) !== pending.targetNodeRid
1514
+ ) {
1391
1515
  pending.reject(
1392
1516
  new ServiceWireProtocolError(`Session relocation ACK '${key}' source node changed.`)
1393
1517
  );
@@ -1411,12 +1535,12 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1411
1535
  if (sourceNodeRid === null) return undefined;
1412
1536
  const node = this.requireMeshNode(meshName);
1413
1537
  const status = node.status();
1414
- if (String(status.routingId) === String(sourceNodeRid)) {
1538
+ if (routingIdsEqual(status.routingId, sourceNodeRid)) {
1415
1539
  return status.lifecycleGeneration;
1416
1540
  }
1417
1541
  return node
1418
1542
  .peers()
1419
- .find((peer) => peer.routingId !== null && String(peer.routingId) === String(sourceNodeRid))
1543
+ .find((peer) => peer.routingId !== null && routingIdsEqual(peer.routingId, sourceNodeRid))
1420
1544
  ?.lifecycleGeneration;
1421
1545
  }
1422
1546
 
@@ -1425,11 +1549,11 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1425
1549
  request: ServiceSessionRelocationSeal,
1426
1550
  sourceNodeRid: RoutingId | null,
1427
1551
  signal?: AbortSignal
1428
- ): Promise<ServiceSessionRelocationSealed> {
1552
+ ): Promise<ServiceSessionRelocationSealed | undefined> {
1429
1553
  if (
1430
1554
  sourceNodeRid === null ||
1431
- String(sourceNodeRid) !== request.coordinator.nodeRid ||
1432
- String(sourceNodeRid) !== request.actor.actor.nodeRid
1555
+ !routingIdsEqual(sourceNodeRid, request.coordinator.nodeRid) ||
1556
+ !routingIdsEqual(sourceNodeRid, request.actor.actor.nodeRid)
1433
1557
  ) {
1434
1558
  throw new ServiceWireProtocolError(
1435
1559
  'Session relocation seal source does not match its coordinator and Actor fence.'
@@ -1449,7 +1573,19 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1449
1573
  if (handler === undefined) {
1450
1574
  throw new Error('Session relocation seal ingress is not configured.');
1451
1575
  }
1452
- return await handler(request, signal);
1576
+ return await handler(request, signal, () => {
1577
+ const node = this.requireMeshNode(meshName);
1578
+ if (
1579
+ routingIdsEqual(node.status().routingId, request.targetNodeRid) ||
1580
+ node.isPeerRouteReady?.(request.targetNodeRid, request.targetNodeGeneration) === true
1581
+ )
1582
+ return;
1583
+ void this.options
1584
+ .requestPeerReconcile?.(meshName, request.targetNodeRid)
1585
+ .catch((error) =>
1586
+ console.error('[zlink.runtime.relocation.session_route_reconcile_failed]', error)
1587
+ );
1588
+ });
1453
1589
  }
1454
1590
 
1455
1591
  private async handleSessionRelocationRoute(
@@ -1461,7 +1597,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1461
1597
  this.validateSessionOwnerFence(meshName, request.session);
1462
1598
  const expectedSource =
1463
1599
  request.route.action === 'commit' ? request.route.targetNodeRid : request.coordinator.nodeRid;
1464
- if (sourceNodeRid === null || String(sourceNodeRid) !== expectedSource) {
1600
+ if (sourceNodeRid === null || !routingIdsEqual(sourceNodeRid, expectedSource)) {
1465
1601
  throw new ServiceWireProtocolError(
1466
1602
  'Session relocation route source does not match its action fence.'
1467
1603
  );
@@ -1489,10 +1625,17 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1489
1625
  ): void {
1490
1626
  const status = this.requireMeshNode(meshName).status();
1491
1627
  const owner = this.options.currentOwner();
1628
+ // Session-Actor binding §4: without this host's own descriptor the command is Unavailable.
1629
+ if (owner === undefined) {
1630
+ throw createInternalFrameworkException(
1631
+ ZLinkFrameworkInternalErrorKind.RouteNotConnected,
1632
+ 'Session owner has no live MeshNode descriptor.'
1633
+ );
1634
+ }
1492
1635
  if (
1493
1636
  !routingIdsEqual(status.routingId, session.sessionOwnerNodeRid) ||
1494
1637
  status.lifecycleGeneration !== session.sessionOwnerNodeGeneration ||
1495
- owner?.ownerId !== session.sessionOwnerId ||
1638
+ owner.ownerId !== session.sessionOwnerId ||
1496
1639
  owner.leaseGeneration !== session.sessionOwnerLeaseGeneration
1497
1640
  ) {
1498
1641
  throw new ServiceWireProtocolError(
@@ -1569,7 +1712,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1569
1712
  );
1570
1713
  target ??= await this.selectTarget(
1571
1714
  meshName,
1572
- String(this.requireMeshNode(meshName).status().routingId),
1715
+ this.requireMeshNode(meshName).status().routingId,
1573
1716
  targetApplicationVersion,
1574
1717
  relocationTargetRequirements([
1575
1718
  {
@@ -1601,17 +1744,9 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1601
1744
  objectGeneration: spotAuthority.objectGeneration,
1602
1745
  authorityOwnerGeneration: spotAuthority.authorityOwnerGeneration,
1603
1746
  seal: async (captureSignal) => {
1604
- const { spotCaptureOperation } = await this.afterSpotCloseDecision(
1605
- meshName,
1606
- activation.spotId,
1607
- () => {
1608
- interruptionStartedAt = performance.now();
1609
- spotMessageFollowSeal = this.sealSpotMessageFollow(meshName, spotAuthority, activation);
1610
- // Both seals run in the same event-loop turn after the Close decision.
1611
- return { spotCaptureOperation: activation.captureRelocation(captureSignal) };
1612
- }
1613
- );
1614
- const preparedSessions = Promise.all(
1747
+ // Relocation 04 §4.1: every bound member's Session is sealed before
1748
+ // the Spot capture stops member dispatch.
1749
+ const preparations = await Promise.allSettled(
1615
1750
  actorStates.map(async (state) => ({
1616
1751
  state,
1617
1752
  actor: state.actor!,
@@ -1620,27 +1755,40 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1620
1755
  state,
1621
1756
  captureSignal,
1622
1757
  false,
1623
- relocationWireId(aggregateId)
1758
+ relocationWireId(aggregateId),
1759
+ target
1624
1760
  )
1625
1761
  }))
1626
1762
  );
1627
- const preparedSessionsOutcome = preparedSessions.then(
1628
- (value) => ({ value }),
1629
- (error) => ({ error })
1763
+ const failures: unknown[] = [];
1764
+ for (const preparation of preparations) {
1765
+ if (preparation.status === 'fulfilled') sessions.push(preparation.value);
1766
+ else failures.push(preparation.reason);
1767
+ }
1768
+ if (failures.length === 1) throw failures[0];
1769
+ if (failures.length > 1) {
1770
+ throw new AggregateError(failures, 'Spot relocation source preparation failed.');
1771
+ }
1772
+ const { spotCaptureOperation } = await this.afterSpotCloseDecision(
1773
+ meshName,
1774
+ activation.spotId,
1775
+ () => {
1776
+ interruptionStartedAt = performance.now();
1777
+ spotMessageFollowSeal = this.sealSpotMessageFollow(meshName, spotAuthority, activation);
1778
+ // Both Spot seals run in the same event-loop turn after the Close decision.
1779
+ return { spotCaptureOperation: activation.captureRelocation(captureSignal) };
1780
+ }
1630
1781
  );
1631
1782
  spotCapture = await spotCaptureOperation;
1632
- const preparation = await preparedSessionsOutcome;
1633
- if ('error' in preparation) throw preparation.error;
1634
- sessions.push(...preparation.value);
1635
1783
  return {
1636
1784
  boundSessionState: Buffer.alloc(0),
1637
1785
  queuedMessages: [],
1638
- timers: spotCapture.timers.map(toServiceTimer)
1786
+ timers: spotCapture!.timers.map(toServiceTimer)
1639
1787
  };
1640
1788
  },
1641
1789
  captureApplicationState: (captureSignal) =>
1642
1790
  this.captureApplication(spotRegistration.relocation, activation.spot, captureSignal),
1643
- commitSeal: async () => {
1791
+ commitSeal: async (followAuthorities) => {
1644
1792
  const failures: unknown[] = [];
1645
1793
  try {
1646
1794
  if (!spotSealCommitted) {
@@ -1653,9 +1801,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1653
1801
  await this.commitSpotMessageFollow(
1654
1802
  meshName,
1655
1803
  spotMessageFollowSeal,
1656
- spotKey,
1657
- activation,
1658
- target
1804
+ followAuthorities.get(spotKey.value),
1805
+ activation
1659
1806
  );
1660
1807
  }
1661
1808
  } catch (error) {
@@ -1663,10 +1810,10 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1663
1810
  }
1664
1811
  for (const session of sessions) {
1665
1812
  try {
1666
- const committedAuthority = await requireAuthority(
1667
- this.requireLocationStore(),
1668
- encodeAuthorityKey('actor', session.state.actorId)
1813
+ const committedAuthority = followAuthorities.get(
1814
+ encodeAuthorityKey('actor', session.state.actorId).value
1669
1815
  );
1816
+ const committedSpot = followAuthorities.get(spotKey.value);
1670
1817
  const targetActorRef = {
1671
1818
  actorId: session.state.actorId,
1672
1819
  objectGeneration: actorAuthorities.get(session.state.actorId)!.objectGeneration,
@@ -1674,15 +1821,21 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1674
1821
  nodeRid: target.rid
1675
1822
  };
1676
1823
  await session.prepared.commit(
1677
- {
1678
- routerChannelId: meshName,
1679
- targetNodeRid: target.rid,
1680
- spotId: activation.spotId,
1681
- spotKind: kind === 'user_spot' ? ZLinkSpotKind.User : ZLinkSpotKind.Instance,
1682
- authorityOwnerGeneration: committedAuthority.authorityOwnerGeneration
1683
- } as never,
1824
+ committedSpotRoute(
1825
+ meshName,
1826
+ activation.spotId,
1827
+ kind === 'user_spot' ? ZLinkSpotKind.User : ZLinkSpotKind.Instance,
1828
+ target.rid,
1829
+ committedSpot
1830
+ ),
1684
1831
  targetActorRef,
1685
- committedActorOwnerFence(session.state.actorId, targetActorRef, committedAuthority)
1832
+ committedAuthority === undefined || committedSpot === undefined
1833
+ ? null
1834
+ : committedActorOwnerFence(
1835
+ session.state.actorId,
1836
+ targetActorRef,
1837
+ committedAuthority
1838
+ )
1686
1839
  );
1687
1840
  } catch (error) {
1688
1841
  failures.push(error);
@@ -1711,6 +1864,16 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1711
1864
  abortSeal: async () => {
1712
1865
  const failures: unknown[] = [];
1713
1866
  if (spotCapture !== undefined) {
1867
+ // Spot model §5.1: Continued completes while the source seal still holds jobs.
1868
+ if (readinessBoundaryConsumed) {
1869
+ try {
1870
+ await activation.completeConsumedRelocationBoundary(
1871
+ ZLinkSpotRelocationReadyOutcome.Continued
1872
+ );
1873
+ } catch (error) {
1874
+ failures.push(error);
1875
+ }
1876
+ }
1714
1877
  try {
1715
1878
  activation.abortRelocation(spotCapture);
1716
1879
  } catch (error) {
@@ -1759,7 +1922,10 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1759
1922
  spotObjectGeneration: spotAuthority.objectGeneration,
1760
1923
  membershipEpoch: state.spotMembershipEpoch > 0n ? state.spotMembershipEpoch : 1n
1761
1924
  }));
1762
- let outcome = 'completed';
1925
+ const relocationMetric =
1926
+ spotRegistration.relocation.kind === 'disabled'
1927
+ ? undefined
1928
+ : this.options.metrics?.startRelocation(meshName, kind, spotRegistration.relocation.kind);
1763
1929
  try {
1764
1930
  const waitForRelocationBoundary = (
1765
1931
  activation as unknown as {
@@ -1808,10 +1974,12 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1808
1974
  )
1809
1975
  ]),
1810
1976
  signal,
1811
- sessions
1977
+ sessions,
1978
+ undefined,
1979
+ undefined,
1980
+ relocationMetric
1812
1981
  );
1813
1982
  } catch (error) {
1814
- outcome = 'failed';
1815
1983
  if (
1816
1984
  readinessBoundaryConsumed &&
1817
1985
  !(error instanceof ServiceRelocationPostCommitError) &&
@@ -1823,14 +1991,14 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1823
1991
  }
1824
1992
  throw error;
1825
1993
  } finally {
1994
+ this.completeSourceRelocationMetric(relocationMetric);
1826
1995
  this.sourceRelocationIds.delete(aggregateId);
1827
1996
  if (interruptionStartedAt !== undefined) {
1828
1997
  this.recordRelocationInterruption(
1829
1998
  meshName,
1830
1999
  kind,
1831
2000
  kind === 'user_spot' ? 'spot_wide' : undefined,
1832
- interruptionStartedAt,
1833
- outcome
2001
+ interruptionStartedAt
1834
2002
  );
1835
2003
  }
1836
2004
  }
@@ -1842,8 +2010,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1842
2010
  target: ZLinkMeshNodeDescriptor | undefined,
1843
2011
  targetApplicationVersion: bigint | undefined,
1844
2012
  signal?: AbortSignal,
1845
- targetMembership?: {
1846
- readonly spotId: RoutingId;
2013
+ targetMembership?: ZLinkSpotRouteTarget & {
1847
2014
  readonly spotKind: ZLinkSpotKind.Entry | ZLinkSpotKind.User;
1848
2015
  readonly spotObjectGeneration: bigint;
1849
2016
  },
@@ -1861,18 +2028,19 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1861
2028
  encodeAuthorityKey('actor', state.actorId),
1862
2029
  signal
1863
2030
  );
2031
+ const actorPolicy = this.actorRegistration(
2032
+ state.meshName ?? meshName,
2033
+ authority.allocation.stableType
2034
+ ).relocation.kind;
1864
2035
  target ??= await this.selectTarget(
1865
2036
  meshName,
1866
- String(this.requireMeshNode(meshName).status().routingId),
2037
+ this.requireMeshNode(meshName).status().routingId,
1867
2038
  targetApplicationVersion,
1868
2039
  [
1869
2040
  {
1870
2041
  objectKind: 'actor',
1871
2042
  stableType: authority.allocation.stableType,
1872
- policy: this.actorRegistration(
1873
- state.meshName ?? meshName,
1874
- authority.allocation.stableType
1875
- ).relocation.kind,
2043
+ policy: actorPolicy,
1876
2044
  count: 1
1877
2045
  }
1878
2046
  ],
@@ -1889,10 +2057,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1889
2057
  true,
1890
2058
  target,
1891
2059
  meshName,
1892
- {
1893
- spotId: membershipSpotId,
1894
- spotKind: targetMembership?.spotKind ?? ZLinkSpotKind.Entry
1895
- },
2060
+ targetMembership,
1896
2061
  relocationWireId(aggregateId),
1897
2062
  retainSourceForActorJoin
1898
2063
  );
@@ -1910,7 +2075,10 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1910
2075
  return unit.seal(captureSignal);
1911
2076
  }
1912
2077
  };
1913
- let outcome = 'completed';
2078
+ const relocationMetric =
2079
+ actorPolicy === 'disabled'
2080
+ ? undefined
2081
+ : this.options.metrics?.startRelocation(meshName, 'actor', actorPolicy);
1914
2082
  try {
1915
2083
  if (isRelocationDebugEnabled()) {
1916
2084
  relocationDebug('standalone_actor.wait_budget', {
@@ -1941,21 +2109,14 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1941
2109
  signal,
1942
2110
  sessions,
1943
2111
  advertisedReceiveChunkLimitBytes,
1944
- deadlineAtMs
2112
+ deadlineAtMs,
2113
+ relocationMetric
1945
2114
  );
1946
- } catch (error) {
1947
- outcome = 'failed';
1948
- throw error;
1949
2115
  } finally {
2116
+ this.completeSourceRelocationMetric(relocationMetric);
1950
2117
  this.sourceRelocationIds.delete(aggregateId);
1951
2118
  if (interruptionStartedAt !== undefined) {
1952
- this.recordRelocationInterruption(
1953
- meshName,
1954
- 'actor',
1955
- undefined,
1956
- interruptionStartedAt,
1957
- outcome
1958
- );
2119
+ this.recordRelocationInterruption(meshName, 'actor', undefined, interruptionStartedAt);
1959
2120
  }
1960
2121
  }
1961
2122
  }
@@ -1994,7 +2155,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1994
2155
  );
1995
2156
  target ??= await this.selectTarget(
1996
2157
  meshName,
1997
- String(this.requireMeshNode(meshName).status().routingId),
2158
+ this.requireMeshNode(meshName).status().routingId,
1998
2159
  targetApplicationVersion,
1999
2160
  [
2000
2161
  {
@@ -2036,7 +2197,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2036
2197
  // PerActor User Spots are stateless shells. Application state and the
2037
2198
  // Spot relocation adapter are intentionally not part of this unit.
2038
2199
  captureApplicationState: async () => Buffer.alloc(0),
2039
- commitSeal: async () => {
2200
+ commitSeal: async (followAuthorities) => {
2040
2201
  if (!sealCommitted) {
2041
2202
  if (spotCapture === undefined || !(await activation.commitRelocation(spotCapture))) {
2042
2203
  throw new Error(
@@ -2049,9 +2210,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2049
2210
  await this.commitSpotMessageFollow(
2050
2211
  meshName,
2051
2212
  spotMessageFollowSeal,
2052
- spotKey,
2053
- activation,
2054
- target
2213
+ followAuthorities.get(spotKey.value),
2214
+ activation
2055
2215
  );
2056
2216
  }
2057
2217
  await this.requireSpotManager().completeRelocationSource(activation);
@@ -2068,7 +2228,14 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2068
2228
  }
2069
2229
  };
2070
2230
  const aggregateId = this.reserveRelocationId();
2071
- let outcome = 'completed';
2231
+ const relocationMetric =
2232
+ spotRegistration.relocation.kind === 'disabled'
2233
+ ? undefined
2234
+ : this.options.metrics?.startRelocation(
2235
+ meshName,
2236
+ 'user_spot',
2237
+ spotRegistration.relocation.kind
2238
+ );
2072
2239
  try {
2073
2240
  await this.waitForRelocationBudgetHeadroom(String(target.rid), signal);
2074
2241
  const captured = await new ServiceRelocationObjectCaptureOwner().captureUserSpotAggregate(
@@ -2086,20 +2253,20 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2086
2253
  captured,
2087
2254
  new Map([[spotKey.value, spotAuthority]]),
2088
2255
  signal,
2089
- []
2256
+ [],
2257
+ undefined,
2258
+ undefined,
2259
+ relocationMetric
2090
2260
  );
2091
- } catch (error) {
2092
- outcome = 'failed';
2093
- throw error;
2094
2261
  } finally {
2262
+ this.completeSourceRelocationMetric(relocationMetric);
2095
2263
  this.sourceRelocationIds.delete(aggregateId);
2096
2264
  if (interruptionStartedAt !== undefined) {
2097
2265
  this.recordRelocationInterruption(
2098
2266
  meshName,
2099
2267
  'user_spot',
2100
2268
  'per_actor',
2101
- interruptionStartedAt,
2102
- outcome
2269
+ interruptionStartedAt
2103
2270
  );
2104
2271
  }
2105
2272
  }
@@ -2112,10 +2279,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2112
2279
  standalone = false,
2113
2280
  target?: ZLinkMeshNodeDescriptor,
2114
2281
  meshName?: string,
2115
- targetMembership?: {
2116
- readonly spotId: RoutingId;
2117
- readonly spotKind: ZLinkSpotKind.Entry | ZLinkSpotKind.User;
2118
- },
2282
+ targetMembership?: ZLinkSpotRouteTarget,
2119
2283
  sessionRelocation?: ServiceWireOperationId,
2120
2284
  retainSourceForActorJoin = false
2121
2285
  ): ServiceRelocationCaptureUnit {
@@ -2140,7 +2304,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2140
2304
  state,
2141
2305
  signal,
2142
2306
  true,
2143
- sessionRelocation
2307
+ sessionRelocation,
2308
+ target
2144
2309
  )
2145
2310
  };
2146
2311
  sessions.push(ownSession);
@@ -2180,7 +2345,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2180
2345
  // The sealed Session identity is part of the canonical participant vector.
2181
2346
  captureApplicationState: (signal) =>
2182
2347
  this.captureApplication(registration.relocation, state.actor!, signal),
2183
- commitSeal: async () => {
2348
+ commitSeal: async (followAuthorities) => {
2184
2349
  if (
2185
2350
  !standalone ||
2186
2351
  ownSession === undefined ||
@@ -2190,11 +2355,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2190
2355
  return;
2191
2356
  const failures: unknown[] = [];
2192
2357
  try {
2193
- const membershipSpotId =
2194
- targetMembership?.spotId ?? ((target.entrySpotId ?? String(target.rid)) as RoutingId);
2195
- const committedAuthority = await requireAuthority(
2196
- this.requireLocationStore(),
2197
- encodeAuthorityKey('actor', state.actorId)
2358
+ const committedAuthority = followAuthorities.get(
2359
+ encodeAuthorityKey('actor', state.actorId).value
2198
2360
  );
2199
2361
  const targetActorRef = {
2200
2362
  actorId: state.actorId,
@@ -2203,25 +2365,28 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2203
2365
  nodeRid: target.rid
2204
2366
  };
2205
2367
  await ownSession.prepared.commit(
2206
- {
2368
+ targetMembership ?? {
2207
2369
  routerChannelId: meshName,
2208
2370
  targetNodeRid: target.rid,
2209
- spotId: membershipSpotId,
2210
- spotKind: targetMembership?.spotKind ?? ZLinkSpotKind.Entry,
2211
- authorityOwnerGeneration: committedAuthority.authorityOwnerGeneration
2212
- } as never,
2371
+ spotId: (target.entrySpotId ?? String(target.rid)) as RoutingId,
2372
+ spotKind: ZLinkSpotKind.Entry
2373
+ },
2213
2374
  targetActorRef,
2214
- committedActorOwnerFence(state.actorId, targetActorRef, committedAuthority)
2375
+ committedAuthority === undefined
2376
+ ? null
2377
+ : committedActorOwnerFence(state.actorId, targetActorRef, committedAuthority)
2215
2378
  );
2216
2379
  } catch (error) {
2217
2380
  failures.push(error);
2218
2381
  }
2219
- if (!retainSourceForActorJoin) {
2220
- try {
2382
+ try {
2383
+ if (retainSourceForActorJoin) {
2384
+ this.requireActorManager().detachRelocationSource(state);
2385
+ } else {
2221
2386
  await this.requireActorManager().completeRelocationSource(state.actorId);
2222
- } catch (error) {
2223
- failures.push(error);
2224
2387
  }
2388
+ } catch (error) {
2389
+ failures.push(error);
2225
2390
  }
2226
2391
  if (failures.length > 0)
2227
2392
  throw failures.length === 1
@@ -2272,7 +2437,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2272
2437
  sessions: readonly SourceActorSession[] = [],
2273
2438
  /** Target's advertised relocation state chunk cap (Actor Join only; spec 15 §4.2). */
2274
2439
  advertisedReceiveChunkLimitBytes?: number,
2275
- controlDeadlineAtMs = performance.now() + RELOCATION_OPERATION_TIMEOUT_MS
2440
+ controlDeadlineAtMs = performance.now() + RELOCATION_OPERATION_TIMEOUT_MS,
2441
+ relocationMetric?: ZLinkRelocationMetricOperation
2276
2442
  ): Promise<void> {
2277
2443
  if (isRelocationDebugEnabled()) {
2278
2444
  relocationDebug('coordinator.begin', {
@@ -2284,12 +2450,12 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2284
2450
  const coordinator = {
2285
2451
  ownerId: primary.ownerId,
2286
2452
  leaseGeneration: primary.ownerLeaseGeneration,
2287
- nodeRid: String(localStatus.routingId),
2453
+ nodeRid: localStatus.routingId,
2288
2454
  nodeGeneration: localStatus.lifecycleGeneration,
2289
2455
  expectedAuthorityStoreVersion: primary.storeVersion.value
2290
2456
  } satisfies ServiceWireRelocationCoordinatorFence;
2291
2457
  const targetFence = {
2292
- nodeRid: String(target.rid),
2458
+ nodeRid: target.rid,
2293
2459
  nodeGeneration: target.lifecycleGeneration,
2294
2460
  ownerId: target.ownerId,
2295
2461
  ownerLeaseGeneration: target.leaseGeneration
@@ -2302,13 +2468,6 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2302
2468
  // attempt even when it reuses the same routing id.
2303
2469
  const targetAttemptGeneration = target.lifecycleGeneration;
2304
2470
  const relayAuthorityId = wireIdText(relocationWireId(captured.envelope.aggregateId));
2305
- const relayAuthorityKey = primaryKey(captured.envelope);
2306
- if (this.relocationAuthorityKeys.has(relayAuthorityId)) {
2307
- throw new Error(
2308
- `Relocation reply relay authority '${relayAuthorityId}' is already registered.`
2309
- );
2310
- }
2311
- this.relocationAuthorityKeys.set(relayAuthorityId, relayAuthorityKey);
2312
2471
  // The captured payload stays in source memory: this copy is the only
2313
2472
  // handoff original until the cutover submit terminal and the
2314
2473
  // retransmission window end (spec 28 §4.2).
@@ -2328,6 +2487,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2328
2487
  primary
2329
2488
  );
2330
2489
  const encoded = encodeServiceRelocationEnvelope(transportEnvelope, target.applicationVersion);
2490
+ relocationMetric?.recordBytes(encoded.byteLength);
2331
2491
  const limits = this.relocationLimits();
2332
2492
  // Actor Join threads the target's admission-time advertised cap in;
2333
2493
  // other relocation paths have no such negotiation and fall back to the
@@ -2344,7 +2504,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2344
2504
  const restoreDeadlineReached = () =>
2345
2505
  signal?.aborted === true || performance.now() >= controlDeadlineAtMs;
2346
2506
  let readyReceived = false;
2347
- let settlement: 'target' | 'source' | 'lost' | undefined;
2507
+ let settlement:
2508
+ Awaited<ReturnType<ZLinkHostServiceRelocationRuntime['settleSourceAuthority']>> | undefined;
2348
2509
  try {
2349
2510
  const prepare = {
2350
2511
  kind: 'prepare',
@@ -2359,7 +2520,10 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2359
2520
  payloadTotalLength: BigInt(plan.totalLength),
2360
2521
  payloadChunkCount: plan.chunkCount,
2361
2522
  payloadChecksumCrc32c: plan.checksumCrc32c,
2362
- applicationVersion: target.applicationVersion
2523
+ applicationVersion: target.applicationVersion,
2524
+ deadlineUnixMs: BigInt(
2525
+ Date.now() + Math.max(0, Math.ceil(controlDeadlineAtMs - performance.now()))
2526
+ )
2363
2527
  } satisfies ServiceMaintenanceRelocationPrepare;
2364
2528
  // Start the Restore request first (it also registers the temporary
2365
2529
  // queue and the assembly on the target), then stream the payload chunks
@@ -2400,6 +2564,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2400
2564
  // forward the source can no longer reopen dispatch without risking two
2401
2565
  // owners.
2402
2566
  readyReceived = true;
2567
+ actorJoinProfile?.relayReady();
2403
2568
 
2404
2569
  const boundaryBatch: Array<{
2405
2570
  readonly frame: Buffer;
@@ -2480,7 +2645,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2480
2645
  primary,
2481
2646
  targetFence,
2482
2647
  restoreDeadlineReached,
2483
- window
2648
+ window,
2649
+ relocationMetric
2484
2650
  );
2485
2651
  } finally {
2486
2652
  this.closeSourceCutoverWindow(relayAuthorityId, window);
@@ -2495,11 +2661,13 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2495
2661
  // Preserve won: the captured queue, timers and held records return to
2496
2662
  // the source queue in their accepted order, and dispatch reopens.
2497
2663
  await captured.abortSource();
2664
+ this.completeSourceRelocationMetric(relocationMetric, settlement);
2498
2665
  throw new Error(
2499
2666
  `Relocation '${relayAuthorityId}' ended by the source Preserve fence; the source kept the authority.`
2500
2667
  );
2501
2668
  }
2502
2669
  if (settlement === 'lost') {
2670
+ this.completeSourceRelocationMetric(relocationMetric, settlement);
2503
2671
  // Expired owner: dispatch and Store changes stop for good and every
2504
2672
  // pending request without a terminal gets one Unavailable.
2505
2673
  const unavailable = createInternalFrameworkException(
@@ -2510,11 +2678,20 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2510
2678
  await captured.discardSource(unavailable);
2511
2679
  throw new ServiceRelocationAuthorityError(unavailable.message, { cause: unavailable });
2512
2680
  }
2681
+ this.completeSourceRelocationMetric(relocationMetric, settlement);
2513
2682
  for (const session of sessions) session.prepared.setReplayResults([]);
2514
- await captured.commitSource();
2683
+ await captured.commitSource(settlement.followAuthorities);
2515
2684
  await this.options.reconcileStatefulAuthorityRoutes?.(signal);
2516
2685
  } catch (error) {
2517
- if (settlement !== 'target') throw error;
2686
+ if (
2687
+ this.disposed &&
2688
+ (error instanceof ServiceRelocationRuntimeStoppedError ||
2689
+ (error instanceof ZLinkBackendResultError &&
2690
+ error.result ===
2691
+ (error.operation === 'request' ? RequestResult.Terminated : SubmitResult.Terminated)))
2692
+ )
2693
+ this.completeSourceRelocationMetric(relocationMetric, undefined, true);
2694
+ if (settlement === undefined || typeof settlement === 'string') throw error;
2518
2695
  // Target commit is confirmed; later source cleanup failures cannot move
2519
2696
  // the authority back (spec 28 §10).
2520
2697
  const authority = await requireAuthority(
@@ -2528,17 +2705,33 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2528
2705
  error
2529
2706
  );
2530
2707
  } finally {
2708
+ this.completeSourceRelocationMetric(relocationMetric);
2531
2709
  if (!readyReceived) {
2532
2710
  // Only an explicit failure before the Ready reply becomes accepted
2533
2711
  // restores the source from the memory payload (spec 28 §9).
2534
2712
  await captured.abortSource().catch(() => undefined);
2535
2713
  }
2536
- if (this.relocationAuthorityKeys.get(relayAuthorityId) === relayAuthorityKey) {
2537
- this.relocationAuthorityKeys.delete(relayAuthorityId);
2538
- }
2539
2714
  }
2540
2715
  }
2541
2716
 
2717
+ private completeSourceRelocationMetric(
2718
+ metric: ZLinkRelocationMetricOperation | undefined,
2719
+ settlement?: SourceAuthorityCommit | 'source' | 'lost',
2720
+ sourceStopped = false
2721
+ ): void {
2722
+ metric?.complete(
2723
+ settlement === 'source'
2724
+ ? 'aborted'
2725
+ : settlement === 'lost'
2726
+ ? 'failed'
2727
+ : settlement !== undefined
2728
+ ? 'completed'
2729
+ : sourceStopped
2730
+ ? 'shutdown'
2731
+ : 'failed'
2732
+ );
2733
+ }
2734
+
2542
2735
  /** Streams the payload chunks of one stage under the effective in-flight budgets. */
2543
2736
  private async sendRelocationStateChunks(
2544
2737
  meshName: string,
@@ -2719,7 +2912,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2719
2912
  * excludes a late target commit. `target`: the
2720
2913
  * target committed. `source`: Preserve fenced the target out while the
2721
2914
  * source owner lease was valid. `lost`: the source owner lease ended first
2722
- * or authority is Missing or another authority owns the object. An uncertain
2915
+ * or authority is Missing or fails the same-object, advanced-generation fence.
2916
+ * A committed later owner has no Follow snapshot (Location runtime §10). An uncertain
2723
2917
  * Store or lease result keeps the work retained and repeats the same decision.
2724
2918
  */
2725
2919
  private async settleSourceAuthority(
@@ -2727,12 +2921,16 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2727
2921
  primary: ZLinkAuthoritySnapshot,
2728
2922
  target: ServiceWireRelocationTarget,
2729
2923
  restoreDeadlineReached: () => boolean,
2730
- window: SourceCutoverWindow
2731
- ): Promise<'target' | 'source' | 'lost'> {
2924
+ window: SourceCutoverWindow,
2925
+ relocationMetric?: ZLinkRelocationMetricOperation
2926
+ ): Promise<SourceAuthorityCommit | 'source' | 'lost'> {
2732
2927
  const owner = { ownerId: primary.ownerId, leaseGeneration: primary.ownerLeaseGeneration };
2733
2928
  const store = this.requireLocationStore();
2734
2929
  for (;;) {
2735
- if (this.disposed) throw new Error('Relocation runtime stopped.');
2930
+ if (this.disposed) {
2931
+ this.completeSourceRelocationMetric(relocationMetric, undefined, true);
2932
+ throw new ServiceRelocationRuntimeStoppedError();
2933
+ }
2736
2934
  await this.resendSourceCutover(window);
2737
2935
  const observed = await this.readSourceSettlement(envelope, primary, target).catch(
2738
2936
  (error: unknown) => {
@@ -2740,7 +2938,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2740
2938
  return undefined;
2741
2939
  }
2742
2940
  );
2743
- if (observed?.kind === 'target') return 'target';
2941
+ if (observed?.kind === 'target') return observed;
2744
2942
  if (observed?.kind === 'other') return 'lost';
2745
2943
  if (restoreDeadlineReached() && observed?.kind === 'source') {
2746
2944
  // An unreadable lease is an uncertain result, not an expiry.
@@ -2783,12 +2981,13 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2783
2981
  primary: ZLinkAuthoritySnapshot,
2784
2982
  target: ServiceWireRelocationTarget
2785
2983
  ): Promise<
2786
- | { readonly kind: 'target' }
2984
+ | SourceAuthorityCommit
2787
2985
  | { readonly kind: 'source'; readonly current: ZLinkAuthoritySnapshot }
2788
2986
  | { readonly kind: 'other' }
2789
2987
  > {
2790
2988
  const primaryAuthorityKey = primaryKey(envelope);
2791
2989
  let committed = 0;
2990
+ const followAuthorities = new Map<string, ZLinkAuthoritySnapshot>();
2792
2991
  let primaryCurrent: ZLinkAuthoritySnapshot | undefined;
2793
2992
  for (const participant of envelope.participants) {
2794
2993
  const current = await this.requireLocationStore().readAuthority({
@@ -2797,17 +2996,23 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2797
2996
  if (current.kind !== 'snapshot') return { kind: 'other' };
2798
2997
  if (
2799
2998
  current.objectGeneration === participant.objectGeneration &&
2800
- current.ownerId === target.ownerId &&
2801
- current.ownerLeaseGeneration === target.ownerLeaseGeneration &&
2802
2999
  current.authorityOwnerGeneration > participant.authorityOwnerGeneration &&
2803
- String(current.allocation.descriptor.rid) === target.nodeRid &&
2804
- current.allocation.descriptorLifecycleGeneration === target.nodeGeneration
3000
+ // Location runtime §6: an owner is the (OwnerId, OwnerLeaseGeneration) pair;
3001
+ // a restarted node keeps its OwnerId under a new lease generation.
3002
+ (current.ownerId !== primary.ownerId ||
3003
+ current.ownerLeaseGeneration !== primary.ownerLeaseGeneration)
2805
3004
  ) {
2806
3005
  committed += 1;
3006
+ // Location runtime §10: only the observed first target can receive Follow.
3007
+ if (
3008
+ current.ownerId === target.ownerId &&
3009
+ current.ownerLeaseGeneration === target.ownerLeaseGeneration
3010
+ )
3011
+ followAuthorities.set(participant.key, current);
2807
3012
  }
2808
3013
  if (participant.key === primaryAuthorityKey) primaryCurrent = current;
2809
3014
  }
2810
- if (committed === envelope.participants.length) return { kind: 'target' };
3015
+ if (committed === envelope.participants.length) return { kind: 'target', followAuthorities };
2811
3016
  if (primaryCurrent !== undefined && isSourceOwner(primaryCurrent, primary)) {
2812
3017
  return { kind: 'source', current: primaryCurrent };
2813
3018
  }
@@ -2858,12 +3063,25 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2858
3063
  sourceNodeRid: RoutingId,
2859
3064
  signal?: AbortSignal
2860
3065
  ): Promise<ServiceMaintenanceRelocationReady> {
3066
+ // Host relocation flow 05: a sealed target refuses a new PREPARE before any
3067
+ // staging. The typed ShuttingDown reaches the source as relocationFailed.
3068
+ if (this.targetAdmissionSealed()) {
3069
+ throw new ZLinkFrameworkException(
3070
+ ZLinkFrameworkErrorKind.ShuttingDown,
3071
+ 'Relocation target admission is sealed for shutdown.'
3072
+ );
3073
+ }
3074
+ signal =
3075
+ signal === undefined
3076
+ ? this.targetPreparationStop.signal
3077
+ : AbortSignal.any([signal, this.targetPreparationStop.signal]);
3078
+ signal.throwIfAborted();
2861
3079
  const targetStatus = this.requireMeshNode(meshName).status();
2862
3080
  const targetOwner = this.options.currentOwner();
2863
3081
  const targetDescriptor = this.options.localDescriptor?.(meshName);
2864
3082
  if (
2865
3083
  targetOwner === undefined ||
2866
- String(targetStatus.routingId) !== request.target.nodeRid ||
3084
+ !routingIdsEqual(targetStatus.routingId, request.target.nodeRid) ||
2867
3085
  targetStatus.lifecycleGeneration !== request.target.nodeGeneration ||
2868
3086
  targetOwner.ownerId !== request.target.ownerId ||
2869
3087
  targetOwner.leaseGeneration !== request.target.ownerLeaseGeneration ||
@@ -2893,11 +3111,9 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2893
3111
  try {
2894
3112
  payload = await pendingAssembly.assembly.payload();
2895
3113
  } catch (error) {
2896
- // A rejection observed after dispose() started is the runtime shutting
2897
- // down, not a chunk-assembly or checksum integrity failure — it must
2898
- // not be reported as DataLost(35). There is no dedicated ShuttingDown
2899
- // wire code in the vocabulary yet, so it falls through unwrapped to
2900
- // the generic InternalFailure code (spec 15 §4).
3114
+ // Target shutdown preserves its lifecycle failure instead of reporting payload corruption.
3115
+ this.targetAssemblies.delete(stagingId);
3116
+ if (this.targetPreparationStop.signal.aborted) throw this.targetPreparationStop.signal.reason;
2901
3117
  if (this.disposed) throw error;
2902
3118
  const message =
2903
3119
  error !== null && typeof error === 'object' && 'message' in error
@@ -2930,6 +3146,51 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2930
3146
  // rejects unrelated authority CAS until commit/abort.
2931
3147
  materialized = await this.materializeTargetEnvelope(meshName, inventoryEnvelope, signal);
2932
3148
  await materialized.owner.restoreSavedWork(materialized.staging, signal);
3149
+ for (const participant of inventoryEnvelope.participants) {
3150
+ const binding = participant.sessionBinding;
3151
+ if (binding === undefined) continue;
3152
+ const peerRid = decodeRoutingId(
3153
+ '',
3154
+ Buffer.from(binding.sessionOwnerNodeRid).toString('hex')
3155
+ );
3156
+ const node = this.requireMeshNode(meshName);
3157
+ if (routingIdsEqual(node.status().routingId, peerRid)) continue;
3158
+ if (node.isPeerRouteReady?.(peerRid, binding.sessionOwnerNodeGeneration) !== true) {
3159
+ // Subscribe before requesting reconciliation so a synchronous admission cannot be lost.
3160
+ const wait = node.waitForPeerRoute;
3161
+ if (wait === undefined) throw new Error('Mesh peer route observation is unavailable.');
3162
+ const ready = wait.call(
3163
+ node,
3164
+ peerRid,
3165
+ binding.sessionOwnerNodeGeneration,
3166
+ request.deadlineUnixMs,
3167
+ signal
3168
+ );
3169
+ void this.options
3170
+ .requestPeerReconcile?.(meshName, String(peerRid))
3171
+ .catch((error) =>
3172
+ console.error('[zlink.runtime.relocation.session_route_reconcile_failed]', error)
3173
+ );
3174
+ try {
3175
+ await ready;
3176
+ } catch (error) {
3177
+ if (error instanceof OperationTimeoutError) {
3178
+ throw new ZLinkFrameworkException(
3179
+ ZLinkFrameworkErrorKind.DeadlineExceeded,
3180
+ 'Relocation source operation deadline exceeded waiting for Session owner route.'
3181
+ );
3182
+ }
3183
+ throw error;
3184
+ }
3185
+ }
3186
+ }
3187
+ if (request.deadlineUnixMs <= BigInt(Date.now())) {
3188
+ throw new ZLinkFrameworkException(
3189
+ ZLinkFrameworkErrorKind.DeadlineExceeded,
3190
+ 'Relocation source operation deadline exceeded before relay-ready.'
3191
+ );
3192
+ }
3193
+ signal.throwIfAborted();
2933
3194
  reservation = await this.reserveTargetForPrepare(
2934
3195
  meshName,
2935
3196
  request,
@@ -2939,7 +3200,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2939
3200
  const offer: TargetRelocationOffer = {
2940
3201
  prepare: request,
2941
3202
  prepareFingerprint: fingerprint,
2942
- authenticatedSourceNodeRid: String(sourceNodeRid),
3203
+ authenticatedSourceNodeRid: sourceNodeRid,
2943
3204
  envelope: inventoryEnvelope,
2944
3205
  reservation
2945
3206
  };
@@ -2955,6 +3216,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2955
3216
  };
2956
3217
  this.targetStages.set(stagingId, stage);
2957
3218
  } catch (error) {
3219
+ this.targetAssemblies.delete(stagingId);
2958
3220
  releaseJoinAdmission();
2959
3221
  releaseActivation();
2960
3222
  if (reservation !== undefined) {
@@ -3122,30 +3384,51 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3122
3384
  stage.releaseActivation();
3123
3385
  resumeStartedAt = performance.now();
3124
3386
  stage.phase = 'committed';
3125
- await stage.owner.normalize(stage.staging, authority, signal);
3126
3387
  await stage.owner.publish(stage.staging, authority, signal);
3127
- // The target may have visited this Actor before and still retain that
3128
- // tenure in its private Actor-client resolver. The target-only CAS is
3129
- // now authoritative, so expire the old projection before OnJoinedActor
3130
- // can issue an Actor request and deadlock behind source leave.
3131
- for (const hidden of stage.staging.hidden.values()) {
3132
- if (hidden.actor !== undefined) {
3133
- this.options.invalidateActorRoute?.(hidden.actor.context.actorId);
3134
- }
3135
- }
3136
- await this.finalizeActorJoinProfiles(meshName, stage, signal);
3137
3388
  // Clear the final relocation publication before admission becomes
3138
3389
  // externally visible so a later Join cannot reserve against the
3139
3390
  // previous root. A bounded CAS conflict must not roll back already
3140
3391
  // committed target ownership or strand its staged object.
3141
3392
  try {
3142
- await this.clearTargetRelocationPublication(stage, authority, signal);
3393
+ authority = await this.clearTargetRelocationPublication(stage, authority, signal);
3143
3394
  } catch (error) {
3144
3395
  console.warn('[zlink.runtime.relocation.publication_clear_failed]', stagingId, error);
3145
3396
  this.options.metrics?.count('zlink.relocation.publication_clear_failed', 1, {
3146
3397
  mesh_name: meshName
3147
3398
  });
3148
3399
  }
3400
+ for (const [index, participant] of stage.staging.envelope.participants.entries()) {
3401
+ const hidden = stage.staging.hidden.get(participant.key);
3402
+ if (hidden?.actor === undefined || hidden.replayPackets.length === 0) continue;
3403
+ const coordinator = this.targetReplyRelayCoordinator(meshName, stage, authority);
3404
+ hidden.replayStart = stage.finalize;
3405
+ hidden.onReplayResult = (packet, result) =>
3406
+ this.relayTerminalReply(
3407
+ meshName,
3408
+ stage,
3409
+ coordinator,
3410
+ BigInt(index + 1),
3411
+ packet,
3412
+ result,
3413
+ signal
3414
+ );
3415
+ }
3416
+ await stage.owner.normalize(stage.staging, authority, signal);
3417
+ // The target may have visited this Actor before and still retain that
3418
+ // tenure in its private Actor-client resolver. The target-only CAS is
3419
+ // now authoritative, so expire the old projection before OnJoinedActor
3420
+ // can issue an Actor request and deadlock behind source leave.
3421
+ for (const hidden of stage.staging.hidden.values()) {
3422
+ if (hidden.actor !== undefined) {
3423
+ this.options.invalidateActorRoute?.(hidden.actor.context.actorId);
3424
+ }
3425
+ }
3426
+ await this.finalizeActorJoinProfiles(meshName, stage, signal);
3427
+ await this.publishSessionRoutes(stage.staging);
3428
+ stage.phase = 'open';
3429
+ this.terminalTargets.remember(stagingId, stage.offer.prepareFingerprint);
3430
+ this.targetStages.delete(stagingId);
3431
+ this.targetAssemblies.delete(stagingId);
3149
3432
  await stage.owner.openAdmission(stage.staging, signal);
3150
3433
  // S2→S3: owner confirmation to application dispatch opening.
3151
3434
  this.options.metrics?.duration(
@@ -3153,17 +3436,6 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3153
3436
  Math.max(0, performance.now() - resumeStartedAt) / MILLISECONDS_PER_SECOND,
3154
3437
  { mesh_name: meshName }
3155
3438
  );
3156
- await this.publishSessionRoutes(stage.staging);
3157
- await this.relayTerminalReplies(
3158
- meshName,
3159
- stage,
3160
- this.targetReplyRelayCoordinator(meshName, stage, authority),
3161
- signal
3162
- );
3163
- stage.phase = 'open';
3164
- this.terminalTargets.remember(stagingId, stage.offer.prepareFingerprint);
3165
- this.targetStages.delete(stagingId);
3166
- this.targetAssemblies.delete(stagingId);
3167
3439
  } catch (error) {
3168
3440
  this.terminalTargets.remember(stagingId, stage.offer.prepareFingerprint);
3169
3441
  this.targetStages.delete(stagingId);
@@ -3309,9 +3581,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3309
3581
  if (profile.state.actorId !== input.actorId || !input.succeeded) {
3310
3582
  throw new Error(`Actor Join relocation '${input.transferId}' source leave identity changed.`);
3311
3583
  }
3312
- this.sourceActorJoinProfiles.delete(input.transferId);
3313
- this.terminalActorJoinSourceLeaves.remember(input.transferId, input.actorId);
3314
- void this.finalizeActorJoinSource(profile);
3584
+ void this.retireActorJoinSource(input.transferId, profile);
3315
3585
  return true;
3316
3586
  }
3317
3587
 
@@ -3349,7 +3619,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3349
3619
  stage: LocalStage,
3350
3620
  primary: ZLinkAuthoritySnapshot,
3351
3621
  signal?: AbortSignal
3352
- ): Promise<void> {
3622
+ ): Promise<ZLinkAuthoritySnapshot> {
3353
3623
  await this.releaseParticipantAuthorities(
3354
3624
  stage.staging.envelope,
3355
3625
  stage.staging.primaryAuthorityKey.value,
@@ -3360,7 +3630,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3360
3630
  // committed snapshot decides. A conflict means another owner changed the
3361
3631
  // row; the caller records it and leaves the committed ownership intact.
3362
3632
  const publication = this.codec.read(primary.payload);
3363
- if (publication === undefined) return;
3633
+ if (publication === undefined) return primary;
3364
3634
  const result = await this.requireLocationStore().compareExchangeAuthority(
3365
3635
  stage.staging.primaryAuthorityKey,
3366
3636
  primary.storeVersion,
@@ -3374,6 +3644,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3374
3644
  if (result.kind !== 'stored') {
3375
3645
  throw new Error('Relocation target authority normalization CAS failed.');
3376
3646
  }
3647
+ return { ...result, kind: 'snapshot' };
3377
3648
  }
3378
3649
 
3379
3650
  private async handleReplyRelay(
@@ -3385,49 +3656,53 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3385
3656
  if (sourceNodeRid === null) {
3386
3657
  throw new Error('Relocation reply relay has no authenticated target node.');
3387
3658
  }
3388
- await this.requireAdmittedPeer(meshName, String(sourceNodeRid), relay.coordinator, signal);
3389
- const relocationId = wireIdText(relay.relocation);
3390
- const authorityKey = this.relocationAuthorityKeys.get(relocationId);
3391
- if (authorityKey === undefined) {
3392
- throw new Error('Relocation reply relay has no registered durable authority.');
3393
- }
3394
- const authority = await requireAuthority(
3395
- this.requireLocationStore(),
3396
- { value: authorityKey } as ZLinkAuthorityKey,
3397
- signal
3398
- );
3399
- if (
3400
- authority.storeVersion.value !== relay.coordinator.expectedAuthorityStoreVersion ||
3401
- authority.ownerId !== relay.coordinator.ownerId ||
3402
- authority.ownerLeaseGeneration !== relay.coordinator.leaseGeneration ||
3403
- String(authority.allocation.descriptor.rid) !== relay.coordinator.nodeRid ||
3404
- authority.allocation.descriptorLifecycleGeneration !== relay.coordinator.nodeGeneration
3405
- ) {
3406
- throw new Error('Relocation reply relay coordinator authority fence is stale.');
3407
- }
3408
- const accepted = this.options.actorTransfer.relayCanonicalMaintenanceTerminal(
3409
- wireIdText(relay.operation),
3410
- relay.replyRouteId.toString(),
3411
- handoffResultFromRelay(relay),
3412
- sourceNodeRid
3413
- );
3414
- if (
3415
- (accepted.status !== 'terminalReceived' && accepted.status !== 'alreadyTerminal') ||
3416
- accepted.source === undefined
3417
- ) {
3418
- throw new Error('Canonical relocation reply relay collided with another source route.');
3419
- }
3420
- const requestSource = handoffSourceFence(accepted.source);
3421
- const localStatus = this.requireMeshNode(meshName).status();
3659
+ await this.requireAdmittedPeer(meshName, sourceNodeRid, relay.coordinator, signal);
3660
+ const node = this.requireMeshNode(meshName);
3661
+ const localStatus = node.status();
3422
3662
  const localOwner = this.options.currentOwner();
3423
- if (
3424
- localOwner === undefined ||
3425
- requestSource.ownerId !== localOwner.ownerId ||
3426
- requestSource.leaseGeneration !== localOwner.leaseGeneration ||
3427
- !routingIdsEqual(requestSource.nodeRid, localStatus.routingId) ||
3428
- requestSource.nodeGeneration !== localStatus.lifecycleGeneration
3429
- ) {
3430
- throw new Error('Relocation reply relay request-source fence changed after capture.');
3663
+ if (localOwner === undefined)
3664
+ throw new Error('Relocation requester owner lease is unavailable.');
3665
+ let status = node.acceptRelocatedActorTerminal?.(relay.operation, relay.replyRouteId, {
3666
+ terminalResult: relay.terminalResult,
3667
+ failureCode: relay.failureCode,
3668
+ ...(relay.payload === undefined
3669
+ ? {}
3670
+ : {
3671
+ payload: {
3672
+ packetName: relay.payload.packetName,
3673
+ contentType: relay.payload.contentType,
3674
+ payload: Buffer.from(relay.payload.bytes)
3675
+ }
3676
+ })
3677
+ });
3678
+ let requestSource: ServiceWireRequestSourceFence = {
3679
+ ownerId: localOwner.ownerId,
3680
+ leaseGeneration: localOwner.leaseGeneration,
3681
+ nodeRid: localStatus.routingId,
3682
+ nodeGeneration: localStatus.lifecycleGeneration
3683
+ };
3684
+ if (status === undefined) {
3685
+ const accepted = this.options.actorTransfer.relayCanonicalMaintenanceTerminal(
3686
+ relay.operation.high.toString(16).padStart(16, '0') +
3687
+ relay.operation.low.toString(16).padStart(16, '0'),
3688
+ relay.replyRouteId.toString(),
3689
+ handoffResultFromRelay(relay),
3690
+ sourceNodeRid
3691
+ );
3692
+ if (accepted.status === 'notAcknowledged' || accepted.source === undefined) {
3693
+ throw new Error('Canonical relocation reply relay collided with another source route.');
3694
+ }
3695
+ requestSource = handoffSourceFence(accepted.source);
3696
+ if (
3697
+ !sameRequestSource(requestSource, {
3698
+ ownerId: localOwner.ownerId,
3699
+ leaseGeneration: localOwner.leaseGeneration,
3700
+ nodeRid: localStatus.routingId,
3701
+ nodeGeneration: localStatus.lifecycleGeneration
3702
+ })
3703
+ )
3704
+ throw new Error('Relocation reply relay request-source fence changed after capture.');
3705
+ status = accepted.status;
3431
3706
  }
3432
3707
  const ack: ServiceMaintenanceReplyRelayAck = {
3433
3708
  relocation: relay.relocation,
@@ -3435,7 +3710,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3435
3710
  operation: relay.operation,
3436
3711
  replyRouteId: relay.replyRouteId,
3437
3712
  requestSource,
3438
- status: accepted.status
3713
+ status
3439
3714
  };
3440
3715
  if (
3441
3716
  (await this.sendInfrastructureControl(
@@ -3455,9 +3730,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3455
3730
  expectedRequestSource: ServiceWireRequestSourceFence,
3456
3731
  signal?: AbortSignal
3457
3732
  ): Promise<RelocationTerminalDelivery> {
3458
- const targetRid = String(ackTargetNodeRid);
3459
3733
  const identityKey = replyRelayIdentityKey(request);
3460
- const key = replyRelayPendingKey(targetRid, request);
3734
+ const key = replyRelayPendingKey(ackTargetNodeRid, request);
3461
3735
  if (this.pendingReplyRelays.has(key)) {
3462
3736
  throw new Error(`Relocation reply relay ACK '${key}' is already pending.`);
3463
3737
  }
@@ -3470,7 +3744,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3470
3744
  action();
3471
3745
  };
3472
3746
  const pending: PendingRelocationReplyRelay = {
3473
- ackTargetNodeRid: targetRid,
3747
+ ackTargetNodeRid,
3474
3748
  identityKey,
3475
3749
  request,
3476
3750
  expectedRequestSource,
@@ -3515,8 +3789,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3515
3789
  signal?: AbortSignal
3516
3790
  ): Promise<void> {
3517
3791
  const identityKey = replyRelayIdentityKey(ack);
3518
- const exactKey =
3519
- sourceNodeRid === null ? undefined : replyRelayPendingKey(String(sourceNodeRid), ack);
3792
+ const exactKey = sourceNodeRid === null ? undefined : replyRelayPendingKey(sourceNodeRid, ack);
3520
3793
  const pending = exactKey === undefined ? undefined : this.pendingReplyRelays.get(exactKey);
3521
3794
  if (pending === undefined) {
3522
3795
  const collision = [...this.pendingReplyRelays.values()].find(
@@ -3554,14 +3827,14 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3554
3827
  signal?: AbortSignal
3555
3828
  ): Promise<void> {
3556
3829
  const descriptor = (await this.options.liveDescriptors(meshName, signal)).find(
3557
- (value) => String(value.rid) === authenticatedRid && value.state === 1
3830
+ (value) => routingIdsEqual(value.rid, authenticatedRid) && value.state === 1
3558
3831
  );
3559
3832
  if (
3560
3833
  descriptor === undefined ||
3561
3834
  (expected !== undefined &&
3562
3835
  (descriptor.ownerId !== expected.ownerId ||
3563
3836
  descriptor.leaseGeneration !== expected.leaseGeneration ||
3564
- String(descriptor.rid) !== expected.nodeRid ||
3837
+ !routingIdsEqual(descriptor.rid, expected.nodeRid) ||
3565
3838
  descriptor.lifecycleGeneration !== expected.nodeGeneration))
3566
3839
  ) {
3567
3840
  throw new Error('Relocation reply relay peer is not the exact admitted source fence.');
@@ -3580,7 +3853,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3580
3853
  owner === undefined ||
3581
3854
  owner.ownerId !== target.ownerId ||
3582
3855
  owner.leaseGeneration !== target.ownerLeaseGeneration ||
3583
- String(status.routingId) !== target.nodeRid ||
3856
+ !routingIdsEqual(status.routingId, target.nodeRid) ||
3584
3857
  status.lifecycleGeneration !== target.nodeGeneration
3585
3858
  ) {
3586
3859
  throw new Error('Relocation reply relay target coordinator fence is stale.');
@@ -3588,42 +3861,47 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3588
3861
  return {
3589
3862
  ownerId: owner.ownerId,
3590
3863
  leaseGeneration: owner.leaseGeneration,
3591
- nodeRid: String(status.routingId),
3864
+ nodeRid: status.routingId,
3592
3865
  nodeGeneration: status.lifecycleGeneration,
3593
3866
  expectedAuthorityStoreVersion: authority.storeVersion.value
3594
3867
  };
3595
3868
  }
3596
3869
 
3597
- private async relayTerminalReplies(
3870
+ private async relayTerminalReply(
3598
3871
  meshName: string,
3599
3872
  stage: LocalStage,
3600
3873
  coordinator: ServiceWireRelocationCoordinatorFence,
3874
+ participantId: bigint,
3875
+ packet: ZLinkActorHandoffPacket,
3876
+ result: ZLinkActorHandoffResult,
3601
3877
  signal?: AbortSignal
3602
3878
  ): Promise<void> {
3603
- for (const [participantIndex, participant] of stage.staging.envelope.participants.entries()) {
3604
- const hidden = stage.staging.hidden.get(participant.key);
3605
- if (hidden?.actor === undefined) continue;
3606
- for (const [resultIndex, packet] of hidden.replayPackets.entries()) {
3607
- if (!packet.returnResponse || packet.source === undefined) continue;
3608
- const result = hidden.replayResults.at(resultIndex);
3609
- if (result === undefined || result.index !== packet.index) {
3610
- throw new Error('Relocation request result changed after target dispatch.');
3611
- }
3612
- const completion: RelocationTerminalCompletion = {
3613
- index: packet.index,
3614
- operationId: packet.messageFollowContext.operationId,
3615
- source: packet.source,
3616
- result
3617
- };
3618
- await this.sendReplyRelay(
3619
- meshName,
3620
- packet.source.nodeRid as RoutingId,
3621
- maintenanceReplyRelay(stage, coordinator, BigInt(participantIndex + 1), completion),
3622
- handoffSourceFence(packet.source),
3623
- signal
3624
- );
3625
- }
3626
- }
3879
+ if (!packet.returnResponse || packet.source === undefined) return;
3880
+ if (result.index !== packet.index) {
3881
+ throw new Error('Relocation request result changed after target dispatch.');
3882
+ }
3883
+ const completion: RelocationTerminalCompletion = {
3884
+ index: packet.index,
3885
+ operationId: packet.messageFollowContext.operationId,
3886
+ source: packet.source,
3887
+ result
3888
+ };
3889
+ const source = handoffSourceFence(packet.source);
3890
+ await this.sendReplyRelay(
3891
+ meshName,
3892
+ source.nodeRid,
3893
+ maintenanceReplyRelay(
3894
+ stage,
3895
+ coordinator,
3896
+ participantId,
3897
+ completion,
3898
+ packet,
3899
+ this.options.registration.messageSerializers,
3900
+ this.options.flowCreationEnabled?.() ?? false
3901
+ ),
3902
+ source,
3903
+ signal
3904
+ );
3627
3905
  }
3628
3906
 
3629
3907
  private async exactSourceLeaseExpired(
@@ -3704,13 +3982,13 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3704
3982
  const descriptors = await this.options.liveDescriptors(meshName, signal);
3705
3983
  const local =
3706
3984
  this.options.localDescriptor?.(meshName) ??
3707
- descriptors.find((descriptor) => String(descriptor.rid) === localRid);
3985
+ descriptors.find((descriptor) => routingIdsEqual(descriptor.rid, localRid));
3708
3986
  const requiredVersion = targetApplicationVersion ?? local?.applicationVersion;
3709
3987
  const peers = this.requireMeshNode(meshName).peers();
3710
3988
  const candidates = descriptors
3711
3989
  .filter(
3712
3990
  (descriptor) =>
3713
- String(descriptor.rid) !== localRid &&
3991
+ !routingIdsEqual(descriptor.rid, localRid) &&
3714
3992
  descriptor.state === ZLinkFrameworkRuntimeState.Serving &&
3715
3993
  descriptor.objectRole === ZLinkObjectRole.Server &&
3716
3994
  descriptor.placementWeight > 0 &&
@@ -3720,7 +3998,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3720
3998
  peers.some(
3721
3999
  (peer) =>
3722
4000
  peer.routingId !== null &&
3723
- String(peer.routingId) === String(descriptor.rid) &&
4001
+ routingIdsEqual(peer.routingId, descriptor.rid) &&
3724
4002
  peer.lifecycleGeneration === descriptor.lifecycleGeneration &&
3725
4003
  peer.state === MeshPeerRuntimeState.Serving
3726
4004
  ) &&
@@ -4308,8 +4586,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
4308
4586
  meshName: string,
4309
4587
  unitKind: 'actor' | 'user_spot' | 'instance_spot',
4310
4588
  executionMode: 'spot_wide' | 'per_actor' | undefined,
4311
- startedAt: number,
4312
- outcome: string
4589
+ startedAt: number
4313
4590
  ): void {
4314
4591
  const seconds = Math.max(0, performance.now() - startedAt) / MILLISECONDS_PER_SECOND;
4315
4592
  const attributes = {
@@ -4317,12 +4594,6 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
4317
4594
  ...(executionMode === undefined ? {} : { execution_mode: executionMode })
4318
4595
  };
4319
4596
  this.options.metrics?.duration(METRIC_NAMES.RelocationInterruption, seconds, attributes);
4320
- this.options.metrics?.count(METRIC_NAMES.RelocationCompleted, 1, {
4321
- mesh_name: meshName,
4322
- object_kind: unitKind,
4323
- policy: unitKind === 'user_spot' && executionMode === 'per_actor' ? 'recreate' : 'configured',
4324
- outcome
4325
- });
4326
4597
  if (seconds <= 1) return;
4327
4598
  console.warn(
4328
4599
  `[zlink.runtime.relocation.changed] ${unitKind} admission interruption ` +
@@ -4376,30 +4647,30 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
4376
4647
  private async commitSpotMessageFollow(
4377
4648
  meshName: string,
4378
4649
  seal: ServiceSpotMessageFollowSeal,
4379
- authorityKey: ZLinkAuthorityKey,
4380
- activation: ZLinkSpotActivation,
4381
- target: ZLinkMeshNodeDescriptor
4650
+ committed: ZLinkAuthoritySnapshot | undefined,
4651
+ activation: ZLinkSpotActivation
4382
4652
  ): Promise<void> {
4383
4653
  const node = this.requireMeshNode(meshName);
4384
4654
  const commit = node.commitSpotMessageFollowIngress;
4385
4655
  if (commit === undefined) {
4386
4656
  throw new Error('Spot Message Follow backend support is incomplete.');
4387
4657
  }
4388
- const committed = await requireAuthority(this.requireLocationStore(), authorityKey);
4389
4658
  const accepted = await commit.call(
4390
4659
  node,
4391
4660
  seal,
4392
- {
4393
- spot: {
4394
- spotId: String(activation.spotId),
4395
- generation: committed.objectGeneration
4396
- },
4397
- targetNodeRid: String(target.rid),
4398
- targetNodeGeneration: target.lifecycleGeneration,
4399
- authorityOwnerGeneration: committed.authorityOwnerGeneration,
4400
- ownerLeaseGeneration: committed.ownerLeaseGeneration,
4401
- storeVersion: committed.storeVersion.value
4402
- },
4661
+ committed === undefined
4662
+ ? null
4663
+ : {
4664
+ spot: {
4665
+ spotId: String(activation.spotId),
4666
+ generation: committed.objectGeneration
4667
+ },
4668
+ targetNodeRid: String(committed.allocation.descriptor.rid),
4669
+ targetNodeGeneration: committed.allocation.descriptorLifecycleGeneration,
4670
+ authorityOwnerGeneration: committed.authorityOwnerGeneration,
4671
+ ownerLeaseGeneration: committed.ownerLeaseGeneration,
4672
+ storeVersion: committed.storeVersion.value
4673
+ },
4403
4674
  this.relocationLimits().messageFollowDurationMs
4404
4675
  );
4405
4676
  if (!accepted) {
@@ -4518,6 +4789,27 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
4518
4789
  }
4519
4790
  }
4520
4791
 
4792
+ function committedSpotRoute<TKind extends ZLinkSpotKind.User | ZLinkSpotKind.Instance>(
4793
+ meshName: string,
4794
+ spotId: RoutingId,
4795
+ spotKind: TKind,
4796
+ targetNodeRid: RoutingId,
4797
+ authority: ZLinkAuthoritySnapshot | undefined
4798
+ ): ZLinkSpotRouteTarget & { readonly spotKind: TKind } {
4799
+ return {
4800
+ routerChannelId: meshName,
4801
+ targetNodeRid,
4802
+ spotId,
4803
+ spotKind,
4804
+ targetSpotGeneration: authority?.objectGeneration,
4805
+ targetNodeGeneration: authority?.allocation.descriptorLifecycleGeneration,
4806
+ authorityOwnerGeneration: authority?.authorityOwnerGeneration,
4807
+ targetOwnerId: authority?.ownerId,
4808
+ ownerLeaseGeneration: authority?.ownerLeaseGeneration,
4809
+ authorityStoreVersion: authority?.storeVersion.value
4810
+ };
4811
+ }
4812
+
4521
4813
  function canonicalActorJoinRecovery(
4522
4814
  participants: readonly ServiceRelocationParticipant[]
4523
4815
  ): CanonicalActorJoinRecovery | undefined {
@@ -4604,6 +4896,9 @@ class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden>
4604
4896
  ): Promise<LocalHidden> {
4605
4897
  const identity = decodeAuthorityKey({ value: participant.key } as ZLinkAuthorityKey).globalId;
4606
4898
  if (participant.objectKind === 'actor') {
4899
+ if (participant.timers.length !== 0) {
4900
+ throw new Error('Actor relocation cannot restore a Spot timer.');
4901
+ }
4607
4902
  const membership = this.envelope.memberships.find(
4608
4903
  (value) => value.actorKey === participant.key
4609
4904
  );
@@ -4630,8 +4925,6 @@ class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden>
4630
4925
  participant,
4631
4926
  actor,
4632
4927
  initialized: false,
4633
- restoredTimers: [],
4634
- replayResults: [],
4635
4928
  replayPackets: []
4636
4929
  };
4637
4930
  }
@@ -4644,6 +4937,19 @@ class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden>
4644
4937
  identity as RoutingId,
4645
4938
  participant.objectGeneration,
4646
4939
  participant.authorityOwnerGeneration,
4940
+ participant.timers.map((value) => ({
4941
+ name: value.timerId,
4942
+ handlerType: value.handlerType,
4943
+ periodMs: value.intervalMs,
4944
+ overrunPolicy: value.overrunPolicy as never,
4945
+ maxCatchUpTicks: value.maxCatchUpTicks,
4946
+ stopOnUnhandledException: value.stopOnUnhandledException,
4947
+ startedAtUnixMs: value.startedAtUnixMs,
4948
+ deliveryIndex: value.deliveryIndex,
4949
+ lastScheduledIndex: value.lastScheduledIndex,
4950
+ nextDueAtUnixMs: value.dueAtUnixMs,
4951
+ pendingTicks: value.pendingTicks
4952
+ })),
4647
4953
  signal
4648
4954
  );
4649
4955
  return {
@@ -4651,8 +4957,6 @@ class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden>
4651
4957
  participant,
4652
4958
  activation,
4653
4959
  initialized: false,
4654
- restoredTimers: [],
4655
- replayResults: [],
4656
4960
  replayPackets: []
4657
4961
  };
4658
4962
  }
@@ -4843,66 +5147,31 @@ class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden>
4843
5147
  }
4844
5148
 
4845
5149
  async openAdmission(hidden: LocalHidden): Promise<void> {
4846
- if (hidden.actor === undefined) return;
4847
- this.requireActorManager().publishRelocationActor(hidden.actor.context.actorId);
4848
- if (hidden.replayPackets.length === 0) return;
4849
- const state = this.requireActorManager().getState(hidden.actor.context.actorId);
4850
- if (state === undefined) throw new Error('Relocated Actor state is not staged.');
4851
- const results = await replayActorHandoffBacklog(
4852
- hidden.replayPackets,
4853
- (parts, returnResponse, remoteBoundSessionTarget, fallbackActorRef) => {
4854
- if (state.spotId === undefined) {
4855
- const runtime = this.options.spotNodeRuntime();
4856
- if (runtime === undefined) {
4857
- throw new Error('Relocated Entry Spot Actor dispatch is unavailable.');
4858
- }
4859
- return runtime.dispatchEntryActorPacket(
4860
- 'remote',
4861
- hidden.actor!.context.actorId,
4862
- parts,
4863
- returnResponse,
4864
- remoteBoundSessionTarget,
4865
- fallbackActorRef
4866
- );
4867
- }
4868
- return this.requireSpotManager().dispatchRoutedActorPacket(
4869
- 'remote',
4870
- state.spotId,
4871
- hidden.actor!.context.actorId,
4872
- parts,
4873
- returnResponse,
4874
- remoteBoundSessionTarget,
4875
- fallbackActorRef
4876
- );
4877
- }
4878
- );
4879
- hidden.replayResults.push(...results);
5150
+ if (hidden.actor !== undefined) {
5151
+ this.requireActorManager().publishRelocationActor(hidden.actor.context.actorId);
5152
+ this.requireSpotManager().openActorJoinRelocation(
5153
+ this.meshName,
5154
+ actorJoinAdmissionIdentity(this.envelope),
5155
+ hidden.actor
5156
+ );
5157
+ }
4880
5158
  }
4881
5159
 
4882
- async restoreTimer(hidden: LocalHidden, timer: ServiceRelocationTimer): Promise<void> {
4883
- if (hidden.activation === undefined)
4884
- throw new Error('Actor relocation cannot restore a Spot timer.');
4885
- hidden.restoredTimers.push(timer);
4886
- if (hidden.restoredTimers.length !== hidden.participant.timers.length) return;
4887
- hidden.activation.timers.restoreRelocation(
4888
- hidden.restoredTimers.map((value) => ({
4889
- name: value.timerId,
4890
- handlerType: value.handlerType,
4891
- periodMs: value.intervalMs,
4892
- overrunPolicy: value.overrunPolicy as never,
4893
- maxCatchUpTicks: value.maxCatchUpTicks,
4894
- stopOnUnhandledException: value.stopOnUnhandledException,
4895
- startedAtUnixMs: value.startedAtUnixMs,
4896
- deliveryIndex: value.deliveryIndex,
4897
- lastScheduledIndex: value.lastScheduledIndex,
4898
- nextDueAtUnixMs: value.dueAtUnixMs,
4899
- pendingTicks: value.pendingTicks
4900
- }))
5160
+ async normalize(hidden: LocalHidden): Promise<void> {
5161
+ if (hidden.actor === undefined || hidden.replayPackets.length === 0) return;
5162
+ if (hidden.replayStart === undefined || hidden.onReplayResult === undefined) {
5163
+ throw new Error('Relocated Actor backlog has no target activation owner.');
5164
+ }
5165
+ this.options.actorTransfer.admitRelocationPrefix(
5166
+ hidden.actor,
5167
+ hidden.participant.objectGeneration,
5168
+ hidden.replayPackets,
5169
+ hidden.replayStart,
5170
+ hidden.onReplayResult
4901
5171
  );
5172
+ hidden.replayPackets.length = 0;
4902
5173
  }
4903
5174
 
4904
- async normalize(_hidden: LocalHidden): Promise<void> {}
4905
-
4906
5175
  async abort(hidden: LocalHidden): Promise<void> {
4907
5176
  if (hidden.actor !== undefined) {
4908
5177
  await this.requireActorManager().abortRelocationActor(hidden.actor.context.actorId);
@@ -5132,6 +5401,22 @@ export function decodeQueuedHandoffPacket(
5132
5401
  ...(messageFollowOrigin === undefined ? {} : { messageFollowOrigin })
5133
5402
  }
5134
5403
  ])[0]!;
5404
+ const requestSource =
5405
+ decoded.source === undefined ? undefined : handoffSourceFence(decoded.source);
5406
+ if (
5407
+ messageFollowOrigin !== undefined &&
5408
+ requestSource !== undefined &&
5409
+ routingIdsEqual(messageFollowOrigin.sourceNodeRid, requestSource.nodeRid)
5410
+ ) {
5411
+ return {
5412
+ ...decoded,
5413
+ index: index as number,
5414
+ messageFollowOrigin: {
5415
+ ...messageFollowOrigin,
5416
+ requestSource
5417
+ }
5418
+ };
5419
+ }
5135
5420
  return { ...decoded, index: index as number };
5136
5421
  }
5137
5422
 
@@ -5306,16 +5591,6 @@ export function createServiceRelocationId(
5306
5591
  }
5307
5592
  }
5308
5593
 
5309
- function operationWireId(value: string): { readonly high: bigint; readonly low: bigint } {
5310
- const match = /^(\d+):(\d+)$/.exec(value);
5311
- if (match === null) throw new Error(`Actor handoff operation '${value}' is not canonical.`);
5312
- const result = { high: BigInt(match[1]!), low: BigInt(match[2]!) };
5313
- if (result.high === 0n && result.low === 0n) {
5314
- throw new Error('Actor handoff operation identity must not be zero.');
5315
- }
5316
- return result;
5317
- }
5318
-
5319
5594
  function sameWireId(
5320
5595
  left: { readonly high: bigint; readonly low: bigint },
5321
5596
  right: { readonly high: bigint; readonly low: bigint }
@@ -5330,7 +5605,7 @@ function sameCoordinator(
5330
5605
  return (
5331
5606
  left.ownerId === right.ownerId &&
5332
5607
  BigInt(left.leaseGeneration) === right.leaseGeneration &&
5333
- left.nodeRid === right.nodeRid &&
5608
+ routingIdsEqual(left.nodeRid, right.nodeRid) &&
5334
5609
  BigInt(left.nodeGeneration) === right.nodeGeneration &&
5335
5610
  left.expectedAuthorityStoreVersion === right.expectedAuthorityStoreVersion
5336
5611
  );
@@ -5341,7 +5616,7 @@ function sameRelocationTarget(
5341
5616
  right: ServiceWireRelocationTarget
5342
5617
  ): boolean {
5343
5618
  return (
5344
- left.nodeRid === right.nodeRid &&
5619
+ routingIdsEqual(left.nodeRid, right.nodeRid) &&
5345
5620
  BigInt(left.nodeGeneration) === right.nodeGeneration &&
5346
5621
  left.ownerId === right.ownerId &&
5347
5622
  BigInt(left.ownerLeaseGeneration) === right.ownerLeaseGeneration
@@ -5394,7 +5669,7 @@ function isSourceOwner(current: ZLinkAuthoritySnapshot, expected: ZLinkAuthority
5394
5669
  current.allocation.state === expected.allocation.state &&
5395
5670
  current.allocation.objectKind === expected.allocation.objectKind &&
5396
5671
  current.allocation.stableType === expected.allocation.stableType &&
5397
- String(current.allocation.descriptor.rid) === String(expected.allocation.descriptor.rid) &&
5672
+ routingIdsEqual(current.allocation.descriptor.rid, expected.allocation.descriptor.rid) &&
5398
5673
  current.allocation.descriptorLifecycleGeneration ===
5399
5674
  expected.allocation.descriptorLifecycleGeneration
5400
5675
  );
@@ -5411,7 +5686,7 @@ function isExactCommittedTarget(
5411
5686
  current.authorityOwnerGeneration > expected.authorityOwnerGeneration &&
5412
5687
  current.ownerId === target.ownerId &&
5413
5688
  current.ownerLeaseGeneration === target.ownerLeaseGeneration &&
5414
- String(current.allocation.descriptor.rid) === target.nodeRid &&
5689
+ routingIdsEqual(current.allocation.descriptor.rid, target.nodeRid) &&
5415
5690
  current.allocation.descriptorLifecycleGeneration === target.nodeGeneration &&
5416
5691
  Buffer.from(current.payload).equals(Buffer.from(payload))
5417
5692
  );
@@ -5440,7 +5715,7 @@ function sameRequestSource(
5440
5715
  return (
5441
5716
  left.ownerId === right.ownerId &&
5442
5717
  left.leaseGeneration === right.leaseGeneration &&
5443
- left.nodeRid === right.nodeRid &&
5718
+ routingIdsEqual(left.nodeRid, right.nodeRid) &&
5444
5719
  left.nodeGeneration === right.nodeGeneration
5445
5720
  );
5446
5721
  }
@@ -5451,7 +5726,7 @@ function handoffSourceFence(
5451
5726
  return {
5452
5727
  ownerId: value.ownerId,
5453
5728
  leaseGeneration: BigInt(value.ownerLeaseGeneration),
5454
- nodeRid: value.nodeRid,
5729
+ nodeRid: decodeRoutingId(value.nodeRid, value.nodeRidHex),
5455
5730
  nodeGeneration: BigInt(value.nodeGeneration)
5456
5731
  };
5457
5732
  }
@@ -5460,14 +5735,34 @@ function maintenanceReplyRelay(
5460
5735
  stage: LocalStage,
5461
5736
  coordinator: ServiceWireRelocationCoordinatorFence,
5462
5737
  participantId: bigint,
5463
- completion: RelocationTerminalCompletion
5738
+ completion: RelocationTerminalCompletion,
5739
+ packet: ZLinkActorHandoffPacket,
5740
+ serializers: Parameters<typeof encodeActorReply>[3],
5741
+ flowEnabled: boolean
5464
5742
  ): ServiceMaintenanceReplyRelay {
5465
5743
  const value = completion.result.ok ? completion.result.response : undefined;
5744
+ const direct = packet.messageFollowOrigin?.requestSource !== undefined;
5745
+ const application =
5746
+ direct && completion.result.ok
5747
+ ? decodeApplicationPayloadView(
5748
+ encodeMultipartApplicationPayload(
5749
+ encodeActorReply(
5750
+ Buffer.from(packet.header, 'base64'),
5751
+ ZLinkStreamMessageKind.Response,
5752
+ value,
5753
+ serializers,
5754
+ flowEnabled
5755
+ ),
5756
+ SERVICE_FRAMEWORK_MULTIPART_PACKET_NAME,
5757
+ SERVICE_FRAMEWORK_MULTIPART_CONTENT_TYPE
5758
+ )
5759
+ )
5760
+ : undefined;
5466
5761
  return {
5467
5762
  relocation: relocationWireId(stage.staging.envelope.aggregateId),
5468
5763
  targetAttemptGeneration: stage.offer.prepare.targetAttemptGeneration,
5469
5764
  coordinator,
5470
- operation: operationWireId(completion.operationId),
5765
+ operation: parseHandoffOperationId(completion.operationId),
5471
5766
  replyRouteId: BigInt(completion.source.replyRouteId),
5472
5767
  participantId,
5473
5768
  sequence: BigInt(completion.index + 1),
@@ -5475,15 +5770,23 @@ function maintenanceReplyRelay(
5475
5770
  failureCode: completion.result.ok
5476
5771
  ? ServiceWireFrameworkErrorCode.none
5477
5772
  : ServiceWireFrameworkErrorCode.requestFailed,
5478
- ...(value === undefined
5479
- ? {}
5480
- : {
5773
+ ...(application !== undefined
5774
+ ? {
5481
5775
  payload: {
5482
- packetName: SERVICE_RELOCATION_REPLY_PACKET_NAME,
5483
- contentType: ZlinkStreamContentType.Json,
5484
- bytes: Buffer.from(JSON.stringify(value), 'utf8')
5776
+ packetName: application.packetName,
5777
+ contentType: application.contentType,
5778
+ bytes: application.payload
5485
5779
  }
5486
- })
5780
+ }
5781
+ : value === undefined
5782
+ ? {}
5783
+ : {
5784
+ payload: {
5785
+ packetName: SERVICE_RELOCATION_REPLY_PACKET_NAME,
5786
+ contentType: ZlinkStreamContentType.Json,
5787
+ bytes: Buffer.from(JSON.stringify(value), 'utf8')
5788
+ }
5789
+ })
5487
5790
  };
5488
5791
  }
5489
5792
 
@@ -5519,6 +5822,14 @@ function wireIdText(value: { readonly high: bigint; readonly low: bigint }): str
5519
5822
  */
5520
5823
  export class ServiceRelocationDataLostError extends Error {}
5521
5824
 
5825
+ /** The local source lifecycle ended a pending relocation operation. */
5826
+ class ServiceRelocationRuntimeStoppedError extends Error {
5827
+ constructor() {
5828
+ super('Relocation runtime stopped.');
5829
+ this.name = 'AbortError';
5830
+ }
5831
+ }
5832
+
5522
5833
  /** Classifies relocation failure with the error model §2.1 mapping. */
5523
5834
  export function relocationFailedFailureCode(error: unknown): number {
5524
5835
  if (error instanceof ServiceRelocationDataLostError) {
@@ -5653,7 +5964,7 @@ function validateTargetOneWayControl(
5653
5964
  ): void {
5654
5965
  const prepare = stage.offer.prepare;
5655
5966
  if (
5656
- String(sourceNodeRid) !== stage.offer.authenticatedSourceNodeRid ||
5967
+ !routingIdsEqual(sourceNodeRid, stage.offer.authenticatedSourceNodeRid) ||
5657
5968
  !sameWireId(request.relocation, prepare.relocation) ||
5658
5969
  request.targetAttemptGeneration !== prepare.targetAttemptGeneration ||
5659
5970
  !sameCoordinator(request.coordinator, prepare.coordinator) ||
@@ -5731,12 +6042,7 @@ function canonicalQueuedFrozenRecord(
5731
6042
  const source =
5732
6043
  packet.source === undefined
5733
6044
  ? coordinatorSource(coordinator)
5734
- : {
5735
- ownerId: packet.source.ownerId,
5736
- leaseGeneration: BigInt(packet.source.ownerLeaseGeneration),
5737
- nodeRid: packet.source.nodeRid,
5738
- nodeGeneration: BigInt(packet.source.nodeGeneration)
5739
- };
6045
+ : handoffSourceFence(packet.source);
5740
6046
  const operationId = parseHandoffOperationId(packet.messageFollowContext.operationId);
5741
6047
  if (packet.returnResponse && packet.source === undefined) {
5742
6048
  throw new Error('Captured Actor request has no exact request-source fence.');
@@ -5874,17 +6180,13 @@ function sessionRelocationRouteSubmitKey(
5874
6180
  request: ServiceSessionRelocationRoute,
5875
6181
  targetNodeRid: RoutingId
5876
6182
  ): string {
5877
- return `${sessionRelocationIdentityKey(request)}:${String(targetNodeRid)}`;
6183
+ return `${sessionRelocationIdentityKey(request)}:${encodeRoutingIdStorageHex(targetNodeRid)}`;
5878
6184
  }
5879
6185
 
5880
6186
  function sessionRelocationResponseKey(response: ServiceSessionRelocationSealed): string {
5881
6187
  return sessionRelocationIdentityKey(response);
5882
6188
  }
5883
6189
 
5884
- function sessionRelocationResponseFingerprint(response: ServiceSessionRelocationSealed): string {
5885
- return encodeSessionRelocationSealed(response).toString('base64');
5886
- }
5887
-
5888
6190
  function sessionRelocationIdentityKey(
5889
6191
  value:
5890
6192
  ServiceSessionRelocationSeal | ServiceSessionRelocationSealed | ServiceSessionRelocationRoute
@@ -5899,16 +6201,16 @@ function validateSessionRelocationResponse(
5899
6201
  const sameCoordinator =
5900
6202
  request.coordinator.ownerId === response.coordinator.ownerId &&
5901
6203
  request.coordinator.leaseGeneration === response.coordinator.leaseGeneration &&
5902
- request.coordinator.nodeRid === response.coordinator.nodeRid &&
6204
+ routingIdsEqual(request.coordinator.nodeRid, response.coordinator.nodeRid) &&
5903
6205
  request.coordinator.nodeGeneration === response.coordinator.nodeGeneration &&
5904
6206
  request.coordinator.expectedAuthorityStoreVersion ===
5905
6207
  response.coordinator.expectedAuthorityStoreVersion;
5906
6208
  const sameSession =
5907
- request.session.sessionOwnerNodeRid === response.session.sessionOwnerNodeRid &&
6209
+ routingIdsEqual(request.session.sessionOwnerNodeRid, response.session.sessionOwnerNodeRid) &&
5908
6210
  request.session.sessionOwnerNodeGeneration === response.session.sessionOwnerNodeGeneration &&
5909
6211
  request.session.sessionOwnerId === response.session.sessionOwnerId &&
5910
6212
  request.session.sessionOwnerLeaseGeneration === response.session.sessionOwnerLeaseGeneration &&
5911
- request.session.sessionRid === response.session.sessionRid &&
6213
+ routingIdsEqual(request.session.sessionRid, response.session.sessionRid) &&
5912
6214
  request.session.bindingGeneration === response.session.bindingGeneration;
5913
6215
  if (
5914
6216
  request.relocation.high !== response.relocation.high ||
@@ -5921,7 +6223,7 @@ function validateSessionRelocationResponse(
5921
6223
  if (
5922
6224
  request.actor.actor.actorId !== response.actor.actor.actorId ||
5923
6225
  request.actor.actor.generation !== response.actor.actor.generation ||
5924
- request.actor.actor.nodeRid !== response.actor.actor.nodeRid ||
6226
+ !routingIdsEqual(request.actor.actor.nodeRid, response.actor.actor.nodeRid) ||
5925
6227
  request.actor.targetNodeGeneration !== response.actor.targetNodeGeneration ||
5926
6228
  request.actor.authorityOwnerGeneration !== response.actor.authorityOwnerGeneration ||
5927
6229
  request.actor.ownerLeaseGeneration !== response.actor.ownerLeaseGeneration
@@ -5944,17 +6246,20 @@ function replyRelayIdentityKey(value: {
5944
6246
  value.relocation.low.toString(),
5945
6247
  value.coordinator.ownerId,
5946
6248
  value.coordinator.leaseGeneration.toString(),
5947
- value.coordinator.nodeRid,
6249
+ encodeRoutingIdStorageHex(value.coordinator.nodeRid),
5948
6250
  value.coordinator.nodeGeneration.toString(),
5949
6251
  value.coordinator.expectedAuthorityStoreVersion
5950
6252
  ]);
5951
6253
  }
5952
6254
 
5953
6255
  function replyRelayPendingKey(
5954
- ackTargetNodeRid: string,
6256
+ ackTargetNodeRid: RoutingId,
5955
6257
  value: Parameters<typeof replyRelayIdentityKey>[0]
5956
6258
  ): string {
5957
- return JSON.stringify([ackTargetNodeRid, replyRelayIdentityKey(value)]);
6259
+ return JSON.stringify([
6260
+ encodeRoutingIdStorageHex(ackTargetNodeRid),
6261
+ replyRelayIdentityKey(value)
6262
+ ]);
5958
6263
  }
5959
6264
 
5960
6265
  export function encodeHandoffQueuedMessages(
@@ -6104,6 +6409,7 @@ function encodeActorSession(target: ZLinkRemoteBoundSessionTarget | undefined):
6104
6409
  version: ACTOR_SESSION_JOURNAL_VERSION,
6105
6410
  routerChannelId: target.routerChannelId,
6106
6411
  targetNodeRid: String(target.targetNodeRid),
6412
+ targetNodeRidHex: encodeRoutingIdStorageHex(target.targetNodeRid),
6107
6413
  spotId: String(target.spotId),
6108
6414
  sessionNodeRid:
6109
6415
  target.sessionNodeRid === undefined ? undefined : String(target.sessionNodeRid),
@@ -6130,12 +6436,15 @@ function encodeActorSession(target: ZLinkRemoteBoundSessionTarget | undefined):
6130
6436
  coordinatorOwnerId: target.serviceWireRelocation.coordinator.ownerId,
6131
6437
  coordinatorLeaseGeneration:
6132
6438
  target.serviceWireRelocation.coordinator.leaseGeneration.toString(),
6133
- coordinatorNodeRid: target.serviceWireRelocation.coordinator.nodeRid,
6439
+ coordinatorNodeRid: String(target.serviceWireRelocation.coordinator.nodeRid),
6440
+ coordinatorNodeRidHex: encodeRoutingIdStorageHex(
6441
+ target.serviceWireRelocation.coordinator.nodeRid
6442
+ ),
6134
6443
  coordinatorNodeGeneration:
6135
6444
  target.serviceWireRelocation.coordinator.nodeGeneration.toString(),
6136
6445
  expectedAuthorityStoreVersion:
6137
6446
  target.serviceWireRelocation.coordinator.expectedAuthorityStoreVersion,
6138
- sessionOwnerNodeRid: target.serviceWireRelocation.session.sessionOwnerNodeRid,
6447
+ sessionOwnerNodeRid: String(target.serviceWireRelocation.session.sessionOwnerNodeRid),
6139
6448
  sessionOwnerNodeRidHex: encodeRoutingIdStorageHex(
6140
6449
  target.serviceWireRelocation.session.sessionOwnerNodeRid as RoutingId
6141
6450
  ),
@@ -6144,7 +6453,7 @@ function encodeActorSession(target: ZLinkRemoteBoundSessionTarget | undefined):
6144
6453
  sessionOwnerId: target.serviceWireRelocation.session.sessionOwnerId,
6145
6454
  sessionOwnerLeaseGeneration:
6146
6455
  target.serviceWireRelocation.session.sessionOwnerLeaseGeneration.toString(),
6147
- sessionRid: target.serviceWireRelocation.session.sessionRid,
6456
+ sessionRid: String(target.serviceWireRelocation.session.sessionRid),
6148
6457
  sessionRidHex: encodeRoutingIdStorageHex(
6149
6458
  target.serviceWireRelocation.session.sessionRid as RoutingId
6150
6459
  ),
@@ -6171,7 +6480,7 @@ function decodeActorSession(payload: Uint8Array): ZLinkRemoteBoundSessionTarget
6171
6480
  const serviceWire = decodeActorSessionServiceWireFence(value.serviceWireRelocation);
6172
6481
  return {
6173
6482
  routerChannelId: value.routerChannelId,
6174
- targetNodeRid: value.targetNodeRid as RoutingId,
6483
+ targetNodeRid: decodeRoutingId(value.targetNodeRid, value.targetNodeRidHex),
6175
6484
  spotId: value.spotId as RoutingId,
6176
6485
  ...(typeof value.sessionNodeRid === 'string'
6177
6486
  ? { sessionNodeRid: decodeRoutingId(value.sessionNodeRid, value.sessionNodeRidHex) }
@@ -6242,7 +6551,7 @@ function decodeActorSessionServiceWireFence(
6242
6551
  coordinator: {
6243
6552
  ownerId: text('coordinatorOwnerId'),
6244
6553
  leaseGeneration: positive('coordinatorLeaseGeneration'),
6245
- nodeRid: text('coordinatorNodeRid'),
6554
+ nodeRid: decodeRoutingId(text('coordinatorNodeRid'), value.coordinatorNodeRidHex),
6246
6555
  nodeGeneration: positive('coordinatorNodeGeneration'),
6247
6556
  expectedAuthorityStoreVersion: text('expectedAuthorityStoreVersion')
6248
6557
  },