@enbox/agent 0.6.1 → 0.6.3
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 +7 -7
- package/dist/browser.mjs.map +3 -3
- package/dist/esm/hd-identity-vault.js +49 -0
- package/dist/esm/hd-identity-vault.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- 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/hd-identity-vault.d.ts +26 -0
- package/dist/types/hd-identity-vault.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.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/dist/types/types/identity-vault.d.ts +20 -0
- package/dist/types/types/identity-vault.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/hd-identity-vault.ts +50 -0
- package/src/index.ts +1 -0
- 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
- package/src/types/identity-vault.ts +18 -0
|
@@ -13,9 +13,13 @@ export type SyncMessageEntry = {
|
|
|
13
13
|
* 202: message was successfully written to the remote DWN
|
|
14
14
|
* 204: an initial write message was written without any data
|
|
15
15
|
* 409: message was already present on the remote DWN
|
|
16
|
-
*
|
|
16
|
+
*
|
|
17
|
+
* When the *pushed* message is known (e.g. during push-sync), pass it as the
|
|
18
|
+
* second argument so that RecordsDelete + 404 ("initial write was not found or
|
|
19
|
+
* already deleted") can be detected. The DWN's 404 reply omits `entry`, so
|
|
20
|
+
* checking `reply.entry` alone is insufficient.
|
|
17
21
|
*/
|
|
18
|
-
export declare function syncMessageReplyIsSuccessful(reply: UnionMessageReply): boolean;
|
|
22
|
+
export declare function syncMessageReplyIsSuccessful(reply: UnionMessageReply, pushedMessage?: GenericMessage): boolean;
|
|
19
23
|
/**
|
|
20
24
|
* Determines whether a failed push reply represents a permanent failure that
|
|
21
25
|
* should NOT be retried. Permanent failures include protocol violations (400),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-messages.d.ts","sourceRoot":"","sources":["../../src/sync-messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAErH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAwB,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAWxE,kFAAkF;AAClF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACxC,8FAA8F;IAC9F,YAAY,CAAC,EAAE,UAAU,CAAC;CAC3B,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"sync-messages.d.ts","sourceRoot":"","sources":["../../src/sync-messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAErH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAwB,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAWxE,kFAAkF;AAClF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACxC,8FAA8F;IAC9F,YAAY,CAAC,EAAE,UAAU,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,iBAAiB,EAAE,aAAa,CAAC,EAAE,cAAc,GAAG,OAAO,CAoB9G;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAIxE;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAM5E;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IACzH,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,gGAAgG;IAChG,UAAU,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACrC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,cAAc,EAAE,cAAc,CAAC;CAChC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA4FpB;AA4DD;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IACpH,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,cAAc,EAAE,cAAc,CAAC;CAChC,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAoE9B;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IAC7G,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,cAAc,EAAE,cAAc,CAAC;CAChC,GAAG,OAAO,CAAC,UAAU,CAAC,CA2DtB;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IAC1G,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,cAAc,EAAE,cAAc,CAAC;CAChC,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAmCxC"}
|
|
@@ -111,6 +111,26 @@ export interface IdentityVault<T extends Record<string, any> = {
|
|
|
111
111
|
unlock(params: {
|
|
112
112
|
password: string;
|
|
113
113
|
}): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Encrypts arbitrary data using the vault's content encryption key.
|
|
116
|
+
* The vault must be unlocked.
|
|
117
|
+
*
|
|
118
|
+
* @returns A compact JWE string that can be safely stored in untrusted storage.
|
|
119
|
+
* @throws An error if the vault is locked.
|
|
120
|
+
*/
|
|
121
|
+
encryptData(params: {
|
|
122
|
+
plaintext: Uint8Array;
|
|
123
|
+
}): Promise<string>;
|
|
124
|
+
/**
|
|
125
|
+
* Decrypts data that was previously encrypted with {@link encryptData}.
|
|
126
|
+
* The vault must be unlocked.
|
|
127
|
+
*
|
|
128
|
+
* @returns The original plaintext bytes.
|
|
129
|
+
* @throws An error if the vault is locked or the JWE is invalid.
|
|
130
|
+
*/
|
|
131
|
+
decryptData(params: {
|
|
132
|
+
jwe: string;
|
|
133
|
+
}): Promise<Uint8Array>;
|
|
114
134
|
}
|
|
115
135
|
export type IdentityVaultStatus = {
|
|
116
136
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity-vault.d.ts","sourceRoot":"","sources":["../../../src/types/identity-vault.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;IAEpB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IAEb,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,yEAAyE;IACzE,GAAG,EAAE,MAAM,CAAC;IAEZ,mFAAmF;IACnF,oBAAoB,EAAE,MAAM,CAAC;IAE7B,qFAAqF;IACrF,MAAM,EAAE,mBAAmB,CAAC;CAC7B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC,+FAA+F;IAC/F,KAAK,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;IAAE,gBAAgB,EAAE,GAAG,CAAA;CAAE;IACtF;;;;;OAKG;IACH,MAAM,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAEvC;;;;;;;OAOG;IACH,cAAc,CAAC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpF;;OAEG;IACH,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC,CAAA;IAE5B;;;OAGG;IACH,SAAS,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAA;IAEzC;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAEzE;;OAEG;IACH,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAElC;;OAEG;IACH,QAAQ,IAAI,OAAO,CAAC;IAEpB;;OAEG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,mBAAmB,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElF;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"identity-vault.d.ts","sourceRoot":"","sources":["../../../src/types/identity-vault.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;IAEpB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IAEb,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,yEAAyE;IACzE,GAAG,EAAE,MAAM,CAAC;IAEZ,mFAAmF;IACnF,oBAAoB,EAAE,MAAM,CAAC;IAE7B,qFAAqF;IACrF,MAAM,EAAE,mBAAmB,CAAC;CAC7B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC,+FAA+F;IAC/F,KAAK,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;IAAE,gBAAgB,EAAE,GAAG,CAAA;CAAE;IACtF;;;;;OAKG;IACH,MAAM,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAEvC;;;;;;;OAOG;IACH,cAAc,CAAC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpF;;OAEG;IACH,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC,CAAA;IAE5B;;;OAGG;IACH,SAAS,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAA;IAEzC;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAEzE;;OAEG;IACH,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAElC;;OAEG;IACH,QAAQ,IAAI,OAAO,CAAC;IAEpB;;OAEG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,mBAAmB,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElF;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpD;;;;;;OAMG;IACH,WAAW,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,UAAU,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEhE;;;;;;OAMG;IACH,WAAW,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC3D;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1B;;OAEG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC"}
|
package/package.json
CHANGED
package/src/hd-identity-vault.ts
CHANGED
|
@@ -792,6 +792,56 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
|
|
|
792
792
|
}
|
|
793
793
|
}
|
|
794
794
|
|
|
795
|
+
/**
|
|
796
|
+
* Encrypts arbitrary data using the vault's content encryption key (CEK).
|
|
797
|
+
*
|
|
798
|
+
* The vault must be unlocked. The returned compact JWE string can be safely
|
|
799
|
+
* stored in untrusted storage (e.g. `localStorage`). Only the vault password
|
|
800
|
+
* can decrypt the data.
|
|
801
|
+
*
|
|
802
|
+
* @param params.plaintext - The data to encrypt.
|
|
803
|
+
* @returns A compact JWE string.
|
|
804
|
+
* @throws If the vault is locked.
|
|
805
|
+
*/
|
|
806
|
+
public async encryptData({ plaintext }: { plaintext: Uint8Array }): Promise<string> {
|
|
807
|
+
if (this.isLocked() || !this._contentEncryptionKey) {
|
|
808
|
+
throw new Error('HdIdentityVault: Cannot encrypt data — vault is locked.');
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return CompactJwe.encrypt({
|
|
812
|
+
key : this._contentEncryptionKey,
|
|
813
|
+
plaintext,
|
|
814
|
+
protectedHeader : { alg: 'dir', enc: 'A256GCM' },
|
|
815
|
+
crypto : this.crypto,
|
|
816
|
+
keyManager : new LocalKeyManager(),
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Decrypts data that was previously encrypted with {@link encryptData}.
|
|
822
|
+
*
|
|
823
|
+
* The vault must be unlocked.
|
|
824
|
+
*
|
|
825
|
+
* @param params.jwe - The compact JWE string to decrypt.
|
|
826
|
+
* @returns The original plaintext bytes.
|
|
827
|
+
* @throws If the vault is locked or the JWE is invalid.
|
|
828
|
+
*/
|
|
829
|
+
public async decryptData({ jwe }: { jwe: string }): Promise<Uint8Array> {
|
|
830
|
+
if (this.isLocked() || !this._contentEncryptionKey) {
|
|
831
|
+
throw new Error('HdIdentityVault: Cannot decrypt data — vault is locked.');
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
const { plaintext } = await CompactJwe.decrypt({
|
|
835
|
+
jwe,
|
|
836
|
+
key : this._contentEncryptionKey,
|
|
837
|
+
crypto : this.crypto,
|
|
838
|
+
keyManager : new LocalKeyManager(),
|
|
839
|
+
options : { minP2cCount: 1 },
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
return plaintext;
|
|
843
|
+
}
|
|
844
|
+
|
|
795
845
|
/**
|
|
796
846
|
* Retrieves the Decentralized Identifier (DID) associated with the identity vault from the vault
|
|
797
847
|
* store.
|
package/src/index.ts
CHANGED
package/src/store-data.ts
CHANGED
|
@@ -291,7 +291,13 @@ export class DwnDataStore<TStoreObject extends Record<string, any> = Jwk> implem
|
|
|
291
291
|
|
|
292
292
|
// Read the record from the store. If encryption is active for this tenant,
|
|
293
293
|
// the agent's auto-decryption pipeline handles ECIES key unwrapping and AES decryption.
|
|
294
|
-
|
|
294
|
+
//
|
|
295
|
+
// When the protocol definition declares `encryptionRequired: true`, always
|
|
296
|
+
// request decryption — even if `initialize()` has not been called for this
|
|
297
|
+
// tenant yet. This covers delegate sessions where key records arrive via
|
|
298
|
+
// sync (`processRawMessage`) rather than `set()`, leaving the per-tenant
|
|
299
|
+
// `_tenantEncryptionActive` cache cold after an agent restart.
|
|
300
|
+
const encryptionActive = this.encryptionRequired || this.isEncryptionActive(tenantDid);
|
|
295
301
|
const { reply: readReply } = await agent.dwn.processRequest({
|
|
296
302
|
author : tenantDid,
|
|
297
303
|
target : tenantDid,
|
|
@@ -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.
|
|
@@ -117,6 +117,24 @@ export interface IdentityVault<T extends Record<string, any> = { InitializeResul
|
|
|
117
117
|
* @throws An error if the password is incorrect.
|
|
118
118
|
*/
|
|
119
119
|
unlock(params: { password: string }): Promise<void>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Encrypts arbitrary data using the vault's content encryption key.
|
|
123
|
+
* The vault must be unlocked.
|
|
124
|
+
*
|
|
125
|
+
* @returns A compact JWE string that can be safely stored in untrusted storage.
|
|
126
|
+
* @throws An error if the vault is locked.
|
|
127
|
+
*/
|
|
128
|
+
encryptData(params: { plaintext: Uint8Array }): Promise<string>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Decrypts data that was previously encrypted with {@link encryptData}.
|
|
132
|
+
* The vault must be unlocked.
|
|
133
|
+
*
|
|
134
|
+
* @returns The original plaintext bytes.
|
|
135
|
+
* @throws An error if the vault is locked or the JWE is invalid.
|
|
136
|
+
*/
|
|
137
|
+
decryptData(params: { jwe: string }): Promise<Uint8Array>;
|
|
120
138
|
}
|
|
121
139
|
|
|
122
140
|
export type IdentityVaultStatus = {
|