keri-ts 2.2.4 → 2.2.6

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 (256) hide show
  1. package/esm/keri/npm/src/_dnt.polyfills.js +14 -14
  2. package/esm/keri/npm/src/acdc/dhking.js +29 -2
  3. package/esm/keri/npm/src/acdc/observing.js +10 -3
  4. package/esm/keri/npm/src/acdc/query-resend.js +74 -0
  5. package/esm/keri/npm/src/acdc/regengine.js +39 -9
  6. package/esm/keri/npm/src/acdc/regeventing.js +5 -3
  7. package/esm/keri/npm/src/acdc/registering.js +19 -0
  8. package/esm/keri/npm/src/acdc/salting.js +4 -0
  9. package/esm/keri/npm/src/acdc/unblinding.js +38 -3
  10. package/esm/keri/npm/src/app/bootstrap/authenticating.js +32 -10
  11. package/esm/keri/npm/src/app/bootstrap/oobiery.js +363 -30
  12. package/esm/keri/npm/src/app/credentials/credential-importing.js +33 -4
  13. package/esm/keri/npm/src/app/credentials/root-replicator.js +27 -15
  14. package/esm/keri/npm/src/app/credentials/schema-resolving.js +5 -1
  15. package/esm/keri/npm/src/app/credentials/tel-publishing.js +16 -7
  16. package/esm/keri/npm/src/app/delivery/forwarding.js +205 -44
  17. package/esm/keri/npm/src/app/delivery/relaying.js +232 -0
  18. package/esm/keri/npm/src/app/delivery/respondant.js +8 -5
  19. package/esm/keri/npm/src/app/exchanges/exchange-messaging.js +1 -1
  20. package/esm/keri/npm/src/app/exchanges/exchanging.js +65 -9
  21. package/esm/keri/npm/src/app/identity/delegating.js +66 -9
  22. package/esm/keri/npm/src/app/identity/habbing.js +190 -71
  23. package/esm/keri/npm/src/app/identity/keeping.js +111 -39
  24. package/esm/keri/npm/src/app/identity/retire-identifier.js +98 -0
  25. package/esm/keri/npm/src/app/ipex/disclosure-replaying.js +116 -0
  26. package/esm/keri/npm/src/app/ipex/group-ipex.js +122 -25
  27. package/esm/keri/npm/src/app/ipex/group-issuance.js +137 -37
  28. package/esm/keri/npm/src/app/ipex/ipex-discloser.js +94 -31
  29. package/esm/keri/npm/src/app/ipex/ipex-grouping.js +53 -3
  30. package/esm/keri/npm/src/app/ipex/ipex-v2.js +7 -2
  31. package/esm/keri/npm/src/app/ipex/issuance.js +147 -44
  32. package/esm/keri/npm/src/app/ipex/pending-presentations.js +4 -2
  33. package/esm/keri/npm/src/app/ipex/presenting.js +3 -18
  34. package/esm/keri/npm/src/app/ipex/receive-issuance.js +2 -1
  35. package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +39 -11
  36. package/esm/keri/npm/src/app/mailbox/agent-inbox.js +292 -0
  37. package/esm/keri/npm/src/app/mailbox/forward-handler.js +298 -52
  38. package/esm/keri/npm/src/app/mailbox/kel-publisher.js +26 -31
  39. package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +70 -62
  40. package/esm/keri/npm/src/app/mailbox/mailboxing.js +20 -4
  41. package/esm/keri/npm/src/app/multisig/counseling.js +71 -27
  42. package/esm/keri/npm/src/app/multisig/group-members.js +41 -5
  43. package/esm/keri/npm/src/app/multisig/grouping.js +50 -2
  44. package/esm/keri/npm/src/app/multisig/multisig-workflows.js +3 -18
  45. package/esm/keri/npm/src/app/multisig/proposing.js +1 -16
  46. package/esm/keri/npm/src/app/multisig/rejoin-group.js +99 -0
  47. package/esm/keri/npm/src/app/multisig/replace-member.js +29 -8
  48. package/esm/keri/npm/src/app/protocol/parsering.js +45 -17
  49. package/esm/keri/npm/src/app/protocol/protocol-processing.js +47 -21
  50. package/esm/keri/npm/src/app/queries/query-replies.js +0 -22
  51. package/esm/keri/npm/src/app/queries/relay-refresh.js +81 -0
  52. package/esm/keri/npm/src/app/queries/witness-inquisitor.js +180 -34
  53. package/esm/keri/npm/src/app/runtime/agent-runtime.js +42 -73
  54. package/esm/keri/npm/src/app/runtime/cue-runtime.js +45 -5
  55. package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +8 -1
  56. package/esm/keri/npm/src/app/runtime/operation-owners.js +26 -56
  57. package/esm/keri/npm/src/app/runtime/workflow-contract.js +7 -4
  58. package/esm/keri/npm/src/app/transport/http-client.js +20 -2
  59. package/esm/keri/npm/src/app/transport/message-source.js +2 -1
  60. package/esm/keri/npm/src/app/transport/messengering.js +39 -8
  61. package/esm/keri/npm/src/app/witness/ingress.js +7 -0
  62. package/esm/keri/npm/src/app/witness/receipt-client.js +43 -16
  63. package/esm/keri/npm/src/app/witness/receipting.js +62 -20
  64. package/esm/keri/npm/src/cli/messaging/multisig.js +4 -3
  65. package/esm/keri/npm/src/core/errors.js +73 -0
  66. package/esm/keri/npm/src/core/kel/delegation-ancestry.js +11 -2
  67. package/esm/keri/npm/src/core/kel/eventing.js +25 -28
  68. package/esm/keri/npm/src/core/kel/kever.js +15 -9
  69. package/esm/keri/npm/src/core/mailbox/kram-policy.js +16 -0
  70. package/esm/keri/npm/src/core/mailbox/kram-pruning.js +25 -6
  71. package/esm/keri/npm/src/core/mailbox/kram-time.js +34 -6
  72. package/esm/keri/npm/src/core/mailbox/kraming.js +58 -2
  73. package/esm/keri/npm/src/core/missing-anchor-query.js +20 -0
  74. package/esm/keri/npm/src/core/routing.js +18 -25
  75. package/esm/keri/npm/src/core/scheming.js +12 -5
  76. package/esm/keri/npm/src/core/wire-fields.js +126 -0
  77. package/esm/keri/npm/src/db/basing.js +4 -13
  78. package/esm/keri/npm/src/db/core/indexeddb-persistence.js +3 -2
  79. package/esm/keri/npm/src/db/core/webdber.js +85 -27
  80. package/esm/keri/npm/src/db/mailbox/mailboxing.js +13 -0
  81. package/esm/keri/npm/src/db/subing.js +4 -0
  82. package/esm/keri/npm/src/runtime/index.js +11 -5
  83. package/esm/keri/npm/src/time/mod.js +5 -0
  84. package/esm/keri/npm/src/vdr/credential-tsn-record.js +2 -7
  85. package/esm/keri/npm/src/vdr/eventing.js +71 -28
  86. package/esm/keri/npm/src/vdr/registry-tsn-record.js +3 -13
  87. package/esm/keri/npm/src/version.js +2 -2
  88. package/package.json +1 -1
  89. package/types/_dnt.polyfills.d.ts +9 -9
  90. package/types/_dnt.polyfills.d.ts.map +1 -1
  91. package/types/acdc/dhking.d.ts +8 -0
  92. package/types/acdc/dhking.d.ts.map +1 -1
  93. package/types/acdc/observing.d.ts +5 -1
  94. package/types/acdc/observing.d.ts.map +1 -1
  95. package/types/acdc/query-resend.d.ts +24 -0
  96. package/types/acdc/query-resend.d.ts.map +1 -0
  97. package/types/acdc/regengine.d.ts +19 -1
  98. package/types/acdc/regengine.d.ts.map +1 -1
  99. package/types/acdc/regeventing.d.ts.map +1 -1
  100. package/types/acdc/registering.d.ts +6 -0
  101. package/types/acdc/registering.d.ts.map +1 -1
  102. package/types/acdc/salting.d.ts.map +1 -1
  103. package/types/acdc/unblinding.d.ts +13 -1
  104. package/types/acdc/unblinding.d.ts.map +1 -1
  105. package/types/app/bootstrap/authenticating.d.ts +12 -2
  106. package/types/app/bootstrap/authenticating.d.ts.map +1 -1
  107. package/types/app/bootstrap/oobiery.d.ts +144 -7
  108. package/types/app/bootstrap/oobiery.d.ts.map +1 -1
  109. package/types/app/credentials/credential-importing.d.ts +8 -0
  110. package/types/app/credentials/credential-importing.d.ts.map +1 -1
  111. package/types/app/credentials/root-replicator.d.ts +19 -8
  112. package/types/app/credentials/root-replicator.d.ts.map +1 -1
  113. package/types/app/credentials/schema-resolving.d.ts.map +1 -1
  114. package/types/app/credentials/tel-publishing.d.ts +4 -2
  115. package/types/app/credentials/tel-publishing.d.ts.map +1 -1
  116. package/types/app/delivery/forwarding.d.ts +90 -5
  117. package/types/app/delivery/forwarding.d.ts.map +1 -1
  118. package/types/app/delivery/relaying.d.ts +144 -0
  119. package/types/app/delivery/relaying.d.ts.map +1 -0
  120. package/types/app/delivery/respondant.d.ts +9 -2
  121. package/types/app/delivery/respondant.d.ts.map +1 -1
  122. package/types/app/exchanges/exchanging.d.ts +36 -0
  123. package/types/app/exchanges/exchanging.d.ts.map +1 -1
  124. package/types/app/identity/delegating.d.ts +6 -0
  125. package/types/app/identity/delegating.d.ts.map +1 -1
  126. package/types/app/identity/habbing.d.ts +106 -19
  127. package/types/app/identity/habbing.d.ts.map +1 -1
  128. package/types/app/identity/keeping.d.ts +54 -19
  129. package/types/app/identity/keeping.d.ts.map +1 -1
  130. package/types/app/identity/retire-identifier.d.ts +24 -0
  131. package/types/app/identity/retire-identifier.d.ts.map +1 -0
  132. package/types/app/ipex/disclosure-replaying.d.ts +61 -0
  133. package/types/app/ipex/disclosure-replaying.d.ts.map +1 -0
  134. package/types/app/ipex/group-ipex.d.ts +60 -14
  135. package/types/app/ipex/group-ipex.d.ts.map +1 -1
  136. package/types/app/ipex/group-issuance.d.ts +54 -8
  137. package/types/app/ipex/group-issuance.d.ts.map +1 -1
  138. package/types/app/ipex/ipex-discloser.d.ts +39 -2
  139. package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
  140. package/types/app/ipex/ipex-grouping.d.ts +23 -2
  141. package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
  142. package/types/app/ipex/ipex-v2.d.ts +5 -2
  143. package/types/app/ipex/ipex-v2.d.ts.map +1 -1
  144. package/types/app/ipex/issuance.d.ts +46 -16
  145. package/types/app/ipex/issuance.d.ts.map +1 -1
  146. package/types/app/ipex/pending-presentations.d.ts +4 -2
  147. package/types/app/ipex/pending-presentations.d.ts.map +1 -1
  148. package/types/app/ipex/presenting.d.ts.map +1 -1
  149. package/types/app/ipex/receive-issuance.d.ts +2 -1
  150. package/types/app/ipex/receive-issuance.d.ts.map +1 -1
  151. package/types/app/ipex/unblinding-workflow.d.ts +13 -2
  152. package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
  153. package/types/app/mailbox/agent-inbox.d.ts +126 -0
  154. package/types/app/mailbox/agent-inbox.d.ts.map +1 -0
  155. package/types/app/mailbox/forward-handler.d.ts +114 -14
  156. package/types/app/mailbox/forward-handler.d.ts.map +1 -1
  157. package/types/app/mailbox/kel-publisher.d.ts +12 -22
  158. package/types/app/mailbox/kel-publisher.d.ts.map +1 -1
  159. package/types/app/mailbox/mailbox-poller.d.ts +40 -24
  160. package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
  161. package/types/app/mailbox/mailboxing.d.ts +19 -5
  162. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  163. package/types/app/multisig/counseling.d.ts +32 -11
  164. package/types/app/multisig/counseling.d.ts.map +1 -1
  165. package/types/app/multisig/group-members.d.ts +17 -1
  166. package/types/app/multisig/group-members.d.ts.map +1 -1
  167. package/types/app/multisig/grouping.d.ts +17 -1
  168. package/types/app/multisig/grouping.d.ts.map +1 -1
  169. package/types/app/multisig/multisig-workflows.d.ts +0 -8
  170. package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
  171. package/types/app/multisig/proposing.d.ts +4 -19
  172. package/types/app/multisig/proposing.d.ts.map +1 -1
  173. package/types/app/multisig/rejoin-group.d.ts +119 -0
  174. package/types/app/multisig/rejoin-group.d.ts.map +1 -0
  175. package/types/app/multisig/replace-member.d.ts +15 -1
  176. package/types/app/multisig/replace-member.d.ts.map +1 -1
  177. package/types/app/protocol/parsering.d.ts +21 -2
  178. package/types/app/protocol/parsering.d.ts.map +1 -1
  179. package/types/app/protocol/protocol-processing.d.ts +25 -5
  180. package/types/app/protocol/protocol-processing.d.ts.map +1 -1
  181. package/types/app/queries/query-replies.d.ts +4 -8
  182. package/types/app/queries/query-replies.d.ts.map +1 -1
  183. package/types/app/queries/relay-refresh.d.ts +26 -0
  184. package/types/app/queries/relay-refresh.d.ts.map +1 -0
  185. package/types/app/queries/witness-inquisitor.d.ts +56 -5
  186. package/types/app/queries/witness-inquisitor.d.ts.map +1 -1
  187. package/types/app/runtime/agent-runtime.d.ts +34 -3
  188. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  189. package/types/app/runtime/cue-runtime.d.ts +15 -3
  190. package/types/app/runtime/cue-runtime.d.ts.map +1 -1
  191. package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -1
  192. package/types/app/runtime/operation-owners.d.ts +16 -26
  193. package/types/app/runtime/operation-owners.d.ts.map +1 -1
  194. package/types/app/runtime/workflow-contract.d.ts +21 -6
  195. package/types/app/runtime/workflow-contract.d.ts.map +1 -1
  196. package/types/app/transport/http-client.d.ts +9 -0
  197. package/types/app/transport/http-client.d.ts.map +1 -1
  198. package/types/app/transport/message-source.d.ts +6 -3
  199. package/types/app/transport/message-source.d.ts.map +1 -1
  200. package/types/app/transport/messengering.d.ts +10 -1
  201. package/types/app/transport/messengering.d.ts.map +1 -1
  202. package/types/app/witness/ingress.d.ts +1 -0
  203. package/types/app/witness/ingress.d.ts.map +1 -1
  204. package/types/app/witness/receipt-client.d.ts +19 -1
  205. package/types/app/witness/receipt-client.d.ts.map +1 -1
  206. package/types/app/witness/receipting.d.ts +24 -1
  207. package/types/app/witness/receipting.d.ts.map +1 -1
  208. package/types/cli/messaging/multisig.d.ts.map +1 -1
  209. package/types/core/cues.d.ts +53 -1
  210. package/types/core/cues.d.ts.map +1 -1
  211. package/types/core/errors.d.ts +32 -0
  212. package/types/core/errors.d.ts.map +1 -1
  213. package/types/core/kel/delegation-ancestry.d.ts.map +1 -1
  214. package/types/core/kel/eventing.d.ts.map +1 -1
  215. package/types/core/kel/kever-decisions.d.ts +3 -1
  216. package/types/core/kel/kever-decisions.d.ts.map +1 -1
  217. package/types/core/kel/kever.d.ts +7 -1
  218. package/types/core/kel/kever.d.ts.map +1 -1
  219. package/types/core/mailbox/kram-policy.d.ts +12 -0
  220. package/types/core/mailbox/kram-policy.d.ts.map +1 -1
  221. package/types/core/mailbox/kram-pruning.d.ts +5 -1
  222. package/types/core/mailbox/kram-pruning.d.ts.map +1 -1
  223. package/types/core/mailbox/kram-time.d.ts +26 -2
  224. package/types/core/mailbox/kram-time.d.ts.map +1 -1
  225. package/types/core/mailbox/kraming.d.ts +14 -1
  226. package/types/core/mailbox/kraming.d.ts.map +1 -1
  227. package/types/core/missing-anchor-query.d.ts +34 -0
  228. package/types/core/missing-anchor-query.d.ts.map +1 -0
  229. package/types/core/routing.d.ts +3 -7
  230. package/types/core/routing.d.ts.map +1 -1
  231. package/types/core/scheming.d.ts.map +1 -1
  232. package/types/core/wire-fields.d.ts +56 -0
  233. package/types/core/wire-fields.d.ts.map +1 -0
  234. package/types/db/basing.d.ts.map +1 -1
  235. package/types/db/core/indexeddb-persistence.d.ts +3 -2
  236. package/types/db/core/indexeddb-persistence.d.ts.map +1 -1
  237. package/types/db/core/webdber.d.ts +32 -6
  238. package/types/db/core/webdber.d.ts.map +1 -1
  239. package/types/db/exchange/exchange-records.d.ts +4 -0
  240. package/types/db/exchange/exchange-records.d.ts.map +1 -1
  241. package/types/db/mailbox/mailboxing.d.ts +10 -1
  242. package/types/db/mailbox/mailboxing.d.ts.map +1 -1
  243. package/types/db/subing.d.ts +2 -0
  244. package/types/db/subing.d.ts.map +1 -1
  245. package/types/runtime/index.d.ts +20 -14
  246. package/types/runtime/index.d.ts.map +1 -1
  247. package/types/time/mod.d.ts +2 -0
  248. package/types/time/mod.d.ts.map +1 -1
  249. package/types/vdr/credential-tsn-record.d.ts.map +1 -1
  250. package/types/vdr/eventing.d.ts +32 -1
  251. package/types/vdr/eventing.d.ts.map +1 -1
  252. package/types/vdr/registry-tsn-record.d.ts.map +1 -1
  253. package/types/version.d.ts +2 -2
  254. package/esm/keri/npm/src/app/runtime/signer-conditions.js +0 -51
  255. package/types/app/runtime/signer-conditions.d.ts +0 -32
  256. package/types/app/runtime/signer-conditions.d.ts.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { Roles } from "../../core/roles.js";
1
2
  import { doEvery, PROTOCOL_PACING } from "./doing.js";
2
3
  /**
3
4
  * Use an explicitly selected or sole local habitat; never guess among hosted identities.
@@ -13,11 +14,28 @@ function resolveCueHab(runtime, hab) {
13
14
  const first = habitats.next();
14
15
  return first.done || !habitats.next().done ? null : first.value;
15
16
  }
17
+ /**
18
+ * Hand an accepted mailbox end role to the KEL publisher and the mailbox poller, so both look at its controller on
19
+ * their next tock rather than at their next sweep. Other roles concern neither.
20
+ *
21
+ * @param runtime Borrowed composed runtime containing the publisher and poller.
22
+ * @param cue The committed end-role fact from `Revery`.
23
+ */
24
+ export function promptForEndRole(runtime, cue) {
25
+ if (cue.role !== Roles.mailbox)
26
+ return;
27
+ runtime.kelPublisher.prompt(cue.cid);
28
+ runtime.mailboxPoller.prompt(cue.cid);
29
+ }
16
30
  /**
17
31
  * Route one initial cue batch to its concrete owners before removing source records.
18
32
  *
19
- * Transfer query instructions to the sender, stream cues to mailbox correlation
20
- * and produced responder messages to Poster. No network delivery runs inline.
33
+ * Transfer query instructions to the sender (a relaying runtime skips one identical to a request already queued),
34
+ * stream cues to mailbox correlation,
35
+ * accepted mailbox end roles to the KEL publisher and poller, and produced responder
36
+ * messages to Poster. No network delivery runs inline.
37
+ * A responder cue whose signing habitat cannot sign yet moves to the tail and is
38
+ * tried again on a later pass; the cues behind it still go now.
21
39
  * Saved key-state facts transfer once to the command's optional query Deck;
22
40
  * its noticer never competes with this worker over the runtime source Deck.
23
41
  * Admission and source removal run synchronously within this finite batch.
@@ -33,14 +51,36 @@ export function* processCuesOnce(runtime, { hab, queryCues } = {}) {
33
51
  const cue = runtime.cues.peekHead();
34
52
  if (!cue)
35
53
  break;
54
+ if (cue.kin === "kramRefused") {
55
+ // Pulled first: a faulty sink must not wedge the head of the Deck.
56
+ runtime.cues.pull();
57
+ runtime.changes?.push({ kind: "kramRefused", id: cue.said, refusal: cue });
58
+ continue;
59
+ }
60
+ if (cue.kin === "endRoleSaved") {
61
+ runtime.cues.pull();
62
+ promptForEndRole(runtime, cue);
63
+ continue;
64
+ }
36
65
  if (cue.kin === "query") {
37
- runtime.querying.enqueueCue(cue, interpreter);
66
+ // A relaying host serves many edges asking about the same subjects; one queued request per query suffices.
67
+ if (runtime.relaying)
68
+ runtime.querying.enqueueCueOnce(cue, interpreter);
69
+ else
70
+ runtime.querying.enqueueCue(cue, interpreter);
38
71
  }
39
72
  else if (cue.kin === "stream") {
40
73
  runtime.mailboxDirector.retainQueryCue(cue);
41
74
  }
42
- else if (interpreter && runtime.respondant.enqueueCue(interpreter, runtime.cues, hab)) {
43
- continue;
75
+ else if (interpreter) {
76
+ const outcome = runtime.respondant.enqueueCue(interpreter, runtime.cues, hab);
77
+ if (outcome === "taken")
78
+ continue;
79
+ if (outcome === "unsignable") {
80
+ // Kept for a later pass, behind the cues that can go now; `count` bounds this pass.
81
+ runtime.cues.append(runtime.cues.pull());
82
+ continue;
83
+ }
44
84
  }
45
85
  runtime.cues.pull();
46
86
  if (cue.kin === "keyStateSaved")
@@ -122,7 +122,14 @@ export class IpexAuxiliaries {
122
122
  const current = this.unblinds.get(hab.pre);
123
123
  if (current?.hab === hab)
124
124
  return current;
125
- const owner = new Unblind({ hab, exc: this.exc, registry: this.registry, rvy: this.rvy, kram: this.kram });
125
+ const owner = new Unblind({
126
+ hab,
127
+ exc: this.exc,
128
+ registry: this.registry,
129
+ rvy: this.rvy,
130
+ now: this.now,
131
+ kram: this.kram,
132
+ });
126
133
  current?.close();
127
134
  this.unblinds.set(hab.pre, owner);
128
135
  return owner;
@@ -14,28 +14,6 @@ export const OWNER_PACING = Object.freeze({
14
14
  tockMs: OWNER_TOCK_MS,
15
15
  sweepTocks: OWNER_SWEEP_TOCKS,
16
16
  });
17
- /**
18
- * Failures of the owner loop itself. A failure of one operation (a delivery that throws, an unreadable stored
19
- * Grant) ends that operation as `failed` and is never recorded here; this records only what escaped an owner
20
- * pass, which the loop survives so that the other owners keep running. `faults` counts them and `last` keeps
21
- * the latest. Nothing clears it: a host reads it and decides.
22
- */
23
- export class OwnerHealth {
24
- constructor() {
25
- Object.defineProperty(this, "faults", {
26
- enumerable: true,
27
- configurable: true,
28
- writable: true,
29
- value: 0
30
- });
31
- Object.defineProperty(this, "last", {
32
- enumerable: true,
33
- configurable: true,
34
- writable: true,
35
- value: null
36
- });
37
- }
38
- }
39
17
  /** Construct the owners over their in-memory tables; the derivation store defaults to the registry database. */
40
18
  export function composeOwners(parts, stores = {}) {
41
19
  const { hby, acdc, exc, protocol, reactor, postman, receiptor, ipex, clock, registryRoot, mux, counselor } = parts;
@@ -82,7 +60,6 @@ export function composeOwners(parts, stores = {}) {
82
60
  retry: parts.retry,
83
61
  changes,
84
62
  }),
85
- health: new OwnerHealth(),
86
63
  unblinding: new UnblindingOperations({
87
64
  hby,
88
65
  exc,
@@ -95,7 +72,9 @@ export function composeOwners(parts, stores = {}) {
95
72
  changes,
96
73
  }),
97
74
  };
98
- const rootReplicator = registryRoot ? new RootReplicator({ root: registryRoot, postman, clock, changes }) : null;
75
+ const rootReplicator = registryRoot
76
+ ? new RootReplicator({ root: registryRoot, postman, clock, changes, changed: parts.groupChanges })
77
+ : null;
99
78
  return { operations, rootReplicator };
100
79
  }
101
80
  /**
@@ -106,47 +85,38 @@ export function composeOwners(parts, stores = {}) {
106
85
  * Idle cost, per tock: four constant-time table checks, one in-memory walk of the Unblind owners for an owed
107
86
  * reply, one drain of each DH owner's cue deck, and the replicator's emptiness checks. The replicator's
108
87
  * rotation check (the establishment SAID of each local group, one keyed read per local identifier) runs on
109
- * tock 0 and every `pacing.sweepTocks`. No owner reads a protocol store. Only the first pass, once, scans the
110
- * pending exchange escrow for resumable Grants.
88
+ * tock 0 and every `pacing.sweepTocks`. No owner reads a protocol store on an ordinary tock. The issuance owner
89
+ * scans the pending exchange escrow on its first pass and then every `ISSUANCE_SWEEP_MS`, for resumable Grants and
90
+ * for group Grants whose custodian's final proof a member follows without signing; nothing else walks it per tock.
111
91
  *
112
- * A failure that escapes an owner pass is recorded in `operations.health` and the loop keeps running, so
113
- * one owner's fault never stops the others. The owners run one after another, so a delivery that does not return holds the others up until it fails or
114
- * its Grant deadline passes; operations already bound each delivery by that deadline.
92
+ * Nothing is caught here. Every remote, network or validation outcome an owner meets is already a typed result
93
+ * below this loop: a failed delivery is retried by its operation, and an operation that cannot go on ends `failed`
94
+ * or `expired` alone. What escapes a pass is a real fault (a store that cannot be read or written, corrupt or
95
+ * missing durable state such as a `MissingDurableStateError`, a `SignerNotHeldError` past a `canSign`
96
+ * gate), and it ends the loop and with it `runAgentRuntime`. Halting the loop is not a fault: Effection halts a
97
+ * pass by returning from it, so no error is thrown. The owners run one after another, so a delivery that does not
98
+ * return holds the others up until it fails or its Grant deadline passes; operations already bound each delivery by
99
+ * that deadline.
115
100
  */
116
101
  export function* operationOwnersDo(owners, pacing = OWNER_PACING) {
117
- const { operations, rootReplicator, ipex, clock } = owners, { health } = operations;
118
- const guarded = function* (owner, pass) {
119
- try {
120
- yield* pass();
121
- }
122
- catch (error) {
123
- health.faults++;
124
- health.last = {
125
- owner,
126
- error: error instanceof Error ? error.message : String(error),
127
- at: new Date(clock.now()).toISOString(),
128
- };
129
- }
130
- };
102
+ const { operations, rootReplicator, ipex, clock } = owners;
131
103
  return yield* doEvery(clock, pacing.tockMs, function* (tock) {
132
104
  if (operations.issuance.active())
133
- yield* guarded("issuance", () => operations.issuance.processOnce());
105
+ yield* operations.issuance.processOnce();
134
106
  if (operations.groupIpex.active())
135
- yield* guarded("groupIpex", () => operations.groupIpex.processOnce());
107
+ yield* operations.groupIpex.processOnce();
136
108
  if (operations.presentation.active())
137
- yield* guarded("presentation", () => operations.presentation.processOnce());
109
+ yield* operations.presentation.processOnce();
138
110
  if (operations.unblinding.active())
139
- yield* guarded("unblinding", () => operations.unblinding.processOnce());
111
+ yield* operations.unblinding.processOnce();
140
112
  if (rootReplicator) {
141
- yield* guarded("replicator", function* () {
142
- for (const dh of ipex.dhkeOwners()) {
143
- while (dh.replicated.length)
144
- rootReplicator.replicate(dh.replicated.pull());
145
- }
146
- if (tock % pacing.sweepTocks === 0)
147
- rootReplicator.sweep();
148
- yield* rootReplicator.processDue();
149
- });
113
+ for (const dh of ipex.dhkeOwners()) {
114
+ while (dh.replicated.length)
115
+ rootReplicator.replicate(dh.replicated.pull());
116
+ }
117
+ if (tock % pacing.sweepTocks === 0)
118
+ rootReplicator.sweep();
119
+ yield* rootReplicator.processDue();
150
120
  }
151
121
  });
152
122
  }
@@ -24,22 +24,25 @@ export class MemoryChangeSink {
24
24
  });
25
25
  }
26
26
  push(change) {
27
- this.changes.push({ kind: change.kind, id: change.id });
27
+ this.changes.push(change.kind === "kramRefused" ? change : { kind: change.kind, id: change.id });
28
28
  }
29
29
  }
30
30
  /**
31
31
  * Back a sink with the host's transient `Signaler`. Publishing needs no polling:
32
32
  * `push` is a synchronous append that collapses repeats of one workflow into a
33
- * single signal (collapse key `kind:id`). Delivery is pull-based, though: the
33
+ * single signal (collapse key `kind:id`). A {@link KramRefusedChange} adds its refusal fields to the signal
34
+ * attributes, so a resent message collapses by SAID. Delivery is pull-based, though: the
34
35
  * Signaler has no subscriber hook, so the consumer must read `signaler.list()`.
35
36
  * A closed or failing Signaler drops the change instead of throwing: a hint must
36
37
  * never break the workflow that published it.
37
38
  */
38
39
  export function signalerChangeSink(signaler) {
39
40
  return {
40
- push: ({ kind, id }) => {
41
+ push: (change) => {
42
+ const { kind, id } = change;
41
43
  try {
42
- signaler.push({ id }, `/workflow/${kind}`, { ckey: `${kind}:${id}` });
44
+ const attrs = change.kind === "kramRefused" ? { id, ...change.refusal } : { id };
45
+ signaler.push(attrs, `/workflow/${kind}`, { ckey: `${kind}:${id}` });
43
46
  }
44
47
  catch {
45
48
  // Dropped on purpose; consumers re-read status and tolerate a missed hint.
@@ -4,6 +4,25 @@ import { ValidationError } from "../../core/errors.js";
4
4
  import { abortable } from "../runtime/abortable.js";
5
5
  import { collectBytes } from "./byte-source.js";
6
6
  import { TransportError } from "./transport-error.js";
7
+ /**
8
+ * Whether {@link FetchHttpClient.open} can request `url` at all: an absolute HTTP(S) URL without embedded
9
+ * credentials. Anything else is refused by construction, before any I/O, so a caller holding a URL from a remote
10
+ * party can refuse it at intake instead of queuing work that can never succeed.
11
+ *
12
+ * @param url Candidate request URL.
13
+ * @returns False for an unparseable URL, a non-HTTP(S) scheme, or a URL carrying a user name or password.
14
+ */
15
+ export function isRequestableHttpUrl(url) {
16
+ let endpoint;
17
+ try {
18
+ endpoint = new URL(url);
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ return (endpoint.protocol === "http:" || endpoint.protocol === "https:") && !endpoint.username
24
+ && !endpoint.password;
25
+ }
7
26
  /**
8
27
  * Native requests and signals that a pending `fetch` still needs. WebKit links an in-flight fetch to
9
28
  * its abort signal only weakly: once the `Request`, or an `AbortSignal.any` composite, is garbage
@@ -78,8 +97,7 @@ export class FetchHttpClient {
78
97
  : new TransportError(`${message} for ${url}`, { cause });
79
98
  try {
80
99
  signal.throwIfAborted();
81
- const endpoint = new URL(url);
82
- if (!["http:", "https:"].includes(endpoint.protocol) || endpoint.username || endpoint.password) {
100
+ if (!isRequestableHttpUrl(url)) {
83
101
  throw new ValidationError("HTTP output requires an HTTP(S) URL without credentials");
84
102
  }
85
103
  // Construction errors are caller errors, before native network classification.
@@ -53,7 +53,8 @@ export function* processMessages(source, protocol, kvy, options = {}) {
53
53
  signal.throwIfAborted();
54
54
  if (result.done)
55
55
  return;
56
- yield* protocol.dispatch(result.value, options, kvy);
56
+ // Only the intake choices: nothing a caller adds to its options can mark live input as retained history.
57
+ yield* protocol.dispatch(result.value, { local: options.local, mailboxAid: options.mailboxAid, escrows: options.escrows }, kvy);
57
58
  }
58
59
  }
59
60
  finally {
@@ -7,25 +7,56 @@
7
7
  * do not select a stored framing preference or acquire workers, stores or queues.
8
8
  */
9
9
  import { ValidationError } from "../../core/errors.js";
10
+ import { readUrl } from "../../core/wire-fields.js";
11
+ import { isRequestableHttpUrl } from "./http-client.js";
10
12
  import { HTTPMessenger } from "./http-messenger.js";
11
13
  import { HTTPStreamMessenger } from "./http-stream-messenger.js";
12
14
  import { requireTcpDialer } from "./tcp-dialer.js";
13
- /** Choose HTTPS, HTTP, then TCP and reject a mismatched advertised scheme before I/O. */
14
- function endpointFrom(urls) {
15
+ /**
16
+ * Choose HTTPS, HTTP, then TCP, refusing before I/O what no messenger can use: an unparseable URL, a scheme other than
17
+ * the advertised one, an HTTP(S) URL the HTTP client refuses ({@link isRequestableHttpUrl}: embedded credentials), or
18
+ * a TCP URL without a valid port. A refused higher-priority URL does not fall through to another transport.
19
+ *
20
+ * @param urls Resolved endpoints, keyed by their advertised scheme.
21
+ * @returns The selection, never a throw.
22
+ */
23
+ function selectEndpoint(urls) {
15
24
  for (const scheme of ["https", "http", "tcp"]) {
16
25
  const value = urls[scheme];
17
26
  if (value === undefined)
18
27
  continue;
19
- const endpoint = new URL(value);
20
- if (endpoint.protocol !== `${scheme}:` || endpoint.username || endpoint.password) {
21
- throw new ValidationError(`Invalid ${scheme} messenger endpoint: ${value}`);
28
+ const endpoint = readUrl(value);
29
+ if (!endpoint)
30
+ return { kind: "refused", reason: `Invalid ${scheme} messenger endpoint: ${value}` };
31
+ const usable = scheme === "tcp" ? !endpoint.username && !endpoint.password : isRequestableHttpUrl(value);
32
+ if (endpoint.protocol !== `${scheme}:` || !usable) {
33
+ return { kind: "refused", reason: `Invalid ${scheme} messenger endpoint: ${value}` };
22
34
  }
23
35
  if (scheme === "tcp" && (!endpoint.port || Number(endpoint.port) < 1 || Number(endpoint.port) > 65535)) {
24
- throw new ValidationError(`TCP messenger endpoint requires a valid port: ${value}`);
36
+ return { kind: "refused", reason: `TCP messenger endpoint requires a valid port: ${value}` };
25
37
  }
26
- return endpoint;
38
+ return { kind: "usable", value, endpoint };
27
39
  }
28
- throw new ValidationError("No supported messenger endpoint is available.");
40
+ return { kind: "refused", reason: "No supported messenger endpoint is available." };
41
+ }
42
+ /** Select the factories' endpoint, refusing an unusable one as a caller error. */
43
+ function endpointFrom(urls) {
44
+ const selection = selectEndpoint(urls);
45
+ if (selection.kind === "refused")
46
+ throw new ValidationError(selection.reason);
47
+ return selection.endpoint;
48
+ }
49
+ /**
50
+ * The URL the messenger factories would use for these endpoints, or null when they would refuse it. A caller holding
51
+ * a location another party advertised reads a refused one as no location, so it fails one delivery as unroutable
52
+ * instead of throwing out of the factory.
53
+ *
54
+ * @param urls Resolved endpoints, keyed by their advertised scheme.
55
+ * @returns The selected URL exactly as advertised, or null.
56
+ */
57
+ export function messengerUrl(urls) {
58
+ const selection = selectEndpoint(urls);
59
+ return selection.kind === "usable" ? selection.value : null;
29
60
  }
30
61
  /**
31
62
  * Bind ordinary signed-message delivery to the first present HTTPS/HTTP/TCP URL.
@@ -2,6 +2,7 @@
2
2
  import { parse } from "cesr-ts";
3
3
  import { call, ensure } from "effection";
4
4
  import { rethrowCleanupFailure } from "../runtime/abortable.js";
5
+ import { promptForEndRole } from "../runtime/cue-runtime.js";
5
6
  import { chunkBytes } from "../transport/byte-source.js";
6
7
  /** Return the active wire-message habitat or `null` when selection is ambiguous. */
7
8
  function cueHab(runtime, serviceHab) {
@@ -15,6 +16,7 @@ function cueHab(runtime, serviceHab) {
15
16
  /**
16
17
  * Route one initial witness cue batch, leaving saved key-state facts for the
17
18
  * application cue worker and its optional command-owned noticer destination.
19
+ * An accepted mailbox end role prompts the KEL publisher and poller as the cue worker does.
18
20
  * Respondant publishes Poster work; this ingress helper never drains Poster
19
21
  * network delivery. Query network I/O stays with the independent sender.
20
22
  *
@@ -39,6 +41,11 @@ export function* drainWitnessCues(runtime, serviceHab, cues, { deliver = true }
39
41
  cues.append(cue);
40
42
  continue;
41
43
  }
44
+ if (cue.kin === "endRoleSaved") {
45
+ cues.pull();
46
+ promptForEndRole(runtime, cue);
47
+ continue;
48
+ }
42
49
  if (deliver) {
43
50
  if (cue.kin === "query") {
44
51
  runtime.querying.enqueueCue(cue, hab);
@@ -8,6 +8,7 @@ import { collectBytes } from "../transport/byte-source.js";
8
8
  import { buildCesrRequest, CESR_CONTENT_TYPE, KERIPY_CESR_JSON_CONTENT_TYPE } from "../transport/cesr-http.js";
9
9
  import { HTTPMessenger } from "../transport/http-messenger.js";
10
10
  import { requireTcpDialer } from "../transport/tcp-dialer.js";
11
+ import { TransportError } from "../transport/transport-error.js";
11
12
  import { inspectReceiptMessage } from "./receipt-messages.js";
12
13
  const WitnessReceiptPollAttempts = 20;
13
14
  const WitnessReceiptPollDelayMs = 100;
@@ -51,6 +52,8 @@ function preferredWitnessTransportUrl(hab, witness) {
51
52
  return witnessHttpUrl(hab, witness) ?? witnessTcpUrl(hab, witness);
52
53
  }
53
54
  /**
55
+ * Deliver one message to a witness, reporting remote failure as data.
56
+ *
54
57
  * Send via HTTP first; an explicit protocol processor processes same-socket TCP replies.
55
58
  * Without a protocol processor TCP reports local transmission only. Actual socket/parser
56
59
  * cleanup finishes before this borrowed protocol processor can be released.
@@ -62,27 +65,51 @@ function preferredWitnessTransportUrl(hab, witness) {
62
65
  * @param clock Borrowed clock with epoch-millisecond now and millisecond timer delays.
63
66
  * @param protocol Optional same-socket reply processor; null selects transmit-only TCP.
64
67
  * @param auth Optional HTTP Authorization header value.
65
- * @throws Missing endpoint, peer refusal, transport/parser or cleanup failures propagate.
68
+ * @returns `unreachable` when the witness advertises no endpoint, refuses over HTTP, or the connection fails
69
+ * (`TransportError`); the caller decides whether to retry later. Parser, storage, programmer and cleanup
70
+ * failures still throw.
66
71
  */
67
- export function* sendWitnessMessage(hab, witness, bytes, http, clock, protocol, auth) {
72
+ export function* deliverWitnessMessage(hab, witness, bytes, http, clock, protocol, auth) {
68
73
  const url = preferredWitnessTransportUrl(hab, witness);
69
74
  if (!url) {
70
- throw new ValidationError(`Unable to find a valid endpoint for witness ${witness}.`);
75
+ return {
76
+ kind: "unreachable",
77
+ witness,
78
+ error: new ValidationError(`Unable to find a valid endpoint for witness ${witness}.`),
79
+ };
71
80
  }
72
- const parsed = new URL(url);
73
- if (parsed.protocol === "tcp:") {
74
- yield* requireTcpDialer(http).messenger(url, clock, protocol).send(bytes, {
75
- replies: Boolean(protocol),
76
- local: true,
77
- });
78
- return;
81
+ try {
82
+ const parsed = new URL(url);
83
+ if (parsed.protocol === "tcp:") {
84
+ yield* requireTcpDialer(http).messenger(url, clock, protocol).send(bytes, {
85
+ replies: Boolean(protocol),
86
+ local: true,
87
+ });
88
+ return { kind: "delivered" };
89
+ }
90
+ const outcome = yield* new HTTPMessenger(url, http, {
91
+ destination: witness,
92
+ headers: auth ? { Authorization: auth } : undefined,
93
+ }).send(bytes);
94
+ if (outcome.kind === "refuse")
95
+ return { kind: "unreachable", witness, error: outcome.error };
96
+ return { kind: "delivered" };
97
+ }
98
+ catch (error) {
99
+ if (!(error instanceof TransportError))
100
+ throw error;
101
+ return { kind: "unreachable", witness, error };
79
102
  }
80
- const outcome = yield* new HTTPMessenger(url, http, {
81
- destination: witness,
82
- headers: auth ? { Authorization: auth } : undefined,
83
- }).send(bytes);
84
- if (outcome.kind === "refuse")
85
- throw outcome.error;
103
+ }
104
+ /**
105
+ * Send one message to a witness for callers that require delivery.
106
+ *
107
+ * @throws The delivery's remote failure (missing endpoint, peer refusal, transport) and any local failure.
108
+ */
109
+ export function* sendWitnessMessage(hab, witness, bytes, http, clock, protocol, auth) {
110
+ const delivery = yield* deliverWitnessMessage(hab, witness, bytes, http, clock, protocol, auth);
111
+ if (delivery.kind === "unreachable")
112
+ throw delivery.error;
86
113
  }
87
114
  function isKeripyContentTypeRejection(status, contentType, responseText) {
88
115
  return status === 406
@@ -8,8 +8,12 @@ import { dgKey } from "../../db/core/keys.js";
8
8
  import { acceptedEventReplayMessage } from "../identity/habbing.js";
9
9
  import { chunkBytes } from "../transport/byte-source.js";
10
10
  import { processMessages } from "../transport/message-source.js";
11
- import { getWitnessReceiptEndpoint, pollWitnessReceipt, postWitnessReceiptEndpoint, sendWitnessMessage, witnessHttpUrl, witnessTcpUrl, } from "./receipt-client.js";
11
+ import { TransportError } from "../transport/transport-error.js";
12
+ import { deliverWitnessMessage, getWitnessReceiptEndpoint, pollWitnessReceipt, postWitnessReceiptEndpoint, witnessHttpUrl, witnessTcpUrl, } from "./receipt-client.js";
12
13
  import { buildDetachedReceiptMessage } from "./receipt-messages.js";
14
+ function incomplete(witness, error) {
15
+ return { kind: "incomplete", witness, error };
16
+ }
13
17
  /** Build one `loc/scheme` replay bundle for witness introductions. */
14
18
  function* witnessSchemeReplies(hab, eids, gvrsn) {
15
19
  const replies = eids
@@ -94,7 +98,17 @@ export class Receiptor {
94
98
  this.gvrsn = gvrsn;
95
99
  }
96
100
  /** Submit the latest or specified event to all witnesses and gather receipts. */
97
- *receipt(pre, { sn, auths = {}, allowTcp = false } = {}) {
101
+ *receipt(pre, options = {}) {
102
+ const outcome = yield* this.attempt(pre, options);
103
+ if (outcome.kind === "incomplete")
104
+ throw outcome.error;
105
+ return outcome.result;
106
+ }
107
+ /**
108
+ * Submit like `receipt`, but report a witness that is unreachable, refuses, or does not receipt as
109
+ * `incomplete` data. Local faults (unknown local AID, missing key state, storage) still throw.
110
+ */
111
+ *attempt(pre, { sn, auths = {}, allowTcp = false } = {}) {
98
112
  return yield* call(() => this.receiptOnce(pre, { sn, auths }, allowTcp));
99
113
  }
100
114
  /**
@@ -118,11 +132,13 @@ export class Receiptor {
118
132
  const { serder, message } = acceptedEventReplayMessage(this.hby, pre, eventSn, this.gvrsn);
119
133
  const witnesses = this.hby.db.acceptedEventWitnesses(serder);
120
134
  if (witnesses.length === 0) {
121
- return { witnesses: [], statuses: {} };
135
+ return { kind: "receipted", result: { witnesses: [], statuses: {} } };
122
136
  }
123
137
  if (serder.ilk === Ilks.rot || serder.ilk === Ilks.drt) {
124
138
  for (const witness of serder.adds) {
125
- yield* this.catchup(pre, witness);
139
+ const caughtUp = yield* this.replayHistory(hab, witness);
140
+ if (caughtUp.kind === "unreachable")
141
+ return incomplete(witness, caughtUp.error);
126
142
  }
127
143
  }
128
144
  const receiptGroups = new Map();
@@ -134,23 +150,35 @@ export class Receiptor {
134
150
  for (const witness of witnesses) {
135
151
  if (kever.delegated) {
136
152
  for (const dmsg of this.hby.db.cloneDelegation(kever, this.gvrsn)) {
137
- yield* sendWitnessMessage(hab, witness, this.selected(dmsg), this.http, this.clock, this.protocol);
153
+ const delivery = yield* deliverWitnessMessage(hab, witness, this.selected(dmsg), this.http, this.clock, this.protocol);
154
+ if (delivery.kind === "unreachable")
155
+ return incomplete(witness, delivery.error);
138
156
  }
139
157
  }
140
158
  if (allowTcp && !witnessHttpUrl(hab, witness) && witnessTcpUrl(hab, witness)) {
141
- yield* sendWitnessMessage(hab, witness, this.selected(message), this.http, this.clock, this.protocol);
159
+ const delivery = yield* deliverWitnessMessage(hab, witness, this.selected(message), this.http, this.clock, this.protocol);
160
+ if (delivery.kind === "unreachable")
161
+ return incomplete(witness, delivery.error);
142
162
  statuses[witness] = 200;
143
163
  }
144
164
  else {
145
165
  // A single receipt attempt owns its response until parsing and collection finish.
146
- let response = yield* call(() => postWitnessReceiptEndpoint(hab, witness, message, this.http, auths[witness], this.gvrsn));
147
- if (response.kind === "escrow") {
148
- response = yield* pollWitnessReceipt(hab, witness, pre, eventSn, said, this.http, this.gvrsn);
166
+ let response;
167
+ try {
168
+ response = yield* call(() => postWitnessReceiptEndpoint(hab, witness, message, this.http, auths[witness], this.gvrsn));
169
+ if (response.kind === "escrow") {
170
+ response = yield* pollWitnessReceipt(hab, witness, pre, eventSn, said, this.http, this.gvrsn);
171
+ }
172
+ }
173
+ catch (error) {
174
+ if (!(error instanceof TransportError))
175
+ throw error;
176
+ return incomplete(witness, error);
149
177
  }
150
178
  statuses[witness] = response.status;
151
179
  if (response.kind !== "accepted") {
152
180
  const detail = response.kind === "reject" ? response.message : "receipt remained escrowed";
153
- throw new ValidationError(`Witness ${witness} failed to receipt ${pre}:${eventSn.toString(16)}: ${detail}`);
181
+ return incomplete(witness, new ValidationError(`Witness ${witness} failed to receipt ${pre}:${eventSn.toString(16)}: ${detail}`));
154
182
  }
155
183
  yield* processMessages((signal) => chunkBytes(response.body, { signal }), this.protocol, this.kvy, {
156
184
  local: false,
@@ -161,7 +189,7 @@ export class Receiptor {
161
189
  const index = witnesses.indexOf(witness);
162
190
  const wigers = this.hby.db.wigs.get(dgKey(pre, said)).filter((siger) => siger.index === index);
163
191
  if (wigers.length === 0) {
164
- throw new ValidationError(`Witness ${witness} did not receipt ${pre}:${eventSn.toString(16)}.`);
192
+ return incomplete(witness, new ValidationError(`Witness ${witness} did not receipt ${pre}:${eventSn.toString(16)}.`));
165
193
  }
166
194
  receiptGroups.set(witness, wigers);
167
195
  }
@@ -190,9 +218,11 @@ export class Receiptor {
190
218
  pipelined: true,
191
219
  genusify: true,
192
220
  }));
193
- yield* sendWitnessMessage(hab, witness, concatBytes(...parts), this.http, this.clock, this.protocol);
221
+ const delivery = yield* deliverWitnessMessage(hab, witness, concatBytes(...parts), this.http, this.clock, this.protocol);
222
+ if (delivery.kind === "unreachable")
223
+ return incomplete(witness, delivery.error);
194
224
  }
195
- return { witnesses, statuses };
225
+ return { kind: "receipted", result: { witnesses, statuses } };
196
226
  }
197
227
  /**
198
228
  * Query a stored witness receipt and apply it to local state when found.
@@ -243,15 +273,27 @@ export class Receiptor {
243
273
  if (!hab) {
244
274
  throw new ValidationError(`${pre} not a valid local AID.`);
245
275
  }
276
+ const delivery = yield* this.replayHistory(hab, witness);
277
+ if (delivery.kind === "unreachable")
278
+ throw delivery.error;
279
+ }
280
+ /** Replay a delegated inception's delegation chain and the KEL to one witness, stopping at the first failure. */
281
+ *replayHistory(hab, witness) {
246
282
  const kever = hab.kever;
247
- if (kever?.delegated) {
248
- for (const msg of this.hby.db.cloneDelegation(kever, this.gvrsn)) {
249
- yield* sendWitnessMessage(hab, witness, this.selected(msg), this.http, this.clock, this.protocol);
250
- }
251
- }
252
- for (const msg of this.hby.db.clonePreIter(pre, 0, this.gvrsn)) {
253
- yield* sendWitnessMessage(hab, witness, this.selected(msg), this.http, this.clock, this.protocol);
283
+ const db = this.hby.db;
284
+ const gvrsn = this.gvrsn;
285
+ const pre = hab.pre;
286
+ const messages = (function* () {
287
+ if (kever?.delegated)
288
+ yield* db.cloneDelegation(kever, gvrsn);
289
+ yield* db.clonePreIter(pre, 0, gvrsn);
290
+ })();
291
+ for (const msg of messages) {
292
+ const delivery = yield* deliverWitnessMessage(hab, witness, this.selected(msg), this.http, this.clock, this.protocol);
293
+ if (delivery.kind === "unreachable")
294
+ return delivery;
254
295
  }
296
+ return { kind: "delivered" };
255
297
  }
256
298
  /** Complete streams select their framing before the body; detached receipt headers retain their existing enclosure. */
257
299
  selected(message) {
@@ -16,7 +16,7 @@ import { telEventStatus, waitForIssuedCredential, waitForTelEvent, } from "../..
16
16
  import { endpointRoleAccepted, isLocalGroupHab, localGroupMember, proposeGroupEndpointRole, } from "../../app/identity/endpoint-roleing.js";
17
17
  import { MULTISIG_REV_ROUTE, MULTISIG_ROT_ROUTE, MULTISIG_VCP_ROUTE } from "../../app/multisig/grouping.js";
18
18
  import { routeForKelEvent } from "../../app/multisig/multisig-workflows.js";
19
- import { proposeGroupInception, proposeGroupInteraction, proposeGroupRotation, settleProposedRotation, } from "../../app/multisig/proposing.js";
19
+ import { proposeGroupInception, proposeGroupInteraction, proposeGroupRotation, } from "../../app/multisig/proposing.js";
20
20
  import { observeUntil } from "../../app/runtime/workflow-contract.js";
21
21
  import { ValidationError } from "../../core/errors.js";
22
22
  import { isV2RegistryRoute } from "../../core/exchange/protocol-exchanging.js";
@@ -272,8 +272,9 @@ export function* multisigRotateCommand(args) {
272
272
  let delegationPhase = null;
273
273
  let rootCopies = [];
274
274
  if (accepted) {
275
- // The proposer's own acceptance must commit the rotated membership as a joiner's does.
276
- settleProposedRotation({ hby }, sent);
275
+ // Acceptance was read straight from the KEL, possibly before the Counselor's pass: follow it now, since the
276
+ // root copies below go to the rotated members.
277
+ hby.alignGroupMembers(sent.group);
277
278
  delegationPhase = yield* finishAcceptedEvent(hby, runtime, sent.event, commandArgs);
278
279
  rootCopies = yield* resealAfterRotation(hby, runtime, sent.group, sent.event.said, since);
279
280
  }