@enbox/dwn-sdk-js 0.3.7 → 0.3.9
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/dist/browser.mjs +8 -8
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/generated/precompiled-validators.js +2591 -1435
- package/dist/esm/generated/precompiled-validators.js.map +1 -1
- package/dist/esm/src/core/constants.js +20 -0
- package/dist/esm/src/core/constants.js.map +1 -1
- package/dist/esm/src/core/dwn-error.js +24 -1
- package/dist/esm/src/core/dwn-error.js.map +1 -1
- package/dist/esm/src/core/grant-authorization.js +4 -4
- package/dist/esm/src/core/grant-authorization.js.map +1 -1
- package/dist/esm/src/core/message.js +89 -4
- package/dist/esm/src/core/message.js.map +1 -1
- package/dist/esm/src/core/messages-grant-authorization.js +147 -55
- package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization.js +76 -0
- package/dist/esm/src/core/protocol-authorization.js.map +1 -1
- package/dist/esm/src/core/records-grant-authorization.js +40 -15
- package/dist/esm/src/core/records-grant-authorization.js.map +1 -1
- package/dist/esm/src/handlers/messages-read.js +5 -5
- package/dist/esm/src/handlers/messages-read.js.map +1 -1
- package/dist/esm/src/handlers/messages-subscribe.js +109 -7
- package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/messages-sync.js +341 -96
- package/dist/esm/src/handlers/messages-sync.js.map +1 -1
- package/dist/esm/src/handlers/protocols-configure.js +81 -2
- package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
- package/dist/esm/src/handlers/records-count.js +30 -0
- package/dist/esm/src/handlers/records-count.js.map +1 -1
- package/dist/esm/src/handlers/records-delete.js +3 -2
- package/dist/esm/src/handlers/records-delete.js.map +1 -1
- package/dist/esm/src/handlers/records-query.js +30 -0
- package/dist/esm/src/handlers/records-query.js.map +1 -1
- package/dist/esm/src/handlers/records-read.js +3 -2
- package/dist/esm/src/handlers/records-read.js.map +1 -1
- package/dist/esm/src/handlers/records-subscribe.js +31 -0
- package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/records-write.js +21 -14
- package/dist/esm/src/handlers/records-write.js.map +1 -1
- package/dist/esm/src/index.js +2 -0
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/interfaces/messages-read.js +6 -3
- package/dist/esm/src/interfaces/messages-read.js.map +1 -1
- package/dist/esm/src/interfaces/messages-subscribe.js +6 -3
- package/dist/esm/src/interfaces/messages-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/messages-sync.js +17 -3
- package/dist/esm/src/interfaces/messages-sync.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-configure.js +5 -2
- package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-query.js +8 -4
- package/dist/esm/src/interfaces/protocols-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-count.js +5 -0
- package/dist/esm/src/interfaces/records-count.js.map +1 -1
- package/dist/esm/src/interfaces/records-delete.js +6 -2
- package/dist/esm/src/interfaces/records-delete.js.map +1 -1
- package/dist/esm/src/interfaces/records-query.js +5 -0
- package/dist/esm/src/interfaces/records-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-read.js +6 -3
- package/dist/esm/src/interfaces/records-read.js.map +1 -1
- package/dist/esm/src/interfaces/records-subscribe.js +5 -0
- package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/records-write.js +6 -3
- package/dist/esm/src/interfaces/records-write.js.map +1 -1
- package/dist/esm/src/protocols/permissions.js +28 -7
- package/dist/esm/src/protocols/permissions.js.map +1 -1
- package/dist/esm/src/sync/records-projection.js +228 -0
- package/dist/esm/src/sync/records-projection.js.map +1 -0
- package/dist/esm/src/types/message-types.js.map +1 -1
- package/dist/esm/src/types/permission-types.js.map +1 -1
- package/dist/esm/src/utils/permission-scope.js +37 -0
- package/dist/esm/src/utils/permission-scope.js.map +1 -0
- package/dist/esm/tests/core/grant-authorization.spec.js +26 -3
- package/dist/esm/tests/core/grant-authorization.spec.js.map +1 -1
- package/dist/esm/tests/core/records-grant-authorization.spec.js +117 -0
- package/dist/esm/tests/core/records-grant-authorization.spec.js.map +1 -0
- package/dist/esm/tests/features/permissions.spec.js +126 -0
- package/dist/esm/tests/features/permissions.spec.js.map +1 -1
- package/dist/esm/tests/features/records-record-limit.spec.js +14 -0
- package/dist/esm/tests/features/records-record-limit.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-read.spec.js +345 -12
- package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-subscribe.spec.js +326 -9
- package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-sync.spec.js +1053 -7
- package/dist/esm/tests/handlers/messages-sync.spec.js.map +1 -1
- package/dist/esm/tests/handlers/protocols-configure.spec.js +361 -0
- package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-count.spec.js +75 -2
- package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-query.spec.js +73 -0
- package/dist/esm/tests/handlers/records-query.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js +75 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-write.spec.js +41 -0
- package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/messages-get.spec.js +107 -5
- package/dist/esm/tests/interfaces/messages-get.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/protocols-configure.spec.js +13 -0
- package/dist/esm/tests/interfaces/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-delete.spec.js +12 -0
- package/dist/esm/tests/interfaces/records-delete.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-query.spec.js +10 -0
- package/dist/esm/tests/interfaces/records-query.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-subscribe.spec.js +10 -0
- package/dist/esm/tests/interfaces/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-write.spec.js +33 -0
- package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
- package/dist/esm/tests/sync/records-projection.spec.js +245 -0
- package/dist/esm/tests/sync/records-projection.spec.js.map +1 -0
- package/dist/esm/tests/test-suite.js +2 -0
- package/dist/esm/tests/test-suite.js.map +1 -1
- package/dist/esm/tests/utils/permission-scope.spec.js +66 -0
- package/dist/esm/tests/utils/permission-scope.spec.js.map +1 -0
- package/dist/esm/tests/utils/test-data-generator.js +5 -2
- package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
- package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
- package/dist/types/src/core/constants.d.ts +13 -0
- package/dist/types/src/core/constants.d.ts.map +1 -1
- package/dist/types/src/core/dwn-error.d.ts +24 -1
- package/dist/types/src/core/dwn-error.d.ts.map +1 -1
- package/dist/types/src/core/grant-authorization.d.ts +1 -2
- package/dist/types/src/core/grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/message.d.ts +41 -1
- package/dist/types/src/core/message.d.ts.map +1 -1
- package/dist/types/src/core/messages-grant-authorization.d.ts +36 -4
- package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization.d.ts +12 -0
- package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
- package/dist/types/src/core/records-grant-authorization.d.ts +6 -0
- package/dist/types/src/core/records-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-read.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts +2 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-sync.d.ts +31 -0
- package/dist/types/src/handlers/messages-sync.d.ts.map +1 -1
- package/dist/types/src/handlers/protocols-configure.d.ts +3 -0
- package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/handlers/records-count.d.ts +4 -0
- package/dist/types/src/handlers/records-count.d.ts.map +1 -1
- package/dist/types/src/handlers/records-delete.d.ts.map +1 -1
- package/dist/types/src/handlers/records-query.d.ts +4 -0
- package/dist/types/src/handlers/records-query.d.ts.map +1 -1
- package/dist/types/src/handlers/records-read.d.ts.map +1 -1
- package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/records-write.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +6 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-read.d.ts +1 -1
- package/dist/types/src/interfaces/messages-read.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-subscribe.d.ts +1 -1
- package/dist/types/src/interfaces/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-sync.d.ts +4 -1
- package/dist/types/src/interfaces/messages-sync.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-query.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-count.d.ts +1 -0
- package/dist/types/src/interfaces/records-count.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-delete.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-query.d.ts +1 -0
- package/dist/types/src/interfaces/records-query.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-read.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-subscribe.d.ts +1 -0
- package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
- package/dist/types/src/protocols/permissions.d.ts +2 -0
- package/dist/types/src/protocols/permissions.d.ts.map +1 -1
- package/dist/types/src/sync/records-projection.d.ts +98 -0
- package/dist/types/src/sync/records-projection.d.ts.map +1 -0
- package/dist/types/src/types/message-types.d.ts +1 -0
- package/dist/types/src/types/message-types.d.ts.map +1 -1
- package/dist/types/src/types/messages-types.d.ts +21 -3
- package/dist/types/src/types/messages-types.d.ts.map +1 -1
- package/dist/types/src/types/permission-types.d.ts +4 -0
- package/dist/types/src/types/permission-types.d.ts.map +1 -1
- package/dist/types/src/types/records-types.d.ts +4 -0
- package/dist/types/src/types/records-types.d.ts.map +1 -1
- package/dist/types/src/types/subscriptions.d.ts +18 -3
- package/dist/types/src/types/subscriptions.d.ts.map +1 -1
- package/dist/types/src/utils/permission-scope.d.ts +29 -0
- package/dist/types/src/utils/permission-scope.d.ts.map +1 -0
- package/dist/types/tests/core/records-grant-authorization.spec.d.ts +2 -0
- package/dist/types/tests/core/records-grant-authorization.spec.d.ts.map +1 -0
- package/dist/types/tests/features/permissions.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-record-limit.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-read.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-sync.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/protocols-configure.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-count.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-query.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
- package/dist/types/tests/sync/records-projection.spec.d.ts +2 -0
- package/dist/types/tests/sync/records-projection.spec.d.ts.map +1 -0
- package/dist/types/tests/test-suite.d.ts.map +1 -1
- package/dist/types/tests/utils/permission-scope.spec.d.ts +2 -0
- package/dist/types/tests/utils/permission-scope.spec.d.ts.map +1 -0
- package/dist/types/tests/utils/test-data-generator.d.ts +5 -2
- package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/constants.ts +24 -0
- package/src/core/dwn-error.ts +24 -1
- package/src/core/grant-authorization.ts +7 -5
- package/src/core/message.ts +153 -6
- package/src/core/messages-grant-authorization.ts +282 -70
- package/src/core/protocol-authorization.ts +130 -0
- package/src/core/records-grant-authorization.ts +64 -21
- package/src/handlers/messages-read.ts +7 -5
- package/src/handlers/messages-subscribe.ts +149 -9
- package/src/handlers/messages-sync.ts +593 -102
- package/src/handlers/protocols-configure.ts +103 -2
- package/src/handlers/records-count.ts +33 -0
- package/src/handlers/records-delete.ts +3 -2
- package/src/handlers/records-query.ts +33 -0
- package/src/handlers/records-read.ts +3 -2
- package/src/handlers/records-subscribe.ts +34 -0
- package/src/handlers/records-write.ts +21 -15
- package/src/index.ts +7 -3
- package/src/interfaces/messages-read.ts +8 -5
- package/src/interfaces/messages-subscribe.ts +12 -9
- package/src/interfaces/messages-sync.ts +33 -12
- package/src/interfaces/protocols-configure.ts +8 -4
- package/src/interfaces/protocols-query.ts +13 -9
- package/src/interfaces/records-count.ts +7 -0
- package/src/interfaces/records-delete.ts +9 -5
- package/src/interfaces/records-query.ts +7 -0
- package/src/interfaces/records-read.ts +6 -3
- package/src/interfaces/records-subscribe.ts +7 -0
- package/src/interfaces/records-write.ts +25 -17
- package/src/protocols/permissions.ts +47 -9
- package/src/sync/records-projection.ts +328 -0
- package/src/types/message-types.ts +1 -0
- package/src/types/messages-types.ts +23 -3
- package/src/types/permission-types.ts +5 -1
- package/src/types/records-types.ts +5 -1
- package/src/types/subscriptions.ts +19 -3
- package/src/utils/permission-scope.ts +55 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import freeForAll from '../vectors/protocol-definitions/free-for-all.json' with { type: 'json' };
|
|
2
2
|
import { Jws } from '../../src/utils/jws.js';
|
|
3
|
+
import { KEY_DELIVERY_PROTOCOL_URI } from '../../src/core/constants.js';
|
|
3
4
|
import { Message } from '../../src/core/message.js';
|
|
4
5
|
import { MessagesSync } from '../../src/interfaces/messages-sync.js';
|
|
5
6
|
import { MessagesSyncHandler } from '../../src/handlers/messages-sync.js';
|
|
@@ -8,8 +9,8 @@ import { TestDataGenerator } from '../utils/test-data-generator.js';
|
|
|
8
9
|
import { TestEventLog } from '../test-event-stream.js';
|
|
9
10
|
import { TestStores } from '../test-stores.js';
|
|
10
11
|
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'bun:test';
|
|
12
|
+
import { DataStream, Dwn, DwnErrorCode, DwnInterfaceName, DwnMethodName, Encoder, PermissionGrant, PermissionsProtocol, RECORDS_PROJECTION_ROOT_VERSION, RecordsProjection, Time } from '../../src/index.js';
|
|
11
13
|
import { DidKey, UniversalResolver } from '@enbox/dids';
|
|
12
|
-
import { Dwn, DwnErrorCode, DwnInterfaceName, DwnMethodName } from '../../src/index.js';
|
|
13
14
|
export function testMessagesSyncHandler() {
|
|
14
15
|
describe('MessagesSyncHandler.handle()', () => {
|
|
15
16
|
let didResolver;
|
|
@@ -231,6 +232,126 @@ export function testMessagesSyncHandler() {
|
|
|
231
232
|
expect(reply.entries).toContain(protocolCid);
|
|
232
233
|
expect(reply.entries).toContain(recordCid);
|
|
233
234
|
});
|
|
235
|
+
it('returns projected record leaves without protocol configs or out-of-path records', async () => {
|
|
236
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
237
|
+
const protocolDefinition = { ...freeForAll, published: true };
|
|
238
|
+
const protocol = protocolDefinition.protocol;
|
|
239
|
+
const { message: protocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
240
|
+
author: alice,
|
|
241
|
+
protocolDefinition,
|
|
242
|
+
});
|
|
243
|
+
expect((await dwn.processMessage(alice.did, protocolMessage)).status.code).toBe(202);
|
|
244
|
+
const { message: postMessage, dataStream: postDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
245
|
+
author: alice,
|
|
246
|
+
protocol,
|
|
247
|
+
protocolPath: 'post',
|
|
248
|
+
schema: protocolDefinition.types.post.schema,
|
|
249
|
+
});
|
|
250
|
+
expect((await dwn.processMessage(alice.did, postMessage, { dataStream: postDataStream })).status.code).toBe(202);
|
|
251
|
+
const { message: attachmentMessage, dataStream: attachmentDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
252
|
+
author: alice,
|
|
253
|
+
protocol,
|
|
254
|
+
protocolPath: 'post/attachment',
|
|
255
|
+
parentContextId: postMessage.contextId,
|
|
256
|
+
});
|
|
257
|
+
expect((await dwn.processMessage(alice.did, attachmentMessage, { dataStream: attachmentDataStream })).status.code).toBe(202);
|
|
258
|
+
const { message } = await MessagesSync.create({
|
|
259
|
+
signer: Jws.createSigner(alice),
|
|
260
|
+
action: 'leaves',
|
|
261
|
+
prefix: '',
|
|
262
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
263
|
+
projectionScopes: [{ protocol, protocolPath: 'post' }],
|
|
264
|
+
});
|
|
265
|
+
const reply = await dwn.processMessage(alice.did, message);
|
|
266
|
+
expect(reply.status.code).toBe(200);
|
|
267
|
+
expect(reply.entries).toEqual([await Message.getCid(postMessage)]);
|
|
268
|
+
expect(reply.entries).not.toContain(await Message.getCid(protocolMessage));
|
|
269
|
+
expect(reply.entries).not.toContain(await Message.getCid(attachmentMessage));
|
|
270
|
+
});
|
|
271
|
+
it('excludes infrastructure protocols from records-primary projection leaves', async () => {
|
|
272
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
273
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
274
|
+
const { message: permissionGrantMessage, dataStream: permissionGrantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
275
|
+
author: alice,
|
|
276
|
+
grantedTo: bob,
|
|
277
|
+
scope: {
|
|
278
|
+
interface: DwnInterfaceName.Messages,
|
|
279
|
+
method: DwnMethodName.Read,
|
|
280
|
+
protocol: 'http://projected-sync-app-protocol',
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
expect((await dwn.processMessage(alice.did, permissionGrantMessage, { dataStream: permissionGrantDataStream })).status.code).toBe(202);
|
|
284
|
+
const keyDeliverySchema = 'https://identity.foundation/schemas/key-delivery/context-key';
|
|
285
|
+
const keyDeliveryProtocolDefinition = {
|
|
286
|
+
protocol: KEY_DELIVERY_PROTOCOL_URI,
|
|
287
|
+
published: false,
|
|
288
|
+
types: {
|
|
289
|
+
contextKey: {
|
|
290
|
+
schema: keyDeliverySchema,
|
|
291
|
+
dataFormats: ['application/json'],
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
structure: {
|
|
295
|
+
contextKey: {},
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
const { message: keyDeliveryConfigureMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
299
|
+
author: alice,
|
|
300
|
+
protocolDefinition: keyDeliveryProtocolDefinition,
|
|
301
|
+
});
|
|
302
|
+
expect((await dwn.processMessage(alice.did, keyDeliveryConfigureMessage)).status.code).toBe(202);
|
|
303
|
+
const { message: keyDeliveryMessage, dataStream: keyDeliveryDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
304
|
+
author: alice,
|
|
305
|
+
protocol: KEY_DELIVERY_PROTOCOL_URI,
|
|
306
|
+
protocolPath: 'contextKey',
|
|
307
|
+
schema: keyDeliverySchema,
|
|
308
|
+
});
|
|
309
|
+
expect((await dwn.processMessage(alice.did, keyDeliveryMessage, { dataStream: keyDeliveryDataStream })).status.code).toBe(202);
|
|
310
|
+
for (const protocol of [PermissionsProtocol.uri, KEY_DELIVERY_PROTOCOL_URI]) {
|
|
311
|
+
const { message } = await MessagesSync.create({
|
|
312
|
+
signer: Jws.createSigner(alice),
|
|
313
|
+
action: 'leaves',
|
|
314
|
+
prefix: '',
|
|
315
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
316
|
+
projectionScopes: [{ protocol }],
|
|
317
|
+
});
|
|
318
|
+
const reply = await dwn.processMessage(alice.did, message);
|
|
319
|
+
expect(reply.status.code).toBe(200);
|
|
320
|
+
expect(reply.entries).toEqual([]);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
it('returns projected roots from the Records projection algorithm', async () => {
|
|
324
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
325
|
+
const protocolDefinition = { ...freeForAll, published: true };
|
|
326
|
+
const protocol = protocolDefinition.protocol;
|
|
327
|
+
const projectionScopes = [{ protocol, protocolPath: 'post' }];
|
|
328
|
+
const { message: protocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
329
|
+
author: alice,
|
|
330
|
+
protocolDefinition,
|
|
331
|
+
});
|
|
332
|
+
expect((await dwn.processMessage(alice.did, protocolMessage)).status.code).toBe(202);
|
|
333
|
+
const { message: postMessage, dataStream: postDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
334
|
+
author: alice,
|
|
335
|
+
protocol,
|
|
336
|
+
protocolPath: 'post',
|
|
337
|
+
schema: protocolDefinition.types.post.schema,
|
|
338
|
+
});
|
|
339
|
+
expect((await dwn.processMessage(alice.did, postMessage, { dataStream: postDataStream })).status.code).toBe(202);
|
|
340
|
+
const expectedRoot = await RecordsProjection.getRootHex({
|
|
341
|
+
tenant: alice.did,
|
|
342
|
+
messageStore: messageStore,
|
|
343
|
+
scopes: projectionScopes,
|
|
344
|
+
});
|
|
345
|
+
const { message } = await MessagesSync.create({
|
|
346
|
+
signer: Jws.createSigner(alice),
|
|
347
|
+
action: 'root',
|
|
348
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
349
|
+
projectionScopes,
|
|
350
|
+
});
|
|
351
|
+
const reply = await dwn.processMessage(alice.did, message);
|
|
352
|
+
expect(reply.status.code).toBe(200);
|
|
353
|
+
expect(reply.root).toBe(expectedRoot);
|
|
354
|
+
});
|
|
234
355
|
});
|
|
235
356
|
describe('authorization', () => {
|
|
236
357
|
it('returns 401 if tenant is not the author', async () => {
|
|
@@ -278,7 +399,7 @@ export function testMessagesSyncHandler() {
|
|
|
278
399
|
const { message: syncMsg } = await MessagesSync.create({
|
|
279
400
|
signer: Jws.createSigner(bob),
|
|
280
401
|
action: 'root',
|
|
281
|
-
|
|
402
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
282
403
|
});
|
|
283
404
|
const reply = await dwn.processMessage(alice.did, syncMsg);
|
|
284
405
|
expect(reply.status.code).toBe(200);
|
|
@@ -309,7 +430,7 @@ export function testMessagesSyncHandler() {
|
|
|
309
430
|
const { message: syncMsg } = await MessagesSync.create({
|
|
310
431
|
signer: Jws.createSigner(bob),
|
|
311
432
|
action: 'root',
|
|
312
|
-
|
|
433
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
313
434
|
});
|
|
314
435
|
const reply2 = await dwn.processMessage(alice.did, syncMsg);
|
|
315
436
|
expect(reply2.status.code).toBe(200);
|
|
@@ -352,7 +473,7 @@ export function testMessagesSyncHandler() {
|
|
|
352
473
|
action: 'leaves',
|
|
353
474
|
prefix: '',
|
|
354
475
|
protocol: protocolDefinition.protocol,
|
|
355
|
-
|
|
476
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
356
477
|
});
|
|
357
478
|
const reply2 = await dwn.processMessage(alice.did, syncMsg);
|
|
358
479
|
expect(reply2.status.code).toBe(200);
|
|
@@ -394,7 +515,7 @@ export function testMessagesSyncHandler() {
|
|
|
394
515
|
action: 'leaves',
|
|
395
516
|
prefix: '',
|
|
396
517
|
protocol: protocolDefinition.protocol,
|
|
397
|
-
|
|
518
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
398
519
|
});
|
|
399
520
|
const reply = await dwn.processMessage(alice.did, syncMsg);
|
|
400
521
|
expect(reply.status.code).toBe(200);
|
|
@@ -406,6 +527,131 @@ export function testMessagesSyncHandler() {
|
|
|
406
527
|
expect(reply.entries).toContain(protocolCid);
|
|
407
528
|
expect(reply.entries).toContain(recordCid);
|
|
408
529
|
});
|
|
530
|
+
it('allows protocol sync when one grant in a plural grant set covers the protocol', async () => {
|
|
531
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
532
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
533
|
+
const protocol1 = { ...freeForAll, published: true, protocol: 'http://plural-grant-sync-1' };
|
|
534
|
+
const protocol2 = { ...freeForAll, published: true, protocol: 'http://plural-grant-sync-2' };
|
|
535
|
+
for (const protocolDefinition of [protocol1, protocol2]) {
|
|
536
|
+
const { message: protocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
537
|
+
author: alice,
|
|
538
|
+
protocolDefinition,
|
|
539
|
+
});
|
|
540
|
+
await dwn.processMessage(alice.did, protocolMessage);
|
|
541
|
+
}
|
|
542
|
+
const { message: recordMessage, dataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
543
|
+
author: alice,
|
|
544
|
+
protocol: protocol2.protocol,
|
|
545
|
+
protocolPath: 'post',
|
|
546
|
+
schema: protocol2.types.post.schema,
|
|
547
|
+
});
|
|
548
|
+
await dwn.processMessage(alice.did, recordMessage, { dataStream });
|
|
549
|
+
const grantIds = [];
|
|
550
|
+
for (const protocolDefinition of [protocol1, protocol2]) {
|
|
551
|
+
const { message: grantMessage, dataStream: grantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
552
|
+
author: alice,
|
|
553
|
+
grantedTo: bob,
|
|
554
|
+
scope: {
|
|
555
|
+
interface: DwnInterfaceName.Messages,
|
|
556
|
+
method: DwnMethodName.Read,
|
|
557
|
+
protocol: protocolDefinition.protocol,
|
|
558
|
+
},
|
|
559
|
+
});
|
|
560
|
+
const grantReply = await dwn.processMessage(alice.did, grantMessage, { dataStream: grantDataStream });
|
|
561
|
+
expect(grantReply.status.code).toBe(202);
|
|
562
|
+
grantIds.push(grantMessage.recordId);
|
|
563
|
+
}
|
|
564
|
+
const { message: syncMsg } = await MessagesSync.create({
|
|
565
|
+
signer: Jws.createSigner(bob),
|
|
566
|
+
action: 'leaves',
|
|
567
|
+
prefix: '',
|
|
568
|
+
protocol: protocol2.protocol,
|
|
569
|
+
permissionGrantIds: grantIds.reverse(),
|
|
570
|
+
});
|
|
571
|
+
const reply = await dwn.processMessage(alice.did, syncMsg);
|
|
572
|
+
expect(reply.status.code).toBe(200);
|
|
573
|
+
expect(reply.entries).toContain(await Message.getCid(recordMessage));
|
|
574
|
+
expect(syncMsg.descriptor.permissionGrantIds).toEqual([...grantIds].sort());
|
|
575
|
+
});
|
|
576
|
+
it('rejects sync when any grant in a plural grant set is expired', async () => {
|
|
577
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
578
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
579
|
+
const protocol = 'http://plural-grant-sync-expired';
|
|
580
|
+
const now = Time.getCurrentTimestamp();
|
|
581
|
+
const { message: activeGrantMessage, dataStream: activeGrantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
582
|
+
author: alice,
|
|
583
|
+
grantedTo: bob,
|
|
584
|
+
dateExpires: Time.createOffsetTimestamp({ seconds: 60 * 60 }, now),
|
|
585
|
+
scope: {
|
|
586
|
+
interface: DwnInterfaceName.Messages,
|
|
587
|
+
method: DwnMethodName.Read,
|
|
588
|
+
protocol,
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
expect((await dwn.processMessage(alice.did, activeGrantMessage, { dataStream: activeGrantDataStream })).status.code).toBe(202);
|
|
592
|
+
const { message: expiredGrantMessage, dataStream: expiredGrantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
593
|
+
author: alice,
|
|
594
|
+
grantedTo: bob,
|
|
595
|
+
dateGranted: Time.createOffsetTimestamp({ seconds: -120 }, now),
|
|
596
|
+
dateExpires: Time.createOffsetTimestamp({ seconds: -60 }, now),
|
|
597
|
+
scope: {
|
|
598
|
+
interface: DwnInterfaceName.Messages,
|
|
599
|
+
method: DwnMethodName.Read,
|
|
600
|
+
protocol,
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
expect((await dwn.processMessage(alice.did, expiredGrantMessage, { dataStream: expiredGrantDataStream })).status.code).toBe(202);
|
|
604
|
+
const { message: syncMsg } = await MessagesSync.create({
|
|
605
|
+
signer: Jws.createSigner(bob),
|
|
606
|
+
action: 'root',
|
|
607
|
+
protocol,
|
|
608
|
+
permissionGrantIds: [activeGrantMessage.recordId, expiredGrantMessage.recordId],
|
|
609
|
+
});
|
|
610
|
+
const reply = await dwn.processMessage(alice.did, syncMsg);
|
|
611
|
+
expect(reply.status.code).toBe(401);
|
|
612
|
+
expect(reply.status.detail).toContain(DwnErrorCode.GrantAuthorizationGrantExpired);
|
|
613
|
+
});
|
|
614
|
+
it('rejects sync when any grant in a plural grant set is revoked', async () => {
|
|
615
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
616
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
617
|
+
const protocol = 'http://plural-grant-sync-revoked';
|
|
618
|
+
const { message: activeGrantMessage, dataStream: activeGrantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
619
|
+
author: alice,
|
|
620
|
+
grantedTo: bob,
|
|
621
|
+
scope: {
|
|
622
|
+
interface: DwnInterfaceName.Messages,
|
|
623
|
+
method: DwnMethodName.Read,
|
|
624
|
+
protocol,
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
expect((await dwn.processMessage(alice.did, activeGrantMessage, { dataStream: activeGrantDataStream })).status.code).toBe(202);
|
|
628
|
+
const revokedGrant = await TestDataGenerator.generateGrantCreate({
|
|
629
|
+
author: alice,
|
|
630
|
+
grantedTo: bob,
|
|
631
|
+
scope: {
|
|
632
|
+
interface: DwnInterfaceName.Messages,
|
|
633
|
+
method: DwnMethodName.Read,
|
|
634
|
+
protocol,
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
expect((await dwn.processMessage(alice.did, revokedGrant.message, { dataStream: revokedGrant.dataStream })).status.code).toBe(202);
|
|
638
|
+
const revocation = await PermissionsProtocol.createRevocation({
|
|
639
|
+
signer: Jws.createSigner(alice),
|
|
640
|
+
grant: PermissionGrant.parse(revokedGrant.dataEncodedMessage),
|
|
641
|
+
});
|
|
642
|
+
const revocationReply = await dwn.processMessage(alice.did, revocation.recordsWrite.message, { dataStream: DataStream.fromBytes(revocation.permissionRevocationBytes) });
|
|
643
|
+
expect(revocationReply.status.code).toBe(202);
|
|
644
|
+
await Time.minimalSleep();
|
|
645
|
+
const { message: syncMsg } = await MessagesSync.create({
|
|
646
|
+
signer: Jws.createSigner(bob),
|
|
647
|
+
action: 'root',
|
|
648
|
+
protocol,
|
|
649
|
+
permissionGrantIds: [activeGrantMessage.recordId, revokedGrant.message.recordId],
|
|
650
|
+
});
|
|
651
|
+
const reply = await dwn.processMessage(alice.did, syncMsg);
|
|
652
|
+
expect(reply.status.code).toBe(401);
|
|
653
|
+
expect(reply.status.detail).toContain(DwnErrorCode.GrantAuthorizationGrantRevoked);
|
|
654
|
+
});
|
|
409
655
|
it('rejects sync with mismatching interface grant scope', async () => {
|
|
410
656
|
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
411
657
|
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
@@ -424,7 +670,7 @@ export function testMessagesSyncHandler() {
|
|
|
424
670
|
const { message: syncMsg } = await MessagesSync.create({
|
|
425
671
|
signer: Jws.createSigner(bob),
|
|
426
672
|
action: 'root',
|
|
427
|
-
|
|
673
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
428
674
|
});
|
|
429
675
|
const reply = await dwn.processMessage(alice.did, syncMsg);
|
|
430
676
|
expect(reply.status.code).toBe(401);
|
|
@@ -450,12 +696,695 @@ export function testMessagesSyncHandler() {
|
|
|
450
696
|
signer: Jws.createSigner(bob),
|
|
451
697
|
action: 'root',
|
|
452
698
|
protocol: 'http://protocol2',
|
|
453
|
-
|
|
699
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
454
700
|
});
|
|
455
701
|
const reply = await dwn.processMessage(alice.did, syncMsg);
|
|
456
702
|
expect(reply.status.code).toBe(401);
|
|
457
703
|
expect(reply.status.detail).toContain(DwnErrorCode.MessagesGrantAuthorizationMismatchedProtocol);
|
|
458
704
|
});
|
|
705
|
+
it('rejects full-tenant sync actions with a protocol-scoped grant', async () => {
|
|
706
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
707
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
708
|
+
const { message: grantMessage, dataStream } = await TestDataGenerator.generateGrantCreate({
|
|
709
|
+
author: alice,
|
|
710
|
+
grantedTo: bob,
|
|
711
|
+
scope: {
|
|
712
|
+
interface: DwnInterfaceName.Messages,
|
|
713
|
+
method: DwnMethodName.Read,
|
|
714
|
+
protocol: 'http://protocol-scoped-sync',
|
|
715
|
+
},
|
|
716
|
+
});
|
|
717
|
+
const grantReply = await dwn.processMessage(alice.did, grantMessage, { dataStream });
|
|
718
|
+
expect(grantReply.status.code).toBe(202);
|
|
719
|
+
const syncActions = [
|
|
720
|
+
{ action: 'root' },
|
|
721
|
+
{ action: 'subtree', prefix: '' },
|
|
722
|
+
{ action: 'leaves', prefix: '' },
|
|
723
|
+
{ action: 'diff', hashes: {}, depth: 2 },
|
|
724
|
+
];
|
|
725
|
+
for (const syncAction of syncActions) {
|
|
726
|
+
const { message: syncMsg } = await MessagesSync.create({
|
|
727
|
+
signer: Jws.createSigner(bob),
|
|
728
|
+
...syncAction,
|
|
729
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
730
|
+
});
|
|
731
|
+
const reply = await dwn.processMessage(alice.did, syncMsg);
|
|
732
|
+
expect(reply.status.code).toBe(401);
|
|
733
|
+
expect(reply.status.detail).toContain(DwnErrorCode.MessagesGrantAuthorizationMismatchedProtocol);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
it('rejects protocol sync with subtree-scoped grants', async () => {
|
|
737
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
738
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
739
|
+
const protocol = 'http://subtree-scoped-sync';
|
|
740
|
+
const scopedGrants = [
|
|
741
|
+
{
|
|
742
|
+
interface: DwnInterfaceName.Messages,
|
|
743
|
+
method: DwnMethodName.Read,
|
|
744
|
+
protocol,
|
|
745
|
+
protocolPath: 'post',
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
interface: DwnInterfaceName.Messages,
|
|
749
|
+
method: DwnMethodName.Read,
|
|
750
|
+
protocol,
|
|
751
|
+
contextId: 'root',
|
|
752
|
+
},
|
|
753
|
+
];
|
|
754
|
+
for (const scope of scopedGrants) {
|
|
755
|
+
const { message: grantMessage, dataStream } = await TestDataGenerator.generateGrantCreate({
|
|
756
|
+
author: alice,
|
|
757
|
+
grantedTo: bob,
|
|
758
|
+
scope,
|
|
759
|
+
});
|
|
760
|
+
const grantReply = await dwn.processMessage(alice.did, grantMessage, { dataStream });
|
|
761
|
+
expect(grantReply.status.code).toBe(202);
|
|
762
|
+
const { message: syncMsg } = await MessagesSync.create({
|
|
763
|
+
signer: Jws.createSigner(bob),
|
|
764
|
+
action: 'root',
|
|
765
|
+
protocol,
|
|
766
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
767
|
+
});
|
|
768
|
+
const reply = await dwn.processMessage(alice.did, syncMsg);
|
|
769
|
+
expect(reply.status.code).toBe(401);
|
|
770
|
+
expect(reply.status.detail).toContain(DwnErrorCode.MessagesGrantAuthorizationMismatchedProtocol);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
it('allows projected sync with a matching protocolPath Messages.Read grant', async () => {
|
|
774
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
775
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
776
|
+
const protocolDefinition = { ...freeForAll, published: true };
|
|
777
|
+
const protocol = protocolDefinition.protocol;
|
|
778
|
+
const { message: protocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
779
|
+
author: alice,
|
|
780
|
+
protocolDefinition,
|
|
781
|
+
});
|
|
782
|
+
expect((await dwn.processMessage(alice.did, protocolMessage)).status.code).toBe(202);
|
|
783
|
+
const { message: postMessage, dataStream: postDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
784
|
+
author: alice,
|
|
785
|
+
protocol,
|
|
786
|
+
protocolPath: 'post',
|
|
787
|
+
schema: protocolDefinition.types.post.schema,
|
|
788
|
+
});
|
|
789
|
+
expect((await dwn.processMessage(alice.did, postMessage, { dataStream: postDataStream })).status.code).toBe(202);
|
|
790
|
+
const { message: attachmentMessage, dataStream: attachmentDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
791
|
+
author: alice,
|
|
792
|
+
protocol,
|
|
793
|
+
protocolPath: 'post/attachment',
|
|
794
|
+
parentContextId: postMessage.contextId,
|
|
795
|
+
});
|
|
796
|
+
expect((await dwn.processMessage(alice.did, attachmentMessage, { dataStream: attachmentDataStream })).status.code).toBe(202);
|
|
797
|
+
const { message: grantMessage, dataStream: grantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
798
|
+
author: alice,
|
|
799
|
+
grantedTo: bob,
|
|
800
|
+
scope: {
|
|
801
|
+
interface: DwnInterfaceName.Messages,
|
|
802
|
+
method: DwnMethodName.Read,
|
|
803
|
+
protocol,
|
|
804
|
+
protocolPath: 'post',
|
|
805
|
+
},
|
|
806
|
+
});
|
|
807
|
+
expect((await dwn.processMessage(alice.did, grantMessage, { dataStream: grantDataStream })).status.code).toBe(202);
|
|
808
|
+
const { message: diffMsg } = await MessagesSync.create({
|
|
809
|
+
signer: Jws.createSigner(bob),
|
|
810
|
+
action: 'diff',
|
|
811
|
+
hashes: {},
|
|
812
|
+
depth: 2,
|
|
813
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
814
|
+
projectionScopes: [{ protocol, protocolPath: 'post' }],
|
|
815
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
816
|
+
});
|
|
817
|
+
const reply = await dwn.processMessage(alice.did, diffMsg);
|
|
818
|
+
expect(reply.status.code).toBe(200);
|
|
819
|
+
const protocolCid = await Message.getCid(protocolMessage);
|
|
820
|
+
const postCid = await Message.getCid(postMessage);
|
|
821
|
+
const remoteCids = reply.onlyRemote.map(entry => entry.messageCid);
|
|
822
|
+
expect(remoteCids).toContain(postCid);
|
|
823
|
+
expect(remoteCids).not.toContain(protocolCid);
|
|
824
|
+
expect(remoteCids).not.toContain(await Message.getCid(attachmentMessage));
|
|
825
|
+
expect(reply.dependencies).toEqual([{
|
|
826
|
+
dependencyClass: 'protocolsConfigure',
|
|
827
|
+
messageCid: protocolCid,
|
|
828
|
+
message: protocolMessage,
|
|
829
|
+
rootMessageCid: postCid,
|
|
830
|
+
}]);
|
|
831
|
+
});
|
|
832
|
+
it('returns the governing protocol config dependency for projected sync', async () => {
|
|
833
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
834
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
835
|
+
const protocol = 'http://projected-sync-config-history';
|
|
836
|
+
const protocolDefinition = {
|
|
837
|
+
...freeForAll,
|
|
838
|
+
protocol,
|
|
839
|
+
published: true,
|
|
840
|
+
};
|
|
841
|
+
const { message: firstProtocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
842
|
+
author: alice,
|
|
843
|
+
messageTimestamp: '2026-01-01T00:00:00.000000Z',
|
|
844
|
+
protocolDefinition,
|
|
845
|
+
});
|
|
846
|
+
expect((await dwn.processMessage(alice.did, firstProtocolMessage)).status.code).toBe(202);
|
|
847
|
+
const { message: secondProtocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
848
|
+
author: alice,
|
|
849
|
+
messageTimestamp: '2026-01-02T00:00:00.000000Z',
|
|
850
|
+
protocolDefinition,
|
|
851
|
+
});
|
|
852
|
+
expect((await dwn.processMessage(alice.did, secondProtocolMessage)).status.code).toBe(202);
|
|
853
|
+
const { message: postMessage, dataStream: postDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
854
|
+
author: alice,
|
|
855
|
+
dateCreated: '2026-01-03T00:00:00.000000Z',
|
|
856
|
+
messageTimestamp: '2026-01-03T00:00:00.000000Z',
|
|
857
|
+
protocol,
|
|
858
|
+
protocolPath: 'post',
|
|
859
|
+
schema: protocolDefinition.types.post.schema,
|
|
860
|
+
});
|
|
861
|
+
expect((await dwn.processMessage(alice.did, postMessage, { dataStream: postDataStream })).status.code).toBe(202);
|
|
862
|
+
const { message: futureProtocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
863
|
+
author: alice,
|
|
864
|
+
messageTimestamp: '2026-01-04T00:00:00.000000Z',
|
|
865
|
+
protocolDefinition,
|
|
866
|
+
});
|
|
867
|
+
expect((await dwn.processMessage(alice.did, futureProtocolMessage)).status.code).toBe(202);
|
|
868
|
+
const { message: grantMessage, dataStream: grantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
869
|
+
author: alice,
|
|
870
|
+
grantedTo: bob,
|
|
871
|
+
scope: {
|
|
872
|
+
interface: DwnInterfaceName.Messages,
|
|
873
|
+
method: DwnMethodName.Read,
|
|
874
|
+
protocol,
|
|
875
|
+
protocolPath: 'post',
|
|
876
|
+
},
|
|
877
|
+
});
|
|
878
|
+
expect((await dwn.processMessage(alice.did, grantMessage, { dataStream: grantDataStream })).status.code).toBe(202);
|
|
879
|
+
const { message: diffMsg } = await MessagesSync.create({
|
|
880
|
+
signer: Jws.createSigner(bob),
|
|
881
|
+
action: 'diff',
|
|
882
|
+
hashes: {},
|
|
883
|
+
depth: 2,
|
|
884
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
885
|
+
projectionScopes: [{ protocol, protocolPath: 'post' }],
|
|
886
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
887
|
+
});
|
|
888
|
+
const reply = await dwn.processMessage(alice.did, diffMsg);
|
|
889
|
+
expect(reply.status.code).toBe(200);
|
|
890
|
+
const postCid = await Message.getCid(postMessage);
|
|
891
|
+
expect(reply.onlyRemote.map(entry => entry.messageCid)).toContain(postCid);
|
|
892
|
+
expect(reply.dependencies.map(entry => entry.messageCid)).not.toContain(await Message.getCid(firstProtocolMessage));
|
|
893
|
+
expect(reply.dependencies.map(entry => entry.messageCid)).not.toContain(await Message.getCid(futureProtocolMessage));
|
|
894
|
+
expect(reply.dependencies).toEqual([
|
|
895
|
+
{
|
|
896
|
+
dependencyClass: 'protocolsConfigure',
|
|
897
|
+
messageCid: await Message.getCid(secondProtocolMessage),
|
|
898
|
+
message: secondProtocolMessage,
|
|
899
|
+
rootMessageCid: postCid,
|
|
900
|
+
},
|
|
901
|
+
]);
|
|
902
|
+
});
|
|
903
|
+
it('returns initial write and protocol config dependencies for projected delete tombstones', async () => {
|
|
904
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
905
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
906
|
+
const protocolDefinition = { ...freeForAll, protocol: 'http://projected-sync-delete-hints', published: true };
|
|
907
|
+
const unrelatedDefinition = { ...freeForAll, protocol: 'http://projected-sync-delete-hints-unrelated', published: true };
|
|
908
|
+
const protocol = protocolDefinition.protocol;
|
|
909
|
+
const { message: protocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
910
|
+
author: alice,
|
|
911
|
+
protocolDefinition,
|
|
912
|
+
});
|
|
913
|
+
expect((await dwn.processMessage(alice.did, protocolMessage)).status.code).toBe(202);
|
|
914
|
+
const { message: unrelatedProtocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
915
|
+
author: alice,
|
|
916
|
+
protocolDefinition: unrelatedDefinition,
|
|
917
|
+
});
|
|
918
|
+
expect((await dwn.processMessage(alice.did, unrelatedProtocolMessage)).status.code).toBe(202);
|
|
919
|
+
const { message: postMessage, dataStream: postDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
920
|
+
author: alice,
|
|
921
|
+
protocol,
|
|
922
|
+
protocolPath: 'post',
|
|
923
|
+
schema: protocolDefinition.types.post.schema,
|
|
924
|
+
});
|
|
925
|
+
expect((await dwn.processMessage(alice.did, postMessage, { dataStream: postDataStream })).status.code).toBe(202);
|
|
926
|
+
const { message: deleteMessage } = await TestDataGenerator.generateRecordsDelete({
|
|
927
|
+
author: alice,
|
|
928
|
+
recordId: postMessage.recordId,
|
|
929
|
+
});
|
|
930
|
+
expect((await dwn.processMessage(alice.did, deleteMessage)).status.code).toBe(202);
|
|
931
|
+
const { message: grantMessage, dataStream: grantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
932
|
+
author: alice,
|
|
933
|
+
grantedTo: bob,
|
|
934
|
+
scope: {
|
|
935
|
+
interface: DwnInterfaceName.Messages,
|
|
936
|
+
method: DwnMethodName.Read,
|
|
937
|
+
protocol,
|
|
938
|
+
protocolPath: 'post',
|
|
939
|
+
},
|
|
940
|
+
});
|
|
941
|
+
expect((await dwn.processMessage(alice.did, grantMessage, { dataStream: grantDataStream })).status.code).toBe(202);
|
|
942
|
+
const { message: diffMsg } = await MessagesSync.create({
|
|
943
|
+
signer: Jws.createSigner(bob),
|
|
944
|
+
action: 'diff',
|
|
945
|
+
hashes: {},
|
|
946
|
+
depth: 2,
|
|
947
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
948
|
+
projectionScopes: [{ protocol, protocolPath: 'post' }],
|
|
949
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
950
|
+
});
|
|
951
|
+
const reply = await dwn.processMessage(alice.did, diffMsg);
|
|
952
|
+
expect(reply.status.code).toBe(200);
|
|
953
|
+
const deleteCid = await Message.getCid(deleteMessage);
|
|
954
|
+
const postCid = await Message.getCid(postMessage);
|
|
955
|
+
const protocolCid = await Message.getCid(protocolMessage);
|
|
956
|
+
expect(reply.onlyRemote.map(entry => entry.messageCid)).toEqual([deleteCid]);
|
|
957
|
+
expect(reply.dependencies.map(entry => entry.messageCid)).not.toContain(await Message.getCid(unrelatedProtocolMessage));
|
|
958
|
+
expect(reply.dependencies).toEqual([
|
|
959
|
+
{
|
|
960
|
+
dependencyClass: 'recordsInitialWrite',
|
|
961
|
+
messageCid: postCid,
|
|
962
|
+
message: postMessage,
|
|
963
|
+
rootMessageCid: deleteCid,
|
|
964
|
+
},
|
|
965
|
+
{
|
|
966
|
+
dependencyClass: 'protocolsConfigure',
|
|
967
|
+
messageCid: protocolCid,
|
|
968
|
+
message: protocolMessage,
|
|
969
|
+
rootMessageCid: deleteCid,
|
|
970
|
+
},
|
|
971
|
+
]);
|
|
972
|
+
const initialWriteDependency = reply.dependencies.find(entry => entry.dependencyClass === 'recordsInitialWrite');
|
|
973
|
+
expect(initialWriteDependency.encodedData).toBeUndefined();
|
|
974
|
+
expect('encodedData' in initialWriteDependency.message).toBe(false);
|
|
975
|
+
});
|
|
976
|
+
it('returns composed protocol config dependencies for projected sync', async () => {
|
|
977
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
978
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
979
|
+
const socialDefinition = {
|
|
980
|
+
...freeForAll,
|
|
981
|
+
protocol: 'http://projected-sync-composed-social',
|
|
982
|
+
published: true,
|
|
983
|
+
};
|
|
984
|
+
const profileDefinition = {
|
|
985
|
+
...freeForAll,
|
|
986
|
+
protocol: 'http://projected-sync-composed-profile',
|
|
987
|
+
published: true,
|
|
988
|
+
uses: { social: socialDefinition.protocol },
|
|
989
|
+
};
|
|
990
|
+
const unrelatedDefinition = {
|
|
991
|
+
...freeForAll,
|
|
992
|
+
protocol: 'http://projected-sync-composed-unrelated',
|
|
993
|
+
published: true,
|
|
994
|
+
};
|
|
995
|
+
const { message: socialProtocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
996
|
+
author: alice,
|
|
997
|
+
protocolDefinition: socialDefinition,
|
|
998
|
+
});
|
|
999
|
+
expect((await dwn.processMessage(alice.did, socialProtocolMessage)).status.code).toBe(202);
|
|
1000
|
+
const { message: profileProtocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
1001
|
+
author: alice,
|
|
1002
|
+
protocolDefinition: profileDefinition,
|
|
1003
|
+
});
|
|
1004
|
+
expect((await dwn.processMessage(alice.did, profileProtocolMessage)).status.code).toBe(202);
|
|
1005
|
+
const { message: unrelatedProtocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
1006
|
+
author: alice,
|
|
1007
|
+
protocolDefinition: unrelatedDefinition,
|
|
1008
|
+
});
|
|
1009
|
+
expect((await dwn.processMessage(alice.did, unrelatedProtocolMessage)).status.code).toBe(202);
|
|
1010
|
+
const { message: postMessage, dataStream: postDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
1011
|
+
author: alice,
|
|
1012
|
+
protocol: profileDefinition.protocol,
|
|
1013
|
+
protocolPath: 'post',
|
|
1014
|
+
schema: profileDefinition.types.post.schema,
|
|
1015
|
+
});
|
|
1016
|
+
expect((await dwn.processMessage(alice.did, postMessage, { dataStream: postDataStream })).status.code).toBe(202);
|
|
1017
|
+
const { message: grantMessage, dataStream: grantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
1018
|
+
author: alice,
|
|
1019
|
+
grantedTo: bob,
|
|
1020
|
+
scope: {
|
|
1021
|
+
interface: DwnInterfaceName.Messages,
|
|
1022
|
+
method: DwnMethodName.Read,
|
|
1023
|
+
protocol: profileDefinition.protocol,
|
|
1024
|
+
protocolPath: 'post',
|
|
1025
|
+
},
|
|
1026
|
+
});
|
|
1027
|
+
expect((await dwn.processMessage(alice.did, grantMessage, { dataStream: grantDataStream })).status.code).toBe(202);
|
|
1028
|
+
const { message: diffMsg } = await MessagesSync.create({
|
|
1029
|
+
signer: Jws.createSigner(bob),
|
|
1030
|
+
action: 'diff',
|
|
1031
|
+
hashes: {},
|
|
1032
|
+
depth: 2,
|
|
1033
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
1034
|
+
projectionScopes: [{ protocol: profileDefinition.protocol, protocolPath: 'post' }],
|
|
1035
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
1036
|
+
});
|
|
1037
|
+
const reply = await dwn.processMessage(alice.did, diffMsg);
|
|
1038
|
+
expect(reply.status.code).toBe(200);
|
|
1039
|
+
const postCid = await Message.getCid(postMessage);
|
|
1040
|
+
expect(reply.onlyRemote.map(entry => entry.messageCid)).toContain(postCid);
|
|
1041
|
+
expect(reply.dependencies.map(entry => entry.messageCid)).not.toContain(await Message.getCid(unrelatedProtocolMessage));
|
|
1042
|
+
expect(reply.dependencies).toEqual([
|
|
1043
|
+
{
|
|
1044
|
+
dependencyClass: 'protocolsConfigure',
|
|
1045
|
+
messageCid: await Message.getCid(profileProtocolMessage),
|
|
1046
|
+
message: profileProtocolMessage,
|
|
1047
|
+
rootMessageCid: postCid,
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
dependencyClass: 'protocolsConfigure',
|
|
1051
|
+
messageCid: await Message.getCid(socialProtocolMessage),
|
|
1052
|
+
message: socialProtocolMessage,
|
|
1053
|
+
rootMessageCid: postCid,
|
|
1054
|
+
},
|
|
1055
|
+
]);
|
|
1056
|
+
});
|
|
1057
|
+
it('terminates cyclic composed protocol dependencies for projected sync', async () => {
|
|
1058
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
1059
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
1060
|
+
const protocolA = 'http://projected-sync-cycle-a';
|
|
1061
|
+
const protocolB = 'http://projected-sync-cycle-b';
|
|
1062
|
+
const protocolBBaseDefinition = {
|
|
1063
|
+
...freeForAll,
|
|
1064
|
+
protocol: protocolB,
|
|
1065
|
+
published: true,
|
|
1066
|
+
};
|
|
1067
|
+
const protocolADefinition = {
|
|
1068
|
+
...freeForAll,
|
|
1069
|
+
protocol: protocolA,
|
|
1070
|
+
published: true,
|
|
1071
|
+
uses: { b: protocolB },
|
|
1072
|
+
};
|
|
1073
|
+
const protocolBCycleDefinition = {
|
|
1074
|
+
...freeForAll,
|
|
1075
|
+
protocol: protocolB,
|
|
1076
|
+
published: true,
|
|
1077
|
+
uses: { a: protocolA },
|
|
1078
|
+
};
|
|
1079
|
+
const { message: protocolBBaseMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
1080
|
+
author: alice,
|
|
1081
|
+
messageTimestamp: '2026-01-01T00:00:00.000000Z',
|
|
1082
|
+
protocolDefinition: protocolBBaseDefinition,
|
|
1083
|
+
});
|
|
1084
|
+
expect((await dwn.processMessage(alice.did, protocolBBaseMessage)).status.code).toBe(202);
|
|
1085
|
+
const { message: protocolAMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
1086
|
+
author: alice,
|
|
1087
|
+
messageTimestamp: '2026-01-02T00:00:00.000000Z',
|
|
1088
|
+
protocolDefinition: protocolADefinition,
|
|
1089
|
+
});
|
|
1090
|
+
expect((await dwn.processMessage(alice.did, protocolAMessage)).status.code).toBe(202);
|
|
1091
|
+
const { message: protocolBCycleMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
1092
|
+
author: alice,
|
|
1093
|
+
messageTimestamp: '2026-01-03T00:00:00.000000Z',
|
|
1094
|
+
protocolDefinition: protocolBCycleDefinition,
|
|
1095
|
+
});
|
|
1096
|
+
expect((await dwn.processMessage(alice.did, protocolBCycleMessage)).status.code).toBe(202);
|
|
1097
|
+
const { message: postMessage, dataStream: postDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
1098
|
+
author: alice,
|
|
1099
|
+
dateCreated: '2026-01-04T00:00:00.000000Z',
|
|
1100
|
+
messageTimestamp: '2026-01-04T00:00:00.000000Z',
|
|
1101
|
+
protocol: protocolA,
|
|
1102
|
+
protocolPath: 'post',
|
|
1103
|
+
schema: protocolADefinition.types.post.schema,
|
|
1104
|
+
});
|
|
1105
|
+
expect((await dwn.processMessage(alice.did, postMessage, { dataStream: postDataStream })).status.code).toBe(202);
|
|
1106
|
+
const { message: grantMessage, dataStream: grantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
1107
|
+
author: alice,
|
|
1108
|
+
grantedTo: bob,
|
|
1109
|
+
scope: {
|
|
1110
|
+
interface: DwnInterfaceName.Messages,
|
|
1111
|
+
method: DwnMethodName.Read,
|
|
1112
|
+
protocol: protocolA,
|
|
1113
|
+
protocolPath: 'post',
|
|
1114
|
+
},
|
|
1115
|
+
});
|
|
1116
|
+
expect((await dwn.processMessage(alice.did, grantMessage, { dataStream: grantDataStream })).status.code).toBe(202);
|
|
1117
|
+
const { message: diffMsg } = await MessagesSync.create({
|
|
1118
|
+
signer: Jws.createSigner(bob),
|
|
1119
|
+
action: 'diff',
|
|
1120
|
+
hashes: {},
|
|
1121
|
+
depth: 2,
|
|
1122
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
1123
|
+
projectionScopes: [{ protocol: protocolA, protocolPath: 'post' }],
|
|
1124
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
1125
|
+
});
|
|
1126
|
+
const reply = await dwn.processMessage(alice.did, diffMsg);
|
|
1127
|
+
expect(reply.status.code).toBe(200);
|
|
1128
|
+
const postCid = await Message.getCid(postMessage);
|
|
1129
|
+
const dependencyCids = reply.dependencies.map(entry => entry.messageCid);
|
|
1130
|
+
expect(reply.onlyRemote.map(entry => entry.messageCid)).toContain(postCid);
|
|
1131
|
+
expect(dependencyCids.sort()).toEqual([
|
|
1132
|
+
await Message.getCid(protocolAMessage),
|
|
1133
|
+
await Message.getCid(protocolBCycleMessage),
|
|
1134
|
+
].sort());
|
|
1135
|
+
expect(dependencyCids).not.toContain(await Message.getCid(protocolBBaseMessage));
|
|
1136
|
+
expect(reply.dependencies.every(entry => entry.rootMessageCid === postCid)).toBe(true);
|
|
1137
|
+
});
|
|
1138
|
+
it('rejects projected sync when no grant covers a requested projection scope', async () => {
|
|
1139
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
1140
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
1141
|
+
const protocol = 'http://projected-sync-scope-mismatch';
|
|
1142
|
+
const { message: grantMessage, dataStream: grantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
1143
|
+
author: alice,
|
|
1144
|
+
grantedTo: bob,
|
|
1145
|
+
scope: {
|
|
1146
|
+
interface: DwnInterfaceName.Messages,
|
|
1147
|
+
method: DwnMethodName.Read,
|
|
1148
|
+
protocol,
|
|
1149
|
+
protocolPath: 'post',
|
|
1150
|
+
},
|
|
1151
|
+
});
|
|
1152
|
+
expect((await dwn.processMessage(alice.did, grantMessage, { dataStream: grantDataStream })).status.code).toBe(202);
|
|
1153
|
+
const { message: syncMsg } = await MessagesSync.create({
|
|
1154
|
+
signer: Jws.createSigner(bob),
|
|
1155
|
+
action: 'root',
|
|
1156
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
1157
|
+
projectionScopes: [{ protocol }],
|
|
1158
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
1159
|
+
});
|
|
1160
|
+
const reply = await dwn.processMessage(alice.did, syncMsg);
|
|
1161
|
+
expect(reply.status.code).toBe(401);
|
|
1162
|
+
expect(reply.status.detail).toContain(DwnErrorCode.MessagesGrantAuthorizationProjectionScopeMismatch);
|
|
1163
|
+
});
|
|
1164
|
+
it('rejects projected sync when any requested projection scope is uncovered', async () => {
|
|
1165
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
1166
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
1167
|
+
const coveredProtocol = 'http://projected-sync-covered-scope';
|
|
1168
|
+
const uncoveredProtocol = 'http://projected-sync-uncovered-scope';
|
|
1169
|
+
const { message: grantMessage, dataStream: grantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
1170
|
+
author: alice,
|
|
1171
|
+
grantedTo: bob,
|
|
1172
|
+
scope: {
|
|
1173
|
+
interface: DwnInterfaceName.Messages,
|
|
1174
|
+
method: DwnMethodName.Read,
|
|
1175
|
+
protocol: coveredProtocol,
|
|
1176
|
+
protocolPath: 'post',
|
|
1177
|
+
},
|
|
1178
|
+
});
|
|
1179
|
+
expect((await dwn.processMessage(alice.did, grantMessage, { dataStream: grantDataStream })).status.code).toBe(202);
|
|
1180
|
+
const { message: syncMsg } = await MessagesSync.create({
|
|
1181
|
+
signer: Jws.createSigner(bob),
|
|
1182
|
+
action: 'root',
|
|
1183
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
1184
|
+
projectionScopes: [
|
|
1185
|
+
{ protocol: coveredProtocol, protocolPath: 'post' },
|
|
1186
|
+
{ protocol: uncoveredProtocol, protocolPath: 'post' },
|
|
1187
|
+
],
|
|
1188
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
1189
|
+
});
|
|
1190
|
+
const reply = await dwn.processMessage(alice.did, syncMsg);
|
|
1191
|
+
expect(reply.status.code).toBe(401);
|
|
1192
|
+
expect(reply.status.detail).toContain(DwnErrorCode.MessagesGrantAuthorizationProjectionScopeMismatch);
|
|
1193
|
+
});
|
|
1194
|
+
it('rejects delegated MessagesSync of infrastructure protocols', async () => {
|
|
1195
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
1196
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
1197
|
+
const carol = await TestDataGenerator.generateDidKeyPersona();
|
|
1198
|
+
const { message: permissionsGrantMessage, dataStream: permissionsGrantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
1199
|
+
author: alice,
|
|
1200
|
+
grantedTo: bob,
|
|
1201
|
+
scope: {
|
|
1202
|
+
interface: DwnInterfaceName.Messages,
|
|
1203
|
+
method: DwnMethodName.Read,
|
|
1204
|
+
protocol: PermissionsProtocol.uri,
|
|
1205
|
+
},
|
|
1206
|
+
});
|
|
1207
|
+
expect((await dwn.processMessage(alice.did, permissionsGrantMessage, { dataStream: permissionsGrantDataStream })).status.code).toBe(202);
|
|
1208
|
+
const { message: keyDeliveryGrantMessage, dataStream: keyDeliveryGrantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
1209
|
+
author: alice,
|
|
1210
|
+
grantedTo: bob,
|
|
1211
|
+
scope: {
|
|
1212
|
+
interface: DwnInterfaceName.Messages,
|
|
1213
|
+
method: DwnMethodName.Read,
|
|
1214
|
+
protocol: KEY_DELIVERY_PROTOCOL_URI,
|
|
1215
|
+
},
|
|
1216
|
+
});
|
|
1217
|
+
expect((await dwn.processMessage(alice.did, keyDeliveryGrantMessage, { dataStream: keyDeliveryGrantDataStream })).status.code).toBe(202);
|
|
1218
|
+
const { message: carolGrantMessage, dataStream: carolGrantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
1219
|
+
author: alice,
|
|
1220
|
+
grantedTo: carol,
|
|
1221
|
+
scope: {
|
|
1222
|
+
interface: DwnInterfaceName.Messages,
|
|
1223
|
+
method: DwnMethodName.Read,
|
|
1224
|
+
protocol: 'http://private-delegate-protocol',
|
|
1225
|
+
},
|
|
1226
|
+
});
|
|
1227
|
+
expect((await dwn.processMessage(alice.did, carolGrantMessage, { dataStream: carolGrantDataStream })).status.code).toBe(202);
|
|
1228
|
+
const { message: readMessage } = await TestDataGenerator.generateMessagesRead({
|
|
1229
|
+
author: bob,
|
|
1230
|
+
messageCid: await Message.getCid(carolGrantMessage),
|
|
1231
|
+
permissionGrantIds: [permissionsGrantMessage.recordId],
|
|
1232
|
+
});
|
|
1233
|
+
const readReply = await dwn.processMessage(alice.did, readMessage);
|
|
1234
|
+
expect(readReply.status.code).toBe(401);
|
|
1235
|
+
expect(readReply.status.detail).toContain(DwnErrorCode.MessagesReadVerifyScopeFailed);
|
|
1236
|
+
const stateIndexSyncActions = [
|
|
1237
|
+
{ action: 'root' },
|
|
1238
|
+
{ action: 'subtree', prefix: '' },
|
|
1239
|
+
{ action: 'leaves', prefix: '' },
|
|
1240
|
+
{ action: 'diff', hashes: {}, depth: 2 },
|
|
1241
|
+
];
|
|
1242
|
+
const infrastructureProtocolGrants = [
|
|
1243
|
+
{ protocol: PermissionsProtocol.uri, grantId: permissionsGrantMessage.recordId },
|
|
1244
|
+
{ protocol: KEY_DELIVERY_PROTOCOL_URI, grantId: keyDeliveryGrantMessage.recordId },
|
|
1245
|
+
];
|
|
1246
|
+
for (const { protocol, grantId } of infrastructureProtocolGrants) {
|
|
1247
|
+
for (const syncAction of stateIndexSyncActions) {
|
|
1248
|
+
const { message: syncMessage } = await MessagesSync.create({
|
|
1249
|
+
signer: Jws.createSigner(bob),
|
|
1250
|
+
...syncAction,
|
|
1251
|
+
protocol,
|
|
1252
|
+
permissionGrantIds: [grantId],
|
|
1253
|
+
});
|
|
1254
|
+
const syncReply = await dwn.processMessage(alice.did, syncMessage);
|
|
1255
|
+
expect(syncReply.status.code).toBe(401);
|
|
1256
|
+
expect(syncReply.status.detail).toContain(DwnErrorCode.MessagesGrantAuthorizationProtocolSyncInfrastructureProtocol);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
for (const { protocol, grantId } of infrastructureProtocolGrants) {
|
|
1260
|
+
const { message: projectedSyncMessage } = await MessagesSync.create({
|
|
1261
|
+
signer: Jws.createSigner(bob),
|
|
1262
|
+
action: 'diff',
|
|
1263
|
+
hashes: {},
|
|
1264
|
+
depth: 2,
|
|
1265
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
1266
|
+
projectionScopes: [{ protocol }],
|
|
1267
|
+
permissionGrantIds: [grantId],
|
|
1268
|
+
});
|
|
1269
|
+
const projectedSyncReply = await dwn.processMessage(alice.did, projectedSyncMessage);
|
|
1270
|
+
expect(projectedSyncReply.status.code).toBe(401);
|
|
1271
|
+
expect(projectedSyncReply.status.detail).toContain(DwnErrorCode.MessagesGrantAuthorizationProjectionInfrastructureProtocol);
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
it('allows projected sync with a matching contextId Messages.Read grant', async () => {
|
|
1275
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
1276
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
1277
|
+
const protocolDefinition = { ...freeForAll, published: true };
|
|
1278
|
+
const protocol = protocolDefinition.protocol;
|
|
1279
|
+
const { message: protocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
1280
|
+
author: alice,
|
|
1281
|
+
protocolDefinition,
|
|
1282
|
+
});
|
|
1283
|
+
expect((await dwn.processMessage(alice.did, protocolMessage)).status.code).toBe(202);
|
|
1284
|
+
const { message: rootMessage, dataStream: rootDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
1285
|
+
author: alice,
|
|
1286
|
+
protocol,
|
|
1287
|
+
protocolPath: 'post',
|
|
1288
|
+
schema: protocolDefinition.types.post.schema,
|
|
1289
|
+
});
|
|
1290
|
+
expect((await dwn.processMessage(alice.did, rootMessage, { dataStream: rootDataStream })).status.code).toBe(202);
|
|
1291
|
+
const { message: childMessage, dataStream: childDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
1292
|
+
author: alice,
|
|
1293
|
+
protocol,
|
|
1294
|
+
protocolPath: 'post/attachment',
|
|
1295
|
+
parentContextId: rootMessage.contextId,
|
|
1296
|
+
});
|
|
1297
|
+
expect((await dwn.processMessage(alice.did, childMessage, { dataStream: childDataStream })).status.code).toBe(202);
|
|
1298
|
+
const { message: siblingMessage, dataStream: siblingDataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
1299
|
+
author: alice,
|
|
1300
|
+
protocol,
|
|
1301
|
+
protocolPath: 'post',
|
|
1302
|
+
schema: protocolDefinition.types.post.schema,
|
|
1303
|
+
});
|
|
1304
|
+
expect((await dwn.processMessage(alice.did, siblingMessage, { dataStream: siblingDataStream })).status.code).toBe(202);
|
|
1305
|
+
const { message: grantMessage, dataStream: grantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
1306
|
+
author: alice,
|
|
1307
|
+
grantedTo: bob,
|
|
1308
|
+
scope: {
|
|
1309
|
+
interface: DwnInterfaceName.Messages,
|
|
1310
|
+
method: DwnMethodName.Read,
|
|
1311
|
+
protocol,
|
|
1312
|
+
contextId: rootMessage.contextId,
|
|
1313
|
+
},
|
|
1314
|
+
});
|
|
1315
|
+
expect((await dwn.processMessage(alice.did, grantMessage, { dataStream: grantDataStream })).status.code).toBe(202);
|
|
1316
|
+
const { message: syncMsg } = await MessagesSync.create({
|
|
1317
|
+
signer: Jws.createSigner(bob),
|
|
1318
|
+
action: 'leaves',
|
|
1319
|
+
prefix: '',
|
|
1320
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
1321
|
+
projectionScopes: [{ protocol, contextId: rootMessage.contextId }],
|
|
1322
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
1323
|
+
});
|
|
1324
|
+
const reply = await dwn.processMessage(alice.did, syncMsg);
|
|
1325
|
+
expect(reply.status.code).toBe(200);
|
|
1326
|
+
expect(reply.entries).toContain(await Message.getCid(rootMessage));
|
|
1327
|
+
expect(reply.entries).toContain(await Message.getCid(childMessage));
|
|
1328
|
+
expect(reply.entries).not.toContain(await Message.getCid(protocolMessage));
|
|
1329
|
+
expect(reply.entries).not.toContain(await Message.getCid(siblingMessage));
|
|
1330
|
+
});
|
|
1331
|
+
it('returns only protocol-scoped diff entries for a delegated protocol grant', async () => {
|
|
1332
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
1333
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
1334
|
+
const protocolA = { ...freeForAll, protocol: 'http://delegated-diff-protocol-a' };
|
|
1335
|
+
const protocolB = { ...freeForAll, protocol: 'http://delegated-diff-protocol-b' };
|
|
1336
|
+
for (const protocolDefinition of [protocolA, protocolB]) {
|
|
1337
|
+
const { message: protocolMessage } = await TestDataGenerator.generateProtocolsConfigure({
|
|
1338
|
+
author: alice,
|
|
1339
|
+
protocolDefinition,
|
|
1340
|
+
});
|
|
1341
|
+
expect((await dwn.processMessage(alice.did, protocolMessage)).status.code).toBe(202);
|
|
1342
|
+
}
|
|
1343
|
+
const { message: recordA, dataStream: dataStreamA } = await TestDataGenerator.generateRecordsWrite({
|
|
1344
|
+
author: alice,
|
|
1345
|
+
protocol: protocolA.protocol,
|
|
1346
|
+
protocolPath: 'post',
|
|
1347
|
+
schema: protocolA.types.post.schema,
|
|
1348
|
+
});
|
|
1349
|
+
expect((await dwn.processMessage(alice.did, recordA, { dataStream: dataStreamA })).status.code).toBe(202);
|
|
1350
|
+
const { message: recordB, dataStream: dataStreamB } = await TestDataGenerator.generateRecordsWrite({
|
|
1351
|
+
author: alice,
|
|
1352
|
+
protocol: protocolB.protocol,
|
|
1353
|
+
protocolPath: 'post',
|
|
1354
|
+
schema: protocolB.types.post.schema,
|
|
1355
|
+
});
|
|
1356
|
+
expect((await dwn.processMessage(alice.did, recordB, { dataStream: dataStreamB })).status.code).toBe(202);
|
|
1357
|
+
const { message: grantMessage, dataStream: grantDataStream } = await TestDataGenerator.generateGrantCreate({
|
|
1358
|
+
author: alice,
|
|
1359
|
+
grantedTo: bob,
|
|
1360
|
+
scope: {
|
|
1361
|
+
interface: DwnInterfaceName.Messages,
|
|
1362
|
+
method: DwnMethodName.Read,
|
|
1363
|
+
protocol: protocolA.protocol,
|
|
1364
|
+
},
|
|
1365
|
+
});
|
|
1366
|
+
expect((await dwn.processMessage(alice.did, grantMessage, { dataStream: grantDataStream })).status.code).toBe(202);
|
|
1367
|
+
const { message: diffMsg } = await MessagesSync.create({
|
|
1368
|
+
signer: Jws.createSigner(bob),
|
|
1369
|
+
action: 'diff',
|
|
1370
|
+
hashes: {},
|
|
1371
|
+
depth: 2,
|
|
1372
|
+
protocol: protocolA.protocol,
|
|
1373
|
+
permissionGrantIds: [grantMessage.recordId],
|
|
1374
|
+
});
|
|
1375
|
+
const reply = await dwn.processMessage(alice.did, diffMsg);
|
|
1376
|
+
expect(reply.status.code).toBe(200);
|
|
1377
|
+
const remoteCids = reply.onlyRemote.map(entry => entry.messageCid);
|
|
1378
|
+
expect(remoteCids).toContain(await Message.getCid(recordA));
|
|
1379
|
+
expect(remoteCids).not.toContain(await Message.getCid(recordB));
|
|
1380
|
+
expect(reply.onlyRemote.every(entry => {
|
|
1381
|
+
if (entry.message?.descriptor.interface !== DwnInterfaceName.Records) {
|
|
1382
|
+
return true;
|
|
1383
|
+
}
|
|
1384
|
+
const recordsMessage = entry.message;
|
|
1385
|
+
return recordsMessage.descriptor.protocol === protocolA.protocol;
|
|
1386
|
+
})).toBe(true);
|
|
1387
|
+
});
|
|
459
1388
|
});
|
|
460
1389
|
});
|
|
461
1390
|
describe('input validation', () => {
|
|
@@ -547,6 +1476,77 @@ export function testMessagesSyncHandler() {
|
|
|
547
1476
|
parseStub.restore();
|
|
548
1477
|
}
|
|
549
1478
|
});
|
|
1479
|
+
it('returns 400 when projection scopes are provided without a projection root version', async () => {
|
|
1480
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
1481
|
+
const { message } = await MessagesSync.create({
|
|
1482
|
+
signer: Jws.createSigner(alice),
|
|
1483
|
+
action: 'root',
|
|
1484
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
1485
|
+
projectionScopes: [{ protocol: 'http://projected-sync-schema' }],
|
|
1486
|
+
});
|
|
1487
|
+
delete message.descriptor.projectionRootVersion;
|
|
1488
|
+
const reply = await dwn.processMessage(alice.did, message);
|
|
1489
|
+
expect(reply.status.code).toBe(400);
|
|
1490
|
+
expect(reply.status.detail).toContain('SchemaValidatorFailure');
|
|
1491
|
+
});
|
|
1492
|
+
it('returns 400 when projection root version is provided without projection scopes', async () => {
|
|
1493
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
1494
|
+
const { message } = await MessagesSync.create({
|
|
1495
|
+
signer: Jws.createSigner(alice),
|
|
1496
|
+
action: 'root',
|
|
1497
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
1498
|
+
projectionScopes: [{ protocol: 'http://projected-sync-schema' }],
|
|
1499
|
+
});
|
|
1500
|
+
delete message.descriptor.projectionScopes;
|
|
1501
|
+
const reply = await dwn.processMessage(alice.did, message);
|
|
1502
|
+
expect(reply.status.code).toBe(400);
|
|
1503
|
+
expect(reply.status.detail).toContain('SchemaValidatorFailure');
|
|
1504
|
+
});
|
|
1505
|
+
it('returns 400 when projection root version is unsupported', async () => {
|
|
1506
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
1507
|
+
const { message } = await MessagesSync.create({
|
|
1508
|
+
signer: Jws.createSigner(alice),
|
|
1509
|
+
action: 'root',
|
|
1510
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
1511
|
+
projectionScopes: [{ protocol: 'http://projected-sync-schema' }],
|
|
1512
|
+
});
|
|
1513
|
+
const unsupportedProjectionRootVersion = 'records-primary-scope-root-v2';
|
|
1514
|
+
const descriptor = {
|
|
1515
|
+
...message.descriptor,
|
|
1516
|
+
projectionRootVersion: unsupportedProjectionRootVersion,
|
|
1517
|
+
};
|
|
1518
|
+
const authorization = await Message.createAuthorization({
|
|
1519
|
+
descriptor,
|
|
1520
|
+
signer: Jws.createSigner(alice),
|
|
1521
|
+
});
|
|
1522
|
+
const unsupportedVersionMessage = {
|
|
1523
|
+
...message,
|
|
1524
|
+
descriptor,
|
|
1525
|
+
authorization,
|
|
1526
|
+
};
|
|
1527
|
+
const validateJsonSchemaStub = sinon.stub(Message, 'validateJsonSchema');
|
|
1528
|
+
try {
|
|
1529
|
+
const reply = await dwn.processMessage(alice.did, unsupportedVersionMessage);
|
|
1530
|
+
expect(reply.status.code).toBe(400);
|
|
1531
|
+
expect(reply.status.detail).toContain(DwnErrorCode.MessagesSyncUnsupportedProjectionRootVersion);
|
|
1532
|
+
}
|
|
1533
|
+
finally {
|
|
1534
|
+
validateJsonSchemaStub.restore();
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
it('returns 400 when projected sync also specifies a protocol root', async () => {
|
|
1538
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
1539
|
+
const { message } = await MessagesSync.create({
|
|
1540
|
+
signer: Jws.createSigner(alice),
|
|
1541
|
+
action: 'root',
|
|
1542
|
+
projectionRootVersion: RECORDS_PROJECTION_ROOT_VERSION,
|
|
1543
|
+
projectionScopes: [{ protocol: 'http://projected-sync-schema' }],
|
|
1544
|
+
});
|
|
1545
|
+
message.descriptor.protocol = 'http://projected-sync-schema';
|
|
1546
|
+
const reply = await dwn.processMessage(alice.did, message);
|
|
1547
|
+
expect(reply.status.code).toBe(400);
|
|
1548
|
+
expect(reply.status.detail).toContain('SchemaValidatorFailure');
|
|
1549
|
+
});
|
|
550
1550
|
});
|
|
551
1551
|
describe('diff action', () => {
|
|
552
1552
|
it('returns empty diff when client hashes match server hashes', async () => {
|
|
@@ -619,6 +1619,26 @@ export function testMessagesSyncHandler() {
|
|
|
619
1619
|
expect(reply.onlyLocal).toContain('00');
|
|
620
1620
|
expect(reply.onlyLocal).toContain('01');
|
|
621
1621
|
});
|
|
1622
|
+
it('prunes empty server subtrees at the current diff depth', async () => {
|
|
1623
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
1624
|
+
const getSubtreeHashSpy = sinon.spy(stateIndex, 'getSubtreeHash');
|
|
1625
|
+
try {
|
|
1626
|
+
const { message: diffMsg } = await MessagesSync.create({
|
|
1627
|
+
signer: Jws.createSigner(alice),
|
|
1628
|
+
action: 'diff',
|
|
1629
|
+
hashes: {},
|
|
1630
|
+
depth: 64,
|
|
1631
|
+
});
|
|
1632
|
+
const reply = await dwn.processMessage(alice.did, diffMsg);
|
|
1633
|
+
expect(reply.status.code).toBe(200);
|
|
1634
|
+
expect(reply.onlyRemote).toEqual([]);
|
|
1635
|
+
expect(reply.onlyLocal).toEqual([]);
|
|
1636
|
+
expect(getSubtreeHashSpy.callCount).toBe(1);
|
|
1637
|
+
}
|
|
1638
|
+
finally {
|
|
1639
|
+
getSubtreeHashSpy.restore();
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
622
1642
|
it('inlines small data payloads as encodedData in onlyRemote entries', async () => {
|
|
623
1643
|
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
624
1644
|
await TestDataGenerator.installDefaultTestProtocol(dwn, alice);
|
|
@@ -645,6 +1665,32 @@ export function testMessagesSyncHandler() {
|
|
|
645
1665
|
expect(recordEntry.encodedData).toBeDefined();
|
|
646
1666
|
expect(typeof recordEntry.encodedData).toBe('string');
|
|
647
1667
|
});
|
|
1668
|
+
it('inlines small dataStore-backed payloads using the RecordsWrite recordId', async () => {
|
|
1669
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
1670
|
+
await TestDataGenerator.installDefaultTestProtocol(dwn, alice);
|
|
1671
|
+
const dataBytes = new TextEncoder().encode('small data-store payload');
|
|
1672
|
+
const { message: recordMessage, recordsWrite } = await TestDataGenerator.generateRecordsWrite({
|
|
1673
|
+
author: alice,
|
|
1674
|
+
data: dataBytes,
|
|
1675
|
+
dataFormat: 'text/plain',
|
|
1676
|
+
});
|
|
1677
|
+
const indexes = await recordsWrite.constructIndexes(true);
|
|
1678
|
+
const messageCid = await Message.getCid(recordMessage);
|
|
1679
|
+
await dataStore.put(alice.did, recordMessage.recordId, recordMessage.descriptor.dataCid, DataStream.fromBytes(dataBytes));
|
|
1680
|
+
await messageStore.put(alice.did, recordMessage, indexes);
|
|
1681
|
+
await stateIndex.insert(alice.did, messageCid, indexes);
|
|
1682
|
+
const { message: diffMsg } = await MessagesSync.create({
|
|
1683
|
+
signer: Jws.createSigner(alice),
|
|
1684
|
+
action: 'diff',
|
|
1685
|
+
hashes: {},
|
|
1686
|
+
depth: 2,
|
|
1687
|
+
});
|
|
1688
|
+
const reply = await dwn.processMessage(alice.did, diffMsg);
|
|
1689
|
+
expect(reply.status.code).toBe(200);
|
|
1690
|
+
const recordEntry = reply.onlyRemote.find(entry => entry.messageCid === messageCid);
|
|
1691
|
+
expect(recordEntry).toBeDefined();
|
|
1692
|
+
expect(recordEntry.encodedData).toBe(Encoder.bytesToBase64Url(dataBytes));
|
|
1693
|
+
});
|
|
648
1694
|
it('returns 400 when hashes or depth are missing', async () => {
|
|
649
1695
|
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
650
1696
|
// create a valid diff message then strip required fields
|