@kokuin/store-vault 0.0.0-stage → 0.1.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.
Files changed (48) hide show
  1. package/README.md +126 -2
  2. package/lib/api.d.ts +5 -0
  3. package/lib/api.js +504 -0
  4. package/lib/batch.d.ts +89 -0
  5. package/lib/batch.js +159 -0
  6. package/lib/coordinator-context.d.ts +71 -0
  7. package/lib/coordinator-context.js +410 -0
  8. package/lib/coordinator-entries.d.ts +20 -0
  9. package/lib/coordinator-entries.js +258 -0
  10. package/lib/coordinator-open.d.ts +9 -0
  11. package/lib/coordinator-open.js +141 -0
  12. package/lib/coordinator-records.d.ts +27 -0
  13. package/lib/coordinator-records.js +132 -0
  14. package/lib/coordinator-rotate.d.ts +9 -0
  15. package/lib/coordinator-rotate.js +436 -0
  16. package/lib/coordinator-types.d.ts +139 -0
  17. package/lib/coordinator-types.js +1 -0
  18. package/lib/coordinator.d.ts +2 -0
  19. package/lib/coordinator.js +459 -0
  20. package/lib/definition.d.ts +6 -0
  21. package/lib/definition.js +12 -0
  22. package/lib/index.d.ts +12 -0
  23. package/lib/index.js +7 -0
  24. package/lib/migrations.d.ts +2 -0
  25. package/lib/migrations.js +52 -0
  26. package/lib/record-context.d.ts +24 -0
  27. package/lib/record-context.js +34 -0
  28. package/lib/record-definition.d.ts +7 -0
  29. package/lib/record-definition.js +93 -0
  30. package/lib/record-tables.d.ts +47 -0
  31. package/lib/record-tables.js +1 -0
  32. package/lib/records-adapter.d.ts +3 -0
  33. package/lib/records-adapter.js +197 -0
  34. package/lib/records-ports.d.ts +4 -0
  35. package/lib/records-ports.js +72 -0
  36. package/lib/records-projection.d.ts +3 -0
  37. package/lib/records-projection.js +105 -0
  38. package/lib/records-rows.d.ts +54 -0
  39. package/lib/records-rows.js +155 -0
  40. package/lib/records.d.ts +59 -0
  41. package/lib/records.js +233 -0
  42. package/lib/settle.d.ts +12 -0
  43. package/lib/settle.js +18 -0
  44. package/lib/tables.d.ts +42 -0
  45. package/lib/tables.js +1 -0
  46. package/lib/types.d.ts +74 -0
  47. package/lib/types.js +1 -0
  48. package/package.json +45 -3
@@ -0,0 +1,436 @@
1
+ import { compareRecordDecisionHLC } from '@kokuin/records';
2
+ import { decodeVaultRecord, factorsFromDescriptors, getSuite, NoAvailableFactor, vaultRevocationRecordID } from '@kokuin/vault';
3
+ import { VaultKeyLocked, VaultNotFound, VaultRepairPending } from './coordinator-context.js';
4
+ import { settleAll } from './settle.js';
5
+ export function createRotateFunctions(ctx, open, records) {
6
+ const { params } = ctx;
7
+ function excluded(row, recipients) {
8
+ const denied = new Set(recipients.map((did)=>vaultRevocationRecordID(row.keyID, did)));
9
+ return [
10
+ row.recipientDID,
11
+ ...row.descriptors.filter((d)=>d.kind === 'did').map((d)=>d.params.recipient)
12
+ ].some((did)=>typeof did === 'string' && denied.has(vaultRevocationRecordID(row.keyID, did)));
13
+ }
14
+ async function currentEntries(key, store) {
15
+ const rows = await store.listEntries(key);
16
+ const infos = await records.entryInfos(rows, store);
17
+ return rows.map((row, index)=>{
18
+ const info = infos[index];
19
+ if (info === undefined) throw new VaultNotFound();
20
+ return {
21
+ entryID: row.entryID,
22
+ row,
23
+ ...info,
24
+ opHash: '',
25
+ keyVersion: row.keyVersion,
26
+ branchID: row.provenance
27
+ };
28
+ });
29
+ }
30
+ async function candidate(key, record) {
31
+ const op = await ctx.verified(record);
32
+ const meta = op.claim.meta;
33
+ if (op.claim.owner !== key.ownerDID || meta.keyID !== key.keyID || typeof meta.contentHLC !== 'string' || typeof meta.keyVersion !== 'number') throw new VaultRepairPending();
34
+ let row;
35
+ if (!op.claim.deleted) {
36
+ const decoded = decodeVaultRecord(record, meta);
37
+ if (decoded.type !== 'vault.entry' || decoded.meta.kind === undefined || decoded.meta.label === undefined || decoded.meta.attributes === undefined || decoded.meta.branchID === undefined) throw new VaultRepairPending();
38
+ row = {
39
+ ownerDID: key.ownerDID,
40
+ keyID: key.keyID,
41
+ entryID: record.id,
42
+ keyVersion: meta.keyVersion,
43
+ hlc: record.hlc,
44
+ provenance: decoded.meta.branchID,
45
+ kind: decoded.meta.kind,
46
+ label: decoded.meta.label,
47
+ attributes: decoded.meta.attributes,
48
+ ...decoded.payload
49
+ };
50
+ }
51
+ return {
52
+ entryID: record.id,
53
+ row,
54
+ contentHLC: meta.contentHLC,
55
+ consumer: meta.consumer,
56
+ opHash: record.opHash,
57
+ keyVersion: meta.keyVersion,
58
+ branchID: typeof meta.branchID === 'string' ? meta.branchID : ''
59
+ };
60
+ }
61
+ async function candidates(key, store) {
62
+ const api = ctx.recordsFor(store);
63
+ const [current, prior] = await settleAll([
64
+ api.listEntryCandidates({
65
+ ...key,
66
+ keyVersion: key.version
67
+ }),
68
+ api.listPriorEntryTombstones({
69
+ ...key,
70
+ beforeVersion: key.version
71
+ })
72
+ ]);
73
+ // Checks run together; the first failure in record order still wins.
74
+ const values = await settleAll([
75
+ ...current,
76
+ ...prior
77
+ ].map((record)=>candidate(key, record)));
78
+ const grouped = new Map();
79
+ for (const value of values){
80
+ const list = grouped.get(value.entryID) ?? [];
81
+ list.push(value);
82
+ grouped.set(value.entryID, list);
83
+ }
84
+ return grouped;
85
+ }
86
+ const compare = (a, b)=>{
87
+ return compareRecordDecisionHLC(a.contentHLC, b.contentHLC) || a.keyVersion - b.keyVersion || (a.opHash < b.opHash ? -1 : a.opHash > b.opHash ? 1 : 0);
88
+ };
89
+ async function rotateLocked(request, store, repair) {
90
+ if (!Array.isArray(request.excludedRecipients) || !Array.isArray(request.reissue)) throw new TypeError('Explicit exclusions and entry reissue instructions are required');
91
+ const key = await ctx.key(request, store);
92
+ const branches = await ctx.branches(key, key.version, store);
93
+ if (repair && branches.length < 2) return null;
94
+ const wrappingsRead = async ()=>{
95
+ const existing = await store.listWrappings(key);
96
+ const current = existing.filter((w)=>w.keyVersion === key.version);
97
+ return {
98
+ existing,
99
+ current,
100
+ evidence: await ctx.wrappingEvidences(current, store)
101
+ };
102
+ };
103
+ // Ordered settling keeps the original precedence: branch, then wrapping, then entry failures.
104
+ const [winner, wrappingState, currentSelections] = await settleAll([
105
+ ctx.branch(key, key.version, store),
106
+ wrappingsRead(),
107
+ currentEntries(key, store)
108
+ ]);
109
+ const expectedBranches = branches.map((b)=>b.branchID);
110
+ const existingWrappings = wrappingState.existing;
111
+ const evidence = new Map(wrappingState.evidence.map((item)=>[
112
+ item.record.id,
113
+ item
114
+ ]));
115
+ let retained = wrappingState.current.filter((w)=>!excluded(w, request.excludedRecipients));
116
+ let selections = currentSelections;
117
+ const keys = new Map();
118
+ const pending = [];
119
+ const plaintexts = [];
120
+ let pinGate = Promise.resolve();
121
+ let generated;
122
+ try {
123
+ const openSource = (row)=>{
124
+ const run = async ()=>{
125
+ const opened = await open.open({
126
+ ...request,
127
+ atVersion: row.keyVersion
128
+ }, store, params.mode === 'replicated' ? row.provenance : undefined);
129
+ return {
130
+ contentKey: opened.contentKey,
131
+ suite: opened.suite
132
+ };
133
+ };
134
+ if (request.inputs?.pin === undefined) return run();
135
+ // PIN opens stay one at a time: a rejected PIN stops later attempts, spending one try.
136
+ const gated = pinGate.then(run);
137
+ pinGate = gated;
138
+ return gated;
139
+ };
140
+ const decrypt = (selection)=>{
141
+ const task = (async ()=>{
142
+ if (selection.row === undefined) return null;
143
+ const row = selection.row;
144
+ const identity = JSON.stringify([
145
+ row.keyVersion,
146
+ row.provenance
147
+ ]);
148
+ let source = keys.get(identity);
149
+ // Cache the promise, not the key: concurrent decrypts must share one open.
150
+ if (source === undefined) {
151
+ source = openSource(row);
152
+ keys.set(identity, source);
153
+ }
154
+ const { contentKey, suite } = await source;
155
+ const plaintext = getSuite(suite).decryptEntry({
156
+ ...row,
157
+ contentKey
158
+ });
159
+ plaintexts.push(plaintext);
160
+ return plaintext;
161
+ })();
162
+ pending.push(task);
163
+ return task;
164
+ };
165
+ if (repair) {
166
+ const grouped = await candidates(key, store);
167
+ const selected = [];
168
+ let addsEntry = false;
169
+ const comparisons = [];
170
+ for (const list of grouped.values()){
171
+ list.sort(compare);
172
+ const chosen = list.at(-1);
173
+ if (chosen === undefined) continue;
174
+ selected.push(chosen);
175
+ if (chosen.branchID === winner.branchID) continue;
176
+ const winning = list.filter((s)=>s.branchID === winner.branchID).sort(compare).at(-1);
177
+ if (winning === undefined || chosen.contentHLC !== winning.contentHLC) {
178
+ addsEntry = true;
179
+ continue;
180
+ }
181
+ comparisons.push([
182
+ chosen,
183
+ winning
184
+ ]);
185
+ }
186
+ // Plaintext comparisons are independent. Decrypts run together; the first failure in
187
+ // group order wins.
188
+ const compared = await settleAll(comparisons.map(([chosen, winning])=>settleAll([
189
+ decrypt(chosen),
190
+ decrypt(winning)
191
+ ])));
192
+ for (const [a, b] of compared)if (a === null !== (b === null) || a !== null && b !== null && (a.length !== b.length || !a.every((v, i)=>v === b[i]))) addsEntry = true;
193
+ const signature = (w)=>{
194
+ return JSON.stringify({
195
+ recipient: w.recipientDID,
196
+ factors: factorsFromDescriptors(w.descriptors, request.inputs)
197
+ });
198
+ };
199
+ const winnerRecipients = new Set(retained.filter((w)=>w.provenance === winner.branchID).map(signature));
200
+ const addsRecipient = retained.some((w)=>!winnerRecipients.has(signature(w)));
201
+ let unreadableProjection = false;
202
+ const versionBranches = new Map();
203
+ for (const selection of selections){
204
+ if (selection.row === undefined) continue;
205
+ // Selections share few key versions. Read each version's branch once.
206
+ let branch = versionBranches.get(selection.keyVersion);
207
+ if (branch === undefined) {
208
+ branch = ctx.branch(key, selection.keyVersion, store);
209
+ versionBranches.set(selection.keyVersion, branch);
210
+ }
211
+ if (selection.branchID !== (await branch).branchID) {
212
+ unreadableProjection = true;
213
+ break;
214
+ }
215
+ }
216
+ if (!addsEntry && !addsRecipient && !unreadableProjection && request.reissue.length === 0 && request.excludedRecipients.length === 0) return null;
217
+ const unique = new Map();
218
+ for (const wrapping of [
219
+ ...retained.filter((w)=>w.provenance === winner.branchID),
220
+ ...retained
221
+ ]){
222
+ const id = signature(wrapping);
223
+ if (!unique.has(id)) unique.set(id, wrapping);
224
+ }
225
+ retained = [
226
+ ...unique.values()
227
+ ];
228
+ selections = selected;
229
+ }
230
+ const instructions = new Map(request.reissue.map((instruction)=>[
231
+ instruction.entryID,
232
+ instruction
233
+ ]));
234
+ if (instructions.size !== request.reissue.length || [
235
+ ...instructions.keys()
236
+ ].some((id)=>!selections.some((s)=>s.entryID === id))) throw new TypeError('Reissue instructions must name distinct existing entries');
237
+ const suite = getSuite(request.suite ?? key.suite);
238
+ const next = {
239
+ ...key,
240
+ version: key.version + 1,
241
+ suite: suite.id
242
+ };
243
+ const contentKey = suite.generateContentKey(params.runtime);
244
+ generated = contentKey;
245
+ // Key records and wraps are independent. Wrapping IDs are drawn in retained order.
246
+ const [chain, wrapped] = await settleAll([
247
+ params.mode === 'replicated' ? ctx.keyRecords(next, store) : Promise.resolve({
248
+ records: [],
249
+ branchID: '',
250
+ branchRecordID: ''
251
+ }),
252
+ settleAll(retained.map(async (previous)=>{
253
+ const wrappingID = params.runtime.getRandomID();
254
+ const result = await suite.wrap({
255
+ runtime: params.runtime,
256
+ contentKey,
257
+ factors: factorsFromDescriptors(previous.descriptors, request.inputs),
258
+ wrappingID,
259
+ keyID: key.keyID,
260
+ keyVersion: next.version,
261
+ methods: params.methods
262
+ });
263
+ return {
264
+ previous,
265
+ wrappingID,
266
+ result
267
+ };
268
+ }))
269
+ ]);
270
+ // Signing stays sequential, so a failed signature stops the rest and a prompting signer is
271
+ // asked nothing after it. HLC allocation and output follow the order: wrappings, entries,
272
+ // then revocations.
273
+ const output = [
274
+ ...chain.records
275
+ ];
276
+ const sign = async (task)=>{
277
+ output.push(await task);
278
+ };
279
+ const wrappings = [];
280
+ for (const { previous, wrappingID, result } of wrapped){
281
+ const row = {
282
+ ...next,
283
+ wrappingID,
284
+ keyVersion: next.version,
285
+ recipientDID: previous.recipientDID,
286
+ provenance: chain.branchID,
287
+ ...result
288
+ };
289
+ wrappings.push(row);
290
+ if (params.mode === 'replicated') {
291
+ const prior = evidence.get(previous.wrappingID);
292
+ if (prior === undefined) throw new VaultNotFound();
293
+ await sign(records.wrapping(row, chain, prior.op.claim.meta.consumer, await params.nextHLC()));
294
+ }
295
+ }
296
+ const entries = [];
297
+ const deletions = [];
298
+ const reissued = selections.map((selected)=>{
299
+ const instruction = instructions.get(selected.entryID);
300
+ const deleting = instruction?.action === 'delete' || selected.row === undefined && instruction?.action !== 'replace';
301
+ const decrypting = instruction?.action === 'skip' || deleting || instruction?.action === 'replace' ? undefined : decrypt(selected);
302
+ // Awaited in selection order below. An earlier failure must not surface as unhandled.
303
+ decrypting?.catch(()=>{});
304
+ return {
305
+ selected,
306
+ instruction,
307
+ deleting,
308
+ decrypting
309
+ };
310
+ });
311
+ for (const { selected, instruction, deleting, decrypting } of reissued){
312
+ if (instruction?.action === 'skip') continue;
313
+ const contentHLC = instruction?.contentHLC ?? selected.contentHLC;
314
+ const consumer = instruction !== undefined && Object.hasOwn(instruction, 'consumer') ? instruction.consumer : selected.consumer;
315
+ const hlc = await params.nextHLC();
316
+ if (deleting) {
317
+ deletions.push(selected.entryID);
318
+ if (params.mode === 'replicated') await sign(ctx.signed('vault.entry', {
319
+ ownerDID: key.ownerDID,
320
+ id: selected.entryID
321
+ }, null, {
322
+ keyID: key.keyID,
323
+ keyVersion: next.version,
324
+ branchID: chain.branchID,
325
+ branchRecordID: chain.branchRecordID,
326
+ contentHLC,
327
+ consumer
328
+ }, hlc));
329
+ continue;
330
+ }
331
+ const plaintext = instruction?.action === 'replace' ? instruction.plaintext : await decrypting;
332
+ if (plaintext === null || selected.row === undefined) throw new VaultRepairPending();
333
+ const encrypted = suite.encryptEntry({
334
+ runtime: params.runtime,
335
+ contentKey,
336
+ plaintext,
337
+ keyID: key.keyID,
338
+ keyVersion: next.version,
339
+ entryID: selected.entryID
340
+ });
341
+ const row = {
342
+ ...selected.row,
343
+ keyVersion: next.version,
344
+ ...encrypted,
345
+ hlc,
346
+ provenance: chain.branchID
347
+ };
348
+ entries.push(row);
349
+ if (params.mode === 'replicated') await sign(records.entry(row, chain, contentHLC, consumer));
350
+ }
351
+ if (params.mode === 'replicated') {
352
+ for (const did of new Set(request.excludedRecipients)){
353
+ const id = vaultRevocationRecordID(key.keyID, did);
354
+ const revokedDID = id.slice(id.indexOf('/') + 1);
355
+ await sign(ctx.signed('vault.revocation', {
356
+ ownerDID: key.ownerDID,
357
+ id
358
+ }, {}, {
359
+ keyID: key.keyID,
360
+ revokedDID,
361
+ consumer: null
362
+ }, await params.nextHLC()));
363
+ }
364
+ await ctx.write(store, output, {
365
+ key: {
366
+ ...key,
367
+ expectedVersion: key.version,
368
+ expectedBranches
369
+ }
370
+ }, request);
371
+ } else {
372
+ await store.putKey(next);
373
+ // Each set names distinct rows, so the batched writes run together. The first failure in
374
+ // the old order wins.
375
+ await settleAll([
376
+ store.putWrappings(wrappings),
377
+ store.putEntries(entries),
378
+ store.deleteEntries(deletions.map((entryID)=>({
379
+ ownerDID: key.ownerDID,
380
+ entryID
381
+ }))),
382
+ store.deleteWrappings(existingWrappings.filter((w)=>excluded(w, request.excludedRecipients)))
383
+ ]);
384
+ }
385
+ return next.version;
386
+ } finally{
387
+ // Let every started decrypt settle first, so no key or plaintext arrives after zeroing.
388
+ await Promise.allSettled(pending);
389
+ for (const source of (await Promise.allSettled(keys.values())))if (source.status === 'fulfilled') source.value.contentKey.fill(0);
390
+ for (const plaintext of plaintexts)plaintext.fill(0);
391
+ generated?.fill(0);
392
+ }
393
+ }
394
+ async function rotate(request) {
395
+ await ctx.authorize('rotate', 'administer', request);
396
+ const version = await ctx.locked([
397
+ request
398
+ ], (tx)=>rotateLocked(request, tx, false));
399
+ if (version === null) throw new VaultNotFound();
400
+ ctx.evict(request);
401
+ return version;
402
+ }
403
+ async function repairFork(request) {
404
+ await ctx.authorize('repairFork', 'administer', request);
405
+ if (params.mode === 'local') return {
406
+ status: 'not-needed'
407
+ };
408
+ try {
409
+ const version = await ctx.locked([
410
+ request
411
+ ], async (tx)=>{
412
+ // A projection naming the wrong record refuses every branch read as pending. Rebuild it
413
+ // from the signed records first, so repair sees the key's real branches.
414
+ await ctx.recordsFor(tx).rebuildKeyBranches(request);
415
+ return rotateLocked(request, tx, true);
416
+ });
417
+ if (version === null) return {
418
+ status: 'not-needed'
419
+ };
420
+ ctx.evict(request);
421
+ return {
422
+ status: 'rotated',
423
+ version
424
+ };
425
+ } catch (error) {
426
+ if (error instanceof VaultRepairPending || error instanceof VaultKeyLocked || error instanceof NoAvailableFactor) return {
427
+ status: 'pending'
428
+ };
429
+ throw error;
430
+ }
431
+ }
432
+ return {
433
+ rotate,
434
+ repairFork
435
+ };
436
+ }
@@ -0,0 +1,139 @@
1
+ import type { SignedRecord } from '@kokuin/records';
2
+ import type { SigningIdentity } from '@kokuin/token';
3
+ import type { FactorInput, FactorInputs, KeyCacheParams, PinThrottleParams, CredentialAuthorityRequest as RecordAuthorityRequest, SecretFactorKind, UnwrapParams, WrapParams } from '@kokuin/vault';
4
+ import type { VaultRecords, VaultRecordsParams } from './records.js';
5
+ import type { EntryAddress, EntryFilter, EntryRow, KeyAddress, KeyRow, UpdateEntryResult, VaultStoreAPI } from './types.js';
6
+ /** Unsigned operation requests never fabricate record signatures, including replicated reads. */
7
+ export type CredentialAuthorityRequest = RecordAuthorityRequest | {
8
+ source: 'local';
9
+ mode: 'local' | 'replicated';
10
+ operation: string;
11
+ owner: string;
12
+ issuer: string;
13
+ verb: 'read' | 'administer';
14
+ keyID?: string;
15
+ entryID?: string;
16
+ transportContext: unknown;
17
+ request: unknown;
18
+ store: Pick<VaultStoreAPI, 'getKey' | 'getEntry' | 'listKeys' | 'listWrappings' | 'listEntries'>;
19
+ };
20
+ export type CredentialAuthority = {
21
+ authorize(request: CredentialAuthorityRequest): Promise<boolean>;
22
+ };
23
+ export type CoordinatorCall = {
24
+ transportContext?: unknown;
25
+ };
26
+ export type OpenKeyParams = KeyAddress & CoordinatorCall & {
27
+ inputs?: FactorInputs;
28
+ atVersion?: number;
29
+ };
30
+ export type OpenedKey = {
31
+ contentKey: Uint8Array;
32
+ version: number;
33
+ branchID: string;
34
+ suite: number;
35
+ };
36
+ export type CreateKeyParams = CoordinatorCall & {
37
+ ownerDID: string;
38
+ wrappings?: Array<Array<FactorInput>>;
39
+ suite?: number;
40
+ consumer?: unknown;
41
+ };
42
+ export type EntryWriteParams = KeyAddress & EntryAddress & CoordinatorCall & Pick<EntryRow, 'kind' | 'label' | 'attributes'> & {
43
+ plaintext: Uint8Array;
44
+ inputs?: FactorInputs;
45
+ consumer?: unknown;
46
+ };
47
+ export type EntryUpdateParams = EntryAddress & CoordinatorCall & {
48
+ plaintext: Uint8Array;
49
+ inputs?: FactorInputs;
50
+ };
51
+ export type EntryReissue = {
52
+ entryID: string;
53
+ contentHLC?: string;
54
+ consumer?: unknown;
55
+ } & ({
56
+ action: 'preserve' | 'skip' | 'delete';
57
+ } | {
58
+ action: 'replace';
59
+ plaintext: Uint8Array;
60
+ });
61
+ export type RotateKeyParams = OpenKeyParams & {
62
+ excludedRecipients: ReadonlyArray<string>;
63
+ reissue: ReadonlyArray<EntryReissue>;
64
+ suite?: number;
65
+ };
66
+ export type ForkRepairResult = {
67
+ status: 'rotated';
68
+ version: number;
69
+ } | {
70
+ status: 'not-needed' | 'pending';
71
+ };
72
+ export type KeyStatus = {
73
+ state: 'unlocked' | 'available' | 'unopenable';
74
+ } | {
75
+ state: 'locked';
76
+ unlockOptions: Array<Array<SecretFactorKind>>;
77
+ };
78
+ type CommonParams = {
79
+ store: VaultStoreAPI;
80
+ principal: string;
81
+ identity: UnwrapParams['identity'];
82
+ runtime: WrapParams['runtime'];
83
+ authority: CredentialAuthority;
84
+ nextHLC(): Promise<string>;
85
+ methods?: WrapParams['methods'];
86
+ cache?: KeyCacheParams;
87
+ pinThrottle?: PinThrottleParams;
88
+ };
89
+ export type VaultCoordinatorParams = CommonParams & ({
90
+ mode: 'local';
91
+ } | {
92
+ mode: 'replicated';
93
+ signer: SigningIdentity;
94
+ records: Omit<VaultRecordsParams, 'store' | 'authority'>;
95
+ });
96
+ export type VaultCoordinator = {
97
+ /** Replicated mode only. Receipt remains subject to the records authority callback. */
98
+ get records(): VaultRecords;
99
+ createKey(params: CreateKeyParams): Promise<KeyAddress>;
100
+ openKey(params: OpenKeyParams): Promise<OpenedKey>;
101
+ unlock(params: OpenKeyParams): Promise<void>;
102
+ lock(address: KeyAddress & CoordinatorCall): Promise<void>;
103
+ lockAll(ownerDID: string, call?: CoordinatorCall): Promise<void>;
104
+ status(params: OpenKeyParams): Promise<KeyStatus>;
105
+ listKeys(ownerDID: string, call?: CoordinatorCall): Promise<Array<KeyRow>>;
106
+ setKeyState(params: KeyAddress & CoordinatorCall & Pick<KeyRow, 'state'>): Promise<void>;
107
+ deleteKey(params: KeyAddress & CoordinatorCall): Promise<void>;
108
+ grant(params: OpenKeyParams & {
109
+ recipientDID: string;
110
+ }): Promise<void>;
111
+ addFactor(params: OpenKeyParams & {
112
+ factor: FactorInput;
113
+ }): Promise<void>;
114
+ removeFactor(params: OpenKeyParams & {
115
+ kind: string;
116
+ }): Promise<void>;
117
+ rotate(params: RotateKeyParams): Promise<number>;
118
+ repairFork(params: RotateKeyParams): Promise<ForkRepairResult>;
119
+ putEntry(params: EntryWriteParams): Promise<EntryAddress>;
120
+ updateEntry(params: EntryUpdateParams): Promise<void>;
121
+ updateEntryIfPresent(params: EntryUpdateParams): Promise<'updated' | 'absent'>;
122
+ updateEntryIfUnchanged(params: EntryUpdateParams & {
123
+ expectedHLC: string;
124
+ }): Promise<UpdateEntryResult>;
125
+ readEntry(params: EntryAddress & CoordinatorCall & {
126
+ inputs?: FactorInputs;
127
+ }): Promise<Uint8Array>;
128
+ getEntry(params: EntryAddress & CoordinatorCall): Promise<EntryRow | undefined>;
129
+ listEntries(params: EntryFilter & CoordinatorCall): Promise<Array<EntryRow>>;
130
+ deleteEntry(params: EntryAddress & CoordinatorCall): Promise<void>;
131
+ reKeyEntry(params: EntryAddress & CoordinatorCall & {
132
+ toKeyID: string;
133
+ inputs?: FactorInputs;
134
+ }): Promise<void>;
135
+ reendorse(params: KeyAddress & CoordinatorCall & {
136
+ records: ReadonlyArray<SignedRecord>;
137
+ }): Promise<void>;
138
+ };
139
+ export {};
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,2 @@
1
+ import type { VaultCoordinator, VaultCoordinatorParams } from './coordinator-types.js';
2
+ export declare function createVaultCoordinator(params: VaultCoordinatorParams): VaultCoordinator;