@zlink-systems/framework 0.28.0 → 0.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/Builders.d.ts +2 -0
  3. package/dist/contracts/Configuration/InternalDefaults.d.ts +3 -0
  4. package/dist/contracts/Configuration/InternalDefaults.js +4 -1
  5. package/dist/contracts/Configuration/RegistrationBuilders.js +9 -0
  6. package/dist/contracts/Configuration/RegistrationNormalizers.d.ts +1 -0
  7. package/dist/contracts/Configuration/RegistrationNormalizers.js +16 -0
  8. package/dist/contracts/Configuration/RegistrationTypes.d.ts +3 -0
  9. package/dist/runtime/actors/actor-mailbox.d.ts +2 -2
  10. package/dist/runtime/actors/actor-mailbox.js +2 -2
  11. package/dist/runtime/actors/actor-runtime-contracts.d.ts +1 -0
  12. package/dist/runtime/actors/index.d.ts +2 -0
  13. package/dist/runtime/actors/index.js +21 -6
  14. package/dist/runtime/backend/node/node-raw-mesh-backend.js +1 -3
  15. package/dist/runtime/execution/serial-execution-queue.d.ts +12 -3
  16. package/dist/runtime/execution/serial-execution-queue.js +116 -28
  17. package/dist/runtime/foundation/operation-registry.d.ts +3 -2
  18. package/dist/runtime/foundation/operation-registry.js +5 -1
  19. package/dist/runtime/foundation/service-authority-payload-codec.d.ts +0 -2
  20. package/dist/runtime/foundation/service-authority-payload-codec.js +0 -43
  21. package/dist/runtime/foundation/service-stateful-registry.d.ts +4 -3
  22. package/dist/runtime/foundation/service-stateful-registry.js +5 -2
  23. package/dist/runtime/foundation/service-stateful-runtime.d.ts +1 -8
  24. package/dist/runtime/foundation/service-stateful-runtime.js +105 -145
  25. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -11
  26. package/dist/runtime/foundation/service-stateful-wire-codec.js +1 -23
  27. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -0
  28. package/dist/runtime/foundation/service-topology-registry.js +16 -17
  29. package/dist/runtime/handlers/handler-instance-scope.js +1 -1
  30. package/dist/runtime/host/actor-placement-coordinator.d.ts +2 -1
  31. package/dist/runtime/host/actor-placement-coordinator.js +2 -1
  32. package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -1
  33. package/dist/runtime/host/actor-runtime-options-factory.js +1 -0
  34. package/dist/runtime/host/index.d.ts +1 -1
  35. package/dist/runtime/host/index.js +3 -2
  36. package/dist/runtime/host/instance-activation-authority.d.ts +0 -1
  37. package/dist/runtime/host/instance-activation-authority.js +46 -97
  38. package/dist/runtime/host/spot-address-transport.js +1 -1
  39. package/dist/runtime/host/stateful-authority-route-runtime.d.ts +1 -0
  40. package/dist/runtime/host/stateful-authority-route-runtime.js +17 -9
  41. package/dist/runtime/locations/domain-store-contract.d.ts +3 -1
  42. package/dist/runtime/locations/in-memory-authority-store.d.ts +3 -0
  43. package/dist/runtime/locations/in-memory-authority-store.js +47 -12
  44. package/dist/runtime/locations/in-memory-location-store.d.ts +2 -1
  45. package/dist/runtime/locations/in-memory-location-store.js +15 -10
  46. package/dist/runtime/locations/internal-location-contracts.d.ts +5 -0
  47. package/dist/runtime/locations/internal-store-contracts.d.ts +1 -1
  48. package/dist/runtime/locations/location-store-repository.d.ts +2 -0
  49. package/dist/runtime/locations/location-store-repository.js +130 -54
  50. package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
  51. package/dist/runtime/spots/spot-entry-activation.js +3 -0
  52. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +1 -0
  53. package/dist/runtime/spots/spot-node-runtime-manager.js +6 -0
  54. package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
  55. package/dist/runtime/spots/spot-serial-executor.js +14 -2
  56. package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
  57. package/dist/runtime/spots/spot-serial-turn-executor.js +5 -0
  58. package/dist/runtime/streams/index.d.ts +0 -1
  59. package/dist/runtime/streams/index.js +4 -3
  60. package/dist/runtime/streams/protocol.js +0 -9
  61. package/dist/runtime/streams/session-context.d.ts +0 -4
  62. package/dist/runtime/streams/session-context.js +0 -26
  63. package/dist/runtime/streams/session-serial-executor.d.ts +0 -1
  64. package/dist/runtime/streams/session-serial-executor.js +0 -3
  65. package/dist/runtime/streams/stream-message-utils.d.ts +0 -1
  66. package/dist/runtime/streams/stream-message-utils.js +0 -20
  67. package/dist/runtime/streams/stream-session-runtime.d.ts +6 -3
  68. package/dist/runtime/streams/stream-session-runtime.js +40 -85
  69. package/package.json +3 -3
  70. package/src/contracts/Configuration/Builders.ts +2 -0
  71. package/src/contracts/Configuration/InternalDefaults.ts +4 -0
  72. package/src/contracts/Configuration/RegistrationBuilders.ts +13 -0
  73. package/src/contracts/Configuration/RegistrationNormalizers.ts +25 -0
  74. package/src/contracts/Configuration/RegistrationTypes.ts +3 -0
  75. package/src/runtime/actors/actor-mailbox.ts +7 -2
  76. package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
  77. package/src/runtime/actors/index.ts +77 -42
  78. package/src/runtime/backend/node/node-raw-mesh-backend.ts +1 -7
  79. package/src/runtime/execution/serial-execution-queue.ts +117 -29
  80. package/src/runtime/foundation/operation-registry.ts +16 -9
  81. package/src/runtime/foundation/service-authority-payload-codec.ts +0 -46
  82. package/src/runtime/foundation/service-stateful-registry.ts +10 -5
  83. package/src/runtime/foundation/service-stateful-runtime.ts +152 -224
  84. package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -25
  85. package/src/runtime/foundation/service-topology-registry.ts +30 -20
  86. package/src/runtime/handlers/handler-instance-scope.ts +2 -1
  87. package/src/runtime/host/actor-placement-coordinator.ts +8 -2
  88. package/src/runtime/host/actor-runtime-options-factory.ts +6 -0
  89. package/src/runtime/host/index.ts +6 -2
  90. package/src/runtime/host/instance-activation-authority.ts +58 -125
  91. package/src/runtime/host/spot-address-transport.ts +1 -1
  92. package/src/runtime/host/stateful-authority-route-runtime.ts +22 -9
  93. package/src/runtime/locations/domain-store-contract.ts +6 -1
  94. package/src/runtime/locations/in-memory-authority-store.ts +83 -12
  95. package/src/runtime/locations/in-memory-location-store.ts +22 -10
  96. package/src/runtime/locations/internal-location-contracts.ts +6 -0
  97. package/src/runtime/locations/internal-store-contracts.ts +1 -0
  98. package/src/runtime/locations/location-store-repository.ts +182 -69
  99. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
  100. package/src/runtime/spots/spot-entry-activation.ts +4 -0
  101. package/src/runtime/spots/spot-node-runtime-manager.ts +7 -0
  102. package/src/runtime/spots/spot-serial-executor.ts +14 -5
  103. package/src/runtime/spots/spot-serial-turn-executor.ts +10 -0
  104. package/src/runtime/streams/index.ts +3 -1
  105. package/src/runtime/streams/protocol.ts +0 -13
  106. package/src/runtime/streams/session-context.ts +0 -32
  107. package/src/runtime/streams/session-serial-executor.ts +0 -8
  108. package/src/runtime/streams/stream-message-utils.ts +1 -27
  109. package/src/runtime/streams/stream-session-runtime.ts +54 -97
  110. package/dist/runtime/streams/session-requests.d.ts +0 -25
  111. package/dist/runtime/streams/session-requests.js +0 -101
  112. package/src/runtime/streams/session-requests.ts +0 -110
@@ -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;
@@ -438,6 +432,7 @@ export class ServiceStatefulRuntime {
438
432
  Promise<{
439
433
  readonly spot: Pick<ServiceSpotState, 'ref'>;
440
434
  readonly route: ServiceInstanceRouteFence;
435
+ readonly stableType: string;
441
436
  }>
442
437
  >();
443
438
  private closed = false;
@@ -980,7 +975,7 @@ export class ServiceStatefulRuntime {
980
975
  const committed = await authority.commit(target, reservation, spot);
981
976
  if (committed.kind !== 'committed') {
982
977
  this.registry.closeSpot(spot.ref);
983
- throw new ServiceInstanceActivationRedirectError(committed.route);
978
+ throw activationRefused();
984
979
  }
985
980
  return this.finishRecoveredInstanceActivation(envelope, committed.route, expectedCurrentRoute);
986
981
  }
@@ -1565,7 +1560,7 @@ export class ServiceStatefulRuntime {
1565
1560
  this.nodeRid,
1566
1561
  sourceSpotId,
1567
1562
  'request',
1568
- { high: this.nodeGeneration, low: pending.id },
1563
+ { high: ZLINK_NATIVE_CORRELATION_OPERATION_NAMESPACE, low: pending.id },
1569
1564
  deadlineUnixMs,
1570
1565
  pending.id,
1571
1566
  metadataFrame !== undefined
@@ -2069,6 +2064,12 @@ export class ServiceStatefulRuntime {
2069
2064
  metadata ? record.parts[1] : undefined
2070
2065
  );
2071
2066
  }
2067
+ if (
2068
+ decoded.kind === 'instanceSpot' &&
2069
+ (decoded.operationKind === 'request') !== (record.requestSequence !== undefined)
2070
+ ) {
2071
+ return 'protocolError';
2072
+ }
2072
2073
  const hasPayload = [
2073
2074
  'spotSend',
2074
2075
  'spotRequest',
@@ -2131,6 +2132,14 @@ export class ServiceStatefulRuntime {
2131
2132
  canonicalPayload
2132
2133
  );
2133
2134
  } catch (error) {
2135
+ if (
2136
+ decoded.kind === 'instanceSpot' &&
2137
+ decoded.activation === 'missing' &&
2138
+ (error instanceof ZLinkFrameworkException || error instanceof ServiceStaleGenerationError)
2139
+ ) {
2140
+ this.finishMissingInstanceActivationFailure(record, decoded, error);
2141
+ return 'infrastructure';
2142
+ }
2134
2143
  const correlation = statefulCorrelation(decoded);
2135
2144
  if (correlation !== undefined) {
2136
2145
  const result = failure(error);
@@ -2243,7 +2252,7 @@ export class ServiceStatefulRuntime {
2243
2252
  sourceSpotId: record.sourceSpotId,
2244
2253
  targetSpot: record.target.spot,
2245
2254
  ...(record.kind === 'spotRequest'
2246
- ? { reply: this.replyPort(ingress, record.correlation, 'spotRequest') }
2255
+ ? { reply: this.replyPort(ingress, record.correlation) }
2247
2256
  : {})
2248
2257
  }
2249
2258
  );
@@ -2291,7 +2300,7 @@ export class ServiceStatefulRuntime {
2291
2300
  originalReplyRouteId: ingress.requestSequence ?? 0n
2292
2301
  },
2293
2302
  ...(record.kind === 'actorRequest'
2294
- ? { reply: this.replyPort(ingress, record.correlation, 'actorRequest') }
2303
+ ? { reply: this.replyPort(ingress, record.correlation) }
2295
2304
  : {})
2296
2305
  }
2297
2306
  );
@@ -2344,7 +2353,8 @@ export class ServiceStatefulRuntime {
2344
2353
  ingress: RawServiceIngressRecord,
2345
2354
  record: Extract<ServiceStatefulWireRecord, { readonly kind: 'instanceSpot' }>,
2346
2355
  payloadFrame: Buffer,
2347
- metadataFrame?: Buffer
2356
+ metadataFrame?: Buffer,
2357
+ localReply?: NonNullable<ServiceStatefulMailboxData['reply']>
2348
2358
  ): RawServicePumpResult {
2349
2359
  if (record.activation === 'missing' && this.asyncInstanceAuthority !== undefined) {
2350
2360
  const ownedIngress = shareIngressOwnership(ingress);
@@ -2352,7 +2362,7 @@ export class ServiceStatefulRuntime {
2352
2362
  ownedIngress,
2353
2363
  record,
2354
2364
  payloadFrame,
2355
- undefined,
2365
+ localReply,
2356
2366
  metadataFrame
2357
2367
  ).finally(() => ownedIngress.applicationJobOwner?.close());
2358
2368
  return 'infrastructure';
@@ -2384,7 +2394,7 @@ export class ServiceStatefulRuntime {
2384
2394
  record,
2385
2395
  payloadFrame,
2386
2396
  spot,
2387
- undefined,
2397
+ localReply,
2388
2398
  this.instanceApplicationTerminalCompletion(applicationTarget),
2389
2399
  metadataFrame
2390
2400
  );
@@ -2440,7 +2450,10 @@ export class ServiceStatefulRuntime {
2440
2450
  }
2441
2451
  const directReply =
2442
2452
  record.operationKind === 'request'
2443
- ? (localReply ?? this.replyPort(ingress, record.replyRouteId, 'instanceSpotRequest'))
2453
+ ? (localReply ??
2454
+ (ingress.requestSequence === undefined
2455
+ ? undefined
2456
+ : this.replyPort(ingress, record.replyRouteId)))
2444
2457
  : undefined;
2445
2458
  const applicationTarget =
2446
2459
  applicationTargetOverride ??
@@ -2475,7 +2488,7 @@ export class ServiceStatefulRuntime {
2475
2488
  ...(metadataFrame === undefined ? {} : { applicationMetadata: metadataFrame }),
2476
2489
  ...(terminalCompletion === undefined ? {} : { onTerminalCompletion: terminalCompletion }),
2477
2490
  ...(onHandlerTurnStarted === undefined ? {} : { onHandlerTurnStarted }),
2478
- ...(record.operationKind === 'request'
2491
+ ...(record.operationKind === 'request' && directReply !== undefined
2479
2492
  ? {
2480
2493
  reply:
2481
2494
  onTerminalCompletion === undefined
@@ -2508,52 +2521,26 @@ export class ServiceStatefulRuntime {
2508
2521
  ): Promise<void> {
2509
2522
  try {
2510
2523
  this.validateInstanceIngress(ingress, record);
2511
- const activation = await this.activateMissingInstanceAsync(
2512
- record,
2513
- payloadFrame,
2514
- metadataFrame
2515
- );
2516
- if (this.closed) return;
2517
- const terminal = this.activationTerminalCompletion(record.target, activation.route);
2518
- const admitted = this.enqueueActivatedInstanceSpot(
2519
- ingress,
2520
- record,
2521
- payloadFrame,
2522
- activation.spot,
2523
- localReply,
2524
- terminal.onTerminalCompletion,
2525
- metadataFrame,
2526
- terminal.onHandlerTurnStarted,
2527
- this.instanceApplicationTarget(record, activation.route.objectGeneration)
2528
- );
2529
- if (admitted !== 'application') {
2530
- throw new Error('Activated Instance message was not admitted to the local queue.');
2531
- }
2532
- } 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;
2524
+ await this.activateMissingInstanceAsync(record, payloadFrame, metadataFrame, (activation) => {
2525
+ if (this.closed) return;
2526
+ const terminal = this.activationTerminalCompletion(record.target, activation.route);
2527
+ const admitted = this.enqueueActivatedInstanceSpot(
2528
+ ingress,
2529
+ record,
2530
+ payloadFrame,
2531
+ activation.spot,
2532
+ localReply,
2533
+ terminal.onTerminalCompletion,
2534
+ metadataFrame,
2535
+ terminal.onHandlerTurnStarted,
2536
+ this.instanceApplicationTarget(record, activation.route.objectGeneration)
2537
+ );
2538
+ if (admitted !== 'application') {
2539
+ throw new Error('Activated Instance message was not admitted to the local queue.');
2547
2540
  }
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
- }
2541
+ });
2542
+ } catch (error) {
2543
+ this.finishMissingInstanceActivationFailure(ingress, record, error, localReply);
2557
2544
  }
2558
2545
  }
2559
2546
 
@@ -2604,7 +2591,12 @@ export class ServiceStatefulRuntime {
2604
2591
  throw new Error('Rematerialized Instance message was not admitted to the local queue.');
2605
2592
  }
2606
2593
  } catch (error) {
2607
- if (record.operationKind !== 'request' || record.replyRouteId === undefined) return;
2594
+ if (
2595
+ record.operationKind !== 'request' ||
2596
+ record.replyRouteId === undefined ||
2597
+ ingress.requestSequence === undefined
2598
+ )
2599
+ return;
2608
2600
  const result = failure(error);
2609
2601
  this.replyWire(ingress, record.replyRouteId, result.terminalResult, result.failureCode);
2610
2602
  }
@@ -2664,10 +2656,11 @@ export class ServiceStatefulRuntime {
2664
2656
 
2665
2657
  private instanceApplicationTerminalCompletion(
2666
2658
  target: ServiceInstanceApplicationTarget
2667
- ): () => Promise<void> {
2659
+ ): (() => Promise<void>) | undefined {
2660
+ const lifecycle = this.instanceApplicationLifecycle;
2661
+ if (lifecycle === undefined) return undefined;
2668
2662
  return async () => {
2669
- const authorityReleased =
2670
- (await this.instanceApplicationLifecycle?.completeTerminal(target)) ?? false;
2663
+ const authorityReleased = await lifecycle.completeTerminal(target);
2671
2664
  if (!authorityReleased) return;
2672
2665
  const current = this.instanceIntents.get(target.targetSpotId);
2673
2666
  if (
@@ -2731,91 +2724,39 @@ export class ServiceStatefulRuntime {
2731
2724
  for (const waiter of waiters) waiter();
2732
2725
  }
2733
2726
 
2734
- private async relayMissingInstanceActivation(
2727
+ private finishMissingInstanceActivationFailure(
2728
+ ingress: RawServiceIngressRecord,
2735
2729
  record: Extract<
2736
2730
  ServiceStatefulWireRecord,
2737
2731
  { readonly kind: 'instanceSpot'; readonly activation: 'missing' }
2738
2732
  >,
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}.`);
2733
+ error: unknown,
2734
+ localReply?: NonNullable<ServiceStatefulMailboxData['reply']>
2735
+ ): void {
2736
+ if (record.operationKind === 'request') {
2737
+ if (record.deadlineUnixMs <= BigInt(Date.now())) return;
2738
+ const result = failure(error);
2739
+ if (localReply !== undefined) {
2740
+ localReply(result.terminalResult, result.failureCode);
2741
+ } else if (ingress.requestSequence !== undefined && record.replyRouteId !== undefined) {
2742
+ this.replyWire(ingress, record.replyRouteId, result.terminalResult, result.failureCode);
2778
2743
  }
2779
2744
  return;
2780
2745
  }
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
- );
2746
+ const reporter = this.dispatchErrors;
2747
+ if (reporter?.captureEnabled() === true) {
2748
+ reporter.report({
2749
+ surface: ZLinkDispatchErrorSurface.InstanceSpot,
2750
+ messageKind: ZLinkDispatchMessageKind.Send,
2751
+ reason: ZLinkDispatchErrorReason.StaleTarget,
2752
+ action: ZLinkDispatchErrorAction.Drop,
2753
+ meshName: this.dispatchErrorMeshName,
2754
+ sourceRid: record.sourceNodeRid,
2755
+ spotId: record.target.targetSpotId,
2756
+ instanceSpotType: record.target.stableType,
2757
+ activationState: 'activating',
2758
+ error
2759
+ });
2819
2760
  }
2820
2761
  }
2821
2762
 
@@ -2971,13 +2912,10 @@ export class ServiceStatefulRuntime {
2971
2912
  return local;
2972
2913
  }
2973
2914
  if (current.kind === 'ready') {
2974
- throw new ServiceInstanceActivationRedirectError(current.route);
2915
+ throw activationRefused();
2975
2916
  }
2976
2917
 
2977
2918
  const reserved = authority.reserve(target, record.operation, record.deadlineUnixMs);
2978
- if (reserved.kind === 'ready') {
2979
- throw new ServiceInstanceActivationRedirectError(reserved.route);
2980
- }
2981
2919
 
2982
2920
  let activation: { readonly spot: ServiceSpotState; readonly created: boolean };
2983
2921
  try {
@@ -2994,7 +2932,7 @@ export class ServiceStatefulRuntime {
2994
2932
  const committed = authority.commit(target, reserved.reservation, activation.spot);
2995
2933
  if (committed.kind === 'lost') {
2996
2934
  if (activation.created) this.registry.closeSpot(activation.spot.ref);
2997
- throw new ServiceInstanceActivationRedirectError(committed.route);
2935
+ throw activationRefused();
2998
2936
  }
2999
2937
  if (!routeMatchesLocal(committed.route, activation.spot, this.nodeRid, this.nodeGeneration)) {
3000
2938
  if (activation.created) this.registry.closeSpot(activation.spot.ref);
@@ -3009,15 +2947,35 @@ export class ServiceStatefulRuntime {
3009
2947
  { readonly kind: 'instanceSpot'; readonly activation: 'missing' }
3010
2948
  >,
3011
2949
  payloadFrame: Buffer,
3012
- metadataFrame?: Buffer
3013
- ): Promise<{
3014
- readonly spot: Pick<ServiceSpotState, 'ref'>;
3015
- readonly route: ServiceInstanceRouteFence;
3016
- }> {
3017
- const key = instanceActivationKey(record);
2950
+ metadataFrame: Buffer | undefined,
2951
+ admit: (activation: {
2952
+ readonly spot: Pick<ServiceSpotState, 'ref'>;
2953
+ readonly route: ServiceInstanceRouteFence;
2954
+ }) => void
2955
+ ): Promise<unknown> {
2956
+ const key = record.target.targetSpotId;
3018
2957
  const pending = this.pendingInstanceActivations.get(key);
3019
- if (pending !== undefined) return pending;
3020
- const activation = this.runMissingInstanceActivation(record, payloadFrame, metadataFrame);
2958
+ const ready =
2959
+ pending ??
2960
+ this.runMissingInstanceActivation(record, payloadFrame, metadataFrame).then((activation) => ({
2961
+ ...activation,
2962
+ stableType: record.target.stableType
2963
+ }));
2964
+ const operation = ready.then((value) => {
2965
+ if (value.stableType !== record.target.stableType) {
2966
+ throw new ZLinkFrameworkException(
2967
+ ZLinkFrameworkErrorKind.TypeMismatch,
2968
+ 'Instance Spot type does not match.'
2969
+ );
2970
+ }
2971
+ admit(value);
2972
+ return value;
2973
+ });
2974
+ // The caller owns operation failure. Keep the activation available for later participants.
2975
+ const activation = operation.then(
2976
+ (value) => value,
2977
+ () => ready
2978
+ );
3021
2979
  this.pendingInstanceActivations.set(key, activation);
3022
2980
  const clear = () => {
3023
2981
  if (this.pendingInstanceActivations.get(key) === activation) {
@@ -3025,7 +2983,7 @@ export class ServiceStatefulRuntime {
3025
2983
  }
3026
2984
  };
3027
2985
  void activation.then(clear, clear);
3028
- return activation;
2986
+ return operation;
3029
2987
  }
3030
2988
 
3031
2989
  private async runMissingInstanceActivation(
@@ -3073,14 +3031,14 @@ export class ServiceStatefulRuntime {
3073
3031
  }
3074
3032
  };
3075
3033
  }
3034
+ if (current.kind === 'ready' && current.route.targetNodeRid !== this.nodeRid) {
3035
+ throw activationRefused();
3036
+ }
3076
3037
  if (
3077
3038
  current.kind === 'ready' &&
3078
3039
  current.authority !== undefined &&
3079
3040
  decodeServiceClosingSpotAuthority(current.authority.payload) !== undefined
3080
3041
  ) {
3081
- if (current.route.targetNodeRid !== this.nodeRid) {
3082
- throw new ServiceInstanceActivationRedirectError(current.route);
3083
- }
3084
3042
  if (
3085
3043
  current.route.targetNodeGeneration !== this.nodeGeneration ||
3086
3044
  (local !== undefined &&
@@ -3095,17 +3053,11 @@ export class ServiceStatefulRuntime {
3095
3053
  };
3096
3054
  }
3097
3055
  if (!this.acceptsLocalMissingInstancePlacement()) {
3098
- if (current.kind === 'ready' && current.route.targetNodeRid !== this.nodeRid) {
3099
- throw new ServiceInstanceActivationRedirectError(current.route);
3100
- }
3101
3056
  // A draining or retiring node must not create a successor generation
3102
3057
  // from a stale client route while relocation is publishing its target.
3103
3058
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3104
3059
  }
3105
3060
  if (current.kind === 'ready') {
3106
- if (current.route.targetNodeRid !== this.nodeRid) {
3107
- throw new ServiceInstanceActivationRedirectError(current.route);
3108
- }
3109
3061
  if (current.route.targetNodeGeneration !== this.nodeGeneration) {
3110
3062
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3111
3063
  }
@@ -3148,16 +3100,9 @@ export class ServiceStatefulRuntime {
3148
3100
  await lifecycle.discard(target);
3149
3101
  materialized = lifecycle.isMaterialized(target);
3150
3102
  }
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.
3103
+ // The local projection does not decide reservation ownership. Preserve
3104
+ // the Creating fence for Reserve, which refuses a competing attempt.
3105
+ // Keep fencing an orphan that disposal did not remove.
3161
3106
  if (
3162
3107
  (!joinsCreating && current.kind !== 'missing') ||
3163
3108
  (!joinsCreating && materialized !== false && !materializing)
@@ -3186,9 +3131,6 @@ export class ServiceStatefulRuntime {
3186
3131
  } finally {
3187
3132
  deadline.close();
3188
3133
  }
3189
- if (reserved.kind === 'ready') {
3190
- throw new ServiceInstanceActivationRedirectError(reserved.route);
3191
- }
3192
3134
  if (this.closed || record.deadlineUnixMs < BigInt(Date.now())) {
3193
3135
  await authority.abort(target, reserved.reservation);
3194
3136
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
@@ -3224,7 +3166,7 @@ export class ServiceStatefulRuntime {
3224
3166
  if (committed.kind === 'lost') {
3225
3167
  if (activation.created) this.registry.closeSpot(activation.spot.ref);
3226
3168
  await this.instanceApplicationLifecycle?.discard(target);
3227
- throw new ServiceInstanceActivationRedirectError(committed.route);
3169
+ throw activationRefused();
3228
3170
  }
3229
3171
  if (!routeMatchesLocal(committed.route, activation.spot, this.nodeRid, this.nodeGeneration)) {
3230
3172
  if (activation.created) this.registry.closeSpot(activation.spot.ref);
@@ -3269,7 +3211,7 @@ export class ServiceStatefulRuntime {
3269
3211
  const confirmed = await authority.read(target);
3270
3212
  if (confirmed.kind !== 'ready' || !sameInstanceRoute(confirmed.route, route)) {
3271
3213
  if (confirmed.kind === 'ready' && confirmed.route.targetNodeRid !== this.nodeRid) {
3272
- throw new ServiceInstanceActivationRedirectError(confirmed.route);
3214
+ throw activationRefused();
3273
3215
  }
3274
3216
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3275
3217
  }
@@ -3404,7 +3346,7 @@ export class ServiceStatefulRuntime {
3404
3346
  kindData: control,
3405
3347
  ...(canonicalApplicationPayload === undefined ? {} : { canonicalApplicationPayload }),
3406
3348
  reply: (terminalResult, failureCode, replyPayload, tail) => {
3407
- return this.replyPort(ingress, record.correlation, 'actorJoin')(
3349
+ return this.replyPort(ingress, record.correlation)(
3408
3350
  terminalResult,
3409
3351
  failureCode,
3410
3352
  replyPayload,
@@ -4037,14 +3979,12 @@ export class ServiceStatefulRuntime {
4037
3979
 
4038
3980
  private replyPort(
4039
3981
  ingress: RawServiceIngressRecord,
4040
- correlation: bigint | undefined,
4041
- operationKind: 'spotRequest' | 'actorRequest' | 'actorJoin' | 'instanceSpotRequest'
3982
+ correlation: bigint | undefined
4042
3983
  ): NonNullable<ServiceStatefulMailboxData['reply']> {
4043
3984
  if (correlation === undefined)
4044
3985
  throw new ServiceWireProtocolError('Request correlation is missing.');
4045
3986
  return (terminalResult, failureCode, payload, tail) => {
4046
3987
  this.replyWire(ingress, correlation, terminalResult, failureCode, payload, tail);
4047
- void operationKind;
4048
3988
  return true;
4049
3989
  };
4050
3990
  }
@@ -4057,10 +3997,11 @@ export class ServiceStatefulRuntime {
4057
3997
  payload?: ServiceApplicationPayload,
4058
3998
  tail?: ServiceStatefulReplyTail
4059
3999
  ): void {
4060
- this.raw.replyService(ingress, [
4000
+ const parts = [
4061
4001
  encodeStatefulReply(correlation, terminalResult, failureCode, tail),
4062
4002
  ...(payload === undefined ? [] : [encodeApplicationPayload(payload)])
4063
- ]);
4003
+ ];
4004
+ this.raw.replyService(ingress, parts);
4064
4005
  }
4065
4006
 
4066
4007
  private handleUserSpotOperation(
@@ -4763,23 +4704,17 @@ export class ServiceStatefulRuntime {
4763
4704
  return;
4764
4705
  }
4765
4706
  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
- }
4707
+ const hasMetadata = (ingress.flags & M6bServiceWireFlag.metadata) !== 0;
4708
+ const metadataFrame = hasMetadata
4709
+ ? validateServiceMetadataFrame(ingress.parts[1]!)
4710
+ : undefined;
4711
+ this.enqueueInstanceSpot(
4712
+ ingress,
4713
+ decoded,
4714
+ ingress.parts[hasMetadata ? 2 : 1]!,
4715
+ metadataFrame,
4716
+ localReply
4717
+ );
4783
4718
  return;
4784
4719
  }
4785
4720
  this.operations.reply(pending.id, {
@@ -4790,7 +4725,7 @@ export class ServiceStatefulRuntime {
4790
4725
  }
4791
4726
 
4792
4727
  private completeRemoteReply(
4793
- pending: ServiceStatefulPendingOperation,
4728
+ pending: Pick<ServiceStatefulPendingOperation, 'id'>,
4794
4729
  targetNodeRid: string,
4795
4730
  operationKind:
4796
4731
  | 'spotRequest'
@@ -5038,9 +4973,7 @@ export class ServiceStatefulRuntime {
5038
4973
  ...(wire.correlation === undefined ? {} : { correlation: wire.correlation }),
5039
4974
  sourceSpotId: wire.sourceSpotId,
5040
4975
  targetSpot: wire.target.spot,
5041
- ...(wire.kind === 'spotRequest'
5042
- ? { reply: this.replyPort(ingress, wire.correlation, 'spotRequest') }
5043
- : {})
4976
+ ...(wire.kind === 'spotRequest' ? { reply: this.replyPort(ingress, wire.correlation) } : {})
5044
4977
  }
5045
4978
  );
5046
4979
  }
@@ -5523,6 +5456,13 @@ function actorLocation(actor: ServiceActorState): ActorLocation {
5523
5456
  };
5524
5457
  }
5525
5458
 
5459
+ function activationRefused(): ZLinkFrameworkException {
5460
+ return new ZLinkFrameworkException(
5461
+ ZLinkFrameworkErrorKind.Unavailable,
5462
+ 'Instance cold activation authority was refused.'
5463
+ );
5464
+ }
5465
+
5526
5466
  function failure(error: unknown): ServiceStatefulResult {
5527
5467
  if (error instanceof ServiceStaleGenerationError) {
5528
5468
  return internalFrameworkWireReply(ZLinkFrameworkInternalErrorKind.ActorLocationStale);
@@ -5895,18 +5835,6 @@ function instanceOperationKey(
5895
5835
  );
5896
5836
  }
5897
5837
 
5898
- function instanceActivationKey(
5899
- record: Extract<
5900
- ServiceStatefulWireRecord,
5901
- { readonly kind: 'instanceSpot'; readonly activation: 'missing' }
5902
- >
5903
- ): string {
5904
- return (
5905
- `${record.target.targetNodeRid}\0${record.target.targetNodeGeneration}\0` +
5906
- `${record.target.targetSpotId}\0${record.target.stableType}`
5907
- );
5908
- }
5909
-
5910
5838
  function subscriptionKey(channelName: string, topicFilter: string): string {
5911
5839
  if (channelName.length === 0 || topicFilter.length === 0) {
5912
5840
  throw new TypeError('Channel name and topic filter must be non-empty.');