@agentplat/mesh 0.3.0-alpha.1 → 0.3.0-alpha.3
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.
- package/README.md +247 -2
- package/dist/coordination-allocation-contracts.d.ts +598 -0
- package/dist/coordination-allocation-contracts.d.ts.map +1 -0
- package/dist/coordination-allocation-contracts.js +2 -0
- package/dist/coordination-allocation-contracts.js.map +1 -0
- package/dist/coordination-allocation-state.d.ts +19 -0
- package/dist/coordination-allocation-state.d.ts.map +1 -0
- package/dist/coordination-allocation-state.js +5496 -0
- package/dist/coordination-allocation-state.js.map +1 -0
- package/dist/coordination-allocation.d.ts +10 -0
- package/dist/coordination-allocation.d.ts.map +1 -0
- package/dist/coordination-allocation.js +2743 -0
- package/dist/coordination-allocation.js.map +1 -0
- package/dist/coordination-contracts.d.ts +78 -0
- package/dist/coordination-contracts.d.ts.map +1 -0
- package/dist/coordination-contracts.js +2 -0
- package/dist/coordination-contracts.js.map +1 -0
- package/dist/coordination-discovery-contracts.d.ts +150 -0
- package/dist/coordination-discovery-contracts.d.ts.map +1 -0
- package/dist/coordination-discovery-contracts.js +2 -0
- package/dist/coordination-discovery-contracts.js.map +1 -0
- package/dist/coordination-discovery-state.d.ts +12 -0
- package/dist/coordination-discovery-state.d.ts.map +1 -0
- package/dist/coordination-discovery-state.js +542 -0
- package/dist/coordination-discovery-state.js.map +1 -0
- package/dist/coordination-discovery.d.ts +19 -0
- package/dist/coordination-discovery.d.ts.map +1 -0
- package/dist/coordination-discovery.js +850 -0
- package/dist/coordination-discovery.js.map +1 -0
- package/dist/coordination-execution.d.ts +6 -0
- package/dist/coordination-execution.d.ts.map +1 -0
- package/dist/coordination-execution.js +854 -0
- package/dist/coordination-execution.js.map +1 -0
- package/dist/coordination-inbound-contracts.d.ts +150 -0
- package/dist/coordination-inbound-contracts.d.ts.map +1 -0
- package/dist/coordination-inbound-contracts.js +2 -0
- package/dist/coordination-inbound-contracts.js.map +1 -0
- package/dist/coordination-inbound-state.d.ts +26 -0
- package/dist/coordination-inbound-state.d.ts.map +1 -0
- package/dist/coordination-inbound-state.js +331 -0
- package/dist/coordination-inbound-state.js.map +1 -0
- package/dist/coordination-inbound.d.ts +16 -0
- package/dist/coordination-inbound.d.ts.map +1 -0
- package/dist/coordination-inbound.js +944 -0
- package/dist/coordination-inbound.js.map +1 -0
- package/dist/coordination-lease-renewal.d.ts +8 -0
- package/dist/coordination-lease-renewal.d.ts.map +1 -0
- package/dist/coordination-lease-renewal.js +518 -0
- package/dist/coordination-lease-renewal.js.map +1 -0
- package/dist/coordination-objective-topic-contracts.d.ts +70 -0
- package/dist/coordination-objective-topic-contracts.d.ts.map +1 -0
- package/dist/coordination-objective-topic-contracts.js +2 -0
- package/dist/coordination-objective-topic-contracts.js.map +1 -0
- package/dist/coordination-objective-topic.d.ts +6 -0
- package/dist/coordination-objective-topic.d.ts.map +1 -0
- package/dist/coordination-objective-topic.js +561 -0
- package/dist/coordination-objective-topic.js.map +1 -0
- package/dist/coordination-objective-work-contracts.d.ts +212 -0
- package/dist/coordination-objective-work-contracts.d.ts.map +1 -0
- package/dist/coordination-objective-work-contracts.js +2 -0
- package/dist/coordination-objective-work-contracts.js.map +1 -0
- package/dist/coordination-objective-work-state.d.ts +10 -0
- package/dist/coordination-objective-work-state.d.ts.map +1 -0
- package/dist/coordination-objective-work-state.js +1075 -0
- package/dist/coordination-objective-work-state.js.map +1 -0
- package/dist/coordination-objective-work-time.d.ts +9 -0
- package/dist/coordination-objective-work-time.d.ts.map +1 -0
- package/dist/coordination-objective-work-time.js +48 -0
- package/dist/coordination-objective-work-time.js.map +1 -0
- package/dist/coordination-objective-work.d.ts +11 -0
- package/dist/coordination-objective-work.d.ts.map +1 -0
- package/dist/coordination-objective-work.js +1090 -0
- package/dist/coordination-objective-work.js.map +1 -0
- package/dist/coordination-recovery.d.ts +13 -0
- package/dist/coordination-recovery.d.ts.map +1 -0
- package/dist/coordination-recovery.js +951 -0
- package/dist/coordination-recovery.js.map +1 -0
- package/dist/coordination-reducer.d.ts +8 -0
- package/dist/coordination-reducer.d.ts.map +1 -0
- package/dist/coordination-reducer.js +81 -0
- package/dist/coordination-reducer.js.map +1 -0
- package/dist/coordination-state.d.ts +13 -0
- package/dist/coordination-state.d.ts.map +1 -0
- package/dist/coordination-state.js +335 -0
- package/dist/coordination-state.js.map +1 -0
- package/dist/coordination-topic-contracts.d.ts +82 -0
- package/dist/coordination-topic-contracts.d.ts.map +1 -0
- package/dist/coordination-topic-contracts.js +2 -0
- package/dist/coordination-topic-contracts.js.map +1 -0
- package/dist/coordination-topic.d.ts +6 -0
- package/dist/coordination-topic.d.ts.map +1 -0
- package/dist/coordination-topic.js +561 -0
- package/dist/coordination-topic.js.map +1 -0
- package/dist/coordination.d.ts +21 -0
- package/dist/coordination.d.ts.map +1 -0
- package/dist/coordination.js +21 -0
- package/dist/coordination.js.map +1 -0
- package/dist/sha256.d.ts +3 -0
- package/dist/sha256.d.ts.map +1 -0
- package/dist/sha256.js +117 -0
- package/dist/sha256.js.map +1 -0
- package/package.json +7 -3
|
@@ -0,0 +1,1090 @@
|
|
|
1
|
+
import { compareMeshTimestamps, validateSignedMeshEnvelope, validateMeshEnvelopeContext, } from "@agentplat/mesh-protocol";
|
|
2
|
+
import { assertFrozenMeshObjectiveWorkState, } from "./coordination-objective-work-state.js";
|
|
3
|
+
import { logicalDeadline, logicalExpiry, } from "./coordination-objective-work-time.js";
|
|
4
|
+
import { assertFrozenMeshCoordinationState } from "./coordination-state.js";
|
|
5
|
+
import { assertFrozenMeshDiscoveryState, createMeshDiscoveryRuntimeState, } from "./coordination-discovery-state.js";
|
|
6
|
+
import { assertMeshLogicalTime, createFrozenRecord, recordEntries, } from "./state.js";
|
|
7
|
+
const digestPattern = /^[A-Za-z0-9_-]{43}$/;
|
|
8
|
+
const identifierPattern = /^[A-Za-z0-9][A-Za-z0-9._:@-]*$/;
|
|
9
|
+
const maximumTimerIdentifierBytes = 768;
|
|
10
|
+
const utf8Encoder = new TextEncoder();
|
|
11
|
+
/** Composes Objective/Work state with the existing aligned core projections. */
|
|
12
|
+
export function createMeshObjectiveWorkRuntimeState(coordination, discovery, objectives) {
|
|
13
|
+
createMeshDiscoveryRuntimeState(coordination, discovery);
|
|
14
|
+
assertFrozenMeshObjectiveWorkState(objectives);
|
|
15
|
+
if (!sameIdentity(coordination.identity, objectives.identity) ||
|
|
16
|
+
coordination.lastLogicalTime !== objectives.lastLogicalTime) {
|
|
17
|
+
throw new TypeError("Mesh Objective/Work runtime snapshots are not aligned");
|
|
18
|
+
}
|
|
19
|
+
for (const policy of Object.values(objectives.objectivePolicies)) {
|
|
20
|
+
const record = coordination.domainRecords[objectivePolicyDomainRecordKey(policy)];
|
|
21
|
+
if (!record ||
|
|
22
|
+
record.recordId !== policy.objectiveDocumentId ||
|
|
23
|
+
record.objectiveId !== policy.objectiveId ||
|
|
24
|
+
record.messageId !== policy.acceptedMessageId ||
|
|
25
|
+
record.acceptedAt !== policy.acceptedAt) {
|
|
26
|
+
throw new TypeError("Mesh Objective policy domain record is missing");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const document of Object.values(objectives.objectiveDocuments)) {
|
|
30
|
+
const envelope = document.envelope;
|
|
31
|
+
const payload = envelope.payload;
|
|
32
|
+
const record = coordination.domainRecords[objectiveDocumentDomainRecordKey(document)];
|
|
33
|
+
if (!record ||
|
|
34
|
+
record.recordId !== payload.objectiveDocumentId ||
|
|
35
|
+
record.objectiveId !== payload.objectiveId ||
|
|
36
|
+
record.messageId !== envelope.messageId ||
|
|
37
|
+
record.acceptedAt !== document.acceptedAt ||
|
|
38
|
+
record.contentDigest !== envelope.payloadHash.slice(7)) {
|
|
39
|
+
throw new TypeError("Mesh accepted Objective document record is missing");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const objective of Object.values(objectives.objectives)) {
|
|
43
|
+
const record = coordination.domainRecords[objectiveDomainRecordKey(objective)];
|
|
44
|
+
if (!record ||
|
|
45
|
+
record.recordId !== objective.objectiveDocumentId ||
|
|
46
|
+
record.objectiveId !== objective.objectiveId ||
|
|
47
|
+
record.messageId !== objective.acceptedMessageId ||
|
|
48
|
+
record.acceptedAt !== objective.acceptedAt) {
|
|
49
|
+
throw new TypeError("Mesh Objective current domain record is missing");
|
|
50
|
+
}
|
|
51
|
+
if (objective.status === "active") {
|
|
52
|
+
const timer = coordination.timers[objective.expiryTimerId];
|
|
53
|
+
if (!timer ||
|
|
54
|
+
timer.kind !== "objective.expiry" ||
|
|
55
|
+
timer.generation !== objective.expiryTimerGeneration ||
|
|
56
|
+
timer.dueAt !== objective.expiresAt ||
|
|
57
|
+
timer.domainRecordKey !== record.recordKey) {
|
|
58
|
+
throw new TypeError("Mesh Objective expiry timer binding is invalid");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (objective.status === "cancelled") {
|
|
62
|
+
const terminal = coordination.domainRecords[objective.terminalRecordKey];
|
|
63
|
+
const cancellation = objective.terminalCancellation;
|
|
64
|
+
if (!cancellation ||
|
|
65
|
+
!terminal ||
|
|
66
|
+
terminal.recordType !== "objective.cancel" ||
|
|
67
|
+
terminal.objectiveId !== objective.objectiveId ||
|
|
68
|
+
terminal.recordKey !== objective.terminalRecordKey ||
|
|
69
|
+
terminal.recordId !== cancellation.envelope.payload.cancellationId ||
|
|
70
|
+
terminal.messageId !== cancellation.envelope.messageId ||
|
|
71
|
+
terminal.contentDigest !== cancellation.envelope.payloadHash.slice(7) ||
|
|
72
|
+
terminal.acceptedAt !== objective.terminalAt) {
|
|
73
|
+
throw new TypeError("Mesh Objective cancellation binding is invalid");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const expectedManagedTimerIds = new Set();
|
|
78
|
+
for (const objective of Object.values(objectives.objectives)) {
|
|
79
|
+
if (objective.status === "active") {
|
|
80
|
+
expectedManagedTimerIds.add(objective.expiryTimerId);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const workItem of Object.values(objectives.workItems)) {
|
|
84
|
+
const boundObjectiveRecordKey = workObjectiveDomainRecordKey(workItem);
|
|
85
|
+
const boundObjectiveRecord = coordination.domainRecords[boundObjectiveRecordKey];
|
|
86
|
+
if (!boundObjectiveRecord ||
|
|
87
|
+
boundObjectiveRecord.recordId !== workItem.objectiveDocumentId ||
|
|
88
|
+
boundObjectiveRecord.objectiveId !== workItem.objectiveId ||
|
|
89
|
+
boundObjectiveRecord.messageId !==
|
|
90
|
+
workItem.objectivePolicy.acceptedMessageId ||
|
|
91
|
+
boundObjectiveRecord.acceptedAt !== workItem.objectivePolicy.acceptedAt) {
|
|
92
|
+
throw new TypeError("Mesh Work Item Objective binding is invalid");
|
|
93
|
+
}
|
|
94
|
+
if (workItem.status !== "ready")
|
|
95
|
+
continue;
|
|
96
|
+
expectedManagedTimerIds.add(workItem.expiryTimerId);
|
|
97
|
+
const timer = coordination.timers[workItem.expiryTimerId];
|
|
98
|
+
if (!timer ||
|
|
99
|
+
timer.kind !== "work.deadline" ||
|
|
100
|
+
timer.generation !== workItem.expiryTimerGeneration ||
|
|
101
|
+
timer.dueAt !== workItem.workDeadlineAt ||
|
|
102
|
+
timer.domainRecordKey !== boundObjectiveRecordKey)
|
|
103
|
+
throw new TypeError("Mesh Work Item deadline timer binding is invalid");
|
|
104
|
+
}
|
|
105
|
+
if (Object.values(coordination.timers).some((timer) => (timer.kind === "objective.expiry" || timer.kind === "work.deadline") &&
|
|
106
|
+
!expectedManagedTimerIds.has(timer.timerId))) {
|
|
107
|
+
throw new TypeError("Mesh Objective/Work runtime has an orphan timer");
|
|
108
|
+
}
|
|
109
|
+
return Object.freeze({ coordination, discovery, objectives });
|
|
110
|
+
}
|
|
111
|
+
/** Applies one already-verified Objective envelope with no key or network I/O. */
|
|
112
|
+
export function evaluateVerifiedMeshObjectiveEnvelope(state, request) {
|
|
113
|
+
assertRuntimeState(state);
|
|
114
|
+
assertObjectiveRequest(request);
|
|
115
|
+
assertMonotonic(state, request.receivedAt);
|
|
116
|
+
const contextFailure = validateContext(state, request);
|
|
117
|
+
if (contextFailure)
|
|
118
|
+
return reject(state, contextFailure);
|
|
119
|
+
const envelope = request.envelope;
|
|
120
|
+
const issuer = state.objectives.issuerAuthorities[envelope.sender.peerId];
|
|
121
|
+
if (!issuer) {
|
|
122
|
+
return reject(state, "issuer_not_authorized");
|
|
123
|
+
}
|
|
124
|
+
if (!issuer.keyIds.includes(envelope.proof.keyId))
|
|
125
|
+
return reject(state, "issuer_key_not_authorized");
|
|
126
|
+
if (compare(request.verifiedAt, issuer.validUntil) >= 0) {
|
|
127
|
+
return reject(state, "issuer_authority_expired");
|
|
128
|
+
}
|
|
129
|
+
if (envelope.payload.type === "objective.cancel") {
|
|
130
|
+
const current = state.objectives.objectives[envelope.payload.objectiveId];
|
|
131
|
+
if (!current || current.issuerPeerId !== envelope.sender.peerId) {
|
|
132
|
+
return reject(state, "issuer_not_authorized");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const metadata = domainMetadata(envelope);
|
|
136
|
+
const existing = state.coordination.domainRecords[metadata.recordKey];
|
|
137
|
+
if (existing) {
|
|
138
|
+
return existing.contentDigest === metadata.contentDigest &&
|
|
139
|
+
existing.objectiveId === envelope.payload.objectiveId &&
|
|
140
|
+
existing.messageId === envelope.messageId
|
|
141
|
+
? Object.freeze({ accepted: true, duplicate: true, state })
|
|
142
|
+
: reject(state, "domain_record_conflict");
|
|
143
|
+
}
|
|
144
|
+
if (Object.values(state.coordination.domainRecords).some((record) => record.messageId === envelope.messageId)) {
|
|
145
|
+
return reject(state, "domain_record_conflict");
|
|
146
|
+
}
|
|
147
|
+
if (Object.keys(state.coordination.domainRecords).length >=
|
|
148
|
+
state.coordination.limits.maximumDomainRecords)
|
|
149
|
+
return reject(state, "domain_capacity_exceeded");
|
|
150
|
+
if (state.coordination.journal.length >=
|
|
151
|
+
state.coordination.limits.maximumJournalEntries)
|
|
152
|
+
return reject(state, "journal_capacity_exceeded");
|
|
153
|
+
const evaluated = evaluateObjectivePayload(state, request, metadata.recordKey);
|
|
154
|
+
if ("code" in evaluated)
|
|
155
|
+
return reject(state, evaluated.code);
|
|
156
|
+
if (state.coordination.localEventSequence >= Number.MAX_SAFE_INTEGER) {
|
|
157
|
+
throw new RangeError("Mesh coordination event sequence exhausted");
|
|
158
|
+
}
|
|
159
|
+
if (evaluated.timer &&
|
|
160
|
+
Object.hasOwn(state.coordination.timers, evaluated.timer.timerId) &&
|
|
161
|
+
evaluated.removeTimerId !== evaluated.timer.timerId) {
|
|
162
|
+
return reject(state, "timer_id_conflict");
|
|
163
|
+
}
|
|
164
|
+
if (evaluated.timer &&
|
|
165
|
+
!Object.hasOwn(state.coordination.timers, evaluated.timer.timerId) &&
|
|
166
|
+
Object.keys(state.coordination.timers).length >=
|
|
167
|
+
state.coordination.limits.maximumTimers) {
|
|
168
|
+
return reject(state, "timer_capacity_exceeded");
|
|
169
|
+
}
|
|
170
|
+
const sequence = state.coordination.localEventSequence + 1;
|
|
171
|
+
const record = Object.freeze({
|
|
172
|
+
recordKey: metadata.recordKey,
|
|
173
|
+
recordType: envelope.payload.type,
|
|
174
|
+
recordId: metadata.recordId,
|
|
175
|
+
contentDigest: metadata.contentDigest,
|
|
176
|
+
messageId: envelope.messageId,
|
|
177
|
+
acceptedAt: request.receivedAt,
|
|
178
|
+
objectiveId: envelope.payload.objectiveId,
|
|
179
|
+
});
|
|
180
|
+
const journal = Object.freeze({
|
|
181
|
+
sequence,
|
|
182
|
+
occurredAt: request.receivedAt,
|
|
183
|
+
kind: "domain.accepted",
|
|
184
|
+
domainRecordKey: record.recordKey,
|
|
185
|
+
});
|
|
186
|
+
const coordination = Object.freeze({
|
|
187
|
+
...state.coordination,
|
|
188
|
+
domainRecords: createFrozenRecord([
|
|
189
|
+
...recordEntries(state.coordination.domainRecords),
|
|
190
|
+
[record.recordKey, record],
|
|
191
|
+
]),
|
|
192
|
+
timers: evaluated.timer
|
|
193
|
+
? createFrozenRecord([
|
|
194
|
+
...recordEntries(state.coordination.timers).filter(([timerId]) => timerId !== evaluated.timer?.timerId &&
|
|
195
|
+
timerId !== evaluated.removeTimerId),
|
|
196
|
+
[evaluated.timer.timerId, evaluated.timer],
|
|
197
|
+
])
|
|
198
|
+
: evaluated.removeTimerId
|
|
199
|
+
? createFrozenRecord(recordEntries(state.coordination.timers).filter(([timerId]) => timerId !== evaluated.removeTimerId))
|
|
200
|
+
: state.coordination.timers,
|
|
201
|
+
journal: Object.freeze([...state.coordination.journal, journal]),
|
|
202
|
+
localEventSequence: sequence,
|
|
203
|
+
lastLogicalTime: request.receivedAt,
|
|
204
|
+
});
|
|
205
|
+
const objectives = Object.freeze({
|
|
206
|
+
...state.objectives,
|
|
207
|
+
objectives: createFrozenRecord(evaluated.objectives),
|
|
208
|
+
objectiveDocuments: evaluated.document === undefined
|
|
209
|
+
? state.objectives.objectiveDocuments
|
|
210
|
+
: createFrozenRecord([
|
|
211
|
+
...recordEntries(state.objectives.objectiveDocuments),
|
|
212
|
+
[
|
|
213
|
+
objectivePolicyKey(evaluated.document.envelope.payload.objectiveId, evaluated.document.envelope.payload.objectiveRevision),
|
|
214
|
+
evaluated.document,
|
|
215
|
+
],
|
|
216
|
+
]),
|
|
217
|
+
objectivePolicies: evaluated.policy === undefined
|
|
218
|
+
? state.objectives.objectivePolicies
|
|
219
|
+
: createFrozenRecord([
|
|
220
|
+
...recordEntries(state.objectives.objectivePolicies),
|
|
221
|
+
[
|
|
222
|
+
objectivePolicyKey(evaluated.policy.objectiveId, evaluated.policy.objectiveRevision),
|
|
223
|
+
evaluated.policy,
|
|
224
|
+
],
|
|
225
|
+
]),
|
|
226
|
+
lastLogicalTime: request.receivedAt,
|
|
227
|
+
});
|
|
228
|
+
return Object.freeze({
|
|
229
|
+
accepted: true,
|
|
230
|
+
duplicate: false,
|
|
231
|
+
state: createMeshObjectiveWorkRuntimeState(coordination, Object.freeze({
|
|
232
|
+
...state.discovery,
|
|
233
|
+
lastLogicalTime: request.receivedAt,
|
|
234
|
+
}), objectives),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/** Consumes exactly one generation-fenced Objective or Work deadline timer. */
|
|
238
|
+
export function evaluateMeshObjectiveWorkTimer(state, input, logicalTime) {
|
|
239
|
+
assertRuntimeState(state);
|
|
240
|
+
assertMeshLogicalTime(logicalTime);
|
|
241
|
+
assertMonotonic(state, logicalTime);
|
|
242
|
+
if (!isPlainDataRecord(input) ||
|
|
243
|
+
Object.keys(input).length !== 3 ||
|
|
244
|
+
Object.keys(input).some((key) => !["kind", "timerId", "generation"].includes(key)) ||
|
|
245
|
+
input.kind !== "timer.fired" ||
|
|
246
|
+
typeof input.timerId !== "string" ||
|
|
247
|
+
!identifierPattern.test(input.timerId) ||
|
|
248
|
+
utf8Encoder.encode(input.timerId).byteLength >
|
|
249
|
+
maximumTimerIdentifierBytes ||
|
|
250
|
+
!Number.isSafeInteger(input.generation) ||
|
|
251
|
+
input.generation < 1) {
|
|
252
|
+
throw new TypeError("Invalid Mesh Objective/Work timer input");
|
|
253
|
+
}
|
|
254
|
+
const timer = state.coordination.timers[input.timerId];
|
|
255
|
+
if (!timer)
|
|
256
|
+
return timerRejection(state, "timer_unknown");
|
|
257
|
+
if (timer.generation !== input.generation) {
|
|
258
|
+
return timerRejection(state, "timer_generation_stale");
|
|
259
|
+
}
|
|
260
|
+
if (logicalTime < timer.dueAt)
|
|
261
|
+
return timerRejection(state, "timer_not_due");
|
|
262
|
+
if (state.coordination.journal.length >=
|
|
263
|
+
state.coordination.limits.maximumJournalEntries) {
|
|
264
|
+
return timerRejection(state, "journal_capacity_exceeded");
|
|
265
|
+
}
|
|
266
|
+
if (state.coordination.localEventSequence >= Number.MAX_SAFE_INTEGER) {
|
|
267
|
+
throw new RangeError("Mesh coordination event sequence exhausted");
|
|
268
|
+
}
|
|
269
|
+
const objective = Object.values(state.objectives.objectives).find((entry) => entry.status === "active" && entry.expiryTimerId === timer.timerId);
|
|
270
|
+
const workItem = Object.values(state.objectives.workItems).find((entry) => entry.status === "ready" && entry.expiryTimerId === timer.timerId);
|
|
271
|
+
if ((timer.kind === "objective.expiry" &&
|
|
272
|
+
(!objective || objective.expiryTimerGeneration !== timer.generation)) ||
|
|
273
|
+
(timer.kind === "work.deadline" &&
|
|
274
|
+
(!workItem || workItem.expiryTimerGeneration !== timer.generation)) ||
|
|
275
|
+
(timer.kind !== "objective.expiry" && timer.kind !== "work.deadline")) {
|
|
276
|
+
throw new TypeError("Mesh Objective/Work timer binding is invalid");
|
|
277
|
+
}
|
|
278
|
+
const journal = Object.freeze({
|
|
279
|
+
sequence: state.coordination.localEventSequence + 1,
|
|
280
|
+
occurredAt: logicalTime,
|
|
281
|
+
kind: "timer.fired",
|
|
282
|
+
domainRecordKey: timer.domainRecordKey,
|
|
283
|
+
timerId: timer.timerId,
|
|
284
|
+
});
|
|
285
|
+
const coordination = Object.freeze({
|
|
286
|
+
...state.coordination,
|
|
287
|
+
timers: createFrozenRecord(recordEntries(state.coordination.timers).filter(([timerId]) => timerId !== timer.timerId)),
|
|
288
|
+
journal: Object.freeze([...state.coordination.journal, journal]),
|
|
289
|
+
localEventSequence: state.coordination.localEventSequence + 1,
|
|
290
|
+
lastLogicalTime: logicalTime,
|
|
291
|
+
});
|
|
292
|
+
const objectives = Object.freeze({
|
|
293
|
+
...state.objectives,
|
|
294
|
+
objectives: objective
|
|
295
|
+
? createFrozenRecord(recordEntries(state.objectives.objectives).map(([key, entry]) => [
|
|
296
|
+
key,
|
|
297
|
+
entry === objective
|
|
298
|
+
? freezeObjective({
|
|
299
|
+
...entry,
|
|
300
|
+
status: "expired",
|
|
301
|
+
expiryTimerId: undefined,
|
|
302
|
+
expiryTimerGeneration: undefined,
|
|
303
|
+
terminalAt: logicalTime,
|
|
304
|
+
})
|
|
305
|
+
: entry,
|
|
306
|
+
]))
|
|
307
|
+
: state.objectives.objectives,
|
|
308
|
+
workItems: workItem
|
|
309
|
+
? createFrozenRecord(recordEntries(state.objectives.workItems).map(([key, entry]) => [
|
|
310
|
+
key,
|
|
311
|
+
entry === workItem
|
|
312
|
+
? freezeWorkItem({
|
|
313
|
+
...entry,
|
|
314
|
+
status: "expired",
|
|
315
|
+
expiryTimerId: undefined,
|
|
316
|
+
expiryTimerGeneration: undefined,
|
|
317
|
+
terminalAt: logicalTime,
|
|
318
|
+
updatedAt: logicalTime,
|
|
319
|
+
})
|
|
320
|
+
: entry,
|
|
321
|
+
]))
|
|
322
|
+
: state.objectives.workItems,
|
|
323
|
+
lastLogicalTime: logicalTime,
|
|
324
|
+
});
|
|
325
|
+
return Object.freeze({
|
|
326
|
+
accepted: true,
|
|
327
|
+
timer,
|
|
328
|
+
state: createMeshObjectiveWorkRuntimeState(coordination, Object.freeze({ ...state.discovery, lastLogicalTime: logicalTime }), objectives),
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
/** Evaluates a local Work command using caller-injected trusted time only. */
|
|
332
|
+
export function evaluateMeshObjectiveWorkCommand(state, command, time) {
|
|
333
|
+
assertRuntimeState(state);
|
|
334
|
+
assertTrustedTime(time);
|
|
335
|
+
assertMonotonic(state, time.receivedAt);
|
|
336
|
+
assertCommand(command);
|
|
337
|
+
if (command.kind !== "work.cancel") {
|
|
338
|
+
assertWorkInput(command.input, state.objectives);
|
|
339
|
+
}
|
|
340
|
+
if (state.coordination.journal.length >=
|
|
341
|
+
state.coordination.limits.maximumJournalEntries)
|
|
342
|
+
return reject(state, "journal_capacity_exceeded");
|
|
343
|
+
if (state.coordination.localEventSequence >= Number.MAX_SAFE_INTEGER)
|
|
344
|
+
throw new RangeError("Mesh coordination event sequence exhausted");
|
|
345
|
+
const result = command.kind === "work.cancel"
|
|
346
|
+
? cancelWork(state, command, time)
|
|
347
|
+
: upsertWork(state, command, time);
|
|
348
|
+
if ("code" in result)
|
|
349
|
+
return reject(state, result.code);
|
|
350
|
+
if (result.workItem.status === "ready" &&
|
|
351
|
+
Object.hasOwn(state.coordination.timers, result.workItem.expiryTimerId) &&
|
|
352
|
+
result.previousTimerId !== result.workItem.expiryTimerId)
|
|
353
|
+
return reject(state, "timer_id_conflict");
|
|
354
|
+
const sequence = state.coordination.localEventSequence + 1;
|
|
355
|
+
const journal = Object.freeze({
|
|
356
|
+
sequence,
|
|
357
|
+
occurredAt: time.receivedAt,
|
|
358
|
+
kind: "command.accepted",
|
|
359
|
+
domainRecordKey: objectiveDomainRecordKey(result.objective),
|
|
360
|
+
});
|
|
361
|
+
const nextTimers = result.workItem.status === "cancelled"
|
|
362
|
+
? recordEntries(state.coordination.timers).filter(([timerId]) => timerId !== result.previousTimerId)
|
|
363
|
+
: [
|
|
364
|
+
...recordEntries(state.coordination.timers).filter(([timerId]) => timerId !== result.previousTimerId),
|
|
365
|
+
[
|
|
366
|
+
result.workItem.expiryTimerId,
|
|
367
|
+
Object.freeze({
|
|
368
|
+
timerId: result.workItem.expiryTimerId,
|
|
369
|
+
kind: "work.deadline",
|
|
370
|
+
dueAt: result.workItem.workDeadlineAt,
|
|
371
|
+
generation: result.workItem.expiryTimerGeneration,
|
|
372
|
+
domainRecordKey: objectiveDomainRecordKey(result.objective),
|
|
373
|
+
}),
|
|
374
|
+
],
|
|
375
|
+
];
|
|
376
|
+
if (result.workItem.status === "ready" &&
|
|
377
|
+
!Object.hasOwn(state.coordination.timers, result.workItem.expiryTimerId) &&
|
|
378
|
+
Object.keys(state.coordination.timers).length >=
|
|
379
|
+
state.coordination.limits.maximumTimers)
|
|
380
|
+
return reject(state, "timer_capacity_exceeded");
|
|
381
|
+
const coordination = Object.freeze({
|
|
382
|
+
...state.coordination,
|
|
383
|
+
timers: createFrozenRecord(nextTimers),
|
|
384
|
+
journal: Object.freeze([...state.coordination.journal, journal]),
|
|
385
|
+
localEventSequence: sequence,
|
|
386
|
+
lastLogicalTime: time.receivedAt,
|
|
387
|
+
});
|
|
388
|
+
const countedObjective = command.kind === "work.create"
|
|
389
|
+
? freezeObjective({
|
|
390
|
+
...result.objective,
|
|
391
|
+
workItemCount: result.objective.workItemCount + 1,
|
|
392
|
+
})
|
|
393
|
+
: result.objective;
|
|
394
|
+
const objectives = Object.freeze({
|
|
395
|
+
...state.objectives,
|
|
396
|
+
objectives: createFrozenRecord([
|
|
397
|
+
...recordEntries(state.objectives.objectives).filter(([key]) => key !== countedObjective.objectiveId),
|
|
398
|
+
[countedObjective.objectiveId, countedObjective],
|
|
399
|
+
]),
|
|
400
|
+
workItems: createFrozenRecord(result.workItems),
|
|
401
|
+
lastLogicalTime: time.receivedAt,
|
|
402
|
+
});
|
|
403
|
+
return Object.freeze({
|
|
404
|
+
accepted: true,
|
|
405
|
+
duplicate: false,
|
|
406
|
+
state: createMeshObjectiveWorkRuntimeState(coordination, Object.freeze({ ...state.discovery, lastLogicalTime: time.receivedAt }), objectives),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function evaluateObjectivePayload(state, request, recordKey) {
|
|
410
|
+
const payload = request.envelope.payload;
|
|
411
|
+
const current = state.objectives.objectives[payload.objectiveId];
|
|
412
|
+
if (payload.type === "objective.cancel")
|
|
413
|
+
return evaluateCancel(state, request, current, recordKey);
|
|
414
|
+
const expiry = logicalExpiry(payload.validFrom, payload.validUntil, request.verifiedAt, request.receivedAt);
|
|
415
|
+
if (!expiry.ok) {
|
|
416
|
+
return {
|
|
417
|
+
code: expiry.reason === "outside_window"
|
|
418
|
+
? "message_expired"
|
|
419
|
+
: "objective_limit_exceeded",
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
if (!current) {
|
|
423
|
+
if (payload.type !== "objective.announce" ||
|
|
424
|
+
payload.objectiveRevision !== 1 ||
|
|
425
|
+
request.envelope.causationId !== undefined)
|
|
426
|
+
return { code: "objective_revision_invalid" };
|
|
427
|
+
if (Object.keys(state.objectives.objectives).length >=
|
|
428
|
+
state.objectives.limits.maximumObjectives)
|
|
429
|
+
return { code: "objective_capacity_exceeded" };
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
if (current.status !== "active")
|
|
433
|
+
return { code: "objective_terminal" };
|
|
434
|
+
if (current.issuerPeerId !== request.envelope.sender.peerId ||
|
|
435
|
+
compare(request.verifiedAt, current.validityVerifiedAt) < 0 ||
|
|
436
|
+
compare(request.verifiedAt, current.validUntil) >= 0 ||
|
|
437
|
+
request.receivedAt >= current.expiresAt) {
|
|
438
|
+
return {
|
|
439
|
+
code: current.issuerPeerId !== request.envelope.sender.peerId
|
|
440
|
+
? "issuer_not_authorized"
|
|
441
|
+
: "objective_terminal",
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
if (payload.type !== "objective.revise" ||
|
|
445
|
+
payload.objectiveRevision !== current.objectiveRevision + 1)
|
|
446
|
+
return { code: "objective_revision_invalid" };
|
|
447
|
+
if (payload.previousObjectiveDocumentId !== current.objectiveDocumentId ||
|
|
448
|
+
request.envelope.causationId !== current.acceptedMessageId)
|
|
449
|
+
return { code: "objective_predecessor_invalid" };
|
|
450
|
+
}
|
|
451
|
+
if (current?.expiryTimerGeneration === Number.MAX_SAFE_INTEGER)
|
|
452
|
+
return { code: "timer_generation_exhausted" };
|
|
453
|
+
if (Object.keys(state.objectives.objectivePolicies).length >=
|
|
454
|
+
state.objectives.limits.maximumObjectivePolicies) {
|
|
455
|
+
return { code: "objective_policy_capacity_exceeded" };
|
|
456
|
+
}
|
|
457
|
+
if (Object.values(state.objectives.objectives).some((entry) => entry.objectiveId !== payload.objectiveId &&
|
|
458
|
+
(entry.objectiveDocumentId === payload.objectiveDocumentId ||
|
|
459
|
+
entry.acceptedMessageId === request.envelope.messageId)) ||
|
|
460
|
+
Object.values(state.objectives.objectivePolicies).some((entry) => entry.objectiveDocumentId === payload.objectiveDocumentId ||
|
|
461
|
+
entry.acceptedMessageId === request.envelope.messageId)) {
|
|
462
|
+
return { code: "domain_record_conflict" };
|
|
463
|
+
}
|
|
464
|
+
const timerId = objectiveTimerId(payload.objectiveId);
|
|
465
|
+
const nextGeneration = (current?.expiryTimerGeneration ?? 0) + 1;
|
|
466
|
+
const currentWorkItemCount = current?.workItemCount ?? 0;
|
|
467
|
+
const currentReservedBudgetUnits = current?.reservedBudgetUnits ?? 0;
|
|
468
|
+
const currentCommittedBudgetUnits = current?.committedBudgetUnits ?? 0;
|
|
469
|
+
if (current &&
|
|
470
|
+
(payload.maximumWorkItems < currentWorkItemCount ||
|
|
471
|
+
payload.maximumBudgetUnits <
|
|
472
|
+
currentReservedBudgetUnits + currentCommittedBudgetUnits))
|
|
473
|
+
return { code: "objective_limit_below_committed" };
|
|
474
|
+
const objectiveCandidate = {
|
|
475
|
+
objectiveId: payload.objectiveId,
|
|
476
|
+
objectiveDocumentId: payload.objectiveDocumentId,
|
|
477
|
+
objectiveRevision: payload.objectiveRevision,
|
|
478
|
+
issuerPeerId: payload.issuerPeerId,
|
|
479
|
+
issuerKeyId: request.envelope.proof.keyId,
|
|
480
|
+
...(payload.summary === undefined
|
|
481
|
+
? { contentReference: payload.contentReference }
|
|
482
|
+
: { summary: payload.summary }),
|
|
483
|
+
successCriteria: payload.successCriteria,
|
|
484
|
+
permittedCapabilityKeys: payload.permittedCapabilityKeys,
|
|
485
|
+
maximumWorkItems: payload.maximumWorkItems,
|
|
486
|
+
maximumConcurrentAssignments: payload.maximumConcurrentAssignments,
|
|
487
|
+
maximumBudgetUnits: payload.maximumBudgetUnits,
|
|
488
|
+
bidWindowMs: payload.bidWindowMs,
|
|
489
|
+
acceptanceWindowMs: payload.acceptanceWindowMs,
|
|
490
|
+
maximumLeaseDurationMs: payload.maximumLeaseDurationMs,
|
|
491
|
+
recoveryGraceMs: payload.recoveryGraceMs,
|
|
492
|
+
maximumLeaseRenewals: payload.maximumLeaseRenewals,
|
|
493
|
+
recoveryWitnessPeerIds: payload.recoveryWitnessPeerIds,
|
|
494
|
+
recoveryWitnessThreshold: payload.recoveryWitnessThreshold,
|
|
495
|
+
validFrom: payload.validFrom,
|
|
496
|
+
validUntil: payload.validUntil,
|
|
497
|
+
...(payload.authorizedObserverPeerIds === undefined
|
|
498
|
+
? {}
|
|
499
|
+
: { authorizedObserverPeerIds: payload.authorizedObserverPeerIds }),
|
|
500
|
+
validityVerifiedAt: request.verifiedAt,
|
|
501
|
+
acceptedMessageId: request.envelope.messageId,
|
|
502
|
+
acceptedAt: request.receivedAt,
|
|
503
|
+
expiresAt: expiry.at,
|
|
504
|
+
expiryTimerId: timerId,
|
|
505
|
+
expiryTimerGeneration: nextGeneration,
|
|
506
|
+
workItemCount: currentWorkItemCount,
|
|
507
|
+
reservedBudgetUnits: currentReservedBudgetUnits,
|
|
508
|
+
committedBudgetUnits: currentCommittedBudgetUnits,
|
|
509
|
+
status: "active",
|
|
510
|
+
};
|
|
511
|
+
if (!objectiveWithinLocalLimits(objectiveCandidate, state.objectives)) {
|
|
512
|
+
return { code: "objective_limit_exceeded" };
|
|
513
|
+
}
|
|
514
|
+
const objective = freezeObjective(objectiveCandidate);
|
|
515
|
+
const policy = freezeObjectivePolicy(objective);
|
|
516
|
+
const document = freezeAcceptedObjectiveDocument(request, expiry.at);
|
|
517
|
+
if (utf8Encoder.encode(JSON.stringify(document)).byteLength >
|
|
518
|
+
state.objectives.limits.maximumProjectionBytes) {
|
|
519
|
+
return { code: "objective_limit_exceeded" };
|
|
520
|
+
}
|
|
521
|
+
const timer = Object.freeze({
|
|
522
|
+
timerId,
|
|
523
|
+
kind: "objective.expiry",
|
|
524
|
+
dueAt: expiry.at,
|
|
525
|
+
generation: nextGeneration,
|
|
526
|
+
domainRecordKey: recordKey,
|
|
527
|
+
});
|
|
528
|
+
return {
|
|
529
|
+
objectives: [
|
|
530
|
+
...recordEntries(state.objectives.objectives).filter(([key]) => key !== payload.objectiveId),
|
|
531
|
+
[payload.objectiveId, objective],
|
|
532
|
+
],
|
|
533
|
+
timer,
|
|
534
|
+
document,
|
|
535
|
+
policy,
|
|
536
|
+
...(current === undefined ? {} : { removeTimerId: current.expiryTimerId }),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function evaluateCancel(state, request, current, recordKey) {
|
|
540
|
+
const payload = request.envelope.payload;
|
|
541
|
+
if (!current ||
|
|
542
|
+
current.status !== "active" ||
|
|
543
|
+
compare(request.verifiedAt, current.validityVerifiedAt) < 0 ||
|
|
544
|
+
compare(request.verifiedAt, current.validUntil) >= 0 ||
|
|
545
|
+
request.receivedAt >= current.expiresAt ||
|
|
546
|
+
payload.objectiveRevision !== current.objectiveRevision ||
|
|
547
|
+
payload.objectiveDocumentId !== current.objectiveDocumentId ||
|
|
548
|
+
request.envelope.causationId !== current.acceptedMessageId)
|
|
549
|
+
return { code: "objective_cancel_invalid" };
|
|
550
|
+
const terminal = freezeObjective({
|
|
551
|
+
...current,
|
|
552
|
+
status: "cancelled",
|
|
553
|
+
expiryTimerId: undefined,
|
|
554
|
+
expiryTimerGeneration: undefined,
|
|
555
|
+
terminalRecordKey: recordKey,
|
|
556
|
+
terminalCancellation: freezeAcceptedObjectiveCancellation(request),
|
|
557
|
+
terminalAt: request.receivedAt,
|
|
558
|
+
});
|
|
559
|
+
if (utf8Encoder.encode(JSON.stringify(terminal)).byteLength >
|
|
560
|
+
state.objectives.limits.maximumProjectionBytes) {
|
|
561
|
+
return { code: "objective_limit_exceeded" };
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
objectives: [
|
|
565
|
+
...recordEntries(state.objectives.objectives).filter(([key]) => key !== current.objectiveId),
|
|
566
|
+
[current.objectiveId, terminal],
|
|
567
|
+
],
|
|
568
|
+
removeTimerId: current.expiryTimerId,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function upsertWork(state, command, time) {
|
|
572
|
+
const input = command.input;
|
|
573
|
+
if (!Number.isSafeInteger(input.budgetReservationUnits) ||
|
|
574
|
+
input.budgetReservationUnits < 0)
|
|
575
|
+
return { code: "work_limit_exceeded" };
|
|
576
|
+
const objective = state.objectives.objectives[input.objectiveId];
|
|
577
|
+
if (!objective)
|
|
578
|
+
return { code: "work_objective_missing" };
|
|
579
|
+
if (compare(time.verifiedAt, objective.validityVerifiedAt) < 0) {
|
|
580
|
+
throw new RangeError("Mesh Objective/Work trusted time cannot move backwards");
|
|
581
|
+
}
|
|
582
|
+
if (objective.status !== "active" ||
|
|
583
|
+
objective.expiresAt <= time.receivedAt ||
|
|
584
|
+
compare(time.verifiedAt, objective.validUntil) >= 0)
|
|
585
|
+
return { code: "work_objective_not_active" };
|
|
586
|
+
const key = workKey(input.objectiveId, input.workItemId);
|
|
587
|
+
const current = state.objectives.workItems[key];
|
|
588
|
+
if (command.kind === "work.create") {
|
|
589
|
+
if (current)
|
|
590
|
+
return { code: "work_duplicate_conflict" };
|
|
591
|
+
if (Object.keys(state.objectives.workItems).length >=
|
|
592
|
+
state.objectives.limits.maximumWorkItems ||
|
|
593
|
+
Object.values(state.objectives.workItems).filter((entry) => entry.objectiveId === input.objectiveId).length >=
|
|
594
|
+
Math.min(state.objectives.limits.maximumWorkItemsPerObjective, objective.maximumWorkItems))
|
|
595
|
+
return { code: "work_capacity_exceeded" };
|
|
596
|
+
}
|
|
597
|
+
else if (!current ||
|
|
598
|
+
current.status !== "ready" ||
|
|
599
|
+
command.expectedWorkItemRevision !== current.workItemRevision)
|
|
600
|
+
return { code: "work_revision_invalid" };
|
|
601
|
+
if (current?.workItemRevision === Number.MAX_SAFE_INTEGER ||
|
|
602
|
+
current?.expiryTimerGeneration === Number.MAX_SAFE_INTEGER)
|
|
603
|
+
return {
|
|
604
|
+
code: current.workItemRevision === Number.MAX_SAFE_INTEGER
|
|
605
|
+
? "work_revision_invalid"
|
|
606
|
+
: "timer_generation_exhausted",
|
|
607
|
+
};
|
|
608
|
+
if (input.budgetReservationUnits > objective.maximumBudgetUnits ||
|
|
609
|
+
input.requiredCapabilityKeys.some((key) => !objective.permittedCapabilityKeys.includes(key)))
|
|
610
|
+
return { code: "work_limit_exceeded" };
|
|
611
|
+
const deadline = logicalDeadline(input.workDeadline, time.verifiedAt, time.receivedAt);
|
|
612
|
+
if (deadline === undefined ||
|
|
613
|
+
compare(input.workDeadline, objective.validUntil) > 0 ||
|
|
614
|
+
deadline > objective.expiresAt)
|
|
615
|
+
return { code: "work_limit_exceeded" };
|
|
616
|
+
const workItemCandidate = {
|
|
617
|
+
objectiveId: objective.objectiveId,
|
|
618
|
+
objectiveDocumentId: objective.objectiveDocumentId,
|
|
619
|
+
objectiveRevision: objective.objectiveRevision,
|
|
620
|
+
objectivePolicy: state.objectives.objectivePolicies[objectivePolicyKey(objective.objectiveId, objective.objectiveRevision)],
|
|
621
|
+
workItemId: input.workItemId,
|
|
622
|
+
workItemRevision: command.kind === "work.create"
|
|
623
|
+
? 1
|
|
624
|
+
: current.workItemRevision + 1,
|
|
625
|
+
ownerPeerId: state.objectives.identity.peerId,
|
|
626
|
+
ownerEpoch: 1,
|
|
627
|
+
requiredCapabilityKeys: input.requiredCapabilityKeys,
|
|
628
|
+
matchingAttributes: input.matchingAttributes ?? Object.create(null),
|
|
629
|
+
completionCriteria: input.completionCriteria,
|
|
630
|
+
...(input.inputSummary === undefined
|
|
631
|
+
? { inputReference: input.inputReference }
|
|
632
|
+
: { inputSummary: input.inputSummary }),
|
|
633
|
+
budgetReservationUnits: input.budgetReservationUnits,
|
|
634
|
+
workDeadline: input.workDeadline,
|
|
635
|
+
workDeadlineAt: deadline,
|
|
636
|
+
offerAttempt: 0,
|
|
637
|
+
expiryTimerId: workTimerId(input.objectiveId, input.workItemId),
|
|
638
|
+
expiryTimerGeneration: (current?.expiryTimerGeneration ?? 0) + 1,
|
|
639
|
+
status: "ready",
|
|
640
|
+
createdAt: current?.createdAt ?? time.receivedAt,
|
|
641
|
+
updatedAt: time.receivedAt,
|
|
642
|
+
};
|
|
643
|
+
if (utf8Encoder.encode(JSON.stringify(workItemCandidate)).byteLength >
|
|
644
|
+
state.objectives.limits.maximumProjectionBytes)
|
|
645
|
+
return { code: "work_limit_exceeded" };
|
|
646
|
+
const workItem = freezeWorkItem(workItemCandidate);
|
|
647
|
+
return {
|
|
648
|
+
objective,
|
|
649
|
+
workItem,
|
|
650
|
+
...(current === undefined
|
|
651
|
+
? {}
|
|
652
|
+
: { previousTimerId: current.expiryTimerId }),
|
|
653
|
+
workItems: [
|
|
654
|
+
...recordEntries(state.objectives.workItems).filter(([entryKey]) => entryKey !== key),
|
|
655
|
+
[key, workItem],
|
|
656
|
+
],
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function cancelWork(state, command, time) {
|
|
660
|
+
const objective = state.objectives.objectives[command.objectiveId];
|
|
661
|
+
const key = workKey(command.objectiveId, command.workItemId);
|
|
662
|
+
const current = state.objectives.workItems[key];
|
|
663
|
+
if (!objective)
|
|
664
|
+
return { code: "work_objective_missing" };
|
|
665
|
+
if (compare(time.verifiedAt, objective.validityVerifiedAt) < 0) {
|
|
666
|
+
throw new RangeError("Mesh Objective/Work trusted time cannot move backwards");
|
|
667
|
+
}
|
|
668
|
+
if (!current ||
|
|
669
|
+
current.status !== "ready" ||
|
|
670
|
+
current.ownerPeerId !== state.objectives.identity.peerId ||
|
|
671
|
+
current.workItemRevision !== command.expectedWorkItemRevision)
|
|
672
|
+
return { code: "work_revision_invalid" };
|
|
673
|
+
const cancelled = freezeWorkItem({
|
|
674
|
+
...current,
|
|
675
|
+
status: "cancelled",
|
|
676
|
+
expiryTimerId: undefined,
|
|
677
|
+
expiryTimerGeneration: undefined,
|
|
678
|
+
terminalAt: time.receivedAt,
|
|
679
|
+
updatedAt: time.receivedAt,
|
|
680
|
+
});
|
|
681
|
+
return {
|
|
682
|
+
objective,
|
|
683
|
+
workItem: cancelled,
|
|
684
|
+
previousTimerId: current.expiryTimerId,
|
|
685
|
+
workItems: [
|
|
686
|
+
...recordEntries(state.objectives.workItems).filter(([entryKey]) => entryKey !== key),
|
|
687
|
+
[key, cancelled],
|
|
688
|
+
],
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function validateContext(state, request) {
|
|
692
|
+
const result = validateMeshEnvelopeContext(request.envelope, {
|
|
693
|
+
tenantId: state.objectives.identity.tenantId,
|
|
694
|
+
meshId: state.objectives.identity.meshId,
|
|
695
|
+
peerId: state.objectives.identity.peerId,
|
|
696
|
+
receivedAt: request.verifiedAt,
|
|
697
|
+
subscribedTopics: state.discovery.subscriptions,
|
|
698
|
+
...(request.supportedCriticalExtensions === undefined
|
|
699
|
+
? {}
|
|
700
|
+
: { supportedCriticalExtensions: request.supportedCriticalExtensions }),
|
|
701
|
+
});
|
|
702
|
+
if (!result.ok) {
|
|
703
|
+
const code = result.issues[0]?.code;
|
|
704
|
+
if (code === "scope_mismatch")
|
|
705
|
+
return "scope_mismatch";
|
|
706
|
+
if (code === "message_expired")
|
|
707
|
+
return "message_expired";
|
|
708
|
+
if (code === "message_from_future")
|
|
709
|
+
return "message_from_future";
|
|
710
|
+
if (code === "unknown_critical_extension")
|
|
711
|
+
return "unknown_critical_extension";
|
|
712
|
+
if (code === "invalid_audience") {
|
|
713
|
+
return request.envelope.audience.kind === "mesh" &&
|
|
714
|
+
!state.discovery.subscriptions.includes("objective")
|
|
715
|
+
? "topic_not_subscribed"
|
|
716
|
+
: "audience_mismatch";
|
|
717
|
+
}
|
|
718
|
+
return "invalid_verified_envelope";
|
|
719
|
+
}
|
|
720
|
+
if (request.envelope.payload.type !== "objective.announce" &&
|
|
721
|
+
request.envelope.payload.type !== "objective.revise" &&
|
|
722
|
+
request.envelope.payload.type !== "objective.cancel")
|
|
723
|
+
return "invalid_verified_envelope";
|
|
724
|
+
if (request.envelope.payload.type !== "objective.cancel" &&
|
|
725
|
+
request.envelope.sender.peerId !== request.envelope.payload.issuerPeerId)
|
|
726
|
+
return "issuer_not_authorized";
|
|
727
|
+
if (request.envelope.audience.kind === "mesh" &&
|
|
728
|
+
request.envelope.audience.topic !== "objective")
|
|
729
|
+
return "audience_mismatch";
|
|
730
|
+
const admission = state.discovery.admittedPeers[request.envelope.sender.peerId];
|
|
731
|
+
if (!admission)
|
|
732
|
+
return "sender_not_admitted";
|
|
733
|
+
if (!admission.instanceIds.includes(request.envelope.sender.instanceId))
|
|
734
|
+
return "sender_instance_not_admitted";
|
|
735
|
+
if (compare(request.verifiedAt, admission.validUntil) >= 0)
|
|
736
|
+
return "sender_admission_expired";
|
|
737
|
+
return undefined;
|
|
738
|
+
}
|
|
739
|
+
function domainMetadata(envelope) {
|
|
740
|
+
const recordId = envelope.payload.type === "objective.cancel"
|
|
741
|
+
? envelope.payload.cancellationId
|
|
742
|
+
: envelope.payload.objectiveDocumentId;
|
|
743
|
+
const contentDigest = envelope.payloadHash.startsWith("sha256:")
|
|
744
|
+
? envelope.payloadHash.slice(7)
|
|
745
|
+
: "";
|
|
746
|
+
if (!digestPattern.test(contentDigest))
|
|
747
|
+
throw new TypeError("Invalid verified Mesh Objective payload digest");
|
|
748
|
+
return {
|
|
749
|
+
recordKey: JSON.stringify([envelope.payload.type, recordId]),
|
|
750
|
+
recordId,
|
|
751
|
+
contentDigest,
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function freezeAcceptedObjectiveDocument(request, expiresAt) {
|
|
755
|
+
const validated = validateSignedMeshEnvelope(request.envelope);
|
|
756
|
+
if (!validated.ok ||
|
|
757
|
+
(validated.value.payload.type !== "objective.announce" &&
|
|
758
|
+
validated.value.payload.type !== "objective.revise")) {
|
|
759
|
+
throw new TypeError("Invalid verified Mesh Objective envelope");
|
|
760
|
+
}
|
|
761
|
+
return Object.freeze({
|
|
762
|
+
envelope: validated.value,
|
|
763
|
+
validityVerifiedAt: request.verifiedAt,
|
|
764
|
+
acceptedAt: request.receivedAt,
|
|
765
|
+
expiresAt,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
function freezeAcceptedObjectiveCancellation(request) {
|
|
769
|
+
const validated = validateSignedMeshEnvelope(request.envelope);
|
|
770
|
+
if (!validated.ok || validated.value.payload.type !== "objective.cancel") {
|
|
771
|
+
throw new TypeError("Invalid verified Mesh Objective cancellation");
|
|
772
|
+
}
|
|
773
|
+
return Object.freeze({
|
|
774
|
+
envelope: validated.value,
|
|
775
|
+
validityVerifiedAt: request.verifiedAt,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
function objectiveDocumentDomainRecordKey(document) {
|
|
779
|
+
const payload = document.envelope.payload;
|
|
780
|
+
return JSON.stringify([payload.type, payload.objectiveDocumentId]);
|
|
781
|
+
}
|
|
782
|
+
function objectiveDomainRecordKey(objective) {
|
|
783
|
+
return JSON.stringify([
|
|
784
|
+
objective.objectiveRevision === 1
|
|
785
|
+
? "objective.announce"
|
|
786
|
+
: "objective.revise",
|
|
787
|
+
objective.objectiveDocumentId,
|
|
788
|
+
]);
|
|
789
|
+
}
|
|
790
|
+
function workObjectiveDomainRecordKey(workItem) {
|
|
791
|
+
return JSON.stringify([
|
|
792
|
+
workItem.objectiveRevision === 1
|
|
793
|
+
? "objective.announce"
|
|
794
|
+
: "objective.revise",
|
|
795
|
+
workItem.objectiveDocumentId,
|
|
796
|
+
]);
|
|
797
|
+
}
|
|
798
|
+
function objectivePolicyDomainRecordKey(policy) {
|
|
799
|
+
return JSON.stringify([
|
|
800
|
+
policy.objectiveRevision === 1 ? "objective.announce" : "objective.revise",
|
|
801
|
+
policy.objectiveDocumentId,
|
|
802
|
+
]);
|
|
803
|
+
}
|
|
804
|
+
function objectiveTimerId(objectiveId) {
|
|
805
|
+
return `objective:${objectiveId.length}:${objectiveId}:expiry`;
|
|
806
|
+
}
|
|
807
|
+
function workTimerId(objectiveId, workItemId) {
|
|
808
|
+
return `work:${objectiveId.length}:${objectiveId}:${workItemId.length}:${workItemId}:deadline`;
|
|
809
|
+
}
|
|
810
|
+
function workKey(objectiveId, workItemId) {
|
|
811
|
+
return JSON.stringify([objectiveId, workItemId]);
|
|
812
|
+
}
|
|
813
|
+
function objectivePolicyKey(objectiveId, objectiveRevision) {
|
|
814
|
+
return JSON.stringify([objectiveId, objectiveRevision]);
|
|
815
|
+
}
|
|
816
|
+
function compare(left, right) {
|
|
817
|
+
const result = compareMeshTimestamps(left, right);
|
|
818
|
+
if (!result.ok)
|
|
819
|
+
throw new TypeError("Invalid Mesh Objective timestamp");
|
|
820
|
+
return result.value;
|
|
821
|
+
}
|
|
822
|
+
function freezeObjective(value) {
|
|
823
|
+
return Object.freeze({
|
|
824
|
+
...value,
|
|
825
|
+
successCriteria: Object.freeze([...value.successCriteria]),
|
|
826
|
+
permittedCapabilityKeys: Object.freeze([...value.permittedCapabilityKeys]),
|
|
827
|
+
recoveryWitnessPeerIds: Object.freeze([...value.recoveryWitnessPeerIds]),
|
|
828
|
+
...(value.authorizedObserverPeerIds === undefined
|
|
829
|
+
? {}
|
|
830
|
+
: {
|
|
831
|
+
authorizedObserverPeerIds: Object.freeze([
|
|
832
|
+
...value.authorizedObserverPeerIds,
|
|
833
|
+
]),
|
|
834
|
+
}),
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
function freezeObjectivePolicy(objective) {
|
|
838
|
+
return Object.freeze({
|
|
839
|
+
objectiveId: objective.objectiveId,
|
|
840
|
+
objectiveDocumentId: objective.objectiveDocumentId,
|
|
841
|
+
objectiveRevision: objective.objectiveRevision,
|
|
842
|
+
acceptedMessageId: objective.acceptedMessageId,
|
|
843
|
+
acceptedAt: objective.acceptedAt,
|
|
844
|
+
expiresAt: objective.expiresAt,
|
|
845
|
+
permittedCapabilityKeys: Object.freeze([
|
|
846
|
+
...objective.permittedCapabilityKeys,
|
|
847
|
+
]),
|
|
848
|
+
maximumBudgetUnits: objective.maximumBudgetUnits,
|
|
849
|
+
acceptanceWindowMs: objective.acceptanceWindowMs,
|
|
850
|
+
maximumLeaseDurationMs: objective.maximumLeaseDurationMs,
|
|
851
|
+
recoveryGraceMs: objective.recoveryGraceMs,
|
|
852
|
+
maximumLeaseRenewals: objective.maximumLeaseRenewals,
|
|
853
|
+
recoveryWitnessPeerIds: Object.freeze([
|
|
854
|
+
...objective.recoveryWitnessPeerIds,
|
|
855
|
+
]),
|
|
856
|
+
recoveryWitnessThreshold: objective.recoveryWitnessThreshold,
|
|
857
|
+
validUntil: objective.validUntil,
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
function freezeWorkItem(value) {
|
|
861
|
+
const attributes = Object.create(null);
|
|
862
|
+
for (const [key, entry] of Object.entries(value.matchingAttributes))
|
|
863
|
+
attributes[key] = entry;
|
|
864
|
+
return Object.freeze({
|
|
865
|
+
...value,
|
|
866
|
+
requiredCapabilityKeys: Object.freeze([...value.requiredCapabilityKeys]),
|
|
867
|
+
completionCriteria: Object.freeze([...value.completionCriteria]),
|
|
868
|
+
objectivePolicy: value.objectivePolicy,
|
|
869
|
+
matchingAttributes: Object.freeze(attributes),
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
function objectiveWithinLocalLimits(value, state) {
|
|
873
|
+
return (value.successCriteria.length <= state.limits.maximumCriteria &&
|
|
874
|
+
new Set(value.successCriteria).size === value.successCriteria.length &&
|
|
875
|
+
value.permittedCapabilityKeys.length <=
|
|
876
|
+
state.limits.maximumRequiredCapabilityKeys &&
|
|
877
|
+
value.recoveryWitnessPeerIds.length <=
|
|
878
|
+
state.limits.maximumIssuerAuthorities &&
|
|
879
|
+
!value.recoveryWitnessPeerIds.includes(value.issuerPeerId) &&
|
|
880
|
+
(value.authorizedObserverPeerIds?.length ?? 0) <=
|
|
881
|
+
state.limits.maximumIssuerAuthorities &&
|
|
882
|
+
utf8Encoder.encode(JSON.stringify(value)).byteLength <=
|
|
883
|
+
state.limits.maximumProjectionBytes);
|
|
884
|
+
}
|
|
885
|
+
function reject(state, code) {
|
|
886
|
+
return Object.freeze({ accepted: false, code, state });
|
|
887
|
+
}
|
|
888
|
+
function timerRejection(state, code) {
|
|
889
|
+
return Object.freeze({ accepted: false, code, state });
|
|
890
|
+
}
|
|
891
|
+
function assertRuntimeState(state) {
|
|
892
|
+
if (!isPlainDataRecord(state) ||
|
|
893
|
+
!Object.isFrozen(state) ||
|
|
894
|
+
Object.keys(state).length !== 3 ||
|
|
895
|
+
Object.keys(state).some((key) => !["coordination", "discovery", "objectives"].includes(key)))
|
|
896
|
+
throw new TypeError("Mesh Objective/Work runtime state is required");
|
|
897
|
+
assertFrozenMeshCoordinationState(state.coordination);
|
|
898
|
+
assertFrozenMeshDiscoveryState(state.discovery);
|
|
899
|
+
assertFrozenMeshObjectiveWorkState(state.objectives);
|
|
900
|
+
createMeshObjectiveWorkRuntimeState(state.coordination, state.discovery, state.objectives);
|
|
901
|
+
}
|
|
902
|
+
function assertMonotonic(state, logicalTime) {
|
|
903
|
+
if (logicalTime < state.coordination.lastLogicalTime ||
|
|
904
|
+
logicalTime < state.discovery.lastLogicalTime ||
|
|
905
|
+
logicalTime < state.objectives.lastLogicalTime)
|
|
906
|
+
throw new RangeError("Mesh Objective/Work logical time cannot move backwards");
|
|
907
|
+
}
|
|
908
|
+
function assertObjectiveRequest(request) {
|
|
909
|
+
if (!isPlainDataRecord(request) ||
|
|
910
|
+
Object.keys(request).some((key) => ![
|
|
911
|
+
"envelope",
|
|
912
|
+
"receivedAt",
|
|
913
|
+
"supportedCriticalExtensions",
|
|
914
|
+
"verifiedAt",
|
|
915
|
+
].includes(key)) ||
|
|
916
|
+
!Object.hasOwn(request, "envelope") ||
|
|
917
|
+
!Object.hasOwn(request, "verifiedAt") ||
|
|
918
|
+
!Object.hasOwn(request, "receivedAt"))
|
|
919
|
+
throw new TypeError("Mesh verified Objective request is required");
|
|
920
|
+
assertTrustedTime({
|
|
921
|
+
verifiedAt: request.verifiedAt,
|
|
922
|
+
receivedAt: request.receivedAt,
|
|
923
|
+
});
|
|
924
|
+
if (request.supportedCriticalExtensions !== undefined &&
|
|
925
|
+
(!isDenseDataArray(request.supportedCriticalExtensions) ||
|
|
926
|
+
request.supportedCriticalExtensions.length > 256 ||
|
|
927
|
+
request.supportedCriticalExtensions.some((entry) => typeof entry !== "string" ||
|
|
928
|
+
entry.length < 1 ||
|
|
929
|
+
utf8Encoder.encode(entry).byteLength > 256) ||
|
|
930
|
+
new Set(request.supportedCriticalExtensions).size !==
|
|
931
|
+
request.supportedCriticalExtensions.length))
|
|
932
|
+
throw new TypeError("Mesh Objective critical extensions are invalid");
|
|
933
|
+
}
|
|
934
|
+
function assertTrustedTime(time) {
|
|
935
|
+
if (!isPlainDataRecord(time) ||
|
|
936
|
+
Object.keys(time).some((key) => key !== "verifiedAt" && key !== "receivedAt") ||
|
|
937
|
+
!Object.hasOwn(time, "verifiedAt") ||
|
|
938
|
+
!Object.hasOwn(time, "receivedAt") ||
|
|
939
|
+
typeof time.verifiedAt !== "string" ||
|
|
940
|
+
!compareMeshTimestamps(time.verifiedAt, time.verifiedAt).ok)
|
|
941
|
+
throw new TypeError("Mesh Objective/Work trusted time is invalid");
|
|
942
|
+
assertMeshLogicalTime(time.receivedAt);
|
|
943
|
+
}
|
|
944
|
+
function assertCommand(command) {
|
|
945
|
+
if (!isPlainDataRecord(command) ||
|
|
946
|
+
!["work.create", "work.revise", "work.cancel"].includes(command.kind))
|
|
947
|
+
throw new TypeError("Mesh Objective/Work command is invalid");
|
|
948
|
+
const keys = Object.keys(command);
|
|
949
|
+
const allowed = command.kind === "work.create"
|
|
950
|
+
? ["kind", "input"]
|
|
951
|
+
: command.kind === "work.revise"
|
|
952
|
+
? ["expectedWorkItemRevision", "input", "kind"]
|
|
953
|
+
: ["expectedWorkItemRevision", "kind", "objectiveId", "workItemId"];
|
|
954
|
+
if (keys.length !== allowed.length ||
|
|
955
|
+
keys.some((key) => !allowed.includes(key)))
|
|
956
|
+
throw new TypeError("Mesh Objective/Work command contains unsupported fields");
|
|
957
|
+
if (command.kind === "work.cancel") {
|
|
958
|
+
assertIdentifier(command.objectiveId, "work cancel objectiveId");
|
|
959
|
+
assertIdentifier(command.workItemId, "work cancel workItemId");
|
|
960
|
+
}
|
|
961
|
+
if (command.kind !== "work.create" &&
|
|
962
|
+
(!Number.isSafeInteger(command.expectedWorkItemRevision) ||
|
|
963
|
+
command.expectedWorkItemRevision < 1)) {
|
|
964
|
+
throw new TypeError("Mesh Objective/Work expected revision is invalid");
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
function assertWorkInput(input, state) {
|
|
968
|
+
const supported = [
|
|
969
|
+
"budgetReservationUnits",
|
|
970
|
+
"completionCriteria",
|
|
971
|
+
"inputReference",
|
|
972
|
+
"inputSummary",
|
|
973
|
+
"matchingAttributes",
|
|
974
|
+
"objectiveId",
|
|
975
|
+
"requiredCapabilityKeys",
|
|
976
|
+
"workDeadline",
|
|
977
|
+
"workItemId",
|
|
978
|
+
];
|
|
979
|
+
const required = [
|
|
980
|
+
"budgetReservationUnits",
|
|
981
|
+
"completionCriteria",
|
|
982
|
+
"objectiveId",
|
|
983
|
+
"requiredCapabilityKeys",
|
|
984
|
+
"workDeadline",
|
|
985
|
+
"workItemId",
|
|
986
|
+
];
|
|
987
|
+
if (!isPlainDataRecord(input) ||
|
|
988
|
+
Object.keys(input).some((key) => !supported.includes(key)) ||
|
|
989
|
+
required.some((key) => !Object.hasOwn(input, key)))
|
|
990
|
+
throw new TypeError("Mesh Objective/Work input is invalid");
|
|
991
|
+
assertIdentifier(input.objectiveId, "Work Item objectiveId");
|
|
992
|
+
assertIdentifier(input.workItemId, "Work Item workItemId");
|
|
993
|
+
assertBoundedStringArray(input.requiredCapabilityKeys, state.limits.maximumRequiredCapabilityKeys, "Work Item capability keys", true);
|
|
994
|
+
assertBoundedStringArray(input.completionCriteria, state.limits.maximumCriteria, "Work Item completion criteria", false);
|
|
995
|
+
const attributes = input.matchingAttributes ?? Object.create(null);
|
|
996
|
+
if (!isPlainDataRecord(attributes)) {
|
|
997
|
+
throw new TypeError("Mesh Work Item matching attributes are invalid");
|
|
998
|
+
}
|
|
999
|
+
const attributeEntries = Object.entries(attributes);
|
|
1000
|
+
if (attributeEntries.length > state.limits.maximumMatchingAttributes ||
|
|
1001
|
+
attributeEntries.some(([key, value]) => key.length < 1 ||
|
|
1002
|
+
typeof value !== "string" ||
|
|
1003
|
+
value.length < 1 ||
|
|
1004
|
+
utf8Encoder.encode(key).byteLength >
|
|
1005
|
+
state.limits.maximumProjectionBytes ||
|
|
1006
|
+
utf8Encoder.encode(value).byteLength >
|
|
1007
|
+
state.limits.maximumProjectionBytes)) {
|
|
1008
|
+
throw new TypeError("Mesh Work Item matching attributes are invalid");
|
|
1009
|
+
}
|
|
1010
|
+
const hasSummary = typeof input.inputSummary === "string" && input.inputSummary.length > 0;
|
|
1011
|
+
const hasReference = typeof input.inputReference === "string" && input.inputReference.length > 0;
|
|
1012
|
+
if (hasSummary === hasReference ||
|
|
1013
|
+
(input.inputSummary !== undefined && !hasSummary) ||
|
|
1014
|
+
(input.inputReference !== undefined && !hasReference)) {
|
|
1015
|
+
throw new TypeError("Mesh Work Item input content is invalid");
|
|
1016
|
+
}
|
|
1017
|
+
const content = input.inputSummary ?? input.inputReference;
|
|
1018
|
+
if (utf8Encoder.encode(content).byteLength >
|
|
1019
|
+
state.limits.maximumProjectionBytes) {
|
|
1020
|
+
throw new TypeError("Mesh Work Item input content is invalid");
|
|
1021
|
+
}
|
|
1022
|
+
if (typeof input.budgetReservationUnits !== "number") {
|
|
1023
|
+
throw new TypeError("Mesh Work Item budget is invalid");
|
|
1024
|
+
}
|
|
1025
|
+
if (typeof input.workDeadline !== "string" ||
|
|
1026
|
+
!compareMeshTimestamps(input.workDeadline, input.workDeadline).ok) {
|
|
1027
|
+
throw new TypeError("Mesh Work Item deadline is invalid");
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
function sameIdentity(left, right) {
|
|
1031
|
+
return (left.tenantId === right.tenantId &&
|
|
1032
|
+
left.meshId === right.meshId &&
|
|
1033
|
+
left.peerId === right.peerId &&
|
|
1034
|
+
left.instanceId === right.instanceId &&
|
|
1035
|
+
left.keyId === right.keyId);
|
|
1036
|
+
}
|
|
1037
|
+
function assertIdentifier(value, name) {
|
|
1038
|
+
if (typeof value !== "string" ||
|
|
1039
|
+
!identifierPattern.test(value) ||
|
|
1040
|
+
utf8Encoder.encode(value).byteLength > 256) {
|
|
1041
|
+
throw new TypeError(`Invalid Mesh Objective/Work ${name}`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
function assertBoundedStringArray(value, maximum, name, sorted) {
|
|
1045
|
+
if (!isDenseDataArray(value) ||
|
|
1046
|
+
value.length < 1 ||
|
|
1047
|
+
value.length > maximum ||
|
|
1048
|
+
value.some((entry) => typeof entry !== "string" ||
|
|
1049
|
+
entry.length < 1 ||
|
|
1050
|
+
utf8Encoder.encode(entry).byteLength > 65_536)) {
|
|
1051
|
+
throw new TypeError(`Invalid Mesh ${name}`);
|
|
1052
|
+
}
|
|
1053
|
+
const strings = value;
|
|
1054
|
+
if (new Set(strings).size !== strings.length ||
|
|
1055
|
+
(sorted &&
|
|
1056
|
+
strings.some((entry, index) => index > 0 && entry <= strings[index - 1]))) {
|
|
1057
|
+
throw new TypeError(`Invalid Mesh ${name}`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function isPlainDataRecord(value) {
|
|
1061
|
+
if (!value ||
|
|
1062
|
+
typeof value !== "object" ||
|
|
1063
|
+
Array.isArray(value) ||
|
|
1064
|
+
(Object.getPrototypeOf(value) !== null &&
|
|
1065
|
+
Object.getPrototypeOf(value) !== Object.prototype) ||
|
|
1066
|
+
Object.getOwnPropertySymbols(value).length > 0) {
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
return Object.values(Object.getOwnPropertyDescriptors(value)).every((descriptor) => descriptor.enumerable && Object.hasOwn(descriptor, "value"));
|
|
1070
|
+
}
|
|
1071
|
+
function isDenseDataArray(value) {
|
|
1072
|
+
if (!Array.isArray(value) || Object.getOwnPropertySymbols(value).length > 0) {
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1076
|
+
if (Object.keys(descriptors).length !== value.length + 1 ||
|
|
1077
|
+
!Object.hasOwn(descriptors, "length")) {
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1081
|
+
const descriptor = descriptors[String(index)];
|
|
1082
|
+
if (!descriptor ||
|
|
1083
|
+
!descriptor.enumerable ||
|
|
1084
|
+
!Object.hasOwn(descriptor, "value")) {
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return true;
|
|
1089
|
+
}
|
|
1090
|
+
//# sourceMappingURL=coordination-objective-work.js.map
|