@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
@@ -49,12 +49,20 @@ export interface ZLinkSpotRuntimeOptionsFactoryOptions {
49
49
  ) => Promise<void>;
50
50
  readonly beginInstanceIdleClosingAuthority: (
51
51
  meshName: string,
52
- spotId: string
53
- ) => Promise<{ restoreReady(): Promise<void> } | undefined>;
52
+ spotId: string,
53
+ onCommitted: () => void
54
+ ) => Promise<{ release(): Promise<void> } | undefined>;
54
55
  readonly beginInstanceClosingAuthority: (
55
56
  meshName: string,
56
- spotId: string
57
- ) => Promise<{ restoreReady(): Promise<void> } | undefined>;
57
+ spotId: string,
58
+ onCommitted: () => void
59
+ ) => Promise<{ release(): Promise<void> } | undefined>;
60
+ readonly beginUserClosingAuthority: (
61
+ meshName: string,
62
+ spotId: string,
63
+ objectGeneration: bigint,
64
+ onCommitted: () => void
65
+ ) => Promise<{ release(): Promise<void> } | undefined>;
58
66
  readonly createLocationSpotRouteResolver: () => ZLinkSpotRouteResolver | undefined;
59
67
  readonly boundSessionRelay: ZLinkBoundSessionRelay;
60
68
  readonly actorHandoff: ZLinkActorHandoffCoordinator;
@@ -149,10 +157,17 @@ export class ZLinkSpotRuntimeOptionsFactory {
149
157
  locationLifecycle: this.options.locationLifecycle(),
150
158
  releaseInstanceAuthority: (meshName, spotId, objectGeneration) =>
151
159
  this.options.releaseInstanceAuthority(meshName, String(spotId), objectGeneration),
152
- beginInstanceIdleClosingAuthority: (meshName, spotId) =>
153
- this.options.beginInstanceIdleClosingAuthority(meshName, String(spotId)),
154
- beginInstanceClosingAuthority: (meshName, spotId) =>
155
- this.options.beginInstanceClosingAuthority(meshName, String(spotId)),
160
+ beginInstanceIdleClosingAuthority: (meshName, spotId, onCommitted) =>
161
+ this.options.beginInstanceIdleClosingAuthority(meshName, String(spotId), onCommitted),
162
+ beginInstanceClosingAuthority: (meshName, spotId, onCommitted) =>
163
+ this.options.beginInstanceClosingAuthority(meshName, String(spotId), onCommitted),
164
+ beginUserClosingAuthority: (meshName, spotId, objectGeneration, onCommitted) =>
165
+ this.options.beginUserClosingAuthority(
166
+ meshName,
167
+ String(spotId),
168
+ objectGeneration,
169
+ onCommitted
170
+ ),
156
171
  instanceSpotApplicationTargetProvider: (meshName, spotId) =>
157
172
  this.options
158
173
  .spotNodeRuntime()
@@ -196,11 +211,6 @@ export class ZLinkSpotRuntimeOptionsFactory {
196
211
  }
197
212
  } as never;
198
213
  },
199
- createReceived: () => {
200
- const runtime = this.options.spotNodeRuntime();
201
- if (runtime === undefined) throw new Error('Spot backend runtime is not initialized.');
202
- return runtime.createReceived();
203
- },
204
214
  createTopicMessage: () => {
205
215
  const runtime = this.options.spotNodeRuntime();
206
216
  if (runtime === undefined) throw new Error('Spot backend runtime is not initialized.');
@@ -275,6 +285,7 @@ export class ZLinkSpotRuntimeOptionsFactory {
275
285
  metrics: this.options.metrics,
276
286
  admission: this.options.admission,
277
287
  statefulExecutionAllowed: this.options.statefulExecutionAllowed,
288
+ closeErrorSink: this.options.runtimeOrPreStartErrorSink,
278
289
  dispatchErrors: this.options.dispatchErrorReporter(this.options.runtimeOrPreStartErrorSink)
279
290
  };
280
291
  }
@@ -19,6 +19,7 @@ import type {
19
19
  } from '../foundation/service-stateful-wire-codec';
20
20
  import { routingIdsEqual } from '../routing-id';
21
21
  import {
22
+ decodeServiceClosingSpotAuthority,
22
23
  decodeServiceInstanceAuthorityPayload,
23
24
  decodeServiceReadySpotAuthority,
24
25
  type ServiceActivationRecoveryState
@@ -74,6 +75,7 @@ interface StatefulAuthorityRouteSink {
74
75
 
75
76
  interface AppliedAuthorityRoute {
76
77
  readonly kind: 'user_spot' | 'instance_spot';
78
+ readonly closing: boolean;
77
79
  readonly stableType: string;
78
80
  readonly meshName: string;
79
81
  readonly spotRoute: ServiceDirectSpotRouteFence;
@@ -189,7 +191,7 @@ export class ZLinkStatefulAuthorityRouteRuntime {
189
191
  const expectedInstanceRoute =
190
192
  previousRoute?.kind === 'instance_spot' ? previousRoute.instanceRoute : null;
191
193
  sink.rememberSpotRoute(route.spotRoute, expectedSpotRoute);
192
- if (route.kind === 'instance_spot') {
194
+ if (route.kind === 'instance_spot' && !route.closing) {
193
195
  const status = sink.status();
194
196
  if (
195
197
  routingIdsEqual(status.routingId, route.instanceRoute.targetNodeRid) &&
@@ -555,9 +557,9 @@ function pendingInstanceActivation(
555
557
 
556
558
  function authorityRoute(snapshot: ZLinkAuthoritySnapshot): AppliedAuthorityRoute | undefined {
557
559
  const allocation = snapshot.allocation;
558
- const decoded = decodeServiceReadySpotAuthority(
559
- serviceRelocationAuthorityApplicationPayload(snapshot.payload)
560
- );
560
+ const payload = serviceRelocationAuthorityApplicationPayload(snapshot.payload);
561
+ const ready = decodeServiceReadySpotAuthority(payload);
562
+ const decoded = ready ?? decodeServiceClosingSpotAuthority(payload);
561
563
  if (
562
564
  allocation.state !== 'active' ||
563
565
  allocation.objectKind === 'actor' ||
@@ -585,6 +587,7 @@ function authorityRoute(snapshot: ZLinkAuthoritySnapshot): AppliedAuthorityRoute
585
587
  };
586
588
  return {
587
589
  kind: allocation.objectKind,
590
+ closing: ready === undefined,
588
591
  stableType: allocation.stableType,
589
592
  meshName: allocation.descriptor.meshName,
590
593
  spotRoute,
@@ -617,6 +620,7 @@ function sameAppliedRoute(
617
620
  if (right === undefined) return false;
618
621
  return (
619
622
  left.kind === right.kind &&
623
+ left.closing === right.closing &&
620
624
  left.stableType === right.stableType &&
621
625
  left.meshName === right.meshName &&
622
626
  left.instanceRoute.targetNodeRid === right.instanceRoute.targetNodeRid &&
@@ -17,6 +17,7 @@ import type {
17
17
  import { ZLinkFrameworkException, ZLinkSpotCreateState } from '../../contracts';
18
18
  import {
19
19
  decodeServiceReadySpotAuthority,
20
+ decodeServiceClosingSpotAuthority,
20
21
  encodeServiceUserSpotAuthorityPayload
21
22
  } from '../foundation/service-authority-payload-codec';
22
23
  import { encodeAuthorityKey } from '../locations/authority-key-codec';
@@ -332,55 +333,34 @@ export class ZLinkUserSpotCreationCoordinator {
332
333
 
333
334
  async close(
334
335
  spot: SpotRef,
335
- closeOwner: (current: SpotRef, snapshot: ZLinkAuthoritySnapshot) => Promise<boolean>,
336
- signal?: AbortSignal,
337
- ownerPresent?: (current: SpotRef) => boolean,
338
- ownerCanClose?: (current: SpotRef) => boolean
336
+ closeOwner: (
337
+ current: SpotRef,
338
+ beginAuthority: (onCommitted: () => void) => Promise<{ release(): Promise<void> }>
339
+ ) => Promise<boolean>,
340
+ signal?: AbortSignal
339
341
  ): Promise<boolean> {
340
342
  const resolved = await this.resolveCloseTarget(spot, signal);
341
343
  if (resolved === undefined) return false;
342
344
  const { spot: currentRef, snapshot: current } = resolved;
343
345
  const key = encodeAuthorityKey('user_spot', String(spot.spotId));
344
- if (ownerPresent?.(currentRef) === false) {
345
- throw createInternalFrameworkException(
346
- ZLinkFrameworkInternalErrorKind.SpotMoving,
347
- `User Spot '${String(spot.spotId)}' is missing from its authority owner.`,
348
- true
349
- );
350
- }
351
- if (ownerCanClose?.(currentRef) === false) return false;
352
- const closing = await this.beginClosing(key, current, String(spot.spotId), signal);
353
- let closed: boolean;
354
- try {
355
- closed = await closeOwner(currentRef, closing);
356
- } catch (error) {
357
- await this.restoreReady(key, closing, String(spot.spotId), signal);
358
- throw error;
359
- }
360
- if (!closed) {
361
- await this.restoreReady(key, closing, String(spot.spotId), signal);
362
- return false;
363
- }
364
- const deleted = await this.options.store.compareExchangeAuthority(
365
- key,
366
- closing.storeVersion,
367
- { kind: 'delete' },
368
- signal
346
+ return await closeOwner(currentRef, async (onCommitted) =>
347
+ this.commitCloseAuthority(key, current, String(spot.spotId), onCommitted)
369
348
  );
370
- if (deleted.kind === 'deleted') {
371
- this.forgetReadyRoute(currentRef.spotId, current);
372
- return true;
373
- }
374
- if (deleted.kind === 'conflict') {
375
- throw createInternalFrameworkException(
376
- ZLinkFrameworkInternalErrorKind.SpotMoving,
377
- `User Spot '${String(spot.spotId)}' authority changed while closing.`,
378
- true
379
- );
380
- }
381
- throw createInternalFrameworkException(
382
- ZLinkFrameworkInternalErrorKind.SpotGenerationStale,
383
- `User Spot '${String(spot.spotId)}' generation cannot be closed.`
349
+ }
350
+
351
+ /** Commits Closing for the owner's own incarnation when its context requests Close. */
352
+ async beginOwnerClose(
353
+ spot: SpotRef,
354
+ onCommitted: () => void
355
+ ): Promise<{ release(): Promise<void> } | undefined> {
356
+ const resolved = await this.resolveCloseTarget(spot);
357
+ if (resolved === undefined) return undefined;
358
+ const spotId = String(spot.spotId);
359
+ return await this.commitCloseAuthority(
360
+ encodeAuthorityKey('user_spot', spotId),
361
+ resolved.snapshot,
362
+ spotId,
363
+ onCommitted
384
364
  );
385
365
  }
386
366
 
@@ -391,29 +371,14 @@ export class ZLinkUserSpotCreationCoordinator {
391
371
  const key = encodeAuthorityKey('user_spot', String(spot.spotId));
392
372
  const current = await this.options.store.readAuthority(key, signal);
393
373
  if (current.kind === 'missing') return undefined;
394
- if (current.objectGeneration !== spot.objectGeneration) {
395
- throw createInternalFrameworkException(
396
- ZLinkFrameworkInternalErrorKind.SpotGenerationStale,
397
- `User Spot '${String(spot.spotId)}' generation is stale.`
398
- );
399
- }
400
- const currentRef = spotRef(current, {
401
- meshName: current.allocation.descriptor.meshName,
402
- spotId: spot.spotId,
403
- stableType: current.allocation.stableType,
404
- requestPayload: Buffer.alloc(0),
405
- timeoutMs: 1
406
- });
407
- if (
408
- String(current.allocation.descriptor.rid) !== String(spot.nodeRid) ||
409
- current.allocation.descriptor.meshName !== spot.meshName
410
- ) {
411
- throw createInternalFrameworkException(
412
- ZLinkFrameworkInternalErrorKind.SpotGenerationStale,
413
- `User Spot '${String(spot.spotId)}' owner route is stale.`
414
- );
415
- }
416
- return { spot: currentRef, snapshot: current };
374
+ return {
375
+ spot: requireCloseTarget(current, String(spot.spotId), {
376
+ objectGeneration: spot.objectGeneration,
377
+ nodeRid: String(spot.nodeRid),
378
+ meshName: spot.meshName
379
+ }),
380
+ snapshot: current
381
+ };
417
382
  }
418
383
 
419
384
  async handleRemoteCreate(
@@ -672,65 +637,71 @@ export class ZLinkUserSpotCreationCoordinator {
672
637
 
673
638
  async handleRemoteClose(
674
639
  record: ServiceUserSpotCloseRecord,
675
- closeOwner: (spot: SpotRef, signal?: AbortSignal) => Promise<boolean>,
676
- signal?: AbortSignal
640
+ closeOwner: (
641
+ spot: SpotRef,
642
+ beginAuthority: (onCommitted: () => void) => Promise<{ release(): Promise<void> }>,
643
+ signal?: AbortSignal
644
+ ) => Promise<boolean>,
645
+ signal?: AbortSignal,
646
+ canResume?: (spot: SpotRef) => boolean
677
647
  ): Promise<boolean> {
678
648
  const key = encodeAuthorityKey('user_spot', record.target.spotId);
679
649
  const current = await this.options.store.readAuthority(key, signal);
680
650
  if (current.kind === 'missing') return false;
681
- if (current.objectGeneration !== record.target.objectGeneration) {
682
- throw createInternalFrameworkException(
683
- ZLinkFrameworkInternalErrorKind.SpotGenerationStale,
684
- `User Spot '${record.target.spotId}' generation is stale.`
685
- );
686
- }
687
- if (
688
- current.authorityOwnerGeneration !== record.target.authorityOwnerGeneration ||
689
- current.storeVersion.value !== record.target.expectedStoreVersion ||
690
- String(current.allocation.descriptor.rid) !== record.target.targetNodeRid ||
691
- current.allocation.descriptorLifecycleGeneration !== record.target.targetNodeGeneration ||
692
- current.allocation.objectKind !== 'user_spot' ||
693
- current.allocation.state !== 'active'
694
- ) {
695
- throw createInternalFrameworkException(
696
- ZLinkFrameworkInternalErrorKind.SpotMoving,
697
- `User Spot '${record.target.spotId}' close authority is moving.`,
698
- true
699
- );
700
- }
701
- const spot: SpotRef = {
651
+ const candidate: SpotRef = {
702
652
  spotId: record.target.spotId as RoutingId,
703
653
  objectGeneration: current.objectGeneration,
704
654
  meshName: current.allocation.descriptor.meshName,
705
655
  nodeRid: current.allocation.descriptor.rid
706
656
  };
707
- const closing = await this.beginClosing(key, current, record.target.spotId, signal);
708
- let closed: boolean;
709
- try {
710
- closed = await closeOwner(spot, signal);
711
- } catch (error) {
712
- await this.restoreReady(key, closing, record.target.spotId, signal);
713
- throw error;
714
- }
715
- if (!closed) {
716
- await this.restoreReady(key, closing, record.target.spotId, signal);
717
- return false;
718
- }
719
- const deleted = await this.options.store.compareExchangeAuthority(
720
- key,
721
- closing.storeVersion,
722
- { kind: 'delete' },
657
+ // A Closing committed by this owner is resumed, so its store version has
658
+ // advanced past the one the source observed.
659
+ const resuming =
660
+ canResume?.(candidate) === true &&
661
+ decodeServiceClosingSpotAuthority(current.payload)?.kind === 'user_spot';
662
+ const spot = requireCloseTarget(current, record.target.spotId, {
663
+ objectGeneration: record.target.objectGeneration,
664
+ nodeRid: record.target.targetNodeRid,
665
+ nodeGeneration: record.target.targetNodeGeneration,
666
+ authorityOwnerGeneration: record.target.authorityOwnerGeneration,
667
+ storeVersion: resuming ? undefined : record.target.expectedStoreVersion
668
+ });
669
+ return await closeOwner(
670
+ spot,
671
+ async (onCommitted) =>
672
+ this.commitCloseAuthority(key, current, record.target.spotId, onCommitted),
723
673
  signal
724
674
  );
725
- if (deleted.kind === 'deleted') {
726
- this.forgetReadyRoute(record.target.spotId as RoutingId, current);
727
- return true;
728
- }
729
- throw createInternalFrameworkException(
730
- ZLinkFrameworkInternalErrorKind.SpotMoving,
731
- `User Spot '${record.target.spotId}' authority changed while closing.`,
732
- true
733
- );
675
+ }
676
+
677
+ private async commitCloseAuthority(
678
+ key: ReturnType<typeof encodeAuthorityKey>,
679
+ current: ZLinkAuthoritySnapshot,
680
+ spotId: string,
681
+ onCommitted: () => void
682
+ ): Promise<{ release(): Promise<void> }> {
683
+ const closing = await this.beginClosing(key, current, spotId);
684
+ const authority = {
685
+ release: async () => {
686
+ this.forgetReadyRoute(spotId as RoutingId, current);
687
+ const deleted = await this.options.store.compareExchangeAuthority(
688
+ key,
689
+ closing.storeVersion,
690
+ {
691
+ kind: 'delete'
692
+ }
693
+ );
694
+ if (deleted.kind !== 'deleted') {
695
+ throw createInternalFrameworkException(
696
+ ZLinkFrameworkInternalErrorKind.SpotMoving,
697
+ `User Spot '${spotId}' authority changed while closing.`,
698
+ true
699
+ );
700
+ }
701
+ }
702
+ };
703
+ onCommitted();
704
+ return authority;
734
705
  }
735
706
 
736
707
  forgetReadyRoute(spotId: RoutingId, snapshot: ZLinkAuthoritySnapshot): void {
@@ -743,8 +714,7 @@ export class ZLinkUserSpotCreationCoordinator {
743
714
  private async beginClosing(
744
715
  key: ReturnType<typeof encodeAuthorityKey>,
745
716
  current: ZLinkAuthoritySnapshot,
746
- spotId: string,
747
- signal?: AbortSignal
717
+ spotId: string
748
718
  ): Promise<ZLinkAuthoritySnapshot> {
749
719
  const ready = decodeServiceReadySpotAuthority(current.payload);
750
720
  if (
@@ -766,7 +736,7 @@ export class ZLinkUserSpotCreationCoordinator {
766
736
  payload: userSpotAuthorityPayload(current, spotId, 'closing'),
767
737
  generationTransition: 'preserve'
768
738
  },
769
- signal
739
+ undefined
770
740
  );
771
741
  if (closing.kind === 'stored') {
772
742
  const { kind: _kind, ...snapshot } = closing;
@@ -779,31 +749,6 @@ export class ZLinkUserSpotCreationCoordinator {
779
749
  );
780
750
  }
781
751
 
782
- private async restoreReady(
783
- key: ReturnType<typeof encodeAuthorityKey>,
784
- closing: ZLinkAuthoritySnapshot,
785
- spotId: string,
786
- signal?: AbortSignal
787
- ): Promise<void> {
788
- const restored = await this.options.store.compareExchangeAuthority(
789
- key,
790
- closing.storeVersion,
791
- {
792
- kind: 'put',
793
- payload: userSpotAuthorityPayload(closing, spotId, 'ready'),
794
- generationTransition: 'preserve'
795
- },
796
- signal
797
- );
798
- if (restored.kind !== 'stored') {
799
- throw createInternalFrameworkException(
800
- ZLinkFrameworkInternalErrorKind.SpotMoving,
801
- 'User Spot Ready authority could not be restored after close was declined.',
802
- true
803
- );
804
- }
805
- }
806
-
807
752
  private async awaitReady(
808
753
  request: ZLinkUserSpotCreationRequest,
809
754
  signal: AbortSignal
@@ -926,6 +871,66 @@ function readySpotRoute(
926
871
  };
927
872
  }
928
873
 
874
+ /** The Close fence a caller observed; omitted parts are not part of that caller's fence. */
875
+ interface ZLinkUserSpotCloseFence {
876
+ readonly objectGeneration: bigint;
877
+ readonly nodeRid: string;
878
+ readonly meshName?: string;
879
+ readonly nodeGeneration?: bigint;
880
+ readonly authorityOwnerGeneration?: bigint;
881
+ readonly storeVersion?: string;
882
+ }
883
+
884
+ /**
885
+ * The single classifier of a Close target (§7, §9): another ObjectGeneration is
886
+ * SpotGenerationStale; any owner fence difference or an authority that is not
887
+ * in a Framework-owned Ready or Closing state is SpotMoving.
888
+ */
889
+ function requireCloseTarget(
890
+ current: ZLinkAuthoritySnapshot,
891
+ spotId: string,
892
+ fence: ZLinkUserSpotCloseFence
893
+ ): SpotRef {
894
+ if (current.objectGeneration !== fence.objectGeneration) {
895
+ throw createInternalFrameworkException(
896
+ ZLinkFrameworkInternalErrorKind.SpotGenerationStale,
897
+ `User Spot '${spotId}' generation is stale.`
898
+ );
899
+ }
900
+ // A committed Closing stays the close target: its owner resumes that Close.
901
+ const decoded =
902
+ decodeServiceReadySpotAuthority(current.payload) ??
903
+ decodeServiceClosingSpotAuthority(current.payload);
904
+ const allocation = current.allocation;
905
+ if (
906
+ decoded?.kind !== 'user_spot' ||
907
+ decoded.spotId !== spotId ||
908
+ decoded.ownerId !== current.ownerId ||
909
+ decoded.ownerLeaseGeneration !== current.ownerLeaseGeneration ||
910
+ allocation.objectKind !== 'user_spot' ||
911
+ allocation.state !== 'active' ||
912
+ String(allocation.descriptor.rid) !== fence.nodeRid ||
913
+ (fence.meshName !== undefined && allocation.descriptor.meshName !== fence.meshName) ||
914
+ (fence.nodeGeneration !== undefined &&
915
+ allocation.descriptorLifecycleGeneration !== fence.nodeGeneration) ||
916
+ (fence.authorityOwnerGeneration !== undefined &&
917
+ current.authorityOwnerGeneration !== fence.authorityOwnerGeneration) ||
918
+ (fence.storeVersion !== undefined && current.storeVersion.value !== fence.storeVersion)
919
+ ) {
920
+ throw createInternalFrameworkException(
921
+ ZLinkFrameworkInternalErrorKind.SpotMoving,
922
+ `User Spot '${spotId}' owner fence changed or the Spot is moving.`,
923
+ true
924
+ );
925
+ }
926
+ return {
927
+ spotId: spotId as RoutingId,
928
+ objectGeneration: current.objectGeneration,
929
+ meshName: allocation.descriptor.meshName,
930
+ nodeRid: allocation.descriptor.rid
931
+ };
932
+ }
933
+
929
934
  function spotRef(
930
935
  snapshot: ZLinkAuthoritySnapshot,
931
936
  request: ZLinkUserSpotCreationRequest,
@@ -70,12 +70,10 @@ export class ZLinkInMemoryProviderLocationStore implements ZLinkLocationStore {
70
70
  this.values.delete(mutation.key.value);
71
71
  continue;
72
72
  }
73
- requireValue(mutation.bytes, mutation.retentionMs);
73
+ const retentionMs = requireValue(mutation.bytes, mutation.retentionMs);
74
74
  const version = storeVersion((++this.nextVersion).toString());
75
75
  const expiresAt =
76
- mutation.retentionMs === undefined
77
- ? undefined
78
- : new Date(storeNow.getTime() + mutation.retentionMs);
76
+ retentionMs === undefined ? undefined : new Date(storeNow.getTime() + retentionMs);
79
77
  this.values.set(mutation.key.value, {
80
78
  bytes: mutation.bytes.slice(),
81
79
  version,
@@ -195,13 +193,19 @@ function requireScanRequest(request: ZLinkStoreScanRequest): void {
195
193
  }
196
194
  }
197
195
 
198
- function requireValue(bytes: Uint8Array, retentionMs: number | undefined): void {
196
+ function requireValue(bytes: Uint8Array, retentionMs: number | undefined): number | undefined {
199
197
  if (bytes.byteLength > 1024 * 1024) {
200
198
  throw new RangeError('Location Store value exceeds 1 MiB.');
201
199
  }
202
- if (retentionMs !== undefined && (!Number.isSafeInteger(retentionMs) || retentionMs < 1)) {
203
- throw new RangeError('Location Store retention must be a positive safe integer.');
200
+ if (
201
+ retentionMs !== undefined &&
202
+ (!Number.isFinite(retentionMs) ||
203
+ retentionMs <= 0 ||
204
+ !Number.isSafeInteger(Math.ceil(retentionMs)))
205
+ ) {
206
+ throw new RangeError('Location Store retention must round to a positive safe integer.');
204
207
  }
208
+ return retentionMs === undefined ? undefined : Math.ceil(retentionMs);
205
209
  }
206
210
 
207
211
  function requireKey(value: string): void {
@@ -226,9 +226,10 @@ export class ZLinkLocationLifecycle {
226
226
 
227
227
  async beginInstanceSpotClosing(
228
228
  meshName: string,
229
- spotId: RoutingId
229
+ spotId: RoutingId,
230
+ onCommitted: () => void
230
231
  ): Promise<ZLinkInstanceClosingAuthority | undefined> {
231
- return await this.spotClaims.beginInstanceClosing(meshName, spotId);
232
+ return await this.spotClaims.beginInstanceClosing(meshName, spotId, onCommitted);
232
233
  }
233
234
 
234
235
  trackInstanceSpot(input: ZLinkTrackedInstanceAuthority): void {
@@ -29,7 +29,10 @@ import type {
29
29
  ZLinkRouteLocationStore,
30
30
  ZLinkSpotLocationStore
31
31
  } from './internal-store-contracts';
32
- import { decodeServiceReadySpotAuthority } from '../foundation/service-authority-payload-codec';
32
+ import {
33
+ decodeServiceClosingSpotAuthority,
34
+ decodeServiceReadySpotAuthority
35
+ } from '../foundation/service-authority-payload-codec';
33
36
  import { serviceRelocationAuthorityApplicationPayload } from '../foundation/service-relocation-runtime';
34
37
  import { decodeActorAuthorityIdentity } from '../actors/actor-authority-publication';
35
38
  import { encodeAuthorityKey } from './authority-key-codec';
@@ -930,9 +933,10 @@ export class ZLinkAuthoritySpotRouteResolver implements ZLinkSpotRouteResolver {
930
933
 
931
934
  const current = await this.store.readAuthority(encodeAuthorityKey('user_spot', key), signal);
932
935
  if (current.kind === 'snapshot' && current.allocation.state === 'active') {
933
- const decoded = decodeServiceReadySpotAuthority(
934
- serviceRelocationAuthorityApplicationPayload(current.payload)
935
- );
936
+ const authorityPayload = serviceRelocationAuthorityApplicationPayload(current.payload);
937
+ const ready = decodeServiceReadySpotAuthority(authorityPayload);
938
+ const closing = decodeServiceClosingSpotAuthority(authorityPayload);
939
+ const decoded = ready ?? closing;
936
940
  if (
937
941
  decoded !== undefined &&
938
942
  decoded.spotId === String(spotId) &&
@@ -975,15 +979,17 @@ export class ZLinkAuthoritySpotRouteResolver implements ZLinkSpotRouteResolver {
975
979
  signal
976
980
  )
977
981
  };
978
- await this.lane.run(() =>
979
- this.cacheResolutionCore(
980
- key,
981
- prepared.epoch,
982
- target,
983
- remainingLeaseMs,
984
- current.storeVersion.value
985
- )
986
- );
982
+ if (ready !== undefined) {
983
+ await this.lane.run(() =>
984
+ this.cacheResolutionCore(
985
+ key,
986
+ prepared.epoch,
987
+ target,
988
+ remainingLeaseMs,
989
+ current.storeVersion.value
990
+ )
991
+ );
992
+ }
987
993
  return target;
988
994
  }
989
995
  }
@@ -126,7 +126,8 @@ export class ZLinkSpotLocationClaims {
126
126
 
127
127
  async beginInstanceClosing(
128
128
  meshName: string,
129
- spotId: RoutingId
129
+ spotId: RoutingId,
130
+ onCommitted: () => void
130
131
  ): Promise<ZLinkInstanceClosingAuthority | undefined> {
131
132
  const canonical = ZLinkLocationKeyCodec.encodeSpotKey({ meshName, spotId });
132
133
  const tracked = this.spots.get(canonical);
@@ -166,39 +167,9 @@ export class ZLinkSpotLocationClaims {
166
167
  return undefined;
167
168
  }
168
169
  tracked.storeVersion = result.storeVersion.value;
169
- this.invalidateSpotRoute?.(tracked.spotId);
170
- let restored = false;
170
+ onCommitted();
171
171
  return {
172
- restoreReady: async () => {
173
- if (restored) return;
174
- const restore = await store.compareExchangeAuthority(key, result.storeVersion, {
175
- kind: 'put',
176
- generationTransition: 'preserve',
177
- payload: replaceServiceRelocationAuthorityApplicationPayload(
178
- current.payload,
179
- encodeServiceInstanceAuthorityPayload({
180
- state: 'ready',
181
- stableType: decoded.stableType,
182
- spotId: decoded.spotId,
183
- ownerId: decoded.ownerId,
184
- ownerLeaseGeneration: decoded.ownerLeaseGeneration,
185
- ownerMeshName: decoded.ownerMeshName,
186
- ownerNodeRid: decoded.ownerNodeRid,
187
- ownerNodeGeneration: decoded.ownerNodeGeneration
188
- })
189
- )
190
- });
191
- if (restore.kind !== 'stored') {
192
- throw createInternalFrameworkException(
193
- ZLinkFrameworkInternalErrorKind.SpotMoving,
194
- `Instance Spot '${String(spotId)}' Ready authority could not be restored after idle eviction was declined.`,
195
- true
196
- );
197
- }
198
- restored = true;
199
- tracked.storeVersion = restore.storeVersion.value;
200
- this.invalidateSpotRoute?.(tracked.spotId);
201
- }
172
+ release: async () => undefined
202
173
  };
203
174
  }
204
175
 
@@ -382,7 +353,7 @@ export interface ZLinkTrackedInstanceAuthority {
382
353
  }
383
354
 
384
355
  export interface ZLinkInstanceClosingAuthority {
385
- restoreReady(): Promise<void>;
356
+ release(): Promise<void>;
386
357
  }
387
358
 
388
359
  interface TrackedLegacySpot {