@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.
Files changed (312) hide show
  1. package/README.md +41 -16
  2. package/dist/clientAnnex/credentialAnchoredGenesis.d.ts +46 -26
  3. package/dist/clientAnnex/credentialAnchoredGenesis.d.ts.map +1 -1
  4. package/dist/clientAnnex/credentialAnchoredGenesis.js +91 -39
  5. package/dist/clientAnnex/credentialAnchoredGenesis.js.map +1 -1
  6. package/dist/clientAnnex/establish.d.ts +55 -34
  7. package/dist/clientAnnex/establish.d.ts.map +1 -1
  8. package/dist/clientAnnex/establish.js +102 -56
  9. package/dist/clientAnnex/establish.js.map +1 -1
  10. package/dist/clientAnnex/forget.d.ts +16 -2
  11. package/dist/clientAnnex/forget.d.ts.map +1 -1
  12. package/dist/clientAnnex/forget.js +11 -9
  13. package/dist/clientAnnex/forget.js.map +1 -1
  14. package/dist/clientAnnex/forgetLast.d.ts +62 -18
  15. package/dist/clientAnnex/forgetLast.d.ts.map +1 -1
  16. package/dist/clientAnnex/forgetLast.js +92 -26
  17. package/dist/clientAnnex/forgetLast.js.map +1 -1
  18. package/dist/clientAnnex/gc.d.ts +3 -2
  19. package/dist/clientAnnex/gc.d.ts.map +1 -1
  20. package/dist/clientAnnex/gc.js +7 -7
  21. package/dist/clientAnnex/gc.js.map +1 -1
  22. package/dist/clientAnnex/heal.d.ts +38 -5
  23. package/dist/clientAnnex/heal.d.ts.map +1 -1
  24. package/dist/clientAnnex/heal.js +346 -236
  25. package/dist/clientAnnex/heal.js.map +1 -1
  26. package/dist/clientAnnex/index.d.ts +21 -5
  27. package/dist/clientAnnex/index.d.ts.map +1 -1
  28. package/dist/clientAnnex/index.js +24 -5
  29. package/dist/clientAnnex/index.js.map +1 -1
  30. package/dist/clientAnnex/ladder.d.ts +58 -0
  31. package/dist/clientAnnex/ladder.d.ts.map +1 -1
  32. package/dist/clientAnnex/ladder.js +222 -33
  33. package/dist/clientAnnex/ladder.js.map +1 -1
  34. package/dist/clientAnnex/ladderAnchored.d.ts +148 -22
  35. package/dist/clientAnnex/ladderAnchored.d.ts.map +1 -1
  36. package/dist/clientAnnex/ladderAnchored.js +391 -346
  37. package/dist/clientAnnex/ladderAnchored.js.map +1 -1
  38. package/dist/clientAnnex/log.d.ts +162 -43
  39. package/dist/clientAnnex/log.d.ts.map +1 -1
  40. package/dist/clientAnnex/log.js +423 -192
  41. package/dist/clientAnnex/log.js.map +1 -1
  42. package/dist/clientAnnex/mend.d.ts +12 -6
  43. package/dist/clientAnnex/mend.d.ts.map +1 -1
  44. package/dist/clientAnnex/mend.js +26 -12
  45. package/dist/clientAnnex/mend.js.map +1 -1
  46. package/dist/clientAnnex/recoveryLadderAnchored.d.ts +6 -4
  47. package/dist/clientAnnex/recoveryLadderAnchored.d.ts.map +1 -1
  48. package/dist/clientAnnex/recoveryLadderAnchored.js +56 -35
  49. package/dist/clientAnnex/recoveryLadderAnchored.js.map +1 -1
  50. package/dist/clientAnnex/spaceCapability.d.ts +123 -0
  51. package/dist/clientAnnex/spaceCapability.d.ts.map +1 -0
  52. package/dist/clientAnnex/spaceCapability.js +152 -0
  53. package/dist/clientAnnex/spaceCapability.js.map +1 -0
  54. package/dist/clientAnnex/stages.d.ts +63 -0
  55. package/dist/clientAnnex/stages.d.ts.map +1 -0
  56. package/dist/clientAnnex/stages.js +64 -0
  57. package/dist/clientAnnex/stages.js.map +1 -0
  58. package/dist/clientAnnex/zcap.d.ts +1 -1
  59. package/dist/clientAnnex/zcap.d.ts.map +1 -1
  60. package/dist/clientAnnex/zcap.js +42 -35
  61. package/dist/clientAnnex/zcap.js.map +1 -1
  62. package/dist/clients/policy.d.ts +15 -1
  63. package/dist/clients/policy.d.ts.map +1 -1
  64. package/dist/clients/policy.js +12 -6
  65. package/dist/clients/policy.js.map +1 -1
  66. package/dist/clients/revocation.d.ts +18 -10
  67. package/dist/clients/revocation.d.ts.map +1 -1
  68. package/dist/clients/revocation.js +13 -5
  69. package/dist/clients/revocation.js.map +1 -1
  70. package/dist/clients/rosterPolicy.d.ts +10 -2
  71. package/dist/clients/rosterPolicy.d.ts.map +1 -1
  72. package/dist/clients/rosterPolicy.js +41 -24
  73. package/dist/clients/rosterPolicy.js.map +1 -1
  74. package/dist/descriptors/acquire.d.ts.map +1 -1
  75. package/dist/descriptors/acquire.js +6 -23
  76. package/dist/descriptors/acquire.js.map +1 -1
  77. package/dist/descriptors/cipher.d.ts.map +1 -1
  78. package/dist/descriptors/cipher.js +5 -0
  79. package/dist/descriptors/cipher.js.map +1 -1
  80. package/dist/descriptors/errors.d.ts +43 -0
  81. package/dist/descriptors/errors.d.ts.map +1 -0
  82. package/dist/descriptors/errors.js +45 -0
  83. package/dist/descriptors/errors.js.map +1 -0
  84. package/dist/descriptors/index.d.ts +5 -0
  85. package/dist/descriptors/index.d.ts.map +1 -1
  86. package/dist/descriptors/index.js +5 -0
  87. package/dist/descriptors/index.js.map +1 -1
  88. package/dist/enrollment/enrollment.d.ts +37 -10
  89. package/dist/enrollment/enrollment.d.ts.map +1 -1
  90. package/dist/enrollment/enrollment.js +52 -15
  91. package/dist/enrollment/enrollment.js.map +1 -1
  92. package/dist/genesis/accountGenesis.d.ts +42 -11
  93. package/dist/genesis/accountGenesis.d.ts.map +1 -1
  94. package/dist/genesis/accountGenesis.js +80 -22
  95. package/dist/genesis/accountGenesis.js.map +1 -1
  96. package/dist/genesis/index.d.ts +3 -1
  97. package/dist/genesis/index.d.ts.map +1 -1
  98. package/dist/genesis/index.js +3 -1
  99. package/dist/genesis/index.js.map +1 -1
  100. package/dist/identity/agents.d.ts +17 -1
  101. package/dist/identity/agents.d.ts.map +1 -1
  102. package/dist/identity/agents.js +23 -7
  103. package/dist/identity/agents.js.map +1 -1
  104. package/dist/identity/index.d.ts +3 -1
  105. package/dist/identity/index.d.ts.map +1 -1
  106. package/dist/identity/index.js +3 -1
  107. package/dist/identity/index.js.map +1 -1
  108. package/dist/index.d.ts +6 -3
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +6 -3
  111. package/dist/index.js.map +1 -1
  112. package/dist/keyring/index.d.ts +2 -1
  113. package/dist/keyring/index.d.ts.map +1 -1
  114. package/dist/keyring/index.js +2 -1
  115. package/dist/keyring/index.js.map +1 -1
  116. package/dist/keyring/record.d.ts.map +1 -1
  117. package/dist/keyring/record.js +3 -6
  118. package/dist/keyring/record.js.map +1 -1
  119. package/dist/keyring/unlockSpace.d.ts +14 -5
  120. package/dist/keyring/unlockSpace.d.ts.map +1 -1
  121. package/dist/keyring/unlockSpace.js +31 -36
  122. package/dist/keyring/unlockSpace.js.map +1 -1
  123. package/dist/keys/index.d.ts +10 -6
  124. package/dist/keys/index.d.ts.map +1 -1
  125. package/dist/keys/index.js +9 -5
  126. package/dist/keys/index.js.map +1 -1
  127. package/dist/keys/rosterLogStore.d.ts +12 -11
  128. package/dist/keys/rosterLogStore.d.ts.map +1 -1
  129. package/dist/keys/rosterLogStore.js +15 -13
  130. package/dist/keys/rosterLogStore.js.map +1 -1
  131. package/dist/keys/rosterStore.d.ts +3 -6
  132. package/dist/keys/rosterStore.d.ts.map +1 -1
  133. package/dist/keys/rosterStore.js +10 -9
  134. package/dist/keys/rosterStore.js.map +1 -1
  135. package/dist/keys/spaceEpochs.d.ts.map +1 -1
  136. package/dist/keys/spaceEpochs.js +2 -3
  137. package/dist/keys/spaceEpochs.js.map +1 -1
  138. package/dist/keys/userKeyRoster.d.ts +57 -7
  139. package/dist/keys/userKeyRoster.d.ts.map +1 -1
  140. package/dist/keys/userKeyRoster.js +222 -17
  141. package/dist/keys/userKeyRoster.js.map +1 -1
  142. package/dist/keys/userKeyRosterCascade.d.ts +7 -6
  143. package/dist/keys/userKeyRosterCascade.d.ts.map +1 -1
  144. package/dist/keys/userKeyRosterCascade.js +7 -6
  145. package/dist/keys/userKeyRosterCascade.js.map +1 -1
  146. package/dist/keys/wasLabelsStore.d.ts +9 -2
  147. package/dist/keys/wasLabelsStore.d.ts.map +1 -1
  148. package/dist/keys/wasLabelsStore.js +14 -6
  149. package/dist/keys/wasLabelsStore.js.map +1 -1
  150. package/dist/log.d.ts +7 -2
  151. package/dist/log.d.ts.map +1 -1
  152. package/dist/log.js +6 -1
  153. package/dist/log.js.map +1 -1
  154. package/dist/recovery/index.d.ts +12 -8
  155. package/dist/recovery/index.d.ts.map +1 -1
  156. package/dist/recovery/index.js +11 -7
  157. package/dist/recovery/index.js.map +1 -1
  158. package/dist/recovery/recoveryCode.d.ts +27 -7
  159. package/dist/recovery/recoveryCode.d.ts.map +1 -1
  160. package/dist/recovery/recoveryCode.js +18 -6
  161. package/dist/recovery/recoveryCode.js.map +1 -1
  162. package/dist/recovery/recoveryDelegation.d.ts.map +1 -1
  163. package/dist/recovery/recoveryDelegation.js +21 -34
  164. package/dist/recovery/recoveryDelegation.js.map +1 -1
  165. package/dist/recovery/recoveryWebvh.d.ts +75 -63
  166. package/dist/recovery/recoveryWebvh.d.ts.map +1 -1
  167. package/dist/recovery/recoveryWebvh.js +127 -116
  168. package/dist/recovery/recoveryWebvh.js.map +1 -1
  169. package/dist/request/classify.d.ts +32 -8
  170. package/dist/request/classify.d.ts.map +1 -1
  171. package/dist/request/classify.js +39 -14
  172. package/dist/request/classify.js.map +1 -1
  173. package/dist/request/ephemeralExchange.d.ts +1 -6
  174. package/dist/request/ephemeralExchange.d.ts.map +1 -1
  175. package/dist/request/ephemeralExchange.js.map +1 -1
  176. package/dist/request/onboarding.d.ts.map +1 -1
  177. package/dist/request/onboarding.js +2 -2
  178. package/dist/request/onboarding.js.map +1 -1
  179. package/dist/request/parse.d.ts.map +1 -1
  180. package/dist/request/parse.js +2 -3
  181. package/dist/request/parse.js.map +1 -1
  182. package/dist/resourceLog/controller.d.ts +34 -12
  183. package/dist/resourceLog/controller.d.ts.map +1 -1
  184. package/dist/resourceLog/controller.js +79 -86
  185. package/dist/resourceLog/controller.js.map +1 -1
  186. package/dist/resourceLog/document.d.ts +182 -0
  187. package/dist/resourceLog/document.d.ts.map +1 -0
  188. package/dist/resourceLog/document.js +159 -0
  189. package/dist/resourceLog/document.js.map +1 -0
  190. package/dist/resourceLog/errors.d.ts +47 -8
  191. package/dist/resourceLog/errors.d.ts.map +1 -1
  192. package/dist/resourceLog/errors.js +54 -8
  193. package/dist/resourceLog/errors.js.map +1 -1
  194. package/dist/resourceLog/index.d.ts +7 -3
  195. package/dist/resourceLog/index.d.ts.map +1 -1
  196. package/dist/resourceLog/index.js +7 -3
  197. package/dist/resourceLog/index.js.map +1 -1
  198. package/dist/resourceLog/ladderRungs.d.ts +35 -0
  199. package/dist/resourceLog/ladderRungs.d.ts.map +1 -0
  200. package/dist/resourceLog/ladderRungs.js +352 -0
  201. package/dist/resourceLog/ladderRungs.js.map +1 -0
  202. package/dist/resourceLog/license.d.ts +42 -17
  203. package/dist/resourceLog/license.d.ts.map +1 -1
  204. package/dist/resourceLog/license.js +38 -24
  205. package/dist/resourceLog/license.js.map +1 -1
  206. package/dist/space/activity.d.ts +15 -15
  207. package/dist/space/activity.d.ts.map +1 -1
  208. package/dist/space/activity.js +15 -15
  209. package/dist/space/activity.js.map +1 -1
  210. package/dist/space/collections.d.ts +11 -0
  211. package/dist/space/collections.d.ts.map +1 -1
  212. package/dist/space/collections.js +13 -0
  213. package/dist/space/collections.js.map +1 -1
  214. package/dist/space/deleteSpace.d.ts +28 -0
  215. package/dist/space/deleteSpace.d.ts.map +1 -0
  216. package/dist/space/deleteSpace.js +44 -0
  217. package/dist/space/deleteSpace.js.map +1 -0
  218. package/dist/space/errors.d.ts.map +1 -1
  219. package/dist/space/errors.js +0 -1
  220. package/dist/space/errors.js.map +1 -1
  221. package/dist/space/index.d.ts +6 -0
  222. package/dist/space/index.d.ts.map +1 -1
  223. package/dist/space/index.js +6 -0
  224. package/dist/space/index.js.map +1 -1
  225. package/dist/space/plaintextCollection.d.ts +43 -0
  226. package/dist/space/plaintextCollection.d.ts.map +1 -0
  227. package/dist/space/plaintextCollection.js +17 -0
  228. package/dist/space/plaintextCollection.js.map +1 -0
  229. package/dist/stages.d.ts +23 -0
  230. package/dist/stages.d.ts.map +1 -0
  231. package/dist/stages.js +23 -0
  232. package/dist/stages.js.map +1 -0
  233. package/dist/sync/index.d.ts +7 -0
  234. package/dist/sync/index.d.ts.map +1 -1
  235. package/dist/sync/index.js +7 -0
  236. package/dist/sync/index.js.map +1 -1
  237. package/dist/sync/push.js +4 -4
  238. package/dist/sync/push.js.map +1 -1
  239. package/dist/sync/remint.js +2 -2
  240. package/dist/sync/remint.js.map +1 -1
  241. package/dist/sync/types.d.ts +41 -1
  242. package/dist/sync/types.d.ts.map +1 -1
  243. package/dist/sync/types.js +47 -1
  244. package/dist/sync/types.js.map +1 -1
  245. package/dist/unlock/index.d.ts +5 -1
  246. package/dist/unlock/index.d.ts.map +1 -1
  247. package/dist/unlock/index.js +5 -1
  248. package/dist/unlock/index.js.map +1 -1
  249. package/dist/unlock/retire.d.ts +35 -7
  250. package/dist/unlock/retire.d.ts.map +1 -1
  251. package/dist/unlock/retire.js +52 -14
  252. package/dist/unlock/retire.js.map +1 -1
  253. package/dist/unlock/standingClient.d.ts.map +1 -1
  254. package/dist/unlock/standingClient.js +5 -1
  255. package/dist/unlock/standingClient.js.map +1 -1
  256. package/dist/unlock/standingWebvh.d.ts +217 -35
  257. package/dist/unlock/standingWebvh.d.ts.map +1 -1
  258. package/dist/unlock/standingWebvh.js +469 -210
  259. package/dist/unlock/standingWebvh.js.map +1 -1
  260. package/dist/webvh/accountEntry.d.ts +146 -0
  261. package/dist/webvh/accountEntry.d.ts.map +1 -0
  262. package/dist/webvh/accountEntry.js +239 -0
  263. package/dist/webvh/accountEntry.js.map +1 -0
  264. package/dist/webvh/didWeb.d.ts +12 -7
  265. package/dist/webvh/didWeb.d.ts.map +1 -1
  266. package/dist/webvh/didWeb.js +2 -2
  267. package/dist/webvh/didWeb.js.map +1 -1
  268. package/dist/webvh/didWebProjection.d.ts +164 -0
  269. package/dist/webvh/didWebProjection.d.ts.map +1 -0
  270. package/dist/webvh/didWebProjection.js +230 -0
  271. package/dist/webvh/didWebProjection.js.map +1 -0
  272. package/dist/webvh/didWebvh.d.ts +275 -136
  273. package/dist/webvh/didWebvh.d.ts.map +1 -1
  274. package/dist/webvh/didWebvh.js +307 -340
  275. package/dist/webvh/didWebvh.js.map +1 -1
  276. package/dist/webvh/enrollClient.d.ts +65 -0
  277. package/dist/webvh/enrollClient.d.ts.map +1 -0
  278. package/dist/webvh/enrollClient.js +172 -0
  279. package/dist/webvh/enrollClient.js.map +1 -0
  280. package/dist/webvh/index.d.ts +37 -12
  281. package/dist/webvh/index.d.ts.map +1 -1
  282. package/dist/webvh/index.js +33 -10
  283. package/dist/webvh/index.js.map +1 -1
  284. package/dist/webvh/listClients.d.ts +1 -35
  285. package/dist/webvh/listClients.d.ts.map +1 -1
  286. package/dist/webvh/listClients.js +2 -30
  287. package/dist/webvh/listClients.js.map +1 -1
  288. package/dist/webvh/revokeClient.d.ts +41 -11
  289. package/dist/webvh/revokeClient.d.ts.map +1 -1
  290. package/dist/webvh/revokeClient.js +99 -48
  291. package/dist/webvh/revokeClient.js.map +1 -1
  292. package/dist/webvh/standingZcap.d.ts +61 -14
  293. package/dist/webvh/standingZcap.d.ts.map +1 -1
  294. package/dist/webvh/standingZcap.js +91 -0
  295. package/dist/webvh/standingZcap.js.map +1 -1
  296. package/dist/webvh/verifyLog.d.ts +59 -5
  297. package/dist/webvh/verifyLog.d.ts.map +1 -1
  298. package/dist/webvh/verifyLog.js +76 -11
  299. package/dist/webvh/verifyLog.js.map +1 -1
  300. package/dist/webvh/wasIdStore.d.ts +8 -8
  301. package/dist/webvh/wasIdStore.d.ts.map +1 -1
  302. package/dist/webvh/wasIdStore.js +39 -14
  303. package/dist/webvh/wasIdStore.js.map +1 -1
  304. package/dist/webvh/zcap.d.ts +14 -1
  305. package/dist/webvh/zcap.d.ts.map +1 -1
  306. package/dist/webvh/zcap.js +3 -27
  307. package/dist/webvh/zcap.js.map +1 -1
  308. package/package.json +2 -2
  309. package/dist/webvh/keyAgreement.d.ts +0 -97
  310. package/dist/webvh/keyAgreement.d.ts.map +0 -1
  311. package/dist/webvh/keyAgreement.js +0 -71
  312. package/dist/webvh/keyAgreement.js.map +0 -1
@@ -21,10 +21,10 @@
21
21
  * key-agreement key (under `keyAgreement`, the source of record for
22
22
  * user-key-wrap recipient keys). The genesis comes in two flavors: a wallet
23
23
  * that keeps a KMS supplies its `didWebKeys` map and the document gains one
24
- * server-held key, the KMS `authentication` key (a convenience for DIDAuth);
25
- * a wallet with no KMS supplies no map and the document holds client keys
26
- * only (a later log entry can still add the KMS authentication key when the
27
- * first KMS-capable client appears). In either flavor every other relation
24
+ * server-held key, the KMS `authentication` key (a DIDAuth signing key the
25
+ * document publishes); a wallet with no KMS supplies no map and the document
26
+ * holds client keys only, permanently -- no later entry adds the KMS key to a
27
+ * document that published without it. In either flavor every other relation
28
28
  * lists client keys only. In particular no server-held key may appear under
29
29
  * `keyAgreement` (no server key is a wrap target) or under `assertionMethod`
30
30
  * (membership there is what entitles a key to issue assertions as the account
@@ -35,8 +35,7 @@
35
35
  * glue: a `Signer` bridge over a client-held update-key seed, the idempotent,
36
36
  * crash-resumable provisioning flow (`ensureDidWebvh`), the per-client
37
37
  * update-key rotation ceremony (`rotateWebvhUpdateKey`), the two-entry client
38
- * enrollment ceremony (`enrollWebvhClient`), and the lost-`keys.json` recovery
39
- * path for the did:web relationship bindings (`repairKeyBindings`). Update-key
38
+ * enrollment ceremony (`enrollWebvhClient`). Update-key
40
39
  * seeds are minted here but persisted by the caller -- with client-held keys a
41
40
  * lost seed is lost update authority, so every publish is preceded by a
42
41
  * caller-persisted write.
@@ -71,10 +70,11 @@ import { equalBytes } from '@noble/ciphers/utils.js';
71
70
  import { sha256 } from '@noble/hashes/sha2.js';
72
71
  import { base58, base64urlnopad } from '@scure/base';
73
72
  import { VOCAB_CONTEXT_URL } from 'byoe-context';
74
- import { DID_DOCUMENT_RESOURCE, DID_LOG_RESOURCE, ID_COLLECTION } from '../space/collections.js';
73
+ import { DID_LOG_RESOURCE, ID_COLLECTION } from '../space/collections.js';
75
74
  import { ResourceLogContinuityError } from '@interop/vh-resource-log';
75
+ import { putDidWebProjection } from './didWebProjection.js';
76
76
  import { multibaseOf } from './didWeb.js';
77
- import { accountLogPinId, checkAccountLogContinuity } from './verifyLog.js';
77
+ import { accountLogPinId, checkAccountLogContinuity, checkAndAdvanceAccountLogPin } from './verifyLog.js';
78
78
  /**
79
79
  * The Multikey verification-method type the did:webvh data model uses for both
80
80
  * the Ed25519 (authentication/assertionMethod/capability*) and X25519
@@ -339,9 +339,9 @@ export function assertCanonicalClientKeys({ signingKeyMultibase, keyAgreementKey
339
339
  * The method is listed under `assertionMethod` and `capabilityDelegation`
340
340
  * ONLY -- no `authentication`, no `capabilityInvocation`, no `keyAgreement`
341
341
  * twin, and no marker property. Recognition is by that relation asymmetry
342
- * (`ladderVmIds` in the listings module): a `capabilityDelegation` member
343
- * absent from `capabilityInvocation` is the ladder VM, which also keeps it
344
- * structurally out of every client listing.
342
+ * (`ladderVmIds`, in the shared account-document readers): a
343
+ * `capabilityDelegation` member absent from `capabilityInvocation` is the
344
+ * ladder VM, which also keeps it structurally out of every client listing.
345
345
  *
346
346
  * Because the key is derived, a reinstall republishes the SAME key under the
347
347
  * SAME id, and a still-unexpired delegation it signed resumes verifying the
@@ -600,7 +600,7 @@ function assembleWebvhVerificationMethods({ controllerTemplate, didWebKeys, clie
600
600
  * @param options.updateKeyPublicKeyMultibase {string}
601
601
  * @param options.nextKeyHashes {string[]}
602
602
  * @param options.signer {Signer}
603
- * @returns {Promise<{ log: DIDLog; webDoc: object; did: string }>}
603
+ * @returns {Promise<CreatedWebvhLog>}
604
604
  */
605
605
  async function createWebvhLog({ wasServerUrl, spaceId, didWebKeys, clientKeys, ladderVm, updateKeyPublicKeyMultibase, nextKeyHashes, signer }) {
606
606
  const { host } = new URL(wasServerUrl);
@@ -633,7 +633,19 @@ async function createWebvhLog({ wasServerUrl, spaceId, didWebKeys, clientKeys, l
633
633
  if (!result.webDoc) {
634
634
  throw new Error('createDID did not return a webDoc despite alsoKnownAsWeb.');
635
635
  }
636
- return { log: result.log, webDoc: result.webDoc, did: result.did };
636
+ return {
637
+ log: result.log,
638
+ webDoc: result.webDoc,
639
+ did: result.did,
640
+ // Detached: `createDID` hands back the very object the genesis entry
641
+ // carries as its `state`, and a read-side head's document is a copy of
642
+ // the entry's state. Cloning here makes both producers of a
643
+ // {@link PublishedWebvhLog} alias the same way, so a caller that mutates
644
+ // a document it was handed can never edit a log entry through it.
645
+ doc: structuredClone(result.doc),
646
+ updateKeys: result.meta.updateKeys ?? [],
647
+ nextKeyHashes: result.meta.nextKeyHashes ?? []
648
+ };
637
649
  }
638
650
  /**
639
651
  * Builds a genesis entry's `nextKeyHashes`: the active update key's own
@@ -690,7 +702,7 @@ export async function genesisNextKeyHashes({ activeKeyMultibase, stagedKeyMultib
690
702
  * @param options.updateKeyPublicKeyMultibase {string} ladder rung 0's key
691
703
  * @param options.nextKeyHashes {string[]} [hash(rung 0), hash(rung 1)]
692
704
  * @param options.signer {Signer} ladder rung 0's signer
693
- * @returns {Promise<{ log: DIDLog; webDoc: object; did: string }>}
705
+ * @returns {Promise<CreatedWebvhLog>}
694
706
  */
695
707
  export async function createLadderAnchoredWebvhLog({ wasServerUrl, spaceId, didWebKeys, ladderVmKeyMultibase, credentialKeyAgreementMethod, updateKeyPublicKeyMultibase, nextKeyHashes, signer }) {
696
708
  return createWebvhLog({
@@ -760,17 +772,20 @@ export async function withLogConflictRetry(run) {
760
772
  * @param options.log {DIDLog}
761
773
  * @param [options.ifMatch] {string} publish only if the log is unchanged
762
774
  * @param [options.ifNoneMatch] {boolean} publish only if the log is absent
763
- * @returns {Promise<void>}
775
+ * @returns {Promise<{ etag?: string }>} the new validator of the log just
776
+ * written, for a stage building its entry on this head; absent against a
777
+ * backend that serves no ETags
764
778
  */
765
779
  export async function putLogResource({ store, log, ifMatch, ifNoneMatch }) {
766
780
  try {
767
- await store.putIdResource({
781
+ const written = await store.putIdResource({
768
782
  resourceId: DID_LOG_RESOURCE,
769
783
  content: logToJsonlString(log),
770
784
  contentType: 'text/jsonl',
771
785
  ifMatch,
772
786
  ifNoneMatch
773
787
  });
788
+ return written?.etag !== undefined ? { etag: written.etag } : {};
774
789
  }
775
790
  catch (err) {
776
791
  if (err?.name === 'PreconditionFailedError') {
@@ -779,6 +794,45 @@ export async function putLogResource({ store, log, ifMatch, ifNoneMatch }) {
779
794
  throw err;
780
795
  }
781
796
  }
797
+ /**
798
+ * THE POSTAMBLE: publishes `did.jsonl` -- the log only, never `did.json` (a
799
+ * caller writing through a bridge delegation is authorized for nothing else)
800
+ * -- and advances the caller's chain-head pin to what it just published, so a
801
+ * host rolling the log back straight afterwards is refused on the next read.
802
+ *
803
+ * So an entry published here leaves the `did:web` projection standing at
804
+ * whatever it said before: a ceremony whose entry REMOVES inventory (a
805
+ * removal, a retirement) must republish the projection itself before the
806
+ * entry lands, and a projection left behind by one that did not is mended by
807
+ * `ensureDidWebProjection` at the next visit holding a writer for the `id`
808
+ * collection (a controller-invoking client, or a transient visit under its
809
+ * generation delegation).
810
+ * The publish is conditional on the read the entry was built on; a lost race
811
+ * surfaces as a {@link WebvhLogConflictError} (the mapping lives in
812
+ * {@link putLogResource}).
813
+ *
814
+ * The two halves are one function on purpose. Separating them is what leaves
815
+ * a pin standing behind an entry this client itself published, the gap that
816
+ * already forced `BuiltOnHeadNotReachedError` into existence as a
817
+ * compensating class.
818
+ *
819
+ * @param options {object}
820
+ * @param options.store {object} anything with the seam's `putIdResource`
821
+ * @param options.log {DIDLog} the log this entry produced
822
+ * @param [options.ifMatch] {string} publish only if `did.jsonl` is unchanged
823
+ * @param [options.pinStore] {ResourceLogPinStore} the caller's chain-head
824
+ * pins; the pin advances only when a `logId` names its slot
825
+ * @param [options.logId] {string} the log's pin slot
826
+ * @returns {Promise<{ etag?: string }>} the new validator of the log this
827
+ * entry just published, for a stage building on the post-entry head
828
+ */
829
+ export async function publishEntryPinned({ store, log, ifMatch, pinStore, logId }) {
830
+ const written = await putLogResource({ store, log, ifMatch });
831
+ if (pinStore && logId !== undefined) {
832
+ await pinStore.write({ logId, pin: pinOfLog(log) });
833
+ }
834
+ return written;
835
+ }
782
836
  /**
783
837
  * Publishes an already-created log: PUT `did.jsonl` (`text/jsonl`) then PUT
784
838
  * `did.json` from `webDoc` (`application/did+json`, adopting the webvh
@@ -795,8 +849,11 @@ export async function putLogResource({ store, log, ifMatch, ifNoneMatch }) {
795
849
  * after the log's compare-and-swap was WON, so it is already serialized behind
796
850
  * that win; the log is the source of truth and the projection a derived cache;
797
851
  * and {@link concludeWithPublishedLog} re-derives and republishes the
798
- * projection from the resolved log on every ceremony's no-op path, healing any
799
- * lag a race or a torn publish leaves behind.
852
+ * projection from the resolved log on the no-op path of every ceremony that
853
+ * reaches this function, so a torn publish on one of those paths is healed by
854
+ * the next run of the same ceremony. That reach is the controller-invoking
855
+ * paths alone; `ensureDidWebProjection` is what mends a projection a
856
+ * ladder-signed entry ({@link publishEntryPinned}) left behind.
800
857
  *
801
858
  * @param options {object}
802
859
  * @param options.idStore {WebvhIdStore}
@@ -804,15 +861,19 @@ export async function putLogResource({ store, log, ifMatch, ifNoneMatch }) {
804
861
  * @param options.webDoc {object}
805
862
  * @param [options.ifMatch] {string} publish only if `did.jsonl` is unchanged
806
863
  * @param [options.ifNoneMatch] {boolean} publish only if `did.jsonl` is absent
807
- * @returns {Promise<void>}
864
+ * @returns {Promise<{ etag?: string }>} the LOG's new validator, never the
865
+ * projection's: the projection is a derived cache, and only the log's ETag
866
+ * is a precondition anything is built on
808
867
  */
809
868
  export async function publishWebvhLog({ idStore, log, webDoc, ifMatch, ifNoneMatch }) {
810
- await putLogResource({ store: idStore, log, ifMatch, ifNoneMatch });
811
- await idStore.putIdResource({
812
- resourceId: DID_DOCUMENT_RESOURCE,
813
- content: webDoc,
814
- contentType: 'application/did+json'
869
+ const written = await putLogResource({
870
+ store: idStore,
871
+ log,
872
+ ifMatch,
873
+ ifNoneMatch
815
874
  });
875
+ await putDidWebProjection({ store: idStore, webDoc });
876
+ return written;
816
877
  }
817
878
  /**
818
879
  * The shared guard-and-publish tail of every ceremony that extends the log
@@ -827,13 +888,13 @@ export async function publishWebvhLog({ idStore, log, webDoc, ifMatch, ifNoneMat
827
888
  * @param [options.updated.webDoc] {object}
828
889
  * @param [options.ifMatch] {string} the ETag of the read this entry was built
829
890
  * on
830
- * @returns {Promise<void>}
891
+ * @returns {Promise<{ etag?: string }>} the log's new validator
831
892
  */
832
893
  export async function publishUpdatedLog({ idStore, updated, ifMatch }) {
833
894
  if (!updated.webDoc) {
834
895
  throw new Error('did:webvh: updateDID returned no webDoc despite the did:web alsoKnownAs.');
835
896
  }
836
- await publishWebvhLog({
897
+ return publishWebvhLog({
837
898
  idStore,
838
899
  log: updated.log,
839
900
  webDoc: updated.webDoc,
@@ -841,20 +902,116 @@ export async function publishUpdatedLog({ idStore, updated, ifMatch }) {
841
902
  });
842
903
  }
843
904
  /**
844
- * Writes `keys.json` v2: the did:web relationship map plus the `webvh` block,
845
- * preserving the three did:web relationships. Exported for the ladder-anchored
846
- * ensure, whose create path records the account DID the same way the
847
- * enrolled-client one does.
905
+ * The served `keys.json`, read for the two members this module acts on. A body
906
+ * of any other shape reads as an empty map rather than throwing: the resource
907
+ * is host-held bookkeeping, and the caller's next step is a rewrite either
908
+ * way.
909
+ *
910
+ * @param content {unknown}
911
+ * @returns {object} the members present, as a partial key map
912
+ */
913
+ function servedKeyMap(content) {
914
+ const map = content;
915
+ const { vmId, kmsKeyId } = map?.authentication ?? {};
916
+ const did = map?.webvh?.did;
917
+ return {
918
+ ...(typeof vmId === 'string' && typeof kmsKeyId === 'string'
919
+ ? { authentication: { vmId, kmsKeyId } }
920
+ : {}),
921
+ ...(typeof did === 'string' ? { webvh: { did } } : {})
922
+ };
923
+ }
924
+ /**
925
+ * Writes `keys.json` v2: the KMS binding plus the `webvh` block. Exported for
926
+ * the ladder-anchored ensure, whose create path records the account DID the
927
+ * same way the enrolled-client one does.
928
+ *
929
+ * The body is CONSTRUCTED from the two members the map carries rather than
930
+ * spread from the caller's, so a legacy `keyAgreement` binding a stored map
931
+ * still holds is dropped by this rewrite.
932
+ *
933
+ * This is the genesis' rewrite of the map the KMS stage created one stage
934
+ * earlier, so it carries that write's ETag as its `ifMatch`. A caller holding
935
+ * no ETag (a backend that versions nothing) writes unconditionally.
936
+ *
937
+ * A lost precondition CONVERGES rather than propagating, since the genesis
938
+ * entry has already published by the time this runs and a bookkeeping resource
939
+ * must not fail a ceremony standing behind it: the served map is re-read, a
940
+ * map already naming this DID under this binding is left alone, and anything
941
+ * else is rewritten once under the served ETag. A second lost precondition
942
+ * propagates. Without the store's optional read there is nothing to converge
943
+ * on, and the first failure propagates.
848
944
  *
849
945
  * @param options {object}
850
946
  * @param options.idStore {WebvhIdStore}
851
- * @param options.didWebKeys {DidWebKeyMap}
947
+ * @param options.didWebKeys {DidWebKeyMap} the binding to record, which on
948
+ * the create path is the one this run published in the genesis entry
852
949
  * @param options.webvh {DidWebvhBlock}
950
+ * @param [options.ifMatch] {string} the ETag the KMS stage's own write
951
+ * returned
952
+ * @returns {Promise<void>}
953
+ */
954
+ export async function writeKeysJson({ idStore, didWebKeys, webvh, ifMatch }) {
955
+ const content = {
956
+ authentication: didWebKeys.authentication,
957
+ webvh
958
+ };
959
+ try {
960
+ await idStore.putKeyMap({
961
+ content,
962
+ ...(ifMatch !== undefined && { ifMatch })
963
+ });
964
+ return;
965
+ }
966
+ catch (err) {
967
+ const lostRace = err?.name === 'PreconditionFailedError';
968
+ if (!lostRace || !idStore.getKeyMapRaw) {
969
+ throw err;
970
+ }
971
+ const served = await idStore.getKeyMapRaw();
972
+ const servedMap = servedKeyMap(served?.content);
973
+ if (servedMap.webvh?.did === webvh.did &&
974
+ servedMap.authentication?.vmId === content.authentication.vmId) {
975
+ // The winner recorded this DID under this binding already: done.
976
+ return;
977
+ }
978
+ await idStore.putKeyMap({
979
+ content,
980
+ ...(served?.etag !== undefined && { ifMatch: served.etag })
981
+ });
982
+ }
983
+ }
984
+ /**
985
+ * Records the account DID into a `keys.json` carrying a KMS binding and no (or
986
+ * a stale) `webvh` block -- the state a run torn between its genesis entry and
987
+ * its rewrite leaves behind. It is the adoption path's half of that rewrite,
988
+ * and the binding comes from the SERVED map: the run that published the log
989
+ * recorded it, while this run's own map may name a key that log never
990
+ * published.
991
+ *
992
+ * A no-op when the store offers no read, when the served map carries no
993
+ * binding, and when it already names this DID.
994
+ *
995
+ * @param options {object}
996
+ * @param options.idStore {WebvhIdStore}
997
+ * @param options.did {string} the DID the adopted log resolves to
853
998
  * @returns {Promise<void>}
854
999
  */
855
- export async function writeKeysJson({ idStore, didWebKeys, webvh }) {
856
- const content = { ...didWebKeys, webvh };
857
- await idStore.putKeyMap({ content });
1000
+ export async function backfillKeyMapWebvhBlock({ idStore, did }) {
1001
+ if (!idStore.getKeyMapRaw) {
1002
+ return;
1003
+ }
1004
+ const served = await idStore.getKeyMapRaw();
1005
+ const servedMap = servedKeyMap(served?.content);
1006
+ if (!servedMap.authentication || servedMap.webvh?.did === did) {
1007
+ return;
1008
+ }
1009
+ await writeKeysJson({
1010
+ idStore,
1011
+ didWebKeys: { authentication: servedMap.authentication },
1012
+ webvh: { did },
1013
+ ...(served?.etag !== undefined && { ifMatch: served.etag })
1014
+ });
858
1015
  }
859
1016
  /**
860
1017
  * Reads and resolves the published `did.jsonl`, or returns `undefined` when
@@ -883,7 +1040,9 @@ export async function writeKeysJson({ idStore, didWebKeys, webvh }) {
883
1040
  * refused as a `rollback` instead of read as `undefined`.
884
1041
  *
885
1042
  * @param options {object}
886
- * @param options.idStore {WebvhIdStore}
1043
+ * @param options.idStore {Pick<WebvhIdStore, 'getIdResourceRaw'>} the read
1044
+ * half of the seam alone, so a caller holding a narrower store (a bridge
1045
+ * delegation's read + PUT pair) passes it directly
887
1046
  * @param [options.expectedDid] {string} the DID the log must resolve to
888
1047
  * @param [options.pinStore] {ResourceLogPinStore} this client's chain-head
889
1048
  * pins for the account log
@@ -917,20 +1076,7 @@ export async function readPublishedLog({ idStore, expectedDid, pinStore, logId }
917
1076
  if (resolved.meta.error || !resolved.did || !resolved.doc) {
918
1077
  throw new Error(`did:webvh: existing did.jsonl failed to resolve (${resolved.meta.error}).`);
919
1078
  }
920
- if (expectedDid !== undefined && resolved.did !== expectedDid) {
921
- throw new Error('did:webvh: the published did.jsonl resolves to a different DID ' +
922
- `(${resolved.did}) than expected (${expectedDid}).`);
923
- }
924
- if (pinned) {
925
- const pin = await pinned.store.read({ logId: pinned.logId });
926
- const served = checkAccountLogContinuity({ log, pin });
927
- // Advanced only when the served head is genuinely ahead of the pin: the
928
- // check above has already refused everything that is not.
929
- if (!pin || pin.head !== served.head) {
930
- await pinned.store.write({ logId: pinned.logId, pin: served });
931
- }
932
- }
933
- return {
1079
+ const published = {
934
1080
  log,
935
1081
  did: resolved.did,
936
1082
  doc: resolved.doc,
@@ -938,6 +1084,77 @@ export async function readPublishedLog({ idStore, expectedDid, pinStore, logId }
938
1084
  nextKeyHashes: resolved.meta.nextKeyHashes ?? [],
939
1085
  etag: read.etag
940
1086
  };
1087
+ assertPublishedLogDid({ published, expectedDid });
1088
+ if (pinned) {
1089
+ await checkAndAdvanceAccountLogPin({
1090
+ pinStore: pinned.store,
1091
+ logId: pinned.logId,
1092
+ log
1093
+ });
1094
+ }
1095
+ return published;
1096
+ }
1097
+ /**
1098
+ * The substituted-account refusal on its own: a resolved head that is not the
1099
+ * DID the caller expected is refused rather than built on. Stated once so a
1100
+ * fresh read and a caller-threaded head refuse identically.
1101
+ *
1102
+ * A ceremony that takes an already-read head from its caller (the transient
1103
+ * visit's one-read composition, where the readiness stage hands its verified
1104
+ * head to the enrollment) runs this explicitly: the read that would otherwise
1105
+ * have run it never happened, and a head resolving to another DID must not
1106
+ * become the entry's basis just because it arrived by parameter.
1107
+ *
1108
+ * @param options {object}
1109
+ * @param options.published {PublishedWebvhLog} the resolved head
1110
+ * @param [options.expectedDid] {string} the DID it must resolve to; absent,
1111
+ * the head is accepted (the caller discovering the DID from the log itself)
1112
+ * @returns {PublishedWebvhLog} the head verbatim
1113
+ */
1114
+ export function assertPublishedLogDid({ published, expectedDid }) {
1115
+ if (expectedDid !== undefined && published.did !== expectedDid) {
1116
+ throw new Error('did:webvh: the published did.jsonl resolves to a different DID ' +
1117
+ `(${published.did}) than expected (${expectedDid}).`);
1118
+ }
1119
+ return published;
1120
+ }
1121
+ /**
1122
+ * {@link readPublishedLog} for the ceremonies whose premise is a log that
1123
+ * already exists: an absent `did.jsonl` is a refusal rather than a state to
1124
+ * branch on, so the caller gets a `PublishedWebvhLog` or an error. The
1125
+ * `missingMessage` is the caller's own phrasing of what there is nothing to do
1126
+ * against ("nothing to enroll into", "nothing to recover"), since the read
1127
+ * itself cannot know which ceremony is standing on it.
1128
+ *
1129
+ * Every other option, and every check behind them -- the `expectedDid`
1130
+ * refusal and the chain-head pin's rollback / fork / identity-switch
1131
+ * refusals -- is {@link readPublishedLog}'s verbatim. Each attempt of a
1132
+ * conflict-retried ceremony reads for itself, so the continuity check runs on
1133
+ * the read the compare-and-swap publish is conditioned on rather than only on
1134
+ * an orchestrator's pre-read.
1135
+ *
1136
+ * @param options {object}
1137
+ * @param options.idStore {Pick<WebvhIdStore, 'getIdResourceRaw'>}
1138
+ * @param [options.expectedDid] {string} the DID the log must resolve to
1139
+ * @param [options.pinStore] {ResourceLogPinStore} this client's chain-head
1140
+ * pins for the log being read
1141
+ * @param [options.logId] {string} the log's pin-slot key; required whenever
1142
+ * a `pinStore` is supplied
1143
+ * @param [options.missingMessage] {string} the thrown `Error`'s message when
1144
+ * the log is absent
1145
+ * @returns {Promise<PublishedWebvhLog>}
1146
+ */
1147
+ export async function readPublishedLogOrThrow({ idStore, expectedDid, pinStore, logId, missingMessage = 'did:webvh: did.jsonl is missing.' }) {
1148
+ const published = await readPublishedLog({
1149
+ idStore,
1150
+ ...(expectedDid !== undefined ? { expectedDid } : {}),
1151
+ ...(pinStore ? { pinStore } : {}),
1152
+ ...(logId !== undefined ? { logId } : {})
1153
+ });
1154
+ if (!published) {
1155
+ throw new Error(missingMessage);
1156
+ }
1157
+ return published;
941
1158
  }
942
1159
  /**
943
1160
  * The chain-head pin a log establishes: the genesis entry's method and SCID
@@ -969,16 +1186,22 @@ export function servedHead(log) {
969
1186
  * already-revoked no-op. All of them used to infer completion from `did.jsonl`
970
1187
  * alone, which is a half of the state: {@link publishWebvhLog} writes the log
971
1188
  * and its `did:web` projection in two non-atomic PUTs, so a crash between them
972
- * leaves a `did.jsonl` that is complete beside a `did.json` that lags it
973
- * forever (nothing else republishes the projection).
1189
+ * leaves a `did.jsonl` that is complete beside a `did.json` that lags it.
974
1190
  *
975
1191
  * So the projection is re-derived from the resolved log and re-PUT
976
1192
  * unconditionally rather than compared first: the write is idempotent and one
977
1193
  * request either way, and the resolved log is the source of truth for what the
978
1194
  * projection must say. A ceremony that no-ops on the log therefore still heals
979
- * a torn earlier publish. The projection PUT is deliberately unconditional
980
- * here too: healing it is the whole point, and the log it was derived from is
981
- * the state this call just read and resolved.
1195
+ * a torn earlier publish OF THAT CEREMONY. The projection PUT is deliberately
1196
+ * unconditional here too: healing it is the whole point, and the log it was
1197
+ * derived from is the state this call just read and resolved.
1198
+ *
1199
+ * The reach is what to hold on to: every caller here invokes as the account's
1200
+ * controller, so this heals nothing for the ladder-signed entries that publish
1201
+ * through {@link publishEntryPinned} and write the log alone. Those are mended
1202
+ * by their own ceremony's pre-entry projection PUT and, failing that, by
1203
+ * `ensureDidWebProjection` at the next visit that holds an `id`-collection
1204
+ * writer.
982
1205
  *
983
1206
  * @param options {object}
984
1207
  * @param options.idStore {WebvhIdStore}
@@ -987,10 +1210,9 @@ export function servedHead(log) {
987
1210
  * resolved document
988
1211
  */
989
1212
  export async function concludeWithPublishedLog({ idStore, published }) {
990
- await idStore.putIdResource({
991
- resourceId: DID_DOCUMENT_RESOURCE,
992
- content: generateParallelDidWeb(published.did, published.doc),
993
- contentType: 'application/did+json'
1213
+ await putDidWebProjection({
1214
+ store: idStore,
1215
+ webDoc: generateParallelDidWeb(published.did, published.doc)
994
1216
  });
995
1217
  return { did: published.did, doc: published.doc };
996
1218
  }
@@ -1019,6 +1241,22 @@ export function effectiveParameters(log) {
1019
1241
  }
1020
1242
  return out;
1021
1243
  }
1244
+ /**
1245
+ * The log's current `updateKeys` / `nextKeyHashes` view: the last entry's
1246
+ * effective parameters, or the empty pair for a log with no entries.
1247
+ *
1248
+ * The empty default is a policy choice, not a convenience: it is what makes
1249
+ * an unresolvable log attribute as "no rung standing" rather than throw, so
1250
+ * it is stated here once rather than at each attribution site.
1251
+ *
1252
+ * @param published {object}
1253
+ * @param published.log {DIDLog}
1254
+ * @returns {object}
1255
+ */
1256
+ export function currentLogParameters(published) {
1257
+ const params = effectiveParameters(published.log);
1258
+ return params[params.length - 1] ?? { updateKeys: [], nextKeyHashes: [] };
1259
+ }
1022
1260
  /**
1023
1261
  * The `publicKeyMultibase` of every update-key seed this client holds, in
1024
1262
  * role order (active, staged, pending).
@@ -1105,8 +1343,10 @@ function advancedSeeds({ published, multibases, updateKeys }) {
1105
1343
  * @param options.wasServerUrl {string}
1106
1344
  * @param options.spaceId {string}
1107
1345
  * @param [options.didWebKeys] {DidWebKeyMapV2} the parsed keys.json (with any
1108
- * webvh block) returned by the did:web provisioning; absent on a
1346
+ * webvh block) returned by the KMS-authentication stage; absent on a
1109
1347
  * client-keys-only genesis (no KMS anywhere in the path)
1348
+ * @param [options.keysJsonEtag] {string} the ETag that stage's own write
1349
+ * returned, carried as the `ifMatch` of the rewrite that records the DID
1110
1350
  * @param options.clientKeys {WebvhClientKeys} this client's published keys
1111
1351
  * @param options.updateKeys {ClientWebvhUpdateKeys} already persisted
1112
1352
  * client-local
@@ -1126,7 +1366,7 @@ export async function ensureDidWebvh(options) {
1126
1366
  * @param options {object} see {@link ensureDidWebvh}
1127
1367
  * @returns {Promise<{ did: string }>}
1128
1368
  */
1129
- async function ensureDidWebvhOnce({ idStore, wasServerUrl, spaceId, didWebKeys, clientKeys, updateKeys, expectedDid, pinStore }) {
1369
+ async function ensureDidWebvhOnce({ idStore, wasServerUrl, spaceId, didWebKeys, keysJsonEtag, clientKeys, updateKeys, expectedDid, pinStore }) {
1130
1370
  // The DID this run expects the published log to resolve to: the caller's,
1131
1371
  // else the one keys.json already records. Undefined only on the documented
1132
1372
  // first-contact adoption, which discovers the DID from the log itself.
@@ -1158,10 +1398,13 @@ async function ensureDidWebvhOnce({ idStore, wasServerUrl, spaceId, didWebKeys,
1158
1398
  await writeKeysJson({
1159
1399
  idStore,
1160
1400
  didWebKeys,
1161
- webvh: { did: published.did }
1401
+ webvh: { did: published.did },
1402
+ ...(keysJsonEtag !== undefined && { ifMatch: keysJsonEtag })
1162
1403
  });
1163
1404
  }
1164
- // Heals a did.json left lagging by a torn earlier publish.
1405
+ // Heals a did.json left lagging by a torn earlier publish of this
1406
+ // controller-invoking path (a ladder-signed entry's lag is not reached
1407
+ // here; `ensureDidWebProjection` is that mender).
1165
1408
  const { did } = await concludeWithPublishedLog({ idStore, published });
1166
1409
  return { did };
1167
1410
  }
@@ -1196,7 +1439,8 @@ async function ensureDidWebvhOnce({ idStore, wasServerUrl, spaceId, didWebKeys,
1196
1439
  await writeKeysJson({
1197
1440
  idStore,
1198
1441
  didWebKeys,
1199
- webvh: { did: created.did }
1442
+ webvh: { did: created.did },
1443
+ ...(keysJsonEtag !== undefined && { ifMatch: keysJsonEtag })
1200
1444
  });
1201
1445
  }
1202
1446
  return { did: created.did };
@@ -1380,281 +1624,4 @@ export async function assertCarryOverCommitments({ published }) {
1380
1624
  }
1381
1625
  }
1382
1626
  }
1383
- /**
1384
- * The relationship references of a resolved document as verification-method
1385
- * ids, tolerating embedded objects beside string references.
1386
- *
1387
- * @param relation {Array} the relationship array, when present
1388
- * @returns {string[]}
1389
- */
1390
- export function relationIds(relation) {
1391
- const ids = [];
1392
- for (const entry of relation ?? []) {
1393
- const id = typeof entry === 'string' ? entry : entry?.id;
1394
- if (id) {
1395
- ids.push(id);
1396
- }
1397
- }
1398
- return ids;
1399
- }
1400
- /**
1401
- * Enrolls a second wallet client into the published did:webvh document -- the
1402
- * log half of the enrollment ceremony (the user key roster wrap happens first,
1403
- * outside this module). Two entries, forced by prerotation (a new update key
1404
- * must hash into the PREVIOUS entry's `nextKeyHashes`):
1405
- *
1406
- * 1. **Commit**: a sparse entry extending `nextKeyHashes` with the new
1407
- * client's update-key and staged-key hashes (document and `updateKeys`
1408
- * untouched).
1409
- * 2. **Add**: an entry adding the new client's verification methods (its
1410
- * Ed25519 key under the four signing relationships, its X25519 twin under
1411
- * `keyAgreement`) and its update key to `updateKeys`.
1412
- *
1413
- * Both entries are signed by THIS client's active update key (quorum-of-one:
1414
- * any single enrolled client can enroll). The ceremony is resumable from
1415
- * durable state alone: a tear after the commit is detected by its hashes
1416
- * already standing in `nextKeyHashes` (skip to the add entry), and a
1417
- * completed enrollment is detected by the update key already being authorized
1418
- * (no-op). Re-running with the same key set converges without forking the
1419
- * log.
1420
- *
1421
- * Each entry publishes conditionally on the read it was built on (the add
1422
- * entry on the mid-ceremony re-read), so a concurrent ceremony -- a revocation,
1423
- * another enrollment -- can never be erased by this one; a lost race re-runs
1424
- * from the top (see {@link withLogConflictRetry}) and rebases on the new head.
1425
- *
1426
- * @param options {object}
1427
- * @param options.idStore {WebvhIdStore}
1428
- * @param options.updateKeys {ClientWebvhUpdateKeys} THIS client's seeds
1429
- * @param options.newClient {WebvhEnrollmentKeys} the enrollee's public halves
1430
- * @returns {Promise<{ did: string }>}
1431
- */
1432
- export async function enrollWebvhClient(options) {
1433
- return withLogConflictRetry(() => enrollWebvhClientOnce(options));
1434
- }
1435
- /**
1436
- * One attempt of {@link enrollWebvhClient}, re-invoked by the conflict retry.
1437
- *
1438
- * @param options {object} see {@link enrollWebvhClient}
1439
- * @returns {Promise<{ did: string }>}
1440
- */
1441
- async function enrollWebvhClientOnce({ idStore, updateKeys, newClient }) {
1442
- let published = await readPublishedLog({ idStore });
1443
- if (!published) {
1444
- throw new Error('did:webvh: did.jsonl is missing; nothing to enroll into.');
1445
- }
1446
- const multibases = await updateKeyMultibases({ updateKeys });
1447
- // Already enrolled (a completed earlier run): the new client's update key is
1448
- // authorized, which only the add entry writes. Idempotent no-op on the log,
1449
- // but it still heals a did.json the earlier run left lagging.
1450
- if (published.updateKeys.includes(newClient.updateKeyMultibase)) {
1451
- const { did } = await concludeWithPublishedLog({ idStore, published });
1452
- return { did };
1453
- }
1454
- // Both entries are signed by this client's active update key; a log that
1455
- // does not authorize it (a rotation torn elsewhere) must heal first.
1456
- if (!published.updateKeys.includes(multibases.update)) {
1457
- throw new Error("did:webvh: the published log does not authorize this client's active " +
1458
- 'update key; finalize the pending rotation before enrolling.');
1459
- }
1460
- const newUpdateKeyHash = await deriveNextKeyHash(newClient.updateKeyMultibase);
1461
- const newStagedKeyHash = await deriveNextKeyHash(newClient.stagedUpdateKeyMultibase);
1462
- // The commit entry (skipped when a torn earlier run already published it).
1463
- const committed = published.nextKeyHashes.includes(newUpdateKeyHash) &&
1464
- published.nextKeyHashes.includes(newStagedKeyHash);
1465
- if (!committed) {
1466
- // A sparse entry re-states the authorized updateKeys, and the resolver
1467
- // checks each against the PREVIOUS entry's commitments -- so every
1468
- // currently authorized key's hash must already stand in nextKeyHashes
1469
- // (the carry-over convention). A log minted before the convention cannot
1470
- // take a non-rotating entry.
1471
- for (const key of published.updateKeys) {
1472
- if (!published.nextKeyHashes.includes(await deriveNextKeyHash(key))) {
1473
- throw new Error('did:webvh: the published log does not carry the active update ' +
1474
- "keys' own hashes in nextKeyHashes (it predates the carry-over " +
1475
- 'commitment convention); re-provision the account before ' +
1476
- 'enrolling.');
1477
- }
1478
- }
1479
- const signer = await updateKeySigner({ seed: updateKeys.updateSeed });
1480
- const updated = await updateDID({
1481
- log: published.log,
1482
- signer,
1483
- alsoKnownAsWeb: true,
1484
- // Re-stated unchanged (the library requires them explicitly while
1485
- // prerotation is active); the carry-over commitments are what make the
1486
- // re-statement resolvable.
1487
- updateKeys: published.updateKeys,
1488
- nextKeyHashes: [
1489
- ...new Set([
1490
- ...published.nextKeyHashes,
1491
- newUpdateKeyHash,
1492
- newStagedKeyHash
1493
- ])
1494
- ]
1495
- });
1496
- await publishUpdatedLog({ idStore, updated, ifMatch: published.etag });
1497
- // Re-read through the same verifying path the resume case uses, so the
1498
- // add entry below always builds on the published, resolved state. It must
1499
- // still be the same account: the DID the first read resolved to is what
1500
- // the entry just published extends.
1501
- published = await readPublishedLog({ idStore, expectedDid: published.did });
1502
- if (!published) {
1503
- throw new Error('did:webvh: did.jsonl vanished mid-enrollment.');
1504
- }
1505
- }
1506
- // The add entry: the new client's two verification methods and its update
1507
- // key, on top of the full existing document (updateDID replaces the
1508
- // verification-method set and relationship arrays wholesale).
1509
- const { did, doc, log, updateKeys: authorizedKeys, nextKeyHashes, etag } = published;
1510
- const vmId = (publicKeyMultibase) => `${did}#${publicKeyMultibase}`;
1511
- // The signing method is controlled by the account; the key-agreement method
1512
- // alone carries the controller marker, and the pair builder refuses a
1513
- // key-agreement key that is not the signing key's canonical twin.
1514
- const addedMethods = markedVerificationMethodPair({
1515
- controller: did,
1516
- signingKeyMultibase: newClient.signingKeyMultibase,
1517
- keyAgreementKeyMultibase: newClient.keyAgreementKeyMultibase
1518
- });
1519
- const existingMethods = (doc.verificationMethod ?? []);
1520
- const verificationMethods = [
1521
- ...existingMethods.filter(method => !addedMethods.some(added => added.id === method.id)),
1522
- ...addedMethods
1523
- ];
1524
- const withReference = (relation, id) => [...new Set([...relationIds(relation), id])];
1525
- const signingVmId = vmId(newClient.signingKeyMultibase);
1526
- const signer = await updateKeySigner({ seed: updateKeys.updateSeed });
1527
- const updated = await updateDID({
1528
- log,
1529
- signer,
1530
- alsoKnownAsWeb: true,
1531
- updateKeys: [...new Set([...authorizedKeys, newClient.updateKeyMultibase])],
1532
- nextKeyHashes,
1533
- verificationMethods,
1534
- authentication: withReference(doc.authentication, signingVmId),
1535
- assertionMethod: withReference(doc.assertionMethod, signingVmId),
1536
- keyAgreement: withReference(doc.keyAgreement, vmId(newClient.keyAgreementKeyMultibase)),
1537
- capabilityInvocation: withReference(doc.capabilityInvocation, signingVmId),
1538
- capabilityDelegation: withReference(doc.capabilityDelegation, signingVmId)
1539
- });
1540
- // The etag of the read this entry was built on: the mid-ceremony re-read
1541
- // when the commit entry ran here, the original read when it was skipped.
1542
- await publishUpdatedLog({ idStore, updated, ifMatch: etag });
1543
- return { did: updated.did };
1544
- }
1545
- /**
1546
- * Rebuilds `keys.json` from the published artifacts plus a WebKMS key listing
1547
- * -- the recovery path for a lost or rolled-back `keys.json`. List Keys is
1548
- * authorized as `read` against the keystore controller, which only the
1549
- * root-controlled keystore agent can invoke.
1550
- *
1551
- * KMS key local ids are server-generated random and appear in no published
1552
- * artifact, so the bindings are rediscovered by public key material instead.
1553
- * List the keystore once -- each listed description carries `keyUrl`, the
1554
- * key's canonical invocation URL (the signable handle its alias-overridden
1555
- * `id` erases) -- then match `did.json`'s relationship verification methods
1556
- * by `publicKeyMultibase` and rewrite `keys.json` from what matched. The
1557
- * `authentication` and `keyAgreement` bindings are required; `assertionMethod`
1558
- * lists client keys only, so no KMS binding exists there and none is rebuilt.
1559
- * When `did.jsonl` is published, its resolved DID is recorded in the `webvh`
1560
- * block; there is nothing else to repair there, since the log's update keys are
1561
- * client-held seeds that no keystore listing could recover.
1562
- *
1563
- * An unmatchable binding is unrepairable and throws: a published artifact
1564
- * depends on a key the keystore no longer lists.
1565
- *
1566
- * The log read takes the same checks every other ceremony read does, and for
1567
- * the same reason: what it reads is written straight back into the rebuilt
1568
- * `keys.json`, so a substituted log would rewrite the `webvh` block to a wrong
1569
- * DID and a truncated one would be adopted as this account's history. A caller
1570
- * that still holds the account pointer passes `expectedDid` and its
1571
- * `pinStore`; the pure-recovery caller that has lost everything but the
1572
- * keystore has no DID to expect and passes neither, which is the one read here
1573
- * that legitimately discovers the DID from the log itself.
1574
- *
1575
- * @param options {object}
1576
- * @param options.keystoreAgent {KeystoreAgent}
1577
- * @param options.idStore {WebvhIdStore}
1578
- * @param [options.expectedDid] {string} the DID the published log must
1579
- * resolve to
1580
- * @param [options.pinStore] {ResourceLogPinStore} this client's chain-head
1581
- * pins for the account log
1582
- * @param [options.logId] {string} the account log's pin-slot key, built by
1583
- * the caller with `accountLogPinId({ spaceId })`; required whenever a
1584
- * `pinStore` is supplied
1585
- * @returns {Promise<DidWebKeyMapV2>} the rebuilt, persisted keys.json
1586
- */
1587
- export async function repairKeyBindings({ keystoreAgent, idStore, expectedDid, pinStore, logId }) {
1588
- if (pinStore && logId === undefined) {
1589
- throw new TypeError('logId is required when pinStore is supplied');
1590
- }
1591
- const didDoc = (await idStore.getIdResource({
1592
- resourceId: DID_DOCUMENT_RESOURCE
1593
- }));
1594
- if (!didDoc) {
1595
- throw new Error('keys.json repair: did.json is not published; there is nothing to ' +
1596
- 'match key bindings against.');
1597
- }
1598
- // One listing, matched by public key material below. `keyUrl` is the
1599
- // list-only projection field (webkms-client >= 14.7.1 types it; an older
1600
- // server omits it, so entries without one are skipped and simply fail to
1601
- // match).
1602
- const listed = (await keystoreAgent.listKeys());
1603
- const keyUrlByMultibase = new Map();
1604
- for (const description of listed) {
1605
- if (description.publicKeyMultibase && description.keyUrl) {
1606
- keyUrlByMultibase.set(description.publicKeyMultibase, description.keyUrl);
1607
- }
1608
- }
1609
- // A relationship's first keystore-backed verification method. A
1610
- // relationship can name several verification methods now that enrolled
1611
- // clients publish their own keys beside the KMS ones, so every reference is
1612
- // tried and the first keystore-backed one wins; a client-held key simply
1613
- // fails to match and is skipped.
1614
- const findKmsBacked = (relationship) => {
1615
- const tried = [];
1616
- for (const reference of didDoc[relationship] ?? []) {
1617
- const vmId = typeof reference === 'string' ? reference : reference?.id;
1618
- if (!vmId) {
1619
- continue;
1620
- }
1621
- const method = didDoc.verificationMethod?.find(entry => entry.id === vmId);
1622
- const publicKeyMultibase = method?.publicKeyMultibase ?? multibaseOf(vmId);
1623
- tried.push(publicKeyMultibase);
1624
- const kmsKeyId = keyUrlByMultibase.get(publicKeyMultibase);
1625
- if (kmsKeyId) {
1626
- return { bound: { vmId, kmsKeyId }, tried };
1627
- }
1628
- }
1629
- return { tried };
1630
- };
1631
- const bind = (relationship) => {
1632
- if ((didDoc[relationship] ?? []).length === 0) {
1633
- throw new Error(`keys.json repair: did.json declares no ${relationship} verification method.`);
1634
- }
1635
- const { bound, tried } = findKmsBacked(relationship);
1636
- if (!bound) {
1637
- throw new Error(`keys.json repair: no keystore key matches the ${relationship} ` +
1638
- `verification method (${tried.join(', ')}).`);
1639
- }
1640
- return bound;
1641
- };
1642
- const repaired = {
1643
- authentication: bind('authentication'),
1644
- keyAgreement: bind('keyAgreement')
1645
- };
1646
- // The webvh block, recovered from the published log: the DID and nothing
1647
- // else, since the update keys never left the client that minted them.
1648
- const published = await readPublishedLog({
1649
- idStore,
1650
- ...(expectedDid !== undefined ? { expectedDid } : {}),
1651
- ...(pinStore && logId !== undefined ? { pinStore, logId } : {})
1652
- });
1653
- if (published) {
1654
- repaired.webvh = { did: published.did };
1655
- }
1656
- // Persist the rebuilt anchor in one write.
1657
- await idStore.putKeyMap({ content: repaired });
1658
- return repaired;
1659
- }
1660
1627
  //# sourceMappingURL=didWebvh.js.map