@zlink-systems/framework 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (557) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Channels/IZLinkFanoutClient.d.ts +0 -6
  3. package/dist/contracts/Common/CoreTypes.d.ts +6 -0
  4. package/dist/contracts/Common/CoreTypes.js +7 -0
  5. package/dist/contracts/Common/index.d.ts +1 -1
  6. package/dist/contracts/Common/index.js +0 -1
  7. package/dist/contracts/Configuration/InternalDefaults.d.ts +8 -0
  8. package/dist/contracts/Configuration/InternalDefaults.js +9 -1
  9. package/dist/contracts/Configuration/Registration.d.ts +13 -2
  10. package/dist/contracts/Configuration/Registration.js +58 -36
  11. package/dist/contracts/Configuration/RegistrationBuilderPolicy.d.ts +13 -1
  12. package/dist/contracts/Configuration/RegistrationBuilderPolicy.js +38 -1
  13. package/dist/contracts/Configuration/RegistrationBuilders.d.ts +2 -3
  14. package/dist/contracts/Configuration/RegistrationBuilders.js +27 -32
  15. package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -0
  16. package/dist/contracts/Configuration/RegistrationValidators.js +15 -12
  17. package/dist/contracts/Configuration/TimerRegistrationValidator.js +3 -2
  18. package/dist/contracts/Dispatch/ZLinkDispatchOptions.d.ts +1 -1
  19. package/dist/contracts/Handlers/JsonContract.js +10 -4
  20. package/dist/contracts/Locations/Authority.d.ts +1 -1
  21. package/dist/contracts/Locations/Authority.js +3 -2
  22. package/dist/contracts/Locations/Options.js +1 -1
  23. package/dist/contracts/Locations/Stores.d.ts +12 -0
  24. package/dist/contracts/Locations/Stores.js +9 -0
  25. package/dist/contracts/RouteMesh/RuntimeTopology.d.ts +8 -0
  26. package/dist/contracts/Spots/Contracts.d.ts +1 -1
  27. package/dist/internal.d.ts +5 -0
  28. package/dist/internal.js +16 -1
  29. package/dist/nest-integration.d.ts +4 -2
  30. package/dist/nest-integration.js +14 -4
  31. package/dist/runtime/abort.d.ts +1 -0
  32. package/dist/runtime/abort.js +8 -5
  33. package/dist/runtime/activation-admission.d.ts +28 -0
  34. package/dist/runtime/activation-admission.js +57 -0
  35. package/dist/runtime/actors/actor-authority-payload-codec.d.ts +1 -2
  36. package/dist/runtime/actors/actor-authority-payload-codec.js +34 -18
  37. package/dist/runtime/actors/actor-client.d.ts +2 -2
  38. package/dist/runtime/actors/actor-client.js +42 -143
  39. package/dist/runtime/actors/actor-context.d.ts +3 -5
  40. package/dist/runtime/actors/actor-context.js +19 -17
  41. package/dist/runtime/actors/actor-handoff.d.ts +13 -7
  42. package/dist/runtime/actors/actor-handoff.js +36 -25
  43. package/dist/runtime/actors/actor-lifecycle-snapshot.js +2 -1
  44. package/dist/runtime/actors/actor-local-native-join.d.ts +11 -6
  45. package/dist/runtime/actors/actor-local-native-join.js +85 -123
  46. package/dist/runtime/actors/actor-native-lookup.js +2 -8
  47. package/dist/runtime/actors/actor-packet-relay-wire.d.ts +3 -3
  48. package/dist/runtime/actors/actor-packet-relay-wire.js +5 -3
  49. package/dist/runtime/actors/actor-remote-joiner.d.ts +3 -3
  50. package/dist/runtime/actors/actor-remote-joiner.js +11 -17
  51. package/dist/runtime/actors/actor-remote-wire.js +5 -3
  52. package/dist/runtime/actors/actor-request-deadline.d.ts +3 -0
  53. package/dist/runtime/actors/actor-request-deadline.js +31 -0
  54. package/dist/runtime/actors/actor-retry-delay.js +2 -1
  55. package/dist/runtime/actors/actor-runtime-contracts.d.ts +4 -2
  56. package/dist/runtime/actors/actor-runtime-state.d.ts +9 -21
  57. package/dist/runtime/actors/actor-runtime-state.js +70 -187
  58. package/dist/runtime/actors/actor-source-transfer.d.ts +5 -5
  59. package/dist/runtime/actors/bound-session-wire.js +2 -7
  60. package/dist/runtime/actors/deferred-join-completion.d.ts +19 -0
  61. package/dist/runtime/actors/deferred-join-completion.js +21 -0
  62. package/dist/runtime/actors/index.d.ts +10 -8
  63. package/dist/runtime/actors/index.js +101 -73
  64. package/dist/runtime/actors/spot-actor-dispatch.d.ts +3 -3
  65. package/dist/runtime/actors/spot-actor-dispatch.js +25 -10
  66. package/dist/runtime/actors/transferred-actor-rollback.d.ts +3 -5
  67. package/dist/runtime/actors/transferred-actor-rollback.js +9 -42
  68. package/dist/runtime/admission.d.ts +7 -1
  69. package/dist/runtime/admission.js +11 -1
  70. package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +1 -1
  71. package/dist/runtime/application-jobs/application-job-queue-scope.js +1 -1
  72. package/dist/runtime/backend/contracts/index.d.ts +8 -33
  73. package/dist/runtime/backend/mesh-actor-session-node-adapter.js +3 -26
  74. package/dist/runtime/backend/mesh-dispatch-pump.d.ts +1 -2
  75. package/dist/runtime/backend/mesh-dispatch-pump.js +95 -107
  76. package/dist/runtime/backend/node/node-backend-adapter-factory.d.ts +1 -2
  77. package/dist/runtime/backend/node/node-backend-adapter-factory.js +4 -18
  78. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +7 -4
  79. package/dist/runtime/backend/node/node-backend-adapter-support.js +28 -34
  80. package/dist/runtime/backend/node/node-event-loop-poller.d.ts +2 -2
  81. package/dist/runtime/backend/node/node-event-loop-poller.js +11 -6
  82. package/dist/runtime/backend/node/node-monitor-backend-adapter.js +1 -0
  83. package/dist/runtime/backend/node/node-raw-binding-port.d.ts +1 -1
  84. package/dist/runtime/backend/node/node-raw-binding-port.js +51 -19
  85. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +21 -19
  86. package/dist/runtime/backend/node/node-raw-mesh-backend.js +171 -111
  87. package/dist/runtime/backend/node/node-socket-backend-adapter.js +4 -3
  88. package/dist/runtime/backend/raw-binding-port.d.ts +13 -1
  89. package/dist/runtime/backend/runtime-values.d.ts +4 -1
  90. package/dist/runtime/backend/runtime-values.js +2 -0
  91. package/dist/runtime/channels/channel-clients.d.ts +20 -9
  92. package/dist/runtime/channels/channel-clients.js +68 -60
  93. package/dist/runtime/channels/channel-envelope-inspection.js +2 -1
  94. package/dist/runtime/channels/channel-envelope.js +47 -29
  95. package/dist/runtime/channels/channel-outbound-operations.d.ts +3 -0
  96. package/dist/runtime/channels/channel-outbound-operations.js +9 -18
  97. package/dist/runtime/channels/channel-receive-loops.d.ts +8 -3
  98. package/dist/runtime/channels/channel-receive-loops.js +27 -6
  99. package/dist/runtime/channels/channel-runtime-lifecycle.d.ts +12 -1
  100. package/dist/runtime/channels/channel-runtime-lifecycle.js +48 -28
  101. package/dist/runtime/channels/channel-runtime-manager.d.ts +2 -5
  102. package/dist/runtime/channels/channel-runtime-manager.js +4 -12
  103. package/dist/runtime/channels/channel-socket-options.js +3 -2
  104. package/dist/runtime/channels/channel-socket-registry.d.ts +30 -32
  105. package/dist/runtime/channels/channel-socket-registry.js +296 -235
  106. package/dist/runtime/channels/channel-transports.d.ts +11 -20
  107. package/dist/runtime/channels/channel-transports.js +68 -47
  108. package/dist/runtime/channels/client-server-location-runtime.d.ts +1 -1
  109. package/dist/runtime/channels/client-server-location-runtime.js +10 -11
  110. package/dist/runtime/channels/client-server-service-wire.d.ts +7 -2
  111. package/dist/runtime/channels/client-server-service-wire.js +32 -11
  112. package/dist/runtime/channels/dispatch-error-reporter.d.ts +1 -2
  113. package/dist/runtime/channels/dispatch-error-reporter.js +2 -1
  114. package/dist/runtime/channels/fanout-location-runtime.d.ts +16 -3
  115. package/dist/runtime/channels/fanout-location-runtime.js +133 -81
  116. package/dist/runtime/channels/spot-direct-envelope.d.ts +1 -0
  117. package/dist/runtime/channels/spot-direct-envelope.js +4 -3
  118. package/dist/runtime/channels/spot-node-route-transport.js +3 -2
  119. package/dist/runtime/diagnostics/diagnostic-text.d.ts +2 -0
  120. package/dist/runtime/diagnostics/diagnostic-text.js +7 -0
  121. package/dist/runtime/diagnostics/dispatch-error-details.d.ts +3 -0
  122. package/dist/runtime/diagnostics/dispatch-error-details.js +30 -2
  123. package/dist/runtime/diagnostics/flow-context.d.ts +6 -0
  124. package/dist/runtime/diagnostics/flow-context.js +16 -2
  125. package/dist/runtime/diagnostics/index.d.ts +4 -2
  126. package/dist/runtime/diagnostics/index.js +14 -4
  127. package/dist/runtime/diagnostics/message-flow.d.ts +1 -1
  128. package/dist/runtime/diagnostics/message-flow.js +10 -19
  129. package/dist/runtime/diagnostics/runtime-metrics.d.ts +94 -31
  130. package/dist/runtime/diagnostics/runtime-metrics.js +168 -105
  131. package/dist/runtime/diagnostics/runtime-observation-queue.js +3 -2
  132. package/dist/runtime/diagnostics/topology-runtime-projections.d.ts +2 -4
  133. package/dist/runtime/diagnostics/topology-runtime-projections.js +135 -154
  134. package/dist/runtime/disposal.d.ts +5 -0
  135. package/dist/runtime/disposal.js +22 -0
  136. package/dist/runtime/execution/index.d.ts +22 -9
  137. package/dist/runtime/execution/index.js +107 -37
  138. package/dist/runtime/execution/serial-execution-queue.d.ts +12 -7
  139. package/dist/runtime/execution/serial-execution-queue.js +99 -26
  140. package/dist/runtime/execution/state-lane.d.ts +8 -1
  141. package/dist/runtime/execution/state-lane.js +118 -3
  142. package/dist/runtime/foundation/actor-join-recovery-codec.d.ts +3 -0
  143. package/dist/runtime/foundation/actor-join-recovery-codec.js +15 -11
  144. package/dist/runtime/foundation/event-loop-resources.d.ts +2 -24
  145. package/dist/runtime/foundation/event-loop-resources.js +6 -119
  146. package/dist/runtime/foundation/listener-records.d.ts +11 -0
  147. package/dist/runtime/foundation/listener-records.js +20 -0
  148. package/dist/runtime/foundation/operation-identity.d.ts +2 -0
  149. package/dist/runtime/foundation/operation-identity.js +9 -5
  150. package/dist/runtime/foundation/operation-registry.d.ts +5 -7
  151. package/dist/runtime/foundation/operation-registry.js +34 -44
  152. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +18 -26
  153. package/dist/runtime/foundation/raw-service-mesh-runtime.js +122 -469
  154. package/dist/runtime/foundation/runtime-state-projections.d.ts +3 -1
  155. package/dist/runtime/foundation/runtime-state-projections.js +8 -0
  156. package/dist/runtime/foundation/service-authority-payload-codec.d.ts +1 -0
  157. package/dist/runtime/foundation/service-authority-payload-codec.js +30 -12
  158. package/dist/runtime/foundation/service-discovery-registry.d.ts +4 -0
  159. package/dist/runtime/foundation/service-discovery-registry.js +22 -3
  160. package/dist/runtime/foundation/service-instance-activation-recovery-codec.js +8 -4
  161. package/dist/runtime/foundation/service-liveness-registry.js +2 -1
  162. package/dist/runtime/foundation/service-maintenance-runtime.d.ts +0 -1
  163. package/dist/runtime/foundation/service-maintenance-runtime.js +23 -8
  164. package/dist/runtime/foundation/service-metadata-codec.js +3 -3
  165. package/dist/runtime/foundation/service-relocation-aggregate-committer.d.ts +2 -5
  166. package/dist/runtime/foundation/service-relocation-aggregate-committer.js +1 -48
  167. package/dist/runtime/foundation/service-relocation-coordinator.d.ts +9 -0
  168. package/dist/runtime/foundation/service-relocation-coordinator.js +14 -1
  169. package/dist/runtime/foundation/service-relocation-object-owner.d.ts +8 -0
  170. package/dist/runtime/foundation/service-relocation-object-owner.js +19 -6
  171. package/dist/runtime/foundation/service-relocation-runtime.js +43 -32
  172. package/dist/runtime/foundation/service-runtime-contracts.d.ts +40 -8
  173. package/dist/runtime/foundation/service-runtime-contracts.js +25 -1
  174. package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +0 -19
  175. package/dist/runtime/foundation/service-stateful-registry.d.ts +2 -0
  176. package/dist/runtime/foundation/service-stateful-registry.js +10 -2
  177. package/dist/runtime/foundation/service-stateful-runtime.d.ts +28 -24
  178. package/dist/runtime/foundation/service-stateful-runtime.js +297 -244
  179. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +12 -7
  180. package/dist/runtime/foundation/service-stateful-wire-codec.js +240 -143
  181. package/dist/runtime/foundation/service-topology-registry.d.ts +1 -3
  182. package/dist/runtime/foundation/service-topology-registry.js +22 -61
  183. package/dist/runtime/foundation/service-wire-binary-primitives.d.ts +5 -0
  184. package/dist/runtime/foundation/service-wire-binary-primitives.js +6 -0
  185. package/dist/runtime/foundation/service-wire-codec.js +10 -8
  186. package/dist/runtime/foundation/service-wire-constants.generated.d.ts +1 -0
  187. package/dist/runtime/foundation/service-wire-constants.generated.js +2 -1
  188. package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +2 -0
  189. package/dist/runtime/foundation/service-wire-m6a-codec.js +23 -13
  190. package/dist/runtime/framework-errors-internal.d.ts +2 -1
  191. package/dist/runtime/framework-errors-internal.js +343 -122
  192. package/dist/runtime/handlers/handler-instance-scope.d.ts +3 -1
  193. package/dist/runtime/handlers/handler-instance-scope.js +61 -74
  194. package/dist/runtime/host/actor-packet-relay.d.ts +9 -6
  195. package/dist/runtime/host/actor-packet-relay.js +48 -91
  196. package/dist/runtime/host/actor-placement-coordinator.js +7 -5
  197. package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -0
  198. package/dist/runtime/host/actor-runtime-options-factory.js +17 -4
  199. package/dist/runtime/host/actor-transfer-runtime.d.ts +28 -28
  200. package/dist/runtime/host/actor-transfer-runtime.js +210 -256
  201. package/dist/runtime/host/application-job-queue.js +16 -33
  202. package/dist/runtime/host/bound-session-relay.js +1 -1
  203. package/dist/runtime/host/channel-runtime-options-factory.js +1 -1
  204. package/dist/runtime/host/index.d.ts +19 -14
  205. package/dist/runtime/host/index.js +234 -175
  206. package/dist/runtime/host/instance-activation-authority.d.ts +2 -3
  207. package/dist/runtime/host/instance-activation-authority.js +17 -45
  208. package/dist/runtime/host/location-runtime-owner.d.ts +1 -1
  209. package/dist/runtime/host/location-runtime-owner.js +2 -2
  210. package/dist/runtime/host/remote-actor-join-receiver.d.ts +2 -2
  211. package/dist/runtime/host/remote-actor-join-receiver.js +1 -14
  212. package/dist/runtime/host/remote-actor-packet-target-store.d.ts +3 -2
  213. package/dist/runtime/host/remote-actor-packet-target-store.js +8 -47
  214. package/dist/runtime/host/remote-bound-session-relay.d.ts +2 -3
  215. package/dist/runtime/host/remote-bound-session-relay.js +11 -23
  216. package/dist/runtime/host/route-mesh-runtime.d.ts +14 -10
  217. package/dist/runtime/host/route-mesh-runtime.js +178 -257
  218. package/dist/runtime/host/runtime-shutdown.d.ts +2 -0
  219. package/dist/runtime/host/runtime-shutdown.js +18 -21
  220. package/dist/runtime/host/service-relocation-control.js +6 -1
  221. package/dist/runtime/host/service-relocation-host-runtime.d.ts +71 -38
  222. package/dist/runtime/host/service-relocation-host-runtime.js +816 -651
  223. package/dist/runtime/host/spot-address-transport.d.ts +2 -3
  224. package/dist/runtime/host/spot-address-transport.js +34 -136
  225. package/dist/runtime/host/spot-node-runtime-options-factory.d.ts +1 -1
  226. package/dist/runtime/host/spot-node-runtime-options-factory.js +3 -2
  227. package/dist/runtime/host/spot-runtime-options-factory.d.ts +4 -7
  228. package/dist/runtime/host/spot-runtime-options-factory.js +53 -21
  229. package/dist/runtime/host/stateful-authority-route-runtime.js +6 -2
  230. package/dist/runtime/host/user-spot-creation-coordinator.d.ts +14 -6
  231. package/dist/runtime/host/user-spot-creation-coordinator.js +96 -95
  232. package/dist/runtime/locations/aggregate-inventory-store.js +13 -10
  233. package/dist/runtime/locations/authority-key-codec.js +4 -3
  234. package/dist/runtime/locations/auto-connect-reconciler.d.ts +4 -0
  235. package/dist/runtime/locations/auto-connect-reconciler.js +37 -25
  236. package/dist/runtime/locations/creation-operation-id.js +6 -4
  237. package/dist/runtime/locations/domain-store-contract.d.ts +3 -3
  238. package/dist/runtime/locations/in-memory-authority-store.js +31 -15
  239. package/dist/runtime/locations/in-memory-location-store.d.ts +2 -2
  240. package/dist/runtime/locations/in-memory-location-store.js +17 -14
  241. package/dist/runtime/locations/in-memory-provider-location-store.d.ts +1 -0
  242. package/dist/runtime/locations/in-memory-provider-location-store.js +79 -27
  243. package/dist/runtime/locations/key-codec.js +3 -2
  244. package/dist/runtime/locations/lease-tracker.d.ts +3 -5
  245. package/dist/runtime/locations/lease-tracker.js +7 -11
  246. package/dist/runtime/locations/lifecycle.d.ts +5 -10
  247. package/dist/runtime/locations/lifecycle.js +4 -33
  248. package/dist/runtime/locations/location-store-failure.d.ts +2 -0
  249. package/dist/runtime/locations/location-store-failure.js +7 -0
  250. package/dist/runtime/locations/location-store-pages.d.ts +3 -0
  251. package/dist/runtime/locations/location-store-pages.js +13 -0
  252. package/dist/runtime/locations/location-store-repository.d.ts +5 -7
  253. package/dist/runtime/locations/location-store-repository.js +303 -332
  254. package/dist/runtime/locations/relocation-blob.js +18 -5
  255. package/dist/runtime/locations/resolvers.d.ts +5 -5
  256. package/dist/runtime/locations/resolvers.js +44 -30
  257. package/dist/runtime/locations/runtime.d.ts +5 -4
  258. package/dist/runtime/locations/runtime.js +123 -92
  259. package/dist/runtime/locations/spot-location-claims.d.ts +8 -2
  260. package/dist/runtime/locations/spot-location-claims.js +40 -23
  261. package/dist/runtime/messaging/payload-codec.js +26 -20
  262. package/dist/runtime/messaging/submission-result.d.ts +3 -0
  263. package/dist/runtime/messaging/submission-result.js +48 -0
  264. package/dist/runtime/protocol/service_wire_codec.generated.d.ts +2 -0
  265. package/dist/runtime/protocol/service_wire_codec.generated.js +17 -8
  266. package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +1 -0
  267. package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +3 -2
  268. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +3 -3
  269. package/dist/runtime/spots/formal-remote-actor-admission-registry.js +4 -4
  270. package/dist/runtime/spots/formal-remote-actor-transfer-registry.d.ts +6 -3
  271. package/dist/runtime/spots/index.d.ts +29 -29
  272. package/dist/runtime/spots/index.js +441 -313
  273. package/dist/runtime/spots/spot-activation-registry.d.ts +4 -4
  274. package/dist/runtime/spots/spot-activation-registry.js +27 -60
  275. package/dist/runtime/spots/spot-activation-state.d.ts +2 -11
  276. package/dist/runtime/spots/spot-activation-state.js +4 -17
  277. package/dist/runtime/spots/spot-activation.d.ts +10 -9
  278. package/dist/runtime/spots/spot-activation.js +103 -81
  279. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +1 -2
  280. package/dist/runtime/spots/spot-actor-admission-coordinator.js +3 -40
  281. package/dist/runtime/spots/spot-actor-join-dispatch.d.ts +10 -18
  282. package/dist/runtime/spots/spot-actor-join-dispatch.js +5 -55
  283. package/dist/runtime/spots/spot-actor-lifecycle-drain.js +2 -2
  284. package/dist/runtime/spots/spot-actor-membership.d.ts +7 -5
  285. package/dist/runtime/spots/spot-actor-membership.js +44 -53
  286. package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +0 -1
  287. package/dist/runtime/spots/spot-actor-packet-dispatch.js +0 -9
  288. package/dist/runtime/spots/spot-actor-packet-relay-dispatch.d.ts +2 -2
  289. package/dist/runtime/spots/spot-actor-packet-relay-dispatch.js +1 -7
  290. package/dist/runtime/spots/spot-context.d.ts +2 -2
  291. package/dist/runtime/spots/spot-entry-activation.d.ts +3 -4
  292. package/dist/runtime/spots/spot-entry-activation.js +9 -21
  293. package/dist/runtime/spots/spot-lifecycle-metrics.js +3 -2
  294. package/dist/runtime/spots/spot-manager-public.d.ts +5 -6
  295. package/dist/runtime/spots/spot-manager-public.js +6 -5
  296. package/dist/runtime/spots/spot-node-autoconnect.js +22 -31
  297. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +22 -21
  298. package/dist/runtime/spots/spot-node-runtime-manager.js +73 -82
  299. package/dist/runtime/spots/spot-outbound.d.ts +4 -3
  300. package/dist/runtime/spots/spot-outbound.js +10 -37
  301. package/dist/runtime/spots/spot-remote-codec.js +5 -3
  302. package/dist/runtime/spots/spot-route-packet-dispatch.d.ts +2 -2
  303. package/dist/runtime/spots/spot-route-packet-dispatch.js +8 -8
  304. package/dist/runtime/spots/spot-route-replies.d.ts +2 -2
  305. package/dist/runtime/spots/spot-route-replies.js +3 -1
  306. package/dist/runtime/spots/spot-routed-actor-admission.d.ts +3 -2
  307. package/dist/runtime/spots/spot-routed-actor-admission.js +3 -2
  308. package/dist/runtime/spots/spot-routed-frame-dispatch.d.ts +2 -7
  309. package/dist/runtime/spots/spot-routed-frame-dispatch.js +1 -70
  310. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.d.ts +4 -0
  311. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.js +65 -15
  312. package/dist/runtime/spots/spot-runtime-ports.d.ts +4 -20
  313. package/dist/runtime/spots/spot-serial-executor.d.ts +5 -0
  314. package/dist/runtime/spots/spot-serial-executor.js +9 -2
  315. package/dist/runtime/spots/spot-serial-turn-executor.d.ts +29 -6
  316. package/dist/runtime/spots/spot-serial-turn-executor.js +89 -13
  317. package/dist/runtime/spots/spot-timer.d.ts +3 -2
  318. package/dist/runtime/streams/actor-session-binding-registry.d.ts +17 -32
  319. package/dist/runtime/streams/actor-session-binding-registry.js +145 -367
  320. package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +2 -6
  321. package/dist/runtime/streams/bound-actor-relay-sender.d.ts +6 -2
  322. package/dist/runtime/streams/bound-actor-relay-sender.js +57 -33
  323. package/dist/runtime/streams/bound-session-service.d.ts +7 -0
  324. package/dist/runtime/streams/bound-session-service.js +12 -19
  325. package/dist/runtime/streams/index.d.ts +15 -14
  326. package/dist/runtime/streams/index.js +38 -39
  327. package/dist/runtime/streams/managed-stream.d.ts +3 -0
  328. package/dist/runtime/streams/managed-stream.js +17 -8
  329. package/dist/runtime/streams/native-fallback-bound-session.js +1 -3
  330. package/dist/runtime/streams/protocol.d.ts +6 -33
  331. package/dist/runtime/streams/protocol.js +38 -110
  332. package/dist/runtime/streams/session-actor-coordinator.d.ts +2 -3
  333. package/dist/runtime/streams/session-actor-coordinator.js +12 -4
  334. package/dist/runtime/streams/session-calls.d.ts +1 -1
  335. package/dist/runtime/streams/session-calls.js +5 -4
  336. package/dist/runtime/streams/session-context.d.ts +6 -6
  337. package/dist/runtime/streams/session-context.js +22 -4
  338. package/dist/runtime/streams/session-requests.js +2 -1
  339. package/dist/runtime/streams/session-serial-executor.js +2 -2
  340. package/dist/runtime/streams/stream-binding-runtime-ports.d.ts +16 -1
  341. package/dist/runtime/streams/stream-frame-factory.js +2 -2
  342. package/dist/runtime/streams/stream-session-runtime.d.ts +10 -11
  343. package/dist/runtime/streams/stream-session-runtime.js +25 -33
  344. package/dist/runtime/workers/index.d.ts +2 -2
  345. package/dist/runtime/workers/index.js +5 -4
  346. package/package.json +3 -3
  347. package/src/contracts/Channels/IZLinkFanoutClient.ts +0 -7
  348. package/src/contracts/Common/CoreTypes.ts +7 -0
  349. package/src/contracts/Common/index.ts +1 -1
  350. package/src/contracts/Configuration/InternalDefaults.ts +12 -0
  351. package/src/contracts/Configuration/Registration.ts +92 -48
  352. package/src/contracts/Configuration/RegistrationBuilderPolicy.ts +44 -2
  353. package/src/contracts/Configuration/RegistrationBuilders.ts +77 -68
  354. package/src/contracts/Configuration/RegistrationTypes.ts +1 -0
  355. package/src/contracts/Configuration/RegistrationValidators.ts +19 -13
  356. package/src/contracts/Configuration/TimerRegistrationValidator.ts +4 -2
  357. package/src/contracts/Dispatch/ZLinkDispatchOptions.ts +1 -0
  358. package/src/contracts/Handlers/JsonContract.ts +14 -4
  359. package/src/contracts/Locations/Authority.ts +7 -3
  360. package/src/contracts/Locations/Options.ts +1 -1
  361. package/src/contracts/Locations/Stores.ts +14 -0
  362. package/src/contracts/RouteMesh/RuntimeTopology.ts +10 -0
  363. package/src/contracts/Spots/Contracts.ts +1 -1
  364. package/src/internal.ts +15 -0
  365. package/src/nest-integration.ts +28 -10
  366. package/src/runtime/abort.ts +6 -5
  367. package/src/runtime/activation-admission.ts +72 -0
  368. package/src/runtime/actors/actor-authority-payload-codec.ts +58 -20
  369. package/src/runtime/actors/actor-client.ts +77 -204
  370. package/src/runtime/actors/actor-context.ts +45 -39
  371. package/src/runtime/actors/actor-handoff.ts +55 -45
  372. package/src/runtime/actors/actor-lifecycle-snapshot.ts +2 -1
  373. package/src/runtime/actors/actor-local-native-join.ts +174 -215
  374. package/src/runtime/actors/actor-native-lookup.ts +2 -9
  375. package/src/runtime/actors/actor-packet-relay-wire.ts +18 -12
  376. package/src/runtime/actors/actor-remote-joiner.ts +15 -36
  377. package/src/runtime/actors/actor-remote-wire.ts +5 -3
  378. package/src/runtime/actors/actor-request-deadline.ts +49 -0
  379. package/src/runtime/actors/actor-retry-delay.ts +2 -1
  380. package/src/runtime/actors/actor-runtime-contracts.ts +4 -10
  381. package/src/runtime/actors/actor-runtime-state.ts +85 -247
  382. package/src/runtime/actors/actor-source-transfer.ts +5 -18
  383. package/src/runtime/actors/bound-session-wire.ts +2 -8
  384. package/src/runtime/actors/deferred-join-completion.ts +44 -0
  385. package/src/runtime/actors/index.ts +122 -88
  386. package/src/runtime/actors/spot-actor-dispatch.ts +60 -29
  387. package/src/runtime/actors/transferred-actor-rollback.ts +11 -62
  388. package/src/runtime/admission.ts +13 -2
  389. package/src/runtime/application-jobs/application-job-queue-scope.ts +4 -4
  390. package/src/runtime/backend/contracts/index.ts +15 -42
  391. package/src/runtime/backend/mesh-actor-session-node-adapter.ts +4 -28
  392. package/src/runtime/backend/mesh-dispatch-pump.ts +111 -117
  393. package/src/runtime/backend/node/node-backend-adapter-factory.ts +5 -25
  394. package/src/runtime/backend/node/node-backend-adapter-support.ts +32 -35
  395. package/src/runtime/backend/node/node-event-loop-poller.ts +16 -8
  396. package/src/runtime/backend/node/node-monitor-backend-adapter.ts +1 -0
  397. package/src/runtime/backend/node/node-raw-binding-port.ts +58 -23
  398. package/src/runtime/backend/node/node-raw-mesh-backend.ts +252 -162
  399. package/src/runtime/backend/node/node-socket-backend-adapter.ts +5 -4
  400. package/src/runtime/backend/raw-binding-port.ts +14 -1
  401. package/src/runtime/backend/runtime-values.ts +4 -1
  402. package/src/runtime/channels/channel-clients.ts +132 -96
  403. package/src/runtime/channels/channel-envelope-inspection.ts +2 -1
  404. package/src/runtime/channels/channel-envelope.ts +58 -29
  405. package/src/runtime/channels/channel-outbound-operations.ts +10 -31
  406. package/src/runtime/channels/channel-receive-loops.ts +39 -7
  407. package/src/runtime/channels/channel-runtime-lifecycle.ts +60 -27
  408. package/src/runtime/channels/channel-runtime-manager.ts +8 -15
  409. package/src/runtime/channels/channel-socket-options.ts +8 -2
  410. package/src/runtime/channels/channel-socket-registry.ts +369 -262
  411. package/src/runtime/channels/channel-transports.ts +161 -115
  412. package/src/runtime/channels/client-server-location-runtime.ts +17 -17
  413. package/src/runtime/channels/client-server-service-wire.ts +40 -12
  414. package/src/runtime/channels/dispatch-error-reporter.ts +7 -5
  415. package/src/runtime/channels/fanout-location-runtime.ts +175 -106
  416. package/src/runtime/channels/spot-direct-envelope.ts +1 -1
  417. package/src/runtime/channels/spot-node-route-transport.ts +5 -2
  418. package/src/runtime/diagnostics/diagnostic-text.ts +4 -0
  419. package/src/runtime/diagnostics/dispatch-error-details.ts +36 -1
  420. package/src/runtime/diagnostics/flow-context.ts +16 -2
  421. package/src/runtime/diagnostics/index.ts +27 -17
  422. package/src/runtime/diagnostics/message-flow.ts +15 -25
  423. package/src/runtime/diagnostics/runtime-metrics.ts +181 -113
  424. package/src/runtime/diagnostics/runtime-observation-queue.ts +3 -2
  425. package/src/runtime/diagnostics/topology-runtime-projections.ts +194 -162
  426. package/src/runtime/disposal.ts +19 -0
  427. package/src/runtime/execution/index.ts +116 -38
  428. package/src/runtime/execution/serial-execution-queue.ts +179 -34
  429. package/src/runtime/execution/state-lane.ts +142 -4
  430. package/src/runtime/foundation/actor-join-recovery-codec.ts +13 -8
  431. package/src/runtime/foundation/event-loop-resources.ts +5 -122
  432. package/src/runtime/foundation/listener-records.ts +21 -0
  433. package/src/runtime/foundation/operation-identity.ts +11 -5
  434. package/src/runtime/foundation/operation-registry.ts +33 -50
  435. package/src/runtime/foundation/raw-service-mesh-runtime.ts +170 -571
  436. package/src/runtime/foundation/runtime-state-projections.ts +9 -1
  437. package/src/runtime/foundation/service-authority-payload-codec.ts +41 -13
  438. package/src/runtime/foundation/service-discovery-registry.ts +32 -3
  439. package/src/runtime/foundation/service-instance-activation-recovery-codec.ts +12 -4
  440. package/src/runtime/foundation/service-liveness-registry.ts +2 -1
  441. package/src/runtime/foundation/service-maintenance-runtime.ts +30 -19
  442. package/src/runtime/foundation/service-metadata-codec.ts +2 -2
  443. package/src/runtime/foundation/service-relocation-aggregate-committer.ts +3 -66
  444. package/src/runtime/foundation/service-relocation-coordinator.ts +13 -0
  445. package/src/runtime/foundation/service-relocation-object-owner.ts +23 -7
  446. package/src/runtime/foundation/service-relocation-runtime.ts +57 -35
  447. package/src/runtime/foundation/service-runtime-contracts.ts +46 -10
  448. package/src/runtime/foundation/service-session-binding-ingress-port.ts +0 -25
  449. package/src/runtime/foundation/service-stateful-registry.ts +14 -2
  450. package/src/runtime/foundation/service-stateful-runtime.ts +435 -327
  451. package/src/runtime/foundation/service-stateful-wire-codec.ts +329 -199
  452. package/src/runtime/foundation/service-topology-registry.ts +30 -79
  453. package/src/runtime/foundation/service-wire-binary-primitives.ts +6 -0
  454. package/src/runtime/foundation/service-wire-codec.ts +15 -8
  455. package/src/runtime/foundation/service-wire-constants.generated.ts +1 -0
  456. package/src/runtime/foundation/service-wire-m6a-codec.ts +29 -15
  457. package/src/runtime/framework-errors-internal.ts +380 -134
  458. package/src/runtime/handlers/handler-instance-scope.ts +68 -95
  459. package/src/runtime/host/actor-packet-relay.ts +57 -147
  460. package/src/runtime/host/actor-placement-coordinator.ts +8 -5
  461. package/src/runtime/host/actor-runtime-options-factory.ts +27 -8
  462. package/src/runtime/host/actor-transfer-runtime.ts +266 -418
  463. package/src/runtime/host/application-job-queue.ts +28 -34
  464. package/src/runtime/host/bound-session-relay.ts +1 -2
  465. package/src/runtime/host/channel-runtime-options-factory.ts +1 -2
  466. package/src/runtime/host/index.ts +408 -312
  467. package/src/runtime/host/instance-activation-authority.ts +89 -109
  468. package/src/runtime/host/location-runtime-owner.ts +4 -2
  469. package/src/runtime/host/remote-actor-join-receiver.ts +3 -31
  470. package/src/runtime/host/remote-actor-packet-target-store.ts +17 -58
  471. package/src/runtime/host/remote-bound-session-relay.ts +12 -44
  472. package/src/runtime/host/route-mesh-runtime.ts +236 -275
  473. package/src/runtime/host/runtime-shutdown.ts +17 -21
  474. package/src/runtime/host/service-relocation-control.ts +14 -1
  475. package/src/runtime/host/service-relocation-host-runtime.ts +1161 -904
  476. package/src/runtime/host/spot-address-transport.ts +73 -195
  477. package/src/runtime/host/spot-node-runtime-options-factory.ts +6 -3
  478. package/src/runtime/host/spot-runtime-options-factory.ts +92 -30
  479. package/src/runtime/host/stateful-authority-route-runtime.ts +8 -4
  480. package/src/runtime/host/user-spot-creation-coordinator.ts +182 -163
  481. package/src/runtime/locations/aggregate-inventory-store.ts +16 -13
  482. package/src/runtime/locations/authority-key-codec.ts +7 -3
  483. package/src/runtime/locations/auto-connect-reconciler.ts +39 -26
  484. package/src/runtime/locations/creation-operation-id.ts +6 -3
  485. package/src/runtime/locations/domain-store-contract.ts +12 -3
  486. package/src/runtime/locations/in-memory-authority-store.ts +44 -15
  487. package/src/runtime/locations/in-memory-location-store.ts +50 -39
  488. package/src/runtime/locations/in-memory-provider-location-store.ts +110 -31
  489. package/src/runtime/locations/key-codec.ts +3 -2
  490. package/src/runtime/locations/lease-tracker.ts +8 -14
  491. package/src/runtime/locations/lifecycle.ts +16 -45
  492. package/src/runtime/locations/location-store-failure.ts +4 -0
  493. package/src/runtime/locations/location-store-pages.ts +18 -0
  494. package/src/runtime/locations/location-store-repository.ts +450 -478
  495. package/src/runtime/locations/relocation-blob.ts +17 -6
  496. package/src/runtime/locations/resolvers.ts +90 -58
  497. package/src/runtime/locations/runtime.ts +220 -110
  498. package/src/runtime/locations/spot-location-claims.ts +54 -26
  499. package/src/runtime/messaging/payload-codec.ts +35 -24
  500. package/src/runtime/messaging/submission-result.ts +57 -1
  501. package/src/runtime/protocol/service_wire_codec.generated.ts +27 -10
  502. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +4 -4
  503. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +6 -6
  504. package/src/runtime/spots/formal-remote-actor-transfer-registry.ts +6 -3
  505. package/src/runtime/spots/index.ts +687 -437
  506. package/src/runtime/spots/spot-activation-registry.ts +42 -71
  507. package/src/runtime/spots/spot-activation-state.ts +4 -16
  508. package/src/runtime/spots/spot-activation.ts +167 -115
  509. package/src/runtime/spots/spot-actor-admission-coordinator.ts +5 -57
  510. package/src/runtime/spots/spot-actor-join-dispatch.ts +25 -79
  511. package/src/runtime/spots/spot-actor-lifecycle-drain.ts +20 -18
  512. package/src/runtime/spots/spot-actor-membership.ts +107 -75
  513. package/src/runtime/spots/spot-actor-packet-dispatch.ts +0 -16
  514. package/src/runtime/spots/spot-actor-packet-relay-dispatch.ts +3 -15
  515. package/src/runtime/spots/spot-context.ts +3 -3
  516. package/src/runtime/spots/spot-entry-activation.ts +13 -33
  517. package/src/runtime/spots/spot-lifecycle-metrics.ts +3 -2
  518. package/src/runtime/spots/spot-manager-public.ts +29 -21
  519. package/src/runtime/spots/spot-node-autoconnect.ts +20 -26
  520. package/src/runtime/spots/spot-node-runtime-manager.ts +145 -140
  521. package/src/runtime/spots/spot-outbound.ts +47 -68
  522. package/src/runtime/spots/spot-remote-codec.ts +7 -3
  523. package/src/runtime/spots/spot-route-packet-dispatch.ts +25 -24
  524. package/src/runtime/spots/spot-route-replies.ts +6 -4
  525. package/src/runtime/spots/spot-routed-actor-admission.ts +13 -6
  526. package/src/runtime/spots/spot-routed-frame-dispatch.ts +6 -77
  527. package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +95 -22
  528. package/src/runtime/spots/spot-runtime-ports.ts +10 -43
  529. package/src/runtime/spots/spot-serial-executor.ts +10 -2
  530. package/src/runtime/spots/spot-serial-turn-executor.ts +120 -13
  531. package/src/runtime/spots/spot-timer.ts +3 -2
  532. package/src/runtime/streams/actor-session-binding-registry.ts +182 -560
  533. package/src/runtime/streams/actor-session-binding-runtime-owner.ts +1 -19
  534. package/src/runtime/streams/bound-actor-relay-sender.ts +89 -43
  535. package/src/runtime/streams/bound-session-service.ts +23 -37
  536. package/src/runtime/streams/index.ts +102 -88
  537. package/src/runtime/streams/managed-stream.ts +29 -10
  538. package/src/runtime/streams/native-fallback-bound-session.ts +1 -3
  539. package/src/runtime/streams/protocol.ts +34 -88
  540. package/src/runtime/streams/session-actor-coordinator.ts +17 -5
  541. package/src/runtime/streams/session-calls.ts +6 -5
  542. package/src/runtime/streams/session-context.ts +54 -10
  543. package/src/runtime/streams/session-requests.ts +2 -1
  544. package/src/runtime/streams/session-serial-executor.ts +2 -2
  545. package/src/runtime/streams/stream-binding-runtime-ports.ts +22 -1
  546. package/src/runtime/streams/stream-frame-factory.ts +2 -3
  547. package/src/runtime/streams/stream-session-runtime.ts +57 -59
  548. package/src/runtime/workers/index.ts +13 -12
  549. package/dist/runtime/actors/deferred-join-accepted-journal.d.ts +0 -47
  550. package/dist/runtime/actors/deferred-join-accepted-journal.js +0 -992
  551. package/dist/runtime/actors/post-commit-actor-location.d.ts +0 -19
  552. package/dist/runtime/actors/post-commit-actor-location.js +0 -72
  553. package/dist/runtime/spots/spot-native-actor-join-admission.d.ts +0 -26
  554. package/dist/runtime/spots/spot-native-actor-join-admission.js +0 -106
  555. package/src/runtime/actors/deferred-join-accepted-journal.ts +0 -1346
  556. package/src/runtime/actors/post-commit-actor-location.ts +0 -121
  557. package/src/runtime/spots/spot-native-actor-join-admission.ts +0 -155
@@ -1,47 +1,88 @@
1
+ import { UINT64_MAX, ZlinkStreamContentType } from '@zlink-systems/stream-wire';
1
2
  import { randomBytes } from 'node:crypto';
2
- import { SubmitResult } from '../backend/runtime-values';
3
- import type {
4
- RoutingId,
5
- Type,
6
- ZLinkActor,
7
- ZLinkActorRelocationAdapter,
8
- ZLinkMeshNodeDescriptor,
9
- ZLinkSpot,
10
- ZLinkSpotRelocationAdapter,
11
- ZLinkInstanceSpot
12
- } from '../../contracts';
13
- import type {
14
- ZLinkAuthorityKey,
15
- ZLinkAuthorityScanCursor,
16
- ZLinkAuthoritySnapshot,
17
- ZLinkCapacityVector,
18
- ZLinkLocationOwnerToken
19
- } from '../locations/internal-location-contracts';
20
3
  import {
4
+ type RoutingId,
5
+ type Type,
6
+ type ZLinkActor,
7
+ type ZLinkActorRelocationAdapter,
8
+ type ZLinkInstanceSpot,
9
+ type ZLinkMeshNodeDescriptor,
10
+ type ZLinkSpot,
11
+ type ZLinkSpotRelocationAdapter,
21
12
  ZLinkFrameworkErrorKind,
22
13
  ZLinkFrameworkException,
23
14
  ZLinkFrameworkRuntimeState,
24
15
  ZLinkObjectRole,
16
+ ZLinkSpotKind,
25
17
  ZLinkSpotRelocationCoordinationMode,
26
18
  ZLinkSpotRelocationReadyOutcome,
27
- ZLinkSpotKind,
28
19
  ZLinkUserSpotExecutionMode
29
20
  } from '../../contracts';
21
+
22
+ import type { ZLinkProviderResolver } from '../../contracts/Common/ZLinkProviderResolver';
30
23
  import { zlinkRuntimeDefaultLocationOptions } from '../../contracts/Locations/Options';
24
+ import { ZLINK_PROVIDER_MAX_PAGE_SIZE } from '../../contracts/Locations/Stores';
25
+ import type { ZLinkActivationAdmission } from '../activation-admission';
26
+ import type { DefaultZLinkActorManager } from '../actors';
31
27
  import {
32
- ZLinkFrameworkInternalErrorKind,
33
- createInternalFrameworkException
34
- } from '../framework-errors-internal';
35
- import type { ZLinkDomainLocationStore as ZLinkLocationStore } from '../locations/domain-store-contract';
36
- import type { ZLinkTrackedInstanceAuthority } from '../locations/spot-location-claims';
37
- import type { ZLinkProviderResolver } from '../../contracts/Common/ZLinkProviderResolver';
38
- import type { ZLinkRuntimeEventPublisher, ZLinkRuntimeMetrics } from '../diagnostics';
39
- import type { ZLinkFrameworkRegistration } from '../configuration';
28
+ decodeRelocatingActorAuthorityIdentity,
29
+ rewriteActorAuthorityRoute
30
+ } from '../actors/actor-authority-publication';
31
+ import {
32
+ replayActorHandoffBacklog,
33
+ type ZLinkActorHandoffPacket,
34
+ type ZLinkActorHandoffResult
35
+ } from '../actors/actor-handoff';
36
+ import type { ZLinkActorJoinRelocation } from '../actors/actor-join-relocation';
37
+ import {
38
+ ZLINK_REMOTE_ACTOR_SOURCE_LEAVE_TERMINAL,
39
+ decodeRemoteActorSourceLeaveTerminal
40
+ } from '../actors/actor-remote-wire';
41
+ import {
42
+ toFrameworkActorRef,
43
+ type ZLinkActorRuntimeState,
44
+ type ZLinkRemoteBoundSessionTarget
45
+ } from '../actors/actor-runtime-state';
40
46
  import type { ZLinkBackendMeshNode, ZLinkMeshCompletionTable } from '../backend';
41
- import { ReceiveKind, type ReceiveRecord } from '../foundation/service-runtime-contracts';
42
- import { decodeRoutingId, encodeRoutingIdStorageHex, routingIdsEqual } from '../routing-id';
43
- import { ServiceWireProtocolError } from '../foundation/service-wire-m6a-codec';
44
- import type { ServiceSpotMessageFollowSeal } from '../foundation/service-stateful-runtime';
47
+ import { RequestResult, SubmitResult } from '../backend/runtime-values';
48
+ import type { ZLinkFrameworkRegistration } from '../configuration';
49
+ import {
50
+ type ZLinkRuntimeEventPublisher,
51
+ type ZLinkRuntimeMetrics,
52
+ isRelocationDebugEnabled,
53
+ relocationDebug
54
+ } from '../diagnostics';
55
+
56
+ import { METRIC_NAMES, MILLISECONDS_PER_SECOND } from '../diagnostics/runtime-metrics';
57
+ import { isStructuralGuardEnabled, trackDiagnosticCompletion } from '../execution/state-lane';
58
+ import {
59
+ RECREATE_RELOCATION_CONTENT_TYPE,
60
+ SNAPSHOT_RELOCATION_CONTENT_TYPE,
61
+ decodeCanonicalActorJoinRecoverySavedWork,
62
+ encodeCanonicalActorJoinRecoverySavedWork,
63
+ type CanonicalActorJoinRecovery
64
+ } from '../foundation/actor-join-recovery-codec';
65
+ import { OPERATION_IDENTITY_BYTES } from '../foundation/operation-identity';
66
+ import { rewriteServiceAuthorityRoute } from '../foundation/service-authority-payload-codec';
67
+ import { ServiceMaintenanceRuntime } from '../foundation/service-maintenance-runtime';
68
+ import {
69
+ ServiceRelocationAggregateCommitter,
70
+ type ServicePreparedRelocationAggregate,
71
+ type ServiceRelocationAggregatePlan
72
+ } from '../foundation/service-relocation-aggregate-committer';
73
+ import {
74
+ ServiceRelocationAuthorityError,
75
+ ServiceRelocationPostCommitError
76
+ } from '../foundation/service-relocation-coordinator';
77
+ import {
78
+ ServiceRelocationObjectCaptureOwner,
79
+ ServiceRelocationObjectRestoreOwner,
80
+ type ServiceCapturedObjectRelocation,
81
+ type ServiceObjectRelocationStaging,
82
+ type ServiceRelocationCaptureUnit,
83
+ type ServiceRelocationHiddenObject,
84
+ type ServiceRelocationTargetObjectPort
85
+ } from '../foundation/service-relocation-object-owner';
45
86
  import {
46
87
  ServiceRelocationAuthorityPayloadCodec,
47
88
  crc32c,
@@ -60,114 +101,108 @@ import {
60
101
  type ServiceRelocationTimer
61
102
  } from '../foundation/service-relocation-runtime';
62
103
  import {
63
- ZLinkRelocationInFlightBudget,
64
- ZLinkRelocationPayloadAssembly,
65
- effectiveActorJoinChunkLimitBytes,
66
- effectiveRelocationBudgetBytes,
67
- planRelocationChunks,
68
- relocationChunkAt
69
- } from './relocation-direct-transfer';
70
- import {
71
- captureRelocationAdapterState,
72
- restoreRelocationAdapterState,
73
- type ZLinkRelocationStateAdapterLike
74
- } from './relocation-state-adapter';
75
- import { ServiceRelocationPostCommitError } from '../foundation/service-relocation-coordinator';
76
- import { ServiceMaintenanceRuntime } from '../foundation/service-maintenance-runtime';
77
- import {
78
- ServiceRelocationObjectCaptureOwner,
79
- ServiceRelocationObjectRestoreOwner,
80
- type ServiceCapturedObjectRelocation,
81
- type ServiceObjectRelocationStaging,
82
- type ServiceRelocationCaptureUnit,
83
- type ServiceRelocationHiddenObject,
84
- type ServiceRelocationTargetObjectPort
85
- } from '../foundation/service-relocation-object-owner';
86
- import {
87
- ServiceRelocationAggregateCommitter,
88
- type ServicePreparedRelocationAggregate,
89
- type ServiceRelocationAggregatePlan
90
- } from '../foundation/service-relocation-aggregate-committer';
91
- import { createProviderInstance } from '../spots/spot-provider';
92
- import type { DefaultZLinkSpotManager, ZLinkSpotNodeRuntimeManager } from '../spots';
93
- import type { ZLinkSpotActivation } from '../spots/spot-activation-state';
94
- import type { DefaultZLinkActorManager } from '../actors';
95
- import type { ZLinkDeferredJoinAcceptedRoot } from '../actors/deferred-join-accepted-journal';
96
- import {
97
- toFrameworkActorRef,
98
- type ZLinkActorRuntimeState,
99
- type ZLinkRemoteBoundSessionTarget
100
- } from '../actors/actor-runtime-state';
101
- import {
102
- replayActorHandoffBacklog,
103
- type ZLinkActorHandoffPacket,
104
- type ZLinkActorHandoffResult
105
- } from '../actors/actor-handoff';
106
- import { decodeHandoffBacklog } from '../spots/spot-remote-codec';
107
- import {
108
- decodeRelocatingActorAuthorityIdentity,
109
- rewriteActorAuthorityRoute
110
- } from '../actors/actor-authority-publication';
111
- import { rewriteServiceAuthorityRoute } from '../foundation/service-authority-payload-codec';
112
- import { committedActorOwnerFence, type ZLinkActorTransferRuntime } from './actor-transfer-runtime';
113
- import { decodeAuthorityKey, encodeAuthorityKey } from '../locations/authority-key-codec';
114
- import {
115
- decodeServiceRelocationControlRequest,
116
- decodeServiceRelocationControlResponse,
117
- encodeServiceRelocationControlRequest,
118
- encodeServiceRelocationControlResponse,
119
- type ZLinkServiceRelocationControlRequest,
120
- type ZLinkServiceRelocationControlResponse
121
- } from './service-relocation-control';
104
+ MeshPeerRuntimeState,
105
+ ReceiveKind,
106
+ type ReceiveRecord
107
+ } from '../foundation/service-runtime-contracts';
108
+ import type { ServiceSpotMessageFollowSeal } from '../foundation/service-stateful-runtime';
122
109
  import {
110
+ M6bServiceWireCommand,
123
111
  decodeMaintenanceReplyRelay,
124
112
  decodeMaintenanceReplyRelayAck,
113
+ decodeServiceWireFrozenRecord,
125
114
  decodeSessionRelocationRoute,
126
115
  decodeSessionRelocationSeal,
127
116
  decodeSessionRelocationSealed,
128
117
  encodeMaintenanceReplyRelay,
129
118
  encodeMaintenanceReplyRelayAck,
119
+ encodeServiceWireFrozenActorApplicationRecord,
130
120
  encodeSessionRelocationRoute,
131
121
  encodeSessionRelocationSeal,
132
122
  encodeSessionRelocationSealed,
133
123
  serviceSessionRelocationIdentityKey,
134
- decodeServiceWireFrozenRecord,
135
- encodeServiceWireFrozenActorApplicationRecord,
136
- M6bServiceWireCommand,
137
- type ServiceMaintenanceReplyRelay,
138
- type ServiceMaintenanceReplyRelayAck,
139
124
  type ServiceMaintenanceRelocationControl,
140
125
  type ServiceMaintenanceRelocationCutover,
141
126
  type ServiceMaintenanceRelocationData,
142
127
  type ServiceMaintenanceRelocationFailed,
143
- type ServiceMaintenanceRelocationReady,
144
128
  type ServiceMaintenanceRelocationPrepare,
129
+ type ServiceMaintenanceRelocationReady,
145
130
  type ServiceMaintenanceRelocationState,
131
+ type ServiceMaintenanceReplyRelay,
132
+ type ServiceMaintenanceReplyRelayAck,
146
133
  type ServiceSessionRelocationRoute,
147
134
  type ServiceSessionRelocationSeal,
148
135
  type ServiceSessionRelocationSealed,
149
136
  type ServiceWireOperationId,
150
- type ServiceWireRequestSourceFence,
151
- type ServiceWireRelocationTarget,
152
137
  type ServiceWireRelocationCoordinatorFence,
153
- type ServiceWireRelocationObject
138
+ type ServiceWireRelocationObject,
139
+ type ServiceWireRelocationTarget,
140
+ type ServiceWireRequestSourceFence
154
141
  } from '../foundation/service-stateful-wire-codec';
155
142
  import { ServiceWireFrameworkErrorCode } from '../foundation/service-wire-constants.generated';
143
+ import { ServiceWireProtocolError } from '../foundation/service-wire-m6a-codec';
144
+ import {
145
+ ZLinkFrameworkInternalErrorKind,
146
+ createInternalFrameworkException,
147
+ frameworkRelocationFailureCode,
148
+ internalFrameworkErrorKindFromWireFailureCode
149
+ } from '../framework-errors-internal';
150
+ import { decodeAuthorityKey, encodeAuthorityKey } from '../locations/authority-key-codec';
151
+ import { isIndeterminateLocationStoreFailure } from '../locations/location-store-failure';
152
+ import type { ZLinkDomainLocationStore as ZLinkLocationStore } from '../locations/domain-store-contract';
153
+ import type {
154
+ ZLinkAuthorityKey,
155
+ ZLinkAuthorityScanCursor,
156
+ ZLinkAuthoritySnapshot,
157
+ ZLinkCapacityVector,
158
+ ZLinkLocationOwnerToken
159
+ } from '../locations/internal-location-contracts';
160
+ import type { ZLinkTrackedInstanceAuthority } from '../locations/spot-location-claims';
161
+ import { decodeRoutingId, encodeRoutingIdStorageHex, routingIdsEqual } from '../routing-id';
162
+ import type { DefaultZLinkSpotManager, ZLinkSpotNodeRuntimeManager } from '../spots';
163
+ import type { ZLinkSpotActivation } from '../spots/spot-activation-state';
164
+ import { createProviderInstance } from '../spots/spot-provider';
165
+ import { decodeHandoffBacklog } from '../spots/spot-remote-codec';
166
+ import { committedActorOwnerFence, type ZLinkActorTransferRuntime } from './actor-transfer-runtime';
156
167
  import { BoundedReplayMap } from './bounded-replay-map';
157
- import type { ZLinkActorJoinRelocation } from '../actors/actor-join-relocation';
168
+ import {
169
+ ZLinkRelocationInFlightBudget,
170
+ ZLinkRelocationPayloadAssembly,
171
+ effectiveActorJoinChunkLimitBytes,
172
+ effectiveRelocationBudgetBytes,
173
+ planRelocationChunks,
174
+ relocationChunkAt
175
+ } from './relocation-direct-transfer';
158
176
  import {
159
177
  ZLINK_INTERNAL_RELOCATION_INTEGRITY_FAULT_GATE,
160
178
  type ZLinkInternalRelocationIntegrityFaultGate
161
179
  } from './relocation-integrity-fault-gate';
162
180
  import {
163
- decodeRemoteActorSourceLeaveTerminal,
164
- ZLINK_REMOTE_ACTOR_SOURCE_LEAVE_TERMINAL
165
- } from '../actors/actor-remote-wire';
181
+ captureRelocationAdapterState,
182
+ restoreRelocationAdapterState,
183
+ type ZLinkRelocationStateAdapterLike
184
+ } from './relocation-state-adapter';
166
185
  import {
167
- decodeCanonicalActorJoinRecoverySavedWork,
168
- encodeCanonicalActorJoinRecoverySavedWork,
169
- type CanonicalActorJoinRecovery
170
- } from '../foundation/actor-join-recovery-codec';
186
+ decodeServiceRelocationControlRequest,
187
+ decodeServiceRelocationControlResponse,
188
+ encodeServiceRelocationControlRequest,
189
+ encodeServiceRelocationControlResponse,
190
+ type ZLinkServiceRelocationControlRequest,
191
+ type ZLinkServiceRelocationControlResponse
192
+ } from './service-relocation-control';
193
+ const RELOCATION_ATTEMPT_TIMEOUT_MS = 250;
194
+
195
+ const RELOCATION_AUTHORITY_RETRY_DELAY_MS = 25;
196
+
197
+ const RELOCATION_MAX_SEND_ATTEMPTS = 120;
198
+
199
+ const RELOCATION_AUTHORITY_POLL_INTERVAL_MS = 10;
200
+
201
+ const RELOCATION_RESEND_DELAY_MS = 250;
202
+
203
+ const RELOCATION_ABORTABLE_OPERATION_TIMEOUT_MS = 24 * 60 * 60 * 1000;
204
+
205
+ const RELOCATION_OPERATION_TIMEOUT_MS = 30_000;
171
206
 
172
207
  export class ZLinkRelocationStateIncompatibleError extends Error {
173
208
  constructor(message: string) {
@@ -186,6 +221,7 @@ interface ZLinkHostRelocationOptions {
186
221
  readonly providerResolver?: ZLinkProviderResolver;
187
222
  readonly locationStore: () => ZLinkLocationStore | undefined;
188
223
  readonly currentOwner: () => ZLinkLocationOwnerToken | undefined;
224
+ readonly targetAdmissionSealed?: () => boolean;
189
225
  readonly liveDescriptors: (
190
226
  meshName: string,
191
227
  signal?: AbortSignal
@@ -210,6 +246,7 @@ interface ZLinkHostRelocationOptions {
210
246
  readonly reconcileStatefulAuthorityRoutes?: (signal?: AbortSignal) => Promise<void>;
211
247
  readonly runtimeEventPublisher?: ZLinkRuntimeEventPublisher;
212
248
  readonly metrics?: ZLinkRuntimeMetrics;
249
+ readonly activationAdmission?: ZLinkActivationAdmission;
213
250
  }
214
251
 
215
252
  interface RelocationTargetRequirement {
@@ -241,6 +278,8 @@ interface LocalStage {
241
278
  readonly boundaryRelay: ServiceMaintenanceRelocationData[];
242
279
  fallback?: ReturnType<typeof setTimeout>;
243
280
  finalize?: Promise<void>;
281
+ /** The Restore's activation admission (MeshNode §5.1); held until target commit or abort. */
282
+ readonly releaseActivation: () => void;
244
283
  }
245
284
 
246
285
  interface TargetPayloadAssembly {
@@ -253,11 +292,11 @@ interface TargetPayloadAssembly {
253
292
  interface SourceCutoverWindow {
254
293
  readonly meshName: string;
255
294
  readonly targetNodeRid: RoutingId;
256
- /** Boundary batch frames plus the cutover frame, retained for retransmission. */
295
+ /** Boundary batch frames plus the cutover frame, retained until authority settlement. */
257
296
  frames?: readonly Buffer[];
297
+ /** A failed submit or a lost target connection requires the whole batch again. */
298
+ resendPending: boolean;
258
299
  readonly cutoverSubmittedAtMs: number;
259
- windowTimer?: ReturnType<typeof setTimeout>;
260
- retryTimer?: ReturnType<typeof setTimeout>;
261
300
  followTimer?: ReturnType<typeof setTimeout>;
262
301
  windowClosed: boolean;
263
302
  followExpired: boolean;
@@ -272,7 +311,6 @@ interface TargetRelocationOffer {
272
311
  readonly prepareFingerprint: string;
273
312
  readonly authenticatedSourceNodeRid: string;
274
313
  readonly envelope: ServiceRelocationEnvelope;
275
- readonly restoreDeadlineAtMs: number;
276
314
  readonly reservation: TargetRelocationReservation;
277
315
  }
278
316
 
@@ -302,7 +340,7 @@ interface SourceActorJoinProfile {
302
340
  Parameters<ZLinkActorJoinRelocation['relocateActorJoin']>[0]['canonicalRecovery']
303
341
  >;
304
342
  readonly ready: Promise<void>;
305
- readonly resolveReady: () => void;
343
+ readonly resolveReady: (failure?: ServiceRelocationPostCommitError) => void;
306
344
  }
307
345
 
308
346
  interface PendingRelocationReplyRelay {
@@ -361,8 +399,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
361
399
  * Terminal restore failures (assembly/checksum/factory/restore error): the
362
400
  * target already sent relocationFailed (command 53) once. An exact-identity
363
401
  * Prepare resend replays the stored response rather than retrying the
364
- * restore; a different exact identity reusing the same RelocationId+attempt
365
- * key supersedes the stale entry (spec 28 §3, §9).
402
+ * restore. A different exact identity never changes this entry (spec 28 §4.3).
366
403
  */
367
404
  private readonly targetReadyFailures = new Map<
368
405
  string,
@@ -419,9 +456,12 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
419
456
  );
420
457
  }
421
458
  this.reserveExactRelocationId(input.relocationId);
422
- let resolveReady!: () => void;
423
- const ready = new Promise<void>((resolve) => {
424
- resolveReady = resolve;
459
+ let resolveReady!: (failure?: ServiceRelocationPostCommitError) => void;
460
+ const ready = new Promise<void>((resolve, reject) => {
461
+ resolveReady = (failure) => {
462
+ if (failure === undefined) resolve();
463
+ else reject(failure.cause);
464
+ };
425
465
  });
426
466
  this.sourceActorJoinProfiles.set(input.relocationId, {
427
467
  actor: input.actor,
@@ -433,8 +473,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
433
473
  relocationContentType:
434
474
  this.actorRegistration(input.state.meshName ?? input.meshName, input.state.actorType ?? '')
435
475
  .relocation.kind === 'snapshot'
436
- ? 'application/vnd.zlink.actor-relocation.snapshot'
437
- : 'application/vnd.zlink.actor-relocation.recreate',
476
+ ? SNAPSHOT_RELOCATION_CONTENT_TYPE
477
+ : RECREATE_RELOCATION_CONTENT_TYPE,
438
478
  completionOperationId: input.completionOperationId,
439
479
  canonicalRecovery: input.canonicalRecovery,
440
480
  ready,
@@ -464,8 +504,13 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
464
504
  );
465
505
  resolveReady();
466
506
  } catch (error) {
467
- this.sourceActorJoinProfiles.delete(input.relocationId);
468
- throw error;
507
+ if (error instanceof ServiceRelocationPostCommitError) {
508
+ resolveReady(error);
509
+ this.completeActorJoinSourceCleanup(input.state.actorId);
510
+ } else {
511
+ this.sourceActorJoinProfiles.delete(input.relocationId);
512
+ throw error;
513
+ }
469
514
  }
470
515
  return {
471
516
  actorRef: {
@@ -481,17 +526,10 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
481
526
  async dispose(): Promise<void> {
482
527
  this.disposed = true;
483
528
  const errors: unknown[] = [];
484
- for (const stage of this.targetStages.values()) {
485
- if (stage.fallback !== undefined) clearTimeout(stage.fallback);
486
- try {
487
- if (stage.finalize !== undefined) {
488
- await stage.finalize;
489
- } else if (stage.phase === 'ready') {
490
- await this.abortTargetStage(stage);
491
- }
492
- } catch (error) {
493
- errors.push(error);
494
- }
529
+ try {
530
+ await this.drainTargetAttempts();
531
+ } catch (error) {
532
+ errors.push(error);
495
533
  }
496
534
  this.targetStages.clear();
497
535
  this.terminalTargets.clear();
@@ -502,8 +540,6 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
502
540
  this.targetReadyFailures.clear();
503
541
  this.targetReadyResponses.clear();
504
542
  for (const window of this.sourceCutoverWindows.values()) {
505
- if (window.windowTimer !== undefined) clearTimeout(window.windowTimer);
506
- if (window.retryTimer !== undefined) clearTimeout(window.retryTimer);
507
543
  if (window.followTimer !== undefined) clearTimeout(window.followTimer);
508
544
  }
509
545
  this.sourceCutoverWindows.clear();
@@ -531,24 +567,78 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
531
567
  if (errors.length > 1) throw new AggregateError(errors, 'Relocation runtime stop failed.');
532
568
  }
533
569
 
570
+ /** Host shutdown waits for the existing attempt terminal before releasing its lease. */
571
+ async drainTargetAttempts(): Promise<void> {
572
+ const results = await Promise.allSettled(
573
+ [...this.targetStages.entries()].map(([stagingId, stage]) => {
574
+ if (stage.fallback !== undefined) clearTimeout(stage.fallback);
575
+ return stage.lane.finally(() => {
576
+ if (stage.phase === 'failed') return stage.finalize;
577
+ if (this.targetStageSealed(stage)) {
578
+ const terminal = this.discardTargetStage(stagingId, stage);
579
+ stage.finalize = terminal;
580
+ return terminal;
581
+ }
582
+ return this.beginTargetFinalize(
583
+ stage.offer.reservation.prepared.plan.targetDescriptor.meshName,
584
+ stagingId,
585
+ stage
586
+ );
587
+ });
588
+ })
589
+ );
590
+ const errors = results.flatMap((result) =>
591
+ result.status === 'rejected' ? [result.reason as unknown] : []
592
+ );
593
+ if (errors.length === 1) throw errors[0];
594
+ if (errors.length > 1) throw new AggregateError(errors, 'Relocation target drain failed.');
595
+ }
596
+
597
+ /** Spec 28 §4.4: the target owner alone orders shutdown seal and verified cutover. */
598
+ private targetStageSealed(stage: LocalStage): boolean {
599
+ return (
600
+ !stage.cutoverReceived && (this.disposed || this.options.targetAdmissionSealed?.() === true)
601
+ );
602
+ }
603
+
534
604
  completeActorJoinSourceCleanup(actorId: string): void {
535
605
  for (const [relocationId, profile] of this.sourceActorJoinProfiles) {
536
606
  if (profile.state.actorId !== actorId) continue;
537
607
  this.sourceActorJoinProfiles.delete(relocationId);
538
608
  this.terminalActorJoinSourceLeaves.remember(relocationId, actorId);
539
- void profile.ready
540
- .then(async () => {
541
- await this.options.actorTransfer.completeRelocationSourceLeave(
542
- profile.actor,
543
- profile.sourceSpotId
544
- );
545
- await this.options
546
- .actorManager()
547
- ?.completeRelocationSource(actorId, profile.sourceActorRef);
548
- })
549
- .catch((error) => {
550
- console.error('[zlink.runtime.relocation.source_cleanup_failed]', error);
551
- });
609
+ void this.finalizeActorJoinSource(profile);
610
+ }
611
+ }
612
+
613
+ private async finalizeActorJoinSource(profile: SourceActorJoinProfile): Promise<void> {
614
+ const failures: unknown[] = [];
615
+ try {
616
+ await profile.ready;
617
+ } catch (error) {
618
+ failures.push(error);
619
+ }
620
+ try {
621
+ await this.options.actorTransfer.completeRelocationSourceLeave(
622
+ profile.actor,
623
+ profile.sourceSpotId
624
+ );
625
+ } catch (error) {
626
+ failures.push(error);
627
+ }
628
+ try {
629
+ await this.options
630
+ .actorManager()
631
+ ?.completeRelocationSource(profile.state.actorId, profile.sourceActorRef);
632
+ } catch (error) {
633
+ failures.push(error);
634
+ }
635
+ if (failures.length > 0) {
636
+ console.error(
637
+ '[zlink.runtime.relocation.source_cleanup_failed]',
638
+ failures.length === 1
639
+ ? failures[0]
640
+ : new AggregateError(failures, 'Actor source retirement failed.')
641
+ );
552
642
  }
553
643
  }
554
644
 
@@ -661,22 +751,59 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
661
751
  publishState: () => undefined,
662
752
  forceStop: () => undefined
663
753
  });
754
+ const started: Array<Promise<void>> = [];
664
755
  for (const unit of work) {
665
756
  maintenance.enqueue({
666
757
  id: unit.id,
667
758
  ready: () => true,
668
- relocate: unit.relocate
759
+ relocate: (relocationSignal) => {
760
+ const run = unit.relocate(relocationSignal);
761
+ if (isStructuralGuardEnabled()) {
762
+ trackDiagnosticCompletion(run, `relocation ${unit.id}`);
763
+ }
764
+ started.push(run);
765
+ return run;
766
+ }
669
767
  });
670
768
  }
671
769
  const operationSignal = signal ?? new AbortController().signal;
672
- const deadlineMs = signal === undefined ? 30_000 : 24 * 60 * 60 * 1000;
673
- const result = await raceAbort(
674
- maintenance.start('retire', deadlineMs, stopStartingSignal, operationSignal),
675
- operationSignal
770
+ const deadlineMs =
771
+ signal === undefined
772
+ ? RELOCATION_OPERATION_TIMEOUT_MS
773
+ : RELOCATION_ABORTABLE_OPERATION_TIMEOUT_MS;
774
+ let failure: unknown;
775
+ try {
776
+ const result = await raceAbort(
777
+ maintenance.start('retire', deadlineMs, stopStartingSignal, operationSignal),
778
+ operationSignal
779
+ );
780
+ if (result.state === 'completed') return;
781
+ failure = result.terminalError ?? new Error(`Host relocation ended in '${result.state}'.`);
782
+ } catch (error) {
783
+ failure = error;
784
+ }
785
+ // The host result follows the settled authority of every started unit
786
+ // (spec 30 §13): a unit completes only after its target commit is
787
+ // confirmed, so a failure beside a completed unit leaves units on both
788
+ // sides.
789
+ const settled = await Promise.allSettled(started);
790
+ const targetSide = settled.filter(
791
+ (value) =>
792
+ value.status === 'fulfilled' || value.reason instanceof ServiceRelocationPostCommitError
793
+ ).length;
794
+ const authorityError = settled.find(
795
+ (value): value is PromiseRejectedResult =>
796
+ value.status === 'rejected' && value.reason instanceof ServiceRelocationAuthorityError
676
797
  );
677
- if (result.state !== 'completed') {
678
- throw result.terminalError ?? new Error(`Host relocation ended in '${result.state}'.`);
798
+ if (authorityError !== undefined) throw authorityError.reason;
799
+ // A unit that never started also stays on the source.
800
+ if (targetSide > 0 && targetSide < work.length) {
801
+ throw new ServiceRelocationAuthorityError(
802
+ 'Relocation units settled on both the source and the target.',
803
+ { cause: failure }
804
+ );
679
805
  }
806
+ throw failure;
680
807
  }
681
808
 
682
809
  async tryHandleControl(
@@ -764,47 +891,57 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
764
891
  }
765
892
  const operationKey = relocationStagingId(request);
766
893
  const fingerprint = stringifyWire(request);
894
+ const readyFailure = this.targetReadyFailures.get(operationKey);
895
+ const readyPending = this.targetReadyResponses.get(operationKey);
896
+ let operation = this.targetPrepareOperations.get(operationKey);
897
+ const acceptedFingerprint =
898
+ readyFailure?.fingerprint ??
899
+ readyPending?.fingerprint ??
900
+ operation?.fingerprint ??
901
+ this.targetAssemblies.get(operationKey)?.fingerprint ??
902
+ this.targetStages.get(operationKey)?.offer.prepareFingerprint ??
903
+ this.terminalTargets.get(operationKey);
904
+ if (request.sourceNodeRid !== String(record.sourceNodeRid)) return true;
905
+ if (acceptedFingerprint !== undefined && acceptedFingerprint !== fingerprint) {
906
+ if (!samePrepareIdentity(acceptedFingerprint, request)) return true;
907
+ this.replyPrepare(
908
+ record,
909
+ relocationFailed(request, ServiceWireFrameworkErrorCode.relocationDataLost)
910
+ );
911
+ return true;
912
+ }
767
913
  // A restore that failed outright is terminal: the source observes the
768
914
  // same explicit relocationFailed on every identical Prepare resend
769
- // rather than a silent stall. A different exact identity reusing the
770
- // same RelocationId+attempt key supersedes the stale failure — the
771
- // newer attempt wins (spec 28 §3).
772
- const readyFailure = this.targetReadyFailures.get(operationKey);
915
+ // rather than a silent stall.
773
916
  if (readyFailure !== undefined) {
774
- if (readyFailure.fingerprint === fingerprint) {
775
- this.replyPrepare(record, readyFailure.response);
776
- return true;
777
- }
778
- this.targetReadyFailures.delete(operationKey);
917
+ this.replyPrepare(record, readyFailure.response);
918
+ return true;
779
919
  }
780
920
  // Spec 28: an exact-identity Restore resend against staging that
781
921
  // already restored successfully reuses that staging — it re-submits
782
922
  // READY rather than restarting the restore.
783
- const readyPending = this.targetReadyResponses.get(operationKey);
784
923
  if (readyPending !== undefined) {
785
- if (readyPending.fingerprint !== fingerprint) {
786
- throw new Error(`Relocation '${operationKey}' repeated Prepare with different bytes.`);
787
- }
788
924
  this.submitTargetReady(meshName, operationKey, record, readyPending.response);
789
925
  return true;
790
926
  }
791
- let operation = this.targetPrepareOperations.get(operationKey);
792
- if (operation !== undefined && operation.fingerprint !== fingerprint) {
793
- throw new Error(`Relocation '${operationKey}' repeated Prepare with different bytes.`);
794
- }
795
927
  if (operation === undefined) {
796
928
  // The prepare turn must not block the ordered dispatch lane: the
797
929
  // payload chunks that complete this operation arrive as later records
798
930
  // on the same connection. Register the assembly synchronously, then
799
931
  // finish the restore and the Ready reply asynchronously.
800
- this.registerTargetAssembly(operationKey, request, String(record.sourceNodeRid));
801
- const promise = this.handlePrepareControl(
802
- meshName,
803
- operationKey,
804
- request,
805
- record.sourceNodeRid,
806
- signal
807
- );
932
+ let promise: Promise<ServiceMaintenanceRelocationReady>;
933
+ try {
934
+ this.registerTargetAssembly(operationKey, request, String(record.sourceNodeRid));
935
+ promise = this.handlePrepareControl(
936
+ meshName,
937
+ operationKey,
938
+ request,
939
+ record.sourceNodeRid,
940
+ signal
941
+ );
942
+ } catch (error) {
943
+ promise = Promise.reject(error);
944
+ }
808
945
  operation = { fingerprint, promise };
809
946
  this.targetPrepareOperations.set(operationKey, operation);
810
947
  void promise
@@ -838,10 +975,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
838
975
  // restore or staging): this is terminal. Send relocationFailed
839
976
  // (command 53) once and remember it so an identical Prepare resend
840
977
  // replays the same response instead of retrying the restore.
841
- const failed = relocationFailed(
842
- request,
843
- relocationFailedFailureCode(error, request.object.kind)
844
- );
978
+ const failed = relocationFailed(request, relocationFailedFailureCode(error));
845
979
  this.targetReadyFailures.set(operationKey, {
846
980
  fingerprint: stringifyWire(request),
847
981
  response: failed
@@ -935,7 +1069,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
935
1069
  String(sourceNodeRid) !== pending.authenticatedSourceNodeRid ||
936
1070
  request.senderRole !== 'source' ||
937
1071
  !sameCoordinator(request.coordinator, pending.prepare.coordinator) ||
938
- stringifyWire(request.object) !== stringifyWire(pending.prepare.object)
1072
+ !sameRelocationObject(request.object, pending.prepare.object)
939
1073
  ) {
940
1074
  // Spec 28 §4.3 — a chunk with a different exact identity is never
941
1075
  // attached to an in-progress assembly; it is discarded.
@@ -959,14 +1093,6 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
959
1093
  ): TargetPayloadAssembly {
960
1094
  const existing = this.targetAssemblies.get(operationKey);
961
1095
  if (existing !== undefined) {
962
- if (existing.fingerprint !== stringifyWire(request)) {
963
- // Same exact identity with a different declared length or checksum is
964
- // an explicit conflict failure; the existing assembly is neither
965
- // reused nor overwritten (spec 28 §4.3).
966
- throw existing.assembly.fail(
967
- `Relocation '${operationKey}' repeated Prepare with a conflicting manifest.`
968
- );
969
- }
970
1096
  return existing;
971
1097
  }
972
1098
  const pending: TargetPayloadAssembly = {
@@ -981,15 +1107,10 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
981
1107
  };
982
1108
  this.targetAssemblies.set(operationKey, pending);
983
1109
  if (this.relocationIntegrityFaultGate()?.consumeIdentityConflict() === true) {
984
- // Route a same-identity, different-manifest delivery through the normal
985
- // conflict branch so existing staging is neither reused nor overwritten.
986
- return this.registerTargetAssembly(
987
- operationKey,
988
- {
989
- ...request,
990
- payloadChecksumCrc32c: request.payloadChecksumCrc32c ^ 1
991
- },
992
- authenticatedSourceNodeRid
1110
+ throw new ServiceRelocationDataLostError(
1111
+ pending.assembly.fail(
1112
+ `Relocation '${operationKey}' repeated Prepare with a conflicting manifest.`
1113
+ ).message
993
1114
  );
994
1115
  }
995
1116
  return pending;
@@ -1167,7 +1288,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1167
1288
  return;
1168
1289
  }
1169
1290
  if (this.pendingSessionRelocations.get(key) === pending) {
1170
- pending.retryTimer = setTimeout(() => void send(), 250);
1291
+ pending.retryTimer = setTimeout(() => void send(), RELOCATION_RESEND_DELAY_MS);
1171
1292
  }
1172
1293
  } catch (error) {
1173
1294
  pending.reject(
@@ -1385,6 +1506,28 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1385
1506
  target: ZLinkMeshNodeDescriptor | undefined,
1386
1507
  targetApplicationVersion: bigint | undefined,
1387
1508
  signal?: AbortSignal
1509
+ ): Promise<void> {
1510
+ await activation.serial.executeControlLifecycleOperation(() =>
1511
+ this.relocateSpotAggregateCore(
1512
+ meshName,
1513
+ activation,
1514
+ kind,
1515
+ actorStates,
1516
+ target,
1517
+ targetApplicationVersion,
1518
+ signal
1519
+ )
1520
+ );
1521
+ }
1522
+
1523
+ private async relocateSpotAggregateCore(
1524
+ meshName: string,
1525
+ activation: ZLinkSpotActivation,
1526
+ kind: 'user_spot' | 'instance_spot',
1527
+ actorStates: readonly ZLinkActorRuntimeState[],
1528
+ target: ZLinkMeshNodeDescriptor | undefined,
1529
+ targetApplicationVersion: bigint | undefined,
1530
+ signal?: AbortSignal
1388
1531
  ): Promise<void> {
1389
1532
  const store = this.requireLocationStore();
1390
1533
  const spotKey = encodeAuthorityKey(kind, String(activation.spotId));
@@ -1441,12 +1584,16 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1441
1584
  objectGeneration: spotAuthority.objectGeneration,
1442
1585
  authorityOwnerGeneration: spotAuthority.authorityOwnerGeneration,
1443
1586
  seal: async (captureSignal) => {
1444
- interruptionStartedAt = performance.now();
1445
- spotMessageFollowSeal = this.sealSpotMessageFollow(meshName, spotAuthority, activation);
1446
- // captureRelocation seals synchronously before its first await. Invoke
1447
- // it in the same event-loop turn as the wire ingress seal so no
1448
- // accepted direct message can enter between the two boundaries.
1449
- const spotCaptureOperation = activation.captureRelocation(captureSignal);
1587
+ const { spotCaptureOperation } = await this.afterSpotCloseDecision(
1588
+ meshName,
1589
+ activation.spotId,
1590
+ () => {
1591
+ interruptionStartedAt = performance.now();
1592
+ spotMessageFollowSeal = this.sealSpotMessageFollow(meshName, spotAuthority, activation);
1593
+ // Both seals run in the same event-loop turn after the Close decision.
1594
+ return { spotCaptureOperation: activation.captureRelocation(captureSignal) };
1595
+ }
1596
+ );
1450
1597
  const preparedSessions = Promise.all(
1451
1598
  actorStates.map(async (state) => ({
1452
1599
  state,
@@ -1477,47 +1624,72 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1477
1624
  captureApplicationState: (captureSignal) =>
1478
1625
  this.captureApplication(spotRegistration.relocation, activation.spot, captureSignal),
1479
1626
  commitSeal: async () => {
1480
- if (!spotSealCommitted) {
1481
- if (spotCapture === undefined || !(await activation.commitRelocation(spotCapture))) {
1482
- throw new Error(`Spot '${String(activation.spotId)}' relocation seal became stale.`);
1627
+ const failures: unknown[] = [];
1628
+ try {
1629
+ if (!spotSealCommitted) {
1630
+ if (spotCapture === undefined || !(await activation.commitRelocation(spotCapture))) {
1631
+ throw new Error(`Spot '${String(activation.spotId)}' relocation seal became stale.`);
1632
+ }
1633
+ spotSealCommitted = true;
1483
1634
  }
1484
- spotSealCommitted = true;
1485
- }
1486
- if (spotMessageFollowSeal !== undefined) {
1487
- await this.commitSpotMessageFollow(
1488
- meshName,
1489
- spotMessageFollowSeal,
1490
- spotKey,
1491
- activation,
1492
- target
1493
- );
1635
+ if (spotMessageFollowSeal !== undefined) {
1636
+ await this.commitSpotMessageFollow(
1637
+ meshName,
1638
+ spotMessageFollowSeal,
1639
+ spotKey,
1640
+ activation,
1641
+ target
1642
+ );
1643
+ }
1644
+ } catch (error) {
1645
+ failures.push(error);
1494
1646
  }
1495
1647
  for (const session of sessions) {
1496
- const committedAuthority = await requireAuthority(
1497
- this.requireLocationStore(),
1498
- encodeAuthorityKey('actor', session.state.actorId)
1499
- );
1500
- const targetActorRef = {
1501
- actorId: session.state.actorId,
1502
- objectGeneration: actorAuthorities.get(session.state.actorId)!.objectGeneration,
1503
- meshName,
1504
- nodeRid: target.rid
1505
- };
1506
- await session.prepared.commit(
1507
- {
1508
- routerChannelId: meshName,
1509
- targetNodeRid: target.rid,
1510
- spotId: activation.spotId,
1511
- spotKind: kind === 'user_spot' ? ZLinkSpotKind.User : ZLinkSpotKind.Instance,
1512
- authorityOwnerGeneration: committedAuthority.authorityOwnerGeneration
1513
- } as never,
1514
- targetActorRef,
1515
- committedActorOwnerFence(session.state.actorId, targetActorRef, committedAuthority)
1516
- );
1517
- activation.commitActorDeparture(session.state.actorId);
1518
- await this.requireActorManager().completeRelocationSource(session.state.actorId);
1648
+ try {
1649
+ const committedAuthority = await requireAuthority(
1650
+ this.requireLocationStore(),
1651
+ encodeAuthorityKey('actor', session.state.actorId)
1652
+ );
1653
+ const targetActorRef = {
1654
+ actorId: session.state.actorId,
1655
+ objectGeneration: actorAuthorities.get(session.state.actorId)!.objectGeneration,
1656
+ meshName,
1657
+ nodeRid: target.rid
1658
+ };
1659
+ await session.prepared.commit(
1660
+ {
1661
+ routerChannelId: meshName,
1662
+ targetNodeRid: target.rid,
1663
+ spotId: activation.spotId,
1664
+ spotKind: kind === 'user_spot' ? ZLinkSpotKind.User : ZLinkSpotKind.Instance,
1665
+ authorityOwnerGeneration: committedAuthority.authorityOwnerGeneration
1666
+ } as never,
1667
+ targetActorRef,
1668
+ committedActorOwnerFence(session.state.actorId, targetActorRef, committedAuthority)
1669
+ );
1670
+ } catch (error) {
1671
+ failures.push(error);
1672
+ }
1673
+ try {
1674
+ activation.commitActorDeparture(session.state.actorId);
1675
+ } catch (error) {
1676
+ failures.push(error);
1677
+ }
1678
+ try {
1679
+ await this.requireActorManager().completeRelocationSource(session.state.actorId);
1680
+ } catch (error) {
1681
+ failures.push(error);
1682
+ }
1519
1683
  }
1520
- await this.requireSpotManager().completeRelocationSource(activation);
1684
+ try {
1685
+ await this.requireSpotManager().completeRelocationSource(activation);
1686
+ } catch (error) {
1687
+ failures.push(error);
1688
+ }
1689
+ if (failures.length > 0)
1690
+ throw failures.length === 1
1691
+ ? failures[0]
1692
+ : new AggregateError(failures, 'Spot source retirement failed.');
1521
1693
  },
1522
1694
  abortSeal: async () => {
1523
1695
  const failures: unknown[] = [];
@@ -1623,7 +1795,11 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1623
1795
  );
1624
1796
  } catch (error) {
1625
1797
  outcome = 'failed';
1626
- if (readinessBoundaryConsumed && !(error instanceof ServiceRelocationPostCommitError)) {
1798
+ if (
1799
+ readinessBoundaryConsumed &&
1800
+ !(error instanceof ServiceRelocationPostCommitError) &&
1801
+ !(error instanceof ServiceRelocationAuthorityError)
1802
+ ) {
1627
1803
  await activation
1628
1804
  .completeConsumedRelocationBoundary(ZLinkSpotRelocationReadyOutcome.Continued)
1629
1805
  .catch(() => undefined);
@@ -1659,7 +1835,9 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1659
1835
  /** Target's advertised relocation state chunk cap (Actor Join only; spec 15 §4.2). */
1660
1836
  advertisedReceiveChunkLimitBytes?: number
1661
1837
  ): Promise<void> {
1662
- relocationDebug('standalone_actor.begin', { actorId: state.actorId });
1838
+ if (isRelocationDebugEnabled()) {
1839
+ relocationDebug('standalone_actor.begin', { actorId: state.actorId });
1840
+ }
1663
1841
  const authority = await requireAuthority(
1664
1842
  this.requireLocationStore(),
1665
1843
  encodeAuthorityKey('actor', state.actorId),
@@ -1716,12 +1894,16 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1716
1894
  };
1717
1895
  let outcome = 'completed';
1718
1896
  try {
1719
- relocationDebug('standalone_actor.wait_budget', {
1720
- actorId: state.actorId,
1721
- targetRid: String(target.rid)
1722
- });
1897
+ if (isRelocationDebugEnabled()) {
1898
+ relocationDebug('standalone_actor.wait_budget', {
1899
+ actorId: state.actorId,
1900
+ targetRid: String(target.rid)
1901
+ });
1902
+ }
1723
1903
  await this.waitForRelocationBudgetHeadroom(String(target.rid), signal);
1724
- relocationDebug('standalone_actor.capture_begin', { actorId: state.actorId });
1904
+ if (isRelocationDebugEnabled()) {
1905
+ relocationDebug('standalone_actor.capture_begin', { actorId: state.actorId });
1906
+ }
1725
1907
  const captured = await new ServiceRelocationObjectCaptureOwner().captureStandaloneActor(
1726
1908
  aggregateId,
1727
1909
  1n,
@@ -1729,7 +1911,9 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1729
1911
  membership,
1730
1912
  signal
1731
1913
  );
1732
- relocationDebug('standalone_actor.capture_complete', { actorId: state.actorId });
1914
+ if (isRelocationDebugEnabled()) {
1915
+ relocationDebug('standalone_actor.capture_complete', { actorId: state.actorId });
1916
+ }
1733
1917
  await this.runCoordinator(
1734
1918
  meshName,
1735
1919
  target,
@@ -1763,6 +1947,24 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1763
1947
  target: ZLinkMeshNodeDescriptor | undefined,
1764
1948
  targetApplicationVersion: bigint | undefined,
1765
1949
  signal?: AbortSignal
1950
+ ): Promise<void> {
1951
+ await activation.serial.executeControlLifecycleOperation(() =>
1952
+ this.relocatePerActorSpotShellCore(
1953
+ meshName,
1954
+ activation,
1955
+ target,
1956
+ targetApplicationVersion,
1957
+ signal
1958
+ )
1959
+ );
1960
+ }
1961
+
1962
+ private async relocatePerActorSpotShellCore(
1963
+ meshName: string,
1964
+ activation: ZLinkSpotActivation,
1965
+ target: ZLinkMeshNodeDescriptor | undefined,
1966
+ targetApplicationVersion: bigint | undefined,
1967
+ signal?: AbortSignal
1766
1968
  ): Promise<void> {
1767
1969
  const spotKey = encodeAuthorityKey('user_spot', String(activation.spotId));
1768
1970
  const spotAuthority = await requireAuthority(this.requireLocationStore(), spotKey, signal);
@@ -1796,9 +1998,16 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1796
1998
  objectGeneration: spotAuthority.objectGeneration,
1797
1999
  authorityOwnerGeneration: spotAuthority.authorityOwnerGeneration,
1798
2000
  seal: async (captureSignal) => {
1799
- interruptionStartedAt = performance.now();
1800
- spotMessageFollowSeal = this.sealSpotMessageFollow(meshName, spotAuthority, activation);
1801
- spotCapture = await activation.captureRelocation(captureSignal);
2001
+ const { spotCaptureOperation } = await this.afterSpotCloseDecision(
2002
+ meshName,
2003
+ activation.spotId,
2004
+ () => {
2005
+ interruptionStartedAt = performance.now();
2006
+ spotMessageFollowSeal = this.sealSpotMessageFollow(meshName, spotAuthority, activation);
2007
+ return { spotCaptureOperation: activation.captureRelocation(captureSignal) };
2008
+ }
2009
+ );
2010
+ spotCapture = await spotCaptureOperation;
1802
2011
  return {
1803
2012
  boundSessionState: Buffer.alloc(0),
1804
2013
  queuedMessages: [],
@@ -1937,36 +2146,54 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
1937
2146
  meshName === undefined
1938
2147
  )
1939
2148
  return;
1940
- const membershipSpotId =
1941
- targetMembership?.spotId ?? ((target.entrySpotId ?? String(target.rid)) as RoutingId);
1942
- const committedAuthority = await requireAuthority(
1943
- this.requireLocationStore(),
1944
- encodeAuthorityKey('actor', state.actorId)
1945
- );
1946
- const targetActorRef = {
1947
- actorId: state.actorId,
1948
- objectGeneration: authority.objectGeneration,
1949
- meshName,
1950
- nodeRid: target.rid
1951
- };
1952
- await ownSession.prepared.commit(
1953
- {
1954
- routerChannelId: meshName,
1955
- targetNodeRid: target.rid,
1956
- spotId: membershipSpotId,
1957
- spotKind: targetMembership?.spotKind ?? ZLinkSpotKind.Entry,
1958
- authorityOwnerGeneration: committedAuthority.authorityOwnerGeneration
1959
- } as never,
1960
- targetActorRef,
1961
- committedActorOwnerFence(state.actorId, targetActorRef, committedAuthority)
1962
- );
2149
+ const failures: unknown[] = [];
2150
+ try {
2151
+ const membershipSpotId =
2152
+ targetMembership?.spotId ?? ((target.entrySpotId ?? String(target.rid)) as RoutingId);
2153
+ const committedAuthority = await requireAuthority(
2154
+ this.requireLocationStore(),
2155
+ encodeAuthorityKey('actor', state.actorId)
2156
+ );
2157
+ const targetActorRef = {
2158
+ actorId: state.actorId,
2159
+ objectGeneration: authority.objectGeneration,
2160
+ meshName,
2161
+ nodeRid: target.rid
2162
+ };
2163
+ await ownSession.prepared.commit(
2164
+ {
2165
+ routerChannelId: meshName,
2166
+ targetNodeRid: target.rid,
2167
+ spotId: membershipSpotId,
2168
+ spotKind: targetMembership?.spotKind ?? ZLinkSpotKind.Entry,
2169
+ authorityOwnerGeneration: committedAuthority.authorityOwnerGeneration
2170
+ } as never,
2171
+ targetActorRef,
2172
+ committedActorOwnerFence(state.actorId, targetActorRef, committedAuthority)
2173
+ );
2174
+ } catch (error) {
2175
+ failures.push(error);
2176
+ }
1963
2177
  if (!retainSourceForActorJoin) {
1964
- await this.requireActorManager().completeRelocationSource(state.actorId);
2178
+ try {
2179
+ await this.requireActorManager().completeRelocationSource(state.actorId);
2180
+ } catch (error) {
2181
+ failures.push(error);
2182
+ }
1965
2183
  }
2184
+ if (failures.length > 0)
2185
+ throw failures.length === 1
2186
+ ? failures[0]
2187
+ : new AggregateError(failures, 'Actor source retirement failed.');
1966
2188
  },
1967
2189
  abortSeal: async () => {
1968
2190
  if (standalone && ownSession !== undefined) await ownSession.prepared.rollback();
1969
- }
2191
+ },
2192
+ discardSeal: (reason) =>
2193
+ (standalone
2194
+ ? ownSession
2195
+ : sessions.find((value) => value.state === state)
2196
+ )?.prepared.discard(reason)
1970
2197
  };
1971
2198
  }
1972
2199
 
@@ -2004,13 +2231,17 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2004
2231
  /** Target's advertised relocation state chunk cap (Actor Join only; spec 15 §4.2). */
2005
2232
  advertisedReceiveChunkLimitBytes?: number
2006
2233
  ): Promise<void> {
2007
- relocationDebug('coordinator.begin', {
2008
- aggregateId: captured.envelope.aggregateId,
2009
- targetRid: String(target.rid)
2010
- });
2234
+ if (isRelocationDebugEnabled()) {
2235
+ relocationDebug('coordinator.begin', {
2236
+ aggregateId: captured.envelope.aggregateId,
2237
+ targetRid: String(target.rid)
2238
+ });
2239
+ }
2011
2240
  const localStatus = this.requireMeshNode(meshName).status();
2012
2241
  const controlDeadlineAtMs =
2013
- signal === undefined ? performance.now() + 30_000 : Number.MAX_SAFE_INTEGER;
2242
+ signal === undefined
2243
+ ? performance.now() + RELOCATION_OPERATION_TIMEOUT_MS
2244
+ : Number.MAX_SAFE_INTEGER;
2014
2245
  const coordinator = {
2015
2246
  ownerId: primary.ownerId,
2016
2247
  leaseGeneration: primary.ownerLeaseGeneration,
@@ -2067,12 +2298,14 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2067
2298
  advertisedReceiveChunkLimitBytes
2068
2299
  );
2069
2300
  const plan = planRelocationChunks(encoded, chunkLimitBytes);
2070
- const convergenceDeadlineAtMs =
2071
- signal === undefined
2072
- ? performance.now() + RELOCATION_OPERATION_RETENTION_MS
2073
- : controlDeadlineAtMs;
2301
+ // The Restore absolute deadline is this unit's relocation deadline: the
2302
+ // caller's signal, or the control deadline without one. From it the
2303
+ // source settles the authority with its Preserve fence (Location runtime
2304
+ // §10). The target keeps no deadline of its own.
2305
+ const restoreDeadlineReached = () =>
2306
+ signal?.aborted === true || performance.now() >= controlDeadlineAtMs;
2074
2307
  let readyReceived = false;
2075
- let sourceCommitted = false;
2308
+ let settlement: 'target' | 'source' | 'lost' | undefined;
2076
2309
  try {
2077
2310
  const prepare = {
2078
2311
  kind: 'prepare',
@@ -2102,17 +2335,25 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2102
2335
  encodePrepareSideband(captured.envelope)
2103
2336
  );
2104
2337
  try {
2105
- relocationDebug('coordinator.state_transfer_begin', {
2106
- aggregateId: captured.envelope.aggregateId
2107
- });
2338
+ if (isRelocationDebugEnabled()) {
2339
+ relocationDebug('coordinator.state_transfer_begin', {
2340
+ aggregateId: captured.envelope.aggregateId
2341
+ });
2342
+ }
2108
2343
  await this.sendRelocationStateChunks(meshName, target, prepare, encoded, plan, signal);
2109
2344
  } catch (chunkError) {
2110
2345
  readyOperation.catch(() => undefined);
2111
2346
  throw chunkError;
2112
2347
  }
2113
- relocationDebug('coordinator.await_ready', { aggregateId: captured.envelope.aggregateId });
2348
+ if (isRelocationDebugEnabled()) {
2349
+ relocationDebug('coordinator.await_ready', { aggregateId: captured.envelope.aggregateId });
2350
+ }
2114
2351
  const ready = await readyOperation;
2115
- relocationDebug('coordinator.ready_received', { aggregateId: captured.envelope.aggregateId });
2352
+ if (isRelocationDebugEnabled()) {
2353
+ relocationDebug('coordinator.ready_received', {
2354
+ aggregateId: captured.envelope.aggregateId
2355
+ });
2356
+ }
2116
2357
  validateControlResponse(prepare, ready);
2117
2358
  // Ready arms the target's cutover-wait fallback. From this point
2118
2359
  // forward the source can no longer reopen dispatch without risking two
@@ -2169,9 +2410,9 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2169
2410
  Buffer.concat(boundaryBatch.map((value) => value.canonicalBytes))
2170
2411
  )
2171
2412
  });
2172
- // The one-way cutover submit reaches a terminal result exactly once;
2173
- // success and failure both end source dispatch permanently. The batch
2174
- // copy stays for the retransmission window (spec 28 §4.4).
2413
+ // The cutover submit result is not the authority result. The memory
2414
+ // copy of the boundary batch and cutover stays until the authority
2415
+ // settlement below (spec 28 §4.4).
2175
2416
  let submitFailure: unknown;
2176
2417
  try {
2177
2418
  for (const record of boundaryBatch) {
@@ -2182,7 +2423,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2182
2423
  submitFailure = error;
2183
2424
  console.warn('[zlink.runtime.relocation.cutover_submit_failed]', relayAuthorityId, error);
2184
2425
  }
2185
- this.beginSourceCutoverWindow(
2426
+ const window = this.beginSourceCutoverWindow(
2186
2427
  meshName,
2187
2428
  target.rid,
2188
2429
  relayAuthorityId,
@@ -2190,53 +2431,59 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2190
2431
  submitFailure !== undefined,
2191
2432
  limits
2192
2433
  );
2193
-
2194
- await this.waitForTargetAuthorities(
2195
- captured.envelope,
2196
- targetFence,
2197
- convergenceDeadlineAtMs,
2198
- signal
2199
- );
2200
- relocationDebug('coordinator.target_authorities_observed', {
2201
- aggregateId: captured.envelope.aggregateId
2202
- });
2203
- for (const session of sessions) session.prepared.setReplayResults([]);
2204
- await captured.commitSource();
2205
- sourceCommitted = true;
2206
- await this.options.reconcileStatefulAuthorityRoutes?.(signal);
2207
- } catch (error) {
2208
- if (!readyReceived) throw error;
2209
2434
  try {
2210
- await this.waitForTargetAuthorities(
2435
+ settlement = await this.settleSourceAuthority(
2211
2436
  captured.envelope,
2437
+ primary,
2212
2438
  targetFence,
2213
- convergenceDeadlineAtMs
2214
- );
2215
- if (!sourceCommitted) {
2216
- for (const session of sessions) session.prepared.setReplayResults([]);
2217
- await captured.commitSource();
2218
- sourceCommitted = true;
2219
- }
2220
- await this.options.reconcileStatefulAuthorityRoutes?.();
2221
- const authority = await requireAuthority(
2222
- this.requireLocationStore(),
2223
- { value: primaryKey(captured.envelope) } as ZLinkAuthorityKey,
2224
- undefined
2439
+ restoreDeadlineReached,
2440
+ window
2225
2441
  );
2226
- throw new ServiceRelocationPostCommitError(
2227
- authority,
2228
- { id: `${captured.envelope.aggregateId}:${captured.envelope.aggregateGeneration}` },
2229
- error
2442
+ } finally {
2443
+ this.closeSourceCutoverWindow(relayAuthorityId, window);
2444
+ }
2445
+ if (isRelocationDebugEnabled()) {
2446
+ relocationDebug('coordinator.authority_settled', {
2447
+ aggregateId: captured.envelope.aggregateId,
2448
+ settlement
2449
+ });
2450
+ }
2451
+ if (settlement === 'source') {
2452
+ // Preserve won: the captured queue, timers and held records return to
2453
+ // the source queue in their accepted order, and dispatch reopens.
2454
+ await captured.abortSource();
2455
+ throw new Error(
2456
+ `Relocation '${relayAuthorityId}' ended by the source Preserve fence; the source kept the authority.`
2230
2457
  );
2231
- } catch (convergenceError) {
2232
- if (convergenceError instanceof ServiceRelocationPostCommitError) {
2233
- throw convergenceError;
2234
- }
2235
- throw new AggregateError(
2236
- [error, convergenceError],
2237
- 'Relocation became irreversible after Ready and target convergence failed.'
2458
+ }
2459
+ if (settlement === 'lost') {
2460
+ // Expired owner: dispatch and Store changes stop for good and every
2461
+ // pending request without a terminal gets one Unavailable.
2462
+ const unavailable = createInternalFrameworkException(
2463
+ ZLinkFrameworkInternalErrorKind.RouteNotConnected,
2464
+ `Relocation '${relayAuthorityId}' source owner lease ended before its authority settled.`,
2465
+ true
2238
2466
  );
2467
+ await captured.discardSource(unavailable);
2468
+ throw new ServiceRelocationAuthorityError(unavailable.message, { cause: unavailable });
2239
2469
  }
2470
+ for (const session of sessions) session.prepared.setReplayResults([]);
2471
+ await captured.commitSource();
2472
+ await this.options.reconcileStatefulAuthorityRoutes?.(signal);
2473
+ } catch (error) {
2474
+ if (settlement !== 'target') throw error;
2475
+ // Target commit is confirmed; later source cleanup failures cannot move
2476
+ // the authority back (spec 28 §10).
2477
+ const authority = await requireAuthority(
2478
+ this.requireLocationStore(),
2479
+ { value: primaryKey(captured.envelope) } as ZLinkAuthorityKey,
2480
+ undefined
2481
+ );
2482
+ throw new ServiceRelocationPostCommitError(
2483
+ authority,
2484
+ { id: `${captured.envelope.aggregateId}:${captured.envelope.aggregateGeneration}` },
2485
+ error
2486
+ );
2240
2487
  } finally {
2241
2488
  if (!readyReceived) {
2242
2489
  // Only an explicit failure before the Ready reply becomes accepted
@@ -2339,12 +2586,11 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2339
2586
  }
2340
2587
 
2341
2588
  /**
2342
- * Starts the source retransmission window after the cutover submit
2343
- * terminal. The boundary batch and cutover copies live in framework memory
2344
- * (not charged to the in-flight budget) and are cleaned exactly once when
2345
- * the window ends. While the window is open a failed submit retries the
2346
- * whole batch on the (re-established) connection. S4 — the Message Follow
2347
- * route removal point — closes the unit for SafeToShutdown.
2589
+ * Starts the source cutover window after the cutover submit terminal. The
2590
+ * boundary batch and cutover copies live in framework memory (not charged
2591
+ * to the in-flight budget) until the authority settlement closes the
2592
+ * window. S4 — the Message Follow route removal point — closes the unit for
2593
+ * SafeToShutdown.
2348
2594
  */
2349
2595
  private beginSourceCutoverWindow(
2350
2596
  meshName: string,
@@ -2353,65 +2599,184 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2353
2599
  frames: readonly Buffer[],
2354
2600
  initialSubmitFailed: boolean,
2355
2601
  limits: ReturnType<ZLinkHostServiceRelocationRuntime['relocationLimits']>
2356
- ): void {
2602
+ ): SourceCutoverWindow {
2357
2603
  const submittedAt = performance.now();
2358
2604
  const window: SourceCutoverWindow = {
2359
2605
  meshName,
2360
2606
  targetNodeRid,
2361
2607
  frames,
2608
+ resendPending: initialSubmitFailed,
2362
2609
  cutoverSubmittedAtMs: submittedAt,
2363
2610
  windowClosed: false,
2364
2611
  followExpired: false
2365
2612
  };
2366
2613
  this.sourceCutoverWindows.set(relocationKey, window);
2367
- const settle = () => {
2368
- if (window.windowClosed && window.followExpired) {
2369
- this.sourceCutoverWindows.delete(relocationKey);
2614
+ window.followTimer = setTimeout(() => {
2615
+ window.followExpired = true;
2616
+ // S1→S4 route convergence: the span the source must keep its Message
2617
+ // Follow route (spec 30 §7.1).
2618
+ this.options.metrics?.duration(
2619
+ METRIC_NAMES.RelocationRouteConvergence,
2620
+ Math.max(0, performance.now() - submittedAt) / MILLISECONDS_PER_SECOND,
2621
+ { mesh_name: meshName }
2622
+ );
2623
+ this.releaseSourceCutoverWindow(relocationKey, window);
2624
+ }, limits.messageFollowDurationMs);
2625
+ window.followTimer.unref();
2626
+ return window;
2627
+ }
2628
+
2629
+ /** The authority settlement ended: the retained batch copy is released. */
2630
+ private closeSourceCutoverWindow(relocationKey: string, window: SourceCutoverWindow): void {
2631
+ window.windowClosed = true;
2632
+ window.frames = undefined;
2633
+ this.releaseSourceCutoverWindow(relocationKey, window);
2634
+ }
2635
+
2636
+ private releaseSourceCutoverWindow(relocationKey: string, window: SourceCutoverWindow): void {
2637
+ if (
2638
+ window.windowClosed &&
2639
+ window.followExpired &&
2640
+ this.sourceCutoverWindows.get(relocationKey) === window
2641
+ ) {
2642
+ this.sourceCutoverWindows.delete(relocationKey);
2643
+ }
2644
+ }
2645
+
2646
+ /**
2647
+ * Sends the whole boundary batch and cutover again after a failed submit
2648
+ * or an observed loss of the target connection (spec 28 §4.4). The target
2649
+ * replaces its partial span with the whole batch.
2650
+ */
2651
+ private async resendSourceCutover(window: SourceCutoverWindow): Promise<void> {
2652
+ const frames = window.frames;
2653
+ if (frames === undefined) return;
2654
+ const peers = this.requireMeshNode(window.meshName).peers();
2655
+ const peer = peers.find(
2656
+ (value) =>
2657
+ value.routingId !== null && String(value.routingId) === String(window.targetNodeRid)
2658
+ );
2659
+ if (peer !== undefined && peer.state !== MeshPeerRuntimeState.Serving) {
2660
+ window.resendPending = true;
2661
+ return;
2662
+ }
2663
+ if (!window.resendPending) return;
2664
+ try {
2665
+ for (const frame of frames) {
2666
+ await this.submitControlFrame(
2667
+ window.meshName,
2668
+ window.targetNodeRid,
2669
+ frame,
2670
+ 'cutover retransmission'
2671
+ );
2672
+ }
2673
+ window.resendPending = false;
2674
+ } catch {
2675
+ // The next settlement round retries the whole batch.
2676
+ }
2677
+ }
2678
+
2679
+ /**
2680
+ * The one source authority settlement after Ready (Location runtime
2681
+ * §6.1·§10, spec 28 §4.4). It reads the Store until the target commit or
2682
+ * the Restore deadline. From the Restore deadline it runs the source
2683
+ * Preserve fence: a CAS on the StoreVersion expected by target NewOwner
2684
+ * excludes a late target commit. `target`: the
2685
+ * target committed. `source`: Preserve fenced the target out while the
2686
+ * source owner lease was valid. `lost`: the source owner lease ended first
2687
+ * or another authority owns the object. An uncertain Store or lease result
2688
+ * keeps the work retained and repeats the same decision.
2689
+ */
2690
+ private async settleSourceAuthority(
2691
+ envelope: ServiceRelocationEnvelope,
2692
+ primary: ZLinkAuthoritySnapshot,
2693
+ target: ServiceWireRelocationTarget,
2694
+ restoreDeadlineReached: () => boolean,
2695
+ window: SourceCutoverWindow
2696
+ ): Promise<'target' | 'source' | 'lost'> {
2697
+ const owner = { ownerId: primary.ownerId, leaseGeneration: primary.ownerLeaseGeneration };
2698
+ const store = this.requireLocationStore();
2699
+ for (;;) {
2700
+ if (this.disposed) throw new Error('Relocation runtime stopped.');
2701
+ await this.resendSourceCutover(window);
2702
+ const observed = await this.readSourceSettlement(envelope, primary, target).catch(
2703
+ (error: unknown) => {
2704
+ if (!isIndeterminateLocationStoreFailure(error)) throw error;
2705
+ return undefined;
2706
+ }
2707
+ );
2708
+ if (observed?.kind === 'target') return 'target';
2709
+ if (observed?.kind === 'other') return 'lost';
2710
+ if (restoreDeadlineReached() && observed?.kind === 'source') {
2711
+ // An unreadable lease is an uncertain result, not an expiry.
2712
+ if (
2713
+ await this.exactSourceLeaseExpired(owner).catch((error: unknown) => {
2714
+ if (!isIndeterminateLocationStoreFailure(error)) throw error;
2715
+ return false;
2716
+ })
2717
+ )
2718
+ return 'lost';
2719
+ const publication = this.codec.read(observed.current.payload);
2720
+ const preserved = await store
2721
+ .compareExchangeAuthority(
2722
+ { value: primaryKey(envelope) } as ZLinkAuthorityKey,
2723
+ observed.current.storeVersion,
2724
+ {
2725
+ kind: 'put',
2726
+ generationTransition: 'preserve',
2727
+ payload:
2728
+ publication === undefined
2729
+ ? observed.current.payload
2730
+ : this.codec.clear(observed.current.payload, publication.reference)
2731
+ }
2732
+ )
2733
+ .catch((error: unknown) => {
2734
+ if (!isIndeterminateLocationStoreFailure(error)) throw error;
2735
+ return undefined;
2736
+ });
2737
+ if (preserved?.kind === 'stored') return 'source';
2370
2738
  }
2371
- };
2372
- window.windowTimer = setTimeout(() => {
2373
- window.windowClosed = true;
2374
- window.frames = undefined;
2375
- if (window.retryTimer !== undefined) clearTimeout(window.retryTimer);
2376
- settle();
2377
- }, limits.relocationCutoverWaitTimeoutMs);
2378
- window.windowTimer.unref();
2379
- window.followTimer = setTimeout(() => {
2380
- window.followExpired = true;
2381
- // S1→S4 route convergence: the span the source must keep its Message
2382
- // Follow route (spec 30 §7.1).
2383
- this.options.metrics?.duration(
2384
- 'zlink.relocation.route_convergence',
2385
- Math.max(0, performance.now() - submittedAt) / 1000,
2386
- { mesh_name: meshName }
2739
+ // The caller awaits this settlement, so the retry timer keeps the loop alive.
2740
+ await new Promise<void>((resolve) =>
2741
+ setTimeout(resolve, RELOCATION_AUTHORITY_POLL_INTERVAL_MS)
2387
2742
  );
2388
- settle();
2389
- }, limits.messageFollowDurationMs);
2390
- window.followTimer.unref();
2391
- if (initialSubmitFailed) this.scheduleCutoverRetransmit(window);
2743
+ }
2392
2744
  }
2393
2745
 
2394
- private scheduleCutoverRetransmit(window: SourceCutoverWindow): void {
2395
- if (window.windowClosed || window.frames === undefined) return;
2396
- window.retryTimer = setTimeout(() => {
2397
- void (async () => {
2398
- const frames = window.frames;
2399
- if (window.windowClosed || frames === undefined) return;
2400
- try {
2401
- for (const frame of frames) {
2402
- await this.submitControlFrame(
2403
- window.meshName,
2404
- window.targetNodeRid,
2405
- frame,
2406
- 'cutover retransmission'
2407
- );
2408
- }
2409
- } catch {
2410
- this.scheduleCutoverRetransmit(window);
2411
- }
2412
- })();
2413
- }, 250);
2414
- window.retryTimer.unref();
2746
+ private async readSourceSettlement(
2747
+ envelope: ServiceRelocationEnvelope,
2748
+ primary: ZLinkAuthoritySnapshot,
2749
+ target: ServiceWireRelocationTarget
2750
+ ): Promise<
2751
+ | { readonly kind: 'target' }
2752
+ | { readonly kind: 'source'; readonly current: ZLinkAuthoritySnapshot }
2753
+ | { readonly kind: 'other' }
2754
+ > {
2755
+ const primaryAuthorityKey = primaryKey(envelope);
2756
+ let committed = 0;
2757
+ let primaryCurrent: ZLinkAuthoritySnapshot | undefined;
2758
+ for (const participant of envelope.participants) {
2759
+ const current = await this.requireLocationStore().readAuthority({
2760
+ value: participant.key
2761
+ } as ZLinkAuthorityKey);
2762
+ if (current.kind !== 'snapshot') return { kind: 'other' };
2763
+ if (
2764
+ current.objectGeneration === participant.objectGeneration &&
2765
+ current.ownerId === target.ownerId &&
2766
+ current.ownerLeaseGeneration === target.ownerLeaseGeneration &&
2767
+ current.authorityOwnerGeneration > participant.authorityOwnerGeneration &&
2768
+ String(current.allocation.descriptor.rid) === target.nodeRid &&
2769
+ current.allocation.descriptorLifecycleGeneration === target.nodeGeneration
2770
+ ) {
2771
+ committed += 1;
2772
+ }
2773
+ if (participant.key === primaryAuthorityKey) primaryCurrent = current;
2774
+ }
2775
+ if (committed === envelope.participants.length) return { kind: 'target' };
2776
+ if (primaryCurrent !== undefined && isSourceOwner(primaryCurrent, primary)) {
2777
+ return { kind: 'source', current: primaryCurrent };
2778
+ }
2779
+ return { kind: 'other' };
2415
2780
  }
2416
2781
 
2417
2782
  /**
@@ -2455,14 +2820,9 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2455
2820
  meshName: string,
2456
2821
  stagingId: string,
2457
2822
  request: ServiceMaintenanceRelocationPrepare,
2458
- sourceNodeRid: RoutingId | null,
2823
+ sourceNodeRid: RoutingId,
2459
2824
  signal?: AbortSignal
2460
2825
  ): Promise<ServiceMaintenanceRelocationReady> {
2461
- if (sourceNodeRid === null || String(sourceNodeRid) !== request.sourceNodeRid) {
2462
- throw new Error(
2463
- 'Relocation prepare source node fence does not match the authenticated peer.'
2464
- );
2465
- }
2466
2826
  const targetStatus = this.requireMeshNode(meshName).status();
2467
2827
  const targetOwner = this.options.currentOwner();
2468
2828
  const targetDescriptor = this.options.localDescriptor?.(meshName);
@@ -2480,24 +2840,15 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2480
2840
  const fingerprint = stringifyWire(request);
2481
2841
  const existing = this.targetStages.get(stagingId);
2482
2842
  if (existing !== undefined) {
2483
- if (
2484
- existing.offer.authenticatedSourceNodeRid !== String(sourceNodeRid) ||
2485
- existing.offer.prepareFingerprint !== fingerprint
2486
- ) {
2487
- throw new Error('Relocation prepare retry source node changed.');
2488
- }
2489
2843
  return relocationReady(request);
2490
2844
  }
2491
2845
  const terminal = this.terminalTargets.get(stagingId);
2492
2846
  if (terminal !== undefined) {
2493
- if (terminal !== fingerprint) {
2494
- throw new Error(`Relocation '${stagingId}' repeated Prepare with different bytes.`);
2495
- }
2496
2847
  this.terminalTargets.touch(stagingId);
2497
2848
  return relocationReady(request);
2498
2849
  }
2499
2850
  const pendingAssembly = this.targetAssemblies.get(stagingId);
2500
- if (pendingAssembly === undefined || pendingAssembly.fingerprint !== fingerprint) {
2851
+ if (pendingAssembly === undefined) {
2501
2852
  throw new Error(`Relocation '${stagingId}' has no matching payload assembly.`);
2502
2853
  }
2503
2854
  // The directly transferred payload replaces the shared durable root: the
@@ -2525,6 +2876,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2525
2876
  request.nodeInternalBoundSessions
2526
2877
  );
2527
2878
  validatePrepareEnvelope(request, inventoryEnvelope);
2879
+ const releaseActivation =
2880
+ (await this.options.activationAdmission?.acquire(meshName, signal)) ?? (() => undefined);
2528
2881
  let materialized:
2529
2882
  (Pick<LocalStage, 'owner' | 'staging'> & { readonly target: LocalTargetPort }) | undefined;
2530
2883
  let reservation: TargetRelocationReservation | undefined;
@@ -2539,7 +2892,6 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2539
2892
  meshName,
2540
2893
  request,
2541
2894
  inventoryEnvelope,
2542
- materialized.target,
2543
2895
  signal
2544
2896
  );
2545
2897
  const offer: TargetRelocationOffer = {
@@ -2547,7 +2899,6 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2547
2899
  prepareFingerprint: fingerprint,
2548
2900
  authenticatedSourceNodeRid: String(sourceNodeRid),
2549
2901
  envelope: inventoryEnvelope,
2550
- restoreDeadlineAtMs: performance.now() + RELOCATION_OPERATION_RETENTION_MS,
2551
2902
  reservation
2552
2903
  };
2553
2904
  const stage: LocalStage = {
@@ -2556,10 +2907,12 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2556
2907
  phase: 'ready',
2557
2908
  lane: Promise.resolve(),
2558
2909
  cutoverReceived: false,
2559
- boundaryRelay: []
2910
+ boundaryRelay: [],
2911
+ releaseActivation
2560
2912
  };
2561
2913
  this.targetStages.set(stagingId, stage);
2562
2914
  } catch (error) {
2915
+ releaseActivation();
2563
2916
  if (reservation !== undefined) {
2564
2917
  await this.abortTargetReservation(reservation, signal).catch(() => undefined);
2565
2918
  }
@@ -2571,11 +2924,23 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2571
2924
  return relocationReady(request);
2572
2925
  }
2573
2926
 
2927
+ /**
2928
+ * The cutover wait of a Ready stage (spec 28 §4.4) is a Warning only: it
2929
+ * records `cutover_timeout` and never submits the target CAS or opens
2930
+ * dispatch. From then on the target settlement reads the Location Store so
2931
+ * a source Preserve fence or a lost target lease ends the staging; only a
2932
+ * verified cutover lets that settlement submit the CAS.
2933
+ */
2574
2934
  private armTargetCutoverFallback(meshName: string, stagingId: string): void {
2575
2935
  const stage = this.targetStages.get(stagingId);
2576
2936
  if (stage === undefined || stage.fallback !== undefined || stage.finalize !== undefined) return;
2577
2937
  stage.fallback = setTimeout(() => {
2578
- void this.beginTargetFinalize(meshName, stagingId, stage, true).catch((error) =>
2938
+ if (stage.finalize !== undefined) return;
2939
+ console.warn('[zlink.runtime.relocation.cutover_timeout]', stagingId);
2940
+ this.options.metrics?.count(METRIC_NAMES.RelocationCutoverTimeout, 1, {
2941
+ mesh_name: meshName
2942
+ });
2943
+ void this.beginTargetFinalize(meshName, stagingId, stage).catch((error) =>
2579
2944
  console.error('[zlink.runtime.relocation.location_update_failed]', error)
2580
2945
  );
2581
2946
  }, this.relocationLimits().relocationCutoverWaitTimeoutMs);
@@ -2594,7 +2959,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2594
2959
  const stagingId = relocationStagingId(request);
2595
2960
  const stage = this.targetStages.get(stagingId);
2596
2961
  if (stage === undefined) {
2597
- if (this.terminalTargets.get(stagingId) !== undefined) {
2962
+ if (this.disposed || this.terminalTargets.get(stagingId) !== undefined) {
2598
2963
  if (request.kind === 'cutover') {
2599
2964
  console.warn('[zlink.runtime.relocation.late_cutover]', stagingId);
2600
2965
  }
@@ -2603,19 +2968,29 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2603
2968
  throw new Error(`Relocation staging '${stagingId}' is missing.`);
2604
2969
  }
2605
2970
  validateTargetOneWayControl(stage, request, sourceNodeRid);
2606
- if (request.kind === 'data') {
2607
- stage.lane = stage.lane.then(() => this.stageBoundaryRelay(stage, request));
2608
- await stage.lane;
2609
- return;
2610
- }
2611
- if (stage.cutoverReceived || stage.phase !== 'ready') {
2612
- console.warn('[zlink.runtime.relocation.late_cutover]', stagingId);
2613
- return;
2614
- }
2615
- this.reconcileBoundaryRelay(stage, request, stagingId);
2616
- stage.cutoverReceived = true;
2971
+ stage.lane = stage.lane.then(() => {
2972
+ // A verified cutover closes the boundary batch. A later record or cutover
2973
+ // is a duplicate retransmission and never changes the staged batch.
2974
+ if (stage.cutoverReceived || (stage.phase !== 'ready' && stage.phase !== 'finalizing')) {
2975
+ if (request.kind === 'cutover') {
2976
+ console.warn('[zlink.runtime.relocation.late_cutover]', stagingId);
2977
+ }
2978
+ return;
2979
+ }
2980
+ if (request.kind === 'data') {
2981
+ return this.stageBoundaryRelay(stage, request);
2982
+ }
2983
+ if (this.targetStageSealed(stage)) return;
2984
+ this.reconcileBoundaryRelay(stage, request, stagingId);
2985
+ stage.cutoverReceived = true;
2986
+ });
2987
+ await stage.lane;
2988
+ if (request.kind === 'data') return;
2989
+ // A settlement started by the cutover wait Warning is still reading the
2990
+ // Store; it submits the CAS on its next round now that cutover verified.
2991
+ if (stage.finalize !== undefined) return;
2617
2992
  if (stage.fallback !== undefined) clearTimeout(stage.fallback);
2618
- await this.beginTargetFinalize(meshName, stagingId, stage, false, signal);
2993
+ await this.beginTargetFinalize(meshName, stagingId, stage, signal);
2619
2994
  }
2620
2995
 
2621
2996
  /** Validates one boundary relay record and buffers it until cutover applies it. */
@@ -2643,15 +3018,15 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2643
3018
  }
2644
3019
  const expectedCount = Number(request.boundaryRecordCount);
2645
3020
  const staged = stage.boundaryRelay;
2646
- const checksumOf = (records: readonly ServiceMaintenanceRelocationData[]) =>
2647
- crc32c(Buffer.concat(records.map((value) => value.frozenRecord.canonicalBytes)));
2648
- if (staged.length === expectedCount && checksumOf(staged) === request.boundaryChecksumCrc32c) {
2649
- return;
2650
- }
2651
- if (staged.length > expectedCount) {
2652
- const suffix = staged.slice(staged.length - expectedCount);
2653
- if (checksumOf(suffix) === request.boundaryChecksumCrc32c) {
2654
- console.warn('[zlink.runtime.relocation.boundary_batch_replaced]', stagingId);
3021
+ // The batch is the last `boundaryRecordCount` records: the source sends
3022
+ // the whole batch right before this cutover on the ordered connection,
3023
+ // so a retransmission replaces an earlier partial span as a whole.
3024
+ if (staged.length >= expectedCount) {
3025
+ const batch = staged.slice(staged.length - expectedCount);
3026
+ if (
3027
+ crc32c(Buffer.concat(batch.map((value) => value.frozenRecord.canonicalBytes))) ===
3028
+ request.boundaryChecksumCrc32c
3029
+ ) {
2655
3030
  staged.splice(0, staged.length - expectedCount);
2656
3031
  return;
2657
3032
  }
@@ -2668,21 +3043,16 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2668
3043
  meshName: string,
2669
3044
  stagingId: string,
2670
3045
  stage: LocalStage,
2671
- fallback: boolean,
2672
3046
  signal?: AbortSignal
2673
3047
  ): Promise<void> {
2674
3048
  if (stage.finalize !== undefined) return stage.finalize;
2675
- if (fallback) {
2676
- console.warn('[zlink.runtime.relocation.cutover_timeout]', stagingId);
2677
- this.options.metrics?.count('zlink.relocation.cutover_timeout', 1, {
2678
- mesh_name: meshName
2679
- });
2680
- }
3049
+ // Records staged before this point finish first. The settlement is not
3050
+ // put on the lane, so a cutover that arrives while it waits for the
3051
+ // authority result is still reconciled.
2681
3052
  const finalize = stage.lane.then(() =>
2682
3053
  this.finalizeTargetStage(meshName, stagingId, stage, signal)
2683
3054
  );
2684
3055
  stage.finalize = finalize;
2685
- stage.lane = finalize;
2686
3056
  return finalize;
2687
3057
  }
2688
3058
 
@@ -2698,14 +3068,14 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2698
3068
  let authority: ZLinkAuthoritySnapshot | undefined;
2699
3069
  let resumeStartedAt: number | undefined;
2700
3070
  try {
2701
- // The boundary relay span is applied to the temporary queue before the
2702
- // owner CAS so the merged order is saved work, pre-boundary relay, then
2703
- // post-boundary temporary records (spec 28 §4.6).
2704
- for (const relay of stage.boundaryRelay.splice(0)) {
2705
- await this.applyRelocationData(stage, relay, signal);
2706
- }
2707
3071
  authority = await this.commitTargetReservation(stage, stage.offer.reservation, signal);
3072
+ if (authority === undefined) {
3073
+ // The seal owner may have ended staging while a Store read was pending.
3074
+ if ((stage as LocalStage).phase === 'failed') return;
3075
+ return this.discardTargetStage(stagingId, stage, signal);
3076
+ }
2708
3077
  authorityCommitted = true;
3078
+ stage.releaseActivation();
2709
3079
  resumeStartedAt = performance.now();
2710
3080
  stage.phase = 'committed';
2711
3081
  await stage.owner.normalize(stage.staging, authority, signal);
@@ -2720,13 +3090,10 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2720
3090
  }
2721
3091
  }
2722
3092
  await this.finalizeActorJoinProfiles(meshName, stage, signal);
2723
- // Actor Join completion may advance its accepted-completion journal in
2724
- // the canonical authority slot. Try to clear that final publication
2725
- // before admission becomes externally visible so a later Join cannot
2726
- // reserve against the previous root. A bounded CAS conflict must not
2727
- // roll back already committed target ownership or strand its staged
2728
- // object: the next reservation recognizes the retained journal root and
2729
- // continues its existing cleanup/retry path.
3093
+ // Clear the final relocation publication before admission becomes
3094
+ // externally visible so a later Join cannot reserve against the
3095
+ // previous root. A bounded CAS conflict must not roll back already
3096
+ // committed target ownership or strand its staged object.
2730
3097
  try {
2731
3098
  await this.clearTargetRelocationPublication(stage, authority, signal);
2732
3099
  } catch (error) {
@@ -2738,8 +3105,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2738
3105
  await stage.owner.openAdmission(stage.staging, signal);
2739
3106
  // S2→S3: owner confirmation to application dispatch opening.
2740
3107
  this.options.metrics?.duration(
2741
- 'zlink.relocation.target_resume',
2742
- Math.max(0, performance.now() - resumeStartedAt) / 1000,
3108
+ METRIC_NAMES.RelocationTargetResume,
3109
+ Math.max(0, performance.now() - resumeStartedAt) / MILLISECONDS_PER_SECOND,
2743
3110
  { mesh_name: meshName }
2744
3111
  );
2745
3112
  await this.publishSessionRoutes(stage.staging);
@@ -2754,14 +3121,15 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2754
3121
  this.targetStages.delete(stagingId);
2755
3122
  this.targetAssemblies.delete(stagingId);
2756
3123
  } catch (error) {
2757
- stage.phase = 'failed';
2758
3124
  this.terminalTargets.remember(stagingId, stage.offer.prepareFingerprint);
2759
3125
  this.targetStages.delete(stagingId);
2760
3126
  this.targetAssemblies.delete(stagingId);
2761
3127
  if (!authorityCommitted) {
2762
- await this.abortTargetStage(stage).catch(() => undefined);
3128
+ // abortTargetStage may already have assigned failed before throwing.
3129
+ if ((stage as LocalStage).phase !== 'failed') await this.abortTargetStage(stage);
2763
3130
  throw error;
2764
3131
  }
3132
+ stage.phase = 'failed';
2765
3133
  if (authority !== undefined) {
2766
3134
  throw new ServiceRelocationPostCommitError(authority, stage.staging, error);
2767
3135
  }
@@ -2897,19 +3265,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2897
3265
  }
2898
3266
  this.sourceActorJoinProfiles.delete(input.transferId);
2899
3267
  this.terminalActorJoinSourceLeaves.remember(input.transferId, input.actorId);
2900
- void profile.ready
2901
- .then(async () => {
2902
- await this.options.actorTransfer.completeRelocationSourceLeave(
2903
- profile.actor,
2904
- profile.sourceSpotId
2905
- );
2906
- await this.options
2907
- .actorManager()
2908
- ?.completeRelocationSource(input.actorId, profile.sourceActorRef);
2909
- })
2910
- .catch((error) => {
2911
- console.error('[zlink.runtime.relocation.source_leave_failed]', error);
2912
- });
3268
+ void this.finalizeActorJoinSource(profile);
2913
3269
  return true;
2914
3270
  }
2915
3271
 
@@ -2941,41 +3297,6 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2941
3297
  }
2942
3298
  }
2943
3299
 
2944
- private async waitForTargetAuthorities(
2945
- envelope: ServiceRelocationEnvelope,
2946
- target: ServiceWireRelocationTarget,
2947
- deadlineAtMs: number,
2948
- signal?: AbortSignal
2949
- ): Promise<void> {
2950
- for (;;) {
2951
- signal?.throwIfAborted();
2952
- let exact = true;
2953
- for (const participant of envelope.participants) {
2954
- const current = await this.requireLocationStore().readAuthority(
2955
- { value: participant.key } as ZLinkAuthorityKey,
2956
- signal
2957
- );
2958
- if (
2959
- current.kind !== 'snapshot' ||
2960
- current.objectGeneration !== participant.objectGeneration ||
2961
- current.ownerId !== target.ownerId ||
2962
- current.ownerLeaseGeneration !== target.ownerLeaseGeneration ||
2963
- current.authorityOwnerGeneration <= participant.authorityOwnerGeneration ||
2964
- String(current.allocation.descriptor.rid) !== target.nodeRid ||
2965
- current.allocation.descriptorLifecycleGeneration !== target.nodeGeneration
2966
- ) {
2967
- exact = false;
2968
- break;
2969
- }
2970
- }
2971
- if (exact) return;
2972
- if (performance.now() >= deadlineAtMs) {
2973
- throw new Error('Relocation target owner was not confirmed before its original deadline.');
2974
- }
2975
- await new Promise<void>((resolve) => setTimeout(resolve, 10));
2976
- }
2977
- }
2978
-
2979
3300
  private async clearTargetRelocationPublication(
2980
3301
  stage: LocalStage,
2981
3302
  primary: ZLinkAuthoritySnapshot,
@@ -2986,36 +3307,25 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
2986
3307
  stage.staging.primaryAuthorityKey.value,
2987
3308
  signal
2988
3309
  );
2989
- // The deferred-Join journal advances its completion cursor with its
2990
- // own authority CAS while the target finalizes, so the initially
2991
- // captured storeVersion can be stale by the time the relocation
2992
- // wrapper is cleared. Re-read and retry on conflict instead of turning
2993
- // a completed relocation into a post-commit failure (spec 28 —
2994
- // retryable store conflicts converge on the same CAS and the same
2995
- // relocation identity until the outcome is determined).
2996
- let current: ZLinkAuthoritySnapshot = primary;
2997
- for (let attempt = 0; attempt < 16; attempt += 1) {
2998
- const publication = this.codec.read(current.payload);
2999
- if (publication === undefined) return;
3000
- const result = await this.requireLocationStore().compareExchangeAuthority(
3001
- stage.staging.primaryAuthorityKey,
3002
- current.storeVersion,
3003
- {
3004
- kind: 'put',
3005
- generationTransition: 'preserve',
3006
- payload: this.codec.clear(current.payload, publication.reference)
3007
- },
3008
- signal
3009
- );
3010
- if (result.kind === 'stored') return;
3011
- const read = await this.requireLocationStore().readAuthority(
3012
- stage.staging.primaryAuthorityKey,
3013
- signal
3014
- );
3015
- if (read.kind !== 'snapshot') break;
3016
- current = read;
3310
+ // The target owner is the only writer of the committed primary
3311
+ // authority between its commit and this clear, so one CAS against the
3312
+ // committed snapshot decides. A conflict means another owner changed the
3313
+ // row; the caller records it and leaves the committed ownership intact.
3314
+ const publication = this.codec.read(primary.payload);
3315
+ if (publication === undefined) return;
3316
+ const result = await this.requireLocationStore().compareExchangeAuthority(
3317
+ stage.staging.primaryAuthorityKey,
3318
+ primary.storeVersion,
3319
+ {
3320
+ kind: 'put',
3321
+ generationTransition: 'preserve',
3322
+ payload: this.codec.clear(primary.payload, publication.reference)
3323
+ },
3324
+ signal
3325
+ );
3326
+ if (result.kind !== 'stored') {
3327
+ throw new Error('Relocation target authority normalization CAS failed.');
3017
3328
  }
3018
- throw new Error('Relocation target authority normalization CAS failed.');
3019
3329
  }
3020
3330
 
3021
3331
  private async handleReplyRelay(
@@ -3129,7 +3439,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3129
3439
  pending.resolve('sourceLeaseExpired');
3130
3440
  return;
3131
3441
  }
3132
- if (++attempts > 120) {
3442
+ if (++attempts > RELOCATION_MAX_SEND_ATTEMPTS) {
3133
3443
  pending.reject(new Error(`Relocation reply relay ACK '${key}' timed out.`));
3134
3444
  return;
3135
3445
  }
@@ -3139,7 +3449,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3139
3449
  encodeMaintenanceReplyRelay(request)
3140
3450
  );
3141
3451
  if (this.pendingReplyRelays.get(key) === pending) {
3142
- pending.timer = setTimeout(() => void send(), 250);
3452
+ pending.timer = setTimeout(() => void send(), RELOCATION_RESEND_DELAY_MS);
3143
3453
  }
3144
3454
  } catch (error) {
3145
3455
  pending.reject(error);
@@ -3269,7 +3579,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3269
3579
  }
3270
3580
 
3271
3581
  private async exactSourceLeaseExpired(
3272
- source: ServiceWireRequestSourceFence,
3582
+ source: Pick<ServiceWireRequestSourceFence, 'ownerId' | 'leaseGeneration'>,
3273
3583
  signal?: AbortSignal
3274
3584
  ): Promise<boolean> {
3275
3585
  const lease = await this.requireLocationStore().readOwnerLease(source.ownerId, signal);
@@ -3284,10 +3594,14 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3284
3594
  private async publishSessionRoutes(
3285
3595
  staging: ServiceObjectRelocationStaging<LocalHidden>
3286
3596
  ): Promise<void> {
3287
- for (const hidden of staging.hidden.values()) {
3288
- if (hidden.actor !== undefined) {
3289
- await this.options.actorTransfer.publishRoutedActorOwnership(hidden.actor);
3290
- }
3597
+ for (const [key, hidden] of staging.hidden) {
3598
+ if (hidden.actor === undefined) continue;
3599
+ // The Session seal travels with the relocation envelope, not the Actor.
3600
+ const carried = staging.envelope.participants.find((value) => value.key === key);
3601
+ await this.options.actorTransfer.publishRoutedActorOwnership(
3602
+ hidden.actor,
3603
+ carried === undefined ? undefined : decodeActorSession(carried.boundSessionState)
3604
+ );
3291
3605
  }
3292
3606
  }
3293
3607
 
@@ -3296,7 +3610,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3296
3610
  targetNodeRid: RoutingId,
3297
3611
  request: ZLinkServiceRelocationControlRequest,
3298
3612
  signal?: AbortSignal,
3299
- deadlineAtMs = performance.now() + 30_000,
3613
+ deadlineAtMs: number = performance.now() + RELOCATION_OPERATION_TIMEOUT_MS,
3300
3614
  sideband: readonly Uint8Array[] = []
3301
3615
  ): Promise<ReturnType<typeof decodeServiceRelocationControlResponse>> {
3302
3616
  const key = controlAckKey(request);
@@ -3316,7 +3630,10 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3316
3630
  lastRequestError
3317
3631
  );
3318
3632
  }
3319
- const attemptTimeoutMs = Math.max(1, Math.min(250, Math.ceil(remainingMs)));
3633
+ const attemptTimeoutMs = Math.max(
3634
+ 1,
3635
+ Math.min(RELOCATION_ATTEMPT_TIMEOUT_MS, Math.ceil(remainingMs))
3636
+ );
3320
3637
  const startedAtMs = performance.now();
3321
3638
  try {
3322
3639
  const replyFrames = await this.requestInfrastructureControl(
@@ -3342,11 +3659,13 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3342
3659
  throw error;
3343
3660
  }
3344
3661
  lastRequestError = error;
3345
- relocationDebug('control.request_retry', {
3346
- key,
3347
- kind: request.kind,
3348
- targetRid: String(targetNodeRid)
3349
- });
3662
+ if (isRelocationDebugEnabled()) {
3663
+ relocationDebug('control.request_retry', {
3664
+ key,
3665
+ kind: request.kind,
3666
+ targetRid: String(targetNodeRid)
3667
+ });
3668
+ }
3350
3669
  const retryDelayMs = Math.min(
3351
3670
  Math.max(0, attemptTimeoutMs - (performance.now() - startedAtMs)),
3352
3671
  Math.max(0, deadlineAtMs - performance.now())
@@ -3386,7 +3705,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3386
3705
  peer.routingId !== null &&
3387
3706
  String(peer.routingId) === String(descriptor.rid) &&
3388
3707
  peer.lifecycleGeneration === descriptor.lifecycleGeneration &&
3389
- peer.state === 3
3708
+ peer.state === MeshPeerRuntimeState.Serving
3390
3709
  ) &&
3391
3710
  relocationTargetSupports(descriptor, requirements)
3392
3711
  )
@@ -3436,51 +3755,10 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3436
3755
  meshName: string,
3437
3756
  prepare: ServiceMaintenanceRelocationPrepare,
3438
3757
  envelope: ServiceRelocationEnvelope,
3439
- targetPort: LocalTargetPort,
3440
3758
  signal?: AbortSignal
3441
3759
  ): Promise<TargetRelocationReservation> {
3442
3760
  const authorities = await this.readTargetParticipantAuthorities(envelope, signal);
3443
3761
  const publication = relocationPublication(prepare, envelope);
3444
- // Carries the exact reference + CRC of a deferred-Join publication the
3445
- // target reservation must keep connected through the CAS (spec 28 —
3446
- // clearing/replacing it here would strand the journal's own
3447
- // prepared->committed transition with nothing left for it to find).
3448
- const retainedDeferredJoinRoots = new Map<
3449
- string,
3450
- { readonly reference: string; readonly checksumCrc32c: number }
3451
- >();
3452
- for (const participant of envelope.participants) {
3453
- const staged = targetPort.deferredJoinRoot(participant.key);
3454
- if (staged !== undefined) {
3455
- retainedDeferredJoinRoots.set(participant.key, {
3456
- reference: staged.reference.value,
3457
- checksumCrc32c: staged.checksumCrc32c
3458
- });
3459
- continue;
3460
- }
3461
- const current = this.codec.read(authorities.get(participant.key)!.payload);
3462
- if (
3463
- participant.objectKind === 'actor' &&
3464
- current?.canonical === true &&
3465
- (await this.options.actorTransfer.isDeferredJoinAcceptedRootPublication(
3466
- current.reference,
3467
- current.checksumCrc32c,
3468
- {
3469
- authorityKey: participant.key,
3470
- objectKind: 'actor',
3471
- objectGeneration: participant.objectGeneration,
3472
- aggregateId: current.aggregateId,
3473
- aggregateGeneration: current.aggregateGeneration
3474
- },
3475
- signal
3476
- ))
3477
- ) {
3478
- retainedDeferredJoinRoots.set(participant.key, {
3479
- reference: current.reference,
3480
- checksumCrc32c: current.checksumCrc32c
3481
- });
3482
- }
3483
- }
3484
3762
  const participants = envelope.participants.map((participant) => {
3485
3763
  const expected = authorities.get(participant.key)!;
3486
3764
  const membership = actorMembershipTarget(envelope, participant.key);
@@ -3495,48 +3773,34 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3495
3773
  ) {
3496
3774
  throw new Error(`Relocation target RouteMesh '${meshName}' has no Entry Spot.`);
3497
3775
  }
3498
- const retainedDeferredJoinRoot = retainedDeferredJoinRoots.get(participant.key);
3499
- const participantPublication =
3500
- retainedDeferredJoinRoot === undefined
3501
- ? publication
3502
- : {
3503
- ...publication,
3504
- reference: retainedDeferredJoinRoot.reference,
3505
- checksumCrc32c: retainedDeferredJoinRoot.checksumCrc32c
3506
- };
3507
3776
  return {
3508
3777
  key: { value: participant.key } as ZLinkAuthorityKey,
3509
3778
  expected,
3510
3779
  ownerTransition: 'newOwner' as const,
3511
- authorityPayload: this.authorityPayloadForPublication(
3512
- expected.payload,
3513
- participantPublication,
3514
- {
3515
- owner: {
3516
- ownerId: prepare.target.ownerId,
3517
- leaseGeneration: prepare.target.ownerLeaseGeneration
3518
- },
3519
- meshName,
3520
- nodeRid: prepare.target.nodeRid,
3521
- nodeGeneration: prepare.target.nodeGeneration,
3522
- objectGeneration: expected.objectGeneration,
3523
- targetAttemptGeneration: prepare.targetAttemptGeneration,
3524
- coordinatorExpectedStoreVersion: expected.storeVersion.value,
3525
- ...(membership === undefined
3526
- ? expected.allocation.objectKind === 'actor'
3527
- ? {
3528
- actorSpotId: entrySpotId!,
3529
- actorSpotGeneration: prepare.target.nodeGeneration,
3530
- actorSpotKind: ZLinkSpotKind.Entry as const
3531
- }
3532
- : {}
3533
- : {
3534
- ...membership,
3535
- actorSpotKind: ZLinkSpotKind.User as const
3536
- })
3780
+ authorityPayload: this.authorityPayloadForPublication(expected.payload, publication, {
3781
+ owner: {
3782
+ ownerId: prepare.target.ownerId,
3783
+ leaseGeneration: prepare.target.ownerLeaseGeneration
3537
3784
  },
3538
- retainedDeferredJoinRoot !== undefined
3539
- ),
3785
+ meshName,
3786
+ nodeRid: prepare.target.nodeRid,
3787
+ nodeGeneration: prepare.target.nodeGeneration,
3788
+ objectGeneration: expected.objectGeneration,
3789
+ targetAttemptGeneration: prepare.targetAttemptGeneration,
3790
+ coordinatorExpectedStoreVersion: expected.storeVersion.value,
3791
+ ...(membership === undefined
3792
+ ? expected.allocation.objectKind === 'actor'
3793
+ ? {
3794
+ actorSpotId: entrySpotId!,
3795
+ actorSpotGeneration: prepare.target.nodeGeneration,
3796
+ actorSpotKind: ZLinkSpotKind.Entry as const
3797
+ }
3798
+ : {}
3799
+ : {
3800
+ ...membership,
3801
+ actorSpotKind: ZLinkSpotKind.User as const
3802
+ })
3803
+ }),
3540
3804
  membershipMutation: encodeMembershipMutation(envelope.memberships, participant.key)
3541
3805
  };
3542
3806
  });
@@ -3576,24 +3840,16 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3576
3840
  readonly actorSpotId?: string;
3577
3841
  readonly actorSpotGeneration?: bigint;
3578
3842
  readonly actorSpotKind?: ZLinkSpotKind.Entry | ZLinkSpotKind.User;
3579
- },
3580
- replacesDeferredJoinRoot = false
3843
+ }
3581
3844
  ): Uint8Array {
3582
3845
  const existing = this.codec.read(payload);
3583
3846
  const canonicalIdentity = serviceRelocationAuthoritySlotIdentity(payload);
3584
- const replacesCanonicalJournal = replacesDeferredJoinRoot && existing?.canonical === true;
3585
3847
  if (
3586
3848
  canonicalIdentity !== undefined &&
3587
3849
  (existing === undefined ||
3588
- (existing.canonical === true &&
3589
- (existing.aggregateId === publication.aggregateId || replacesCanonicalJournal)))
3850
+ (existing.canonical === true && existing.aggregateId === publication.aggregateId))
3590
3851
  ) {
3591
- const published =
3592
- existing === undefined
3593
- ? this.codec.publish(payload, publication)
3594
- : replacesCanonicalJournal && existing.aggregateId !== publication.aggregateId
3595
- ? this.codec.publish(this.codec.clear(payload, existing.reference), publication)
3596
- : payload;
3852
+ const published = existing === undefined ? this.codec.publish(payload, publication) : payload;
3597
3853
  if (target === undefined) return published;
3598
3854
  return projectServiceRelocationAuthorityTargetReady(
3599
3855
  replaceServiceRelocationAuthorityApplicationPayload(
@@ -3665,6 +3921,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3665
3921
 
3666
3922
  private async abortTargetStage(stage: LocalStage, signal?: AbortSignal): Promise<void> {
3667
3923
  if (stage.fallback !== undefined) clearTimeout(stage.fallback);
3924
+ stage.releaseActivation();
3668
3925
  const failures: unknown[] = [];
3669
3926
  try {
3670
3927
  await this.abortTargetReservation(stage.offer.reservation, signal);
@@ -3683,35 +3940,73 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3683
3940
  }
3684
3941
  }
3685
3942
 
3943
+ private async discardTargetStage(
3944
+ stagingId: string,
3945
+ stage: LocalStage,
3946
+ signal?: AbortSignal
3947
+ ): Promise<void> {
3948
+ stage.phase = 'failed';
3949
+ console.warn('[zlink.runtime.relocation.target_staging_discarded]', stagingId);
3950
+ try {
3951
+ await this.abortTargetStage(stage, signal);
3952
+ } finally {
3953
+ this.terminalTargets.remember(stagingId, stage.offer.prepareFingerprint);
3954
+ this.targetStages.delete(stagingId);
3955
+ this.targetAssemblies.delete(stagingId);
3956
+ }
3957
+ }
3958
+
3686
3959
  private async commitTargetReservation(
3687
3960
  stage: LocalStage,
3688
3961
  reservation: TargetRelocationReservation,
3689
3962
  signal?: AbortSignal
3690
- ): Promise<ZLinkAuthoritySnapshot> {
3963
+ ): Promise<ZLinkAuthoritySnapshot | undefined> {
3691
3964
  return await this.commitTargetAggregate(stage, reservation.prepared, signal);
3692
3965
  }
3693
3966
 
3967
+ /**
3968
+ * The one target authority settlement (Location runtime §10, spec 28
3969
+ * §4.4-§4.5). Only a verified cutover lets the target submit its NewOwner
3970
+ * CAS; a transient or indeterminate result resubmits the same RelocationId
3971
+ * and expected StoreVersion while the target owner lease is valid (no
3972
+ * separate timeout, no fixed count). Before a verified cutover the target
3973
+ * only reads. It returns the committed authority, or undefined when a
3974
+ * definitive Conflict excludes this commit after reconciliation, a source
3975
+ * Preserve fence moved the source row, or the target lease ended; the
3976
+ * caller then discards the staging.
3977
+ */
3694
3978
  private async commitTargetAggregate(
3695
3979
  stage: LocalStage,
3696
3980
  prepared: ServicePreparedRelocationAggregate,
3697
3981
  signal?: AbortSignal
3698
- ): Promise<ZLinkAuthoritySnapshot> {
3699
- let firstError: unknown;
3982
+ ): Promise<ZLinkAuthoritySnapshot | undefined> {
3983
+ const target = stage.offer.prepare.target;
3700
3984
  for (;;) {
3985
+ if (stage.phase === 'failed') return undefined;
3701
3986
  signal?.throwIfAborted();
3702
- let result: Awaited<ReturnType<ZLinkLocationStore['commitAggregate']>> | undefined;
3703
- try {
3704
- result = await this.requireLocationStore().commitAggregate(prepared.fence, signal);
3705
- } catch (error) {
3706
- firstError ??= error;
3707
- }
3708
- if (result?.kind === 'stale' || result?.kind === 'generationExhausted') {
3709
- throw new Error(
3710
- `location_update_failed: relocation aggregate commit returned ${result.kind}.`,
3711
- { cause: firstError }
3712
- );
3987
+ if (this.targetStageSealed(stage)) return undefined;
3988
+ let conflict = false;
3989
+ if (stage.cutoverReceived) {
3990
+ // The boundary relay span is applied to the temporary queue before
3991
+ // the owner CAS so the merged order is saved work, pre-boundary relay,
3992
+ // then post-boundary temporary records (spec 28 §4.6).
3993
+ for (const relay of stage.boundaryRelay.splice(0)) {
3994
+ await this.applyRelocationData(stage, relay, signal);
3995
+ }
3996
+ // A thrown provider call has an unknown result; the read below decides.
3997
+ const result = await this.requireLocationStore()
3998
+ .commitAggregate(prepared.fence, signal)
3999
+ .catch((error: unknown) => {
4000
+ if (!isIndeterminateLocationStoreFailure(error, signal)) throw error;
4001
+ return undefined;
4002
+ });
4003
+ if (result?.kind === 'generationExhausted') {
4004
+ throw new Error('location_update_failed: relocation aggregate commit exhausted.');
4005
+ }
4006
+ conflict = result?.kind === 'stale';
3713
4007
  }
3714
4008
  const observed = await this.readAggregateForCommitRetry(prepared, signal);
4009
+ if ((stage as LocalStage).phase === 'failed') return undefined;
3715
4010
  if (observed.kind === 'committed') {
3716
4011
  const primary = observed.authorities.get(stage.staging.primaryAuthorityKey.value);
3717
4012
  if (primary === undefined) {
@@ -3719,15 +4014,20 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3719
4014
  }
3720
4015
  return primary;
3721
4016
  }
3722
- if (observed.kind === 'stale') {
3723
- throw new Error(
3724
- 'location_update_failed: relocation aggregate source authority became stale.',
3725
- {
3726
- cause: firstError
3727
- }
3728
- );
4017
+ if (observed.kind === 'stale' || (conflict && observed.kind === 'source')) return undefined;
4018
+ // An unreadable lease is an uncertain result, not an expiry.
4019
+ if (
4020
+ await this.exactSourceLeaseExpired(
4021
+ { ownerId: target.ownerId, leaseGeneration: target.ownerLeaseGeneration },
4022
+ signal
4023
+ ).catch((error: unknown) => {
4024
+ if (!isIndeterminateLocationStoreFailure(error, signal)) throw error;
4025
+ return false;
4026
+ })
4027
+ ) {
4028
+ return undefined;
3729
4029
  }
3730
- await waitForLocationRetry(stage.offer.restoreDeadlineAtMs, signal, firstError);
4030
+ await waitForRelocationRetry(RELOCATION_AUTHORITY_RETRY_DELAY_MS, signal);
3731
4031
  }
3732
4032
  }
3733
4033
 
@@ -3769,7 +4069,8 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3769
4069
  return { kind: 'stale' };
3770
4070
  }
3771
4071
  }
3772
- } catch {
4072
+ } catch (error) {
4073
+ if (!isIndeterminateLocationStoreFailure(error, signal)) throw error;
3773
4074
  return { kind: 'unknown' };
3774
4075
  }
3775
4076
  if (committed === prepared.plan.participants.length) {
@@ -3821,7 +4122,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3821
4122
  if (request.object.kind === 'userSpot' || request.object.kind === 'instanceSpot') {
3822
4123
  let cursor: ZLinkAuthorityScanCursor | undefined;
3823
4124
  for (;;) {
3824
- const page = await store.listAuthorities('', cursor, 1000, signal);
4125
+ const page = await store.listAuthorities('', cursor, ZLINK_PROVIDER_MAX_PAGE_SIZE, signal);
3825
4126
  if (page.kind === 'scanExpired') {
3826
4127
  throw new Error('Location Store authority scan expired during relocation.');
3827
4128
  }
@@ -3966,7 +4267,7 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3966
4267
  );
3967
4268
  if (captured.byteLength > RELOCATION_PARTICIPANT_STATE_LIMIT_BYTES) {
3968
4269
  throw new ZLinkRelocationStateIncompatibleError(
3969
- 'Relocation application state exceeds the 64 MiB participant limit.'
4270
+ `Relocation application state exceeds the ${RELOCATION_PARTICIPANT_STATE_LIMIT_BYTES / (1024 * 1024)} MiB participant limit.`
3970
4271
  );
3971
4272
  }
3972
4273
  return captured;
@@ -3987,13 +4288,13 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
3987
4288
  startedAt: number,
3988
4289
  outcome: string
3989
4290
  ): void {
3990
- const seconds = Math.max(0, performance.now() - startedAt) / 1000;
4291
+ const seconds = Math.max(0, performance.now() - startedAt) / MILLISECONDS_PER_SECOND;
3991
4292
  const attributes = {
3992
4293
  unit_kind: unitKind,
3993
4294
  ...(executionMode === undefined ? {} : { execution_mode: executionMode })
3994
4295
  };
3995
- this.options.metrics?.duration('zlink.relocation.interruption', seconds, attributes);
3996
- this.options.metrics?.count('zlink.relocation.completed', 1, {
4296
+ this.options.metrics?.duration(METRIC_NAMES.RelocationInterruption, seconds, attributes);
4297
+ this.options.metrics?.count(METRIC_NAMES.RelocationCompleted, 1, {
3997
4298
  mesh_name: meshName,
3998
4299
  object_kind: unitKind,
3999
4300
  policy: unitKind === 'user_spot' && executionMode === 'per_actor' ? 'recreate' : 'configured',
@@ -4095,6 +4396,26 @@ export class ZLinkHostServiceRelocationRuntime implements ZLinkActorJoinRelocati
4095
4396
  return value;
4096
4397
  }
4097
4398
 
4399
+ private afterSpotCloseDecision<T>(
4400
+ meshName: string,
4401
+ spotId: RoutingId,
4402
+ seal: () => T
4403
+ ): T | Promise<T> {
4404
+ const manager = this.requireSpotManager();
4405
+ const sealAfterDecision = (): T => {
4406
+ if (manager.isSpotClosing(meshName, spotId)) {
4407
+ throw createInternalFrameworkException(
4408
+ ZLinkFrameworkInternalErrorKind.SpotMoving,
4409
+ `Spot '${String(spotId)}' is closing.`,
4410
+ true
4411
+ );
4412
+ }
4413
+ return seal();
4414
+ };
4415
+ const decision = manager.pendingSpotCloseDecision(meshName, spotId);
4416
+ return decision === undefined ? sealAfterDecision() : decision.then(sealAfterDecision);
4417
+ }
4418
+
4098
4419
  private requireActorManager(): DefaultZLinkActorManager {
4099
4420
  const value = this.options.actorManager();
4100
4421
  if (value === undefined) throw new Error('Host relocation requires the Actor manager.');
@@ -4231,14 +4552,7 @@ function actorJoinTargetMembership(
4231
4552
  ];
4232
4553
  }
4233
4554
 
4234
- function relocationDebug(marker: string, detail: Record<string, unknown>): void {
4235
- if (process.env.ZLINK_DEBUG_FRAMEWORK_RELOCATION !== '1') return;
4236
- console.error('[zlink.runtime.relocation]', marker, detail);
4237
- }
4238
-
4239
4555
  class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden> {
4240
- private readonly deferredJoinRoots = new Map<string, ZLinkDeferredJoinAcceptedRoot>();
4241
-
4242
4556
  constructor(
4243
4557
  private readonly options: ZLinkHostRelocationOptions,
4244
4558
  private readonly meshName: string,
@@ -4266,8 +4580,8 @@ class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden>
4266
4580
  participant.stableType,
4267
4581
  participant.objectGeneration,
4268
4582
  participant.authorityOwnerGeneration + 1n,
4269
- (nativeEntrySpot ? localDescriptor.rid : spotIdentity) as RoutingId,
4270
- nativeEntrySpot ? localDescriptor.lifecycleGeneration : membership.spotObjectGeneration,
4583
+ nativeEntrySpot ? undefined : (spotIdentity as RoutingId),
4584
+ membership.spotObjectGeneration,
4271
4585
  membership.membershipEpoch,
4272
4586
  signal
4273
4587
  );
@@ -4407,11 +4721,22 @@ class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden>
4407
4721
  hidden.participant.objectKind === 'user_spot' &&
4408
4722
  registration.options?.relocationCoordinationMode ===
4409
4723
  ZLinkSpotRelocationCoordinationMode.ApplicationSignaled;
4724
+ // createHidden could only predict the owner generation; the native Spot
4725
+ // takes the committed authority, as the Actor branch does above.
4726
+ const node = this.options.meshNode(this.meshName);
4727
+ node?.restoreSpotAuthority?.(
4728
+ String(hidden.activation.spotId),
4729
+ // createHidden stages an activation only for a Spot participant.
4730
+ hidden.participant.objectKind as 'user_spot' | 'instance_spot',
4731
+ hidden.participant.stableType,
4732
+ authority.objectGeneration,
4733
+ authority.authorityOwnerGeneration
4734
+ );
4410
4735
  await this.requireSpotManager().publishRelocationSpot(
4411
4736
  hidden.activation,
4412
4737
  applicationSignaled ? () => hidden.activation!.notifyRelocatedBoundary() : undefined
4413
4738
  );
4414
- this.options.meshNode(this.meshName)?.rememberSpotRoute?.({
4739
+ node?.rememberSpotRoute?.({
4415
4740
  spot: {
4416
4741
  spotId: String(hidden.activation.spotId),
4417
4742
  generation: authority.objectGeneration
@@ -4442,14 +4767,13 @@ class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden>
4442
4767
  }
4443
4768
 
4444
4769
  async restoreBoundSession(hidden: LocalHidden, payload: Uint8Array): Promise<void> {
4445
- if (hidden.actor === undefined || payload.byteLength === 0) return;
4446
- const target = decodeActorSession(payload);
4447
- const state = this.requireActorManager().getState(hidden.actor.context.actorId)!;
4448
- state.setRemoteBoundSessionTarget(target);
4449
- state.setBoundSessionTransferTarget(target);
4450
- if (target.bindingGeneration !== undefined) {
4451
- state.setBoundSessionBindingGeneration(target.bindingGeneration);
4452
- }
4770
+ const carried = decodeActorSession(payload);
4771
+ if (hidden.actor === undefined || carried === undefined) return;
4772
+ // The relocation delivers the binding it carried; the target installs it
4773
+ // through the one install rule (Session–Actor binding §6, §8).
4774
+ this.requireActorManager()
4775
+ .getState(hidden.actor.context.actorId)!
4776
+ .installBoundSessionBinding(carried);
4453
4777
  }
4454
4778
 
4455
4779
  async replayQueuedMessage(
@@ -4464,14 +4788,7 @@ class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden>
4464
4788
  if (recovery.request.actorId !== hidden.actor.context.actorId) {
4465
4789
  throw new Error('Canonical Actor Join recovery names a different staged Actor.');
4466
4790
  }
4467
- const deferred = await this.requireSpotManager().restoreCanonicalActorJoinRecovery(
4468
- recovery,
4469
- undefined,
4470
- inventoryDigest(this.envelope.participants, this.envelope.memberships)
4471
- );
4472
- if (deferred !== undefined) {
4473
- this.deferredJoinRoots.set(hidden.authorityKey, deferred);
4474
- }
4791
+ await this.requireSpotManager().restoreCanonicalActorJoinRecovery(recovery);
4475
4792
  return;
4476
4793
  }
4477
4794
  const packet = decodeQueuedHandoffPacket(message);
@@ -4480,10 +4797,6 @@ class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden>
4480
4797
  hidden.replayPackets.push(packet);
4481
4798
  }
4482
4799
 
4483
- deferredJoinRoot(authorityKey: string): ZLinkDeferredJoinAcceptedRoot | undefined {
4484
- return this.deferredJoinRoots.get(authorityKey);
4485
- }
4486
-
4487
4800
  async openAdmission(hidden: LocalHidden): Promise<void> {
4488
4801
  if (hidden.actor === undefined) return;
4489
4802
  this.requireActorManager().publishRelocationActor(hidden.actor.context.actorId);
@@ -4916,9 +5229,11 @@ export function createServiceRelocationId(
4916
5229
  entropy: (size: number) => Uint8Array = randomBytes
4917
5230
  ): string {
4918
5231
  for (;;) {
4919
- const bytes = entropy(16);
4920
- if (bytes.byteLength !== 16) {
4921
- throw new Error('Relocation identity entropy must return exactly 16 bytes.');
5232
+ const bytes = entropy(OPERATION_IDENTITY_BYTES);
5233
+ if (bytes.byteLength !== OPERATION_IDENTITY_BYTES) {
5234
+ throw new Error(
5235
+ `Relocation identity entropy must return exactly ${OPERATION_IDENTITY_BYTES} bytes.`
5236
+ );
4922
5237
  }
4923
5238
  let nonZero = false;
4924
5239
  for (const byte of bytes) {
@@ -4959,19 +5274,64 @@ function sameCoordinator(
4959
5274
  ): boolean {
4960
5275
  return (
4961
5276
  left.ownerId === right.ownerId &&
4962
- left.leaseGeneration === right.leaseGeneration &&
5277
+ BigInt(left.leaseGeneration) === right.leaseGeneration &&
4963
5278
  left.nodeRid === right.nodeRid &&
4964
- left.nodeGeneration === right.nodeGeneration &&
5279
+ BigInt(left.nodeGeneration) === right.nodeGeneration &&
4965
5280
  left.expectedAuthorityStoreVersion === right.expectedAuthorityStoreVersion
4966
5281
  );
4967
5282
  }
4968
5283
 
5284
+ function sameRelocationTarget(
5285
+ left: ServiceWireRelocationTarget,
5286
+ right: ServiceWireRelocationTarget
5287
+ ): boolean {
5288
+ return (
5289
+ left.nodeRid === right.nodeRid &&
5290
+ BigInt(left.nodeGeneration) === right.nodeGeneration &&
5291
+ left.ownerId === right.ownerId &&
5292
+ BigInt(left.ownerLeaseGeneration) === right.ownerLeaseGeneration
5293
+ );
5294
+ }
5295
+
5296
+ function sameRelocationObject(
5297
+ left: ServiceWireRelocationObject,
5298
+ right: ServiceWireRelocationObject
5299
+ ): boolean {
5300
+ if (left.kind !== right.kind || BigInt(left.objectGeneration) !== right.objectGeneration) {
5301
+ return false;
5302
+ }
5303
+ if (left.kind === 'actor' && right.kind === 'actor') {
5304
+ return (
5305
+ left.actorId === right.actorId &&
5306
+ BigInt(left.expectedAuthorityOwnerGeneration) === right.expectedAuthorityOwnerGeneration
5307
+ );
5308
+ }
5309
+ if (left.kind === 'userSpot' && right.kind === 'userSpot') {
5310
+ return (
5311
+ left.spotId === right.spotId &&
5312
+ BigInt(left.expectedAuthorityOwnerGeneration) === right.expectedAuthorityOwnerGeneration
5313
+ );
5314
+ }
5315
+ return (
5316
+ left.kind === 'instanceSpot' &&
5317
+ right.kind === 'instanceSpot' &&
5318
+ left.spotId === right.spotId &&
5319
+ left.stableType === right.stableType
5320
+ );
5321
+ }
5322
+
4969
5323
  function isExactSourceAuthority(
4970
5324
  current: ZLinkAuthoritySnapshot,
4971
5325
  expected: ZLinkAuthoritySnapshot
4972
5326
  ): boolean {
4973
5327
  return (
4974
- current.storeVersion.value === expected.storeVersion.value &&
5328
+ current.storeVersion.value === expected.storeVersion.value && isSourceOwner(current, expected)
5329
+ );
5330
+ }
5331
+
5332
+ /** The same source tenure owns the row, at any StoreVersion. */
5333
+ function isSourceOwner(current: ZLinkAuthoritySnapshot, expected: ZLinkAuthoritySnapshot): boolean {
5334
+ return (
4975
5335
  current.objectGeneration === expected.objectGeneration &&
4976
5336
  current.authorityOwnerGeneration === expected.authorityOwnerGeneration &&
4977
5337
  current.ownerId === expected.ownerId &&
@@ -5002,36 +5362,6 @@ function isExactCommittedTarget(
5002
5362
  );
5003
5363
  }
5004
5364
 
5005
- async function waitForLocationRetry(
5006
- deadlineAtMs: number,
5007
- signal: AbortSignal | undefined,
5008
- cause: unknown
5009
- ): Promise<void> {
5010
- signal?.throwIfAborted();
5011
- if (performance.now() >= deadlineAtMs) {
5012
- throw new Error(
5013
- 'location_update_failed: relocation Restore validity expired before exact target owner confirmation.',
5014
- { cause }
5015
- );
5016
- }
5017
- await new Promise<void>((resolve, reject) => {
5018
- let timer: ReturnType<typeof setTimeout> | undefined;
5019
- const aborted = () => {
5020
- if (timer !== undefined) clearTimeout(timer);
5021
- reject(signal?.reason);
5022
- };
5023
- signal?.addEventListener('abort', aborted, { once: true });
5024
- timer = setTimeout(
5025
- () => {
5026
- signal?.removeEventListener('abort', aborted);
5027
- resolve();
5028
- },
5029
- Math.min(25, Math.max(1, deadlineAtMs - performance.now()))
5030
- );
5031
- timer.unref();
5032
- });
5033
- }
5034
-
5035
5365
  async function waitForRelocationRetry(delayMs: number, signal?: AbortSignal): Promise<void> {
5036
5366
  signal?.throwIfAborted();
5037
5367
  await new Promise<void>((resolve, reject) => {
@@ -5045,7 +5375,6 @@ async function waitForRelocationRetry(delayMs: number, signal?: AbortSignal): Pr
5045
5375
  signal?.removeEventListener('abort', aborted);
5046
5376
  resolve();
5047
5377
  }, delayMs);
5048
- timer.unref();
5049
5378
  });
5050
5379
  }
5051
5380
 
@@ -5087,14 +5416,16 @@ function maintenanceReplyRelay(
5087
5416
  replyRouteId: BigInt(completion.source.replyRouteId),
5088
5417
  participantId,
5089
5418
  sequence: BigInt(completion.index + 1),
5090
- terminalResult: completion.result.ok ? 0 : 105,
5091
- failureCode: completion.result.ok ? 0 : 17,
5419
+ terminalResult: completion.result.ok ? RequestResult.Ok : RequestResult.InternalError,
5420
+ failureCode: completion.result.ok
5421
+ ? ServiceWireFrameworkErrorCode.none
5422
+ : ServiceWireFrameworkErrorCode.requestFailed,
5092
5423
  ...(value === undefined
5093
5424
  ? {}
5094
5425
  : {
5095
5426
  payload: {
5096
- packetName: 'zlink.relocation.reply',
5097
- contentType: 'application/json',
5427
+ packetName: SERVICE_RELOCATION_REPLY_PACKET_NAME,
5428
+ contentType: ZlinkStreamContentType.Json,
5098
5429
  bytes: Buffer.from(JSON.stringify(value), 'utf8')
5099
5430
  }
5100
5431
  })
@@ -5108,16 +5439,16 @@ function handoffResultFromRelay(relay: ServiceMaintenanceReplyRelay): ZLinkActor
5108
5439
  let value: unknown;
5109
5440
  if (relay.payload !== undefined) {
5110
5441
  if (
5111
- relay.terminalResult !== 0 ||
5112
- relay.payload.packetName !== 'zlink.relocation.reply' ||
5113
- relay.payload.contentType !== 'application/json'
5442
+ relay.terminalResult !== RequestResult.Ok ||
5443
+ relay.payload.packetName !== SERVICE_RELOCATION_REPLY_PACKET_NAME ||
5444
+ relay.payload.contentType !== ZlinkStreamContentType.Json
5114
5445
  ) {
5115
5446
  throw new TypeError('Relocation terminal payload boundary is invalid.');
5116
5447
  }
5117
5448
  value = JSON.parse(Buffer.from(relay.payload.bytes).toString('utf8')) as unknown;
5118
5449
  }
5119
5450
  const index = Number(relay.sequence - 1n);
5120
- return relay.terminalResult === 0
5451
+ return relay.terminalResult === RequestResult.Ok
5121
5452
  ? { index, ok: true, ...(value === undefined ? {} : { response: value }) }
5122
5453
  : { index, ok: false, error: `Actor handoff failed with framework code ${relay.failureCode}.` };
5123
5454
  }
@@ -5133,76 +5464,15 @@ function wireIdText(value: { readonly high: bigint; readonly low: bigint }): str
5133
5464
  */
5134
5465
  export class ServiceRelocationDataLostError extends Error {}
5135
5466
 
5136
- /**
5137
- * Maps a target-side relocation Prepare failure to the closest wire
5138
- * framework-error code the generated schema
5139
- * ({@link ServiceWireFrameworkErrorCode}) defines — mirrors the java
5140
- * reference mapping (commit 97fc074058) and the dotnet/cpp ports. The wire
5141
- * vocabulary predates the framework's typed error kinds and has no
5142
- * one-to-one code for every kind, so several kinds share the nearest fit
5143
- * (documented per case below); unresolvable vocabulary gaps belong at the
5144
- * schema level, not invented here. {@link ServiceRelocationDataLostError}
5145
- * is this runtime's dedicated tag for a verified checksum/assembly/digest
5146
- * integrity failure — the case that keeps relocationDataLost(35). An
5147
- * unclassified error carries no evidence of integrity loss and takes the
5148
- * generic opaque requestFailed(17). `objectKind` (the Prepare's
5149
- * ObjectFence.kind, spec 28 §4.2) picks between an Actor- and
5150
- * Spot-specific code where the schema splits by object kind.
5151
- */
5152
- export function relocationFailedFailureCode(
5153
- error: unknown,
5154
- objectKind: ServiceWireRelocationObject['kind']
5155
- ): number {
5467
+ /** Classifies relocation failure with the error model §2.1 mapping. */
5468
+ export function relocationFailedFailureCode(error: unknown): number {
5156
5469
  if (error instanceof ServiceRelocationDataLostError) {
5157
- return ServiceWireFrameworkErrorCode.relocationDataLost;
5470
+ return frameworkRelocationFailureCode(ZLinkFrameworkErrorKind.DataLost);
5158
5471
  }
5159
5472
  if (!(error instanceof ZLinkFrameworkException)) {
5160
- return ServiceWireFrameworkErrorCode.requestFailed;
5161
- }
5162
- switch (error.kind) {
5163
- case ZLinkFrameworkErrorKind.DataLost:
5164
- return ServiceWireFrameworkErrorCode.relocationDataLost;
5165
- case ZLinkFrameworkErrorKind.Rejected:
5166
- return ServiceWireFrameworkErrorCode.requestRejected;
5167
- case ZLinkFrameworkErrorKind.ProtocolError:
5168
- return ServiceWireFrameworkErrorCode.requestProtocolError;
5169
- // No dedicated "deadline exceeded" wire code exists; a worker timeout
5170
- // is the closest timeout-shaped signal.
5171
- case ZLinkFrameworkErrorKind.DeadlineExceeded:
5172
- return ServiceWireFrameworkErrorCode.workerTimedOut;
5173
- // A stale generation/fence is the concrete cause of InvalidOperation
5174
- // along this path (spec 15 failure table); pick the
5175
- // object-kind-specific stale code.
5176
- case ZLinkFrameworkErrorKind.InvalidOperation:
5177
- return objectKind === 'actor'
5178
- ? ServiceWireFrameworkErrorCode.actorLocationStale
5179
- : ServiceWireFrameworkErrorCode.spotGenerationStale;
5180
- // No dedicated generic "unavailable" wire code exists; a disconnected
5181
- // route is the closest "cannot reach/use the target" signal.
5182
- case ZLinkFrameworkErrorKind.Unavailable:
5183
- return ServiceWireFrameworkErrorCode.routeNotConnected;
5184
- case ZLinkFrameworkErrorKind.NotFound:
5185
- return ServiceWireFrameworkErrorCode.requestTargetNotFound;
5186
- // The only "already exists" wire code is Actor-specific; not expected
5187
- // along this target-failure path, mapped for completeness.
5188
- case ZLinkFrameworkErrorKind.AlreadyExists:
5189
- return ServiceWireFrameworkErrorCode.actorAlreadyExists;
5190
- case ZLinkFrameworkErrorKind.TypeMismatch:
5191
- return objectKind === 'actor'
5192
- ? ServiceWireFrameworkErrorCode.actorTypeMismatch
5193
- : ServiceWireFrameworkErrorCode.spotTypeMismatch;
5194
- // No dedicated "not configured" wire code exists; a missing configured
5195
- // handler is the closest analog.
5196
- case ZLinkFrameworkErrorKind.NotConfigured:
5197
- return ServiceWireFrameworkErrorCode.handlerNotFound;
5198
- // No dedicated generic "internal failure" or "shutting down" wire code
5199
- // exists; the generic opaque request-failure code is the closest fit
5200
- // for both (ShuttingDown included — re-judged 2026-08-19, C-10).
5201
- case ZLinkFrameworkErrorKind.ShuttingDown:
5202
- case ZLinkFrameworkErrorKind.InternalFailure:
5203
- default:
5204
- return ServiceWireFrameworkErrorCode.requestFailed;
5473
+ return frameworkRelocationFailureCode(ZLinkFrameworkErrorKind.InternalFailure);
5205
5474
  }
5475
+ return frameworkRelocationFailureCode(error);
5206
5476
  }
5207
5477
 
5208
5478
  function relocationStagingId(value: {
@@ -5315,7 +5585,7 @@ function validatePrepareEnvelope(
5315
5585
  if (
5316
5586
  !sameWireId(request.relocation, relocationWireId(envelope.aggregateId)) ||
5317
5587
  request.applicationVersion !== envelope.applicationVersion ||
5318
- stringifyWire(request.object) !== stringifyWire(relocationObject(envelope))
5588
+ !sameRelocationObject(request.object, relocationObject(envelope))
5319
5589
  ) {
5320
5590
  throw new Error('Relocation Prepare does not match its immutable root identity.');
5321
5591
  }
@@ -5332,17 +5602,10 @@ function validateTargetOneWayControl(
5332
5602
  !sameWireId(request.relocation, prepare.relocation) ||
5333
5603
  request.targetAttemptGeneration !== prepare.targetAttemptGeneration ||
5334
5604
  !sameCoordinator(request.coordinator, prepare.coordinator) ||
5335
- stringifyWire(request.object) !==
5336
- stringifyWire(request.kind === 'data' ? request.object : prepare.object)
5605
+ (request.kind === 'cutover' && !sameRelocationObject(request.object, prepare.object))
5337
5606
  ) {
5338
5607
  throw new Error('Relocation one-way control changed its exact Prepare fence.');
5339
5608
  }
5340
- if (
5341
- request.kind === 'cutover' &&
5342
- stringifyWire(request.object) !== stringifyWire(prepare.object)
5343
- ) {
5344
- throw new Error('Relocation Cutover changed its prepared object.');
5345
- }
5346
5609
  }
5347
5610
 
5348
5611
  function relocationPublication(
@@ -5447,7 +5710,7 @@ function canonicalQueuedFrozenRecord(
5447
5710
  ...(packet.returnResponse ? { replyRouteId: BigInt(packet.source!.replyRouteId) } : {}),
5448
5711
  payload: {
5449
5712
  packetName: '__zlink.actor.handoff.accepted',
5450
- contentType: 'application/json',
5713
+ contentType: ZlinkStreamContentType.Json,
5451
5714
  bytes: message.payload
5452
5715
  }
5453
5716
  });
@@ -5471,6 +5734,20 @@ function stringifyWire(value: unknown): string {
5471
5734
  return JSON.stringify(value, (_key, item) => (typeof item === 'bigint' ? item.toString() : item));
5472
5735
  }
5473
5736
 
5737
+ function samePrepareIdentity(
5738
+ acceptedFingerprint: string,
5739
+ incoming: ServiceMaintenanceRelocationPrepare
5740
+ ): boolean {
5741
+ const accepted = JSON.parse(acceptedFingerprint) as ServiceMaintenanceRelocationPrepare;
5742
+ return (
5743
+ accepted.sourceNodeRid === incoming.sourceNodeRid &&
5744
+ BigInt(accepted.sourceNodeGeneration) === incoming.sourceNodeGeneration &&
5745
+ sameCoordinator(accepted.coordinator, incoming.coordinator) &&
5746
+ sameRelocationTarget(accepted.target, incoming.target) &&
5747
+ sameRelocationObject(accepted.object, incoming.object)
5748
+ );
5749
+ }
5750
+
5474
5751
  function validateControlResponse(
5475
5752
  request: ZLinkServiceRelocationControlRequest,
5476
5753
  response: ZLinkServiceRelocationControlResponse
@@ -5481,8 +5758,8 @@ function validateControlResponse(
5481
5758
  !sameWireId(response.relocation, request.relocation) ||
5482
5759
  response.targetAttemptGeneration !== request.targetAttemptGeneration ||
5483
5760
  !sameCoordinator(response.coordinator, request.coordinator) ||
5484
- stringifyWire(response.target) !== stringifyWire(request.target) ||
5485
- stringifyWire(response.object) !== stringifyWire(request.object) ||
5761
+ !sameRelocationTarget(response.target, request.target) ||
5762
+ !sameRelocationObject(response.object, request.object) ||
5486
5763
  response.senderRole !== 'target'
5487
5764
  ) {
5488
5765
  throw new Error('Relocation control response does not match the request fence.');
@@ -5505,82 +5782,19 @@ function validateControlFailureResponse(
5505
5782
  !sameWireId(response.relocation, request.relocation) ||
5506
5783
  response.targetAttemptGeneration !== request.targetAttemptGeneration ||
5507
5784
  !sameCoordinator(response.coordinator, request.coordinator) ||
5508
- stringifyWire(response.target) !== stringifyWire(request.target) ||
5509
- stringifyWire(response.object) !== stringifyWire(request.object) ||
5785
+ !sameRelocationTarget(response.target, request.target) ||
5786
+ !sameRelocationObject(response.object, request.object) ||
5510
5787
  response.senderRole !== 'target'
5511
5788
  ) {
5512
5789
  throw new Error('Relocation control response does not match the request fence.');
5513
5790
  }
5514
5791
  }
5515
5792
 
5516
- /**
5517
- * Maps a relocationFailed(53) wire failureCode (the generated
5518
- * ServiceWireFrameworkErrorCode vocabulary) to its internal error kind, so a
5519
- * source-side reject carries the same classification the target chose
5520
- * instead of a generic Error. Direct 1:1 correspondence with the generated
5521
- * names; an unrecognised or absent code falls back to RequestFailed
5522
- * (InternalFailure) — the closest existing kind (spec 15 §"Failed.Kind").
5523
- */
5524
- function relocationFailureCodeKind(failureCode: number): ZLinkFrameworkInternalErrorKind {
5525
- switch (failureCode) {
5526
- case 1:
5527
- return ZLinkFrameworkInternalErrorKind.ActorRouteNotFound;
5528
- case 2:
5529
- return ZLinkFrameworkInternalErrorKind.ActorCreateFailed;
5530
- case 3:
5531
- return ZLinkFrameworkInternalErrorKind.ActorAlreadyExists;
5532
- case 4:
5533
- return ZLinkFrameworkInternalErrorKind.ActorTypeMismatch;
5534
- case 5:
5535
- return ZLinkFrameworkInternalErrorKind.SpotCreateFailed;
5536
- case 6:
5537
- return ZLinkFrameworkInternalErrorKind.SpotRouteNotFound;
5538
- case 7:
5539
- return ZLinkFrameworkInternalErrorKind.SpotTypeMismatch;
5540
- case 8:
5541
- return ZLinkFrameworkInternalErrorKind.ActorSessionNotBound;
5542
- case 9:
5543
- return ZLinkFrameworkInternalErrorKind.HandlerNotFound;
5544
- case 10:
5545
- return ZLinkFrameworkInternalErrorKind.RouteHandlerNotFound;
5546
- case 11:
5547
- return ZLinkFrameworkInternalErrorKind.ActorDispatchHandlerNotFound;
5548
- case 12:
5549
- return ZLinkFrameworkInternalErrorKind.PayloadDecodeFailed;
5550
- case 13:
5551
- return ZLinkFrameworkInternalErrorKind.RouteNotConnected;
5552
- case 14:
5553
- return ZLinkFrameworkInternalErrorKind.RequestTargetNotFound;
5554
- case 15:
5555
- return ZLinkFrameworkInternalErrorKind.RequestRejected;
5556
- case 16:
5557
- return ZLinkFrameworkInternalErrorKind.RequestProtocolError;
5558
- case 18:
5559
- return ZLinkFrameworkInternalErrorKind.WorkerQueueFull;
5560
- case 19:
5561
- return ZLinkFrameworkInternalErrorKind.WorkerTimedOut;
5562
- case 20:
5563
- return ZLinkFrameworkInternalErrorKind.WorkerFailed;
5564
- case 21:
5565
- return ZLinkFrameworkInternalErrorKind.ActorLocationStale;
5566
- case 22:
5567
- return ZLinkFrameworkInternalErrorKind.ActorCreateRejected;
5568
- case 33:
5569
- return ZLinkFrameworkInternalErrorKind.SpotGenerationStale;
5570
- case 34:
5571
- return ZLinkFrameworkInternalErrorKind.SpotMoving;
5572
- case 35:
5573
- return ZLinkFrameworkInternalErrorKind.RelocationDataLost;
5574
- case 17:
5575
- default:
5576
- return ZLinkFrameworkInternalErrorKind.RequestFailed;
5577
- }
5578
- }
5579
-
5580
5793
  /** Builds the typed exception a Prepare waiter rejects with on an explicit Failed(53). */
5581
5794
  function relocationFailureException(response: ServiceMaintenanceRelocationFailed) {
5582
5795
  return createInternalFrameworkException(
5583
- relocationFailureCodeKind(response.failureCode),
5796
+ internalFrameworkErrorKindFromWireFailureCode(response.failureCode) ??
5797
+ ZLinkFrameworkInternalErrorKind.RequestFailed,
5584
5798
  `Relocation '${response.relocation.high}:${response.relocation.low}:` +
5585
5799
  `${response.targetAttemptGeneration}' failed with wire failureCode ${response.failureCode}.`,
5586
5800
  true
@@ -5760,7 +5974,7 @@ function queuedMessageU64(value: unknown, field: string): bigint {
5760
5974
  throw new TypeError(`Relocation Actor Message Follow ${field} is invalid.`);
5761
5975
  }
5762
5976
  const result = BigInt(value);
5763
- if (result > 0xffff_ffff_ffff_ffffn) {
5977
+ if (result > UINT64_MAX) {
5764
5978
  throw new TypeError(`Relocation Actor Message Follow ${field} is outside u64.`);
5765
5979
  }
5766
5980
  return result;
@@ -5784,6 +5998,15 @@ function toServiceTimer(
5784
5998
  };
5785
5999
  }
5786
6000
 
6001
+ const PREPARE_SIDEBAND_MAGIC = 'ZLNI';
6002
+ const PREPARE_SIDEBAND_VERSION = 1;
6003
+ const PREPARE_SIDEBAND_HEADER_BYTES = 11;
6004
+ const PREPARE_SIDEBAND_VERSION_OFFSET = 4;
6005
+ const PREPARE_SIDEBAND_KEY_LENGTH_OFFSET = 5;
6006
+ const PREPARE_SIDEBAND_PAYLOAD_LENGTH_OFFSET = 7;
6007
+ const ACTOR_SESSION_JOURNAL_VERSION = 1;
6008
+ const SERVICE_RELOCATION_REPLY_PACKET_NAME = 'zlink.relocation.reply';
6009
+
5787
6010
  /**
5788
6011
  * NODE-INTERNAL Prepare sideband. Frame zero remains the exact canonical
5789
6012
  * command-40 schema record; each following ZLNI frame carries one sealed
@@ -5799,11 +6022,11 @@ function encodePrepareSideband(envelope: ServiceRelocationEnvelope): readonly Bu
5799
6022
  if (key.byteLength === 0 || key.byteLength > 0xffff || payload.byteLength > 0xffff_ffff) {
5800
6023
  throw new TypeError('Bound-session relocation sideband is out of bounds.');
5801
6024
  }
5802
- const header = Buffer.allocUnsafe(11);
5803
- header.write('ZLNI', 0, 'ascii');
5804
- header[4] = 1;
5805
- header.writeUInt16BE(key.byteLength, 5);
5806
- header.writeUInt32BE(payload.byteLength, 7);
6025
+ const header = Buffer.allocUnsafe(PREPARE_SIDEBAND_HEADER_BYTES);
6026
+ header.write(PREPARE_SIDEBAND_MAGIC, 0, 'ascii');
6027
+ header[PREPARE_SIDEBAND_VERSION_OFFSET] = PREPARE_SIDEBAND_VERSION;
6028
+ header.writeUInt16BE(key.byteLength, PREPARE_SIDEBAND_KEY_LENGTH_OFFSET);
6029
+ header.writeUInt32BE(payload.byteLength, PREPARE_SIDEBAND_PAYLOAD_LENGTH_OFFSET);
5807
6030
  return Buffer.concat([header, key, payload]);
5808
6031
  });
5809
6032
  }
@@ -5819,19 +6042,29 @@ function decodePrepareSideband(frames: readonly Uint8Array[]):
5819
6042
  for (const frame of frames) {
5820
6043
  const bytes = Buffer.from(frame);
5821
6044
  if (
5822
- bytes.byteLength < 11 ||
5823
- bytes.subarray(0, 4).toString('ascii') !== 'ZLNI' ||
5824
- bytes[4] !== 1
6045
+ bytes.byteLength < PREPARE_SIDEBAND_HEADER_BYTES ||
6046
+ bytes.subarray(0, PREPARE_SIDEBAND_VERSION_OFFSET).toString('ascii') !==
6047
+ PREPARE_SIDEBAND_MAGIC ||
6048
+ bytes[PREPARE_SIDEBAND_VERSION_OFFSET] !== PREPARE_SIDEBAND_VERSION
5825
6049
  ) {
5826
6050
  return undefined;
5827
6051
  }
5828
- const keyLength = bytes.readUInt16BE(5);
5829
- const payloadLength = bytes.readUInt32BE(7);
5830
- if (11 + keyLength + payloadLength !== bytes.byteLength || keyLength === 0) return undefined;
5831
- const participantKey = bytes.subarray(11, 11 + keyLength).toString('utf8');
6052
+ const keyLength = bytes.readUInt16BE(PREPARE_SIDEBAND_KEY_LENGTH_OFFSET);
6053
+ const payloadLength = bytes.readUInt32BE(PREPARE_SIDEBAND_PAYLOAD_LENGTH_OFFSET);
6054
+ if (
6055
+ PREPARE_SIDEBAND_HEADER_BYTES + keyLength + payloadLength !== bytes.byteLength ||
6056
+ keyLength === 0
6057
+ )
6058
+ return undefined;
6059
+ const participantKey = bytes
6060
+ .subarray(PREPARE_SIDEBAND_HEADER_BYTES, PREPARE_SIDEBAND_HEADER_BYTES + keyLength)
6061
+ .toString('utf8');
5832
6062
  if (keys.has(participantKey)) return undefined;
5833
6063
  keys.add(participantKey);
5834
- result.push({ participantKey, payload: Buffer.from(bytes.subarray(11 + keyLength)) });
6064
+ result.push({
6065
+ participantKey,
6066
+ payload: Buffer.from(bytes.subarray(PREPARE_SIDEBAND_HEADER_BYTES + keyLength))
6067
+ });
5835
6068
  }
5836
6069
  return result;
5837
6070
  }
@@ -5862,13 +6095,16 @@ function encodeActorSession(target: ZLinkRemoteBoundSessionTarget | undefined):
5862
6095
  if (target === undefined) return Buffer.alloc(0);
5863
6096
  return Buffer.from(
5864
6097
  JSON.stringify({
5865
- version: 1,
6098
+ version: ACTOR_SESSION_JOURNAL_VERSION,
5866
6099
  routerChannelId: target.routerChannelId,
5867
6100
  targetNodeRid: String(target.targetNodeRid),
5868
6101
  spotId: String(target.spotId),
5869
6102
  sessionNodeRid:
5870
6103
  target.sessionNodeRid === undefined ? undefined : String(target.sessionNodeRid),
5871
6104
  sessionRid: target.sessionRid === undefined ? undefined : String(target.sessionRid),
6105
+ sessionOwnerNodeGeneration: target.sessionOwnerNodeGeneration?.toString(),
6106
+ sessionOwnerId: target.sessionOwnerId,
6107
+ sessionOwnerLeaseGeneration: target.sessionOwnerLeaseGeneration?.toString(),
5872
6108
  bindingGeneration: target.bindingGeneration?.toString(),
5873
6109
  previousAuthorityOwnerGeneration: target.previousAuthorityOwnerGeneration?.toString(),
5874
6110
  previousOwnerLeaseGeneration: target.previousOwnerLeaseGeneration?.toString(),
@@ -5901,12 +6137,13 @@ function encodeActorSession(target: ZLinkRemoteBoundSessionTarget | undefined):
5901
6137
  );
5902
6138
  }
5903
6139
 
5904
- function decodeActorSession(payload: Uint8Array): ZLinkRemoteBoundSessionTarget {
6140
+ function decodeActorSession(payload: Uint8Array): ZLinkRemoteBoundSessionTarget | undefined {
6141
+ if (payload.byteLength === 0) return undefined;
5905
6142
  const value = JSON.parse(Buffer.from(payload).toString('utf8')) as Record<string, unknown>;
5906
6143
  const optionalBigInt = (field: string) =>
5907
6144
  typeof value[field] === 'string' ? BigInt(value[field] as string) : undefined;
5908
6145
  if (
5909
- value.version !== 1 ||
6146
+ value.version !== ACTOR_SESSION_JOURNAL_VERSION ||
5910
6147
  typeof value.routerChannelId !== 'string' ||
5911
6148
  typeof value.targetNodeRid !== 'string' ||
5912
6149
  typeof value.spotId !== 'string'
@@ -5922,6 +6159,13 @@ function decodeActorSession(payload: Uint8Array): ZLinkRemoteBoundSessionTarget
5922
6159
  ? { sessionNodeRid: value.sessionNodeRid as RoutingId }
5923
6160
  : {}),
5924
6161
  ...(typeof value.sessionRid === 'string' ? { sessionRid: value.sessionRid as RoutingId } : {}),
6162
+ ...(optionalBigInt('sessionOwnerNodeGeneration') === undefined
6163
+ ? {}
6164
+ : { sessionOwnerNodeGeneration: optionalBigInt('sessionOwnerNodeGeneration') }),
6165
+ ...(typeof value.sessionOwnerId === 'string' ? { sessionOwnerId: value.sessionOwnerId } : {}),
6166
+ ...(optionalBigInt('sessionOwnerLeaseGeneration') === undefined
6167
+ ? {}
6168
+ : { sessionOwnerLeaseGeneration: optionalBigInt('sessionOwnerLeaseGeneration') }),
5925
6169
  ...(optionalBigInt('bindingGeneration') === undefined
5926
6170
  ? {}
5927
6171
  : { bindingGeneration: optionalBigInt('bindingGeneration') }),
@@ -6034,3 +6278,16 @@ function addCapacity(left: ZLinkCapacityVector, right: ZLinkCapacityVector): ZLi
6034
6278
  })
6035
6279
  };
6036
6280
  }
6281
+
6282
+ if (isStructuralGuardEnabled()) {
6283
+ const relocateActorJoin = ZLinkHostServiceRelocationRuntime.prototype.relocateActorJoin;
6284
+ ZLinkHostServiceRelocationRuntime.prototype.relocateActorJoin = function (
6285
+ this: ZLinkHostServiceRelocationRuntime,
6286
+ input: Parameters<ZLinkActorJoinRelocation['relocateActorJoin']>[0]
6287
+ ): ReturnType<ZLinkActorJoinRelocation['relocateActorJoin']> {
6288
+ return trackDiagnosticCompletion(
6289
+ relocateActorJoin.call(this, input),
6290
+ `relocation join actor:${input.state.actorId}`
6291
+ );
6292
+ };
6293
+ }