keri-ts 2.0.1 → 2.1.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 (229) hide show
  1. package/esm/keri/npm/src/acdc/chaining.js +223 -89
  2. package/esm/keri/npm/src/acdc/observing.js +2 -2
  3. package/esm/keri/npm/src/acdc/regbasing.js +20 -29
  4. package/esm/keri/npm/src/acdc/regengine.js +4 -6
  5. package/esm/keri/npm/src/acdc/registering.js +13 -10
  6. package/esm/keri/npm/src/acdc/registraring.js +4 -9
  7. package/esm/keri/npm/src/acdc/salting.js +127 -25
  8. package/esm/keri/npm/src/acdc/unblinding.js +2 -4
  9. package/esm/keri/npm/src/app/bootstrap/oobiery.js +1 -1
  10. package/esm/keri/npm/src/app/credentials/root-replicating.js +123 -0
  11. package/esm/keri/npm/src/app/credentials/verifying.js +12 -3
  12. package/esm/keri/npm/src/app/delivery/forwarding.js +49 -7
  13. package/esm/keri/npm/src/app/exchanges/exchanging.js +11 -12
  14. package/esm/keri/npm/src/app/identity/configing.js +17 -43
  15. package/esm/keri/npm/src/app/identity/habbing.js +54 -35
  16. package/esm/keri/npm/src/app/identity/keeping.js +1 -1
  17. package/esm/keri/npm/src/app/identity/lmdb-habery.js +12 -0
  18. package/esm/keri/npm/src/app/ipex/ipex-graph.js +7 -70
  19. package/esm/keri/npm/src/app/ipex/ipex-grouping.js +8 -5
  20. package/esm/keri/npm/src/app/ipex/ipex-v2.js +21 -5
  21. package/esm/keri/npm/src/app/ipex/ipexing.js +1 -1
  22. package/esm/keri/npm/src/app/mailbox/mailboxing.js +5 -9
  23. package/esm/keri/npm/src/app/multisig/group-members.js +14 -0
  24. package/esm/keri/npm/src/app/multisig/grouping.js +4 -3
  25. package/esm/keri/npm/src/app/multisig/multisig-workflows.js +7 -2
  26. package/esm/keri/npm/src/app/notifications/notifying.js +5 -9
  27. package/esm/keri/npm/src/app/protocol/protocol-processing.js +35 -3
  28. package/esm/keri/npm/src/app/queries/witness-inquisitor.js +2 -2
  29. package/esm/keri/npm/src/app/runtime/agent-runtime.js +7 -10
  30. package/esm/keri/npm/src/app/transport/byte-source.js +6 -42
  31. package/esm/keri/npm/src/app/transport/cesr-egress.js +77 -13
  32. package/esm/keri/npm/src/app/transport/file-bytes.js +41 -0
  33. package/esm/keri/npm/src/app/transport/http-messenger.js +14 -9
  34. package/esm/keri/npm/src/app/transport/message-source.js +1 -1
  35. package/esm/keri/npm/src/app/transport/messengering.js +3 -4
  36. package/esm/keri/npm/src/app/transport/node-http-client.js +21 -0
  37. package/esm/keri/npm/src/app/transport/tcp-dialer.js +14 -0
  38. package/esm/keri/npm/src/app/witness/receipt-client.js +2 -2
  39. package/esm/keri/npm/src/cli/common/context.js +2 -2
  40. package/esm/keri/npm/src/cli/common/existing.js +3 -5
  41. package/esm/keri/npm/src/cli/credentials/ipex.js +2 -1
  42. package/esm/keri/npm/src/cli/endpoints/oobi-clean.js +4 -3
  43. package/esm/keri/npm/src/cli/endpoints/oobi.js +2 -2
  44. package/esm/keri/npm/src/cli/identity/challenge.js +2 -2
  45. package/esm/keri/npm/src/cli/identity/incept.js +2 -2
  46. package/esm/keri/npm/src/cli/identity/init.js +4 -3
  47. package/esm/keri/npm/src/cli/identity/interact.js +2 -2
  48. package/esm/keri/npm/src/cli/identity/rotate.js +2 -2
  49. package/esm/keri/npm/src/cli/messaging/exchange.js +2 -2
  50. package/esm/keri/npm/src/cli/messaging/multisig.js +61 -3
  51. package/esm/keri/npm/src/cli/tools/annotate.js +2 -1
  52. package/esm/keri/npm/src/cli/tools/benchmark.js +2 -1
  53. package/esm/keri/npm/src/cli/tools/db-dump.js +1 -4
  54. package/esm/keri/npm/src/core/bytes.js +9 -0
  55. package/esm/keri/npm/src/core/kel/delegation-ancestry.js +92 -0
  56. package/esm/keri/npm/src/core/kel/eventing.js +1 -2
  57. package/esm/keri/npm/src/core/kel/kever.js +11 -9
  58. package/esm/keri/npm/src/core/mailbox/kram-policy.js +2 -3
  59. package/esm/keri/npm/src/core/mailbox/kram-pruning.js +15 -22
  60. package/esm/keri/npm/src/core/mailbox/kram-staging.js +2 -2
  61. package/esm/keri/npm/src/core/routing.js +2 -2
  62. package/esm/keri/npm/src/db/basing.js +149 -203
  63. package/esm/keri/npm/src/db/core/dber.js +1062 -0
  64. package/esm/keri/npm/src/db/core/index.js +4 -2
  65. package/esm/keri/npm/src/db/core/lmdb-opener.js +30 -0
  66. package/esm/keri/npm/src/db/core/lmdber.js +199 -1067
  67. package/esm/keri/npm/src/db/core/memstore.js +532 -0
  68. package/esm/keri/npm/src/db/core/path-manager.js +109 -223
  69. package/esm/keri/npm/src/db/core/store-owner.js +104 -0
  70. package/esm/keri/npm/src/db/core/webdber.js +252 -0
  71. package/esm/keri/npm/src/db/escrowing.js +2 -2
  72. package/esm/keri/npm/src/db/exchange/exchange-nests.js +3 -2
  73. package/esm/keri/npm/src/db/keeping.js +27 -121
  74. package/esm/keri/npm/src/db/koming.js +1 -1
  75. package/esm/keri/npm/src/db/lmdb-stores.js +68 -0
  76. package/esm/keri/npm/src/db/lmdb.js +6 -0
  77. package/esm/keri/npm/src/db/mailbox/mailboxing.js +12 -65
  78. package/esm/keri/npm/src/db/noting.js +11 -55
  79. package/esm/keri/npm/src/db/reger.js +43 -105
  80. package/esm/keri/npm/src/db/replay/witness-replay.js +2 -20
  81. package/esm/keri/npm/src/db/subing.js +1 -1
  82. package/esm/keri/npm/src/npm/db-lmdb.js +3 -0
  83. package/esm/keri/npm/src/npm/runtime-node.js +3 -0
  84. package/esm/keri/npm/src/runtime/index.js +9 -10
  85. package/esm/keri/npm/src/runtime/node.js +14 -0
  86. package/esm/keri/npm/src/vdr/eventing.js +1 -1
  87. package/esm/keri/npm/src/vdr/tel-publication.js +3 -3
  88. package/esm/keri/npm/src/version.js +2 -2
  89. package/package.json +11 -4
  90. package/types/acdc/chaining.d.ts +56 -6
  91. package/types/acdc/chaining.d.ts.map +1 -1
  92. package/types/acdc/regbasing.d.ts +7 -6
  93. package/types/acdc/regbasing.d.ts.map +1 -1
  94. package/types/acdc/regengine.d.ts.map +1 -1
  95. package/types/acdc/registering.d.ts +8 -4
  96. package/types/acdc/registering.d.ts.map +1 -1
  97. package/types/acdc/registraring.d.ts.map +1 -1
  98. package/types/acdc/salting.d.ts +53 -6
  99. package/types/acdc/salting.d.ts.map +1 -1
  100. package/types/acdc/unblinding.d.ts.map +1 -1
  101. package/types/app/credentials/root-replicating.d.ts +85 -0
  102. package/types/app/credentials/root-replicating.d.ts.map +1 -0
  103. package/types/app/credentials/verifying.d.ts +2 -1
  104. package/types/app/credentials/verifying.d.ts.map +1 -1
  105. package/types/app/delivery/forwarding.d.ts +10 -0
  106. package/types/app/delivery/forwarding.d.ts.map +1 -1
  107. package/types/app/exchanges/exchanging.d.ts +2 -0
  108. package/types/app/exchanges/exchanging.d.ts.map +1 -1
  109. package/types/app/identity/configing.d.ts +7 -5
  110. package/types/app/identity/configing.d.ts.map +1 -1
  111. package/types/app/identity/habbing.d.ts +23 -5
  112. package/types/app/identity/habbing.d.ts.map +1 -1
  113. package/types/app/identity/keeping.d.ts +1 -1
  114. package/types/app/identity/lmdb-habery.d.ts +6 -0
  115. package/types/app/identity/lmdb-habery.d.ts.map +1 -0
  116. package/types/app/ipex/ipex-graph.d.ts +4 -4
  117. package/types/app/ipex/ipex-graph.d.ts.map +1 -1
  118. package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
  119. package/types/app/ipex/ipex-v2.d.ts +11 -2
  120. package/types/app/ipex/ipex-v2.d.ts.map +1 -1
  121. package/types/app/ipex/ipexing.d.ts +3 -0
  122. package/types/app/ipex/ipexing.d.ts.map +1 -1
  123. package/types/app/mailbox/mailboxing.d.ts +1 -1
  124. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  125. package/types/app/multisig/group-members.d.ts +9 -0
  126. package/types/app/multisig/group-members.d.ts.map +1 -1
  127. package/types/app/multisig/grouping.d.ts.map +1 -1
  128. package/types/app/multisig/multisig-workflows.d.ts +2 -1
  129. package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
  130. package/types/app/notifications/notifying.d.ts +1 -1
  131. package/types/app/notifications/notifying.d.ts.map +1 -1
  132. package/types/app/protocol/parsering.d.ts +3 -1
  133. package/types/app/protocol/parsering.d.ts.map +1 -1
  134. package/types/app/protocol/protocol-processing.d.ts +2 -0
  135. package/types/app/protocol/protocol-processing.d.ts.map +1 -1
  136. package/types/app/runtime/agent-runtime.d.ts +7 -0
  137. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  138. package/types/app/transport/byte-source.d.ts +13 -10
  139. package/types/app/transport/byte-source.d.ts.map +1 -1
  140. package/types/app/transport/cesr-egress.d.ts +21 -7
  141. package/types/app/transport/cesr-egress.d.ts.map +1 -1
  142. package/types/app/transport/file-bytes.d.ts +11 -0
  143. package/types/app/transport/file-bytes.d.ts.map +1 -0
  144. package/types/app/transport/http-client.d.ts +3 -0
  145. package/types/app/transport/http-client.d.ts.map +1 -1
  146. package/types/app/transport/http-messenger.d.ts +15 -10
  147. package/types/app/transport/http-messenger.d.ts.map +1 -1
  148. package/types/app/transport/message-source.d.ts +1 -1
  149. package/types/app/transport/messengering.d.ts +2 -2
  150. package/types/app/transport/messengering.d.ts.map +1 -1
  151. package/types/app/transport/node-http-client.d.ts +10 -0
  152. package/types/app/transport/node-http-client.d.ts.map +1 -0
  153. package/types/app/transport/tcp-dialer.d.ts +21 -0
  154. package/types/app/transport/tcp-dialer.d.ts.map +1 -0
  155. package/types/cli/common/existing.d.ts.map +1 -1
  156. package/types/cli/credentials/ipex.d.ts.map +1 -1
  157. package/types/cli/endpoints/oobi-clean.d.ts.map +1 -1
  158. package/types/cli/identity/init.d.ts.map +1 -1
  159. package/types/cli/index.d.ts +1 -1
  160. package/types/cli/index.d.ts.map +1 -1
  161. package/types/cli/messaging/multisig.d.ts +10 -1
  162. package/types/cli/messaging/multisig.d.ts.map +1 -1
  163. package/types/cli/tools/annotate.d.ts.map +1 -1
  164. package/types/cli/tools/benchmark.d.ts.map +1 -1
  165. package/types/cli/tools/db-dump.d.ts.map +1 -1
  166. package/types/core/bytes.d.ts +2 -0
  167. package/types/core/bytes.d.ts.map +1 -1
  168. package/types/core/kel/delegation-ancestry.d.ts +41 -0
  169. package/types/core/kel/delegation-ancestry.d.ts.map +1 -0
  170. package/types/core/kel/eventing.d.ts.map +1 -1
  171. package/types/core/kel/kever.d.ts +5 -4
  172. package/types/core/kel/kever.d.ts.map +1 -1
  173. package/types/core/mailbox/kram-policy.d.ts.map +1 -1
  174. package/types/core/mailbox/kram-pruning.d.ts +1 -3
  175. package/types/core/mailbox/kram-pruning.d.ts.map +1 -1
  176. package/types/db/basing.d.ts +14 -23
  177. package/types/db/basing.d.ts.map +1 -1
  178. package/types/db/core/dber.d.ts +505 -0
  179. package/types/db/core/dber.d.ts.map +1 -0
  180. package/types/db/core/index.d.ts +4 -2
  181. package/types/db/core/index.d.ts.map +1 -1
  182. package/types/db/core/lmdb-opener.d.ts +17 -0
  183. package/types/db/core/lmdb-opener.d.ts.map +1 -0
  184. package/types/db/core/lmdber.d.ts +60 -383
  185. package/types/db/core/lmdber.d.ts.map +1 -1
  186. package/types/db/core/memstore.d.ts +129 -0
  187. package/types/db/core/memstore.d.ts.map +1 -0
  188. package/types/db/core/path-manager.d.ts +22 -45
  189. package/types/db/core/path-manager.d.ts.map +1 -1
  190. package/types/db/core/store-owner.d.ts +59 -0
  191. package/types/db/core/store-owner.d.ts.map +1 -0
  192. package/types/db/core/webdber.d.ts +127 -0
  193. package/types/db/core/webdber.d.ts.map +1 -0
  194. package/types/db/escrowing.d.ts +3 -3
  195. package/types/db/escrowing.d.ts.map +1 -1
  196. package/types/db/exchange/exchange-nests.d.ts.map +1 -1
  197. package/types/db/keeping.d.ts +9 -23
  198. package/types/db/keeping.d.ts.map +1 -1
  199. package/types/db/koming.d.ts +7 -8
  200. package/types/db/koming.d.ts.map +1 -1
  201. package/types/db/lmdb-stores.d.ts +45 -0
  202. package/types/db/lmdb-stores.d.ts.map +1 -0
  203. package/types/db/lmdb.d.ts +7 -0
  204. package/types/db/lmdb.d.ts.map +1 -0
  205. package/types/db/mailbox/mailboxing.d.ts +7 -21
  206. package/types/db/mailbox/mailboxing.d.ts.map +1 -1
  207. package/types/db/noting.d.ts +7 -12
  208. package/types/db/noting.d.ts.map +1 -1
  209. package/types/db/reger.d.ts +7 -11
  210. package/types/db/reger.d.ts.map +1 -1
  211. package/types/db/replay/witness-replay.d.ts +0 -2
  212. package/types/db/replay/witness-replay.d.ts.map +1 -1
  213. package/types/db/subing.d.ts +24 -25
  214. package/types/db/subing.d.ts.map +1 -1
  215. package/types/npm/db-lmdb.d.ts +4 -0
  216. package/types/npm/db-lmdb.d.ts.map +1 -0
  217. package/types/npm/runtime-node.d.ts +4 -0
  218. package/types/npm/runtime-node.d.ts.map +1 -0
  219. package/types/runtime/index.d.ts +9 -10
  220. package/types/runtime/index.d.ts.map +1 -1
  221. package/types/runtime/node.d.ts +15 -0
  222. package/types/runtime/node.d.ts.map +1 -0
  223. package/types/version.d.ts +2 -2
  224. package/esm/keri/npm/src/acdc/relating.js +0 -59
  225. package/esm/keri/npm/src/db/baser-defaults.js +0 -11
  226. package/types/acdc/relating.d.ts +0 -21
  227. package/types/acdc/relating.d.ts.map +0 -1
  228. package/types/db/baser-defaults.d.ts +0 -5
  229. package/types/db/baser-defaults.d.ts.map +0 -1
@@ -1,8 +1,10 @@
1
1
  /**
2
- * Explicit encrypted copies of existing registry roots.
2
+ * Encrypted copies of existing registry roots.
3
3
  *
4
4
  * `RegistryRoot` replicates held roots to individual members with ESSR `exn`
5
5
  * messages, ported from KERIpy `keri.acdc.salting` (kentbull/keripy v2-spec).
6
+ * Operators send them explicitly; group issuance completion and accepted group
7
+ * rotations also send them, fire and forget (`app/credentials/root-replicating.ts`).
6
8
  *
7
9
  * Local profile, not a KERI, ACDC or #1740 standard. Upstream KERIpy uses ESSR
8
10
  * only as the v1 `/essr/req` transport envelope on direct sends.
@@ -19,9 +21,12 @@
19
21
  * dependency escrow (`Baser.rrpe`, no timeout) that `processEscrows` retries.
20
22
  */
21
23
  import { concatBytes, Counter, Dater, Diger, Encrypter, essrChunks, Kinds, LabelDex, makeBlindUuid, MtrDex, ParserError, Prefixer, Saider, Salter, Texter, unblindBlindState, unblindBoundState, Vrsn_2_0, } from "cesr-ts";
22
- import { groupWitnesserSelection, isLocalGroupHab } from "../app/multisig/group-members.js";
24
+ import { groupWitnesserSelection, isLocalGroupHab, localGroupMember } from "../app/multisig/group-members.js";
23
25
  import { ValidationError } from "../core/errors.js";
26
+ import { Kever } from "../core/kel/kever.js";
24
27
  import { exchange } from "../core/protocol-eventing.js";
28
+ import { dgKey } from "../db/core/keys.js";
29
+ import { hasNests, loadNests } from "../db/exchange/exchange-nests.js";
25
30
  import { makeNowIso8601 } from "../time/mod.js";
26
31
  import { validateRip, validateUpdate, verifyRegistryAnchor, vetBlind } from "./regeventing.js";
27
32
  /** A stored registry-root copy that can never be retained as received. */
@@ -65,9 +70,9 @@ function contribution(db, member, verfers) {
65
70
  const event = db.getEvtSerder(member, said);
66
71
  if (!event?.estive)
67
72
  continue;
68
- const index = keys.indexOf(event.verfers[0]?.qb64 ?? "");
73
+ const key = event.verfers[0]?.qb64 ?? "", index = keys.indexOf(key);
69
74
  if (index >= 0)
70
- return { sn, index };
75
+ return { sn, index, key };
71
76
  }
72
77
  return null;
73
78
  }
@@ -290,14 +295,8 @@ export class RegistryRoot {
290
295
  }
291
296
  return rip;
292
297
  }
293
- /**
294
- * Return current signing member AIDs added by one accepted group rotation.
295
- *
296
- * Members, not keys, are compared: a continuing member usually rotates its
297
- * own key, so it contributes to both establishments. Candidates are the AIDs
298
- * with accepted key state in this Habery.
299
- */
300
- newcomers(ghab, rotation) {
298
+ /** Return `ghab`'s accepted `rotation` and the establishment before it; ValidationError otherwise. */
299
+ rotation(ghab, rotation) {
301
300
  const db = this.hby.db, pre = ghab.pre;
302
301
  if (!pre || ghab.db !== db || !isLocalGroupHab(this.hby, ghab)) {
303
302
  throw new ValidationError("newcomers require a local group habitat");
@@ -306,29 +305,59 @@ export class RegistryRoot {
306
305
  if (!event || !["rot", "drt"].includes(event.ilk) || db.kels.getLast(pre, event.sn) !== rotation) {
307
306
  throw new ValidationError("newcomers require an accepted group rotation");
308
307
  }
309
- let prior = null;
310
308
  for (const [, sn, said] of db.kels.getOnItemBackIter(pre, event.sn - 1)) {
311
309
  if (db.kels.getLast(pre, sn) !== said)
312
310
  continue;
313
311
  const candidate = db.getEvtSerder(pre, said);
314
- if (candidate?.estive) {
315
- prior = candidate;
316
- break;
317
- }
312
+ if (candidate?.estive)
313
+ return [event, candidate];
318
314
  }
319
- if (!prior)
320
- throw new ValidationError("rotation has no accepted prior establishment");
315
+ throw new ValidationError("rotation has no accepted prior establishment");
316
+ }
317
+ /**
318
+ * Return current signing member AIDs added by one accepted group rotation.
319
+ *
320
+ * Members, not keys, are compared: a continuing member usually rotates its
321
+ * own key, so it contributes to both establishments. `rekeyed` also returns
322
+ * continuing members whose contributing key changed, the recipients a
323
+ * re-seal must reach under their new key. Candidates are the AIDs with
324
+ * accepted key state in this Habery.
325
+ */
326
+ newcomers(ghab, rotation, { rekeyed = false } = {}) {
327
+ const db = this.hby.db, pre = ghab.pre, [event, prior] = this.rotation(ghab, rotation);
321
328
  const found = [];
322
329
  for (const [keys] of db.states.getTopItemIter()) {
323
330
  const aid = keys.join(".");
324
331
  if (aid === pre || !db.getKever(aid))
325
332
  continue;
326
- const added = contribution(db, aid, event.verfers);
327
- if (added && !contribution(db, aid, prior.verfers) && this.current(aid, pre))
333
+ const added = contribution(db, aid, event.verfers), before = contribution(db, aid, prior.verfers);
334
+ if (added && this.current(aid, pre) && (!before || rekeyed && before.key !== added.key)) {
328
335
  found.push([added.index, aid]);
336
+ }
329
337
  }
330
338
  return found.sort((left, right) => left[0] - right[0]).map(([, aid]) => aid);
331
339
  }
340
+ /**
341
+ * Return the members `ghab`'s local member re-seals held roots to after `rotation`.
342
+ *
343
+ * Only a signer of the accepted rotation that held a key in the prior
344
+ * establishment sends, and it sends to each current signing member that is
345
+ * new in the rotation or whose contributing key changed (`newcomers` with
346
+ * `rekeyed`). Every such signer sends: copies are idempotent, so no single
347
+ * sender is elected. An empty list means nothing is owed.
348
+ */
349
+ resealed(ghab, rotation) {
350
+ const recipients = this.newcomers(ghab, rotation, { rekeyed: true });
351
+ const db = this.hby.db, [event, prior] = this.rotation(ghab, rotation);
352
+ const member = localGroupMember(this.hby, ghab.pre).pre;
353
+ const own = contribution(db, member, event.verfers);
354
+ if (!own || !contribution(db, member, prior.verfers))
355
+ return [];
356
+ const { indices } = Kever.verifyIndexedSignatures(event.raw, db.sigs.get(dgKey(ghab.pre, rotation)), event.verfers);
357
+ if (!indices.includes(own.index))
358
+ return [];
359
+ return recipients.filter(aid => aid !== member);
360
+ }
332
361
  /** Return the current signing member AIDs of `gid` known to this Habery. */
333
362
  members(gid) {
334
363
  const db = this.hby.db, gkever = db.getKever(gid), found = [];
@@ -365,10 +394,17 @@ export class RegistryRoot {
365
394
  // sending
366
395
  /**
367
396
  * Return one authorized copy of a root held locally for `gid` or the issuer.
397
+ *
368
398
  * `hab` is the local individual sender. `event` defaults to the registry's
369
- * accepted BUP head.
399
+ * accepted BUP head. `td` is the recipient's verification hint: omitted, it
400
+ * is derived for this registry (`hint`); `""` sends none; any other value is
401
+ * an override. Before sealing, a derived or override hint must let this
402
+ * sender's own root reproduce the BUP `b` commitment, so a copy never carries
403
+ * a hint that cannot verify it. `hinted` refuses with RootUnverified, instead
404
+ * of sending a copy without a hint, when no local evidence yields one;
405
+ * automatic sends set it.
370
406
  */
371
- prepare(hab, recipient, gid, registry, { event, td } = {}) {
407
+ prepare(hab, recipient, gid, registry, { event, td, hinted = false } = {}) {
372
408
  const rip = this.authorize(hab.pre, recipient, gid, registry);
373
409
  let root = null;
374
410
  for (const holder of [gid, rip.issuer]) {
@@ -380,8 +416,74 @@ export class RegistryRoot {
380
416
  }
381
417
  if (!root)
382
418
  throw new ValidationError("no local root is held for this registry");
383
- const [bup] = this.event(registry, event ?? this.rgy.store.headEvent(registry).said, rip);
384
- return rootExn(hab, recipient, gid, registry, root, { event: bup.said, td });
419
+ const [bup, sn] = this.event(registry, event ?? this.rgy.store.headEvent(registry).said, rip);
420
+ const uuid = makeBlindUuid({ salt: root.qb64, sn }).qb64;
421
+ let hint = td;
422
+ if (hint === undefined) {
423
+ hint = this.hint(gid, registry, uuid, bup) ?? undefined;
424
+ if (hint === undefined && hinted) {
425
+ throw new RootUnverified(`no held opening or stored Grant ACDC lets this root reproduce registry ${registry} update ${bup.said}; `
426
+ + "send it with an ACDC hint");
427
+ }
428
+ }
429
+ else if (hint && !this.reproduces(bup.sad.b, uuid, hint, gid)) {
430
+ throw new ValidationError(`the local root does not reproduce registry ${registry} update ${bup.said} with ACDC hint ${hint}`);
431
+ }
432
+ return rootExn(hab, recipient, gid, registry, root, { event: bup.said, td: hint });
433
+ }
434
+ /**
435
+ * Return the ACDC SAID by which `uuid` reproduces `bup`'s commitment, or null.
436
+ *
437
+ * Derived per registry, never shared across registries: the ACDC of the
438
+ * BUP's locally held opening first, then each ACDC of `registry` nested in a
439
+ * locally stored exchange. A candidate counts only when it reproduces the
440
+ * commitment, which is the sender self-check.
441
+ */
442
+ hint(gid, registry, uuid, bup) {
443
+ const held = this.rgy.store.publicOpening(bup.said)?.td.nonce;
444
+ const blid = bup.sad.b;
445
+ if (held && this.reproduces(blid, uuid, held, gid))
446
+ return held;
447
+ for (const td of this.acdcs(registry)) {
448
+ if (this.reproduces(blid, uuid, td, gid))
449
+ return td;
450
+ }
451
+ return null;
452
+ }
453
+ /**
454
+ * Yield SAIDs of ACDCs in `registry` nested in any locally stored exchange.
455
+ *
456
+ * A custodian finds them in its own `/ipex/grant`; a member holding only a
457
+ * received copy finds them nested in the `/multisig/exn` proposal it signed.
458
+ * Nests are walked recursively, so the wrapper depth does not matter, and no
459
+ * index table is needed. An exchange whose stored nests no longer decode is
460
+ * skipped: hint search is advisory and the self-check still guards the copy.
461
+ */
462
+ *acdcs(registry) {
463
+ const db = this.hby.db;
464
+ for (const [keys] of db.exns.getTopItemIter()) {
465
+ const said = keys.join(".");
466
+ if (!hasNests(db, said))
467
+ continue;
468
+ let nests;
469
+ try {
470
+ nests = loadNests(db, said);
471
+ }
472
+ catch (error) {
473
+ if (error instanceof ParserError || error instanceof ValidationError)
474
+ continue;
475
+ throw error;
476
+ }
477
+ yield* RegistryRoot.nested(nests, registry);
478
+ }
479
+ }
480
+ /** Depth-first SAIDs of nested bodies whose `rd` is `registry`. */
481
+ static *nested(nests, registry) {
482
+ for (const nest of nests) {
483
+ if (nest.body.ked?.rd === registry && nest.body.said)
484
+ yield nest.body.said;
485
+ yield* RegistryRoot.nested(nest.nests ?? [], registry);
486
+ }
385
487
  }
386
488
  // receiving
387
489
  /** Recheck exact accepted BUP membership and issuer anchoring; return the BUP and its ordinal. */
@@ -537,7 +537,7 @@ export class Unblind {
537
537
  // Serialization may suspend; repeat all authority and snapshot checks inside
538
538
  // the single writer. This does not rewrite another Grant or any Admit.
539
539
  const wires = yield* abortable((signal) => prepareNests(nests, signal));
540
- const result = db.env.transactionSync(() => {
540
+ const result = db.transaction(() => {
541
541
  this.live(op);
542
542
  this.current(op);
543
543
  const retained = db.enst.get([op.grant]);
@@ -553,9 +553,7 @@ export class Unblind {
553
553
  db.exrs.pin([op.grant, node], record);
554
554
  return { kind: "accept", said: op.grant };
555
555
  });
556
- yield* abortable(async () => {
557
- await db.env.flushed;
558
- });
556
+ yield* db.flushed();
559
557
  return result;
560
558
  }
561
559
  /** Discard approved work and unsent replies; delivered bytes remain caller-owned. */
@@ -266,7 +266,7 @@ export class Oobiery {
266
266
  };
267
267
  // A new or resumed attempt supersedes old terminal facts, but keeps its
268
268
  // queued row until intake succeeds. Cancellation can therefore retry it.
269
- this.hby.db.env.transactionSync(() => {
269
+ this.hby.db.transaction(() => {
270
270
  this.pinQueueStore(kind, url, queuedRecord);
271
271
  this.hby.db.eoobi.rem(url);
272
272
  this.hby.db.roobi.rem(url);
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Finite delivery of prepared `/registry/root` copies; transmission is never receipt.
3
+ *
4
+ * `replicateRegistryRoots` is the KERIpy `rootreplicating.replicate` happy path:
5
+ * group commands call it once a send is possible and report each outcome. There
6
+ * is no acknowledgement, sent-copies record or retry; receiving is idempotent,
7
+ * so duplicate copies are harmless, and `tufa vc registry root send` is the
8
+ * recovery path for a copy that was not sent.
9
+ */
10
+ import { parseBytes } from "cesr-ts";
11
+ import { action, race } from "effection";
12
+ import { AppError, ValidationError } from "../../core/errors.js";
13
+ import { CREDENTIAL_MAILBOX_TOPIC } from "../../core/mailbox/mailbox-topics.js";
14
+ import { localGroupMember } from "../multisig/group-members.js";
15
+ /** The operator command that resends a copy automatic replication did not send. */
16
+ export const RootCopyRecovery = "tufa vc registry root send";
17
+ /**
18
+ * Send each copy from `sender` to its recipient's `credential` mailbox, in
19
+ * order, and return one outcome per copy. A failed copy does not stop later
20
+ * copies. The caller runs `postman.deliverDo` for the duration; Poster wait
21
+ * misuse or closure still throws.
22
+ */
23
+ export function* deliverRegistryRoots(postman, sender, copies) {
24
+ const outcomes = [];
25
+ for (const copy of copies) {
26
+ const event = postman.send({ src: sender, dest: copy.recipient, topic: CREDENTIAL_MAILBOX_TOPIC, msg: copy.wire }, { notify: true });
27
+ const outcome = yield* postman.wait(event);
28
+ outcomes.push(outcome.kin === "sent"
29
+ ? { kin: "sent", copy, deliveries: outcome.deliveries }
30
+ : { kin: "failed", copy, error: outcome.error });
31
+ }
32
+ return outcomes;
33
+ }
34
+ /** Run `operation` for at most `milliseconds` of `clock` time; null when the budget ends first. */
35
+ function* within(clock, milliseconds, operation) {
36
+ return yield* race([
37
+ operation,
38
+ action(resolve => {
39
+ const timer = clock.setTimeout(() => resolve(null), Math.max(milliseconds, 0));
40
+ return () => clock.clearTimeout(timer);
41
+ }),
42
+ ]);
43
+ }
44
+ /**
45
+ * Fire and forget one copy of each of `registries`' held roots for group
46
+ * `ghab` to each recipient, signed by `ghab`'s local member.
47
+ *
48
+ * `recipients` defaults to every other current signing member. Each copy is
49
+ * prepared with `RegistryRoot.prepare` and a per-registry derived hint
50
+ * (`hinted`, so a copy that could never verify is reported, not sent) and
51
+ * delivered through `deliverRegistryRoots`, as `tufa vc registry root send`
52
+ * does. `timeoutMs` is this replication's own budget: a copy reached after it
53
+ * ends, or still sending when it ends, is reported unsent. Nothing is retried
54
+ * or recorded and an `AppError` never escapes: every outcome is returned for
55
+ * display. The caller runs `postman.deliverDo` for the duration.
56
+ */
57
+ export function* replicateRegistryRoots(root, postman, clock, ghab, registries, { recipients, timeoutMs }) {
58
+ const gid = ghab.pre, sender = localGroupMember(root.hby, gid), deadline = clock.now() + Math.max(timeoutMs, 0);
59
+ const targets = recipients ?? root.members(gid).filter(aid => aid !== sender.pre);
60
+ const reports = [];
61
+ for (const registry of registries) {
62
+ for (const recipient of targets) {
63
+ const result = {
64
+ phase: "root_copy",
65
+ gid,
66
+ registry,
67
+ sender: sender.pre,
68
+ recipient,
69
+ transmitted: false,
70
+ };
71
+ let failure = null;
72
+ try {
73
+ if (clock.now() >= deadline) {
74
+ throw new ValidationError("registry root copies did not complete within their budget");
75
+ }
76
+ const wire = root.prepare(sender, recipient, gid, registry, { hinted: true });
77
+ result.exchange = parseBytes(wire)[0].body.said;
78
+ const outcome = yield* within(clock, deadline - clock.now(), deliverRegistryRoots(postman, sender.pre, [{ recipient, wire }]));
79
+ if (!outcome)
80
+ failure = new ValidationError("registry root copies did not complete within their budget");
81
+ else if (outcome[0].kin === "failed")
82
+ failure = outcome[0].error ?? "delivery failed";
83
+ }
84
+ catch (error) {
85
+ if (!(error instanceof AppError))
86
+ throw error;
87
+ failure = error;
88
+ }
89
+ reports.push(failure === null ? { ...result, transmitted: true } : {
90
+ ...result,
91
+ error: failure instanceof Error ? failure.message : String(failure),
92
+ recovery: RootCopyRecovery,
93
+ });
94
+ }
95
+ }
96
+ return reports;
97
+ }
98
+ /** Budget for re-seal copies from a command without its own `--timeout`, as KERIpy `rootreplicating.Budget`. */
99
+ export const RootResealBudgetMs = 30_000;
100
+ /**
101
+ * After an accepted group rotation, send held roots to new or rekeyed members.
102
+ *
103
+ * Fire and forget like `replicateRegistryRoots`: only a signer of the rotation
104
+ * that held a prior key sends (`RegistryRoot.resealed`), every such signer
105
+ * sends with no election, a member holding no root for the group sends
106
+ * nothing, and no failure ever fails the rotation. The caller runs
107
+ * `postman.deliverDo` for the duration.
108
+ */
109
+ export function* resealRegistryRoots(root, postman, clock, ghab, rotation, { timeoutMs = RootResealBudgetMs } = {}) {
110
+ let recipients;
111
+ try {
112
+ recipients = root.resealed(ghab, rotation);
113
+ }
114
+ catch (error) {
115
+ if (!(error instanceof AppError))
116
+ throw error;
117
+ return [{ phase: "root_copy", gid: ghab.pre, rotation, transmitted: false, error: error.message }];
118
+ }
119
+ const registries = root.held(ghab.pre);
120
+ if (!recipients.length || !registries.length)
121
+ return [];
122
+ return yield* replicateRegistryRoots(root, postman, clock, ghab, registries, { recipients, timeoutMs });
123
+ }
@@ -7,7 +7,8 @@
7
7
  *
8
8
  * TypeScript divergence:
9
9
  * - ordinary outcomes are explicit decisions instead of exception-only control
10
- * flow; durable corruption and unsupported `DI2I` still fail explicitly
10
+ * flow; durable corruption and unsupported delegated `DI2I`/`DI1I` still
11
+ * fail explicitly
11
12
  */
12
13
  import { Dater, Ilks, Saider, } from "cesr-ts";
13
14
  import { Deck } from "../../core/deck.js";
@@ -268,10 +269,18 @@ export class Verifier {
268
269
  }
269
270
  const attrib = creder.attrib;
270
271
  let operator = op;
271
- if (operator !== "I2I" && operator !== "DI2I" && operator !== "NI2I") {
272
+ if (operator === "I1I" || operator === "DI1I") {
273
+ // Issuer identity suppresses the issuee default and never reads issuees.
274
+ if (creder.issuer !== issuer) {
275
+ if (operator === "I1I")
276
+ return null;
277
+ throw new ValidationError("DI1I credential chain verification is not implemented.");
278
+ }
279
+ }
280
+ else if (operator !== "I2I" && operator !== "DI2I" && operator !== "NI2I") {
272
281
  operator = isRecord(attrib) && typeof attrib.i === "string" ? "I2I" : "NI2I";
273
282
  }
274
- if (operator !== "NI2I") {
283
+ if (operator === "I2I" || operator === "DI2I") {
275
284
  if (!isRecord(attrib) || typeof attrib.i !== "string") {
276
285
  return null;
277
286
  }
@@ -11,9 +11,31 @@ import { abortable } from "../runtime/abortable.js";
11
11
  import { runtimeTurn } from "../runtime/runtime-turn.js";
12
12
  import { CesrHttpRefusal } from "../transport/cesr-egress.js";
13
13
  import { messengerFrom, streamMessengerFrom } from "../transport/messengering.js";
14
- import { TCPMessenger } from "../transport/tcp-messenger.js";
14
+ import { requireTcpDialer } from "../transport/tcp-dialer.js";
15
15
  import { TransportError } from "../transport/transport-error.js";
16
16
  import { introduce } from "./introducing.js";
17
+ /**
18
+ * A v2 message refused for a peer this node must treat as v1.
19
+ *
20
+ * A v1 parser treats a v2 version string as an extraction failure and discards the rest of
21
+ * its stream, so sending would lose more than this one message.
22
+ */
23
+ export class PeerVersionError extends ValidationError {
24
+ }
25
+ /**
26
+ * Whether an AID's own accepted KEL ends in a v1 event, which is the only evidence of a v1 reader.
27
+ *
28
+ * An AID with no accepted KEL is not refused. A delegate awaiting approval has only an escrowed
29
+ * inception, and refusing its approval traffic would stop the delegation from ever completing.
30
+ */
31
+ function knownV1Peer(hby, aid) {
32
+ const kever = hby.db.getKever(aid);
33
+ return kever !== null && kever !== undefined && kever.serder.pvrsn.major < 2;
34
+ }
35
+ /** Whether any frame of this complete signed wire needs a v2 reader. */
36
+ function wireNeedsV2(message) {
37
+ return parseBytes(message).some(frame => frame.body.pvrsn.major >= 2 || (frame.gvrsn?.major ?? 1) >= 2);
38
+ }
17
39
  /**
18
40
  * Preserve resolved endpoint schemes and signed bytes for concrete transport construction.
19
41
  *
@@ -374,11 +396,20 @@ export class Poster {
374
396
  failed.push(new ValidationError(`No supported endpoint URL for ${endpoint.eid}.`));
375
397
  continue;
376
398
  }
377
- const target = yield* this.prepareBytesTarget(hab, route, {
378
- eid: endpoint.eid,
379
- url: endpoint.url,
380
- urls: endpoint.urls,
381
- }, evt);
399
+ let target;
400
+ try {
401
+ target = yield* this.prepareBytesTarget(hab, route, {
402
+ eid: endpoint.eid,
403
+ url: endpoint.url,
404
+ urls: endpoint.urls,
405
+ }, evt);
406
+ }
407
+ catch (error) {
408
+ if (!(error instanceof PeerVersionError))
409
+ throw error;
410
+ failed.push(error);
411
+ continue;
412
+ }
382
413
  try {
383
414
  if (target.kind !== "stored") {
384
415
  const refusal = yield* this.sendPreparedTarget(target, evt, signal);
@@ -416,7 +447,7 @@ export class Poster {
416
447
  *sendPreparedTarget(target, evt, signal) {
417
448
  if (target.kind === "tcp" && evt.replies) {
418
449
  // Explicit duplex work retains its receive owner; send-only factories need none.
419
- yield* new TCPMessenger(target.url, this.clock, this.protocol).send(target.message, {
450
+ yield* requireTcpDialer(this.http).messenger(target.url, this.clock, this.protocol).send(target.message, {
420
451
  signal,
421
452
  replies: true,
422
453
  });
@@ -482,11 +513,18 @@ export class Poster {
482
513
  * @param endpoint One selected endpoint AID and URL from the route.
483
514
  * @param evt Original signed source and concrete ordinary/stream operation.
484
515
  * @returns A prepared remote target, or stored after authorized local mailbox publication.
516
+ * @throws PeerVersionError when the wire needs a v2 reader that this peer is known not to be.
485
517
  */
486
518
  *prepareBytesTarget(hab, route, endpoint, evt) {
519
+ if (wireNeedsV2(evt.msg) && knownV1Peer(this.hby, evt.dest)) {
520
+ throw new PeerVersionError(`Refusing a v2 message for ${evt.dest}, whose latest accepted event is v1.`);
521
+ }
487
522
  if (route.kind !== "direct") {
488
523
  return yield* this.prepareForwardedTarget(hab, evt.dest, evt.topic, evt, endpoint);
489
524
  }
525
+ if (wireNeedsV2(evt.msg) && knownV1Peer(this.hby, endpoint.eid)) {
526
+ throw new PeerVersionError(`Refusing a v2 message for endpoint ${endpoint.eid}, whose latest accepted event is v1.`);
527
+ }
490
528
  return remoteCesrTarget(endpoint, evt.msg);
491
529
  }
492
530
  /**
@@ -516,6 +554,10 @@ export class Poster {
516
554
  else {
517
555
  msgs.push(evt.msg);
518
556
  }
557
+ // The provider reads the envelope, so it must read v2 whenever the envelope or child is v2.
558
+ if ((hab.kelVersion.major >= 2 || msgs.some(wireNeedsV2)) && knownV1Peer(this.hby, endpoint.eid)) {
559
+ throw new PeerVersionError(`Refusing a v2 envelope for provider ${endpoint.eid}, whose latest accepted event is v1.`);
560
+ }
519
561
  const forwarded = [];
520
562
  for (const message of msgs)
521
563
  forwarded.push(yield* buildForwardedDelivery(hab, { recipient, topic, message }));
@@ -142,8 +142,11 @@ export class Exchanger {
142
142
  }
143
143
  /** Resolve the configured body-version route owner without dispatching or changing evidence. */
144
144
  handlerFor(serder) {
145
- const route = serder.route ?? "";
146
- return this.versionedRoutes.get(route)?.get(serder.pvrsn.major) ?? this.routes.get(route);
145
+ return this.routeHandler(serder.route ?? "", serder.pvrsn.major);
146
+ }
147
+ /** Resolve the owner one route and protocol major would dispatch to, without a message body. */
148
+ routeHandler(route, major) {
149
+ return this.versionedRoutes.get(route)?.get(major) ?? this.routes.get(route);
147
150
  }
148
151
  /**
149
152
  * Verify, persist, and dispatch one inbound `exn`.
@@ -209,7 +212,7 @@ export class Exchanger {
209
212
  const nests = args.nests?.length
210
213
  ? yield* abortable((signal) => prepareNests(args.nests, signal))
211
214
  : undefined;
212
- const result = this.hby.db.env.transactionSync(() => {
215
+ const result = this.hby.db.transaction(() => {
213
216
  if (selectedGrant) {
214
217
  const db = this.hby.db;
215
218
  const stored = selectedGrant === "proof" ? db.epse.get([said]) : db.exns.get([said]) ?? db.epse.get([said]);
@@ -268,9 +271,7 @@ export class Exchanger {
268
271
  return admission;
269
272
  });
270
273
  // Durable completion joins the actual LMDB flush; no external file lifetime remains.
271
- yield* abortable(async () => {
272
- await this.hby.db.env.flushed;
273
- });
274
+ yield* this.hby.db.flushed();
274
275
  if (!result)
275
276
  return undefined;
276
277
  for (const cue of result.cues)
@@ -448,7 +449,7 @@ export class Exchanger {
448
449
  const table = schema ? this.hby.db.emse : this.hby.db.epse;
449
450
  let end;
450
451
  // Capture a finite key horizon without retaining bodies or holding a cursor across mutations.
451
- for (const key of table.sdb.getKeys({ reverse: true, limit: 1 })) {
452
+ for (const key of this.hby.db.scanKeys(table.sdb, { reverse: true, limit: 1 })) {
452
453
  end = b(`${textDecoder.decode(key)}\0`);
453
454
  }
454
455
  if (!end) {
@@ -457,7 +458,7 @@ export class Exchanger {
457
458
  let start;
458
459
  for (;;) {
459
460
  let physical;
460
- for (const key of table.sdb.getKeys({ start, end, limit: 1 })) {
461
+ for (const key of this.hby.db.scanKeys(table.sdb, { start, end, limit: 1 })) {
461
462
  physical = textDecoder.decode(key);
462
463
  }
463
464
  if (!physical)
@@ -1291,7 +1292,7 @@ export class Exchanger {
1291
1292
  /** Decide expiry and remove pending rows in the same native transaction. */
1292
1293
  *expirePending(serder) {
1293
1294
  const said = serder.said;
1294
- const result = this.hby.db.env.transactionSync(() => {
1295
+ const result = this.hby.db.transaction(() => {
1295
1296
  const status = this.escrowStatus(said);
1296
1297
  if (status.kind === "accepted") {
1297
1298
  this.clearPending(serder);
@@ -1301,9 +1302,7 @@ export class Exchanger {
1301
1302
  return undefined;
1302
1303
  return this.retireCurrent(serder, this.expiryReport(status));
1303
1304
  });
1304
- yield* abortable(async () => {
1305
- await this.hby.db.env.flushed;
1306
- });
1305
+ yield* this.hby.db.flushed();
1307
1306
  return result;
1308
1307
  }
1309
1308
  /** Clear only dependency markers, keeping all accepted evidence and metadata intact. */