@zlink-systems/framework 0.29.0 → 0.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/runtime/actors/actor-runtime-contracts.d.ts +1 -0
  3. package/dist/runtime/actors/index.d.ts +2 -0
  4. package/dist/runtime/actors/index.js +21 -6
  5. package/dist/runtime/foundation/service-stateful-runtime.js +27 -19
  6. package/dist/runtime/handlers/handler-instance-scope.js +1 -1
  7. package/dist/runtime/host/actor-placement-coordinator.d.ts +2 -1
  8. package/dist/runtime/host/actor-placement-coordinator.js +2 -1
  9. package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -1
  10. package/dist/runtime/host/actor-runtime-options-factory.js +1 -0
  11. package/dist/runtime/host/index.d.ts +1 -1
  12. package/dist/runtime/host/index.js +3 -2
  13. package/dist/runtime/locations/domain-store-contract.d.ts +2 -1
  14. package/dist/runtime/locations/in-memory-authority-store.d.ts +1 -0
  15. package/dist/runtime/locations/in-memory-authority-store.js +32 -11
  16. package/dist/runtime/locations/in-memory-location-store.d.ts +1 -1
  17. package/dist/runtime/locations/in-memory-location-store.js +12 -10
  18. package/dist/runtime/locations/internal-location-contracts.d.ts +5 -0
  19. package/dist/runtime/locations/location-store-repository.d.ts +1 -0
  20. package/dist/runtime/locations/location-store-repository.js +54 -21
  21. package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
  22. package/dist/runtime/spots/spot-entry-activation.js +3 -0
  23. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +1 -0
  24. package/dist/runtime/spots/spot-node-runtime-manager.js +6 -0
  25. package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
  26. package/dist/runtime/spots/spot-serial-executor.js +10 -1
  27. package/package.json +2 -2
  28. package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
  29. package/src/runtime/actors/index.ts +77 -42
  30. package/src/runtime/foundation/service-stateful-runtime.ts +49 -43
  31. package/src/runtime/handlers/handler-instance-scope.ts +2 -1
  32. package/src/runtime/host/actor-placement-coordinator.ts +8 -2
  33. package/src/runtime/host/actor-runtime-options-factory.ts +6 -0
  34. package/src/runtime/host/index.ts +6 -2
  35. package/src/runtime/locations/domain-store-contract.ts +1 -1
  36. package/src/runtime/locations/in-memory-authority-store.ts +47 -10
  37. package/src/runtime/locations/in-memory-location-store.ts +14 -10
  38. package/src/runtime/locations/internal-location-contracts.ts +6 -0
  39. package/src/runtime/locations/location-store-repository.ts +82 -28
  40. package/src/runtime/spots/spot-entry-activation.ts +4 -0
  41. package/src/runtime/spots/spot-node-runtime-manager.ts +7 -0
  42. package/src/runtime/spots/spot-serial-executor.ts +10 -4
@@ -27,6 +27,7 @@ export interface ZLinkActorManagerOptions {
27
27
  readonly actorRefResolver?: ZLinkActorRuntimeLocationLookup;
28
28
  readonly actorCreatedNotifier?: (nodeRid: RoutingId, actor: ZLinkActor, createRequest: ZLinkMessage, signal?: AbortSignal) => Promise<ZLinkActorCreateResponse | undefined>;
29
29
  readonly actorDestroyedCleanup?: (actorId: string) => void;
30
+ readonly terminateActorActivation?: (actor: ZLinkActor, terminal: () => Promise<void>) => Promise<void>;
30
31
  readonly publishActorAuthority?: (actorType: string, actorRef: ActorRef, ownerNodeGeneration: bigint, signal?: AbortSignal) => Promise<void>;
31
32
  readonly locationLifecycle?: ZLinkLocationLifecycle;
32
33
  readonly boundSessionFactory?: ZLinkActorBoundSessionFactory;
@@ -55,6 +55,8 @@ export declare class DefaultZLinkActorManager implements ZLinkActorManager {
55
55
  completeCoreRelocationSource(actorId: string): Promise<void>;
56
56
  private finalizeActorState;
57
57
  private finalizeDestroyedActor;
58
+ private terminateActor;
59
+ private awaitActorDestroy;
58
60
  getOrCreateActor(actorId: string, actorType: string, signal?: AbortSignal): Promise<ZLinkActor>;
59
61
  getOrCreateWithNativeRef(actorId: string, actorType: string, actorRef: ZLinkBackendActorRef, createRequest?: unknown, signal?: AbortSignal): Promise<ZLinkActor>;
60
62
  getOrCreateActorResult(actorId: string, actorType: string, createRequest?: unknown, signal?: AbortSignal): Promise<ZLinkActorLocalCreateResult>;
@@ -11,6 +11,7 @@ const runtime_metrics_1 = require("../diagnostics/runtime-metrics");
11
11
  const execution_1 = require("../execution");
12
12
  const framework_errors_internal_1 = require("../framework-errors-internal");
13
13
  const handler_instance_scope_1 = require("../handlers/handler-instance-scope");
14
+ const actor_execution_context_1 = require("./actor-execution-context");
14
15
  const payload_codec_1 = require("../messaging/payload-codec");
15
16
  const actor_creation_1 = require("./actor-creation");
16
17
  const actor_runtime_state_1 = require("./actor-runtime-state");
@@ -139,7 +140,7 @@ class DefaultZLinkActorManager {
139
140
  const entryNodeRid = state.entryNodeRid ?? current.nodeRid;
140
141
  if (node === undefined || completions === undefined)
141
142
  return false;
142
- const destroyTask = state.getOrStartDestroy(entryNodeRid, async (nativeRef) => {
143
+ const destroyTask = state.getOrStartDestroy(entryNodeRid, (nativeRef) => this.terminateActor(state, async () => {
143
144
  if (nativeRef !== undefined) {
144
145
  const completion = await completions.submit(() => node.destroyActor(nativeRef, 0), signal);
145
146
  try {
@@ -161,9 +162,9 @@ class DefaultZLinkActorManager {
161
162
  await this.options.locationLifecycle?.releaseActor(state.actorType, actor.actorId, current);
162
163
  }
163
164
  await this.finalizeDestroyedActor(actor.actorId, state);
164
- });
165
+ }));
165
166
  try {
166
- await destroyTask;
167
+ await this.awaitActorDestroy(state, destroyTask);
167
168
  }
168
169
  catch (error) {
169
170
  state.clearFailedDestroy(destroyTask);
@@ -381,6 +382,20 @@ class DefaultZLinkActorManager {
381
382
  this.options.metrics?.change(runtime_metrics_1.METRIC_NAMES.ActorCount, -1);
382
383
  }
383
384
  }
385
+ terminateActor(state, terminal) {
386
+ const actor = state.actor;
387
+ const completion = actor === undefined || this.options.terminateActorActivation === undefined
388
+ ? terminal()
389
+ : this.options.terminateActorActivation(actor, terminal);
390
+ return completion;
391
+ }
392
+ awaitActorDestroy(state, completion) {
393
+ if ((0, actor_execution_context_1.currentZLinkActorExecution)()?.actorId === state.actorId) {
394
+ this.detachedTaskRunner.runDetached('Actor terminal cleanup', () => completion);
395
+ return Promise.resolve();
396
+ }
397
+ return completion;
398
+ }
384
399
  async getOrCreateActor(actorId, actorType, signal) {
385
400
  this.rememberActorMeshFromType(actorId, actorType);
386
401
  const result = await this.createOrGet(actorId, actorType, false, undefined, signal);
@@ -429,7 +444,7 @@ class DefaultZLinkActorManager {
429
444
  if (state.isJoined) {
430
445
  throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorRouteNotFound, `Actor '${actor.context.actorId}' must leave its current SPOT before destroy.`);
431
446
  }
432
- const destroyTask = state.getOrStartDestroy(entryNodeRid, async (actorRef) => {
447
+ const destroyTask = state.getOrStartDestroy(entryNodeRid, (actorRef) => this.terminateActor(state, async () => {
433
448
  const destroyedActorRef = actorRef === undefined ? undefined : (0, actor_runtime_state_1.toFrameworkActorRef)(actorRef, state.meshName ?? '');
434
449
  if (actorRef !== undefined) {
435
450
  await node.destroyActor(actorRef, 0, destroySignal);
@@ -439,9 +454,9 @@ class DefaultZLinkActorManager {
439
454
  await this.options.locationLifecycle?.releaseActor(state.actorType, actor.context.actorId, destroyedActorRef);
440
455
  }
441
456
  await this.finalizeDestroyedActor(actor.context.actorId, state);
442
- });
457
+ }));
443
458
  try {
444
- await destroyTask;
459
+ await this.awaitActorDestroy(state, destroyTask);
445
460
  }
446
461
  catch (error) {
447
462
  state.clearFailedDestroy(destroyTask);
@@ -754,7 +754,7 @@ class ServiceStatefulRuntime {
754
754
  const timeoutMs = remainingDeadlineMs(deadlineUnixMs);
755
755
  const pending = this.operations.register(timeoutMs);
756
756
  this.submitRequest(pending, target.targetNodeRid, instanceOperationParts([
757
- (0, service_stateful_wire_codec_1.encodeInstanceSpotActivationHeader)(target, this.nodeGeneration, this.nodeRid, sourceSpotId, 'request', { high: this.nodeGeneration, low: pending.id }, deadlineUnixMs, pending.id, metadataFrame !== undefined),
757
+ (0, service_stateful_wire_codec_1.encodeInstanceSpotActivationHeader)(target, this.nodeGeneration, this.nodeRid, sourceSpotId, 'request', { high: operation_identity_1.ZLINK_NATIVE_CORRELATION_OPERATION_NAMESPACE, low: pending.id }, deadlineUnixMs, pending.id, metadataFrame !== undefined),
758
758
  payloadFrame
759
759
  ], metadataFrame), timeoutMs, 'instanceSpotRequest');
760
760
  return pending;
@@ -1436,14 +1436,15 @@ class ServiceStatefulRuntime {
1436
1436
  async continueMissingInstanceActivation(ingress, record, payloadFrame, localReply, metadataFrame) {
1437
1437
  try {
1438
1438
  this.validateInstanceIngress(ingress, record);
1439
- const activation = await this.activateMissingInstanceAsync(record, payloadFrame, metadataFrame);
1440
- if (this.closed)
1441
- return;
1442
- const terminal = this.activationTerminalCompletion(record.target, activation.route);
1443
- const admitted = this.enqueueActivatedInstanceSpot(ingress, record, payloadFrame, activation.spot, localReply, terminal.onTerminalCompletion, metadataFrame, terminal.onHandlerTurnStarted, this.instanceApplicationTarget(record, activation.route.objectGeneration));
1444
- if (admitted !== 'application') {
1445
- throw new Error('Activated Instance message was not admitted to the local queue.');
1446
- }
1439
+ await this.activateMissingInstanceAsync(record, payloadFrame, metadataFrame, (activation) => {
1440
+ if (this.closed)
1441
+ return;
1442
+ const terminal = this.activationTerminalCompletion(record.target, activation.route);
1443
+ const admitted = this.enqueueActivatedInstanceSpot(ingress, record, payloadFrame, activation.spot, localReply, terminal.onTerminalCompletion, metadataFrame, terminal.onHandlerTurnStarted, this.instanceApplicationTarget(record, activation.route.objectGeneration));
1444
+ if (admitted !== 'application') {
1445
+ throw new Error('Activated Instance message was not admitted to the local queue.');
1446
+ }
1447
+ });
1447
1448
  }
1448
1449
  catch (error) {
1449
1450
  this.finishMissingInstanceActivationFailure(ingress, record, error, localReply);
@@ -1738,12 +1739,23 @@ class ServiceStatefulRuntime {
1738
1739
  }
1739
1740
  return activation.spot;
1740
1741
  }
1741
- activateMissingInstanceAsync(record, payloadFrame, metadataFrame) {
1742
- const key = instanceActivationKey(record);
1742
+ activateMissingInstanceAsync(record, payloadFrame, metadataFrame, admit) {
1743
+ const key = record.target.targetSpotId;
1743
1744
  const pending = this.pendingInstanceActivations.get(key);
1744
- if (pending !== undefined)
1745
- return pending;
1746
- const activation = this.runMissingInstanceActivation(record, payloadFrame, metadataFrame);
1745
+ const ready = pending ??
1746
+ this.runMissingInstanceActivation(record, payloadFrame, metadataFrame).then((activation) => ({
1747
+ ...activation,
1748
+ stableType: record.target.stableType
1749
+ }));
1750
+ const operation = ready.then((value) => {
1751
+ if (value.stableType !== record.target.stableType) {
1752
+ throw new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.TypeMismatch, 'Instance Spot type does not match.');
1753
+ }
1754
+ admit(value);
1755
+ return value;
1756
+ });
1757
+ // The caller owns operation failure. Keep the activation available for later participants.
1758
+ const activation = operation.then((value) => value, () => ready);
1747
1759
  this.pendingInstanceActivations.set(key, activation);
1748
1760
  const clear = () => {
1749
1761
  if (this.pendingInstanceActivations.get(key) === activation) {
@@ -1751,7 +1763,7 @@ class ServiceStatefulRuntime {
1751
1763
  }
1752
1764
  };
1753
1765
  void activation.then(clear, clear);
1754
- return activation;
1766
+ return operation;
1755
1767
  }
1756
1768
  async runMissingInstanceActivation(record, payloadFrame, metadataFrame) {
1757
1769
  const target = record.target;
@@ -3855,10 +3867,6 @@ function instanceOperationKey(record) {
3855
3867
  return (`${record.sourceNodeRid}\0${record.sourceNodeGeneration}\0` +
3856
3868
  `${record.operation.high}\0${record.operation.low}`);
3857
3869
  }
3858
- function instanceActivationKey(record) {
3859
- return (`${record.target.targetNodeRid}\0${record.target.targetNodeGeneration}\0` +
3860
- `${record.target.targetSpotId}\0${record.target.stableType}`);
3861
- }
3862
3870
  function subscriptionKey(channelName, topicFilter) {
3863
3871
  if (channelName.length === 0 || topicFilter.length === 0) {
3864
3872
  throw new TypeError('Channel name and topic filter must be non-empty.');
@@ -158,7 +158,7 @@ class LifecycleHandlerInstanceScope {
158
158
  }
159
159
  }
160
160
  async dispose(detachedTaskRunner) {
161
- const disposeFromActiveInvocation = activeLifecycleScope.getStore() === this;
161
+ const disposeFromActiveInvocation = this.activeInvocations > 0 && activeLifecycleScope.getStore() === this;
162
162
  const disposal = this.beginDisposeCore();
163
163
  if (disposal.deferred !== undefined) {
164
164
  startOutsideLifecycleInvocation(() => {
@@ -1,5 +1,5 @@
1
1
  import type { ActorRef, RoutingId, ZLinkActorCreateResult } from '../../contracts';
2
- import type { ZLinkAuthoritySnapshot, ZLinkLocationOwnerToken } from '../locations/internal-location-contracts';
2
+ import type { ZLinkAuthoritySnapshot, ZLinkLocationOwnerToken, ZLinkObjectReserveRequest } from '../locations/internal-location-contracts';
3
3
  import type { ZLinkAuthorityStore, ZLinkObjectCreationStore } from '../locations/internal-store-contracts';
4
4
  import type { ServiceActorCreateRecord } from '../foundation/service-stateful-wire-codec';
5
5
  import type { ServiceUserSpotOperationResult } from '../foundation/service-stateful-runtime';
@@ -14,6 +14,7 @@ export interface ZLinkActorPlacementTarget {
14
14
  readonly sourceNodeGeneration: bigint;
15
15
  }
16
16
  export interface ZLinkActorPlacementCoordinatorOptions {
17
+ readonly actorRelocationPolicy?: (meshName: string, stableType: string) => ZLinkObjectReserveRequest['actorRelocationPolicy'];
17
18
  readonly store: ZLinkObjectCreationStore & ZLinkAuthorityStore;
18
19
  readonly target: (meshName: string | undefined, stableType: string, signal?: AbortSignal, excludedNodeRids?: ReadonlySet<string>) => Promise<ZLinkActorPlacementTarget | undefined>;
19
20
  readonly remoteCreate: (meshName: string, targetNodeRid: string, request: Omit<ServiceActorCreateRecord, 'kind' | 'correlation'>, timeoutMs: number) => Promise<ServiceUserSpotOperationResult>;
@@ -61,7 +61,8 @@ class ZLinkActorPlacementCoordinator {
61
61
  },
62
62
  target: reservationTarget(target),
63
63
  creatingPayload,
64
- capacity: { actors: 1, spots: 0 }
64
+ capacity: { actors: 1, spots: 0 },
65
+ actorRelocationPolicy: this.options.actorRelocationPolicy?.(target.meshName, stableType)
65
66
  }, deadline.signal, BigInt(deadlineUnixMs));
66
67
  const existing = existingActor(reserved, actorId, stableType);
67
68
  if (existing !== undefined) {
@@ -15,6 +15,7 @@ import type { ZLinkRuntimeAdmissionGate } from '../admission';
15
15
  import type { ZLinkRemoteActorPacketTarget } from '../actors';
16
16
  import type { ZLinkActorJoinRelocation } from '../actors/actor-join-relocation';
17
17
  export interface ZLinkActorRuntimeOptionsFactoryOptions {
18
+ readonly terminateActorActivation: (actor: ZLinkActor, terminal: () => Promise<void>) => Promise<void>;
18
19
  readonly registration: ZLinkFrameworkRegistration;
19
20
  /** Message flow tracer, so a failed deferred Join records its cause on the flow. */
20
21
  readonly messageFlow?: () => import('../diagnostics/message-flow').ZLinkMessageFlowTracer | undefined;
@@ -61,7 +62,7 @@ export interface ZLinkActorRuntimeOptionsFactoryOptions {
61
62
  export declare class ZLinkActorRuntimeOptionsFactory {
62
63
  private readonly options;
63
64
  constructor(options: ZLinkActorRuntimeOptionsFactoryOptions);
64
- createActorManagerOptions(): Pick<ZLinkActorManagerOptions, 'joinCoordinator' | 'actorMeshNameProvider' | 'actorLeaveSpot' | 'messageSerializers' | 'nativeActorNode' | 'nativeActorNodeProvider' | 'nativeActorCompletionTableProvider' | 'actorCreatedNodeRidProvider' | 'actorRefResolver' | 'actorCreatedNotifier' | 'actorDestroyedCleanup' | 'publishActorAuthority' | 'locationLifecycle' | 'boundSessionFactory' | 'actorTransferRegistry' | 'shutdownSignal' | 'metrics' | 'admission'>;
65
+ createActorManagerOptions(): Pick<ZLinkActorManagerOptions, 'joinCoordinator' | 'actorMeshNameProvider' | 'actorLeaveSpot' | 'messageSerializers' | 'nativeActorNode' | 'nativeActorNodeProvider' | 'nativeActorCompletionTableProvider' | 'actorCreatedNodeRidProvider' | 'actorRefResolver' | 'actorCreatedNotifier' | 'actorDestroyedCleanup' | 'terminateActorActivation' | 'publishActorAuthority' | 'locationLifecycle' | 'boundSessionFactory' | 'actorTransferRegistry' | 'shutdownSignal' | 'metrics' | 'admission'>;
65
66
  createActorClientOptions(): ConstructorParameters<typeof DefaultZLinkActorClient>[0];
66
67
  private actorBelongsToMesh;
67
68
  }
@@ -13,6 +13,7 @@ class ZLinkActorRuntimeOptionsFactory {
13
13
  createActorManagerOptions() {
14
14
  const actorTransferRegistry = this.options.actorTransferRegistry;
15
15
  return {
16
+ terminateActorActivation: this.options.terminateActorActivation,
16
17
  actorMeshNameProvider: this.options.actorMeshName,
17
18
  actorLeaveSpot: (meshName, spotId, actor, signal) => {
18
19
  const spotManager = this.options.spotManager();
@@ -155,7 +155,7 @@ export declare class ZLinkFrameworkRuntimeHost implements ZLinkFrameworkRuntimeL
155
155
  private registerInstanceApplicationLifecycle;
156
156
  setActorManager(actorManager: DefaultZLinkActorManager): void;
157
157
  setSpotManager(spotManager: DefaultZLinkSpotManager): void;
158
- createActorManagerOptions(): Pick<ZLinkActorManagerOptions, 'joinCoordinator' | 'actorMeshNameProvider' | 'actorLeaveSpot' | 'messageSerializers' | 'nativeActorNode' | 'nativeActorNodeProvider' | 'actorCreatedNodeRidProvider' | 'actorRefResolver' | 'actorCreatedNotifier' | 'actorDestroyedCleanup' | 'locationLifecycle' | 'boundSessionFactory' | 'shutdownSignal' | 'admission' | 'activationAdmission' | 'placementCreate'>;
158
+ createActorManagerOptions(): Pick<ZLinkActorManagerOptions, 'joinCoordinator' | 'actorMeshNameProvider' | 'actorLeaveSpot' | 'messageSerializers' | 'nativeActorNode' | 'nativeActorNodeProvider' | 'actorCreatedNodeRidProvider' | 'actorRefResolver' | 'actorCreatedNotifier' | 'actorDestroyedCleanup' | 'terminateActorActivation' | 'locationLifecycle' | 'boundSessionFactory' | 'shutdownSignal' | 'admission' | 'activationAdmission' | 'placementCreate'>;
159
159
  createActorClientOptions(): ConstructorParameters<typeof DefaultZLinkActorClient>[0];
160
160
  createLocationHandleResolver(): ZLinkStoreLocationResolvers | undefined;
161
161
  createSpotManagerOptions(): Partial<ZLinkSpotManagerOptions>;
@@ -1511,6 +1511,8 @@ class ZLinkFrameworkRuntimeHost {
1511
1511
  }
1512
1512
  this.actorPlacement ??= new actor_placement_coordinator_1.ZLinkActorPlacementCoordinator({
1513
1513
  store: locationStore,
1514
+ actorRelocationPolicy: (meshName, stableType) => this.options.registration.spotNodes.get(meshName)?.actorFactoryRegistrations?.[stableType]
1515
+ ?.relocation.kind,
1514
1516
  remoteCreate: (meshName, targetNodeRid, request, timeoutMs) => {
1515
1517
  const node = this.spotNodeRuntime?.meshNode(meshName);
1516
1518
  if (node === undefined) {
@@ -1546,13 +1548,11 @@ class ZLinkFrameworkRuntimeHost {
1546
1548
  const descriptors = await (0, location_store_pages_1.listAllMeshNodeDescriptors)(locationStore, meshName, signal);
1547
1549
  const selected = await selectReadyLocationPlacementDescriptor(descriptors, meshNode, signal, (descriptor) => {
1548
1550
  const capability = descriptor.objectCapabilities.find((candidate) => candidate.objectKind === 'actor' && candidate.stableType === stableType);
1549
- const actors = descriptor.populationCapacity.actors;
1550
1551
  return (descriptor.state === contracts_1.ZLinkFrameworkRuntimeState.Serving &&
1551
1552
  descriptor.objectRole === 'server' &&
1552
1553
  descriptor.placementWeight > 0 &&
1553
1554
  descriptor.entrySpotId !== undefined &&
1554
1555
  excludedNodeRids?.has(String(descriptor.rid)) !== true &&
1555
- (actors.limit === 0 || actors.active + actors.reserved < actors.limit) &&
1556
1556
  capability !== undefined);
1557
1557
  });
1558
1558
  if (selected === undefined)
@@ -1832,6 +1832,7 @@ class ZLinkFrameworkRuntimeHost {
1832
1832
  }
1833
1833
  actorRuntimeOptionsFactory() {
1834
1834
  return new actor_runtime_options_factory_1.ZLinkActorRuntimeOptionsFactory({
1835
+ terminateActorActivation: (actor, terminal) => this.spotNodeRuntime?.terminateActorActivation(actor, terminal) ?? terminal(),
1835
1836
  registration: this.options.registration,
1836
1837
  messageFlow: () => this.createDispatchErrorReporter(this.runtimeOrPreStartErrorSink).flow,
1837
1838
  routeTransport: this.routeTransport,
@@ -1,5 +1,6 @@
1
1
  import type { ZLinkLocationOwnerToken, ZLinkLocationPage, ZLinkLocationWriteIntent, ZLinkLocationWriteResult, ZLinkLocationWriteStatus, ZLinkMeshNodeDescriptor, ZLinkMeshNodeDescriptorKey, ZLinkClientServerServerDescriptor, ZLinkClientServerServerDescriptorKey, ZLinkFanoutPublisherDescriptor, ZLinkFanoutPublisherDescriptorKey, ZLinkOwnerLeaseClaimResult, ZLinkOwnerLeaseReadResult, ZLinkOwnerLeaseReleaseResult, ZLinkOwnerLeaseRenewResult, ZLinkPageRequest } from '../../contracts/Locations/Models';
2
- import type { ZLinkAggregateAbortResult, ZLinkAggregateCommitResult, ZLinkAggregateFence, ZLinkAggregatePrepareRequest, ZLinkAggregatePrepareResult, ZLinkAuthorityCompareExchangeResult, ZLinkAuthorityKey, ZLinkAuthorityMutation, ZLinkAuthorityReadResult, ZLinkAuthorityScanCursor, ZLinkAuthorityScanResult, ZLinkAuthorityStoreVersion, ZLinkCreationOperationIdentity, ZLinkCreationTerminalReadResult, ZLinkObjectAbortRequest, ZLinkObjectAbortResult, ZLinkObjectCommitRequest, ZLinkObjectCommitResult, ZLinkObjectCreationCompleteRequest, ZLinkObjectCreationCompleteResult, ZLinkObjectReserveRequest, ZLinkObjectReserveResult } from '../../contracts/Locations/Authority';
2
+ import type { ZLinkAggregateAbortResult, ZLinkAggregateCommitResult, ZLinkAggregateFence, ZLinkAggregatePrepareRequest, ZLinkAggregatePrepareResult, ZLinkAuthorityCompareExchangeResult, ZLinkAuthorityKey, ZLinkAuthorityMutation, ZLinkAuthorityReadResult, ZLinkAuthorityScanCursor, ZLinkAuthorityScanResult, ZLinkAuthorityStoreVersion, ZLinkCreationOperationIdentity, ZLinkCreationTerminalReadResult, ZLinkObjectAbortRequest, ZLinkObjectAbortResult, ZLinkObjectCommitRequest, ZLinkObjectCommitResult, ZLinkObjectCreationCompleteRequest, ZLinkObjectCreationCompleteResult, ZLinkObjectReserveResult } from '../../contracts/Locations/Authority';
3
+ import type { ZLinkObjectReserveRequest } from './internal-location-contracts';
3
4
  /**
4
5
  * Framework-private domain repository. Provider implementations never
5
6
  * implement this interface; the framework maps it to the opaque Store SPI.
@@ -1,5 +1,6 @@
1
1
  import type { ZLinkAggregateAbortResult, ZLinkAggregateCommitResult, ZLinkAggregateFence, ZLinkAggregatePrepareRequest, ZLinkAggregatePrepareResult, ZLinkAuthorityCompareExchangeResult, ZLinkAuthorityKey, ZLinkAuthorityMutation, ZLinkAuthorityReadResult, ZLinkAuthorityScanCursor, ZLinkAuthorityScanResult, ZLinkAuthorityStoreVersion, ZLinkCapacityVector, ZLinkLocationOwnerToken, ZLinkMeshNodeDescriptorKey, ZLinkObjectAbortRequest, ZLinkObjectAbortResult, ZLinkObjectCommitRequest, ZLinkObjectCommitResult, ZLinkObjectCreationCompleteRequest, ZLinkObjectCreationCompleteResult, ZLinkObjectReserveRequest, ZLinkObjectReserveResult, ZLinkCreationOperationIdentity, ZLinkCreationTerminalReadResult } from './internal-location-contracts';
2
2
  export interface ZLinkInMemoryAuthorityValidation {
3
+ isOwnerLive(owner: ZLinkLocationOwnerToken): boolean;
3
4
  isTargetLive(descriptor: ZLinkMeshNodeDescriptorKey, lifecycleGeneration: bigint, owner: ZLinkLocationOwnerToken): boolean;
4
5
  placementCapacityAvailable?(descriptor: ZLinkMeshNodeDescriptorKey, requested: ZLinkCapacityVector, currentReserved: ZLinkCapacityVector, currentActive: ZLinkCapacityVector): boolean;
5
6
  identityClaimed?(authorityKey: string): boolean;
@@ -6,12 +6,15 @@ const stream_wire_1 = require("@zlink-systems/stream-wire");
6
6
  const CoreTypes_1 = require("../../contracts/Common/CoreTypes");
7
7
  const Stores_1 = require("../../contracts/Locations/Stores");
8
8
  const node_crypto_1 = require("node:crypto");
9
+ const contracts_1 = require("../../contracts");
9
10
  const actor_join_recovery_codec_1 = require("../foundation/actor-join-recovery-codec");
10
11
  const authority_key_codec_1 = require("./authority-key-codec");
11
12
  const opaque_record_key_1 = require("./opaque-record-key");
13
+ const service_relocation_runtime_1 = require("../foundation/service-relocation-runtime");
12
14
  const MAX_GENERATION = 0x7fffffffffffffffn;
13
15
  const CREATION_TERMINAL_RETENTION_MS = 5 * 60 * 1000;
14
16
  const MAX_U64 = stream_wire_1.UINT64_MAX;
17
+ const relocationAuthorityCodec = new service_relocation_runtime_1.ServiceRelocationAuthorityPayloadCodec();
15
18
  /**
16
19
  * In-memory reference provider for the provider-owned authority state machine.
17
20
  * Opaque payload bytes are copied but never parsed.
@@ -175,20 +178,39 @@ class ZLinkInMemoryAuthorityStore {
175
178
  signal?.throwIfAborted();
176
179
  return this.tryReleaseEndedReservation(key, expectedStoreVersion);
177
180
  }
178
- tryReleaseEndedReservation(key, expectedStoreVersion) {
181
+ tryReleaseEndedReservation(key, expectedStoreVersion, request) {
179
182
  const current = this.rows.get(key.value);
180
183
  if (current === undefined ||
181
184
  current.snapshot.storeVersion.value !== expectedStoreVersion ||
182
- current.snapshot.allocation.state !== 'reserved' ||
183
- current.creation === undefined ||
184
- this.validation.isTargetLive(current.snapshot.allocation.descriptor, current.snapshot.allocation.descriptorLifecycleGeneration, {
185
+ (current.snapshot.allocation.state === 'active'
186
+ ? request?.key.kind !== 'actor'
187
+ : current.creation === undefined))
188
+ return false;
189
+ if (current.snapshot.allocation.state === 'active') {
190
+ for (const aggregate of this.aggregates.values()) {
191
+ if (aggregate.state === 'prepared' &&
192
+ aggregate.request.participants.some((participant) => participant.authorityKey.value === key.value))
193
+ return false;
194
+ }
195
+ if (relocationAuthorityCodec.read(current.snapshot.payload) !== undefined)
196
+ return false;
197
+ if (this.validation.isOwnerLive({
185
198
  ownerId: current.snapshot.ownerId,
186
199
  leaseGeneration: current.snapshot.ownerLeaseGeneration
187
200
  }))
201
+ return false;
202
+ if (request?.actorRelocationPolicy !== 'disabled')
203
+ throw new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.Unavailable, 'Actor owner lease is unavailable.');
204
+ }
205
+ else if (this.validation.isTargetLive(current.snapshot.allocation.descriptor, current.snapshot.allocation.descriptorLifecycleGeneration, {
206
+ ownerId: current.snapshot.ownerId,
207
+ leaseGeneration: current.snapshot.ownerLeaseGeneration
208
+ }))
188
209
  return false;
189
- this.adjustCapacity(this.pendingCapacity, current.snapshot.allocation, -1);
210
+ this.adjustCapacity(current.snapshot.allocation.state === 'active' ? this.activeCapacity : this.pendingCapacity, current.snapshot.allocation, -1);
190
211
  this.rows.delete(key.value);
191
- this.creationTerminals.set(current.creation.reservationId, 'aborted');
212
+ if (current.snapshot.allocation.state === 'reserved' && current.creation !== undefined)
213
+ this.creationTerminals.set(current.creation.reservationId, 'aborted');
192
214
  this.scanRevision++;
193
215
  return true;
194
216
  }
@@ -205,11 +227,10 @@ class ZLinkInMemoryAuthorityStore {
205
227
  current.snapshot.allocation.stableType !== request.intent.stableType) {
206
228
  return { kind: 'typeMismatch', current: this.snapshot(current.snapshot) };
207
229
  }
208
- if (current.snapshot.allocation.state === 'active') {
209
- return { kind: 'alreadyExists', current: this.snapshot(current.snapshot) };
210
- }
211
- if (!this.tryReleaseEndedReservation((0, authority_key_codec_1.encodeAuthorityKey)(request.key.kind, request.key.globalId), current.snapshot.storeVersion.value))
212
- return { kind: 'conflict', current: this.read(key) };
230
+ if (!this.tryReleaseEndedReservation((0, authority_key_codec_1.encodeAuthorityKey)(request.key.kind, request.key.globalId), current.snapshot.storeVersion.value, request))
231
+ return current.snapshot.allocation.state === 'active'
232
+ ? { kind: 'alreadyExists', current: this.snapshot(current.snapshot) }
233
+ : { kind: 'conflict', current: this.read(key) };
213
234
  }
214
235
  const target = creationTarget(request);
215
236
  if (!this.validation.isTargetLive(target.descriptor, target.lifecycleGeneration, target.owner)) {
@@ -72,7 +72,7 @@ export declare class ZLinkInMemoryLocationStore implements ZLinkDomainLocationSt
72
72
  private transitionActorTransfer;
73
73
  private remove;
74
74
  private removeByOwner;
75
- private isOwnerLive;
75
+ private liveOwnerLease;
76
76
  private bump;
77
77
  private removeFanoutByOwner;
78
78
  private bumpScope;
@@ -27,19 +27,19 @@ class ZLinkInMemoryLocationStore {
27
27
  authority;
28
28
  constructor(now = () => new Date()) {
29
29
  this.now = now;
30
+ const isOwnerLive = (owner) => this.liveOwnerLease(owner.ownerId, this.now())?.token.leaseGeneration ===
31
+ owner.leaseGeneration;
30
32
  this.authority = new in_memory_authority_store_1.ZLinkInMemoryAuthorityStore({
33
+ isOwnerLive,
31
34
  isTargetLive: (key, lifecycleGeneration, owner) => {
32
35
  const descriptor = this.meshNodes.rows.get(meshNodeKey(key.meshName, key.rid));
33
- const lease = this.leases.get(owner.ownerId);
34
36
  return (descriptor !== undefined &&
35
37
  descriptor.lifecycleGeneration === lifecycleGeneration &&
36
38
  descriptor.ownerId === owner.ownerId &&
37
39
  descriptor.leaseGeneration === owner.leaseGeneration &&
38
40
  descriptor.objectRole === internal_location_contracts_1.ZLinkObjectRole.Server &&
39
41
  descriptor.state === internal_location_contracts_1.ZLinkFrameworkRuntimeState.Serving &&
40
- lease !== undefined &&
41
- lease.token.leaseGeneration === owner.leaseGeneration &&
42
- lease.leaseExpiresAt.getTime() > this.now().getTime());
42
+ isOwnerLive(owner));
43
43
  },
44
44
  placementCapacityAvailable: (key, requested, reserved, active) => {
45
45
  const descriptor = this.meshNodes.rows.get(meshNodeKey(key.meshName, key.rid));
@@ -316,7 +316,7 @@ class ZLinkInMemoryLocationStore {
316
316
  async listFanoutPublishers(channelName, page = {}, signal) {
317
317
  signal?.throwIfAborted();
318
318
  const storeNow = this.now();
319
- return pageRows(this.fanoutPublishers, (row) => row.channelName === channelName && this.isOwnerLive(row.ownerId, storeNow), page);
319
+ return pageRows(this.fanoutPublishers, (row) => row.channelName === channelName && this.liveOwnerLease(row.ownerId, storeNow) !== undefined, page);
320
320
  }
321
321
  async updatePeer(peer, intent) {
322
322
  return this.write(this.peers, key_codec_1.ZLinkLocationKeyCodec.encodePeerKey({
@@ -342,7 +342,7 @@ class ZLinkInMemoryLocationStore {
342
342
  const current = this.spots.rows.get(key);
343
343
  if (intent === internal_location_contracts_1.ZLinkLocationWriteIntent.NewClaim &&
344
344
  current !== undefined &&
345
- this.isOwnerLive(current.ownerId, updatedAt)) {
345
+ this.liveOwnerLease(current.ownerId, updatedAt) !== undefined) {
346
346
  return rejectedConflict();
347
347
  }
348
348
  if (intent === internal_location_contracts_1.ZLinkLocationWriteIntent.Renew) {
@@ -391,7 +391,7 @@ class ZLinkInMemoryLocationStore {
391
391
  const current = this.actors.rows.get(key);
392
392
  if (intent === internal_location_contracts_1.ZLinkLocationWriteIntent.NewClaim &&
393
393
  current !== undefined &&
394
- this.isOwnerLive(current.ownerId, updatedAt)) {
394
+ this.liveOwnerLease(current.ownerId, updatedAt) !== undefined) {
395
395
  return rejectedConflict();
396
396
  }
397
397
  if (intent === internal_location_contracts_1.ZLinkLocationWriteIntent.Renew) {
@@ -651,7 +651,7 @@ class ZLinkInMemoryLocationStore {
651
651
  const current = table.rows.get(key);
652
652
  if (intent === internal_location_contracts_1.ZLinkLocationWriteIntent.NewClaim &&
653
653
  current !== undefined &&
654
- this.isOwnerLive(ownerOf(current), updatedAt)) {
654
+ this.liveOwnerLease(ownerOf(current), updatedAt) !== undefined) {
655
655
  return rejectedConflict();
656
656
  }
657
657
  if (intent === internal_location_contracts_1.ZLinkLocationWriteIntent.NewClaim ||
@@ -714,9 +714,11 @@ class ZLinkInMemoryLocationStore {
714
714
  }
715
715
  return removed;
716
716
  }
717
- isOwnerLive(ownerId, now) {
717
+ liveOwnerLease(ownerId, now) {
718
718
  const lease = this.leases.get(ownerId);
719
- return lease !== undefined && lease.leaseExpiresAt.getTime() > now.getTime();
719
+ return lease !== undefined && lease.leaseExpiresAt.getTime() > now.getTime()
720
+ ? lease
721
+ : undefined;
720
722
  }
721
723
  bump(kind, meshName) {
722
724
  this.bumpScope({ kind, meshName });
@@ -1,4 +1,9 @@
1
1
  export * from '../../contracts/Locations';
2
+ import type { ZLinkObjectReserveRequest as ReserveRequest } from '../../contracts/Locations/Authority';
3
+ import type { ZLinkRelocationConfiguration } from '../../contracts/Configuration/RegistrationTypes';
4
+ export interface ZLinkObjectReserveRequest extends ReserveRequest {
5
+ readonly actorRelocationPolicy?: ZLinkRelocationConfiguration<unknown>['kind'];
6
+ }
2
7
  export type * from '../../contracts/Locations/Authority';
3
8
  export type { ZLinkActorLocation, ZLinkPeerLocation, ZLinkRouteLocation, ZLinkSpotLocation } from '../../contracts/Locations/Rows';
4
9
  export type { ZLinkActorLocationFilter, ZLinkActorLocationKey, ZLinkLocationKey, ZLinkPeerLocationFilter, ZLinkPeerLocationKey, ZLinkRouteLocationFilter, ZLinkRouteLocationKey, ZLinkSpotLocationFilter, ZLinkSpotLocationKey } from '../../contracts/Locations/Keys';
@@ -21,6 +21,7 @@ export declare class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationS
21
21
  private commitAggregateCore;
22
22
  abortAggregate(fence: ZLinkAggregateFence, signal?: AbortSignal): Promise<ZLinkAggregateAbortResult>;
23
23
  releaseEndedReservation(key: ZLinkAuthorityKey, expectedStoreVersion: string, signal?: AbortSignal): Promise<boolean>;
24
+ private tryReleaseEndedReservation;
24
25
  reserve(request: ZLinkObjectReserveRequest, signal?: AbortSignal, deadlineUnixMs?: bigint): Promise<ZLinkObjectReserveResult>;
25
26
  commit(request: ZLinkObjectCommitRequest, signal?: AbortSignal, deadlineUnixMs?: bigint): Promise<ZLinkObjectCommitResult>;
26
27
  abort(request: ZLinkObjectAbortRequest, signal?: AbortSignal, deadlineUnixMs?: bigint): Promise<ZLinkObjectAbortResult>;
@@ -815,17 +815,21 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
815
815
  }
816
816
  }
817
817
  async releaseEndedReservation(key, expectedStoreVersion, signal) {
818
+ return ((await this.tryReleaseEndedReservation(key, expectedStoreVersion, signal)) === 'reclaimed');
819
+ }
820
+ async tryReleaseEndedReservation(key, expectedStoreVersion, signal, request) {
818
821
  const rowKey = authorityKey(key.value);
819
822
  const current = await this.provider.read(rowKey, signal);
820
823
  if (current.kind === 'missing' || current.value.version.value !== expectedStoreVersion)
821
- return false;
824
+ return 'conflict';
822
825
  const record = decodeAuthorityRecord(current.value.bytes);
823
826
  const snapshot = record.snapshot;
824
- if (snapshot.allocation.state !== 'reserved' ||
825
- snapshot.pendingCreation === undefined ||
827
+ if ((snapshot.allocation.state === 'active'
828
+ ? request?.key.kind !== 'actor'
829
+ : snapshot.pendingCreation === undefined) ||
826
830
  record.aggregate !== undefined ||
827
831
  hasRelocationAuthority(snapshot.payload))
828
- return false;
832
+ return 'notReclaimable';
829
833
  const leaseKey = ownerKey(snapshot.ownerId);
830
834
  const descriptorKey = meshKey(snapshot.allocation.descriptor.meshName, snapshot.allocation.descriptor.rid);
831
835
  const capacityRowKey = capacityKey(snapshot.allocation.descriptor.meshName, String(snapshot.allocation.descriptor.rid));
@@ -836,23 +840,46 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
836
840
  ]);
837
841
  if (lease.kind === 'found' && lease.value.expiresAt === undefined)
838
842
  throw new Error('Location Store owner lease record is invalid.');
839
- if (sameLiveOwner(lease, snapshot) &&
843
+ const ownerLive = sameLiveOwner(lease, snapshot);
844
+ if (snapshot.allocation.state === 'active') {
845
+ if (ownerLive)
846
+ return 'notReclaimable';
847
+ if (request?.actorRelocationPolicy !== 'disabled')
848
+ throw new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.Unavailable, 'Actor owner lease is unavailable.');
849
+ }
850
+ else if (ownerLive &&
840
851
  descriptor.kind === 'found' &&
841
852
  reviveMeshDescriptor(decodeCanonicalDescriptorRecord(descriptor.value.bytes).descriptor).lifecycleGeneration === snapshot.allocation.descriptorLifecycleGeneration)
842
- return false;
853
+ return 'notReclaimable';
843
854
  const mutations = [
844
855
  { kind: 'delete', key: rowKey }
845
856
  ];
846
857
  if (capacity.kind === 'found') {
847
858
  const stored = decodeJson(capacity.value.bytes);
848
- mutations.push({
849
- kind: 'put',
850
- key: capacityRowKey,
851
- bytes: encodeJson({
852
- active: stored.active,
853
- pending: subtractCapacity(stored.pending, snapshot.allocation.capacity)
854
- })
855
- });
859
+ try {
860
+ mutations.push({
861
+ kind: 'put',
862
+ key: capacityRowKey,
863
+ bytes: encodeJson({
864
+ active: snapshot.allocation.state === 'active'
865
+ ? subtractCapacity(stored.active, snapshot.allocation.capacity)
866
+ : stored.active,
867
+ pending: snapshot.allocation.state === 'reserved'
868
+ ? subtractCapacity(stored.pending, snapshot.allocation.capacity)
869
+ : stored.pending
870
+ })
871
+ });
872
+ }
873
+ catch (error) {
874
+ if (!(error instanceof contracts_1.ZLinkFrameworkException) ||
875
+ error.kind !== contracts_1.ZLinkFrameworkErrorKind.DataLost)
876
+ throw error;
877
+ const observed = await this.provider.read(rowKey, signal);
878
+ if (observed.kind === 'missing' ||
879
+ observed.value.version.value !== current.value.version.value)
880
+ return 'conflict';
881
+ throw error;
882
+ }
856
883
  }
857
884
  const result = await this.provider.write({
858
885
  conditions: [
@@ -863,11 +890,12 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
863
890
  ],
864
891
  mutations
865
892
  }, signal);
866
- return result.kind === 'applied';
893
+ return result.kind === 'applied' ? 'reclaimed' : 'conflict';
867
894
  }
868
895
  async reserve(request, signal, deadlineUnixMs) {
869
896
  const encodedAuthorityKey = (0, authority_key_codec_1.encodeAuthorityKey)(request.key.kind, request.key.globalId);
870
897
  const reservationId = (0, node_crypto_1.randomUUID)();
898
+ let reclaimVersion;
871
899
  for (;;) {
872
900
  throwIfOperationExpired(signal, deadlineUnixMs);
873
901
  const rowKey = authorityKey(encodedAuthorityKey.value);
@@ -882,13 +910,18 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
882
910
  snapshot.allocation.stableType !== request.intent.stableType) {
883
911
  return { kind: 'typeMismatch', current: snapshot };
884
912
  }
885
- if (snapshot.allocation.state === 'active') {
886
- return { kind: 'alreadyExists', current: snapshot };
887
- }
888
- if (!(await this.releaseEndedReservation(encodedAuthorityKey, current.value.version.value, signal)))
913
+ if (reclaimVersion !== undefined && snapshot.storeVersion.value !== reclaimVersion)
889
914
  return { kind: 'conflict', current: snapshot };
915
+ const reclaim = await this.tryReleaseEndedReservation(encodedAuthorityKey, current.value.version.value, signal, request);
916
+ if (reclaim === 'notReclaimable')
917
+ return snapshot.allocation.state === 'active'
918
+ ? { kind: 'alreadyExists', current: snapshot }
919
+ : { kind: 'conflict', current: snapshot };
920
+ reclaimVersion = reclaim === 'conflict' ? snapshot.storeVersion.value : undefined;
890
921
  continue;
891
922
  }
923
+ if (reclaimVersion !== undefined)
924
+ return { kind: 'conflict', current: { kind: 'missing', storeNow: current.storeNow } };
892
925
  const [descriptorRead, leaseRead] = await Promise.all([
893
926
  this.provider.read(descriptorKey, signal),
894
927
  this.provider.read(leaseKey, signal)
@@ -2941,7 +2974,7 @@ function adjustCapacity(usage, delta, direction) {
2941
2974
  const key = capacityTypeKey(delta.spotType);
2942
2975
  const next = (spotTypes[key] ?? 0) + direction * delta.spotType.count;
2943
2976
  if (next < 0)
2944
- throw new Error('Provider capacity counter underflow.');
2977
+ throw new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.DataLost, 'Provider capacity counter underflow.');
2945
2978
  if (next === 0)
2946
2979
  delete spotTypes[key];
2947
2980
  else
@@ -2950,7 +2983,7 @@ function adjustCapacity(usage, delta, direction) {
2950
2983
  const actors = usage.actors + direction * delta.actors;
2951
2984
  const spots = usage.spots + direction * delta.spots;
2952
2985
  if (actors < 0 || spots < 0)
2953
- throw new Error('Provider capacity counter underflow.');
2986
+ throw new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.DataLost, 'Provider capacity counter underflow.');
2954
2987
  return { actors, spots, spotTypes };
2955
2988
  }
2956
2989
  function capacityTypeKey(value) {