@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
@@ -1,4 +1,10 @@
1
- import { ServiceRelocationAuthorityPayloadCodec } from '../foundation/service-relocation-runtime';
1
+ import {
2
+ ServiceRelocationAuthorityPayloadCodec,
3
+ serviceRelocationAuthorityApplicationPayload
4
+ } from '../foundation/service-relocation-runtime';
5
+ import { decodeServiceReadySpotAuthority } from '../foundation/service-authority-payload-codec';
6
+ import { decodeActorAuthorityPayload } from '../actors/actor-authority-payload-codec';
7
+ import { ZLinkSpotKind } from '../../contracts';
2
8
  import { UINT64_MAX } from '@zlink-systems/stream-wire';
3
9
  import { createHash, randomUUID } from 'node:crypto';
4
10
  import { zlinkRuntimeDefaultLocationOptions } from '../../contracts/Locations/Options';
@@ -76,6 +82,7 @@ import {
76
82
  type ZLinkObjectCommitResult,
77
83
  type ZLinkObjectCreationCompleteRequest,
78
84
  type ZLinkObjectCreationCompleteResult,
85
+ type ZLinkEndedOwnerReleaseIntent,
79
86
  type ZLinkObjectReserveRequest,
80
87
  type ZLinkObjectReserveResult,
81
88
  type ZLinkOwnerLeaseClaimResult,
@@ -429,10 +436,39 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
429
436
  payload: Buffer.from(mutation.payload)
430
437
  }
431
438
  };
439
+ const actor =
440
+ mutation.kind !== 'delete' && record.snapshot.allocation.objectKind === 'actor'
441
+ ? decodeActorAuthorityPayload(
442
+ serviceRelocationAuthorityApplicationPayload(mutation.payload)
443
+ )
444
+ : undefined;
445
+ const previousActor =
446
+ actor === undefined
447
+ ? undefined
448
+ : decodeActorAuthorityPayload(
449
+ serviceRelocationAuthorityApplicationPayload(record.snapshot.payload)
450
+ );
451
+ const membershipConditions =
452
+ actor?.currentSpotKind === ZLinkSpotKind.User &&
453
+ (previousActor?.currentSpotKind !== ZLinkSpotKind.User ||
454
+ previousActor.currentSpotId !== actor.currentSpotId ||
455
+ previousActor.currentSpotGeneration !== actor.currentSpotGeneration)
456
+ ? await this.userSpotMembershipConditions(
457
+ actor.currentSpotId,
458
+ actor.currentSpotGeneration,
459
+ signal,
460
+ {
461
+ ownerId: record.snapshot.ownerId,
462
+ leaseGeneration: record.snapshot.ownerLeaseGeneration
463
+ }
464
+ )
465
+ : [];
466
+ if (membershipConditions === undefined) return { kind: 'conflict', current: snapshot };
432
467
  const result = await this.provider.write(
433
468
  {
434
469
  conditions: [
435
470
  { kind: 'version', key: rowKey, expected: current.value.version },
471
+ ...membershipConditions,
436
472
  ...(mutation.kind === 'restore'
437
473
  ? []
438
474
  : [
@@ -945,7 +981,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
945
981
  ) {
946
982
  throw new Error('Aggregate participant staging checksum does not match inventory.');
947
983
  }
948
- authorityRows.push({ key: rowKeyForParticipant, current, record, entry });
984
+ authorityRows.push({ key: rowKeyForParticipant, current, record, entry, payload });
949
985
  }
950
986
 
951
987
  const targetDescriptorKey = meshKey(
@@ -1024,12 +1060,52 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
1024
1060
  bytes: encodeJson(capacity)
1025
1061
  });
1026
1062
  }
1063
+ const membershipConditions: ZLinkStoreCondition[] = [];
1064
+ for (const row of authorityRows) {
1065
+ if (row.record.snapshot.allocation.objectKind !== 'actor') continue;
1066
+ const actor = decodeActorAuthorityPayload(
1067
+ serviceRelocationAuthorityApplicationPayload(row.payload)
1068
+ );
1069
+ if (actor?.currentSpotKind !== ZLinkSpotKind.User) continue;
1070
+ const spotKey = encodeAuthorityKey('user_spot', actor.currentSpotId);
1071
+ const participantSpot = authorityRows.find(
1072
+ (candidate) => candidate.entry.authorityKey === spotKey.value
1073
+ );
1074
+ if (participantSpot !== undefined) {
1075
+ if (
1076
+ participantSpot.record.snapshot.allocation.objectKind !== 'user_spot' ||
1077
+ participantSpot.record.snapshot.objectGeneration !== actor.currentSpotGeneration
1078
+ )
1079
+ return { kind: 'stale' };
1080
+ continue;
1081
+ }
1082
+ if (
1083
+ membershipConditions.some(
1084
+ (condition) => condition.key.value === authorityKey(spotKey.value).value
1085
+ )
1086
+ )
1087
+ continue;
1088
+ const conditions = await this.userSpotMembershipConditions(
1089
+ actor.currentSpotId,
1090
+ actor.currentSpotGeneration,
1091
+ signal,
1092
+ aggregate.targetOwner
1093
+ );
1094
+ if (conditions === undefined) return { kind: 'stale' };
1095
+ membershipConditions.push(
1096
+ ...conditions.filter(
1097
+ (condition) =>
1098
+ !membershipConditions.some((existing) => existing.key.value === condition.key.value)
1099
+ )
1100
+ );
1101
+ }
1027
1102
  const published = await this.provider.write(
1028
1103
  {
1029
1104
  conditions: [
1030
1105
  { kind: 'version', key: rowKey, expected: aggregateRead.value.version },
1031
1106
  versionCondition(targetDescriptorKey, descriptorRead),
1032
1107
  leaseValueCondition(targetLeaseKey, aggregate.targetOwner),
1108
+ ...membershipConditions,
1033
1109
  ...[...capacityReads.entries()].map(([value, read]) => ({
1034
1110
  kind: 'version' as const,
1035
1111
  key: capacityKeys.get(value)!,
@@ -1128,21 +1204,33 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
1128
1204
  override async releaseEndedReservation(
1129
1205
  key: ZLinkAuthorityKey,
1130
1206
  expectedStoreVersion: string,
1131
- signal?: AbortSignal
1207
+ signal?: AbortSignal,
1208
+ request?: ZLinkEndedOwnerReleaseIntent
1132
1209
  ): Promise<boolean> {
1210
+ return (
1211
+ (await this.tryReleaseEndedReservation(key, expectedStoreVersion, signal, request)) ===
1212
+ 'reclaimed'
1213
+ );
1214
+ }
1215
+
1216
+ private async tryReleaseEndedReservation(
1217
+ key: ZLinkAuthorityKey,
1218
+ expectedStoreVersion: string,
1219
+ signal?: AbortSignal,
1220
+ request?: ZLinkEndedOwnerReleaseIntent
1221
+ ): Promise<'notReclaimable' | 'conflict' | 'reclaimed'> {
1133
1222
  const rowKey = authorityKey(key.value);
1134
1223
  const current = await this.provider.read(rowKey, signal);
1135
1224
  if (current.kind === 'missing' || current.value.version.value !== expectedStoreVersion)
1136
- return false;
1225
+ return 'conflict';
1137
1226
  const record = decodeAuthorityRecord(current.value.bytes);
1138
1227
  const snapshot = record.snapshot;
1139
1228
  if (
1140
- snapshot.allocation.state !== 'reserved' ||
1141
- snapshot.pendingCreation === undefined ||
1142
- record.aggregate !== undefined ||
1143
- hasRelocationAuthority(snapshot.payload)
1229
+ snapshot.allocation.state === 'active'
1230
+ ? request === undefined
1231
+ : snapshot.pendingCreation === undefined
1144
1232
  )
1145
- return false;
1233
+ return 'notReclaimable';
1146
1234
  const leaseKey = ownerKey(snapshot.ownerId);
1147
1235
  const descriptorKey = meshKey(
1148
1236
  snapshot.allocation.descriptor.meshName,
@@ -1159,27 +1247,111 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
1159
1247
  ]);
1160
1248
  if (lease.kind === 'found' && lease.value.expiresAt === undefined)
1161
1249
  throw new Error('Location Store owner lease record is invalid.');
1162
- if (
1163
- sameLiveOwner(lease, snapshot) &&
1250
+ const ownerLive = sameLiveOwner(lease, snapshot);
1251
+ if (snapshot.allocation.state === 'active') {
1252
+ if (ownerLive) return 'notReclaimable';
1253
+ let unavailable = record.aggregate !== undefined || hasRelocationAuthority(snapshot.payload);
1254
+ if (snapshot.allocation.objectKind !== 'actor') {
1255
+ const spot = decodeServiceReadySpotAuthority(snapshot.payload);
1256
+ unavailable ||=
1257
+ spot === undefined ||
1258
+ spot.kind !== request?.key.kind ||
1259
+ spot.stableType !== request.intent.stableType ||
1260
+ spot.spotId !== request.key.globalId ||
1261
+ spot.activationRecovery !== undefined;
1262
+ if (spot?.kind === 'user_spot') {
1263
+ // The snapshot starts only after the old owner's lease was found invalid.
1264
+ for (const prefix of [`${PREFIX}actor:`, `${AUTHORITY_PREIMAGE_PREFIX}actor\0`]) {
1265
+ membershipSnapshot: for (;;) {
1266
+ let snapshotHasMember = false;
1267
+ let cursor: ZLinkStoreScanCursor | undefined;
1268
+ do {
1269
+ const page = await this.provider.scan(
1270
+ { prefix, cursor, limit: ZLINK_PROVIDER_MAX_PAGE_SIZE },
1271
+ signal
1272
+ );
1273
+ if (page.kind === 'expired') continue membershipSnapshot;
1274
+ for (const item of page.value.items) {
1275
+ if (prefix === `${PREFIX}actor:`) {
1276
+ const actor = normalizeActor(
1277
+ decodeJson<DescriptorRecord<ZLinkActorLocation>>(item.value.bytes).descriptor
1278
+ );
1279
+ snapshotHasMember ||=
1280
+ actor.spotKind === ZLinkSpotKind.User &&
1281
+ String(actor.spotId) === spot.spotId &&
1282
+ actor.spotGeneration === snapshot.objectGeneration;
1283
+ } else {
1284
+ const authority = decodeAuthorityRecord(item.value.bytes).snapshot;
1285
+ if (authority.allocation.objectKind !== 'actor') continue;
1286
+ const actor = decodeActorAuthorityPayload(
1287
+ serviceRelocationAuthorityApplicationPayload(authority.payload)
1288
+ );
1289
+ snapshotHasMember ||=
1290
+ actor?.currentSpotKind === ZLinkSpotKind.User &&
1291
+ actor.currentSpotId === spot.spotId &&
1292
+ actor.currentSpotGeneration === snapshot.objectGeneration;
1293
+ }
1294
+ }
1295
+ cursor = page.value.nextCursor;
1296
+ } while (cursor !== undefined);
1297
+ unavailable ||= snapshotHasMember;
1298
+ break;
1299
+ }
1300
+ }
1301
+ }
1302
+ }
1303
+ if (unavailable || request?.actorRelocationPolicy !== 'disabled')
1304
+ throw new ZLinkFrameworkException(
1305
+ ZLinkFrameworkErrorKind.Unavailable,
1306
+ 'Object owner lease is unavailable.'
1307
+ );
1308
+ } else if (
1309
+ ownerLive &&
1164
1310
  descriptor.kind === 'found' &&
1165
1311
  reviveMeshDescriptor(
1166
1312
  decodeCanonicalDescriptorRecord<ZLinkMeshNodeDescriptor>(descriptor.value.bytes).descriptor
1167
1313
  ).lifecycleGeneration === snapshot.allocation.descriptorLifecycleGeneration
1168
1314
  )
1169
- return false;
1315
+ return 'notReclaimable';
1316
+ if (
1317
+ snapshot.allocation.state !== 'active' &&
1318
+ (record.aggregate !== undefined || hasRelocationAuthority(snapshot.payload))
1319
+ )
1320
+ return 'notReclaimable';
1170
1321
  const mutations: ZLinkStoreWriteRequest['mutations'][number][] = [
1171
1322
  { kind: 'delete', key: rowKey }
1172
1323
  ];
1173
1324
  if (capacity.kind === 'found') {
1174
1325
  const stored = decodeJson<CapacityRecord>(capacity.value.bytes);
1175
- mutations.push({
1176
- kind: 'put',
1177
- key: capacityRowKey,
1178
- bytes: encodeJson({
1179
- active: stored.active,
1180
- pending: subtractCapacity(stored.pending, snapshot.allocation.capacity)
1181
- } satisfies CapacityRecord)
1182
- });
1326
+ try {
1327
+ mutations.push({
1328
+ kind: 'put',
1329
+ key: capacityRowKey,
1330
+ bytes: encodeJson({
1331
+ active:
1332
+ snapshot.allocation.state === 'active'
1333
+ ? subtractCapacity(stored.active, snapshot.allocation.capacity)
1334
+ : stored.active,
1335
+ pending:
1336
+ snapshot.allocation.state === 'reserved'
1337
+ ? subtractCapacity(stored.pending, snapshot.allocation.capacity)
1338
+ : stored.pending
1339
+ } satisfies CapacityRecord)
1340
+ });
1341
+ } catch (error) {
1342
+ if (
1343
+ !(error instanceof ZLinkFrameworkException) ||
1344
+ error.kind !== ZLinkFrameworkErrorKind.DataLost
1345
+ )
1346
+ throw error;
1347
+ const observed = await this.provider.read(rowKey, signal);
1348
+ if (
1349
+ observed.kind === 'missing' ||
1350
+ observed.value.version.value !== current.value.version.value
1351
+ )
1352
+ return 'conflict';
1353
+ throw error;
1354
+ }
1183
1355
  }
1184
1356
  const result = await this.provider.write(
1185
1357
  {
@@ -1193,7 +1365,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
1193
1365
  },
1194
1366
  signal
1195
1367
  );
1196
- return result.kind === 'applied';
1368
+ return result.kind === 'applied' ? 'reclaimed' : 'conflict';
1197
1369
  }
1198
1370
 
1199
1371
  override async reserve(
@@ -1203,6 +1375,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
1203
1375
  ): Promise<ZLinkObjectReserveResult> {
1204
1376
  const encodedAuthorityKey = encodeAuthorityKey(request.key.kind, request.key.globalId);
1205
1377
  const reservationId = randomUUID();
1378
+ let reclaimVersion: string | undefined;
1206
1379
  for (;;) {
1207
1380
  throwIfOperationExpired(signal, deadlineUnixMs);
1208
1381
  const rowKey = authorityKey(encodedAuthorityKey.value);
@@ -1223,19 +1396,23 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
1223
1396
  ) {
1224
1397
  return { kind: 'typeMismatch', current: snapshot };
1225
1398
  }
1226
- if (snapshot.allocation.state === 'active') {
1227
- return { kind: 'alreadyExists', current: snapshot };
1228
- }
1229
- if (
1230
- !(await this.releaseEndedReservation(
1231
- encodedAuthorityKey,
1232
- current.value.version.value,
1233
- signal
1234
- ))
1235
- )
1399
+ if (reclaimVersion !== undefined && snapshot.storeVersion.value !== reclaimVersion)
1236
1400
  return { kind: 'conflict', current: snapshot };
1401
+ const reclaim = await this.tryReleaseEndedReservation(
1402
+ encodedAuthorityKey,
1403
+ current.value.version.value,
1404
+ signal,
1405
+ request
1406
+ );
1407
+ if (reclaim === 'notReclaimable')
1408
+ return snapshot.allocation.state === 'active'
1409
+ ? { kind: 'alreadyExists', current: snapshot }
1410
+ : { kind: 'conflict', current: snapshot };
1411
+ reclaimVersion = reclaim === 'conflict' ? snapshot.storeVersion.value : undefined;
1237
1412
  continue;
1238
1413
  }
1414
+ if (reclaimVersion !== undefined)
1415
+ return { kind: 'conflict', current: { kind: 'missing', storeNow: current.storeNow } };
1239
1416
  const [descriptorRead, leaseRead] = await Promise.all([
1240
1417
  this.provider.read(descriptorKey, signal),
1241
1418
  this.provider.read(leaseKey, signal)
@@ -1555,6 +1732,22 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
1555
1732
  'Creation terminal expiry must be the live operation deadline plus five minutes.'
1556
1733
  );
1557
1734
  }
1735
+ const actor =
1736
+ request.completion.kind === 'created'
1737
+ ? decodeActorAuthorityPayload(
1738
+ serviceRelocationAuthorityApplicationPayload(request.completion.readyPayload)
1739
+ )
1740
+ : undefined;
1741
+ const membershipConditions =
1742
+ actor?.currentSpotKind === ZLinkSpotKind.User
1743
+ ? await this.userSpotMembershipConditions(
1744
+ actor.currentSpotId,
1745
+ actor.currentSpotGeneration,
1746
+ signal,
1747
+ request.target.owner
1748
+ )
1749
+ : [];
1750
+ if (membershipConditions === undefined) return { kind: 'stale' };
1558
1751
  const mutations =
1559
1752
  request.completion.kind === 'created'
1560
1753
  ? [
@@ -1596,6 +1789,7 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
1596
1789
  conditions: [
1597
1790
  { kind: 'version', key: rowKey, expected: current.value.version },
1598
1791
  { kind: 'missing', key: terminalRowKey },
1792
+ ...membershipConditions,
1599
1793
  versionCondition(descriptorKey, descriptorRead),
1600
1794
  leaseValueCondition(leaseKey, request.target.owner),
1601
1795
  conditionFor(capacityRowKey, capacityRead)
@@ -2027,7 +2221,8 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
2027
2221
  actorKey(location.meshName, location.actorId),
2028
2222
  normalizeActor(location),
2029
2223
  intent,
2030
- signal
2224
+ signal,
2225
+ location
2031
2226
  );
2032
2227
  }
2033
2228
 
@@ -2569,11 +2764,48 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
2569
2764
  };
2570
2765
  }
2571
2766
 
2767
+ private async userSpotMembershipConditions(
2768
+ spotId: string,
2769
+ objectGeneration: bigint,
2770
+ signal?: AbortSignal,
2771
+ conditionedOwner?: ZLinkLocationOwnerToken
2772
+ ): Promise<ZLinkStoreCondition[] | undefined> {
2773
+ const key = authorityKey(encodeAuthorityKey('user_spot', spotId).value);
2774
+ const read = await this.provider.read(key, signal);
2775
+ if (read.kind === 'missing') return undefined;
2776
+ const record = decodeAuthorityRecord(read.value.bytes);
2777
+ const snapshot = record.snapshot;
2778
+ if (
2779
+ record.aggregate !== undefined ||
2780
+ snapshot.allocation.objectKind !== 'user_spot' ||
2781
+ snapshot.allocation.state !== 'active' ||
2782
+ snapshot.objectGeneration !== objectGeneration ||
2783
+ decodeServiceReadySpotAuthority(snapshot.payload)?.kind !== 'user_spot'
2784
+ )
2785
+ return undefined;
2786
+ const leaseKey = ownerKey(snapshot.ownerId);
2787
+ const lease = await this.provider.read(leaseKey, signal);
2788
+ if (!sameLiveOwner(lease, snapshot)) return undefined;
2789
+ return [
2790
+ { kind: 'version', key, expected: read.value.version },
2791
+ ...(conditionedOwner?.ownerId === snapshot.ownerId &&
2792
+ conditionedOwner.leaseGeneration === snapshot.ownerLeaseGeneration
2793
+ ? []
2794
+ : [
2795
+ leaseValueCondition(leaseKey, {
2796
+ ownerId: snapshot.ownerId,
2797
+ leaseGeneration: snapshot.ownerLeaseGeneration
2798
+ })
2799
+ ])
2800
+ ];
2801
+ }
2802
+
2572
2803
  private async updateOwnedLocation<T extends LeaseOwnedLocation>(
2573
2804
  rowKey: ZLinkStoreKey,
2574
2805
  location: T,
2575
2806
  intent: ZLinkLocationWriteIntent,
2576
- signal?: AbortSignal
2807
+ signal?: AbortSignal,
2808
+ membership?: Pick<ZLinkActorLocation, 'spotKind' | 'spotId' | 'spotGeneration'>
2577
2809
  ): Promise<ZLinkLocationWriteResult> {
2578
2810
  const leaseKey = ownerKey(location.ownerId);
2579
2811
  const [lease, current] = await Promise.all([
@@ -2627,6 +2859,24 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
2627
2859
  return rejected(lease.value.storeNow);
2628
2860
  }
2629
2861
 
2862
+ const previousMembership =
2863
+ current.kind === 'found' && membership !== undefined
2864
+ ? decodeJson<DescriptorRecord<ZLinkActorLocation>>(current.value.bytes).descriptor
2865
+ : undefined;
2866
+ const membershipConditions =
2867
+ membership?.spotKind === ZLinkSpotKind.User &&
2868
+ (previousMembership?.spotKind !== ZLinkSpotKind.User ||
2869
+ String(previousMembership.spotId) !== String(membership.spotId) ||
2870
+ BigInt(previousMembership.spotGeneration) !== membership.spotGeneration)
2871
+ ? await this.userSpotMembershipConditions(
2872
+ String(membership.spotId),
2873
+ membership.spotGeneration,
2874
+ signal,
2875
+ { ownerId: location.ownerId, leaseGeneration: locationOwnerGeneration(location) }
2876
+ )
2877
+ : [];
2878
+ if (membershipConditions === undefined) return rejected(lease.value.storeNow);
2879
+
2630
2880
  const result = await this.provider.write(
2631
2881
  {
2632
2882
  conditions: [
@@ -2634,7 +2884,8 @@ export class ZLinkLocationStoreRepository extends ZLinkInMemoryLocationStore {
2634
2884
  ownerId: location.ownerId,
2635
2885
  leaseGeneration: locationOwnerGeneration(location)
2636
2886
  }),
2637
- rowCondition
2887
+ rowCondition,
2888
+ ...membershipConditions
2638
2889
  ],
2639
2890
  mutations: [
2640
2891
  {
@@ -4040,13 +4291,21 @@ function adjustCapacity(
4040
4291
  if (delta.spotType !== undefined) {
4041
4292
  const key = capacityTypeKey(delta.spotType);
4042
4293
  const next = (spotTypes[key] ?? 0) + direction * delta.spotType.count;
4043
- if (next < 0) throw new Error('Provider capacity counter underflow.');
4294
+ if (next < 0)
4295
+ throw new ZLinkFrameworkException(
4296
+ ZLinkFrameworkErrorKind.DataLost,
4297
+ 'Provider capacity counter underflow.'
4298
+ );
4044
4299
  if (next === 0) delete spotTypes[key];
4045
4300
  else spotTypes[key] = next;
4046
4301
  }
4047
4302
  const actors = usage.actors + direction * delta.actors;
4048
4303
  const spots = usage.spots + direction * delta.spots;
4049
- if (actors < 0 || spots < 0) throw new Error('Provider capacity counter underflow.');
4304
+ if (actors < 0 || spots < 0)
4305
+ throw new ZLinkFrameworkException(
4306
+ ZLinkFrameworkErrorKind.DataLost,
4307
+ 'Provider capacity counter underflow.'
4308
+ );
4050
4309
  return { actors, spots, spotTypes };
4051
4310
  }
4052
4311
 
@@ -26,7 +26,11 @@ import {
26
26
  type ResolvedSpotHandle
27
27
  } from '../spots/spot-handle';
28
28
 
29
- import type { ZLinkSpotRouteResolver, ZLinkSpotRouteTarget } from '../spots/spot-routing-internal';
29
+ import type {
30
+ ZLinkSpotRouteResolver,
31
+ ZLinkSpotRouteTarget,
32
+ ZLinkInstanceReactivationRoute
33
+ } from '../spots/spot-routing-internal';
30
34
  import { encodeAuthorityKey } from './authority-key-codec';
31
35
  import { isKnownZLinkLocationAutoConnectType, isKnownZLinkLocationRole } from './canonical-codec';
32
36
  import {
@@ -951,7 +955,17 @@ export class ZLinkAuthoritySpotRouteResolver implements ZLinkSpotRouteResolver {
951
955
  ) => Promise<ZLinkFrameworkRuntimeState | undefined>
952
956
  ) {}
953
957
 
954
- async resolve(spotId: RoutingId, signal?: AbortSignal): Promise<ZLinkSpotRouteTarget> {
958
+ resolve(
959
+ spotId: RoutingId,
960
+ signal: AbortSignal | undefined,
961
+ instanceIntent: true
962
+ ): Promise<ZLinkSpotRouteTarget | ZLinkInstanceReactivationRoute>;
963
+ resolve(spotId: RoutingId, signal?: AbortSignal): Promise<ZLinkSpotRouteTarget>;
964
+ async resolve(
965
+ spotId: RoutingId,
966
+ signal?: AbortSignal,
967
+ instanceIntent?: true
968
+ ): Promise<ZLinkSpotRouteTarget | ZLinkInstanceReactivationRoute> {
955
969
  const key = String(spotId);
956
970
  const prepared = await this.lane.run(() => this.beginResolutionCore(key));
957
971
  try {
@@ -980,6 +994,15 @@ export class ZLinkAuthoritySpotRouteResolver implements ZLinkSpotRouteResolver {
980
994
  signal
981
995
  );
982
996
  if (remainingLeaseMs !== undefined && remainingLeaseMs <= 0) {
997
+ if (instanceIntent === true && ready?.kind === 'instance_spot') {
998
+ return {
999
+ kind: 'coldActivation',
1000
+ spotId,
1001
+ spotKind: ZLinkSpotKind.Instance,
1002
+ meshName: ready.ownerMeshName,
1003
+ stableType: ready.stableType
1004
+ };
1005
+ }
983
1006
  throw new ZLinkFrameworkException(
984
1007
  ZLinkFrameworkErrorKind.Unavailable,
985
1008
  `SPOT '${spotId}' Ready authority owner lease is unavailable.`
@@ -18,13 +18,13 @@ export interface ZLinkSubmitResult {
18
18
  readonly status: ZLinkSubmitStatus;
19
19
  }
20
20
 
21
- /** Projects immediate refusal and pending admission failure from their Core phase. */
21
+ /** Projects typed submit failure; capacity refusal has no admission wait token. */
22
22
  export function submitToRequestResult(result: number, phase: 'submit' | 'completion'): number {
23
23
  switch (result) {
24
24
  case SubmitResult.Ok:
25
25
  return RequestResult.Ok;
26
26
  case SubmitResult.Backpressured:
27
- return phase === 'submit' ? RequestResult.NotConnected : RequestResult.TimedOut;
27
+ return RequestResult.NotConnected;
28
28
  case SubmitResult.NotConnected:
29
29
  return RequestResult.NotConnected;
30
30
  case SubmitResult.NotFound:
@@ -1,3 +1,4 @@
1
+ import type { ApplicationJobOrigin } from '../application-jobs/contracts';
1
2
  import type { ActorRef, RoutingId, ZLinkActor, ZLinkActorJoinOperationId } from '../../contracts';
2
3
  import type { ZLinkRemoteBoundSessionTarget } from '../actors/actor-runtime-state';
3
4
  import type { ZLinkDeferredJoinCompletion } from '../actors/deferred-join-completion';
@@ -13,6 +14,7 @@ const ADMISSION_RETENTION_MS = 30_000;
13
14
  * how long the underlying buffer stays valid.
14
15
  */
15
16
  export interface ZLinkParkedActorArrival {
17
+ readonly origin: ApplicationJobOrigin;
16
18
  readonly header: Buffer;
17
19
  readonly payload: Buffer;
18
20
  readonly returnResponse: boolean;
@@ -1,3 +1,4 @@
1
+ import type { ApplicationJobOrigin } from '../application-jobs/contracts';
1
2
  import { dispatchReasonFromError } from '../diagnostics/dispatch-error-details';
2
3
  import type {
3
4
  ActorRef,
@@ -190,6 +191,7 @@ export interface ZLinkSpotManagerOptions {
190
191
  ): Promise<void>;
191
192
  };
192
193
  readonly dispatchEntryActorPacket?: (
194
+ origin: ApplicationJobOrigin,
193
195
  actorId: string,
194
196
  parts: readonly Message[],
195
197
  returnResponse?: boolean,
@@ -1532,6 +1534,7 @@ export class DefaultZLinkSpotManager {
1532
1534
  }
1533
1535
 
1534
1536
  dispatchRoutedActorPacket(
1537
+ origin: ApplicationJobOrigin,
1535
1538
  spotId: RoutingId,
1536
1539
  actorId: string,
1537
1540
  parts: readonly Message[],
@@ -1544,6 +1547,7 @@ export class DefaultZLinkSpotManager {
1544
1547
  throw new ZLinkConfigurationException(`Spot '${spotId}' is not active.`);
1545
1548
  }
1546
1549
  return this.dispatchActorPacket(
1550
+ origin,
1547
1551
  activation,
1548
1552
  actorId,
1549
1553
  parts,
@@ -1555,6 +1559,7 @@ export class DefaultZLinkSpotManager {
1555
1559
 
1556
1560
  /** @internal Replays a record already owned by the Actor handoff queue. */
1557
1561
  dispatchRoutedActorPacketDirect(
1562
+ origin: ApplicationJobOrigin,
1558
1563
  spotId: RoutingId,
1559
1564
  actorId: string,
1560
1565
  parts: readonly Message[],
@@ -1567,6 +1572,7 @@ export class DefaultZLinkSpotManager {
1567
1572
  throw new ZLinkConfigurationException(`Spot '${spotId}' is not active.`);
1568
1573
  }
1569
1574
  return this.activationLifecycle.dispatchActorPacketDirect(
1575
+ origin,
1570
1576
  activation,
1571
1577
  actorId,
1572
1578
  parts,
@@ -1965,6 +1971,7 @@ export class DefaultZLinkSpotManager {
1965
1971
  }
1966
1972
 
1967
1973
  async dispatchMeshActor(
1974
+ origin: ApplicationJobOrigin,
1968
1975
  meshName: string,
1969
1976
  owner: ReadyRecord,
1970
1977
  record: ReceiveRecord
@@ -2027,6 +2034,7 @@ export class DefaultZLinkSpotManager {
2027
2034
  }
2028
2035
  const response = targetsEntrySpot
2029
2036
  ? await this.options.dispatchEntryActorPacket?.(
2037
+ origin,
2030
2038
  actor.actorId,
2031
2039
  record.parts,
2032
2040
  request,
@@ -2036,6 +2044,7 @@ export class DefaultZLinkSpotManager {
2036
2044
  record.messageFollowOrigin
2037
2045
  )
2038
2046
  : await this.dispatchMeshActorPacket(
2047
+ origin,
2039
2048
  meshName,
2040
2049
  spotId,
2041
2050
  actor.actorId,
@@ -2693,6 +2702,7 @@ export class DefaultZLinkSpotManager {
2693
2702
  );
2694
2703
  }
2695
2704
  return await this.options.dispatchEntryActorPacket(
2705
+ 'remote',
2696
2706
  entryActor.context.actorId,
2697
2707
  parts,
2698
2708
  returnResponse,
@@ -2922,6 +2932,7 @@ export class DefaultZLinkSpotManager {
2922
2932
  pendingTransfer.handoffBacklog,
2923
2933
  (parts, returnResponse, remoteBoundSessionTarget, _fallbackActorRef) =>
2924
2934
  this.dispatchActorPacket(
2935
+ 'remote',
2925
2936
  activation,
2926
2937
  actor.context.actorId,
2927
2938
  parts,
@@ -3271,6 +3282,7 @@ export class DefaultZLinkSpotManager {
3271
3282
  }
3272
3283
 
3273
3284
  private async dispatchActorPacket(
3285
+ origin: ApplicationJobOrigin,
3274
3286
  activation: ZLinkSpotActivation,
3275
3287
  actorId: string,
3276
3288
  parts: readonly Message[],
@@ -3281,6 +3293,7 @@ export class DefaultZLinkSpotManager {
3281
3293
  messageFollowOrigin?: ZLinkMessageFollowOrigin
3282
3294
  ): Promise<unknown> {
3283
3295
  return await this.activationLifecycle.dispatchActorPacket(
3296
+ origin,
3284
3297
  activation,
3285
3298
  actorId,
3286
3299
  parts,
@@ -3311,6 +3324,7 @@ export class DefaultZLinkSpotManager {
3311
3324
  for (const arrival of drained) {
3312
3325
  try {
3313
3326
  const response = await this.dispatchActorPacket(
3327
+ arrival.origin,
3314
3328
  activation,
3315
3329
  actor.context.actorId,
3316
3330
  [RuntimeMessage.from(arrival.header), RuntimeMessage.from(arrival.payload)],
@@ -3327,6 +3341,7 @@ export class DefaultZLinkSpotManager {
3327
3341
  }
3328
3342
 
3329
3343
  private async dispatchMeshActorPacket(
3344
+ origin: ApplicationJobOrigin,
3330
3345
  meshName: string,
3331
3346
  spotId: RoutingId,
3332
3347
  actorId: string,
@@ -3343,6 +3358,7 @@ export class DefaultZLinkSpotManager {
3343
3358
  );
3344
3359
  }
3345
3360
  return await this.dispatchActorPacket(
3361
+ origin,
3346
3362
  activation,
3347
3363
  actorId,
3348
3364
  parts,