@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,7 +1,7 @@
1
1
  import { UINT64_MAX } from '@zlink-systems/stream-wire';
2
2
  export interface ServiceProbe {
3
3
  readonly nodeRoutingId: string;
4
- readonly connectionId: string;
4
+ readonly connectionId: string | bigint;
5
5
  readonly probeId: bigint;
6
6
  }
7
7
 
@@ -10,20 +10,52 @@ export interface ServiceLivenessTick {
10
10
  readonly timedOutNodes: readonly string[];
11
11
  }
12
12
 
13
- interface PeerState {
14
- readonly connectionId: string;
13
+ export const DEFAULT_SERVICE_PROBE_INTERVAL_MS = 5_000;
14
+ export const DEFAULT_SERVICE_PEER_TIMEOUT_MS = 15_000;
15
+
16
+ export class ServiceLivenessConnection {
15
17
  deadlineMs: number;
16
18
  nextProbeMs: number;
17
19
  outstandingProbe?: bigint;
18
- ready: boolean;
19
- }
20
20
 
21
- export const DEFAULT_SERVICE_PROBE_INTERVAL_MS = 5_000;
22
- export const DEFAULT_SERVICE_PEER_TIMEOUT_MS = 15_000;
21
+ constructor(
22
+ readonly connectionId: string | bigint,
23
+ nowMs: number,
24
+ private readonly probeIntervalMs = DEFAULT_SERVICE_PROBE_INTERVAL_MS,
25
+ private readonly peerTimeoutMs = DEFAULT_SERVICE_PEER_TIMEOUT_MS
26
+ ) {
27
+ this.deadlineMs = nowMs + peerTimeoutMs;
28
+ this.nextProbeMs = nowMs;
29
+ }
30
+
31
+ readonly recordReceived = (nowMs = performance.now()): void => {
32
+ this.deadlineMs = Math.max(this.deadlineMs, nowMs + this.peerTimeoutMs);
33
+ };
34
+
35
+ isExpired(nowMs: number): boolean {
36
+ return nowMs >= this.deadlineMs;
37
+ }
38
+
39
+ tryGetProbe(nowMs: number, allocateProbeId: () => bigint): bigint | undefined {
40
+ if (nowMs < this.nextProbeMs) return undefined;
41
+ this.outstandingProbe ??= allocateProbeId();
42
+ do {
43
+ this.nextProbeMs += this.probeIntervalMs;
44
+ } while (this.nextProbeMs <= nowMs);
45
+ return this.outstandingProbe;
46
+ }
47
+
48
+ acknowledge(probeId: bigint, nowMs: number): boolean {
49
+ this.recordReceived(nowMs);
50
+ if (this.outstandingProbe !== probeId) return false;
51
+ this.outstandingProbe = undefined;
52
+ return true;
53
+ }
54
+ }
23
55
 
24
56
  /** Uses monotonic milliseconds and never treats application traffic as a probe ACK. */
25
57
  export class ServiceLivenessRegistry {
26
- private readonly peers = new Map<string, PeerState>();
58
+ private readonly peers = new Map<string, ServiceLivenessConnection>();
27
59
  private nextProbeId = 1n;
28
60
 
29
61
  constructor(
@@ -40,33 +72,31 @@ export class ServiceLivenessRegistry {
40
72
  }
41
73
  }
42
74
 
43
- admit(nodeRoutingId: string, connectionId: string, nowMs: number): void {
75
+ admit(
76
+ nodeRoutingId: string,
77
+ connectionId: string | bigint,
78
+ nowMs: number
79
+ ): ServiceLivenessConnection {
44
80
  requireIdentity(nodeRoutingId, 'nodeRoutingId');
45
- requireIdentity(connectionId, 'connectionId');
81
+ if (typeof connectionId === 'string') requireIdentity(connectionId, 'connectionId');
82
+ else if (connectionId === 0n) throw new TypeError('connectionId must be nonzero.');
46
83
  const current = this.peers.get(nodeRoutingId);
47
- if (current?.connectionId === connectionId) return;
48
- this.peers.set(nodeRoutingId, {
84
+ if (current?.connectionId === connectionId) return current;
85
+ const admitted = new ServiceLivenessConnection(
49
86
  connectionId,
50
- deadlineMs: nowMs + this.peerTimeoutMs,
51
- nextProbeMs: nowMs + this.probeIntervalMs,
52
- ready: false
53
- });
87
+ nowMs,
88
+ this.probeIntervalMs,
89
+ this.peerTimeoutMs
90
+ );
91
+ this.peers.set(nodeRoutingId, admitted);
92
+ return admitted;
54
93
  }
55
94
 
56
- requestProbe(nodeRoutingId: string, connectionId: string, nowMs: number): boolean {
57
- const current = this.peers.get(nodeRoutingId);
58
- if (
59
- current === undefined ||
60
- current.connectionId !== connectionId ||
61
- current.ready ||
62
- current.outstandingProbe !== undefined
63
- )
64
- return false;
65
- current.nextProbeMs = Math.min(current.nextProbeMs, nowMs);
66
- return true;
95
+ recordReceived(connection: ServiceLivenessConnection, nowMs: number): void {
96
+ connection.recordReceived(nowMs);
67
97
  }
68
98
 
69
- disconnect(nodeRoutingId: string, connectionId: string): boolean {
99
+ disconnect(nodeRoutingId: string, connectionId: string | bigint): boolean {
70
100
  const current = this.peers.get(nodeRoutingId);
71
101
  if (current === undefined || current.connectionId !== connectionId) return false;
72
102
  this.peers.delete(nodeRoutingId);
@@ -75,27 +105,18 @@ export class ServiceLivenessRegistry {
75
105
 
76
106
  acknowledge(
77
107
  nodeRoutingId: string,
78
- connectionId: string,
108
+ connectionId: string | bigint,
79
109
  probeId: bigint,
80
110
  nowMs: number
81
111
  ): boolean {
82
112
  const current = this.peers.get(nodeRoutingId);
83
- if (
84
- current === undefined ||
85
- current.connectionId !== connectionId ||
86
- current.outstandingProbe !== probeId
87
- ) {
88
- return false;
89
- }
90
- current.outstandingProbe = undefined;
91
- current.deadlineMs = nowMs + this.peerTimeoutMs;
92
- current.ready = true;
93
- return true;
113
+ if (current === undefined || current.connectionId !== connectionId) return false;
114
+ return current.acknowledge(probeId, nowMs);
94
115
  }
95
116
 
96
117
  acknowledgeProbe(
97
118
  nodeRoutingId: string,
98
- connectionId: string,
119
+ connectionId: string | bigint,
99
120
  probeId: bigint
100
121
  ): ServiceProbe | undefined {
101
122
  const current = this.peers.get(nodeRoutingId);
@@ -109,21 +130,14 @@ export class ServiceLivenessRegistry {
109
130
  const probes: ServiceProbe[] = [];
110
131
  const timedOutNodes: string[] = [];
111
132
  for (const [nodeRoutingId, peer] of this.peers) {
112
- if (peer.deadlineMs <= nowMs) {
133
+ if (peer.isExpired(nowMs)) {
113
134
  timedOutNodes.push(nodeRoutingId);
114
135
  this.peers.delete(nodeRoutingId);
115
136
  continue;
116
137
  }
117
- if (peer.nextProbeMs > nowMs) continue;
118
- peer.outstandingProbe ??= this.allocateProbeId();
119
- probes.push({
120
- nodeRoutingId,
121
- connectionId: peer.connectionId,
122
- probeId: peer.outstandingProbe
123
- });
124
- do {
125
- peer.nextProbeMs += this.probeIntervalMs;
126
- } while (peer.nextProbeMs <= nowMs);
138
+ const probeId = peer.tryGetProbe(nowMs, this.allocateProbeId);
139
+ if (probeId !== undefined)
140
+ probes.push({ nodeRoutingId, connectionId: peer.connectionId, probeId });
127
141
  }
128
142
  timedOutNodes.sort();
129
143
  return { probes, timedOutNodes };
@@ -133,16 +147,11 @@ export class ServiceLivenessRegistry {
133
147
  return this.peers.size;
134
148
  }
135
149
 
136
- isReady(nodeRoutingId: string, connectionId: string): boolean {
137
- const current = this.peers.get(nodeRoutingId);
138
- return current?.connectionId === connectionId && current.ready;
139
- }
140
-
141
- private allocateProbeId(): bigint {
150
+ private readonly allocateProbeId = (): bigint => {
142
151
  const result = this.nextProbeId++;
143
152
  if (this.nextProbeId > UINT64_MAX) this.nextProbeId = 1n;
144
153
  return result;
145
- }
154
+ };
146
155
  }
147
156
 
148
157
  function requireIdentity(value: string, field: string): void {
@@ -1,4 +1,5 @@
1
1
  import type { ApplicationJobRecordLease } from '../application-jobs/application-ingress-record-owner';
2
+ import type { ReceiveRecord } from './service-runtime-contracts';
2
3
 
3
4
  export type ServiceMailboxDomain = 'application' | 'infrastructure';
4
5
 
@@ -23,6 +24,8 @@ export interface ServiceMailboxRecord {
23
24
  readonly stateful?: unknown;
24
25
  /** Host queue permit plus its ordinary Framework-owned ingress record. */
25
26
  readonly applicationJob?: ApplicationJobRecordLease;
27
+ /** Unregistered raw-pull terminal, retained by the existing mailbox owner. */
28
+ readonly completion?: ReceiveRecord;
26
29
  }
27
30
 
28
31
  export interface ServiceMailboxClaim {
@@ -85,7 +88,10 @@ export class ServiceMailbox {
85
88
  }
86
89
 
87
90
  tryEnqueue(record: ServiceMailboxRecord): boolean {
88
- if (record.owner.length === 0 || record.parts.length === 0) {
91
+ if (
92
+ record.owner.length === 0 ||
93
+ (record.parts.length === 0 && record.completion === undefined)
94
+ ) {
89
95
  throw new TypeError('Mailbox records require an owner and retained payload.');
90
96
  }
91
97
  const target = this.domain(record.domain);
@@ -90,6 +90,7 @@ import {
90
90
  type ServiceBoundSessionActorAuthority,
91
91
  type ServiceDirectSpotRouteFence,
92
92
  type ServiceInstanceActivationTarget,
93
+ type ServiceInstanceColdActivationTarget,
93
94
  type ServiceInstanceRouteFence,
94
95
  type ServiceMessageFollowRoute,
95
96
  type ServiceRetiredBoundSessionRouteFence,
@@ -313,7 +314,12 @@ export interface ServiceInstanceActivationAuthority {
313
314
  * operation completes; the binding callback itself never blocks.
314
315
  */
315
316
  export interface ServiceAsyncInstanceActivationAuthority {
316
- read(target: ServiceInstanceActivationTarget): Promise<ServiceInstanceAuthorityRead>;
317
+ read(
318
+ target: ServiceInstanceActivationTarget,
319
+ instanceIntent?: true,
320
+ activation?: ServiceInstanceActivationRecoveryEnvelope,
321
+ acceptsLocal?: (route: ServiceInstanceRouteFence) => boolean
322
+ ): Promise<ServiceInstanceAuthorityRead>;
317
323
  reserve(
318
324
  activation: Omit<ServiceInstanceActivationRecoveryEnvelope, 'targetMeshName'>,
319
325
  signal?: AbortSignal
@@ -883,7 +889,7 @@ export class ServiceStatefulRuntime {
883
889
  const record = {
884
890
  kind: 'instanceSpot' as const,
885
891
  activation: 'missing' as const,
886
- target,
892
+ target: { ...target, targetMeshName: envelope.targetMeshName },
887
893
  sourceNodeGeneration: envelope.sourceNodeGeneration,
888
894
  sourceNodeRid: envelope.sourceNodeRid,
889
895
  ...(envelope.sourceSpotId === undefined ? {} : { sourceSpotId: envelope.sourceSpotId }),
@@ -902,7 +908,7 @@ export class ServiceStatefulRuntime {
902
908
  sourceRoutingId: envelope.sourceNodeRid,
903
909
  parts: [
904
910
  encodeInstanceSpotActivationHeader(
905
- target,
911
+ record.target,
906
912
  envelope.sourceNodeGeneration,
907
913
  envelope.sourceNodeRid,
908
914
  envelope.sourceSpotId,
@@ -1442,7 +1448,7 @@ export class ServiceStatefulRuntime {
1442
1448
  }
1443
1449
 
1444
1450
  async sendToMissingInstanceSpot(
1445
- target: ServiceInstanceActivationTarget,
1451
+ target: ServiceInstanceColdActivationTarget,
1446
1452
  payload: ServiceApplicationPayload,
1447
1453
  deadlineUnixMs: bigint,
1448
1454
  sourceSpotId?: string,
@@ -1458,7 +1464,7 @@ export class ServiceStatefulRuntime {
1458
1464
  }
1459
1465
 
1460
1466
  async sendToMissingInstanceSpotFrame(
1461
- target: ServiceInstanceActivationTarget,
1467
+ target: ServiceInstanceColdActivationTarget,
1462
1468
  payloadFrame: Buffer,
1463
1469
  deadlineUnixMs: bigint,
1464
1470
  sourceSpotId?: string,
@@ -1523,7 +1529,7 @@ export class ServiceStatefulRuntime {
1523
1529
  }
1524
1530
 
1525
1531
  requestToMissingInstanceSpot(
1526
- target: ServiceInstanceActivationTarget,
1532
+ target: ServiceInstanceColdActivationTarget,
1527
1533
  payload: ServiceApplicationPayload,
1528
1534
  timeoutMs: number,
1529
1535
  sourceSpotId?: string,
@@ -1541,7 +1547,7 @@ export class ServiceStatefulRuntime {
1541
1547
  }
1542
1548
 
1543
1549
  requestToMissingInstanceSpotFrame(
1544
- target: ServiceInstanceActivationTarget,
1550
+ target: ServiceInstanceColdActivationTarget,
1545
1551
  payloadFrame: Buffer,
1546
1552
  deadlineUnixMs: bigint,
1547
1553
  sourceSpotId?: string,
@@ -2941,6 +2947,24 @@ export class ServiceStatefulRuntime {
2941
2947
  return activation.spot;
2942
2948
  }
2943
2949
 
2950
+ private localReadyInstance(
2951
+ target: ServiceInstanceActivationTarget,
2952
+ route: ServiceInstanceRouteFence
2953
+ ): ServiceSpotState | undefined {
2954
+ const local = this.registry.spot(target.targetSpotId);
2955
+ if (
2956
+ local !== undefined &&
2957
+ (local.kind !== 'instance' || local.stableType !== target.stableType)
2958
+ ) {
2959
+ throw new ServiceStaleGenerationError('spot', target.targetSpotId);
2960
+ }
2961
+ return local !== undefined &&
2962
+ local.state === 'ready' &&
2963
+ routeMatchesLocal(route, local, this.nodeRid, this.nodeGeneration)
2964
+ ? local
2965
+ : undefined;
2966
+ }
2967
+
2944
2968
  private activateMissingInstanceAsync(
2945
2969
  record: Extract<
2946
2970
  ServiceStatefulWireRecord,
@@ -2961,7 +2985,20 @@ export class ServiceStatefulRuntime {
2961
2985
  ...activation,
2962
2986
  stableType: record.target.stableType
2963
2987
  }));
2964
- const operation = ready.then((value) => {
2988
+ const operation = ready.then(async (value) => {
2989
+ if (pending !== undefined) {
2990
+ await this.asyncInstanceAuthority?.read(
2991
+ record.target,
2992
+ undefined,
2993
+ {
2994
+ ...record,
2995
+ targetMeshName: record.target.targetMeshName,
2996
+ ...(metadataFrame === undefined ? {} : { metadataFrame }),
2997
+ applicationPayloadFrame: payloadFrame
2998
+ },
2999
+ (route) => this.localReadyInstance(record.target, route) !== undefined
3000
+ );
3001
+ }
2965
3002
  if (value.stableType !== record.target.stableType) {
2966
3003
  throw new ZLinkFrameworkException(
2967
3004
  ZLinkFrameworkErrorKind.TypeMismatch,
@@ -3010,8 +3047,19 @@ export class ServiceStatefulRuntime {
3010
3047
  throw new Error('Async Instance activation authority is not registered.');
3011
3048
  }
3012
3049
 
3050
+ const activationRequest = {
3051
+ ...record,
3052
+ targetMeshName: record.target.targetMeshName,
3053
+ ...(metadataFrame === undefined ? {} : { metadataFrame }),
3054
+ applicationPayloadFrame: payloadFrame
3055
+ };
3056
+ const current = await authority.read(
3057
+ target,
3058
+ true,
3059
+ activationRequest,
3060
+ (route) => this.localReadyInstance(target, route) !== undefined
3061
+ );
3013
3062
  const local = this.registry.spot(target.targetSpotId);
3014
- const current = await authority.read(target);
3015
3063
  if (
3016
3064
  current.kind === 'creating' &&
3017
3065
  current.authority?.allocation.state === 'active' &&
@@ -3061,17 +3109,9 @@ export class ServiceStatefulRuntime {
3061
3109
  if (current.route.targetNodeGeneration !== this.nodeGeneration) {
3062
3110
  throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3063
3111
  }
3064
- if (
3065
- local !== undefined &&
3066
- (local.kind !== 'instance' || local.stableType !== target.stableType)
3067
- ) {
3068
- throw new ServiceStaleGenerationError('spot', target.targetSpotId);
3069
- }
3070
- if (
3071
- local !== undefined &&
3072
- routeMatchesLocal(current.route, local, this.nodeRid, this.nodeGeneration)
3073
- ) {
3074
- return { spot: local, route: current.route };
3112
+ const readyLocal = this.localReadyInstance(target, current.route);
3113
+ if (readyLocal !== undefined) {
3114
+ return { spot: readyLocal, route: current.route };
3075
3115
  }
3076
3116
  return await this.reconcileReadyInstanceProjection(target, current.route, authority);
3077
3117
  }
@@ -3113,21 +3153,7 @@ export class ServiceStatefulRuntime {
3113
3153
  const deadline = operationDeadline(record.deadlineUnixMs);
3114
3154
  let reserved: ServiceInstanceAuthorityReserve;
3115
3155
  try {
3116
- reserved = await authority.reserve(
3117
- {
3118
- target,
3119
- sourceNodeRid: record.sourceNodeRid,
3120
- sourceNodeGeneration: record.sourceNodeGeneration,
3121
- ...(record.sourceSpotId === undefined ? {} : { sourceSpotId: record.sourceSpotId }),
3122
- operationKind: record.operationKind,
3123
- operation: record.operation,
3124
- ...(record.replyRouteId === undefined ? {} : { replyRouteId: record.replyRouteId }),
3125
- deadlineUnixMs: record.deadlineUnixMs,
3126
- ...(metadataFrame === undefined ? {} : { metadataFrame }),
3127
- applicationPayloadFrame: payloadFrame
3128
- },
3129
- deadline.signal
3130
- );
3156
+ reserved = await authority.reserve(activationRequest, deadline.signal);
3131
3157
  } finally {
3132
3158
  deadline.close();
3133
3159
  }
@@ -14,6 +14,7 @@ import {
14
14
  encodeInstanceRouteV1,
15
15
  encodeSpotRequestCommand,
16
16
  encodeSpotSendCommand,
17
+ type InstanceRouteV1,
17
18
  type ServiceWireDecoderContext
18
19
  } from '../protocol/service_wire_codec.generated';
19
20
  import {
@@ -82,8 +83,6 @@ import {
82
83
  import { APPLICATION_PAYLOAD_VERSION, ServiceWireProtocolError } from './service-wire-m6a-codec';
83
84
  const MESSAGE_FOLLOW_MAX_BODY_BYTES = 16 * 1024 * 1024;
84
85
  const MESSAGE_FOLLOW_VERSION = 1;
85
- const INSTANCE_ROUTE_VERSION = 1;
86
- const INSTANCE_ACTIVATION_VERSION = 2;
87
86
 
88
87
  const PREFIX_SIZE = SERVICE_WIRE_PREFIX_SIZE;
89
88
  const MAGIC_0 = SERVICE_WIRE_MAGIC[0];
@@ -946,6 +945,10 @@ export interface ServiceInstanceActivationTarget {
946
945
  readonly descriptorVersion: string;
947
946
  }
948
947
 
948
+ export interface ServiceInstanceColdActivationTarget extends ServiceInstanceActivationTarget {
949
+ readonly targetMeshName: string;
950
+ }
951
+
949
952
  export interface ServiceUserSpotReservationFence {
950
953
  readonly reservationId: string;
951
954
  readonly expectedStoreVersion: string;
@@ -1105,7 +1108,7 @@ export type ServiceStatefulWireRecord =
1105
1108
  | {
1106
1109
  readonly kind: 'instanceSpot';
1107
1110
  readonly activation: 'missing';
1108
- readonly target: ServiceInstanceActivationTarget;
1111
+ readonly target: ServiceInstanceColdActivationTarget;
1109
1112
  readonly sourceNodeGeneration: bigint;
1110
1113
  readonly sourceNodeRid: string;
1111
1114
  readonly sourceSpotId?: string;
@@ -1435,7 +1438,7 @@ export function encodeInstanceSpotHeader(
1435
1438
  }
1436
1439
 
1437
1440
  export function encodeInstanceSpotActivationHeader(
1438
- target: ServiceInstanceActivationTarget,
1441
+ target: ServiceInstanceColdActivationTarget,
1439
1442
  sourceNodeGeneration: bigint,
1440
1443
  sourceNodeRid: string,
1441
1444
  sourceSpotId: string | undefined,
@@ -1454,25 +1457,28 @@ export function encodeInstanceSpotActivationHeader(
1454
1457
  if (operationKind === 'send' && replyRouteId !== undefined) {
1455
1458
  throw new RangeError('Instance Spot send must not carry a reply route.');
1456
1459
  }
1457
- const targetBody = concat(
1458
- rid(target.targetNodeRid, 'targetNodeRid'),
1459
- u64(target.targetNodeGeneration),
1460
- rid(target.targetSpotId, 'targetSpotId'),
1461
- text16(target.stableType, 'stableType'),
1462
- text16(target.descriptorVersion, 'descriptorVersion')
1460
+ const encodedRoute = encodeInstanceRouteV1(
1461
+ {
1462
+ routeKind: 'coldActivation',
1463
+ targetNodeRid: toGeneratedRoutingId(target.targetNodeRid, 'targetNodeRid'),
1464
+ targetNodeGeneration: target.targetNodeGeneration,
1465
+ targetSpotId: target.targetSpotId,
1466
+ targetMeshName: target.targetMeshName,
1467
+ stableType: target.stableType,
1468
+ targetDescriptorVersion: target.descriptorVersion,
1469
+ deadlineUnixMs
1470
+ },
1471
+ STATEFUL_MESSAGE_CONTEXT
1463
1472
  );
1464
1473
  return concat(
1465
1474
  prefix(M6bServiceWireCommand.instanceSpot, hasMetadata ? M6bServiceWireFlag.metadata : 0),
1466
- Buffer.of(INSTANCE_ACTIVATION_VERSION),
1467
- u16(targetBody.byteLength),
1468
- targetBody,
1475
+ encodedRoute,
1469
1476
  u64Any(sourceNodeGeneration),
1470
1477
  rid(sourceNodeRid, 'sourceNodeRid'),
1471
1478
  optionalRid(sourceSpotId),
1472
1479
  Buffer.of(operationKind === 'send' ? 1 : 2),
1473
1480
  u64Any(operation.high),
1474
1481
  u64Any(operation.low),
1475
- u64(deadlineUnixMs),
1476
1482
  ...(operationKind === 'request' ? [u64(requirePositive(replyRouteId, 'replyRouteId'))] : [])
1477
1483
  );
1478
1484
  }
@@ -1741,53 +1747,31 @@ export function decodeStatefulHeader(
1741
1747
  fail(`Invalid command flags '${command.flags}'.`);
1742
1748
  }
1743
1749
  const routeStart = reader.offset;
1744
- const version = reader.u8('instanceRoute.version');
1745
- if (version !== INSTANCE_ROUTE_VERSION && version !== INSTANCE_ACTIVATION_VERSION)
1746
- fail('Unsupported Instance route version.');
1750
+ reader.u8('instanceRoute.kind');
1747
1751
  const routeLength = reader.u16('instanceRoute.length');
1748
1752
  const routeEnd = reader.offset + routeLength;
1749
- const generatedReadyRoute =
1750
- version === INSTANCE_ROUTE_VERSION
1751
- ? decodeInstanceRouteV1(
1752
- reader.bytes.subarray(routeStart, routeEnd),
1753
- STATEFUL_MESSAGE_CONTEXT
1754
- )
1755
- : undefined;
1756
- if (generatedReadyRoute !== undefined && generatedReadyRoute.routeKind !== 'ready') {
1757
- fail('Invalid Ready Instance route kind.');
1758
- }
1759
- const commonTarget =
1760
- generatedReadyRoute?.routeKind === 'ready'
1761
- ? {
1762
- targetNodeRid: fromGeneratedRoutingId(
1763
- generatedReadyRoute.targetNodeRid,
1764
- 'targetNodeRid'
1765
- ),
1766
- targetNodeGeneration: generatedReadyRoute.targetNodeGeneration,
1767
- targetSpotId: generatedReadyRoute.targetSpotId
1768
- }
1769
- : {
1770
- targetNodeRid: reader.rid('targetNodeRid'),
1771
- targetNodeGeneration: reader.nonZeroU64('targetNodeGeneration'),
1772
- targetSpotId: reader.rid('targetSpotId')
1773
- };
1774
- const route: ServiceInstanceRouteFence | undefined =
1775
- generatedReadyRoute?.routeKind === 'ready'
1776
- ? {
1777
- ...commonTarget,
1778
- ...generatedReadyRoute.authority
1779
- }
1753
+ const generatedRoute = decodeCanonicalInstanceRoute(
1754
+ reader.bytes.subarray(routeStart, routeEnd)
1755
+ );
1756
+ const commonTarget = {
1757
+ targetNodeRid: fromGeneratedRoutingId(generatedRoute.targetNodeRid, 'targetNodeRid'),
1758
+ targetNodeGeneration: generatedRoute.targetNodeGeneration,
1759
+ targetSpotId: generatedRoute.targetSpotId
1760
+ };
1761
+ const route =
1762
+ generatedRoute.routeKind === 'ready'
1763
+ ? { ...commonTarget, ...generatedRoute.authority }
1780
1764
  : undefined;
1781
- const target: ServiceInstanceActivationTarget | undefined =
1782
- version === INSTANCE_ACTIVATION_VERSION
1765
+ const target =
1766
+ generatedRoute.routeKind === 'coldActivation'
1783
1767
  ? {
1784
1768
  ...commonTarget,
1785
- stableType: reader.text16('stableType'),
1786
- descriptorVersion: reader.text16('descriptorVersion')
1769
+ targetMeshName: generatedRoute.targetMeshName,
1770
+ stableType: generatedRoute.stableType,
1771
+ descriptorVersion: generatedRoute.targetDescriptorVersion
1787
1772
  }
1788
1773
  : undefined;
1789
- if (generatedReadyRoute !== undefined) reader.offset = routeEnd;
1790
- if (reader.offset !== routeEnd) fail('Invalid Instance route body length.');
1774
+ reader.offset = routeEnd;
1791
1775
  const sourceNodeGeneration = reader.nonZeroU64('sourceNodeGeneration');
1792
1776
  const sourceNodeRid = reader.rid('sourceNodeRid');
1793
1777
  const sourceSpotId = reader.optionalRid('sourceSpotId');
@@ -1799,21 +1783,21 @@ export function decodeStatefulHeader(
1799
1783
  };
1800
1784
  const operationKind = operationValue === 1 ? ('send' as const) : ('request' as const);
1801
1785
  if (
1802
- version === INSTANCE_ROUTE_VERSION &&
1786
+ generatedRoute.routeKind === 'ready' &&
1803
1787
  ((operationKind === 'send' && (operation.high !== 0n || operation.low !== 0n)) ||
1804
1788
  (operationKind === 'request' && operation.high === 0n && operation.low === 0n))
1805
1789
  ) {
1806
1790
  fail('Invalid Instance operation identity.');
1807
1791
  }
1808
1792
  if (
1809
- version === INSTANCE_ACTIVATION_VERSION &&
1793
+ generatedRoute.routeKind === 'coldActivation' &&
1810
1794
  operation.high === 0n &&
1811
1795
  operation.low === 0n
1812
1796
  ) {
1813
1797
  fail('Instance activation requires a non-zero operation identity.');
1814
1798
  }
1815
1799
  const deadlineUnixMs =
1816
- version === INSTANCE_ACTIVATION_VERSION ? reader.nonZeroU64('deadlineUnixMs') : undefined;
1800
+ generatedRoute.routeKind === 'coldActivation' ? generatedRoute.deadlineUnixMs : undefined;
1817
1801
  const replyRouteId =
1818
1802
  operationKind === 'request' ? reader.nonZeroU64('replyRouteId') : undefined;
1819
1803
  reader.end();
@@ -1826,12 +1810,12 @@ export function decodeStatefulHeader(
1826
1810
  operation,
1827
1811
  ...(replyRouteId === undefined ? {} : { replyRouteId })
1828
1812
  };
1829
- return version === INSTANCE_ROUTE_VERSION
1813
+ return generatedRoute.routeKind === 'ready'
1830
1814
  ? {
1831
1815
  ...common,
1832
1816
  activation: 'ready',
1833
1817
  route: route!,
1834
- instanceIntent: generatedReadyRoute!.instanceIntent === 'true'
1818
+ instanceIntent: generatedRoute.instanceIntent === 'true'
1835
1819
  }
1836
1820
  : {
1837
1821
  ...common,
@@ -2387,11 +2371,6 @@ function text8(value: string, name: string): Buffer {
2387
2371
  return sized8(value, name);
2388
2372
  }
2389
2373
 
2390
- function text16(value: string, name: string): Buffer {
2391
- const bytes = encodeCanonicalServiceWireText(value, name, 0xffff, outOfRange);
2392
- return concat(u16(bytes.byteLength), bytes);
2393
- }
2394
-
2395
2374
  function optionalRid(value: string | undefined): Buffer {
2396
2375
  return value === undefined ? Buffer.of(0) : rid(value, 'sourceSpotId');
2397
2376
  }
@@ -2448,6 +2427,15 @@ function concat(...parts: readonly Uint8Array[]): Buffer {
2448
2427
  return Buffer.concat(parts.map((part) => Buffer.from(part)));
2449
2428
  }
2450
2429
 
2430
+ function decodeCanonicalInstanceRoute(bytes: Uint8Array): InstanceRouteV1 {
2431
+ try {
2432
+ return decodeInstanceRouteV1(bytes, STATEFUL_MESSAGE_CONTEXT);
2433
+ } catch (error) {
2434
+ if (error instanceof RangeError) throw new ServiceWireProtocolError(error.message);
2435
+ throw error;
2436
+ }
2437
+ }
2438
+
2451
2439
  function fail(message: string): never {
2452
2440
  throw new ServiceWireProtocolError(message);
2453
2441
  }
@@ -3093,21 +3081,9 @@ class FrozenReader {
3093
3081
 
3094
3082
  private instanceRoute(): void {
3095
3083
  const start = this.offset;
3096
- const kind = this.u8('instanceRouteKind');
3097
- if (kind < 1 || kind > 2) fail('Invalid Instance route kind.');
3098
- const body = this.body16('Instance route');
3099
- if (kind === INSTANCE_ROUTE_VERSION) {
3100
- decodeInstanceRouteV1(this.bytes.subarray(start, this.offset), STATEFUL_MESSAGE_CONTEXT);
3101
- return;
3102
- }
3103
- body.rid8('targetNodeRid');
3104
- body.nonZeroU64('targetNodeGeneration');
3105
- body.text8('targetSpotId');
3106
- body.text8('targetMeshName');
3107
- body.text8('stableType');
3108
- body.text8('targetDescriptorVersion');
3109
- body.nonZeroU64('deadlineUnixMs');
3110
- body.end('Instance route');
3084
+ this.u8('instanceRouteKind');
3085
+ this.body16('Instance route');
3086
+ decodeCanonicalInstanceRoute(this.bytes.subarray(start, this.offset));
3111
3087
  }
3112
3088
 
3113
3089
  private relocationObject(): void {