@interop/wallet-core 0.6.0 → 0.7.0
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/index.d.ts +7 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/markers/acquire.d.ts +107 -0
- package/dist/markers/acquire.d.ts.map +1 -0
- package/dist/markers/acquire.js +83 -0
- package/dist/markers/acquire.js.map +1 -0
- package/dist/markers/cipher.d.ts +56 -0
- package/dist/markers/cipher.d.ts.map +1 -0
- package/dist/markers/cipher.js +73 -0
- package/dist/markers/cipher.js.map +1 -0
- package/dist/markers/index.d.ts +30 -0
- package/dist/markers/index.d.ts.map +1 -0
- package/dist/markers/index.js +29 -0
- package/dist/markers/index.js.map +1 -0
- package/dist/markers/refresh.d.ts +60 -0
- package/dist/markers/refresh.d.ts.map +1 -0
- package/dist/markers/refresh.js +67 -0
- package/dist/markers/refresh.js.map +1 -0
- package/dist/recovery/index.d.ts +31 -0
- package/dist/recovery/index.d.ts.map +1 -0
- package/dist/recovery/index.js +28 -0
- package/dist/recovery/index.js.map +1 -0
- package/dist/recovery/recoveryCode.d.ts +105 -0
- package/dist/recovery/recoveryCode.d.ts.map +1 -0
- package/dist/recovery/recoveryCode.js +155 -0
- package/dist/recovery/recoveryCode.js.map +1 -0
- package/dist/recovery/recoveryRecord.d.ts +75 -0
- package/dist/recovery/recoveryRecord.d.ts.map +1 -0
- package/dist/recovery/recoveryRecord.js +89 -0
- package/dist/recovery/recoveryRecord.js.map +1 -0
- package/dist/recovery/recoveryWebvh.d.ts +126 -0
- package/dist/recovery/recoveryWebvh.d.ts.map +1 -0
- package/dist/recovery/recoveryWebvh.js +379 -0
- package/dist/recovery/recoveryWebvh.js.map +1 -0
- package/dist/webvh/didWebvh.d.ts +79 -0
- package/dist/webvh/didWebvh.d.ts.map +1 -1
- package/dist/webvh/didWebvh.js +5 -5
- package/dist/webvh/didWebvh.js.map +1 -1
- package/package.json +13 -1
package/dist/index.d.ts
CHANGED
|
@@ -21,16 +21,19 @@
|
|
|
21
21
|
* entries, and ZCap signing under the did:webvh verification-method id.
|
|
22
22
|
* - `@interop/wallet-core/keys` -- the per-user key (PUK) and its
|
|
23
23
|
* `key-map/puk.json` wrap-set roster.
|
|
24
|
+
* - `@interop/wallet-core/markers` -- collection-encryption marker
|
|
25
|
+
* acquisition (fetch / cache / offline fallback) and the unknown-epoch
|
|
26
|
+
* refresh policy, including a self-refreshing EDV document cipher.
|
|
24
27
|
* - `@interop/wallet-core/keyring` -- the unlock layer: the unlock derivation,
|
|
25
28
|
* the account-pointer record codec, and the unlock Space.
|
|
26
29
|
* - `@interop/wallet-core/enrollment` -- the client enrollment ceremony
|
|
27
30
|
* (connect code, approval, completion).
|
|
28
31
|
*
|
|
29
32
|
* This root re-exports `sync` and `space` for convenience. `identity`,
|
|
30
|
-
* `request`, `display`, `webvh`, `keys`, `keyring`, and
|
|
31
|
-
* deliberately NOT re-exported here, so plaintext consumers
|
|
32
|
-
* pull the signing / KMS / document-loader dependency graph
|
|
33
|
-
* subpath-isolation pattern).
|
|
33
|
+
* `request`, `display`, `webvh`, `keys`, `markers`, `keyring`, and
|
|
34
|
+
* `enrollment` are deliberately NOT re-exported here, so plaintext consumers
|
|
35
|
+
* of the root never pull the signing / KMS / document-loader dependency graph
|
|
36
|
+
* (the was-client subpath-isolation pattern).
|
|
34
37
|
*/
|
|
35
38
|
export * from './sync/index.js';
|
|
36
39
|
export * from './space/index.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -21,16 +21,19 @@
|
|
|
21
21
|
* entries, and ZCap signing under the did:webvh verification-method id.
|
|
22
22
|
* - `@interop/wallet-core/keys` -- the per-user key (PUK) and its
|
|
23
23
|
* `key-map/puk.json` wrap-set roster.
|
|
24
|
+
* - `@interop/wallet-core/markers` -- collection-encryption marker
|
|
25
|
+
* acquisition (fetch / cache / offline fallback) and the unknown-epoch
|
|
26
|
+
* refresh policy, including a self-refreshing EDV document cipher.
|
|
24
27
|
* - `@interop/wallet-core/keyring` -- the unlock layer: the unlock derivation,
|
|
25
28
|
* the account-pointer record codec, and the unlock Space.
|
|
26
29
|
* - `@interop/wallet-core/enrollment` -- the client enrollment ceremony
|
|
27
30
|
* (connect code, approval, completion).
|
|
28
31
|
*
|
|
29
32
|
* This root re-exports `sync` and `space` for convenience. `identity`,
|
|
30
|
-
* `request`, `display`, `webvh`, `keys`, `keyring`, and
|
|
31
|
-
* deliberately NOT re-exported here, so plaintext consumers
|
|
32
|
-
* pull the signing / KMS / document-loader dependency graph
|
|
33
|
-
* subpath-isolation pattern).
|
|
33
|
+
* `request`, `display`, `webvh`, `keys`, `markers`, `keyring`, and
|
|
34
|
+
* `enrollment` are deliberately NOT re-exported here, so plaintext consumers
|
|
35
|
+
* of the root never pull the signing / KMS / document-loader dependency graph
|
|
36
|
+
* (the was-client subpath-isolation pattern).
|
|
34
37
|
*/
|
|
35
38
|
export * from './sync/index.js';
|
|
36
39
|
export * from './space/index.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Collection-encryption marker acquisition: reading a collection's
|
|
6
|
+
* `CollectionEncryption` marker (its key-epoch roster) from the Collection
|
|
7
|
+
* Description, caching each success, and falling back to the cached copy when
|
|
8
|
+
* the description cannot be fetched -- offline, a previously-shared collection
|
|
9
|
+
* must keep encrypting under its current epoch. A successful fetch that
|
|
10
|
+
* returns no marker (an unshared collection) yields `undefined`: the
|
|
11
|
+
* single-key path.
|
|
12
|
+
*
|
|
13
|
+
* The two seams are deliberately narrow, so a wallet app's own classes satisfy
|
|
14
|
+
* them structurally -- no adapter needed. A {@link MarkerSource} is one signed
|
|
15
|
+
* describe; a {@link MarkerCache} is a durable get/put the host has already
|
|
16
|
+
* scoped to one account's Space (a web wallet: a localStorage pair keyed by
|
|
17
|
+
* Space id; a mobile wallet: a per-(profile, collection) table column), so no
|
|
18
|
+
* scope key appears in the interface.
|
|
19
|
+
*/
|
|
20
|
+
import type { CollectionEncryption, WasClient } from '@interop/was-client';
|
|
21
|
+
/**
|
|
22
|
+
* Where markers come from: one signed read of the collection's Description.
|
|
23
|
+
* Resolves `undefined` for a collection that is plaintext or has no marker;
|
|
24
|
+
* network errors throw through (callers treat the fetch as best-effort and
|
|
25
|
+
* fall back to a cached copy).
|
|
26
|
+
*/
|
|
27
|
+
export interface MarkerSource {
|
|
28
|
+
collectionEncryption(options: {
|
|
29
|
+
collectionId: string;
|
|
30
|
+
}): Promise<CollectionEncryption | undefined>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Where fetched markers survive offline: a durable get/put pre-scoped by the
|
|
34
|
+
* host to one account's Space. `readMarker` resolves `undefined` when nothing
|
|
35
|
+
* is cached (never throws for absence).
|
|
36
|
+
*/
|
|
37
|
+
export interface MarkerCache {
|
|
38
|
+
readMarker(options: {
|
|
39
|
+
collectionId: string;
|
|
40
|
+
}): Promise<CollectionEncryption | undefined>;
|
|
41
|
+
writeMarker(options: {
|
|
42
|
+
collectionId: string;
|
|
43
|
+
marker: CollectionEncryption;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The {@link MarkerSource} over a was-client handle: reads the collection's
|
|
48
|
+
* Description in the given Space and returns its `encryption` marker.
|
|
49
|
+
*
|
|
50
|
+
* @param options {object}
|
|
51
|
+
* @param options.was {WasClient} a client whose signer can read the Space
|
|
52
|
+
* @param options.spaceId {string}
|
|
53
|
+
* @returns {MarkerSource}
|
|
54
|
+
*/
|
|
55
|
+
export declare function wasMarkerSource({ was, spaceId }: {
|
|
56
|
+
was: WasClient;
|
|
57
|
+
spaceId: string;
|
|
58
|
+
}): MarkerSource;
|
|
59
|
+
/**
|
|
60
|
+
* Acquires one collection's marker: fetches it from the source, caching a
|
|
61
|
+
* success; falls back to the cached copy when the fetch fails; and with no
|
|
62
|
+
* source at all (a purely local code path) reads the cache alone. A
|
|
63
|
+
* successful fetch that returns no marker resolves `undefined` -- an unshared
|
|
64
|
+
* collection stays on the single-key path -- and deliberately leaves any
|
|
65
|
+
* cached copy in place, mirroring the fetch-failure fallback.
|
|
66
|
+
*
|
|
67
|
+
* @param options {object}
|
|
68
|
+
* @param [options.source] {MarkerSource} omit for cache-only acquisition
|
|
69
|
+
* @param options.cache {MarkerCache}
|
|
70
|
+
* @param options.collectionId {string}
|
|
71
|
+
* @param [options.onFetchError] {function} observes a swallowed fetch
|
|
72
|
+
* failure (the cached-fallback branch); errors from the cache itself throw
|
|
73
|
+
* through
|
|
74
|
+
* @returns {Promise<CollectionEncryption | undefined>}
|
|
75
|
+
*/
|
|
76
|
+
export declare function acquireMarker({ source, cache, collectionId, onFetchError }: {
|
|
77
|
+
source?: MarkerSource;
|
|
78
|
+
cache: MarkerCache;
|
|
79
|
+
collectionId: string;
|
|
80
|
+
onFetchError?: (err: unknown, info: {
|
|
81
|
+
collectionId: string;
|
|
82
|
+
}) => void;
|
|
83
|
+
}): Promise<CollectionEncryption | undefined>;
|
|
84
|
+
/**
|
|
85
|
+
* Acquires markers for a set of collections concurrently (each fetch is an
|
|
86
|
+
* independent signed round trip, so a session start is not gated on a serial
|
|
87
|
+
* chain of describes). Collections that resolve no marker are simply absent
|
|
88
|
+
* from the result.
|
|
89
|
+
*
|
|
90
|
+
* @param options {object}
|
|
91
|
+
* @param [options.source] {MarkerSource} omit for cache-only acquisition
|
|
92
|
+
* @param options.cache {MarkerCache}
|
|
93
|
+
* @param options.collectionIds {string[]}
|
|
94
|
+
* @param [options.onFetchError] {function} observes each swallowed fetch
|
|
95
|
+
* failure
|
|
96
|
+
* @returns {Promise<Record<string, CollectionEncryption>>} keyed by
|
|
97
|
+
* collection id
|
|
98
|
+
*/
|
|
99
|
+
export declare function acquireMarkers({ source, cache, collectionIds, onFetchError }: {
|
|
100
|
+
source?: MarkerSource;
|
|
101
|
+
cache: MarkerCache;
|
|
102
|
+
collectionIds: string[];
|
|
103
|
+
onFetchError?: (err: unknown, info: {
|
|
104
|
+
collectionId: string;
|
|
105
|
+
}) => void;
|
|
106
|
+
}): Promise<Record<string, CollectionEncryption>>;
|
|
107
|
+
//# sourceMappingURL=acquire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acquire.d.ts","sourceRoot":"","sources":["../../src/markers/acquire.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE1E;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,oBAAoB,CAAC,OAAO,EAAE;QAC5B,YAAY,EAAE,MAAM,CAAA;KACrB,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAAA;CAC9C;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,OAAO,EAAE;QAClB,YAAY,EAAE,MAAM,CAAA;KACrB,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAAA;IAC7C,WAAW,CAAC,OAAO,EAAE;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,MAAM,EAAE,oBAAoB,CAAA;KAC7B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,EAC9B,GAAG,EACH,OAAO,EACR,EAAE;IACD,GAAG,EAAE,SAAS,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,YAAY,CAUf;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,CAAC,EAClC,MAAM,EACN,KAAK,EACL,YAAY,EACZ,YAAY,EACb,EAAE;IACD,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,KAAK,EAAE,WAAW,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CACtE,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAe5C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAAC,EACnC,MAAM,EACN,KAAK,EACL,aAAa,EACb,YAAY,EACb,EAAE;IACD,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,KAAK,EAAE,WAAW,CAAA;IAClB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CACtE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAiBhD"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The {@link MarkerSource} over a was-client handle: reads the collection's
|
|
3
|
+
* Description in the given Space and returns its `encryption` marker.
|
|
4
|
+
*
|
|
5
|
+
* @param options {object}
|
|
6
|
+
* @param options.was {WasClient} a client whose signer can read the Space
|
|
7
|
+
* @param options.spaceId {string}
|
|
8
|
+
* @returns {MarkerSource}
|
|
9
|
+
*/
|
|
10
|
+
export function wasMarkerSource({ was, spaceId }) {
|
|
11
|
+
return {
|
|
12
|
+
async collectionEncryption({ collectionId }) {
|
|
13
|
+
const description = await was
|
|
14
|
+
.space(spaceId)
|
|
15
|
+
.collection(collectionId)
|
|
16
|
+
.describe();
|
|
17
|
+
return description?.encryption ?? undefined;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Acquires one collection's marker: fetches it from the source, caching a
|
|
23
|
+
* success; falls back to the cached copy when the fetch fails; and with no
|
|
24
|
+
* source at all (a purely local code path) reads the cache alone. A
|
|
25
|
+
* successful fetch that returns no marker resolves `undefined` -- an unshared
|
|
26
|
+
* collection stays on the single-key path -- and deliberately leaves any
|
|
27
|
+
* cached copy in place, mirroring the fetch-failure fallback.
|
|
28
|
+
*
|
|
29
|
+
* @param options {object}
|
|
30
|
+
* @param [options.source] {MarkerSource} omit for cache-only acquisition
|
|
31
|
+
* @param options.cache {MarkerCache}
|
|
32
|
+
* @param options.collectionId {string}
|
|
33
|
+
* @param [options.onFetchError] {function} observes a swallowed fetch
|
|
34
|
+
* failure (the cached-fallback branch); errors from the cache itself throw
|
|
35
|
+
* through
|
|
36
|
+
* @returns {Promise<CollectionEncryption | undefined>}
|
|
37
|
+
*/
|
|
38
|
+
export async function acquireMarker({ source, cache, collectionId, onFetchError }) {
|
|
39
|
+
if (!source) {
|
|
40
|
+
return cache.readMarker({ collectionId });
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const fetched = await source.collectionEncryption({ collectionId });
|
|
44
|
+
if (fetched) {
|
|
45
|
+
await cache.writeMarker({ collectionId, marker: fetched });
|
|
46
|
+
return fetched;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
onFetchError?.(err, { collectionId });
|
|
52
|
+
return cache.readMarker({ collectionId });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Acquires markers for a set of collections concurrently (each fetch is an
|
|
57
|
+
* independent signed round trip, so a session start is not gated on a serial
|
|
58
|
+
* chain of describes). Collections that resolve no marker are simply absent
|
|
59
|
+
* from the result.
|
|
60
|
+
*
|
|
61
|
+
* @param options {object}
|
|
62
|
+
* @param [options.source] {MarkerSource} omit for cache-only acquisition
|
|
63
|
+
* @param options.cache {MarkerCache}
|
|
64
|
+
* @param options.collectionIds {string[]}
|
|
65
|
+
* @param [options.onFetchError] {function} observes each swallowed fetch
|
|
66
|
+
* failure
|
|
67
|
+
* @returns {Promise<Record<string, CollectionEncryption>>} keyed by
|
|
68
|
+
* collection id
|
|
69
|
+
*/
|
|
70
|
+
export async function acquireMarkers({ source, cache, collectionIds, onFetchError }) {
|
|
71
|
+
const resolved = await Promise.all(collectionIds.map(async (collectionId) => [
|
|
72
|
+
collectionId,
|
|
73
|
+
await acquireMarker({ source, cache, collectionId, onFetchError })
|
|
74
|
+
]));
|
|
75
|
+
const markers = {};
|
|
76
|
+
for (const [collectionId, marker] of resolved) {
|
|
77
|
+
if (marker) {
|
|
78
|
+
markers[collectionId] = marker;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return markers;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=acquire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acquire.js","sourceRoot":"","sources":["../../src/markers/acquire.ts"],"names":[],"mappings":"AAgDA;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,GAAG,EACH,OAAO,EAIR;IACC,OAAO;QACL,KAAK,CAAC,oBAAoB,CAAC,EAAE,YAAY,EAAE;YACzC,MAAM,WAAW,GAAG,MAAM,GAAG;iBAC1B,KAAK,CAAC,OAAO,CAAC;iBACd,UAAU,CAAC,YAAY,CAAC;iBACxB,QAAQ,EAAE,CAAA;YACb,OAAO,WAAW,EAAE,UAAU,IAAI,SAAS,CAAA;QAC7C,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAClC,MAAM,EACN,KAAK,EACL,YAAY,EACZ,YAAY,EAMb;IACC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,KAAK,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;IAC3C,CAAC;IACD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;QACnE,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;YAC1D,OAAO,OAAO,CAAA;QAChB,CAAC;QACD,OAAO,SAAS,CAAA;IAClB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,CAAC,CAAA;QACrC,OAAO,KAAK,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;IAC3C,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,EACnC,MAAM,EACN,KAAK,EACL,aAAa,EACb,YAAY,EAMb;IACC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,aAAa,CAAC,GAAG,CACf,KAAK,EAAC,YAAY,EAAC,EAAE,CACnB;QACE,YAAY;QACZ,MAAM,aAAa,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;KAC1D,CACb,CACF,CAAA;IACD,MAAM,OAAO,GAAyC,EAAE,CAAA;IACxD,KAAK,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QAC9C,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,CAAC,YAAY,CAAC,GAAG,MAAM,CAAA;QAChC,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* A self-refreshing EDV document cipher: `createEdvDocCipher` bound to marker
|
|
6
|
+
* acquisition and the once-per-session refresh rule, for a host whose decrypt
|
|
7
|
+
* seam is the cipher itself (a sync engine's `decryptDoc`, a conflict
|
|
8
|
+
* resolver) rather than a row-scanning store.
|
|
9
|
+
*
|
|
10
|
+
* Built, the cipher acquires the collection's marker (fetch, cache the
|
|
11
|
+
* success, cached fallback on failure -- see `acquire.ts`) and constructs the
|
|
12
|
+
* underlying EDV cipher from it; with no marker, or a marker with no epochs,
|
|
13
|
+
* that is the single-key path, unchanged. When a decrypt throws
|
|
14
|
+
* `UnknownEpochError`, the cipher re-acquires the marker, rebuilds itself,
|
|
15
|
+
* and retries that decrypt exactly once -- and only once per cipher instance,
|
|
16
|
+
* which the host scopes to one `(profile, collection)` session by dropping
|
|
17
|
+
* its cipher cache when the session ends. A second failure (or any unknown
|
|
18
|
+
* epoch after the one refresh is spent) propagates.
|
|
19
|
+
*/
|
|
20
|
+
import type { IKeyAgreementKey, IKeyResolver } from '@interop/data-integrity-core';
|
|
21
|
+
import { type DocCipher } from '@interop/was-client/edv';
|
|
22
|
+
import { type MarkerCache, type MarkerSource } from './acquire.js';
|
|
23
|
+
/**
|
|
24
|
+
* Builds a {@link DocCipher} whose marker is acquired through the
|
|
25
|
+
* source/cache seams and refreshed (once per instance) on an unknown-epoch
|
|
26
|
+
* decrypt.
|
|
27
|
+
*
|
|
28
|
+
* With no `source` the marker is served from the cache alone and the refresh
|
|
29
|
+
* path is inert (an unknown-epoch decrypt propagates immediately) -- the shape
|
|
30
|
+
* for a purely local code path that must never touch the network.
|
|
31
|
+
*
|
|
32
|
+
* @param options {object}
|
|
33
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the vault key pair this
|
|
34
|
+
* collection's envelopes are sealed to
|
|
35
|
+
* @param options.keyResolver {IKeyResolver}
|
|
36
|
+
* @param options.collectionId {string}
|
|
37
|
+
* @param [options.idDerivation] {'content' | 'random'} defaults to
|
|
38
|
+
* `'content'`
|
|
39
|
+
* @param [options.source] {MarkerSource}
|
|
40
|
+
* @param options.cache {MarkerCache}
|
|
41
|
+
* @param [options.onFetchError] {function} observes swallowed marker-fetch
|
|
42
|
+
* failures
|
|
43
|
+
* @returns {Promise<DocCipher>}
|
|
44
|
+
*/
|
|
45
|
+
export declare function createRefreshingEdvDocCipher({ keyAgreementKey, keyResolver, collectionId, idDerivation, source, cache, onFetchError }: {
|
|
46
|
+
keyAgreementKey: IKeyAgreementKey;
|
|
47
|
+
keyResolver: IKeyResolver;
|
|
48
|
+
collectionId: string;
|
|
49
|
+
idDerivation?: 'content' | 'random';
|
|
50
|
+
source?: MarkerSource;
|
|
51
|
+
cache: MarkerCache;
|
|
52
|
+
onFetchError?: (err: unknown, info: {
|
|
53
|
+
collectionId: string;
|
|
54
|
+
}) => void;
|
|
55
|
+
}): Promise<DocCipher>;
|
|
56
|
+
//# sourceMappingURL=cipher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cipher.d.ts","sourceRoot":"","sources":["../../src/markers/cipher.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACb,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAA;AAErB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,4BAA4B,CAAC,EACjD,eAAe,EACf,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,MAAM,EACN,KAAK,EACL,YAAY,EACb,EAAE;IACD,eAAe,EAAE,gBAAgB,CAAA;IACjC,WAAW,EAAE,YAAY,CAAA;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACnC,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,KAAK,EAAE,WAAW,CAAA;IAClB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CACtE,GAAG,OAAO,CAAC,SAAS,CAAC,CAqDrB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createEdvDocCipher, UnknownEpochError } from '@interop/was-client/edv';
|
|
2
|
+
import { acquireMarker } from './acquire.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds a {@link DocCipher} whose marker is acquired through the
|
|
5
|
+
* source/cache seams and refreshed (once per instance) on an unknown-epoch
|
|
6
|
+
* decrypt.
|
|
7
|
+
*
|
|
8
|
+
* With no `source` the marker is served from the cache alone and the refresh
|
|
9
|
+
* path is inert (an unknown-epoch decrypt propagates immediately) -- the shape
|
|
10
|
+
* for a purely local code path that must never touch the network.
|
|
11
|
+
*
|
|
12
|
+
* @param options {object}
|
|
13
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the vault key pair this
|
|
14
|
+
* collection's envelopes are sealed to
|
|
15
|
+
* @param options.keyResolver {IKeyResolver}
|
|
16
|
+
* @param options.collectionId {string}
|
|
17
|
+
* @param [options.idDerivation] {'content' | 'random'} defaults to
|
|
18
|
+
* `'content'`
|
|
19
|
+
* @param [options.source] {MarkerSource}
|
|
20
|
+
* @param options.cache {MarkerCache}
|
|
21
|
+
* @param [options.onFetchError] {function} observes swallowed marker-fetch
|
|
22
|
+
* failures
|
|
23
|
+
* @returns {Promise<DocCipher>}
|
|
24
|
+
*/
|
|
25
|
+
export async function createRefreshingEdvDocCipher({ keyAgreementKey, keyResolver, collectionId, idDerivation, source, cache, onFetchError }) {
|
|
26
|
+
const build = async () => createEdvDocCipher({
|
|
27
|
+
keyAgreementKey,
|
|
28
|
+
keyResolver,
|
|
29
|
+
collectionId,
|
|
30
|
+
idDerivation,
|
|
31
|
+
encryption: await acquireMarker({
|
|
32
|
+
source,
|
|
33
|
+
cache,
|
|
34
|
+
collectionId,
|
|
35
|
+
onFetchError
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
let inner = await build();
|
|
39
|
+
// The one marker refresh this cipher instance (= this collection this
|
|
40
|
+
// session) may spend, shared so concurrent unknown-epoch decrypts ride a
|
|
41
|
+
// single re-read instead of each spending one.
|
|
42
|
+
let refreshed = null;
|
|
43
|
+
return {
|
|
44
|
+
encrypt: options => inner.encrypt(options),
|
|
45
|
+
encryptUpdate: options => {
|
|
46
|
+
if (!inner.encryptUpdate) {
|
|
47
|
+
throw new Error(`Collection "${collectionId}" cipher has no in-place update.`);
|
|
48
|
+
}
|
|
49
|
+
return inner.encryptUpdate(options);
|
|
50
|
+
},
|
|
51
|
+
async decrypt({ envelope }) {
|
|
52
|
+
try {
|
|
53
|
+
return await inner.decrypt({ envelope });
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (!(err instanceof UnknownEpochError) || !source) {
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
refreshed ??= build().then(cipher => {
|
|
60
|
+
inner = cipher;
|
|
61
|
+
});
|
|
62
|
+
await refreshed;
|
|
63
|
+
// One retry under the swapped cipher. If the refresh was already
|
|
64
|
+
// spent before this decrypt began, this re-attempt is a local
|
|
65
|
+
// no-network decrypt that fails the same way -- so a genuinely
|
|
66
|
+
// foreign envelope still surfaces UnknownEpochError, and never a
|
|
67
|
+
// second description read.
|
|
68
|
+
return inner.decrypt({ envelope });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=cipher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cipher.js","sourceRoot":"","sources":["../../src/markers/cipher.ts"],"names":[],"mappings":"AAuBA,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EAElB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,aAAa,EAGd,MAAM,cAAc,CAAA;AAErB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,EACjD,eAAe,EACf,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,MAAM,EACN,KAAK,EACL,YAAY,EASb;IACC,MAAM,KAAK,GAAG,KAAK,IAAwB,EAAE,CAC3C,kBAAkB,CAAC;QACjB,eAAe;QACf,WAAW;QACX,YAAY;QACZ,YAAY;QACZ,UAAU,EAAE,MAAM,aAAa,CAAC;YAC9B,MAAM;YACN,KAAK;YACL,YAAY;YACZ,YAAY;SACb,CAAC;KACH,CAAC,CAAA;IAEJ,IAAI,KAAK,GAAG,MAAM,KAAK,EAAE,CAAA;IACzB,sEAAsE;IACtE,yEAAyE;IACzE,+CAA+C;IAC/C,IAAI,SAAS,GAAyB,IAAI,CAAA;IAE1C,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAE1C,aAAa,EAAE,OAAO,CAAC,EAAE;YACvB,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;gBACzB,MAAM,IAAI,KAAK,CACb,eAAe,YAAY,kCAAkC,CAC9D,CAAA;YACH,CAAC;YACD,OAAO,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;QACrC,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE;YACxB,IAAI,CAAC;gBACH,OAAO,MAAM,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAA;YAC1C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,CAAC,GAAG,YAAY,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;oBACnD,MAAM,GAAG,CAAA;gBACX,CAAC;gBACD,SAAS,KAAK,KAAK,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;oBAClC,KAAK,GAAG,MAAM,CAAA;gBAChB,CAAC,CAAC,CAAA;gBACF,MAAM,SAAS,CAAA;gBACf,iEAAiE;gBACjE,8DAA8D;gBAC9D,+DAA+D;gBAC/D,iEAAiE;gBACjE,2BAA2B;gBAC3B,OAAO,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAA;YACpC,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The `@interop/wallet-core/markers` subpath: collection-encryption marker
|
|
6
|
+
* acquisition and the unknown-epoch refresh policy -- the one implementation
|
|
7
|
+
* of "which key epoch does this collection encrypt under, and when do we ask
|
|
8
|
+
* again" that every wallet replica must share (a drift here does not fail
|
|
9
|
+
* loudly; it fails as a resource one replica cannot decrypt).
|
|
10
|
+
*
|
|
11
|
+
* - `MarkerSource` / `MarkerCache` -- the narrow seams a host implements:
|
|
12
|
+
* one signed Collection Description read, and a durable get/put pre-scoped
|
|
13
|
+
* to one account's Space.
|
|
14
|
+
* - `wasMarkerSource` -- the `MarkerSource` over a was-client handle.
|
|
15
|
+
* - `acquireMarker` / `acquireMarkers` -- fetch + cache with the cached
|
|
16
|
+
* fallback (offline, a previously-shared collection keeps encrypting under
|
|
17
|
+
* its current epoch; no marker at all is the single-key path).
|
|
18
|
+
* - `MarkerRefreshPolicy` -- the once-per-collection-per-session unknown-epoch
|
|
19
|
+
* refresh guard, plus the refresh-and-re-read-once wrapper for hosts whose
|
|
20
|
+
* reads scan rows and count unknown-epoch skips.
|
|
21
|
+
* - `createRefreshingEdvDocCipher` -- `createEdvDocCipher` bound to both: a
|
|
22
|
+
* cipher that acquires its own marker and, on an unknown-epoch decrypt,
|
|
23
|
+
* re-reads the description, swaps itself, and retries exactly once per
|
|
24
|
+
* instance.
|
|
25
|
+
*/
|
|
26
|
+
export { acquireMarker, acquireMarkers, wasMarkerSource } from './acquire.js';
|
|
27
|
+
export type { MarkerCache, MarkerSource } from './acquire.js';
|
|
28
|
+
export { MarkerRefreshPolicy } from './refresh.js';
|
|
29
|
+
export { createRefreshingEdvDocCipher } from './cipher.js';
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/markers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC7E,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The `@interop/wallet-core/markers` subpath: collection-encryption marker
|
|
6
|
+
* acquisition and the unknown-epoch refresh policy -- the one implementation
|
|
7
|
+
* of "which key epoch does this collection encrypt under, and when do we ask
|
|
8
|
+
* again" that every wallet replica must share (a drift here does not fail
|
|
9
|
+
* loudly; it fails as a resource one replica cannot decrypt).
|
|
10
|
+
*
|
|
11
|
+
* - `MarkerSource` / `MarkerCache` -- the narrow seams a host implements:
|
|
12
|
+
* one signed Collection Description read, and a durable get/put pre-scoped
|
|
13
|
+
* to one account's Space.
|
|
14
|
+
* - `wasMarkerSource` -- the `MarkerSource` over a was-client handle.
|
|
15
|
+
* - `acquireMarker` / `acquireMarkers` -- fetch + cache with the cached
|
|
16
|
+
* fallback (offline, a previously-shared collection keeps encrypting under
|
|
17
|
+
* its current epoch; no marker at all is the single-key path).
|
|
18
|
+
* - `MarkerRefreshPolicy` -- the once-per-collection-per-session unknown-epoch
|
|
19
|
+
* refresh guard, plus the refresh-and-re-read-once wrapper for hosts whose
|
|
20
|
+
* reads scan rows and count unknown-epoch skips.
|
|
21
|
+
* - `createRefreshingEdvDocCipher` -- `createEdvDocCipher` bound to both: a
|
|
22
|
+
* cipher that acquires its own marker and, on an unknown-epoch decrypt,
|
|
23
|
+
* re-reads the description, swaps itself, and retries exactly once per
|
|
24
|
+
* instance.
|
|
25
|
+
*/
|
|
26
|
+
export { acquireMarker, acquireMarkers, wasMarkerSource } from './acquire.js';
|
|
27
|
+
export { MarkerRefreshPolicy } from './refresh.js';
|
|
28
|
+
export { createRefreshingEdvDocCipher } from './cipher.js';
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/markers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAG7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The unknown-epoch refresh policy: an epoch rotation emits no change-feed
|
|
6
|
+
* entry, so a cipher built from a cached marker can meet envelopes stamped
|
|
7
|
+
* with an epoch it has never seen. The remedy is one re-read of the
|
|
8
|
+
* Collection Description plus a cipher rebuild and a single retry -- and the
|
|
9
|
+
* policy guards that remedy to ONCE per collection per session, so a
|
|
10
|
+
* genuinely foreign envelope (one no marker will ever route) cannot drive a
|
|
11
|
+
* refetch loop, let alone a refetch per resource.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Tracks which collections have already spent their one refresh this session,
|
|
15
|
+
* and runs reads under the refresh-and-retry-once rule. One instance per
|
|
16
|
+
* session (it IS the session scope of the guard); the injected `refresh` does
|
|
17
|
+
* the host's whole swap -- re-acquire the marker(s), rebuild the cipher(s),
|
|
18
|
+
* and install them wherever the host keeps them.
|
|
19
|
+
*/
|
|
20
|
+
export declare class MarkerRefreshPolicy {
|
|
21
|
+
#private;
|
|
22
|
+
constructor({ refresh }: {
|
|
23
|
+
refresh: (options: {
|
|
24
|
+
collectionId: string;
|
|
25
|
+
}) => Promise<void>;
|
|
26
|
+
});
|
|
27
|
+
/** Whether a collection still has its one refresh this session. */
|
|
28
|
+
shouldRefresh({ collectionId }: {
|
|
29
|
+
collectionId: string;
|
|
30
|
+
}): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Runs a read that reports whether it skipped unknown-epoch rows; on the
|
|
33
|
+
* first such report for a collection this session, spends the collection's
|
|
34
|
+
* refresh (marker re-read + cipher swap, via the injected `refresh`) and
|
|
35
|
+
* re-reads once. A later unknown-epoch report for the same collection
|
|
36
|
+
* returns the read's value as-is.
|
|
37
|
+
*
|
|
38
|
+
* @param options {object}
|
|
39
|
+
* @param options.collectionId {string}
|
|
40
|
+
* @param options.read {function} the read, reporting `unknownEpoch`
|
|
41
|
+
* @returns {Promise<T>} the (possibly re-read) value
|
|
42
|
+
*/
|
|
43
|
+
readWithRefresh<T>({ collectionId, read }: {
|
|
44
|
+
collectionId: string;
|
|
45
|
+
read: () => Promise<{
|
|
46
|
+
value: T;
|
|
47
|
+
unknownEpoch: boolean;
|
|
48
|
+
}>;
|
|
49
|
+
}): Promise<T>;
|
|
50
|
+
/**
|
|
51
|
+
* Re-arms the guard -- for one collection, or (with no argument) for all.
|
|
52
|
+
* Call when a fresh marker is installed by some other path (a share,
|
|
53
|
+
* unshare, or recipient rotation this session performed itself), since the
|
|
54
|
+
* next unknown-epoch read is then evidence of a NEW rotation elsewhere.
|
|
55
|
+
*/
|
|
56
|
+
reset(options?: {
|
|
57
|
+
collectionId?: string;
|
|
58
|
+
}): void;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.d.ts","sourceRoot":"","sources":["../../src/markers/refresh.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,qBAAa,mBAAmB;;gBAKlB,EACV,OAAO,EACR,EAAE;QACD,OAAO,EAAE,CAAC,OAAO,EAAE;YAAE,YAAY,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KAC9D;IAID,mEAAmE;IACnE,aAAa,CAAC,EAAE,YAAY,EAAE,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO;IAIlE;;;;;;;;;;;OAWG;IACG,eAAe,CAAC,CAAC,EAAE,EACvB,YAAY,EACZ,IAAI,EACL,EAAE;QACD,YAAY,EAAE,MAAM,CAAA;QACpB,IAAI,EAAE,MAAM,OAAO,CAAC;YAAE,KAAK,EAAE,CAAC,CAAC;YAAC,YAAY,EAAE,OAAO,CAAA;SAAE,CAAC,CAAA;KACzD,GAAG,OAAO,CAAC,CAAC,CAAC;IAUd;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;CAOjD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The unknown-epoch refresh policy: an epoch rotation emits no change-feed
|
|
6
|
+
* entry, so a cipher built from a cached marker can meet envelopes stamped
|
|
7
|
+
* with an epoch it has never seen. The remedy is one re-read of the
|
|
8
|
+
* Collection Description plus a cipher rebuild and a single retry -- and the
|
|
9
|
+
* policy guards that remedy to ONCE per collection per session, so a
|
|
10
|
+
* genuinely foreign envelope (one no marker will ever route) cannot drive a
|
|
11
|
+
* refetch loop, let alone a refetch per resource.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Tracks which collections have already spent their one refresh this session,
|
|
15
|
+
* and runs reads under the refresh-and-retry-once rule. One instance per
|
|
16
|
+
* session (it IS the session scope of the guard); the injected `refresh` does
|
|
17
|
+
* the host's whole swap -- re-acquire the marker(s), rebuild the cipher(s),
|
|
18
|
+
* and install them wherever the host keeps them.
|
|
19
|
+
*/
|
|
20
|
+
export class MarkerRefreshPolicy {
|
|
21
|
+
#refresh;
|
|
22
|
+
// Collection ids whose one refresh this session is already spent.
|
|
23
|
+
#refreshed = new Set();
|
|
24
|
+
constructor({ refresh }) {
|
|
25
|
+
this.#refresh = refresh;
|
|
26
|
+
}
|
|
27
|
+
/** Whether a collection still has its one refresh this session. */
|
|
28
|
+
shouldRefresh({ collectionId }) {
|
|
29
|
+
return !this.#refreshed.has(collectionId);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Runs a read that reports whether it skipped unknown-epoch rows; on the
|
|
33
|
+
* first such report for a collection this session, spends the collection's
|
|
34
|
+
* refresh (marker re-read + cipher swap, via the injected `refresh`) and
|
|
35
|
+
* re-reads once. A later unknown-epoch report for the same collection
|
|
36
|
+
* returns the read's value as-is.
|
|
37
|
+
*
|
|
38
|
+
* @param options {object}
|
|
39
|
+
* @param options.collectionId {string}
|
|
40
|
+
* @param options.read {function} the read, reporting `unknownEpoch`
|
|
41
|
+
* @returns {Promise<T>} the (possibly re-read) value
|
|
42
|
+
*/
|
|
43
|
+
async readWithRefresh({ collectionId, read }) {
|
|
44
|
+
const first = await read();
|
|
45
|
+
if (first.unknownEpoch && this.shouldRefresh({ collectionId })) {
|
|
46
|
+
this.#refreshed.add(collectionId);
|
|
47
|
+
await this.#refresh({ collectionId });
|
|
48
|
+
return (await read()).value;
|
|
49
|
+
}
|
|
50
|
+
return first.value;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Re-arms the guard -- for one collection, or (with no argument) for all.
|
|
54
|
+
* Call when a fresh marker is installed by some other path (a share,
|
|
55
|
+
* unshare, or recipient rotation this session performed itself), since the
|
|
56
|
+
* next unknown-epoch read is then evidence of a NEW rotation elsewhere.
|
|
57
|
+
*/
|
|
58
|
+
reset(options) {
|
|
59
|
+
if (options?.collectionId === undefined) {
|
|
60
|
+
this.#refreshed.clear();
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.#refreshed.delete(options.collectionId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/markers/refresh.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,MAAM,OAAO,mBAAmB;IACrB,QAAQ,CAAsD;IACvE,kEAAkE;IACzD,UAAU,GAAG,IAAI,GAAG,EAAU,CAAA;IAEvC,YAAY,EACV,OAAO,EAGR;QACC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;IACzB,CAAC;IAED,mEAAmE;IACnE,aAAa,CAAC,EAAE,YAAY,EAA4B;QACtD,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IAC3C,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,eAAe,CAAI,EACvB,YAAY,EACZ,IAAI,EAIL;QACC,MAAM,KAAK,GAAG,MAAM,IAAI,EAAE,CAAA;QAC1B,IAAI,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;YAC/D,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;YACjC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;YACrC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,CAAA;QAC7B,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAA;IACpB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,OAAmC;QACvC,IAAI,OAAO,EAAE,YAAY,KAAK,SAAS,EAAE,CAAC;YACxC,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAA;QACzB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;QAC9C,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The `@interop/wallet-core/recovery` subpath: recovery codes on the roster
|
|
6
|
+
* identity model -- a code as a minimal always-enrolled wallet client.
|
|
7
|
+
*
|
|
8
|
+
* - `generateRecoveryCode` / `formatRecoveryCode` / `normalizeRecoveryCode` /
|
|
9
|
+
* `decodeRecoveryCode` / `RECOVERY_KDF` -- the format layer and the code's
|
|
10
|
+
* own unlock-derivation parameter set (its salt distinct from every other
|
|
11
|
+
* unlock method's).
|
|
12
|
+
* - `recoveryClientFromCode` -- the deterministic client key set:
|
|
13
|
+
* unlock identity, client seed (signing + key-agreement pair), and the
|
|
14
|
+
* single did:webvh update key whose hash stands pre-committed.
|
|
15
|
+
* - `wrapRecoveryRecord` / `unwrapRecoveryRecord` -- the keyring-record
|
|
16
|
+
* sibling carrying the account pointer plus the pre-minted
|
|
17
|
+
* PUT-on-`did.jsonl` delegation (never a seed, never a PUK wrap).
|
|
18
|
+
* - `publishRecoveryKey` / `removeRecoveryKey` / `recoverWebvhClient` -- the
|
|
19
|
+
* document half: issuance's split posture, revocation's removal, and the
|
|
20
|
+
* self-enrolling recovery continuation.
|
|
21
|
+
*
|
|
22
|
+
* Kept out of the root export: this subpath pulls the webkms-client / ezcap /
|
|
23
|
+
* was-client dependency graph (the same isolation pattern as `./keyring`).
|
|
24
|
+
*/
|
|
25
|
+
export { decodeRecoveryCode, formatRecoveryCode, generateRecoveryCode, normalizeRecoveryCode, RECOVERY_CODE_BYTES, RECOVERY_KDF, RecoveryCodeInvalidError, recoveryClientFromCode } from './recoveryCode.js';
|
|
26
|
+
export type { RecoveryClient } from './recoveryCode.js';
|
|
27
|
+
export { unwrapRecoveryRecord, wrapRecoveryRecord } from './recoveryRecord.js';
|
|
28
|
+
export type { RecoveryRecordContents } from './recoveryRecord.js';
|
|
29
|
+
export { publishRecoveryKey, recoverWebvhClient, RecoveryKeyNotCommittedError, recoveryVmId, removeRecoveryKey } from './recoveryWebvh.js';
|
|
30
|
+
export type { RecoveryLogStore, RecoveryPublicKeys } from './recoveryWebvh.js';
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/recovery/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,YAAY,EACZ,wBAAwB,EACxB,sBAAsB,EACvB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC9E,YAAY,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAEjE,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,4BAA4B,EAC5B,YAAY,EACZ,iBAAiB,EAClB,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA"}
|