@zlink-systems/framework 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/Builders.d.ts +1 -2
  3. package/dist/contracts/Configuration/Configs.d.ts +0 -5
  4. package/dist/contracts/Configuration/RegistrationBuilders.js +6 -4
  5. package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -6
  6. package/dist/contracts/Configuration/RegistrationValidators.js +4 -4
  7. package/dist/contracts/Configuration/index.d.ts +1 -1
  8. package/dist/internal.d.ts +1 -0
  9. package/dist/internal.js +3 -1
  10. package/dist/runtime/actors/actor-handoff.d.ts +3 -2
  11. package/dist/runtime/actors/actor-handoff.js +9 -4
  12. package/dist/runtime/actors/actor-runtime-contracts.d.ts +1 -0
  13. package/dist/runtime/actors/index.d.ts +2 -0
  14. package/dist/runtime/actors/index.js +21 -6
  15. package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +3 -1
  16. package/dist/runtime/application-jobs/application-ingress-record-owner.js +6 -1
  17. package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +3 -1
  18. package/dist/runtime/application-jobs/application-job-queue-scope.js +5 -0
  19. package/dist/runtime/application-jobs/contracts.d.ts +5 -1
  20. package/dist/runtime/backend/contracts/index.d.ts +10 -19
  21. package/dist/runtime/backend/mesh-completion-table.d.ts +6 -4
  22. package/dist/runtime/backend/mesh-completion-table.js +28 -18
  23. package/dist/runtime/backend/mesh-dispatch-pump.js +21 -25
  24. package/dist/runtime/backend/node/node-backend-adapter-factory.js +6 -1
  25. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +3 -3
  26. package/dist/runtime/backend/node/node-backend-adapter-support.js +24 -29
  27. package/dist/runtime/backend/node/node-mesh-backend-adapter.d.ts +1 -9
  28. package/dist/runtime/backend/node/node-mesh-backend-adapter.js +1 -1
  29. package/dist/runtime/backend/node/node-raw-binding-port.js +11 -6
  30. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +10 -12
  31. package/dist/runtime/backend/node/node-raw-mesh-backend.js +64 -98
  32. package/dist/runtime/backend/node/node-socket-backend-adapter.js +10 -5
  33. package/dist/runtime/backend/raw-binding-port.d.ts +9 -2
  34. package/dist/runtime/channels/channel-receive-loops.js +3 -3
  35. package/dist/runtime/channels/channel-runtime-lifecycle.js +2 -1
  36. package/dist/runtime/channels/channel-socket-registry.d.ts +10 -3
  37. package/dist/runtime/channels/channel-socket-registry.js +59 -82
  38. package/dist/runtime/diagnostics/message-flow.d.ts +1 -0
  39. package/dist/runtime/diagnostics/message-flow.js +3 -3
  40. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +15 -1
  41. package/dist/runtime/foundation/raw-service-mesh-runtime.js +193 -64
  42. package/dist/runtime/foundation/service-liveness-registry.d.ts +20 -8
  43. package/dist/runtime/foundation/service-liveness-registry.js +57 -44
  44. package/dist/runtime/foundation/service-mailbox.d.ts +3 -0
  45. package/dist/runtime/foundation/service-mailbox.js +2 -1
  46. package/dist/runtime/foundation/service-stateful-runtime.d.ts +7 -6
  47. package/dist/runtime/foundation/service-stateful-runtime.js +60 -41
  48. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +5 -2
  49. package/dist/runtime/foundation/service-stateful-wire-codec.js +43 -61
  50. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -3
  51. package/dist/runtime/foundation/service-topology-registry.js +9 -4
  52. package/dist/runtime/handlers/handler-instance-scope.js +1 -1
  53. package/dist/runtime/host/actor-packet-relay.js +7 -7
  54. package/dist/runtime/host/actor-placement-coordinator.d.ts +2 -1
  55. package/dist/runtime/host/actor-placement-coordinator.js +2 -1
  56. package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -1
  57. package/dist/runtime/host/actor-runtime-options-factory.js +2 -1
  58. package/dist/runtime/host/application-job-queue.d.ts +11 -4
  59. package/dist/runtime/host/application-job-queue.js +66 -10
  60. package/dist/runtime/host/entry-actor-runtime.js +1 -1
  61. package/dist/runtime/host/index.d.ts +1 -1
  62. package/dist/runtime/host/index.js +18 -13
  63. package/dist/runtime/host/instance-activation-authority.d.ts +2 -1
  64. package/dist/runtime/host/instance-activation-authority.js +89 -25
  65. package/dist/runtime/host/service-relocation-host-runtime.js +2 -2
  66. package/dist/runtime/host/spot-address-transport.d.ts +1 -0
  67. package/dist/runtime/host/spot-address-transport.js +25 -12
  68. package/dist/runtime/host/spot-runtime-options-factory.js +2 -2
  69. package/dist/runtime/host/user-spot-creation-coordinator.d.ts +1 -0
  70. package/dist/runtime/host/user-spot-creation-coordinator.js +1 -0
  71. package/dist/runtime/locations/domain-store-contract.d.ts +3 -2
  72. package/dist/runtime/locations/in-memory-authority-store.d.ts +5 -2
  73. package/dist/runtime/locations/in-memory-authority-store.js +107 -14
  74. package/dist/runtime/locations/in-memory-location-store.d.ts +3 -3
  75. package/dist/runtime/locations/in-memory-location-store.js +31 -13
  76. package/dist/runtime/locations/internal-location-contracts.d.ts +8 -0
  77. package/dist/runtime/locations/location-store-repository.d.ts +4 -2
  78. package/dist/runtime/locations/location-store-repository.js +209 -33
  79. package/dist/runtime/locations/resolvers.d.ts +2 -1
  80. package/dist/runtime/locations/resolvers.js +10 -1
  81. package/dist/runtime/messaging/submission-result.d.ts +1 -1
  82. package/dist/runtime/messaging/submission-result.js +2 -2
  83. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +2 -0
  84. package/dist/runtime/spots/index.d.ts +4 -3
  85. package/dist/runtime/spots/index.js +14 -14
  86. package/dist/runtime/spots/spot-activation.d.ts +2 -1
  87. package/dist/runtime/spots/spot-activation.js +4 -4
  88. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +2 -1
  89. package/dist/runtime/spots/spot-actor-admission-coordinator.js +11 -10
  90. package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +4 -1
  91. package/dist/runtime/spots/spot-actor-packet-dispatch.js +7 -5
  92. package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
  93. package/dist/runtime/spots/spot-actor-packet-relay-dispatch.js +1 -0
  94. package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
  95. package/dist/runtime/spots/spot-entry-activation.js +6 -1
  96. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +3 -1
  97. package/dist/runtime/spots/spot-node-runtime-manager.js +10 -11
  98. package/dist/runtime/spots/spot-routing-internal.d.ts +6 -0
  99. package/dist/runtime/spots/spot-runtime-ports.d.ts +2 -1
  100. package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
  101. package/dist/runtime/spots/spot-serial-executor.js +10 -1
  102. package/dist/runtime/streams/stream-session-runtime.js +1 -1
  103. package/package.json +3 -3
  104. package/src/contracts/Configuration/Builders.ts +1 -2
  105. package/src/contracts/Configuration/Configs.ts +0 -6
  106. package/src/contracts/Configuration/RegistrationBuilders.ts +8 -12
  107. package/src/contracts/Configuration/RegistrationTypes.ts +1 -6
  108. package/src/contracts/Configuration/RegistrationValidators.ts +9 -10
  109. package/src/contracts/Configuration/index.ts +1 -6
  110. package/src/internal.ts +1 -0
  111. package/src/runtime/actors/actor-handoff.ts +13 -4
  112. package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
  113. package/src/runtime/actors/index.ts +77 -42
  114. package/src/runtime/application-jobs/application-ingress-record-owner.ts +12 -2
  115. package/src/runtime/application-jobs/application-job-queue-scope.ts +6 -1
  116. package/src/runtime/application-jobs/contracts.ts +14 -1
  117. package/src/runtime/backend/contracts/index.ts +10 -18
  118. package/src/runtime/backend/mesh-completion-table.ts +36 -19
  119. package/src/runtime/backend/mesh-dispatch-pump.ts +19 -22
  120. package/src/runtime/backend/node/node-backend-adapter-factory.ts +12 -5
  121. package/src/runtime/backend/node/node-backend-adapter-support.ts +31 -29
  122. package/src/runtime/backend/node/node-mesh-backend-adapter.ts +3 -10
  123. package/src/runtime/backend/node/node-raw-binding-port.ts +19 -10
  124. package/src/runtime/backend/node/node-raw-mesh-backend.ts +81 -111
  125. package/src/runtime/backend/node/node-socket-backend-adapter.ts +12 -8
  126. package/src/runtime/backend/raw-binding-port.ts +12 -2
  127. package/src/runtime/channels/channel-receive-loops.ts +3 -3
  128. package/src/runtime/channels/channel-runtime-lifecycle.ts +7 -1
  129. package/src/runtime/channels/channel-socket-registry.ts +84 -130
  130. package/src/runtime/diagnostics/message-flow.ts +1 -1
  131. package/src/runtime/foundation/raw-service-mesh-runtime.ts +268 -70
  132. package/src/runtime/foundation/service-liveness-registry.ts +68 -59
  133. package/src/runtime/foundation/service-mailbox.ts +7 -1
  134. package/src/runtime/foundation/service-stateful-runtime.ts +109 -77
  135. package/src/runtime/foundation/service-stateful-wire-codec.ts +55 -79
  136. package/src/runtime/foundation/service-topology-registry.ts +13 -7
  137. package/src/runtime/handlers/handler-instance-scope.ts +2 -1
  138. package/src/runtime/host/actor-packet-relay.ts +7 -0
  139. package/src/runtime/host/actor-placement-coordinator.ts +8 -2
  140. package/src/runtime/host/actor-runtime-options-factory.ts +7 -0
  141. package/src/runtime/host/application-job-queue.ts +77 -10
  142. package/src/runtime/host/entry-actor-runtime.ts +1 -0
  143. package/src/runtime/host/index.ts +30 -7
  144. package/src/runtime/host/instance-activation-authority.ts +132 -27
  145. package/src/runtime/host/service-relocation-host-runtime.ts +2 -0
  146. package/src/runtime/host/spot-address-transport.ts +45 -22
  147. package/src/runtime/host/spot-runtime-options-factory.ts +2 -0
  148. package/src/runtime/host/user-spot-creation-coordinator.ts +8 -0
  149. package/src/runtime/locations/domain-store-contract.ts +6 -2
  150. package/src/runtime/locations/in-memory-authority-store.ts +153 -12
  151. package/src/runtime/locations/in-memory-location-store.ts +39 -13
  152. package/src/runtime/locations/internal-location-contracts.ts +10 -0
  153. package/src/runtime/locations/location-store-repository.ts +295 -36
  154. package/src/runtime/locations/resolvers.ts +25 -2
  155. package/src/runtime/messaging/submission-result.ts +2 -2
  156. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +2 -0
  157. package/src/runtime/spots/index.ts +16 -0
  158. package/src/runtime/spots/spot-activation.ts +5 -0
  159. package/src/runtime/spots/spot-actor-admission-coordinator.ts +13 -0
  160. package/src/runtime/spots/spot-actor-packet-dispatch.ts +10 -0
  161. package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
  162. package/src/runtime/spots/spot-actor-packet-relay-dispatch.ts +1 -0
  163. package/src/runtime/spots/spot-entry-activation.ts +7 -0
  164. package/src/runtime/spots/spot-node-runtime-manager.ts +14 -12
  165. package/src/runtime/spots/spot-routing-internal.ts +14 -0
  166. package/src/runtime/spots/spot-runtime-ports.ts +2 -0
  167. package/src/runtime/spots/spot-serial-executor.ts +10 -4
  168. package/src/runtime/streams/stream-session-runtime.ts +1 -1
@@ -7,6 +7,7 @@ import { UINT64_MAX } from '@zlink-systems/stream-wire';
7
7
  import { ZLINK_MAX_ROUTING_ID_BYTES } from '../../contracts/Common/CoreTypes';
8
8
  import { ZLINK_PROVIDER_MAX_PAGE_SIZE } from '../../contracts/Locations/Stores';
9
9
  import { randomUUID } from 'node:crypto';
10
+ import { ZLinkFrameworkException, ZLinkFrameworkErrorKind } from '../../contracts';
10
11
  import { SHA256_DIGEST_BYTES } from '../foundation/actor-join-recovery-codec';
11
12
  import type {
12
13
  ZLinkAggregateAbortResult,
@@ -31,6 +32,7 @@ import type {
31
32
  ZLinkObjectCommitResult,
32
33
  ZLinkObjectCreationCompleteRequest,
33
34
  ZLinkObjectCreationCompleteResult,
35
+ ZLinkEndedOwnerReleaseIntent,
34
36
  ZLinkObjectReserveRequest,
35
37
  ZLinkObjectReserveResult,
36
38
  ZLinkCreationOperationIdentity,
@@ -41,13 +43,22 @@ import type {
41
43
  } from './internal-location-contracts';
42
44
  import { encodeAuthorityKey } from './authority-key-codec';
43
45
  import { creationTerminalPreimage } from './opaque-record-key';
46
+ import {
47
+ ServiceRelocationAuthorityPayloadCodec,
48
+ serviceRelocationAuthorityApplicationPayload
49
+ } from '../foundation/service-relocation-runtime';
50
+ import { decodeServiceReadySpotAuthority } from '../foundation/service-authority-payload-codec';
51
+ import { decodeActorAuthorityPayload } from '../actors/actor-authority-payload-codec';
52
+ import { ZLinkSpotKind } from '../../contracts';
44
53
 
45
54
  const MAX_GENERATION = 0x7fff_ffff_ffff_ffffn;
46
55
 
47
56
  const CREATION_TERMINAL_RETENTION_MS = 5 * 60 * 1000;
48
57
  const MAX_U64 = UINT64_MAX;
58
+ const relocationAuthorityCodec = new ServiceRelocationAuthorityPayloadCodec();
49
59
 
50
60
  export interface ZLinkInMemoryAuthorityValidation {
61
+ isOwnerLive(owner: ZLinkLocationOwnerToken): boolean;
51
62
  isTargetLive(
52
63
  descriptor: ZLinkMeshNodeDescriptorKey,
53
64
  lifecycleGeneration: bigint,
@@ -60,6 +71,7 @@ export interface ZLinkInMemoryAuthorityValidation {
60
71
  currentActive: ZLinkCapacityVector
61
72
  ): boolean;
62
73
  identityClaimed?(authorityKey: string): boolean;
74
+ hasSpotMembers?(spotId: string, objectGeneration: bigint): boolean;
63
75
  }
64
76
 
65
77
  interface AuthorityRow {
@@ -199,6 +211,26 @@ export class ZLinkInMemoryAuthorityStore {
199
211
  if (!this.isOwnerLive(row.snapshot)) {
200
212
  return { kind: 'conflict', current: this.read(keyValue) };
201
213
  }
214
+ const actor =
215
+ row.snapshot.allocation.objectKind === 'actor'
216
+ ? decodeActorAuthorityPayload(
217
+ serviceRelocationAuthorityApplicationPayload(mutation.payload)
218
+ )
219
+ : undefined;
220
+ const previousActor =
221
+ actor === undefined
222
+ ? undefined
223
+ : decodeActorAuthorityPayload(
224
+ serviceRelocationAuthorityApplicationPayload(row.snapshot.payload)
225
+ );
226
+ if (
227
+ actor?.currentSpotKind === ZLinkSpotKind.User &&
228
+ (previousActor?.currentSpotKind !== ZLinkSpotKind.User ||
229
+ previousActor.currentSpotId !== actor.currentSpotId ||
230
+ previousActor.currentSpotGeneration !== actor.currentSpotGeneration) &&
231
+ !this.acceptsUserSpotMembership(actor.currentSpotId, actor.currentSpotGeneration)
232
+ )
233
+ return { kind: 'conflict', current: this.read(keyValue) };
202
234
  if (
203
235
  mutation.generationTransition === 'reincarnate' &&
204
236
  (this.objectGeneration >= MAX_GENERATION || this.ownerGeneration >= MAX_GENERATION)
@@ -279,22 +311,91 @@ export class ZLinkInMemoryAuthorityStore {
279
311
  async releaseEndedReservation(
280
312
  key: ZLinkAuthorityKey,
281
313
  expectedStoreVersion: string,
282
- signal?: AbortSignal
314
+ signal?: AbortSignal,
315
+ request?: ZLinkEndedOwnerReleaseIntent
283
316
  ): Promise<boolean> {
284
317
  signal?.throwIfAborted();
285
- return this.tryReleaseEndedReservation(key, expectedStoreVersion);
318
+ return this.tryReleaseEndedReservation(key, expectedStoreVersion, request);
319
+ }
320
+
321
+ acceptsUserSpotMembership(spotId: string, objectGeneration: bigint): boolean {
322
+ const row = this.rows.get(encodeAuthorityKey('user_spot', spotId).value);
323
+ return (
324
+ row !== undefined &&
325
+ row.snapshot.allocation.objectKind === 'user_spot' &&
326
+ row.snapshot.allocation.state === 'active' &&
327
+ row.snapshot.objectGeneration === objectGeneration &&
328
+ decodeServiceReadySpotAuthority(row.snapshot.payload)?.kind === 'user_spot' &&
329
+ this.isOwnerLive(row.snapshot)
330
+ );
286
331
  }
287
332
 
288
333
  private tryReleaseEndedReservation(
289
334
  key: ZLinkAuthorityKey,
290
- expectedStoreVersion: string
335
+ expectedStoreVersion: string,
336
+ request?: ZLinkEndedOwnerReleaseIntent
291
337
  ): boolean {
292
338
  const current = this.rows.get(key.value);
293
339
  if (
294
340
  current === undefined ||
295
341
  current.snapshot.storeVersion.value !== expectedStoreVersion ||
296
- current.snapshot.allocation.state !== 'reserved' ||
297
- current.creation === undefined ||
342
+ (current.snapshot.allocation.state === 'active'
343
+ ? request === undefined
344
+ : current.creation === undefined)
345
+ )
346
+ return false;
347
+ if (current.snapshot.allocation.state === 'active') {
348
+ let unavailable = false;
349
+ for (const aggregate of this.aggregates.values()) {
350
+ if (
351
+ aggregate.state === 'prepared' &&
352
+ aggregate.request.participants.some(
353
+ (participant) => participant.authorityKey.value === key.value
354
+ )
355
+ )
356
+ unavailable = true;
357
+ }
358
+ unavailable ||= relocationAuthorityCodec.read(current.snapshot.payload) !== undefined;
359
+ if (
360
+ this.validation.isOwnerLive({
361
+ ownerId: current.snapshot.ownerId,
362
+ leaseGeneration: current.snapshot.ownerLeaseGeneration
363
+ })
364
+ )
365
+ return false;
366
+ const spot =
367
+ current.snapshot.allocation.objectKind === 'actor'
368
+ ? undefined
369
+ : decodeServiceReadySpotAuthority(current.snapshot.payload);
370
+ if (current.snapshot.allocation.objectKind !== 'actor') {
371
+ unavailable ||=
372
+ spot === undefined ||
373
+ spot.kind !== request?.key.kind ||
374
+ spot.stableType !== request.intent.stableType ||
375
+ spot.spotId !== request.key.globalId ||
376
+ spot.activationRecovery !== undefined;
377
+ if (spot?.kind === 'user_spot') {
378
+ unavailable ||=
379
+ this.validation.hasSpotMembers?.(spot.spotId, current.snapshot.objectGeneration) ===
380
+ true;
381
+ for (const row of this.rows.values()) {
382
+ if (row.snapshot.allocation.objectKind !== 'actor') continue;
383
+ const actor = decodeActorAuthorityPayload(
384
+ serviceRelocationAuthorityApplicationPayload(row.snapshot.payload)
385
+ );
386
+ unavailable ||=
387
+ actor?.currentSpotKind === ZLinkSpotKind.User &&
388
+ actor.currentSpotId === spot.spotId &&
389
+ actor.currentSpotGeneration === current.snapshot.objectGeneration;
390
+ }
391
+ }
392
+ }
393
+ if (unavailable || request?.actorRelocationPolicy !== 'disabled')
394
+ throw new ZLinkFrameworkException(
395
+ ZLinkFrameworkErrorKind.Unavailable,
396
+ 'Object owner lease is unavailable.'
397
+ );
398
+ } else if (
298
399
  this.validation.isTargetLive(
299
400
  current.snapshot.allocation.descriptor,
300
401
  current.snapshot.allocation.descriptorLifecycleGeneration,
@@ -305,9 +406,14 @@ export class ZLinkInMemoryAuthorityStore {
305
406
  )
306
407
  )
307
408
  return false;
308
- this.adjustCapacity(this.pendingCapacity, current.snapshot.allocation, -1);
409
+ this.adjustCapacity(
410
+ current.snapshot.allocation.state === 'active' ? this.activeCapacity : this.pendingCapacity,
411
+ current.snapshot.allocation,
412
+ -1
413
+ );
309
414
  this.rows.delete(key.value);
310
- this.creationTerminals.set(current.creation.reservationId, 'aborted');
415
+ if (current.snapshot.allocation.state === 'reserved' && current.creation !== undefined)
416
+ this.creationTerminals.set(current.creation.reservationId, 'aborted');
311
417
  this.scanRevision++;
312
418
  return true;
313
419
  }
@@ -330,16 +436,16 @@ export class ZLinkInMemoryAuthorityStore {
330
436
  ) {
331
437
  return { kind: 'typeMismatch', current: this.snapshot(current.snapshot) };
332
438
  }
333
- if (current.snapshot.allocation.state === 'active') {
334
- return { kind: 'alreadyExists', current: this.snapshot(current.snapshot) };
335
- }
336
439
  if (
337
440
  !this.tryReleaseEndedReservation(
338
441
  encodeAuthorityKey(request.key.kind, request.key.globalId),
339
- current.snapshot.storeVersion.value
442
+ current.snapshot.storeVersion.value,
443
+ request
340
444
  )
341
445
  )
342
- return { kind: 'conflict', current: this.read(key) };
446
+ return current.snapshot.allocation.state === 'active'
447
+ ? { kind: 'alreadyExists', current: this.snapshot(current.snapshot) }
448
+ : { kind: 'conflict', current: this.read(key) };
343
449
  }
344
450
  const target = creationTarget(request);
345
451
  if (
@@ -495,6 +601,17 @@ export class ZLinkInMemoryAuthorityStore {
495
601
  if (!this.creationTerminalAvailable(terminalRecord)) {
496
602
  return { kind: 'stale' };
497
603
  }
604
+ const actor =
605
+ request.completion.kind === 'created'
606
+ ? decodeActorAuthorityPayload(
607
+ serviceRelocationAuthorityApplicationPayload(request.completion.readyPayload)
608
+ )
609
+ : undefined;
610
+ if (
611
+ actor?.currentSpotKind === ZLinkSpotKind.User &&
612
+ !this.acceptsUserSpotMembership(actor.currentSpotId, actor.currentSpotGeneration)
613
+ )
614
+ return { kind: 'stale' };
498
615
  const nextVersion = this.tryNextStoreVersion();
499
616
  if (nextVersion === undefined) return { kind: 'generationExhausted' };
500
617
  const pending = row.snapshot.allocation;
@@ -661,6 +778,30 @@ export class ZLinkInMemoryAuthorityStore {
661
778
  ) {
662
779
  return { kind: 'stale' };
663
780
  }
781
+ const actor =
782
+ row.snapshot.allocation.objectKind === 'actor'
783
+ ? decodeActorAuthorityPayload(
784
+ serviceRelocationAuthorityApplicationPayload(participant.authorityPayload)
785
+ )
786
+ : undefined;
787
+ if (actor?.currentSpotKind === ZLinkSpotKind.User) {
788
+ const participantSpot = aggregate.request.participants.find(
789
+ (candidate) =>
790
+ candidate.authorityKey.value ===
791
+ encodeAuthorityKey('user_spot', actor.currentSpotId).value
792
+ );
793
+ if (participantSpot === undefined) {
794
+ if (!this.acceptsUserSpotMembership(actor.currentSpotId, actor.currentSpotGeneration))
795
+ return { kind: 'stale' };
796
+ } else {
797
+ const spot = this.rows.get(participantSpot.authorityKey.value);
798
+ if (
799
+ spot?.snapshot.allocation.objectKind !== 'user_spot' ||
800
+ spot.snapshot.objectGeneration !== actor.currentSpotGeneration
801
+ )
802
+ return { kind: 'stale' };
803
+ }
804
+ }
664
805
  if (participant.ownerTransition === 'newOwner') {
665
806
  if (
666
807
  !this.validation.isTargetLive(
@@ -1,4 +1,5 @@
1
1
  import type { RoutingId } from '../../contracts/Common';
2
+ import { ZLinkSpotKind } from '../../contracts';
2
3
  import { ZLINK_MAX_IDENTITY_TEXT_BYTES } from '../../contracts/Common/CoreTypes';
3
4
  import {
4
5
  isValidPublicWeight,
@@ -58,6 +59,7 @@ import {
58
59
  type ZLinkObjectCommitResult,
59
60
  type ZLinkObjectCreationCompleteRequest,
60
61
  type ZLinkObjectCreationCompleteResult,
62
+ type ZLinkEndedOwnerReleaseIntent,
61
63
  type ZLinkObjectReserveRequest,
62
64
  type ZLinkObjectReserveResult,
63
65
  type ZLinkOwnerLeaseClaimResult,
@@ -99,11 +101,14 @@ export class ZLinkInMemoryLocationStore
99
101
  private readonly authority: ZLinkInMemoryAuthorityStore;
100
102
 
101
103
  constructor(private readonly now: () => Date = () => new Date()) {
104
+ const isOwnerLive = (owner: ZLinkLocationOwnerToken): boolean =>
105
+ this.liveOwnerLease(owner.ownerId, this.now())?.token.leaseGeneration ===
106
+ owner.leaseGeneration;
102
107
  this.authority = new ZLinkInMemoryAuthorityStore(
103
108
  {
109
+ isOwnerLive,
104
110
  isTargetLive: (key, lifecycleGeneration, owner) => {
105
111
  const descriptor = this.meshNodes.rows.get(meshNodeKey(key.meshName, key.rid));
106
- const lease = this.leases.get(owner.ownerId);
107
112
  return (
108
113
  descriptor !== undefined &&
109
114
  descriptor.lifecycleGeneration === lifecycleGeneration &&
@@ -111,9 +116,7 @@ export class ZLinkInMemoryLocationStore
111
116
  descriptor.leaseGeneration === owner.leaseGeneration &&
112
117
  descriptor.objectRole === ZLinkObjectRole.Server &&
113
118
  descriptor.state === ZLinkFrameworkRuntimeState.Serving &&
114
- lease !== undefined &&
115
- lease.token.leaseGeneration === owner.leaseGeneration &&
116
- lease.leaseExpiresAt.getTime() > this.now().getTime()
119
+ isOwnerLive(owner)
117
120
  );
118
121
  },
119
122
  placementCapacityAvailable: (key, requested, reserved, active) => {
@@ -142,7 +145,18 @@ export class ZLinkInMemoryLocationStore
142
145
  typeCapacity.limit)))
143
146
  );
144
147
  },
145
- identityClaimed: (authorityKey) => this.entrySpotClaims.has(authorityKey)
148
+ identityClaimed: (authorityKey) => this.entrySpotClaims.has(authorityKey),
149
+ hasSpotMembers: (spotId, objectGeneration) => {
150
+ for (const actor of this.actors.rows.values()) {
151
+ if (
152
+ actor.spotKind === ZLinkSpotKind.User &&
153
+ String(actor.spotId) === spotId &&
154
+ actor.spotGeneration === objectGeneration
155
+ )
156
+ return true;
157
+ }
158
+ return false;
159
+ }
146
160
  },
147
161
  now
148
162
  );
@@ -204,9 +218,10 @@ export class ZLinkInMemoryLocationStore
204
218
  async releaseEndedReservation(
205
219
  key: ZLinkAuthorityKey,
206
220
  expectedStoreVersion: string,
207
- signal?: AbortSignal
221
+ signal?: AbortSignal,
222
+ request?: ZLinkEndedOwnerReleaseIntent
208
223
  ): Promise<boolean> {
209
- return this.authority.releaseEndedReservation(key, expectedStoreVersion, signal);
224
+ return this.authority.releaseEndedReservation(key, expectedStoreVersion, signal, request);
210
225
  }
211
226
 
212
227
  async abort(
@@ -566,7 +581,8 @@ export class ZLinkInMemoryLocationStore
566
581
  const storeNow = this.now();
567
582
  return pageRows(
568
583
  this.fanoutPublishers,
569
- (row) => row.channelName === channelName && this.isOwnerLive(row.ownerId, storeNow),
584
+ (row) =>
585
+ row.channelName === channelName && this.liveOwnerLease(row.ownerId, storeNow) !== undefined,
570
586
  page
571
587
  );
572
588
  }
@@ -628,7 +644,7 @@ export class ZLinkInMemoryLocationStore
628
644
  if (
629
645
  intent === ZLinkLocationWriteIntent.NewClaim &&
630
646
  current !== undefined &&
631
- this.isOwnerLive(current.ownerId, updatedAt)
647
+ this.liveOwnerLease(current.ownerId, updatedAt) !== undefined
632
648
  ) {
633
649
  return rejectedConflict();
634
650
  }
@@ -703,10 +719,18 @@ export class ZLinkInMemoryLocationStore
703
719
  });
704
720
  const updatedAt = this.now();
705
721
  const current = this.actors.rows.get(key);
722
+ if (
723
+ actor.spotKind === ZLinkSpotKind.User &&
724
+ (current?.spotKind !== ZLinkSpotKind.User ||
725
+ String(current.spotId) !== String(actor.spotId) ||
726
+ current.spotGeneration !== actor.spotGeneration) &&
727
+ !this.authority.acceptsUserSpotMembership(String(actor.spotId), actor.spotGeneration)
728
+ )
729
+ return rejectedConflict();
706
730
  if (
707
731
  intent === ZLinkLocationWriteIntent.NewClaim &&
708
732
  current !== undefined &&
709
- this.isOwnerLive(current.ownerId, updatedAt)
733
+ this.liveOwnerLease(current.ownerId, updatedAt) !== undefined
710
734
  ) {
711
735
  return rejectedConflict();
712
736
  }
@@ -1155,7 +1179,7 @@ export class ZLinkInMemoryLocationStore
1155
1179
  if (
1156
1180
  intent === ZLinkLocationWriteIntent.NewClaim &&
1157
1181
  current !== undefined &&
1158
- this.isOwnerLive(ownerOf(current), updatedAt)
1182
+ this.liveOwnerLease(ownerOf(current), updatedAt) !== undefined
1159
1183
  ) {
1160
1184
  return rejectedConflict();
1161
1185
  }
@@ -1252,9 +1276,11 @@ export class ZLinkInMemoryLocationStore
1252
1276
  return removed;
1253
1277
  }
1254
1278
 
1255
- private isOwnerLive(ownerId: string, now: Date): boolean {
1279
+ private liveOwnerLease(ownerId: string, now: Date): InMemoryOwnerLease | undefined {
1256
1280
  const lease = this.leases.get(ownerId);
1257
- return lease !== undefined && lease.leaseExpiresAt.getTime() > now.getTime();
1281
+ return lease !== undefined && lease.leaseExpiresAt.getTime() > now.getTime()
1282
+ ? lease
1283
+ : undefined;
1258
1284
  }
1259
1285
 
1260
1286
  private bump(kind: ZLinkLocationKind, meshName: string | undefined): void {
@@ -1,4 +1,14 @@
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
+
5
+ export interface ZLinkObjectReserveRequest extends ReserveRequest {
6
+ readonly actorRelocationPolicy?: ZLinkRelocationConfiguration<unknown>['kind'];
7
+ }
8
+ export type ZLinkEndedOwnerReleaseIntent = Pick<
9
+ ZLinkObjectReserveRequest,
10
+ 'key' | 'actorRelocationPolicy'
11
+ > & { readonly intent: Pick<ZLinkObjectReserveRequest['intent'], 'stableType'> };
2
12
  export type * from '../../contracts/Locations/Authority';
3
13
  export type {
4
14
  ZLinkActorLocation,