@zlink-systems/framework 0.28.0 → 0.29.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 (87) 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/backend/node/node-raw-mesh-backend.js +1 -3
  12. package/dist/runtime/execution/serial-execution-queue.d.ts +12 -3
  13. package/dist/runtime/execution/serial-execution-queue.js +116 -28
  14. package/dist/runtime/foundation/operation-registry.d.ts +3 -2
  15. package/dist/runtime/foundation/operation-registry.js +5 -1
  16. package/dist/runtime/foundation/service-authority-payload-codec.d.ts +0 -2
  17. package/dist/runtime/foundation/service-authority-payload-codec.js +0 -43
  18. package/dist/runtime/foundation/service-stateful-registry.d.ts +4 -3
  19. package/dist/runtime/foundation/service-stateful-registry.js +5 -2
  20. package/dist/runtime/foundation/service-stateful-runtime.d.ts +1 -8
  21. package/dist/runtime/foundation/service-stateful-runtime.js +78 -126
  22. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -11
  23. package/dist/runtime/foundation/service-stateful-wire-codec.js +1 -23
  24. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -0
  25. package/dist/runtime/foundation/service-topology-registry.js +16 -17
  26. package/dist/runtime/host/instance-activation-authority.d.ts +0 -1
  27. package/dist/runtime/host/instance-activation-authority.js +46 -97
  28. package/dist/runtime/host/spot-address-transport.js +1 -1
  29. package/dist/runtime/host/stateful-authority-route-runtime.d.ts +1 -0
  30. package/dist/runtime/host/stateful-authority-route-runtime.js +17 -9
  31. package/dist/runtime/locations/domain-store-contract.d.ts +1 -0
  32. package/dist/runtime/locations/in-memory-authority-store.d.ts +2 -0
  33. package/dist/runtime/locations/in-memory-authority-store.js +22 -8
  34. package/dist/runtime/locations/in-memory-location-store.d.ts +1 -0
  35. package/dist/runtime/locations/in-memory-location-store.js +3 -0
  36. package/dist/runtime/locations/internal-store-contracts.d.ts +1 -1
  37. package/dist/runtime/locations/location-store-repository.d.ts +1 -0
  38. package/dist/runtime/locations/location-store-repository.js +92 -49
  39. package/dist/runtime/spots/spot-serial-executor.js +4 -1
  40. package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
  41. package/dist/runtime/spots/spot-serial-turn-executor.js +5 -0
  42. package/dist/runtime/streams/index.d.ts +0 -1
  43. package/dist/runtime/streams/index.js +4 -3
  44. package/dist/runtime/streams/protocol.js +0 -9
  45. package/dist/runtime/streams/session-context.d.ts +0 -4
  46. package/dist/runtime/streams/session-context.js +0 -26
  47. package/dist/runtime/streams/session-serial-executor.d.ts +0 -1
  48. package/dist/runtime/streams/session-serial-executor.js +0 -3
  49. package/dist/runtime/streams/stream-message-utils.d.ts +0 -1
  50. package/dist/runtime/streams/stream-message-utils.js +0 -20
  51. package/dist/runtime/streams/stream-session-runtime.d.ts +6 -3
  52. package/dist/runtime/streams/stream-session-runtime.js +40 -85
  53. package/package.json +3 -3
  54. package/src/contracts/Configuration/Builders.ts +2 -0
  55. package/src/contracts/Configuration/InternalDefaults.ts +4 -0
  56. package/src/contracts/Configuration/RegistrationBuilders.ts +13 -0
  57. package/src/contracts/Configuration/RegistrationNormalizers.ts +25 -0
  58. package/src/contracts/Configuration/RegistrationTypes.ts +3 -0
  59. package/src/runtime/actors/actor-mailbox.ts +7 -2
  60. package/src/runtime/backend/node/node-raw-mesh-backend.ts +1 -7
  61. package/src/runtime/execution/serial-execution-queue.ts +117 -29
  62. package/src/runtime/foundation/operation-registry.ts +16 -9
  63. package/src/runtime/foundation/service-authority-payload-codec.ts +0 -46
  64. package/src/runtime/foundation/service-stateful-registry.ts +10 -5
  65. package/src/runtime/foundation/service-stateful-runtime.ts +103 -181
  66. package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -25
  67. package/src/runtime/foundation/service-topology-registry.ts +30 -20
  68. package/src/runtime/host/instance-activation-authority.ts +58 -125
  69. package/src/runtime/host/spot-address-transport.ts +1 -1
  70. package/src/runtime/host/stateful-authority-route-runtime.ts +22 -9
  71. package/src/runtime/locations/domain-store-contract.ts +5 -0
  72. package/src/runtime/locations/in-memory-authority-store.ts +42 -8
  73. package/src/runtime/locations/in-memory-location-store.ts +8 -0
  74. package/src/runtime/locations/internal-store-contracts.ts +1 -0
  75. package/src/runtime/locations/location-store-repository.ts +123 -64
  76. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
  77. package/src/runtime/spots/spot-serial-executor.ts +4 -1
  78. package/src/runtime/spots/spot-serial-turn-executor.ts +10 -0
  79. package/src/runtime/streams/index.ts +3 -1
  80. package/src/runtime/streams/protocol.ts +0 -13
  81. package/src/runtime/streams/session-context.ts +0 -32
  82. package/src/runtime/streams/session-serial-executor.ts +0 -8
  83. package/src/runtime/streams/stream-message-utils.ts +1 -27
  84. package/src/runtime/streams/stream-session-runtime.ts +54 -97
  85. package/dist/runtime/streams/session-requests.d.ts +0 -25
  86. package/dist/runtime/streams/session-requests.js +0 -101
  87. package/src/runtime/streams/session-requests.ts +0 -110
@@ -11,7 +11,6 @@ const relocation_blob_1 = require("../locations/relocation-blob");
11
11
  const service_instance_activation_recovery_codec_1 = require("../foundation/service-instance-activation-recovery-codec");
12
12
  const CREATION_REQUEST_RETENTION_MS = 24 * 60 * 60 * 1_000;
13
13
  const MAX_CREATION_REQUEST_BYTES = 1024 * 1024;
14
- const ACTIVATION_JOIN_POLL_INTERVAL_MS = 10;
15
14
  class ZLinkInstanceActivationAuthority {
16
15
  options;
17
16
  pending = new Map();
@@ -48,68 +47,64 @@ class ZLinkInstanceActivationAuthority {
48
47
  }
49
48
  let reserved;
50
49
  try {
51
- for (;;) {
52
- reserved = await this.options.store.reserve({
53
- key: { kind: 'instance_spot', globalId: target.targetSpotId },
54
- intent: {
55
- stableType: target.stableType,
56
- requestContentReference: stored.reference.value,
57
- requestSha256: requestHash,
58
- requestEncodedSize: BigInt(requestBytes.byteLength)
59
- },
60
- target: {
61
- meshName: this.options.meshName,
62
- nodeRid: target.targetNodeRid,
63
- nodeLifecycleGeneration: target.targetNodeGeneration,
64
- owner
65
- },
66
- creatingPayload: (0, service_authority_payload_codec_1.encodeServiceInstanceAuthorityPayload)({
67
- state: 'coldActivating',
50
+ reserved = await this.options.store.reserve({
51
+ key: { kind: 'instance_spot', globalId: target.targetSpotId },
52
+ intent: {
53
+ stableType: target.stableType,
54
+ requestContentReference: stored.reference.value,
55
+ requestSha256: requestHash,
56
+ requestEncodedSize: BigInt(requestBytes.byteLength)
57
+ },
58
+ target: {
59
+ meshName: this.options.meshName,
60
+ nodeRid: target.targetNodeRid,
61
+ nodeLifecycleGeneration: target.targetNodeGeneration,
62
+ owner
63
+ },
64
+ creatingPayload: (0, service_authority_payload_codec_1.encodeServiceInstanceAuthorityPayload)({
65
+ state: 'coldActivating',
66
+ stableType: target.stableType,
67
+ spotId: target.targetSpotId,
68
+ ownerId: owner.ownerId,
69
+ ownerLeaseGeneration: owner.leaseGeneration,
70
+ ownerMeshName: this.options.meshName,
71
+ ownerNodeRid: target.targetNodeRid,
72
+ ownerNodeGeneration: target.targetNodeGeneration
73
+ }),
74
+ capacity: {
75
+ actors: 0,
76
+ spots: 1,
77
+ spotType: {
78
+ objectKind: 'instance_spot',
68
79
  stableType: target.stableType,
69
- spotId: target.targetSpotId,
70
- ownerId: owner.ownerId,
71
- ownerLeaseGeneration: owner.leaseGeneration,
72
- ownerMeshName: this.options.meshName,
73
- ownerNodeRid: target.targetNodeRid,
74
- ownerNodeGeneration: target.targetNodeGeneration
75
- }),
76
- capacity: {
77
- actors: 0,
78
- spots: 1,
79
- spotType: {
80
- objectKind: 'instance_spot',
81
- stableType: target.stableType,
82
- count: 1
83
- }
80
+ count: 1
84
81
  }
85
- }, undefined, activation.deadlineUnixMs);
86
- if (reserved.kind !== 'alreadyExists')
87
- break;
88
- this.options.metrics?.recordInstanceSpotClaimConflict(this.options.meshName, target.stableType, 'already_exists');
89
- await this.deleteOrphan(stored.reference);
90
- return readyExisting(reserved.current, target);
91
- }
82
+ }
83
+ }, undefined, activation.deadlineUnixMs);
92
84
  }
93
85
  catch (error) {
94
86
  await this.deleteOrphan(stored.reference);
95
87
  throw error;
96
88
  }
97
- if (reserved.kind === 'conflict' &&
98
- reserved.current.kind === 'snapshot' &&
99
- reserved.current.allocation.objectKind === 'instance_spot' &&
100
- reserved.current.allocation.stableType === target.stableType &&
101
- reserved.current.allocation.state === 'reserved') {
102
- this.options.metrics?.recordInstanceSpotClaimConflict(this.options.meshName, target.stableType, 'activation_in_progress');
103
- await this.deleteOrphan(stored.reference);
104
- return await this.awaitConcurrentActivation(target, activation.deadlineUnixMs);
105
- }
106
89
  if (reserved.kind !== 'reserved') {
107
- this.options.metrics?.recordInstanceSpotClaimConflict(this.options.meshName, target.stableType, reserved.kind);
90
+ const conflictReason = reserved.kind === 'alreadyExists'
91
+ ? 'already_exists'
92
+ : reserved.kind === 'conflict' &&
93
+ reserved.current.kind === 'snapshot' &&
94
+ reserved.current.allocation.objectKind === 'instance_spot' &&
95
+ reserved.current.allocation.stableType === target.stableType &&
96
+ reserved.current.allocation.state === 'reserved'
97
+ ? 'activation_in_progress'
98
+ : reserved.kind;
99
+ this.options.metrics?.recordInstanceSpotClaimConflict(this.options.meshName, target.stableType, conflictReason);
108
100
  await this.deleteOrphan(stored.reference);
101
+ if (reserved.kind === 'typeMismatch') {
102
+ throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.SpotTypeMismatch, `Instance activation reservation failed: ${reserved.kind}.`);
103
+ }
109
104
  if (reserved.kind === 'placementCapacityExhausted') {
110
105
  throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.PlacementCapacityExhausted, `Instance Spot placement capacity is exhausted for '${String(target.targetSpotId)}'.`);
111
106
  }
112
- throw new Error(`Instance activation reservation failed: ${reserved.kind}.`);
107
+ throw new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.Unavailable, `Instance activation reservation failed: ${reserved.kind}.`);
113
108
  }
114
109
  const reservation = {
115
110
  attempt: reserved.creating.objectGeneration,
@@ -334,27 +329,6 @@ class ZLinkInstanceActivationAuthority {
334
329
  // best-effort deletion fails.
335
330
  }
336
331
  }
337
- async awaitConcurrentActivation(target, deadlineUnixMs) {
338
- const key = authorityKey(target.targetSpotId);
339
- const deadlineMs = performance.now() + Number(deadlineUnixMs - BigInt(Date.now()));
340
- while (performance.now() <= deadlineMs) {
341
- const current = await this.options.store.readAuthority(key);
342
- if (current.kind === 'snapshot') {
343
- if (current.allocation.objectKind !== 'instance_spot' ||
344
- current.allocation.stableType !== target.stableType) {
345
- throw new Error('Concurrent Instance activation resolved to a different Spot type.');
346
- }
347
- if (current.allocation.state === 'active') {
348
- return readyExisting(current, target);
349
- }
350
- }
351
- else {
352
- throw new Error('Concurrent Instance activation ended before the Ready barrier.');
353
- }
354
- await waitForActivationJoin(deadlineMs);
355
- }
356
- throw new Error(`Concurrent Instance activation for '${target.targetSpotId}' did not reach Ready before deadline.`);
357
- }
358
332
  requirePending(reservation) {
359
333
  const pending = this.pending.get(reservation.token);
360
334
  if (pending === undefined || pending.creating.objectGeneration !== reservation.attempt) {
@@ -402,31 +376,6 @@ function readyRead(snapshot, target) {
402
376
  }
403
377
  return { kind: 'ready', route: routeFromSnapshot(snapshot), authority: snapshot };
404
378
  }
405
- function readyExisting(snapshot, target) {
406
- const decoded = (0, service_authority_payload_codec_1.decodeServiceReadySpotAuthority)(snapshot.payload) ??
407
- (0, service_authority_payload_codec_1.decodeServiceClosingSpotAuthority)(snapshot.payload);
408
- if (decoded?.kind !== 'instance_spot' ||
409
- decoded.stableType !== target.stableType ||
410
- decoded.spotId !== target.targetSpotId ||
411
- snapshot.allocation.objectKind !== 'instance_spot' ||
412
- snapshot.allocation.state !== 'active' ||
413
- decoded.ownerId !== snapshot.ownerId ||
414
- decoded.ownerLeaseGeneration !== snapshot.ownerLeaseGeneration ||
415
- decoded.ownerMeshName !== snapshot.allocation.descriptor.meshName ||
416
- decoded.ownerNodeGeneration !== snapshot.allocation.descriptorLifecycleGeneration ||
417
- !(0, routing_id_1.routingIdsEqual)(decoded.ownerNodeRid, snapshot.allocation.descriptor.rid)) {
418
- throw new Error('Existing Instance authority is not a matching Ready allocation.');
419
- }
420
- return { kind: 'ready', route: routeFromSnapshot(snapshot), authority: snapshot };
421
- }
422
- async function waitForActivationJoin(deadlineMs) {
423
- const remaining = deadlineMs - performance.now();
424
- if (remaining <= 0)
425
- return;
426
- await new Promise((resolve) => {
427
- setTimeout(resolve, Math.min(ACTIVATION_JOIN_POLL_INTERVAL_MS, remaining));
428
- });
429
- }
430
379
  function requireCommitIdentity(target, reservation, creating, spot, meshName) {
431
380
  if (creating.allocation.state !== 'reserved' ||
432
381
  creating.allocation.objectKind !== 'instance_spot' ||
@@ -254,7 +254,7 @@ class ZLinkHostSpotAddressTransport {
254
254
  if (stableType === undefined) {
255
255
  if (distinctTypes.length === 0)
256
256
  return { kind: 'unsupported' };
257
- throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.InvalidConfiguration, 'Instance Spot type is required when multiple types are registered.');
257
+ throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.InvalidOperation, 'Instance Spot type is required when multiple types are registered.');
258
258
  }
259
259
  if (canInspectPlacementTypes && !distinctTypes.includes(stableType)) {
260
260
  return { kind: 'unsupported' };
@@ -28,6 +28,7 @@ export declare class ZLinkStatefulAuthorityRouteRuntime {
28
28
  reconcile(signal?: AbortSignal, requireComplete?: boolean): Promise<void>;
29
29
  private recoverInstanceActivation;
30
30
  private recoverPendingInstanceActivation;
31
+ private clearRecoveryRoot;
31
32
  private readRecoveryEnvelope;
32
33
  private run;
33
34
  private readCompleteSnapshot;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ZLinkStatefulAuthorityRouteRuntime = void 0;
4
+ const authority_key_codec_1 = require("../locations/authority-key-codec");
4
5
  const node_crypto_1 = require("node:crypto");
5
6
  const relocation_blob_1 = require("../locations/relocation-blob");
6
7
  const routing_id_1 = require("../routing-id");
@@ -65,11 +66,19 @@ class ZLinkStatefulAuthorityRouteRuntime {
65
66
  const previous = this.appliedByMesh.get(meshName) ?? new Map();
66
67
  const status = sink.status();
67
68
  for (const pending of snapshot.pending) {
68
- if (pending.targetMeshName === meshName &&
69
- (0, routing_id_1.routingIdsEqual)(status.routingId, pending.targetNodeRid) &&
69
+ if (pending.targetMeshName !== meshName)
70
+ continue;
71
+ if ((0, routing_id_1.routingIdsEqual)(status.routingId, pending.targetNodeRid) &&
70
72
  status.lifecycleGeneration === pending.targetNodeGeneration) {
71
73
  await this.recoverPendingInstanceActivation(sink, pending, signal);
72
74
  }
75
+ else {
76
+ const release = this.options.creationStore?.releaseEndedReservation;
77
+ if (release === undefined)
78
+ throw new Error('Reservation lifecycle recovery requires a Location repository.');
79
+ if (await release.call(this.options.creationStore, (0, authority_key_codec_1.encodeAuthorityKey)('instance_spot', pending.spotId), pending.pending.storeVersion, signal))
80
+ await this.clearRecoveryRoot(pending.pending.requestReference, signal);
81
+ }
73
82
  }
74
83
  for (const [key, route] of current) {
75
84
  if (route.meshName !== meshName)
@@ -207,19 +216,18 @@ class ZLinkStatefulAuthorityRouteRuntime {
207
216
  }
208
217
  }, signal);
209
218
  if (aborted.kind === 'aborted' || aborted.kind === 'alreadyAborted') {
210
- try {
211
- await this.options.relocationStore?.delete((0, relocation_blob_1.relocationBlobReference)(recovery.pending.requestReference));
212
- }
213
- catch {
214
- // The authority no longer publishes this root. Retention owns a
215
- // failed best-effort orphan cleanup.
216
- }
219
+ await this.clearRecoveryRoot(recovery.pending.requestReference, signal);
217
220
  }
218
221
  return;
219
222
  }
220
223
  if ((await sink.recoverPendingInstanceActivation(envelope, recovery.pending, null)) === false)
221
224
  return;
222
225
  }
226
+ async clearRecoveryRoot(reference, signal) {
227
+ if (this.options.relocationStore === undefined)
228
+ throw new Error('Instance activation recovery requires a Relocation Store.');
229
+ await this.options.relocationStore.delete((0, relocation_blob_1.relocationBlobReference)(reference), signal);
230
+ }
223
231
  async readRecoveryEnvelope(recovery, signal) {
224
232
  const relocationStore = this.options.relocationStore;
225
233
  if (relocationStore === undefined) {
@@ -25,6 +25,7 @@ export interface ZLinkDomainLocationStore {
25
25
  reserve(request: ZLinkObjectReserveRequest, signal?: AbortSignal, deadlineUnixMs?: bigint): Promise<ZLinkObjectReserveResult>;
26
26
  commit(request: ZLinkObjectCommitRequest, signal?: AbortSignal, deadlineUnixMs?: bigint): Promise<ZLinkObjectCommitResult>;
27
27
  completeCreation(request: ZLinkObjectCreationCompleteRequest, signal?: AbortSignal): Promise<ZLinkObjectCreationCompleteResult>;
28
+ releaseEndedReservation?(key: ZLinkAuthorityKey, expectedStoreVersion: string, signal?: AbortSignal): Promise<boolean>;
28
29
  abort(request: ZLinkObjectAbortRequest, signal?: AbortSignal, deadlineUnixMs?: bigint): Promise<ZLinkObjectAbortResult>;
29
30
  prepareAggregate(request: ZLinkAggregatePrepareRequest, signal?: AbortSignal): Promise<ZLinkAggregatePrepareResult>;
30
31
  commitAggregate(fence: ZLinkAggregateFence, signal?: AbortSignal): Promise<ZLinkAggregateCommitResult>;
@@ -26,6 +26,8 @@ export declare class ZLinkInMemoryAuthorityStore {
26
26
  compareExchangeAuthority(key: ZLinkAuthorityKey, expectedStoreVersion: ZLinkAuthorityStoreVersion, mutation: ZLinkAuthorityMutation, signal?: AbortSignal): Promise<ZLinkAuthorityCompareExchangeResult>;
27
27
  listAuthorities(prefix: string, cursor: ZLinkAuthorityScanCursor | undefined, limit: number, signal?: AbortSignal): Promise<ZLinkAuthorityScanResult>;
28
28
  removeAllByOwner(owner: ZLinkLocationOwnerToken, signal?: AbortSignal): Promise<bigint>;
29
+ releaseEndedReservation(key: ZLinkAuthorityKey, expectedStoreVersion: string, signal?: AbortSignal): Promise<boolean>;
30
+ private tryReleaseEndedReservation;
29
31
  reserve(request: ZLinkObjectReserveRequest, signal?: AbortSignal): Promise<ZLinkObjectReserveResult>;
30
32
  commit(request: ZLinkObjectCommitRequest, signal?: AbortSignal): Promise<ZLinkObjectCommitResult>;
31
33
  completeCreation(request: ZLinkObjectCreationCompleteRequest, signal?: AbortSignal): Promise<ZLinkObjectCreationCompleteResult>;
@@ -171,6 +171,27 @@ class ZLinkInMemoryAuthorityStore {
171
171
  this.scanRevision++;
172
172
  return BigInt(removed);
173
173
  }
174
+ async releaseEndedReservation(key, expectedStoreVersion, signal) {
175
+ signal?.throwIfAborted();
176
+ return this.tryReleaseEndedReservation(key, expectedStoreVersion);
177
+ }
178
+ tryReleaseEndedReservation(key, expectedStoreVersion) {
179
+ const current = this.rows.get(key.value);
180
+ if (current === undefined ||
181
+ 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
+ ownerId: current.snapshot.ownerId,
186
+ leaseGeneration: current.snapshot.ownerLeaseGeneration
187
+ }))
188
+ return false;
189
+ this.adjustCapacity(this.pendingCapacity, current.snapshot.allocation, -1);
190
+ this.rows.delete(key.value);
191
+ this.creationTerminals.set(current.creation.reservationId, 'aborted');
192
+ this.scanRevision++;
193
+ return true;
194
+ }
174
195
  async reserve(request, signal) {
175
196
  signal?.throwIfAborted();
176
197
  validateReserve(request);
@@ -187,15 +208,8 @@ class ZLinkInMemoryAuthorityStore {
187
208
  if (current.snapshot.allocation.state === 'active') {
188
209
  return { kind: 'alreadyExists', current: this.snapshot(current.snapshot) };
189
210
  }
190
- // An unfinished creation whose owner lease ended is cancellable.
191
- // Reclaim it so the key does not stay blocked forever.
192
- if (this.isOwnerLive(current.snapshot) || current.creation === undefined) {
211
+ if (!this.tryReleaseEndedReservation((0, authority_key_codec_1.encodeAuthorityKey)(request.key.kind, request.key.globalId), current.snapshot.storeVersion.value))
193
212
  return { kind: 'conflict', current: this.read(key) };
194
- }
195
- this.adjustCapacity(this.pendingCapacity, current.snapshot.allocation, -1);
196
- this.rows.delete(key);
197
- this.creationTerminals.set(current.creation.reservationId, 'aborted');
198
- this.scanRevision++;
199
213
  }
200
214
  const target = creationTarget(request);
201
215
  if (!this.validation.isTargetLive(target.descriptor, target.lifecycleGeneration, target.owner)) {
@@ -25,6 +25,7 @@ export declare class ZLinkInMemoryLocationStore implements ZLinkDomainLocationSt
25
25
  commit(request: ZLinkObjectCommitRequest, signal?: AbortSignal): Promise<ZLinkObjectCommitResult>;
26
26
  completeCreation(request: ZLinkObjectCreationCompleteRequest, signal?: AbortSignal): Promise<ZLinkObjectCreationCompleteResult>;
27
27
  readCreationTerminal(operation: ZLinkCreationOperationIdentity, signal?: AbortSignal): Promise<ZLinkCreationTerminalReadResult>;
28
+ releaseEndedReservation(key: ZLinkAuthorityKey, expectedStoreVersion: string, signal?: AbortSignal): Promise<boolean>;
28
29
  abort(request: ZLinkObjectAbortRequest, signal?: AbortSignal): Promise<ZLinkObjectAbortResult>;
29
30
  prepareAggregate(request: ZLinkAggregatePrepareRequest, signal?: AbortSignal): Promise<ZLinkAggregatePrepareResult>;
30
31
  commitAggregate(fence: ZLinkAggregateFence, signal?: AbortSignal): Promise<ZLinkAggregateCommitResult>;
@@ -86,6 +86,9 @@ class ZLinkInMemoryLocationStore {
86
86
  async readCreationTerminal(operation, signal) {
87
87
  return this.authority.readCreationTerminal(operation, signal);
88
88
  }
89
+ async releaseEndedReservation(key, expectedStoreVersion, signal) {
90
+ return this.authority.releaseEndedReservation(key, expectedStoreVersion, signal);
91
+ }
89
92
  async abort(request, signal) {
90
93
  return this.authority.abort(request, signal);
91
94
  }
@@ -1,7 +1,7 @@
1
1
  import type { ZLinkActorLocation, ZLinkActorLocationFilter, ZLinkActorLocationKey, ZLinkLocationOwnerToken, ZLinkLocationPage, ZLinkLocationWriteIntent, ZLinkLocationWriteResult, ZLinkLocationWriteStatus, ZLinkPageRequest, ZLinkPeerLocation, ZLinkPeerLocationFilter, ZLinkPeerLocationKey, ZLinkRouteLocation, ZLinkRouteLocationFilter, ZLinkRouteLocationKey, ZLinkSpotLocation, ZLinkSpotLocationFilter, ZLinkSpotLocationKey } from './internal-location-contracts';
2
2
  import type { ZLinkDomainLocationStore } from './domain-store-contract';
3
3
  export type ZLinkAuthorityStore = Pick<ZLinkDomainLocationStore, 'readAuthority' | 'compareExchangeAuthority' | 'listAuthorities'>;
4
- export type ZLinkObjectCreationStore = Pick<ZLinkDomainLocationStore, 'readCreationTerminal' | 'reserve' | 'commit' | 'completeCreation' | 'abort' | 'prepareAggregate' | 'commitAggregate' | 'abortAggregate'>;
4
+ export type ZLinkObjectCreationStore = Pick<ZLinkDomainLocationStore, 'readCreationTerminal' | 'reserve' | 'commit' | 'completeCreation' | 'abort' | 'releaseEndedReservation' | 'prepareAggregate' | 'commitAggregate' | 'abortAggregate'>;
5
5
  export type ZLinkOwnerLeaseStore = Pick<ZLinkDomainLocationStore, 'claimOwnerLease' | 'readOwnerLease' | 'renewOwnerLease' | 'releaseOwnerLease'>;
6
6
  export type ZLinkMeshNodeLocationStore = Pick<ZLinkDomainLocationStore, 'updateMeshNode' | 'removeMeshNode' | 'listMeshNodes'>;
7
7
  export type ZLinkClientServerLocationStore = Pick<ZLinkDomainLocationStore, 'updateClientServer' | 'removeClientServer' | 'listClientServers'>;
@@ -20,6 +20,7 @@ export declare class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationS
20
20
  commitAggregate(fence: ZLinkAggregateFence, signal?: AbortSignal): Promise<ZLinkAggregateCommitResult>;
21
21
  private commitAggregateCore;
22
22
  abortAggregate(fence: ZLinkAggregateFence, signal?: AbortSignal): Promise<ZLinkAggregateAbortResult>;
23
+ releaseEndedReservation(key: ZLinkAuthorityKey, expectedStoreVersion: string, signal?: AbortSignal): Promise<boolean>;
23
24
  reserve(request: ZLinkObjectReserveRequest, signal?: AbortSignal, deadlineUnixMs?: bigint): Promise<ZLinkObjectReserveResult>;
24
25
  commit(request: ZLinkObjectCommitRequest, signal?: AbortSignal, deadlineUnixMs?: bigint): Promise<ZLinkObjectCommitResult>;
25
26
  abort(request: ZLinkObjectAbortRequest, signal?: AbortSignal, deadlineUnixMs?: bigint): Promise<ZLinkObjectAbortResult>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ZLinkLocationStoreRepository = void 0;
4
+ const service_relocation_runtime_1 = require("../foundation/service-relocation-runtime");
4
5
  const stream_wire_1 = require("@zlink-systems/stream-wire");
5
6
  const node_crypto_1 = require("node:crypto");
6
7
  const Options_1 = require("../../contracts/Locations/Options");
@@ -46,7 +47,33 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
46
47
  constructor(provider, nowProvider = () => new Date()) {
47
48
  super(nowProvider);
48
49
  this.nowProvider = nowProvider;
49
- this.provider = provider;
50
+ // Classify only failures of the opaque SPI, before interpreting its records.
51
+ this.provider = {
52
+ async read(key, signal) {
53
+ try {
54
+ return await provider.read(key, signal);
55
+ }
56
+ catch (error) {
57
+ throw locationStoreProviderFailure(error, signal);
58
+ }
59
+ },
60
+ async write(request, signal) {
61
+ try {
62
+ return await provider.write(request, signal);
63
+ }
64
+ catch (error) {
65
+ throw locationStoreProviderFailure(error, signal);
66
+ }
67
+ },
68
+ async scan(request, signal) {
69
+ try {
70
+ return await provider.scan(request, signal);
71
+ }
72
+ catch (error) {
73
+ throw locationStoreProviderFailure(error, signal);
74
+ }
75
+ }
76
+ };
50
77
  this.aggregateInventory = new aggregate_inventory_store_1.ZLinkAggregateInventoryStore(this.provider);
51
78
  }
52
79
  async readAuthority(key, signal) {
@@ -787,6 +814,57 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
787
814
  return { kind: 'aborted' };
788
815
  }
789
816
  }
817
+ async releaseEndedReservation(key, expectedStoreVersion, signal) {
818
+ const rowKey = authorityKey(key.value);
819
+ const current = await this.provider.read(rowKey, signal);
820
+ if (current.kind === 'missing' || current.value.version.value !== expectedStoreVersion)
821
+ return false;
822
+ const record = decodeAuthorityRecord(current.value.bytes);
823
+ const snapshot = record.snapshot;
824
+ if (snapshot.allocation.state !== 'reserved' ||
825
+ snapshot.pendingCreation === undefined ||
826
+ record.aggregate !== undefined ||
827
+ hasRelocationAuthority(snapshot.payload))
828
+ return false;
829
+ const leaseKey = ownerKey(snapshot.ownerId);
830
+ const descriptorKey = meshKey(snapshot.allocation.descriptor.meshName, snapshot.allocation.descriptor.rid);
831
+ const capacityRowKey = capacityKey(snapshot.allocation.descriptor.meshName, String(snapshot.allocation.descriptor.rid));
832
+ const [lease, descriptor, capacity] = await Promise.all([
833
+ this.provider.read(leaseKey, signal),
834
+ this.provider.read(descriptorKey, signal),
835
+ this.provider.read(capacityRowKey, signal)
836
+ ]);
837
+ if (lease.kind === 'found' && lease.value.expiresAt === undefined)
838
+ throw new Error('Location Store owner lease record is invalid.');
839
+ if (sameLiveOwner(lease, snapshot) &&
840
+ descriptor.kind === 'found' &&
841
+ reviveMeshDescriptor(decodeCanonicalDescriptorRecord(descriptor.value.bytes).descriptor).lifecycleGeneration === snapshot.allocation.descriptorLifecycleGeneration)
842
+ return false;
843
+ const mutations = [
844
+ { kind: 'delete', key: rowKey }
845
+ ];
846
+ if (capacity.kind === 'found') {
847
+ 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
+ });
856
+ }
857
+ const result = await this.provider.write({
858
+ conditions: [
859
+ { kind: 'version', key: rowKey, expected: current.value.version },
860
+ conditionFor(leaseKey, lease),
861
+ conditionFor(descriptorKey, descriptor),
862
+ conditionFor(capacityRowKey, capacity)
863
+ ],
864
+ mutations
865
+ }, signal);
866
+ return result.kind === 'applied';
867
+ }
790
868
  async reserve(request, signal, deadlineUnixMs) {
791
869
  const encodedAuthorityKey = (0, authority_key_codec_1.encodeAuthorityKey)(request.key.kind, request.key.globalId);
792
870
  const reservationId = (0, node_crypto_1.randomUUID)();
@@ -807,51 +885,8 @@ class ZLinkLocationStoreRepository extends in_memory_location_store_1.ZLinkInMem
807
885
  if (snapshot.allocation.state === 'active') {
808
886
  return { kind: 'alreadyExists', current: snapshot };
809
887
  }
810
- if (record.aggregate !== undefined ||
811
- isCanonicalAuthorityPayload(record.snapshot.payload)) {
812
- // A relocation record carries its own recovery protocol. Reserve
813
- // may reclaim only an unfinished plain creation.
814
- return { kind: 'conflict', current: snapshot };
815
- }
816
- // The row is Reserved by another owner. An unfinished creation
817
- // whose owner lease ended is cancellable, so reclaim it and retry
818
- // instead of blocking the key forever.
819
- const staleOwnerKey = ownerKey(snapshot.ownerId);
820
- const staleCapacityKey = capacityKey(snapshot.allocation.descriptor.meshName, String(snapshot.allocation.descriptor.rid));
821
- const [staleOwnerRead, staleCapacityRead] = await Promise.all([
822
- this.provider.read(staleOwnerKey, signal),
823
- this.provider.read(staleCapacityKey, signal)
824
- ]);
825
- if (staleOwnerRead.kind === 'found' && staleOwnerRead.value.expiresAt === undefined) {
826
- // A lease is always written with a positive TTL, so a missing
827
- // expiry is a corrupt record. Reclaiming deletes authority state,
828
- // so refuse rather than read the absence as expiry.
829
- throw new Error('Location Store owner lease record is invalid.');
830
- }
831
- if (sameLiveOwner(staleOwnerRead, record.snapshot)) {
888
+ if (!(await this.releaseEndedReservation(encodedAuthorityKey, current.value.version.value, signal)))
832
889
  return { kind: 'conflict', current: snapshot };
833
- }
834
- const staleCapacity = staleCapacityRead.kind === 'missing'
835
- ? emptyCapacityRecord()
836
- : decodeJson(staleCapacityRead.value.bytes);
837
- await this.provider.write({
838
- conditions: [
839
- { kind: 'version', key: rowKey, expected: current.value.version },
840
- versionCondition(staleOwnerKey, staleOwnerRead),
841
- conditionFor(staleCapacityKey, staleCapacityRead)
842
- ],
843
- mutations: [
844
- { kind: 'delete', key: rowKey },
845
- {
846
- kind: 'put',
847
- key: staleCapacityKey,
848
- bytes: encodeJson({
849
- active: staleCapacity.active,
850
- pending: subtractCapacity(staleCapacity.pending, record.snapshot.allocation.capacity)
851
- })
852
- }
853
- ]
854
- }, signal);
855
890
  continue;
856
891
  }
857
892
  const [descriptorRead, leaseRead] = await Promise.all([
@@ -2942,10 +2977,9 @@ function sameCreationTarget(snapshot, target) {
2942
2977
  snapshot.ownerId === target.owner.ownerId &&
2943
2978
  snapshot.ownerLeaseGeneration === target.owner.leaseGeneration);
2944
2979
  }
2945
- const CANONICAL_AUTHORITY_MAGIC = Buffer.from('ZLAU');
2946
- function isCanonicalAuthorityPayload(payload) {
2947
- return (payload.byteLength >= CANONICAL_AUTHORITY_MAGIC.byteLength &&
2948
- Buffer.from(payload.buffer, payload.byteOffset, CANONICAL_AUTHORITY_MAGIC.byteLength).equals(CANONICAL_AUTHORITY_MAGIC));
2980
+ const relocationAuthorityCodec = new service_relocation_runtime_1.ServiceRelocationAuthorityPayloadCodec();
2981
+ function hasRelocationAuthority(payload) {
2982
+ return relocationAuthorityCodec.read(payload) !== undefined;
2949
2983
  }
2950
2984
  function sameLiveOwner(lease, snapshot) {
2951
2985
  if (lease.kind === 'missing')
@@ -3239,3 +3273,12 @@ function throwIfOperationExpired(signal, deadlineUnixMs) {
3239
3273
  throw new DOMException('Operation deadline exceeded.', 'TimeoutError');
3240
3274
  }
3241
3275
  }
3276
+ // Store SPI §6: preserve caller failures and classify provider failures once.
3277
+ function locationStoreProviderFailure(error, signal) {
3278
+ if (error instanceof contracts_1.ZLinkFrameworkException ||
3279
+ error instanceof TypeError ||
3280
+ error instanceof RangeError ||
3281
+ signal?.aborted === true)
3282
+ return error;
3283
+ return new contracts_1.ZLinkFrameworkException(contracts_1.ZLinkFrameworkErrorKind.Unavailable, 'Location Store provider is unavailable.', error);
3284
+ }
@@ -96,7 +96,10 @@ class ZLinkSpotSerialExecutor {
96
96
  this.throwIfClosing();
97
97
  let executor = this.actorExecutors.get(actorId);
98
98
  if (executor === undefined) {
99
- executor = new actors_1.ZLinkActorSerialExecutor(actorId, this.sourceSpotId, this.actorOptions);
99
+ executor =
100
+ this.executionMode === contracts_1.ZLinkUserSpotExecutionMode.SpotWide
101
+ ? this.spotSerial.createActorExecutor(actorId, this.sourceSpotId, this.actorOptions)
102
+ : new actors_1.ZLinkActorSerialExecutor(actorId, this.sourceSpotId, this.actorOptions);
100
103
  this.actorExecutors.set(actorId, executor);
101
104
  }
102
105
  return executor;
@@ -3,6 +3,7 @@ import type { SpotId } from '../../contracts';
3
3
  import type { ZLinkFrameworkException } from '../../contracts/Errors/ZLinkFrameworkException';
4
4
  import { type ZLinkSerialSchedulerOptions, type ZLinkSerialWorkOptions, type ZLinkSerialWorkRecord } from '../execution/serial-execution-queue';
5
5
  import type { ZLinkSerialLifecycleContext } from '../execution/serial-execution-queue';
6
+ import { ZLinkActorSerialExecutor } from '../actors/actor-mailbox';
6
7
  export type ZLinkSpotApplicationTurnContext = ZLinkExecutionBarrierClaim & {
7
8
  readonly replay?: (failure?: unknown) => Promise<unknown> | unknown;
8
9
  };
@@ -19,6 +20,8 @@ export declare class ZLinkSpotSerialTurnExecutor {
19
20
  activeTurnId: number;
20
21
  constructor(yieldAllowed?: boolean, sourceSpotId?: SpotId | undefined, schedulerOptions?: ZLinkSerialSchedulerOptions);
21
22
  get isExecuting(): boolean;
23
+ /** Creates a passive Actor mailbox consumed by this Spot's scheduler. */
24
+ createActorExecutor(actorId: string, sourceSpotId: unknown, options?: ZLinkSerialSchedulerOptions): ZLinkActorSerialExecutor;
22
25
  get isCurrentTurn(): boolean;
23
26
  get hasPendingWork(): boolean;
24
27
  get lastActivityAt(): number;
@@ -4,6 +4,7 @@ exports.ZLinkSpotSerialTurnExecutor = void 0;
4
4
  const execution_1 = require("../execution");
5
5
  const framework_errors_internal_1 = require("../framework-errors-internal");
6
6
  const serial_execution_queue_1 = require("../execution/serial-execution-queue");
7
+ const actor_mailbox_1 = require("../actors/actor-mailbox");
7
8
  const application_job_queue_scope_1 = require("../application-jobs/application-job-queue-scope");
8
9
  class ZLinkSpotSerialTurnExecutor {
9
10
  yieldAllowed;
@@ -24,6 +25,10 @@ class ZLinkSpotSerialTurnExecutor {
24
25
  get isExecuting() {
25
26
  return this.depth > 0;
26
27
  }
28
+ /** Creates a passive Actor mailbox consumed by this Spot's scheduler. */
29
+ createActorExecutor(actorId, sourceSpotId, options) {
30
+ return new actor_mailbox_1.ZLinkActorSerialExecutor(actorId, sourceSpotId, options, this.scheduler);
31
+ }
27
32
  get isCurrentTurn() {
28
33
  return this.depth > 0 && (0, execution_1.isCurrentZLinkSpotSerialTurn)(this);
29
34
  }
@@ -16,7 +16,6 @@ import { ZLinkStreamCodec, ZLinkStreamMessageKind, type ZLinkStreamFrameHeader,
16
16
  import { DefaultZLinkSessionActor, DefaultZLinkSessionContext } from './session-context';
17
17
  import type { ZLinkActorRouteCommitOptions, ZLinkSessionBindingConfirmationOptions, ZLinkSessionBindingIdentity } from './stream-binding-runtime-ports';
18
18
  import { ZLinkStreamSessionNodeRuntime as ZLinkStreamSessionNodeRuntimeCore, ZLinkStreamSessionRuntime as ZLinkStreamSessionRuntimeCore, type ZLinkStreamSessionNodeRuntimeOptions as ZLinkStreamSessionNodeRuntimeCoreOptions, type ZLinkStreamSessionRuntimeOptions as ZLinkStreamSessionRuntimeCoreOptions } from './stream-session-runtime';
19
- export { ZLinkPendingSessionRequest } from './session-requests';
20
19
  export { ZLinkActorSessionLifecycleCoordinator } from './actor-session-lifecycle-coordinator';
21
20
  export { ZLinkActorSessionBindingRegistry } from './actor-session-binding-registry';
22
21
  export { zlinkStreamLz4CompressionCodec } from './stream-frame-factory';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ZLinkStreamBindingRuntime = exports.ZLinkStreamSessionNodeRuntime = exports.ZLinkStreamSessionRuntime = exports.ZLinkStreamRuntimeManager = exports.DefaultZLinkSessionContext = exports.DefaultZLinkSessionActor = exports.DefaultZLinkBoundSessionFactory = exports.DefaultZLinkBoundSession = exports.ZLinkManagedStream = exports.zlinkStreamLz4CompressionCodec = exports.ZLinkActorSessionBindingRegistry = exports.ZLinkActorSessionLifecycleCoordinator = exports.ZLinkPendingSessionRequest = void 0;
3
+ exports.ZLinkStreamBindingRuntime = exports.ZLinkStreamSessionNodeRuntime = exports.ZLinkStreamSessionRuntime = exports.ZLinkStreamRuntimeManager = exports.DefaultZLinkSessionContext = exports.DefaultZLinkSessionActor = exports.DefaultZLinkBoundSessionFactory = exports.DefaultZLinkBoundSession = exports.ZLinkManagedStream = exports.zlinkStreamLz4CompressionCodec = exports.ZLinkActorSessionBindingRegistry = exports.ZLinkActorSessionLifecycleCoordinator = void 0;
4
4
  const InternalDefaults_1 = require("../../contracts/Configuration/InternalDefaults");
5
5
  const Options_1 = require("../../contracts/Locations/Options");
6
6
  const configuration_1 = require("../configuration");
@@ -20,8 +20,6 @@ const stream_frame_factory_1 = require("./stream-frame-factory");
20
20
  const stream_message_utils_1 = require("./stream-message-utils");
21
21
  const stream_session_runtime_1 = require("./stream-session-runtime");
22
22
  const DEFAULT_STREAM_SERVICE_SHUTDOWN_TIMEOUT_MS = 1000;
23
- var session_requests_1 = require("./session-requests");
24
- Object.defineProperty(exports, "ZLinkPendingSessionRequest", { enumerable: true, get: function () { return session_requests_1.ZLinkPendingSessionRequest; } });
25
23
  var actor_session_lifecycle_coordinator_2 = require("./actor-session-lifecycle-coordinator");
26
24
  Object.defineProperty(exports, "ZLinkActorSessionLifecycleCoordinator", { enumerable: true, get: function () { return actor_session_lifecycle_coordinator_2.ZLinkActorSessionLifecycleCoordinator; } });
27
25
  var actor_session_binding_registry_2 = require("./actor-session-binding-registry");
@@ -116,6 +114,9 @@ class ZLinkStreamRuntimeManager {
116
114
  const readablePoller = streamAdapter.createReadablePoller(socket);
117
115
  const runtime = new stream_session_runtime_1.ZLinkStreamSessionNodeRuntime({
118
116
  nodeName,
117
+ heartbeatIntervalMs: streamNode.heartbeatIntervalMs,
118
+ heartbeatTimeoutMs: streamNode.heartbeatTimeoutMs,
119
+ idleTimeoutMs: streamNode.idleTimeoutMs,
119
120
  socket,
120
121
  createPacket: () => streamAdapter.createStreamPacket(),
121
122
  readablePoller,