@interop/wallet-core 0.53.0 → 0.55.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/dist/clientAnnex/credentialAnchoredGenesis.d.ts +24 -10
- package/dist/clientAnnex/credentialAnchoredGenesis.d.ts.map +1 -1
- package/dist/clientAnnex/credentialAnchoredGenesis.js +42 -13
- package/dist/clientAnnex/credentialAnchoredGenesis.js.map +1 -1
- package/dist/clientAnnex/heal.d.ts +199 -0
- package/dist/clientAnnex/heal.d.ts.map +1 -0
- package/dist/clientAnnex/heal.js +421 -0
- package/dist/clientAnnex/heal.js.map +1 -0
- package/dist/clientAnnex/index.d.ts +2 -0
- package/dist/clientAnnex/index.d.ts.map +1 -1
- package/dist/clientAnnex/index.js +1 -0
- package/dist/clientAnnex/index.js.map +1 -1
- package/dist/clientAnnex/ladderAnchored.d.ts +15 -2
- package/dist/clientAnnex/ladderAnchored.d.ts.map +1 -1
- package/dist/clientAnnex/ladderAnchored.js +30 -4
- package/dist/clientAnnex/ladderAnchored.js.map +1 -1
- package/dist/clientAnnex/log.d.ts.map +1 -1
- package/dist/clientAnnex/log.js +8 -1
- package/dist/clientAnnex/log.js.map +1 -1
- package/dist/recovery/recoveryWebvh.d.ts +12 -2
- package/dist/recovery/recoveryWebvh.d.ts.map +1 -1
- package/dist/recovery/recoveryWebvh.js +12 -2
- package/dist/recovery/recoveryWebvh.js.map +1 -1
- package/dist/unlock/standingWebvh.d.ts +10 -0
- package/dist/unlock/standingWebvh.d.ts.map +1 -1
- package/dist/unlock/standingWebvh.js +10 -2
- package/dist/unlock/standingWebvh.js.map +1 -1
- package/dist/webvh/didWebvh.d.ts +23 -2
- package/dist/webvh/didWebvh.d.ts.map +1 -1
- package/dist/webvh/didWebvh.js +37 -14
- package/dist/webvh/didWebvh.js.map +1 -1
- package/package.json +2 -2
|
@@ -12,19 +12,24 @@
|
|
|
12
12
|
* resolution of the orphan-Space tear): re-derivable from the unlock
|
|
13
13
|
* record's ladder seed, so a tab death here strands nothing a later
|
|
14
14
|
* login cannot finish or unwind.
|
|
15
|
-
* 2. The
|
|
15
|
+
* 2. The optional KMS key map (`provideDidWebKeys`), acquired only once the
|
|
16
|
+
* Space exists -- the durable ceremony's stage, verbatim: a KMS-keeping
|
|
17
|
+
* wallet creates the keystore under the ladder VM's bare did:key and
|
|
18
|
+
* writes keys.json and did.json into the Space here. Best-effort: a throw
|
|
19
|
+
* is collected and the genesis proceeds keystore-less; a later login's
|
|
20
|
+
* heal supplies the missing convenience key.
|
|
21
|
+
* 3. The ladder-anchored did:webvh genesis
|
|
16
22
|
* (`ensureLadderAnchoredDidWebvh`): the entry signed by ladder rung 0, `updateKeys` = [rung 0], `nextKeyHashes`
|
|
17
23
|
* = [hash(rung 0), hash(rung 1)], the ladder VM and the credential's
|
|
18
|
-
* `keyAgreement` inventory folded in,
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* 3. The user-key roster genesis, wrapped to the CREDENTIAL's standing
|
|
24
|
+
* `keyAgreement` inventory folded in -- plus, when stage 2 delivered a key
|
|
25
|
+
* map, the KMS-held authentication VM under `authentication` only --
|
|
26
|
+
* `portable` unchanged.
|
|
27
|
+
* 4. The user-key roster genesis, wrapped to the CREDENTIAL's standing
|
|
23
28
|
* key-agreement key -- the only recipient a ladder-anchored account has
|
|
24
29
|
* -- with the entry proof signed by the ladder VM (the ceremony-tail
|
|
25
30
|
* license's first-entry shape). The account is credential-recoverable
|
|
26
31
|
* from the moment this lands.
|
|
27
|
-
*
|
|
32
|
+
* 5. Epoch[0] on every encrypted roster collection -- gated twice, unlike
|
|
28
33
|
* the durable flow. The roster stage must have landed, AND the roster's
|
|
29
34
|
* current epoch must BE the `userKey` this run was handed: the user key
|
|
30
35
|
* here exists only in this tab's memory, so installing collection epochs
|
|
@@ -39,7 +44,7 @@
|
|
|
39
44
|
* that recovers the roster's real key is the one installer. With both
|
|
40
45
|
* gates the tear heal is always clean: no roster means no epochs, and a
|
|
41
46
|
* fresh user key re-runs both.
|
|
42
|
-
*
|
|
47
|
+
* 6. Space-controller promotion, last -- every earlier stage ran under the
|
|
43
48
|
* bootstrap did:key the Space's stored controller authorizes.
|
|
44
49
|
*
|
|
45
50
|
* Idempotent end to end on the durable flow's convention; the did:webvh
|
|
@@ -49,7 +54,7 @@
|
|
|
49
54
|
import { type RecipientPublicKey } from '@interop/was-client/edv';
|
|
50
55
|
import type { WasClient } from '@interop/was-client';
|
|
51
56
|
import type { EncryptionDescriptorStore } from '@interop/was-client/edv';
|
|
52
|
-
import type { WebvhIdStore } from '../webvh/didWebvh.js';
|
|
57
|
+
import type { DidWebKeyMapV2, WebvhIdStore } from '../webvh/didWebvh.js';
|
|
53
58
|
import type { UnlockKeyAgreementPublication } from '../unlock/standingWebvh.js';
|
|
54
59
|
import { type UserKey } from '../keys/index.js';
|
|
55
60
|
import type { ResourceLogPinStore } from '@interop/vh-resource-log';
|
|
@@ -93,6 +98,14 @@ export declare function mintCredentialAnchoredAccountKeySet(): Promise<{
|
|
|
93
98
|
* @param options.rosterStoreFor {Function} `({ did }) =>
|
|
94
99
|
* EncryptionDescriptorStore` -- the user-key roster's store once the DID is
|
|
95
100
|
* known, with a LADDER-signed `ResourceLogSigner` (the first-entry shape)
|
|
101
|
+
* @param [options.provideDidWebKeys] {Function} `() =>
|
|
102
|
+
* Promise<DidWebKeyMapV2 | undefined>` -- the KMS key-map acquisition (a
|
|
103
|
+
* wallet that keeps a KMS runs its did:web provisioning here, after the
|
|
104
|
+
* Space exists, under the ladder VM's bare did:key); absent or resolving
|
|
105
|
+
* `undefined`, the genesis is ladder-and-credential-only and no `keys.json`
|
|
106
|
+
* is ever written. A throw is collected, not fatal: the genesis proceeds
|
|
107
|
+
* keystore-less and a later login heals the document with the convenience
|
|
108
|
+
* key
|
|
96
109
|
* @param [options.expectedDid] {string} the account DID, when the caller
|
|
97
110
|
* holds a pointer that already names one (a heal re-run); a fresh signup
|
|
98
111
|
* and a fresh-terminal heal legitimately hold none
|
|
@@ -105,7 +118,7 @@ export declare function mintCredentialAnchoredAccountKeySet(): Promise<{
|
|
|
105
118
|
* re-bind) passes `false` and promotes after that write
|
|
106
119
|
* @returns {Promise<AccountGenesisResult>}
|
|
107
120
|
*/
|
|
108
|
-
export declare function ensureCredentialAnchoredAccountGenesis({ was, wasServerUrl, spaceId, ladderSeed, keyAgreement, standingRecipient, userKey, idStore, rosterStoreFor, expectedDid, accountLogPinStore, onDidPublished, promoteController }: {
|
|
121
|
+
export declare function ensureCredentialAnchoredAccountGenesis({ was, wasServerUrl, spaceId, ladderSeed, keyAgreement, standingRecipient, userKey, idStore, rosterStoreFor, provideDidWebKeys, expectedDid, accountLogPinStore, onDidPublished, promoteController }: {
|
|
109
122
|
was: WasClient;
|
|
110
123
|
wasServerUrl: string;
|
|
111
124
|
spaceId: string;
|
|
@@ -117,6 +130,7 @@ export declare function ensureCredentialAnchoredAccountGenesis({ was, wasServerU
|
|
|
117
130
|
rosterStoreFor: (options: {
|
|
118
131
|
did: string;
|
|
119
132
|
}) => EncryptionDescriptorStore;
|
|
133
|
+
provideDidWebKeys?: () => Promise<DidWebKeyMapV2 | undefined>;
|
|
120
134
|
expectedDid?: string;
|
|
121
135
|
accountLogPinStore?: ResourceLogPinStore;
|
|
122
136
|
onDidPublished?: (published: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentialAnchoredGenesis.d.ts","sourceRoot":"","sources":["../../src/clientAnnex/credentialAnchoredGenesis.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"credentialAnchoredGenesis.d.ts","sourceRoot":"","sources":["../../src/clientAnnex/credentialAnchoredGenesis.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAwB,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC1E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AAIxE,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAExE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAA;AAC/E,OAAO,EAGL,KAAK,OAAO,EAEb,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AACnE,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,8BAA8B,CAAA;AAErC;;;;;;;;GAQG;AACH,wBAAsB,mCAAmC,IAAI,OAAO,CAAC;IACnE,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CAC3B,CAAC,CAED;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAsB,sCAAsC,CAAC,EAC3D,GAAG,EACH,YAAY,EACZ,OAAO,EACP,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,OAAO,EACP,OAAO,EACP,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,iBAAwB,EACzB,EAAE;IACD,GAAG,EAAE,SAAS,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,UAAU,CAAA;IACtB,YAAY,EAAE,6BAA6B,CAAA;IAC3C,iBAAiB,EAAE,kBAAkB,CAAA;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,YAAY,CAAA;IACrB,cAAc,EAAE,CAAC,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,yBAAyB,CAAA;IACvE,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAA;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB,CAAC,EAAE,mBAAmB,CAAA;IACxC,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9D,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA0GhC"}
|
|
@@ -12,19 +12,24 @@
|
|
|
12
12
|
* resolution of the orphan-Space tear): re-derivable from the unlock
|
|
13
13
|
* record's ladder seed, so a tab death here strands nothing a later
|
|
14
14
|
* login cannot finish or unwind.
|
|
15
|
-
* 2. The
|
|
15
|
+
* 2. The optional KMS key map (`provideDidWebKeys`), acquired only once the
|
|
16
|
+
* Space exists -- the durable ceremony's stage, verbatim: a KMS-keeping
|
|
17
|
+
* wallet creates the keystore under the ladder VM's bare did:key and
|
|
18
|
+
* writes keys.json and did.json into the Space here. Best-effort: a throw
|
|
19
|
+
* is collected and the genesis proceeds keystore-less; a later login's
|
|
20
|
+
* heal supplies the missing convenience key.
|
|
21
|
+
* 3. The ladder-anchored did:webvh genesis
|
|
16
22
|
* (`ensureLadderAnchoredDidWebvh`): the entry signed by ladder rung 0, `updateKeys` = [rung 0], `nextKeyHashes`
|
|
17
23
|
* = [hash(rung 0), hash(rung 1)], the ladder VM and the credential's
|
|
18
|
-
* `keyAgreement` inventory folded in,
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* 3. The user-key roster genesis, wrapped to the CREDENTIAL's standing
|
|
24
|
+
* `keyAgreement` inventory folded in -- plus, when stage 2 delivered a key
|
|
25
|
+
* map, the KMS-held authentication VM under `authentication` only --
|
|
26
|
+
* `portable` unchanged.
|
|
27
|
+
* 4. The user-key roster genesis, wrapped to the CREDENTIAL's standing
|
|
23
28
|
* key-agreement key -- the only recipient a ladder-anchored account has
|
|
24
29
|
* -- with the entry proof signed by the ladder VM (the ceremony-tail
|
|
25
30
|
* license's first-entry shape). The account is credential-recoverable
|
|
26
31
|
* from the moment this lands.
|
|
27
|
-
*
|
|
32
|
+
* 5. Epoch[0] on every encrypted roster collection -- gated twice, unlike
|
|
28
33
|
* the durable flow. The roster stage must have landed, AND the roster's
|
|
29
34
|
* current epoch must BE the `userKey` this run was handed: the user key
|
|
30
35
|
* here exists only in this tab's memory, so installing collection epochs
|
|
@@ -39,7 +44,7 @@
|
|
|
39
44
|
* that recovers the roster's real key is the one installer. With both
|
|
40
45
|
* gates the tear heal is always clean: no roster means no epochs, and a
|
|
41
46
|
* fresh user key re-runs both.
|
|
42
|
-
*
|
|
47
|
+
* 6. Space-controller promotion, last -- every earlier stage ran under the
|
|
43
48
|
* bootstrap did:key the Space's stored controller authorizes.
|
|
44
49
|
*
|
|
45
50
|
* Idempotent end to end on the durable flow's convention; the did:webvh
|
|
@@ -90,6 +95,14 @@ export async function mintCredentialAnchoredAccountKeySet() {
|
|
|
90
95
|
* @param options.rosterStoreFor {Function} `({ did }) =>
|
|
91
96
|
* EncryptionDescriptorStore` -- the user-key roster's store once the DID is
|
|
92
97
|
* known, with a LADDER-signed `ResourceLogSigner` (the first-entry shape)
|
|
98
|
+
* @param [options.provideDidWebKeys] {Function} `() =>
|
|
99
|
+
* Promise<DidWebKeyMapV2 | undefined>` -- the KMS key-map acquisition (a
|
|
100
|
+
* wallet that keeps a KMS runs its did:web provisioning here, after the
|
|
101
|
+
* Space exists, under the ladder VM's bare did:key); absent or resolving
|
|
102
|
+
* `undefined`, the genesis is ladder-and-credential-only and no `keys.json`
|
|
103
|
+
* is ever written. A throw is collected, not fatal: the genesis proceeds
|
|
104
|
+
* keystore-less and a later login heals the document with the convenience
|
|
105
|
+
* key
|
|
93
106
|
* @param [options.expectedDid] {string} the account DID, when the caller
|
|
94
107
|
* holds a pointer that already names one (a heal re-run); a fresh signup
|
|
95
108
|
* and a fresh-terminal heal legitimately hold none
|
|
@@ -102,7 +115,7 @@ export async function mintCredentialAnchoredAccountKeySet() {
|
|
|
102
115
|
* re-bind) passes `false` and promotes after that write
|
|
103
116
|
* @returns {Promise<AccountGenesisResult>}
|
|
104
117
|
*/
|
|
105
|
-
export async function ensureCredentialAnchoredAccountGenesis({ was, wasServerUrl, spaceId, ladderSeed, keyAgreement, standingRecipient, userKey, idStore, rosterStoreFor, expectedDid, accountLogPinStore, onDidPublished, promoteController = true }) {
|
|
118
|
+
export async function ensureCredentialAnchoredAccountGenesis({ was, wasServerUrl, spaceId, ladderSeed, keyAgreement, standingRecipient, userKey, idStore, rosterStoreFor, provideDidWebKeys, expectedDid, accountLogPinStore, onDidPublished, promoteController = true }) {
|
|
106
119
|
const failed = [];
|
|
107
120
|
const bootstrap = await ladderVmAgent({ ladderSeed });
|
|
108
121
|
// 1. The Space and its collection roster, create-if-absent under the
|
|
@@ -114,19 +127,35 @@ export async function ensureCredentialAnchoredAccountGenesis({ was, wasServerUrl
|
|
|
114
127
|
catch (err) {
|
|
115
128
|
throw new AccountGenesisSpaceError({ spaceId, cause: err });
|
|
116
129
|
}
|
|
117
|
-
// 2. The
|
|
130
|
+
// 2. The optional KMS key map, acquired only once the Space exists (a
|
|
131
|
+
// KMS-keeping wallet creates the keystore under the ladder VM's bare
|
|
132
|
+
// did:key and writes keys.json and did.json into the Space here). A throw
|
|
133
|
+
// degrades to the ladder-and-credential-only genesis rather than aborting:
|
|
134
|
+
// every later ceremony anchors in the ladder, and a later login heals the
|
|
135
|
+
// document with the convenience key.
|
|
136
|
+
let didWebKeys;
|
|
137
|
+
if (provideDidWebKeys) {
|
|
138
|
+
try {
|
|
139
|
+
didWebKeys = await provideDidWebKeys();
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
failed.push({ stage: 'didWebKeys', error: err });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// 3. The ladder-anchored did:webvh genesis -- probe, adopt
|
|
118
146
|
// (ladder-attributed), or create-and-publish. Fatal on failure, like the durable stage.
|
|
119
147
|
const { did } = await ensureLadderAnchoredDidWebvh({
|
|
120
148
|
idStore,
|
|
121
149
|
wasServerUrl,
|
|
122
150
|
spaceId,
|
|
151
|
+
...(didWebKeys ? { didWebKeys } : {}),
|
|
123
152
|
ladderSeed,
|
|
124
153
|
keyAgreement,
|
|
125
154
|
...(expectedDid !== undefined ? { expectedDid } : {}),
|
|
126
155
|
...(accountLogPinStore ? { pinStore: accountLogPinStore } : {})
|
|
127
156
|
});
|
|
128
157
|
await onDidPublished?.({ did });
|
|
129
|
-
//
|
|
158
|
+
// 4. The roster genesis: epoch[0] IS the user key, wrapped once, to the
|
|
130
159
|
// credential's standing key-agreement key. The store's ladder-signed
|
|
131
160
|
// genesis append is the ceremony-tail license's first-entry shape.
|
|
132
161
|
let rosterDescriptor;
|
|
@@ -145,7 +174,7 @@ export async function ensureCredentialAnchoredAccountGenesis({ was, wasServerUrl
|
|
|
145
174
|
catch (err) {
|
|
146
175
|
failed.push({ stage: 'roster', error: err });
|
|
147
176
|
}
|
|
148
|
-
//
|
|
177
|
+
// 5. Epoch[0] on every encrypted roster collection, only behind a landed
|
|
149
178
|
// roster whose current epoch IS this run's user key (see the module doc:
|
|
150
179
|
// the user key is memory-only here, and an adopted roster keyed to another
|
|
151
180
|
// run's key would have the collections installed under a throwaway).
|
|
@@ -167,7 +196,7 @@ export async function ensureCredentialAnchoredAccountGenesis({ was, wasServerUrl
|
|
|
167
196
|
: {};
|
|
168
197
|
}
|
|
169
198
|
}
|
|
170
|
-
//
|
|
199
|
+
// 6. The controller promotion, last. The bootstrap client IS the bare
|
|
171
200
|
// did:key client, so it serves the heal branch too.
|
|
172
201
|
let promotion;
|
|
173
202
|
if (promoteController) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentialAnchoredGenesis.js","sourceRoot":"","sources":["../../src/clientAnnex/credentialAnchoredGenesis.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"credentialAnchoredGenesis.js","sourceRoot":"","sources":["../../src/clientAnnex/credentialAnchoredGenesis.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The client-annex generation ensure a TRANSIENT visit runs -- a session
|
|
6
|
+
* holding nothing but a standing unlock credential (its ladder seed, its
|
|
7
|
+
* `delegatedClients` sibling delegation, and the standing-client identity
|
|
8
|
+
* derived from the typed secret). Four durable states make the annex
|
|
9
|
+
* unreachable from such a visit: the account document carries no
|
|
10
|
+
* `#DelegatedClients` pointer; the pointed generation's log is gone (GC'd,
|
|
11
|
+
* or never minted); the embedded generation delegation is expired, inside
|
|
12
|
+
* its renewal window, or signed by a key the document no longer lists; the
|
|
13
|
+
* record carries no sibling delegation, or its sibling targets another
|
|
14
|
+
* Space. On a ladder-anchored account -- the ladder VM a document
|
|
15
|
+
* verification method, the ladder's rungs the log's update keys -- the visit
|
|
16
|
+
* itself can mend all four, and this module is the orchestrator: a
|
|
17
|
+
* converging ensure that detects each state from durable state, mends it
|
|
18
|
+
* with the existing annex primitives, and reports what ran.
|
|
19
|
+
*
|
|
20
|
+
* The ordering rules are the established ones, composed rather than
|
|
21
|
+
* re-decided:
|
|
22
|
+
*
|
|
23
|
+
* - RENEW PRECEDES MINT: a live, verifiable pointed generation is renewed in
|
|
24
|
+
* place (`ensureGenerationDelegationCurrent`, the ladder-signed minter);
|
|
25
|
+
* only a dead generation -- or one whose log does not commit this
|
|
26
|
+
* credential's annex rung (`ClientAnnexRungUncommittedError`, the same
|
|
27
|
+
* escape the GC swap's no-committed-survivor arm takes) -- gets a fresh
|
|
28
|
+
* mint.
|
|
29
|
+
* - The PRE-FLIGHT RUNG ATTRIBUTION precedes any mint that will need a
|
|
30
|
+
* pointer entry: when no current account-log update key is a rung of this
|
|
31
|
+
* ladder, nothing is minted at all -- a generation the pointer entry could
|
|
32
|
+
* not then name would only widen the orphan window.
|
|
33
|
+
* - A fresh generation in an EXISTING Space mirrors the GC swap's stage
|
|
34
|
+
* order minus its revoke stage (mint, install the delegation, re-point --
|
|
35
|
+
* no transient reach could invoke the old delegation's revocation; the
|
|
36
|
+
* pointer move retires it on a conforming server and it otherwise rots on
|
|
37
|
+
* its TTL); a fresh SPACE mirrors the credential-anchored genesis
|
|
38
|
+
* ordering exactly (Space under the ladder VM's bare did:key, mint
|
|
39
|
+
* controller-tier, delegation embed while the Space still answers to the
|
|
40
|
+
* bootstrap key, controller flip, then the pointer entry).
|
|
41
|
+
* - Pointer entries go through the caller's account-log store with
|
|
42
|
+
* `logOnly: true`: a bridge-delegated writer has no `did.json` projection
|
|
43
|
+
* rights, and the log is the source of truth.
|
|
44
|
+
*
|
|
45
|
+
* A fresh sibling delegation (minted when the record carries none, or one
|
|
46
|
+
* targeting another Space) is handed back through the REQUIRED
|
|
47
|
+
* `onRebindRecord` seam after the generation and pointer are durable -- the
|
|
48
|
+
* caller re-seals the unlock record with it, and a run torn before that
|
|
49
|
+
* re-seal re-derives everything from the ladder seed at the next visit.
|
|
50
|
+
*/
|
|
51
|
+
import type { IZcap } from '@interop/data-integrity-core';
|
|
52
|
+
import type { ZcapClient } from '@interop/ezcap';
|
|
53
|
+
import { WasClient } from '@interop/was-client';
|
|
54
|
+
import type { ResourceLogPinStore } from '@interop/vh-resource-log';
|
|
55
|
+
import type { PublishedWebvhLog, WebvhIdStore } from '../webvh/didWebvh.js';
|
|
56
|
+
import type { ICapabilityAgent } from '../webvh/zcap.js';
|
|
57
|
+
/**
|
|
58
|
+
* Why the visit cannot mend the annex.
|
|
59
|
+
*
|
|
60
|
+
* `'ladder-vm-not-anchored'`: this credential's ladder VM is not a
|
|
61
|
+
* verification method of the account document, so nothing ladder-signed can
|
|
62
|
+
* verify. Two distinct states land here: an account with enrolled durable
|
|
63
|
+
* clients and no ladder VM at all (the self-enrollment's add entry removes
|
|
64
|
+
* them; the mender there is the step-up ceremony, not this ensure), and a
|
|
65
|
+
* client-less account anchored on ANOTHER standing credential's ladder --
|
|
66
|
+
* e.g. a passkey visiting an account the passphrase's ladder anchors, whose
|
|
67
|
+
* own VM no ceremony has published.
|
|
68
|
+
*
|
|
69
|
+
* `'update-key-not-attributable'`: a pointer entry is needed, but no current
|
|
70
|
+
* account-log update key is a rung derivable from this ladder seed, so the
|
|
71
|
+
* entry could not be signed.
|
|
72
|
+
*/
|
|
73
|
+
export type ClientAnnexGenerationUnavailableReason = 'ladder-vm-not-anchored' | 'update-key-not-attributable';
|
|
74
|
+
/**
|
|
75
|
+
* The typed refusal of {@link ensureCredentialClientAnnexGeneration}: the
|
|
76
|
+
* account is not in a shape this visit can mend, and nothing was written.
|
|
77
|
+
* Matched on `name` -- error classes do not survive crossing package copies.
|
|
78
|
+
*/
|
|
79
|
+
export declare class ClientAnnexGenerationUnavailableError extends Error {
|
|
80
|
+
readonly reason: ClientAnnexGenerationUnavailableReason;
|
|
81
|
+
constructor({ reason, message }: {
|
|
82
|
+
reason: ClientAnnexGenerationUnavailableReason;
|
|
83
|
+
message: string;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The ladder-signed generation-delegation minter: the
|
|
88
|
+
* `mintGenerationDelegation` closure shape `ensureGenerationDelegationCurrent`
|
|
89
|
+
* takes, signing with the credential's ladder VM (`ladderVmZcapClient`) --
|
|
90
|
+
* the renewal must not depend on the very delegation it replaces, and on a
|
|
91
|
+
* ladder-anchored account the ladder VM is the one licensed delegator.
|
|
92
|
+
* Exported on its own: the transient App Connect approval's blocking renewal
|
|
93
|
+
* stage consumes the same closure.
|
|
94
|
+
*
|
|
95
|
+
* @param options {object}
|
|
96
|
+
* @param options.accountDid {string} the account did:webvh
|
|
97
|
+
* @param options.ladderSeed {Uint8Array} the credential's ladder seed, from
|
|
98
|
+
* its unlock record
|
|
99
|
+
* @param options.wasServerUrl {string} the ACCOUNT Space's storage server
|
|
100
|
+
* @param options.spaceId {string} the ACCOUNT Space's id (the delegation's
|
|
101
|
+
* target subtree)
|
|
102
|
+
* @param [options.now] {number} epoch milliseconds, for tests
|
|
103
|
+
* @returns {Function} `({ clientAnnexDid }) => Promise<IZcap>`
|
|
104
|
+
*/
|
|
105
|
+
export declare function ladderSignedGenerationDelegationMinter({ accountDid, ladderSeed, wasServerUrl, spaceId, now }: {
|
|
106
|
+
accountDid: string;
|
|
107
|
+
ladderSeed: Uint8Array;
|
|
108
|
+
wasServerUrl: string;
|
|
109
|
+
spaceId: string;
|
|
110
|
+
now?: number;
|
|
111
|
+
}): (options: {
|
|
112
|
+
clientAnnexDid: string;
|
|
113
|
+
}) => Promise<IZcap>;
|
|
114
|
+
/**
|
|
115
|
+
* What one ensure pass did. Honest skips never throw: a `false` member means
|
|
116
|
+
* the durable state was already current, not that a stage failed. A
|
|
117
|
+
* superseded generation's own delegation is never revoked here (no transient
|
|
118
|
+
* reach could invoke the revocation); the pointer move retires it on a
|
|
119
|
+
* conforming server, and it otherwise rots on its TTL.
|
|
120
|
+
*/
|
|
121
|
+
export interface ClientAnnexGenerationEnsureOutcome {
|
|
122
|
+
clientAnnexDid: string;
|
|
123
|
+
generationDelegation: IZcap;
|
|
124
|
+
/**
|
|
125
|
+
* The usable sibling delegation -- the record's own, or the fresh one
|
|
126
|
+
* `onRebindRecord` was handed.
|
|
127
|
+
*/
|
|
128
|
+
delegatedClients: IZcap;
|
|
129
|
+
generationMinted: boolean;
|
|
130
|
+
spaceMinted: boolean;
|
|
131
|
+
delegationRenewed: boolean;
|
|
132
|
+
siblingReminted: boolean;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Ensures a transient visit can reach a live client-annex generation with a
|
|
136
|
+
* current generation delegation and a usable sibling delegation, mending
|
|
137
|
+
* from durable state alone (see the module doc for the states and the stage
|
|
138
|
+
* orders). A healthy account is a pure no-op report.
|
|
139
|
+
*
|
|
140
|
+
* Known residue: the pre-flight rung attribution runs against the SUPPLIED
|
|
141
|
+
* account view, while the pointer entry's own publish re-reads the log. The
|
|
142
|
+
* no-orphan guarantee therefore holds against that view; a concurrent
|
|
143
|
+
* ceremony advancing the rung between the two makes the pointer entry fail
|
|
144
|
+
* loudly AFTER the mint, leaving an inert unpointed generation this ensure
|
|
145
|
+
* does not reuse -- the next run converges on a fresh generation, and the
|
|
146
|
+
* orphan is the standing collect fan-out's to pick up.
|
|
147
|
+
*
|
|
148
|
+
* @param options {object}
|
|
149
|
+
* @param options.wasServerUrl {string} the account pointer's host
|
|
150
|
+
* @param options.spaceId {string} the ACCOUNT Space's id
|
|
151
|
+
* @param options.account {object} the VERIFIED account log view
|
|
152
|
+
* (`{ did, doc, log }` -- the caller's `verifyAccountLog` read; never
|
|
153
|
+
* re-fetched here)
|
|
154
|
+
* @param options.ladderSeed {Uint8Array} the credential's ladder seed, from
|
|
155
|
+
* its unlock record
|
|
156
|
+
* @param options.standingClient {object} the standing-client identity
|
|
157
|
+
* derived from the typed secret: `did` (the sibling delegation's
|
|
158
|
+
* controller) and `zcapClient` (its signer, which invokes annex requests
|
|
159
|
+
* under the sibling capability)
|
|
160
|
+
* @param options.bootstrapWasFor {Function} `({ keyAgent }) => WasClient`
|
|
161
|
+
* -- the storage client for the fresh-Space arm, signing as the ladder
|
|
162
|
+
* VM's bare did:key (the caller wires the transport; the agent is derived
|
|
163
|
+
* here from the ladder seed)
|
|
164
|
+
* @param options.idStore {WebvhIdStore} the ACCOUNT log's store (a
|
|
165
|
+
* bridge-delegated one suffices: pointer entries publish with
|
|
166
|
+
* `logOnly: true`)
|
|
167
|
+
* @param options.onRebindRecord {Function}
|
|
168
|
+
* `({ delegatedClients }) => Promise<void>` -- REQUIRED: re-seals the
|
|
169
|
+
* unlock record with the fresh sibling delegation; called whenever one was
|
|
170
|
+
* minted, after the generation and pointer are durable
|
|
171
|
+
* @param [options.delegatedClients] {IZcap} the record's sibling
|
|
172
|
+
* delegation, when the record carries one
|
|
173
|
+
* @param [options.pinStore] {ResourceLogPinStore} chain-head pins (a
|
|
174
|
+
* transient session passes an in-memory store); slot keys are derived here
|
|
175
|
+
* per log
|
|
176
|
+
* @param [options.now] {number} epoch milliseconds, for tests
|
|
177
|
+
* @returns {Promise<ClientAnnexGenerationEnsureOutcome>}
|
|
178
|
+
*/
|
|
179
|
+
export declare function ensureCredentialClientAnnexGeneration(options: {
|
|
180
|
+
wasServerUrl: string;
|
|
181
|
+
spaceId: string;
|
|
182
|
+
account: Pick<PublishedWebvhLog, 'did' | 'doc' | 'log'>;
|
|
183
|
+
ladderSeed: Uint8Array;
|
|
184
|
+
standingClient: {
|
|
185
|
+
did: string;
|
|
186
|
+
zcapClient: ZcapClient;
|
|
187
|
+
};
|
|
188
|
+
bootstrapWasFor: (options: {
|
|
189
|
+
keyAgent: ICapabilityAgent;
|
|
190
|
+
}) => WasClient;
|
|
191
|
+
idStore: WebvhIdStore;
|
|
192
|
+
onRebindRecord: (options: {
|
|
193
|
+
delegatedClients: IZcap;
|
|
194
|
+
}) => Promise<void>;
|
|
195
|
+
delegatedClients?: IZcap;
|
|
196
|
+
pinStore?: ResourceLogPinStore;
|
|
197
|
+
now?: number;
|
|
198
|
+
}): Promise<ClientAnnexGenerationEnsureOutcome>;
|
|
199
|
+
//# sourceMappingURL=heal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heal.d.ts","sourceRoot":"","sources":["../../src/clientAnnex/heal.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAEnE,OAAO,KAAK,EAEV,iBAAiB,EACjB,YAAY,EACb,MAAM,sBAAsB,CAAA;AAM7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAoBxD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,sCAAsC,GAChD,wBAAwB,GAAG,6BAA6B,CAAA;AAE1D;;;;GAIG;AACH,qBAAa,qCAAsC,SAAQ,KAAK;IAC9D,QAAQ,CAAC,MAAM,EAAE,sCAAsC,CAAA;gBAE3C,EACV,MAAM,EACN,OAAO,EACR,EAAE;QACD,MAAM,EAAE,sCAAsC,CAAA;QAC9C,OAAO,EAAE,MAAM,CAAA;KAChB;CAKF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sCAAsC,CAAC,EACrD,UAAU,EACV,UAAU,EACV,YAAY,EACZ,OAAO,EACP,GAAG,EACJ,EAAE;IACD,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,UAAU,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,GAAG,CAAC,OAAO,EAAE;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC,KAAK,CAAC,CAW1D;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kCAAkC;IACjD,cAAc,EAAE,MAAM,CAAA;IACtB,oBAAoB,EAAE,KAAK,CAAA;IAC3B;;;OAGG;IACH,gBAAgB,EAAE,KAAK,CAAA;IACvB,gBAAgB,EAAE,OAAO,CAAA;IACzB,WAAW,EAAE,OAAO,CAAA;IACpB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,eAAe,EAAE,OAAO,CAAA;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,qCAAqC,CAAC,OAAO,EAAE;IAC7D,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC,CAAA;IACvD,UAAU,EAAE,UAAU,CAAA;IACtB,cAAc,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,UAAU,CAAA;KAAE,CAAA;IACvD,eAAe,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,gBAAgB,CAAA;KAAE,KAAK,SAAS,CAAA;IACvE,OAAO,EAAE,YAAY,CAAA;IACrB,cAAc,EAAE,CAAC,OAAO,EAAE;QAAE,gBAAgB,EAAE,KAAK,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACvE,gBAAgB,CAAC,EAAE,KAAK,CAAA;IACxB,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,GAAG,OAAO,CAAC,kCAAkC,CAAC,CAU9C"}
|