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
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Agent side of relay mode: refresh the agent's own view of a subject when a served edge asks about it.
3
+ *
4
+ * An edge's only view of third-party key state is its agent's: every edge query is addressed to the agent, which
5
+ * answers from its own store. That store may be stale for an identifier the agent has no reason of its own to
6
+ * follow. So when a relaying agent receives a `logs` or `ksn` query from a sender it serves about a subject that is
7
+ * not one of its own, it also issues its own ordinary `logs` query for that subject, as an ordinary `query` cue its
8
+ * own querier consumes. The agent is a forwarder, so that query goes directly to the subject's controller, agent or
9
+ * witness endpoint. This is on demand only, as KERIA queries for its Signify clients: no watching, no subscription
10
+ * list, no new store.
11
+ *
12
+ * What the edge sees: the answer to the query that caused the refresh comes from what the agent held then, unless
13
+ * the query names a sequence number or anchor the agent lacks, in which case the agent escrows it (query-not-found)
14
+ * and answers once the refresh lands. A later edge query returns the refreshed state.
15
+ */
16
+ import { Ilks } from "cesr-ts";
17
+ import { Kever } from "../../core/kel/kever.js";
18
+ import { servingHost } from "../mailbox/forward-handler.js";
19
+ import { queryEnvelopeFromDispatch } from "../protocol/parsering.js";
20
+ import { addressableAttesters } from "./witness-inquisitor.js";
21
+ /**
22
+ * The requester of a query whose attached signatures satisfy its current signing threshold, or null.
23
+ *
24
+ * `logs` and `ksn` answers are public, so the KEL query path does not authenticate the requester (nor does KERIpy).
25
+ * A refresh spends the agent's own traffic on the requester's behalf, so it needs the requester proven: a
26
+ * transferable source whose KEL the agent holds and whose current keys signed this query to threshold.
27
+ *
28
+ * @param hby Borrowed accepted-state store holding the requester's KEL.
29
+ * @param query Normalized query envelope with its requester source and indexed signatures.
30
+ * @returns The authenticated requester AID, or null.
31
+ */
32
+ function authenticatedRequester(hby, query) {
33
+ const source = query.source?.qb64;
34
+ if (!source)
35
+ return null;
36
+ const kever = hby.db.getKever(source);
37
+ if (!kever?.tholder)
38
+ return null;
39
+ const verified = Kever.verifyIndexedSignatures(query.serder.raw, query.sigers ?? [], kever.verfers);
40
+ return kever.tholder.satisfy(verified.indices) ? source : null;
41
+ }
42
+ /**
43
+ * Decide whether one processed query makes a relaying host refresh its view of the subject, and return the query cue
44
+ * that does it.
45
+ *
46
+ * Every condition must hold:
47
+ * - the message is a remote (not local) `logs` or `ksn` query the KEL owner accepted or escrowed;
48
+ * - its subject `q.i` is not a prefix of this Habery;
49
+ * - its requester is authenticated (see {@link authenticatedRequester}) and served by a local host under the shared
50
+ * `servingHost` rule (an accepted mailbox or agent end role naming the host);
51
+ * - the host can address an attester for the subject now ({@link addressableAttesters}: one with an accepted
52
+ * location), so the request is sent on the querier's next pass instead of waiting in its queue.
53
+ *
54
+ * The cue is `{ route: "logs", q: {} }` for the subject, signed by the serving host: one full replay from the
55
+ * attester covers both routes, since `ksn` is answered from the same key state. A `tels` query is not refreshed:
56
+ * its wire body names the registry and credential but not the issuer the querier selects attesters by.
57
+ *
58
+ * @param hby Borrowed habitats and accepted-state store of the relaying host.
59
+ * @param result Processed envelope and the decision its owner made.
60
+ * @param mailboxAid Receiving hosted endpoint AID, or null when the request named none.
61
+ * @returns The refresh query cue, or null when this query refreshes nothing.
62
+ */
63
+ export function relayRefreshCue(hby, result, mailboxAid) {
64
+ const { envelope, decision } = result;
65
+ const serder = envelope.serder;
66
+ if (envelope.local || serder.ilk !== Ilks.qry || (serder.route !== "logs" && serder.route !== "ksn"))
67
+ return null;
68
+ if (!decision || (decision.kind !== "accept" && decision.kind !== "escrow"))
69
+ return null;
70
+ const subject = serder.ked?.q?.i;
71
+ if (typeof subject !== "string" || hby.db.prefixes.has(subject))
72
+ return null;
73
+ const requester = authenticatedRequester(hby, queryEnvelopeFromDispatch(envelope));
74
+ if (!requester)
75
+ return null;
76
+ const host = servingHost(hby, { sender: requester, origin: null }, mailboxAid);
77
+ const hab = host ? hby.habs.get(host) : undefined;
78
+ if (!hab || addressableAttesters(hab, subject).length === 0)
79
+ return null;
80
+ return { kin: "query", pre: subject, route: "logs", q: {}, signer: hab.pre };
81
+ }
@@ -7,22 +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";
19
- import { runtimeTurn } from "../runtime/runtime-turn.js";
23
+ import { relayEndpoint, relayRefusal } from "../delivery/relaying.js";
24
+ import { PROTOCOL_TOCK_MS } from "../runtime/doing.js";
25
+ import { waitOnClock } from "../runtime/workflow-contract.js";
20
26
  import { CesrHttpRefusal } from "../transport/cesr-egress.js";
27
+ import { isRequestableHttpUrl } from "../transport/http-client.js";
21
28
  import { HTTPMessenger } from "../transport/http-messenger.js";
29
+ import { messengerUrl } from "../transport/messengering.js";
22
30
  import { requireTcpDialer } from "../transport/tcp-dialer.js";
23
31
  import { TransportError } from "../transport/transport-error.js";
24
32
  /** First eligible role used by KERIpy WitnessInquisitor before random endpoint selection. */
25
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;
26
40
  /**
27
41
  * Copy only the owned query-body level without erasing its route/payload relationship.
28
42
  *
@@ -40,6 +54,22 @@ function copyQuery(instruction) {
40
54
  return { route: instruction.route, q: { ...instruction.q } };
41
55
  }
42
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
+ }
43
73
  /**
44
74
  * Return the sole local habitat when exactly one exists.
45
75
  *
@@ -55,12 +85,47 @@ function soleHab(hby) {
55
85
  const first = habitats.next();
56
86
  return first.done || !habitats.next().done ? null : first.value;
57
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
+ }
58
123
  /**
59
124
  * Resolve the actual transport URL for one query destination AID.
60
125
  *
61
126
  * An explicit witness uses its own location records. Ordinary queries use only
62
127
  * the selected controller, agent or witness endpoint; mailbox providers are not
63
- * query attesters. Missing/unsupported locations do not promote another endpoint.
128
+ * query attesters. Missing, unsupported or unusable locations do not promote another endpoint.
64
129
  *
65
130
  * @param hab Local habitat used to construct/sign this output; stores remain borrowed.
66
131
  * @param queriedPre Subject AID whose state is requested.
@@ -69,18 +134,27 @@ function soleHab(hby) {
69
134
  * @returns Preferred endpoint URL, or null if no permitted route exists.
70
135
  */
71
136
  function resolveQueryDestinationUrl(hab, queriedPre, destination, { explicitWitness = false, } = {}) {
72
- if (explicitWitness) {
73
- const urls = hab.fetchUrls(destination);
74
- return urls[Schemes.https] ?? urls[Schemes.http] ?? urls[Schemes.tcp] ?? null;
75
- }
137
+ if (explicitWitness)
138
+ return preferredLocation(hab.fetchUrls(destination));
76
139
  const ends = hab.endsFor(queriedPre);
77
140
  for (const role of [Roles.controller, Roles.agent, Roles.witness]) {
78
141
  const urls = ends[role]?.[destination];
79
142
  if (urls)
80
- return urls[Schemes.https] ?? urls[Schemes.http] ?? urls[Schemes.tcp] ?? null;
143
+ return preferredLocation(urls);
81
144
  }
82
145
  return null;
83
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
+ }
84
158
  /**
85
159
  * KERIpy-shaped query sender, independent from semantic query completion.
86
160
  *
@@ -108,9 +182,10 @@ export class WitnessInquisitor {
108
182
  * @param protocol Borrowed same-database processor for incoming query replies.
109
183
  * @param http Borrowed HTTP implementation selected by composition.
110
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.
111
186
  * @param hab Optional default local signer; null requires an unambiguous sole habitat.
112
187
  */
113
- constructor(hby, protocol, http, clock, hab = null) {
188
+ constructor(hby, protocol, http, clock, relay, hab = null) {
114
189
  /** Borrowed local signers and accepted-state stores. */
115
190
  Object.defineProperty(this, "hby", {
116
191
  enumerable: true,
@@ -181,12 +256,20 @@ export class WitnessInquisitor {
181
256
  writable: true,
182
257
  value: void 0
183
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
+ });
184
266
  if (protocol.kvy.db !== hby.db)
185
267
  throw new RangeError("Query protocol must use the same database");
186
268
  this.hby = hby;
187
269
  this.protocol = protocol;
188
270
  this.http = http;
189
271
  this.clock = clock;
272
+ this.relay = relay;
190
273
  this.hab = hab;
191
274
  }
192
275
  /**
@@ -197,6 +280,17 @@ export class WitnessInquisitor {
197
280
  configure({ hab = null } = {}) {
198
281
  this.hab = hab;
199
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
+ }
200
294
  /** Deferred instructions and active sends are this sender's only pending work. */
201
295
  hasPendingWork() {
202
296
  return !this.requests.empty || this.hasPendingDelivery;
@@ -214,6 +308,7 @@ export class WitnessInquisitor {
214
308
  ...request,
215
309
  ...copyQuery(request),
216
310
  wits: request.wits ? [...request.wits] : undefined,
311
+ unaddressedPasses: 0,
217
312
  });
218
313
  }
219
314
  /**
@@ -231,6 +326,27 @@ export class WitnessInquisitor {
231
326
  const selected = { ...request, signer: request.signer ?? hab?.pre };
232
327
  this.enqueue(selected);
233
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
+ }
234
350
  /** Include actual query I/O in convergence; pending instructions have their own queue. */
235
351
  get hasPendingDelivery() {
236
352
  return this.delivering;
@@ -281,7 +397,16 @@ export class WitnessInquisitor {
281
397
  const packet = self.prepareRequest(request);
282
398
  self.requests.pull();
283
399
  if (!packet) {
284
- 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
+ }
285
410
  continue;
286
411
  }
287
412
  active.signal.throwIfAborted();
@@ -339,12 +464,15 @@ export class WitnessInquisitor {
339
464
  * preparation/cleanup faults terminate the worker. Caller cancellation joins
340
465
  * the current attempt; it does not close the borrowed state or HTTP service.
341
466
  */
342
- *deliverDo() {
467
+ *deliverDo(tockMs = PROTOCOL_TOCK_MS) {
343
468
  while (true) {
344
- const failure = yield* this.deliveryPass();
345
- if (failure)
346
- consoleLogger.warn("Query delivery failed", failure);
347
- yield* runtimeTurn();
469
+ // Fixed tock: a pass only when a request is queued; a request waits at most `tockMs` to start.
470
+ if (!this.requests.empty) {
471
+ const failure = yield* this.deliveryPass();
472
+ if (failure)
473
+ consoleLogger.warn("Query delivery failed", failure);
474
+ }
475
+ yield* waitOnClock(this.clock, tockMs);
348
476
  }
349
477
  }
350
478
  /**
@@ -365,51 +493,73 @@ export class WitnessInquisitor {
365
493
  * Resolve/sign one selected query and introduction immediately before its attempt.
366
494
  *
367
495
  * @param request Deferred unsigned request; nested query values must remain immutable.
368
- * @returns Prepared packet, or null when signer/attester evidence is unresolved.
369
- * @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.
370
504
  */
371
505
  prepareRequest(request) {
372
506
  const hab = this.configuredHab(request.signer);
373
- 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())
374
509
  return null;
375
- const attesters = request.src ? [] : this.attestersFor(request.pre, { signer: hab.pre, wits: request.wits });
376
- const destination = request.src ?? attesters[Math.floor(Math.random() * attesters.length)];
377
- if (!destination)
378
- return null;
379
- const url = resolveQueryDestinationUrl(hab, request.pre, destination, {
380
- 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 }] : [];
381
521
  });
382
- if (!url)
383
- 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;
384
526
  const message = hab.query(request.pre, destination, copyQuery(request));
385
527
  const intro = introduce(hab, destination);
386
528
  return { url, body: concatBytes(intro, message), destination };
387
529
  }
388
530
  /**
389
- * 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.
390
550
  *
391
551
  * @param pre Subject AID whose accepted endpoint roles are consulted.
392
552
  * @param options Optional local signer AID and remote witness/attester overrides; neither selects a transport implementation.
393
553
  * @returns Eligible attester AIDs, or an empty array for an unknown subject or unresolved signer/role.
394
554
  */
395
555
  attestersFor(pre, { signer, wits } = {}) {
556
+ if (this.relay)
557
+ return [this.relay.aid];
396
558
  if (wits && wits.length > 0) {
397
559
  // Explicit watcher/witness overrides outrank endpoint-role lookup.
398
560
  return [...wits];
399
561
  }
400
562
  const hab = this.configuredHab(signer);
401
- if (!hab || !this.hby.db.getKever(pre))
402
- return [];
403
- const ends = hab.endsFor(pre);
404
- for (const role of QUERY_ROLE_PRIORITY) {
405
- const roleEnds = ends[role];
406
- if (!roleEnds) {
407
- continue;
408
- }
409
- const eids = Object.keys(roleEnds);
410
- if (eids.length > 0)
411
- return eids;
412
- }
413
- return [];
563
+ return hab ? queryAttesters(hab, pre) : [];
414
564
  }
415
565
  }