@interop/wallet-core 0.108.0 → 0.110.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +5 -5
  2. package/dist/clientAnnex/credentialAnchoredGenesis.d.ts +3 -3
  3. package/dist/clientAnnex/credentialAnchoredGenesis.d.ts.map +1 -1
  4. package/dist/clientAnnex/credentialAnchoredGenesis.js +3 -3
  5. package/dist/clientAnnex/credentialAnchoredGenesis.js.map +1 -1
  6. package/dist/clientAnnex/establish.d.ts +5 -5
  7. package/dist/clientAnnex/establish.d.ts.map +1 -1
  8. package/dist/clientAnnex/establish.js +15 -12
  9. package/dist/clientAnnex/establish.js.map +1 -1
  10. package/dist/clientAnnex/forgetLast.d.ts +2 -2
  11. package/dist/clientAnnex/forgetLast.d.ts.map +1 -1
  12. package/dist/clientAnnex/forgetLast.js +2 -2
  13. package/dist/clientAnnex/forgetLast.js.map +1 -1
  14. package/dist/clientAnnex/gc.d.ts +35 -6
  15. package/dist/clientAnnex/gc.d.ts.map +1 -1
  16. package/dist/clientAnnex/gc.js +50 -13
  17. package/dist/clientAnnex/gc.js.map +1 -1
  18. package/dist/clientAnnex/heal.d.ts +5 -5
  19. package/dist/clientAnnex/heal.d.ts.map +1 -1
  20. package/dist/clientAnnex/heal.js +29 -16
  21. package/dist/clientAnnex/heal.js.map +1 -1
  22. package/dist/clientAnnex/index.d.ts +2 -2
  23. package/dist/clientAnnex/index.d.ts.map +1 -1
  24. package/dist/clientAnnex/index.js +1 -1
  25. package/dist/clientAnnex/index.js.map +1 -1
  26. package/dist/clientAnnex/ladderAnchored.d.ts +5 -5
  27. package/dist/clientAnnex/ladderAnchored.d.ts.map +1 -1
  28. package/dist/clientAnnex/ladderAnchored.js +7 -7
  29. package/dist/clientAnnex/ladderAnchored.js.map +1 -1
  30. package/dist/clientAnnex/log.d.ts +190 -33
  31. package/dist/clientAnnex/log.d.ts.map +1 -1
  32. package/dist/clientAnnex/log.js +269 -56
  33. package/dist/clientAnnex/log.js.map +1 -1
  34. package/dist/clientAnnex/mend.d.ts.map +1 -1
  35. package/dist/clientAnnex/mend.js +22 -16
  36. package/dist/clientAnnex/mend.js.map +1 -1
  37. package/dist/clientAnnex/selfEnroll.d.ts.map +1 -1
  38. package/dist/clientAnnex/selfEnroll.js +5 -6
  39. package/dist/clientAnnex/selfEnroll.js.map +1 -1
  40. package/dist/clientAnnex/spaceCapability.d.ts +6 -6
  41. package/dist/clientAnnex/spaceCapability.d.ts.map +1 -1
  42. package/dist/clientAnnex/spaceCapability.js +9 -12
  43. package/dist/clientAnnex/spaceCapability.js.map +1 -1
  44. package/dist/clients/index.d.ts +1 -1
  45. package/dist/clients/index.d.ts.map +1 -1
  46. package/dist/clients/index.js.map +1 -1
  47. package/dist/clients/listing.d.ts +12 -18
  48. package/dist/clients/listing.d.ts.map +1 -1
  49. package/dist/clients/listing.js +17 -13
  50. package/dist/clients/listing.js.map +1 -1
  51. package/dist/clients/rosterPolicy.d.ts +4 -4
  52. package/dist/clients/rosterPolicy.d.ts.map +1 -1
  53. package/dist/clients/rosterPolicy.js +5 -4
  54. package/dist/clients/rosterPolicy.js.map +1 -1
  55. package/dist/connections/grantProof.d.ts.map +1 -1
  56. package/dist/connections/grantProof.js +4 -5
  57. package/dist/connections/grantProof.js.map +1 -1
  58. package/dist/connections/grants.d.ts +48 -3
  59. package/dist/connections/grants.d.ts.map +1 -1
  60. package/dist/connections/grants.js +54 -3
  61. package/dist/connections/grants.js.map +1 -1
  62. package/dist/connections/index.d.ts +7 -4
  63. package/dist/connections/index.d.ts.map +1 -1
  64. package/dist/connections/index.js +7 -4
  65. package/dist/connections/index.js.map +1 -1
  66. package/dist/connections/replicas.d.ts +43 -1
  67. package/dist/connections/replicas.d.ts.map +1 -1
  68. package/dist/connections/replicas.js +87 -14
  69. package/dist/connections/replicas.js.map +1 -1
  70. package/dist/connections/upsert.d.ts +52 -19
  71. package/dist/connections/upsert.d.ts.map +1 -1
  72. package/dist/connections/upsert.js +93 -41
  73. package/dist/connections/upsert.js.map +1 -1
  74. package/dist/enrollment/enrollment.d.ts.map +1 -1
  75. package/dist/enrollment/enrollment.js +4 -5
  76. package/dist/enrollment/enrollment.js.map +1 -1
  77. package/dist/genesis/accountGenesis.d.ts +4 -4
  78. package/dist/genesis/accountGenesis.d.ts.map +1 -1
  79. package/dist/genesis/accountGenesis.js +4 -4
  80. package/dist/genesis/accountGenesis.js.map +1 -1
  81. package/dist/keyring/fetch.d.ts +3 -3
  82. package/dist/keyring/fetch.d.ts.map +1 -1
  83. package/dist/keyring/fetch.js +3 -3
  84. package/dist/keyring/fetch.js.map +1 -1
  85. package/dist/keyring/record.d.ts.map +1 -1
  86. package/dist/keyring/record.js +1 -2
  87. package/dist/keyring/record.js.map +1 -1
  88. package/dist/keyring/recordEnvelope.d.ts +8 -6
  89. package/dist/keyring/recordEnvelope.d.ts.map +1 -1
  90. package/dist/keyring/recordEnvelope.js +12 -6
  91. package/dist/keyring/recordEnvelope.js.map +1 -1
  92. package/dist/keyring/unlockSpace.d.ts +15 -15
  93. package/dist/keyring/unlockSpace.d.ts.map +1 -1
  94. package/dist/keyring/unlockSpace.js +18 -19
  95. package/dist/keyring/unlockSpace.js.map +1 -1
  96. package/dist/keys/collectionLogStore.d.ts +3 -3
  97. package/dist/keys/collectionLogStore.d.ts.map +1 -1
  98. package/dist/keys/collectionLogStore.js +5 -7
  99. package/dist/keys/collectionLogStore.js.map +1 -1
  100. package/dist/keys/rosterStore.d.ts +3 -3
  101. package/dist/keys/rosterStore.d.ts.map +1 -1
  102. package/dist/keys/rosterStore.js +3 -4
  103. package/dist/keys/rosterStore.js.map +1 -1
  104. package/dist/recovery/recoveryDelegation.d.ts.map +1 -1
  105. package/dist/recovery/recoveryDelegation.js +2 -21
  106. package/dist/recovery/recoveryDelegation.js.map +1 -1
  107. package/dist/space/collections.d.ts +2 -2
  108. package/dist/space/collections.js +2 -2
  109. package/dist/space/deleteSpace.d.ts +3 -3
  110. package/dist/space/deleteSpace.d.ts.map +1 -1
  111. package/dist/space/deleteSpace.js +3 -4
  112. package/dist/space/deleteSpace.js.map +1 -1
  113. package/dist/space/errors.d.ts +1 -1
  114. package/dist/space/errors.js +1 -1
  115. package/dist/space/index.d.ts +4 -4
  116. package/dist/space/index.js +3 -3
  117. package/dist/space/provisioning.d.ts.map +1 -1
  118. package/dist/space/provisioning.js +6 -5
  119. package/dist/space/provisioning.js.map +1 -1
  120. package/dist/space/publicLink.d.ts +4 -4
  121. package/dist/space/publicLink.d.ts.map +1 -1
  122. package/dist/space/publicLink.js +9 -8
  123. package/dist/space/publicLink.js.map +1 -1
  124. package/dist/space/pwsLink.d.ts +58 -0
  125. package/dist/space/pwsLink.d.ts.map +1 -0
  126. package/dist/space/{wasLink.js → pwsLink.js} +42 -37
  127. package/dist/space/pwsLink.js.map +1 -0
  128. package/dist/testing/accountBuilder.d.ts +19 -19
  129. package/dist/testing/accountBuilder.d.ts.map +1 -1
  130. package/dist/testing/accountBuilder.js +44 -40
  131. package/dist/testing/accountBuilder.js.map +1 -1
  132. package/dist/unlock/unlockRecord.d.ts.map +1 -1
  133. package/dist/unlock/unlockRecord.js +7 -7
  134. package/dist/unlock/unlockRecord.js.map +1 -1
  135. package/dist/webvh/clientAnnexDid.d.ts +21 -14
  136. package/dist/webvh/clientAnnexDid.d.ts.map +1 -1
  137. package/dist/webvh/clientAnnexDid.js +19 -14
  138. package/dist/webvh/clientAnnexDid.js.map +1 -1
  139. package/dist/webvh/delegatedLogStore.d.ts +3 -3
  140. package/dist/webvh/delegatedLogStore.d.ts.map +1 -1
  141. package/dist/webvh/delegatedLogStore.js +9 -8
  142. package/dist/webvh/delegatedLogStore.js.map +1 -1
  143. package/dist/webvh/didWebvh.d.ts +6 -25
  144. package/dist/webvh/didWebvh.d.ts.map +1 -1
  145. package/dist/webvh/didWebvh.js +19 -34
  146. package/dist/webvh/didWebvh.js.map +1 -1
  147. package/dist/webvh/index.d.ts +1 -1
  148. package/dist/webvh/index.d.ts.map +1 -1
  149. package/dist/webvh/index.js +1 -1
  150. package/dist/webvh/index.js.map +1 -1
  151. package/dist/webvh/logLocation.d.ts +75 -0
  152. package/dist/webvh/logLocation.d.ts.map +1 -0
  153. package/dist/webvh/logLocation.js +102 -0
  154. package/dist/webvh/logLocation.js.map +1 -0
  155. package/dist/webvh/verifyLog.d.ts +9 -18
  156. package/dist/webvh/verifyLog.d.ts.map +1 -1
  157. package/dist/webvh/verifyLog.js +43 -50
  158. package/dist/webvh/verifyLog.js.map +1 -1
  159. package/package.json +5 -5
  160. package/dist/space/wasLink.d.ts +0 -57
  161. package/dist/space/wasLink.d.ts.map +0 -1
  162. package/dist/space/wasLink.js.map +0 -1
@@ -27,10 +27,13 @@
27
27
  * portability move.
28
28
  * - The genesis document is bare -- no verification methods, no service
29
29
  * entries, the DID core context only. Transient verification methods are
30
- * published per visit by later entries, and the generation delegation's
31
- * service entry is installed by the entry publishing the generation's first
32
- * transient method, never by genesis (its bytes would have to name the SCID
33
- * the genesis hash derives from).
30
+ * published per visit by later entries. The entry that installs the
31
+ * generation delegation (the swap's install stage, or the entry publishing
32
+ * the generation's first transient method) also carries the generation's
33
+ * replica pointers, one entry per replica server the caller restates.
34
+ * Genesis carries neither: the delegation's bytes would have to name the
35
+ * SCID the genesis hash derives from, and the pointers ride the same entry
36
+ * so a generation with no delegation points at no replica.
34
37
  * - No `did:web` projection exists: the generation collection holds only its
35
38
  * `did.jsonl`, capability-gated rather than world-readable.
36
39
  *
@@ -47,13 +50,16 @@
47
50
  * generation collection's name, and the annex DID string embeds it.
48
51
  */
49
52
  import { createDID, deriveNextKeyHash, updateDID } from '@interop/did-method-webvh';
50
- import { rootCapabilityId, spacePath, toUrl } from '@interop/was-client/paths';
53
+ import { createMultihash, MultihashAlgorithm } from '@interop/data-integrity-core/multihash';
54
+ import { rootCapabilityId, spaceUrlById } from '@interop/was-client/paths';
55
+ import { sha256 } from '@noble/hashes/sha2.js';
51
56
  import { base64urlnopad } from '@scure/base';
52
57
  import { isJsonObject } from '../jsonObject.js';
58
+ import { ID_COLLECTION } from '../space/collections.js';
53
59
  import { plaintextCollection } from '../space/plaintextCollection.js';
54
60
  import { clientAnnexRung } from './generationRung.js';
55
61
  import { ladderRung } from '../webvh/ladderDerivation.js';
56
- import { assertCarryOverCommitments, assertPublishedLogDid, currentLogParameters, didWebvhControllerTemplate, MULTIKEY_VM_TYPE, putLogResource, readPublishedLog, readPublishedLogOrThrow, updateKeySigner, withLogConflictRetry, withThreadedHeadOnce } from '../webvh/didWebvh.js';
62
+ import { assertCarryOverCommitments, assertPublishedLogDid, currentLogParameters, didWebvhControllerTemplate, didWebvhLogLocation, didWebvhLogPaths, MULTIKEY_VM_TYPE, putLogResource, readPublishedLog, readPublishedLogOrThrow, updateKeySigner, withLogConflictRetry, withThreadedHeadOnce } from '../webvh/didWebvh.js';
57
63
  import { accountEntryHead, signAccountEntry } from '../webvh/accountEntry.js';
58
64
  import { relationIds } from '../resourceLog/document.js';
59
65
  import { delegationAtExpiry, delegationExpired, delegationSignerGone, STANDING_ZCAP_TTL_MS, standingZcapStale } from '../webvh/standingZcap.js';
@@ -160,7 +166,7 @@ const MULTIKEY_CONTEXT_URL = 'https://w3id.org/security/multikey/v1';
160
166
  * publishing a generation no one can ever extend.
161
167
  *
162
168
  * @param options {object}
163
- * @param options.wasServerUrl {string}
169
+ * @param options.baseServerUrl {string}
164
170
  * @param options.spaceId {string} the auxiliary annex Space's id
165
171
  * @param options.generationId {string} the generation collection's name
166
172
  * @param options.updateKeyPublicKeyMultibase {string} the minting
@@ -171,7 +177,7 @@ const MULTIKEY_CONTEXT_URL = 'https://w3id.org/security/multikey/v1';
171
177
  * @returns {Promise<object>} the one-entry log, its DID and document, and
172
178
  * the resolver's meta for it
173
179
  */
174
- export async function createClientAnnexLog({ wasServerUrl, spaceId, generationId, updateKeyPublicKeyMultibase, nextKeyHashes, signer }) {
180
+ export async function createClientAnnexLog({ baseServerUrl, spaceId, generationId, updateKeyPublicKeyMultibase, nextKeyHashes, signer }) {
175
181
  assertGenerationId(generationId);
176
182
  const carryOverHash = await deriveNextKeyHash(updateKeyPublicKeyMultibase);
177
183
  if (!nextKeyHashes.includes(carryOverHash)) {
@@ -179,15 +185,14 @@ export async function createClientAnnexLog({ wasServerUrl, spaceId, generationId
179
185
  "credential's own rung-0 hash (the carry-over commitment), or no " +
180
186
  'later entry could ever re-state the revealed key.');
181
187
  }
182
- const { host } = new URL(wasServerUrl);
183
188
  const controllerTemplate = didWebvhControllerTemplate({
184
- wasServerUrl,
189
+ baseServerUrl,
185
190
  spaceId,
186
191
  collectionId: generationId
187
192
  });
188
193
  const result = await createDID({
189
- address: host,
190
- paths: ['space', spaceId, generationId],
194
+ address: baseServerUrl,
195
+ paths: didWebvhLogPaths({ spaceId, collectionId: generationId }),
191
196
  signer,
192
197
  updateKeys: [updateKeyPublicKeyMultibase],
193
198
  nextKeyHashes,
@@ -295,7 +300,7 @@ function typedAnnexSpace({ spaceId, read }) {
295
300
  * @param options {object}
296
301
  * @param options.was {WasClient} the storage client, signing as an enrolled
297
302
  * client (or the bootstrap controller on a ladder-anchored signup)
298
- * @param options.wasServerUrl {string}
303
+ * @param options.baseServerUrl {string}
299
304
  * @param options.spaceId {string} the auxiliary annex Space's id
300
305
  * @param options.controller {string} the auxiliary Space's controller, used
301
306
  * only when the Space does not exist yet
@@ -315,7 +320,7 @@ function typedAnnexSpace({ spaceId, read }) {
315
320
  * controller afterwards need not re-read it. Always present here, since
316
321
  * this minter always runs the ensure.
317
322
  */
318
- export async function mintClientAnnexGeneration({ was, wasServerUrl, spaceId, controller, updateKeyPublicKeyMultibase, nextKeyHashes, signer, pinStore }) {
323
+ export async function mintClientAnnexGeneration({ was, baseServerUrl, spaceId, controller, updateKeyPublicKeyMultibase, nextKeyHashes, signer, pinStore }) {
319
324
  const spaceDescription = await ensureClientAnnexSpace({
320
325
  was,
321
326
  spaceId,
@@ -324,7 +329,7 @@ export async function mintClientAnnexGeneration({ was, wasServerUrl, spaceId, co
324
329
  const generationId = mintGenerationId();
325
330
  const published = await publishClientAnnexGenesis({
326
331
  was,
327
- wasServerUrl,
332
+ baseServerUrl,
328
333
  spaceId,
329
334
  generationId,
330
335
  updateKeyPublicKeyMultibase,
@@ -341,7 +346,7 @@ export async function mintClientAnnexGeneration({ was, wasServerUrl, spaceId, co
341
346
  *
342
347
  * @param options {object}
343
348
  * @param options.was {WasClient}
344
- * @param options.wasServerUrl {string}
349
+ * @param options.baseServerUrl {string}
345
350
  * @param options.spaceId {string} the auxiliary annex Space's id
346
351
  * @param options.generationId {string} the freshly minted generation id
347
352
  * @param options.updateKeyPublicKeyMultibase {string}
@@ -361,7 +366,7 @@ export async function mintClientAnnexGeneration({ was, wasServerUrl, spaceId, co
361
366
  * for itself rather than degrade its compare-and-swap to an unconditional
362
367
  * write.
363
368
  */
364
- async function publishClientAnnexGenesis({ was, wasServerUrl, spaceId, generationId, updateKeyPublicKeyMultibase, nextKeyHashes, signer, pinStore, capability }) {
369
+ async function publishClientAnnexGenesis({ was, baseServerUrl, spaceId, generationId, updateKeyPublicKeyMultibase, nextKeyHashes, signer, pinStore, capability }) {
365
370
  // The generation collection must exist before its first resource PUT; a
366
371
  // fresh random generation id means this is always a create, so the truthful
367
372
  // `current: null` skips was-client's pre-merge describe of a Collection
@@ -375,7 +380,7 @@ async function publishClientAnnexGenesis({ was, wasServerUrl, spaceId, generatio
375
380
  capability
376
381
  }).configure({ current: null, name: generationId, force: true });
377
382
  const created = await createClientAnnexLog({
378
- wasServerUrl,
383
+ baseServerUrl,
379
384
  spaceId,
380
385
  generationId,
381
386
  updateKeyPublicKeyMultibase,
@@ -413,7 +418,7 @@ async function publishClientAnnexGenesis({ was, wasServerUrl, spaceId, generatio
413
418
  * @param options {object}
414
419
  * @param options.was {WasClient} the storage client, signing as an enrolled
415
420
  * client (or the bootstrap controller on a ladder-anchored signup)
416
- * @param options.wasServerUrl {string}
421
+ * @param options.baseServerUrl {string}
417
422
  * @param options.spaceId {string} the auxiliary annex Space's id
418
423
  * @param options.controller {string} the auxiliary Space's controller, used
419
424
  * only when the Space does not exist yet
@@ -447,7 +452,7 @@ async function publishClientAnnexGenesis({ was, wasServerUrl, spaceId, generatio
447
452
  * controller afterwards need not re-read it. Present exactly when the
448
453
  * ensure ran, so absent under a supplied `capability`.
449
454
  */
450
- export async function mintCredentialClientAnnexGeneration({ was, wasServerUrl, spaceId, controller, ladderSeed, pinStore, standingRungHashes = [], capability }) {
455
+ export async function mintCredentialClientAnnexGeneration({ was, baseServerUrl, spaceId, controller, ladderSeed, pinStore, standingRungHashes = [], capability }) {
451
456
  const spaceDescription = capability === undefined
452
457
  ? await ensureClientAnnexSpace({ was, spaceId, controller })
453
458
  : undefined;
@@ -456,7 +461,7 @@ export async function mintCredentialClientAnnexGeneration({ was, wasServerUrl, s
456
461
  const ownHash = await deriveNextKeyHash(rung.keyMultibase);
457
462
  const published = await publishClientAnnexGenesis({
458
463
  was,
459
- wasServerUrl,
464
+ baseServerUrl,
460
465
  spaceId,
461
466
  generationId,
462
467
  updateKeyPublicKeyMultibase: rung.keyMultibase,
@@ -601,9 +606,9 @@ export function delegatedClientsPointer({ doc }) {
601
606
  *
602
607
  * The account deletion ceremony is the reader: it must name each auxiliary
603
608
  * Space it is about to destroy, and the log is the only durable record of
604
- * the superseded ones. Each entry carries the annex DID and its host beside
605
- * the Space id, because an account that has migrated hosts leaves entries
606
- * this deployment cannot address: deleting `spaceId` on the CURRENT host
609
+ * the superseded ones. Each entry carries the annex DID and its base server URL beside
610
+ * the Space id, because an account that has migrated servers leaves entries
611
+ * this deployment cannot address: deleting `spaceId` on the CURRENT server
607
612
  * would address a Space that is not the one the entry names, and a 404 there
608
613
  * would otherwise read as a clean deletion. Such an entry is the caller's to
609
614
  * report as a residue.
@@ -614,12 +619,15 @@ export function delegatedClientsPointer({ doc }) {
614
619
  *
615
620
  * An entry carrying no document state, and an endpoint that does not parse
616
621
  * as a client-annex DID, are skipped rather than refused: the walk is an
617
- * enumeration aid, and a caller cannot act on an id it could not read.
622
+ * enumeration aid, and a caller cannot act on an id it could not read. An
623
+ * annex DID whose location cannot be read back off it is refused instead,
624
+ * with the location reader's own error, since a deletion that skipped it
625
+ * would leave that Space standing unreported.
618
626
  *
619
627
  * @param options {object}
620
628
  * @param options.log {DIDLog} the VERIFIED account log
621
- * @returns {Array<{ did: string, host: string, spaceId: string }>} the
622
- * Spaces, in log order
629
+ * @returns {Array<{ did: string, baseServerUrl: string, spaceId: string }>}
630
+ * the Spaces, in log order, each with the base server URL its DID locates
623
631
  */
624
632
  export function delegatedClientsSpaceHistory({ log }) {
625
633
  const spaces = [];
@@ -634,7 +642,8 @@ export function delegatedClientsSpaceHistory({ log }) {
634
642
  if (spaces.some(space => space.spaceId === parts.spaceId)) {
635
643
  continue;
636
644
  }
637
- spaces.push({ did: pointed, host: parts.host, spaceId: parts.spaceId });
645
+ const { baseServerUrl } = didWebvhLogLocation({ did: pointed });
646
+ spaces.push({ did: pointed, baseServerUrl, spaceId: parts.spaceId });
638
647
  }
639
648
  return spaces;
640
649
  }
@@ -740,18 +749,17 @@ export const DELEGATED_CLIENTS_DELEGATION_TTL_MS = STANDING_ZCAP_TTL_MS;
740
749
  * @param options {object}
741
750
  * @param options.zcapClient {ZcapClient} the delegating signer (an
742
751
  * enrolled client's promoted signer, or the account ladder VM)
743
- * @param options.wasServerUrl {string} the auxiliary Space's storage
744
- * server (the account pointer's host)
752
+ * @param options.baseServerUrl {string} the server's base server URL
745
753
  * @param options.clientAnnexSpaceId {string} the auxiliary annex
746
754
  * Space's id
747
755
  * @param options.controller {string} the credential-derived signing DID
748
756
  * @param [options.now] {number} epoch milliseconds, for tests
749
757
  * @returns {Promise<IZcap>}
750
758
  */
751
- export async function mintDelegatedClientsDelegation({ zcapClient, wasServerUrl, clientAnnexSpaceId, controller, now = Date.now() }) {
752
- const spaceUrl = toUrl({
753
- baseUrl: wasServerUrl,
754
- path: spacePath(clientAnnexSpaceId)
759
+ export async function mintDelegatedClientsDelegation({ zcapClient, baseServerUrl, clientAnnexSpaceId, controller, now = Date.now() }) {
760
+ const spaceUrl = spaceUrlById({
761
+ baseServerUrl,
762
+ spaceId: clientAnnexSpaceId
755
763
  });
756
764
  return (await zcapClient.delegate({
757
765
  capability: rootCapabilityId(spaceUrl),
@@ -774,11 +782,10 @@ export async function mintDelegatedClientsDelegation({ zcapClient, wasServerUrl,
774
782
  * @param options.doc {object} the locally verified account document
775
783
  * @param options.zcapClient {ZcapClient} the acting client's promoted
776
784
  * signer, which mints the fresh delegations
777
- * @param options.wasServerUrl {string} the auxiliary Space's storage
778
- * server (the account pointer's host)
785
+ * @param options.baseServerUrl {string} the server's base server URL
779
786
  * @returns {Function} `({ controller }) => Promise<IZcap | undefined>`
780
787
  */
781
- export function delegatedClientsDelegationMinter({ doc, zcapClient, wasServerUrl }) {
788
+ export function delegatedClientsDelegationMinter({ doc, zcapClient, baseServerUrl }) {
782
789
  return async ({ controller }) => {
783
790
  const clientAnnexDid = delegatedClientsPointer({
784
791
  doc: doc
@@ -795,7 +802,7 @@ export function delegatedClientsDelegationMinter({ doc, zcapClient, wasServerUrl
795
802
  }
796
803
  return mintDelegatedClientsDelegation({
797
804
  zcapClient,
798
- wasServerUrl,
805
+ baseServerUrl,
799
806
  clientAnnexSpaceId,
800
807
  controller
801
808
  });
@@ -937,15 +944,15 @@ export const GENERATION_DELEGATION_TTL_MS = STANDING_ZCAP_TTL_MS;
937
944
  * @param options {object}
938
945
  * @param options.zcapClient {ZcapClient} the delegating signer (ladder VM
939
946
  * or an enrolled client's promoted signer)
940
- * @param options.wasServerUrl {string} the ACCOUNT Space's storage server
947
+ * @param options.baseServerUrl {string} the ACCOUNT Space's storage server
941
948
  * @param options.spaceId {string} the ACCOUNT Space's id
942
949
  * @param options.clientAnnexDid {string} the generation's annex DID
943
950
  * @param [options.now] {number} epoch milliseconds, for tests
944
951
  * @returns {Promise<IZcap>}
945
952
  */
946
- export async function mintGenerationDelegation({ zcapClient, wasServerUrl, spaceId, clientAnnexDid, now = Date.now() }) {
953
+ export async function mintGenerationDelegation({ zcapClient, baseServerUrl, spaceId, clientAnnexDid, now = Date.now() }) {
947
954
  clientAnnexDidParts({ did: clientAnnexDid });
948
- const spaceUrl = toUrl({ baseUrl: wasServerUrl, path: spacePath(spaceId) });
955
+ const spaceUrl = spaceUrlById({ baseServerUrl, spaceId });
949
956
  return (await zcapClient.delegate({
950
957
  capability: rootCapabilityId(spaceUrl),
951
958
  // Subtree marker: pending the profile change; flip this target to `${spaceUrl}*` when it ships.
@@ -1017,6 +1024,187 @@ export function embeddedGenerationDelegation({ doc }) {
1017
1024
  endpoint: 'map'
1018
1025
  });
1019
1026
  }
1027
+ /**
1028
+ * The type IRI of the annex document's replica pointer entries: one entry per
1029
+ * replica server the account registers, each naming that server's DID. The
1030
+ * term is the PWS companion spec's, since replication is its domain.
1031
+ * Wire-level and permanent (app-connect-spec decision 0001): the source
1032
+ * server gates a ladder-signed pull on it, and readers dispatch on this IRI
1033
+ * rather than on the entry's fragment id.
1034
+ */
1035
+ export const REPLICA_SERVER_SERVICE_TYPE = 'https://w3id.org/pws#ReplicaServer';
1036
+ /**
1037
+ * The Space id a replica server publishes its own did:webvh log under.
1038
+ */
1039
+ const REPLICA_SERVER_SPACE_ID = 'server';
1040
+ /**
1041
+ * Refuses, with a `TypeError`, a replica server DID that is not a did:webvh
1042
+ * whose path is `space:server:id` (app-connect-spec decision 0003, predicate
1043
+ * 6: the source server checks the grantee's shape beside the pointer). A
1044
+ * malformed value would land permanently in a hash-chained annex entry the
1045
+ * server refuses anyway, so the wallet refuses it before minting the entry.
1046
+ *
1047
+ * @param options {object}
1048
+ * @param options.serverDid {string}
1049
+ */
1050
+ export function assertReplicaServerDid({ serverDid }) {
1051
+ let location;
1052
+ try {
1053
+ location = didWebvhLogLocation({ did: serverDid });
1054
+ }
1055
+ catch {
1056
+ location = undefined;
1057
+ }
1058
+ if (location === undefined ||
1059
+ location.spaceId !== REPLICA_SERVER_SPACE_ID ||
1060
+ location.collectionId !== ID_COLLECTION.id) {
1061
+ throw new TypeError('A replica server DID is a did:webvh whose path is ' +
1062
+ `space:${REPLICA_SERVER_SPACE_ID}:${ID_COLLECTION.id}; got ` +
1063
+ `"${serverDid}".`);
1064
+ }
1065
+ }
1066
+ /**
1067
+ * The fragment id of a replica pointer entry: `replica-` followed by the
1068
+ * SHA-256 multihash of the replica server's DID string (its UTF-8 bytes),
1069
+ * base64url with no padding. Derived rather than random, so one server's
1070
+ * entry has the same id on every restate. Wire-level and permanent; readers
1071
+ * still dispatch on {@link REPLICA_SERVER_SERVICE_TYPE}.
1072
+ *
1073
+ * @param options {object}
1074
+ * @param options.serverDid {string} the replica server's DID
1075
+ * @returns {string} the fragment, without the `#`
1076
+ */
1077
+ export function replicaServerServiceFragment({ serverDid }) {
1078
+ const digest = sha256(new TextEncoder().encode(serverDid));
1079
+ const multihash = createMultihash(digest, MultihashAlgorithm.SHA2_256);
1080
+ return `replica-${base64urlnopad.encode(multihash)}`;
1081
+ }
1082
+ /**
1083
+ * Builds a replica pointer entry for an annex document. The
1084
+ * `serviceEndpoint` is the replica server's bare DID STRING, and the entry
1085
+ * carries nothing beside it. Refuses a malformed `serverDid`
1086
+ * ({@link assertReplicaServerDid}).
1087
+ *
1088
+ * @param options {object}
1089
+ * @param options.clientAnnexDid {string} the generation's annex DID
1090
+ * @param options.serverDid {string} the replica server's DID
1091
+ * @returns {ServiceEndpoint}
1092
+ */
1093
+ export function replicaServerServiceEntry({ clientAnnexDid, serverDid }) {
1094
+ assertReplicaServerDid({ serverDid });
1095
+ return {
1096
+ id: `${clientAnnexDid}#${replicaServerServiceFragment({ serverDid })}`,
1097
+ type: REPLICA_SERVER_SERVICE_TYPE,
1098
+ serviceEndpoint: serverDid
1099
+ };
1100
+ }
1101
+ /**
1102
+ * The replica servers an annex document points at: the `serviceEndpoint` of
1103
+ * every entry whose `type` names (or includes)
1104
+ * {@link REPLICA_SERVER_SERVICE_TYPE}, in document order, each DID once.
1105
+ * Only a bare DID-string endpoint counts, the same predicate the server's
1106
+ * replica-pointer clause evaluates, so wallet and server read the same
1107
+ * pointer set off one document.
1108
+ *
1109
+ * @param options {object}
1110
+ * @param options.doc {DIDDoc} the resolved (and verified) annex document
1111
+ * @returns {string[]}
1112
+ */
1113
+ export function replicaServerPointers({ doc }) {
1114
+ return [
1115
+ ...new Set(serviceEntriesWith({
1116
+ doc,
1117
+ type: REPLICA_SERVER_SERVICE_TYPE,
1118
+ endpoint: 'string'
1119
+ }).map(entry => entry.serviceEndpoint))
1120
+ ];
1121
+ }
1122
+ /**
1123
+ * Whether an annex document carries a replica pointer at `serverDid`: a
1124
+ * replica pointer entry whose endpoint is the server's DID. The match is by
1125
+ * endpoint alone, the one the server's replica-pointer clause makes; an
1126
+ * entry's fragment id is a selector and names no server.
1127
+ *
1128
+ * @param options {object}
1129
+ * @param options.doc {DIDDoc} the resolved (and verified) annex document
1130
+ * @param options.serverDid {string} the replica server's DID
1131
+ * @returns {boolean}
1132
+ */
1133
+ export function replicaServerPointerStands({ doc, serverDid }) {
1134
+ return replicaServerPointers({ doc }).includes(serverDid);
1135
+ }
1136
+ /**
1137
+ * The replica servers of `serverDids`, each once, that an annex document
1138
+ * carries no pointer at ({@link replicaServerPointerStands}). The set the
1139
+ * installing entry adds, and what a caller whose install pass published
1140
+ * nothing reads off the head that pass stood on.
1141
+ *
1142
+ * @param options {object}
1143
+ * @param options.doc {DIDDoc}
1144
+ * @param options.serverDids {string[]}
1145
+ * @returns {string[]}
1146
+ */
1147
+ export function replicaServersMissing({ doc, serverDids }) {
1148
+ const standing = new Set(replicaServerPointers({ doc }));
1149
+ return [...new Set(serverDids)].filter(serverDid => !standing.has(serverDid));
1150
+ }
1151
+ /**
1152
+ * An {@link InstalledServices} hook that points the generation at
1153
+ * `serverDids`: a replica pointer entry for each one the document does not
1154
+ * already carry ({@link replicaServerPointerStands}). Every standing entry,
1155
+ * replica pointers among them, is carried through untouched: the hook only
1156
+ * adds, since dropping a pointer is the replica pass's decision, made against
1157
+ * the connections directory.
1158
+ *
1159
+ * The hook refuses, before any entry is minted, a malformed server DID
1160
+ * ({@link assertReplicaServerDid}) with a `TypeError`, standing ones
1161
+ * included, and with an `Error` a document that already carries a service
1162
+ * entry under the id an added pointer would take (one of another type,
1163
+ * another endpoint, or an endpoint that is not a DID string): a second entry
1164
+ * under one id is a document no resolver admits, and the entry standing
1165
+ * there was not written by this hook.
1166
+ *
1167
+ * @param options {object}
1168
+ * @param options.serverDids {string[]} the replica servers to point at
1169
+ * @returns {InstalledServices}
1170
+ */
1171
+ export function replicaPointerInstaller({ serverDids }) {
1172
+ return ({ doc, clientAnnexDid }) => {
1173
+ for (const serverDid of serverDids) {
1174
+ assertReplicaServerDid({ serverDid });
1175
+ }
1176
+ const added = replicaServersMissing({ doc, serverDids }).map(serverDid => replicaServerServiceEntry({ clientAnnexDid, serverDid }));
1177
+ const taken = new Set((doc.service ?? []).map(entry => entry.id));
1178
+ for (const entry of added) {
1179
+ if (taken.has(entry.id)) {
1180
+ throw new Error(`client annex: the document already carries a service entry at ` +
1181
+ `"${entry.id}" that is not the replica pointer at ` +
1182
+ `"${entry.serviceEndpoint}"; nothing was written.`);
1183
+ }
1184
+ }
1185
+ return added;
1186
+ };
1187
+ }
1188
+ /**
1189
+ * The service list the entry installing a generation delegation publishes:
1190
+ * the delegation entry ({@link withGenerationDelegationEntry}) followed by
1191
+ * whatever the caller's {@link InstalledServices} hook adds. The one
1192
+ * composition both installing writers run, so an install-time entry can
1193
+ * never land in an entry other than the delegation's.
1194
+ *
1195
+ * @param options {object}
1196
+ * @param options.doc {DIDDoc} the annex document as published
1197
+ * @param options.clientAnnexDid {string}
1198
+ * @param options.delegation {IZcap}
1199
+ * @param [options.installedServices] {InstalledServices}
1200
+ * @returns {ServiceEndpoint[]}
1201
+ */
1202
+ function withInstalledEntries({ doc, clientAnnexDid, delegation, installedServices }) {
1203
+ return [
1204
+ ...withGenerationDelegationEntry({ doc, clientAnnexDid, delegation }),
1205
+ ...(installedServices?.({ doc, clientAnnexDid }) ?? [])
1206
+ ];
1207
+ }
1020
1208
  /**
1021
1209
  * Every generation delegation a generation's log has ever embedded, in log
1022
1210
  * order and deduplicated by zcap id -- the annex-log HISTORY WALK the
@@ -1392,9 +1580,13 @@ export async function readClientAnnexLogOrAbsent({ store, expectedDid }) {
1392
1580
  * delegation this entry installs when it publishes the generation's FIRST
1393
1581
  * transient verification method (and the document carries no delegation
1394
1582
  * entry yet). Never invoked otherwise: the delegation is installed with
1395
- * the first transient VM or by the GC ceremony's own install stage, never
1396
- * by genesis (a genesis-embedded signed zcap can never verify -- its
1583
+ * the first transient VM or by the GC ceremony's own install stage, and
1584
+ * not by genesis (a genesis-embedded signed zcap can never verify -- its
1397
1585
  * `controller` embeds the SCID the genesis hash derives from)
1586
+ * @param [options.installedServices] {InstalledServices} the service
1587
+ * entries the installing entry writes beside the delegation (the replica
1588
+ * pointers, {@link replicaPointerInstaller}). Invoked only by the entry
1589
+ * that installs the delegation; ignored on every other entry
1398
1590
  * @param [options.expectedDid] {string} the annex DID the log must
1399
1591
  * resolve to, from the account document's pointer. The read and the publish
1400
1592
  * run under the store's own chain-head pin
@@ -1422,7 +1614,7 @@ export async function enrollClientAnnexTransientClient({ published: threadedHead
1422
1614
  * @param options {object} see {@link enrollClientAnnexTransientClient}
1423
1615
  * @returns {Promise<{ did: string, doc: DIDDoc, log: DIDLog }>}
1424
1616
  */
1425
- async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, transientKeyMultibase, services, mintGenerationDelegation: mintDelegation, expectedDid, published: alreadyRead }) {
1617
+ async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, transientKeyMultibase, services, mintGenerationDelegation: mintDelegation, installedServices, expectedDid, published: alreadyRead }) {
1426
1618
  const published = alreadyRead !== undefined
1427
1619
  ? assertPublishedLogDid({
1428
1620
  published: alreadyRead,
@@ -1456,16 +1648,18 @@ async function enrollClientAnnexTransientClientOnce({ store, ladderSeed, transie
1456
1648
  // The generation delegation installs with the first transient VM (never at
1457
1649
  // genesis): a delegation-less document about to receive its first method
1458
1650
  // gets the entry minted and appended here, so a generation with no visits
1459
- // never carries a delegation and stays authorization-inert.
1651
+ // never carries a delegation and stays authorization-inert. The replica
1652
+ // pointers ride the same entry.
1460
1653
  if (services === undefined &&
1461
1654
  mintDelegation !== undefined &&
1462
1655
  existingMethods.length === 0 &&
1463
1656
  embeddedGenerationDelegation({ doc }) === undefined) {
1464
1657
  const delegation = await mintDelegation({ clientAnnexDid: did });
1465
- services = withGenerationDelegationEntry({
1658
+ services = withInstalledEntries({
1466
1659
  doc,
1467
1660
  clientAnnexDid: did,
1468
- delegation
1661
+ delegation,
1662
+ ...(installedServices !== undefined ? { installedServices } : {})
1469
1663
  });
1470
1664
  }
1471
1665
  const transientMethod = {
@@ -1711,6 +1905,9 @@ export async function setDelegatedClientsPointerOnce({ idStore, signer, clientAn
1711
1905
  * {@link enrollClientAnnexTransientClient}). The closure receives whichever
1712
1906
  * annex DID the round enrolls into, so a GC-race re-enroll mints for
1713
1907
  * the fresh generation
1908
+ * @param [options.installedServices] {InstalledServices} forwarded to
1909
+ * the enrollment entry: what the installing entry writes beside the
1910
+ * delegation
1714
1911
  * @param [options.maxRounds] {number} how many pointer moves to chase
1715
1912
  * before giving up (a GC pass is quarterly, so more than one mid-ceremony
1716
1913
  * move means something else is wrong)
@@ -1722,7 +1919,7 @@ export async function setDelegatedClientsPointerOnce({ idStore, signer, clientAn
1722
1919
  * every later round does
1723
1920
  * @returns {Promise<{ clientAnnexDid: string, doc: DIDDoc, log: DIDLog }>}
1724
1921
  */
1725
- export async function enrollTransientClient({ readAccountDocument, storeForGenerationId, ladderSeed, transientKeyMultibase, mintGenerationDelegation: mintDelegation, maxRounds = 3, published: threadedHead }) {
1922
+ export async function enrollTransientClient({ readAccountDocument, storeForGenerationId, ladderSeed, transientKeyMultibase, mintGenerationDelegation: mintDelegation, installedServices, maxRounds = 3, published: threadedHead }) {
1726
1923
  let accountDoc = await readAccountDocument();
1727
1924
  for (let round = 0; round < maxRounds; round++) {
1728
1925
  const clientAnnexDid = delegatedClientsPointer({ doc: accountDoc });
@@ -1745,7 +1942,8 @@ export async function enrollTransientClient({ readAccountDocument, storeForGener
1745
1942
  ...(head !== undefined ? { published: head } : {}),
1746
1943
  ...(mintDelegation !== undefined
1747
1944
  ? { mintGenerationDelegation: mintDelegation }
1748
- : {})
1945
+ : {}),
1946
+ ...(installedServices !== undefined ? { installedServices } : {})
1749
1947
  });
1750
1948
  // The GC-race re-read: an unchanged pointer means the enrollment stands
1751
1949
  // in the pointed generation; a moved one means a concurrent GC abandoned
@@ -1781,7 +1979,7 @@ export async function enrollTransientClient({ readAccountDocument, storeForGener
1781
1979
  * @param options {object}
1782
1980
  * @param options.was {WasClient} the storage client the mint and the
1783
1981
  * install write through
1784
- * @param options.wasServerUrl {string}
1982
+ * @param options.baseServerUrl {string}
1785
1983
  * @param options.spaceId {string} the auxiliary annex Space's id
1786
1984
  * @param options.controller {string} the Space's creation controller (see
1787
1985
  * {@link mintCredentialClientAnnexGeneration})
@@ -1799,6 +1997,10 @@ export async function enrollTransientClient({ readAccountDocument, storeForGener
1799
1997
  * generation's standing set, restated in the fresh genesis (see
1800
1998
  * {@link mintCredentialClientAnnexGeneration}); absent on a first
1801
1999
  * generation and wherever no superseded log was readable
2000
+ * @param [options.installedServices] {InstalledServices} what the
2001
+ * install entry writes beside the delegation, the restated replica pointers
2002
+ * ({@link replicaPointerInstaller}); absent on a first generation and
2003
+ * wherever the caller holds no list
1802
2004
  * @param [options.beforePointerEntry] {Function}
1803
2005
  * `({ minted }) => Promise<void>` -- runs after the install and before the
1804
2006
  * pointer entry, with the mint's published head (its `spaceDescription`
@@ -1807,10 +2009,10 @@ export async function enrollTransientClient({ readAccountDocument, storeForGener
1807
2009
  * @returns {Promise<{ clientAnnexDid: string, generationDelegation: IZcap,
1808
2010
  * pointed: T }>}
1809
2011
  */
1810
- export async function mintPointedClientAnnexGeneration({ was, wasServerUrl, spaceId, controller, ladderSeed, mintGenerationDelegation, point, pinStore, capability, standingRungHashes, beforePointerEntry, now }) {
2012
+ export async function mintPointedClientAnnexGeneration({ was, baseServerUrl, spaceId, controller, ladderSeed, mintGenerationDelegation, point, pinStore, capability, standingRungHashes, installedServices, beforePointerEntry, now }) {
1811
2013
  const minted = await mintCredentialClientAnnexGeneration({
1812
2014
  was,
1813
- wasServerUrl,
2015
+ baseServerUrl,
1814
2016
  spaceId,
1815
2017
  controller,
1816
2018
  ladderSeed,
@@ -1828,6 +2030,7 @@ export async function mintPointedClientAnnexGeneration({ was, wasServerUrl, spac
1828
2030
  }),
1829
2031
  ladderSeed,
1830
2032
  mintGenerationDelegation,
2033
+ ...(installedServices !== undefined ? { installedServices } : {}),
1831
2034
  expectedDid: minted.did,
1832
2035
  ...(minted.etag !== undefined ? { published: minted } : {}),
1833
2036
  ...(now !== undefined ? { now } : {})
@@ -1899,6 +2102,14 @@ export async function mintPointedClientAnnexGeneration({ was, wasServerUrl, spac
1899
2102
  * its own ladder VM (whose delegations it revokes server-side in the next
1900
2103
  * breath, a state no client-side predicate can read) and the client its
1901
2104
  * removal entry has yet to strike
2105
+ * @param [options.installedServices] {InstalledServices} the service
2106
+ * entries the installing entry writes beside the delegation, the replica
2107
+ * pointers ({@link replicaPointerInstaller}): a fresh generation's install
2108
+ * stage writes them beside the delegation, and a renewal on a live
2109
+ * generation adds what the document lacks and keeps every standing entry
2110
+ * through the prior-state clone. A pass that publishes nothing never
2111
+ * invokes the hook; the caller reads what the head still lacks off the
2112
+ * `published` it hands back ({@link replicaServersMissing})
1902
2113
  * @param [options.now] {number} epoch milliseconds, for tests
1903
2114
  * @param [options.published] {PublishedWebvhLog} a head the caller already
1904
2115
  * read and verified under this same pin slot, so the stage builds on it
@@ -1933,7 +2144,7 @@ export async function ensureGenerationDelegationCurrent({ published: threadedHea
1933
2144
  * @returns {Promise<{ delegation: IZcap, renewed: boolean,
1934
2145
  * published?: PublishedWebvhLog }>}
1935
2146
  */
1936
- async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, mintGenerationDelegation: mintDelegation, expectedDid, accountDoc, retiringKeyMultibases = [], now, published: alreadyRead }) {
2147
+ async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, mintGenerationDelegation: mintDelegation, expectedDid, accountDoc, retiringKeyMultibases = [], installedServices, now, published: alreadyRead }) {
1937
2148
  const published = alreadyRead !== undefined
1938
2149
  ? assertPublishedLogDid({
1939
2150
  published: alreadyRead,
@@ -1974,13 +2185,15 @@ async function ensureGenerationDelegationCurrentOnce({ store, ladderSeed, mintGe
1974
2185
  // The writer's rung-0 key reveals at its first annex write, exactly
1975
2186
  // as the enrollment entry does; `nextKeyHashes` is re-stated verbatim,
1976
2187
  // never inherited. Verification methods, relationship arrays, and every
1977
- // other service entry ride the library's prior-state clone untouched.
2188
+ // other service entry (the standing replica pointers among them) ride
2189
+ // the library's prior-state clone untouched.
1978
2190
  updateKeys: [...new Set([...published.updateKeys, rung.keyMultibase])],
1979
2191
  nextKeyHashes: [...published.nextKeyHashes],
1980
- services: withGenerationDelegationEntry({
2192
+ services: withInstalledEntries({
1981
2193
  doc,
1982
2194
  clientAnnexDid: did,
1983
- delegation: fresh
2195
+ delegation: fresh,
2196
+ ...(installedServices !== undefined ? { installedServices } : {})
1984
2197
  })
1985
2198
  });
1986
2199
  // The publish advances the store's chain-head pin to what it just wrote, so