keri-ts 2.1.0 → 2.2.1

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 (300) 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 +187 -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 +341 -0
  27. package/esm/keri/npm/src/app/ipex/group-issuance.js +905 -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 +50 -19
  33. package/esm/keri/npm/src/app/ipex/ipex-node-authentication.js +42 -18
  34. package/esm/keri/npm/src/app/ipex/issuance.js +896 -0
  35. package/esm/keri/npm/src/app/ipex/pending-presentations.js +46 -0
  36. package/esm/keri/npm/src/app/ipex/presentation-files.js +200 -0
  37. package/esm/keri/npm/src/app/ipex/presenting.js +408 -0
  38. package/esm/keri/npm/src/app/ipex/receive-issuance.js +86 -0
  39. package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +667 -0
  40. package/esm/keri/npm/src/app/mailbox/ipex-arrival.js +103 -0
  41. package/esm/keri/npm/src/app/mailbox/kel-publisher.js +322 -0
  42. package/esm/keri/npm/src/app/mailbox/kel-publishing.js +65 -0
  43. package/esm/keri/npm/src/app/mailbox/kel-submission.js +24 -0
  44. package/esm/keri/npm/src/app/mailbox/mailbox-admin.js +208 -0
  45. package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +212 -47
  46. package/esm/keri/npm/src/app/mailbox/mailboxing.js +10 -5
  47. package/esm/keri/npm/src/app/multisig/counseling.js +488 -0
  48. package/esm/keri/npm/src/app/multisig/group-members.js +18 -0
  49. package/esm/keri/npm/src/app/multisig/multisig-messaging.js +33 -6
  50. package/esm/keri/npm/src/app/multisig/multisig-workflows.js +98 -147
  51. package/esm/keri/npm/src/app/multisig/proposals.js +281 -0
  52. package/esm/keri/npm/src/app/multisig/proposing.js +122 -0
  53. package/esm/keri/npm/src/app/notifications/signaling.js +5 -5
  54. package/esm/keri/npm/src/app/protocol/parsering.js +26 -1
  55. package/esm/keri/npm/src/app/protocol/protocol-processing.js +9 -3
  56. package/esm/keri/npm/src/app/queries/query-workflows.js +98 -0
  57. package/esm/keri/npm/src/app/runtime/agent-runtime.js +106 -55
  58. package/esm/keri/npm/src/app/runtime/cue-runtime.js +3 -6
  59. package/esm/keri/npm/src/app/runtime/delivery-outbox.js +87 -0
  60. package/esm/keri/npm/src/app/runtime/doing.js +17 -0
  61. package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +207 -0
  62. package/esm/keri/npm/src/app/runtime/operation-owners.js +151 -0
  63. package/esm/keri/npm/src/app/runtime/retry-backoff.js +93 -0
  64. package/esm/keri/npm/src/app/runtime/workflow-contract.js +80 -0
  65. package/esm/keri/npm/src/app/transport/http-client.js +25 -6
  66. package/esm/keri/npm/src/app/witness/http-endpoints.js +4 -4
  67. package/esm/keri/npm/src/cli/common/event-completion.js +49 -0
  68. package/esm/keri/npm/src/cli/common/runtime-workers.js +23 -0
  69. package/esm/keri/npm/src/cli/credentials/ipex.js +164 -191
  70. package/esm/keri/npm/src/cli/credentials/vc.js +53 -72
  71. package/esm/keri/npm/src/cli/endpoints/did.js +12 -3
  72. package/esm/keri/npm/src/cli/endpoints/oobi.js +10 -13
  73. package/esm/keri/npm/src/cli/identity/challenge.js +8 -34
  74. package/esm/keri/npm/src/cli/identity/delegate.js +81 -258
  75. package/esm/keri/npm/src/cli/identity/incept.js +15 -32
  76. package/esm/keri/npm/src/cli/identity/interact.js +5 -17
  77. package/esm/keri/npm/src/cli/identity/rotate.js +8 -36
  78. package/esm/keri/npm/src/cli/index.js +3 -1
  79. package/esm/keri/npm/src/cli/messaging/group-join.js +83 -0
  80. package/esm/keri/npm/src/cli/messaging/group-wait.js +88 -0
  81. package/esm/keri/npm/src/cli/messaging/multisig.js +190 -285
  82. package/esm/keri/npm/src/cli/messaging/root-copy-lines.js +31 -0
  83. package/esm/keri/npm/src/core/kel/eventing.js +66 -10
  84. package/esm/keri/npm/src/core/mailbox/kram-policy.js +39 -6
  85. package/esm/keri/npm/src/core/mailbox/kraming.js +2 -0
  86. package/esm/keri/npm/src/core/records.js +23 -0
  87. package/esm/keri/npm/src/core/routing.js +13 -0
  88. package/esm/keri/npm/src/db/basing.js +34 -2
  89. package/esm/keri/npm/src/db/core/indexeddb-persistence.js +221 -0
  90. package/esm/keri/npm/src/db/indexeddb.js +98 -0
  91. package/esm/keri/npm/src/db/keeping.js +11 -2
  92. package/esm/keri/npm/src/db/store-lock.js +150 -0
  93. package/esm/keri/npm/src/npm/db-indexeddb.browser.js +2 -0
  94. package/esm/keri/npm/src/npm/db-indexeddb.js +3 -0
  95. package/esm/keri/npm/src/npm/db.browser.js +2 -0
  96. package/esm/keri/npm/src/npm/index.browser.js +9 -0
  97. package/esm/keri/npm/src/npm/runtime-web.browser.js +2 -0
  98. package/esm/keri/npm/src/npm/runtime-web.js +3 -0
  99. package/esm/keri/npm/src/npm/runtime.browser.js +2 -0
  100. package/esm/keri/npm/src/runtime/index.js +51 -4
  101. package/esm/keri/npm/src/runtime/web/output-barrier.js +47 -0
  102. package/esm/keri/npm/src/runtime/web/persistent-storage.js +37 -0
  103. package/esm/keri/npm/src/runtime/web.js +10 -0
  104. package/esm/keri/npm/src/vdr/tel-publication.js +16 -0
  105. package/esm/keri/npm/src/version.js +2 -2
  106. package/package.json +15 -2
  107. package/types/acdc/authoring.d.ts +54 -0
  108. package/types/acdc/authoring.d.ts.map +1 -0
  109. package/types/acdc/custody.d.ts +84 -0
  110. package/types/acdc/custody.d.ts.map +1 -0
  111. package/types/acdc/dhking.d.ts +94 -3
  112. package/types/acdc/dhking.d.ts.map +1 -1
  113. package/types/acdc/regbasing.d.ts +4 -0
  114. package/types/acdc/regbasing.d.ts.map +1 -1
  115. package/types/acdc/salting.d.ts +6 -1
  116. package/types/acdc/salting.d.ts.map +1 -1
  117. package/types/acdc/scheming.d.ts +2 -0
  118. package/types/acdc/scheming.d.ts.map +1 -1
  119. package/types/acdc/unblinding.d.ts +58 -19
  120. package/types/acdc/unblinding.d.ts.map +1 -1
  121. package/types/app/bootstrap/authenticating.d.ts +3 -1
  122. package/types/app/bootstrap/authenticating.d.ts.map +1 -1
  123. package/types/app/bootstrap/oobi-resolving.d.ts +92 -0
  124. package/types/app/bootstrap/oobi-resolving.d.ts.map +1 -0
  125. package/types/app/bootstrap/oobiery.d.ts +3 -1
  126. package/types/app/bootstrap/oobiery.d.ts.map +1 -1
  127. package/types/app/credentials/credential-revoking.d.ts +3 -0
  128. package/types/app/credentials/credential-revoking.d.ts.map +1 -1
  129. package/types/app/credentials/credential-workflows.d.ts +5 -0
  130. package/types/app/credentials/credential-workflows.d.ts.map +1 -1
  131. package/types/app/credentials/registry-settling.d.ts +85 -0
  132. package/types/app/credentials/registry-settling.d.ts.map +1 -0
  133. package/types/app/credentials/root-replicating.d.ts +56 -44
  134. package/types/app/credentials/root-replicating.d.ts.map +1 -1
  135. package/types/app/credentials/root-replicator.d.ts +105 -0
  136. package/types/app/credentials/root-replicator.d.ts.map +1 -0
  137. package/types/app/credentials/tel-completion.d.ts +55 -0
  138. package/types/app/credentials/tel-completion.d.ts.map +1 -0
  139. package/types/app/delivery/forwarding.d.ts +6 -0
  140. package/types/app/delivery/forwarding.d.ts.map +1 -1
  141. package/types/app/exchanges/exchanging.d.ts.map +1 -1
  142. package/types/app/identity/challenging.d.ts +54 -0
  143. package/types/app/identity/challenging.d.ts.map +1 -1
  144. package/types/app/identity/delegating.d.ts +21 -4
  145. package/types/app/identity/delegating.d.ts.map +1 -1
  146. package/types/app/identity/delegation-approval.d.ts +228 -0
  147. package/types/app/identity/delegation-approval.d.ts.map +1 -0
  148. package/types/app/identity/event-completion.d.ts +151 -0
  149. package/types/app/identity/event-completion.d.ts.map +1 -0
  150. package/types/app/identity/habbing.d.ts +67 -2
  151. package/types/app/identity/habbing.d.ts.map +1 -1
  152. package/types/app/identity/keeping.d.ts +49 -3
  153. package/types/app/identity/keeping.d.ts.map +1 -1
  154. package/types/app/ipex/group-ipex.d.ts +254 -0
  155. package/types/app/ipex/group-ipex.d.ts.map +1 -0
  156. package/types/app/ipex/group-issuance.d.ts +281 -0
  157. package/types/app/ipex/group-issuance.d.ts.map +1 -0
  158. package/types/app/ipex/ipex-admitting.d.ts +47 -0
  159. package/types/app/ipex/ipex-admitting.d.ts.map +1 -0
  160. package/types/app/ipex/ipex-conversation.d.ts +195 -0
  161. package/types/app/ipex/ipex-conversation.d.ts.map +1 -0
  162. package/types/app/ipex/ipex-discloser.d.ts +156 -0
  163. package/types/app/ipex/ipex-discloser.d.ts.map +1 -0
  164. package/types/app/ipex/ipex-granting.d.ts +145 -0
  165. package/types/app/ipex/ipex-granting.d.ts.map +1 -0
  166. package/types/app/ipex/ipex-grouping.d.ts +18 -0
  167. package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
  168. package/types/app/ipex/ipex-node-authentication.d.ts +9 -1
  169. package/types/app/ipex/ipex-node-authentication.d.ts.map +1 -1
  170. package/types/app/ipex/issuance.d.ts +445 -0
  171. package/types/app/ipex/issuance.d.ts.map +1 -0
  172. package/types/app/ipex/pending-presentations.d.ts +25 -0
  173. package/types/app/ipex/pending-presentations.d.ts.map +1 -0
  174. package/types/app/ipex/presentation-files.d.ts +47 -0
  175. package/types/app/ipex/presentation-files.d.ts.map +1 -0
  176. package/types/app/ipex/presenting.d.ts +300 -0
  177. package/types/app/ipex/presenting.d.ts.map +1 -0
  178. package/types/app/ipex/receive-issuance.d.ts +99 -0
  179. package/types/app/ipex/receive-issuance.d.ts.map +1 -0
  180. package/types/app/ipex/unblinding-workflow.d.ts +257 -0
  181. package/types/app/ipex/unblinding-workflow.d.ts.map +1 -0
  182. package/types/app/mailbox/ipex-arrival.d.ts +104 -0
  183. package/types/app/mailbox/ipex-arrival.d.ts.map +1 -0
  184. package/types/app/mailbox/kel-publisher.d.ts +141 -0
  185. package/types/app/mailbox/kel-publisher.d.ts.map +1 -0
  186. package/types/app/mailbox/kel-publishing.d.ts +70 -0
  187. package/types/app/mailbox/kel-publishing.d.ts.map +1 -0
  188. package/types/app/mailbox/kel-submission.d.ts +18 -0
  189. package/types/app/mailbox/kel-submission.d.ts.map +1 -0
  190. package/types/app/mailbox/mailbox-admin.d.ts +113 -0
  191. package/types/app/mailbox/mailbox-admin.d.ts.map +1 -0
  192. package/types/app/mailbox/mailbox-poller.d.ts +92 -3
  193. package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
  194. package/types/app/mailbox/mailboxing.d.ts +3 -0
  195. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  196. package/types/app/multisig/counseling.d.ts +196 -0
  197. package/types/app/multisig/counseling.d.ts.map +1 -0
  198. package/types/app/multisig/group-members.d.ts +11 -0
  199. package/types/app/multisig/group-members.d.ts.map +1 -1
  200. package/types/app/multisig/grouping.d.ts +39 -20
  201. package/types/app/multisig/grouping.d.ts.map +1 -1
  202. package/types/app/multisig/multisig-messaging.d.ts +23 -6
  203. package/types/app/multisig/multisig-messaging.d.ts.map +1 -1
  204. package/types/app/multisig/multisig-workflows.d.ts +91 -22
  205. package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
  206. package/types/app/multisig/proposals.d.ts +129 -0
  207. package/types/app/multisig/proposals.d.ts.map +1 -0
  208. package/types/app/multisig/proposing.d.ts +125 -0
  209. package/types/app/multisig/proposing.d.ts.map +1 -0
  210. package/types/app/notifications/signaling.d.ts +3 -1
  211. package/types/app/notifications/signaling.d.ts.map +1 -1
  212. package/types/app/protocol/parsering.d.ts +13 -0
  213. package/types/app/protocol/parsering.d.ts.map +1 -1
  214. package/types/app/protocol/protocol-processing.d.ts +7 -1
  215. package/types/app/protocol/protocol-processing.d.ts.map +1 -1
  216. package/types/app/queries/query-workflows.d.ts +98 -0
  217. package/types/app/queries/query-workflows.d.ts.map +1 -0
  218. package/types/app/runtime/agent-runtime.d.ts +72 -25
  219. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  220. package/types/app/runtime/cue-runtime.d.ts +2 -1
  221. package/types/app/runtime/cue-runtime.d.ts.map +1 -1
  222. package/types/app/runtime/delivery-outbox.d.ts +68 -0
  223. package/types/app/runtime/delivery-outbox.d.ts.map +1 -0
  224. package/types/app/runtime/doing.d.ts +27 -0
  225. package/types/app/runtime/doing.d.ts.map +1 -0
  226. package/types/app/runtime/ipex-auxiliaries.d.ts +74 -0
  227. package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -0
  228. package/types/app/runtime/operation-owners.d.ts +137 -0
  229. package/types/app/runtime/operation-owners.d.ts.map +1 -0
  230. package/types/app/runtime/retry-backoff.d.ts +49 -0
  231. package/types/app/runtime/retry-backoff.d.ts.map +1 -0
  232. package/types/app/runtime/workflow-contract.d.ts +79 -0
  233. package/types/app/runtime/workflow-contract.d.ts.map +1 -0
  234. package/types/app/transport/http-client.d.ts.map +1 -1
  235. package/types/cli/common/event-completion.d.ts +26 -0
  236. package/types/cli/common/event-completion.d.ts.map +1 -0
  237. package/types/cli/common/runtime-workers.d.ts +23 -0
  238. package/types/cli/common/runtime-workers.d.ts.map +1 -0
  239. package/types/cli/credentials/ipex.d.ts.map +1 -1
  240. package/types/cli/credentials/vc.d.ts +1 -1
  241. package/types/cli/credentials/vc.d.ts.map +1 -1
  242. package/types/cli/endpoints/did.d.ts.map +1 -1
  243. package/types/cli/endpoints/oobi.d.ts +3 -1
  244. package/types/cli/endpoints/oobi.d.ts.map +1 -1
  245. package/types/cli/identity/challenge.d.ts +1 -1
  246. package/types/cli/identity/challenge.d.ts.map +1 -1
  247. package/types/cli/identity/delegate.d.ts +14 -1
  248. package/types/cli/identity/delegate.d.ts.map +1 -1
  249. package/types/cli/identity/incept.d.ts.map +1 -1
  250. package/types/cli/identity/interact.d.ts.map +1 -1
  251. package/types/cli/identity/rotate.d.ts.map +1 -1
  252. package/types/cli/index.d.ts +3 -1
  253. package/types/cli/index.d.ts.map +1 -1
  254. package/types/cli/messaging/group-join.d.ts +56 -0
  255. package/types/cli/messaging/group-join.d.ts.map +1 -0
  256. package/types/cli/messaging/group-wait.d.ts +34 -0
  257. package/types/cli/messaging/group-wait.d.ts.map +1 -0
  258. package/types/cli/messaging/multisig.d.ts +29 -5
  259. package/types/cli/messaging/multisig.d.ts.map +1 -1
  260. package/types/cli/messaging/root-copy-lines.d.ts +39 -0
  261. package/types/cli/messaging/root-copy-lines.d.ts.map +1 -0
  262. package/types/core/cues.d.ts +11 -0
  263. package/types/core/cues.d.ts.map +1 -1
  264. package/types/core/kel/eventing.d.ts.map +1 -1
  265. package/types/core/mailbox/kram-policy.d.ts +14 -0
  266. package/types/core/mailbox/kram-policy.d.ts.map +1 -1
  267. package/types/core/mailbox/kraming.d.ts +1 -1
  268. package/types/core/mailbox/kraming.d.ts.map +1 -1
  269. package/types/core/records.d.ts +61 -0
  270. package/types/core/records.d.ts.map +1 -1
  271. package/types/core/routing.d.ts +6 -0
  272. package/types/core/routing.d.ts.map +1 -1
  273. package/types/db/basing.d.ts +8 -2
  274. package/types/db/basing.d.ts.map +1 -1
  275. package/types/db/core/indexeddb-persistence.d.ts +59 -0
  276. package/types/db/core/indexeddb-persistence.d.ts.map +1 -0
  277. package/types/db/indexeddb.d.ts +37 -0
  278. package/types/db/indexeddb.d.ts.map +1 -0
  279. package/types/db/keeping.d.ts +5 -2
  280. package/types/db/keeping.d.ts.map +1 -1
  281. package/types/db/store-lock.d.ts +72 -0
  282. package/types/db/store-lock.d.ts.map +1 -0
  283. package/types/npm/db-indexeddb.d.ts +4 -0
  284. package/types/npm/db-indexeddb.d.ts.map +1 -0
  285. package/types/npm/runtime-web.d.ts +4 -0
  286. package/types/npm/runtime-web.d.ts.map +1 -0
  287. package/types/runtime/index.d.ts +42 -7
  288. package/types/runtime/index.d.ts.map +1 -1
  289. package/types/runtime/web/output-barrier.d.ts +30 -0
  290. package/types/runtime/web/output-barrier.d.ts.map +1 -0
  291. package/types/runtime/web/persistent-storage.d.ts +36 -0
  292. package/types/runtime/web/persistent-storage.d.ts.map +1 -0
  293. package/types/runtime/web.d.ts +11 -0
  294. package/types/runtime/web.d.ts.map +1 -0
  295. package/types/vdr/tel-publication.d.ts +6 -0
  296. package/types/vdr/tel-publication.d.ts.map +1 -1
  297. package/types/version.d.ts +2 -2
  298. package/esm/keri/npm/src/cli/common/tel-completion.js +0 -24
  299. package/types/cli/common/tel-completion.d.ts +0 -8
  300. package/types/cli/common/tel-completion.d.ts.map +0 -1
@@ -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
+ }
@@ -0,0 +1,122 @@
1
+ import { ValidationError } from "../../core/errors.js";
2
+ import { revokeCredential, } from "../credentials/credential-revoking.js";
3
+ import { createCredential, inceptCredentialRegistry, } from "../credentials/credential-workflows.js";
4
+ import { groupSigningMembers, localGroupMember, uniqueMembers } from "./group-members.js";
5
+ import { MULTISIG_ICP_ROUTE, MULTISIG_IXN_ROUTE, MULTISIG_ROT_ROUTE } from "./grouping.js";
6
+ import { remoteGroupMembers } from "./multisig-messaging.js";
7
+ import { approveAcdcV2Registry, commitAcceptedGroupMembers, publishAcdcV2Registry, publishProposal, } from "./multisig-workflows.js";
8
+ /** Propose a new group: create this member's inception and publish it. */
9
+ export function* proposeGroupInception(deps, member, alias, { smids, rmids = smids, ...args }, inputs) {
10
+ // Publishing needs a route to every other member. Refuse before the group habitat exists, so a call that could
11
+ // not be sent leaves nothing behind and the same alias can be used again.
12
+ for (const other of remoteGroupMembers(deps.hby, member, uniqueMembers([...smids, ...rmids]))) {
13
+ if (!deps.postman.canReach(member.pre, other)) {
14
+ throw new ValidationError(`Group member ${other} cannot be reached: no end role is known for it. Resolve its OOBI first.`);
15
+ }
16
+ }
17
+ const created = deps.hby.makeGroupHab(alias, member, [...smids], [...rmids], undefined, args);
18
+ const event = { pre: created.hab.pre, sn: created.serder.sn ?? 0, said: created.serder.said };
19
+ deps.counselor.start(event, inputs);
20
+ const sent = yield* publishProposal(deps.hby, deps.protocol, deps.postman, member, uniqueMembers([...smids, ...rmids]), {
21
+ route: MULTISIG_ICP_ROUTE,
22
+ attrs: { smids: [...smids], rmids: [...rmids], ...(args.delpre ? { delegator: args.delpre } : {}) },
23
+ }, created.message);
24
+ return { ...sent, route: MULTISIG_ICP_ROUTE, group: created.hab.pre, event, smids, rmids };
25
+ }
26
+ /** Propose a group rotation, including member add or remove through new `smids` / `rmids`. */
27
+ export function* proposeGroupRotation(deps, group, { smids: newSmids, rmids: newRmids, ...args } = {}, inputs) {
28
+ const { hby } = deps;
29
+ const rotated = hby.rotateGroupHab(group.name, newSmids && [...newSmids], newRmids && [...newRmids], args);
30
+ const smids = newSmids?.length ? newSmids : groupSigningMembers(hby, rotated.hab.pre);
31
+ const rmids = newRmids?.length ? newRmids : smids;
32
+ const event = { pre: rotated.hab.pre, sn: rotated.serder.sn, said: rotated.serder.said };
33
+ deps.counselor.start(event, inputs);
34
+ const sent = yield* publishProposal(hby, deps.protocol, deps.postman, localGroupMember(hby, rotated.hab.pre), uniqueMembers([...smids, ...rmids]), { route: MULTISIG_ROT_ROUTE, attrs: { gid: rotated.hab.pre, smids: [...smids], rmids: [...rmids] } }, rotated.message);
35
+ return { ...sent, route: MULTISIG_ROT_ROUTE, group: rotated.hab.pre, event, smids, rmids };
36
+ }
37
+ /** Propose a group interaction event carrying `data`. */
38
+ export function* proposeGroupInteraction(deps, group, data, inputs, version) {
39
+ const { hby } = deps;
40
+ const created = hby.interactGroupHab(group.name, undefined, { data, version });
41
+ const smids = groupSigningMembers(hby, created.hab.pre);
42
+ const event = { pre: created.hab.pre, sn: created.serder.sn, said: created.serder.said };
43
+ deps.counselor.start(event, inputs);
44
+ const sent = yield* publishProposal(hby, deps.protocol, deps.postman, localGroupMember(hby, created.hab.pre), smids, { route: MULTISIG_IXN_ROUTE, attrs: { gid: created.hab.pre, smids } }, created.message);
45
+ return { ...sent, route: MULTISIG_IXN_ROUTE, group: created.hab.pre, event, smids, rmids: [] };
46
+ }
47
+ /**
48
+ * Commit the members a proposed rotation named, once the rotation is accepted and
49
+ * is the group's latest establishment event. Idempotent; false until then, so a
50
+ * proposed newcomer is never adopted early. Other event kinds commit nothing.
51
+ *
52
+ * @throws ValidationError when the event is not an accepted event body held locally.
53
+ */
54
+ export function settleProposedRotation(deps, result) {
55
+ if (result.route !== MULTISIG_ROT_ROUTE)
56
+ return false;
57
+ const serder = deps.hby.db.getEvtSerder(result.event.pre, result.event.said);
58
+ if (!serder)
59
+ throw new ValidationError(`No rotation ${result.event.said} is held for ${result.event.pre}.`);
60
+ return commitAcceptedGroupMembers(deps.hby, serder, result.smids, result.rmids);
61
+ }
62
+ /**
63
+ * Propose an ACDC v2 registry event (`rip` or `bup`): author the group anchor
64
+ * that seals it, process this member's own contribution, hand the anchor to the
65
+ * Counselor and publish the proposal. The proposer's contribution is also its
66
+ * approval. Returns without waiting; the anchor's progress is
67
+ * `counselor.status(result.event)`. A `bup` needs its `opening`.
68
+ *
69
+ * @throws ValidationError for a group that is not joined and accepted, or a TEL event the group cannot propose.
70
+ */
71
+ export function* proposeRegistryEvent(deps, group, event, opening, inputs) {
72
+ const { hby } = deps;
73
+ const approval = yield* approveAcdcV2Registry(hby, deps.protocol, group, event, { opening, resume: true });
74
+ const anchorSaid = approval[0].sad?.a?.anc;
75
+ const anchor = anchorSaid ? hby.db.getEvtSerder(group.pre, anchorSaid) : null;
76
+ if (!anchor || anchor.sn === null)
77
+ throw new ValidationError("Registry proposal lost its group anchor.");
78
+ const anchorRef = { pre: group.pre, sn: anchor.sn, said: anchor.said };
79
+ deps.counselor.start(anchorRef, inputs);
80
+ const sent = yield* publishAcdcV2Registry(hby, deps.postman, group, approval);
81
+ const regid = event.ilk === "rip" ? event.said : event.regid;
82
+ const accepted = deps.registryStore?.seqEvent(regid, Number(BigInt(`0x${event.sad.n}`)))?.said === event.said;
83
+ return {
84
+ route: approval[0].route,
85
+ said: approval[0].said,
86
+ embedded: event.said,
87
+ group: group.pre,
88
+ accepted,
89
+ ...sent,
90
+ event: anchorRef,
91
+ };
92
+ }
93
+ function proposedGroup(result, what) {
94
+ if (!result.group)
95
+ throw new ValidationError(`${what} must be proposed by a local group.`);
96
+ return result;
97
+ }
98
+ /**
99
+ * Propose a group's legacy credential registry (`vcp`): author the anchoring group event,
100
+ * process this member's contribution, hand the anchor to the Counselor and publish the
101
+ * proposal. Returns without waiting; the anchor's progress is `counselor.status(result.group.event)`
102
+ * and the registry's is `waitForTelEvent`.
103
+ *
104
+ * @throws ValidationError when `group` is not a local group or the registry name is taken.
105
+ */
106
+ export function* proposeCredentialRegistry(deps, group, options, inputs) {
107
+ const result = proposedGroup(yield* inceptCredentialRegistry(deps.rgy, deps.input, deps.postman, group, options), "A group registry");
108
+ deps.counselor.start(result.group.event, inputs);
109
+ return result;
110
+ }
111
+ /** Propose a group credential issuance (`iss`) the way {@link proposeCredentialRegistry} proposes a registry. */
112
+ export function* proposeCredentialIssue(deps, options, inputs) {
113
+ const result = proposedGroup(yield* createCredential(deps.rgy, deps.input, deps.postman, options), "A group credential");
114
+ deps.counselor.start(result.group.event, inputs);
115
+ return result;
116
+ }
117
+ /** Propose a group credential revocation (`rev`) the way {@link proposeCredentialRegistry} proposes a registry. */
118
+ export function* proposeCredentialRevocation(deps, options, inputs) {
119
+ const result = proposedGroup(yield* revokeCredential(deps.rgy, deps.input, deps.postman, options), "A group revocation");
120
+ deps.counselor.start(result.group.event, inputs);
121
+ return result;
122
+ }
@@ -11,7 +11,8 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
11
11
  };
12
12
  var _Signal_raw, _Signal_pad, _Signal_ckey;
13
13
  import { MtrDex, Salter } from "cesr-ts";
14
- import { runtimeTurn } from "../runtime/runtime-turn.js";
14
+ import { doEvery, PROTOCOL_PACING } from "../runtime/doing.js";
15
+ import { systemClock } from "../runtime/runtime-clock.js";
15
16
  const textEncoder = new TextEncoder();
16
17
  const textDecoder = new TextDecoder();
17
18
  function randomNonce() {
@@ -162,11 +163,10 @@ export class Signaler {
162
163
  this.closed = true;
163
164
  this.clear();
164
165
  }
165
- *signalDo() {
166
- while (true) {
166
+ *signalDo(clock = systemClock, pacing = PROTOCOL_PACING) {
167
+ return yield* doEvery(clock, pacing.tockMs, function* () {
167
168
  this.processOnce();
168
- yield* runtimeTurn();
169
- }
169
+ }.bind(this));
170
170
  }
171
171
  }
172
172
  Object.defineProperty(Signaler, "SignalTimeoutMs", {
@@ -144,7 +144,7 @@ function* dispatchMessage(envelope, revery, kevery, exchanger, tevery, mailboxAi
144
144
  }
145
145
  }
146
146
  if (serder.ilk === "qry" && !["logs", "ksn", "mbx", "tels", "tsn"].includes(serder.route ?? "")
147
- && qvy?.resource !== serder.route) {
147
+ && !(qvy?.claims ? qvy.claims(serder) : qvy?.resource === serder.route)) {
148
148
  return { envelope, decision: { kind: "drop", reason: "missingProcessor" } };
149
149
  }
150
150
  const result = kevery.kramer?.intake(envelope);
@@ -487,6 +487,31 @@ export function copyAuxiliaryQuery(envelope) {
487
487
  copied.tsgs.splice(0, copied.tsgs.length, ...normalized.tsgs);
488
488
  return copied;
489
489
  }
490
+ /**
491
+ * Serialize an auxiliary query without nested evidence so a later process can rebuild it: its own frame with
492
+ * every attachment group (plus `children`, nested evidence already encoded as counted frames), then the
493
+ * normalized proof KRAM may have completed from earlier inputs.
494
+ */
495
+ export function auxiliaryQueryWire(envelope, children = []) {
496
+ const version = envelope.gvrsn ?? envelope.serder.gvrsn ?? envelope.serder.pvrsn;
497
+ const frame = concatBytes(Counter.makeGVC(version), envelope.serder.raw, Counter.enclose({
498
+ code: "AttachmentGroup",
499
+ version,
500
+ qb64: concatBytes(...envelope.attachmentGroups.map(group => group.raw), ...children),
501
+ }));
502
+ const proof = serializeMessage(envelope.serder, { tsgs: envelope.tsgs, gvrsn: version, genusify: true });
503
+ const size = new Uint8Array(4);
504
+ new DataView(size.buffer).setUint32(0, frame.length);
505
+ return concatBytes(size, frame, proof);
506
+ }
507
+ /** Rebuild the envelope {@link auxiliaryQueryWire} serialized. */
508
+ export function restoreAuxiliaryQuery(wire, local = false) {
509
+ const length = new DataView(wire.buffer, wire.byteOffset, 4).getUint32(0);
510
+ const restored = envelopeFromFrame(parseBytes(wire.slice(4, 4 + length))[0], local);
511
+ const normalized = envelopeFromFrame(parseBytes(wire.slice(4 + length))[0]);
512
+ restored.tsgs.splice(0, restored.tsgs.length, ...normalized.tsgs);
513
+ return restored;
514
+ }
490
515
  /** Normalize the actual query endorsements for ingress and per-request authorization. */
491
516
  export function queryEnvelopeFromDispatch(envelope) {
492
517
  const lastSsg = envelope.ssgs.at(-1);
@@ -6,8 +6,14 @@ import { consoleLogger } from "../../core/logger.js";
6
6
  import { BasicReplyRouteHandler, Revery } from "../../core/routing.js";
7
7
  import { Exchanger } from "../exchanges/exchanging.js";
8
8
  import { QueryReplies } from "../queries/query-replies.js";
9
- import { runtimeTurn } from "../runtime/runtime-turn.js";
9
+ import { systemClock } from "../runtime/runtime-clock.js";
10
+ import { waitOnClock } from "../runtime/workflow-contract.js";
10
11
  import { dispatchEnvelope, envelopeFromFrame, queryEnvelopeFromDispatch, } from "./parsering.js";
12
+ /**
13
+ * Wait after every escrow pass, as KERIpy's Doist cycle (`tock` 0.03125 s, app/directing.py) paces its `escrowDo`,
14
+ * which runs one full pass per cycle. A pass is never skipped or shortened; this is the time others get between passes.
15
+ */
16
+ export const ESCROW_TOCK_MS = 31.25;
11
17
  /**
12
18
  * Identify a refusal of one already-extracted message, which must not end its stream.
13
19
  *
@@ -255,10 +261,10 @@ export class ProtocolProcessor {
255
261
  * - mirrors the dedicated `escrowDo` loop rather than folding escrow
256
262
  * processing into parser ingress
257
263
  */
258
- *escrowDo() {
264
+ *escrowDo(clock = systemClock) {
259
265
  while (true) {
260
266
  yield* this.processEscrowsOnce();
261
- yield* runtimeTurn();
267
+ yield* waitOnClock(clock, ESCROW_TOCK_MS);
262
268
  }
263
269
  }
264
270
  }
@@ -0,0 +1,98 @@
1
+ import { call, race, sleep, spawn } from "effection";
2
+ import { ValidationError } from "../../core/errors.js";
3
+ import { isLocalGroupHab } from "../multisig/group-members.js";
4
+ import { AnchorQuerier, KeyStateNoticer } from "./querying.js";
5
+ /** One elapsed deadline covers query sending and independently received evidence. */
6
+ export const QUERY_TIMEOUT_MS = 10_000;
7
+ /** Reject group habs: a query must be signed by one member, not by a group needing more signatures. */
8
+ export function requireQueryRequester(hby, hab) {
9
+ if (isLocalGroupHab(hby, hab)) {
10
+ throw new ValidationError("Query --alias must select a member requester, not a group requiring additional signatures.");
11
+ }
12
+ }
13
+ /** Durable anchor status (`scope: "durable"`): the accepted KEL of `prefix` already holds the exact seal. */
14
+ export function anchorQueryStatus(deps, prefix, anchor) {
15
+ return deps.hby.db.fetchLastSealingEventByEventSeal(prefix, anchor)
16
+ ? { state: "completed", scope: "durable" }
17
+ : { state: "active", phase: "awaitingEvidence", scope: "durable" };
18
+ }
19
+ /** Start a key-state query in the caller's scope. */
20
+ export function* beginKeyStateQuery(deps, target) {
21
+ requireQueryRequester(deps.hby, target.hab);
22
+ const workflow = new KeyStateNoticer(deps.hby.db, deps.querying, target.prefix, deps.cues, {
23
+ signer: target.hab.pre,
24
+ });
25
+ return yield* startQuery(deps, target.prefix, workflow);
26
+ }
27
+ /** Start an anchor query in the caller's scope. */
28
+ export function* beginAnchorQuery(deps, target) {
29
+ requireQueryRequester(deps.hby, target.hab);
30
+ const workflow = new AnchorQuerier(deps.hby.db, deps.querying, target.prefix, target.anchor, {
31
+ signer: target.hab.pre,
32
+ });
33
+ return yield* startQuery(deps, target.prefix, workflow);
34
+ }
35
+ /**
36
+ * Query current key state for `prefix` and wait for accepted evidence.
37
+ *
38
+ * @returns `settled` (status `completed`), or `timed-out`; a timeout is not permission to treat
39
+ * stale local state as success. Workflow failures propagate.
40
+ */
41
+ export function* queryKeyState(deps, target, options = {}) {
42
+ return yield* beginThenWait(beginKeyStateQuery(deps, target), options);
43
+ }
44
+ /** Query for an accepted KEL event holding the exact seal `target.anchor`; see {@link queryKeyState}. */
45
+ export function* queryAnchor(deps, target, options = {}) {
46
+ return yield* beginThenWait(beginAnchorQuery(deps, target), options);
47
+ }
48
+ /** Spawn the workflow in the caller's scope. */
49
+ function* startQuery(deps, prefix, workflow) {
50
+ let completed = false;
51
+ const status = () => completed
52
+ ? { state: "completed", scope: "process" }
53
+ : { state: "active", phase: "awaitingEvidence", scope: "process" };
54
+ const observing = yield* spawn(function* () {
55
+ yield* workflow.queryDo();
56
+ completed = true;
57
+ });
58
+ deps.changes?.push({ kind: "query", id: prefix });
59
+ return {
60
+ prefix,
61
+ status,
62
+ *wait(options = {}) {
63
+ const outcome = yield* waitForQuery(observing, status, options.timeoutMs ?? QUERY_TIMEOUT_MS);
64
+ if (outcome.outcome === "settled")
65
+ deps.changes?.push({ kind: "query", id: prefix });
66
+ return outcome;
67
+ },
68
+ halt: () => observing.halt(),
69
+ };
70
+ }
71
+ /**
72
+ * Observe completion within the window without transferring task ownership.
73
+ * The race cancels only its observation when the timer wins; the run's `halt`
74
+ * joins the original task.
75
+ */
76
+ function* waitForQuery(observing, status, timeoutMs) {
77
+ const done = yield* race([
78
+ call(function* () {
79
+ yield* observing;
80
+ return true;
81
+ }),
82
+ call(function* () {
83
+ yield* sleep(timeoutMs);
84
+ return false;
85
+ }),
86
+ ]);
87
+ return done ? { outcome: "settled", status: status() } : { outcome: "timed-out", status: status(), timeoutMs };
88
+ }
89
+ /** begin + wait, halting the run on every exit path. */
90
+ function* beginThenWait(begin, options) {
91
+ const run = yield* begin;
92
+ try {
93
+ return yield* run.wait(options);
94
+ }
95
+ finally {
96
+ yield* run.halt();
97
+ }
98
+ }