keri-ts 2.2.4 → 2.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/esm/keri/npm/src/_dnt.polyfills.js +14 -14
  2. package/esm/keri/npm/src/acdc/dhking.js +29 -2
  3. package/esm/keri/npm/src/acdc/observing.js +10 -3
  4. package/esm/keri/npm/src/acdc/query-resend.js +74 -0
  5. package/esm/keri/npm/src/acdc/regengine.js +39 -9
  6. package/esm/keri/npm/src/acdc/regeventing.js +5 -3
  7. package/esm/keri/npm/src/acdc/registering.js +19 -0
  8. package/esm/keri/npm/src/acdc/salting.js +4 -0
  9. package/esm/keri/npm/src/acdc/unblinding.js +38 -3
  10. package/esm/keri/npm/src/app/bootstrap/authenticating.js +32 -10
  11. package/esm/keri/npm/src/app/bootstrap/oobiery.js +363 -30
  12. package/esm/keri/npm/src/app/credentials/credential-importing.js +33 -4
  13. package/esm/keri/npm/src/app/credentials/root-replicator.js +27 -15
  14. package/esm/keri/npm/src/app/credentials/schema-resolving.js +5 -1
  15. package/esm/keri/npm/src/app/credentials/tel-publishing.js +16 -7
  16. package/esm/keri/npm/src/app/delivery/forwarding.js +205 -44
  17. package/esm/keri/npm/src/app/delivery/relaying.js +232 -0
  18. package/esm/keri/npm/src/app/delivery/respondant.js +8 -5
  19. package/esm/keri/npm/src/app/exchanges/exchange-messaging.js +1 -1
  20. package/esm/keri/npm/src/app/exchanges/exchanging.js +65 -9
  21. package/esm/keri/npm/src/app/identity/delegating.js +66 -9
  22. package/esm/keri/npm/src/app/identity/habbing.js +190 -71
  23. package/esm/keri/npm/src/app/identity/keeping.js +111 -39
  24. package/esm/keri/npm/src/app/identity/retire-identifier.js +98 -0
  25. package/esm/keri/npm/src/app/ipex/disclosure-replaying.js +116 -0
  26. package/esm/keri/npm/src/app/ipex/group-ipex.js +122 -25
  27. package/esm/keri/npm/src/app/ipex/group-issuance.js +137 -37
  28. package/esm/keri/npm/src/app/ipex/ipex-discloser.js +94 -31
  29. package/esm/keri/npm/src/app/ipex/ipex-grouping.js +53 -3
  30. package/esm/keri/npm/src/app/ipex/ipex-v2.js +7 -2
  31. package/esm/keri/npm/src/app/ipex/issuance.js +147 -44
  32. package/esm/keri/npm/src/app/ipex/pending-presentations.js +4 -2
  33. package/esm/keri/npm/src/app/ipex/presenting.js +3 -18
  34. package/esm/keri/npm/src/app/ipex/receive-issuance.js +2 -1
  35. package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +39 -11
  36. package/esm/keri/npm/src/app/mailbox/agent-inbox.js +292 -0
  37. package/esm/keri/npm/src/app/mailbox/forward-handler.js +298 -52
  38. package/esm/keri/npm/src/app/mailbox/kel-publisher.js +26 -31
  39. package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +70 -62
  40. package/esm/keri/npm/src/app/mailbox/mailboxing.js +20 -4
  41. package/esm/keri/npm/src/app/multisig/counseling.js +71 -27
  42. package/esm/keri/npm/src/app/multisig/group-members.js +41 -5
  43. package/esm/keri/npm/src/app/multisig/grouping.js +50 -2
  44. package/esm/keri/npm/src/app/multisig/multisig-workflows.js +3 -18
  45. package/esm/keri/npm/src/app/multisig/proposing.js +1 -16
  46. package/esm/keri/npm/src/app/multisig/rejoin-group.js +99 -0
  47. package/esm/keri/npm/src/app/multisig/replace-member.js +29 -8
  48. package/esm/keri/npm/src/app/protocol/parsering.js +45 -17
  49. package/esm/keri/npm/src/app/protocol/protocol-processing.js +47 -21
  50. package/esm/keri/npm/src/app/queries/query-replies.js +0 -22
  51. package/esm/keri/npm/src/app/queries/relay-refresh.js +81 -0
  52. package/esm/keri/npm/src/app/queries/witness-inquisitor.js +180 -34
  53. package/esm/keri/npm/src/app/runtime/agent-runtime.js +42 -73
  54. package/esm/keri/npm/src/app/runtime/cue-runtime.js +45 -5
  55. package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +8 -1
  56. package/esm/keri/npm/src/app/runtime/operation-owners.js +26 -56
  57. package/esm/keri/npm/src/app/runtime/workflow-contract.js +7 -4
  58. package/esm/keri/npm/src/app/transport/http-client.js +20 -2
  59. package/esm/keri/npm/src/app/transport/message-source.js +2 -1
  60. package/esm/keri/npm/src/app/transport/messengering.js +39 -8
  61. package/esm/keri/npm/src/app/witness/ingress.js +7 -0
  62. package/esm/keri/npm/src/app/witness/receipt-client.js +43 -16
  63. package/esm/keri/npm/src/app/witness/receipting.js +62 -20
  64. package/esm/keri/npm/src/cli/messaging/multisig.js +4 -3
  65. package/esm/keri/npm/src/core/errors.js +73 -0
  66. package/esm/keri/npm/src/core/kel/delegation-ancestry.js +11 -2
  67. package/esm/keri/npm/src/core/kel/eventing.js +25 -28
  68. package/esm/keri/npm/src/core/kel/kever.js +15 -9
  69. package/esm/keri/npm/src/core/mailbox/kram-policy.js +16 -0
  70. package/esm/keri/npm/src/core/mailbox/kram-pruning.js +25 -6
  71. package/esm/keri/npm/src/core/mailbox/kram-time.js +34 -6
  72. package/esm/keri/npm/src/core/mailbox/kraming.js +58 -2
  73. package/esm/keri/npm/src/core/missing-anchor-query.js +20 -0
  74. package/esm/keri/npm/src/core/routing.js +18 -25
  75. package/esm/keri/npm/src/core/scheming.js +12 -5
  76. package/esm/keri/npm/src/core/wire-fields.js +126 -0
  77. package/esm/keri/npm/src/db/basing.js +4 -13
  78. package/esm/keri/npm/src/db/core/indexeddb-persistence.js +3 -2
  79. package/esm/keri/npm/src/db/core/webdber.js +85 -27
  80. package/esm/keri/npm/src/db/mailbox/mailboxing.js +13 -0
  81. package/esm/keri/npm/src/db/subing.js +4 -0
  82. package/esm/keri/npm/src/runtime/index.js +11 -5
  83. package/esm/keri/npm/src/time/mod.js +5 -0
  84. package/esm/keri/npm/src/vdr/credential-tsn-record.js +2 -7
  85. package/esm/keri/npm/src/vdr/eventing.js +71 -28
  86. package/esm/keri/npm/src/vdr/registry-tsn-record.js +3 -13
  87. package/esm/keri/npm/src/version.js +2 -2
  88. package/package.json +1 -1
  89. package/types/_dnt.polyfills.d.ts +9 -9
  90. package/types/_dnt.polyfills.d.ts.map +1 -1
  91. package/types/acdc/dhking.d.ts +8 -0
  92. package/types/acdc/dhking.d.ts.map +1 -1
  93. package/types/acdc/observing.d.ts +5 -1
  94. package/types/acdc/observing.d.ts.map +1 -1
  95. package/types/acdc/query-resend.d.ts +24 -0
  96. package/types/acdc/query-resend.d.ts.map +1 -0
  97. package/types/acdc/regengine.d.ts +19 -1
  98. package/types/acdc/regengine.d.ts.map +1 -1
  99. package/types/acdc/regeventing.d.ts.map +1 -1
  100. package/types/acdc/registering.d.ts +6 -0
  101. package/types/acdc/registering.d.ts.map +1 -1
  102. package/types/acdc/salting.d.ts.map +1 -1
  103. package/types/acdc/unblinding.d.ts +13 -1
  104. package/types/acdc/unblinding.d.ts.map +1 -1
  105. package/types/app/bootstrap/authenticating.d.ts +12 -2
  106. package/types/app/bootstrap/authenticating.d.ts.map +1 -1
  107. package/types/app/bootstrap/oobiery.d.ts +144 -7
  108. package/types/app/bootstrap/oobiery.d.ts.map +1 -1
  109. package/types/app/credentials/credential-importing.d.ts +8 -0
  110. package/types/app/credentials/credential-importing.d.ts.map +1 -1
  111. package/types/app/credentials/root-replicator.d.ts +19 -8
  112. package/types/app/credentials/root-replicator.d.ts.map +1 -1
  113. package/types/app/credentials/schema-resolving.d.ts.map +1 -1
  114. package/types/app/credentials/tel-publishing.d.ts +4 -2
  115. package/types/app/credentials/tel-publishing.d.ts.map +1 -1
  116. package/types/app/delivery/forwarding.d.ts +90 -5
  117. package/types/app/delivery/forwarding.d.ts.map +1 -1
  118. package/types/app/delivery/relaying.d.ts +144 -0
  119. package/types/app/delivery/relaying.d.ts.map +1 -0
  120. package/types/app/delivery/respondant.d.ts +9 -2
  121. package/types/app/delivery/respondant.d.ts.map +1 -1
  122. package/types/app/exchanges/exchanging.d.ts +36 -0
  123. package/types/app/exchanges/exchanging.d.ts.map +1 -1
  124. package/types/app/identity/delegating.d.ts +6 -0
  125. package/types/app/identity/delegating.d.ts.map +1 -1
  126. package/types/app/identity/habbing.d.ts +106 -19
  127. package/types/app/identity/habbing.d.ts.map +1 -1
  128. package/types/app/identity/keeping.d.ts +54 -19
  129. package/types/app/identity/keeping.d.ts.map +1 -1
  130. package/types/app/identity/retire-identifier.d.ts +24 -0
  131. package/types/app/identity/retire-identifier.d.ts.map +1 -0
  132. package/types/app/ipex/disclosure-replaying.d.ts +61 -0
  133. package/types/app/ipex/disclosure-replaying.d.ts.map +1 -0
  134. package/types/app/ipex/group-ipex.d.ts +60 -14
  135. package/types/app/ipex/group-ipex.d.ts.map +1 -1
  136. package/types/app/ipex/group-issuance.d.ts +54 -8
  137. package/types/app/ipex/group-issuance.d.ts.map +1 -1
  138. package/types/app/ipex/ipex-discloser.d.ts +39 -2
  139. package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
  140. package/types/app/ipex/ipex-grouping.d.ts +23 -2
  141. package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
  142. package/types/app/ipex/ipex-v2.d.ts +5 -2
  143. package/types/app/ipex/ipex-v2.d.ts.map +1 -1
  144. package/types/app/ipex/issuance.d.ts +46 -16
  145. package/types/app/ipex/issuance.d.ts.map +1 -1
  146. package/types/app/ipex/pending-presentations.d.ts +4 -2
  147. package/types/app/ipex/pending-presentations.d.ts.map +1 -1
  148. package/types/app/ipex/presenting.d.ts.map +1 -1
  149. package/types/app/ipex/receive-issuance.d.ts +2 -1
  150. package/types/app/ipex/receive-issuance.d.ts.map +1 -1
  151. package/types/app/ipex/unblinding-workflow.d.ts +13 -2
  152. package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
  153. package/types/app/mailbox/agent-inbox.d.ts +126 -0
  154. package/types/app/mailbox/agent-inbox.d.ts.map +1 -0
  155. package/types/app/mailbox/forward-handler.d.ts +114 -14
  156. package/types/app/mailbox/forward-handler.d.ts.map +1 -1
  157. package/types/app/mailbox/kel-publisher.d.ts +12 -22
  158. package/types/app/mailbox/kel-publisher.d.ts.map +1 -1
  159. package/types/app/mailbox/mailbox-poller.d.ts +40 -24
  160. package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
  161. package/types/app/mailbox/mailboxing.d.ts +19 -5
  162. package/types/app/mailbox/mailboxing.d.ts.map +1 -1
  163. package/types/app/multisig/counseling.d.ts +32 -11
  164. package/types/app/multisig/counseling.d.ts.map +1 -1
  165. package/types/app/multisig/group-members.d.ts +17 -1
  166. package/types/app/multisig/group-members.d.ts.map +1 -1
  167. package/types/app/multisig/grouping.d.ts +17 -1
  168. package/types/app/multisig/grouping.d.ts.map +1 -1
  169. package/types/app/multisig/multisig-workflows.d.ts +0 -8
  170. package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
  171. package/types/app/multisig/proposing.d.ts +4 -19
  172. package/types/app/multisig/proposing.d.ts.map +1 -1
  173. package/types/app/multisig/rejoin-group.d.ts +119 -0
  174. package/types/app/multisig/rejoin-group.d.ts.map +1 -0
  175. package/types/app/multisig/replace-member.d.ts +15 -1
  176. package/types/app/multisig/replace-member.d.ts.map +1 -1
  177. package/types/app/protocol/parsering.d.ts +21 -2
  178. package/types/app/protocol/parsering.d.ts.map +1 -1
  179. package/types/app/protocol/protocol-processing.d.ts +25 -5
  180. package/types/app/protocol/protocol-processing.d.ts.map +1 -1
  181. package/types/app/queries/query-replies.d.ts +4 -8
  182. package/types/app/queries/query-replies.d.ts.map +1 -1
  183. package/types/app/queries/relay-refresh.d.ts +26 -0
  184. package/types/app/queries/relay-refresh.d.ts.map +1 -0
  185. package/types/app/queries/witness-inquisitor.d.ts +56 -5
  186. package/types/app/queries/witness-inquisitor.d.ts.map +1 -1
  187. package/types/app/runtime/agent-runtime.d.ts +34 -3
  188. package/types/app/runtime/agent-runtime.d.ts.map +1 -1
  189. package/types/app/runtime/cue-runtime.d.ts +15 -3
  190. package/types/app/runtime/cue-runtime.d.ts.map +1 -1
  191. package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -1
  192. package/types/app/runtime/operation-owners.d.ts +16 -26
  193. package/types/app/runtime/operation-owners.d.ts.map +1 -1
  194. package/types/app/runtime/workflow-contract.d.ts +21 -6
  195. package/types/app/runtime/workflow-contract.d.ts.map +1 -1
  196. package/types/app/transport/http-client.d.ts +9 -0
  197. package/types/app/transport/http-client.d.ts.map +1 -1
  198. package/types/app/transport/message-source.d.ts +6 -3
  199. package/types/app/transport/message-source.d.ts.map +1 -1
  200. package/types/app/transport/messengering.d.ts +10 -1
  201. package/types/app/transport/messengering.d.ts.map +1 -1
  202. package/types/app/witness/ingress.d.ts +1 -0
  203. package/types/app/witness/ingress.d.ts.map +1 -1
  204. package/types/app/witness/receipt-client.d.ts +19 -1
  205. package/types/app/witness/receipt-client.d.ts.map +1 -1
  206. package/types/app/witness/receipting.d.ts +24 -1
  207. package/types/app/witness/receipting.d.ts.map +1 -1
  208. package/types/cli/messaging/multisig.d.ts.map +1 -1
  209. package/types/core/cues.d.ts +53 -1
  210. package/types/core/cues.d.ts.map +1 -1
  211. package/types/core/errors.d.ts +32 -0
  212. package/types/core/errors.d.ts.map +1 -1
  213. package/types/core/kel/delegation-ancestry.d.ts.map +1 -1
  214. package/types/core/kel/eventing.d.ts.map +1 -1
  215. package/types/core/kel/kever-decisions.d.ts +3 -1
  216. package/types/core/kel/kever-decisions.d.ts.map +1 -1
  217. package/types/core/kel/kever.d.ts +7 -1
  218. package/types/core/kel/kever.d.ts.map +1 -1
  219. package/types/core/mailbox/kram-policy.d.ts +12 -0
  220. package/types/core/mailbox/kram-policy.d.ts.map +1 -1
  221. package/types/core/mailbox/kram-pruning.d.ts +5 -1
  222. package/types/core/mailbox/kram-pruning.d.ts.map +1 -1
  223. package/types/core/mailbox/kram-time.d.ts +26 -2
  224. package/types/core/mailbox/kram-time.d.ts.map +1 -1
  225. package/types/core/mailbox/kraming.d.ts +14 -1
  226. package/types/core/mailbox/kraming.d.ts.map +1 -1
  227. package/types/core/missing-anchor-query.d.ts +34 -0
  228. package/types/core/missing-anchor-query.d.ts.map +1 -0
  229. package/types/core/routing.d.ts +3 -7
  230. package/types/core/routing.d.ts.map +1 -1
  231. package/types/core/scheming.d.ts.map +1 -1
  232. package/types/core/wire-fields.d.ts +56 -0
  233. package/types/core/wire-fields.d.ts.map +1 -0
  234. package/types/db/basing.d.ts.map +1 -1
  235. package/types/db/core/indexeddb-persistence.d.ts +3 -2
  236. package/types/db/core/indexeddb-persistence.d.ts.map +1 -1
  237. package/types/db/core/webdber.d.ts +32 -6
  238. package/types/db/core/webdber.d.ts.map +1 -1
  239. package/types/db/exchange/exchange-records.d.ts +4 -0
  240. package/types/db/exchange/exchange-records.d.ts.map +1 -1
  241. package/types/db/mailbox/mailboxing.d.ts +10 -1
  242. package/types/db/mailbox/mailboxing.d.ts.map +1 -1
  243. package/types/db/subing.d.ts +2 -0
  244. package/types/db/subing.d.ts.map +1 -1
  245. package/types/runtime/index.d.ts +20 -14
  246. package/types/runtime/index.d.ts.map +1 -1
  247. package/types/time/mod.d.ts +2 -0
  248. package/types/time/mod.d.ts.map +1 -1
  249. package/types/vdr/credential-tsn-record.d.ts.map +1 -1
  250. package/types/vdr/eventing.d.ts +32 -1
  251. package/types/vdr/eventing.d.ts.map +1 -1
  252. package/types/vdr/registry-tsn-record.d.ts.map +1 -1
  253. package/types/version.d.ts +2 -2
  254. package/esm/keri/npm/src/app/runtime/signer-conditions.js +0 -51
  255. package/types/app/runtime/signer-conditions.d.ts +0 -32
  256. package/types/app/runtime/signer-conditions.d.ts.map +0 -1
@@ -32,6 +32,34 @@ export class ValidationError extends AppError {
32
32
  });
33
33
  }
34
34
  }
35
+ /**
36
+ * A caller asked to treat a KRAM refusal as a failure. Carries the refused message SAID and the refusal decision;
37
+ * the message names the reason and, when the refusal has one, the last instant the message was acceptable.
38
+ */
39
+ export class KramRefusedError extends ValidationError {
40
+ constructor(said, refusal) {
41
+ super(`KRAM refused message ${said}: ${refusal.reason}`
42
+ + (refusal.deadline ? ` (acceptable until ${refusal.deadline})` : ""));
43
+ Object.defineProperty(this, "said", {
44
+ enumerable: true,
45
+ configurable: true,
46
+ writable: true,
47
+ value: said
48
+ });
49
+ Object.defineProperty(this, "refusal", {
50
+ enumerable: true,
51
+ configurable: true,
52
+ writable: true,
53
+ value: refusal
54
+ });
55
+ Object.defineProperty(this, "name", {
56
+ enumerable: true,
57
+ configurable: true,
58
+ writable: true,
59
+ value: "KramRefusedError"
60
+ });
61
+ }
62
+ }
35
63
  /** Filesystem or path-resolution failure from path-managed resources. */
36
64
  export class PathError extends AppError {
37
65
  constructor() {
@@ -120,3 +148,48 @@ export class DatabaseOperationError extends DatabaseError {
120
148
  });
121
149
  }
122
150
  }
151
+ /**
152
+ * Local durable state that the store's own records say must exist is missing or contradicts itself: a local group
153
+ * names a member habitat the store does not hold, for example. Never a remote outcome: no peer message removes a
154
+ * local record, so a long-running loop does not survive it and lets it end the runtime. Recovery is repairing or
155
+ * replacing the store, not retrying.
156
+ */
157
+ export class MissingDurableStateError extends DatabaseError {
158
+ constructor() {
159
+ super(...arguments);
160
+ Object.defineProperty(this, "name", {
161
+ enumerable: true,
162
+ configurable: true,
163
+ writable: true,
164
+ value: "MissingDurableStateError"
165
+ });
166
+ }
167
+ }
168
+ /**
169
+ * A browser composition's persistence queue gave up on a write after its retries (ADR-0015 rule 3), so it accepts
170
+ * no more commits and reports this from every later commit, flush and close. The cause is the write's first failure.
171
+ * The latch is terminal for that composition; recovery is closing it and opening a new one.
172
+ */
173
+ export class PersistenceLatchedError extends DatabaseOperationError {
174
+ constructor() {
175
+ super(...arguments);
176
+ Object.defineProperty(this, "name", {
177
+ enumerable: true,
178
+ configurable: true,
179
+ writable: true,
180
+ value: "PersistenceLatchedError"
181
+ });
182
+ }
183
+ }
184
+ /** A task passed to `PersistenceQueue.read` failed. Only its caller sees it; the queue does not latch. */
185
+ export class PersistenceReadError extends DatabaseOperationError {
186
+ constructor() {
187
+ super(...arguments);
188
+ Object.defineProperty(this, "name", {
189
+ enumerable: true,
190
+ configurable: true,
191
+ writable: true,
192
+ value: "PersistenceReadError"
193
+ });
194
+ }
195
+ }
@@ -1,8 +1,17 @@
1
1
  import { dgKey } from "../../db/core/keys.js";
2
2
  import { ValidationError } from "../errors.js";
3
- /** Whether one accepted delegator event carries the exact `{i,s,d}` seal of a delegated inception. */
3
+ import { readEventSeal } from "../wire-fields.js";
4
+ /**
5
+ * Whether one accepted delegator event carries the exact `{i,s,d}` seal of a delegated inception.
6
+ *
7
+ * The delegator's seals are remote-authored and unchecked at acceptance; other shapes and undecodable values are
8
+ * skipped (`readEventSeal`), never thrown.
9
+ */
4
10
  function approves(event, dip) {
5
- return event.eventSeals.some((seal) => seal.i.qb64 === dip.pre && seal.s.numh === dip.snh && seal.d.qb64 === dip.said);
11
+ return event.seals.some((raw) => {
12
+ const seal = readEventSeal(raw);
13
+ return seal !== null && seal.i.qb64 === dip.pre && seal.s.numh === dip.snh && seal.d.qb64 === dip.said;
14
+ });
6
15
  }
7
16
  /**
8
17
  * Find the delegator's accepted approval of `dip`.
@@ -8,6 +8,7 @@ import { normalizeMbxTopicCursor } from "../mailbox/mailbox-topics.js";
8
8
  import { reply as replyEvent } from "../protocol-eventing.js";
9
9
  import { KeyStateRecord, ObservedRecord } from "../records.js";
10
10
  import { acceptReplyDecision, unverifiedReplyDecision } from "../routing.js";
11
+ import { kelEventFieldDefect, readDater, readDiger, readOrdinal, readPrefixer, readSequenceNumber, } from "../wire-fields.js";
11
12
  import { acceptEscrow, acceptQuery, acceptReceipt, dropEscrow, dropQuery, dropReceipt, escrowQuery, escrowReceipt, ignoreReceipt, keepEscrow, logEscrowDecision, } from "./kever-decisions.js";
12
13
  import { Kever } from "./kever.js";
13
14
  import { deriveRotatedWitnessSet, hasUniqueWitnesses } from "./witnesses.js";
@@ -365,14 +366,15 @@ export class Kevery {
365
366
  processReceipt(envelope) {
366
367
  const serder = envelope.serder;
367
368
  const pre = serder.pre;
368
- const sn = serder.sn;
369
+ // Decided before any escrow: a remote receipt's sn and prefix are read through the guarded readers.
370
+ const sn = readSequenceNumber(serder);
369
371
  const said = serder.said;
370
372
  const local = envelope.local ?? this.local;
371
- if (!pre || sn === null || !said) {
373
+ if (!pre || !readPrefixer(pre) || sn === null || !said) {
372
374
  this.cues.push({
373
375
  kin: "invalid",
374
376
  serder,
375
- reason: "Receipt message is missing receipted pre, sn, or said.",
377
+ reason: "Receipt message is missing a valid receipted pre, hex sn, or said.",
376
378
  });
377
379
  return;
378
380
  }
@@ -611,23 +613,22 @@ export class Kevery {
611
613
  const pre = serder.pre;
612
614
  const ilk = serder.ilk;
613
615
  const said = serder.said;
614
- const sn = serder.sn;
616
+ // Every remote field value is decided here, before any escrow or store write: escrow replay re-enters through
617
+ // this method, so a row stored with a malformed value is rejected and removed instead of failing every pass.
618
+ const sn = readSequenceNumber(serder);
615
619
  if (!pre || !ilk || !said || sn === null) {
616
620
  return {
617
621
  kind: "reject",
618
622
  code: !pre ? "invalidPre" : !ilk ? "invalidIlk" : "invalidSn",
619
- message: "KEL event must include pre, ilk, said, and sn.",
623
+ message: `KEL event must include pre, ilk, said, and a hex sn of at most ${Number.MAX_SAFE_INTEGER}.`,
620
624
  };
621
625
  }
622
- try {
623
- new Prefixer({ qb64: pre });
626
+ if (!readPrefixer(pre)) {
627
+ return { kind: "reject", code: "invalidPre", message: `Invalid pre=${pre} for event ${said}.` };
624
628
  }
625
- catch (error) {
626
- return {
627
- kind: "reject",
628
- code: "invalidPre",
629
- message: `Invalid pre=${pre} for event ${said}: ${error instanceof Error ? error.message : String(error)}`,
630
- };
629
+ const defect = kelEventFieldDefect(serder);
630
+ if (defect) {
631
+ return { kind: "reject", code: "malformedField", message: `Malformed ${defect} in event ${said} for ${pre}.` };
631
632
  }
632
633
  const local = envelope.local ?? this.local;
633
634
  const init = this.makeKeverEventInit(envelope, local);
@@ -939,16 +940,18 @@ export class Kevery {
939
940
  throw new ValidationError(`Unsupported route=${args.route} in ${Ilks.rpy} reply.`);
940
941
  }
941
942
  const ksn = KeyStateRecord.fromDict(args.serder.ked?.a);
942
- const pre = typeof ksn.i === "string" ? ksn.i : null;
943
- const sn = typeof ksn.s === "string" ? parseInt(ksn.s, 16) : null;
944
- const said = typeof ksn.d === "string" ? ksn.d : null;
945
- const dt = typeof ksn.dt === "string" ? ksn.dt : null;
946
- if (!pre || sn === null || Number.isNaN(sn) || !said || !dt) {
943
+ // Decide the notice's field values before decideReply can escrow it.
944
+ const pre = readPrefixer(ksn.i)?.qb64 ?? null;
945
+ const sn = typeof ksn.s === "string" ? readOrdinal(ksn.s) : null;
946
+ const saider = readDiger(ksn.d);
947
+ const dater = readDater(ksn.dt);
948
+ if (!pre || sn === null || !saider || !dater) {
947
949
  throw new ValidationError("Malformed key state notice reply body.");
948
950
  }
951
+ const said = saider.qb64;
949
952
  if (!this.lax) {
950
953
  const watchers = this.configuredWatchers();
951
- const backers = ksn.b ?? [];
954
+ const backers = Array.isArray(ksn.b) ? ksn.b : [];
952
955
  // In non-lax mode, only the controller, a reported KSN backer, or a
953
956
  // locally configured watcher may advance saved key state.
954
957
  const trusted = args.aid === pre
@@ -986,8 +989,6 @@ export class Kevery {
986
989
  if (ldig && ldig !== said) {
987
990
  throw new ValidationError(`Mismatch key state at sn=${sn} with accepted event log for ${pre}.`);
988
991
  }
989
- const saider = new Diger({ qb64: said });
990
- const dater = new Dater({ qb64: encodeDateTimeToDater(dt) });
991
992
  const cues = [{ kin: "keyStateSaved", ksn }];
992
993
  this.rvy.writeReply(reply);
993
994
  this.updateKeyState(args.aid, ksn, saider, dater);
@@ -1025,15 +1026,11 @@ export class Kevery {
1025
1026
  throw new ValidationError(`Unsupported route=${args.route} in ${Ilks.rpy} reply.`);
1026
1027
  }
1027
1028
  const data = args.serder.ked?.a;
1028
- const cid = typeof data?.cid === "string"
1029
- ? new Prefixer({ qb64: data.cid }).qb64
1030
- : null;
1031
- const oid = typeof data?.oid === "string"
1032
- ? new Prefixer({ qb64: data.oid }).qb64
1033
- : null;
1029
+ const cid = readPrefixer(data?.cid)?.qb64 ?? null;
1030
+ const oid = readPrefixer(data?.oid)?.qb64 ?? null;
1034
1031
  const oobi = typeof data?.oobi === "string" ? data.oobi : null;
1035
1032
  if (!cid || !oid) {
1036
- throw new ValidationError("Missing one of cid/oid in /watcher reply.");
1033
+ throw new ValidationError("Missing or malformed one of cid/oid in /watcher reply.");
1037
1034
  }
1038
1035
  const keys = [cid, args.aid, oid];
1039
1036
  let osaider = this.db.wwas.get(keys);
@@ -1,8 +1,9 @@
1
- import { Dater, Diger, Ilks, NumberPrimitive, Prefixer, SealSource, Siger, Tholder, Verfer, } from "cesr-ts";
1
+ import { Dater, Diger, Ilks, NumberPrimitive, Prefixer, SealEvent, SealSource, Siger, Tholder, Verfer, } from "cesr-ts";
2
2
  import { dgKey } from "../../db/core/keys.js";
3
3
  import { encodeDateTimeToDater, makeNowIso8601 } from "../../time/mod.js";
4
4
  import { ValidationError } from "../errors.js";
5
5
  import { KeyStateRecord } from "../records.js";
6
+ import { readEventSeal } from "../wire-fields.js";
6
7
  import { deriveRotatedWitnessSet } from "./witnesses.js";
7
8
  /**
8
9
  * Convert one integer into the Huge-number CESR family used for durable replay
@@ -1434,16 +1435,21 @@ export class Kever {
1434
1435
  }
1435
1436
  return this.db.getState(pre)?.di || null;
1436
1437
  }
1437
- /** Return the zero-based index of the matching event seal or `-1`. */
1438
+ /**
1439
+ * Return the zero-based index of the matching event seal among the candidate's event-shaped seals, or `-1`.
1440
+ *
1441
+ * The candidate is an accepted delegator event, which is remote-authored: its `a` may hold any seal shape and
1442
+ * undecodable values. Like KERIpy, the index counts entries with the `{i, s, d}` field shape; other shapes and
1443
+ * malformed values never match (`readEventSeal`).
1444
+ */
1438
1445
  eventAnchorSealIndex(candidate, serder) {
1439
- for (const [index, seal] of candidate.eventSeals.entries()) {
1440
- if (seal.i.qb64 === serder.pre
1446
+ return candidate.seals.filter((raw) => SealEvent.isSad(raw)).findIndex((raw) => {
1447
+ const seal = readEventSeal(raw);
1448
+ return seal !== null
1449
+ && seal.i.qb64 === serder.pre
1441
1450
  && seal.s.numh === serder.snh
1442
- && seal.d.qb64 === serder.said) {
1443
- return index;
1444
- }
1445
- }
1446
- return -1;
1451
+ && seal.d.qb64 === serder.said;
1452
+ });
1447
1453
  }
1448
1454
  /**
1449
1455
  * Verify one indexed-signature set against a given verifier list.
@@ -29,6 +29,22 @@ function storedCacheType(db, ilk, route) {
29
29
  export function singleKeyQueryWindow(db, route) {
30
30
  return storedCacheType(db, "qry", route)?.sl ?? null;
31
31
  }
32
+ /**
33
+ * How long a multi-key (group threshold) query on `route` stays timely under the windows this store was configured
34
+ * with, in milliseconds: the multi-key lag. A group sender uses its own windows as the estimate of its peer's, as
35
+ * {@link singleKeyQueryWindow} does. Null when this store has no KRAM cache types (KRAM never ran on it).
36
+ */
37
+ export function multiKeyQueryWindow(db, route) {
38
+ return storedCacheType(db, "qry", route)?.ll ?? null;
39
+ }
40
+ /**
41
+ * How long an exchange of a message of `ilk` on `route` stays open after its opener under the windows this store
42
+ * is configured with now, in milliseconds: the exchange lag `xl`. Null when this store has no KRAM cache types
43
+ * (KRAM never ran on it).
44
+ */
45
+ export function exchangeWindow(db, ilk, route) {
46
+ return storedCacheType(db, ilk, route)?.xl ?? null;
47
+ }
32
48
  /** Validate caller configuration before any durable policy mutation. */
33
49
  function windows(value, transitional = false) {
34
50
  if (!Array.isArray(value) || value.length !== 7 || value.some((v) => !Number.isSafeInteger(v) || v < 0)) {
@@ -2,11 +2,12 @@
2
2
  import { b } from "cesr-ts";
3
3
  import { removeKramFactors } from "../../db/mailbox/kram-factors.js";
4
4
  import { timestampMicros } from "../../time/mod.js";
5
+ import { consoleLogger } from "../logger.js";
5
6
  import { TxnMsgCacheRecord } from "../records.js";
6
7
  import { kramCacheValues } from "./kram-time.js";
7
8
  /** Prune stored windows, then prove orphan XIDs with restartable finite existence scans. */
8
9
  export class KramPruner {
9
- constructor(db, policy) {
10
+ constructor(db, policy, logger = consoleLogger) {
10
11
  Object.defineProperty(this, "db", {
11
12
  enumerable: true,
12
13
  configurable: true,
@@ -19,6 +20,12 @@ export class KramPruner {
19
20
  writable: true,
20
21
  value: policy
21
22
  });
23
+ Object.defineProperty(this, "logger", {
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true,
27
+ value: logger
28
+ });
22
29
  Object.defineProperty(this, "bound", {
23
30
  enumerable: true,
24
31
  configurable: true,
@@ -95,7 +102,14 @@ export class KramPruner {
95
102
  throw new Error("Invalid KRAM pruning receiver clock");
96
103
  if (!Number.isSafeInteger(maxRows) || maxRows < 1)
97
104
  throw new RangeError("Invalid KRAM pruning batch");
98
- const result = { kind: "pruned", visited: 0, removed: 0, openers: 0, passComplete: false };
105
+ const result = {
106
+ kind: "pruned",
107
+ visited: 0,
108
+ removed: 0,
109
+ openers: 0,
110
+ partials: 0,
111
+ passComplete: false,
112
+ };
99
113
  if (!this.db.opened)
100
114
  throw new Error("KRAM pruning database is closed");
101
115
  if (this.bound !== this.db.msgc.sdb) {
@@ -114,15 +128,20 @@ export class KramPruner {
114
128
  continue;
115
129
  }
116
130
  const keys = this.keys(physical, this.phase + 2), table = this.phase ? this.db.tmsc : this.db.msgc;
117
- this.db.transaction(() => {
131
+ const partial = this.db.transaction(() => {
118
132
  const record = table.get(keys);
119
133
  if (!record || !this.expired(record, when))
120
- return;
134
+ return null;
121
135
  table.rem(keys);
122
- const partial = [keys[0], keys.at(-1)];
123
- removeKramFactors(this.db, partial);
136
+ const owner = [keys[0], keys.at(-1)], incomplete = this.db.pmkm.get(owner) !== null;
137
+ removeKramFactors(this.db, owner);
124
138
  result.removed++;
139
+ return incomplete ? owner : null;
125
140
  });
141
+ if (partial) {
142
+ result.partials++;
143
+ this.logger.warn("KRAM expired incomplete multi-signature message", { sender: partial[0], said: partial[1] });
144
+ }
126
145
  scan.start = this.after(physical);
127
146
  result.visited++;
128
147
  continue;
@@ -1,5 +1,16 @@
1
- import { timestampMicros } from "../../time/mod.js";
1
+ import { timestampFromMicros, timestampMicros } from "../../time/mod.js";
2
2
  import { MsgCacheRecord, TxnMsgCacheRecord } from "../records.js";
3
+ import { readDater, readDiger } from "../wire-fields.js";
4
+ /**
5
+ * Whether a drop reason means an authenticated message arrived too late or outside a live exchange.
6
+ *
7
+ * `timestamp`, `exchangeId`, `replay` and `authentication` are reachable by anyone who can post bytes (see
8
+ * kram-staging.ts), so callers that report or signal a refusal to anyone beyond the log must restrict themselves to
9
+ * these.
10
+ */
11
+ export function isKramLateReason(reason) {
12
+ return reason === "timeliness" || reason === "lifetime" || reason === "unknownThread";
13
+ }
3
14
  /** Validate durable cache values without turning corruption into remote refusal. */
4
15
  export function kramCacheValues(cache) {
5
16
  const { mdt, d, ml, pml, xl, pxl } = cache;
@@ -10,9 +21,19 @@ export function kramCacheValues(cache) {
10
21
  throw new Error("Corrupt KRAM timeliness cache");
11
22
  return { mdt: mdt, d: d, ml: ml, pml: pml, xl: xl, pxl: pxl };
12
23
  }
24
+ /** Exchange id KRAM threads a message by: the `xip` SAID or the v2 `exn` `x` field; empty when it has none. */
25
+ export function kramExchangeId(body) {
26
+ const said = body.said;
27
+ return body.ilk === "xip" ? said ?? "" : body.ilk === "exn" && body.pvrsn.major >= 2 ? String(body.ked?.x ?? "") : "";
28
+ }
13
29
  /**
14
30
  * Inspect frozen message/xid routing and strict completed-message replay rules.
15
31
  * Only an incomplete multi-signature message retains partial state.
32
+ *
33
+ * The stamp and exchange id are remote-authored and become durable values: an exchange message's stamp can be stored
34
+ * as its thread opener (a Dater, which needs the fixed-width form), and the exchange id is a cache key part (a value
35
+ * that is not one qualified digest, such as one containing `.`, would make a key the pruner cannot split). Both are
36
+ * refused here, before any store write.
16
37
  */
17
38
  export function inspectKramReplay(db, body) {
18
39
  const mdt = timestampMicros(body.stamp);
@@ -22,7 +43,11 @@ export function inspectKramReplay(db, body) {
22
43
  if (!sender || !said)
23
44
  throw new Error("KRAM replay inspection requires sender and message SAID");
24
45
  const key = [sender, said];
25
- const xid = body.ilk === "xip" ? said : body.ilk === "exn" && body.pvrsn.major >= 2 ? String(body.ked?.x ?? "") : "";
46
+ const xid = kramExchangeId(body);
47
+ if (xid && readDiger(xid)?.qb64 !== xid)
48
+ return { kind: "drop", reason: "exchangeId" };
49
+ if (xid && !readDater(body.stamp))
50
+ return { kind: "drop", reason: "timestamp" };
26
51
  const cacheKey = xid ? [sender, xid, said] : key;
27
52
  const cache = xid ? db.tmsc.get(cacheKey) : db.msgc.get(cacheKey);
28
53
  const partial = db.pmkm.get(key) !== null;
@@ -54,8 +79,9 @@ export function evaluateKramTime(db, policy, inspection, authentication, receive
54
79
  else {
55
80
  const type = policy.cacheType(body.ilk, body.route), multi = authentication.kind === "multi";
56
81
  const ml = multi ? type.ll : type.sl, pml = multi ? type.pll : type.psl;
57
- if (!within(mdt, receipt - (BigInt(type.d) + BigInt(ml)) * 1000n, receipt + BigInt(type.d) * 1000n)) {
58
- return { kind: "drop", reason: "timeliness" };
82
+ const reach = (BigInt(type.d) + BigInt(ml)) * 1000n;
83
+ if (!within(mdt, receipt - reach, receipt + BigInt(type.d) * 1000n)) {
84
+ return { kind: "drop", reason: "timeliness", deadline: timestampFromMicros(mdt + reach) };
59
85
  }
60
86
  values = { mdt: body.stamp, d: type.d, ml, pml, xl: type.xl, pxl: type.pxl };
61
87
  }
@@ -79,8 +105,10 @@ export function evaluateKramTime(db, policy, inspection, authentication, receive
79
105
  const opener = timestampMicros(xdt);
80
106
  if (opener === null)
81
107
  throw new Error("Corrupt KRAM transaction opener");
82
- if (!cache && !within(mdt, opener, opener + BigInt(values.xl) * 1000n))
83
- return { kind: "drop", reason: "lifetime" };
108
+ const closing = opener + BigInt(values.xl) * 1000n;
109
+ if (!cache && !within(mdt, opener, closing)) {
110
+ return { kind: "drop", reason: "lifetime", deadline: timestampFromMicros(closing) };
111
+ }
84
112
  const complete = authentication.kind !== "multi" || authentication.complete;
85
113
  const rdt = cache instanceof TxnMsgCacheRecord && cache.rdt ? cache.rdt : complete ? receiverTime : "";
86
114
  return {
@@ -1,19 +1,27 @@
1
1
  import { makeNowIso8601, timestampMicros } from "../../time/mod.js";
2
2
  import { Deck } from "../deck.js";
3
+ import { consoleLogger } from "../logger.js";
3
4
  import { KramPolicy } from "./kram-policy.js";
4
5
  import { KramPruner } from "./kram-pruning.js";
5
6
  import { stageKram } from "./kram-staging.js";
7
+ import { isKramLateReason, kramExchangeId } from "./kram-time.js";
6
8
  /** Explicit receiver-owned cache and exemption configuration. */
7
9
  export { DEFAULT_KRAM_CACHES } from "./kram-policy.js";
8
10
  /** Optional receiver intake returns ordinary decisions without owning parser input. */
9
11
  export class Kramer {
10
- constructor(db, { config = {}, cues = new Deck(), now = Date.now } = {}) {
12
+ constructor(db, { config = {}, cues = new Deck(), now = Date.now } = {}, logger = consoleLogger) {
11
13
  Object.defineProperty(this, "db", {
12
14
  enumerable: true,
13
15
  configurable: true,
14
16
  writable: true,
15
17
  value: db
16
18
  });
19
+ Object.defineProperty(this, "logger", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: logger
24
+ });
17
25
  /** Shared runtime cues, or the standalone caller's drainable receiver-state notifications. */
18
26
  Object.defineProperty(this, "cues", {
19
27
  enumerable: true,
@@ -42,7 +50,7 @@ export class Kramer {
42
50
  this.now = now;
43
51
  this.policy = new KramPolicy(db, config, now);
44
52
  this.cues = cues;
45
- this.pruner = new KramPruner(db, this.policy);
53
+ this.pruner = new KramPruner(db, this.policy, logger);
46
54
  }
47
55
  /** Decide intake synchronously; protocol dispatch consumes the returned evidence. */
48
56
  intake(envelope) {
@@ -64,8 +72,56 @@ export class Kramer {
64
72
  q: result.sn === null ? {} : { s: result.sn.toString(16) },
65
73
  });
66
74
  }
75
+ if (result.kind === "drop") {
76
+ this.report(envelope, result);
77
+ const cue = this.refusedCue(envelope, result);
78
+ if (cue)
79
+ this.cues.push(cue);
80
+ }
67
81
  return result;
68
82
  }
83
+ /**
84
+ * Describe a refusal the application may want to hear about, or null. Staging has already committed. A local
85
+ * caller receives its own refusal, and only the reasons KRAM decides after authentication are safe to signal:
86
+ * `timestamp`, `replay` and `authentication` are reachable by anyone who can post bytes.
87
+ */
88
+ refusedCue(envelope, refusal) {
89
+ if (envelope.local || !isKramLateReason(refusal.reason))
90
+ return null;
91
+ const { serder } = envelope;
92
+ const xid = kramExchangeId(serder), deadline = "deadline" in refusal ? refusal.deadline : undefined;
93
+ return {
94
+ kin: "kramRefused",
95
+ said: serder.said,
96
+ sender: serder.pre,
97
+ ilk: serder.ilk,
98
+ route: serder.route,
99
+ reason: refusal.reason,
100
+ stamp: serder.stamp,
101
+ ...(deadline === undefined ? {} : { deadline }),
102
+ ...(xid ? { xid } : {}),
103
+ };
104
+ }
105
+ /**
106
+ * Report one refused message. Replay is routine (every duplicate delivery lands here), so it logs at debug;
107
+ * the other reasons mean a sender or clock is misbehaving and log at warn. Dispatch does not log again.
108
+ */
109
+ report(envelope, refusal) {
110
+ const { serder } = envelope;
111
+ const fields = {
112
+ said: serder.said,
113
+ sender: serder.pre,
114
+ ilk: serder.ilk,
115
+ route: serder.route,
116
+ reason: refusal.reason,
117
+ stamp: serder.stamp,
118
+ deadline: "deadline" in refusal ? refusal.deadline : undefined,
119
+ };
120
+ if (refusal.reason === "replay")
121
+ this.logger.debug("KRAM refused message", fields);
122
+ else
123
+ this.logger.warn("KRAM refused message", fields);
124
+ }
69
125
  /** Reconfigure cache windows through the durable policy transition facility. */
70
126
  changeConfig(caches, delay) {
71
127
  this.policy.change(caches, delay);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Name the issuer KEL events still missing for a pending event, or null when more KEL cannot supply the anchor.
3
+ *
4
+ * @param pre Issuer whose KEL holds the anchoring seal.
5
+ * @param accepted Sequence number of the issuer's accepted KEL here, or null when the issuer is unknown.
6
+ * @param anchor Sequence number of the anchoring event when a source seal names it, otherwise null.
7
+ * @returns The query, or null when the accepted KEL already reaches a known anchor sequence number: the anchor was
8
+ * found wanting rather than missing, and replay cannot change that.
9
+ */
10
+ export function missingAnchorQuery(pre, accepted, anchor) {
11
+ if (accepted !== null && anchor !== null && accepted >= anchor)
12
+ return null;
13
+ const fn = (accepted === null ? 0n : accepted + 1n).toString(16);
14
+ return anchor === null ? { pre, fn } : { pre, fn, s: anchor.toString(16) };
15
+ }
16
+ /** The routed query cue that asks for `query`; the existing query path sends it. */
17
+ export function missingAnchorCue(query) {
18
+ const { pre, fn, s } = query;
19
+ return { kin: "query", pre, route: "logs", q: s === undefined ? { fn } : { fn, s } };
20
+ }
@@ -1,11 +1,12 @@
1
- import { Dater, Diger, NumberPrimitive, Prefixer, Siger } from "cesr-ts";
2
- import { encodeDateTimeToDater, timestampMicros } from "../time/mod.js";
1
+ import { Diger, NumberPrimitive, Prefixer, Siger } from "cesr-ts";
2
+ import { timestampMicros } from "../time/mod.js";
3
3
  import { Deck } from "./deck.js";
4
4
  import { TransIdxSigGroup } from "./dispatch.js";
5
5
  import { ValidationError } from "./errors.js";
6
6
  import { acceptEscrow, dropEscrow, keepEscrow, logEscrowDecision, } from "./kel/kever-decisions.js";
7
7
  import { EndpointRecord, LocationRecord } from "./records.js";
8
8
  import { isRole } from "./roles.js";
9
+ import { readDater, readPrefixer, readUrl } from "./wire-fields.js";
9
10
  /** Builder for one accepted live reply decision. */
10
11
  export function acceptReplyDecision() {
11
12
  return { kind: "accept" };
@@ -276,6 +277,7 @@ Object.defineProperty(Router, "defaultResourceFunc", {
276
277
  * - persist accepted reply artifacts into `sdts.`, `rpys.`, `scgs.`, `ssgs.`
277
278
  * - escrow partially verifiable transferable replies into `rpes.`
278
279
  * - emit `query` cues when missing signer state blocks verification
280
+ * - emit an `endRoleSaved` cue after an accepted `/end/role` reply commits (TypeScript-only; KERIpy has no consumer)
279
281
  *
280
282
  * Current `keri-ts` differences:
281
283
  * - only the Gate E bootstrap reply families are currently routed
@@ -321,17 +323,6 @@ export class Revery {
321
323
  writable: true,
322
324
  value: void 0
323
325
  });
324
- /**
325
- * Told after an `/end/role` reply is accepted, with the controller, role, endpoint and whether the role is
326
- * allowed. The runtime uses it to have the mailbox poller and KEL publisher look at a controller whose mailbox
327
- * role just changed, rather than at their next sweep.
328
- */
329
- Object.defineProperty(this, "onEndRole", {
330
- enumerable: true,
331
- configurable: true,
332
- writable: true,
333
- value: void 0
334
- });
335
326
  this.db = db;
336
327
  this.rtr = rtr ?? new Router();
337
328
  this.cues = cues ?? new Deck();
@@ -404,11 +395,10 @@ export class Revery {
404
395
  // Same-call groups see the last staged pin exactly as they previously saw the durable write.
405
396
  const staged = new Map();
406
397
  const groupKey = (pre, snh, said) => `${pre}.${snh}.${said}`;
407
- const daterText = args.serder.ked?.dt;
408
- if (typeof daterText !== "string") {
409
- throw new ValidationError("Reply message is missing 'dt'.");
398
+ const dater = readDater(args.serder.ked?.dt);
399
+ if (!dater) {
400
+ throw new ValidationError("Reply message is missing a valid 'dt'.");
410
401
  }
411
- const dater = new Dater({ qb64: encodeDateTimeToDater(daterText) });
412
402
  const odater = args.osaider ? this.db.sdts.get([args.osaider.qb64]) : null;
413
403
  let pending = false;
414
404
  const escrow = (material) => {
@@ -802,7 +792,8 @@ export class BasicReplyRouteHandler {
802
792
  * - derives the BADA route base `/end/role`
803
793
  * - applies same-SAID idempotence before acceptance
804
794
  * - evaluates proof and updates proof/`eans.`/`ends.` in its own transaction
805
- * - publishes queries only after that commit; caller-owned writers are refused
795
+ * - publishes queries, then an `endRoleSaved` cue for an accepted reply, only after that commit; caller-owned
796
+ * writers are refused
806
797
  */
807
798
  processReplyEndRole(args) {
808
799
  const allowed = args.route.startsWith("/end/role/add")
@@ -817,11 +808,11 @@ export class BasicReplyRouteHandler {
817
808
  if (!data || typeof data !== "object" || Array.isArray(data)) {
818
809
  throw new ValidationError("Missing one of cid/role/eid in /end/role reply.");
819
810
  }
820
- const cid = "cid" in data && typeof data.cid === "string" ? new Prefixer({ qb64: data.cid }).qb64 : null;
811
+ const cid = "cid" in data ? readPrefixer(data.cid)?.qb64 ?? null : null;
821
812
  const role = "role" in data && typeof data.role === "string" ? data.role : null;
822
- const eid = "eid" in data && typeof data.eid === "string" ? new Prefixer({ qb64: data.eid }).qb64 : null;
813
+ const eid = "eid" in data ? readPrefixer(data.eid)?.qb64 ?? null : null;
823
814
  if (!cid || !role || !eid) {
824
- throw new ValidationError("Missing one of cid/role/eid in /end/role reply.");
815
+ throw new ValidationError("Missing or malformed one of cid/role/eid in /end/role reply.");
825
816
  }
826
817
  if (!isRole(role)) {
827
818
  throw new ValidationError(`Invalid endpoint role ${role}.`);
@@ -855,7 +846,7 @@ export class BasicReplyRouteHandler {
855
846
  if (queryCues.length)
856
847
  this.rvy.cues.extend(queryCues);
857
848
  if (decision.kind === "accept")
858
- this.rvy.onEndRole?.(cid, role, eid, allowed);
849
+ this.rvy.cues.push({ kin: "endRoleSaved", cid, role, eid, allowed });
859
850
  return decision;
860
851
  }
861
852
  /**
@@ -877,14 +868,16 @@ export class BasicReplyRouteHandler {
877
868
  if (!data || typeof data !== "object" || Array.isArray(data)) {
878
869
  throw new ValidationError("Missing one of eid/scheme/url in /loc/scheme reply.");
879
870
  }
880
- const eid = "eid" in data && typeof data.eid === "string" ? new Prefixer({ qb64: data.eid }).qb64 : null;
871
+ const eid = "eid" in data ? readPrefixer(data.eid)?.qb64 ?? null : null;
881
872
  const scheme = "scheme" in data && typeof data.scheme === "string" ? data.scheme : null;
882
873
  const url = "url" in data && typeof data.url === "string" ? data.url : null;
883
874
  if (!eid || !scheme || url === null) {
884
- throw new ValidationError("Missing one of eid/scheme/url in /loc/scheme reply.");
875
+ throw new ValidationError("Missing or malformed one of eid/scheme/url in /loc/scheme reply.");
885
876
  }
886
877
  if (url) {
887
- const parsed = new URL(url);
878
+ const parsed = readUrl(url);
879
+ if (!parsed)
880
+ throw new ValidationError(`Unparseable URL ${url} in /loc/scheme reply.`);
888
881
  if (parsed.protocol && parsed.protocol.slice(0, -1) !== scheme) {
889
882
  throw new ValidationError(`URL ${url} does not match declared scheme ${scheme}.`);
890
883
  }