@zlink-systems/framework 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Channels/IZLinkFanoutClient.d.ts +0 -6
  3. package/dist/contracts/RouteMesh/RuntimeTopology.d.ts +8 -0
  4. package/dist/internal.d.ts +1 -0
  5. package/dist/internal.js +1 -0
  6. package/dist/runtime/activation-admission.d.ts +29 -0
  7. package/dist/runtime/activation-admission.js +61 -0
  8. package/dist/runtime/actors/actor-context.js +6 -6
  9. package/dist/runtime/actors/actor-handoff.d.ts +9 -3
  10. package/dist/runtime/actors/actor-handoff.js +28 -17
  11. package/dist/runtime/actors/actor-local-native-join.js +2 -18
  12. package/dist/runtime/actors/actor-remote-joiner.d.ts +2 -2
  13. package/dist/runtime/actors/actor-remote-joiner.js +6 -8
  14. package/dist/runtime/actors/actor-runtime-contracts.d.ts +4 -2
  15. package/dist/runtime/actors/actor-source-transfer.d.ts +5 -5
  16. package/dist/runtime/actors/deferred-join-completion.d.ts +14 -0
  17. package/dist/runtime/actors/deferred-join-completion.js +20 -0
  18. package/dist/runtime/actors/index.d.ts +1 -1
  19. package/dist/runtime/actors/index.js +15 -5
  20. package/dist/runtime/admission.d.ts +5 -1
  21. package/dist/runtime/admission.js +4 -0
  22. package/dist/runtime/backend/contracts/index.d.ts +2 -10
  23. package/dist/runtime/backend/mesh-actor-session-node-adapter.js +2 -9
  24. package/dist/runtime/backend/node/node-backend-adapter-factory.d.ts +1 -2
  25. package/dist/runtime/backend/node/node-backend-adapter-factory.js +4 -18
  26. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +6 -3
  27. package/dist/runtime/backend/node/node-backend-adapter-support.js +12 -28
  28. package/dist/runtime/backend/node/node-event-loop-poller.d.ts +1 -1
  29. package/dist/runtime/backend/node/node-event-loop-poller.js +8 -2
  30. package/dist/runtime/backend/node/node-raw-binding-port.d.ts +1 -1
  31. package/dist/runtime/backend/node/node-raw-binding-port.js +11 -3
  32. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +12 -9
  33. package/dist/runtime/backend/node/node-raw-mesh-backend.js +61 -29
  34. package/dist/runtime/backend/node/node-socket-backend-adapter.js +1 -1
  35. package/dist/runtime/backend/raw-binding-port.d.ts +12 -0
  36. package/dist/runtime/channels/channel-clients.d.ts +0 -1
  37. package/dist/runtime/channels/channel-clients.js +0 -8
  38. package/dist/runtime/channels/channel-outbound-operations.js +2 -10
  39. package/dist/runtime/channels/channel-runtime-manager.d.ts +2 -5
  40. package/dist/runtime/channels/channel-runtime-manager.js +1 -12
  41. package/dist/runtime/channels/channel-socket-registry.d.ts +17 -4
  42. package/dist/runtime/channels/channel-socket-registry.js +49 -62
  43. package/dist/runtime/channels/channel-transports.d.ts +0 -3
  44. package/dist/runtime/channels/fanout-location-runtime.js +3 -9
  45. package/dist/runtime/execution/index.d.ts +18 -8
  46. package/dist/runtime/execution/index.js +55 -29
  47. package/dist/runtime/execution/serial-execution-queue.d.ts +5 -6
  48. package/dist/runtime/execution/serial-execution-queue.js +49 -15
  49. package/dist/runtime/foundation/listener-records.d.ts +11 -0
  50. package/dist/runtime/foundation/listener-records.js +20 -0
  51. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +10 -21
  52. package/dist/runtime/foundation/raw-service-mesh-runtime.js +66 -392
  53. package/dist/runtime/foundation/service-authority-payload-codec.d.ts +1 -0
  54. package/dist/runtime/foundation/service-authority-payload-codec.js +8 -0
  55. package/dist/runtime/foundation/service-maintenance-runtime.js +23 -2
  56. package/dist/runtime/foundation/service-relocation-coordinator.d.ts +9 -0
  57. package/dist/runtime/foundation/service-relocation-coordinator.js +14 -1
  58. package/dist/runtime/foundation/service-relocation-object-owner.d.ts +8 -0
  59. package/dist/runtime/foundation/service-relocation-object-owner.js +16 -4
  60. package/dist/runtime/foundation/service-session-binding-ingress-port.d.ts +6 -13
  61. package/dist/runtime/foundation/service-stateful-runtime.d.ts +10 -7
  62. package/dist/runtime/foundation/service-stateful-runtime.js +100 -60
  63. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +9 -4
  64. package/dist/runtime/foundation/service-stateful-wire-codec.js +153 -90
  65. package/dist/runtime/foundation/service-topology-registry.d.ts +1 -2
  66. package/dist/runtime/foundation/service-topology-registry.js +6 -30
  67. package/dist/runtime/host/actor-transfer-runtime.d.ts +17 -15
  68. package/dist/runtime/host/actor-transfer-runtime.js +73 -86
  69. package/dist/runtime/host/index.d.ts +6 -2
  70. package/dist/runtime/host/index.js +63 -37
  71. package/dist/runtime/host/remote-bound-session-relay.js +4 -3
  72. package/dist/runtime/host/route-mesh-runtime.d.ts +2 -0
  73. package/dist/runtime/host/route-mesh-runtime.js +7 -5
  74. package/dist/runtime/host/service-relocation-host-runtime.d.ts +50 -8
  75. package/dist/runtime/host/service-relocation-host-runtime.js +343 -270
  76. package/dist/runtime/host/spot-runtime-options-factory.d.ts +7 -4
  77. package/dist/runtime/host/spot-runtime-options-factory.js +4 -8
  78. package/dist/runtime/host/stateful-authority-route-runtime.js +6 -2
  79. package/dist/runtime/host/user-spot-creation-coordinator.d.ts +11 -3
  80. package/dist/runtime/host/user-spot-creation-coordinator.js +85 -87
  81. package/dist/runtime/locations/in-memory-provider-location-store.js +8 -6
  82. package/dist/runtime/locations/lifecycle.d.ts +1 -1
  83. package/dist/runtime/locations/lifecycle.js +2 -2
  84. package/dist/runtime/locations/resolvers.js +7 -2
  85. package/dist/runtime/locations/spot-location-claims.d.ts +2 -2
  86. package/dist/runtime/locations/spot-location-claims.js +3 -27
  87. package/dist/runtime/protocol/service_wire_codec.generated.d.ts +1 -0
  88. package/dist/runtime/protocol/service_wire_codec.generated.js +5 -1
  89. package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +1 -0
  90. package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +3 -2
  91. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +3 -3
  92. package/dist/runtime/spots/formal-remote-actor-admission-registry.js +4 -4
  93. package/dist/runtime/spots/formal-remote-actor-transfer-registry.d.ts +3 -3
  94. package/dist/runtime/spots/index.d.ts +25 -19
  95. package/dist/runtime/spots/index.js +207 -205
  96. package/dist/runtime/spots/spot-activation-registry.d.ts +4 -2
  97. package/dist/runtime/spots/spot-activation-registry.js +34 -31
  98. package/dist/runtime/spots/spot-activation-state.d.ts +2 -11
  99. package/dist/runtime/spots/spot-activation-state.js +4 -17
  100. package/dist/runtime/spots/spot-activation.d.ts +8 -6
  101. package/dist/runtime/spots/spot-activation.js +54 -49
  102. package/dist/runtime/spots/spot-actor-admission-coordinator.d.ts +1 -1
  103. package/dist/runtime/spots/spot-actor-admission-coordinator.js +1 -1
  104. package/dist/runtime/spots/spot-actor-join-dispatch.d.ts +2 -2
  105. package/dist/runtime/spots/spot-actor-join-dispatch.js +3 -5
  106. package/dist/runtime/spots/spot-actor-lifecycle-drain.js +2 -2
  107. package/dist/runtime/spots/spot-actor-membership.d.ts +4 -0
  108. package/dist/runtime/spots/spot-actor-membership.js +26 -12
  109. package/dist/runtime/spots/spot-entry-activation.d.ts +1 -2
  110. package/dist/runtime/spots/spot-entry-activation.js +0 -1
  111. package/dist/runtime/spots/spot-manager-public.js +1 -1
  112. package/dist/runtime/spots/spot-native-actor-join-admission.d.ts +1 -1
  113. package/dist/runtime/spots/spot-native-actor-join-admission.js +2 -2
  114. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +5 -5
  115. package/dist/runtime/spots/spot-node-runtime-manager.js +7 -11
  116. package/dist/runtime/spots/spot-outbound.js +1 -29
  117. package/dist/runtime/spots/spot-routed-actor-admission.d.ts +1 -0
  118. package/dist/runtime/spots/spot-routed-actor-admission.js +10 -0
  119. package/dist/runtime/spots/spot-routed-frame-dispatch.d.ts +2 -6
  120. package/dist/runtime/spots/spot-routed-frame-dispatch.js +2 -70
  121. package/dist/runtime/spots/spot-runtime-ports.d.ts +3 -5
  122. package/dist/runtime/spots/spot-serial-executor.d.ts +5 -0
  123. package/dist/runtime/spots/spot-serial-executor.js +8 -1
  124. package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
  125. package/dist/runtime/spots/spot-serial-turn-executor.js +8 -4
  126. package/dist/runtime/streams/actor-session-binding-registry.d.ts +13 -15
  127. package/dist/runtime/streams/actor-session-binding-registry.js +19 -171
  128. package/dist/runtime/streams/actor-session-binding-runtime-owner.d.ts +3 -4
  129. package/dist/runtime/streams/bound-session-service.js +1 -18
  130. package/dist/runtime/streams/index.d.ts +2 -0
  131. package/dist/runtime/streams/index.js +19 -19
  132. package/dist/runtime/streams/session-serial-executor.js +2 -2
  133. package/package.json +3 -3
  134. package/src/contracts/Channels/IZLinkFanoutClient.ts +0 -7
  135. package/src/contracts/RouteMesh/RuntimeTopology.ts +10 -0
  136. package/src/internal.ts +1 -0
  137. package/src/runtime/activation-admission.ts +78 -0
  138. package/src/runtime/actors/actor-context.ts +6 -6
  139. package/src/runtime/actors/actor-handoff.ts +31 -23
  140. package/src/runtime/actors/actor-local-native-join.ts +3 -42
  141. package/src/runtime/actors/actor-remote-joiner.ts +6 -25
  142. package/src/runtime/actors/actor-runtime-contracts.ts +4 -10
  143. package/src/runtime/actors/actor-source-transfer.ts +5 -18
  144. package/src/runtime/actors/deferred-join-completion.ts +40 -0
  145. package/src/runtime/actors/index.ts +22 -9
  146. package/src/runtime/admission.ts +5 -1
  147. package/src/runtime/backend/contracts/index.ts +2 -13
  148. package/src/runtime/backend/mesh-actor-session-node-adapter.ts +2 -10
  149. package/src/runtime/backend/node/node-backend-adapter-factory.ts +5 -25
  150. package/src/runtime/backend/node/node-backend-adapter-support.ts +9 -29
  151. package/src/runtime/backend/node/node-event-loop-poller.ts +14 -2
  152. package/src/runtime/backend/node/node-raw-binding-port.ts +17 -3
  153. package/src/runtime/backend/node/node-raw-mesh-backend.ts +65 -32
  154. package/src/runtime/backend/node/node-socket-backend-adapter.ts +2 -2
  155. package/src/runtime/backend/raw-binding-port.ts +13 -0
  156. package/src/runtime/channels/channel-clients.ts +0 -11
  157. package/src/runtime/channels/channel-outbound-operations.ts +3 -11
  158. package/src/runtime/channels/channel-runtime-manager.ts +4 -15
  159. package/src/runtime/channels/channel-socket-registry.ts +69 -71
  160. package/src/runtime/channels/channel-transports.ts +0 -3
  161. package/src/runtime/channels/fanout-location-runtime.ts +4 -16
  162. package/src/runtime/execution/index.ts +68 -30
  163. package/src/runtime/execution/serial-execution-queue.ts +52 -20
  164. package/src/runtime/foundation/listener-records.ts +21 -0
  165. package/src/runtime/foundation/raw-service-mesh-runtime.ts +66 -466
  166. package/src/runtime/foundation/service-authority-payload-codec.ts +10 -0
  167. package/src/runtime/foundation/service-maintenance-runtime.ts +30 -9
  168. package/src/runtime/foundation/service-relocation-coordinator.ts +13 -0
  169. package/src/runtime/foundation/service-relocation-object-owner.ts +18 -5
  170. package/src/runtime/foundation/service-session-binding-ingress-port.ts +6 -14
  171. package/src/runtime/foundation/service-stateful-runtime.ts +151 -89
  172. package/src/runtime/foundation/service-stateful-wire-codec.ts +203 -121
  173. package/src/runtime/foundation/service-topology-registry.ts +8 -48
  174. package/src/runtime/host/actor-transfer-runtime.ts +94 -166
  175. package/src/runtime/host/index.ts +119 -54
  176. package/src/runtime/host/remote-bound-session-relay.ts +4 -3
  177. package/src/runtime/host/route-mesh-runtime.ts +9 -5
  178. package/src/runtime/host/service-relocation-host-runtime.ts +495 -376
  179. package/src/runtime/host/spot-runtime-options-factory.ts +24 -13
  180. package/src/runtime/host/stateful-authority-route-runtime.ts +8 -4
  181. package/src/runtime/host/user-spot-creation-coordinator.ts +147 -142
  182. package/src/runtime/locations/in-memory-provider-location-store.ts +11 -7
  183. package/src/runtime/locations/lifecycle.ts +3 -2
  184. package/src/runtime/locations/resolvers.ts +19 -13
  185. package/src/runtime/locations/spot-location-claims.ts +5 -34
  186. package/src/runtime/protocol/service_wire_codec.generated.ts +9 -2
  187. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +4 -4
  188. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +6 -6
  189. package/src/runtime/spots/formal-remote-actor-transfer-registry.ts +3 -3
  190. package/src/runtime/spots/index.ts +341 -295
  191. package/src/runtime/spots/spot-activation-registry.ts +41 -31
  192. package/src/runtime/spots/spot-activation-state.ts +4 -16
  193. package/src/runtime/spots/spot-activation.ts +102 -80
  194. package/src/runtime/spots/spot-actor-admission-coordinator.ts +2 -2
  195. package/src/runtime/spots/spot-actor-join-dispatch.ts +6 -7
  196. package/src/runtime/spots/spot-actor-lifecycle-drain.ts +20 -18
  197. package/src/runtime/spots/spot-actor-membership.ts +81 -17
  198. package/src/runtime/spots/spot-entry-activation.ts +0 -3
  199. package/src/runtime/spots/spot-manager-public.ts +8 -4
  200. package/src/runtime/spots/spot-native-actor-join-admission.ts +2 -2
  201. package/src/runtime/spots/spot-node-runtime-manager.ts +12 -18
  202. package/src/runtime/spots/spot-outbound.ts +4 -47
  203. package/src/runtime/spots/spot-routed-actor-admission.ts +20 -0
  204. package/src/runtime/spots/spot-routed-frame-dispatch.ts +6 -76
  205. package/src/runtime/spots/spot-runtime-ports.ts +6 -17
  206. package/src/runtime/spots/spot-serial-executor.ts +9 -1
  207. package/src/runtime/spots/spot-serial-turn-executor.ts +10 -4
  208. package/src/runtime/streams/actor-session-binding-registry.ts +20 -308
  209. package/src/runtime/streams/actor-session-binding-runtime-owner.ts +2 -12
  210. package/src/runtime/streams/bound-session-service.ts +2 -36
  211. package/src/runtime/streams/index.ts +29 -27
  212. package/src/runtime/streams/session-serial-executor.ts +2 -2
  213. package/dist/runtime/actors/deferred-join-accepted-journal.d.ts +0 -47
  214. package/dist/runtime/actors/deferred-join-accepted-journal.js +0 -992
  215. package/src/runtime/actors/deferred-join-accepted-journal.ts +0 -1346
@@ -1,1346 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import type {
3
- ActorRef,
4
- ZLinkActor,
5
- ZLinkActorJoinCompletion,
6
- ZLinkActorJoinOperationId,
7
- ZLinkBlobReference,
8
- ZLinkMessageSerializer,
9
- ZLinkRelocationStore
10
- } from '../../contracts';
11
- import type { ZLinkAuthoritySnapshot } from '../locations/internal-location-contracts';
12
- import type { ZLinkAuthorityStore } from '../locations/internal-store-contracts';
13
- import {
14
- ZLinkEncodedPayload,
15
- ZLinkFrameworkErrorKind,
16
- ZLinkFrameworkException,
17
- ZLinkMessage
18
- } from '../../contracts';
19
- import { encodeAuthorityKey } from '../locations/authority-key-codec';
20
- import {
21
- crc32c,
22
- ServiceRelocationAuthorityPayloadCodec,
23
- replaceServiceRelocationAuthorityApplicationPayload,
24
- serviceRelocationAuthorityApplicationPayload,
25
- serviceRelocationAuthoritySlotIdentity,
26
- type ServiceRelocationPublication
27
- } from '../foundation/service-relocation-runtime';
28
- import { putNewRelocationBlob } from '../locations/relocation-blob';
29
- import { decodeRoutingId, encodeRoutingIdStorageHex, routingIdWireHex } from '../routing-id';
30
- import {
31
- decodeRelocatingActorAuthorityIdentity,
32
- encodeActorAuthorityIdentity
33
- } from './actor-authority-publication';
34
- import { replaceActorRelocationAuthorityApplicationPayload } from './actor-authority-payload-codec';
35
- import { ZLinkBufferMessage as RuntimeMessage } from '../backend/runtime-message';
36
- import { wrapFrameworkPayloadMessage } from '../messaging/payload-codec';
37
- import {
38
- decodeRelocationDataChunkV1,
39
- decodeRelocationManifestV1,
40
- encodeRelocationDataChunkV1,
41
- encodeRelocationManifestV1
42
- } from '../protocol/service_wire_pilot_codec.generated';
43
-
44
- const RETENTION_MS = 24 * 60 * 60 * 1_000;
45
- const ROOT_VERSION = 2;
46
- const ROOT_MAGIC = Buffer.from('ZLJR');
47
- const JOURNAL_INVENTORY_DOMAIN = Buffer.from('zlink-node-deferred-join-authority-v1\0', 'utf8');
48
-
49
- export type ZLinkDeferredJoinDeliveryCursor = 'prepared' | 'committed' | 'delivered';
50
-
51
- export interface ZLinkDeferredJoinAcceptedRoot {
52
- readonly authority: ZLinkAuthoritySnapshot;
53
- readonly reference: ZLinkBlobReference;
54
- readonly checksumCrc32c: number;
55
- readonly operationId: ZLinkActorJoinOperationId;
56
- readonly actor: ActorRef;
57
- readonly rawReply: Buffer;
58
- readonly replyContentType?: string;
59
- readonly cursor: ZLinkDeferredJoinDeliveryCursor;
60
- }
61
-
62
- export interface ZLinkDeferredJoinRootIdentity {
63
- readonly authorityKey: string;
64
- readonly objectKind: 'actor' | 'user_spot' | 'instance_spot';
65
- readonly objectGeneration: bigint;
66
- readonly aggregateId: string;
67
- readonly aggregateGeneration: bigint;
68
- }
69
-
70
- /** Identifies whether an authority slot points at a deferred Join journal root. */
71
- export async function isDeferredJoinAcceptedRootPublication(
72
- relocation: ZLinkRelocationStore,
73
- reference: string,
74
- checksumCrc32c: number,
75
- expected: ZLinkDeferredJoinRootIdentity,
76
- signal?: AbortSignal
77
- ): Promise<boolean> {
78
- if (expected.objectKind !== 'actor' || expected.aggregateGeneration === 0n) return false;
79
- const read = await relocation.read({ value: reference } as ZLinkBlobReference, signal);
80
- if (read.kind !== 'found' || crc32c(read.bytes) !== checksumCrc32c) return false;
81
- if (hasJournalRootMagic(read.bytes)) {
82
- try {
83
- const root = decodeRoot(read.bytes);
84
- return (
85
- expected.aggregateId === operationAggregateId(root.operationId) &&
86
- expected.aggregateGeneration === root.actor.objectGeneration &&
87
- expected.authorityKey === encodeAuthorityKey('actor', root.actor.actorId).value &&
88
- expected.objectGeneration === root.actor.objectGeneration
89
- );
90
- } catch {
91
- return false;
92
- }
93
- }
94
- const root = await readCanonicalDeferredJoinRoot(relocation, read.bytes, signal);
95
- return (
96
- root !== undefined &&
97
- root.identity.aggregateId === expected.aggregateId &&
98
- root.identity.aggregateGeneration === expected.aggregateGeneration &&
99
- root.identity.authorityKey === expected.authorityKey &&
100
- root.identity.objectKind === expected.objectKind &&
101
- root.identity.objectGeneration === expected.objectGeneration
102
- );
103
- }
104
-
105
- interface DeferredJoinAuthorityPublication {
106
- readonly applicationPayload: Buffer;
107
- readonly reference: ZLinkBlobReference;
108
- readonly checksumCrc32c: number;
109
- readonly aggregateId: string;
110
- readonly aggregateGeneration: bigint;
111
- readonly targetOwnerId: string;
112
- readonly targetOwnerLeaseGeneration: bigint;
113
- readonly canonical?: boolean;
114
- readonly canonicalPublication?: ServiceRelocationPublication;
115
- }
116
-
117
- /**
118
- * Keeps a cross-node Accepted Join completion under the Actor authority.
119
- * Every cursor transition writes and verifies a new immutable root before the
120
- * authority CAS. A failed callback leaves the committed root available for the
121
- * next target Actor mailbox retry.
122
- */
123
- export class ZLinkDeferredJoinAcceptedJournal {
124
- constructor(
125
- private readonly authority: ZLinkAuthorityStore,
126
- private readonly relocation: ZLinkRelocationStore,
127
- private readonly messageSerializers?: ReadonlyMap<string, ZLinkMessageSerializer>
128
- ) {}
129
-
130
- async prepare(
131
- actorId: string,
132
- operationId: ZLinkActorJoinOperationId,
133
- actor: ActorRef,
134
- rawReply: Uint8Array,
135
- replyContentType?: string,
136
- signal?: AbortSignal,
137
- canonicalInventoryDigest?: string
138
- ): Promise<ZLinkDeferredJoinAcceptedRoot> {
139
- validateIdentity(actorId, operationId, actor);
140
- if (replyContentType !== undefined && replyContentType.length === 0) {
141
- throw new TypeError('Deferred Join reply content type must be non-empty.');
142
- }
143
- signal?.throwIfAborted();
144
- const key = encodeAuthorityKey('actor', actorId);
145
- const read = await this.authority.readAuthority(key, signal);
146
- if (read.kind !== 'snapshot') {
147
- throw new Error(`Actor '${actorId}' has no authority for durable Join completion.`);
148
- }
149
- requireAuthorityActor(read.payload, actor);
150
- const existing = await this.readPublished(read, signal);
151
- if (existing !== undefined) {
152
- if (isSameOperation(existing, operationId, actor)) {
153
- return existing;
154
- }
155
- if (existing.cursor !== 'delivered') {
156
- requireSameOperation(existing, operationId, actor);
157
- }
158
- }
159
-
160
- const rootValue = {
161
- operationId,
162
- actor,
163
- rawReply: Buffer.from(rawReply),
164
- ...(replyContentType === undefined ? {} : { replyContentType }),
165
- cursor: 'prepared'
166
- } as const;
167
- const initialPublication = decodeAuthorityPublication(read.payload);
168
- const initialCanonicalIdentity = serviceRelocationAuthoritySlotIdentity(read.payload);
169
- const canonicalPublication =
170
- initialPublication?.canonical === true ? initialPublication : undefined;
171
- const root =
172
- canonicalPublication !== undefined &&
173
- (canonicalInventoryDigest !== undefined ||
174
- !canonicalPublication.reference.value.startsWith('zlink-direct:'))
175
- ? await this.storeCanonicalRoot(
176
- rootValue,
177
- read,
178
- canonicalPublication,
179
- signal,
180
- canonicalInventoryDigest
181
- )
182
- : await this.storeRoot(rootValue, signal);
183
- if (initialCanonicalIdentity !== undefined && canonicalInventoryDigest !== undefined) {
184
- return {
185
- ...root,
186
- authority: read
187
- };
188
- }
189
- let expected: ZLinkAuthoritySnapshot = read;
190
- for (let attempt = 0; attempt < 3; attempt++) {
191
- const currentPublication = decodeAuthorityPublication(expected.payload);
192
- const canonicalIdentity = serviceRelocationAuthoritySlotIdentity(expected.payload);
193
- const publication: DeferredJoinAuthorityPublication = {
194
- applicationPayload: Buffer.from(
195
- currentPublication?.applicationPayload ??
196
- serviceRelocationAuthorityApplicationPayload(expected.payload)
197
- ),
198
- reference: root.reference,
199
- checksumCrc32c: root.checksumCrc32c,
200
- aggregateId:
201
- canonicalIdentity !== undefined
202
- ? canonicalIdentity.aggregateId
203
- : operationAggregateId(operationId),
204
- aggregateGeneration:
205
- canonicalIdentity === undefined
206
- ? actor.objectGeneration
207
- : requireCanonicalAggregateGeneration(canonicalIdentity.aggregateGeneration),
208
- targetOwnerId: expected.ownerId,
209
- targetOwnerLeaseGeneration: expected.ownerLeaseGeneration,
210
- ...(currentPublication?.canonicalPublication === undefined
211
- ? {}
212
- : { canonicalPublication: currentPublication.canonicalPublication })
213
- };
214
- const result = await this.authority.compareExchangeAuthority(
215
- key,
216
- expected.storeVersion,
217
- {
218
- kind: 'put',
219
- generationTransition: 'preserve',
220
- payload:
221
- currentPublication === undefined && canonicalIdentity !== undefined
222
- ? new ServiceRelocationAuthorityPayloadCodec().publish(
223
- expected.payload,
224
- toServiceRelocationPublication(publication)
225
- )
226
- : replaceDeferredJoinAuthorityPublication(
227
- expected.payload,
228
- encodeAuthorityPublication(publication)
229
- )
230
- },
231
- signal
232
- );
233
- if (result.kind === 'stored') {
234
- if (existing !== undefined) {
235
- await this.deleteBestEffort(existing.reference);
236
- }
237
- return {
238
- ...root,
239
- authority: storedSnapshot(result)
240
- };
241
- }
242
- if (result.kind !== 'conflict' || result.current.kind !== 'snapshot') break;
243
- const current = await this.readPublished(result.current, signal);
244
- if (current !== undefined) {
245
- await this.deleteBestEffort(root.reference);
246
- requireSameOperation(current, operationId, actor);
247
- return current;
248
- }
249
- requireAuthorityActor(result.current.payload, actor);
250
- expected = result.current;
251
- }
252
- await this.deleteBestEffort(root.reference);
253
- throw new Error(`Actor '${actorId}' authority rejected Join completion publication.`);
254
- }
255
-
256
- async recover(
257
- actorId: string,
258
- signal?: AbortSignal
259
- ): Promise<ZLinkDeferredJoinAcceptedRoot | undefined> {
260
- const read = await this.authority.readAuthority(encodeAuthorityKey('actor', actorId), signal);
261
- return read.kind === 'snapshot' ? await this.readPublished(read, signal) : undefined;
262
- }
263
-
264
- markCommitted(
265
- root: ZLinkDeferredJoinAcceptedRoot,
266
- actor?: ActorRef,
267
- signal?: AbortSignal
268
- ): Promise<ZLinkDeferredJoinAcceptedRoot> {
269
- if (actor !== undefined) requireSameActor(root, actor);
270
- return this.moveCursor(root, 'committed', actor, signal);
271
- }
272
-
273
- markDelivered(
274
- root: ZLinkDeferredJoinAcceptedRoot,
275
- signal?: AbortSignal
276
- ): Promise<ZLinkDeferredJoinAcceptedRoot> {
277
- return this.moveCursor(root, 'delivered', undefined, signal);
278
- }
279
-
280
- async discardPrepared(root: ZLinkDeferredJoinAcceptedRoot, signal?: AbortSignal): Promise<void> {
281
- const key = encodeAuthorityKey('actor', root.actor.actorId);
282
- for (let attempt = 0; attempt < 3; attempt++) {
283
- const read = await this.authority.readAuthority(key, signal);
284
- if (read.kind !== 'snapshot') return;
285
- const current = await this.readPublished(read, signal);
286
- if (current === undefined) return;
287
- requireSameOperation(current, root.operationId, root.actor);
288
- if (current.cursor !== 'prepared') return;
289
- const publication = decodeAuthorityPublication(read.payload);
290
- if (publication === undefined) return;
291
- const result = await this.authority.compareExchangeAuthority(
292
- key,
293
- read.storeVersion,
294
- {
295
- kind: 'put',
296
- generationTransition: 'preserve',
297
- payload: replaceDeferredJoinAuthorityPublication(
298
- read.payload,
299
- publication.applicationPayload
300
- )
301
- },
302
- signal
303
- );
304
- if (result.kind === 'stored') {
305
- await this.deleteBestEffort(current.reference);
306
- return;
307
- }
308
- }
309
- throw new Error(
310
- `Actor '${root.actor.actorId}' deferred Join preparation could not be discarded.`
311
- );
312
- }
313
-
314
- async deliver(
315
- root: ZLinkDeferredJoinAcceptedRoot,
316
- actor: ZLinkActor,
317
- actorRef: ActorRef,
318
- submitMailbox: <T>(operation: () => Promise<T>) => Promise<T>,
319
- signal?: AbortSignal
320
- ): Promise<ZLinkDeferredJoinAcceptedRoot> {
321
- requireSameActor(root, actorRef);
322
- return await submitMailbox(async () => {
323
- const current = await this.recover(actorRef.actorId, signal);
324
- if (current === undefined) {
325
- if (root.cursor === 'delivered') return root;
326
- throw new Error(`Actor '${actorRef.actorId}' lost its deferred Join completion root.`);
327
- }
328
- requireSameOperation(current, root.operationId, actorRef);
329
- if (current.cursor === 'delivered') {
330
- await this.releaseDelivered(current, signal);
331
- return current;
332
- }
333
- if (current.cursor !== 'committed') {
334
- throw new Error(`Actor '${actorRef.actorId}' Join completion is not committed.`);
335
- }
336
- const completion: ZLinkActorJoinCompletion = {
337
- status: 'accepted',
338
- operationId: current.operationId,
339
- actor: current.actor,
340
- reply:
341
- current.rawReply.byteLength === 0
342
- ? undefined
343
- : current.replyContentType === undefined
344
- ? ZLinkMessage.fromEncoded(ZLinkEncodedPayload.from(current.rawReply))
345
- : wrapFrameworkPayloadMessage(
346
- RuntimeMessage.from(current.rawReply),
347
- this.messageSerializers,
348
- current.replyContentType,
349
- undefined,
350
- 'reply'
351
- )
352
- };
353
- await actor.onJoinCompleted?.(completion);
354
- let delivered = current;
355
- for (let attempt = 0; attempt < 3; attempt++) {
356
- delivered = await this.markDelivered(delivered, signal);
357
- if (delivered.cursor === 'delivered') {
358
- await this.releaseDelivered(delivered, signal);
359
- return delivered;
360
- }
361
- }
362
- throw new Error(
363
- `Actor '${actorRef.actorId}' Join completion callback ran but its Delivered cursor could not be stored.`
364
- );
365
- });
366
- }
367
-
368
- private async releaseDelivered(
369
- root: ZLinkDeferredJoinAcceptedRoot,
370
- signal?: AbortSignal
371
- ): Promise<void> {
372
- const key = encodeAuthorityKey('actor', root.actor.actorId);
373
- for (let attempt = 0; attempt < 3; attempt++) {
374
- const read = await this.authority.readAuthority(key, signal);
375
- if (read.kind !== 'snapshot') return;
376
- const current = await this.readPublished(read, signal);
377
- if (current === undefined) return;
378
- requireSameOperation(current, root.operationId, root.actor);
379
- if (current.cursor !== 'delivered') {
380
- throw new Error(`Actor '${root.actor.actorId}' Join completion root is not Delivered.`);
381
- }
382
- const publication = decodeAuthorityPublication(read.payload);
383
- if (publication === undefined) return;
384
- const result = await this.authority.compareExchangeAuthority(
385
- key,
386
- read.storeVersion,
387
- {
388
- kind: 'put',
389
- generationTransition: 'preserve',
390
- payload: replaceDeferredJoinAuthorityPublication(
391
- read.payload,
392
- publication.applicationPayload
393
- )
394
- },
395
- signal
396
- );
397
- if (result.kind === 'stored') {
398
- await this.deleteBestEffort(current.reference);
399
- return;
400
- }
401
- }
402
- throw new Error(
403
- `Actor '${root.actor.actorId}' Delivered Join completion could not be released.`
404
- );
405
- }
406
-
407
- private async moveCursor(
408
- root: ZLinkDeferredJoinAcceptedRoot,
409
- next: ZLinkDeferredJoinDeliveryCursor,
410
- actor: ActorRef | undefined,
411
- signal?: AbortSignal
412
- ): Promise<ZLinkDeferredJoinAcceptedRoot> {
413
- const currentIndex = cursorIndex(root.cursor);
414
- const nextIndex = cursorIndex(next);
415
- if (nextIndex < currentIndex || nextIndex > currentIndex + 1) {
416
- throw new Error('Deferred Join completion cursor transition is invalid.');
417
- }
418
- if (next === root.cursor) return root;
419
-
420
- const key = encodeAuthorityKey('actor', root.actor.actorId);
421
- const read = await this.authority.readAuthority(key, signal);
422
- if (read.kind !== 'snapshot') {
423
- throw new Error(
424
- `Actor '${root.actor.actorId}' authority disappeared during Join completion.`
425
- );
426
- }
427
- let current = await this.readPublished(read, signal);
428
- if (current === undefined) {
429
- const retainedRoot = await this.relocation.read(root.reference, signal);
430
- if (retainedRoot.kind === 'found' && crc32c(retainedRoot.bytes) === root.checksumCrc32c) {
431
- const retained = hasJournalRootMagic(retainedRoot.bytes)
432
- ? decodeRoot(retainedRoot.bytes)
433
- : (await readCanonicalDeferredJoinRoot(this.relocation, retainedRoot.bytes, signal))
434
- ?.completion;
435
- if (
436
- retained?.cursor === 'delivered' &&
437
- isSameOperation({ ...root, ...retained }, root.operationId, root.actor)
438
- ) {
439
- current = {
440
- authority: read,
441
- reference: root.reference,
442
- checksumCrc32c: root.checksumCrc32c,
443
- ...retained
444
- };
445
- }
446
- }
447
- if (
448
- current === undefined &&
449
- next === 'committed' &&
450
- actor !== undefined &&
451
- authorityMatchesActor(read.payload, actor)
452
- ) {
453
- throw new ZLinkFrameworkException(
454
- ZLinkFrameworkErrorKind.Unavailable,
455
- `Actor '${root.actor.actorId}' Join completion delivery is indeterminate: ` +
456
- 'the authority no longer references the operation and no matching retained root exists.'
457
- );
458
- }
459
- if (current === undefined) {
460
- throw new Error(
461
- `Actor '${root.actor.actorId}' no longer references its Join completion root.`
462
- );
463
- }
464
- }
465
- requireSameOperation(current, root.operationId, root.actor);
466
- if (cursorIndex(current.cursor) >= nextIndex) return current;
467
-
468
- const replacement = await this.storeRoot(
469
- {
470
- operationId: current.operationId,
471
- actor: actor ?? current.actor,
472
- rawReply: current.rawReply,
473
- ...(current.replyContentType === undefined
474
- ? {}
475
- : { replyContentType: current.replyContentType }),
476
- cursor: next
477
- },
478
- signal
479
- );
480
- const publication = decodeAuthorityPublication(read.payload);
481
- if (publication === undefined) {
482
- await this.deleteBestEffort(replacement.reference);
483
- throw new Error('Actor authority lost its deferred Join publication.');
484
- }
485
- const identity = decodeRelocatingActorAuthorityIdentity(
486
- publication.applicationPayload,
487
- read.objectGeneration
488
- );
489
- if (identity === undefined) {
490
- await this.deleteBestEffort(replacement.reference);
491
- throw new Error('Actor authority identity is invalid.');
492
- }
493
- const result = await this.authority.compareExchangeAuthority(
494
- key,
495
- read.storeVersion,
496
- {
497
- kind: 'put',
498
- generationTransition: 'preserve',
499
- payload: replaceDeferredJoinAuthorityPublication(
500
- read.payload,
501
- encodeAuthorityPublication({
502
- ...publication,
503
- applicationPayload:
504
- actor === undefined
505
- ? publication.applicationPayload
506
- : replaceActorRelocationAuthorityApplicationPayload(
507
- publication.applicationPayload,
508
- encodeActorAuthorityIdentity({ ...identity, actor })
509
- ),
510
- reference: replacement.reference,
511
- checksumCrc32c: replacement.checksumCrc32c
512
- })
513
- )
514
- },
515
- signal
516
- );
517
- if (result.kind !== 'stored') {
518
- await this.deleteBestEffort(replacement.reference);
519
- const recovered = await this.recover(root.actor.actorId, signal);
520
- if (recovered === undefined) {
521
- throw new Error('Deferred Join cursor CAS conflicted without a recoverable root.');
522
- }
523
- requireSameOperation(recovered, root.operationId, root.actor);
524
- return recovered;
525
- }
526
- await this.deleteBestEffort(current.reference);
527
- return {
528
- ...replacement,
529
- authority: storedSnapshot(result)
530
- };
531
- }
532
-
533
- private async readPublished(
534
- authority: ZLinkAuthoritySnapshot,
535
- signal?: AbortSignal
536
- ): Promise<ZLinkDeferredJoinAcceptedRoot | undefined> {
537
- const publication = decodeAuthorityPublication(authority.payload);
538
- if (publication === undefined) return undefined;
539
- // Preparing has no relocation root, while Captured still names the
540
- // source-memory direct-transfer payload. Neither pre-target-publication
541
- // phase can contain a Relocation Store backed deferred Join root.
542
- const canonicalPhase = publication.canonicalPublication?.canonicalPhase;
543
- if (canonicalPhase === 1 || canonicalPhase === 2) return undefined;
544
- const read = await this.relocation.read(publication.reference, signal);
545
- if (
546
- read.kind !== 'found' &&
547
- publication.canonical === true &&
548
- publication.reference.value.startsWith('zlink-direct:')
549
- ) {
550
- return undefined;
551
- }
552
- if (read.kind !== 'found' || crc32c(read.bytes) !== publication.checksumCrc32c) {
553
- throw new Error('Published deferred Join completion root is missing or corrupt.');
554
- }
555
- if (publication.canonical === true && !hasJournalRootMagic(read.bytes)) {
556
- const root = await readCanonicalDeferredJoinRoot(this.relocation, read.bytes, signal);
557
- if (root === undefined) {
558
- // A canonical relocation root and the post-commit Join journal share
559
- // the embedded slot. A root without a completion remains Host-owned.
560
- return undefined;
561
- }
562
- if (
563
- root.identity.aggregateId !== publication.aggregateId ||
564
- root.identity.aggregateGeneration !== publication.aggregateGeneration ||
565
- root.identity.objectGeneration !== authority.objectGeneration
566
- ) {
567
- throw new Error(
568
- 'Published deferred Join completion root identity does not match its authority slot.'
569
- );
570
- }
571
- return {
572
- authority,
573
- reference: publication.reference,
574
- checksumCrc32c: publication.checksumCrc32c,
575
- ...root.completion
576
- };
577
- }
578
- return {
579
- authority,
580
- reference: publication.reference,
581
- checksumCrc32c: publication.checksumCrc32c,
582
- ...decodeRoot(read.bytes)
583
- };
584
- }
585
-
586
- private async storeRoot(
587
- value: Omit<ZLinkDeferredJoinAcceptedRoot, 'authority' | 'reference' | 'checksumCrc32c'>,
588
- signal?: AbortSignal
589
- ): Promise<Omit<ZLinkDeferredJoinAcceptedRoot, 'authority'>> {
590
- const payload = encodeRoot(value);
591
- const checksumCrc32c = crc32c(payload);
592
- const stored = await putNewRelocationBlob(this.relocation, payload, RETENTION_MS, signal);
593
- if (stored.expiresAt.getTime() <= stored.storeNow.getTime()) {
594
- await this.deleteBestEffort(stored.reference);
595
- throw new Error('Relocation Store returned an invalid deferred Join root receipt.');
596
- }
597
- const read = await this.relocation.read(stored.reference, signal);
598
- if (read.kind !== 'found' || !Buffer.from(read.bytes).equals(payload)) {
599
- await this.deleteBestEffort(stored.reference);
600
- throw new Error('Relocation Store failed deferred Join root verification.');
601
- }
602
- return {
603
- ...value,
604
- rawReply: Buffer.from(value.rawReply),
605
- reference: stored.reference,
606
- checksumCrc32c
607
- };
608
- }
609
-
610
- private async storeCanonicalRoot(
611
- value: Omit<ZLinkDeferredJoinAcceptedRoot, 'authority' | 'reference' | 'checksumCrc32c'>,
612
- authority: ZLinkAuthoritySnapshot,
613
- publication: DeferredJoinAuthorityPublication,
614
- signal?: AbortSignal,
615
- canonicalInventoryDigest?: string
616
- ): Promise<Omit<ZLinkDeferredJoinAcceptedRoot, 'authority'>> {
617
- let inventory: Buffer;
618
- if (canonicalInventoryDigest !== undefined) {
619
- if (!/^[0-9a-f]{64}$/u.test(canonicalInventoryDigest)) {
620
- throw new Error('Canonical relocation inventory digest is invalid.');
621
- }
622
- inventory = Buffer.from(canonicalInventoryDigest, 'hex');
623
- } else {
624
- const current = await this.relocation.read(publication.reference, signal);
625
- if (current.kind !== 'found' || crc32c(current.bytes) !== publication.checksumCrc32c) {
626
- throw new Error('Canonical relocation manifest is missing or corrupt.');
627
- }
628
- inventory = decodeCanonicalTreeManifest(current.bytes).inventoryDigest;
629
- }
630
- const logicalRoot = encodeCanonicalDeferredJoinRoot(value, authority, publication, inventory);
631
- const stored = await storeCanonicalTree(this.relocation, logicalRoot, inventory, signal);
632
- return {
633
- ...value,
634
- rawReply: Buffer.from(value.rawReply),
635
- reference: stored.reference,
636
- checksumCrc32c: stored.checksumCrc32c
637
- };
638
- }
639
-
640
- private async deleteBestEffort(reference: ZLinkBlobReference): Promise<void> {
641
- try {
642
- await this.relocation.delete(reference);
643
- } catch {
644
- // Fixed retention remains the orphan cleanup boundary.
645
- }
646
- }
647
- }
648
-
649
- function requireAuthorityActor(payload: Uint8Array, actor: ActorRef): void {
650
- if (!authorityMatchesActor(payload, actor)) {
651
- throw new Error(`Actor '${actor.actorId}' authority fence does not match its ActorRef.`);
652
- }
653
- }
654
-
655
- function authorityMatchesActor(payload: Uint8Array, actor: ActorRef): boolean {
656
- const publication = decodeAuthorityPublication(payload);
657
- const applications =
658
- publication === undefined
659
- ? [serviceRelocationAuthorityApplicationPayload(payload)]
660
- : [publication.applicationPayload, serviceRelocationAuthorityApplicationPayload(payload)];
661
- return applications.some((application) => {
662
- const identity = decodeRelocatingActorAuthorityIdentity(application, actor.objectGeneration);
663
- return (
664
- identity !== undefined &&
665
- identity.actor.actorId === actor.actorId &&
666
- identity.actor.objectGeneration === actor.objectGeneration &&
667
- routingIdWireHex(identity.actor.nodeRid) === routingIdWireHex(actor.nodeRid)
668
- );
669
- });
670
- }
671
-
672
- function encodeRoot(
673
- value: Omit<ZLinkDeferredJoinAcceptedRoot, 'authority' | 'reference' | 'checksumCrc32c'>
674
- ): Buffer {
675
- const nodeRidHex = routingIdWireHex(value.actor.nodeRid);
676
- const encoded = Buffer.concat([
677
- ROOT_MAGIC,
678
- Buffer.from(
679
- JSON.stringify({
680
- version: ROOT_VERSION,
681
- operationHigh: value.operationId.high.toString(),
682
- operationLow: value.operationId.low.toString(),
683
- actorId: value.actor.actorId,
684
- actorGeneration: value.actor.objectGeneration.toString(),
685
- actorMeshName: value.actor.meshName,
686
- actorNodeRid: String(value.actor.nodeRid),
687
- actorNodeRidHex: nodeRidHex,
688
- rawReply: Buffer.from(value.rawReply).toString('base64'),
689
- ...(value.replyContentType === undefined
690
- ? {}
691
- : { replyContentType: value.replyContentType }),
692
- cursor: value.cursor
693
- }),
694
- 'utf8'
695
- )
696
- ]);
697
- return encoded;
698
- }
699
-
700
- function decodeRoot(
701
- payload: Uint8Array
702
- ): Omit<ZLinkDeferredJoinAcceptedRoot, 'authority' | 'reference' | 'checksumCrc32c'> {
703
- if (payload.byteLength === 0) {
704
- throw new Error('Deferred Join completion root is empty.');
705
- }
706
- const bytes = Buffer.from(payload);
707
- const encoded = hasJournalRootMagic(bytes) ? bytes.subarray(ROOT_MAGIC.byteLength) : bytes;
708
- const value = JSON.parse(encoded.toString('utf8')) as Record<string, unknown>;
709
- if (
710
- value.version !== ROOT_VERSION ||
711
- typeof value.operationHigh !== 'string' ||
712
- typeof value.operationLow !== 'string' ||
713
- typeof value.actorId !== 'string' ||
714
- typeof value.actorGeneration !== 'string' ||
715
- typeof value.actorMeshName !== 'string' ||
716
- typeof value.actorNodeRid !== 'string' ||
717
- typeof value.rawReply !== 'string' ||
718
- (value.replyContentType !== undefined &&
719
- (typeof value.replyContentType !== 'string' || value.replyContentType.length === 0)) ||
720
- !isCursor(value.cursor)
721
- ) {
722
- throw new Error('Deferred Join completion root is invalid.');
723
- }
724
- const rawReply = Buffer.from(value.rawReply, 'base64');
725
- if (rawReply.toString('base64') !== value.rawReply) {
726
- throw new Error('Deferred Join completion reply is not canonical base64.');
727
- }
728
- const actorGeneration = BigInt(value.actorGeneration);
729
- const operationId = {
730
- high: BigInt(value.operationHigh),
731
- low: BigInt(value.operationLow)
732
- };
733
- const actor: ActorRef = {
734
- actorId: value.actorId,
735
- objectGeneration: actorGeneration,
736
- meshName: value.actorMeshName,
737
- nodeRid: decodeRoutingId(
738
- value.actorNodeRid,
739
- typeof value.actorNodeRidHex === 'string' ? value.actorNodeRidHex : undefined
740
- )
741
- };
742
- validateIdentity(value.actorId, operationId, actor);
743
- return {
744
- operationId,
745
- actor,
746
- rawReply,
747
- ...(typeof value.replyContentType === 'string'
748
- ? { replyContentType: value.replyContentType }
749
- : {}),
750
- cursor: value.cursor
751
- };
752
- }
753
-
754
- function encodeAuthorityPublication(value: DeferredJoinAuthorityPublication): Buffer {
755
- return Buffer.from(
756
- new ServiceRelocationAuthorityPayloadCodec().publish(
757
- value.applicationPayload,
758
- toServiceRelocationPublication(value)
759
- )
760
- );
761
- }
762
-
763
- function toServiceRelocationPublication(
764
- value: DeferredJoinAuthorityPublication
765
- ): ServiceRelocationPublication {
766
- return {
767
- ...value.canonicalPublication,
768
- reference: value.reference.value,
769
- checksumCrc32c: value.checksumCrc32c,
770
- aggregateId: value.aggregateId,
771
- aggregateGeneration: value.aggregateGeneration,
772
- inventoryDigest: journalInventoryDigest(value.applicationPayload),
773
- targetOwnerId: value.targetOwnerId,
774
- targetOwnerLeaseGeneration: value.targetOwnerLeaseGeneration
775
- };
776
- }
777
-
778
- function decodeAuthorityPublication(
779
- payload: Uint8Array
780
- ): DeferredJoinAuthorityPublication | undefined {
781
- const outerApplication = serviceRelocationAuthorityApplicationPayload(payload);
782
- const candidates = Buffer.from(outerApplication).equals(Buffer.from(payload))
783
- ? [Buffer.from(payload)]
784
- : [Buffer.from(outerApplication), Buffer.from(payload)];
785
- for (const candidate of candidates) {
786
- const publication = decodeDirectAuthorityPublication(candidate);
787
- if (publication !== undefined) return publication;
788
- }
789
- return undefined;
790
- }
791
-
792
- function replaceDeferredJoinAuthorityPublication(
793
- payload: Uint8Array,
794
- replacement: Uint8Array
795
- ): Buffer {
796
- const codec = new ServiceRelocationAuthorityPayloadCodec();
797
- const currentPublication = codec.read(payload);
798
- const replacementPublication = codec.read(replacement);
799
- if (currentPublication?.canonical === true || replacementPublication?.canonical === true) {
800
- // Canonical authority has exactly one embedded relocation slot. A journal
801
- // cursor replaces that slot atomically; it must never be wrapped or nested
802
- // under an older ZLAR/application layer.
803
- return Buffer.from(replacement);
804
- }
805
- const outerApplication = serviceRelocationAuthorityApplicationPayload(payload);
806
- if (
807
- !Buffer.from(outerApplication).equals(Buffer.from(payload)) &&
808
- decodeDirectAuthorityPublication(outerApplication) !== undefined
809
- ) {
810
- return replaceServiceRelocationAuthorityApplicationPayload(payload, replacement);
811
- }
812
- return decodeDirectAuthorityPublication(payload) === undefined
813
- ? replaceServiceRelocationAuthorityApplicationPayload(payload, replacement)
814
- : Buffer.from(replacement);
815
- }
816
-
817
- function decodeDirectAuthorityPublication(
818
- payload: Uint8Array
819
- ): DeferredJoinAuthorityPublication | undefined {
820
- const publication = new ServiceRelocationAuthorityPayloadCodec().read(payload);
821
- if (publication === undefined) return undefined;
822
- const applicationPayload = serviceRelocationAuthorityApplicationPayload(payload);
823
- return publication.canonical === true ||
824
- publication.inventoryDigest === journalInventoryDigest(applicationPayload)
825
- ? deferredJoinPublication(applicationPayload, publication)
826
- : undefined;
827
- }
828
-
829
- function deferredJoinPublication(
830
- applicationPayload: Buffer,
831
- publication: ServiceRelocationPublication
832
- ): DeferredJoinAuthorityPublication {
833
- return {
834
- applicationPayload,
835
- reference: { value: publication.reference } as ZLinkBlobReference,
836
- checksumCrc32c: publication.checksumCrc32c,
837
- aggregateId: publication.aggregateId,
838
- aggregateGeneration: publication.aggregateGeneration,
839
- targetOwnerId: publication.targetOwnerId,
840
- targetOwnerLeaseGeneration: publication.targetOwnerLeaseGeneration,
841
- canonical: publication.canonical,
842
- ...(publication.canonical === true ? { canonicalPublication: publication } : {})
843
- };
844
- }
845
-
846
- function hasJournalRootMagic(payload: Uint8Array): boolean {
847
- const bytes = Buffer.from(payload);
848
- return (
849
- bytes.byteLength >= ROOT_MAGIC.byteLength &&
850
- bytes.subarray(0, ROOT_MAGIC.byteLength).equals(ROOT_MAGIC)
851
- );
852
- }
853
-
854
- function journalInventoryDigest(applicationPayload: Uint8Array): string {
855
- return createHash('sha256')
856
- .update(JOURNAL_INVENTORY_DOMAIN)
857
- .update(applicationPayload)
858
- .digest('hex');
859
- }
860
-
861
- function operationAggregateId(operationId: ZLinkActorJoinOperationId): string {
862
- const encoded = [operationId.high, operationId.low]
863
- .map((value) => BigInt.asUintN(64, value).toString(16).padStart(16, '0'))
864
- .join('');
865
- return (
866
- `${encoded.slice(0, 8)}-${encoded.slice(8, 12)}-${encoded.slice(12, 16)}` +
867
- `-${encoded.slice(16, 20)}-${encoded.slice(20)}`
868
- );
869
- }
870
-
871
- function requireCanonicalAggregateGeneration(value: bigint): bigint {
872
- if (value === 0n) {
873
- throw new ZLinkFrameworkException(
874
- ZLinkFrameworkErrorKind.DataLost,
875
- 'Canonical relocation slot has no aggregate generation.'
876
- );
877
- }
878
- return value;
879
- }
880
-
881
- interface CanonicalTreeManifest {
882
- readonly inventoryDigest: Buffer;
883
- readonly logicalLength: bigint;
884
- readonly logicalChecksumCrc32c: number;
885
- readonly chunks: readonly {
886
- readonly order: number;
887
- readonly reference: ZLinkBlobReference;
888
- readonly length: bigint;
889
- readonly checksumCrc32c: number;
890
- }[];
891
- }
892
-
893
- function decodeCanonicalTreeManifest(payload: Uint8Array): CanonicalTreeManifest {
894
- const generated = decodeRelocationManifestV1(payload);
895
- const chunks = generated.chunks.map((chunk, index) => {
896
- if (chunk.order !== index) {
897
- throw new Error('Canonical relocation chunk identity is invalid.');
898
- }
899
- return {
900
- order: chunk.order,
901
- reference: { value: chunk.reference } as ZLinkBlobReference,
902
- length: chunk.length,
903
- checksumCrc32c: chunk.checksumCrc32c
904
- };
905
- });
906
- if (chunks.length === 0) {
907
- throw new Error('Canonical relocation chunk count is invalid.');
908
- }
909
- if (chunks.reduce((total, value) => total + value.length, 0n) !== generated.totalLength) {
910
- throw new Error('Canonical relocation manifest length is invalid.');
911
- }
912
- return {
913
- inventoryDigest: Buffer.from(generated.inventoryDigestSha256),
914
- logicalLength: generated.totalLength,
915
- logicalChecksumCrc32c: generated.totalChecksumCrc32c,
916
- chunks
917
- };
918
- }
919
-
920
- async function storeCanonicalTree(
921
- store: ZLinkRelocationStore,
922
- logicalRoot: Uint8Array,
923
- inventoryDigest: Uint8Array,
924
- signal?: AbortSignal
925
- ): Promise<{ readonly reference: ZLinkBlobReference; readonly checksumCrc32c: number }> {
926
- const logical = Buffer.from(logicalRoot);
927
- const logicalChecksumCrc32c = crc32c(logical);
928
- const chunk = Buffer.from(
929
- encodeRelocationDataChunkV1({
930
- order: 0,
931
- data: logical
932
- })
933
- );
934
- const storedChunk = await putAndVerifyCanonicalBlob(store, chunk, signal);
935
- const manifest = Buffer.from(
936
- encodeRelocationManifestV1({
937
- logicalFormatVersion: 1,
938
- totalLength: BigInt(logical.byteLength),
939
- totalChecksumCrc32c: logicalChecksumCrc32c,
940
- inventoryDigestSha256: Buffer.from(inventoryDigest),
941
- chunks: [
942
- {
943
- order: 0,
944
- reference: storedChunk.reference.value,
945
- length: BigInt(logical.byteLength),
946
- checksumCrc32c: logicalChecksumCrc32c
947
- }
948
- ]
949
- })
950
- );
951
- const storedManifest = await putAndVerifyCanonicalBlob(store, manifest, signal);
952
- return {
953
- reference: storedManifest.reference,
954
- checksumCrc32c: crc32c(manifest)
955
- };
956
- }
957
-
958
- async function putAndVerifyCanonicalBlob(
959
- store: ZLinkRelocationStore,
960
- payload: Uint8Array,
961
- signal?: AbortSignal
962
- ): Promise<{ readonly reference: ZLinkBlobReference }> {
963
- const stored = await putNewRelocationBlob(store, payload, RETENTION_MS, signal);
964
- const read = await store.read(stored.reference, signal);
965
- if (read.kind !== 'found' || !Buffer.from(read.bytes).equals(Buffer.from(payload))) {
966
- await store.delete(stored.reference).catch(() => undefined);
967
- throw new Error('Relocation Store failed canonical tree verification.');
968
- }
969
- return { reference: stored.reference };
970
- }
971
-
972
- async function readCanonicalDeferredJoinRoot(
973
- store: ZLinkRelocationStore,
974
- manifestPayload: Uint8Array,
975
- signal?: AbortSignal
976
- ): Promise<
977
- | {
978
- readonly identity: ZLinkDeferredJoinRootIdentity;
979
- readonly completion: Omit<
980
- ZLinkDeferredJoinAcceptedRoot,
981
- 'authority' | 'reference' | 'checksumCrc32c'
982
- >;
983
- }
984
- | undefined
985
- > {
986
- let manifest: CanonicalTreeManifest;
987
- try {
988
- manifest = decodeCanonicalTreeManifest(manifestPayload);
989
- } catch {
990
- return undefined;
991
- }
992
- const parts: Buffer[] = [];
993
- for (const expected of manifest.chunks) {
994
- const read = await store.read(expected.reference, signal);
995
- if (read.kind !== 'found') throw new Error('Canonical relocation chunk is missing.');
996
- const chunk = decodeRelocationDataChunkV1(read.bytes);
997
- if (chunk.order !== expected.order || BigInt(chunk.data.byteLength) !== expected.length) {
998
- throw new Error('Canonical relocation chunk identity is invalid.');
999
- }
1000
- const data = Buffer.from(chunk.data);
1001
- if (crc32c(data) !== expected.checksumCrc32c) {
1002
- throw new Error('Canonical relocation chunk data checksum is invalid.');
1003
- }
1004
- parts.push(data);
1005
- }
1006
- const logical = Buffer.concat(parts);
1007
- if (
1008
- BigInt(logical.byteLength) !== manifest.logicalLength ||
1009
- crc32c(logical) !== manifest.logicalChecksumCrc32c
1010
- ) {
1011
- throw new Error('Canonical relocation logical root checksum is invalid.');
1012
- }
1013
- return decodeCanonicalDeferredJoinRoot(logical);
1014
- }
1015
-
1016
- function encodeCanonicalDeferredJoinRoot(
1017
- value: Omit<ZLinkDeferredJoinAcceptedRoot, 'authority' | 'reference' | 'checksumCrc32c'>,
1018
- authority: ZLinkAuthoritySnapshot,
1019
- publication: DeferredJoinAuthorityPublication,
1020
- inventoryDigest: Uint8Array
1021
- ): Buffer {
1022
- const key = encodeAuthorityKey('actor', value.actor.actorId).value;
1023
- const completion = encodeDotNetDeferredJoinCompletion(value);
1024
- return Buffer.concat([
1025
- u32le(0x5a4c5231),
1026
- u16le(2),
1027
- dotNetGuidBytes(publication.aggregateId),
1028
- u64le(publication.aggregateGeneration),
1029
- bytes32le(inventoryDigest),
1030
- u32le(1),
1031
- text16le(key),
1032
- Buffer.of(1),
1033
- u64le(authority.objectGeneration),
1034
- u64le(authority.authorityOwnerGeneration),
1035
- bytes32le(Buffer.alloc(0)),
1036
- u32le(0),
1037
- u32le(0),
1038
- bytes32le(Buffer.alloc(0)),
1039
- bytes32le(completion)
1040
- ]);
1041
- }
1042
-
1043
- function decodeCanonicalDeferredJoinRoot(payload: Uint8Array):
1044
- | {
1045
- readonly identity: ZLinkDeferredJoinRootIdentity;
1046
- readonly completion: Omit<
1047
- ZLinkDeferredJoinAcceptedRoot,
1048
- 'authority' | 'reference' | 'checksumCrc32c'
1049
- >;
1050
- }
1051
- | undefined {
1052
- const reader = new LittleEndianReader(payload);
1053
- if (reader.u32() !== 0x5a4c5231 || reader.u16() !== 2) return undefined;
1054
- const aggregateId = canonicalUuidFromDotNetBytes(reader.take(16));
1055
- const aggregateGeneration = reader.u64();
1056
- reader.bytes32();
1057
- const count = reader.u32();
1058
- let completion: ReturnType<typeof decodeDotNetDeferredJoinCompletion> | undefined;
1059
- let completionIdentity:
1060
- | Pick<ZLinkDeferredJoinRootIdentity, 'authorityKey' | 'objectKind' | 'objectGeneration'>
1061
- | undefined;
1062
- for (let index = 0; index < count; index++) {
1063
- const authorityKey = reader.text16();
1064
- const objectKind = reader.u8();
1065
- const objectGeneration = reader.u64();
1066
- reader.u64();
1067
- reader.bytes32();
1068
- const jobs = reader.u32();
1069
- for (let job = 0; job < jobs; job++) {
1070
- reader.u64();
1071
- reader.bytes32();
1072
- }
1073
- const timers = reader.u32();
1074
- for (let timer = 0; timer < timers; timer++) {
1075
- reader.text16();
1076
- reader.i64();
1077
- reader.i64();
1078
- reader.bytes32();
1079
- }
1080
- reader.bytes32();
1081
- const encodedCompletion = reader.bytes32();
1082
- if (encodedCompletion.byteLength !== 0) {
1083
- if (completion !== undefined)
1084
- throw new Error('Canonical relocation root has duplicate completions.');
1085
- completion = decodeDotNetDeferredJoinCompletion(encodedCompletion);
1086
- if (objectKind !== 1) return undefined;
1087
- completionIdentity = {
1088
- authorityKey,
1089
- objectKind: 'actor',
1090
- objectGeneration
1091
- };
1092
- }
1093
- }
1094
- if (!reader.done) throw new Error('Canonical relocation root has trailing bytes.');
1095
- if (completion === undefined || completionIdentity === undefined) return undefined;
1096
- if (
1097
- completion.actor.objectGeneration !== completionIdentity.objectGeneration ||
1098
- encodeAuthorityKey('actor', completion.actor.actorId).value !== completionIdentity.authorityKey
1099
- ) {
1100
- return undefined;
1101
- }
1102
- return {
1103
- identity: {
1104
- ...completionIdentity,
1105
- aggregateId,
1106
- aggregateGeneration
1107
- },
1108
- completion
1109
- };
1110
- }
1111
-
1112
- function encodeDotNetDeferredJoinCompletion(
1113
- value: Omit<ZLinkDeferredJoinAcceptedRoot, 'authority' | 'reference' | 'checksumCrc32c'>
1114
- ): Buffer {
1115
- const routingId = Buffer.from(encodeRoutingIdStorageHex(value.actor.nodeRid), 'hex');
1116
- return Buffer.concat([
1117
- u32le(0x5a4c4a43),
1118
- Buffer.of(2),
1119
- text16le(value.actor.actorId),
1120
- u64le(value.actor.objectGeneration),
1121
- u64le(value.operationId.high),
1122
- u64le(value.operationId.low),
1123
- text16le(value.actor.meshName),
1124
- Buffer.of(routingId.byteLength),
1125
- routingId,
1126
- u64le(value.actor.objectGeneration),
1127
- Buffer.of(value.replyContentType === undefined ? 0 : 1),
1128
- ...(value.replyContentType === undefined ? [] : [text16le(value.replyContentType)]),
1129
- bytes32le(value.rawReply),
1130
- Buffer.of(cursorIndex(value.cursor) + 1)
1131
- ]);
1132
- }
1133
-
1134
- function decodeDotNetDeferredJoinCompletion(
1135
- payload: Uint8Array
1136
- ): Omit<ZLinkDeferredJoinAcceptedRoot, 'authority' | 'reference' | 'checksumCrc32c'> {
1137
- const reader = new LittleEndianReader(payload);
1138
- if (reader.u32() !== 0x5a4c4a43 || reader.u8() !== 2) {
1139
- throw new Error('Canonical deferred Join completion header is invalid.');
1140
- }
1141
- const actorId = reader.text16();
1142
- const objectGeneration = reader.u64();
1143
- const operationId = { high: reader.u64(), low: reader.u64() };
1144
- const meshName = reader.text16();
1145
- const nodeBytes = reader.take(reader.u8());
1146
- const actorGeneration = reader.u64();
1147
- const contentTypePresence = reader.u8();
1148
- if (contentTypePresence > 1) {
1149
- throw new Error('Canonical deferred Join completion content type is invalid.');
1150
- }
1151
- const replyContentType = contentTypePresence === 1 ? reader.text16() : undefined;
1152
- const rawReply = reader.bytes32();
1153
- const cursorValue = reader.u8();
1154
- if (!reader.done || actorGeneration !== objectGeneration || cursorValue < 1 || cursorValue > 3) {
1155
- throw new Error('Canonical deferred Join completion is invalid.');
1156
- }
1157
- const actor: ActorRef = {
1158
- actorId,
1159
- objectGeneration,
1160
- meshName,
1161
- nodeRid: decodeRoutingId('', nodeBytes.toString('hex'))
1162
- };
1163
- validateIdentity(actorId, operationId, actor);
1164
- return {
1165
- operationId,
1166
- actor,
1167
- rawReply,
1168
- ...(replyContentType === undefined ? {} : { replyContentType }),
1169
- cursor: (['prepared', 'committed', 'delivered'] as const)[cursorValue - 1]!
1170
- };
1171
- }
1172
-
1173
- class LittleEndianReader {
1174
- private offset = 0;
1175
- private readonly bytes: Buffer;
1176
- constructor(payload: Uint8Array) {
1177
- this.bytes = Buffer.from(payload);
1178
- }
1179
- get done(): boolean {
1180
- return this.offset === this.bytes.byteLength;
1181
- }
1182
- take(length: number): Buffer {
1183
- if (length < 0 || this.offset + length > this.bytes.byteLength) {
1184
- throw new Error('Canonical relocation root is truncated.');
1185
- }
1186
- const result = this.bytes.subarray(this.offset, this.offset + length);
1187
- this.offset += length;
1188
- return result;
1189
- }
1190
- u8(): number {
1191
- return this.take(1)[0]!;
1192
- }
1193
- u16(): number {
1194
- return this.take(2).readUInt16LE();
1195
- }
1196
- u32(): number {
1197
- return this.take(4).readUInt32LE();
1198
- }
1199
- u64(): bigint {
1200
- return this.take(8).readBigUInt64LE();
1201
- }
1202
- i64(): bigint {
1203
- return this.take(8).readBigInt64LE();
1204
- }
1205
- text16(): string {
1206
- const bytes = this.take(this.u16());
1207
- return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
1208
- }
1209
- bytes32(): Buffer {
1210
- return this.take(this.u32());
1211
- }
1212
- }
1213
-
1214
- function dotNetGuidBytes(value: string): Buffer {
1215
- const bytes = Buffer.from(value.replaceAll('-', ''), 'hex');
1216
- return Buffer.from([
1217
- bytes[3]!,
1218
- bytes[2]!,
1219
- bytes[1]!,
1220
- bytes[0]!,
1221
- bytes[5]!,
1222
- bytes[4]!,
1223
- bytes[7]!,
1224
- bytes[6]!,
1225
- ...bytes.subarray(8)
1226
- ]);
1227
- }
1228
-
1229
- function canonicalUuidFromDotNetBytes(value: Uint8Array): string {
1230
- const bytes = Buffer.from(value);
1231
- if (bytes.byteLength !== 16) {
1232
- throw new TypeError('Canonical relocation aggregate id is invalid.');
1233
- }
1234
- const canonical = Buffer.from([
1235
- bytes[3]!,
1236
- bytes[2]!,
1237
- bytes[1]!,
1238
- bytes[0]!,
1239
- bytes[5]!,
1240
- bytes[4]!,
1241
- bytes[7]!,
1242
- bytes[6]!,
1243
- ...bytes.subarray(8)
1244
- ]).toString('hex');
1245
- return (
1246
- `${canonical.slice(0, 8)}-${canonical.slice(8, 12)}-${canonical.slice(12, 16)}` +
1247
- `-${canonical.slice(16, 20)}-${canonical.slice(20)}`
1248
- );
1249
- }
1250
-
1251
- function text16le(value: string): Buffer {
1252
- const bytes = Buffer.from(value, 'utf8');
1253
- if (bytes.byteLength === 0 || bytes.byteLength > 0xffff) throw new Error('Text16 is invalid.');
1254
- return Buffer.concat([u16le(bytes.byteLength), bytes]);
1255
- }
1256
-
1257
- function bytes32le(value: Uint8Array): Buffer {
1258
- return Buffer.concat([u32le(value.byteLength), Buffer.from(value)]);
1259
- }
1260
-
1261
- function u16le(value: number): Buffer {
1262
- const result = Buffer.alloc(2);
1263
- result.writeUInt16LE(value);
1264
- return result;
1265
- }
1266
-
1267
- function u32le(value: number): Buffer {
1268
- const result = Buffer.alloc(4);
1269
- result.writeUInt32LE(value);
1270
- return result;
1271
- }
1272
-
1273
- function u64le(value: bigint): Buffer {
1274
- const result = Buffer.alloc(8);
1275
- result.writeBigUInt64LE(value);
1276
- return result;
1277
- }
1278
-
1279
- function validateIdentity(
1280
- actorId: string,
1281
- operationId: ZLinkActorJoinOperationId,
1282
- actor: ActorRef
1283
- ): void {
1284
- if (
1285
- actorId.length === 0 ||
1286
- actor.actorId !== actorId ||
1287
- actor.objectGeneration <= 0n ||
1288
- (operationId.high === 0n && operationId.low === 0n)
1289
- ) {
1290
- throw new Error('Deferred Join completion identity is invalid.');
1291
- }
1292
- }
1293
-
1294
- function requireSameOperation(
1295
- root: ZLinkDeferredJoinAcceptedRoot,
1296
- operationId: ZLinkActorJoinOperationId,
1297
- actor: ActorRef
1298
- ): void {
1299
- if (!isSameOperation(root, operationId, actor)) {
1300
- throw new Error(`Actor '${actor.actorId}' already has a different durable Join completion.`);
1301
- }
1302
- }
1303
-
1304
- function isSameOperation(
1305
- root: ZLinkDeferredJoinAcceptedRoot,
1306
- operationId: ZLinkActorJoinOperationId,
1307
- actor: ActorRef
1308
- ): boolean {
1309
- return (
1310
- root.operationId.high === operationId.high &&
1311
- root.operationId.low === operationId.low &&
1312
- root.actor.actorId === actor.actorId &&
1313
- root.actor.objectGeneration === actor.objectGeneration
1314
- );
1315
- }
1316
-
1317
- function requireSameActor(root: ZLinkDeferredJoinAcceptedRoot, actor: ActorRef): void {
1318
- if (
1319
- root.actor.actorId !== actor.actorId ||
1320
- root.actor.objectGeneration !== actor.objectGeneration
1321
- ) {
1322
- throw new Error(
1323
- 'Deferred Join completion generation fence is stale ' +
1324
- `(root ${root.actor.actorId}/${root.actor.objectGeneration}, ` +
1325
- `actor ${actor.actorId}/${actor.objectGeneration}).`
1326
- );
1327
- }
1328
- }
1329
-
1330
- function cursorIndex(cursor: ZLinkDeferredJoinDeliveryCursor): number {
1331
- return cursor === 'prepared' ? 1 : cursor === 'committed' ? 2 : 3;
1332
- }
1333
-
1334
- function isCursor(value: unknown): value is ZLinkDeferredJoinDeliveryCursor {
1335
- return value === 'prepared' || value === 'committed' || value === 'delivered';
1336
- }
1337
-
1338
- function storedSnapshot(
1339
- result: Extract<
1340
- Awaited<ReturnType<ZLinkAuthorityStore['compareExchangeAuthority']>>,
1341
- { readonly kind: 'stored' }
1342
- >
1343
- ): ZLinkAuthoritySnapshot {
1344
- const { kind: _kind, ...snapshot } = result;
1345
- return { kind: 'snapshot', ...snapshot };
1346
- }