@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.
- 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,1075 @@
|
|
|
1
|
+
import { canonicalizeMeshPayload, compareMeshTimestamps, validateSignedMeshEnvelope, } from "@agentplat/mesh-protocol";
|
|
2
|
+
import { logicalExpiry } from "./coordination-objective-work-time.js";
|
|
3
|
+
import { sha256Base64Url } from "./sha256.js";
|
|
4
|
+
import { assertMeshLogicalTime, assertMeshMessageId, createFrozenRecord, } from "./state.js";
|
|
5
|
+
const identifierPattern = /^[A-Za-z0-9][A-Za-z0-9._:@-]*$/;
|
|
6
|
+
const utf8Encoder = new TextEncoder();
|
|
7
|
+
const identityKeys = ["instanceId", "keyId", "meshId", "peerId", "tenantId"];
|
|
8
|
+
const limitKeys = [
|
|
9
|
+
"maximumCriteria",
|
|
10
|
+
"maximumIssuerAuthorities",
|
|
11
|
+
"maximumIssuerKeys",
|
|
12
|
+
"maximumMatchingAttributes",
|
|
13
|
+
"maximumObjectivePolicies",
|
|
14
|
+
"maximumObjectives",
|
|
15
|
+
"maximumProjectionBytes",
|
|
16
|
+
"maximumRequiredCapabilityKeys",
|
|
17
|
+
"maximumWorkItems",
|
|
18
|
+
"maximumWorkItemsPerObjective",
|
|
19
|
+
];
|
|
20
|
+
/** Fixed upper bounds; configuration can only lower them. */
|
|
21
|
+
export const DEFAULT_MESH_OBJECTIVE_WORK_LIMITS = Object.freeze({
|
|
22
|
+
maximumIssuerAuthorities: 256,
|
|
23
|
+
maximumIssuerKeys: 32,
|
|
24
|
+
maximumObjectives: 1_024,
|
|
25
|
+
maximumObjectivePolicies: 8_192,
|
|
26
|
+
maximumWorkItems: 8_192,
|
|
27
|
+
maximumWorkItemsPerObjective: 1_024,
|
|
28
|
+
maximumRequiredCapabilityKeys: 32,
|
|
29
|
+
maximumMatchingAttributes: 32,
|
|
30
|
+
maximumCriteria: 32,
|
|
31
|
+
maximumProjectionBytes: 65_536,
|
|
32
|
+
});
|
|
33
|
+
/** Creates an empty separately restorable Objective/Work projection. */
|
|
34
|
+
export function createMeshObjectiveWorkState(options) {
|
|
35
|
+
assertPlainRecord(options, "state options");
|
|
36
|
+
assertExactKeys(options, ["identity", "issuerAuthorities", "limits"], ["identity"]);
|
|
37
|
+
const limits = resolveLimits(options.limits, false);
|
|
38
|
+
const authorities = options.issuerAuthorities ?? [];
|
|
39
|
+
if (!Array.isArray(authorities) ||
|
|
40
|
+
authorities.length > limits.maximumIssuerAuthorities) {
|
|
41
|
+
throw new RangeError("Mesh Objective issuer authority limit exceeded");
|
|
42
|
+
}
|
|
43
|
+
return Object.freeze({
|
|
44
|
+
schemaVersion: 2,
|
|
45
|
+
identity: freezeIdentity(options.identity),
|
|
46
|
+
issuerAuthorities: createFrozenRecord(authorities.map((authority) => {
|
|
47
|
+
const frozen = freezeIssuerAuthority(authority, limits);
|
|
48
|
+
return [frozen.peerId, frozen];
|
|
49
|
+
}), "Duplicate Mesh Objective issuer authority"),
|
|
50
|
+
objectives: createFrozenRecord([]),
|
|
51
|
+
objectiveDocuments: createFrozenRecord([]),
|
|
52
|
+
objectivePolicies: createFrozenRecord([]),
|
|
53
|
+
workItems: createFrozenRecord([]),
|
|
54
|
+
limits,
|
|
55
|
+
lastLogicalTime: 0,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** Strictly validates and canonicalizes decoded Objective/Work state. */
|
|
59
|
+
export function restoreMeshObjectiveWorkState(snapshot) {
|
|
60
|
+
const parsed = validateSnapshot(snapshot, true);
|
|
61
|
+
const objectiveDocuments = createFrozenRecord(parsed.objectiveDocuments.map(([key, document]) => [
|
|
62
|
+
key,
|
|
63
|
+
freezeAcceptedObjectiveDocument(document, parsed.limits, parsed.lastLogicalTime, true),
|
|
64
|
+
]));
|
|
65
|
+
const objectivePolicies = createFrozenRecord(parsed.objectivePolicies.map(([key, policy]) => [
|
|
66
|
+
key,
|
|
67
|
+
freezeWorkObjectivePolicy(policy, parsed.limits, parsed.lastLogicalTime),
|
|
68
|
+
]));
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
schemaVersion: 2,
|
|
71
|
+
identity: freezeIdentity(parsed.identity),
|
|
72
|
+
issuerAuthorities: createFrozenRecord(parsed.issuerAuthorities.map(([key, authority]) => [
|
|
73
|
+
key,
|
|
74
|
+
freezeIssuerAuthority(authority, parsed.limits),
|
|
75
|
+
])),
|
|
76
|
+
objectives: createFrozenRecord(parsed.objectives.map(([key, objective]) => [
|
|
77
|
+
key,
|
|
78
|
+
freezeObjective(objective, parsed.limits, parsed.lastLogicalTime, true),
|
|
79
|
+
])),
|
|
80
|
+
objectiveDocuments,
|
|
81
|
+
objectivePolicies,
|
|
82
|
+
workItems: createFrozenRecord(parsed.workItems.map(([key, workItem]) => {
|
|
83
|
+
const policy = objectivePolicies[objectivePolicyKey(workItem.objectiveId, workItem.objectiveRevision)];
|
|
84
|
+
return [
|
|
85
|
+
key,
|
|
86
|
+
freezeWorkItem({ ...workItem, objectivePolicy: policy }, parsed.limits, parsed.lastLogicalTime),
|
|
87
|
+
];
|
|
88
|
+
})),
|
|
89
|
+
limits: Object.freeze({ ...parsed.limits }),
|
|
90
|
+
lastLogicalTime: parsed.lastLogicalTime,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/** Strict assertion shared by Objective and Work evaluators. */
|
|
94
|
+
export function assertFrozenMeshObjectiveWorkState(state) {
|
|
95
|
+
validateSnapshot(state, false);
|
|
96
|
+
if (!Object.isFrozen(state) ||
|
|
97
|
+
!Object.isFrozen(state.identity) ||
|
|
98
|
+
!Object.isFrozen(state.issuerAuthorities) ||
|
|
99
|
+
!Object.isFrozen(state.objectives) ||
|
|
100
|
+
!Object.isFrozen(state.objectiveDocuments) ||
|
|
101
|
+
!Object.isFrozen(state.objectivePolicies) ||
|
|
102
|
+
!Object.isFrozen(state.workItems) ||
|
|
103
|
+
!Object.isFrozen(state.limits) ||
|
|
104
|
+
Object.getPrototypeOf(state.issuerAuthorities) !== null ||
|
|
105
|
+
Object.getPrototypeOf(state.objectives) !== null ||
|
|
106
|
+
Object.getPrototypeOf(state.objectiveDocuments) !== null ||
|
|
107
|
+
Object.getPrototypeOf(state.objectivePolicies) !== null ||
|
|
108
|
+
Object.getPrototypeOf(state.workItems) !== null ||
|
|
109
|
+
Object.values(state.issuerAuthorities).some((entry) => !Object.isFrozen(entry) || !Object.isFrozen(entry.keyIds)) ||
|
|
110
|
+
Object.values(state.objectives).some((entry) => !Object.isFrozen(entry) ||
|
|
111
|
+
!Object.isFrozen(entry.successCriteria) ||
|
|
112
|
+
!Object.isFrozen(entry.permittedCapabilityKeys) ||
|
|
113
|
+
!Object.isFrozen(entry.recoveryWitnessPeerIds) ||
|
|
114
|
+
(entry.terminalCancellation !== undefined &&
|
|
115
|
+
!isDeepFrozenData(entry.terminalCancellation)) ||
|
|
116
|
+
(entry.authorizedObserverPeerIds !== undefined &&
|
|
117
|
+
!Object.isFrozen(entry.authorizedObserverPeerIds))) ||
|
|
118
|
+
Object.values(state.objectiveDocuments).some((entry) => !isDeepFrozenData(entry)) ||
|
|
119
|
+
Object.values(state.objectivePolicies).some((entry) => !Object.isFrozen(entry) ||
|
|
120
|
+
!Object.isFrozen(entry.permittedCapabilityKeys) ||
|
|
121
|
+
!Object.isFrozen(entry.recoveryWitnessPeerIds)) ||
|
|
122
|
+
Object.values(state.workItems).some((entry) => !Object.isFrozen(entry) ||
|
|
123
|
+
!Object.isFrozen(entry.requiredCapabilityKeys) ||
|
|
124
|
+
!Object.isFrozen(entry.completionCriteria) ||
|
|
125
|
+
!Object.isFrozen(entry.objectivePolicy) ||
|
|
126
|
+
!Object.isFrozen(entry.objectivePolicy.permittedCapabilityKeys) ||
|
|
127
|
+
!Object.isFrozen(entry.objectivePolicy.recoveryWitnessPeerIds) ||
|
|
128
|
+
entry.objectivePolicy !==
|
|
129
|
+
state.objectivePolicies[objectivePolicyKey(entry.objectiveId, entry.objectiveRevision)] ||
|
|
130
|
+
!Object.isFrozen(entry.matchingAttributes) ||
|
|
131
|
+
Object.getPrototypeOf(entry.matchingAttributes) !== null)) {
|
|
132
|
+
throw new TypeError("Mesh Objective/Work state must be an immutable snapshot");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function validateSnapshot(snapshot, verifyDocumentDigests = true) {
|
|
136
|
+
assertPlainRecord(snapshot, "snapshot");
|
|
137
|
+
assertExactKeys(snapshot, [
|
|
138
|
+
"identity",
|
|
139
|
+
"issuerAuthorities",
|
|
140
|
+
"lastLogicalTime",
|
|
141
|
+
"limits",
|
|
142
|
+
"objectiveDocuments",
|
|
143
|
+
"objectives",
|
|
144
|
+
"objectivePolicies",
|
|
145
|
+
"schemaVersion",
|
|
146
|
+
"workItems",
|
|
147
|
+
], [
|
|
148
|
+
"identity",
|
|
149
|
+
"issuerAuthorities",
|
|
150
|
+
"lastLogicalTime",
|
|
151
|
+
"limits",
|
|
152
|
+
"objectiveDocuments",
|
|
153
|
+
"objectives",
|
|
154
|
+
"objectivePolicies",
|
|
155
|
+
"schemaVersion",
|
|
156
|
+
"workItems",
|
|
157
|
+
]);
|
|
158
|
+
const raw = snapshot;
|
|
159
|
+
if (raw.schemaVersion !== 1 && raw.schemaVersion !== 2)
|
|
160
|
+
throw new TypeError("Mesh Objective/Work schema version is unsupported");
|
|
161
|
+
const candidate = migrateObjectiveWorkSnapshot(raw);
|
|
162
|
+
const identity = freezeIdentity(candidate.identity);
|
|
163
|
+
const limits = resolveLimits(candidate.limits, true);
|
|
164
|
+
assertMeshLogicalTime(candidate.lastLogicalTime);
|
|
165
|
+
assertRecord(candidate.issuerAuthorities, "issuer authorities");
|
|
166
|
+
assertRecord(candidate.objectives, "objectives");
|
|
167
|
+
assertRecord(candidate.objectiveDocuments, "Objective documents");
|
|
168
|
+
assertRecord(candidate.objectivePolicies, "Objective policies");
|
|
169
|
+
assertRecord(candidate.workItems, "work items");
|
|
170
|
+
const issuerAuthorities = Object.entries(candidate.issuerAuthorities);
|
|
171
|
+
const objectives = Object.entries(candidate.objectives);
|
|
172
|
+
const objectiveDocuments = Object.entries(candidate.objectiveDocuments);
|
|
173
|
+
const objectivePolicies = Object.entries(candidate.objectivePolicies);
|
|
174
|
+
const workItems = Object.entries(candidate.workItems);
|
|
175
|
+
if (issuerAuthorities.length > limits.maximumIssuerAuthorities ||
|
|
176
|
+
objectives.length > limits.maximumObjectives ||
|
|
177
|
+
objectiveDocuments.length > limits.maximumObjectivePolicies ||
|
|
178
|
+
objectivePolicies.length > limits.maximumObjectivePolicies ||
|
|
179
|
+
objectiveDocuments.length !== objectivePolicies.length ||
|
|
180
|
+
workItems.length > limits.maximumWorkItems)
|
|
181
|
+
throw new RangeError("Mesh Objective/Work snapshot exceeds its limits");
|
|
182
|
+
for (const [key, authority] of issuerAuthorities) {
|
|
183
|
+
if (key !== authority.peerId)
|
|
184
|
+
throw new TypeError("Mesh Objective issuer authority key is invalid");
|
|
185
|
+
freezeIssuerAuthority(authority, limits);
|
|
186
|
+
}
|
|
187
|
+
const workCounts = new Map();
|
|
188
|
+
const objectiveDocumentIds = new Set();
|
|
189
|
+
const objectiveMessageIds = new Set();
|
|
190
|
+
const objectiveTerminalRecordKeys = new Set();
|
|
191
|
+
const objectivePolicyCounts = new Map();
|
|
192
|
+
const acceptedDocuments = new Map();
|
|
193
|
+
for (const [key, document] of objectiveDocuments) {
|
|
194
|
+
const accepted = freezeAcceptedObjectiveDocument(document, limits, candidate.lastLogicalTime, verifyDocumentDigests);
|
|
195
|
+
const payload = accepted.envelope.payload;
|
|
196
|
+
if (accepted.envelope.tenantId !== identity.tenantId ||
|
|
197
|
+
accepted.envelope.meshId !== identity.meshId ||
|
|
198
|
+
key !== objectivePolicyKey(payload.objectiveId, payload.objectiveRevision)) {
|
|
199
|
+
throw new TypeError("Mesh accepted Objective document key is invalid");
|
|
200
|
+
}
|
|
201
|
+
acceptedDocuments.set(key, accepted);
|
|
202
|
+
}
|
|
203
|
+
for (const document of acceptedDocuments.values()) {
|
|
204
|
+
validateAcceptedObjectiveChain(document, acceptedDocuments);
|
|
205
|
+
}
|
|
206
|
+
for (const [key, policy] of objectivePolicies) {
|
|
207
|
+
if (key !== objectivePolicyKey(policy.objectiveId, policy.objectiveRevision))
|
|
208
|
+
throw new TypeError("Mesh Objective policy key is invalid");
|
|
209
|
+
validateWorkObjectivePolicy(policy, limits, candidate.lastLogicalTime);
|
|
210
|
+
const current = candidate.objectives[policy.objectiveId];
|
|
211
|
+
if (!current ||
|
|
212
|
+
policy.objectiveRevision > current.objectiveRevision ||
|
|
213
|
+
!policyMatchesAcceptedDocument(policy, acceptedDocuments.get(key)) ||
|
|
214
|
+
objectiveDocumentIds.has(policy.objectiveDocumentId) ||
|
|
215
|
+
objectiveMessageIds.has(policy.acceptedMessageId)) {
|
|
216
|
+
throw new TypeError("Mesh Objective policy binding is not unique");
|
|
217
|
+
}
|
|
218
|
+
objectiveDocumentIds.add(policy.objectiveDocumentId);
|
|
219
|
+
objectiveMessageIds.add(policy.acceptedMessageId);
|
|
220
|
+
objectivePolicyCounts.set(policy.objectiveId, (objectivePolicyCounts.get(policy.objectiveId) ?? 0) + 1);
|
|
221
|
+
}
|
|
222
|
+
for (const [key, objective] of objectives) {
|
|
223
|
+
if (key !== objective.objectiveId)
|
|
224
|
+
throw new TypeError("Mesh Objective projection key is invalid");
|
|
225
|
+
validateObjective(objective, limits, candidate.lastLogicalTime, verifyDocumentDigests);
|
|
226
|
+
if (!policyMatchesObjective(candidate.objectivePolicies[objectivePolicyKey(objective.objectiveId, objective.objectiveRevision)], objective) ||
|
|
227
|
+
!objectiveMatchesAcceptedDocument(objective, acceptedDocuments.get(objectivePolicyKey(objective.objectiveId, objective.objectiveRevision))) ||
|
|
228
|
+
(objective.terminalCancellation !== undefined &&
|
|
229
|
+
(objective.terminalCancellation.envelope.tenantId !==
|
|
230
|
+
identity.tenantId ||
|
|
231
|
+
objective.terminalCancellation.envelope.meshId !==
|
|
232
|
+
identity.meshId)) ||
|
|
233
|
+
objectivePolicyCounts.get(objective.objectiveId) !==
|
|
234
|
+
objective.objectiveRevision ||
|
|
235
|
+
(objective.terminalCancellation !== undefined &&
|
|
236
|
+
objectiveMessageIds.has(objective.terminalCancellation.envelope.messageId)) ||
|
|
237
|
+
(objective.terminalRecordKey !== undefined &&
|
|
238
|
+
objectiveTerminalRecordKeys.has(objective.terminalRecordKey))) {
|
|
239
|
+
throw new TypeError("Mesh Objective projection binding is not unique");
|
|
240
|
+
}
|
|
241
|
+
if (objective.terminalRecordKey !== undefined) {
|
|
242
|
+
objectiveTerminalRecordKeys.add(objective.terminalRecordKey);
|
|
243
|
+
}
|
|
244
|
+
if (objective.terminalCancellation !== undefined) {
|
|
245
|
+
objectiveMessageIds.add(objective.terminalCancellation.envelope.messageId);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const [key, workItem] of workItems) {
|
|
249
|
+
if (key !== workKey(workItem.objectiveId, workItem.workItemId)) {
|
|
250
|
+
throw new TypeError("Mesh Work Item projection key is invalid");
|
|
251
|
+
}
|
|
252
|
+
const objective = candidate.objectives[workItem.objectiveId];
|
|
253
|
+
const policy = candidate.objectivePolicies[objectivePolicyKey(workItem.objectiveId, workItem.objectiveRevision)];
|
|
254
|
+
if (!objective ||
|
|
255
|
+
!policy ||
|
|
256
|
+
workItem.objectiveRevision > objective.objectiveRevision ||
|
|
257
|
+
workItem.ownerPeerId !== identity.peerId ||
|
|
258
|
+
!policiesEqual(workItem.objectivePolicy, policy)) {
|
|
259
|
+
throw new TypeError("Mesh Work Item Objective binding is invalid");
|
|
260
|
+
}
|
|
261
|
+
validateWorkItem(workItem, limits, candidate.lastLogicalTime);
|
|
262
|
+
const count = (workCounts.get(workItem.objectiveId) ?? 0) + 1;
|
|
263
|
+
if (count > limits.maximumWorkItemsPerObjective) {
|
|
264
|
+
throw new RangeError("Mesh Work Item per Objective limit exceeded");
|
|
265
|
+
}
|
|
266
|
+
workCounts.set(workItem.objectiveId, count);
|
|
267
|
+
}
|
|
268
|
+
for (const [objectiveId, objective] of objectives) {
|
|
269
|
+
if (objective.workItemCount !== (workCounts.get(objectiveId) ?? 0)) {
|
|
270
|
+
throw new TypeError("Mesh Objective Work Item counter is invalid");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
identity,
|
|
275
|
+
issuerAuthorities,
|
|
276
|
+
objectives,
|
|
277
|
+
objectiveDocuments,
|
|
278
|
+
objectivePolicies,
|
|
279
|
+
workItems,
|
|
280
|
+
limits,
|
|
281
|
+
lastLogicalTime: candidate.lastLogicalTime,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function migrateObjectiveWorkSnapshot(raw) {
|
|
285
|
+
if (raw.schemaVersion === 2)
|
|
286
|
+
return raw;
|
|
287
|
+
const documents = raw.objectiveDocuments;
|
|
288
|
+
const legacyPolicies = raw.objectivePolicies;
|
|
289
|
+
const migratedPolicies = Object.create(null);
|
|
290
|
+
for (const [key, policy] of Object.entries(legacyPolicies)) {
|
|
291
|
+
const payload = documents[key]?.envelope.payload;
|
|
292
|
+
migratedPolicies[key] = {
|
|
293
|
+
...policy,
|
|
294
|
+
acceptanceWindowMs: payload?.acceptanceWindowMs,
|
|
295
|
+
maximumLeaseDurationMs: payload?.maximumLeaseDurationMs,
|
|
296
|
+
recoveryGraceMs: payload?.recoveryGraceMs,
|
|
297
|
+
maximumLeaseRenewals: payload?.maximumLeaseRenewals,
|
|
298
|
+
recoveryWitnessPeerIds: payload?.recoveryWitnessPeerIds,
|
|
299
|
+
recoveryWitnessThreshold: payload?.recoveryWitnessThreshold,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const legacyWorkItems = raw.workItems;
|
|
303
|
+
const migratedWorkItems = Object.create(null);
|
|
304
|
+
for (const [key, workItem] of Object.entries(legacyWorkItems)) {
|
|
305
|
+
migratedWorkItems[key] = {
|
|
306
|
+
...workItem,
|
|
307
|
+
objectivePolicy: migratedPolicies[objectivePolicyKey(workItem.objectiveId, workItem.objectiveRevision)],
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
...raw,
|
|
312
|
+
schemaVersion: 2,
|
|
313
|
+
objectivePolicies: migratedPolicies,
|
|
314
|
+
workItems: migratedWorkItems,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function validateObjective(value, limits, lastLogicalTime, verifyDigest) {
|
|
318
|
+
assertPlainRecord(value, "Objective projection");
|
|
319
|
+
assertExactKeys(value, [
|
|
320
|
+
"acceptanceWindowMs",
|
|
321
|
+
"acceptedAt",
|
|
322
|
+
"acceptedMessageId",
|
|
323
|
+
"bidWindowMs",
|
|
324
|
+
"contentReference",
|
|
325
|
+
"authorizedObserverPeerIds",
|
|
326
|
+
"committedBudgetUnits",
|
|
327
|
+
"expiresAt",
|
|
328
|
+
"expiryTimerGeneration",
|
|
329
|
+
"expiryTimerId",
|
|
330
|
+
"issuerKeyId",
|
|
331
|
+
"issuerPeerId",
|
|
332
|
+
"maximumBudgetUnits",
|
|
333
|
+
"maximumConcurrentAssignments",
|
|
334
|
+
"maximumLeaseDurationMs",
|
|
335
|
+
"maximumLeaseRenewals",
|
|
336
|
+
"maximumWorkItems",
|
|
337
|
+
"objectiveDocumentId",
|
|
338
|
+
"objectiveId",
|
|
339
|
+
"objectiveRevision",
|
|
340
|
+
"permittedCapabilityKeys",
|
|
341
|
+
"recoveryGraceMs",
|
|
342
|
+
"recoveryWitnessPeerIds",
|
|
343
|
+
"recoveryWitnessThreshold",
|
|
344
|
+
"status",
|
|
345
|
+
"successCriteria",
|
|
346
|
+
"summary",
|
|
347
|
+
"terminalAt",
|
|
348
|
+
"terminalCancellation",
|
|
349
|
+
"terminalRecordKey",
|
|
350
|
+
"validFrom",
|
|
351
|
+
"validUntil",
|
|
352
|
+
"validityVerifiedAt",
|
|
353
|
+
"workItemCount",
|
|
354
|
+
"reservedBudgetUnits",
|
|
355
|
+
], [
|
|
356
|
+
"acceptanceWindowMs",
|
|
357
|
+
"acceptedAt",
|
|
358
|
+
"acceptedMessageId",
|
|
359
|
+
"bidWindowMs",
|
|
360
|
+
"expiresAt",
|
|
361
|
+
"issuerKeyId",
|
|
362
|
+
"issuerPeerId",
|
|
363
|
+
"maximumBudgetUnits",
|
|
364
|
+
"maximumConcurrentAssignments",
|
|
365
|
+
"maximumLeaseDurationMs",
|
|
366
|
+
"maximumLeaseRenewals",
|
|
367
|
+
"maximumWorkItems",
|
|
368
|
+
"objectiveDocumentId",
|
|
369
|
+
"objectiveId",
|
|
370
|
+
"objectiveRevision",
|
|
371
|
+
"permittedCapabilityKeys",
|
|
372
|
+
"recoveryGraceMs",
|
|
373
|
+
"recoveryWitnessPeerIds",
|
|
374
|
+
"recoveryWitnessThreshold",
|
|
375
|
+
"status",
|
|
376
|
+
"successCriteria",
|
|
377
|
+
"validFrom",
|
|
378
|
+
"validUntil",
|
|
379
|
+
"validityVerifiedAt",
|
|
380
|
+
"workItemCount",
|
|
381
|
+
"reservedBudgetUnits",
|
|
382
|
+
"committedBudgetUnits",
|
|
383
|
+
]);
|
|
384
|
+
for (const [name, id] of Object.entries({
|
|
385
|
+
objectiveId: value.objectiveId,
|
|
386
|
+
objectiveDocumentId: value.objectiveDocumentId,
|
|
387
|
+
issuerPeerId: value.issuerPeerId,
|
|
388
|
+
issuerKeyId: value.issuerKeyId,
|
|
389
|
+
}))
|
|
390
|
+
assertIdentifier(id, name);
|
|
391
|
+
if (!Number.isSafeInteger(value.objectiveRevision) ||
|
|
392
|
+
value.objectiveRevision < 1)
|
|
393
|
+
throw new TypeError("Mesh Objective revision is invalid");
|
|
394
|
+
assertStringXor(value.summary, value.contentReference, "Objective content");
|
|
395
|
+
assertStringArray(value.successCriteria, limits.maximumCriteria, "Objective success criteria");
|
|
396
|
+
assertSortedStringArray(value.permittedCapabilityKeys, limits.maximumRequiredCapabilityKeys, "Objective capability keys");
|
|
397
|
+
assertSortedStringArray(value.recoveryWitnessPeerIds, limits.maximumIssuerAuthorities, "Objective witnesses", false);
|
|
398
|
+
value.recoveryWitnessPeerIds.forEach((peerId) => assertIdentifier(peerId, "Objective witness peerId"));
|
|
399
|
+
if (value.recoveryWitnessPeerIds.length < 3 ||
|
|
400
|
+
value.recoveryWitnessPeerIds.includes(value.issuerPeerId))
|
|
401
|
+
throw new TypeError("Mesh Objective witnesses are invalid");
|
|
402
|
+
if (value.authorizedObserverPeerIds !== undefined)
|
|
403
|
+
assertSortedStringArray(value.authorizedObserverPeerIds, limits.maximumIssuerAuthorities, "Objective observers", true);
|
|
404
|
+
value.authorizedObserverPeerIds?.forEach((peerId) => assertIdentifier(peerId, "Objective observer peerId"));
|
|
405
|
+
for (const number of [
|
|
406
|
+
value.maximumWorkItems,
|
|
407
|
+
value.maximumConcurrentAssignments,
|
|
408
|
+
value.maximumBudgetUnits,
|
|
409
|
+
value.bidWindowMs,
|
|
410
|
+
value.acceptanceWindowMs,
|
|
411
|
+
value.maximumLeaseDurationMs,
|
|
412
|
+
value.recoveryGraceMs,
|
|
413
|
+
value.maximumLeaseRenewals,
|
|
414
|
+
]) {
|
|
415
|
+
if (!Number.isSafeInteger(number) || number < 0)
|
|
416
|
+
throw new TypeError("Mesh Objective policy value is invalid");
|
|
417
|
+
}
|
|
418
|
+
if (value.maximumWorkItems < 1 ||
|
|
419
|
+
value.maximumConcurrentAssignments < 1 ||
|
|
420
|
+
value.maximumConcurrentAssignments > value.maximumWorkItems ||
|
|
421
|
+
value.bidWindowMs < 1 ||
|
|
422
|
+
value.acceptanceWindowMs < 1 ||
|
|
423
|
+
value.maximumLeaseDurationMs < 1 ||
|
|
424
|
+
value.recoveryGraceMs < 1 ||
|
|
425
|
+
!Number.isSafeInteger(value.workItemCount) ||
|
|
426
|
+
value.workItemCount < 0 ||
|
|
427
|
+
value.workItemCount > value.maximumWorkItems ||
|
|
428
|
+
!Number.isSafeInteger(value.reservedBudgetUnits) ||
|
|
429
|
+
!Number.isSafeInteger(value.committedBudgetUnits) ||
|
|
430
|
+
value.reservedBudgetUnits < 0 ||
|
|
431
|
+
value.committedBudgetUnits < 0 ||
|
|
432
|
+
value.reservedBudgetUnits + value.committedBudgetUnits >
|
|
433
|
+
value.maximumBudgetUnits)
|
|
434
|
+
throw new TypeError("Mesh Objective policy value is invalid");
|
|
435
|
+
if (!Number.isSafeInteger(value.recoveryWitnessThreshold) ||
|
|
436
|
+
value.recoveryWitnessThreshold <= value.recoveryWitnessPeerIds.length / 2 ||
|
|
437
|
+
value.recoveryWitnessThreshold > value.recoveryWitnessPeerIds.length)
|
|
438
|
+
throw new TypeError("Mesh Objective witness threshold is invalid");
|
|
439
|
+
assertTimestamp(value.validFrom, "Objective validFrom");
|
|
440
|
+
assertTimestamp(value.validUntil, "Objective validUntil");
|
|
441
|
+
assertTimestamp(value.validityVerifiedAt, "Objective validityVerifiedAt");
|
|
442
|
+
assertMeshMessageId(value.acceptedMessageId);
|
|
443
|
+
if (compare(value.validFrom, value.validUntil) >= 0 ||
|
|
444
|
+
compare(value.validityVerifiedAt, value.validFrom) < 0 ||
|
|
445
|
+
compare(value.validityVerifiedAt, value.validUntil) >= 0)
|
|
446
|
+
throw new TypeError("Mesh Objective validity is invalid");
|
|
447
|
+
for (const number of [value.acceptedAt, value.expiresAt])
|
|
448
|
+
assertMeshLogicalTime(number);
|
|
449
|
+
if (value.acceptedAt > lastLogicalTime ||
|
|
450
|
+
value.expiresAt < value.acceptedAt ||
|
|
451
|
+
!["active", "cancelled", "expired"].includes(value.status))
|
|
452
|
+
throw new TypeError("Mesh Objective state is invalid");
|
|
453
|
+
const hasTimer = value.expiryTimerId !== undefined ||
|
|
454
|
+
value.expiryTimerGeneration !== undefined;
|
|
455
|
+
if (value.status === "active") {
|
|
456
|
+
if (!hasTimer ||
|
|
457
|
+
value.terminalAt !== undefined ||
|
|
458
|
+
value.terminalCancellation !== undefined ||
|
|
459
|
+
value.terminalRecordKey !== undefined)
|
|
460
|
+
throw new TypeError("Mesh active Objective timer is invalid");
|
|
461
|
+
assertTimerIdentifier(value.expiryTimerId, "Objective timerId");
|
|
462
|
+
if (value.expiryTimerId !== objectiveTimerId(value.objectiveId) ||
|
|
463
|
+
!Number.isSafeInteger(value.expiryTimerGeneration) ||
|
|
464
|
+
value.expiryTimerGeneration < 1)
|
|
465
|
+
throw new TypeError("Mesh Objective timer generation is invalid");
|
|
466
|
+
}
|
|
467
|
+
else if (value.status === "cancelled") {
|
|
468
|
+
if (hasTimer ||
|
|
469
|
+
value.terminalAt === undefined ||
|
|
470
|
+
value.terminalCancellation === undefined ||
|
|
471
|
+
value.terminalRecordKey === undefined)
|
|
472
|
+
throw new TypeError("Mesh cancelled Objective terminal binding is invalid");
|
|
473
|
+
assertMeshLogicalTime(value.terminalAt);
|
|
474
|
+
assertCancellationRecordKey(value.terminalRecordKey);
|
|
475
|
+
freezeAcceptedObjectiveCancellation(value.terminalCancellation, value, limits, verifyDigest);
|
|
476
|
+
if (value.terminalAt < value.acceptedAt ||
|
|
477
|
+
value.terminalAt >= value.expiresAt ||
|
|
478
|
+
value.terminalAt > lastLogicalTime)
|
|
479
|
+
throw new TypeError("Mesh cancelled Objective time is invalid");
|
|
480
|
+
}
|
|
481
|
+
else if (hasTimer ||
|
|
482
|
+
value.terminalAt === undefined ||
|
|
483
|
+
value.terminalAt < value.expiresAt ||
|
|
484
|
+
value.terminalAt > lastLogicalTime ||
|
|
485
|
+
value.terminalRecordKey !== undefined ||
|
|
486
|
+
value.terminalCancellation !== undefined) {
|
|
487
|
+
throw new TypeError("Mesh expired Objective terminal binding is invalid");
|
|
488
|
+
}
|
|
489
|
+
assertByteBound(value, limits.maximumProjectionBytes, "Objective projection");
|
|
490
|
+
}
|
|
491
|
+
function validateWorkItem(value, limits, lastLogicalTime) {
|
|
492
|
+
assertPlainRecord(value, "Work Item projection");
|
|
493
|
+
assertExactKeys(value, [
|
|
494
|
+
"budgetReservationUnits",
|
|
495
|
+
"completionCriteria",
|
|
496
|
+
"createdAt",
|
|
497
|
+
"expiryTimerGeneration",
|
|
498
|
+
"expiryTimerId",
|
|
499
|
+
"inputReference",
|
|
500
|
+
"inputSummary",
|
|
501
|
+
"matchingAttributes",
|
|
502
|
+
"objectiveDocumentId",
|
|
503
|
+
"objectiveId",
|
|
504
|
+
"objectivePolicy",
|
|
505
|
+
"objectiveRevision",
|
|
506
|
+
"offerAttempt",
|
|
507
|
+
"ownerEpoch",
|
|
508
|
+
"ownerPeerId",
|
|
509
|
+
"requiredCapabilityKeys",
|
|
510
|
+
"status",
|
|
511
|
+
"terminalAt",
|
|
512
|
+
"updatedAt",
|
|
513
|
+
"workDeadline",
|
|
514
|
+
"workDeadlineAt",
|
|
515
|
+
"workItemId",
|
|
516
|
+
"workItemRevision",
|
|
517
|
+
], [
|
|
518
|
+
"budgetReservationUnits",
|
|
519
|
+
"completionCriteria",
|
|
520
|
+
"createdAt",
|
|
521
|
+
"matchingAttributes",
|
|
522
|
+
"objectiveDocumentId",
|
|
523
|
+
"objectiveId",
|
|
524
|
+
"objectivePolicy",
|
|
525
|
+
"objectiveRevision",
|
|
526
|
+
"offerAttempt",
|
|
527
|
+
"ownerEpoch",
|
|
528
|
+
"ownerPeerId",
|
|
529
|
+
"requiredCapabilityKeys",
|
|
530
|
+
"status",
|
|
531
|
+
"updatedAt",
|
|
532
|
+
"workDeadline",
|
|
533
|
+
"workDeadlineAt",
|
|
534
|
+
"workItemId",
|
|
535
|
+
"workItemRevision",
|
|
536
|
+
]);
|
|
537
|
+
for (const [name, id] of Object.entries({
|
|
538
|
+
objectiveId: value.objectiveId,
|
|
539
|
+
objectiveDocumentId: value.objectiveDocumentId,
|
|
540
|
+
workItemId: value.workItemId,
|
|
541
|
+
ownerPeerId: value.ownerPeerId,
|
|
542
|
+
}))
|
|
543
|
+
assertIdentifier(id, name);
|
|
544
|
+
validateWorkObjectivePolicy(value.objectivePolicy, limits, lastLogicalTime);
|
|
545
|
+
if (value.objectivePolicy.objectiveId !== value.objectiveId ||
|
|
546
|
+
value.objectivePolicy.objectiveDocumentId !== value.objectiveDocumentId ||
|
|
547
|
+
value.objectivePolicy.objectiveRevision !== value.objectiveRevision ||
|
|
548
|
+
!Number.isSafeInteger(value.objectiveRevision) ||
|
|
549
|
+
value.objectiveRevision < 1 ||
|
|
550
|
+
!Number.isSafeInteger(value.workItemRevision) ||
|
|
551
|
+
value.workItemRevision < 1 ||
|
|
552
|
+
value.ownerEpoch !== 1 ||
|
|
553
|
+
!Number.isSafeInteger(value.offerAttempt) ||
|
|
554
|
+
value.offerAttempt < 0)
|
|
555
|
+
throw new TypeError("Mesh Work Item version is invalid");
|
|
556
|
+
assertSortedStringArray(value.requiredCapabilityKeys, limits.maximumRequiredCapabilityKeys, "Work Item capability keys", false);
|
|
557
|
+
assertStringArray(value.completionCriteria, limits.maximumCriteria, "Work Item completion criteria");
|
|
558
|
+
assertStringRecord(value.matchingAttributes, limits.maximumMatchingAttributes, "Work Item matching attributes");
|
|
559
|
+
assertStringXor(value.inputSummary, value.inputReference, "Work Item input");
|
|
560
|
+
const hasTimer = value.expiryTimerId !== undefined ||
|
|
561
|
+
value.expiryTimerGeneration !== undefined;
|
|
562
|
+
if (!Number.isSafeInteger(value.budgetReservationUnits) ||
|
|
563
|
+
value.budgetReservationUnits < 0 ||
|
|
564
|
+
!["ready", "completed", "released", "cancelled", "expired"].includes(value.status) ||
|
|
565
|
+
(value.status === "ready" &&
|
|
566
|
+
(!hasTimer ||
|
|
567
|
+
!Number.isSafeInteger(value.expiryTimerGeneration) ||
|
|
568
|
+
value.expiryTimerGeneration < 1 ||
|
|
569
|
+
typeof value.expiryTimerId !== "string" ||
|
|
570
|
+
value.expiryTimerId !==
|
|
571
|
+
workTimerId(value.objectiveId, value.workItemId))) ||
|
|
572
|
+
(value.status !== "ready" && hasTimer))
|
|
573
|
+
throw new TypeError("Mesh Work Item fields are invalid");
|
|
574
|
+
assertTimestamp(value.workDeadline, "Work Item deadline");
|
|
575
|
+
for (const number of [value.workDeadlineAt, value.createdAt, value.updatedAt])
|
|
576
|
+
assertMeshLogicalTime(number);
|
|
577
|
+
if (value.requiredCapabilityKeys.some((key) => !value.objectivePolicy.permittedCapabilityKeys.includes(key)) ||
|
|
578
|
+
value.budgetReservationUnits > value.objectivePolicy.maximumBudgetUnits ||
|
|
579
|
+
compare(value.workDeadline, value.objectivePolicy.validUntil) > 0 ||
|
|
580
|
+
value.workDeadlineAt > value.objectivePolicy.expiresAt ||
|
|
581
|
+
value.updatedAt < value.objectivePolicy.acceptedAt)
|
|
582
|
+
throw new TypeError("Mesh Work Item Objective policy binding is invalid");
|
|
583
|
+
if (value.workDeadlineAt < value.createdAt ||
|
|
584
|
+
value.createdAt > value.updatedAt ||
|
|
585
|
+
value.updatedAt > lastLogicalTime ||
|
|
586
|
+
(value.status === "ready" && value.terminalAt !== undefined) ||
|
|
587
|
+
(["completed", "released", "cancelled"].includes(value.status) &&
|
|
588
|
+
(value.terminalAt === undefined ||
|
|
589
|
+
value.terminalAt !== value.updatedAt ||
|
|
590
|
+
value.terminalAt < value.createdAt ||
|
|
591
|
+
value.terminalAt >= value.workDeadlineAt ||
|
|
592
|
+
value.terminalAt > lastLogicalTime)) ||
|
|
593
|
+
(value.status === "expired" &&
|
|
594
|
+
(value.terminalAt === undefined ||
|
|
595
|
+
value.terminalAt !== value.updatedAt ||
|
|
596
|
+
value.terminalAt < value.workDeadlineAt ||
|
|
597
|
+
value.terminalAt > lastLogicalTime)))
|
|
598
|
+
throw new TypeError("Mesh Work Item time is invalid");
|
|
599
|
+
assertByteBound(value, limits.maximumProjectionBytes, "Work Item projection");
|
|
600
|
+
}
|
|
601
|
+
function freezeIssuerAuthority(value, limits) {
|
|
602
|
+
assertPlainRecord(value, "issuer authority");
|
|
603
|
+
assertExactKeys(value, ["keyIds", "peerId", "validUntil"], ["keyIds", "peerId", "validUntil"]);
|
|
604
|
+
assertIdentifier(value.peerId, "issuer peerId");
|
|
605
|
+
assertTimestamp(value.validUntil, "issuer validUntil");
|
|
606
|
+
if (!isDenseDataArray(value.keyIds) ||
|
|
607
|
+
value.keyIds.length < 1 ||
|
|
608
|
+
value.keyIds.length > limits.maximumIssuerKeys ||
|
|
609
|
+
value.keyIds.some((key) => typeof key !== "string") ||
|
|
610
|
+
new Set(value.keyIds).size !== value.keyIds.length ||
|
|
611
|
+
value.keyIds.some((key, index) => index > 0 && key <= value.keyIds[index - 1]))
|
|
612
|
+
throw new TypeError("Mesh Objective issuer key list is invalid");
|
|
613
|
+
value.keyIds.forEach((key) => assertIdentifier(key, "issuer keyId"));
|
|
614
|
+
return Object.freeze({
|
|
615
|
+
peerId: value.peerId,
|
|
616
|
+
keyIds: Object.freeze([...value.keyIds]),
|
|
617
|
+
validUntil: value.validUntil,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
function freezeObjective(value, limits, lastLogicalTime, verifyDigest) {
|
|
621
|
+
validateObjective(value, limits, lastLogicalTime, verifyDigest);
|
|
622
|
+
const terminalCancellation = value.terminalCancellation === undefined
|
|
623
|
+
? undefined
|
|
624
|
+
: freezeAcceptedObjectiveCancellation(value.terminalCancellation, value, limits, verifyDigest);
|
|
625
|
+
return Object.freeze({
|
|
626
|
+
...value,
|
|
627
|
+
successCriteria: Object.freeze([...value.successCriteria]),
|
|
628
|
+
permittedCapabilityKeys: Object.freeze([...value.permittedCapabilityKeys]),
|
|
629
|
+
recoveryWitnessPeerIds: Object.freeze([...value.recoveryWitnessPeerIds]),
|
|
630
|
+
...(terminalCancellation === undefined ? {} : { terminalCancellation }),
|
|
631
|
+
...(value.authorizedObserverPeerIds === undefined
|
|
632
|
+
? {}
|
|
633
|
+
: {
|
|
634
|
+
authorizedObserverPeerIds: Object.freeze([
|
|
635
|
+
...value.authorizedObserverPeerIds,
|
|
636
|
+
]),
|
|
637
|
+
}),
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
function freezeWorkItem(value, limits, lastLogicalTime) {
|
|
641
|
+
validateWorkItem(value, limits, lastLogicalTime);
|
|
642
|
+
const attributes = Object.create(null);
|
|
643
|
+
for (const [key, entry] of Object.entries(value.matchingAttributes))
|
|
644
|
+
attributes[key] = entry;
|
|
645
|
+
return Object.freeze({
|
|
646
|
+
...value,
|
|
647
|
+
requiredCapabilityKeys: Object.freeze([...value.requiredCapabilityKeys]),
|
|
648
|
+
completionCriteria: Object.freeze([...value.completionCriteria]),
|
|
649
|
+
objectivePolicy: value.objectivePolicy,
|
|
650
|
+
matchingAttributes: Object.freeze(attributes),
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
function freezeAcceptedObjectiveDocument(value, limits, lastLogicalTime, verifyDigest) {
|
|
654
|
+
assertPlainRecord(value, "accepted Objective document");
|
|
655
|
+
assertExactKeys(value, ["acceptedAt", "envelope", "expiresAt", "validityVerifiedAt"], ["acceptedAt", "envelope", "expiresAt", "validityVerifiedAt"]);
|
|
656
|
+
assertTimestamp(value.validityVerifiedAt, "accepted Objective validityVerifiedAt");
|
|
657
|
+
assertMeshLogicalTime(value.acceptedAt);
|
|
658
|
+
assertMeshLogicalTime(value.expiresAt);
|
|
659
|
+
if (value.acceptedAt > lastLogicalTime ||
|
|
660
|
+
value.expiresAt <= value.acceptedAt) {
|
|
661
|
+
throw new TypeError("Mesh accepted Objective document time is invalid");
|
|
662
|
+
}
|
|
663
|
+
const validated = validateSignedMeshEnvelope(value.envelope);
|
|
664
|
+
if (!validated.ok ||
|
|
665
|
+
(validated.value.payload.type !== "objective.announce" &&
|
|
666
|
+
validated.value.payload.type !== "objective.revise")) {
|
|
667
|
+
throw new TypeError("Mesh accepted Objective envelope is invalid");
|
|
668
|
+
}
|
|
669
|
+
const envelope = validated.value;
|
|
670
|
+
if (compare(value.validityVerifiedAt, envelope.payload.validFrom) < 0 ||
|
|
671
|
+
compare(value.validityVerifiedAt, envelope.payload.validUntil) >= 0) {
|
|
672
|
+
throw new TypeError("Mesh accepted Objective validity is invalid");
|
|
673
|
+
}
|
|
674
|
+
const expectedExpiry = logicalExpiry(envelope.payload.validFrom, envelope.payload.validUntil, value.validityVerifiedAt, value.acceptedAt);
|
|
675
|
+
if (!expectedExpiry.ok || expectedExpiry.at !== value.expiresAt) {
|
|
676
|
+
throw new TypeError("Mesh accepted Objective logical expiry is invalid");
|
|
677
|
+
}
|
|
678
|
+
const canonical = canonicalizeMeshPayload(envelope.payload);
|
|
679
|
+
if (!canonical.ok) {
|
|
680
|
+
throw new TypeError("Mesh accepted Objective payload is invalid");
|
|
681
|
+
}
|
|
682
|
+
if (verifyDigest &&
|
|
683
|
+
envelope.payloadHash !== `sha256:${sha256Base64Url(canonical.value)}`) {
|
|
684
|
+
throw new TypeError("Mesh accepted Objective payload digest is invalid");
|
|
685
|
+
}
|
|
686
|
+
const document = Object.freeze({
|
|
687
|
+
envelope,
|
|
688
|
+
validityVerifiedAt: value.validityVerifiedAt,
|
|
689
|
+
acceptedAt: value.acceptedAt,
|
|
690
|
+
expiresAt: value.expiresAt,
|
|
691
|
+
});
|
|
692
|
+
assertByteBound(document, limits.maximumProjectionBytes, "accepted Objective document");
|
|
693
|
+
return document;
|
|
694
|
+
}
|
|
695
|
+
function freezeAcceptedObjectiveCancellation(value, objective, limits, verifyDigest) {
|
|
696
|
+
assertPlainRecord(value, "accepted Objective cancellation");
|
|
697
|
+
assertExactKeys(value, ["envelope", "validityVerifiedAt"], ["envelope", "validityVerifiedAt"]);
|
|
698
|
+
assertTimestamp(value.validityVerifiedAt, "accepted Objective cancellation validityVerifiedAt");
|
|
699
|
+
const validated = validateSignedMeshEnvelope(value.envelope);
|
|
700
|
+
if (!validated.ok || validated.value.payload.type !== "objective.cancel") {
|
|
701
|
+
throw new TypeError("Mesh accepted Objective cancellation is invalid");
|
|
702
|
+
}
|
|
703
|
+
const envelope = validated.value;
|
|
704
|
+
const payload = envelope.payload;
|
|
705
|
+
if (payload.objectiveId !== objective.objectiveId ||
|
|
706
|
+
payload.objectiveDocumentId !== objective.objectiveDocumentId ||
|
|
707
|
+
payload.objectiveRevision !== objective.objectiveRevision ||
|
|
708
|
+
envelope.sender.peerId !== objective.issuerPeerId ||
|
|
709
|
+
envelope.causationId !== objective.acceptedMessageId ||
|
|
710
|
+
objective.terminalRecordKey !==
|
|
711
|
+
JSON.stringify(["objective.cancel", payload.cancellationId]) ||
|
|
712
|
+
compare(value.validityVerifiedAt, objective.validityVerifiedAt) < 0 ||
|
|
713
|
+
compare(value.validityVerifiedAt, objective.validUntil) >= 0) {
|
|
714
|
+
throw new TypeError("Mesh accepted Objective cancellation binding is invalid");
|
|
715
|
+
}
|
|
716
|
+
const canonical = canonicalizeMeshPayload(payload);
|
|
717
|
+
if (!canonical.ok) {
|
|
718
|
+
throw new TypeError("Mesh accepted Objective cancellation payload is invalid");
|
|
719
|
+
}
|
|
720
|
+
if (verifyDigest &&
|
|
721
|
+
envelope.payloadHash !== `sha256:${sha256Base64Url(canonical.value)}`) {
|
|
722
|
+
throw new TypeError("Mesh accepted Objective cancellation payload digest is invalid");
|
|
723
|
+
}
|
|
724
|
+
const cancellation = Object.freeze({
|
|
725
|
+
envelope,
|
|
726
|
+
validityVerifiedAt: value.validityVerifiedAt,
|
|
727
|
+
});
|
|
728
|
+
assertByteBound(cancellation, limits.maximumProjectionBytes, "accepted Objective cancellation");
|
|
729
|
+
return cancellation;
|
|
730
|
+
}
|
|
731
|
+
function freezeWorkObjectivePolicy(value, limits, lastLogicalTime) {
|
|
732
|
+
validateWorkObjectivePolicy(value, limits, lastLogicalTime);
|
|
733
|
+
return Object.freeze({
|
|
734
|
+
...value,
|
|
735
|
+
permittedCapabilityKeys: Object.freeze([...value.permittedCapabilityKeys]),
|
|
736
|
+
recoveryWitnessPeerIds: Object.freeze([...value.recoveryWitnessPeerIds]),
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
function validateAcceptedObjectiveChain(document, documents) {
|
|
740
|
+
const { envelope } = document;
|
|
741
|
+
const payload = envelope.payload;
|
|
742
|
+
if (payload.objectiveRevision === 1) {
|
|
743
|
+
if (payload.type !== "objective.announce" ||
|
|
744
|
+
envelope.causationId !== undefined) {
|
|
745
|
+
throw new TypeError("Mesh accepted Objective history is invalid");
|
|
746
|
+
}
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (payload.type !== "objective.revise") {
|
|
750
|
+
throw new TypeError("Mesh accepted Objective history is invalid");
|
|
751
|
+
}
|
|
752
|
+
const previous = documents.get(objectivePolicyKey(payload.objectiveId, payload.objectiveRevision - 1));
|
|
753
|
+
if (!previous ||
|
|
754
|
+
previous.envelope.payload.objectiveDocumentId !==
|
|
755
|
+
payload.previousObjectiveDocumentId ||
|
|
756
|
+
previous.envelope.messageId !== envelope.causationId ||
|
|
757
|
+
previous.envelope.sender.peerId !== envelope.sender.peerId ||
|
|
758
|
+
previous.acceptedAt > document.acceptedAt ||
|
|
759
|
+
compare(previous.validityVerifiedAt, document.validityVerifiedAt) > 0) {
|
|
760
|
+
throw new TypeError("Mesh accepted Objective history is invalid");
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function policyMatchesAcceptedDocument(policy, document) {
|
|
764
|
+
if (!document)
|
|
765
|
+
return false;
|
|
766
|
+
const { envelope } = document;
|
|
767
|
+
const payload = envelope.payload;
|
|
768
|
+
return (policy.objectiveId === payload.objectiveId &&
|
|
769
|
+
policy.objectiveDocumentId === payload.objectiveDocumentId &&
|
|
770
|
+
policy.objectiveRevision === payload.objectiveRevision &&
|
|
771
|
+
policy.acceptedMessageId === envelope.messageId &&
|
|
772
|
+
policy.acceptedAt === document.acceptedAt &&
|
|
773
|
+
policy.expiresAt === document.expiresAt &&
|
|
774
|
+
policy.maximumBudgetUnits === payload.maximumBudgetUnits &&
|
|
775
|
+
policy.acceptanceWindowMs === payload.acceptanceWindowMs &&
|
|
776
|
+
policy.maximumLeaseDurationMs === payload.maximumLeaseDurationMs &&
|
|
777
|
+
policy.recoveryGraceMs === payload.recoveryGraceMs &&
|
|
778
|
+
policy.maximumLeaseRenewals === payload.maximumLeaseRenewals &&
|
|
779
|
+
arraysEqual(policy.recoveryWitnessPeerIds, payload.recoveryWitnessPeerIds) &&
|
|
780
|
+
policy.recoveryWitnessThreshold === payload.recoveryWitnessThreshold &&
|
|
781
|
+
policy.validUntil === payload.validUntil &&
|
|
782
|
+
arraysEqual(policy.permittedCapabilityKeys, payload.permittedCapabilityKeys));
|
|
783
|
+
}
|
|
784
|
+
function objectiveMatchesAcceptedDocument(objective, document) {
|
|
785
|
+
if (!document)
|
|
786
|
+
return false;
|
|
787
|
+
const { envelope } = document;
|
|
788
|
+
const payload = envelope.payload;
|
|
789
|
+
return (objective.objectiveId === payload.objectiveId &&
|
|
790
|
+
objective.objectiveDocumentId === payload.objectiveDocumentId &&
|
|
791
|
+
objective.objectiveRevision === payload.objectiveRevision &&
|
|
792
|
+
objective.issuerPeerId === payload.issuerPeerId &&
|
|
793
|
+
objective.issuerPeerId === envelope.sender.peerId &&
|
|
794
|
+
objective.issuerKeyId === envelope.proof.keyId &&
|
|
795
|
+
objective.summary === payload.summary &&
|
|
796
|
+
objective.contentReference === payload.contentReference &&
|
|
797
|
+
arraysEqual(objective.successCriteria, payload.successCriteria) &&
|
|
798
|
+
arraysEqual(objective.permittedCapabilityKeys, payload.permittedCapabilityKeys) &&
|
|
799
|
+
objective.maximumWorkItems === payload.maximumWorkItems &&
|
|
800
|
+
objective.maximumConcurrentAssignments ===
|
|
801
|
+
payload.maximumConcurrentAssignments &&
|
|
802
|
+
objective.maximumBudgetUnits === payload.maximumBudgetUnits &&
|
|
803
|
+
objective.bidWindowMs === payload.bidWindowMs &&
|
|
804
|
+
objective.acceptanceWindowMs === payload.acceptanceWindowMs &&
|
|
805
|
+
objective.maximumLeaseDurationMs === payload.maximumLeaseDurationMs &&
|
|
806
|
+
objective.recoveryGraceMs === payload.recoveryGraceMs &&
|
|
807
|
+
objective.maximumLeaseRenewals === payload.maximumLeaseRenewals &&
|
|
808
|
+
arraysEqual(objective.recoveryWitnessPeerIds, payload.recoveryWitnessPeerIds) &&
|
|
809
|
+
objective.recoveryWitnessThreshold === payload.recoveryWitnessThreshold &&
|
|
810
|
+
arraysEqualOptional(objective.authorizedObserverPeerIds, payload.authorizedObserverPeerIds) &&
|
|
811
|
+
objective.validFrom === payload.validFrom &&
|
|
812
|
+
objective.validUntil === payload.validUntil &&
|
|
813
|
+
objective.validityVerifiedAt === document.validityVerifiedAt &&
|
|
814
|
+
objective.acceptedMessageId === envelope.messageId &&
|
|
815
|
+
objective.acceptedAt === document.acceptedAt &&
|
|
816
|
+
objective.expiresAt === document.expiresAt);
|
|
817
|
+
}
|
|
818
|
+
function validateWorkObjectivePolicy(value, limits, lastLogicalTime) {
|
|
819
|
+
assertPlainRecord(value, "Work Objective policy");
|
|
820
|
+
assertExactKeys(value, [
|
|
821
|
+
"acceptedAt",
|
|
822
|
+
"acceptedMessageId",
|
|
823
|
+
"expiresAt",
|
|
824
|
+
"acceptanceWindowMs",
|
|
825
|
+
"maximumBudgetUnits",
|
|
826
|
+
"maximumLeaseDurationMs",
|
|
827
|
+
"maximumLeaseRenewals",
|
|
828
|
+
"objectiveDocumentId",
|
|
829
|
+
"objectiveId",
|
|
830
|
+
"objectiveRevision",
|
|
831
|
+
"permittedCapabilityKeys",
|
|
832
|
+
"recoveryGraceMs",
|
|
833
|
+
"recoveryWitnessPeerIds",
|
|
834
|
+
"recoveryWitnessThreshold",
|
|
835
|
+
"validUntil",
|
|
836
|
+
], [
|
|
837
|
+
"acceptedAt",
|
|
838
|
+
"acceptedMessageId",
|
|
839
|
+
"expiresAt",
|
|
840
|
+
"acceptanceWindowMs",
|
|
841
|
+
"maximumBudgetUnits",
|
|
842
|
+
"maximumLeaseDurationMs",
|
|
843
|
+
"maximumLeaseRenewals",
|
|
844
|
+
"objectiveDocumentId",
|
|
845
|
+
"objectiveId",
|
|
846
|
+
"objectiveRevision",
|
|
847
|
+
"permittedCapabilityKeys",
|
|
848
|
+
"recoveryGraceMs",
|
|
849
|
+
"recoveryWitnessPeerIds",
|
|
850
|
+
"recoveryWitnessThreshold",
|
|
851
|
+
"validUntil",
|
|
852
|
+
]);
|
|
853
|
+
assertIdentifier(value.objectiveId, "Work policy objectiveId");
|
|
854
|
+
assertIdentifier(value.objectiveDocumentId, "Work policy objectiveDocumentId");
|
|
855
|
+
assertMeshMessageId(value.acceptedMessageId);
|
|
856
|
+
if (!Number.isSafeInteger(value.objectiveRevision) ||
|
|
857
|
+
value.objectiveRevision < 1 ||
|
|
858
|
+
!Number.isSafeInteger(value.maximumBudgetUnits) ||
|
|
859
|
+
value.maximumBudgetUnits < 0 ||
|
|
860
|
+
!Number.isSafeInteger(value.acceptanceWindowMs) ||
|
|
861
|
+
value.acceptanceWindowMs < 1 ||
|
|
862
|
+
!Number.isSafeInteger(value.maximumLeaseDurationMs) ||
|
|
863
|
+
value.maximumLeaseDurationMs < 1 ||
|
|
864
|
+
!Number.isSafeInteger(value.recoveryGraceMs) ||
|
|
865
|
+
value.recoveryGraceMs < 1 ||
|
|
866
|
+
!Number.isSafeInteger(value.maximumLeaseRenewals) ||
|
|
867
|
+
value.maximumLeaseRenewals < 0)
|
|
868
|
+
throw new TypeError("Mesh Work Objective policy value is invalid");
|
|
869
|
+
assertSortedStringArray(value.permittedCapabilityKeys, limits.maximumRequiredCapabilityKeys, "Work Objective policy capability keys");
|
|
870
|
+
assertSortedStringArray(value.recoveryWitnessPeerIds, limits.maximumIssuerAuthorities, "Work Objective policy witnesses", false);
|
|
871
|
+
value.recoveryWitnessPeerIds.forEach((peerId) => assertIdentifier(peerId, "Work Objective policy witness peerId"));
|
|
872
|
+
if (value.recoveryWitnessPeerIds.length < 3 ||
|
|
873
|
+
!Number.isSafeInteger(value.recoveryWitnessThreshold) ||
|
|
874
|
+
value.recoveryWitnessThreshold <= value.recoveryWitnessPeerIds.length / 2 ||
|
|
875
|
+
value.recoveryWitnessThreshold > value.recoveryWitnessPeerIds.length)
|
|
876
|
+
throw new TypeError("Mesh Work Objective witness policy is invalid");
|
|
877
|
+
assertTimestamp(value.validUntil, "Work Objective policy validUntil");
|
|
878
|
+
assertMeshLogicalTime(value.acceptedAt);
|
|
879
|
+
assertMeshLogicalTime(value.expiresAt);
|
|
880
|
+
if (value.acceptedAt > lastLogicalTime || value.expiresAt <= value.acceptedAt)
|
|
881
|
+
throw new TypeError("Mesh Work Objective policy time is invalid");
|
|
882
|
+
assertByteBound(value, limits.maximumProjectionBytes, "Objective policy projection");
|
|
883
|
+
}
|
|
884
|
+
function policyMatchesObjective(policy, objective) {
|
|
885
|
+
return (policy !== undefined &&
|
|
886
|
+
policy.objectiveId === objective.objectiveId &&
|
|
887
|
+
policy.objectiveDocumentId === objective.objectiveDocumentId &&
|
|
888
|
+
policy.objectiveRevision === objective.objectiveRevision &&
|
|
889
|
+
policy.acceptedMessageId === objective.acceptedMessageId &&
|
|
890
|
+
policy.acceptedAt === objective.acceptedAt &&
|
|
891
|
+
policy.expiresAt === objective.expiresAt &&
|
|
892
|
+
policy.maximumBudgetUnits === objective.maximumBudgetUnits &&
|
|
893
|
+
policy.acceptanceWindowMs === objective.acceptanceWindowMs &&
|
|
894
|
+
policy.maximumLeaseDurationMs === objective.maximumLeaseDurationMs &&
|
|
895
|
+
policy.recoveryGraceMs === objective.recoveryGraceMs &&
|
|
896
|
+
policy.maximumLeaseRenewals === objective.maximumLeaseRenewals &&
|
|
897
|
+
arraysEqual(policy.recoveryWitnessPeerIds, objective.recoveryWitnessPeerIds) &&
|
|
898
|
+
policy.recoveryWitnessThreshold === objective.recoveryWitnessThreshold &&
|
|
899
|
+
policy.validUntil === objective.validUntil &&
|
|
900
|
+
arraysEqual(policy.permittedCapabilityKeys, objective.permittedCapabilityKeys));
|
|
901
|
+
}
|
|
902
|
+
function policiesEqual(left, right) {
|
|
903
|
+
return (left.objectiveId === right.objectiveId &&
|
|
904
|
+
left.objectiveDocumentId === right.objectiveDocumentId &&
|
|
905
|
+
left.objectiveRevision === right.objectiveRevision &&
|
|
906
|
+
left.acceptedMessageId === right.acceptedMessageId &&
|
|
907
|
+
left.acceptedAt === right.acceptedAt &&
|
|
908
|
+
left.expiresAt === right.expiresAt &&
|
|
909
|
+
left.maximumBudgetUnits === right.maximumBudgetUnits &&
|
|
910
|
+
left.acceptanceWindowMs === right.acceptanceWindowMs &&
|
|
911
|
+
left.maximumLeaseDurationMs === right.maximumLeaseDurationMs &&
|
|
912
|
+
left.recoveryGraceMs === right.recoveryGraceMs &&
|
|
913
|
+
left.maximumLeaseRenewals === right.maximumLeaseRenewals &&
|
|
914
|
+
arraysEqual(left.recoveryWitnessPeerIds, right.recoveryWitnessPeerIds) &&
|
|
915
|
+
left.recoveryWitnessThreshold === right.recoveryWitnessThreshold &&
|
|
916
|
+
left.validUntil === right.validUntil &&
|
|
917
|
+
arraysEqual(left.permittedCapabilityKeys, right.permittedCapabilityKeys));
|
|
918
|
+
}
|
|
919
|
+
function resolveLimits(overrides, complete) {
|
|
920
|
+
if (overrides !== undefined) {
|
|
921
|
+
assertPlainRecord(overrides, "limits");
|
|
922
|
+
assertExactKeys(overrides, limitKeys, complete ? limitKeys : []);
|
|
923
|
+
}
|
|
924
|
+
const limits = { ...DEFAULT_MESH_OBJECTIVE_WORK_LIMITS, ...overrides };
|
|
925
|
+
for (const [name, value] of Object.entries(limits)) {
|
|
926
|
+
const ceiling = DEFAULT_MESH_OBJECTIVE_WORK_LIMITS[name];
|
|
927
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > ceiling)
|
|
928
|
+
throw new RangeError(`Mesh Objective/Work limit ${name} is invalid`);
|
|
929
|
+
}
|
|
930
|
+
return Object.freeze(limits);
|
|
931
|
+
}
|
|
932
|
+
function freezeIdentity(identity) {
|
|
933
|
+
assertPlainRecord(identity, "identity");
|
|
934
|
+
assertExactKeys(identity, identityKeys, identityKeys);
|
|
935
|
+
for (const [name, value] of Object.entries(identity))
|
|
936
|
+
assertIdentifier(value, name);
|
|
937
|
+
return Object.freeze({ ...identity });
|
|
938
|
+
}
|
|
939
|
+
function assertIdentifier(value, name) {
|
|
940
|
+
if (typeof value !== "string" ||
|
|
941
|
+
!identifierPattern.test(value) ||
|
|
942
|
+
utf8Encoder.encode(value).byteLength > 256)
|
|
943
|
+
throw new TypeError(`Invalid Mesh Objective/Work ${name}`);
|
|
944
|
+
}
|
|
945
|
+
function assertTimerIdentifier(value, name) {
|
|
946
|
+
if (typeof value !== "string" ||
|
|
947
|
+
!identifierPattern.test(value) ||
|
|
948
|
+
utf8Encoder.encode(value).byteLength > 768)
|
|
949
|
+
throw new TypeError(`Invalid Mesh Objective/Work ${name}`);
|
|
950
|
+
}
|
|
951
|
+
function assertTimestamp(value, name) {
|
|
952
|
+
if (typeof value !== "string" || !compareMeshTimestamps(value, value).ok)
|
|
953
|
+
throw new TypeError(`Invalid Mesh Objective/Work ${name}`);
|
|
954
|
+
}
|
|
955
|
+
function compare(left, right) {
|
|
956
|
+
const result = compareMeshTimestamps(left, right);
|
|
957
|
+
if (!result.ok)
|
|
958
|
+
throw new TypeError("Invalid Mesh Objective/Work timestamp");
|
|
959
|
+
return result.value;
|
|
960
|
+
}
|
|
961
|
+
function assertStringArray(value, maximum, name) {
|
|
962
|
+
if (!isDenseDataArray(value) ||
|
|
963
|
+
value.length < 1 ||
|
|
964
|
+
value.length > maximum ||
|
|
965
|
+
value.some((entry) => typeof entry !== "string" || entry.length < 1) ||
|
|
966
|
+
new Set(value).size !== value.length)
|
|
967
|
+
throw new TypeError(`Invalid Mesh ${name}`);
|
|
968
|
+
}
|
|
969
|
+
function assertSortedStringArray(value, maximum, name, allowEmpty = false) {
|
|
970
|
+
if (allowEmpty && isDenseDataArray(value) && value.length === 0)
|
|
971
|
+
return;
|
|
972
|
+
assertStringArray(value, maximum, name);
|
|
973
|
+
if (value.some((entry, index) => index > 0 && entry <= value[index - 1]))
|
|
974
|
+
throw new TypeError(`Invalid Mesh ${name}`);
|
|
975
|
+
}
|
|
976
|
+
function assertStringRecord(value, maximum, name) {
|
|
977
|
+
assertRecord(value, name);
|
|
978
|
+
const entries = Object.entries(value);
|
|
979
|
+
if (entries.length > maximum ||
|
|
980
|
+
entries.some(([key, entry]) => key.length < 1 || typeof entry !== "string"))
|
|
981
|
+
throw new TypeError(`Invalid Mesh ${name}`);
|
|
982
|
+
}
|
|
983
|
+
function assertStringXor(left, right, name) {
|
|
984
|
+
if ((typeof left === "string" && left.length > 0) ===
|
|
985
|
+
(typeof right === "string" && right.length > 0))
|
|
986
|
+
throw new TypeError(`Invalid Mesh ${name}`);
|
|
987
|
+
}
|
|
988
|
+
function assertByteBound(value, maximum, name) {
|
|
989
|
+
if (utf8Encoder.encode(JSON.stringify(value)).byteLength > maximum)
|
|
990
|
+
throw new RangeError(`Mesh ${name} exceeds its byte limit`);
|
|
991
|
+
}
|
|
992
|
+
function assertPlainRecord(value, name) {
|
|
993
|
+
if (!value ||
|
|
994
|
+
typeof value !== "object" ||
|
|
995
|
+
Array.isArray(value) ||
|
|
996
|
+
(Object.getPrototypeOf(value) !== null &&
|
|
997
|
+
Object.getPrototypeOf(value) !== Object.prototype) ||
|
|
998
|
+
Object.getOwnPropertySymbols(value).length > 0 ||
|
|
999
|
+
Object.values(Object.getOwnPropertyDescriptors(value)).some((descriptor) => !descriptor.enumerable || !Object.hasOwn(descriptor, "value")))
|
|
1000
|
+
throw new TypeError(`Mesh Objective/Work ${name} must be a plain record`);
|
|
1001
|
+
}
|
|
1002
|
+
function isDenseDataArray(value) {
|
|
1003
|
+
if (!Array.isArray(value) || Object.getOwnPropertySymbols(value).length > 0) {
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1007
|
+
const names = Object.keys(descriptors);
|
|
1008
|
+
if (names.length !== value.length + 1 ||
|
|
1009
|
+
!Object.hasOwn(descriptors, "length")) {
|
|
1010
|
+
return false;
|
|
1011
|
+
}
|
|
1012
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1013
|
+
const descriptor = descriptors[String(index)];
|
|
1014
|
+
if (!descriptor ||
|
|
1015
|
+
!descriptor.enumerable ||
|
|
1016
|
+
!Object.hasOwn(descriptor, "value")) {
|
|
1017
|
+
return false;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return true;
|
|
1021
|
+
}
|
|
1022
|
+
function assertRecord(value, name) {
|
|
1023
|
+
assertPlainRecord(value, name);
|
|
1024
|
+
}
|
|
1025
|
+
function assertExactKeys(value, supported, required) {
|
|
1026
|
+
const allowed = new Set(supported);
|
|
1027
|
+
if (Object.keys(value).some((key) => !allowed.has(key)) ||
|
|
1028
|
+
required.some((key) => !Object.hasOwn(value, key)))
|
|
1029
|
+
throw new TypeError("Mesh Objective/Work value contains unsupported fields");
|
|
1030
|
+
}
|
|
1031
|
+
function workKey(objectiveId, workItemId) {
|
|
1032
|
+
return JSON.stringify([objectiveId, workItemId]);
|
|
1033
|
+
}
|
|
1034
|
+
function objectiveTimerId(objectiveId) {
|
|
1035
|
+
return `objective:${objectiveId.length}:${objectiveId}:expiry`;
|
|
1036
|
+
}
|
|
1037
|
+
function objectivePolicyKey(objectiveId, objectiveRevision) {
|
|
1038
|
+
return JSON.stringify([objectiveId, objectiveRevision]);
|
|
1039
|
+
}
|
|
1040
|
+
function workTimerId(objectiveId, workItemId) {
|
|
1041
|
+
return `work:${objectiveId.length}:${objectiveId}:${workItemId.length}:${workItemId}:deadline`;
|
|
1042
|
+
}
|
|
1043
|
+
function arraysEqual(left, right) {
|
|
1044
|
+
return (left.length === right.length &&
|
|
1045
|
+
left.every((entry, index) => entry === right[index]));
|
|
1046
|
+
}
|
|
1047
|
+
function arraysEqualOptional(left, right) {
|
|
1048
|
+
return left === undefined
|
|
1049
|
+
? right === undefined
|
|
1050
|
+
: right !== undefined && arraysEqual(left, right);
|
|
1051
|
+
}
|
|
1052
|
+
function isDeepFrozenData(value) {
|
|
1053
|
+
if (!value || typeof value !== "object")
|
|
1054
|
+
return true;
|
|
1055
|
+
if (!Object.isFrozen(value))
|
|
1056
|
+
return false;
|
|
1057
|
+
return Object.values(value).every((entry) => isDeepFrozenData(entry));
|
|
1058
|
+
}
|
|
1059
|
+
function assertCancellationRecordKey(value) {
|
|
1060
|
+
if (typeof value !== "string")
|
|
1061
|
+
throw new TypeError("Invalid Mesh Objective terminalRecordKey");
|
|
1062
|
+
try {
|
|
1063
|
+
const parsed = JSON.parse(value);
|
|
1064
|
+
if (!Array.isArray(parsed) ||
|
|
1065
|
+
parsed.length !== 2 ||
|
|
1066
|
+
parsed[0] !== "objective.cancel" ||
|
|
1067
|
+
typeof parsed[1] !== "string")
|
|
1068
|
+
throw new Error();
|
|
1069
|
+
assertIdentifier(parsed[1], "Objective cancellationId");
|
|
1070
|
+
}
|
|
1071
|
+
catch {
|
|
1072
|
+
throw new TypeError("Invalid Mesh Objective terminalRecordKey");
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
//# sourceMappingURL=coordination-objective-work-state.js.map
|