@agentplat/mesh 0.3.0-alpha.1 → 0.3.0-alpha.2

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 (102) hide show
  1. package/README.md +247 -2
  2. package/dist/coordination-allocation-contracts.d.ts +598 -0
  3. package/dist/coordination-allocation-contracts.d.ts.map +1 -0
  4. package/dist/coordination-allocation-contracts.js +2 -0
  5. package/dist/coordination-allocation-contracts.js.map +1 -0
  6. package/dist/coordination-allocation-state.d.ts +19 -0
  7. package/dist/coordination-allocation-state.d.ts.map +1 -0
  8. package/dist/coordination-allocation-state.js +5496 -0
  9. package/dist/coordination-allocation-state.js.map +1 -0
  10. package/dist/coordination-allocation.d.ts +10 -0
  11. package/dist/coordination-allocation.d.ts.map +1 -0
  12. package/dist/coordination-allocation.js +2743 -0
  13. package/dist/coordination-allocation.js.map +1 -0
  14. package/dist/coordination-contracts.d.ts +78 -0
  15. package/dist/coordination-contracts.d.ts.map +1 -0
  16. package/dist/coordination-contracts.js +2 -0
  17. package/dist/coordination-contracts.js.map +1 -0
  18. package/dist/coordination-discovery-contracts.d.ts +150 -0
  19. package/dist/coordination-discovery-contracts.d.ts.map +1 -0
  20. package/dist/coordination-discovery-contracts.js +2 -0
  21. package/dist/coordination-discovery-contracts.js.map +1 -0
  22. package/dist/coordination-discovery-state.d.ts +12 -0
  23. package/dist/coordination-discovery-state.d.ts.map +1 -0
  24. package/dist/coordination-discovery-state.js +542 -0
  25. package/dist/coordination-discovery-state.js.map +1 -0
  26. package/dist/coordination-discovery.d.ts +19 -0
  27. package/dist/coordination-discovery.d.ts.map +1 -0
  28. package/dist/coordination-discovery.js +850 -0
  29. package/dist/coordination-discovery.js.map +1 -0
  30. package/dist/coordination-execution.d.ts +6 -0
  31. package/dist/coordination-execution.d.ts.map +1 -0
  32. package/dist/coordination-execution.js +854 -0
  33. package/dist/coordination-execution.js.map +1 -0
  34. package/dist/coordination-inbound-contracts.d.ts +150 -0
  35. package/dist/coordination-inbound-contracts.d.ts.map +1 -0
  36. package/dist/coordination-inbound-contracts.js +2 -0
  37. package/dist/coordination-inbound-contracts.js.map +1 -0
  38. package/dist/coordination-inbound-state.d.ts +26 -0
  39. package/dist/coordination-inbound-state.d.ts.map +1 -0
  40. package/dist/coordination-inbound-state.js +331 -0
  41. package/dist/coordination-inbound-state.js.map +1 -0
  42. package/dist/coordination-inbound.d.ts +16 -0
  43. package/dist/coordination-inbound.d.ts.map +1 -0
  44. package/dist/coordination-inbound.js +944 -0
  45. package/dist/coordination-inbound.js.map +1 -0
  46. package/dist/coordination-lease-renewal.d.ts +8 -0
  47. package/dist/coordination-lease-renewal.d.ts.map +1 -0
  48. package/dist/coordination-lease-renewal.js +518 -0
  49. package/dist/coordination-lease-renewal.js.map +1 -0
  50. package/dist/coordination-objective-topic-contracts.d.ts +70 -0
  51. package/dist/coordination-objective-topic-contracts.d.ts.map +1 -0
  52. package/dist/coordination-objective-topic-contracts.js +2 -0
  53. package/dist/coordination-objective-topic-contracts.js.map +1 -0
  54. package/dist/coordination-objective-topic.d.ts +6 -0
  55. package/dist/coordination-objective-topic.d.ts.map +1 -0
  56. package/dist/coordination-objective-topic.js +561 -0
  57. package/dist/coordination-objective-topic.js.map +1 -0
  58. package/dist/coordination-objective-work-contracts.d.ts +212 -0
  59. package/dist/coordination-objective-work-contracts.d.ts.map +1 -0
  60. package/dist/coordination-objective-work-contracts.js +2 -0
  61. package/dist/coordination-objective-work-contracts.js.map +1 -0
  62. package/dist/coordination-objective-work-state.d.ts +10 -0
  63. package/dist/coordination-objective-work-state.d.ts.map +1 -0
  64. package/dist/coordination-objective-work-state.js +1075 -0
  65. package/dist/coordination-objective-work-state.js.map +1 -0
  66. package/dist/coordination-objective-work-time.d.ts +9 -0
  67. package/dist/coordination-objective-work-time.d.ts.map +1 -0
  68. package/dist/coordination-objective-work-time.js +48 -0
  69. package/dist/coordination-objective-work-time.js.map +1 -0
  70. package/dist/coordination-objective-work.d.ts +11 -0
  71. package/dist/coordination-objective-work.d.ts.map +1 -0
  72. package/dist/coordination-objective-work.js +1090 -0
  73. package/dist/coordination-objective-work.js.map +1 -0
  74. package/dist/coordination-recovery.d.ts +13 -0
  75. package/dist/coordination-recovery.d.ts.map +1 -0
  76. package/dist/coordination-recovery.js +951 -0
  77. package/dist/coordination-recovery.js.map +1 -0
  78. package/dist/coordination-reducer.d.ts +8 -0
  79. package/dist/coordination-reducer.d.ts.map +1 -0
  80. package/dist/coordination-reducer.js +81 -0
  81. package/dist/coordination-reducer.js.map +1 -0
  82. package/dist/coordination-state.d.ts +13 -0
  83. package/dist/coordination-state.d.ts.map +1 -0
  84. package/dist/coordination-state.js +335 -0
  85. package/dist/coordination-state.js.map +1 -0
  86. package/dist/coordination-topic-contracts.d.ts +82 -0
  87. package/dist/coordination-topic-contracts.d.ts.map +1 -0
  88. package/dist/coordination-topic-contracts.js +2 -0
  89. package/dist/coordination-topic-contracts.js.map +1 -0
  90. package/dist/coordination-topic.d.ts +6 -0
  91. package/dist/coordination-topic.d.ts.map +1 -0
  92. package/dist/coordination-topic.js +561 -0
  93. package/dist/coordination-topic.js.map +1 -0
  94. package/dist/coordination.d.ts +21 -0
  95. package/dist/coordination.d.ts.map +1 -0
  96. package/dist/coordination.js +21 -0
  97. package/dist/coordination.js.map +1 -0
  98. package/dist/sha256.d.ts +3 -0
  99. package/dist/sha256.d.ts.map +1 -0
  100. package/dist/sha256.js +117 -0
  101. package/dist/sha256.js.map +1 -0
  102. package/package.json +7 -3
@@ -0,0 +1,951 @@
1
+ import { canonicalizeMeshPayload, compareMeshTimestamps, validateMeshEnvelopeContext, validateSignedMeshEnvelope, } from "@agentplat/mesh-protocol";
2
+ import { createMeshAllocationRuntimeState, meshAllocationRetainsMessageId, meshAssignmentFenceKey, restoreMeshAllocationState, } from "./coordination-allocation-state.js";
3
+ import { logicalDeadline } from "./coordination-objective-work-time.js";
4
+ import { sha256Base64Url } from "./sha256.js";
5
+ import { createFrozenRecord, recordEntries } from "./state.js";
6
+ const utf8Encoder = new TextEncoder();
7
+ /** Applies a locally signed recovery proposal, vote, or certificate. */
8
+ export function evaluateMeshRecoveryCommand(state, command, verifiedAt, receivedAt) {
9
+ if (!command ||
10
+ typeof command !== "object" ||
11
+ Object.keys(command).sort().join(",") !== "kind,recipients" ||
12
+ command.kind !== "allocation.recovery" ||
13
+ !Array.isArray(command.recipients) ||
14
+ command.recipients.length === 0 ||
15
+ command.recipients.length > 32 ||
16
+ command.recipients.some((recipient) => !recipient ||
17
+ typeof recipient !== "object" ||
18
+ Object.keys(recipient).sort().join(",") !==
19
+ "envelope,preparedAt,recipientPeerId" ||
20
+ typeof recipient.recipientPeerId !== "string" ||
21
+ !Number.isSafeInteger(recipient.preparedAt) ||
22
+ recipient.preparedAt !== receivedAt))
23
+ throw new TypeError("Invalid Mesh recovery command");
24
+ const parsed = command.recipients.map((recipient) => ({
25
+ recipient,
26
+ result: validateSignedMeshEnvelope(recipient.envelope),
27
+ }));
28
+ if (parsed.some(({ result }) => !result.ok) ||
29
+ parsed.some(({ result }) => result.ok && !isRecoveryPayload(result.value.payload)))
30
+ return reject(state, "recovery_invalid");
31
+ const prepared = parsed
32
+ .map(({ recipient, result }) => {
33
+ if (!result.ok)
34
+ throw new TypeError("Invalid Mesh recovery envelope");
35
+ return {
36
+ recipientPeerId: recipient.recipientPeerId,
37
+ envelope: result.value,
38
+ };
39
+ })
40
+ .sort((left, right) => compareText(left.recipientPeerId, right.recipientPeerId));
41
+ const first = prepared[0].envelope;
42
+ const expectedRecipients = expectedRecoveryRecipients(state, first.payload);
43
+ const recipientPeerIds = prepared.map((recipient) => recipient.recipientPeerId);
44
+ if (expectedRecipients === undefined ||
45
+ !sameArray(recipientPeerIds, expectedRecipients) ||
46
+ new Set(recipientPeerIds).size !== recipientPeerIds.length ||
47
+ prepared.some(({ envelope, recipientPeerId }) => envelope.audience.kind !== "peer" ||
48
+ envelope.audience.peerId !== recipientPeerId ||
49
+ envelope.sender.peerId !== state.allocation.identity.peerId ||
50
+ envelope.sender.instanceId !== state.allocation.identity.instanceId ||
51
+ envelope.proof.keyId !== state.allocation.identity.keyId ||
52
+ !sameData(envelope.payload, first.payload) ||
53
+ envelope.payloadHash !== first.payloadHash ||
54
+ envelope.sentAt !== first.sentAt ||
55
+ envelope.expiresAt !== first.expiresAt ||
56
+ envelope.causationId !== first.causationId ||
57
+ envelope.correlationId !== first.correlationId ||
58
+ !sameData(envelope.criticalExtensions, first.criticalExtensions) ||
59
+ !canonicalDigest(envelope) ||
60
+ !validateMeshEnvelopeContext(envelope, {
61
+ tenantId: state.allocation.identity.tenantId,
62
+ meshId: state.allocation.identity.meshId,
63
+ peerId: recipientPeerId,
64
+ receivedAt: verifiedAt,
65
+ }).ok))
66
+ return reject(state, "recovery_invalid");
67
+ const recipientEnvelopes = createFrozenRecord(prepared.map(({ recipientPeerId, envelope }) => [
68
+ recipientPeerId,
69
+ envelope,
70
+ ]));
71
+ return applyRecovery(state, first, verifiedAt, receivedAt, "local", undefined, recipientEnvelopes);
72
+ }
73
+ /** Applies one authenticated remote recovery proposal, vote, or certificate. */
74
+ export function evaluateVerifiedMeshRecoveryEnvelope(state, request) {
75
+ if (!isRecoveryPayload(request.envelope.payload))
76
+ return reject(state, "recovery_invalid");
77
+ return applyRecovery(state, request.envelope, request.verifiedAt, request.receivedAt, "received", request.supportedCriticalExtensions);
78
+ }
79
+ /**
80
+ * Retains assignment authority delivered directly to a configured witness.
81
+ *
82
+ * Award, acceptance, renewal, and checkpoint copies use the normal
83
+ * authenticated inbound boundary. They never grant local execution authority.
84
+ */
85
+ export function evaluateVerifiedMeshWitnessEnvelope(state, request) {
86
+ const payload = request.envelope.payload;
87
+ if (!isWitnessPayload(payload))
88
+ return reject(state, "witness_assignment_invalid");
89
+ const envelope = request.envelope;
90
+ const policy = historicalPolicy(state, payload.objectiveId, payload.objectiveRevision);
91
+ if (policy === undefined ||
92
+ !policy.recoveryWitnessPeerIds.includes(state.allocation.identity.peerId) ||
93
+ envelope.audience.kind !== "peer" ||
94
+ envelope.audience.peerId !== state.allocation.identity.peerId)
95
+ return reject(state, "witness_assignment_invalid");
96
+ const context = validateMeshEnvelopeContext(envelope, {
97
+ tenantId: state.allocation.identity.tenantId,
98
+ meshId: state.allocation.identity.meshId,
99
+ peerId: state.allocation.identity.peerId,
100
+ receivedAt: request.verifiedAt,
101
+ ...(request.supportedCriticalExtensions === undefined
102
+ ? {}
103
+ : {
104
+ supportedCriticalExtensions: request.supportedCriticalExtensions,
105
+ }),
106
+ });
107
+ if (!context.ok || !canonicalDigest(envelope))
108
+ return reject(state, "witness_assignment_invalid");
109
+ const retainedWitnessEnvelope = retainedWitnessEnvelopeByMessageId(state, envelope.messageId);
110
+ if (retainedWitnessEnvelope !== undefined)
111
+ return sameData(retainedWitnessEnvelope, envelope)
112
+ ? acceptedDuplicate(state)
113
+ : reject(state, "witness_assignment_duplicate_conflict");
114
+ if (meshAllocationRetainsMessageId(state, envelope.messageId))
115
+ return reject(state, "witness_assignment_duplicate_conflict");
116
+ const fenceKey = meshAssignmentFenceKey(payload);
117
+ const prior = state.allocation.witnessAssignments[fenceKey];
118
+ if (payload.type === "work.award")
119
+ return acceptWitnessAward(state, request, policy.recoveryWitnessPeerIds, prior);
120
+ if (prior === undefined)
121
+ return reject(state, "witness_assignment_invalid");
122
+ if (payload.type === "work.accept")
123
+ return acceptWitnessAcceptance(state, request, prior);
124
+ if (payload.type === "lease.renew")
125
+ return acceptWitnessRenewal(state, request, prior, policy.maximumLeaseDurationMs, policy.maximumLeaseRenewals);
126
+ return acceptWitnessCheckpoint(state, request, prior);
127
+ }
128
+ function applyRecovery(state, envelope, verifiedAt, acceptedAt, direction, supportedCriticalExtensions, recipientEnvelopes) {
129
+ const payload = envelope.payload;
130
+ const recordType = payload.type;
131
+ const recordId = recoveryRecordId(payload);
132
+ const existing = recoveryProjection(state, payload);
133
+ if (existing !== undefined)
134
+ return existing.direction === direction &&
135
+ sameData(existing.envelope, envelope)
136
+ ? acceptedDuplicate(state)
137
+ : reject(state, "recovery_duplicate_conflict");
138
+ if (state.coordination.domainRecords[domainKey(recordType, recordId)] !==
139
+ undefined)
140
+ return reject(state, "domain_record_conflict");
141
+ if (meshAllocationRetainsMessageId(state, envelope.messageId))
142
+ return reject(state, "recovery_duplicate_conflict");
143
+ if (envelope.audience.kind !== "peer" ||
144
+ (direction === "received" &&
145
+ envelope.audience.peerId !== state.allocation.identity.peerId) ||
146
+ (direction === "local" &&
147
+ (envelope.sender.peerId !== state.allocation.identity.peerId ||
148
+ envelope.sender.instanceId !== state.allocation.identity.instanceId ||
149
+ envelope.proof.keyId !== state.allocation.identity.keyId)))
150
+ return reject(state, "recovery_invalid");
151
+ const context = validateMeshEnvelopeContext(envelope, {
152
+ tenantId: state.allocation.identity.tenantId,
153
+ meshId: state.allocation.identity.meshId,
154
+ peerId: envelope.audience.peerId,
155
+ receivedAt: verifiedAt,
156
+ ...(supportedCriticalExtensions === undefined
157
+ ? {}
158
+ : { supportedCriticalExtensions }),
159
+ });
160
+ if (!context.ok || !canonicalDigest(envelope))
161
+ return reject(state, "recovery_invalid");
162
+ if (payload.type === "lease.takeover_proposal")
163
+ return applyProposal(state, envelope, verifiedAt, acceptedAt, direction, supportedCriticalExtensions, recipientEnvelopes);
164
+ if (payload.type === "lease.vote")
165
+ return applyVote(state, envelope, verifiedAt, acceptedAt, direction, supportedCriticalExtensions, recipientEnvelopes);
166
+ return applyCertificate(state, envelope, verifiedAt, acceptedAt, direction, supportedCriticalExtensions, recipientEnvelopes);
167
+ }
168
+ function applyProposal(state, envelope, verifiedAt, acceptedAt, direction, supportedCriticalExtensions, recipientEnvelopes) {
169
+ const payload = envelope.payload;
170
+ if (envelope.audience.kind !== "peer")
171
+ return reject(state, "recovery_invalid");
172
+ const recipientPeerId = envelope.audience.peerId;
173
+ const key = meshAssignmentFenceKey(payload);
174
+ const fence = state.allocation.assignmentFenceHeads[key];
175
+ const witness = state.allocation.witnessAssignments[key];
176
+ const lease = currentLeaseForFence(state, fence, witness);
177
+ const policy = historicalPolicy(state, payload.objectiveId, payload.objectiveRevision);
178
+ if (fence === undefined ||
179
+ fence.phase !== "expired" ||
180
+ lease === undefined ||
181
+ lease.status !== "expired" ||
182
+ policy === undefined ||
183
+ acceptedAt < lease.currentLeaseExpiresAtLogical + policy.recoveryGraceMs ||
184
+ payload.proposedAssignmentEpoch !== fence.assignmentEpoch + 1 ||
185
+ !proposalMatchesLease(payload, lease) ||
186
+ envelope.sender.peerId !== payload.proposerPeerId ||
187
+ !isRecoveryParticipant(recipientPeerId, payload, policy.recoveryWitnessPeerIds, true) ||
188
+ !isAdmittedPeer(state, payload.proposedAssigneePeerId, verifiedAt))
189
+ return reject(state, acceptedAt <
190
+ (lease?.currentLeaseExpiresAtLogical ?? 0) +
191
+ (policy?.recoveryGraceMs ?? 0)
192
+ ? "recovery_grace_not_elapsed"
193
+ : "recovery_authority_stale");
194
+ if (payload.proposalAuthority === "witness") {
195
+ if (!policy.recoveryWitnessPeerIds.includes(payload.proposerPeerId))
196
+ return reject(state, "recovery_invalid");
197
+ const consent = state.allocation.takeoverProposals[payload.candidateConsentProposalId];
198
+ if (consent === undefined ||
199
+ consent.envelope.payload.proposalAuthority !== "candidate" ||
200
+ consent.envelope.payload.proposedAssigneePeerId !==
201
+ payload.proposedAssigneePeerId ||
202
+ consent.envelope.payload.proposedAssignmentEpoch !==
203
+ payload.proposedAssignmentEpoch ||
204
+ meshAssignmentFenceKey(consent.envelope.payload) !== key ||
205
+ (direction === "local" &&
206
+ envelope.causationId !==
207
+ recoveryEnvelopeForPeer(consent, payload.proposerPeerId).messageId))
208
+ return reject(state, "recovery_invalid");
209
+ }
210
+ else {
211
+ if (payload.proposerPeerId !== payload.proposedAssigneePeerId ||
212
+ (direction === "local" &&
213
+ envelope.causationId !== currentLeaseCausationMessageId(witness, lease)))
214
+ return reject(state, "recovery_invalid");
215
+ }
216
+ if (Object.keys(state.allocation.takeoverProposals).length >=
217
+ state.allocation.limits.maximumTakeoverProposals)
218
+ return reject(state, "recovery_capacity_exceeded");
219
+ const projection = Object.freeze({
220
+ takeoverProposalId: payload.takeoverProposalId,
221
+ direction,
222
+ acceptedAt,
223
+ validityVerifiedAt: verifiedAt,
224
+ ...(supportedCriticalExtensions === undefined
225
+ ? {}
226
+ : {
227
+ supportedCriticalExtensions: Object.freeze([
228
+ ...supportedCriticalExtensions,
229
+ ]),
230
+ }),
231
+ envelope,
232
+ ...(recipientEnvelopes === undefined ? {} : { recipientEnvelopes }),
233
+ });
234
+ return commitRecord(state, {
235
+ ...state.allocation,
236
+ takeoverProposals: createFrozenRecord([
237
+ ...recordEntries(state.allocation.takeoverProposals),
238
+ [payload.takeoverProposalId, projection],
239
+ ]),
240
+ }, envelope, payload.type, payload.takeoverProposalId, acceptedAt, direction, recoveryEffects(envelope, payload.takeoverProposalId, direction, recipientEnvelopes));
241
+ }
242
+ function applyVote(state, envelope, verifiedAt, acceptedAt, direction, supportedCriticalExtensions, recipientEnvelopes) {
243
+ const payload = envelope.payload;
244
+ if (envelope.audience.kind !== "peer")
245
+ return reject(state, "recovery_invalid");
246
+ const recipientPeerId = envelope.audience.peerId;
247
+ const proposal = state.allocation.takeoverProposals[payload.takeoverProposalId];
248
+ if (proposal === undefined)
249
+ return reject(state, "recovery_invalid");
250
+ const proposalPayload = proposal.envelope.payload;
251
+ const policy = historicalPolicy(state, proposalPayload.objectiveId, proposalPayload.objectiveRevision);
252
+ const key = meshAssignmentFenceKey(proposalPayload);
253
+ const fence = state.allocation.assignmentFenceHeads[key];
254
+ if (policy === undefined ||
255
+ fence === undefined ||
256
+ fence.phase !== "expired" ||
257
+ payload.objectiveId !== proposalPayload.objectiveId ||
258
+ acceptedAt < proposal.acceptedAt ||
259
+ (direction === "local" &&
260
+ envelope.causationId !==
261
+ recoveryEnvelopeForPeer(proposal, payload.witnessPeerId).messageId) ||
262
+ !policy.recoveryWitnessPeerIds.includes(payload.witnessPeerId) ||
263
+ envelope.sender.peerId !== payload.witnessPeerId ||
264
+ !isRecoveryParticipant(recipientPeerId, proposalPayload, policy.recoveryWitnessPeerIds))
265
+ return reject(state, "recovery_authority_stale");
266
+ const conflicting = Object.values(state.allocation.leaseVotes).find((vote) => {
267
+ const votedProposal = state.allocation.takeoverProposals[vote.takeoverProposalId];
268
+ return (vote.witnessPeerId === payload.witnessPeerId &&
269
+ votedProposal !== undefined &&
270
+ meshAssignmentFenceKey(votedProposal.envelope.payload) === key &&
271
+ votedProposal.envelope.payload.proposedAssignmentEpoch ===
272
+ proposalPayload.proposedAssignmentEpoch);
273
+ });
274
+ if (conflicting !== undefined)
275
+ return reject(state, "recovery_vote_conflict");
276
+ if (Object.keys(state.allocation.leaseVotes).length >=
277
+ state.allocation.limits.maximumLeaseVotes)
278
+ return reject(state, "recovery_capacity_exceeded");
279
+ const projection = Object.freeze({
280
+ leaseVoteId: payload.leaseVoteId,
281
+ takeoverProposalId: payload.takeoverProposalId,
282
+ witnessPeerId: payload.witnessPeerId,
283
+ direction,
284
+ acceptedAt,
285
+ validityVerifiedAt: verifiedAt,
286
+ ...(supportedCriticalExtensions === undefined
287
+ ? {}
288
+ : {
289
+ supportedCriticalExtensions: Object.freeze([
290
+ ...supportedCriticalExtensions,
291
+ ]),
292
+ }),
293
+ envelope,
294
+ ...(recipientEnvelopes === undefined ? {} : { recipientEnvelopes }),
295
+ });
296
+ return commitRecord(state, {
297
+ ...state.allocation,
298
+ leaseVotes: createFrozenRecord([
299
+ ...recordEntries(state.allocation.leaseVotes),
300
+ [payload.leaseVoteId, projection],
301
+ ]),
302
+ }, envelope, payload.type, payload.leaseVoteId, acceptedAt, direction, recoveryEffects(envelope, payload.leaseVoteId, direction, recipientEnvelopes));
303
+ }
304
+ function applyCertificate(state, envelope, verifiedAt, acceptedAt, direction, supportedCriticalExtensions, recipientEnvelopes) {
305
+ const payload = envelope.payload;
306
+ if (envelope.audience.kind !== "peer")
307
+ return reject(state, "recovery_invalid");
308
+ const recipientPeerId = envelope.audience.peerId;
309
+ const proposal = state.allocation.takeoverProposals[payload.takeoverProposalId];
310
+ if (proposal === undefined)
311
+ return reject(state, "recovery_invalid");
312
+ const proposalPayload = proposal.envelope.payload;
313
+ const key = meshAssignmentFenceKey(proposalPayload);
314
+ const fence = state.allocation.assignmentFenceHeads[key];
315
+ const policy = historicalPolicy(state, proposalPayload.objectiveId, proposalPayload.objectiveRevision);
316
+ const votes = payload.leaseVoteIds.map((voteId) => state.allocation.leaseVotes[voteId]);
317
+ const witnesses = new Set(votes.map((vote) => vote?.witnessPeerId).filter(Boolean));
318
+ if (policy === undefined ||
319
+ fence === undefined ||
320
+ fence.phase !== "expired" ||
321
+ fence.assignmentEpoch + 1 !== proposalPayload.proposedAssignmentEpoch ||
322
+ payload.objectiveId !== proposalPayload.objectiveId ||
323
+ envelope.sender.peerId !== payload.certificateAssemblerPeerId ||
324
+ acceptedAt < proposal.acceptedAt ||
325
+ (direction === "local" &&
326
+ envelope.causationId !==
327
+ recoveryEnvelopeForPeer(proposal, payload.certificateAssemblerPeerId)
328
+ .messageId) ||
329
+ !isRecoveryParticipant(payload.certificateAssemblerPeerId, proposalPayload, policy.recoveryWitnessPeerIds) ||
330
+ !isRecoveryParticipant(recipientPeerId, proposalPayload, policy.recoveryWitnessPeerIds, true) ||
331
+ votes.some((vote) => vote === undefined ||
332
+ vote.takeoverProposalId !== payload.takeoverProposalId ||
333
+ vote.acceptedAt > acceptedAt ||
334
+ !policy.recoveryWitnessPeerIds.includes(vote.witnessPeerId)) ||
335
+ witnesses.size !== votes.length)
336
+ return reject(state, "recovery_authority_stale");
337
+ if (votes.length < policy.recoveryWitnessThreshold)
338
+ return reject(state, "recovery_quorum_insufficient");
339
+ if (Object.keys(state.allocation.recoveryCertificates).length >=
340
+ state.allocation.limits.maximumRecoveryCertificates)
341
+ return reject(state, "recovery_capacity_exceeded");
342
+ const projection = Object.freeze({
343
+ certificateId: payload.certificateId,
344
+ takeoverProposalId: payload.takeoverProposalId,
345
+ direction,
346
+ acceptedAt,
347
+ validityVerifiedAt: verifiedAt,
348
+ ...(supportedCriticalExtensions === undefined
349
+ ? {}
350
+ : {
351
+ supportedCriticalExtensions: Object.freeze([
352
+ ...supportedCriticalExtensions,
353
+ ]),
354
+ }),
355
+ envelope,
356
+ ...(recipientEnvelopes === undefined ? {} : { recipientEnvelopes }),
357
+ });
358
+ const nextFence = Object.freeze({
359
+ ...fence,
360
+ assignmentEpoch: proposalPayload.proposedAssignmentEpoch,
361
+ assignmentAuthorityId: payload.certificateId,
362
+ fencingToken: payload.certificateId,
363
+ assigneePeerId: proposalPayload.proposedAssigneePeerId,
364
+ activeAwardId: undefined,
365
+ recoveryCertificateId: payload.certificateId,
366
+ phase: "recovering",
367
+ });
368
+ const workKey = JSON.stringify([
369
+ proposalPayload.objectiveId,
370
+ proposalPayload.workItemId,
371
+ ]);
372
+ const work = state.allocation.workAllocations[workKey];
373
+ const allocation = {
374
+ ...state.allocation,
375
+ assignmentFenceHeads: createFrozenRecord([
376
+ ...recordEntries(state.allocation.assignmentFenceHeads).filter(([entryKey]) => entryKey !== key),
377
+ [key, nextFence],
378
+ ]),
379
+ recoveryCertificates: createFrozenRecord([
380
+ ...recordEntries(state.allocation.recoveryCertificates),
381
+ [payload.certificateId, projection],
382
+ ]),
383
+ ...(work === undefined
384
+ ? {}
385
+ : {
386
+ workAllocations: createFrozenRecord([
387
+ ...recordEntries(state.allocation.workAllocations).filter(([entryKey]) => entryKey !== workKey),
388
+ [
389
+ workKey,
390
+ Object.freeze({
391
+ ...work,
392
+ phase: "recovering",
393
+ activeAwardId: undefined,
394
+ activeAcceptanceId: undefined,
395
+ updatedAt: acceptedAt,
396
+ }),
397
+ ],
398
+ ]),
399
+ }),
400
+ };
401
+ return commitRecord(state, allocation, envelope, payload.type, payload.certificateId, acceptedAt, direction, recoveryEffects(envelope, payload.certificateId, direction, recipientEnvelopes));
402
+ }
403
+ function acceptWitnessAward(state, request, witnesses, prior) {
404
+ const envelope = request.envelope;
405
+ const payload = envelope.payload;
406
+ if (!witnesses.includes(state.allocation.identity.peerId) ||
407
+ envelope.sender.peerId !== payload.ownerPeerId)
408
+ return reject(state, "witness_assignment_invalid");
409
+ if (prior !== undefined && sameData(prior.awardEnvelope, envelope))
410
+ return acceptedDuplicate(state);
411
+ const recoveryAward = payload.authorityKind === "recovery_certificate";
412
+ const certificate = recoveryAward
413
+ ? state.allocation.recoveryCertificates[payload.recoveryCertificateId]
414
+ : undefined;
415
+ const fence = state.allocation.assignmentFenceHeads[meshAssignmentFenceKey(payload)];
416
+ if (prior !== undefined &&
417
+ (!recoveryAward ||
418
+ certificate === undefined ||
419
+ prior.leaseHead?.status !== "expired" ||
420
+ payload.assignmentEpoch !== prior.leaseHead.assignmentEpoch + 1 ||
421
+ payload.resumeCheckpointId !== prior.latestCheckpoint?.recordId ||
422
+ fence === undefined ||
423
+ !["recovering", "award_pending"].includes(fence.phase) ||
424
+ fence.recoveryCertificateId !== certificate.certificateId ||
425
+ fence.assignmentEpoch !== payload.assignmentEpoch ||
426
+ fence.assignmentAuthorityId !== payload.assignmentAuthorityId ||
427
+ fence.fencingToken !== payload.fencingToken ||
428
+ fence.assigneePeerId !== payload.assigneePeerId))
429
+ return reject(state, "witness_assignment_duplicate_conflict");
430
+ if (prior === undefined &&
431
+ Object.keys(state.allocation.witnessAssignments).length >=
432
+ state.allocation.limits.maximumWitnessAssignments)
433
+ return reject(state, "witness_assignment_capacity_exceeded");
434
+ const key = meshAssignmentFenceKey(payload);
435
+ const projection = Object.freeze({
436
+ assignmentFenceKey: key,
437
+ observedAt: request.receivedAt,
438
+ awardValidityVerifiedAt: request.verifiedAt,
439
+ ...(request.supportedCriticalExtensions === undefined
440
+ ? {}
441
+ : {
442
+ supportedCriticalExtensions: Object.freeze([
443
+ ...request.supportedCriticalExtensions,
444
+ ]),
445
+ }),
446
+ awardEnvelope: envelope,
447
+ leaseRenewals: Object.freeze([]),
448
+ });
449
+ const nextFence = !recoveryAward || fence === undefined
450
+ ? undefined
451
+ : Object.freeze({
452
+ ...fence,
453
+ activeAwardId: payload.awardId,
454
+ phase: "award_pending",
455
+ });
456
+ return commitRecord(state, {
457
+ ...state.allocation,
458
+ witnessAssignments: createFrozenRecord([
459
+ ...recordEntries(state.allocation.witnessAssignments).filter(([entryKey]) => entryKey !== key),
460
+ [key, projection],
461
+ ]),
462
+ ...(nextFence === undefined
463
+ ? {}
464
+ : {
465
+ assignmentFenceHeads: replaceRecord(state.allocation.assignmentFenceHeads, key, nextFence),
466
+ }),
467
+ }, envelope, payload.type, payload.awardId, request.receivedAt, "received");
468
+ }
469
+ function acceptWitnessAcceptance(state, request, prior) {
470
+ const envelope = request.envelope;
471
+ const payload = envelope.payload;
472
+ const award = prior.awardEnvelope.payload;
473
+ if (prior.acceptanceEnvelope !== undefined ||
474
+ envelope.sender.peerId !== award.assigneePeerId ||
475
+ envelope.causationId !== prior.awardEnvelope.messageId ||
476
+ !responseMatchesAward(payload, award))
477
+ return reject(state, "witness_assignment_invalid");
478
+ const leaseLogical = logicalDeadline(award.leaseExpiresAt, request.verifiedAt, request.receivedAt);
479
+ const workLogical = logicalDeadline(award.workDeadline, request.verifiedAt, request.receivedAt);
480
+ if (leaseLogical === undefined ||
481
+ workLogical === undefined ||
482
+ leaseLogical <= request.receivedAt ||
483
+ leaseLogical > workLogical)
484
+ return reject(state, "witness_assignment_invalid");
485
+ const scope = executionScopeKey(award);
486
+ const timerId = witnessLeaseTimerId(prior.assignmentFenceKey);
487
+ if (state.coordination.timers[timerId] !== undefined ||
488
+ Object.keys(state.coordination.timers).length >=
489
+ state.coordination.limits.maximumTimers)
490
+ return reject(state, "timer_capacity_exceeded");
491
+ const leaseHead = Object.freeze({
492
+ executionScopeKey: scope,
493
+ objectiveId: award.objectiveId,
494
+ objectiveDocumentId: award.objectiveDocumentId,
495
+ objectiveRevision: award.objectiveRevision,
496
+ workItemId: award.workItemId,
497
+ workItemRevision: award.workItemRevision,
498
+ ownerPeerId: award.ownerPeerId,
499
+ ownerEpoch: award.ownerEpoch,
500
+ assigneePeerId: award.assigneePeerId,
501
+ awardId: award.awardId,
502
+ assignmentEpoch: award.assignmentEpoch,
503
+ assignmentAuthorityId: award.assignmentAuthorityId,
504
+ fencingToken: award.fencingToken,
505
+ acceptanceId: payload.acceptanceId,
506
+ acceptanceMessageId: envelope.messageId,
507
+ originalLeaseExpiresAt: award.leaseExpiresAt,
508
+ originalLeaseExpiresAtLogical: leaseLogical,
509
+ workDeadline: award.workDeadline,
510
+ workDeadlineAt: workLogical,
511
+ leaseRenewalSequence: 0,
512
+ currentLeaseExpiresAt: award.leaseExpiresAt,
513
+ currentLeaseExpiresAtLogical: leaseLogical,
514
+ status: "active",
515
+ expiryTimerId: timerId,
516
+ expiryTimerGeneration: 1,
517
+ });
518
+ const projection = Object.freeze({
519
+ ...prior,
520
+ observedAt: request.receivedAt,
521
+ acceptanceValidityVerifiedAt: request.verifiedAt,
522
+ acceptanceEnvelope: envelope,
523
+ leaseHead,
524
+ });
525
+ const fence = state.allocation.assignmentFenceHeads[prior.assignmentFenceKey];
526
+ if (fence !== undefined &&
527
+ (fence.phase !== "award_pending" ||
528
+ fence.assignmentEpoch !== award.assignmentEpoch ||
529
+ fence.assignmentAuthorityId !== award.assignmentAuthorityId ||
530
+ fence.assigneePeerId !== award.assigneePeerId))
531
+ return reject(state, "recovery_authority_stale");
532
+ const nextFence = Object.freeze({
533
+ assignmentFenceKey: prior.assignmentFenceKey,
534
+ objectiveId: award.objectiveId,
535
+ objectiveRevision: award.objectiveRevision,
536
+ workItemId: award.workItemId,
537
+ workItemRevision: award.workItemRevision,
538
+ ownerPeerId: award.ownerPeerId,
539
+ ownerEpoch: award.ownerEpoch,
540
+ assignmentEpoch: award.assignmentEpoch,
541
+ assignmentAuthorityId: award.assignmentAuthorityId,
542
+ fencingToken: award.fencingToken,
543
+ assigneePeerId: award.assigneePeerId,
544
+ activeAwardId: award.awardId,
545
+ ...(award.authorityKind === "recovery_certificate"
546
+ ? { recoveryCertificateId: award.recoveryCertificateId }
547
+ : {}),
548
+ phase: "active",
549
+ });
550
+ return commitRecord(state, {
551
+ ...state.allocation,
552
+ witnessAssignments: replaceRecord(state.allocation.witnessAssignments, prior.assignmentFenceKey, projection),
553
+ assignmentFenceHeads: replaceRecord(state.allocation.assignmentFenceHeads, prior.assignmentFenceKey, nextFence),
554
+ }, envelope, payload.type, payload.acceptanceId, request.receivedAt, "received", undefined, Object.freeze({
555
+ timerId,
556
+ kind: "lease.expiry",
557
+ dueAt: leaseLogical,
558
+ generation: 1,
559
+ domainRecordKey: domainKey(payload.type, payload.acceptanceId),
560
+ }));
561
+ }
562
+ function acceptWitnessRenewal(state, request, prior, maximumLeaseDurationMs, maximumLeaseRenewals) {
563
+ const envelope = request.envelope;
564
+ const payload = envelope.payload;
565
+ const head = prior.leaseHead;
566
+ if (prior.acceptanceEnvelope === undefined ||
567
+ head === undefined ||
568
+ head.status !== "active" ||
569
+ envelope.sender.peerId !== head.assigneePeerId ||
570
+ !authorityMatchesLease(payload, head) ||
571
+ payload.leaseExpiresAt !== head.currentLeaseExpiresAt ||
572
+ payload.leaseRenewalSequence !== head.leaseRenewalSequence + 1 ||
573
+ payload.previousLeaseRenewalId !== head.latestLeaseRenewalId ||
574
+ envelope.causationId !== currentLeaseCausationMessageId(prior, head) ||
575
+ request.receivedAt >= head.currentLeaseExpiresAtLogical ||
576
+ compare(request.verifiedAt, head.currentLeaseExpiresAt) >= 0 ||
577
+ payload.leaseRenewalSequence > maximumLeaseRenewals)
578
+ return reject(state, "witness_assignment_invalid");
579
+ const renewedLogical = logicalDeadline(payload.renewedLeaseExpiresAt, head.currentLeaseExpiresAt, head.currentLeaseExpiresAtLogical);
580
+ if (renewedLogical === undefined ||
581
+ renewedLogical <= head.currentLeaseExpiresAtLogical ||
582
+ renewedLogical - head.currentLeaseExpiresAtLogical >
583
+ maximumLeaseDurationMs ||
584
+ renewedLogical > head.workDeadlineAt)
585
+ return reject(state, "witness_assignment_invalid");
586
+ const evidence = Object.freeze({
587
+ leaseRenewalId: payload.leaseRenewalId,
588
+ executionScopeKey: head.executionScopeKey,
589
+ leaseRenewalSequence: payload.leaseRenewalSequence,
590
+ ...(payload.previousLeaseRenewalId === undefined
591
+ ? {}
592
+ : { previousLeaseRenewalId: payload.previousLeaseRenewalId }),
593
+ acceptedAt: request.receivedAt,
594
+ validityVerifiedAt: request.verifiedAt,
595
+ direction: "received",
596
+ ...(request.supportedCriticalExtensions === undefined
597
+ ? {}
598
+ : {
599
+ supportedCriticalExtensions: Object.freeze([
600
+ ...request.supportedCriticalExtensions,
601
+ ]),
602
+ }),
603
+ renewedLeaseExpiresAtLogical: renewedLogical,
604
+ envelope,
605
+ });
606
+ const generation = (head.expiryTimerGeneration ?? 0) + 1;
607
+ if (generation > Number.MAX_SAFE_INTEGER)
608
+ return reject(state, "timer_generation_exhausted");
609
+ const timerId = witnessLeaseTimerId(prior.assignmentFenceKey);
610
+ const nextHead = Object.freeze({
611
+ ...head,
612
+ leaseRenewalSequence: payload.leaseRenewalSequence,
613
+ latestLeaseRenewalId: payload.leaseRenewalId,
614
+ currentLeaseExpiresAt: payload.renewedLeaseExpiresAt,
615
+ currentLeaseExpiresAtLogical: renewedLogical,
616
+ expiryTimerId: timerId,
617
+ expiryTimerGeneration: generation,
618
+ });
619
+ const projection = Object.freeze({
620
+ ...prior,
621
+ observedAt: request.receivedAt,
622
+ leaseHead: nextHead,
623
+ leaseRenewals: Object.freeze([...prior.leaseRenewals, evidence]),
624
+ });
625
+ return commitRecord(state, {
626
+ ...state.allocation,
627
+ witnessAssignments: replaceRecord(state.allocation.witnessAssignments, prior.assignmentFenceKey, projection),
628
+ }, envelope, payload.type, payload.leaseRenewalId, request.receivedAt, "received", undefined, Object.freeze({
629
+ timerId,
630
+ kind: "lease.expiry",
631
+ dueAt: renewedLogical,
632
+ generation,
633
+ domainRecordKey: domainKey(payload.type, payload.leaseRenewalId),
634
+ }), timerId);
635
+ }
636
+ function acceptWitnessCheckpoint(state, request, prior) {
637
+ const envelope = request.envelope;
638
+ const payload = envelope.payload;
639
+ const head = prior.leaseHead;
640
+ const latest = prior.latestCheckpoint;
641
+ if (prior.acceptanceEnvelope === undefined ||
642
+ head === undefined ||
643
+ head.status !== "active" ||
644
+ envelope.sender.peerId !== head.assigneePeerId ||
645
+ !authorityMatchesLease(payload, head) ||
646
+ payload.leaseExpiresAt !== head.currentLeaseExpiresAt ||
647
+ request.receivedAt >= head.currentLeaseExpiresAtLogical ||
648
+ compare(request.verifiedAt, head.currentLeaseExpiresAt) >= 0 ||
649
+ (latest === undefined
650
+ ? payload.checkpointSequence !== 1 ||
651
+ payload.previousCheckpointId !== undefined ||
652
+ envelope.causationId !== head.acceptanceMessageId
653
+ : payload.checkpointSequence !==
654
+ latest.envelope.payload
655
+ .checkpointSequence +
656
+ 1 ||
657
+ payload.previousCheckpointId !== latest.recordId ||
658
+ envelope.causationId !== latest.envelope.messageId))
659
+ return reject(state, "witness_assignment_invalid");
660
+ const record = Object.freeze({
661
+ recordType: "checkpoint",
662
+ recordId: payload.checkpointId,
663
+ direction: "received",
664
+ recordedAt: request.receivedAt,
665
+ validityVerifiedAt: request.verifiedAt,
666
+ ...(request.supportedCriticalExtensions === undefined
667
+ ? {}
668
+ : {
669
+ supportedCriticalExtensions: Object.freeze([
670
+ ...request.supportedCriticalExtensions,
671
+ ]),
672
+ }),
673
+ envelope,
674
+ });
675
+ const projection = Object.freeze({
676
+ ...prior,
677
+ observedAt: request.receivedAt,
678
+ latestCheckpoint: record,
679
+ });
680
+ return commitRecord(state, {
681
+ ...state.allocation,
682
+ witnessAssignments: replaceRecord(state.allocation.witnessAssignments, prior.assignmentFenceKey, projection),
683
+ }, envelope, payload.type, payload.checkpointId, request.receivedAt, "received");
684
+ }
685
+ function commitRecord(state, allocationCandidate, envelope, recordType, recordId, acceptedAt, direction, effect, timer, removeTimerId) {
686
+ if (Object.keys(state.coordination.domainRecords).length >=
687
+ state.coordination.limits.maximumDomainRecords)
688
+ return reject(state, "domain_capacity_exceeded");
689
+ if (state.coordination.journal.length >=
690
+ state.coordination.limits.maximumJournalEntries)
691
+ return reject(state, "journal_capacity_exceeded");
692
+ if (state.coordination.localEventSequence >= Number.MAX_SAFE_INTEGER)
693
+ throw new RangeError("Mesh coordination event sequence exhausted");
694
+ const recordKey = domainKey(recordType, recordId);
695
+ if (state.coordination.domainRecords[recordKey] !== undefined)
696
+ return reject(state, "domain_record_conflict");
697
+ const allocation = restoreMeshAllocationState({
698
+ ...allocationCandidate,
699
+ lastLogicalTime: acceptedAt,
700
+ });
701
+ const sequence = state.coordination.localEventSequence + 1;
702
+ const coordination = Object.freeze({
703
+ ...state.coordination,
704
+ ...(timer === undefined && removeTimerId === undefined
705
+ ? {}
706
+ : {
707
+ timers: createFrozenRecord([
708
+ ...recordEntries(state.coordination.timers).filter(([timerId]) => timerId !== removeTimerId),
709
+ ...(timer === undefined ? [] : [[timer.timerId, timer]]),
710
+ ]),
711
+ }),
712
+ domainRecords: createFrozenRecord([
713
+ ...recordEntries(state.coordination.domainRecords),
714
+ [
715
+ recordKey,
716
+ Object.freeze({
717
+ recordKey,
718
+ recordType,
719
+ recordId,
720
+ contentDigest: envelope.payloadHash.slice("sha256:".length),
721
+ messageId: envelope.messageId,
722
+ acceptedAt,
723
+ objectiveId: envelope.payload.objectiveId,
724
+ }),
725
+ ],
726
+ ]),
727
+ journal: Object.freeze([
728
+ ...state.coordination.journal,
729
+ Object.freeze({
730
+ sequence,
731
+ occurredAt: acceptedAt,
732
+ kind: direction === "local"
733
+ ? "command.accepted"
734
+ : "domain.accepted",
735
+ domainRecordKey: recordKey,
736
+ }),
737
+ ]),
738
+ localEventSequence: sequence,
739
+ lastLogicalTime: acceptedAt,
740
+ });
741
+ const next = createMeshAllocationRuntimeState(coordination, Object.freeze({ ...state.discovery, lastLogicalTime: acceptedAt }), Object.freeze({ ...state.objectives, lastLogicalTime: acceptedAt }), allocation);
742
+ return Object.freeze({
743
+ accepted: true,
744
+ duplicate: false,
745
+ state: next,
746
+ effects: Object.freeze(effect === undefined
747
+ ? []
748
+ : Array.isArray(effect)
749
+ ? [...effect]
750
+ : [effect]),
751
+ });
752
+ }
753
+ function recoveryEffects(envelope, recordId, direction, recipientEnvelopes) {
754
+ if (direction !== "local" || recipientEnvelopes === undefined)
755
+ return undefined;
756
+ return Object.freeze(recordEntries(recipientEnvelopes).map(([recipientPeerId, recipientEnvelope]) => Object.freeze({
757
+ kind: "allocation.recovery.dispatch",
758
+ recordId,
759
+ recordType: recipientEnvelope.payload.type,
760
+ recipientPeerId,
761
+ messageId: recipientEnvelope.messageId,
762
+ envelope: recipientEnvelope,
763
+ })));
764
+ }
765
+ function replaceRecord(record, key, value) {
766
+ return createFrozenRecord([
767
+ ...recordEntries(record).filter(([entryKey]) => entryKey !== key),
768
+ [key, value],
769
+ ]);
770
+ }
771
+ function historicalPolicy(state, objectiveId, objectiveRevision) {
772
+ return state.objectives.objectivePolicies[JSON.stringify([objectiveId, objectiveRevision])];
773
+ }
774
+ function expectedRecoveryRecipients(state, payload) {
775
+ const proposal = payload.type === "lease.takeover_proposal"
776
+ ? payload
777
+ : state.allocation.takeoverProposals[payload.takeoverProposalId]?.envelope
778
+ .payload;
779
+ if (proposal === undefined)
780
+ return undefined;
781
+ const policy = historicalPolicy(state, proposal.objectiveId, proposal.objectiveRevision);
782
+ if (policy === undefined)
783
+ return undefined;
784
+ const recipients = [
785
+ proposal.ownerPeerId,
786
+ proposal.assigneePeerId,
787
+ proposal.proposedAssigneePeerId,
788
+ ...policy.recoveryWitnessPeerIds,
789
+ ];
790
+ return Object.freeze([...new Set(recipients)]
791
+ .filter((peerId) => peerId !== state.allocation.identity.peerId)
792
+ .sort(compareText));
793
+ }
794
+ function recoveryEnvelopeForPeer(projection, peerId) {
795
+ return projection.recipientEnvelopes?.[peerId] ?? projection.envelope;
796
+ }
797
+ function currentLeaseForFence(state, fence, witness) {
798
+ if (fence === undefined)
799
+ return undefined;
800
+ return (Object.values(state.allocation.leaseHeads).find((head) => meshAssignmentFenceKey(head) === fence.assignmentFenceKey &&
801
+ head.assignmentEpoch === fence.assignmentEpoch) ?? witness?.leaseHead);
802
+ }
803
+ function currentLeaseCausationMessageId(witness, lease) {
804
+ if (lease.latestLeaseRenewalId === undefined)
805
+ return lease.acceptanceMessageId;
806
+ return witness?.leaseRenewals.find((renewal) => renewal.leaseRenewalId === lease.latestLeaseRenewalId)?.envelope.messageId;
807
+ }
808
+ function proposalMatchesLease(payload, lease) {
809
+ return (authorityMatchesLease(payload, lease) &&
810
+ payload.leaseExpiresAt === lease.currentLeaseExpiresAt &&
811
+ payload.leaseRenewalSequence === lease.leaseRenewalSequence &&
812
+ payload.latestLeaseRenewalId === lease.latestLeaseRenewalId);
813
+ }
814
+ function authorityMatchesLease(payload, lease) {
815
+ return (payload.objectiveId === lease.objectiveId &&
816
+ payload.objectiveDocumentId === lease.objectiveDocumentId &&
817
+ payload.objectiveRevision === lease.objectiveRevision &&
818
+ payload.workItemId === lease.workItemId &&
819
+ payload.workItemRevision === lease.workItemRevision &&
820
+ payload.ownerPeerId === lease.ownerPeerId &&
821
+ payload.ownerEpoch === lease.ownerEpoch &&
822
+ payload.assigneePeerId === lease.assigneePeerId &&
823
+ payload.awardId === lease.awardId &&
824
+ payload.acceptanceId === lease.acceptanceId &&
825
+ payload.assignmentEpoch === lease.assignmentEpoch &&
826
+ payload.assignmentAuthorityId === lease.assignmentAuthorityId &&
827
+ payload.fencingToken === lease.fencingToken);
828
+ }
829
+ function responseMatchesAward(response, award) {
830
+ return (response.awardId === award.awardId &&
831
+ response.objectiveId === award.objectiveId &&
832
+ response.objectiveDocumentId === award.objectiveDocumentId &&
833
+ response.objectiveRevision === award.objectiveRevision &&
834
+ response.workItemId === award.workItemId &&
835
+ response.workItemRevision === award.workItemRevision &&
836
+ response.ownerPeerId === award.ownerPeerId &&
837
+ response.ownerEpoch === award.ownerEpoch &&
838
+ response.assigneePeerId === award.assigneePeerId &&
839
+ response.assignmentEpoch === award.assignmentEpoch &&
840
+ response.assignmentAuthorityId === award.assignmentAuthorityId &&
841
+ response.fencingToken === award.fencingToken &&
842
+ response.acceptanceDeadline === award.acceptanceDeadline);
843
+ }
844
+ function isRecoveryParticipant(peerId, proposal, witnesses, includeOldAssignee = false) {
845
+ return (peerId === proposal.ownerPeerId ||
846
+ peerId === proposal.proposedAssigneePeerId ||
847
+ witnesses.includes(peerId) ||
848
+ (includeOldAssignee && peerId === proposal.assigneePeerId));
849
+ }
850
+ function isAdmittedPeer(state, peerId, verifiedAt) {
851
+ if (peerId === state.allocation.identity.peerId)
852
+ return true;
853
+ const admission = state.discovery.admittedPeers[peerId];
854
+ return (admission !== undefined && compare(verifiedAt, admission.validUntil) < 0);
855
+ }
856
+ function executionScopeKey(authority) {
857
+ return JSON.stringify([
858
+ authority.objectiveId,
859
+ authority.objectiveRevision,
860
+ authority.workItemId,
861
+ authority.workItemRevision,
862
+ authority.ownerPeerId,
863
+ authority.ownerEpoch,
864
+ authority.awardId,
865
+ authority.assignmentEpoch,
866
+ ]);
867
+ }
868
+ function witnessLeaseTimerId(fenceKey) {
869
+ return `witness.lease.expiry:${sha256Base64Url(utf8Encoder.encode(fenceKey))}`;
870
+ }
871
+ function recoveryProjection(state, payload) {
872
+ if (payload.type === "lease.takeover_proposal")
873
+ return state.allocation.takeoverProposals[payload.takeoverProposalId];
874
+ if (payload.type === "lease.vote")
875
+ return state.allocation.leaseVotes[payload.leaseVoteId];
876
+ return state.allocation.recoveryCertificates[payload.certificateId];
877
+ }
878
+ function recoveryRecordId(payload) {
879
+ if (payload.type === "lease.takeover_proposal")
880
+ return payload.takeoverProposalId;
881
+ if (payload.type === "lease.vote")
882
+ return payload.leaseVoteId;
883
+ return payload.certificateId;
884
+ }
885
+ function isRecoveryPayload(payload) {
886
+ return Boolean(payload &&
887
+ typeof payload === "object" &&
888
+ ["lease.takeover_proposal", "lease.vote", "lease.certificate"].includes(payload.type));
889
+ }
890
+ function isWitnessPayload(payload) {
891
+ return Boolean(payload &&
892
+ typeof payload === "object" &&
893
+ ["work.award", "work.accept", "lease.renew", "work.checkpoint"].includes(payload.type));
894
+ }
895
+ function retainedWitnessEnvelopeByMessageId(state, messageId) {
896
+ for (const witness of Object.values(state.allocation.witnessAssignments)) {
897
+ const envelopes = [
898
+ witness.awardEnvelope,
899
+ ...(witness.acceptanceEnvelope === undefined
900
+ ? []
901
+ : [witness.acceptanceEnvelope]),
902
+ ...witness.leaseRenewals.map((renewal) => renewal.envelope),
903
+ ...(witness.latestCheckpoint === undefined
904
+ ? []
905
+ : [
906
+ witness.latestCheckpoint
907
+ .envelope,
908
+ ]),
909
+ ];
910
+ const retained = envelopes.find((envelope) => envelope.messageId === messageId);
911
+ if (retained !== undefined)
912
+ return retained;
913
+ }
914
+ return undefined;
915
+ }
916
+ function canonicalDigest(envelope) {
917
+ const canonical = canonicalizeMeshPayload(envelope.payload);
918
+ return (canonical.ok &&
919
+ envelope.payloadHash === `sha256:${sha256Base64Url(canonical.value)}`);
920
+ }
921
+ function domainKey(type, id) {
922
+ return JSON.stringify([type, id]);
923
+ }
924
+ function compare(left, right) {
925
+ const result = compareMeshTimestamps(left, right);
926
+ if (!result.ok)
927
+ throw new TypeError("Invalid recovery timestamp");
928
+ return result.value;
929
+ }
930
+ function sameData(left, right) {
931
+ return JSON.stringify(left) === JSON.stringify(right);
932
+ }
933
+ function sameArray(left, right) {
934
+ return (left.length === right.length &&
935
+ left.every((value, index) => value === right[index]));
936
+ }
937
+ function compareText(left, right) {
938
+ return left < right ? -1 : left > right ? 1 : 0;
939
+ }
940
+ function acceptedDuplicate(state) {
941
+ return Object.freeze({
942
+ accepted: true,
943
+ duplicate: true,
944
+ state,
945
+ effects: Object.freeze([]),
946
+ });
947
+ }
948
+ function reject(state, code) {
949
+ return Object.freeze({ accepted: false, code, state });
950
+ }
951
+ //# sourceMappingURL=coordination-recovery.js.map