keri-ts 2.2.4 → 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 (233) 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 +32 -10
  5. package/esm/keri/npm/src/app/bootstrap/oobiery.js +363 -30
  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 +177 -43
  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 +71 -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 +47 -21
  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 +180 -34
  47. package/esm/keri/npm/src/app/runtime/agent-runtime.js +41 -72
  48. package/esm/keri/npm/src/app/runtime/cue-runtime.js +45 -5
  49. package/esm/keri/npm/src/app/runtime/operation-owners.js +26 -56
  50. package/esm/keri/npm/src/app/runtime/workflow-contract.js +7 -4
  51. package/esm/keri/npm/src/app/transport/http-client.js +20 -2
  52. package/esm/keri/npm/src/app/transport/message-source.js +2 -1
  53. package/esm/keri/npm/src/app/transport/messengering.js +39 -8
  54. package/esm/keri/npm/src/app/witness/ingress.js +7 -0
  55. package/esm/keri/npm/src/app/witness/receipt-client.js +43 -16
  56. package/esm/keri/npm/src/app/witness/receipting.js +62 -20
  57. package/esm/keri/npm/src/cli/messaging/multisig.js +4 -3
  58. package/esm/keri/npm/src/core/errors.js +73 -0
  59. package/esm/keri/npm/src/core/kel/delegation-ancestry.js +11 -2
  60. package/esm/keri/npm/src/core/kel/eventing.js +25 -28
  61. package/esm/keri/npm/src/core/kel/kever.js +15 -9
  62. package/esm/keri/npm/src/core/mailbox/kram-policy.js +8 -0
  63. package/esm/keri/npm/src/core/mailbox/kram-pruning.js +25 -6
  64. package/esm/keri/npm/src/core/mailbox/kram-time.js +34 -6
  65. package/esm/keri/npm/src/core/mailbox/kraming.js +58 -2
  66. package/esm/keri/npm/src/core/routing.js +18 -25
  67. package/esm/keri/npm/src/core/scheming.js +12 -5
  68. package/esm/keri/npm/src/core/wire-fields.js +126 -0
  69. package/esm/keri/npm/src/db/basing.js +4 -13
  70. package/esm/keri/npm/src/db/core/indexeddb-persistence.js +3 -2
  71. package/esm/keri/npm/src/db/core/webdber.js +85 -27
  72. package/esm/keri/npm/src/db/mailbox/mailboxing.js +13 -0
  73. package/esm/keri/npm/src/db/subing.js +4 -0
  74. package/esm/keri/npm/src/runtime/index.js +11 -5
  75. package/esm/keri/npm/src/time/mod.js +5 -0
  76. package/esm/keri/npm/src/vdr/credential-tsn-record.js +2 -7
  77. package/esm/keri/npm/src/vdr/eventing.js +28 -19
  78. package/esm/keri/npm/src/vdr/registry-tsn-record.js +3 -13
  79. package/esm/keri/npm/src/version.js +2 -2
  80. package/package.json +1 -1
  81. package/types/_dnt.polyfills.d.ts +9 -9
  82. package/types/_dnt.polyfills.d.ts.map +1 -1
  83. package/types/acdc/registering.d.ts +6 -0
  84. package/types/acdc/registering.d.ts.map +1 -1
  85. package/types/acdc/salting.d.ts.map +1 -1
  86. package/types/app/bootstrap/authenticating.d.ts +12 -2
  87. package/types/app/bootstrap/authenticating.d.ts.map +1 -1
  88. package/types/app/bootstrap/oobiery.d.ts +144 -7
  89. package/types/app/bootstrap/oobiery.d.ts.map +1 -1
  90. package/types/app/credentials/credential-importing.d.ts +8 -0
  91. package/types/app/credentials/credential-importing.d.ts.map +1 -1
  92. package/types/app/credentials/root-replicator.d.ts +19 -8
  93. package/types/app/credentials/root-replicator.d.ts.map +1 -1
  94. package/types/app/credentials/schema-resolving.d.ts.map +1 -1
  95. package/types/app/credentials/tel-publishing.d.ts +4 -2
  96. package/types/app/credentials/tel-publishing.d.ts.map +1 -1
  97. package/types/app/delivery/forwarding.d.ts +69 -5
  98. package/types/app/delivery/forwarding.d.ts.map +1 -1
  99. package/types/app/delivery/relaying.d.ts +144 -0
  100. package/types/app/delivery/relaying.d.ts.map +1 -0
  101. package/types/app/delivery/respondant.d.ts +9 -2
  102. package/types/app/delivery/respondant.d.ts.map +1 -1
  103. package/types/app/exchanges/exchanging.d.ts +36 -0
  104. package/types/app/exchanges/exchanging.d.ts.map +1 -1
  105. package/types/app/identity/delegating.d.ts +6 -0
  106. package/types/app/identity/delegating.d.ts.map +1 -1
  107. package/types/app/identity/habbing.d.ts +106 -19
  108. package/types/app/identity/habbing.d.ts.map +1 -1
  109. package/types/app/identity/keeping.d.ts +54 -19
  110. package/types/app/identity/keeping.d.ts.map +1 -1
  111. package/types/app/identity/retire-identifier.d.ts +24 -0
  112. package/types/app/identity/retire-identifier.d.ts.map +1 -0
  113. package/types/app/ipex/disclosure-replaying.d.ts +61 -0
  114. package/types/app/ipex/disclosure-replaying.d.ts.map +1 -0
  115. package/types/app/ipex/group-ipex.d.ts +60 -14
  116. package/types/app/ipex/group-ipex.d.ts.map +1 -1
  117. package/types/app/ipex/group-issuance.d.ts +48 -6
  118. package/types/app/ipex/group-issuance.d.ts.map +1 -1
  119. package/types/app/ipex/ipex-discloser.d.ts +14 -2
  120. package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
  121. package/types/app/ipex/ipex-grouping.d.ts +23 -2
  122. package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
  123. package/types/app/ipex/ipex-v2.d.ts +5 -2
  124. package/types/app/ipex/ipex-v2.d.ts.map +1 -1
  125. package/types/app/ipex/issuance.d.ts +35 -11
  126. package/types/app/ipex/issuance.d.ts.map +1 -1
  127. package/types/app/ipex/pending-presentations.d.ts +4 -2
  128. package/types/app/ipex/pending-presentations.d.ts.map +1 -1
  129. package/types/app/ipex/presenting.d.ts.map +1 -1
  130. package/types/app/ipex/receive-issuance.d.ts +2 -1
  131. package/types/app/ipex/receive-issuance.d.ts.map +1 -1
  132. package/types/app/ipex/unblinding-workflow.d.ts +4 -1
  133. package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
  134. package/types/app/mailbox/agent-inbox.d.ts +126 -0
  135. package/types/app/mailbox/agent-inbox.d.ts.map +1 -0
  136. package/types/app/mailbox/forward-handler.d.ts +114 -14
  137. package/types/app/mailbox/forward-handler.d.ts.map +1 -1
  138. package/types/app/mailbox/kel-publisher.d.ts +12 -22
  139. package/types/app/mailbox/kel-publisher.d.ts.map +1 -1
  140. package/types/app/mailbox/mailbox-poller.d.ts +40 -24
  141. package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
  142. package/types/app/mailbox/mailboxing.d.ts +19 -5
  143. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  144. package/types/app/multisig/counseling.d.ts +32 -11
  145. package/types/app/multisig/counseling.d.ts.map +1 -1
  146. package/types/app/multisig/group-members.d.ts +17 -1
  147. package/types/app/multisig/group-members.d.ts.map +1 -1
  148. package/types/app/multisig/grouping.d.ts +17 -1
  149. package/types/app/multisig/grouping.d.ts.map +1 -1
  150. package/types/app/multisig/multisig-workflows.d.ts +0 -8
  151. package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
  152. package/types/app/multisig/proposing.d.ts +4 -19
  153. package/types/app/multisig/proposing.d.ts.map +1 -1
  154. package/types/app/multisig/rejoin-group.d.ts +119 -0
  155. package/types/app/multisig/rejoin-group.d.ts.map +1 -0
  156. package/types/app/multisig/replace-member.d.ts +15 -1
  157. package/types/app/multisig/replace-member.d.ts.map +1 -1
  158. package/types/app/protocol/parsering.d.ts +21 -2
  159. package/types/app/protocol/parsering.d.ts.map +1 -1
  160. package/types/app/protocol/protocol-processing.d.ts +25 -5
  161. package/types/app/protocol/protocol-processing.d.ts.map +1 -1
  162. package/types/app/queries/query-replies.d.ts +4 -8
  163. package/types/app/queries/query-replies.d.ts.map +1 -1
  164. package/types/app/queries/relay-refresh.d.ts +26 -0
  165. package/types/app/queries/relay-refresh.d.ts.map +1 -0
  166. package/types/app/queries/witness-inquisitor.d.ts +56 -5
  167. package/types/app/queries/witness-inquisitor.d.ts.map +1 -1
  168. package/types/app/runtime/agent-runtime.d.ts +34 -3
  169. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  170. package/types/app/runtime/cue-runtime.d.ts +15 -3
  171. package/types/app/runtime/cue-runtime.d.ts.map +1 -1
  172. package/types/app/runtime/operation-owners.d.ts +16 -26
  173. package/types/app/runtime/operation-owners.d.ts.map +1 -1
  174. package/types/app/runtime/workflow-contract.d.ts +21 -6
  175. package/types/app/runtime/workflow-contract.d.ts.map +1 -1
  176. package/types/app/transport/http-client.d.ts +9 -0
  177. package/types/app/transport/http-client.d.ts.map +1 -1
  178. package/types/app/transport/message-source.d.ts +6 -3
  179. package/types/app/transport/message-source.d.ts.map +1 -1
  180. package/types/app/transport/messengering.d.ts +10 -1
  181. package/types/app/transport/messengering.d.ts.map +1 -1
  182. package/types/app/witness/ingress.d.ts +1 -0
  183. package/types/app/witness/ingress.d.ts.map +1 -1
  184. package/types/app/witness/receipt-client.d.ts +19 -1
  185. package/types/app/witness/receipt-client.d.ts.map +1 -1
  186. package/types/app/witness/receipting.d.ts +24 -1
  187. package/types/app/witness/receipting.d.ts.map +1 -1
  188. package/types/cli/messaging/multisig.d.ts.map +1 -1
  189. package/types/core/cues.d.ts +53 -1
  190. package/types/core/cues.d.ts.map +1 -1
  191. package/types/core/errors.d.ts +32 -0
  192. package/types/core/errors.d.ts.map +1 -1
  193. package/types/core/kel/delegation-ancestry.d.ts.map +1 -1
  194. package/types/core/kel/eventing.d.ts.map +1 -1
  195. package/types/core/kel/kever-decisions.d.ts +3 -1
  196. package/types/core/kel/kever-decisions.d.ts.map +1 -1
  197. package/types/core/kel/kever.d.ts +7 -1
  198. package/types/core/kel/kever.d.ts.map +1 -1
  199. package/types/core/mailbox/kram-policy.d.ts +6 -0
  200. package/types/core/mailbox/kram-policy.d.ts.map +1 -1
  201. package/types/core/mailbox/kram-pruning.d.ts +5 -1
  202. package/types/core/mailbox/kram-pruning.d.ts.map +1 -1
  203. package/types/core/mailbox/kram-time.d.ts +26 -2
  204. package/types/core/mailbox/kram-time.d.ts.map +1 -1
  205. package/types/core/mailbox/kraming.d.ts +14 -1
  206. package/types/core/mailbox/kraming.d.ts.map +1 -1
  207. package/types/core/routing.d.ts +3 -7
  208. package/types/core/routing.d.ts.map +1 -1
  209. package/types/core/scheming.d.ts.map +1 -1
  210. package/types/core/wire-fields.d.ts +56 -0
  211. package/types/core/wire-fields.d.ts.map +1 -0
  212. package/types/db/basing.d.ts.map +1 -1
  213. package/types/db/core/indexeddb-persistence.d.ts +3 -2
  214. package/types/db/core/indexeddb-persistence.d.ts.map +1 -1
  215. package/types/db/core/webdber.d.ts +32 -6
  216. package/types/db/core/webdber.d.ts.map +1 -1
  217. package/types/db/exchange/exchange-records.d.ts +4 -0
  218. package/types/db/exchange/exchange-records.d.ts.map +1 -1
  219. package/types/db/mailbox/mailboxing.d.ts +10 -1
  220. package/types/db/mailbox/mailboxing.d.ts.map +1 -1
  221. package/types/db/subing.d.ts +2 -0
  222. package/types/db/subing.d.ts.map +1 -1
  223. package/types/runtime/index.d.ts +20 -14
  224. package/types/runtime/index.d.ts.map +1 -1
  225. package/types/time/mod.d.ts +2 -0
  226. package/types/time/mod.d.ts.map +1 -1
  227. package/types/vdr/credential-tsn-record.d.ts.map +1 -1
  228. package/types/vdr/eventing.d.ts.map +1 -1
  229. package/types/vdr/registry-tsn-record.d.ts.map +1 -1
  230. package/types/version.d.ts +2 -2
  231. package/esm/keri/npm/src/app/runtime/signer-conditions.js +0 -51
  232. package/types/app/runtime/signer-conditions.d.ts +0 -32
  233. package/types/app/runtime/signer-conditions.d.ts.map +0 -1
@@ -0,0 +1,232 @@
1
+ import { ValidationError } from "../../core/errors.js";
2
+ import { Roles } from "../../core/roles.js";
3
+ import { fetchEndpointUrls } from "../mailbox/mailboxing.js";
4
+ import { messengerUrl } from "../transport/messengering.js";
5
+ import { TransportError } from "../transport/transport-error.js";
6
+ /** The relay's location is not accepted yet (its OOBI has not resolved); nothing was sent. */
7
+ export class RelayUnresolvedError extends ValidationError {
8
+ constructor() {
9
+ super(...arguments);
10
+ Object.defineProperty(this, "name", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: "RelayUnresolvedError"
15
+ });
16
+ }
17
+ }
18
+ /**
19
+ * The sending identity has not authorized the relay as its `mailbox`; nothing was sent. The agent relays only for a
20
+ * sender that named it its mailbox, so a wrapper sent before that would be dropped there without an answer.
21
+ */
22
+ export class RelayUnauthorizedError extends ValidationError {
23
+ constructor() {
24
+ super(...arguments);
25
+ Object.defineProperty(this, "name", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: "RelayUnauthorizedError"
30
+ });
31
+ }
32
+ }
33
+ /** Refuses a request whose origin is neither the relay's OOBI origin nor an accepted relay location. */
34
+ export class RelayOnlyError extends TransportError {
35
+ constructor() {
36
+ super(...arguments);
37
+ Object.defineProperty(this, "name", {
38
+ enumerable: true,
39
+ configurable: true,
40
+ writable: true,
41
+ value: "RelayOnlyError"
42
+ });
43
+ }
44
+ }
45
+ /**
46
+ * Read the relay's accepted location, as `Hab.endsFor` reads any endpoint's locations.
47
+ *
48
+ * Locations are accepted only from `/loc/scheme` replies verified against the relay's KEL, so a non-null result
49
+ * means the relay's OOBI (or an equivalent introduction) has resolved. A location no messenger can use
50
+ * ({@link messengerUrl}: one the HTTP client refuses, or a TCP URL without a port) reads as unresolved.
51
+ *
52
+ * @param hby Borrowed accepted-state store holding the relay's location replies.
53
+ * @param relay The configured relay agent.
54
+ * @returns The relay's preferred URL and scheme map, or null until a usable location is accepted.
55
+ */
56
+ export function relayEndpoint(hby, relay) {
57
+ const accepted = fetchEndpointUrls(hby, relay.aid);
58
+ const urls = {};
59
+ if (accepted.https)
60
+ urls.https = accepted.https;
61
+ if (accepted.http)
62
+ urls.http = accepted.http;
63
+ if (accepted.tcp)
64
+ urls.tcp = accepted.tcp;
65
+ const url = messengerUrl(urls);
66
+ return url ? { eid: relay.aid, url, urls } : null;
67
+ }
68
+ /**
69
+ * Whether `pre` has authorized the relay as its mailbox: the accepted row `ends.[pre, mailbox, relay.aid]` is
70
+ * allowed. This is the row the agent reads to decide that it serves the sender, and the edge holds it because it
71
+ * signed and accepted the end-role reply itself (`addMailbox`).
72
+ *
73
+ * @param hby Borrowed accepted-state store holding end-role replies.
74
+ * @param pre Local identifier that signs what goes to the relay (for a group, its local member).
75
+ * @param relay The configured relay agent.
76
+ * @returns True when the mailbox role naming the relay is accepted and allowed.
77
+ */
78
+ export function relayAuthorized(hby, pre, relay) {
79
+ return hby.db.ends.get([pre, Roles.mailbox, relay.aid])?.allowed === true;
80
+ }
81
+ /**
82
+ * Decide whether the relay can carry a message signed by `signer` now, before anything is prepared or sent.
83
+ *
84
+ * The relay must have an accepted location. A message it is to forward also needs `signer` to have authorized it as
85
+ * mailbox; a message addressed to the relay itself (`signer` null) needs no authorization, as the relay consumes it
86
+ * rather than forwarding it. Onboarding needs neither path: the mailbox add is posted to the relay's `/mailboxes`
87
+ * admin resource and carries the KEL that introduces the edge.
88
+ *
89
+ * @param hby Borrowed accepted-state store holding the relay's location and the signer's end roles.
90
+ * @param relay The configured relay agent.
91
+ * @param signer Local identifier that signs the forwarding wrapper or query; null for a message to the relay itself.
92
+ * @returns The refusal to report, or null when the relay can carry the message.
93
+ */
94
+ export function relayRefusal(hby, relay, signer) {
95
+ if (!relayEndpoint(hby, relay)) {
96
+ return new RelayUnresolvedError(`Relay ${relay.aid} has no accepted location; its OOBI has not resolved.`);
97
+ }
98
+ if (signer !== null && !relayAuthorized(hby, signer, relay)) {
99
+ return new RelayUnauthorizedError(`${signer} has not authorized relay ${relay.aid} as its mailbox.`);
100
+ }
101
+ return null;
102
+ }
103
+ /**
104
+ * Refuse a relay nothing can be composed over: its AID must be non-empty and its OOBI an absolute HTTP(S) URL,
105
+ * because the egress guard admits the OOBI's origin and the edge resolves that OOBI before anything else. A
106
+ * composition root (the guard, a wallet) calls it before it opens or sends anything.
107
+ *
108
+ * @param relay Candidate relay, possibly from untyped embedder input.
109
+ * @throws ValidationError when the AID is empty or the OOBI is not an absolute HTTP(S) URL.
110
+ */
111
+ export function assertRelay(relay) {
112
+ const aid = relay.aid, oobi = relay.oobi;
113
+ const origin = typeof oobi === "string" ? originOf(oobi) : null;
114
+ if (typeof aid !== "string" || !aid || origin === null || !/^https?:\/\//.test(origin)) {
115
+ throw new ValidationError(`Relay requires an AID and an absolute HTTP(S) OOBI URL; got ${String(oobi)}.`);
116
+ }
117
+ }
118
+ /**
119
+ * The onboarding steps still missing for delivery through the relay, derived from accepted state on every call;
120
+ * nothing records progress. Order is the order to take them: resolve the relay's OOBI, then authorize each
121
+ * single-signature identifier, then each group (whose inception needs its members to reach each other first).
122
+ *
123
+ * Only identifiers with accepted key state are listed: an identifier that has not completed its inception cannot
124
+ * sign an end-role reply yet. The resolution step comes from {@link relayRefusal}; the authorization steps from
125
+ * {@link relayAuthorized}, the row both the edge's senders and the relay read.
126
+ *
127
+ * @param hby Borrowed accepted-state store holding the relay's location and the local end roles.
128
+ * @param relay The configured relay agent.
129
+ * @returns The missing steps; empty when every accepted local identifier can send and receive through the relay.
130
+ */
131
+ export function relayOnboarding(hby, relay) {
132
+ const steps = [];
133
+ if (relayRefusal(hby, relay, null))
134
+ steps.push({ step: "resolve-oobi", oobi: relay.oobi });
135
+ const groups = [];
136
+ for (const hab of hby.habs.values()) {
137
+ if (!hab.accepted || relayAuthorized(hby, hab.pre, relay))
138
+ continue;
139
+ if (hby.db.getHab(hab.pre)?.mid)
140
+ groups.push({ step: "authorize-group", pre: hab.pre });
141
+ else
142
+ steps.push({ step: "authorize-member", pre: hab.pre });
143
+ }
144
+ return [...steps, ...groups];
145
+ }
146
+ /**
147
+ * Scheme and authority of a URL, comparable across `http(s):` and `tcp:` (whose WHATWG `origin` is `"null"`).
148
+ *
149
+ * @param url Candidate absolute URL.
150
+ * @returns `scheme://host[:port]`, or null when the URL does not parse.
151
+ */
152
+ function originOf(url) {
153
+ try {
154
+ const parsed = new URL(url);
155
+ return parsed.host ? `${parsed.protocol}//${parsed.host}` : null;
156
+ }
157
+ catch {
158
+ return null;
159
+ }
160
+ }
161
+ /**
162
+ * Whether a URL addresses the relay: its origin is the relay's own OOBI origin or the origin of a currently accepted
163
+ * relay location. The accepted locations are read on every call, so a location accepted (or replaced) later is
164
+ * honored at once. The egress guard refuses everything else, and the edge's Oobiery fetches such a URL directly
165
+ * while it resolves any other origin's OOBI through the relay.
166
+ *
167
+ * @param hby Borrowed accepted-state store holding the relay's location replies.
168
+ * @param relay The configured relay agent.
169
+ * @param url Candidate absolute URL.
170
+ * @returns True when the URL's origin is the relay's.
171
+ */
172
+ export function isRelayOrigin(hby, relay, url) {
173
+ const origin = originOf(url);
174
+ if (origin === null)
175
+ return false;
176
+ if (origin === originOf(relay.oobi))
177
+ return true;
178
+ return Object.values(fetchEndpointUrls(hby, relay.aid)).some((location) => originOf(location) === origin);
179
+ }
180
+ /**
181
+ * Refuse egress to anything but the relay (see {@link isRelayOrigin}).
182
+ *
183
+ * @param hby Borrowed accepted-state store holding the relay's location replies.
184
+ * @param relay The configured relay agent.
185
+ * @param url Requested URL.
186
+ * @throws RelayOnlyError when the URL's origin is not the relay's.
187
+ */
188
+ function requireRelayOrigin(hby, relay, url) {
189
+ if (isRelayOrigin(hby, relay, url))
190
+ return;
191
+ throw new RelayOnlyError(`Relay-only egress refused ${originOf(url) ?? "an unparseable URL"}; relay is ${relay.aid}.`);
192
+ }
193
+ /**
194
+ * Wrap an HTTP client (and its TCP dialer, when present) so it reaches only the relay.
195
+ *
196
+ * The guard is the edge's fail-closed boundary: egress kinds not yet routed through the relay (delegation, TEL
197
+ * receipting, witness registration, well-known authentication) fail as `TransportError` instead of going direct.
198
+ * It allows the origin of the relay's OOBI and the origins of the relay's currently accepted locations.
199
+ *
200
+ * @param inner Borrowed client that performs permitted requests.
201
+ * @param hby Borrowed accepted-state store holding the relay's location replies.
202
+ * @param relay The configured relay agent.
203
+ * @returns A client with the same contract whose refused requests throw RelayOnlyError before any I/O.
204
+ * @throws ValidationError when the relay's OOBI is not an absolute HTTP(S) URL.
205
+ */
206
+ export function relayOnlyHttpClient(inner, hby, relay) {
207
+ assertRelay(relay);
208
+ const dialer = inner.tcp;
209
+ const tcp = dialer
210
+ ? {
211
+ messenger(url, clock, protocol) {
212
+ requireRelayOrigin(hby, relay, url);
213
+ return dialer.messenger(url, clock, protocol);
214
+ },
215
+ streamMessenger(url, clock, payload) {
216
+ requireRelayOrigin(hby, relay, url);
217
+ return dialer.streamMessenger(url, clock, payload);
218
+ },
219
+ }
220
+ : undefined;
221
+ return {
222
+ ...(tcp ? { tcp } : {}),
223
+ *request(url, init) {
224
+ requireRelayOrigin(hby, relay, url);
225
+ return yield* inner.request(url, init);
226
+ },
227
+ *open(url, init) {
228
+ requireRelayOrigin(hby, relay, url);
229
+ return yield* inner.open(url, init);
230
+ },
231
+ };
232
+ }
@@ -46,26 +46,29 @@ export class Respondant {
46
46
  * @param hab Local habitat used to construct/sign this output; stores remain borrowed.
47
47
  * @param cues Source Deck; only its current head is interpreted, and removed after full publication.
48
48
  * @param hint Optional local sender hint used when cue provenance does not resolve a habitat.
49
- * @returns True after complete admission/removal; false if the head has no routable responder output.
49
+ * @returns What became of the head; see {@link CueOutcome}. Only `taken` removes it.
50
50
  * @throws Construction/admission failures leave the source cue present.
51
51
  */
52
52
  enqueueCue(hab, cues, hint) {
53
53
  this.postman.ensureOpen();
54
54
  const cue = cues.peekHead();
55
55
  if (!cue)
56
- return false;
56
+ return "unrouted";
57
57
  const dest = recipientForCue(cue), topic = topicForCue(cue);
58
58
  const src = senderHabForCue(this.hby, cue, hint);
59
59
  if (this.nothingToSend(cue, dest, topic, src)) {
60
60
  cues.pull();
61
- return true;
61
+ return "taken";
62
62
  }
63
63
  if (!dest || !topic || !src)
64
- return false;
64
+ return "unrouted";
65
+ // A replay carries already-signed messages; receipts, witness receipts and replies are signed here.
66
+ if (cue.kin !== "replay" && !hab.canSign())
67
+ return "unsignable";
65
68
  const msgs = [...hab.processCuesIter([cue])];
66
69
  this.postman.sendAll(msgs.map((msg) => ({ src: src.pre, dest, topic, msg, replies: true })));
67
70
  cues.pull();
68
- return true;
71
+ return "taken";
69
72
  }
70
73
  /**
71
74
  * Whether a receipt cue has nothing to send to. A receipt goes to the controller of the event it receipts, so it
@@ -42,7 +42,7 @@ export function createExchangeMessage(hab, args) {
42
42
  */
43
43
  export function sendExchangeMessage(hby, hab, http, clock, args) {
44
44
  return call(function* () {
45
- const postman = new Poster(hby, null, null, http, clock);
45
+ const postman = new Poster(hby, null, null, http, clock, null);
46
46
  yield* resource(function* (provide) {
47
47
  try {
48
48
  yield* provide();
@@ -8,7 +8,9 @@ import { verifyExchangeProofs, } from "../../core/exchange/exchange-proofing.js"
8
8
  import { nestedIpexRoute, supportsNestedExchange } from "../../core/exchange/protocol-exchanging.js";
9
9
  import { Kever } from "../../core/kel/kever.js";
10
10
  import { consoleLogger } from "../../core/logger.js";
11
+ import { exchangeWindow } from "../../core/mailbox/kram-policy.js";
11
12
  import { messageAttachmentGroups, serializeMessage } from "../../core/protocol-serialization.js";
13
+ import { readPrefixer } from "../../core/wire-fields.js";
12
14
  import { hasNests, loadNests, prepareNests, publishNests } from "../../db/exchange/exchange-nests.js";
13
15
  import { encodeDateTimeToDater, makeNowIso8601, timestampMicros } from "../../time/mod.js";
14
16
  import { groupMemberHasKey } from "../multisig/group-members.js";
@@ -115,6 +117,17 @@ export class Exchanger {
115
117
  writable: true,
116
118
  value: void 0
117
119
  });
120
+ /**
121
+ * Lifetimes of the pending exchanges this process's escrow sweep ejected, by SAID. Ejection removes the only
122
+ * stored body, so this is all that still says when such an exchange expired ({@link exchangeExpiry}). Lost on
123
+ * restart, by design: no store keeps ejected exchanges.
124
+ */
125
+ Object.defineProperty(this, "ejected", {
126
+ enumerable: true,
127
+ configurable: true,
128
+ writable: true,
129
+ value: new Map()
130
+ });
118
131
  this.now = now;
119
132
  this.hby = hby;
120
133
  this.cues = cues ?? new Deck();
@@ -203,6 +216,9 @@ export class Exchanger {
203
216
  }
204
217
  if (!args.serder.pre || !args.serder.said)
205
218
  return reject("Exchange message is missing sender or SAID.");
219
+ // Proof resolution and every later sender read decode this prefix; a remote value is decided here.
220
+ if (!readPrefixer(args.serder.pre))
221
+ return reject(`Exchange sender ${args.serder.pre} is not a valid prefix.`);
206
222
  if (args.receiverTime !== undefined && timestampMicros(args.receiverTime) === null) {
207
223
  throw new Error("Invalid exchange receiver time");
208
224
  }
@@ -328,6 +344,8 @@ export class Exchanger {
328
344
  const previous = this.hby.db.exchangeMeta.get([said]);
329
345
  const receiverTime = storedReceiverTime(previous) ?? args.receiverTime;
330
346
  const mailboxAid = args.mailboxAid ?? previous?.mailboxAid ?? null;
347
+ // Once imported as retained history an exchange stays so; a retry rebuilds its input from the stores.
348
+ const retained = previous?.retained === true || args.retained === true;
331
349
  const nests = args.nests?.length ? args.nests : loadNests(this.hby.db, said);
332
350
  if (nests.length
333
351
  && (!handler?.acceptsNests || !supportsNestedExchange(args.serder.route ?? "", args.serder.pvrsn.major))) {
@@ -413,10 +431,11 @@ export class Exchanger {
413
431
  mailboxAid,
414
432
  ...(receiverTime !== undefined ? { receiverTime } : {}),
415
433
  gvrsn: previous?.gvrsn ?? args.gvrsn ?? args.serder.gvrsn ?? args.serder.pvrsn,
434
+ ...(retained ? { retained } : {}),
416
435
  };
417
436
  // A retry that learns nothing new leaves the stored record as it is rather than committing it again.
418
437
  if (!previous || previous.mailboxAid !== meta.mailboxAid || previous.receiverTime !== meta.receiverTime
419
- || JSON.stringify(previous.gvrsn) !== JSON.stringify(meta.gvrsn)) {
438
+ || JSON.stringify(previous.gvrsn) !== JSON.stringify(meta.gvrsn) || (previous.retained === true) !== retained) {
420
439
  this.hby.db.exchangeMeta.pin([said], meta);
421
440
  }
422
441
  if (pending) {
@@ -430,7 +449,7 @@ export class Exchanger {
430
449
  }
431
450
  return { decision: pending, cues };
432
451
  }
433
- return { decision: { kind: "accept", said }, cues, handling };
452
+ return { decision: { kind: "accept", said }, cues, handling: retained ? { ...handling, retained } : handling };
434
453
  }
435
454
  /** Retry escrows using ordinary LMDB evidence and retained mailbox provenance. */
436
455
  processEscrows() {
@@ -481,13 +500,18 @@ export class Exchanger {
481
500
  }
482
501
  if (schema && new Diger({ qb64: keys[1] }).qb64 !== keys[1])
483
502
  throw new Error("Corrupt schema escrow dependency.");
503
+ // Read before ejection removes the metadata that says whether the exchange is retained history.
504
+ const deadline = this.lifetime(serder);
484
505
  const expired = yield* call(() => this.expirePending(serder));
485
506
  if (expired) {
507
+ if (deadline)
508
+ this.ejected.set(said, deadline);
486
509
  consoleLogger.warn("Exchanger ejected exchange escrow", {
487
510
  said,
488
511
  pre: serder.pre,
489
512
  route: serder.route,
490
513
  ...expired,
514
+ ...(deadline && { deadline }),
491
515
  });
492
516
  continue;
493
517
  }
@@ -570,11 +594,8 @@ export class Exchanger {
570
594
  }
571
595
  /** Original accepted KRAM lifetime; partial-escrow timestamps never authorize live auxiliary work. */
572
596
  exchangeDeadline(serder) {
573
- const said = serder.said, sender = serder.pre;
574
- const xid = serder.ilk === "xip" ? said : serder.sad?.x;
575
- if (!said || !sender || typeof xid !== "string" || !xid)
576
- return null;
577
- const cache = this.hby.db.tmsc.get([sender, xid, said]);
597
+ const key = transactionCacheKey(serder);
598
+ const cache = key && this.hby.db.tmsc.get(key);
578
599
  if (!cache?.rdt)
579
600
  return null;
580
601
  const opener = timestampMicros(cache.xdt);
@@ -583,6 +604,36 @@ export class Exchanger {
583
604
  }
584
605
  return this.deadlineDater(opener + BigInt(cache.xl) * 1000n).dts;
585
606
  }
607
+ /**
608
+ * When the exchange `said` expires, or expired: its KRAM lifetime, opener plus exchange window. Unlike
609
+ * {@link exchangeDeadline} this keeps answering after KRAM prunes the cache row (at opener + `pxl`, which the
610
+ * default windows make the deadline itself) and after the escrow sweep ejects an expired pending exchange, so a
611
+ * caller can say why the exchange is gone. A pure read. Null when KRAM is off (no exchange window), while KRAM
612
+ * holds the message without accepting it (an incomplete multi-signature message), for a pruned message that
613
+ * continues another message's thread, for an ejected exchange after a restart, and for retained history a local
614
+ * import admitted without KRAM ({@link ExchangeInput.retained}), which never had a window here.
615
+ */
616
+ exchangeExpiry(said) {
617
+ const db = this.hby.db, serder = db.exns.get([said]) ?? db.epse.get([said]);
618
+ return serder?.said === said ? this.lifetime(serder) : this.ejected.get(said) ?? null;
619
+ }
620
+ /**
621
+ * The accepted lifetime while KRAM keeps the cache row; once the row is pruned, an exchange that opens its own
622
+ * thread (an `xip`, or an `exn` with no prior, the rule KRAM opened it with) is its own opener, so its stamp
623
+ * plus the exchange window configured now gives the deadline again. Reads no escrow row, so it also works on
624
+ * the body of an exchange that is being ejected. Retained history never had a cache row here, so its stamp gives
625
+ * no deadline: it would only report an old exchange expired for its age.
626
+ */
627
+ lifetime(serder) {
628
+ const accepted = this.exchangeDeadline(serder), key = transactionCacheKey(serder);
629
+ if (accepted || !key || this.hby.db.tmsc.get(key))
630
+ return accepted;
631
+ if (this.hby.db.exchangeMeta.get([serder.said])?.retained === true)
632
+ return null;
633
+ const opener = serder.ilk === "xip" || !serder.ked?.p ? timestampMicros(serder.stamp) : null;
634
+ const window = exchangeWindow(this.hby.db, serder.ilk, serder.route);
635
+ return opener === null || window === null ? null : this.deadlineDater(opener + BigInt(window) * 1000n).dts;
636
+ }
586
637
  /**
587
638
  * Rehydrate authenticated retained factors without route handling or writes.
588
639
  * Pending-only access also enforces the original deadline with this owner's
@@ -1104,8 +1155,7 @@ export class Exchanger {
1104
1155
  if (prior && timestampMicros(prior.dts) === null)
1105
1156
  throw new Error("Corrupt missing-schema deadline.");
1106
1157
  if (!prior) {
1107
- const xid = serder.ilk === "xip" ? said : serder.sad.x;
1108
- const cache = typeof xid === "string" && xid ? this.hby.db.tmsc.get([serder.pre, xid, said]) : null;
1158
+ const key = transactionCacheKey(serder), cache = key ? this.hby.db.tmsc.get(key) : null;
1109
1159
  const opener = cache && timestampMicros(cache.xdt);
1110
1160
  if (cache && (opener === null || opener === undefined || !Number.isSafeInteger(cache.pxl) || cache.pxl < 0)) {
1111
1161
  throw new Error("Corrupt missing-schema transaction lifetime.");
@@ -1324,6 +1374,12 @@ export class Exchanger {
1324
1374
  this.hby.db.emsd?.rem([said]);
1325
1375
  }
1326
1376
  }
1377
+ /** The KRAM transaction cache key `[sender, xid, said]` of an exchange; null for a message outside a transaction. */
1378
+ function transactionCacheKey(serder) {
1379
+ const said = serder.said, sender = serder.pre;
1380
+ const xid = serder.ilk === "xip" ? said : serder.sad?.x;
1381
+ return said && sender && typeof xid === "string" && xid ? [sender, xid, said] : null;
1382
+ }
1327
1383
  function normalizeAttachments(ptds) {
1328
1384
  return ptds.map((group) => ({
1329
1385
  raw: group.raw.slice(),
@@ -19,11 +19,21 @@ import { Schemes } from "../../core/schemes.js";
19
19
  import { dgKey } from "../../db/core/keys.js";
20
20
  import { createExchangeMessage } from "../exchanges/exchange-messaging.js";
21
21
  import { isLocalGroupHab, localGroupMember } from "../multisig/group-members.js";
22
- import { sendWitnessMessage } from "../witness/receipt-client.js";
22
+ import { deliverWitnessMessage } from "../witness/receipt-client.js";
23
23
  import { eventPayloadMessage, eventReplayMessage } from "./habbing.js";
24
24
  export const DELEGATE_REQUEST_ROUTE = "/delegate/request";
25
25
  /** Runtime passes to wait between repeated delegator-witness anchor queries. */
26
26
  const DELEGATION_ANCHOR_QUERY_RETRY_PASSES = 8;
27
+ /** Keep the delegation request row after the delegator could not take it; the next pass sends it again. */
28
+ function retainedForDelegator(pre, said, error) {
29
+ return {
30
+ kind: "keep",
31
+ pre,
32
+ said,
33
+ phase: "waitingWitnessReceipts",
34
+ reason: `Delegation request is not delivered yet: ${error instanceof Error ? error.message : String(error)}`,
35
+ };
36
+ }
27
37
  function eventKey(serder) {
28
38
  const pre = serder.pre;
29
39
  const snh = serder.snh;
@@ -410,6 +420,20 @@ export class Anchorer {
410
420
  this.http = http;
411
421
  this.clock = clock;
412
422
  }
423
+ /** Workflow ids (`pre:snh`) of `pre`'s delegated events still in the `dpwe.`, `dune.` or `dpub.` phase. */
424
+ pendingOf(pre) {
425
+ const ids = new Set();
426
+ const { dpwe, dune, dpub } = this.hby.db;
427
+ for (const phase of [dpwe, dune, dpub]) {
428
+ for (const [, serder] of phase.getTopItemIter([pre], { topive: true }))
429
+ ids.add(workflowId(serder));
430
+ }
431
+ return [...ids];
432
+ }
433
+ /** Workflow ids whose pinned communication habitat (the proxy that sends the request) is `pre`. */
434
+ proxiedBy(pre) {
435
+ return [...this.communicationHabPins].filter(([, pinned]) => pinned === pre).map(([id]) => id);
436
+ }
413
437
  /**
414
438
  * Start one delegated event in witness-receipt escrow and replace any previous local proxy pin.
415
439
  *
@@ -565,6 +589,16 @@ export class Anchorer {
565
589
  reason: "Delegate witness receipts are not converged yet.",
566
590
  };
567
591
  }
592
+ // The request is signed by the communication habitat; one that cannot sign yet keeps the row for a later pass.
593
+ if (!communicationHab.canSign()) {
594
+ return {
595
+ kind: "keep",
596
+ pre,
597
+ said,
598
+ phase: "waitingWitnessReceipts",
599
+ reason: `Communication habitat ${communicationHab.pre} cannot sign yet; its signer is not held.`,
600
+ };
601
+ }
568
602
  // Do not ask the delegator to approve until the delegate's own witnesses
569
603
  // have receipted the event. That preserves the KERIpy approval ordering
570
604
  // and avoids anchoring an event the delegate cannot yet complete.
@@ -588,7 +622,7 @@ export class Anchorer {
588
622
  // Admission alone cannot release the next send or the delegation phase transition.
589
623
  const requested = yield* postman.wait(request);
590
624
  if (requested.kin === "failed")
591
- throw requested.error;
625
+ return retainedForDelegator(pre, said, requested.error);
592
626
  const replay = postman.send({
593
627
  src: communicationHab.pre,
594
628
  dest: delpre,
@@ -597,7 +631,7 @@ export class Anchorer {
597
631
  }, { notify: true });
598
632
  const replayed = yield* postman.wait(replay);
599
633
  if (replayed.kin === "failed")
600
- throw replayed.error;
634
+ return retainedForDelegator(pre, said, replayed.error);
601
635
  // KERIpy issues the first delegator-witness anchor query in the same
602
636
  // publish phase that forwards the delegation request and raw event bytes.
603
637
  // Keep that ordering here so delegate-side approval discovery does not
@@ -657,8 +691,8 @@ export class Anchorer {
657
691
  // event is learned locally; pinning `aess.` is what lets delegated unescrow
658
692
  // and later reopen paths prove the authorization.
659
693
  pinAuthorizingSeal(this.hby, serder, delegating);
660
- this.hby.db.dune.rem(keys);
661
694
  if ((hab.kever?.wits.length ?? 0) === 0) {
695
+ this.hby.db.dune.rem(keys);
662
696
  this.hby.db.cdel.appendOn(pre, completionDiger(serder));
663
697
  this.clearWorkflowPins(serder);
664
698
  return {
@@ -669,7 +703,18 @@ export class Anchorer {
669
703
  reason: "Delegator approval is anchored and no witness republication is required.",
670
704
  };
671
705
  }
672
- yield* this.publishDelegator(serder);
706
+ // A delegator or witness that cannot take the publication keeps this row for the next pass.
707
+ const published = yield* this.publishDelegator(serder);
708
+ if (published.kind === "unreachable") {
709
+ return {
710
+ kind: "keep",
711
+ pre,
712
+ said,
713
+ phase: "waitingDelegatorAnchor",
714
+ reason: `Delegation publication is not delivered yet: ${published.error.message}`,
715
+ };
716
+ }
717
+ this.hby.db.dune.rem(keys);
673
718
  this.hby.db.dpub.pin(keys, serder);
674
719
  return {
675
720
  kind: "advance",
@@ -723,6 +768,8 @@ export class Anchorer {
723
768
  * Send the delegation chain before the approving event, awaiting actual Poster or witness transmission outcomes.
724
769
  *
725
770
  * @param serder Accepted delegator event to publish after its required delegation chain.
771
+ * @returns `unreachable` with the first remote failure (a witness or the delegator that is down, refuses or
772
+ * has no usable endpoint); nothing past it is attempted. Local faults still throw.
726
773
  */
727
774
  *publishDelegator(serder) {
728
775
  const pre = workflowPre(serder);
@@ -736,7 +783,9 @@ export class Anchorer {
736
783
  // matches the witness-visible material KERIpy sends for delegated approval.
737
784
  for (const msg of this.hby.db.cloneDelegation(kever)) {
738
785
  for (const witness of kever.wits) {
739
- yield* sendWitnessMessage(transportHab, witness, msg, this.http, this.clock, this.protocol);
786
+ const delivery = yield* deliverWitnessMessage(transportHab, witness, msg, this.http, this.clock, this.protocol);
787
+ if (delivery.kind === "unreachable")
788
+ return delivery;
740
789
  }
741
790
  }
742
791
  // Publish the approving event only after the complete delegation chain has been sent.
@@ -750,11 +799,19 @@ export class Anchorer {
750
799
  msg: approvedEvent,
751
800
  }, { notify: true });
752
801
  const outcome = yield* postman.wait(evt);
753
- if (outcome.kin === "failed")
754
- throw outcome.error;
802
+ if (outcome.kin === "failed") {
803
+ return {
804
+ kind: "unreachable",
805
+ witness: kever.delpre,
806
+ error: outcome.error instanceof Error ? outcome.error : new Error(String(outcome.error)),
807
+ };
808
+ }
755
809
  }
756
810
  for (const witness of kever.wits) {
757
- yield* sendWitnessMessage(transportHab, witness, approvedEvent, this.http, this.clock, this.protocol);
811
+ const delivery = yield* deliverWitnessMessage(transportHab, witness, approvedEvent, this.http, this.clock, this.protocol);
812
+ if (delivery.kind === "unreachable")
813
+ return delivery;
758
814
  }
815
+ return { kind: "delivered" };
759
816
  }
760
817
  }