keri-ts 2.1.0 → 2.2.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 (294) hide show
  1. package/README.md +30 -2
  2. package/esm/keri/npm/src/acdc/authoring.js +195 -0
  3. package/esm/keri/npm/src/acdc/custody.js +164 -0
  4. package/esm/keri/npm/src/acdc/dhking.js +244 -32
  5. package/esm/keri/npm/src/acdc/regbasing.js +10 -0
  6. package/esm/keri/npm/src/acdc/salting.js +16 -1
  7. package/esm/keri/npm/src/acdc/scheming.js +93 -54
  8. package/esm/keri/npm/src/acdc/unblinding.js +118 -95
  9. package/esm/keri/npm/src/app/bootstrap/authenticating.js +4 -6
  10. package/esm/keri/npm/src/app/bootstrap/oobi-resolving.js +41 -0
  11. package/esm/keri/npm/src/app/bootstrap/oobiery.js +4 -6
  12. package/esm/keri/npm/src/app/credentials/credential-revoking.js +1 -0
  13. package/esm/keri/npm/src/app/credentials/credential-workflows.js +2 -0
  14. package/esm/keri/npm/src/app/credentials/registry-settling.js +44 -0
  15. package/esm/keri/npm/src/app/credentials/root-replicating.js +68 -60
  16. package/esm/keri/npm/src/app/credentials/root-replicator.js +198 -0
  17. package/esm/keri/npm/src/app/credentials/tel-completion.js +36 -0
  18. package/esm/keri/npm/src/app/delivery/forwarding.js +16 -3
  19. package/esm/keri/npm/src/app/exchanges/exchanging.js +14 -4
  20. package/esm/keri/npm/src/app/identity/challenging.js +50 -0
  21. package/esm/keri/npm/src/app/identity/delegating.js +77 -77
  22. package/esm/keri/npm/src/app/identity/delegation-approval.js +333 -0
  23. package/esm/keri/npm/src/app/identity/event-completion.js +154 -0
  24. package/esm/keri/npm/src/app/identity/habbing.js +129 -6
  25. package/esm/keri/npm/src/app/identity/keeping.js +77 -4
  26. package/esm/keri/npm/src/app/ipex/group-ipex.js +327 -0
  27. package/esm/keri/npm/src/app/ipex/group-issuance.js +902 -0
  28. package/esm/keri/npm/src/app/ipex/ipex-admitting.js +120 -0
  29. package/esm/keri/npm/src/app/ipex/ipex-conversation.js +197 -0
  30. package/esm/keri/npm/src/app/ipex/ipex-discloser.js +591 -0
  31. package/esm/keri/npm/src/app/ipex/ipex-granting.js +93 -0
  32. package/esm/keri/npm/src/app/ipex/ipex-grouping.js +34 -11
  33. package/esm/keri/npm/src/app/ipex/issuance.js +888 -0
  34. package/esm/keri/npm/src/app/ipex/pending-presentations.js +46 -0
  35. package/esm/keri/npm/src/app/ipex/presentation-files.js +200 -0
  36. package/esm/keri/npm/src/app/ipex/presenting.js +401 -0
  37. package/esm/keri/npm/src/app/ipex/receive-issuance.js +86 -0
  38. package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +660 -0
  39. package/esm/keri/npm/src/app/mailbox/ipex-arrival.js +103 -0
  40. package/esm/keri/npm/src/app/mailbox/kel-publisher.js +322 -0
  41. package/esm/keri/npm/src/app/mailbox/kel-publishing.js +65 -0
  42. package/esm/keri/npm/src/app/mailbox/kel-submission.js +24 -0
  43. package/esm/keri/npm/src/app/mailbox/mailbox-admin.js +208 -0
  44. package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +212 -47
  45. package/esm/keri/npm/src/app/mailbox/mailboxing.js +10 -5
  46. package/esm/keri/npm/src/app/multisig/counseling.js +482 -0
  47. package/esm/keri/npm/src/app/multisig/group-members.js +18 -0
  48. package/esm/keri/npm/src/app/multisig/multisig-workflows.js +94 -139
  49. package/esm/keri/npm/src/app/multisig/proposals.js +281 -0
  50. package/esm/keri/npm/src/app/multisig/proposing.js +122 -0
  51. package/esm/keri/npm/src/app/notifications/signaling.js +5 -5
  52. package/esm/keri/npm/src/app/protocol/parsering.js +26 -1
  53. package/esm/keri/npm/src/app/protocol/protocol-processing.js +9 -3
  54. package/esm/keri/npm/src/app/queries/query-workflows.js +98 -0
  55. package/esm/keri/npm/src/app/runtime/agent-runtime.js +106 -55
  56. package/esm/keri/npm/src/app/runtime/cue-runtime.js +3 -6
  57. package/esm/keri/npm/src/app/runtime/delivery-outbox.js +87 -0
  58. package/esm/keri/npm/src/app/runtime/doing.js +17 -0
  59. package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +207 -0
  60. package/esm/keri/npm/src/app/runtime/operation-owners.js +151 -0
  61. package/esm/keri/npm/src/app/runtime/retry-backoff.js +93 -0
  62. package/esm/keri/npm/src/app/runtime/workflow-contract.js +80 -0
  63. package/esm/keri/npm/src/app/transport/http-client.js +25 -6
  64. package/esm/keri/npm/src/app/witness/http-endpoints.js +4 -4
  65. package/esm/keri/npm/src/cli/common/event-completion.js +49 -0
  66. package/esm/keri/npm/src/cli/common/runtime-workers.js +23 -0
  67. package/esm/keri/npm/src/cli/credentials/ipex.js +157 -190
  68. package/esm/keri/npm/src/cli/credentials/vc.js +53 -72
  69. package/esm/keri/npm/src/cli/endpoints/did.js +12 -3
  70. package/esm/keri/npm/src/cli/endpoints/oobi.js +10 -13
  71. package/esm/keri/npm/src/cli/identity/challenge.js +8 -34
  72. package/esm/keri/npm/src/cli/identity/delegate.js +81 -258
  73. package/esm/keri/npm/src/cli/identity/incept.js +15 -32
  74. package/esm/keri/npm/src/cli/identity/interact.js +5 -17
  75. package/esm/keri/npm/src/cli/identity/rotate.js +8 -36
  76. package/esm/keri/npm/src/cli/index.js +3 -1
  77. package/esm/keri/npm/src/cli/messaging/group-join.js +79 -0
  78. package/esm/keri/npm/src/cli/messaging/group-wait.js +88 -0
  79. package/esm/keri/npm/src/cli/messaging/multisig.js +163 -284
  80. package/esm/keri/npm/src/cli/messaging/root-copy-lines.js +31 -0
  81. package/esm/keri/npm/src/core/kel/eventing.js +66 -10
  82. package/esm/keri/npm/src/core/mailbox/kram-policy.js +39 -6
  83. package/esm/keri/npm/src/core/mailbox/kraming.js +2 -0
  84. package/esm/keri/npm/src/core/records.js +23 -0
  85. package/esm/keri/npm/src/core/routing.js +13 -0
  86. package/esm/keri/npm/src/db/basing.js +34 -2
  87. package/esm/keri/npm/src/db/core/indexeddb-persistence.js +221 -0
  88. package/esm/keri/npm/src/db/indexeddb.js +98 -0
  89. package/esm/keri/npm/src/db/keeping.js +11 -2
  90. package/esm/keri/npm/src/db/store-lock.js +150 -0
  91. package/esm/keri/npm/src/npm/db-indexeddb.browser.js +2 -0
  92. package/esm/keri/npm/src/npm/db-indexeddb.js +3 -0
  93. package/esm/keri/npm/src/npm/db.browser.js +2 -0
  94. package/esm/keri/npm/src/npm/index.browser.js +9 -0
  95. package/esm/keri/npm/src/npm/runtime-web.browser.js +2 -0
  96. package/esm/keri/npm/src/npm/runtime-web.js +3 -0
  97. package/esm/keri/npm/src/npm/runtime.browser.js +2 -0
  98. package/esm/keri/npm/src/runtime/index.js +51 -4
  99. package/esm/keri/npm/src/runtime/web/output-barrier.js +47 -0
  100. package/esm/keri/npm/src/runtime/web/persistent-storage.js +37 -0
  101. package/esm/keri/npm/src/runtime/web.js +10 -0
  102. package/esm/keri/npm/src/vdr/tel-publication.js +16 -0
  103. package/esm/keri/npm/src/version.js +2 -2
  104. package/package.json +15 -2
  105. package/types/acdc/authoring.d.ts +54 -0
  106. package/types/acdc/authoring.d.ts.map +1 -0
  107. package/types/acdc/custody.d.ts +77 -0
  108. package/types/acdc/custody.d.ts.map +1 -0
  109. package/types/acdc/dhking.d.ts +94 -3
  110. package/types/acdc/dhking.d.ts.map +1 -1
  111. package/types/acdc/regbasing.d.ts +4 -0
  112. package/types/acdc/regbasing.d.ts.map +1 -1
  113. package/types/acdc/salting.d.ts +6 -1
  114. package/types/acdc/salting.d.ts.map +1 -1
  115. package/types/acdc/scheming.d.ts +2 -0
  116. package/types/acdc/scheming.d.ts.map +1 -1
  117. package/types/acdc/unblinding.d.ts +58 -19
  118. package/types/acdc/unblinding.d.ts.map +1 -1
  119. package/types/app/bootstrap/authenticating.d.ts +3 -1
  120. package/types/app/bootstrap/authenticating.d.ts.map +1 -1
  121. package/types/app/bootstrap/oobi-resolving.d.ts +92 -0
  122. package/types/app/bootstrap/oobi-resolving.d.ts.map +1 -0
  123. package/types/app/bootstrap/oobiery.d.ts +3 -1
  124. package/types/app/bootstrap/oobiery.d.ts.map +1 -1
  125. package/types/app/credentials/credential-revoking.d.ts +3 -0
  126. package/types/app/credentials/credential-revoking.d.ts.map +1 -1
  127. package/types/app/credentials/credential-workflows.d.ts +5 -0
  128. package/types/app/credentials/credential-workflows.d.ts.map +1 -1
  129. package/types/app/credentials/registry-settling.d.ts +85 -0
  130. package/types/app/credentials/registry-settling.d.ts.map +1 -0
  131. package/types/app/credentials/root-replicating.d.ts +56 -44
  132. package/types/app/credentials/root-replicating.d.ts.map +1 -1
  133. package/types/app/credentials/root-replicator.d.ts +105 -0
  134. package/types/app/credentials/root-replicator.d.ts.map +1 -0
  135. package/types/app/credentials/tel-completion.d.ts +55 -0
  136. package/types/app/credentials/tel-completion.d.ts.map +1 -0
  137. package/types/app/delivery/forwarding.d.ts +6 -0
  138. package/types/app/delivery/forwarding.d.ts.map +1 -1
  139. package/types/app/exchanges/exchanging.d.ts.map +1 -1
  140. package/types/app/identity/challenging.d.ts +54 -0
  141. package/types/app/identity/challenging.d.ts.map +1 -1
  142. package/types/app/identity/delegating.d.ts +21 -4
  143. package/types/app/identity/delegating.d.ts.map +1 -1
  144. package/types/app/identity/delegation-approval.d.ts +228 -0
  145. package/types/app/identity/delegation-approval.d.ts.map +1 -0
  146. package/types/app/identity/event-completion.d.ts +151 -0
  147. package/types/app/identity/event-completion.d.ts.map +1 -0
  148. package/types/app/identity/habbing.d.ts +67 -2
  149. package/types/app/identity/habbing.d.ts.map +1 -1
  150. package/types/app/identity/keeping.d.ts +49 -3
  151. package/types/app/identity/keeping.d.ts.map +1 -1
  152. package/types/app/ipex/group-ipex.d.ts +247 -0
  153. package/types/app/ipex/group-ipex.d.ts.map +1 -0
  154. package/types/app/ipex/group-issuance.d.ts +281 -0
  155. package/types/app/ipex/group-issuance.d.ts.map +1 -0
  156. package/types/app/ipex/ipex-admitting.d.ts +47 -0
  157. package/types/app/ipex/ipex-admitting.d.ts.map +1 -0
  158. package/types/app/ipex/ipex-conversation.d.ts +195 -0
  159. package/types/app/ipex/ipex-conversation.d.ts.map +1 -0
  160. package/types/app/ipex/ipex-discloser.d.ts +156 -0
  161. package/types/app/ipex/ipex-discloser.d.ts.map +1 -0
  162. package/types/app/ipex/ipex-granting.d.ts +145 -0
  163. package/types/app/ipex/ipex-granting.d.ts.map +1 -0
  164. package/types/app/ipex/ipex-grouping.d.ts +18 -0
  165. package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
  166. package/types/app/ipex/issuance.d.ts +443 -0
  167. package/types/app/ipex/issuance.d.ts.map +1 -0
  168. package/types/app/ipex/pending-presentations.d.ts +25 -0
  169. package/types/app/ipex/pending-presentations.d.ts.map +1 -0
  170. package/types/app/ipex/presentation-files.d.ts +47 -0
  171. package/types/app/ipex/presentation-files.d.ts.map +1 -0
  172. package/types/app/ipex/presenting.d.ts +298 -0
  173. package/types/app/ipex/presenting.d.ts.map +1 -0
  174. package/types/app/ipex/receive-issuance.d.ts +99 -0
  175. package/types/app/ipex/receive-issuance.d.ts.map +1 -0
  176. package/types/app/ipex/unblinding-workflow.d.ts +255 -0
  177. package/types/app/ipex/unblinding-workflow.d.ts.map +1 -0
  178. package/types/app/mailbox/ipex-arrival.d.ts +104 -0
  179. package/types/app/mailbox/ipex-arrival.d.ts.map +1 -0
  180. package/types/app/mailbox/kel-publisher.d.ts +141 -0
  181. package/types/app/mailbox/kel-publisher.d.ts.map +1 -0
  182. package/types/app/mailbox/kel-publishing.d.ts +70 -0
  183. package/types/app/mailbox/kel-publishing.d.ts.map +1 -0
  184. package/types/app/mailbox/kel-submission.d.ts +18 -0
  185. package/types/app/mailbox/kel-submission.d.ts.map +1 -0
  186. package/types/app/mailbox/mailbox-admin.d.ts +113 -0
  187. package/types/app/mailbox/mailbox-admin.d.ts.map +1 -0
  188. package/types/app/mailbox/mailbox-poller.d.ts +92 -3
  189. package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
  190. package/types/app/mailbox/mailboxing.d.ts +3 -0
  191. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  192. package/types/app/multisig/counseling.d.ts +196 -0
  193. package/types/app/multisig/counseling.d.ts.map +1 -0
  194. package/types/app/multisig/group-members.d.ts +11 -0
  195. package/types/app/multisig/group-members.d.ts.map +1 -1
  196. package/types/app/multisig/grouping.d.ts +39 -20
  197. package/types/app/multisig/grouping.d.ts.map +1 -1
  198. package/types/app/multisig/multisig-workflows.d.ts +91 -22
  199. package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
  200. package/types/app/multisig/proposals.d.ts +129 -0
  201. package/types/app/multisig/proposals.d.ts.map +1 -0
  202. package/types/app/multisig/proposing.d.ts +125 -0
  203. package/types/app/multisig/proposing.d.ts.map +1 -0
  204. package/types/app/notifications/signaling.d.ts +3 -1
  205. package/types/app/notifications/signaling.d.ts.map +1 -1
  206. package/types/app/protocol/parsering.d.ts +13 -0
  207. package/types/app/protocol/parsering.d.ts.map +1 -1
  208. package/types/app/protocol/protocol-processing.d.ts +7 -1
  209. package/types/app/protocol/protocol-processing.d.ts.map +1 -1
  210. package/types/app/queries/query-workflows.d.ts +98 -0
  211. package/types/app/queries/query-workflows.d.ts.map +1 -0
  212. package/types/app/runtime/agent-runtime.d.ts +72 -25
  213. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  214. package/types/app/runtime/cue-runtime.d.ts +2 -1
  215. package/types/app/runtime/cue-runtime.d.ts.map +1 -1
  216. package/types/app/runtime/delivery-outbox.d.ts +68 -0
  217. package/types/app/runtime/delivery-outbox.d.ts.map +1 -0
  218. package/types/app/runtime/doing.d.ts +27 -0
  219. package/types/app/runtime/doing.d.ts.map +1 -0
  220. package/types/app/runtime/ipex-auxiliaries.d.ts +74 -0
  221. package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -0
  222. package/types/app/runtime/operation-owners.d.ts +137 -0
  223. package/types/app/runtime/operation-owners.d.ts.map +1 -0
  224. package/types/app/runtime/retry-backoff.d.ts +49 -0
  225. package/types/app/runtime/retry-backoff.d.ts.map +1 -0
  226. package/types/app/runtime/workflow-contract.d.ts +79 -0
  227. package/types/app/runtime/workflow-contract.d.ts.map +1 -0
  228. package/types/app/transport/http-client.d.ts.map +1 -1
  229. package/types/cli/common/event-completion.d.ts +26 -0
  230. package/types/cli/common/event-completion.d.ts.map +1 -0
  231. package/types/cli/common/runtime-workers.d.ts +23 -0
  232. package/types/cli/common/runtime-workers.d.ts.map +1 -0
  233. package/types/cli/credentials/ipex.d.ts.map +1 -1
  234. package/types/cli/credentials/vc.d.ts +1 -1
  235. package/types/cli/credentials/vc.d.ts.map +1 -1
  236. package/types/cli/endpoints/did.d.ts.map +1 -1
  237. package/types/cli/endpoints/oobi.d.ts +3 -1
  238. package/types/cli/endpoints/oobi.d.ts.map +1 -1
  239. package/types/cli/identity/challenge.d.ts +1 -1
  240. package/types/cli/identity/challenge.d.ts.map +1 -1
  241. package/types/cli/identity/delegate.d.ts +14 -1
  242. package/types/cli/identity/delegate.d.ts.map +1 -1
  243. package/types/cli/identity/incept.d.ts.map +1 -1
  244. package/types/cli/identity/interact.d.ts.map +1 -1
  245. package/types/cli/identity/rotate.d.ts.map +1 -1
  246. package/types/cli/index.d.ts +3 -1
  247. package/types/cli/index.d.ts.map +1 -1
  248. package/types/cli/messaging/group-join.d.ts +53 -0
  249. package/types/cli/messaging/group-join.d.ts.map +1 -0
  250. package/types/cli/messaging/group-wait.d.ts +34 -0
  251. package/types/cli/messaging/group-wait.d.ts.map +1 -0
  252. package/types/cli/messaging/multisig.d.ts +28 -5
  253. package/types/cli/messaging/multisig.d.ts.map +1 -1
  254. package/types/cli/messaging/root-copy-lines.d.ts +39 -0
  255. package/types/cli/messaging/root-copy-lines.d.ts.map +1 -0
  256. package/types/core/cues.d.ts +11 -0
  257. package/types/core/cues.d.ts.map +1 -1
  258. package/types/core/kel/eventing.d.ts.map +1 -1
  259. package/types/core/mailbox/kram-policy.d.ts +14 -0
  260. package/types/core/mailbox/kram-policy.d.ts.map +1 -1
  261. package/types/core/mailbox/kraming.d.ts +1 -1
  262. package/types/core/mailbox/kraming.d.ts.map +1 -1
  263. package/types/core/records.d.ts +61 -0
  264. package/types/core/records.d.ts.map +1 -1
  265. package/types/core/routing.d.ts +6 -0
  266. package/types/core/routing.d.ts.map +1 -1
  267. package/types/db/basing.d.ts +8 -2
  268. package/types/db/basing.d.ts.map +1 -1
  269. package/types/db/core/indexeddb-persistence.d.ts +59 -0
  270. package/types/db/core/indexeddb-persistence.d.ts.map +1 -0
  271. package/types/db/indexeddb.d.ts +37 -0
  272. package/types/db/indexeddb.d.ts.map +1 -0
  273. package/types/db/keeping.d.ts +5 -2
  274. package/types/db/keeping.d.ts.map +1 -1
  275. package/types/db/store-lock.d.ts +72 -0
  276. package/types/db/store-lock.d.ts.map +1 -0
  277. package/types/npm/db-indexeddb.d.ts +4 -0
  278. package/types/npm/db-indexeddb.d.ts.map +1 -0
  279. package/types/npm/runtime-web.d.ts +4 -0
  280. package/types/npm/runtime-web.d.ts.map +1 -0
  281. package/types/runtime/index.d.ts +41 -6
  282. package/types/runtime/index.d.ts.map +1 -1
  283. package/types/runtime/web/output-barrier.d.ts +30 -0
  284. package/types/runtime/web/output-barrier.d.ts.map +1 -0
  285. package/types/runtime/web/persistent-storage.d.ts +36 -0
  286. package/types/runtime/web/persistent-storage.d.ts.map +1 -0
  287. package/types/runtime/web.d.ts +11 -0
  288. package/types/runtime/web.d.ts.map +1 -0
  289. package/types/vdr/tel-publication.d.ts +6 -0
  290. package/types/vdr/tel-publication.d.ts.map +1 -1
  291. package/types/version.d.ts +2 -2
  292. package/esm/keri/npm/src/cli/common/tel-completion.js +0 -24
  293. package/types/cli/common/tel-completion.d.ts +0 -8
  294. package/types/cli/common/tel-completion.d.ts.map +0 -1
@@ -2,33 +2,35 @@ import { Organizer } from "../identity/organizing.js";
2
2
  /**
3
3
  * Multisig proposal and approval workflows.
4
4
  *
5
- * CLI orchestration owns timed waits, worker lifetimes, prompts and output.
6
5
  * This module publishes proposals and approves stored KEL/RPY/VDR proposals
7
- * without starting or driving background workers.
6
+ * without starting or driving background workers. `approveGroupProposal` is the
7
+ * only caller that approves; it never asks, because calling it is the consent.
8
8
  */
9
- import { attachmentPayload, collectPayload, concatBytes, Counter, Diger, Ilks, NumberPrimitive, NumDex, parseBytes, Prefixer, SerderACDC, SerderKERI, TraitDex, Vrsn_2_0, } from "cesr-ts";
9
+ import { attachmentPayload, collectPayload, concatBytes, Counter, Diger, Ilks, NumberPrimitive, NumDex, parseBytes, Prefixer, SerderACDC, SerderKERI, TraitDex, Verfer, Vrsn_2_0, } from "cesr-ts";
10
10
  import { verifyRegistryAnchor, vetBlind, } from "../../acdc/regeventing.js";
11
11
  import { publicOpeningAttachment } from "../../acdc/registering.js";
12
12
  import { ValidationError } from "../../core/errors.js";
13
- import { isV2RegistryRoute } from "../../core/exchange/protocol-exchanging.js";
14
13
  import { Kever } from "../../core/kel/kever.js";
15
14
  import { messagize } from "../../core/protocol-serialization.js";
16
15
  import { RegistryRecord } from "../../core/records.js";
16
+ import { dgKey } from "../../db/core/keys.js";
17
17
  import { loadNests } from "../../db/exchange/exchange-nests.js";
18
18
  import { Credentialer, Registry, serializeCredential } from "../../vdr/credentialing.js";
19
19
  import { envelopeFromFrame } from "../protocol/parsering.js";
20
20
  import { groupCredentialAnchorReady, validateCredentialIssueProposal, validateCredentialRevocationProposal, validateCredentialSource, validateRegistryName, validateRegistryProposal, } from "../credentials/credential-proposals.js";
21
+ import { telEventStatus } from "../credentials/tel-completion.js";
21
22
  import { endpointRoleAccepted, groupEndorseReply, groupEventKeys, groupSigningMembers, localGroupMember, signLocalGroupEvent, } from "../identity/endpoint-roleing.js";
22
23
  import { Reactor } from "../protocol/reactor.js";
23
24
  import { abortable } from "../runtime/abortable.js";
24
25
  import { collectBytes } from "../transport/byte-source.js";
25
26
  import { findLocalGroupMember as findLocalMember, groupMemberHasKey, uniqueMembers } from "./group-members.js";
26
27
  import { acdcV2RegistryProposal, MULTISIG_ICP_ROUTE, MULTISIG_ISS_ROUTE, MULTISIG_IXN_ROUTE, MULTISIG_REV_ROUTE, MULTISIG_ROT_ROUTE, MULTISIG_RPY_ROUTE, MULTISIG_VCP_ROUTE, multisigChild, multisigChildEvidence, multisigChildGenusAttrs, prepareAcdcV2RegistryExn, prepareMultisigEventExn, prepareMultisigVdrExn, registryProposalEvent, } from "./grouping.js";
27
- import { admitAndSendGroupMessage, groupSendResult, multisigPayloadSaid, prepareGroupMessage, remoteGroupMembers, signGroupMessage, storedGroupMessage, } from "./multisig-messaging.js";
28
+ import { admitAndSendGroupMessage, groupSendResult, prepareGroupMessage, remoteGroupMembers, signGroupMessage, storedGroupMessage, } from "./multisig-messaging.js";
28
29
  /** Approve one exact ACDC v2 TEL/anchor through existing member signing and ordinary processors.
29
30
  * A returned wrapper records consent, not quorum/witness/TEL completion; retry the accepted-state owners.
31
+ * `resume` reuses this member's recorded anchor signatures after a crash instead of signing again.
30
32
  */
31
- export function* approveAcdcV2Registry(hby, protocol, group, event, { anchor, opening } = {}) {
33
+ export function* approveAcdcV2Registry(hby, protocol, group, event, { anchor, opening, resume } = {}) {
32
34
  const registry = protocol.registry, member = localGroupMember(hby, group.pre);
33
35
  if (!registry || group.db !== hby.db || registry.db !== hby.db) {
34
36
  throw new ValidationError("ACDC v2 group registry owner is unavailable.");
@@ -105,7 +107,10 @@ export function* approveAcdcV2Registry(hby, protocol, group, event, { anchor, op
105
107
  throw new ValidationError("Registry anchor does not extend the current group event.");
106
108
  }
107
109
  const keys = groupEventKeys(hby, group.pre, anchor);
108
- const sigers = signLocalGroupEvent(hby, anchor, groupSigningMembers(hby, group.pre), keys);
110
+ const members = groupSigningMembers(hby, group.pre);
111
+ // A resuming caller reuses the signatures the first attempt recorded in `sigs.` instead of signing the anchor again.
112
+ const sigers = (resume ? recordedLocalSignatures(hby, anchor, group.pre, members) : null)
113
+ ?? signLocalGroupEvent(hby, anchor, members, keys);
109
114
  const own = messagize(anchor, { sigers, gvrsn: Vrsn_2_0 });
110
115
  const tel = concatBytes(event.raw, Counter.enclose({
111
116
  code: "AttachmentGroup",
@@ -222,109 +227,34 @@ export function routeForKelEvent(serder) {
222
227
  }
223
228
  }
224
229
  /**
225
- * Select accepted coordination evidence before any approval effects.
226
- * An explicit SAID names the outer EXN and survives notification consumption.
227
- * Notices discover candidates; their attributes do not authorize a route or group.
228
- * An explicit accepted wrapper missing its handler index is replayed by the caller.
230
+ * Approve and publish this member's contribution to one `rip` or `bup` proposal.
231
+ * Null when the proposal is not a well-formed one for a joined group; it then stays inert.
229
232
  */
230
- function* pendingMultisigProposals(hby, runtime, notices, options) {
231
- const candidates = options.said ? [options.said] : notices.map(note => note.attrs.d);
232
- const group = options.group && (hby.habByName(options.group)?.pre ?? options.group);
233
- const selected = [];
234
- for (const said of new Set(candidates)) {
235
- if (typeof said !== "string")
236
- continue;
237
- const exn = hby.db.exns.get([said]);
238
- if (!exn?.ked)
239
- continue;
240
- const route = exn.route ?? "";
241
- if (!isKelRoute(route) && !isVdrRoute(route) && route !== MULTISIG_RPY_ROUTE && !isV2RegistryRoute(route)) {
242
- continue;
243
- }
244
- const payloadSaid = multisigPayloadSaid(exn);
245
- if (!payloadSaid)
246
- continue;
247
- const indexed = hby.db.meids.get([payloadSaid]).some(index => index.qb64 === said);
248
- if (!indexed && !options.said)
249
- continue;
250
- const proposalGroup = isKelRoute(route)
251
- ? kelApproval(exn, route, yield* kelChild(hby, runtime, exn, route))?.group
252
- : stringField(payloadSection(exn.ked), "gid");
253
- if (!proposalGroup)
254
- continue;
255
- if (group && group !== proposalGroup) {
256
- if (options.said) {
257
- throw new ValidationError(`Multisig exchange ${said} belongs to group ${proposalGroup}, not ${group}.`);
258
- }
259
- continue;
260
- }
261
- selected.push({ exn, route, indexed });
262
- }
263
- return selected;
264
- }
265
- /** Approve matching stored evidence; host progress and deadlines belong to the caller. */
266
- export function* approvePendingMultisigProposal(hby, runtime, options) {
267
- const notifier = runtime.notifier;
268
- if (!notifier && !options.said) {
269
- throw new ValidationError("Multisig join requires notification storage or an explicit exchange SAID.");
270
- }
271
- const notices = (notifier?.list(0, 100) ?? []);
272
- for (const { exn, route, indexed } of yield* pendingMultisigProposals(hby, runtime, notices, options)) {
273
- if (!indexed) {
274
- // Acceptance commits before the asynchronous handler. Resume that ordinary
275
- // authenticated intake after interruption; never synthesize approval indexes.
276
- const message = yield* abortable(signal => collectBytes(runtime.exc.replay(exn.said, signal)));
277
- yield* runtime.reactor.processBytes(message, { local: true });
278
- const payloadSaid = multisigPayloadSaid(exn);
279
- if (!hby.db.meids.get([payloadSaid]).some(index => index.qb64 === exn.said))
280
- continue;
281
- }
282
- const rgy = isVdrRoute(route) ? runtime.vdr : null;
283
- let result;
284
- if (isKelRoute(route)) {
285
- result = yield* approveKelProposal(hby, runtime, exn, route, options);
286
- }
287
- else if (isVdrRoute(route)) {
288
- result = yield* approveVdrProposal(hby, rgy, runtime, exn, route, options);
289
- }
290
- else if (isV2RegistryRoute(route)) {
291
- const nests = loadNests(hby.db, exn.said);
292
- const proposal = acdcV2RegistryProposal(exn, nests, runtime.acdc?.store ?? null);
293
- const group = proposal?.anchor.pre ? hby.habs.get(proposal.anchor.pre) : null;
294
- if (!proposal || !group)
295
- continue;
296
- const head = group.kever?.serder.said;
297
- if (!head)
298
- throw new ValidationError("Registry approval requires an accepted local group.");
299
- if (!options.callbacks.approveProposal({ route, group: group.pre, embedded: proposal.tel.said }))
300
- continue;
301
- if (group.kever?.serder.said !== head || !acdcV2RegistryProposal(exn, nests, runtime.acdc?.store ?? null)) {
302
- throw new ValidationError("Group or registry changed while approval was pending.");
303
- }
304
- const envelope = envelopeFromFrame(nests[0]), opening = [...envelope.bsqs, ...envelope.bsss];
305
- if (opening.length > 1)
306
- throw new ValidationError("Registry proposal carries multiple openings.");
307
- const approval = yield* approveAcdcV2Registry(hby, runtime.protocol, group, proposal.tel, {
308
- anchor: proposal.anchor,
309
- opening: opening[0],
310
- });
311
- const published = yield* publishAcdcV2Registry(hby, runtime.postman, group, approval);
312
- const regid = proposal.tel.ilk === "rip" ? proposal.tel.said : proposal.tel.regid;
313
- const accepted = runtime.acdc.store.seqEvent(regid, Number(BigInt(`0x${proposal.tel.sad.n}`)))?.said === proposal.tel.said;
314
- result = { route, said: exn.said, embedded: proposal.tel.said, group: group.pre, accepted, ...published };
315
- }
316
- else {
317
- result = yield* approveRpyProposal(hby, runtime, exn, route, options);
318
- }
319
- if (result) {
320
- for (const note of notices) {
321
- if (note.attrs.d === exn.said)
322
- notifier?.remove(note.rid);
323
- }
324
- return result;
325
- }
326
- }
327
- return null;
233
+ export function* approveRegistryV2Proposal(hby, runtime, exn, route, options) {
234
+ const nests = loadNests(hby.db, exn.said);
235
+ const proposal = acdcV2RegistryProposal(exn, nests, runtime.registryStore);
236
+ const group = proposal?.anchor.pre ? hby.habs.get(proposal.anchor.pre) : null;
237
+ if (!proposal || !group)
238
+ return null;
239
+ const head = group.kever?.serder.said;
240
+ if (!head)
241
+ throw new ValidationError("Registry approval requires an accepted local group.");
242
+ if (group.kever?.serder.said !== head || !acdcV2RegistryProposal(exn, nests, runtime.registryStore)) {
243
+ throw new ValidationError("Group or registry changed while approval was pending.");
244
+ }
245
+ const envelope = envelopeFromFrame(nests[0]), opening = [...envelope.bsqs, ...envelope.bsss];
246
+ if (opening.length > 1)
247
+ throw new ValidationError("Registry proposal carries multiple openings.");
248
+ const approval = yield* approveAcdcV2Registry(hby, runtime.protocol, group, proposal.tel, {
249
+ anchor: proposal.anchor,
250
+ opening: opening[0],
251
+ resume: options.resume,
252
+ });
253
+ const published = yield* publishAcdcV2Registry(hby, runtime.postman, group, approval);
254
+ const regid = proposal.tel.ilk === "rip" ? proposal.tel.said : proposal.tel.regid;
255
+ const accepted = runtime.registryStore?.seqEvent(regid, Number(BigInt(`0x${proposal.tel.sad.n}`)))?.said
256
+ === proposal.tel.said;
257
+ return { route, said: exn.said, embedded: proposal.tel.said, group: group.pre, accepted, ...published };
328
258
  }
329
259
  /**
330
260
  * Approve and publish a local contribution to one endpoint-role reply proposal.
@@ -335,10 +265,10 @@ export function* approvePendingMultisigProposal(hby, runtime, options) {
335
265
  * @param runtime Borrowed composed runtime; caller owns its worker and store lifetime.
336
266
  * @param exn Stored multisig exchange wrapper.
337
267
  * @param route Selected multisig route identifying the proposal kind.
338
- * @param options Human approval/alias callbacks and workflow choices; refusal leaves the proposal inert.
268
+ * @param options Alias and name choices plus `resume`; a proposal that is not well formed is left inert.
339
269
  * @returns Approval result with protocol acceptance and deliveries, or null for declined/incomplete input.
340
270
  */
341
- function* approveRpyProposal(hby, runtime, exn, route, options) {
271
+ export function* approveRpyProposal(hby, runtime, exn, route, options) {
342
272
  const wrapperSaid = exn.said;
343
273
  if (!wrapperSaid) {
344
274
  return null;
@@ -356,9 +286,6 @@ function* approveRpyProposal(hby, runtime, exn, route, options) {
356
286
  const { event: rpySerder, attachment, genus } = child;
357
287
  const embeddedSaid = rpySerder.said ?? "";
358
288
  const prior = storedGroupMessage(hby.db, member.pre, exn);
359
- if (!prior && !options.callbacks.approveProposal({ route, group: groupPre, embedded: embeddedSaid || "<unknown>" })) {
360
- return null;
361
- }
362
289
  yield* runtime.reactor.processBytes(concatBytes(Counter.makeGVC(genus), rpySerder.raw, attachment), { local: true });
363
290
  let localRpy;
364
291
  if (prior) {
@@ -386,10 +313,10 @@ function* approveRpyProposal(hby, runtime, exn, route, options) {
386
313
  * @param runtime Borrowed composed runtime; caller owns its worker and store lifetime.
387
314
  * @param exn Stored multisig exchange wrapper.
388
315
  * @param route Selected multisig route identifying the proposal kind.
389
- * @param options Human approval/alias callbacks and workflow choices; refusal leaves the proposal inert.
316
+ * @param options Alias and name choices plus `resume`; a proposal that is not well formed is left inert.
390
317
  * @returns Approval result with protocol acceptance and deliveries, or null for declined/incomplete input.
391
318
  */
392
- function* approveKelProposal(hby, runtime, exn, route, options) {
319
+ export function* approveKelProposal(hby, runtime, exn, route, options) {
393
320
  const wrapperSaid = exn.said;
394
321
  if (!wrapperSaid) {
395
322
  return null;
@@ -429,17 +356,26 @@ function* approveKelProposal(hby, runtime, exn, route, options) {
429
356
  if (eventAccepted(hby, serder) && !smids.some(mid => hby.habs.has(mid))) {
430
357
  return null;
431
358
  }
432
- if (!options.callbacks.approveProposal({ route, group: groupPre, embedded: embeddedSaid ?? "<unknown>" })) {
433
- return null;
359
+ // Signing writes the member's signatures to `sigs.` when the event is first
360
+ // processed, before any wrapper exists. A resuming caller (explicit consent
361
+ // already given) finds them and reuses them: no second signature and no
362
+ // second join.
363
+ const signed = options.resume ? recordedLocalSignatures(hby, serder, groupPre, smids) : null;
364
+ if (signed) {
365
+ localMessage = messagize(serder, { sigers: signed, pipelined: false, gvrsn: approval.genus });
434
366
  }
435
- if (proposal.route === MULTISIG_ICP_ROUTE || (proposal.route === MULTISIG_ROT_ROUTE && !hby.habs.has(groupPre))) {
436
- const rmids = proposal.attrs.rmids;
437
- const alias = options.callbacks.chooseGroupAlias(groupPre);
438
- hby.joinGroupHab(groupPre, alias, member, smids, rmids.length > 0 ? rmids : smids);
367
+ else {
368
+ if (proposal.route === MULTISIG_ICP_ROUTE || (proposal.route === MULTISIG_ROT_ROUTE && !hby.habs.has(groupPre))) {
369
+ const rmids = proposal.attrs.rmids;
370
+ const alias = options.groupAlias;
371
+ if (!alias)
372
+ throw new ValidationError(`A group alias is required to join ${groupPre}.`);
373
+ hby.joinGroupHab(groupPre, alias, member, smids, rmids.length > 0 ? rmids : smids);
374
+ }
375
+ const keys = groupEventKeys(hby, groupPre, serder);
376
+ const sigers = signLocalGroupEvent(hby, serder, smids, keys);
377
+ localMessage = messagize(serder, { sigers, pipelined: false, gvrsn: approval.genus });
439
378
  }
440
- const keys = groupEventKeys(hby, groupPre, serder);
441
- const sigers = signLocalGroupEvent(hby, serder, smids, keys);
442
- localMessage = messagize(serder, { sigers, pipelined: false, gvrsn: approval.genus });
443
379
  }
444
380
  // Each contribution may already own a counted attachment enclosure. Admit
445
381
  // complete messages separately and let the ordinary KEL owner union proofs.
@@ -466,10 +402,10 @@ function* approveKelProposal(hby, runtime, exn, route, options) {
466
402
  * @param runtime Borrowed composed runtime; caller owns its worker and store lifetime.
467
403
  * @param exn Stored multisig exchange wrapper.
468
404
  * @param route Selected multisig route identifying the proposal kind.
469
- * @param options Human approval/alias callbacks and workflow choices; refusal leaves the proposal inert.
405
+ * @param options Alias and name choices plus `resume`; a proposal that is not well formed is left inert.
470
406
  * @returns Approval result with protocol acceptance and deliveries, or null for declined/incomplete input.
471
407
  */
472
- function* approveVdrProposal(hby, rgy, runtime, exn, route, options) {
408
+ export function* approveVdrProposal(hby, rgy, runtime, exn, route, options) {
473
409
  const wrapperSaid = exn.said;
474
410
  if (!wrapperSaid) {
475
411
  return null;
@@ -527,15 +463,14 @@ function* approveVdrProposal(hby, rgy, runtime, exn, route, options) {
527
463
  const prior = storedGroupMessage(hby.db, member.pre, exn);
528
464
  if (!prior && !groupCredentialAnchorReady(hby, groupPre, anchorSerder))
529
465
  return null;
530
- if (!prior && !options.callbacks.approveProposal({ route, group: groupPre, embedded: embeddedSaid || "<unknown>" })) {
531
- return null;
532
- }
533
- // Declined or malformed proposals remain inert; require VDR before signing or intake.
466
+ // Malformed proposals remain inert; require VDR before signing or intake.
534
467
  if (!rgy)
535
468
  throw new ValidationError("Legacy credential registry is unavailable in this runtime.");
536
469
  let registryName;
537
470
  if (proposal.route === MULTISIG_VCP_ROUTE && !prior) {
538
- registryName = options.callbacks.chooseRegistryName(embeddedSerder.pre);
471
+ registryName = options.registryName;
472
+ if (!registryName)
473
+ throw new ValidationError(`A registry name is required for ${embeddedSerder.pre}.`);
539
474
  validateRegistryName(rgy.reger, registryName, embeddedSerder.pre);
540
475
  }
541
476
  const smids = groupSigningMembers(hby, groupPre);
@@ -545,7 +480,8 @@ function* approveVdrProposal(hby, rgy, runtime, exn, route, options) {
545
480
  }
546
481
  else {
547
482
  const keys = groupEventKeys(hby, groupPre, anchorSerder);
548
- const sigers = signLocalGroupEvent(hby, anchorSerder, smids, keys);
483
+ const sigers = (options.resume ? recordedLocalSignatures(hby, anchorSerder, groupPre, smids) : null)
484
+ ?? signLocalGroupEvent(hby, anchorSerder, smids, keys);
549
485
  localAnchor = messagize(anchorSerder, { sigers, pipelined: false, gvrsn: genus });
550
486
  }
551
487
  yield* runtime.reactor.processBytes(concatBytes(Counter.makeGVC(genus), localAnchor), { local: true });
@@ -600,13 +536,32 @@ function* approveVdrProposal(hby, rgy, runtime, exn, route, options) {
600
536
  tel: {
601
537
  pre: embeddedSerder.pre,
602
538
  sn: embeddedSerder.sn,
603
- publicationComplete: rgy.publication.complete(embeddedSerder.pre, embeddedSerder.sn),
539
+ publicationComplete: telEventStatus({ publication: rgy.publication }, embeddedSerder.pre, embeddedSerder.sn).state === "complete",
604
540
  },
605
541
  ...published,
606
542
  };
607
543
  }
544
+ /**
545
+ * This process's verified signatures for a group event, one per local signing
546
+ * member, read from `sigs.`; null unless every local member's key index has one.
547
+ * Remote members' signatures in the same row never count.
548
+ */
549
+ function recordedLocalSignatures(hby, serder, groupPre, smids) {
550
+ if (!hby.habs.has(groupPre) || !serder.pre || !serder.said)
551
+ return null;
552
+ const local = smids.flatMap((mid, index) => hby.habs.has(mid) ? [index] : []);
553
+ const stored = hby.db.sigs.get(dgKey(serder.pre, serder.said));
554
+ if (local.length === 0 || stored.length === 0)
555
+ return null;
556
+ const verfers = groupEventKeys(hby, groupPre, serder).map(qb64 => new Verfer({ qb64 }));
557
+ const { sigers } = Kever.verifyIndexedSignatures(serder.raw, stored, verfers);
558
+ const own = sigers.filter(siger => local.includes(siger.index));
559
+ return local.every(index => own.some(siger => siger.index === index))
560
+ ? own.sort((left, right) => left.index - right.index)
561
+ : null;
562
+ }
608
563
  /** Preserve the recorded signing/rotation participant order during approval and resume. */
609
- function kelApprovalMembers({ proposal }) {
564
+ export function kelApprovalMembers({ proposal }) {
610
565
  if (proposal.route === MULTISIG_IXN_ROUTE)
611
566
  return [...proposal.attrs.smids];
612
567
  return uniqueMembers([...proposal.attrs.smids, ...proposal.attrs.rmids]);
@@ -662,7 +617,7 @@ function kelProposalLabel(route) {
662
617
  }
663
618
  }
664
619
  /** Decode the fields used by approval, preserving existing missing-embed and member-list behavior. */
665
- function kelApproval(exn, route, child) {
620
+ export function kelApproval(exn, route, child) {
666
621
  if (!child)
667
622
  return null;
668
623
  const { event } = child;
@@ -791,7 +746,7 @@ function rpyAttrs(serder) {
791
746
  }
792
747
  return { cid: attrs.cid, role: attrs.role, eid: attrs.eid };
793
748
  }
794
- function payloadSection(ked) {
749
+ export function payloadSection(ked) {
795
750
  return isRecord(ked.a) ? ked.a : {};
796
751
  }
797
752
  function embeddedSection(ked) {
@@ -812,10 +767,10 @@ function stringField(record, field) {
812
767
  function isRecord(value) {
813
768
  return typeof value === "object" && value !== null && !Array.isArray(value);
814
769
  }
815
- function isKelRoute(route) {
770
+ export function isKelRoute(route) {
816
771
  return route === MULTISIG_ICP_ROUTE || route === MULTISIG_ROT_ROUTE || route === MULTISIG_IXN_ROUTE;
817
772
  }
818
- function isVdrRoute(route) {
773
+ export function isVdrRoute(route) {
819
774
  return route === MULTISIG_VCP_ROUTE || route === MULTISIG_ISS_ROUTE || route === MULTISIG_REV_ROUTE;
820
775
  }
821
776
  function requireText(value, label) {
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Group proposal reads, owned by the library so that a CLI, a browser wallet and
3
+ * a service all see and dismiss the same proposals. Operations follow the
4
+ * workflow contract (`app/runtime/workflow-contract.ts`):
5
+ * - `listGroupProposals` and `findGroupProposal` are pure reads of stored
6
+ * exchange messages, the multisig embed index (`meids.`) and the notifier.
7
+ * - `dismissGroupProposal` removes local notices only.
8
+ *
9
+ * A proposal is identified by the SAID of the outer exchange wrapper that
10
+ * announced it. Approval, which is the only step that signs, names that SAID
11
+ * explicitly; nothing here or in the notice store selects a proposal to approve.
12
+ *
13
+ * Boundaries:
14
+ * - Dependencies are declared collaborators, not an AgentRuntime (ADR-0013).
15
+ * - Notices discover candidates only. Their attributes never authorize a route
16
+ * or a group; every field below is read from the stored, indexed wrapper.
17
+ */
18
+ import { Vrsn_2_0 } from "cesr-ts";
19
+ import { ValidationError } from "../../core/errors.js";
20
+ import { isV2RegistryRoute } from "../../core/exchange/protocol-exchanging.js";
21
+ import { loadNests } from "../../db/exchange/exchange-nests.js";
22
+ import { abortable } from "../runtime/abortable.js";
23
+ import { collectBytes } from "../transport/byte-source.js";
24
+ import { findLocalGroupMember, groupSigningMembers, uniqueMembers } from "./group-members.js";
25
+ import { acdcV2RegistryProposal, MULTISIG_RPY_ROUTE, multisigChild } from "./grouping.js";
26
+ import { multisigEmbeddedSection, multisigPayloadSaid, storedGroupMessage } from "./multisig-messaging.js";
27
+ import { approveKelProposal, approveRegistryV2Proposal, approveRpyProposal, approveVdrProposal, eventAccepted, isKelRoute, isVdrRoute, kelApproval, payloadSection, } from "./multisig-workflows.js";
28
+ /** Page size when reading the notice store; no proposal is hidden behind the first page. */
29
+ const NOTICE_PAGE = 100;
30
+ /** Wrapper route to proposal kind, or null for routes this API does not cover. */
31
+ function proposalKind(route) {
32
+ if (!isKelRoute(route) && !isVdrRoute(route) && route !== MULTISIG_RPY_ROUTE && !isV2RegistryRoute(route)) {
33
+ return null;
34
+ }
35
+ return route.slice("/multisig/".length);
36
+ }
37
+ /** Page through every notice; `fn` sees each in durable order. */
38
+ function eachNotice(notifier, fn) {
39
+ for (let start = 0;; start += NOTICE_PAGE) {
40
+ const page = notifier.list(start, NOTICE_PAGE);
41
+ for (const note of page)
42
+ fn({ rid: note.pad.i, attrs: note.pad.a });
43
+ if (page.length < NOTICE_PAGE)
44
+ break;
45
+ }
46
+ }
47
+ /** Wrapper SAIDs the notice store holds for multisig proposals, oldest first and without repeats. */
48
+ function noticedWrappers(notifier) {
49
+ const saids = new Set();
50
+ eachNotice(notifier, ({ attrs }) => {
51
+ if (typeof attrs.d === "string")
52
+ saids.add(attrs.d);
53
+ });
54
+ return [...saids];
55
+ }
56
+ /** The local member that would approve, without throwing for a group not yet joined. */
57
+ function approvingMember(hby, group, members) {
58
+ const mid = hby.db.getHab(group)?.mid;
59
+ return (mid ? hby.habs.get(mid) : null) ?? findLocalGroupMember(hby, members);
60
+ }
61
+ /**
62
+ * Event body of a wrapper's named child. v1 reads the pathed embed; v2 reads the
63
+ * first nest of a wrapper that authenticates through its own route. Proofs are
64
+ * not read: a listing never signs, so only the body is needed.
65
+ */
66
+ function childBody(deps, wrapper, route, label) {
67
+ if (wrapper.pvrsn.major === 1)
68
+ return multisigChild(deps.hby, wrapper, label);
69
+ if (!wrapper.said || !deps.exc.verifyAccepted(wrapper.said, route))
70
+ return null;
71
+ const body = loadNests(deps.hby.db, wrapper.said)[0]?.body;
72
+ const event = body;
73
+ if (!event || typeof event.pre !== "string")
74
+ return null;
75
+ return { event, attachment: new Uint8Array(), genus: event.gvrsn ?? event.pvrsn ?? Vrsn_2_0 };
76
+ }
77
+ function eventRef(event, pre) {
78
+ return event.said && event.sn !== null ? { pre, sn: event.sn, said: event.said } : undefined;
79
+ }
80
+ function decode(deps, exn, route, kind) {
81
+ const { hby } = deps;
82
+ const payload = payloadSection(exn.ked ?? {});
83
+ if (isKelRoute(route)) {
84
+ const child = childBody(deps, exn, route, kind);
85
+ const approval = child && kelApproval(exn, route, child);
86
+ if (!approval)
87
+ return null;
88
+ const { smids, rmids } = approval.proposal.attrs;
89
+ return {
90
+ kind,
91
+ group: approval.group,
92
+ embedded: approval.event.said ?? "",
93
+ smids,
94
+ rmids: rmids ?? [],
95
+ event: eventRef(approval.event, approval.group),
96
+ inapplicable: eventAccepted(hby, approval.event) && !smids.some(mid => hby.habs.has(mid)),
97
+ };
98
+ }
99
+ if (isV2RegistryRoute(route)) {
100
+ const nests = loadNests(hby.db, exn.said);
101
+ const registry = acdcV2RegistryProposal(exn, nests, deps.registryStore);
102
+ if (!registry?.anchor.pre)
103
+ return null;
104
+ const group = registry.anchor.pre;
105
+ return {
106
+ kind,
107
+ group,
108
+ embedded: registry.tel.said ?? "",
109
+ smids: groupSigningMembers(hby, group),
110
+ rmids: [],
111
+ event: eventRef(registry.anchor, group),
112
+ };
113
+ }
114
+ const group = typeof payload.gid === "string" ? payload.gid : "";
115
+ if (!group)
116
+ return null;
117
+ const smids = groupSigningMembers(hby, group);
118
+ if (route === MULTISIG_RPY_ROUTE) {
119
+ const child = childBody(deps, exn, route, "rpy");
120
+ return child?.event.said ? { kind, group, embedded: child.event.said, smids, rmids: [] } : null;
121
+ }
122
+ // vcp, iss and rev: the named TEL event, sealed by the group event in `anc`.
123
+ const anchor = multisigChild(hby, exn, "anc");
124
+ const tel = multisigEmbeddedSection(exn.ked ?? {})?.[kind];
125
+ const embedded = tel && typeof tel === "object" ? tel.d : undefined;
126
+ if (!anchor || anchor.event.pre !== group || typeof embedded !== "string")
127
+ return null;
128
+ return { kind, group, embedded, smids, rmids: [], event: eventRef(anchor.event, group) };
129
+ }
130
+ /** Read one stored wrapper; null when it is not a well-formed proposal of a supported kind. */
131
+ function readProposal(deps, said) {
132
+ const { hby } = deps;
133
+ const exn = hby.db.exns.get([said]);
134
+ if (!exn?.ked || !exn.said || !exn.pre)
135
+ return null;
136
+ const route = exn.route ?? "";
137
+ const kind = proposalKind(route);
138
+ if (!kind || !multisigPayloadSaid(exn))
139
+ return null;
140
+ let decoded;
141
+ try {
142
+ decoded = decode(deps, exn, route, kind);
143
+ }
144
+ catch (error) {
145
+ if (error instanceof ValidationError)
146
+ return null; // one malformed row never hides the others
147
+ throw error;
148
+ }
149
+ if (!decoded)
150
+ return null;
151
+ const member = approvingMember(hby, decoded.group, uniqueMembers([...decoded.smids, ...decoded.rmids]));
152
+ const approved = !!member && storedGroupMessage(hby.db, member.pre, exn) !== null;
153
+ const inapplicable = !approved && decoded.inapplicable === true;
154
+ return {
155
+ said: exn.said,
156
+ kind,
157
+ group: decoded.group,
158
+ embedded: decoded.embedded,
159
+ proposer: exn.pre,
160
+ smids: decoded.smids,
161
+ rmids: decoded.rmids,
162
+ local: approved ? "approved" : inapplicable ? "inapplicable" : "unapproved",
163
+ needs: {
164
+ groupAlias: !approved && !inapplicable && (kind === "icp" || (kind === "rot" && !hby.habs.has(decoded.group))),
165
+ registryName: !approved && !inapplicable && kind === "vcp",
166
+ },
167
+ ...(decoded.event ? { event: decoded.event } : {}),
168
+ };
169
+ }
170
+ /** Group prefix for a prefix or local alias. */
171
+ function resolveGroup(hby, group) {
172
+ return hby.habByName(group)?.pre ?? group;
173
+ }
174
+ /**
175
+ * List the proposals the notice store announces, oldest notice first. Pure. Only
176
+ * wrappers that are stored, indexed in `meids.` and well formed are listed, one
177
+ * entry per wrapper; every notice page is read, so nothing hides behind the
178
+ * first 100. A dismissed proposal has no notice and is not listed; it remains
179
+ * available through {@link findGroupProposal}.
180
+ */
181
+ export function listGroupProposals(deps, { group } = {}) {
182
+ if (!deps.notifier)
183
+ return [];
184
+ const wanted = group === undefined ? undefined : resolveGroup(deps.hby, group);
185
+ const proposals = [];
186
+ const seen = new Set();
187
+ for (const said of noticedWrappers(deps.notifier)) {
188
+ const exn = deps.hby.db.exns.get([said]);
189
+ const payloadSaid = exn && multisigPayloadSaid(exn);
190
+ if (!payloadSaid || !deps.hby.db.meids.get([payloadSaid]).some(index => index.qb64 === said))
191
+ continue;
192
+ const proposal = readProposal(deps, said);
193
+ if (!proposal || (wanted !== undefined && proposal.group !== wanted) || seen.has(said))
194
+ continue;
195
+ seen.add(said);
196
+ proposals.push(proposal);
197
+ }
198
+ return proposals;
199
+ }
200
+ /**
201
+ * Read one stored proposal by its wrapper SAID, whether or not a notice remains.
202
+ * Pure. Null when no such well-formed proposal is stored.
203
+ */
204
+ export function findGroupProposal(deps, said) {
205
+ return readProposal(deps, said);
206
+ }
207
+ /**
208
+ * Dismiss a proposal locally: remove the notices that announce it, including
209
+ * those other members' wrappers of the same payload raised. The stored wrapper
210
+ * and the multisig index stay, so the proposal can still be found and approved
211
+ * by SAID. KERI has no reject message: nothing is sent, and the proposer and the
212
+ * other members learn nothing from a dismissal.
213
+ *
214
+ * @returns Number of notices removed.
215
+ */
216
+ export function dismissGroupProposal(deps, said) {
217
+ if (!deps.notifier)
218
+ return 0;
219
+ const exn = deps.hby.db.exns.get([said]);
220
+ const payloadSaid = exn && multisigPayloadSaid(exn);
221
+ const wrappers = new Set([said]);
222
+ if (payloadSaid)
223
+ for (const index of deps.hby.db.meids.get([payloadSaid]))
224
+ wrappers.add(index.qb64);
225
+ const stale = [];
226
+ eachNotice(deps.notifier, ({ rid, attrs }) => {
227
+ if (typeof attrs.d === "string" && wrappers.has(attrs.d))
228
+ stale.push(rid);
229
+ });
230
+ return stale.filter(rid => deps.notifier.remove(rid)).length;
231
+ }
232
+ /**
233
+ * Approve one proposal, named by its wrapper SAID: sign this member's
234
+ * contribution to the embedded event, send it to the other members and hand the
235
+ * group event to the Counselor. Returns without waiting for other members; read
236
+ * progress with `counselor.status(result.event)`. This is the only call that
237
+ * signs, and it acts on exactly the SAID given: nothing is selected from the
238
+ * notice store, so a proposal the caller did not name cannot be approved.
239
+ *
240
+ * Calling it is the user's consent, so it never prompts. Repeating it after a
241
+ * crash resumes: this member's recorded signatures and stored wrapper are
242
+ * replayed, never replaced, and the group is not joined twice.
243
+ *
244
+ * Supports every proposal kind: KEL (`icp`, `rot`, `ixn`), reply (`rpy`), legacy registry and credential (`vcp`, `iss`, `rev`) and
245
+ * ACDC v2 registry (`rip`, `bup`). For the registry kinds `event` is the group anchor that seals the TEL event.
246
+ *
247
+ * @throws ValidationError for a SAID that is not a stored supported proposal, a missing `groupAlias` when the proposal {@link GroupProposal.needs} one, or a proposal that cannot be approved.
248
+ */
249
+ export function* approveGroupProposal(deps, said, choices = {}) {
250
+ const { hby } = deps;
251
+ const proposal = findGroupProposal(deps, said);
252
+ const exn = hby.db.exns.get([said]);
253
+ if (!proposal || !exn)
254
+ throw new ValidationError(`No group proposal ${said} is stored.`);
255
+ const route = exn.route ?? "";
256
+ const payloadSaid = multisigPayloadSaid(exn);
257
+ if (!hby.db.meids.get([payloadSaid]).some(index => index.qb64 === said)) {
258
+ // Acceptance commits before the asynchronous handler; resume that ordinary intake.
259
+ const message = yield* abortable(signal => collectBytes(deps.exc.replay(said, signal)));
260
+ yield* deps.reactor.processBytes(message, { local: true });
261
+ if (!hby.db.meids.get([payloadSaid]).some(index => index.qb64 === said)) {
262
+ throw new ValidationError(`Group proposal ${said} is not indexed after replay.`);
263
+ }
264
+ }
265
+ const options = { resume: true, groupAlias: choices.groupAlias, registryName: choices.registryName };
266
+ const result = isKelRoute(route)
267
+ ? yield* approveKelProposal(hby, deps, exn, route, options)
268
+ : isVdrRoute(route)
269
+ ? yield* approveVdrProposal(hby, deps.vdr, deps, exn, route, options)
270
+ : isV2RegistryRoute(route)
271
+ ? yield* approveRegistryV2Proposal(hby, deps, exn, route, options)
272
+ : yield* approveRpyProposal(hby, deps, exn, route, options);
273
+ if (!result)
274
+ throw new ValidationError(`Group proposal ${said} cannot be approved.`);
275
+ const { event } = proposal;
276
+ if (event) {
277
+ const { auths, fallbackAfterMs, proxy } = choices;
278
+ deps.counselor.start(event, { auths, fallbackAfterMs, proxy });
279
+ }
280
+ return { ...result, ...(event ? { event } : {}) };
281
+ }