keri-ts 2.2.3 → 2.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/esm/keri/npm/src/_dnt.polyfills.js +14 -14
  2. package/esm/keri/npm/src/acdc/registering.js +19 -0
  3. package/esm/keri/npm/src/acdc/salting.js +4 -0
  4. package/esm/keri/npm/src/app/bootstrap/authenticating.js +34 -12
  5. package/esm/keri/npm/src/app/bootstrap/oobiery.js +365 -32
  6. package/esm/keri/npm/src/app/credentials/credential-importing.js +33 -4
  7. package/esm/keri/npm/src/app/credentials/root-replicator.js +27 -15
  8. package/esm/keri/npm/src/app/credentials/schema-resolving.js +5 -1
  9. package/esm/keri/npm/src/app/credentials/tel-publishing.js +16 -7
  10. package/esm/keri/npm/src/app/delivery/forwarding.js +188 -47
  11. package/esm/keri/npm/src/app/delivery/relaying.js +232 -0
  12. package/esm/keri/npm/src/app/delivery/respondant.js +8 -5
  13. package/esm/keri/npm/src/app/exchanges/exchange-messaging.js +1 -1
  14. package/esm/keri/npm/src/app/exchanges/exchanging.js +65 -9
  15. package/esm/keri/npm/src/app/identity/delegating.js +66 -9
  16. package/esm/keri/npm/src/app/identity/habbing.js +190 -71
  17. package/esm/keri/npm/src/app/identity/keeping.js +111 -39
  18. package/esm/keri/npm/src/app/identity/retire-identifier.js +98 -0
  19. package/esm/keri/npm/src/app/ipex/disclosure-replaying.js +116 -0
  20. package/esm/keri/npm/src/app/ipex/group-ipex.js +122 -25
  21. package/esm/keri/npm/src/app/ipex/group-issuance.js +109 -23
  22. package/esm/keri/npm/src/app/ipex/ipex-discloser.js +36 -22
  23. package/esm/keri/npm/src/app/ipex/ipex-grouping.js +53 -3
  24. package/esm/keri/npm/src/app/ipex/ipex-v2.js +7 -2
  25. package/esm/keri/npm/src/app/ipex/issuance.js +131 -35
  26. package/esm/keri/npm/src/app/ipex/pending-presentations.js +4 -2
  27. package/esm/keri/npm/src/app/ipex/presenting.js +3 -18
  28. package/esm/keri/npm/src/app/ipex/receive-issuance.js +2 -1
  29. package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +16 -3
  30. package/esm/keri/npm/src/app/mailbox/agent-inbox.js +292 -0
  31. package/esm/keri/npm/src/app/mailbox/forward-handler.js +298 -52
  32. package/esm/keri/npm/src/app/mailbox/kel-publisher.js +26 -31
  33. package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +70 -62
  34. package/esm/keri/npm/src/app/mailbox/mailboxing.js +20 -4
  35. package/esm/keri/npm/src/app/multisig/counseling.js +75 -27
  36. package/esm/keri/npm/src/app/multisig/group-members.js +41 -5
  37. package/esm/keri/npm/src/app/multisig/grouping.js +50 -2
  38. package/esm/keri/npm/src/app/multisig/multisig-workflows.js +3 -18
  39. package/esm/keri/npm/src/app/multisig/proposing.js +1 -16
  40. package/esm/keri/npm/src/app/multisig/rejoin-group.js +99 -0
  41. package/esm/keri/npm/src/app/multisig/replace-member.js +29 -8
  42. package/esm/keri/npm/src/app/protocol/parsering.js +45 -17
  43. package/esm/keri/npm/src/app/protocol/protocol-processing.js +62 -27
  44. package/esm/keri/npm/src/app/queries/query-replies.js +0 -22
  45. package/esm/keri/npm/src/app/queries/relay-refresh.js +81 -0
  46. package/esm/keri/npm/src/app/queries/witness-inquisitor.js +190 -40
  47. package/esm/keri/npm/src/app/runtime/agent-runtime.js +46 -73
  48. package/esm/keri/npm/src/app/runtime/cue-runtime.js +45 -5
  49. package/esm/keri/npm/src/app/runtime/doing.js +25 -0
  50. package/esm/keri/npm/src/app/runtime/operation-owners.js +26 -56
  51. package/esm/keri/npm/src/app/runtime/workflow-contract.js +7 -4
  52. package/esm/keri/npm/src/app/transport/http-client.js +20 -2
  53. package/esm/keri/npm/src/app/transport/message-source.js +2 -1
  54. package/esm/keri/npm/src/app/transport/messengering.js +39 -8
  55. package/esm/keri/npm/src/app/witness/ingress.js +7 -0
  56. package/esm/keri/npm/src/app/witness/receipt-client.js +43 -16
  57. package/esm/keri/npm/src/app/witness/receipting.js +62 -20
  58. package/esm/keri/npm/src/cli/messaging/multisig.js +4 -3
  59. package/esm/keri/npm/src/core/errors.js +73 -0
  60. package/esm/keri/npm/src/core/kel/delegation-ancestry.js +11 -2
  61. package/esm/keri/npm/src/core/kel/eventing.js +25 -28
  62. package/esm/keri/npm/src/core/kel/kever.js +15 -9
  63. package/esm/keri/npm/src/core/mailbox/kram-policy.js +8 -0
  64. package/esm/keri/npm/src/core/mailbox/kram-pruning.js +25 -6
  65. package/esm/keri/npm/src/core/mailbox/kram-time.js +34 -6
  66. package/esm/keri/npm/src/core/mailbox/kraming.js +58 -2
  67. package/esm/keri/npm/src/core/routing.js +18 -25
  68. package/esm/keri/npm/src/core/scheming.js +12 -5
  69. package/esm/keri/npm/src/core/wire-fields.js +126 -0
  70. package/esm/keri/npm/src/db/basing.js +4 -13
  71. package/esm/keri/npm/src/db/core/indexeddb-persistence.js +3 -2
  72. package/esm/keri/npm/src/db/core/lmdber.js +25 -6
  73. package/esm/keri/npm/src/db/core/memstore.js +4 -0
  74. package/esm/keri/npm/src/db/core/store-owner.js +4 -0
  75. package/esm/keri/npm/src/db/core/webdber.js +85 -27
  76. package/esm/keri/npm/src/db/mailbox/mailboxing.js +13 -0
  77. package/esm/keri/npm/src/db/subing.js +4 -0
  78. package/esm/keri/npm/src/runtime/index.js +11 -5
  79. package/esm/keri/npm/src/time/mod.js +5 -0
  80. package/esm/keri/npm/src/vdr/credential-tsn-record.js +2 -7
  81. package/esm/keri/npm/src/vdr/eventing.js +28 -19
  82. package/esm/keri/npm/src/vdr/registry-tsn-record.js +3 -13
  83. package/esm/keri/npm/src/version.js +2 -2
  84. package/package.json +1 -1
  85. package/types/_dnt.polyfills.d.ts +9 -9
  86. package/types/_dnt.polyfills.d.ts.map +1 -1
  87. package/types/acdc/registering.d.ts +6 -0
  88. package/types/acdc/registering.d.ts.map +1 -1
  89. package/types/acdc/salting.d.ts.map +1 -1
  90. package/types/app/bootstrap/authenticating.d.ts +12 -2
  91. package/types/app/bootstrap/authenticating.d.ts.map +1 -1
  92. package/types/app/bootstrap/oobiery.d.ts +144 -7
  93. package/types/app/bootstrap/oobiery.d.ts.map +1 -1
  94. package/types/app/credentials/credential-importing.d.ts +8 -0
  95. package/types/app/credentials/credential-importing.d.ts.map +1 -1
  96. package/types/app/credentials/root-replicator.d.ts +19 -8
  97. package/types/app/credentials/root-replicator.d.ts.map +1 -1
  98. package/types/app/credentials/schema-resolving.d.ts.map +1 -1
  99. package/types/app/credentials/tel-publishing.d.ts +4 -2
  100. package/types/app/credentials/tel-publishing.d.ts.map +1 -1
  101. package/types/app/delivery/forwarding.d.ts +75 -7
  102. package/types/app/delivery/forwarding.d.ts.map +1 -1
  103. package/types/app/delivery/relaying.d.ts +144 -0
  104. package/types/app/delivery/relaying.d.ts.map +1 -0
  105. package/types/app/delivery/respondant.d.ts +9 -2
  106. package/types/app/delivery/respondant.d.ts.map +1 -1
  107. package/types/app/exchanges/exchanging.d.ts +36 -0
  108. package/types/app/exchanges/exchanging.d.ts.map +1 -1
  109. package/types/app/identity/delegating.d.ts +6 -0
  110. package/types/app/identity/delegating.d.ts.map +1 -1
  111. package/types/app/identity/habbing.d.ts +106 -19
  112. package/types/app/identity/habbing.d.ts.map +1 -1
  113. package/types/app/identity/keeping.d.ts +54 -19
  114. package/types/app/identity/keeping.d.ts.map +1 -1
  115. package/types/app/identity/retire-identifier.d.ts +24 -0
  116. package/types/app/identity/retire-identifier.d.ts.map +1 -0
  117. package/types/app/ipex/disclosure-replaying.d.ts +61 -0
  118. package/types/app/ipex/disclosure-replaying.d.ts.map +1 -0
  119. package/types/app/ipex/group-ipex.d.ts +60 -14
  120. package/types/app/ipex/group-ipex.d.ts.map +1 -1
  121. package/types/app/ipex/group-issuance.d.ts +48 -6
  122. package/types/app/ipex/group-issuance.d.ts.map +1 -1
  123. package/types/app/ipex/ipex-discloser.d.ts +14 -2
  124. package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
  125. package/types/app/ipex/ipex-grouping.d.ts +23 -2
  126. package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
  127. package/types/app/ipex/ipex-v2.d.ts +5 -2
  128. package/types/app/ipex/ipex-v2.d.ts.map +1 -1
  129. package/types/app/ipex/issuance.d.ts +35 -11
  130. package/types/app/ipex/issuance.d.ts.map +1 -1
  131. package/types/app/ipex/pending-presentations.d.ts +4 -2
  132. package/types/app/ipex/pending-presentations.d.ts.map +1 -1
  133. package/types/app/ipex/presenting.d.ts.map +1 -1
  134. package/types/app/ipex/receive-issuance.d.ts +2 -1
  135. package/types/app/ipex/receive-issuance.d.ts.map +1 -1
  136. package/types/app/ipex/unblinding-workflow.d.ts +4 -1
  137. package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
  138. package/types/app/mailbox/agent-inbox.d.ts +126 -0
  139. package/types/app/mailbox/agent-inbox.d.ts.map +1 -0
  140. package/types/app/mailbox/forward-handler.d.ts +114 -14
  141. package/types/app/mailbox/forward-handler.d.ts.map +1 -1
  142. package/types/app/mailbox/kel-publisher.d.ts +12 -22
  143. package/types/app/mailbox/kel-publisher.d.ts.map +1 -1
  144. package/types/app/mailbox/mailbox-poller.d.ts +40 -24
  145. package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
  146. package/types/app/mailbox/mailboxing.d.ts +19 -5
  147. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  148. package/types/app/multisig/counseling.d.ts +34 -11
  149. package/types/app/multisig/counseling.d.ts.map +1 -1
  150. package/types/app/multisig/group-members.d.ts +17 -1
  151. package/types/app/multisig/group-members.d.ts.map +1 -1
  152. package/types/app/multisig/grouping.d.ts +17 -1
  153. package/types/app/multisig/grouping.d.ts.map +1 -1
  154. package/types/app/multisig/multisig-workflows.d.ts +0 -8
  155. package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
  156. package/types/app/multisig/proposing.d.ts +4 -19
  157. package/types/app/multisig/proposing.d.ts.map +1 -1
  158. package/types/app/multisig/rejoin-group.d.ts +119 -0
  159. package/types/app/multisig/rejoin-group.d.ts.map +1 -0
  160. package/types/app/multisig/replace-member.d.ts +15 -1
  161. package/types/app/multisig/replace-member.d.ts.map +1 -1
  162. package/types/app/protocol/parsering.d.ts +21 -2
  163. package/types/app/protocol/parsering.d.ts.map +1 -1
  164. package/types/app/protocol/protocol-processing.d.ts +31 -6
  165. package/types/app/protocol/protocol-processing.d.ts.map +1 -1
  166. package/types/app/queries/query-replies.d.ts +4 -8
  167. package/types/app/queries/query-replies.d.ts.map +1 -1
  168. package/types/app/queries/relay-refresh.d.ts +26 -0
  169. package/types/app/queries/relay-refresh.d.ts.map +1 -0
  170. package/types/app/queries/witness-inquisitor.d.ts +57 -6
  171. package/types/app/queries/witness-inquisitor.d.ts.map +1 -1
  172. package/types/app/runtime/agent-runtime.d.ts +34 -3
  173. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  174. package/types/app/runtime/cue-runtime.d.ts +15 -3
  175. package/types/app/runtime/cue-runtime.d.ts.map +1 -1
  176. package/types/app/runtime/doing.d.ts +17 -0
  177. package/types/app/runtime/doing.d.ts.map +1 -1
  178. package/types/app/runtime/operation-owners.d.ts +16 -26
  179. package/types/app/runtime/operation-owners.d.ts.map +1 -1
  180. package/types/app/runtime/workflow-contract.d.ts +21 -6
  181. package/types/app/runtime/workflow-contract.d.ts.map +1 -1
  182. package/types/app/transport/http-client.d.ts +9 -0
  183. package/types/app/transport/http-client.d.ts.map +1 -1
  184. package/types/app/transport/message-source.d.ts +6 -3
  185. package/types/app/transport/message-source.d.ts.map +1 -1
  186. package/types/app/transport/messengering.d.ts +10 -1
  187. package/types/app/transport/messengering.d.ts.map +1 -1
  188. package/types/app/witness/ingress.d.ts +1 -0
  189. package/types/app/witness/ingress.d.ts.map +1 -1
  190. package/types/app/witness/receipt-client.d.ts +19 -1
  191. package/types/app/witness/receipt-client.d.ts.map +1 -1
  192. package/types/app/witness/receipting.d.ts +24 -1
  193. package/types/app/witness/receipting.d.ts.map +1 -1
  194. package/types/cli/messaging/multisig.d.ts.map +1 -1
  195. package/types/core/cues.d.ts +53 -1
  196. package/types/core/cues.d.ts.map +1 -1
  197. package/types/core/errors.d.ts +32 -0
  198. package/types/core/errors.d.ts.map +1 -1
  199. package/types/core/kel/delegation-ancestry.d.ts.map +1 -1
  200. package/types/core/kel/eventing.d.ts.map +1 -1
  201. package/types/core/kel/kever-decisions.d.ts +3 -1
  202. package/types/core/kel/kever-decisions.d.ts.map +1 -1
  203. package/types/core/kel/kever.d.ts +7 -1
  204. package/types/core/kel/kever.d.ts.map +1 -1
  205. package/types/core/mailbox/kram-policy.d.ts +6 -0
  206. package/types/core/mailbox/kram-policy.d.ts.map +1 -1
  207. package/types/core/mailbox/kram-pruning.d.ts +5 -1
  208. package/types/core/mailbox/kram-pruning.d.ts.map +1 -1
  209. package/types/core/mailbox/kram-time.d.ts +26 -2
  210. package/types/core/mailbox/kram-time.d.ts.map +1 -1
  211. package/types/core/mailbox/kraming.d.ts +14 -1
  212. package/types/core/mailbox/kraming.d.ts.map +1 -1
  213. package/types/core/routing.d.ts +3 -7
  214. package/types/core/routing.d.ts.map +1 -1
  215. package/types/core/scheming.d.ts.map +1 -1
  216. package/types/core/wire-fields.d.ts +56 -0
  217. package/types/core/wire-fields.d.ts.map +1 -0
  218. package/types/db/basing.d.ts.map +1 -1
  219. package/types/db/core/dber.d.ts +5 -0
  220. package/types/db/core/dber.d.ts.map +1 -1
  221. package/types/db/core/indexeddb-persistence.d.ts +3 -2
  222. package/types/db/core/indexeddb-persistence.d.ts.map +1 -1
  223. package/types/db/core/lmdber.d.ts +6 -0
  224. package/types/db/core/lmdber.d.ts.map +1 -1
  225. package/types/db/core/memstore.d.ts +2 -0
  226. package/types/db/core/memstore.d.ts.map +1 -1
  227. package/types/db/core/store-owner.d.ts +2 -0
  228. package/types/db/core/store-owner.d.ts.map +1 -1
  229. package/types/db/core/webdber.d.ts +32 -6
  230. package/types/db/core/webdber.d.ts.map +1 -1
  231. package/types/db/exchange/exchange-records.d.ts +4 -0
  232. package/types/db/exchange/exchange-records.d.ts.map +1 -1
  233. package/types/db/mailbox/mailboxing.d.ts +10 -1
  234. package/types/db/mailbox/mailboxing.d.ts.map +1 -1
  235. package/types/db/subing.d.ts +2 -0
  236. package/types/db/subing.d.ts.map +1 -1
  237. package/types/runtime/index.d.ts +20 -14
  238. package/types/runtime/index.d.ts.map +1 -1
  239. package/types/time/mod.d.ts +2 -0
  240. package/types/time/mod.d.ts.map +1 -1
  241. package/types/vdr/credential-tsn-record.d.ts.map +1 -1
  242. package/types/vdr/eventing.d.ts.map +1 -1
  243. package/types/vdr/registry-tsn-record.d.ts.map +1 -1
  244. package/types/version.d.ts +2 -2
  245. package/esm/keri/npm/src/app/runtime/signer-conditions.js +0 -51
  246. package/types/app/runtime/signer-conditions.d.ts +0 -32
  247. package/types/app/runtime/signer-conditions.d.ts.map +0 -1
@@ -184,21 +184,6 @@ function* publishVdrProposal(hby, protocol, postman, member, recipients, proposa
184
184
  return groupSendResult(outcomes);
185
185
  }
186
186
  /** True when a KEL event is accepted locally. */
187
- /**
188
- * Commit the members of a locally accepted group establishment event.
189
- *
190
- * Only the current latest establishment event commits; replaying an older
191
- * approval must not restore historical group membership. Returns whether the
192
- * members were committed.
193
- */
194
- export function commitAcceptedGroupMembers(hby, serder, smids, rmids) {
195
- const pre = serder.pre;
196
- if (!pre || !eventAccepted(hby, serder) || hby.db.getKever(pre)?.lastEst.d !== serder.said) {
197
- return false;
198
- }
199
- hby.commitGroupHabMembers(pre, smids, rmids.length > 0 ? rmids : smids);
200
- return true;
201
- }
202
187
  export function eventAccepted(hby, serder) {
203
188
  const pre = serder.pre;
204
189
  const said = serder.said;
@@ -383,9 +368,9 @@ export function* approveKelProposal(hby, runtime, exn, route, options) {
383
368
  // approval must reuse its proposal's wrapper version for the proposer to merge it.
384
369
  exn.pvrsn);
385
370
  const accepted = eventAccepted(hby, serder);
386
- if (proposal.route !== MULTISIG_IXN_ROUTE) {
387
- commitAcceptedGroupMembers(hby, serder, smids, proposal.attrs.rmids);
388
- }
371
+ // An establishment event this approval completed: the members follow accepted state now, not the proposal's lists.
372
+ if (accepted && proposal.route !== MULTISIG_IXN_ROUTE)
373
+ hby.alignGroupMembers(groupPre);
389
374
  return { route, said: wrapperSaid, embedded: embeddedSaid ?? "", group: groupPre, accepted, ...publication };
390
375
  }
391
376
  /**
@@ -4,7 +4,7 @@ import { createCredential, inceptCredentialRegistry, } from "../credentials/cred
4
4
  import { groupSigningMembers, localGroupMember, uniqueMembers } from "./group-members.js";
5
5
  import { MULTISIG_ICP_ROUTE, MULTISIG_IXN_ROUTE, MULTISIG_ROT_ROUTE } from "./grouping.js";
6
6
  import { remoteGroupMembers } from "./multisig-messaging.js";
7
- import { approveAcdcV2Registry, commitAcceptedGroupMembers, publishAcdcV2Registry, publishProposal, } from "./multisig-workflows.js";
7
+ import { approveAcdcV2Registry, publishAcdcV2Registry, publishProposal, } from "./multisig-workflows.js";
8
8
  /** Propose a new group: create this member's inception and publish it. */
9
9
  export function* proposeGroupInception(deps, member, alias, { smids, rmids = smids, ...args }, inputs) {
10
10
  // Publishing needs a route to every other member. Refuse before the group habitat exists, so a call that could
@@ -44,21 +44,6 @@ export function* proposeGroupInteraction(deps, group, data, inputs, version) {
44
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
45
  return { ...sent, route: MULTISIG_IXN_ROUTE, group: created.hab.pre, event, smids, rmids: [] };
46
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
47
  /**
63
48
  * Propose an ACDC v2 registry event (`rip` or `bup`): author the group anchor
64
49
  * that seals it, process this member's own contribution, hand the anchor to the
@@ -0,0 +1,99 @@
1
+ import { ValidationError } from "../../core/errors.js";
2
+ import { beginOobi, oobiStatus } from "../bootstrap/oobi-resolving.js";
3
+ import { observeUntil } from "../runtime/workflow-contract.js";
4
+ import { acceptedGroupMembers, isAcceptedGroupMember } from "./group-members.js";
5
+ /**
6
+ * Resolve `input.oobi`, wait for the group's accepted key state, and record the group with `input.member` as
7
+ * its local member, using the accepted members. Then the mailbox poller, the KEL publisher and the root
8
+ * replicator are prompted for the group, so each acts for it on its next tock. Creates no event and signs
9
+ * nothing. Observe-only: the host must already run the runtime's workers (see `workflow-contract.ts`).
10
+ *
11
+ * A record that already names `member` returns `joined` at once with `written: false`, with no OOBI and no write.
12
+ *
13
+ * @throws ValidationError for misuse: `member` is not a local single-signature habitat, `group` is a local
14
+ * single-signature habitat, the group already has a local record naming a different member (use
15
+ * `replaceGroupMember`), or `alias` is bound to another prefix.
16
+ */
17
+ export function* rejoinGroup(deps, input) {
18
+ const { hby } = deps, { group, member, alias, timeoutMs, domain } = input;
19
+ const recorded = rejoinedRecord(hby, input);
20
+ if (recorded)
21
+ return recorded;
22
+ beginOobi(deps, input.oobi);
23
+ const read = () => readRejoin(hby.db, input.oobi, group, member.pre);
24
+ yield* observeUntil(() => read().kind !== "waiting", { timeoutMs, clock: deps.clock });
25
+ const reading = read();
26
+ switch (reading.kind) {
27
+ case "waiting":
28
+ return { outcome: "timed-out", group, timeoutMs, waiting: reading.waiting };
29
+ case "oobi-failed":
30
+ return { outcome: "oobi-failed", group, reason: reading.reason };
31
+ case "not-member":
32
+ return { outcome: "not-member", group, member: member.pre };
33
+ }
34
+ const { smids, rmids } = reading.members;
35
+ if (!smids.includes(member.pre) && !rmids.includes(member.pre)) {
36
+ return { outcome: "not-member", group, member: member.pre };
37
+ }
38
+ hby.joinGroupHab(group, alias, member, smids, rmids, domain);
39
+ deps.mailboxPoller.prompt(group);
40
+ deps.kelPublisher.prompt(group);
41
+ deps.rootReplicator?.prompt(group);
42
+ deps.changes?.push({ kind: "group", id: group });
43
+ return { outcome: "joined", group, alias, smids, rmids, written: true };
44
+ }
45
+ /**
46
+ * Refuse misuse before any I/O, and return `joined` for a record that already names the member.
47
+ * Null when the group has no local record yet.
48
+ */
49
+ function rejoinedRecord(hby, { group, member, alias, domain }) {
50
+ if (!group)
51
+ throw new ValidationError("Group prefix is required.");
52
+ if (!alias)
53
+ throw new ValidationError("Group alias is required.");
54
+ const pre = member.pre;
55
+ if (!pre || hby.habs.get(pre) !== member) {
56
+ throw new ValidationError(`Member ${pre} is not a habitat of this Habery.`);
57
+ }
58
+ if (hby.db.getHab(pre)?.mid) {
59
+ throw new ValidationError(`Member ${pre} is a group identifier; a group rejoins through a single-signature member.`);
60
+ }
61
+ const record = hby.db.getHab(group);
62
+ if (record && !record.mid) {
63
+ throw new ValidationError(`${group} is a local single-signature identifier, not a group.`);
64
+ }
65
+ if (record?.mid && record.mid !== pre) {
66
+ throw new ValidationError(`Group ${group} is already recorded with local member ${record.mid}; use replaceGroupMember to change it.`);
67
+ }
68
+ if (record?.mid) {
69
+ return {
70
+ outcome: "joined",
71
+ group,
72
+ alias: record.name ?? alias,
73
+ smids: [...(record.smids ?? [])],
74
+ rmids: [...(record.rmids ?? [])],
75
+ written: false,
76
+ };
77
+ }
78
+ const bound = hby.db.getName(domain ?? "", alias);
79
+ if (bound && bound !== group)
80
+ throw new ValidationError(`Alias ${alias} is already bound to ${bound}.`);
81
+ return null;
82
+ }
83
+ /** Read the OOBI, the group's accepted key state and its members, in that order. */
84
+ function readRejoin(db, oobi, group, member) {
85
+ const status = oobiStatus({ db }, oobi);
86
+ if (status.state === "pending")
87
+ return { kind: "waiting", waiting: "oobi" };
88
+ if (!db.getKever(group)) {
89
+ return status.state === "failed"
90
+ ? { kind: "oobi-failed", reason: status.reason }
91
+ : { kind: "waiting", waiting: "group-key-state" };
92
+ }
93
+ // Every list member is an accepted member, so this answers `not-member` before the other members' KELs arrive.
94
+ if (!isAcceptedGroupMember(db, group, member))
95
+ return { kind: "not-member" };
96
+ const candidates = [...db.states.getTopItemIter()].map(([keys]) => keys.join("."));
97
+ const members = acceptedGroupMembers(db, group, candidates);
98
+ return members ? { kind: "members", members } : { kind: "waiting", waiting: "member-key-state" };
99
+ }
@@ -1,16 +1,27 @@
1
1
  /**
2
- * Replace a local group's recorded member after a rotation removed it, and end the runtime's work bound to it.
2
+ * Replace a local group's recorded member after a rotation removed it, end the runtime's work bound to it, and
3
+ * retire it when nothing else needs it.
3
4
  *
4
5
  * `Habery.joinGroupHab` re-points the record and clears the durable rows addressed to the old member. The
5
6
  * runtime owns the live operations, so this is the one call that also ends them: an issuance whose party
6
7
  * member or frozen DH custodian is the old member, and an Unblind holding it. Each ends through its owner's
7
8
  * own end path, which releases the approval and drops the held `heldq`/`heldu` rows. Group IPEX messages and
8
9
  * presentations keep no member binding (their contributions are already signed and the member is read live),
9
- * so they are left to run. Nothing is signed and no event is made.
10
+ * so they are left to run. It then prompts the mailbox poller, the KEL publisher and the root replicator for the
11
+ * group, so each acts as the new member on its next tock instead of at its next sweep. Nothing is signed and no
12
+ * event is made.
13
+ *
14
+ * Last, the old member is retired with `retireIdentifier` (its stored keys deleted, its mailbox workers
15
+ * stopped, every KEL and BADA record about it kept) unless the caller set `keepPrevious` or
16
+ * `retireIdentifierRefusal` names a reason to keep it: it is a member of another local group, a delegator, a
17
+ * registry issuer, or has live work. A kept member does not fail the replacement; the result says why it was
18
+ * kept.
10
19
  */
11
20
  import { ValidationError } from "../../core/errors.js";
21
+ import { retireIdentifier, retireIdentifierRefusal } from "../identity/retire-identifier.js";
12
22
  /**
13
- * Replace the local member of `input.group`, then end live operations bound to the previous one.
23
+ * Replace the local member of `input.group`, end live operations bound to the previous one, then retire the
24
+ * previous one unless it is kept (see the module doc).
14
25
  *
15
26
  * @throws ValidationError when `joinGroupHab` refuses: the group has no accepted state, the previous member is
16
27
  * still a member of it, or the new one is not.
@@ -25,10 +36,20 @@ export function replaceGroupMember(runtime, input) {
25
36
  throw new ValidationError(`Member ${previous} is already the local member of ${group}.`);
26
37
  hby.joinGroupHab(group, alias, member, input.smids, input.rmids ?? input.smids, record.domain);
27
38
  const reason = `Local member ${previous} of group ${group} was replaced by ${member.pre}`;
39
+ runtime.mailboxPoller.prompt(group);
40
+ runtime.kelPublisher.prompt(group);
41
+ runtime.rootReplicator?.prompt(group);
28
42
  const ops = runtime.operations;
29
- return {
30
- previous,
31
- issuance: ops?.issuance.endBoundToMember(group, previous, reason) ?? [],
32
- unblinding: ops?.unblinding.endBoundToMember(group, previous, reason) ?? [],
33
- };
43
+ const issuance = ops?.issuance.endBoundToMember(group, previous, reason) ?? [];
44
+ const unblinding = ops?.unblinding.endBoundToMember(group, previous, reason) ?? [];
45
+ return { previous, issuance, unblinding, previousMember: retirePrevious(runtime, previous, input.keepPrevious) };
46
+ }
47
+ /** Retire the replaced member, or say why it is kept. */
48
+ function retirePrevious(runtime, previous, keep = false) {
49
+ if (keep)
50
+ return { kind: "kept", reason: "The caller asked to keep the previous member." };
51
+ const refusal = retireIdentifierRefusal(runtime, previous);
52
+ if (refusal)
53
+ return { kind: "kept", reason: refusal };
54
+ return { kind: "retired", retired: retireIdentifier(runtime, previous) };
34
55
  }
@@ -1,7 +1,9 @@
1
- import { BlindState, BoundState, Cigar, concatBytes, Counter, Dater, Diger, FirstSeen, Ilks, isCounterGroupLike, isGroupTuple, Labeler, Noncer, NumberPrimitive, parseBytes, parseSerder, Prefixer, requireFlatFrame, SealEvent, SealKind, SealSource, SerderACDC, SerderKERI, Siger, Texter, TypeMedia, Verfer, Verser, } from "cesr-ts";
1
+ import { BlindState, BoundState, Cigar, concatBytes, Counter, Dater, Diger, FirstSeen, Ilks, isCounterGroupLike, isGroupTuple, Labeler, Noncer, NumberPrimitive, parseBytes, parseSerder, Prefixer, SealEvent, SealKind, SealSource, SerderACDC, SerderKERI, Siger, Texter, TypeMedia, Verfer, Verser, } from "cesr-ts";
2
2
  import { KeriDispatchEnvelope, PathedMaterialGroup, TransIdxSigGroup, TransLastIdxSigGroup, TransReceiptQuadruple, } from "../../core/dispatch.js";
3
+ import { ValidationError } from "../../core/errors.js";
3
4
  import { nestedIpexRoute, supportsNestedExchange } from "../../core/exchange/protocol-exchanging.js";
4
5
  import { serializeMessage } from "../../core/protocol-serialization.js";
6
+ import { readPrefixer } from "../../core/wire-fields.js";
5
7
  /**
6
8
  * Convert one or more already-parsed CESR frames into KERI dispatch envelopes.
7
9
  *
@@ -29,12 +31,14 @@ export function envelopesFromFrames(frames, local = false) {
29
31
  * @param messageKevery Authoritative shared message-policy/KRAM processor when distinct from input Kevery.
30
32
  * @param registry V2 registry intake over accepted public KEL state, without requiring a local alias.
31
33
  * @param qvy Optional auxiliary query owner; receives full evidence after v2 message admission.
34
+ * @param retained Whether this is retained history from a finite local import (`DispatchOptions.retained`): a v2
35
+ * `exn`/`xip` then skips KRAM intake and reaches the Exchanger marked retained. Network intake never sets it.
32
36
  * @returns The processed envelope and protocol-specific decision data.
33
37
  * @throws Unsupported input and infrastructure/handler failures follow their owning processor contracts.
34
38
  */
35
39
  export function* dispatchEnvelope(envelope, revery, kevery, exchanger, tevery, verifier, mailboxAid,
36
40
  /** Long-lived v2 message policy/KRAM owner; input-specific Kevery handles KEL and legacy traffic. */
37
- messageKevery = kevery, registry, qvy) {
41
+ messageKevery = kevery, registry, qvy, retained = false) {
38
42
  const serder = envelope.serder;
39
43
  if (serder instanceof SerderACDC) {
40
44
  // Historical upd bytes remain decodable, but cannot enter live registry processing.
@@ -59,7 +63,7 @@ messageKevery = kevery, registry, qvy) {
59
63
  }
60
64
  if (serder.pvrsn.major >= 2
61
65
  && [Ilks.qry, Ilks.rpy, Ilks.exn, Ilks.xip, Ilks.pro, Ilks.bar].some((ilk) => ilk === serder.ilk)) {
62
- return yield* dispatchMessage(envelope, revery, messageKevery, exchanger, tevery, mailboxAid, qvy);
66
+ return yield* dispatchMessage(envelope, revery, messageKevery, exchanger, tevery, mailboxAid, qvy, retained);
63
67
  }
64
68
  switch (serder.ilk) {
65
69
  case Ilks.rpy:
@@ -132,8 +136,9 @@ messageKevery = kevery, registry, qvy) {
132
136
  * processMsg boundary: sender policy precedes optional KRAM and proof
133
137
  * prerequisites and delegation. Downstream processors authenticate their own
134
138
  * evidence; this layer borrows the envelope and never owns its payloads.
139
+ * A retained exchange (finite local import) skips only the KRAM step.
135
140
  */
136
- function* dispatchMessage(envelope, revery, kevery, exchanger, tevery, mailboxAid, qvy) {
141
+ function* dispatchMessage(envelope, revery, kevery, exchanger, tevery, mailboxAid, qvy, retained = false) {
137
142
  const serder = envelope.serder;
138
143
  const sender = serder.pre;
139
144
  if (sender !== null) {
@@ -147,15 +152,18 @@ function* dispatchMessage(envelope, revery, kevery, exchanger, tevery, mailboxAi
147
152
  && !(qvy?.claims ? qvy.claims(serder) : qvy?.resource === serder.route)) {
148
153
  return { envelope, decision: { kind: "drop", reason: "missingProcessor" } };
149
154
  }
150
- const result = kevery.kramer?.intake(envelope);
155
+ // Retained history was KRAM-admitted where it was first received; its window here has passed by design. Queries
156
+ // and replies are never retained history and always pass KRAM.
157
+ const history = retained && (serder.ilk === Ilks.exn || serder.ilk === Ilks.xip);
158
+ const result = history ? undefined : kevery.kramer?.intake(envelope);
151
159
  if (result && result.kind !== "ready")
152
160
  return { envelope, decision: result };
153
161
  const ready = result?.kind === "ready" ? result.envelope : envelope;
154
- const decision = yield* dispatchReadyMessage(ready, revery, kevery, exchanger, tevery, mailboxAid, result?.kind === "ready" ? result.receiverTime : undefined, qvy);
162
+ const decision = yield* dispatchReadyMessage(ready, revery, kevery, exchanger, tevery, mailboxAid, result?.kind === "ready" ? result.receiverTime : undefined, qvy, history);
155
163
  return { envelope: ready, decision };
156
164
  }
157
165
  /** Delegate accepted evidence; downstream processors verify their own proof owners. */
158
- function* dispatchReadyMessage(envelope, revery, kevery, exchanger, tevery, mailboxAid, receiverTime, qvy) {
166
+ function* dispatchReadyMessage(envelope, revery, kevery, exchanger, tevery, mailboxAid, receiverTime, qvy, retained = false) {
159
167
  const serder = envelope.serder;
160
168
  switch (serder.ilk) {
161
169
  case Ilks.qry: {
@@ -186,7 +194,7 @@ function* dispatchReadyMessage(envelope, revery, kevery, exchanger, tevery, mail
186
194
  }
187
195
  if (!exchanger)
188
196
  return { kind: "drop", reason: "missingProcessor" };
189
- return yield* dispatchExchange(envelope, exchanger, mailboxAid, receiverTime);
197
+ return yield* dispatchExchange(envelope, exchanger, mailboxAid, receiverTime, retained);
190
198
  default:
191
199
  // processPro/processBar remain pass stubs at the selected reference.
192
200
  return { kind: "drop", reason: "referenceStub" };
@@ -201,7 +209,7 @@ function dispatchTelQuery(query, tevery) {
201
209
  return tevery.processQuery(query);
202
210
  }
203
211
  /** Await the route owner while preserving parent, child and mailbox provenance. */
204
- function* dispatchExchange(envelope, exchanger, mailboxAid, receiverTime) {
212
+ function* dispatchExchange(envelope, exchanger, mailboxAid, receiverTime, retained = false) {
205
213
  return yield* exchanger.processEvent({
206
214
  serder: envelope.serder,
207
215
  tsgs: envelope.tsgs,
@@ -216,6 +224,7 @@ function* dispatchExchange(envelope, exchanger, mailboxAid, receiverTime) {
216
224
  nests: envelope.nests,
217
225
  mailboxAid,
218
226
  receiverTime,
227
+ ...(retained ? { retained } : {}),
219
228
  });
220
229
  }
221
230
  function telEnvelopeFromDispatch(envelope, serder) {
@@ -439,14 +448,32 @@ function collectAttachments(group, envelope) {
439
448
  return;
440
449
  }
441
450
  }
442
- /** Project one complete frame for message routing without an intermediate event array. */
443
- export function envelopeFromFrame(message, local = false) {
444
- // Only completed consumers may retain parent-owned children through normalization.
451
+ /**
452
+ * Whether a frame carries nested messages that no completed consumer owns.
453
+ *
454
+ * Only supported exchange routes, IPEX disclosure `xip`s and the v2 Unblind query retain parent-owned children; any
455
+ * other frame with children is unsupported input. A remote frame is refused as one invalid message
456
+ * (`ProtocolProcessor.dispatch`); {@link envelopeFromFrame} refuses it with a `ValidationError`.
457
+ */
458
+ export function hasUnsupportedNests(message) {
459
+ if (!message.nests?.length)
460
+ return false;
445
461
  const route = String(message.body.ked?.r ?? "");
446
462
  const unblindQuery = message.body.ilk === "qry" && message.body.pvrsn.major === 2 && route === "/ipex/unblind";
447
- if (!unblindQuery && ((message.body.ilk !== "exn" && !(message.body.ilk === "xip" && nestedIpexRoute(route)))
448
- || !supportsNestedExchange(route, message.body.pvrsn.major))) {
449
- requireFlatFrame(message, "KERI runtime normalization");
463
+ return !unblindQuery && ((message.body.ilk !== "exn" && !(message.body.ilk === "xip" && nestedIpexRoute(route)))
464
+ || !supportsNestedExchange(route, message.body.pvrsn.major));
465
+ }
466
+ /**
467
+ * Project one complete frame for message routing without an intermediate event array.
468
+ *
469
+ * Only completed consumers may retain parent-owned children through normalization. A frame with children no consumer
470
+ * owns is usually a remote child nested inside a parent (a BUP inside an Unblind query, an event inside a multisig or
471
+ * IPEX exchange): it is refused with a `ValidationError`, which the parent's processor reports as its own typed
472
+ * refusal (an invalid-message drop, a rejected escrow row), never a fault that ends the runtime.
473
+ */
474
+ export function envelopeFromFrame(message, local = false) {
475
+ if (hasUnsupportedNests(message)) {
476
+ throw new ValidationError(`KERI runtime normalization does not yet support nested message trees (${message.body.ilk ?? "unknown"} frame).`);
450
477
  }
451
478
  const serder = (message.body instanceof SerderKERI || message.body instanceof SerderACDC)
452
479
  ? message.body
@@ -516,8 +543,9 @@ export function restoreAuxiliaryQuery(wire, local = false) {
516
543
  export function queryEnvelopeFromDispatch(envelope) {
517
544
  const lastSsg = envelope.ssgs.at(-1);
518
545
  // V2 processMsg falls back to the claimed sender only when raw sigers exist.
519
- const rawSource = !lastSsg && envelope.serder.pvrsn.major >= 2 && envelope.sigers.length && envelope.serder.pre
520
- ? new Prefixer({ qb64: envelope.serder.pre })
546
+ // A claimed sender that is not a prefix authenticates nothing; the query is then refused as unauthenticated.
547
+ const rawSource = !lastSsg && envelope.serder.pvrsn.major >= 2 && envelope.sigers.length
548
+ ? readPrefixer(envelope.serder.pre) ?? undefined
521
549
  : undefined;
522
550
  return {
523
551
  serder: envelope.serder,
@@ -6,9 +6,10 @@ 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 { relayRefreshCue } from "../queries/relay-refresh.js";
10
+ import { doEveryOnWrite, PROTOCOL_PACING } from "../runtime/doing.js";
9
11
  import { systemClock } from "../runtime/runtime-clock.js";
10
- import { waitOnClock } from "../runtime/workflow-contract.js";
11
- import { dispatchEnvelope, envelopeFromFrame, queryEnvelopeFromDispatch, } from "./parsering.js";
12
+ import { dispatchEnvelope, envelopeFromFrame, hasUnsupportedNests, queryEnvelopeFromDispatch, } from "./parsering.js";
12
13
  /**
13
14
  * Wait after every escrow pass, as KERIpy's Doist cycle (`tock` 0.03125 s, app/directing.py) paces its `escrowDo`,
14
15
  * which runs one full pass per cycle. A pass is never skipped or shortened; this is the time others get between passes.
@@ -25,6 +26,24 @@ export const ESCROW_TOCK_MS = 31.25;
25
26
  function isInvalidMessageError(error) {
26
27
  return error instanceof ValidationError;
27
28
  }
29
+ /** Why a remote frame whose nested children no consumer owns is refused. */
30
+ const NESTED_REFUSAL = "KERI runtime normalization does not yet support nested message trees";
31
+ /**
32
+ * Report and describe the refusal of one extracted message, so its input continues with the next frame.
33
+ *
34
+ * @param envelope The refused message's own body and attachments.
35
+ * @param detail Why validation refused it.
36
+ * @returns The `invalidMessage` drop for this envelope.
37
+ */
38
+ function invalidMessage(envelope, detail) {
39
+ consoleLogger.warn("Skipping invalid message", {
40
+ said: envelope.serder.said,
41
+ ilk: envelope.serder.ilk,
42
+ route: envelope.serder.route,
43
+ detail,
44
+ });
45
+ return { envelope, decision: { kind: "drop", reason: "invalidMessage", detail } };
46
+ }
28
47
  /**
29
48
  * Dispatch to synchronous semantic owners and publish their committed output.
30
49
  *
@@ -48,6 +67,8 @@ export class ProtocolProcessor {
48
67
  * @param cues Application output Deck consumed by the composed cue router.
49
68
  * @param replies Shared live request correlations captured before application publication.
50
69
  * @param qvy Optional caller-owned auxiliary query receiver; owns its authorization and lifecycle.
70
+ * @param relaying Whether this domain is a relaying host's (the agent side of relay mode): a served sender's query
71
+ * about a remote subject then also publishes the host's own refresh query cue (see `relayRefreshCue`).
51
72
  * @throws TypeError for inconsistent stores or cue bindings.
52
73
  */
53
74
  constructor(
@@ -68,7 +89,9 @@ export class ProtocolProcessor {
68
89
  /** Ordinary application cues; the composed cue router is the consumer. */
69
90
  cues,
70
91
  /** Shared live request correlations; each requesting connection owns its slots. */
71
- replies, qvy) {
92
+ replies, qvy,
93
+ /** Relaying host: served senders' queries about remote subjects refresh this host's own view of them. */
94
+ relaying = false) {
72
95
  Object.defineProperty(this, "kvy", {
73
96
  enumerable: true,
74
97
  configurable: true,
@@ -129,6 +152,12 @@ export class ProtocolProcessor {
129
152
  writable: true,
130
153
  value: qvy
131
154
  });
155
+ Object.defineProperty(this, "relaying", {
156
+ enumerable: true,
157
+ configurable: true,
158
+ writable: true,
159
+ value: relaying
160
+ });
132
161
  if (protocolCues === cues || kvy.cues !== protocolCues || rvy.cues !== protocolCues
133
162
  || exc.cues !== protocolCues || kvy.db !== rvy.db || exc.hby.db !== kvy.db || kvy.rvy !== rvy) {
134
163
  throw new TypeError("Protocol processors require one store and a distinct shared protocol cue flow");
@@ -156,14 +185,15 @@ export class ProtocolProcessor {
156
185
  * Process one frame and publish its committed protocol cues before returning decision data.
157
186
  *
158
187
  * @param frame Complete hydrated frame whose evidence remains valid for this operation.
159
- * @param options Local intake policy, hosted mailbox provenance and whether to advance escrows.
188
+ * @param options Local intake policy, hosted mailbox provenance, whether to advance escrows, and whether the frame is
189
+ * retained history from a finite local import.
160
190
  * @param kvy Input-specific Kevery in this domain; defaults to the shared owner.
161
191
  * @returns Exact dispatch result and current mailbox authorization at its synchronous query decision.
162
192
  * A message refused by validation after extraction is an `invalidMessage` drop, so the caller
163
193
  * continues with the next frame as KERIpy's parser does.
164
194
  * @throws Domain mismatch, infrastructure/handler or cleanup errors propagate; publication still runs in the dispatch finalizer.
165
195
  */
166
- dispatch(frame, { local = this.kvy.local, mailboxAid, escrows = false } = {}, kvy = this.kvy) {
196
+ dispatch(frame, { local = this.kvy.local, mailboxAid, escrows = false, retained = false } = {}, kvy = this.kvy) {
167
197
  // A short-lived input processor must publish into this owner's private cue flow.
168
198
  if (kvy.db !== this.kvy.db || kvy.cues !== this.protocolCues || kvy.rvy !== this.rvy) {
169
199
  throw new TypeError("Kevery belongs to another protocol processing domain");
@@ -172,27 +202,28 @@ export class ProtocolProcessor {
172
202
  const self = this;
173
203
  return call(function* () {
174
204
  try {
175
- const envelope = envelopeFromFrame(frame, local);
205
+ // Children no consumer owns make the frame one invalid message; its own body and attachments remain evidence.
206
+ const nested = hasUnsupportedNests(frame);
207
+ const envelope = envelopeFromFrame(nested ? { ...frame, nests: [] } : frame, local);
176
208
  let result;
177
209
  let invalid = false;
178
210
  try {
179
- result = yield* dispatchEnvelope(envelope, self.rvy, kvy, self.exc, self.tvy ?? undefined, self.vry ?? undefined, mailboxAid, self.kvy, // One authoritative v2 sender policy and KRAM intake across all inputs.
180
- self.registry ?? undefined, self.qvy);
211
+ result = nested ? invalidMessage(envelope, NESTED_REFUSAL) : yield* dispatchEnvelope(envelope, self.rvy, kvy, self.exc, self.tvy ?? undefined, self.vry ?? undefined, mailboxAid, self.kvy, // One authoritative v2 sender policy and KRAM intake across all inputs.
212
+ self.registry ?? undefined, self.qvy, retained);
213
+ invalid = nested;
181
214
  }
182
215
  catch (error) {
183
216
  if (!isInvalidMessageError(error))
184
217
  throw error;
185
218
  invalid = true;
186
- consoleLogger.warn("Skipping invalid message", {
187
- said: envelope.serder.said,
188
- ilk: envelope.serder.ilk,
189
- route: envelope.serder.route,
190
- detail: error.message,
191
- });
192
- result = { envelope, decision: { kind: "drop", reason: "invalidMessage", detail: error.message } };
219
+ result = invalidMessage(envelope, error.message);
193
220
  }
194
221
  const mailboxQueryAuthorized = !invalid && result.envelope.serder.route === "mbx"
195
222
  && kvy.isMailboxQueryAuthorized(queryEnvelopeFromDispatch(result.envelope));
223
+ // Published with this query's own cues; the cue router hands it to this host's querier.
224
+ const refresh = self.relaying && !invalid ? relayRefreshCue(self.exc.hby, result, mailboxAid ?? null) : null;
225
+ if (refresh)
226
+ self.protocolCues.push(refresh);
196
227
  // Capture this request's facts before optional escrows can suspend or change key state.
197
228
  self.publishCues();
198
229
  if (escrows)
@@ -206,14 +237,9 @@ export class ProtocolProcessor {
206
237
  }
207
238
  /** Capture direct replies before handing ordinary protocol records to the application consumer. */
208
239
  publishCues() {
209
- // Readiness observers may synchronously reenter dispatch. Own this committed
210
- // batch before notifying them so nested publication cannot take its records.
211
- const committed = new Deck();
240
+ this.replies.capture(this.protocolCues);
212
241
  while (!this.protocolCues.empty)
213
- committed.push(this.protocolCues.pull());
214
- this.replies.capture(committed);
215
- while (!committed.empty)
216
- this.cues.push(committed.pull());
242
+ this.cues.push(this.protocolCues.pull());
217
243
  }
218
244
  /** Advance configured escrow owners; each scan joins its own I/O and handler cleanup. */
219
245
  *processEscrowsOnce() {
@@ -260,12 +286,21 @@ export class ProtocolProcessor {
260
286
  * KERIpy correspondence:
261
287
  * - mirrors the dedicated `escrowDo` loop rather than folding escrow
262
288
  * processing into parser ingress
289
+ *
290
+ * A pass is a function of the store and of time, so this is a {@link doEveryOnWrite} doer: a pass on tock 0,
291
+ * after any store write, and every `pacing.idleTocks` tocks for what only time releases (timeouts, KRAM
292
+ * windows). Data-driven work waits at most one tock.
263
293
  */
264
- *escrowDo(clock = systemClock) {
265
- while (true) {
266
- yield* this.processEscrowsOnce();
267
- yield* waitOnClock(clock, ESCROW_TOCK_MS);
268
- }
294
+ *escrowDo(clock = systemClock, pacing = PROTOCOL_PACING) {
295
+ // Escrows live in the KEL store and, for TEL and registry work, in the registry stores; a write to any of them
296
+ // runs the next tock's pass.
297
+ const stores = [this.kvy.db, this.tvy?.reger, this.registry?.store.baser].filter((store) => !!store);
298
+ const written = {
299
+ get writeActivity() {
300
+ return stores.reduce((sum, store) => sum + store.writeActivity, 0);
301
+ },
302
+ };
303
+ return yield* doEveryOnWrite(clock, pacing, written, () => this.processEscrowsOnce());
269
304
  }
270
305
  }
271
306
  /**
@@ -67,23 +67,6 @@ export class QueryReplies {
67
67
  get ready() {
68
68
  return slot.reply !== undefined;
69
69
  },
70
- /**
71
- * Install the sole readiness observer for this slot.
72
- *
73
- * @param notify Synchronous wake-up callback; invoked immediately if ready or closed.
74
- * @returns A function removing this observer without closing the request slot.
75
- */
76
- observeReady(notify) {
77
- if (slot.notify)
78
- throw new Error("Reply slot already has a readiness observer");
79
- slot.notify = notify;
80
- if (slot.reply || closed)
81
- notify();
82
- return () => {
83
- if (slot.notify === notify)
84
- slot.notify = undefined;
85
- };
86
- },
87
70
  /**
88
71
  * Inspect evidence without consuming it so callers can finish construction first.
89
72
  *
@@ -112,8 +95,6 @@ export class QueryReplies {
112
95
  group.delete(slot);
113
96
  if (group.size === 0)
114
97
  this.active.delete(said);
115
- slot.notify?.();
116
- slot.notify = undefined;
117
98
  },
118
99
  };
119
100
  }
@@ -142,9 +123,6 @@ export class QueryReplies {
142
123
  && cue.kin === "reply" && cue.route === "/tsn/credential"
143
124
  || slot.reply.kin === "receipt" && cue.kin === "witness"))
144
125
  slot.companion = cue;
145
- else
146
- continue;
147
- slot.notify?.();
148
126
  }
149
127
  }
150
128
  }