@interop/was-client 0.51.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 +23 -0
- package/dist/Collection.d.ts +19 -2
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +89 -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/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/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/log/logStore.d.ts +8 -5
- package/dist/log/logStore.d.ts.map +1 -1
- package/dist/log/logStore.js +22 -10
- package/dist/log/logStore.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
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;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,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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/edv/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;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"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { type ResourceLogController, type ResourceLogPinStore, type ResourceLogSigner, type ResourceLogStore, type VerifiedResourceLog } from '@interop/vh-resource-log';
|
|
2
|
+
import type { Collection } from '../Collection.js';
|
|
3
|
+
import type { CollectionEncryption } from '../types.js';
|
|
4
|
+
import type { EncryptionDescriptorStore } from './descriptorStore.js';
|
|
5
|
+
/**
|
|
6
|
+
* The state-document schema identifier an encryption descriptor carries in a
|
|
7
|
+
* governed log entry, per the Encrypted Collections profile.
|
|
8
|
+
*/
|
|
9
|
+
export declare const EPOCH_CONFIGURATION_STATE_TYPE = "WasEpochConfiguration";
|
|
10
|
+
/**
|
|
11
|
+
* The log-entry `state` for a descriptor: the point-state projection's
|
|
12
|
+
* `history` member stripped (it belongs to the projection only; the verifier
|
|
13
|
+
* refuses an entry whose state carries it) and the schema identifier stamped.
|
|
14
|
+
*
|
|
15
|
+
* @param descriptor {CollectionEncryption}
|
|
16
|
+
* @returns {CollectionEncryption & { type: string }}
|
|
17
|
+
*/
|
|
18
|
+
export declare function toEpochConfigurationState(descriptor: CollectionEncryption): CollectionEncryption & {
|
|
19
|
+
type: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The one governed epoch-configuration read: the fail-closed boundary that
|
|
23
|
+
* decides whether a served log state may be treated as an encryption
|
|
24
|
+
* descriptor. Resolves the controller view, reads and fully verifies the log
|
|
25
|
+
* (chain, proofs, external authorization, and the chain-head pin, via
|
|
26
|
+
* `readResourceLog`), and refuses a verified head whose state is not a
|
|
27
|
+
* `WasEpochConfiguration` rather than handing it out as a descriptor.
|
|
28
|
+
* Resolves `null` for an absent log (the pre-genesis state) only while no pin
|
|
29
|
+
* is held for it; under a held pin an absent log is refused as a `rollback`,
|
|
30
|
+
* the library's rule. Every trusted descriptor read goes through this helper,
|
|
31
|
+
* so a hardening applied here reaches all of them.
|
|
32
|
+
*
|
|
33
|
+
* @param options {object}
|
|
34
|
+
* @param options.store {ResourceLogStore} the log's transport seam
|
|
35
|
+
* @param options.resolveController {function}
|
|
36
|
+
* `() => Promise<ResourceLogController>` -- the caller's currently verified
|
|
37
|
+
* controller view, resolved per operation
|
|
38
|
+
* @param options.pinStore {ResourceLogPinStore} this client's chain-head
|
|
39
|
+
* pin for this log
|
|
40
|
+
* @param options.logId {string} the pin-slot key for this log, from
|
|
41
|
+
* `resourceLogPinId`
|
|
42
|
+
* @returns {Promise<{ verified: VerifiedResourceLog; descriptor: CollectionEncryption; controller: ResourceLogController; etag?: string } | null>}
|
|
43
|
+
* the verified log, its head state as the descriptor, the controller view
|
|
44
|
+
* it was verified under, and the log's validator
|
|
45
|
+
*/
|
|
46
|
+
export declare function readGovernedEpochConfiguration({ store, resolveController, pinStore, logId }: {
|
|
47
|
+
store: ResourceLogStore;
|
|
48
|
+
resolveController: () => Promise<ResourceLogController>;
|
|
49
|
+
pinStore: ResourceLogPinStore;
|
|
50
|
+
logId: string;
|
|
51
|
+
}): Promise<{
|
|
52
|
+
verified: VerifiedResourceLog;
|
|
53
|
+
descriptor: CollectionEncryption;
|
|
54
|
+
controller: ResourceLogController;
|
|
55
|
+
etag?: string;
|
|
56
|
+
} | null>;
|
|
57
|
+
/**
|
|
58
|
+
* An `EncryptionDescriptorStore` over a resource log, which can therefore be
|
|
59
|
+
* SEALED: `seal()` runs the sealing sweep (`sealResourceLog`) against the
|
|
60
|
+
* caller's currently verified controller view, appending the idempotent no-op
|
|
61
|
+
* backstop entry when the log's head still carries a controller version
|
|
62
|
+
* before the controller's latest membership change -- `'sealed'` -- and
|
|
63
|
+
* writing nothing when the log is already sealed, absent, or has no
|
|
64
|
+
* membership change to seal against -- `'noop'`. A store built without a
|
|
65
|
+
* signer has no `create` and refuses `replace` and `seal`.
|
|
66
|
+
*/
|
|
67
|
+
export interface LogGovernedDescriptorStore extends EncryptionDescriptorStore {
|
|
68
|
+
/**
|
|
69
|
+
* The port's read, plus the verified log the descriptor is the head state
|
|
70
|
+
* of, for a caller that needs the log's history beside its head (the
|
|
71
|
+
* Collection store uses it to tell a stale served projection from a forged
|
|
72
|
+
* one). Absent only from the Collection store's read of a Collection that
|
|
73
|
+
* is not log-governed, whose descriptor comes from no log.
|
|
74
|
+
*/
|
|
75
|
+
read(): Promise<{
|
|
76
|
+
descriptor: CollectionEncryption;
|
|
77
|
+
verified?: VerifiedResourceLog;
|
|
78
|
+
etag?: string;
|
|
79
|
+
} | null>;
|
|
80
|
+
seal(): Promise<'sealed' | 'noop'>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Builds the generic log-governed `EncryptionDescriptorStore` over a resource
|
|
84
|
+
* log's transport seam. Because the seam is the same one the edv recipient
|
|
85
|
+
* primitives (`initRecipients` / `addRecipient` / `removeRecipient`, with
|
|
86
|
+
* their compare-and-swap retry loops) already drive, they run over a log
|
|
87
|
+
* without knowing it: a lost race on the log is translated back to the
|
|
88
|
+
* `PreconditionFailedError` those loops rebase on.
|
|
89
|
+
*
|
|
90
|
+
* The controller view is resolved per operation rather than held, so a
|
|
91
|
+
* caller that just edited its controller document hands every subsequent
|
|
92
|
+
* write the view it now verifies. A replace builds its entry on the head the most
|
|
93
|
+
* recent read on this instance verified, pinned to that read's `etag`, so a
|
|
94
|
+
* stale head loses the compare-and-swap instead of forking; the library's
|
|
95
|
+
* pre-write pass verifies the candidate as a reader would before it lands,
|
|
96
|
+
* so a refused entry never poisons the served log for other readers.
|
|
97
|
+
*
|
|
98
|
+
* @param options {object}
|
|
99
|
+
* @param options.log {ResourceLogStore} the log's transport seam
|
|
100
|
+
* @param options.resolveController {function}
|
|
101
|
+
* `() => Promise<ResourceLogController>` -- the caller's currently verified
|
|
102
|
+
* controller view, resolved per operation
|
|
103
|
+
* @param options.pinStore {ResourceLogPinStore} this client's chain-head pin
|
|
104
|
+
* for this log
|
|
105
|
+
* @param options.logId {string} the pin-slot key for this log, from
|
|
106
|
+
* `resourceLogPinId`
|
|
107
|
+
* @param [options.signer] {ResourceLogSigner} this client's enrolled
|
|
108
|
+
* signing key, for the appends this store writes; absent, the store is
|
|
109
|
+
* read-only
|
|
110
|
+
* @returns {LogGovernedDescriptorStore}
|
|
111
|
+
*/
|
|
112
|
+
export declare function logGovernedDescriptorStore({ log, resolveController, pinStore, logId, signer }: {
|
|
113
|
+
log: ResourceLogStore;
|
|
114
|
+
resolveController: () => Promise<ResourceLogController>;
|
|
115
|
+
pinStore: ResourceLogPinStore;
|
|
116
|
+
logId: string;
|
|
117
|
+
signer?: ResourceLogSigner;
|
|
118
|
+
}): LogGovernedDescriptorStore;
|
|
119
|
+
/**
|
|
120
|
+
* Builds the `EncryptionDescriptorStore` over a Collection that may be
|
|
121
|
+
* log-governed. `read()` fetches the Collection Description and dispatches on
|
|
122
|
+
* its `encryption` member: absent, the store resolves `null` (a fresh
|
|
123
|
+
* Collection that `create` makes log-governed with a genesis entry); present
|
|
124
|
+
* without `history`, it is the plain point-state descriptor and the store
|
|
125
|
+
* behaves exactly like `collectionDescriptorStore`; present with `history`,
|
|
126
|
+
* the store refuses a `history.method` other than the profile's format
|
|
127
|
+
* identifier before any fetch, refuses a `history.resource` that is not this
|
|
128
|
+
* Collection's own log URL, opens the log through `resourceLogStore`, reads
|
|
129
|
+
* it through the generic {@link logGovernedDescriptorStore}, and holds the
|
|
130
|
+
* projection to the verified head's state after stripping `history`. The
|
|
131
|
+
* Description and the log are two reads, so a projection that equals an
|
|
132
|
+
* EARLIER entry's state is a concurrent append landing between them and is
|
|
133
|
+
* reported as `PreconditionFailedError` (412), which the recipient loops
|
|
134
|
+
* rebase on; a projection matching no entry at all is refused as
|
|
135
|
+
* `ResourceLogIntegrityError`. The descriptor handed out is the verified head
|
|
136
|
+
* state with the served `history` pointer kept on it.
|
|
137
|
+
*
|
|
138
|
+
* Writes on a governed Collection are the generic store's signed appends and
|
|
139
|
+
* need `signer`; without one the store is read-only for governed collections
|
|
140
|
+
* (`replace` and `seal` refuse, `create` is absent). A lost race surfaces as
|
|
141
|
+
* `PreconditionFailedError` (412), the port's documented class. `seal()` is
|
|
142
|
+
* the generic store's sealing sweep over the Collection's log.
|
|
143
|
+
*
|
|
144
|
+
* @param options {object}
|
|
145
|
+
* @param options.collection {Collection}
|
|
146
|
+
* @param options.resolveController {function}
|
|
147
|
+
* `() => Promise<ResourceLogController>` -- the caller's currently verified
|
|
148
|
+
* controller view, resolved per operation
|
|
149
|
+
* @param options.pinStore {ResourceLogPinStore} this client's chain-head pin
|
|
150
|
+
* for this log
|
|
151
|
+
* @param options.logId {string} the pin-slot key for this log, from
|
|
152
|
+
* `resourceLogPinId`
|
|
153
|
+
* @param [options.signer] {ResourceLogSigner} this client's enrolled
|
|
154
|
+
* signing key, for the appends this store writes
|
|
155
|
+
* @returns {LogGovernedDescriptorStore}
|
|
156
|
+
*/
|
|
157
|
+
export declare function logGovernedCollectionDescriptorStore({ collection, resolveController, pinStore, logId, signer }: {
|
|
158
|
+
collection: Collection;
|
|
159
|
+
resolveController: () => Promise<ResourceLogController>;
|
|
160
|
+
pinStore: ResourceLogPinStore;
|
|
161
|
+
logId: string;
|
|
162
|
+
signer?: ResourceLogSigner;
|
|
163
|
+
}): LogGovernedDescriptorStore;
|
|
164
|
+
//# sourceMappingURL=logGovernedDescriptorStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logGovernedDescriptorStore.d.ts","sourceRoot":"","sources":["../../src/edv/logGovernedDescriptorStore.ts"],"names":[],"mappings":"AA+BA,OAAO,EAWL,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAIlD,OAAO,KAAK,EAAyB,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAC9E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAA;AAErE;;;GAGG;AACH,eAAO,MAAM,8BAA8B,0BAA0B,CAAA;AAErE;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,oBAAoB,GAC/B,oBAAoB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAGzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,8BAA8B,CAAC,EACnD,KAAK,EACL,iBAAiB,EACjB,QAAQ,EACR,KAAK,EACN,EAAE;IACD,KAAK,EAAE,gBAAgB,CAAA;IACvB,iBAAiB,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACvD,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,KAAK,EAAE,MAAM,CAAA;CACd,GAAG,OAAO,CAAC;IACV,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,UAAU,EAAE,oBAAoB,CAAA;IAChC,UAAU,EAAE,qBAAqB,CAAA;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,GAAG,IAAI,CAAC,CAyBR;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,0BAA2B,SAAQ,yBAAyB;IAC3E;;;;;;OAMG;IACH,IAAI,IAAI,OAAO,CAAC;QACd,UAAU,EAAE,oBAAoB,CAAA;QAChC,QAAQ,CAAC,EAAE,mBAAmB,CAAA;QAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,GAAG,IAAI,CAAC,CAAA;IACT,IAAI,IAAI,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,CAAA;CACnC;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,GAAG,EACH,iBAAiB,EACjB,QAAQ,EACR,KAAK,EACL,MAAM,EACP,EAAE;IACD,GAAG,EAAE,gBAAgB,CAAA;IACrB,iBAAiB,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACvD,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAC3B,GAAG,0BAA0B,CA8L7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,oCAAoC,CAAC,EACnD,UAAU,EACV,iBAAiB,EACjB,QAAQ,EACR,KAAK,EACL,MAAM,EACP,EAAE;IACD,UAAU,EAAE,UAAU,CAAA;IACtB,iBAAiB,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACvD,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAC3B,GAAG,0BAA0B,CAuI7B"}
|
|
@@ -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
|