@zlink-systems/framework 0.22.0 → 0.24.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 (104) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Dispatch/ZLinkDispatchOptions.d.ts +1 -0
  3. package/dist/contracts/Streams/IZLinkSession.d.ts +2 -1
  4. package/dist/runtime/actors/actor-context.js +4 -4
  5. package/dist/runtime/actors/actor-join-deferred-scope.d.ts +0 -1
  6. package/dist/runtime/actors/actor-join-deferred-scope.js +0 -18
  7. package/dist/runtime/actors/deferred-join-accepted-journal.js +2 -6
  8. package/dist/runtime/backend/contracts/index.d.ts +2 -0
  9. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +4 -0
  10. package/dist/runtime/backend/node/node-raw-mesh-backend.js +24 -8
  11. package/dist/runtime/channels/channel-dispatch-pipeline.js +2 -2
  12. package/dist/runtime/channels/channel-transports.js +3 -9
  13. package/dist/runtime/channels/dispatch-error-reporter.d.ts +1 -0
  14. package/dist/runtime/channels/dispatch-error-reporter.js +6 -38
  15. package/dist/runtime/diagnostics/dispatch-error-details.d.ts +6 -0
  16. package/dist/runtime/diagnostics/dispatch-error-details.js +55 -0
  17. package/dist/runtime/diagnostics/flow-context.d.ts +2 -1
  18. package/dist/runtime/diagnostics/flow-context.js +2 -2
  19. package/dist/runtime/diagnostics/message-flow.js +11 -3
  20. package/dist/runtime/foundation/actor-join-recovery-codec.js +0 -4
  21. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +4 -2
  22. package/dist/runtime/foundation/raw-service-mesh-runtime.js +34 -9
  23. package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +1 -0
  24. package/dist/runtime/foundation/service-stateful-runtime.d.ts +5 -0
  25. package/dist/runtime/foundation/service-stateful-runtime.js +52 -2
  26. package/dist/runtime/foundation/service-topology-registry.d.ts +1 -0
  27. package/dist/runtime/foundation/service-topology-registry.js +12 -4
  28. package/dist/runtime/host/index.js +7 -3
  29. package/dist/runtime/locations/in-memory-location-store.js +5 -0
  30. package/dist/runtime/locations/location-store-repository.js +3 -1
  31. package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +20 -18
  32. package/dist/runtime/spots/index.js +9 -3
  33. package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +2 -1
  34. package/dist/runtime/spots/spot-actor-packet-dispatch.js +6 -1
  35. package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
  36. package/dist/runtime/spots/spot-node-runtime-manager.js +5 -1
  37. package/dist/runtime/spots/spot-route-packet-dispatch.js +1 -1
  38. package/dist/runtime/spots/spot-routed-bound-session-dispatch.js +1 -1
  39. package/dist/runtime/spots/spot-subscription-dispatch.js +1 -1
  40. package/dist/runtime/spots/spot-timer.js +1 -1
  41. package/dist/runtime/streams/actor-session-binding-registry.d.ts +21 -8
  42. package/dist/runtime/streams/actor-session-binding-registry.js +71 -42
  43. package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +1 -0
  44. package/dist/runtime/streams/bound-actor-relay-sender.js +2 -1
  45. package/dist/runtime/streams/bound-session-response-target.js +8 -1
  46. package/dist/runtime/streams/bound-session-service.d.ts +1 -0
  47. package/dist/runtime/streams/bound-session-service.js +6 -3
  48. package/dist/runtime/streams/index.js +8 -1
  49. package/dist/runtime/streams/managed-stream.d.ts +3 -0
  50. package/dist/runtime/streams/managed-stream.js +20 -1
  51. package/dist/runtime/streams/protocol.d.ts +5 -1
  52. package/dist/runtime/streams/protocol.js +41 -4
  53. package/dist/runtime/streams/session-actor-coordinator.js +3 -4
  54. package/dist/runtime/streams/session-context.d.ts +9 -2
  55. package/dist/runtime/streams/session-context.js +20 -4
  56. package/dist/runtime/streams/session-local-actors.d.ts +3 -1
  57. package/dist/runtime/streams/session-local-actors.js +17 -2
  58. package/dist/runtime/streams/stream-frame-factory.d.ts +1 -1
  59. package/dist/runtime/streams/stream-frame-factory.js +3 -2
  60. package/dist/runtime/streams/stream-session-runtime.js +42 -12
  61. package/package.json +3 -3
  62. package/src/contracts/Dispatch/ZLinkDispatchOptions.ts +1 -0
  63. package/src/contracts/Streams/IZLinkSession.ts +2 -1
  64. package/src/runtime/actors/actor-context.ts +4 -4
  65. package/src/runtime/actors/actor-join-deferred-scope.ts +0 -32
  66. package/src/runtime/actors/deferred-join-accepted-journal.ts +2 -6
  67. package/src/runtime/backend/contracts/index.ts +5 -0
  68. package/src/runtime/backend/node/node-raw-mesh-backend.ts +43 -11
  69. package/src/runtime/channels/channel-dispatch-pipeline.ts +4 -2
  70. package/src/runtime/channels/channel-transports.ts +3 -9
  71. package/src/runtime/channels/dispatch-error-reporter.ts +7 -45
  72. package/src/runtime/diagnostics/dispatch-error-details.ts +62 -0
  73. package/src/runtime/diagnostics/flow-context.ts +6 -4
  74. package/src/runtime/diagnostics/message-flow.ts +12 -3
  75. package/src/runtime/foundation/actor-join-recovery-codec.ts +0 -4
  76. package/src/runtime/foundation/raw-service-mesh-runtime.ts +54 -13
  77. package/src/runtime/foundation/service-session-binding-ingress-port.ts +1 -0
  78. package/src/runtime/foundation/service-stateful-runtime.ts +71 -5
  79. package/src/runtime/foundation/service-topology-registry.ts +18 -5
  80. package/src/runtime/host/index.ts +11 -4
  81. package/src/runtime/locations/in-memory-location-store.ts +7 -0
  82. package/src/runtime/locations/location-store-repository.ts +4 -1
  83. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +18 -17
  84. package/src/runtime/spots/index.ts +22 -2
  85. package/src/runtime/spots/spot-actor-packet-dispatch.ts +20 -3
  86. package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
  87. package/src/runtime/spots/spot-node-runtime-manager.ts +5 -1
  88. package/src/runtime/spots/spot-route-packet-dispatch.ts +2 -1
  89. package/src/runtime/spots/spot-routed-bound-session-dispatch.ts +2 -1
  90. package/src/runtime/spots/spot-subscription-dispatch.ts +2 -1
  91. package/src/runtime/spots/spot-timer.ts +6 -1
  92. package/src/runtime/streams/actor-session-binding-registry.ts +150 -63
  93. package/src/runtime/streams/actor-session-binding-runtime-owner.ts +1 -0
  94. package/src/runtime/streams/bound-actor-relay-sender.ts +10 -2
  95. package/src/runtime/streams/bound-session-response-target.ts +10 -1
  96. package/src/runtime/streams/bound-session-service.ts +13 -3
  97. package/src/runtime/streams/index.ts +8 -1
  98. package/src/runtime/streams/managed-stream.ts +34 -12
  99. package/src/runtime/streams/protocol.ts +44 -5
  100. package/src/runtime/streams/session-actor-coordinator.ts +12 -4
  101. package/src/runtime/streams/session-context.ts +31 -4
  102. package/src/runtime/streams/session-local-actors.ts +19 -3
  103. package/src/runtime/streams/stream-frame-factory.ts +4 -2
  104. package/src/runtime/streams/stream-session-runtime.ts +58 -13
@@ -4,13 +4,18 @@ export interface ZLinkActorSessionBindingActor {
4
4
  }
5
5
  export interface ZLinkActorSessionBindingContext<TActor extends ZLinkActorSessionBindingActor> {
6
6
  readonly routingId?: unknown;
7
- bindLocal(actor: TActor, bindingToken: string): void;
7
+ readonly actorSlotControls?: {
8
+ enqueueBound(actorSlot: number, actorId: string): Promise<void>;
9
+ enqueueUnbound(actorSlot: number): Promise<void>;
10
+ };
11
+ bindLocal(actor: TActor, bindingToken: string, actorSlot?: number): void;
8
12
  unbindLocal(actorId: string, bindingToken: string): void;
9
13
  }
10
14
  export interface ZLinkActorSessionRoute<TContext extends ZLinkActorSessionBindingContext<TActor>, TActor extends ZLinkActorSessionBindingActor> {
11
15
  readonly context: TContext;
12
16
  readonly actor: TActor;
13
17
  readonly bindingToken: string;
18
+ readonly actorSlot?: number;
14
19
  readonly sessionIdentity?: string;
15
20
  readonly activeFrames: ZLinkActorSessionActiveFrames;
16
21
  sealId?: string;
@@ -68,22 +73,30 @@ export interface ZLinkActorSessionAuthorityFence {
68
73
  readonly authorityStoreVersion?: string;
69
74
  readonly actorType?: string;
70
75
  }
76
+ export declare enum ZLinkActorSessionBindingTermination {
77
+ BindingEnd = 0,
78
+ PhysicalDisconnect = 1
79
+ }
71
80
  export declare class ZLinkActorSessionBindingRegistry<TContext extends ZLinkActorSessionBindingContext<TActor>, TActor extends ZLinkActorSessionBindingActor> {
72
81
  private readonly terminalRelocationCapacity;
73
82
  private readonly outboundCapacity;
74
83
  private readonly sealWaitTimeoutMs;
84
+ private readonly errorSink?;
75
85
  private readonly lane;
76
86
  private readonly routes;
87
+ private readonly nextActorSlots;
77
88
  private readonly sealWaiters;
78
89
  private readonly activeFrameWaiters;
79
90
  private readonly relocations;
80
91
  private readonly terminalRelocations;
81
- constructor(terminalRelocationCapacity?: number, outboundCapacity?: number, sealWaitTimeoutMs?: number);
82
- bind(context: TContext, actor: TActor, bindingToken: string, authorityFence?: ZLinkActorSessionAuthorityFence, sessionIdentity?: string): Promise<void>;
83
- private bindCore;
84
- replace(previous: ZLinkActorSessionRoute<TContext, TActor>, context: TContext, actor: TActor, bindingToken: string, authorityFence?: ZLinkActorSessionAuthorityFence, sessionIdentity?: string): Promise<void>;
85
- private replaceCore;
86
- replaceAndReleaseSeal(previous: ZLinkActorSessionRoute<TContext, TActor>, context: TContext, actor: TActor, bindingToken: string, sealId: string, authorityFence?: ZLinkActorSessionAuthorityFence, sessionIdentity?: string): Promise<void>;
92
+ constructor(terminalRelocationCapacity?: number, outboundCapacity?: number, sealWaitTimeoutMs?: number, errorSink?: (() => {
93
+ reportRuntimeTaskException(taskName: string, error: unknown): void;
94
+ } | undefined) | undefined);
95
+ bind(context: TContext, actor: TActor, bindingToken: string, authorityFence?: ZLinkActorSessionAuthorityFence, sessionIdentity?: string, commitActor?: () => void): Promise<void>;
96
+ private commitCore;
97
+ private issueActorSlot;
98
+ replace(previous: ZLinkActorSessionRoute<TContext, TActor>, context: TContext, actor: TActor, bindingToken: string, authorityFence?: ZLinkActorSessionAuthorityFence, sessionIdentity?: string, commitActor?: () => void): Promise<void>;
99
+ replaceAndReleaseSeal(previous: ZLinkActorSessionRoute<TContext, TActor>, context: TContext, actor: TActor, bindingToken: string, sealId: string, authorityFence?: ZLinkActorSessionAuthorityFence, sessionIdentity?: string, commitActor?: () => void): Promise<void>;
87
100
  private replaceAndReleaseSealCore;
88
101
  find(actorId: string): Promise<TActor | undefined>;
89
102
  private findCore;
@@ -93,7 +106,7 @@ export declare class ZLinkActorSessionBindingRegistry<TContext extends ZLinkActo
93
106
  readonly context: TContext;
94
107
  } | undefined>;
95
108
  private capturePendingReplyClaimCore;
96
- unbind(actorId: string, context: TContext, bindingToken: string): Promise<void>;
109
+ unbind(actorId: string, context: TContext, bindingToken: string, termination?: ZLinkActorSessionBindingTermination): Promise<void>;
97
110
  private unbindCore;
98
111
  unbindActor(actorId: string): Promise<void>;
99
112
  private unbindActorCore;
@@ -1,18 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ZLinkActorSessionBindingRegistry = void 0;
3
+ exports.ZLinkActorSessionBindingRegistry = exports.ZLinkActorSessionBindingTermination = void 0;
4
4
  const framework_errors_internal_1 = require("../framework-errors-internal");
5
5
  const abort_1 = require("../abort");
6
6
  const node_async_hooks_1 = require("node:async_hooks");
7
7
  const state_lane_1 = require("../execution/state-lane");
8
8
  const routing_id_1 = require("../routing-id");
9
9
  const detachedStateLaneResource = new node_async_hooks_1.AsyncResource('zlink:actor-session-binding-registry');
10
+ var ZLinkActorSessionBindingTermination;
11
+ (function (ZLinkActorSessionBindingTermination) {
12
+ ZLinkActorSessionBindingTermination[ZLinkActorSessionBindingTermination["BindingEnd"] = 0] = "BindingEnd";
13
+ ZLinkActorSessionBindingTermination[ZLinkActorSessionBindingTermination["PhysicalDisconnect"] = 1] = "PhysicalDisconnect";
14
+ })(ZLinkActorSessionBindingTermination || (exports.ZLinkActorSessionBindingTermination = ZLinkActorSessionBindingTermination = {}));
10
15
  class ZLinkActorSessionBindingRegistry {
11
16
  terminalRelocationCapacity;
12
17
  outboundCapacity;
13
18
  sealWaitTimeoutMs;
19
+ errorSink;
14
20
  lane = new state_lane_1.ZLinkStateLane();
15
21
  routes = new Map();
22
+ nextActorSlots = new WeakMap();
16
23
  sealWaiters = new Map();
17
24
  activeFrameWaiters = new Map();
18
25
  relocations = new Map();
@@ -24,10 +31,11 @@ class ZLinkActorSessionBindingRegistry {
24
31
  // (the sealing side) must observe that bound; an unbounded wait here
25
32
  // turns a lost control or a lifecycle interlock into a silent stall
26
33
  // (spec 48:205 — transport/deadline limits apply during relocation).
27
- sealWaitTimeoutMs = 3_000) {
34
+ sealWaitTimeoutMs = 3_000, errorSink) {
28
35
  this.terminalRelocationCapacity = terminalRelocationCapacity;
29
36
  this.outboundCapacity = outboundCapacity;
30
37
  this.sealWaitTimeoutMs = sealWaitTimeoutMs;
38
+ this.errorSink = errorSink;
31
39
  if (!Number.isSafeInteger(terminalRelocationCapacity) || terminalRelocationCapacity <= 0) {
32
40
  throw new RangeError('Session relocation terminal capacity must be a positive safe integer.');
33
41
  }
@@ -38,61 +46,78 @@ class ZLinkActorSessionBindingRegistry {
38
46
  throw new RangeError('Session relocation seal timeout must be a positive safe integer.');
39
47
  }
40
48
  }
41
- async bind(context, actor, bindingToken, authorityFence, sessionIdentity) {
42
- await this.lane.run(() => this.bindCore(context, actor, bindingToken, authorityFence, sessionIdentity));
49
+ async bind(context, actor, bindingToken, authorityFence, sessionIdentity, commitActor) {
50
+ await this.lane.run(() => this.commitCore(undefined, context, actor, bindingToken, authorityFence, sessionIdentity, commitActor));
43
51
  }
44
- bindCore(context, actor, bindingToken, authorityFence, sessionIdentity) {
45
- this.routes.set(actor.actorId, {
46
- context,
47
- actor,
48
- bindingToken,
49
- sessionIdentity: sessionIdentity ?? sessionIdentityFromContext(context),
50
- activeFrames: { count: 0, requests: new Set() },
51
- authorityFence
52
- });
53
- context.bindLocal(actor, bindingToken);
54
- }
55
- async replace(previous, context, actor, bindingToken, authorityFence, sessionIdentity) {
56
- await this.lane.run(() => this.replaceCore(previous, context, actor, bindingToken, authorityFence, sessionIdentity));
57
- }
58
- replaceCore(previous, context, actor, bindingToken, authorityFence, sessionIdentity) {
52
+ async commitCore(previous, context, actor, bindingToken, authorityFence, sessionIdentity, commitActor) {
59
53
  const current = this.routes.get(actor.actorId);
60
54
  if (current !== previous) {
61
- throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorSessionNotBound, `Actor '${actor.actorId}' session binding changed before route replacement.`, true);
55
+ throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorSessionNotBound, `Actor '${actor.actorId}' session binding changed before route commit.`, true);
62
56
  }
63
- context.bindLocal(actor, bindingToken);
64
- const sameLocalBinding = previous.context === context && previous.bindingToken === bindingToken;
65
- if (!sameLocalBinding) {
66
- try {
57
+ const sameBinding = previous !== undefined &&
58
+ previous.context === context &&
59
+ previous.bindingToken === bindingToken;
60
+ const actorSlot = sameBinding ? previous.actorSlot : this.issueActorSlot(context);
61
+ commitActor?.();
62
+ context.bindLocal(actor, bindingToken, actorSlot);
63
+ try {
64
+ if (previous !== undefined && !sameBinding) {
65
+ this.routes.delete(actor.actorId);
67
66
  previous.context.unbindLocal(actor.actorId, previous.bindingToken);
67
+ if (previous.actorSlot !== undefined) {
68
+ let unbound;
69
+ try {
70
+ unbound = previous.context.actorSlotControls?.enqueueUnbound(previous.actorSlot);
71
+ }
72
+ catch (error) {
73
+ unbound = Promise.reject(error);
74
+ }
75
+ void unbound?.catch((error) => this.errorSink?.()?.reportRuntimeTaskException('retired session actor unbound', error));
76
+ }
68
77
  }
69
- catch (error) {
70
- context.unbindLocal(actor.actorId, bindingToken);
71
- previous.context.bindLocal(previous.actor, previous.bindingToken);
72
- throw error;
78
+ if (!sameBinding && actorSlot !== undefined) {
79
+ await context.actorSlotControls.enqueueBound(actorSlot, actor.actorId);
73
80
  }
74
81
  }
82
+ catch (error) {
83
+ context.unbindLocal(actor.actorId, bindingToken);
84
+ throw error;
85
+ }
75
86
  this.routes.set(actor.actorId, {
76
87
  context,
77
88
  actor,
78
89
  bindingToken,
90
+ actorSlot,
79
91
  sessionIdentity: sessionIdentity ?? sessionIdentityFromContext(context),
80
- activeFrames: previous.activeFrames,
81
- sealId: previous.sealId,
82
- authorityFence: authorityFence ?? previous.authorityFence
92
+ activeFrames: previous?.activeFrames ?? { count: 0, requests: new Set() },
93
+ sealId: previous?.sealId,
94
+ authorityFence: authorityFence ?? previous?.authorityFence
83
95
  });
84
96
  }
85
- async replaceAndReleaseSeal(previous, context, actor, bindingToken, sealId, authorityFence, sessionIdentity) {
86
- await this.lane.run(() => this.replaceAndReleaseSealCore(previous, context, actor, bindingToken, sealId, authorityFence, sessionIdentity));
97
+ issueActorSlot(context) {
98
+ if (context.actorSlotControls === undefined)
99
+ return undefined;
100
+ const actorSlot = this.nextActorSlots.get(context) ?? 1;
101
+ if (actorSlot > 0xffff) {
102
+ throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.InvalidOperation, `Stream session '${String(context.routingId)}' exhausted its Actor slots.`);
103
+ }
104
+ this.nextActorSlots.set(context, actorSlot + 1);
105
+ return actorSlot;
106
+ }
107
+ async replace(previous, context, actor, bindingToken, authorityFence, sessionIdentity, commitActor) {
108
+ await this.lane.run(() => this.commitCore(previous, context, actor, bindingToken, authorityFence, sessionIdentity, commitActor));
87
109
  }
88
- replaceAndReleaseSealCore(previous, context, actor, bindingToken, sealId, authorityFence, sessionIdentity) {
110
+ async replaceAndReleaseSeal(previous, context, actor, bindingToken, sealId, authorityFence, sessionIdentity, commitActor) {
111
+ await this.lane.run(() => this.replaceAndReleaseSealCore(previous, context, actor, bindingToken, sealId, authorityFence, sessionIdentity, commitActor));
112
+ }
113
+ async replaceAndReleaseSealCore(previous, context, actor, bindingToken, sealId, authorityFence, sessionIdentity, commitActor) {
89
114
  if (previous.sealId !== sealId) {
90
115
  throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorLocationStale, `Actor '${actor.actorId}' route switch did not match its relocation seal.`, true);
91
116
  }
92
117
  // JavaScript cannot interleave another ingress turn between these two
93
118
  // synchronous mutations. The replacement preserves the seal, then the
94
119
  // exact release publishes the route to held ingress as one owner turn.
95
- this.replaceCore(previous, context, actor, bindingToken, authorityFence, sessionIdentity);
120
+ await this.commitCore(previous, context, actor, bindingToken, authorityFence, sessionIdentity, commitActor);
96
121
  if (!this.abortSealCore(actor.actorId, sealId)) {
97
122
  throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorLocationStale, `Actor '${actor.actorId}' route switch lost its relocation seal.`, true);
98
123
  }
@@ -119,10 +144,10 @@ class ZLinkActorSessionBindingRegistry {
119
144
  }
120
145
  return Object.freeze({ context: route.context });
121
146
  }
122
- async unbind(actorId, context, bindingToken) {
123
- await this.lane.run(() => this.unbindCore(actorId, context, bindingToken));
147
+ async unbind(actorId, context, bindingToken, termination = ZLinkActorSessionBindingTermination.BindingEnd) {
148
+ await this.lane.run(() => this.unbindCore(actorId, context, bindingToken, termination));
124
149
  }
125
- unbindCore(actorId, context, bindingToken) {
150
+ async unbindCore(actorId, context, bindingToken, termination) {
126
151
  const route = this.routes.get(actorId);
127
152
  if (route === undefined || route.context !== context || route.bindingToken !== bindingToken) {
128
153
  return;
@@ -132,24 +157,28 @@ class ZLinkActorSessionBindingRegistry {
132
157
  this.rejectSealWaiters(actorId, new Error(`Actor '${actorId}' session binding was removed while ingress was held.`));
133
158
  this.rejectActiveFrameWaiters(actorId, new Error(`Actor '${actorId}' session binding was removed while accepted frames were active.`));
134
159
  this.clearRouteRelocation(actorId, route, new Error(`Actor '${actorId}' session binding was removed.`));
160
+ if (termination === ZLinkActorSessionBindingTermination.BindingEnd &&
161
+ route.actorSlot !== undefined) {
162
+ await context.actorSlotControls?.enqueueUnbound(route.actorSlot);
163
+ }
135
164
  }
136
165
  async unbindActor(actorId) {
137
166
  await this.lane.run(() => this.unbindActorCore(actorId));
138
167
  }
139
- unbindActorCore(actorId) {
168
+ async unbindActorCore(actorId) {
140
169
  const route = this.routes.get(actorId);
141
170
  if (route === undefined) {
142
171
  return;
143
172
  }
144
- this.unbindCore(actorId, route.context, route.bindingToken);
173
+ await this.unbindCore(actorId, route.context, route.bindingToken, ZLinkActorSessionBindingTermination.BindingEnd);
145
174
  }
146
175
  async cleanup(context) {
147
176
  await this.lane.run(() => this.cleanupCore(context));
148
177
  }
149
- cleanupCore(context) {
178
+ async cleanupCore(context) {
150
179
  for (const route of [...this.routes.values()]) {
151
180
  if (route.context === context) {
152
- this.unbindCore(route.actor.actorId, context, route.bindingToken);
181
+ await this.unbindCore(route.actor.actorId, context, route.bindingToken, ZLinkActorSessionBindingTermination.PhysicalDisconnect);
153
182
  }
154
183
  }
155
184
  }
@@ -2,6 +2,7 @@ import type { ActorRef } from '../../contracts';
2
2
  import type { ServiceSessionBindingAdmissionClaim, ServiceSessionBindingAdmissionResult } from '../foundation/service-session-binding-ingress-port';
3
3
  import type { ZLinkActorSessionAuthorityFence, ZLinkActorSessionAcceptedProducerProof, ZLinkActorSessionRelocationClaim, ZLinkActorSessionRelocationSnapshot, ZLinkActorSessionRetainedOutbound, ZLinkActorSessionRouteFence } from './actor-session-binding-registry';
4
4
  export interface ZLinkActorSessionBindingRuntimeOwner {
5
+ actorSlot(actorId: string, sessionRid: string): Promise<number | undefined>;
5
6
  sealRelocation(claim: ZLinkActorSessionRelocationClaim, expected: ZLinkActorSessionRouteFence, signal?: AbortSignal): Promise<void>;
6
7
  relocationSnapshot(actorId: string, sealId: string): Promise<ZLinkActorSessionRelocationSnapshot | undefined>;
7
8
  retainRelocationOutbound(actorId: string, operation: ZLinkActorSessionRetainedOutbound, sealId?: string): Promise<ServiceSessionBindingAdmissionResult>;
@@ -6,6 +6,7 @@ const payload_codec_1 = require("../messaging/payload-codec");
6
6
  const protocol_1 = require("./protocol");
7
7
  const abort_1 = require("../abort");
8
8
  const execution_1 = require("../execution");
9
+ const actor_session_binding_registry_1 = require("./actor-session-binding-registry");
9
10
  const actor_session_lifecycle_coordinator_1 = require("./actor-session-lifecycle-coordinator");
10
11
  const managed_stream_1 = require("./managed-stream");
11
12
  class ZLinkBoundActorRelaySender {
@@ -159,7 +160,7 @@ class ZLinkBoundActorRelaySender {
159
160
  // The transport is already closed, so remove its exact route before
160
161
  // invoking application lifecycle code. A reconnect can then install
161
162
  // a successor while this best-effort notification is in flight.
162
- await this.routes.unbind(actor.actorId, context, actor.bindingToken);
163
+ await this.routes.unbind(actor.actorId, context, actor.bindingToken, actor_session_binding_registry_1.ZLinkActorSessionBindingTermination.PhysicalDisconnect);
163
164
  return true;
164
165
  });
165
166
  if (!detached)
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DefaultZLinkBoundSessionResponseTarget = void 0;
4
4
  exports.boundSessionErrorPayload = boundSessionErrorPayload;
5
+ const contracts_1 = require("../../contracts");
6
+ const channel_envelope_1 = require("../channels/channel-envelope");
7
+ const route_disconnected_error_1 = require("../channels/route-disconnected-error");
5
8
  const submission_result_1 = require("../messaging/submission-result");
6
9
  const protocol_1 = require("./protocol");
7
10
  class DefaultZLinkBoundSessionResponseTarget {
@@ -36,7 +39,11 @@ class DefaultZLinkBoundSessionResponseTarget {
36
39
  exports.DefaultZLinkBoundSessionResponseTarget = DefaultZLinkBoundSessionResponseTarget;
37
40
  function boundSessionErrorPayload(error) {
38
41
  return {
39
- code: error instanceof Error ? error.constructor.name : 'RemoteError',
42
+ code: (0, channel_envelope_1.channelErrorCodeName)(error instanceof contracts_1.ZLinkFrameworkException
43
+ ? error.kind
44
+ : error instanceof route_disconnected_error_1.ZLinkRouteDisconnectedError
45
+ ? contracts_1.ZLinkFrameworkErrorKind.Unavailable
46
+ : contracts_1.ZLinkFrameworkErrorKind.InternalFailure),
40
47
  message: errorMessage(error)
41
48
  };
42
49
  }
@@ -45,4 +45,5 @@ export declare class ZLinkBoundSessionService {
45
45
  private submitNativeBoundSessionPayload;
46
46
  private submitNativeBoundSessionFrame;
47
47
  private requireTransport;
48
+ private actorSlot;
48
49
  }
@@ -20,7 +20,7 @@ class ZLinkBoundSessionService {
20
20
  async sendBoundSession(actorId, message, packetName, metadata, signal) {
21
21
  (0, abort_1.throwIfAborted)(signal);
22
22
  const route = await this.routes.requireRoute(actorId);
23
- const frame = this.frameMessages.createJsonFrameMessage(protocol_1.ZLinkStreamMessageKind.Send, (0, protocol_1.resolvePacketName)(message, packetName), metadata, false, undefined, message);
23
+ const frame = this.frameMessages.createJsonFrameMessage(protocol_1.ZLinkStreamMessageKind.Send, (0, protocol_1.resolvePacketName)(message, packetName), metadata, false, undefined, message, undefined, this.actorSlot(route));
24
24
  try {
25
25
  const result = await this.requireTransport().send(actorId, frame, {
26
26
  bindingToken: route.bindingToken,
@@ -40,7 +40,7 @@ class ZLinkBoundSessionService {
40
40
  if (route === undefined) {
41
41
  return { status: submission_result_1.ZLinkSubmitStatus.TargetNotFound };
42
42
  }
43
- const frame = this.frameMessages.createJsonFrameMessage(protocol_1.ZLinkStreamMessageKind.Send, (0, protocol_1.resolvePacketName)(message, packetName), metadata, false, undefined, message);
43
+ const frame = this.frameMessages.createJsonFrameMessage(protocol_1.ZLinkStreamMessageKind.Send, (0, protocol_1.resolvePacketName)(message, packetName), metadata, false, undefined, message, undefined, this.actorSlot(route));
44
44
  try {
45
45
  (0, abort_1.throwIfAborted)(signal);
46
46
  if ((await this.routes.route(actorId))?.bindingToken !== route.bindingToken) {
@@ -124,7 +124,7 @@ class ZLinkBoundSessionService {
124
124
  }
125
125
  }
126
126
  async writeLocalBoundSessionFrame(actorId, route, kind, packetName, metadata, compressPayload, requestSeq, payload, operationName) {
127
- const frame = this.frameMessages.createJsonFrameMessage(kind, packetName, metadata, compressPayload, requestSeq, payload);
127
+ const frame = this.frameMessages.createJsonFrameMessage(kind, packetName, metadata, compressPayload, requestSeq, payload, undefined, this.actorSlot(route));
128
128
  try {
129
129
  if ((await this.routes.route(actorId))?.bindingToken !== route.bindingToken) {
130
130
  return false;
@@ -165,6 +165,9 @@ class ZLinkBoundSessionService {
165
165
  }
166
166
  return this.options.transport;
167
167
  }
168
+ actorSlot(route) {
169
+ return route.actorSlot;
170
+ }
168
171
  }
169
172
  exports.ZLinkBoundSessionService = ZLinkBoundSessionService;
170
173
  function requireBoundSessionGeneration(actorRef) {
@@ -88,6 +88,7 @@ class ZLinkStreamRuntimeManager {
88
88
  const service = createService.call(meshNode, socket.nativeInstance);
89
89
  const bindingOwner = (0, actor_session_binding_runtime_owner_1.actorSessionBindingRuntimeOwner)(this.options.bindingRuntime);
90
90
  (0, service_session_binding_ingress_port_1.registerServiceSessionBindingIngressPort)(service, {
91
+ actorSlot: (actorId, sessionRid) => bindingOwner.actorSlot(actorId, sessionRid),
91
92
  retainOutbound: (claim, delivery) => bindingOwner.admitRelocationOutbound(claim, delivery),
92
93
  clearOutbound: (actorId, error) => bindingOwner.clearRelocation(actorId, error)
93
94
  });
@@ -206,7 +207,7 @@ class ZLinkStreamBindingRuntime {
206
207
  ...options,
207
208
  actorBindTimeoutMs: options.actorBindTimeoutMs ?? DEFAULT_ACTOR_BIND_TIMEOUT_MS
208
209
  };
209
- this.routes = new actor_session_binding_registry_1.ZLinkActorSessionBindingRegistry(4096, 4096, runtimeOptions.sessionRelocationSealTimeoutMs ?? 3_000);
210
+ this.routes = new actor_session_binding_registry_1.ZLinkActorSessionBindingRegistry(4096, 4096, runtimeOptions.sessionRelocationSealTimeoutMs ?? 3_000, runtimeOptions.errorSink);
210
211
  this.compressionCodec = (0, stream_frame_factory_1.resolveStreamCompressionCodec)(runtimeOptions.streamCompression);
211
212
  this.frameMessages = new stream_frame_factory_1.ZLinkStreamFrameMessageFactory(runtimeOptions);
212
213
  this.boundSessions = new bound_session_service_1.ZLinkBoundSessionService(this.routes, this.frameMessages, runtimeOptions);
@@ -214,6 +215,12 @@ class ZLinkStreamBindingRuntime {
214
215
  this.sessionActors = new session_actor_coordinator_1.ZLinkSessionActorCoordinator(this.routes, this, runtimeOptions, actorSessionLifecycle);
215
216
  this.boundActorRelay = new bound_actor_relay_sender_1.ZLinkBoundActorRelaySender(this.routes, this.frameMessages, runtimeOptions, actorSessionLifecycle);
216
217
  (0, actor_session_binding_runtime_owner_1.registerActorSessionBindingRuntimeOwner)(this, {
218
+ actorSlot: async (actorId, sessionRid) => {
219
+ const route = await this.routes.route(actorId);
220
+ return route !== undefined && route.sessionIdentity === sessionRid
221
+ ? route.actorSlot
222
+ : undefined;
223
+ },
217
224
  sealRelocation: (claim, expected, signal) => this.routes.sealRelocation(claim, expected, signal),
218
225
  relocationSnapshot: (actorId, sealId) => this.routes.relocationSnapshot(actorId, sealId),
219
226
  retainRelocationOutbound: (actorId, operation, sealId) => this.routes.retainRelocationOutbound(actorId, operation, sealId),
@@ -43,6 +43,9 @@ export declare class ZLinkManagedStream implements ZLinkStream {
43
43
  bindActor(actor: ActorRef, timeoutMs: number, signal?: AbortSignal, onBindingReplaced?: (actor: ServiceActorRef, retiredSession: ServiceRetiredBoundSessionRouteFence) => void, authorityFence?: ZLinkActorSessionAuthorityFence): Promise<void>;
44
44
  private ensureNativeActorRoute;
45
45
  unbindActor(actorId: string, timeoutMs: number, signal?: AbortSignal): Promise<void>;
46
+ enqueueActorBound(actorSlot: number, actorId: string): Promise<void>;
47
+ enqueueActorUnbound(actorSlot: number): Promise<void>;
48
+ private submitActorBindingControl;
46
49
  sendBoundActor(actorId: string, parts: readonly Message[], flags?: ZLinkBackendSendFlags): Promise<boolean>;
47
50
  updateAddresses(localAddr: string | undefined, remoteAddr: string | undefined): void;
48
51
  private hasNativeBinding;
@@ -122,12 +122,12 @@ class ZLinkManagedStream {
122
122
  if (this.transportClosed) {
123
123
  throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RouteNotConnected, `Stream session '${this.sessionId}' is disconnected.`);
124
124
  }
125
+ const nativeActor = toNativeActorRef(actor);
125
126
  const route = this.nativeRoute(actor.meshName);
126
127
  if (route !== undefined) {
127
128
  if (route.service.status().state === 1) {
128
129
  route.service.start();
129
130
  }
130
- const nativeActor = toNativeActorRef(actor);
131
131
  await this.ensureNativeActorRoute(route, actor, timeoutMs, signal);
132
132
  if (this.isTransportClosed()) {
133
133
  throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.RouteNotConnected, `Stream session '${this.sessionId}' is disconnected.`);
@@ -163,6 +163,7 @@ class ZLinkManagedStream {
163
163
  return;
164
164
  }
165
165
  await this.socket.bindActor(this.backendRoutingId(), toBackendActorRef(actor), timeoutMs, signal);
166
+ this.nativeActorBindings.set(actor.actorId, { actor: nativeActor });
166
167
  }
167
168
  async ensureNativeActorRoute(route, actor, timeoutMs, signal) {
168
169
  const completion = await route.completions.submit(() => route.service.lookupActor(actor.nodeRid, actor.actorId, timeoutMs), signal);
@@ -223,6 +224,24 @@ class ZLinkManagedStream {
223
224
  return;
224
225
  }
225
226
  await this.socket.unbindActor(this.backendRoutingId(), actorId, timeoutMs, signal);
227
+ if (binding !== undefined && this.nativeActorBindings.get(actorId) === binding) {
228
+ this.nativeActorBindings.delete(actorId);
229
+ }
230
+ }
231
+ async enqueueActorBound(actorSlot, actorId) {
232
+ await this.submitActorBindingControl((0, protocol_1.encodeActorBoundFrame)(actorSlot, actorId));
233
+ }
234
+ async enqueueActorUnbound(actorSlot) {
235
+ await this.submitActorBindingControl((0, protocol_1.encodeActorUnboundFrame)(actorSlot));
236
+ }
237
+ async submitActorBindingControl(frame) {
238
+ const message = runtime_message_1.ZLinkBufferMessage.from(frame);
239
+ try {
240
+ await this.socket.submit(this.backendRoutingId(), message);
241
+ }
242
+ finally {
243
+ message.close();
244
+ }
226
245
  }
227
246
  async sendBoundActor(actorId, parts, flags) {
228
247
  if (this.transportClosed) {
@@ -22,7 +22,8 @@ export declare enum ZLinkStreamHeaderFlags {
22
22
  HasMetadata = 2,
23
23
  PayloadCompressed = 4,
24
24
  HasCorrelationId = 8,
25
- HasFlowId = 16
25
+ HasFlowId = 16,
26
+ HasActorSlot = 32
26
27
  }
27
28
  export declare enum ZLinkStreamCloseReasonCode {
28
29
  ClientClose = 1,
@@ -44,6 +45,7 @@ export interface ZLinkStreamFrameHeader {
44
45
  readonly correlationId?: string;
45
46
  readonly flowId?: string;
46
47
  readonly flowOrigin?: ZLinkFlowOrigin;
48
+ readonly actorSlot?: number;
47
49
  }
48
50
  export type ZLinkStreamReplyMessageKind = ZLinkStreamMessageKind.Response | ZLinkStreamMessageKind.Error;
49
51
  export interface ZLinkStreamFrame {
@@ -54,6 +56,8 @@ export declare function resolvePacketName(message: unknown, explicitPacketName:
54
56
  export declare function ensureSingleSubmit(executed: boolean): void;
55
57
  export declare function encodeStreamFrame(header: ZLinkStreamFrameHeader, payload: Uint8Array): Uint8Array;
56
58
  export declare function encodeStreamControlFrame(name: string): Uint8Array;
59
+ export declare function encodeActorBoundFrame(actorSlot: number, actorId: string): Uint8Array;
60
+ export declare function encodeActorUnboundFrame(actorSlot: number): Uint8Array;
57
61
  export declare function encodeSessionClosingFrame(diagnostic?: string, reason?: ZLinkStreamCloseReasonCode): Uint8Array;
58
62
  export declare function decodeStreamFrame(frame: Uint8Array): ZLinkStreamFrame;
59
63
  export declare function tryDecodeStreamFrame(frame: Uint8Array): ZLinkStreamFrame | undefined;
@@ -7,6 +7,8 @@ exports.resolvePacketName = resolvePacketName;
7
7
  exports.ensureSingleSubmit = ensureSingleSubmit;
8
8
  exports.encodeStreamFrame = encodeStreamFrame;
9
9
  exports.encodeStreamControlFrame = encodeStreamControlFrame;
10
+ exports.encodeActorBoundFrame = encodeActorBoundFrame;
11
+ exports.encodeActorUnboundFrame = encodeActorUnboundFrame;
10
12
  exports.encodeSessionClosingFrame = encodeSessionClosingFrame;
11
13
  exports.decodeStreamFrame = decodeStreamFrame;
12
14
  exports.tryDecodeStreamFrame = tryDecodeStreamFrame;
@@ -78,6 +80,7 @@ var ZLinkStreamHeaderFlags;
78
80
  ZLinkStreamHeaderFlags[ZLinkStreamHeaderFlags["PayloadCompressed"] = 4] = "PayloadCompressed";
79
81
  ZLinkStreamHeaderFlags[ZLinkStreamHeaderFlags["HasCorrelationId"] = 8] = "HasCorrelationId";
80
82
  ZLinkStreamHeaderFlags[ZLinkStreamHeaderFlags["HasFlowId"] = 16] = "HasFlowId";
83
+ ZLinkStreamHeaderFlags[ZLinkStreamHeaderFlags["HasActorSlot"] = 32] = "HasActorSlot";
81
84
  })(ZLinkStreamHeaderFlags || (exports.ZLinkStreamHeaderFlags = ZLinkStreamHeaderFlags = {}));
82
85
  var ZLinkStreamCloseReasonCode;
83
86
  (function (ZLinkStreamCloseReasonCode) {
@@ -114,6 +117,26 @@ function encodeStreamControlFrame(name) {
114
117
  metadata: EMPTY_STREAM_METADATA
115
118
  }, new Uint8Array());
116
119
  }
120
+ function encodeActorBoundFrame(actorSlot, actorId) {
121
+ const actorIdBytes = (0, stream_wire_1.utf8Encode)(actorId);
122
+ if (actorIdBytes.length < 1 || actorIdBytes.length > 0xff) {
123
+ throw new Error('Actor id length is invalid for a STREAM binding control packet.');
124
+ }
125
+ const payload = new Uint8Array(4 + actorIdBytes.length);
126
+ payload[0] = 1;
127
+ payload[1] = actorSlot >>> 8;
128
+ payload[2] = actorSlot & 0xff;
129
+ payload[3] = actorIdBytes.length;
130
+ payload.set(actorIdBytes, 4);
131
+ return encodeStreamFrame(controlHeader('$zlink.actor.bound'), payload);
132
+ }
133
+ function encodeActorUnboundFrame(actorSlot) {
134
+ const payload = new Uint8Array(3);
135
+ payload[0] = 1;
136
+ payload[1] = actorSlot >>> 8;
137
+ payload[2] = actorSlot & 0xff;
138
+ return encodeStreamFrame(controlHeader('$zlink.actor.unbound'), payload);
139
+ }
117
140
  function encodeSessionClosingFrame(diagnostic = '', reason = ZLinkStreamCloseReasonCode.ServerDrain) {
118
141
  const diagnosticBytes = (0, stream_wire_1.utf8Encode)(diagnostic);
119
142
  if (diagnosticBytes.length > 512)
@@ -143,8 +166,9 @@ function encodeStreamHeader(header) {
143
166
  const hasMetadata = header.metadata.size > 0;
144
167
  const hasCorrelation = header.correlationId !== undefined && header.correlationId.length > 0;
145
168
  const hasFlow = header.flowId !== undefined || header.flowOrigin !== undefined;
169
+ const hasActorSlot = header.actorSlot !== undefined;
146
170
  if (header.kind === ZLinkStreamMessageKind.Control &&
147
- (hasCorrelation || hasRequestSeq || hasMetadata || hasFlow)) {
171
+ (hasCorrelation || hasRequestSeq || hasMetadata || hasFlow || hasActorSlot)) {
148
172
  throw new Error('Control packet must not contain a request sequence, metadata, correlation id, or flow id.');
149
173
  }
150
174
  // Explicit construction: this runs for every outbound frame, so avoid a
@@ -158,7 +182,8 @@ function encodeStreamHeader(header) {
158
182
  metadata: header.metadata,
159
183
  correlationId: header.correlationId,
160
184
  flowId: header.flowId,
161
- flowOrigin: encodeFlowOrigin(header.flowOrigin)
185
+ flowOrigin: encodeFlowOrigin(header.flowOrigin),
186
+ actorSlot: header.actorSlot
162
187
  });
163
188
  }
164
189
  function decodeStreamHeader(header, flowEnabled = true) {
@@ -169,8 +194,9 @@ function decodeStreamHeader(header, flowEnabled = true) {
169
194
  const hasMetadata = (flags & ZLinkStreamHeaderFlags.HasMetadata) !== 0;
170
195
  const hasCorrelation = (flags & ZLinkStreamHeaderFlags.HasCorrelationId) !== 0;
171
196
  const hasFlow = (flags & ZLinkStreamHeaderFlags.HasFlowId) !== 0;
197
+ const hasActorSlot = (flags & ZLinkStreamHeaderFlags.HasActorSlot) !== 0;
172
198
  if (kind === ZLinkStreamMessageKind.Control &&
173
- (hasCorrelation || hasRequestSeq || hasMetadata || hasFlow)) {
199
+ (hasCorrelation || hasRequestSeq || hasMetadata || hasFlow || hasActorSlot)) {
174
200
  throw new Error('Control packet must not contain a request sequence, metadata, correlation id, or flow id.');
175
201
  }
176
202
  const metadata = publicStreamMetadata(decoded.metadata);
@@ -184,7 +210,8 @@ function decodeStreamHeader(header, flowEnabled = true) {
184
210
  metadata,
185
211
  correlationId: decoded.correlationId,
186
212
  flowId: decoded.flowId,
187
- flowOrigin: decodeFlowOrigin(decoded.flowOrigin)
213
+ flowOrigin: decodeFlowOrigin(decoded.flowOrigin),
214
+ actorSlot: decoded.actorSlot
188
215
  };
189
216
  }
190
217
  function actorRequestDeadlineMetadata(deadlineUnixMs) {
@@ -221,9 +248,19 @@ function createStreamReplyHeader(requestHeader, kind, codec, flags, metadata, in
221
248
  // observation-only and is not copied into the reply when tracing is Off
222
249
  // (spec 27 §4, §7).
223
250
  correlationId: requestHeader.correlationId,
251
+ actorSlot: requestHeader.actorSlot,
224
252
  ...(includeFlow ? { flowId: requestHeader.flowId, flowOrigin: requestHeader.flowOrigin } : {})
225
253
  };
226
254
  }
255
+ function controlHeader(name) {
256
+ return {
257
+ kind: ZLinkStreamMessageKind.Control,
258
+ codec: ZLinkStreamCodec.Raw,
259
+ flags: ZLinkStreamHeaderFlags.None,
260
+ name,
261
+ metadata: EMPTY_STREAM_METADATA
262
+ };
263
+ }
227
264
  function encodeFlowOrigin(origin) {
228
265
  return origin === undefined
229
266
  ? undefined
@@ -60,18 +60,17 @@ class ZLinkSessionActorCoordinator {
60
60
  const sessionActor = reuseActor ??
61
61
  new session_context_1.DefaultZLinkSessionActor(this.sessionActorRuntime, boundActorRef, bindingToken);
62
62
  const sessionIdentity = String(this.actorBindingRoutingId(context));
63
- sessionActor.updateRef(boundActorRef);
64
63
  if (previous === undefined) {
65
64
  if (releaseSeal !== undefined) {
66
65
  throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorSessionNotBound, `Actor '${actorRef.actorId}' route switch has no Session binding to release.`, true);
67
66
  }
68
- await this.routes.bind(context, sessionActor, bindingToken, authorityFence, sessionIdentity);
67
+ await this.routes.bind(context, sessionActor, bindingToken, authorityFence, sessionIdentity, () => sessionActor.updateRef(boundActorRef));
69
68
  }
70
69
  else if (releaseSeal !== undefined) {
71
- await this.routes.replaceAndReleaseSeal(previous, context, sessionActor, bindingToken, releaseSeal.sealId, authorityFence, sessionIdentity);
70
+ await this.routes.replaceAndReleaseSeal(previous, context, sessionActor, bindingToken, releaseSeal.sealId, authorityFence, sessionIdentity, () => sessionActor.updateRef(boundActorRef));
72
71
  }
73
72
  else {
74
- await this.routes.replace(previous, context, sessionActor, bindingToken, authorityFence, sessionIdentity);
73
+ await this.routes.replace(previous, context, sessionActor, bindingToken, authorityFence, sessionIdentity, () => sessionActor.updateRef(boundActorRef));
75
74
  }
76
75
  if (confirmRemoteSessionBinding !== false &&
77
76
  this.options.confirmRemoteActorSessionBinding !== undefined) {
@@ -10,6 +10,8 @@ import type { ServiceRetiredBoundSessionRouteFence } from '../foundation/service
10
10
  export interface ZLinkSessionContextStream extends ZLinkStream {
11
11
  writeRaw(payload: Message, flags?: number): boolean;
12
12
  submitRaw(payload: Message, signal?: AbortSignal, timeoutMs?: number): Promise<ZLinkSubmitResult>;
13
+ enqueueActorBound?(actorSlot: number, actorId: string): Promise<void>;
14
+ enqueueActorUnbound?(actorSlot: number): Promise<void>;
13
15
  }
14
16
  interface ZLinkSessionContextRuntime {
15
17
  cleanup(context: DefaultZLinkSessionContext): Promise<void>;
@@ -76,7 +78,12 @@ export declare class DefaultZLinkSessionContext implements ZLinkSessionContext {
76
78
  payloadForHeader(header: ZLinkStreamFrameHeader, payload: Message): Message;
77
79
  get boundActors(): readonly DefaultZLinkSessionActor[];
78
80
  findBoundActor(actorId: string): DefaultZLinkSessionActor | undefined;
79
- bindLocal(actor: DefaultZLinkSessionActor, token: string): void;
81
+ actorForSlot(actorSlot: number | undefined): DefaultZLinkSessionActor | undefined;
82
+ get actorSlotControls(): {
83
+ enqueueBound(actorSlot: number, actorId: string): Promise<void>;
84
+ enqueueUnbound(actorSlot: number): Promise<void>;
85
+ } | undefined;
86
+ bindLocal(actor: DefaultZLinkSessionActor, token: string, actorSlot?: number): void;
80
87
  unbindLocal(actorId: string, token: string): void;
81
88
  completeConfiguration(): void;
82
89
  }
@@ -92,7 +99,7 @@ export declare class DefaultZLinkSessionActor implements ZLinkSessionActor {
92
99
  relay(dispatch: ZLinkSessionDispatchContext, payload: ZLinkMessage, signal?: AbortSignal): Promise<void>;
93
100
  notifyDisconnected(signal?: AbortSignal): Promise<void>;
94
101
  }
95
- export declare function createSessionDispatchContext(header: ZLinkStreamFrameHeader): ZLinkSessionDispatchContext;
102
+ export declare function createSessionDispatchContext(header: ZLinkStreamFrameHeader, actor?: DefaultZLinkSessionActor): ZLinkSessionDispatchContext;
96
103
  export declare class DefaultZLinkBoundSession implements ZLinkBoundSession {
97
104
  private readonly runtime;
98
105
  private readonly actorId;