@zlink-systems/framework 0.31.0 → 0.32.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 (171) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Actors/ZLinkActor.d.ts +2 -0
  3. package/dist/contracts/Actors/ZLinkSessionBindingIdentity.d.ts +6 -0
  4. package/dist/contracts/Actors/ZLinkSessionBindingIdentity.js +2 -0
  5. package/dist/contracts/Actors/index.d.ts +1 -0
  6. package/dist/contracts/Actors/index.js +1 -0
  7. package/dist/contracts/Common/payload-slab.d.ts +4 -0
  8. package/dist/contracts/Common/payload-slab.js +30 -0
  9. package/dist/contracts/Handlers/Contexts.d.ts +1 -0
  10. package/dist/internal.d.ts +9 -0
  11. package/dist/internal.js +23 -1
  12. package/dist/runtime/actors/actor-client.d.ts +1 -1
  13. package/dist/runtime/actors/actor-client.js +32 -23
  14. package/dist/runtime/actors/actor-mailbox.d.ts +1 -1
  15. package/dist/runtime/actors/actor-runtime-state.d.ts +1 -1
  16. package/dist/runtime/actors/spot-actor-dispatch.d.ts +4 -2
  17. package/dist/runtime/actors/spot-actor-dispatch.js +2 -1
  18. package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +1 -0
  19. package/dist/runtime/application-jobs/application-ingress-record-owner.js +3 -0
  20. package/dist/runtime/backend/contracts/index.d.ts +2 -0
  21. package/dist/runtime/backend/mesh-completion-table.d.ts +1 -0
  22. package/dist/runtime/backend/mesh-completion-table.js +1 -0
  23. package/dist/runtime/backend/node/node-mesh-backend-adapter.js +5 -1
  24. package/dist/runtime/backend/node/node-raw-binding-port.d.ts +7 -2
  25. package/dist/runtime/backend/node/node-raw-binding-port.js +17 -11
  26. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +2 -2
  27. package/dist/runtime/backend/node/node-raw-mesh-backend.js +15 -8
  28. package/dist/runtime/channels/channel-dispatch-pipeline.d.ts +7 -6
  29. package/dist/runtime/channels/channel-dispatch-pipeline.js +1 -0
  30. package/dist/runtime/channels/channel-dispatchers.d.ts +7 -3
  31. package/dist/runtime/channels/channel-dispatchers.js +27 -19
  32. package/dist/runtime/channels/channel-envelope.js +543 -23
  33. package/dist/runtime/channels/channel-receive-loops.d.ts +9 -6
  34. package/dist/runtime/channels/channel-receive-loops.js +59 -18
  35. package/dist/runtime/channels/channel-runtime-lifecycle.d.ts +22 -3
  36. package/dist/runtime/channels/channel-socket-registry.d.ts +22 -4
  37. package/dist/runtime/diagnostics/runtime-metrics.d.ts +1 -1
  38. package/dist/runtime/diagnostics/runtime-metrics.js +2 -1
  39. package/dist/runtime/execution/serial-execution-queue.d.ts +26 -13
  40. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +27 -11
  41. package/dist/runtime/foundation/raw-service-mesh-runtime.js +29 -25
  42. package/dist/runtime/foundation/service-liveness-registry.d.ts +1 -1
  43. package/dist/runtime/foundation/service-mailbox.d.ts +3 -1
  44. package/dist/runtime/foundation/service-mailbox.js +4 -1
  45. package/dist/runtime/foundation/service-runtime-contracts.d.ts +3 -1
  46. package/dist/runtime/foundation/service-stateful-registry.d.ts +2 -0
  47. package/dist/runtime/foundation/service-stateful-registry.js +28 -12
  48. package/dist/runtime/foundation/service-stateful-runtime.d.ts +51 -19
  49. package/dist/runtime/foundation/service-stateful-runtime.js +54 -5
  50. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +3 -0
  51. package/dist/runtime/foundation/service-stateful-wire-codec.js +15 -0
  52. package/dist/runtime/foundation/service-wire-constants.generated.d.ts +1 -0
  53. package/dist/runtime/foundation/service-wire-constants.generated.js +12 -0
  54. package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +2 -0
  55. package/dist/runtime/foundation/service-wire-m6a-codec.js +17 -7
  56. package/dist/runtime/host/actor-placement-coordinator.d.ts +1 -1
  57. package/dist/runtime/host/actor-transfer-runtime.d.ts +3 -3
  58. package/dist/runtime/host/application-job-queue.d.ts +1 -1
  59. package/dist/runtime/host/index.d.ts +89 -36
  60. package/dist/runtime/host/index.js +101 -7
  61. package/dist/runtime/host/location-runtime-owner.d.ts +2 -2
  62. package/dist/runtime/host/service-relocation-host-runtime.d.ts +181 -44
  63. package/dist/runtime/host/service-relocation-host-runtime.js +47 -13
  64. package/dist/runtime/host/spot-address-transport.js +50 -42
  65. package/dist/runtime/locations/auto-connect-loop.d.ts +0 -1
  66. package/dist/runtime/locations/auto-connect-loop.js +25 -19
  67. package/dist/runtime/locations/auto-connect-planner.js +2 -5
  68. package/dist/runtime/locations/auto-connect-reconciler.d.ts +1 -0
  69. package/dist/runtime/locations/auto-connect-reconciler.js +35 -23
  70. package/dist/runtime/locations/in-memory-location-store.d.ts +0 -2
  71. package/dist/runtime/locations/in-memory-location-store.js +1 -38
  72. package/dist/runtime/locations/location-store-repository.d.ts +0 -1
  73. package/dist/runtime/locations/location-store-repository.js +13 -115
  74. package/dist/runtime/locations/runtime.d.ts +3 -3
  75. package/dist/runtime/locations/runtime.js +12 -16
  76. package/dist/runtime/messaging/creation-payload-codec.js +2 -2
  77. package/dist/runtime/protocol/service_wire_codec.generated.d.ts +4 -0
  78. package/dist/runtime/protocol/service_wire_codec.generated.js +17 -7
  79. package/dist/runtime/spots/index.d.ts +20 -5
  80. package/dist/runtime/spots/index.js +14 -0
  81. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +3 -3
  82. package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +1 -0
  83. package/dist/runtime/spots/spot-actor-packet-dispatch.js +13 -7
  84. package/dist/runtime/spots/spot-actor-packet-drain.d.ts +1 -0
  85. package/dist/runtime/spots/spot-actor-packet-drain.js +12 -5
  86. package/dist/runtime/spots/spot-entry-activation.d.ts +2 -1
  87. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +8 -6
  88. package/dist/runtime/spots/spot-node-runtime-manager.js +2 -0
  89. package/dist/runtime/spots/spot-route-packet-dispatch.js +7 -4
  90. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.js +3 -1
  91. package/dist/runtime/spots/spot-serial-executor.d.ts +2 -1
  92. package/dist/runtime/spots/spot-subscription-dispatch.js +3 -1
  93. package/dist/runtime/spots/spot-timer.d.ts +1 -1
  94. package/dist/runtime/streams/actor-session-binding-registry.d.ts +9 -3
  95. package/dist/runtime/streams/actor-session-binding-registry.js +17 -0
  96. package/dist/runtime/streams/index.d.ts +3 -3
  97. package/dist/runtime/streams/index.js +22 -2
  98. package/dist/runtime/streams/managed-stream.js +2 -1
  99. package/dist/runtime/streams/session-calls.d.ts +1 -1
  100. package/dist/runtime/streams/session-calls.js +3 -9
  101. package/dist/runtime/streams/session-context.d.ts +2 -2
  102. package/dist/runtime/streams/session-context.js +2 -2
  103. package/dist/runtime/streams/stream-session-runtime.d.ts +1 -0
  104. package/dist/runtime/streams/stream-session-runtime.js +37 -12
  105. package/package.json +3 -3
  106. package/src/contracts/Actors/ZLinkActor.ts +6 -0
  107. package/src/contracts/Actors/ZLinkSessionBindingIdentity.ts +7 -0
  108. package/src/contracts/Actors/index.ts +1 -0
  109. package/src/contracts/Common/payload-slab.ts +29 -0
  110. package/src/contracts/Handlers/Contexts.ts +1 -0
  111. package/src/internal.ts +17 -0
  112. package/src/runtime/actors/actor-client.ts +48 -23
  113. package/src/runtime/actors/actor-mailbox.ts +1 -1
  114. package/src/runtime/actors/actor-runtime-state.ts +1 -1
  115. package/src/runtime/actors/spot-actor-dispatch.ts +4 -3
  116. package/src/runtime/application-jobs/application-ingress-record-owner.ts +4 -0
  117. package/src/runtime/backend/contracts/index.ts +2 -0
  118. package/src/runtime/backend/mesh-completion-table.ts +2 -0
  119. package/src/runtime/backend/node/node-mesh-backend-adapter.ts +5 -1
  120. package/src/runtime/backend/node/node-raw-binding-port.ts +20 -7
  121. package/src/runtime/backend/node/node-raw-mesh-backend.ts +18 -10
  122. package/src/runtime/channels/channel-dispatch-pipeline.ts +6 -5
  123. package/src/runtime/channels/channel-dispatchers.ts +39 -19
  124. package/src/runtime/channels/channel-envelope.ts +547 -20
  125. package/src/runtime/channels/channel-receive-loops.ts +93 -36
  126. package/src/runtime/channels/channel-runtime-lifecycle.ts +2 -2
  127. package/src/runtime/channels/channel-socket-registry.ts +3 -3
  128. package/src/runtime/diagnostics/runtime-metrics.ts +3 -1
  129. package/src/runtime/execution/serial-execution-queue.ts +13 -13
  130. package/src/runtime/foundation/raw-service-mesh-runtime.ts +40 -39
  131. package/src/runtime/foundation/service-liveness-registry.ts +1 -1
  132. package/src/runtime/foundation/service-mailbox.ts +6 -1
  133. package/src/runtime/foundation/service-runtime-contracts.ts +7 -1
  134. package/src/runtime/foundation/service-stateful-registry.ts +39 -14
  135. package/src/runtime/foundation/service-stateful-runtime.ts +87 -21
  136. package/src/runtime/foundation/service-stateful-wire-codec.ts +18 -0
  137. package/src/runtime/foundation/service-wire-constants.generated.ts +10 -0
  138. package/src/runtime/foundation/service-wire-m6a-codec.ts +29 -7
  139. package/src/runtime/host/actor-placement-coordinator.ts +1 -1
  140. package/src/runtime/host/actor-transfer-runtime.ts +3 -3
  141. package/src/runtime/host/application-job-queue.ts +1 -1
  142. package/src/runtime/host/index.ts +183 -44
  143. package/src/runtime/host/location-runtime-owner.ts +2 -2
  144. package/src/runtime/host/service-relocation-host-runtime.ts +101 -65
  145. package/src/runtime/host/spot-address-transport.ts +67 -61
  146. package/src/runtime/locations/auto-connect-loop.ts +24 -16
  147. package/src/runtime/locations/auto-connect-planner.ts +2 -5
  148. package/src/runtime/locations/auto-connect-reconciler.ts +37 -24
  149. package/src/runtime/locations/in-memory-location-store.ts +1 -74
  150. package/src/runtime/locations/location-store-repository.ts +32 -153
  151. package/src/runtime/locations/runtime.ts +16 -28
  152. package/src/runtime/messaging/creation-payload-codec.ts +3 -3
  153. package/src/runtime/protocol/service_wire_codec.generated.ts +18 -8
  154. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
  155. package/src/runtime/spots/index.ts +28 -4
  156. package/src/runtime/spots/spot-actor-admission-coordinator.ts +1 -1
  157. package/src/runtime/spots/spot-actor-packet-dispatch.ts +16 -6
  158. package/src/runtime/spots/spot-actor-packet-drain.ts +21 -6
  159. package/src/runtime/spots/spot-entry-activation.ts +1 -1
  160. package/src/runtime/spots/spot-node-runtime-manager.ts +8 -6
  161. package/src/runtime/spots/spot-route-packet-dispatch.ts +8 -4
  162. package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +3 -1
  163. package/src/runtime/spots/spot-serial-executor.ts +1 -1
  164. package/src/runtime/spots/spot-subscription-dispatch.ts +3 -1
  165. package/src/runtime/spots/spot-timer.ts +1 -1
  166. package/src/runtime/streams/actor-session-binding-registry.ts +30 -3
  167. package/src/runtime/streams/index.ts +30 -4
  168. package/src/runtime/streams/managed-stream.ts +2 -1
  169. package/src/runtime/streams/session-calls.ts +9 -14
  170. package/src/runtime/streams/session-context.ts +6 -11
  171. package/src/runtime/streams/stream-session-runtime.ts +49 -20
@@ -62,6 +62,11 @@ export interface ServiceSessionBinding {
62
62
  readonly membershipEpoch: bigint;
63
63
  }
64
64
 
65
+ interface ServiceSessionBindingEntry {
66
+ readonly binding: ServiceSessionBinding;
67
+ lastAcceptedSequence: bigint;
68
+ }
69
+
65
70
  export interface ServiceObjectReservation {
66
71
  readonly id: bigint;
67
72
  readonly objectKind: 'actor' | 'userSpot' | 'instanceSpot';
@@ -112,7 +117,7 @@ export class ServiceStatefulRegistry {
112
117
  >();
113
118
  private readonly actors = new Map<string, ServiceActorState>();
114
119
  private readonly actorTypes = new Map<string, string>();
115
- private readonly bindings = new Map<string, ServiceSessionBinding>();
120
+ private readonly bindings = new Map<string, ServiceSessionBindingEntry>();
116
121
  private readonly reservations = new Map<string, ServiceObjectReservation>();
117
122
  private readonly spotGenerations = new Map<string, bigint>();
118
123
  private readonly actorGenerations = new Map<string, bigint>();
@@ -408,7 +413,7 @@ export class ServiceStatefulRegistry {
408
413
  );
409
414
  requireText(binding.sessionOwnerNodeRid, 'sessionOwnerNodeRid');
410
415
  requirePositive(binding.bindingGeneration, 'bindingGeneration');
411
- const current = this.bindings.get(actorKey(binding.actor));
416
+ const current = this.bindings.get(actorKey(binding.actor))?.binding;
412
417
  if (
413
418
  current !== undefined &&
414
419
  current.sessionOwnerNodeRid === binding.sessionOwnerNodeRid &&
@@ -423,7 +428,7 @@ export class ServiceStatefulRegistry {
423
428
  throw new ServiceStaleGenerationError('binding', binding.actor.actorId);
424
429
  }
425
430
  const accepted = Object.freeze({ ...binding });
426
- this.bindings.set(actorKey(binding.actor), accepted);
431
+ this.bindings.set(actorKey(binding.actor), { binding: accepted, lastAcceptedSequence: 0n });
427
432
  return accepted;
428
433
  }
429
434
 
@@ -432,10 +437,29 @@ export class ServiceStatefulRegistry {
432
437
  }
433
438
 
434
439
  private bindingCore(actor: ServiceActorRef): ServiceSessionBinding | undefined {
435
- const binding = this.bindings.get(actorKey(actor));
440
+ const binding = this.bindings.get(actorKey(actor))?.binding;
436
441
  return binding?.actor.generation === actor.generation ? binding : undefined;
437
442
  }
438
443
 
444
+ /** The Actor owner admits each relayed Session sequence once per binding. */
445
+ acceptBoundSessionSequence(
446
+ actor: ServiceActorRef,
447
+ expectedBindingGeneration: bigint,
448
+ sequence: bigint
449
+ ): boolean {
450
+ const entry = this.bindings.get(actorKey(actor));
451
+ if (
452
+ entry === undefined ||
453
+ entry.binding.actor.generation !== actor.generation ||
454
+ entry.binding.bindingGeneration !== expectedBindingGeneration ||
455
+ sequence <= entry.lastAcceptedSequence
456
+ ) {
457
+ return false;
458
+ }
459
+ entry.lastAcceptedSequence = sequence;
460
+ return true;
461
+ }
462
+
439
463
  unbindSession(
440
464
  actor: ServiceActorRef,
441
465
  expectedBindingGeneration: bigint,
@@ -443,8 +467,9 @@ export class ServiceStatefulRegistry {
443
467
  expectedSessionOwnerNodeRid?: string
444
468
  ): boolean {
445
469
  const key = actorKey(actor);
446
- const binding = this.bindings.get(key);
447
- if (binding === undefined) return false;
470
+ const entry = this.bindings.get(key);
471
+ if (entry === undefined) return false;
472
+ const binding = entry.binding;
448
473
  if (
449
474
  binding.bindingGeneration !== expectedBindingGeneration ||
450
475
  (expectedSessionRid !== undefined &&
@@ -577,16 +602,16 @@ export class ServiceStatefulRegistry {
577
602
 
578
603
  private updateBindingMembership(actor: ServiceActorState): void {
579
604
  const key = actorKey(actor.ref);
580
- const binding = this.bindings.get(key);
581
- if (binding === undefined) return;
582
- this.bindings.set(
583
- key,
584
- Object.freeze({
585
- ...binding,
605
+ const entry = this.bindings.get(key);
606
+ if (entry === undefined) return;
607
+ this.bindings.set(key, {
608
+ binding: Object.freeze({
609
+ ...entry.binding,
586
610
  actor: actor.ref,
587
611
  membershipEpoch: actor.membershipEpoch
588
- })
589
- );
612
+ }),
613
+ lastAcceptedSequence: entry.lastAcceptedSequence
614
+ });
590
615
  }
591
616
  }
592
617
 
@@ -123,12 +123,14 @@ import {
123
123
  SERVICE_WIRE_MAJOR,
124
124
  SERVICE_FRAMEWORK_MULTIPART_CONTENT_TYPE
125
125
  } from './service-wire-constants.generated';
126
+ import { copyPayloadBytes } from '../../contracts/Common/payload-slab';
126
127
  import {
127
128
  ServiceWireProtocolError,
128
129
  decodeApplicationPayload,
129
130
  decodeApplicationPayloadView,
130
131
  decodeMultipartApplicationParts,
131
132
  encodeApplicationPayload,
133
+ encodeOwnedApplicationPayload,
132
134
  type ServiceApplicationPayload
133
135
  } from './service-wire-m6a-codec';
134
136
 
@@ -167,6 +169,7 @@ interface ServiceSpotMessageFollowState {
167
169
  export interface ServiceStatefulResult {
168
170
  readonly terminalResult: number;
169
171
  readonly failureCode: number;
172
+ readonly failureMessage?: string;
170
173
  readonly payload?: ServiceApplicationPayload;
171
174
  readonly kindData?: ReceiveKindData;
172
175
  readonly applicationMetadata?: Buffer;
@@ -476,7 +479,7 @@ export class ServiceStatefulRuntime {
476
479
 
477
480
  readonly registry: ServiceStatefulRegistry;
478
481
 
479
- private readonly operations: ServiceTerminalOperationRegistry<ServiceStatefulResult, bigint>;
482
+ readonly operations: ServiceTerminalOperationRegistry<ServiceStatefulResult, bigint>;
480
483
  private readonly sessionDeliveries = new Map<string, ServiceSessionDelivery>();
481
484
  /**
482
485
  * A local Actor owner can replace its own session binding before the
@@ -520,7 +523,7 @@ export class ServiceStatefulRuntime {
520
523
  private dispatchErrors?: ZLinkDispatchErrorReporter;
521
524
  private sessionOwnerDescriptors?: ServiceSessionOwnerDescriptors;
522
525
  private dispatchErrorMeshName?: string;
523
- private readonly admittedUserSpotOperations = new Map<
526
+ readonly admittedUserSpotOperations = new Map<
524
527
  string,
525
528
  {
526
529
  readonly request: string;
@@ -2437,7 +2440,7 @@ export class ServiceStatefulRuntime {
2437
2440
  ? record.parts.length >= 2
2438
2441
  ? record.parts[record.parts.length - 1]
2439
2442
  : undefined
2440
- : encodeApplicationPayload(canonicalPayload);
2443
+ : encodeOwnedApplicationPayload(canonicalPayload);
2441
2444
  try {
2442
2445
  return await this.handleIngress(
2443
2446
  record,
@@ -2503,7 +2506,7 @@ export class ServiceStatefulRuntime {
2503
2506
  }
2504
2507
  }
2505
2508
 
2506
- private async handleIngress(
2509
+ async handleIngress(
2507
2510
  ingress: RawServiceIngressRecord,
2508
2511
  record: ServiceStatefulWireRecord,
2509
2512
  payloadFrame: Buffer | undefined,
@@ -2596,6 +2599,62 @@ export class ServiceStatefulRuntime {
2596
2599
  ) {
2597
2600
  throw new ServiceStaleGenerationError('binding', record.target.actor.actorId);
2598
2601
  }
2602
+ if (
2603
+ !this.registry.acceptBoundSessionSequence(
2604
+ record.target.actor,
2605
+ record.boundSession.bindingGeneration,
2606
+ record.boundSession.sequence
2607
+ )
2608
+ ) {
2609
+ const error = new ZLinkFrameworkException(
2610
+ ZLinkFrameworkErrorKind.InvalidOperation,
2611
+ 'Bound Session relay sequence does not increase.'
2612
+ );
2613
+ const reporter = this.dispatchErrors;
2614
+ if (record.kind === 'actorRequest') {
2615
+ if (reporter?.captureEnabled() === true) {
2616
+ reporter.report({
2617
+ surface: ZLinkDispatchErrorSurface.StreamSession,
2618
+ messageKind: ZLinkDispatchMessageKind.Request,
2619
+ reason: ZLinkDispatchErrorReason.StaleTarget,
2620
+ action: ZLinkDispatchErrorAction.ReplyError,
2621
+ meshName: this.dispatchErrorMeshName,
2622
+ sourceRid: ingress.sourceRoutingId,
2623
+ actorId: record.target.actor.actorId,
2624
+ correlationId: record.correlation?.toString(),
2625
+ error
2626
+ });
2627
+ }
2628
+ const result = failure(error);
2629
+ this.replyWire(
2630
+ ingress,
2631
+ record.correlation!,
2632
+ result.terminalResult,
2633
+ result.failureCode
2634
+ );
2635
+ return 'infrastructure';
2636
+ }
2637
+ const tracePoint = flowIfEnabled(reporter?.flow, ZLinkMessageFlowOutcome.Dropped);
2638
+ if (tracePoint !== undefined) {
2639
+ tracePoint.trace({
2640
+ outcome: ZLinkMessageFlowOutcome.Dropped,
2641
+ surface: ZLinkDispatchErrorSurface.StreamSession,
2642
+ messageKind: ZLinkDispatchMessageKind.Send,
2643
+ packetName: decodeApplicationPayloadView(payloadFrame!).packetName,
2644
+ ...(this.dispatchErrorMeshName === undefined
2645
+ ? {}
2646
+ : { meshName: this.dispatchErrorMeshName }),
2647
+ ...(record.correlation === undefined
2648
+ ? {}
2649
+ : { correlationId: record.correlation.toString() }),
2650
+ sourceRid: ingress.sourceRoutingId,
2651
+ actorId: record.target.actor.actorId,
2652
+ streamSessionId: record.boundSession.sessionRid,
2653
+ errorReason: ZLinkDispatchErrorReason.StaleTarget
2654
+ });
2655
+ }
2656
+ return 'application';
2657
+ }
2599
2658
  }
2600
2659
  return this.enqueueApplicationFrame(
2601
2660
  ingress,
@@ -2755,7 +2814,7 @@ export class ServiceStatefulRuntime {
2755
2814
  return !lifecycle.isMaterialized(applicationTarget);
2756
2815
  }
2757
2816
 
2758
- private enqueueActivatedInstanceSpot(
2817
+ enqueueActivatedInstanceSpot(
2759
2818
  ingress: RawServiceIngressRecord,
2760
2819
  record: Extract<ServiceStatefulWireRecord, { readonly kind: 'instanceSpot' }>,
2761
2820
  payloadFrame: Buffer,
@@ -2836,7 +2895,7 @@ export class ServiceStatefulRuntime {
2836
2895
  return result;
2837
2896
  }
2838
2897
 
2839
- private async continueMissingInstanceActivation(
2898
+ async continueMissingInstanceActivation(
2840
2899
  ingress: RawServiceIngressRecord,
2841
2900
  record: Extract<
2842
2901
  ServiceStatefulWireRecord,
@@ -2935,7 +2994,7 @@ export class ServiceStatefulRuntime {
2935
2994
  * record only of a message whose handler turn started before it; a message queued
2936
2995
  * behind that Close is not counted, so it cannot hold the Close it follows.
2937
2996
  */
2938
- private activationTerminalCompletion(
2997
+ activationTerminalCompletion(
2939
2998
  target: ServiceInstanceActivationTarget,
2940
2999
  route: ServiceInstanceRouteFence
2941
3000
  ): {
@@ -2982,7 +3041,7 @@ export class ServiceStatefulRuntime {
2982
3041
  return { onTerminalCompletion, onHandlerTurnStarted };
2983
3042
  }
2984
3043
 
2985
- private instanceApplicationTerminalCompletion(
3044
+ instanceApplicationTerminalCompletion(
2986
3045
  target: ServiceInstanceApplicationTarget
2987
3046
  ): (() => Promise<void>) | undefined {
2988
3047
  const lifecycle = this.instanceApplicationLifecycle;
@@ -3052,7 +3111,7 @@ export class ServiceStatefulRuntime {
3052
3111
  for (const waiter of waiters) waiter();
3053
3112
  }
3054
3113
 
3055
- private finishMissingInstanceActivationFailure(
3114
+ finishMissingInstanceActivationFailure(
3056
3115
  ingress: RawServiceIngressRecord,
3057
3116
  record: Extract<
3058
3117
  ServiceStatefulWireRecord,
@@ -3220,7 +3279,7 @@ export class ServiceStatefulRuntime {
3220
3279
  return { intent, route: current };
3221
3280
  }
3222
3281
 
3223
- private validateInstanceIngress(
3282
+ validateInstanceIngress(
3224
3283
  ingress: RawServiceIngressRecord,
3225
3284
  record: Extract<ServiceStatefulWireRecord, { readonly kind: 'instanceSpot' }>
3226
3285
  ): void {
@@ -3322,7 +3381,7 @@ export class ServiceStatefulRuntime {
3322
3381
  : undefined;
3323
3382
  }
3324
3383
 
3325
- private activateMissingInstanceAsync(
3384
+ activateMissingInstanceAsync(
3326
3385
  record: Extract<
3327
3386
  ServiceStatefulWireRecord,
3328
3387
  { readonly kind: 'instanceSpot'; readonly activation: 'missing' }
@@ -3393,7 +3452,7 @@ export class ServiceStatefulRuntime {
3393
3452
  return operation;
3394
3453
  }
3395
3454
 
3396
- private async runMissingInstanceActivation(
3455
+ async runMissingInstanceActivation(
3397
3456
  record: Extract<
3398
3457
  ServiceStatefulWireRecord,
3399
3458
  { readonly kind: 'instanceSpot'; readonly activation: 'missing' }
@@ -3757,7 +3816,7 @@ export class ServiceStatefulRuntime {
3757
3816
  }
3758
3817
  })
3759
3818
  };
3760
- const applicationFrame = payloadFrame ?? encodeApplicationPayload(emptyPayload());
3819
+ const applicationFrame = payloadFrame ?? encodeOwnedApplicationPayload(emptyPayload());
3761
3820
  return this.enqueueLifecycleFrame(
3762
3821
  ingress,
3763
3822
  `spot:${record.target.spot.spotId}`,
@@ -4259,7 +4318,7 @@ export class ServiceStatefulRuntime {
4259
4318
  );
4260
4319
  }
4261
4320
 
4262
- private enqueueApplicationFrame(
4321
+ enqueueApplicationFrame(
4263
4322
  ingress: RawServiceIngressRecord,
4264
4323
  owner: string,
4265
4324
  payloadFrame: Buffer,
@@ -4343,7 +4402,7 @@ export class ServiceStatefulRuntime {
4343
4402
  channelName,
4344
4403
  topic,
4345
4404
  sourceSpotId,
4346
- encodeApplicationPayload(payload),
4405
+ encodeOwnedApplicationPayload(payload),
4347
4406
  sourceNodeRid,
4348
4407
  applicationJobOwner
4349
4408
  );
@@ -4787,6 +4846,7 @@ export class ServiceStatefulRuntime {
4787
4846
  private async submitOneWay(targetNodeRid: string, parts: readonly Buffer[]): Promise<number> {
4788
4847
  this.requireOpen();
4789
4848
  if (targetNodeRid === this.nodeRid) {
4849
+ parts = copyRetainedServiceFrames(parts);
4790
4850
  const applicationJobOwner = await this.raw.reserveLocalIngress();
4791
4851
  if (captureZLinkExecutionTurn() !== undefined) {
4792
4852
  // The sender is inside a Spot serial turn. Running ingress inline
@@ -4862,6 +4922,7 @@ export class ServiceStatefulRuntime {
4862
4922
  receiver?: import('./operation-registry').OperationObserver<unknown, unknown>
4863
4923
  ): void {
4864
4924
  if (targetNodeRid === this.nodeRid) {
4925
+ parts = copyRetainedServiceFrames(parts);
4865
4926
  const deadlineUnixMs = BigInt(Date.now() + Math.max(0, timeoutMs));
4866
4927
  const capacityStop = new AbortController();
4867
4928
  void pending.promise.then(
@@ -5031,7 +5092,7 @@ export class ServiceStatefulRuntime {
5031
5092
  );
5032
5093
  }
5033
5094
 
5034
- private async submitLocalRequest(
5095
+ async submitLocalRequest(
5035
5096
  ingress: RawServiceIngressRecord,
5036
5097
  pending: ServiceStatefulPendingOperation,
5037
5098
  operationKind:
@@ -5058,7 +5119,7 @@ export class ServiceStatefulRuntime {
5058
5119
  ? ingress.parts.length < 2
5059
5120
  ? undefined
5060
5121
  : ingress.parts[1]
5061
- : encodeApplicationPayload({
5122
+ : encodeOwnedApplicationPayload({
5062
5123
  packetName: canonicalActorJoin.payload.packetName,
5063
5124
  contentType: canonicalActorJoin.payload.contentType,
5064
5125
  payload: Buffer.from(canonicalActorJoin.payload.payload)
@@ -5171,7 +5232,7 @@ export class ServiceStatefulRuntime {
5171
5232
  this.enqueueLifecycleFrame(
5172
5233
  ingress,
5173
5234
  `spot:${decoded.target.spot.spotId}`,
5174
- payloadFrame ?? encodeApplicationPayload(emptyPayload()),
5235
+ payloadFrame ?? encodeOwnedApplicationPayload(emptyPayload()),
5175
5236
  {
5176
5237
  receiveKind: ReceiveKind.SpotControl,
5177
5238
  operationKind: OperationKind.ActorJoin,
@@ -5381,6 +5442,7 @@ export class ServiceStatefulRuntime {
5381
5442
  return {
5382
5443
  terminalResult,
5383
5444
  failureCode,
5445
+ ...(tail?.kind === 'streamBindRejected' ? { failureMessage: tail.rejectionMessage } : {}),
5384
5446
  ...(payload === undefined ? {} : { payload }),
5385
5447
  ...(tail?.kind === 'streamBind'
5386
5448
  ? {
@@ -5394,7 +5456,7 @@ export class ServiceStatefulRuntime {
5394
5456
  };
5395
5457
  }
5396
5458
 
5397
- private validateActorFence(fence: ServiceActorRouteFence): ServiceActorState {
5459
+ validateActorFence(fence: ServiceActorRouteFence): ServiceActorState {
5398
5460
  if (
5399
5461
  fence.actor.nodeRid !== this.nodeRid ||
5400
5462
  fence.targetNodeGeneration !== this.nodeGeneration
@@ -5408,7 +5470,7 @@ export class ServiceStatefulRuntime {
5408
5470
  return actor;
5409
5471
  }
5410
5472
 
5411
- private validateActorMessageFence(fence: ServiceActorRouteFence): ServiceActorState {
5473
+ validateActorMessageFence(fence: ServiceActorRouteFence): ServiceActorState {
5412
5474
  if (
5413
5475
  fence.actor.nodeRid !== this.nodeRid ||
5414
5476
  fence.targetNodeGeneration !== this.nodeGeneration
@@ -5439,7 +5501,7 @@ export class ServiceStatefulRuntime {
5439
5501
  return spot;
5440
5502
  }
5441
5503
 
5442
- private validateDirectSpotFence(fence: ServiceSpotRouteFence): ServiceSpotState {
5504
+ validateDirectSpotFence(fence: ServiceSpotRouteFence): ServiceSpotState {
5443
5505
  if (
5444
5506
  fence.targetNodeRid !== this.nodeRid ||
5445
5507
  fence.targetNodeGeneration !== this.nodeGeneration
@@ -6412,6 +6474,10 @@ function emptyPayload(): ServiceApplicationPayload {
6412
6474
  };
6413
6475
  }
6414
6476
 
6477
+ function copyRetainedServiceFrames(parts: readonly Buffer[]): Buffer[] {
6478
+ return parts.map((part) => copyPayloadBytes(part));
6479
+ }
6480
+
6415
6481
  /**
6416
6482
  * The generated command-28 decoder is the canonical wire authority. Keep
6417
6483
  * the stateful mailbox's internal record shape as an adapter only; it must
@@ -3,6 +3,7 @@ import { decodeBoundSessionCloseCommand } from '../protocol/service_wire_codec.g
3
3
  import { UINT64_MAX, ZlinkStreamContentType } from '@zlink-systems/stream-wire';
4
4
  import { RELOCATION_STATE_CHUNK_DATA_MAX_BYTES } from '../../contracts/Configuration/InternalDefaults';
5
5
  import { isCanonicalWireReplyTerminal } from '../framework-errors-internal';
6
+ import { RequestResult } from '../backend/runtime-values';
6
7
  import {
7
8
  decodeActorRequestCommand,
8
9
  decodeActorSendCommand,
@@ -1153,6 +1154,10 @@ export type ServiceStatefulReplyTail =
1153
1154
  readonly bindingGeneration: bigint;
1154
1155
  readonly authorityOwnerGeneration: bigint;
1155
1156
  }
1157
+ | {
1158
+ readonly kind: 'streamBindRejected';
1159
+ readonly rejectionMessage: string;
1160
+ }
1156
1161
  | {
1157
1162
  readonly kind: 'userSpotCreate';
1158
1163
  readonly createResult: 'existing' | 'created' | 'rejected';
@@ -2149,6 +2154,11 @@ export function decodeStatefulReply(
2149
2154
  ...(actor === undefined ? {} : { actor })
2150
2155
  };
2151
2156
  }
2157
+ } else if (operationKind === 'streamBind' && terminalResult === RequestResult.Rejected) {
2158
+ tail = {
2159
+ kind: 'streamBindRejected',
2160
+ rejectionMessage: reader.text16('rejectionMessage')
2161
+ };
2152
2162
  }
2153
2163
  reader.end();
2154
2164
  return {
@@ -2222,6 +2232,8 @@ function encodeReplyTail(tail: ServiceStatefulReplyTail): Buffer {
2222
2232
  }
2223
2233
  case 'streamBind':
2224
2234
  return concat(u64(tail.bindingGeneration), u64(tail.authorityOwnerGeneration));
2235
+ case 'streamBindRejected':
2236
+ return concat(text16(tail.rejectionMessage, 'rejectionMessage'));
2225
2237
  case 'userSpotCreate':
2226
2238
  return concat(
2227
2239
  Buffer.of((['existing', 'created', 'rejected'] as const).indexOf(tail.createResult) + 1),
@@ -2404,6 +2416,12 @@ function text8(value: string, name: string): Buffer {
2404
2416
  return sized8(value, name);
2405
2417
  }
2406
2418
 
2419
+ function text16(value: string, name: string): Buffer {
2420
+ const bytes = encodeCanonicalServiceWireText(value, name, 0xffff, outOfRange);
2421
+ if (bytes.byteLength === 0) fail(`${name} must not be empty.`);
2422
+ return concat(u16(bytes.byteLength), bytes);
2423
+ }
2424
+
2407
2425
  function optionalRid(value: string | undefined): Buffer {
2408
2426
  return value === undefined ? Buffer.of(0) : rid(value, 'sourceSpotId');
2409
2427
  }
@@ -131,3 +131,13 @@ export const isValidServiceWireTerminalFailure = (
131
131
  }
132
132
  return ServiceWireExactTerminalByFailureCode[failureCode as keyof typeof ServiceWireExactTerminalByFailureCode] === terminal;
133
133
  };
134
+ export function recoverRequestCorrelation(bytes: Uint8Array): bigint | undefined {
135
+ if (bytes.length < 13
136
+ || bytes[0] !== 90 || bytes[1] !== 77
137
+ || bytes[2] !== 1
138
+ || ![17, 19, 22, 25, 26, 27, 28, 38, 47, 48, 49, 54].includes(bytes[3]!)) return undefined;
139
+ let correlation = 0n;
140
+ for (let index = 5; index < 13; ++index)
141
+ correlation = (correlation << 8n) | BigInt(bytes[index]!);
142
+ return correlation === 0n ? undefined : correlation;
143
+ }
@@ -1,4 +1,5 @@
1
1
  import { UINT32_MAX, UINT64_MAX } from '@zlink-systems/stream-wire';
2
+ import { allocatePayloadBytes } from '../../contracts/Common/payload-slab';
2
3
  import { normalizeEndpoint } from '../../contracts/Configuration/EndpointNotation';
3
4
  import {
4
5
  validateDescriptor,
@@ -185,10 +186,23 @@ export function decodeReplyHeader(frame: Uint8Array): ServiceReplyHeader {
185
186
  }
186
187
 
187
188
  export function encodeApplicationPayload(payload: ServiceApplicationPayload): Buffer {
189
+ return encodeApplicationPayloadWith(payload, allocatePayloadBytes);
190
+ }
191
+
192
+ /** Encodes a retained internal application frame with its own backing store. */
193
+ export function encodeOwnedApplicationPayload(payload: ServiceApplicationPayload): Buffer {
194
+ return encodeApplicationPayloadWith(payload, allocateOwnedPayloadBytes);
195
+ }
196
+
197
+ function encodeApplicationPayloadWith(
198
+ payload: ServiceApplicationPayload,
199
+ allocate: (length: number) => Buffer
200
+ ): Buffer {
188
201
  const result = allocateApplicationPayload(
189
202
  payload.packetName,
190
203
  payload.contentType,
191
- payload.payload.byteLength
204
+ payload.payload.byteLength,
205
+ allocate
192
206
  );
193
207
  result.set(payload.payload, result.byteLength - payload.payload.byteLength);
194
208
  return result;
@@ -205,7 +219,12 @@ export function encodeMultipartApplicationPayload(
205
219
  contentType: string
206
220
  ): Buffer {
207
221
  const multipartLength = multipartPayloadLength(parts);
208
- const result = allocateApplicationPayload(packetName, contentType, multipartLength);
222
+ const result = allocateApplicationPayload(
223
+ packetName,
224
+ contentType,
225
+ multipartLength,
226
+ allocatePayloadBytes
227
+ );
209
228
  writeMultipartPayload(result, result.byteLength - multipartLength, parts);
210
229
  return result;
211
230
  }
@@ -255,7 +274,8 @@ function writeMultipartPayload(
255
274
  function allocateApplicationPayload(
256
275
  packetName: string,
257
276
  contentType: string,
258
- payloadLength: number
277
+ payloadLength: number,
278
+ allocate: (length: number) => Buffer
259
279
  ): Buffer {
260
280
  const packetNameLength = canonicalServiceWireTextLength(
261
281
  packetName,
@@ -275,10 +295,8 @@ function allocateApplicationPayload(
275
295
  const prefixLength = 1 + APPLICATION_PAYLOAD_LENGTH_FIELD_BYTES;
276
296
  const bodyLength = frameLength - prefixLength;
277
297
  if (bodyLength > MAX_U32) fail('Application payload body exceeds u32.');
278
- // Both callers write every payload byte after these header fields, so the
279
- // frame needs no zero fill and a small frame comes from the shared pool
280
- // instead of its own backing store.
281
- const result = Buffer.allocUnsafe(frameLength);
298
+ // Both callers write every payload byte after these header fields.
299
+ const result = allocate(frameLength);
282
300
  result[0] = APPLICATION_PAYLOAD_VERSION;
283
301
  result.writeUInt32BE(bodyLength, 1);
284
302
  let offset = writeText8(result, prefixLength, packetName, packetNameLength);
@@ -287,6 +305,10 @@ function allocateApplicationPayload(
287
305
  return result;
288
306
  }
289
307
 
308
+ function allocateOwnedPayloadBytes(length: number): Buffer {
309
+ return Buffer.allocUnsafeSlow(length);
310
+ }
311
+
290
312
  export function decodeApplicationPayload(frame: Uint8Array): ServiceApplicationPayload {
291
313
  const decoded = decodeApplicationPayloadView(frame);
292
314
  return {
@@ -82,7 +82,7 @@ export interface ZLinkActorPlacementCoordinatorOptions {
82
82
  }
83
83
 
84
84
  export class ZLinkActorPlacementCoordinator {
85
- constructor(private readonly options: ZLinkActorPlacementCoordinatorOptions) {}
85
+ constructor(readonly options: ZLinkActorPlacementCoordinatorOptions) {}
86
86
 
87
87
  async create(
88
88
  actorId: string,
@@ -703,7 +703,7 @@ export class ZLinkActorTransferRuntime {
703
703
  );
704
704
  }
705
705
 
706
- private async sealBoundSessionRoute(
706
+ async sealBoundSessionRoute(
707
707
  actor: ZLinkActor,
708
708
  state: ZLinkActorRuntimeState,
709
709
  signal?: AbortSignal,
@@ -814,7 +814,7 @@ export class ZLinkActorTransferRuntime {
814
814
  };
815
815
  }
816
816
 
817
- private async abortBoundSessionRouteSeal(
817
+ async abortBoundSessionRouteSeal(
818
818
  actor: ZLinkActor,
819
819
  state: ZLinkActorRuntimeState,
820
820
  target: ZLinkRemoteBoundSessionTarget
@@ -1336,7 +1336,7 @@ export class ZLinkActorTransferRuntime {
1336
1336
  return actorManager;
1337
1337
  }
1338
1338
 
1339
- private async publishBoundSessionOwnership(
1339
+ async publishBoundSessionOwnership(
1340
1340
  actorId: string,
1341
1341
  actorRef: ZLinkBackendActorRef,
1342
1342
  ownershipGeneration: bigint,
@@ -608,7 +608,7 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
608
608
  }
609
609
  }
610
610
 
611
- private evaluatePressure(): void {
611
+ evaluatePressure(): void {
612
612
  const next =
613
613
  this.pressureStateValue === 'running'
614
614
  ? this.permitsInUse() >= this.pauseCount