@kubun/credential 0.17.4 → 0.18.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 (51) hide show
  1. package/lib/api.d.ts +61 -30
  2. package/lib/api.js +455 -111
  3. package/lib/authority.d.ts +4 -0
  4. package/lib/authority.js +3 -0
  5. package/lib/branch.d.ts +4 -0
  6. package/lib/branch.js +24 -0
  7. package/lib/builder.d.ts +5 -0
  8. package/lib/builder.js +8 -6
  9. package/lib/canonical-token.d.ts +2 -0
  10. package/lib/canonical-token.js +10 -0
  11. package/lib/errors.d.ts +39 -1
  12. package/lib/errors.js +77 -0
  13. package/lib/fork-repair.d.ts +19 -0
  14. package/lib/fork-repair.js +3 -0
  15. package/lib/grantor-authority.d.ts +26 -0
  16. package/lib/grantor-authority.js +43 -0
  17. package/lib/index.d.ts +15 -6
  18. package/lib/index.js +7 -2
  19. package/lib/key-cache.d.ts +4 -3
  20. package/lib/key-cache.js +22 -15
  21. package/lib/manager-context.d.ts +50 -0
  22. package/lib/manager-context.js +82 -0
  23. package/lib/manager-entries.d.ts +3 -0
  24. package/lib/manager-entries.js +585 -0
  25. package/lib/manager-keys.d.ts +3 -0
  26. package/lib/manager-keys.js +413 -0
  27. package/lib/manager-open.d.ts +45 -0
  28. package/lib/manager-open.js +421 -0
  29. package/lib/manager-records.d.ts +94 -0
  30. package/lib/manager-records.js +291 -0
  31. package/lib/manager-removal-recovery.d.ts +7 -0
  32. package/lib/manager-removal-recovery.js +211 -0
  33. package/lib/manager-rotate.d.ts +15 -0
  34. package/lib/manager-rotate.js +536 -0
  35. package/lib/manager-types.d.ts +337 -0
  36. package/lib/manager-types.js +1 -0
  37. package/lib/manager.d.ts +2 -293
  38. package/lib/manager.js +38 -1073
  39. package/lib/manifest-token.d.ts +31 -0
  40. package/lib/manifest-token.js +51 -0
  41. package/lib/provider.d.ts +15 -2
  42. package/lib/provider.js +39 -0
  43. package/lib/record-types.d.ts +65 -0
  44. package/lib/record-types.js +865 -0
  45. package/lib/refreshing-credential-provider.d.ts +6 -2
  46. package/lib/refreshing-credential-provider.js +76 -13
  47. package/lib/suite.d.ts +5 -4
  48. package/lib/suite.js +2 -2
  49. package/package.json +16 -15
  50. package/lib/op.d.ts +0 -128
  51. package/lib/op.js +0 -304
@@ -0,0 +1,291 @@
1
+ import { normalizeDID } from '@kokuin/token';
2
+ import { recordClaimDigest, recordPayloadDigest, signRecordOp, verifyRecordOp, verifySignedRecord } from '@kubun/records';
3
+ import { credentialRecordAdapter, decodeCredentialRecord, encodeCredentialRecordPayload } from '@kubun/store-credential';
4
+ import { CredentialNotAuthorized, CredentialNotFound } from './errors.js';
5
+ export function createRecordFunctions(ctx) {
6
+ const { params, store, identity, authority, nextHLC } = ctx;
7
+ const records = ()=>credentialRecordAdapter(store);
8
+ const selectedCopy = (decision)=>decision?.copies[0] ?? null;
9
+ async function storedKeyRecord(keyID) {
10
+ const key = await store.getKey(keyID);
11
+ if (key === undefined) return null;
12
+ return selectedCopy(await records().get({
13
+ type: 'credential.key',
14
+ owner: key.owner_did,
15
+ id: keyID
16
+ }));
17
+ }
18
+ async function entryRecord(entryID, target = store, ownerDID) {
19
+ const entry = await target.getEntry(entryID, ownerDID);
20
+ const key = entry === undefined ? undefined : await target.getKey(entry.key_id, entry.owner_did);
21
+ if (key === undefined) return null;
22
+ return selectedCopy(await credentialRecordAdapter(target).get({
23
+ type: 'credential.entry',
24
+ owner: key.owner_did,
25
+ id: entryID
26
+ }));
27
+ }
28
+ async function winningBranch(branches) {
29
+ let winner = null;
30
+ const verified = await Promise.all(branches.map(async (branch)=>({
31
+ branch,
32
+ op: await verifyRecordOp(branch.op_jwt)
33
+ })));
34
+ for (const { branch, op } of verified){
35
+ if (op === null || branch.branch_id !== `${op.claim.hlc}:${op.opHash}`) continue;
36
+ const candidate = {
37
+ branchID: branch.branch_id,
38
+ digest: recordClaimDigest(op.claim),
39
+ opHash: op.opHash
40
+ };
41
+ if (winner === null || candidate.digest > winner.digest || candidate.digest === winner.digest && candidate.opHash > winner.opHash) {
42
+ winner = candidate;
43
+ }
44
+ }
45
+ return winner?.branchID ?? null;
46
+ }
47
+ async function putRecord(args) {
48
+ const { type, id, owner, hlc, payload, meta } = args;
49
+ const target = args.target ?? store;
50
+ const recordAuthority = args.authority ?? authority;
51
+ if (params.removalRecovery === undefined && normalizeDID(owner).startsWith('did:kokuin:')) {
52
+ throw new CredentialNotAuthorized('administer', id);
53
+ }
54
+ const bytes = payload === null ? null : encodeCredentialRecordPayload(type, payload);
55
+ const { opJWT, opHash } = await signRecordOp(identity, {
56
+ type,
57
+ id,
58
+ owner: normalizeDID(owner),
59
+ hlc,
60
+ payloadDigest: recordPayloadDigest(bytes),
61
+ deleted: bytes === null,
62
+ meta: recordAuthority.delegationTokens?.length ? {
63
+ ...meta,
64
+ delegationTokens: [
65
+ ...recordAuthority.delegationTokens
66
+ ]
67
+ } : meta
68
+ });
69
+ const record = {
70
+ type,
71
+ id,
72
+ owner: normalizeDID(owner),
73
+ hlc,
74
+ payload: bytes,
75
+ opJWT,
76
+ opHash
77
+ };
78
+ await target.putRecord(record);
79
+ return record;
80
+ }
81
+ async function keyRecord(args) {
82
+ const { key, keyUnique, hlc, repairDigest } = args;
83
+ const recordAuthority = args.authority ?? authority;
84
+ const target = args.target ?? store;
85
+ const record = await putRecord({
86
+ type: 'credential.key',
87
+ id: key.keyID,
88
+ owner: key.ownerDID,
89
+ hlc,
90
+ payload: {
91
+ suite: key.suite,
92
+ state: key.state
93
+ },
94
+ meta: {
95
+ keyID: key.keyID,
96
+ keyUnique,
97
+ version: key.version,
98
+ state: key.state
99
+ },
100
+ target,
101
+ authority: recordAuthority
102
+ });
103
+ const branchID = `${record.hlc}:${record.opHash}`;
104
+ const payload = {
105
+ authorDID: normalizeDID(identity.id),
106
+ hlc: record.hlc,
107
+ opJWT: record.opJWT,
108
+ opHash: record.opHash,
109
+ suite: key.suite
110
+ };
111
+ const branchIDRecord = recordPayloadDigest(encodeCredentialRecordPayload('credential.key-branch', payload));
112
+ if (branchIDRecord === null) throw new Error('Key branch payload has no digest');
113
+ await putRecord({
114
+ type: 'credential.key-branch',
115
+ id: branchIDRecord,
116
+ owner: key.ownerDID,
117
+ hlc,
118
+ payload,
119
+ meta: {
120
+ keyID: key.keyID,
121
+ keyVersion: key.version,
122
+ branchID,
123
+ ...repairDigest === undefined ? {} : {
124
+ repairDigest
125
+ }
126
+ },
127
+ target,
128
+ authority: recordAuthority
129
+ });
130
+ return {
131
+ branchID,
132
+ branchRecordID: branchIDRecord
133
+ };
134
+ }
135
+ async function keyUnique(keyID) {
136
+ const stored = await storedKeyRecord(keyID);
137
+ const op = stored === null ? null : await verifySignedRecord(stored);
138
+ const unique = op?.claim.meta.keyUnique;
139
+ if (typeof unique !== 'string') throw new CredentialNotFound('Credential key record', keyID);
140
+ return unique;
141
+ }
142
+ async function branchRecordID(args) {
143
+ const { keyID, version, branchID } = args;
144
+ const target = args.target ?? store;
145
+ const branches = await credentialRecordAdapter(target).list([
146
+ 'credential.key-branch'
147
+ ]);
148
+ for (const decision of branches){
149
+ const branch = decision.copies[0];
150
+ if (branch === undefined) continue;
151
+ const op = await verifySignedRecord(branch);
152
+ if (op?.claim.meta.keyID === keyID && op.claim.meta.keyVersion === version && op.claim.meta.branchID === branchID) return branch.id;
153
+ }
154
+ throw new CredentialNotFound('Credential key branch record', branchID);
155
+ }
156
+ async function putWrappingRecord(args) {
157
+ const { wrapping, owner, branchID } = args;
158
+ const recordAuthority = args.authority ?? authority;
159
+ const target = args.target ?? store;
160
+ await putRecord({
161
+ type: 'credential.wrapping',
162
+ id: wrapping.wrappingID,
163
+ owner,
164
+ hlc: await nextHLC(),
165
+ payload: {
166
+ factors: wrapping.factors,
167
+ iv: wrapping.iv,
168
+ wrappedKey: wrapping.wrappedKey
169
+ },
170
+ meta: {
171
+ keyID: wrapping.keyID,
172
+ keyVersion: wrapping.keyVersion,
173
+ branchID,
174
+ branchRecordID: args.branchRecordID ?? await branchRecordID({
175
+ keyID: wrapping.keyID,
176
+ version: wrapping.keyVersion,
177
+ branchID,
178
+ target
179
+ }),
180
+ recipient: wrapping.recipientDID ?? null
181
+ },
182
+ target,
183
+ authority: recordAuthority
184
+ });
185
+ }
186
+ async function putEntryRecord(args) {
187
+ const { entry, owner, branchID, grant } = args;
188
+ const contentHLC = args.contentHLC ?? entry.hlc;
189
+ const target = args.target ?? store;
190
+ const recordAuthority = args.authority ?? authority;
191
+ const branchIDRecord = await branchRecordID({
192
+ keyID: entry.keyID,
193
+ version: entry.keyVersion,
194
+ branchID,
195
+ target
196
+ });
197
+ await putRecord({
198
+ type: 'credential.entry',
199
+ id: entry.entryID,
200
+ owner,
201
+ hlc: entry.hlc,
202
+ payload: {
203
+ iv: entry.iv,
204
+ ciphertext: entry.ciphertext
205
+ },
206
+ meta: {
207
+ keyID: entry.keyID,
208
+ keyVersion: entry.keyVersion,
209
+ contentHLC,
210
+ branchID,
211
+ branchRecordID: branchIDRecord,
212
+ ...grant === undefined ? {} : {
213
+ grantDeviceDID: normalizeDID(grant.grantDeviceDID),
214
+ grantName: grant.grantName,
215
+ grantOwnerDID: normalizeDID(grant.grantOwnerDID ?? owner)
216
+ }
217
+ },
218
+ target,
219
+ authority: recordAuthority
220
+ });
221
+ }
222
+ async function entryGrant(entryID, target = store, ownerDID) {
223
+ const record = await entryRecord(entryID, target, ownerDID);
224
+ const op = record === null ? null : await verifySignedRecord(record);
225
+ const device = op?.claim.meta.grantDeviceDID;
226
+ const name = op?.claim.meta.grantName;
227
+ const accountOwner = op?.claim.meta.grantOwnerDID;
228
+ return typeof device === 'string' && typeof name === 'string' ? {
229
+ grantDeviceDID: device,
230
+ grantName: name,
231
+ grantOwnerDID: typeof accountOwner === 'string' ? accountOwner : op?.claim.owner
232
+ } : undefined;
233
+ }
234
+ async function writableGrant(entryID, ownerDID) {
235
+ const grant = await entryGrant(entryID, store, ownerDID);
236
+ if (grant !== undefined && normalizeDID(grant.grantDeviceDID) !== normalizeDID(identity.id)) {
237
+ throw new CredentialNotAuthorized('administer', entryID);
238
+ }
239
+ return grant;
240
+ }
241
+ async function suiteForVersion(keyID, version, currentSuite) {
242
+ const current = await store.getKey(keyID);
243
+ if (current?.version === version) return currentSuite;
244
+ const branches = await store.listKeyBranches(keyID, version);
245
+ const winner = await winningBranch(branches);
246
+ for (const decision of (await records().list([
247
+ 'credential.key-branch'
248
+ ]))){
249
+ const record = decision.copies[0];
250
+ if (record === undefined) continue;
251
+ const op = await verifySignedRecord(record);
252
+ if (op?.claim.meta.keyID !== keyID || op.claim.meta.keyVersion !== version || op.claim.meta.branchID !== winner) continue;
253
+ const decoded = decodeCredentialRecord(record, op.claim.meta);
254
+ if (decoded.type === 'credential.key-branch') return decoded.payload.suite;
255
+ }
256
+ throw new CredentialNotFound('Historical credential suite', `${keyID}@${version}`);
257
+ }
258
+ async function branchProofs(keyID, version) {
259
+ const proofs = new Map();
260
+ for (const decision of (await records().list([
261
+ 'credential.key-branch'
262
+ ]))){
263
+ const record = decision.copies[0];
264
+ if (record === undefined) continue;
265
+ const op = await verifySignedRecord(record);
266
+ if (op?.claim.meta.keyID !== keyID || op.claim.meta.keyVersion !== version) continue;
267
+ const decoded = decodeCredentialRecord(record, op.claim.meta);
268
+ if (decoded.type !== 'credential.key-branch') continue;
269
+ proofs.set(op.claim.meta.branchID, {
270
+ suite: decoded.payload.suite,
271
+ repairDigest: op.claim.meta.repairDigest
272
+ });
273
+ }
274
+ return proofs;
275
+ }
276
+ return {
277
+ storedKeyRecord,
278
+ entryRecord,
279
+ winningBranch,
280
+ putRecord,
281
+ keyRecord,
282
+ keyUnique,
283
+ branchRecordID,
284
+ putWrappingRecord,
285
+ putEntryRecord,
286
+ entryGrant,
287
+ writableGrant,
288
+ branchProofs,
289
+ suiteForVersion
290
+ };
291
+ }
@@ -0,0 +1,7 @@
1
+ import { type ManagerContext, type ManagerInternals } from './manager-context.js';
2
+ import type { RecoverRemovedDevicesParams } from './manager-types.js';
3
+ export type RemovalRecoveryFunctions = {
4
+ recoverRemovedDevices: (options: RecoverRemovedDevicesParams) => Promise<number>;
5
+ checkRemovalRecovery: () => Promise<void>;
6
+ };
7
+ export declare function createRemovalRecoveryFunctions(ctx: ManagerContext, internals: ManagerInternals): RemovalRecoveryFunctions;
@@ -0,0 +1,211 @@
1
+ import { normalizeDID } from '@kokuin/token';
2
+ import { HLC } from '@kubun/hlc';
3
+ import { recordPayloadDigest, signRecordOp, verifySignedRecord } from '@kubun/records';
4
+ import { credentialRecordAdapter, decodeCredentialRecord } from '@kubun/store-credential';
5
+ import { CredentialKeyVersionMismatch, CredentialNotFound } from './errors.js';
6
+ import { wrappingNamesRemovedDevice } from './manager-context.js';
7
+ import { getSuite } from './suite.js';
8
+ export function createRemovalRecoveryFunctions(ctx, internals) {
9
+ const { store, identity, runtime, hlc, nextHLC, params, state } = ctx;
10
+ const { storedKeyRecord, winningBranch, branchRecordID, putEntryRecord, suiteForVersion } = internals.records;
11
+ const { openKey } = internals.open;
12
+ const records = ()=>credentialRecordAdapter(store);
13
+ async function recoverRemovedDevices(options) {
14
+ if (params.removalRecovery === undefined) {
15
+ // The explicit call supplied the missing recovery context. Keep that context scoped
16
+ // to this recovery, rather than letting unrelated writes bypass the constructor guard.
17
+ return internals.createManager({
18
+ ...params,
19
+ removalRecovery: {
20
+ ...options,
21
+ headVersion: async (ownerDID)=>JSON.stringify([
22
+ ...await options.removedDevices?.(ownerDID) ?? new Set()
23
+ ].sort())
24
+ }
25
+ }).recoverRemovedDevices(options);
26
+ }
27
+ const controller = await options.resolveController(identity.id);
28
+ if (controller === null) return 0;
29
+ const owner = normalizeDID(controller);
30
+ const self = normalizeDID(identity.id);
31
+ const recordTypes = [
32
+ 'credential.key',
33
+ 'credential.wrapping',
34
+ 'credential.entry',
35
+ 'credential.revocation'
36
+ ];
37
+ const removed = new Set();
38
+ for (const decision of (await records().list(recordTypes))){
39
+ if (decision.key.owner !== owner) continue;
40
+ for (const record of decision.copies){
41
+ const op = await verifySignedRecord(record, options.methods);
42
+ if (op === null || op.issuer === owner || op.issuer === self) continue;
43
+ if (await options.resolveController(op.issuer) !== owner) removed.add(op.issuer);
44
+ }
45
+ }
46
+ if (removed.size === 0) return 0;
47
+ for (const did of await options.removedDevices?.(owner) ?? []){
48
+ removed.add(normalizeDID(did));
49
+ }
50
+ let written = 0;
51
+ for (const key of (await store.listKeys(owner))){
52
+ const record = await storedKeyRecord(key.key_id);
53
+ const op = record === null ? null : await verifySignedRecord(record, options.methods);
54
+ if (op === null || !removed.has(op.issuer) || op.claim.deleted) continue;
55
+ try {
56
+ await internals.rotate.rotateKey({
57
+ keyID: key.key_id,
58
+ rotateParams: {
59
+ methods: options.methods
60
+ },
61
+ excludedRecipients: removed
62
+ });
63
+ written += 1;
64
+ } catch (error) {
65
+ // A second local recovery may have rotated the same key after our scan.
66
+ // Its transaction committed the complete rotation; reissue below still
67
+ // picks up any former-device records it left behind.
68
+ if (!(error instanceof CredentialKeyVersionMismatch) || ((await store.getKey(key.key_id))?.version ?? 0) <= key.version) {
69
+ throw error;
70
+ }
71
+ }
72
+ }
73
+ // Re-read after rotation: it may already have replaced some former-device entries.
74
+ for (const decision of (await records().list(recordTypes))){
75
+ if (decision.key.owner !== owner || decision.key.type === 'credential.key') continue;
76
+ let record;
77
+ let op = null;
78
+ for (const copy of decision.copies){
79
+ const verified = await verifySignedRecord(copy, options.methods);
80
+ if (verified !== null && removed.has(verified.issuer)) {
81
+ record = copy;
82
+ op = verified;
83
+ break;
84
+ }
85
+ }
86
+ if (record === undefined || op === null) continue;
87
+ if (record.type === 'credential.entry') {
88
+ const grantDevice = op.claim.meta.grantDeviceDID;
89
+ if (typeof grantDevice === 'string' && removed.has(normalizeDID(grantDevice))) continue;
90
+ }
91
+ if (record.type === 'credential.wrapping') {
92
+ if (record.payload !== null) {
93
+ const decoded = decodeCredentialRecord(record, op.claim.meta);
94
+ if (decoded.type === 'credential.wrapping' && wrappingNamesRemovedDevice({
95
+ recipient_did: op.claim.meta.recipient ?? null,
96
+ factors: decoded.payload.factors
97
+ }, removed)) continue;
98
+ }
99
+ }
100
+ const oldStamp = HLC.tryParse(record.hlc);
101
+ if (oldStamp !== null) hlc.receive(oldStamp);
102
+ const stamp = await nextHLC();
103
+ let meta = op.claim.meta;
104
+ if (record.type === 'credential.entry') {
105
+ const keyID = op.claim.meta.keyID;
106
+ const oldVersion = op.claim.meta.keyVersion;
107
+ if (typeof keyID !== 'string' || typeof oldVersion !== 'number') continue;
108
+ const key = await store.getKey(keyID);
109
+ if (key === undefined) continue;
110
+ meta = {
111
+ ...meta,
112
+ contentHLC: stamp,
113
+ keyVersion: key.version
114
+ };
115
+ if (record.payload === null && oldVersion !== key.version) {
116
+ const branches = await store.listKeyBranches(keyID, key.version);
117
+ if (branches.length === 0) {
118
+ delete meta.branchID;
119
+ delete meta.branchRecordID;
120
+ } else {
121
+ const currentBranchID = await winningBranch(branches);
122
+ if (currentBranchID === null) throw new CredentialNotFound('Credential key branch', keyID);
123
+ meta.branchID = currentBranchID;
124
+ meta.branchRecordID = await branchRecordID({
125
+ keyID,
126
+ version: key.version,
127
+ branchID: currentBranchID
128
+ });
129
+ }
130
+ }
131
+ if (record.payload !== null && oldVersion !== key.version) {
132
+ const entry = await store.getEntry(record.id, record.owner);
133
+ if (entry === undefined) continue;
134
+ const source = await openKey(keyID, undefined, oldVersion);
135
+ let target;
136
+ let plaintext;
137
+ try {
138
+ target = await openKey(keyID);
139
+ const sourceSuite = getSuite(await suiteForVersion(keyID, oldVersion, key.suite));
140
+ plaintext = sourceSuite.decryptEntry({
141
+ contentKey: source.contentKey,
142
+ iv: new Uint8Array(entry.iv),
143
+ ciphertext: new Uint8Array(entry.ciphertext),
144
+ keyID,
145
+ keyVersion: oldVersion,
146
+ entryID: record.id
147
+ });
148
+ const encrypted = getSuite(key.suite).encryptEntry({
149
+ runtime,
150
+ contentKey: target.contentKey,
151
+ plaintext,
152
+ keyID,
153
+ keyVersion: key.version,
154
+ entryID: record.id
155
+ });
156
+ await putEntryRecord({
157
+ entry: {
158
+ entryID: record.id,
159
+ keyID,
160
+ keyVersion: key.version,
161
+ ...encrypted,
162
+ hlc: stamp
163
+ },
164
+ owner: record.owner,
165
+ branchID: target.branchID,
166
+ contentHLC: stamp
167
+ });
168
+ } finally{
169
+ plaintext?.fill(0);
170
+ source.contentKey.fill(0);
171
+ target?.contentKey.fill(0);
172
+ }
173
+ written += 1;
174
+ continue;
175
+ }
176
+ }
177
+ const { opJWT, opHash } = await signRecordOp(identity, {
178
+ type: record.type,
179
+ id: record.id,
180
+ owner: record.owner,
181
+ hlc: stamp,
182
+ payloadDigest: recordPayloadDigest(record.payload),
183
+ deleted: record.payload === null,
184
+ meta
185
+ });
186
+ await store.putRecord({
187
+ ...record,
188
+ hlc: stamp,
189
+ opJWT,
190
+ opHash
191
+ }, options.methods);
192
+ written += 1;
193
+ }
194
+ return written;
195
+ }
196
+ async function checkRemovalRecovery() {
197
+ const recovery = params.removalRecovery;
198
+ if (recovery === undefined) return;
199
+ if ('ensure' in recovery) return await recovery.ensure();
200
+ const controller = await recovery.resolveController(identity.id);
201
+ if (controller === null) return;
202
+ const version = await recovery.headVersion(normalizeDID(controller));
203
+ if (version === state.completedRecoveryHead) return;
204
+ await recoverRemovedDevices(recovery);
205
+ state.completedRecoveryHead = version;
206
+ }
207
+ return {
208
+ recoverRemovedDevices,
209
+ checkRemovalRecovery
210
+ };
211
+ }
@@ -0,0 +1,15 @@
1
+ import type { ForkRepairResult } from './fork-repair.js';
2
+ import { type ManagerContext, type ManagerInternals } from './manager-context.js';
3
+ import type { RotateKeyParams } from './manager-types.js';
4
+ import { type FactorInputs } from './suite.js';
5
+ export type RotateKeyRunParams = {
6
+ keyID: string;
7
+ rotateParams?: RotateKeyParams;
8
+ revokedDID?: string;
9
+ excludedRecipients?: ReadonlySet<string>;
10
+ };
11
+ export type RotateFunctions = {
12
+ rotateKey: (params: RotateKeyRunParams) => Promise<number>;
13
+ repairFork: (keyID: string, inputs?: FactorInputs) => Promise<ForkRepairResult>;
14
+ };
15
+ export declare function createRotateFunctions(ctx: ManagerContext, internals: ManagerInternals): RotateFunctions;