@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.
Files changed (79) hide show
  1. package/dist/browser.mjs +11 -11
  2. package/dist/browser.mjs.map +4 -4
  3. package/dist/esm/anonymous-dwn-api.js +184 -0
  4. package/dist/esm/anonymous-dwn-api.js.map +1 -0
  5. package/dist/esm/dwn-api.js +85 -785
  6. package/dist/esm/dwn-api.js.map +1 -1
  7. package/dist/esm/dwn-encryption.js +342 -0
  8. package/dist/esm/dwn-encryption.js.map +1 -0
  9. package/dist/esm/dwn-key-delivery.js +256 -0
  10. package/dist/esm/dwn-key-delivery.js.map +1 -0
  11. package/dist/esm/dwn-record-upgrade.js +119 -0
  12. package/dist/esm/dwn-record-upgrade.js.map +1 -0
  13. package/dist/esm/dwn-type-guards.js +23 -0
  14. package/dist/esm/dwn-type-guards.js.map +1 -0
  15. package/dist/esm/index.js +6 -0
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/permissions-api.js +43 -2
  18. package/dist/esm/permissions-api.js.map +1 -1
  19. package/dist/esm/protocol-utils.js +158 -0
  20. package/dist/esm/protocol-utils.js.map +1 -0
  21. package/dist/esm/store-data-protocols.js +1 -1
  22. package/dist/esm/store-data-protocols.js.map +1 -1
  23. package/dist/esm/store-data.js +3 -0
  24. package/dist/esm/store-data.js.map +1 -1
  25. package/dist/esm/sync-engine-level.js +23 -354
  26. package/dist/esm/sync-engine-level.js.map +1 -1
  27. package/dist/esm/sync-messages.js +237 -0
  28. package/dist/esm/sync-messages.js.map +1 -0
  29. package/dist/esm/sync-topological-sort.js +143 -0
  30. package/dist/esm/sync-topological-sort.js.map +1 -0
  31. package/dist/esm/test-harness.js +20 -0
  32. package/dist/esm/test-harness.js.map +1 -1
  33. package/dist/types/anonymous-dwn-api.d.ts +140 -0
  34. package/dist/types/anonymous-dwn-api.d.ts.map +1 -0
  35. package/dist/types/dwn-api.d.ts +36 -184
  36. package/dist/types/dwn-api.d.ts.map +1 -1
  37. package/dist/types/dwn-encryption.d.ts +144 -0
  38. package/dist/types/dwn-encryption.d.ts.map +1 -0
  39. package/dist/types/dwn-key-delivery.d.ts +112 -0
  40. package/dist/types/dwn-key-delivery.d.ts.map +1 -0
  41. package/dist/types/dwn-record-upgrade.d.ts +33 -0
  42. package/dist/types/dwn-record-upgrade.d.ts.map +1 -0
  43. package/dist/types/dwn-type-guards.d.ts +9 -0
  44. package/dist/types/dwn-type-guards.d.ts.map +1 -0
  45. package/dist/types/index.d.ts +6 -0
  46. package/dist/types/index.d.ts.map +1 -1
  47. package/dist/types/permissions-api.d.ts +6 -1
  48. package/dist/types/permissions-api.d.ts.map +1 -1
  49. package/dist/types/protocol-utils.d.ts +70 -0
  50. package/dist/types/protocol-utils.d.ts.map +1 -0
  51. package/dist/types/store-data.d.ts +4 -0
  52. package/dist/types/store-data.d.ts.map +1 -1
  53. package/dist/types/sync-engine-level.d.ts +5 -42
  54. package/dist/types/sync-engine-level.d.ts.map +1 -1
  55. package/dist/types/sync-messages.d.ts +76 -0
  56. package/dist/types/sync-messages.d.ts.map +1 -0
  57. package/dist/types/sync-topological-sort.d.ts +15 -0
  58. package/dist/types/sync-topological-sort.d.ts.map +1 -0
  59. package/dist/types/test-harness.d.ts +10 -0
  60. package/dist/types/test-harness.d.ts.map +1 -1
  61. package/dist/types/types/permissions.d.ts +2 -0
  62. package/dist/types/types/permissions.d.ts.map +1 -1
  63. package/package.json +5 -5
  64. package/src/anonymous-dwn-api.ts +263 -0
  65. package/src/dwn-api.ts +158 -1024
  66. package/src/dwn-encryption.ts +481 -0
  67. package/src/dwn-key-delivery.ts +370 -0
  68. package/src/dwn-record-upgrade.ts +166 -0
  69. package/src/dwn-type-guards.ts +43 -0
  70. package/src/index.ts +6 -0
  71. package/src/permissions-api.ts +54 -2
  72. package/src/protocol-utils.ts +185 -0
  73. package/src/store-data-protocols.ts +1 -1
  74. package/src/store-data.ts +5 -2
  75. package/src/sync-engine-level.ts +25 -414
  76. package/src/sync-messages.ts +279 -0
  77. package/src/sync-topological-sort.ts +167 -0
  78. package/src/test-harness.ts +19 -0
  79. 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://enbox.org/protocols/key-delivery',
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> {