@zlink-systems/framework 0.27.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 (191) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Actors/ZLinkActorClient.d.ts +1 -1
  3. package/dist/contracts/Channels/Calls.d.ts +1 -1
  4. package/dist/contracts/Configuration/Builders.d.ts +2 -1
  5. package/dist/contracts/Configuration/Configs.d.ts +0 -1
  6. package/dist/contracts/Configuration/InternalDefaults.d.ts +3 -0
  7. package/dist/contracts/Configuration/InternalDefaults.js +4 -1
  8. package/dist/contracts/Configuration/RegistrationBuilders.js +9 -0
  9. package/dist/contracts/Configuration/RegistrationNormalizers.d.ts +1 -0
  10. package/dist/contracts/Configuration/RegistrationNormalizers.js +16 -0
  11. package/dist/contracts/Configuration/RegistrationTypes.d.ts +3 -5
  12. package/dist/contracts/Configuration/RegistrationValidators.js +0 -2
  13. package/dist/contracts/Spots/Contracts.d.ts +1 -1
  14. package/dist/contracts/Streams/BoundSessionContracts.d.ts +1 -1
  15. package/dist/contracts/Streams/IZLinkSession.d.ts +2 -3
  16. package/dist/contracts/Streams/IZLinkSessionActor.d.ts +2 -2
  17. package/dist/runtime/actors/actor-client.js +7 -10
  18. package/dist/runtime/actors/actor-mailbox.d.ts +2 -2
  19. package/dist/runtime/actors/actor-mailbox.js +2 -2
  20. package/dist/runtime/actors/actor-request-deadline.d.ts +1 -3
  21. package/dist/runtime/actors/actor-request-deadline.js +4 -29
  22. package/dist/runtime/actors/actor-routed-join-transport.d.ts +2 -3
  23. package/dist/runtime/backend/contracts/index.d.ts +1 -4
  24. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -0
  25. package/dist/runtime/backend/node/node-backend-adapter-support.js +35 -4
  26. package/dist/runtime/backend/node/node-dealer-channel-client-transport.d.ts +1 -1
  27. package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +1 -2
  28. package/dist/runtime/backend/node/node-raw-binding-port.js +10 -27
  29. package/dist/runtime/backend/node/node-raw-mesh-backend.js +1 -3
  30. package/dist/runtime/backend/node/node-socket-backend-adapter.js +1 -1
  31. package/dist/runtime/channels/channel-clients.js +8 -10
  32. package/dist/runtime/channels/channel-envelope.d.ts +1 -1
  33. package/dist/runtime/channels/channel-envelope.js +4 -10
  34. package/dist/runtime/channels/channel-outbound-operations.d.ts +2 -2
  35. package/dist/runtime/channels/channel-outbound-operations.js +20 -36
  36. package/dist/runtime/channels/channel-runtime-manager.d.ts +4 -4
  37. package/dist/runtime/channels/channel-runtime-manager.js +8 -8
  38. package/dist/runtime/channels/channel-socket-options.js +0 -10
  39. package/dist/runtime/channels/channel-socket-registry.d.ts +1 -1
  40. package/dist/runtime/channels/channel-socket-registry.js +3 -14
  41. package/dist/runtime/channels/channel-transports.d.ts +10 -14
  42. package/dist/runtime/channels/channel-transports.js +11 -14
  43. package/dist/runtime/channels/local-spot-route-transport.d.ts +1 -1
  44. package/dist/runtime/channels/local-spot-route-transport.js +1 -3
  45. package/dist/runtime/channels/router-socket-spot-route-transport.d.ts +1 -1
  46. package/dist/runtime/channels/router-socket-spot-route-transport.js +2 -2
  47. package/dist/runtime/channels/source-spot-router.d.ts +1 -1
  48. package/dist/runtime/channels/source-spot-router.js +2 -4
  49. package/dist/runtime/channels/spot-node-route-transport.d.ts +1 -1
  50. package/dist/runtime/channels/spot-node-route-transport.js +2 -3
  51. package/dist/runtime/channels/spot-route-bridge-transport.d.ts +1 -1
  52. package/dist/runtime/channels/spot-route-bridge-transport.js +2 -2
  53. package/dist/runtime/channels/spot-route-dispatch-strategy.d.ts +2 -4
  54. package/dist/runtime/channels/spot-route-dispatch-strategy.js +11 -12
  55. package/dist/runtime/execution/serial-execution-queue.d.ts +12 -3
  56. package/dist/runtime/execution/serial-execution-queue.js +116 -28
  57. package/dist/runtime/foundation/operation-registry.d.ts +3 -2
  58. package/dist/runtime/foundation/operation-registry.js +5 -1
  59. package/dist/runtime/foundation/raw-service-mesh-runtime.js +17 -11
  60. package/dist/runtime/foundation/service-authority-payload-codec.d.ts +0 -2
  61. package/dist/runtime/foundation/service-authority-payload-codec.js +0 -43
  62. package/dist/runtime/foundation/service-stateful-registry.d.ts +4 -3
  63. package/dist/runtime/foundation/service-stateful-registry.js +5 -2
  64. package/dist/runtime/foundation/service-stateful-runtime.d.ts +1 -8
  65. package/dist/runtime/foundation/service-stateful-runtime.js +78 -126
  66. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -11
  67. package/dist/runtime/foundation/service-stateful-wire-codec.js +1 -23
  68. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -0
  69. package/dist/runtime/foundation/service-topology-registry.js +16 -17
  70. package/dist/runtime/host/actor-packet-relay.d.ts +2 -2
  71. package/dist/runtime/host/actor-packet-relay.js +8 -10
  72. package/dist/runtime/host/index.js +1 -15
  73. package/dist/runtime/host/instance-activation-authority.d.ts +0 -1
  74. package/dist/runtime/host/instance-activation-authority.js +46 -97
  75. package/dist/runtime/host/remote-bound-session-relay.js +8 -8
  76. package/dist/runtime/host/spot-address-transport.d.ts +1 -9
  77. package/dist/runtime/host/spot-address-transport.js +47 -87
  78. package/dist/runtime/host/stateful-authority-route-runtime.d.ts +1 -0
  79. package/dist/runtime/host/stateful-authority-route-runtime.js +17 -9
  80. package/dist/runtime/locations/domain-store-contract.d.ts +1 -0
  81. package/dist/runtime/locations/in-memory-authority-store.d.ts +2 -0
  82. package/dist/runtime/locations/in-memory-authority-store.js +22 -8
  83. package/dist/runtime/locations/in-memory-location-store.d.ts +1 -0
  84. package/dist/runtime/locations/in-memory-location-store.js +3 -0
  85. package/dist/runtime/locations/internal-store-contracts.d.ts +1 -1
  86. package/dist/runtime/locations/location-store-repository.d.ts +1 -0
  87. package/dist/runtime/locations/location-store-repository.js +98 -53
  88. package/dist/runtime/messaging/request-deadline.d.ts +3 -0
  89. package/dist/runtime/messaging/request-deadline.js +39 -0
  90. package/dist/runtime/messaging/submission-result.d.ts +2 -1
  91. package/dist/runtime/messaging/submission-result.js +11 -4
  92. package/dist/runtime/spots/spot-outbound.d.ts +3 -5
  93. package/dist/runtime/spots/spot-outbound.js +12 -15
  94. package/dist/runtime/spots/spot-serial-executor.js +4 -1
  95. package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
  96. package/dist/runtime/spots/spot-serial-turn-executor.js +5 -0
  97. package/dist/runtime/streams/bound-actor-relay-sender.d.ts +2 -2
  98. package/dist/runtime/streams/bound-actor-relay-sender.js +14 -14
  99. package/dist/runtime/streams/bound-session-service.js +1 -1
  100. package/dist/runtime/streams/index.d.ts +2 -3
  101. package/dist/runtime/streams/index.js +6 -5
  102. package/dist/runtime/streams/managed-stream.d.ts +2 -2
  103. package/dist/runtime/streams/managed-stream.js +3 -5
  104. package/dist/runtime/streams/native-fallback-bound-session.js +13 -16
  105. package/dist/runtime/streams/protocol.js +0 -9
  106. package/dist/runtime/streams/session-calls.d.ts +5 -7
  107. package/dist/runtime/streams/session-calls.js +6 -23
  108. package/dist/runtime/streams/session-context.d.ts +4 -8
  109. package/dist/runtime/streams/session-context.js +3 -32
  110. package/dist/runtime/streams/session-serial-executor.d.ts +0 -1
  111. package/dist/runtime/streams/session-serial-executor.js +0 -3
  112. package/dist/runtime/streams/stream-message-utils.d.ts +0 -1
  113. package/dist/runtime/streams/stream-message-utils.js +0 -20
  114. package/dist/runtime/streams/stream-session-runtime.d.ts +6 -3
  115. package/dist/runtime/streams/stream-session-runtime.js +40 -85
  116. package/package.json +3 -3
  117. package/src/contracts/Actors/ZLinkActorClient.ts +1 -1
  118. package/src/contracts/Channels/Calls.ts +1 -1
  119. package/src/contracts/Configuration/Builders.ts +2 -1
  120. package/src/contracts/Configuration/Configs.ts +0 -1
  121. package/src/contracts/Configuration/InternalDefaults.ts +4 -0
  122. package/src/contracts/Configuration/RegistrationBuilders.ts +13 -3
  123. package/src/contracts/Configuration/RegistrationNormalizers.ts +25 -0
  124. package/src/contracts/Configuration/RegistrationTypes.ts +3 -5
  125. package/src/contracts/Configuration/RegistrationValidators.ts +0 -4
  126. package/src/contracts/Spots/Contracts.ts +1 -1
  127. package/src/contracts/Streams/BoundSessionContracts.ts +1 -1
  128. package/src/contracts/Streams/IZLinkSession.ts +2 -3
  129. package/src/contracts/Streams/IZLinkSessionActor.ts +2 -6
  130. package/src/runtime/actors/actor-client.ts +9 -15
  131. package/src/runtime/actors/actor-mailbox.ts +7 -2
  132. package/src/runtime/actors/actor-request-deadline.ts +4 -49
  133. package/src/runtime/actors/actor-routed-join-transport.ts +3 -5
  134. package/src/runtime/backend/contracts/index.ts +1 -8
  135. package/src/runtime/backend/node/node-backend-adapter-support.ts +45 -5
  136. package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +0 -2
  137. package/src/runtime/backend/node/node-raw-binding-port.ts +15 -41
  138. package/src/runtime/backend/node/node-raw-mesh-backend.ts +1 -7
  139. package/src/runtime/backend/node/node-socket-backend-adapter.ts +1 -1
  140. package/src/runtime/channels/channel-clients.ts +9 -19
  141. package/src/runtime/channels/channel-envelope.ts +16 -28
  142. package/src/runtime/channels/channel-outbound-operations.ts +36 -38
  143. package/src/runtime/channels/channel-runtime-manager.ts +6 -28
  144. package/src/runtime/channels/channel-socket-options.ts +0 -13
  145. package/src/runtime/channels/channel-socket-registry.ts +3 -17
  146. package/src/runtime/channels/channel-transports.ts +7 -33
  147. package/src/runtime/channels/local-spot-route-transport.ts +1 -5
  148. package/src/runtime/channels/router-socket-spot-route-transport.ts +2 -6
  149. package/src/runtime/channels/source-spot-router.ts +2 -9
  150. package/src/runtime/channels/spot-node-route-transport.ts +2 -7
  151. package/src/runtime/channels/spot-route-bridge-transport.ts +5 -12
  152. package/src/runtime/channels/spot-route-dispatch-strategy.ts +11 -30
  153. package/src/runtime/execution/serial-execution-queue.ts +117 -29
  154. package/src/runtime/foundation/operation-registry.ts +16 -9
  155. package/src/runtime/foundation/raw-service-mesh-runtime.ts +17 -14
  156. package/src/runtime/foundation/service-authority-payload-codec.ts +0 -46
  157. package/src/runtime/foundation/service-stateful-registry.ts +10 -5
  158. package/src/runtime/foundation/service-stateful-runtime.ts +103 -181
  159. package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -25
  160. package/src/runtime/foundation/service-topology-registry.ts +30 -20
  161. package/src/runtime/host/actor-packet-relay.ts +8 -15
  162. package/src/runtime/host/index.ts +2 -26
  163. package/src/runtime/host/instance-activation-authority.ts +58 -125
  164. package/src/runtime/host/remote-bound-session-relay.ts +8 -16
  165. package/src/runtime/host/spot-address-transport.ts +107 -166
  166. package/src/runtime/host/stateful-authority-route-runtime.ts +22 -9
  167. package/src/runtime/locations/domain-store-contract.ts +5 -0
  168. package/src/runtime/locations/in-memory-authority-store.ts +42 -8
  169. package/src/runtime/locations/in-memory-location-store.ts +8 -0
  170. package/src/runtime/locations/internal-store-contracts.ts +1 -0
  171. package/src/runtime/locations/location-store-repository.ts +130 -68
  172. package/src/runtime/messaging/request-deadline.ts +58 -0
  173. package/src/runtime/messaging/submission-result.ts +18 -4
  174. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
  175. package/src/runtime/spots/spot-outbound.ts +14 -24
  176. package/src/runtime/spots/spot-serial-executor.ts +4 -1
  177. package/src/runtime/spots/spot-serial-turn-executor.ts +10 -0
  178. package/src/runtime/streams/bound-actor-relay-sender.ts +12 -26
  179. package/src/runtime/streams/bound-session-service.ts +1 -1
  180. package/src/runtime/streams/index.ts +5 -5
  181. package/src/runtime/streams/managed-stream.ts +3 -13
  182. package/src/runtime/streams/native-fallback-bound-session.ts +13 -25
  183. package/src/runtime/streams/protocol.ts +0 -13
  184. package/src/runtime/streams/session-calls.ts +8 -34
  185. package/src/runtime/streams/session-context.ts +6 -47
  186. package/src/runtime/streams/session-serial-executor.ts +0 -8
  187. package/src/runtime/streams/stream-message-utils.ts +1 -27
  188. package/src/runtime/streams/stream-session-runtime.ts +54 -97
  189. package/dist/runtime/streams/session-requests.d.ts +0 -25
  190. package/dist/runtime/streams/session-requests.js +0 -101
  191. package/src/runtime/streams/session-requests.ts +0 -110
@@ -1,6 +1,6 @@
1
1
  import { ZlinkStreamContentType } from '@zlink-systems/stream-wire';
2
2
  import { AsyncLocalStorage } from 'node:async_hooks';
3
- import { ZLinkFrameworkException } from '../../contracts';
3
+ import { ZLinkFrameworkErrorKind, ZLinkFrameworkException } from '../../contracts';
4
4
  import type { ZLinkAuthoritySnapshot } from '../locations/internal-location-contracts';
5
5
  import { decodeServiceClosingSpotAuthority } from './service-authority-payload-codec';
6
6
  import {
@@ -280,9 +280,10 @@ export type ServiceInstanceAuthorityRead =
280
280
  readonly authority?: ZLinkAuthoritySnapshot;
281
281
  };
282
282
 
283
- export type ServiceInstanceAuthorityReserve =
284
- | { readonly kind: 'reserved'; readonly reservation: ServiceInstanceActivationReservation }
285
- | { readonly kind: 'ready'; readonly route: ServiceInstanceRouteFence };
283
+ export type ServiceInstanceAuthorityReserve = {
284
+ readonly kind: 'reserved';
285
+ readonly reservation: ServiceInstanceActivationReservation;
286
+ };
286
287
 
287
288
  /**
288
289
  * Synchronous authority port used by the raw ingress turn. An asynchronous
@@ -337,13 +338,6 @@ export interface ServiceAsyncInstanceActivationAuthority {
337
338
  ): Promise<void>;
338
339
  }
339
340
 
340
- export class ServiceInstanceActivationRedirectError extends Error {
341
- constructor(readonly route: ServiceInstanceRouteFence) {
342
- super(`Instance Spot '${route.targetSpotId}' is owned by '${route.targetNodeRid}'.`);
343
- this.name = 'ServiceInstanceActivationRedirectError';
344
- }
345
- }
346
-
347
341
  export interface ServiceUserSpotOperationResult {
348
342
  readonly terminalResult: number;
349
343
  readonly failureCode: number;
@@ -980,7 +974,7 @@ export class ServiceStatefulRuntime {
980
974
  const committed = await authority.commit(target, reservation, spot);
981
975
  if (committed.kind !== 'committed') {
982
976
  this.registry.closeSpot(spot.ref);
983
- throw new ServiceInstanceActivationRedirectError(committed.route);
977
+ throw activationRefused();
984
978
  }
985
979
  return this.finishRecoveredInstanceActivation(envelope, committed.route, expectedCurrentRoute);
986
980
  }
@@ -2069,6 +2063,12 @@ export class ServiceStatefulRuntime {
2069
2063
  metadata ? record.parts[1] : undefined
2070
2064
  );
2071
2065
  }
2066
+ if (
2067
+ decoded.kind === 'instanceSpot' &&
2068
+ (decoded.operationKind === 'request') !== (record.requestSequence !== undefined)
2069
+ ) {
2070
+ return 'protocolError';
2071
+ }
2072
2072
  const hasPayload = [
2073
2073
  'spotSend',
2074
2074
  'spotRequest',
@@ -2131,6 +2131,14 @@ export class ServiceStatefulRuntime {
2131
2131
  canonicalPayload
2132
2132
  );
2133
2133
  } catch (error) {
2134
+ if (
2135
+ decoded.kind === 'instanceSpot' &&
2136
+ decoded.activation === 'missing' &&
2137
+ (error instanceof ZLinkFrameworkException || error instanceof ServiceStaleGenerationError)
2138
+ ) {
2139
+ this.finishMissingInstanceActivationFailure(record, decoded, error);
2140
+ return 'infrastructure';
2141
+ }
2134
2142
  const correlation = statefulCorrelation(decoded);
2135
2143
  if (correlation !== undefined) {
2136
2144
  const result = failure(error);
@@ -2243,7 +2251,7 @@ export class ServiceStatefulRuntime {
2243
2251
  sourceSpotId: record.sourceSpotId,
2244
2252
  targetSpot: record.target.spot,
2245
2253
  ...(record.kind === 'spotRequest'
2246
- ? { reply: this.replyPort(ingress, record.correlation, 'spotRequest') }
2254
+ ? { reply: this.replyPort(ingress, record.correlation) }
2247
2255
  : {})
2248
2256
  }
2249
2257
  );
@@ -2291,7 +2299,7 @@ export class ServiceStatefulRuntime {
2291
2299
  originalReplyRouteId: ingress.requestSequence ?? 0n
2292
2300
  },
2293
2301
  ...(record.kind === 'actorRequest'
2294
- ? { reply: this.replyPort(ingress, record.correlation, 'actorRequest') }
2302
+ ? { reply: this.replyPort(ingress, record.correlation) }
2295
2303
  : {})
2296
2304
  }
2297
2305
  );
@@ -2344,7 +2352,8 @@ export class ServiceStatefulRuntime {
2344
2352
  ingress: RawServiceIngressRecord,
2345
2353
  record: Extract<ServiceStatefulWireRecord, { readonly kind: 'instanceSpot' }>,
2346
2354
  payloadFrame: Buffer,
2347
- metadataFrame?: Buffer
2355
+ metadataFrame?: Buffer,
2356
+ localReply?: NonNullable<ServiceStatefulMailboxData['reply']>
2348
2357
  ): RawServicePumpResult {
2349
2358
  if (record.activation === 'missing' && this.asyncInstanceAuthority !== undefined) {
2350
2359
  const ownedIngress = shareIngressOwnership(ingress);
@@ -2352,7 +2361,7 @@ export class ServiceStatefulRuntime {
2352
2361
  ownedIngress,
2353
2362
  record,
2354
2363
  payloadFrame,
2355
- undefined,
2364
+ localReply,
2356
2365
  metadataFrame
2357
2366
  ).finally(() => ownedIngress.applicationJobOwner?.close());
2358
2367
  return 'infrastructure';
@@ -2384,7 +2393,7 @@ export class ServiceStatefulRuntime {
2384
2393
  record,
2385
2394
  payloadFrame,
2386
2395
  spot,
2387
- undefined,
2396
+ localReply,
2388
2397
  this.instanceApplicationTerminalCompletion(applicationTarget),
2389
2398
  metadataFrame
2390
2399
  );
@@ -2440,7 +2449,10 @@ export class ServiceStatefulRuntime {
2440
2449
  }
2441
2450
  const directReply =
2442
2451
  record.operationKind === 'request'
2443
- ? (localReply ?? this.replyPort(ingress, record.replyRouteId, 'instanceSpotRequest'))
2452
+ ? (localReply ??
2453
+ (ingress.requestSequence === undefined
2454
+ ? undefined
2455
+ : this.replyPort(ingress, record.replyRouteId)))
2444
2456
  : undefined;
2445
2457
  const applicationTarget =
2446
2458
  applicationTargetOverride ??
@@ -2475,7 +2487,7 @@ export class ServiceStatefulRuntime {
2475
2487
  ...(metadataFrame === undefined ? {} : { applicationMetadata: metadataFrame }),
2476
2488
  ...(terminalCompletion === undefined ? {} : { onTerminalCompletion: terminalCompletion }),
2477
2489
  ...(onHandlerTurnStarted === undefined ? {} : { onHandlerTurnStarted }),
2478
- ...(record.operationKind === 'request'
2490
+ ...(record.operationKind === 'request' && directReply !== undefined
2479
2491
  ? {
2480
2492
  reply:
2481
2493
  onTerminalCompletion === undefined
@@ -2530,30 +2542,7 @@ export class ServiceStatefulRuntime {
2530
2542
  throw new Error('Activated Instance message was not admitted to the local queue.');
2531
2543
  }
2532
2544
  } catch (error) {
2533
- let terminalError = error;
2534
- if (error instanceof ServiceInstanceActivationRedirectError && !this.closed) {
2535
- try {
2536
- await this.relayMissingInstanceActivation(
2537
- record,
2538
- payloadFrame,
2539
- error.route,
2540
- ingress,
2541
- localReply,
2542
- metadataFrame
2543
- );
2544
- return;
2545
- } catch (relayError) {
2546
- terminalError = relayError;
2547
- }
2548
- }
2549
- if (record.operationKind !== 'request') return;
2550
- const result = failure(terminalError);
2551
- if (localReply !== undefined) {
2552
- localReply(result.terminalResult, result.failureCode);
2553
- } else {
2554
- if (record.replyRouteId === undefined) return;
2555
- this.replyWire(ingress, record.replyRouteId, result.terminalResult, result.failureCode);
2556
- }
2545
+ this.finishMissingInstanceActivationFailure(ingress, record, error, localReply);
2557
2546
  }
2558
2547
  }
2559
2548
 
@@ -2604,7 +2593,12 @@ export class ServiceStatefulRuntime {
2604
2593
  throw new Error('Rematerialized Instance message was not admitted to the local queue.');
2605
2594
  }
2606
2595
  } catch (error) {
2607
- if (record.operationKind !== 'request' || record.replyRouteId === undefined) return;
2596
+ if (
2597
+ record.operationKind !== 'request' ||
2598
+ record.replyRouteId === undefined ||
2599
+ ingress.requestSequence === undefined
2600
+ )
2601
+ return;
2608
2602
  const result = failure(error);
2609
2603
  this.replyWire(ingress, record.replyRouteId, result.terminalResult, result.failureCode);
2610
2604
  }
@@ -2664,10 +2658,11 @@ export class ServiceStatefulRuntime {
2664
2658
 
2665
2659
  private instanceApplicationTerminalCompletion(
2666
2660
  target: ServiceInstanceApplicationTarget
2667
- ): () => Promise<void> {
2661
+ ): (() => Promise<void>) | undefined {
2662
+ const lifecycle = this.instanceApplicationLifecycle;
2663
+ if (lifecycle === undefined) return undefined;
2668
2664
  return async () => {
2669
- const authorityReleased =
2670
- (await this.instanceApplicationLifecycle?.completeTerminal(target)) ?? false;
2665
+ const authorityReleased = await lifecycle.completeTerminal(target);
2671
2666
  if (!authorityReleased) return;
2672
2667
  const current = this.instanceIntents.get(target.targetSpotId);
2673
2668
  if (
@@ -2731,91 +2726,39 @@ export class ServiceStatefulRuntime {
2731
2726
  for (const waiter of waiters) waiter();
2732
2727
  }
2733
2728
 
2734
- private async relayMissingInstanceActivation(
2729
+ private finishMissingInstanceActivationFailure(
2730
+ ingress: RawServiceIngressRecord,
2735
2731
  record: Extract<
2736
2732
  ServiceStatefulWireRecord,
2737
2733
  { readonly kind: 'instanceSpot'; readonly activation: 'missing' }
2738
2734
  >,
2739
- payloadFrame: Buffer,
2740
- route: ServiceInstanceRouteFence,
2741
- ingress: RawServiceIngressRecord,
2742
- localReply?: NonNullable<ServiceStatefulMailboxData['reply']>,
2743
- metadataFrame?: Buffer
2744
- ): Promise<void> {
2745
- const remainingMs = Number(record.deadlineUnixMs - BigInt(Date.now()));
2746
- if (remainingMs <= 0) {
2747
- throw new ServiceStaleGenerationError('spot', route.targetSpotId);
2748
- }
2749
- const redirectedTarget: ServiceInstanceActivationTarget = {
2750
- ...record.target,
2751
- targetNodeRid: route.targetNodeRid,
2752
- targetNodeGeneration: route.targetNodeGeneration,
2753
- targetSpotId: route.targetSpotId
2754
- };
2755
- if (record.operationKind === 'send') {
2756
- const submitted = await this.submitOneWay(
2757
- route.targetNodeRid,
2758
- instanceOperationParts(
2759
- [
2760
- encodeInstanceSpotActivationHeader(
2761
- redirectedTarget,
2762
- this.nodeGeneration,
2763
- this.nodeRid,
2764
- record.sourceSpotId,
2765
- 'send',
2766
- record.operation,
2767
- record.deadlineUnixMs,
2768
- undefined,
2769
- metadataFrame !== undefined
2770
- ),
2771
- payloadFrame
2772
- ],
2773
- metadataFrame
2774
- )
2775
- );
2776
- if (submitted !== SubmitResult.Ok) {
2777
- throw new Error(`Instance activation redirect was not admitted: ${submitted}.`);
2735
+ error: unknown,
2736
+ localReply?: NonNullable<ServiceStatefulMailboxData['reply']>
2737
+ ): void {
2738
+ if (record.operationKind === 'request') {
2739
+ if (record.deadlineUnixMs <= BigInt(Date.now())) return;
2740
+ const result = failure(error);
2741
+ if (localReply !== undefined) {
2742
+ localReply(result.terminalResult, result.failureCode);
2743
+ } else if (ingress.requestSequence !== undefined && record.replyRouteId !== undefined) {
2744
+ this.replyWire(ingress, record.replyRouteId, result.terminalResult, result.failureCode);
2778
2745
  }
2779
2746
  return;
2780
2747
  }
2781
-
2782
- const pending = this.operations.register(remainingMs);
2783
- this.submitRequest(
2784
- pending,
2785
- route.targetNodeRid,
2786
- instanceOperationParts(
2787
- [
2788
- encodeInstanceSpotActivationHeader(
2789
- redirectedTarget,
2790
- this.nodeGeneration,
2791
- this.nodeRid,
2792
- record.sourceSpotId,
2793
- 'request',
2794
- record.operation,
2795
- record.deadlineUnixMs,
2796
- pending.id,
2797
- metadataFrame !== undefined
2798
- ),
2799
- payloadFrame
2800
- ],
2801
- metadataFrame
2802
- ),
2803
- remainingMs,
2804
- 'instanceSpotRequest'
2805
- );
2806
- const result = await pending.promise;
2807
- if (localReply !== undefined) {
2808
- localReply(result.terminalResult, result.failureCode, result.payload);
2809
- return;
2810
- }
2811
- if (record.replyRouteId !== undefined) {
2812
- this.replyWire(
2813
- ingress,
2814
- record.replyRouteId,
2815
- result.terminalResult,
2816
- result.failureCode,
2817
- result.payload
2818
- );
2748
+ const reporter = this.dispatchErrors;
2749
+ if (reporter?.captureEnabled() === true) {
2750
+ reporter.report({
2751
+ surface: ZLinkDispatchErrorSurface.InstanceSpot,
2752
+ messageKind: ZLinkDispatchMessageKind.Send,
2753
+ reason: ZLinkDispatchErrorReason.StaleTarget,
2754
+ action: ZLinkDispatchErrorAction.Drop,
2755
+ meshName: this.dispatchErrorMeshName,
2756
+ sourceRid: record.sourceNodeRid,
2757
+ spotId: record.target.targetSpotId,
2758
+ instanceSpotType: record.target.stableType,
2759
+ activationState: 'activating',
2760
+ error
2761
+ });
2819
2762
  }
2820
2763
  }
2821
2764
 
@@ -2971,13 +2914,10 @@ export class ServiceStatefulRuntime {
2971
2914
  return local;
2972
2915
  }
2973
2916
  if (current.kind === 'ready') {
2974
- throw new ServiceInstanceActivationRedirectError(current.route);
2917
+ throw activationRefused();
2975
2918
  }
2976
2919
 
2977
2920
  const reserved = authority.reserve(target, record.operation, record.deadlineUnixMs);
2978
- if (reserved.kind === 'ready') {
2979
- throw new ServiceInstanceActivationRedirectError(reserved.route);
2980
- }
2981
2921
 
2982
2922
  let activation: { readonly spot: ServiceSpotState; readonly created: boolean };
2983
2923
  try {
@@ -2994,7 +2934,7 @@ export class ServiceStatefulRuntime {
2994
2934
  const committed = authority.commit(target, reserved.reservation, activation.spot);
2995
2935
  if (committed.kind === 'lost') {
2996
2936
  if (activation.created) this.registry.closeSpot(activation.spot.ref);
2997
- throw new ServiceInstanceActivationRedirectError(committed.route);
2937
+ throw activationRefused();
2998
2938
  }
2999
2939
  if (!routeMatchesLocal(committed.route, activation.spot, this.nodeRid, this.nodeGeneration)) {
3000
2940
  if (activation.created) this.registry.closeSpot(activation.spot.ref);
@@ -3073,14 +3013,14 @@ export class ServiceStatefulRuntime {
3073
3013
  }
3074
3014
  };
3075
3015
  }
3016
+ if (current.kind === 'ready' && current.route.targetNodeRid !== this.nodeRid) {
3017
+ throw activationRefused();
3018
+ }
3076
3019
  if (
3077
3020
  current.kind === 'ready' &&
3078
3021
  current.authority !== undefined &&
3079
3022
  decodeServiceClosingSpotAuthority(current.authority.payload) !== undefined
3080
3023
  ) {
3081
- if (current.route.targetNodeRid !== this.nodeRid) {
3082
- throw new ServiceInstanceActivationRedirectError(current.route);
3083
- }
3084
3024
  if (
3085
3025
  current.route.targetNodeGeneration !== this.nodeGeneration ||
3086
3026
  (local !== undefined &&
@@ -3095,17 +3035,11 @@ export class ServiceStatefulRuntime {
3095
3035
  };
3096
3036
  }
3097
3037
  if (!this.acceptsLocalMissingInstancePlacement()) {
3098
- if (current.kind === 'ready' && current.route.targetNodeRid !== this.nodeRid) {
3099
- throw new ServiceInstanceActivationRedirectError(current.route);
3100
- }
3101
3038
  // A draining or retiring node must not create a successor generation
3102
3039
  // from a stale client route while relocation is publishing its target.
3103
3040
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3104
3041
  }
3105
3042
  if (current.kind === 'ready') {
3106
- if (current.route.targetNodeRid !== this.nodeRid) {
3107
- throw new ServiceInstanceActivationRedirectError(current.route);
3108
- }
3109
3043
  if (current.route.targetNodeGeneration !== this.nodeGeneration) {
3110
3044
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3111
3045
  }
@@ -3148,16 +3082,9 @@ export class ServiceStatefulRuntime {
3148
3082
  await lifecycle.discard(target);
3149
3083
  materialized = lifecycle.isMaterialized(target);
3150
3084
  }
3151
- // The authority can still be in a Creating reservation after the local
3152
- // registry exposes the activation. Allow the authority reserve operation
3153
- // to join that attempt instead of treating the local projection as stale.
3154
- // A newer reservation can also become visible after an explicit close
3155
- // releases authority but before local disposal removes the old
3156
- // projection. The close gate serializes that replacement; rejecting it
3157
- // here turns a valid next-generation Instance intent into a stale-route
3158
- // failure.
3159
- // If disposal did not remove an orphaned materialization, keep fencing
3160
- // it instead of admitting work into two application generations.
3085
+ // The local projection does not decide reservation ownership. Preserve
3086
+ // the Creating fence for Reserve, which refuses a competing attempt.
3087
+ // Keep fencing an orphan that disposal did not remove.
3161
3088
  if (
3162
3089
  (!joinsCreating && current.kind !== 'missing') ||
3163
3090
  (!joinsCreating && materialized !== false && !materializing)
@@ -3186,9 +3113,6 @@ export class ServiceStatefulRuntime {
3186
3113
  } finally {
3187
3114
  deadline.close();
3188
3115
  }
3189
- if (reserved.kind === 'ready') {
3190
- throw new ServiceInstanceActivationRedirectError(reserved.route);
3191
- }
3192
3116
  if (this.closed || record.deadlineUnixMs < BigInt(Date.now())) {
3193
3117
  await authority.abort(target, reserved.reservation);
3194
3118
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
@@ -3224,7 +3148,7 @@ export class ServiceStatefulRuntime {
3224
3148
  if (committed.kind === 'lost') {
3225
3149
  if (activation.created) this.registry.closeSpot(activation.spot.ref);
3226
3150
  await this.instanceApplicationLifecycle?.discard(target);
3227
- throw new ServiceInstanceActivationRedirectError(committed.route);
3151
+ throw activationRefused();
3228
3152
  }
3229
3153
  if (!routeMatchesLocal(committed.route, activation.spot, this.nodeRid, this.nodeGeneration)) {
3230
3154
  if (activation.created) this.registry.closeSpot(activation.spot.ref);
@@ -3269,7 +3193,7 @@ export class ServiceStatefulRuntime {
3269
3193
  const confirmed = await authority.read(target);
3270
3194
  if (confirmed.kind !== 'ready' || !sameInstanceRoute(confirmed.route, route)) {
3271
3195
  if (confirmed.kind === 'ready' && confirmed.route.targetNodeRid !== this.nodeRid) {
3272
- throw new ServiceInstanceActivationRedirectError(confirmed.route);
3196
+ throw activationRefused();
3273
3197
  }
3274
3198
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3275
3199
  }
@@ -3404,7 +3328,7 @@ export class ServiceStatefulRuntime {
3404
3328
  kindData: control,
3405
3329
  ...(canonicalApplicationPayload === undefined ? {} : { canonicalApplicationPayload }),
3406
3330
  reply: (terminalResult, failureCode, replyPayload, tail) => {
3407
- return this.replyPort(ingress, record.correlation, 'actorJoin')(
3331
+ return this.replyPort(ingress, record.correlation)(
3408
3332
  terminalResult,
3409
3333
  failureCode,
3410
3334
  replyPayload,
@@ -4037,14 +3961,12 @@ export class ServiceStatefulRuntime {
4037
3961
 
4038
3962
  private replyPort(
4039
3963
  ingress: RawServiceIngressRecord,
4040
- correlation: bigint | undefined,
4041
- operationKind: 'spotRequest' | 'actorRequest' | 'actorJoin' | 'instanceSpotRequest'
3964
+ correlation: bigint | undefined
4042
3965
  ): NonNullable<ServiceStatefulMailboxData['reply']> {
4043
3966
  if (correlation === undefined)
4044
3967
  throw new ServiceWireProtocolError('Request correlation is missing.');
4045
3968
  return (terminalResult, failureCode, payload, tail) => {
4046
3969
  this.replyWire(ingress, correlation, terminalResult, failureCode, payload, tail);
4047
- void operationKind;
4048
3970
  return true;
4049
3971
  };
4050
3972
  }
@@ -4057,10 +3979,11 @@ export class ServiceStatefulRuntime {
4057
3979
  payload?: ServiceApplicationPayload,
4058
3980
  tail?: ServiceStatefulReplyTail
4059
3981
  ): void {
4060
- this.raw.replyService(ingress, [
3982
+ const parts = [
4061
3983
  encodeStatefulReply(correlation, terminalResult, failureCode, tail),
4062
3984
  ...(payload === undefined ? [] : [encodeApplicationPayload(payload)])
4063
- ]);
3985
+ ];
3986
+ this.raw.replyService(ingress, parts);
4064
3987
  }
4065
3988
 
4066
3989
  private handleUserSpotOperation(
@@ -4763,23 +4686,17 @@ export class ServiceStatefulRuntime {
4763
4686
  return;
4764
4687
  }
4765
4688
  if (decoded.kind === 'instanceSpot') {
4766
- if (decoded.activation === 'missing' && this.asyncInstanceAuthority !== undefined) {
4767
- void this.continueMissingInstanceActivation(ingress, decoded, payloadFrame!, localReply);
4768
- return;
4769
- }
4770
- try {
4771
- const activation = this.requireInstanceActivation(ingress, decoded);
4772
- this.enqueueApplicationFrame(ingress, `spot:${activation.ref.spotId}`, payloadFrame!, {
4773
- receiveKind: ReceiveKind.InstanceSpotActivation,
4774
- operationKind: OperationKind.InstanceSpotRequest,
4775
- correlation: pending.id,
4776
- ...(decoded.sourceSpotId === undefined ? {} : { sourceSpotId: decoded.sourceSpotId }),
4777
- targetSpot: activation.ref,
4778
- reply: localReply
4779
- });
4780
- } catch (error) {
4781
- this.operations.reply(pending.id, failure(error));
4782
- }
4689
+ const hasMetadata = (ingress.flags & M6bServiceWireFlag.metadata) !== 0;
4690
+ const metadataFrame = hasMetadata
4691
+ ? validateServiceMetadataFrame(ingress.parts[1]!)
4692
+ : undefined;
4693
+ this.enqueueInstanceSpot(
4694
+ ingress,
4695
+ decoded,
4696
+ ingress.parts[hasMetadata ? 2 : 1]!,
4697
+ metadataFrame,
4698
+ localReply
4699
+ );
4783
4700
  return;
4784
4701
  }
4785
4702
  this.operations.reply(pending.id, {
@@ -4790,7 +4707,7 @@ export class ServiceStatefulRuntime {
4790
4707
  }
4791
4708
 
4792
4709
  private completeRemoteReply(
4793
- pending: ServiceStatefulPendingOperation,
4710
+ pending: Pick<ServiceStatefulPendingOperation, 'id'>,
4794
4711
  targetNodeRid: string,
4795
4712
  operationKind:
4796
4713
  | 'spotRequest'
@@ -5038,9 +4955,7 @@ export class ServiceStatefulRuntime {
5038
4955
  ...(wire.correlation === undefined ? {} : { correlation: wire.correlation }),
5039
4956
  sourceSpotId: wire.sourceSpotId,
5040
4957
  targetSpot: wire.target.spot,
5041
- ...(wire.kind === 'spotRequest'
5042
- ? { reply: this.replyPort(ingress, wire.correlation, 'spotRequest') }
5043
- : {})
4958
+ ...(wire.kind === 'spotRequest' ? { reply: this.replyPort(ingress, wire.correlation) } : {})
5044
4959
  }
5045
4960
  );
5046
4961
  }
@@ -5523,6 +5438,13 @@ function actorLocation(actor: ServiceActorState): ActorLocation {
5523
5438
  };
5524
5439
  }
5525
5440
 
5441
+ function activationRefused(): ZLinkFrameworkException {
5442
+ return new ZLinkFrameworkException(
5443
+ ZLinkFrameworkErrorKind.Unavailable,
5444
+ 'Instance cold activation authority was refused.'
5445
+ );
5446
+ }
5447
+
5526
5448
  function failure(error: unknown): ServiceStatefulResult {
5527
5449
  if (error instanceof ServiceStaleGenerationError) {
5528
5450
  return internalFrameworkWireReply(ZLinkFrameworkInternalErrorKind.ActorLocationStale);
@@ -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[] {