keri-ts 2.2.3 → 2.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/esm/keri/npm/src/_dnt.polyfills.js +14 -14
  2. package/esm/keri/npm/src/acdc/registering.js +19 -0
  3. package/esm/keri/npm/src/acdc/salting.js +4 -0
  4. package/esm/keri/npm/src/app/bootstrap/authenticating.js +34 -12
  5. package/esm/keri/npm/src/app/bootstrap/oobiery.js +365 -32
  6. package/esm/keri/npm/src/app/credentials/credential-importing.js +33 -4
  7. package/esm/keri/npm/src/app/credentials/root-replicator.js +27 -15
  8. package/esm/keri/npm/src/app/credentials/schema-resolving.js +5 -1
  9. package/esm/keri/npm/src/app/credentials/tel-publishing.js +16 -7
  10. package/esm/keri/npm/src/app/delivery/forwarding.js +188 -47
  11. package/esm/keri/npm/src/app/delivery/relaying.js +232 -0
  12. package/esm/keri/npm/src/app/delivery/respondant.js +8 -5
  13. package/esm/keri/npm/src/app/exchanges/exchange-messaging.js +1 -1
  14. package/esm/keri/npm/src/app/exchanges/exchanging.js +65 -9
  15. package/esm/keri/npm/src/app/identity/delegating.js +66 -9
  16. package/esm/keri/npm/src/app/identity/habbing.js +190 -71
  17. package/esm/keri/npm/src/app/identity/keeping.js +111 -39
  18. package/esm/keri/npm/src/app/identity/retire-identifier.js +98 -0
  19. package/esm/keri/npm/src/app/ipex/disclosure-replaying.js +116 -0
  20. package/esm/keri/npm/src/app/ipex/group-ipex.js +122 -25
  21. package/esm/keri/npm/src/app/ipex/group-issuance.js +109 -23
  22. package/esm/keri/npm/src/app/ipex/ipex-discloser.js +36 -22
  23. package/esm/keri/npm/src/app/ipex/ipex-grouping.js +53 -3
  24. package/esm/keri/npm/src/app/ipex/ipex-v2.js +7 -2
  25. package/esm/keri/npm/src/app/ipex/issuance.js +131 -35
  26. package/esm/keri/npm/src/app/ipex/pending-presentations.js +4 -2
  27. package/esm/keri/npm/src/app/ipex/presenting.js +3 -18
  28. package/esm/keri/npm/src/app/ipex/receive-issuance.js +2 -1
  29. package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +16 -3
  30. package/esm/keri/npm/src/app/mailbox/agent-inbox.js +292 -0
  31. package/esm/keri/npm/src/app/mailbox/forward-handler.js +298 -52
  32. package/esm/keri/npm/src/app/mailbox/kel-publisher.js +26 -31
  33. package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +70 -62
  34. package/esm/keri/npm/src/app/mailbox/mailboxing.js +20 -4
  35. package/esm/keri/npm/src/app/multisig/counseling.js +75 -27
  36. package/esm/keri/npm/src/app/multisig/group-members.js +41 -5
  37. package/esm/keri/npm/src/app/multisig/grouping.js +50 -2
  38. package/esm/keri/npm/src/app/multisig/multisig-workflows.js +3 -18
  39. package/esm/keri/npm/src/app/multisig/proposing.js +1 -16
  40. package/esm/keri/npm/src/app/multisig/rejoin-group.js +99 -0
  41. package/esm/keri/npm/src/app/multisig/replace-member.js +29 -8
  42. package/esm/keri/npm/src/app/protocol/parsering.js +45 -17
  43. package/esm/keri/npm/src/app/protocol/protocol-processing.js +62 -27
  44. package/esm/keri/npm/src/app/queries/query-replies.js +0 -22
  45. package/esm/keri/npm/src/app/queries/relay-refresh.js +81 -0
  46. package/esm/keri/npm/src/app/queries/witness-inquisitor.js +190 -40
  47. package/esm/keri/npm/src/app/runtime/agent-runtime.js +46 -73
  48. package/esm/keri/npm/src/app/runtime/cue-runtime.js +45 -5
  49. package/esm/keri/npm/src/app/runtime/doing.js +25 -0
  50. package/esm/keri/npm/src/app/runtime/operation-owners.js +26 -56
  51. package/esm/keri/npm/src/app/runtime/workflow-contract.js +7 -4
  52. package/esm/keri/npm/src/app/transport/http-client.js +20 -2
  53. package/esm/keri/npm/src/app/transport/message-source.js +2 -1
  54. package/esm/keri/npm/src/app/transport/messengering.js +39 -8
  55. package/esm/keri/npm/src/app/witness/ingress.js +7 -0
  56. package/esm/keri/npm/src/app/witness/receipt-client.js +43 -16
  57. package/esm/keri/npm/src/app/witness/receipting.js +62 -20
  58. package/esm/keri/npm/src/cli/messaging/multisig.js +4 -3
  59. package/esm/keri/npm/src/core/errors.js +73 -0
  60. package/esm/keri/npm/src/core/kel/delegation-ancestry.js +11 -2
  61. package/esm/keri/npm/src/core/kel/eventing.js +25 -28
  62. package/esm/keri/npm/src/core/kel/kever.js +15 -9
  63. package/esm/keri/npm/src/core/mailbox/kram-policy.js +8 -0
  64. package/esm/keri/npm/src/core/mailbox/kram-pruning.js +25 -6
  65. package/esm/keri/npm/src/core/mailbox/kram-time.js +34 -6
  66. package/esm/keri/npm/src/core/mailbox/kraming.js +58 -2
  67. package/esm/keri/npm/src/core/routing.js +18 -25
  68. package/esm/keri/npm/src/core/scheming.js +12 -5
  69. package/esm/keri/npm/src/core/wire-fields.js +126 -0
  70. package/esm/keri/npm/src/db/basing.js +4 -13
  71. package/esm/keri/npm/src/db/core/indexeddb-persistence.js +3 -2
  72. package/esm/keri/npm/src/db/core/lmdber.js +25 -6
  73. package/esm/keri/npm/src/db/core/memstore.js +4 -0
  74. package/esm/keri/npm/src/db/core/store-owner.js +4 -0
  75. package/esm/keri/npm/src/db/core/webdber.js +85 -27
  76. package/esm/keri/npm/src/db/mailbox/mailboxing.js +13 -0
  77. package/esm/keri/npm/src/db/subing.js +4 -0
  78. package/esm/keri/npm/src/runtime/index.js +11 -5
  79. package/esm/keri/npm/src/time/mod.js +5 -0
  80. package/esm/keri/npm/src/vdr/credential-tsn-record.js +2 -7
  81. package/esm/keri/npm/src/vdr/eventing.js +28 -19
  82. package/esm/keri/npm/src/vdr/registry-tsn-record.js +3 -13
  83. package/esm/keri/npm/src/version.js +2 -2
  84. package/package.json +1 -1
  85. package/types/_dnt.polyfills.d.ts +9 -9
  86. package/types/_dnt.polyfills.d.ts.map +1 -1
  87. package/types/acdc/registering.d.ts +6 -0
  88. package/types/acdc/registering.d.ts.map +1 -1
  89. package/types/acdc/salting.d.ts.map +1 -1
  90. package/types/app/bootstrap/authenticating.d.ts +12 -2
  91. package/types/app/bootstrap/authenticating.d.ts.map +1 -1
  92. package/types/app/bootstrap/oobiery.d.ts +144 -7
  93. package/types/app/bootstrap/oobiery.d.ts.map +1 -1
  94. package/types/app/credentials/credential-importing.d.ts +8 -0
  95. package/types/app/credentials/credential-importing.d.ts.map +1 -1
  96. package/types/app/credentials/root-replicator.d.ts +19 -8
  97. package/types/app/credentials/root-replicator.d.ts.map +1 -1
  98. package/types/app/credentials/schema-resolving.d.ts.map +1 -1
  99. package/types/app/credentials/tel-publishing.d.ts +4 -2
  100. package/types/app/credentials/tel-publishing.d.ts.map +1 -1
  101. package/types/app/delivery/forwarding.d.ts +75 -7
  102. package/types/app/delivery/forwarding.d.ts.map +1 -1
  103. package/types/app/delivery/relaying.d.ts +144 -0
  104. package/types/app/delivery/relaying.d.ts.map +1 -0
  105. package/types/app/delivery/respondant.d.ts +9 -2
  106. package/types/app/delivery/respondant.d.ts.map +1 -1
  107. package/types/app/exchanges/exchanging.d.ts +36 -0
  108. package/types/app/exchanges/exchanging.d.ts.map +1 -1
  109. package/types/app/identity/delegating.d.ts +6 -0
  110. package/types/app/identity/delegating.d.ts.map +1 -1
  111. package/types/app/identity/habbing.d.ts +106 -19
  112. package/types/app/identity/habbing.d.ts.map +1 -1
  113. package/types/app/identity/keeping.d.ts +54 -19
  114. package/types/app/identity/keeping.d.ts.map +1 -1
  115. package/types/app/identity/retire-identifier.d.ts +24 -0
  116. package/types/app/identity/retire-identifier.d.ts.map +1 -0
  117. package/types/app/ipex/disclosure-replaying.d.ts +61 -0
  118. package/types/app/ipex/disclosure-replaying.d.ts.map +1 -0
  119. package/types/app/ipex/group-ipex.d.ts +60 -14
  120. package/types/app/ipex/group-ipex.d.ts.map +1 -1
  121. package/types/app/ipex/group-issuance.d.ts +48 -6
  122. package/types/app/ipex/group-issuance.d.ts.map +1 -1
  123. package/types/app/ipex/ipex-discloser.d.ts +14 -2
  124. package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
  125. package/types/app/ipex/ipex-grouping.d.ts +23 -2
  126. package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
  127. package/types/app/ipex/ipex-v2.d.ts +5 -2
  128. package/types/app/ipex/ipex-v2.d.ts.map +1 -1
  129. package/types/app/ipex/issuance.d.ts +35 -11
  130. package/types/app/ipex/issuance.d.ts.map +1 -1
  131. package/types/app/ipex/pending-presentations.d.ts +4 -2
  132. package/types/app/ipex/pending-presentations.d.ts.map +1 -1
  133. package/types/app/ipex/presenting.d.ts.map +1 -1
  134. package/types/app/ipex/receive-issuance.d.ts +2 -1
  135. package/types/app/ipex/receive-issuance.d.ts.map +1 -1
  136. package/types/app/ipex/unblinding-workflow.d.ts +4 -1
  137. package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
  138. package/types/app/mailbox/agent-inbox.d.ts +126 -0
  139. package/types/app/mailbox/agent-inbox.d.ts.map +1 -0
  140. package/types/app/mailbox/forward-handler.d.ts +114 -14
  141. package/types/app/mailbox/forward-handler.d.ts.map +1 -1
  142. package/types/app/mailbox/kel-publisher.d.ts +12 -22
  143. package/types/app/mailbox/kel-publisher.d.ts.map +1 -1
  144. package/types/app/mailbox/mailbox-poller.d.ts +40 -24
  145. package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
  146. package/types/app/mailbox/mailboxing.d.ts +19 -5
  147. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  148. package/types/app/multisig/counseling.d.ts +34 -11
  149. package/types/app/multisig/counseling.d.ts.map +1 -1
  150. package/types/app/multisig/group-members.d.ts +17 -1
  151. package/types/app/multisig/group-members.d.ts.map +1 -1
  152. package/types/app/multisig/grouping.d.ts +17 -1
  153. package/types/app/multisig/grouping.d.ts.map +1 -1
  154. package/types/app/multisig/multisig-workflows.d.ts +0 -8
  155. package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
  156. package/types/app/multisig/proposing.d.ts +4 -19
  157. package/types/app/multisig/proposing.d.ts.map +1 -1
  158. package/types/app/multisig/rejoin-group.d.ts +119 -0
  159. package/types/app/multisig/rejoin-group.d.ts.map +1 -0
  160. package/types/app/multisig/replace-member.d.ts +15 -1
  161. package/types/app/multisig/replace-member.d.ts.map +1 -1
  162. package/types/app/protocol/parsering.d.ts +21 -2
  163. package/types/app/protocol/parsering.d.ts.map +1 -1
  164. package/types/app/protocol/protocol-processing.d.ts +31 -6
  165. package/types/app/protocol/protocol-processing.d.ts.map +1 -1
  166. package/types/app/queries/query-replies.d.ts +4 -8
  167. package/types/app/queries/query-replies.d.ts.map +1 -1
  168. package/types/app/queries/relay-refresh.d.ts +26 -0
  169. package/types/app/queries/relay-refresh.d.ts.map +1 -0
  170. package/types/app/queries/witness-inquisitor.d.ts +57 -6
  171. package/types/app/queries/witness-inquisitor.d.ts.map +1 -1
  172. package/types/app/runtime/agent-runtime.d.ts +34 -3
  173. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  174. package/types/app/runtime/cue-runtime.d.ts +15 -3
  175. package/types/app/runtime/cue-runtime.d.ts.map +1 -1
  176. package/types/app/runtime/doing.d.ts +17 -0
  177. package/types/app/runtime/doing.d.ts.map +1 -1
  178. package/types/app/runtime/operation-owners.d.ts +16 -26
  179. package/types/app/runtime/operation-owners.d.ts.map +1 -1
  180. package/types/app/runtime/workflow-contract.d.ts +21 -6
  181. package/types/app/runtime/workflow-contract.d.ts.map +1 -1
  182. package/types/app/transport/http-client.d.ts +9 -0
  183. package/types/app/transport/http-client.d.ts.map +1 -1
  184. package/types/app/transport/message-source.d.ts +6 -3
  185. package/types/app/transport/message-source.d.ts.map +1 -1
  186. package/types/app/transport/messengering.d.ts +10 -1
  187. package/types/app/transport/messengering.d.ts.map +1 -1
  188. package/types/app/witness/ingress.d.ts +1 -0
  189. package/types/app/witness/ingress.d.ts.map +1 -1
  190. package/types/app/witness/receipt-client.d.ts +19 -1
  191. package/types/app/witness/receipt-client.d.ts.map +1 -1
  192. package/types/app/witness/receipting.d.ts +24 -1
  193. package/types/app/witness/receipting.d.ts.map +1 -1
  194. package/types/cli/messaging/multisig.d.ts.map +1 -1
  195. package/types/core/cues.d.ts +53 -1
  196. package/types/core/cues.d.ts.map +1 -1
  197. package/types/core/errors.d.ts +32 -0
  198. package/types/core/errors.d.ts.map +1 -1
  199. package/types/core/kel/delegation-ancestry.d.ts.map +1 -1
  200. package/types/core/kel/eventing.d.ts.map +1 -1
  201. package/types/core/kel/kever-decisions.d.ts +3 -1
  202. package/types/core/kel/kever-decisions.d.ts.map +1 -1
  203. package/types/core/kel/kever.d.ts +7 -1
  204. package/types/core/kel/kever.d.ts.map +1 -1
  205. package/types/core/mailbox/kram-policy.d.ts +6 -0
  206. package/types/core/mailbox/kram-policy.d.ts.map +1 -1
  207. package/types/core/mailbox/kram-pruning.d.ts +5 -1
  208. package/types/core/mailbox/kram-pruning.d.ts.map +1 -1
  209. package/types/core/mailbox/kram-time.d.ts +26 -2
  210. package/types/core/mailbox/kram-time.d.ts.map +1 -1
  211. package/types/core/mailbox/kraming.d.ts +14 -1
  212. package/types/core/mailbox/kraming.d.ts.map +1 -1
  213. package/types/core/routing.d.ts +3 -7
  214. package/types/core/routing.d.ts.map +1 -1
  215. package/types/core/scheming.d.ts.map +1 -1
  216. package/types/core/wire-fields.d.ts +56 -0
  217. package/types/core/wire-fields.d.ts.map +1 -0
  218. package/types/db/basing.d.ts.map +1 -1
  219. package/types/db/core/dber.d.ts +5 -0
  220. package/types/db/core/dber.d.ts.map +1 -1
  221. package/types/db/core/indexeddb-persistence.d.ts +3 -2
  222. package/types/db/core/indexeddb-persistence.d.ts.map +1 -1
  223. package/types/db/core/lmdber.d.ts +6 -0
  224. package/types/db/core/lmdber.d.ts.map +1 -1
  225. package/types/db/core/memstore.d.ts +2 -0
  226. package/types/db/core/memstore.d.ts.map +1 -1
  227. package/types/db/core/store-owner.d.ts +2 -0
  228. package/types/db/core/store-owner.d.ts.map +1 -1
  229. package/types/db/core/webdber.d.ts +32 -6
  230. package/types/db/core/webdber.d.ts.map +1 -1
  231. package/types/db/exchange/exchange-records.d.ts +4 -0
  232. package/types/db/exchange/exchange-records.d.ts.map +1 -1
  233. package/types/db/mailbox/mailboxing.d.ts +10 -1
  234. package/types/db/mailbox/mailboxing.d.ts.map +1 -1
  235. package/types/db/subing.d.ts +2 -0
  236. package/types/db/subing.d.ts.map +1 -1
  237. package/types/runtime/index.d.ts +20 -14
  238. package/types/runtime/index.d.ts.map +1 -1
  239. package/types/time/mod.d.ts +2 -0
  240. package/types/time/mod.d.ts.map +1 -1
  241. package/types/vdr/credential-tsn-record.d.ts.map +1 -1
  242. package/types/vdr/eventing.d.ts.map +1 -1
  243. package/types/vdr/registry-tsn-record.d.ts.map +1 -1
  244. package/types/version.d.ts +2 -2
  245. package/esm/keri/npm/src/app/runtime/signer-conditions.js +0 -51
  246. package/types/app/runtime/signer-conditions.d.ts +0 -32
  247. package/types/app/runtime/signer-conditions.d.ts.map +0 -1
@@ -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";
@@ -44,10 +43,9 @@ import { QueryReplies } from "../queries/query-replies.js";
44
43
  import { WitnessInquisitor } from "../queries/witness-inquisitor.js";
45
44
  import { Receiptor } from "../witness/receipting.js";
46
45
  import { cueDo } from "./cue-runtime.js";
47
- import { doEvery, PROTOCOL_PACING } from "./doing.js";
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;
@@ -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,24 +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()),
636
- yield* alive("postman", () => runtime.postman.deliverDo()),
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", () => doEvery(runtime.clock, pacing.tockMs, () => publishPendingTel(runtime.hby, runtime.vdr.publication, telReceiptor, runtime.http, runtime.clock))),
629
+ yield* spawn(() => doEveryOnWrite(runtime.clock, pacing, {
630
+ get writeActivity() {
631
+ return runtime.hby.db.writeActivity + runtime.vdr.publication.reger.writeActivity;
632
+ },
633
+ }, () => publishPendingTel(runtime.hby, runtime.vdr.publication, telReceiptor, runtime.http, runtime.clock))),
640
634
  ]
641
635
  : []),
642
- yield* alive("cues", () => cueDo(runtime, options, pacing)),
643
- yield* alive("escrows", () => runtime.protocol.escrowDo(runtime.clock)),
644
- yield* alive("oobi", () => runtime.oobiery.oobiDo(runtime.clock, pacing)),
645
- 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)),
646
640
  ...(runtime.signaler
647
641
  ? [
648
- yield* alive("signals", () => runtime.signaler.signalDo(runtime.clock, pacing)),
642
+ yield* spawn(() => runtime.signaler.signalDo(runtime.clock, pacing)),
649
643
  ]
650
644
  : []),
651
645
  ...(options.pollMailbox ?? true
@@ -655,12 +649,12 @@ export function* runAgentRuntime(runtime, options = {}) {
655
649
  }),
656
650
  ]
657
651
  : []),
658
- yield* alive("delegation", () => doEvery(runtime.clock, pacing.tockMs, () => runtime.delegating.processAllOnce())),
659
- yield* alive("counselor", () => doEvery(runtime.clock, pacing.tockMs, () => runtime.counselor.processOnce())),
660
- 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)),
661
655
  ...(runtime.operations && runtime.ipex
662
656
  ? [
663
- yield* alive("operation-owners", () => operationOwnersDo({
657
+ yield* spawn(() => operationOwnersDo({
664
658
  operations: runtime.operations,
665
659
  rootReplicator: runtime.rootReplicator,
666
660
  ipex: runtime.ipex,
@@ -673,29 +667,8 @@ export function* runAgentRuntime(runtime, options = {}) {
673
667
  return yield* suspend();
674
668
  }
675
669
  finally {
676
- stopHoldWatch();
677
670
  for (const task of tasks.reverse()) {
678
671
  yield* task.halt();
679
672
  }
680
673
  }
681
674
  }
682
- /** How long a loop waits for a held signer before trying again, in milliseconds. */
683
- const SIGNER_RETRY_MS = 1_000;
684
- /**
685
- * Run a background loop so that a signer the host released does not end the runtime: the loop's
686
- * `SignerNotHeldError` is recorded in `runtime.signerConditions`, the loop waits for a signer to be held (or a
687
- * second) and starts again. Every other failure still ends it.
688
- */
689
- function* surviving(runtime, loop, make) {
690
- while (true) {
691
- try {
692
- return yield* make();
693
- }
694
- catch (error) {
695
- if (!(error instanceof SignerNotHeldError))
696
- throw error;
697
- runtime.signerConditions.report(`runtime:${loop}`, error.pub, runtime.clock.now());
698
- yield* runtime.signerConditions.waitForHold(runtime.clock, SIGNER_RETRY_MS);
699
- }
700
- }
701
- }
@@ -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")
@@ -3,10 +3,13 @@ import { waitOnClock } from "./workflow-contract.js";
3
3
  export const PROTOCOL_TOCK_MS = 31.25;
4
4
  /** Tocks between full sweeps of a loop that has one: 1920 tocks of 31.25 ms is a minute. */
5
5
  export const PROTOCOL_SWEEP_TOCKS = 1920;
6
+ /** Tocks between passes of a write-driven loop when the store did not change: 32 tocks of 31.25 ms is one second. */
7
+ export const PROTOCOL_IDLE_TOCKS = 32;
6
8
  /** The production pacing of the protocol-facing background loops. */
7
9
  export const PROTOCOL_PACING = Object.freeze({
8
10
  tockMs: PROTOCOL_TOCK_MS,
9
11
  sweepTocks: PROTOCOL_SWEEP_TOCKS,
12
+ idleTocks: PROTOCOL_IDLE_TOCKS,
10
13
  });
11
14
  /** Run `pass` forever: pass 0, wait `tockMs` on `clock`, pass 1, and so on. A thrown error ends the doer. */
12
15
  export function* doEvery(clock, tockMs, pass) {
@@ -15,3 +18,25 @@ export function* doEvery(clock, tockMs, pass) {
15
18
  yield* waitOnClock(clock, tockMs);
16
19
  }
17
20
  }
21
+ /**
22
+ * A fixed-tock doer for a loop whose work lives in store rows: pass 0, then a pass only on a tock after the
23
+ * store's write counter moved (a pass's own writes included, so it repeats until the store is quiet),
24
+ * after `busy()` says work is held in memory, or every `idleTocks`-th tock for what only time releases
25
+ * (timeouts, retry delays). A tock with nothing new costs a counter read, which touches no store, and no table scan. Rows written by
26
+ * another loop are seen on the next tock; time-driven work waits at most `idleTocks` tocks.
27
+ */
28
+ export function* doEveryOnWrite(clock, pacing, db, pass, busy = () => false) {
29
+ const idle = pacing.idleTocks ?? PROTOCOL_IDLE_TOCKS;
30
+ let seen = -1;
31
+ for (let tock = 0;; tock++) {
32
+ if (tock % idle === 0 || db.writeActivity !== seen || busy()) {
33
+ try {
34
+ yield* pass(tock);
35
+ }
36
+ finally {
37
+ seen = db.writeActivity;
38
+ }
39
+ }
40
+ yield* waitOnClock(clock, pacing.tockMs);
41
+ }
42
+ }
@@ -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 {