@zlink-systems/framework 0.10.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.
- package/LICENSE +105 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/contracts/Actors/ZLinkActor.d.ts +7 -0
- package/dist/contracts/Actors/ZLinkActor.js +2 -0
- package/dist/contracts/Actors/ZLinkActorClient.d.ts +15 -0
- package/dist/contracts/Actors/ZLinkActorClient.js +2 -0
- package/dist/contracts/Actors/ZLinkActorContext.d.ts +14 -0
- package/dist/contracts/Actors/ZLinkActorContext.js +2 -0
- package/dist/contracts/Actors/ZLinkActorFactory.d.ts +34 -0
- package/dist/contracts/Actors/ZLinkActorFactory.js +2 -0
- package/dist/contracts/Actors/ZLinkActorManager.d.ts +34 -0
- package/dist/contracts/Actors/ZLinkActorManager.js +2 -0
- package/dist/contracts/Actors/index.d.ts +5 -0
- package/dist/contracts/Actors/index.js +21 -0
- package/dist/contracts/Channels/Calls.d.ts +28 -0
- package/dist/contracts/Channels/Calls.js +2 -0
- package/dist/contracts/Channels/IZLinkChannelClient.d.ts +5 -0
- package/dist/contracts/Channels/IZLinkChannelClient.js +2 -0
- package/dist/contracts/Channels/IZLinkFanoutClient.d.ts +11 -0
- package/dist/contracts/Channels/IZLinkFanoutClient.js +2 -0
- package/dist/contracts/Channels/RouteCalls.d.ts +11 -0
- package/dist/contracts/Channels/RouteCalls.js +2 -0
- package/dist/contracts/Channels/index.d.ts +4 -0
- package/dist/contracts/Channels/index.js +20 -0
- package/dist/contracts/Codecs/IZLinkCodecRegistryBuilder.d.ts +14 -0
- package/dist/contracts/Codecs/IZLinkCodecRegistryBuilder.js +2 -0
- package/dist/contracts/Codecs/ZLinkMessageSerializer.d.ts +5 -0
- package/dist/contracts/Codecs/ZLinkMessageSerializer.js +2 -0
- package/dist/contracts/Codecs/index.d.ts +2 -0
- package/dist/contracts/Codecs/index.js +18 -0
- package/dist/contracts/Common/ActorRef.d.ts +7 -0
- package/dist/contracts/Common/ActorRef.js +2 -0
- package/dist/contracts/Common/CoreTypes.d.ts +6 -0
- package/dist/contracts/Common/CoreTypes.js +2 -0
- package/dist/contracts/Common/Message.d.ts +9 -0
- package/dist/contracts/Common/Message.js +2 -0
- package/dist/contracts/Common/ZLinkEncodedPayload.d.ts +12 -0
- package/dist/contracts/Common/ZLinkEncodedPayload.js +41 -0
- package/dist/contracts/Common/ZLinkMessage.d.ts +25 -0
- package/dist/contracts/Common/ZLinkMessage.js +127 -0
- package/dist/contracts/Common/ZLinkMessageMetadata.d.ts +10 -0
- package/dist/contracts/Common/ZLinkMessageMetadata.js +49 -0
- package/dist/contracts/Common/ZLinkProviderResolver.d.ts +5 -0
- package/dist/contracts/Common/ZLinkProviderResolver.js +2 -0
- package/dist/contracts/Common/encoded-payload-storage.d.ts +3 -0
- package/dist/contracts/Common/encoded-payload-storage.js +18 -0
- package/dist/contracts/Common/index.d.ts +5 -0
- package/dist/contracts/Common/index.js +24 -0
- package/dist/contracts/Configuration/Builders.d.ts +153 -0
- package/dist/contracts/Configuration/Builders.js +2 -0
- package/dist/contracts/Configuration/CodecContentType.d.ts +2 -0
- package/dist/contracts/Configuration/CodecContentType.js +68 -0
- package/dist/contracts/Configuration/CodecSerializerSelection.d.ts +9 -0
- package/dist/contracts/Configuration/CodecSerializerSelection.js +15 -0
- package/dist/contracts/Configuration/Configs.d.ts +23 -0
- package/dist/contracts/Configuration/Configs.js +2 -0
- package/dist/contracts/Configuration/ConfigurationException.d.ts +3 -0
- package/dist/contracts/Configuration/ConfigurationException.js +10 -0
- package/dist/contracts/Configuration/Connections.d.ts +5 -0
- package/dist/contracts/Configuration/Connections.js +2 -0
- package/dist/contracts/Configuration/DiagnosticsValidation.d.ts +3 -0
- package/dist/contracts/Configuration/DiagnosticsValidation.js +23 -0
- package/dist/contracts/Configuration/EndpointNotation.d.ts +77 -0
- package/dist/contracts/Configuration/EndpointNotation.js +218 -0
- package/dist/contracts/Configuration/InternalDefaults.d.ts +7 -0
- package/dist/contracts/Configuration/InternalDefaults.js +47 -0
- package/dist/contracts/Configuration/ObjectRoles.d.ts +38 -0
- package/dist/contracts/Configuration/ObjectRoles.js +13 -0
- package/dist/contracts/Configuration/Registration.d.ts +11 -0
- package/dist/contracts/Configuration/Registration.js +162 -0
- package/dist/contracts/Configuration/RegistrationBuilderPolicy.d.ts +14 -0
- package/dist/contracts/Configuration/RegistrationBuilderPolicy.js +61 -0
- package/dist/contracts/Configuration/RegistrationBuilders.d.ts +61 -0
- package/dist/contracts/Configuration/RegistrationBuilders.js +1181 -0
- package/dist/contracts/Configuration/RegistrationCodecRegistry.d.ts +19 -0
- package/dist/contracts/Configuration/RegistrationCodecRegistry.js +79 -0
- package/dist/contracts/Configuration/RegistrationNormalizers.d.ts +23 -0
- package/dist/contracts/Configuration/RegistrationNormalizers.js +319 -0
- package/dist/contracts/Configuration/RegistrationTypes.d.ts +414 -0
- package/dist/contracts/Configuration/RegistrationTypes.js +2 -0
- package/dist/contracts/Configuration/RegistrationValidators.d.ts +2 -0
- package/dist/contracts/Configuration/RegistrationValidators.js +461 -0
- package/dist/contracts/Configuration/RouteChannelInternalState.d.ts +10 -0
- package/dist/contracts/Configuration/RouteChannelInternalState.js +39 -0
- package/dist/contracts/Configuration/RuntimeEndpointConnections.d.ts +9 -0
- package/dist/contracts/Configuration/RuntimeEndpointConnections.js +80 -0
- package/dist/contracts/Configuration/SendTimeoutValidation.d.ts +2 -0
- package/dist/contracts/Configuration/SendTimeoutValidation.js +16 -0
- package/dist/contracts/Configuration/TimerRegistrationValidator.d.ts +2 -0
- package/dist/contracts/Configuration/TimerRegistrationValidator.js +24 -0
- package/dist/contracts/Configuration/index.d.ts +5 -0
- package/dist/contracts/Configuration/index.js +18 -0
- package/dist/contracts/Dispatch/ZLinkDispatchOptions.d.ts +181 -0
- package/dist/contracts/Dispatch/ZLinkDispatchOptions.js +89 -0
- package/dist/contracts/Dispatch/ZLinkFlowOrigin.d.ts +1 -0
- package/dist/contracts/Dispatch/ZLinkFlowOrigin.js +2 -0
- package/dist/contracts/Dispatch/index.d.ts +2 -0
- package/dist/contracts/Dispatch/index.js +18 -0
- package/dist/contracts/Errors/ZLinkFrameworkException.d.ts +20 -0
- package/dist/contracts/Errors/ZLinkFrameworkException.js +29 -0
- package/dist/contracts/Errors/index.d.ts +1 -0
- package/dist/contracts/Errors/index.js +6 -0
- package/dist/contracts/Eventing/Metrics.d.ts +29 -0
- package/dist/contracts/Eventing/Metrics.js +4 -0
- package/dist/contracts/Eventing/index.d.ts +1 -0
- package/dist/contracts/Eventing/index.js +17 -0
- package/dist/contracts/Handlers/Attributes.d.ts +24 -0
- package/dist/contracts/Handlers/Attributes.js +84 -0
- package/dist/contracts/Handlers/Contexts.d.ts +18 -0
- package/dist/contracts/Handlers/Contexts.js +2 -0
- package/dist/contracts/Handlers/IZLinkChannelHandlers.d.ts +43 -0
- package/dist/contracts/Handlers/IZLinkChannelHandlers.js +2 -0
- package/dist/contracts/Handlers/IZLinkHandlerFilter.d.ts +15 -0
- package/dist/contracts/Handlers/IZLinkHandlerFilter.js +11 -0
- package/dist/contracts/Handlers/JsonContract.d.ts +46 -0
- package/dist/contracts/Handlers/JsonContract.js +137 -0
- package/dist/contracts/Handlers/ZLinkHandlerFilterNext.d.ts +1 -0
- package/dist/contracts/Handlers/ZLinkHandlerFilterNext.js +2 -0
- package/dist/contracts/Handlers/index.d.ts +5 -0
- package/dist/contracts/Handlers/index.js +33 -0
- package/dist/contracts/Locations/ActorTransfer.d.ts +41 -0
- package/dist/contracts/Locations/ActorTransfer.js +2 -0
- package/dist/contracts/Locations/Authority.d.ts +296 -0
- package/dist/contracts/Locations/Authority.js +17 -0
- package/dist/contracts/Locations/Diagnostics.d.ts +42 -0
- package/dist/contracts/Locations/Diagnostics.js +12 -0
- package/dist/contracts/Locations/Keys.d.ts +86 -0
- package/dist/contracts/Locations/Keys.js +2 -0
- package/dist/contracts/Locations/Models.d.ts +7 -0
- package/dist/contracts/Locations/Models.js +23 -0
- package/dist/contracts/Locations/Options.d.ts +23 -0
- package/dist/contracts/Locations/Options.js +20 -0
- package/dist/contracts/Locations/Readiness.d.ts +5 -0
- package/dist/contracts/Locations/Readiness.js +2 -0
- package/dist/contracts/Locations/RelocationStore.d.ts +38 -0
- package/dist/contracts/Locations/RelocationStore.js +2 -0
- package/dist/contracts/Locations/Resolvers.d.ts +4 -0
- package/dist/contracts/Locations/Resolvers.js +2 -0
- package/dist/contracts/Locations/Rows.d.ts +152 -0
- package/dist/contracts/Locations/Rows.js +19 -0
- package/dist/contracts/Locations/RuntimeQuery.d.ts +25 -0
- package/dist/contracts/Locations/RuntimeQuery.js +2 -0
- package/dist/contracts/Locations/Stores.d.ts +88 -0
- package/dist/contracts/Locations/Stores.js +2 -0
- package/dist/contracts/Locations/Values.d.ts +27 -0
- package/dist/contracts/Locations/Values.js +36 -0
- package/dist/contracts/Locations/Watch.d.ts +14 -0
- package/dist/contracts/Locations/Watch.js +12 -0
- package/dist/contracts/Locations/Writes.d.ts +48 -0
- package/dist/contracts/Locations/Writes.js +15 -0
- package/dist/contracts/Locations/index.d.ts +12 -0
- package/dist/contracts/Locations/index.js +30 -0
- package/dist/contracts/RouteMesh/Contracts.d.ts +102 -0
- package/dist/contracts/RouteMesh/Contracts.js +39 -0
- package/dist/contracts/RouteMesh/RuntimeTopology.d.ts +165 -0
- package/dist/contracts/RouteMesh/RuntimeTopology.js +38 -0
- package/dist/contracts/RouteMesh/index.d.ts +2 -0
- package/dist/contracts/RouteMesh/index.js +18 -0
- package/dist/contracts/Spots/Builders.d.ts +10 -0
- package/dist/contracts/Spots/Builders.js +2 -0
- package/dist/contracts/Spots/Contracts.d.ts +110 -0
- package/dist/contracts/Spots/Contracts.js +9 -0
- package/dist/contracts/Spots/SpotKind.d.ts +8 -0
- package/dist/contracts/Spots/SpotKind.js +28 -0
- package/dist/contracts/Spots/ZLinkSpot.d.ts +62 -0
- package/dist/contracts/Spots/ZLinkSpot.js +15 -0
- package/dist/contracts/Spots/index.d.ts +3 -0
- package/dist/contracts/Spots/index.js +19 -0
- package/dist/contracts/Streams/BoundSessionContracts.d.ts +8 -0
- package/dist/contracts/Streams/BoundSessionContracts.js +2 -0
- package/dist/contracts/Streams/IZLinkSession.d.ts +44 -0
- package/dist/contracts/Streams/IZLinkSession.js +2 -0
- package/dist/contracts/Streams/IZLinkSessionActor.d.ts +15 -0
- package/dist/contracts/Streams/IZLinkSessionActor.js +2 -0
- package/dist/contracts/Streams/IZLinkSessionPacketHandler.d.ts +9 -0
- package/dist/contracts/Streams/IZLinkSessionPacketHandler.js +2 -0
- package/dist/contracts/Streams/IZLinkStream.d.ts +9 -0
- package/dist/contracts/Streams/IZLinkStream.js +2 -0
- package/dist/contracts/Streams/ZLinkMessageMetadata.d.ts +1 -0
- package/dist/contracts/Streams/ZLinkMessageMetadata.js +2 -0
- package/dist/contracts/Streams/ZLinkStreamCompression.d.ts +8 -0
- package/dist/contracts/Streams/ZLinkStreamCompression.js +2 -0
- package/dist/contracts/Streams/ZLinkStreamError.d.ts +8 -0
- package/dist/contracts/Streams/ZLinkStreamError.js +8 -0
- package/dist/contracts/Streams/index.d.ts +8 -0
- package/dist/contracts/Streams/index.js +24 -0
- package/dist/contracts/Timers/IZLinkTimer.d.ts +5 -0
- package/dist/contracts/Timers/IZLinkTimer.js +2 -0
- package/dist/contracts/Timers/ZLinkTimerOptions.d.ts +10 -0
- package/dist/contracts/Timers/ZLinkTimerOptions.js +9 -0
- package/dist/contracts/Timers/ZLinkTimerTick.d.ts +12 -0
- package/dist/contracts/Timers/ZLinkTimerTick.js +2 -0
- package/dist/contracts/Timers/index.d.ts +3 -0
- package/dist/contracts/Timers/index.js +19 -0
- package/dist/contracts/index.d.ts +25 -0
- package/dist/contracts/index.js +45 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -0
- package/dist/internal.d.ts +26 -0
- package/dist/internal.js +48 -0
- package/dist/location-store-integration.d.ts +5 -0
- package/dist/location-store-integration.js +31 -0
- package/dist/nest-integration.d.ts +49 -0
- package/dist/nest-integration.js +143 -0
- package/dist/runtime/abort.d.ts +13 -0
- package/dist/runtime/abort.js +71 -0
- package/dist/runtime/actors/actor-authority-payload-codec.d.ts +33 -0
- package/dist/runtime/actors/actor-authority-payload-codec.js +366 -0
- package/dist/runtime/actors/actor-authority-publication.d.ts +28 -0
- package/dist/runtime/actors/actor-authority-publication.js +207 -0
- package/dist/runtime/actors/actor-client.d.ts +36 -0
- package/dist/runtime/actors/actor-client.js +612 -0
- package/dist/runtime/actors/actor-context.d.ts +26 -0
- package/dist/runtime/actors/actor-context.js +433 -0
- package/dist/runtime/actors/actor-creation.d.ts +19 -0
- package/dist/runtime/actors/actor-creation.js +158 -0
- package/dist/runtime/actors/actor-execution-context.d.ts +6 -0
- package/dist/runtime/actors/actor-execution-context.js +27 -0
- package/dist/runtime/actors/actor-handoff.d.ts +191 -0
- package/dist/runtime/actors/actor-handoff.js +1252 -0
- package/dist/runtime/actors/actor-join-deferred-scope.d.ts +11 -0
- package/dist/runtime/actors/actor-join-deferred-scope.js +144 -0
- package/dist/runtime/actors/actor-join-relocation.d.ts +38 -0
- package/dist/runtime/actors/actor-join-relocation.js +2 -0
- package/dist/runtime/actors/actor-lifecycle-snapshot.d.ts +21 -0
- package/dist/runtime/actors/actor-lifecycle-snapshot.js +58 -0
- package/dist/runtime/actors/actor-local-native-join.d.ts +27 -0
- package/dist/runtime/actors/actor-local-native-join.js +409 -0
- package/dist/runtime/actors/actor-mailbox.d.ts +10 -0
- package/dist/runtime/actors/actor-mailbox.js +53 -0
- package/dist/runtime/actors/actor-message-follow-context.d.ts +46 -0
- package/dist/runtime/actors/actor-message-follow-context.js +257 -0
- package/dist/runtime/actors/actor-native-lookup.d.ts +4 -0
- package/dist/runtime/actors/actor-native-lookup.js +30 -0
- package/dist/runtime/actors/actor-packet-relay-wire.d.ts +73 -0
- package/dist/runtime/actors/actor-packet-relay-wire.js +144 -0
- package/dist/runtime/actors/actor-remote-joiner.d.ts +37 -0
- package/dist/runtime/actors/actor-remote-joiner.js +122 -0
- package/dist/runtime/actors/actor-remote-wire.d.ts +73 -0
- package/dist/runtime/actors/actor-remote-wire.js +84 -0
- package/dist/runtime/actors/actor-retry-delay.d.ts +5 -0
- package/dist/runtime/actors/actor-retry-delay.js +40 -0
- package/dist/runtime/actors/actor-routed-join-transport.d.ts +33 -0
- package/dist/runtime/actors/actor-routed-join-transport.js +2 -0
- package/dist/runtime/actors/actor-routed-json-request.d.ts +11 -0
- package/dist/runtime/actors/actor-routed-json-request.js +39 -0
- package/dist/runtime/actors/actor-runtime-contracts.d.ts +63 -0
- package/dist/runtime/actors/actor-runtime-contracts.js +2 -0
- package/dist/runtime/actors/actor-runtime-state.d.ts +152 -0
- package/dist/runtime/actors/actor-runtime-state.js +545 -0
- package/dist/runtime/actors/actor-source-transfer.d.ts +27 -0
- package/dist/runtime/actors/actor-source-transfer.js +2 -0
- package/dist/runtime/actors/actor-transfer-registry.d.ts +18 -0
- package/dist/runtime/actors/actor-transfer-registry.js +71 -0
- package/dist/runtime/actors/actor-transport-delivery-gate.d.ts +6 -0
- package/dist/runtime/actors/actor-transport-delivery-gate.js +4 -0
- package/dist/runtime/actors/bound-session-wire.d.ts +81 -0
- package/dist/runtime/actors/bound-session-wire.js +184 -0
- package/dist/runtime/actors/deferred-join-accepted-journal.d.ts +47 -0
- package/dist/runtime/actors/deferred-join-accepted-journal.js +962 -0
- package/dist/runtime/actors/index.d.ts +79 -0
- package/dist/runtime/actors/index.js +639 -0
- package/dist/runtime/actors/post-commit-actor-binder.d.ts +18 -0
- package/dist/runtime/actors/post-commit-actor-binder.js +68 -0
- package/dist/runtime/actors/post-commit-actor-location.d.ts +19 -0
- package/dist/runtime/actors/post-commit-actor-location.js +72 -0
- package/dist/runtime/actors/spot-actor-dispatch.d.ts +59 -0
- package/dist/runtime/actors/spot-actor-dispatch.js +179 -0
- package/dist/runtime/actors/transferred-actor-rollback.d.ts +16 -0
- package/dist/runtime/actors/transferred-actor-rollback.js +91 -0
- package/dist/runtime/admission.d.ts +20 -0
- package/dist/runtime/admission.js +166 -0
- package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +57 -0
- package/dist/runtime/application-jobs/application-ingress-record-owner.js +185 -0
- package/dist/runtime/application-jobs/application-job-queue-handler-start-gate.d.ts +8 -0
- package/dist/runtime/application-jobs/application-job-queue-handler-start-gate.js +8 -0
- package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +17 -0
- package/dist/runtime/application-jobs/application-job-queue-scope.js +57 -0
- package/dist/runtime/application-jobs/contracts.d.ts +19 -0
- package/dist/runtime/application-jobs/contracts.js +2 -0
- package/dist/runtime/application-jobs/receive-flow-controller.d.ts +32 -0
- package/dist/runtime/application-jobs/receive-flow-controller.js +165 -0
- package/dist/runtime/backend/contracts/index.d.ts +487 -0
- package/dist/runtime/backend/contracts/index.js +29 -0
- package/dist/runtime/backend/index.d.ts +9 -0
- package/dist/runtime/backend/index.js +25 -0
- package/dist/runtime/backend/mesh-actor-session-node-adapter.d.ts +3 -0
- package/dist/runtime/backend/mesh-actor-session-node-adapter.js +51 -0
- package/dist/runtime/backend/mesh-completion-table.d.ts +34 -0
- package/dist/runtime/backend/mesh-completion-table.js +124 -0
- package/dist/runtime/backend/mesh-dispatch-pump.d.ts +48 -0
- package/dist/runtime/backend/mesh-dispatch-pump.js +322 -0
- package/dist/runtime/backend/mesh-record-dispatcher.d.ts +19 -0
- package/dist/runtime/backend/mesh-record-dispatcher.js +53 -0
- package/dist/runtime/backend/node/index.d.ts +4 -0
- package/dist/runtime/backend/node/index.js +22 -0
- package/dist/runtime/backend/node/node-backend-adapter-factory.d.ts +12 -0
- package/dist/runtime/backend/node/node-backend-adapter-factory.js +161 -0
- package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +52 -0
- package/dist/runtime/backend/node/node-backend-adapter-support.js +203 -0
- package/dist/runtime/backend/node/node-dealer-channel-client-transport.d.ts +13 -0
- package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +52 -0
- package/dist/runtime/backend/node/node-mesh-backend-adapter.d.ts +11 -0
- package/dist/runtime/backend/node/node-mesh-backend-adapter.js +23 -0
- package/dist/runtime/backend/node/node-monitor-backend-adapter.d.ts +6 -0
- package/dist/runtime/backend/node/node-monitor-backend-adapter.js +38 -0
- package/dist/runtime/backend/node/node-raw-binding-port.d.ts +8 -0
- package/dist/runtime/backend/node/node-raw-binding-port.js +411 -0
- package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +266 -0
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +1707 -0
- package/dist/runtime/backend/node/node-socket-backend-adapter.d.ts +9 -0
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +286 -0
- package/dist/runtime/backend/node-backend-adapter.d.ts +9 -0
- package/dist/runtime/backend/node-backend-adapter.js +12 -0
- package/dist/runtime/backend/raw-binding-port.d.ts +58 -0
- package/dist/runtime/backend/raw-binding-port.js +2 -0
- package/dist/runtime/backend/runtime-message.d.ts +14 -0
- package/dist/runtime/backend/runtime-message.js +44 -0
- package/dist/runtime/backend/runtime-values.d.ts +78 -0
- package/dist/runtime/backend/runtime-values.js +66 -0
- package/dist/runtime/channels/channel-autoconnect.d.ts +14 -0
- package/dist/runtime/channels/channel-autoconnect.js +23 -0
- package/dist/runtime/channels/channel-clients.d.ts +50 -0
- package/dist/runtime/channels/channel-clients.js +329 -0
- package/dist/runtime/channels/channel-dispatch-pipeline.d.ts +76 -0
- package/dist/runtime/channels/channel-dispatch-pipeline.js +237 -0
- package/dist/runtime/channels/channel-dispatch-services.d.ts +52 -0
- package/dist/runtime/channels/channel-dispatch-services.js +132 -0
- package/dist/runtime/channels/channel-dispatchers.d.ts +125 -0
- package/dist/runtime/channels/channel-dispatchers.js +575 -0
- package/dist/runtime/channels/channel-envelope-inspection.d.ts +25 -0
- package/dist/runtime/channels/channel-envelope-inspection.js +96 -0
- package/dist/runtime/channels/channel-envelope.d.ts +73 -0
- package/dist/runtime/channels/channel-envelope.js +546 -0
- package/dist/runtime/channels/channel-framework-packets.d.ts +2 -0
- package/dist/runtime/channels/channel-framework-packets.js +6 -0
- package/dist/runtime/channels/channel-multipart.d.ts +16 -0
- package/dist/runtime/channels/channel-multipart.js +31 -0
- package/dist/runtime/channels/channel-outbound-operations.d.ts +33 -0
- package/dist/runtime/channels/channel-outbound-operations.js +343 -0
- package/dist/runtime/channels/channel-receive-loops.d.ts +151 -0
- package/dist/runtime/channels/channel-receive-loops.js +728 -0
- package/dist/runtime/channels/channel-receive-task-tracker.d.ts +14 -0
- package/dist/runtime/channels/channel-receive-task-tracker.js +37 -0
- package/dist/runtime/channels/channel-runtime-lifecycle.d.ts +77 -0
- package/dist/runtime/channels/channel-runtime-lifecycle.js +630 -0
- package/dist/runtime/channels/channel-runtime-manager.d.ts +84 -0
- package/dist/runtime/channels/channel-runtime-manager.js +193 -0
- package/dist/runtime/channels/channel-socket-options.d.ts +18 -0
- package/dist/runtime/channels/channel-socket-options.js +119 -0
- package/dist/runtime/channels/channel-socket-registry.d.ts +157 -0
- package/dist/runtime/channels/channel-socket-registry.js +1428 -0
- package/dist/runtime/channels/channel-transports.d.ts +126 -0
- package/dist/runtime/channels/channel-transports.js +448 -0
- package/dist/runtime/channels/client-server-location-runtime.d.ts +41 -0
- package/dist/runtime/channels/client-server-location-runtime.js +465 -0
- package/dist/runtime/channels/client-server-service-wire.d.ts +48 -0
- package/dist/runtime/channels/client-server-service-wire.js +306 -0
- package/dist/runtime/channels/dispatch-error-reporter.d.ts +22 -0
- package/dist/runtime/channels/dispatch-error-reporter.js +133 -0
- package/dist/runtime/channels/fanout-location-runtime.d.ts +38 -0
- package/dist/runtime/channels/fanout-location-runtime.js +356 -0
- package/dist/runtime/channels/fanout-service-wire.d.ts +12 -0
- package/dist/runtime/channels/fanout-service-wire.js +29 -0
- package/dist/runtime/channels/handler-filter-scope.d.ts +8 -0
- package/dist/runtime/channels/handler-filter-scope.js +11 -0
- package/dist/runtime/channels/index.d.ts +9 -0
- package/dist/runtime/channels/index.js +30 -0
- package/dist/runtime/channels/local-spot-route-transport.d.ts +9 -0
- package/dist/runtime/channels/local-spot-route-transport.js +40 -0
- package/dist/runtime/channels/route-disconnected-error.d.ts +3 -0
- package/dist/runtime/channels/route-disconnected-error.js +10 -0
- package/dist/runtime/channels/route-member-snapshot.d.ts +10 -0
- package/dist/runtime/channels/route-member-snapshot.js +55 -0
- package/dist/runtime/channels/router-socket-spot-route-transport.d.ts +12 -0
- package/dist/runtime/channels/router-socket-spot-route-transport.js +43 -0
- package/dist/runtime/channels/source-spot-router.d.ts +10 -0
- package/dist/runtime/channels/source-spot-router.js +46 -0
- package/dist/runtime/channels/spot-direct-envelope.d.ts +5 -0
- package/dist/runtime/channels/spot-direct-envelope.js +29 -0
- package/dist/runtime/channels/spot-node-route-transport.d.ts +13 -0
- package/dist/runtime/channels/spot-node-route-transport.js +60 -0
- package/dist/runtime/channels/spot-route-bridge-transport.d.ts +14 -0
- package/dist/runtime/channels/spot-route-bridge-transport.js +58 -0
- package/dist/runtime/channels/spot-route-dispatch-strategy.d.ts +50 -0
- package/dist/runtime/channels/spot-route-dispatch-strategy.js +128 -0
- package/dist/runtime/channels/spot-route-target-resolver.d.ts +16 -0
- package/dist/runtime/channels/spot-route-target-resolver.js +92 -0
- package/dist/runtime/codecs/index.d.ts +14 -0
- package/dist/runtime/codecs/index.js +44 -0
- package/dist/runtime/configuration/index.d.ts +1 -0
- package/dist/runtime/configuration/index.js +17 -0
- package/dist/runtime/diagnostics/dispatch-error-port.d.ts +8 -0
- package/dist/runtime/diagnostics/dispatch-error-port.js +2 -0
- package/dist/runtime/diagnostics/flow-context.d.ts +21 -0
- package/dist/runtime/diagnostics/flow-context.js +68 -0
- package/dist/runtime/diagnostics/index.d.ts +82 -0
- package/dist/runtime/diagnostics/index.js +335 -0
- package/dist/runtime/diagnostics/internal-event-contracts.d.ts +139 -0
- package/dist/runtime/diagnostics/internal-event-contracts.js +68 -0
- package/dist/runtime/diagnostics/message-flow.d.ts +55 -0
- package/dist/runtime/diagnostics/message-flow.js +420 -0
- package/dist/runtime/diagnostics/runtime-metrics.d.ts +123 -0
- package/dist/runtime/diagnostics/runtime-metrics.js +454 -0
- package/dist/runtime/diagnostics/runtime-observation-queue.d.ts +58 -0
- package/dist/runtime/diagnostics/runtime-observation-queue.js +324 -0
- package/dist/runtime/diagnostics/topology-runtime-projections.d.ts +33 -0
- package/dist/runtime/diagnostics/topology-runtime-projections.js +255 -0
- package/dist/runtime/execution/index.d.ts +114 -0
- package/dist/runtime/execution/index.js +363 -0
- package/dist/runtime/execution/serial-execution-queue.d.ts +94 -0
- package/dist/runtime/execution/serial-execution-queue.js +411 -0
- package/dist/runtime/execution/serial-work-size.d.ts +4 -0
- package/dist/runtime/execution/serial-work-size.js +28 -0
- package/dist/runtime/execution/state-lane.d.ts +28 -0
- package/dist/runtime/execution/state-lane.js +105 -0
- package/dist/runtime/foundation/actor-join-recovery-codec.d.ts +72 -0
- package/dist/runtime/foundation/actor-join-recovery-codec.js +333 -0
- package/dist/runtime/foundation/event-loop-resources.d.ts +37 -0
- package/dist/runtime/foundation/event-loop-resources.js +163 -0
- package/dist/runtime/foundation/index.d.ts +18 -0
- package/dist/runtime/foundation/index.js +34 -0
- package/dist/runtime/foundation/message-follow-suppression-registry.d.ts +38 -0
- package/dist/runtime/foundation/message-follow-suppression-registry.js +89 -0
- package/dist/runtime/foundation/operation-identity.d.ts +12 -0
- package/dist/runtime/foundation/operation-identity.js +33 -0
- package/dist/runtime/foundation/operation-registry.d.ts +42 -0
- package/dist/runtime/foundation/operation-registry.js +146 -0
- package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +143 -0
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +1349 -0
- package/dist/runtime/foundation/route-mesh-connection-policy.d.ts +3 -0
- package/dist/runtime/foundation/route-mesh-connection-policy.js +13 -0
- package/dist/runtime/foundation/runtime-state-projections.d.ts +13 -0
- package/dist/runtime/foundation/runtime-state-projections.js +44 -0
- package/dist/runtime/foundation/service-authority-payload-codec.d.ts +56 -0
- package/dist/runtime/foundation/service-authority-payload-codec.js +411 -0
- package/dist/runtime/foundation/service-discovery-registry.d.ts +42 -0
- package/dist/runtime/foundation/service-discovery-registry.js +175 -0
- package/dist/runtime/foundation/service-instance-activation-recovery-codec.d.ts +19 -0
- package/dist/runtime/foundation/service-instance-activation-recovery-codec.js +287 -0
- package/dist/runtime/foundation/service-liveness-registry.d.ts +28 -0
- package/dist/runtime/foundation/service-liveness-registry.js +109 -0
- package/dist/runtime/foundation/service-mailbox.d.ts +60 -0
- package/dist/runtime/foundation/service-mailbox.js +283 -0
- package/dist/runtime/foundation/service-maintenance-runtime.d.ts +45 -0
- package/dist/runtime/foundation/service-maintenance-runtime.js +198 -0
- package/dist/runtime/foundation/service-metadata-codec.d.ts +2 -0
- package/dist/runtime/foundation/service-metadata-codec.js +69 -0
- package/dist/runtime/foundation/service-relocation-aggregate-committer.d.ts +35 -0
- package/dist/runtime/foundation/service-relocation-aggregate-committer.js +139 -0
- package/dist/runtime/foundation/service-relocation-coordinator.d.ts +19 -0
- package/dist/runtime/foundation/service-relocation-coordinator.js +16 -0
- package/dist/runtime/foundation/service-relocation-object-owner.d.ts +71 -0
- package/dist/runtime/foundation/service-relocation-object-owner.js +242 -0
- package/dist/runtime/foundation/service-relocation-runtime.d.ts +143 -0
- package/dist/runtime/foundation/service-relocation-runtime.js +1080 -0
- package/dist/runtime/foundation/service-runtime-contracts.d.ts +332 -0
- package/dist/runtime/foundation/service-runtime-contracts.js +66 -0
- package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +23 -0
- package/dist/runtime/foundation/service-session-binding-ingress-port.js +11 -0
- package/dist/runtime/foundation/service-stateful-registry.d.ts +142 -0
- package/dist/runtime/foundation/service-stateful-registry.js +465 -0
- package/dist/runtime/foundation/service-stateful-runtime.d.ts +393 -0
- package/dist/runtime/foundation/service-stateful-runtime.js +3898 -0
- package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +597 -0
- package/dist/runtime/foundation/service-stateful-wire-codec.js +2006 -0
- package/dist/runtime/foundation/service-topology-registry.d.ts +68 -0
- package/dist/runtime/foundation/service-topology-registry.js +393 -0
- package/dist/runtime/foundation/service-weighted-selection.d.ts +14 -0
- package/dist/runtime/foundation/service-weighted-selection.js +128 -0
- package/dist/runtime/foundation/service-wire-binary-primitives.d.ts +10 -0
- package/dist/runtime/foundation/service-wire-binary-primitives.js +53 -0
- package/dist/runtime/foundation/service-wire-codec.d.ts +22 -0
- package/dist/runtime/foundation/service-wire-codec.js +67 -0
- package/dist/runtime/foundation/service-wire-constants.generated.d.ts +110 -0
- package/dist/runtime/foundation/service-wire-constants.generated.js +132 -0
- package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +102 -0
- package/dist/runtime/foundation/service-wire-m6a-codec.js +568 -0
- package/dist/runtime/framework-errors-internal.d.ts +95 -0
- package/dist/runtime/framework-errors-internal.js +301 -0
- package/dist/runtime/handlers/handler-instance-scope.d.ts +11 -0
- package/dist/runtime/handlers/handler-instance-scope.js +284 -0
- package/dist/runtime/handlers/index.d.ts +13 -0
- package/dist/runtime/handlers/index.js +57 -0
- package/dist/runtime/host/actor-packet-relay.d.ts +60 -0
- package/dist/runtime/host/actor-packet-relay.js +664 -0
- package/dist/runtime/host/actor-placement-coordinator.d.ts +32 -0
- package/dist/runtime/host/actor-placement-coordinator.js +386 -0
- package/dist/runtime/host/actor-runtime-options-factory.d.ts +65 -0
- package/dist/runtime/host/actor-runtime-options-factory.js +160 -0
- package/dist/runtime/host/actor-transfer-authority-runtime.d.ts +26 -0
- package/dist/runtime/host/actor-transfer-authority-runtime.js +138 -0
- package/dist/runtime/host/actor-transfer-runtime.d.ts +149 -0
- package/dist/runtime/host/actor-transfer-runtime.js +1245 -0
- package/dist/runtime/host/application-job-queue.d.ts +99 -0
- package/dist/runtime/host/application-job-queue.js +409 -0
- package/dist/runtime/host/bound-session-relay.d.ts +41 -0
- package/dist/runtime/host/bound-session-relay.js +52 -0
- package/dist/runtime/host/bounded-replay-map.d.ts +12 -0
- package/dist/runtime/host/bounded-replay-map.js +42 -0
- package/dist/runtime/host/channel-runtime-options-factory.d.ts +22 -0
- package/dist/runtime/host/channel-runtime-options-factory.js +76 -0
- package/dist/runtime/host/entry-actor-runtime.d.ts +30 -0
- package/dist/runtime/host/entry-actor-runtime.js +80 -0
- package/dist/runtime/host/host-capacity-status.d.ts +11 -0
- package/dist/runtime/host/host-capacity-status.js +85 -0
- package/dist/runtime/host/index.d.ts +188 -0
- package/dist/runtime/host/index.js +2552 -0
- package/dist/runtime/host/instance-activation-authority.d.ts +33 -0
- package/dist/runtime/host/instance-activation-authority.js +496 -0
- package/dist/runtime/host/location-runtime-owner.d.ts +48 -0
- package/dist/runtime/host/location-runtime-owner.js +227 -0
- package/dist/runtime/host/mesh-router-resolver.d.ts +16 -0
- package/dist/runtime/host/mesh-router-resolver.js +108 -0
- package/dist/runtime/host/relocation-direct-transfer.d.ts +81 -0
- package/dist/runtime/host/relocation-direct-transfer.js +240 -0
- package/dist/runtime/host/relocation-integrity-fault-gate.d.ts +6 -0
- package/dist/runtime/host/relocation-integrity-fault-gate.js +5 -0
- package/dist/runtime/host/relocation-state-adapter.d.ts +8 -0
- package/dist/runtime/host/relocation-state-adapter.js +13 -0
- package/dist/runtime/host/remote-actor-join-receiver.d.ts +47 -0
- package/dist/runtime/host/remote-actor-join-receiver.js +161 -0
- package/dist/runtime/host/remote-actor-packet-target-store.d.ts +29 -0
- package/dist/runtime/host/remote-actor-packet-target-store.js +185 -0
- package/dist/runtime/host/remote-bound-session-relay.d.ts +58 -0
- package/dist/runtime/host/remote-bound-session-relay.js +540 -0
- package/dist/runtime/host/route-mesh-runtime-options.d.ts +9 -0
- package/dist/runtime/host/route-mesh-runtime-options.js +41 -0
- package/dist/runtime/host/route-mesh-runtime.d.ts +76 -0
- package/dist/runtime/host/route-mesh-runtime.js +746 -0
- package/dist/runtime/host/runtime-shutdown.d.ts +33 -0
- package/dist/runtime/host/runtime-shutdown.js +68 -0
- package/dist/runtime/host/service-relocation-control.d.ts +7 -0
- package/dist/runtime/host/service-relocation-control.js +24 -0
- package/dist/runtime/host/service-relocation-host-runtime.d.ts +241 -0
- package/dist/runtime/host/service-relocation-host-runtime.js +4113 -0
- package/dist/runtime/host/spot-address-transport.d.ts +47 -0
- package/dist/runtime/host/spot-address-transport.js +601 -0
- package/dist/runtime/host/spot-node-runtime-options-factory.d.ts +38 -0
- package/dist/runtime/host/spot-node-runtime-options-factory.js +36 -0
- package/dist/runtime/host/spot-runtime-options-factory.d.ts +48 -0
- package/dist/runtime/host/spot-runtime-options-factory.js +169 -0
- package/dist/runtime/host/stateful-authority-route-runtime.d.ts +34 -0
- package/dist/runtime/host/stateful-authority-route-runtime.js +440 -0
- package/dist/runtime/host/user-spot-creation-coordinator.d.ts +69 -0
- package/dist/runtime/host/user-spot-creation-coordinator.js +759 -0
- package/dist/runtime/locations/actor-location-claims.d.ts +43 -0
- package/dist/runtime/locations/actor-location-claims.js +319 -0
- package/dist/runtime/locations/actor-session-route-claims.d.ts +10 -0
- package/dist/runtime/locations/actor-session-route-claims.js +51 -0
- package/dist/runtime/locations/aggregate-inventory-store.d.ts +18 -0
- package/dist/runtime/locations/aggregate-inventory-store.js +399 -0
- package/dist/runtime/locations/authority-key-codec.d.ts +6 -0
- package/dist/runtime/locations/authority-key-codec.js +78 -0
- package/dist/runtime/locations/auto-connect-loop.d.ts +37 -0
- package/dist/runtime/locations/auto-connect-loop.js +112 -0
- package/dist/runtime/locations/auto-connect-planner.d.ts +9 -0
- package/dist/runtime/locations/auto-connect-planner.js +137 -0
- package/dist/runtime/locations/auto-connect-reconciler.d.ts +57 -0
- package/dist/runtime/locations/auto-connect-reconciler.js +358 -0
- package/dist/runtime/locations/auto-connect-types.d.ts +42 -0
- package/dist/runtime/locations/auto-connect-types.js +2 -0
- package/dist/runtime/locations/auto-connect.d.ts +4 -0
- package/dist/runtime/locations/auto-connect.js +20 -0
- package/dist/runtime/locations/canonical-codec.d.ts +7 -0
- package/dist/runtime/locations/canonical-codec.js +48 -0
- package/dist/runtime/locations/domain-store-contract.d.ts +34 -0
- package/dist/runtime/locations/domain-store-contract.js +2 -0
- package/dist/runtime/locations/in-memory-authority-store.d.ts +51 -0
- package/dist/runtime/locations/in-memory-authority-store.js +895 -0
- package/dist/runtime/locations/in-memory-location-store.d.ts +78 -0
- package/dist/runtime/locations/in-memory-location-store.js +1008 -0
- package/dist/runtime/locations/in-memory-provider-location-store.d.ts +19 -0
- package/dist/runtime/locations/in-memory-provider-location-store.js +183 -0
- package/dist/runtime/locations/index.d.ts +11 -0
- package/dist/runtime/locations/index.js +27 -0
- package/dist/runtime/locations/internal-location-contracts.d.ts +7 -0
- package/dist/runtime/locations/internal-location-contracts.js +21 -0
- package/dist/runtime/locations/internal-store-contracts.d.ts +35 -0
- package/dist/runtime/locations/internal-store-contracts.js +2 -0
- package/dist/runtime/locations/key-codec.d.ts +13 -0
- package/dist/runtime/locations/key-codec.js +41 -0
- package/dist/runtime/locations/lease-tracker.d.ts +36 -0
- package/dist/runtime/locations/lease-tracker.js +155 -0
- package/dist/runtime/locations/lifecycle-runtime.d.ts +19 -0
- package/dist/runtime/locations/lifecycle-runtime.js +2 -0
- package/dist/runtime/locations/lifecycle.d.ts +40 -0
- package/dist/runtime/locations/lifecycle.js +120 -0
- package/dist/runtime/locations/location-store-repository.d.ts +72 -0
- package/dist/runtime/locations/location-store-repository.js +3198 -0
- package/dist/runtime/locations/relocation-blob.d.ts +7 -0
- package/dist/runtime/locations/relocation-blob.js +24 -0
- package/dist/runtime/locations/resolvers.d.ts +145 -0
- package/dist/runtime/locations/resolvers.js +693 -0
- package/dist/runtime/locations/runtime.d.ts +144 -0
- package/dist/runtime/locations/runtime.js +792 -0
- package/dist/runtime/locations/spot-location-claims.d.ts +36 -0
- package/dist/runtime/locations/spot-location-claims.js +305 -0
- package/dist/runtime/messaging/creation-payload-codec.d.ts +4 -0
- package/dist/runtime/messaging/creation-payload-codec.js +40 -0
- package/dist/runtime/messaging/framework-json-v1.d.ts +7 -0
- package/dist/runtime/messaging/framework-json-v1.js +296 -0
- package/dist/runtime/messaging/index.d.ts +1 -0
- package/dist/runtime/messaging/index.js +2 -0
- package/dist/runtime/messaging/packet-name.d.ts +4 -0
- package/dist/runtime/messaging/packet-name.js +79 -0
- package/dist/runtime/messaging/payload-codec.d.ts +25 -0
- package/dist/runtime/messaging/payload-codec.js +255 -0
- package/dist/runtime/messaging/submission-result.d.ts +15 -0
- package/dist/runtime/messaging/submission-result.js +37 -0
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +395 -0
- package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +645 -0
- package/dist/runtime/routing-id.d.ts +8 -0
- package/dist/runtime/routing-id.js +112 -0
- package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +124 -0
- package/dist/runtime/spots/formal-remote-actor-admission-registry.js +269 -0
- package/dist/runtime/spots/formal-remote-actor-transfer-registry.d.ts +33 -0
- package/dist/runtime/spots/formal-remote-actor-transfer-registry.js +94 -0
- package/dist/runtime/spots/index.d.ts +247 -0
- package/dist/runtime/spots/index.js +2090 -0
- package/dist/runtime/spots/spot-activation-registry.d.ts +47 -0
- package/dist/runtime/spots/spot-activation-registry.js +272 -0
- package/dist/runtime/spots/spot-activation-state.d.ts +104 -0
- package/dist/runtime/spots/spot-activation-state.js +288 -0
- package/dist/runtime/spots/spot-activation.d.ts +89 -0
- package/dist/runtime/spots/spot-activation.js +601 -0
- package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +20 -0
- package/dist/runtime/spots/spot-actor-admission-coordinator.js +194 -0
- package/dist/runtime/spots/spot-actor-join-dispatch.d.ts +83 -0
- package/dist/runtime/spots/spot-actor-join-dispatch.js +185 -0
- package/dist/runtime/spots/spot-actor-lifecycle-drain.d.ts +22 -0
- package/dist/runtime/spots/spot-actor-lifecycle-drain.js +51 -0
- package/dist/runtime/spots/spot-actor-membership.d.ts +39 -0
- package/dist/runtime/spots/spot-actor-membership.js +179 -0
- package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +91 -0
- package/dist/runtime/spots/spot-actor-packet-dispatch.js +315 -0
- package/dist/runtime/spots/spot-actor-packet-drain.d.ts +40 -0
- package/dist/runtime/spots/spot-actor-packet-drain.js +235 -0
- package/dist/runtime/spots/spot-actor-packet-relay-dispatch.d.ts +18 -0
- package/dist/runtime/spots/spot-actor-packet-relay-dispatch.js +110 -0
- package/dist/runtime/spots/spot-closing.d.ts +2 -0
- package/dist/runtime/spots/spot-closing.js +30 -0
- package/dist/runtime/spots/spot-context.d.ts +46 -0
- package/dist/runtime/spots/spot-context.js +113 -0
- package/dist/runtime/spots/spot-entry-activation.d.ts +107 -0
- package/dist/runtime/spots/spot-entry-activation.js +342 -0
- package/dist/runtime/spots/spot-handle.d.ts +28 -0
- package/dist/runtime/spots/spot-handle.js +38 -0
- package/dist/runtime/spots/spot-handler-registry.d.ts +52 -0
- package/dist/runtime/spots/spot-handler-registry.js +155 -0
- package/dist/runtime/spots/spot-lifecycle-metrics.d.ts +8 -0
- package/dist/runtime/spots/spot-lifecycle-metrics.js +16 -0
- package/dist/runtime/spots/spot-location-claim.d.ts +25 -0
- package/dist/runtime/spots/spot-location-claim.js +55 -0
- package/dist/runtime/spots/spot-manager-internal-contracts.d.ts +10 -0
- package/dist/runtime/spots/spot-manager-internal-contracts.js +2 -0
- package/dist/runtime/spots/spot-manager-public.d.ts +32 -0
- package/dist/runtime/spots/spot-manager-public.js +219 -0
- package/dist/runtime/spots/spot-native-actor-join-admission.d.ts +26 -0
- package/dist/runtime/spots/spot-native-actor-join-admission.js +105 -0
- package/dist/runtime/spots/spot-native-flags.d.ts +2 -0
- package/dist/runtime/spots/spot-native-flags.js +5 -0
- package/dist/runtime/spots/spot-node-autoconnect.d.ts +23 -0
- package/dist/runtime/spots/spot-node-autoconnect.js +246 -0
- package/dist/runtime/spots/spot-node-runtime-manager.d.ts +137 -0
- package/dist/runtime/spots/spot-node-runtime-manager.js +1111 -0
- package/dist/runtime/spots/spot-outbound.d.ts +78 -0
- package/dist/runtime/spots/spot-outbound.js +460 -0
- package/dist/runtime/spots/spot-provider.d.ts +4 -0
- package/dist/runtime/spots/spot-provider.js +28 -0
- package/dist/runtime/spots/spot-publisher-transport.d.ts +14 -0
- package/dist/runtime/spots/spot-publisher-transport.js +28 -0
- package/dist/runtime/spots/spot-remote-codec.d.ts +50 -0
- package/dist/runtime/spots/spot-remote-codec.js +190 -0
- package/dist/runtime/spots/spot-remote-route-codec.d.ts +47 -0
- package/dist/runtime/spots/spot-remote-route-codec.js +168 -0
- package/dist/runtime/spots/spot-route-packet-dispatch.d.ts +25 -0
- package/dist/runtime/spots/spot-route-packet-dispatch.js +238 -0
- package/dist/runtime/spots/spot-route-replies.d.ts +17 -0
- package/dist/runtime/spots/spot-route-replies.js +76 -0
- package/dist/runtime/spots/spot-route-reply-wire.d.ts +10 -0
- package/dist/runtime/spots/spot-route-reply-wire.js +6 -0
- package/dist/runtime/spots/spot-routed-actor-admission.d.ts +36 -0
- package/dist/runtime/spots/spot-routed-actor-admission.js +240 -0
- package/dist/runtime/spots/spot-routed-bound-session-dispatch.d.ts +19 -0
- package/dist/runtime/spots/spot-routed-bound-session-dispatch.js +46 -0
- package/dist/runtime/spots/spot-routed-frame-dispatch.d.ts +54 -0
- package/dist/runtime/spots/spot-routed-frame-dispatch.js +162 -0
- package/dist/runtime/spots/spot-routed-spot-packet-dispatch.d.ts +43 -0
- package/dist/runtime/spots/spot-routed-spot-packet-dispatch.js +152 -0
- package/dist/runtime/spots/spot-routing-internal.d.ts +28 -0
- package/dist/runtime/spots/spot-routing-internal.js +2 -0
- package/dist/runtime/spots/spot-runtime-ports.d.ts +64 -0
- package/dist/runtime/spots/spot-runtime-ports.js +2 -0
- package/dist/runtime/spots/spot-serial-executor.d.ts +38 -0
- package/dist/runtime/spots/spot-serial-executor.js +133 -0
- package/dist/runtime/spots/spot-serial-turn-executor.d.ts +54 -0
- package/dist/runtime/spots/spot-serial-turn-executor.js +219 -0
- package/dist/runtime/spots/spot-subscription-dispatch.d.ts +32 -0
- package/dist/runtime/spots/spot-subscription-dispatch.js +194 -0
- package/dist/runtime/spots/spot-timer.d.ts +110 -0
- package/dist/runtime/spots/spot-timer.js +419 -0
- package/dist/runtime/streams/actor-session-binding-registry.d.ts +193 -0
- package/dist/runtime/streams/actor-session-binding-registry.js +1127 -0
- package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +20 -0
- package/dist/runtime/streams/actor-session-binding-runtime-owner.js +19 -0
- package/dist/runtime/streams/actor-session-lifecycle-coordinator.d.ts +4 -0
- package/dist/runtime/streams/actor-session-lifecycle-coordinator.js +24 -0
- package/dist/runtime/streams/bound-actor-relay-sender.d.ts +29 -0
- package/dist/runtime/streams/bound-actor-relay-sender.js +181 -0
- package/dist/runtime/streams/bound-session-response-target.d.ts +25 -0
- package/dist/runtime/streams/bound-session-response-target.js +54 -0
- package/dist/runtime/streams/bound-session-service.d.ts +48 -0
- package/dist/runtime/streams/bound-session-service.js +201 -0
- package/dist/runtime/streams/index.d.ts +152 -0
- package/dist/runtime/streams/index.js +338 -0
- package/dist/runtime/streams/managed-stream.d.ts +53 -0
- package/dist/runtime/streams/managed-stream.js +329 -0
- package/dist/runtime/streams/native-fallback-bound-session.d.ts +24 -0
- package/dist/runtime/streams/native-fallback-bound-session.js +201 -0
- package/dist/runtime/streams/protocol.d.ts +67 -0
- package/dist/runtime/streams/protocol.js +244 -0
- package/dist/runtime/streams/session-actor-coordinator.d.ts +56 -0
- package/dist/runtime/streams/session-actor-coordinator.js +371 -0
- package/dist/runtime/streams/session-calls.d.ts +59 -0
- package/dist/runtime/streams/session-calls.js +123 -0
- package/dist/runtime/streams/session-context.d.ts +108 -0
- package/dist/runtime/streams/session-context.js +377 -0
- package/dist/runtime/streams/session-local-actors.d.ts +10 -0
- package/dist/runtime/streams/session-local-actors.js +26 -0
- package/dist/runtime/streams/session-provider.d.ts +4 -0
- package/dist/runtime/streams/session-provider.js +30 -0
- package/dist/runtime/streams/session-requests.d.ts +25 -0
- package/dist/runtime/streams/session-requests.js +100 -0
- package/dist/runtime/streams/session-serial-executor.d.ts +12 -0
- package/dist/runtime/streams/session-serial-executor.js +58 -0
- package/dist/runtime/streams/stream-binding-runtime-ports.d.ts +62 -0
- package/dist/runtime/streams/stream-binding-runtime-ports.js +2 -0
- package/dist/runtime/streams/stream-frame-factory.d.ts +44 -0
- package/dist/runtime/streams/stream-frame-factory.js +149 -0
- package/dist/runtime/streams/stream-message-utils.d.ts +5 -0
- package/dist/runtime/streams/stream-message-utils.js +74 -0
- package/dist/runtime/streams/stream-session-runtime.d.ts +166 -0
- package/dist/runtime/streams/stream-session-runtime.js +1144 -0
- package/dist/runtime/workers/index.d.ts +38 -0
- package/dist/runtime/workers/index.js +474 -0
- package/package.json +19 -0
- package/src/contracts/Actors/ZLinkActor.ts +8 -0
- package/src/contracts/Actors/ZLinkActorClient.ts +18 -0
- package/src/contracts/Actors/ZLinkActorContext.ts +15 -0
- package/src/contracts/Actors/ZLinkActorFactory.ts +41 -0
- package/src/contracts/Actors/ZLinkActorManager.ts +31 -0
- package/src/contracts/Actors/index.ts +5 -0
- package/src/contracts/Channels/Calls.ts +33 -0
- package/src/contracts/Channels/IZLinkChannelClient.ts +6 -0
- package/src/contracts/Channels/IZLinkFanoutClient.ts +13 -0
- package/src/contracts/Channels/RouteCalls.ts +13 -0
- package/src/contracts/Channels/index.ts +4 -0
- package/src/contracts/Codecs/IZLinkCodecRegistryBuilder.ts +22 -0
- package/src/contracts/Codecs/ZLinkMessageSerializer.ts +6 -0
- package/src/contracts/Codecs/index.ts +2 -0
- package/src/contracts/Common/ActorRef.ts +8 -0
- package/src/contracts/Common/CoreTypes.ts +6 -0
- package/src/contracts/Common/Message.ts +9 -0
- package/src/contracts/Common/ZLinkEncodedPayload.ts +47 -0
- package/src/contracts/Common/ZLinkMessage.ts +135 -0
- package/src/contracts/Common/ZLinkMessageMetadata.ts +77 -0
- package/src/contracts/Common/ZLinkProviderResolver.ts +6 -0
- package/src/contracts/Common/encoded-payload-storage.ts +16 -0
- package/src/contracts/Common/index.ts +5 -0
- package/src/contracts/Configuration/Builders.ts +200 -0
- package/src/contracts/Configuration/CodecContentType.ts +68 -0
- package/src/contracts/Configuration/CodecSerializerSelection.ts +28 -0
- package/src/contracts/Configuration/Configs.ts +27 -0
- package/src/contracts/Configuration/ConfigurationException.ts +6 -0
- package/src/contracts/Configuration/Connections.ts +5 -0
- package/src/contracts/Configuration/DiagnosticsValidation.ts +25 -0
- package/src/contracts/Configuration/EndpointNotation.ts +232 -0
- package/src/contracts/Configuration/InternalDefaults.ts +13 -0
- package/src/contracts/Configuration/ObjectRoles.ts +47 -0
- package/src/contracts/Configuration/Registration.ts +191 -0
- package/src/contracts/Configuration/RegistrationBuilderPolicy.ts +76 -0
- package/src/contracts/Configuration/RegistrationBuilders.ts +1705 -0
- package/src/contracts/Configuration/RegistrationCodecRegistry.ts +118 -0
- package/src/contracts/Configuration/RegistrationNormalizers.ts +404 -0
- package/src/contracts/Configuration/RegistrationTypes.ts +499 -0
- package/src/contracts/Configuration/RegistrationValidators.ts +698 -0
- package/src/contracts/Configuration/RouteChannelInternalState.ts +50 -0
- package/src/contracts/Configuration/RuntimeEndpointConnections.ts +91 -0
- package/src/contracts/Configuration/SendTimeoutValidation.ts +15 -0
- package/src/contracts/Configuration/TimerRegistrationValidator.ts +37 -0
- package/src/contracts/Configuration/index.ts +35 -0
- package/src/contracts/Dispatch/ZLinkDispatchOptions.ts +222 -0
- package/src/contracts/Dispatch/ZLinkFlowOrigin.ts +1 -0
- package/src/contracts/Dispatch/index.ts +2 -0
- package/src/contracts/Errors/ZLinkFrameworkException.ts +27 -0
- package/src/contracts/Errors/index.ts +4 -0
- package/src/contracts/Eventing/Metrics.ts +27 -0
- package/src/contracts/Eventing/index.ts +1 -0
- package/src/contracts/Handlers/Attributes.ts +111 -0
- package/src/contracts/Handlers/Contexts.ts +21 -0
- package/src/contracts/Handlers/IZLinkChannelHandlers.ts +69 -0
- package/src/contracts/Handlers/IZLinkHandlerFilter.ts +22 -0
- package/src/contracts/Handlers/JsonContract.ts +172 -0
- package/src/contracts/Handlers/ZLinkHandlerFilterNext.ts +1 -0
- package/src/contracts/Handlers/index.ts +17 -0
- package/src/contracts/Locations/ActorTransfer.ts +84 -0
- package/src/contracts/Locations/Authority.ts +308 -0
- package/src/contracts/Locations/Diagnostics.ts +48 -0
- package/src/contracts/Locations/Keys.ts +93 -0
- package/src/contracts/Locations/Models.ts +7 -0
- package/src/contracts/Locations/Options.ts +42 -0
- package/src/contracts/Locations/Readiness.ts +11 -0
- package/src/contracts/Locations/RelocationStore.ts +51 -0
- package/src/contracts/Locations/Resolvers.ts +8 -0
- package/src/contracts/Locations/Rows.ts +164 -0
- package/src/contracts/Locations/RuntimeQuery.ts +56 -0
- package/src/contracts/Locations/Stores.ts +92 -0
- package/src/contracts/Locations/Values.ts +32 -0
- package/src/contracts/Locations/Watch.ts +16 -0
- package/src/contracts/Locations/Writes.ts +56 -0
- package/src/contracts/Locations/index.ts +48 -0
- package/src/contracts/RouteMesh/Contracts.ts +145 -0
- package/src/contracts/RouteMesh/RuntimeTopology.ts +201 -0
- package/src/contracts/RouteMesh/index.ts +2 -0
- package/src/contracts/Spots/Builders.ts +11 -0
- package/src/contracts/Spots/Contracts.ts +150 -0
- package/src/contracts/Spots/SpotKind.ts +24 -0
- package/src/contracts/Spots/ZLinkSpot.ts +88 -0
- package/src/contracts/Spots/index.ts +3 -0
- package/src/contracts/Streams/BoundSessionContracts.ts +9 -0
- package/src/contracts/Streams/IZLinkSession.ts +52 -0
- package/src/contracts/Streams/IZLinkSessionActor.ts +17 -0
- package/src/contracts/Streams/IZLinkSessionPacketHandler.ts +11 -0
- package/src/contracts/Streams/IZLinkStream.ts +10 -0
- package/src/contracts/Streams/ZLinkMessageMetadata.ts +1 -0
- package/src/contracts/Streams/ZLinkStreamCompression.ts +9 -0
- package/src/contracts/Streams/ZLinkStreamError.ts +9 -0
- package/src/contracts/Streams/index.ts +8 -0
- package/src/contracts/Timers/IZLinkTimer.ts +5 -0
- package/src/contracts/Timers/ZLinkTimerOptions.ts +11 -0
- package/src/contracts/Timers/ZLinkTimerTick.ts +12 -0
- package/src/contracts/Timers/index.ts +3 -0
- package/src/contracts/index.ts +71 -0
- package/src/index.ts +1 -0
- package/src/internal.ts +29 -0
- package/src/location-store-integration.ts +51 -0
- package/src/nest-integration.ts +251 -0
- package/src/runtime/abort.ts +70 -0
- package/src/runtime/actors/actor-authority-payload-codec.ts +454 -0
- package/src/runtime/actors/actor-authority-publication.ts +277 -0
- package/src/runtime/actors/actor-client.ts +1018 -0
- package/src/runtime/actors/actor-context.ts +583 -0
- package/src/runtime/actors/actor-creation.ts +254 -0
- package/src/runtime/actors/actor-execution-context.ts +38 -0
- package/src/runtime/actors/actor-handoff.ts +1911 -0
- package/src/runtime/actors/actor-join-deferred-scope.ts +183 -0
- package/src/runtime/actors/actor-join-relocation.ts +39 -0
- package/src/runtime/actors/actor-lifecycle-snapshot.ts +90 -0
- package/src/runtime/actors/actor-local-native-join.ts +725 -0
- package/src/runtime/actors/actor-mailbox.ts +92 -0
- package/src/runtime/actors/actor-message-follow-context.ts +347 -0
- package/src/runtime/actors/actor-native-lookup.ts +38 -0
- package/src/runtime/actors/actor-packet-relay-wire.ts +223 -0
- package/src/runtime/actors/actor-remote-joiner.ts +238 -0
- package/src/runtime/actors/actor-remote-wire.ts +169 -0
- package/src/runtime/actors/actor-retry-delay.ts +37 -0
- package/src/runtime/actors/actor-routed-join-transport.ts +58 -0
- package/src/runtime/actors/actor-routed-json-request.ts +44 -0
- package/src/runtime/actors/actor-runtime-contracts.ts +126 -0
- package/src/runtime/actors/actor-runtime-state.ts +805 -0
- package/src/runtime/actors/actor-source-transfer.ts +62 -0
- package/src/runtime/actors/actor-transfer-registry.ts +112 -0
- package/src/runtime/actors/actor-transport-delivery-gate.ts +17 -0
- package/src/runtime/actors/bound-session-wire.ts +257 -0
- package/src/runtime/actors/deferred-join-accepted-journal.ts +1319 -0
- package/src/runtime/actors/index.ts +972 -0
- package/src/runtime/actors/post-commit-actor-binder.ts +70 -0
- package/src/runtime/actors/post-commit-actor-location.ts +114 -0
- package/src/runtime/actors/spot-actor-dispatch.ts +362 -0
- package/src/runtime/actors/transferred-actor-rollback.ts +123 -0
- package/src/runtime/admission.ts +192 -0
- package/src/runtime/application-jobs/application-ingress-record-owner.ts +216 -0
- package/src/runtime/application-jobs/application-job-queue-handler-start-gate.ts +10 -0
- package/src/runtime/application-jobs/application-job-queue-scope.ts +64 -0
- package/src/runtime/application-jobs/contracts.ts +26 -0
- package/src/runtime/application-jobs/receive-flow-controller.ts +188 -0
- package/src/runtime/backend/contracts/index.ts +765 -0
- package/src/runtime/backend/index.ts +9 -0
- package/src/runtime/backend/mesh-actor-session-node-adapter.ts +73 -0
- package/src/runtime/backend/mesh-completion-table.ts +165 -0
- package/src/runtime/backend/mesh-dispatch-pump.ts +363 -0
- package/src/runtime/backend/mesh-record-dispatcher.ts +65 -0
- package/src/runtime/backend/node/index.ts +4 -0
- package/src/runtime/backend/node/node-backend-adapter-factory.ts +204 -0
- package/src/runtime/backend/node/node-backend-adapter-support.ts +257 -0
- package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +133 -0
- package/src/runtime/backend/node/node-mesh-backend-adapter.ts +44 -0
- package/src/runtime/backend/node/node-monitor-backend-adapter.ts +39 -0
- package/src/runtime/backend/node/node-raw-binding-port.contract.ts +21 -0
- package/src/runtime/backend/node/node-raw-binding-port.ts +518 -0
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +2669 -0
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +372 -0
- package/src/runtime/backend/node-backend-adapter.ts +19 -0
- package/src/runtime/backend/raw-binding-port.ts +69 -0
- package/src/runtime/backend/runtime-message.ts +51 -0
- package/src/runtime/backend/runtime-values.ts +107 -0
- package/src/runtime/channels/channel-autoconnect.ts +46 -0
- package/src/runtime/channels/channel-clients.ts +527 -0
- package/src/runtime/channels/channel-dispatch-pipeline.ts +408 -0
- package/src/runtime/channels/channel-dispatch-services.ts +234 -0
- package/src/runtime/channels/channel-dispatchers.ts +790 -0
- package/src/runtime/channels/channel-envelope-inspection.ts +125 -0
- package/src/runtime/channels/channel-envelope.ts +743 -0
- package/src/runtime/channels/channel-framework-packets.ts +8 -0
- package/src/runtime/channels/channel-multipart.ts +61 -0
- package/src/runtime/channels/channel-outbound-operations.ts +584 -0
- package/src/runtime/channels/channel-receive-loops.ts +901 -0
- package/src/runtime/channels/channel-receive-task-tracker.ts +39 -0
- package/src/runtime/channels/channel-runtime-lifecycle.ts +969 -0
- package/src/runtime/channels/channel-runtime-manager.ts +444 -0
- package/src/runtime/channels/channel-socket-options.ts +153 -0
- package/src/runtime/channels/channel-socket-registry.ts +1837 -0
- package/src/runtime/channels/channel-transports.ts +1154 -0
- package/src/runtime/channels/client-server-location-runtime.ts +576 -0
- package/src/runtime/channels/client-server-service-wire.ts +390 -0
- package/src/runtime/channels/dispatch-error-reporter.ts +165 -0
- package/src/runtime/channels/fanout-location-runtime.ts +459 -0
- package/src/runtime/channels/fanout-service-wire.ts +43 -0
- package/src/runtime/channels/handler-filter-scope.ts +29 -0
- package/src/runtime/channels/index.ts +45 -0
- package/src/runtime/channels/local-spot-route-transport.ts +59 -0
- package/src/runtime/channels/route-disconnected-error.ts +6 -0
- package/src/runtime/channels/route-member-snapshot.ts +55 -0
- package/src/runtime/channels/router-socket-spot-route-transport.ts +77 -0
- package/src/runtime/channels/source-spot-router.ts +89 -0
- package/src/runtime/channels/spot-direct-envelope.ts +44 -0
- package/src/runtime/channels/spot-node-route-transport.ts +92 -0
- package/src/runtime/channels/spot-route-bridge-transport.ts +75 -0
- package/src/runtime/channels/spot-route-dispatch-strategy.ts +329 -0
- package/src/runtime/channels/spot-route-target-resolver.ts +101 -0
- package/src/runtime/codecs/index.ts +69 -0
- package/src/runtime/configuration/index.ts +1 -0
- package/src/runtime/diagnostics/dispatch-error-port.ts +8 -0
- package/src/runtime/diagnostics/flow-context.ts +84 -0
- package/src/runtime/diagnostics/index.ts +441 -0
- package/src/runtime/diagnostics/internal-event-contracts.ts +135 -0
- package/src/runtime/diagnostics/message-flow.ts +560 -0
- package/src/runtime/diagnostics/runtime-metrics.ts +601 -0
- package/src/runtime/diagnostics/runtime-observation-queue.ts +362 -0
- package/src/runtime/diagnostics/topology-runtime-projections.ts +298 -0
- package/src/runtime/execution/index.ts +466 -0
- package/src/runtime/execution/serial-execution-queue.ts +534 -0
- package/src/runtime/execution/serial-work-size.ts +31 -0
- package/src/runtime/execution/state-lane.ts +116 -0
- package/src/runtime/foundation/actor-join-recovery-codec.ts +465 -0
- package/src/runtime/foundation/event-loop-resources.ts +162 -0
- package/src/runtime/foundation/index.ts +18 -0
- package/src/runtime/foundation/message-follow-suppression-registry.ts +130 -0
- package/src/runtime/foundation/operation-identity.ts +49 -0
- package/src/runtime/foundation/operation-registry.ts +162 -0
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +1771 -0
- package/src/runtime/foundation/route-mesh-connection-policy.ts +28 -0
- package/src/runtime/foundation/runtime-state-projections.ts +68 -0
- package/src/runtime/foundation/service-authority-payload-codec.ts +566 -0
- package/src/runtime/foundation/service-discovery-registry.ts +263 -0
- package/src/runtime/foundation/service-instance-activation-recovery-codec.ts +329 -0
- package/src/runtime/foundation/service-liveness-registry.ts +144 -0
- package/src/runtime/foundation/service-mailbox.ts +369 -0
- package/src/runtime/foundation/service-maintenance-runtime.ts +242 -0
- package/src/runtime/foundation/service-metadata-codec.ts +75 -0
- package/src/runtime/foundation/service-relocation-aggregate-committer.ts +218 -0
- package/src/runtime/foundation/service-relocation-coordinator.ts +37 -0
- package/src/runtime/foundation/service-relocation-object-owner.ts +428 -0
- package/src/runtime/foundation/service-relocation-runtime.ts +1407 -0
- package/src/runtime/foundation/service-runtime-contracts.ts +423 -0
- package/src/runtime/foundation/service-session-binding-ingress-port.ts +45 -0
- package/src/runtime/foundation/service-stateful-registry.ts +664 -0
- package/src/runtime/foundation/service-stateful-runtime.ts +5753 -0
- package/src/runtime/foundation/service-stateful-wire-codec.ts +3008 -0
- package/src/runtime/foundation/service-topology-registry.ts +599 -0
- package/src/runtime/foundation/service-weighted-selection.ts +166 -0
- package/src/runtime/foundation/service-wire-binary-primitives.ts +80 -0
- package/src/runtime/foundation/service-wire-codec.ts +91 -0
- package/src/runtime/foundation/service-wire-constants.generated.ts +131 -0
- package/src/runtime/foundation/service-wire-m6a-codec.ts +647 -0
- package/src/runtime/framework-errors-internal.ts +338 -0
- package/src/runtime/handlers/handler-instance-scope.ts +372 -0
- package/src/runtime/handlers/index.ts +93 -0
- package/src/runtime/host/actor-packet-relay.ts +1070 -0
- package/src/runtime/host/actor-placement-coordinator.ts +573 -0
- package/src/runtime/host/actor-runtime-options-factory.ts +282 -0
- package/src/runtime/host/actor-transfer-authority-runtime.ts +225 -0
- package/src/runtime/host/actor-transfer-runtime.ts +1958 -0
- package/src/runtime/host/application-job-queue.ts +518 -0
- package/src/runtime/host/bound-session-relay.ts +94 -0
- package/src/runtime/host/bounded-replay-map.ts +42 -0
- package/src/runtime/host/channel-runtime-options-factory.ts +104 -0
- package/src/runtime/host/entry-actor-runtime.ts +115 -0
- package/src/runtime/host/host-capacity-status.ts +103 -0
- package/src/runtime/host/index.ts +3492 -0
- package/src/runtime/host/instance-activation-authority.ts +658 -0
- package/src/runtime/host/location-runtime-owner.ts +313 -0
- package/src/runtime/host/mesh-router-resolver.ts +114 -0
- package/src/runtime/host/relocation-direct-transfer.ts +273 -0
- package/src/runtime/host/relocation-integrity-fault-gate.ts +8 -0
- package/src/runtime/host/relocation-state-adapter.ts +24 -0
- package/src/runtime/host/remote-actor-join-receiver.ts +264 -0
- package/src/runtime/host/remote-actor-packet-target-store.ts +244 -0
- package/src/runtime/host/remote-bound-session-relay.ts +900 -0
- package/src/runtime/host/route-mesh-runtime-options.ts +46 -0
- package/src/runtime/host/route-mesh-runtime.ts +891 -0
- package/src/runtime/host/runtime-shutdown.ts +101 -0
- package/src/runtime/host/service-relocation-control.ts +28 -0
- package/src/runtime/host/service-relocation-host-runtime.ts +5817 -0
- package/src/runtime/host/spot-address-transport.ts +997 -0
- package/src/runtime/host/spot-node-runtime-options-factory.ts +82 -0
- package/src/runtime/host/spot-runtime-options-factory.ts +286 -0
- package/src/runtime/host/stateful-authority-route-runtime.ts +662 -0
- package/src/runtime/host/user-spot-creation-coordinator.ts +1176 -0
- package/src/runtime/locations/actor-location-claims.ts +426 -0
- package/src/runtime/locations/actor-session-route-claims.ts +64 -0
- package/src/runtime/locations/aggregate-inventory-store.ts +581 -0
- package/src/runtime/locations/authority-key-codec.ts +80 -0
- package/src/runtime/locations/auto-connect-loop.ts +131 -0
- package/src/runtime/locations/auto-connect-planner.ts +178 -0
- package/src/runtime/locations/auto-connect-reconciler.ts +425 -0
- package/src/runtime/locations/auto-connect-types.ts +62 -0
- package/src/runtime/locations/auto-connect.ts +4 -0
- package/src/runtime/locations/canonical-codec.ts +54 -0
- package/src/runtime/locations/domain-store-contract.ts +149 -0
- package/src/runtime/locations/in-memory-authority-store.ts +1172 -0
- package/src/runtime/locations/in-memory-location-store.ts +1467 -0
- package/src/runtime/locations/in-memory-provider-location-store.ts +213 -0
- package/src/runtime/locations/index.ts +11 -0
- package/src/runtime/locations/internal-location-contracts.ts +22 -0
- package/src/runtime/locations/internal-store-contracts.ts +90 -0
- package/src/runtime/locations/key-codec.ts +61 -0
- package/src/runtime/locations/lease-tracker.ts +221 -0
- package/src/runtime/locations/lifecycle-runtime.ts +44 -0
- package/src/runtime/locations/lifecycle.ts +272 -0
- package/src/runtime/locations/location-store-repository.ts +4211 -0
- package/src/runtime/locations/relocation-blob.ts +36 -0
- package/src/runtime/locations/resolvers.ts +1054 -0
- package/src/runtime/locations/runtime.ts +1126 -0
- package/src/runtime/locations/spot-location-claims.ts +435 -0
- package/src/runtime/messaging/creation-payload-codec.ts +59 -0
- package/src/runtime/messaging/framework-json-v1.ts +307 -0
- package/src/runtime/messaging/index.ts +1 -0
- package/src/runtime/messaging/packet-name.ts +94 -0
- package/src/runtime/messaging/payload-codec.ts +418 -0
- package/src/runtime/messaging/submission-result.ts +61 -0
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +189 -0
- package/src/runtime/routing-id.ts +125 -0
- package/src/runtime/spots/formal-remote-actor-admission-registry.ts +418 -0
- package/src/runtime/spots/formal-remote-actor-transfer-registry.ts +147 -0
- package/src/runtime/spots/index.ts +3303 -0
- package/src/runtime/spots/spot-activation-registry.ts +335 -0
- package/src/runtime/spots/spot-activation-state.ts +414 -0
- package/src/runtime/spots/spot-activation.ts +1027 -0
- package/src/runtime/spots/spot-actor-admission-coordinator.ts +354 -0
- package/src/runtime/spots/spot-actor-join-dispatch.ts +303 -0
- package/src/runtime/spots/spot-actor-lifecycle-drain.ts +79 -0
- package/src/runtime/spots/spot-actor-membership.ts +266 -0
- package/src/runtime/spots/spot-actor-packet-dispatch.ts +593 -0
- package/src/runtime/spots/spot-actor-packet-drain.ts +328 -0
- package/src/runtime/spots/spot-actor-packet-relay-dispatch.ts +162 -0
- package/src/runtime/spots/spot-closing.ts +34 -0
- package/src/runtime/spots/spot-context.ts +259 -0
- package/src/runtime/spots/spot-entry-activation.ts +551 -0
- package/src/runtime/spots/spot-handle.ts +101 -0
- package/src/runtime/spots/spot-handler-registry.ts +273 -0
- package/src/runtime/spots/spot-lifecycle-metrics.ts +15 -0
- package/src/runtime/spots/spot-location-claim.ts +86 -0
- package/src/runtime/spots/spot-manager-internal-contracts.ts +11 -0
- package/src/runtime/spots/spot-manager-public.ts +361 -0
- package/src/runtime/spots/spot-native-actor-join-admission.ts +151 -0
- package/src/runtime/spots/spot-native-flags.ts +4 -0
- package/src/runtime/spots/spot-node-autoconnect.ts +325 -0
- package/src/runtime/spots/spot-node-runtime-manager.ts +1573 -0
- package/src/runtime/spots/spot-outbound.ts +761 -0
- package/src/runtime/spots/spot-provider.ts +36 -0
- package/src/runtime/spots/spot-publisher-transport.ts +69 -0
- package/src/runtime/spots/spot-remote-codec.ts +308 -0
- package/src/runtime/spots/spot-remote-route-codec.ts +284 -0
- package/src/runtime/spots/spot-route-packet-dispatch.ts +359 -0
- package/src/runtime/spots/spot-route-replies.ts +126 -0
- package/src/runtime/spots/spot-route-reply-wire.ts +15 -0
- package/src/runtime/spots/spot-routed-actor-admission.ts +351 -0
- package/src/runtime/spots/spot-routed-bound-session-dispatch.ts +132 -0
- package/src/runtime/spots/spot-routed-frame-dispatch.ts +250 -0
- package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +254 -0
- package/src/runtime/spots/spot-routing-internal.ts +33 -0
- package/src/runtime/spots/spot-runtime-ports.ts +196 -0
- package/src/runtime/spots/spot-serial-executor.ts +170 -0
- package/src/runtime/spots/spot-serial-turn-executor.ts +319 -0
- package/src/runtime/spots/spot-subscription-dispatch.ts +252 -0
- package/src/runtime/spots/spot-timer.ts +609 -0
- package/src/runtime/streams/actor-session-binding-registry.ts +1691 -0
- package/src/runtime/streams/actor-session-binding-runtime-owner.ts +78 -0
- package/src/runtime/streams/actor-session-lifecycle-coordinator.ts +20 -0
- package/src/runtime/streams/bound-actor-relay-sender.ts +275 -0
- package/src/runtime/streams/bound-session-response-target.ts +91 -0
- package/src/runtime/streams/bound-session-service.ts +500 -0
- package/src/runtime/streams/index.ts +706 -0
- package/src/runtime/streams/managed-stream.ts +509 -0
- package/src/runtime/streams/native-fallback-bound-session.ts +321 -0
- package/src/runtime/streams/protocol.ts +291 -0
- package/src/runtime/streams/session-actor-coordinator.ts +605 -0
- package/src/runtime/streams/session-calls.ts +221 -0
- package/src/runtime/streams/session-context.ts +609 -0
- package/src/runtime/streams/session-local-actors.ts +30 -0
- package/src/runtime/streams/session-provider.ts +50 -0
- package/src/runtime/streams/session-requests.ts +109 -0
- package/src/runtime/streams/session-serial-executor.ts +97 -0
- package/src/runtime/streams/stream-binding-runtime-ports.ts +106 -0
- package/src/runtime/streams/stream-frame-factory.ts +255 -0
- package/src/runtime/streams/stream-message-utils.ts +89 -0
- package/src/runtime/streams/stream-session-runtime.ts +1420 -0
- package/src/runtime/workers/index.ts +580 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,4113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ServiceRelocationDataLostError = exports.ZLinkHostServiceRelocationRuntime = exports.ZLinkRelocationStateIncompatibleError = void 0;
|
|
4
|
+
exports.decodeQueuedHandoffPacket = decodeQueuedHandoffPacket;
|
|
5
|
+
exports.createServiceRelocationId = createServiceRelocationId;
|
|
6
|
+
exports.relocationFailedFailureCode = relocationFailedFailureCode;
|
|
7
|
+
exports.encodeHandoffQueuedMessages = encodeHandoffQueuedMessages;
|
|
8
|
+
const node_crypto_1 = require("node:crypto");
|
|
9
|
+
const runtime_values_1 = require("../backend/runtime-values");
|
|
10
|
+
const contracts_1 = require("../../contracts");
|
|
11
|
+
const Options_1 = require("../../contracts/Locations/Options");
|
|
12
|
+
const framework_errors_internal_1 = require("../framework-errors-internal");
|
|
13
|
+
const service_runtime_contracts_1 = require("../foundation/service-runtime-contracts");
|
|
14
|
+
const routing_id_1 = require("../routing-id");
|
|
15
|
+
const service_wire_m6a_codec_1 = require("../foundation/service-wire-m6a-codec");
|
|
16
|
+
const service_relocation_runtime_1 = require("../foundation/service-relocation-runtime");
|
|
17
|
+
const relocation_direct_transfer_1 = require("./relocation-direct-transfer");
|
|
18
|
+
const relocation_state_adapter_1 = require("./relocation-state-adapter");
|
|
19
|
+
const service_relocation_coordinator_1 = require("../foundation/service-relocation-coordinator");
|
|
20
|
+
const service_maintenance_runtime_1 = require("../foundation/service-maintenance-runtime");
|
|
21
|
+
const service_relocation_object_owner_1 = require("../foundation/service-relocation-object-owner");
|
|
22
|
+
const service_relocation_aggregate_committer_1 = require("../foundation/service-relocation-aggregate-committer");
|
|
23
|
+
const spot_provider_1 = require("../spots/spot-provider");
|
|
24
|
+
const actor_runtime_state_1 = require("../actors/actor-runtime-state");
|
|
25
|
+
const actor_handoff_1 = require("../actors/actor-handoff");
|
|
26
|
+
const spot_remote_codec_1 = require("../spots/spot-remote-codec");
|
|
27
|
+
const actor_authority_publication_1 = require("../actors/actor-authority-publication");
|
|
28
|
+
const service_authority_payload_codec_1 = require("../foundation/service-authority-payload-codec");
|
|
29
|
+
const actor_transfer_runtime_1 = require("./actor-transfer-runtime");
|
|
30
|
+
const authority_key_codec_1 = require("../locations/authority-key-codec");
|
|
31
|
+
const service_relocation_control_1 = require("./service-relocation-control");
|
|
32
|
+
const service_stateful_wire_codec_1 = require("../foundation/service-stateful-wire-codec");
|
|
33
|
+
const service_wire_constants_generated_1 = require("../foundation/service-wire-constants.generated");
|
|
34
|
+
const bounded_replay_map_1 = require("./bounded-replay-map");
|
|
35
|
+
const relocation_integrity_fault_gate_1 = require("./relocation-integrity-fault-gate");
|
|
36
|
+
const actor_remote_wire_1 = require("../actors/actor-remote-wire");
|
|
37
|
+
const actor_join_recovery_codec_1 = require("../foundation/actor-join-recovery-codec");
|
|
38
|
+
class ZLinkRelocationStateIncompatibleError extends Error {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = 'ZLinkRelocationStateIncompatibleError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.ZLinkRelocationStateIncompatibleError = ZLinkRelocationStateIncompatibleError;
|
|
45
|
+
const RELOCATION_PARTICIPANT_STATE_LIMIT_BYTES = 64 * 1024 * 1024;
|
|
46
|
+
const RELOCATION_TARGET_TOMBSTONE_LIMIT = 1024;
|
|
47
|
+
const RELOCATION_OPERATION_RETENTION_MS = 5 * 60_000;
|
|
48
|
+
const SERVICE_CONTROL_TERMINAL_CAPACITY = 4096;
|
|
49
|
+
/** Production host bridge from Retire inventory to remote RouteMesh owners. */
|
|
50
|
+
class ZLinkHostServiceRelocationRuntime {
|
|
51
|
+
options;
|
|
52
|
+
targetStages = new Map();
|
|
53
|
+
terminalTargets = new bounded_replay_map_1.BoundedReplayMap(RELOCATION_TARGET_TOMBSTONE_LIMIT);
|
|
54
|
+
relocationAuthorityKeys = new Map();
|
|
55
|
+
targetPrepareOperations = new Map();
|
|
56
|
+
pendingReplyRelays = new Map();
|
|
57
|
+
pendingSessionRelocations = new Map();
|
|
58
|
+
terminalSessionRelocations = new bounded_replay_map_1.BoundedReplayMap(SERVICE_CONTROL_TERMINAL_CAPACITY);
|
|
59
|
+
/**
|
|
60
|
+
* A command 44 submit has no response path. Keep a bounded record of a
|
|
61
|
+
* rejected one-way attempt without turning the committed relocation into a
|
|
62
|
+
* retry loop.
|
|
63
|
+
*/
|
|
64
|
+
failedSessionRouteSubmits = new bounded_replay_map_1.BoundedReplayMap(SERVICE_CONTROL_TERMINAL_CAPACITY);
|
|
65
|
+
sourceRelocationIds = new Set();
|
|
66
|
+
targetAssemblies = new Map();
|
|
67
|
+
/**
|
|
68
|
+
* Terminal restore failures (assembly/checksum/factory/restore error): the
|
|
69
|
+
* target already sent relocationFailed (command 53) once. An exact-identity
|
|
70
|
+
* Prepare resend replays the stored response rather than retrying the
|
|
71
|
+
* restore; a different exact identity reusing the same RelocationId+attempt
|
|
72
|
+
* key supersedes the stale entry (spec 28 §3, §9).
|
|
73
|
+
*/
|
|
74
|
+
targetReadyFailures = new Map();
|
|
75
|
+
/**
|
|
76
|
+
* Successful restores retained across a Ready delivery retry: an
|
|
77
|
+
* exact-identity Prepare resend re-submits Ready against this retained
|
|
78
|
+
* staging instead of redoing the restore (spec 28).
|
|
79
|
+
*/
|
|
80
|
+
targetReadyResponses = new Map();
|
|
81
|
+
sourceCutoverWindows = new Map();
|
|
82
|
+
peerPayloadBudgets = new Map();
|
|
83
|
+
nodePayloadBudget;
|
|
84
|
+
sourceActorJoinProfiles = new Map();
|
|
85
|
+
terminalActorJoinSourceLeaves = new bounded_replay_map_1.BoundedReplayMap(SERVICE_CONTROL_TERMINAL_CAPACITY);
|
|
86
|
+
codec = new service_relocation_runtime_1.ServiceRelocationAuthorityPayloadCodec();
|
|
87
|
+
/**
|
|
88
|
+
* Set once dispose() starts tearing assemblies down. A payload() rejection
|
|
89
|
+
* observed after this point is the shutdown itself, not a chunk-assembly or
|
|
90
|
+
* checksum integrity failure — it must not be reported as DataLost(35).
|
|
91
|
+
*/
|
|
92
|
+
disposed = false;
|
|
93
|
+
constructor(options) {
|
|
94
|
+
this.options = options;
|
|
95
|
+
}
|
|
96
|
+
async relocateActorJoin(input) {
|
|
97
|
+
const sourceRef = input.state.nativeActorRef;
|
|
98
|
+
if (sourceRef === undefined) {
|
|
99
|
+
throw new Error(`Actor '${input.state.actorId}' has no source authority identity.`);
|
|
100
|
+
}
|
|
101
|
+
const descriptors = await this.options.liveDescriptors(input.meshName, input.signal);
|
|
102
|
+
const target = descriptors.find(value => (0, routing_id_1.routingIdsEqual)(value.rid, input.target.targetNodeRid));
|
|
103
|
+
if (target === undefined) {
|
|
104
|
+
throw new Error(`Actor '${input.state.actorId}' Join target '${String(input.target.targetNodeRid)}' is not live.`);
|
|
105
|
+
}
|
|
106
|
+
if (input.target.targetNodeGeneration !== undefined
|
|
107
|
+
&& target.lifecycleGeneration !== input.target.targetNodeGeneration) {
|
|
108
|
+
throw new Error(`Actor '${input.state.actorId}' Join target generation changed after admission.`);
|
|
109
|
+
}
|
|
110
|
+
this.reserveExactRelocationId(input.relocationId);
|
|
111
|
+
let resolveReady;
|
|
112
|
+
const ready = new Promise((resolve) => {
|
|
113
|
+
resolveReady = resolve;
|
|
114
|
+
});
|
|
115
|
+
this.sourceActorJoinProfiles.set(input.relocationId, {
|
|
116
|
+
actor: input.actor,
|
|
117
|
+
state: input.state,
|
|
118
|
+
sourceActorRef: sourceRef,
|
|
119
|
+
sourceSpotId: input.state.spotId,
|
|
120
|
+
targetSpotId: input.target.spotId,
|
|
121
|
+
targetNodeRid: input.target.targetNodeRid,
|
|
122
|
+
relocationContentType: this.actorRegistration(input.state.meshName ?? input.meshName, input.state.actorType ?? '').relocation.kind === 'snapshot'
|
|
123
|
+
? 'application/vnd.zlink.actor-relocation.snapshot'
|
|
124
|
+
: 'application/vnd.zlink.actor-relocation.recreate',
|
|
125
|
+
completionOperationId: input.completionOperationId,
|
|
126
|
+
canonicalRecovery: input.canonicalRecovery,
|
|
127
|
+
ready,
|
|
128
|
+
resolveReady
|
|
129
|
+
});
|
|
130
|
+
const membershipEpoch = input.state.spotMembershipEpoch > 0n
|
|
131
|
+
? input.state.spotMembershipEpoch
|
|
132
|
+
: 1n;
|
|
133
|
+
const spotGeneration = input.target.targetSpotGeneration ?? target.lifecycleGeneration;
|
|
134
|
+
try {
|
|
135
|
+
await this.relocateStandaloneActor(input.meshName, input.state, target, target.applicationVersion, input.signal, {
|
|
136
|
+
spotId: input.target.spotId,
|
|
137
|
+
spotKind: input.target.spotKind === contracts_1.ZLinkSpotKind.Entry
|
|
138
|
+
? contracts_1.ZLinkSpotKind.Entry
|
|
139
|
+
: contracts_1.ZLinkSpotKind.User,
|
|
140
|
+
spotObjectGeneration: spotGeneration
|
|
141
|
+
}, input.relocationId, true, input.advertisedReceiveChunkLimitBytes);
|
|
142
|
+
resolveReady();
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
this.sourceActorJoinProfiles.delete(input.relocationId);
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
actorRef: {
|
|
150
|
+
actorId: sourceRef.actorId,
|
|
151
|
+
generation: sourceRef.generation,
|
|
152
|
+
nodeRid: target.rid
|
|
153
|
+
},
|
|
154
|
+
membershipEpoch,
|
|
155
|
+
spotGeneration
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
async dispose() {
|
|
159
|
+
this.disposed = true;
|
|
160
|
+
const errors = [];
|
|
161
|
+
for (const stage of this.targetStages.values()) {
|
|
162
|
+
if (stage.fallback !== undefined)
|
|
163
|
+
clearTimeout(stage.fallback);
|
|
164
|
+
try {
|
|
165
|
+
if (stage.finalize !== undefined) {
|
|
166
|
+
await stage.finalize;
|
|
167
|
+
}
|
|
168
|
+
else if (stage.phase === 'ready') {
|
|
169
|
+
await this.abortTargetStage(stage);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
errors.push(error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
this.targetStages.clear();
|
|
177
|
+
this.terminalTargets.clear();
|
|
178
|
+
for (const pending of this.targetAssemblies.values()) {
|
|
179
|
+
pending.assembly.fail('Relocation runtime stopped.');
|
|
180
|
+
}
|
|
181
|
+
this.targetAssemblies.clear();
|
|
182
|
+
this.targetReadyFailures.clear();
|
|
183
|
+
this.targetReadyResponses.clear();
|
|
184
|
+
for (const window of this.sourceCutoverWindows.values()) {
|
|
185
|
+
if (window.windowTimer !== undefined)
|
|
186
|
+
clearTimeout(window.windowTimer);
|
|
187
|
+
if (window.retryTimer !== undefined)
|
|
188
|
+
clearTimeout(window.retryTimer);
|
|
189
|
+
if (window.followTimer !== undefined)
|
|
190
|
+
clearTimeout(window.followTimer);
|
|
191
|
+
}
|
|
192
|
+
this.sourceCutoverWindows.clear();
|
|
193
|
+
const stopped = new Error('Relocation runtime stopped.');
|
|
194
|
+
stopped.name = 'AbortError';
|
|
195
|
+
this.targetPrepareOperations.clear();
|
|
196
|
+
for (const pending of this.pendingReplyRelays.values()) {
|
|
197
|
+
if (pending.timer !== undefined)
|
|
198
|
+
clearTimeout(pending.timer);
|
|
199
|
+
pending.reject(stopped);
|
|
200
|
+
}
|
|
201
|
+
this.pendingReplyRelays.clear();
|
|
202
|
+
for (const pending of this.pendingSessionRelocations.values()) {
|
|
203
|
+
if (pending.retryTimer !== undefined)
|
|
204
|
+
clearTimeout(pending.retryTimer);
|
|
205
|
+
if (pending.deadlineTimer !== undefined)
|
|
206
|
+
clearTimeout(pending.deadlineTimer);
|
|
207
|
+
pending.reject(stopped);
|
|
208
|
+
}
|
|
209
|
+
this.pendingSessionRelocations.clear();
|
|
210
|
+
this.terminalSessionRelocations.clear();
|
|
211
|
+
this.failedSessionRouteSubmits.clear();
|
|
212
|
+
this.sourceActorJoinProfiles.clear();
|
|
213
|
+
this.terminalActorJoinSourceLeaves.clear();
|
|
214
|
+
this.options.boundSessionRelocation?.clear?.();
|
|
215
|
+
this.relocationAuthorityKeys.clear();
|
|
216
|
+
if (errors.length === 1)
|
|
217
|
+
throw errors[0];
|
|
218
|
+
if (errors.length > 1)
|
|
219
|
+
throw new AggregateError(errors, 'Relocation runtime stop failed.');
|
|
220
|
+
}
|
|
221
|
+
completeActorJoinSourceCleanup(actorId) {
|
|
222
|
+
for (const [relocationId, profile] of this.sourceActorJoinProfiles) {
|
|
223
|
+
if (profile.state.actorId !== actorId)
|
|
224
|
+
continue;
|
|
225
|
+
this.sourceActorJoinProfiles.delete(relocationId);
|
|
226
|
+
this.terminalActorJoinSourceLeaves.remember(relocationId, actorId);
|
|
227
|
+
void profile.ready.then(async () => {
|
|
228
|
+
await this.options.actorTransfer.completeRelocationSourceLeave(profile.actor, profile.sourceSpotId);
|
|
229
|
+
await this.options.actorManager()?.completeRelocationSource(actorId, profile.sourceActorRef);
|
|
230
|
+
}).catch(error => {
|
|
231
|
+
console.error('[zlink.runtime.relocation.source_cleanup_failed]', error);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async relocateMesh(meshName, targetApplicationVersion, signal, stopStartingSignal) {
|
|
236
|
+
const spotManager = this.options.spotManager();
|
|
237
|
+
const actorManager = this.options.actorManager();
|
|
238
|
+
// A stateless host has no relocation work. The corresponding managers are
|
|
239
|
+
// not created for that registration, and the no-op path must still allow
|
|
240
|
+
// the host relocation lifecycle to complete.
|
|
241
|
+
if (spotManager === undefined || actorManager === undefined)
|
|
242
|
+
return;
|
|
243
|
+
const groupedActorIds = new Set();
|
|
244
|
+
const work = [];
|
|
245
|
+
for (const activation of spotManager.relocationActivations(meshName)) {
|
|
246
|
+
const kind = this.spotKind(meshName, activation);
|
|
247
|
+
if (kind === undefined)
|
|
248
|
+
continue;
|
|
249
|
+
const states = actorManager.snapshotStates().filter(state => state.actor !== undefined && String(state.spotId) === String(activation.spotId));
|
|
250
|
+
for (const state of states)
|
|
251
|
+
groupedActorIds.add(state.actorId);
|
|
252
|
+
if (kind === 'user_spot'
|
|
253
|
+
&& activation.executionMode === contracts_1.ZLinkUserSpotExecutionMode.PerActor) {
|
|
254
|
+
let shellRelocation;
|
|
255
|
+
const relocateShell = (relocationSignal) => {
|
|
256
|
+
shellRelocation ??= this.relocatePerActorSpotShell(meshName, activation, undefined, targetApplicationVersion, relocationSignal);
|
|
257
|
+
return shellRelocation;
|
|
258
|
+
};
|
|
259
|
+
work.push({
|
|
260
|
+
id: `spot:${String(activation.spotId)}`,
|
|
261
|
+
relocate: relocateShell
|
|
262
|
+
});
|
|
263
|
+
for (const state of states) {
|
|
264
|
+
work.push({
|
|
265
|
+
id: `actor:${state.actorId}`,
|
|
266
|
+
relocate: async (relocationSignal) => {
|
|
267
|
+
await relocateShell(relocationSignal);
|
|
268
|
+
await this.relocateStandaloneActor(meshName, state, undefined, targetApplicationVersion, relocationSignal, {
|
|
269
|
+
spotId: activation.spotId,
|
|
270
|
+
spotKind: contracts_1.ZLinkSpotKind.User,
|
|
271
|
+
spotObjectGeneration: (await requireAuthority(this.requireLocationStore(), (0, authority_key_codec_1.encodeAuthorityKey)('user_spot', String(activation.spotId)), relocationSignal)).objectGeneration
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
work.push({
|
|
279
|
+
id: `spot:${String(activation.spotId)}`,
|
|
280
|
+
relocate: relocationSignal => this.relocateSpotAggregate(meshName, activation, kind, states, undefined, targetApplicationVersion, relocationSignal)
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
for (const state of actorManager.snapshotStates()) {
|
|
284
|
+
if (state.actor === undefined || groupedActorIds.has(state.actorId))
|
|
285
|
+
continue;
|
|
286
|
+
if ((state.meshName ?? meshName) !== meshName)
|
|
287
|
+
continue;
|
|
288
|
+
work.push({
|
|
289
|
+
id: `actor:${state.actorId}`,
|
|
290
|
+
relocate: relocationSignal => this.relocateStandaloneActor(meshName, state, undefined, targetApplicationVersion, relocationSignal)
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (work.length === 0)
|
|
294
|
+
return;
|
|
295
|
+
const maintenance = new service_maintenance_runtime_1.ServiceMaintenanceRuntime({
|
|
296
|
+
preflight: async () => true,
|
|
297
|
+
publishState: () => undefined,
|
|
298
|
+
forceStop: () => undefined
|
|
299
|
+
});
|
|
300
|
+
for (const unit of work) {
|
|
301
|
+
maintenance.enqueue({
|
|
302
|
+
id: unit.id,
|
|
303
|
+
ready: () => true,
|
|
304
|
+
relocate: unit.relocate
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
const operationSignal = signal ?? new AbortController().signal;
|
|
308
|
+
const deadlineMs = signal === undefined ? 30_000 : 24 * 60 * 60 * 1000;
|
|
309
|
+
const result = await raceAbort(maintenance.start('retire', deadlineMs, stopStartingSignal, operationSignal), operationSignal);
|
|
310
|
+
if (result.state !== 'completed') {
|
|
311
|
+
throw result.terminalError ?? new Error(`Host relocation ended in '${result.state}'.`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async tryHandleControl(meshName, record, signal) {
|
|
315
|
+
if (record.parts.length === 0)
|
|
316
|
+
return false;
|
|
317
|
+
const payload = record.parts[0].data();
|
|
318
|
+
if (isServiceWireCommand(payload, service_stateful_wire_codec_1.M6bServiceWireCommand.replyRelay)) {
|
|
319
|
+
await this.handleReplyRelay(meshName, (0, service_stateful_wire_codec_1.decodeMaintenanceReplyRelay)(record.parts.map(part => part.data())), record.sourceNodeRid, signal);
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
const sideband = record.parts.length === 1
|
|
323
|
+
? undefined
|
|
324
|
+
: decodePrepareSideband(record.parts.slice(1).map(part => part.data()));
|
|
325
|
+
if (record.parts.length > 1 && sideband === undefined)
|
|
326
|
+
return false;
|
|
327
|
+
if (record.kind === service_runtime_contracts_1.ReceiveKind.NodeSend) {
|
|
328
|
+
const sourceLeave = (0, actor_remote_wire_1.decodeRemoteActorSourceLeaveTerminal)(payload);
|
|
329
|
+
if (sourceLeave !== undefined) {
|
|
330
|
+
return this.acceptActorJoinSourceLeave(sourceLeave);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (isServiceWireCommand(payload, service_stateful_wire_codec_1.M6bServiceWireCommand.sessionRelocationSealed)) {
|
|
334
|
+
this.acceptSessionRelocationResponse((0, service_stateful_wire_codec_1.decodeSessionRelocationSealed)(payload), record.sourceNodeRid);
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
if (isServiceWireCommand(payload, service_stateful_wire_codec_1.M6bServiceWireCommand.sessionRelocationSeal)) {
|
|
338
|
+
const request = (0, service_stateful_wire_codec_1.decodeSessionRelocationSeal)(payload);
|
|
339
|
+
const response = await this.handleSessionRelocationSeal(meshName, request, record.sourceNodeRid, signal);
|
|
340
|
+
await this.sendSessionRelocationResponse(meshName, record.sourceNodeRid, (0, service_stateful_wire_codec_1.encodeSessionRelocationSealed)(response));
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
if (isServiceWireCommand(payload, service_stateful_wire_codec_1.M6bServiceWireCommand.sessionRelocationRoute)) {
|
|
344
|
+
const request = (0, service_stateful_wire_codec_1.decodeSessionRelocationRoute)(payload);
|
|
345
|
+
await this.handleSessionRelocationRoute(meshName, request, record.sourceNodeRid, signal);
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
if (isServiceWireCommand(payload, service_stateful_wire_codec_1.M6bServiceWireCommand.replyRelayAck)) {
|
|
349
|
+
await this.acceptReplyRelayAck(meshName, (0, service_stateful_wire_codec_1.decodeMaintenanceReplyRelayAck)(payload), record.sourceNodeRid, signal);
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
let request = (0, service_relocation_control_1.decodeServiceRelocationControlRequest)(payload);
|
|
353
|
+
if (request === undefined)
|
|
354
|
+
return false;
|
|
355
|
+
if (sideband !== undefined) {
|
|
356
|
+
if (request.kind !== 'prepare')
|
|
357
|
+
return false;
|
|
358
|
+
request = { ...request, nodeInternalBoundSessions: sideband };
|
|
359
|
+
}
|
|
360
|
+
// Ready(30) and Failed(53) are replies to Prepare(40), never independent
|
|
361
|
+
// inbound controls. The source consumes them on Core's request reply leg.
|
|
362
|
+
if (request.kind === 'ready' || request.kind === 'failed')
|
|
363
|
+
return false;
|
|
364
|
+
if (request.kind === 'state') {
|
|
365
|
+
this.acceptRelocationStateChunk(request, record.sourceNodeRid);
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
if (request.kind === 'prepare') {
|
|
369
|
+
if (record.sourceNodeRid === null) {
|
|
370
|
+
throw new Error('Relocation prepare has no authenticated source node.');
|
|
371
|
+
}
|
|
372
|
+
if (record.kind !== service_runtime_contracts_1.ReceiveKind.NodeRequest) {
|
|
373
|
+
throw new service_wire_m6a_codec_1.ServiceWireProtocolError('Relocation prepare requires a Core request leg.');
|
|
374
|
+
}
|
|
375
|
+
const operationKey = relocationStagingId(request);
|
|
376
|
+
const fingerprint = stringifyWire(request);
|
|
377
|
+
// A restore that failed outright is terminal: the source observes the
|
|
378
|
+
// same explicit relocationFailed on every identical Prepare resend
|
|
379
|
+
// rather than a silent stall. A different exact identity reusing the
|
|
380
|
+
// same RelocationId+attempt key supersedes the stale failure — the
|
|
381
|
+
// newer attempt wins (spec 28 §3).
|
|
382
|
+
const readyFailure = this.targetReadyFailures.get(operationKey);
|
|
383
|
+
if (readyFailure !== undefined) {
|
|
384
|
+
if (readyFailure.fingerprint === fingerprint) {
|
|
385
|
+
this.replyPrepare(record, readyFailure.response);
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
this.targetReadyFailures.delete(operationKey);
|
|
389
|
+
}
|
|
390
|
+
// Spec 28: an exact-identity Restore resend against staging that
|
|
391
|
+
// already restored successfully reuses that staging — it re-submits
|
|
392
|
+
// READY rather than restarting the restore.
|
|
393
|
+
const readyPending = this.targetReadyResponses.get(operationKey);
|
|
394
|
+
if (readyPending !== undefined) {
|
|
395
|
+
if (readyPending.fingerprint !== fingerprint) {
|
|
396
|
+
throw new Error(`Relocation '${operationKey}' repeated Prepare with different bytes.`);
|
|
397
|
+
}
|
|
398
|
+
this.submitTargetReady(meshName, operationKey, record, readyPending.response);
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
let operation = this.targetPrepareOperations.get(operationKey);
|
|
402
|
+
if (operation !== undefined && operation.fingerprint !== fingerprint) {
|
|
403
|
+
throw new Error(`Relocation '${operationKey}' repeated Prepare with different bytes.`);
|
|
404
|
+
}
|
|
405
|
+
if (operation === undefined) {
|
|
406
|
+
// The prepare turn must not block the ordered dispatch lane: the
|
|
407
|
+
// payload chunks that complete this operation arrive as later records
|
|
408
|
+
// on the same connection. Register the assembly synchronously, then
|
|
409
|
+
// finish the restore and the Ready reply asynchronously.
|
|
410
|
+
this.registerTargetAssembly(operationKey, request, String(record.sourceNodeRid));
|
|
411
|
+
const promise = this.handlePrepareControl(meshName, operationKey, request, record.sourceNodeRid, signal);
|
|
412
|
+
operation = { fingerprint, promise };
|
|
413
|
+
this.targetPrepareOperations.set(operationKey, operation);
|
|
414
|
+
void promise.finally(() => {
|
|
415
|
+
if (this.targetPrepareOperations.get(operationKey) === operation) {
|
|
416
|
+
this.targetPrepareOperations.delete(operationKey);
|
|
417
|
+
}
|
|
418
|
+
}).catch(() => undefined);
|
|
419
|
+
}
|
|
420
|
+
void this.deliverTargetReady(meshName, operationKey, request, record, operation.promise);
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
await this.handleOneWayControl(meshName, request, record.sourceNodeRid, signal);
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
/** Sends the Ready reply once the async restore finished; failures replay on resend. */
|
|
427
|
+
async deliverTargetReady(meshName, operationKey, request, ingress, operation) {
|
|
428
|
+
let response;
|
|
429
|
+
try {
|
|
430
|
+
response = await operation;
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
// The restore itself failed (chunk assembly/checksum, factory,
|
|
434
|
+
// restore or staging): this is terminal. Send relocationFailed
|
|
435
|
+
// (command 53) once and remember it so an identical Prepare resend
|
|
436
|
+
// replays the same response instead of retrying the restore.
|
|
437
|
+
const failed = relocationFailed(request, relocationFailedFailureCode(error, request.object.kind));
|
|
438
|
+
this.targetReadyFailures.set(operationKey, {
|
|
439
|
+
fingerprint: stringifyWire(request),
|
|
440
|
+
response: failed
|
|
441
|
+
});
|
|
442
|
+
// The stored failure replay is bounded by the same Restore validity
|
|
443
|
+
// window as everything else in this attempt — it is not kept forever
|
|
444
|
+
// (spec 28 §9).
|
|
445
|
+
const failureExpiry = setTimeout(() => {
|
|
446
|
+
this.targetReadyFailures.delete(operationKey);
|
|
447
|
+
}, RELOCATION_OPERATION_RETENTION_MS);
|
|
448
|
+
failureExpiry.unref();
|
|
449
|
+
console.error('[zlink.runtime.relocation.restore_failed]', operationKey, error);
|
|
450
|
+
const submitted = this.replyPrepare(ingress, failed);
|
|
451
|
+
if (submitted !== runtime_values_1.SubmitResult.Ok) {
|
|
452
|
+
console.error('[zlink.runtime.relocation.failure_reply_failed]', operationKey, error);
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
// Spec 28: staging that finished restoring successfully is retained
|
|
457
|
+
// across a Ready delivery retry — an identical Prepare resend re-submits
|
|
458
|
+
// Ready against this staging instead of the restore being redone or the
|
|
459
|
+
// attempt being failed outright.
|
|
460
|
+
this.targetReadyResponses.set(operationKey, {
|
|
461
|
+
fingerprint: stringifyWire(request),
|
|
462
|
+
response
|
|
463
|
+
});
|
|
464
|
+
// A Ready that never delivers within the Restore validity window is
|
|
465
|
+
// bounded, too: the retained staging, its reservation and hidden objects
|
|
466
|
+
// are cleaned up exactly once instead of being held indefinitely (spec
|
|
467
|
+
// 28 §9, finding F3). A successful submit removes the map entry first,
|
|
468
|
+
// so this fire is then a harmless no-op.
|
|
469
|
+
const readyExpiry = setTimeout(() => {
|
|
470
|
+
this.expireTargetReadyResponse(operationKey);
|
|
471
|
+
}, RELOCATION_OPERATION_RETENTION_MS);
|
|
472
|
+
readyExpiry.unref();
|
|
473
|
+
this.submitTargetReady(meshName, operationKey, ingress, response);
|
|
474
|
+
}
|
|
475
|
+
/** Cleans up a Ready reply that never delivered within the Restore validity window. */
|
|
476
|
+
expireTargetReadyResponse(operationKey) {
|
|
477
|
+
if (!this.targetReadyResponses.delete(operationKey))
|
|
478
|
+
return;
|
|
479
|
+
const stage = this.targetStages.get(operationKey);
|
|
480
|
+
if (stage === undefined)
|
|
481
|
+
return;
|
|
482
|
+
this.targetStages.delete(operationKey);
|
|
483
|
+
void this.abortTargetStage(stage).catch(error => {
|
|
484
|
+
console.error('[zlink.runtime.relocation.ready_expired_abort_failed]', operationKey, error);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Submits one Ready reply. A failed submit is left for the next identical
|
|
489
|
+
* Prepare resend to retry against the retained staging (spec 28) — it is
|
|
490
|
+
* not rolled back here and does not arm the cutover fallback.
|
|
491
|
+
*/
|
|
492
|
+
submitTargetReady(meshName, operationKey, ingress, response) {
|
|
493
|
+
const submitted = this.replyPrepare(ingress, response);
|
|
494
|
+
if (submitted !== runtime_values_1.SubmitResult.Ok) {
|
|
495
|
+
console.warn('[zlink.runtime.relocation.ready_submit_pending_resend]', operationKey);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
this.targetReadyResponses.delete(operationKey);
|
|
499
|
+
this.armTargetCutoverFallback(meshName, operationKey);
|
|
500
|
+
}
|
|
501
|
+
replyPrepare(ingress, response) {
|
|
502
|
+
try {
|
|
503
|
+
return ingress.reply((0, service_relocation_control_1.encodeServiceRelocationControlResponse)(response));
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
return runtime_values_1.SubmitResult.NotConnected;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
/** Copies one exact-identity payload chunk into its registered assembly. */
|
|
510
|
+
acceptRelocationStateChunk(request, sourceNodeRid) {
|
|
511
|
+
const operationKey = relocationStagingId(request);
|
|
512
|
+
const pending = this.targetAssemblies.get(operationKey);
|
|
513
|
+
if (pending === undefined
|
|
514
|
+
|| sourceNodeRid === null
|
|
515
|
+
|| String(sourceNodeRid) !== pending.authenticatedSourceNodeRid
|
|
516
|
+
|| request.senderRole !== 'source'
|
|
517
|
+
|| !sameCoordinator(request.coordinator, pending.prepare.coordinator)
|
|
518
|
+
|| stringifyWire(request.object) !== stringifyWire(pending.prepare.object)) {
|
|
519
|
+
// Spec 28 §4.3 — a chunk with a different exact identity is never
|
|
520
|
+
// attached to an in-progress assembly; it is discarded.
|
|
521
|
+
console.warn('[zlink.runtime.relocation.stale_state_chunk]', operationKey);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
let chunkData = request.chunkData;
|
|
525
|
+
if (this.relocationIntegrityFaultGate()?.consumeChecksumMismatch() === true) {
|
|
526
|
+
// Preserve the exact command identity and manifest while changing only
|
|
527
|
+
// one delivered payload byte.
|
|
528
|
+
chunkData = Buffer.from(chunkData);
|
|
529
|
+
if (chunkData.byteLength > 0)
|
|
530
|
+
chunkData[0] ^= 0x01;
|
|
531
|
+
}
|
|
532
|
+
pending.assembly.accept(request.chunkOrdinal, chunkData);
|
|
533
|
+
}
|
|
534
|
+
registerTargetAssembly(operationKey, request, authenticatedSourceNodeRid) {
|
|
535
|
+
const existing = this.targetAssemblies.get(operationKey);
|
|
536
|
+
if (existing !== undefined) {
|
|
537
|
+
if (existing.fingerprint !== stringifyWire(request)) {
|
|
538
|
+
// Same exact identity with a different declared length or checksum is
|
|
539
|
+
// an explicit conflict failure; the existing assembly is neither
|
|
540
|
+
// reused nor overwritten (spec 28 §4.3).
|
|
541
|
+
throw existing.assembly.fail(`Relocation '${operationKey}' repeated Prepare with a conflicting manifest.`);
|
|
542
|
+
}
|
|
543
|
+
return existing;
|
|
544
|
+
}
|
|
545
|
+
const pending = {
|
|
546
|
+
prepare: request,
|
|
547
|
+
fingerprint: stringifyWire(request),
|
|
548
|
+
authenticatedSourceNodeRid,
|
|
549
|
+
assembly: new relocation_direct_transfer_1.ZLinkRelocationPayloadAssembly(Number(request.payloadTotalLength), request.payloadChunkCount, request.payloadChecksumCrc32c)
|
|
550
|
+
};
|
|
551
|
+
this.targetAssemblies.set(operationKey, pending);
|
|
552
|
+
if (this.relocationIntegrityFaultGate()?.consumeIdentityConflict() === true) {
|
|
553
|
+
// Route a same-identity, different-manifest delivery through the normal
|
|
554
|
+
// conflict branch so existing staging is neither reused nor overwritten.
|
|
555
|
+
return this.registerTargetAssembly(operationKey, {
|
|
556
|
+
...request,
|
|
557
|
+
payloadChecksumCrc32c: request.payloadChecksumCrc32c ^ 1
|
|
558
|
+
}, authenticatedSourceNodeRid);
|
|
559
|
+
}
|
|
560
|
+
return pending;
|
|
561
|
+
}
|
|
562
|
+
relocationIntegrityFaultGate() {
|
|
563
|
+
try {
|
|
564
|
+
return this.options.providerResolver?.get?.(relocation_integrity_fault_gate_1.ZLINK_INTERNAL_RELOCATION_INTEGRITY_FAULT_GATE);
|
|
565
|
+
}
|
|
566
|
+
catch {
|
|
567
|
+
return undefined;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
requestSessionRelocationSeal(meshName, targetNodeRid, request, signal) {
|
|
571
|
+
return this.requestSessionRelocationSealControl(meshName, targetNodeRid, request, signal);
|
|
572
|
+
}
|
|
573
|
+
async sendSessionRelocationRoute(meshName, targetNodeRid, request, signal) {
|
|
574
|
+
// A relocation whose session owner IS this node publishes the route
|
|
575
|
+
// to itself; RouteMesh has no self connection (NotConnected), so the
|
|
576
|
+
// control dispatches through the same inbound handler locally.
|
|
577
|
+
const localRid = this.requireMeshNode(meshName).status().routingId;
|
|
578
|
+
if (String(localRid) === String(targetNodeRid)) {
|
|
579
|
+
await this.handleSessionRelocationRoute(meshName, request, localRid, signal);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
// Spec 20 §5.1 / config-10 ST-E1C: command 44 is a one-way control with
|
|
583
|
+
// no response or application ACK. Submit exactly once; a rejected
|
|
584
|
+
// submit is retained only as bounded per-attempt state. The receiving
|
|
585
|
+
// Session owns seal-timeout cleanup, after which reconnect plus explicit
|
|
586
|
+
// bind is the only recovery path -- never a second command 44 send.
|
|
587
|
+
const bytes = (0, service_stateful_wire_codec_1.encodeSessionRelocationRoute)(request);
|
|
588
|
+
signal?.throwIfAborted();
|
|
589
|
+
const submitted = await this.sendInfrastructureControl(meshName, targetNodeRid, bytes);
|
|
590
|
+
if (submitted !== runtime_values_1.SubmitResult.Ok) {
|
|
591
|
+
this.failedSessionRouteSubmits.remember(sessionRelocationRouteSubmitKey(request, targetNodeRid), submitted);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
requestSessionRelocationSealControl(meshName, targetNodeRid, request, signal) {
|
|
595
|
+
const key = sessionRelocationPendingKey(request);
|
|
596
|
+
const bytes = (0, service_stateful_wire_codec_1.encodeSessionRelocationSeal)(request);
|
|
597
|
+
const requestFingerprint = bytes.toString('base64');
|
|
598
|
+
const expectedTarget = String(targetNodeRid);
|
|
599
|
+
const terminal = this.terminalSessionRelocations.get(key);
|
|
600
|
+
if (terminal !== undefined) {
|
|
601
|
+
if (terminal.targetNodeRid !== expectedTarget
|
|
602
|
+
|| terminal.requestFingerprint !== requestFingerprint) {
|
|
603
|
+
return Promise.reject(new service_wire_m6a_codec_1.ServiceWireProtocolError(`Session relocation control '${key}' repeated with different bytes or target.`));
|
|
604
|
+
}
|
|
605
|
+
this.terminalSessionRelocations.touch(key);
|
|
606
|
+
}
|
|
607
|
+
const existing = this.pendingSessionRelocations.get(key);
|
|
608
|
+
if (existing !== undefined) {
|
|
609
|
+
if (existing.targetNodeRid !== expectedTarget
|
|
610
|
+
|| existing.requestFingerprint !== requestFingerprint) {
|
|
611
|
+
return Promise.reject(new service_wire_m6a_codec_1.ServiceWireProtocolError(`Session relocation control '${key}' repeated with different bytes or target.`));
|
|
612
|
+
}
|
|
613
|
+
return existing.promise;
|
|
614
|
+
}
|
|
615
|
+
let resolvePromise;
|
|
616
|
+
let rejectPromise;
|
|
617
|
+
const promise = new Promise((resolve, reject) => {
|
|
618
|
+
resolvePromise = resolve;
|
|
619
|
+
rejectPromise = reject;
|
|
620
|
+
});
|
|
621
|
+
let abortListener;
|
|
622
|
+
let settled = false;
|
|
623
|
+
const finish = (action) => {
|
|
624
|
+
if (settled)
|
|
625
|
+
return;
|
|
626
|
+
settled = true;
|
|
627
|
+
const current = this.pendingSessionRelocations.get(key);
|
|
628
|
+
if (current === pending) {
|
|
629
|
+
if (current.retryTimer !== undefined)
|
|
630
|
+
clearTimeout(current.retryTimer);
|
|
631
|
+
if (current.deadlineTimer !== undefined)
|
|
632
|
+
clearTimeout(current.deadlineTimer);
|
|
633
|
+
this.pendingSessionRelocations.delete(key);
|
|
634
|
+
}
|
|
635
|
+
if (abortListener !== undefined) {
|
|
636
|
+
signal?.removeEventListener('abort', abortListener);
|
|
637
|
+
}
|
|
638
|
+
action();
|
|
639
|
+
};
|
|
640
|
+
let pending;
|
|
641
|
+
// Spec 48:205/20:408 — the seal control keeps the operation's deadline
|
|
642
|
+
// and cancellation contract; an unbounded identical-bytes resend loop
|
|
643
|
+
// turned a lost ACK into a permanent relocation stall (observed ~30s
|
|
644
|
+
// admission interruption until teardown). Resends stay within the
|
|
645
|
+
// relocation seal window, then the join terminates DeadlineExceeded
|
|
646
|
+
// (spec 32:88).
|
|
647
|
+
// Spec 06 — the configured SessionRelocationSealTimeout is an additional
|
|
648
|
+
// finite upper bound; the caller's earlier absolute deadline still wins.
|
|
649
|
+
const registration = Object.hasOwn(this.options, 'registration')
|
|
650
|
+
? this.options.registration
|
|
651
|
+
: undefined;
|
|
652
|
+
const sealTimeoutMs = registration?.locations.options
|
|
653
|
+
.sessionRelocationSealTimeoutMs
|
|
654
|
+
?? Options_1.zlinkRuntimeDefaultLocationOptions.sessionRelocationSealTimeoutMs;
|
|
655
|
+
const resendDeadline = performance.now() + sealTimeoutMs;
|
|
656
|
+
const send = async () => {
|
|
657
|
+
if (signal?.aborted === true) {
|
|
658
|
+
finish(() => rejectPromise(signal.reason));
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
// A seal whose session owner IS this node has no RouteMesh self
|
|
662
|
+
// connection (NotConnected); dispatch through the inbound handler
|
|
663
|
+
// locally instead of the wire.
|
|
664
|
+
const localRid = this.requireMeshNode(meshName).status().routingId;
|
|
665
|
+
if (String(localRid) === expectedTarget) {
|
|
666
|
+
try {
|
|
667
|
+
const response = await this.handleSessionRelocationSeal(meshName, request, localRid, signal);
|
|
668
|
+
pending.resolve(response);
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
pending.reject(error);
|
|
672
|
+
}
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (performance.now() >= resendDeadline) {
|
|
676
|
+
pending.reject((0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.DeadlineExceeded, `Session relocation seal '${key}' was not acknowledged within the relocation seal timeout.`, true));
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
try {
|
|
680
|
+
const submitted = await this.sendInfrastructureControl(meshName, targetNodeRid, bytes);
|
|
681
|
+
if (submitted !== runtime_values_1.SubmitResult.Ok) {
|
|
682
|
+
pending.reject((0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RelocationTargetUnavailable, `Session relocation seal '${key}' was not accepted by RouteMesh.`, true));
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
if (this.pendingSessionRelocations.get(key) === pending) {
|
|
686
|
+
pending.retryTimer = setTimeout(() => void send(), 250);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
catch (error) {
|
|
690
|
+
pending.reject((0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RelocationTargetUnavailable, `Session relocation seal '${key}' could not reach its RouteMesh owner.`, true, error));
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
pending = {
|
|
694
|
+
targetNodeRid: expectedTarget,
|
|
695
|
+
request,
|
|
696
|
+
requestFingerprint,
|
|
697
|
+
promise,
|
|
698
|
+
resolve: response => finish(() => {
|
|
699
|
+
this.terminalSessionRelocations.remember(key, {
|
|
700
|
+
targetNodeRid: expectedTarget,
|
|
701
|
+
request,
|
|
702
|
+
requestFingerprint,
|
|
703
|
+
responseFingerprint: sessionRelocationResponseFingerprint(response)
|
|
704
|
+
});
|
|
705
|
+
resolvePromise(response);
|
|
706
|
+
}),
|
|
707
|
+
reject: error => finish(() => rejectPromise(error))
|
|
708
|
+
};
|
|
709
|
+
this.pendingSessionRelocations.set(key, pending);
|
|
710
|
+
pending.deadlineTimer = setTimeout(() => pending.reject((0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.DeadlineExceeded, `Session relocation seal '${key}' was not acknowledged within the relocation seal timeout.`, true)), Math.max(0, resendDeadline - performance.now()));
|
|
711
|
+
pending.deadlineTimer.unref();
|
|
712
|
+
abortListener = () => pending.reject(signal?.reason ?? (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.DeadlineExceeded, `Session relocation seal '${key}' was cancelled.`, true));
|
|
713
|
+
if (signal?.aborted === true) {
|
|
714
|
+
abortListener();
|
|
715
|
+
return promise;
|
|
716
|
+
}
|
|
717
|
+
signal?.addEventListener('abort', abortListener, { once: true });
|
|
718
|
+
void send();
|
|
719
|
+
return promise;
|
|
720
|
+
}
|
|
721
|
+
acceptSessionRelocationResponse(response, sourceNodeRid) {
|
|
722
|
+
const key = sessionRelocationResponseKey(response);
|
|
723
|
+
const pending = this.pendingSessionRelocations.get(key);
|
|
724
|
+
if (pending === undefined) {
|
|
725
|
+
const terminal = this.terminalSessionRelocations.get(key);
|
|
726
|
+
if (terminal === undefined)
|
|
727
|
+
return;
|
|
728
|
+
if (sourceNodeRid === null || String(sourceNodeRid) !== terminal.targetNodeRid) {
|
|
729
|
+
throw new service_wire_m6a_codec_1.ServiceWireProtocolError(`Session relocation ACK '${key}' source node changed.`);
|
|
730
|
+
}
|
|
731
|
+
validateSessionRelocationResponse(terminal.request, response);
|
|
732
|
+
if (sessionRelocationResponseFingerprint(response) !== terminal.responseFingerprint) {
|
|
733
|
+
throw new service_wire_m6a_codec_1.ServiceWireProtocolError(`Session relocation ACK '${key}' repeated with different bytes.`);
|
|
734
|
+
}
|
|
735
|
+
this.terminalSessionRelocations.touch(key);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (sourceNodeRid === null || String(sourceNodeRid) !== pending.targetNodeRid) {
|
|
739
|
+
pending.reject(new service_wire_m6a_codec_1.ServiceWireProtocolError(`Session relocation ACK '${key}' source node changed.`));
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
try {
|
|
743
|
+
validateSessionRelocationResponse(pending.request, response);
|
|
744
|
+
pending.resolve(response);
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
pending.reject(error);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
// A relocation control dispatched locally names THIS node as its
|
|
751
|
+
// source; the peers() view never contains the local node, so its
|
|
752
|
+
// lifecycle generation comes from the node status instead.
|
|
753
|
+
sourceLifecycleGeneration(meshName, sourceNodeRid) {
|
|
754
|
+
if (sourceNodeRid === null)
|
|
755
|
+
return undefined;
|
|
756
|
+
const node = this.requireMeshNode(meshName);
|
|
757
|
+
const status = node.status();
|
|
758
|
+
if (String(status.routingId) === String(sourceNodeRid)) {
|
|
759
|
+
return status.lifecycleGeneration;
|
|
760
|
+
}
|
|
761
|
+
return node.peers().find(peer => peer.routingId !== null
|
|
762
|
+
&& String(peer.routingId) === String(sourceNodeRid))?.lifecycleGeneration;
|
|
763
|
+
}
|
|
764
|
+
async handleSessionRelocationSeal(meshName, request, sourceNodeRid, signal) {
|
|
765
|
+
if (sourceNodeRid === null
|
|
766
|
+
|| String(sourceNodeRid) !== request.coordinator.nodeRid
|
|
767
|
+
|| String(sourceNodeRid) !== request.actor.actor.nodeRid) {
|
|
768
|
+
throw new service_wire_m6a_codec_1.ServiceWireProtocolError('Session relocation seal source does not match its coordinator and Actor fence.');
|
|
769
|
+
}
|
|
770
|
+
this.validateSessionOwnerFence(meshName, request.session);
|
|
771
|
+
const sealSourceGeneration = this.sourceLifecycleGeneration(meshName, sourceNodeRid);
|
|
772
|
+
if (sealSourceGeneration !== request.actor.targetNodeGeneration
|
|
773
|
+
|| sealSourceGeneration !== request.coordinator.nodeGeneration) {
|
|
774
|
+
throw new service_wire_m6a_codec_1.ServiceWireProtocolError('Session relocation seal source lifecycle generation is stale.');
|
|
775
|
+
}
|
|
776
|
+
const handler = this.options.boundSessionRelocation?.receiveSeal;
|
|
777
|
+
if (handler === undefined) {
|
|
778
|
+
throw new Error('Session relocation seal ingress is not configured.');
|
|
779
|
+
}
|
|
780
|
+
return await handler(request, signal);
|
|
781
|
+
}
|
|
782
|
+
async handleSessionRelocationRoute(meshName, request, sourceNodeRid, _signal) {
|
|
783
|
+
this.validateSessionOwnerFence(meshName, request.session);
|
|
784
|
+
const expectedSource = request.route.action === 'commit'
|
|
785
|
+
? request.route.targetNodeRid
|
|
786
|
+
: request.coordinator.nodeRid;
|
|
787
|
+
if (sourceNodeRid === null || String(sourceNodeRid) !== expectedSource) {
|
|
788
|
+
throw new service_wire_m6a_codec_1.ServiceWireProtocolError('Session relocation route source does not match its action fence.');
|
|
789
|
+
}
|
|
790
|
+
const routeSourceGeneration = this.sourceLifecycleGeneration(meshName, sourceNodeRid);
|
|
791
|
+
const expectedSourceGeneration = request.route.action === 'commit'
|
|
792
|
+
? request.route.targetNodeGeneration
|
|
793
|
+
: request.coordinator.nodeGeneration;
|
|
794
|
+
if (routeSourceGeneration !== expectedSourceGeneration) {
|
|
795
|
+
throw new service_wire_m6a_codec_1.ServiceWireProtocolError('Session relocation route source lifecycle generation is stale.');
|
|
796
|
+
}
|
|
797
|
+
const handler = this.options.boundSessionRelocation?.receiveRoute;
|
|
798
|
+
if (handler === undefined) {
|
|
799
|
+
throw new Error('Session relocation route ingress is not configured.');
|
|
800
|
+
}
|
|
801
|
+
await handler(request);
|
|
802
|
+
}
|
|
803
|
+
validateSessionOwnerFence(meshName, session) {
|
|
804
|
+
const status = this.requireMeshNode(meshName).status();
|
|
805
|
+
const owner = this.options.currentOwner();
|
|
806
|
+
if (String(status.routingId) !== session.sessionOwnerNodeRid
|
|
807
|
+
|| status.lifecycleGeneration !== session.sessionOwnerNodeGeneration
|
|
808
|
+
|| owner?.ownerId !== session.sessionOwnerId
|
|
809
|
+
|| owner.leaseGeneration !== session.sessionOwnerLeaseGeneration) {
|
|
810
|
+
throw new service_wire_m6a_codec_1.ServiceWireProtocolError('Session relocation command does not match the local Session owner fence.');
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
async sendSessionRelocationResponse(meshName, targetNodeRid, bytes) {
|
|
814
|
+
if (targetNodeRid === null) {
|
|
815
|
+
throw new service_wire_m6a_codec_1.ServiceWireProtocolError('Session relocation command has no authenticated source.');
|
|
816
|
+
}
|
|
817
|
+
const submitted = await this.sendInfrastructureControl(meshName, targetNodeRid, bytes);
|
|
818
|
+
if (submitted !== runtime_values_1.SubmitResult.Ok) {
|
|
819
|
+
throw new Error('Session relocation ACK was not accepted by RouteMesh.');
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
async relocateSpotAggregate(meshName, activation, kind, actorStates, target, targetApplicationVersion, signal) {
|
|
823
|
+
const store = this.requireLocationStore();
|
|
824
|
+
const spotKey = (0, authority_key_codec_1.encodeAuthorityKey)(kind, String(activation.spotId));
|
|
825
|
+
const spotAuthority = await requireAuthority(store, spotKey, signal);
|
|
826
|
+
const actorAuthorities = new Map();
|
|
827
|
+
for (const state of actorStates) {
|
|
828
|
+
actorAuthorities.set(state.actorId, await requireAuthority(store, (0, authority_key_codec_1.encodeAuthorityKey)('actor', state.actorId), signal));
|
|
829
|
+
}
|
|
830
|
+
const sessions = [];
|
|
831
|
+
let spotCapture;
|
|
832
|
+
let spotMessageFollowSeal;
|
|
833
|
+
let spotSealCommitted = false;
|
|
834
|
+
let readinessBoundaryConsumed = false;
|
|
835
|
+
let interruptionStartedAt;
|
|
836
|
+
const spotRegistration = this.spotRegistration(meshName, kind, spotAuthority.allocation.stableType);
|
|
837
|
+
target ??= await this.selectTarget(meshName, String(this.requireMeshNode(meshName).status().routingId), targetApplicationVersion, relocationTargetRequirements([
|
|
838
|
+
{
|
|
839
|
+
objectKind: kind,
|
|
840
|
+
stableType: spotAuthority.allocation.stableType,
|
|
841
|
+
policy: spotRegistration.relocation.kind,
|
|
842
|
+
count: 1
|
|
843
|
+
},
|
|
844
|
+
...actorStates.map(state => {
|
|
845
|
+
const actorAuthority = actorAuthorities.get(state.actorId);
|
|
846
|
+
return {
|
|
847
|
+
objectKind: 'actor',
|
|
848
|
+
stableType: actorAuthority.allocation.stableType,
|
|
849
|
+
policy: this.actorRegistration(state.meshName ?? meshName, actorAuthority.allocation.stableType).relocation.kind,
|
|
850
|
+
count: 1
|
|
851
|
+
};
|
|
852
|
+
})
|
|
853
|
+
]), signal);
|
|
854
|
+
const aggregateId = this.reserveRelocationId();
|
|
855
|
+
const spotUnit = {
|
|
856
|
+
authorityKey: spotKey.value,
|
|
857
|
+
objectKind: kind,
|
|
858
|
+
stableType: spotAuthority.allocation.stableType,
|
|
859
|
+
objectGeneration: spotAuthority.objectGeneration,
|
|
860
|
+
authorityOwnerGeneration: spotAuthority.authorityOwnerGeneration,
|
|
861
|
+
seal: async (captureSignal) => {
|
|
862
|
+
interruptionStartedAt = performance.now();
|
|
863
|
+
spotMessageFollowSeal = this.sealSpotMessageFollow(meshName, spotAuthority, activation);
|
|
864
|
+
// captureRelocation seals synchronously before its first await. Invoke
|
|
865
|
+
// it in the same event-loop turn as the wire ingress seal so no
|
|
866
|
+
// accepted direct message can enter between the two boundaries.
|
|
867
|
+
const spotCaptureOperation = activation.captureRelocation(captureSignal);
|
|
868
|
+
const preparedSessions = Promise.all(actorStates.map(async (state) => ({
|
|
869
|
+
state,
|
|
870
|
+
actor: state.actor,
|
|
871
|
+
prepared: await this.options.actorTransfer.prepareMaintenanceSession(state.actor, state, captureSignal, false, relocationWireId(aggregateId))
|
|
872
|
+
})));
|
|
873
|
+
const preparedSessionsOutcome = preparedSessions.then(value => ({ value }), error => ({ error }));
|
|
874
|
+
spotCapture = await spotCaptureOperation;
|
|
875
|
+
const preparation = await preparedSessionsOutcome;
|
|
876
|
+
if ('error' in preparation)
|
|
877
|
+
throw preparation.error;
|
|
878
|
+
sessions.push(...preparation.value);
|
|
879
|
+
return {
|
|
880
|
+
boundSessionState: Buffer.alloc(0),
|
|
881
|
+
queuedMessages: [],
|
|
882
|
+
timers: spotCapture.timers.map(toServiceTimer)
|
|
883
|
+
};
|
|
884
|
+
},
|
|
885
|
+
captureApplicationState: captureSignal => this.captureApplication(spotRegistration.relocation, activation.spot, captureSignal),
|
|
886
|
+
commitSeal: async () => {
|
|
887
|
+
if (!spotSealCommitted) {
|
|
888
|
+
if (spotCapture === undefined || !await activation.commitRelocation(spotCapture)) {
|
|
889
|
+
throw new Error(`Spot '${String(activation.spotId)}' relocation seal became stale.`);
|
|
890
|
+
}
|
|
891
|
+
spotSealCommitted = true;
|
|
892
|
+
}
|
|
893
|
+
if (spotMessageFollowSeal !== undefined) {
|
|
894
|
+
await this.commitSpotMessageFollow(meshName, spotMessageFollowSeal, spotKey, activation, target);
|
|
895
|
+
}
|
|
896
|
+
for (const session of sessions) {
|
|
897
|
+
const committedAuthority = await requireAuthority(this.requireLocationStore(), (0, authority_key_codec_1.encodeAuthorityKey)('actor', session.state.actorId));
|
|
898
|
+
const targetActorRef = {
|
|
899
|
+
actorId: session.state.actorId,
|
|
900
|
+
objectGeneration: actorAuthorities.get(session.state.actorId).objectGeneration,
|
|
901
|
+
meshName,
|
|
902
|
+
nodeRid: target.rid
|
|
903
|
+
};
|
|
904
|
+
await session.prepared.commit({
|
|
905
|
+
routerChannelId: meshName,
|
|
906
|
+
targetNodeRid: target.rid,
|
|
907
|
+
spotId: activation.spotId,
|
|
908
|
+
spotKind: kind === 'user_spot'
|
|
909
|
+
? contracts_1.ZLinkSpotKind.User
|
|
910
|
+
: contracts_1.ZLinkSpotKind.Instance,
|
|
911
|
+
authorityOwnerGeneration: committedAuthority.authorityOwnerGeneration
|
|
912
|
+
}, targetActorRef, (0, actor_transfer_runtime_1.committedActorOwnerFence)(session.state.actorId, targetActorRef, committedAuthority));
|
|
913
|
+
activation.commitActorDeparture(session.state.actorId);
|
|
914
|
+
await this.requireActorManager().completeRelocationSource(session.state.actorId);
|
|
915
|
+
}
|
|
916
|
+
await this.requireSpotManager().completeRelocationSource(activation);
|
|
917
|
+
},
|
|
918
|
+
abortSeal: async () => {
|
|
919
|
+
const failures = [];
|
|
920
|
+
if (spotCapture !== undefined) {
|
|
921
|
+
try {
|
|
922
|
+
activation.abortRelocation(spotCapture);
|
|
923
|
+
}
|
|
924
|
+
catch (error) {
|
|
925
|
+
failures.push(error);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (spotMessageFollowSeal !== undefined) {
|
|
929
|
+
try {
|
|
930
|
+
const node = this.requireMeshNode(meshName);
|
|
931
|
+
const abort = node.abortSpotMessageFollowIngress;
|
|
932
|
+
if (abort === undefined || !abort.call(node, spotMessageFollowSeal)) {
|
|
933
|
+
throw new Error('Spot Message Follow abort fence became stale.');
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
catch (error) {
|
|
937
|
+
failures.push(error);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
for (const session of [...sessions].reverse()) {
|
|
941
|
+
try {
|
|
942
|
+
await session.prepared.rollback();
|
|
943
|
+
}
|
|
944
|
+
catch (error) {
|
|
945
|
+
failures.push(error);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
if (failures.length !== 0) {
|
|
949
|
+
throw new AggregateError(failures, 'Spot relocation source rollback was incomplete.');
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
const actorUnits = actorStates.map(state => this.actorCaptureUnit(state, actorAuthorities.get(state.actorId), sessions, false, undefined, undefined, undefined, undefined, false));
|
|
954
|
+
const memberships = actorStates.map(state => ({
|
|
955
|
+
actorKey: (0, authority_key_codec_1.encodeAuthorityKey)('actor', state.actorId).value,
|
|
956
|
+
spotKey: spotKey.value,
|
|
957
|
+
spotObjectGeneration: spotAuthority.objectGeneration,
|
|
958
|
+
membershipEpoch: state.spotMembershipEpoch > 0n ? state.spotMembershipEpoch : 1n
|
|
959
|
+
}));
|
|
960
|
+
let outcome = 'completed';
|
|
961
|
+
try {
|
|
962
|
+
const waitForRelocationBoundary = activation.waitForRelocationBoundary;
|
|
963
|
+
readinessBoundaryConsumed = waitForRelocationBoundary === undefined
|
|
964
|
+
? false
|
|
965
|
+
: await waitForRelocationBoundary.call(activation, signal);
|
|
966
|
+
// A full in-flight payload budget delays this unit before its seal, so
|
|
967
|
+
// the Spot and its Actors keep processing messages while waiting.
|
|
968
|
+
await this.waitForRelocationBudgetHeadroom(String(target.rid), signal);
|
|
969
|
+
const captureOwner = new service_relocation_object_owner_1.ServiceRelocationObjectCaptureOwner();
|
|
970
|
+
const captured = kind === 'user_spot'
|
|
971
|
+
? await captureOwner.captureUserSpotAggregate(aggregateId, 1n, spotUnit, actorUnits, memberships, signal)
|
|
972
|
+
: await captureOwner.captureInstanceSpotAggregate(aggregateId, 1n, spotUnit, actorUnits, memberships, signal);
|
|
973
|
+
await this.runCoordinator(meshName, target, spotAuthority, captured, new Map([[spotKey.value, spotAuthority], ...actorStates.map(state => [
|
|
974
|
+
(0, authority_key_codec_1.encodeAuthorityKey)('actor', state.actorId).value,
|
|
975
|
+
actorAuthorities.get(state.actorId)
|
|
976
|
+
])]), signal, sessions);
|
|
977
|
+
}
|
|
978
|
+
catch (error) {
|
|
979
|
+
outcome = 'failed';
|
|
980
|
+
if (readinessBoundaryConsumed
|
|
981
|
+
&& !(error instanceof service_relocation_coordinator_1.ServiceRelocationPostCommitError)) {
|
|
982
|
+
await activation.completeConsumedRelocationBoundary(contracts_1.ZLinkSpotRelocationReadyOutcome.Continued).catch(() => undefined);
|
|
983
|
+
}
|
|
984
|
+
throw error;
|
|
985
|
+
}
|
|
986
|
+
finally {
|
|
987
|
+
this.sourceRelocationIds.delete(aggregateId);
|
|
988
|
+
if (interruptionStartedAt !== undefined) {
|
|
989
|
+
this.recordRelocationInterruption(meshName, kind, kind === 'user_spot' ? 'spot_wide' : undefined, interruptionStartedAt, outcome);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
async relocateStandaloneActor(meshName, state, target, targetApplicationVersion, signal, targetMembership, aggregateIdOverride, retainSourceForActorJoin = false,
|
|
994
|
+
/** Target's advertised relocation state chunk cap (Actor Join only; spec 15 §4.2). */
|
|
995
|
+
advertisedReceiveChunkLimitBytes) {
|
|
996
|
+
relocationDebug('standalone_actor.begin', { actorId: state.actorId });
|
|
997
|
+
const authority = await requireAuthority(this.requireLocationStore(), (0, authority_key_codec_1.encodeAuthorityKey)('actor', state.actorId), signal);
|
|
998
|
+
target ??= await this.selectTarget(meshName, String(this.requireMeshNode(meshName).status().routingId), targetApplicationVersion, [{
|
|
999
|
+
objectKind: 'actor',
|
|
1000
|
+
stableType: authority.allocation.stableType,
|
|
1001
|
+
policy: this.actorRegistration(state.meshName ?? meshName, authority.allocation.stableType).relocation.kind,
|
|
1002
|
+
count: 1
|
|
1003
|
+
}], signal);
|
|
1004
|
+
const sessions = [];
|
|
1005
|
+
const membershipSpotId = targetMembership?.spotId
|
|
1006
|
+
?? (target.entrySpotId ?? String(target.rid));
|
|
1007
|
+
const aggregateId = aggregateIdOverride ?? this.reserveRelocationId();
|
|
1008
|
+
const unit = this.actorCaptureUnit(state, authority, sessions, true, target, meshName, {
|
|
1009
|
+
spotId: membershipSpotId,
|
|
1010
|
+
spotKind: targetMembership?.spotKind ?? contracts_1.ZLinkSpotKind.Entry
|
|
1011
|
+
}, relocationWireId(aggregateId), retainSourceForActorJoin);
|
|
1012
|
+
const membership = {
|
|
1013
|
+
actorKey: (0, authority_key_codec_1.encodeAuthorityKey)('actor', state.actorId).value,
|
|
1014
|
+
spotKey: (0, authority_key_codec_1.encodeAuthorityKey)('user_spot', String(membershipSpotId)).value,
|
|
1015
|
+
spotObjectGeneration: targetMembership?.spotObjectGeneration ?? target.lifecycleGeneration,
|
|
1016
|
+
membershipEpoch: state.spotMembershipEpoch > 0n ? state.spotMembershipEpoch : 1n
|
|
1017
|
+
};
|
|
1018
|
+
let interruptionStartedAt;
|
|
1019
|
+
const measuredUnit = {
|
|
1020
|
+
...unit,
|
|
1021
|
+
seal: async (captureSignal) => {
|
|
1022
|
+
interruptionStartedAt = performance.now();
|
|
1023
|
+
return unit.seal(captureSignal);
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
let outcome = 'completed';
|
|
1027
|
+
try {
|
|
1028
|
+
relocationDebug('standalone_actor.wait_budget', { actorId: state.actorId, targetRid: String(target.rid) });
|
|
1029
|
+
await this.waitForRelocationBudgetHeadroom(String(target.rid), signal);
|
|
1030
|
+
relocationDebug('standalone_actor.capture_begin', { actorId: state.actorId });
|
|
1031
|
+
const captured = await new service_relocation_object_owner_1.ServiceRelocationObjectCaptureOwner().captureStandaloneActor(aggregateId, 1n, measuredUnit, membership, signal);
|
|
1032
|
+
relocationDebug('standalone_actor.capture_complete', { actorId: state.actorId });
|
|
1033
|
+
await this.runCoordinator(meshName, target, authority, captured, new Map([[measuredUnit.authorityKey, authority]]), signal, sessions, advertisedReceiveChunkLimitBytes);
|
|
1034
|
+
}
|
|
1035
|
+
catch (error) {
|
|
1036
|
+
outcome = 'failed';
|
|
1037
|
+
throw error;
|
|
1038
|
+
}
|
|
1039
|
+
finally {
|
|
1040
|
+
this.sourceRelocationIds.delete(aggregateId);
|
|
1041
|
+
if (interruptionStartedAt !== undefined) {
|
|
1042
|
+
this.recordRelocationInterruption(meshName, 'actor', undefined, interruptionStartedAt, outcome);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
async relocatePerActorSpotShell(meshName, activation, target, targetApplicationVersion, signal) {
|
|
1047
|
+
const spotKey = (0, authority_key_codec_1.encodeAuthorityKey)('user_spot', String(activation.spotId));
|
|
1048
|
+
const spotAuthority = await requireAuthority(this.requireLocationStore(), spotKey, signal);
|
|
1049
|
+
const spotRegistration = this.spotRegistration(meshName, 'user_spot', spotAuthority.allocation.stableType);
|
|
1050
|
+
target ??= await this.selectTarget(meshName, String(this.requireMeshNode(meshName).status().routingId), targetApplicationVersion, [{
|
|
1051
|
+
objectKind: 'user_spot',
|
|
1052
|
+
stableType: spotAuthority.allocation.stableType,
|
|
1053
|
+
policy: spotRegistration.relocation.kind,
|
|
1054
|
+
count: 1
|
|
1055
|
+
}], signal);
|
|
1056
|
+
let spotCapture;
|
|
1057
|
+
let spotMessageFollowSeal;
|
|
1058
|
+
let sealCommitted = false;
|
|
1059
|
+
let interruptionStartedAt;
|
|
1060
|
+
const spotUnit = {
|
|
1061
|
+
authorityKey: spotKey.value,
|
|
1062
|
+
objectKind: 'user_spot',
|
|
1063
|
+
stableType: spotAuthority.allocation.stableType,
|
|
1064
|
+
objectGeneration: spotAuthority.objectGeneration,
|
|
1065
|
+
authorityOwnerGeneration: spotAuthority.authorityOwnerGeneration,
|
|
1066
|
+
seal: async (captureSignal) => {
|
|
1067
|
+
interruptionStartedAt = performance.now();
|
|
1068
|
+
spotMessageFollowSeal = this.sealSpotMessageFollow(meshName, spotAuthority, activation);
|
|
1069
|
+
spotCapture = await activation.captureRelocation(captureSignal);
|
|
1070
|
+
return {
|
|
1071
|
+
boundSessionState: Buffer.alloc(0),
|
|
1072
|
+
queuedMessages: [],
|
|
1073
|
+
timers: spotCapture.timers.map(toServiceTimer)
|
|
1074
|
+
};
|
|
1075
|
+
},
|
|
1076
|
+
// PerActor User Spots are stateless shells. Application state and the
|
|
1077
|
+
// Spot relocation adapter are intentionally not part of this unit.
|
|
1078
|
+
captureApplicationState: async () => Buffer.alloc(0),
|
|
1079
|
+
commitSeal: async () => {
|
|
1080
|
+
if (!sealCommitted) {
|
|
1081
|
+
if (spotCapture === undefined || !await activation.commitRelocation(spotCapture)) {
|
|
1082
|
+
throw new Error(`PerActor Spot '${String(activation.spotId)}' relocation seal became stale.`);
|
|
1083
|
+
}
|
|
1084
|
+
sealCommitted = true;
|
|
1085
|
+
}
|
|
1086
|
+
if (spotMessageFollowSeal !== undefined) {
|
|
1087
|
+
await this.commitSpotMessageFollow(meshName, spotMessageFollowSeal, spotKey, activation, target);
|
|
1088
|
+
}
|
|
1089
|
+
await this.requireSpotManager().completeRelocationSource(activation);
|
|
1090
|
+
},
|
|
1091
|
+
abortSeal: async () => {
|
|
1092
|
+
if (spotCapture !== undefined)
|
|
1093
|
+
activation.abortRelocation(spotCapture);
|
|
1094
|
+
if (spotMessageFollowSeal !== undefined) {
|
|
1095
|
+
const node = this.requireMeshNode(meshName);
|
|
1096
|
+
const abort = node.abortSpotMessageFollowIngress;
|
|
1097
|
+
if (abort === undefined || !abort.call(node, spotMessageFollowSeal)) {
|
|
1098
|
+
throw new Error('Spot Message Follow abort fence became stale.');
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
const aggregateId = this.reserveRelocationId();
|
|
1104
|
+
let outcome = 'completed';
|
|
1105
|
+
try {
|
|
1106
|
+
await this.waitForRelocationBudgetHeadroom(String(target.rid), signal);
|
|
1107
|
+
const captured = await new service_relocation_object_owner_1.ServiceRelocationObjectCaptureOwner().captureUserSpotAggregate(aggregateId, 1n, spotUnit, [], [], signal);
|
|
1108
|
+
await this.runCoordinator(meshName, target, spotAuthority, captured, new Map([[spotKey.value, spotAuthority]]), signal, []);
|
|
1109
|
+
}
|
|
1110
|
+
catch (error) {
|
|
1111
|
+
outcome = 'failed';
|
|
1112
|
+
throw error;
|
|
1113
|
+
}
|
|
1114
|
+
finally {
|
|
1115
|
+
this.sourceRelocationIds.delete(aggregateId);
|
|
1116
|
+
if (interruptionStartedAt !== undefined) {
|
|
1117
|
+
this.recordRelocationInterruption(meshName, 'user_spot', 'per_actor', interruptionStartedAt, outcome);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
actorCaptureUnit(state, authority, sessions, standalone = false, target, meshName, targetMembership, sessionRelocation, retainSourceForActorJoin = false) {
|
|
1122
|
+
const registration = this.actorRegistration(state.meshName ?? meshName ?? '', state.actorType ?? authority.allocation.stableType);
|
|
1123
|
+
let ownSession;
|
|
1124
|
+
return {
|
|
1125
|
+
authorityKey: (0, authority_key_codec_1.encodeAuthorityKey)('actor', state.actorId).value,
|
|
1126
|
+
objectKind: 'actor',
|
|
1127
|
+
stableType: authority.allocation.stableType,
|
|
1128
|
+
objectGeneration: authority.objectGeneration,
|
|
1129
|
+
authorityOwnerGeneration: authority.authorityOwnerGeneration,
|
|
1130
|
+
seal: async (signal) => {
|
|
1131
|
+
if (standalone) {
|
|
1132
|
+
ownSession = {
|
|
1133
|
+
state,
|
|
1134
|
+
actor: state.actor,
|
|
1135
|
+
prepared: await this.options.actorTransfer.prepareMaintenanceSession(state.actor, state, signal, true, sessionRelocation)
|
|
1136
|
+
};
|
|
1137
|
+
sessions.push(ownSession);
|
|
1138
|
+
}
|
|
1139
|
+
const prepared = standalone ? ownSession : sessions.find(value => value.state === state);
|
|
1140
|
+
return {
|
|
1141
|
+
boundSessionState: encodeActorSession(prepared?.prepared.target),
|
|
1142
|
+
queuedMessages: encodeHandoffQueuedMessages(prepared?.prepared.handoffBacklog ?? []),
|
|
1143
|
+
timers: []
|
|
1144
|
+
};
|
|
1145
|
+
},
|
|
1146
|
+
// The canonical participant body (discriminator 1) is raw application
|
|
1147
|
+
// state. The sealed bound-session target remains in sealed work and is
|
|
1148
|
+
// transported only by Prepare's NODE-INTERNAL sideband frames.
|
|
1149
|
+
captureApplicationState: signal => this.captureApplication(registration.relocation, state.actor, signal),
|
|
1150
|
+
commitSeal: async () => {
|
|
1151
|
+
if (!standalone || ownSession === undefined || target === undefined || meshName === undefined)
|
|
1152
|
+
return;
|
|
1153
|
+
const membershipSpotId = targetMembership?.spotId
|
|
1154
|
+
?? (target.entrySpotId ?? String(target.rid));
|
|
1155
|
+
const committedAuthority = await requireAuthority(this.requireLocationStore(), (0, authority_key_codec_1.encodeAuthorityKey)('actor', state.actorId));
|
|
1156
|
+
const targetActorRef = {
|
|
1157
|
+
actorId: state.actorId,
|
|
1158
|
+
objectGeneration: authority.objectGeneration,
|
|
1159
|
+
meshName,
|
|
1160
|
+
nodeRid: target.rid
|
|
1161
|
+
};
|
|
1162
|
+
await ownSession.prepared.commit({
|
|
1163
|
+
routerChannelId: meshName,
|
|
1164
|
+
targetNodeRid: target.rid,
|
|
1165
|
+
spotId: membershipSpotId,
|
|
1166
|
+
spotKind: targetMembership?.spotKind ?? contracts_1.ZLinkSpotKind.Entry,
|
|
1167
|
+
authorityOwnerGeneration: committedAuthority.authorityOwnerGeneration
|
|
1168
|
+
}, targetActorRef, (0, actor_transfer_runtime_1.committedActorOwnerFence)(state.actorId, targetActorRef, committedAuthority));
|
|
1169
|
+
if (!retainSourceForActorJoin) {
|
|
1170
|
+
await this.requireActorManager().completeRelocationSource(state.actorId);
|
|
1171
|
+
}
|
|
1172
|
+
},
|
|
1173
|
+
abortSeal: async () => {
|
|
1174
|
+
if (standalone && ownSession !== undefined)
|
|
1175
|
+
await ownSession.prepared.rollback();
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
reserveRelocationId() {
|
|
1180
|
+
const id = createServiceRelocationId(candidate => {
|
|
1181
|
+
if (this.sourceRelocationIds.has(candidate))
|
|
1182
|
+
return true;
|
|
1183
|
+
const wire = relocationWireId(candidate);
|
|
1184
|
+
const prefix = `${wire.high}:${wire.low}:`;
|
|
1185
|
+
return [...this.targetStages.keys()].some(key => key.startsWith(prefix));
|
|
1186
|
+
});
|
|
1187
|
+
this.sourceRelocationIds.add(id);
|
|
1188
|
+
return id;
|
|
1189
|
+
}
|
|
1190
|
+
reserveExactRelocationId(id) {
|
|
1191
|
+
const wire = relocationWireId(id);
|
|
1192
|
+
const prefix = `${wire.high}:${wire.low}:`;
|
|
1193
|
+
if (this.sourceRelocationIds.has(id)
|
|
1194
|
+
|| [...this.targetStages.keys()].some(key => key.startsWith(prefix))) {
|
|
1195
|
+
throw new Error(`Actor Join relocation '${id}' is already active.`);
|
|
1196
|
+
}
|
|
1197
|
+
this.sourceRelocationIds.add(id);
|
|
1198
|
+
}
|
|
1199
|
+
async runCoordinator(meshName, target, primary, captured, _authorities, signal, sessions = [],
|
|
1200
|
+
/** Target's advertised relocation state chunk cap (Actor Join only; spec 15 §4.2). */
|
|
1201
|
+
advertisedReceiveChunkLimitBytes) {
|
|
1202
|
+
relocationDebug('coordinator.begin', { aggregateId: captured.envelope.aggregateId, targetRid: String(target.rid) });
|
|
1203
|
+
const localStatus = this.requireMeshNode(meshName).status();
|
|
1204
|
+
const controlDeadlineAtMs = signal === undefined
|
|
1205
|
+
? performance.now() + 30_000
|
|
1206
|
+
: Number.MAX_SAFE_INTEGER;
|
|
1207
|
+
const coordinator = {
|
|
1208
|
+
ownerId: primary.ownerId,
|
|
1209
|
+
leaseGeneration: primary.ownerLeaseGeneration,
|
|
1210
|
+
nodeRid: String(localStatus.routingId),
|
|
1211
|
+
nodeGeneration: localStatus.lifecycleGeneration,
|
|
1212
|
+
expectedAuthorityStoreVersion: primary.storeVersion.value
|
|
1213
|
+
};
|
|
1214
|
+
const targetFence = {
|
|
1215
|
+
nodeRid: String(target.rid),
|
|
1216
|
+
nodeGeneration: target.lifecycleGeneration,
|
|
1217
|
+
ownerId: target.ownerId,
|
|
1218
|
+
ownerLeaseGeneration: target.leaseGeneration
|
|
1219
|
+
};
|
|
1220
|
+
const relocation = relocationWireId(captured.envelope.aggregateId);
|
|
1221
|
+
const object = relocationObject(captured.envelope);
|
|
1222
|
+
// The selected target lifecycle is the attempt fence used by the
|
|
1223
|
+
// reference C++ relocation path. Keep the envelope and every control leg
|
|
1224
|
+
// on that one exact identity; a process replacement is a different
|
|
1225
|
+
// attempt even when it reuses the same routing id.
|
|
1226
|
+
const targetAttemptGeneration = target.lifecycleGeneration;
|
|
1227
|
+
const relayAuthorityId = wireIdText(relocationWireId(captured.envelope.aggregateId));
|
|
1228
|
+
const relayAuthorityKey = primaryKey(captured.envelope);
|
|
1229
|
+
if (this.relocationAuthorityKeys.has(relayAuthorityId)) {
|
|
1230
|
+
throw new Error(`Relocation reply relay authority '${relayAuthorityId}' is already registered.`);
|
|
1231
|
+
}
|
|
1232
|
+
this.relocationAuthorityKeys.set(relayAuthorityId, relayAuthorityKey);
|
|
1233
|
+
// The captured payload stays in source memory: this copy is the only
|
|
1234
|
+
// handoff original until the cutover submit terminal and the
|
|
1235
|
+
// retransmission window end (spec 28 §4.2).
|
|
1236
|
+
const canonicalEnvelope = canonicalizeCapturedHandoffBacklog(captured.envelope, coordinator, targetFence);
|
|
1237
|
+
const actorJoinProfile = this.sourceActorJoinProfiles.get(captured.envelope.aggregateId);
|
|
1238
|
+
const transportEnvelope = actorJoinProfile?.canonicalRecovery === undefined
|
|
1239
|
+
? canonicalEnvelope
|
|
1240
|
+
: appendCanonicalActorJoinRecovery(canonicalEnvelope, actorJoinProfile, coordinator, primary);
|
|
1241
|
+
const encoded = (0, service_relocation_runtime_1.encodeServiceRelocationEnvelope)(transportEnvelope, target.applicationVersion);
|
|
1242
|
+
const limits = this.relocationLimits();
|
|
1243
|
+
// Actor Join threads the target's admission-time advertised cap in;
|
|
1244
|
+
// other relocation paths have no such negotiation and fall back to the
|
|
1245
|
+
// configured limit unchanged (spec 15 §4.2).
|
|
1246
|
+
const chunkLimitBytes = (0, relocation_direct_transfer_1.effectiveActorJoinChunkLimitBytes)(limits.relocationPayloadChunkLimitBytes, advertisedReceiveChunkLimitBytes);
|
|
1247
|
+
const plan = (0, relocation_direct_transfer_1.planRelocationChunks)(encoded, chunkLimitBytes);
|
|
1248
|
+
const convergenceDeadlineAtMs = signal === undefined
|
|
1249
|
+
? performance.now() + RELOCATION_OPERATION_RETENTION_MS
|
|
1250
|
+
: controlDeadlineAtMs;
|
|
1251
|
+
let readyReceived = false;
|
|
1252
|
+
let sourceCommitted = false;
|
|
1253
|
+
try {
|
|
1254
|
+
const prepare = {
|
|
1255
|
+
kind: 'prepare',
|
|
1256
|
+
relocation,
|
|
1257
|
+
targetAttemptGeneration,
|
|
1258
|
+
coordinator,
|
|
1259
|
+
target: targetFence,
|
|
1260
|
+
initiatorRole: 'source',
|
|
1261
|
+
object,
|
|
1262
|
+
sourceNodeRid: coordinator.nodeRid,
|
|
1263
|
+
sourceNodeGeneration: coordinator.nodeGeneration,
|
|
1264
|
+
payloadTotalLength: BigInt(plan.totalLength),
|
|
1265
|
+
payloadChunkCount: plan.chunkCount,
|
|
1266
|
+
payloadChecksumCrc32c: plan.checksumCrc32c,
|
|
1267
|
+
applicationVersion: target.applicationVersion
|
|
1268
|
+
};
|
|
1269
|
+
// Start the Restore request first (it also registers the temporary
|
|
1270
|
+
// queue and the assembly on the target), then stream the payload chunks
|
|
1271
|
+
// on the same ordered connection. The Ready reply arrives only after
|
|
1272
|
+
// the target assembled, verified, and restored the payload.
|
|
1273
|
+
const readyOperation = this.sendControl(meshName, target.rid, prepare, signal, controlDeadlineAtMs, encodePrepareSideband(captured.envelope));
|
|
1274
|
+
try {
|
|
1275
|
+
relocationDebug('coordinator.state_transfer_begin', { aggregateId: captured.envelope.aggregateId });
|
|
1276
|
+
await this.sendRelocationStateChunks(meshName, target, prepare, encoded, plan, signal);
|
|
1277
|
+
}
|
|
1278
|
+
catch (chunkError) {
|
|
1279
|
+
readyOperation.catch(() => undefined);
|
|
1280
|
+
throw chunkError;
|
|
1281
|
+
}
|
|
1282
|
+
relocationDebug('coordinator.await_ready', { aggregateId: captured.envelope.aggregateId });
|
|
1283
|
+
const ready = await readyOperation;
|
|
1284
|
+
relocationDebug('coordinator.ready_received', { aggregateId: captured.envelope.aggregateId });
|
|
1285
|
+
validateControlResponse(prepare, ready);
|
|
1286
|
+
// Ready arms the target's cutover-wait fallback. From this point
|
|
1287
|
+
// forward the source can no longer reopen dispatch without risking two
|
|
1288
|
+
// owners.
|
|
1289
|
+
readyReceived = true;
|
|
1290
|
+
const boundaryBatch = [];
|
|
1291
|
+
for (const session of sessions) {
|
|
1292
|
+
const participant = captured.envelope.participants.find(value => value.objectKind === 'actor'
|
|
1293
|
+
&& (0, authority_key_codec_1.decodeAuthorityKey)({ value: value.key }).globalId
|
|
1294
|
+
=== session.state.actorId);
|
|
1295
|
+
if (participant === undefined) {
|
|
1296
|
+
throw new Error(`Relocation Actor '${session.state.actorId}' is missing from its envelope.`);
|
|
1297
|
+
}
|
|
1298
|
+
for (const packet of session.prepared.takeRelocationRelay()) {
|
|
1299
|
+
const frozenRecord = canonicalRelayFrozenRecord(participant, packet, coordinator, targetFence);
|
|
1300
|
+
const data = {
|
|
1301
|
+
kind: 'data',
|
|
1302
|
+
relocation,
|
|
1303
|
+
targetAttemptGeneration,
|
|
1304
|
+
coordinator,
|
|
1305
|
+
senderRole: 'source',
|
|
1306
|
+
object: relocationObjectForParticipant(participant),
|
|
1307
|
+
frozenRecord
|
|
1308
|
+
};
|
|
1309
|
+
boundaryBatch.push({
|
|
1310
|
+
frame: (0, service_relocation_control_1.encodeServiceRelocationControlRequest)(data),
|
|
1311
|
+
canonicalBytes: frozenRecord.canonicalBytes
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
const cutoverFrame = (0, service_relocation_control_1.encodeServiceRelocationControlRequest)({
|
|
1316
|
+
kind: 'cutover',
|
|
1317
|
+
relocation,
|
|
1318
|
+
targetAttemptGeneration,
|
|
1319
|
+
coordinator,
|
|
1320
|
+
senderRole: 'source',
|
|
1321
|
+
object,
|
|
1322
|
+
boundaryRecordCount: BigInt(boundaryBatch.length),
|
|
1323
|
+
boundaryChecksumCrc32c: (0, service_relocation_runtime_1.crc32c)(Buffer.concat(boundaryBatch.map(value => value.canonicalBytes)))
|
|
1324
|
+
});
|
|
1325
|
+
// The one-way cutover submit reaches a terminal result exactly once;
|
|
1326
|
+
// success and failure both end source dispatch permanently. The batch
|
|
1327
|
+
// copy stays for the retransmission window (spec 28 §4.4).
|
|
1328
|
+
let submitFailure;
|
|
1329
|
+
try {
|
|
1330
|
+
for (const record of boundaryBatch) {
|
|
1331
|
+
await this.submitControlFrame(meshName, target.rid, record.frame, 'relay');
|
|
1332
|
+
}
|
|
1333
|
+
await this.submitControlFrame(meshName, target.rid, cutoverFrame, 'cutover');
|
|
1334
|
+
}
|
|
1335
|
+
catch (error) {
|
|
1336
|
+
submitFailure = error;
|
|
1337
|
+
console.warn('[zlink.runtime.relocation.cutover_submit_failed]', relayAuthorityId, error);
|
|
1338
|
+
}
|
|
1339
|
+
this.beginSourceCutoverWindow(meshName, target.rid, relayAuthorityId, [...boundaryBatch.map(value => value.frame), cutoverFrame], submitFailure !== undefined, limits);
|
|
1340
|
+
await this.waitForTargetAuthorities(captured.envelope, targetFence, convergenceDeadlineAtMs, signal);
|
|
1341
|
+
relocationDebug('coordinator.target_authorities_observed', { aggregateId: captured.envelope.aggregateId });
|
|
1342
|
+
for (const session of sessions)
|
|
1343
|
+
session.prepared.setReplayResults([]);
|
|
1344
|
+
await captured.commitSource();
|
|
1345
|
+
sourceCommitted = true;
|
|
1346
|
+
await this.options.reconcileStatefulAuthorityRoutes?.(signal);
|
|
1347
|
+
}
|
|
1348
|
+
catch (error) {
|
|
1349
|
+
if (!readyReceived)
|
|
1350
|
+
throw error;
|
|
1351
|
+
try {
|
|
1352
|
+
await this.waitForTargetAuthorities(captured.envelope, targetFence, convergenceDeadlineAtMs);
|
|
1353
|
+
if (!sourceCommitted) {
|
|
1354
|
+
for (const session of sessions)
|
|
1355
|
+
session.prepared.setReplayResults([]);
|
|
1356
|
+
await captured.commitSource();
|
|
1357
|
+
sourceCommitted = true;
|
|
1358
|
+
}
|
|
1359
|
+
await this.options.reconcileStatefulAuthorityRoutes?.();
|
|
1360
|
+
const authority = await requireAuthority(this.requireLocationStore(), { value: primaryKey(captured.envelope) }, undefined);
|
|
1361
|
+
throw new service_relocation_coordinator_1.ServiceRelocationPostCommitError(authority, { id: `${captured.envelope.aggregateId}:${captured.envelope.aggregateGeneration}` }, error);
|
|
1362
|
+
}
|
|
1363
|
+
catch (convergenceError) {
|
|
1364
|
+
if (convergenceError instanceof service_relocation_coordinator_1.ServiceRelocationPostCommitError) {
|
|
1365
|
+
throw convergenceError;
|
|
1366
|
+
}
|
|
1367
|
+
throw new AggregateError([error, convergenceError], 'Relocation became irreversible after Ready and target convergence failed.');
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
finally {
|
|
1371
|
+
if (!readyReceived) {
|
|
1372
|
+
// Only an explicit failure before the Ready reply becomes accepted
|
|
1373
|
+
// restores the source from the memory payload (spec 28 §9).
|
|
1374
|
+
await captured.abortSource().catch(() => undefined);
|
|
1375
|
+
}
|
|
1376
|
+
if (this.relocationAuthorityKeys.get(relayAuthorityId) === relayAuthorityKey) {
|
|
1377
|
+
this.relocationAuthorityKeys.delete(relayAuthorityId);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
/** Streams the payload chunks of one stage under the effective in-flight budgets. */
|
|
1382
|
+
async sendRelocationStateChunks(meshName, target, identity, encoded, plan, signal) {
|
|
1383
|
+
for (let ordinal = 0; ordinal < plan.chunkCount; ordinal += 1) {
|
|
1384
|
+
const chunkData = (0, relocation_direct_transfer_1.relocationChunkAt)(encoded, plan, ordinal);
|
|
1385
|
+
const releases = await this.acquireRelocationBudget(String(target.rid), chunkData.byteLength, signal);
|
|
1386
|
+
try {
|
|
1387
|
+
await this.sendControlOneWay(meshName, target.rid, {
|
|
1388
|
+
kind: 'state',
|
|
1389
|
+
relocation: identity.relocation,
|
|
1390
|
+
targetAttemptGeneration: identity.targetAttemptGeneration,
|
|
1391
|
+
coordinator: identity.coordinator,
|
|
1392
|
+
senderRole: 'source',
|
|
1393
|
+
object: identity.object,
|
|
1394
|
+
chunkOrdinal: ordinal,
|
|
1395
|
+
chunkData
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
finally {
|
|
1399
|
+
for (const release of releases)
|
|
1400
|
+
release();
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Phase 1 in-flight accounting: the charge covers the submit window of one
|
|
1406
|
+
* chunk under min(configured, conservative) budgets per peer and per node.
|
|
1407
|
+
*/
|
|
1408
|
+
async acquireRelocationBudget(targetRid, bytes, signal) {
|
|
1409
|
+
const releases = [];
|
|
1410
|
+
const nodeBudget = this.requireNodePayloadBudget();
|
|
1411
|
+
const peerBudget = this.requirePeerPayloadBudget(targetRid);
|
|
1412
|
+
releases.push(await nodeBudget.acquire(bytes, signal));
|
|
1413
|
+
try {
|
|
1414
|
+
releases.push(await peerBudget.acquire(bytes, signal));
|
|
1415
|
+
}
|
|
1416
|
+
catch (error) {
|
|
1417
|
+
for (const release of releases)
|
|
1418
|
+
release();
|
|
1419
|
+
throw error;
|
|
1420
|
+
}
|
|
1421
|
+
return releases;
|
|
1422
|
+
}
|
|
1423
|
+
/** Pre-seal admission wait — a full budget delays the next unit before seal. */
|
|
1424
|
+
async waitForRelocationBudgetHeadroom(targetRid, signal) {
|
|
1425
|
+
await this.requireNodePayloadBudget().waitForHeadroom(signal);
|
|
1426
|
+
await this.requirePeerPayloadBudget(targetRid).waitForHeadroom(signal);
|
|
1427
|
+
}
|
|
1428
|
+
requireNodePayloadBudget() {
|
|
1429
|
+
this.nodePayloadBudget ??= new relocation_direct_transfer_1.ZLinkRelocationInFlightBudget((0, relocation_direct_transfer_1.effectiveRelocationBudgetBytes)(this.relocationLimits().relocationNodeInFlightPayloadBudgetBytes));
|
|
1430
|
+
return this.nodePayloadBudget;
|
|
1431
|
+
}
|
|
1432
|
+
requirePeerPayloadBudget(targetRid) {
|
|
1433
|
+
let budget = this.peerPayloadBudgets.get(targetRid);
|
|
1434
|
+
if (budget === undefined) {
|
|
1435
|
+
budget = new relocation_direct_transfer_1.ZLinkRelocationInFlightBudget((0, relocation_direct_transfer_1.effectiveRelocationBudgetBytes)(this.relocationLimits().relocationInFlightPayloadBudgetBytes));
|
|
1436
|
+
this.peerPayloadBudgets.set(targetRid, budget);
|
|
1437
|
+
}
|
|
1438
|
+
return budget;
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Starts the source retransmission window after the cutover submit
|
|
1442
|
+
* terminal. The boundary batch and cutover copies live in framework memory
|
|
1443
|
+
* (not charged to the in-flight budget) and are cleaned exactly once when
|
|
1444
|
+
* the window ends. While the window is open a failed submit retries the
|
|
1445
|
+
* whole batch on the (re-established) connection. S4 — the Message Follow
|
|
1446
|
+
* route removal point — closes the unit for SafeToShutdown.
|
|
1447
|
+
*/
|
|
1448
|
+
beginSourceCutoverWindow(meshName, targetNodeRid, relocationKey, frames, initialSubmitFailed, limits) {
|
|
1449
|
+
const submittedAt = performance.now();
|
|
1450
|
+
const window = {
|
|
1451
|
+
meshName,
|
|
1452
|
+
targetNodeRid,
|
|
1453
|
+
frames,
|
|
1454
|
+
cutoverSubmittedAtMs: submittedAt,
|
|
1455
|
+
windowClosed: false,
|
|
1456
|
+
followExpired: false
|
|
1457
|
+
};
|
|
1458
|
+
this.sourceCutoverWindows.set(relocationKey, window);
|
|
1459
|
+
const settle = () => {
|
|
1460
|
+
if (window.windowClosed && window.followExpired) {
|
|
1461
|
+
this.sourceCutoverWindows.delete(relocationKey);
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
1464
|
+
window.windowTimer = setTimeout(() => {
|
|
1465
|
+
window.windowClosed = true;
|
|
1466
|
+
window.frames = undefined;
|
|
1467
|
+
if (window.retryTimer !== undefined)
|
|
1468
|
+
clearTimeout(window.retryTimer);
|
|
1469
|
+
settle();
|
|
1470
|
+
}, limits.relocationCutoverWaitTimeoutMs);
|
|
1471
|
+
window.windowTimer.unref();
|
|
1472
|
+
window.followTimer = setTimeout(() => {
|
|
1473
|
+
window.followExpired = true;
|
|
1474
|
+
// S1→S4 route convergence: the span the source must keep its Message
|
|
1475
|
+
// Follow route (spec 30 §7.1).
|
|
1476
|
+
this.options.metrics?.duration('zlink.relocation.route_convergence', Math.max(0, performance.now() - submittedAt) / 1000, { mesh_name: meshName });
|
|
1477
|
+
settle();
|
|
1478
|
+
}, limits.messageFollowDurationMs);
|
|
1479
|
+
window.followTimer.unref();
|
|
1480
|
+
if (initialSubmitFailed)
|
|
1481
|
+
this.scheduleCutoverRetransmit(window);
|
|
1482
|
+
}
|
|
1483
|
+
scheduleCutoverRetransmit(window) {
|
|
1484
|
+
if (window.windowClosed || window.frames === undefined)
|
|
1485
|
+
return;
|
|
1486
|
+
window.retryTimer = setTimeout(() => {
|
|
1487
|
+
void (async () => {
|
|
1488
|
+
const frames = window.frames;
|
|
1489
|
+
if (window.windowClosed || frames === undefined)
|
|
1490
|
+
return;
|
|
1491
|
+
try {
|
|
1492
|
+
for (const frame of frames) {
|
|
1493
|
+
await this.submitControlFrame(window.meshName, window.targetNodeRid, frame, 'cutover retransmission');
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
catch {
|
|
1497
|
+
this.scheduleCutoverRetransmit(window);
|
|
1498
|
+
}
|
|
1499
|
+
})();
|
|
1500
|
+
}, 250);
|
|
1501
|
+
window.retryTimer.unref();
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* True when every relocation this source started reached its Message
|
|
1505
|
+
* Follow route removal point (S4) and its cutover retransmission window
|
|
1506
|
+
* ended — both source-local events (spec 30 §11).
|
|
1507
|
+
*/
|
|
1508
|
+
isSafeToShutdown() {
|
|
1509
|
+
return this.sourceCutoverWindows.size === 0;
|
|
1510
|
+
}
|
|
1511
|
+
async releaseParticipantAuthorities(envelope, primaryKey, signal) {
|
|
1512
|
+
for (const participant of envelope.participants) {
|
|
1513
|
+
if (participant.key === primaryKey)
|
|
1514
|
+
continue;
|
|
1515
|
+
const key = { value: participant.key };
|
|
1516
|
+
const current = await requireAuthority(this.requireLocationStore(), key, signal);
|
|
1517
|
+
const publication = this.codec.read(current.payload);
|
|
1518
|
+
if (publication === undefined)
|
|
1519
|
+
continue;
|
|
1520
|
+
const result = await this.requireLocationStore().compareExchangeAuthority(key, current.storeVersion, {
|
|
1521
|
+
kind: 'put',
|
|
1522
|
+
generationTransition: 'preserve',
|
|
1523
|
+
payload: this.codec.clear(current.payload, publication.reference)
|
|
1524
|
+
}, signal);
|
|
1525
|
+
if (result.kind !== 'stored') {
|
|
1526
|
+
throw new Error(`Participant '${participant.key}' recovery pointer release failed.`);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
/** Restores the hidden object and its saved-work prefix before Ready. */
|
|
1531
|
+
async handlePrepareControl(meshName, stagingId, request, sourceNodeRid, signal) {
|
|
1532
|
+
if (sourceNodeRid === null || String(sourceNodeRid) !== request.sourceNodeRid) {
|
|
1533
|
+
throw new Error('Relocation prepare source node fence does not match the authenticated peer.');
|
|
1534
|
+
}
|
|
1535
|
+
const targetStatus = this.requireMeshNode(meshName).status();
|
|
1536
|
+
const targetOwner = this.options.currentOwner();
|
|
1537
|
+
const targetDescriptor = this.options.localDescriptor?.(meshName);
|
|
1538
|
+
if (targetOwner === undefined
|
|
1539
|
+
|| String(targetStatus.routingId) !== request.target.nodeRid
|
|
1540
|
+
|| targetStatus.lifecycleGeneration !== request.target.nodeGeneration
|
|
1541
|
+
|| targetOwner.ownerId !== request.target.ownerId
|
|
1542
|
+
|| targetOwner.leaseGeneration !== request.target.ownerLeaseGeneration
|
|
1543
|
+
|| targetDescriptor !== undefined
|
|
1544
|
+
&& targetDescriptor.applicationVersion !== request.applicationVersion) {
|
|
1545
|
+
throw new Error('Relocation prepare target fence does not match the target owner.');
|
|
1546
|
+
}
|
|
1547
|
+
const fingerprint = stringifyWire(request);
|
|
1548
|
+
const existing = this.targetStages.get(stagingId);
|
|
1549
|
+
if (existing !== undefined) {
|
|
1550
|
+
if (existing.offer.authenticatedSourceNodeRid !== String(sourceNodeRid)
|
|
1551
|
+
|| existing.offer.prepareFingerprint !== fingerprint) {
|
|
1552
|
+
throw new Error('Relocation prepare retry source node changed.');
|
|
1553
|
+
}
|
|
1554
|
+
return relocationReady(request);
|
|
1555
|
+
}
|
|
1556
|
+
const terminal = this.terminalTargets.get(stagingId);
|
|
1557
|
+
if (terminal !== undefined) {
|
|
1558
|
+
if (terminal !== fingerprint) {
|
|
1559
|
+
throw new Error(`Relocation '${stagingId}' repeated Prepare with different bytes.`);
|
|
1560
|
+
}
|
|
1561
|
+
this.terminalTargets.touch(stagingId);
|
|
1562
|
+
return relocationReady(request);
|
|
1563
|
+
}
|
|
1564
|
+
const pendingAssembly = this.targetAssemblies.get(stagingId);
|
|
1565
|
+
if (pendingAssembly === undefined || pendingAssembly.fingerprint !== fingerprint) {
|
|
1566
|
+
throw new Error(`Relocation '${stagingId}' has no matching payload assembly.`);
|
|
1567
|
+
}
|
|
1568
|
+
// The directly transferred payload replaces the shared durable root: the
|
|
1569
|
+
// Restore starts only after every chunk arrived and the assembled bytes
|
|
1570
|
+
// matched the manifest checksum exactly once.
|
|
1571
|
+
let payload;
|
|
1572
|
+
try {
|
|
1573
|
+
payload = await pendingAssembly.assembly.payload();
|
|
1574
|
+
}
|
|
1575
|
+
catch (error) {
|
|
1576
|
+
// A rejection observed after dispose() started is the runtime shutting
|
|
1577
|
+
// down, not a chunk-assembly or checksum integrity failure — it must
|
|
1578
|
+
// not be reported as DataLost(35). There is no dedicated ShuttingDown
|
|
1579
|
+
// wire code in the vocabulary yet, so it falls through unwrapped to
|
|
1580
|
+
// the generic InternalFailure code (spec 15 §4).
|
|
1581
|
+
if (this.disposed)
|
|
1582
|
+
throw error;
|
|
1583
|
+
const message = error !== null && typeof error === 'object' && 'message' in error
|
|
1584
|
+
? error.message ?? error
|
|
1585
|
+
: error;
|
|
1586
|
+
throw new ServiceRelocationDataLostError(String(message));
|
|
1587
|
+
}
|
|
1588
|
+
const envelope = (0, service_relocation_runtime_1.decodeServiceRelocationEnvelope)(payload, request.targetAttemptGeneration);
|
|
1589
|
+
const inventoryEnvelope = attachPrepareBoundSessions(await this.resolveTargetParticipantInventory(request, envelope, signal), request.nodeInternalBoundSessions);
|
|
1590
|
+
validatePrepareEnvelope(request, inventoryEnvelope);
|
|
1591
|
+
let materialized;
|
|
1592
|
+
let reservation;
|
|
1593
|
+
try {
|
|
1594
|
+
// Deferred Join completion publishes into the source authority's
|
|
1595
|
+
// canonical relocation slot. Do that while the row is still CAS-able;
|
|
1596
|
+
// the aggregate marker installed by reserveTargetForPrepare deliberately
|
|
1597
|
+
// rejects unrelated authority CAS until commit/abort.
|
|
1598
|
+
materialized = await this.materializeTargetEnvelope(meshName, inventoryEnvelope, signal);
|
|
1599
|
+
await materialized.owner.restoreSavedWork(materialized.staging, signal);
|
|
1600
|
+
reservation = await this.reserveTargetForPrepare(meshName, request, inventoryEnvelope, materialized.target, signal);
|
|
1601
|
+
const offer = {
|
|
1602
|
+
prepare: request,
|
|
1603
|
+
prepareFingerprint: fingerprint,
|
|
1604
|
+
authenticatedSourceNodeRid: String(sourceNodeRid),
|
|
1605
|
+
envelope: inventoryEnvelope,
|
|
1606
|
+
restoreDeadlineAtMs: performance.now() + RELOCATION_OPERATION_RETENTION_MS,
|
|
1607
|
+
reservation
|
|
1608
|
+
};
|
|
1609
|
+
const stage = {
|
|
1610
|
+
offer,
|
|
1611
|
+
...materialized,
|
|
1612
|
+
phase: 'ready',
|
|
1613
|
+
lane: Promise.resolve(),
|
|
1614
|
+
cutoverReceived: false,
|
|
1615
|
+
boundaryRelay: []
|
|
1616
|
+
};
|
|
1617
|
+
this.targetStages.set(stagingId, stage);
|
|
1618
|
+
}
|
|
1619
|
+
catch (error) {
|
|
1620
|
+
if (reservation !== undefined) {
|
|
1621
|
+
await this.abortTargetReservation(reservation, signal).catch(() => undefined);
|
|
1622
|
+
}
|
|
1623
|
+
if (materialized !== undefined) {
|
|
1624
|
+
await materialized.owner.abort(materialized.staging).catch(() => undefined);
|
|
1625
|
+
}
|
|
1626
|
+
throw error;
|
|
1627
|
+
}
|
|
1628
|
+
return relocationReady(request);
|
|
1629
|
+
}
|
|
1630
|
+
armTargetCutoverFallback(meshName, stagingId) {
|
|
1631
|
+
const stage = this.targetStages.get(stagingId);
|
|
1632
|
+
if (stage === undefined || stage.fallback !== undefined || stage.finalize !== undefined)
|
|
1633
|
+
return;
|
|
1634
|
+
stage.fallback = setTimeout(() => {
|
|
1635
|
+
void this.beginTargetFinalize(meshName, stagingId, stage, true)
|
|
1636
|
+
.catch(error => console.error('[zlink.runtime.relocation.location_update_failed]', error));
|
|
1637
|
+
}, this.relocationLimits().relocationCutoverWaitTimeoutMs);
|
|
1638
|
+
stage.fallback.unref();
|
|
1639
|
+
}
|
|
1640
|
+
async handleOneWayControl(meshName, request, sourceNodeRid, signal) {
|
|
1641
|
+
if (request.senderRole !== 'source' || sourceNodeRid === null) {
|
|
1642
|
+
throw new Error('Relocation one-way control has no exact source peer.');
|
|
1643
|
+
}
|
|
1644
|
+
const stagingId = relocationStagingId(request);
|
|
1645
|
+
const stage = this.targetStages.get(stagingId);
|
|
1646
|
+
if (stage === undefined) {
|
|
1647
|
+
if (this.terminalTargets.get(stagingId) !== undefined) {
|
|
1648
|
+
if (request.kind === 'cutover') {
|
|
1649
|
+
console.warn('[zlink.runtime.relocation.late_cutover]', stagingId);
|
|
1650
|
+
}
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
throw new Error(`Relocation staging '${stagingId}' is missing.`);
|
|
1654
|
+
}
|
|
1655
|
+
validateTargetOneWayControl(stage, request, sourceNodeRid);
|
|
1656
|
+
if (request.kind === 'data') {
|
|
1657
|
+
stage.lane = stage.lane.then(() => this.stageBoundaryRelay(stage, request));
|
|
1658
|
+
await stage.lane;
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
if (stage.cutoverReceived || stage.phase !== 'ready') {
|
|
1662
|
+
console.warn('[zlink.runtime.relocation.late_cutover]', stagingId);
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
this.reconcileBoundaryRelay(stage, request, stagingId);
|
|
1666
|
+
stage.cutoverReceived = true;
|
|
1667
|
+
if (stage.fallback !== undefined)
|
|
1668
|
+
clearTimeout(stage.fallback);
|
|
1669
|
+
await this.beginTargetFinalize(meshName, stagingId, stage, false, signal);
|
|
1670
|
+
}
|
|
1671
|
+
/** Validates one boundary relay record and buffers it until cutover applies it. */
|
|
1672
|
+
async stageBoundaryRelay(stage, request) {
|
|
1673
|
+
this.validateRelocationData(stage, request);
|
|
1674
|
+
stage.boundaryRelay.push(request);
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Compares the cutover confirmation values against the staged boundary
|
|
1678
|
+
* relay. A retransmitted batch replaces a partially received span as a
|
|
1679
|
+
* whole (the retransmission always resends the entire batch, so the exact
|
|
1680
|
+
* span is the staged suffix of `boundaryRecordCount` records).
|
|
1681
|
+
*/
|
|
1682
|
+
reconcileBoundaryRelay(stage, request, stagingId) {
|
|
1683
|
+
if (request.boundaryRecordCount > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
1684
|
+
throw new Error('Relocation cutover boundary record count is out of range.');
|
|
1685
|
+
}
|
|
1686
|
+
const expectedCount = Number(request.boundaryRecordCount);
|
|
1687
|
+
const staged = stage.boundaryRelay;
|
|
1688
|
+
const checksumOf = (records) => (0, service_relocation_runtime_1.crc32c)(Buffer.concat(records.map(value => value.frozenRecord.canonicalBytes)));
|
|
1689
|
+
if (staged.length === expectedCount
|
|
1690
|
+
&& checksumOf(staged) === request.boundaryChecksumCrc32c) {
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
if (staged.length > expectedCount) {
|
|
1694
|
+
const suffix = staged.slice(staged.length - expectedCount);
|
|
1695
|
+
if (checksumOf(suffix) === request.boundaryChecksumCrc32c) {
|
|
1696
|
+
console.warn('[zlink.runtime.relocation.boundary_batch_replaced]', stagingId);
|
|
1697
|
+
staged.splice(0, staged.length - expectedCount);
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
// Spec 28 §4.4 — on an ordered connection this comparison always holds on
|
|
1702
|
+
// the normal path; a mismatch signals an implementation defect.
|
|
1703
|
+
throw new Error(`Relocation cutover boundary confirmation mismatch for '${stagingId}' `
|
|
1704
|
+
+ `(staged=${staged.length} declared=${expectedCount}).`);
|
|
1705
|
+
}
|
|
1706
|
+
beginTargetFinalize(meshName, stagingId, stage, fallback, signal) {
|
|
1707
|
+
if (stage.finalize !== undefined)
|
|
1708
|
+
return stage.finalize;
|
|
1709
|
+
if (fallback) {
|
|
1710
|
+
console.warn('[zlink.runtime.relocation.cutover_timeout]', stagingId);
|
|
1711
|
+
this.options.metrics?.count('zlink.relocation.cutover_timeout', 1, {
|
|
1712
|
+
mesh_name: meshName
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
const finalize = stage.lane.then(() => this.finalizeTargetStage(meshName, stagingId, stage, signal));
|
|
1716
|
+
stage.finalize = finalize;
|
|
1717
|
+
stage.lane = finalize;
|
|
1718
|
+
return finalize;
|
|
1719
|
+
}
|
|
1720
|
+
async finalizeTargetStage(meshName, stagingId, stage, signal) {
|
|
1721
|
+
if (stage.phase !== 'ready')
|
|
1722
|
+
return;
|
|
1723
|
+
stage.phase = 'finalizing';
|
|
1724
|
+
let authorityCommitted = false;
|
|
1725
|
+
let authority;
|
|
1726
|
+
let resumeStartedAt;
|
|
1727
|
+
try {
|
|
1728
|
+
// The boundary relay span is applied to the temporary queue before the
|
|
1729
|
+
// owner CAS so the merged order is saved work, pre-boundary relay, then
|
|
1730
|
+
// post-boundary temporary records (spec 28 §4.6).
|
|
1731
|
+
for (const relay of stage.boundaryRelay.splice(0)) {
|
|
1732
|
+
await this.applyRelocationData(stage, relay, signal);
|
|
1733
|
+
}
|
|
1734
|
+
authority = await this.commitTargetReservation(stage, stage.offer.reservation, signal);
|
|
1735
|
+
authorityCommitted = true;
|
|
1736
|
+
resumeStartedAt = performance.now();
|
|
1737
|
+
stage.phase = 'committed';
|
|
1738
|
+
await stage.owner.normalize(stage.staging, authority, signal);
|
|
1739
|
+
await stage.owner.publish(stage.staging, authority, signal);
|
|
1740
|
+
// The target may have visited this Actor before and still retain that
|
|
1741
|
+
// tenure in its private Actor-client resolver. The target-only CAS is
|
|
1742
|
+
// now authoritative, so expire the old projection before OnJoinedActor
|
|
1743
|
+
// can issue an Actor request and deadlock behind source leave.
|
|
1744
|
+
for (const hidden of stage.staging.hidden.values()) {
|
|
1745
|
+
if (hidden.actor !== undefined) {
|
|
1746
|
+
this.options.invalidateActorRoute?.(hidden.actor.context.actorId);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
await this.finalizeActorJoinProfiles(meshName, stage, signal);
|
|
1750
|
+
// Actor Join completion may advance its accepted-completion journal in
|
|
1751
|
+
// the canonical authority slot. Try to clear that final publication
|
|
1752
|
+
// before admission becomes externally visible so a later Join cannot
|
|
1753
|
+
// reserve against the previous root. A bounded CAS conflict must not
|
|
1754
|
+
// roll back already committed target ownership or strand its staged
|
|
1755
|
+
// object: the next reservation recognizes the retained journal root and
|
|
1756
|
+
// continues its existing cleanup/retry path.
|
|
1757
|
+
try {
|
|
1758
|
+
await this.clearTargetRelocationPublication(stage, authority, signal);
|
|
1759
|
+
}
|
|
1760
|
+
catch (error) {
|
|
1761
|
+
console.warn('[zlink.runtime.relocation.publication_clear_failed]', stagingId, error);
|
|
1762
|
+
this.options.metrics?.count('zlink.relocation.publication_clear_failed', 1, {
|
|
1763
|
+
mesh_name: meshName
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
await stage.owner.openAdmission(stage.staging, signal);
|
|
1767
|
+
// S2→S3: owner confirmation to application dispatch opening.
|
|
1768
|
+
this.options.metrics?.duration('zlink.relocation.target_resume', Math.max(0, performance.now() - resumeStartedAt) / 1000, { mesh_name: meshName });
|
|
1769
|
+
await this.publishSessionRoutes(stage.staging);
|
|
1770
|
+
await this.relayTerminalReplies(meshName, stage, this.targetReplyRelayCoordinator(meshName, stage, authority), signal);
|
|
1771
|
+
stage.phase = 'open';
|
|
1772
|
+
this.terminalTargets.remember(stagingId, stage.offer.prepareFingerprint);
|
|
1773
|
+
this.targetStages.delete(stagingId);
|
|
1774
|
+
this.targetAssemblies.delete(stagingId);
|
|
1775
|
+
}
|
|
1776
|
+
catch (error) {
|
|
1777
|
+
stage.phase = 'failed';
|
|
1778
|
+
this.terminalTargets.remember(stagingId, stage.offer.prepareFingerprint);
|
|
1779
|
+
this.targetStages.delete(stagingId);
|
|
1780
|
+
this.targetAssemblies.delete(stagingId);
|
|
1781
|
+
if (!authorityCommitted) {
|
|
1782
|
+
await this.abortTargetStage(stage).catch(() => undefined);
|
|
1783
|
+
throw error;
|
|
1784
|
+
}
|
|
1785
|
+
if (authority !== undefined) {
|
|
1786
|
+
throw new service_relocation_coordinator_1.ServiceRelocationPostCommitError(authority, stage.staging, error);
|
|
1787
|
+
}
|
|
1788
|
+
throw error;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
validateRelocationData(stage, request) {
|
|
1792
|
+
const frozen = request.frozenRecord;
|
|
1793
|
+
const target = frozen.target;
|
|
1794
|
+
const payload = frozen.applicationPayload;
|
|
1795
|
+
const preparedTarget = stage.offer.prepare.target;
|
|
1796
|
+
if (request.object.kind !== 'actor'
|
|
1797
|
+
|| target?.kind !== 'actor'
|
|
1798
|
+
|| payload === undefined
|
|
1799
|
+
|| payload.packetName !== '__zlink.actor.handoff.accepted'
|
|
1800
|
+
|| target.actorId !== request.object.actorId
|
|
1801
|
+
|| target.generation !== request.object.objectGeneration
|
|
1802
|
+
|| target.targetNodeRid !== preparedTarget.nodeRid
|
|
1803
|
+
|| target.targetNodeGeneration !== preparedTarget.nodeGeneration
|
|
1804
|
+
|| target.authorityOwnerGeneration
|
|
1805
|
+
!== request.object.expectedAuthorityOwnerGeneration + 1n
|
|
1806
|
+
|| target.ownerLeaseGeneration !== preparedTarget.ownerLeaseGeneration) {
|
|
1807
|
+
throw new Error('Relocation data does not target the exact temporary Actor queue.');
|
|
1808
|
+
}
|
|
1809
|
+
const key = (0, authority_key_codec_1.encodeAuthorityKey)('actor', request.object.actorId).value;
|
|
1810
|
+
const participant = stage.staging.envelope.participants.find(value => value.key === key);
|
|
1811
|
+
if (participant === undefined
|
|
1812
|
+
|| participant.objectGeneration !== request.object.objectGeneration
|
|
1813
|
+
|| participant.authorityOwnerGeneration
|
|
1814
|
+
!== request.object.expectedAuthorityOwnerGeneration) {
|
|
1815
|
+
throw new Error('Relocation data Actor is not part of the prepared unit.');
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
async applyRelocationData(stage, request, signal) {
|
|
1819
|
+
const payload = request.frozenRecord.applicationPayload;
|
|
1820
|
+
await stage.owner.replayRelayedMessage(stage.staging, (0, authority_key_codec_1.encodeAuthorityKey)('actor', request.object.actorId).value, { sequence: 0n, payload: Buffer.from(payload.bytes) }, signal);
|
|
1821
|
+
}
|
|
1822
|
+
async finalizeActorJoinProfiles(meshName, stage, signal) {
|
|
1823
|
+
const manager = this.options.spotManager();
|
|
1824
|
+
if (manager === undefined)
|
|
1825
|
+
return;
|
|
1826
|
+
for (const hidden of stage.staging.hidden.values()) {
|
|
1827
|
+
if (hidden.actor === undefined)
|
|
1828
|
+
continue;
|
|
1829
|
+
const state = this.options.actorManager()?.getState(hidden.actor.context.actorId);
|
|
1830
|
+
const nativeRef = state?.nativeActorRef;
|
|
1831
|
+
if (nativeRef === undefined) {
|
|
1832
|
+
throw new Error(`Actor '${hidden.actor.context.actorId}' relocation target has no native identity.`);
|
|
1833
|
+
}
|
|
1834
|
+
await manager.finalizeActorJoinRelocation(meshName, actorJoinAdmissionIdentity(stage.staging.envelope), hidden.actor, (0, actor_runtime_state_1.toFrameworkActorRef)(nativeRef, meshName), sourceNodeRid => this.submitActorJoinSourceLeave(meshName, sourceNodeRid, stage.staging.envelope.aggregateId, hidden.actor.context.actorId), signal);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
async submitActorJoinSourceLeave(meshName, sourceNodeRid, relocationId, actorId) {
|
|
1838
|
+
try {
|
|
1839
|
+
const submitted = await this.requireMeshNode(meshName).sendToNode(sourceNodeRid, Buffer.from(JSON.stringify({
|
|
1840
|
+
packetName: actor_remote_wire_1.ZLINK_REMOTE_ACTOR_SOURCE_LEAVE_TERMINAL,
|
|
1841
|
+
transferId: relocationId,
|
|
1842
|
+
actorId,
|
|
1843
|
+
succeeded: true
|
|
1844
|
+
})));
|
|
1845
|
+
if (submitted !== runtime_values_1.SubmitResult.Ok) {
|
|
1846
|
+
console.warn('[zlink.runtime.relocation.source_leave_submit_failed]', actorId, submitted);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
catch (error) {
|
|
1850
|
+
console.warn('[zlink.runtime.relocation.source_leave_submit_failed]', actorId, error);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
acceptActorJoinSourceLeave(input) {
|
|
1854
|
+
const profile = this.sourceActorJoinProfiles.get(input.transferId);
|
|
1855
|
+
if (profile === undefined) {
|
|
1856
|
+
const terminalActorId = this.terminalActorJoinSourceLeaves.get(input.transferId);
|
|
1857
|
+
if (terminalActorId === undefined)
|
|
1858
|
+
return false;
|
|
1859
|
+
if (terminalActorId !== input.actorId || !input.succeeded) {
|
|
1860
|
+
throw new Error(`Actor Join relocation '${input.transferId}' source leave identity changed.`);
|
|
1861
|
+
}
|
|
1862
|
+
this.terminalActorJoinSourceLeaves.touch(input.transferId);
|
|
1863
|
+
return true;
|
|
1864
|
+
}
|
|
1865
|
+
if (profile.state.actorId !== input.actorId || !input.succeeded) {
|
|
1866
|
+
throw new Error(`Actor Join relocation '${input.transferId}' source leave identity changed.`);
|
|
1867
|
+
}
|
|
1868
|
+
this.sourceActorJoinProfiles.delete(input.transferId);
|
|
1869
|
+
this.terminalActorJoinSourceLeaves.remember(input.transferId, input.actorId);
|
|
1870
|
+
void profile.ready.then(async () => {
|
|
1871
|
+
await this.options.actorTransfer.completeRelocationSourceLeave(profile.actor, profile.sourceSpotId);
|
|
1872
|
+
await this.options.actorManager()?.completeRelocationSource(input.actorId, profile.sourceActorRef);
|
|
1873
|
+
}).catch(error => {
|
|
1874
|
+
console.error('[zlink.runtime.relocation.source_leave_failed]', error);
|
|
1875
|
+
});
|
|
1876
|
+
return true;
|
|
1877
|
+
}
|
|
1878
|
+
async sendControlOneWay(meshName, targetNodeRid, request) {
|
|
1879
|
+
await this.submitControlFrame(meshName, targetNodeRid, (0, service_relocation_control_1.encodeServiceRelocationControlRequest)(request), request.kind);
|
|
1880
|
+
}
|
|
1881
|
+
async submitControlFrame(meshName, targetNodeRid, frame, kind) {
|
|
1882
|
+
const submitted = await this.sendInfrastructureControl(meshName, targetNodeRid, frame);
|
|
1883
|
+
if (submitted !== runtime_values_1.SubmitResult.Ok) {
|
|
1884
|
+
throw new Error(`Relocation ${kind} was not accepted by RouteMesh.`);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
async waitForTargetAuthorities(envelope, target, deadlineAtMs, signal) {
|
|
1888
|
+
for (;;) {
|
|
1889
|
+
signal?.throwIfAborted();
|
|
1890
|
+
let exact = true;
|
|
1891
|
+
for (const participant of envelope.participants) {
|
|
1892
|
+
const current = await this.requireLocationStore().readAuthority({ value: participant.key }, signal);
|
|
1893
|
+
if (current.kind !== 'snapshot'
|
|
1894
|
+
|| current.objectGeneration !== participant.objectGeneration
|
|
1895
|
+
|| current.ownerId !== target.ownerId
|
|
1896
|
+
|| current.ownerLeaseGeneration !== target.ownerLeaseGeneration
|
|
1897
|
+
|| current.authorityOwnerGeneration <= participant.authorityOwnerGeneration
|
|
1898
|
+
|| String(current.allocation.descriptor.rid) !== target.nodeRid
|
|
1899
|
+
|| current.allocation.descriptorLifecycleGeneration !== target.nodeGeneration) {
|
|
1900
|
+
exact = false;
|
|
1901
|
+
break;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
if (exact)
|
|
1905
|
+
return;
|
|
1906
|
+
if (performance.now() >= deadlineAtMs) {
|
|
1907
|
+
throw new Error('Relocation target owner was not confirmed before its original deadline.');
|
|
1908
|
+
}
|
|
1909
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
async clearTargetRelocationPublication(stage, primary, signal) {
|
|
1913
|
+
await this.releaseParticipantAuthorities(stage.staging.envelope, stage.staging.primaryAuthorityKey.value, signal);
|
|
1914
|
+
// The deferred-Join journal advances its completion cursor with its
|
|
1915
|
+
// own authority CAS while the target finalizes, so the initially
|
|
1916
|
+
// captured storeVersion can be stale by the time the relocation
|
|
1917
|
+
// wrapper is cleared. Re-read and retry on conflict instead of turning
|
|
1918
|
+
// a completed relocation into a post-commit failure (spec 28 —
|
|
1919
|
+
// retryable store conflicts converge on the same CAS and the same
|
|
1920
|
+
// relocation identity until the outcome is determined).
|
|
1921
|
+
let current = primary;
|
|
1922
|
+
for (let attempt = 0; attempt < 16; attempt += 1) {
|
|
1923
|
+
const publication = this.codec.read(current.payload);
|
|
1924
|
+
if (publication === undefined)
|
|
1925
|
+
return;
|
|
1926
|
+
const result = await this.requireLocationStore().compareExchangeAuthority(stage.staging.primaryAuthorityKey, current.storeVersion, {
|
|
1927
|
+
kind: 'put',
|
|
1928
|
+
generationTransition: 'preserve',
|
|
1929
|
+
payload: this.codec.clear(current.payload, publication.reference)
|
|
1930
|
+
}, signal);
|
|
1931
|
+
if (result.kind === 'stored')
|
|
1932
|
+
return;
|
|
1933
|
+
const read = await this.requireLocationStore().readAuthority(stage.staging.primaryAuthorityKey, signal);
|
|
1934
|
+
if (read.kind !== 'snapshot')
|
|
1935
|
+
break;
|
|
1936
|
+
current = read;
|
|
1937
|
+
}
|
|
1938
|
+
throw new Error('Relocation target authority normalization CAS failed.');
|
|
1939
|
+
}
|
|
1940
|
+
async handleReplyRelay(meshName, relay, sourceNodeRid, signal) {
|
|
1941
|
+
if (sourceNodeRid === null) {
|
|
1942
|
+
throw new Error('Relocation reply relay has no authenticated target node.');
|
|
1943
|
+
}
|
|
1944
|
+
await this.requireAdmittedPeer(meshName, String(sourceNodeRid), relay.coordinator, signal);
|
|
1945
|
+
const relocationId = wireIdText(relay.relocation);
|
|
1946
|
+
const authorityKey = this.relocationAuthorityKeys.get(relocationId);
|
|
1947
|
+
if (authorityKey === undefined) {
|
|
1948
|
+
throw new Error('Relocation reply relay has no registered durable authority.');
|
|
1949
|
+
}
|
|
1950
|
+
const authority = await requireAuthority(this.requireLocationStore(), { value: authorityKey }, signal);
|
|
1951
|
+
if (authority.storeVersion.value !== relay.coordinator.expectedAuthorityStoreVersion
|
|
1952
|
+
|| authority.ownerId !== relay.coordinator.ownerId
|
|
1953
|
+
|| authority.ownerLeaseGeneration !== relay.coordinator.leaseGeneration
|
|
1954
|
+
|| String(authority.allocation.descriptor.rid) !== relay.coordinator.nodeRid
|
|
1955
|
+
|| authority.allocation.descriptorLifecycleGeneration
|
|
1956
|
+
!== relay.coordinator.nodeGeneration) {
|
|
1957
|
+
throw new Error('Relocation reply relay coordinator authority fence is stale.');
|
|
1958
|
+
}
|
|
1959
|
+
const accepted = this.options.actorTransfer.relayCanonicalMaintenanceTerminal(wireIdText(relay.operation), relay.replyRouteId.toString(), handoffResultFromRelay(relay), sourceNodeRid);
|
|
1960
|
+
if (accepted.status !== 'terminalReceived' && accepted.status !== 'alreadyTerminal'
|
|
1961
|
+
|| accepted.source === undefined) {
|
|
1962
|
+
throw new Error('Canonical relocation reply relay collided with another source route.');
|
|
1963
|
+
}
|
|
1964
|
+
const requestSource = handoffSourceFence(accepted.source);
|
|
1965
|
+
const localStatus = this.requireMeshNode(meshName).status();
|
|
1966
|
+
const localOwner = this.options.currentOwner();
|
|
1967
|
+
if (localOwner === undefined
|
|
1968
|
+
|| requestSource.ownerId !== localOwner.ownerId
|
|
1969
|
+
|| requestSource.leaseGeneration !== localOwner.leaseGeneration
|
|
1970
|
+
|| !(0, routing_id_1.routingIdsEqual)(requestSource.nodeRid, localStatus.routingId)
|
|
1971
|
+
|| requestSource.nodeGeneration !== localStatus.lifecycleGeneration) {
|
|
1972
|
+
throw new Error('Relocation reply relay request-source fence changed after capture.');
|
|
1973
|
+
}
|
|
1974
|
+
const ack = {
|
|
1975
|
+
relocation: relay.relocation,
|
|
1976
|
+
coordinator: relay.coordinator,
|
|
1977
|
+
operation: relay.operation,
|
|
1978
|
+
replyRouteId: relay.replyRouteId,
|
|
1979
|
+
requestSource,
|
|
1980
|
+
status: accepted.status
|
|
1981
|
+
};
|
|
1982
|
+
if (await this.sendInfrastructureControl(meshName, sourceNodeRid, (0, service_stateful_wire_codec_1.encodeMaintenanceReplyRelayAck)(ack)) !== runtime_values_1.SubmitResult.Ok) {
|
|
1983
|
+
throw new Error('Relocation reply relay ACK send was rejected.');
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
async sendReplyRelay(meshName, ackTargetNodeRid, request, expectedRequestSource, signal) {
|
|
1987
|
+
const targetRid = String(ackTargetNodeRid);
|
|
1988
|
+
const identityKey = replyRelayIdentityKey(request);
|
|
1989
|
+
const key = replyRelayPendingKey(targetRid, request);
|
|
1990
|
+
if (this.pendingReplyRelays.has(key)) {
|
|
1991
|
+
throw new Error(`Relocation reply relay ACK '${key}' is already pending.`);
|
|
1992
|
+
}
|
|
1993
|
+
return await new Promise((resolve, reject) => {
|
|
1994
|
+
let attempts = 0;
|
|
1995
|
+
const finish = (action) => {
|
|
1996
|
+
const pending = this.pendingReplyRelays.get(key);
|
|
1997
|
+
if (pending?.timer !== undefined)
|
|
1998
|
+
clearTimeout(pending.timer);
|
|
1999
|
+
this.pendingReplyRelays.delete(key);
|
|
2000
|
+
action();
|
|
2001
|
+
};
|
|
2002
|
+
const pending = {
|
|
2003
|
+
ackTargetNodeRid: targetRid,
|
|
2004
|
+
identityKey,
|
|
2005
|
+
request,
|
|
2006
|
+
expectedRequestSource,
|
|
2007
|
+
resolve: delivery => finish(() => resolve(delivery)),
|
|
2008
|
+
reject: error => finish(() => reject(error))
|
|
2009
|
+
};
|
|
2010
|
+
const send = async () => {
|
|
2011
|
+
try {
|
|
2012
|
+
if (signal?.aborted === true) {
|
|
2013
|
+
pending.reject(signal.reason);
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
if (await this.exactSourceLeaseExpired(expectedRequestSource, signal)) {
|
|
2017
|
+
pending.resolve('sourceLeaseExpired');
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
if (++attempts > 120) {
|
|
2021
|
+
pending.reject(new Error(`Relocation reply relay ACK '${key}' timed out.`));
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
await this.sendInfrastructureControl(meshName, ackTargetNodeRid, (0, service_stateful_wire_codec_1.encodeMaintenanceReplyRelay)(request));
|
|
2025
|
+
if (this.pendingReplyRelays.get(key) === pending) {
|
|
2026
|
+
pending.timer = setTimeout(() => void send(), 250);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
catch (error) {
|
|
2030
|
+
pending.reject(error);
|
|
2031
|
+
}
|
|
2032
|
+
};
|
|
2033
|
+
this.pendingReplyRelays.set(key, pending);
|
|
2034
|
+
void send();
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
async acceptReplyRelayAck(meshName, ack, sourceNodeRid, signal) {
|
|
2038
|
+
const identityKey = replyRelayIdentityKey(ack);
|
|
2039
|
+
const exactKey = sourceNodeRid === null
|
|
2040
|
+
? undefined
|
|
2041
|
+
: replyRelayPendingKey(String(sourceNodeRid), ack);
|
|
2042
|
+
const pending = exactKey === undefined ? undefined : this.pendingReplyRelays.get(exactKey);
|
|
2043
|
+
if (pending === undefined) {
|
|
2044
|
+
const collision = [...this.pendingReplyRelays.values()].find(value => value.identityKey === identityKey);
|
|
2045
|
+
collision?.reject(new Error('Relocation reply relay ACK target source collided.'));
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
try {
|
|
2049
|
+
await this.requireAdmittedPeer(meshName, pending.ackTargetNodeRid, pending.expectedRequestSource, signal);
|
|
2050
|
+
if (!sameWireId(ack.relocation, pending.request.relocation)
|
|
2051
|
+
|| !sameWireId(ack.operation, pending.request.operation)
|
|
2052
|
+
|| ack.replyRouteId !== pending.request.replyRouteId
|
|
2053
|
+
|| !sameCoordinator(ack.coordinator, pending.request.coordinator)
|
|
2054
|
+
|| !sameRequestSource(ack.requestSource, pending.expectedRequestSource)) {
|
|
2055
|
+
throw new Error('Relocation reply relay ACK does not match its durable source fence.');
|
|
2056
|
+
}
|
|
2057
|
+
pending.resolve(ack.status);
|
|
2058
|
+
}
|
|
2059
|
+
catch (error) {
|
|
2060
|
+
pending.reject(error);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
async requireAdmittedPeer(meshName, authenticatedRid, expected, signal) {
|
|
2064
|
+
const descriptor = (await this.options.liveDescriptors(meshName, signal)).find(value => String(value.rid) === authenticatedRid && value.state === 1);
|
|
2065
|
+
if (descriptor === undefined
|
|
2066
|
+
|| expected !== undefined && (descriptor.ownerId !== expected.ownerId
|
|
2067
|
+
|| descriptor.leaseGeneration !== expected.leaseGeneration
|
|
2068
|
+
|| String(descriptor.rid) !== expected.nodeRid
|
|
2069
|
+
|| descriptor.lifecycleGeneration !== expected.nodeGeneration)) {
|
|
2070
|
+
throw new Error('Relocation reply relay peer is not the exact admitted source fence.');
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
targetReplyRelayCoordinator(meshName, stage, authority) {
|
|
2074
|
+
const status = this.requireMeshNode(meshName).status();
|
|
2075
|
+
const owner = this.options.currentOwner();
|
|
2076
|
+
const target = stage.offer.prepare.target;
|
|
2077
|
+
if (owner === undefined
|
|
2078
|
+
|| owner.ownerId !== target.ownerId
|
|
2079
|
+
|| owner.leaseGeneration !== target.ownerLeaseGeneration
|
|
2080
|
+
|| String(status.routingId) !== target.nodeRid
|
|
2081
|
+
|| status.lifecycleGeneration !== target.nodeGeneration) {
|
|
2082
|
+
throw new Error('Relocation reply relay target coordinator fence is stale.');
|
|
2083
|
+
}
|
|
2084
|
+
return {
|
|
2085
|
+
ownerId: owner.ownerId,
|
|
2086
|
+
leaseGeneration: owner.leaseGeneration,
|
|
2087
|
+
nodeRid: String(status.routingId),
|
|
2088
|
+
nodeGeneration: status.lifecycleGeneration,
|
|
2089
|
+
expectedAuthorityStoreVersion: authority.storeVersion.value
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
async relayTerminalReplies(meshName, stage, coordinator, signal) {
|
|
2093
|
+
for (const [participantIndex, participant] of stage.staging.envelope.participants.entries()) {
|
|
2094
|
+
const hidden = stage.staging.hidden.get(participant.key);
|
|
2095
|
+
if (hidden?.actor === undefined)
|
|
2096
|
+
continue;
|
|
2097
|
+
for (const [resultIndex, packet] of hidden.replayPackets.entries()) {
|
|
2098
|
+
if (!packet.returnResponse || packet.source === undefined)
|
|
2099
|
+
continue;
|
|
2100
|
+
const result = hidden.replayResults.at(resultIndex);
|
|
2101
|
+
if (result === undefined || result.index !== packet.index) {
|
|
2102
|
+
throw new Error('Relocation request result changed after target dispatch.');
|
|
2103
|
+
}
|
|
2104
|
+
const completion = {
|
|
2105
|
+
index: packet.index,
|
|
2106
|
+
operationId: packet.messageFollowContext.operationId,
|
|
2107
|
+
source: packet.source,
|
|
2108
|
+
result
|
|
2109
|
+
};
|
|
2110
|
+
await this.sendReplyRelay(meshName, packet.source.nodeRid, maintenanceReplyRelay(stage, coordinator, BigInt(participantIndex + 1), completion), handoffSourceFence(packet.source), signal);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
async exactSourceLeaseExpired(source, signal) {
|
|
2115
|
+
const lease = await this.requireLocationStore().readOwnerLease(source.ownerId, signal);
|
|
2116
|
+
return lease.kind === 'missing'
|
|
2117
|
+
|| lease.token.ownerId !== source.ownerId
|
|
2118
|
+
|| lease.token.leaseGeneration !== source.leaseGeneration
|
|
2119
|
+
|| lease.leaseExpiresAt.getTime() <= lease.storeNow.getTime();
|
|
2120
|
+
}
|
|
2121
|
+
async publishSessionRoutes(staging) {
|
|
2122
|
+
for (const hidden of staging.hidden.values()) {
|
|
2123
|
+
if (hidden.actor !== undefined) {
|
|
2124
|
+
await this.options.actorTransfer.publishRoutedActorOwnership(hidden.actor);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
async sendControl(meshName, targetNodeRid, request, signal, deadlineAtMs = performance.now() + 30_000, sideband = []) {
|
|
2129
|
+
const key = controlAckKey(request);
|
|
2130
|
+
const frames = sideband.length === 0
|
|
2131
|
+
? [(0, service_relocation_control_1.encodeServiceRelocationControlRequest)(request)]
|
|
2132
|
+
: [(0, service_relocation_control_1.encodeServiceRelocationControlRequest)(request), ...sideband];
|
|
2133
|
+
let lastRequestError;
|
|
2134
|
+
for (;;) {
|
|
2135
|
+
signal?.throwIfAborted();
|
|
2136
|
+
const remainingMs = deadlineAtMs - performance.now();
|
|
2137
|
+
if (remainingMs <= 0) {
|
|
2138
|
+
throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.DeadlineExceeded, `Relocation control ACK '${key}' timed out.`, true, lastRequestError);
|
|
2139
|
+
}
|
|
2140
|
+
const attemptTimeoutMs = Math.max(1, Math.min(250, Math.ceil(remainingMs)));
|
|
2141
|
+
const startedAtMs = performance.now();
|
|
2142
|
+
try {
|
|
2143
|
+
const replyFrames = await this.requestInfrastructureControl(meshName, targetNodeRid, frames, attemptTimeoutMs);
|
|
2144
|
+
if (replyFrames.length !== 1) {
|
|
2145
|
+
throw new service_wire_m6a_codec_1.ServiceWireProtocolError(`Relocation control '${key}' reply must contain exactly one frame.`);
|
|
2146
|
+
}
|
|
2147
|
+
const response = (0, service_relocation_control_1.decodeServiceRelocationControlResponse)(Buffer.from(replyFrames[0]));
|
|
2148
|
+
if (response.kind === 'failed') {
|
|
2149
|
+
validateControlFailureResponse(request, response);
|
|
2150
|
+
throw relocationFailureException(response);
|
|
2151
|
+
}
|
|
2152
|
+
validateControlResponse(request, response);
|
|
2153
|
+
return response;
|
|
2154
|
+
}
|
|
2155
|
+
catch (error) {
|
|
2156
|
+
if (error instanceof service_wire_m6a_codec_1.ServiceWireProtocolError
|
|
2157
|
+
|| error instanceof contracts_1.ZLinkFrameworkException) {
|
|
2158
|
+
throw error;
|
|
2159
|
+
}
|
|
2160
|
+
lastRequestError = error;
|
|
2161
|
+
relocationDebug('control.request_retry', {
|
|
2162
|
+
key,
|
|
2163
|
+
kind: request.kind,
|
|
2164
|
+
targetRid: String(targetNodeRid)
|
|
2165
|
+
});
|
|
2166
|
+
const retryDelayMs = Math.min(Math.max(0, attemptTimeoutMs - (performance.now() - startedAtMs)), Math.max(0, deadlineAtMs - performance.now()));
|
|
2167
|
+
if (retryDelayMs > 0) {
|
|
2168
|
+
await waitForRelocationRetry(retryDelayMs, signal);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
async selectTarget(meshName, localRid, targetApplicationVersion, requirements = [], signal) {
|
|
2174
|
+
const descriptors = await this.options.liveDescriptors(meshName, signal);
|
|
2175
|
+
const local = this.options.localDescriptor?.(meshName)
|
|
2176
|
+
?? descriptors.find(descriptor => String(descriptor.rid) === localRid);
|
|
2177
|
+
const requiredVersion = targetApplicationVersion ?? local?.applicationVersion;
|
|
2178
|
+
const peers = this.requireMeshNode(meshName).peers();
|
|
2179
|
+
const candidates = descriptors
|
|
2180
|
+
.filter(descriptor => String(descriptor.rid) !== localRid
|
|
2181
|
+
&& descriptor.state === contracts_1.ZLinkFrameworkRuntimeState.Serving
|
|
2182
|
+
&& descriptor.objectRole === contracts_1.ZLinkObjectRole.Server
|
|
2183
|
+
&& descriptor.placementWeight > 0
|
|
2184
|
+
&& (requiredVersion === undefined
|
|
2185
|
+
|| descriptor.applicationVersion === requiredVersion)
|
|
2186
|
+
&& (local?.maintenanceWave === undefined
|
|
2187
|
+
|| descriptor.maintenanceWave !== local.maintenanceWave)
|
|
2188
|
+
&& peers.some(peer => peer.routingId !== null
|
|
2189
|
+
&& String(peer.routingId) === String(descriptor.rid)
|
|
2190
|
+
&& peer.lifecycleGeneration === descriptor.lifecycleGeneration
|
|
2191
|
+
&& peer.state === 3)
|
|
2192
|
+
&& relocationTargetSupports(descriptor, requirements))
|
|
2193
|
+
.sort((left, right) => String(left.rid).localeCompare(String(right.rid)));
|
|
2194
|
+
const target = selectWeightedRelocationTarget(candidates);
|
|
2195
|
+
if (target === undefined) {
|
|
2196
|
+
// Spec 15 §"Failed.Kind": no compatible target node is Unavailable, the
|
|
2197
|
+
// same classification the session-seal control path uses for a target
|
|
2198
|
+
// it cannot reach.
|
|
2199
|
+
throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RelocationTargetUnavailable, `RouteMesh '${meshName}' has no relocation target.`, true);
|
|
2200
|
+
}
|
|
2201
|
+
return target;
|
|
2202
|
+
}
|
|
2203
|
+
async readTargetParticipantAuthorities(envelope, signal) {
|
|
2204
|
+
const authorities = new Map();
|
|
2205
|
+
for (const participant of envelope.participants) {
|
|
2206
|
+
const current = await requireAuthority(this.requireLocationStore(), { value: participant.key }, signal);
|
|
2207
|
+
if (current.objectGeneration !== participant.objectGeneration
|
|
2208
|
+
|| current.authorityOwnerGeneration !== participant.authorityOwnerGeneration
|
|
2209
|
+
|| current.allocation.objectKind !== participant.objectKind
|
|
2210
|
+
|| current.allocation.stableType !== participant.stableType
|
|
2211
|
+
|| current.allocation.state !== 'active') {
|
|
2212
|
+
throw new Error(`Relocation participant '${participant.key}' authority changed before reservation.`);
|
|
2213
|
+
}
|
|
2214
|
+
authorities.set(participant.key, current);
|
|
2215
|
+
}
|
|
2216
|
+
return authorities;
|
|
2217
|
+
}
|
|
2218
|
+
async reserveTargetForPrepare(meshName, prepare, envelope, targetPort, signal) {
|
|
2219
|
+
const authorities = await this.readTargetParticipantAuthorities(envelope, signal);
|
|
2220
|
+
const publication = relocationPublication(prepare, envelope);
|
|
2221
|
+
// Carries the exact reference + CRC of a deferred-Join publication the
|
|
2222
|
+
// target reservation must keep connected through the CAS (spec 28 —
|
|
2223
|
+
// clearing/replacing it here would strand the journal's own
|
|
2224
|
+
// prepared->committed transition with nothing left for it to find).
|
|
2225
|
+
const retainedDeferredJoinRoots = new Map();
|
|
2226
|
+
for (const participant of envelope.participants) {
|
|
2227
|
+
const staged = targetPort.deferredJoinRoot(participant.key);
|
|
2228
|
+
if (staged !== undefined) {
|
|
2229
|
+
retainedDeferredJoinRoots.set(participant.key, {
|
|
2230
|
+
reference: staged.reference.value,
|
|
2231
|
+
checksumCrc32c: staged.checksumCrc32c
|
|
2232
|
+
});
|
|
2233
|
+
continue;
|
|
2234
|
+
}
|
|
2235
|
+
const current = this.codec.read(authorities.get(participant.key).payload);
|
|
2236
|
+
if (participant.objectKind === 'actor'
|
|
2237
|
+
&& current?.canonical === true
|
|
2238
|
+
&& await this.options.actorTransfer.isDeferredJoinAcceptedRootPublication(current.reference, current.checksumCrc32c, {
|
|
2239
|
+
authorityKey: participant.key,
|
|
2240
|
+
objectKind: 'actor',
|
|
2241
|
+
objectGeneration: participant.objectGeneration,
|
|
2242
|
+
aggregateId: current.aggregateId,
|
|
2243
|
+
aggregateGeneration: current.aggregateGeneration
|
|
2244
|
+
}, signal)) {
|
|
2245
|
+
retainedDeferredJoinRoots.set(participant.key, {
|
|
2246
|
+
reference: current.reference,
|
|
2247
|
+
checksumCrc32c: current.checksumCrc32c
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
const participants = envelope.participants.map(participant => {
|
|
2252
|
+
const expected = authorities.get(participant.key);
|
|
2253
|
+
const membership = actorMembershipTarget(envelope, participant.key);
|
|
2254
|
+
const entrySpotId = expected.allocation.objectKind === 'actor'
|
|
2255
|
+
&& membership === undefined
|
|
2256
|
+
? this.options.spotManager()?.entrySpotIdForMesh(meshName)
|
|
2257
|
+
: undefined;
|
|
2258
|
+
if (expected.allocation.objectKind === 'actor'
|
|
2259
|
+
&& membership === undefined
|
|
2260
|
+
&& entrySpotId === undefined) {
|
|
2261
|
+
throw new Error(`Relocation target RouteMesh '${meshName}' has no Entry Spot.`);
|
|
2262
|
+
}
|
|
2263
|
+
const retainedDeferredJoinRoot = retainedDeferredJoinRoots.get(participant.key);
|
|
2264
|
+
const participantPublication = retainedDeferredJoinRoot === undefined
|
|
2265
|
+
? publication
|
|
2266
|
+
: {
|
|
2267
|
+
...publication,
|
|
2268
|
+
reference: retainedDeferredJoinRoot.reference,
|
|
2269
|
+
checksumCrc32c: retainedDeferredJoinRoot.checksumCrc32c
|
|
2270
|
+
};
|
|
2271
|
+
return {
|
|
2272
|
+
key: { value: participant.key },
|
|
2273
|
+
expected,
|
|
2274
|
+
ownerTransition: 'newOwner',
|
|
2275
|
+
authorityPayload: this.authorityPayloadForPublication(expected.payload, participantPublication, {
|
|
2276
|
+
owner: {
|
|
2277
|
+
ownerId: prepare.target.ownerId,
|
|
2278
|
+
leaseGeneration: prepare.target.ownerLeaseGeneration
|
|
2279
|
+
},
|
|
2280
|
+
meshName,
|
|
2281
|
+
nodeRid: prepare.target.nodeRid,
|
|
2282
|
+
nodeGeneration: prepare.target.nodeGeneration,
|
|
2283
|
+
objectGeneration: expected.objectGeneration,
|
|
2284
|
+
targetAttemptGeneration: prepare.targetAttemptGeneration,
|
|
2285
|
+
coordinatorExpectedStoreVersion: expected.storeVersion.value,
|
|
2286
|
+
...(membership === undefined
|
|
2287
|
+
? expected.allocation.objectKind === 'actor'
|
|
2288
|
+
? {
|
|
2289
|
+
actorSpotId: entrySpotId,
|
|
2290
|
+
actorSpotGeneration: prepare.target.nodeGeneration,
|
|
2291
|
+
actorSpotKind: contracts_1.ZLinkSpotKind.Entry
|
|
2292
|
+
}
|
|
2293
|
+
: {}
|
|
2294
|
+
: {
|
|
2295
|
+
...membership,
|
|
2296
|
+
actorSpotKind: contracts_1.ZLinkSpotKind.User
|
|
2297
|
+
})
|
|
2298
|
+
}, retainedDeferredJoinRoot !== undefined),
|
|
2299
|
+
membershipMutation: encodeMembershipMutation(envelope.memberships, participant.key)
|
|
2300
|
+
};
|
|
2301
|
+
});
|
|
2302
|
+
const plan = {
|
|
2303
|
+
envelope,
|
|
2304
|
+
participants,
|
|
2305
|
+
targetDescriptor: { meshName, rid: prepare.target.nodeRid },
|
|
2306
|
+
targetDescriptorLifecycleGeneration: prepare.target.nodeGeneration,
|
|
2307
|
+
capacity: participants.reduce((sum, participant) => addCapacity(sum, participant.expected.allocation.capacity), { actors: 0, spots: 0 }),
|
|
2308
|
+
targetOwner: {
|
|
2309
|
+
ownerId: prepare.target.ownerId,
|
|
2310
|
+
leaseGeneration: prepare.target.ownerLeaseGeneration
|
|
2311
|
+
}
|
|
2312
|
+
};
|
|
2313
|
+
return {
|
|
2314
|
+
prepared: await new service_relocation_aggregate_committer_1.ServiceRelocationAggregateCommitter(this.requireLocationStore())
|
|
2315
|
+
.prepare(plan, signal)
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
authorityPayloadForPublication(payload, publication, target, replacesDeferredJoinRoot = false) {
|
|
2319
|
+
const existing = this.codec.read(payload);
|
|
2320
|
+
const canonicalIdentity = (0, service_relocation_runtime_1.serviceRelocationAuthoritySlotIdentity)(payload);
|
|
2321
|
+
const replacesCanonicalJournal = replacesDeferredJoinRoot
|
|
2322
|
+
&& existing?.canonical === true;
|
|
2323
|
+
if (canonicalIdentity !== undefined
|
|
2324
|
+
&& (existing === undefined
|
|
2325
|
+
|| existing.canonical === true
|
|
2326
|
+
&& (existing.aggregateId === publication.aggregateId
|
|
2327
|
+
|| replacesCanonicalJournal))) {
|
|
2328
|
+
const published = existing === undefined
|
|
2329
|
+
? this.codec.publish(payload, publication)
|
|
2330
|
+
: replacesCanonicalJournal
|
|
2331
|
+
&& existing.aggregateId !== publication.aggregateId
|
|
2332
|
+
? this.codec.publish(this.codec.clear(payload, existing.reference), publication)
|
|
2333
|
+
: payload;
|
|
2334
|
+
if (target === undefined)
|
|
2335
|
+
return published;
|
|
2336
|
+
return (0, service_relocation_runtime_1.projectServiceRelocationAuthorityTargetReady)((0, service_relocation_runtime_1.replaceServiceRelocationAuthorityApplicationPayload)(published, rewriteAuthorityApplicationRoute(published, target)), {
|
|
2337
|
+
targetAttemptGeneration: target.targetAttemptGeneration,
|
|
2338
|
+
nodeRid: target.nodeRid,
|
|
2339
|
+
nodeGeneration: target.nodeGeneration,
|
|
2340
|
+
ownerId: target.owner.ownerId,
|
|
2341
|
+
ownerLeaseGeneration: target.owner.leaseGeneration,
|
|
2342
|
+
coordinatorExpectedStoreVersion: target.coordinatorExpectedStoreVersion
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
if (existing === undefined) {
|
|
2346
|
+
const published = this.codec.publish(payload, publication);
|
|
2347
|
+
if (target === undefined) {
|
|
2348
|
+
return published;
|
|
2349
|
+
}
|
|
2350
|
+
if ((0, service_relocation_runtime_1.serviceRelocationAuthoritySlotIdentity)(published) !== undefined) {
|
|
2351
|
+
return (0, service_relocation_runtime_1.projectServiceRelocationAuthorityTargetReady)((0, service_relocation_runtime_1.replaceServiceRelocationAuthorityApplicationPayload)(published, rewriteAuthorityApplicationRoute(published, target)), {
|
|
2352
|
+
targetAttemptGeneration: target.targetAttemptGeneration,
|
|
2353
|
+
nodeRid: target.nodeRid,
|
|
2354
|
+
nodeGeneration: target.nodeGeneration,
|
|
2355
|
+
ownerId: target.owner.ownerId,
|
|
2356
|
+
ownerLeaseGeneration: target.owner.leaseGeneration,
|
|
2357
|
+
coordinatorExpectedStoreVersion: target.coordinatorExpectedStoreVersion
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
return this.codec.publish(rewriteAuthorityApplicationRoute(payload, target), publication);
|
|
2361
|
+
}
|
|
2362
|
+
if (existing.reference !== publication.reference
|
|
2363
|
+
|| existing.checksumCrc32c !== publication.checksumCrc32c
|
|
2364
|
+
|| existing.aggregateId !== publication.aggregateId
|
|
2365
|
+
|| existing.aggregateGeneration !== publication.aggregateGeneration
|
|
2366
|
+
|| existing.inventoryDigest !== publication.inventoryDigest
|
|
2367
|
+
|| existing.targetOwnerId !== publication.targetOwnerId
|
|
2368
|
+
|| existing.targetOwnerLeaseGeneration
|
|
2369
|
+
!== publication.targetOwnerLeaseGeneration) {
|
|
2370
|
+
throw new Error('Relocation authority contains another published root '
|
|
2371
|
+
+ `(current=${existing.aggregateId}/${existing.aggregateGeneration}`
|
|
2372
|
+
+ `/${existing.reference}/canonical=${existing.canonical === true}, `
|
|
2373
|
+
+ `requested=${publication.aggregateId}/${publication.aggregateGeneration}`
|
|
2374
|
+
+ `/${publication.reference}).`);
|
|
2375
|
+
}
|
|
2376
|
+
return payload;
|
|
2377
|
+
}
|
|
2378
|
+
async abortTargetReservation(reservation, signal) {
|
|
2379
|
+
await new service_relocation_aggregate_committer_1.ServiceRelocationAggregateCommitter(this.requireLocationStore())
|
|
2380
|
+
.abort(reservation.prepared, signal);
|
|
2381
|
+
}
|
|
2382
|
+
async abortTargetStage(stage, signal) {
|
|
2383
|
+
if (stage.fallback !== undefined)
|
|
2384
|
+
clearTimeout(stage.fallback);
|
|
2385
|
+
const failures = [];
|
|
2386
|
+
try {
|
|
2387
|
+
await this.abortTargetReservation(stage.offer.reservation, signal);
|
|
2388
|
+
}
|
|
2389
|
+
catch (error) {
|
|
2390
|
+
failures.push(error);
|
|
2391
|
+
}
|
|
2392
|
+
try {
|
|
2393
|
+
await stage.owner.abort(stage.staging);
|
|
2394
|
+
}
|
|
2395
|
+
catch (error) {
|
|
2396
|
+
failures.push(error);
|
|
2397
|
+
}
|
|
2398
|
+
stage.phase = 'failed';
|
|
2399
|
+
if (failures.length === 1)
|
|
2400
|
+
throw failures[0];
|
|
2401
|
+
if (failures.length > 1) {
|
|
2402
|
+
throw new AggregateError(failures, 'Relocation target rollback was incomplete.');
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
async commitTargetReservation(stage, reservation, signal) {
|
|
2406
|
+
return await this.commitTargetAggregate(stage, reservation.prepared, signal);
|
|
2407
|
+
}
|
|
2408
|
+
async commitTargetAggregate(stage, prepared, signal) {
|
|
2409
|
+
let firstError;
|
|
2410
|
+
for (;;) {
|
|
2411
|
+
signal?.throwIfAborted();
|
|
2412
|
+
let result;
|
|
2413
|
+
try {
|
|
2414
|
+
result = await this.requireLocationStore().commitAggregate(prepared.fence, signal);
|
|
2415
|
+
}
|
|
2416
|
+
catch (error) {
|
|
2417
|
+
firstError ??= error;
|
|
2418
|
+
}
|
|
2419
|
+
if (result?.kind === 'stale' || result?.kind === 'generationExhausted') {
|
|
2420
|
+
throw new Error(`location_update_failed: relocation aggregate commit returned ${result.kind}.`, { cause: firstError });
|
|
2421
|
+
}
|
|
2422
|
+
const observed = await this.readAggregateForCommitRetry(prepared, signal);
|
|
2423
|
+
if (observed.kind === 'committed') {
|
|
2424
|
+
const primary = observed.authorities.get(stage.staging.primaryAuthorityKey.value);
|
|
2425
|
+
if (primary === undefined) {
|
|
2426
|
+
throw new Error('Committed relocation aggregate omitted its primary authority.');
|
|
2427
|
+
}
|
|
2428
|
+
return primary;
|
|
2429
|
+
}
|
|
2430
|
+
if (observed.kind === 'stale') {
|
|
2431
|
+
throw new Error('location_update_failed: relocation aggregate source authority became stale.', {
|
|
2432
|
+
cause: firstError
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
await waitForLocationRetry(stage.offer.restoreDeadlineAtMs, signal, firstError);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
async readAggregateForCommitRetry(prepared, signal) {
|
|
2439
|
+
const authorities = new Map();
|
|
2440
|
+
let source = 0;
|
|
2441
|
+
let committed = 0;
|
|
2442
|
+
try {
|
|
2443
|
+
for (const participant of prepared.plan.participants) {
|
|
2444
|
+
const current = await this.requireLocationStore().readAuthority(participant.key, signal);
|
|
2445
|
+
if (current.kind !== 'snapshot')
|
|
2446
|
+
return { kind: 'stale' };
|
|
2447
|
+
if (isExactCommittedTarget(current, participant.expected, {
|
|
2448
|
+
nodeRid: String(prepared.plan.targetDescriptor.rid),
|
|
2449
|
+
nodeGeneration: prepared.plan.targetDescriptorLifecycleGeneration,
|
|
2450
|
+
ownerId: prepared.plan.targetOwner.ownerId,
|
|
2451
|
+
ownerLeaseGeneration: prepared.plan.targetOwner.leaseGeneration
|
|
2452
|
+
}, participant.authorityPayload)) {
|
|
2453
|
+
committed += 1;
|
|
2454
|
+
authorities.set(participant.key.value, current);
|
|
2455
|
+
}
|
|
2456
|
+
else if (isExactSourceAuthority(current, participant.expected)) {
|
|
2457
|
+
source += 1;
|
|
2458
|
+
}
|
|
2459
|
+
else {
|
|
2460
|
+
return { kind: 'stale' };
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
catch {
|
|
2465
|
+
return { kind: 'unknown' };
|
|
2466
|
+
}
|
|
2467
|
+
if (committed === prepared.plan.participants.length) {
|
|
2468
|
+
return { kind: 'committed', authorities };
|
|
2469
|
+
}
|
|
2470
|
+
return source === prepared.plan.participants.length
|
|
2471
|
+
? { kind: 'source' }
|
|
2472
|
+
: { kind: 'stale' };
|
|
2473
|
+
}
|
|
2474
|
+
async materializeTargetEnvelope(meshName, envelope, signal) {
|
|
2475
|
+
const target = new LocalTargetPort(this.options, meshName, envelope);
|
|
2476
|
+
const owner = new service_relocation_object_owner_1.ServiceRelocationObjectRestoreOwner(target, value => ({ value }));
|
|
2477
|
+
const staging = await owner.prepare(envelope, signal);
|
|
2478
|
+
if (staging.id !== `${envelope.aggregateId}:${envelope.aggregateGeneration}`) {
|
|
2479
|
+
throw new Error('Relocation materialization returned a different staging identity.');
|
|
2480
|
+
}
|
|
2481
|
+
return { owner, staging, target };
|
|
2482
|
+
}
|
|
2483
|
+
/** Maps schema ordinals to the authoritative Location Store inventory. */
|
|
2484
|
+
async resolveTargetParticipantInventory(request, envelope, signal) {
|
|
2485
|
+
if (!envelope.participants.some(value => value.rootSpotId !== undefined)) {
|
|
2486
|
+
return envelope;
|
|
2487
|
+
}
|
|
2488
|
+
const store = this.requireLocationStore();
|
|
2489
|
+
const manager = this.options.spotManager();
|
|
2490
|
+
const primaryKey = request.object.kind === 'actor'
|
|
2491
|
+
? (0, authority_key_codec_1.encodeAuthorityKey)('actor', request.object.actorId)
|
|
2492
|
+
: (0, authority_key_codec_1.encodeAuthorityKey)(request.object.kind === 'userSpot' ? 'user_spot' : 'instance_spot', request.object.spotId);
|
|
2493
|
+
const primary = await requireAuthority(store, primaryKey, signal);
|
|
2494
|
+
const entries = [
|
|
2495
|
+
{ key: primaryKey, snapshot: primary }
|
|
2496
|
+
];
|
|
2497
|
+
if (request.object.kind === 'userSpot' || request.object.kind === 'instanceSpot') {
|
|
2498
|
+
let cursor;
|
|
2499
|
+
for (;;) {
|
|
2500
|
+
const page = await store.listAuthorities('', cursor, 1000, signal);
|
|
2501
|
+
if (page.kind === 'scanExpired') {
|
|
2502
|
+
throw new Error('Location Store authority scan expired during relocation.');
|
|
2503
|
+
}
|
|
2504
|
+
for (const candidate of page.items) {
|
|
2505
|
+
if (candidate.key.value === primaryKey.value
|
|
2506
|
+
|| (0, authority_key_codec_1.decodeAuthorityKey)(candidate.key).kind !== 'actor')
|
|
2507
|
+
continue;
|
|
2508
|
+
const actor = (0, actor_authority_publication_1.decodeRelocatingActorAuthorityIdentity)(candidate.snapshot.payload, candidate.snapshot.objectGeneration);
|
|
2509
|
+
if (actor?.spotId === request.object.spotId) {
|
|
2510
|
+
entries.push(candidate);
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
if (page.nextCursor === undefined)
|
|
2514
|
+
break;
|
|
2515
|
+
cursor = page.nextCursor;
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
entries.sort((left, right) => Buffer.compare(Buffer.from(left.key.value, 'utf8'), Buffer.from(right.key.value, 'utf8')));
|
|
2519
|
+
if (new Set(entries.map(entry => entry.key.value)).size !== entries.length) {
|
|
2520
|
+
throw new Error('Location Store relocation inventory has duplicate authority keys.');
|
|
2521
|
+
}
|
|
2522
|
+
const root = envelope.participants.find(value => value.rootSpotId !== undefined);
|
|
2523
|
+
const expectedRootId = request.object.kind === 'actor'
|
|
2524
|
+
? request.object.actorId
|
|
2525
|
+
: request.object.spotId;
|
|
2526
|
+
if (root === undefined
|
|
2527
|
+
|| root.rootSpotId !== expectedRootId
|
|
2528
|
+
|| root.rootObjectKind !== primary.allocation.objectKind
|
|
2529
|
+
|| root.rootSpotGeneration !== primary.objectGeneration
|
|
2530
|
+
|| root.rootObjectKind !== 'instance_spot'
|
|
2531
|
+
&& root.rootOwnerGeneration !== primary.authorityOwnerGeneration) {
|
|
2532
|
+
throw new Error('Relocation root object projection does not match Location Store.');
|
|
2533
|
+
}
|
|
2534
|
+
const transferred = new Map(envelope.participants.map(participant => [participant.participantId, participant]));
|
|
2535
|
+
if (transferred.size !== envelope.participants.length || transferred.size !== entries.length) {
|
|
2536
|
+
throw new Error('Relocation payload participant inventory does not match Location Store.');
|
|
2537
|
+
}
|
|
2538
|
+
const participants = entries.map(({ key, snapshot }, index) => {
|
|
2539
|
+
const participantId = BigInt(index + 1);
|
|
2540
|
+
const transferredParticipant = transferred.get(participantId);
|
|
2541
|
+
if (transferredParticipant === undefined) {
|
|
2542
|
+
throw new Error('Relocation payload participant identity does not match Location Store.');
|
|
2543
|
+
}
|
|
2544
|
+
return {
|
|
2545
|
+
...transferredParticipant,
|
|
2546
|
+
key: key.value,
|
|
2547
|
+
objectKind: snapshot.allocation.objectKind,
|
|
2548
|
+
stableType: snapshot.allocation.stableType,
|
|
2549
|
+
objectGeneration: snapshot.objectGeneration,
|
|
2550
|
+
authorityOwnerGeneration: snapshot.authorityOwnerGeneration
|
|
2551
|
+
};
|
|
2552
|
+
});
|
|
2553
|
+
// Actor-root saved work is admitted against the destination membership.
|
|
2554
|
+
// The canonical root intentionally omits a membership vector; meanwhile
|
|
2555
|
+
// Location Store still names the source Spot until target cutover. Rebuild
|
|
2556
|
+
// the Actor lane from the already accepted ActorJoin admission, preserving
|
|
2557
|
+
// the target queue boundary for decoded saved-work.
|
|
2558
|
+
const memberships = request.object.kind === 'actor'
|
|
2559
|
+
? actorJoinTargetMembership(manager, envelope.aggregateId, participants)
|
|
2560
|
+
: entries
|
|
2561
|
+
.filter(entry => entry.snapshot.allocation.objectKind === 'actor')
|
|
2562
|
+
.map(entry => ({
|
|
2563
|
+
actorKey: entry.key.value,
|
|
2564
|
+
spotKey: primaryKey.value,
|
|
2565
|
+
spotObjectGeneration: primary.objectGeneration,
|
|
2566
|
+
membershipEpoch: 1n
|
|
2567
|
+
}));
|
|
2568
|
+
return {
|
|
2569
|
+
...envelope,
|
|
2570
|
+
participants,
|
|
2571
|
+
memberships
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
spotKind(meshName, activation) {
|
|
2575
|
+
const node = this.options.registration.spotNodes.get(meshName);
|
|
2576
|
+
const registrations = activation.domain.kind === 'user'
|
|
2577
|
+
? node?.spotFactoryRegistrations
|
|
2578
|
+
: node?.instanceSpotFactoryRegistrations;
|
|
2579
|
+
return Object.values(registrations ?? {})
|
|
2580
|
+
.some(value => value.implementation === activation.spotType)
|
|
2581
|
+
? activation.domain.kind === 'user' ? 'user_spot' : 'instance_spot'
|
|
2582
|
+
: undefined;
|
|
2583
|
+
}
|
|
2584
|
+
spotRegistration(meshName, kind, stableType) {
|
|
2585
|
+
const node = this.options.registration.spotNodes.get(meshName);
|
|
2586
|
+
const value = kind === 'user_spot'
|
|
2587
|
+
? node?.spotFactoryRegistrations?.[stableType]
|
|
2588
|
+
: node?.instanceSpotFactoryRegistrations?.[stableType];
|
|
2589
|
+
if (value === undefined)
|
|
2590
|
+
throw new Error(`Relocation Spot type '${stableType}' is not registered.`);
|
|
2591
|
+
return value;
|
|
2592
|
+
}
|
|
2593
|
+
actorRegistration(meshName, stableType) {
|
|
2594
|
+
const value = this.options.registration.spotNodes.get(meshName)
|
|
2595
|
+
?.actorFactoryRegistrations?.[stableType];
|
|
2596
|
+
if (value === undefined)
|
|
2597
|
+
throw new Error(`Relocation Actor type '${stableType}' is not registered.`);
|
|
2598
|
+
return value;
|
|
2599
|
+
}
|
|
2600
|
+
async captureApplication(policy, value, signal) {
|
|
2601
|
+
if (policy.kind === 'disabled') {
|
|
2602
|
+
throw new Error(`Relocation is disabled for '${value.constructor?.name ?? 'object'}'.`);
|
|
2603
|
+
}
|
|
2604
|
+
if (policy.kind === 'recreate')
|
|
2605
|
+
return Buffer.alloc(0);
|
|
2606
|
+
const adapter = await this.createRelocationAdapter(policy.adapterType);
|
|
2607
|
+
const captured = await (0, relocation_state_adapter_1.captureRelocationAdapterState)(adapter, value, signal ?? new AbortController().signal);
|
|
2608
|
+
if (captured.byteLength > RELOCATION_PARTICIPANT_STATE_LIMIT_BYTES) {
|
|
2609
|
+
throw new ZLinkRelocationStateIncompatibleError('Relocation application state exceeds the 64 MiB participant limit.');
|
|
2610
|
+
}
|
|
2611
|
+
return captured;
|
|
2612
|
+
}
|
|
2613
|
+
async createRelocationAdapter(adapterType) {
|
|
2614
|
+
return await (0, spot_provider_1.createProviderInstance)(adapterType, this.options.providerResolver);
|
|
2615
|
+
}
|
|
2616
|
+
recordRelocationInterruption(meshName, unitKind, executionMode, startedAt, outcome) {
|
|
2617
|
+
const seconds = Math.max(0, performance.now() - startedAt) / 1000;
|
|
2618
|
+
const attributes = {
|
|
2619
|
+
unit_kind: unitKind,
|
|
2620
|
+
...(executionMode === undefined ? {} : { execution_mode: executionMode })
|
|
2621
|
+
};
|
|
2622
|
+
this.options.metrics?.duration('zlink.relocation.interruption', seconds, attributes);
|
|
2623
|
+
this.options.metrics?.count('zlink.relocation.completed', 1, {
|
|
2624
|
+
mesh_name: meshName,
|
|
2625
|
+
object_kind: unitKind,
|
|
2626
|
+
policy: unitKind === 'user_spot' && executionMode === 'per_actor'
|
|
2627
|
+
? 'recreate'
|
|
2628
|
+
: 'configured',
|
|
2629
|
+
outcome
|
|
2630
|
+
});
|
|
2631
|
+
if (seconds <= 1)
|
|
2632
|
+
return;
|
|
2633
|
+
console.warn(`[zlink.runtime.relocation.changed] ${unitKind} admission interruption ` +
|
|
2634
|
+
`exceeded 1 second (${seconds.toFixed(3)}s).`);
|
|
2635
|
+
void this.options.runtimeEventPublisher?.publish({
|
|
2636
|
+
sourceName: 'zlink.runtime.relocation',
|
|
2637
|
+
timestamp: new Date(),
|
|
2638
|
+
identifier: 'zlink.runtime.relocation.changed',
|
|
2639
|
+
meshName,
|
|
2640
|
+
unitKind,
|
|
2641
|
+
executionMode,
|
|
2642
|
+
interruptionTargetExceeded: true,
|
|
2643
|
+
durationSeconds: seconds
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
sealSpotMessageFollow(meshName, authority, activation) {
|
|
2647
|
+
const node = this.requireMeshNode(meshName);
|
|
2648
|
+
const seal = node.sealSpotMessageFollowIngress;
|
|
2649
|
+
const abort = node.abortSpotMessageFollowIngress;
|
|
2650
|
+
const commit = node.commitSpotMessageFollowIngress;
|
|
2651
|
+
if (seal === undefined && abort === undefined && commit === undefined)
|
|
2652
|
+
return undefined;
|
|
2653
|
+
if (seal === undefined || abort === undefined || commit === undefined) {
|
|
2654
|
+
throw new Error('Spot Message Follow backend support is incomplete.');
|
|
2655
|
+
}
|
|
2656
|
+
const status = node.status();
|
|
2657
|
+
const result = seal.call(node, {
|
|
2658
|
+
spot: {
|
|
2659
|
+
spotId: String(activation.spotId),
|
|
2660
|
+
generation: authority.objectGeneration
|
|
2661
|
+
},
|
|
2662
|
+
targetNodeRid: String(status.routingId),
|
|
2663
|
+
targetNodeGeneration: status.lifecycleGeneration,
|
|
2664
|
+
authorityOwnerGeneration: authority.authorityOwnerGeneration,
|
|
2665
|
+
ownerLeaseGeneration: authority.ownerLeaseGeneration,
|
|
2666
|
+
storeVersion: authority.storeVersion.value
|
|
2667
|
+
});
|
|
2668
|
+
if (result === undefined) {
|
|
2669
|
+
throw new Error(`Spot '${String(activation.spotId)}' ingress could not be sealed for relocation.`);
|
|
2670
|
+
}
|
|
2671
|
+
return result;
|
|
2672
|
+
}
|
|
2673
|
+
async commitSpotMessageFollow(meshName, seal, authorityKey, activation, target) {
|
|
2674
|
+
const node = this.requireMeshNode(meshName);
|
|
2675
|
+
const commit = node.commitSpotMessageFollowIngress;
|
|
2676
|
+
if (commit === undefined) {
|
|
2677
|
+
throw new Error('Spot Message Follow backend support is incomplete.');
|
|
2678
|
+
}
|
|
2679
|
+
const committed = await requireAuthority(this.requireLocationStore(), authorityKey);
|
|
2680
|
+
const accepted = await commit.call(node, seal, {
|
|
2681
|
+
spot: {
|
|
2682
|
+
spotId: String(activation.spotId),
|
|
2683
|
+
generation: committed.objectGeneration
|
|
2684
|
+
},
|
|
2685
|
+
targetNodeRid: String(target.rid),
|
|
2686
|
+
targetNodeGeneration: target.lifecycleGeneration,
|
|
2687
|
+
authorityOwnerGeneration: committed.authorityOwnerGeneration,
|
|
2688
|
+
ownerLeaseGeneration: committed.ownerLeaseGeneration,
|
|
2689
|
+
storeVersion: committed.storeVersion.value
|
|
2690
|
+
}, this.relocationLimits().messageFollowDurationMs);
|
|
2691
|
+
if (!accepted) {
|
|
2692
|
+
throw new Error(`Spot '${String(activation.spotId)}' Message Follow route became stale.`);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
requireLocationStore() {
|
|
2696
|
+
const value = this.options.locationStore();
|
|
2697
|
+
if (value === undefined)
|
|
2698
|
+
throw new Error('Host relocation requires a Location Store.');
|
|
2699
|
+
return value;
|
|
2700
|
+
}
|
|
2701
|
+
requireSpotManager() {
|
|
2702
|
+
const value = this.options.spotManager();
|
|
2703
|
+
if (value === undefined)
|
|
2704
|
+
throw new Error('Host relocation requires the Spot manager.');
|
|
2705
|
+
return value;
|
|
2706
|
+
}
|
|
2707
|
+
requireActorManager() {
|
|
2708
|
+
const value = this.options.actorManager();
|
|
2709
|
+
if (value === undefined)
|
|
2710
|
+
throw new Error('Host relocation requires the Actor manager.');
|
|
2711
|
+
return value;
|
|
2712
|
+
}
|
|
2713
|
+
relocationLimits() {
|
|
2714
|
+
const configured = this.options.registration?.locations?.options;
|
|
2715
|
+
return {
|
|
2716
|
+
...Options_1.zlinkRuntimeDefaultLocationOptions,
|
|
2717
|
+
...(configured ?? {})
|
|
2718
|
+
};
|
|
2719
|
+
}
|
|
2720
|
+
requireMeshNode(meshName) {
|
|
2721
|
+
const value = this.options.meshNode(meshName);
|
|
2722
|
+
if (value === undefined)
|
|
2723
|
+
throw new Error(`RouteMesh '${meshName}' is not started.`);
|
|
2724
|
+
return value;
|
|
2725
|
+
}
|
|
2726
|
+
async sendInfrastructureControl(meshName, targetNodeRid, record) {
|
|
2727
|
+
const node = this.requireMeshNode(meshName);
|
|
2728
|
+
const send = node.sendInfrastructureControl;
|
|
2729
|
+
if (send === undefined) {
|
|
2730
|
+
// Compatibility backends may expose raw RouteMesh delivery through the
|
|
2731
|
+
// ordinary node-send port. Production Node backends provide the
|
|
2732
|
+
// dedicated service-control API above, so this branch neither changes
|
|
2733
|
+
// their framing nor turns a missing send into an immediate terminal:
|
|
2734
|
+
// command 42 still owns its configured deadline while admission waits.
|
|
2735
|
+
if (Array.isArray(record)) {
|
|
2736
|
+
return await node.sendToNode(targetNodeRid, record.map(frame => Buffer.from(frame)));
|
|
2737
|
+
}
|
|
2738
|
+
return await node.sendToNode(targetNodeRid, Buffer.from(record));
|
|
2739
|
+
}
|
|
2740
|
+
if (Array.isArray(record)) {
|
|
2741
|
+
const sendFrames = node.sendInfrastructureControlFrames;
|
|
2742
|
+
if (sendFrames !== undefined) {
|
|
2743
|
+
return await sendFrames.call(node, targetNodeRid, record);
|
|
2744
|
+
}
|
|
2745
|
+
// Legacy in-memory backends have no multipart ingress. They exercise
|
|
2746
|
+
// the canonical frame only; production Node backends implement the
|
|
2747
|
+
// explicit multipart capability above.
|
|
2748
|
+
return await send.call(node, targetNodeRid, record[0]);
|
|
2749
|
+
}
|
|
2750
|
+
return await send.call(node, targetNodeRid, record);
|
|
2751
|
+
}
|
|
2752
|
+
async requestInfrastructureControl(meshName, targetNodeRid, frames, timeoutMs) {
|
|
2753
|
+
const node = this.requireMeshNode(meshName);
|
|
2754
|
+
const request = node.requestInfrastructureControlFrames;
|
|
2755
|
+
if (request === undefined) {
|
|
2756
|
+
throw new Error('RouteMesh backend cannot submit bare infrastructure requests.');
|
|
2757
|
+
}
|
|
2758
|
+
return await request.call(node, targetNodeRid, frames, { timeoutMs });
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
exports.ZLinkHostServiceRelocationRuntime = ZLinkHostServiceRelocationRuntime;
|
|
2762
|
+
function canonicalActorJoinRecovery(participants) {
|
|
2763
|
+
const matches = participants.flatMap(participant => participant.queuedMessages.flatMap(message => {
|
|
2764
|
+
const recovery = (0, actor_join_recovery_codec_1.decodeCanonicalActorJoinRecoverySavedWork)(message.payload);
|
|
2765
|
+
return recovery === undefined ? [] : [recovery];
|
|
2766
|
+
}));
|
|
2767
|
+
if (matches.length > 1) {
|
|
2768
|
+
throw new Error('Actor relocation contains duplicate canonical Join recovery records.');
|
|
2769
|
+
}
|
|
2770
|
+
return matches[0];
|
|
2771
|
+
}
|
|
2772
|
+
function actorJoinAdmissionIdentity(envelope) {
|
|
2773
|
+
return canonicalActorJoinRecovery(envelope.participants)?.request.handoffId
|
|
2774
|
+
?? envelope.aggregateId;
|
|
2775
|
+
}
|
|
2776
|
+
function actorJoinTargetMembership(manager, relocationId, participants) {
|
|
2777
|
+
const recovery = canonicalActorJoinRecovery(participants);
|
|
2778
|
+
const admissionId = recovery?.request.handoffId ?? relocationId;
|
|
2779
|
+
const admission = manager?.formalRemoteActorAdmissions?.get(admissionId);
|
|
2780
|
+
const actor = participants.find(value => value.objectKind === 'actor');
|
|
2781
|
+
if (admission === undefined || actor === undefined) {
|
|
2782
|
+
throw new Error(`Actor relocation '${relocationId}' has no accepted target membership.`);
|
|
2783
|
+
}
|
|
2784
|
+
return [{
|
|
2785
|
+
actorKey: actor.key,
|
|
2786
|
+
spotKey: (0, authority_key_codec_1.encodeAuthorityKey)('user_spot', String(admission.admission.spotId)).value,
|
|
2787
|
+
spotObjectGeneration: admission.admission.targetSpotGeneration,
|
|
2788
|
+
membershipEpoch: admission.admission.expectedMembershipEpoch
|
|
2789
|
+
}];
|
|
2790
|
+
}
|
|
2791
|
+
function relocationDebug(marker, detail) {
|
|
2792
|
+
if (process.env.ZLINK_DEBUG_FRAMEWORK_RELOCATION !== '1')
|
|
2793
|
+
return;
|
|
2794
|
+
console.error('[zlink.runtime.relocation]', marker, detail);
|
|
2795
|
+
}
|
|
2796
|
+
class LocalTargetPort {
|
|
2797
|
+
options;
|
|
2798
|
+
meshName;
|
|
2799
|
+
envelope;
|
|
2800
|
+
deferredJoinRoots = new Map();
|
|
2801
|
+
constructor(options, meshName, envelope) {
|
|
2802
|
+
this.options = options;
|
|
2803
|
+
this.meshName = meshName;
|
|
2804
|
+
this.envelope = envelope;
|
|
2805
|
+
}
|
|
2806
|
+
async createHidden(participant, signal) {
|
|
2807
|
+
const identity = (0, authority_key_codec_1.decodeAuthorityKey)({ value: participant.key }).globalId;
|
|
2808
|
+
if (participant.objectKind === 'actor') {
|
|
2809
|
+
const membership = this.envelope.memberships.find(value => value.actorKey === participant.key);
|
|
2810
|
+
if (membership === undefined)
|
|
2811
|
+
throw new Error(`Actor '${identity}' relocation membership is missing.`);
|
|
2812
|
+
const spotIdentity = (0, authority_key_codec_1.decodeAuthorityKey)({ value: membership.spotKey }).globalId;
|
|
2813
|
+
const localDescriptor = this.options.localDescriptor?.(this.meshName);
|
|
2814
|
+
const nativeEntrySpot = localDescriptor?.entrySpotId === spotIdentity;
|
|
2815
|
+
const actor = await this.requireActorManager().prepareRelocationActor(identity, participant.stableType, participant.objectGeneration, participant.authorityOwnerGeneration + 1n, (nativeEntrySpot ? localDescriptor.rid : spotIdentity), nativeEntrySpot ? localDescriptor.lifecycleGeneration : membership.spotObjectGeneration, membership.membershipEpoch, signal);
|
|
2816
|
+
return {
|
|
2817
|
+
authorityKey: participant.key,
|
|
2818
|
+
participant,
|
|
2819
|
+
actor,
|
|
2820
|
+
initialized: false,
|
|
2821
|
+
restoredTimers: [],
|
|
2822
|
+
replayResults: [],
|
|
2823
|
+
replayPackets: []
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
2826
|
+
const registration = this.registration(participant.objectKind, participant.stableType);
|
|
2827
|
+
const activation = await this.requireSpotManager().prepareRelocationSpot(this.meshName, participant.objectKind, participant.stableType, registration.implementation, identity, participant.objectGeneration, participant.authorityOwnerGeneration + 1n, signal);
|
|
2828
|
+
return {
|
|
2829
|
+
authorityKey: participant.key,
|
|
2830
|
+
participant,
|
|
2831
|
+
activation,
|
|
2832
|
+
initialized: false,
|
|
2833
|
+
restoredTimers: [],
|
|
2834
|
+
replayResults: [],
|
|
2835
|
+
replayPackets: []
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
async restoreApplicationState(hidden, payload, signal) {
|
|
2839
|
+
const registration = this.registration(hidden.participant.objectKind, hidden.participant.stableType);
|
|
2840
|
+
if (registration.relocation.kind === 'recreate') {
|
|
2841
|
+
if (payload.byteLength !== 0)
|
|
2842
|
+
throw new Error('Recreate relocation contains snapshot state.');
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
if (registration.relocation.kind !== 'snapshot') {
|
|
2846
|
+
throw new Error(`Relocation is disabled for '${hidden.participant.stableType}'.`);
|
|
2847
|
+
}
|
|
2848
|
+
const adapter = await (0, spot_provider_1.createProviderInstance)(registration.relocation.adapterType, this.options.providerResolver);
|
|
2849
|
+
const instance = (hidden.actor ?? hidden.activation?.spot);
|
|
2850
|
+
await (0, relocation_state_adapter_1.restoreRelocationAdapterState)(adapter, instance, payload, signal ?? new AbortController().signal);
|
|
2851
|
+
}
|
|
2852
|
+
async restoreMemberships(hidden, memberships) {
|
|
2853
|
+
for (const membership of memberships) {
|
|
2854
|
+
const actor = hidden.get(membership.actorKey)?.actor;
|
|
2855
|
+
const spotIdentity = (0, authority_key_codec_1.decodeAuthorityKey)({ value: membership.spotKey }).globalId;
|
|
2856
|
+
const spot = hidden.get(membership.spotKey)?.activation
|
|
2857
|
+
?? this.requireSpotManager().resolveRelocationActivation(this.meshName, spotIdentity);
|
|
2858
|
+
if (actor === undefined)
|
|
2859
|
+
throw new Error('Relocation membership Actor staging is missing.');
|
|
2860
|
+
const state = this.requireActorManager().getState(actor.context.actorId);
|
|
2861
|
+
const localDescriptor = this.options.localDescriptor?.(this.meshName);
|
|
2862
|
+
const isEntryMembership = localDescriptor?.entrySpotId === spotIdentity;
|
|
2863
|
+
if (isEntryMembership) {
|
|
2864
|
+
// Entry Spot identity is published as a Location membership key, but
|
|
2865
|
+
// it is not a User Spot activation in the Actor runtime. The native
|
|
2866
|
+
// restore already received the target MeshNode RID as its Entry
|
|
2867
|
+
// membership; clearing the framework Spot state keeps dispatch on the
|
|
2868
|
+
// local Entry Spot path after the target commit.
|
|
2869
|
+
state.clearJoinedSpot();
|
|
2870
|
+
}
|
|
2871
|
+
else {
|
|
2872
|
+
state.setJoinedSpot(spotIdentity, spot?.spot, membership.membershipEpoch);
|
|
2873
|
+
}
|
|
2874
|
+
spot?.commitActorJoin(actor);
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
async publish(hidden, authority) {
|
|
2878
|
+
if (hidden.actor !== undefined) {
|
|
2879
|
+
const store = this.requireLocationStore();
|
|
2880
|
+
const current = await requireAuthority(store, { value: hidden.authorityKey });
|
|
2881
|
+
const actorId = hidden.actor.context.actorId;
|
|
2882
|
+
const node = this.options.meshNode(this.meshName);
|
|
2883
|
+
const restore = node?.restoreActorAuthority;
|
|
2884
|
+
if (node === undefined || restore === undefined) {
|
|
2885
|
+
throw new Error(`Relocation Actor '${actorId}' native authority publication is unavailable.`);
|
|
2886
|
+
}
|
|
2887
|
+
const native = node.actorLookup(actorId);
|
|
2888
|
+
restore.call(node, actorId, hidden.participant.stableType, current.objectGeneration, current.authorityOwnerGeneration, String(native.spotId), native.spotGeneration, native.membershipEpoch);
|
|
2889
|
+
this.requireActorManager().adoptCreatedAuthority(actorId, current.authorityOwnerGeneration, current.ownerLeaseGeneration);
|
|
2890
|
+
hidden.targetAuthorityOwnerGeneration = current.authorityOwnerGeneration;
|
|
2891
|
+
}
|
|
2892
|
+
else if (hidden.activation !== undefined) {
|
|
2893
|
+
const registration = this.registration(hidden.participant.objectKind, hidden.participant.stableType);
|
|
2894
|
+
const applicationSignaled = hidden.participant.objectKind === 'user_spot'
|
|
2895
|
+
&& registration.options?.relocationCoordinationMode
|
|
2896
|
+
=== contracts_1.ZLinkSpotRelocationCoordinationMode.ApplicationSignaled;
|
|
2897
|
+
await this.requireSpotManager().publishRelocationSpot(hidden.activation, applicationSignaled
|
|
2898
|
+
? () => hidden.activation.notifyRelocatedBoundary()
|
|
2899
|
+
: undefined);
|
|
2900
|
+
this.options.meshNode(this.meshName)?.rememberSpotRoute?.({
|
|
2901
|
+
spot: {
|
|
2902
|
+
spotId: String(hidden.activation.spotId),
|
|
2903
|
+
generation: authority.objectGeneration
|
|
2904
|
+
},
|
|
2905
|
+
targetNodeRid: String(authority.allocation.descriptor.rid),
|
|
2906
|
+
targetNodeGeneration: authority.allocation.descriptorLifecycleGeneration,
|
|
2907
|
+
authorityOwnerGeneration: authority.authorityOwnerGeneration,
|
|
2908
|
+
ownerLeaseGeneration: authority.ownerLeaseGeneration,
|
|
2909
|
+
storeVersion: authority.storeVersion.value
|
|
2910
|
+
});
|
|
2911
|
+
if (hidden.participant.objectKind === 'instance_spot') {
|
|
2912
|
+
this.options.trackInstanceSpot?.({
|
|
2913
|
+
meshName: this.meshName,
|
|
2914
|
+
spotId: hidden.activation.spotId,
|
|
2915
|
+
stableType: hidden.participant.stableType,
|
|
2916
|
+
nodeRid: authority.allocation.descriptor.rid,
|
|
2917
|
+
nodeGeneration: authority.allocation.descriptorLifecycleGeneration,
|
|
2918
|
+
objectGeneration: authority.objectGeneration,
|
|
2919
|
+
authorityOwnerGeneration: authority.authorityOwnerGeneration,
|
|
2920
|
+
ownerId: authority.ownerId,
|
|
2921
|
+
ownerLeaseGeneration: authority.ownerLeaseGeneration,
|
|
2922
|
+
storeVersion: authority.storeVersion.value,
|
|
2923
|
+
deactivate: () => this.requireSpotManager().abortRelocationSpot(hidden.activation)
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
hidden.initialized = true;
|
|
2928
|
+
}
|
|
2929
|
+
async restoreBoundSession(hidden, payload) {
|
|
2930
|
+
if (hidden.actor === undefined || payload.byteLength === 0)
|
|
2931
|
+
return;
|
|
2932
|
+
const target = decodeActorSession(payload);
|
|
2933
|
+
const state = this.requireActorManager().getState(hidden.actor.context.actorId);
|
|
2934
|
+
state.setRemoteBoundSessionTarget(target);
|
|
2935
|
+
state.setBoundSessionTransferTarget(target);
|
|
2936
|
+
if (target.bindingGeneration !== undefined) {
|
|
2937
|
+
state.setBoundSessionBindingGeneration(target.bindingGeneration);
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
async replayQueuedMessage(hidden, message) {
|
|
2941
|
+
if (hidden.actor === undefined) {
|
|
2942
|
+
throw new Error('Only Actor relocation participants can contain packet backlog.');
|
|
2943
|
+
}
|
|
2944
|
+
const recovery = (0, actor_join_recovery_codec_1.decodeCanonicalActorJoinRecoverySavedWork)(message.payload);
|
|
2945
|
+
if (recovery !== undefined) {
|
|
2946
|
+
if (recovery.request.actorId !== hidden.actor.context.actorId) {
|
|
2947
|
+
throw new Error('Canonical Actor Join recovery names a different staged Actor.');
|
|
2948
|
+
}
|
|
2949
|
+
const deferred = await this.requireSpotManager().restoreCanonicalActorJoinRecovery(recovery, undefined, (0, service_relocation_runtime_1.inventoryDigest)(this.envelope.participants, this.envelope.memberships));
|
|
2950
|
+
if (deferred !== undefined) {
|
|
2951
|
+
this.deferredJoinRoots.set(hidden.authorityKey, deferred);
|
|
2952
|
+
}
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
const packet = decodeQueuedHandoffPacket(message);
|
|
2956
|
+
const state = this.requireActorManager().getState(hidden.actor.context.actorId);
|
|
2957
|
+
if (state === undefined)
|
|
2958
|
+
throw new Error('Relocated Actor state is not staged.');
|
|
2959
|
+
hidden.replayPackets.push(packet);
|
|
2960
|
+
}
|
|
2961
|
+
deferredJoinRoot(authorityKey) {
|
|
2962
|
+
return this.deferredJoinRoots.get(authorityKey);
|
|
2963
|
+
}
|
|
2964
|
+
async openAdmission(hidden) {
|
|
2965
|
+
if (hidden.actor === undefined)
|
|
2966
|
+
return;
|
|
2967
|
+
this.requireActorManager().publishRelocationActor(hidden.actor.context.actorId);
|
|
2968
|
+
if (hidden.replayPackets.length === 0)
|
|
2969
|
+
return;
|
|
2970
|
+
const state = this.requireActorManager().getState(hidden.actor.context.actorId);
|
|
2971
|
+
if (state === undefined)
|
|
2972
|
+
throw new Error('Relocated Actor state is not staged.');
|
|
2973
|
+
const results = await (0, actor_handoff_1.replayActorHandoffBacklog)(hidden.replayPackets, (parts, returnResponse, remoteBoundSessionTarget, fallbackActorRef) => {
|
|
2974
|
+
if (state.spotId === undefined) {
|
|
2975
|
+
const runtime = this.options.spotNodeRuntime();
|
|
2976
|
+
if (runtime === undefined) {
|
|
2977
|
+
throw new Error('Relocated Entry Spot Actor dispatch is unavailable.');
|
|
2978
|
+
}
|
|
2979
|
+
return runtime.dispatchEntryActorPacket(hidden.actor.context.actorId, parts, returnResponse, remoteBoundSessionTarget, fallbackActorRef);
|
|
2980
|
+
}
|
|
2981
|
+
return this.requireSpotManager().dispatchRoutedActorPacket(state.spotId, hidden.actor.context.actorId, parts, returnResponse, remoteBoundSessionTarget, fallbackActorRef);
|
|
2982
|
+
});
|
|
2983
|
+
hidden.replayResults.push(...results);
|
|
2984
|
+
}
|
|
2985
|
+
async restoreTimer(hidden, timer) {
|
|
2986
|
+
if (hidden.activation === undefined)
|
|
2987
|
+
throw new Error('Actor relocation cannot restore a Spot timer.');
|
|
2988
|
+
hidden.restoredTimers.push(timer);
|
|
2989
|
+
if (hidden.restoredTimers.length !== hidden.participant.timers.length)
|
|
2990
|
+
return;
|
|
2991
|
+
hidden.activation.timers.restoreRelocation(hidden.restoredTimers.map(value => ({
|
|
2992
|
+
name: value.timerId,
|
|
2993
|
+
handlerType: value.handlerType,
|
|
2994
|
+
periodMs: value.intervalMs,
|
|
2995
|
+
overrunPolicy: value.overrunPolicy,
|
|
2996
|
+
maxCatchUpTicks: value.maxCatchUpTicks,
|
|
2997
|
+
stopOnUnhandledException: value.stopOnUnhandledException,
|
|
2998
|
+
startedAtUnixMs: value.startedAtUnixMs,
|
|
2999
|
+
deliveryIndex: value.deliveryIndex,
|
|
3000
|
+
lastScheduledIndex: value.lastScheduledIndex,
|
|
3001
|
+
nextDueAtUnixMs: value.dueAtUnixMs,
|
|
3002
|
+
pendingTicks: value.pendingTicks
|
|
3003
|
+
})));
|
|
3004
|
+
}
|
|
3005
|
+
async normalize(_hidden) { }
|
|
3006
|
+
async abort(hidden) {
|
|
3007
|
+
if (hidden.actor !== undefined) {
|
|
3008
|
+
await this.requireActorManager().abortRelocationActor(hidden.actor.context.actorId);
|
|
3009
|
+
}
|
|
3010
|
+
else if (hidden.activation !== undefined) {
|
|
3011
|
+
await this.requireSpotManager().abortRelocationSpot(hidden.activation);
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
registration(kind, stableType) {
|
|
3015
|
+
const node = this.options.registration.spotNodes.get(this.meshName);
|
|
3016
|
+
const value = kind === 'actor'
|
|
3017
|
+
? node?.actorFactoryRegistrations?.[stableType]
|
|
3018
|
+
: kind === 'user_spot'
|
|
3019
|
+
? node?.spotFactoryRegistrations?.[stableType]
|
|
3020
|
+
: node?.instanceSpotFactoryRegistrations?.[stableType];
|
|
3021
|
+
if (value === undefined)
|
|
3022
|
+
throw new Error(`Relocation type '${stableType}' is not registered.`);
|
|
3023
|
+
return value;
|
|
3024
|
+
}
|
|
3025
|
+
requireLocationStore() {
|
|
3026
|
+
const store = this.options.locationStore();
|
|
3027
|
+
if (store === undefined)
|
|
3028
|
+
throw new Error('Relocation target Location Store is unavailable.');
|
|
3029
|
+
return store;
|
|
3030
|
+
}
|
|
3031
|
+
requireSpotManager() {
|
|
3032
|
+
const manager = this.options.spotManager();
|
|
3033
|
+
if (manager === undefined)
|
|
3034
|
+
throw new Error('Relocation target Spot manager is unavailable.');
|
|
3035
|
+
return manager;
|
|
3036
|
+
}
|
|
3037
|
+
requireActorManager() {
|
|
3038
|
+
const manager = this.options.actorManager();
|
|
3039
|
+
if (manager === undefined)
|
|
3040
|
+
throw new Error('Relocation target Actor manager is unavailable.');
|
|
3041
|
+
return manager;
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
async function requireAuthority(store, key, signal) {
|
|
3045
|
+
const current = await store.readAuthority(key, signal);
|
|
3046
|
+
if (current.kind !== 'snapshot')
|
|
3047
|
+
throw new Error(`Authority '${key.value}' is missing.`);
|
|
3048
|
+
return current;
|
|
3049
|
+
}
|
|
3050
|
+
function rewriteAuthorityApplicationRoute(payload, target) {
|
|
3051
|
+
const applicationPayload = (0, service_relocation_runtime_1.serviceRelocationAuthorityApplicationPayload)(payload);
|
|
3052
|
+
const actor = (0, actor_authority_publication_1.decodeRelocatingActorAuthorityIdentity)(applicationPayload, target.objectGeneration);
|
|
3053
|
+
if (actor !== undefined) {
|
|
3054
|
+
return (0, actor_authority_publication_1.rewriteActorAuthorityRoute)(applicationPayload, {
|
|
3055
|
+
actorId: actor.actor.actorId,
|
|
3056
|
+
objectGeneration: actor.actor.objectGeneration,
|
|
3057
|
+
meshName: target.meshName,
|
|
3058
|
+
nodeRid: target.nodeRid
|
|
3059
|
+
}, target.actorSpotId ?? actor.spotId, target.actorSpotGeneration ?? actor.spotGeneration, target.actorSpotKind ?? actor.spotKind, target.nodeGeneration, target.owner);
|
|
3060
|
+
}
|
|
3061
|
+
return (0, service_authority_payload_codec_1.rewriteServiceAuthorityRoute)(applicationPayload, target.owner, target)
|
|
3062
|
+
?? applicationPayload;
|
|
3063
|
+
}
|
|
3064
|
+
function actorMembershipTarget(envelope, actorKey) {
|
|
3065
|
+
const membership = envelope.memberships.find(value => value.actorKey === actorKey);
|
|
3066
|
+
if (membership === undefined)
|
|
3067
|
+
return undefined;
|
|
3068
|
+
return {
|
|
3069
|
+
actorSpotId: (0, authority_key_codec_1.decodeAuthorityKey)({ value: membership.spotKey }).globalId,
|
|
3070
|
+
actorSpotGeneration: membership.spotObjectGeneration
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
3073
|
+
function primaryKey(envelope) {
|
|
3074
|
+
return envelope.participants.find(value => value.objectKind !== 'actor')?.key
|
|
3075
|
+
?? envelope.participants[0].key;
|
|
3076
|
+
}
|
|
3077
|
+
function relocationTargetRequirements(values) {
|
|
3078
|
+
const grouped = new Map();
|
|
3079
|
+
for (const value of values) {
|
|
3080
|
+
const key = `${value.objectKind}\0${value.stableType}\0${value.policy}`;
|
|
3081
|
+
const existing = grouped.get(key);
|
|
3082
|
+
grouped.set(key, existing === undefined
|
|
3083
|
+
? value
|
|
3084
|
+
: { ...existing, count: existing.count + value.count });
|
|
3085
|
+
}
|
|
3086
|
+
return [...grouped.values()];
|
|
3087
|
+
}
|
|
3088
|
+
function relocationTargetSupports(descriptor, requirements) {
|
|
3089
|
+
if (descriptor.activationConcurrency.limit !== 0
|
|
3090
|
+
&& descriptor.activationConcurrency.active >= descriptor.activationConcurrency.limit) {
|
|
3091
|
+
return false;
|
|
3092
|
+
}
|
|
3093
|
+
const actorCount = requirements
|
|
3094
|
+
.filter(value => value.objectKind === 'actor')
|
|
3095
|
+
.reduce((sum, value) => sum + value.count, 0);
|
|
3096
|
+
const spotCount = requirements
|
|
3097
|
+
.filter(value => value.objectKind !== 'actor')
|
|
3098
|
+
.reduce((sum, value) => sum + value.count, 0);
|
|
3099
|
+
if ((descriptor.populationCapacity.actors.limit !== 0
|
|
3100
|
+
&& descriptor.populationCapacity.actors.active
|
|
3101
|
+
+ descriptor.populationCapacity.actors.reserved
|
|
3102
|
+
+ actorCount > descriptor.populationCapacity.actors.limit) || (descriptor.populationCapacity.spots.limit !== 0
|
|
3103
|
+
&& descriptor.populationCapacity.spots.active
|
|
3104
|
+
+ descriptor.populationCapacity.spots.reserved
|
|
3105
|
+
+ spotCount > descriptor.populationCapacity.spots.limit)) {
|
|
3106
|
+
return false;
|
|
3107
|
+
}
|
|
3108
|
+
for (const requirement of requirements) {
|
|
3109
|
+
if (!descriptor.objectCapabilities.some(capability => capability.objectKind === requirement.objectKind
|
|
3110
|
+
&& capability.stableType === requirement.stableType
|
|
3111
|
+
&& capability.policy === requirement.policy)) {
|
|
3112
|
+
return false;
|
|
3113
|
+
}
|
|
3114
|
+
if (requirement.objectKind !== 'actor') {
|
|
3115
|
+
const capacity = descriptor.populationCapacity.spotTypes.find(value => value.objectKind === requirement.objectKind
|
|
3116
|
+
&& value.stableType === requirement.stableType);
|
|
3117
|
+
if (capacity === undefined
|
|
3118
|
+
|| (capacity.limit !== 0
|
|
3119
|
+
&& capacity.active + capacity.reserved + requirement.count > capacity.limit)) {
|
|
3120
|
+
return false;
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
return true;
|
|
3125
|
+
}
|
|
3126
|
+
function selectWeightedRelocationTarget(candidates) {
|
|
3127
|
+
const total = candidates.reduce((sum, value) => sum + value.placementWeight, 0);
|
|
3128
|
+
if (total <= 0)
|
|
3129
|
+
return undefined;
|
|
3130
|
+
let selected = Math.random() * total;
|
|
3131
|
+
for (const candidate of candidates) {
|
|
3132
|
+
selected -= candidate.placementWeight;
|
|
3133
|
+
if (selected < 0)
|
|
3134
|
+
return candidate;
|
|
3135
|
+
}
|
|
3136
|
+
return candidates.at(-1);
|
|
3137
|
+
}
|
|
3138
|
+
function decodeQueuedHandoffPacket(message) {
|
|
3139
|
+
const payload = Buffer.from(message.payload);
|
|
3140
|
+
const frozen = isCanonicalFrozenRecord(payload)
|
|
3141
|
+
? (0, service_stateful_wire_codec_1.decodeServiceWireFrozenRecord)(payload)
|
|
3142
|
+
: undefined;
|
|
3143
|
+
const value = JSON.parse(Buffer.from(frozen?.applicationPayload?.bytes ?? payload).toString('utf8'));
|
|
3144
|
+
const index = value.index;
|
|
3145
|
+
if (!Number.isSafeInteger(index) || index < 0) {
|
|
3146
|
+
throw new TypeError('Relocation Actor packet index is invalid.');
|
|
3147
|
+
}
|
|
3148
|
+
const messageFollowOrigin = decodeQueuedMessageFollowOrigin(value.messageFollowOrigin);
|
|
3149
|
+
const decoded = (0, spot_remote_codec_1.decodeHandoffBacklog)([{
|
|
3150
|
+
...value,
|
|
3151
|
+
index: 0,
|
|
3152
|
+
...(messageFollowOrigin === undefined ? {} : { messageFollowOrigin })
|
|
3153
|
+
}])[0];
|
|
3154
|
+
return { ...decoded, index: index };
|
|
3155
|
+
}
|
|
3156
|
+
/**
|
|
3157
|
+
* The JSON relocation envelope remains the Stage 1 container, but Actor
|
|
3158
|
+
* backlog entries are frozen service-wire records before it leaves source
|
|
3159
|
+
* ownership. This makes correlation, reply route and canonical payload
|
|
3160
|
+
* validation part of the captured record rather than an ad-hoc JSON packet.
|
|
3161
|
+
*/
|
|
3162
|
+
function canonicalizeCapturedHandoffBacklog(envelope, coordinator, target) {
|
|
3163
|
+
return {
|
|
3164
|
+
...envelope,
|
|
3165
|
+
participants: envelope.participants.map(participant => participant.objectKind !== 'actor'
|
|
3166
|
+
? participant
|
|
3167
|
+
: {
|
|
3168
|
+
...participant,
|
|
3169
|
+
queuedMessages: participant.queuedMessages.map(message => ({
|
|
3170
|
+
...message,
|
|
3171
|
+
payload: canonicalQueuedFrozenRecord(participant, message, coordinator, target).canonicalBytes
|
|
3172
|
+
}))
|
|
3173
|
+
})
|
|
3174
|
+
};
|
|
3175
|
+
}
|
|
3176
|
+
function appendCanonicalActorJoinRecovery(envelope, profile, coordinator, sourceAuthoritySnapshot) {
|
|
3177
|
+
const recovery = profile.canonicalRecovery;
|
|
3178
|
+
const actorType = profile.state.actorType;
|
|
3179
|
+
const sourceAuthority = (0, actor_authority_publication_1.decodeRelocatingActorAuthorityIdentity)(sourceAuthoritySnapshot.payload, sourceAuthoritySnapshot.objectGeneration);
|
|
3180
|
+
if (actorType === undefined
|
|
3181
|
+
|| sourceAuthority === undefined
|
|
3182
|
+
|| sourceAuthority.actor.actorId !== profile.state.actorId
|
|
3183
|
+
|| sourceAuthority.actor.objectGeneration !== profile.sourceActorRef.generation
|
|
3184
|
+
|| !(0, routing_id_1.routingIdsEqual)(sourceAuthority.actor.nodeRid, profile.sourceActorRef.nodeRid)
|
|
3185
|
+
|| sourceAuthority.ownerNodeGeneration !== recovery.actorNodeGeneration
|
|
3186
|
+
|| sourceAuthoritySnapshot.authorityOwnerGeneration !== profile.state.locationGeneration) {
|
|
3187
|
+
throw new Error(`Actor '${profile.state.actorId}' canonical Join recovery identity is incomplete.`);
|
|
3188
|
+
}
|
|
3189
|
+
const attachment = { attached: false };
|
|
3190
|
+
const participants = envelope.participants.map(participant => {
|
|
3191
|
+
if (participant.objectKind !== 'actor'
|
|
3192
|
+
|| (0, authority_key_codec_1.decodeAuthorityKey)({ value: participant.key }).globalId
|
|
3193
|
+
!== profile.state.actorId) {
|
|
3194
|
+
return participant;
|
|
3195
|
+
}
|
|
3196
|
+
if (attachment.attached)
|
|
3197
|
+
throw new Error('Canonical Actor Join recovery has duplicate Actor participants.');
|
|
3198
|
+
attachment.attached = true;
|
|
3199
|
+
const encoded = (0, actor_join_recovery_codec_1.encodeCanonicalActorJoinRecoverySavedWork)({
|
|
3200
|
+
actorId: profile.state.actorId,
|
|
3201
|
+
actorType,
|
|
3202
|
+
handoffId: recovery.handoffId,
|
|
3203
|
+
// The command-28 target admitted this same fenced Store authority. Keep
|
|
3204
|
+
// its CurrentSpotId as the recovery identity instead of projecting the
|
|
3205
|
+
// application Actor state (which may already name an Entry fallback).
|
|
3206
|
+
sourceSpotId: sourceAuthority.spotId,
|
|
3207
|
+
sourceNodeRid: profile.sourceActorRef.nodeRid,
|
|
3208
|
+
actorGeneration: profile.sourceActorRef.generation,
|
|
3209
|
+
actorAuthorityOwnerGeneration: sourceAuthoritySnapshot.authorityOwnerGeneration,
|
|
3210
|
+
actorNodeGeneration: recovery.actorNodeGeneration,
|
|
3211
|
+
expectedOwnerLeaseGeneration: recovery.expectedOwnerLeaseGeneration,
|
|
3212
|
+
relocationId: envelope.aggregateId,
|
|
3213
|
+
relocationContentType: profile.relocationContentType,
|
|
3214
|
+
requestContentType: recovery.requestContentType,
|
|
3215
|
+
request: recovery.request,
|
|
3216
|
+
targetSpotId: String(profile.targetSpotId),
|
|
3217
|
+
targetNodeRid: profile.targetNodeRid,
|
|
3218
|
+
targetNodeGeneration: recovery.targetNodeGeneration,
|
|
3219
|
+
targetSpotGeneration: recovery.targetSpotGeneration,
|
|
3220
|
+
targetAuthorityOwnerGeneration: recovery.targetAuthorityOwnerGeneration,
|
|
3221
|
+
targetSpotAuthorityOwnerGeneration: recovery.targetSpotAuthorityOwnerGeneration,
|
|
3222
|
+
coordinator: {
|
|
3223
|
+
ownerId: coordinator.ownerId,
|
|
3224
|
+
leaseGeneration: coordinator.leaseGeneration,
|
|
3225
|
+
nodeRid: coordinator.nodeRid,
|
|
3226
|
+
nodeGeneration: coordinator.nodeGeneration,
|
|
3227
|
+
expectedAuthorityStoreVersion: coordinator.expectedAuthorityStoreVersion
|
|
3228
|
+
},
|
|
3229
|
+
operationId: recovery.admissionOperationId,
|
|
3230
|
+
...(recovery.replyContentType === undefined
|
|
3231
|
+
? {}
|
|
3232
|
+
: { replyContentType: recovery.replyContentType }),
|
|
3233
|
+
reply: recovery.reply
|
|
3234
|
+
});
|
|
3235
|
+
return {
|
|
3236
|
+
...participant,
|
|
3237
|
+
queuedMessages: [
|
|
3238
|
+
{ sequence: 1n, payload: encoded },
|
|
3239
|
+
...participant.queuedMessages.map(message => ({
|
|
3240
|
+
...message,
|
|
3241
|
+
sequence: message.sequence + 1n
|
|
3242
|
+
}))
|
|
3243
|
+
]
|
|
3244
|
+
};
|
|
3245
|
+
});
|
|
3246
|
+
if (!attachment.attached)
|
|
3247
|
+
throw new Error('Canonical Actor Join recovery Actor participant is missing.');
|
|
3248
|
+
return { ...envelope, participants };
|
|
3249
|
+
}
|
|
3250
|
+
function isCanonicalFrozenRecord(payload) {
|
|
3251
|
+
try {
|
|
3252
|
+
(0, service_stateful_wire_codec_1.decodeServiceWireFrozenRecord)(payload);
|
|
3253
|
+
return true;
|
|
3254
|
+
}
|
|
3255
|
+
catch {
|
|
3256
|
+
return false;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
function relocationWireId(value) {
|
|
3260
|
+
const hex = value.replaceAll('-', '');
|
|
3261
|
+
if (!/^[0-9a-fA-F]{32}$/.test(hex)) {
|
|
3262
|
+
throw new Error(`Relocation aggregate '${value}' is not a canonical 128-bit identity.`);
|
|
3263
|
+
}
|
|
3264
|
+
const result = {
|
|
3265
|
+
high: BigInt(`0x${hex.slice(0, 16)}`),
|
|
3266
|
+
low: BigInt(`0x${hex.slice(16)}`)
|
|
3267
|
+
};
|
|
3268
|
+
if (result.high === 0n && result.low === 0n) {
|
|
3269
|
+
throw new Error('Relocation identity must not be zero.');
|
|
3270
|
+
}
|
|
3271
|
+
return result;
|
|
3272
|
+
}
|
|
3273
|
+
function createServiceRelocationId(isInUse, entropy = node_crypto_1.randomBytes) {
|
|
3274
|
+
for (;;) {
|
|
3275
|
+
const bytes = entropy(16);
|
|
3276
|
+
if (bytes.byteLength !== 16) {
|
|
3277
|
+
throw new Error('Relocation identity entropy must return exactly 16 bytes.');
|
|
3278
|
+
}
|
|
3279
|
+
let nonZero = false;
|
|
3280
|
+
for (const byte of bytes) {
|
|
3281
|
+
if (byte !== 0) {
|
|
3282
|
+
nonZero = true;
|
|
3283
|
+
break;
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
if (!nonZero)
|
|
3287
|
+
continue;
|
|
3288
|
+
const hex = Buffer.from(bytes).toString('hex');
|
|
3289
|
+
const candidate = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-`
|
|
3290
|
+
+ `${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
3291
|
+
if (!isInUse(candidate))
|
|
3292
|
+
return candidate;
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
function operationWireId(value) {
|
|
3296
|
+
const match = /^(\d+):(\d+)$/.exec(value);
|
|
3297
|
+
if (match === null)
|
|
3298
|
+
throw new Error(`Actor handoff operation '${value}' is not canonical.`);
|
|
3299
|
+
const result = { high: BigInt(match[1]), low: BigInt(match[2]) };
|
|
3300
|
+
if (result.high === 0n && result.low === 0n) {
|
|
3301
|
+
throw new Error('Actor handoff operation identity must not be zero.');
|
|
3302
|
+
}
|
|
3303
|
+
return result;
|
|
3304
|
+
}
|
|
3305
|
+
function sameWireId(left, right) {
|
|
3306
|
+
return left.high === right.high && left.low === right.low;
|
|
3307
|
+
}
|
|
3308
|
+
function sameCoordinator(left, right) {
|
|
3309
|
+
return left.ownerId === right.ownerId
|
|
3310
|
+
&& left.leaseGeneration === right.leaseGeneration
|
|
3311
|
+
&& left.nodeRid === right.nodeRid
|
|
3312
|
+
&& left.nodeGeneration === right.nodeGeneration
|
|
3313
|
+
&& left.expectedAuthorityStoreVersion === right.expectedAuthorityStoreVersion;
|
|
3314
|
+
}
|
|
3315
|
+
function isExactSourceAuthority(current, expected) {
|
|
3316
|
+
return current.storeVersion.value === expected.storeVersion.value
|
|
3317
|
+
&& current.objectGeneration === expected.objectGeneration
|
|
3318
|
+
&& current.authorityOwnerGeneration === expected.authorityOwnerGeneration
|
|
3319
|
+
&& current.ownerId === expected.ownerId
|
|
3320
|
+
&& current.ownerLeaseGeneration === expected.ownerLeaseGeneration
|
|
3321
|
+
&& current.allocation.state === expected.allocation.state
|
|
3322
|
+
&& current.allocation.objectKind === expected.allocation.objectKind
|
|
3323
|
+
&& current.allocation.stableType === expected.allocation.stableType
|
|
3324
|
+
&& String(current.allocation.descriptor.rid)
|
|
3325
|
+
=== String(expected.allocation.descriptor.rid)
|
|
3326
|
+
&& current.allocation.descriptorLifecycleGeneration
|
|
3327
|
+
=== expected.allocation.descriptorLifecycleGeneration;
|
|
3328
|
+
}
|
|
3329
|
+
function isExactCommittedTarget(current, expected, target, payload) {
|
|
3330
|
+
return current.objectGeneration === expected.objectGeneration
|
|
3331
|
+
&& current.authorityOwnerGeneration > expected.authorityOwnerGeneration
|
|
3332
|
+
&& current.ownerId === target.ownerId
|
|
3333
|
+
&& current.ownerLeaseGeneration === target.ownerLeaseGeneration
|
|
3334
|
+
&& String(current.allocation.descriptor.rid) === target.nodeRid
|
|
3335
|
+
&& current.allocation.descriptorLifecycleGeneration === target.nodeGeneration
|
|
3336
|
+
&& Buffer.from(current.payload).equals(Buffer.from(payload));
|
|
3337
|
+
}
|
|
3338
|
+
async function waitForLocationRetry(deadlineAtMs, signal, cause) {
|
|
3339
|
+
signal?.throwIfAborted();
|
|
3340
|
+
if (performance.now() >= deadlineAtMs) {
|
|
3341
|
+
throw new Error('location_update_failed: relocation Restore validity expired before exact target owner confirmation.', { cause });
|
|
3342
|
+
}
|
|
3343
|
+
await new Promise((resolve, reject) => {
|
|
3344
|
+
let timer;
|
|
3345
|
+
const aborted = () => {
|
|
3346
|
+
if (timer !== undefined)
|
|
3347
|
+
clearTimeout(timer);
|
|
3348
|
+
reject(signal?.reason);
|
|
3349
|
+
};
|
|
3350
|
+
signal?.addEventListener('abort', aborted, { once: true });
|
|
3351
|
+
timer = setTimeout(() => {
|
|
3352
|
+
signal?.removeEventListener('abort', aborted);
|
|
3353
|
+
resolve();
|
|
3354
|
+
}, Math.min(25, Math.max(1, deadlineAtMs - performance.now())));
|
|
3355
|
+
timer.unref();
|
|
3356
|
+
});
|
|
3357
|
+
}
|
|
3358
|
+
async function waitForRelocationRetry(delayMs, signal) {
|
|
3359
|
+
signal?.throwIfAborted();
|
|
3360
|
+
await new Promise((resolve, reject) => {
|
|
3361
|
+
let timer;
|
|
3362
|
+
const aborted = () => {
|
|
3363
|
+
if (timer !== undefined)
|
|
3364
|
+
clearTimeout(timer);
|
|
3365
|
+
reject(signal?.reason);
|
|
3366
|
+
};
|
|
3367
|
+
signal?.addEventListener('abort', aborted, { once: true });
|
|
3368
|
+
timer = setTimeout(() => {
|
|
3369
|
+
signal?.removeEventListener('abort', aborted);
|
|
3370
|
+
resolve();
|
|
3371
|
+
}, delayMs);
|
|
3372
|
+
timer.unref();
|
|
3373
|
+
});
|
|
3374
|
+
}
|
|
3375
|
+
function sameRequestSource(left, right) {
|
|
3376
|
+
return left.ownerId === right.ownerId
|
|
3377
|
+
&& left.leaseGeneration === right.leaseGeneration
|
|
3378
|
+
&& left.nodeRid === right.nodeRid
|
|
3379
|
+
&& left.nodeGeneration === right.nodeGeneration;
|
|
3380
|
+
}
|
|
3381
|
+
function handoffSourceFence(value) {
|
|
3382
|
+
return {
|
|
3383
|
+
ownerId: value.ownerId,
|
|
3384
|
+
leaseGeneration: BigInt(value.ownerLeaseGeneration),
|
|
3385
|
+
nodeRid: value.nodeRid,
|
|
3386
|
+
nodeGeneration: BigInt(value.nodeGeneration)
|
|
3387
|
+
};
|
|
3388
|
+
}
|
|
3389
|
+
function maintenanceReplyRelay(stage, coordinator, participantId, completion) {
|
|
3390
|
+
const value = completion.result.ok ? completion.result.response : undefined;
|
|
3391
|
+
return {
|
|
3392
|
+
relocation: relocationWireId(stage.staging.envelope.aggregateId),
|
|
3393
|
+
targetAttemptGeneration: stage.offer.prepare.targetAttemptGeneration,
|
|
3394
|
+
coordinator,
|
|
3395
|
+
operation: operationWireId(completion.operationId),
|
|
3396
|
+
replyRouteId: BigInt(completion.source.replyRouteId),
|
|
3397
|
+
participantId,
|
|
3398
|
+
sequence: BigInt(completion.index + 1),
|
|
3399
|
+
terminalResult: completion.result.ok ? 0 : 105,
|
|
3400
|
+
failureCode: completion.result.ok ? 0 : 17,
|
|
3401
|
+
...(value === undefined ? {} : { payload: {
|
|
3402
|
+
packetName: 'zlink.relocation.reply',
|
|
3403
|
+
contentType: 'application/json',
|
|
3404
|
+
bytes: Buffer.from(JSON.stringify(value), 'utf8')
|
|
3405
|
+
} })
|
|
3406
|
+
};
|
|
3407
|
+
}
|
|
3408
|
+
function handoffResultFromRelay(relay) {
|
|
3409
|
+
if (relay.sequence > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
3410
|
+
throw new TypeError('Relocation terminal sequence exceeds the Node index range.');
|
|
3411
|
+
}
|
|
3412
|
+
let value;
|
|
3413
|
+
if (relay.payload !== undefined) {
|
|
3414
|
+
if (relay.terminalResult !== 0
|
|
3415
|
+
|| relay.payload.packetName !== 'zlink.relocation.reply'
|
|
3416
|
+
|| relay.payload.contentType !== 'application/json') {
|
|
3417
|
+
throw new TypeError('Relocation terminal payload boundary is invalid.');
|
|
3418
|
+
}
|
|
3419
|
+
value = JSON.parse(Buffer.from(relay.payload.bytes).toString('utf8'));
|
|
3420
|
+
}
|
|
3421
|
+
const index = Number(relay.sequence - 1n);
|
|
3422
|
+
return relay.terminalResult === 0
|
|
3423
|
+
? { index, ok: true, ...(value === undefined ? {} : { response: value }) }
|
|
3424
|
+
: { index, ok: false, error: `Actor handoff failed with framework code ${relay.failureCode}.` };
|
|
3425
|
+
}
|
|
3426
|
+
function wireIdText(value) {
|
|
3427
|
+
return `${value.high}:${value.low}`;
|
|
3428
|
+
}
|
|
3429
|
+
/**
|
|
3430
|
+
* Tags a target-side restore failure as data loss (chunk assembly or
|
|
3431
|
+
* checksum verification) so relocationFailed reports failureCode 35 rather
|
|
3432
|
+
* than the generic internal-failure code (spec 15 §4).
|
|
3433
|
+
*/
|
|
3434
|
+
class ServiceRelocationDataLostError extends Error {
|
|
3435
|
+
}
|
|
3436
|
+
exports.ServiceRelocationDataLostError = ServiceRelocationDataLostError;
|
|
3437
|
+
/**
|
|
3438
|
+
* Maps a target-side relocation Prepare failure to the closest wire
|
|
3439
|
+
* framework-error code the generated schema
|
|
3440
|
+
* ({@link ServiceWireFrameworkErrorCode}) defines — mirrors the java
|
|
3441
|
+
* reference mapping (commit 97fc074058) and the dotnet/cpp ports. The wire
|
|
3442
|
+
* vocabulary predates the framework's typed error kinds and has no
|
|
3443
|
+
* one-to-one code for every kind, so several kinds share the nearest fit
|
|
3444
|
+
* (documented per case below); unresolvable vocabulary gaps belong at the
|
|
3445
|
+
* schema level, not invented here. {@link ServiceRelocationDataLostError}
|
|
3446
|
+
* is this runtime's dedicated tag for a verified checksum/assembly/digest
|
|
3447
|
+
* integrity failure — the case that keeps relocationDataLost(35). An
|
|
3448
|
+
* unclassified error carries no evidence of integrity loss and takes the
|
|
3449
|
+
* generic opaque requestFailed(17). `objectKind` (the Prepare's
|
|
3450
|
+
* ObjectFence.kind, spec 28 §4.2) picks between an Actor- and
|
|
3451
|
+
* Spot-specific code where the schema splits by object kind.
|
|
3452
|
+
*/
|
|
3453
|
+
function relocationFailedFailureCode(error, objectKind) {
|
|
3454
|
+
if (error instanceof ServiceRelocationDataLostError) {
|
|
3455
|
+
return service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.relocationDataLost;
|
|
3456
|
+
}
|
|
3457
|
+
if (!(error instanceof contracts_1.ZLinkFrameworkException)) {
|
|
3458
|
+
return service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.requestFailed;
|
|
3459
|
+
}
|
|
3460
|
+
switch (error.kind) {
|
|
3461
|
+
case contracts_1.ZLinkFrameworkErrorKind.DataLost:
|
|
3462
|
+
return service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.relocationDataLost;
|
|
3463
|
+
case contracts_1.ZLinkFrameworkErrorKind.Rejected:
|
|
3464
|
+
return service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.requestRejected;
|
|
3465
|
+
case contracts_1.ZLinkFrameworkErrorKind.ProtocolError:
|
|
3466
|
+
return service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.requestProtocolError;
|
|
3467
|
+
// No dedicated "capacity exceeded" wire code exists; a full queue is
|
|
3468
|
+
// the closest capacity-shaped signal.
|
|
3469
|
+
case contracts_1.ZLinkFrameworkErrorKind.CapacityExceeded:
|
|
3470
|
+
return service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.workerQueueFull;
|
|
3471
|
+
// No dedicated "deadline exceeded" wire code exists; a worker timeout
|
|
3472
|
+
// is the closest timeout-shaped signal.
|
|
3473
|
+
case contracts_1.ZLinkFrameworkErrorKind.DeadlineExceeded:
|
|
3474
|
+
return service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.workerTimedOut;
|
|
3475
|
+
// A stale generation/fence is the concrete cause of InvalidOperation
|
|
3476
|
+
// along this path (spec 15 failure table); pick the
|
|
3477
|
+
// object-kind-specific stale code.
|
|
3478
|
+
case contracts_1.ZLinkFrameworkErrorKind.InvalidOperation:
|
|
3479
|
+
return objectKind === 'actor'
|
|
3480
|
+
? service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.actorLocationStale
|
|
3481
|
+
: service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.spotGenerationStale;
|
|
3482
|
+
// No dedicated generic "unavailable" wire code exists; a disconnected
|
|
3483
|
+
// route is the closest "cannot reach/use the target" signal.
|
|
3484
|
+
case contracts_1.ZLinkFrameworkErrorKind.Unavailable:
|
|
3485
|
+
return service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.routeNotConnected;
|
|
3486
|
+
case contracts_1.ZLinkFrameworkErrorKind.NotFound:
|
|
3487
|
+
return service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.requestTargetNotFound;
|
|
3488
|
+
// The only "already exists" wire code is Actor-specific; not expected
|
|
3489
|
+
// along this target-failure path, mapped for completeness.
|
|
3490
|
+
case contracts_1.ZLinkFrameworkErrorKind.AlreadyExists:
|
|
3491
|
+
return service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.actorAlreadyExists;
|
|
3492
|
+
case contracts_1.ZLinkFrameworkErrorKind.TypeMismatch:
|
|
3493
|
+
return objectKind === 'actor'
|
|
3494
|
+
? service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.actorTypeMismatch
|
|
3495
|
+
: service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.spotTypeMismatch;
|
|
3496
|
+
// No dedicated "not configured" wire code exists; a missing configured
|
|
3497
|
+
// handler is the closest analog.
|
|
3498
|
+
case contracts_1.ZLinkFrameworkErrorKind.NotConfigured:
|
|
3499
|
+
return service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.handlerNotFound;
|
|
3500
|
+
// No dedicated generic "internal failure" or "shutting down" wire code
|
|
3501
|
+
// exists; the generic opaque request-failure code is the closest fit
|
|
3502
|
+
// for both (ShuttingDown included — re-judged 2026-08-19, C-10).
|
|
3503
|
+
case contracts_1.ZLinkFrameworkErrorKind.ShuttingDown:
|
|
3504
|
+
case contracts_1.ZLinkFrameworkErrorKind.InternalFailure:
|
|
3505
|
+
default:
|
|
3506
|
+
return service_wire_constants_generated_1.ServiceWireFrameworkErrorCode.requestFailed;
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
function relocationStagingId(value) {
|
|
3510
|
+
return `${value.relocation.high}:${value.relocation.low}:${value.targetAttemptGeneration}`;
|
|
3511
|
+
}
|
|
3512
|
+
function coordinatorSource(value) {
|
|
3513
|
+
return { ownerId: value.ownerId, leaseGeneration: value.leaseGeneration,
|
|
3514
|
+
nodeRid: value.nodeRid, nodeGeneration: value.nodeGeneration };
|
|
3515
|
+
}
|
|
3516
|
+
function relocationObject(envelope) {
|
|
3517
|
+
const participant = envelope.participants.find(value => value.objectKind !== 'actor')
|
|
3518
|
+
?? envelope.participants[0];
|
|
3519
|
+
const identity = (0, authority_key_codec_1.decodeAuthorityKey)({ value: participant.key }).globalId;
|
|
3520
|
+
if (participant.objectKind === 'actor') {
|
|
3521
|
+
return { kind: 'actor', actorId: identity, objectGeneration: participant.objectGeneration,
|
|
3522
|
+
expectedAuthorityOwnerGeneration: participant.authorityOwnerGeneration };
|
|
3523
|
+
}
|
|
3524
|
+
if (participant.objectKind === 'user_spot') {
|
|
3525
|
+
return { kind: 'userSpot', spotId: identity, objectGeneration: participant.objectGeneration,
|
|
3526
|
+
expectedAuthorityOwnerGeneration: participant.authorityOwnerGeneration };
|
|
3527
|
+
}
|
|
3528
|
+
return { kind: 'instanceSpot', stableType: participant.stableType,
|
|
3529
|
+
spotId: identity, objectGeneration: participant.objectGeneration };
|
|
3530
|
+
}
|
|
3531
|
+
function relocationObjectForParticipant(participant) {
|
|
3532
|
+
const identity = (0, authority_key_codec_1.decodeAuthorityKey)({ value: participant.key }).globalId;
|
|
3533
|
+
if (participant.objectKind === 'actor') {
|
|
3534
|
+
return {
|
|
3535
|
+
kind: 'actor',
|
|
3536
|
+
actorId: identity,
|
|
3537
|
+
objectGeneration: participant.objectGeneration,
|
|
3538
|
+
expectedAuthorityOwnerGeneration: participant.authorityOwnerGeneration
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
if (participant.objectKind === 'user_spot') {
|
|
3542
|
+
return {
|
|
3543
|
+
kind: 'userSpot',
|
|
3544
|
+
spotId: identity,
|
|
3545
|
+
objectGeneration: participant.objectGeneration,
|
|
3546
|
+
expectedAuthorityOwnerGeneration: participant.authorityOwnerGeneration
|
|
3547
|
+
};
|
|
3548
|
+
}
|
|
3549
|
+
return {
|
|
3550
|
+
kind: 'instanceSpot',
|
|
3551
|
+
stableType: participant.stableType,
|
|
3552
|
+
spotId: identity,
|
|
3553
|
+
objectGeneration: participant.objectGeneration
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3556
|
+
function relocationReady(request) {
|
|
3557
|
+
return {
|
|
3558
|
+
kind: 'ready',
|
|
3559
|
+
relocation: request.relocation,
|
|
3560
|
+
targetAttemptGeneration: request.targetAttemptGeneration,
|
|
3561
|
+
coordinator: request.coordinator,
|
|
3562
|
+
target: request.target,
|
|
3563
|
+
object: request.object,
|
|
3564
|
+
senderRole: 'target'
|
|
3565
|
+
};
|
|
3566
|
+
}
|
|
3567
|
+
function relocationFailed(request, failureCode) {
|
|
3568
|
+
return {
|
|
3569
|
+
kind: 'failed',
|
|
3570
|
+
relocation: request.relocation,
|
|
3571
|
+
targetAttemptGeneration: request.targetAttemptGeneration,
|
|
3572
|
+
coordinator: request.coordinator,
|
|
3573
|
+
target: request.target,
|
|
3574
|
+
object: request.object,
|
|
3575
|
+
senderRole: 'target',
|
|
3576
|
+
failureCode
|
|
3577
|
+
};
|
|
3578
|
+
}
|
|
3579
|
+
function validatePrepareEnvelope(request, envelope) {
|
|
3580
|
+
if (!sameWireId(request.relocation, relocationWireId(envelope.aggregateId))
|
|
3581
|
+
|| request.applicationVersion !== envelope.applicationVersion
|
|
3582
|
+
|| stringifyWire(request.object) !== stringifyWire(relocationObject(envelope))) {
|
|
3583
|
+
throw new Error('Relocation Prepare does not match its immutable root identity.');
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
function validateTargetOneWayControl(stage, request, sourceNodeRid) {
|
|
3587
|
+
const prepare = stage.offer.prepare;
|
|
3588
|
+
if (String(sourceNodeRid) !== stage.offer.authenticatedSourceNodeRid
|
|
3589
|
+
|| !sameWireId(request.relocation, prepare.relocation)
|
|
3590
|
+
|| request.targetAttemptGeneration !== prepare.targetAttemptGeneration
|
|
3591
|
+
|| !sameCoordinator(request.coordinator, prepare.coordinator)
|
|
3592
|
+
|| stringifyWire(request.object) !== stringifyWire(request.kind === 'data' ? request.object : prepare.object)) {
|
|
3593
|
+
throw new Error('Relocation one-way control changed its exact Prepare fence.');
|
|
3594
|
+
}
|
|
3595
|
+
if (request.kind === 'cutover'
|
|
3596
|
+
&& stringifyWire(request.object) !== stringifyWire(prepare.object)) {
|
|
3597
|
+
throw new Error('Relocation Cutover changed its prepared object.');
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
function relocationPublication(prepare, envelope) {
|
|
3601
|
+
// The direct transfer has no durable store root; the authority wrapper
|
|
3602
|
+
// keeps a synthetic in-progress marker derived from the exact relocation
|
|
3603
|
+
// identity and the payload manifest checksum.
|
|
3604
|
+
return {
|
|
3605
|
+
reference: `zlink-direct:${envelope.aggregateId}:${prepare.targetAttemptGeneration}`,
|
|
3606
|
+
checksumCrc32c: prepare.payloadChecksumCrc32c,
|
|
3607
|
+
aggregateId: envelope.aggregateId,
|
|
3608
|
+
aggregateGeneration: envelope.aggregateGeneration,
|
|
3609
|
+
targetAttemptGeneration: prepare.targetAttemptGeneration,
|
|
3610
|
+
targetNodeRid: prepare.target.nodeRid,
|
|
3611
|
+
targetNodeGeneration: prepare.target.nodeGeneration,
|
|
3612
|
+
coordinatorOwnerId: prepare.coordinator.ownerId,
|
|
3613
|
+
coordinatorLeaseGeneration: prepare.coordinator.leaseGeneration,
|
|
3614
|
+
coordinatorNodeRid: prepare.coordinator.nodeRid,
|
|
3615
|
+
coordinatorNodeGeneration: prepare.coordinator.nodeGeneration,
|
|
3616
|
+
coordinatorExpectedStoreVersion: prepare.coordinator.expectedAuthorityStoreVersion,
|
|
3617
|
+
inventoryDigest: (0, service_relocation_runtime_1.inventoryDigest)(envelope.participants, envelope.memberships),
|
|
3618
|
+
targetOwnerId: prepare.target.ownerId,
|
|
3619
|
+
targetOwnerLeaseGeneration: prepare.target.ownerLeaseGeneration
|
|
3620
|
+
};
|
|
3621
|
+
}
|
|
3622
|
+
function canonicalRelayFrozenRecord(participant, packet, coordinator, target) {
|
|
3623
|
+
const message = encodeHandoffQueuedMessages([packet])[0];
|
|
3624
|
+
return canonicalQueuedFrozenRecord(participant, message, coordinator, target);
|
|
3625
|
+
}
|
|
3626
|
+
function raceAbort(operation, signal) {
|
|
3627
|
+
if (signal.aborted)
|
|
3628
|
+
return Promise.reject(signal.reason);
|
|
3629
|
+
return new Promise((resolve, reject) => {
|
|
3630
|
+
const aborted = () => reject(signal.reason);
|
|
3631
|
+
signal.addEventListener('abort', aborted, { once: true });
|
|
3632
|
+
operation.then(value => {
|
|
3633
|
+
signal.removeEventListener('abort', aborted);
|
|
3634
|
+
resolve(value);
|
|
3635
|
+
}, error => {
|
|
3636
|
+
signal.removeEventListener('abort', aborted);
|
|
3637
|
+
reject(error);
|
|
3638
|
+
});
|
|
3639
|
+
});
|
|
3640
|
+
}
|
|
3641
|
+
function canonicalQueuedFrozenRecord(participant, message, coordinator, target) {
|
|
3642
|
+
if (participant.objectKind !== 'actor') {
|
|
3643
|
+
throw new Error('Only Actor object-mailbox participants can contain accepted packets.');
|
|
3644
|
+
}
|
|
3645
|
+
const packet = decodeQueuedHandoffPacket(message);
|
|
3646
|
+
const identity = (0, authority_key_codec_1.decodeAuthorityKey)({ value: participant.key }).globalId;
|
|
3647
|
+
const source = packet.source === undefined
|
|
3648
|
+
? coordinatorSource(coordinator)
|
|
3649
|
+
: {
|
|
3650
|
+
ownerId: packet.source.ownerId,
|
|
3651
|
+
leaseGeneration: BigInt(packet.source.ownerLeaseGeneration),
|
|
3652
|
+
nodeRid: packet.source.nodeRid,
|
|
3653
|
+
nodeGeneration: BigInt(packet.source.nodeGeneration)
|
|
3654
|
+
};
|
|
3655
|
+
const operationId = parseHandoffOperationId(packet.messageFollowContext.operationId);
|
|
3656
|
+
if (packet.returnResponse && packet.source === undefined) {
|
|
3657
|
+
throw new Error('Captured Actor request has no exact request-source fence.');
|
|
3658
|
+
}
|
|
3659
|
+
if (!packet.returnResponse && packet.source === undefined
|
|
3660
|
+
&& packet.remoteBoundSessionTarget !== undefined) {
|
|
3661
|
+
// A durable frozen record is permitted only for a lease-backed source.
|
|
3662
|
+
// Work tied to a Session connection's lifetime drains before Captured,
|
|
3663
|
+
// so freezing it under the coordinator's fence is a protocol error.
|
|
3664
|
+
throw new Error('Captured connection-bound Actor send cannot enter a relocation envelope.');
|
|
3665
|
+
}
|
|
3666
|
+
return (0, service_stateful_wire_codec_1.encodeServiceWireFrozenActorApplicationRecord)({
|
|
3667
|
+
source,
|
|
3668
|
+
target: {
|
|
3669
|
+
actorId: identity,
|
|
3670
|
+
objectGeneration: participant.objectGeneration,
|
|
3671
|
+
nodeRid: target.nodeRid,
|
|
3672
|
+
nodeGeneration: target.nodeGeneration,
|
|
3673
|
+
authorityOwnerGeneration: participant.authorityOwnerGeneration + 1n,
|
|
3674
|
+
ownerLeaseGeneration: target.ownerLeaseGeneration
|
|
3675
|
+
},
|
|
3676
|
+
operationId,
|
|
3677
|
+
...(packet.returnResponse ? { replyRouteId: BigInt(packet.source.replyRouteId) } : {}),
|
|
3678
|
+
payload: {
|
|
3679
|
+
packetName: '__zlink.actor.handoff.accepted',
|
|
3680
|
+
contentType: 'application/json',
|
|
3681
|
+
bytes: message.payload
|
|
3682
|
+
}
|
|
3683
|
+
});
|
|
3684
|
+
}
|
|
3685
|
+
function parseHandoffOperationId(value) {
|
|
3686
|
+
if (!/^[0-9a-f]{32}$/.test(value)) {
|
|
3687
|
+
throw new Error('Captured Actor operation ID is invalid.');
|
|
3688
|
+
}
|
|
3689
|
+
const operation = {
|
|
3690
|
+
high: BigInt(`0x${value.slice(0, 16)}`),
|
|
3691
|
+
low: BigInt(`0x${value.slice(16)}`)
|
|
3692
|
+
};
|
|
3693
|
+
if (operation.high === 0n && operation.low === 0n) {
|
|
3694
|
+
throw new Error('Captured Actor operation ID must not be zero.');
|
|
3695
|
+
}
|
|
3696
|
+
return operation;
|
|
3697
|
+
}
|
|
3698
|
+
function stringifyWire(value) {
|
|
3699
|
+
return JSON.stringify(value, (_key, item) => typeof item === 'bigint' ? item.toString() : item);
|
|
3700
|
+
}
|
|
3701
|
+
function validateControlResponse(request, response) {
|
|
3702
|
+
if (request.kind !== 'prepare'
|
|
3703
|
+
|| response.kind !== 'ready'
|
|
3704
|
+
|| !sameWireId(response.relocation, request.relocation)
|
|
3705
|
+
|| response.targetAttemptGeneration !== request.targetAttemptGeneration
|
|
3706
|
+
|| !sameCoordinator(response.coordinator, request.coordinator)
|
|
3707
|
+
|| stringifyWire(response.target) !== stringifyWire(request.target)
|
|
3708
|
+
|| stringifyWire(response.object) !== stringifyWire(request.object)
|
|
3709
|
+
|| response.senderRole !== 'target') {
|
|
3710
|
+
throw new Error('Relocation control response does not match the request fence.');
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
/**
|
|
3714
|
+
* Same exact-identity fence as {@link validateControlResponse}, for an
|
|
3715
|
+
* explicit Failed(53) reply. A Failed for a different relocation identity
|
|
3716
|
+
* (exact ordinal/coordinator/target/object) must never resolve a different
|
|
3717
|
+
* pending Prepare's ACK.
|
|
3718
|
+
*/
|
|
3719
|
+
function validateControlFailureResponse(request, response) {
|
|
3720
|
+
if (request.kind !== 'prepare'
|
|
3721
|
+
|| response.kind !== 'failed'
|
|
3722
|
+
|| !sameWireId(response.relocation, request.relocation)
|
|
3723
|
+
|| response.targetAttemptGeneration !== request.targetAttemptGeneration
|
|
3724
|
+
|| !sameCoordinator(response.coordinator, request.coordinator)
|
|
3725
|
+
|| stringifyWire(response.target) !== stringifyWire(request.target)
|
|
3726
|
+
|| stringifyWire(response.object) !== stringifyWire(request.object)
|
|
3727
|
+
|| response.senderRole !== 'target') {
|
|
3728
|
+
throw new Error('Relocation control response does not match the request fence.');
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
/**
|
|
3732
|
+
* Maps a relocationFailed(53) wire failureCode (the generated
|
|
3733
|
+
* ServiceWireFrameworkErrorCode vocabulary) to its internal error kind, so a
|
|
3734
|
+
* source-side reject carries the same classification the target chose
|
|
3735
|
+
* instead of a generic Error. Direct 1:1 correspondence with the generated
|
|
3736
|
+
* names; an unrecognised or absent code falls back to RequestFailed
|
|
3737
|
+
* (InternalFailure) — the closest existing kind (spec 15 §"Failed.Kind").
|
|
3738
|
+
*/
|
|
3739
|
+
function relocationFailureCodeKind(failureCode) {
|
|
3740
|
+
switch (failureCode) {
|
|
3741
|
+
case 1: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorRouteNotFound;
|
|
3742
|
+
case 2: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorCreateFailed;
|
|
3743
|
+
case 3: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorAlreadyExists;
|
|
3744
|
+
case 4: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorTypeMismatch;
|
|
3745
|
+
case 5: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.SpotCreateFailed;
|
|
3746
|
+
case 6: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.SpotRouteNotFound;
|
|
3747
|
+
case 7: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.SpotTypeMismatch;
|
|
3748
|
+
case 8: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorSessionNotBound;
|
|
3749
|
+
case 9: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.HandlerNotFound;
|
|
3750
|
+
case 10: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RouteHandlerNotFound;
|
|
3751
|
+
case 11: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorDispatchHandlerNotFound;
|
|
3752
|
+
case 12: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.PayloadDecodeFailed;
|
|
3753
|
+
case 13: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RouteNotConnected;
|
|
3754
|
+
case 14: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RequestTargetNotFound;
|
|
3755
|
+
case 15: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RequestRejected;
|
|
3756
|
+
case 16: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RequestProtocolError;
|
|
3757
|
+
case 18: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.WorkerQueueFull;
|
|
3758
|
+
case 19: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.WorkerTimedOut;
|
|
3759
|
+
case 20: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.WorkerFailed;
|
|
3760
|
+
case 21: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorLocationStale;
|
|
3761
|
+
case 22: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorCreateRejected;
|
|
3762
|
+
case 33: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.SpotGenerationStale;
|
|
3763
|
+
case 34: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.SpotMoving;
|
|
3764
|
+
case 35: return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RelocationDataLost;
|
|
3765
|
+
case 17:
|
|
3766
|
+
default:
|
|
3767
|
+
return framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RequestFailed;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
/** Builds the typed exception a Prepare waiter rejects with on an explicit Failed(53). */
|
|
3771
|
+
function relocationFailureException(response) {
|
|
3772
|
+
return (0, framework_errors_internal_1.createInternalFrameworkException)(relocationFailureCodeKind(response.failureCode), `Relocation '${response.relocation.high}:${response.relocation.low}:`
|
|
3773
|
+
+ `${response.targetAttemptGeneration}' failed with wire failureCode ${response.failureCode}.`, true);
|
|
3774
|
+
}
|
|
3775
|
+
function controlAckKey(request) {
|
|
3776
|
+
if (request.kind !== 'prepare') {
|
|
3777
|
+
throw new Error(`Relocation ${request.kind} is one-way and has no response key.`);
|
|
3778
|
+
}
|
|
3779
|
+
return `relocation:${request.relocation.high}:${request.relocation.low}:`
|
|
3780
|
+
+ `${request.targetAttemptGeneration}:ready`;
|
|
3781
|
+
}
|
|
3782
|
+
function sessionRelocationPendingKey(request) {
|
|
3783
|
+
return sessionRelocationIdentityKey(request);
|
|
3784
|
+
}
|
|
3785
|
+
function sessionRelocationRouteSubmitKey(request, targetNodeRid) {
|
|
3786
|
+
return `${sessionRelocationIdentityKey(request)}:${String(targetNodeRid)}`;
|
|
3787
|
+
}
|
|
3788
|
+
function sessionRelocationResponseKey(response) {
|
|
3789
|
+
return sessionRelocationIdentityKey(response);
|
|
3790
|
+
}
|
|
3791
|
+
function sessionRelocationResponseFingerprint(response) {
|
|
3792
|
+
return (0, service_stateful_wire_codec_1.encodeSessionRelocationSealed)(response).toString('base64');
|
|
3793
|
+
}
|
|
3794
|
+
function sessionRelocationIdentityKey(value) {
|
|
3795
|
+
return `session-relocation:${(0, service_stateful_wire_codec_1.serviceSessionRelocationIdentityKey)(value)}:sealed`;
|
|
3796
|
+
}
|
|
3797
|
+
function validateSessionRelocationResponse(request, response) {
|
|
3798
|
+
const sameCoordinator = request.coordinator.ownerId === response.coordinator.ownerId
|
|
3799
|
+
&& request.coordinator.leaseGeneration === response.coordinator.leaseGeneration
|
|
3800
|
+
&& request.coordinator.nodeRid === response.coordinator.nodeRid
|
|
3801
|
+
&& request.coordinator.nodeGeneration === response.coordinator.nodeGeneration
|
|
3802
|
+
&& request.coordinator.expectedAuthorityStoreVersion
|
|
3803
|
+
=== response.coordinator.expectedAuthorityStoreVersion;
|
|
3804
|
+
const sameSession = request.session.sessionOwnerNodeRid === response.session.sessionOwnerNodeRid
|
|
3805
|
+
&& request.session.sessionOwnerNodeGeneration
|
|
3806
|
+
=== response.session.sessionOwnerNodeGeneration
|
|
3807
|
+
&& request.session.sessionOwnerId === response.session.sessionOwnerId
|
|
3808
|
+
&& request.session.sessionOwnerLeaseGeneration
|
|
3809
|
+
=== response.session.sessionOwnerLeaseGeneration
|
|
3810
|
+
&& request.session.sessionRid === response.session.sessionRid
|
|
3811
|
+
&& request.session.bindingGeneration === response.session.bindingGeneration;
|
|
3812
|
+
if (request.relocation.high !== response.relocation.high
|
|
3813
|
+
|| request.relocation.low !== response.relocation.low
|
|
3814
|
+
|| !sameCoordinator
|
|
3815
|
+
|| !sameSession) {
|
|
3816
|
+
throw new service_wire_m6a_codec_1.ServiceWireProtocolError('Session relocation ACK changed its exact fence.');
|
|
3817
|
+
}
|
|
3818
|
+
if (request.actor.actor.actorId !== response.actor.actor.actorId
|
|
3819
|
+
|| request.actor.actor.generation !== response.actor.actor.generation
|
|
3820
|
+
|| request.actor.actor.nodeRid !== response.actor.actor.nodeRid
|
|
3821
|
+
|| request.actor.targetNodeGeneration !== response.actor.targetNodeGeneration
|
|
3822
|
+
|| request.actor.authorityOwnerGeneration !== response.actor.authorityOwnerGeneration
|
|
3823
|
+
|| request.actor.ownerLeaseGeneration !== response.actor.ownerLeaseGeneration) {
|
|
3824
|
+
throw new service_wire_m6a_codec_1.ServiceWireProtocolError('Command 43 ACK does not echo command 42.');
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
function replyRelayIdentityKey(value) {
|
|
3828
|
+
return JSON.stringify([
|
|
3829
|
+
value.operation.high.toString(),
|
|
3830
|
+
value.operation.low.toString(),
|
|
3831
|
+
value.replyRouteId.toString(),
|
|
3832
|
+
value.relocation.high.toString(),
|
|
3833
|
+
value.relocation.low.toString(),
|
|
3834
|
+
value.coordinator.ownerId,
|
|
3835
|
+
value.coordinator.leaseGeneration.toString(),
|
|
3836
|
+
value.coordinator.nodeRid,
|
|
3837
|
+
value.coordinator.nodeGeneration.toString(),
|
|
3838
|
+
value.coordinator.expectedAuthorityStoreVersion
|
|
3839
|
+
]);
|
|
3840
|
+
}
|
|
3841
|
+
function replyRelayPendingKey(ackTargetNodeRid, value) {
|
|
3842
|
+
return JSON.stringify([ackTargetNodeRid, replyRelayIdentityKey(value)]);
|
|
3843
|
+
}
|
|
3844
|
+
function isServiceWireCommand(payload, command) {
|
|
3845
|
+
return payload.byteLength >= 5
|
|
3846
|
+
&& payload[0] === 0x5a && payload[1] === 0x4d && payload[2] === 1
|
|
3847
|
+
&& payload[3] === command;
|
|
3848
|
+
}
|
|
3849
|
+
function encodeHandoffQueuedMessages(backlog) {
|
|
3850
|
+
return backlog.map(packet => ({
|
|
3851
|
+
sequence: BigInt(packet.index) + 1n,
|
|
3852
|
+
payload: Buffer.from(JSON.stringify({
|
|
3853
|
+
...packet,
|
|
3854
|
+
...(packet.messageFollowOrigin === undefined ? {} : {
|
|
3855
|
+
messageFollowOrigin: {
|
|
3856
|
+
sourceNodeRid: String(packet.messageFollowOrigin.sourceNodeRid),
|
|
3857
|
+
sourceNodeRidHex: (0, routing_id_1.encodeRoutingIdStorageHex)(packet.messageFollowOrigin.sourceNodeRid),
|
|
3858
|
+
originalOperation: {
|
|
3859
|
+
high: packet.messageFollowOrigin.originalOperation.high.toString(),
|
|
3860
|
+
low: packet.messageFollowOrigin.originalOperation.low.toString()
|
|
3861
|
+
},
|
|
3862
|
+
originalReplyRouteId: packet.messageFollowOrigin.originalReplyRouteId.toString()
|
|
3863
|
+
}
|
|
3864
|
+
})
|
|
3865
|
+
}), 'utf8')
|
|
3866
|
+
}));
|
|
3867
|
+
}
|
|
3868
|
+
function decodeQueuedMessageFollowOrigin(value) {
|
|
3869
|
+
if (value === undefined)
|
|
3870
|
+
return undefined;
|
|
3871
|
+
if (typeof value !== 'object' || value === null) {
|
|
3872
|
+
throw new TypeError('Relocation Actor Message Follow origin is invalid.');
|
|
3873
|
+
}
|
|
3874
|
+
const origin = value;
|
|
3875
|
+
const operation = origin.originalOperation;
|
|
3876
|
+
if (typeof origin.sourceNodeRid !== 'string'
|
|
3877
|
+
|| origin.sourceNodeRid.length === 0
|
|
3878
|
+
|| typeof operation !== 'object'
|
|
3879
|
+
|| operation === null) {
|
|
3880
|
+
throw new TypeError('Relocation Actor Message Follow origin is invalid.');
|
|
3881
|
+
}
|
|
3882
|
+
const operationRecord = operation;
|
|
3883
|
+
const high = queuedMessageU64(operationRecord.high, 'originalOperation.high');
|
|
3884
|
+
const low = queuedMessageU64(operationRecord.low, 'originalOperation.low');
|
|
3885
|
+
if (high === 0n && low === 0n) {
|
|
3886
|
+
throw new TypeError('Relocation Actor Message Follow operation must not be zero.');
|
|
3887
|
+
}
|
|
3888
|
+
return {
|
|
3889
|
+
sourceNodeRid: (0, routing_id_1.decodeRoutingId)(origin.sourceNodeRid, origin.sourceNodeRidHex),
|
|
3890
|
+
originalOperation: { high, low },
|
|
3891
|
+
originalReplyRouteId: queuedMessageU64(origin.originalReplyRouteId, 'originalReplyRouteId')
|
|
3892
|
+
};
|
|
3893
|
+
}
|
|
3894
|
+
function queuedMessageU64(value, field) {
|
|
3895
|
+
if (typeof value !== 'string' || !/^(?:0|[1-9][0-9]*)$/.test(value)) {
|
|
3896
|
+
throw new TypeError(`Relocation Actor Message Follow ${field} is invalid.`);
|
|
3897
|
+
}
|
|
3898
|
+
const result = BigInt(value);
|
|
3899
|
+
if (result > 0xffffffffffffffffn) {
|
|
3900
|
+
throw new TypeError(`Relocation Actor Message Follow ${field} is outside u64.`);
|
|
3901
|
+
}
|
|
3902
|
+
return result;
|
|
3903
|
+
}
|
|
3904
|
+
function toServiceTimer(value) {
|
|
3905
|
+
return {
|
|
3906
|
+
timerId: value.name,
|
|
3907
|
+
handlerType: value.handlerType,
|
|
3908
|
+
startedAtUnixMs: value.startedAtUnixMs,
|
|
3909
|
+
dueAtUnixMs: value.nextDueAtUnixMs,
|
|
3910
|
+
intervalMs: value.periodMs,
|
|
3911
|
+
deliveryIndex: value.deliveryIndex,
|
|
3912
|
+
lastScheduledIndex: value.lastScheduledIndex,
|
|
3913
|
+
overrunPolicy: value.overrunPolicy,
|
|
3914
|
+
maxCatchUpTicks: value.maxCatchUpTicks,
|
|
3915
|
+
stopOnUnhandledException: value.stopOnUnhandledException,
|
|
3916
|
+
pendingTicks: value.pendingTicks
|
|
3917
|
+
};
|
|
3918
|
+
}
|
|
3919
|
+
/**
|
|
3920
|
+
* NODE-INTERNAL Prepare sideband. Frame zero remains the exact canonical
|
|
3921
|
+
* command-40 schema record; each following ZLNI frame carries one sealed
|
|
3922
|
+
* actor-session journal keyed by its canonical participant authority key.
|
|
3923
|
+
* Sessionless relocation remains a single-frame, wire-identical Prepare.
|
|
3924
|
+
*/
|
|
3925
|
+
function encodePrepareSideband(envelope) {
|
|
3926
|
+
return envelope.participants
|
|
3927
|
+
.filter(participant => participant.boundSessionState.byteLength !== 0)
|
|
3928
|
+
.map(participant => {
|
|
3929
|
+
const key = Buffer.from(participant.key, 'utf8');
|
|
3930
|
+
const payload = Buffer.from(participant.boundSessionState);
|
|
3931
|
+
if (key.byteLength === 0 || key.byteLength > 0xffff || payload.byteLength > 0xffff_ffff) {
|
|
3932
|
+
throw new TypeError('Bound-session relocation sideband is out of bounds.');
|
|
3933
|
+
}
|
|
3934
|
+
const header = Buffer.allocUnsafe(11);
|
|
3935
|
+
header.write('ZLNI', 0, 'ascii');
|
|
3936
|
+
header[4] = 1;
|
|
3937
|
+
header.writeUInt16BE(key.byteLength, 5);
|
|
3938
|
+
header.writeUInt32BE(payload.byteLength, 7);
|
|
3939
|
+
return Buffer.concat([header, key, payload]);
|
|
3940
|
+
});
|
|
3941
|
+
}
|
|
3942
|
+
function decodePrepareSideband(frames) {
|
|
3943
|
+
const result = [];
|
|
3944
|
+
const keys = new Set();
|
|
3945
|
+
for (const frame of frames) {
|
|
3946
|
+
const bytes = Buffer.from(frame);
|
|
3947
|
+
if (bytes.byteLength < 11 || bytes.subarray(0, 4).toString('ascii') !== 'ZLNI' || bytes[4] !== 1) {
|
|
3948
|
+
return undefined;
|
|
3949
|
+
}
|
|
3950
|
+
const keyLength = bytes.readUInt16BE(5);
|
|
3951
|
+
const payloadLength = bytes.readUInt32BE(7);
|
|
3952
|
+
if (11 + keyLength + payloadLength !== bytes.byteLength || keyLength === 0)
|
|
3953
|
+
return undefined;
|
|
3954
|
+
const participantKey = bytes.subarray(11, 11 + keyLength).toString('utf8');
|
|
3955
|
+
if (keys.has(participantKey))
|
|
3956
|
+
return undefined;
|
|
3957
|
+
keys.add(participantKey);
|
|
3958
|
+
result.push({ participantKey, payload: Buffer.from(bytes.subarray(11 + keyLength)) });
|
|
3959
|
+
}
|
|
3960
|
+
return result;
|
|
3961
|
+
}
|
|
3962
|
+
function attachPrepareBoundSessions(envelope, sideband) {
|
|
3963
|
+
if (sideband === undefined || sideband.length === 0)
|
|
3964
|
+
return envelope;
|
|
3965
|
+
const byKey = new Map(sideband.map(value => [value.participantKey, Buffer.from(value.payload)]));
|
|
3966
|
+
const participants = envelope.participants.map(participant => {
|
|
3967
|
+
const payload = byKey.get(participant.key);
|
|
3968
|
+
if (payload === undefined)
|
|
3969
|
+
return participant;
|
|
3970
|
+
if (participant.objectKind !== 'actor') {
|
|
3971
|
+
throw new TypeError('Bound-session relocation sideband names a non-Actor participant.');
|
|
3972
|
+
}
|
|
3973
|
+
byKey.delete(participant.key);
|
|
3974
|
+
return { ...participant, boundSessionState: payload };
|
|
3975
|
+
});
|
|
3976
|
+
if (byKey.size !== 0)
|
|
3977
|
+
throw new TypeError('Bound-session relocation sideband names an unknown participant.');
|
|
3978
|
+
return { ...envelope, participants };
|
|
3979
|
+
}
|
|
3980
|
+
function encodeActorSession(target) {
|
|
3981
|
+
if (target === undefined)
|
|
3982
|
+
return Buffer.alloc(0);
|
|
3983
|
+
return Buffer.from(JSON.stringify({
|
|
3984
|
+
version: 1,
|
|
3985
|
+
routerChannelId: target.routerChannelId,
|
|
3986
|
+
targetNodeRid: String(target.targetNodeRid),
|
|
3987
|
+
spotId: String(target.spotId),
|
|
3988
|
+
sessionNodeRid: target.sessionNodeRid === undefined ? undefined : String(target.sessionNodeRid),
|
|
3989
|
+
sessionRid: target.sessionRid === undefined ? undefined : String(target.sessionRid),
|
|
3990
|
+
bindingGeneration: target.bindingGeneration?.toString(),
|
|
3991
|
+
previousAuthorityOwnerGeneration: target.previousAuthorityOwnerGeneration?.toString(),
|
|
3992
|
+
previousOwnerLeaseGeneration: target.previousOwnerLeaseGeneration?.toString(),
|
|
3993
|
+
relocationSealId: target.relocationSealId,
|
|
3994
|
+
serviceWireRelocation: target.serviceWireRelocation === undefined
|
|
3995
|
+
? undefined
|
|
3996
|
+
: {
|
|
3997
|
+
relocationHigh: target.serviceWireRelocation.relocation.high.toString(),
|
|
3998
|
+
relocationLow: target.serviceWireRelocation.relocation.low.toString(),
|
|
3999
|
+
coordinatorOwnerId: target.serviceWireRelocation.coordinator.ownerId,
|
|
4000
|
+
coordinatorLeaseGeneration: target.serviceWireRelocation.coordinator.leaseGeneration.toString(),
|
|
4001
|
+
coordinatorNodeRid: target.serviceWireRelocation.coordinator.nodeRid,
|
|
4002
|
+
coordinatorNodeGeneration: target.serviceWireRelocation.coordinator.nodeGeneration.toString(),
|
|
4003
|
+
expectedAuthorityStoreVersion: target.serviceWireRelocation.coordinator.expectedAuthorityStoreVersion,
|
|
4004
|
+
sessionOwnerNodeRid: target.serviceWireRelocation.session.sessionOwnerNodeRid,
|
|
4005
|
+
sessionOwnerNodeGeneration: target.serviceWireRelocation.session.sessionOwnerNodeGeneration.toString(),
|
|
4006
|
+
sessionOwnerId: target.serviceWireRelocation.session.sessionOwnerId,
|
|
4007
|
+
sessionOwnerLeaseGeneration: target.serviceWireRelocation.session.sessionOwnerLeaseGeneration.toString(),
|
|
4008
|
+
sessionRid: target.serviceWireRelocation.session.sessionRid,
|
|
4009
|
+
bindingGeneration: target.serviceWireRelocation.session.bindingGeneration.toString()
|
|
4010
|
+
}
|
|
4011
|
+
}), 'utf8');
|
|
4012
|
+
}
|
|
4013
|
+
function decodeActorSession(payload) {
|
|
4014
|
+
const value = JSON.parse(Buffer.from(payload).toString('utf8'));
|
|
4015
|
+
const optionalBigInt = (field) => typeof value[field] === 'string'
|
|
4016
|
+
? BigInt(value[field])
|
|
4017
|
+
: undefined;
|
|
4018
|
+
if (value.version !== 1 || typeof value.routerChannelId !== 'string'
|
|
4019
|
+
|| typeof value.targetNodeRid !== 'string' || typeof value.spotId !== 'string') {
|
|
4020
|
+
throw new TypeError('Actor relocation Session journal is invalid.');
|
|
4021
|
+
}
|
|
4022
|
+
const serviceWire = decodeActorSessionServiceWireFence(value.serviceWireRelocation);
|
|
4023
|
+
return {
|
|
4024
|
+
routerChannelId: value.routerChannelId,
|
|
4025
|
+
targetNodeRid: value.targetNodeRid,
|
|
4026
|
+
spotId: value.spotId,
|
|
4027
|
+
...(typeof value.sessionNodeRid === 'string' ? { sessionNodeRid: value.sessionNodeRid } : {}),
|
|
4028
|
+
...(typeof value.sessionRid === 'string' ? { sessionRid: value.sessionRid } : {}),
|
|
4029
|
+
...(optionalBigInt('bindingGeneration') === undefined ? {} : { bindingGeneration: optionalBigInt('bindingGeneration') }),
|
|
4030
|
+
...(optionalBigInt('previousAuthorityOwnerGeneration') === undefined ? {} : { previousAuthorityOwnerGeneration: optionalBigInt('previousAuthorityOwnerGeneration') }),
|
|
4031
|
+
...(optionalBigInt('previousOwnerLeaseGeneration') === undefined ? {} : { previousOwnerLeaseGeneration: optionalBigInt('previousOwnerLeaseGeneration') }),
|
|
4032
|
+
...(typeof value.relocationSealId === 'string' ? { relocationSealId: value.relocationSealId } : {}),
|
|
4033
|
+
...(serviceWire === undefined ? {} : { serviceWireRelocation: serviceWire })
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
function decodeActorSessionServiceWireFence(input) {
|
|
4037
|
+
if (input === undefined)
|
|
4038
|
+
return undefined;
|
|
4039
|
+
if (typeof input !== 'object' || input === null) {
|
|
4040
|
+
throw new TypeError('Actor relocation Session service-wire fence is invalid.');
|
|
4041
|
+
}
|
|
4042
|
+
const value = input;
|
|
4043
|
+
const text = (field) => {
|
|
4044
|
+
if (typeof value[field] !== 'string' || value[field].length === 0) {
|
|
4045
|
+
throw new TypeError(`Actor relocation Session service-wire '${field}' is invalid.`);
|
|
4046
|
+
}
|
|
4047
|
+
return value[field];
|
|
4048
|
+
};
|
|
4049
|
+
const positive = (field) => {
|
|
4050
|
+
const parsed = BigInt(text(field));
|
|
4051
|
+
if (parsed <= 0n) {
|
|
4052
|
+
throw new TypeError(`Actor relocation Session service-wire '${field}' is invalid.`);
|
|
4053
|
+
}
|
|
4054
|
+
return parsed;
|
|
4055
|
+
};
|
|
4056
|
+
const ordinal = (field) => {
|
|
4057
|
+
const parsed = BigInt(text(field));
|
|
4058
|
+
if (parsed < 0n) {
|
|
4059
|
+
throw new TypeError(`Actor relocation Session service-wire '${field}' is invalid.`);
|
|
4060
|
+
}
|
|
4061
|
+
return parsed;
|
|
4062
|
+
};
|
|
4063
|
+
const relocation = {
|
|
4064
|
+
high: ordinal('relocationHigh'),
|
|
4065
|
+
low: ordinal('relocationLow')
|
|
4066
|
+
};
|
|
4067
|
+
if (relocation.high === 0n && relocation.low === 0n) {
|
|
4068
|
+
throw new TypeError('Actor relocation Session service-wire identity is zero.');
|
|
4069
|
+
}
|
|
4070
|
+
return {
|
|
4071
|
+
relocation,
|
|
4072
|
+
coordinator: {
|
|
4073
|
+
ownerId: text('coordinatorOwnerId'),
|
|
4074
|
+
leaseGeneration: positive('coordinatorLeaseGeneration'),
|
|
4075
|
+
nodeRid: text('coordinatorNodeRid'),
|
|
4076
|
+
nodeGeneration: positive('coordinatorNodeGeneration'),
|
|
4077
|
+
expectedAuthorityStoreVersion: text('expectedAuthorityStoreVersion')
|
|
4078
|
+
},
|
|
4079
|
+
session: {
|
|
4080
|
+
sessionOwnerNodeRid: text('sessionOwnerNodeRid'),
|
|
4081
|
+
sessionOwnerNodeGeneration: positive('sessionOwnerNodeGeneration'),
|
|
4082
|
+
sessionOwnerId: text('sessionOwnerId'),
|
|
4083
|
+
sessionOwnerLeaseGeneration: positive('sessionOwnerLeaseGeneration'),
|
|
4084
|
+
sessionRid: text('sessionRid'),
|
|
4085
|
+
bindingGeneration: positive('bindingGeneration')
|
|
4086
|
+
}
|
|
4087
|
+
};
|
|
4088
|
+
}
|
|
4089
|
+
function encodeMembershipMutation(memberships, participantKey) {
|
|
4090
|
+
return Buffer.from(JSON.stringify(memberships.filter(value => value.actorKey === participantKey || value.spotKey === participantKey).map(value => ({
|
|
4091
|
+
...value,
|
|
4092
|
+
spotObjectGeneration: value.spotObjectGeneration.toString(),
|
|
4093
|
+
membershipEpoch: value.membershipEpoch.toString()
|
|
4094
|
+
}))), 'utf8');
|
|
4095
|
+
}
|
|
4096
|
+
function addCapacity(left, right) {
|
|
4097
|
+
const spotType = left.spotType ?? right.spotType;
|
|
4098
|
+
if (left.spotType !== undefined && right.spotType !== undefined
|
|
4099
|
+
&& (left.spotType.objectKind !== right.spotType.objectKind
|
|
4100
|
+
|| left.spotType.stableType !== right.spotType.stableType)) {
|
|
4101
|
+
throw new Error('Relocation aggregate contains more than one Spot stable type.');
|
|
4102
|
+
}
|
|
4103
|
+
return {
|
|
4104
|
+
actors: left.actors + right.actors,
|
|
4105
|
+
spots: left.spots + right.spots,
|
|
4106
|
+
...(spotType === undefined ? {} : {
|
|
4107
|
+
spotType: {
|
|
4108
|
+
...spotType,
|
|
4109
|
+
count: (left.spotType?.count ?? 0) + (right.spotType?.count ?? 0)
|
|
4110
|
+
}
|
|
4111
|
+
})
|
|
4112
|
+
};
|
|
4113
|
+
}
|