@enbox/agent 0.6.2 → 0.6.4
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 +2 -2
- package/dist/browser.mjs.map +3 -3
- package/dist/esm/store-data.js +7 -1
- package/dist/esm/store-data.js.map +1 -1
- package/dist/esm/sync-closure-resolver.js +48 -26
- package/dist/esm/sync-closure-resolver.js.map +1 -1
- package/dist/esm/sync-closure-types.js +2 -1
- package/dist/esm/sync-closure-types.js.map +1 -1
- package/dist/esm/sync-engine-level.js +3 -1
- package/dist/esm/sync-engine-level.js.map +1 -1
- package/dist/esm/sync-messages.js +23 -9
- package/dist/esm/sync-messages.js.map +1 -1
- package/dist/types/store-data.d.ts.map +1 -1
- package/dist/types/sync-closure-resolver.d.ts.map +1 -1
- package/dist/types/sync-closure-types.d.ts +20 -1
- package/dist/types/sync-closure-types.d.ts.map +1 -1
- package/dist/types/sync-engine-level.d.ts.map +1 -1
- package/dist/types/sync-messages.d.ts +6 -2
- package/dist/types/sync-messages.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/store-data.ts +7 -1
- package/src/sync-closure-resolver.ts +49 -25
- package/src/sync-closure-types.ts +29 -6
- package/src/sync-engine-level.ts +3 -1
- package/src/sync-messages.ts +26 -11
|
@@ -200,6 +200,7 @@ function getRuleSetAtProtocolPath(
|
|
|
200
200
|
function extractProtocolAwareDeps(
|
|
201
201
|
message: GenericMessage,
|
|
202
202
|
protocolDef: any,
|
|
203
|
+
isDelegateSession?: boolean,
|
|
203
204
|
): ClosureDependencyEdge[] {
|
|
204
205
|
const desc = message.descriptor as Record<string, unknown>;
|
|
205
206
|
if (desc.interface !== 'Records') { return []; }
|
|
@@ -246,15 +247,39 @@ function extractProtocolAwareDeps(
|
|
|
246
247
|
identifierType : 'protocol',
|
|
247
248
|
});
|
|
248
249
|
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
250
|
+
// Determine whether this record uses multi-party (ProtocolContext)
|
|
251
|
+
// encryption before emitting key-delivery edges.
|
|
252
|
+
const contextId = (message as any).contextId as string | undefined;
|
|
253
|
+
let isMultiParty = false;
|
|
254
|
+
if (contextId) {
|
|
255
|
+
const rootProtocolPath = protocolPath.split('/')[0];
|
|
256
|
+
isMultiParty = isMultiPartyContext(protocolDef, rootProtocolPath);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Level 2: key-delivery protocol ProtocolsConfigure.
|
|
260
|
+
//
|
|
261
|
+
// For **owner** sessions this is always emitted — the DWN needs the
|
|
262
|
+
// protocol installed to authorize contextKey record access.
|
|
263
|
+
//
|
|
264
|
+
// For **delegate** sessions:
|
|
265
|
+
// - Single-party: the delegate decrypts via pre-derived
|
|
266
|
+
// `delegateDecryptionKeys` (ProtocolPath keys). No key-delivery
|
|
267
|
+
// records are involved. Edge is suppressed.
|
|
268
|
+
// - Multi-party: on in-memory cache miss the runtime falls back to
|
|
269
|
+
// `fetchCrossDeviceContextKey()` (dwn-encryption.ts:453,
|
|
270
|
+
// dwn-key-delivery.ts:268) which does a local RecordsQuery +
|
|
271
|
+
// RecordsRead against the owner's tenant. That authorization path
|
|
272
|
+
// requires the key-delivery ProtocolsConfigure. Edge is emitted.
|
|
273
|
+
const suppressKeyDelivery = isDelegateSession && !isMultiParty;
|
|
274
|
+
if (!suppressKeyDelivery) {
|
|
275
|
+
const keyDeliveryProtocol = 'https://identity.foundation/protocols/key-delivery';
|
|
276
|
+
edges.push({
|
|
277
|
+
dependencyClass : 5,
|
|
278
|
+
label : 'keyDeliveryProtocol',
|
|
279
|
+
identifier : keyDeliveryProtocol,
|
|
280
|
+
identifierType : 'protocol',
|
|
281
|
+
});
|
|
282
|
+
}
|
|
258
283
|
|
|
259
284
|
// Level 3: Context key enforcement for multi-party contexts.
|
|
260
285
|
// Uses isMultiPartyContext() from protocol-utils to determine whether the
|
|
@@ -264,21 +289,20 @@ function extractProtocolAwareDeps(
|
|
|
264
289
|
//
|
|
265
290
|
// Single-party contexts use ProtocolPath encryption (owner-only) and do
|
|
266
291
|
// NOT need a context key — the edge is skipped entirely.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
292
|
+
//
|
|
293
|
+
// For delegates this edge is always emitted when applicable: the
|
|
294
|
+
// runtime's cross-device fallback still needs the contextKey record
|
|
295
|
+
// locally (on cache miss).
|
|
296
|
+
if (isMultiParty && contextId) {
|
|
297
|
+
const rootContextId = contextId.split('/')[0];
|
|
298
|
+
// Separator is '|' (not ':') because protocol URIs contain '://'
|
|
299
|
+
// which would break indexOf(':') parsing in resolveDependency.
|
|
300
|
+
edges.push({
|
|
301
|
+
dependencyClass : 5,
|
|
302
|
+
label : 'contextKeyRecord',
|
|
303
|
+
identifier : `${desc.protocol}|${rootContextId}`,
|
|
304
|
+
identifierType : 'messageCid',
|
|
305
|
+
});
|
|
282
306
|
}
|
|
283
307
|
}
|
|
284
308
|
}
|
|
@@ -496,7 +520,7 @@ export async function evaluateClosure(
|
|
|
496
520
|
const cachedProtocolMsg = context.protocolCache.get(currentProtocol);
|
|
497
521
|
const protocolDef = (cachedProtocolMsg?.descriptor as any)?.definition;
|
|
498
522
|
if (protocolDef) {
|
|
499
|
-
const protoAwareEdges = extractProtocolAwareDeps(current, protocolDef);
|
|
523
|
+
const protoAwareEdges = extractProtocolAwareDeps(current, protocolDef, context.isDelegateSession);
|
|
500
524
|
const protoResult = await resolveEdges(
|
|
501
525
|
protoAwareEdges, allEdges, messageStore, context, visited, queue, rootCid, currentDepth
|
|
502
526
|
);
|
|
@@ -122,19 +122,42 @@ export type ClosureEvaluationContext = {
|
|
|
122
122
|
missingDeps: Set<string>;
|
|
123
123
|
/** Maximum traversal depth. Default 32. */
|
|
124
124
|
maxDepth: number;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* When `true`, the sync link is operating as a delegated session.
|
|
128
|
+
*
|
|
129
|
+
* Affects class 5 (encryption) dependency extraction:
|
|
130
|
+
*
|
|
131
|
+
* - **Single-party** protocols: the delegate decrypts via pre-derived
|
|
132
|
+
* `delegateDecryptionKeys` (ProtocolPath keys). No key-delivery
|
|
133
|
+
* records are involved, so the `keyDeliveryProtocol` edge is
|
|
134
|
+
* suppressed entirely in `extractProtocolAwareDeps()`.
|
|
135
|
+
*
|
|
136
|
+
* - **Multi-party** protocols: on in-memory cache miss the runtime
|
|
137
|
+
* falls back to `fetchCrossDeviceContextKey()` (see
|
|
138
|
+
* `dwn-encryption.ts:453`, `dwn-key-delivery.ts:268`) which queries
|
|
139
|
+
* the local DWN. Both `keyDeliveryProtocol` and `contextKeyRecord`
|
|
140
|
+
* are emitted and resolved normally.
|
|
141
|
+
*/
|
|
142
|
+
isDelegateSession?: boolean;
|
|
125
143
|
};
|
|
126
144
|
|
|
127
145
|
/**
|
|
128
146
|
* Create a fresh evaluation context for a batch of closure evaluations.
|
|
129
147
|
*/
|
|
130
|
-
export function createClosureContext(
|
|
148
|
+
export function createClosureContext(
|
|
149
|
+
tenantDid: string,
|
|
150
|
+
maxDepth?: number,
|
|
151
|
+
options?: { isDelegateSession?: boolean },
|
|
152
|
+
): ClosureEvaluationContext {
|
|
131
153
|
return {
|
|
132
154
|
tenantDid,
|
|
133
|
-
protocolCache
|
|
134
|
-
grantCache
|
|
135
|
-
satisfiedDeps
|
|
136
|
-
missingDeps
|
|
137
|
-
maxDepth
|
|
155
|
+
protocolCache : new Map(),
|
|
156
|
+
grantCache : new Map(),
|
|
157
|
+
satisfiedDeps : new Set(),
|
|
158
|
+
missingDeps : new Set(),
|
|
159
|
+
maxDepth : maxDepth ?? 32,
|
|
160
|
+
isDelegateSession : options?.isDelegateSession,
|
|
138
161
|
};
|
|
139
162
|
}
|
|
140
163
|
|
package/src/sync-engine-level.ts
CHANGED
|
@@ -1476,7 +1476,9 @@ export class SyncEngineLevel implements SyncEngine {
|
|
|
1476
1476
|
const messageStore = this.agent.dwn.node.storage.messageStore;
|
|
1477
1477
|
let closureCtx = this._closureContexts.get(did);
|
|
1478
1478
|
if (!closureCtx) {
|
|
1479
|
-
closureCtx = createClosureContext(did
|
|
1479
|
+
closureCtx = createClosureContext(did, undefined, {
|
|
1480
|
+
isDelegateSession: !!delegateDid,
|
|
1481
|
+
});
|
|
1480
1482
|
this._closureContexts.set(did, closureCtx);
|
|
1481
1483
|
}
|
|
1482
1484
|
|
package/src/sync-messages.ts
CHANGED
|
@@ -25,17 +25,32 @@ export type SyncMessageEntry = {
|
|
|
25
25
|
* 202: message was successfully written to the remote DWN
|
|
26
26
|
* 204: an initial write message was written without any data
|
|
27
27
|
* 409: message was already present on the remote DWN
|
|
28
|
-
*
|
|
28
|
+
*
|
|
29
|
+
* When the *pushed* message is known (e.g. during push-sync), pass it as the
|
|
30
|
+
* second argument so that RecordsDelete + 404 ("initial write was not found or
|
|
31
|
+
* already deleted") can be detected. The DWN's 404 reply omits `entry`, so
|
|
32
|
+
* checking `reply.entry` alone is insufficient.
|
|
29
33
|
*/
|
|
30
|
-
export function syncMessageReplyIsSuccessful(reply: UnionMessageReply): boolean {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
export function syncMessageReplyIsSuccessful(reply: UnionMessageReply, pushedMessage?: GenericMessage): boolean {
|
|
35
|
+
if (reply.status.code === 202 || reply.status.code === 204 || reply.status.code === 409) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (reply.status.code === 404) {
|
|
40
|
+
// Check the pushed message first (always available during push-sync).
|
|
41
|
+
if (pushedMessage?.descriptor.interface === DwnInterfaceName.Records &&
|
|
42
|
+
pushedMessage?.descriptor.method === DwnMethodName.Delete) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fallback: check the reply entry (for callers that don't pass the pushed message).
|
|
47
|
+
if (reply.entry?.message.descriptor.interface === DwnInterfaceName.Records &&
|
|
48
|
+
reply.entry?.message.descriptor.method === DwnMethodName.Delete) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false;
|
|
39
54
|
}
|
|
40
55
|
|
|
41
56
|
/**
|
|
@@ -364,7 +379,7 @@ export async function pushMessages({ did, dwnUrl, delegateDid, protocol, message
|
|
|
364
379
|
message : entry.message
|
|
365
380
|
});
|
|
366
381
|
|
|
367
|
-
if (syncMessageReplyIsSuccessful(reply)) {
|
|
382
|
+
if (syncMessageReplyIsSuccessful(reply, entry.message)) {
|
|
368
383
|
succeeded.push(cid);
|
|
369
384
|
} else if (isPermanentPushFailure(reply)) {
|
|
370
385
|
// Permanent failures (400/401/403) will never succeed — do NOT retry.
|