@interop/wallet-core 0.61.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 (313) 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 +126 -35
  15. package/dist/clientAnnex/forgetLast.d.ts.map +1 -1
  16. package/dist/clientAnnex/forgetLast.js +208 -60
  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 +101 -33
  23. package/dist/clientAnnex/heal.d.ts.map +1 -1
  24. package/dist/clientAnnex/heal.js +559 -212
  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 +304 -18
  31. package/dist/clientAnnex/ladder.d.ts.map +1 -1
  32. package/dist/clientAnnex/ladder.js +942 -64
  33. package/dist/clientAnnex/ladder.js.map +1 -1
  34. package/dist/clientAnnex/ladderAnchored.d.ts +261 -28
  35. package/dist/clientAnnex/ladderAnchored.d.ts.map +1 -1
  36. package/dist/clientAnnex/ladderAnchored.js +521 -295
  37. package/dist/clientAnnex/ladderAnchored.js.map +1 -1
  38. package/dist/clientAnnex/log.d.ts +193 -38
  39. package/dist/clientAnnex/log.d.ts.map +1 -1
  40. package/dist/clientAnnex/log.js +445 -190
  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 +38 -11
  47. package/dist/clientAnnex/recoveryLadderAnchored.d.ts.map +1 -1
  48. package/dist/clientAnnex/recoveryLadderAnchored.js +177 -55
  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 +24 -8
  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 +89 -9
  139. package/dist/keys/userKeyRoster.d.ts.map +1 -1
  140. package/dist/keys/userKeyRoster.js +262 -19
  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 +4 -1
  163. package/dist/recovery/recoveryDelegation.d.ts.map +1 -1
  164. package/dist/recovery/recoveryDelegation.js +25 -36
  165. package/dist/recovery/recoveryDelegation.js.map +1 -1
  166. package/dist/recovery/recoveryWebvh.d.ts +117 -65
  167. package/dist/recovery/recoveryWebvh.d.ts.map +1 -1
  168. package/dist/recovery/recoveryWebvh.js +279 -126
  169. package/dist/recovery/recoveryWebvh.js.map +1 -1
  170. package/dist/request/classify.d.ts +32 -8
  171. package/dist/request/classify.d.ts.map +1 -1
  172. package/dist/request/classify.js +39 -14
  173. package/dist/request/classify.js.map +1 -1
  174. package/dist/request/ephemeralExchange.d.ts +1 -6
  175. package/dist/request/ephemeralExchange.d.ts.map +1 -1
  176. package/dist/request/ephemeralExchange.js.map +1 -1
  177. package/dist/request/onboarding.d.ts.map +1 -1
  178. package/dist/request/onboarding.js +2 -2
  179. package/dist/request/onboarding.js.map +1 -1
  180. package/dist/request/parse.d.ts.map +1 -1
  181. package/dist/request/parse.js +2 -3
  182. package/dist/request/parse.js.map +1 -1
  183. package/dist/resourceLog/controller.d.ts +34 -12
  184. package/dist/resourceLog/controller.d.ts.map +1 -1
  185. package/dist/resourceLog/controller.js +79 -86
  186. package/dist/resourceLog/controller.js.map +1 -1
  187. package/dist/resourceLog/document.d.ts +182 -0
  188. package/dist/resourceLog/document.d.ts.map +1 -0
  189. package/dist/resourceLog/document.js +159 -0
  190. package/dist/resourceLog/document.js.map +1 -0
  191. package/dist/resourceLog/errors.d.ts +47 -8
  192. package/dist/resourceLog/errors.d.ts.map +1 -1
  193. package/dist/resourceLog/errors.js +54 -8
  194. package/dist/resourceLog/errors.js.map +1 -1
  195. package/dist/resourceLog/index.d.ts +7 -3
  196. package/dist/resourceLog/index.d.ts.map +1 -1
  197. package/dist/resourceLog/index.js +7 -3
  198. package/dist/resourceLog/index.js.map +1 -1
  199. package/dist/resourceLog/ladderRungs.d.ts +35 -0
  200. package/dist/resourceLog/ladderRungs.d.ts.map +1 -0
  201. package/dist/resourceLog/ladderRungs.js +352 -0
  202. package/dist/resourceLog/ladderRungs.js.map +1 -0
  203. package/dist/resourceLog/license.d.ts +42 -17
  204. package/dist/resourceLog/license.d.ts.map +1 -1
  205. package/dist/resourceLog/license.js +38 -24
  206. package/dist/resourceLog/license.js.map +1 -1
  207. package/dist/space/activity.d.ts +15 -15
  208. package/dist/space/activity.d.ts.map +1 -1
  209. package/dist/space/activity.js +15 -15
  210. package/dist/space/activity.js.map +1 -1
  211. package/dist/space/collections.d.ts +11 -0
  212. package/dist/space/collections.d.ts.map +1 -1
  213. package/dist/space/collections.js +13 -0
  214. package/dist/space/collections.js.map +1 -1
  215. package/dist/space/deleteSpace.d.ts +28 -0
  216. package/dist/space/deleteSpace.d.ts.map +1 -0
  217. package/dist/space/deleteSpace.js +44 -0
  218. package/dist/space/deleteSpace.js.map +1 -0
  219. package/dist/space/errors.d.ts.map +1 -1
  220. package/dist/space/errors.js +0 -1
  221. package/dist/space/errors.js.map +1 -1
  222. package/dist/space/index.d.ts +6 -0
  223. package/dist/space/index.d.ts.map +1 -1
  224. package/dist/space/index.js +6 -0
  225. package/dist/space/index.js.map +1 -1
  226. package/dist/space/plaintextCollection.d.ts +43 -0
  227. package/dist/space/plaintextCollection.d.ts.map +1 -0
  228. package/dist/space/plaintextCollection.js +17 -0
  229. package/dist/space/plaintextCollection.js.map +1 -0
  230. package/dist/stages.d.ts +23 -0
  231. package/dist/stages.d.ts.map +1 -0
  232. package/dist/stages.js +23 -0
  233. package/dist/stages.js.map +1 -0
  234. package/dist/sync/index.d.ts +7 -0
  235. package/dist/sync/index.d.ts.map +1 -1
  236. package/dist/sync/index.js +7 -0
  237. package/dist/sync/index.js.map +1 -1
  238. package/dist/sync/push.js +4 -4
  239. package/dist/sync/push.js.map +1 -1
  240. package/dist/sync/remint.js +2 -2
  241. package/dist/sync/remint.js.map +1 -1
  242. package/dist/sync/types.d.ts +41 -1
  243. package/dist/sync/types.d.ts.map +1 -1
  244. package/dist/sync/types.js +47 -1
  245. package/dist/sync/types.js.map +1 -1
  246. package/dist/unlock/index.d.ts +6 -2
  247. package/dist/unlock/index.d.ts.map +1 -1
  248. package/dist/unlock/index.js +5 -1
  249. package/dist/unlock/index.js.map +1 -1
  250. package/dist/unlock/retire.d.ts +95 -14
  251. package/dist/unlock/retire.d.ts.map +1 -1
  252. package/dist/unlock/retire.js +102 -10
  253. package/dist/unlock/retire.js.map +1 -1
  254. package/dist/unlock/standingClient.d.ts.map +1 -1
  255. package/dist/unlock/standingClient.js +5 -1
  256. package/dist/unlock/standingClient.js.map +1 -1
  257. package/dist/unlock/standingWebvh.d.ts +341 -49
  258. package/dist/unlock/standingWebvh.d.ts.map +1 -1
  259. package/dist/unlock/standingWebvh.js +608 -164
  260. package/dist/unlock/standingWebvh.js.map +1 -1
  261. package/dist/webvh/accountEntry.d.ts +146 -0
  262. package/dist/webvh/accountEntry.d.ts.map +1 -0
  263. package/dist/webvh/accountEntry.js +239 -0
  264. package/dist/webvh/accountEntry.js.map +1 -0
  265. package/dist/webvh/didWeb.d.ts +12 -7
  266. package/dist/webvh/didWeb.d.ts.map +1 -1
  267. package/dist/webvh/didWeb.js +2 -2
  268. package/dist/webvh/didWeb.js.map +1 -1
  269. package/dist/webvh/didWebProjection.d.ts +164 -0
  270. package/dist/webvh/didWebProjection.d.ts.map +1 -0
  271. package/dist/webvh/didWebProjection.js +230 -0
  272. package/dist/webvh/didWebProjection.js.map +1 -0
  273. package/dist/webvh/didWebvh.d.ts +278 -138
  274. package/dist/webvh/didWebvh.d.ts.map +1 -1
  275. package/dist/webvh/didWebvh.js +314 -345
  276. package/dist/webvh/didWebvh.js.map +1 -1
  277. package/dist/webvh/enrollClient.d.ts +65 -0
  278. package/dist/webvh/enrollClient.d.ts.map +1 -0
  279. package/dist/webvh/enrollClient.js +172 -0
  280. package/dist/webvh/enrollClient.js.map +1 -0
  281. package/dist/webvh/index.d.ts +37 -12
  282. package/dist/webvh/index.d.ts.map +1 -1
  283. package/dist/webvh/index.js +33 -10
  284. package/dist/webvh/index.js.map +1 -1
  285. package/dist/webvh/listClients.d.ts +1 -33
  286. package/dist/webvh/listClients.d.ts.map +1 -1
  287. package/dist/webvh/listClients.js +2 -28
  288. package/dist/webvh/listClients.js.map +1 -1
  289. package/dist/webvh/revokeClient.d.ts +89 -11
  290. package/dist/webvh/revokeClient.d.ts.map +1 -1
  291. package/dist/webvh/revokeClient.js +182 -47
  292. package/dist/webvh/revokeClient.js.map +1 -1
  293. package/dist/webvh/standingZcap.d.ts +61 -14
  294. package/dist/webvh/standingZcap.d.ts.map +1 -1
  295. package/dist/webvh/standingZcap.js +91 -0
  296. package/dist/webvh/standingZcap.js.map +1 -1
  297. package/dist/webvh/verifyLog.d.ts +59 -5
  298. package/dist/webvh/verifyLog.d.ts.map +1 -1
  299. package/dist/webvh/verifyLog.js +76 -11
  300. package/dist/webvh/verifyLog.js.map +1 -1
  301. package/dist/webvh/wasIdStore.d.ts +8 -8
  302. package/dist/webvh/wasIdStore.d.ts.map +1 -1
  303. package/dist/webvh/wasIdStore.js +39 -14
  304. package/dist/webvh/wasIdStore.js.map +1 -1
  305. package/dist/webvh/zcap.d.ts +14 -1
  306. package/dist/webvh/zcap.d.ts.map +1 -1
  307. package/dist/webvh/zcap.js +3 -27
  308. package/dist/webvh/zcap.js.map +1 -1
  309. package/package.json +5 -5
  310. package/dist/webvh/keyAgreement.d.ts +0 -72
  311. package/dist/webvh/keyAgreement.d.ts.map +0 -1
  312. package/dist/webvh/keyAgreement.js +0 -47
  313. package/dist/webvh/keyAgreement.js.map +0 -1
@@ -12,11 +12,15 @@
12
12
  * key (a passphrase), so the world-readable document carries a check on the
13
13
  * key without carrying the key -- and `nextKeyHashes` gains the hash of the
14
14
  * credential's current update key (a ladder rung, or a code's single derived
15
- * key).
15
+ * key). A credential that carries a ladder gains its LADDER VM in the same
16
+ * entry, under `assertionMethod` and `capabilityDelegation`: the VM's life is
17
+ * the credential's, installed when it becomes standing and struck when it
18
+ * retires, and enrollment never touches it.
16
19
  * Decryption standing, authority latent: the credential's update key joins
17
- * `updateKeys` nowhere, and both entries are deliberately unmarked, so client
18
- * listings (keyed on `capabilityInvocation`) and revocation removals never
19
- * see them. {@link publishUnlockKey} / {@link removeUnlockKey} are one merged
20
+ * `updateKeys` nowhere, and the key-agreement entry is deliberately unmarked,
21
+ * so client listings (keyed on `capabilityInvocation`) and revocation
22
+ * removals never see it; the VM's relation asymmetry keeps it out of the same
23
+ * listings. {@link publishUnlockKey} / {@link removeUnlockKey} are one merged
20
24
  * add/remove pair, shared verbatim by the recovery-code wrappers.
21
25
  *
22
26
  * The ceremonies that EXERCISE a credential's ladder against the account log
@@ -24,16 +28,90 @@
24
28
  * one-entry forget -- live in `clientAnnex/ladderAnchored.ts`. What stays
25
29
  * here is the verify-side half every wallet needs regardless of account configuration.
26
30
  */
27
- import { deriveNextKeyHash, updateDID } from '@interop/did-method-webvh';
28
- import { assertCarryOverCommitments, MULTIKEY_COMMITMENT_VM_TYPE, MULTIKEY_VM_TYPE, publishUpdatedLog, readPublishedLog, relationIds, updateKeyMultibase, updateKeySigner, withLogConflictRetry } from '../webvh/didWebvh.js';
29
- import { ladderVmIds } from '../webvh/listClients.js';
31
+ import { deriveNextKeyHash } from '@interop/did-method-webvh';
32
+ import { MULTIKEY_COMMITMENT_VM_TYPE, MULTIKEY_VM_TYPE, ladderVerificationMethod, readPublishedLogOrThrow, withLogConflictRetry } from '../webvh/didWebvh.js';
33
+ import { signAccountEntry } from '../webvh/accountEntry.js';
34
+ import { preEntryProjectionPublisher } from '../webvh/didWebProjection.js';
35
+ import { ladderVmIds, relationIds } from '../resourceLog/document.js';
30
36
  // The one deliberate base-side dependency on the annex subpath, pinned as an
31
- // exception in the lint rule: `removeUnlockKey` resolves the retired
32
- // credential's CURRENT ladder inventory from the log itself (the shared
33
- // attribution helpers in `clientAnnex/ladder.ts`), never touching the annex
34
- // log machinery, and derives the credential's ladder VM id from a supplied
35
- // seed so the removal strikes that too.
36
- import { attributeLadderInventory, ladderVmKeyMultibase } from '../clientAnnex/ladder.js';
37
+ // exception in the lint rule: this module resolves a credential's CURRENT
38
+ // ladder inventory from the log itself (the shared attribution helpers in
39
+ // `clientAnnex/ladder.ts`), never touching the annex log machinery, and
40
+ // derives the credential's ladder VM from its seed at the install.
41
+ import { attributeLadderInventory, LadderAttributionError, ladderVmIdsIntroducedWithCredential, ladderVmKeyMultibase } from '../clientAnnex/ladder.js';
42
+ /**
43
+ * The ladder inventory the removal edit resolved from the log diverges from
44
+ * what the caller was told one read earlier. A ceremony that names its
45
+ * expected ladder VM ids (the retirement's stage 0, which hands the same list
46
+ * to the dependent-record re-mint pass) gets this refusal BEFORE the edit is
47
+ * published, so a concurrent ceremony -- or a host serving different log
48
+ * versions to the two reads -- cannot make the strike diverge from what the
49
+ * re-mint pass acted on. Nothing is written. Matched on `name` (the error
50
+ * crosses app-injected seams that may resolve to another copy of this
51
+ * package).
52
+ */
53
+ export class LadderInventoryDriftError extends Error {
54
+ expected;
55
+ attributed;
56
+ constructor({ expected, attributed }) {
57
+ super('did:webvh: the published log attributes a different ladder VM set to ' +
58
+ 'this credential than the caller resolved a moment earlier ' +
59
+ `(expected ${expected.join(', ') || '(none)'}; attributed ` +
60
+ `${attributed.join(', ') || '(none)'}); the inventory edit was not ` +
61
+ 'published. Re-run the retirement on a fresh read.');
62
+ this.name = 'LadderInventoryDriftError';
63
+ this.expected = expected;
64
+ this.attributed = attributed;
65
+ }
66
+ }
67
+ /**
68
+ * A retirement whose ladder attribution could not claim the retired
69
+ * credential's ladder VM, refused with nothing written. The shape is the
70
+ * seedless strike claiming nothing: the credential still stands in the
71
+ * document, the walk struck no ladder VM, and ladder VMs stand there that it
72
+ * could not claim. A leftover VM would keep the retired credential's
73
+ * delegation authority alive -- under `capabilityDelegation` it can still
74
+ * sign a DELETE-only capability on the account Space -- and nothing
75
+ * downstream can tell such a leftover from a sibling credential's standing
76
+ * VM, so the retirement is the one place the state can be closed
77
+ * (`decisions/0015`).
78
+ *
79
+ * `unclaimedLadderVmIds` names every ladder VM the walk left unclaimed, and
80
+ * `anchorKeyMultibase` the update key the walk was anchored on (a recovery
81
+ * code's revocation anchors on the rung-0 multibase the registry recorded at
82
+ * issuance). On a
83
+ * multi-credential account that list carries the siblings' VMs beside the
84
+ * retired credential's, since telling them apart is exactly what the walk
85
+ * could not do. `retryableWithLadderSeed` says whether a retry supplying the
86
+ * credential's ladder seed can let attribution succeed. The gate raises this
87
+ * error only from a seedless claim (a seeded one either strikes the derived
88
+ * VM or proves there is none), so the hint is `true` from this library; the
89
+ * member is the wallets' read for the retry they offer.
90
+ * Matched on `name` (the error crosses app-injected seams that may resolve
91
+ * to another copy of this package).
92
+ */
93
+ export class UnclaimedLadderVmRetirementError extends Error {
94
+ unclaimedLadderVmIds;
95
+ retryableWithLadderSeed;
96
+ anchorKeyMultibase;
97
+ constructor({ unclaimedLadderVmIds, retryableWithLadderSeed, anchorKeyMultibase }) {
98
+ super("did:webvh: the retirement cannot claim the retired credential's ladder " +
99
+ `VM (standing unclaimed: ${unclaimedLadderVmIds.join(', ')}` +
100
+ (anchorKeyMultibase === undefined
101
+ ? ''
102
+ : `; anchored on ${anchorKeyMultibase}`) +
103
+ '); nothing was published and the credential still stands. ' +
104
+ (retryableWithLadderSeed
105
+ ? "Retry with the credential's ladder seed in hand."
106
+ : 'No retry with the ladder seed can claim it.'));
107
+ this.name = 'UnclaimedLadderVmRetirementError';
108
+ this.unclaimedLadderVmIds = unclaimedLadderVmIds;
109
+ this.retryableWithLadderSeed = retryableWithLadderSeed;
110
+ if (anchorKeyMultibase !== undefined) {
111
+ this.anchorKeyMultibase = anchorKeyMultibase;
112
+ }
113
+ }
114
+ }
37
115
  /**
38
116
  * The verification-method id a credential's key-agreement entry publishes
39
117
  * under: `<did>#<multibase>` for a verbatim key (indistinguishable by id from
@@ -51,6 +129,223 @@ export function unlockKeyVmId({ did, keyAgreement }) {
51
129
  : keyAgreement.commitment;
52
130
  return `${did}#${fragment}`;
53
131
  }
132
+ /**
133
+ * What of a standing credential's ladder currently stands in the published
134
+ * log, resolved with the recorded update key as ANCHOR and the credential's
135
+ * own key-agreement id as the attribution's second arm. The removal edit uses
136
+ * it to know what to strike; the retirement ceremony uses it one stage
137
+ * earlier, to name the ladder VM it is about to strike to the pass that
138
+ * re-mints whatever that VM signed for other credentials.
139
+ *
140
+ * It lives here rather than in the ceremony because this module is the one
141
+ * base-side holder of the annex attribution helpers (the pinned lint
142
+ * exception).
143
+ *
144
+ * @param options {object}
145
+ * @param options.log {DIDLog} a resolved, caller-verified log
146
+ * @param options.did {string} the account DID the log resolves to
147
+ * @param options.unlockKeys {StandingUnlockKeys} the credential's recorded
148
+ * public inventory
149
+ * @param [options.ladderSeed] {Uint8Array} the credential's ladder seed,
150
+ * when the ceremony holds it
151
+ * @returns {Promise<LadderStandingInventory>}
152
+ */
153
+ export async function attributeUnlockLadderInventory({ log, did, unlockKeys, ladderSeed }) {
154
+ return attributeLadderInventory({
155
+ log,
156
+ anchorKeyMultibase: unlockKeys.updateKeyMultibase,
157
+ credentialVmId: unlockKeyVmId({
158
+ did,
159
+ keyAgreement: unlockKeys.keyAgreement
160
+ }),
161
+ ...(ladderSeed ? { ladderSeed } : {})
162
+ });
163
+ }
164
+ /**
165
+ * What of the document's ladder VMs a credential's attribution claims: the
166
+ * VM its seed derives (when the ceremony holds one), plus every VM the log
167
+ * attributes to its ladder. Resolved once here and shared by the removal
168
+ * edit, the retirement ceremony's pre-edit stage, and the read-only
169
+ * pre-flight, so the three agree on what is struck and what is left.
170
+ *
171
+ * `struck` is what the removal edit strikes: the derived id when it stands,
172
+ * and the attributed ids. `unclaimed` is every ladder VM standing in the
173
+ * document that neither the seed nor the attribution claims -- on a
174
+ * multi-credential account, the siblings' VMs at least. A supplied seed also
175
+ * cross-checks the attribution: a log attributing a VM the seed does not
176
+ * derive refuses with {@link LadderAttributionError}.
177
+ *
178
+ * @param options {object}
179
+ * @param options.doc {DIDDoc} the document the attribution ran over
180
+ * @param options.did {string} the account DID
181
+ * @param options.inventory {LadderStandingInventory} the credential's
182
+ * attributed ladder inventory ({@link attributeUnlockLadderInventory})
183
+ * @param [options.ladderSeed] {Uint8Array} the credential's ladder seed
184
+ * @returns {Promise<{ ladderVmId?: string, struck: string[], unclaimed:
185
+ * string[] }>} the seed-derived VM id when a seed was held
186
+ */
187
+ export async function ladderVmClaimOf({ doc, did, inventory, ladderSeed }) {
188
+ const ladderVmId = ladderSeed
189
+ ? `${did}#${await ladderVmKeyMultibase({ ladderSeed })}`
190
+ : undefined;
191
+ if (ladderVmId !== undefined) {
192
+ const foreign = inventory.ladderVmIds.filter(id => id !== ladderVmId);
193
+ if (foreign.length > 0) {
194
+ throw new LadderAttributionError("The published log attributes a ladder VM this credential's seed " +
195
+ 'does not derive; refusing to strike on an ambiguous ' +
196
+ 'attribution.');
197
+ }
198
+ }
199
+ const standing = ladderVmIds({ doc });
200
+ const struck = new Set();
201
+ if (ladderVmId !== undefined && standing.includes(ladderVmId)) {
202
+ struck.add(ladderVmId);
203
+ }
204
+ for (const id of inventory.ladderVmIds) {
205
+ struck.add(id);
206
+ }
207
+ const claimed = new Set([
208
+ ...struck,
209
+ ...(ladderVmId !== undefined ? [ladderVmId] : [])
210
+ ]);
211
+ return {
212
+ ...(ladderVmId !== undefined ? { ladderVmId } : {}),
213
+ struck: [...struck],
214
+ unclaimed: standing.filter(id => !claimed.has(id))
215
+ };
216
+ }
217
+ /**
218
+ * The retirement gate (`decisions/0015`): refuses, with
219
+ * {@link UnclaimedLadderVmRetirementError}, a retirement of a
220
+ * ladder-carrying credential whose SEEDLESS claim struck nothing while a
221
+ * ladder VM that COULD BE THIS CREDENTIAL'S stands unclaimed and the
222
+ * credential itself still stands in the document. Deliberately narrower than
223
+ * "`unclaimed` is non-empty", which every retirement on a healthy
224
+ * multi-credential account produces, and narrower again than the standing
225
+ * unclaimed set: a sibling credential's VM is nothing this retirement could
226
+ * leave behind.
227
+ *
228
+ * Which unclaimed VMs are candidates is
229
+ * {@link ladderVmIdsIntroducedWithCredential}'s question, read off the log's
230
+ * entry shapes rather than off an attribution that has already refused: a VM
231
+ * introduced by the entry that introduced this credential's `keyAgreement`
232
+ * member, or by the entry that committed or authorized its anchor (the split
233
+ * bind, whose key and authority entries sit two versions apart). None, and
234
+ * the credential never had a VM to leave standing, so the retirement
235
+ * completes and strikes what it can. That is the torn issuance's orphan -- a
236
+ * credential with a `keyAgreement` member, no ladder VM, and no committed
237
+ * rung -- which previously could never be removed at all, since a sibling's
238
+ * standing VM refused it forever.
239
+ *
240
+ * Two shapes pass ahead of that. A credential whose `keyAgreement` member is
241
+ * already gone is a completed retirement re-running. And a claim resolved
242
+ * WITH the seed never refuses: the derived VM is either standing (and
243
+ * struck) or absent, and an absent derived VM is proof the credential has
244
+ * nothing to claim -- the last-client transition torn between its strike and
245
+ * reinstall entries leaves exactly that, and a seeded retirement there must
246
+ * complete rather than wait on the transition's re-run. So the error's retry
247
+ * hint is `true` whenever this gate raises it.
248
+ *
249
+ * The caller decides whether the credential carries a ladder.
250
+ *
251
+ * @param options {object}
252
+ * @param options.log {DIDLog} the verified log the claim was resolved over
253
+ * @param options.doc {DIDDoc} the document the claim was resolved over
254
+ * @param options.credentialVmId {string} the credential's `keyAgreement`
255
+ * verification-method id ({@link unlockKeyVmId})
256
+ * @param options.claim {{ ladderVmId?: string, struck: string[], unclaimed:
257
+ * string[] }} from {@link ladderVmClaimOf}
258
+ * @param [options.anchorKeyMultibase] {string} the update key the walk was
259
+ * anchored on: the candidate reading's second anchor form, and named in
260
+ * the refusal
261
+ * @returns {Promise<void>}
262
+ */
263
+ export async function assertLadderVmClaimed({ log, doc, credentialVmId, claim, anchorKeyMultibase }) {
264
+ const credentialStands = (doc.verificationMethod ?? []).some(method => method.id === credentialVmId);
265
+ if (!credentialStands ||
266
+ claim.ladderVmId !== undefined ||
267
+ claim.struck.length > 0 ||
268
+ claim.unclaimed.length === 0) {
269
+ return;
270
+ }
271
+ const candidates = await ladderVmIdsIntroducedWithCredential({
272
+ log,
273
+ credentialVmId,
274
+ ...(anchorKeyMultibase !== undefined ? { anchorKeyMultibase } : {})
275
+ });
276
+ const unclaimed = claim.unclaimed.filter(id => candidates.includes(id));
277
+ if (unclaimed.length === 0) {
278
+ return;
279
+ }
280
+ throw new UnclaimedLadderVmRetirementError({
281
+ unclaimedLadderVmIds: unclaimed,
282
+ retryableWithLadderSeed: true,
283
+ ...(anchorKeyMultibase !== undefined ? { anchorKeyMultibase } : {})
284
+ });
285
+ }
286
+ /**
287
+ * The retirement gate run read-only, before anything is written: one pinned
288
+ * read of the account log, the credential's ladder attribution, and
289
+ * {@link assertLadderVmClaimed} over the result. A caller that establishes a
290
+ * replacement credential before it retires the old one (a passphrase change,
291
+ * a tap-confirmed passkey removal) runs this first, so a gate refusal lands
292
+ * the way an invalid-input check does -- nothing established, no
293
+ * pending-shaped registry entry written -- rather than after establishment,
294
+ * where the refusal would leave a torn state no seedless repair can clear.
295
+ * The in-ceremony gate stays as defense in depth, and it is what answers a
296
+ * log entry landing between the pre-flight and the retirement: the
297
+ * pre-flight's verdict holds for the head it read, and nothing binds the two
298
+ * reads.
299
+ *
300
+ * @param options {object}
301
+ * @param options.idStore {UnlockLogStore} the account's `id` collection
302
+ * read side
303
+ * @param options.unlockKeys {StandingUnlockKeys} the credential's recorded
304
+ * public inventory
305
+ * @param [options.ladderSeed] {Uint8Array} the credential's ladder seed,
306
+ * when the caller holds it
307
+ * @param [options.expectedDid] {string} the account DID the log must
308
+ * resolve to
309
+ * @param [options.pinStore] {ResourceLogPinStore} the caller's chain-head
310
+ * pins
311
+ * @param [options.logId] {string} the account log's pin slot; required
312
+ * whenever a `pinStore` is supplied
313
+ * @returns {Promise<LadderVmRemovalReport>} what the retirement would
314
+ * strike and what it would leave unclaimed
315
+ */
316
+ export async function preflightUnlockCredentialRetirement({ idStore, unlockKeys, ladderSeed, expectedDid, pinStore, logId }) {
317
+ const published = await readPublishedLogOrThrow({
318
+ idStore,
319
+ ...(expectedDid !== undefined ? { expectedDid } : {}),
320
+ ...(pinStore ? { pinStore } : {}),
321
+ ...(logId !== undefined ? { logId } : {}),
322
+ missingMessage: 'did:webvh: did.jsonl is missing; nothing to retire from.'
323
+ });
324
+ const { did, doc } = published;
325
+ const inventory = await attributeUnlockLadderInventory({
326
+ log: published.log,
327
+ did,
328
+ unlockKeys,
329
+ ...(ladderSeed ? { ladderSeed } : {})
330
+ });
331
+ const claim = await ladderVmClaimOf({
332
+ doc,
333
+ did,
334
+ inventory,
335
+ ...(ladderSeed ? { ladderSeed } : {})
336
+ });
337
+ await assertLadderVmClaimed({
338
+ log: published.log,
339
+ doc,
340
+ credentialVmId: unlockKeyVmId({
341
+ did,
342
+ keyAgreement: unlockKeys.keyAgreement
343
+ }),
344
+ claim,
345
+ anchorKeyMultibase: unlockKeys.updateKeyMultibase
346
+ });
347
+ return { struck: claim.struck, unclaimed: claim.unclaimed };
348
+ }
54
349
  /**
55
350
  * The credential's `keyAgreement` verification method: an ordinary unmarked
56
351
  * entry carrying either the key verbatim (a `Multikey` with
@@ -84,50 +379,46 @@ export function unlockKeyVerificationMethod({ did, keyAgreement }) {
84
379
  publicKeyCommitment: keyAgreement.commitment
85
380
  };
86
381
  }
87
- /**
88
- * Reads and resolves the published log through the narrow store seam.
89
- *
90
- * @param options {object}
91
- * @param options.store {UnlockLogStore}
92
- * @param [options.expectedDid] {string} the account DID the log must resolve
93
- * to, where the caller holds one
94
- * @param [options.pinStore] {ResourceLogPinStore} the caller's chain-head
95
- * pins; a served log that is a rollback, a fork, or an identity switch
96
- * against the pinned head is refused (`ResourceLogContinuityError`)
97
- * @param [options.logId] {string} the account log's pin slot
98
- * (`accountLogPinId({ spaceId })`); required whenever a `pinStore` is
99
- * supplied
100
- * @returns {Promise<PublishedWebvhLog>}
101
- */
102
- export async function readLogOrThrow({ store, expectedDid, pinStore, logId }) {
103
- // readPublishedLog only calls getIdResourceRaw, so the narrow seam is safe.
104
- const published = await readPublishedLog({
105
- idStore: store,
106
- ...(expectedDid !== undefined ? { expectedDid } : {}),
107
- ...(pinStore ? { pinStore } : {}),
108
- ...(logId !== undefined ? { logId } : {})
109
- });
110
- if (!published) {
111
- throw new Error('did:webvh: did.jsonl is missing; nothing to enroll into.');
112
- }
113
- return published;
114
- }
115
382
  /**
116
383
  * BIND (run by an enrolled client, root authority): publishes a standing
117
384
  * credential's split configuration into the document -- one entry adding the
118
- * credential's `keyAgreement` entry (verbatim or commitment) and committing
119
- * its current update key's hash in `nextKeyHashes`. The update key joins
120
- * `updateKeys` nowhere. Idempotent: an inventory already published is a no-op,
121
- * so re-running a torn bind converges. The entry publishes conditionally on
122
- * the log this call read; a race lost to a concurrent ceremony re-runs and
123
- * rebases on the new head.
385
+ * credential's `keyAgreement` entry (verbatim or commitment), committing its
386
+ * current update key's hash in `nextKeyHashes`, and installing its LADDER VM
387
+ * under `assertionMethod` and `capabilityDelegation`, and under no other
388
+ * relation -- the asymmetry that recognizes it. The update key joins `updateKeys` nowhere.
389
+ * One entry for the whole inventory: a separate install would open a window
390
+ * in which the credential stands without the key it signs with.
391
+ *
392
+ * The ladder seed is the CALLER's to mint and to have already written
393
+ * durably. This function never mints one, so a torn bind's re-run tests
394
+ * idempotence against the SAME seed, finds the completed stage and publishes
395
+ * nothing -- where a mint-when-absent would publish a second VM that no
396
+ * anchored attribution could later strike. A credential with no ladder at all
397
+ * passes `null` and gets no VM.
398
+ *
399
+ * `part` splits the bind across two entries where a ceremony needs the
400
+ * credential's decryption material to precede its authority: `'key'`
401
+ * publishes the `keyAgreement` member alone, `'authority'` installs the
402
+ * ladder VM and commits the rung-0 hash, and `'all'` (the default) is the one
403
+ * merged entry every other caller writes.
404
+ *
405
+ * Idempotent: an inventory already published is a no-op, so re-running a torn
406
+ * bind converges. The entry publishes conditionally on the log this call
407
+ * read; a race lost to a concurrent ceremony re-runs and rebases on the new
408
+ * head.
124
409
  *
125
410
  * @param options {object}
126
411
  * @param options.idStore {WebvhIdStore}
127
- * @param options.updateKeys {ClientWebvhUpdateKeys} the BINDING client's own
128
- * did:webvh update-key seeds
412
+ * @param options.signer {AccountLogSigner} who signs the entry: the BINDING
413
+ * client's own did:webvh update-key seeds, or the acting credential's
414
+ * ladder seed
129
415
  * @param options.unlockKeys {StandingUnlockKeys} the credential's public
130
416
  * inventory
417
+ * @param options.ladderSeed {Uint8Array | null} the credential's ladder
418
+ * seed, whose VM this entry installs; `null` for a credential that carries
419
+ * no ladder
420
+ * @param [options.part] {string} `'all'` (the default), `'key'`, or
421
+ * `'authority'` -- see above
131
422
  * @param [options.expectedDid] {string} the account DID the log must resolve
132
423
  * to, from the caller's stored account pointer
133
424
  * @param [options.pinStore] {ResourceLogPinStore} the caller's chain-head
@@ -163,25 +454,70 @@ export async function publishUnlockKey(options) {
163
454
  * rung key still sitting in `updateKeys` (plus the never-claimed hashes its
164
455
  * reveal entry committed). Trusting the recorded multibase alone would leave
165
456
  * the live rung commitment standing: a latent re-seizure credential via the
166
- * reveal mechanism. A supplied `ladderSeed` strengthens the attribution (every
167
- * rung known a priori, independent of the anchor's staleness); without it the
168
- * log walk alone resolves the inventory. For a single-key credential (a
457
+ * reveal mechanism. The seedless walk recovers the rungs BEHIND the anchor
458
+ * too, reading the log's positional rules backwards, so an anchor advanced by
459
+ * a self-enrollment resolves the same inventory a bind-time anchor does
460
+ * wherever each rung's hash was committed by an entry that also revealed the
461
+ * previous rung, or by a handover. One reachable shape falls outside that: a
462
+ * ladder VM the last-client transition reinstalled, whose acting rung a later
463
+ * self-enrollment then spends. That reveal-and-commit entry authorizes no key,
464
+ * so the backward walk cannot name the rung that signed it, and the VM stays
465
+ * standing as `unclaimed` (WC-158). A supplied `ladderSeed` is then a shortcut
466
+ * and a cross-check rather than a requirement (every rung known outright, no
467
+ * backward walk). For a
468
+ * single-key credential (a
169
469
  * recovery code, a never-self-enrolled bind) the resolution degenerates to
170
470
  * exactly the recorded key's hash, as before.
171
471
  *
172
- * The seed also names the credential's LADDER VM -- the stable sibling a
173
- * last-client forget's install entry publishes under `assertionMethod` and
174
- * `capabilityDelegation`, left standing by a forget torn after that entry --
175
- * and the removal strikes it in the same entry, so a retired seed no longer
176
- * signs governed-log appends or account delegations. The sibling is derived
177
- * from the seed alone, with nothing in the log attributing it to a ladder, so
178
- * a removal without the seed leaves it: rotation is the remedy for a leaked
179
- * seed exactly when the ceremony holds the credential.
472
+ * The credential's LADDER VM goes in the same entry, so a retired credential
473
+ * no longer signs governed-log appends or account delegations. This is the
474
+ * sole remover, and it needs no seed to do it: the VM is attributed from the
475
+ * log on any of three arms ({@link attributeLadderInventory}). The SIGNER
476
+ * arm claims a VM whose publishing entry a ladder rung signed. The
477
+ * CO-INTRODUCTION arm claims one whose publishing entry also introduced this
478
+ * credential's own `keyAgreement` member, which is what reaches a bind entry
479
+ * an enrolled client signed; it fires only when that entry introduced exactly
480
+ * one credential-class key-agreement member and exactly one ladder VM. The
481
+ * COMMITMENT arm claims one whose publishing entry committed a hash this
482
+ * ladder knows a priori and introduced no other credential's member, which is
483
+ * what reaches a reinstall for a credential whose member already stands. A VM
484
+ * no arm claims is left standing rather than struck -- on an account
485
+ * with several standing credentials, striking an unattributed key would take
486
+ * out a survivor's. With the seed in hand the derived id is struck too, and
487
+ * an attribution naming any OTHER VM refuses with
488
+ * {@link LadderAttributionError} rather than acting on a ladder the seed and
489
+ * the recorded anchor disagree about.
180
490
  *
181
- * @param options {object} see {@link publishUnlockKey}, plus
182
- * `[ladderSeed]` -- the retired credential's ladder seed, when in hand
183
- * @returns {Promise<{ did: string, doc: DIDDoc, log: DIDLog }>} see
184
- * {@link publishUnlockKey}
491
+ * @param options {object} see {@link publishUnlockKey}, plus:
492
+ * @param [options.ladderSeed] {Uint8Array} the retired credential's ladder
493
+ * seed, when in hand
494
+ * @param [options.projectionStore] {object} an `id`-collection store the
495
+ * caller may write through (a transient session's, bound to its generation
496
+ * delegation; an enrolled client's own root-invoking store). Supplied, the
497
+ * post-strike `did:web` projection is PUT through it immediately BEFORE
498
+ * this entry publishes, which is what keeps a ladder-signed removal from
499
+ * leaving `did.json` naming the retired credential. Best-effort: a failed
500
+ * PUT is warned and the removal proceeds. Omitted, the ladder arm leaves
501
+ * the projection to the next visit's `ensureDidWebProjection` and the
502
+ * client arm publishes it after the entry, as before
503
+ * @param [options.expectedLadderVmIds] {string[]} the ladder VM ids the
504
+ * caller already resolved for this credential, from its own read of the
505
+ * log. Supplied, this edit's own attribution must match them as a set --
506
+ * after the seed cross-check above -- or the edit refuses with
507
+ * {@link LadderInventoryDriftError} before writing anything. That is what
508
+ * ties the retirement's stage-0 read (whose list the dependent-record
509
+ * re-mint pass acted on) to this one, which is otherwise independent
510
+ * @param [options.requireLadderVmClaim] {boolean} the credential carries a
511
+ * ladder, so its VM must be claimed: the edit refuses with
512
+ * {@link UnclaimedLadderVmRetirementError} before writing when the claim
513
+ * struck nothing while ladder VMs stand unclaimed and the credential still
514
+ * stands ({@link assertLadderVmClaimed}). The retirement ceremony sets it,
515
+ * and so does a recovery code's removal, whose inventory carries the code's
516
+ * own ladder VM. The refusal names the recorded update key the walk was
517
+ * anchored on
518
+ * @returns {Promise<{ did: string, doc: DIDDoc, log: DIDLog, ladderVm:
519
+ * LadderVmRemovalReport }>} see {@link publishUnlockKey}, plus the ladder
520
+ * VM report: what this entry struck, and what stands unclaimed after it
185
521
  */
186
522
  export async function removeUnlockKey(options) {
187
523
  return withLogConflictRetry(() => setUnlockKeyInventoryOnce({ ...options, polarity: 'remove' }));
@@ -195,108 +531,216 @@ export async function removeUnlockKey(options) {
195
531
  * @param options {object} see {@link publishUnlockKey}, plus `polarity`
196
532
  * @returns {Promise<{ did: string, doc: DIDDoc, log: DIDLog }>}
197
533
  */
198
- async function setUnlockKeyInventoryOnce({ idStore, updateKeys, unlockKeys, ladderSeed, expectedDid, pinStore, logId, verb, polarity }) {
199
- const published = await readLogOrThrow({
200
- store: idStore,
534
+ async function setUnlockKeyInventoryOnce({ idStore, signer, projectionStore, unlockKeys, ladderSeed, part = 'all', expectedLadderVmIds, requireLadderVmClaim, expectedDid, pinStore, logId, verb, polarity }) {
535
+ let ladderVmReport = { struck: [], unclaimed: [] };
536
+ const outcome = await signAccountEntry({
537
+ idStore,
538
+ signer,
201
539
  ...(expectedDid !== undefined ? { expectedDid } : {}),
202
540
  ...(pinStore ? { pinStore } : {}),
203
- ...(logId !== undefined ? { logId } : {})
541
+ ...(logId !== undefined ? { logId } : {}),
542
+ missingMessage: 'did:webvh: did.jsonl is missing; nothing to enroll into.',
543
+ verb: verb ?? 'changing an unlock credential',
544
+ // The post-strike projection, published while the caller's store can
545
+ // still write it and before the entry the ladder arm cannot publish it
546
+ // with. Only the removal polarity is ever handed one: a publish adds
547
+ // inventory, which the served projection under-lists until some later
548
+ // writer refreshes it -- the safe direction.
549
+ ...(projectionStore
550
+ ? {
551
+ beforePublish: preEntryProjectionPublisher({
552
+ store: projectionStore
553
+ })
554
+ }
555
+ : {}),
556
+ build: async ({ published }) => {
557
+ const { did, doc } = published;
558
+ const keyHash = await deriveNextKeyHash(unlockKeys.updateKeyMultibase);
559
+ const vmId = unlockKeyVmId({ did, keyAgreement: unlockKeys.keyAgreement });
560
+ const vmPresent = (doc.verificationMethod ?? []).some(method => method.id === vmId);
561
+ // The remove polarity strikes the ladder's CURRENT inventory, resolved
562
+ // from the log with the recorded key as anchor -- never just the
563
+ // recorded key's hash, which a self-enrollment since the bind leaves
564
+ // stale (see {@link removeUnlockKey}). The credential's own
565
+ // verification-method id goes along: it is what tells the walk a climb
566
+ // from a spend, so the removal never annexes the commitment a spend
567
+ // handed to its replacement.
568
+ const inventory = polarity === 'remove'
569
+ ? await attributeUnlockLadderInventory({
570
+ log: published.log,
571
+ did,
572
+ unlockKeys,
573
+ ...(ladderSeed ? { ladderSeed } : {})
574
+ })
575
+ : { revealedKeys: [], committedHashes: [], ladderVmIds: [] };
576
+ const removedHashes = new Set(inventory.committedHashes);
577
+ const removedKeys = new Set(inventory.revealedKeys);
578
+ // The credential's ladder VM: installed by the publish polarity in this
579
+ // same entry, struck by the remove polarity in this same entry. The
580
+ // derived id is what the install publishes; the removal takes it from
581
+ // the seed when the ceremony holds one and from the log's attribution
582
+ // otherwise, so a seedless retirement still ends the credential's
583
+ // delegation authority.
584
+ const ladderVmKey = ladderSeed
585
+ ? await ladderVmKeyMultibase({ ladderSeed })
586
+ : undefined;
587
+ const ladderVmId = ladderVmKey === undefined ? undefined : `${did}#${ladderVmKey}`;
588
+ const standingLadderVmIds = ladderVmIds({ doc });
589
+ const claim = polarity === 'remove'
590
+ ? await ladderVmClaimOf({
591
+ doc,
592
+ did,
593
+ inventory,
594
+ ...(ladderSeed ? { ladderSeed } : {})
595
+ })
596
+ : { struck: [], unclaimed: [] };
597
+ if (polarity === 'remove') {
598
+ // The drift check, before any write: this edit's own attribution
599
+ // against the list the caller resolved a read earlier. A concurrent
600
+ // ceremony, or a host serving two different log versions to the two
601
+ // reads, would otherwise let the strike diverge from what the
602
+ // caller's dependent-record pass already acted on.
603
+ if (expectedLadderVmIds !== undefined) {
604
+ const attributed = new Set(inventory.ladderVmIds);
605
+ const expected = new Set(expectedLadderVmIds);
606
+ const sameSet = attributed.size === expected.size &&
607
+ [...expected].every(id => attributed.has(id));
608
+ if (!sameSet) {
609
+ throw new LadderInventoryDriftError({
610
+ expected: [...expected],
611
+ attributed: [...attributed]
612
+ });
613
+ }
614
+ }
615
+ // The retirement gate, before any write and after the drift check: a
616
+ // ladder-carrying credential whose claim struck nothing while ladder
617
+ // VMs stand unclaimed is refused rather than retired with its VM left
618
+ // standing.
619
+ if (requireLadderVmClaim) {
620
+ await assertLadderVmClaimed({
621
+ log: published.log,
622
+ doc,
623
+ credentialVmId: vmId,
624
+ claim,
625
+ anchorKeyMultibase: unlockKeys.updateKeyMultibase
626
+ });
627
+ }
628
+ }
629
+ const struckLadderVmIds = new Set(claim.struck);
630
+ const ladderVmPresent = polarity === 'publish'
631
+ ? ladderVmId !== undefined && standingLadderVmIds.includes(ladderVmId)
632
+ : struckLadderVmIds.size > 0;
633
+ const struckIds = new Set([vmId, ...struckLadderVmIds]);
634
+ const hashCommitted = published.nextKeyHashes.includes(keyHash);
635
+ // What this entry is responsible for, by `part`: the key half publishes
636
+ // the `keyAgreement` member alone, the authority half the ladder VM and
637
+ // the rung's commitment, and the default entry both.
638
+ const publishesKey = part === 'all' || part === 'key';
639
+ const publishesAuthority = part === 'all' || part === 'authority';
640
+ const settled = polarity === 'publish'
641
+ ? (!publishesKey || vmPresent) &&
642
+ (!publishesAuthority ||
643
+ (hashCommitted && (ladderVmId === undefined || ladderVmPresent)))
644
+ : !vmPresent &&
645
+ !ladderVmPresent &&
646
+ removedHashes.size === 0 &&
647
+ removedKeys.size === 0;
648
+ ladderVmReport = {
649
+ struck: claim.struck,
650
+ unclaimed: claim.unclaimed
651
+ };
652
+ if (settled) {
653
+ return undefined;
654
+ }
655
+ const existingMethods = (doc.verificationMethod ??
656
+ []);
657
+ // The publish polarity commits the credential's update-key hash through
658
+ // the seam's `commitHashes`, so on a ladder-signed entry it lands after
659
+ // the acting rung's own carry-over hash (`decisions/0007` order).
660
+ const commitHashes = polarity === 'publish' && publishesAuthority && !hashCommitted
661
+ ? [keyHash]
662
+ : [];
663
+ const nextKeyHashes = polarity === 'publish'
664
+ ? undefined
665
+ : published.nextKeyHashes.filter(hash => !removedHashes.has(hash));
666
+ // A torn self-enrollment leaves a revealed rung in `updateKeys`; the
667
+ // remove polarity strikes it in the same entry as its hash, keeping the
668
+ // carry-over invariant self-consistent. On the publish polarity and the
669
+ // ordinary committed-only removal this is the published set unchanged.
670
+ const statedUpdateKeys = polarity === 'publish'
671
+ ? undefined
672
+ : published.updateKeys.filter(key => !removedKeys.has(key));
673
+ const installedLadderVmKey = publishesAuthority ? ladderVmKey : undefined;
674
+ const verificationMethods = polarity === 'publish'
675
+ ? [
676
+ ...existingMethods.filter(method => method.id !== (publishesKey ? vmId : undefined) &&
677
+ method.id !== (publishesAuthority ? ladderVmId : undefined)),
678
+ ...(publishesKey
679
+ ? [
680
+ unlockKeyVerificationMethod({
681
+ did,
682
+ keyAgreement: unlockKeys.keyAgreement
683
+ })
684
+ ]
685
+ : []),
686
+ ...(installedLadderVmKey !== undefined
687
+ ? [
688
+ ladderVerificationMethod({
689
+ controller: did,
690
+ publicKeyMultibase: installedLadderVmKey
691
+ })
692
+ ]
693
+ : [])
694
+ ]
695
+ : existingMethods.filter(method => method.id === undefined || !struckIds.has(method.id));
696
+ // On the remove polarity every relation drops the struck ids: the
697
+ // credential's entry sits under `keyAgreement` alone and the ladder VM
698
+ // under `assertionMethod` and `capabilityDelegation` alone, so one
699
+ // filter serves all five without restating either placement here.
700
+ const relation = (ids) => polarity === 'publish'
701
+ ? relationIds(ids)
702
+ : relationIds(ids).filter(id => !struckIds.has(id));
703
+ const keyAgreement = polarity === 'publish'
704
+ ? publishesKey
705
+ ? [...new Set([...relationIds(doc.keyAgreement), vmId])]
706
+ : relationIds(doc.keyAgreement)
707
+ : relation(doc.keyAgreement);
708
+ // The ladder VM's two relations, and only those: the asymmetry is what
709
+ // recognizes it (`ladderVmIds`) and what keeps it out of every client
710
+ // listing.
711
+ const withLadderVm = (ids) => installedLadderVmKey === undefined || ladderVmId === undefined
712
+ ? relationIds(ids)
713
+ : [...new Set([...relationIds(ids), ladderVmId])];
714
+ const assertionMethod = polarity === 'publish'
715
+ ? withLadderVm(doc.assertionMethod)
716
+ : relation(doc.assertionMethod);
717
+ const capabilityDelegation = polarity === 'publish'
718
+ ? withLadderVm(doc.capabilityDelegation)
719
+ : relation(doc.capabilityDelegation);
720
+ return {
721
+ // The byoe context that defines a commitment entry's terms is
722
+ // installed at genesis and carried forward by every update, so no
723
+ // edit re-appends it.
724
+ ...(statedUpdateKeys !== undefined
725
+ ? { updateKeys: statedUpdateKeys }
726
+ : {}),
727
+ ...(nextKeyHashes !== undefined ? { nextKeyHashes } : {}),
728
+ ...(commitHashes.length > 0 ? { commitHashes } : {}),
729
+ verificationMethods,
730
+ authentication: relation(doc.authentication),
731
+ assertionMethod,
732
+ keyAgreement,
733
+ capabilityInvocation: relation(doc.capabilityInvocation),
734
+ capabilityDelegation
735
+ };
736
+ }
204
737
  });
205
- const { did, doc } = published;
206
- const keyHash = await deriveNextKeyHash(unlockKeys.updateKeyMultibase);
207
- const vmId = unlockKeyVmId({ did, keyAgreement: unlockKeys.keyAgreement });
208
- const vmPresent = (doc.verificationMethod ?? []).some(method => method.id === vmId);
209
- // The remove polarity strikes the ladder's CURRENT inventory, resolved from
210
- // the log with the recorded key as anchor -- never just the recorded key's
211
- // hash, which a self-enrollment since the bind leaves stale (see
212
- // {@link removeUnlockKey}). The credential's own verification-method id
213
- // goes along: it is what tells the walk a climb from a spend, so the
214
- // removal never annexes the commitment a spend handed to its replacement.
215
- const inventory = polarity === 'remove'
216
- ? await attributeLadderInventory({
217
- log: published.log,
218
- anchorKeyMultibase: unlockKeys.updateKeyMultibase,
219
- credentialVmId: vmId,
220
- ...(ladderSeed ? { ladderSeed } : {})
221
- })
222
- : { revealedKeys: [], committedHashes: [] };
223
- const removedHashes = new Set(inventory.committedHashes);
224
- const removedKeys = new Set(inventory.revealedKeys);
225
- // The credential's ladder VM, where one stands: the stable sibling a
226
- // last-client forget's install entry publishes, which stays behind when
227
- // that forget is torn after its first entry. It is derived from the ladder
228
- // seed and nothing else, so ownership is attributable only with the seed in
229
- // hand; a removal without it leaves the sibling standing (see
230
- // {@link removeUnlockKey}).
231
- const ladderVmId = polarity === 'remove' && ladderSeed
232
- ? `${did}#${await ladderVmKeyMultibase({ ladderSeed })}`
233
- : undefined;
234
- const ladderVmPresent = ladderVmId !== undefined && ladderVmIds({ doc }).includes(ladderVmId);
235
- const struckIds = new Set([vmId, ...(ladderVmPresent ? [ladderVmId] : [])]);
236
- const hashCommitted = published.nextKeyHashes.includes(keyHash);
237
- const settled = polarity === 'publish'
238
- ? vmPresent && hashCommitted
239
- : !vmPresent &&
240
- !ladderVmPresent &&
241
- removedHashes.size === 0 &&
242
- removedKeys.size === 0;
243
- if (settled) {
244
- return { did, doc, log: published.log };
245
- }
246
- const activeKey = await updateKeyMultibase({ seed: updateKeys.updateSeed });
247
- if (!published.updateKeys.includes(activeKey)) {
248
- throw new Error("did:webvh: the published log does not authorize this client's active " +
249
- 'update key; finalize the pending rotation before ' +
250
- `${verb ?? 'changing an unlock credential'}.`);
251
- }
252
- await assertCarryOverCommitments({ published });
253
- const existingMethods = (doc.verificationMethod ?? []);
254
- const signer = await updateKeySigner({ seed: updateKeys.updateSeed });
255
- const nextKeyHashes = polarity === 'publish'
256
- ? [...new Set([...published.nextKeyHashes, keyHash])]
257
- : published.nextKeyHashes.filter(hash => !removedHashes.has(hash));
258
- // A torn self-enrollment leaves a revealed rung in `updateKeys`; the remove
259
- // polarity strikes it in the same entry as its hash, keeping the carry-over
260
- // invariant self-consistent. On the publish polarity and the ordinary
261
- // committed-only removal this is the published set unchanged.
262
- const statedUpdateKeys = polarity === 'publish'
263
- ? published.updateKeys
264
- : published.updateKeys.filter(key => !removedKeys.has(key));
265
- const verificationMethods = polarity === 'publish'
266
- ? [
267
- ...existingMethods.filter(method => method.id !== vmId),
268
- unlockKeyVerificationMethod({
269
- did,
270
- keyAgreement: unlockKeys.keyAgreement
271
- })
272
- ]
273
- : existingMethods.filter(method => method.id === undefined || !struckIds.has(method.id));
274
- // On the remove polarity every relation drops the struck ids: the
275
- // credential's entry sits under `keyAgreement` alone and the ladder VM under
276
- // `assertionMethod` and `capabilityDelegation` alone, so one filter serves
277
- // all five without restating either placement here.
278
- const relation = (ids) => polarity === 'publish'
279
- ? relationIds(ids)
280
- : relationIds(ids).filter(id => !struckIds.has(id));
281
- const keyAgreement = polarity === 'publish'
282
- ? [...new Set([...relationIds(doc.keyAgreement), vmId])]
283
- : relation(doc.keyAgreement);
284
- const updated = await updateDID({
285
- log: published.log,
286
- signer,
287
- alsoKnownAsWeb: true,
288
- // The byoe context that defines a commitment entry's terms is installed
289
- // at genesis and carried forward by every update, so no edit re-appends it.
290
- updateKeys: statedUpdateKeys,
291
- nextKeyHashes,
292
- verificationMethods,
293
- authentication: relation(doc.authentication),
294
- assertionMethod: relation(doc.assertionMethod),
295
- keyAgreement,
296
- capabilityInvocation: relation(doc.capabilityInvocation),
297
- capabilityDelegation: relation(doc.capabilityDelegation)
298
- });
299
- await publishUpdatedLog({ idStore, updated, ifMatch: published.etag });
300
- return { did: updated.did, doc: updated.doc, log: updated.log };
738
+ const settledHead = outcome.updated ?? outcome.published;
739
+ return {
740
+ did: settledHead.did,
741
+ doc: settledHead.doc,
742
+ log: settledHead.log,
743
+ ladderVm: ladderVmReport
744
+ };
301
745
  }
302
746
  //# sourceMappingURL=standingWebvh.js.map