@zlink-systems/framework 0.29.1 → 0.30.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 (152) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/Builders.d.ts +1 -2
  3. package/dist/contracts/Configuration/Configs.d.ts +0 -5
  4. package/dist/contracts/Configuration/RegistrationBuilders.js +6 -4
  5. package/dist/contracts/Configuration/RegistrationTypes.d.ts +1 -6
  6. package/dist/contracts/Configuration/RegistrationValidators.js +4 -4
  7. package/dist/contracts/Configuration/index.d.ts +1 -1
  8. package/dist/internal.d.ts +1 -0
  9. package/dist/internal.js +3 -1
  10. package/dist/runtime/actors/actor-handoff.d.ts +3 -2
  11. package/dist/runtime/actors/actor-handoff.js +9 -4
  12. package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +3 -1
  13. package/dist/runtime/application-jobs/application-ingress-record-owner.js +6 -1
  14. package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +3 -1
  15. package/dist/runtime/application-jobs/application-job-queue-scope.js +5 -0
  16. package/dist/runtime/application-jobs/contracts.d.ts +5 -1
  17. package/dist/runtime/backend/contracts/index.d.ts +10 -19
  18. package/dist/runtime/backend/mesh-completion-table.d.ts +6 -4
  19. package/dist/runtime/backend/mesh-completion-table.js +28 -18
  20. package/dist/runtime/backend/mesh-dispatch-pump.js +21 -25
  21. package/dist/runtime/backend/node/node-backend-adapter-factory.js +6 -1
  22. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +3 -3
  23. package/dist/runtime/backend/node/node-backend-adapter-support.js +24 -29
  24. package/dist/runtime/backend/node/node-mesh-backend-adapter.d.ts +1 -9
  25. package/dist/runtime/backend/node/node-mesh-backend-adapter.js +1 -1
  26. package/dist/runtime/backend/node/node-raw-binding-port.js +11 -6
  27. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +10 -12
  28. package/dist/runtime/backend/node/node-raw-mesh-backend.js +64 -98
  29. package/dist/runtime/backend/node/node-socket-backend-adapter.js +10 -5
  30. package/dist/runtime/backend/raw-binding-port.d.ts +9 -2
  31. package/dist/runtime/channels/channel-receive-loops.js +3 -3
  32. package/dist/runtime/channels/channel-runtime-lifecycle.js +2 -1
  33. package/dist/runtime/channels/channel-socket-registry.d.ts +10 -3
  34. package/dist/runtime/channels/channel-socket-registry.js +59 -82
  35. package/dist/runtime/diagnostics/message-flow.d.ts +1 -0
  36. package/dist/runtime/diagnostics/message-flow.js +3 -3
  37. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +15 -1
  38. package/dist/runtime/foundation/raw-service-mesh-runtime.js +193 -64
  39. package/dist/runtime/foundation/service-liveness-registry.d.ts +20 -8
  40. package/dist/runtime/foundation/service-liveness-registry.js +57 -44
  41. package/dist/runtime/foundation/service-mailbox.d.ts +3 -0
  42. package/dist/runtime/foundation/service-mailbox.js +2 -1
  43. package/dist/runtime/foundation/service-stateful-runtime.d.ts +7 -6
  44. package/dist/runtime/foundation/service-stateful-runtime.js +34 -23
  45. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +5 -2
  46. package/dist/runtime/foundation/service-stateful-wire-codec.js +43 -61
  47. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -3
  48. package/dist/runtime/foundation/service-topology-registry.js +9 -4
  49. package/dist/runtime/host/actor-packet-relay.js +7 -7
  50. package/dist/runtime/host/actor-runtime-options-factory.js +1 -1
  51. package/dist/runtime/host/application-job-queue.d.ts +11 -4
  52. package/dist/runtime/host/application-job-queue.js +66 -10
  53. package/dist/runtime/host/entry-actor-runtime.js +1 -1
  54. package/dist/runtime/host/index.js +15 -11
  55. package/dist/runtime/host/instance-activation-authority.d.ts +2 -1
  56. package/dist/runtime/host/instance-activation-authority.js +89 -25
  57. package/dist/runtime/host/service-relocation-host-runtime.js +2 -2
  58. package/dist/runtime/host/spot-address-transport.d.ts +1 -0
  59. package/dist/runtime/host/spot-address-transport.js +25 -12
  60. package/dist/runtime/host/spot-runtime-options-factory.js +2 -2
  61. package/dist/runtime/host/user-spot-creation-coordinator.d.ts +1 -0
  62. package/dist/runtime/host/user-spot-creation-coordinator.js +1 -0
  63. package/dist/runtime/locations/domain-store-contract.d.ts +2 -2
  64. package/dist/runtime/locations/in-memory-authority-store.d.ts +4 -2
  65. package/dist/runtime/locations/in-memory-authority-store.js +80 -8
  66. package/dist/runtime/locations/in-memory-location-store.d.ts +2 -2
  67. package/dist/runtime/locations/in-memory-location-store.js +19 -3
  68. package/dist/runtime/locations/internal-location-contracts.d.ts +3 -0
  69. package/dist/runtime/locations/location-store-repository.d.ts +3 -2
  70. package/dist/runtime/locations/location-store-repository.js +165 -22
  71. package/dist/runtime/locations/resolvers.d.ts +2 -1
  72. package/dist/runtime/locations/resolvers.js +10 -1
  73. package/dist/runtime/messaging/submission-result.d.ts +1 -1
  74. package/dist/runtime/messaging/submission-result.js +2 -2
  75. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +2 -0
  76. package/dist/runtime/spots/index.d.ts +4 -3
  77. package/dist/runtime/spots/index.js +14 -14
  78. package/dist/runtime/spots/spot-activation.d.ts +2 -1
  79. package/dist/runtime/spots/spot-activation.js +4 -4
  80. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +2 -1
  81. package/dist/runtime/spots/spot-actor-admission-coordinator.js +11 -10
  82. package/dist/runtime/spots/spot-actor-packet-dispatch.d.ts +4 -1
  83. package/dist/runtime/spots/spot-actor-packet-dispatch.js +7 -5
  84. package/dist/runtime/spots/spot-actor-packet-drain.js +2 -0
  85. package/dist/runtime/spots/spot-actor-packet-relay-dispatch.js +1 -0
  86. package/dist/runtime/spots/spot-entry-activation.js +3 -1
  87. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +2 -1
  88. package/dist/runtime/spots/spot-node-runtime-manager.js +4 -11
  89. package/dist/runtime/spots/spot-routing-internal.d.ts +6 -0
  90. package/dist/runtime/spots/spot-runtime-ports.d.ts +2 -1
  91. package/dist/runtime/streams/stream-session-runtime.js +1 -1
  92. package/package.json +3 -3
  93. package/src/contracts/Configuration/Builders.ts +1 -2
  94. package/src/contracts/Configuration/Configs.ts +0 -6
  95. package/src/contracts/Configuration/RegistrationBuilders.ts +8 -12
  96. package/src/contracts/Configuration/RegistrationTypes.ts +1 -6
  97. package/src/contracts/Configuration/RegistrationValidators.ts +9 -10
  98. package/src/contracts/Configuration/index.ts +1 -6
  99. package/src/internal.ts +1 -0
  100. package/src/runtime/actors/actor-handoff.ts +13 -4
  101. package/src/runtime/application-jobs/application-ingress-record-owner.ts +12 -2
  102. package/src/runtime/application-jobs/application-job-queue-scope.ts +6 -1
  103. package/src/runtime/application-jobs/contracts.ts +14 -1
  104. package/src/runtime/backend/contracts/index.ts +10 -18
  105. package/src/runtime/backend/mesh-completion-table.ts +36 -19
  106. package/src/runtime/backend/mesh-dispatch-pump.ts +19 -22
  107. package/src/runtime/backend/node/node-backend-adapter-factory.ts +12 -5
  108. package/src/runtime/backend/node/node-backend-adapter-support.ts +31 -29
  109. package/src/runtime/backend/node/node-mesh-backend-adapter.ts +3 -10
  110. package/src/runtime/backend/node/node-raw-binding-port.ts +19 -10
  111. package/src/runtime/backend/node/node-raw-mesh-backend.ts +81 -111
  112. package/src/runtime/backend/node/node-socket-backend-adapter.ts +12 -8
  113. package/src/runtime/backend/raw-binding-port.ts +12 -2
  114. package/src/runtime/channels/channel-receive-loops.ts +3 -3
  115. package/src/runtime/channels/channel-runtime-lifecycle.ts +7 -1
  116. package/src/runtime/channels/channel-socket-registry.ts +84 -130
  117. package/src/runtime/diagnostics/message-flow.ts +1 -1
  118. package/src/runtime/foundation/raw-service-mesh-runtime.ts +268 -70
  119. package/src/runtime/foundation/service-liveness-registry.ts +68 -59
  120. package/src/runtime/foundation/service-mailbox.ts +7 -1
  121. package/src/runtime/foundation/service-stateful-runtime.ts +61 -35
  122. package/src/runtime/foundation/service-stateful-wire-codec.ts +55 -79
  123. package/src/runtime/foundation/service-topology-registry.ts +13 -7
  124. package/src/runtime/host/actor-packet-relay.ts +7 -0
  125. package/src/runtime/host/actor-runtime-options-factory.ts +1 -0
  126. package/src/runtime/host/application-job-queue.ts +77 -10
  127. package/src/runtime/host/entry-actor-runtime.ts +1 -0
  128. package/src/runtime/host/index.ts +24 -5
  129. package/src/runtime/host/instance-activation-authority.ts +132 -27
  130. package/src/runtime/host/service-relocation-host-runtime.ts +2 -0
  131. package/src/runtime/host/spot-address-transport.ts +45 -22
  132. package/src/runtime/host/spot-runtime-options-factory.ts +2 -0
  133. package/src/runtime/host/user-spot-creation-coordinator.ts +8 -0
  134. package/src/runtime/locations/domain-store-contract.ts +6 -2
  135. package/src/runtime/locations/in-memory-authority-store.ts +113 -9
  136. package/src/runtime/locations/in-memory-location-store.ts +25 -3
  137. package/src/runtime/locations/internal-location-contracts.ts +4 -0
  138. package/src/runtime/locations/location-store-repository.ts +220 -15
  139. package/src/runtime/locations/resolvers.ts +25 -2
  140. package/src/runtime/messaging/submission-result.ts +2 -2
  141. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +2 -0
  142. package/src/runtime/spots/index.ts +16 -0
  143. package/src/runtime/spots/spot-activation.ts +5 -0
  144. package/src/runtime/spots/spot-actor-admission-coordinator.ts +13 -0
  145. package/src/runtime/spots/spot-actor-packet-dispatch.ts +10 -0
  146. package/src/runtime/spots/spot-actor-packet-drain.ts +2 -0
  147. package/src/runtime/spots/spot-actor-packet-relay-dispatch.ts +1 -0
  148. package/src/runtime/spots/spot-entry-activation.ts +3 -0
  149. package/src/runtime/spots/spot-node-runtime-manager.ts +7 -12
  150. package/src/runtime/spots/spot-routing-internal.ts +14 -0
  151. package/src/runtime/spots/spot-runtime-ports.ts +2 -0
  152. package/src/runtime/streams/stream-session-runtime.ts +1 -1
@@ -1,4 +1,5 @@
1
1
  import { ZLINK_MAX_IDENTITY_TEXT_BYTES } from '../../contracts/Common/CoreTypes';
2
+ import type { ServiceLivenessConnection } from './service-liveness-registry';
2
3
  import {
3
4
  ZLINK_MAX_PUBLIC_WEIGHT,
4
5
  isValidCapacity,
@@ -43,7 +44,8 @@ export interface AdmittedServicePeer {
43
44
  readonly descriptor: ServiceNodeDescriptor;
44
45
  // The Core selected-route generation this admission belongs to (Core ROUTER
45
46
  // §10.1), as an opaque token compared only for equality.
46
- readonly connectionId: string;
47
+ readonly connectionId: string | bigint;
48
+ readonly liveness?: ServiceLivenessConnection;
47
49
  }
48
50
 
49
51
  export interface ServicePeerAdmissionExpectation {
@@ -103,12 +105,14 @@ export class ServiceTopologyRegistry {
103
105
 
104
106
  admit(
105
107
  descriptor: ServiceNodeDescriptor,
106
- connectionId: string,
107
- expected?: ServicePeerAdmissionExpectation
108
+ connectionId: string | bigint,
109
+ expected?: ServicePeerAdmissionExpectation,
110
+ admitLiveness?: () => ServiceLivenessConnection
108
111
  ): PeerAdmissionResult {
109
112
  try {
110
113
  validateDescriptor(descriptor);
111
- requireText(connectionId, 'connectionId');
114
+ if (typeof connectionId === 'string') requireText(connectionId, 'connectionId');
115
+ else if (connectionId === 0n) throw new TypeError('connectionId must be nonzero.');
112
116
  } catch (error) {
113
117
  return 'invalidDescriptor';
114
118
  }
@@ -154,14 +158,15 @@ export class ServiceTopologyRegistry {
154
158
  // that route replaces the admission of a route it replaced.
155
159
  this.peersByRid.set(descriptor.nodeRoutingId, {
156
160
  descriptor: cloneDescriptor(descriptor),
157
- connectionId
161
+ connectionId,
162
+ liveness: admitLiveness?.() ?? current?.liveness
158
163
  });
159
164
  this.remember(descriptor);
160
165
  this.rebuildSelections();
161
166
  return 'admitted';
162
167
  }
163
168
 
164
- disconnect(nodeRoutingId: string, connectionId: string): boolean {
169
+ disconnect(nodeRoutingId: string, connectionId: string | bigint): boolean {
165
170
  const current = this.peersByRid.get(nodeRoutingId);
166
171
  if (current === undefined || current.connectionId !== connectionId) return false;
167
172
  this.peersByRid.delete(nodeRoutingId);
@@ -525,7 +530,8 @@ function cloneDescriptor(descriptor: ServiceNodeDescriptor): ServiceNodeDescript
525
530
  function clonePeer(peer: AdmittedServicePeer): AdmittedServicePeer {
526
531
  return {
527
532
  descriptor: cloneDescriptor(peer.descriptor),
528
- connectionId: peer.connectionId
533
+ connectionId: peer.connectionId,
534
+ liveness: peer.liveness
529
535
  };
530
536
  }
531
537
 
@@ -303,6 +303,7 @@ export class ZLinkActorPacketRelay {
303
303
  const dispatch =
304
304
  state?.spotId === undefined
305
305
  ? this.requireSpotNodeRuntime().dispatchEntryActorPacket(
306
+ 'remote',
306
307
  relay.actorId,
307
308
  [header, body],
308
309
  false,
@@ -310,6 +311,7 @@ export class ZLinkActorPacketRelay {
310
311
  fallbackActorRef
311
312
  )
312
313
  : this.requireSpotManager().dispatchRoutedActorPacket(
314
+ 'remote',
313
315
  state.spotId,
314
316
  relay.actorId,
315
317
  [header, body],
@@ -356,6 +358,7 @@ export class ZLinkActorPacketRelay {
356
358
  const dispatch =
357
359
  state?.spotId === undefined
358
360
  ? this.requireSpotNodeRuntime().dispatchEntryActorPacket(
361
+ 'remote',
359
362
  relay.actorId,
360
363
  [header, body],
361
364
  false,
@@ -363,6 +366,7 @@ export class ZLinkActorPacketRelay {
363
366
  fallbackActorRef
364
367
  )
365
368
  : this.requireSpotManager().dispatchRoutedActorPacket(
369
+ 'remote',
366
370
  state.spotId,
367
371
  relay.actorId,
368
372
  [header, body],
@@ -423,6 +427,7 @@ export class ZLinkActorPacketRelay {
423
427
  const response =
424
428
  state?.spotId === undefined
425
429
  ? await this.requireSpotNodeRuntime().dispatchEntryActorPacket(
430
+ 'remote',
426
431
  relay.actorId,
427
432
  [header, body],
428
433
  relay.returnResponse === true,
@@ -430,6 +435,7 @@ export class ZLinkActorPacketRelay {
430
435
  fallbackActorRef
431
436
  )
432
437
  : await this.requireSpotManager().dispatchRoutedActorPacket(
438
+ 'remote',
433
439
  state.spotId,
434
440
  relay.actorId,
435
441
  [header, body],
@@ -892,6 +898,7 @@ export class ZLinkActorPacketRelay {
892
898
  this.options.detachedTaskRunner.runDetached('local actor packet relay', async () => {
893
899
  try {
894
900
  const response = await this.requireSpotManager().dispatchRoutedActorPacket(
901
+ 'local',
895
902
  spotId,
896
903
  actor.actorId,
897
904
  [header, body],
@@ -280,6 +280,7 @@ export class ZLinkActorRuntimeOptionsFactory {
280
280
  this.actorBelongsToMesh(meshName, actorId) &&
281
281
  !this.options.actorHandoff.isProvisional(actorId)
282
282
  ? this.options.actorHandoff.capture(
283
+ 'local',
283
284
  actorId,
284
285
  parts,
285
286
  returnResponse,
@@ -1,3 +1,5 @@
1
+ import { SeverityNumber } from '@opentelemetry/api-logs';
2
+ import { telemetryLogger } from '../diagnostics/message-flow';
1
3
  import { MILLISECONDS_PER_SECOND } from '../diagnostics/runtime-metrics';
2
4
  import { ZLinkApplicationJobQueueProfile } from '../../contracts/Dispatch';
3
5
  import {
@@ -11,6 +13,7 @@ import {
11
13
  import { readFileSync } from 'node:fs';
12
14
  import { availableParallelism } from 'node:os';
13
15
  import type {
16
+ ApplicationJobOrigin,
14
17
  ApplicationJobPermitPort,
15
18
  ApplicationJobQueuePort
16
19
  } from '../application-jobs/contracts';
@@ -122,7 +125,10 @@ export function resolveApplicationJobQueueConfiguration(
122
125
  export class ApplicationJobQueuePermit implements ApplicationJobPermitPort {
123
126
  private state: 'reserved' | 'queued' | 'released' = 'reserved';
124
127
 
125
- constructor(private readonly owner: ApplicationJobQueue) {}
128
+ constructor(
129
+ private readonly owner: ApplicationJobQueue,
130
+ readonly origin: ApplicationJobOrigin
131
+ ) {}
126
132
 
127
133
  markApplicationQueued(): void {
128
134
  if (this.state !== 'reserved') {
@@ -149,6 +155,7 @@ export class ApplicationJobQueuePermit implements ApplicationJobPermitPort {
149
155
  }
150
156
 
151
157
  interface CapacityWaiter {
158
+ readonly origin: ApplicationJobOrigin;
152
159
  readonly resolve: (permit: ApplicationJobQueuePermit) => void;
153
160
  readonly reject: (reason: unknown) => void;
154
161
  readonly signal?: AbortSignal;
@@ -164,6 +171,8 @@ interface CapacityWaiter {
164
171
  * directly to the oldest live waiter without allocating the configured limit.
165
172
  */
166
173
  export class ApplicationJobQueue implements ApplicationJobQueuePort {
174
+ private localWaiters = 0;
175
+ private localBacklogLogged = false;
167
176
  private reservedSupplyPermits = 0n;
168
177
  private queuedApplicationJobs = 0n;
169
178
  private peakPermitsInUse = 0n;
@@ -191,7 +200,8 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
191
200
  constructor(
192
201
  private readonly configuration: ApplicationJobQueueConfiguration,
193
202
  private readonly nowMs: () => number = () => performance.now(),
194
- private readonly handlerStartGate?: () => boolean
203
+ private readonly handlerStartGate?: () => boolean,
204
+ private readonly loggerFailureReporter?: (error: unknown) => void
195
205
  ) {
196
206
  this.receiveFlowController = new ApplicationJobReceiveFlowController(this, (state) => state);
197
207
  }
@@ -242,16 +252,33 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
242
252
  this.receiveFlowController.unregister(target);
243
253
  }
244
254
 
245
- acquire(signal?: AbortSignal): Promise<ApplicationJobQueuePermit> {
255
+ acquire(
256
+ signal: AbortSignal | undefined,
257
+ origin: ApplicationJobOrigin
258
+ ): Promise<ApplicationJobQueuePermit> {
259
+ return new Promise((resolve, reject) => {
260
+ const permit = this.acquireOrResume(resolve, reject, signal, origin);
261
+ if (permit !== undefined) resolve(permit);
262
+ });
263
+ }
264
+
265
+ acquireOrResume(
266
+ resume: (permit: ApplicationJobQueuePermit) => void,
267
+ reject: (reason: unknown) => void,
268
+ signal: AbortSignal | undefined,
269
+ origin: ApplicationJobOrigin
270
+ ): ApplicationJobQueuePermit | undefined {
246
271
  if (signal?.aborted === true) {
247
- return Promise.reject(abortReason(signal));
272
+ reject(abortReason(signal));
273
+ return undefined;
248
274
  }
249
275
  if (this.waiters.length === 0 && this.permitsInUse() < this.limit()) {
250
- return Promise.resolve(this.reserve());
276
+ return this.reserve(origin);
251
277
  }
252
278
 
253
- return new Promise<ApplicationJobQueuePermit>((resolve, reject) => {
279
+ new Promise<ApplicationJobQueuePermit>((resolve, reject) => {
254
280
  const waiter: CapacityWaiter = {
281
+ origin,
255
282
  resolve,
256
283
  reject,
257
284
  signal,
@@ -262,9 +289,14 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
262
289
  };
263
290
  this.waiters.push(waiter);
264
291
  this.capacityWaitCount += 1n;
292
+ const warningCount = this.updateLocalWaiters(origin, 1);
265
293
  signal?.addEventListener('abort', waiter.abort, { once: true });
266
294
  this.drainWaiters();
267
- });
295
+ if (warningCount !== undefined) this.logLocalBacklog(warningCount);
296
+ })
297
+ .then(resume)
298
+ .catch(reject);
299
+ return undefined;
268
300
  }
269
301
 
270
302
  snapshot(): ApplicationJobQueueSnapshot {
@@ -356,12 +388,15 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
356
388
  return this.reservedSupplyPermits + this.queuedApplicationJobs;
357
389
  }
358
390
 
359
- private reserve(evaluatePressure = true): ApplicationJobQueuePermit {
391
+ private reserve(
392
+ origin: ApplicationJobOrigin,
393
+ evaluatePressure = true
394
+ ): ApplicationJobQueuePermit {
360
395
  this.reservedSupplyPermits += 1n;
361
396
  const current = this.permitsInUse();
362
397
  if (current > this.peakPermitsInUse) this.peakPermitsInUse = current;
363
398
  if (evaluatePressure) this.evaluatePressure();
364
- return new ApplicationJobQueuePermit(this);
399
+ return new ApplicationJobQueuePermit(this, origin);
365
400
  }
366
401
 
367
402
  private drainWaiters(): void {
@@ -371,7 +406,7 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
371
406
  waiter.active = false;
372
407
  waiter.signal?.removeEventListener('abort', waiter.abort);
373
408
  this.recordCompletedWait(waiter);
374
- waiter.resolve(this.reserve(false));
409
+ waiter.resolve(this.reserve(waiter.origin, false));
375
410
  }
376
411
  }
377
412
 
@@ -387,11 +422,43 @@ export class ApplicationJobQueue implements ApplicationJobQueuePort {
387
422
  }
388
423
 
389
424
  private recordCompletedWait(waiter: CapacityWaiter): void {
425
+ this.updateLocalWaiters(waiter.origin, -1);
390
426
  if (waiter.metricsEpoch !== this.metricsEpoch) return;
391
427
  const durationMs = Math.max(0, this.nowMs() - waiter.startedAtMs);
392
428
  this.capacityWaitDurationSeconds += durationMs / MILLISECONDS_PER_SECOND;
393
429
  }
394
430
 
431
+ private updateLocalWaiters(origin: ApplicationJobOrigin, delta: number): number | undefined {
432
+ if (origin !== 'local') return undefined;
433
+ this.localWaiters += delta;
434
+ if (this.localWaiters === 0) this.localBacklogLogged = false;
435
+ if (!this.localBacklogLogged && this.localWaiters > Number(this.limit())) {
436
+ this.localBacklogLogged = true;
437
+ return this.localWaiters;
438
+ }
439
+ return undefined;
440
+ }
441
+
442
+ private logLocalBacklog(localWaiters: number): void {
443
+ try {
444
+ telemetryLogger.emit({
445
+ eventName: 'zlink.runtime.host.local_job_backlog_exceeded',
446
+ timestamp: new Date(),
447
+ severityNumber: SeverityNumber.WARN,
448
+ severityText: 'WARN',
449
+ body: 'Same-host jobs waiting for a host permit exceeded the application job queue maximum.',
450
+ attributes: {
451
+ source_kind: 'host',
452
+ source_name: 'zlink.runtime.host',
453
+ local_waiters: localWaiters,
454
+ effective_maximum: Number(this.limit())
455
+ }
456
+ });
457
+ } catch (error) {
458
+ this.loggerFailureReporter?.(error);
459
+ }
460
+ }
461
+
395
462
  private evaluatePressure(): void {
396
463
  const permitsInUse = this.permitsInUse();
397
464
  const next =
@@ -95,6 +95,7 @@ export class ZLinkEntryActorRuntimeService implements ZLinkEntryActorRuntime {
95
95
  return {
96
96
  handled: true,
97
97
  response: await spotManager.dispatchRoutedActorPacket(
98
+ 'remote',
98
99
  spotId,
99
100
  actorId,
100
101
  parts,
@@ -1,3 +1,5 @@
1
+ import type { ApplicationJobOrigin } from '../application-jobs/contracts';
2
+ import { currentApplicationJobOrigin } from '../application-jobs/application-job-queue-scope';
1
3
  import { listAllMeshNodeDescriptors } from '../locations/location-store-pages';
2
4
  import { ZlinkStreamContentType } from '@zlink-systems/stream-wire';
3
5
  import { randomBytes, randomUUID } from 'node:crypto';
@@ -349,7 +351,12 @@ export class ZLinkFrameworkRuntimeHost
349
351
  } catch {
350
352
  return false;
351
353
  }
352
- }
354
+ },
355
+ (error) =>
356
+ (this.errorSink ?? this.preStartErrorSink).reportRuntimeTaskException(
357
+ 'logger-provider',
358
+ error
359
+ )
353
360
  );
354
361
  this.capacityStatus = new HostCapacityStatusProjection(
355
362
  options.registration.coreHwm,
@@ -721,13 +728,13 @@ export class ZLinkFrameworkRuntimeHost
721
728
  sessionRelocationWire: () => this.serviceRelocation,
722
729
  clearRemoteActorPacketTarget: (actorId) =>
723
730
  this.boundSessionRelay.clearRemoteActorPacketTarget(actorId),
724
- prepareApplicationJob: async (preparationSignal) => {
731
+ prepareApplicationJob: async (preparationSignal, origin) => {
725
732
  const runtimeSignal = this.executionState?.abortController.signal;
726
733
  const signal =
727
734
  runtimeSignal === undefined
728
735
  ? preparationSignal
729
736
  : AbortSignal.any([runtimeSignal, preparationSignal]);
730
- const permit = await this.applicationJobQueue.acquire(signal);
737
+ const permit = await this.applicationJobQueue.acquire(signal, origin);
731
738
  let state: 'ready' | 'running' | 'closed' = 'ready';
732
739
  let closedReason: unknown;
733
740
  let abort: (() => void) | undefined;
@@ -1722,6 +1729,10 @@ export class ZLinkFrameworkRuntimeHost
1722
1729
  activationNode.registerAsyncInstanceActivationAuthority?.(
1723
1730
  new ZLinkInstanceActivationAuthority({
1724
1731
  store: locationStore,
1732
+ relocationPolicy: (stableType) =>
1733
+ this.options.registration.spotNodes.get(meshName)?.instanceSpotFactoryRegistrations?.[
1734
+ stableType
1735
+ ]?.relocation.kind,
1725
1736
  relocationStore: this.options.registration.locations.relocationStoreInstance,
1726
1737
  meshName,
1727
1738
  owner: () => this.locationOwner.currentRuntime?.currentOwnerToken,
@@ -2235,6 +2246,9 @@ export class ZLinkFrameworkRuntimeHost
2235
2246
  });
2236
2247
  const coordinator = (this.userSpotCoordinator = new ZLinkUserSpotCreationCoordinator({
2237
2248
  store: locationStore,
2249
+ relocationPolicy: (meshName, stableType) =>
2250
+ this.options.registration.spotNodes.get(meshName)?.spotFactoryRegistrations?.[stableType]
2251
+ ?.relocation.kind,
2238
2252
  publishReadyRoute: (meshName, route) => {
2239
2253
  this.cachedLocationSpotRouteResolver?.invalidate?.(route.spot.spotId);
2240
2254
  this.spotNodeRuntime?.meshNode(meshName)?.rememberSpotRoute?.(route);
@@ -2757,6 +2771,7 @@ export class ZLinkFrameworkRuntimeHost
2757
2771
  },
2758
2772
  meshRecordDispatcher: (meshName: string, owner: ReadyRecord, record: ReceiveRecord) =>
2759
2773
  this.dispatchMeshRecord(
2774
+ currentApplicationJobOrigin,
2760
2775
  meshName,
2761
2776
  owner,
2762
2777
  record,
@@ -2767,6 +2782,7 @@ export class ZLinkFrameworkRuntimeHost
2767
2782
  }
2768
2783
 
2769
2784
  private async dispatchMeshRecord(
2785
+ applicationJobOrigin: () => ApplicationJobOrigin,
2770
2786
  meshName: string,
2771
2787
  owner: ReadyRecord,
2772
2788
  record: ReceiveRecord,
@@ -2968,7 +2984,7 @@ export class ZLinkFrameworkRuntimeHost
2968
2984
  );
2969
2985
  }
2970
2986
  return this.admission.run(meshName, 'RouteMesh Actor dispatch', () =>
2971
- this.spotManager!.dispatchMeshActor(meshName, owner, record)
2987
+ this.spotManager!.dispatchMeshActor(applicationJobOrigin(), meshName, owner, record)
2972
2988
  );
2973
2989
  case ReceiveKind.SendReady:
2974
2990
  return Promise.resolve();
@@ -3012,7 +3028,10 @@ export class ZLinkFrameworkRuntimeHost
3012
3028
  try {
3013
3029
  state.taskRunner.runDetached('mesh-node-local-route-dispatch', async () => {
3014
3030
  try {
3015
- const permit = await this.applicationJobQueue.acquire(state.abortController.signal);
3031
+ const permit = await this.applicationJobQueue.acquire(
3032
+ state.abortController.signal,
3033
+ 'local'
3034
+ );
3016
3035
  permit.markApplicationQueued();
3017
3036
  await runWithApplicationJobPermit(permit, () =>
3018
3037
  channelRuntime.dispatchLocalMeshRoute(meshName, sourceNodeRid, ownedParts)
@@ -41,6 +41,7 @@ import { putNewRelocationBlob, relocationBlobReference } from '../locations/relo
41
41
 
42
42
  import {
43
43
  encodeInstanceActivationRecoveryEnvelope,
44
+ decodeInstanceActivationRecoveryEnvelope,
44
45
  type ServiceInstanceActivationRecoveryEnvelope
45
46
  } from '../foundation/service-instance-activation-recovery-codec';
46
47
 
@@ -57,6 +58,9 @@ interface PendingReservation {
57
58
  }
58
59
 
59
60
  export interface ZLinkInstanceActivationAuthorityOptions {
61
+ readonly relocationPolicy?: (
62
+ stableType: string
63
+ ) => import('../locations/internal-location-contracts').ZLinkObjectReserveRequest['actorRelocationPolicy'];
60
64
  readonly store: ZLinkObjectCreationStore & ZLinkAuthorityStore;
61
65
  readonly relocationStore?: ZLinkRelocationStore;
62
66
  readonly meshName: string;
@@ -73,9 +77,101 @@ export class ZLinkInstanceActivationAuthority implements ServiceAsyncInstanceAct
73
77
 
74
78
  constructor(private readonly options: ZLinkInstanceActivationAuthorityOptions) {}
75
79
 
76
- async read(target: ServiceInstanceActivationTarget): Promise<ServiceInstanceAuthorityRead> {
77
- const current = await this.options.store.readAuthority(authorityKey(target.targetSpotId));
78
- return current.kind === 'snapshot' ? readyRead(current, target) : { kind: 'missing' };
80
+ async read(
81
+ target: ServiceInstanceActivationTarget,
82
+ instanceIntent?: true,
83
+ activation?: ServiceInstanceActivationRecoveryEnvelope,
84
+ acceptsLocal?: (route: ServiceInstanceRouteFence) => boolean
85
+ ): Promise<ServiceInstanceAuthorityRead> {
86
+ const key = authorityKey(target.targetSpotId);
87
+ const current = await this.options.store.readAuthority(key);
88
+ if (current.kind !== 'snapshot') return { kind: 'missing' };
89
+ const ready = decodeServiceInstanceAuthorityPayload(current.payload);
90
+ const resolved = readyRead(current, target, ready);
91
+ if (
92
+ ready?.state === 'ready' &&
93
+ resolved.kind === 'ready' &&
94
+ acceptsLocal?.(resolved.route) === true
95
+ ) {
96
+ return resolved;
97
+ }
98
+ if (
99
+ activation !== undefined &&
100
+ current.allocation.objectKind === 'instance_spot' &&
101
+ routingIdsEqual(current.allocation.descriptor.rid, target.targetNodeRid) &&
102
+ current.allocation.descriptorLifecycleGeneration === target.targetNodeGeneration
103
+ ) {
104
+ const reference =
105
+ current.pendingCreation?.requestContentReference ?? ready?.activationRecovery?.reference;
106
+ if (reference !== undefined) {
107
+ const root = await this.options.relocationStore?.read(relocationBlobReference(reference));
108
+ if (root?.kind !== 'found') {
109
+ throw new ZLinkFrameworkException(
110
+ ZLinkFrameworkErrorKind.DataLost,
111
+ 'Stored Instance activation is missing.'
112
+ );
113
+ }
114
+ let stored: ServiceInstanceActivationRecoveryEnvelope;
115
+ try {
116
+ stored = decodeInstanceActivationRecoveryEnvelope(root.bytes);
117
+ } catch (error) {
118
+ if (!(error instanceof TypeError || error instanceof RangeError)) throw error;
119
+ throw new ZLinkFrameworkException(
120
+ ZLinkFrameworkErrorKind.ProtocolError,
121
+ 'Stored Instance activation is invalid.'
122
+ );
123
+ }
124
+ if (
125
+ stored.operation.high === activation.operation.high &&
126
+ stored.operation.low === activation.operation.low &&
127
+ (stored.targetMeshName !== activation.targetMeshName ||
128
+ stored.target.stableType !== target.stableType ||
129
+ stored.target.descriptorVersion !== target.descriptorVersion ||
130
+ stored.deadlineUnixMs !== activation.deadlineUnixMs ||
131
+ (stored.metadataFrame === undefined) !== (activation.metadataFrame === undefined) ||
132
+ (stored.metadataFrame !== undefined &&
133
+ activation.metadataFrame !== undefined &&
134
+ Buffer.compare(stored.metadataFrame, activation.metadataFrame) !== 0))
135
+ ) {
136
+ throw new ZLinkFrameworkException(
137
+ ZLinkFrameworkErrorKind.ProtocolError,
138
+ 'Instance route does not match the stored activation.'
139
+ );
140
+ }
141
+ }
142
+ }
143
+
144
+ if (
145
+ instanceIntent === true &&
146
+ current.allocation.state === 'active' &&
147
+ ready?.state === 'ready'
148
+ ) {
149
+ if (
150
+ current.allocation.objectKind !== 'instance_spot' ||
151
+ current.allocation.stableType !== target.stableType
152
+ ) {
153
+ throw createInternalFrameworkException(
154
+ ZLinkFrameworkInternalErrorKind.SpotTypeMismatch,
155
+ 'Instance activation type does not match the current authority.'
156
+ );
157
+ }
158
+ const released = await this.options.store.releaseEndedReservation?.(
159
+ key,
160
+ current.storeVersion.value,
161
+ undefined,
162
+ {
163
+ key: { kind: 'instance_spot', globalId: target.targetSpotId },
164
+ intent: { stableType: target.stableType },
165
+ actorRelocationPolicy: this.options.relocationPolicy?.(target.stableType)
166
+ }
167
+ );
168
+ if (released === true) return { kind: 'missing' };
169
+ const refreshed = await this.options.store.readAuthority(key);
170
+ return refreshed.kind === 'snapshot'
171
+ ? readyRead(refreshed, target, decodeServiceInstanceAuthorityPayload(refreshed.payload))
172
+ : { kind: 'missing' };
173
+ }
174
+ return resolved;
79
175
  }
80
176
 
81
177
  async reserve(
@@ -117,6 +213,7 @@ export class ZLinkInstanceActivationAuthority implements ServiceAsyncInstanceAct
117
213
  reserved = await this.options.store.reserve(
118
214
  {
119
215
  key: { kind: 'instance_spot', globalId: target.targetSpotId },
216
+ actorRelocationPolicy: this.options.relocationPolicy?.(target.stableType),
120
217
  intent: {
121
218
  stableType: target.stableType,
122
219
  requestContentReference: stored.reference.value,
@@ -310,7 +407,11 @@ export class ZLinkInstanceActivationAuthority implements ServiceAsyncInstanceAct
310
407
  }
311
408
  const current = await this.options.store.readAuthority(authorityKey(target.targetSpotId));
312
409
  if (current.kind === 'snapshot') {
313
- const ready = readyRead(current, target);
410
+ const ready = readyRead(
411
+ current,
412
+ target,
413
+ decodeServiceInstanceAuthorityPayload(current.payload)
414
+ );
314
415
  if (ready.kind === 'ready') {
315
416
  this.pending.delete(reservation.token);
316
417
  return { kind: 'lost', route: ready.route };
@@ -344,7 +445,7 @@ export class ZLinkInstanceActivationAuthority implements ServiceAsyncInstanceAct
344
445
  throw new Error('Instance activation authority changed before inbox release.');
345
446
  }
346
447
  const recovery = decoded.activationRecovery;
347
- if (recovery === undefined) return routeFromSnapshot(current);
448
+ if (recovery === undefined) return routeFromSnapshot(current, decoded.spotId);
348
449
  let completed = current;
349
450
  if (recovery.replayCursor < recovery.inboxSequence) {
350
451
  const cursorStored = await this.options.store.compareExchangeAuthority(
@@ -459,22 +560,19 @@ export class ZLinkInstanceActivationAuthority implements ServiceAsyncInstanceAct
459
560
 
460
561
  function readyRead(
461
562
  snapshot: ZLinkAuthoritySnapshot,
462
- target: ServiceInstanceActivationTarget
563
+ target: ServiceInstanceActivationTarget,
564
+ decoded: ReturnType<typeof decodeServiceInstanceAuthorityPayload>
463
565
  ): ServiceInstanceAuthorityRead {
464
- const decoded =
465
- decodeServiceReadySpotAuthority(snapshot.payload) ??
466
- decodeServiceClosingSpotAuthority(snapshot.payload);
467
- const creating = decodeServiceInstanceAuthorityPayload(snapshot.payload);
468
566
  if (
469
- creating?.state === 'coldActivating' &&
567
+ decoded?.state === 'coldActivating' &&
470
568
  snapshot.allocation.objectKind === 'instance_spot' &&
471
- creating.stableType === target.stableType &&
472
- creating.spotId === target.targetSpotId &&
473
- creating.ownerId === snapshot.ownerId &&
474
- creating.ownerLeaseGeneration === snapshot.ownerLeaseGeneration &&
475
- creating.ownerMeshName === snapshot.allocation.descriptor.meshName &&
476
- creating.ownerNodeGeneration === snapshot.allocation.descriptorLifecycleGeneration &&
477
- routingIdsEqual(creating.ownerNodeRid, snapshot.allocation.descriptor.rid) &&
569
+ decoded.stableType === target.stableType &&
570
+ decoded.spotId === target.targetSpotId &&
571
+ decoded.ownerId === snapshot.ownerId &&
572
+ decoded.ownerLeaseGeneration === snapshot.ownerLeaseGeneration &&
573
+ decoded.ownerMeshName === snapshot.allocation.descriptor.meshName &&
574
+ decoded.ownerNodeGeneration === snapshot.allocation.descriptorLifecycleGeneration &&
575
+ routingIdsEqual(decoded.ownerNodeRid, snapshot.allocation.descriptor.rid) &&
478
576
  routingIdsEqual(target.targetNodeRid, snapshot.allocation.descriptor.rid) &&
479
577
  target.targetNodeGeneration === snapshot.allocation.descriptorLifecycleGeneration
480
578
  ) {
@@ -486,7 +584,8 @@ function readyRead(
486
584
  };
487
585
  }
488
586
  if (
489
- decoded?.kind !== 'instance_spot' ||
587
+ decoded === undefined ||
588
+ decoded.state === 'coldActivating' ||
490
589
  decoded.stableType !== target.stableType ||
491
590
  decoded.spotId !== target.targetSpotId ||
492
591
  snapshot.allocation.objectKind !== 'instance_spot' ||
@@ -501,7 +600,7 @@ function readyRead(
501
600
  ) {
502
601
  return { kind: 'missing' };
503
602
  }
504
- return { kind: 'ready', route: routeFromSnapshot(snapshot), authority: snapshot };
603
+ return { kind: 'ready', route: routeFromSnapshot(snapshot, decoded.spotId), authority: snapshot };
505
604
  }
506
605
 
507
606
  function requireCommitIdentity(
@@ -529,17 +628,23 @@ function requireCommitIdentity(
529
628
  }
530
629
  }
531
630
 
532
- function routeFromSnapshot(snapshot: ZLinkAuthoritySnapshot): ServiceInstanceRouteFence {
533
- const decoded =
534
- decodeServiceReadySpotAuthority(snapshot.payload) ??
535
- decodeServiceClosingSpotAuthority(snapshot.payload);
536
- if (decoded?.kind !== 'instance_spot') {
537
- throw new Error('Instance authority is not Ready.');
631
+ function routeFromSnapshot(
632
+ snapshot: ZLinkAuthoritySnapshot,
633
+ spotId?: string
634
+ ): ServiceInstanceRouteFence {
635
+ if (spotId === undefined) {
636
+ const decoded =
637
+ decodeServiceReadySpotAuthority(snapshot.payload) ??
638
+ decodeServiceClosingSpotAuthority(snapshot.payload);
639
+ if (decoded?.kind !== 'instance_spot') {
640
+ throw new Error('Instance authority is not Ready.');
641
+ }
642
+ spotId = decoded.spotId;
538
643
  }
539
644
  return {
540
645
  targetNodeRid: String(snapshot.allocation.descriptor.rid),
541
646
  targetNodeGeneration: snapshot.allocation.descriptorLifecycleGeneration,
542
- targetSpotId: decoded.spotId,
647
+ targetSpotId: spotId,
543
648
  objectGeneration: snapshot.objectGeneration,
544
649
  ownerId: snapshot.ownerId,
545
650
  authorityOwnerGeneration: snapshot.authorityOwnerGeneration,
@@ -4812,6 +4812,7 @@ class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden>
4812
4812
  throw new Error('Relocated Entry Spot Actor dispatch is unavailable.');
4813
4813
  }
4814
4814
  return runtime.dispatchEntryActorPacket(
4815
+ 'remote',
4815
4816
  hidden.actor!.context.actorId,
4816
4817
  parts,
4817
4818
  returnResponse,
@@ -4820,6 +4821,7 @@ class LocalTargetPort implements ServiceRelocationTargetObjectPort<LocalHidden>
4820
4821
  );
4821
4822
  }
4822
4823
  return this.requireSpotManager().dispatchRoutedActorPacket(
4824
+ 'remote',
4823
4825
  state.spotId,
4824
4826
  hidden.actor!.context.actorId,
4825
4827
  parts,