@agentplat/mesh 0.3.0-beta.2 → 0.3.0-beta.4

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 (95) hide show
  1. package/README.md +133 -4
  2. package/dist/adaptive-overlay-contracts.d.ts +149 -0
  3. package/dist/adaptive-overlay-contracts.d.ts.map +1 -0
  4. package/dist/adaptive-overlay-contracts.js +3 -0
  5. package/dist/adaptive-overlay-contracts.js.map +1 -0
  6. package/dist/adaptive-overlay-runtime.d.ts +67 -0
  7. package/dist/adaptive-overlay-runtime.d.ts.map +1 -0
  8. package/dist/adaptive-overlay-runtime.js +427 -0
  9. package/dist/adaptive-overlay-runtime.js.map +1 -0
  10. package/dist/adaptive-overlay-validation.d.ts +19 -0
  11. package/dist/adaptive-overlay-validation.d.ts.map +1 -0
  12. package/dist/adaptive-overlay-validation.js +326 -0
  13. package/dist/adaptive-overlay-validation.js.map +1 -0
  14. package/dist/adaptive-overlay.d.ts +4 -0
  15. package/dist/adaptive-overlay.d.ts.map +1 -0
  16. package/dist/adaptive-overlay.js +4 -0
  17. package/dist/adaptive-overlay.js.map +1 -0
  18. package/dist/authenticated-overlay-transport.d.ts +227 -0
  19. package/dist/authenticated-overlay-transport.d.ts.map +1 -0
  20. package/dist/authenticated-overlay-transport.js +702 -0
  21. package/dist/authenticated-overlay-transport.js.map +1 -0
  22. package/dist/authority-continuity-contracts.d.ts +249 -0
  23. package/dist/authority-continuity-contracts.d.ts.map +1 -0
  24. package/dist/authority-continuity-contracts.js +11 -0
  25. package/dist/authority-continuity-contracts.js.map +1 -0
  26. package/dist/authority-continuity.d.ts +43 -0
  27. package/dist/authority-continuity.d.ts.map +1 -0
  28. package/dist/authority-continuity.js +1338 -0
  29. package/dist/authority-continuity.js.map +1 -0
  30. package/dist/continuity.d.ts +3 -0
  31. package/dist/continuity.d.ts.map +1 -0
  32. package/dist/continuity.js +3 -0
  33. package/dist/continuity.js.map +1 -0
  34. package/dist/coordination-allocation-contracts.d.ts +6 -0
  35. package/dist/coordination-allocation-contracts.d.ts.map +1 -1
  36. package/dist/coordination-allocation-state.d.ts.map +1 -1
  37. package/dist/coordination-allocation-state.js +37 -7
  38. package/dist/coordination-allocation-state.js.map +1 -1
  39. package/dist/coordination-allocation.d.ts +1 -1
  40. package/dist/coordination-allocation.d.ts.map +1 -1
  41. package/dist/coordination-allocation.js +92 -11
  42. package/dist/coordination-allocation.js.map +1 -1
  43. package/dist/coordination-discovery-state.d.ts.map +1 -1
  44. package/dist/coordination-discovery-state.js +5 -3
  45. package/dist/coordination-discovery-state.js.map +1 -1
  46. package/dist/coordination-recovery.js +5 -1
  47. package/dist/coordination-recovery.js.map +1 -1
  48. package/dist/durability.d.ts +22 -0
  49. package/dist/durability.d.ts.map +1 -1
  50. package/dist/durability.js +22 -0
  51. package/dist/durability.js.map +1 -1
  52. package/dist/overlay-transport.d.ts +7 -0
  53. package/dist/overlay-transport.d.ts.map +1 -0
  54. package/dist/overlay-transport.js +7 -0
  55. package/dist/overlay-transport.js.map +1 -0
  56. package/dist/overlay.d.ts +9 -0
  57. package/dist/overlay.d.ts.map +1 -0
  58. package/dist/overlay.js +9 -0
  59. package/dist/overlay.js.map +1 -0
  60. package/dist/sparse-overlay-contracts.d.ts +91 -0
  61. package/dist/sparse-overlay-contracts.d.ts.map +1 -0
  62. package/dist/sparse-overlay-contracts.js +2 -0
  63. package/dist/sparse-overlay-contracts.js.map +1 -0
  64. package/dist/sparse-overlay-durability.d.ts +23 -0
  65. package/dist/sparse-overlay-durability.d.ts.map +1 -0
  66. package/dist/sparse-overlay-durability.js +97 -0
  67. package/dist/sparse-overlay-durability.js.map +1 -0
  68. package/dist/sparse-overlay-runtime-contracts.d.ts +158 -0
  69. package/dist/sparse-overlay-runtime-contracts.d.ts.map +1 -0
  70. package/dist/sparse-overlay-runtime-contracts.js +2 -0
  71. package/dist/sparse-overlay-runtime-contracts.js.map +1 -0
  72. package/dist/sparse-overlay-runtime.d.ts +48 -0
  73. package/dist/sparse-overlay-runtime.d.ts.map +1 -0
  74. package/dist/sparse-overlay-runtime.js +763 -0
  75. package/dist/sparse-overlay-runtime.js.map +1 -0
  76. package/dist/sparse-overlay-transport-contracts.d.ts +183 -0
  77. package/dist/sparse-overlay-transport-contracts.d.ts.map +1 -0
  78. package/dist/sparse-overlay-transport-contracts.js +2 -0
  79. package/dist/sparse-overlay-transport-contracts.js.map +1 -0
  80. package/dist/sparse-overlay-transport-durability.d.ts +19 -0
  81. package/dist/sparse-overlay-transport-durability.d.ts.map +1 -0
  82. package/dist/sparse-overlay-transport-durability.js +91 -0
  83. package/dist/sparse-overlay-transport-durability.js.map +1 -0
  84. package/dist/sparse-overlay-transport.d.ts +97 -0
  85. package/dist/sparse-overlay-transport.d.ts.map +1 -0
  86. package/dist/sparse-overlay-transport.js +888 -0
  87. package/dist/sparse-overlay-transport.js.map +1 -0
  88. package/dist/sparse-overlay.d.ts +74 -0
  89. package/dist/sparse-overlay.d.ts.map +1 -0
  90. package/dist/sparse-overlay.js +817 -0
  91. package/dist/sparse-overlay.js.map +1 -0
  92. package/fixtures/beta1/manifest.json +4 -4
  93. package/fixtures/beta1/states/discovery-v1.json +3 -3
  94. package/fixtures/beta1/states/discovery-v2.json +3 -3
  95. package/package.json +20 -4
@@ -0,0 +1,888 @@
1
+ import { sha256Base64Url } from "./sha256.js";
2
+ import { createMeshSparseRelayDeliveryV2, validateMeshSparseDeliveryV2, validateMeshSparseOverlayProfileV2, validateMeshSparsePeerViewV2, validateMeshSparseUpdateV2, } from "./sparse-overlay.js";
3
+ const DEFAULT_LIMITS = Object.freeze({
4
+ maximumOutbox: 4_096,
5
+ maximumInbox: 4_096,
6
+ maximumUpdates: 1_024,
7
+ maximumHistory: 8_192,
8
+ maximumDirectPeers: 64,
9
+ maximumBatchItems: 128,
10
+ maximumCatchUpItems: 256,
11
+ maximumCommitAttempts: 8,
12
+ retryBaseDelay: 1,
13
+ retryMaximumDelay: 1_024,
14
+ });
15
+ /**
16
+ * One portable, CAS-backed next-hop transport. It implements the peer-plane
17
+ * durable queue and update admission ports without owning routing decisions.
18
+ */
19
+ export class MeshSparsePeerTransportRuntimeV1 {
20
+ #options;
21
+ #limits;
22
+ #localPeer;
23
+ constructor(options) {
24
+ if (!options || typeof options !== "object")
25
+ fail("transport options are required");
26
+ identifier(options.overlayId, "overlayId");
27
+ const profile = validateMeshSparseOverlayProfileV2(options.profile);
28
+ integer(options.localPeerIndex, "localPeerIndex", 0, profile.maximumPeers - 1);
29
+ identifier(options.localPeerId, "localPeerId");
30
+ digest(options.membershipDigest, "membershipDigest");
31
+ if (!options.store || typeof options.store.load !== "function" || typeof options.store.compareAndSwap !== "function")
32
+ fail("durable transport store is required");
33
+ if (!options.membership || typeof options.membership.resolve !== "function")
34
+ fail("membership projection port is required");
35
+ if (!options.nextHop || typeof options.nextHop.deliver !== "function" || typeof options.nextHop.catchUp !== "function" || typeof options.nextHop.fetchCausal !== "function")
36
+ fail("next-hop transport is required");
37
+ if (typeof options.currentView !== "function")
38
+ fail("current sparse view port is required");
39
+ this.#options = options;
40
+ this.#limits = limits(options.limits);
41
+ this.#localPeer = Object.freeze({
42
+ peerIndex: options.localPeerIndex,
43
+ peerId: options.localPeerId,
44
+ availability: "active",
45
+ });
46
+ }
47
+ async restore(logicalTime = 0) {
48
+ integer(logicalTime, "logicalTime", 0, Number.MAX_SAFE_INTEGER);
49
+ const existing = await this.#options.store.load(this.#options.overlayId);
50
+ if (existing)
51
+ return validateState(existing, this.#options, this.#limits);
52
+ const initial = buildState({
53
+ overlayId: this.#options.overlayId,
54
+ localPeerIndex: this.#options.localPeerIndex,
55
+ localPeerId: this.#options.localPeerId,
56
+ membershipDigest: this.#options.membershipDigest,
57
+ profileDigest: this.#options.profile.profileDigest,
58
+ revision: 0,
59
+ connectivity: "active",
60
+ nextHistorySequence: 1,
61
+ outbox: [],
62
+ inbox: [],
63
+ updates: [],
64
+ acknowledgedUpdates: [],
65
+ history: [],
66
+ neighborCursors: [],
67
+ lastLogicalTime: logicalTime,
68
+ });
69
+ if (await this.#options.store.compareAndSwap({
70
+ overlayId: initial.overlayId,
71
+ expectedRevision: null,
72
+ expectedStateDigest: null,
73
+ next: initial,
74
+ }))
75
+ return initial;
76
+ const raced = await this.#options.store.load(this.#options.overlayId);
77
+ return raced
78
+ ? validateState(raced, this.#options, this.#limits)
79
+ : fail("transport initialization conflict");
80
+ }
81
+ async pending(identity) {
82
+ identifier(identity, "pending identity");
83
+ const state = await this.restore();
84
+ return state.outbox.filter((record) => record.recipient.peerId === identity).length;
85
+ }
86
+ async enqueue(input) {
87
+ if (input.overlayId !== this.#options.overlayId)
88
+ return rejected("overlay_mismatch");
89
+ const delivery = validateMeshSparseDeliveryV2(this.#options.profile, input.delivery);
90
+ const recipient = membershipPeer(input.recipient, this.#options.profile.maximumPeers);
91
+ if (delivery.senderPeerIndex !== this.#options.localPeerIndex || delivery.recipientPeerIndex !== recipient.peerIndex)
92
+ return rejected("delivery_peer_binding_invalid");
93
+ let outcome = admitted();
94
+ await this.#mutate(undefined, (state) => {
95
+ const existing = state.outbox.find((record) => record.delivery.deliveryDigest === delivery.deliveryDigest);
96
+ if (existing) {
97
+ outcome = existing.recipient.peerId === recipient.peerId
98
+ ? duplicate()
99
+ : rejected("delivery_identity_conflict");
100
+ return state;
101
+ }
102
+ if (state.outbox.length >= this.#limits.maximumOutbox) {
103
+ outcome = backpressured("transport_outbox_capacity");
104
+ return state;
105
+ }
106
+ const queuedAt = Math.max(state.lastLogicalTime, delivery.update.createdAtLogicalTime);
107
+ return nextState(state, {
108
+ outbox: [...state.outbox, Object.freeze({
109
+ recipient,
110
+ delivery,
111
+ attempts: 0,
112
+ queuedAtLogicalTime: queuedAt,
113
+ availableAtLogicalTime: queuedAt,
114
+ lastReasonCode: null,
115
+ })],
116
+ history: appendHistory(state, delivery, queuedAt, this.#limits.maximumHistory),
117
+ nextHistorySequence: hasHistory(state.history, delivery.deliveryDigest)
118
+ ? state.nextHistorySequence
119
+ : state.nextHistorySequence + 1,
120
+ lastLogicalTime: queuedAt,
121
+ });
122
+ });
123
+ return outcome;
124
+ }
125
+ async admit(input) {
126
+ if (input.overlayId !== this.#options.overlayId)
127
+ return rejected("overlay_mismatch");
128
+ const update = validateMeshSparseUpdateV2(this.#options.profile, input.update);
129
+ let outcome = admitted();
130
+ await this.#mutate(undefined, (state) => {
131
+ const known = [
132
+ ...state.updates.map((record) => ({
133
+ updateId: record.update.updateId,
134
+ updateDigest: record.update.updateDigest,
135
+ })),
136
+ ...state.acknowledgedUpdates,
137
+ ].find((candidate) => candidate.updateId === update.updateId);
138
+ if (known) {
139
+ outcome = known.updateDigest === update.updateDigest
140
+ ? duplicate()
141
+ : rejected("update_identity_conflict");
142
+ return state;
143
+ }
144
+ if (state.updates.length >= this.#limits.maximumUpdates) {
145
+ outcome = backpressured("transport_update_capacity");
146
+ return state;
147
+ }
148
+ const conflictingEvidence = state.history
149
+ .map((record) => record.delivery.update)
150
+ .find((candidate) => candidate.updateId === update.updateId && candidate.updateDigest !== update.updateDigest);
151
+ if (conflictingEvidence) {
152
+ outcome = rejected("update_identity_conflict");
153
+ return state;
154
+ }
155
+ return nextState(state, {
156
+ updates: [...state.updates, Object.freeze({
157
+ update,
158
+ admittedAtLogicalTime: state.lastLogicalTime,
159
+ })],
160
+ });
161
+ });
162
+ return outcome;
163
+ }
164
+ /** Durable remote ingress, idempotent by delivery digest and update identity. */
165
+ async receive(input) {
166
+ integer(input.logicalTime, "logicalTime", 0, Number.MAX_SAFE_INTEGER);
167
+ if (input.overlayId !== this.#options.overlayId)
168
+ return rejected("overlay_mismatch");
169
+ if (input.membershipDigest !== this.#options.membershipDigest)
170
+ return rejected("membership_mismatch");
171
+ const sender = membershipPeer(input.sender, this.#options.profile.maximumPeers);
172
+ const delivery = validateMeshSparseDeliveryV2(this.#options.profile, input.delivery);
173
+ if (sender.availability !== "active" || delivery.senderPeerIndex !== sender.peerIndex || delivery.recipientPeerIndex !== this.#options.localPeerIndex)
174
+ return rejected("delivery_peer_binding_invalid");
175
+ let outcome = admitted();
176
+ await this.#mutate(input.logicalTime, (state) => {
177
+ const exact = state.history.find((record) => record.delivery.deliveryDigest === delivery.deliveryDigest);
178
+ if (exact) {
179
+ outcome = exact.delivery.update.updateDigest === delivery.update.updateDigest
180
+ ? duplicate()
181
+ : rejected("delivery_identity_conflict");
182
+ return state;
183
+ }
184
+ const sameUpdate = state.history.find((record) => record.delivery.update.updateId === delivery.update.updateId)?.delivery.update;
185
+ if (sameUpdate && sameUpdate.updateDigest !== delivery.update.updateDigest) {
186
+ outcome = rejected("update_identity_conflict");
187
+ return state;
188
+ }
189
+ if (state.inbox.length >= this.#limits.maximumInbox) {
190
+ outcome = backpressured("transport_inbox_capacity");
191
+ return state;
192
+ }
193
+ return nextState(state, {
194
+ inbox: [...state.inbox, Object.freeze({
195
+ sender,
196
+ delivery,
197
+ attempts: 0,
198
+ queuedAtLogicalTime: input.logicalTime,
199
+ availableAtLogicalTime: input.logicalTime,
200
+ lastReasonCode: null,
201
+ })],
202
+ history: appendHistory(state, delivery, input.logicalTime, this.#limits.maximumHistory),
203
+ nextHistorySequence: state.nextHistorySequence + 1,
204
+ lastLogicalTime: input.logicalTime,
205
+ });
206
+ });
207
+ return outcome;
208
+ }
209
+ async setConnectivity(connectivity, logicalTime) {
210
+ if (connectivity !== "active" && connectivity !== "partitioned" && connectivity !== "rejoining")
211
+ fail("connectivity is invalid");
212
+ return this.#mutate(logicalTime, (state) => state.connectivity === connectivity
213
+ ? state
214
+ : nextState(state, { connectivity, lastLogicalTime: logicalTime }));
215
+ }
216
+ /** Retries durable next-hop work; a partition never discards queued work. */
217
+ async pumpOutbox(input) {
218
+ const maximumItems = batchSize(input.maximumItems, this.#limits);
219
+ let attempted = 0;
220
+ let completed = 0;
221
+ let duplicates = 0;
222
+ let deferred = 0;
223
+ let rejectedCount = 0;
224
+ const blocked = new Set();
225
+ while (attempted < maximumItems) {
226
+ const state = await this.restore(input.logicalTime);
227
+ if (state.connectivity === "partitioned") {
228
+ deferred += state.outbox.length > 0 ? 1 : 0;
229
+ break;
230
+ }
231
+ const record = state.outbox.find((candidate) => candidate.availableAtLogicalTime <= input.logicalTime &&
232
+ !blocked.has(candidate.delivery.deliveryDigest));
233
+ if (!record)
234
+ break;
235
+ attempted += 1;
236
+ if (record.delivery.update.expiresAtLogicalTime <= input.logicalTime) {
237
+ rejectedCount += 1;
238
+ await this.#removeOutbox(record.delivery.deliveryDigest, input.logicalTime);
239
+ continue;
240
+ }
241
+ let outcome;
242
+ try {
243
+ outcome = admission(await this.#options.nextHop.deliver({
244
+ recipient: record.recipient,
245
+ overlayId: state.overlayId,
246
+ membershipDigest: state.membershipDigest,
247
+ sender: this.#localPeer,
248
+ delivery: record.delivery,
249
+ logicalTime: input.logicalTime,
250
+ }));
251
+ }
252
+ catch {
253
+ outcome = backpressured("next_hop_unavailable");
254
+ }
255
+ if (outcome.status === "admitted" || outcome.status === "duplicate") {
256
+ completed += 1;
257
+ if (outcome.status === "duplicate")
258
+ duplicates += 1;
259
+ await this.#removeOutbox(record.delivery.deliveryDigest, input.logicalTime);
260
+ }
261
+ else if (outcome.status === "rejected") {
262
+ rejectedCount += 1;
263
+ await this.#removeOutbox(record.delivery.deliveryDigest, input.logicalTime);
264
+ }
265
+ else {
266
+ deferred += 1;
267
+ blocked.add(record.delivery.deliveryDigest);
268
+ await this.#defer("outbox", record.delivery.deliveryDigest, "reasonCode" in outcome ? outcome.reasonCode : "next_hop_deferred", input.logicalTime);
269
+ }
270
+ }
271
+ const state = await this.restore(input.logicalTime);
272
+ return Object.freeze({ attempted, completed, duplicates, deferred, rejected: rejectedCount, remaining: state.outbox.length });
273
+ }
274
+ /**
275
+ * Delivers durable ingress to the peer plane. A crash after the handler and
276
+ * before acknowledgement safely replays because the peer plane deduplicates.
277
+ */
278
+ async processInbox(input) {
279
+ if (typeof input.handler !== "function")
280
+ fail("inbound handler is required");
281
+ const maximumItems = batchSize(input.maximumItems, this.#limits);
282
+ let attempted = 0;
283
+ let completed = 0;
284
+ let duplicates = 0;
285
+ let deferred = 0;
286
+ let rejectedCount = 0;
287
+ const blocked = new Set();
288
+ while (attempted < maximumItems) {
289
+ const state = await this.restore(input.logicalTime);
290
+ const record = state.inbox.find((candidate) => candidate.availableAtLogicalTime <= input.logicalTime &&
291
+ !blocked.has(candidate.delivery.deliveryDigest));
292
+ if (!record)
293
+ break;
294
+ attempted += 1;
295
+ if (record.delivery.update.expiresAtLogicalTime <= input.logicalTime) {
296
+ rejectedCount += 1;
297
+ await this.#removeInbox(record.delivery.deliveryDigest, input.logicalTime);
298
+ continue;
299
+ }
300
+ const causal = await this.#ensureCausal(record, input.logicalTime);
301
+ if (!causal) {
302
+ deferred += 1;
303
+ blocked.add(record.delivery.deliveryDigest);
304
+ await this.#defer("inbox", record.delivery.deliveryDigest, "causal_predecessor_unavailable", input.logicalTime);
305
+ continue;
306
+ }
307
+ try {
308
+ const outcome = await input.handler({ delivery: record.delivery, logicalTime: input.logicalTime });
309
+ if (outcome.accepted) {
310
+ completed += 1;
311
+ if (outcome.duplicate)
312
+ duplicates += 1;
313
+ await this.#removeInbox(record.delivery.deliveryDigest, input.logicalTime);
314
+ }
315
+ else if (retryablePeerPlaneReason(outcome.reasonCode)) {
316
+ deferred += 1;
317
+ blocked.add(record.delivery.deliveryDigest);
318
+ await this.#defer("inbox", record.delivery.deliveryDigest, outcome.reasonCode ?? "peer_plane_deferred", input.logicalTime);
319
+ }
320
+ else {
321
+ rejectedCount += 1;
322
+ await this.#removeInbox(record.delivery.deliveryDigest, input.logicalTime);
323
+ }
324
+ }
325
+ catch {
326
+ deferred += 1;
327
+ blocked.add(record.delivery.deliveryDigest);
328
+ await this.#defer("inbox", record.delivery.deliveryDigest, "peer_plane_unavailable", input.logicalTime);
329
+ }
330
+ }
331
+ const state = await this.restore(input.logicalTime);
332
+ return Object.freeze({ attempted, completed, duplicates, deferred, rejected: rejectedCount, remaining: state.inbox.length });
333
+ }
334
+ async readUpdates(maximumItems = this.#limits.maximumBatchItems) {
335
+ integer(maximumItems, "maximumItems", 1, this.#limits.maximumBatchItems);
336
+ const state = await this.restore();
337
+ return Object.freeze(state.updates.slice(0, maximumItems));
338
+ }
339
+ async acknowledgeUpdate(updateId, logicalTime) {
340
+ identifier(updateId, "updateId");
341
+ let removed = false;
342
+ await this.#mutate(logicalTime, (state) => {
343
+ const acknowledged = state.updates.find((record) => record.update.updateId === updateId);
344
+ const updates = state.updates.filter((record) => record.update.updateId !== updateId);
345
+ removed = updates.length !== state.updates.length;
346
+ if (!removed || !acknowledged)
347
+ return state;
348
+ const acknowledgedUpdates = [
349
+ ...state.acknowledgedUpdates.filter((record) => record.updateId !== updateId),
350
+ Object.freeze({
351
+ updateId: acknowledged.update.updateId,
352
+ updateDigest: acknowledged.update.updateDigest,
353
+ acknowledgedAtLogicalTime: logicalTime,
354
+ }),
355
+ ].slice(-this.#limits.maximumHistory);
356
+ return nextState(state, { updates, acknowledgedUpdates, lastLogicalTime: logicalTime });
357
+ });
358
+ return removed;
359
+ }
360
+ /** Pulls bounded deltas from current local-view neighbors after a partition. */
361
+ async rejoin(input) {
362
+ const view = validateMeshSparsePeerViewV2(this.#options.profile, await this.#options.currentView());
363
+ const allowed = new Set([...view.activeNeighborIndexes, ...view.reserveNeighborIndexes]);
364
+ const peerIndexes = uniqueIndexes(input.neighborPeerIndexes, this.#options.profile.maximumPeers);
365
+ if (peerIndexes.length > this.#limits.maximumDirectPeers || peerIndexes.some((index) => !allowed.has(index)))
366
+ fail("rejoin peers exceed the current local sparse view");
367
+ const maximumItems = input.maximumItemsPerNeighbor ?? this.#limits.maximumCatchUpItems;
368
+ integer(maximumItems, "maximumItemsPerNeighbor", 1, this.#limits.maximumCatchUpItems);
369
+ await this.setConnectivity("rejoining", input.logicalTime);
370
+ const slice = await this.#options.membership.resolve({
371
+ overlayId: this.#options.overlayId,
372
+ membershipDigest: this.#options.membershipDigest,
373
+ peerIndexes,
374
+ logicalTime: input.logicalTime,
375
+ });
376
+ validateMembershipSlice(slice, this.#options.overlayId, this.#options.membershipDigest, peerIndexes);
377
+ let contacted = 0;
378
+ let admittedCount = 0;
379
+ let duplicateCount = 0;
380
+ let deferred = 0;
381
+ const gaps = [];
382
+ for (const peer of slice.peers) {
383
+ if (peer.availability !== "active") {
384
+ deferred += 1;
385
+ continue;
386
+ }
387
+ const state = await this.restore(input.logicalTime);
388
+ const afterSequence = state.neighborCursors.find((cursor) => cursor.peerIndex === peer.peerIndex)?.acknowledgedSequence ?? 0;
389
+ let response;
390
+ try {
391
+ response = await this.#options.nextHop.catchUp({
392
+ recipient: peer,
393
+ overlayId: state.overlayId,
394
+ membershipDigest: state.membershipDigest,
395
+ requester: this.#localPeer,
396
+ afterSequence,
397
+ maximumItems,
398
+ logicalTime: input.logicalTime,
399
+ });
400
+ }
401
+ catch {
402
+ deferred += 1;
403
+ continue;
404
+ }
405
+ contacted += 1;
406
+ validateCatchUpResponse(response, state, peer, afterSequence, maximumItems);
407
+ if (response.truncated) {
408
+ gaps.push(peer.peerId);
409
+ deferred += 1;
410
+ continue;
411
+ }
412
+ let acceptedAll = true;
413
+ for (const record of response.records) {
414
+ const outcome = await this.receive({
415
+ overlayId: state.overlayId,
416
+ membershipDigest: state.membershipDigest,
417
+ sender: peer,
418
+ delivery: record.delivery,
419
+ logicalTime: input.logicalTime,
420
+ });
421
+ if (outcome.status === "admitted")
422
+ admittedCount += 1;
423
+ else if (outcome.status === "duplicate")
424
+ duplicateCount += 1;
425
+ else {
426
+ deferred += 1;
427
+ acceptedAll = false;
428
+ break;
429
+ }
430
+ }
431
+ if (acceptedAll)
432
+ await this.#advanceCursor(peer, response.latestSequence, input.logicalTime);
433
+ }
434
+ const connectivity = gaps.length === 0 ? "active" : "rejoining";
435
+ const state = await this.setConnectivity(connectivity, input.logicalTime);
436
+ return Object.freeze({ contacted, admitted: admittedCount, duplicates: duplicateCount, deferred, gaps: Object.freeze(gaps), state });
437
+ }
438
+ /** Serves only retained local evidence to a peer in the current sparse view. */
439
+ async catchUp(input) {
440
+ this.#assertRemoteRequest(input.overlayId, input.membershipDigest, input.requester, input.logicalTime);
441
+ integer(input.afterSequence, "afterSequence", 0, Number.MAX_SAFE_INTEGER);
442
+ integer(input.maximumItems, "maximumItems", 1, this.#limits.maximumCatchUpItems);
443
+ const view = await this.#assertCurrentNeighbor(input.requester);
444
+ const state = await this.restore(input.logicalTime);
445
+ const earliestAvailableSequence = state.history[0]?.localSequence ?? state.nextHistorySequence;
446
+ const headSequence = state.nextHistorySequence - 1;
447
+ const truncated = input.afterSequence + 1 < earliestAvailableSequence && input.afterSequence < headSequence;
448
+ const selected = [];
449
+ const updates = new Set();
450
+ let latestSequence = input.afterSequence;
451
+ if (!truncated) {
452
+ for (const record of state.history) {
453
+ if (record.localSequence <= input.afterSequence)
454
+ continue;
455
+ latestSequence = record.localSequence;
456
+ if (selected.length < input.maximumItems &&
457
+ record.delivery.update.expiresAtLogicalTime > input.logicalTime &&
458
+ record.delivery.hop < record.delivery.update.maximumHops &&
459
+ !updates.has(record.delivery.update.updateId)) {
460
+ updates.add(record.delivery.update.updateId);
461
+ selected.push(Object.freeze({
462
+ providerSequence: record.localSequence,
463
+ delivery: createMeshSparseRelayDeliveryV2({
464
+ schemaVersion: 2,
465
+ profile: this.#options.profile,
466
+ senderView: view,
467
+ recipientPeerIndex: input.requester.peerIndex,
468
+ predecessor: record.delivery,
469
+ }),
470
+ }));
471
+ }
472
+ if (selected.length >= input.maximumItems)
473
+ break;
474
+ }
475
+ if (selected.length === 0)
476
+ latestSequence = headSequence;
477
+ }
478
+ return Object.freeze({
479
+ overlayId: state.overlayId,
480
+ membershipDigest: state.membershipDigest,
481
+ provider: Object.freeze({ peerIndex: state.localPeerIndex, peerId: state.localPeerId }),
482
+ earliestAvailableSequence,
483
+ latestSequence,
484
+ truncated,
485
+ records: Object.freeze(selected),
486
+ });
487
+ }
488
+ async fetchCausal(input) {
489
+ this.#assertRemoteRequest(input.overlayId, input.membershipDigest, input.requester, input.logicalTime);
490
+ digest(input.deliveryDigest, "deliveryDigest");
491
+ await this.#assertCurrentNeighbor(input.requester);
492
+ return (await this.restore(input.logicalTime)).history
493
+ .find((record) => record.delivery.deliveryDigest === input.deliveryDigest)?.delivery;
494
+ }
495
+ async #ensureCausal(record, logicalTime) {
496
+ const predecessor = record.delivery.previousDeliveryDigest;
497
+ if (predecessor === null)
498
+ return true;
499
+ let state = await this.restore(logicalTime);
500
+ if (hasHistory(state.history, predecessor))
501
+ return true;
502
+ let evidence;
503
+ try {
504
+ evidence = await this.#options.nextHop.fetchCausal({
505
+ recipient: record.sender,
506
+ overlayId: state.overlayId,
507
+ membershipDigest: state.membershipDigest,
508
+ requester: this.#localPeer,
509
+ deliveryDigest: predecessor,
510
+ logicalTime,
511
+ });
512
+ }
513
+ catch {
514
+ return false;
515
+ }
516
+ if (!evidence)
517
+ return false;
518
+ const validated = validateMeshSparseDeliveryV2(this.#options.profile, evidence);
519
+ if (validated.deliveryDigest !== predecessor || validated.update.updateDigest !== record.delivery.update.updateDigest)
520
+ return false;
521
+ await this.#mutate(logicalTime, (current) => {
522
+ if (hasHistory(current.history, predecessor))
523
+ return current;
524
+ return nextState(current, {
525
+ history: appendHistory(current, validated, logicalTime, this.#limits.maximumHistory),
526
+ nextHistorySequence: current.nextHistorySequence + 1,
527
+ lastLogicalTime: logicalTime,
528
+ });
529
+ });
530
+ state = await this.restore(logicalTime);
531
+ return hasHistory(state.history, predecessor);
532
+ }
533
+ async #assertCurrentNeighbor(peer) {
534
+ const view = validateMeshSparsePeerViewV2(this.#options.profile, await this.#options.currentView());
535
+ if (![...view.activeNeighborIndexes, ...view.reserveNeighborIndexes].includes(peer.peerIndex))
536
+ fail("remote peer is outside the current local sparse view");
537
+ return view;
538
+ }
539
+ #assertRemoteRequest(overlayId, membershipDigest, requester, logicalTime) {
540
+ if (overlayId !== this.#options.overlayId || membershipDigest !== this.#options.membershipDigest)
541
+ fail("remote request binding is invalid");
542
+ membershipPeer(requester, this.#options.profile.maximumPeers);
543
+ integer(logicalTime, "logicalTime", 0, Number.MAX_SAFE_INTEGER);
544
+ }
545
+ async #advanceCursor(peer, sequence, logicalTime) {
546
+ await this.#mutate(logicalTime, (state) => {
547
+ const current = state.neighborCursors.find((cursor) => cursor.peerIndex === peer.peerIndex);
548
+ if (current && current.acknowledgedSequence >= sequence)
549
+ return state;
550
+ const neighborCursors = [
551
+ ...state.neighborCursors.filter((cursor) => cursor.peerIndex !== peer.peerIndex),
552
+ Object.freeze({ peerIndex: peer.peerIndex, peerId: peer.peerId, acknowledgedSequence: sequence }),
553
+ ].sort((left, right) => left.peerIndex - right.peerIndex);
554
+ if (neighborCursors.length > this.#limits.maximumDirectPeers)
555
+ fail("neighbor cursor capacity exceeded");
556
+ return nextState(state, { neighborCursors, lastLogicalTime: logicalTime });
557
+ });
558
+ }
559
+ async #removeOutbox(deliveryDigest, logicalTime) {
560
+ await this.#mutate(logicalTime, (state) => {
561
+ const outbox = state.outbox.filter((record) => record.delivery.deliveryDigest !== deliveryDigest);
562
+ return outbox.length === state.outbox.length ? state : nextState(state, { outbox, lastLogicalTime: logicalTime });
563
+ });
564
+ }
565
+ async #removeInbox(deliveryDigest, logicalTime) {
566
+ await this.#mutate(logicalTime, (state) => {
567
+ const inbox = state.inbox.filter((record) => record.delivery.deliveryDigest !== deliveryDigest);
568
+ return inbox.length === state.inbox.length ? state : nextState(state, { inbox, lastLogicalTime: logicalTime });
569
+ });
570
+ }
571
+ async #defer(kind, deliveryDigest, reasonCode, logicalTime) {
572
+ await this.#mutate(logicalTime, (state) => {
573
+ if (kind === "outbox") {
574
+ const record = state.outbox.find((candidate) => candidate.delivery.deliveryDigest === deliveryDigest);
575
+ if (!record)
576
+ return state;
577
+ const attempts = record.attempts + 1;
578
+ const replacement = Object.freeze({
579
+ ...record,
580
+ attempts,
581
+ availableAtLogicalTime: safeAdd(logicalTime, retryDelay(attempts, this.#limits)),
582
+ lastReasonCode: reasonCode,
583
+ });
584
+ return nextState(state, {
585
+ outbox: state.outbox.map((candidate) => candidate === record ? replacement : candidate),
586
+ lastLogicalTime: logicalTime,
587
+ });
588
+ }
589
+ const record = state.inbox.find((candidate) => candidate.delivery.deliveryDigest === deliveryDigest);
590
+ if (!record)
591
+ return state;
592
+ const attempts = record.attempts + 1;
593
+ const replacement = Object.freeze({
594
+ ...record,
595
+ attempts,
596
+ availableAtLogicalTime: safeAdd(logicalTime, retryDelay(attempts, this.#limits)),
597
+ lastReasonCode: reasonCode,
598
+ });
599
+ return nextState(state, {
600
+ inbox: state.inbox.map((candidate) => candidate === record ? replacement : candidate),
601
+ lastLogicalTime: logicalTime,
602
+ });
603
+ });
604
+ }
605
+ async #mutate(logicalTime, operation) {
606
+ if (logicalTime !== undefined)
607
+ integer(logicalTime, "logicalTime", 0, Number.MAX_SAFE_INTEGER);
608
+ for (let attempt = 0; attempt < this.#limits.maximumCommitAttempts; attempt += 1) {
609
+ const current = await this.restore(logicalTime ?? 0);
610
+ if (logicalTime !== undefined && logicalTime < current.lastLogicalTime)
611
+ fail("transport logical time regressed");
612
+ const next = operation(current);
613
+ if (next === current)
614
+ return current;
615
+ if (await this.#options.store.compareAndSwap({
616
+ overlayId: current.overlayId,
617
+ expectedRevision: current.revision,
618
+ expectedStateDigest: current.stateDigest,
619
+ next,
620
+ }))
621
+ return next;
622
+ }
623
+ return fail("transport compare-and-swap attempts exhausted");
624
+ }
625
+ }
626
+ /** Reference CAS store. Export/import the immutable state to preserve it across host restarts. */
627
+ export class InMemoryMeshSparseTransportStoreV1 {
628
+ #states = new Map();
629
+ constructor(states = []) {
630
+ for (const state of states)
631
+ this.#states.set(state.overlayId, state);
632
+ }
633
+ async load(overlayId) { return this.#states.get(overlayId); }
634
+ async compareAndSwap(input) {
635
+ const current = this.#states.get(input.overlayId);
636
+ if ((current?.revision ?? null) !== input.expectedRevision || (current?.stateDigest ?? null) !== input.expectedStateDigest)
637
+ return false;
638
+ this.#states.set(input.overlayId, input.next);
639
+ return true;
640
+ }
641
+ snapshot() {
642
+ return Object.freeze([...this.#states.values()]);
643
+ }
644
+ }
645
+ /**
646
+ * Explicit neighbor-only direct exchange. It has no process-global registry;
647
+ * callers wire at most the local active/reserve endpoints.
648
+ */
649
+ export class MeshSparseDirectNextHopTransportV1 {
650
+ #maximumDirectPeers;
651
+ #endpoints = new Map();
652
+ constructor(maximumDirectPeers = DEFAULT_LIMITS.maximumDirectPeers) {
653
+ integer(maximumDirectPeers, "maximumDirectPeers", 1, 1_024);
654
+ this.#maximumDirectPeers = maximumDirectPeers;
655
+ }
656
+ connect(peer, endpoint) {
657
+ integer(peer.peerIndex, "peerIndex", 0, Number.MAX_SAFE_INTEGER);
658
+ identifier(peer.peerId, "peerId");
659
+ if (!endpoint || typeof endpoint.receive !== "function" || typeof endpoint.catchUp !== "function" || typeof endpoint.fetchCausal !== "function")
660
+ fail("direct peer endpoint is invalid");
661
+ if (!this.#endpoints.has(peer.peerId) && this.#endpoints.size >= this.#maximumDirectPeers)
662
+ fail("direct peer capacity exceeded");
663
+ this.#endpoints.set(peer.peerId, { peerIndex: peer.peerIndex, endpoint, available: true });
664
+ }
665
+ disconnect(peerId) { this.#endpoints.delete(peerId); }
666
+ setAvailable(peerId, available) {
667
+ const current = this.#endpoints.get(peerId);
668
+ if (!current)
669
+ fail("direct peer is not connected");
670
+ current.available = available;
671
+ }
672
+ async deliver(input) {
673
+ const endpoint = this.#resolve(input.recipient);
674
+ if (!endpoint)
675
+ return backpressured("next_hop_unavailable");
676
+ return endpoint.receive(input);
677
+ }
678
+ async catchUp(input) {
679
+ const endpoint = this.#resolve(input.recipient);
680
+ if (!endpoint)
681
+ throw new Error("next_hop_unavailable");
682
+ return endpoint.catchUp(input);
683
+ }
684
+ async fetchCausal(input) {
685
+ const endpoint = this.#resolve(input.recipient);
686
+ if (!endpoint)
687
+ return undefined;
688
+ return endpoint.fetchCausal(input);
689
+ }
690
+ #resolve(peer) {
691
+ const record = this.#endpoints.get(peer.peerId);
692
+ return record?.available && record.peerIndex === peer.peerIndex ? record.endpoint : undefined;
693
+ }
694
+ }
695
+ function appendHistory(state, delivery, observedAtLogicalTime, maximum) {
696
+ if (hasHistory(state.history, delivery.deliveryDigest))
697
+ return state.history;
698
+ const record = Object.freeze({ localSequence: state.nextHistorySequence, delivery, observedAtLogicalTime });
699
+ return Object.freeze([...state.history, record].slice(-maximum));
700
+ }
701
+ function hasHistory(history, deliveryDigest) {
702
+ return history.some((record) => record.delivery.deliveryDigest === deliveryDigest);
703
+ }
704
+ function buildState(input) {
705
+ const body = { schemaVersion: 1, ...input };
706
+ return deepFreeze({ ...body, stateDigest: contentDigest(body) });
707
+ }
708
+ function nextState(state, changes) {
709
+ return buildState({
710
+ overlayId: state.overlayId,
711
+ localPeerIndex: state.localPeerIndex,
712
+ localPeerId: state.localPeerId,
713
+ membershipDigest: state.membershipDigest,
714
+ profileDigest: state.profileDigest,
715
+ revision: state.revision + 1,
716
+ connectivity: changes.connectivity ?? state.connectivity,
717
+ nextHistorySequence: changes.nextHistorySequence ?? state.nextHistorySequence,
718
+ outbox: changes.outbox ?? state.outbox,
719
+ inbox: changes.inbox ?? state.inbox,
720
+ updates: changes.updates ?? state.updates,
721
+ acknowledgedUpdates: changes.acknowledgedUpdates ?? state.acknowledgedUpdates,
722
+ history: changes.history ?? state.history,
723
+ neighborCursors: changes.neighborCursors ?? state.neighborCursors,
724
+ lastLogicalTime: changes.lastLogicalTime ?? state.lastLogicalTime,
725
+ });
726
+ }
727
+ function validateState(state, options, configuredLimits) {
728
+ if (!state || state.schemaVersion !== 1 || state.overlayId !== options.overlayId || state.localPeerIndex !== options.localPeerIndex || state.localPeerId !== options.localPeerId || state.membershipDigest !== options.membershipDigest || state.profileDigest !== options.profile.profileDigest)
729
+ fail("transport state binding is invalid");
730
+ integer(state.revision, "state.revision", 0, Number.MAX_SAFE_INTEGER);
731
+ integer(state.nextHistorySequence, "state.nextHistorySequence", 1, Number.MAX_SAFE_INTEGER);
732
+ integer(state.lastLogicalTime, "state.lastLogicalTime", 0, Number.MAX_SAFE_INTEGER);
733
+ if (state.connectivity !== "active" && state.connectivity !== "partitioned" && state.connectivity !== "rejoining")
734
+ fail("transport connectivity is invalid");
735
+ if (!Array.isArray(state.outbox) || state.outbox.length > configuredLimits.maximumOutbox || !Array.isArray(state.inbox) || state.inbox.length > configuredLimits.maximumInbox || !Array.isArray(state.updates) || state.updates.length > configuredLimits.maximumUpdates || !Array.isArray(state.acknowledgedUpdates) || state.acknowledgedUpdates.length > configuredLimits.maximumHistory || !Array.isArray(state.history) || state.history.length > configuredLimits.maximumHistory || !Array.isArray(state.neighborCursors) || state.neighborCursors.length > configuredLimits.maximumDirectPeers)
736
+ fail("transport state capacity is invalid");
737
+ const deliveryIds = new Set();
738
+ for (const record of [...state.outbox, ...state.inbox]) {
739
+ const delivery = validateMeshSparseDeliveryV2(options.profile, record.delivery);
740
+ integer(record.attempts, "record.attempts", 0, Number.MAX_SAFE_INTEGER);
741
+ integer(record.queuedAtLogicalTime, "record.queuedAtLogicalTime", 0, Number.MAX_SAFE_INTEGER);
742
+ integer(record.availableAtLogicalTime, "record.availableAtLogicalTime", record.queuedAtLogicalTime, Number.MAX_SAFE_INTEGER);
743
+ if (deliveryIds.has(delivery.deliveryDigest))
744
+ fail("transport pending delivery is duplicated");
745
+ deliveryIds.add(delivery.deliveryDigest);
746
+ }
747
+ let previousSequence = 0;
748
+ const historyIds = new Set();
749
+ for (const record of state.history) {
750
+ integer(record.localSequence, "history.localSequence", previousSequence + 1, Number.MAX_SAFE_INTEGER);
751
+ previousSequence = record.localSequence;
752
+ validateMeshSparseDeliveryV2(options.profile, record.delivery);
753
+ if (historyIds.has(record.delivery.deliveryDigest))
754
+ fail("transport history is duplicated");
755
+ historyIds.add(record.delivery.deliveryDigest);
756
+ }
757
+ if (previousSequence >= state.nextHistorySequence)
758
+ fail("transport history sequence is invalid");
759
+ for (const record of state.updates)
760
+ validateMeshSparseUpdateV2(options.profile, record.update);
761
+ for (const record of state.acknowledgedUpdates) {
762
+ identifier(record.updateId, "acknowledged updateId");
763
+ digest(record.updateDigest, "acknowledged updateDigest");
764
+ integer(record.acknowledgedAtLogicalTime, "acknowledgedAtLogicalTime", 0, Number.MAX_SAFE_INTEGER);
765
+ }
766
+ const rebuilt = buildState({
767
+ overlayId: state.overlayId,
768
+ localPeerIndex: state.localPeerIndex,
769
+ localPeerId: state.localPeerId,
770
+ membershipDigest: state.membershipDigest,
771
+ profileDigest: state.profileDigest,
772
+ revision: state.revision,
773
+ connectivity: state.connectivity,
774
+ nextHistorySequence: state.nextHistorySequence,
775
+ outbox: state.outbox,
776
+ inbox: state.inbox,
777
+ updates: state.updates,
778
+ acknowledgedUpdates: state.acknowledgedUpdates,
779
+ history: state.history,
780
+ neighborCursors: state.neighborCursors,
781
+ lastLogicalTime: state.lastLogicalTime,
782
+ });
783
+ if (rebuilt.stateDigest !== state.stateDigest)
784
+ fail("transport state digest is invalid");
785
+ return state;
786
+ }
787
+ function validateMembershipSlice(slice, overlayId, membershipDigest, requested) {
788
+ if (!slice || slice.schemaVersion !== 1 || slice.overlayId !== overlayId || slice.membershipDigest !== membershipDigest || slice.peers.length > requested.length)
789
+ fail("membership slice binding is invalid");
790
+ const allowed = new Set(requested);
791
+ if (slice.peers.some((peer) => !allowed.has(peer.peerIndex)))
792
+ fail("membership slice returned an unrequested peer");
793
+ }
794
+ function validateCatchUpResponse(response, state, peer, afterSequence, maximumItems) {
795
+ if (!response || response.overlayId !== state.overlayId || response.membershipDigest !== state.membershipDigest || response.provider.peerIndex !== peer.peerIndex || response.provider.peerId !== peer.peerId || response.records.length > maximumItems)
796
+ fail("catch-up response binding is invalid");
797
+ integer(response.earliestAvailableSequence, "earliestAvailableSequence", 1, Number.MAX_SAFE_INTEGER);
798
+ integer(response.latestSequence, "latestSequence", afterSequence, Number.MAX_SAFE_INTEGER);
799
+ let sequence = afterSequence;
800
+ for (const record of response.records) {
801
+ integer(record.providerSequence, "providerSequence", sequence + 1, response.latestSequence);
802
+ sequence = record.providerSequence;
803
+ }
804
+ }
805
+ function membershipPeer(peer, maximumPeers) {
806
+ if (!peer || typeof peer !== "object")
807
+ fail("membership peer is required");
808
+ integer(peer.peerIndex, "peerIndex", 0, maximumPeers - 1);
809
+ identifier(peer.peerId, "peerId");
810
+ if (peer.availability !== "active" && peer.availability !== "suspect" && peer.availability !== "departed")
811
+ fail("peer availability is invalid");
812
+ return Object.freeze({ peerIndex: peer.peerIndex, peerId: peer.peerId, availability: peer.availability });
813
+ }
814
+ function retryablePeerPlaneReason(reasonCode) {
815
+ return reasonCode === "peer_plane_backpressure" || reasonCode === "recent_update_capacity_exceeded";
816
+ }
817
+ function retryDelay(attempts, configuredLimits) {
818
+ return Math.min(configuredLimits.retryMaximumDelay, configuredLimits.retryBaseDelay * 2 ** Math.min(attempts - 1, 30));
819
+ }
820
+ function safeAdd(left, right) {
821
+ if (left > Number.MAX_SAFE_INTEGER - right)
822
+ return Number.MAX_SAFE_INTEGER;
823
+ return left + right;
824
+ }
825
+ function batchSize(value, configuredLimits) {
826
+ const result = value ?? configuredLimits.maximumBatchItems;
827
+ integer(result, "maximumItems", 1, configuredLimits.maximumBatchItems);
828
+ return result;
829
+ }
830
+ function uniqueIndexes(values, maximumPeers) {
831
+ if (!Array.isArray(values))
832
+ fail("neighborPeerIndexes are required");
833
+ const result = [...new Set(values)];
834
+ result.forEach((value) => integer(value, "neighborPeerIndex", 0, maximumPeers - 1));
835
+ return Object.freeze(result.sort((left, right) => left - right));
836
+ }
837
+ function limits(input) {
838
+ const result = { ...DEFAULT_LIMITS, ...input };
839
+ for (const [key, value] of Object.entries(result))
840
+ integer(value, key, 1, 1_000_000);
841
+ if (result.retryBaseDelay > result.retryMaximumDelay)
842
+ fail("retry delay bounds are invalid");
843
+ return Object.freeze(result);
844
+ }
845
+ function admission(input) {
846
+ if (!input || (input.status !== "admitted" && input.status !== "duplicate" && input.status !== "backpressured" && input.status !== "rejected"))
847
+ fail("transport admission result is invalid");
848
+ return input;
849
+ }
850
+ function admitted() { return Object.freeze({ status: "admitted" }); }
851
+ function duplicate() { return Object.freeze({ status: "duplicate" }); }
852
+ function backpressured(reasonCode) { return Object.freeze({ status: "backpressured", reasonCode }); }
853
+ function rejected(reasonCode) { return Object.freeze({ status: "rejected", reasonCode }); }
854
+ function contentDigest(value) {
855
+ return `sha256:${sha256Base64Url(new TextEncoder().encode(canonical(value)))}`;
856
+ }
857
+ function canonical(value) {
858
+ if (value === undefined)
859
+ fail("undefined is not canonical JSON");
860
+ if (value === null || typeof value !== "object")
861
+ return JSON.stringify(value);
862
+ if (Array.isArray(value))
863
+ return `[${value.map(canonical).join(",")}]`;
864
+ const record = value;
865
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonical(record[key])}`).join(",")}}`;
866
+ }
867
+ function deepFreeze(value) {
868
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
869
+ Object.freeze(value);
870
+ for (const child of Object.values(value))
871
+ deepFreeze(child);
872
+ }
873
+ return value;
874
+ }
875
+ function identifier(value, label) {
876
+ if (typeof value !== "string" || value.length > 256 || !/^[A-Za-z0-9][A-Za-z0-9._:@/-]*$/u.test(value))
877
+ fail(`${label} is invalid`);
878
+ }
879
+ function digest(value, label) {
880
+ if (typeof value !== "string" || !/^sha256:[A-Za-z0-9_-]{43}$/u.test(value))
881
+ fail(`${label} is invalid`);
882
+ }
883
+ function integer(value, label, minimum, maximum) {
884
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum)
885
+ fail(`${label} is invalid`);
886
+ }
887
+ function fail(message) { throw new TypeError(message); }
888
+ //# sourceMappingURL=sparse-overlay-transport.js.map