keri-ts 2.2.4 → 2.2.5

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 (233) hide show
  1. package/esm/keri/npm/src/_dnt.polyfills.js +14 -14
  2. package/esm/keri/npm/src/acdc/registering.js +19 -0
  3. package/esm/keri/npm/src/acdc/salting.js +4 -0
  4. package/esm/keri/npm/src/app/bootstrap/authenticating.js +32 -10
  5. package/esm/keri/npm/src/app/bootstrap/oobiery.js +363 -30
  6. package/esm/keri/npm/src/app/credentials/credential-importing.js +33 -4
  7. package/esm/keri/npm/src/app/credentials/root-replicator.js +27 -15
  8. package/esm/keri/npm/src/app/credentials/schema-resolving.js +5 -1
  9. package/esm/keri/npm/src/app/credentials/tel-publishing.js +16 -7
  10. package/esm/keri/npm/src/app/delivery/forwarding.js +177 -43
  11. package/esm/keri/npm/src/app/delivery/relaying.js +232 -0
  12. package/esm/keri/npm/src/app/delivery/respondant.js +8 -5
  13. package/esm/keri/npm/src/app/exchanges/exchange-messaging.js +1 -1
  14. package/esm/keri/npm/src/app/exchanges/exchanging.js +65 -9
  15. package/esm/keri/npm/src/app/identity/delegating.js +66 -9
  16. package/esm/keri/npm/src/app/identity/habbing.js +190 -71
  17. package/esm/keri/npm/src/app/identity/keeping.js +111 -39
  18. package/esm/keri/npm/src/app/identity/retire-identifier.js +98 -0
  19. package/esm/keri/npm/src/app/ipex/disclosure-replaying.js +116 -0
  20. package/esm/keri/npm/src/app/ipex/group-ipex.js +122 -25
  21. package/esm/keri/npm/src/app/ipex/group-issuance.js +109 -23
  22. package/esm/keri/npm/src/app/ipex/ipex-discloser.js +36 -22
  23. package/esm/keri/npm/src/app/ipex/ipex-grouping.js +53 -3
  24. package/esm/keri/npm/src/app/ipex/ipex-v2.js +7 -2
  25. package/esm/keri/npm/src/app/ipex/issuance.js +131 -35
  26. package/esm/keri/npm/src/app/ipex/pending-presentations.js +4 -2
  27. package/esm/keri/npm/src/app/ipex/presenting.js +3 -18
  28. package/esm/keri/npm/src/app/ipex/receive-issuance.js +2 -1
  29. package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +16 -3
  30. package/esm/keri/npm/src/app/mailbox/agent-inbox.js +292 -0
  31. package/esm/keri/npm/src/app/mailbox/forward-handler.js +298 -52
  32. package/esm/keri/npm/src/app/mailbox/kel-publisher.js +26 -31
  33. package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +70 -62
  34. package/esm/keri/npm/src/app/mailbox/mailboxing.js +20 -4
  35. package/esm/keri/npm/src/app/multisig/counseling.js +71 -27
  36. package/esm/keri/npm/src/app/multisig/group-members.js +41 -5
  37. package/esm/keri/npm/src/app/multisig/grouping.js +50 -2
  38. package/esm/keri/npm/src/app/multisig/multisig-workflows.js +3 -18
  39. package/esm/keri/npm/src/app/multisig/proposing.js +1 -16
  40. package/esm/keri/npm/src/app/multisig/rejoin-group.js +99 -0
  41. package/esm/keri/npm/src/app/multisig/replace-member.js +29 -8
  42. package/esm/keri/npm/src/app/protocol/parsering.js +45 -17
  43. package/esm/keri/npm/src/app/protocol/protocol-processing.js +47 -21
  44. package/esm/keri/npm/src/app/queries/query-replies.js +0 -22
  45. package/esm/keri/npm/src/app/queries/relay-refresh.js +81 -0
  46. package/esm/keri/npm/src/app/queries/witness-inquisitor.js +180 -34
  47. package/esm/keri/npm/src/app/runtime/agent-runtime.js +41 -72
  48. package/esm/keri/npm/src/app/runtime/cue-runtime.js +45 -5
  49. package/esm/keri/npm/src/app/runtime/operation-owners.js +26 -56
  50. package/esm/keri/npm/src/app/runtime/workflow-contract.js +7 -4
  51. package/esm/keri/npm/src/app/transport/http-client.js +20 -2
  52. package/esm/keri/npm/src/app/transport/message-source.js +2 -1
  53. package/esm/keri/npm/src/app/transport/messengering.js +39 -8
  54. package/esm/keri/npm/src/app/witness/ingress.js +7 -0
  55. package/esm/keri/npm/src/app/witness/receipt-client.js +43 -16
  56. package/esm/keri/npm/src/app/witness/receipting.js +62 -20
  57. package/esm/keri/npm/src/cli/messaging/multisig.js +4 -3
  58. package/esm/keri/npm/src/core/errors.js +73 -0
  59. package/esm/keri/npm/src/core/kel/delegation-ancestry.js +11 -2
  60. package/esm/keri/npm/src/core/kel/eventing.js +25 -28
  61. package/esm/keri/npm/src/core/kel/kever.js +15 -9
  62. package/esm/keri/npm/src/core/mailbox/kram-policy.js +8 -0
  63. package/esm/keri/npm/src/core/mailbox/kram-pruning.js +25 -6
  64. package/esm/keri/npm/src/core/mailbox/kram-time.js +34 -6
  65. package/esm/keri/npm/src/core/mailbox/kraming.js +58 -2
  66. package/esm/keri/npm/src/core/routing.js +18 -25
  67. package/esm/keri/npm/src/core/scheming.js +12 -5
  68. package/esm/keri/npm/src/core/wire-fields.js +126 -0
  69. package/esm/keri/npm/src/db/basing.js +4 -13
  70. package/esm/keri/npm/src/db/core/indexeddb-persistence.js +3 -2
  71. package/esm/keri/npm/src/db/core/webdber.js +85 -27
  72. package/esm/keri/npm/src/db/mailbox/mailboxing.js +13 -0
  73. package/esm/keri/npm/src/db/subing.js +4 -0
  74. package/esm/keri/npm/src/runtime/index.js +11 -5
  75. package/esm/keri/npm/src/time/mod.js +5 -0
  76. package/esm/keri/npm/src/vdr/credential-tsn-record.js +2 -7
  77. package/esm/keri/npm/src/vdr/eventing.js +28 -19
  78. package/esm/keri/npm/src/vdr/registry-tsn-record.js +3 -13
  79. package/esm/keri/npm/src/version.js +2 -2
  80. package/package.json +1 -1
  81. package/types/_dnt.polyfills.d.ts +9 -9
  82. package/types/_dnt.polyfills.d.ts.map +1 -1
  83. package/types/acdc/registering.d.ts +6 -0
  84. package/types/acdc/registering.d.ts.map +1 -1
  85. package/types/acdc/salting.d.ts.map +1 -1
  86. package/types/app/bootstrap/authenticating.d.ts +12 -2
  87. package/types/app/bootstrap/authenticating.d.ts.map +1 -1
  88. package/types/app/bootstrap/oobiery.d.ts +144 -7
  89. package/types/app/bootstrap/oobiery.d.ts.map +1 -1
  90. package/types/app/credentials/credential-importing.d.ts +8 -0
  91. package/types/app/credentials/credential-importing.d.ts.map +1 -1
  92. package/types/app/credentials/root-replicator.d.ts +19 -8
  93. package/types/app/credentials/root-replicator.d.ts.map +1 -1
  94. package/types/app/credentials/schema-resolving.d.ts.map +1 -1
  95. package/types/app/credentials/tel-publishing.d.ts +4 -2
  96. package/types/app/credentials/tel-publishing.d.ts.map +1 -1
  97. package/types/app/delivery/forwarding.d.ts +69 -5
  98. package/types/app/delivery/forwarding.d.ts.map +1 -1
  99. package/types/app/delivery/relaying.d.ts +144 -0
  100. package/types/app/delivery/relaying.d.ts.map +1 -0
  101. package/types/app/delivery/respondant.d.ts +9 -2
  102. package/types/app/delivery/respondant.d.ts.map +1 -1
  103. package/types/app/exchanges/exchanging.d.ts +36 -0
  104. package/types/app/exchanges/exchanging.d.ts.map +1 -1
  105. package/types/app/identity/delegating.d.ts +6 -0
  106. package/types/app/identity/delegating.d.ts.map +1 -1
  107. package/types/app/identity/habbing.d.ts +106 -19
  108. package/types/app/identity/habbing.d.ts.map +1 -1
  109. package/types/app/identity/keeping.d.ts +54 -19
  110. package/types/app/identity/keeping.d.ts.map +1 -1
  111. package/types/app/identity/retire-identifier.d.ts +24 -0
  112. package/types/app/identity/retire-identifier.d.ts.map +1 -0
  113. package/types/app/ipex/disclosure-replaying.d.ts +61 -0
  114. package/types/app/ipex/disclosure-replaying.d.ts.map +1 -0
  115. package/types/app/ipex/group-ipex.d.ts +60 -14
  116. package/types/app/ipex/group-ipex.d.ts.map +1 -1
  117. package/types/app/ipex/group-issuance.d.ts +48 -6
  118. package/types/app/ipex/group-issuance.d.ts.map +1 -1
  119. package/types/app/ipex/ipex-discloser.d.ts +14 -2
  120. package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
  121. package/types/app/ipex/ipex-grouping.d.ts +23 -2
  122. package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
  123. package/types/app/ipex/ipex-v2.d.ts +5 -2
  124. package/types/app/ipex/ipex-v2.d.ts.map +1 -1
  125. package/types/app/ipex/issuance.d.ts +35 -11
  126. package/types/app/ipex/issuance.d.ts.map +1 -1
  127. package/types/app/ipex/pending-presentations.d.ts +4 -2
  128. package/types/app/ipex/pending-presentations.d.ts.map +1 -1
  129. package/types/app/ipex/presenting.d.ts.map +1 -1
  130. package/types/app/ipex/receive-issuance.d.ts +2 -1
  131. package/types/app/ipex/receive-issuance.d.ts.map +1 -1
  132. package/types/app/ipex/unblinding-workflow.d.ts +4 -1
  133. package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
  134. package/types/app/mailbox/agent-inbox.d.ts +126 -0
  135. package/types/app/mailbox/agent-inbox.d.ts.map +1 -0
  136. package/types/app/mailbox/forward-handler.d.ts +114 -14
  137. package/types/app/mailbox/forward-handler.d.ts.map +1 -1
  138. package/types/app/mailbox/kel-publisher.d.ts +12 -22
  139. package/types/app/mailbox/kel-publisher.d.ts.map +1 -1
  140. package/types/app/mailbox/mailbox-poller.d.ts +40 -24
  141. package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
  142. package/types/app/mailbox/mailboxing.d.ts +19 -5
  143. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  144. package/types/app/multisig/counseling.d.ts +32 -11
  145. package/types/app/multisig/counseling.d.ts.map +1 -1
  146. package/types/app/multisig/group-members.d.ts +17 -1
  147. package/types/app/multisig/group-members.d.ts.map +1 -1
  148. package/types/app/multisig/grouping.d.ts +17 -1
  149. package/types/app/multisig/grouping.d.ts.map +1 -1
  150. package/types/app/multisig/multisig-workflows.d.ts +0 -8
  151. package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
  152. package/types/app/multisig/proposing.d.ts +4 -19
  153. package/types/app/multisig/proposing.d.ts.map +1 -1
  154. package/types/app/multisig/rejoin-group.d.ts +119 -0
  155. package/types/app/multisig/rejoin-group.d.ts.map +1 -0
  156. package/types/app/multisig/replace-member.d.ts +15 -1
  157. package/types/app/multisig/replace-member.d.ts.map +1 -1
  158. package/types/app/protocol/parsering.d.ts +21 -2
  159. package/types/app/protocol/parsering.d.ts.map +1 -1
  160. package/types/app/protocol/protocol-processing.d.ts +25 -5
  161. package/types/app/protocol/protocol-processing.d.ts.map +1 -1
  162. package/types/app/queries/query-replies.d.ts +4 -8
  163. package/types/app/queries/query-replies.d.ts.map +1 -1
  164. package/types/app/queries/relay-refresh.d.ts +26 -0
  165. package/types/app/queries/relay-refresh.d.ts.map +1 -0
  166. package/types/app/queries/witness-inquisitor.d.ts +56 -5
  167. package/types/app/queries/witness-inquisitor.d.ts.map +1 -1
  168. package/types/app/runtime/agent-runtime.d.ts +34 -3
  169. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  170. package/types/app/runtime/cue-runtime.d.ts +15 -3
  171. package/types/app/runtime/cue-runtime.d.ts.map +1 -1
  172. package/types/app/runtime/operation-owners.d.ts +16 -26
  173. package/types/app/runtime/operation-owners.d.ts.map +1 -1
  174. package/types/app/runtime/workflow-contract.d.ts +21 -6
  175. package/types/app/runtime/workflow-contract.d.ts.map +1 -1
  176. package/types/app/transport/http-client.d.ts +9 -0
  177. package/types/app/transport/http-client.d.ts.map +1 -1
  178. package/types/app/transport/message-source.d.ts +6 -3
  179. package/types/app/transport/message-source.d.ts.map +1 -1
  180. package/types/app/transport/messengering.d.ts +10 -1
  181. package/types/app/transport/messengering.d.ts.map +1 -1
  182. package/types/app/witness/ingress.d.ts +1 -0
  183. package/types/app/witness/ingress.d.ts.map +1 -1
  184. package/types/app/witness/receipt-client.d.ts +19 -1
  185. package/types/app/witness/receipt-client.d.ts.map +1 -1
  186. package/types/app/witness/receipting.d.ts +24 -1
  187. package/types/app/witness/receipting.d.ts.map +1 -1
  188. package/types/cli/messaging/multisig.d.ts.map +1 -1
  189. package/types/core/cues.d.ts +53 -1
  190. package/types/core/cues.d.ts.map +1 -1
  191. package/types/core/errors.d.ts +32 -0
  192. package/types/core/errors.d.ts.map +1 -1
  193. package/types/core/kel/delegation-ancestry.d.ts.map +1 -1
  194. package/types/core/kel/eventing.d.ts.map +1 -1
  195. package/types/core/kel/kever-decisions.d.ts +3 -1
  196. package/types/core/kel/kever-decisions.d.ts.map +1 -1
  197. package/types/core/kel/kever.d.ts +7 -1
  198. package/types/core/kel/kever.d.ts.map +1 -1
  199. package/types/core/mailbox/kram-policy.d.ts +6 -0
  200. package/types/core/mailbox/kram-policy.d.ts.map +1 -1
  201. package/types/core/mailbox/kram-pruning.d.ts +5 -1
  202. package/types/core/mailbox/kram-pruning.d.ts.map +1 -1
  203. package/types/core/mailbox/kram-time.d.ts +26 -2
  204. package/types/core/mailbox/kram-time.d.ts.map +1 -1
  205. package/types/core/mailbox/kraming.d.ts +14 -1
  206. package/types/core/mailbox/kraming.d.ts.map +1 -1
  207. package/types/core/routing.d.ts +3 -7
  208. package/types/core/routing.d.ts.map +1 -1
  209. package/types/core/scheming.d.ts.map +1 -1
  210. package/types/core/wire-fields.d.ts +56 -0
  211. package/types/core/wire-fields.d.ts.map +1 -0
  212. package/types/db/basing.d.ts.map +1 -1
  213. package/types/db/core/indexeddb-persistence.d.ts +3 -2
  214. package/types/db/core/indexeddb-persistence.d.ts.map +1 -1
  215. package/types/db/core/webdber.d.ts +32 -6
  216. package/types/db/core/webdber.d.ts.map +1 -1
  217. package/types/db/exchange/exchange-records.d.ts +4 -0
  218. package/types/db/exchange/exchange-records.d.ts.map +1 -1
  219. package/types/db/mailbox/mailboxing.d.ts +10 -1
  220. package/types/db/mailbox/mailboxing.d.ts.map +1 -1
  221. package/types/db/subing.d.ts +2 -0
  222. package/types/db/subing.d.ts.map +1 -1
  223. package/types/runtime/index.d.ts +20 -14
  224. package/types/runtime/index.d.ts.map +1 -1
  225. package/types/time/mod.d.ts +2 -0
  226. package/types/time/mod.d.ts.map +1 -1
  227. package/types/vdr/credential-tsn-record.d.ts.map +1 -1
  228. package/types/vdr/eventing.d.ts.map +1 -1
  229. package/types/vdr/registry-tsn-record.d.ts.map +1 -1
  230. package/types/version.d.ts +2 -2
  231. package/esm/keri/npm/src/app/runtime/signer-conditions.js +0 -51
  232. package/types/app/runtime/signer-conditions.d.ts +0 -32
  233. package/types/app/runtime/signer-conditions.d.ts.map +0 -1
@@ -277,21 +277,22 @@ export class Hab {
277
277
  return !!this.pre && this.db.getKever(this.pre) !== null;
278
278
  }
279
279
  /**
280
- * Whether the held or stored signing keys currently allow this habitat to sign.
281
- * False for a keyless habitat (`makeSignedHab`) until the host holds its signer.
280
+ * Whether this habitat can sign now: every key `sign` would use has a held or stored signer
281
+ * (`Manager.hasSigner`). A pure read that signs nothing. False for a keyless habitat (`makeSignedHab`) until the
282
+ * host holds its signer, and for a habitat with no signing keys (no accepted state, or a group whose local member
283
+ * has no contribution). A group answers through its local member's contribution.
284
+ *
285
+ * Background loops read it before they sign and skip a habitat that cannot sign yet; a later `Manager.hold` lets
286
+ * the habitat act on the next tock.
282
287
  */
283
288
  canSign() {
284
- try {
285
- const probe = new Uint8Array(1);
286
- if (this.kever?.transferable === false)
287
- this.sign(probe, false);
288
- else
289
- this.sign(probe, true);
290
- return true;
291
- }
292
- catch {
293
- return false;
294
- }
289
+ const keys = this.signingKeys();
290
+ return keys.kind === "keys" && keys.pubs.every((pub) => this.mgr.hasSigner(pub));
291
+ }
292
+ /** The current public keys `sign` would use whose signers are neither held nor stored; empty when it can sign. */
293
+ missingSigners() {
294
+ const keys = this.signingKeys();
295
+ return keys.kind === "keys" ? keys.pubs.filter((pub) => !this.mgr.hasSigner(pub)) : [];
295
296
  }
296
297
  /** Return the live accepted-state `Kever` for this habitat when available. */
297
298
  get kever() {
@@ -408,8 +409,32 @@ export class Hab {
408
409
  * - mirrors the pattern where local inception is signed in the habitat layer
409
410
  * and then fed through `Kevery.processEvent()` rather than hand-writing
410
411
  * `states.`/`kels.` directly
412
+ *
413
+ * Failure leaves nothing behind: the Keeper rows `Manager.incept` and `move`
414
+ * wrote roll back with the enclosing Keeper transaction, and the `habs.`
415
+ * record and `names.` mapping are undone, as `makeSigned` does. KERIpy keeps
416
+ * them.
411
417
  */
412
418
  make(args = {}) {
419
+ const ns = this.ns ?? "";
420
+ const priorName = this.db.getName(ns, this.name);
421
+ try {
422
+ this.ks.transaction(() => this.inceptLocally(args));
423
+ }
424
+ catch (error) {
425
+ if (this.pre && !args.hidden) {
426
+ this.db.habs.rem(this.pre);
427
+ if (priorName)
428
+ this.db.pinName(ns, this.name, priorName);
429
+ else
430
+ this.db.names.rem([ns, this.name]);
431
+ }
432
+ this.pre = "";
433
+ throw error;
434
+ }
435
+ }
436
+ /** Derive keys, sign and admit the inception; `make` owns the Keeper transaction and the rollback. */
437
+ inceptLocally(args) {
413
438
  const { code = "E", transferable = true, isith = undefined, icount = 1, icode = undefined, nsith = undefined, ncount = undefined, ncode = undefined, toad = 0, wits = [], delpre = undefined, estOnly = false, DnD = false, hidden = false, data = [], algo = undefined, salt = undefined, tier = undefined, version = Vrsn_2_0, } = args;
414
439
  let nextCount = ncount ?? icount;
415
440
  let nextSith = nsith ?? isith;
@@ -660,43 +685,43 @@ export class Hab {
660
685
  return this.mgr.decrypt(ser, { verfers: verfers ?? kever.verfers });
661
686
  }
662
687
  sign(ser, indexed = false) {
663
- if (!this.pre) {
664
- throw new ValidationError("Signing requires a local habitat prefix.");
665
- }
666
- const kever = this.kever;
667
- if (!kever) {
668
- throw new ValidationError(`Missing accepted key state for ${this.pre}.`);
669
- }
670
- const pubs = kever.verfers.map((verfer) => verfer.qb64);
671
- const member = this.db.getHab(this.pre)?.mid;
672
- if (member) {
673
- const state = this.db.getKever(member);
674
- if (!state)
675
- throw new ValidationError(`Missing accepted member key state for ${member}.`);
676
- // KERIpy GroupHab.sign/fetchLatestContribTo: a member may have rotated
677
- // ahead of its contribution to the group's currently accepted key list.
678
- const contribution = latestGroupContribution(this.db, member, kever.verfers);
679
- if (contribution) {
680
- const { index, verfer } = contribution;
681
- if (indexed) {
682
- // Endorsement proves current authority only. Rotation signatures have
683
- // a separate prior-next commitment path in rotateGroupHab().
684
- return this.mgr.sign(ser, {
685
- pubs: [verfer.qb64],
686
- indexed: true,
687
- indices: [index],
688
- ondices: [null],
689
- });
690
- }
691
- return this.mgr.sign(ser, [verfer.qb64], false);
692
- }
693
- throw new ValidationError(`Member ${member} has no contribution to group ${this.pre}.`);
688
+ const keys = this.signingKeys();
689
+ if (keys.kind === "none")
690
+ throw new ValidationError(keys.reason);
691
+ const { pubs, index } = keys;
692
+ if (index !== null && indexed) {
693
+ // Endorsement proves current authority only. Rotation signatures have
694
+ // a separate prior-next commitment path in rotateGroupHab().
695
+ return this.mgr.sign(ser, { pubs, indexed: true, indices: [index], ondices: [null] });
694
696
  }
695
697
  if (indexed) {
696
698
  return this.mgr.sign(ser, pubs, true);
697
699
  }
698
700
  return this.mgr.sign(ser, pubs, false);
699
701
  }
702
+ /**
703
+ * Select the keys `sign` uses. A group habitat uses only its designated member's latest single-key contribution,
704
+ * located in its accepted KEL; any other habitat uses its accepted current keys.
705
+ */
706
+ signingKeys() {
707
+ if (!this.pre)
708
+ return { kind: "none", reason: "Signing requires a local habitat prefix." };
709
+ const kever = this.kever;
710
+ if (!kever)
711
+ return { kind: "none", reason: `Missing accepted key state for ${this.pre}.` };
712
+ const member = this.db.getHab(this.pre)?.mid;
713
+ if (!member)
714
+ return { kind: "keys", pubs: kever.verfers.map((verfer) => verfer.qb64), index: null };
715
+ if (!this.db.getKever(member)) {
716
+ return { kind: "none", reason: `Missing accepted member key state for ${member}.` };
717
+ }
718
+ // KERIpy GroupHab.sign/fetchLatestContribTo: a member may have rotated
719
+ // ahead of its contribution to the group's currently accepted key list.
720
+ const contribution = latestGroupContribution(this.db, member, kever.verfers);
721
+ if (!contribution)
722
+ return { kind: "none", reason: `Member ${member} has no contribution to group ${this.pre}.` };
723
+ return { kind: "keys", pubs: [contribution.verfer.qb64], index: contribution.index };
724
+ }
700
725
  /**
701
726
  * Endorse one already-built KERI message body with this habitat's current
702
727
  * establishment keys.
@@ -1357,42 +1382,44 @@ export class Habery {
1357
1382
  }
1358
1383
  hab.reconfigure();
1359
1384
  }
1360
- // Restart recovery: commit members of an accepted establishment event
1361
- // whose live commit was interrupted. Read-only openings never write.
1362
- for (const pre of this.readonly ? [] : this.habs.keys()) {
1363
- this.recoverAcceptedGroupMembers(pre);
1385
+ // Restart recovery: a group whose establishment event was accepted while no runtime aligned its members.
1386
+ for (const pre of this.habs.keys()) {
1387
+ this.alignGroupMembers(pre);
1364
1388
  }
1365
1389
  }
1366
1390
  /**
1367
- * Commit the members of a local group's latest accepted establishment event
1368
- * when the stored record is stale (KERIpy `GroupHab.recoverAcceptedMembers`).
1391
+ * Bring a local group's stored members (`smids`/`rmids` on its habitat record and in the keeper) in line with
1392
+ * the group's latest accepted establishment event. This is the one writer of a local group's member lists after
1393
+ * its record is created; acceptance, reopen recovery, member replacement and a proposer's immediate acceptance
1394
+ * all reach it, and the runtime's Counselor calls it on its pass after each accepted establishment event, so a
1395
+ * rotation that completes late (a co-member's signature arriving after the proposer stopped waiting) is followed
1396
+ * without any caller asking.
1397
+ *
1398
+ * Members come from accepted key state only (`acceptedGroupMembers`, KERIpy `GroupHab.acceptedMembers`), never
1399
+ * from a proposal's or caller's lists: the stored members are tried first, then every AID with local key state.
1400
+ * When a key cannot be attributed yet nothing is written and the result is `unattributed`; a partial list is
1401
+ * never stored. Only the latest accepted event is followed, so members never roll back, and repeating it
1402
+ * changes nothing.
1369
1403
  *
1370
- * A process that stopped after a rotation was accepted but before its live
1371
- * `commitAcceptedGroupMembers` leaves `smids`/`rmids` stale. Members are
1372
- * derived from local KELs, trying the stored members first and every AID
1373
- * with local key state only when they do not account for every key. They
1374
- * are committed only when they differ from the stored record and the local
1375
- * member contributes to that event; a record that does not yet include the
1376
- * local member is a pending join, not a stale commit. Only the latest
1377
- * accepted event is committed, so this never rolls members back, and
1378
- * repeating it changes nothing. Returns whether stale members were committed.
1404
+ * The recorded local member (`mid`) is never changed here; replacing it stays `joinGroupHab`'s explicit
1405
+ * replacement. Diverging from KERIpy `GroupHab.recoverAcceptedMembers`, the lists follow accepted state even
1406
+ * when it does not include the local member (a member rotated out, or one whose join is not accepted yet).
1407
+ * Weight plays no part: a member whose signing weight is 0 is a member like any other.
1379
1408
  */
1380
- recoverAcceptedGroupMembers(pre) {
1409
+ alignGroupMembers(pre) {
1381
1410
  const record = this.db.getHab(pre);
1382
- if (!record?.mid || record.sid || !this.habs.has(record.mid))
1383
- return false;
1411
+ if (this.readonly || !record?.mid || record.sid || !this.habs.has(record.mid))
1412
+ return "unchanged";
1384
1413
  const smids = [...(record.smids ?? [])], rmids = [...(record.rmids ?? [])];
1385
1414
  const accepted = acceptedGroupMembers(this.db, pre, new Set([...smids, ...rmids]))
1386
1415
  ?? acceptedGroupMembers(this.db, pre, [...this.db.states.getTopItemIter()].map(([keys]) => keys.join(".")));
1387
1416
  if (!accepted)
1388
- return false;
1417
+ return "unattributed";
1389
1418
  const same = (left, right) => left.length === right.length && left.every((aid, index) => aid === right[index]);
1390
1419
  if (same(accepted.smids, smids) && same(accepted.rmids, rmids))
1391
- return false;
1392
- if (!accepted.smids.includes(record.mid) && !accepted.rmids.includes(record.mid))
1393
- return false;
1420
+ return "unchanged";
1394
1421
  this.commitGroupHabMembers(pre, accepted.smids, accepted.rmids);
1395
- return true;
1422
+ return "aligned";
1396
1423
  }
1397
1424
  /** Local AID prefixes currently managed by this habery. */
1398
1425
  get prefixes() {
@@ -1596,8 +1623,8 @@ export class Habery {
1596
1623
  }
1597
1624
  this.habs.set(pre, hab);
1598
1625
  if (replacing && !this.readonly) {
1599
- // The stored lists must follow accepted state, which the old member's record kept from being committed.
1600
- this.recoverAcceptedGroupMembers(pre);
1626
+ // The stored lists must follow accepted state, whatever lists the caller passed.
1627
+ this.alignGroupMembers(pre);
1601
1628
  this.retireGroupMemberState(pre, existingRecord.mid);
1602
1629
  }
1603
1630
  return hab;
@@ -1621,7 +1648,7 @@ export class Habery {
1621
1648
  this.db.rrsn.rem(keys);
1622
1649
  }
1623
1650
  }
1624
- /** Commit accepted group member metadata after the corresponding group event is accepted. */
1651
+ /** Write member lists already derived from accepted state; only `alignGroupMembers` calls this. */
1625
1652
  commitGroupHabMembers(pre, smids, rmids = smids) {
1626
1653
  const record = this.db.getHab(pre);
1627
1654
  if (!record?.mid) {
@@ -1696,8 +1723,9 @@ export class Habery {
1696
1723
  if (decision.kind === "reject") {
1697
1724
  throw new ValidationError(decision.message);
1698
1725
  }
1726
+ // Accepted on local signatures alone: the members follow it now. Otherwise they follow when it is accepted.
1699
1727
  if (serder.sn !== null && serder.said && this.db.kels.getLast(hab.pre, serder.sn) === serder.said) {
1700
- this.commitGroupHabMembers(hab.pre, signingMembers, rotationMembers);
1728
+ this.alignGroupMembers(hab.pre);
1701
1729
  }
1702
1730
  return {
1703
1731
  hab,
@@ -1872,6 +1900,97 @@ export class Habery {
1872
1900
  this.habs.set(pre, hab);
1873
1901
  return hab;
1874
1902
  }
1903
+ /**
1904
+ * Retire one local, non-group habitat: it stops being local, and its stored private keys are deleted.
1905
+ *
1906
+ * Removed, durable rows in one `db.transaction` (the Keeper deletes run in a nested Keeper transaction, see
1907
+ * `Manager.forget`): the `habs.` record, its `names.` mapping when that still maps to `pre`, and the
1908
+ * Keeper's stored key rows. After commit, `pre` leaves `db.prefixes`, `habs` and `deferredReconfigure`.
1909
+ *
1910
+ * Kept, deliberately: the KEL with its attachments, `states.`, receipts, key-state notices, every accepted
1911
+ * reply the identifier authored or that names it (end-role and location records and their BADA date-times
1912
+ * and signatures), and the `Kever` cache entry. KERI never deletes a KEL, and BADA/RUN acceptance compares
1913
+ * new replies against the stored ones, so deleting them would let an older signed reply be accepted again.
1914
+ * The identifier is afterwards an ordinary remote one; `Baser.reloadKevers` rebuilds `prefixes` from
1915
+ * `habs.` only, so a reopen agrees. Retirement is one-way: `makeSignedHab` refuses a prefix with a `Kever`,
1916
+ * and the kept Keeper `pres.` rows make `makeHab` refuse an inception that derives the retired keys again.
1917
+ * Retirement deletes the stored keys and the ability to act as a local habitat, not the secret's
1918
+ * derivability: a rooted salty habitat's seeds stay derivable from the Keeper's root salt plus its name.
1919
+ * No event is made and nothing is signed. The held signers of the identifier's current keys are dropped
1920
+ * (`Manager.forget`) once the rows are deleted. Runtime state (pollers, cursors, operations) is the caller's to
1921
+ * clean up.
1922
+ *
1923
+ * Refused with a `ValidationError`, before any write, when `pre` is not a local habitat, is a group
1924
+ * habitat, is the recorded local member (`mid`) of a local group, is an accepted signing or rotation
1925
+ * member of a local group, or is the delegator of a local habitat.
1926
+ *
1927
+ * Differences from KERIpy `Habery.deleteHab(name, ns)`: keyed by prefix rather than by name, throws where
1928
+ * KERIpy returns `False`, refuses the cases above where KERIpy has no guards, and deletes the Keeper's stored
1929
+ * key rows other than `pres.` where KERIpy keeps them all.
1930
+ */
1931
+ retireHab(pre) {
1932
+ const refusal = this.retireRefusal(pre);
1933
+ if (refusal)
1934
+ throw new ValidationError(refusal);
1935
+ const record = this.db.getHab(pre);
1936
+ const name = record.name;
1937
+ const ns = record.domain ?? "";
1938
+ const current = this.db.getKever(pre)?.verfers.map((verfer) => verfer.qb64) ?? [];
1939
+ const { deleted, released } = this.db.transaction(() => {
1940
+ this.db.habs.rem(pre);
1941
+ if (this.db.getName(ns, name) === pre)
1942
+ this.db.names.rem([ns, name]);
1943
+ return this.mgr.forget(pre, current);
1944
+ });
1945
+ this.db.prefixes.delete(pre);
1946
+ this.habs.delete(pre);
1947
+ const deferred = this.deferredReconfigure.indexOf(pre);
1948
+ if (deferred >= 0)
1949
+ this.deferredReconfigure.splice(deferred, 1);
1950
+ return { pre, name, domain: record.domain, keys: deleted, released };
1951
+ }
1952
+ /**
1953
+ * The accepted local habitats that cannot sign now, each with the current public keys whose signers are neither
1954
+ * held nor stored. Derived on every call from `Hab.missingSigners`; nothing records it. A store opened without a
1955
+ * habitat's signer lists that habitat until the host calls `Manager.hold`. A habitat with no signing keys at all
1956
+ * (a group whose local member has no contribution) is not listed: that is not a missing signer.
1957
+ */
1958
+ unsignable() {
1959
+ const unsignable = [];
1960
+ for (const hab of this.habs.values()) {
1961
+ if (!hab.accepted)
1962
+ continue;
1963
+ const missing = hab.missingSigners();
1964
+ if (missing.length)
1965
+ unsignable.push({ pre: hab.pre, missing });
1966
+ }
1967
+ return unsignable;
1968
+ }
1969
+ /**
1970
+ * Why {@link retireHab} would refuse `pre`, or null when it would retire it. A pure read: the refusals listed
1971
+ * on `retireHab`, in the same order, so a caller can decide between retiring and keeping without a throw.
1972
+ */
1973
+ retireRefusal(pre) {
1974
+ const record = this.db.getHab(pre);
1975
+ if (!record?.name)
1976
+ return `${pre} is not a local habitat.`;
1977
+ if (record.mid || this.db.groups.has(pre)) {
1978
+ return `${pre} is a group habitat; retiring group habitats is not supported.`;
1979
+ }
1980
+ for (const [key, local] of this.db.getHabItemIter()) {
1981
+ const hid = local.hid || key;
1982
+ if (hid === pre)
1983
+ continue;
1984
+ if (local.mid === pre)
1985
+ return `${pre} is the recorded local member of group ${hid}.`;
1986
+ if (local.mid && isAcceptedGroupMember(this.db, hid, pre)) {
1987
+ return `${pre} is an accepted member of local group ${hid}.`;
1988
+ }
1989
+ if (this.db.getKever(hid)?.delpre === pre)
1990
+ return `${pre} is the delegator of local habitat ${hid}.`;
1991
+ }
1992
+ return null;
1993
+ }
1875
1994
  /** Creates this Habery's sidecar stores. */
1876
1995
  get opener() {
1877
1996
  if (!this.storeOpener)
@@ -1,5 +1,5 @@
1
1
  import { decodeB64, Decrypter, Diger, Encrypter, hydrateMatter, intToB64, MtrDex, NumberPrimitive, NumDex, parseMatter, Prefixer, Saider, Salter, Signer, Tiers, Verfer, } from "cesr-ts";
2
- import { b } from "cesr-ts";
2
+ import { b, ParserError } from "cesr-ts";
3
3
  import { ValidationError } from "../../core/errors.js";
4
4
  import { PubLot } from "../../db/keeping.js";
5
5
  /**
@@ -194,9 +194,9 @@ function emptyLot(dt = "") {
194
194
  return new PubLot({ pubs: [], ridx: 0, kidx: 0, dt });
195
195
  }
196
196
  /**
197
- * No private key for `pub`: neither held (`Manager.hold`) nor stored in the Keeper. A custodial host releases
198
- * its signers on purpose, so a background loop that needs one treats this as a condition to wait out, not as a
199
- * failure of the runtime. The message is the one the Keeper has always used.
197
+ * No private key for `pub`: neither held (`Manager.hold`) nor stored in the Keeper. A background loop reads
198
+ * `Hab.canSign()` before it signs and skips a habitat that cannot, so this error escaping a loop is a real fault.
199
+ * The message is the one the Keeper has always used.
200
200
  */
201
201
  export class SignerNotHeldError extends Error {
202
202
  constructor(pub) {
@@ -270,13 +270,6 @@ export class Manager {
270
270
  writable: true,
271
271
  value: new Map()
272
272
  });
273
- /** Told after `hold()` borrows signers; a loop that waited for a missing signer resumes on it. */
274
- Object.defineProperty(this, "holdListeners", {
275
- enumerable: true,
276
- configurable: true,
277
- writable: true,
278
- value: new Set()
279
- });
280
273
  const { ks, seed = "", aeid = "", pidx = 0, algo = Algos.salty, salt = randomSaltQb64(), tier = Tiers.low, } = args;
281
274
  this._ks = ks;
282
275
  this._seed = seed;
@@ -433,9 +426,9 @@ export class Manager {
433
426
  this.decrypter = decrypter;
434
427
  this._seed = seed;
435
428
  }
436
- /** Require full canonical keys; local ownership is the calling operation's responsibility. */
429
+ /** Require full canonical keys for a write; local ownership is the calling operation's responsibility. */
437
430
  registryRootKeys(aid, regid) {
438
- if (new Prefixer({ qb64: aid }).qb64 !== aid || new Diger({ qb64: regid }).qb64 !== regid) {
431
+ if (!canonicalRegistryRootKeys(aid, regid)) {
439
432
  throw new Error("Registry root keys must be canonical AID and registry SAID strings");
440
433
  }
441
434
  return [aid, regid];
@@ -454,7 +447,10 @@ export class Manager {
454
447
  }
455
448
  /** Return the retained Salt128 root; absent reads require no encryption. */
456
449
  getRegistryRoot(aid, regid) {
457
- const cipher = this.ks.rroots.get(this.registryRootKeys(aid, regid));
450
+ // No root is stored under a key that is not canonical (see `canonicalRegistryRootKeys`).
451
+ if (!canonicalRegistryRootKeys(aid, regid))
452
+ return null;
453
+ const cipher = this.ks.rroots.get([aid, regid]);
458
454
  if (!cipher)
459
455
  return null;
460
456
  const { decrypter } = this.registryRootCrypto();
@@ -476,9 +472,12 @@ export class Manager {
476
472
  const { aeid, encrypter, decrypter } = this.registryRootCrypto();
477
473
  return this.ks.retainRegistryRoot(keys, root, aeid, encrypter, decrypter, overwrite);
478
474
  }
479
- /** Whether a root is retained for `aid` and `regid`; reads no ciphertext and needs no unlocked Keeper. */
475
+ /**
476
+ * Whether a root is retained for `aid` and `regid`; reads no ciphertext and needs no unlocked Keeper. False for
477
+ * a key that is not canonical, since no root is stored under one (see `canonicalRegistryRootKeys`).
478
+ */
480
479
  hasRegistryRoot(aid, regid) {
481
- return !!this.ks.rroots.get(this.registryRootKeys(aid, regid));
480
+ return canonicalRegistryRootKeys(aid, regid) && !!this.ks.rroots.get([aid, regid]);
482
481
  }
483
482
  signerDecrypter() {
484
483
  if (this.aeid && !this.decrypter) {
@@ -504,7 +503,8 @@ export class Manager {
504
503
  * and hold that encoding. A request for the other encoding does not match.
505
504
  * - The host owns each `Signer` and its seed bytes, including zeroing them.
506
505
  * This keeps the reference only; it never copies seed bytes and never
507
- * erases them. Dropping a reference with `release()` does not zero memory.
506
+ * erases them. Retiring the habitat (`forget()`) drops the reference but
507
+ * does not zero memory.
508
508
  * - Holding a pub that is already held replaces the earlier signer. A held
509
509
  * signer shadows a stored key with the same public key.
510
510
  * - The `pre/path` branch of `sign()` re-derives salty keys from the stored
@@ -522,32 +522,18 @@ export class Manager {
522
522
  }
523
523
  for (const [pub, signer] of batch)
524
524
  this.held.set(pub, signer);
525
- for (const listener of [...this.holdListeners])
526
- listener();
527
525
  }
528
526
  /**
529
- * Be told after each `hold()`, so a background loop whose signer was released can resume at once.
527
+ * Whether this Manager can sign with `pub` now: a signer is held for it, or the Keeper stores its private key.
528
+ * A pure read; it neither decrypts nor signs.
530
529
  *
531
- * @returns A function that stops the notifications.
530
+ * A store may be opened without a habitat's signer (a keyless habitat before its host calls `hold()`), so this
531
+ * is ordinary state: background loops read it before they sign and skip what cannot be signed yet.
532
532
  */
533
- onHold(listener) {
534
- this.holdListeners.add(listener);
535
- return () => this.holdListeners.delete(listener);
536
- }
537
- /**
538
- * Drop held signer references: all of them, or only those for `pubs`.
539
- *
540
- * Unheld pubs are ignored. Afterwards a request for a released key resolves
541
- * from stored keys only and fails as any missing key does. This drops
542
- * references; zeroing a signer's seed stays the host's job.
543
- */
544
- release(pubs) {
545
- if (pubs === undefined) {
546
- this.held.clear();
547
- return;
548
- }
549
- for (const pub of pubs)
550
- this.held.delete(pub);
533
+ hasSigner(pub) {
534
+ if (this.held.has(pub))
535
+ return true;
536
+ return this.ks.pris.has(pub);
551
537
  }
552
538
  /** Resolve one signer by qualified public key: held first, then the Keeper's `pris.`. */
553
539
  getSignerByPub(pub) {
@@ -835,6 +821,15 @@ export class Manager {
835
821
  if (!opre) {
836
822
  throw new Error("Invalid incept configuration produced no current verfers.");
837
823
  }
824
+ // The same seed under the other transferability encodes the same public key differently (`D...`/`B...`),
825
+ // so a forgotten prefix's kept `pres.` row is looked up under both encodings.
826
+ const first = isigners[0];
827
+ const twin = new Signer({ code: first.code, raw: first.raw, transferable: !first.transferable }).verfer.qb64;
828
+ const retired = [opre, twin].map((pub) => this.ks.getPres(pub)).find((owner) => owner !== null && this.ks.getSits(owner) === null);
829
+ if (retired) {
830
+ throw new Error(`Already incepted pre=${opre}: these keys derive the forgotten keys of retired identifier ${retired}; `
831
+ + "use another name, namespace or salt.");
832
+ }
838
833
  if (!this.ks.putPres(opre, opre)) {
839
834
  throw new Error(`Already incepted pre=${opre}.`);
840
835
  }
@@ -981,6 +976,68 @@ export class Manager {
981
976
  }
982
977
  return [verfers, digers];
983
978
  }
979
+ /**
980
+ * Delete the stored key rows of one managed prefix, in one Keeper transaction.
981
+ *
982
+ * Removed rows:
983
+ * - `prms.` and `sits.` for `pre`
984
+ * - every `pubs.` lot stored under `pre` or under an inception alias that
985
+ * `move()` bound to it (the first current public key of a self-addressing prefix)
986
+ * - `pris.` for every public key named by those lots or by the situation
987
+ *
988
+ * Kept: every `pres.` row that resolves to `pre`. They are the one-way marker:
989
+ * `incept()` refuses any later inception whose first key is one of them, so
990
+ * the same name, namespace and salt cannot derive these keys again under a
991
+ * new or the same prefix.
992
+ *
993
+ * Limit: this deletes stored rows, not the secret's derivability. For a
994
+ * rooted salty prefix the seeds stay derivable from the Keeper-global salt
995
+ * and the habitat's derivation stem (its namespace and name).
996
+ *
997
+ * A prefix with no key rows, such as a keyless habitat whose signers are held
998
+ * externally (`hold()`) or one already forgotten, has no stored keys to delete.
999
+ * Held signers for `current` (the prefix's current public keys, which a keyless
1000
+ * habitat has no Keeper row for) are dropped as well; zeroing their seeds stays
1001
+ * the host's job. There is no other way to drop a held signer: a store's signers
1002
+ * only ever grow until it closes.
1003
+ *
1004
+ * No KERIpy counterpart: `Habery.deleteHab` keeps all of the Keeper's state.
1005
+ *
1006
+ * @param pre Prefix to forget.
1007
+ * @param current The prefix's current public keys, whose held signers are dropped.
1008
+ * @returns What was deleted and dropped.
1009
+ */
1010
+ forget(pre, current) {
1011
+ const deleted = this.ks.transaction(() => {
1012
+ const aliases = new Set([pre]);
1013
+ for (const [keys, prefixer] of this.ks.pres.getTopItemIter()) {
1014
+ if (prefixer.qb64 === pre)
1015
+ aliases.add(keys.join("."));
1016
+ }
1017
+ const pubs = new Set();
1018
+ const sit = this.ks.getSits(pre);
1019
+ for (const lot of sit ? [sit.old, sit.new, sit.nxt] : []) {
1020
+ for (const pub of lot.pubs)
1021
+ pubs.add(pub);
1022
+ }
1023
+ for (const alias of aliases) {
1024
+ for (const [, lot] of this.ks.pubs.getTopItemIter(alias, { topive: true })) {
1025
+ for (const pub of lot.pubs)
1026
+ pubs.add(pub);
1027
+ }
1028
+ this.ks.pubs.trim(alias, { topive: true });
1029
+ }
1030
+ this.ks.prms.rem(pre);
1031
+ this.ks.sits.rem(pre);
1032
+ let deleted = 0;
1033
+ for (const pub of pubs) {
1034
+ if (this.ks.pris.rem(pub))
1035
+ deleted += 1;
1036
+ }
1037
+ return deleted;
1038
+ });
1039
+ return { deleted, released: current.filter((pub) => this.held.delete(pub)) };
1040
+ }
984
1041
  sign(ser, pubsOrArgs = [], indexed = true) {
985
1042
  const args = Array.isArray(pubsOrArgs)
986
1043
  ? { pubs: pubsOrArgs, indexed }
@@ -1312,3 +1369,18 @@ export function b64DecodeUrl(text) {
1312
1369
  export function ensureKeeperCryptoReady() {
1313
1370
  // no-op
1314
1371
  }
1372
+ /**
1373
+ * Whether `aid` and `regid` are one complete canonical AID and one complete canonical SAID. A registry identifier is
1374
+ * often a peer's text (a Grant's `rd`), so text that does not decode as CESR is a key that names no stored root, not
1375
+ * a fault: a lookup answers "not held" and only a write refuses it.
1376
+ */
1377
+ function canonicalRegistryRootKeys(aid, regid) {
1378
+ try {
1379
+ return new Prefixer({ qb64: aid }).qb64 === aid && new Diger({ qb64: regid }).qb64 === regid;
1380
+ }
1381
+ catch (error) {
1382
+ if (error instanceof ParserError)
1383
+ return false;
1384
+ throw error;
1385
+ }
1386
+ }