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
@@ -7,23 +7,36 @@
7
7
  * is an explicitly selected remote attester. Resolve/sign just before sending,
8
8
  * using accepted endpoint evidence. One worker owns the unsigned request Deck
9
9
  * and current network attempt; there is no retained signed retry backlog.
10
+ *
11
+ * With a relay (an edge runtime) every query is addressed and sent to the relay,
12
+ * which is the attester: it answers from its own store through its Respondant
13
+ * and Poster into the edge's mailbox, which it hosts. No query goes to a witness
14
+ * or controller, whatever `src` or `wits` the request names.
10
15
  */
11
16
  import { concatBytes } from "cesr-ts";
12
17
  import { call, resource } from "effection";
13
18
  import { Deck } from "../../core/deck.js";
14
- import { ValidationError } from "../../core/errors.js";
15
19
  import { consoleLogger } from "../../core/logger.js";
16
20
  import { Roles } from "../../core/roles.js";
17
21
  import { Schemes } from "../../core/schemes.js";
18
22
  import { introduce } from "../delivery/introducing.js";
23
+ import { relayEndpoint, relayRefusal } from "../delivery/relaying.js";
19
24
  import { PROTOCOL_TOCK_MS } from "../runtime/doing.js";
20
25
  import { waitOnClock } from "../runtime/workflow-contract.js";
21
26
  import { CesrHttpRefusal } from "../transport/cesr-egress.js";
27
+ import { isRequestableHttpUrl } from "../transport/http-client.js";
22
28
  import { HTTPMessenger } from "../transport/http-messenger.js";
29
+ import { messengerUrl } from "../transport/messengering.js";
23
30
  import { requireTcpDialer } from "../transport/tcp-dialer.js";
24
31
  import { TransportError } from "../transport/transport-error.js";
25
32
  /** First eligible role used by KERIpy WitnessInquisitor before random endpoint selection. */
26
33
  const QUERY_ROLE_PRIORITY = [Roles.controller, Roles.agent, Roles.witness];
34
+ /**
35
+ * Delivery passes an ordinary request may rotate with a ready signer but no reachable attester before it is dropped:
36
+ * about five minutes at the protocol tock. Any remote party can cause such requests (every unverified reply signed by
37
+ * an unknown prefix yields a `query` cue), so without a bound they would rotate in the queue forever.
38
+ */
39
+ export const UNADDRESSED_QUERY_PASS_LIMIT = 9_600;
27
40
  /**
28
41
  * Copy only the owned query-body level without erasing its route/payload relationship.
29
42
  *
@@ -41,6 +54,22 @@ function copyQuery(instruction) {
41
54
  return { route: instruction.route, q: { ...instruction.q } };
42
55
  }
43
56
  }
57
+ /**
58
+ * Identity of one unsigned request for duplicate detection: every field that changes what is signed or where it goes.
59
+ *
60
+ * @param request Queued or candidate request; query filters are strings and string-valued seal SADs.
61
+ * @returns A canonical string equal for two requests exactly when they would produce the same query.
62
+ */
63
+ function requestKey(request) {
64
+ return JSON.stringify([
65
+ request.route,
66
+ request.pre,
67
+ request.signer ?? null,
68
+ request.src ?? null,
69
+ request.wits ?? null,
70
+ request.q,
71
+ ]);
72
+ }
44
73
  /**
45
74
  * Return the sole local habitat when exactly one exists.
46
75
  *
@@ -56,12 +85,47 @@ function soleHab(hby) {
56
85
  const first = habitats.next();
57
86
  return first.done || !habitats.next().done ? null : first.value;
58
87
  }
88
+ /**
89
+ * The attesters an ordinary (non-relay) query for `pre` may go to: the AIDs of the first role among controller,
90
+ * agent and witness that has any accepted endpoint for the subject. Mailbox providers are not query attesters.
91
+ *
92
+ * @param hab Local habitat whose accepted-state store is read.
93
+ * @param pre Subject AID whose accepted endpoint roles are consulted.
94
+ * @returns Eligible attester AIDs, or an empty array for an unknown subject or a subject with no such endpoint.
95
+ */
96
+ function queryAttesters(hab, pre) {
97
+ if (!hab.db.getKever(pre))
98
+ return [];
99
+ const ends = hab.endsFor(pre);
100
+ for (const role of QUERY_ROLE_PRIORITY) {
101
+ const eids = Object.keys(ends[role] ?? {});
102
+ if (eids.length > 0)
103
+ return eids;
104
+ }
105
+ return [];
106
+ }
107
+ /**
108
+ * The location a query can be sent to, preferring HTTPS, then HTTP, then TCP. An accepted location comes from a remote
109
+ * `/loc/scheme` reply, so one the HTTP client refuses by construction (embedded credentials) is no location: sending
110
+ * to it would throw a refusal that is not a send outcome.
111
+ *
112
+ * @param urls Accepted locations of one endpoint, by scheme.
113
+ * @returns The preferred usable location, or null when none is usable.
114
+ */
115
+ function preferredLocation(urls) {
116
+ for (const url of [urls[Schemes.https], urls[Schemes.http]])
117
+ if (url && isRequestableHttpUrl(url))
118
+ return url;
119
+ // A TCP location without a valid port is refused by the TCP messenger; it is no location here.
120
+ const tcp = urls[Schemes.tcp];
121
+ return tcp && messengerUrl({ tcp }) ? tcp : null;
122
+ }
59
123
  /**
60
124
  * Resolve the actual transport URL for one query destination AID.
61
125
  *
62
126
  * An explicit witness uses its own location records. Ordinary queries use only
63
127
  * the selected controller, agent or witness endpoint; mailbox providers are not
64
- * query attesters. Missing/unsupported locations do not promote another endpoint.
128
+ * query attesters. Missing, unsupported or unusable locations do not promote another endpoint.
65
129
  *
66
130
  * @param hab Local habitat used to construct/sign this output; stores remain borrowed.
67
131
  * @param queriedPre Subject AID whose state is requested.
@@ -70,18 +134,27 @@ function soleHab(hby) {
70
134
  * @returns Preferred endpoint URL, or null if no permitted route exists.
71
135
  */
72
136
  function resolveQueryDestinationUrl(hab, queriedPre, destination, { explicitWitness = false, } = {}) {
73
- if (explicitWitness) {
74
- const urls = hab.fetchUrls(destination);
75
- return urls[Schemes.https] ?? urls[Schemes.http] ?? urls[Schemes.tcp] ?? null;
76
- }
137
+ if (explicitWitness)
138
+ return preferredLocation(hab.fetchUrls(destination));
77
139
  const ends = hab.endsFor(queriedPre);
78
140
  for (const role of [Roles.controller, Roles.agent, Roles.witness]) {
79
141
  const urls = ends[role]?.[destination];
80
142
  if (urls)
81
- return urls[Schemes.https] ?? urls[Schemes.http] ?? urls[Schemes.tcp] ?? null;
143
+ return preferredLocation(urls);
82
144
  }
83
145
  return null;
84
146
  }
147
+ /**
148
+ * The attesters of an ordinary query for `pre` that can be sent to now: those {@link queryAttesters} selects that also
149
+ * have an accepted location.
150
+ *
151
+ * @param hab Local habitat whose accepted-state store is read.
152
+ * @param pre Subject AID whose accepted endpoint roles and locations are consulted.
153
+ * @returns Addressable attester AIDs of the first eligible role; empty when none can be reached now.
154
+ */
155
+ export function addressableAttesters(hab, pre) {
156
+ return queryAttesters(hab, pre).filter((aid) => resolveQueryDestinationUrl(hab, pre, aid) !== null);
157
+ }
85
158
  /**
86
159
  * KERIpy-shaped query sender, independent from semantic query completion.
87
160
  *
@@ -109,9 +182,10 @@ export class WitnessInquisitor {
109
182
  * @param protocol Borrowed same-database processor for incoming query replies.
110
183
  * @param http Borrowed HTTP implementation selected by composition.
111
184
  * @param clock Borrowed clock for transport deadlines; times are milliseconds.
185
+ * @param relay The agent every query goes to; null selects attesters from accepted endpoint roles.
112
186
  * @param hab Optional default local signer; null requires an unambiguous sole habitat.
113
187
  */
114
- constructor(hby, protocol, http, clock, hab = null) {
188
+ constructor(hby, protocol, http, clock, relay, hab = null) {
115
189
  /** Borrowed local signers and accepted-state stores. */
116
190
  Object.defineProperty(this, "hby", {
117
191
  enumerable: true,
@@ -182,12 +256,20 @@ export class WitnessInquisitor {
182
256
  writable: true,
183
257
  value: void 0
184
258
  });
259
+ /** The agent every query is addressed and sent to (an edge); null selects ordinary attesters. */
260
+ Object.defineProperty(this, "relay", {
261
+ enumerable: true,
262
+ configurable: true,
263
+ writable: true,
264
+ value: void 0
265
+ });
185
266
  if (protocol.kvy.db !== hby.db)
186
267
  throw new RangeError("Query protocol must use the same database");
187
268
  this.hby = hby;
188
269
  this.protocol = protocol;
189
270
  this.http = http;
190
271
  this.clock = clock;
272
+ this.relay = relay;
191
273
  this.hab = hab;
192
274
  }
193
275
  /**
@@ -198,6 +280,17 @@ export class WitnessInquisitor {
198
280
  configure({ hab = null } = {}) {
199
281
  this.hab = hab;
200
282
  }
283
+ /** The explicitly configured default signer, or null when the sole local habitat is used. */
284
+ get defaultSigner() {
285
+ return this.hab?.pre ?? null;
286
+ }
287
+ /** Whether a queued request names `signer` as its explicit signer. */
288
+ hasRequestsSignedBy(signer) {
289
+ for (const request of this.requests)
290
+ if (request.signer === signer)
291
+ return true;
292
+ return false;
293
+ }
201
294
  /** Deferred instructions and active sends are this sender's only pending work. */
202
295
  hasPendingWork() {
203
296
  return !this.requests.empty || this.hasPendingDelivery;
@@ -215,6 +308,7 @@ export class WitnessInquisitor {
215
308
  ...request,
216
309
  ...copyQuery(request),
217
310
  wits: request.wits ? [...request.wits] : undefined,
311
+ unaddressedPasses: 0,
218
312
  });
219
313
  }
220
314
  /**
@@ -232,6 +326,27 @@ export class WitnessInquisitor {
232
326
  const selected = { ...request, signer: request.signer ?? hab?.pre };
233
327
  this.enqueue(selected);
234
328
  }
329
+ /**
330
+ * Admit a routed query cue unless an identical request is already queued: same subject, route, filters, signer,
331
+ * attester and witness overrides, with the signer resolved as {@link enqueueCue} resolves it. A relaying host's cue
332
+ * router uses this, so the queries of the many edges it serves about one subject leave one request queued; a request
333
+ * already taken for delivery is no longer queued, so a later cue queues the next one.
334
+ *
335
+ * @param cue Typed query cue; missing route/subject produces no request.
336
+ * @param hab Optional local signer hint when the cue has no explicit signer.
337
+ */
338
+ enqueueCueOnce(cue, hab) {
339
+ if (this.closed)
340
+ throw new Error("Query sender is closed");
341
+ if (!cue.route || !cue.pre)
342
+ return;
343
+ const { kin: _, ...request } = cue;
344
+ const key = requestKey({ ...request, signer: request.signer ?? hab?.pre });
345
+ for (const queued of this.requests)
346
+ if (requestKey(queued) === key)
347
+ return;
348
+ this.enqueueCue(cue, hab);
349
+ }
235
350
  /** Include actual query I/O in convergence; pending instructions have their own queue. */
236
351
  get hasPendingDelivery() {
237
352
  return this.delivering;
@@ -282,7 +397,16 @@ export class WitnessInquisitor {
282
397
  const packet = self.prepareRequest(request);
283
398
  self.requests.pull();
284
399
  if (!packet) {
285
- self.requests.append(request);
400
+ // A signer that cannot sign yet, or a relay not yet usable, is local state that waits; a ready signer with no
401
+ // reachable attester counts toward the bound.
402
+ if (!self.relay && self.configuredHab(request.signer)?.canSign())
403
+ request.unaddressedPasses++;
404
+ if (request.unaddressedPasses >= UNADDRESSED_QUERY_PASS_LIMIT) {
405
+ consoleLogger.warn(`Dropping ${request.route} query for ${request.pre}: no reachable attester after ${request.unaddressedPasses} passes.`);
406
+ }
407
+ else {
408
+ self.requests.append(request);
409
+ }
286
410
  continue;
287
411
  }
288
412
  active.signal.throwIfAborted();
@@ -369,51 +493,73 @@ export class WitnessInquisitor {
369
493
  * Resolve/sign one selected query and introduction immediately before its attempt.
370
494
  *
371
495
  * @param request Deferred unsigned request; nested query values must remain immutable.
372
- * @returns Prepared packet, or null when signer/attester evidence is unresolved.
373
- * @throws A selected attester with no endpoint URL throws; source removal remains with the caller.
496
+ * The attester is chosen at random among the eligible ones (the explicit `src`, the `wits` overrides, or the first
497
+ * eligible endpoint role) that have an accepted location. An attester without one is never promoted to another
498
+ * role; when none of the eligible attesters can be reached the request is unresolved, like a signer that cannot sign
499
+ * yet, so no endpoint condition ends the delivery loop or fails a caller's pass.
500
+ *
501
+ * @param request Deferred unsigned request; nested query values must remain immutable.
502
+ * @returns Prepared packet, or null when signer/attester evidence is unresolved, no eligible attester has an
503
+ * accepted location, or the signer cannot sign yet.
374
504
  */
375
505
  prepareRequest(request) {
376
506
  const hab = this.configuredHab(request.signer);
377
- if (!hab)
507
+ // A signer that cannot sign yet leaves the request unresolved: it goes to the tail, and the next pass tries again.
508
+ if (!hab || !hab.canSign())
378
509
  return null;
379
- const attesters = request.src ? [] : this.attestersFor(request.pre, { signer: hab.pre, wits: request.wits });
380
- const destination = request.src ?? attesters[Math.floor(Math.random() * attesters.length)];
381
- if (!destination)
382
- return null;
383
- const url = resolveQueryDestinationUrl(hab, request.pre, destination, {
384
- explicitWitness: request.wits?.includes(destination) ?? false,
510
+ if (this.relay)
511
+ return this.prepareRelayRequest(hab, request, this.relay);
512
+ const eligible = request.src
513
+ ? [request.src]
514
+ : this.attestersFor(request.pre, { signer: hab.pre, wits: request.wits });
515
+ const reachable = eligible.flatMap((destination) => {
516
+ const url = resolveQueryDestinationUrl(hab, request.pre, destination, {
517
+ explicitWitness: request.wits?.includes(destination) ?? false,
518
+ });
519
+ // A TCP location on a host without a TCP dialer cannot be sent to: it is unreachable, not a fault.
520
+ return url && (this.http.tcp || !url.startsWith("tcp:")) ? [{ destination, url }] : [];
385
521
  });
386
- if (!url)
387
- throw new ValidationError(`No endpoint URL found for query destination ${destination}.`);
522
+ const selected = reachable[Math.floor(Math.random() * reachable.length)];
523
+ if (!selected)
524
+ return null;
525
+ const { destination, url } = selected;
388
526
  const message = hab.query(request.pre, destination, copyQuery(request));
389
527
  const intro = introduce(hab, destination);
390
528
  return { url, body: concatBytes(intro, message), destination };
391
529
  }
392
530
  /**
393
- * Return the first eligible role’s attester IDs; preparation chooses one randomly.
531
+ * Address one query to the relay and sign it for the relay's location. The relay must have an accepted location
532
+ * and the signer must have authorized it as mailbox (where the answer is stored); until then the request stays
533
+ * unresolved and is tried again on a later pass, as for a signer that cannot sign yet.
534
+ *
535
+ * @param hab Local signing habitat.
536
+ * @param request Deferred unsigned request; its `src` and `wits` are not consulted.
537
+ * @param relay The configured relay agent, which is the attester.
538
+ * @returns Prepared packet for the relay, or null while {@link relayRefusal} refuses the relay for this signer.
539
+ */
540
+ prepareRelayRequest(hab, request, relay) {
541
+ if (relayRefusal(this.hby, relay, hab.pre))
542
+ return null;
543
+ const endpoint = relayEndpoint(this.hby, relay);
544
+ const message = hab.query(request.pre, relay.aid, copyQuery(request));
545
+ return { url: endpoint.url, body: concatBytes(introduce(hab, relay.aid), message), destination: relay.aid };
546
+ }
547
+ /**
548
+ * Return the first eligible role’s attester IDs; preparation chooses one randomly. With a relay, the relay is the
549
+ * only attester.
394
550
  *
395
551
  * @param pre Subject AID whose accepted endpoint roles are consulted.
396
552
  * @param options Optional local signer AID and remote witness/attester overrides; neither selects a transport implementation.
397
553
  * @returns Eligible attester AIDs, or an empty array for an unknown subject or unresolved signer/role.
398
554
  */
399
555
  attestersFor(pre, { signer, wits } = {}) {
556
+ if (this.relay)
557
+ return [this.relay.aid];
400
558
  if (wits && wits.length > 0) {
401
559
  // Explicit watcher/witness overrides outrank endpoint-role lookup.
402
560
  return [...wits];
403
561
  }
404
562
  const hab = this.configuredHab(signer);
405
- if (!hab || !this.hby.db.getKever(pre))
406
- return [];
407
- const ends = hab.endsFor(pre);
408
- for (const role of QUERY_ROLE_PRIORITY) {
409
- const roleEnds = ends[role];
410
- if (!roleEnds) {
411
- continue;
412
- }
413
- const eids = Object.keys(roleEnds);
414
- if (eids.length > 0)
415
- return eids;
416
- }
417
- return [];
563
+ return hab ? queryAttesters(hab, pre) : [];
418
564
  }
419
565
  }
@@ -8,7 +8,6 @@ import { DatabaseNotOpenError } from "../../core/errors.js";
8
8
  import { Kevery } from "../../core/kel/eventing.js";
9
9
  import { Kramer } from "../../core/mailbox/kraming.js";
10
10
  import { CREDENTIAL_MAILBOX_TOPIC, DELEGATE_MAILBOX_TOPIC, RECEIPT_MAILBOX_TOPIC, REPLAY_MAILBOX_TOPIC, REPLY_MAILBOX_TOPIC, } from "../../core/mailbox/mailbox-topics.js";
11
- import { Roles } from "../../core/roles.js";
12
11
  import { Revery } from "../../core/routing.js";
13
12
  import { openOwner } from "../../db/core/store-owner.js";
14
13
  import { Mailboxer } from "../../db/mailbox/mailboxing.js";
@@ -20,12 +19,12 @@ import { Authenticator } from "../bootstrap/authenticating.js";
20
19
  import { loadOobiHandlers, Oobiery, oobiTerminalState } from "../bootstrap/oobiery.js";
21
20
  import { publishPendingTel } from "../credentials/tel-publishing.js";
22
21
  import { Poster } from "../delivery/forwarding.js";
22
+ import { relayEndpoint, relayOnlyHttpClient } from "../delivery/relaying.js";
23
23
  import { ReplyStreams } from "../delivery/reply-streams.js";
24
24
  import { Respondant } from "../delivery/respondant.js";
25
25
  import { Exchanger, RouteRefuser } from "../exchanges/exchanging.js";
26
26
  import { loadChallengeHandlers } from "../identity/challenging.js";
27
27
  import { Anchorer, loadDelegationHandlers } from "../identity/delegating.js";
28
- import { SignerNotHeldError } from "../identity/keeping.js";
29
28
  import { loadIpexHandlers } from "../ipex/ipexing.js";
30
29
  import { ForwardHandler } from "../mailbox/forward-handler.js";
31
30
  import { KEL_PUBLISHER_PACING, KelPublisher, kelPublisherDo } from "../mailbox/kel-publisher.js";
@@ -47,7 +46,6 @@ import { cueDo } from "./cue-runtime.js";
47
46
  import { doEveryOnWrite, PROTOCOL_PACING } from "./doing.js";
48
47
  import { IpexAuxiliaries } from "./ipex-auxiliaries.js";
49
48
  import { composeOwners, operationOwnersDo, OWNER_PACING, } from "./operation-owners.js";
50
- import { SignerConditions } from "./signer-conditions.js";
51
49
  import { signalerChangeSink } from "./workflow-contract.js";
52
50
  import { observeUntil } from "./workflow-contract.js";
53
51
  /**
@@ -67,12 +65,18 @@ import { observeUntil } from "./workflow-contract.js";
67
65
  * @throws Dependency mismatch/acquisition errors propagate after owned resource cleanup.
68
66
  */
69
67
  export function* createAgentRuntime(hby, http, clock, options = {}) {
68
+ // An edge reaches only its relay: every component below, and `runtime.http`, gets the guarded client.
69
+ if (options.relay)
70
+ http = relayOnlyHttpClient(http, hby, options.relay);
70
71
  const injectedSignaler = options.signaler ?? options.notifier?.signaler;
71
72
  if ((options.notifier && options.notifier.signator !== hby.signator)
72
73
  || (options.noter && options.notifier && options.noter !== options.notifier.noter)
73
74
  || (options.signaler && options.notifier && options.signaler !== options.notifier.signaler)) {
74
75
  throw new RangeError("AgentRuntime notification services require its exact Signator, Noter and Signaler");
75
76
  }
77
+ if (options.relaying && !options.mailboxer && !options.enableMailboxStore) {
78
+ throw new RangeError("Relaying requires a mailbox store for /fwd intake");
79
+ }
76
80
  const mailboxer = options.mailboxer
77
81
  ?? (options.enableMailboxStore ? (yield* openRuntimeMailboxer(hby)) : null);
78
82
  const ownsMailboxer = options.mailboxer === undefined && mailboxer !== null;
@@ -131,7 +135,7 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
131
135
  const kramer = options.kram ? new Kramer(hby.db, { config: options.kram, cues: protocolCues, now }) : undefined;
132
136
  const kvy = new Kevery(hby.db, { cues: protocolCues, rvy: revery, messagePolicy: options.messagePolicy, kramer });
133
137
  const exc = new Exchanger(hby, { cues: protocolCues, now });
134
- const registry = acdc ? new RegistryObserver(hby.db, acdc.store) : null;
138
+ const registry = acdc ? new RegistryObserver(hby.db, acdc.store, protocolCues) : null;
135
139
  // Every runtime with a v2 registry store owns the per-identifier DH and Unblind owners: one dispatch path.
136
140
  const ipex = registry
137
141
  ? yield* resource(function* (provide) {
@@ -146,11 +150,11 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
146
150
  })
147
151
  : null;
148
152
  const replies = new QueryReplies(cues);
149
- const protocol = new ProtocolProcessor(kvy, revery, exc, vdr?.tvy ?? null, vdr?.vry ?? null, registry, protocolCues, cues, replies, ipex ?? undefined);
153
+ const protocol = new ProtocolProcessor(kvy, revery, exc, vdr?.tvy ?? null, vdr?.vry ?? null, registry, protocolCues, cues, replies, ipex ?? undefined, options.relaying === true);
150
154
  const reactor = new Reactor(protocol);
151
155
  loadChallengeHandlers(hby.db, exc);
152
156
  loadDelegationHandlers(hby, exc, notifier);
153
- loadOobiHandlers(hby, exc, notifier);
157
+ loadOobiHandlers(hby, exc, notifier, options.relaying === true);
154
158
  const mux = loadMultisigHandlers(hby, exc, { notifier, registry: protocol.registry, messageKevery: protocol.kvy });
155
159
  loadIpexHandlers(hby, exc, {
156
160
  notifier,
@@ -158,8 +162,9 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
158
162
  grantWalk: options.grantWalk,
159
163
  });
160
164
  // Construction adopts stored copies an earlier handler-less Exchanger left unescrowed.
165
+ const changes = options.operationStores?.changes ?? (signaler ? signalerChangeSink(signaler) : undefined);
161
166
  const registryRoot = options.registryRoots !== "refuse" && acdc
162
- ? new RegistryRoot(hby, acdc, notifier, options.operationStores?.changes ?? (signaler ? signalerChangeSink(signaler) : undefined))
167
+ ? new RegistryRoot(hby, acdc, notifier, changes)
163
168
  : null;
164
169
  if (registryRoot)
165
170
  exc.addHandler(registryRoot);
@@ -175,7 +180,7 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
175
180
  }
176
181
  });
177
182
  const postman = yield* resource(function* (provide) {
178
- const postman = new Poster(hby, mailboxer, protocol, http, clock);
183
+ const postman = new Poster(hby, mailboxer, protocol, http, clock, options.relay ?? null);
179
184
  try {
180
185
  yield* provide(postman);
181
186
  }
@@ -185,7 +190,7 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
185
190
  });
186
191
  const respondant = new Respondant(hby, postman);
187
192
  if (mailboxer) {
188
- exc.addHandler(new ForwardHandler(mailboxDirector));
193
+ exc.addHandler(new ForwardHandler(mailboxDirector, reactor, options.relaying ? postman : null));
189
194
  }
190
195
  for (const route of exc.routes.keys()) {
191
196
  if (route === ForwardHandler.resource) {
@@ -206,20 +211,7 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
206
211
  changes: signaler ? signalerChangeSink(signaler) : undefined,
207
212
  retry: options.kelPublishRetry,
208
213
  });
209
- const signerConditions = new SignerConditions();
210
- const mailboxPoller = new MailboxPoller(hby, mailboxDirector, http, clock, {
211
- signerConditions,
212
- onPolled: (pre, mailbox) => kelPublisher.prompt(pre, mailbox),
213
- onUnauthorized: (pre, mailbox) => kelPublisher.repair(pre, mailbox),
214
- timeouts: options.mailboxPolling,
215
- });
216
- // A mailbox role accepted from a reply (a group member's approved add, an OOBI) is acted on at the next tock.
217
- revery.onEndRole = (cid, role) => {
218
- if (role !== Roles.mailbox)
219
- return;
220
- kelPublisher.prompt(cid);
221
- mailboxPoller.prompt(cid);
222
- };
214
+ const mailboxPoller = new MailboxPoller(hby, mailboxDirector, http, clock, options.relay ?? null, { polls: kelPublisher.polls, timeouts: options.mailboxPolling });
223
215
  for (const topic of [
224
216
  DELEGATE_MAILBOX_TOPIC,
225
217
  CREDENTIAL_MAILBOX_TOPIC,
@@ -230,7 +222,7 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
230
222
  mailboxDirector.registerTopic(topic);
231
223
  }
232
224
  const querying = yield* resource(function* (provide) {
233
- const inquisitor = new WitnessInquisitor(hby, protocol, http, clock);
225
+ const inquisitor = new WitnessInquisitor(hby, protocol, http, clock, options.relay ?? null);
234
226
  try {
235
227
  yield* provide(inquisitor);
236
228
  }
@@ -239,17 +231,9 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
239
231
  }
240
232
  });
241
233
  const delegating = new Anchorer(hby, postman, querying, protocol, http, clock);
242
- // Group receipting reaches TCP-only witnesses as `WitnessReceiptor` does, as KERIpy's Counselor does.
243
- const groupReceiptor = new Receiptor(hby, protocol, http, clock);
244
- const counselor = new Counselor(hby, {
245
- receipt: (pre, options) => groupReceiptor.receipt(pre, { ...options, allowTcp: true }),
246
- get: (pre, options) => groupReceiptor.get(pre, options),
247
- }, clock, signaler ? signalerChangeSink(signaler) : undefined, { anchorer: delegating, querying }, {
248
- rotated: (cue) => {
249
- kelPublisher.rotated.push(cue);
250
- owned?.rootReplicator?.prompt(cue.pre);
251
- },
252
- }, options.counselorReceiptRetry, groupEventForwarding(hby, postman));
234
+ // Only a runtime with operations owners and a registry root has a root replicator to drain this deck.
235
+ const groupChanges = registryRoot && ipex ? new Deck() : undefined;
236
+ const counselor = new Counselor(hby, new Receiptor(hby, protocol, http, clock), clock, signaler ? signalerChangeSink(signaler) : undefined, { anchorer: delegating, querying }, kelPublisher.rotated, groupChanges, options.counselorReceiptRetry, groupEventForwarding(hby, postman));
253
237
  const owned = acdc && ipex
254
238
  ? composeOwners({
255
239
  hby,
@@ -264,21 +248,26 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
264
248
  registryRoot,
265
249
  mux,
266
250
  counselor,
251
+ groupChanges,
267
252
  retry: options.operationRetry,
268
253
  queryResendMs: options.issuanceQueryResendMs,
269
254
  changes: signaler ? signalerChangeSink(signaler) : undefined,
270
255
  }, options.operationStores)
271
256
  : null;
272
- const oobiery = new Oobiery(hby, protocol, revery, http, cues);
257
+ const oobiery = new Oobiery(hby, protocol, revery, http, cues, options.relay ? { relay: options.relay, postman, clock } : null);
273
258
  oobiery.registerReplyRoutes(revery.rtr);
259
+ if (options.relay && !relayEndpoint(hby, options.relay))
260
+ oobiery.resolve(options.relay.oobi);
274
261
  const authenticator = new Authenticator(hby, http);
275
262
  return {
276
263
  hby,
277
264
  mailboxer,
265
+ relaying: options.relaying === true,
278
266
  noter,
279
267
  notifier,
280
268
  signaler,
281
269
  cues,
270
+ changes: changes ?? null,
282
271
  reactor,
283
272
  protocol,
284
273
  replies,
@@ -296,7 +285,6 @@ export function* createAgentRuntime(hby, http, clock, options = {}) {
296
285
  delegating,
297
286
  counselor,
298
287
  kelPublisher,
299
- signerConditions,
300
288
  ownerPacing: options.ownerPacing ?? OWNER_PACING,
301
289
  publisherPacing: options.publisherPacing ?? KEL_PUBLISHER_PACING,
302
290
  protocolPacing: options.protocolPacing ?? PROTOCOL_PACING,
@@ -618,6 +606,11 @@ export { runtimeTurn } from "./runtime-turn.js";
618
606
  * delegation and ordinary query sending. The finalizer halts children in reverse
619
607
  * order; store ownership remains with the composition scope.
620
608
  *
609
+ * Each loop is a plain spawned fixed-tock doer. A loop that signs for a local habitat reads `Hab.canSign()` on its
610
+ * tock and skips a habitat that cannot sign yet (a store opened without its signer); a later `Manager.hold` lets
611
+ * that habitat act on the next tock. A `SignerNotHeldError` that still escapes a loop is a fault: the loop ends
612
+ * and the failure ends this operation.
613
+ *
621
614
  * @param runtime Borrowed composed runtime; caller owns its worker and store lifetime.
622
615
  * @param options Optional default query/cue signer, remote pollMailbox switch
623
616
  * (default true) and command-owned queryCues Deck. Wire queryCues before starting
@@ -628,28 +621,25 @@ export function* runAgentRuntime(runtime, options = {}) {
628
621
  runtime.querying.configure({ hab: options.hab });
629
622
  const pacing = runtime.protocolPacing;
630
623
  const telReceiptor = runtime.vdr ? new Receiptor(runtime.hby, runtime.protocol, runtime.http, runtime.clock) : null;
631
- // A loop that needs a signer the host released waits for it; it never ends the runtime (see `surviving`).
632
- const alive = (loop, make) => spawn(() => surviving(runtime, loop, make));
633
- const stopHoldWatch = runtime.hby.mgr.onHold(() => runtime.signerConditions.held());
634
624
  const tasks = [
635
- yield* alive("querying", () => runtime.querying.deliverDo(pacing.tockMs)),
636
- yield* alive("postman", () => runtime.postman.deliverDo(pacing.tockMs)),
625
+ yield* spawn(() => runtime.querying.deliverDo(pacing.tockMs)),
626
+ yield* spawn(() => runtime.postman.deliverDo(pacing.tockMs)),
637
627
  ...(runtime.vdr && telReceiptor
638
628
  ? [
639
- yield* alive("tel-publication", () => doEveryOnWrite(runtime.clock, pacing, {
629
+ yield* spawn(() => doEveryOnWrite(runtime.clock, pacing, {
640
630
  get writeActivity() {
641
631
  return runtime.hby.db.writeActivity + runtime.vdr.publication.reger.writeActivity;
642
632
  },
643
633
  }, () => publishPendingTel(runtime.hby, runtime.vdr.publication, telReceiptor, runtime.http, runtime.clock))),
644
634
  ]
645
635
  : []),
646
- yield* alive("cues", () => cueDo(runtime, options, pacing)),
647
- yield* alive("escrows", () => runtime.protocol.escrowDo(runtime.clock, pacing)),
648
- yield* alive("oobi", () => runtime.oobiery.oobiDo(runtime.clock, pacing)),
649
- yield* alive("authentication", () => runtime.authenticator.authDo(runtime.clock, pacing)),
636
+ yield* spawn(() => cueDo(runtime, options, pacing)),
637
+ yield* spawn(() => runtime.protocol.escrowDo(runtime.clock, pacing)),
638
+ yield* spawn(() => runtime.oobiery.oobiDo(runtime.clock, pacing)),
639
+ yield* spawn(() => runtime.authenticator.authDo(runtime.clock, pacing)),
650
640
  ...(runtime.signaler
651
641
  ? [
652
- yield* alive("signals", () => runtime.signaler.signalDo(runtime.clock, pacing)),
642
+ yield* spawn(() => runtime.signaler.signalDo(runtime.clock, pacing)),
653
643
  ]
654
644
  : []),
655
645
  ...(options.pollMailbox ?? true
@@ -659,12 +649,12 @@ export function* runAgentRuntime(runtime, options = {}) {
659
649
  }),
660
650
  ]
661
651
  : []),
662
- yield* alive("delegation", () => doEveryOnWrite(runtime.clock, pacing, runtime.hby.db, () => runtime.delegating.processAllOnce())),
663
- yield* alive("counselor", () => doEveryOnWrite(runtime.clock, pacing, runtime.hby.db, () => runtime.counselor.processOnce(), () => runtime.counselor.holdsForwards)),
664
- yield* alive("kel-publisher", () => kelPublisherDo(runtime.kelPublisher, runtime.clock, runtime.publisherPacing)),
652
+ yield* spawn(() => doEveryOnWrite(runtime.clock, pacing, runtime.hby.db, () => runtime.delegating.processAllOnce())),
653
+ yield* spawn(() => doEveryOnWrite(runtime.clock, pacing, runtime.hby.db, () => runtime.counselor.processOnce(), () => runtime.counselor.holdsForwards)),
654
+ yield* spawn(() => kelPublisherDo(runtime.kelPublisher, runtime.clock, runtime.publisherPacing)),
665
655
  ...(runtime.operations && runtime.ipex
666
656
  ? [
667
- yield* alive("operation-owners", () => operationOwnersDo({
657
+ yield* spawn(() => operationOwnersDo({
668
658
  operations: runtime.operations,
669
659
  rootReplicator: runtime.rootReplicator,
670
660
  ipex: runtime.ipex,
@@ -677,29 +667,8 @@ export function* runAgentRuntime(runtime, options = {}) {
677
667
  return yield* suspend();
678
668
  }
679
669
  finally {
680
- stopHoldWatch();
681
670
  for (const task of tasks.reverse()) {
682
671
  yield* task.halt();
683
672
  }
684
673
  }
685
674
  }
686
- /** How long a loop waits for a held signer before trying again, in milliseconds. */
687
- const SIGNER_RETRY_MS = 1_000;
688
- /**
689
- * Run a background loop so that a signer the host released does not end the runtime: the loop's
690
- * `SignerNotHeldError` is recorded in `runtime.signerConditions`, the loop waits for a signer to be held (or a
691
- * second) and starts again. Every other failure still ends it.
692
- */
693
- function* surviving(runtime, loop, make) {
694
- while (true) {
695
- try {
696
- return yield* make();
697
- }
698
- catch (error) {
699
- if (!(error instanceof SignerNotHeldError))
700
- throw error;
701
- runtime.signerConditions.report(`runtime:${loop}`, error.pub, runtime.clock.now());
702
- yield* runtime.signerConditions.waitForHold(runtime.clock, SIGNER_RETRY_MS);
703
- }
704
- }
705
- }