@interop/wallet-core 0.62.0 → 0.65.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 +41 -16
- package/dist/clientAnnex/credentialAnchoredGenesis.d.ts +46 -26
- package/dist/clientAnnex/credentialAnchoredGenesis.d.ts.map +1 -1
- package/dist/clientAnnex/credentialAnchoredGenesis.js +91 -39
- package/dist/clientAnnex/credentialAnchoredGenesis.js.map +1 -1
- package/dist/clientAnnex/establish.d.ts +55 -34
- package/dist/clientAnnex/establish.d.ts.map +1 -1
- package/dist/clientAnnex/establish.js +102 -56
- package/dist/clientAnnex/establish.js.map +1 -1
- package/dist/clientAnnex/forget.d.ts +16 -2
- package/dist/clientAnnex/forget.d.ts.map +1 -1
- package/dist/clientAnnex/forget.js +11 -9
- package/dist/clientAnnex/forget.js.map +1 -1
- package/dist/clientAnnex/forgetLast.d.ts +62 -18
- package/dist/clientAnnex/forgetLast.d.ts.map +1 -1
- package/dist/clientAnnex/forgetLast.js +92 -26
- package/dist/clientAnnex/forgetLast.js.map +1 -1
- package/dist/clientAnnex/gc.d.ts +3 -2
- package/dist/clientAnnex/gc.d.ts.map +1 -1
- package/dist/clientAnnex/gc.js +7 -7
- package/dist/clientAnnex/gc.js.map +1 -1
- package/dist/clientAnnex/heal.d.ts +38 -5
- package/dist/clientAnnex/heal.d.ts.map +1 -1
- package/dist/clientAnnex/heal.js +346 -236
- package/dist/clientAnnex/heal.js.map +1 -1
- package/dist/clientAnnex/index.d.ts +21 -5
- package/dist/clientAnnex/index.d.ts.map +1 -1
- package/dist/clientAnnex/index.js +24 -5
- package/dist/clientAnnex/index.js.map +1 -1
- package/dist/clientAnnex/ladder.d.ts +58 -0
- package/dist/clientAnnex/ladder.d.ts.map +1 -1
- package/dist/clientAnnex/ladder.js +222 -33
- package/dist/clientAnnex/ladder.js.map +1 -1
- package/dist/clientAnnex/ladderAnchored.d.ts +148 -22
- package/dist/clientAnnex/ladderAnchored.d.ts.map +1 -1
- package/dist/clientAnnex/ladderAnchored.js +391 -346
- package/dist/clientAnnex/ladderAnchored.js.map +1 -1
- package/dist/clientAnnex/log.d.ts +162 -43
- package/dist/clientAnnex/log.d.ts.map +1 -1
- package/dist/clientAnnex/log.js +423 -192
- package/dist/clientAnnex/log.js.map +1 -1
- package/dist/clientAnnex/mend.d.ts +12 -6
- package/dist/clientAnnex/mend.d.ts.map +1 -1
- package/dist/clientAnnex/mend.js +26 -12
- package/dist/clientAnnex/mend.js.map +1 -1
- package/dist/clientAnnex/recoveryLadderAnchored.d.ts +6 -4
- package/dist/clientAnnex/recoveryLadderAnchored.d.ts.map +1 -1
- package/dist/clientAnnex/recoveryLadderAnchored.js +56 -35
- package/dist/clientAnnex/recoveryLadderAnchored.js.map +1 -1
- package/dist/clientAnnex/spaceCapability.d.ts +123 -0
- package/dist/clientAnnex/spaceCapability.d.ts.map +1 -0
- package/dist/clientAnnex/spaceCapability.js +152 -0
- package/dist/clientAnnex/spaceCapability.js.map +1 -0
- package/dist/clientAnnex/stages.d.ts +63 -0
- package/dist/clientAnnex/stages.d.ts.map +1 -0
- package/dist/clientAnnex/stages.js +64 -0
- package/dist/clientAnnex/stages.js.map +1 -0
- package/dist/clientAnnex/zcap.d.ts +1 -1
- package/dist/clientAnnex/zcap.d.ts.map +1 -1
- package/dist/clientAnnex/zcap.js +42 -35
- package/dist/clientAnnex/zcap.js.map +1 -1
- package/dist/clients/policy.d.ts +15 -1
- package/dist/clients/policy.d.ts.map +1 -1
- package/dist/clients/policy.js +12 -6
- package/dist/clients/policy.js.map +1 -1
- package/dist/clients/revocation.d.ts +18 -10
- package/dist/clients/revocation.d.ts.map +1 -1
- package/dist/clients/revocation.js +13 -5
- package/dist/clients/revocation.js.map +1 -1
- package/dist/clients/rosterPolicy.d.ts +10 -2
- package/dist/clients/rosterPolicy.d.ts.map +1 -1
- package/dist/clients/rosterPolicy.js +41 -24
- package/dist/clients/rosterPolicy.js.map +1 -1
- package/dist/descriptors/acquire.d.ts.map +1 -1
- package/dist/descriptors/acquire.js +6 -23
- package/dist/descriptors/acquire.js.map +1 -1
- package/dist/descriptors/cipher.d.ts.map +1 -1
- package/dist/descriptors/cipher.js +5 -0
- package/dist/descriptors/cipher.js.map +1 -1
- package/dist/descriptors/errors.d.ts +43 -0
- package/dist/descriptors/errors.d.ts.map +1 -0
- package/dist/descriptors/errors.js +45 -0
- package/dist/descriptors/errors.js.map +1 -0
- package/dist/descriptors/index.d.ts +5 -0
- package/dist/descriptors/index.d.ts.map +1 -1
- package/dist/descriptors/index.js +5 -0
- package/dist/descriptors/index.js.map +1 -1
- package/dist/enrollment/enrollment.d.ts +37 -10
- package/dist/enrollment/enrollment.d.ts.map +1 -1
- package/dist/enrollment/enrollment.js +52 -15
- package/dist/enrollment/enrollment.js.map +1 -1
- package/dist/genesis/accountGenesis.d.ts +42 -11
- package/dist/genesis/accountGenesis.d.ts.map +1 -1
- package/dist/genesis/accountGenesis.js +80 -22
- package/dist/genesis/accountGenesis.js.map +1 -1
- package/dist/genesis/index.d.ts +3 -1
- package/dist/genesis/index.d.ts.map +1 -1
- package/dist/genesis/index.js +3 -1
- package/dist/genesis/index.js.map +1 -1
- package/dist/identity/agents.d.ts +17 -1
- package/dist/identity/agents.d.ts.map +1 -1
- package/dist/identity/agents.js +23 -7
- package/dist/identity/agents.js.map +1 -1
- package/dist/identity/index.d.ts +3 -1
- package/dist/identity/index.d.ts.map +1 -1
- package/dist/identity/index.js +3 -1
- package/dist/identity/index.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/keyring/index.d.ts +2 -1
- package/dist/keyring/index.d.ts.map +1 -1
- package/dist/keyring/index.js +2 -1
- package/dist/keyring/index.js.map +1 -1
- package/dist/keyring/record.d.ts.map +1 -1
- package/dist/keyring/record.js +3 -6
- package/dist/keyring/record.js.map +1 -1
- package/dist/keyring/unlockSpace.d.ts +14 -5
- package/dist/keyring/unlockSpace.d.ts.map +1 -1
- package/dist/keyring/unlockSpace.js +31 -36
- package/dist/keyring/unlockSpace.js.map +1 -1
- package/dist/keys/index.d.ts +10 -6
- package/dist/keys/index.d.ts.map +1 -1
- package/dist/keys/index.js +9 -5
- package/dist/keys/index.js.map +1 -1
- package/dist/keys/rosterLogStore.d.ts +12 -11
- package/dist/keys/rosterLogStore.d.ts.map +1 -1
- package/dist/keys/rosterLogStore.js +15 -13
- package/dist/keys/rosterLogStore.js.map +1 -1
- package/dist/keys/rosterStore.d.ts +3 -6
- package/dist/keys/rosterStore.d.ts.map +1 -1
- package/dist/keys/rosterStore.js +10 -9
- package/dist/keys/rosterStore.js.map +1 -1
- package/dist/keys/spaceEpochs.d.ts.map +1 -1
- package/dist/keys/spaceEpochs.js +2 -3
- package/dist/keys/spaceEpochs.js.map +1 -1
- package/dist/keys/userKeyRoster.d.ts +57 -7
- package/dist/keys/userKeyRoster.d.ts.map +1 -1
- package/dist/keys/userKeyRoster.js +222 -17
- package/dist/keys/userKeyRoster.js.map +1 -1
- package/dist/keys/userKeyRosterCascade.d.ts +7 -6
- package/dist/keys/userKeyRosterCascade.d.ts.map +1 -1
- package/dist/keys/userKeyRosterCascade.js +7 -6
- package/dist/keys/userKeyRosterCascade.js.map +1 -1
- package/dist/keys/wasLabelsStore.d.ts +9 -2
- package/dist/keys/wasLabelsStore.d.ts.map +1 -1
- package/dist/keys/wasLabelsStore.js +14 -6
- package/dist/keys/wasLabelsStore.js.map +1 -1
- package/dist/log.d.ts +7 -2
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +6 -1
- package/dist/log.js.map +1 -1
- package/dist/recovery/index.d.ts +12 -8
- package/dist/recovery/index.d.ts.map +1 -1
- package/dist/recovery/index.js +11 -7
- package/dist/recovery/index.js.map +1 -1
- package/dist/recovery/recoveryCode.d.ts +27 -7
- package/dist/recovery/recoveryCode.d.ts.map +1 -1
- package/dist/recovery/recoveryCode.js +18 -6
- package/dist/recovery/recoveryCode.js.map +1 -1
- package/dist/recovery/recoveryDelegation.d.ts.map +1 -1
- package/dist/recovery/recoveryDelegation.js +21 -34
- package/dist/recovery/recoveryDelegation.js.map +1 -1
- package/dist/recovery/recoveryWebvh.d.ts +75 -63
- package/dist/recovery/recoveryWebvh.d.ts.map +1 -1
- package/dist/recovery/recoveryWebvh.js +127 -116
- package/dist/recovery/recoveryWebvh.js.map +1 -1
- package/dist/request/classify.d.ts +32 -8
- package/dist/request/classify.d.ts.map +1 -1
- package/dist/request/classify.js +39 -14
- package/dist/request/classify.js.map +1 -1
- package/dist/request/ephemeralExchange.d.ts +1 -6
- package/dist/request/ephemeralExchange.d.ts.map +1 -1
- package/dist/request/ephemeralExchange.js.map +1 -1
- package/dist/request/onboarding.d.ts.map +1 -1
- package/dist/request/onboarding.js +2 -2
- package/dist/request/onboarding.js.map +1 -1
- package/dist/request/parse.d.ts.map +1 -1
- package/dist/request/parse.js +2 -3
- package/dist/request/parse.js.map +1 -1
- package/dist/resourceLog/controller.d.ts +34 -12
- package/dist/resourceLog/controller.d.ts.map +1 -1
- package/dist/resourceLog/controller.js +79 -86
- package/dist/resourceLog/controller.js.map +1 -1
- package/dist/resourceLog/document.d.ts +182 -0
- package/dist/resourceLog/document.d.ts.map +1 -0
- package/dist/resourceLog/document.js +159 -0
- package/dist/resourceLog/document.js.map +1 -0
- package/dist/resourceLog/errors.d.ts +47 -8
- package/dist/resourceLog/errors.d.ts.map +1 -1
- package/dist/resourceLog/errors.js +54 -8
- package/dist/resourceLog/errors.js.map +1 -1
- package/dist/resourceLog/index.d.ts +7 -3
- package/dist/resourceLog/index.d.ts.map +1 -1
- package/dist/resourceLog/index.js +7 -3
- package/dist/resourceLog/index.js.map +1 -1
- package/dist/resourceLog/ladderRungs.d.ts +35 -0
- package/dist/resourceLog/ladderRungs.d.ts.map +1 -0
- package/dist/resourceLog/ladderRungs.js +352 -0
- package/dist/resourceLog/ladderRungs.js.map +1 -0
- package/dist/resourceLog/license.d.ts +42 -17
- package/dist/resourceLog/license.d.ts.map +1 -1
- package/dist/resourceLog/license.js +38 -24
- package/dist/resourceLog/license.js.map +1 -1
- package/dist/space/activity.d.ts +15 -15
- package/dist/space/activity.d.ts.map +1 -1
- package/dist/space/activity.js +15 -15
- package/dist/space/activity.js.map +1 -1
- package/dist/space/collections.d.ts +11 -0
- package/dist/space/collections.d.ts.map +1 -1
- package/dist/space/collections.js +13 -0
- package/dist/space/collections.js.map +1 -1
- package/dist/space/deleteSpace.d.ts +28 -0
- package/dist/space/deleteSpace.d.ts.map +1 -0
- package/dist/space/deleteSpace.js +44 -0
- package/dist/space/deleteSpace.js.map +1 -0
- package/dist/space/errors.d.ts.map +1 -1
- package/dist/space/errors.js +0 -1
- package/dist/space/errors.js.map +1 -1
- package/dist/space/index.d.ts +6 -0
- package/dist/space/index.d.ts.map +1 -1
- package/dist/space/index.js +6 -0
- package/dist/space/index.js.map +1 -1
- package/dist/space/plaintextCollection.d.ts +43 -0
- package/dist/space/plaintextCollection.d.ts.map +1 -0
- package/dist/space/plaintextCollection.js +17 -0
- package/dist/space/plaintextCollection.js.map +1 -0
- package/dist/stages.d.ts +23 -0
- package/dist/stages.d.ts.map +1 -0
- package/dist/stages.js +23 -0
- package/dist/stages.js.map +1 -0
- package/dist/sync/index.d.ts +7 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +7 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/push.js +4 -4
- package/dist/sync/push.js.map +1 -1
- package/dist/sync/remint.js +2 -2
- package/dist/sync/remint.js.map +1 -1
- package/dist/sync/types.d.ts +41 -1
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/sync/types.js +47 -1
- package/dist/sync/types.js.map +1 -1
- package/dist/unlock/index.d.ts +5 -1
- package/dist/unlock/index.d.ts.map +1 -1
- package/dist/unlock/index.js +5 -1
- package/dist/unlock/index.js.map +1 -1
- package/dist/unlock/retire.d.ts +35 -7
- package/dist/unlock/retire.d.ts.map +1 -1
- package/dist/unlock/retire.js +52 -14
- package/dist/unlock/retire.js.map +1 -1
- package/dist/unlock/standingClient.d.ts.map +1 -1
- package/dist/unlock/standingClient.js +5 -1
- package/dist/unlock/standingClient.js.map +1 -1
- package/dist/unlock/standingWebvh.d.ts +217 -35
- package/dist/unlock/standingWebvh.d.ts.map +1 -1
- package/dist/unlock/standingWebvh.js +469 -210
- package/dist/unlock/standingWebvh.js.map +1 -1
- package/dist/webvh/accountEntry.d.ts +146 -0
- package/dist/webvh/accountEntry.d.ts.map +1 -0
- package/dist/webvh/accountEntry.js +239 -0
- package/dist/webvh/accountEntry.js.map +1 -0
- package/dist/webvh/didWeb.d.ts +12 -7
- package/dist/webvh/didWeb.d.ts.map +1 -1
- package/dist/webvh/didWeb.js +2 -2
- package/dist/webvh/didWeb.js.map +1 -1
- package/dist/webvh/didWebProjection.d.ts +164 -0
- package/dist/webvh/didWebProjection.d.ts.map +1 -0
- package/dist/webvh/didWebProjection.js +230 -0
- package/dist/webvh/didWebProjection.js.map +1 -0
- package/dist/webvh/didWebvh.d.ts +275 -136
- package/dist/webvh/didWebvh.d.ts.map +1 -1
- package/dist/webvh/didWebvh.js +307 -340
- package/dist/webvh/didWebvh.js.map +1 -1
- package/dist/webvh/enrollClient.d.ts +65 -0
- package/dist/webvh/enrollClient.d.ts.map +1 -0
- package/dist/webvh/enrollClient.js +172 -0
- package/dist/webvh/enrollClient.js.map +1 -0
- package/dist/webvh/index.d.ts +37 -12
- package/dist/webvh/index.d.ts.map +1 -1
- package/dist/webvh/index.js +33 -10
- package/dist/webvh/index.js.map +1 -1
- package/dist/webvh/listClients.d.ts +1 -35
- package/dist/webvh/listClients.d.ts.map +1 -1
- package/dist/webvh/listClients.js +2 -30
- package/dist/webvh/listClients.js.map +1 -1
- package/dist/webvh/revokeClient.d.ts +41 -11
- package/dist/webvh/revokeClient.d.ts.map +1 -1
- package/dist/webvh/revokeClient.js +99 -48
- package/dist/webvh/revokeClient.js.map +1 -1
- package/dist/webvh/standingZcap.d.ts +61 -14
- package/dist/webvh/standingZcap.d.ts.map +1 -1
- package/dist/webvh/standingZcap.js +91 -0
- package/dist/webvh/standingZcap.js.map +1 -1
- package/dist/webvh/verifyLog.d.ts +59 -5
- package/dist/webvh/verifyLog.d.ts.map +1 -1
- package/dist/webvh/verifyLog.js +76 -11
- package/dist/webvh/verifyLog.js.map +1 -1
- package/dist/webvh/wasIdStore.d.ts +8 -8
- package/dist/webvh/wasIdStore.d.ts.map +1 -1
- package/dist/webvh/wasIdStore.js +39 -14
- package/dist/webvh/wasIdStore.js.map +1 -1
- package/dist/webvh/zcap.d.ts +14 -1
- package/dist/webvh/zcap.d.ts.map +1 -1
- package/dist/webvh/zcap.js +3 -27
- package/dist/webvh/zcap.js.map +1 -1
- package/package.json +2 -2
- package/dist/webvh/keyAgreement.d.ts +0 -97
- package/dist/webvh/keyAgreement.d.ts.map +0 -1
- package/dist/webvh/keyAgreement.js +0 -71
- package/dist/webvh/keyAgreement.js.map +0 -1
package/dist/clientAnnex/log.js
CHANGED
|
@@ -52,11 +52,13 @@ import { createDID, deriveNextKeyHash, updateDID } from '@interop/did-method-web
|
|
|
52
52
|
import { rootCapabilityId, spaceItems, spacePath, toUrl } from '@interop/was-client/paths';
|
|
53
53
|
import { base64urlnopad } from '@scure/base';
|
|
54
54
|
import { DID_LOG_RESOURCE } from '../space/collections.js';
|
|
55
|
+
import { plaintextCollection } from '../space/plaintextCollection.js';
|
|
55
56
|
import { resourceLogPinId } from '@interop/vh-resource-log';
|
|
56
57
|
import { clientAnnexRung } from './ladder.js';
|
|
57
|
-
import { assertCarryOverCommitments, concludeWithPublishedLog, didWebvhControllerTemplate, MULTIKEY_VM_TYPE, pinOfLog,
|
|
58
|
-
import {
|
|
59
|
-
import {
|
|
58
|
+
import { assertCarryOverCommitments, assertPublishedLogDid, concludeWithPublishedLog, didWebvhControllerTemplate, MULTIKEY_VM_TYPE, pinOfLog, putLogResource, readPublishedLogOrThrow, updateKeySigner, WebvhLogConflictError, withLogConflictRetry } from '../webvh/didWebvh.js';
|
|
59
|
+
import { signAccountEntry } from '../webvh/accountEntry.js';
|
|
60
|
+
import { relationIds } from '../resourceLog/document.js';
|
|
61
|
+
import { STANDING_ZCAP_TTL_MS, standingZcapStale } from '../webvh/standingZcap.js';
|
|
60
62
|
import { wasWebvhLogStore } from '../webvh/wasIdStore.js';
|
|
61
63
|
/**
|
|
62
64
|
* The Space Description `type` array of the auxiliary annex Space, set at
|
|
@@ -256,24 +258,30 @@ export async function createClientAnnexLog({ wasServerUrl, spaceId, generationId
|
|
|
256
258
|
* @param options.controller {string} the Space controller (the account
|
|
257
259
|
* did:webvh where it exists; a bootstrap did:key on a ladder-anchored signup,
|
|
258
260
|
* promoted the same way the account Space's controller is)
|
|
259
|
-
* @returns {Promise<
|
|
261
|
+
* @returns {Promise<SpaceDescription>} the Space Description this call read
|
|
262
|
+
* (the Space already existed) or wrote (it did not), so a caller flipping
|
|
263
|
+
* the controller straight afterwards can hand it back as `current` instead
|
|
264
|
+
* of paying for a second read.
|
|
260
265
|
*/
|
|
261
266
|
export async function ensureClientAnnexSpace({ was, spaceId, controller }) {
|
|
262
267
|
const space = was.space(spaceId);
|
|
263
268
|
const current = await space.describe();
|
|
264
269
|
if (current === null) {
|
|
265
|
-
|
|
270
|
+
// `current: null` is the answer this read just produced, so was-client
|
|
271
|
+
// skips its own pre-merge describe rather than repeating it.
|
|
272
|
+
return space.configure({
|
|
273
|
+
current: null,
|
|
266
274
|
controller,
|
|
267
275
|
type: CLIENT_ANNEX_SPACE_TYPE,
|
|
268
276
|
force: true
|
|
269
277
|
});
|
|
270
|
-
return;
|
|
271
278
|
}
|
|
272
279
|
if (!current.type?.includes(DELEGATED_CLIENTS_SPACE_TYPE)) {
|
|
273
280
|
throw new Error(`The Space "${spaceId}" exists but is not typed as the ` +
|
|
274
281
|
'delegated-clients auxiliary Space; its type is immutable, so it ' +
|
|
275
282
|
'cannot hold client-annex generations.');
|
|
276
283
|
}
|
|
284
|
+
return current;
|
|
277
285
|
}
|
|
278
286
|
/**
|
|
279
287
|
* Mints a fresh annex generation with controller-tier signing: ensures
|
|
@@ -308,12 +316,19 @@ export async function ensureClientAnnexSpace({ was, spaceId, controller }) {
|
|
|
308
316
|
* rung-0 hash, the minting credential's included
|
|
309
317
|
* @param options.signer {Signer} the minting credential's rung-0 signer
|
|
310
318
|
* @returns {Promise<{ did: string; generationId: string; log: DIDLog;
|
|
311
|
-
* doc: DIDDoc }>}
|
|
319
|
+
* doc: DIDDoc; spaceDescription?: SpaceDescription }>} `spaceDescription`
|
|
320
|
+
* is the auxiliary Space's Description as the ensure read or wrote it, so a
|
|
321
|
+
* caller flipping the controller afterwards need not re-read it. Always
|
|
322
|
+
* present here, since this minter always runs the ensure.
|
|
312
323
|
*/
|
|
313
324
|
export async function mintClientAnnexGeneration({ was, wasServerUrl, spaceId, controller, updateKeyPublicKeyMultibase, nextKeyHashes, signer }) {
|
|
314
|
-
await ensureClientAnnexSpace({
|
|
325
|
+
const spaceDescription = await ensureClientAnnexSpace({
|
|
326
|
+
was,
|
|
327
|
+
spaceId,
|
|
328
|
+
controller
|
|
329
|
+
});
|
|
315
330
|
const generationId = mintGenerationId();
|
|
316
|
-
|
|
331
|
+
const published = await publishClientAnnexGenesis({
|
|
317
332
|
was,
|
|
318
333
|
wasServerUrl,
|
|
319
334
|
spaceId,
|
|
@@ -322,6 +337,7 @@ export async function mintClientAnnexGeneration({ was, wasServerUrl, spaceId, co
|
|
|
322
337
|
nextKeyHashes,
|
|
323
338
|
signer
|
|
324
339
|
});
|
|
340
|
+
return { ...published, spaceDescription };
|
|
325
341
|
}
|
|
326
342
|
/**
|
|
327
343
|
* The shared genesis-publish tail of both generation minters: creates the
|
|
@@ -343,14 +359,17 @@ export async function mintClientAnnexGeneration({ was, wasServerUrl, spaceId, co
|
|
|
343
359
|
*/
|
|
344
360
|
async function publishClientAnnexGenesis({ was, wasServerUrl, spaceId, generationId, updateKeyPublicKeyMultibase, nextKeyHashes, signer, capability }) {
|
|
345
361
|
// The generation collection must exist before its first resource PUT; a
|
|
346
|
-
// fresh random generation id means this is always a create
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
// collection is capability-gated rather
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
362
|
+
// fresh random generation id means this is always a create, so the truthful
|
|
363
|
+
// `current: null` skips was-client's pre-merge describe of a Collection
|
|
364
|
+
// that cannot exist. Plaintext on purpose: the server resolves the annex
|
|
365
|
+
// DID out of its own storage, and the collection is capability-gated rather
|
|
366
|
+
// than encrypted.
|
|
367
|
+
await plaintextCollection({
|
|
368
|
+
was,
|
|
369
|
+
spaceId,
|
|
370
|
+
collectionId: generationId,
|
|
371
|
+
capability
|
|
372
|
+
}).configure({ current: null, name: generationId, force: true });
|
|
354
373
|
const created = await createClientAnnexLog({
|
|
355
374
|
wasServerUrl,
|
|
356
375
|
spaceId,
|
|
@@ -404,15 +423,18 @@ async function publishClientAnnexGenesis({ was, wasServerUrl, spaceId, generatio
|
|
|
404
423
|
* covers the collections beneath the Space, never the Space Description,
|
|
405
424
|
* and a standing sibling delegation presupposes the auxiliary Space
|
|
406
425
|
* @returns {Promise<{ did: string; generationId: string; log: DIDLog;
|
|
407
|
-
* doc: DIDDoc }>}
|
|
426
|
+
* doc: DIDDoc; spaceDescription?: SpaceDescription }>} `spaceDescription`
|
|
427
|
+
* is the auxiliary Space's Description as the ensure read or wrote it, so a
|
|
428
|
+
* caller flipping the controller afterwards need not re-read it. Present
|
|
429
|
+
* exactly when the ensure ran, so absent under a supplied `capability`.
|
|
408
430
|
*/
|
|
409
431
|
export async function mintCredentialClientAnnexGeneration({ was, wasServerUrl, spaceId, controller, ladderSeed, extraNextKeyHashes = [], capability }) {
|
|
410
|
-
|
|
411
|
-
await ensureClientAnnexSpace({ was, spaceId, controller })
|
|
412
|
-
|
|
432
|
+
const spaceDescription = capability === undefined
|
|
433
|
+
? await ensureClientAnnexSpace({ was, spaceId, controller })
|
|
434
|
+
: undefined;
|
|
413
435
|
const generationId = mintGenerationId();
|
|
414
436
|
const rung = await clientAnnexRung({ ladderSeed, generationId });
|
|
415
|
-
|
|
437
|
+
const published = await publishClientAnnexGenesis({
|
|
416
438
|
was,
|
|
417
439
|
wasServerUrl,
|
|
418
440
|
spaceId,
|
|
@@ -425,6 +447,10 @@ export async function mintCredentialClientAnnexGeneration({ was, wasServerUrl, s
|
|
|
425
447
|
signer: await updateKeySigner({ seed: rung.seed }),
|
|
426
448
|
...(capability !== undefined ? { capability } : {})
|
|
427
449
|
});
|
|
450
|
+
return {
|
|
451
|
+
...published,
|
|
452
|
+
...(spaceDescription !== undefined ? { spaceDescription } : {})
|
|
453
|
+
};
|
|
428
454
|
}
|
|
429
455
|
/**
|
|
430
456
|
* The type IRI of the account document's delegated-clients service entry --
|
|
@@ -481,6 +507,61 @@ export function delegatedClientsPointer({ doc }) {
|
|
|
481
507
|
}
|
|
482
508
|
return undefined;
|
|
483
509
|
}
|
|
510
|
+
/**
|
|
511
|
+
* Every auxiliary annex Space the account log's `#DelegatedClients` pointer
|
|
512
|
+
* has ever named, oldest first and one entry per Space. A pointer entry is
|
|
513
|
+
* append-only: a superseded value stops being current but its Space survives
|
|
514
|
+
* the move, so an enumeration that reads only the resolved document (the
|
|
515
|
+
* {@link delegatedClientsPointer} case) misses every Space a generation swap
|
|
516
|
+
* has left behind. Several generations of one Space collapse to the entry
|
|
517
|
+
* that named it first.
|
|
518
|
+
*
|
|
519
|
+
* The account deletion ceremony is the reader: it must name each auxiliary
|
|
520
|
+
* Space it is about to destroy, and the log is the only durable record of
|
|
521
|
+
* the superseded ones. Each entry carries the annex DID and its host beside
|
|
522
|
+
* the Space id, because an account that has migrated hosts leaves entries
|
|
523
|
+
* this deployment cannot address: deleting `spaceId` on the CURRENT host
|
|
524
|
+
* would address a Space that is not the one the entry names, and a 404 there
|
|
525
|
+
* would otherwise read as a clean deletion. Such an entry is the caller's to
|
|
526
|
+
* report as a residue.
|
|
527
|
+
*
|
|
528
|
+
* The acting unlock record's own `delegatedClients` sibling target is NOT
|
|
529
|
+
* included here; a caller that wants it unions it in itself, since a torn
|
|
530
|
+
* establishment can converge on a Space no pointer entry ever named.
|
|
531
|
+
*
|
|
532
|
+
* An entry carrying no document state, and an endpoint that does not parse
|
|
533
|
+
* as a client-annex DID, are skipped rather than refused: the walk is an
|
|
534
|
+
* enumeration aid, and a caller cannot act on an id it could not read.
|
|
535
|
+
*
|
|
536
|
+
* @param options {object}
|
|
537
|
+
* @param options.log {DIDLog} the VERIFIED account log
|
|
538
|
+
* @returns {Array<{ did: string, host: string, spaceId: string }>} the
|
|
539
|
+
* Spaces, in log order
|
|
540
|
+
*/
|
|
541
|
+
export function delegatedClientsSpaceHistory({ log }) {
|
|
542
|
+
const spaces = [];
|
|
543
|
+
for (const entry of log) {
|
|
544
|
+
if (entry?.state === undefined || entry.state === null) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
const pointed = delegatedClientsPointer({ doc: entry.state });
|
|
548
|
+
if (pointed === undefined) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
let parts;
|
|
552
|
+
try {
|
|
553
|
+
parts = clientAnnexDidParts({ did: pointed });
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (spaces.some(space => space.spaceId === parts.spaceId)) {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
spaces.push({ did: pointed, host: parts.host, spaceId: parts.spaceId });
|
|
562
|
+
}
|
|
563
|
+
return spaces;
|
|
564
|
+
}
|
|
484
565
|
/**
|
|
485
566
|
* The account document's `service` array with the delegated-clients pointer
|
|
486
567
|
* set to `clientAnnexDid`. An existing pointer entry is re-pointed in place,
|
|
@@ -901,33 +982,42 @@ function withGenerationDelegationEntry({ doc, clientAnnexDid, delegation }) {
|
|
|
901
982
|
];
|
|
902
983
|
}
|
|
903
984
|
/**
|
|
904
|
-
* Parses the auxiliary Space id and generation id out of an
|
|
905
|
-
* string.
|
|
985
|
+
* Parses the host, the auxiliary Space id and the generation id out of an
|
|
986
|
+
* annex DID string. All three are permanent substrings of every annex DID by
|
|
906
987
|
* construction: the generation id is the final path segment of the annex
|
|
907
988
|
* DID (`did:webvh:<scid>:<host>:...:space:<spaceId>:<generationId>`), and it
|
|
908
989
|
* is the generation-identifying half of the annex rung HKDF
|
|
909
990
|
* labels, so this parse is what lets an enrollee derive its writing key from
|
|
910
991
|
* the pointer alone -- no log read, no registry.
|
|
911
992
|
*
|
|
993
|
+
* The host is the method-specific id's second segment, percent-decoded (a
|
|
994
|
+
* port rides as `%3A` inside the one segment). A caller enumerating Spaces
|
|
995
|
+
* out of a log compares it against the deployment it is talking to: an
|
|
996
|
+
* account that has migrated hosts carries entries naming the old one, which
|
|
997
|
+
* this deployment cannot address.
|
|
998
|
+
*
|
|
912
999
|
* @param options {object}
|
|
913
1000
|
* @param options.did {string} an annex did:webvh string
|
|
914
|
-
* @returns {{ spaceId: string, generationId: string }}
|
|
1001
|
+
* @returns {{ host: string, spaceId: string, generationId: string }}
|
|
915
1002
|
*/
|
|
916
1003
|
export function clientAnnexDidParts({ did }) {
|
|
917
1004
|
const parts = did.split(':');
|
|
918
1005
|
const generationId = parts[parts.length - 1];
|
|
919
1006
|
const spaceId = parts[parts.length - 2];
|
|
1007
|
+
const host = parts[3];
|
|
920
1008
|
if (parts.length < 7 ||
|
|
921
1009
|
parts[0] !== 'did' ||
|
|
922
1010
|
parts[1] !== 'webvh' ||
|
|
923
1011
|
parts[parts.length - 3] !== 'space' ||
|
|
924
1012
|
generationId === undefined ||
|
|
925
1013
|
spaceId === undefined ||
|
|
926
|
-
spaceId.length === 0
|
|
1014
|
+
spaceId.length === 0 ||
|
|
1015
|
+
host === undefined ||
|
|
1016
|
+
host.length === 0) {
|
|
927
1017
|
throw new Error(`Not a client annex did:webvh: "${did}".`);
|
|
928
1018
|
}
|
|
929
1019
|
assertGenerationId(generationId);
|
|
930
|
-
return { spaceId, generationId };
|
|
1020
|
+
return { host: decodeURIComponent(host), spaceId, generationId };
|
|
931
1021
|
}
|
|
932
1022
|
/**
|
|
933
1023
|
* Thrown when the published annex log commits neither the writing
|
|
@@ -944,6 +1034,37 @@ export class ClientAnnexRungUncommittedError extends Error {
|
|
|
944
1034
|
this.name = 'ClientAnnexRungUncommittedError';
|
|
945
1035
|
}
|
|
946
1036
|
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Derives a generation's rung-0 key for a ladder and admits it as the writer
|
|
1039
|
+
* of the entry about to be built: it must stand revealed in the log's
|
|
1040
|
+
* `updateKeys`, or its hash must stand committed in `nextKeyHashes`.
|
|
1041
|
+
*
|
|
1042
|
+
* The one implementation of the annex's writer-admission rule, which every
|
|
1043
|
+
* entry builder in this module applies before it mints anything. Annex
|
|
1044
|
+
* entries verify against the log's own hash-commitment chain, so a key that
|
|
1045
|
+
* is neither revealed nor committed can never be made to verify mid-log --
|
|
1046
|
+
* the caller supplies only the refusal message, since what a locked-out
|
|
1047
|
+
* credential should do next differs per ceremony.
|
|
1048
|
+
*
|
|
1049
|
+
* @param options {object}
|
|
1050
|
+
* @param options.ladderSeed {Uint8Array} the acting credential's ladder seed
|
|
1051
|
+
* @param options.generationId {string}
|
|
1052
|
+
* @param options.updateKeys {string[]} the keys the entry will be verified
|
|
1053
|
+
* against (the retired members already excluded, where a ceremony excludes
|
|
1054
|
+
* them)
|
|
1055
|
+
* @param options.nextKeyHashes {string[]} likewise
|
|
1056
|
+
* @param options.message {string} the refusal's message
|
|
1057
|
+
* @returns {Promise<{ seed: Uint8Array; keyMultibase: string }>}
|
|
1058
|
+
*/
|
|
1059
|
+
async function admitActingRung({ ladderSeed, generationId, updateKeys, nextKeyHashes, message }) {
|
|
1060
|
+
const rung = await clientAnnexRung({ ladderSeed, generationId });
|
|
1061
|
+
const rungHash = await deriveNextKeyHash(rung.keyMultibase);
|
|
1062
|
+
if (!updateKeys.includes(rung.keyMultibase) &&
|
|
1063
|
+
!nextKeyHashes.includes(rungHash)) {
|
|
1064
|
+
throw new ClientAnnexRungUncommittedError(message);
|
|
1065
|
+
}
|
|
1066
|
+
return rung;
|
|
1067
|
+
}
|
|
947
1068
|
/**
|
|
948
1069
|
* Reads and resolves the published annex log through the narrow seam, or
|
|
949
1070
|
* throws when the generation's `did.jsonl` is missing (an unpointed or
|
|
@@ -957,18 +1078,14 @@ export class ClientAnnexRungUncommittedError extends Error {
|
|
|
957
1078
|
* @returns {Promise<PublishedWebvhLog>}
|
|
958
1079
|
*/
|
|
959
1080
|
async function readClientAnnexLogOrThrow({ store, expectedDid, pinStore, logId }) {
|
|
960
|
-
|
|
961
|
-
const published = await readPublishedLog({
|
|
1081
|
+
return readPublishedLogOrThrow({
|
|
962
1082
|
idStore: store,
|
|
963
1083
|
...(expectedDid !== undefined ? { expectedDid } : {}),
|
|
964
1084
|
...(pinStore !== undefined ? { pinStore } : {}),
|
|
965
|
-
...(logId !== undefined ? { logId } : {})
|
|
1085
|
+
...(logId !== undefined ? { logId } : {}),
|
|
1086
|
+
missingMessage: 'client annex: did.jsonl is missing; the generation was never minted ' +
|
|
1087
|
+
'or has been collected.'
|
|
966
1088
|
});
|
|
967
|
-
if (!published) {
|
|
968
|
-
throw new Error('client annex: did.jsonl is missing; the generation was never minted or ' +
|
|
969
|
-
'has been collected.');
|
|
970
|
-
}
|
|
971
|
-
return published;
|
|
972
1089
|
}
|
|
973
1090
|
/**
|
|
974
1091
|
* TRANSIENT ENROLLMENT: publishes one per-visit verification method into a
|
|
@@ -1032,27 +1149,56 @@ async function readClientAnnexLogOrThrow({ store, expectedDid, pinStore, logId }
|
|
|
1032
1149
|
* transient session passes an in-memory store)
|
|
1033
1150
|
* @param [options.logId] {string} the generation's pin-slot key, from
|
|
1034
1151
|
* {@link clientAnnexLogPinId}; required whenever a `pinStore` is supplied
|
|
1152
|
+
* @param [options.published] {PublishedWebvhLog} a head the caller already
|
|
1153
|
+
* read and verified under this same pin slot, so the enrollment builds its
|
|
1154
|
+
* entry on it instead of spending a second round trip on the same log (the
|
|
1155
|
+
* transient visit's one-read composition). The FIRST attempt alone rides it:
|
|
1156
|
+
* a lost compare-and-swap means the head is stale by definition, so the
|
|
1157
|
+
* conflict retry re-reads under the pin. That threaded attempt is EXTRA
|
|
1158
|
+
* rather than one of the retry's three: a caller who saved a read is left
|
|
1159
|
+
* with the same conflict budget as one who did not
|
|
1035
1160
|
* @returns {Promise<{ did: string, doc: DIDDoc, log: DIDLog }>}
|
|
1036
1161
|
*/
|
|
1037
|
-
export async function enrollClientAnnexTransientClient(
|
|
1038
|
-
|
|
1162
|
+
export async function enrollClientAnnexTransientClient({ published: threadedHead, ...rest }) {
|
|
1163
|
+
if (threadedHead !== undefined) {
|
|
1164
|
+
try {
|
|
1165
|
+
return await enrollClientAnnexTransientClientOnce({
|
|
1166
|
+
...rest,
|
|
1167
|
+
published: threadedHead
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
catch (err) {
|
|
1171
|
+
// A lost compare-and-swap on the threaded head says only that the head
|
|
1172
|
+
// is stale; the retry below re-reads under the pin with its whole
|
|
1173
|
+
// budget. Every other failure is the caller's.
|
|
1174
|
+
if (!(err instanceof WebvhLogConflictError)) {
|
|
1175
|
+
throw err;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return withLogConflictRetry(() => enrollClientAnnexTransientClientOnce(rest));
|
|
1039
1180
|
}
|
|
1040
1181
|
/**
|
|
1041
1182
|
* One attempt of {@link enrollClientAnnexTransientClient}, re-invoked by the
|
|
1042
1183
|
* conflict retry (with the same signing key -- static rung 0 has no
|
|
1043
|
-
* advanced-rung retry shape).
|
|
1184
|
+
* advanced-rung retry shape) and with no threaded head after the first.
|
|
1044
1185
|
*
|
|
1045
1186
|
* @param options {object} see {@link enrollClientAnnexTransientClient}
|
|
1046
1187
|
* @returns {Promise<{ did: string, doc: DIDDoc, log: DIDLog }>}
|
|
1047
1188
|
*/
|
|
1048
|
-
async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generationId, transientKeyMultibase, services, mintGenerationDelegation: mintDelegation, expectedDid, pinStore, logId }) {
|
|
1189
|
+
async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generationId, transientKeyMultibase, services, mintGenerationDelegation: mintDelegation, expectedDid, pinStore, logId, published: alreadyRead }) {
|
|
1049
1190
|
assertGenerationId(generationId);
|
|
1050
|
-
const published =
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1191
|
+
const published = alreadyRead !== undefined
|
|
1192
|
+
? assertPublishedLogDid({
|
|
1193
|
+
published: alreadyRead,
|
|
1194
|
+
...(expectedDid !== undefined ? { expectedDid } : {})
|
|
1195
|
+
})
|
|
1196
|
+
: await readClientAnnexLogOrThrow({
|
|
1197
|
+
store,
|
|
1198
|
+
...(expectedDid !== undefined ? { expectedDid } : {}),
|
|
1199
|
+
...(pinStore !== undefined ? { pinStore } : {}),
|
|
1200
|
+
...(logId !== undefined ? { logId } : {})
|
|
1201
|
+
});
|
|
1056
1202
|
const { did, doc } = published;
|
|
1057
1203
|
const vmId = `${did}#${transientKeyMultibase}`;
|
|
1058
1204
|
// Already enrolled (a completed earlier run): the VM and everything the
|
|
@@ -1062,14 +1208,15 @@ async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generat
|
|
|
1062
1208
|
if (existingMethods.some(method => method.id === vmId)) {
|
|
1063
1209
|
return { did, doc, log: published.log };
|
|
1064
1210
|
}
|
|
1065
|
-
const rung = await
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1211
|
+
const rung = await admitActingRung({
|
|
1212
|
+
ladderSeed,
|
|
1213
|
+
generationId,
|
|
1214
|
+
updateKeys: published.updateKeys,
|
|
1215
|
+
nextKeyHashes: published.nextKeyHashes,
|
|
1216
|
+
message: "client annex: the log commits neither this credential's rung-0 key nor " +
|
|
1070
1217
|
'its hash; a credential bound mid-generation cannot write the ' +
|
|
1071
|
-
'annex until a writer commits its hash or the next GC swap does.'
|
|
1072
|
-
}
|
|
1218
|
+
'annex until a writer commits its hash or the next GC swap does.'
|
|
1219
|
+
});
|
|
1073
1220
|
// A non-rotating entry re-states `updateKeys`, which the resolver checks
|
|
1074
1221
|
// against the previous entry's commitments -- genesis enforces the
|
|
1075
1222
|
// carry-over convention, and this refuses a log that lost it anyway.
|
|
@@ -1125,6 +1272,13 @@ async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generat
|
|
|
1125
1272
|
// The log only -- an annex has no did:web projection -- conditional on
|
|
1126
1273
|
// the read this entry was built on.
|
|
1127
1274
|
await putLogResource({ store, log: updated.log, ifMatch: published.etag });
|
|
1275
|
+
// Advance the pin to what this entry just published, so a host serving the
|
|
1276
|
+
// pre-entry log straight afterwards is refused as a rollback on the next
|
|
1277
|
+
// read (equal-to-pin would otherwise be accepted, and a later stage built on
|
|
1278
|
+
// the stale head would miss this entry).
|
|
1279
|
+
if (pinStore && logId !== undefined) {
|
|
1280
|
+
await pinStore.write({ logId, pin: pinOfLog(updated.log) });
|
|
1281
|
+
}
|
|
1128
1282
|
return { did: updated.did, doc: updated.doc, log: updated.log };
|
|
1129
1283
|
}
|
|
1130
1284
|
/**
|
|
@@ -1140,16 +1294,19 @@ async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generat
|
|
|
1140
1294
|
* entry is appended ({@link delegatedClientsServiceEntry}). Every other
|
|
1141
1295
|
* service entry, the verification methods, and the relationship arrays are
|
|
1142
1296
|
* preserved untouched. Idempotent: a document already pointing at the DID is
|
|
1143
|
-
* a no-op on the log (
|
|
1297
|
+
* a no-op on the log (and, unless `logOnly`, it republishes `did.json` from
|
|
1298
|
+
* the resolved log, which the enrolled-client caller has the authority to
|
|
1299
|
+
* do).
|
|
1144
1300
|
*
|
|
1145
1301
|
* @param options {object}
|
|
1146
1302
|
* @param options.idStore {WebvhIdStore} the ACCOUNT log's store; with
|
|
1147
1303
|
* `logOnly`, only its log read and `did.jsonl` PUT are used, so the narrow
|
|
1148
1304
|
* delegated seam satisfies it
|
|
1149
|
-
* @param options.
|
|
1150
|
-
* update-key seeds -- or the ladder-rung idiom on a
|
|
1151
|
-
* account (`{ updateSeed: rung0.seed, stagedSeed:
|
|
1152
|
-
* credential-anchored genesis and the
|
|
1305
|
+
* @param options.signer {AccountLogSigner} who signs the pointer entry:
|
|
1306
|
+
* this enrolled client's update-key seeds -- or the ladder-rung idiom on a
|
|
1307
|
+
* ladder-anchored account (`{ updateSeed: rung0.seed, stagedSeed:
|
|
1308
|
+
* rung1.seed }`), as the credential-anchored genesis and the
|
|
1309
|
+
* transient-recovery continuation pass
|
|
1153
1310
|
* @param options.clientAnnexDid {string} the generation to point at
|
|
1154
1311
|
* @param [options.expectedDid] {string} the account DID the log must
|
|
1155
1312
|
* resolve to, from the account pointer
|
|
@@ -1161,24 +1318,56 @@ async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, generat
|
|
|
1161
1318
|
* @param [options.logOnly] {boolean} publish `did.jsonl` only, never the
|
|
1162
1319
|
* `did.json` projection -- the transient-recovery continuation writing
|
|
1163
1320
|
* through the record's bridge delegation, whose narrow scope covers nothing
|
|
1164
|
-
* but the log. The projection
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
1321
|
+
* but the log. The projection then lags until some caller holding an
|
|
1322
|
+
* `id`-collection writer runs `ensureDidWebProjection` over the resolved
|
|
1323
|
+
* log; on a client-less account that is a transient visit under its
|
|
1324
|
+
* generation delegation. The log stays the source of truth meanwhile, and
|
|
1325
|
+
* the server reads it rather than the projection, so the lag is a `did:web`
|
|
1326
|
+
* verifier's concern alone
|
|
1327
|
+
* @param [options.published] {PublishedWebvhLog} a head the caller already
|
|
1328
|
+
* read and verified under this same pin slot, so the pointer entry builds
|
|
1329
|
+
* on it instead of spending a second round trip on the log the caller just
|
|
1330
|
+
* read or published (the establishment's one-read composition). The FIRST
|
|
1331
|
+
* attempt alone rides it: a lost compare-and-swap means the head is stale
|
|
1332
|
+
* by definition, so the conflict retry re-reads under the pin. That
|
|
1333
|
+
* threaded attempt is EXTRA rather than one of the retry's three: a caller
|
|
1334
|
+
* who saved a read is left with the same conflict budget as one who did not
|
|
1335
|
+
* @returns {Promise<{ did: string, doc: DIDDoc,
|
|
1336
|
+
* published: PublishedWebvhLog }>} `published` is the head this call
|
|
1337
|
+
* leaves standing: the post-entry one, paired with its publish's own ETag,
|
|
1338
|
+
* when the entry was appended; the head it stood on verbatim when the
|
|
1339
|
+
* document already pointed at the DID
|
|
1167
1340
|
*/
|
|
1168
|
-
export async function setDelegatedClientsPointer(
|
|
1169
|
-
|
|
1341
|
+
export async function setDelegatedClientsPointer({ published: threadedHead, ...rest }) {
|
|
1342
|
+
if (threadedHead !== undefined) {
|
|
1343
|
+
try {
|
|
1344
|
+
return await setDelegatedClientsPointerOnce({
|
|
1345
|
+
...rest,
|
|
1346
|
+
published: threadedHead
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
catch (err) {
|
|
1350
|
+
// A lost compare-and-swap on the threaded head says only that the head
|
|
1351
|
+
// is stale; the retry below re-reads under the pin with its whole
|
|
1352
|
+
// budget. Every other failure is the caller's.
|
|
1353
|
+
if (!(err instanceof WebvhLogConflictError)) {
|
|
1354
|
+
throw err;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
return withLogConflictRetry(() => setDelegatedClientsPointerOnce(rest));
|
|
1170
1359
|
}
|
|
1171
1360
|
/**
|
|
1172
1361
|
* ONE attempt of {@link setDelegatedClientsPointer}, re-invoked by that
|
|
1173
1362
|
* function's conflict retry and exported for a caller that runs its own.
|
|
1174
1363
|
* A caller whose retry loop also attributes the signing rung must use this
|
|
1175
1364
|
* form: the wrapper's inner retry would re-invoke the attempt with the SAME
|
|
1176
|
-
*
|
|
1365
|
+
* signer, and a rung a racing ceremony consumed meanwhile can never
|
|
1177
1366
|
* become authorized by re-reading, so the attempt would fail on the plain
|
|
1178
1367
|
* not-authorized refusal instead of surfacing the conflict the caller's loop
|
|
1179
1368
|
* knows how to re-attribute from.
|
|
1180
1369
|
*
|
|
1181
|
-
* A caller that already read the head its
|
|
1370
|
+
* A caller that already read the head its signer was attributed
|
|
1182
1371
|
* against passes it as `published`, and the entry is built on exactly that
|
|
1183
1372
|
* head. A racing entry landing in between then loses the CAS on the PUT and
|
|
1184
1373
|
* surfaces as a {@link WebvhLogConflictError}, which is what a re-attributing
|
|
@@ -1189,72 +1378,64 @@ export async function setDelegatedClientsPointer(options) {
|
|
|
1189
1378
|
* @param [options.published] {PublishedWebvhLog} the verified head to build
|
|
1190
1379
|
* this entry on, when the caller has already read it under the same pin;
|
|
1191
1380
|
* absent, the attempt reads the head itself
|
|
1192
|
-
* @returns {Promise<{ did: string, doc: DIDDoc
|
|
1381
|
+
* @returns {Promise<{ did: string, doc: DIDDoc,
|
|
1382
|
+
* published: PublishedWebvhLog }>}
|
|
1193
1383
|
*/
|
|
1194
|
-
export async function setDelegatedClientsPointerOnce({ idStore,
|
|
1384
|
+
export async function setDelegatedClientsPointerOnce({ idStore, signer, clientAnnexDid, expectedDid, pinStore, logId, logOnly = false, published: alreadyRead }) {
|
|
1195
1385
|
// Refuses a malformed target before anything is read or written.
|
|
1196
1386
|
clientAnnexDidParts({ did: clientAnnexDid });
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
...(expectedDid !== undefined ? { expectedDid } : {}),
|
|
1201
|
-
...(pinStore !== undefined ? { pinStore } : {}),
|
|
1202
|
-
...(logId !== undefined ? { logId } : {})
|
|
1203
|
-
}));
|
|
1204
|
-
if (!published) {
|
|
1205
|
-
throw new Error('did:webvh: did.jsonl is missing; nothing to point at a client annex.');
|
|
1206
|
-
}
|
|
1207
|
-
const { did, doc } = published;
|
|
1208
|
-
if (delegatedClientsPointer({ doc }) === clientAnnexDid) {
|
|
1209
|
-
if (!logOnly) {
|
|
1210
|
-
await concludeWithPublishedLog({ idStore, published });
|
|
1211
|
-
}
|
|
1212
|
-
return { did, doc };
|
|
1213
|
-
}
|
|
1214
|
-
// The entry is signed by this client's active update key; a log that does
|
|
1215
|
-
// not authorize it (a rotation torn elsewhere) must heal first.
|
|
1216
|
-
const activeKey = await updateKeyMultibase({ seed: updateKeys.updateSeed });
|
|
1217
|
-
if (!published.updateKeys.includes(activeKey)) {
|
|
1218
|
-
throw new Error("did:webvh: the published log does not authorize this client's active " +
|
|
1219
|
-
'update key; finalize the pending rotation before re-pointing the ' +
|
|
1220
|
-
'delegated-clients entry.');
|
|
1221
|
-
}
|
|
1222
|
-
await assertCarryOverCommitments({ published });
|
|
1223
|
-
const services = servicesPointedAtClientAnnex({
|
|
1224
|
-
doc,
|
|
1225
|
-
accountDid: did,
|
|
1226
|
-
clientAnnexDid
|
|
1227
|
-
});
|
|
1228
|
-
const signer = await updateKeySigner({ seed: updateKeys.updateSeed });
|
|
1229
|
-
const updated = await updateDID({
|
|
1230
|
-
log: published.log,
|
|
1387
|
+
let settled;
|
|
1388
|
+
const outcome = await signAccountEntry({
|
|
1389
|
+
idStore,
|
|
1231
1390
|
signer,
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1391
|
+
...(alreadyRead !== undefined ? { published: alreadyRead } : {}),
|
|
1392
|
+
...(expectedDid !== undefined ? { expectedDid } : {}),
|
|
1393
|
+
...(pinStore ? { pinStore } : {}),
|
|
1394
|
+
...(logId !== undefined ? { logId } : {}),
|
|
1395
|
+
missingMessage: 'did:webvh: did.jsonl is missing; nothing to point at a client annex.',
|
|
1396
|
+
verb: 're-pointing the delegated-clients entry',
|
|
1397
|
+
logOnly,
|
|
1398
|
+
build: async ({ published }) => {
|
|
1399
|
+
const { did, doc } = published;
|
|
1400
|
+
if (delegatedClientsPointer({ doc }) === clientAnnexDid) {
|
|
1401
|
+
if (!logOnly && signer.kind === 'client') {
|
|
1402
|
+
await concludeWithPublishedLog({ idStore, published });
|
|
1403
|
+
}
|
|
1404
|
+
// The head verbatim: the projection PUT touches no log, so this
|
|
1405
|
+
// read's own ETag is still the log's validator.
|
|
1406
|
+
settled = { did, doc, published };
|
|
1407
|
+
return undefined;
|
|
1408
|
+
}
|
|
1409
|
+
return {
|
|
1410
|
+
services: servicesPointedAtClientAnnex({
|
|
1411
|
+
doc,
|
|
1412
|
+
accountDid: did,
|
|
1413
|
+
clientAnnexDid
|
|
1414
|
+
})
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1239
1417
|
});
|
|
1240
|
-
if (
|
|
1241
|
-
|
|
1242
|
-
store: idStore,
|
|
1243
|
-
log: updated.log,
|
|
1244
|
-
ifMatch: published.etag
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
else {
|
|
1248
|
-
await publishUpdatedLog({ idStore, updated, ifMatch: published.etag });
|
|
1249
|
-
}
|
|
1250
|
-
// Advance the pin to what this entry just published, so a host serving the
|
|
1251
|
-
// pre-entry log straight afterwards is refused as a rollback on the next
|
|
1252
|
-
// read (equal-to-pin would otherwise be accepted, and a composition built
|
|
1253
|
-
// on the stale head would re-heal and mint litter).
|
|
1254
|
-
if (pinStore && logId !== undefined) {
|
|
1255
|
-
await pinStore.write({ logId, pin: pinOfLog(updated.log) });
|
|
1418
|
+
if (settled) {
|
|
1419
|
+
return settled;
|
|
1256
1420
|
}
|
|
1257
|
-
|
|
1421
|
+
const updated = outcome.updated;
|
|
1422
|
+
return {
|
|
1423
|
+
did: updated.did,
|
|
1424
|
+
doc: updated.doc,
|
|
1425
|
+
// The post-entry head, from what `updateDID` already resolved plus this
|
|
1426
|
+
// publish's own validator: the update-key parameters are the ones the
|
|
1427
|
+
// entry re-stated unchanged above, so nothing is re-resolved or re-read.
|
|
1428
|
+
published: {
|
|
1429
|
+
log: updated.log,
|
|
1430
|
+
did: updated.did,
|
|
1431
|
+
// Detached from the entry's own `state`, as every other producer of
|
|
1432
|
+
// this type is, so a consumer editing the document cannot edit the log.
|
|
1433
|
+
doc: structuredClone(updated.doc),
|
|
1434
|
+
updateKeys: updated.meta.updateKeys,
|
|
1435
|
+
nextKeyHashes: updated.meta.nextKeyHashes,
|
|
1436
|
+
...(outcome.etag !== undefined ? { etag: outcome.etag } : {})
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1258
1439
|
}
|
|
1259
1440
|
/**
|
|
1260
1441
|
* The whole transient-enrollment ceremony as the enrollee runs it: resolve
|
|
@@ -1291,9 +1472,15 @@ export async function setDelegatedClientsPointerOnce({ idStore, updateKeys, clie
|
|
|
1291
1472
|
* @param [options.maxRounds] {number} how many pointer moves to chase
|
|
1292
1473
|
* before giving up (a GC pass is quarterly, so more than one mid-ceremony
|
|
1293
1474
|
* move means something else is wrong)
|
|
1475
|
+
* @param [options.published] {PublishedWebvhLog} a generation head the
|
|
1476
|
+
* caller already read and verified under this same pin store (the readiness
|
|
1477
|
+
* stage's, when it published nothing to that log). Round 0 alone rides it,
|
|
1478
|
+
* and only when it is the generation the account document points at NOW: a
|
|
1479
|
+
* head for any other generation is ignored and the round reads fresh, as
|
|
1480
|
+
* every later round does
|
|
1294
1481
|
* @returns {Promise<{ clientAnnexDid: string, doc: DIDDoc, log: DIDLog }>}
|
|
1295
1482
|
*/
|
|
1296
|
-
export async function enrollTransientClient({ readAccountDocument, storeForGenerationId, ladderSeed, transientKeyMultibase, mintGenerationDelegation: mintDelegation, pinStore, maxRounds = 3 }) {
|
|
1483
|
+
export async function enrollTransientClient({ readAccountDocument, storeForGenerationId, ladderSeed, transientKeyMultibase, mintGenerationDelegation: mintDelegation, pinStore, maxRounds = 3, published: threadedHead }) {
|
|
1297
1484
|
let accountDoc = await readAccountDocument();
|
|
1298
1485
|
for (let round = 0; round < maxRounds; round++) {
|
|
1299
1486
|
const clientAnnexDid = delegatedClientsPointer({ doc: accountDoc });
|
|
@@ -1304,12 +1491,19 @@ export async function enrollTransientClient({ readAccountDocument, storeForGener
|
|
|
1304
1491
|
const { spaceId, generationId } = clientAnnexDidParts({
|
|
1305
1492
|
did: clientAnnexDid
|
|
1306
1493
|
});
|
|
1494
|
+
// Round 0's threaded head, and only for the generation the pointer names:
|
|
1495
|
+
// a head read before a GC swap belongs to the abandoned generation and
|
|
1496
|
+
// says nothing about the one this round enrolls into.
|
|
1497
|
+
const head = round === 0 && threadedHead?.did === clientAnnexDid
|
|
1498
|
+
? threadedHead
|
|
1499
|
+
: undefined;
|
|
1307
1500
|
const enrolled = await enrollClientAnnexTransientClient({
|
|
1308
1501
|
store: storeForGenerationId(generationId),
|
|
1309
1502
|
ladderSeed,
|
|
1310
1503
|
generationId,
|
|
1311
1504
|
transientKeyMultibase,
|
|
1312
1505
|
expectedDid: clientAnnexDid,
|
|
1506
|
+
...(head !== undefined ? { published: head } : {}),
|
|
1313
1507
|
...(mintDelegation !== undefined
|
|
1314
1508
|
? { mintGenerationDelegation: mintDelegation }
|
|
1315
1509
|
: {}),
|
|
@@ -1332,8 +1526,8 @@ export async function enrollTransientClient({ readAccountDocument, storeForGener
|
|
|
1332
1526
|
* RENEW PRECEDES MINT: the blocking pre-mint stage a transient App Connect
|
|
1333
1527
|
* approval runs before delegating any grant. Reads the annex document
|
|
1334
1528
|
* and hands back its embedded generation delegation -- renewing it first
|
|
1335
|
-
* when it is
|
|
1336
|
-
*
|
|
1529
|
+
* when it is stale under the house policy ({@link standingZcapStale}): a
|
|
1530
|
+
* fresh delegation is minted through the caller's closure
|
|
1337
1531
|
* (ladder-signed -- the renewal must not depend on the very delegation it
|
|
1338
1532
|
* replaces; published through the store, which in a transient session is
|
|
1339
1533
|
* the credential's sibling delegation, so even a hard-expired delegation is
|
|
@@ -1344,14 +1538,16 @@ export async function enrollTransientClient({ readAccountDocument, storeForGener
|
|
|
1344
1538
|
* same way (the GC ceremony's own install stage and the first-VM install
|
|
1345
1539
|
* make this rare; a heal, not a policy).
|
|
1346
1540
|
*
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1541
|
+
* That policy is three axes. Expiry -- past, or inside the 30-day renewal
|
|
1542
|
+
* window. SIGNER DEATH, which an `accountDoc` adds: a standing delegation
|
|
1543
|
+
* whose proof key is no longer in the supplied verified account document has
|
|
1544
|
+
* rotted under the current-key-set rule (the enrolled client that minted it
|
|
1545
|
+
* was revoked, or the ladder VM that signed it left with the first
|
|
1546
|
+
* self-enrollment). And RETIREMENT, the projected post-edit reading a caller
|
|
1547
|
+
* asks for with `retiringKeyMultibases`, for a key still listed whose
|
|
1548
|
+
* authority the ceremony is about to end. No revocation POST accompanies the
|
|
1549
|
+
* replacement: a rotted chain no longer verifies at the revocation endpoint,
|
|
1550
|
+
* and the expiry-renewal path never revoked either.
|
|
1355
1551
|
*
|
|
1356
1552
|
* Failure is the caller's failure: a renewal that cannot complete throws,
|
|
1357
1553
|
* and the App Connect approval fails with the standard retryable-ceremony
|
|
@@ -1380,64 +1576,96 @@ export async function enrollTransientClient({ readAccountDocument, storeForGener
|
|
|
1380
1576
|
* @param [options.accountDoc] {PublishedKeyDocument} the locally VERIFIED
|
|
1381
1577
|
* account document; supplied, a standing delegation whose proof key it no
|
|
1382
1578
|
* longer lists is replaced (the signer-death axis above)
|
|
1383
|
-
* @param [options.
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1579
|
+
* @param [options.retiringKeyMultibases] {string[]} keys whose authority is
|
|
1580
|
+
* about to end, read as a projected post-edit document: a standing
|
|
1581
|
+
* delegation one of them signed is replaced even though the served document
|
|
1582
|
+
* still lists the key. The last-client transition's replacement stage names
|
|
1583
|
+
* its own ladder VM (whose delegations it revokes server-side in the next
|
|
1584
|
+
* breath, a state no client-side predicate can read) and the client its
|
|
1585
|
+
* removal entry has yet to strike
|
|
1387
1586
|
* @param [options.now] {number} epoch milliseconds, for tests
|
|
1388
|
-
* @
|
|
1587
|
+
* @param [options.published] {PublishedWebvhLog} a head the caller already
|
|
1588
|
+
* read and verified under this same pin slot, so the stage builds on it
|
|
1589
|
+
* instead of spending a second round trip on the same log (the transient
|
|
1590
|
+
* visit's one-read composition). The FIRST attempt alone rides it: a lost
|
|
1591
|
+
* compare-and-swap means the head is stale by definition, so the conflict
|
|
1592
|
+
* retry re-reads under the pin. That threaded attempt is EXTRA rather than
|
|
1593
|
+
* one of the retry's three: a caller who saved a read is left with the same
|
|
1594
|
+
* conflict budget as one who did not
|
|
1595
|
+
* @returns {Promise<{ delegation: IZcap, renewed: boolean,
|
|
1596
|
+
* published?: PublishedWebvhLog }>} `published` is the verified head this
|
|
1597
|
+
* pass stood on -- the one read, or the one handed in -- and is present
|
|
1598
|
+
* ONLY when `renewed` is false. The two store implementations differ on
|
|
1599
|
+
* what a renewal's publish hands back: the controller-tier store forwards
|
|
1600
|
+
* the PUT's own ETag, while the delegated store discards the response and
|
|
1601
|
+
* yields none. This stage takes either, and cannot tell which from here, so
|
|
1602
|
+
* it stays conservative and passes no post-renewal head on; a caller
|
|
1603
|
+
* wanting one reads for itself
|
|
1389
1604
|
*/
|
|
1390
|
-
export async function ensureGenerationDelegationCurrent(
|
|
1391
|
-
|
|
1605
|
+
export async function ensureGenerationDelegationCurrent({ published: threadedHead, ...rest }) {
|
|
1606
|
+
if (threadedHead !== undefined) {
|
|
1607
|
+
try {
|
|
1608
|
+
return await ensureGenerationDelegationCurrentOnce({
|
|
1609
|
+
...rest,
|
|
1610
|
+
published: threadedHead
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
catch (err) {
|
|
1614
|
+
// A lost compare-and-swap on the threaded head says only that the head
|
|
1615
|
+
// is stale; the retry below re-reads under the pin with its whole
|
|
1616
|
+
// budget. Every other failure is the caller's.
|
|
1617
|
+
if (!(err instanceof WebvhLogConflictError)) {
|
|
1618
|
+
throw err;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
return withLogConflictRetry(() => ensureGenerationDelegationCurrentOnce(rest));
|
|
1392
1623
|
}
|
|
1393
1624
|
/**
|
|
1394
1625
|
* One attempt of {@link ensureGenerationDelegationCurrent}, re-invoked by the
|
|
1395
1626
|
* conflict retry (with the same signing key -- static rung 0 has no
|
|
1396
|
-
* advanced-rung retry shape).
|
|
1627
|
+
* advanced-rung retry shape) and with no threaded head after the first.
|
|
1397
1628
|
*
|
|
1398
1629
|
* @param options {object} see {@link ensureGenerationDelegationCurrent}
|
|
1399
|
-
* @returns {Promise<{ delegation: IZcap, renewed: boolean
|
|
1630
|
+
* @returns {Promise<{ delegation: IZcap, renewed: boolean,
|
|
1631
|
+
* published?: PublishedWebvhLog }>}
|
|
1400
1632
|
*/
|
|
1401
|
-
async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, generationId, mintGenerationDelegation: mintDelegation, expectedDid, pinStore, logId, accountDoc,
|
|
1633
|
+
async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, generationId, mintGenerationDelegation: mintDelegation, expectedDid, pinStore, logId, accountDoc, retiringKeyMultibases = [], now, published: alreadyRead }) {
|
|
1402
1634
|
assertGenerationId(generationId);
|
|
1403
|
-
const published =
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1635
|
+
const published = alreadyRead !== undefined
|
|
1636
|
+
? assertPublishedLogDid({
|
|
1637
|
+
published: alreadyRead,
|
|
1638
|
+
...(expectedDid !== undefined ? { expectedDid } : {})
|
|
1639
|
+
})
|
|
1640
|
+
: await readClientAnnexLogOrThrow({
|
|
1641
|
+
store,
|
|
1642
|
+
...(expectedDid !== undefined ? { expectedDid } : {}),
|
|
1643
|
+
...(pinStore !== undefined ? { pinStore } : {}),
|
|
1644
|
+
...(logId !== undefined ? { logId } : {})
|
|
1645
|
+
});
|
|
1409
1646
|
const { did, doc } = published;
|
|
1410
1647
|
const standing = embeddedGenerationDelegation({ doc });
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
doc: accountDoc,
|
|
1415
|
-
|
|
1416
|
-
? { delegationKeyId: delegationProofKeyId(standing) }
|
|
1417
|
-
: {})
|
|
1418
|
-
});
|
|
1419
|
-
if (!force &&
|
|
1420
|
-
standing !== undefined &&
|
|
1421
|
-
!signerRotted &&
|
|
1422
|
-
!zcapExpiring({
|
|
1423
|
-
...(standing.expires !== undefined
|
|
1424
|
-
? { expires: standing.expires }
|
|
1425
|
-
: {}),
|
|
1648
|
+
if (standing !== undefined &&
|
|
1649
|
+
!standingZcapStale({
|
|
1650
|
+
zcap: standing,
|
|
1651
|
+
...(accountDoc !== undefined ? { doc: accountDoc } : {}),
|
|
1652
|
+
retiringKeyMultibases,
|
|
1426
1653
|
...(now !== undefined ? { now } : {})
|
|
1427
1654
|
})) {
|
|
1428
|
-
return { delegation: standing, renewed: false };
|
|
1655
|
+
return { delegation: standing, renewed: false, published };
|
|
1429
1656
|
}
|
|
1430
1657
|
// The rung refusal precedes the mint: nothing is delegated for a writer
|
|
1431
1658
|
// who cannot publish the entry that would carry it.
|
|
1432
|
-
const rung = await
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1659
|
+
const rung = await admitActingRung({
|
|
1660
|
+
ladderSeed,
|
|
1661
|
+
generationId,
|
|
1662
|
+
updateKeys: published.updateKeys,
|
|
1663
|
+
nextKeyHashes: published.nextKeyHashes,
|
|
1664
|
+
message: "client annex: the log commits neither this credential's rung-0 key nor " +
|
|
1437
1665
|
'its hash; a credential bound mid-generation cannot renew the ' +
|
|
1438
1666
|
'generation delegation until a writer commits its hash or the next ' +
|
|
1439
|
-
'GC swap does.'
|
|
1440
|
-
}
|
|
1667
|
+
'GC swap does.'
|
|
1668
|
+
});
|
|
1441
1669
|
await assertCarryOverCommitments({ published });
|
|
1442
1670
|
const fresh = await mintDelegation({ clientAnnexDid: did });
|
|
1443
1671
|
const signer = await updateKeySigner({ seed: rung.seed });
|
|
@@ -1457,6 +1685,13 @@ async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, genera
|
|
|
1457
1685
|
})
|
|
1458
1686
|
});
|
|
1459
1687
|
await putLogResource({ store, log: updated.log, ifMatch: published.etag });
|
|
1688
|
+
// Advance the pin to what this entry just published, so a host serving the
|
|
1689
|
+
// pre-entry log straight afterwards is refused as a rollback on the next
|
|
1690
|
+
// read (equal-to-pin would otherwise be accepted, and a later stage built on
|
|
1691
|
+
// the stale head would miss this entry).
|
|
1692
|
+
if (pinStore && logId !== undefined) {
|
|
1693
|
+
await pinStore.write({ logId, pin: pinOfLog(updated.log) });
|
|
1694
|
+
}
|
|
1460
1695
|
return { delegation: fresh, renewed: true };
|
|
1461
1696
|
}
|
|
1462
1697
|
/**
|
|
@@ -1527,17 +1762,15 @@ async function retireClientAnnexRungOnce({ store, retiredLadderSeed, actingLadde
|
|
|
1527
1762
|
}
|
|
1528
1763
|
// The acting rung must be committed AFTER the retired members are
|
|
1529
1764
|
// excluded, so the retired credential can never sign its own strike.
|
|
1530
|
-
const acting = await
|
|
1765
|
+
const acting = await admitActingRung({
|
|
1531
1766
|
ladderSeed: actingLadderSeed,
|
|
1532
|
-
generationId
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
if (!revealed && !remainingHashes.includes(actingHash)) {
|
|
1537
|
-
throw new ClientAnnexRungUncommittedError("client annex: the log commits neither the acting credential's rung-0 " +
|
|
1767
|
+
generationId,
|
|
1768
|
+
updateKeys: remainingKeys,
|
|
1769
|
+
nextKeyHashes: remainingHashes,
|
|
1770
|
+
message: "client annex: the log commits neither the acting credential's rung-0 " +
|
|
1538
1771
|
'key nor its hash (or it is the retired rung itself); the strike ' +
|
|
1539
|
-
'needs a distinct committed writer -- swap the generation instead.'
|
|
1540
|
-
}
|
|
1772
|
+
'needs a distinct committed writer -- swap the generation instead.'
|
|
1773
|
+
});
|
|
1541
1774
|
await assertCarryOverCommitments({ published });
|
|
1542
1775
|
const signer = await updateKeySigner({ seed: acting.seed });
|
|
1543
1776
|
const updated = await updateDID({
|
|
@@ -1617,17 +1850,15 @@ async function commitClientAnnexRungOnce({ store, boundLadderSeed, actingLadderS
|
|
|
1617
1850
|
// generation the bound credential itself minted.
|
|
1618
1851
|
return { committed: false };
|
|
1619
1852
|
}
|
|
1620
|
-
const acting = await
|
|
1853
|
+
const acting = await admitActingRung({
|
|
1621
1854
|
ladderSeed: actingLadderSeed,
|
|
1622
|
-
generationId
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
if (!revealed && !published.nextKeyHashes.includes(actingHash)) {
|
|
1627
|
-
throw new ClientAnnexRungUncommittedError("client annex: the log commits neither the acting credential's rung-0 " +
|
|
1855
|
+
generationId,
|
|
1856
|
+
updateKeys: published.updateKeys,
|
|
1857
|
+
nextKeyHashes: published.nextKeyHashes,
|
|
1858
|
+
message: "client annex: the log commits neither the acting credential's rung-0 " +
|
|
1628
1859
|
'key nor its hash; a commit entry needs a committed writer -- the ' +
|
|
1629
|
-
'bound credential stays locked out until the next GC swap.'
|
|
1630
|
-
}
|
|
1860
|
+
'bound credential stays locked out until the next GC swap.'
|
|
1861
|
+
});
|
|
1631
1862
|
await assertCarryOverCommitments({ published });
|
|
1632
1863
|
const signer = await updateKeySigner({ seed: acting.seed });
|
|
1633
1864
|
const updated = await updateDID({
|