@zlink-systems/framework 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Actors/ZLinkActorClient.d.ts +1 -1
  3. package/dist/contracts/Channels/Calls.d.ts +1 -1
  4. package/dist/contracts/Configuration/Builders.d.ts +2 -1
  5. package/dist/contracts/Configuration/Configs.d.ts +0 -1
  6. package/dist/contracts/Configuration/InternalDefaults.d.ts +3 -0
  7. package/dist/contracts/Configuration/InternalDefaults.js +4 -1
  8. package/dist/contracts/Configuration/RegistrationBuilders.js +9 -0
  9. package/dist/contracts/Configuration/RegistrationNormalizers.d.ts +1 -0
  10. package/dist/contracts/Configuration/RegistrationNormalizers.js +16 -0
  11. package/dist/contracts/Configuration/RegistrationTypes.d.ts +3 -5
  12. package/dist/contracts/Configuration/RegistrationValidators.js +0 -2
  13. package/dist/contracts/Spots/Contracts.d.ts +1 -1
  14. package/dist/contracts/Streams/BoundSessionContracts.d.ts +1 -1
  15. package/dist/contracts/Streams/IZLinkSession.d.ts +2 -3
  16. package/dist/contracts/Streams/IZLinkSessionActor.d.ts +2 -2
  17. package/dist/runtime/actors/actor-client.js +7 -10
  18. package/dist/runtime/actors/actor-mailbox.d.ts +2 -2
  19. package/dist/runtime/actors/actor-mailbox.js +2 -2
  20. package/dist/runtime/actors/actor-request-deadline.d.ts +1 -3
  21. package/dist/runtime/actors/actor-request-deadline.js +4 -29
  22. package/dist/runtime/actors/actor-routed-join-transport.d.ts +2 -3
  23. package/dist/runtime/backend/contracts/index.d.ts +1 -4
  24. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -0
  25. package/dist/runtime/backend/node/node-backend-adapter-support.js +35 -4
  26. package/dist/runtime/backend/node/node-dealer-channel-client-transport.d.ts +1 -1
  27. package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +1 -2
  28. package/dist/runtime/backend/node/node-raw-binding-port.js +10 -27
  29. package/dist/runtime/backend/node/node-raw-mesh-backend.js +1 -3
  30. package/dist/runtime/backend/node/node-socket-backend-adapter.js +1 -1
  31. package/dist/runtime/channels/channel-clients.js +8 -10
  32. package/dist/runtime/channels/channel-envelope.d.ts +1 -1
  33. package/dist/runtime/channels/channel-envelope.js +4 -10
  34. package/dist/runtime/channels/channel-outbound-operations.d.ts +2 -2
  35. package/dist/runtime/channels/channel-outbound-operations.js +20 -36
  36. package/dist/runtime/channels/channel-runtime-manager.d.ts +4 -4
  37. package/dist/runtime/channels/channel-runtime-manager.js +8 -8
  38. package/dist/runtime/channels/channel-socket-options.js +0 -10
  39. package/dist/runtime/channels/channel-socket-registry.d.ts +1 -1
  40. package/dist/runtime/channels/channel-socket-registry.js +3 -14
  41. package/dist/runtime/channels/channel-transports.d.ts +10 -14
  42. package/dist/runtime/channels/channel-transports.js +11 -14
  43. package/dist/runtime/channels/local-spot-route-transport.d.ts +1 -1
  44. package/dist/runtime/channels/local-spot-route-transport.js +1 -3
  45. package/dist/runtime/channels/router-socket-spot-route-transport.d.ts +1 -1
  46. package/dist/runtime/channels/router-socket-spot-route-transport.js +2 -2
  47. package/dist/runtime/channels/source-spot-router.d.ts +1 -1
  48. package/dist/runtime/channels/source-spot-router.js +2 -4
  49. package/dist/runtime/channels/spot-node-route-transport.d.ts +1 -1
  50. package/dist/runtime/channels/spot-node-route-transport.js +2 -3
  51. package/dist/runtime/channels/spot-route-bridge-transport.d.ts +1 -1
  52. package/dist/runtime/channels/spot-route-bridge-transport.js +2 -2
  53. package/dist/runtime/channels/spot-route-dispatch-strategy.d.ts +2 -4
  54. package/dist/runtime/channels/spot-route-dispatch-strategy.js +11 -12
  55. package/dist/runtime/execution/serial-execution-queue.d.ts +12 -3
  56. package/dist/runtime/execution/serial-execution-queue.js +116 -28
  57. package/dist/runtime/foundation/operation-registry.d.ts +3 -2
  58. package/dist/runtime/foundation/operation-registry.js +5 -1
  59. package/dist/runtime/foundation/raw-service-mesh-runtime.js +17 -11
  60. package/dist/runtime/foundation/service-authority-payload-codec.d.ts +0 -2
  61. package/dist/runtime/foundation/service-authority-payload-codec.js +0 -43
  62. package/dist/runtime/foundation/service-stateful-registry.d.ts +4 -3
  63. package/dist/runtime/foundation/service-stateful-registry.js +5 -2
  64. package/dist/runtime/foundation/service-stateful-runtime.d.ts +1 -8
  65. package/dist/runtime/foundation/service-stateful-runtime.js +78 -126
  66. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -11
  67. package/dist/runtime/foundation/service-stateful-wire-codec.js +1 -23
  68. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -0
  69. package/dist/runtime/foundation/service-topology-registry.js +16 -17
  70. package/dist/runtime/host/actor-packet-relay.d.ts +2 -2
  71. package/dist/runtime/host/actor-packet-relay.js +8 -10
  72. package/dist/runtime/host/index.js +1 -15
  73. package/dist/runtime/host/instance-activation-authority.d.ts +0 -1
  74. package/dist/runtime/host/instance-activation-authority.js +46 -97
  75. package/dist/runtime/host/remote-bound-session-relay.js +8 -8
  76. package/dist/runtime/host/spot-address-transport.d.ts +1 -9
  77. package/dist/runtime/host/spot-address-transport.js +47 -87
  78. package/dist/runtime/host/stateful-authority-route-runtime.d.ts +1 -0
  79. package/dist/runtime/host/stateful-authority-route-runtime.js +17 -9
  80. package/dist/runtime/locations/domain-store-contract.d.ts +1 -0
  81. package/dist/runtime/locations/in-memory-authority-store.d.ts +2 -0
  82. package/dist/runtime/locations/in-memory-authority-store.js +22 -8
  83. package/dist/runtime/locations/in-memory-location-store.d.ts +1 -0
  84. package/dist/runtime/locations/in-memory-location-store.js +3 -0
  85. package/dist/runtime/locations/internal-store-contracts.d.ts +1 -1
  86. package/dist/runtime/locations/location-store-repository.d.ts +1 -0
  87. package/dist/runtime/locations/location-store-repository.js +98 -53
  88. package/dist/runtime/messaging/request-deadline.d.ts +3 -0
  89. package/dist/runtime/messaging/request-deadline.js +39 -0
  90. package/dist/runtime/messaging/submission-result.d.ts +2 -1
  91. package/dist/runtime/messaging/submission-result.js +11 -4
  92. package/dist/runtime/spots/spot-outbound.d.ts +3 -5
  93. package/dist/runtime/spots/spot-outbound.js +12 -15
  94. package/dist/runtime/spots/spot-serial-executor.js +4 -1
  95. package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
  96. package/dist/runtime/spots/spot-serial-turn-executor.js +5 -0
  97. package/dist/runtime/streams/bound-actor-relay-sender.d.ts +2 -2
  98. package/dist/runtime/streams/bound-actor-relay-sender.js +14 -14
  99. package/dist/runtime/streams/bound-session-service.js +1 -1
  100. package/dist/runtime/streams/index.d.ts +2 -3
  101. package/dist/runtime/streams/index.js +6 -5
  102. package/dist/runtime/streams/managed-stream.d.ts +2 -2
  103. package/dist/runtime/streams/managed-stream.js +3 -5
  104. package/dist/runtime/streams/native-fallback-bound-session.js +13 -16
  105. package/dist/runtime/streams/protocol.js +0 -9
  106. package/dist/runtime/streams/session-calls.d.ts +5 -7
  107. package/dist/runtime/streams/session-calls.js +6 -23
  108. package/dist/runtime/streams/session-context.d.ts +4 -8
  109. package/dist/runtime/streams/session-context.js +3 -32
  110. package/dist/runtime/streams/session-serial-executor.d.ts +0 -1
  111. package/dist/runtime/streams/session-serial-executor.js +0 -3
  112. package/dist/runtime/streams/stream-message-utils.d.ts +0 -1
  113. package/dist/runtime/streams/stream-message-utils.js +0 -20
  114. package/dist/runtime/streams/stream-session-runtime.d.ts +6 -3
  115. package/dist/runtime/streams/stream-session-runtime.js +40 -85
  116. package/package.json +3 -3
  117. package/src/contracts/Actors/ZLinkActorClient.ts +1 -1
  118. package/src/contracts/Channels/Calls.ts +1 -1
  119. package/src/contracts/Configuration/Builders.ts +2 -1
  120. package/src/contracts/Configuration/Configs.ts +0 -1
  121. package/src/contracts/Configuration/InternalDefaults.ts +4 -0
  122. package/src/contracts/Configuration/RegistrationBuilders.ts +13 -3
  123. package/src/contracts/Configuration/RegistrationNormalizers.ts +25 -0
  124. package/src/contracts/Configuration/RegistrationTypes.ts +3 -5
  125. package/src/contracts/Configuration/RegistrationValidators.ts +0 -4
  126. package/src/contracts/Spots/Contracts.ts +1 -1
  127. package/src/contracts/Streams/BoundSessionContracts.ts +1 -1
  128. package/src/contracts/Streams/IZLinkSession.ts +2 -3
  129. package/src/contracts/Streams/IZLinkSessionActor.ts +2 -6
  130. package/src/runtime/actors/actor-client.ts +9 -15
  131. package/src/runtime/actors/actor-mailbox.ts +7 -2
  132. package/src/runtime/actors/actor-request-deadline.ts +4 -49
  133. package/src/runtime/actors/actor-routed-join-transport.ts +3 -5
  134. package/src/runtime/backend/contracts/index.ts +1 -8
  135. package/src/runtime/backend/node/node-backend-adapter-support.ts +45 -5
  136. package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +0 -2
  137. package/src/runtime/backend/node/node-raw-binding-port.ts +15 -41
  138. package/src/runtime/backend/node/node-raw-mesh-backend.ts +1 -7
  139. package/src/runtime/backend/node/node-socket-backend-adapter.ts +1 -1
  140. package/src/runtime/channels/channel-clients.ts +9 -19
  141. package/src/runtime/channels/channel-envelope.ts +16 -28
  142. package/src/runtime/channels/channel-outbound-operations.ts +36 -38
  143. package/src/runtime/channels/channel-runtime-manager.ts +6 -28
  144. package/src/runtime/channels/channel-socket-options.ts +0 -13
  145. package/src/runtime/channels/channel-socket-registry.ts +3 -17
  146. package/src/runtime/channels/channel-transports.ts +7 -33
  147. package/src/runtime/channels/local-spot-route-transport.ts +1 -5
  148. package/src/runtime/channels/router-socket-spot-route-transport.ts +2 -6
  149. package/src/runtime/channels/source-spot-router.ts +2 -9
  150. package/src/runtime/channels/spot-node-route-transport.ts +2 -7
  151. package/src/runtime/channels/spot-route-bridge-transport.ts +5 -12
  152. package/src/runtime/channels/spot-route-dispatch-strategy.ts +11 -30
  153. package/src/runtime/execution/serial-execution-queue.ts +117 -29
  154. package/src/runtime/foundation/operation-registry.ts +16 -9
  155. package/src/runtime/foundation/raw-service-mesh-runtime.ts +17 -14
  156. package/src/runtime/foundation/service-authority-payload-codec.ts +0 -46
  157. package/src/runtime/foundation/service-stateful-registry.ts +10 -5
  158. package/src/runtime/foundation/service-stateful-runtime.ts +103 -181
  159. package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -25
  160. package/src/runtime/foundation/service-topology-registry.ts +30 -20
  161. package/src/runtime/host/actor-packet-relay.ts +8 -15
  162. package/src/runtime/host/index.ts +2 -26
  163. package/src/runtime/host/instance-activation-authority.ts +58 -125
  164. package/src/runtime/host/remote-bound-session-relay.ts +8 -16
  165. package/src/runtime/host/spot-address-transport.ts +107 -166
  166. package/src/runtime/host/stateful-authority-route-runtime.ts +22 -9
  167. package/src/runtime/locations/domain-store-contract.ts +5 -0
  168. package/src/runtime/locations/in-memory-authority-store.ts +42 -8
  169. package/src/runtime/locations/in-memory-location-store.ts +8 -0
  170. package/src/runtime/locations/internal-store-contracts.ts +1 -0
  171. package/src/runtime/locations/location-store-repository.ts +130 -68
  172. package/src/runtime/messaging/request-deadline.ts +58 -0
  173. package/src/runtime/messaging/submission-result.ts +18 -4
  174. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
  175. package/src/runtime/spots/spot-outbound.ts +14 -24
  176. package/src/runtime/spots/spot-serial-executor.ts +4 -1
  177. package/src/runtime/spots/spot-serial-turn-executor.ts +10 -0
  178. package/src/runtime/streams/bound-actor-relay-sender.ts +12 -26
  179. package/src/runtime/streams/bound-session-service.ts +1 -1
  180. package/src/runtime/streams/index.ts +5 -5
  181. package/src/runtime/streams/managed-stream.ts +3 -13
  182. package/src/runtime/streams/native-fallback-bound-session.ts +13 -25
  183. package/src/runtime/streams/protocol.ts +0 -13
  184. package/src/runtime/streams/session-calls.ts +8 -34
  185. package/src/runtime/streams/session-context.ts +6 -47
  186. package/src/runtime/streams/session-serial-executor.ts +0 -8
  187. package/src/runtime/streams/stream-message-utils.ts +1 -27
  188. package/src/runtime/streams/stream-session-runtime.ts +54 -97
  189. package/dist/runtime/streams/session-requests.d.ts +0 -25
  190. package/dist/runtime/streams/session-requests.js +0 -101
  191. package/src/runtime/streams/session-requests.ts +0 -110
@@ -32,8 +32,6 @@ export interface ZLinkLocalSpotRouteDispatcher {
32
32
  message: unknown,
33
33
  context: {
34
34
  readonly channelName: string;
35
- readonly admissionTimeoutMs?: number;
36
- readonly signal?: AbortSignal;
37
35
  readonly metadata?: ReadonlyMap<string, string>;
38
36
  }
39
37
  ): Promise<void>;
@@ -101,14 +99,12 @@ export class ZLinkSpotRouteDispatchStrategy {
101
99
  spotRouteTarget: ZLinkSpotRouteTarget,
102
100
  packetName: string | undefined,
103
101
  message: unknown,
104
- signal?: AbortSignal,
105
- metadata?: ReadonlyMap<string, string>,
106
- timeoutMs?: number
102
+ metadata?: ReadonlyMap<string, string>
107
103
  ): Promise<void> {
108
104
  // Call-scoped flow (spec 27 §4): the envelope flow pair lives only for
109
105
  // the duration of this outbound call, never in the caller's context.
110
106
  return runWithOutboundFlow(this.options.flowCreationEnabled?.() ?? true, () =>
111
- this.routeSendToSpotScoped(spotRouteTarget, packetName, message, signal, metadata, timeoutMs)
107
+ this.routeSendToSpotScoped(spotRouteTarget, packetName, message, metadata)
112
108
  );
113
109
  }
114
110
 
@@ -116,11 +112,8 @@ export class ZLinkSpotRouteDispatchStrategy {
116
112
  spotRouteTarget: ZLinkSpotRouteTarget,
117
113
  packetName: string | undefined,
118
114
  message: unknown,
119
- signal?: AbortSignal,
120
- metadata?: ReadonlyMap<string, string>,
121
- timeoutMs?: number
115
+ metadata?: ReadonlyMap<string, string>
122
116
  ): Promise<void> {
123
- throwIfAborted(signal);
124
117
  const localSpotRouteNode = this.targets.localRouteNode(spotRouteTarget);
125
118
  if (localSpotRouteNode !== undefined) {
126
119
  await this.localTransport.send(
@@ -128,9 +121,7 @@ export class ZLinkSpotRouteDispatchStrategy {
128
121
  spotRouteTarget.spotId,
129
122
  packetName,
130
123
  message,
131
- signal,
132
- metadata,
133
- timeoutMs
124
+ metadata
134
125
  );
135
126
  return;
136
127
  }
@@ -148,22 +139,22 @@ export class ZLinkSpotRouteDispatchStrategy {
148
139
  ) as readonly Message[];
149
140
  if (
150
141
  this.targets.hasNamedSpotNode(spotRouteTarget.routerChannelId) &&
151
- (await this.spotNodeTransport.send(spotRouteTarget, parts, signal))
142
+ (await this.spotNodeTransport.send(spotRouteTarget, parts))
152
143
  ) {
153
144
  return;
154
145
  }
155
146
  if (this.bridgeTransport.has(spotRouteTarget.routerChannelId)) {
156
- await this.bridgeTransport.send(spotRouteTarget, parts, signal);
147
+ await this.bridgeTransport.send(spotRouteTarget, parts);
157
148
  return;
158
149
  }
159
150
  if (this.targets.hasBoundRouteRouter(spotRouteTarget.routerChannelId)) {
160
- await this.routerSocketTransport.send(spotRouteTarget, parts, ZLINK_SEND_DONT_WAIT, signal);
151
+ await this.routerSocketTransport.send(spotRouteTarget, parts, ZLINK_SEND_DONT_WAIT);
161
152
  return;
162
153
  }
163
- if (await this.spotNodeTransport.send(spotRouteTarget, parts, signal)) {
154
+ if (await this.spotNodeTransport.send(spotRouteTarget, parts)) {
164
155
  return;
165
156
  }
166
- await this.routerSocketTransport.send(spotRouteTarget, parts, ZLINK_SEND_DONT_WAIT, signal);
157
+ await this.routerSocketTransport.send(spotRouteTarget, parts, ZLINK_SEND_DONT_WAIT);
167
158
  }
168
159
 
169
160
  routeRequestToSpot<TReply>(
@@ -291,19 +282,9 @@ export class ZLinkSpotRouteDispatchStrategy {
291
282
  spotRouteTarget: ZLinkSpotRouteTarget,
292
283
  packetName: string | undefined,
293
284
  message: unknown,
294
- signal?: AbortSignal,
295
- metadata?: ReadonlyMap<string, string>,
296
- timeoutMs?: number
285
+ metadata?: ReadonlyMap<string, string>
297
286
  ): Promise<void> {
298
- await this.sourceSpotRouter.send(
299
- sourceSpot,
300
- spotRouteTarget,
301
- packetName,
302
- message,
303
- signal,
304
- metadata,
305
- timeoutMs
306
- );
287
+ await this.sourceSpotRouter.send(sourceSpot, spotRouteTarget, packetName, message, metadata);
307
288
  }
308
289
 
309
290
  routeRequestRawFromSpotToSpot(
@@ -52,6 +52,7 @@ export interface ZLinkSerialWorkPreparation {
52
52
  type SerialWorkRecord<T> = Omit<ZLinkSerialWorkRecord<T>, 'acceptedSequence'> & {
53
53
  acceptedSequence: bigint;
54
54
  readonly operationEnvelope: boolean;
55
+ readonly mailbox?: ZLinkSerialExecutionQueue;
55
56
  readonly preparation?: ZLinkSerialWorkPreparation;
56
57
  preparationState: 'idle' | 'pending' | 'canceling' | 'ready' | 'failed';
57
58
  preparationController?: AbortController;
@@ -70,11 +71,13 @@ export class ZLinkSerialExecutionQueue {
70
71
  private readonly application: ZLinkSerialAdmissionLane;
71
72
  private readonly lifecycle: ZLinkSerialAdmissionLane;
72
73
  private activeLifecycle?: ZLinkSerialWorkRecord<unknown>;
74
+ private activeRecord?: ZLinkSerialWorkRecord<unknown>;
75
+ /** The admitted node itself owns ready membership; no second readiness flag. */
76
+ private ownerMembership?: SerialWorkRecord<unknown>;
73
77
  private readonly ownerTimeBudget: number;
74
78
  private readonly lifecycleBurstLimit: number;
75
79
  private nextAcceptedSequence = 1n;
76
- private draining = false;
77
- private drainScheduled = false;
80
+ private readonly consumer?: { running: boolean; scheduled: boolean };
78
81
  private lifecycleStreak = 0;
79
82
  private lifecycleDebt = false;
80
83
  private claimStartedAt?: number;
@@ -83,7 +86,8 @@ export class ZLinkSerialExecutionQueue {
83
86
 
84
87
  constructor(
85
88
  private readonly executeRecord: (record: ZLinkSerialWorkRecord<unknown>) => Promise<void>,
86
- options: ZLinkSerialSchedulerOptions = {}
89
+ options: ZLinkSerialSchedulerOptions = {},
90
+ private readonly sharedOwner?: ZLinkSerialExecutionQueue
87
91
  ) {
88
92
  const configured = { ...ZLINK_DEFAULT_SERIAL_SCHEDULER_OPTIONS, ...options };
89
93
  validateNonNegative(configured.ownerTimeBudget, 'ownerTimeBudget');
@@ -92,6 +96,7 @@ export class ZLinkSerialExecutionQueue {
92
96
  this.lifecycle = createLane();
93
97
  this.ownerTimeBudget = configured.ownerTimeBudget;
94
98
  this.lifecycleBurstLimit = configured.lifecycleBurstLimit;
99
+ if (sharedOwner === undefined) this.consumer = { running: false, scheduled: false };
95
100
  }
96
101
 
97
102
  submit<T>(
@@ -162,12 +167,15 @@ export class ZLinkSerialExecutionQueue {
162
167
  }
163
168
 
164
169
  visitPendingApplication(visitor: (record: ZLinkSerialWorkRecord<unknown>) => void): void {
165
- for (const record of this.application.records) visitor(record);
170
+ for (const record of this.application.records) {
171
+ if (record.mailbox === undefined) visitor(record);
172
+ }
166
173
  }
167
174
 
168
175
  get hasPendingWork(): boolean {
169
176
  return (
170
- this.draining ||
177
+ this.consumer?.running === true ||
178
+ this.activeRecord !== undefined ||
171
179
  this.activeLifecycle !== undefined ||
172
180
  this.application.records.length > 0 ||
173
181
  this.lifecycle.records.length > 0
@@ -219,7 +227,7 @@ export class ZLinkSerialExecutionQueue {
219
227
  this.activeLifecycle = undefined;
220
228
  if (this.application.records.length > 0 || this.lifecycle.records.length > 0) {
221
229
  this.scheduleDrain();
222
- } else if (!this.draining) {
230
+ } else if (this.consumer?.running !== true) {
223
231
  this.resolveIdleWaiters();
224
232
  }
225
233
  }
@@ -266,39 +274,95 @@ export class ZLinkSerialExecutionQueue {
266
274
  }
267
275
 
268
276
  private scheduleDrain(): void {
269
- if (this.drainScheduled || this.draining) return;
270
- this.drainScheduled = true;
277
+ if (this.sharedOwner !== undefined) {
278
+ if (this.activeRecord !== undefined) return;
279
+ // Ready membership in the existing owner queue is the only notification state.
280
+ if (this.ownerMembership !== undefined) return;
281
+ this.enqueueMailboxTurn(() => this.consumeMailbox());
282
+ return;
283
+ }
284
+ const consumer = this.consumer!;
285
+ if (consumer.scheduled || consumer.running) return;
286
+ consumer.scheduled = true;
271
287
  queueMicrotask(() => {
272
- this.drainScheduled = false;
288
+ consumer.scheduled = false;
273
289
  void this.drain();
274
290
  });
275
291
  }
276
292
 
293
+ /** Notifications carry no payload, admission sequence, result Promise, or host permit. */
294
+ private enqueueMailboxTurn(operation: () => void): void {
295
+ const entry: SerialWorkRecord<unknown> = {
296
+ acceptedSequence: 0n,
297
+ lane: 'application',
298
+ mailbox: this,
299
+ operation,
300
+ operationEnvelope: false,
301
+ preparationState: 'ready',
302
+ resolve: unexpectedMailboxSettlement,
303
+ reject: unexpectedMailboxSettlement,
304
+ release: unexpectedMailboxSettlement,
305
+ fail: unexpectedMailboxSettlement
306
+ };
307
+ const owner = this.sharedOwner!;
308
+ owner.application.records.push(entry);
309
+ this.ownerMembership = entry;
310
+ owner.scheduleDrain();
311
+ }
312
+
313
+ /** Called only by the shared scheduler's consumer, never by a producer. */
314
+ private consumeMailbox(): void {
315
+ const selection = this.selectNext();
316
+ if (selection === undefined) {
317
+ if (!this.hasPendingWork) this.resolveIdleWaiters();
318
+ return;
319
+ }
320
+ const record = selection.record;
321
+ if (!this.prepareSelected(selection)) return;
322
+ this.takeSelected(selection);
323
+ if (record.preparationState === 'failed') {
324
+ record.preparation?.cancel();
325
+ record.fail(record.preparationError);
326
+ this.scheduleDrain();
327
+ return;
328
+ }
329
+ this.activeRecord = record;
330
+ // The operation can submit Spot turns, including Yield continuations. Its
331
+ // Actor claim remains held until terminal settlement returns to this gate.
332
+ let terminal: Promise<unknown>;
333
+ try {
334
+ terminal = Promise.resolve(record.operation());
335
+ } catch (error) {
336
+ terminal = Promise.reject(error);
337
+ }
338
+ void terminal.then(
339
+ (value) => this.finishMailbox(record, () => record.resolve(value)),
340
+ (error) => this.finishMailbox(record, () => record.reject(error))
341
+ );
342
+ }
343
+
344
+ private finishMailbox(record: SerialWorkRecord<unknown>, settle: () => void): void {
345
+ this.enqueueMailboxTurn(() => {
346
+ settle();
347
+ record.release();
348
+ record.preparation?.cancel();
349
+ this.activeRecord = undefined;
350
+ if (this.selectNext() !== undefined) this.scheduleDrain();
351
+ else if (!this.hasPendingWork) this.resolveIdleWaiters();
352
+ });
353
+ }
354
+
277
355
  private async drain(): Promise<void> {
278
- if (this.draining) return;
279
- this.draining = true;
356
+ const consumer = this.consumer!;
357
+ if (consumer.running) return;
358
+ consumer.running = true;
280
359
  let waitingForPreparation = false;
281
360
  try {
282
361
  for (;;) {
283
362
  const selection = this.selectNext();
284
363
  if (selection === undefined) break;
285
364
  const record = selection.record;
286
- if (selection.lane === 'lifecycle') {
287
- const applicationHead = this.application.records[0]!;
288
- if (
289
- this.application.records.length > 0 &&
290
- !this.cancelPreparationForRearbitration(applicationHead)
291
- ) {
292
- waitingForPreparation = true;
293
- break;
294
- }
295
- }
296
- if (record.preparationState === 'idle') {
297
- this.startPreparation(record);
298
- waitingForPreparation = true;
299
- break;
300
- }
301
- if (record.preparationState === 'pending' || record.preparationState === 'canceling') {
365
+ if (!this.prepareSelected(selection)) {
302
366
  waitingForPreparation = true;
303
367
  break;
304
368
  }
@@ -310,7 +374,9 @@ export class ZLinkSerialExecutionQueue {
310
374
  }
311
375
  this.claimStartedAt ??= performance.now();
312
376
  try {
313
- if (record.operationEnvelope) {
377
+ if (record.mailbox !== undefined) {
378
+ record.operation();
379
+ } else if (record.operationEnvelope) {
314
380
  try {
315
381
  const outcome = record.operation();
316
382
  void Promise.resolve(outcome).then(
@@ -330,6 +396,7 @@ export class ZLinkSerialExecutionQueue {
330
396
  await this.executeRecord(record);
331
397
  }
332
398
  } catch (error) {
399
+ if (record.mailbox !== undefined) throw error;
333
400
  record.fail(error);
334
401
  } finally {
335
402
  record.preparation?.cancel();
@@ -340,7 +407,7 @@ export class ZLinkSerialExecutionQueue {
340
407
  }
341
408
  }
342
409
  } finally {
343
- this.draining = false;
410
+ consumer.running = false;
344
411
  this.claimStartedAt = undefined;
345
412
  if (!waitingForPreparation && this.selectNext() !== undefined) {
346
413
  this.scheduleDrain();
@@ -352,6 +419,21 @@ export class ZLinkSerialExecutionQueue {
352
419
  }
353
420
  }
354
421
 
422
+ private prepareSelected(selection: {
423
+ readonly lane: ZLinkSerialWorkLane;
424
+ readonly record: SerialWorkRecord<unknown>;
425
+ }): boolean {
426
+ if (
427
+ selection.lane === 'lifecycle' &&
428
+ this.application.records.length > 0 &&
429
+ !this.cancelPreparationForRearbitration(this.application.records[0]!)
430
+ )
431
+ return false;
432
+ const record = selection.record;
433
+ if (record.preparationState === 'idle') this.startPreparation(record);
434
+ return record.preparationState !== 'pending' && record.preparationState !== 'canceling';
435
+ }
436
+
355
437
  private selectNext():
356
438
  | {
357
439
  readonly lane: ZLinkSerialWorkLane;
@@ -389,6 +471,8 @@ export class ZLinkSerialExecutionQueue {
389
471
  if (record !== selection.record) {
390
472
  throw new Error('The selected serial work record changed before owner claim.');
391
473
  }
474
+ if (selection.record.mailbox !== undefined)
475
+ selection.record.mailbox.ownerMembership = undefined;
392
476
  return record;
393
477
  }
394
478
 
@@ -565,6 +649,10 @@ function createLane(): ZLinkSerialAdmissionLane {
565
649
  };
566
650
  }
567
651
 
652
+ function unexpectedMailboxSettlement(): never {
653
+ throw new Error('A mailbox notification has no independent terminal result.');
654
+ }
655
+
568
656
  function validatePositive(value: number, field: string): void {
569
657
  if (!Number.isSafeInteger(value) || value < 1) {
570
658
  throw new RangeError(`${field} must be a positive safe integer.`);
@@ -24,7 +24,8 @@ export interface PendingOperation<T> {
24
24
  readonly promise: Promise<T>;
25
25
  }
26
26
 
27
- interface Entry<T> {
27
+ interface Entry<T, Context> {
28
+ readonly context: Context | undefined;
28
29
  readonly deadlineMs?: number;
29
30
  readonly resolve: (value: T | PromiseLike<T>) => void;
30
31
  readonly reject: (reason: unknown) => void;
@@ -35,10 +36,10 @@ const systemClock: OperationClock = {
35
36
  };
36
37
 
37
38
  /** Owns request completion so reply, timeout, cancellation, and shutdown race safely. */
38
- export class OperationRegistry<T> {
39
- private readonly entries = new Map<bigint, Entry<T>>();
39
+ export class OperationRegistry<T, Context = unknown> {
40
+ private readonly entries = new Map<bigint, Entry<T, Context>>();
40
41
  private nextId = 1n;
41
- private deadlineCursor?: MapIterator<[bigint, Entry<T>]>;
42
+ private deadlineCursor?: MapIterator<[bigint, Entry<T, Context>]>;
42
43
  private closed = false;
43
44
 
44
45
  constructor(
@@ -48,20 +49,22 @@ export class OperationRegistry<T> {
48
49
 
49
50
  register(
50
51
  timeoutMs: number,
51
- timeoutOwner: 'registry' | 'sender' = 'registry'
52
+ timeoutOwner: 'registry' | 'sender' = 'registry',
53
+ context?: Context
52
54
  ): PendingOperation<T> {
53
55
  if (this.closed) throw new Error('Operation registry is closed.');
54
56
  if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
55
57
  throw new RangeError('timeoutMs must be a non-negative finite number.');
56
58
  }
57
59
  const id = this.nextId++;
58
- let resolve!: Entry<T>['resolve'];
59
- let reject!: Entry<T>['reject'];
60
+ let resolve!: Entry<T, Context>['resolve'];
61
+ let reject!: Entry<T, Context>['reject'];
60
62
  const promise = new Promise<T>((onResolve, onReject) => {
61
63
  resolve = onResolve;
62
64
  reject = onReject;
63
65
  });
64
- const entry: Entry<T> = {
66
+ const entry: Entry<T, Context> = {
67
+ context,
65
68
  deadlineMs: timeoutOwner === 'registry' ? this.clock.now() + timeoutMs : undefined,
66
69
  resolve,
67
70
  reject
@@ -93,6 +96,10 @@ export class OperationRegistry<T> {
93
96
  return this.entries.has(id);
94
97
  }
95
98
 
99
+ context(id: bigint): Context | undefined {
100
+ return this.entries.get(id)?.context;
101
+ }
102
+
96
103
  close(reason = 'Operation registry closed.'): void {
97
104
  if (this.closed) return;
98
105
  this.closed = true;
@@ -121,7 +128,7 @@ export class OperationRegistry<T> {
121
128
  return expired;
122
129
  }
123
130
 
124
- private take(id: bigint): Entry<T> | undefined {
131
+ private take(id: bigint): Entry<T, Context> | undefined {
125
132
  const entry = this.entries.get(id);
126
133
  if (entry === undefined) return undefined;
127
134
  this.entries.delete(id);
@@ -10,7 +10,8 @@ import type {
10
10
  ZLinkRawReceivedRecord,
11
11
  ZLinkRawRouterPort
12
12
  } from '../backend/raw-binding-port';
13
- import { RequestResult, SubmitResult } from '../backend/runtime-values';
13
+ import { submitToRequestResult } from '../messaging/submission-result';
14
+ import { RequestResult, SubmitResult, isZLinkBackendResultError } from '../backend/runtime-values';
14
15
  import { enumWireRejectReason } from '../protocol/service_wire_codec.generated';
15
16
  import { OperationRegistry, type PendingOperation } from './operation-registry';
16
17
  import { ServiceLivenessRegistry, type ServiceLivenessTick } from './service-liveness-registry';
@@ -830,17 +831,14 @@ export class RawServiceMeshRuntime {
830
831
  peer.connectionId,
831
832
  record.probeId
832
833
  );
833
- const sent =
834
- ack !== undefined &&
835
- (await this.send(received.sourceRid, [
836
- livenessCodec.encodeLivenessRecord({
837
- command: M6aServiceWireCommand.livenessAck,
838
- probeId: record.probeId
839
- })
840
- ]));
841
- if (!sent) {
842
- return 'protocolError';
843
- }
834
+ if (ack === undefined) return 'protocolError';
835
+ const sent = await this.send(received.sourceRid, [
836
+ livenessCodec.encodeLivenessRecord({
837
+ command: M6aServiceWireCommand.livenessAck,
838
+ probeId: record.probeId
839
+ })
840
+ ]);
841
+ if (!sent) return 'dropped';
844
842
  } else {
845
843
  this.liveness.acknowledge(received.sourceRid, peer.connectionId, record.probeId, nowMs);
846
844
  }
@@ -1263,8 +1261,13 @@ export class RawServiceMeshRuntime {
1263
1261
  try {
1264
1262
  await this.requireStarted().send(targetNodeRoutingId, parts);
1265
1263
  return true;
1266
- } catch {
1267
- return false;
1264
+ } catch (error) {
1265
+ if (isZLinkBackendResultError(error) && error.operation === 'submit') {
1266
+ const terminal = submitToRequestResult(error.result, error.phase);
1267
+ if (terminal === RequestResult.NotConnected || terminal === RequestResult.NotFound)
1268
+ return false;
1269
+ }
1270
+ throw error;
1268
1271
  }
1269
1272
  }
1270
1273
  }
@@ -62,52 +62,6 @@ export interface ServiceUserSpotAuthorityPayload {
62
62
  readonly ownerNodeGeneration: bigint;
63
63
  }
64
64
 
65
- /** Replaces only authority-relocation-state and preserves the canonical owner payload. */
66
- export function replaceServiceAuthorityRelocationState(
67
- payload: Uint8Array,
68
- relocationState: Uint8Array
69
- ): Buffer {
70
- const bytes = Buffer.from(payload);
71
- const reader = new AuthorityReader(bytes);
72
- reader.expect(AUTHORITY_MAGIC);
73
- if (reader.u8() !== AUTHORITY_FORMAT_VERSION || reader.u16() !== AUTHORITY_FLAGS) {
74
- throw new TypeError('Authority payload header is invalid.');
75
- }
76
- const bodyLength = reader.u32();
77
- const body = reader.takeReader(bodyLength);
78
- const checksumOffset = reader.offset;
79
- const checksum = reader.u32();
80
- if (!reader.done || crc32c(bytes.subarray(0, checksumOffset)) !== checksum) {
81
- throw new TypeError('Authority payload checksum is invalid.');
82
- }
83
- body.u8();
84
- body.u8();
85
- body.skip(body.u16());
86
- body.text8();
87
- body.nonZeroU64();
88
- body.text8();
89
- body.rid();
90
- body.nonZeroU64();
91
- const relocationOffset = body.offset;
92
- body.bool8();
93
- body.skip(body.u32());
94
- const relocationEnd = body.offset;
95
- const currentBody = bytes.subarray(11, 11 + bodyLength);
96
- const nextBody = Buffer.concat([
97
- currentBody.subarray(0, relocationOffset),
98
- Buffer.from(relocationState),
99
- currentBody.subarray(relocationEnd)
100
- ]);
101
- const envelope = Buffer.concat([
102
- AUTHORITY_MAGIC,
103
- Buffer.of(AUTHORITY_FORMAT_VERSION),
104
- u16(AUTHORITY_FLAGS),
105
- u32(nextBody.byteLength),
106
- nextBody
107
- ]);
108
- return Buffer.concat([envelope, u32(crc32c(envelope))]);
109
- }
110
-
111
65
  export function encodeServiceUserSpotAuthorityPayload(
112
66
  value: ServiceUserSpotAuthorityPayload
113
67
  ): Buffer {
@@ -621,18 +621,19 @@ function createSpotState(
621
621
  }
622
622
  }
623
623
 
624
- export class ServiceTerminalOperationRegistry<T> {
625
- private readonly operations: OperationRegistry<T>;
624
+ export class ServiceTerminalOperationRegistry<T, Context = unknown> {
625
+ private readonly operations: OperationRegistry<T, Context>;
626
626
 
627
- constructor(operations = new OperationRegistry<T>()) {
627
+ constructor(operations = new OperationRegistry<T, Context>()) {
628
628
  this.operations = operations;
629
629
  }
630
630
 
631
631
  register(
632
632
  timeoutMs: number,
633
- timeoutOwner: 'registry' | 'sender' = 'registry'
633
+ timeoutOwner: 'registry' | 'sender' = 'registry',
634
+ context?: Context
634
635
  ): PendingOperation<T> {
635
- return this.operations.register(timeoutMs, timeoutOwner);
636
+ return this.operations.register(timeoutMs, timeoutOwner, context);
636
637
  }
637
638
 
638
639
  reply(id: bigint, value: T): boolean {
@@ -651,6 +652,10 @@ export class ServiceTerminalOperationRegistry<T> {
651
652
  return this.operations.isPending(id);
652
653
  }
653
654
 
655
+ context(id: bigint): Context | undefined {
656
+ return this.operations.context(id);
657
+ }
658
+
654
659
  get size(): number {
655
660
  return this.operations.size;
656
661
  }