@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,944 @@
|
|
|
1
|
+
import { verifyMeshEnvelope } from '@agentplat/mesh-crypto';
|
|
2
|
+
import { canonicalizeMeshJson, compareMeshTimestamps, DEFAULT_MESH_PROTOCOL_LIMITS, validateMeshEnvelopeContext, } from '@agentplat/mesh-protocol';
|
|
3
|
+
import { assertFrozenMeshCoordinationInboundState, createMeshAllocationInboundRuntimeState, createMeshDiscoveryInboundRuntimeState, createMeshObjectiveInboundRuntimeState, synchronizeMeshObjectiveLogicalTime, } from './coordination-inbound-state.js';
|
|
4
|
+
import { evaluateVerifiedMeshDiscoveryEnvelope } from './coordination-discovery.js';
|
|
5
|
+
import { createMeshDiscoveryRuntimeState } from './coordination-discovery-state.js';
|
|
6
|
+
import { createMeshObjectiveWorkRuntimeState, evaluateVerifiedMeshObjectiveEnvelope, } from './coordination-objective-work.js';
|
|
7
|
+
import { evaluateVerifiedMeshAllocationEnvelope } from './coordination-allocation.js';
|
|
8
|
+
import { createMeshAllocationRuntimeState } from './coordination-allocation-state.js';
|
|
9
|
+
import { assertMeshLogicalTime, createFrozenRecord, recordEntries, } from './state.js';
|
|
10
|
+
const cryptoRejectionCodes = new Set([
|
|
11
|
+
'crypto_unavailable',
|
|
12
|
+
'crypto_operation_failed',
|
|
13
|
+
'invalid_envelope',
|
|
14
|
+
'invalid_verification_time',
|
|
15
|
+
'unsupported_algorithm',
|
|
16
|
+
'payload_hash_mismatch',
|
|
17
|
+
'key_not_found',
|
|
18
|
+
'key_resolution_failed',
|
|
19
|
+
'key_binding_mismatch',
|
|
20
|
+
'invalid_key_record',
|
|
21
|
+
'invalid_key_material',
|
|
22
|
+
'key_not_yet_valid',
|
|
23
|
+
'key_expired',
|
|
24
|
+
'key_revoked',
|
|
25
|
+
'signature_invalid',
|
|
26
|
+
]);
|
|
27
|
+
const trustedObjectiveDecisionBindings = new WeakMap();
|
|
28
|
+
/**
|
|
29
|
+
* Binds local trust dependencies once, outside the remote-message path.
|
|
30
|
+
*/
|
|
31
|
+
export function createMeshDiscoveryInboundProcessor(options) {
|
|
32
|
+
assertProcessorOptions(options);
|
|
33
|
+
const resolve = options.resolver.resolve.bind(options.resolver);
|
|
34
|
+
const crypto = snapshotCrypto(options.crypto);
|
|
35
|
+
const configuration = Object.freeze({
|
|
36
|
+
resolver: Object.freeze({ resolve }),
|
|
37
|
+
cryptoPolicy: Object.freeze({
|
|
38
|
+
allowedAlgorithms: Object.freeze([
|
|
39
|
+
...options.cryptoPolicy.allowedAlgorithms,
|
|
40
|
+
]),
|
|
41
|
+
}),
|
|
42
|
+
crypto,
|
|
43
|
+
...(options.protocolOptions === undefined
|
|
44
|
+
? {}
|
|
45
|
+
: {
|
|
46
|
+
protocolOptions: Object.freeze({
|
|
47
|
+
...(options.protocolOptions.limits === undefined
|
|
48
|
+
? {}
|
|
49
|
+
: {
|
|
50
|
+
limits: Object.freeze({
|
|
51
|
+
...options.protocolOptions.limits,
|
|
52
|
+
}),
|
|
53
|
+
}),
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
56
|
+
...(options.supportedCriticalExtensions === undefined
|
|
57
|
+
? {}
|
|
58
|
+
: {
|
|
59
|
+
supportedCriticalExtensions: Object.freeze([
|
|
60
|
+
...options.supportedCriticalExtensions,
|
|
61
|
+
]),
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
return Object.freeze({
|
|
65
|
+
process: (state, request) => processMeshDiscoveryEnvelope(state, request, configuration),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Binds local trust dependencies once, outside the Objective message path. */
|
|
69
|
+
export function createMeshObjectiveInboundProcessor(options) {
|
|
70
|
+
assertProcessorOptions(options);
|
|
71
|
+
const resolve = options.resolver.resolve.bind(options.resolver);
|
|
72
|
+
const crypto = snapshotCrypto(options.crypto);
|
|
73
|
+
const configuration = Object.freeze({
|
|
74
|
+
resolver: Object.freeze({ resolve }),
|
|
75
|
+
cryptoPolicy: Object.freeze({
|
|
76
|
+
allowedAlgorithms: Object.freeze([
|
|
77
|
+
...options.cryptoPolicy.allowedAlgorithms,
|
|
78
|
+
]),
|
|
79
|
+
}),
|
|
80
|
+
crypto,
|
|
81
|
+
...(options.protocolOptions === undefined
|
|
82
|
+
? {}
|
|
83
|
+
: {
|
|
84
|
+
protocolOptions: Object.freeze({
|
|
85
|
+
...(options.protocolOptions.limits === undefined
|
|
86
|
+
? {}
|
|
87
|
+
: {
|
|
88
|
+
limits: Object.freeze({
|
|
89
|
+
...options.protocolOptions.limits,
|
|
90
|
+
}),
|
|
91
|
+
}),
|
|
92
|
+
}),
|
|
93
|
+
}),
|
|
94
|
+
...(options.supportedCriticalExtensions === undefined
|
|
95
|
+
? {}
|
|
96
|
+
: {
|
|
97
|
+
supportedCriticalExtensions: Object.freeze([
|
|
98
|
+
...options.supportedCriticalExtensions,
|
|
99
|
+
]),
|
|
100
|
+
}),
|
|
101
|
+
});
|
|
102
|
+
return Object.freeze({
|
|
103
|
+
process: async (state, request) => {
|
|
104
|
+
const decision = await processMeshObjectiveEnvelope(state, request, configuration);
|
|
105
|
+
trustedObjectiveDecisionBindings.set(decision, Object.freeze({ state, request }));
|
|
106
|
+
return decision;
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** Binds local trust dependencies once, outside the allocation message path. */
|
|
111
|
+
export function createMeshAllocationInboundProcessor(options) {
|
|
112
|
+
assertProcessorOptions(options);
|
|
113
|
+
const resolve = options.resolver.resolve.bind(options.resolver);
|
|
114
|
+
const crypto = snapshotCrypto(options.crypto);
|
|
115
|
+
const configuration = Object.freeze({
|
|
116
|
+
resolver: Object.freeze({ resolve }),
|
|
117
|
+
cryptoPolicy: Object.freeze({
|
|
118
|
+
allowedAlgorithms: Object.freeze([
|
|
119
|
+
...options.cryptoPolicy.allowedAlgorithms,
|
|
120
|
+
]),
|
|
121
|
+
}),
|
|
122
|
+
crypto,
|
|
123
|
+
...(options.protocolOptions === undefined
|
|
124
|
+
? {}
|
|
125
|
+
: {
|
|
126
|
+
protocolOptions: Object.freeze({
|
|
127
|
+
...(options.protocolOptions.limits === undefined
|
|
128
|
+
? {}
|
|
129
|
+
: {
|
|
130
|
+
limits: Object.freeze({
|
|
131
|
+
...options.protocolOptions.limits,
|
|
132
|
+
}),
|
|
133
|
+
}),
|
|
134
|
+
}),
|
|
135
|
+
}),
|
|
136
|
+
...(options.supportedCriticalExtensions === undefined
|
|
137
|
+
? {}
|
|
138
|
+
: {
|
|
139
|
+
supportedCriticalExtensions: Object.freeze([
|
|
140
|
+
...options.supportedCriticalExtensions,
|
|
141
|
+
]),
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
return Object.freeze({
|
|
145
|
+
process: (state, request) => processMeshAllocationEnvelope(state, request, configuration),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Package-internal attestation used by the reference topic driver. It prevents
|
|
150
|
+
* a processor wrapper from substituting a decision produced for different
|
|
151
|
+
* state or input while keeping the public decision contract serializable.
|
|
152
|
+
*/
|
|
153
|
+
export function isTrustedMeshObjectiveInboundDecision(decision, state, request) {
|
|
154
|
+
if (!decision || typeof decision !== 'object')
|
|
155
|
+
return false;
|
|
156
|
+
const binding = trustedObjectiveDecisionBindings.get(decision);
|
|
157
|
+
return binding?.state === state && binding.request === request;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Authenticates one signed discovery envelope before admission, replay and
|
|
161
|
+
* projection. Rejections after replay admission retain only security state.
|
|
162
|
+
*/
|
|
163
|
+
async function processMeshDiscoveryEnvelope(state, request, configuration) {
|
|
164
|
+
assertRuntimeState(state);
|
|
165
|
+
assertRequest(request);
|
|
166
|
+
const receivedAt = request.receivedAt;
|
|
167
|
+
const verifiedAt = request.verifiedAt;
|
|
168
|
+
const resolver = configuration.resolver;
|
|
169
|
+
const crypto = configuration.crypto;
|
|
170
|
+
const cryptoPolicy = configuration.cryptoPolicy;
|
|
171
|
+
const protocolOptions = configuration.protocolOptions;
|
|
172
|
+
const supportedCriticalExtensions = configuration.supportedCriticalExtensions;
|
|
173
|
+
assertMeshLogicalTime(receivedAt);
|
|
174
|
+
if (receivedAt <
|
|
175
|
+
Math.max(state.coordination.lastLogicalTime, state.discovery.lastLogicalTime, state.inbound.lastLogicalTime)) {
|
|
176
|
+
return rejection(state, 'logical_time_regressed');
|
|
177
|
+
}
|
|
178
|
+
const context = validateMeshEnvelopeContext(request.envelope, {
|
|
179
|
+
tenantId: state.discovery.identity.tenantId,
|
|
180
|
+
meshId: state.discovery.identity.meshId,
|
|
181
|
+
peerId: state.discovery.identity.peerId,
|
|
182
|
+
receivedAt: verifiedAt,
|
|
183
|
+
subscribedTopics: state.discovery.subscriptions,
|
|
184
|
+
...(supportedCriticalExtensions === undefined
|
|
185
|
+
? {}
|
|
186
|
+
: { supportedCriticalExtensions }),
|
|
187
|
+
}, protocolOptions);
|
|
188
|
+
if (!context.ok) {
|
|
189
|
+
return rejection(state, contextRejection(context.issues[0]?.code, request.envelope, state.discovery.subscriptions));
|
|
190
|
+
}
|
|
191
|
+
const contextualEnvelope = context.value;
|
|
192
|
+
const familyFailure = validateDiscoveryFamily(contextualEnvelope);
|
|
193
|
+
if (familyFailure)
|
|
194
|
+
return rejection(state, familyFailure);
|
|
195
|
+
let verification;
|
|
196
|
+
try {
|
|
197
|
+
verification = await verifyMeshEnvelope({
|
|
198
|
+
envelope: contextualEnvelope,
|
|
199
|
+
resolver,
|
|
200
|
+
policy: cryptoPolicy,
|
|
201
|
+
verifiedAt,
|
|
202
|
+
crypto,
|
|
203
|
+
protocolOptions,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return rejection(state, 'crypto_operation_failed');
|
|
208
|
+
}
|
|
209
|
+
let verifiedEnvelope;
|
|
210
|
+
try {
|
|
211
|
+
if (!verification ||
|
|
212
|
+
typeof verification !== 'object' ||
|
|
213
|
+
verification.verified !== true) {
|
|
214
|
+
const code = verification?.verified === false &&
|
|
215
|
+
cryptoRejectionCodes.has(verification.code)
|
|
216
|
+
? verification.code
|
|
217
|
+
: 'crypto_operation_failed';
|
|
218
|
+
return rejection(state, code);
|
|
219
|
+
}
|
|
220
|
+
const rebound = validateMeshEnvelopeContext(verification.envelope, {
|
|
221
|
+
tenantId: state.discovery.identity.tenantId,
|
|
222
|
+
meshId: state.discovery.identity.meshId,
|
|
223
|
+
peerId: state.discovery.identity.peerId,
|
|
224
|
+
receivedAt: verifiedAt,
|
|
225
|
+
subscribedTopics: state.discovery.subscriptions,
|
|
226
|
+
...(supportedCriticalExtensions === undefined
|
|
227
|
+
? {}
|
|
228
|
+
: { supportedCriticalExtensions }),
|
|
229
|
+
}, protocolOptions);
|
|
230
|
+
if (!rebound.ok ||
|
|
231
|
+
!sameCanonicalEnvelope(contextualEnvelope, rebound.value, protocolOptions)) {
|
|
232
|
+
return rejection(state, 'crypto_operation_failed');
|
|
233
|
+
}
|
|
234
|
+
verifiedEnvelope =
|
|
235
|
+
rebound.value;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return rejection(state, 'crypto_operation_failed');
|
|
239
|
+
}
|
|
240
|
+
const authorityFailure = validateAdmissionAndOwnership(state, verifiedEnvelope, verifiedAt);
|
|
241
|
+
if (authorityFailure)
|
|
242
|
+
return rejection(state, authorityFailure);
|
|
243
|
+
const replay = advanceReplayState(state.inbound, verifiedEnvelope, receivedAt);
|
|
244
|
+
if ('code' in replay)
|
|
245
|
+
return rejection(state, replay.code);
|
|
246
|
+
const replayRuntime = createMeshDiscoveryInboundRuntimeState(state.coordination, state.discovery, replay.inbound);
|
|
247
|
+
if (isAuthenticatedDomainDuplicate(state, verifiedEnvelope)) {
|
|
248
|
+
return acceptance(replayRuntime, verifiedEnvelope, true);
|
|
249
|
+
}
|
|
250
|
+
const projection = evaluateVerifiedMeshDiscoveryEnvelope(createMeshDiscoveryRuntimeState(state.coordination, state.discovery), {
|
|
251
|
+
envelope: verifiedEnvelope,
|
|
252
|
+
verifiedAt,
|
|
253
|
+
receivedAt,
|
|
254
|
+
...(supportedCriticalExtensions === undefined
|
|
255
|
+
? {}
|
|
256
|
+
: { supportedCriticalExtensions }),
|
|
257
|
+
});
|
|
258
|
+
if (!projection.accepted) {
|
|
259
|
+
return rejection(replayRuntime, projection.code);
|
|
260
|
+
}
|
|
261
|
+
return acceptance(createMeshDiscoveryInboundRuntimeState(projection.state.coordination, projection.state.discovery, replay.inbound), verifiedEnvelope, projection.duplicate);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Authenticates one signed Objective envelope before admission, replay and
|
|
265
|
+
* projection. Rejections after replay admission retain only security state.
|
|
266
|
+
*/
|
|
267
|
+
async function processMeshObjectiveEnvelope(state, request, configuration) {
|
|
268
|
+
assertObjectiveRuntimeState(state);
|
|
269
|
+
assertObjectiveRequest(request);
|
|
270
|
+
const receivedAt = request.receivedAt;
|
|
271
|
+
const verifiedAt = request.verifiedAt;
|
|
272
|
+
assertMeshLogicalTime(receivedAt);
|
|
273
|
+
if (receivedAt <
|
|
274
|
+
Math.max(state.coordination.lastLogicalTime, state.discovery.lastLogicalTime, state.objectives.lastLogicalTime, state.inbound.lastLogicalTime)) {
|
|
275
|
+
return objectiveRejection(state, 'logical_time_regressed');
|
|
276
|
+
}
|
|
277
|
+
const context = validateMeshEnvelopeContext(request.envelope, {
|
|
278
|
+
tenantId: state.discovery.identity.tenantId,
|
|
279
|
+
meshId: state.discovery.identity.meshId,
|
|
280
|
+
peerId: state.discovery.identity.peerId,
|
|
281
|
+
receivedAt: verifiedAt,
|
|
282
|
+
subscribedTopics: state.discovery.subscriptions,
|
|
283
|
+
...(configuration.supportedCriticalExtensions === undefined
|
|
284
|
+
? {}
|
|
285
|
+
: {
|
|
286
|
+
supportedCriticalExtensions: configuration.supportedCriticalExtensions,
|
|
287
|
+
}),
|
|
288
|
+
}, configuration.protocolOptions);
|
|
289
|
+
if (!context.ok) {
|
|
290
|
+
return objectiveRejection(state, objectiveContextRejection(context.issues[0]?.code, request.envelope, state.discovery.subscriptions));
|
|
291
|
+
}
|
|
292
|
+
const contextualEnvelope = context.value;
|
|
293
|
+
const familyFailure = validateObjectiveFamily(contextualEnvelope);
|
|
294
|
+
if (familyFailure)
|
|
295
|
+
return objectiveRejection(state, familyFailure);
|
|
296
|
+
let verification;
|
|
297
|
+
try {
|
|
298
|
+
verification = await verifyMeshEnvelope({
|
|
299
|
+
envelope: contextualEnvelope,
|
|
300
|
+
resolver: configuration.resolver,
|
|
301
|
+
policy: configuration.cryptoPolicy,
|
|
302
|
+
verifiedAt,
|
|
303
|
+
crypto: configuration.crypto,
|
|
304
|
+
protocolOptions: configuration.protocolOptions,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return objectiveRejection(state, 'crypto_operation_failed');
|
|
309
|
+
}
|
|
310
|
+
let verifiedEnvelope;
|
|
311
|
+
try {
|
|
312
|
+
if (!verification ||
|
|
313
|
+
typeof verification !== 'object' ||
|
|
314
|
+
verification.verified !== true) {
|
|
315
|
+
const code = verification?.verified === false &&
|
|
316
|
+
cryptoRejectionCodes.has(verification.code)
|
|
317
|
+
? verification.code
|
|
318
|
+
: 'crypto_operation_failed';
|
|
319
|
+
return objectiveRejection(state, code);
|
|
320
|
+
}
|
|
321
|
+
const rebound = validateMeshEnvelopeContext(verification.envelope, {
|
|
322
|
+
tenantId: state.discovery.identity.tenantId,
|
|
323
|
+
meshId: state.discovery.identity.meshId,
|
|
324
|
+
peerId: state.discovery.identity.peerId,
|
|
325
|
+
receivedAt: verifiedAt,
|
|
326
|
+
subscribedTopics: state.discovery.subscriptions,
|
|
327
|
+
...(configuration.supportedCriticalExtensions === undefined
|
|
328
|
+
? {}
|
|
329
|
+
: {
|
|
330
|
+
supportedCriticalExtensions: configuration.supportedCriticalExtensions,
|
|
331
|
+
}),
|
|
332
|
+
}, configuration.protocolOptions);
|
|
333
|
+
if (!rebound.ok ||
|
|
334
|
+
!sameCanonicalEnvelope(contextualEnvelope, rebound.value, configuration.protocolOptions)) {
|
|
335
|
+
return objectiveRejection(state, 'crypto_operation_failed');
|
|
336
|
+
}
|
|
337
|
+
verifiedEnvelope =
|
|
338
|
+
rebound.value;
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
return objectiveRejection(state, 'crypto_operation_failed');
|
|
342
|
+
}
|
|
343
|
+
const admissionFailure = validateObjectiveAdmission(state, verifiedEnvelope, verifiedAt);
|
|
344
|
+
if (admissionFailure)
|
|
345
|
+
return objectiveRejection(state, admissionFailure);
|
|
346
|
+
const authorityFailure = validateObjectiveIssuerAuthority(state, verifiedEnvelope, verifiedAt);
|
|
347
|
+
if (authorityFailure)
|
|
348
|
+
return objectiveRejection(state, authorityFailure);
|
|
349
|
+
const replay = advanceReplayState(state.inbound, verifiedEnvelope, receivedAt);
|
|
350
|
+
if ('code' in replay)
|
|
351
|
+
return objectiveRejection(state, replay.code);
|
|
352
|
+
const replayRuntime = createMeshObjectiveInboundRuntimeState(state.coordination, state.discovery, state.objectives, replay.inbound);
|
|
353
|
+
const projection = evaluateVerifiedMeshObjectiveEnvelope(createMeshObjectiveWorkRuntimeState(state.coordination, state.discovery, synchronizeMeshObjectiveLogicalTime(state.objectives, state.coordination.lastLogicalTime)), {
|
|
354
|
+
envelope: verifiedEnvelope,
|
|
355
|
+
verifiedAt,
|
|
356
|
+
receivedAt,
|
|
357
|
+
...(configuration.supportedCriticalExtensions === undefined
|
|
358
|
+
? {}
|
|
359
|
+
: {
|
|
360
|
+
supportedCriticalExtensions: configuration.supportedCriticalExtensions,
|
|
361
|
+
}),
|
|
362
|
+
});
|
|
363
|
+
if (!projection.accepted)
|
|
364
|
+
return objectiveRejection(replayRuntime, projection.code);
|
|
365
|
+
return objectiveAcceptance(createMeshObjectiveInboundRuntimeState(projection.state.coordination, projection.state.discovery, projection.state.objectives, replay.inbound), verifiedEnvelope, projection.duplicate);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Authenticates one signed allocation envelope before admission, replay and
|
|
369
|
+
* the authoritative allocation reducer. Rejections after replay retain only
|
|
370
|
+
* inbound security accounting.
|
|
371
|
+
*/
|
|
372
|
+
async function processMeshAllocationEnvelope(state, request, configuration) {
|
|
373
|
+
assertAllocationRuntimeState(state);
|
|
374
|
+
assertAllocationRequest(request);
|
|
375
|
+
const receivedAt = request.receivedAt;
|
|
376
|
+
const verifiedAt = request.verifiedAt;
|
|
377
|
+
assertMeshLogicalTime(receivedAt);
|
|
378
|
+
if (receivedAt <
|
|
379
|
+
Math.max(state.coordination.lastLogicalTime, state.discovery.lastLogicalTime, state.objectives.lastLogicalTime, state.allocation.lastLogicalTime, state.inbound.lastLogicalTime)) {
|
|
380
|
+
return allocationRejection(state, 'logical_time_regressed');
|
|
381
|
+
}
|
|
382
|
+
const context = validateMeshEnvelopeContext(request.envelope, {
|
|
383
|
+
tenantId: state.discovery.identity.tenantId,
|
|
384
|
+
meshId: state.discovery.identity.meshId,
|
|
385
|
+
peerId: state.discovery.identity.peerId,
|
|
386
|
+
receivedAt: verifiedAt,
|
|
387
|
+
subscribedTopics: state.discovery.subscriptions,
|
|
388
|
+
...(configuration.supportedCriticalExtensions === undefined
|
|
389
|
+
? {}
|
|
390
|
+
: {
|
|
391
|
+
supportedCriticalExtensions: configuration.supportedCriticalExtensions,
|
|
392
|
+
}),
|
|
393
|
+
}, configuration.protocolOptions);
|
|
394
|
+
if (!context.ok) {
|
|
395
|
+
return allocationRejection(state, allocationContextRejection(context.issues[0]?.code, request.envelope, state.discovery.subscriptions));
|
|
396
|
+
}
|
|
397
|
+
const contextualEnvelope = context.value;
|
|
398
|
+
const familyFailure = validateAllocationFamily(contextualEnvelope);
|
|
399
|
+
if (familyFailure)
|
|
400
|
+
return allocationRejection(state, familyFailure);
|
|
401
|
+
let verification;
|
|
402
|
+
try {
|
|
403
|
+
verification = await verifyMeshEnvelope({
|
|
404
|
+
envelope: contextualEnvelope,
|
|
405
|
+
resolver: configuration.resolver,
|
|
406
|
+
policy: configuration.cryptoPolicy,
|
|
407
|
+
verifiedAt,
|
|
408
|
+
crypto: configuration.crypto,
|
|
409
|
+
protocolOptions: configuration.protocolOptions,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
return allocationRejection(state, 'crypto_operation_failed');
|
|
414
|
+
}
|
|
415
|
+
let verifiedEnvelope;
|
|
416
|
+
try {
|
|
417
|
+
if (!verification ||
|
|
418
|
+
typeof verification !== 'object' ||
|
|
419
|
+
verification.verified !== true) {
|
|
420
|
+
const code = verification?.verified === false &&
|
|
421
|
+
cryptoRejectionCodes.has(verification.code)
|
|
422
|
+
? verification.code
|
|
423
|
+
: 'crypto_operation_failed';
|
|
424
|
+
return allocationRejection(state, code);
|
|
425
|
+
}
|
|
426
|
+
const rebound = validateMeshEnvelopeContext(verification.envelope, {
|
|
427
|
+
tenantId: state.discovery.identity.tenantId,
|
|
428
|
+
meshId: state.discovery.identity.meshId,
|
|
429
|
+
peerId: state.discovery.identity.peerId,
|
|
430
|
+
receivedAt: verifiedAt,
|
|
431
|
+
subscribedTopics: state.discovery.subscriptions,
|
|
432
|
+
...(configuration.supportedCriticalExtensions === undefined
|
|
433
|
+
? {}
|
|
434
|
+
: {
|
|
435
|
+
supportedCriticalExtensions: configuration.supportedCriticalExtensions,
|
|
436
|
+
}),
|
|
437
|
+
}, configuration.protocolOptions);
|
|
438
|
+
if (!rebound.ok ||
|
|
439
|
+
!sameCanonicalEnvelope(contextualEnvelope, rebound.value, configuration.protocolOptions)) {
|
|
440
|
+
return allocationRejection(state, 'crypto_operation_failed');
|
|
441
|
+
}
|
|
442
|
+
verifiedEnvelope =
|
|
443
|
+
rebound.value;
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
return allocationRejection(state, 'crypto_operation_failed');
|
|
447
|
+
}
|
|
448
|
+
const admissionFailure = validateAllocationAdmission(state, verifiedEnvelope, verifiedAt);
|
|
449
|
+
if (admissionFailure)
|
|
450
|
+
return allocationRejection(state, admissionFailure);
|
|
451
|
+
const replay = advanceReplayState(state.inbound, verifiedEnvelope, receivedAt);
|
|
452
|
+
if ('code' in replay)
|
|
453
|
+
return allocationRejection(state, replay.code);
|
|
454
|
+
const replayRuntime = createMeshAllocationInboundRuntimeState(state.coordination, state.discovery, state.objectives, state.allocation, replay.inbound);
|
|
455
|
+
const projection = evaluateVerifiedMeshAllocationEnvelope(createMeshAllocationRuntimeState(state.coordination, state.discovery, state.objectives, state.allocation), {
|
|
456
|
+
envelope: verifiedEnvelope,
|
|
457
|
+
verifiedAt,
|
|
458
|
+
receivedAt,
|
|
459
|
+
...(configuration.supportedCriticalExtensions === undefined
|
|
460
|
+
? {}
|
|
461
|
+
: {
|
|
462
|
+
supportedCriticalExtensions: configuration.supportedCriticalExtensions,
|
|
463
|
+
}),
|
|
464
|
+
});
|
|
465
|
+
if (!projection.accepted)
|
|
466
|
+
return allocationRejection(replayRuntime, projection.code);
|
|
467
|
+
return allocationAcceptance(createMeshAllocationInboundRuntimeState(projection.state.coordination, projection.state.discovery, projection.state.objectives, projection.state.allocation, replay.inbound), verifiedEnvelope, projection.duplicate);
|
|
468
|
+
}
|
|
469
|
+
function validateObjectiveFamily(envelope) {
|
|
470
|
+
const payload = envelope.payload;
|
|
471
|
+
if (payload.type !== 'objective.announce' &&
|
|
472
|
+
payload.type !== 'objective.revise' &&
|
|
473
|
+
payload.type !== 'objective.cancel') {
|
|
474
|
+
return 'unsupported_message_type';
|
|
475
|
+
}
|
|
476
|
+
if (envelope.audience.kind === 'mesh' &&
|
|
477
|
+
envelope.audience.topic !== 'objective') {
|
|
478
|
+
return 'audience_mismatch';
|
|
479
|
+
}
|
|
480
|
+
return payload.type !== 'objective.cancel' &&
|
|
481
|
+
envelope.sender.peerId !== payload.issuerPeerId
|
|
482
|
+
? 'issuer_not_authorized'
|
|
483
|
+
: undefined;
|
|
484
|
+
}
|
|
485
|
+
function validateAllocationFamily(envelope) {
|
|
486
|
+
const type = envelope.payload.type;
|
|
487
|
+
if (type !== 'work.offer' &&
|
|
488
|
+
type !== 'work.bid' &&
|
|
489
|
+
type !== 'work.award' &&
|
|
490
|
+
type !== 'work.accept' &&
|
|
491
|
+
type !== 'work.decline' &&
|
|
492
|
+
type !== 'work.progress' &&
|
|
493
|
+
type !== 'work.checkpoint' &&
|
|
494
|
+
type !== 'work.result' &&
|
|
495
|
+
type !== 'work.release' &&
|
|
496
|
+
type !== 'work.cancel' &&
|
|
497
|
+
type !== 'lease.renew' &&
|
|
498
|
+
type !== 'lease.takeover_proposal' &&
|
|
499
|
+
type !== 'lease.vote' &&
|
|
500
|
+
type !== 'lease.certificate') {
|
|
501
|
+
return 'unsupported_message_type';
|
|
502
|
+
}
|
|
503
|
+
return undefined;
|
|
504
|
+
}
|
|
505
|
+
function validateObjectiveAdmission(state, envelope, verifiedAt) {
|
|
506
|
+
const admission = state.discovery.admittedPeers[envelope.sender.peerId];
|
|
507
|
+
if (!admission)
|
|
508
|
+
return 'sender_not_admitted';
|
|
509
|
+
if (!admission.instanceIds.includes(envelope.sender.instanceId)) {
|
|
510
|
+
return 'sender_instance_not_admitted';
|
|
511
|
+
}
|
|
512
|
+
const expiry = compareMeshTimestamps(verifiedAt, admission.validUntil);
|
|
513
|
+
return !expiry.ok || expiry.value >= 0
|
|
514
|
+
? 'sender_admission_expired'
|
|
515
|
+
: undefined;
|
|
516
|
+
}
|
|
517
|
+
function validateAllocationAdmission(state, envelope, verifiedAt) {
|
|
518
|
+
const admission = state.discovery.admittedPeers[envelope.sender.peerId];
|
|
519
|
+
if (!admission)
|
|
520
|
+
return 'sender_not_admitted';
|
|
521
|
+
if (!admission.instanceIds.includes(envelope.sender.instanceId)) {
|
|
522
|
+
return 'sender_instance_not_admitted';
|
|
523
|
+
}
|
|
524
|
+
const expiry = compareMeshTimestamps(verifiedAt, admission.validUntil);
|
|
525
|
+
return !expiry.ok || expiry.value >= 0
|
|
526
|
+
? 'sender_admission_expired'
|
|
527
|
+
: undefined;
|
|
528
|
+
}
|
|
529
|
+
function validateObjectiveIssuerAuthority(state, envelope, verifiedAt) {
|
|
530
|
+
const authority = state.objectives.issuerAuthorities[envelope.sender.peerId];
|
|
531
|
+
if (!authority)
|
|
532
|
+
return 'issuer_not_authorized';
|
|
533
|
+
if (!authority.keyIds.includes(envelope.proof.keyId)) {
|
|
534
|
+
return 'issuer_key_not_authorized';
|
|
535
|
+
}
|
|
536
|
+
const expiry = compareMeshTimestamps(verifiedAt, authority.validUntil);
|
|
537
|
+
return !expiry.ok || expiry.value >= 0
|
|
538
|
+
? 'issuer_authority_expired'
|
|
539
|
+
: undefined;
|
|
540
|
+
}
|
|
541
|
+
function validateDiscoveryFamily(envelope) {
|
|
542
|
+
const type = envelope.payload.type;
|
|
543
|
+
if (type !== 'peer.card' &&
|
|
544
|
+
type !== 'peer.goodbye' &&
|
|
545
|
+
type !== 'capability.advertise' &&
|
|
546
|
+
type !== 'capability.withdraw') {
|
|
547
|
+
return 'unsupported_message_type';
|
|
548
|
+
}
|
|
549
|
+
if (envelope.audience.kind !== 'mesh')
|
|
550
|
+
return undefined;
|
|
551
|
+
const expectedTopic = type === 'peer.card' || type === 'peer.goodbye'
|
|
552
|
+
? 'membership'
|
|
553
|
+
: 'capability';
|
|
554
|
+
return envelope.audience.topic === expectedTopic
|
|
555
|
+
? undefined
|
|
556
|
+
: 'audience_mismatch';
|
|
557
|
+
}
|
|
558
|
+
function validateAdmissionAndOwnership(state, envelope, verifiedAt) {
|
|
559
|
+
const admission = state.discovery.admittedPeers[envelope.sender.peerId];
|
|
560
|
+
if (!admission)
|
|
561
|
+
return 'sender_not_admitted';
|
|
562
|
+
if (!admission.instanceIds.includes(envelope.sender.instanceId)) {
|
|
563
|
+
return 'sender_instance_not_admitted';
|
|
564
|
+
}
|
|
565
|
+
const admissionExpiry = compareMeshTimestamps(verifiedAt, admission.validUntil);
|
|
566
|
+
if (!admissionExpiry.ok || admissionExpiry.value >= 0) {
|
|
567
|
+
return 'sender_admission_expired';
|
|
568
|
+
}
|
|
569
|
+
const payload = envelope.payload;
|
|
570
|
+
switch (payload.type) {
|
|
571
|
+
case 'peer.card':
|
|
572
|
+
return payload.subjectPeerId === envelope.sender.peerId &&
|
|
573
|
+
payload.instanceId === envelope.sender.instanceId
|
|
574
|
+
? undefined
|
|
575
|
+
: 'message_not_authorized';
|
|
576
|
+
case 'peer.goodbye':
|
|
577
|
+
return payload.instanceId === envelope.sender.instanceId
|
|
578
|
+
? undefined
|
|
579
|
+
: 'message_not_authorized';
|
|
580
|
+
case 'capability.advertise': {
|
|
581
|
+
if (payload.ownerPeerId !== envelope.sender.peerId) {
|
|
582
|
+
return 'message_not_authorized';
|
|
583
|
+
}
|
|
584
|
+
const card = state.discovery.peerCards[payload.ownerPeerId];
|
|
585
|
+
return card && card.instanceId !== envelope.sender.instanceId
|
|
586
|
+
? 'message_not_authorized'
|
|
587
|
+
: undefined;
|
|
588
|
+
}
|
|
589
|
+
case 'capability.withdraw': {
|
|
590
|
+
const capability = state.discovery.capabilities[JSON.stringify([envelope.sender.peerId, payload.capabilityId])];
|
|
591
|
+
return capability && capability.instanceId !== envelope.sender.instanceId
|
|
592
|
+
? 'message_not_authorized'
|
|
593
|
+
: undefined;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function advanceReplayState(inbound, envelope, receivedAt) {
|
|
598
|
+
const retainedIds = recordEntries(inbound.messageIds).filter(([, expiresAt]) => expiresAt > receivedAt);
|
|
599
|
+
if (retainedIds.some(([messageId]) => messageId === envelope.messageId)) {
|
|
600
|
+
return { code: 'message_replayed' };
|
|
601
|
+
}
|
|
602
|
+
if (retainedIds.length >= inbound.limits.maximumTrackedMessageIds ||
|
|
603
|
+
receivedAt > Number.MAX_SAFE_INTEGER - inbound.limits.messageIdRetentionMs) {
|
|
604
|
+
return { code: 'replay_capacity_exceeded' };
|
|
605
|
+
}
|
|
606
|
+
const replayKey = JSON.stringify([
|
|
607
|
+
envelope.sender.peerId,
|
|
608
|
+
envelope.sender.instanceId,
|
|
609
|
+
]);
|
|
610
|
+
const current = inbound.replay[replayKey];
|
|
611
|
+
if (!current &&
|
|
612
|
+
Object.keys(inbound.replay).length >= inbound.limits.maximumReplayWindows) {
|
|
613
|
+
return { code: 'replay_capacity_exceeded' };
|
|
614
|
+
}
|
|
615
|
+
let nextWindow;
|
|
616
|
+
if (!current) {
|
|
617
|
+
nextWindow = Object.freeze({
|
|
618
|
+
highestSequence: envelope.sequence,
|
|
619
|
+
seenOffsets: Object.freeze([0]),
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
else if (envelope.sequence > current.highestSequence) {
|
|
623
|
+
const advance = envelope.sequence - current.highestSequence;
|
|
624
|
+
nextWindow = Object.freeze({
|
|
625
|
+
highestSequence: envelope.sequence,
|
|
626
|
+
seenOffsets: Object.freeze(advance >= inbound.limits.replayWindowSize
|
|
627
|
+
? [0]
|
|
628
|
+
: [
|
|
629
|
+
0,
|
|
630
|
+
...current.seenOffsets
|
|
631
|
+
.map((offset) => offset + advance)
|
|
632
|
+
.filter((offset) => offset < inbound.limits.replayWindowSize),
|
|
633
|
+
]),
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
const offset = current.highestSequence - envelope.sequence;
|
|
638
|
+
if (offset >= inbound.limits.replayWindowSize) {
|
|
639
|
+
return { code: 'sequence_outside_window' };
|
|
640
|
+
}
|
|
641
|
+
if (current.seenOffsets.includes(offset)) {
|
|
642
|
+
return { code: 'message_replayed' };
|
|
643
|
+
}
|
|
644
|
+
nextWindow = Object.freeze({
|
|
645
|
+
highestSequence: current.highestSequence,
|
|
646
|
+
seenOffsets: Object.freeze([...current.seenOffsets, offset].sort((left, right) => left - right)),
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
inbound: Object.freeze({
|
|
651
|
+
...inbound,
|
|
652
|
+
replay: createFrozenRecord([
|
|
653
|
+
...recordEntries(inbound.replay).filter(([key]) => key !== replayKey),
|
|
654
|
+
[replayKey, nextWindow],
|
|
655
|
+
]),
|
|
656
|
+
messageIds: createFrozenRecord([
|
|
657
|
+
...retainedIds,
|
|
658
|
+
[envelope.messageId, receivedAt + inbound.limits.messageIdRetentionMs],
|
|
659
|
+
]),
|
|
660
|
+
lastLogicalTime: receivedAt,
|
|
661
|
+
}),
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function isAuthenticatedDomainDuplicate(state, envelope) {
|
|
665
|
+
const recordId = envelope.payload.type === 'peer.card' ||
|
|
666
|
+
envelope.payload.type === 'peer.goodbye'
|
|
667
|
+
? envelope.payload.peerCardId
|
|
668
|
+
: envelope.payload.advertisementId;
|
|
669
|
+
const record = state.coordination.domainRecords[JSON.stringify([envelope.payload.type, recordId])];
|
|
670
|
+
return (record !== undefined &&
|
|
671
|
+
record.contentDigest === envelope.payloadHash.slice('sha256:'.length) &&
|
|
672
|
+
isCurrentDuplicateHead(state, envelope));
|
|
673
|
+
}
|
|
674
|
+
function isCurrentDuplicateHead(state, envelope) {
|
|
675
|
+
const payload = envelope.payload;
|
|
676
|
+
switch (payload.type) {
|
|
677
|
+
case 'peer.card': {
|
|
678
|
+
const card = state.discovery.peerCards[payload.subjectPeerId];
|
|
679
|
+
return (card?.peerCardId === payload.peerCardId &&
|
|
680
|
+
card.cardRevision === payload.cardRevision &&
|
|
681
|
+
card.instanceId === envelope.sender.instanceId);
|
|
682
|
+
}
|
|
683
|
+
case 'peer.goodbye': {
|
|
684
|
+
const card = state.discovery.peerCards[envelope.sender.peerId];
|
|
685
|
+
return (card?.status === 'departed' &&
|
|
686
|
+
card.peerCardId === payload.peerCardId &&
|
|
687
|
+
card.cardRevision === payload.cardRevision &&
|
|
688
|
+
card.instanceId === envelope.sender.instanceId);
|
|
689
|
+
}
|
|
690
|
+
case 'capability.advertise': {
|
|
691
|
+
const capability = state.discovery.capabilities[JSON.stringify([payload.ownerPeerId, payload.capabilityId])];
|
|
692
|
+
return (capability?.advertisementId === payload.advertisementId &&
|
|
693
|
+
capability.capabilityRevision === payload.capabilityRevision &&
|
|
694
|
+
capability.instanceId === envelope.sender.instanceId);
|
|
695
|
+
}
|
|
696
|
+
case 'capability.withdraw': {
|
|
697
|
+
const capability = state.discovery.capabilities[JSON.stringify([envelope.sender.peerId, payload.capabilityId])];
|
|
698
|
+
return (capability?.status === 'withdrawn' &&
|
|
699
|
+
capability.advertisementId === payload.advertisementId &&
|
|
700
|
+
capability.capabilityRevision === payload.capabilityRevision &&
|
|
701
|
+
capability.instanceId === envelope.sender.instanceId);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
function sameCanonicalEnvelope(requested, verified, protocolOptions) {
|
|
706
|
+
const requestedCanonical = canonicalizeMeshJson(requested, protocolOptions);
|
|
707
|
+
const verifiedCanonical = canonicalizeMeshJson(verified, protocolOptions);
|
|
708
|
+
return (requestedCanonical.ok &&
|
|
709
|
+
verifiedCanonical.ok &&
|
|
710
|
+
requestedCanonical.value === verifiedCanonical.value);
|
|
711
|
+
}
|
|
712
|
+
function contextRejection(code, envelope, subscriptions) {
|
|
713
|
+
switch (code) {
|
|
714
|
+
case 'scope_mismatch':
|
|
715
|
+
return 'scope_mismatch';
|
|
716
|
+
case 'invalid_audience':
|
|
717
|
+
return envelope.audience.kind === 'mesh' &&
|
|
718
|
+
!subscriptions.includes(envelope.audience.topic)
|
|
719
|
+
? 'topic_not_subscribed'
|
|
720
|
+
: 'audience_mismatch';
|
|
721
|
+
case 'message_expired':
|
|
722
|
+
case 'message_from_future':
|
|
723
|
+
case 'unknown_critical_extension':
|
|
724
|
+
return code;
|
|
725
|
+
default:
|
|
726
|
+
return 'invalid_envelope';
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
function objectiveContextRejection(code, envelope, subscriptions) {
|
|
730
|
+
switch (code) {
|
|
731
|
+
case 'scope_mismatch':
|
|
732
|
+
return 'scope_mismatch';
|
|
733
|
+
case 'invalid_audience':
|
|
734
|
+
return envelope.audience.kind === 'mesh' &&
|
|
735
|
+
!subscriptions.includes('objective')
|
|
736
|
+
? 'topic_not_subscribed'
|
|
737
|
+
: 'audience_mismatch';
|
|
738
|
+
case 'message_expired':
|
|
739
|
+
case 'message_from_future':
|
|
740
|
+
case 'unknown_critical_extension':
|
|
741
|
+
return code;
|
|
742
|
+
default:
|
|
743
|
+
return 'invalid_envelope';
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function allocationContextRejection(code, envelope, subscriptions) {
|
|
747
|
+
switch (code) {
|
|
748
|
+
case 'scope_mismatch':
|
|
749
|
+
return 'scope_mismatch';
|
|
750
|
+
case 'invalid_audience':
|
|
751
|
+
return envelope.audience.kind === 'mesh' &&
|
|
752
|
+
!subscriptions.includes(envelope.audience.topic)
|
|
753
|
+
? 'topic_not_subscribed'
|
|
754
|
+
: 'audience_mismatch';
|
|
755
|
+
case 'message_expired':
|
|
756
|
+
case 'message_from_future':
|
|
757
|
+
case 'unknown_critical_extension':
|
|
758
|
+
return code;
|
|
759
|
+
default:
|
|
760
|
+
return 'invalid_verified_envelope';
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function acceptance(state, envelope, duplicate) {
|
|
764
|
+
return Object.freeze({
|
|
765
|
+
accepted: true,
|
|
766
|
+
duplicate,
|
|
767
|
+
envelope,
|
|
768
|
+
state,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
function rejection(state, code) {
|
|
772
|
+
return Object.freeze({ accepted: false, code, state });
|
|
773
|
+
}
|
|
774
|
+
function objectiveAcceptance(state, envelope, duplicate) {
|
|
775
|
+
return Object.freeze({ accepted: true, duplicate, envelope, state });
|
|
776
|
+
}
|
|
777
|
+
function allocationAcceptance(state, envelope, duplicate) {
|
|
778
|
+
return Object.freeze({ accepted: true, duplicate, envelope, state });
|
|
779
|
+
}
|
|
780
|
+
function objectiveRejection(state, code) {
|
|
781
|
+
return Object.freeze({ accepted: false, code, state });
|
|
782
|
+
}
|
|
783
|
+
function allocationRejection(state, code) {
|
|
784
|
+
return Object.freeze({ accepted: false, code, state });
|
|
785
|
+
}
|
|
786
|
+
function assertRuntimeState(state) {
|
|
787
|
+
if (!state ||
|
|
788
|
+
typeof state !== 'object' ||
|
|
789
|
+
Object.getPrototypeOf(state) !== Object.prototype ||
|
|
790
|
+
!hasExactDataKeys(state, ['coordination', 'discovery', 'inbound'], ['coordination', 'discovery', 'inbound']) ||
|
|
791
|
+
!Object.isFrozen(state)) {
|
|
792
|
+
throw new TypeError('Mesh discovery inbound runtime state must be immutable');
|
|
793
|
+
}
|
|
794
|
+
assertFrozenMeshCoordinationInboundState(state.inbound);
|
|
795
|
+
createMeshDiscoveryInboundRuntimeState(state.coordination, state.discovery, state.inbound);
|
|
796
|
+
}
|
|
797
|
+
function assertObjectiveRuntimeState(state) {
|
|
798
|
+
if (!state ||
|
|
799
|
+
typeof state !== 'object' ||
|
|
800
|
+
Object.getPrototypeOf(state) !== Object.prototype ||
|
|
801
|
+
!hasExactDataKeys(state, ['coordination', 'discovery', 'inbound', 'objectives'], ['coordination', 'discovery', 'inbound', 'objectives']) ||
|
|
802
|
+
!Object.isFrozen(state)) {
|
|
803
|
+
throw new TypeError('Mesh Objective inbound runtime state must be immutable');
|
|
804
|
+
}
|
|
805
|
+
assertFrozenMeshCoordinationInboundState(state.inbound);
|
|
806
|
+
createMeshObjectiveInboundRuntimeState(state.coordination, state.discovery, state.objectives, state.inbound);
|
|
807
|
+
}
|
|
808
|
+
function assertAllocationRuntimeState(state) {
|
|
809
|
+
if (!state ||
|
|
810
|
+
typeof state !== 'object' ||
|
|
811
|
+
Object.getPrototypeOf(state) !== Object.prototype ||
|
|
812
|
+
!hasExactDataKeys(state, ['allocation', 'coordination', 'discovery', 'inbound', 'objectives'], ['allocation', 'coordination', 'discovery', 'inbound', 'objectives']) ||
|
|
813
|
+
!Object.isFrozen(state)) {
|
|
814
|
+
throw new TypeError('Mesh allocation inbound runtime state must be immutable');
|
|
815
|
+
}
|
|
816
|
+
assertFrozenMeshCoordinationInboundState(state.inbound);
|
|
817
|
+
createMeshAllocationInboundRuntimeState(state.coordination, state.discovery, state.objectives, state.allocation, state.inbound);
|
|
818
|
+
}
|
|
819
|
+
function assertRequest(request) {
|
|
820
|
+
if (!request || typeof request !== 'object') {
|
|
821
|
+
throw new TypeError('Mesh discovery inbound request is required');
|
|
822
|
+
}
|
|
823
|
+
const prototype = Object.getPrototypeOf(request);
|
|
824
|
+
if ((prototype !== null && prototype !== Object.prototype) ||
|
|
825
|
+
!hasExactDataKeys(request, ['envelope', 'receivedAt', 'verifiedAt'], ['envelope', 'receivedAt', 'verifiedAt'])) {
|
|
826
|
+
throw new TypeError('Invalid Mesh discovery inbound request');
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
function assertObjectiveRequest(request) {
|
|
830
|
+
if (!request || typeof request !== 'object') {
|
|
831
|
+
throw new TypeError('Mesh Objective inbound request is required');
|
|
832
|
+
}
|
|
833
|
+
const prototype = Object.getPrototypeOf(request);
|
|
834
|
+
if ((prototype !== null && prototype !== Object.prototype) ||
|
|
835
|
+
!hasExactDataKeys(request, ['envelope', 'receivedAt', 'verifiedAt'], ['envelope', 'receivedAt', 'verifiedAt'])) {
|
|
836
|
+
throw new TypeError('Invalid Mesh Objective inbound request');
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function assertAllocationRequest(request) {
|
|
840
|
+
if (!request || typeof request !== 'object') {
|
|
841
|
+
throw new TypeError('Mesh allocation inbound request is required');
|
|
842
|
+
}
|
|
843
|
+
const prototype = Object.getPrototypeOf(request);
|
|
844
|
+
if ((prototype !== null && prototype !== Object.prototype) ||
|
|
845
|
+
!hasExactDataKeys(request, ['envelope', 'receivedAt', 'verifiedAt'], ['envelope', 'receivedAt', 'verifiedAt'])) {
|
|
846
|
+
throw new TypeError('Invalid Mesh allocation inbound request');
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
function snapshotCrypto(injected) {
|
|
850
|
+
const candidate = injected ?? globalThis.crypto;
|
|
851
|
+
const subtle = candidate?.subtle;
|
|
852
|
+
if (!subtle ||
|
|
853
|
+
typeof subtle.digest !== 'function' ||
|
|
854
|
+
typeof subtle.verify !== 'function') {
|
|
855
|
+
throw new TypeError('Mesh discovery inbound processor requires Web Crypto');
|
|
856
|
+
}
|
|
857
|
+
const digest = subtle.digest.bind(subtle);
|
|
858
|
+
const verify = subtle.verify.bind(subtle);
|
|
859
|
+
return Object.freeze({
|
|
860
|
+
subtle: Object.freeze({ digest, verify }),
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
function assertProcessorOptions(options) {
|
|
864
|
+
if (!options || typeof options !== 'object') {
|
|
865
|
+
throw new TypeError('Mesh discovery inbound processor options are required');
|
|
866
|
+
}
|
|
867
|
+
const prototype = Object.getPrototypeOf(options);
|
|
868
|
+
if ((prototype !== null && prototype !== Object.prototype) ||
|
|
869
|
+
!hasExactDataKeys(options, [
|
|
870
|
+
'crypto',
|
|
871
|
+
'cryptoPolicy',
|
|
872
|
+
'protocolOptions',
|
|
873
|
+
'resolver',
|
|
874
|
+
'supportedCriticalExtensions',
|
|
875
|
+
], ['cryptoPolicy', 'resolver']) ||
|
|
876
|
+
!options.resolver ||
|
|
877
|
+
typeof options.resolver.resolve !== 'function' ||
|
|
878
|
+
!options.cryptoPolicy ||
|
|
879
|
+
typeof options.cryptoPolicy !== 'object' ||
|
|
880
|
+
Object.getPrototypeOf(options.cryptoPolicy) !== Object.prototype ||
|
|
881
|
+
!hasExactDataKeys(options.cryptoPolicy, ['allowedAlgorithms'], ['allowedAlgorithms']) ||
|
|
882
|
+
!isDenseDataArray(options.cryptoPolicy.allowedAlgorithms) ||
|
|
883
|
+
options.cryptoPolicy.allowedAlgorithms.length < 1 ||
|
|
884
|
+
options.cryptoPolicy.allowedAlgorithms.some((algorithm) => typeof algorithm !== 'string') ||
|
|
885
|
+
(options.supportedCriticalExtensions !== undefined &&
|
|
886
|
+
(!isDenseDataArray(options.supportedCriticalExtensions) ||
|
|
887
|
+
options.supportedCriticalExtensions.length >
|
|
888
|
+
DEFAULT_MESH_PROTOCOL_LIMITS.maximumCriticalExtensions ||
|
|
889
|
+
new Set(options.supportedCriticalExtensions).size !==
|
|
890
|
+
options.supportedCriticalExtensions.length ||
|
|
891
|
+
options.supportedCriticalExtensions.some((extension) => typeof extension !== 'string' || extension.length < 1))) ||
|
|
892
|
+
(options.protocolOptions !== undefined &&
|
|
893
|
+
(!isPlainDataRecord(options.protocolOptions) ||
|
|
894
|
+
!hasExactDataKeys(options.protocolOptions, ['limits'], []) ||
|
|
895
|
+
(options.protocolOptions.limits !== undefined &&
|
|
896
|
+
!isPlainDataRecord(options.protocolOptions.limits))))) {
|
|
897
|
+
throw new TypeError('Invalid Mesh discovery inbound processor options');
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
function isPlainDataRecord(value) {
|
|
901
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
902
|
+
return false;
|
|
903
|
+
const prototype = Object.getPrototypeOf(value);
|
|
904
|
+
return ((prototype === null || prototype === Object.prototype) &&
|
|
905
|
+
hasOnlyDataProperties(value));
|
|
906
|
+
}
|
|
907
|
+
function hasOnlyDataProperties(value) {
|
|
908
|
+
return (Object.getOwnPropertySymbols(value).length === 0 &&
|
|
909
|
+
Object.getOwnPropertyNames(value).every((key) => {
|
|
910
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
911
|
+
return (descriptor !== undefined &&
|
|
912
|
+
'value' in descriptor &&
|
|
913
|
+
descriptor.enumerable === true);
|
|
914
|
+
}));
|
|
915
|
+
}
|
|
916
|
+
function hasExactDataKeys(value, supportedKeys, requiredKeys) {
|
|
917
|
+
const supported = new Set(supportedKeys);
|
|
918
|
+
return (hasOnlyDataProperties(value) &&
|
|
919
|
+
Object.getOwnPropertyNames(value).every((key) => supported.has(key)) &&
|
|
920
|
+
requiredKeys.every((key) => Object.hasOwn(value, key)));
|
|
921
|
+
}
|
|
922
|
+
function isDenseDataArray(value) {
|
|
923
|
+
if (!Array.isArray(value) ||
|
|
924
|
+
Object.getPrototypeOf(value) !== Array.prototype ||
|
|
925
|
+
Object.getOwnPropertySymbols(value).length > 0) {
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
const names = Object.getOwnPropertyNames(value);
|
|
929
|
+
if (names.length !== value.length + 1)
|
|
930
|
+
return false;
|
|
931
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
932
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
933
|
+
if (descriptor === undefined ||
|
|
934
|
+
!('value' in descriptor) ||
|
|
935
|
+
descriptor.enumerable !== true) {
|
|
936
|
+
return false;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, 'length');
|
|
940
|
+
return (lengthDescriptor !== undefined &&
|
|
941
|
+
'value' in lengthDescriptor &&
|
|
942
|
+
lengthDescriptor.enumerable === false);
|
|
943
|
+
}
|
|
944
|
+
//# sourceMappingURL=coordination-inbound.js.map
|