@zlink-systems/framework 0.24.0 → 0.26.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 (215) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Channels/IZLinkFanoutClient.d.ts +0 -6
  3. package/dist/contracts/RouteMesh/RuntimeTopology.d.ts +8 -0
  4. package/dist/internal.d.ts +1 -0
  5. package/dist/internal.js +1 -0
  6. package/dist/runtime/activation-admission.d.ts +29 -0
  7. package/dist/runtime/activation-admission.js +61 -0
  8. package/dist/runtime/actors/actor-context.js +6 -6
  9. package/dist/runtime/actors/actor-handoff.d.ts +9 -3
  10. package/dist/runtime/actors/actor-handoff.js +28 -17
  11. package/dist/runtime/actors/actor-local-native-join.js +2 -18
  12. package/dist/runtime/actors/actor-remote-joiner.d.ts +2 -2
  13. package/dist/runtime/actors/actor-remote-joiner.js +6 -8
  14. package/dist/runtime/actors/actor-runtime-contracts.d.ts +4 -2
  15. package/dist/runtime/actors/actor-source-transfer.d.ts +5 -5
  16. package/dist/runtime/actors/deferred-join-completion.d.ts +14 -0
  17. package/dist/runtime/actors/deferred-join-completion.js +20 -0
  18. package/dist/runtime/actors/index.d.ts +1 -1
  19. package/dist/runtime/actors/index.js +15 -5
  20. package/dist/runtime/admission.d.ts +5 -1
  21. package/dist/runtime/admission.js +4 -0
  22. package/dist/runtime/backend/contracts/index.d.ts +2 -10
  23. package/dist/runtime/backend/mesh-actor-session-node-adapter.js +2 -9
  24. package/dist/runtime/backend/node/node-backend-adapter-factory.d.ts +1 -2
  25. package/dist/runtime/backend/node/node-backend-adapter-factory.js +4 -18
  26. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +6 -3
  27. package/dist/runtime/backend/node/node-backend-adapter-support.js +12 -28
  28. package/dist/runtime/backend/node/node-event-loop-poller.d.ts +1 -1
  29. package/dist/runtime/backend/node/node-event-loop-poller.js +8 -2
  30. package/dist/runtime/backend/node/node-raw-binding-port.d.ts +1 -1
  31. package/dist/runtime/backend/node/node-raw-binding-port.js +11 -3
  32. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +12 -9
  33. package/dist/runtime/backend/node/node-raw-mesh-backend.js +61 -29
  34. package/dist/runtime/backend/node/node-socket-backend-adapter.js +1 -1
  35. package/dist/runtime/backend/raw-binding-port.d.ts +12 -0
  36. package/dist/runtime/channels/channel-clients.d.ts +0 -1
  37. package/dist/runtime/channels/channel-clients.js +0 -8
  38. package/dist/runtime/channels/channel-outbound-operations.js +2 -10
  39. package/dist/runtime/channels/channel-runtime-manager.d.ts +2 -5
  40. package/dist/runtime/channels/channel-runtime-manager.js +1 -12
  41. package/dist/runtime/channels/channel-socket-registry.d.ts +17 -4
  42. package/dist/runtime/channels/channel-socket-registry.js +49 -62
  43. package/dist/runtime/channels/channel-transports.d.ts +0 -3
  44. package/dist/runtime/channels/fanout-location-runtime.js +3 -9
  45. package/dist/runtime/execution/index.d.ts +18 -8
  46. package/dist/runtime/execution/index.js +55 -29
  47. package/dist/runtime/execution/serial-execution-queue.d.ts +5 -6
  48. package/dist/runtime/execution/serial-execution-queue.js +49 -15
  49. package/dist/runtime/foundation/listener-records.d.ts +11 -0
  50. package/dist/runtime/foundation/listener-records.js +20 -0
  51. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +10 -21
  52. package/dist/runtime/foundation/raw-service-mesh-runtime.js +66 -392
  53. package/dist/runtime/foundation/service-authority-payload-codec.d.ts +1 -0
  54. package/dist/runtime/foundation/service-authority-payload-codec.js +8 -0
  55. package/dist/runtime/foundation/service-maintenance-runtime.js +23 -2
  56. package/dist/runtime/foundation/service-relocation-coordinator.d.ts +9 -0
  57. package/dist/runtime/foundation/service-relocation-coordinator.js +14 -1
  58. package/dist/runtime/foundation/service-relocation-object-owner.d.ts +8 -0
  59. package/dist/runtime/foundation/service-relocation-object-owner.js +16 -4
  60. package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +6 -13
  61. package/dist/runtime/foundation/service-stateful-runtime.d.ts +10 -7
  62. package/dist/runtime/foundation/service-stateful-runtime.js +100 -60
  63. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +9 -4
  64. package/dist/runtime/foundation/service-stateful-wire-codec.js +153 -90
  65. package/dist/runtime/foundation/service-topology-registry.d.ts +1 -2
  66. package/dist/runtime/foundation/service-topology-registry.js +6 -30
  67. package/dist/runtime/host/actor-transfer-runtime.d.ts +17 -15
  68. package/dist/runtime/host/actor-transfer-runtime.js +73 -86
  69. package/dist/runtime/host/index.d.ts +6 -2
  70. package/dist/runtime/host/index.js +63 -37
  71. package/dist/runtime/host/remote-bound-session-relay.js +4 -3
  72. package/dist/runtime/host/route-mesh-runtime.d.ts +2 -0
  73. package/dist/runtime/host/route-mesh-runtime.js +7 -5
  74. package/dist/runtime/host/service-relocation-host-runtime.d.ts +50 -8
  75. package/dist/runtime/host/service-relocation-host-runtime.js +343 -270
  76. package/dist/runtime/host/spot-runtime-options-factory.d.ts +7 -4
  77. package/dist/runtime/host/spot-runtime-options-factory.js +4 -8
  78. package/dist/runtime/host/stateful-authority-route-runtime.js +6 -2
  79. package/dist/runtime/host/user-spot-creation-coordinator.d.ts +11 -3
  80. package/dist/runtime/host/user-spot-creation-coordinator.js +85 -87
  81. package/dist/runtime/locations/in-memory-provider-location-store.js +8 -6
  82. package/dist/runtime/locations/lifecycle.d.ts +1 -1
  83. package/dist/runtime/locations/lifecycle.js +2 -2
  84. package/dist/runtime/locations/resolvers.js +7 -2
  85. package/dist/runtime/locations/spot-location-claims.d.ts +2 -2
  86. package/dist/runtime/locations/spot-location-claims.js +3 -27
  87. package/dist/runtime/protocol/service_wire_codec.generated.d.ts +1 -0
  88. package/dist/runtime/protocol/service_wire_codec.generated.js +5 -1
  89. package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +1 -0
  90. package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +3 -2
  91. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +3 -3
  92. package/dist/runtime/spots/formal-remote-actor-admission-registry.js +4 -4
  93. package/dist/runtime/spots/formal-remote-actor-transfer-registry.d.ts +3 -3
  94. package/dist/runtime/spots/index.d.ts +25 -19
  95. package/dist/runtime/spots/index.js +207 -205
  96. package/dist/runtime/spots/spot-activation-registry.d.ts +4 -2
  97. package/dist/runtime/spots/spot-activation-registry.js +34 -31
  98. package/dist/runtime/spots/spot-activation-state.d.ts +2 -11
  99. package/dist/runtime/spots/spot-activation-state.js +4 -17
  100. package/dist/runtime/spots/spot-activation.d.ts +8 -6
  101. package/dist/runtime/spots/spot-activation.js +54 -49
  102. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +1 -1
  103. package/dist/runtime/spots/spot-actor-admission-coordinator.js +1 -1
  104. package/dist/runtime/spots/spot-actor-join-dispatch.d.ts +2 -2
  105. package/dist/runtime/spots/spot-actor-join-dispatch.js +3 -5
  106. package/dist/runtime/spots/spot-actor-lifecycle-drain.js +2 -2
  107. package/dist/runtime/spots/spot-actor-membership.d.ts +4 -0
  108. package/dist/runtime/spots/spot-actor-membership.js +26 -12
  109. package/dist/runtime/spots/spot-entry-activation.d.ts +1 -2
  110. package/dist/runtime/spots/spot-entry-activation.js +0 -1
  111. package/dist/runtime/spots/spot-manager-public.js +1 -1
  112. package/dist/runtime/spots/spot-native-actor-join-admission.d.ts +1 -1
  113. package/dist/runtime/spots/spot-native-actor-join-admission.js +2 -2
  114. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +5 -5
  115. package/dist/runtime/spots/spot-node-runtime-manager.js +7 -11
  116. package/dist/runtime/spots/spot-outbound.js +1 -29
  117. package/dist/runtime/spots/spot-routed-actor-admission.d.ts +1 -0
  118. package/dist/runtime/spots/spot-routed-actor-admission.js +10 -0
  119. package/dist/runtime/spots/spot-routed-frame-dispatch.d.ts +2 -6
  120. package/dist/runtime/spots/spot-routed-frame-dispatch.js +2 -70
  121. package/dist/runtime/spots/spot-runtime-ports.d.ts +3 -5
  122. package/dist/runtime/spots/spot-serial-executor.d.ts +5 -0
  123. package/dist/runtime/spots/spot-serial-executor.js +8 -1
  124. package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
  125. package/dist/runtime/spots/spot-serial-turn-executor.js +8 -4
  126. package/dist/runtime/streams/actor-session-binding-registry.d.ts +13 -15
  127. package/dist/runtime/streams/actor-session-binding-registry.js +19 -171
  128. package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +3 -4
  129. package/dist/runtime/streams/bound-session-service.js +1 -18
  130. package/dist/runtime/streams/index.d.ts +2 -0
  131. package/dist/runtime/streams/index.js +19 -19
  132. package/dist/runtime/streams/session-serial-executor.js +2 -2
  133. package/package.json +3 -3
  134. package/src/contracts/Channels/IZLinkFanoutClient.ts +0 -7
  135. package/src/contracts/RouteMesh/RuntimeTopology.ts +10 -0
  136. package/src/internal.ts +1 -0
  137. package/src/runtime/activation-admission.ts +78 -0
  138. package/src/runtime/actors/actor-context.ts +6 -6
  139. package/src/runtime/actors/actor-handoff.ts +31 -23
  140. package/src/runtime/actors/actor-local-native-join.ts +3 -42
  141. package/src/runtime/actors/actor-remote-joiner.ts +6 -25
  142. package/src/runtime/actors/actor-runtime-contracts.ts +4 -10
  143. package/src/runtime/actors/actor-source-transfer.ts +5 -18
  144. package/src/runtime/actors/deferred-join-completion.ts +40 -0
  145. package/src/runtime/actors/index.ts +22 -9
  146. package/src/runtime/admission.ts +5 -1
  147. package/src/runtime/backend/contracts/index.ts +2 -13
  148. package/src/runtime/backend/mesh-actor-session-node-adapter.ts +2 -10
  149. package/src/runtime/backend/node/node-backend-adapter-factory.ts +5 -25
  150. package/src/runtime/backend/node/node-backend-adapter-support.ts +9 -29
  151. package/src/runtime/backend/node/node-event-loop-poller.ts +14 -2
  152. package/src/runtime/backend/node/node-raw-binding-port.ts +17 -3
  153. package/src/runtime/backend/node/node-raw-mesh-backend.ts +65 -32
  154. package/src/runtime/backend/node/node-socket-backend-adapter.ts +2 -2
  155. package/src/runtime/backend/raw-binding-port.ts +13 -0
  156. package/src/runtime/channels/channel-clients.ts +0 -11
  157. package/src/runtime/channels/channel-outbound-operations.ts +3 -11
  158. package/src/runtime/channels/channel-runtime-manager.ts +4 -15
  159. package/src/runtime/channels/channel-socket-registry.ts +69 -71
  160. package/src/runtime/channels/channel-transports.ts +0 -3
  161. package/src/runtime/channels/fanout-location-runtime.ts +4 -16
  162. package/src/runtime/execution/index.ts +68 -30
  163. package/src/runtime/execution/serial-execution-queue.ts +52 -20
  164. package/src/runtime/foundation/listener-records.ts +21 -0
  165. package/src/runtime/foundation/raw-service-mesh-runtime.ts +66 -466
  166. package/src/runtime/foundation/service-authority-payload-codec.ts +10 -0
  167. package/src/runtime/foundation/service-maintenance-runtime.ts +30 -9
  168. package/src/runtime/foundation/service-relocation-coordinator.ts +13 -0
  169. package/src/runtime/foundation/service-relocation-object-owner.ts +18 -5
  170. package/src/runtime/foundation/service-session-binding-ingress-port.ts +6 -14
  171. package/src/runtime/foundation/service-stateful-runtime.ts +151 -89
  172. package/src/runtime/foundation/service-stateful-wire-codec.ts +203 -121
  173. package/src/runtime/foundation/service-topology-registry.ts +8 -48
  174. package/src/runtime/host/actor-transfer-runtime.ts +94 -166
  175. package/src/runtime/host/index.ts +119 -54
  176. package/src/runtime/host/remote-bound-session-relay.ts +4 -3
  177. package/src/runtime/host/route-mesh-runtime.ts +9 -5
  178. package/src/runtime/host/service-relocation-host-runtime.ts +495 -376
  179. package/src/runtime/host/spot-runtime-options-factory.ts +24 -13
  180. package/src/runtime/host/stateful-authority-route-runtime.ts +8 -4
  181. package/src/runtime/host/user-spot-creation-coordinator.ts +147 -142
  182. package/src/runtime/locations/in-memory-provider-location-store.ts +11 -7
  183. package/src/runtime/locations/lifecycle.ts +3 -2
  184. package/src/runtime/locations/resolvers.ts +19 -13
  185. package/src/runtime/locations/spot-location-claims.ts +5 -34
  186. package/src/runtime/protocol/service_wire_codec.generated.ts +9 -2
  187. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +4 -4
  188. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +6 -6
  189. package/src/runtime/spots/formal-remote-actor-transfer-registry.ts +3 -3
  190. package/src/runtime/spots/index.ts +341 -295
  191. package/src/runtime/spots/spot-activation-registry.ts +41 -31
  192. package/src/runtime/spots/spot-activation-state.ts +4 -16
  193. package/src/runtime/spots/spot-activation.ts +102 -80
  194. package/src/runtime/spots/spot-actor-admission-coordinator.ts +2 -2
  195. package/src/runtime/spots/spot-actor-join-dispatch.ts +6 -7
  196. package/src/runtime/spots/spot-actor-lifecycle-drain.ts +20 -18
  197. package/src/runtime/spots/spot-actor-membership.ts +81 -17
  198. package/src/runtime/spots/spot-entry-activation.ts +0 -3
  199. package/src/runtime/spots/spot-manager-public.ts +8 -4
  200. package/src/runtime/spots/spot-native-actor-join-admission.ts +2 -2
  201. package/src/runtime/spots/spot-node-runtime-manager.ts +12 -18
  202. package/src/runtime/spots/spot-outbound.ts +4 -47
  203. package/src/runtime/spots/spot-routed-actor-admission.ts +20 -0
  204. package/src/runtime/spots/spot-routed-frame-dispatch.ts +6 -76
  205. package/src/runtime/spots/spot-runtime-ports.ts +6 -17
  206. package/src/runtime/spots/spot-serial-executor.ts +9 -1
  207. package/src/runtime/spots/spot-serial-turn-executor.ts +10 -4
  208. package/src/runtime/streams/actor-session-binding-registry.ts +20 -308
  209. package/src/runtime/streams/actor-session-binding-runtime-owner.ts +2 -12
  210. package/src/runtime/streams/bound-session-service.ts +2 -36
  211. package/src/runtime/streams/index.ts +29 -27
  212. package/src/runtime/streams/session-serial-executor.ts +2 -2
  213. package/dist/runtime/actors/deferred-join-accepted-journal.d.ts +0 -47
  214. package/dist/runtime/actors/deferred-join-accepted-journal.js +0 -992
  215. package/src/runtime/actors/deferred-join-accepted-journal.ts +0 -1346
@@ -63,10 +63,6 @@ export function isRouteRecvRetryable(error: unknown): boolean {
63
63
  );
64
64
  }
65
65
 
66
- export function isPollerInterruptedError(error: unknown): boolean {
67
- return error instanceof zlink.RecvError && error.nativeErrno === 4;
68
- }
69
-
70
66
  function isNativeBadAddress(error: { nativeErrno?: unknown; message?: unknown }): boolean {
71
67
  return error.nativeErrno === 14 || /Bad address/i.test(String(error.message ?? ''));
72
68
  }
@@ -176,32 +172,16 @@ export async function submitBindingRequest(
176
172
  }
177
173
  }
178
174
 
179
- export async function closeWithBusyRetry(target: { close(): void }): Promise<void> {
180
- let lastError: unknown;
181
- for (let attempt = 0; attempt < 8; attempt++) {
182
- try {
183
- target.close();
184
- return;
185
- } catch (error) {
186
- if (isSuccessfulOrAlreadyShutdownCloseError(error)) {
187
- return;
188
- }
189
- if (!isBusyCloseError(error)) {
190
- throw error;
191
- }
192
- lastError = error;
193
- await new Promise<void>((resolve) => setImmediate(resolve));
194
- }
175
+ /**
176
+ * Closes a binding handle once. An already closed or terminated handle counts as
177
+ * closed. A busy close propagates to the caller.
178
+ */
179
+ export function closeBindingHandle(target: { close(): void }): void {
180
+ try {
181
+ target.close();
182
+ } catch (error) {
183
+ if (!isSuccessfulOrAlreadyShutdownCloseError(error)) throw error;
195
184
  }
196
- throw lastError;
197
- }
198
-
199
- function isBusyCloseError(error: unknown): boolean {
200
- return (
201
- error instanceof Error &&
202
- 'code' in error &&
203
- [401, 404].includes((error as { code: number }).code)
204
- );
205
185
  }
206
186
 
207
187
  function isSuccessfulOrAlreadyShutdownCloseError(error: unknown): boolean {
@@ -12,11 +12,19 @@ export class ZLinkNodeEventLoopPoller {
12
12
  private readableHandler: () => void;
13
13
  private disposed = false;
14
14
 
15
- constructor(socket: Socket, pollCompletion: boolean, readableHandler: () => void) {
15
+ constructor(
16
+ socket: Socket,
17
+ pollCompletion: boolean,
18
+ readableHandler: () => void,
19
+ pollRoute = false
20
+ ) {
16
21
  this.readableHandler = readableHandler;
17
22
  try {
18
23
  const pollEvents: PollEventFlagValue[] = [zlink.PollEventFlag.PollIn];
19
24
  if (pollCompletion) pollEvents.push(zlink.PollEventFlag.PollCompletion);
25
+ // A ROUTER selected-route change (Core ROUTER §10.1) wakes the same
26
+ // handler, whose owner is the socket's one route observer.
27
+ if (pollRoute) pollEvents.push(zlink.PollEventFlag.PollRoute);
20
28
  this.poller.add(socket as BaseSocket, pollEvents, 0);
21
29
  socket.setReadableHandler(this.onReady);
22
30
  } catch (error) {
@@ -40,7 +48,11 @@ export class ZLinkNodeEventLoopPoller {
40
48
  private readonly onReady = (): void => {
41
49
  if (this.disposed) return;
42
50
  const ready = this.poller.wait(this.events, 0);
43
- if (ready === 0 || this.events.hasEvent(0, zlink.PollEventFlag.PollIn)) {
51
+ if (
52
+ ready === 0 ||
53
+ this.events.hasEvent(0, zlink.PollEventFlag.PollIn) ||
54
+ this.events.hasEvent(0, zlink.PollEventFlag.PollRoute)
55
+ ) {
44
56
  this.readableHandler();
45
57
  }
46
58
  };
@@ -33,6 +33,7 @@ import type {
33
33
  ZLinkRawMonitorRecord,
34
34
  ZLinkRawReceivedRecord,
35
35
  ZLinkRawRouterPort,
36
+ ZLinkRawRouterRoute,
36
37
  ZLinkRawSocketPort
37
38
  } from '../raw-binding-port';
38
39
  export type {
@@ -43,6 +44,7 @@ export type {
43
44
  ZLinkRawMonitorRecord,
44
45
  ZLinkRawReceivedRecord,
45
46
  ZLinkRawRouterPort,
47
+ ZLinkRawRouterRoute,
46
48
  ZLinkRawSocketPort
47
49
  } from '../raw-binding-port';
48
50
 
@@ -131,8 +133,11 @@ abstract class NodeRawSocketPort<TSocket extends Socket> implements ZLinkRawSock
131
133
  private readonly eventLoopPoller: ZLinkNodeEventLoopPoller;
132
134
  private closed = false;
133
135
 
134
- protected constructor(protected readonly socket: TSocket) {
135
- this.eventLoopPoller = new ZLinkNodeEventLoopPoller(socket, true, () => {});
136
+ protected constructor(
137
+ protected readonly socket: TSocket,
138
+ pollRoute = false
139
+ ) {
140
+ this.eventLoopPoller = new ZLinkNodeEventLoopPoller(socket, true, () => {}, pollRoute);
136
141
  }
137
142
 
138
143
  setReadableHandler(handler: () => void): void {
@@ -236,7 +241,7 @@ class NodeRawRouterPort extends NodeRawSocketPort<RouterSocket> implements ZLink
236
241
  }
237
242
 
238
243
  constructor(socket: RouterSocket, receiveTimeoutMs?: number) {
239
- super(socket);
244
+ super(socket, true);
240
245
  socket.options.handover = true;
241
246
  socket.options.mandatory = true;
242
247
  socket.options.probe = true;
@@ -291,6 +296,14 @@ class NodeRawRouterPort extends NodeRawSocketPort<RouterSocket> implements ZLink
291
296
  }
292
297
  }
293
298
 
299
+ routesSnapshot(): readonly ZLinkRawRouterRoute[] {
300
+ this.requireOpen();
301
+ return this.socket.routesSnapshot().map((route) => ({
302
+ routingId: route.routingId.toString(),
303
+ routeGeneration: route.routeGeneration
304
+ }));
305
+ }
306
+
294
307
  receive(dontWait = false): ZLinkRawReceivedRecord | undefined {
295
308
  this.requireOpen();
296
309
  return receiveRecord(
@@ -450,6 +463,7 @@ function receiveRecord(
450
463
  return {
451
464
  sourceRid,
452
465
  sourceRoute,
466
+ routeGeneration: received.routeGeneration,
453
467
  ...(requestSeq === undefined ? {} : { requestSeq }),
454
468
  ...(receivedReply === undefined
455
469
  ? {}
@@ -7,6 +7,7 @@ import {
7
7
  encodeStreamWireHeader
8
8
  } from '@zlink-systems/stream-wire';
9
9
  import { disconnectStreamPeer } from './node-socket-backend-adapter';
10
+ import { translateBindingResultError } from './node-backend-adapter-support';
10
11
  import { ZLinkFrameworkException } from '../../../contracts';
11
12
  import { internalFrameworkWireReply } from '../../framework-errors-internal';
12
13
  import {
@@ -105,7 +106,6 @@ import { buildAdvertisedEndpoint } from '../../../contracts/Configuration/Endpoi
105
106
  import { ZLinkConfigurationException } from '../../../contracts/Configuration/ConfigurationException';
106
107
  import type {
107
108
  ZLinkBackendActorRef,
108
- ZLinkBackendActorSessionSendFence,
109
109
  ZLinkBackendObjectPlacement,
110
110
  ZLinkBackendMeshNode
111
111
  } from '../contracts';
@@ -116,7 +116,7 @@ const MULTIPART_PACKET_NAME = SERVICE_FRAMEWORK_MULTIPART_PACKET_NAME;
116
116
  const MULTIPART_CONTENT_TYPE = SERVICE_FRAMEWORK_MULTIPART_CONTENT_TYPE;
117
117
  const FATAL_UTF8 = new TextDecoder('utf-8', { fatal: true });
118
118
  const MAX_DRAIN_RECORDS = 64;
119
- // Preserve the existing monitor/admission/liveness cadence. Only the binding
119
+ // Preserve the existing route observation/admission/liveness cadence. Only the binding
120
120
  // readable handler admits receive work; this timer never probes the socket.
121
121
  const MESH_BACKEND_MAINTENANCE_INTERVAL_MS = 1;
122
122
  /**
@@ -186,6 +186,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
186
186
  record: import('../../foundation/service-stateful-wire-codec').ServiceMessageFollowRecord
187
187
  ) => void;
188
188
  private dispatchErrors?: ZLinkDispatchErrorReporter;
189
+ private spotAdmissionProvider?: Parameters<ServiceStatefulRuntime['setSpotAdmissionProvider']>[0];
189
190
  private readonly peerDisconnectedHandlers = new Set<(endpoint: string) => void>();
190
191
  constructor(
191
192
  private readonly meshName: string,
@@ -241,6 +242,13 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
241
242
  this.stateful?.setDispatchErrorReporter(reporter, this.meshName);
242
243
  }
243
244
 
245
+ setSpotAdmissionProvider(
246
+ provider: Parameters<ServiceStatefulRuntime['setSpotAdmissionProvider']>[0]
247
+ ): void {
248
+ this.spotAdmissionProvider = provider;
249
+ this.stateful?.setSpotAdmissionProvider(provider);
250
+ }
251
+
244
252
  setProtocolErrorHandler(
245
253
  handler: (record: {
246
254
  readonly sourceNodeRid: string;
@@ -394,6 +402,9 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
394
402
  descriptor.nodeRoutingId,
395
403
  descriptor.lifecycleGeneration
396
404
  );
405
+ if (this.spotAdmissionProvider !== undefined) {
406
+ this.stateful.setSpotAdmissionProvider(this.spotAdmissionProvider);
407
+ }
397
408
  if (this.dispatchErrors !== undefined) {
398
409
  this.stateful.setDispatchErrorReporter(this.dispatchErrors, this.meshName);
399
410
  }
@@ -1327,14 +1338,12 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
1327
1338
  actor: ZLinkBackendActorRef,
1328
1339
  expectedBindingGeneration: bigint,
1329
1340
  parts: MessageLike | readonly MessageLike[],
1330
- _flags?: number,
1331
- actorFence?: ZLinkBackendActorSessionSendFence
1341
+ _flags?: number
1332
1342
  ): Promise<SubmitResultValue> {
1333
1343
  return (await this.requireStateful().sendBoundSession(
1334
1344
  actor,
1335
1345
  expectedBindingGeneration,
1336
- encodeMultipart(parts),
1337
- actorFence
1346
+ encodeMultipart(parts)
1338
1347
  )) as SubmitResultValue;
1339
1348
  }
1340
1349
 
@@ -1524,7 +1533,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
1524
1533
  const id = { high: 1n, low };
1525
1534
  void promise.then(
1526
1535
  (result) => this.enqueueCompletion(id, operationKind, result),
1527
- (error) => this.enqueueCompletion(id, operationKind, genericOperationFailure(error))
1536
+ (error) => this.enqueueCompletion(id, operationKind, requestFailureResult(error))
1528
1537
  );
1529
1538
  return id;
1530
1539
  }
@@ -1536,7 +1545,7 @@ export class ZLinkNodeRawMeshBackend implements ZLinkBackendMeshNode {
1536
1545
  const id = { high: 2n, low: pending.id };
1537
1546
  void pending.promise.then(
1538
1547
  (result) => this.enqueueCompletion(id, operationKind, result),
1539
- (error) => this.enqueueCompletion(id, operationKind, statefulOperationFailure(error))
1548
+ (error) => this.enqueueCompletion(id, operationKind, requestFailureResult(error))
1540
1549
  );
1541
1550
  return id;
1542
1551
  }
@@ -2430,40 +2439,64 @@ function readyDomain(domain: ServiceMailboxDomain): number {
2430
2439
  }
2431
2440
 
2432
2441
  /**
2433
- * Classifies a generic node/channel request rejection into a completion
2434
- * terminal. Spec 32-framework-error-model:91-92 — a reply that could not be
2435
- * decoded (ServiceWireProtocolError from the reply parser) is a protocol
2436
- * failure, not connection loss; only genuine transport rejections collapse to
2437
- * NotConnected.
2442
+ * The one classification of a failed node/channel/stateful request into its
2443
+ * completion terminal. A binding REQUEST result is Core's decision and is kept
2444
+ * as is; a submit failure retains its Core meaning at the request boundary.
2445
+ * A reply that could not be decoded is a protocol
2446
+ * failure (spec 32-framework-error-model:91-92). The failure code is a wire
2447
+ * code, so a Core result carries 0.
2438
2448
  */
2439
- export function genericOperationFailure(error: unknown): RawServiceRequestResult {
2449
+ export function requestFailureResult(error: unknown): RawServiceRequestResult {
2450
+ const failure = translateBindingResultError(error);
2451
+ if (isZLinkBackendResultError(failure)) {
2452
+ return {
2453
+ terminalResult:
2454
+ failure.operation === 'request' ? failure.result : submitFailureTerminal(failure.result),
2455
+ failureCode: 0
2456
+ };
2457
+ }
2440
2458
  // requestProtocolError(16): the schema terminal-failure-integrity rule
2441
- // forbids a typed terminal (104) with failure none, so the synthesized
2442
- // decode-failure pair is 104+16; genuine transport loss stays 109+0.
2443
- return error instanceof ServiceWireProtocolError
2444
- ? { terminalResult: RequestResult.ProtocolError, failureCode: 16 }
2445
- : { terminalResult: RequestResult.NotConnected, failureCode: 0 };
2446
- }
2447
-
2448
- function statefulOperationFailure(error: unknown): RawServiceRequestResult {
2449
- if (error instanceof ZLinkFrameworkException) {
2450
- return internalFrameworkWireReply(error);
2459
+ // forbids a typed terminal (104) with failure none.
2460
+ if (failure instanceof ServiceWireProtocolError) {
2461
+ return { terminalResult: RequestResult.ProtocolError, failureCode: 16 };
2451
2462
  }
2452
- if (error instanceof OperationTimeoutError) {
2453
- return { terminalResult: RequestResult.TimedOut, failureCode: 0 };
2463
+ if (failure instanceof ZLinkFrameworkException) {
2464
+ return internalFrameworkWireReply(failure);
2454
2465
  }
2455
- if (isZLinkBackendResultError(error)) {
2456
- return {
2457
- terminalResult: error.result,
2458
- failureCode: error.nativeErrno ?? 0
2459
- };
2466
+ if (failure instanceof OperationTimeoutError) {
2467
+ return { terminalResult: RequestResult.TimedOut, failureCode: 0 };
2460
2468
  }
2461
- if (error instanceof OperationCancelledError) {
2469
+ if (failure instanceof OperationCancelledError) {
2462
2470
  return { terminalResult: RequestResult.NotConnected, failureCode: 0 };
2463
2471
  }
2464
2472
  return { terminalResult: RequestResult.InternalError, failureCode: 17 };
2465
2473
  }
2466
2474
 
2475
+ function submitFailureTerminal(result: number): number {
2476
+ switch (result) {
2477
+ case SubmitResult.Backpressured:
2478
+ return RequestResult.Backpressured;
2479
+ case SubmitResult.NotConnected:
2480
+ return RequestResult.NotConnected;
2481
+ case SubmitResult.NotFound:
2482
+ return RequestResult.NotFound;
2483
+ case SubmitResult.NotAdmitted:
2484
+ return RequestResult.Rejected;
2485
+ case SubmitResult.InvalidHandle:
2486
+ case SubmitResult.InvalidArgument:
2487
+ case SubmitResult.ThreadViolation:
2488
+ return RequestResult.InvalidArgument;
2489
+ case SubmitResult.InvalidState:
2490
+ return RequestResult.InvalidState;
2491
+ case SubmitResult.NotSupported:
2492
+ return RequestResult.NotSupported;
2493
+ case SubmitResult.Terminated:
2494
+ return RequestResult.Terminated;
2495
+ default:
2496
+ return RequestResult.InternalError;
2497
+ }
2498
+ }
2499
+
2467
2500
  function encodeMultipart(parts: MessageLike | readonly MessageLike[]) {
2468
2501
  const values = Array.isArray(parts) ? parts : [parts];
2469
2502
  if (values.length === 0) throw new TypeError('Multipart payload must contain at least one part.');
@@ -1,7 +1,7 @@
1
1
  import type { Socket } from '@zlink-systems/zlink';
2
2
  import type { ZLinkBackendObject } from '../contracts';
3
3
  import {
4
- closeWithBusyRetry,
4
+ closeBindingHandle,
5
5
  disableSocketLinger,
6
6
  isBindingNotFound,
7
7
  isContextTerminatedError,
@@ -52,7 +52,7 @@ export function wrapSocket<T extends { close(): void }>(
52
52
  async dispose(): Promise<void> {
53
53
  eventLoopPoller?.dispose();
54
54
  disableSocketLinger(nativeInstance);
55
- await closeWithBusyRetry(nativeInstance);
55
+ closeBindingHandle(nativeInstance);
56
56
  },
57
57
  close(): void {
58
58
  eventLoopPoller?.dispose();
@@ -2,6 +2,11 @@ export interface ZLinkRawReceivedRecord {
2
2
  readonly sourceRid: string;
3
3
  readonly sourceRoute: Uint8Array;
4
4
  readonly requestSeq?: bigint;
5
+ /**
6
+ * Core selected-route generation of the ROUTER route that delivered this
7
+ * record (Core ROUTER §10.1); 0n for a non-ROUTER receive. Equality only.
8
+ */
9
+ readonly routeGeneration: bigint;
5
10
  readonly reply?: (parts: readonly Uint8Array[]) => void;
6
11
  readonly parts: readonly Buffer[];
7
12
  /** Releases the ordinary Framework-owned receive record exactly once. */
@@ -37,6 +42,12 @@ export interface ZLinkRawMonitorPort {
37
42
  close(): void;
38
43
  }
39
44
 
45
+ /** One row of the ROUTER selected-route snapshot (Core ROUTER §10.1). */
46
+ export interface ZLinkRawRouterRoute {
47
+ readonly routingId: string;
48
+ readonly routeGeneration: bigint;
49
+ }
50
+
40
51
  export interface ZLinkRawRouterPort extends ZLinkRawSocketPort {
41
52
  disconnectRid?(routingId: string): void;
42
53
  localEndpoint(): string;
@@ -49,6 +60,8 @@ export interface ZLinkRawRouterPort extends ZLinkRawSocketPort {
49
60
  timeoutMs: number
50
61
  ): Promise<readonly Buffer[]>;
51
62
  receive(dontWait?: boolean): ZLinkRawReceivedRecord | undefined;
63
+ /** The selected route of every RID. The socket's one route observer calls it. */
64
+ routesSnapshot(): readonly ZLinkRawRouterRoute[];
52
65
  }
53
66
 
54
67
  export interface ZLinkRawDealerPort extends ZLinkRawSocketPort {
@@ -138,17 +138,6 @@ export class DefaultZLinkFanoutClient implements ZLinkFanoutClient {
138
138
  );
139
139
  }
140
140
 
141
- getListenerStatus(channelName: string): import('../../contracts').ZLinkFanoutListenerStatus {
142
- this.requirePublisherChannel(channelName);
143
- const status = this.requireTransport().getFanoutListenerStatus?.(channelName);
144
- if (status === undefined) {
145
- throw new ZLinkConfigurationException(
146
- `Fanout publisher '${channelName}' has not reported a bound listener.`
147
- );
148
- }
149
- return status;
150
- }
151
-
152
141
  private requirePublisherChannel(channelName: string): void {
153
142
  if (!this.registration.fanoutPublishers.has(channelName)) {
154
143
  throw new ZLinkConfigurationException(
@@ -72,11 +72,7 @@ export class ZLinkChannelOutboundOperations {
72
72
  throwIfAborted(signal);
73
73
  const dealer = await this.sockets.awaitClientDealerForOutbound(channelName, signal);
74
74
  if (dealer === undefined) {
75
- return {
76
- status: this.sockets.hasKnownClientServerTargets(channelName)
77
- ? ZLinkSubmitStatus.RouteNotConnected
78
- : ZLinkSubmitStatus.TargetNotFound
79
- };
75
+ return { status: ZLinkSubmitStatus.RouteNotConnected };
80
76
  }
81
77
  const parts = encodeChannelEnvelopeParts(
82
78
  ZLinkChannelMessageKind.Command,
@@ -185,12 +181,8 @@ export class ZLinkChannelOutboundOperations {
185
181
  deadlineAtMs === undefined ? undefined : Math.floor(deadlineAtMs - performance.now());
186
182
  if (dealer === undefined || (remainingMs !== undefined && remainingMs <= 0)) {
187
183
  throw createInternalFrameworkException(
188
- this.sockets.hasKnownClientServerTargets(channelName)
189
- ? ZLinkFrameworkInternalErrorKind.RouteNotConnected
190
- : ZLinkFrameworkInternalErrorKind.RequestTargetNotFound,
191
- this.sockets.hasKnownClientServerTargets(channelName)
192
- ? `Channel '${channelName}' has known ClientServer targets but no ready server.`
193
- : `Channel '${channelName}' has no ready ClientServer server.`
184
+ ZLinkFrameworkInternalErrorKind.RouteNotConnected,
185
+ `Channel '${channelName}' has no ready ClientServer server.`
194
186
  );
195
187
  }
196
188
  const serverRid = this.sockets.selectedClientServerRid(channelName, dealer);
@@ -1,3 +1,4 @@
1
+ import type { ZLinkListenerRecords } from '../foundation/listener-records';
1
2
  import type { ZLinkLocationOptionOverrides } from '../../contracts/Locations/Options';
2
3
  import type { RoutingId } from '../../contracts';
3
4
  import type { ZLinkProviderResolver } from '../../contracts/Common/ZLinkProviderResolver';
@@ -62,7 +63,8 @@ export class ZLinkChannelRuntimeManager {
62
63
  context,
63
64
  options.monitoringAdapter,
64
65
  options.oneWayFailureSink,
65
- applicationJobQueue
66
+ applicationJobQueue,
67
+ options.listenerRecords
66
68
  );
67
69
  const codecs: ZLinkChannelEnvelopeCodecRegistry = {
68
70
  serializers: registration.messageSerializers
@@ -118,20 +120,6 @@ export class ZLinkChannelRuntimeManager {
118
120
  return { descriptors: this.sockets.fanoutActiveTargets(channelName) };
119
121
  }
120
122
 
121
- getFanoutListenerStatus(channelName: string) {
122
- const endpoint = this.sockets.fanoutPublisherEndpoint(channelName);
123
- if (endpoint === undefined) {
124
- throw new ZLinkConfigurationException(
125
- `Fanout publisher '${channelName}' has not reported a bound listener.`
126
- );
127
- }
128
- return {
129
- channelName,
130
- endpoint,
131
- observedAt: new Date()
132
- };
133
- }
134
-
135
123
  observeClientServerTopology(channelName: string, changed: () => void): () => void {
136
124
  const monitor = this.sockets.clientServerMonitoringSource(channelName);
137
125
  monitor.onEvent(changed);
@@ -447,6 +435,7 @@ function splitMonitoringSocketSourceName(sourceName: string): readonly [string,
447
435
  }
448
436
 
449
437
  export interface ZLinkChannelRuntimeManagerOptions {
438
+ readonly listenerRecords?: ZLinkListenerRecords;
450
439
  readonly internalRouteSendHandlers?: ReadonlyMap<string, ZLinkRouteRuntimeSendHandler>;
451
440
  readonly internalRouteRequestHandlers?: ReadonlyMap<string, ZLinkRouteRuntimeRequestHandler>;
452
441
  readonly monitoringAdapter?: ZLinkMonitoringBackendAdapter;