@interop/was-client 0.53.0 → 0.55.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/README.md +70 -14
- package/dist/WasClient.d.ts +1 -1
- package/dist/WasClient.d.ts.map +1 -1
- package/dist/WasClient.js +5 -18
- package/dist/WasClient.js.map +1 -1
- package/dist/edv/acquire.d.ts +98 -0
- package/dist/edv/acquire.d.ts.map +1 -0
- package/dist/edv/acquire.js +130 -0
- package/dist/edv/acquire.js.map +1 -0
- package/dist/edv/index.d.ts +35 -2
- package/dist/edv/index.d.ts.map +1 -1
- package/dist/edv/index.js +34 -2
- package/dist/edv/index.js.map +1 -1
- package/dist/edv/refresh.d.ts +62 -0
- package/dist/edv/refresh.d.ts.map +1 -0
- package/dist/edv/refresh.js +69 -0
- package/dist/edv/refresh.js.map +1 -0
- package/dist/edv/refreshingDocCipher.d.ts +70 -0
- package/dist/edv/refreshingDocCipher.d.ts.map +1 -0
- package/dist/edv/refreshingDocCipher.js +110 -0
- package/dist/edv/refreshingDocCipher.js.map +1 -0
- package/dist/errors.d.ts +4 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -3
- package/dist/errors.js.map +1 -1
- package/dist/identity/agents.d.ts +84 -0
- package/dist/identity/agents.d.ts.map +1 -0
- package/dist/identity/agents.js +104 -0
- package/dist/identity/agents.js.map +1 -0
- package/dist/identity/index.d.ts +15 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +14 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/keyResolver.d.ts +30 -0
- package/dist/identity/keyResolver.d.ts.map +1 -0
- package/dist/identity/keyResolver.js +25 -0
- package/dist/identity/keyResolver.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -1
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/port.d.ts +5 -1
- package/dist/sync/port.d.ts.map +1 -1
- package/dist/sync/port.js +27 -11
- package/dist/sync/port.js.map +1 -1
- package/dist/sync/predicates.d.ts +21 -4
- package/dist/sync/predicates.d.ts.map +1 -1
- package/dist/sync/predicates.js +23 -4
- package/dist/sync/predicates.js.map +1 -1
- package/dist/sync/provisioning.d.ts +13 -5
- package/dist/sync/provisioning.d.ts.map +1 -1
- package/dist/sync/provisioning.js +13 -7
- package/dist/sync/provisioning.js.map +1 -1
- package/dist/sync/types.d.ts +23 -14
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/zcapClient.d.ts +19 -0
- package/dist/zcapClient.d.ts.map +1 -0
- package/dist/zcapClient.js +32 -0
- package/dist/zcapClient.js.map +1 -0
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- [Install](#install)
|
|
13
13
|
- [Usage](#usage)
|
|
14
14
|
- [Creating a client (signer + zcapClient)](#creating-a-client-signer--zcapclient)
|
|
15
|
+
- [Deriving a client from a secret or seed (`./identity`)](#deriving-a-client-from-a-secret-or-seed-identity)
|
|
15
16
|
- [The handle model](#the-handle-model)
|
|
16
17
|
- [Spaces](#spaces)
|
|
17
18
|
- [Collections](#collections)
|
|
@@ -131,14 +132,44 @@ const was = WasClient.fromSigner({
|
|
|
131
132
|
|
|
132
133
|
The `seed` is where a passphrase-, stored-secret-, or KMS-derived key plugs in:
|
|
133
134
|
deriving the same 32-byte seed yields the same DID, and therefore access to the
|
|
134
|
-
same spaces.
|
|
135
|
-
via `CapabilityAgent.fromSecret()` from `@digitalbazaar/webkms-client` -- not
|
|
136
|
-
required, just a common alternative.)
|
|
135
|
+
same spaces.
|
|
137
136
|
|
|
138
137
|
`serverUrl` is the base for both URL building and zcap `invocationTarget`s, so
|
|
139
138
|
the "server URL must equal the invocation target host:port" constraint holds by
|
|
140
139
|
construction.
|
|
141
140
|
|
|
141
|
+
#### Deriving a client from a secret or seed (`./identity`)
|
|
142
|
+
|
|
143
|
+
Apps with user accounts usually do not hold a `did:key` signer directly -- they
|
|
144
|
+
hold a passphrase, or a stored secret, and need to derive the same client from
|
|
145
|
+
it every time. The `./identity` subpath does that derivation:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { agentsFromSecret } from '@interop/was-client/identity'
|
|
149
|
+
import { WasClient } from '@interop/was-client'
|
|
150
|
+
|
|
151
|
+
const agents = await agentsFromSecret({ secret: 'the account secret' })
|
|
152
|
+
|
|
153
|
+
const was = WasClient.fromSigner({
|
|
154
|
+
serverUrl: 'https://was.example',
|
|
155
|
+
signer: agents.keyAgent.getSigner()
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`agents.zcapClient` is the same client `fromSigner` builds, for callers that
|
|
160
|
+
need it directly.
|
|
161
|
+
|
|
162
|
+
The derivation is type-sensitive, so the secret is always a string: a
|
|
163
|
+
passphrase, or the base64url text of random bytes, not the decoded bytes. To
|
|
164
|
+
derive from an already-derived 32-byte seed instead, use
|
|
165
|
+
`agentsFromSeed({ seed })`.
|
|
166
|
+
|
|
167
|
+
The derivation is deterministic and permanent: the same secret, on any client,
|
|
168
|
+
derives the same `did:key` and joins the same account. `agents.keyAgreementKey`
|
|
169
|
+
and `agents.keyResolver` are the key-agreement key and resolver the `./edv`
|
|
170
|
+
cipher takes, so a caller building an encrypted collection has both halves from
|
|
171
|
+
one call.
|
|
172
|
+
|
|
142
173
|
### The delegation-proof suite
|
|
143
174
|
|
|
144
175
|
Delegation proofs are signed with `eddsa-jcs-2022`. That cryptosuite
|
|
@@ -147,6 +178,10 @@ JSON-LD canonicalization and needs no document loader to serve the suite's
|
|
|
147
178
|
context at signing time. Invocations are unaffected -- those ride HTTP
|
|
148
179
|
signatures, not proofs.
|
|
149
180
|
|
|
181
|
+
`zcapClientForSigner({ signer })`, exported from the package root, is the one
|
|
182
|
+
`ZcapClient` construction site that hard-codes this suite; `fromSigner()` above
|
|
183
|
+
builds its client through it, and so does every `./identity` derivation.
|
|
184
|
+
|
|
150
185
|
The server has to verify `eddsa-jcs-2022` for grants from this client to be
|
|
151
186
|
accepted. A server that verifies both it and the older `Ed25519Signature2020`
|
|
152
187
|
accepts chains whose links mix the two, which is what a fleet upgrading at its
|
|
@@ -905,17 +940,21 @@ plugs into:
|
|
|
905
940
|
`ETag`. The port moves stored bodies **verbatim** -- for an encrypted
|
|
906
941
|
collection that means the opaque EDV envelope, never plaintext, and the port
|
|
907
942
|
itself never touches keys. A rejected precondition throws
|
|
908
|
-
`WasSyncConflictError` (412); a delete of an already-gone resource
|
|
909
|
-
`WasSyncNotFoundError` (404) -- both catchable
|
|
910
|
-
`PreconditionFailedError` / `NotFoundError`. Every other
|
|
911
|
-
the typed `WasError` subclass for its status, carrying the
|
|
912
|
-
`problem+json` fields.
|
|
943
|
+
`WasSyncConflictError` (412); a delete of an already-gone resource or a
|
|
944
|
+
`putMeta` against one throws `WasSyncNotFoundError` (404) -- both catchable
|
|
945
|
+
subtypes of the core `PreconditionFailedError` / `NotFoundError`. Every other
|
|
946
|
+
failure arrives as the typed `WasError` subclass for its status, carrying the
|
|
947
|
+
server's `problem+json` fields.
|
|
913
948
|
- **`isSyncConflictError` / `isSyncNotFoundError` / `isSyncAuthError` /
|
|
914
|
-
`isUnknownEpochError`** classify those signals by
|
|
915
|
-
than `instanceof`: the port and the `DocCipher`
|
|
916
|
-
and a tree that resolves two copies of this
|
|
917
|
-
check quietly false. Read what you need off the
|
|
918
|
-
`err.status` on an auth error to tell a 401 from the
|
|
949
|
+
`isUnknownEpochError` / `isKeyUnwrapError`** classify those signals by
|
|
950
|
+
`err.name`. Use them rather than `instanceof`: the port and the `DocCipher`
|
|
951
|
+
are seams your app injects, and a tree that resolves two copies of this
|
|
952
|
+
package makes an `instanceof` check quietly false. Read what you need off the
|
|
953
|
+
matched value, such as `err.status` on an auth error to tell a 401 from the
|
|
954
|
+
masked 404. The last two tell a decrypt's two no-key outcomes apart: an epoch
|
|
955
|
+
the reader's descriptor does not list (a re-read may fix it) and an epoch it
|
|
956
|
+
lists but this reader holds no key for (real data, permanently unreadable
|
|
957
|
+
here, never garbage).
|
|
919
958
|
- **`SyncStatus`** (`'idle' | 'syncing' | 'synced' | 'error'`) is the closed
|
|
920
959
|
vocabulary a replication driver reports one feed's state through.
|
|
921
960
|
- **`DocCipher`** is the per-collection encrypt/decrypt seam sitting above the
|
|
@@ -932,6 +971,21 @@ plugs into:
|
|
|
932
971
|
counterpart, built from the descriptor alone with no key-agreement secret
|
|
933
972
|
(writes seal to the current epoch's public key, reconstructed from the epoch
|
|
934
973
|
id); `decrypt` on it refuses with the typed `EncryptOnlyCipherError`.
|
|
974
|
+
- **`createRefreshingEdvDocCipher(...)`** (from `@interop/was-client/edv`) is
|
|
975
|
+
`createEdvDocCipher` bound to descriptor acquisition and the unknown-epoch
|
|
976
|
+
refresh rule: it acquires the collection's descriptor through the
|
|
977
|
+
`EncryptionDescriptorSource` / `EncryptionDescriptorCache` seams
|
|
978
|
+
(`wasDescriptorSource` is the source over a `WasClient`; the cache is a
|
|
979
|
+
client-local get/put your app scopes to one Space), refuses fail-closed to
|
|
980
|
+
build without one, and on an `UnknownEpochError` decrypt re-reads the
|
|
981
|
+
descriptor, swaps itself, and retries exactly once per instance. An epoch
|
|
982
|
+
rotation emits no change-feed entry, so this is how a replica meets an
|
|
983
|
+
envelope sealed under an epoch it has never seen without a refetch per
|
|
984
|
+
resource. `acquireDescriptor` / `acquireDescriptors` are the acquisition alone
|
|
985
|
+
(fetch, cache the success, fall back to the cache whenever the fetch yields no
|
|
986
|
+
descriptor), and `DescriptorRefreshPolicy` is the
|
|
987
|
+
once-per-collection-per-session guard for a host whose reads scan rows instead
|
|
988
|
+
of going through a cipher.
|
|
935
989
|
- **`contentCid(doc)`** and **`deriveSpaceId(controllerDid)`** derive
|
|
936
990
|
content-addressed ids -- `base64url(SHA-256(utf8(JCS-canonicalized JSON)))`,
|
|
937
991
|
unpadded -- so the same logical document (and the same controller) lands on
|
|
@@ -940,7 +994,9 @@ plugs into:
|
|
|
940
994
|
Space, configure the collection (`edv`, `plaintext`, or `governed`, optionally
|
|
941
995
|
world-readable). Safe to re-run on every connect. A `governed` collection is
|
|
942
996
|
created with no `encryption` member, the caller declaring governance
|
|
943
|
-
afterwards through the history log's guarded create.
|
|
997
|
+
afterwards through the history log's guarded create. An optional `capability`
|
|
998
|
+
is the invocation capability every request rides, for a client holding a
|
|
999
|
+
delegated zcap rather than the Space's root authority.
|
|
944
1000
|
|
|
945
1001
|
```ts
|
|
946
1002
|
import {
|
package/dist/WasClient.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* (`grant`), capability-rebuilding (`fromCapability`), and the signed
|
|
10
10
|
* escape-hatch (`request`).
|
|
11
11
|
*/
|
|
12
|
-
import { ZcapClient } from '@interop/ezcap';
|
|
12
|
+
import type { ZcapClient } from '@interop/ezcap';
|
|
13
13
|
import type { HttpResponse } from '@interop/http-client';
|
|
14
14
|
import type { EncryptionProvider } from './codec.js';
|
|
15
15
|
import { Space } from './Space.js';
|
package/dist/WasClient.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WasClient.d.ts","sourceRoot":"","sources":["../src/WasClient.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"WasClient.d.ts","sourceRoot":"","sources":["../src/WasClient.ts"],"names":[],"mappings":"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"}
|
package/dist/WasClient.js
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* The top-level WAS client. Wraps an ezcap `ZcapClient` (which holds the active
|
|
6
|
-
* signer) and exposes the WAS containment model
|
|
7
|
-
* (`SpacesRepository > Space > Collection > Resource`) through lazy
|
|
8
|
-
* navigational handles. Also hosts the general delegation primitive
|
|
9
|
-
* (`grant`), capability-rebuilding (`fromCapability`), and the signed
|
|
10
|
-
* escape-hatch (`request`).
|
|
11
|
-
*/
|
|
12
|
-
import { ZcapClient } from '@interop/ezcap';
|
|
13
|
-
import { EddsaJcs2022 } from '@interop/ed25519-signature/eddsa-jcs-2022';
|
|
14
1
|
import { collectionItemsUrl, parseSpaceTarget, spacesRoot, toUrl } from './internal/paths.js';
|
|
15
2
|
import { send, rawRequest, unsignedRequest } from './internal/request.js';
|
|
16
3
|
import { createdId, parseResource, readJsonData } from './internal/content.js';
|
|
@@ -18,6 +5,7 @@ import { buildPageWalk, collectPages, collectWalk, signedPageWalk, walkItems, wa
|
|
|
18
5
|
import { delegateGrant } from './internal/grant.js';
|
|
19
6
|
import { spaceIdOf, submitRevocation } from './internal/revoke.js';
|
|
20
7
|
import { ValidationError } from './errors.js';
|
|
8
|
+
import { zcapClientForSigner } from './zcapClient.js';
|
|
21
9
|
import { Space } from './Space.js';
|
|
22
10
|
import { Collection } from './Collection.js';
|
|
23
11
|
import { Resource } from './Resource.js';
|
|
@@ -59,12 +47,11 @@ export class WasClient {
|
|
|
59
47
|
* @returns {WasClient}
|
|
60
48
|
*/
|
|
61
49
|
static fromSigner({ serverUrl, signer, encryption }) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
50
|
+
return new WasClient({
|
|
51
|
+
serverUrl,
|
|
52
|
+
zcapClient: zcapClientForSigner({ signer }),
|
|
53
|
+
encryption
|
|
66
54
|
});
|
|
67
|
-
return new WasClient({ serverUrl, zcapClient, encryption });
|
|
68
55
|
}
|
|
69
56
|
/**
|
|
70
57
|
* The DID controlling the wrapped signer (`signer.id` without the key
|
package/dist/WasClient.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WasClient.js","sourceRoot":"","sources":["../src/WasClient.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"WasClient.js","sourceRoot":"","sources":["../src/WasClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { CollectionEncryption } from '../types.js';
|
|
2
|
+
import type { WasClient } from '../WasClient.js';
|
|
3
|
+
/**
|
|
4
|
+
* Where descriptors come from: one signed read of the collection's
|
|
5
|
+
* Description. Resolves `undefined` when the description carries no encryption
|
|
6
|
+
* member -- which a WAS host also serves for a read this client is not
|
|
7
|
+
* authorized to make, so the absence is ambiguous and callers fall back to a
|
|
8
|
+
* cached copy just as they do for a thrown fetch. Network errors throw through
|
|
9
|
+
* (callers treat the fetch as best-effort).
|
|
10
|
+
*/
|
|
11
|
+
export interface EncryptionDescriptorSource {
|
|
12
|
+
collectionEncryption(options: {
|
|
13
|
+
collectionId: string;
|
|
14
|
+
}): Promise<CollectionEncryption | undefined>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Where fetched descriptors survive offline: a client-local get/put
|
|
18
|
+
* pre-scoped by the host to one Space. `readDescriptor` resolves `undefined`
|
|
19
|
+
* when nothing is cached (never throws for absence).
|
|
20
|
+
*/
|
|
21
|
+
export interface EncryptionDescriptorCache {
|
|
22
|
+
readDescriptor(options: {
|
|
23
|
+
collectionId: string;
|
|
24
|
+
}): Promise<CollectionEncryption | undefined>;
|
|
25
|
+
writeDescriptor(options: {
|
|
26
|
+
collectionId: string;
|
|
27
|
+
descriptor: CollectionEncryption;
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The {@link EncryptionDescriptorSource} over a was-client handle: reads the
|
|
32
|
+
* collection's Description in the given Space and returns its `encryption`
|
|
33
|
+
* descriptor.
|
|
34
|
+
*
|
|
35
|
+
* @param options {object}
|
|
36
|
+
* @param options.was {WasClient} a client whose signer can read the Space
|
|
37
|
+
* @param options.spaceId {string}
|
|
38
|
+
* @returns {EncryptionDescriptorSource}
|
|
39
|
+
*/
|
|
40
|
+
export declare function wasDescriptorSource({ was, spaceId }: {
|
|
41
|
+
was: WasClient;
|
|
42
|
+
spaceId: string;
|
|
43
|
+
}): EncryptionDescriptorSource;
|
|
44
|
+
/**
|
|
45
|
+
* Acquires one collection's descriptor: fetches it from the source, caching a
|
|
46
|
+
* success; falls back to the cached copy whenever the fetch yields no
|
|
47
|
+
* descriptor (it threw, or it came back empty -- a masked 404 for an
|
|
48
|
+
* unauthorized read looks exactly like an unencrypted collection); and with no
|
|
49
|
+
* source at all (a purely local code path) reads the cache alone. Any cached
|
|
50
|
+
* copy is deliberately left in place, never cleared by an empty fetch.
|
|
51
|
+
* `undefined` means no descriptor exists anywhere for this collection.
|
|
52
|
+
*
|
|
53
|
+
* @param options {object}
|
|
54
|
+
* @param [options.source] {EncryptionDescriptorSource} omit for cache-only
|
|
55
|
+
* acquisition
|
|
56
|
+
* @param options.cache {EncryptionDescriptorCache}
|
|
57
|
+
* @param options.collectionId {string}
|
|
58
|
+
* @param [options.onFetchError] {function} observes a swallowed fetch
|
|
59
|
+
* failure (the thrown-fetch branch only; an empty description is not an
|
|
60
|
+
* error). Errors from the cache itself throw through, as do a log-governed
|
|
61
|
+
* source's refusal classes (a fabricated or discontinuous log is a security
|
|
62
|
+
* signal, not an outage the cache should paper over) -- except a continuity
|
|
63
|
+
* `rollback`, which falls back to the cache like any transport hiccup
|
|
64
|
+
* @returns {Promise<CollectionEncryption | undefined>}
|
|
65
|
+
*/
|
|
66
|
+
export declare function acquireDescriptor({ source, cache, collectionId, onFetchError }: {
|
|
67
|
+
source?: EncryptionDescriptorSource;
|
|
68
|
+
cache: EncryptionDescriptorCache;
|
|
69
|
+
collectionId: string;
|
|
70
|
+
onFetchError?: (err: unknown, info: {
|
|
71
|
+
collectionId: string;
|
|
72
|
+
}) => void;
|
|
73
|
+
}): Promise<CollectionEncryption | undefined>;
|
|
74
|
+
/**
|
|
75
|
+
* Acquires descriptors for a set of collections concurrently (each fetch is an
|
|
76
|
+
* independent signed round trip, so a session start is not gated on a serial
|
|
77
|
+
* chain of describes). Collections that resolve no descriptor are simply
|
|
78
|
+
* absent from the result.
|
|
79
|
+
*
|
|
80
|
+
* @param options {object}
|
|
81
|
+
* @param [options.source] {EncryptionDescriptorSource} omit for cache-only
|
|
82
|
+
* acquisition
|
|
83
|
+
* @param options.cache {EncryptionDescriptorCache}
|
|
84
|
+
* @param options.collectionIds {string[]}
|
|
85
|
+
* @param [options.onFetchError] {function} observes each swallowed fetch
|
|
86
|
+
* failure
|
|
87
|
+
* @returns {Promise<Record<string, CollectionEncryption>>} keyed by
|
|
88
|
+
* collection id
|
|
89
|
+
*/
|
|
90
|
+
export declare function acquireDescriptors({ source, cache, collectionIds, onFetchError }: {
|
|
91
|
+
source?: EncryptionDescriptorSource;
|
|
92
|
+
cache: EncryptionDescriptorCache;
|
|
93
|
+
collectionIds: string[];
|
|
94
|
+
onFetchError?: (err: unknown, info: {
|
|
95
|
+
collectionId: string;
|
|
96
|
+
}) => void;
|
|
97
|
+
}): Promise<Record<string, CollectionEncryption>>;
|
|
98
|
+
//# sourceMappingURL=acquire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acquire.d.ts","sourceRoot":"","sources":["../../src/edv/acquire.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhD;;;;;;;GAOG;AACH,MAAM,WAAW,0BAA0B;IACzC,oBAAoB,CAAC,OAAO,EAAE;QAC5B,YAAY,EAAE,MAAM,CAAA;KACrB,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAAA;CAC9C;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,cAAc,CAAC,OAAO,EAAE;QACtB,YAAY,EAAE,MAAM,CAAA;KACrB,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAAA;IAC7C,eAAe,CAAC,OAAO,EAAE;QACvB,YAAY,EAAE,MAAM,CAAA;QACpB,UAAU,EAAE,oBAAoB,CAAA;KACjC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,GAAG,EACH,OAAO,EACR,EAAE;IACD,GAAG,EAAE,SAAS,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,0BAA0B,CAU7B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,MAAM,EACN,KAAK,EACL,YAAY,EACZ,YAAY,EACb,EAAE;IACD,MAAM,CAAC,EAAE,0BAA0B,CAAA;IACnC,KAAK,EAAE,yBAAyB,CAAA;IAChC,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,CAwB5C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CAAC,EACvC,MAAM,EACN,KAAK,EACL,aAAa,EACb,YAAY,EACb,EAAE;IACD,MAAM,CAAC,EAAE,0BAA0B,CAAA;IACnC,KAAK,EAAE,yBAAyB,CAAA;IAChC,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,130 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Collection encryption-descriptor acquisition: reading a collection's
|
|
6
|
+
* `CollectionEncryption` descriptor (its key-epoch roster) from the Collection
|
|
7
|
+
* Description, caching each success, and falling back to the cached copy
|
|
8
|
+
* whenever the description yields no descriptor -- whether it could not be
|
|
9
|
+
* fetched at all (offline, a collection must keep encrypting under its current
|
|
10
|
+
* epoch) or came back empty (WAS masks an unauthorized read as an absent one,
|
|
11
|
+
* so an empty description is ambiguous, never an authoritative "no
|
|
12
|
+
* encryption"). `undefined` therefore means only this: nothing, fetched or
|
|
13
|
+
* cached, describes this collection's encryption -- a plaintext collection, or
|
|
14
|
+
* an encrypted one whose epoch[0] install has not landed. A caller that has
|
|
15
|
+
* declared the collection encrypted must refuse fail-closed rather than
|
|
16
|
+
* encrypt without a roster.
|
|
17
|
+
*
|
|
18
|
+
* The two seams are deliberately narrow, so a consumer's own classes satisfy
|
|
19
|
+
* them structurally -- no adapter needed. An {@link EncryptionDescriptorSource}
|
|
20
|
+
* is one signed describe; an {@link EncryptionDescriptorCache} is a
|
|
21
|
+
* client-local get/put the host has already scoped to one Space (a web app: a
|
|
22
|
+
* localStorage pair keyed by Space id; a mobile app: a per-(profile,
|
|
23
|
+
* collection) table column), so no scope key appears in the interface.
|
|
24
|
+
*
|
|
25
|
+
* A source over a log-governed descriptor throws the resource-log refusal
|
|
26
|
+
* classes, and those are security signals rather than outages: the read path
|
|
27
|
+
* rethrows them past a warm cache, matched by `@interop/vh-resource-log`'s
|
|
28
|
+
* `isResourceLogRefusal` (a continuity `rollback` is the one carve-out, and
|
|
29
|
+
* falls back to the cache like a transport failure).
|
|
30
|
+
*/
|
|
31
|
+
import { isResourceLogRefusal } from '@interop/vh-resource-log';
|
|
32
|
+
/**
|
|
33
|
+
* The {@link EncryptionDescriptorSource} over a was-client handle: reads the
|
|
34
|
+
* collection's Description in the given Space and returns its `encryption`
|
|
35
|
+
* descriptor.
|
|
36
|
+
*
|
|
37
|
+
* @param options {object}
|
|
38
|
+
* @param options.was {WasClient} a client whose signer can read the Space
|
|
39
|
+
* @param options.spaceId {string}
|
|
40
|
+
* @returns {EncryptionDescriptorSource}
|
|
41
|
+
*/
|
|
42
|
+
export function wasDescriptorSource({ was, spaceId }) {
|
|
43
|
+
return {
|
|
44
|
+
async collectionEncryption({ collectionId }) {
|
|
45
|
+
const description = await was
|
|
46
|
+
.space(spaceId)
|
|
47
|
+
.collection(collectionId)
|
|
48
|
+
.describe();
|
|
49
|
+
return description?.encryption ?? undefined;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Acquires one collection's descriptor: fetches it from the source, caching a
|
|
55
|
+
* success; falls back to the cached copy whenever the fetch yields no
|
|
56
|
+
* descriptor (it threw, or it came back empty -- a masked 404 for an
|
|
57
|
+
* unauthorized read looks exactly like an unencrypted collection); and with no
|
|
58
|
+
* source at all (a purely local code path) reads the cache alone. Any cached
|
|
59
|
+
* copy is deliberately left in place, never cleared by an empty fetch.
|
|
60
|
+
* `undefined` means no descriptor exists anywhere for this collection.
|
|
61
|
+
*
|
|
62
|
+
* @param options {object}
|
|
63
|
+
* @param [options.source] {EncryptionDescriptorSource} omit for cache-only
|
|
64
|
+
* acquisition
|
|
65
|
+
* @param options.cache {EncryptionDescriptorCache}
|
|
66
|
+
* @param options.collectionId {string}
|
|
67
|
+
* @param [options.onFetchError] {function} observes a swallowed fetch
|
|
68
|
+
* failure (the thrown-fetch branch only; an empty description is not an
|
|
69
|
+
* error). Errors from the cache itself throw through, as do a log-governed
|
|
70
|
+
* source's refusal classes (a fabricated or discontinuous log is a security
|
|
71
|
+
* signal, not an outage the cache should paper over) -- except a continuity
|
|
72
|
+
* `rollback`, which falls back to the cache like any transport hiccup
|
|
73
|
+
* @returns {Promise<CollectionEncryption | undefined>}
|
|
74
|
+
*/
|
|
75
|
+
export async function acquireDescriptor({ source, cache, collectionId, onFetchError }) {
|
|
76
|
+
if (!source) {
|
|
77
|
+
return cache.readDescriptor({ collectionId });
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const fetched = await source.collectionEncryption({ collectionId });
|
|
81
|
+
if (fetched) {
|
|
82
|
+
await cache.writeDescriptor({ collectionId, descriptor: fetched });
|
|
83
|
+
return fetched;
|
|
84
|
+
}
|
|
85
|
+
// Empty description: not authoritative (an unauthorized read is masked as
|
|
86
|
+
// an absent one), so a warm cache still serves the collection.
|
|
87
|
+
return cache.readDescriptor({ collectionId });
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
// A resource-log refusal the cache must not paper over -- a fabricated
|
|
91
|
+
// log, or one that is not the continuation of the pinned history. A
|
|
92
|
+
// rollback is not one of them and falls through to the cache, as any
|
|
93
|
+
// transport failure does.
|
|
94
|
+
if (isResourceLogRefusal(err)) {
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
onFetchError?.(err, { collectionId });
|
|
98
|
+
return cache.readDescriptor({ collectionId });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Acquires descriptors for a set of collections concurrently (each fetch is an
|
|
103
|
+
* independent signed round trip, so a session start is not gated on a serial
|
|
104
|
+
* chain of describes). Collections that resolve no descriptor are simply
|
|
105
|
+
* absent from the result.
|
|
106
|
+
*
|
|
107
|
+
* @param options {object}
|
|
108
|
+
* @param [options.source] {EncryptionDescriptorSource} omit for cache-only
|
|
109
|
+
* acquisition
|
|
110
|
+
* @param options.cache {EncryptionDescriptorCache}
|
|
111
|
+
* @param options.collectionIds {string[]}
|
|
112
|
+
* @param [options.onFetchError] {function} observes each swallowed fetch
|
|
113
|
+
* failure
|
|
114
|
+
* @returns {Promise<Record<string, CollectionEncryption>>} keyed by
|
|
115
|
+
* collection id
|
|
116
|
+
*/
|
|
117
|
+
export async function acquireDescriptors({ source, cache, collectionIds, onFetchError }) {
|
|
118
|
+
const resolved = await Promise.all(collectionIds.map(async (collectionId) => [
|
|
119
|
+
collectionId,
|
|
120
|
+
await acquireDescriptor({ source, cache, collectionId, onFetchError })
|
|
121
|
+
]));
|
|
122
|
+
const descriptors = {};
|
|
123
|
+
for (const [collectionId, descriptor] of resolved) {
|
|
124
|
+
if (descriptor) {
|
|
125
|
+
descriptors[collectionId] = descriptor;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return descriptors;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=acquire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acquire.js","sourceRoot":"","sources":["../../src/edv/acquire.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAiC/D;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAClC,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;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EACtC,MAAM,EACN,KAAK,EACL,YAAY,EACZ,YAAY,EAMb;IACC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,KAAK,CAAC,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;IAC/C,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,eAAe,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAA;YAClE,OAAO,OAAO,CAAA;QAChB,CAAC;QACD,0EAA0E;QAC1E,+DAA+D;QAC/D,OAAO,KAAK,CAAC,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,uEAAuE;QACvE,oEAAoE;QACpE,qEAAqE;QACrE,0BAA0B;QAC1B,IAAI,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,GAAG,CAAA;QACX,CAAC;QACD,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,CAAC,CAAA;QACrC,OAAO,KAAK,CAAC,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;IAC/C,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,EACvC,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,iBAAiB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;KAC9D,CACb,CACF,CAAA;IACD,MAAM,WAAW,GAAyC,EAAE,CAAA;IAC5D,KAAK,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,IAAI,QAAQ,EAAE,CAAC;QAClD,IAAI,UAAU,EAAE,CAAC;YACf,WAAW,CAAC,YAAY,CAAC,GAAG,UAAU,CAAA;QACxC,CAAC;IACH,CAAC;IACD,OAAO,WAAW,CAAA;AACpB,CAAC"}
|
package/dist/edv/index.d.ts
CHANGED
|
@@ -50,8 +50,36 @@
|
|
|
50
50
|
* cannot help). Both ship from this subpath beside the cipher that raises
|
|
51
51
|
* them, so a consumer classifying what a cipher threw need not reach for the
|
|
52
52
|
* package root. Both assign their `name` explicitly, and a consumer whose
|
|
53
|
-
* cipher arrives through an injected seam
|
|
54
|
-
*
|
|
53
|
+
* cipher arrives through an injected seam matches on that name rather than
|
|
54
|
+
* `instanceof`, since the seam may resolve to a second copy of this package:
|
|
55
|
+
* `isKeyUnwrapError` here, and `isUnknownEpochError` on `./sync`, are those
|
|
56
|
+
* matchers.
|
|
57
|
+
*
|
|
58
|
+
* Which epoch a collection encrypts under, and when to ask again, is the
|
|
59
|
+
* descriptor acquisition and refresh policy every consumer running an
|
|
60
|
+
* encrypted collection must share (a drift between two replicas does not fail
|
|
61
|
+
* loudly; it fails as a resource one of them cannot decrypt):
|
|
62
|
+
*
|
|
63
|
+
* - `EncryptionDescriptorSource` / `EncryptionDescriptorCache` -- the narrow
|
|
64
|
+
* seams a host implements: one signed Collection Description read, and a
|
|
65
|
+
* client-local get/put pre-scoped to one Space. `wasDescriptorSource` is the
|
|
66
|
+
* source over a `WasClient` handle.
|
|
67
|
+
* - `acquireDescriptor` / `acquireDescriptors` -- fetch + cache with the
|
|
68
|
+
* cached fallback whenever the fetch yields no descriptor, thrown or empty
|
|
69
|
+
* (offline, a collection keeps encrypting under its current epoch; an empty
|
|
70
|
+
* description is ambiguous, since WAS masks an unauthorized read as an
|
|
71
|
+
* absent one). No descriptor anywhere means a plaintext collection, or an
|
|
72
|
+
* encrypted one whose epoch[0] install has not landed -- which a caller that
|
|
73
|
+
* has declared the collection encrypted must refuse fail-closed. A
|
|
74
|
+
* log-governed source's refusal classes rethrow instead of falling back
|
|
75
|
+
* (except a continuity rollback, which is reconcilable divergence).
|
|
76
|
+
* - `DescriptorRefreshPolicy` -- the once-per-collection-per-session
|
|
77
|
+
* unknown-epoch refresh guard, plus the refresh-and-re-read-once wrapper
|
|
78
|
+
* for hosts whose reads scan rows and count unknown-epoch skips.
|
|
79
|
+
* - `createRefreshingEdvDocCipher` -- `createEdvDocCipher` bound to both: a
|
|
80
|
+
* cipher that acquires its own descriptor and, on an unknown-epoch decrypt,
|
|
81
|
+
* re-reads the description, swaps itself, and retries exactly once per
|
|
82
|
+
* instance.
|
|
55
83
|
*/
|
|
56
84
|
export { createEdvEncryption, EdvCodec, wasTransportFactory } from './EdvCodec.js';
|
|
57
85
|
export type { CodecTransportFactory, EdvKeys } from './EdvCodec.js';
|
|
@@ -72,4 +100,9 @@ export { HMAC_KEY_TYPE, mintHmacKey, hmacKeyFromSecret, resolveHmacKey } from '.
|
|
|
72
100
|
export type { BlindingKey } from './hmacKey.js';
|
|
73
101
|
export { createEdvDocCipher, createEdvEncryptOnlyDocCipher, ownerRecipient, EncryptOnlyCipherError, KeyUnwrapError, UnknownEpochError, isEncryptedEnvelope } from './docCipher.js';
|
|
74
102
|
export type { DocCipher, EdvDocCipher } from './docCipher.js';
|
|
103
|
+
export { acquireDescriptor, acquireDescriptors, wasDescriptorSource } from './acquire.js';
|
|
104
|
+
export type { EncryptionDescriptorCache, EncryptionDescriptorSource } from './acquire.js';
|
|
105
|
+
export { DescriptorRefreshPolicy } from './refresh.js';
|
|
106
|
+
export { createRefreshingEdvDocCipher } from './refreshingDocCipher.js';
|
|
107
|
+
export { isKeyUnwrapError } from '../sync/predicates.js';
|
|
75
108
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/edv/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/edv/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/edv/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+EG;AACH,OAAO,EACL,mBAAmB,EACnB,QAAQ,EACR,mBAAmB,EACpB,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACtE,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,gBAAgB,EACjB,MAAM,iBAAiB,CAAA;AACxB,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EACL,eAAe,EACf,yBAAyB,EAC1B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACxB,MAAM,sBAAsB,CAAA;AAC7B,YAAY,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAA;AACrE,OAAO,EACL,8BAA8B,EAC9B,oCAAoC,EACpC,0BAA0B,EAC1B,8BAA8B,EAC9B,yBAAyB,EAC1B,MAAM,iCAAiC,CAAA;AACxC,YAAY,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAA;AACjF,OAAO,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,eAAe,EAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,EACL,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,cAAc,EACf,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,cAAc,EACd,sBAAsB,EACtB,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,gBAAgB,CAAA;AACvB,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC3B,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAA;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA"}
|
package/dist/edv/index.js
CHANGED
|
@@ -50,8 +50,36 @@
|
|
|
50
50
|
* cannot help). Both ship from this subpath beside the cipher that raises
|
|
51
51
|
* them, so a consumer classifying what a cipher threw need not reach for the
|
|
52
52
|
* package root. Both assign their `name` explicitly, and a consumer whose
|
|
53
|
-
* cipher arrives through an injected seam
|
|
54
|
-
*
|
|
53
|
+
* cipher arrives through an injected seam matches on that name rather than
|
|
54
|
+
* `instanceof`, since the seam may resolve to a second copy of this package:
|
|
55
|
+
* `isKeyUnwrapError` here, and `isUnknownEpochError` on `./sync`, are those
|
|
56
|
+
* matchers.
|
|
57
|
+
*
|
|
58
|
+
* Which epoch a collection encrypts under, and when to ask again, is the
|
|
59
|
+
* descriptor acquisition and refresh policy every consumer running an
|
|
60
|
+
* encrypted collection must share (a drift between two replicas does not fail
|
|
61
|
+
* loudly; it fails as a resource one of them cannot decrypt):
|
|
62
|
+
*
|
|
63
|
+
* - `EncryptionDescriptorSource` / `EncryptionDescriptorCache` -- the narrow
|
|
64
|
+
* seams a host implements: one signed Collection Description read, and a
|
|
65
|
+
* client-local get/put pre-scoped to one Space. `wasDescriptorSource` is the
|
|
66
|
+
* source over a `WasClient` handle.
|
|
67
|
+
* - `acquireDescriptor` / `acquireDescriptors` -- fetch + cache with the
|
|
68
|
+
* cached fallback whenever the fetch yields no descriptor, thrown or empty
|
|
69
|
+
* (offline, a collection keeps encrypting under its current epoch; an empty
|
|
70
|
+
* description is ambiguous, since WAS masks an unauthorized read as an
|
|
71
|
+
* absent one). No descriptor anywhere means a plaintext collection, or an
|
|
72
|
+
* encrypted one whose epoch[0] install has not landed -- which a caller that
|
|
73
|
+
* has declared the collection encrypted must refuse fail-closed. A
|
|
74
|
+
* log-governed source's refusal classes rethrow instead of falling back
|
|
75
|
+
* (except a continuity rollback, which is reconcilable divergence).
|
|
76
|
+
* - `DescriptorRefreshPolicy` -- the once-per-collection-per-session
|
|
77
|
+
* unknown-epoch refresh guard, plus the refresh-and-re-read-once wrapper
|
|
78
|
+
* for hosts whose reads scan rows and count unknown-epoch skips.
|
|
79
|
+
* - `createRefreshingEdvDocCipher` -- `createEdvDocCipher` bound to both: a
|
|
80
|
+
* cipher that acquires its own descriptor and, on an unknown-epoch decrypt,
|
|
81
|
+
* re-reads the description, swaps itself, and retries exactly once per
|
|
82
|
+
* instance.
|
|
55
83
|
*/
|
|
56
84
|
export { createEdvEncryption, EdvCodec, wasTransportFactory } from './EdvCodec.js';
|
|
57
85
|
export { WasTransport } from './WasTransport.js';
|
|
@@ -65,4 +93,8 @@ export { hasKeyEpochs, epochRostersEqual } from './epochRoster.js';
|
|
|
65
93
|
export { resolveEpochKeys } from './epochKeys.js';
|
|
66
94
|
export { HMAC_KEY_TYPE, mintHmacKey, hmacKeyFromSecret, resolveHmacKey } from './hmacKey.js';
|
|
67
95
|
export { createEdvDocCipher, createEdvEncryptOnlyDocCipher, ownerRecipient, EncryptOnlyCipherError, KeyUnwrapError, UnknownEpochError, isEncryptedEnvelope } from './docCipher.js';
|
|
96
|
+
export { acquireDescriptor, acquireDescriptors, wasDescriptorSource } from './acquire.js';
|
|
97
|
+
export { DescriptorRefreshPolicy } from './refresh.js';
|
|
98
|
+
export { createRefreshingEdvDocCipher } from './refreshingDocCipher.js';
|
|
99
|
+
export { isKeyUnwrapError } from '../sync/predicates.js';
|
|
68
100
|
//# sourceMappingURL=index.js.map
|
package/dist/edv/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/edv/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/edv/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+EG;AACH,OAAO,EACL,mBAAmB,EACnB,QAAQ,EACR,mBAAmB,EACpB,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACtE,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,gBAAgB,EACjB,MAAM,iBAAiB,CAAA;AAExB,OAAO,EACL,eAAe,EACf,yBAAyB,EAC1B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACxB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EACL,8BAA8B,EAC9B,oCAAoC,EACpC,0BAA0B,EAC1B,8BAA8B,EAC9B,yBAAyB,EAC1B,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,eAAe,EAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEjD,OAAO,EACL,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,cAAc,EACf,MAAM,cAAc,CAAA;AAErB,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,cAAc,EACd,sBAAsB,EACtB,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,cAAc,CAAA;AAKrB,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAA;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
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 descriptor 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 descriptor will ever route) cannot drive
|
|
11
|
+
* a 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 descriptor(s), rebuild the
|
|
18
|
+
* cipher(s), and install them wherever the host keeps them.
|
|
19
|
+
*/
|
|
20
|
+
export declare class DescriptorRefreshPolicy {
|
|
21
|
+
#private;
|
|
22
|
+
constructor({ refresh }: {
|
|
23
|
+
refresh: (options: {
|
|
24
|
+
collectionId: string;
|
|
25
|
+
}) => Promise<void>;
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Whether a collection still has its one refresh this session.
|
|
29
|
+
*/
|
|
30
|
+
shouldRefresh({ collectionId }: {
|
|
31
|
+
collectionId: string;
|
|
32
|
+
}): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Runs a read that reports whether it skipped unknown-epoch rows; on the
|
|
35
|
+
* first such report for a collection this session, spends the collection's
|
|
36
|
+
* refresh (descriptor re-read + cipher swap, via the injected `refresh`) and
|
|
37
|
+
* re-reads once. A later unknown-epoch report for the same collection
|
|
38
|
+
* returns the read's value as-is.
|
|
39
|
+
*
|
|
40
|
+
* @param options {object}
|
|
41
|
+
* @param options.collectionId {string}
|
|
42
|
+
* @param options.read {function} the read, reporting `unknownEpoch`
|
|
43
|
+
* @returns {Promise<T>} the (possibly re-read) value
|
|
44
|
+
*/
|
|
45
|
+
readWithRefresh<T>({ collectionId, read }: {
|
|
46
|
+
collectionId: string;
|
|
47
|
+
read: () => Promise<{
|
|
48
|
+
value: T;
|
|
49
|
+
unknownEpoch: boolean;
|
|
50
|
+
}>;
|
|
51
|
+
}): Promise<T>;
|
|
52
|
+
/**
|
|
53
|
+
* Re-arms the guard -- for one collection, or (with no argument) for all.
|
|
54
|
+
* Call when a fresh descriptor 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
|
+
collectionId?: string;
|
|
60
|
+
}): void;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=refresh.d.ts.map
|