@zlink-systems/framework 0.28.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 (112) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/Builders.d.ts +2 -0
  3. package/dist/contracts/Configuration/InternalDefaults.d.ts +3 -0
  4. package/dist/contracts/Configuration/InternalDefaults.js +4 -1
  5. package/dist/contracts/Configuration/RegistrationBuilders.js +9 -0
  6. package/dist/contracts/Configuration/RegistrationNormalizers.d.ts +1 -0
  7. package/dist/contracts/Configuration/RegistrationNormalizers.js +16 -0
  8. package/dist/contracts/Configuration/RegistrationTypes.d.ts +3 -0
  9. package/dist/runtime/actors/actor-mailbox.d.ts +2 -2
  10. package/dist/runtime/actors/actor-mailbox.js +2 -2
  11. package/dist/runtime/actors/actor-runtime-contracts.d.ts +1 -0
  12. package/dist/runtime/actors/index.d.ts +2 -0
  13. package/dist/runtime/actors/index.js +21 -6
  14. package/dist/runtime/backend/node/node-raw-mesh-backend.js +1 -3
  15. package/dist/runtime/execution/serial-execution-queue.d.ts +12 -3
  16. package/dist/runtime/execution/serial-execution-queue.js +116 -28
  17. package/dist/runtime/foundation/operation-registry.d.ts +3 -2
  18. package/dist/runtime/foundation/operation-registry.js +5 -1
  19. package/dist/runtime/foundation/service-authority-payload-codec.d.ts +0 -2
  20. package/dist/runtime/foundation/service-authority-payload-codec.js +0 -43
  21. package/dist/runtime/foundation/service-stateful-registry.d.ts +4 -3
  22. package/dist/runtime/foundation/service-stateful-registry.js +5 -2
  23. package/dist/runtime/foundation/service-stateful-runtime.d.ts +1 -8
  24. package/dist/runtime/foundation/service-stateful-runtime.js +105 -145
  25. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -11
  26. package/dist/runtime/foundation/service-stateful-wire-codec.js +1 -23
  27. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -0
  28. package/dist/runtime/foundation/service-topology-registry.js +16 -17
  29. package/dist/runtime/handlers/handler-instance-scope.js +1 -1
  30. package/dist/runtime/host/actor-placement-coordinator.d.ts +2 -1
  31. package/dist/runtime/host/actor-placement-coordinator.js +2 -1
  32. package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -1
  33. package/dist/runtime/host/actor-runtime-options-factory.js +1 -0
  34. package/dist/runtime/host/index.d.ts +1 -1
  35. package/dist/runtime/host/index.js +3 -2
  36. package/dist/runtime/host/instance-activation-authority.d.ts +0 -1
  37. package/dist/runtime/host/instance-activation-authority.js +46 -97
  38. package/dist/runtime/host/spot-address-transport.js +1 -1
  39. package/dist/runtime/host/stateful-authority-route-runtime.d.ts +1 -0
  40. package/dist/runtime/host/stateful-authority-route-runtime.js +17 -9
  41. package/dist/runtime/locations/domain-store-contract.d.ts +3 -1
  42. package/dist/runtime/locations/in-memory-authority-store.d.ts +3 -0
  43. package/dist/runtime/locations/in-memory-authority-store.js +47 -12
  44. package/dist/runtime/locations/in-memory-location-store.d.ts +2 -1
  45. package/dist/runtime/locations/in-memory-location-store.js +15 -10
  46. package/dist/runtime/locations/internal-location-contracts.d.ts +5 -0
  47. package/dist/runtime/locations/internal-store-contracts.d.ts +1 -1
  48. package/dist/runtime/locations/location-store-repository.d.ts +2 -0
  49. package/dist/runtime/locations/location-store-repository.js +130 -54
  50. package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
  51. package/dist/runtime/spots/spot-entry-activation.js +3 -0
  52. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +1 -0
  53. package/dist/runtime/spots/spot-node-runtime-manager.js +6 -0
  54. package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
  55. package/dist/runtime/spots/spot-serial-executor.js +14 -2
  56. package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
  57. package/dist/runtime/spots/spot-serial-turn-executor.js +5 -0
  58. package/dist/runtime/streams/index.d.ts +0 -1
  59. package/dist/runtime/streams/index.js +4 -3
  60. package/dist/runtime/streams/protocol.js +0 -9
  61. package/dist/runtime/streams/session-context.d.ts +0 -4
  62. package/dist/runtime/streams/session-context.js +0 -26
  63. package/dist/runtime/streams/session-serial-executor.d.ts +0 -1
  64. package/dist/runtime/streams/session-serial-executor.js +0 -3
  65. package/dist/runtime/streams/stream-message-utils.d.ts +0 -1
  66. package/dist/runtime/streams/stream-message-utils.js +0 -20
  67. package/dist/runtime/streams/stream-session-runtime.d.ts +6 -3
  68. package/dist/runtime/streams/stream-session-runtime.js +40 -85
  69. package/package.json +3 -3
  70. package/src/contracts/Configuration/Builders.ts +2 -0
  71. package/src/contracts/Configuration/InternalDefaults.ts +4 -0
  72. package/src/contracts/Configuration/RegistrationBuilders.ts +13 -0
  73. package/src/contracts/Configuration/RegistrationNormalizers.ts +25 -0
  74. package/src/contracts/Configuration/RegistrationTypes.ts +3 -0
  75. package/src/runtime/actors/actor-mailbox.ts +7 -2
  76. package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
  77. package/src/runtime/actors/index.ts +77 -42
  78. package/src/runtime/backend/node/node-raw-mesh-backend.ts +1 -7
  79. package/src/runtime/execution/serial-execution-queue.ts +117 -29
  80. package/src/runtime/foundation/operation-registry.ts +16 -9
  81. package/src/runtime/foundation/service-authority-payload-codec.ts +0 -46
  82. package/src/runtime/foundation/service-stateful-registry.ts +10 -5
  83. package/src/runtime/foundation/service-stateful-runtime.ts +152 -224
  84. package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -25
  85. package/src/runtime/foundation/service-topology-registry.ts +30 -20
  86. package/src/runtime/handlers/handler-instance-scope.ts +2 -1
  87. package/src/runtime/host/actor-placement-coordinator.ts +8 -2
  88. package/src/runtime/host/actor-runtime-options-factory.ts +6 -0
  89. package/src/runtime/host/index.ts +6 -2
  90. package/src/runtime/host/instance-activation-authority.ts +58 -125
  91. package/src/runtime/host/spot-address-transport.ts +1 -1
  92. package/src/runtime/host/stateful-authority-route-runtime.ts +22 -9
  93. package/src/runtime/locations/domain-store-contract.ts +6 -1
  94. package/src/runtime/locations/in-memory-authority-store.ts +83 -12
  95. package/src/runtime/locations/in-memory-location-store.ts +22 -10
  96. package/src/runtime/locations/internal-location-contracts.ts +6 -0
  97. package/src/runtime/locations/internal-store-contracts.ts +1 -0
  98. package/src/runtime/locations/location-store-repository.ts +182 -69
  99. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
  100. package/src/runtime/spots/spot-entry-activation.ts +4 -0
  101. package/src/runtime/spots/spot-node-runtime-manager.ts +7 -0
  102. package/src/runtime/spots/spot-serial-executor.ts +14 -5
  103. package/src/runtime/spots/spot-serial-turn-executor.ts +10 -0
  104. package/src/runtime/streams/index.ts +3 -1
  105. package/src/runtime/streams/protocol.ts +0 -13
  106. package/src/runtime/streams/session-context.ts +0 -32
  107. package/src/runtime/streams/session-serial-executor.ts +0 -8
  108. package/src/runtime/streams/stream-message-utils.ts +1 -27
  109. package/src/runtime/streams/stream-session-runtime.ts +54 -97
  110. package/dist/runtime/streams/session-requests.d.ts +0 -25
  111. package/dist/runtime/streams/session-requests.js +0 -101
  112. package/src/runtime/streams/session-requests.ts +0 -110
@@ -167,8 +167,6 @@ export type ServiceWireRelocationObject =
167
167
  readonly objectGeneration: bigint;
168
168
  };
169
169
 
170
- /** Shared CRC-32C wire bounds for the direct relocation payload transfer. */
171
- export const RELOCATION_PAYLOAD_TOTAL_LENGTH_MAX = 274_877_906_944n;
172
170
  export const RELOCATION_PAYLOAD_CHUNK_COUNT_MAX = 4096;
173
171
  export { RELOCATION_STATE_CHUNK_DATA_MAX_BYTES } from '../../contracts/Configuration/InternalDefaults';
174
172
 
@@ -2658,29 +2656,6 @@ export function decodeServiceWireFrozenRecord(bytes: Uint8Array): ServiceWireFro
2658
2656
  return { ...record, canonicalBytes: Buffer.from(bytes) };
2659
2657
  }
2660
2658
 
2661
- /**
2662
- * Consumes exactly one canonical frozen record at `offset`. Relocation
2663
- * saved-work is a vector of back-to-back records, so its boundary is the
2664
- * record grammar itself rather than an enclosing length field.
2665
- */
2666
- export function decodeServiceWireFrozenRecordPrefix(
2667
- bytes: Uint8Array,
2668
- offset = 0
2669
- ): { readonly record: ServiceWireFrozenRecord; readonly length: number } {
2670
- if (!Number.isSafeInteger(offset) || offset < 0 || offset >= bytes.byteLength) {
2671
- fail('Frozen record offset is outside the encoded bytes.');
2672
- }
2673
- const reader = new FrozenReader(bytes.subarray(offset));
2674
- const record = decodeServiceWireFrozenRecordFrom(reader);
2675
- return {
2676
- record: {
2677
- ...record,
2678
- canonicalBytes: Buffer.from(bytes.subarray(offset, offset + reader.position))
2679
- },
2680
- length: reader.position
2681
- };
2682
- }
2683
-
2684
2659
  function decodeServiceWireFrozenRecordFrom(reader: FrozenReader): ServiceWireFrozenRecord {
2685
2660
  const recordKind = reader.u8('recordKind');
2686
2661
  if (recordKind < 1 || recordKind > 14) fail('Invalid frozen record kind.');
@@ -305,35 +305,46 @@ export class ServiceTopologyRegistry {
305
305
  stableType: string,
306
306
  isReady: (descriptor: ServiceNodeDescriptor) => boolean = () => true
307
307
  ): ServiceNodeDescriptor | undefined {
308
+ return this.selectObjectPlacementWithStatus(stableType, isReady).descriptor;
309
+ }
310
+
311
+ selectObjectPlacementWithStatus(
312
+ stableType: string,
313
+ isReady: (descriptor: ServiceNodeDescriptor) => boolean = () => true
314
+ ): {
315
+ readonly status: ServiceObjectPlacementStatus;
316
+ readonly descriptor?: ServiceNodeDescriptor;
317
+ } {
308
318
  requireText(stableType, 'stableType');
309
- return this.selectWeightedCycle(
319
+ const candidates = this.objectPlacementCandidates(stableType, isReady);
320
+ if (candidates.status !== 'available') return { status: candidates.status };
321
+ const descriptor = this.selectWeightedCycle(
310
322
  `object:${stableType}`,
311
- () => {
312
- const capability = `object-type:${stableType}`;
313
- return [this.local, ...this.peersByRid.values()]
314
- .map((value) => ('descriptor' in value ? value.descriptor : value))
315
- .filter(
316
- (descriptor) =>
317
- descriptor.state === 'serving' &&
318
- descriptor.objectRole === 'server' &&
319
- descriptor.placementWeight > 0 &&
320
- descriptor.activeCapacityUsed < descriptor.activeCapacityLimit &&
321
- descriptor.pendingCapacityUsed < descriptor.pendingCapacityLimit &&
322
- descriptor.protocolCapabilities.includes(capability)
323
- )
324
- .map(cloneDescriptor);
325
- },
323
+ () => this.objectPlacementCandidates(stableType).eligible.map(cloneDescriptor),
326
324
  (descriptor) => descriptor.placementWeight,
327
325
  (descriptor) => descriptor.nodeRoutingId,
328
326
  (left, right) => compareOrdinal(left.nodeRoutingId, right.nodeRoutingId),
329
327
  isReady
330
328
  );
329
+ return descriptor === undefined
330
+ ? { status: 'unavailable' }
331
+ : { status: 'available', descriptor };
331
332
  }
332
333
 
333
334
  objectPlacementStatus(
334
335
  stableType: string,
335
336
  isReady: (descriptor: ServiceNodeDescriptor) => boolean = () => true
336
337
  ): ServiceObjectPlacementStatus {
338
+ return this.objectPlacementCandidates(stableType, isReady).status;
339
+ }
340
+
341
+ private objectPlacementCandidates(
342
+ stableType: string,
343
+ isReady: (descriptor: ServiceNodeDescriptor) => boolean = () => true
344
+ ): {
345
+ readonly status: ServiceObjectPlacementStatus;
346
+ readonly eligible: readonly ServiceNodeDescriptor[];
347
+ } {
337
348
  requireText(stableType, 'stableType');
338
349
  const capability = `object-type:${stableType}`;
339
350
  const supported = [
@@ -345,16 +356,15 @@ export class ServiceTopologyRegistry {
345
356
  descriptor.objectRole === 'server' &&
346
357
  descriptor.protocolCapabilities.includes(capability)
347
358
  );
348
- if (supported.length === 0) return 'unsupported';
359
+ if (supported.length === 0) return { status: 'unsupported', eligible: [] };
349
360
  const withCapacity = supported.filter(
350
361
  (descriptor) =>
351
362
  descriptor.activeCapacityUsed < descriptor.activeCapacityLimit &&
352
363
  descriptor.pendingCapacityUsed < descriptor.pendingCapacityLimit
353
364
  );
354
- if (withCapacity.length === 0) return 'capacity';
365
+ if (withCapacity.length === 0) return { status: 'capacity', eligible: [] };
355
366
  const weighted = withCapacity.filter((descriptor) => descriptor.placementWeight > 0);
356
- if (weighted.length === 0) return 'unsupported';
357
- return weighted.some(isReady) ? 'available' : 'unavailable';
367
+ return { status: weighted.some(isReady) ? 'available' : 'unavailable', eligible: weighted };
358
368
  }
359
369
 
360
370
  instanceSpotPlacementTypes(): readonly string[] {
@@ -212,7 +212,8 @@ class LifecycleHandlerInstanceScope {
212
212
  }
213
213
 
214
214
  async dispose(detachedTaskRunner: ZLinkDetachedTaskRunner): Promise<void> {
215
- const disposeFromActiveInvocation = activeLifecycleScope.getStore() === this;
215
+ const disposeFromActiveInvocation =
216
+ this.activeInvocations > 0 && activeLifecycleScope.getStore() === this;
216
217
  const disposal = this.beginDisposeCore();
217
218
  if (disposal.deferred !== undefined) {
218
219
  startOutsideLifecycleInvocation(() => {
@@ -11,7 +11,8 @@ import type {
11
11
  ZLinkAuthoritySnapshot,
12
12
  ZLinkCreationOperationIdentity,
13
13
  ZLinkLocationOwnerToken,
14
- ZLinkObjectReserveResult
14
+ ZLinkObjectReserveResult,
15
+ ZLinkObjectReserveRequest
15
16
  } from '../locations/internal-location-contracts';
16
17
  import type {
17
18
  ZLinkAuthorityStore,
@@ -57,6 +58,10 @@ export interface ZLinkActorPlacementTarget {
57
58
  }
58
59
 
59
60
  export interface ZLinkActorPlacementCoordinatorOptions {
61
+ readonly actorRelocationPolicy?: (
62
+ meshName: string,
63
+ stableType: string
64
+ ) => ZLinkObjectReserveRequest['actorRelocationPolicy'];
60
65
  readonly store: ZLinkObjectCreationStore & ZLinkAuthorityStore;
61
66
  readonly target: (
62
67
  meshName: string | undefined,
@@ -130,7 +135,8 @@ export class ZLinkActorPlacementCoordinator {
130
135
  },
131
136
  target: reservationTarget(target),
132
137
  creatingPayload,
133
- capacity: { actors: 1, spots: 0 }
138
+ capacity: { actors: 1, spots: 0 },
139
+ actorRelocationPolicy: this.options.actorRelocationPolicy?.(target.meshName, stableType)
134
140
  },
135
141
  deadline.signal,
136
142
  BigInt(deadlineUnixMs)
@@ -34,6 +34,10 @@ import type { ZLinkRemoteActorPacketTarget } from '../actors';
34
34
  import type { ZLinkActorJoinRelocation } from '../actors/actor-join-relocation';
35
35
 
36
36
  export interface ZLinkActorRuntimeOptionsFactoryOptions {
37
+ readonly terminateActorActivation: (
38
+ actor: ZLinkActor,
39
+ terminal: () => Promise<void>
40
+ ) => Promise<void>;
37
41
  readonly registration: ZLinkFrameworkRegistration;
38
42
  /** Message flow tracer, so a failed deferred Join records its cause on the flow. */
39
43
  readonly messageFlow?: () =>
@@ -101,6 +105,7 @@ export class ZLinkActorRuntimeOptionsFactory {
101
105
  | 'actorRefResolver'
102
106
  | 'actorCreatedNotifier'
103
107
  | 'actorDestroyedCleanup'
108
+ | 'terminateActorActivation'
104
109
  | 'publishActorAuthority'
105
110
  | 'locationLifecycle'
106
111
  | 'boundSessionFactory'
@@ -111,6 +116,7 @@ export class ZLinkActorRuntimeOptionsFactory {
111
116
  > {
112
117
  const actorTransferRegistry = this.options.actorTransferRegistry;
113
118
  return {
119
+ terminateActorActivation: this.options.terminateActorActivation,
114
120
  actorMeshNameProvider: this.options.actorMeshName,
115
121
  actorLeaveSpot: (meshName, spotId, actor, signal) => {
116
122
  const spotManager = this.options.spotManager();
@@ -2016,6 +2016,7 @@ export class ZLinkFrameworkRuntimeHost
2016
2016
  | 'actorRefResolver'
2017
2017
  | 'actorCreatedNotifier'
2018
2018
  | 'actorDestroyedCleanup'
2019
+ | 'terminateActorActivation'
2019
2020
  | 'locationLifecycle'
2020
2021
  | 'boundSessionFactory'
2021
2022
  | 'shutdownSignal'
@@ -2139,6 +2140,9 @@ export class ZLinkFrameworkRuntimeHost
2139
2140
  }
2140
2141
  this.actorPlacement ??= new ZLinkActorPlacementCoordinator({
2141
2142
  store: locationStore,
2143
+ actorRelocationPolicy: (meshName, stableType) =>
2144
+ this.options.registration.spotNodes.get(meshName)?.actorFactoryRegistrations?.[stableType]
2145
+ ?.relocation.kind,
2142
2146
  remoteCreate: (meshName, targetNodeRid, request, timeoutMs) => {
2143
2147
  const node = this.spotNodeRuntime?.meshNode(meshName);
2144
2148
  if (node === undefined) {
@@ -2194,14 +2198,12 @@ export class ZLinkFrameworkRuntimeHost
2194
2198
  const capability = descriptor.objectCapabilities.find(
2195
2199
  (candidate) => candidate.objectKind === 'actor' && candidate.stableType === stableType
2196
2200
  );
2197
- const actors = descriptor.populationCapacity.actors;
2198
2201
  return (
2199
2202
  descriptor.state === ZLinkFrameworkRuntimeState.Serving &&
2200
2203
  descriptor.objectRole === 'server' &&
2201
2204
  descriptor.placementWeight > 0 &&
2202
2205
  descriptor.entrySpotId !== undefined &&
2203
2206
  excludedNodeRids?.has(String(descriptor.rid)) !== true &&
2204
- (actors.limit === 0 || actors.active + actors.reserved < actors.limit) &&
2205
2207
  capability !== undefined
2206
2208
  );
2207
2209
  }
@@ -2592,6 +2594,8 @@ export class ZLinkFrameworkRuntimeHost
2592
2594
 
2593
2595
  private actorRuntimeOptionsFactory(): ZLinkActorRuntimeOptionsFactory {
2594
2596
  return new ZLinkActorRuntimeOptionsFactory({
2597
+ terminateActorActivation: (actor, terminal) =>
2598
+ this.spotNodeRuntime?.terminateActorActivation(actor, terminal) ?? terminal(),
2595
2599
  registration: this.options.registration,
2596
2600
  messageFlow: () => this.createDispatchErrorReporter(this.runtimeOrPreStartErrorSink).flow,
2597
2601
  routeTransport: this.routeTransport,
@@ -16,7 +16,7 @@ import type {
16
16
  ZLinkAuthorityStore,
17
17
  ZLinkObjectCreationStore
18
18
  } from '../locations/internal-store-contracts';
19
- import { ZLinkFrameworkException } from '../../contracts';
19
+ import { ZLinkFrameworkErrorKind, ZLinkFrameworkException } from '../../contracts';
20
20
  import {
21
21
  decodeServiceInstanceAuthorityPayload,
22
22
  decodeServiceReadySpotAuthority,
@@ -46,7 +46,6 @@ import {
46
46
 
47
47
  const CREATION_REQUEST_RETENTION_MS = 24 * 60 * 60 * 1_000;
48
48
  const MAX_CREATION_REQUEST_BYTES = 1024 * 1024;
49
- const ACTIVATION_JOIN_POLL_INTERVAL_MS = 10;
50
49
 
51
50
  interface PendingReservation {
52
51
  readonly reservationId: string;
@@ -115,87 +114,81 @@ export class ZLinkInstanceActivationAuthority implements ServiceAsyncInstanceAct
115
114
  }
116
115
  let reserved: Awaited<ReturnType<ZLinkObjectCreationStore['reserve']>>;
117
116
  try {
118
- for (;;) {
119
- reserved = await this.options.store.reserve(
120
- {
121
- key: { kind: 'instance_spot', globalId: target.targetSpotId },
122
- intent: {
123
- stableType: target.stableType,
124
- requestContentReference: stored.reference.value,
125
- requestSha256: requestHash,
126
- requestEncodedSize: BigInt(requestBytes.byteLength)
127
- },
128
- target: {
129
- meshName: this.options.meshName,
130
- nodeRid: target.targetNodeRid,
131
- nodeLifecycleGeneration: target.targetNodeGeneration,
132
- owner
133
- },
134
- creatingPayload: encodeServiceInstanceAuthorityPayload({
135
- state: 'coldActivating',
117
+ reserved = await this.options.store.reserve(
118
+ {
119
+ key: { kind: 'instance_spot', globalId: target.targetSpotId },
120
+ intent: {
121
+ stableType: target.stableType,
122
+ requestContentReference: stored.reference.value,
123
+ requestSha256: requestHash,
124
+ requestEncodedSize: BigInt(requestBytes.byteLength)
125
+ },
126
+ target: {
127
+ meshName: this.options.meshName,
128
+ nodeRid: target.targetNodeRid,
129
+ nodeLifecycleGeneration: target.targetNodeGeneration,
130
+ owner
131
+ },
132
+ creatingPayload: encodeServiceInstanceAuthorityPayload({
133
+ state: 'coldActivating',
134
+ stableType: target.stableType,
135
+ spotId: target.targetSpotId,
136
+ ownerId: owner.ownerId,
137
+ ownerLeaseGeneration: owner.leaseGeneration,
138
+ ownerMeshName: this.options.meshName,
139
+ ownerNodeRid: target.targetNodeRid,
140
+ ownerNodeGeneration: target.targetNodeGeneration
141
+ }),
142
+ capacity: {
143
+ actors: 0,
144
+ spots: 1,
145
+ spotType: {
146
+ objectKind: 'instance_spot',
136
147
  stableType: target.stableType,
137
- spotId: target.targetSpotId,
138
- ownerId: owner.ownerId,
139
- ownerLeaseGeneration: owner.leaseGeneration,
140
- ownerMeshName: this.options.meshName,
141
- ownerNodeRid: target.targetNodeRid,
142
- ownerNodeGeneration: target.targetNodeGeneration
143
- }),
144
- capacity: {
145
- actors: 0,
146
- spots: 1,
147
- spotType: {
148
- objectKind: 'instance_spot',
149
- stableType: target.stableType,
150
- count: 1
151
- }
148
+ count: 1
152
149
  }
153
- },
154
- undefined,
155
- activation.deadlineUnixMs
156
- );
157
- if (reserved.kind !== 'alreadyExists') break;
158
- this.options.metrics?.recordInstanceSpotClaimConflict(
159
- this.options.meshName,
160
- target.stableType,
161
- 'already_exists'
162
- );
163
- await this.deleteOrphan(stored.reference);
164
- return readyExisting(reserved.current, target);
165
- }
150
+ }
151
+ },
152
+ undefined,
153
+ activation.deadlineUnixMs
154
+ );
166
155
  } catch (error) {
167
156
  await this.deleteOrphan(stored.reference);
168
157
  throw error;
169
158
  }
170
- if (
171
- reserved.kind === 'conflict' &&
172
- reserved.current.kind === 'snapshot' &&
173
- reserved.current.allocation.objectKind === 'instance_spot' &&
174
- reserved.current.allocation.stableType === target.stableType &&
175
- reserved.current.allocation.state === 'reserved'
176
- ) {
177
- this.options.metrics?.recordInstanceSpotClaimConflict(
178
- this.options.meshName,
179
- target.stableType,
180
- 'activation_in_progress'
181
- );
182
- await this.deleteOrphan(stored.reference);
183
- return await this.awaitConcurrentActivation(target, activation.deadlineUnixMs);
184
- }
185
159
  if (reserved.kind !== 'reserved') {
160
+ const conflictReason =
161
+ reserved.kind === 'alreadyExists'
162
+ ? 'already_exists'
163
+ : reserved.kind === 'conflict' &&
164
+ reserved.current.kind === 'snapshot' &&
165
+ reserved.current.allocation.objectKind === 'instance_spot' &&
166
+ reserved.current.allocation.stableType === target.stableType &&
167
+ reserved.current.allocation.state === 'reserved'
168
+ ? 'activation_in_progress'
169
+ : reserved.kind;
186
170
  this.options.metrics?.recordInstanceSpotClaimConflict(
187
171
  this.options.meshName,
188
172
  target.stableType,
189
- reserved.kind
173
+ conflictReason
190
174
  );
191
175
  await this.deleteOrphan(stored.reference);
176
+ if (reserved.kind === 'typeMismatch') {
177
+ throw createInternalFrameworkException(
178
+ ZLinkFrameworkInternalErrorKind.SpotTypeMismatch,
179
+ `Instance activation reservation failed: ${reserved.kind}.`
180
+ );
181
+ }
192
182
  if (reserved.kind === 'placementCapacityExhausted') {
193
183
  throw createInternalFrameworkException(
194
184
  ZLinkFrameworkInternalErrorKind.PlacementCapacityExhausted,
195
185
  `Instance Spot placement capacity is exhausted for '${String(target.targetSpotId)}'.`
196
186
  );
197
187
  }
198
- throw new Error(`Instance activation reservation failed: ${reserved.kind}.`);
188
+ throw new ZLinkFrameworkException(
189
+ ZLinkFrameworkErrorKind.Unavailable,
190
+ `Instance activation reservation failed: ${reserved.kind}.`
191
+ );
199
192
  }
200
193
  const reservation: ServiceInstanceActivationReservation = {
201
194
  attempt: reserved.creating.objectGeneration,
@@ -455,34 +448,6 @@ export class ZLinkInstanceActivationAuthority implements ServiceAsyncInstanceAct
455
448
  }
456
449
  }
457
450
 
458
- private async awaitConcurrentActivation(
459
- target: ServiceInstanceActivationTarget,
460
- deadlineUnixMs: bigint
461
- ): Promise<Extract<ServiceInstanceAuthorityRead, { readonly kind: 'ready' }>> {
462
- const key = authorityKey(target.targetSpotId);
463
- const deadlineMs = performance.now() + Number(deadlineUnixMs - BigInt(Date.now()));
464
- while (performance.now() <= deadlineMs) {
465
- const current = await this.options.store.readAuthority(key);
466
- if (current.kind === 'snapshot') {
467
- if (
468
- current.allocation.objectKind !== 'instance_spot' ||
469
- current.allocation.stableType !== target.stableType
470
- ) {
471
- throw new Error('Concurrent Instance activation resolved to a different Spot type.');
472
- }
473
- if (current.allocation.state === 'active') {
474
- return readyExisting(current, target);
475
- }
476
- } else {
477
- throw new Error('Concurrent Instance activation ended before the Ready barrier.');
478
- }
479
- await waitForActivationJoin(deadlineMs);
480
- }
481
- throw new Error(
482
- `Concurrent Instance activation for '${target.targetSpotId}' did not reach Ready before deadline.`
483
- );
484
- }
485
-
486
451
  private requirePending(reservation: ServiceInstanceActivationReservation): PendingReservation {
487
452
  const pending = this.pending.get(reservation.token);
488
453
  if (pending === undefined || pending.creating.objectGeneration !== reservation.attempt) {
@@ -539,38 +504,6 @@ function readyRead(
539
504
  return { kind: 'ready', route: routeFromSnapshot(snapshot), authority: snapshot };
540
505
  }
541
506
 
542
- function readyExisting(
543
- snapshot: ZLinkAuthoritySnapshot,
544
- target: ServiceInstanceActivationTarget
545
- ): Extract<ServiceInstanceAuthorityRead, { readonly kind: 'ready' }> {
546
- const decoded =
547
- decodeServiceReadySpotAuthority(snapshot.payload) ??
548
- decodeServiceClosingSpotAuthority(snapshot.payload);
549
- if (
550
- decoded?.kind !== 'instance_spot' ||
551
- decoded.stableType !== target.stableType ||
552
- decoded.spotId !== target.targetSpotId ||
553
- snapshot.allocation.objectKind !== 'instance_spot' ||
554
- snapshot.allocation.state !== 'active' ||
555
- decoded.ownerId !== snapshot.ownerId ||
556
- decoded.ownerLeaseGeneration !== snapshot.ownerLeaseGeneration ||
557
- decoded.ownerMeshName !== snapshot.allocation.descriptor.meshName ||
558
- decoded.ownerNodeGeneration !== snapshot.allocation.descriptorLifecycleGeneration ||
559
- !routingIdsEqual(decoded.ownerNodeRid, snapshot.allocation.descriptor.rid)
560
- ) {
561
- throw new Error('Existing Instance authority is not a matching Ready allocation.');
562
- }
563
- return { kind: 'ready', route: routeFromSnapshot(snapshot), authority: snapshot };
564
- }
565
-
566
- async function waitForActivationJoin(deadlineMs: number): Promise<void> {
567
- const remaining = deadlineMs - performance.now();
568
- if (remaining <= 0) return;
569
- await new Promise<void>((resolve) => {
570
- setTimeout(resolve, Math.min(ACTIVATION_JOIN_POLL_INTERVAL_MS, remaining));
571
- });
572
- }
573
-
574
507
  function requireCommitIdentity(
575
508
  target: ServiceInstanceActivationTarget,
576
509
  reservation: ServiceInstanceActivationReservation,
@@ -576,7 +576,7 @@ export class ZLinkHostSpotAddressTransport implements ZLinkSpotAddressTransport
576
576
  if (stableType === undefined) {
577
577
  if (distinctTypes.length === 0) return { kind: 'unsupported' };
578
578
  throw createInternalFrameworkException(
579
- ZLinkFrameworkInternalErrorKind.InvalidConfiguration,
579
+ ZLinkFrameworkInternalErrorKind.InvalidOperation,
580
580
  'Instance Spot type is required when multiple types are registered.'
581
581
  );
582
582
  }
@@ -1,3 +1,4 @@
1
+ import { encodeAuthorityKey } from '../locations/authority-key-codec';
1
2
  import { createHash } from 'node:crypto';
2
3
  import type {
3
4
  ZLinkAuthorityKey,
@@ -175,12 +176,25 @@ export class ZLinkStatefulAuthorityRouteRuntime {
175
176
 
176
177
  const status = sink.status();
177
178
  for (const pending of snapshot.pending) {
179
+ if (pending.targetMeshName !== meshName) continue;
178
180
  if (
179
- pending.targetMeshName === meshName &&
180
181
  routingIdsEqual(status.routingId, pending.targetNodeRid) &&
181
182
  status.lifecycleGeneration === pending.targetNodeGeneration
182
183
  ) {
183
184
  await this.recoverPendingInstanceActivation(sink, pending, signal);
185
+ } else {
186
+ const release = this.options.creationStore?.releaseEndedReservation;
187
+ if (release === undefined)
188
+ throw new Error('Reservation lifecycle recovery requires a Location repository.');
189
+ if (
190
+ await release.call(
191
+ this.options.creationStore,
192
+ encodeAuthorityKey('instance_spot', pending.spotId),
193
+ pending.pending.storeVersion,
194
+ signal
195
+ )
196
+ )
197
+ await this.clearRecoveryRoot(pending.pending.requestReference, signal);
184
198
  }
185
199
  }
186
200
 
@@ -386,14 +400,7 @@ export class ZLinkStatefulAuthorityRouteRuntime {
386
400
  signal
387
401
  );
388
402
  if (aborted.kind === 'aborted' || aborted.kind === 'alreadyAborted') {
389
- try {
390
- await this.options.relocationStore?.delete(
391
- relocationBlobReference(recovery.pending.requestReference)
392
- );
393
- } catch {
394
- // The authority no longer publishes this root. Retention owns a
395
- // failed best-effort orphan cleanup.
396
- }
403
+ await this.clearRecoveryRoot(recovery.pending.requestReference, signal);
397
404
  }
398
405
  return;
399
406
  }
@@ -401,6 +408,12 @@ export class ZLinkStatefulAuthorityRouteRuntime {
401
408
  return;
402
409
  }
403
410
 
411
+ private async clearRecoveryRoot(reference: string, signal?: AbortSignal): Promise<void> {
412
+ if (this.options.relocationStore === undefined)
413
+ throw new Error('Instance activation recovery requires a Relocation Store.');
414
+ await this.options.relocationStore.delete(relocationBlobReference(reference), signal);
415
+ }
416
+
404
417
  private async readRecoveryEnvelope(
405
418
  recovery: ServiceActivationRecoveryState,
406
419
  signal?: AbortSignal
@@ -37,9 +37,9 @@ import type {
37
37
  ZLinkObjectCommitResult,
38
38
  ZLinkObjectCreationCompleteRequest,
39
39
  ZLinkObjectCreationCompleteResult,
40
- ZLinkObjectReserveRequest,
41
40
  ZLinkObjectReserveResult
42
41
  } from '../../contracts/Locations/Authority';
42
+ import type { ZLinkObjectReserveRequest } from './internal-location-contracts';
43
43
 
44
44
  /**
45
45
  * Framework-private domain repository. Provider implementations never
@@ -140,6 +140,11 @@ export interface ZLinkDomainLocationStore {
140
140
  request: ZLinkObjectCreationCompleteRequest,
141
141
  signal?: AbortSignal
142
142
  ): Promise<ZLinkObjectCreationCompleteResult>;
143
+ releaseEndedReservation?(
144
+ key: ZLinkAuthorityKey,
145
+ expectedStoreVersion: string,
146
+ signal?: AbortSignal
147
+ ): Promise<boolean>;
143
148
  abort(
144
149
  request: ZLinkObjectAbortRequest,
145
150
  signal?: AbortSignal,