@zlink-systems/framework 0.25.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
@@ -6,7 +6,6 @@ import type { RoutingId, Type, ZLinkSpot, ZLinkSpotInfo } from '../../contracts'
6
6
  import type { ZLinkLocalSpotCreateResult } from './spot-manager-internal-contracts';
7
7
  import { ZLinkSpotCreateState, ZLinkSpotCloseReason } from '../../contracts';
8
8
  import type { ZLinkSpotActivation } from './spot-activation-state';
9
- import { ZLinkSpotCloseOccupiedError } from './spot-activation-state';
10
9
  import { ZLinkConfigurationException } from '../configuration';
11
10
  import { createAbortError } from '../abort';
12
11
  import { ZLinkSpotLifecycleMetrics } from './spot-lifecycle-metrics';
@@ -40,7 +39,11 @@ export class ZLinkSpotActivationRegistry {
40
39
  private readonly lifecycleMetrics: ZLinkSpotLifecycleMetrics;
41
40
  private activeScan: Iterator<ZLinkSpotActivation> | undefined;
42
41
 
43
- constructor(metrics?: import('../diagnostics').ZLinkRuntimeMetrics) {
42
+ constructor(
43
+ metrics?: import('../diagnostics').ZLinkRuntimeMetrics,
44
+ private readonly isCommittedClose: (meshName: string, spotId: RoutingId) => boolean = () =>
45
+ false
46
+ ) {
44
47
  this.lifecycleMetrics = new ZLinkSpotLifecycleMetrics(metrics);
45
48
  }
46
49
 
@@ -55,7 +58,10 @@ export class ZLinkSpotActivationRegistry {
55
58
 
56
59
  resolve(meshName: string, spotId: RoutingId): ZLinkSpotActivation | undefined {
57
60
  const key = spotActivationKey(meshName, spotId);
58
- return this.closing.has(key) || this.failedClose.has(key) || this.staged.has(key)
61
+ return this.isCommittedClose(meshName, spotId) ||
62
+ this.closing.has(key) ||
63
+ this.failedClose.has(key) ||
64
+ this.staged.has(key)
59
65
  ? undefined
60
66
  : this.activations.get(key);
61
67
  }
@@ -64,6 +70,7 @@ export class ZLinkSpotActivationRegistry {
64
70
  const matches = [...this.activations.values()].filter(
65
71
  (activation) =>
66
72
  String(activation.spotId) === String(spotId) &&
73
+ !this.isCommittedClose(activation.meshName, activation.spotId) &&
67
74
  !this.staged.has(spotActivationKey(activation.meshName, activation.spotId)) &&
68
75
  !this.closing.has(spotActivationKey(activation.meshName, activation.spotId)) &&
69
76
  !this.failedClose.has(spotActivationKey(activation.meshName, activation.spotId))
@@ -74,6 +81,7 @@ export class ZLinkSpotActivationRegistry {
74
81
  has(meshName: string, spotId: RoutingId): boolean {
75
82
  const key = spotActivationKey(meshName, spotId);
76
83
  return (
84
+ !this.isCommittedClose(meshName, spotId) &&
77
85
  !this.staged.has(key) &&
78
86
  !this.closing.has(key) &&
79
87
  !this.failedClose.has(key) &&
@@ -81,22 +89,17 @@ export class ZLinkSpotActivationRegistry {
81
89
  );
82
90
  }
83
91
 
84
- canClose(meshName: string, spotId: RoutingId): boolean {
85
- const key = spotActivationKey(meshName, spotId);
86
- return (
87
- !this.staged.has(key) &&
88
- !this.closing.has(key) &&
89
- !this.failedClose.has(key) &&
90
- this.activations.get(key)?.canClose() === true
91
- );
92
- }
93
-
94
92
  list(meshName: string): readonly ZLinkSpotInfo[] {
95
93
  return [...this.activations.values()]
96
94
  .filter((activation) => {
97
95
  if (activation.meshName !== meshName) return false;
98
96
  const key = spotActivationKey(activation.meshName, activation.spotId);
99
- return !this.staged.has(key) && !this.closing.has(key) && !this.failedClose.has(key);
97
+ return (
98
+ !this.isCommittedClose(meshName, activation.spotId) &&
99
+ !this.staged.has(key) &&
100
+ !this.closing.has(key) &&
101
+ !this.failedClose.has(key)
102
+ );
100
103
  })
101
104
  .map((activation) => String(activation.spotId))
102
105
  .sort((left, right) => left.localeCompare(right))
@@ -107,6 +110,18 @@ export class ZLinkSpotActivationRegistry {
107
110
  return [...this.activations.values()];
108
111
  }
109
112
 
113
+ activationForClose(meshName: string, spotId: RoutingId): ZLinkSpotActivation | undefined {
114
+ return this.activations.get(spotActivationKey(meshName, spotId));
115
+ }
116
+
117
+ finishClose(meshName: string, spotId: RoutingId): void {
118
+ const key = spotActivationKey(meshName, spotId);
119
+ if (this.activations.delete(key)) this.lifecycleMetrics.closed('user');
120
+ this.staged.delete(key);
121
+ this.failedClose.delete(key);
122
+ this.resolveEmptyWaiters();
123
+ }
124
+
110
125
  hasActiveActivations(): boolean {
111
126
  return this.activations.size > 0;
112
127
  }
@@ -215,30 +230,14 @@ export class ZLinkSpotActivationRegistry {
215
230
  }
216
231
  const operation = {} as ZLinkSpotCloseOperation;
217
232
  let completed = false;
218
- let occupiedAfterQuiescence = false;
219
233
  const ready = Promise.resolve()
220
234
  .then(() => close(activation))
221
235
  .then(() => {
222
236
  completed = true;
223
237
  })
224
- .catch((error: unknown) => {
225
- // The seal's post-quiescence recheck (spot-activation.ts
226
- // closeAfterSeal) found a new join and released the seal instead of
227
- // closing. The activation is already back to normal operation, so
228
- // this is a "did not close" outcome, not a close failure.
229
- if (error instanceof ZLinkSpotCloseOccupiedError) {
230
- occupiedAfterQuiescence = true;
231
- return;
232
- }
233
- throw error;
234
- })
235
238
  .finally(() => {
236
239
  if (this.closing.get(key) === operation) {
237
240
  this.closing.delete(key);
238
- if (occupiedAfterQuiescence) {
239
- this.resolveEmptyWaiters();
240
- return;
241
- }
242
241
  if (completed || resourcesReleased(activation)) {
243
242
  this.activations.delete(key);
244
243
  this.staged.delete(key);
@@ -250,7 +249,7 @@ export class ZLinkSpotActivationRegistry {
250
249
  this.resolveEmptyWaiters();
251
250
  }
252
251
  })
253
- .then(() => !occupiedAfterQuiescence);
252
+ .then(() => true);
254
253
  Object.assign(operation, { activation, ready, started: true });
255
254
  this.closing.set(key, operation);
256
255
  return operation;
@@ -263,6 +262,17 @@ export class ZLinkSpotActivationRegistry {
263
262
  create: () => Promise<ZLinkLocalSpotCreateResult>
264
263
  ): ZLinkSpotActivationOperation {
265
264
  const key = spotActivationKey(meshName, spotId);
265
+ if (this.isCommittedClose(meshName, spotId)) {
266
+ return {
267
+ owner: false,
268
+ ready: Promise.reject(
269
+ createInternalFrameworkException(
270
+ ZLinkFrameworkInternalErrorKind.RequestRejected,
271
+ `Spot '${String(spotId)}' is Closing.`
272
+ )
273
+ )
274
+ };
275
+ }
266
276
  const closing = this.closing.get(key);
267
277
  if (closing !== undefined) {
268
278
  return {
@@ -25,6 +25,7 @@ import type { ZLinkTimerRelocationState } from './spot-timer';
25
25
  export type ZLinkSpotActivationDomain =
26
26
  | {
27
27
  readonly kind: 'user';
28
+ readonly objectGeneration?: bigint;
28
29
  readonly executionMode: ZLinkUserSpotExecutionMode;
29
30
  readonly relocationCoordinationMode: ZLinkSpotRelocationCoordinationMode;
30
31
  }
@@ -56,19 +57,6 @@ export interface ZLinkSpotRelocationCapture {
56
57
  readonly timers: readonly ZLinkTimerRelocationState[];
57
58
  }
58
59
 
59
- /**
60
- * Internal signal that a close seal reached quiescence but the activation
61
- * became occupied again while it waited (an actor join queued ahead of the
62
- * close finished after the eager pre-seal occupancy check). This is not a
63
- * close failure: the seal is released, the Spot stays open, and the caller
64
- * reports `close(...)` as `false` instead of throwing.
65
- */
66
- export class ZLinkSpotCloseOccupiedError extends Error {
67
- constructor(spotId: RoutingId) {
68
- super(`Spot '${String(spotId)}' became occupied while its close seal reached quiescence.`);
69
- }
70
- }
71
-
72
60
  export class ZLinkSpotActivation {
73
61
  readonly meshName: string;
74
62
  readonly spotId: RoutingId;
@@ -126,7 +114,7 @@ export class ZLinkSpotActivation {
126
114
  }
127
115
 
128
116
  get objectGeneration(): bigint | undefined {
129
- return this.domain.kind === 'instance' ? this.domain.objectGeneration : undefined;
117
+ return this.domain.objectGeneration;
130
118
  }
131
119
 
132
120
  get executionMode(): ZLinkUserSpotExecutionMode {
@@ -274,8 +262,8 @@ export class ZLinkSpotActivation {
274
262
  return this.serialExecutor.admitActorDurablePrefix(actorId, records);
275
263
  }
276
264
 
277
- sealExecution(): ZLinkExecutionBarrierSeal {
278
- return this.executionBarrier.seal();
265
+ sealExecution(drainsYieldedTurns = false): ZLinkExecutionBarrierSeal {
266
+ return this.executionBarrier.seal(drainsYieldedTurns);
279
267
  }
280
268
 
281
269
  waitForExecutionQuiescence(seal: ZLinkExecutionBarrierSeal, signal?: AbortSignal): Promise<void> {
@@ -31,8 +31,11 @@ import type { Message } from '../../contracts/Common/Message';
31
31
  import type { ZLinkMessageFollowOrigin } from '../foundation/service-runtime-contracts';
32
32
  import { throwIfAborted } from '../abort';
33
33
  import { ZLinkConfigurationException } from '../configuration';
34
+ import {
35
+ ZLinkFrameworkInternalErrorKind,
36
+ createInternalFrameworkException
37
+ } from '../framework-errors-internal';
34
38
  import type {
35
- ZLinkBackendReceived,
36
39
  ZLinkBackendSpot,
37
40
  ZLinkBackendSpotNode,
38
41
  ZLinkBackendTopicMessage
@@ -77,7 +80,7 @@ import type {
77
80
  } from './spot-actor-join-dispatch';
78
81
  import { ZLinkSpotActorAdmissionCoordinator } from './spot-actor-admission-coordinator';
79
82
  import type { ZLinkRouteToActorJoinPrewarm } from './spot-actor-packet-dispatch';
80
- import { ZLinkSpotActivation, ZLinkSpotCloseOccupiedError } from './spot-activation-state';
83
+ import { ZLinkSpotActivation } from './spot-activation-state';
81
84
  import type { ZLinkSpotLocationClaim } from './spot-location-claim';
82
85
  import type {
83
86
  ZLinkSpotActorHandoffRuntime,
@@ -114,6 +117,7 @@ export interface ZLinkSpotActivationLifecycleOptions {
114
117
  readonly channelMeshNameForChannel?: (channelName: string) => string | undefined;
115
118
  readonly providerResolver?: ZLinkProviderResolver;
116
119
  readonly dispatchErrors?: ZLinkDispatchErrorReporter;
120
+ readonly closeErrorSink?: import('../diagnostics/dispatch-error-port').ZLinkDispatchErrorSink;
117
121
  readonly runtimeEventPublisher?: ZLinkRuntimeEventPublisher;
118
122
  readonly workerRuntime: ZLinkWorkerRuntime;
119
123
  readonly messageSerializers?: ReadonlyMap<string, ZLinkMessageSerializer>;
@@ -123,7 +127,6 @@ export interface ZLinkSpotActivationLifecycleOptions {
123
127
  spotId: RoutingId,
124
128
  authority?: ZLinkNativeSpotAuthority
125
129
  ) => ZLinkBackendSpot | undefined;
126
- readonly createReceived?: () => ZLinkBackendReceived;
127
130
  readonly createTopicMessage?: () => ZLinkBackendTopicMessage;
128
131
  readonly nativeSpotNodeProvider?: (meshName: string) => ZLinkBackendSpotNode | undefined;
129
132
  readonly actorResolver?: (actorId: string) => ZLinkActor | undefined;
@@ -139,12 +142,13 @@ export interface ZLinkSpotActivationLifecycleOptions {
139
142
  signal?: AbortSignal,
140
143
  meshName?: string
141
144
  ) => Promise<void>;
142
- readonly closeSpot: (
143
- meshName: string,
144
- spotId: RoutingId,
145
- signal?: AbortSignal,
146
- reason?: ZLinkSpotCloseReason
145
+ /** Starts a context Close request after its calling turn has ended. */
146
+ readonly requestContextClose: (
147
+ activation: ZLinkSpotActivation,
148
+ objectGeneration: bigint,
149
+ signal?: AbortSignal
147
150
  ) => Promise<boolean>;
151
+ readonly isSpotClosing: (activation: ZLinkSpotActivation) => boolean;
148
152
  readonly registerActivation: (activation: ZLinkSpotActivation) => void;
149
153
  readonly routeToActorJoinPrewarm?: ZLinkRouteToActorJoinPrewarm;
150
154
  readonly releaseLocation: (
@@ -173,10 +177,12 @@ export class ZLinkSpotActivationLifecycle {
173
177
  {
174
178
  closingAttempted: boolean;
175
179
  timersDisposed: boolean;
180
+ serialDisposed: boolean;
176
181
  handlersDisposed: boolean;
182
+ actorDispatchDisposed: boolean;
177
183
  nativeDisposed: boolean;
178
184
  locationReleased: boolean;
179
- inFlight?: Promise<void>;
185
+ inFlight?: Promise<unknown>;
180
186
  }
181
187
  >();
182
188
 
@@ -259,7 +265,7 @@ export class ZLinkSpotActivationLifecycle {
259
265
  providerResolver: this.options.providerResolver,
260
266
  runtimeEventPublisher: this.options.runtimeEventPublisher,
261
267
  workerRuntime: this.options.workerRuntime,
262
- close: this.contextClose(meshName, spotId, () => activation)
268
+ close: this.contextClose(objectGeneration, () => activation)
263
269
  };
264
270
  const context =
265
271
  objectKind === 'user_spot'
@@ -314,6 +320,7 @@ export class ZLinkSpotActivationLifecycle {
314
320
  objectKind === 'user_spot'
315
321
  ? {
316
322
  kind: 'user',
323
+ objectGeneration,
317
324
  executionMode,
318
325
  relocationCoordinationMode:
319
326
  this.options.userSpotRelocationCoordinationMode?.(
@@ -398,7 +405,7 @@ export class ZLinkSpotActivationLifecycle {
398
405
  providerResolver: this.options.providerResolver,
399
406
  runtimeEventPublisher: this.options.runtimeEventPublisher,
400
407
  workerRuntime: this.options.workerRuntime,
401
- close: this.contextClose(meshName, spotId, () => activation)
408
+ close: this.contextClose(objectGeneration, () => activation)
402
409
  });
403
410
  instance = await createFreshProviderInstance(
404
411
  implementation,
@@ -487,16 +494,6 @@ export class ZLinkSpotActivationLifecycle {
487
494
  }
488
495
  }
489
496
 
490
- resourcesReleased(activation: ZLinkSpotActivation): boolean {
491
- const state = this.cleanupStates.get(activation);
492
- return (
493
- state?.timersDisposed === true &&
494
- state.handlersDisposed === true &&
495
- state.nativeDisposed === true &&
496
- state.locationReleased === true
497
- );
498
- }
499
-
500
497
  async create<TSpot extends ZLinkSpot>(
501
498
  meshName: string,
502
499
  spotType: Type<TSpot>,
@@ -607,7 +604,7 @@ export class ZLinkSpotActivationLifecycle {
607
604
  ensureOperationAllowed: () => activation?.ensureContextOperationAllowed(),
608
605
  leaveActor: (actor, contextSignal) =>
609
606
  this.options.leaveActor(spotId, actor, contextSignal, meshName),
610
- close: this.contextClose(meshName, spotId, () => activation)
607
+ close: this.contextClose(spotGeneration, () => activation)
611
608
  });
612
609
  try {
613
610
  spot = await createFreshProviderInstance(spotType, this.options.providerResolver, context);
@@ -624,6 +621,7 @@ export class ZLinkSpotActivationLifecycle {
624
621
  spotId,
625
622
  domain: {
626
623
  kind: 'user',
624
+ objectGeneration: spotGeneration,
627
625
  executionMode,
628
626
  relocationCoordinationMode:
629
627
  this.options.userSpotRelocationCoordinationMode?.(meshName, spotType) ??
@@ -738,7 +736,7 @@ export class ZLinkSpotActivationLifecycle {
738
736
  ensureOperationAllowed: () => activation?.ensureContextOperationAllowed(),
739
737
  leaveActor: (actor, contextSignal) =>
740
738
  this.options.leaveActor(spotId, actor, contextSignal, meshName),
741
- close: this.contextClose(meshName, spotId, () => activation)
739
+ close: this.contextClose(objectGeneration, () => activation)
742
740
  });
743
741
  spot = await createFreshProviderInstance(spotType, this.options.providerResolver, context);
744
742
  Object.defineProperty(spot, 'context', {
@@ -751,6 +749,7 @@ export class ZLinkSpotActivationLifecycle {
751
749
  spotId,
752
750
  domain: {
753
751
  kind: 'user',
752
+ objectGeneration,
754
753
  executionMode,
755
754
  relocationCoordinationMode:
756
755
  this.options.userSpotRelocationCoordinationMode?.(meshName, spotType) ??
@@ -777,47 +776,25 @@ export class ZLinkSpotActivationLifecycle {
777
776
  }
778
777
 
779
778
  private contextClose(
780
- meshName: string,
781
- spotId: RoutingId,
779
+ objectGeneration: bigint,
782
780
  activationProvider: () => ZLinkSpotActivation | undefined
783
781
  ): (signal?: AbortSignal) => Promise<boolean> {
784
- return async (signal) => {
782
+ return (signal) => {
785
783
  const activation = activationProvider();
786
- // Native callbacks can cross a promise boundary that does not retain
787
- // the serial turn context. Queue the close before calling the manager
788
- // so the callback never waits for work behind its own serial turn.
789
- if (activation?.serial.isExecuting === true && !activation.serial.isCurrentTurn) {
790
- activation.requestClose();
791
- const retry = activation.serial.post(() =>
792
- this.options.closeSpot(meshName, spotId, signal)
784
+ if (activation === undefined) {
785
+ throw createInternalFrameworkException(
786
+ ZLinkFrameworkInternalErrorKind.InvalidOperation,
787
+ 'Spot context Close requires its active generation.'
793
788
  );
794
- this.options.detachedTaskRunner?.runDetached(`spot close ${String(spotId)}`, async () => {
795
- await retry;
796
- });
797
- if (this.options.detachedTaskRunner === undefined) {
798
- void retry.catch(() => undefined);
799
- }
800
- return true;
801
789
  }
802
- return await this.options.closeSpot(meshName, spotId, signal);
790
+ return this.options.requestContextClose(activation, objectGeneration, signal);
803
791
  };
804
792
  }
805
793
 
806
- async close(
807
- activation: ZLinkSpotActivation,
808
- signal?: AbortSignal,
809
- reason = ZLinkSpotCloseReason.ExplicitClose
810
- ): Promise<void> {
811
- const seal = activation.sealExecution();
812
- await this.closeAfterSeal(activation, seal, signal, reason);
813
- }
814
-
815
- async closeAfterSeal(
794
+ async sealForClose(
816
795
  activation: ZLinkSpotActivation,
817
796
  seal: import('../execution').ZLinkExecutionBarrierSeal,
818
- signal?: AbortSignal,
819
- reason = ZLinkSpotCloseReason.ExplicitClose,
820
- deadline?: Date
797
+ signal?: AbortSignal
821
798
  ): Promise<void> {
822
799
  try {
823
800
  await activation.waitForExecutionQuiescence(seal, signal);
@@ -825,19 +802,26 @@ export class ZLinkSpotActivationLifecycle {
825
802
  activation.abortExecutionSeal(seal);
826
803
  throw error;
827
804
  }
828
- // The eager occupancy check that authorized this seal (startClose ->
829
- // canClose()) ran before quiescence, so it can miss an actor join that
830
- // was already queued on the serial executor at that moment. Recheck now
831
- // that every turn admitted before the seal has finished, and release the
832
- // seal instead of closing an occupied Spot.
833
- if (!activation.canClose(reason)) {
834
- activation.abortExecutionSeal(seal);
835
- throw new ZLinkSpotCloseOccupiedError(activation.spotId);
836
- }
837
805
  if (!activation.commitExecutionSeal(seal)) {
838
806
  throw new Error(`Spot '${String(activation.spotId)}' close seal is stale.`);
839
807
  }
840
- await this.cleanupActivation(activation, activation.meshName, true, signal, reason, deadline);
808
+ }
809
+
810
+ /** Returns the OnClosing failure, which cleanup records and does not rethrow. */
811
+ async cleanupClosedActivation(
812
+ activation: ZLinkSpotActivation,
813
+ reason = ZLinkSpotCloseReason.ExplicitClose,
814
+ deadline?: Date
815
+ ): Promise<unknown> {
816
+ return await this.cleanupActivation(
817
+ activation,
818
+ activation.meshName,
819
+ true,
820
+ undefined,
821
+ reason,
822
+ deadline,
823
+ true
824
+ );
841
825
  }
842
826
 
843
827
  async dispatchActorPacket(
@@ -961,13 +945,16 @@ export class ZLinkSpotActivationLifecycle {
961
945
  notifyClosing: boolean,
962
946
  signal?: AbortSignal,
963
947
  reason = ZLinkSpotCloseReason.ExplicitClose,
964
- deadline?: Date
965
- ): Promise<void> {
948
+ deadline?: Date,
949
+ owningLifecycle = false
950
+ ): Promise<unknown> {
966
951
  throwIfAborted(signal);
967
952
  const state = this.cleanupStates.get(activation) ?? {
968
953
  closingAttempted: false,
969
954
  timersDisposed: false,
955
+ serialDisposed: false,
970
956
  handlersDisposed: false,
957
+ actorDispatchDisposed: false,
971
958
  nativeDisposed: false,
972
959
  locationReleased: false
973
960
  };
@@ -979,7 +966,8 @@ export class ZLinkSpotActivationLifecycle {
979
966
  notifyClosing,
980
967
  reason,
981
968
  state,
982
- deadline
969
+ deadline,
970
+ owningLifecycle
983
971
  ).finally(() => {
984
972
  state.inFlight = undefined;
985
973
  });
@@ -994,13 +982,17 @@ export class ZLinkSpotActivationLifecycle {
994
982
  state: {
995
983
  closingAttempted: boolean;
996
984
  timersDisposed: boolean;
985
+ serialDisposed: boolean;
997
986
  handlersDisposed: boolean;
987
+ actorDispatchDisposed: boolean;
998
988
  nativeDisposed: boolean;
999
989
  locationReleased: boolean;
1000
990
  },
1001
- deadline?: Date
1002
- ): Promise<void> {
991
+ deadline?: Date,
992
+ owningLifecycle = false
993
+ ): Promise<unknown> {
1003
994
  const errors: unknown[] = [];
995
+ let closingFailure: unknown;
1004
996
  const cleanup = async (operation: () => Promise<void> | void, completed: () => void) => {
1005
997
  try {
1006
998
  await operation();
@@ -1011,10 +1003,21 @@ export class ZLinkSpotActivationLifecycle {
1011
1003
  };
1012
1004
  if (notifyClosing && !state.closingAttempted) {
1013
1005
  state.closingAttempted = true;
1014
- await cleanup(
1015
- () => invokeSpotClosing(activation.spot.onClosing?.bind(activation.spot), reason, deadline),
1016
- () => undefined
1017
- );
1006
+ try {
1007
+ // OnClosing is user code: it runs as a Framework turn through the
1008
+ // execution gate while the Close lifecycle item keeps its FIFO slot.
1009
+ await activation.serial.postBarrierTurn(() =>
1010
+ invokeSpotClosing(activation.spot.onClosing?.bind(activation.spot), reason, deadline)
1011
+ );
1012
+ } catch (error) {
1013
+ // An OnClosing failure is diagnostic and cleanup continues. It is
1014
+ // returned so host shutdown can classify its teardown outcome.
1015
+ this.options.closeErrorSink?.reportRuntimeTaskException(
1016
+ `spot ${String(activation.spotId)} onClosing`,
1017
+ error
1018
+ );
1019
+ closingFailure = error;
1020
+ }
1018
1021
  }
1019
1022
  if (!state.timersDisposed) {
1020
1023
  await cleanup(
@@ -1024,10 +1027,17 @@ export class ZLinkSpotActivationLifecycle {
1024
1027
  }
1025
1028
  );
1026
1029
  }
1027
- await cleanup(
1028
- () => activation.serialExecutor.close(),
1029
- () => undefined
1030
- );
1030
+ if (!state.serialDisposed && state.timersDisposed) {
1031
+ await cleanup(
1032
+ () =>
1033
+ owningLifecycle
1034
+ ? activation.serialExecutor.closeChildren()
1035
+ : activation.serialExecutor.close(),
1036
+ () => {
1037
+ state.serialDisposed = true;
1038
+ }
1039
+ );
1040
+ }
1031
1041
  if (!state.handlersDisposed) {
1032
1042
  await cleanup(
1033
1043
  () => disposeLifecycleHandlers(activation.spot),
@@ -1036,11 +1046,15 @@ export class ZLinkSpotActivationLifecycle {
1036
1046
  }
1037
1047
  );
1038
1048
  }
1039
- if (!state.nativeDisposed) {
1049
+ if (!state.actorDispatchDisposed) {
1040
1050
  await cleanup(
1041
1051
  () => activation.actorDispatch?.dispose(),
1042
- () => undefined
1052
+ () => {
1053
+ state.actorDispatchDisposed = true;
1054
+ }
1043
1055
  );
1056
+ }
1057
+ if (!state.nativeDisposed) {
1044
1058
  await cleanup(
1045
1059
  () => activation.nativeSpot?.dispose(),
1046
1060
  () => {
@@ -1048,7 +1062,14 @@ export class ZLinkSpotActivationLifecycle {
1048
1062
  }
1049
1063
  );
1050
1064
  }
1051
- if (!state.locationReleased) {
1065
+ if (
1066
+ !state.locationReleased &&
1067
+ state.timersDisposed &&
1068
+ state.serialDisposed &&
1069
+ state.handlersDisposed &&
1070
+ state.actorDispatchDisposed &&
1071
+ state.nativeDisposed
1072
+ ) {
1052
1073
  await cleanup(
1053
1074
  () => this.options.releaseLocation(activation, locationMeshName, activation.spotId),
1054
1075
  () => {
@@ -1060,6 +1081,7 @@ export class ZLinkSpotActivationLifecycle {
1060
1081
  if (errors.length > 1) {
1061
1082
  throw new AggregateError(errors, `Spot '${activation.spotId}' cleanup failed.`);
1062
1083
  }
1084
+ return closingFailure;
1063
1085
  }
1064
1086
 
1065
1087
  private decodeCreateReply(reply: unknown): unknown {
@@ -30,11 +30,11 @@ type AdmissionOptions = Pick<
30
30
  | 'locationClaim'
31
31
  | 'messageSerializers'
32
32
  | 'nativeSpotNodeProvider'
33
- | 'createReceived'
34
33
  | 'createTopicMessage'
35
34
  | 'providerResolver'
36
35
  | 'runtimeEventPublisher'
37
36
  | 'routeToActorJoinPrewarm'
37
+ | 'isSpotClosing'
38
38
  >;
39
39
 
40
40
  export class ZLinkSpotActorAdmissionCoordinator {
@@ -92,12 +92,12 @@ export class ZLinkSpotActorAdmissionCoordinator {
92
92
  if (nativeSpot === undefined) return undefined;
93
93
  const nativeDispatch = new ZLinkSpotActorJoinDispatch({
94
94
  nativeSpot,
95
- createReceived: requireBackendValueFactory(this.options.createReceived, 'Received'),
96
95
  createTopicMessage: requireBackendValueFactory(
97
96
  this.options.createTopicMessage,
98
97
  'TopicMessage'
99
98
  ),
100
99
  serial: activation.serial,
100
+ isSpotClosing: () => this.options.isSpotClosing(activation),
101
101
  actors: {
102
102
  resolveActor: (actorId) =>
103
103
  activation.hasDepartedActor(actorId)
@@ -11,7 +11,6 @@ import { ZLinkBackendSpotDispatchEvent } from '../backend/contracts';
11
11
  import type {
12
12
  ZLinkBackendActorRef,
13
13
  ZLinkBackendActorRecvInfo,
14
- ZLinkBackendReceived,
15
14
  ZLinkBackendSpot,
16
15
  ZLinkBackendTopicMessage
17
16
  } from '../backend/contracts';
@@ -92,9 +91,9 @@ export interface ZLinkDetachedTaskRunner {
92
91
 
93
92
  interface ZLinkSpotActorJoinDispatchOptions {
94
93
  readonly nativeSpot: ZLinkBackendSpot;
95
- readonly createReceived: () => ZLinkBackendReceived;
96
94
  readonly createTopicMessage: () => ZLinkBackendTopicMessage;
97
95
  readonly serial: ZLinkSpotSerialTurnExecutor;
96
+ readonly isSpotClosing?: () => boolean;
98
97
  readonly actors: ZLinkSpotActorAdmissionRuntime;
99
98
  readonly packets?: ZLinkSpotActorPacketRuntime;
100
99
  readonly boundSessionRuntime?: ZLinkSpotBoundSessionRuntime;
@@ -148,10 +147,9 @@ export class ZLinkSpotActorJoinDispatch {
148
147
  dispatchErrors: options.dispatchErrors
149
148
  });
150
149
  this.routedFrames = new ZLinkSpotRoutedFrameDispatch({
151
- nativeSpot: options.nativeSpot,
152
- createReceived: options.createReceived,
153
150
  nativeSpotId: this.nativeSpotId,
154
151
  serial: options.serial,
152
+ isSpotClosing: options.isSpotClosing,
155
153
  resolveActor: actors.resolveActor,
156
154
  getTarget: () => actors.getTarget() as ZLinkActorJoinAdmissionTarget & ZLinkSpot,
157
155
  defaultAccept: actors.defaultAccept,
@@ -189,8 +187,7 @@ export class ZLinkSpotActorJoinDispatch {
189
187
  messageSerializers: options.messageSerializers,
190
188
  providerResolver: options.providerResolver,
191
189
  dispatchErrors: options.dispatchErrors,
192
- claimApplicationWork: options.claimApplicationWork,
193
- waitIdle: waitSpotDispatchIdle
190
+ claimApplicationWork: options.claimApplicationWork
194
191
  });
195
192
  this.subscriptions = new ZLinkSpotSubscriptionDispatch({
196
193
  nativeSpot: options.nativeSpot,
@@ -309,7 +306,9 @@ export class ZLinkSpotActorJoinDispatch {
309
306
  await waitSpotDispatchIdle();
310
307
  return;
311
308
  }
312
- await this.nativeActorJoinAdmission.admit(request);
309
+ await this.options.serial.executeLifecycleOperation(() =>
310
+ this.nativeActorJoinAdmission.admit(request, this.options.isSpotClosing?.() === true)
311
+ );
313
312
  }
314
313
  }
315
314
  }