@interop/was-client 0.50.0 → 0.52.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 +59 -7
- package/dist/Collection.d.ts +66 -0
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +161 -53
- package/dist/Collection.js.map +1 -1
- package/dist/Resource.js +2 -2
- package/dist/Resource.js.map +1 -1
- package/dist/Space.d.ts.map +1 -1
- package/dist/Space.js +15 -11
- package/dist/Space.js.map +1 -1
- package/dist/codec.d.ts +54 -12
- package/dist/codec.d.ts.map +1 -1
- package/dist/edv/EdvCodec.d.ts +9 -8
- package/dist/edv/EdvCodec.d.ts.map +1 -1
- package/dist/edv/EdvCodec.js +65 -33
- package/dist/edv/EdvCodec.js.map +1 -1
- package/dist/edv/descriptorStore.d.ts.map +1 -1
- package/dist/edv/descriptorStore.js +2 -0
- package/dist/edv/descriptorStore.js.map +1 -1
- package/dist/edv/docCipher.d.ts.map +1 -1
- package/dist/edv/docCipher.js +1 -1
- package/dist/edv/docCipher.js.map +1 -1
- package/dist/edv/index.d.ts +2 -0
- package/dist/edv/index.d.ts.map +1 -1
- package/dist/edv/index.js +1 -0
- package/dist/edv/index.js.map +1 -1
- package/dist/edv/logGovernedDescriptorStore.d.ts +164 -0
- package/dist/edv/logGovernedDescriptorStore.d.ts.map +1 -0
- package/dist/edv/logGovernedDescriptorStore.js +476 -0
- package/dist/edv/logGovernedDescriptorStore.js.map +1 -0
- package/dist/edv/recipients.d.ts.map +1 -1
- package/dist/edv/recipients.js +25 -82
- package/dist/edv/recipients.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/internal/cas.d.ts +95 -0
- package/dist/internal/cas.d.ts.map +1 -0
- package/dist/internal/cas.js +116 -0
- package/dist/internal/cas.js.map +1 -0
- package/dist/internal/codec.d.ts.map +1 -1
- package/dist/internal/codec.js +6 -6
- package/dist/internal/codec.js.map +1 -1
- package/dist/internal/conditional.d.ts +12 -0
- package/dist/internal/conditional.d.ts.map +1 -1
- package/dist/internal/conditional.js +17 -0
- package/dist/internal/conditional.js.map +1 -1
- package/dist/internal/content.d.ts +8 -0
- package/dist/internal/content.d.ts.map +1 -1
- package/dist/internal/content.js +8 -0
- package/dist/internal/content.js.map +1 -1
- package/dist/internal/meta.d.ts +9 -9
- package/dist/internal/meta.d.ts.map +1 -1
- package/dist/internal/meta.js +13 -14
- package/dist/internal/meta.js.map +1 -1
- package/dist/internal/paths.d.ts +6 -0
- package/dist/internal/paths.d.ts.map +1 -1
- package/dist/internal/paths.js +8 -0
- package/dist/internal/paths.js.map +1 -1
- package/dist/internal/write.d.ts +1 -1
- package/dist/internal/write.d.ts.map +1 -1
- package/dist/internal/write.js +4 -2
- package/dist/internal/write.js.map +1 -1
- package/dist/log/index.d.ts +2 -1
- package/dist/log/index.d.ts.map +1 -1
- package/dist/log/index.js +2 -1
- package/dist/log/index.js.map +1 -1
- package/dist/log/logStore.d.ts +38 -25
- package/dist/log/logStore.d.ts.map +1 -1
- package/dist/log/logStore.js +99 -42
- package/dist/log/logStore.js.map +1 -1
- package/dist/paths.d.ts +1 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +1 -1
- package/dist/paths.js.map +1 -1
- package/dist/sync/provisioning.d.ts.map +1 -1
- package/dist/sync/provisioning.js +49 -14
- package/dist/sync/provisioning.js.map +1 -1
- package/dist/types.d.ts +11 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The log-governed case of the descriptor-store seam. Two stores:
|
|
6
|
+
*
|
|
7
|
+
* - `logGovernedDescriptorStore` is the generic one, over any
|
|
8
|
+
* `ResourceLogStore` (a Resource-hosted key roster, a Collection's history
|
|
9
|
+
* log): reads resolve to the log's VERIFIED head state -- chain, proofs,
|
|
10
|
+
* external authorization against a caller-supplied controller view, and the
|
|
11
|
+
* chain-head pin all checked by `@interop/vh-resource-log` before any
|
|
12
|
+
* descriptor is handed out -- and writes become signed appends (verified-head
|
|
13
|
+
* build, the library's pre-write pass, compare-and-swap, read-back and pin).
|
|
14
|
+
* It also carries the sealing sweep, the idempotent backstop append that
|
|
15
|
+
* advances a log's head past the controller's latest membership change.
|
|
16
|
+
* - `logGovernedCollectionDescriptorStore` is the pointer-following one, over a
|
|
17
|
+
* Collection whose served `encryption` member is a point-state projection of
|
|
18
|
+
* its governing history log, named by the projection's `history` member. It
|
|
19
|
+
* dispatches on that member, and for a governed Collection reads through the
|
|
20
|
+
* generic store and then holds the served projection to the verified head:
|
|
21
|
+
* a projection that does not JCS-equal the head's state after stripping
|
|
22
|
+
* `history` is refused.
|
|
23
|
+
*
|
|
24
|
+
* The generic read boundary (`readGovernedEpochConfiguration`) and the
|
|
25
|
+
* `WasEpochConfiguration` state shape are shared with the wallet's user key
|
|
26
|
+
* roster, which wraps the generic store. What stays outside this module is
|
|
27
|
+
* DID-method resolution: the controller port and the pin store are injected by
|
|
28
|
+
* the caller.
|
|
29
|
+
*/
|
|
30
|
+
import { canonicalize } from 'json-canonicalize';
|
|
31
|
+
import { RESOURCE_LOG_METHOD } from '@interop/storage-core';
|
|
32
|
+
import { buildResourceLogEntry, confirmAppend, createResourceLog, isResourceLogConflictError, readResourceLog, ResourceLogClosedError, ResourceLogIntegrityError, sealResourceLog, verifyResourceLog, verifyResourceLogAppend } from '@interop/vh-resource-log';
|
|
33
|
+
import { PreconditionFailedError, ValidationError } from '../errors.js';
|
|
34
|
+
import { unreadableDescriptionError } from '../internal/describe.js';
|
|
35
|
+
import { resourceLogStore } from '../log/logStore.js';
|
|
36
|
+
/**
|
|
37
|
+
* The state-document schema identifier an encryption descriptor carries in a
|
|
38
|
+
* governed log entry, per the Encrypted Collections profile.
|
|
39
|
+
*/
|
|
40
|
+
export const EPOCH_CONFIGURATION_STATE_TYPE = 'WasEpochConfiguration';
|
|
41
|
+
/**
|
|
42
|
+
* The log-entry `state` for a descriptor: the point-state projection's
|
|
43
|
+
* `history` member stripped (it belongs to the projection only; the verifier
|
|
44
|
+
* refuses an entry whose state carries it) and the schema identifier stamped.
|
|
45
|
+
*
|
|
46
|
+
* @param descriptor {CollectionEncryption}
|
|
47
|
+
* @returns {CollectionEncryption & { type: string }}
|
|
48
|
+
*/
|
|
49
|
+
export function toEpochConfigurationState(descriptor) {
|
|
50
|
+
const { history: _history, ...rest } = descriptor;
|
|
51
|
+
return { ...rest, type: EPOCH_CONFIGURATION_STATE_TYPE };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The one governed epoch-configuration read: the fail-closed boundary that
|
|
55
|
+
* decides whether a served log state may be treated as an encryption
|
|
56
|
+
* descriptor. Resolves the controller view, reads and fully verifies the log
|
|
57
|
+
* (chain, proofs, external authorization, and the chain-head pin, via
|
|
58
|
+
* `readResourceLog`), and refuses a verified head whose state is not a
|
|
59
|
+
* `WasEpochConfiguration` rather than handing it out as a descriptor.
|
|
60
|
+
* Resolves `null` for an absent log (the pre-genesis state) only while no pin
|
|
61
|
+
* is held for it; under a held pin an absent log is refused as a `rollback`,
|
|
62
|
+
* the library's rule. Every trusted descriptor read goes through this helper,
|
|
63
|
+
* so a hardening applied here reaches all of them.
|
|
64
|
+
*
|
|
65
|
+
* @param options {object}
|
|
66
|
+
* @param options.store {ResourceLogStore} the log's transport seam
|
|
67
|
+
* @param options.resolveController {function}
|
|
68
|
+
* `() => Promise<ResourceLogController>` -- the caller's currently verified
|
|
69
|
+
* controller view, resolved per operation
|
|
70
|
+
* @param options.pinStore {ResourceLogPinStore} this client's chain-head
|
|
71
|
+
* pin for this log
|
|
72
|
+
* @param options.logId {string} the pin-slot key for this log, from
|
|
73
|
+
* `resourceLogPinId`
|
|
74
|
+
* @returns {Promise<{ verified: VerifiedResourceLog; descriptor: CollectionEncryption; controller: ResourceLogController; etag?: string } | null>}
|
|
75
|
+
* the verified log, its head state as the descriptor, the controller view
|
|
76
|
+
* it was verified under, and the log's validator
|
|
77
|
+
*/
|
|
78
|
+
export async function readGovernedEpochConfiguration({ store, resolveController, pinStore, logId }) {
|
|
79
|
+
const controller = await resolveController();
|
|
80
|
+
const current = await readResourceLog({
|
|
81
|
+
store,
|
|
82
|
+
controller,
|
|
83
|
+
expectedMethod: RESOURCE_LOG_METHOD,
|
|
84
|
+
pinStore,
|
|
85
|
+
logId
|
|
86
|
+
});
|
|
87
|
+
if (current === null) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const state = current.verified.state;
|
|
91
|
+
if (state.type !== EPOCH_CONFIGURATION_STATE_TYPE) {
|
|
92
|
+
throw new ResourceLogIntegrityError(`The governed descriptor log carries state of type ` +
|
|
93
|
+
`"${state.type}", not "${EPOCH_CONFIGURATION_STATE_TYPE}".`);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
verified: current.verified,
|
|
97
|
+
descriptor: state,
|
|
98
|
+
controller,
|
|
99
|
+
etag: current.etag
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Translates the log-store port's CAS conflict (the library's
|
|
104
|
+
* `ResourceLogConflictError`, matched by `name` -- it is minted in the store
|
|
105
|
+
* adapter's package, which may resolve its own library copy) into the
|
|
106
|
+
* `PreconditionFailedError` the `EncryptionDescriptorStore` port documents
|
|
107
|
+
* and the edv recipient loops rebase on. Any other error -- an untranslated
|
|
108
|
+
* `PreconditionFailedError` included -- returns unchanged.
|
|
109
|
+
*
|
|
110
|
+
* @param err {unknown}
|
|
111
|
+
* @returns {unknown} the error to rethrow
|
|
112
|
+
*/
|
|
113
|
+
function asDescriptorStoreConflict(err) {
|
|
114
|
+
if (isResourceLogConflictError(err)) {
|
|
115
|
+
return new PreconditionFailedError(err.message, {
|
|
116
|
+
status: 412,
|
|
117
|
+
cause: err
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return err;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Builds the generic log-governed `EncryptionDescriptorStore` over a resource
|
|
124
|
+
* log's transport seam. Because the seam is the same one the edv recipient
|
|
125
|
+
* primitives (`initRecipients` / `addRecipient` / `removeRecipient`, with
|
|
126
|
+
* their compare-and-swap retry loops) already drive, they run over a log
|
|
127
|
+
* without knowing it: a lost race on the log is translated back to the
|
|
128
|
+
* `PreconditionFailedError` those loops rebase on.
|
|
129
|
+
*
|
|
130
|
+
* The controller view is resolved per operation rather than held, so a
|
|
131
|
+
* caller that just edited its controller document hands every subsequent
|
|
132
|
+
* write the view it now verifies. A replace builds its entry on the head the most
|
|
133
|
+
* recent read on this instance verified, pinned to that read's `etag`, so a
|
|
134
|
+
* stale head loses the compare-and-swap instead of forking; the library's
|
|
135
|
+
* pre-write pass verifies the candidate as a reader would before it lands,
|
|
136
|
+
* so a refused entry never poisons the served log for other readers.
|
|
137
|
+
*
|
|
138
|
+
* @param options {object}
|
|
139
|
+
* @param options.log {ResourceLogStore} the log's transport seam
|
|
140
|
+
* @param options.resolveController {function}
|
|
141
|
+
* `() => Promise<ResourceLogController>` -- the caller's currently verified
|
|
142
|
+
* controller view, resolved per operation
|
|
143
|
+
* @param options.pinStore {ResourceLogPinStore} this client's chain-head pin
|
|
144
|
+
* for this log
|
|
145
|
+
* @param options.logId {string} the pin-slot key for this log, from
|
|
146
|
+
* `resourceLogPinId`
|
|
147
|
+
* @param [options.signer] {ResourceLogSigner} this client's enrolled
|
|
148
|
+
* signing key, for the appends this store writes; absent, the store is
|
|
149
|
+
* read-only
|
|
150
|
+
* @returns {LogGovernedDescriptorStore}
|
|
151
|
+
*/
|
|
152
|
+
export function logGovernedDescriptorStore({ log, resolveController, pinStore, logId, signer }) {
|
|
153
|
+
// The verified log observed by the most recent read or confirmed append on
|
|
154
|
+
// this store instance, and the controller view it was verified under: the
|
|
155
|
+
// library's pre-write pass reads the head's controller version as an index
|
|
156
|
+
// into THAT view's version list, so a replace may only run it against a
|
|
157
|
+
// view this one is a prefix of.
|
|
158
|
+
let lastVerified = null;
|
|
159
|
+
let lastVerifiedView = null;
|
|
160
|
+
function requireSigner(operation) {
|
|
161
|
+
if (signer === undefined) {
|
|
162
|
+
throw new ValidationError(`Cannot ${operation} the governed descriptor: this store was built ` +
|
|
163
|
+
'without a signer, so it is read-only.');
|
|
164
|
+
}
|
|
165
|
+
return signer;
|
|
166
|
+
}
|
|
167
|
+
async function settle({ entry, controller }) {
|
|
168
|
+
const readBack = await confirmAppend({ store: log, entry });
|
|
169
|
+
const confirmed = await verifyResourceLog({
|
|
170
|
+
entries: readBack.entries,
|
|
171
|
+
controller,
|
|
172
|
+
expectedMethod: RESOURCE_LOG_METHOD,
|
|
173
|
+
pin: await pinStore.read({ logId })
|
|
174
|
+
});
|
|
175
|
+
await pinStore.write({ logId, pin: confirmed.pin });
|
|
176
|
+
lastVerified = confirmed;
|
|
177
|
+
lastVerifiedView = controller;
|
|
178
|
+
}
|
|
179
|
+
const store = {
|
|
180
|
+
async read() {
|
|
181
|
+
const current = await readGovernedEpochConfiguration({
|
|
182
|
+
store: log,
|
|
183
|
+
resolveController,
|
|
184
|
+
pinStore,
|
|
185
|
+
logId
|
|
186
|
+
});
|
|
187
|
+
if (current === null) {
|
|
188
|
+
lastVerified = null;
|
|
189
|
+
lastVerifiedView = null;
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
lastVerified = current.verified;
|
|
193
|
+
lastVerifiedView = current.controller;
|
|
194
|
+
return {
|
|
195
|
+
descriptor: current.descriptor,
|
|
196
|
+
verified: current.verified,
|
|
197
|
+
etag: current.etag
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
async replace(descriptor, { ifMatch }) {
|
|
201
|
+
const logSigner = requireSigner('replace');
|
|
202
|
+
if (ifMatch === undefined) {
|
|
203
|
+
throw new ValidationError('Cannot replace the governed descriptor: the backend returned no ' +
|
|
204
|
+
'validator, and the profile forbids an unconditional write.');
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* An append builds on the head this instance last verified. A replace
|
|
208
|
+
* that no read on this instance precedes (a caller seeding the
|
|
209
|
+
* compare-and-swap from a read another instance made) acquires that
|
|
210
|
+
* head now. The caller's validator still guards the append, so a seed
|
|
211
|
+
* behind the served log loses the compare-and-swap as it would anywhere.
|
|
212
|
+
*/
|
|
213
|
+
if (lastVerified === null) {
|
|
214
|
+
await store.read();
|
|
215
|
+
}
|
|
216
|
+
if (lastVerified === null) {
|
|
217
|
+
throw new ValidationError('Cannot replace the governed descriptor: the log is absent.');
|
|
218
|
+
}
|
|
219
|
+
if (lastVerified.terminal) {
|
|
220
|
+
throw new ResourceLogClosedError({ nextLog: lastVerified.terminal });
|
|
221
|
+
}
|
|
222
|
+
const controller = await resolveController();
|
|
223
|
+
/**
|
|
224
|
+
* The pre-write pass's precondition, enforced rather than assumed: the
|
|
225
|
+
* view that verified `lastVerified` must be a prefix of this one (the
|
|
226
|
+
* controller log is append-only, so carrying its head version is
|
|
227
|
+
* enough). A resolver that regressed is reported as the port's conflict
|
|
228
|
+
* class, so the edv machinery re-reads under the current view and
|
|
229
|
+
* rebases instead of the pass refusing on a bound that indexes another
|
|
230
|
+
* list.
|
|
231
|
+
*/
|
|
232
|
+
const verifiedHead = lastVerifiedView?.versionIds[lastVerifiedView.versionIds.length - 1];
|
|
233
|
+
if (verifiedHead !== undefined &&
|
|
234
|
+
!controller.versionIds.includes(verifiedHead)) {
|
|
235
|
+
throw new PreconditionFailedError('Cannot replace the governed descriptor: the controller view ' +
|
|
236
|
+
`resolved for this write does not carry version "${verifiedHead}", ` +
|
|
237
|
+
'which the preceding read verified against; re-read and retry.', { status: 412 });
|
|
238
|
+
}
|
|
239
|
+
const entry = await buildResourceLogEntry({
|
|
240
|
+
head: lastVerified.head,
|
|
241
|
+
state: toEpochConfigurationState(descriptor),
|
|
242
|
+
controller,
|
|
243
|
+
signer: logSigner
|
|
244
|
+
});
|
|
245
|
+
await verifyResourceLogAppend({ entry, controller, head: lastVerified });
|
|
246
|
+
try {
|
|
247
|
+
await log.append(entry, { ifMatch });
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
throw asDescriptorStoreConflict(err);
|
|
251
|
+
}
|
|
252
|
+
await settle({ entry, controller });
|
|
253
|
+
},
|
|
254
|
+
async seal() {
|
|
255
|
+
const logSigner = requireSigner('seal');
|
|
256
|
+
const controller = await resolveController();
|
|
257
|
+
/**
|
|
258
|
+
* Reuse the log view the most recent read or confirmed append on this
|
|
259
|
+
* store instance verified: a rotation that just appended carries a
|
|
260
|
+
* version past the removal, so the sweep resolves noop with no
|
|
261
|
+
* re-fetch, and a stale view is safe (sealResourceLog's append path
|
|
262
|
+
* re-reads before writing).
|
|
263
|
+
*/
|
|
264
|
+
const { sealed, verified } = await sealResourceLog({
|
|
265
|
+
store: log,
|
|
266
|
+
controller,
|
|
267
|
+
expectedMethod: RESOURCE_LOG_METHOD,
|
|
268
|
+
pinStore,
|
|
269
|
+
logId,
|
|
270
|
+
signer: logSigner,
|
|
271
|
+
...(lastVerified === null ? {} : { verified: lastVerified })
|
|
272
|
+
});
|
|
273
|
+
// The sweep hands back the caller's own view untouched when it had
|
|
274
|
+
// nothing to write; only a log it read or appended was verified under
|
|
275
|
+
// this call's controller view.
|
|
276
|
+
if (verified !== null && verified !== lastVerified) {
|
|
277
|
+
lastVerified = verified;
|
|
278
|
+
lastVerifiedView = controller;
|
|
279
|
+
}
|
|
280
|
+
return sealed ? 'sealed' : 'noop';
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
if (signer !== undefined) {
|
|
284
|
+
const logSigner = signer;
|
|
285
|
+
store.create = async (descriptor) => {
|
|
286
|
+
/**
|
|
287
|
+
* The library's guarded create: under a held pin it adopts the pinned
|
|
288
|
+
* slot's served log (refusing an absent one as a rollback) and builds
|
|
289
|
+
* nothing; otherwise it verifies the built genesis pre-write, creates it
|
|
290
|
+
* guarded, and confirms by read-back and pin. Either way of losing the
|
|
291
|
+
* race (a refused genesis against a log that already exists, or a lost
|
|
292
|
+
* guarded create) adopts the winner's log, and is reported as the
|
|
293
|
+
* port's conflict class so the edv machinery re-reads and adopts the
|
|
294
|
+
* winner's descriptor.
|
|
295
|
+
*/
|
|
296
|
+
const controller = await resolveController();
|
|
297
|
+
const { verified, created } = await createResourceLog({
|
|
298
|
+
store: log,
|
|
299
|
+
controller,
|
|
300
|
+
method: RESOURCE_LOG_METHOD,
|
|
301
|
+
pinStore,
|
|
302
|
+
logId,
|
|
303
|
+
signer: logSigner,
|
|
304
|
+
state: toEpochConfigurationState(descriptor)
|
|
305
|
+
});
|
|
306
|
+
lastVerified = verified;
|
|
307
|
+
lastVerifiedView = controller;
|
|
308
|
+
if (!created) {
|
|
309
|
+
throw new PreconditionFailedError('The resource log create lost its guarded-create race: a log is ' +
|
|
310
|
+
'already served; re-read and adopt it.', { status: 412 });
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return store;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Builds the `EncryptionDescriptorStore` over a Collection that may be
|
|
318
|
+
* log-governed. `read()` fetches the Collection Description and dispatches on
|
|
319
|
+
* its `encryption` member: absent, the store resolves `null` (a fresh
|
|
320
|
+
* Collection that `create` makes log-governed with a genesis entry); present
|
|
321
|
+
* without `history`, it is the plain point-state descriptor and the store
|
|
322
|
+
* behaves exactly like `collectionDescriptorStore`; present with `history`,
|
|
323
|
+
* the store refuses a `history.method` other than the profile's format
|
|
324
|
+
* identifier before any fetch, refuses a `history.resource` that is not this
|
|
325
|
+
* Collection's own log URL, opens the log through `resourceLogStore`, reads
|
|
326
|
+
* it through the generic {@link logGovernedDescriptorStore}, and holds the
|
|
327
|
+
* projection to the verified head's state after stripping `history`. The
|
|
328
|
+
* Description and the log are two reads, so a projection that equals an
|
|
329
|
+
* EARLIER entry's state is a concurrent append landing between them and is
|
|
330
|
+
* reported as `PreconditionFailedError` (412), which the recipient loops
|
|
331
|
+
* rebase on; a projection matching no entry at all is refused as
|
|
332
|
+
* `ResourceLogIntegrityError`. The descriptor handed out is the verified head
|
|
333
|
+
* state with the served `history` pointer kept on it.
|
|
334
|
+
*
|
|
335
|
+
* Writes on a governed Collection are the generic store's signed appends and
|
|
336
|
+
* need `signer`; without one the store is read-only for governed collections
|
|
337
|
+
* (`replace` and `seal` refuse, `create` is absent). A lost race surfaces as
|
|
338
|
+
* `PreconditionFailedError` (412), the port's documented class. `seal()` is
|
|
339
|
+
* the generic store's sealing sweep over the Collection's log.
|
|
340
|
+
*
|
|
341
|
+
* @param options {object}
|
|
342
|
+
* @param options.collection {Collection}
|
|
343
|
+
* @param options.resolveController {function}
|
|
344
|
+
* `() => Promise<ResourceLogController>` -- the caller's currently verified
|
|
345
|
+
* controller view, resolved per operation
|
|
346
|
+
* @param options.pinStore {ResourceLogPinStore} this client's chain-head pin
|
|
347
|
+
* for this log
|
|
348
|
+
* @param options.logId {string} the pin-slot key for this log, from
|
|
349
|
+
* `resourceLogPinId`
|
|
350
|
+
* @param [options.signer] {ResourceLogSigner} this client's enrolled
|
|
351
|
+
* signing key, for the appends this store writes
|
|
352
|
+
* @returns {LogGovernedDescriptorStore}
|
|
353
|
+
*/
|
|
354
|
+
export function logGovernedCollectionDescriptorStore({ collection, resolveController, pinStore, logId, signer }) {
|
|
355
|
+
const governed = logGovernedDescriptorStore({
|
|
356
|
+
log: resourceLogStore({ collection }),
|
|
357
|
+
resolveController,
|
|
358
|
+
pinStore,
|
|
359
|
+
logId,
|
|
360
|
+
signer
|
|
361
|
+
});
|
|
362
|
+
/**
|
|
363
|
+
* The description observed by the most recent read: its sibling fields are
|
|
364
|
+
* forwarded by a point-state replace (the server's replace semantics would
|
|
365
|
+
* otherwise drop them), and its `encryption` member decides which write path
|
|
366
|
+
* a replace takes. Safe to carry even if stale: every write is pinned to the
|
|
367
|
+
* same read's validator, so a concurrent change fails the CAS instead. A
|
|
368
|
+
* create clears it, since the Collection it described is now log-governed.
|
|
369
|
+
*/
|
|
370
|
+
let described;
|
|
371
|
+
async function readGoverned(projection) {
|
|
372
|
+
const { history, ...pointState } = projection;
|
|
373
|
+
if (history.method !== RESOURCE_LOG_METHOD) {
|
|
374
|
+
throw new ResourceLogIntegrityError(`The descriptor's history names the format "${history.method}", ` +
|
|
375
|
+
`not "${RESOURCE_LOG_METHOD}".`);
|
|
376
|
+
}
|
|
377
|
+
if (!sameUrl(history.resource, collection.historyLogUrl)) {
|
|
378
|
+
throw new ResourceLogIntegrityError(`The descriptor's history names the log "${history.resource}", ` +
|
|
379
|
+
`not this Collection's own log "${collection.historyLogUrl}".`);
|
|
380
|
+
}
|
|
381
|
+
const current = await governed.read();
|
|
382
|
+
if (current === null) {
|
|
383
|
+
throw new ResourceLogIntegrityError('The descriptor names a governing log, but the Collection serves none.');
|
|
384
|
+
}
|
|
385
|
+
// The generic store's descriptor IS the verified head's state.
|
|
386
|
+
const served = canonicalize(pointState);
|
|
387
|
+
if (served !== canonicalize(current.descriptor)) {
|
|
388
|
+
/**
|
|
389
|
+
* The Description was read before the log. A projection that equals an
|
|
390
|
+
* earlier entry's state is the head the server derived it from before a
|
|
391
|
+
* concurrent append moved the log on: a lost race, not a forgery, so
|
|
392
|
+
* the port's conflict class lets the caller re-read and rebase.
|
|
393
|
+
*/
|
|
394
|
+
const entries = current.verified?.entries ?? [];
|
|
395
|
+
const stale = entries
|
|
396
|
+
.slice(0, -1)
|
|
397
|
+
.some(entry => canonicalize(entry.state) === served);
|
|
398
|
+
if (stale) {
|
|
399
|
+
throw new PreconditionFailedError('The served descriptor is behind the verified head of its ' +
|
|
400
|
+
'governing log (a concurrent append landed); re-read and retry.', { status: 412 });
|
|
401
|
+
}
|
|
402
|
+
throw new ResourceLogIntegrityError('The served descriptor does not match the verified head of its ' +
|
|
403
|
+
'governing log.');
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
descriptor: { ...current.descriptor, history },
|
|
407
|
+
etag: current.etag
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
const store = {
|
|
411
|
+
async read() {
|
|
412
|
+
const current = await collection.describeWithEtag();
|
|
413
|
+
if (current === null) {
|
|
414
|
+
throw unreadableDescriptionError({
|
|
415
|
+
operation: 'manage recipients',
|
|
416
|
+
advice: 'Use a capability that can read the Collection Description.'
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
described = current.description;
|
|
420
|
+
const descriptor = current.description.encryption;
|
|
421
|
+
if (!descriptor) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
if (descriptor.scheme !== 'edv') {
|
|
425
|
+
throw new ValidationError('Cannot manage recipients: this collection is not declared ' +
|
|
426
|
+
"encrypted with the 'edv' scheme.");
|
|
427
|
+
}
|
|
428
|
+
if (descriptor.history === undefined) {
|
|
429
|
+
return { descriptor, etag: current.etag };
|
|
430
|
+
}
|
|
431
|
+
return readGoverned(descriptor);
|
|
432
|
+
},
|
|
433
|
+
async replace(descriptor, { ifMatch }) {
|
|
434
|
+
if (described === undefined) {
|
|
435
|
+
await store.read();
|
|
436
|
+
}
|
|
437
|
+
if (described?.encryption?.history !== undefined) {
|
|
438
|
+
await governed.replace(descriptor, { ifMatch });
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
await collection.replaceDescription({
|
|
442
|
+
name: described?.name,
|
|
443
|
+
backend: described?.backend,
|
|
444
|
+
generator: described?.generator,
|
|
445
|
+
generatorOrigin: described?.generatorOrigin,
|
|
446
|
+
encryption: descriptor
|
|
447
|
+
}, { ifMatch });
|
|
448
|
+
},
|
|
449
|
+
seal: () => governed.seal()
|
|
450
|
+
};
|
|
451
|
+
if (governed.create !== undefined) {
|
|
452
|
+
const create = governed.create;
|
|
453
|
+
store.create = async (descriptor) => {
|
|
454
|
+
await create(descriptor);
|
|
455
|
+
described = undefined;
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return store;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Whether two absolute URLs name the same resource, compared normalized (so
|
|
462
|
+
* a differently cased host or a default port does not read as a mismatch).
|
|
463
|
+
*
|
|
464
|
+
* @param left {string}
|
|
465
|
+
* @param right {string}
|
|
466
|
+
* @returns {boolean}
|
|
467
|
+
*/
|
|
468
|
+
function sameUrl(left, right) {
|
|
469
|
+
try {
|
|
470
|
+
return new URL(left).href === new URL(right).href;
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
//# sourceMappingURL=logGovernedDescriptorStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logGovernedDescriptorStore.js","sourceRoot":"","sources":["../../src/edv/logGovernedDescriptorStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recipients.d.ts","sourceRoot":"","sources":["../../src/edv/recipients.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"recipients.d.ts","sourceRoot":"","sources":["../../src/edv/recipients.ts"],"names":[],"mappings":"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"}
|
package/dist/edv/recipients.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { EDV_SCHEME_VERSION } from './constants.js';
|
|
2
|
-
import { EncryptionError,
|
|
2
|
+
import { EncryptionError, ValidationError } from '../errors.js';
|
|
3
|
+
import { compareAndSwap } from '../internal/cas.js';
|
|
3
4
|
import { collectionDescriptorStore } from './descriptorStore.js';
|
|
4
5
|
import { didKeyResolver, mintEpoch, unwrapEpochSecret, wrapEpochSecret, wrapEpochSecretTo } from './epochCrypto.js';
|
|
5
6
|
import { pickEpoch } from './epochRoster.js';
|
|
6
7
|
import { mintHmacKey } from './hmacKey.js';
|
|
7
|
-
/**
|
|
8
|
-
* How many times a recipient CAS write retries a stale (`412`) description
|
|
9
|
-
* before surfacing {@link PreconditionFailedError}.
|
|
10
|
-
*/
|
|
11
|
-
const MAX_CAS_ATTEMPTS = 3;
|
|
12
8
|
/**
|
|
13
9
|
* Escrows one or more readers into a single wrap roster (one epoch's
|
|
14
10
|
* `recipients`, or the blinded-index key's): the caller unwraps its OWN entry
|
|
@@ -857,38 +853,13 @@ function descriptorStoreFor({ collection, store }) {
|
|
|
857
853
|
throw new ValidationError('A recipient operation needs its descriptor host: pass `collection` or ' +
|
|
858
854
|
'`store`.');
|
|
859
855
|
}
|
|
860
|
-
/**
|
|
861
|
-
* Whether `err` is the compare-and-swap conflict a descriptor store raises
|
|
862
|
-
* (`PreconditionFailedError`, 412). Matched by `name` as well as `instanceof`:
|
|
863
|
-
* a store implemented by a consumer (wallet-core's log-governed store) mints
|
|
864
|
-
* the conflict from its own `@interop/was-client` import, and in a tree that
|
|
865
|
-
* resolves was-client twice that class is a different object from ours, so an
|
|
866
|
-
* `instanceof`-only check would turn every lost race into a hard failure
|
|
867
|
-
* instead of a rebase.
|
|
868
|
-
*
|
|
869
|
-
* @param err {unknown}
|
|
870
|
-
* @returns {boolean}
|
|
871
|
-
*/
|
|
872
|
-
function isPreconditionFailed(err) {
|
|
873
|
-
return (err instanceof PreconditionFailedError ||
|
|
874
|
-
(err instanceof Error && err.name === 'PreconditionFailedError'));
|
|
875
|
-
}
|
|
876
856
|
/**
|
|
877
857
|
* Reads the store's descriptor, applies `mutate`, and writes the result back
|
|
878
|
-
* with a compare-and-swap
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
*
|
|
883
|
-
* written and the current descriptor is returned as-is.
|
|
884
|
-
*
|
|
885
|
-
* When the store reports no descriptor yet (`read()` resolves `null`, e.g. the
|
|
886
|
-
* resource adapter before the first `initRecipients`), the optional `seed` is
|
|
887
|
-
* mutated instead and the result written with the store's create-if-absent
|
|
888
|
-
* guard (`If-None-Match: *`). Only `initRecipients` passes a seed; without
|
|
889
|
-
* one an absent descriptor is refused. Losing the create race (a concurrent
|
|
890
|
-
* writer created the first descriptor) re-enters the loop and re-reads, like a
|
|
891
|
-
* stale CAS.
|
|
858
|
+
* with a compare-and-swap: the shared {@link compareAndSwap} loop over an
|
|
859
|
+
* `EncryptionDescriptorStore`, with the recipient-specific handling of a store
|
|
860
|
+
* that holds no descriptor yet. Only `initRecipients` passes a `seed` (mutated
|
|
861
|
+
* in place of the absent descriptor and written create-if-absent); without one
|
|
862
|
+
* an absent descriptor is refused.
|
|
892
863
|
*
|
|
893
864
|
* @param options {object}
|
|
894
865
|
* @param options.store {EncryptionDescriptorStore}
|
|
@@ -899,56 +870,28 @@ function isPreconditionFailed(err) {
|
|
|
899
870
|
* @returns {Promise<CollectionEncryption>} the written (or current) descriptor
|
|
900
871
|
*/
|
|
901
872
|
async function casUpdateDescriptor({ store, mutate, seed }) {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
873
|
+
return compareAndSwap({
|
|
874
|
+
store: {
|
|
875
|
+
async read() {
|
|
876
|
+
const current = await store.read();
|
|
877
|
+
return current === null
|
|
878
|
+
? null
|
|
879
|
+
: { value: current.descriptor, etag: current.etag };
|
|
880
|
+
},
|
|
881
|
+
replace: (descriptor, options) => store.replace(descriptor, options),
|
|
882
|
+
...(store.create !== undefined && {
|
|
883
|
+
create: (descriptor) => store.create(descriptor)
|
|
884
|
+
})
|
|
885
|
+
},
|
|
886
|
+
mutate,
|
|
887
|
+
operation: 'Recipient change',
|
|
888
|
+
onAbsent: () => {
|
|
906
889
|
if (seed === undefined) {
|
|
907
890
|
throw new ValidationError('Cannot manage recipients: this descriptor store holds no encryption ' +
|
|
908
891
|
'descriptor yet. Call initRecipients first.');
|
|
909
892
|
}
|
|
910
|
-
|
|
911
|
-
throw new ValidationError('Cannot initialize recipients: this descriptor store holds no descriptor ' +
|
|
912
|
-
'and does not support creating one.');
|
|
913
|
-
}
|
|
914
|
-
const created = await mutate(seed);
|
|
915
|
-
if (created === null) {
|
|
916
|
-
return seed;
|
|
917
|
-
}
|
|
918
|
-
try {
|
|
919
|
-
await store.create(created);
|
|
920
|
-
return created;
|
|
921
|
-
}
|
|
922
|
-
catch (err) {
|
|
923
|
-
if (isPreconditionFailed(err)) {
|
|
924
|
-
// A concurrent writer created the first descriptor: re-read and
|
|
925
|
-
// re-apply.
|
|
926
|
-
lastError = err;
|
|
927
|
-
continue;
|
|
928
|
-
}
|
|
929
|
-
throw err;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
const next = await mutate(current.descriptor);
|
|
933
|
-
if (next === null) {
|
|
934
|
-
// The descriptor already reflects the desired state: nothing to write.
|
|
935
|
-
return current.descriptor;
|
|
936
|
-
}
|
|
937
|
-
try {
|
|
938
|
-
await store.replace(next, { ifMatch: current.etag });
|
|
939
|
-
return next;
|
|
940
|
-
}
|
|
941
|
-
catch (err) {
|
|
942
|
-
if (isPreconditionFailed(err)) {
|
|
943
|
-
// A concurrent recipient change landed first: re-read and re-apply.
|
|
944
|
-
lastError = err;
|
|
945
|
-
continue;
|
|
946
|
-
}
|
|
947
|
-
throw err;
|
|
893
|
+
return seed;
|
|
948
894
|
}
|
|
949
|
-
}
|
|
950
|
-
throw new PreconditionFailedError(`Recipient change lost the compare-and-swap race after ${MAX_CAS_ATTEMPTS} ` +
|
|
951
|
-
'attempts (another writer kept updating the stored descriptor). ' +
|
|
952
|
-
'Retry the operation.', { cause: lastError });
|
|
895
|
+
});
|
|
953
896
|
}
|
|
954
897
|
//# sourceMappingURL=recipients.js.map
|