@enbox/agent 0.1.5 → 0.1.7
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 +11 -11
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/anonymous-dwn-api.js +184 -0
- package/dist/esm/anonymous-dwn-api.js.map +1 -0
- package/dist/esm/dwn-api.js +85 -785
- package/dist/esm/dwn-api.js.map +1 -1
- package/dist/esm/dwn-encryption.js +342 -0
- package/dist/esm/dwn-encryption.js.map +1 -0
- package/dist/esm/dwn-key-delivery.js +256 -0
- package/dist/esm/dwn-key-delivery.js.map +1 -0
- package/dist/esm/dwn-record-upgrade.js +119 -0
- package/dist/esm/dwn-record-upgrade.js.map +1 -0
- package/dist/esm/dwn-type-guards.js +23 -0
- package/dist/esm/dwn-type-guards.js.map +1 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/permissions-api.js +43 -2
- package/dist/esm/permissions-api.js.map +1 -1
- package/dist/esm/protocol-utils.js +158 -0
- package/dist/esm/protocol-utils.js.map +1 -0
- package/dist/esm/store-data-protocols.js +1 -1
- package/dist/esm/store-data-protocols.js.map +1 -1
- package/dist/esm/store-data.js +3 -0
- package/dist/esm/store-data.js.map +1 -1
- package/dist/esm/sync-engine-level.js +23 -354
- package/dist/esm/sync-engine-level.js.map +1 -1
- package/dist/esm/sync-messages.js +237 -0
- package/dist/esm/sync-messages.js.map +1 -0
- package/dist/esm/sync-topological-sort.js +143 -0
- package/dist/esm/sync-topological-sort.js.map +1 -0
- package/dist/esm/test-harness.js +20 -0
- package/dist/esm/test-harness.js.map +1 -1
- package/dist/types/anonymous-dwn-api.d.ts +140 -0
- package/dist/types/anonymous-dwn-api.d.ts.map +1 -0
- package/dist/types/dwn-api.d.ts +36 -184
- package/dist/types/dwn-api.d.ts.map +1 -1
- package/dist/types/dwn-encryption.d.ts +144 -0
- package/dist/types/dwn-encryption.d.ts.map +1 -0
- package/dist/types/dwn-key-delivery.d.ts +112 -0
- package/dist/types/dwn-key-delivery.d.ts.map +1 -0
- package/dist/types/dwn-record-upgrade.d.ts +33 -0
- package/dist/types/dwn-record-upgrade.d.ts.map +1 -0
- package/dist/types/dwn-type-guards.d.ts +9 -0
- package/dist/types/dwn-type-guards.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/permissions-api.d.ts +6 -1
- package/dist/types/permissions-api.d.ts.map +1 -1
- package/dist/types/protocol-utils.d.ts +70 -0
- package/dist/types/protocol-utils.d.ts.map +1 -0
- package/dist/types/store-data.d.ts +4 -0
- package/dist/types/store-data.d.ts.map +1 -1
- package/dist/types/sync-engine-level.d.ts +5 -42
- package/dist/types/sync-engine-level.d.ts.map +1 -1
- package/dist/types/sync-messages.d.ts +76 -0
- package/dist/types/sync-messages.d.ts.map +1 -0
- package/dist/types/sync-topological-sort.d.ts +15 -0
- package/dist/types/sync-topological-sort.d.ts.map +1 -0
- package/dist/types/test-harness.d.ts +10 -0
- package/dist/types/test-harness.d.ts.map +1 -1
- package/dist/types/types/permissions.d.ts +2 -0
- package/dist/types/types/permissions.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/anonymous-dwn-api.ts +263 -0
- package/src/dwn-api.ts +158 -1024
- package/src/dwn-encryption.ts +481 -0
- package/src/dwn-key-delivery.ts +370 -0
- package/src/dwn-record-upgrade.ts +166 -0
- package/src/dwn-type-guards.ts +43 -0
- package/src/index.ts +6 -0
- package/src/permissions-api.ts +54 -2
- package/src/protocol-utils.ts +185 -0
- package/src/store-data-protocols.ts +1 -1
- package/src/store-data.ts +5 -2
- package/src/sync-engine-level.ts +25 -414
- package/src/sync-messages.ts +279 -0
- package/src/sync-topological-sort.ts +167 -0
- package/src/test-harness.ts +19 -0
- package/src/types/permissions.ts +2 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { ProtocolDefinition, ProtocolRuleSet } from '@enbox/dwn-sdk-js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Navigates a protocol definition's structure to find the rule set at a given protocol path.
|
|
5
|
+
* @param protocolDefinition - The protocol definition to search
|
|
6
|
+
* @param protocolPath - The dot-separated protocol path (e.g. 'thread/message')
|
|
7
|
+
* @returns The rule set at the given path, or undefined if the path doesn't exist
|
|
8
|
+
*/
|
|
9
|
+
export function getRuleSetAtPath(
|
|
10
|
+
protocolDefinition: ProtocolDefinition,
|
|
11
|
+
protocolPath: string,
|
|
12
|
+
): ProtocolRuleSet | undefined {
|
|
13
|
+
const segments = protocolPath.split('/');
|
|
14
|
+
let ruleSet: ProtocolRuleSet | undefined =
|
|
15
|
+
protocolDefinition.structure as unknown as ProtocolRuleSet;
|
|
16
|
+
for (const segment of segments) {
|
|
17
|
+
ruleSet = ruleSet[segment] as ProtocolRuleSet | undefined;
|
|
18
|
+
if (!ruleSet) { return undefined; }
|
|
19
|
+
}
|
|
20
|
+
return ruleSet;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Extracts the root context ID from a contextId or parentContextId.
|
|
25
|
+
* e.g. 'abc/def/ghi' -> 'abc', 'abc' -> 'abc'
|
|
26
|
+
* @param contextId - The context ID to extract the root from
|
|
27
|
+
* @returns The root context ID
|
|
28
|
+
*/
|
|
29
|
+
export function getRootContextId(contextId: string): string {
|
|
30
|
+
return contextId.split('/')[0] || contextId;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Checks if a protocol path represents a multi-party context.
|
|
35
|
+
* Returns true if the root path's subtree contains $role descendants
|
|
36
|
+
* or relational who/of $actions rules that grant read access.
|
|
37
|
+
*
|
|
38
|
+
* @param protocolDefinition - The full protocol definition
|
|
39
|
+
* @param rootProtocolPath - The root protocol path to check
|
|
40
|
+
* @returns true if the protocol path represents a multi-party context
|
|
41
|
+
*/
|
|
42
|
+
export function isMultiPartyContext(
|
|
43
|
+
protocolDefinition: ProtocolDefinition,
|
|
44
|
+
rootProtocolPath: string,
|
|
45
|
+
): boolean {
|
|
46
|
+
const ruleSet = getRuleSetAtPath(protocolDefinition, rootProtocolPath);
|
|
47
|
+
if (!ruleSet) { return false; }
|
|
48
|
+
|
|
49
|
+
// (a) Check for $role descendants in the subtree
|
|
50
|
+
function hasRoleRecursive(rs: ProtocolRuleSet): boolean {
|
|
51
|
+
for (const key in rs) {
|
|
52
|
+
if (!key.startsWith('$')) {
|
|
53
|
+
const child = rs[key] as ProtocolRuleSet;
|
|
54
|
+
if (child.$role === true) { return true; }
|
|
55
|
+
if (hasRoleRecursive(child)) { return true; }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (hasRoleRecursive(ruleSet)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// (b) Check for relational who/of read rules anywhere in the protocol
|
|
66
|
+
// that reference a path within this subtree. A rule like
|
|
67
|
+
// { who: 'recipient', of: 'email', can: ['read'] } on any record
|
|
68
|
+
// type means the email recipient needs a context key.
|
|
69
|
+
return hasRelationalReadAccess(
|
|
70
|
+
undefined, rootProtocolPath, protocolDefinition,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Checks whether any relational who/of rule in the protocol grants
|
|
76
|
+
* read access for a given actor type and ancestor path.
|
|
77
|
+
*
|
|
78
|
+
* Walks the *entire* protocol structure looking for any $actions rule that:
|
|
79
|
+
* - Has `who` equal to `actorType` ('recipient' or 'author'), or any actor
|
|
80
|
+
* type if `actorType` is `undefined`
|
|
81
|
+
* - Has `of` equal to `ofPath`
|
|
82
|
+
* - Has `can` including 'read'
|
|
83
|
+
*
|
|
84
|
+
* @param actorType - 'author' | 'recipient', or undefined for any
|
|
85
|
+
* @param ofPath - The protocol path to check (e.g. 'thread', 'email')
|
|
86
|
+
* @param protocolDefinition - The full protocol definition
|
|
87
|
+
* @returns true if a matching relational read rule exists
|
|
88
|
+
*/
|
|
89
|
+
export function hasRelationalReadAccess(
|
|
90
|
+
actorType: 'author' | 'recipient' | undefined,
|
|
91
|
+
ofPath: string,
|
|
92
|
+
protocolDefinition: ProtocolDefinition,
|
|
93
|
+
): boolean {
|
|
94
|
+
const structure = protocolDefinition.structure as unknown as ProtocolRuleSet;
|
|
95
|
+
|
|
96
|
+
function walkRuleSet(rs: ProtocolRuleSet): boolean {
|
|
97
|
+
// Check $actions on this node
|
|
98
|
+
if (rs.$actions) {
|
|
99
|
+
for (const rule of rs.$actions) {
|
|
100
|
+
if (
|
|
101
|
+
rule.who &&
|
|
102
|
+
rule.who !== 'anyone' &&
|
|
103
|
+
(actorType === undefined || rule.who === actorType) &&
|
|
104
|
+
rule.of === ofPath &&
|
|
105
|
+
rule.can?.includes('read')
|
|
106
|
+
) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Recurse into child record types
|
|
113
|
+
for (const key in rs) {
|
|
114
|
+
if (!key.startsWith('$')) {
|
|
115
|
+
if (walkRuleSet(rs[key] as ProtocolRuleSet)) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return walkRuleSet(structure);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Analyses a record write to determine which DIDs need context key delivery.
|
|
128
|
+
*
|
|
129
|
+
* Returns a set of participant DIDs that should receive `contextKey` records.
|
|
130
|
+
* The DWN owner (tenantDid) is always excluded — they have ProtocolPath access.
|
|
131
|
+
*
|
|
132
|
+
* Cases handled:
|
|
133
|
+
* 1. `$role` record with a recipient -> recipient is a participant
|
|
134
|
+
* 2. Record has a recipient and a relational read rule grants access
|
|
135
|
+
* via `{ who: 'recipient', of: '<path>', can: ['read'] }`
|
|
136
|
+
* 3. Record is authored by an external party -> if `{ who: 'author', of:
|
|
137
|
+
* '<path>', can: ['read'] }` rules grant read access, the author needs
|
|
138
|
+
* a context key.
|
|
139
|
+
*
|
|
140
|
+
* @param params.protocolDefinition - The installed protocol definition
|
|
141
|
+
* @param params.protocolPath - The written record's protocol path
|
|
142
|
+
* @param params.recipient - Recipient DID from the record, if any
|
|
143
|
+
* @param params.tenantDid - The DWN owner's DID (excluded from results)
|
|
144
|
+
* @param params.authorDid - Author DID if externally authored, undefined otherwise
|
|
145
|
+
* @returns Set of DIDs that need context key delivery
|
|
146
|
+
*/
|
|
147
|
+
export function detectNewParticipants({ protocolDefinition, protocolPath, recipient, tenantDid, authorDid }: {
|
|
148
|
+
protocolDefinition: ProtocolDefinition;
|
|
149
|
+
protocolPath: string;
|
|
150
|
+
recipient?: string;
|
|
151
|
+
tenantDid: string;
|
|
152
|
+
authorDid?: string;
|
|
153
|
+
}): Set<string> {
|
|
154
|
+
const participants = new Set<string>();
|
|
155
|
+
|
|
156
|
+
// Navigate to the rule set at the given protocol path
|
|
157
|
+
const ruleSet = getRuleSetAtPath(protocolDefinition, protocolPath);
|
|
158
|
+
if (!ruleSet) { return participants; }
|
|
159
|
+
|
|
160
|
+
// Case 1: $role record -> recipient is a participant
|
|
161
|
+
if (ruleSet.$role === true && recipient) {
|
|
162
|
+
participants.add(recipient);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Case 2: Record has a recipient -> check if relational read rules exist
|
|
166
|
+
if (recipient && recipient !== tenantDid) {
|
|
167
|
+
if (hasRelationalReadAccess('recipient', protocolPath, protocolDefinition)) {
|
|
168
|
+
participants.add(recipient);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Case 3: External author -> check if author-based relational read rules exist.
|
|
173
|
+
// If `{ who: 'author', of: '<path>', can: ['read'] }` is defined anywhere
|
|
174
|
+
// in the protocol, the external author needs a context key to decrypt.
|
|
175
|
+
if (authorDid && authorDid !== tenantDid) {
|
|
176
|
+
if (hasRelationalReadAccess('author', protocolPath, protocolDefinition)) {
|
|
177
|
+
participants.add(authorDid);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Remove the DWN owner — they always have ProtocolPath access
|
|
182
|
+
participants.delete(tenantDid);
|
|
183
|
+
|
|
184
|
+
return participants;
|
|
185
|
+
}
|
|
@@ -24,7 +24,7 @@ export const IdentityProtocolDefinition: ProtocolDefinition = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export const KeyDeliveryProtocolDefinition: ProtocolDefinition = {
|
|
27
|
-
protocol : 'https://
|
|
27
|
+
protocol : 'https://identity.foundation/protocols/key-delivery',
|
|
28
28
|
published : false,
|
|
29
29
|
types : {
|
|
30
30
|
contextKey: {
|
package/src/store-data.ts
CHANGED
|
@@ -89,9 +89,12 @@ export class DwnDataStore<TStoreObject extends Record<string, any> = Jwk> implem
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Properties to use when writing and querying records with the DWN store.
|
|
92
|
+
* Subclasses MUST override this to include `protocol` and `protocolPath`.
|
|
92
93
|
*/
|
|
93
|
-
protected _recordProperties = {
|
|
94
|
-
dataFormat: 'application/json',
|
|
94
|
+
protected _recordProperties: { dataFormat: string; protocol: string; protocolPath: string; schema?: string } = {
|
|
95
|
+
dataFormat : 'application/json',
|
|
96
|
+
protocol : '', // overridden by subclass
|
|
97
|
+
protocolPath : '', // overridden by subclass
|
|
95
98
|
};
|
|
96
99
|
|
|
97
100
|
public async delete({ id, agent, tenant }: DataStoreDeleteParams): Promise<boolean> {
|