@zlink-systems/framework 0.21.0 → 0.22.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/dist/.tsbuildinfo +1 -1
- package/dist/contracts/Common/ZLinkMessage.js +5 -5
- package/dist/contracts/Configuration/CodecContentType.js +5 -7
- package/dist/contracts/Configuration/DiagnosticsValidation.js +1 -6
- package/dist/contracts/Configuration/EndpointNotation.js +4 -8
- package/dist/contracts/Configuration/Registration.js +14 -11
- package/dist/contracts/Configuration/RegistrationBuilders.js +32 -25
- package/dist/contracts/Configuration/RegistrationNormalizers.js +25 -26
- package/dist/contracts/Configuration/RegistrationValidators.js +36 -38
- package/dist/contracts/Configuration/SendTimeoutValidation.js +4 -4
- package/dist/contracts/Configuration/TimerRegistrationValidator.js +7 -7
- package/dist/contracts/Handlers/JsonContract.js +23 -11
- package/dist/contracts/Spots/SpotKind.js +16 -8
- package/dist/location-store-integration.js +16 -16
- package/dist/nest-integration.js +17 -11
- package/dist/runtime/abort.js +9 -5
- package/dist/runtime/actors/actor-authority-payload-codec.js +12 -16
- package/dist/runtime/actors/actor-authority-publication.js +18 -17
- package/dist/runtime/actors/actor-client.js +83 -63
- package/dist/runtime/actors/actor-context.js +24 -18
- package/dist/runtime/actors/actor-creation.js +4 -3
- package/dist/runtime/actors/actor-handoff.js +89 -92
- package/dist/runtime/actors/actor-local-native-join.js +18 -19
- package/dist/runtime/actors/actor-mailbox.js +1 -1
- package/dist/runtime/actors/actor-message-follow-context.js +24 -21
- package/dist/runtime/actors/actor-packet-relay-wire.js +1 -3
- package/dist/runtime/actors/actor-remote-joiner.js +20 -17
- package/dist/runtime/actors/actor-remote-wire.js +6 -7
- package/dist/runtime/actors/actor-runtime-state.js +87 -81
- package/dist/runtime/actors/actor-transfer-registry.js +3 -5
- package/dist/runtime/actors/bound-session-wire.js +14 -9
- package/dist/runtime/actors/deferred-join-accepted-journal.js +158 -124
- package/dist/runtime/actors/index.js +2 -5
- package/dist/runtime/actors/post-commit-actor-binder.js +3 -6
- package/dist/runtime/actors/post-commit-actor-location.js +1 -1
- package/dist/runtime/actors/spot-actor-dispatch.js +5 -5
- package/dist/runtime/actors/transferred-actor-rollback.js +4 -4
- package/dist/runtime/admission.js +2 -6
- package/dist/runtime/application-jobs/application-ingress-record-owner.js +2 -6
- package/dist/runtime/application-jobs/receive-flow-controller.js +11 -12
- package/dist/runtime/backend/mesh-completion-table.js +1 -3
- package/dist/runtime/backend/mesh-dispatch-pump.js +12 -16
- package/dist/runtime/backend/node/node-backend-adapter-factory.js +1 -3
- package/dist/runtime/backend/node/node-backend-adapter-support.js +26 -17
- package/dist/runtime/backend/node/node-mesh-backend-adapter.js +4 -3
- package/dist/runtime/backend/node/node-raw-binding-port.js +7 -6
- package/dist/runtime/backend/node/node-raw-mesh-backend.js +116 -113
- package/dist/runtime/backend/node/node-socket-backend-adapter.js +12 -13
- package/dist/runtime/backend/runtime-message.js +3 -3
- package/dist/runtime/backend/runtime-values.d.ts +2 -2
- package/dist/runtime/backend/runtime-values.js +4 -5
- package/dist/runtime/channels/channel-clients.js +27 -24
- package/dist/runtime/channels/channel-dispatch-pipeline.js +10 -18
- package/dist/runtime/channels/channel-dispatch-services.js +7 -4
- package/dist/runtime/channels/channel-dispatchers.js +9 -9
- package/dist/runtime/channels/channel-envelope-inspection.js +16 -13
- package/dist/runtime/channels/channel-envelope.js +83 -47
- package/dist/runtime/channels/channel-outbound-operations.js +8 -8
- package/dist/runtime/channels/channel-receive-loops.js +20 -22
- package/dist/runtime/channels/channel-receive-task-tracker.js +1 -1
- package/dist/runtime/channels/channel-runtime-lifecycle.js +61 -56
- package/dist/runtime/channels/channel-runtime-manager.js +18 -9
- package/dist/runtime/channels/channel-socket-registry.js +126 -123
- package/dist/runtime/channels/channel-transports.js +43 -33
- package/dist/runtime/channels/client-server-location-runtime.js +32 -40
- package/dist/runtime/channels/client-server-service-wire.js +29 -22
- package/dist/runtime/channels/dispatch-error-reporter.js +9 -7
- package/dist/runtime/channels/fanout-location-runtime.js +34 -47
- package/dist/runtime/channels/fanout-service-wire.js +3 -5
- package/dist/runtime/channels/local-spot-route-transport.js +7 -1
- package/dist/runtime/channels/route-member-snapshot.js +3 -3
- package/dist/runtime/channels/source-spot-router.js +6 -2
- package/dist/runtime/channels/spot-route-bridge-transport.js +5 -2
- package/dist/runtime/channels/spot-route-dispatch-strategy.js +2 -2
- package/dist/runtime/channels/spot-route-target-resolver.js +6 -6
- package/dist/runtime/diagnostics/index.js +38 -10
- package/dist/runtime/diagnostics/message-flow.js +47 -39
- package/dist/runtime/diagnostics/runtime-metrics.js +18 -9
- package/dist/runtime/diagnostics/runtime-observation-queue.js +8 -10
- package/dist/runtime/diagnostics/topology-runtime-projections.js +17 -10
- package/dist/runtime/execution/index.js +7 -6
- package/dist/runtime/execution/serial-execution-queue.js +12 -18
- package/dist/runtime/execution/state-lane.js +2 -2
- package/dist/runtime/foundation/actor-join-recovery-codec.js +31 -23
- package/dist/runtime/foundation/event-loop-resources.js +4 -4
- package/dist/runtime/foundation/message-follow-suppression-registry.js +4 -4
- package/dist/runtime/foundation/raw-service-mesh-runtime.js +136 -136
- package/dist/runtime/foundation/route-mesh-connection-policy.js +4 -4
- package/dist/runtime/foundation/runtime-state-projections.js +22 -11
- package/dist/runtime/foundation/service-authority-payload-codec.js +13 -15
- package/dist/runtime/foundation/service-discovery-registry.js +35 -33
- package/dist/runtime/foundation/service-instance-activation-recovery-codec.js +20 -22
- package/dist/runtime/foundation/service-liveness-registry.js +16 -12
- package/dist/runtime/foundation/service-maintenance-runtime.js +2 -4
- package/dist/runtime/foundation/service-relocation-aggregate-committer.js +24 -24
- package/dist/runtime/foundation/service-relocation-object-owner.js +6 -6
- package/dist/runtime/foundation/service-relocation-runtime.js +137 -94
- package/dist/runtime/foundation/service-runtime-contracts.js +5 -5
- package/dist/runtime/foundation/service-stateful-registry.js +24 -23
- package/dist/runtime/foundation/service-stateful-runtime.js +416 -438
- package/dist/runtime/foundation/service-stateful-wire-codec.js +95 -98
- package/dist/runtime/foundation/service-topology-registry.js +96 -95
- package/dist/runtime/foundation/service-weighted-selection.js +6 -11
- package/dist/runtime/foundation/service-wire-binary-primitives.js +5 -6
- package/dist/runtime/foundation/service-wire-m6a-codec.js +46 -35
- package/dist/runtime/framework-errors-internal.js +7 -10
- package/dist/runtime/handlers/handler-instance-scope.js +4 -5
- package/dist/runtime/handlers/index.js +3 -1
- package/dist/runtime/host/actor-packet-relay.js +74 -70
- package/dist/runtime/host/actor-placement-coordinator.js +29 -31
- package/dist/runtime/host/actor-runtime-options-factory.js +12 -14
- package/dist/runtime/host/actor-transfer-authority-runtime.js +7 -9
- package/dist/runtime/host/actor-transfer-runtime.js +176 -153
- package/dist/runtime/host/application-job-queue.js +18 -22
- package/dist/runtime/host/bound-session-relay.js +3 -1
- package/dist/runtime/host/channel-runtime-options-factory.js +31 -14
- package/dist/runtime/host/host-capacity-status.js +8 -4
- package/dist/runtime/host/index.js +244 -227
- package/dist/runtime/host/instance-activation-authority.js +93 -96
- package/dist/runtime/host/location-runtime-owner.js +16 -14
- package/dist/runtime/host/mesh-router-resolver.js +11 -13
- package/dist/runtime/host/relocation-direct-transfer.js +10 -9
- package/dist/runtime/host/remote-actor-join-receiver.js +20 -15
- package/dist/runtime/host/remote-actor-packet-target-store.js +18 -22
- package/dist/runtime/host/remote-bound-session-relay.js +97 -73
- package/dist/runtime/host/route-mesh-runtime.js +64 -69
- package/dist/runtime/host/runtime-shutdown.js +1 -2
- package/dist/runtime/host/service-relocation-host-runtime.js +586 -511
- package/dist/runtime/host/spot-address-transport.js +69 -71
- package/dist/runtime/host/spot-runtime-options-factory.js +30 -29
- package/dist/runtime/host/stateful-authority-route-runtime.js +65 -68
- package/dist/runtime/host/user-spot-creation-coordinator.js +61 -67
- package/dist/runtime/locations/actor-location-claims.js +26 -20
- package/dist/runtime/locations/aggregate-inventory-store.js +72 -75
- package/dist/runtime/locations/authority-key-codec.js +7 -7
- package/dist/runtime/locations/auto-connect-loop.js +3 -3
- package/dist/runtime/locations/auto-connect-planner.js +25 -19
- package/dist/runtime/locations/auto-connect-reconciler.js +51 -31
- package/dist/runtime/locations/in-memory-authority-store.js +128 -118
- package/dist/runtime/locations/in-memory-location-store.js +195 -168
- package/dist/runtime/locations/in-memory-provider-location-store.js +8 -10
- package/dist/runtime/locations/key-codec.js +1 -1
- package/dist/runtime/locations/lease-tracker.js +23 -21
- package/dist/runtime/locations/lifecycle.js +4 -5
- package/dist/runtime/locations/location-store-repository.js +368 -324
- package/dist/runtime/locations/opaque-record-key.js +2 -2
- package/dist/runtime/locations/resolvers.js +73 -82
- package/dist/runtime/locations/runtime.js +68 -63
- package/dist/runtime/locations/spot-location-claims.js +39 -35
- package/dist/runtime/messaging/framework-json-v1.js +7 -7
- package/dist/runtime/messaging/packet-name.js +7 -6
- package/dist/runtime/messaging/payload-codec.js +21 -18
- package/dist/runtime/protocol/service_wire_codec.generated.d.ts +30 -2
- package/dist/runtime/protocol/service_wire_codec.generated.js +1671 -234
- package/dist/runtime/routing-id.js +3 -5
- package/dist/runtime/spots/formal-remote-actor-admission-registry.js +35 -31
- package/dist/runtime/spots/formal-remote-actor-transfer-registry.js +1 -2
- package/dist/runtime/spots/index.js +208 -219
- package/dist/runtime/spots/spot-activation-registry.js +15 -14
- package/dist/runtime/spots/spot-activation-state.js +17 -15
- package/dist/runtime/spots/spot-activation.js +33 -16
- package/dist/runtime/spots/spot-actor-admission-coordinator.js +18 -15
- package/dist/runtime/spots/spot-actor-membership.js +1 -3
- package/dist/runtime/spots/spot-actor-packet-dispatch.js +23 -20
- package/dist/runtime/spots/spot-actor-packet-drain.js +12 -10
- package/dist/runtime/spots/spot-actor-packet-relay-dispatch.js +10 -6
- package/dist/runtime/spots/spot-closing.js +3 -7
- package/dist/runtime/spots/spot-context.js +2 -2
- package/dist/runtime/spots/spot-entry-activation.js +13 -14
- package/dist/runtime/spots/spot-handle.js +6 -3
- package/dist/runtime/spots/spot-handler-registry.js +12 -12
- package/dist/runtime/spots/spot-manager-public.js +5 -6
- package/dist/runtime/spots/spot-native-actor-join-admission.js +4 -3
- package/dist/runtime/spots/spot-node-autoconnect.js +15 -18
- package/dist/runtime/spots/spot-node-runtime-manager.js +74 -85
- package/dist/runtime/spots/spot-outbound.js +10 -14
- package/dist/runtime/spots/spot-remote-codec.js +22 -26
- package/dist/runtime/spots/spot-remote-route-codec.js +5 -7
- package/dist/runtime/spots/spot-route-packet-dispatch.js +16 -14
- package/dist/runtime/spots/spot-route-replies.js +4 -6
- package/dist/runtime/spots/spot-routed-actor-admission.js +9 -7
- package/dist/runtime/spots/spot-routed-bound-session-dispatch.js +1 -2
- package/dist/runtime/spots/spot-routed-frame-dispatch.js +5 -5
- package/dist/runtime/spots/spot-routed-spot-packet-dispatch.js +14 -10
- package/dist/runtime/spots/spot-serial-executor.js +5 -8
- package/dist/runtime/spots/spot-serial-turn-executor.js +4 -6
- package/dist/runtime/spots/spot-subscription-dispatch.js +5 -5
- package/dist/runtime/spots/spot-timer.js +25 -29
- package/dist/runtime/streams/actor-session-binding-registry.js +98 -107
- package/dist/runtime/streams/actor-session-lifecycle-coordinator.js +3 -1
- package/dist/runtime/streams/bound-actor-relay-sender.js +18 -16
- package/dist/runtime/streams/bound-session-service.js +8 -9
- package/dist/runtime/streams/index.js +20 -19
- package/dist/runtime/streams/managed-stream.js +32 -30
- package/dist/runtime/streams/native-fallback-bound-session.js +21 -17
- package/dist/runtime/streams/protocol.js +29 -18
- package/dist/runtime/streams/session-actor-coordinator.js +34 -37
- package/dist/runtime/streams/session-context.js +30 -28
- package/dist/runtime/streams/session-provider.js +2 -2
- package/dist/runtime/streams/session-serial-executor.js +1 -2
- package/dist/runtime/streams/stream-frame-factory.js +11 -5
- package/dist/runtime/streams/stream-session-runtime.js +70 -66
- package/dist/runtime/workers/index.js +15 -4
- package/package.json +2 -2
- package/src/contracts/Channels/RouteCalls.ts +6 -1
- package/src/contracts/Common/ZLinkMessage.ts +5 -5
- package/src/contracts/Common/ZLinkMessageMetadata.ts +4 -3
- package/src/contracts/Configuration/Builders.ts +14 -14
- package/src/contracts/Configuration/CodecContentType.ts +8 -10
- package/src/contracts/Configuration/DiagnosticsValidation.ts +1 -6
- package/src/contracts/Configuration/EndpointNotation.ts +11 -11
- package/src/contracts/Configuration/ObjectRoles.ts +1 -3
- package/src/contracts/Configuration/Registration.ts +46 -17
- package/src/contracts/Configuration/RegistrationBuilderPolicy.ts +10 -8
- package/src/contracts/Configuration/RegistrationBuilders.ts +107 -96
- package/src/contracts/Configuration/RegistrationCodecRegistry.ts +9 -3
- package/src/contracts/Configuration/RegistrationNormalizers.ts +116 -84
- package/src/contracts/Configuration/RegistrationTypes.ts +21 -25
- package/src/contracts/Configuration/RegistrationValidators.ts +122 -73
- package/src/contracts/Configuration/RouteChannelInternalState.ts +1 -4
- package/src/contracts/Configuration/RuntimeEndpointConnections.ts +4 -1
- package/src/contracts/Configuration/SendTimeoutValidation.ts +6 -4
- package/src/contracts/Configuration/TimerRegistrationValidator.ts +8 -13
- package/src/contracts/Dispatch/ZLinkDispatchOptions.ts +1 -6
- package/src/contracts/Errors/index.ts +1 -4
- package/src/contracts/Handlers/Attributes.ts +15 -9
- package/src/contracts/Handlers/IZLinkChannelHandlers.ts +24 -4
- package/src/contracts/Handlers/JsonContract.ts +53 -19
- package/src/contracts/Locations/ActorTransfer.ts +1 -5
- package/src/contracts/Locations/Authority.ts +4 -11
- package/src/contracts/Locations/Keys.ts +6 -1
- package/src/contracts/Locations/Options.ts +3 -2
- package/src/contracts/Locations/RelocationStore.ts +1 -4
- package/src/contracts/Locations/Resolvers.ts +5 -5
- package/src/contracts/Locations/RuntimeQuery.ts +6 -2
- package/src/contracts/Locations/Stores.ts +1 -2
- package/src/contracts/Locations/index.ts +2 -8
- package/src/contracts/RouteMesh/Contracts.ts +1 -7
- package/src/contracts/RouteMesh/RuntimeTopology.ts +1 -4
- package/src/contracts/Spots/Contracts.ts +2 -8
- package/src/contracts/Spots/SpotKind.ts +16 -8
- package/src/contracts/Spots/ZLinkSpot.ts +8 -11
- package/src/contracts/Streams/IZLinkSession.ts +0 -1
- package/src/contracts/Streams/IZLinkSessionActor.ts +5 -1
- package/src/contracts/Streams/IZLinkSessionPacketHandler.ts +5 -1
- package/src/internal.ts +5 -4
- package/src/location-store-integration.ts +24 -16
- package/src/nest-integration.ts +40 -25
- package/src/runtime/abort.ts +10 -4
- package/src/runtime/actors/actor-authority-payload-codec.ts +20 -29
- package/src/runtime/actors/actor-authority-publication.ts +92 -77
- package/src/runtime/actors/actor-client.ts +162 -133
- package/src/runtime/actors/actor-context.ts +138 -130
- package/src/runtime/actors/actor-creation.ts +47 -44
- package/src/runtime/actors/actor-execution-context.ts +6 -4
- package/src/runtime/actors/actor-handoff.ts +269 -293
- package/src/runtime/actors/actor-join-deferred-scope.ts +5 -4
- package/src/runtime/actors/actor-lifecycle-snapshot.ts +5 -8
- package/src/runtime/actors/actor-local-native-join.ts +131 -159
- package/src/runtime/actors/actor-mailbox.ts +16 -19
- package/src/runtime/actors/actor-message-follow-context.ts +46 -36
- package/src/runtime/actors/actor-packet-relay-wire.ts +32 -21
- package/src/runtime/actors/actor-remote-joiner.ts +50 -54
- package/src/runtime/actors/actor-remote-wire.ts +9 -13
- package/src/runtime/actors/actor-routed-join-transport.ts +10 -2
- package/src/runtime/actors/actor-runtime-contracts.ts +2 -5
- package/src/runtime/actors/actor-runtime-state.ts +127 -124
- package/src/runtime/actors/actor-transfer-registry.ts +9 -14
- package/src/runtime/actors/actor-transport-delivery-gate.ts +4 -6
- package/src/runtime/actors/bound-session-wire.ts +37 -17
- package/src/runtime/actors/deferred-join-accepted-journal.ts +351 -320
- package/src/runtime/actors/index.ts +43 -86
- package/src/runtime/actors/post-commit-actor-binder.ts +10 -10
- package/src/runtime/actors/post-commit-actor-location.ts +30 -23
- package/src/runtime/actors/spot-actor-dispatch.ts +78 -53
- package/src/runtime/actors/transferred-actor-rollback.ts +18 -13
- package/src/runtime/admission.ts +9 -9
- package/src/runtime/application-jobs/application-ingress-record-owner.ts +5 -15
- package/src/runtime/application-jobs/application-job-queue-handler-start-gate.ts +3 -2
- package/src/runtime/application-jobs/receive-flow-controller.ts +17 -14
- package/src/runtime/backend/contracts/index.ts +73 -52
- package/src/runtime/backend/mesh-actor-session-node-adapter.ts +4 -14
- package/src/runtime/backend/mesh-completion-table.ts +6 -9
- package/src/runtime/backend/mesh-dispatch-pump.ts +38 -30
- package/src/runtime/backend/node/node-backend-adapter-factory.ts +23 -21
- package/src/runtime/backend/node/node-backend-adapter-support.ts +36 -22
- package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +59 -58
- package/src/runtime/backend/node/node-event-loop-poller.ts +2 -10
- package/src/runtime/backend/node/node-mesh-backend-adapter.ts +12 -5
- package/src/runtime/backend/node/node-monitor-backend-adapter.ts +4 -3
- package/src/runtime/backend/node/node-raw-binding-port.ts +29 -29
- package/src/runtime/backend/node/node-raw-mesh-backend.ts +335 -383
- package/src/runtime/backend/node/node-socket-backend-adapter.ts +101 -54
- package/src/runtime/backend/runtime-message.ts +5 -3
- package/src/runtime/backend/runtime-values.ts +10 -7
- package/src/runtime/channels/channel-autoconnect.ts +1 -4
- package/src/runtime/channels/channel-clients.ts +93 -73
- package/src/runtime/channels/channel-dispatch-pipeline.ts +40 -42
- package/src/runtime/channels/channel-dispatch-services.ts +51 -49
- package/src/runtime/channels/channel-dispatchers.ts +152 -96
- package/src/runtime/channels/channel-envelope-inspection.ts +20 -16
- package/src/runtime/channels/channel-envelope.ts +256 -150
- package/src/runtime/channels/channel-multipart.ts +13 -5
- package/src/runtime/channels/channel-outbound-operations.ts +37 -41
- package/src/runtime/channels/channel-receive-loops.ts +102 -98
- package/src/runtime/channels/channel-receive-task-tracker.ts +1 -1
- package/src/runtime/channels/channel-runtime-lifecycle.ts +248 -190
- package/src/runtime/channels/channel-runtime-manager.ts +63 -37
- package/src/runtime/channels/channel-socket-options.ts +3 -10
- package/src/runtime/channels/channel-socket-registry.ts +331 -273
- package/src/runtime/channels/channel-transports.ts +215 -197
- package/src/runtime/channels/client-server-location-runtime.ts +116 -100
- package/src/runtime/channels/client-server-service-wire.ts +37 -36
- package/src/runtime/channels/dispatch-error-reporter.ts +11 -8
- package/src/runtime/channels/fanout-location-runtime.ts +91 -94
- package/src/runtime/channels/fanout-service-wire.ts +5 -12
- package/src/runtime/channels/handler-filter-scope.ts +1 -4
- package/src/runtime/channels/index.ts +1 -4
- package/src/runtime/channels/local-spot-route-transport.ts +20 -5
- package/src/runtime/channels/route-member-snapshot.ts +11 -5
- package/src/runtime/channels/router-socket-spot-route-transport.ts +1 -7
- package/src/runtime/channels/source-spot-router.ts +26 -22
- package/src/runtime/channels/spot-direct-envelope.ts +10 -8
- package/src/runtime/channels/spot-node-route-transport.ts +13 -16
- package/src/runtime/channels/spot-route-bridge-transport.ts +26 -9
- package/src/runtime/channels/spot-route-dispatch-strategy.ts +40 -13
- package/src/runtime/channels/spot-route-target-resolver.ts +18 -9
- package/src/runtime/codecs/index.ts +3 -2
- package/src/runtime/diagnostics/flow-context.ts +12 -3
- package/src/runtime/diagnostics/index.ts +96 -61
- package/src/runtime/diagnostics/internal-event-contracts.ts +87 -33
- package/src/runtime/diagnostics/message-flow.ts +95 -76
- package/src/runtime/diagnostics/runtime-metrics.ts +46 -24
- package/src/runtime/diagnostics/runtime-observation-queue.ts +17 -28
- package/src/runtime/diagnostics/topology-runtime-projections.ts +62 -43
- package/src/runtime/execution/index.ts +18 -26
- package/src/runtime/execution/serial-execution-queue.ts +31 -40
- package/src/runtime/execution/state-lane.ts +2 -2
- package/src/runtime/foundation/actor-join-recovery-codec.ts +69 -51
- package/src/runtime/foundation/event-loop-resources.ts +8 -4
- package/src/runtime/foundation/message-follow-suppression-registry.ts +7 -14
- package/src/runtime/foundation/operation-identity.ts +1 -2
- package/src/runtime/foundation/operation-registry.ts +8 -2
- package/src/runtime/foundation/raw-service-mesh-runtime.ts +312 -385
- package/src/runtime/foundation/route-mesh-connection-policy.ts +7 -8
- package/src/runtime/foundation/runtime-state-projections.ts +24 -23
- package/src/runtime/foundation/service-authority-payload-codec.ts +54 -57
- package/src/runtime/foundation/service-discovery-registry.ts +67 -59
- package/src/runtime/foundation/service-instance-activation-recovery-codec.ts +28 -29
- package/src/runtime/foundation/service-liveness-registry.ts +18 -13
- package/src/runtime/foundation/service-mailbox.ts +14 -14
- package/src/runtime/foundation/service-maintenance-runtime.ts +22 -21
- package/src/runtime/foundation/service-metadata-codec.ts +2 -9
- package/src/runtime/foundation/service-relocation-aggregate-committer.ts +39 -36
- package/src/runtime/foundation/service-relocation-coordinator.ts +1 -4
- package/src/runtime/foundation/service-relocation-object-owner.ts +40 -58
- package/src/runtime/foundation/service-relocation-runtime.ts +292 -216
- package/src/runtime/foundation/service-runtime-contracts.ts +8 -8
- package/src/runtime/foundation/service-session-binding-ingress-port.ts +1 -4
- package/src/runtime/foundation/service-stateful-registry.ts +69 -59
- package/src/runtime/foundation/service-stateful-runtime.ts +1174 -1112
- package/src/runtime/foundation/service-stateful-wire-codec.ts +318 -270
- package/src/runtime/foundation/service-topology-registry.ts +157 -159
- package/src/runtime/foundation/service-weighted-selection.ts +9 -15
- package/src/runtime/foundation/service-wire-binary-primitives.ts +6 -12
- package/src/runtime/foundation/service-wire-m6a-codec.ts +69 -47
- package/src/runtime/framework-errors-internal.ts +31 -30
- package/src/runtime/handlers/handler-instance-scope.ts +18 -29
- package/src/runtime/handlers/index.ts +18 -8
- package/src/runtime/host/actor-packet-relay.ts +290 -261
- package/src/runtime/host/actor-placement-coordinator.ts +165 -150
- package/src/runtime/host/actor-runtime-options-factory.ts +89 -87
- package/src/runtime/host/actor-transfer-authority-runtime.ts +46 -38
- package/src/runtime/host/actor-transfer-runtime.ts +391 -395
- package/src/runtime/host/application-job-queue.ts +32 -48
- package/src/runtime/host/bound-session-relay.ts +7 -9
- package/src/runtime/host/channel-runtime-options-factory.ts +75 -35
- package/src/runtime/host/host-capacity-status.ts +10 -9
- package/src/runtime/host/index.ts +861 -789
- package/src/runtime/host/instance-activation-authority.ts +125 -143
- package/src/runtime/host/location-runtime-owner.ts +38 -26
- package/src/runtime/host/mesh-router-resolver.ts +30 -23
- package/src/runtime/host/relocation-direct-transfer.ts +15 -15
- package/src/runtime/host/relocation-integrity-fault-gate.ts +3 -2
- package/src/runtime/host/remote-actor-join-receiver.ts +43 -34
- package/src/runtime/host/remote-actor-packet-target-store.ts +54 -52
- package/src/runtime/host/remote-bound-session-relay.ts +274 -247
- package/src/runtime/host/route-mesh-runtime-options.ts +1 -3
- package/src/runtime/host/route-mesh-runtime.ts +227 -181
- package/src/runtime/host/runtime-shutdown.ts +4 -3
- package/src/runtime/host/service-relocation-control.ts +14 -5
- package/src/runtime/host/service-relocation-host-runtime.ts +1388 -1165
- package/src/runtime/host/spot-address-transport.ts +284 -237
- package/src/runtime/host/spot-node-runtime-options-factory.ts +14 -8
- package/src/runtime/host/spot-runtime-options-factory.ts +71 -62
- package/src/runtime/host/stateful-authority-route-runtime.ts +117 -116
- package/src/runtime/host/user-spot-creation-coordinator.ts +225 -220
- package/src/runtime/locations/actor-location-claims.ts +61 -49
- package/src/runtime/locations/actor-session-route-claims.ts +1 -4
- package/src/runtime/locations/aggregate-inventory-store.ts +105 -134
- package/src/runtime/locations/authority-key-codec.ts +13 -10
- package/src/runtime/locations/auto-connect-loop.ts +14 -11
- package/src/runtime/locations/auto-connect-planner.ts +42 -31
- package/src/runtime/locations/auto-connect-reconciler.ts +106 -63
- package/src/runtime/locations/canonical-codec.ts +3 -1
- package/src/runtime/locations/creation-operation-id.ts +4 -3
- package/src/runtime/locations/domain-store-contract.ts +13 -7
- package/src/runtime/locations/in-memory-authority-store.ts +232 -199
- package/src/runtime/locations/in-memory-location-store.ts +432 -250
- package/src/runtime/locations/in-memory-provider-location-store.ts +16 -17
- package/src/runtime/locations/internal-store-contracts.ts +95 -38
- package/src/runtime/locations/key-codec.ts +3 -4
- package/src/runtime/locations/lease-tracker.ts +36 -27
- package/src/runtime/locations/lifecycle-runtime.ts +15 -3
- package/src/runtime/locations/lifecycle.ts +33 -28
- package/src/runtime/locations/location-store-repository.ts +1329 -1185
- package/src/runtime/locations/opaque-record-key.ts +4 -9
- package/src/runtime/locations/resolvers.ts +201 -178
- package/src/runtime/locations/runtime.ts +251 -159
- package/src/runtime/locations/spot-location-claims.ts +110 -112
- package/src/runtime/messaging/creation-payload-codec.ts +4 -1
- package/src/runtime/messaging/framework-json-v1.ts +34 -15
- package/src/runtime/messaging/packet-name.ts +8 -6
- package/src/runtime/messaging/payload-codec.ts +70 -50
- package/src/runtime/messaging/submission-result.ts +6 -9
- package/src/runtime/protocol/service_wire_codec.generated.ts +1376 -159
- package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
- package/src/runtime/routing-id.ts +10 -7
- package/src/runtime/spots/formal-remote-actor-admission-registry.ts +56 -62
- package/src/runtime/spots/formal-remote-actor-transfer-registry.ts +23 -20
- package/src/runtime/spots/index.ts +593 -563
- package/src/runtime/spots/spot-activation-registry.ts +38 -34
- package/src/runtime/spots/spot-activation-state.ts +43 -51
- package/src/runtime/spots/spot-activation.ts +179 -116
- package/src/runtime/spots/spot-actor-admission-coordinator.ts +110 -78
- package/src/runtime/spots/spot-actor-join-dispatch.ts +57 -41
- package/src/runtime/spots/spot-actor-lifecycle-drain.ts +5 -7
- package/src/runtime/spots/spot-actor-membership.ts +25 -27
- package/src/runtime/spots/spot-actor-packet-dispatch.ts +184 -153
- package/src/runtime/spots/spot-actor-packet-drain.ts +54 -42
- package/src/runtime/spots/spot-actor-packet-relay-dispatch.ts +37 -39
- package/src/runtime/spots/spot-closing.ts +11 -12
- package/src/runtime/spots/spot-context.ts +46 -26
- package/src/runtime/spots/spot-entry-activation.ts +84 -65
- package/src/runtime/spots/spot-handle.ts +17 -15
- package/src/runtime/spots/spot-handler-registry.ts +44 -31
- package/src/runtime/spots/spot-location-claim.ts +5 -4
- package/src/runtime/spots/spot-manager-public.ts +62 -82
- package/src/runtime/spots/spot-native-actor-join-admission.ts +20 -16
- package/src/runtime/spots/spot-node-autoconnect.ts +54 -61
- package/src/runtime/spots/spot-node-runtime-manager.ts +230 -221
- package/src/runtime/spots/spot-outbound.ts +47 -59
- package/src/runtime/spots/spot-publisher-transport.ts +5 -4
- package/src/runtime/spots/spot-remote-codec.ts +66 -65
- package/src/runtime/spots/spot-remote-route-codec.ts +61 -41
- package/src/runtime/spots/spot-route-packet-dispatch.ts +128 -95
- package/src/runtime/spots/spot-route-replies.ts +23 -24
- package/src/runtime/spots/spot-route-reply-wire.ts +3 -2
- package/src/runtime/spots/spot-routed-actor-admission.ts +37 -27
- package/src/runtime/spots/spot-routed-bound-session-dispatch.ts +11 -17
- package/src/runtime/spots/spot-routed-frame-dispatch.ts +18 -15
- package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +35 -26
- package/src/runtime/spots/spot-routing-internal.ts +1 -4
- package/src/runtime/spots/spot-runtime-ports.ts +5 -5
- package/src/runtime/spots/spot-serial-executor.ts +5 -9
- package/src/runtime/spots/spot-serial-turn-executor.ts +19 -26
- package/src/runtime/spots/spot-subscription-dispatch.ts +56 -46
- package/src/runtime/spots/spot-timer.ts +98 -91
- package/src/runtime/streams/actor-session-binding-registry.ts +277 -235
- package/src/runtime/streams/actor-session-binding-runtime-owner.ts +7 -4
- package/src/runtime/streams/actor-session-lifecycle-coordinator.ts +3 -1
- package/src/runtime/streams/bound-actor-relay-sender.ts +53 -51
- package/src/runtime/streams/bound-session-response-target.ts +29 -8
- package/src/runtime/streams/bound-session-service.ts +39 -39
- package/src/runtime/streams/index.ts +176 -100
- package/src/runtime/streams/managed-stream.ts +98 -87
- package/src/runtime/streams/native-fallback-bound-session.ts +57 -52
- package/src/runtime/streams/protocol.ts +73 -43
- package/src/runtime/streams/session-actor-coordinator.ts +97 -89
- package/src/runtime/streams/session-calls.ts +2 -8
- package/src/runtime/streams/session-context.ts +66 -48
- package/src/runtime/streams/session-provider.ts +5 -7
- package/src/runtime/streams/session-serial-executor.ts +7 -6
- package/src/runtime/streams/stream-binding-runtime-ports.ts +79 -29
- package/src/runtime/streams/stream-frame-factory.ts +55 -38
- package/src/runtime/streams/stream-session-runtime.ts +268 -210
- package/src/runtime/workers/index.ts +68 -29
|
@@ -67,10 +67,7 @@ import type {
|
|
|
67
67
|
ZLinkSpotLocationFilter,
|
|
68
68
|
ZLinkSpotLocationKey
|
|
69
69
|
} from './internal-location-contracts';
|
|
70
|
-
import {
|
|
71
|
-
ZLinkFrameworkRuntimeState,
|
|
72
|
-
ZLinkObjectRole
|
|
73
|
-
} from '../../contracts';
|
|
70
|
+
import { ZLinkFrameworkRuntimeState, ZLinkObjectRole } from '../../contracts';
|
|
74
71
|
import { ZLinkLocationWriteStatus as WriteStatus } from '../../contracts/Locations';
|
|
75
72
|
import { ZLinkInMemoryLocationStore } from './in-memory-location-store';
|
|
76
73
|
import { storeKey } from './in-memory-provider-location-store';
|
|
@@ -99,10 +96,7 @@ const AGGREGATE_COMMIT_RETRY_WINDOW_MS = 5_000;
|
|
|
99
96
|
const MAX_AGGREGATE_COMMIT_CONFLICT_RETRIES = 64;
|
|
100
97
|
const MAX_DESCRIPTOR_WRITE_RETRIES = 3;
|
|
101
98
|
|
|
102
|
-
type StoredAuthoritySnapshot = Omit<
|
|
103
|
-
ZLinkAuthoritySnapshot,
|
|
104
|
-
'kind' | 'storeVersion' | 'storeNow'
|
|
105
|
-
>;
|
|
99
|
+
type StoredAuthoritySnapshot = Omit<ZLinkAuthoritySnapshot, 'kind' | 'storeVersion' | 'storeNow'>;
|
|
106
100
|
|
|
107
101
|
interface AuthorityRecord {
|
|
108
102
|
readonly snapshot: StoredAuthoritySnapshot;
|
|
@@ -224,13 +218,15 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
224
218
|
if (!Number.isInteger(limit) || limit < 1 || limit > 1_000) {
|
|
225
219
|
throw new RangeError('Authority scan limit must be in 1..1000.');
|
|
226
220
|
}
|
|
227
|
-
const scan = await this.provider.scan(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
221
|
+
const scan = await this.provider.scan(
|
|
222
|
+
{
|
|
223
|
+
prefix: AUTHORITY_PREIMAGE_PREFIX,
|
|
224
|
+
cursor:
|
|
225
|
+
cursor === undefined ? undefined : ({ value: cursor.encoded } as ZLinkStoreScanCursor),
|
|
226
|
+
limit
|
|
227
|
+
},
|
|
228
|
+
signal
|
|
229
|
+
);
|
|
234
230
|
if (scan.kind === 'expired') return { kind: 'scanExpired' };
|
|
235
231
|
const items = [];
|
|
236
232
|
for (const item of scan.value.items) {
|
|
@@ -247,9 +243,10 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
247
243
|
return {
|
|
248
244
|
kind: 'page',
|
|
249
245
|
items,
|
|
250
|
-
nextCursor:
|
|
251
|
-
|
|
252
|
-
|
|
246
|
+
nextCursor:
|
|
247
|
+
scan.value.nextCursor === undefined
|
|
248
|
+
? undefined
|
|
249
|
+
: ({ encoded: scan.value.nextCursor.value } as ZLinkAuthorityScanCursor)
|
|
253
250
|
};
|
|
254
251
|
}
|
|
255
252
|
|
|
@@ -280,39 +277,36 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
280
277
|
return { kind: 'conflict', current: snapshot };
|
|
281
278
|
}
|
|
282
279
|
if (
|
|
283
|
-
record.snapshot.allocation.state !== 'active'
|
|
284
|
-
|
|
285
|
-
!== expectedStoreVersion.value
|
|
280
|
+
record.snapshot.allocation.state !== 'active' ||
|
|
281
|
+
(record.visibleStoreVersion ?? current.value.version.value) !== expectedStoreVersion.value
|
|
286
282
|
) {
|
|
287
283
|
return { kind: 'conflict', current: snapshot };
|
|
288
284
|
}
|
|
289
285
|
if (mutation.kind === 'rebindOwnerLease') {
|
|
290
286
|
if (
|
|
291
|
-
mutation.expectedOwner.ownerId !== record.snapshot.ownerId
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
287
|
+
mutation.expectedOwner.ownerId !== record.snapshot.ownerId ||
|
|
288
|
+
mutation.expectedOwner.leaseGeneration !== record.snapshot.ownerLeaseGeneration ||
|
|
289
|
+
mutation.targetOwner.ownerId !== record.snapshot.ownerId ||
|
|
290
|
+
mutation.targetOwner.leaseGeneration === record.snapshot.ownerLeaseGeneration
|
|
295
291
|
) {
|
|
296
292
|
return { kind: 'conflict', current: snapshot };
|
|
297
293
|
}
|
|
298
294
|
const descriptorKey = meshKey(
|
|
299
295
|
record.snapshot.allocation.descriptor.meshName,
|
|
300
296
|
record.snapshot.allocation.descriptor.rid
|
|
301
|
-
|
|
297
|
+
);
|
|
302
298
|
const [descriptorRead, targetLeaseRead] = await Promise.all([
|
|
303
299
|
this.provider.read(descriptorKey, signal),
|
|
304
300
|
this.provider.read(ownerKey(mutation.targetOwner.ownerId), signal)
|
|
305
301
|
]);
|
|
306
|
-
if (
|
|
307
|
-
descriptorRead,
|
|
308
|
-
targetLeaseRead,
|
|
309
|
-
{
|
|
302
|
+
if (
|
|
303
|
+
liveTargetDescriptor(descriptorRead, targetLeaseRead, {
|
|
310
304
|
meshName: record.snapshot.allocation.descriptor.meshName,
|
|
311
305
|
nodeRid: record.snapshot.allocation.descriptor.rid,
|
|
312
306
|
nodeLifecycleGeneration: record.snapshot.allocation.descriptorLifecycleGeneration,
|
|
313
307
|
owner: mutation.targetOwner
|
|
314
|
-
}
|
|
315
|
-
)
|
|
308
|
+
}) === undefined
|
|
309
|
+
) {
|
|
316
310
|
return { kind: 'conflict', current: snapshot };
|
|
317
311
|
}
|
|
318
312
|
const nextRecord: AuthorityRecord = {
|
|
@@ -324,17 +318,21 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
324
318
|
payload: Buffer.from(mutation.payload)
|
|
325
319
|
}
|
|
326
320
|
};
|
|
327
|
-
const result = await this.provider.write(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
321
|
+
const result = await this.provider.write(
|
|
322
|
+
{
|
|
323
|
+
conditions: [
|
|
324
|
+
{ kind: 'version', key: rowKey, expected: current.value.version },
|
|
325
|
+
versionCondition(descriptorKey, descriptorRead),
|
|
326
|
+
versionCondition(ownerKey(mutation.targetOwner.ownerId), targetLeaseRead)
|
|
327
|
+
],
|
|
328
|
+
mutations: [{ kind: 'put', key: rowKey, bytes: encodeAuthorityRecord(nextRecord) }]
|
|
329
|
+
},
|
|
330
|
+
signal
|
|
331
|
+
);
|
|
335
332
|
if (result.kind === 'conflict') continue;
|
|
336
|
-
const storeVersion = result.putVersions.find(
|
|
337
|
-
entry.key.value === rowKey.value
|
|
333
|
+
const storeVersion = result.putVersions.find(
|
|
334
|
+
(entry) => entry.key.value === rowKey.value
|
|
335
|
+
)?.version;
|
|
338
336
|
if (storeVersion === undefined) {
|
|
339
337
|
throw new Error('Authority lease rebind did not return a row version.');
|
|
340
338
|
}
|
|
@@ -343,74 +341,70 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
343
341
|
return { kind: 'stored', ...withoutKind };
|
|
344
342
|
}
|
|
345
343
|
if (
|
|
346
|
-
mutation.kind === 'restore'
|
|
347
|
-
|
|
348
|
-
mutation.expectedOwner.
|
|
349
|
-
|| mutation.expectedOwner.leaseGeneration
|
|
350
|
-
!== record.snapshot.ownerLeaseGeneration
|
|
351
|
-
)
|
|
344
|
+
mutation.kind === 'restore' &&
|
|
345
|
+
(mutation.expectedOwner.ownerId !== record.snapshot.ownerId ||
|
|
346
|
+
mutation.expectedOwner.leaseGeneration !== record.snapshot.ownerLeaseGeneration)
|
|
352
347
|
) {
|
|
353
348
|
return { kind: 'conflict', current: snapshot };
|
|
354
349
|
}
|
|
355
350
|
const leaseKey = ownerKey(record.snapshot.ownerId);
|
|
356
351
|
const lease = await this.provider.read(leaseKey, signal);
|
|
357
|
-
if (
|
|
358
|
-
mutation.kind !== 'restore'
|
|
359
|
-
&& !sameLiveOwner(lease, record.snapshot)
|
|
360
|
-
) {
|
|
352
|
+
if (mutation.kind !== 'restore' && !sameLiveOwner(lease, record.snapshot)) {
|
|
361
353
|
return { kind: 'conflict', current: snapshot };
|
|
362
354
|
}
|
|
363
355
|
const capacityRowKey = capacityKey(
|
|
364
356
|
record.snapshot.allocation.descriptor.meshName,
|
|
365
357
|
String(record.snapshot.allocation.descriptor.rid)
|
|
366
358
|
);
|
|
367
|
-
const capacityRead =
|
|
368
|
-
? await this.provider.read(capacityRowKey, signal)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
const result = await this.provider.write({
|
|
381
|
-
conditions: [
|
|
382
|
-
{ kind: 'version', key: rowKey, expected: current.value.version },
|
|
383
|
-
...(mutation.kind === 'restore'
|
|
384
|
-
? []
|
|
385
|
-
: [versionCondition(leaseKey, lease)]),
|
|
386
|
-
...(capacityRead === undefined
|
|
387
|
-
? []
|
|
388
|
-
: [conditionFor(capacityRowKey, capacityRead)])
|
|
389
|
-
],
|
|
390
|
-
mutations: mutation.kind === 'delete'
|
|
391
|
-
? [
|
|
392
|
-
{ kind: 'delete', key: rowKey },
|
|
393
|
-
{
|
|
394
|
-
kind: 'put',
|
|
395
|
-
key: capacityRowKey,
|
|
396
|
-
bytes: encodeJson({
|
|
397
|
-
active: subtractCapacity(
|
|
398
|
-
capacityRead?.kind === 'found'
|
|
399
|
-
? decodeJson<CapacityRecord>(capacityRead.value.bytes).active
|
|
400
|
-
: emptyCapacityRecord().active,
|
|
401
|
-
record.snapshot.allocation.capacity
|
|
402
|
-
),
|
|
403
|
-
pending: capacityRead?.kind === 'found'
|
|
404
|
-
? decodeJson<CapacityRecord>(capacityRead.value.bytes).pending
|
|
405
|
-
: emptyCapacityRecord().pending
|
|
406
|
-
} satisfies CapacityRecord)
|
|
359
|
+
const capacityRead =
|
|
360
|
+
mutation.kind === 'delete' ? await this.provider.read(capacityRowKey, signal) : undefined;
|
|
361
|
+
const nextRecord: AuthorityRecord | undefined =
|
|
362
|
+
mutation.kind === 'delete'
|
|
363
|
+
? undefined
|
|
364
|
+
: {
|
|
365
|
+
...record,
|
|
366
|
+
visibleStoreVersion: undefined,
|
|
367
|
+
snapshot: {
|
|
368
|
+
...record.snapshot,
|
|
369
|
+
payload: Buffer.from(mutation.payload)
|
|
407
370
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
371
|
+
};
|
|
372
|
+
const result = await this.provider.write(
|
|
373
|
+
{
|
|
374
|
+
conditions: [
|
|
375
|
+
{ kind: 'version', key: rowKey, expected: current.value.version },
|
|
376
|
+
...(mutation.kind === 'restore' ? [] : [versionCondition(leaseKey, lease)]),
|
|
377
|
+
...(capacityRead === undefined ? [] : [conditionFor(capacityRowKey, capacityRead)])
|
|
378
|
+
],
|
|
379
|
+
mutations:
|
|
380
|
+
mutation.kind === 'delete'
|
|
381
|
+
? [
|
|
382
|
+
{ kind: 'delete', key: rowKey },
|
|
383
|
+
{
|
|
384
|
+
kind: 'put',
|
|
385
|
+
key: capacityRowKey,
|
|
386
|
+
bytes: encodeJson({
|
|
387
|
+
active: subtractCapacity(
|
|
388
|
+
capacityRead?.kind === 'found'
|
|
389
|
+
? decodeJson<CapacityRecord>(capacityRead.value.bytes).active
|
|
390
|
+
: emptyCapacityRecord().active,
|
|
391
|
+
record.snapshot.allocation.capacity
|
|
392
|
+
),
|
|
393
|
+
pending:
|
|
394
|
+
capacityRead?.kind === 'found'
|
|
395
|
+
? decodeJson<CapacityRecord>(capacityRead.value.bytes).pending
|
|
396
|
+
: emptyCapacityRecord().pending
|
|
397
|
+
} satisfies CapacityRecord)
|
|
398
|
+
}
|
|
399
|
+
]
|
|
400
|
+
: [{ kind: 'put', key: rowKey, bytes: encodeAuthorityRecord(nextRecord!) }]
|
|
401
|
+
},
|
|
402
|
+
signal
|
|
403
|
+
);
|
|
411
404
|
if (result.kind === 'conflict') continue;
|
|
412
|
-
const storeVersion = result.putVersions.find(
|
|
413
|
-
entry.key.value === rowKey.value
|
|
405
|
+
const storeVersion = result.putVersions.find(
|
|
406
|
+
(entry) => entry.key.value === rowKey.value
|
|
407
|
+
)?.version;
|
|
414
408
|
if (mutation.kind === 'delete') {
|
|
415
409
|
return {
|
|
416
410
|
kind: 'deleted',
|
|
@@ -449,10 +443,13 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
449
443
|
}
|
|
450
444
|
} else {
|
|
451
445
|
const staging = aggregateRecord(request, fingerprint, 'staging');
|
|
452
|
-
const claimed = await this.provider.write(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
446
|
+
const claimed = await this.provider.write(
|
|
447
|
+
{
|
|
448
|
+
conditions: [{ kind: 'missing', key: rowKey }],
|
|
449
|
+
mutations: [{ kind: 'put', key: rowKey, bytes: encodeJson(staging) }]
|
|
450
|
+
},
|
|
451
|
+
signal
|
|
452
|
+
);
|
|
456
453
|
if (claimed.kind === 'conflict') {
|
|
457
454
|
aggregateRead = await this.provider.read(rowKey, signal);
|
|
458
455
|
if (aggregateRead.kind === 'missing') return { kind: 'conflict' };
|
|
@@ -460,8 +457,9 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
460
457
|
const reconciled = reconcileAggregatePrepare(existing, fingerprint, fence);
|
|
461
458
|
if (reconciled !== undefined) return reconciled;
|
|
462
459
|
} else {
|
|
463
|
-
const version = claimed.putVersions.find(
|
|
464
|
-
value.key.value === rowKey.value
|
|
460
|
+
const version = claimed.putVersions.find(
|
|
461
|
+
(value) => value.key.value === rowKey.value
|
|
462
|
+
)?.version;
|
|
465
463
|
if (version === undefined) {
|
|
466
464
|
throw new Error('Aggregate staging did not return a Store version.');
|
|
467
465
|
}
|
|
@@ -476,28 +474,21 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
476
474
|
}
|
|
477
475
|
}
|
|
478
476
|
const aggregate = decodeJson<AggregateRecord>(aggregateRead.value.bytes);
|
|
479
|
-
if (
|
|
480
|
-
aggregate.state !== 'staging'
|
|
481
|
-
|| aggregate.requestFingerprint !== fingerprint
|
|
482
|
-
) {
|
|
477
|
+
if (aggregate.state !== 'staging' || aggregate.requestFingerprint !== fingerprint) {
|
|
483
478
|
return { kind: 'conflict' };
|
|
484
479
|
}
|
|
485
480
|
try {
|
|
486
481
|
await this.aggregateInventory.store(request, signal);
|
|
487
|
-
const entries = await this.aggregateInventory.read(
|
|
488
|
-
fence,
|
|
489
|
-
request.inventoryDigest,
|
|
490
|
-
signal
|
|
491
|
-
);
|
|
482
|
+
const entries = await this.aggregateInventory.read(fence, request.inventoryDigest, signal);
|
|
492
483
|
if (entries.length !== request.participants.length) {
|
|
493
484
|
throw new Error('Aggregate inventory count changed after staging.');
|
|
494
485
|
}
|
|
495
486
|
await parallelForEach(request.participants, 64, async (participant, index) => {
|
|
496
487
|
const entry = entries[index]!;
|
|
497
488
|
if (
|
|
498
|
-
entry.authorityKey !== participant.authorityKey.value
|
|
499
|
-
|
|
500
|
-
|
|
489
|
+
entry.authorityKey !== participant.authorityKey.value ||
|
|
490
|
+
entry.expectedStoreVersion !== participant.expectedStoreVersion.value ||
|
|
491
|
+
entry.ownerTransition !== participant.ownerTransition
|
|
501
492
|
) {
|
|
502
493
|
throw new Error('Aggregate inventory differs from its participant request.');
|
|
503
494
|
}
|
|
@@ -522,26 +513,19 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
522
513
|
request.targetDescriptor.meshName,
|
|
523
514
|
request.targetDescriptor.rid
|
|
524
515
|
);
|
|
525
|
-
const [
|
|
526
|
-
descriptorRead,
|
|
527
|
-
leaseRead,
|
|
528
|
-
targetCapacityRead
|
|
529
|
-
] = await Promise.all([
|
|
516
|
+
const [descriptorRead, leaseRead, targetCapacityRead] = await Promise.all([
|
|
530
517
|
this.provider.read(targetDescriptorKey, signal),
|
|
531
518
|
this.provider.read(targetLeaseKey, signal),
|
|
532
519
|
this.provider.read(targetCapacityKey, signal)
|
|
533
520
|
]);
|
|
534
|
-
const descriptor = liveTargetDescriptor(
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
const targetCapacity = targetCapacityRead.kind === 'missing'
|
|
540
|
-
? emptyCapacityRecord()
|
|
541
|
-
: decodeJson<CapacityRecord>(targetCapacityRead.value.bytes);
|
|
521
|
+
const descriptor = liveTargetDescriptor(descriptorRead, leaseRead, aggregateTarget(request));
|
|
522
|
+
const targetCapacity =
|
|
523
|
+
targetCapacityRead.kind === 'missing'
|
|
524
|
+
? emptyCapacityRecord()
|
|
525
|
+
: decodeJson<CapacityRecord>(targetCapacityRead.value.bytes);
|
|
542
526
|
if (
|
|
543
|
-
descriptor === undefined
|
|
544
|
-
|
|
527
|
+
descriptor === undefined ||
|
|
528
|
+
!capacityAvailable(descriptor, request.capacity, targetCapacity)
|
|
545
529
|
) {
|
|
546
530
|
await this.abortAggregateStaging(fence, signal);
|
|
547
531
|
return { kind: 'conflict' };
|
|
@@ -559,7 +543,10 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
559
543
|
await this.abortAggregateStaging(fence, signal);
|
|
560
544
|
return { kind: 'generationExhausted' };
|
|
561
545
|
}
|
|
562
|
-
} else if (
|
|
546
|
+
} else if (
|
|
547
|
+
ownerGenerationEnd !==
|
|
548
|
+
ownerGenerationStart + BigInt(request.participants.length) - 1n
|
|
549
|
+
) {
|
|
563
550
|
await this.abortAggregateStaging(fence, signal);
|
|
564
551
|
return { kind: 'conflict' };
|
|
565
552
|
}
|
|
@@ -582,8 +569,10 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
582
569
|
const reservedAggregate = await this.provider.read(rowKey, signal);
|
|
583
570
|
if (reservedAggregate.kind === 'missing') return { kind: 'conflict' };
|
|
584
571
|
const reservedRecord = decodeJson<AggregateRecord>(reservedAggregate.value.bytes);
|
|
585
|
-
if (
|
|
586
|
-
|
|
572
|
+
if (
|
|
573
|
+
reservedRecord.ownerGenerationStart === undefined ||
|
|
574
|
+
reservedRecord.ownerGenerationEnd === undefined
|
|
575
|
+
) {
|
|
587
576
|
return { kind: 'conflict' };
|
|
588
577
|
}
|
|
589
578
|
ownerGenerationStart = reservedRecord.ownerGenerationStart;
|
|
@@ -596,18 +585,15 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
596
585
|
expectedStoreVersion: participant.expectedStoreVersion.value
|
|
597
586
|
});
|
|
598
587
|
if (participant.ownerTransition === 'newOwner') {
|
|
599
|
-
sourceCapacity = addCapacityVector(
|
|
600
|
-
sourceCapacity,
|
|
601
|
-
record.snapshot.allocation.capacity
|
|
602
|
-
);
|
|
588
|
+
sourceCapacity = addCapacityVector(sourceCapacity, record.snapshot.allocation.capacity);
|
|
603
589
|
}
|
|
604
590
|
continue;
|
|
605
591
|
}
|
|
606
592
|
if (
|
|
607
|
-
record.aggregate !== undefined
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
593
|
+
record.aggregate !== undefined ||
|
|
594
|
+
record.snapshot.allocation.state !== 'active' ||
|
|
595
|
+
(record.visibleStoreVersion ?? current.value.version.value) !==
|
|
596
|
+
participant.expectedStoreVersion.value
|
|
611
597
|
) {
|
|
612
598
|
await this.clearAggregateMarkers(fence, installed, signal);
|
|
613
599
|
await this.abortAggregateStaging(fence, signal);
|
|
@@ -621,75 +607,85 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
621
607
|
return { kind: 'conflict' };
|
|
622
608
|
}
|
|
623
609
|
} else {
|
|
624
|
-
sourceCapacity = addCapacityVector(
|
|
625
|
-
sourceCapacity,
|
|
626
|
-
record.snapshot.allocation.capacity
|
|
627
|
-
);
|
|
610
|
+
sourceCapacity = addCapacityVector(sourceCapacity, record.snapshot.allocation.capacity);
|
|
628
611
|
}
|
|
629
|
-
const targetAuthorityOwnerGeneration =
|
|
630
|
-
|
|
631
|
-
|
|
612
|
+
const targetAuthorityOwnerGeneration =
|
|
613
|
+
participant.ownerTransition === 'newOwner'
|
|
614
|
+
? ownerGenerationStart + BigInt(index)
|
|
615
|
+
: record.snapshot.authorityOwnerGeneration;
|
|
632
616
|
const marker: AggregateParticipantFenceRecord = {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
617
|
+
aggregateId: fence.aggregateId.value,
|
|
618
|
+
aggregateGeneration: fence.aggregateGeneration,
|
|
619
|
+
index,
|
|
620
|
+
expectedStoreVersion: participant.expectedStoreVersion.value,
|
|
621
|
+
ownerTransition: participant.ownerTransition,
|
|
622
|
+
targetAuthorityOwnerGeneration,
|
|
623
|
+
authorityPayloadSha256: entry.authorityPayloadSha256,
|
|
624
|
+
membershipMutationSha256: entry.membershipMutationSha256
|
|
625
|
+
};
|
|
642
626
|
const reservingOwnerGenerationBlock = ownerGenerationCounterRead !== undefined;
|
|
643
|
-
const stored = await this.provider.write(
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
record
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
627
|
+
const stored = await this.provider.write(
|
|
628
|
+
{
|
|
629
|
+
conditions: [
|
|
630
|
+
{ kind: 'version', key: authorityRowKey, expected: current.value.version },
|
|
631
|
+
...(reservingOwnerGenerationBlock
|
|
632
|
+
? [conditionFor(AUTHORITY_OWNER_COUNTER_KEY, ownerGenerationCounterRead!)]
|
|
633
|
+
: []),
|
|
634
|
+
...(reservingOwnerGenerationBlock
|
|
635
|
+
? [{ kind: 'version' as const, key: rowKey, expected: aggregateRead.value.version }]
|
|
636
|
+
: [])
|
|
637
|
+
],
|
|
638
|
+
mutations: [
|
|
639
|
+
{
|
|
640
|
+
kind: 'put',
|
|
641
|
+
key: authorityRowKey,
|
|
642
|
+
bytes: encodeAuthorityRecord({
|
|
643
|
+
...record,
|
|
644
|
+
aggregate: marker,
|
|
645
|
+
visibleStoreVersion: record.visibleStoreVersion ?? current.value.version.value
|
|
646
|
+
} satisfies AuthorityRecord)
|
|
647
|
+
},
|
|
648
|
+
...(reservingOwnerGenerationBlock
|
|
649
|
+
? [
|
|
650
|
+
{
|
|
651
|
+
kind: 'put' as const,
|
|
652
|
+
key: AUTHORITY_OWNER_COUNTER_KEY,
|
|
653
|
+
bytes: encodeText((ownerGenerationEnd! + 1n).toString())
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
kind: 'put' as const,
|
|
657
|
+
key: rowKey,
|
|
658
|
+
bytes: encodeJson({
|
|
659
|
+
...aggregate,
|
|
660
|
+
ownerGenerationStart,
|
|
661
|
+
ownerGenerationEnd
|
|
662
|
+
} satisfies AggregateRecord)
|
|
663
|
+
}
|
|
664
|
+
]
|
|
665
|
+
: [])
|
|
666
|
+
]
|
|
667
|
+
},
|
|
668
|
+
signal
|
|
669
|
+
);
|
|
682
670
|
if (stored.kind === 'conflict') {
|
|
683
671
|
const raced = await this.provider.read(authorityRowKey, signal);
|
|
684
|
-
if (
|
|
685
|
-
|
|
686
|
-
|
|
672
|
+
if (
|
|
673
|
+
raced.kind === 'found' &&
|
|
674
|
+
sameAggregateMarker(
|
|
675
|
+
decodeAuthorityRecord(raced.value.bytes).aggregate,
|
|
676
|
+
fence,
|
|
677
|
+
index,
|
|
678
|
+
participant
|
|
679
|
+
)
|
|
680
|
+
) {
|
|
687
681
|
if (reservingOwnerGenerationBlock) {
|
|
688
682
|
const reservedAggregate = await this.provider.read(rowKey, signal);
|
|
689
683
|
if (reservedAggregate.kind === 'missing') return { kind: 'conflict' };
|
|
690
684
|
const reservedRecord = decodeJson<AggregateRecord>(reservedAggregate.value.bytes);
|
|
691
|
-
if (
|
|
692
|
-
|
|
685
|
+
if (
|
|
686
|
+
reservedRecord.ownerGenerationStart === undefined ||
|
|
687
|
+
reservedRecord.ownerGenerationEnd === undefined
|
|
688
|
+
) {
|
|
693
689
|
return { kind: 'conflict' };
|
|
694
690
|
}
|
|
695
691
|
ownerGenerationStart = reservedRecord.ownerGenerationStart;
|
|
@@ -697,7 +693,10 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
697
693
|
ownerGenerationCounterRead = undefined;
|
|
698
694
|
aggregateRead = reservedAggregate;
|
|
699
695
|
}
|
|
700
|
-
installed.push({
|
|
696
|
+
installed.push({
|
|
697
|
+
key: authorityRowKey,
|
|
698
|
+
expectedStoreVersion: participant.expectedStoreVersion.value
|
|
699
|
+
});
|
|
701
700
|
continue;
|
|
702
701
|
}
|
|
703
702
|
// A peer can be in the middle of publishing this same aggregate.
|
|
@@ -706,8 +705,10 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
706
705
|
const aggregateRaced = await this.provider.read(rowKey, signal);
|
|
707
706
|
if (aggregateRaced.kind === 'found') {
|
|
708
707
|
const aggregateRecord = decodeJson<AggregateRecord>(aggregateRaced.value.bytes);
|
|
709
|
-
if (
|
|
710
|
-
|
|
708
|
+
if (
|
|
709
|
+
aggregateRecord.requestFingerprint === fingerprint &&
|
|
710
|
+
(aggregateRecord.state === 'staging' || aggregateRecord.state === 'prepared')
|
|
711
|
+
) {
|
|
711
712
|
return await this.prepareAggregate(request, signal);
|
|
712
713
|
}
|
|
713
714
|
}
|
|
@@ -738,29 +739,32 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
738
739
|
if (latest.state !== 'staging' || latest.requestFingerprint !== fingerprint) {
|
|
739
740
|
return { kind: 'conflict' };
|
|
740
741
|
}
|
|
741
|
-
const reserved = await this.provider.write(
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
742
|
+
const reserved = await this.provider.write(
|
|
743
|
+
{
|
|
744
|
+
conditions: [
|
|
745
|
+
{ kind: 'version', key: rowKey, expected: aggregateRead.value.version },
|
|
746
|
+
versionCondition(targetDescriptorKey, descriptorRead),
|
|
747
|
+
versionCondition(targetLeaseKey, leaseRead),
|
|
748
|
+
conditionFor(targetCapacityKey, targetCapacityRead)
|
|
749
|
+
],
|
|
750
|
+
mutations: [
|
|
751
|
+
{
|
|
752
|
+
kind: 'put',
|
|
753
|
+
key: rowKey,
|
|
754
|
+
bytes: encodeJson({ ...latest, state: 'prepared' } satisfies AggregateRecord)
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
kind: 'put',
|
|
758
|
+
key: targetCapacityKey,
|
|
759
|
+
bytes: encodeJson({
|
|
760
|
+
active: targetCapacity.active,
|
|
761
|
+
pending: addCapacity(targetCapacity.pending, request.capacity)
|
|
762
|
+
} satisfies CapacityRecord)
|
|
763
|
+
}
|
|
764
|
+
]
|
|
765
|
+
},
|
|
766
|
+
signal
|
|
767
|
+
);
|
|
764
768
|
if (reserved.kind === 'conflict') {
|
|
765
769
|
const raced = await this.provider.read(rowKey, signal);
|
|
766
770
|
if (raced.kind === 'found') {
|
|
@@ -840,20 +844,12 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
840
844
|
return { kind: 'stale' };
|
|
841
845
|
}
|
|
842
846
|
const [payload, membership] = await Promise.all([
|
|
843
|
-
requireProviderBytes(
|
|
844
|
-
|
|
845
|
-
aggregateParticipantPayloadKey(fence, index),
|
|
846
|
-
signal
|
|
847
|
-
),
|
|
848
|
-
requireProviderBytes(
|
|
849
|
-
this.provider,
|
|
850
|
-
aggregateParticipantMembershipKey(fence, index),
|
|
851
|
-
signal
|
|
852
|
-
)
|
|
847
|
+
requireProviderBytes(this.provider, aggregateParticipantPayloadKey(fence, index), signal),
|
|
848
|
+
requireProviderBytes(this.provider, aggregateParticipantMembershipKey(fence, index), signal)
|
|
853
849
|
]);
|
|
854
850
|
if (
|
|
855
|
-
sha256Hex(payload) !== entry.authorityPayloadSha256
|
|
856
|
-
|
|
851
|
+
sha256Hex(payload) !== entry.authorityPayloadSha256 ||
|
|
852
|
+
sha256Hex(membership) !== entry.membershipMutationSha256
|
|
857
853
|
) {
|
|
858
854
|
throw new Error('Aggregate participant staging checksum does not match inventory.');
|
|
859
855
|
}
|
|
@@ -874,11 +870,8 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
874
870
|
this.provider.read(targetLeaseKey, signal)
|
|
875
871
|
]);
|
|
876
872
|
if (
|
|
877
|
-
liveTargetDescriptor(
|
|
878
|
-
|
|
879
|
-
leaseRead,
|
|
880
|
-
aggregateTargetFromRecord(aggregate)
|
|
881
|
-
) === undefined
|
|
873
|
+
liveTargetDescriptor(descriptorRead, leaseRead, aggregateTargetFromRecord(aggregate)) ===
|
|
874
|
+
undefined
|
|
882
875
|
) {
|
|
883
876
|
return { kind: 'stale' };
|
|
884
877
|
}
|
|
@@ -939,44 +932,44 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
939
932
|
bytes: encodeJson(capacity)
|
|
940
933
|
});
|
|
941
934
|
}
|
|
942
|
-
const published = await this.provider.write(
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
935
|
+
const published = await this.provider.write(
|
|
936
|
+
{
|
|
937
|
+
conditions: [
|
|
938
|
+
{ kind: 'version', key: rowKey, expected: aggregateRead.value.version },
|
|
939
|
+
versionCondition(targetDescriptorKey, descriptorRead),
|
|
940
|
+
versionCondition(targetLeaseKey, leaseRead),
|
|
941
|
+
...[...capacityReads.entries()].map(([value, read]) => ({
|
|
942
|
+
kind: 'version' as const,
|
|
943
|
+
key: capacityKeys.get(value)!,
|
|
944
|
+
expected: read.value.version
|
|
945
|
+
}))
|
|
946
|
+
],
|
|
947
|
+
mutations: [
|
|
948
|
+
{
|
|
949
|
+
kind: 'put',
|
|
950
|
+
key: rowKey,
|
|
951
|
+
bytes: encodeJson({ ...aggregate, state: 'committed' } satisfies AggregateRecord)
|
|
952
|
+
},
|
|
953
|
+
...capacityMutations
|
|
954
|
+
]
|
|
955
|
+
},
|
|
956
|
+
signal
|
|
957
|
+
);
|
|
962
958
|
if (published.kind === 'conflict') {
|
|
963
959
|
aggregateRead = await this.provider.read(rowKey, signal);
|
|
964
960
|
if (aggregateRead.kind === 'missing') return { kind: 'stale' };
|
|
965
961
|
aggregate = decodeJson<AggregateRecord>(aggregateRead.value.bytes);
|
|
966
|
-
if (
|
|
967
|
-
|
|
968
|
-
|
|
962
|
+
if (
|
|
963
|
+
aggregate.state === 'prepared' &&
|
|
964
|
+
retryAttempt < MAX_AGGREGATE_COMMIT_CONFLICT_RETRIES &&
|
|
965
|
+
performance.now() < retryDeadlineAtMs
|
|
966
|
+
) {
|
|
969
967
|
// The commit CAS also fences the target's live owner lease, descriptor,
|
|
970
968
|
// and capacity rows. Re-read the whole prepared fence after an
|
|
971
969
|
// auxiliary-row race; a changed aggregate or participant fence exits
|
|
972
970
|
// through the normal stale checks on the next attempt.
|
|
973
971
|
await waitForAggregateCommitRetry(retryAttempt, retryDeadlineAtMs, signal);
|
|
974
|
-
return await this.commitAggregateCore(
|
|
975
|
-
fence,
|
|
976
|
-
retryAttempt + 1,
|
|
977
|
-
retryDeadlineAtMs,
|
|
978
|
-
signal
|
|
979
|
-
);
|
|
972
|
+
return await this.commitAggregateCore(fence, retryAttempt + 1, retryDeadlineAtMs, signal);
|
|
980
973
|
}
|
|
981
974
|
if (aggregate.state !== 'committed') return { kind: 'stale' };
|
|
982
975
|
} else {
|
|
@@ -1001,16 +994,20 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1001
994
|
return { kind: 'alreadyAborted' };
|
|
1002
995
|
}
|
|
1003
996
|
if (aggregate.state === 'committed') return { kind: 'stale' };
|
|
1004
|
-
const conditions: ZLinkStoreCondition[] = [
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
997
|
+
const conditions: ZLinkStoreCondition[] = [
|
|
998
|
+
{
|
|
999
|
+
kind: 'version',
|
|
1000
|
+
key: rowKey,
|
|
1001
|
+
expected: current.value.version
|
|
1002
|
+
}
|
|
1003
|
+
];
|
|
1004
|
+
const mutations: ZLinkStoreWriteRequest['mutations'][number][] = [
|
|
1005
|
+
{
|
|
1006
|
+
kind: 'put',
|
|
1007
|
+
key: rowKey,
|
|
1008
|
+
bytes: encodeJson({ ...aggregate, state: 'aborted' } satisfies AggregateRecord)
|
|
1009
|
+
}
|
|
1010
|
+
];
|
|
1014
1011
|
if (aggregate.state === 'prepared') {
|
|
1015
1012
|
const targetCapacityKey = capacityKey(
|
|
1016
1013
|
aggregate.targetDescriptor.meshName,
|
|
@@ -1044,19 +1041,13 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1044
1041
|
request: ZLinkObjectReserveRequest,
|
|
1045
1042
|
signal?: AbortSignal
|
|
1046
1043
|
): Promise<ZLinkObjectReserveResult> {
|
|
1047
|
-
const encodedAuthorityKey = encodeAuthorityKey(
|
|
1048
|
-
request.key.kind,
|
|
1049
|
-
request.key.globalId
|
|
1050
|
-
);
|
|
1044
|
+
const encodedAuthorityKey = encodeAuthorityKey(request.key.kind, request.key.globalId);
|
|
1051
1045
|
for (;;) {
|
|
1052
1046
|
signal?.throwIfAborted();
|
|
1053
1047
|
const rowKey = authorityKey(encodedAuthorityKey.value);
|
|
1054
1048
|
const descriptorKey = meshKey(request.target.meshName, request.target.nodeRid);
|
|
1055
1049
|
const leaseKey = ownerKey(request.target.owner.ownerId);
|
|
1056
|
-
const capacityRowKey = capacityKey(
|
|
1057
|
-
request.target.meshName,
|
|
1058
|
-
String(request.target.nodeRid)
|
|
1059
|
-
);
|
|
1050
|
+
const capacityRowKey = capacityKey(request.target.meshName, String(request.target.nodeRid));
|
|
1060
1051
|
const [
|
|
1061
1052
|
current,
|
|
1062
1053
|
descriptorRead,
|
|
@@ -1064,15 +1055,14 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1064
1055
|
capacityRead,
|
|
1065
1056
|
objectGenerationRead,
|
|
1066
1057
|
authorityOwnerGenerationRead
|
|
1067
|
-
] =
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
]);
|
|
1058
|
+
] = await Promise.all([
|
|
1059
|
+
this.provider.read(rowKey, signal),
|
|
1060
|
+
this.provider.read(descriptorKey, signal),
|
|
1061
|
+
this.provider.read(leaseKey, signal),
|
|
1062
|
+
this.provider.read(capacityRowKey, signal),
|
|
1063
|
+
this.provider.read(OBJECT_COUNTER_KEY, signal),
|
|
1064
|
+
this.provider.read(AUTHORITY_OWNER_COUNTER_KEY, signal)
|
|
1065
|
+
]);
|
|
1076
1066
|
if (current.kind === 'found') {
|
|
1077
1067
|
const record = decodeAuthorityRecord(current.value.bytes);
|
|
1078
1068
|
const snapshot = authoritySnapshot(
|
|
@@ -1081,16 +1071,18 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1081
1071
|
current.value.storeNow
|
|
1082
1072
|
);
|
|
1083
1073
|
if (
|
|
1084
|
-
snapshot.allocation.objectKind !== request.key.kind
|
|
1085
|
-
|
|
1074
|
+
snapshot.allocation.objectKind !== request.key.kind ||
|
|
1075
|
+
snapshot.allocation.stableType !== request.intent.stableType
|
|
1086
1076
|
) {
|
|
1087
1077
|
return { kind: 'typeMismatch', current: snapshot };
|
|
1088
1078
|
}
|
|
1089
1079
|
if (snapshot.allocation.state === 'active') {
|
|
1090
1080
|
return { kind: 'alreadyExists', current: snapshot };
|
|
1091
1081
|
}
|
|
1092
|
-
if (
|
|
1093
|
-
|
|
1082
|
+
if (
|
|
1083
|
+
record.aggregate !== undefined ||
|
|
1084
|
+
isCanonicalAuthorityPayload(record.snapshot.payload)
|
|
1085
|
+
) {
|
|
1094
1086
|
// A relocation record carries its own recovery protocol. Reserve
|
|
1095
1087
|
// may reclaim only an unfinished plain creation.
|
|
1096
1088
|
return { kind: 'conflict', current: snapshot };
|
|
@@ -1107,8 +1099,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1107
1099
|
this.provider.read(staleOwnerKey, signal),
|
|
1108
1100
|
this.provider.read(staleCapacityKey, signal)
|
|
1109
1101
|
]);
|
|
1110
|
-
if (staleOwnerRead.kind === 'found'
|
|
1111
|
-
&& staleOwnerRead.value.expiresAt === undefined) {
|
|
1102
|
+
if (staleOwnerRead.kind === 'found' && staleOwnerRead.value.expiresAt === undefined) {
|
|
1112
1103
|
// A lease is always written with a positive TTL, so a missing
|
|
1113
1104
|
// expiry is a corrupt record. Reclaiming deletes authority state,
|
|
1114
1105
|
// so refuse rather than read the absence as expiry.
|
|
@@ -1117,43 +1108,44 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1117
1108
|
if (sameLiveOwner(staleOwnerRead, record.snapshot)) {
|
|
1118
1109
|
return { kind: 'conflict', current: snapshot };
|
|
1119
1110
|
}
|
|
1120
|
-
const staleCapacity =
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
kind: '
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
staleCapacity.
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1111
|
+
const staleCapacity =
|
|
1112
|
+
staleCapacityRead.kind === 'missing'
|
|
1113
|
+
? emptyCapacityRecord()
|
|
1114
|
+
: decodeJson<CapacityRecord>(staleCapacityRead.value.bytes);
|
|
1115
|
+
await this.provider.write(
|
|
1116
|
+
{
|
|
1117
|
+
conditions: [
|
|
1118
|
+
{ kind: 'version', key: rowKey, expected: current.value.version },
|
|
1119
|
+
versionCondition(staleOwnerKey, staleOwnerRead),
|
|
1120
|
+
conditionFor(staleCapacityKey, staleCapacityRead)
|
|
1121
|
+
],
|
|
1122
|
+
mutations: [
|
|
1123
|
+
{ kind: 'delete', key: rowKey },
|
|
1124
|
+
{
|
|
1125
|
+
kind: 'put',
|
|
1126
|
+
key: staleCapacityKey,
|
|
1127
|
+
bytes: encodeJson({
|
|
1128
|
+
active: staleCapacity.active,
|
|
1129
|
+
pending: subtractCapacity(
|
|
1130
|
+
staleCapacity.pending,
|
|
1131
|
+
record.snapshot.allocation.capacity
|
|
1132
|
+
)
|
|
1133
|
+
} satisfies CapacityRecord)
|
|
1134
|
+
}
|
|
1135
|
+
]
|
|
1136
|
+
},
|
|
1137
|
+
signal
|
|
1138
|
+
);
|
|
1144
1139
|
continue;
|
|
1145
1140
|
}
|
|
1146
|
-
const descriptor = liveTargetDescriptor(
|
|
1147
|
-
descriptorRead,
|
|
1148
|
-
leaseRead,
|
|
1149
|
-
request.target
|
|
1150
|
-
);
|
|
1141
|
+
const descriptor = liveTargetDescriptor(descriptorRead, leaseRead, request.target);
|
|
1151
1142
|
if (descriptor === undefined) {
|
|
1152
1143
|
return { kind: 'conflict', current: { kind: 'missing', storeNow: current.storeNow } };
|
|
1153
1144
|
}
|
|
1154
|
-
const capacity =
|
|
1155
|
-
|
|
1156
|
-
|
|
1145
|
+
const capacity =
|
|
1146
|
+
capacityRead.kind === 'missing'
|
|
1147
|
+
? emptyCapacityRecord()
|
|
1148
|
+
: decodeJson<CapacityRecord>(capacityRead.value.bytes);
|
|
1157
1149
|
if (!capacityAvailable(descriptor, request.capacity, capacity)) {
|
|
1158
1150
|
return { kind: 'placementCapacityExhausted' };
|
|
1159
1151
|
}
|
|
@@ -1193,40 +1185,42 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1193
1185
|
}
|
|
1194
1186
|
}
|
|
1195
1187
|
};
|
|
1196
|
-
const result = await this.provider.write(
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1188
|
+
const result = await this.provider.write(
|
|
1189
|
+
{
|
|
1190
|
+
conditions: [
|
|
1191
|
+
{ kind: 'missing', key: rowKey },
|
|
1192
|
+
versionCondition(descriptorKey, descriptorRead),
|
|
1193
|
+
versionCondition(leaseKey, leaseRead),
|
|
1194
|
+
conditionFor(capacityRowKey, capacityRead),
|
|
1195
|
+
conditionFor(OBJECT_COUNTER_KEY, objectGenerationRead),
|
|
1196
|
+
conditionFor(AUTHORITY_OWNER_COUNTER_KEY, authorityOwnerGenerationRead)
|
|
1197
|
+
],
|
|
1198
|
+
mutations: [
|
|
1199
|
+
{ kind: 'put', key: rowKey, bytes: encodeAuthorityRecord(record) },
|
|
1200
|
+
{
|
|
1201
|
+
kind: 'put',
|
|
1202
|
+
key: capacityRowKey,
|
|
1203
|
+
bytes: encodeJson({
|
|
1204
|
+
active: capacity.active,
|
|
1205
|
+
pending: addCapacity(capacity.pending, request.capacity)
|
|
1206
|
+
} satisfies CapacityRecord)
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
kind: 'put',
|
|
1210
|
+
key: OBJECT_COUNTER_KEY,
|
|
1211
|
+
bytes: encodeText((generation + 1n).toString())
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
kind: 'put',
|
|
1215
|
+
key: AUTHORITY_OWNER_COUNTER_KEY,
|
|
1216
|
+
bytes: encodeText((authorityOwnerGeneration + 1n).toString())
|
|
1217
|
+
}
|
|
1218
|
+
]
|
|
1219
|
+
},
|
|
1220
|
+
signal
|
|
1221
|
+
);
|
|
1227
1222
|
if (result.kind === 'conflict') continue;
|
|
1228
|
-
const version = result.putVersions.find(entry =>
|
|
1229
|
-
entry.key.value === rowKey.value)?.version;
|
|
1223
|
+
const version = result.putVersions.find((entry) => entry.key.value === rowKey.value)?.version;
|
|
1230
1224
|
if (version === undefined) throw new Error('Authority reserve did not return a row version.');
|
|
1231
1225
|
return {
|
|
1232
1226
|
kind: 'reserved',
|
|
@@ -1248,9 +1242,9 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1248
1242
|
if (current.kind === 'missing') return { kind: 'stale' };
|
|
1249
1243
|
const record = decodeAuthorityRecord(current.value.bytes);
|
|
1250
1244
|
if (
|
|
1251
|
-
record.terminal === 'committed'
|
|
1252
|
-
|
|
1253
|
-
|
|
1245
|
+
record.terminal === 'committed' &&
|
|
1246
|
+
record.snapshot.pendingCreation === undefined &&
|
|
1247
|
+
record.snapshot.allocation.state === 'active'
|
|
1254
1248
|
) {
|
|
1255
1249
|
return {
|
|
1256
1250
|
kind: 'alreadyCommitted',
|
|
@@ -1258,10 +1252,10 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1258
1252
|
};
|
|
1259
1253
|
}
|
|
1260
1254
|
if (
|
|
1261
|
-
record.snapshot.pendingCreation?.reservationId !== request.reservationId
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1255
|
+
record.snapshot.pendingCreation?.reservationId !== request.reservationId ||
|
|
1256
|
+
current.value.version.value !== request.expectedStoreVersion ||
|
|
1257
|
+
record.snapshot.allocation.state !== 'reserved' ||
|
|
1258
|
+
!sameCreationTarget(record.snapshot, request.target)
|
|
1265
1259
|
) {
|
|
1266
1260
|
return { kind: 'stale' };
|
|
1267
1261
|
}
|
|
@@ -1276,9 +1270,10 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1276
1270
|
if (liveTargetDescriptor(descriptorRead, leaseRead, request.target) === undefined) {
|
|
1277
1271
|
return { kind: 'stale' };
|
|
1278
1272
|
}
|
|
1279
|
-
const capacity =
|
|
1280
|
-
|
|
1281
|
-
|
|
1273
|
+
const capacity =
|
|
1274
|
+
capacityRead.kind === 'missing'
|
|
1275
|
+
? emptyCapacityRecord()
|
|
1276
|
+
: decodeJson<CapacityRecord>(capacityRead.value.bytes);
|
|
1282
1277
|
const ready: AuthorityRecord = {
|
|
1283
1278
|
terminal: 'committed',
|
|
1284
1279
|
snapshot: {
|
|
@@ -1288,28 +1283,30 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1288
1283
|
pendingCreation: undefined
|
|
1289
1284
|
}
|
|
1290
1285
|
};
|
|
1291
|
-
const result = await this.provider.write(
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1286
|
+
const result = await this.provider.write(
|
|
1287
|
+
{
|
|
1288
|
+
conditions: [
|
|
1289
|
+
{ kind: 'version', key: rowKey, expected: current.value.version },
|
|
1290
|
+
versionCondition(descriptorKey, descriptorRead),
|
|
1291
|
+
versionCondition(leaseKey, leaseRead),
|
|
1292
|
+
conditionFor(capacityRowKey, capacityRead)
|
|
1293
|
+
],
|
|
1294
|
+
mutations: [
|
|
1295
|
+
{ kind: 'put', key: rowKey, bytes: encodeAuthorityRecord(ready) },
|
|
1296
|
+
{
|
|
1297
|
+
kind: 'put',
|
|
1298
|
+
key: capacityRowKey,
|
|
1299
|
+
bytes: encodeJson({
|
|
1300
|
+
active: addCapacity(capacity.active, record.snapshot.allocation.capacity),
|
|
1301
|
+
pending: subtractCapacity(capacity.pending, record.snapshot.allocation.capacity)
|
|
1302
|
+
} satisfies CapacityRecord)
|
|
1303
|
+
}
|
|
1304
|
+
]
|
|
1305
|
+
},
|
|
1306
|
+
signal
|
|
1307
|
+
);
|
|
1310
1308
|
if (result.kind === 'conflict') continue;
|
|
1311
|
-
const version = result.putVersions.find(entry =>
|
|
1312
|
-
entry.key.value === rowKey.value)?.version;
|
|
1309
|
+
const version = result.putVersions.find((entry) => entry.key.value === rowKey.value)?.version;
|
|
1313
1310
|
if (version === undefined) throw new Error('Authority commit did not return a row version.');
|
|
1314
1311
|
return {
|
|
1315
1312
|
kind: 'committed',
|
|
@@ -1330,18 +1327,15 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1330
1327
|
if (current.kind === 'missing') return { kind: 'stale' };
|
|
1331
1328
|
const record = decodeAuthorityRecord(current.value.bytes);
|
|
1332
1329
|
if (
|
|
1333
|
-
record.snapshot.pendingCreation?.reservationId !== request.reservationId
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1330
|
+
record.snapshot.pendingCreation?.reservationId !== request.reservationId ||
|
|
1331
|
+
current.value.version.value !== request.expectedStoreVersion ||
|
|
1332
|
+
record.snapshot.allocation.state !== 'reserved' ||
|
|
1333
|
+
!sameCreationTarget(record.snapshot, request.target)
|
|
1337
1334
|
) {
|
|
1338
1335
|
return { kind: 'stale' };
|
|
1339
1336
|
}
|
|
1340
1337
|
const leaseKey = ownerKey(request.target.owner.ownerId);
|
|
1341
|
-
const capacityRowKey = capacityKey(
|
|
1342
|
-
request.target.meshName,
|
|
1343
|
-
String(request.target.nodeRid)
|
|
1344
|
-
);
|
|
1338
|
+
const capacityRowKey = capacityKey(request.target.meshName, String(request.target.nodeRid));
|
|
1345
1339
|
const [leaseRead, capacityRead] = await Promise.all([
|
|
1346
1340
|
this.provider.read(leaseKey, signal),
|
|
1347
1341
|
this.provider.read(capacityRowKey, signal)
|
|
@@ -1353,30 +1347,31 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1353
1347
|
throw new Error('Location Store owner lease record is invalid.');
|
|
1354
1348
|
}
|
|
1355
1349
|
if (!sameLiveOwner(leaseRead, record.snapshot)) return { kind: 'stale' };
|
|
1356
|
-
const capacity =
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
kind: '
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
capacity.
|
|
1374
|
-
record.snapshot.allocation.capacity
|
|
1375
|
-
)
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1350
|
+
const capacity =
|
|
1351
|
+
capacityRead.kind === 'missing'
|
|
1352
|
+
? emptyCapacityRecord()
|
|
1353
|
+
: decodeJson<CapacityRecord>(capacityRead.value.bytes);
|
|
1354
|
+
const result = await this.provider.write(
|
|
1355
|
+
{
|
|
1356
|
+
conditions: [
|
|
1357
|
+
{ kind: 'version', key: rowKey, expected: current.value.version },
|
|
1358
|
+
versionCondition(leaseKey, leaseRead),
|
|
1359
|
+
conditionFor(capacityRowKey, capacityRead)
|
|
1360
|
+
],
|
|
1361
|
+
mutations: [
|
|
1362
|
+
{ kind: 'delete', key: rowKey },
|
|
1363
|
+
{
|
|
1364
|
+
kind: 'put',
|
|
1365
|
+
key: capacityRowKey,
|
|
1366
|
+
bytes: encodeJson({
|
|
1367
|
+
active: capacity.active,
|
|
1368
|
+
pending: subtractCapacity(capacity.pending, record.snapshot.allocation.capacity)
|
|
1369
|
+
} satisfies CapacityRecord)
|
|
1370
|
+
}
|
|
1371
|
+
]
|
|
1372
|
+
},
|
|
1373
|
+
signal
|
|
1374
|
+
);
|
|
1380
1375
|
if (result.kind === 'conflict') continue;
|
|
1381
1376
|
return { kind: 'aborted' };
|
|
1382
1377
|
}
|
|
@@ -1389,13 +1384,8 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1389
1384
|
if (request.key.kind !== 'actor') {
|
|
1390
1385
|
throw new TypeError('completeCreation is reserved for Actor creation.');
|
|
1391
1386
|
}
|
|
1392
|
-
const terminal = createTerminalRecord(
|
|
1393
|
-
|
|
1394
|
-
this.nowProvider()
|
|
1395
|
-
);
|
|
1396
|
-
const rowKey = authorityKey(
|
|
1397
|
-
encodeAuthorityKey(request.key.kind, request.key.globalId).value
|
|
1398
|
-
);
|
|
1387
|
+
const terminal = createTerminalRecord(request, this.nowProvider());
|
|
1388
|
+
const rowKey = authorityKey(encodeAuthorityKey(request.key.kind, request.key.globalId).value);
|
|
1399
1389
|
const terminalRowKey = creationTerminalKey(terminal.operation);
|
|
1400
1390
|
for (;;) {
|
|
1401
1391
|
signal?.throwIfAborted();
|
|
@@ -1417,10 +1407,10 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1417
1407
|
if (current.kind === 'missing') return { kind: 'stale' };
|
|
1418
1408
|
const record = decodeAuthorityRecord(current.value.bytes);
|
|
1419
1409
|
if (
|
|
1420
|
-
record.snapshot.pendingCreation?.reservationId !== request.reservationId
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1410
|
+
record.snapshot.pendingCreation?.reservationId !== request.reservationId ||
|
|
1411
|
+
current.value.version.value !== request.expectedStoreVersion ||
|
|
1412
|
+
record.snapshot.allocation.state !== 'reserved' ||
|
|
1413
|
+
!sameCreationTarget(record.snapshot, request.target)
|
|
1424
1414
|
) {
|
|
1425
1415
|
return { kind: 'stale' };
|
|
1426
1416
|
}
|
|
@@ -1435,73 +1425,77 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1435
1425
|
if (liveTargetDescriptor(descriptorRead, leaseRead, request.target) === undefined) {
|
|
1436
1426
|
return { kind: 'stale' };
|
|
1437
1427
|
}
|
|
1438
|
-
const capacity =
|
|
1439
|
-
|
|
1440
|
-
|
|
1428
|
+
const capacity =
|
|
1429
|
+
capacityRead.kind === 'missing'
|
|
1430
|
+
? emptyCapacityRecord()
|
|
1431
|
+
: decodeJson<CapacityRecord>(capacityRead.value.bytes);
|
|
1441
1432
|
const terminalAtStore = {
|
|
1442
1433
|
...terminal,
|
|
1443
1434
|
storeNow: current.value.storeNow
|
|
1444
1435
|
};
|
|
1445
|
-
const retentionMs = terminalAtStore.expiresAt.getTime()
|
|
1446
|
-
- current.value.storeNow.getTime();
|
|
1436
|
+
const retentionMs = terminalAtStore.expiresAt.getTime() - current.value.storeNow.getTime();
|
|
1447
1437
|
if (retentionMs <= 0) {
|
|
1448
1438
|
throw new RangeError(
|
|
1449
1439
|
'Creation terminal expiry must be the live operation deadline plus five minutes.'
|
|
1450
1440
|
);
|
|
1451
1441
|
}
|
|
1452
|
-
const mutations =
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1442
|
+
const mutations =
|
|
1443
|
+
request.completion.kind === 'created'
|
|
1444
|
+
? [
|
|
1445
|
+
{
|
|
1446
|
+
kind: 'put' as const,
|
|
1447
|
+
key: rowKey,
|
|
1448
|
+
bytes: encodeAuthorityRecord({
|
|
1449
|
+
terminal: 'committed',
|
|
1450
|
+
snapshot: {
|
|
1451
|
+
...record.snapshot,
|
|
1452
|
+
payload: Buffer.from(request.completion.readyPayload),
|
|
1453
|
+
allocation: { ...record.snapshot.allocation, state: 'active' },
|
|
1454
|
+
pendingCreation: undefined
|
|
1455
|
+
}
|
|
1456
|
+
} satisfies AuthorityRecord)
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
kind: 'put' as const,
|
|
1460
|
+
key: capacityRowKey,
|
|
1461
|
+
bytes: encodeJson({
|
|
1462
|
+
active: addCapacity(capacity.active, record.snapshot.allocation.capacity),
|
|
1463
|
+
pending: subtractCapacity(capacity.pending, record.snapshot.allocation.capacity)
|
|
1464
|
+
} satisfies CapacityRecord)
|
|
1465
|
+
}
|
|
1466
|
+
]
|
|
1467
|
+
: [
|
|
1468
|
+
{ kind: 'delete' as const, key: rowKey },
|
|
1469
|
+
{
|
|
1470
|
+
kind: 'put' as const,
|
|
1471
|
+
key: capacityRowKey,
|
|
1472
|
+
bytes: encodeJson({
|
|
1473
|
+
active: capacity.active,
|
|
1474
|
+
pending: subtractCapacity(capacity.pending, record.snapshot.allocation.capacity)
|
|
1475
|
+
} satisfies CapacityRecord)
|
|
1476
|
+
}
|
|
1477
|
+
];
|
|
1478
|
+
const result = await this.provider.write(
|
|
1479
|
+
{
|
|
1480
|
+
conditions: [
|
|
1481
|
+
{ kind: 'version', key: rowKey, expected: current.value.version },
|
|
1482
|
+
{ kind: 'missing', key: terminalRowKey },
|
|
1483
|
+
versionCondition(descriptorKey, descriptorRead),
|
|
1484
|
+
versionCondition(leaseKey, leaseRead),
|
|
1485
|
+
conditionFor(capacityRowKey, capacityRead)
|
|
1486
|
+
],
|
|
1487
|
+
mutations: [
|
|
1488
|
+
...mutations,
|
|
1467
1489
|
{
|
|
1468
|
-
kind: 'put'
|
|
1469
|
-
key:
|
|
1470
|
-
bytes:
|
|
1471
|
-
|
|
1472
|
-
pending: subtractCapacity(capacity.pending, record.snapshot.allocation.capacity)
|
|
1473
|
-
} satisfies CapacityRecord)
|
|
1490
|
+
kind: 'put',
|
|
1491
|
+
key: terminalRowKey,
|
|
1492
|
+
bytes: terminalAtStore.terminalEnvelope,
|
|
1493
|
+
retentionMs
|
|
1474
1494
|
}
|
|
1475
1495
|
]
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
kind: 'put' as const,
|
|
1480
|
-
key: capacityRowKey,
|
|
1481
|
-
bytes: encodeJson({
|
|
1482
|
-
active: capacity.active,
|
|
1483
|
-
pending: subtractCapacity(capacity.pending, record.snapshot.allocation.capacity)
|
|
1484
|
-
} satisfies CapacityRecord)
|
|
1485
|
-
}
|
|
1486
|
-
];
|
|
1487
|
-
const result = await this.provider.write({
|
|
1488
|
-
conditions: [
|
|
1489
|
-
{ kind: 'version', key: rowKey, expected: current.value.version },
|
|
1490
|
-
{ kind: 'missing', key: terminalRowKey },
|
|
1491
|
-
versionCondition(descriptorKey, descriptorRead),
|
|
1492
|
-
versionCondition(leaseKey, leaseRead),
|
|
1493
|
-
conditionFor(capacityRowKey, capacityRead)
|
|
1494
|
-
],
|
|
1495
|
-
mutations: [
|
|
1496
|
-
...mutations,
|
|
1497
|
-
{
|
|
1498
|
-
kind: 'put',
|
|
1499
|
-
key: terminalRowKey,
|
|
1500
|
-
bytes: terminalAtStore.terminalEnvelope,
|
|
1501
|
-
retentionMs
|
|
1502
|
-
}
|
|
1503
|
-
]
|
|
1504
|
-
}, signal);
|
|
1496
|
+
},
|
|
1497
|
+
signal
|
|
1498
|
+
);
|
|
1505
1499
|
if (result.kind === 'conflict') continue;
|
|
1506
1500
|
const publishedTerminal = {
|
|
1507
1501
|
...terminalAtStore,
|
|
@@ -1510,8 +1504,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1510
1504
|
if (request.completion.kind !== 'created') {
|
|
1511
1505
|
return { kind: request.completion.kind, terminal: publishedTerminal };
|
|
1512
1506
|
}
|
|
1513
|
-
const version = result.putVersions.find(entry =>
|
|
1514
|
-
entry.key.value === rowKey.value)?.version;
|
|
1507
|
+
const version = result.putVersions.find((entry) => entry.key.value === rowKey.value)?.version;
|
|
1515
1508
|
if (version === undefined) {
|
|
1516
1509
|
throw new Error('Actor creation completion did not return an authority row version.');
|
|
1517
1510
|
}
|
|
@@ -1571,22 +1564,25 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1571
1564
|
? { kind: 'missing', key: OWNER_COUNTER_KEY }
|
|
1572
1565
|
: { kind: 'version', key: OWNER_COUNTER_KEY, expected: counter.value.version }
|
|
1573
1566
|
];
|
|
1574
|
-
const result = await this.provider.write(
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1567
|
+
const result = await this.provider.write(
|
|
1568
|
+
{
|
|
1569
|
+
conditions,
|
|
1570
|
+
mutations: [
|
|
1571
|
+
{
|
|
1572
|
+
kind: 'put',
|
|
1573
|
+
key: ownerKey(ownerId),
|
|
1574
|
+
bytes: encodeOwnerRecord(ownerId, generation),
|
|
1575
|
+
retentionMs: leaseTtlMs
|
|
1576
|
+
},
|
|
1577
|
+
{
|
|
1578
|
+
kind: 'put',
|
|
1579
|
+
key: OWNER_COUNTER_KEY,
|
|
1580
|
+
bytes: encodeText((generation + 1n).toString())
|
|
1581
|
+
}
|
|
1582
|
+
]
|
|
1583
|
+
},
|
|
1584
|
+
signal
|
|
1585
|
+
);
|
|
1590
1586
|
if (result.kind === 'conflict') continue;
|
|
1591
1587
|
return {
|
|
1592
1588
|
kind: 'claimed',
|
|
@@ -1626,15 +1622,20 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1626
1622
|
if (current.kind === 'missing') return { kind: 'stale' };
|
|
1627
1623
|
const record = decodeOwnerRecord(current.value.bytes);
|
|
1628
1624
|
if (BigInt(record.leaseGeneration) !== token.leaseGeneration) return { kind: 'stale' };
|
|
1629
|
-
const result = await this.provider.write(
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1625
|
+
const result = await this.provider.write(
|
|
1626
|
+
{
|
|
1627
|
+
conditions: [{ kind: 'version', key, expected: current.value.version }],
|
|
1628
|
+
mutations: [
|
|
1629
|
+
{
|
|
1630
|
+
kind: 'put',
|
|
1631
|
+
key,
|
|
1632
|
+
bytes: current.value.bytes,
|
|
1633
|
+
retentionMs: leaseTtlMs
|
|
1634
|
+
}
|
|
1635
|
+
]
|
|
1636
|
+
},
|
|
1637
|
+
signal
|
|
1638
|
+
);
|
|
1638
1639
|
if (result.kind === 'conflict') return { kind: 'stale' };
|
|
1639
1640
|
return {
|
|
1640
1641
|
kind: 'renewed',
|
|
@@ -1652,10 +1653,13 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1652
1653
|
if (current.kind === 'missing') return 'stale';
|
|
1653
1654
|
const record = decodeOwnerRecord(current.value.bytes);
|
|
1654
1655
|
if (BigInt(record.leaseGeneration) !== token.leaseGeneration) return 'stale';
|
|
1655
|
-
const result = await this.provider.write(
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1656
|
+
const result = await this.provider.write(
|
|
1657
|
+
{
|
|
1658
|
+
conditions: [{ kind: 'version', key, expected: current.value.version }],
|
|
1659
|
+
mutations: [{ kind: 'delete', key }]
|
|
1660
|
+
},
|
|
1661
|
+
signal
|
|
1662
|
+
);
|
|
1659
1663
|
return result.kind === 'applied' ? 'released' : 'stale';
|
|
1660
1664
|
}
|
|
1661
1665
|
|
|
@@ -1683,15 +1687,22 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1683
1687
|
const rowKey = meshKey(key.meshName, key.rid);
|
|
1684
1688
|
const current = await this.provider.read(rowKey, signal);
|
|
1685
1689
|
if (current.kind === 'missing') return WriteStatus.IgnoredStale;
|
|
1686
|
-
const descriptor = reviveMeshDescriptor(
|
|
1687
|
-
|
|
1688
|
-
|
|
1690
|
+
const descriptor = reviveMeshDescriptor(
|
|
1691
|
+
decodeCanonicalDescriptorRecord<ZLinkMeshNodeDescriptor>(current.value.bytes).descriptor
|
|
1692
|
+
);
|
|
1693
|
+
if (
|
|
1694
|
+
descriptor.ownerId !== owner.ownerId ||
|
|
1695
|
+
descriptor.leaseGeneration !== owner.leaseGeneration
|
|
1696
|
+
) {
|
|
1689
1697
|
return WriteStatus.IgnoredStale;
|
|
1690
1698
|
}
|
|
1691
|
-
const result = await this.provider.write(
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1699
|
+
const result = await this.provider.write(
|
|
1700
|
+
{
|
|
1701
|
+
conditions: [{ kind: 'version', key: rowKey, expected: current.value.version }],
|
|
1702
|
+
mutations: [{ kind: 'delete', key: rowKey }]
|
|
1703
|
+
},
|
|
1704
|
+
signal
|
|
1705
|
+
);
|
|
1695
1706
|
return result.kind === 'applied' ? WriteStatus.Stored : WriteStatus.IgnoredStale;
|
|
1696
1707
|
}
|
|
1697
1708
|
|
|
@@ -1700,55 +1711,62 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1700
1711
|
page: ZLinkPageRequest = {},
|
|
1701
1712
|
signal?: AbortSignal
|
|
1702
1713
|
): Promise<ZLinkLocationPage<ZLinkMeshNodeDescriptor>> {
|
|
1703
|
-
const result = await this.provider.scan(
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1714
|
+
const result = await this.provider.scan(
|
|
1715
|
+
{
|
|
1716
|
+
prefix: meshPrefix(meshName),
|
|
1717
|
+
cursor:
|
|
1718
|
+
page.continuationToken === undefined
|
|
1719
|
+
? undefined
|
|
1720
|
+
: ({ value: page.continuationToken } as ZLinkStoreScanCursor),
|
|
1721
|
+
limit: page.pageSize ?? 100
|
|
1722
|
+
},
|
|
1723
|
+
signal
|
|
1724
|
+
);
|
|
1710
1725
|
if (result.kind === 'expired') {
|
|
1711
1726
|
throw new Error('Location Store scan snapshot expired.');
|
|
1712
1727
|
}
|
|
1713
|
-
const items = await Promise.all(
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1728
|
+
const items = await Promise.all(
|
|
1729
|
+
result.value.items.map(async (item) => {
|
|
1730
|
+
const descriptor = reviveMeshDescriptor(
|
|
1731
|
+
decodeCanonicalDescriptorRecord<ZLinkMeshNodeDescriptor>(item.value.bytes).descriptor
|
|
1732
|
+
);
|
|
1733
|
+
const capacityRead = await this.provider.read(
|
|
1734
|
+
capacityKey(descriptor.meshName, String(descriptor.rid)),
|
|
1735
|
+
signal
|
|
1736
|
+
);
|
|
1737
|
+
const capacity =
|
|
1738
|
+
capacityRead.kind === 'missing'
|
|
1739
|
+
? emptyCapacityRecord()
|
|
1740
|
+
: decodeJson<CapacityRecord>(capacityRead.value.bytes);
|
|
1741
|
+
return {
|
|
1742
|
+
...descriptor,
|
|
1743
|
+
populationCapacity: {
|
|
1744
|
+
actors: {
|
|
1745
|
+
...descriptor.populationCapacity.actors,
|
|
1746
|
+
active: capacity.active.actors,
|
|
1747
|
+
reserved: capacity.pending.actors
|
|
1748
|
+
},
|
|
1749
|
+
spots: {
|
|
1750
|
+
...descriptor.populationCapacity.spots,
|
|
1751
|
+
active: capacity.active.spots,
|
|
1752
|
+
reserved: capacity.pending.spots
|
|
1753
|
+
},
|
|
1754
|
+
spotTypes: descriptor.populationCapacity.spotTypes.map((value) => {
|
|
1755
|
+
const key = capacityTypeKey({
|
|
1756
|
+
objectKind: value.objectKind,
|
|
1757
|
+
stableType: value.stableType,
|
|
1758
|
+
count: 0
|
|
1759
|
+
});
|
|
1760
|
+
return {
|
|
1761
|
+
...value,
|
|
1762
|
+
active: capacity.active.spotTypes[key] ?? 0,
|
|
1763
|
+
reserved: capacity.pending.spotTypes[key] ?? 0
|
|
1764
|
+
};
|
|
1765
|
+
})
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
})
|
|
1769
|
+
);
|
|
1752
1770
|
return {
|
|
1753
1771
|
items,
|
|
1754
1772
|
continuationToken: result.value.nextCursor?.value
|
|
@@ -1835,12 +1853,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1835
1853
|
page: ZLinkPageRequest = {},
|
|
1836
1854
|
signal?: AbortSignal
|
|
1837
1855
|
): Promise<ZLinkLocationPage<ZLinkFanoutPublisherDescriptor>> {
|
|
1838
|
-
return this.listDescriptors(
|
|
1839
|
-
fanoutPrefix(channelName),
|
|
1840
|
-
page,
|
|
1841
|
-
reviveFanoutDescriptor,
|
|
1842
|
-
signal
|
|
1843
|
-
);
|
|
1856
|
+
return this.listDescriptors(fanoutPrefix(channelName), page, reviveFanoutDescriptor, signal);
|
|
1844
1857
|
}
|
|
1845
1858
|
|
|
1846
1859
|
override async updateSpot(
|
|
@@ -1861,11 +1874,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1861
1874
|
owner: ZLinkLocationOwnerToken,
|
|
1862
1875
|
signal?: AbortSignal
|
|
1863
1876
|
): Promise<ZLinkLocationWriteStatus> {
|
|
1864
|
-
return this.removeOwnedLocation(
|
|
1865
|
-
spotKey(key.meshName, String(key.spotId)),
|
|
1866
|
-
owner,
|
|
1867
|
-
signal
|
|
1868
|
-
);
|
|
1877
|
+
return this.removeOwnedLocation(spotKey(key.meshName, String(key.spotId)), owner, signal);
|
|
1869
1878
|
}
|
|
1870
1879
|
|
|
1871
1880
|
override async resolveSpot(
|
|
@@ -1888,7 +1897,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1888
1897
|
`${PREFIX}spot:`,
|
|
1889
1898
|
page,
|
|
1890
1899
|
normalizeSpot,
|
|
1891
|
-
value => matchesSpot(value, filter),
|
|
1900
|
+
(value) => matchesSpot(value, filter),
|
|
1892
1901
|
signal
|
|
1893
1902
|
);
|
|
1894
1903
|
}
|
|
@@ -1934,7 +1943,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1934
1943
|
`${PREFIX}actor:`,
|
|
1935
1944
|
page,
|
|
1936
1945
|
normalizeActor,
|
|
1937
|
-
value => matchesActor(value, filter),
|
|
1946
|
+
(value) => matchesActor(value, filter),
|
|
1938
1947
|
signal
|
|
1939
1948
|
);
|
|
1940
1949
|
}
|
|
@@ -1962,9 +1971,11 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1962
1971
|
updatedAt: current.value.storeNow
|
|
1963
1972
|
};
|
|
1964
1973
|
}
|
|
1965
|
-
} else if (
|
|
1966
|
-
|
|
1967
|
-
|
|
1974
|
+
} else if (
|
|
1975
|
+
intent === 2 &&
|
|
1976
|
+
(stored.ownerId !== normalized.ownerId ||
|
|
1977
|
+
(normalized.generation !== 0n && stored.generation !== normalized.generation))
|
|
1978
|
+
) {
|
|
1968
1979
|
return {
|
|
1969
1980
|
status: WriteStatus.IgnoredStale,
|
|
1970
1981
|
generation,
|
|
@@ -1977,17 +1988,22 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
1977
1988
|
return rejected(new Date());
|
|
1978
1989
|
}
|
|
1979
1990
|
const stored = { ...normalized, generation };
|
|
1980
|
-
const result = await this.provider.write(
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
+
const result = await this.provider.write(
|
|
1992
|
+
{
|
|
1993
|
+
conditions: [condition],
|
|
1994
|
+
mutations: [
|
|
1995
|
+
{
|
|
1996
|
+
kind: 'put',
|
|
1997
|
+
key: rowKey,
|
|
1998
|
+
bytes: encodeJson<DescriptorRecord<ZLinkRouteLocation>>({
|
|
1999
|
+
generation: generation.toString(),
|
|
2000
|
+
descriptor: stored
|
|
2001
|
+
})
|
|
2002
|
+
}
|
|
2003
|
+
]
|
|
2004
|
+
},
|
|
2005
|
+
signal
|
|
2006
|
+
);
|
|
1991
2007
|
return result.kind === 'applied'
|
|
1992
2008
|
? { status: WriteStatus.Stored, generation, updatedAt: result.storeNow }
|
|
1993
2009
|
: rejected(result.storeNow);
|
|
@@ -2002,20 +2018,29 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2002
2018
|
const current = await this.provider.read(rowKey, signal);
|
|
2003
2019
|
if (current.kind === 'missing') return rejected(current.storeNow);
|
|
2004
2020
|
const record = decodeJson<DescriptorRecord<ZLinkRouteLocation>>(current.value.bytes);
|
|
2005
|
-
if (
|
|
2006
|
-
|
|
2021
|
+
if (
|
|
2022
|
+
record.descriptor.ownerId !== owner.ownerId ||
|
|
2023
|
+
record.descriptor.generation !== owner.leaseGeneration
|
|
2024
|
+
) {
|
|
2007
2025
|
return {
|
|
2008
2026
|
status: WriteStatus.IgnoredStale,
|
|
2009
2027
|
generation: BigInt(record.generation),
|
|
2010
2028
|
updatedAt: current.value.storeNow
|
|
2011
2029
|
};
|
|
2012
2030
|
}
|
|
2013
|
-
const result = await this.provider.write(
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2031
|
+
const result = await this.provider.write(
|
|
2032
|
+
{
|
|
2033
|
+
conditions: [{ kind: 'version', key: rowKey, expected: current.value.version }],
|
|
2034
|
+
mutations: [{ kind: 'delete', key: rowKey }]
|
|
2035
|
+
},
|
|
2036
|
+
signal
|
|
2037
|
+
);
|
|
2017
2038
|
return result.kind === 'applied'
|
|
2018
|
-
? {
|
|
2039
|
+
? {
|
|
2040
|
+
status: WriteStatus.Stored,
|
|
2041
|
+
generation: BigInt(record.generation),
|
|
2042
|
+
updatedAt: result.storeNow
|
|
2043
|
+
}
|
|
2019
2044
|
: rejected(result.storeNow);
|
|
2020
2045
|
}
|
|
2021
2046
|
|
|
@@ -2039,7 +2064,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2039
2064
|
`${PREFIX}route:`,
|
|
2040
2065
|
page,
|
|
2041
2066
|
normalizeRoute,
|
|
2042
|
-
value => matchesRoute(value, filter),
|
|
2067
|
+
(value) => matchesRoute(value, filter),
|
|
2043
2068
|
signal
|
|
2044
2069
|
);
|
|
2045
2070
|
}
|
|
@@ -2094,7 +2119,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2094
2119
|
signal
|
|
2095
2120
|
);
|
|
2096
2121
|
}
|
|
2097
|
-
return removed + await super.removeAllByOwner(owner);
|
|
2122
|
+
return removed + (await super.removeAllByOwner(owner));
|
|
2098
2123
|
}
|
|
2099
2124
|
|
|
2100
2125
|
private async removeOwnedAuthorities(
|
|
@@ -2106,12 +2131,15 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2106
2131
|
const pageCandidates: OwnerCleanupCandidate[] = [];
|
|
2107
2132
|
let cursor: ZLinkStoreScanCursor | undefined;
|
|
2108
2133
|
let expired = false;
|
|
2109
|
-
do {
|
|
2110
|
-
const result = await this.provider.scan(
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2134
|
+
do {
|
|
2135
|
+
const result = await this.provider.scan(
|
|
2136
|
+
{
|
|
2137
|
+
prefix: AUTHORITY_PREIMAGE_PREFIX,
|
|
2138
|
+
cursor,
|
|
2139
|
+
limit: 1_000
|
|
2140
|
+
},
|
|
2141
|
+
signal
|
|
2142
|
+
);
|
|
2115
2143
|
if (result.kind === 'expired') {
|
|
2116
2144
|
expired = true;
|
|
2117
2145
|
break;
|
|
@@ -2133,14 +2161,15 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2133
2161
|
for (const candidate of candidates) {
|
|
2134
2162
|
signal?.throwIfAborted();
|
|
2135
2163
|
const current = await this.provider.read(candidate.key, signal);
|
|
2136
|
-
if (current.kind === 'missing'
|
|
2137
|
-
|
|
2164
|
+
if (current.kind === 'missing' || current.value.version.value !== candidate.scanVersion.value)
|
|
2165
|
+
continue;
|
|
2138
2166
|
const record = decodeAuthorityRecord(current.value.bytes);
|
|
2139
2167
|
if (
|
|
2140
|
-
record.snapshot.ownerId !== owner.ownerId
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
)
|
|
2168
|
+
record.snapshot.ownerId !== owner.ownerId ||
|
|
2169
|
+
record.snapshot.ownerLeaseGeneration !== owner.leaseGeneration ||
|
|
2170
|
+
record.aggregate !== undefined
|
|
2171
|
+
)
|
|
2172
|
+
continue;
|
|
2144
2173
|
const encodedAuthority = authorityContractValueFromPreimage(candidate.key.value);
|
|
2145
2174
|
const identity = decodeAuthorityKey({ value: encodedAuthority } as ZLinkAuthorityKey);
|
|
2146
2175
|
if (record.snapshot.allocation.state === 'active') {
|
|
@@ -2155,21 +2184,23 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2155
2184
|
}
|
|
2156
2185
|
const reservationId = record.snapshot.pendingCreation?.reservationId;
|
|
2157
2186
|
if (reservationId === undefined) continue;
|
|
2158
|
-
const aborted = await this.abort(
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
record.snapshot.allocation.descriptorLifecycleGeneration,
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2187
|
+
const aborted = await this.abort(
|
|
2188
|
+
{
|
|
2189
|
+
key: identity,
|
|
2190
|
+
reservationId,
|
|
2191
|
+
expectedStoreVersion: current.value.version.value,
|
|
2192
|
+
target: {
|
|
2193
|
+
meshName: record.snapshot.allocation.descriptor.meshName,
|
|
2194
|
+
nodeRid: record.snapshot.allocation.descriptor.rid,
|
|
2195
|
+
nodeLifecycleGeneration: record.snapshot.allocation.descriptorLifecycleGeneration,
|
|
2196
|
+
owner: {
|
|
2197
|
+
ownerId: record.snapshot.ownerId,
|
|
2198
|
+
leaseGeneration: record.snapshot.ownerLeaseGeneration
|
|
2199
|
+
}
|
|
2170
2200
|
}
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2201
|
+
},
|
|
2202
|
+
signal
|
|
2203
|
+
);
|
|
2173
2204
|
if (aborted.kind === 'aborted') removed += 1n;
|
|
2174
2205
|
}
|
|
2175
2206
|
return removed;
|
|
@@ -2183,20 +2214,25 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2183
2214
|
): Promise<bigint> {
|
|
2184
2215
|
if (candidates.length === 0) return 0n;
|
|
2185
2216
|
|
|
2186
|
-
const currentRows = await Promise.all(
|
|
2187
|
-
this.provider.read(candidate.key, signal))
|
|
2217
|
+
const currentRows = await Promise.all(
|
|
2218
|
+
candidates.map((candidate) => this.provider.read(candidate.key, signal))
|
|
2219
|
+
);
|
|
2188
2220
|
const unchangedOwnedRows: OwnerCleanupCandidate[] = [];
|
|
2189
2221
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
2190
2222
|
const candidate = candidates[index]!;
|
|
2191
2223
|
const current = currentRows[index]!;
|
|
2192
|
-
if (
|
|
2193
|
-
|
|
2224
|
+
if (
|
|
2225
|
+
current.kind === 'missing' ||
|
|
2226
|
+
current.value.version.value !== candidate.scanVersion.value
|
|
2227
|
+
) {
|
|
2194
2228
|
continue;
|
|
2195
2229
|
}
|
|
2196
2230
|
const record = decodeJson<OwnerCleanupRecord>(current.value.bytes);
|
|
2197
2231
|
const descriptor = record.descriptor;
|
|
2198
|
-
if (
|
|
2199
|
-
|
|
2232
|
+
if (
|
|
2233
|
+
descriptor.ownerId !== owner.ownerId ||
|
|
2234
|
+
descriptorOwnerLeaseGeneration(descriptor) !== owner.leaseGeneration
|
|
2235
|
+
) {
|
|
2200
2236
|
continue;
|
|
2201
2237
|
}
|
|
2202
2238
|
unchangedOwnedRows.push({
|
|
@@ -2207,24 +2243,29 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2207
2243
|
if (unchangedOwnedRows.length === 0) return 0n;
|
|
2208
2244
|
|
|
2209
2245
|
const currentLease = await this.provider.read(leaseKey, signal);
|
|
2210
|
-
if (
|
|
2211
|
-
|
|
2246
|
+
if (
|
|
2247
|
+
currentLease.kind === 'missing' ||
|
|
2248
|
+
liveOwnerLeaseGeneration(currentLease) !== owner.leaseGeneration
|
|
2249
|
+
) {
|
|
2212
2250
|
return 0n;
|
|
2213
2251
|
}
|
|
2214
|
-
const result = await this.provider.write(
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2252
|
+
const result = await this.provider.write(
|
|
2253
|
+
{
|
|
2254
|
+
conditions: [
|
|
2255
|
+
{ kind: 'version', key: leaseKey, expected: currentLease.value.version },
|
|
2256
|
+
...unchangedOwnedRows.map((row) => ({
|
|
2257
|
+
kind: 'version' as const,
|
|
2258
|
+
key: row.key,
|
|
2259
|
+
expected: row.scanVersion
|
|
2260
|
+
}))
|
|
2261
|
+
],
|
|
2262
|
+
mutations: unchangedOwnedRows.map((row) => ({
|
|
2263
|
+
kind: 'delete' as const,
|
|
2264
|
+
key: row.key
|
|
2221
2265
|
}))
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
key: row.key
|
|
2226
|
-
}))
|
|
2227
|
-
}, signal);
|
|
2266
|
+
},
|
|
2267
|
+
signal
|
|
2268
|
+
);
|
|
2228
2269
|
if (result.kind === 'applied') return BigInt(unchangedOwnedRows.length);
|
|
2229
2270
|
|
|
2230
2271
|
// A row may have changed while the page was being prepared. Split the
|
|
@@ -2263,11 +2304,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2263
2304
|
this.provider.read(rowKey, signal)
|
|
2264
2305
|
]);
|
|
2265
2306
|
if (lease.kind === 'missing') return rejected(lease.storeNow);
|
|
2266
|
-
if (!matchesLiveOwnerLease(
|
|
2267
|
-
lease,
|
|
2268
|
-
descriptor.ownerId,
|
|
2269
|
-
descriptor.leaseGeneration
|
|
2270
|
-
)) {
|
|
2307
|
+
if (!matchesLiveOwnerLease(lease, descriptor.ownerId, descriptor.leaseGeneration)) {
|
|
2271
2308
|
return rejected(lease.value.storeNow);
|
|
2272
2309
|
}
|
|
2273
2310
|
|
|
@@ -2330,16 +2367,20 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2330
2367
|
let currentRow = predecessor;
|
|
2331
2368
|
|
|
2332
2369
|
for (let attempt = 0; attempt <= MAX_DESCRIPTOR_WRITE_RETRIES; attempt += 1) {
|
|
2333
|
-
const rowCondition: ZLinkStoreCondition =
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2370
|
+
const rowCondition: ZLinkStoreCondition =
|
|
2371
|
+
currentRow.kind === 'found'
|
|
2372
|
+
? { kind: 'version', key: rowKey, expected: currentRow.value.version }
|
|
2373
|
+
: { kind: 'missing', key: rowKey };
|
|
2374
|
+
const result = await this.provider.write(
|
|
2375
|
+
{
|
|
2376
|
+
conditions: [
|
|
2377
|
+
{ kind: 'version', key: leaseKey, expected: currentLease.value.version },
|
|
2378
|
+
rowCondition
|
|
2379
|
+
],
|
|
2380
|
+
mutations: [{ kind: 'put', key: rowKey, bytes: encodedDescriptor }]
|
|
2381
|
+
},
|
|
2382
|
+
signal
|
|
2383
|
+
);
|
|
2343
2384
|
if (result.kind === 'applied') {
|
|
2344
2385
|
return { status: WriteStatus.Stored, generation, updatedAt: result.storeNow };
|
|
2345
2386
|
}
|
|
@@ -2349,9 +2390,11 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2349
2390
|
this.provider.read(leaseKey, signal),
|
|
2350
2391
|
this.provider.read(rowKey, signal)
|
|
2351
2392
|
]);
|
|
2352
|
-
if (
|
|
2353
|
-
|
|
2354
|
-
|
|
2393
|
+
if (
|
|
2394
|
+
refreshedLease.kind === 'missing' ||
|
|
2395
|
+
!matchesLiveOwnerLease(refreshedLease, ownerId, leaseGeneration) ||
|
|
2396
|
+
!sameDescriptorPredecessor(predecessor, refreshedRow)
|
|
2397
|
+
) {
|
|
2355
2398
|
return ignoredStale(result.storeNow);
|
|
2356
2399
|
}
|
|
2357
2400
|
|
|
@@ -2369,16 +2412,20 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2369
2412
|
): Promise<ZLinkLocationWriteStatus> {
|
|
2370
2413
|
const current = await this.provider.read(rowKey, signal);
|
|
2371
2414
|
if (current.kind === 'missing') return WriteStatus.IgnoredStale;
|
|
2372
|
-
const descriptor = revive(
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2415
|
+
const descriptor = revive(decodeCanonicalDescriptorRecord<T>(current.value.bytes).descriptor);
|
|
2416
|
+
if (
|
|
2417
|
+
descriptor.ownerId !== owner.ownerId ||
|
|
2418
|
+
descriptor.leaseGeneration !== owner.leaseGeneration
|
|
2419
|
+
) {
|
|
2376
2420
|
return WriteStatus.IgnoredStale;
|
|
2377
2421
|
}
|
|
2378
|
-
const result = await this.provider.write(
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2422
|
+
const result = await this.provider.write(
|
|
2423
|
+
{
|
|
2424
|
+
conditions: [{ kind: 'version', key: rowKey, expected: current.value.version }],
|
|
2425
|
+
mutations: [{ kind: 'delete', key: rowKey }]
|
|
2426
|
+
},
|
|
2427
|
+
signal
|
|
2428
|
+
);
|
|
2382
2429
|
return result.kind === 'applied' ? WriteStatus.Stored : WriteStatus.IgnoredStale;
|
|
2383
2430
|
}
|
|
2384
2431
|
|
|
@@ -2388,19 +2435,24 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2388
2435
|
revive: (descriptor: T) => T,
|
|
2389
2436
|
signal?: AbortSignal
|
|
2390
2437
|
): Promise<ZLinkLocationPage<T>> {
|
|
2391
|
-
const result = await this.provider.scan(
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2438
|
+
const result = await this.provider.scan(
|
|
2439
|
+
{
|
|
2440
|
+
prefix,
|
|
2441
|
+
cursor:
|
|
2442
|
+
page.continuationToken === undefined
|
|
2443
|
+
? undefined
|
|
2444
|
+
: ({ value: page.continuationToken } as ZLinkStoreScanCursor),
|
|
2445
|
+
limit: page.pageSize ?? 100
|
|
2446
|
+
},
|
|
2447
|
+
signal
|
|
2448
|
+
);
|
|
2398
2449
|
if (result.kind === 'expired') {
|
|
2399
2450
|
throw new Error('Location Store scan snapshot expired.');
|
|
2400
2451
|
}
|
|
2401
2452
|
return {
|
|
2402
|
-
items: result.value.items.map(item =>
|
|
2403
|
-
revive(decodeCanonicalDescriptorRecord<T>(item.value.bytes).descriptor)
|
|
2453
|
+
items: result.value.items.map((item) =>
|
|
2454
|
+
revive(decodeCanonicalDescriptorRecord<T>(item.value.bytes).descriptor)
|
|
2455
|
+
),
|
|
2404
2456
|
continuationToken: result.value.nextCursor?.value
|
|
2405
2457
|
};
|
|
2406
2458
|
}
|
|
@@ -2428,8 +2480,10 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2428
2480
|
generation = BigInt(record.generation);
|
|
2429
2481
|
const stored = record.descriptor;
|
|
2430
2482
|
if (intent === 2) {
|
|
2431
|
-
if (
|
|
2432
|
-
|
|
2483
|
+
if (
|
|
2484
|
+
stored.ownerId !== location.ownerId ||
|
|
2485
|
+
locationOwnerGeneration(stored) !== locationOwnerGeneration(location)
|
|
2486
|
+
) {
|
|
2433
2487
|
return {
|
|
2434
2488
|
status: WriteStatus.IgnoredStale,
|
|
2435
2489
|
generation,
|
|
@@ -2461,20 +2515,25 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2461
2515
|
return rejected(lease.value.storeNow);
|
|
2462
2516
|
}
|
|
2463
2517
|
|
|
2464
|
-
const result = await this.provider.write(
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2518
|
+
const result = await this.provider.write(
|
|
2519
|
+
{
|
|
2520
|
+
conditions: [
|
|
2521
|
+
{ kind: 'version', key: leaseKey, expected: lease.value.version },
|
|
2522
|
+
rowCondition
|
|
2523
|
+
],
|
|
2524
|
+
mutations: [
|
|
2525
|
+
{
|
|
2526
|
+
kind: 'put',
|
|
2527
|
+
key: rowKey,
|
|
2528
|
+
bytes: encodeJson<DescriptorRecord<T>>({
|
|
2529
|
+
generation: generation.toString(),
|
|
2530
|
+
descriptor: location
|
|
2531
|
+
})
|
|
2532
|
+
}
|
|
2533
|
+
]
|
|
2534
|
+
},
|
|
2535
|
+
signal
|
|
2536
|
+
);
|
|
2478
2537
|
return result.kind === 'applied'
|
|
2479
2538
|
? { status: WriteStatus.Stored, generation, updatedAt: result.storeNow }
|
|
2480
2539
|
: rejected(result.storeNow);
|
|
@@ -2493,14 +2552,16 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2493
2552
|
readonly leaseGeneration?: bigint;
|
|
2494
2553
|
};
|
|
2495
2554
|
const leaseGeneration = descriptor.leaseGeneration ?? BigInt(record.generation);
|
|
2496
|
-
if (descriptor.ownerId !== owner.ownerId
|
|
2497
|
-
|| leaseGeneration !== owner.leaseGeneration) {
|
|
2555
|
+
if (descriptor.ownerId !== owner.ownerId || leaseGeneration !== owner.leaseGeneration) {
|
|
2498
2556
|
return WriteStatus.IgnoredStale;
|
|
2499
2557
|
}
|
|
2500
|
-
const result = await this.provider.write(
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2558
|
+
const result = await this.provider.write(
|
|
2559
|
+
{
|
|
2560
|
+
conditions: [{ kind: 'version', key: rowKey, expected: current.value.version }],
|
|
2561
|
+
mutations: [{ kind: 'delete', key: rowKey }]
|
|
2562
|
+
},
|
|
2563
|
+
signal
|
|
2564
|
+
);
|
|
2504
2565
|
return result.kind === 'applied' ? WriteStatus.Stored : WriteStatus.IgnoredStale;
|
|
2505
2566
|
}
|
|
2506
2567
|
|
|
@@ -2522,22 +2583,25 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2522
2583
|
signal?: AbortSignal
|
|
2523
2584
|
): Promise<ZLinkLocationPage<T>> {
|
|
2524
2585
|
const requested = page.pageSize ?? 100;
|
|
2525
|
-
let cursor =
|
|
2526
|
-
|
|
2527
|
-
|
|
2586
|
+
let cursor =
|
|
2587
|
+
page.continuationToken === undefined
|
|
2588
|
+
? undefined
|
|
2589
|
+
: ({ value: page.continuationToken } as ZLinkStoreScanCursor);
|
|
2528
2590
|
const items: T[] = [];
|
|
2529
2591
|
do {
|
|
2530
|
-
const result = await this.provider.scan(
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2592
|
+
const result = await this.provider.scan(
|
|
2593
|
+
{
|
|
2594
|
+
prefix,
|
|
2595
|
+
cursor,
|
|
2596
|
+
limit: requested
|
|
2597
|
+
},
|
|
2598
|
+
signal
|
|
2599
|
+
);
|
|
2535
2600
|
if (result.kind === 'expired') {
|
|
2536
2601
|
throw new Error('Location Store scan snapshot expired.');
|
|
2537
2602
|
}
|
|
2538
2603
|
for (const item of result.value.items) {
|
|
2539
|
-
const value = revive(
|
|
2540
|
-
decodeJson<DescriptorRecord<T>>(item.value.bytes).descriptor);
|
|
2604
|
+
const value = revive(decodeJson<DescriptorRecord<T>>(item.value.bytes).descriptor);
|
|
2541
2605
|
if (matches(value)) items.push(value);
|
|
2542
2606
|
if (items.length === requested) {
|
|
2543
2607
|
return {
|
|
@@ -2568,10 +2632,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2568
2632
|
);
|
|
2569
2633
|
}
|
|
2570
2634
|
const aggregateRead = await this.provider.read(
|
|
2571
|
-
aggregateKey(aggregateFence(
|
|
2572
|
-
aggregateId(marker.aggregateId),
|
|
2573
|
-
marker.aggregateGeneration
|
|
2574
|
-
)),
|
|
2635
|
+
aggregateKey(aggregateFence(aggregateId(marker.aggregateId), marker.aggregateGeneration)),
|
|
2575
2636
|
signal
|
|
2576
2637
|
);
|
|
2577
2638
|
if (aggregateRead.kind === 'missing') {
|
|
@@ -2585,10 +2646,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2585
2646
|
storeNow
|
|
2586
2647
|
);
|
|
2587
2648
|
}
|
|
2588
|
-
const fence = aggregateFence(
|
|
2589
|
-
aggregateId(marker.aggregateId),
|
|
2590
|
-
marker.aggregateGeneration
|
|
2591
|
-
);
|
|
2649
|
+
const fence = aggregateFence(aggregateId(marker.aggregateId), marker.aggregateGeneration);
|
|
2592
2650
|
const payload = await requireProviderBytes(
|
|
2593
2651
|
this.provider,
|
|
2594
2652
|
aggregateParticipantPayloadKey(fence, marker.index),
|
|
@@ -2597,25 +2655,25 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2597
2655
|
if (sha256Hex(payload) !== marker.authorityPayloadSha256) {
|
|
2598
2656
|
throw new Error('Committed aggregate participant payload checksum is invalid.');
|
|
2599
2657
|
}
|
|
2600
|
-
const projected: StoredAuthoritySnapshot =
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
aggregate.targetDescriptorLifecycleGeneration,
|
|
2612
|
-
|
|
2658
|
+
const projected: StoredAuthoritySnapshot =
|
|
2659
|
+
marker.ownerTransition === 'newOwner'
|
|
2660
|
+
? {
|
|
2661
|
+
payload: Buffer.from(payload),
|
|
2662
|
+
objectGeneration: record.snapshot.objectGeneration,
|
|
2663
|
+
authorityOwnerGeneration: marker.targetAuthorityOwnerGeneration,
|
|
2664
|
+
ownerId: aggregate.targetOwner.ownerId,
|
|
2665
|
+
ownerLeaseGeneration: aggregate.targetOwner.leaseGeneration,
|
|
2666
|
+
allocation: {
|
|
2667
|
+
...record.snapshot.allocation,
|
|
2668
|
+
descriptor: { ...aggregate.targetDescriptor },
|
|
2669
|
+
descriptorLifecycleGeneration: aggregate.targetDescriptorLifecycleGeneration,
|
|
2670
|
+
capacity: cloneCapacity(record.snapshot.allocation.capacity)
|
|
2671
|
+
}
|
|
2613
2672
|
}
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
};
|
|
2673
|
+
: {
|
|
2674
|
+
...record.snapshot,
|
|
2675
|
+
payload: Buffer.from(payload)
|
|
2676
|
+
};
|
|
2619
2677
|
return authoritySnapshot(projected, version, storeNow);
|
|
2620
2678
|
}
|
|
2621
2679
|
|
|
@@ -2640,11 +2698,13 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2640
2698
|
if (record.aggregate === undefined) {
|
|
2641
2699
|
if (
|
|
2642
2700
|
!Buffer.from(record.snapshot.payload).equals(
|
|
2643
|
-
Buffer.from(
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2701
|
+
Buffer.from(
|
|
2702
|
+
await requireProviderBytes(
|
|
2703
|
+
this.provider,
|
|
2704
|
+
aggregateParticipantPayloadKey(fence, index),
|
|
2705
|
+
signal
|
|
2706
|
+
)
|
|
2707
|
+
)
|
|
2648
2708
|
)
|
|
2649
2709
|
) {
|
|
2650
2710
|
throw new Error('Normalized aggregate participant payload changed.');
|
|
@@ -2667,15 +2727,13 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2667
2727
|
? {
|
|
2668
2728
|
payload: Buffer.from(payload),
|
|
2669
2729
|
objectGeneration: record.snapshot.objectGeneration,
|
|
2670
|
-
authorityOwnerGeneration:
|
|
2671
|
-
record.aggregate.targetAuthorityOwnerGeneration,
|
|
2730
|
+
authorityOwnerGeneration: record.aggregate.targetAuthorityOwnerGeneration,
|
|
2672
2731
|
ownerId: aggregate.targetOwner.ownerId,
|
|
2673
2732
|
ownerLeaseGeneration: aggregate.targetOwner.leaseGeneration,
|
|
2674
2733
|
allocation: {
|
|
2675
2734
|
...record.snapshot.allocation,
|
|
2676
2735
|
descriptor: { ...aggregate.targetDescriptor },
|
|
2677
|
-
descriptorLifecycleGeneration:
|
|
2678
|
-
aggregate.targetDescriptorLifecycleGeneration,
|
|
2736
|
+
descriptorLifecycleGeneration: aggregate.targetDescriptorLifecycleGeneration,
|
|
2679
2737
|
capacity: cloneCapacity(record.snapshot.allocation.capacity)
|
|
2680
2738
|
}
|
|
2681
2739
|
}
|
|
@@ -2689,18 +2747,25 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2689
2747
|
aggregate: undefined,
|
|
2690
2748
|
visibleStoreVersion: undefined
|
|
2691
2749
|
};
|
|
2692
|
-
const result = await this.provider.write(
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2750
|
+
const result = await this.provider.write(
|
|
2751
|
+
{
|
|
2752
|
+
conditions: [
|
|
2753
|
+
{
|
|
2754
|
+
kind: 'version',
|
|
2755
|
+
key: rowKey,
|
|
2756
|
+
expected: current.value.version
|
|
2757
|
+
}
|
|
2758
|
+
],
|
|
2759
|
+
mutations: [
|
|
2760
|
+
{
|
|
2761
|
+
kind: 'put',
|
|
2762
|
+
key: rowKey,
|
|
2763
|
+
bytes: encodeAuthorityRecord(normalized)
|
|
2764
|
+
}
|
|
2765
|
+
]
|
|
2766
|
+
},
|
|
2767
|
+
signal
|
|
2768
|
+
);
|
|
2704
2769
|
if (result.kind === 'applied') return;
|
|
2705
2770
|
}
|
|
2706
2771
|
});
|
|
@@ -2714,16 +2779,16 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2714
2779
|
if (bytes.byteLength > 1024 * 1024) {
|
|
2715
2780
|
throw new RangeError('Aggregate participant staging value exceeds 1 MiB.');
|
|
2716
2781
|
}
|
|
2717
|
-
const result = await this.provider.write(
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2782
|
+
const result = await this.provider.write(
|
|
2783
|
+
{
|
|
2784
|
+
conditions: [{ kind: 'missing', key }],
|
|
2785
|
+
mutations: [{ kind: 'put', key, bytes: Buffer.from(bytes) }]
|
|
2786
|
+
},
|
|
2787
|
+
signal
|
|
2788
|
+
);
|
|
2721
2789
|
if (result.kind === 'applied') return;
|
|
2722
2790
|
const current = await this.provider.read(key, signal);
|
|
2723
|
-
if (
|
|
2724
|
-
current.kind !== 'found'
|
|
2725
|
-
|| !Buffer.from(current.value.bytes).equals(Buffer.from(bytes))
|
|
2726
|
-
) {
|
|
2791
|
+
if (current.kind !== 'found' || !Buffer.from(current.value.bytes).equals(Buffer.from(bytes))) {
|
|
2727
2792
|
throw new Error(`Immutable aggregate participant value changed: ${key.value}.`);
|
|
2728
2793
|
}
|
|
2729
2794
|
}
|
|
@@ -2742,14 +2807,19 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2742
2807
|
return;
|
|
2743
2808
|
}
|
|
2744
2809
|
if (record.state !== 'staging') return;
|
|
2745
|
-
const result = await this.provider.write(
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2810
|
+
const result = await this.provider.write(
|
|
2811
|
+
{
|
|
2812
|
+
conditions: [{ kind: 'version', key, expected: current.value.version }],
|
|
2813
|
+
mutations: [
|
|
2814
|
+
{
|
|
2815
|
+
kind: 'put',
|
|
2816
|
+
key,
|
|
2817
|
+
bytes: encodeJson({ ...record, state: 'aborted' } satisfies AggregateRecord)
|
|
2818
|
+
}
|
|
2819
|
+
]
|
|
2820
|
+
},
|
|
2821
|
+
signal
|
|
2822
|
+
);
|
|
2753
2823
|
if (result.kind === 'applied') {
|
|
2754
2824
|
await this.clearAggregateMarkersByScan(fence, signal);
|
|
2755
2825
|
return;
|
|
@@ -2765,7 +2835,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2765
2835
|
}[],
|
|
2766
2836
|
signal?: AbortSignal
|
|
2767
2837
|
): Promise<void> {
|
|
2768
|
-
await parallelForEach(installed, 64, async value => {
|
|
2838
|
+
await parallelForEach(installed, 64, async (value) => {
|
|
2769
2839
|
await this.clearAggregateMarker(fence, value.key, signal);
|
|
2770
2840
|
});
|
|
2771
2841
|
}
|
|
@@ -2776,21 +2846,26 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2776
2846
|
): Promise<void> {
|
|
2777
2847
|
let cursor: ZLinkStoreScanCursor | undefined;
|
|
2778
2848
|
for (;;) {
|
|
2779
|
-
const result = await this.provider.scan(
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2849
|
+
const result = await this.provider.scan(
|
|
2850
|
+
{
|
|
2851
|
+
prefix: AUTHORITY_PREIMAGE_PREFIX,
|
|
2852
|
+
cursor,
|
|
2853
|
+
limit: 1_000
|
|
2854
|
+
},
|
|
2855
|
+
signal
|
|
2856
|
+
);
|
|
2784
2857
|
if (result.kind === 'expired') {
|
|
2785
2858
|
cursor = undefined;
|
|
2786
2859
|
continue;
|
|
2787
2860
|
}
|
|
2788
|
-
const matching = result.value.items.filter(item => {
|
|
2861
|
+
const matching = result.value.items.filter((item) => {
|
|
2789
2862
|
const record = decodeAuthorityRecord(item.value.bytes);
|
|
2790
|
-
return
|
|
2791
|
-
|
|
2863
|
+
return (
|
|
2864
|
+
record.aggregate?.aggregateId === fence.aggregateId.value &&
|
|
2865
|
+
record.aggregate.aggregateGeneration === fence.aggregateGeneration
|
|
2866
|
+
);
|
|
2792
2867
|
});
|
|
2793
|
-
await parallelForEach(matching, 64, async item => {
|
|
2868
|
+
await parallelForEach(matching, 64, async (item) => {
|
|
2794
2869
|
await this.clearAggregateMarker(fence, item.key, signal);
|
|
2795
2870
|
});
|
|
2796
2871
|
if (result.value.nextCursor === undefined) return;
|
|
@@ -2808,20 +2883,28 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
|
|
|
2808
2883
|
if (current.kind === 'missing') return;
|
|
2809
2884
|
const record = decodeAuthorityRecord(current.value.bytes);
|
|
2810
2885
|
if (
|
|
2811
|
-
record.aggregate === undefined
|
|
2812
|
-
|
|
2813
|
-
|
|
2886
|
+
record.aggregate === undefined ||
|
|
2887
|
+
record.aggregate.aggregateId !== fence.aggregateId.value ||
|
|
2888
|
+
record.aggregate.aggregateGeneration !== fence.aggregateGeneration
|
|
2814
2889
|
) {
|
|
2815
2890
|
return;
|
|
2816
2891
|
}
|
|
2817
|
-
const result = await this.provider.write(
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2892
|
+
const result = await this.provider.write(
|
|
2893
|
+
{
|
|
2894
|
+
conditions: [{ kind: 'version', key, expected: current.value.version }],
|
|
2895
|
+
mutations: [
|
|
2896
|
+
{
|
|
2897
|
+
kind: 'put',
|
|
2898
|
+
key,
|
|
2899
|
+
bytes: encodeAuthorityRecord({
|
|
2900
|
+
...record,
|
|
2901
|
+
aggregate: undefined
|
|
2902
|
+
} satisfies AuthorityRecord)
|
|
2903
|
+
}
|
|
2904
|
+
]
|
|
2905
|
+
},
|
|
2906
|
+
signal
|
|
2907
|
+
);
|
|
2825
2908
|
if (result.kind === 'applied') return;
|
|
2826
2909
|
}
|
|
2827
2910
|
}
|
|
@@ -2846,10 +2929,7 @@ class AmbiguousWriteReconcilingLocationStore implements ZLinkLocationStore {
|
|
|
2846
2929
|
return this.inner.read(key, signal);
|
|
2847
2930
|
}
|
|
2848
2931
|
|
|
2849
|
-
scan(
|
|
2850
|
-
request: ZLinkStoreScanRequest,
|
|
2851
|
-
signal?: AbortSignal
|
|
2852
|
-
): Promise<ZLinkStoreScanResult> {
|
|
2932
|
+
scan(request: ZLinkStoreScanRequest, signal?: AbortSignal): Promise<ZLinkStoreScanResult> {
|
|
2853
2933
|
return this.inner.scan(request, signal);
|
|
2854
2934
|
}
|
|
2855
2935
|
|
|
@@ -2868,18 +2948,14 @@ class AmbiguousWriteReconcilingLocationStore implements ZLinkLocationStore {
|
|
|
2868
2948
|
}
|
|
2869
2949
|
}
|
|
2870
2950
|
|
|
2871
|
-
private async reconcile(
|
|
2872
|
-
request: ZLinkStoreWriteRequest
|
|
2873
|
-
): Promise<ZLinkStoreWriteResult> {
|
|
2951
|
+
private async reconcile(request: ZLinkStoreWriteRequest): Promise<ZLinkStoreWriteResult> {
|
|
2874
2952
|
if (request.mutations.length === 0) {
|
|
2875
2953
|
throw new Error('Cannot reconcile an opaque Store write without mutations.');
|
|
2876
2954
|
}
|
|
2877
|
-
const reconciliationSignal = AbortSignal.timeout(
|
|
2878
|
-
|
|
2955
|
+
const reconciliationSignal = AbortSignal.timeout(AMBIGUOUS_WRITE_RECONCILIATION_TIMEOUT_MS);
|
|
2956
|
+
const reads = await Promise.all(
|
|
2957
|
+
request.mutations.map((mutation) => this.inner.read(mutation.key, reconciliationSignal))
|
|
2879
2958
|
);
|
|
2880
|
-
const reads = await Promise.all(request.mutations.map(
|
|
2881
|
-
mutation => this.inner.read(mutation.key, reconciliationSignal)
|
|
2882
|
-
));
|
|
2883
2959
|
const putVersions: Array<{
|
|
2884
2960
|
readonly key: ZLinkStoreKey;
|
|
2885
2961
|
readonly version: ZLinkStoreVersion;
|
|
@@ -2889,17 +2965,20 @@ class AmbiguousWriteReconcilingLocationStore implements ZLinkLocationStore {
|
|
|
2889
2965
|
for (let index = 0; index < request.mutations.length; index += 1) {
|
|
2890
2966
|
const mutation = request.mutations[index];
|
|
2891
2967
|
const read = reads[index];
|
|
2892
|
-
storeNow =
|
|
2893
|
-
|
|
2894
|
-
|
|
2968
|
+
storeNow =
|
|
2969
|
+
read.kind === 'found'
|
|
2970
|
+
? latestDate(storeNow, read.value.storeNow)
|
|
2971
|
+
: latestDate(storeNow, read.storeNow);
|
|
2895
2972
|
|
|
2896
2973
|
if (mutation.kind === 'delete') {
|
|
2897
2974
|
if (read.kind !== 'missing') return { kind: 'conflict', storeNow };
|
|
2898
2975
|
continue;
|
|
2899
2976
|
}
|
|
2900
|
-
if (
|
|
2901
|
-
|
|
2902
|
-
|
|
2977
|
+
if (
|
|
2978
|
+
read.kind !== 'found' ||
|
|
2979
|
+
!Buffer.from(read.value.bytes).equals(Buffer.from(mutation.bytes)) ||
|
|
2980
|
+
!versionAdvanced(request.conditions, mutation.key, read.value.version)
|
|
2981
|
+
) {
|
|
2903
2982
|
return { kind: 'conflict', storeNow };
|
|
2904
2983
|
}
|
|
2905
2984
|
putVersions.push({ key: mutation.key, version: read.value.version });
|
|
@@ -2914,7 +2993,7 @@ function versionAdvanced(
|
|
|
2914
2993
|
key: ZLinkStoreKey,
|
|
2915
2994
|
current: ZLinkStoreVersion
|
|
2916
2995
|
): boolean {
|
|
2917
|
-
const condition = conditions.find(candidate => candidate.key.value === key.value);
|
|
2996
|
+
const condition = conditions.find((candidate) => candidate.key.value === key.value);
|
|
2918
2997
|
return condition?.kind !== 'version' || condition.expected.value !== current.value;
|
|
2919
2998
|
}
|
|
2920
2999
|
|
|
@@ -2923,9 +3002,7 @@ function latestDate(left: Date, right: Date): Date {
|
|
|
2923
3002
|
}
|
|
2924
3003
|
|
|
2925
3004
|
type OwnedDescriptor =
|
|
2926
|
-
|
|
|
2927
|
-
| ZLinkClientServerServerDescriptor
|
|
2928
|
-
| ZLinkFanoutPublisherDescriptor;
|
|
3005
|
+
ZLinkMeshNodeDescriptor | ZLinkClientServerServerDescriptor | ZLinkFanoutPublisherDescriptor;
|
|
2929
3006
|
|
|
2930
3007
|
type LeaseOwnedLocation = ZLinkSpotLocation | ZLinkActorLocation;
|
|
2931
3008
|
type StoredLocation = LeaseOwnedLocation | ZLinkRouteLocation;
|
|
@@ -2939,10 +3016,10 @@ function validateProviderAggregateRequest(request: ZLinkAggregatePrepareRequest)
|
|
|
2939
3016
|
if (request.inventoryDigest.byteLength !== 32) {
|
|
2940
3017
|
throw new TypeError('Aggregate inventory digest must contain 32 bytes.');
|
|
2941
3018
|
}
|
|
2942
|
-
const keys = request.participants.map(value => value.authorityKey.value);
|
|
3019
|
+
const keys = request.participants.map((value) => value.authorityKey.value);
|
|
2943
3020
|
if (
|
|
2944
|
-
new Set(keys).size !== keys.length
|
|
2945
|
-
|
|
3021
|
+
new Set(keys).size !== keys.length ||
|
|
3022
|
+
keys.some((key, index) => index > 0 && keys[index - 1]!.localeCompare(key) >= 0)
|
|
2946
3023
|
) {
|
|
2947
3024
|
throw new TypeError('Aggregate participants must be unique and canonically sorted.');
|
|
2948
3025
|
}
|
|
@@ -2950,8 +3027,8 @@ function validateProviderAggregateRequest(request: ZLinkAggregatePrepareRequest)
|
|
|
2950
3027
|
requireText(participant.authorityKey.value, 'aggregate authority key');
|
|
2951
3028
|
requireText(participant.expectedStoreVersion.value, 'aggregate expected Store version');
|
|
2952
3029
|
if (
|
|
2953
|
-
participant.authorityPayload.byteLength > 1024 * 1024
|
|
2954
|
-
|
|
3030
|
+
participant.authorityPayload.byteLength > 1024 * 1024 ||
|
|
3031
|
+
participant.membershipMutation.byteLength > 1024 * 1024
|
|
2955
3032
|
) {
|
|
2956
3033
|
throw new RangeError('Aggregate participant value exceeds 1 MiB.');
|
|
2957
3034
|
}
|
|
@@ -2976,8 +3053,7 @@ function aggregateRecord(
|
|
|
2976
3053
|
participantCount: request.participants.length,
|
|
2977
3054
|
inventoryDigest: Buffer.from(request.inventoryDigest),
|
|
2978
3055
|
targetDescriptor: { ...request.targetDescriptor },
|
|
2979
|
-
targetDescriptorLifecycleGeneration:
|
|
2980
|
-
request.targetDescriptorLifecycleGeneration,
|
|
3056
|
+
targetDescriptorLifecycleGeneration: request.targetDescriptorLifecycleGeneration,
|
|
2981
3057
|
capacity: cloneCapacity(request.capacity),
|
|
2982
3058
|
targetOwner: { ...request.targetOwner }
|
|
2983
3059
|
};
|
|
@@ -3001,12 +3077,14 @@ function sameAggregateMarker(
|
|
|
3001
3077
|
index: number,
|
|
3002
3078
|
participant: ZLinkAggregatePrepareRequest['participants'][number]
|
|
3003
3079
|
): boolean {
|
|
3004
|
-
return
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3080
|
+
return (
|
|
3081
|
+
marker !== undefined &&
|
|
3082
|
+
marker.aggregateId === fence.aggregateId.value &&
|
|
3083
|
+
marker.aggregateGeneration === fence.aggregateGeneration &&
|
|
3084
|
+
marker.index === index &&
|
|
3085
|
+
marker.expectedStoreVersion === participant.expectedStoreVersion.value &&
|
|
3086
|
+
marker.ownerTransition === participant.ownerTransition
|
|
3087
|
+
);
|
|
3010
3088
|
}
|
|
3011
3089
|
|
|
3012
3090
|
function sameAggregateMarkerEntry(
|
|
@@ -3020,14 +3098,16 @@ function sameAggregateMarkerEntry(
|
|
|
3020
3098
|
readonly membershipMutationSha256: string;
|
|
3021
3099
|
}
|
|
3022
3100
|
): boolean {
|
|
3023
|
-
return
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3101
|
+
return (
|
|
3102
|
+
marker !== undefined &&
|
|
3103
|
+
marker.aggregateId === fence.aggregateId.value &&
|
|
3104
|
+
marker.aggregateGeneration === fence.aggregateGeneration &&
|
|
3105
|
+
marker.index === index &&
|
|
3106
|
+
marker.expectedStoreVersion === entry.expectedStoreVersion &&
|
|
3107
|
+
marker.ownerTransition === entry.ownerTransition &&
|
|
3108
|
+
marker.authorityPayloadSha256 === entry.authorityPayloadSha256 &&
|
|
3109
|
+
marker.membershipMutationSha256 === entry.membershipMutationSha256
|
|
3110
|
+
);
|
|
3031
3111
|
}
|
|
3032
3112
|
|
|
3033
3113
|
function aggregateTarget(
|
|
@@ -3041,9 +3121,7 @@ function aggregateTarget(
|
|
|
3041
3121
|
};
|
|
3042
3122
|
}
|
|
3043
3123
|
|
|
3044
|
-
function aggregateTargetFromRecord(
|
|
3045
|
-
record: AggregateRecord
|
|
3046
|
-
): ZLinkObjectCommitRequest['target'] {
|
|
3124
|
+
function aggregateTargetFromRecord(record: AggregateRecord): ZLinkObjectCommitRequest['target'] {
|
|
3047
3125
|
return {
|
|
3048
3126
|
meshName: record.targetDescriptor.meshName,
|
|
3049
3127
|
nodeRid: record.targetDescriptor.rid,
|
|
@@ -3059,11 +3137,9 @@ function addCapacityVector(
|
|
|
3059
3137
|
let spotType = left.spotType;
|
|
3060
3138
|
if (right.spotType !== undefined) {
|
|
3061
3139
|
if (
|
|
3062
|
-
spotType !== undefined
|
|
3063
|
-
|
|
3064
|
-
spotType.
|
|
3065
|
-
|| spotType.stableType !== right.spotType.stableType
|
|
3066
|
-
)
|
|
3140
|
+
spotType !== undefined &&
|
|
3141
|
+
(spotType.objectKind !== right.spotType.objectKind ||
|
|
3142
|
+
spotType.stableType !== right.spotType.stableType)
|
|
3067
3143
|
) {
|
|
3068
3144
|
throw new TypeError('Aggregate capacity contains more than one Spot type.');
|
|
3069
3145
|
}
|
|
@@ -3097,15 +3173,14 @@ async function parallelForEach<T>(
|
|
|
3097
3173
|
operation: (value: T, index: number) => Promise<void>
|
|
3098
3174
|
): Promise<void> {
|
|
3099
3175
|
let next = 0;
|
|
3100
|
-
await Promise.all(
|
|
3101
|
-
{ length: Math.min(concurrency, values.length) },
|
|
3102
|
-
async () => {
|
|
3176
|
+
await Promise.all(
|
|
3177
|
+
Array.from({ length: Math.min(concurrency, values.length) }, async () => {
|
|
3103
3178
|
while (next < values.length) {
|
|
3104
3179
|
const index = next++;
|
|
3105
3180
|
await operation(values[index]!, index);
|
|
3106
3181
|
}
|
|
3107
|
-
}
|
|
3108
|
-
)
|
|
3182
|
+
})
|
|
3183
|
+
);
|
|
3109
3184
|
}
|
|
3110
3185
|
|
|
3111
3186
|
async function waitForAggregateCommitRetry(
|
|
@@ -3160,11 +3235,7 @@ function authorityPreimage(value: string): string {
|
|
|
3160
3235
|
// §2.3: Entry|User|Instance Spot kinds share one segment ("spot") -- one
|
|
3161
3236
|
// Id has exactly one authority row regardless of spot kind.
|
|
3162
3237
|
const segment = decoded.kind === 'actor' ? 'actor' : 'spot';
|
|
3163
|
-
return opaqueRecordPreimage(
|
|
3164
|
-
'authority',
|
|
3165
|
-
segment,
|
|
3166
|
-
requireNoNul(decoded.globalId, 'Authority Id')
|
|
3167
|
-
);
|
|
3238
|
+
return opaqueRecordPreimage('authority', segment, requireNoNul(decoded.globalId, 'Authority Id'));
|
|
3168
3239
|
}
|
|
3169
3240
|
|
|
3170
3241
|
// Reverses authorityPreimage(): recovers the framework-internal zla1-encoded
|
|
@@ -3200,10 +3271,7 @@ function aggregateId(value: string): ZLinkAggregateId {
|
|
|
3200
3271
|
return { value } as ZLinkAggregateId;
|
|
3201
3272
|
}
|
|
3202
3273
|
|
|
3203
|
-
function aggregateFence(
|
|
3204
|
-
id: ZLinkAggregateId,
|
|
3205
|
-
generation: bigint
|
|
3206
|
-
): ZLinkAggregateFence {
|
|
3274
|
+
function aggregateFence(id: ZLinkAggregateId, generation: bigint): ZLinkAggregateFence {
|
|
3207
3275
|
return { aggregateId: id, aggregateGeneration: generation };
|
|
3208
3276
|
}
|
|
3209
3277
|
|
|
@@ -3213,13 +3281,10 @@ function aggregateKey(fence: ZLinkAggregateFence): ZLinkStoreKey {
|
|
|
3213
3281
|
);
|
|
3214
3282
|
}
|
|
3215
3283
|
|
|
3216
|
-
function aggregateParticipantPayloadKey(
|
|
3217
|
-
fence: ZLinkAggregateFence,
|
|
3218
|
-
index: number
|
|
3219
|
-
): ZLinkStoreKey {
|
|
3284
|
+
function aggregateParticipantPayloadKey(fence: ZLinkAggregateFence, index: number): ZLinkStoreKey {
|
|
3220
3285
|
return storeKey(
|
|
3221
|
-
`${PREFIX}aggregate-participant:${encodeURIComponent(fence.aggregateId.value)}:`
|
|
3222
|
-
|
|
3286
|
+
`${PREFIX}aggregate-participant:${encodeURIComponent(fence.aggregateId.value)}:` +
|
|
3287
|
+
`${fence.aggregateGeneration}:${index}:authority`
|
|
3223
3288
|
);
|
|
3224
3289
|
}
|
|
3225
3290
|
|
|
@@ -3228,8 +3293,8 @@ function aggregateParticipantMembershipKey(
|
|
|
3228
3293
|
index: number
|
|
3229
3294
|
): ZLinkStoreKey {
|
|
3230
3295
|
return storeKey(
|
|
3231
|
-
`${PREFIX}aggregate-participant:${encodeURIComponent(fence.aggregateId.value)}:`
|
|
3232
|
-
|
|
3296
|
+
`${PREFIX}aggregate-participant:${encodeURIComponent(fence.aggregateId.value)}:` +
|
|
3297
|
+
`${fence.aggregateGeneration}:${index}:membership`
|
|
3233
3298
|
);
|
|
3234
3299
|
}
|
|
3235
3300
|
|
|
@@ -3249,56 +3314,53 @@ function meshPrefix(meshName: string): string {
|
|
|
3249
3314
|
}
|
|
3250
3315
|
|
|
3251
3316
|
function meshKey(meshName: string, nodeRid: RoutingId) {
|
|
3252
|
-
return storeKey(
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3317
|
+
return storeKey(
|
|
3318
|
+
opaqueRecordPreimage(
|
|
3319
|
+
'mesh-node',
|
|
3320
|
+
requireNoNul(meshName, 'MeshName'),
|
|
3321
|
+
routingIdHexSegment(nodeRid)
|
|
3322
|
+
)
|
|
3323
|
+
);
|
|
3257
3324
|
}
|
|
3258
3325
|
|
|
3259
3326
|
function clientServerPrefix(channelName: string): string {
|
|
3260
|
-
return `${opaqueRecordPreimage(
|
|
3261
|
-
'client-server',
|
|
3262
|
-
requireNoNul(channelName, 'ChannelName')
|
|
3263
|
-
)}\0`;
|
|
3327
|
+
return `${opaqueRecordPreimage('client-server', requireNoNul(channelName, 'ChannelName'))}\0`;
|
|
3264
3328
|
}
|
|
3265
3329
|
|
|
3266
3330
|
function clientServerKey(channelName: string, serverRid: RoutingId) {
|
|
3267
|
-
return storeKey(
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3331
|
+
return storeKey(
|
|
3332
|
+
opaqueRecordPreimage(
|
|
3333
|
+
'client-server',
|
|
3334
|
+
requireNoNul(channelName, 'ChannelName'),
|
|
3335
|
+
routingIdHexSegment(serverRid)
|
|
3336
|
+
)
|
|
3337
|
+
);
|
|
3272
3338
|
}
|
|
3273
3339
|
|
|
3274
3340
|
function fanoutPrefix(channelName: string): string {
|
|
3275
|
-
return `${opaqueRecordPreimage(
|
|
3276
|
-
'fanout-publisher',
|
|
3277
|
-
requireNoNul(channelName, 'ChannelName')
|
|
3278
|
-
)}\0`;
|
|
3341
|
+
return `${opaqueRecordPreimage('fanout-publisher', requireNoNul(channelName, 'ChannelName'))}\0`;
|
|
3279
3342
|
}
|
|
3280
3343
|
|
|
3281
3344
|
function fanoutKey(channelName: string, publisherRid: RoutingId) {
|
|
3282
|
-
return storeKey(
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3345
|
+
return storeKey(
|
|
3346
|
+
opaqueRecordPreimage(
|
|
3347
|
+
'fanout-publisher',
|
|
3348
|
+
requireNoNul(channelName, 'ChannelName'),
|
|
3349
|
+
routingIdHexSegment(publisherRid)
|
|
3350
|
+
)
|
|
3351
|
+
);
|
|
3287
3352
|
}
|
|
3288
3353
|
|
|
3289
3354
|
function spotKey(meshName: string, spotId: string) {
|
|
3290
|
-
return storeKey(
|
|
3291
|
-
`${PREFIX}spot:${encodeURIComponent(meshName)}:${encodeURIComponent(spotId)}`);
|
|
3355
|
+
return storeKey(`${PREFIX}spot:${encodeURIComponent(meshName)}:${encodeURIComponent(spotId)}`);
|
|
3292
3356
|
}
|
|
3293
3357
|
|
|
3294
3358
|
function actorKey(meshName: string, actorId: string) {
|
|
3295
|
-
return storeKey(
|
|
3296
|
-
`${PREFIX}actor:${encodeURIComponent(meshName)}:${encodeURIComponent(actorId)}`);
|
|
3359
|
+
return storeKey(`${PREFIX}actor:${encodeURIComponent(meshName)}:${encodeURIComponent(actorId)}`);
|
|
3297
3360
|
}
|
|
3298
3361
|
|
|
3299
3362
|
function routeKey(routeKind: string, value: string) {
|
|
3300
|
-
return storeKey(
|
|
3301
|
-
`${PREFIX}route:${encodeURIComponent(routeKind)}:${encodeURIComponent(value)}`);
|
|
3363
|
+
return storeKey(`${PREFIX}route:${encodeURIComponent(routeKind)}:${encodeURIComponent(value)}`);
|
|
3302
3364
|
}
|
|
3303
3365
|
|
|
3304
3366
|
function encodeText(value: string): Uint8Array {
|
|
@@ -3324,24 +3386,28 @@ function counterNextValue(counter: ZLinkStoreReadResult): bigint {
|
|
|
3324
3386
|
}
|
|
3325
3387
|
|
|
3326
3388
|
function encodeJson<T>(value: T): Uint8Array {
|
|
3327
|
-
return Buffer.from(
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
Object.
|
|
3340
|
-
.sort(([left], [right]) =>
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3389
|
+
return Buffer.from(
|
|
3390
|
+
JSON.stringify(value, (key, candidate) => {
|
|
3391
|
+
if (typeof candidate === 'bigint') return { $bigint: candidate.toString() };
|
|
3392
|
+
if (candidate instanceof Uint8Array) {
|
|
3393
|
+
return { $bytes: Buffer.from(candidate).toString('base64') };
|
|
3394
|
+
}
|
|
3395
|
+
if (
|
|
3396
|
+
key === 'spotTypes' &&
|
|
3397
|
+
candidate !== null &&
|
|
3398
|
+
typeof candidate === 'object' &&
|
|
3399
|
+
!Array.isArray(candidate)
|
|
3400
|
+
) {
|
|
3401
|
+
return Object.fromEntries(
|
|
3402
|
+
Object.entries(candidate as Record<string, unknown>).sort(([left], [right]) =>
|
|
3403
|
+
compareUtf16Ordinal(left, right)
|
|
3404
|
+
)
|
|
3405
|
+
);
|
|
3406
|
+
}
|
|
3407
|
+
return candidate;
|
|
3408
|
+
}),
|
|
3409
|
+
'utf8'
|
|
3410
|
+
);
|
|
3345
3411
|
}
|
|
3346
3412
|
|
|
3347
3413
|
function compareUtf16Ordinal(left: string, right: string): number {
|
|
@@ -3350,16 +3416,20 @@ function compareUtf16Ordinal(left: string, right: string): number {
|
|
|
3350
3416
|
|
|
3351
3417
|
function decodeJson<T>(value: Uint8Array): T {
|
|
3352
3418
|
return JSON.parse(decodeText(value), (_key, candidate) => {
|
|
3353
|
-
if (
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3419
|
+
if (
|
|
3420
|
+
candidate !== null &&
|
|
3421
|
+
typeof candidate === 'object' &&
|
|
3422
|
+
Object.keys(candidate).length === 1 &&
|
|
3423
|
+
typeof candidate.$bigint === 'string'
|
|
3424
|
+
) {
|
|
3357
3425
|
return BigInt(candidate.$bigint);
|
|
3358
3426
|
}
|
|
3359
|
-
if (
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3427
|
+
if (
|
|
3428
|
+
candidate !== null &&
|
|
3429
|
+
typeof candidate === 'object' &&
|
|
3430
|
+
Object.keys(candidate).length === 1 &&
|
|
3431
|
+
typeof candidate.$bytes === 'string'
|
|
3432
|
+
) {
|
|
3363
3433
|
return Buffer.from(candidate.$bytes, 'base64');
|
|
3364
3434
|
}
|
|
3365
3435
|
return candidate;
|
|
@@ -3367,8 +3437,11 @@ function decodeJson<T>(value: Uint8Array): T {
|
|
|
3367
3437
|
}
|
|
3368
3438
|
|
|
3369
3439
|
function requireRecordVersion(value: unknown, kind: string): void {
|
|
3370
|
-
if (
|
|
3371
|
-
|
|
3440
|
+
if (
|
|
3441
|
+
value === null ||
|
|
3442
|
+
typeof value !== 'object' ||
|
|
3443
|
+
(value as { recordVersion?: unknown }).recordVersion !== 1
|
|
3444
|
+
) {
|
|
3372
3445
|
throw new Error(`Location Store ${kind} record has an unrecognized recordVersion.`);
|
|
3373
3446
|
}
|
|
3374
3447
|
}
|
|
@@ -3379,21 +3452,33 @@ function canonicalize(value: unknown): unknown {
|
|
|
3379
3452
|
if (value instanceof Date) return value.toISOString();
|
|
3380
3453
|
if (Array.isArray(value)) return value.map(canonicalize);
|
|
3381
3454
|
if (value !== null && typeof value === 'object') {
|
|
3382
|
-
return Object.fromEntries(
|
|
3455
|
+
return Object.fromEntries(
|
|
3456
|
+
Object.entries(value).map(([key, item]) => [key, canonicalize(item)])
|
|
3457
|
+
);
|
|
3383
3458
|
}
|
|
3384
3459
|
return value;
|
|
3385
3460
|
}
|
|
3386
3461
|
|
|
3387
3462
|
function reviveCanonical(value: unknown, key = ''): unknown {
|
|
3388
3463
|
const generationKeys = new Set([
|
|
3389
|
-
'generation',
|
|
3390
|
-
'
|
|
3391
|
-
'
|
|
3464
|
+
'generation',
|
|
3465
|
+
'leaseGeneration',
|
|
3466
|
+
'ownerLeaseGeneration',
|
|
3467
|
+
'objectGeneration',
|
|
3468
|
+
'authorityOwnerGeneration',
|
|
3469
|
+
'aggregateGeneration',
|
|
3470
|
+
'lifecycleGeneration',
|
|
3471
|
+
'descriptorRevision',
|
|
3472
|
+
'nodeGeneration',
|
|
3473
|
+
'applicationVersion',
|
|
3474
|
+
'requestEncodedSize'
|
|
3392
3475
|
]);
|
|
3393
3476
|
if (typeof value === 'string' && generationKeys.has(key)) return BigInt(value);
|
|
3394
|
-
if (Array.isArray(value)) return value.map(item => reviveCanonical(item));
|
|
3477
|
+
if (Array.isArray(value)) return value.map((item) => reviveCanonical(item));
|
|
3395
3478
|
if (value !== null && typeof value === 'object') {
|
|
3396
|
-
return Object.fromEntries(
|
|
3479
|
+
return Object.fromEntries(
|
|
3480
|
+
Object.entries(value).map(([name, item]) => [name, reviveCanonical(item, name)])
|
|
3481
|
+
);
|
|
3397
3482
|
}
|
|
3398
3483
|
return value;
|
|
3399
3484
|
}
|
|
@@ -3405,7 +3490,11 @@ function decodeOwnerRecord(bytes: Uint8Array): OwnerRecord {
|
|
|
3405
3490
|
}
|
|
3406
3491
|
|
|
3407
3492
|
function encodeOwnerRecord(ownerId: string, leaseGeneration: bigint): Uint8Array {
|
|
3408
|
-
return encodeJson<OwnerRecord>({
|
|
3493
|
+
return encodeJson<OwnerRecord>({
|
|
3494
|
+
recordVersion: 1,
|
|
3495
|
+
ownerId,
|
|
3496
|
+
leaseGeneration: leaseGeneration.toString()
|
|
3497
|
+
});
|
|
3409
3498
|
}
|
|
3410
3499
|
|
|
3411
3500
|
function encodeCanonicalDescriptorRecord<T extends OwnedDescriptor>(
|
|
@@ -3416,16 +3505,21 @@ function encodeCanonicalDescriptorRecord<T extends OwnedDescriptor>(
|
|
|
3416
3505
|
// cmsgpack version is the interoperable row version, so it is deliberately
|
|
3417
3506
|
// not serialized (21-location-runtime §2.4).
|
|
3418
3507
|
void generation;
|
|
3419
|
-
return Buffer.from(
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3508
|
+
return Buffer.from(
|
|
3509
|
+
JSON.stringify({
|
|
3510
|
+
recordVersion: 1,
|
|
3511
|
+
ownerId: descriptor.ownerId,
|
|
3512
|
+
leaseGeneration: descriptor.leaseGeneration.toString(),
|
|
3513
|
+
descriptorRevision: descriptor.descriptorRevision.toString(),
|
|
3514
|
+
descriptor: canonicalDescriptor(descriptor)
|
|
3515
|
+
} satisfies CanonicalDescriptorRecord<unknown>),
|
|
3516
|
+
'utf8'
|
|
3517
|
+
);
|
|
3426
3518
|
}
|
|
3427
3519
|
|
|
3428
|
-
function decodeCanonicalDescriptorRecord<T extends OwnedDescriptor>(
|
|
3520
|
+
function decodeCanonicalDescriptorRecord<T extends OwnedDescriptor>(
|
|
3521
|
+
bytes: Uint8Array
|
|
3522
|
+
): CanonicalDescriptorRecord<T> {
|
|
3429
3523
|
const record = JSON.parse(decodeText(bytes)) as CanonicalDescriptorRecord<unknown>;
|
|
3430
3524
|
requireRecordVersion(record, 'descriptor');
|
|
3431
3525
|
return reviveCanonical(record) as CanonicalDescriptorRecord<T>;
|
|
@@ -3440,19 +3534,20 @@ function descriptorStoreGeneration(
|
|
|
3440
3534
|
|
|
3441
3535
|
function canonicalDescriptor(descriptor: OwnedDescriptor): Record<string, unknown> {
|
|
3442
3536
|
if ('rid' in descriptor) return canonicalMeshDescriptor(descriptor);
|
|
3443
|
-
if ('serverRid' in descriptor)
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3537
|
+
if ('serverRid' in descriptor)
|
|
3538
|
+
return {
|
|
3539
|
+
channelName: descriptor.channelName,
|
|
3540
|
+
serverRoutingIdHex: encodeRoutingIdStorageHex(descriptor.serverRid),
|
|
3541
|
+
lifecycleGeneration: descriptor.lifecycleGeneration.toString(),
|
|
3542
|
+
descriptorRevision: descriptor.descriptorRevision.toString(),
|
|
3543
|
+
endpoint: descriptor.endpoint,
|
|
3544
|
+
weight: descriptor.weight,
|
|
3545
|
+
state: canonicalRuntimeState(descriptor.state),
|
|
3546
|
+
securityIdentity: descriptor.securityIdentity,
|
|
3547
|
+
ownerId: descriptor.ownerId,
|
|
3548
|
+
leaseGeneration: descriptor.leaseGeneration.toString(),
|
|
3549
|
+
updatedAtEpochMs: descriptor.updatedAt.getTime().toString()
|
|
3550
|
+
};
|
|
3456
3551
|
const publisher = descriptor as ZLinkFanoutPublisherDescriptor;
|
|
3457
3552
|
return {
|
|
3458
3553
|
channelName: publisher.channelName,
|
|
@@ -3478,7 +3573,7 @@ function canonicalMeshDescriptor(descriptor: ZLinkMeshNodeDescriptor): Record<st
|
|
|
3478
3573
|
entrySpotId: descriptor.entrySpotId ?? null,
|
|
3479
3574
|
channelWeights: descriptor.channelWeights,
|
|
3480
3575
|
applicationVersion: descriptor.applicationVersion.toString(),
|
|
3481
|
-
objectCapabilities: descriptor.objectCapabilities.map(capability => ({
|
|
3576
|
+
objectCapabilities: descriptor.objectCapabilities.map((capability) => ({
|
|
3482
3577
|
...capability,
|
|
3483
3578
|
objectKind: canonicalObjectKind(capability.objectKind),
|
|
3484
3579
|
policy: capability.policy === 'disabled' ? 'disabled' : capability.policy
|
|
@@ -3488,7 +3583,7 @@ function canonicalMeshDescriptor(descriptor: ZLinkMeshNodeDescriptor): Record<st
|
|
|
3488
3583
|
capacity: {
|
|
3489
3584
|
actors: descriptor.populationCapacity.actors,
|
|
3490
3585
|
spots: descriptor.populationCapacity.spots,
|
|
3491
|
-
spotTypes: descriptor.populationCapacity.spotTypes.map(capacity => ({
|
|
3586
|
+
spotTypes: descriptor.populationCapacity.spotTypes.map((capacity) => ({
|
|
3492
3587
|
...capacity,
|
|
3493
3588
|
objectKind: canonicalObjectKind(capacity.objectKind)
|
|
3494
3589
|
}))
|
|
@@ -3503,19 +3598,23 @@ function canonicalMeshDescriptor(descriptor: ZLinkMeshNodeDescriptor): Record<st
|
|
|
3503
3598
|
};
|
|
3504
3599
|
}
|
|
3505
3600
|
|
|
3506
|
-
function reviveCanonicalCapacity(
|
|
3601
|
+
function reviveCanonicalCapacity(
|
|
3602
|
+
value: Record<string, unknown>
|
|
3603
|
+
): ZLinkMeshNodeDescriptor['populationCapacity'] {
|
|
3507
3604
|
return {
|
|
3508
3605
|
actors: value.actors as ZLinkMeshNodeDescriptor['populationCapacity']['actors'],
|
|
3509
3606
|
spots: value.spots as ZLinkMeshNodeDescriptor['populationCapacity']['spots'],
|
|
3510
|
-
spotTypes: (value.spotTypes as readonly Record<string, unknown>[]).map(capacity => ({
|
|
3607
|
+
spotTypes: (value.spotTypes as readonly Record<string, unknown>[]).map((capacity) => ({
|
|
3511
3608
|
...capacity,
|
|
3512
3609
|
objectKind: runtimeSpotObjectKind(String(capacity.objectKind))
|
|
3513
3610
|
})) as unknown as ZLinkMeshNodeDescriptor['populationCapacity']['spotTypes']
|
|
3514
3611
|
};
|
|
3515
3612
|
}
|
|
3516
3613
|
|
|
3517
|
-
function reviveCanonicalCapabilities(
|
|
3518
|
-
|
|
3614
|
+
function reviveCanonicalCapabilities(
|
|
3615
|
+
value: unknown
|
|
3616
|
+
): ZLinkMeshNodeDescriptor['objectCapabilities'] {
|
|
3617
|
+
return (value as readonly Record<string, unknown>[]).map((capability) => ({
|
|
3519
3618
|
...capability,
|
|
3520
3619
|
objectKind: runtimeObjectKind(String(capability.objectKind))
|
|
3521
3620
|
})) as unknown as ZLinkMeshNodeDescriptor['objectCapabilities'];
|
|
@@ -3534,17 +3633,30 @@ function runtimeObjectKind(kind: string): 'actor' | 'user_spot' | 'instance_spot
|
|
|
3534
3633
|
|
|
3535
3634
|
function runtimeSpotObjectKind(kind: string): 'user_spot' | 'instance_spot' {
|
|
3536
3635
|
const result = runtimeObjectKind(kind);
|
|
3537
|
-
if (result === 'actor')
|
|
3636
|
+
if (result === 'actor')
|
|
3637
|
+
throw new TypeError('Location Store spot capacity cannot have actor objectKind.');
|
|
3538
3638
|
return result;
|
|
3539
3639
|
}
|
|
3540
3640
|
|
|
3541
3641
|
function canonicalRuntimeState(state: ZLinkFrameworkRuntimeState): string {
|
|
3542
|
-
return
|
|
3543
|
-
|
|
3642
|
+
return (
|
|
3643
|
+
['preparing', 'serving', 'relocating', 'relocated', 'draining', 'stopped', 'error'][state] ??
|
|
3644
|
+
(() => {
|
|
3645
|
+
throw new TypeError(`Location Store descriptor has invalid state '${state}'.`);
|
|
3646
|
+
})()
|
|
3647
|
+
);
|
|
3544
3648
|
}
|
|
3545
3649
|
|
|
3546
3650
|
function runtimeState(state: string): ZLinkFrameworkRuntimeState {
|
|
3547
|
-
const index = [
|
|
3651
|
+
const index = [
|
|
3652
|
+
'preparing',
|
|
3653
|
+
'serving',
|
|
3654
|
+
'relocating',
|
|
3655
|
+
'relocated',
|
|
3656
|
+
'draining',
|
|
3657
|
+
'stopped',
|
|
3658
|
+
'error'
|
|
3659
|
+
].indexOf(state);
|
|
3548
3660
|
if (index < 0) throw new TypeError(`Location Store descriptor has invalid state '${state}'.`);
|
|
3549
3661
|
return index as ZLinkFrameworkRuntimeState;
|
|
3550
3662
|
}
|
|
@@ -3570,19 +3682,23 @@ function encodeAuthorityRecord(record: AuthorityRecord): Uint8Array {
|
|
|
3570
3682
|
descriptorLifecycleGeneration: allocation.descriptorLifecycleGeneration.toString(),
|
|
3571
3683
|
capacity: canonicalAuthorityCapacity(allocation.capacity)
|
|
3572
3684
|
},
|
|
3573
|
-
pendingCreation:
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3685
|
+
pendingCreation:
|
|
3686
|
+
snapshot.pendingCreation === undefined
|
|
3687
|
+
? null
|
|
3688
|
+
: {
|
|
3689
|
+
reservationId: snapshot.pendingCreation.reservationId,
|
|
3690
|
+
requestContentReference: snapshot.pendingCreation.requestContentReference,
|
|
3691
|
+
requestSha256: Buffer.from(snapshot.pendingCreation.requestSha256).toString('hex'),
|
|
3692
|
+
requestEncodedSize: Number(snapshot.pendingCreation.requestEncodedSize)
|
|
3693
|
+
}
|
|
3579
3694
|
};
|
|
3580
3695
|
// These fields gate Node-only creation/aggregate recovery. They are absent
|
|
3581
3696
|
// from normal authority values (including the golden vector), while the
|
|
3582
3697
|
// interoperable envelope above remains byte-canonical.
|
|
3583
3698
|
if (record.terminal !== undefined) envelope.terminal = record.terminal;
|
|
3584
3699
|
if (record.aggregate !== undefined) envelope.aggregate = canonicalize(record.aggregate);
|
|
3585
|
-
if (record.visibleStoreVersion !== undefined)
|
|
3700
|
+
if (record.visibleStoreVersion !== undefined)
|
|
3701
|
+
envelope.visibleStoreVersion = record.visibleStoreVersion;
|
|
3586
3702
|
return Buffer.from(JSON.stringify(envelope), 'utf8');
|
|
3587
3703
|
}
|
|
3588
3704
|
|
|
@@ -3596,11 +3712,12 @@ function decodeAuthorityRecord(bytes: Uint8Array): AuthorityRecord {
|
|
|
3596
3712
|
const objectKind = runtimePlacementObjectKind(String(allocation.objectKind));
|
|
3597
3713
|
return {
|
|
3598
3714
|
terminal: value.terminal as AuthorityRecord['terminal'],
|
|
3599
|
-
aggregate:
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3715
|
+
aggregate:
|
|
3716
|
+
value.aggregate === undefined
|
|
3717
|
+
? undefined
|
|
3718
|
+
: (reviveCanonical(value.aggregate) as AggregateParticipantFenceRecord),
|
|
3719
|
+
visibleStoreVersion:
|
|
3720
|
+
typeof value.visibleStoreVersion === 'string' ? value.visibleStoreVersion : undefined,
|
|
3604
3721
|
snapshot: {
|
|
3605
3722
|
payload: Buffer.from(String(value.payload), 'base64'),
|
|
3606
3723
|
objectGeneration: BigInt(String(value.objectGeneration)),
|
|
@@ -3613,44 +3730,52 @@ function decodeAuthorityRecord(bytes: Uint8Array): AuthorityRecord {
|
|
|
3613
3730
|
stableType: String(allocation.stableType),
|
|
3614
3731
|
descriptor: {
|
|
3615
3732
|
meshName: String(descriptor.meshName),
|
|
3616
|
-
rid: decodeRoutingId(
|
|
3617
|
-
String(descriptor.routingIdHex),
|
|
3618
|
-
descriptor.routingIdHex
|
|
3619
|
-
)
|
|
3733
|
+
rid: decodeRoutingId(String(descriptor.routingIdHex), descriptor.routingIdHex)
|
|
3620
3734
|
},
|
|
3621
3735
|
descriptorLifecycleGeneration: BigInt(String(allocation.descriptorLifecycleGeneration)),
|
|
3622
3736
|
capacity: {
|
|
3623
3737
|
actors: Number(capacity.actors),
|
|
3624
3738
|
spots: Number(capacity.spots),
|
|
3625
|
-
spotType:
|
|
3626
|
-
? undefined
|
|
3627
|
-
: decodeAuthoritySpotType(capacity.spotType)
|
|
3739
|
+
spotType:
|
|
3740
|
+
capacity.spotType === null ? undefined : decodeAuthoritySpotType(capacity.spotType)
|
|
3628
3741
|
}
|
|
3629
3742
|
},
|
|
3630
|
-
pendingCreation:
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3743
|
+
pendingCreation:
|
|
3744
|
+
pending === null
|
|
3745
|
+
? undefined
|
|
3746
|
+
: {
|
|
3747
|
+
reservationId: String(pending.reservationId),
|
|
3748
|
+
requestContentReference: String(pending.requestContentReference),
|
|
3749
|
+
requestSha256: Buffer.from(String(pending.requestSha256), 'hex'),
|
|
3750
|
+
requestEncodedSize: BigInt(String(pending.requestEncodedSize))
|
|
3751
|
+
}
|
|
3636
3752
|
}
|
|
3637
3753
|
};
|
|
3638
3754
|
}
|
|
3639
3755
|
|
|
3640
3756
|
function canonicalPlacementObjectKind(kind: ZLinkPlacementAllocation['objectKind']): string {
|
|
3641
3757
|
switch (kind) {
|
|
3642
|
-
case 'actor':
|
|
3643
|
-
|
|
3644
|
-
case '
|
|
3758
|
+
case 'actor':
|
|
3759
|
+
return 'actor';
|
|
3760
|
+
case 'user_spot':
|
|
3761
|
+
return 'userSpot';
|
|
3762
|
+
case 'instance_spot':
|
|
3763
|
+
return 'instanceSpot';
|
|
3645
3764
|
}
|
|
3646
3765
|
}
|
|
3647
3766
|
|
|
3648
3767
|
function runtimePlacementObjectKind(kind: string): ZLinkPlacementAllocation['objectKind'] {
|
|
3649
3768
|
switch (kind) {
|
|
3650
|
-
case 'actor':
|
|
3651
|
-
|
|
3652
|
-
case '
|
|
3653
|
-
|
|
3769
|
+
case 'actor':
|
|
3770
|
+
return 'actor';
|
|
3771
|
+
case 'userSpot':
|
|
3772
|
+
return 'user_spot';
|
|
3773
|
+
case 'instanceSpot':
|
|
3774
|
+
return 'instance_spot';
|
|
3775
|
+
default:
|
|
3776
|
+
throw new Error(
|
|
3777
|
+
`Location Store authority record has an invalid allocation objectKind '${kind}'.`
|
|
3778
|
+
);
|
|
3654
3779
|
}
|
|
3655
3780
|
}
|
|
3656
3781
|
|
|
@@ -3658,13 +3783,14 @@ function canonicalAuthorityCapacity(capacity: ZLinkCapacityVector): Record<strin
|
|
|
3658
3783
|
return {
|
|
3659
3784
|
actors: capacity.actors,
|
|
3660
3785
|
spots: capacity.spots,
|
|
3661
|
-
spotType:
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3786
|
+
spotType:
|
|
3787
|
+
capacity.spotType === undefined
|
|
3788
|
+
? null
|
|
3789
|
+
: {
|
|
3790
|
+
objectKind: canonicalPlacementObjectKind(capacity.spotType.objectKind),
|
|
3791
|
+
stableType: capacity.spotType.stableType,
|
|
3792
|
+
count: capacity.spotType.count
|
|
3793
|
+
}
|
|
3668
3794
|
};
|
|
3669
3795
|
}
|
|
3670
3796
|
|
|
@@ -3699,9 +3825,9 @@ function createTerminalRecord(
|
|
|
3699
3825
|
const deadlineMs = publication.operationDeadline.getTime();
|
|
3700
3826
|
const expiresAtMs = deadlineMs + CREATION_TERMINAL_RETENTION_MS;
|
|
3701
3827
|
if (
|
|
3702
|
-
!Number.isSafeInteger(deadlineMs)
|
|
3703
|
-
|
|
3704
|
-
|
|
3828
|
+
!Number.isSafeInteger(deadlineMs) ||
|
|
3829
|
+
!Number.isSafeInteger(expiresAtMs) ||
|
|
3830
|
+
expiresAtMs <= now.getTime()
|
|
3705
3831
|
) {
|
|
3706
3832
|
throw new RangeError(
|
|
3707
3833
|
'Creation terminal expiry must be the live operation deadline plus five minutes.'
|
|
@@ -3752,13 +3878,13 @@ function validateCreationOperation(operation: ZLinkCreationOperationIdentity): v
|
|
|
3752
3878
|
);
|
|
3753
3879
|
}
|
|
3754
3880
|
if (
|
|
3755
|
-
operation.sourceNodeGeneration < 1n
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3881
|
+
operation.sourceNodeGeneration < 1n ||
|
|
3882
|
+
operation.sourceNodeGeneration > MAX_U64 ||
|
|
3883
|
+
operation.operationId.high < 0n ||
|
|
3884
|
+
operation.operationId.high > MAX_U64 ||
|
|
3885
|
+
operation.operationId.low < 0n ||
|
|
3886
|
+
operation.operationId.low > MAX_U64 ||
|
|
3887
|
+
(operation.operationId.high === 0n && operation.operationId.low === 0n)
|
|
3762
3888
|
) {
|
|
3763
3889
|
throw new RangeError('Creation terminal source generation and operation ID are invalid.');
|
|
3764
3890
|
}
|
|
@@ -3786,12 +3912,13 @@ function authoritySnapshot(
|
|
|
3786
3912
|
kind: 'snapshot',
|
|
3787
3913
|
...snapshot,
|
|
3788
3914
|
payload: Buffer.from(snapshot.payload),
|
|
3789
|
-
pendingCreation:
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3915
|
+
pendingCreation:
|
|
3916
|
+
snapshot.pendingCreation === undefined
|
|
3917
|
+
? undefined
|
|
3918
|
+
: {
|
|
3919
|
+
...snapshot.pendingCreation,
|
|
3920
|
+
requestSha256: Buffer.from(snapshot.pendingCreation.requestSha256)
|
|
3921
|
+
},
|
|
3795
3922
|
storeVersion: { value: storeVersion.value } as ZLinkAuthorityStoreVersion,
|
|
3796
3923
|
storeNow
|
|
3797
3924
|
};
|
|
@@ -3812,34 +3939,28 @@ function liveTargetDescriptor(
|
|
|
3812
3939
|
decodeCanonicalDescriptorRecord<ZLinkMeshNodeDescriptor>(descriptorRead.value.bytes).descriptor
|
|
3813
3940
|
);
|
|
3814
3941
|
const lease = decodeOwnerRecord(leaseRead.value.bytes);
|
|
3815
|
-
return descriptor.meshName === target.meshName
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
}
|
|
3829
|
-
|
|
3830
|
-
function conditionFor(
|
|
3831
|
-
key: ZLinkStoreKey,
|
|
3832
|
-
read: ZLinkStoreReadResult
|
|
3833
|
-
): ZLinkStoreCondition {
|
|
3942
|
+
return descriptor.meshName === target.meshName &&
|
|
3943
|
+
String(descriptor.rid) === String(target.nodeRid) &&
|
|
3944
|
+
descriptor.lifecycleGeneration === target.nodeLifecycleGeneration &&
|
|
3945
|
+
descriptor.ownerId === target.owner.ownerId &&
|
|
3946
|
+
descriptor.leaseGeneration === target.owner.leaseGeneration &&
|
|
3947
|
+
descriptor.objectRole === ZLinkObjectRole.Server &&
|
|
3948
|
+
descriptor.state === ZLinkFrameworkRuntimeState.Serving &&
|
|
3949
|
+
lease.ownerId === target.owner.ownerId &&
|
|
3950
|
+
BigInt(lease.leaseGeneration) === target.owner.leaseGeneration &&
|
|
3951
|
+
leaseRead.value.expiresAt !== undefined &&
|
|
3952
|
+
leaseRead.value.expiresAt.getTime() > leaseRead.value.storeNow.getTime()
|
|
3953
|
+
? descriptor
|
|
3954
|
+
: undefined;
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3957
|
+
function conditionFor(key: ZLinkStoreKey, read: ZLinkStoreReadResult): ZLinkStoreCondition {
|
|
3834
3958
|
return read.kind === 'missing'
|
|
3835
3959
|
? { kind: 'missing', key }
|
|
3836
3960
|
: { kind: 'version', key, expected: read.value.version };
|
|
3837
3961
|
}
|
|
3838
3962
|
|
|
3839
|
-
function versionCondition(
|
|
3840
|
-
key: ZLinkStoreKey,
|
|
3841
|
-
read: ZLinkStoreReadResult
|
|
3842
|
-
): ZLinkStoreCondition {
|
|
3963
|
+
function versionCondition(key: ZLinkStoreKey, read: ZLinkStoreReadResult): ZLinkStoreCondition {
|
|
3843
3964
|
if (read.kind === 'missing') {
|
|
3844
3965
|
throw new Error(`Required provider row '${key}' is missing.`);
|
|
3845
3966
|
}
|
|
@@ -3859,24 +3980,30 @@ function capacityAvailable(
|
|
|
3859
3980
|
capacity: CapacityRecord
|
|
3860
3981
|
): boolean {
|
|
3861
3982
|
const spotType = requested.spotType;
|
|
3862
|
-
const typeCapacity =
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3983
|
+
const typeCapacity =
|
|
3984
|
+
spotType === undefined
|
|
3985
|
+
? undefined
|
|
3986
|
+
: descriptor.populationCapacity.spotTypes.find(
|
|
3987
|
+
(candidate) =>
|
|
3988
|
+
candidate.objectKind === spotType.objectKind &&
|
|
3989
|
+
candidate.stableType === spotType.stableType
|
|
3990
|
+
);
|
|
3867
3991
|
const typeKey = spotType === undefined ? undefined : capacityTypeKey(spotType);
|
|
3868
|
-
return (
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3992
|
+
return (
|
|
3993
|
+
(descriptor.populationCapacity.actors.limit === 0 ||
|
|
3994
|
+
capacity.active.actors + capacity.pending.actors + requested.actors <=
|
|
3995
|
+
descriptor.populationCapacity.actors.limit) &&
|
|
3996
|
+
(descriptor.populationCapacity.spots.limit === 0 ||
|
|
3997
|
+
capacity.active.spots + capacity.pending.spots + requested.spots <=
|
|
3998
|
+
descriptor.populationCapacity.spots.limit) &&
|
|
3999
|
+
(spotType === undefined ||
|
|
4000
|
+
(typeCapacity !== undefined &&
|
|
4001
|
+
(typeCapacity.limit === 0 ||
|
|
4002
|
+
(capacity.active.spotTypes[typeKey!] ?? 0) +
|
|
4003
|
+
(capacity.pending.spotTypes[typeKey!] ?? 0) +
|
|
4004
|
+
spotType.count <=
|
|
4005
|
+
typeCapacity.limit)))
|
|
4006
|
+
);
|
|
3880
4007
|
}
|
|
3881
4008
|
|
|
3882
4009
|
function addCapacity(usage: CapacityUsage, delta: ZLinkCapacityVector): CapacityUsage {
|
|
@@ -3906,9 +4033,7 @@ function adjustCapacity(
|
|
|
3906
4033
|
return { actors, spots, spotTypes };
|
|
3907
4034
|
}
|
|
3908
4035
|
|
|
3909
|
-
function capacityTypeKey(
|
|
3910
|
-
value: NonNullable<ZLinkCapacityVector['spotType']>
|
|
3911
|
-
): string {
|
|
4036
|
+
function capacityTypeKey(value: NonNullable<ZLinkCapacityVector['spotType']>): string {
|
|
3912
4037
|
return `${value.objectKind}\0${value.stableType}`;
|
|
3913
4038
|
}
|
|
3914
4039
|
|
|
@@ -3921,46 +4046,42 @@ function cloneCapacity(value: ZLinkCapacityVector): ZLinkCapacityVector {
|
|
|
3921
4046
|
}
|
|
3922
4047
|
|
|
3923
4048
|
function sameCapacityVector(left: ZLinkCapacityVector, right: ZLinkCapacityVector): boolean {
|
|
3924
|
-
return
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4049
|
+
return (
|
|
4050
|
+
left.actors === right.actors &&
|
|
4051
|
+
left.spots === right.spots &&
|
|
4052
|
+
left.spotType?.objectKind === right.spotType?.objectKind &&
|
|
4053
|
+
left.spotType?.stableType === right.spotType?.stableType &&
|
|
4054
|
+
left.spotType?.count === right.spotType?.count
|
|
4055
|
+
);
|
|
3929
4056
|
}
|
|
3930
4057
|
|
|
3931
4058
|
function sameCreationTarget(
|
|
3932
4059
|
snapshot: StoredAuthoritySnapshot,
|
|
3933
4060
|
target: ZLinkObjectCommitRequest['target']
|
|
3934
4061
|
): boolean {
|
|
3935
|
-
return
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
4062
|
+
return (
|
|
4063
|
+
snapshot.allocation.descriptor.meshName === target.meshName &&
|
|
4064
|
+
String(snapshot.allocation.descriptor.rid) === String(target.nodeRid) &&
|
|
4065
|
+
snapshot.allocation.descriptorLifecycleGeneration === target.nodeLifecycleGeneration &&
|
|
4066
|
+
snapshot.ownerId === target.owner.ownerId &&
|
|
4067
|
+
snapshot.ownerLeaseGeneration === target.owner.leaseGeneration
|
|
4068
|
+
);
|
|
3941
4069
|
}
|
|
3942
4070
|
|
|
3943
4071
|
const CANONICAL_AUTHORITY_MAGIC = Buffer.from('ZLAU');
|
|
3944
4072
|
|
|
3945
4073
|
function isCanonicalAuthorityPayload(payload: Uint8Array): boolean {
|
|
3946
|
-
return
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
4074
|
+
return (
|
|
4075
|
+
payload.byteLength >= CANONICAL_AUTHORITY_MAGIC.byteLength &&
|
|
4076
|
+
Buffer.from(payload.buffer, payload.byteOffset, CANONICAL_AUTHORITY_MAGIC.byteLength).equals(
|
|
4077
|
+
CANONICAL_AUTHORITY_MAGIC
|
|
4078
|
+
)
|
|
4079
|
+
);
|
|
3952
4080
|
}
|
|
3953
4081
|
|
|
3954
|
-
function sameLiveOwner(
|
|
3955
|
-
lease: ZLinkStoreReadResult,
|
|
3956
|
-
snapshot: StoredAuthoritySnapshot
|
|
3957
|
-
): boolean {
|
|
4082
|
+
function sameLiveOwner(lease: ZLinkStoreReadResult, snapshot: StoredAuthoritySnapshot): boolean {
|
|
3958
4083
|
if (lease.kind === 'missing') return false;
|
|
3959
|
-
return matchesLiveOwnerLease(
|
|
3960
|
-
lease,
|
|
3961
|
-
snapshot.ownerId,
|
|
3962
|
-
snapshot.ownerLeaseGeneration
|
|
3963
|
-
);
|
|
4084
|
+
return matchesLiveOwnerLease(lease, snapshot.ownerId, snapshot.ownerLeaseGeneration);
|
|
3964
4085
|
}
|
|
3965
4086
|
|
|
3966
4087
|
function matchesLiveOwnerLease(
|
|
@@ -3968,13 +4089,14 @@ function matchesLiveOwnerLease(
|
|
|
3968
4089
|
ownerId: string,
|
|
3969
4090
|
leaseGeneration: bigint
|
|
3970
4091
|
): boolean {
|
|
3971
|
-
if (
|
|
3972
|
-
|
|
4092
|
+
if (
|
|
4093
|
+
lease.value.expiresAt === undefined ||
|
|
4094
|
+
lease.value.expiresAt.getTime() <= lease.value.storeNow.getTime()
|
|
4095
|
+
) {
|
|
3973
4096
|
return false;
|
|
3974
4097
|
}
|
|
3975
4098
|
const owner = decodeOwnerRecord(lease.value.bytes);
|
|
3976
|
-
return owner.ownerId === ownerId
|
|
3977
|
-
&& BigInt(owner.leaseGeneration) === leaseGeneration;
|
|
4099
|
+
return owner.ownerId === ownerId && BigInt(owner.leaseGeneration) === leaseGeneration;
|
|
3978
4100
|
}
|
|
3979
4101
|
|
|
3980
4102
|
function sameDescriptorPredecessor(
|
|
@@ -3987,12 +4109,12 @@ function sameDescriptorPredecessor(
|
|
|
3987
4109
|
return Buffer.from(expected.value.bytes).equals(Buffer.from(current.value.bytes));
|
|
3988
4110
|
}
|
|
3989
4111
|
|
|
3990
|
-
function liveOwnerLeaseGeneration(
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
4112
|
+
function liveOwnerLeaseGeneration(lease: ZLinkStoreReadResult): bigint | undefined {
|
|
4113
|
+
if (
|
|
4114
|
+
lease.kind === 'missing' ||
|
|
4115
|
+
lease.value.expiresAt === undefined ||
|
|
4116
|
+
lease.value.expiresAt.getTime() <= lease.value.storeNow.getTime()
|
|
4117
|
+
) {
|
|
3996
4118
|
return undefined;
|
|
3997
4119
|
}
|
|
3998
4120
|
return BigInt(decodeOwnerRecord(lease.value.bytes).leaseGeneration);
|
|
@@ -4015,9 +4137,11 @@ function canTakeOverStoredLocation(
|
|
|
4015
4137
|
return false;
|
|
4016
4138
|
}
|
|
4017
4139
|
if (liveLeaseGeneration === undefined) return true;
|
|
4018
|
-
return
|
|
4019
|
-
|
|
4020
|
-
|
|
4140
|
+
return (
|
|
4141
|
+
currentOwnerId === nextOwnerId &&
|
|
4142
|
+
currentLeaseGeneration !== nextLeaseGeneration &&
|
|
4143
|
+
liveLeaseGeneration === nextLeaseGeneration
|
|
4144
|
+
);
|
|
4021
4145
|
}
|
|
4022
4146
|
|
|
4023
4147
|
function persistMeshDescriptor(descriptor: ZLinkMeshNodeDescriptor): ZLinkMeshNodeDescriptor {
|
|
@@ -4035,15 +4159,18 @@ function reviveMeshDescriptor(descriptor: ZLinkMeshNodeDescriptor): ZLinkMeshNod
|
|
|
4035
4159
|
descriptorRevision: BigInt(String(value.descriptorRevision)),
|
|
4036
4160
|
endpoint: String(value.endpoint),
|
|
4037
4161
|
objectRole: value.objectRole as ZLinkObjectRole,
|
|
4038
|
-
entrySpotId:
|
|
4162
|
+
entrySpotId:
|
|
4163
|
+
value.entrySpotId === null ? undefined : (value.entrySpotId as string | undefined),
|
|
4039
4164
|
placementWeight: Number(value.placementWeight),
|
|
4040
4165
|
populationCapacity: reviveCanonicalCapacity(capacity),
|
|
4041
|
-
activationConcurrency:
|
|
4166
|
+
activationConcurrency:
|
|
4167
|
+
value.activationConcurrency as ZLinkMeshNodeDescriptor['activationConcurrency'],
|
|
4042
4168
|
channelWeights: value.channelWeights as Readonly<Record<string, number>>,
|
|
4043
4169
|
applicationVersion: BigInt(String(value.applicationVersion)),
|
|
4044
4170
|
spotTypes: [],
|
|
4045
4171
|
objectCapabilities: reviveCanonicalCapabilities(value.objectCapabilities),
|
|
4046
|
-
maintenanceWave:
|
|
4172
|
+
maintenanceWave:
|
|
4173
|
+
value.maintenanceWave === null ? undefined : (value.maintenanceWave as string | undefined),
|
|
4047
4174
|
state: runtimeState(String(value.state)),
|
|
4048
4175
|
securityIdentity: String(value.securityIdentity),
|
|
4049
4176
|
ownerId: String(value.ownerId),
|
|
@@ -4054,9 +4181,8 @@ function reviveMeshDescriptor(descriptor: ZLinkMeshNodeDescriptor): ZLinkMeshNod
|
|
|
4054
4181
|
return {
|
|
4055
4182
|
...descriptor,
|
|
4056
4183
|
rid: descriptor.rid,
|
|
4057
|
-
updatedAt:
|
|
4058
|
-
? descriptor.updatedAt
|
|
4059
|
-
: new Date(descriptor.updatedAt)
|
|
4184
|
+
updatedAt:
|
|
4185
|
+
descriptor.updatedAt instanceof Date ? descriptor.updatedAt : new Date(descriptor.updatedAt)
|
|
4060
4186
|
};
|
|
4061
4187
|
}
|
|
4062
4188
|
|
|
@@ -4093,7 +4219,10 @@ function reviveFanoutDescriptor(
|
|
|
4093
4219
|
if ('publisherRoutingIdHex' in value) {
|
|
4094
4220
|
return {
|
|
4095
4221
|
channelName: String(value.channelName),
|
|
4096
|
-
publisherRid: decodeRoutingId(
|
|
4222
|
+
publisherRid: decodeRoutingId(
|
|
4223
|
+
String(value.publisherRoutingIdHex),
|
|
4224
|
+
value.publisherRoutingIdHex
|
|
4225
|
+
),
|
|
4097
4226
|
lifecycleGeneration: BigInt(String(value.lifecycleGeneration)),
|
|
4098
4227
|
descriptorRevision: BigInt(String(value.descriptorRevision)),
|
|
4099
4228
|
endpoint: String(value.endpoint),
|
|
@@ -4147,24 +4276,30 @@ function normalizeRoute(value: ZLinkRouteLocation): ZLinkRouteLocation {
|
|
|
4147
4276
|
}
|
|
4148
4277
|
|
|
4149
4278
|
function matchesSpot(value: ZLinkSpotLocation, filter: ZLinkSpotLocationFilter): boolean {
|
|
4150
|
-
return (
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4279
|
+
return (
|
|
4280
|
+
(filter.meshName === undefined || value.meshName === filter.meshName) &&
|
|
4281
|
+
(filter.spotType === undefined || value.spotType === filter.spotType) &&
|
|
4282
|
+
(filter.nodeRid === undefined || String(value.ownerNodeRid) === String(filter.nodeRid)) &&
|
|
4283
|
+
(filter.spotKind === undefined || value.spotKind === filter.spotKind)
|
|
4284
|
+
);
|
|
4154
4285
|
}
|
|
4155
4286
|
|
|
4156
4287
|
function matchesActor(value: ZLinkActorLocation, filter: ZLinkActorLocationFilter): boolean {
|
|
4157
|
-
return (
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4288
|
+
return (
|
|
4289
|
+
(filter.actorType === undefined || value.actorType === filter.actorType) &&
|
|
4290
|
+
(filter.nodeRid === undefined || String(value.ownerNodeRid) === String(filter.nodeRid)) &&
|
|
4291
|
+
(filter.spotId === undefined || String(value.spotId) === String(filter.spotId)) &&
|
|
4292
|
+
(filter.locationKind === undefined || value.spotKind === filter.locationKind)
|
|
4293
|
+
);
|
|
4161
4294
|
}
|
|
4162
4295
|
|
|
4163
4296
|
function matchesRoute(value: ZLinkRouteLocation, filter: ZLinkRouteLocationFilter): boolean {
|
|
4164
|
-
return (
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4297
|
+
return (
|
|
4298
|
+
(filter.routeKind === undefined || value.routeKind === filter.routeKind) &&
|
|
4299
|
+
(filter.ownerNodeRid === undefined ||
|
|
4300
|
+
String(value.ownerNodeRid) === String(filter.ownerNodeRid)) &&
|
|
4301
|
+
(filter.ownerId === undefined || value.ownerId === filter.ownerId)
|
|
4302
|
+
);
|
|
4168
4303
|
}
|
|
4169
4304
|
|
|
4170
4305
|
function locationOwnerGeneration(value: LeaseOwnedLocation): bigint {
|
|
@@ -4178,9 +4313,7 @@ function descriptorOwnerLeaseGeneration(value: OwnedStoreRecord): bigint | undef
|
|
|
4178
4313
|
: BigInt(value.leaseGeneration);
|
|
4179
4314
|
}
|
|
4180
4315
|
if ('generation' in value) {
|
|
4181
|
-
return typeof value.generation === 'bigint'
|
|
4182
|
-
? value.generation
|
|
4183
|
-
: BigInt(value.generation);
|
|
4316
|
+
return typeof value.generation === 'bigint' ? value.generation : BigInt(value.generation);
|
|
4184
4317
|
}
|
|
4185
4318
|
return undefined;
|
|
4186
4319
|
}
|
|
@@ -4190,7 +4323,8 @@ function sameMeshDescriptor(
|
|
|
4190
4323
|
right: ZLinkMeshNodeDescriptor
|
|
4191
4324
|
): boolean {
|
|
4192
4325
|
return Buffer.from(encodeJson(persistMeshDescriptor(left))).equals(
|
|
4193
|
-
Buffer.from(encodeJson(persistMeshDescriptor(right)))
|
|
4326
|
+
Buffer.from(encodeJson(persistMeshDescriptor(right)))
|
|
4327
|
+
);
|
|
4194
4328
|
}
|
|
4195
4329
|
|
|
4196
4330
|
function sameClientServerDescriptor(
|
|
@@ -4207,44 +4341,43 @@ function sameFanoutDescriptor(
|
|
|
4207
4341
|
return descriptorFingerprint(left, ['updatedAt']) === descriptorFingerprint(right, ['updatedAt']);
|
|
4208
4342
|
}
|
|
4209
4343
|
|
|
4210
|
-
function canRenewMesh(
|
|
4211
|
-
current
|
|
4212
|
-
next: ZLinkMeshNodeDescriptor
|
|
4213
|
-
): boolean {
|
|
4214
|
-
return sameOwnerGeneration(current, next)
|
|
4215
|
-
&& next.descriptorRevision > current.descriptorRevision;
|
|
4344
|
+
function canRenewMesh(current: ZLinkMeshNodeDescriptor, next: ZLinkMeshNodeDescriptor): boolean {
|
|
4345
|
+
return sameOwnerGeneration(current, next) && next.descriptorRevision > current.descriptorRevision;
|
|
4216
4346
|
}
|
|
4217
4347
|
|
|
4218
4348
|
function canRenewClientServer(
|
|
4219
4349
|
current: ZLinkClientServerServerDescriptor,
|
|
4220
4350
|
next: ZLinkClientServerServerDescriptor
|
|
4221
4351
|
): boolean {
|
|
4222
|
-
return
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4352
|
+
return (
|
|
4353
|
+
sameOwnerGeneration(current, next) &&
|
|
4354
|
+
next.descriptorRevision > current.descriptorRevision &&
|
|
4355
|
+
descriptorFingerprint(current, ['descriptorRevision', 'weight', 'state', 'updatedAt']) ===
|
|
4356
|
+
descriptorFingerprint(next, ['descriptorRevision', 'weight', 'state', 'updatedAt'])
|
|
4357
|
+
);
|
|
4226
4358
|
}
|
|
4227
4359
|
|
|
4228
4360
|
function canRenewFanout(
|
|
4229
4361
|
current: ZLinkFanoutPublisherDescriptor,
|
|
4230
4362
|
next: ZLinkFanoutPublisherDescriptor
|
|
4231
4363
|
): boolean {
|
|
4232
|
-
return
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4364
|
+
return (
|
|
4365
|
+
sameOwnerGeneration(current, next) &&
|
|
4366
|
+
next.descriptorRevision > current.descriptorRevision &&
|
|
4367
|
+
descriptorFingerprint(current, ['descriptorRevision', 'state', 'updatedAt']) ===
|
|
4368
|
+
descriptorFingerprint(next, ['descriptorRevision', 'state', 'updatedAt'])
|
|
4369
|
+
);
|
|
4236
4370
|
}
|
|
4237
4371
|
|
|
4238
4372
|
function sameOwnerGeneration(left: OwnedDescriptor, right: OwnedDescriptor): boolean {
|
|
4239
|
-
return
|
|
4240
|
-
|
|
4241
|
-
|
|
4373
|
+
return (
|
|
4374
|
+
left.ownerId === right.ownerId &&
|
|
4375
|
+
left.leaseGeneration === right.leaseGeneration &&
|
|
4376
|
+
left.lifecycleGeneration === right.lifecycleGeneration
|
|
4377
|
+
);
|
|
4242
4378
|
}
|
|
4243
4379
|
|
|
4244
|
-
function descriptorFingerprint(
|
|
4245
|
-
descriptor: OwnedDescriptor,
|
|
4246
|
-
omitted: readonly string[]
|
|
4247
|
-
): string {
|
|
4380
|
+
function descriptorFingerprint(descriptor: OwnedDescriptor, omitted: readonly string[]): string {
|
|
4248
4381
|
// Descriptor rows are canonicalized through routing-id hex. Comparing the
|
|
4249
4382
|
// in-memory shape here would distinguish a binding RoutingId instance from
|
|
4250
4383
|
// the equivalent ID revived from a canonical row, incorrectly rejecting a
|
|
@@ -4257,13 +4390,16 @@ function descriptorFingerprint(
|
|
|
4257
4390
|
}
|
|
4258
4391
|
|
|
4259
4392
|
function validateClientServerDescriptor(descriptor: ZLinkClientServerServerDescriptor): void {
|
|
4260
|
-
validateDescriptorIdentity(
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4393
|
+
validateDescriptorIdentity(
|
|
4394
|
+
[
|
|
4395
|
+
descriptor.channelName,
|
|
4396
|
+
String(descriptor.serverRid),
|
|
4397
|
+
descriptor.endpoint,
|
|
4398
|
+
descriptor.securityIdentity,
|
|
4399
|
+
descriptor.ownerId
|
|
4400
|
+
],
|
|
4401
|
+
'ClientServer'
|
|
4402
|
+
);
|
|
4267
4403
|
validateDescriptorGenerations(descriptor, 'ClientServer');
|
|
4268
4404
|
if (!Number.isInteger(descriptor.weight) || descriptor.weight < 0 || descriptor.weight > 10_000) {
|
|
4269
4405
|
throw new RangeError('ClientServer descriptor weight must be an integer in 0..10000.');
|
|
@@ -4271,26 +4407,34 @@ function validateClientServerDescriptor(descriptor: ZLinkClientServerServerDescr
|
|
|
4271
4407
|
}
|
|
4272
4408
|
|
|
4273
4409
|
function validateFanoutPublisherDescriptor(descriptor: ZLinkFanoutPublisherDescriptor): void {
|
|
4274
|
-
validateDescriptorIdentity(
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4410
|
+
validateDescriptorIdentity(
|
|
4411
|
+
[
|
|
4412
|
+
descriptor.channelName,
|
|
4413
|
+
String(descriptor.publisherRid),
|
|
4414
|
+
descriptor.endpoint,
|
|
4415
|
+
descriptor.securityIdentity,
|
|
4416
|
+
descriptor.ownerId
|
|
4417
|
+
],
|
|
4418
|
+
'Fanout publisher'
|
|
4419
|
+
);
|
|
4281
4420
|
validateDescriptorGenerations(descriptor, 'Fanout publisher');
|
|
4282
4421
|
}
|
|
4283
4422
|
|
|
4284
4423
|
function validateDescriptorIdentity(values: readonly string[], kind: string): void {
|
|
4285
|
-
if (values.some(value => Buffer.byteLength(value, 'utf8') < 1 || value.includes('\0'))) {
|
|
4424
|
+
if (values.some((value) => Buffer.byteLength(value, 'utf8') < 1 || value.includes('\0'))) {
|
|
4286
4425
|
throw new TypeError(`${kind} descriptor identity and endpoint are required.`);
|
|
4287
4426
|
}
|
|
4288
4427
|
}
|
|
4289
4428
|
|
|
4290
4429
|
function validateDescriptorGenerations(descriptor: OwnedDescriptor, kind: string): void {
|
|
4291
|
-
if (
|
|
4292
|
-
|
|
4293
|
-
|
|
4430
|
+
if (
|
|
4431
|
+
descriptor.lifecycleGeneration < 1n ||
|
|
4432
|
+
descriptor.lifecycleGeneration > MAX_GENERATION ||
|
|
4433
|
+
descriptor.descriptorRevision < 1n ||
|
|
4434
|
+
descriptor.descriptorRevision > MAX_GENERATION ||
|
|
4435
|
+
descriptor.leaseGeneration < 1n ||
|
|
4436
|
+
descriptor.leaseGeneration > MAX_GENERATION
|
|
4437
|
+
) {
|
|
4294
4438
|
throw new RangeError(`${kind} descriptor generations are invalid.`);
|
|
4295
4439
|
}
|
|
4296
4440
|
}
|