From e7cb25a06ac5c521943bd0bb1cac55770c2ea82c Mon Sep 17 00:00:00 2001
From: Liran Cohen <c.liran.c@gmail.com>
Date: Wed, 11 Sep 2024 20:35:24 -0400
Subject: [PATCH] Protocol Query with or without grant, Configure with grant.
 (#894)

- protocol query with regular permission grant
  - if grant is not found, author query as delegate did to get any public protocols
- protocol configure with delegate grant
- `Permission` can now include `configure` which represents `ProtocolsConfigure` of a particular protocol
- `createPermissionRequestForProtocol` now includes a grant for `ProtocolsQuery` for the protocol.
---
 .changeset/eighty-bikes-join.md       |    5 +
 .changeset/slimy-bulldogs-kiss.md     |    8 +
 package.json                          |    2 +-
 packages/agent/package.json           |    2 +-
 packages/agent/src/connect.ts         |   20 +-
 packages/agent/src/oidc.ts            |   21 +-
 packages/agent/src/permissions-api.ts |   18 +-
 packages/agent/src/utils.ts           |    2 +-
 packages/agent/tests/connect.spec.ts  |   15 +-
 packages/api/package.json             |    2 +-
 packages/api/src/dwn-api.ts           |   57 +-
 packages/api/src/web5.ts              |    2 +-
 packages/api/tests/dwn-api.spec.ts    | 1020 ++++++++++++++-----------
 packages/dev-env/docker-compose.yaml  |    2 +-
 pnpm-lock.yaml                        |   36 +-
 15 files changed, 711 insertions(+), 501 deletions(-)
 create mode 100644 .changeset/eighty-bikes-join.md
 create mode 100644 .changeset/slimy-bulldogs-kiss.md

diff --git a/.changeset/eighty-bikes-join.md b/.changeset/eighty-bikes-join.md
new file mode 100644
index 000000000..0c1aa988b
--- /dev/null
+++ b/.changeset/eighty-bikes-join.md
@@ -0,0 +1,5 @@
+---
+"@web5/api": patch
+---
+
+Enable Protocol Query/Configure with delegate Grant
diff --git a/.changeset/slimy-bulldogs-kiss.md b/.changeset/slimy-bulldogs-kiss.md
new file mode 100644
index 000000000..0c5e07900
--- /dev/null
+++ b/.changeset/slimy-bulldogs-kiss.md
@@ -0,0 +1,8 @@
+---
+"@web5/agent": patch
+"@web5/identity-agent": patch
+"@web5/proxy-agent": patch
+"@web5/user-agent": patch
+---
+
+Enable ProtocolQuery/Configure with delegate grant
diff --git a/package.json b/package.json
index d5a5292b5..765a28f44 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
     "@changesets/cli": "^2.27.5",
     "@npmcli/package-json": "5.0.0",
     "@typescript-eslint/eslint-plugin": "7.9.0",
-    "@web5/dwn-server": "0.4.9",
+    "@web5/dwn-server": "0.4.10",
     "audit-ci": "^7.0.1",
     "eslint-plugin-mocha": "10.4.3",
     "globals": "^13.24.0",
diff --git a/packages/agent/package.json b/packages/agent/package.json
index 4bfec2ed7..4ef00ec5a 100644
--- a/packages/agent/package.json
+++ b/packages/agent/package.json
@@ -71,7 +71,7 @@
   "dependencies": {
     "@noble/ciphers": "0.5.3",
     "@scure/bip39": "1.2.2",
-    "@tbd54566975/dwn-sdk-js": "0.4.6",
+    "@tbd54566975/dwn-sdk-js": "0.4.7",
     "@web5/common": "1.0.0",
     "@web5/crypto": "workspace:*",
     "@web5/dids": "workspace:*",
diff --git a/packages/agent/src/connect.ts b/packages/agent/src/connect.ts
index 884c15b96..68aff74e9 100644
--- a/packages/agent/src/connect.ts
+++ b/packages/agent/src/connect.ts
@@ -188,7 +188,7 @@ export type ConnectPermissionRequest = {
 /**
  * Shorthand for the types of permissions that can be requested.
  */
-export type Permission = 'write' | 'read' | 'delete' | 'query' | 'subscribe';
+export type Permission = 'write' | 'read' | 'delete' | 'query' | 'subscribe' | 'configure';
 
 /**
  * The options for creating a permission request for a given protocol.
@@ -203,11 +203,20 @@ export type ProtocolPermissionOptions = {
 
 /**
  * Creates a set of Dwn Permission Scopes to request for a given protocol.
- * If no permissions are provided, the default is to request all permissions (write, read, delete, query, subscribe).
+ *
+ * If no permissions are provided, the default is to request all relevant record permissions (write, read, delete, query, subscribe).
+ * 'configure' is not included by default, as this gives the application a lot of control over the protocol.
  */
 function createPermissionRequestForProtocol({ definition, permissions }: ProtocolPermissionOptions): ConnectPermissionRequest {
   const requests: DwnPermissionScope[] = [];
 
+  // Add the ability to query for the specific protocol
+  requests.push({
+    protocol  : definition.protocol,
+    interface : DwnInterfaceName.Protocols,
+    method    : DwnMethodName.Query,
+  });
+
   // In order to enable sync, we must request permissions for `MessagesQuery`, `MessagesRead` and `MessagesSubscribe`
   requests.push({
     protocol  : definition.protocol,
@@ -261,6 +270,13 @@ function createPermissionRequestForProtocol({ definition, permissions }: Protoco
           method    : DwnMethodName.Subscribe,
         });
         break;
+      case 'configure':
+        requests.push({
+          protocol  : definition.protocol,
+          interface : DwnInterfaceName.Protocols,
+          method    : DwnMethodName.Configure,
+        });
+        break;
     }
   }
 
diff --git a/packages/agent/src/oidc.ts b/packages/agent/src/oidc.ts
index 3443aabe5..e56e9eb1f 100644
--- a/packages/agent/src/oidc.ts
+++ b/packages/agent/src/oidc.ts
@@ -16,6 +16,7 @@ import { DwnDataEncodedRecordsWriteMessage, DwnInterface, DwnPermissionScope, Dw
 import { AgentPermissionsApi } from './permissions-api.js';
 import type { Web5Agent } from './types/agent.js';
 import { isRecordPermissionScope } from './dwn-api.js';
+import { DwnInterfaceName, DwnMethodName } from '@tbd54566975/dwn-sdk-js';
 
 /**
  * Sent to an OIDC server to authorize a client. Allows clients
@@ -600,6 +601,20 @@ function encryptAuthResponse({
   return compactJwe;
 }
 
+function shouldUseDelegatePermission(scope: DwnPermissionScope): boolean {
+  // Currently all record permissions are treated as delegated permissions
+  // In the future only methods that modify state will be delegated and the rest will be normal permissions
+  if (isRecordPermissionScope(scope)) {
+    return true;
+  } else if (scope.interface === DwnInterfaceName.Protocols && scope.method === DwnMethodName.Configure) {
+    // ProtocolConfigure messages are also delegated, as they modify state
+    return true;
+  }
+
+  // All other permissions are not treated as delegated
+  return false;
+}
+
 /**
  * Creates the permission grants that assign to the selectedDid the level of
  * permissions that the web app requested in the {@link Web5ConnectAuthRequest}
@@ -615,9 +630,8 @@ async function createPermissionGrants(
   // TODO: cleanup all grants if one fails by deleting them from the DWN: https://github.com/TBD54566975/web5-js/issues/849
   const permissionGrants = await Promise.all(
     scopes.map((scope) => {
-
-      // check if the scope is a records permission scope, if so it is a delegated permission
-      const delegated = isRecordPermissionScope(scope);
+      // check if the scope is a records permission scope, or a protocol configure scope, if so it should use a delegated permission.
+      const delegated = shouldUseDelegatePermission(scope);
       return permissionsApi.createGrant({
         delegated,
         store       : true,
@@ -626,7 +640,6 @@ async function createPermissionGrants(
         dateExpires : '2040-06-25T16:09:16.693356Z', // TODO: make dateExpires optional
         author      : selectedDid,
       });
-
     })
   );
 
diff --git a/packages/agent/src/permissions-api.ts b/packages/agent/src/permissions-api.ts
index 86ae5e343..6abf2fd5a 100644
--- a/packages/agent/src/permissions-api.ts
+++ b/packages/agent/src/permissions-api.ts
@@ -365,7 +365,7 @@ export class AgentPermissionsApi implements PermissionsApi {
     if (scopeMessageType === messageType) {
       if (isRecordsType(messageType)) {
         const recordScope = scope as DwnRecordsPermissionScope;
-        if (!this.matchesProtocol(recordScope, protocol)) {
+        if (recordScope.protocol !== protocol) {
           return false;
         }
 
@@ -386,11 +386,12 @@ export class AgentPermissionsApi implements PermissionsApi {
         }
       } else {
         const messagesScope = scope as DwnMessagesPermissionScope | DwnProtocolPermissionScope;
-        if (this.protocolScopeUnrestricted(messagesScope)) {
+        // Checks for unrestricted protocol scope, if no protocol is defined in the scope it is unrestricted
+        if (messagesScope.protocol === undefined) {
           return true;
         }
 
-        if (!this.matchesProtocol(messagesScope, protocol)) {
+        if (messagesScope.protocol !== protocol) {
           return false;
         }
 
@@ -401,17 +402,6 @@ export class AgentPermissionsApi implements PermissionsApi {
     return false;
   }
 
-  private static matchesProtocol(scope: DwnPermissionScope & { protocol?: string }, protocol?: string): boolean {
-    return scope.protocol !== undefined && scope.protocol === protocol;
-  }
-
-  /**
-   *  Checks if the scope is restricted to a specific protocol
-   */
-  private static protocolScopeUnrestricted(scope: DwnPermissionScope & { protocol?: string }): boolean {
-    return scope.protocol === undefined;
-  }
-
   private static isUnrestrictedProtocolScope(scope: DwnPermissionScope & { contextId?: string, protocolPath?: string }): boolean {
     return scope.contextId === undefined && scope.protocolPath === undefined;
   }
diff --git a/packages/agent/src/utils.ts b/packages/agent/src/utils.ts
index 936d95980..f0d1824aa 100644
--- a/packages/agent/src/utils.ts
+++ b/packages/agent/src/utils.ts
@@ -39,7 +39,7 @@ export async function getDwnServiceEndpointUrls(didUri: string, dereferencer: Di
 }
 
 export function getRecordAuthor(record: RecordsWriteMessage | RecordsDeleteMessage): string | undefined {
-  return Records.getAuthor(record);
+  return Message.getAuthor(record);
 }
 
 export function isRecordsWrite(obj: unknown): obj is RecordsWrite {
diff --git a/packages/agent/tests/connect.spec.ts b/packages/agent/tests/connect.spec.ts
index 286f7234d..f6a1b87cb 100644
--- a/packages/agent/tests/connect.spec.ts
+++ b/packages/agent/tests/connect.spec.ts
@@ -827,10 +827,11 @@ describe('web5 connect', function () {
       });
 
       expect(permissionRequests.protocolDefinition).to.deep.equal(protocol);
-      expect(permissionRequests.permissionScopes.length).to.equal(3); // only includes the sync permissions
+      expect(permissionRequests.permissionScopes.length).to.equal(4); // only includes the sync permissions + protocol query permission
       expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Messages && scope.method === DwnMethodName.Read)).to.not.be.undefined;
       expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Messages && scope.method === DwnMethodName.Query)).to.not.be.undefined;
       expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Messages && scope.method === DwnMethodName.Subscribe)).to.not.be.undefined;
+      expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Protocols && scope.method === DwnMethodName.Query)).to.not.be.undefined;
     });
 
     it('should add requested permissions to the request', async () => {
@@ -854,13 +855,13 @@ describe('web5 connect', function () {
 
       expect(permissionRequests.protocolDefinition).to.deep.equal(protocol);
 
-      // the 3 sync permissions plus the 2 requested permissions
-      expect(permissionRequests.permissionScopes.length).to.equal(5);
+      // the 3 sync permissions plus the 2 requested permissions, and a protocol query permission
+      expect(permissionRequests.permissionScopes.length).to.equal(6);
       expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Records && scope.method === DwnMethodName.Read)).to.not.be.undefined;
       expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Records && scope.method === DwnMethodName.Write)).to.not.be.undefined;
     });
 
-    it('supports requesting `read`, `write`, `delete`, `query` and `subscribe` permissions', async () => {
+    it('supports requesting `read`, `write`, `delete`, `query`, `subscribe` and `configure` permissions', async () => {
       const protocol:DwnProtocolDefinition = {
         published : true,
         protocol  : 'https://exmaple.org/protocols/social',
@@ -876,18 +877,20 @@ describe('web5 connect', function () {
       };
 
       const permissionRequests = WalletConnect.createPermissionRequestForProtocol({
-        definition: protocol, permissions: ['write', 'read', 'delete', 'query', 'subscribe']
+        definition: protocol, permissions: ['write', 'read', 'delete', 'query', 'subscribe', 'configure']
       });
 
       expect(permissionRequests.protocolDefinition).to.deep.equal(protocol);
 
       // the 3 sync permissions plus the 5 requested permissions
-      expect(permissionRequests.permissionScopes.length).to.equal(8);
+      expect(permissionRequests.permissionScopes.length).to.equal(10);
       expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Records && scope.method === DwnMethodName.Read)).to.not.be.undefined;
       expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Records && scope.method === DwnMethodName.Write)).to.not.be.undefined;
       expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Records && scope.method === DwnMethodName.Delete)).to.not.be.undefined;
       expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Records && scope.method === DwnMethodName.Query)).to.not.be.undefined;
       expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Records && scope.method === DwnMethodName.Subscribe)).to.not.be.undefined;
+      expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Protocols && scope.method === DwnMethodName.Query)).to.not.be.undefined;
+      expect(permissionRequests.permissionScopes.find(scope => scope.interface === DwnInterfaceName.Protocols && scope.method === DwnMethodName.Configure)).to.not.be.undefined;
     });
   });
 });
diff --git a/packages/api/package.json b/packages/api/package.json
index 22e311d31..a5da39837 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -85,7 +85,7 @@
   },
   "devDependencies": {
     "@playwright/test": "1.45.3",
-    "@tbd54566975/dwn-sdk-js": "0.4.6",
+    "@tbd54566975/dwn-sdk-js": "0.4.7",
     "@types/chai": "4.3.6",
     "@types/eslint": "8.56.10",
     "@types/mocha": "10.0.1",
diff --git a/packages/api/src/dwn-api.ts b/packages/api/src/dwn-api.ts
index b7405958a..464b1bb00 100644
--- a/packages/api/src/dwn-api.ts
+++ b/packages/api/src/dwn-api.ts
@@ -7,7 +7,6 @@
 import type {
   CreateGrantParams,
   CreateRequestParams,
-  DwnRecordsInterfaces,
   FetchPermissionRequestParams,
   FetchPermissionsParams
 } from '@web5/agent';
@@ -428,12 +427,32 @@ export class DwnApi {
        * Configure method, used to setup a new protocol (or update) with the passed definitions
        */
       configure: async (request: ProtocolsConfigureRequest): Promise<ProtocolsConfigureResponse> => {
-        const agentResponse = await this.agent.processDwnRequest({
+
+        const agentRequest:ProcessDwnRequest<DwnInterface.ProtocolsConfigure> = {
           author        : this.connectedDid,
           messageParams : request.message,
           messageType   : DwnInterface.ProtocolsConfigure,
           target        : this.connectedDid
-        });
+        };
+
+        if (this.delegateDid) {
+          const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
+            connectedDid : this.connectedDid,
+            delegateDid  : this.delegateDid,
+            protocol     : request.message.definition.protocol,
+            delegate     : true,
+            cached       : true,
+            messageType  : agentRequest.messageType
+          });
+
+          agentRequest.messageParams = {
+            ...agentRequest.messageParams,
+            delegatedGrant
+          };
+          agentRequest.granteeDid = this.delegateDid;
+        }
+
+        const agentResponse = await this.agent.processDwnRequest(agentRequest);
 
         const { message, messageCid, reply: { status }} = agentResponse;
         const response: ProtocolsConfigureResponse = { status };
@@ -457,6 +476,30 @@ export class DwnApi {
           target        : request.from || this.connectedDid
         };
 
+        if (this.delegateDid) {
+          // We attempt to get a grant within a try catch, if there is no grant we will still sign the query with the delegate DID's key
+          // If the protocol is public, the query should be successful. This allows the app to query for public protocols without having a grant.
+
+          try {
+            const { grant: { id: permissionGrantId } } = await this.permissionsApi.getPermissionForRequest({
+              connectedDid : this.connectedDid,
+              delegateDid  : this.delegateDid,
+              protocol     : request.message.filter.protocol,
+              cached       : true,
+              messageType  : agentRequest.messageType
+            });
+
+            agentRequest.messageParams = {
+              ...agentRequest.messageParams,
+              permissionGrantId
+            };
+            agentRequest.granteeDid = this.delegateDid;
+          } catch(_error:any) {
+            // if a grant is not found, we should author the request as the delegated DID to get public protocols
+            agentRequest.author = this.delegateDid;
+          }
+        }
+
         let agentResponse: DwnResponse<DwnInterface.ProtocolsQuery>;
 
         if (request.from) {
@@ -616,8 +659,8 @@ export class DwnApi {
               delegatedGrant
             };
             agentRequest.granteeDid = this.delegateDid;
-          } catch(error:any) {
-            // set the author of the request to the delegate did
+          } catch(_error:any) {
+            // if a grant is not found, we should author the request as the delegated DID to get public records
             agentRequest.author = this.delegateDid;
           }
         }
@@ -708,7 +751,7 @@ export class DwnApi {
             };
             agentRequest.granteeDid = this.delegateDid;
           } catch(_error:any) {
-            // set the author of the request to the delegate did
+            // if a grant is not found, we should author the request as the delegated DID to get public records
             agentRequest.author = this.delegateDid;
           }
         }
@@ -811,7 +854,7 @@ export class DwnApi {
             };
             agentRequest.granteeDid = this.delegateDid;
           } catch(_error:any) {
-            // set the author of the request to the delegate did
+            // if a grant is not found, we should author the request as the delegated DID to get public records
             agentRequest.author = this.delegateDid;
           }
         };
diff --git a/packages/api/src/web5.ts b/packages/api/src/web5.ts
index de33ad20a..bf07f732d 100644
--- a/packages/api/src/web5.ts
+++ b/packages/api/src/web5.ts
@@ -17,7 +17,7 @@ import type {
 } from '@web5/agent';
 
 import { Web5UserAgent } from '@web5/user-agent';
-import { DwnRegistrar, WalletConnect } from '@web5/agent';
+import { DwnInterface, DwnRegistrar, WalletConnect } from '@web5/agent';
 
 import { DidApi } from './did-api.js';
 import { DwnApi } from './dwn-api.js';
diff --git a/packages/api/tests/dwn-api.spec.ts b/packages/api/tests/dwn-api.spec.ts
index 16cdc0613..ca3081664 100644
--- a/packages/api/tests/dwn-api.spec.ts
+++ b/packages/api/tests/dwn-api.spec.ts
@@ -169,511 +169,643 @@ describe('DwnApi', () => {
       } as any }));
     });
 
-    it('should create a record with a delegated grant', async () => {
-      const { status, record } = await delegateDwn.records.create({
-        data    : 'Hello, world!',
-        message : {
-          protocol     : notesProtocol.protocol,
-          protocolPath : 'note',
-          schema       : notesProtocol.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
+    describe('records', () => {
+      it('should create a record with a delegated grant', async () => {
+        const { status, record } = await delegateDwn.records.create({
+          data    : 'Hello, world!',
+          message : {
+            protocol     : notesProtocol.protocol,
+            protocolPath : 'note',
+            schema       : notesProtocol.types.note.schema,
+            dataFormat   : 'text/plain',
+          }
+        });
+
+        expect(status.code).to.equal(202);
+        expect(record).to.not.be.undefined;
+
+        // alice is the author, but the signer is the delegateDid
+        expect(record.author).to.equal(aliceDid.uri);
+        const signerDid = Jws.getSignerDid(record.rawMessage.authorization.signature.signatures[0]);
+        expect(signerDid).to.equal(delegateDid.uri);
+        expect(record.rawMessage.authorization.authorDelegatedGrant).to.not.be.undefined;
       });
 
-      expect(status.code).to.equal(202);
-      expect(record).to.not.be.undefined;
+      it('should read records with a delegated grant', async () => {
+        const { status: writeStatus, record } = await dwnAlice.records.create({
+          data    : 'Hello, world!',
+          message : {
+            protocol     : notesProtocol.protocol,
+            protocolPath : 'note',
+            schema       : notesProtocol.types.note.schema,
+            dataFormat   : 'text/plain',
+          }
+        });
 
-      // alice is the author, but the signer is the delegateDid
-      expect(record.author).to.equal(aliceDid.uri);
-      const signerDid = Jws.getSignerDid(record.rawMessage.authorization.signature.signatures[0]);
-      expect(signerDid).to.equal(delegateDid.uri);
-      expect(record.rawMessage.authorization.authorDelegatedGrant).to.not.be.undefined;
-    });
+        expect(writeStatus.code).to.equal(202);
+        expect(record).to.not.be.undefined;
+        const { status: sendStatus } = await record.send();
+        expect(sendStatus.code).to.equal(202);
 
-    it('should read records with a delegated grant', async () => {
-      const { status: writeStatus, record } = await dwnAlice.records.create({
-        data    : 'Hello, world!',
-        message : {
-          protocol     : notesProtocol.protocol,
-          protocolPath : 'note',
-          schema       : notesProtocol.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
+        const { status: readStatus, record: readRecord } = await delegateDwn.records.read({
+          from     : aliceDid.uri,
+          protocol : notesProtocol.protocol,
+          message  : {
+            filter: {
+              recordId: record.id
+            }
+          }
+        });
+
+        expect(readStatus.code).to.equal(200);
+        expect(readRecord).to.exist;
+        expect(readRecord.id).to.equal;
       });
 
-      expect(writeStatus.code).to.equal(202);
-      expect(record).to.not.be.undefined;
-      const { status: sendStatus } = await record.send();
-      expect(sendStatus.code).to.equal(202);
+      it('should query records with a delegated grant', async () => {
+        const { status: writeStatus, record } = await delegateDwn.records.create({
+          data    : 'Hello, world!',
+          message : {
+            protocol     : notesProtocol.protocol,
+            protocolPath : 'note',
+            schema       : notesProtocol.types.note.schema,
+            dataFormat   : 'text/plain',
+          }
+        });
+
+        expect(writeStatus.code).to.equal(202);
+        expect(record).to.not.be.undefined;
 
-      const { status: readStatus, record: readRecord } = await delegateDwn.records.read({
-        from     : aliceDid.uri,
-        protocol : notesProtocol.protocol,
-        message  : {
-          filter: {
-            recordId: record.id
+        const { status: queryStatus, records } = await delegateDwn.records.query({
+          protocol : notesProtocol.protocol,
+          message  : {
+            filter: {
+              protocol     : notesProtocol.protocol,
+              protocolPath : 'note'
+            }
           }
-        }
-      });
+        });
 
-      expect(readStatus.code).to.equal(200);
-      expect(readRecord).to.exist;
-      expect(readRecord.id).to.equal;
-    });
+        expect(queryStatus.code).to.equal(200);
+        expect(records).to.exist;
+        expect(records).to.have.lengthOf(1);
 
-    it('should query records with a delegated grant', async () => {
-      const { status: writeStatus, record } = await delegateDwn.records.create({
-        data    : 'Hello, world!',
-        message : {
-          protocol     : notesProtocol.protocol,
-          protocolPath : 'note',
-          schema       : notesProtocol.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
+        // alice is the author, but the signer is the delegateDid
+        expect(records![0].author).to.equal(aliceDid.uri);
+        const signerDid = Jws.getSignerDid(records![0].rawMessage.authorization.signature.signatures[0]);
+        expect(signerDid).to.equal(delegateDid.uri);
+        expect(records![0].rawMessage.authorization.authorDelegatedGrant).to.not.be.undefined;
+
+        // the record should be the same
+        expect(records![0].id).to.equal(record!.id);
       });
 
-      expect(writeStatus.code).to.equal(202);
-      expect(record).to.not.be.undefined;
+      it('should subscribe to records with a delegated grant', async () => {
+        // subscribe to all messages from the protocol
+        const records: Map<string, Record> = new Map();
+        const subscriptionHandler = async (record: Record) => {
+          records.set(record.id, record);
+        };
 
-      const { status: queryStatus, records } = await delegateDwn.records.query({
-        protocol : notesProtocol.protocol,
-        message  : {
-          filter: {
+        const subscribeResult = await delegateDwn.records.subscribe({
+          protocol : notesProtocol.protocol,
+          message  : {
+            filter: {
+              protocol: notesProtocol.protocol
+            }
+          },
+          subscriptionHandler
+        });
+        expect(subscribeResult.status.code).to.equal(200);
+
+        // write a record
+        const writeResult = await delegateDwn.records.write({
+          data    : 'Hello, world!',
+          message : {
+            recipient    : bobDid.uri,
             protocol     : notesProtocol.protocol,
-            protocolPath : 'note'
+            protocolPath : 'note',
+            schema       : notesProtocol.types.note.schema,
+            dataFormat   : 'text/plain',
           }
-        }
-      });
-
-      expect(queryStatus.code).to.equal(200);
-      expect(records).to.exist;
-      expect(records).to.have.lengthOf(1);
+        });
+        expect(writeResult.status.code).to.equal(202);
 
-      // alice is the author, but the signer is the delegateDid
-      expect(records![0].author).to.equal(aliceDid.uri);
-      const signerDid = Jws.getSignerDid(records![0].rawMessage.authorization.signature.signatures[0]);
-      expect(signerDid).to.equal(delegateDid.uri);
-      expect(records![0].rawMessage.authorization.authorDelegatedGrant).to.not.be.undefined;
+        // wait for the record to be received
+        await Poller.pollUntilSuccessOrTimeout(async () => {
+          expect(records.size).to.equal(1);
+          const record = records.get(writeResult.record.id);
+          expect(record.toJSON()).to.deep.equal(writeResult.record.toJSON());
+          expect(record.deleted).to.be.false;
+        });
 
-      // the record should be the same
-      expect(records![0].id).to.equal(record!.id);
-    });
+        // delete the record using the original writeResult instance of it
+        const deleteResult = await writeResult.record.delete();
+        expect(deleteResult.status.code).to.equal(202);
 
-    it('should subscribe to records with a delegated grant', async () => {
-      // subscribe to all messages from the protocol
-      const records: Map<string, Record> = new Map();
-      const subscriptionHandler = async (record: Record) => {
-        records.set(record.id, record);
-      };
+        // wait for the record state to be reflected as deleted
+        await Poller.pollUntilSuccessOrTimeout(async () => {
+          const record = records.get(writeResult.record.id);
+          expect(record).to.exist;
+          expect(record.deleted).to.be.true;
+        });
 
-      const subscribeResult = await delegateDwn.records.subscribe({
-        protocol : notesProtocol.protocol,
-        message  : {
-          filter: {
-            protocol: notesProtocol.protocol
+        // write another record and delete the previous one, the state should be updated
+        const writeResult2 = await delegateDwn.records.write({
+          data    : 'Hello, world!',
+          message : {
+            recipient    : bobDid.uri,
+            protocol     : notesProtocol.protocol,
+            protocolPath : 'note',
+            schema       : notesProtocol.types.note.schema,
+            dataFormat   : 'text/plain',
           }
-        },
-        subscriptionHandler
-      });
-      expect(subscribeResult.status.code).to.equal(200);
-
-      // write a record
-      const writeResult = await delegateDwn.records.write({
-        data    : 'Hello, world!',
-        message : {
-          recipient    : bobDid.uri,
-          protocol     : notesProtocol.protocol,
-          protocolPath : 'note',
-          schema       : notesProtocol.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
-      });
-      expect(writeResult.status.code).to.equal(202);
+        });
+        expect(writeResult2.status.code).to.equal(202);
 
-      // wait for the record to be received
-      await Poller.pollUntilSuccessOrTimeout(async () => {
-        expect(records.size).to.equal(1);
-        const record = records.get(writeResult.record.id);
-        expect(record.toJSON()).to.deep.equal(writeResult.record.toJSON());
-        expect(record.deleted).to.be.false;
+        // wait for the record to be received
+        await Poller.pollUntilSuccessOrTimeout(async () => {
+          expect(records.size).to.equal(2);
+          const record = records.get(writeResult2.record.id);
+          expect(record.toJSON()).to.deep.equal(writeResult2.record.toJSON());
+          expect(record.deleted).to.be.false;
+
+          //check the deleted record
+          const deletedRecord = records.get(writeResult.record.id);
+          expect(deletedRecord).to.exist;
+          expect(deletedRecord.deleted).to.be.true;
+        });
       });
 
-      // delete the record using the original writeResult instance of it
-      const deleteResult = await writeResult.record.delete();
-      expect(deleteResult.status.code).to.equal(202);
+      it('should read records as the delegate DID if no grant is found', async () => {
+        // alice installs some other protocol
+        const { status: aliceConfigStatus, protocol: aliceOtherProtocol } = await dwnAlice.protocols.configure({ message: { definition: {
+          ...notesProtocol,
+          protocol: `http://other-protocol.xyz/protocol/${TestDataGenerator.randomString(15)}`
+        }} });
+        expect(aliceConfigStatus.code).to.equal(202);
+        const { status: aliceOtherProtocolSend } = await aliceOtherProtocol.send(aliceDid.uri);
+        expect(aliceOtherProtocolSend.code).to.equal(202);
 
-      // wait for the record state to be reflected as deleted
-      await Poller.pollUntilSuccessOrTimeout(async () => {
-        const record = records.get(writeResult.record.id);
-        expect(record).to.exist;
-        expect(record.deleted).to.be.true;
-      });
+        // alice writes a note record to the permissioned protocol
+        const { status: writeStatus1, record: allowedRecord } = await dwnAlice.records.create({
+          data    : 'Hello, world!',
+          message : {
+            protocol     : notesProtocol.protocol,
+            protocolPath : 'note',
+            schema       : notesProtocol.types.note.schema,
+            dataFormat   : 'text/plain',
+          }
+        });
+        expect(writeStatus1.code).to.equal(202);
+        expect(allowedRecord).to.not.be.undefined;
+        const { status: allowedRecordSendStatus } = await allowedRecord.send();
+        expect(allowedRecordSendStatus.code).to.equal(202);
 
-      // write another record and delete the previous one, the state should be updated
-      const writeResult2 = await delegateDwn.records.write({
-        data    : 'Hello, world!',
-        message : {
-          recipient    : bobDid.uri,
-          protocol     : notesProtocol.protocol,
-          protocolPath : 'note',
-          schema       : notesProtocol.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
-      });
-      expect(writeResult2.status.code).to.equal(202);
+        // alice writes a public and private note to the other protocol
+        const { status: writeStatus2, record: publicRecord } = await dwnAlice.records.create({
+          data    : 'Hello, world!',
+          message : {
+            published    : true,
+            protocol     : aliceOtherProtocol.definition.protocol,
+            protocolPath : 'note',
+            schema       : aliceOtherProtocol.definition.types.note.schema,
+            dataFormat   : 'text/plain',
+          }
+        });
+        expect(writeStatus2.code).to.equal(202);
+        expect(publicRecord).to.not.be.undefined;
+        const { status: publicRecordSendStatus } = await publicRecord.send();
+        expect(publicRecordSendStatus.code).to.equal(202);
 
-      // wait for the record to be received
-      await Poller.pollUntilSuccessOrTimeout(async () => {
-        expect(records.size).to.equal(2);
-        const record = records.get(writeResult2.record.id);
-        expect(record.toJSON()).to.deep.equal(writeResult2.record.toJSON());
-        expect(record.deleted).to.be.false;
+        const { status: writeStatus3, record: privateRecord } = await dwnAlice.records.create({
+          data    : 'Hello, world!',
+          message : {
+            protocol     : aliceOtherProtocol.definition.protocol,
+            protocolPath : 'note',
+            schema       : aliceOtherProtocol.definition.types.note.schema,
+            dataFormat   : 'text/plain',
+          }
+        });
+        expect(writeStatus3.code).to.equal(202);
+        expect(privateRecord).to.not.be.undefined;
+        const { status: privateRecordSendStatus } = await privateRecord.send();
+        expect(privateRecordSendStatus.code).to.equal(202);
 
-        //check the deleted record
-        const deletedRecord = records.get(writeResult.record.id);
-        expect(deletedRecord).to.exist;
-        expect(deletedRecord.deleted).to.be.true;
-      });
-    });
 
-    it('should read records as the delegate DID if no grant is found', async () => {
-      // alice installs some other protocol
-      const { status: aliceConfigStatus, protocol: aliceOtherProtocol } = await dwnAlice.protocols.configure({ message: { definition: {
-        ...notesProtocol,
-        protocol: `http://other-protocol.xyz/protocol/${TestDataGenerator.randomString(15)}`
-      }} });
-      expect(aliceConfigStatus.code).to.equal(202);
-      const { status: aliceOtherProtocolSend } = await aliceOtherProtocol.send(aliceDid.uri);
-      expect(aliceOtherProtocolSend.code).to.equal(202);
-
-      // alice writes a note record to the permissioned protocol
-      const { status: writeStatus1, record: allowedRecord } = await dwnAlice.records.create({
-        data    : 'Hello, world!',
-        message : {
-          protocol     : notesProtocol.protocol,
-          protocolPath : 'note',
-          schema       : notesProtocol.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
-      });
-      expect(writeStatus1.code).to.equal(202);
-      expect(allowedRecord).to.not.be.undefined;
-      const { status: allowedRecordSendStatus } = await allowedRecord.send();
-      expect(allowedRecordSendStatus.code).to.equal(202);
-
-      // alice writes a public and private note to the other protocol
-      const { status: writeStatus2, record: publicRecord } = await dwnAlice.records.create({
-        data    : 'Hello, world!',
-        message : {
-          published    : true,
-          protocol     : aliceOtherProtocol.definition.protocol,
-          protocolPath : 'note',
-          schema       : aliceOtherProtocol.definition.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
-      });
-      expect(writeStatus2.code).to.equal(202);
-      expect(publicRecord).to.not.be.undefined;
-      const { status: publicRecordSendStatus } = await publicRecord.send();
-      expect(publicRecordSendStatus.code).to.equal(202);
-
-      const { status: writeStatus3, record: privateRecord } = await dwnAlice.records.create({
-        data    : 'Hello, world!',
-        message : {
-          protocol     : aliceOtherProtocol.definition.protocol,
-          protocolPath : 'note',
-          schema       : aliceOtherProtocol.definition.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
-      });
-      expect(writeStatus3.code).to.equal(202);
-      expect(privateRecord).to.not.be.undefined;
-      const { status: privateRecordSendStatus } = await privateRecord.send();
-      expect(privateRecordSendStatus.code).to.equal(202);
+        // sanity: delegateDwn reads from the allowed record from alice's DWN
+        const { status: readStatus1, record: allowedRecordReturned } = await delegateDwn.records.read({
+          from     : aliceDid.uri,
+          protocol : notesProtocol.protocol,
+          message  : {
+            filter: {
+              recordId: allowedRecord.id
+            }
+          }
+        });
+        expect(readStatus1.code).to.equal(200);
+        expect(allowedRecordReturned).to.exist;
+        expect(allowedRecordReturned.id).to.equal(allowedRecord.id);
 
+        // delegateDwn reads from the other protocol, which no permissions exist
+        // only the public record is successfully returned
+        const { status: readStatus2, record: publicRecordReturned } = await delegateDwn.records.read({
+          from     : aliceDid.uri,
+          protocol : aliceOtherProtocol.definition.protocol,
+          message  : {
+            filter: {
+              recordId: publicRecord.id
+            }
+          }
+        });
+        expect(readStatus2.code).to.equal(200);
+        expect(publicRecordReturned).to.exist;
+        expect(publicRecordReturned.id).to.equal(publicRecord.id);
 
-      // sanity: delegateDwn reads from the allowed record from alice's DWN
-      const { status: readStatus1, record: allowedRecordReturned } = await delegateDwn.records.read({
-        from     : aliceDid.uri,
-        protocol : notesProtocol.protocol,
-        message  : {
-          filter: {
-            recordId: allowedRecord.id
+        // attempt to read the private record, which should fail
+        const { status: readStatus3, record: privateRecordReturned } = await delegateDwn.records.read({
+          from     : aliceDid.uri,
+          protocol : aliceOtherProtocol.definition.protocol,
+          message  : {
+            filter: {
+              recordId: privateRecord.id
+            }
           }
-        }
-      });
-      expect(readStatus1.code).to.equal(200);
-      expect(allowedRecordReturned).to.exist;
-      expect(allowedRecordReturned.id).to.equal(allowedRecord.id);
+        });
+        expect(readStatus3.code).to.equal(401);
+        expect(privateRecordReturned).to.be.undefined;
 
-      // delegateDwn reads from the other protocol, which no permissions exist
-      // only the public record is successfully returned
-      const { status: readStatus2, record: publicRecordReturned } = await delegateDwn.records.read({
-        from     : aliceDid.uri,
-        protocol : aliceOtherProtocol.definition.protocol,
-        message  : {
-          filter: {
-            recordId: publicRecord.id
+        // sanity: query as alice to get both records
+        const { status: readStatus4, record: privateRecordReturnedAlice } = await dwnAlice.records.read({
+          from     : aliceDid.uri,
+          protocol : aliceOtherProtocol.definition.protocol,
+          message  : {
+            filter: {
+              recordId: privateRecord.id
+            }
           }
-        }
+        });
+        expect(readStatus4.code).to.equal(200);
+        expect(privateRecordReturnedAlice).to.exist;
+        expect(privateRecordReturnedAlice.id).to.equal(privateRecord.id);
       });
-      expect(readStatus2.code).to.equal(200);
-      expect(publicRecordReturned).to.exist;
-      expect(publicRecordReturned.id).to.equal(publicRecord.id);
 
-      // attempt to read the private record, which should fail
-      const { status: readStatus3, record: privateRecordReturned } = await delegateDwn.records.read({
-        from     : aliceDid.uri,
-        protocol : aliceOtherProtocol.definition.protocol,
-        message  : {
-          filter: {
-            recordId: privateRecord.id
+      it('should query records as the delegate DID if no grant is found', async () => {
+        // alice installs some other protocol
+        const { status: aliceConfigStatus, protocol: aliceOtherProtocol } = await dwnAlice.protocols.configure({ message: { definition: {
+          ...notesProtocol,
+          protocol: `http://other-protocol.xyz/protocol/${TestDataGenerator.randomString(15)}`
+        }} });
+        expect(aliceConfigStatus.code).to.equal(202);
+        const { status: aliceOtherProtocolSend } = await aliceOtherProtocol.send(aliceDid.uri);
+        expect(aliceOtherProtocolSend.code).to.equal(202);
+
+        // alice writes a note record to the permissioned protocol
+        const { status: writeStatus1, record: allowedRecord } = await dwnAlice.records.create({
+          data    : 'Hello, world!',
+          message : {
+            protocol     : notesProtocol.protocol,
+            protocolPath : 'note',
+            schema       : notesProtocol.types.note.schema,
+            dataFormat   : 'text/plain',
           }
-        }
-      });
-      expect(readStatus3.code).to.equal(401);
-      expect(privateRecordReturned).to.be.undefined;
+        });
+        expect(writeStatus1.code).to.equal(202);
+        expect(allowedRecord).to.not.be.undefined;
+        const { status: allowedRecordSendStatus } = await allowedRecord.send();
+        expect(allowedRecordSendStatus.code).to.equal(202);
 
-      // sanity: query as alice to get both records
-      const { status: readStatus4, record: privateRecordReturnedAlice } = await dwnAlice.records.read({
-        from     : aliceDid.uri,
-        protocol : aliceOtherProtocol.definition.protocol,
-        message  : {
-          filter: {
-            recordId: privateRecord.id
+        // alice writes a public and private note to the other protocol
+        const { status: writeStatus2, record: publicRecord } = await dwnAlice.records.create({
+          data    : 'Hello, world!',
+          message : {
+            published    : true,
+            protocol     : aliceOtherProtocol.definition.protocol,
+            protocolPath : 'note',
+            schema       : aliceOtherProtocol.definition.types.note.schema,
+            dataFormat   : 'text/plain',
           }
-        }
-      });
-      expect(readStatus4.code).to.equal(200);
-      expect(privateRecordReturnedAlice).to.exist;
-      expect(privateRecordReturnedAlice.id).to.equal(privateRecord.id);
-    });
+        });
+        expect(writeStatus2.code).to.equal(202);
+        expect(publicRecord).to.not.be.undefined;
+        const { status: publicRecordSendStatus } = await publicRecord.send();
+        expect(publicRecordSendStatus.code).to.equal(202);
 
-    it('should query records as the delegate DID if no grant is found', async () => {
-      // alice installs some other protocol
-      const { status: aliceConfigStatus, protocol: aliceOtherProtocol } = await dwnAlice.protocols.configure({ message: { definition: {
-        ...notesProtocol,
-        protocol: `http://other-protocol.xyz/protocol/${TestDataGenerator.randomString(15)}`
-      }} });
-      expect(aliceConfigStatus.code).to.equal(202);
-      const { status: aliceOtherProtocolSend } = await aliceOtherProtocol.send(aliceDid.uri);
-      expect(aliceOtherProtocolSend.code).to.equal(202);
-
-      // alice writes a note record to the permissioned protocol
-      const { status: writeStatus1, record: allowedRecord } = await dwnAlice.records.create({
-        data    : 'Hello, world!',
-        message : {
-          protocol     : notesProtocol.protocol,
-          protocolPath : 'note',
-          schema       : notesProtocol.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
-      });
-      expect(writeStatus1.code).to.equal(202);
-      expect(allowedRecord).to.not.be.undefined;
-      const { status: allowedRecordSendStatus } = await allowedRecord.send();
-      expect(allowedRecordSendStatus.code).to.equal(202);
-
-      // alice writes a public and private note to the other protocol
-      const { status: writeStatus2, record: publicRecord } = await dwnAlice.records.create({
-        data    : 'Hello, world!',
-        message : {
-          published    : true,
-          protocol     : aliceOtherProtocol.definition.protocol,
-          protocolPath : 'note',
-          schema       : aliceOtherProtocol.definition.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
-      });
-      expect(writeStatus2.code).to.equal(202);
-      expect(publicRecord).to.not.be.undefined;
-      const { status: publicRecordSendStatus } = await publicRecord.send();
-      expect(publicRecordSendStatus.code).to.equal(202);
-
-      const { status: writeStatus3, record: privateRecord } = await dwnAlice.records.create({
-        data    : 'Hello, world!',
-        message : {
-          protocol     : aliceOtherProtocol.definition.protocol,
-          protocolPath : 'note',
-          schema       : aliceOtherProtocol.definition.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
-      });
-      expect(writeStatus3.code).to.equal(202);
-      expect(privateRecord).to.not.be.undefined;
-      const { status: privateRecordSendStatus } = await privateRecord.send();
-      expect(privateRecordSendStatus.code).to.equal(202);
+        const { status: writeStatus3, record: privateRecord } = await dwnAlice.records.create({
+          data    : 'Hello, world!',
+          message : {
+            protocol     : aliceOtherProtocol.definition.protocol,
+            protocolPath : 'note',
+            schema       : aliceOtherProtocol.definition.types.note.schema,
+            dataFormat   : 'text/plain',
+          }
+        });
+        expect(writeStatus3.code).to.equal(202);
+        expect(privateRecord).to.not.be.undefined;
+        const { status: privateRecordSendStatus } = await privateRecord.send();
+        expect(privateRecordSendStatus.code).to.equal(202);
 
 
-      // sanity: delegateDwn queries for the allowed record from alice's DWN
-      const { status: queryStatus1, records: allowedRecords } = await delegateDwn.records.query({
-        from     : aliceDid.uri,
-        protocol : notesProtocol.protocol,
-        message  : {
-          filter: {
-            protocol: notesProtocol.protocol
+        // sanity: delegateDwn queries for the allowed record from alice's DWN
+        const { status: queryStatus1, records: allowedRecords } = await delegateDwn.records.query({
+          from     : aliceDid.uri,
+          protocol : notesProtocol.protocol,
+          message  : {
+            filter: {
+              protocol: notesProtocol.protocol
+            }
           }
-        }
-      });
-      expect(queryStatus1.code).to.equal(200);
-      expect(allowedRecords).to.exist;
-      expect(allowedRecords).to.have.lengthOf(1);
+        });
+        expect(queryStatus1.code).to.equal(200);
+        expect(allowedRecords).to.exist;
+        expect(allowedRecords).to.have.lengthOf(1);
 
-      // delegateDwn queries for the other protocol, which no permissions exist
-      // only the public record is returned
-      const { status: queryStatus2, records: publicRecords } = await delegateDwn.records.query({
-        from     : aliceDid.uri,
-        protocol : aliceOtherProtocol.definition.protocol,
-        message  : {
-          filter: {
-            protocol: aliceOtherProtocol.definition.protocol
+        // delegateDwn queries for the other protocol, which no permissions exist
+        // only the public record is returned
+        const { status: queryStatus2, records: publicRecords } = await delegateDwn.records.query({
+          from     : aliceDid.uri,
+          protocol : aliceOtherProtocol.definition.protocol,
+          message  : {
+            filter: {
+              protocol: aliceOtherProtocol.definition.protocol
+            }
           }
-        }
-      });
-      expect(queryStatus2.code).to.equal(200);
-      expect(publicRecords).to.exist;
-      expect(publicRecords).to.have.lengthOf(1);
-      expect(publicRecords![0].id).to.equal(publicRecord.id);
+        });
+        expect(queryStatus2.code).to.equal(200);
+        expect(publicRecords).to.exist;
+        expect(publicRecords).to.have.lengthOf(1);
+        expect(publicRecords![0].id).to.equal(publicRecord.id);
 
-      // sanity: query as alice to get both records
-      const { status: queryStatus3, records: allRecords } = await dwnAlice.records.query({
-        from     : aliceDid.uri,
-        protocol : aliceOtherProtocol.definition.protocol,
-        message  : {
-          filter: {
-            protocol: aliceOtherProtocol.definition.protocol
+        // sanity: query as alice to get both records
+        const { status: queryStatus3, records: allRecords } = await dwnAlice.records.query({
+          from     : aliceDid.uri,
+          protocol : aliceOtherProtocol.definition.protocol,
+          message  : {
+            filter: {
+              protocol: aliceOtherProtocol.definition.protocol
+            }
           }
-        }
+        });
+        expect(queryStatus3.code).to.equal(200);
+        expect(allRecords).to.exist;
+        expect(allRecords).to.have.lengthOf(2);
+        expect(allRecords.map(r => r.id)).to.have.members([publicRecord.id, privateRecord.id]);
       });
-      expect(queryStatus3.code).to.equal(200);
-      expect(allRecords).to.exist;
-      expect(allRecords).to.have.lengthOf(2);
-      expect(allRecords.map(r => r.id)).to.have.members([publicRecord.id, privateRecord.id]);
-    });
 
-    it('should subscribe to records as the delegate DID if no grant is found', async () => {
-      // alice installs some other protocol
-      const { status: aliceConfigStatus, protocol: aliceOtherProtocol } = await dwnAlice.protocols.configure({ message: { definition: {
-        ...notesProtocol,
-        protocol: `http://other-protocol.xyz/protocol/${TestDataGenerator.randomString(15)}`
-      }} });
-      expect(aliceConfigStatus.code).to.equal(202);
-      const { status: aliceOtherProtocolSend } = await aliceOtherProtocol.send(aliceDid.uri);
-      expect(aliceOtherProtocolSend.code).to.equal(202);
+      it('should subscribe to records as the delegate DID if no grant is found', async () => {
+        // alice installs some other protocol
+        const { status: aliceConfigStatus, protocol: aliceOtherProtocol } = await dwnAlice.protocols.configure({ message: { definition: {
+          ...notesProtocol,
+          protocol: `http://other-protocol.xyz/protocol/${TestDataGenerator.randomString(15)}`
+        }} });
+        expect(aliceConfigStatus.code).to.equal(202);
+        const { status: aliceOtherProtocolSend } = await aliceOtherProtocol.send(aliceDid.uri);
+        expect(aliceOtherProtocolSend.code).to.equal(202);
 
-      // delegatedDwn subscribes to both protocols
-      const permissionedNotesRecords: Map<string, Record> = new Map();
-      const permissionedNotesSubscriptionHandler = async (record: Record) => {
-        permissionedNotesRecords.set(record.id, record);
-      };
-      const permissionedNotesSubscribeResult = await delegateDwn.records.subscribe({
-        from     : aliceDid.uri,
-        protocol : notesProtocol.protocol,
-        message  : {
-          filter: {
-            protocol: notesProtocol.protocol
+        // delegatedDwn subscribes to both protocols
+        const permissionedNotesRecords: Map<string, Record> = new Map();
+        const permissionedNotesSubscriptionHandler = async (record: Record) => {
+          permissionedNotesRecords.set(record.id, record);
+        };
+        const permissionedNotesSubscribeResult = await delegateDwn.records.subscribe({
+          from     : aliceDid.uri,
+          protocol : notesProtocol.protocol,
+          message  : {
+            filter: {
+              protocol: notesProtocol.protocol
+            }
+          },
+          subscriptionHandler: permissionedNotesSubscriptionHandler
+        });
+        expect(permissionedNotesSubscribeResult.status.code).to.equal(200);
+
+        const otherProtocolRecords: Map<string, Record> = new Map();
+        const otherProtocolSubscriptionHandler = async (record: Record) => {
+          otherProtocolRecords.set(record.id, record);
+        };
+        const otherProtocolSubscribeResult = await delegateDwn.records.subscribe({
+          from     : aliceDid.uri,
+          protocol : aliceOtherProtocol.definition.protocol,
+          message  : {
+            filter: {
+              protocol: aliceOtherProtocol.definition.protocol
+            }
+          },
+          subscriptionHandler: otherProtocolSubscriptionHandler
+        });
+        expect(otherProtocolSubscribeResult.status.code).to.equal(200);
+
+        // alice subscribes to the other protocol as a sanity
+        const aliceOtherProtocolRecords: Map<string, Record> = new Map();
+        const aliceOtherProtocolSubscriptionHandler = async (record: Record) => {
+          aliceOtherProtocolRecords.set(record.id, record);
+        };
+        const aliceOtherProtocolSubscribeResult = await dwnAlice.records.subscribe({
+          from     : aliceDid.uri,
+          protocol : aliceOtherProtocol.definition.protocol,
+          message  : {
+            filter: {
+              protocol: aliceOtherProtocol.definition.protocol
+            }
+          },
+          subscriptionHandler: aliceOtherProtocolSubscriptionHandler
+        });
+        expect(aliceOtherProtocolSubscribeResult.status.code).to.equal(200);
+
+        // NOTE: write the private record before the public so that it should be received first
+        // alice writes a public and private note to the other protocol
+        const { status: writeStatus2, record: publicRecord } = await dwnAlice.records.create({
+          data    : 'Hello, world!',
+          message : {
+            published    : true,
+            protocol     : aliceOtherProtocol.definition.protocol,
+            protocolPath : 'note',
+            schema       : aliceOtherProtocol.definition.types.note.schema,
+            dataFormat   : 'text/plain',
           }
-        },
-        subscriptionHandler: permissionedNotesSubscriptionHandler
-      });
-      expect(permissionedNotesSubscribeResult.status.code).to.equal(200);
+        });
+        expect(writeStatus2.code).to.equal(202);
+        expect(publicRecord).to.not.be.undefined;
+        const { status: publicRecordSendStatus } = await publicRecord.send();
+        expect(publicRecordSendStatus.code).to.equal(202);
 
-      const otherProtocolRecords: Map<string, Record> = new Map();
-      const otherProtocolSubscriptionHandler = async (record: Record) => {
-        otherProtocolRecords.set(record.id, record);
-      };
-      const otherProtocolSubscribeResult = await delegateDwn.records.subscribe({
-        from     : aliceDid.uri,
-        protocol : aliceOtherProtocol.definition.protocol,
-        message  : {
-          filter: {
-            protocol: aliceOtherProtocol.definition.protocol
+        // alice writes a note record to the permissioned protocol
+        const { status: writeStatus1, record: allowedRecord } = await dwnAlice.records.create({
+          data    : 'Hello, world!',
+          message : {
+            protocol     : notesProtocol.protocol,
+            protocolPath : 'note',
+            schema       : notesProtocol.types.note.schema,
+            dataFormat   : 'text/plain',
           }
-        },
-        subscriptionHandler: otherProtocolSubscriptionHandler
-      });
-      expect(otherProtocolSubscribeResult.status.code).to.equal(200);
+        });
+        expect(writeStatus1.code).to.equal(202);
+        expect(allowedRecord).to.not.be.undefined;
+        const { status: allowedRecordSendStatus } = await allowedRecord.send();
+        expect(allowedRecordSendStatus.code).to.equal(202);
 
-      // alice subscribes to the other protocol as a sanity
-      const aliceOtherProtocolRecords: Map<string, Record> = new Map();
-      const aliceOtherProtocolSubscriptionHandler = async (record: Record) => {
-        aliceOtherProtocolRecords.set(record.id, record);
-      };
-      const aliceOtherProtocolSubscribeResult = await dwnAlice.records.subscribe({
-        from     : aliceDid.uri,
-        protocol : aliceOtherProtocol.definition.protocol,
-        message  : {
-          filter: {
-            protocol: aliceOtherProtocol.definition.protocol
+        const { status: writeStatus3, record: privateRecord } = await dwnAlice.records.create({
+          data    : 'Hello, world!',
+          message : {
+            protocol     : aliceOtherProtocol.definition.protocol,
+            protocolPath : 'note',
+            schema       : aliceOtherProtocol.definition.types.note.schema,
+            dataFormat   : 'text/plain',
           }
-        },
-        subscriptionHandler: aliceOtherProtocolSubscriptionHandler
-      });
-      expect(aliceOtherProtocolSubscribeResult.status.code).to.equal(200);
-
-      // NOTE: write the private record before the public so that it should be received first
-      // alice writes a public and private note to the other protocol
-      const { status: writeStatus2, record: publicRecord } = await dwnAlice.records.create({
-        data    : 'Hello, world!',
-        message : {
-          published    : true,
-          protocol     : aliceOtherProtocol.definition.protocol,
-          protocolPath : 'note',
-          schema       : aliceOtherProtocol.definition.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
-      });
-      expect(writeStatus2.code).to.equal(202);
-      expect(publicRecord).to.not.be.undefined;
-      const { status: publicRecordSendStatus } = await publicRecord.send();
-      expect(publicRecordSendStatus.code).to.equal(202);
-
-      // alice writes a note record to the permissioned protocol
-      const { status: writeStatus1, record: allowedRecord } = await dwnAlice.records.create({
-        data    : 'Hello, world!',
-        message : {
-          protocol     : notesProtocol.protocol,
-          protocolPath : 'note',
-          schema       : notesProtocol.types.note.schema,
-          dataFormat   : 'text/plain',
-        }
+        });
+        expect(writeStatus3.code).to.equal(202);
+        expect(privateRecord).to.not.be.undefined;
+        const { status: privateRecordSendStatus } = await privateRecord.send();
+        expect(privateRecordSendStatus.code).to.equal(202);
+
+        // wait for the records to be received
+        // alice receives both the public and private records on her subscription
+        await Poller.pollUntilSuccessOrTimeout(async () => {
+          expect(aliceOtherProtocolRecords.size).to.equal(2);
+          expect(aliceOtherProtocolRecords.get(publicRecord.id)).to.exist;
+          expect(aliceOtherProtocolRecords.get(privateRecord.id)).to.exist;
+        });
+
+        // delegated agent only receives the public record from the other protocol
+        await Poller.pollUntilSuccessOrTimeout(async () => {
+          // permissionedNotesRecords should have the allowedRecord
+          expect(permissionedNotesRecords.size).to.equal(1);
+          expect(permissionedNotesRecords.get(allowedRecord.id)).to.exist;
+
+          // otherProtocolRecords should have only the publicRecord
+          expect(otherProtocolRecords.size).to.equal(1);
+          expect(otherProtocolRecords.get(publicRecord.id)).to.exist;
+        });
       });
-      expect(writeStatus1.code).to.equal(202);
-      expect(allowedRecord).to.not.be.undefined;
-      const { status: allowedRecordSendStatus } = await allowedRecord.send();
-      expect(allowedRecordSendStatus.code).to.equal(202);
-
-      const { status: writeStatus3, record: privateRecord } = await dwnAlice.records.create({
-        data    : 'Hello, world!',
-        message : {
-          protocol     : aliceOtherProtocol.definition.protocol,
-          protocolPath : 'note',
-          schema       : aliceOtherProtocol.definition.types.note.schema,
-          dataFormat   : 'text/plain',
+    });
+
+    describe('protocols', () => {
+      it('should configure a protocol with a delegated grant', async () => {
+        const protocolUri = `http://protocol-configure.xyz/protocol/${TestDataGenerator.randomString(15)}`;
+
+        // attempt to configure the protocol without a grant, it should fail
+        try {
+          await delegateDwn.protocols.configure({
+            message: {
+              definition: {
+                ...notesProtocol,
+                protocol: protocolUri,
+              }
+            }
+          });
+          expect.fail('Expected an error to be thrown.');
+        } catch(error: any) {
+          expect(error.message).to.equal(`CachedPermissions: No permissions found for ProtocolsConfigure: ${protocolUri}`);
         }
+
+        // create a grant for the protocol
+        const delegatedBearerDid = await delegateHarness.agent.did.get({ didUri: delegateDid.uri, tenant: delegateDid.uri });
+        const grants = await Oidc.createPermissionGrants(aliceDid.uri, delegatedBearerDid, testHarness.agent, [{
+          interface : DwnInterfaceName.Protocols,
+          method    : DwnMethodName.Configure,
+          protocol  : protocolUri
+        }]);
+
+        await Web5.processConnectedGrants({ grants, delegateDid: delegateDid.uri, agent: delegateHarness.agent });
+
+        // now try again after processing the connected grant
+        const { status, protocol } = await delegateDwn.protocols.configure({
+          message: {
+            definition: {
+              ...notesProtocol,
+              protocol: protocolUri,
+            }
+          }
+        });
+        expect(status.code).to.equal(202);
+        expect(protocol).to.exist;
+        expect(protocol.definition.protocol).to.equal(protocolUri);
       });
-      expect(writeStatus3.code).to.equal(202);
-      expect(privateRecord).to.not.be.undefined;
-      const { status: privateRecordSendStatus } = await privateRecord.send();
-      expect(privateRecordSendStatus.code).to.equal(202);
 
-      // wait for the records to be received
-      // alice receives both the public and private records on her subscription
-      await Poller.pollUntilSuccessOrTimeout(async () => {
-        expect(aliceOtherProtocolRecords.size).to.equal(2);
-        expect(aliceOtherProtocolRecords.get(publicRecord.id)).to.exist;
-        expect(aliceOtherProtocolRecords.get(privateRecord.id)).to.exist;
+      it('should query for a protocol with a permission grant', async () => {
+        // configure a non public protocol
+        const nonPublicProtocol = {
+          ...notesProtocol,
+          protocol  : `http://non-public-protocol.xyz/protocol/${TestDataGenerator.randomString(15)}`,
+          published : false
+        };
+
+        const { status: nonPublicStatus, protocol: nonPublicProtocolResponse } = await dwnAlice.protocols.configure({
+          message: {
+            definition: nonPublicProtocol
+          }
+        });
+        expect(nonPublicStatus.code).to.equal(202);
+        expect(nonPublicProtocolResponse).to.exist;
+        const nonPublicProtocolSend = await nonPublicProtocolResponse.send(aliceDid.uri);
+        expect(nonPublicProtocolSend.status.code).to.equal(202);
+
+        // attempt to query the protocol, should not return any results as there are no grants for it
+        const { status: nonPublicQueryStatus, protocols: nonPublicProtocols } = await delegateDwn.protocols.query({
+          from    : aliceDid.uri,
+          message : {
+            filter: {
+              protocol: nonPublicProtocol.protocol
+            }
+          }
+        });
+        expect(nonPublicQueryStatus.code).to.equal(200);
+        expect(nonPublicProtocols).to.exist;
+        expect(nonPublicProtocols).to.have.lengthOf(0);
+
+        // grant the delegate DID access to query the non-public protocol
+        const delegatedBearerDid = await delegateHarness.agent.did.get({ didUri: delegateDid.uri, tenant: delegateDid.uri });
+        const grants = await Oidc.createPermissionGrants(aliceDid.uri, delegatedBearerDid, testHarness.agent, [{
+          interface : DwnInterfaceName.Protocols,
+          method    : DwnMethodName.Query,
+          protocol  : nonPublicProtocol.protocol
+        }]);
+        await Web5.processConnectedGrants({ grants, delegateDid: delegateDid.uri, agent: delegateHarness.agent });
+
+        // now query for the non-public protocol, should return the protocol
+        const { status: nonPublicQueryStatus2, protocols: nonPublicProtocols2 } = await delegateDwn.protocols.query({
+          from    : aliceDid.uri,
+          message : {
+            filter: {
+              protocol: nonPublicProtocol.protocol
+            }
+          }
+        });
+        expect(nonPublicQueryStatus2.code).to.equal(200);
+        expect(nonPublicProtocols2).to.exist;
+        expect(nonPublicProtocols2).to.have.lengthOf(1);
       });
 
-      // delegated agent only receives the public record from the other protocol
-      await Poller.pollUntilSuccessOrTimeout(async () => {
-        // permissionedNotesRecords should have the allowedRecord
-        expect(permissionedNotesRecords.size).to.equal(1);
-        expect(permissionedNotesRecords.get(allowedRecord.id)).to.exist;
+      it('should query for a protocol as the delegate DID if no grant is found', async () => {
+        // configure a public protocol without any grants
+        const publicProtocol = {
+          ...notesProtocol,
+          protocol  : `http://public-protocol.xyz/protocol/${TestDataGenerator.randomString(15)}`,
+          published : true
+        };
+
+        const { status: publicStatus, protocol: publicProtocolResponse } = await dwnAlice.protocols.configure({
+          message: {
+            definition: publicProtocol
+          }
+        });
+        expect(publicStatus.code).to.equal(202);
+        expect(publicProtocolResponse).to.exist;
+        const publicProtocolSend = await publicProtocolResponse.send(aliceDid.uri);
+        expect(publicProtocolSend.status.code).to.equal(202);
 
-        // otherProtocolRecords should have only the publicRecord
-        expect(otherProtocolRecords.size).to.equal(1);
-        expect(otherProtocolRecords.get(publicRecord.id)).to.exist;
+        const { status: publicQueryStatus, protocols: publicProtocols } = await delegateDwn.protocols.query({
+          from    : aliceDid.uri,
+          message : {
+            filter: {
+              protocol: publicProtocol.protocol
+            }
+          }
+        });
+        expect(publicQueryStatus.code).to.equal(200);
+        expect(publicProtocols).to.exist;
+        expect(publicProtocols).to.have.lengthOf(1);
+        expect(publicProtocols[0].definition.protocol).to.equal(publicProtocol.protocol);
       });
     });
   });
diff --git a/packages/dev-env/docker-compose.yaml b/packages/dev-env/docker-compose.yaml
index 116d52148..f4beb63af 100644
--- a/packages/dev-env/docker-compose.yaml
+++ b/packages/dev-env/docker-compose.yaml
@@ -3,6 +3,6 @@ version: "3.98"
 services:
   dwn-server:
     container_name: dwn-server
-    image: ghcr.io/tbd54566975/dwn-server:0.4.9
+    image: ghcr.io/tbd54566975/dwn-server:0.4.10
     ports:
       - "3000:3000"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 860e902e7..d2af32d77 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -40,8 +40,8 @@ importers:
         specifier: 7.9.0
         version: 7.9.0(@typescript-eslint/parser@7.14.1(eslint@9.7.0)(typescript@5.5.4))(eslint@9.7.0)(typescript@5.5.4)
       '@web5/dwn-server':
-        specifier: 0.4.9
-        version: 0.4.9
+        specifier: 0.4.10
+        version: 0.4.10
       audit-ci:
         specifier: ^7.0.1
         version: 7.1.0
@@ -64,8 +64,8 @@ importers:
         specifier: 1.2.2
         version: 1.2.2
       '@tbd54566975/dwn-sdk-js':
-        specifier: 0.4.6
-        version: 0.4.6
+        specifier: 0.4.7
+        version: 0.4.7
       '@web5/common':
         specifier: 1.0.0
         version: 1.0.0
@@ -192,8 +192,8 @@ importers:
         specifier: 1.45.3
         version: 1.45.3
       '@tbd54566975/dwn-sdk-js':
-        specifier: 0.4.6
-        version: 0.4.6
+        specifier: 0.4.7
+        version: 0.4.7
       '@types/chai':
         specifier: 4.3.6
         version: 4.3.6
@@ -2108,12 +2108,12 @@ packages:
   '@sphereon/ssi-types@0.26.0':
     resolution: {integrity: sha512-r4JQIN7rnPunEv0HvCFC1ZCc9qlWcegYvhJbMJqSvyFE6VhmT5NNdH9jNV9QetgMa0yo5r3k+TnHNv3nH58Dmg==}
 
-  '@tbd54566975/dwn-sdk-js@0.4.6':
-    resolution: {integrity: sha512-eTd9v2ioT+hYrmob28OgxyLgOPAqJosb8rIAHDpFzEjYlQZSxCEohIZysMrLgWIcSLljyViSFr06mDelRPgGPg==}
+  '@tbd54566975/dwn-sdk-js@0.4.7':
+    resolution: {integrity: sha512-VYaLT4FKdHfVvUPZbicUpF77erkOSi1xBP/EVQIpnp0khPujp2lYcojbRcw4c4JR23CrRvLPy/iWXmEhdP8LqA==}
     engines: {node: '>= 18'}
 
-  '@tbd54566975/dwn-sql-store@0.6.6':
-    resolution: {integrity: sha512-LY8it9npYjI/Kx/aK94gR6/1AfptmRGagUuXOfprm/lUcK3uJ79EReOq8zk7CXyTK66+GAu+oGFzuCoo12EJ1g==}
+  '@tbd54566975/dwn-sql-store@0.6.7':
+    resolution: {integrity: sha512-5v/BudrItBx8UUMEIH42nMBwykpM9ZyBpMERmWwJn06Xe47wv+ojkDhVX000Npuv4q+bsLv0lQhCaIAmKcMlaQ==}
     engines: {node: '>=18'}
 
   '@tootallnate/quickjs-emscripten@0.23.0':
@@ -2511,8 +2511,8 @@ packages:
     resolution: {integrity: sha512-M9EfsEYcOtYuEvUQjow4vpxXbD0Sz5H8EuDXMtwuvP4UdYL0ATl+60F8+8HDmwPFeUy6M2wxuoixrLDwSRFwZA==}
     engines: {node: '>=18.0.0'}
 
-  '@web5/dwn-server@0.4.9':
-    resolution: {integrity: sha512-LCBu7gcmfWcT8i571LPK5bHsBqtF2b0gC1VjAqZTo7ESCjGPrL6byvntiGiYWfSfzl9zgxpb/dIdVu/Ia8xvFA==}
+  '@web5/dwn-server@0.4.10':
+    resolution: {integrity: sha512-gdXIDC4OkCS58+EG85SN82IeWynl3uqkpeoq79A6X9NCGWO9+5XM5pNKCjkPxxNdsGfz0sX+nYLkSqrRX5BcFA==}
     hasBin: true
 
   '@webassemblyjs/ast@1.12.1':
@@ -7369,7 +7369,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@tbd54566975/dwn-sdk-js@0.4.6':
+  '@tbd54566975/dwn-sdk-js@0.4.7':
     dependencies:
       '@ipld/dag-cbor': 9.0.3
       '@js-temporal/polyfill': 0.4.4
@@ -7402,10 +7402,10 @@ snapshots:
       - encoding
       - supports-color
 
-  '@tbd54566975/dwn-sql-store@0.6.6':
+  '@tbd54566975/dwn-sql-store@0.6.7':
     dependencies:
       '@ipld/dag-cbor': 9.0.5
-      '@tbd54566975/dwn-sdk-js': 0.4.6
+      '@tbd54566975/dwn-sdk-js': 0.4.7
       kysely: 0.26.3
       multiformats: 12.0.1
       readable-stream: 4.4.2
@@ -8355,10 +8355,10 @@ snapshots:
       level: 8.0.1
       ms: 2.1.3
 
-  '@web5/dwn-server@0.4.9':
+  '@web5/dwn-server@0.4.10':
     dependencies:
-      '@tbd54566975/dwn-sdk-js': 0.4.6
-      '@tbd54566975/dwn-sql-store': 0.6.6
+      '@tbd54566975/dwn-sdk-js': 0.4.7
+      '@tbd54566975/dwn-sql-store': 0.6.7
       '@web5/crypto': 1.0.3
       better-sqlite3: 8.7.0
       body-parser: 1.20.3