@fedify/fedify 2.3.0-dev.994 → 2.3.0-pr.809.37

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 (138) hide show
  1. package/README.md +16 -7
  2. package/dist/{assert-DikXweDx.mjs → assert-OguE97r2.mjs} +1 -1
  3. package/dist/{assert_instance_of-C4Ri6VuN.mjs → assert_instance_of-DBC5X09g.mjs} +1 -1
  4. package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-DkVK8oqV.mjs} +1 -1
  5. package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DN60FHPX.mjs} +28 -3
  6. package/dist/{assert_strict_equals-Dmjbg-bA.mjs → assert_strict_equals-XEgZAlrj.mjs} +1 -1
  7. package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-BOkhLGYc.mjs} +1 -1
  8. package/dist/{builder-yAlpiIqP.mjs → builder-Dl5Sdmc9.mjs} +96 -42
  9. package/dist/{chunk-nlSIicah.js → chunk-CRNNMoPX.js} +2 -2
  10. package/dist/circuit-breaker-CSWsyoef.mjs +337 -0
  11. package/dist/{client-z-8dc-e1.d.cts → client-CAM_bQXx.d.cts} +1 -0
  12. package/dist/{client-AtlibPOU.d.ts → client-CSddvgWN.d.ts} +1 -2
  13. package/dist/compat/mod.d.cts +2 -1
  14. package/dist/compat/mod.d.ts +2 -3
  15. package/dist/compat/mod.js +3 -3
  16. package/dist/compat/outgoing-jsonld.test.mjs +4 -4
  17. package/dist/compat/public-audience.test.mjs +4 -4
  18. package/dist/compat/transformers.test.mjs +5 -5
  19. package/dist/{context-BzH2-ajs.d.ts → context-BBVLF7lx.d.cts} +342 -269
  20. package/dist/{context-DJGagtNd.d.cts → context-BU6jSQdo.d.ts} +344 -266
  21. package/dist/{context-Dk_tacqz.mjs → context-DVoTs_wM.mjs} +64 -5
  22. package/dist/{deno-XBVlKnOX.mjs → deno-BID9qTPk.mjs} +1 -1
  23. package/dist/{docloader-DPp-X6gk.mjs → docloader-BYFuVwwi.mjs} +4 -4
  24. package/dist/{esm-DVILvP5e.mjs → esm-vrlUxr60.mjs} +4 -7
  25. package/dist/federation/builder.test.mjs +163 -11
  26. package/dist/federation/circuit-breaker.test.d.mts +2 -0
  27. package/dist/federation/circuit-breaker.test.mjs +446 -0
  28. package/dist/federation/collection.test.mjs +3 -3
  29. package/dist/federation/handler.test.mjs +1770 -29
  30. package/dist/federation/idempotency.test.mjs +5 -5
  31. package/dist/federation/inbox.test.mjs +4 -4
  32. package/dist/federation/keycache.test.mjs +5 -5
  33. package/dist/federation/kv.test.mjs +2 -2
  34. package/dist/federation/metrics.test.d.mts +2 -0
  35. package/dist/federation/metrics.test.mjs +634 -0
  36. package/dist/federation/middleware.test.mjs +4036 -146
  37. package/dist/federation/mod.cjs +195 -14
  38. package/dist/federation/mod.d.cts +6 -4
  39. package/dist/federation/mod.d.ts +6 -6
  40. package/dist/federation/mod.js +192 -14
  41. package/dist/federation/mq.test.mjs +176 -15
  42. package/dist/federation/negotiation.test.mjs +4 -4
  43. package/dist/federation/retry.test.mjs +3 -3
  44. package/dist/federation/router.test.mjs +190 -9
  45. package/dist/federation/send.test.mjs +153 -15
  46. package/dist/federation/temporal.test.d.mts +2 -0
  47. package/dist/federation/temporal.test.mjs +71 -0
  48. package/dist/federation/webfinger.test.mjs +150 -5
  49. package/dist/{http-B4rrzxtg.js → http-B47Jg9iX.js} +994 -64
  50. package/dist/{http-CSlLA54v.mjs → http-CO59u1Yk.mjs} +146 -47
  51. package/dist/{http-BrGqJDXL.cjs → http-CjZwUPiT.cjs} +1099 -61
  52. package/dist/{http-aQzN9Ayi.d.ts → http-VyDTd4G3.d.cts} +15 -3
  53. package/dist/{http-CrGuipxe.d.cts → http-lf8Hsd91.d.ts} +15 -1
  54. package/dist/{key-Bzx--sHD.mjs → key-Dqdlo3mG.mjs} +43 -18
  55. package/dist/{kv-CbLNp3zQ.d.cts → kv-D6hNiMTK.d.ts} +1 -0
  56. package/dist/{kv-cache-DI-Sk7-I.cjs → kv-cache-BQ1-zDKX.cjs} +20 -3
  57. package/dist/{kv-cache-EMIqoIuB.js → kv-cache-Dh2ZH9Gr.js} +20 -3
  58. package/dist/{kv-cache-U__xU4qR.mjs → kv-cache-DhLkLv0P.mjs} +21 -3
  59. package/dist/{kv-GFYnFoOl.d.ts → kv-gJ8LYbxX.d.cts} +1 -3
  60. package/dist/ld-JbCByP5P.mjs +626 -0
  61. package/dist/metrics-ColY3QKj.mjs +815 -0
  62. package/dist/{middleware-zjZ6AFCW.mjs → middleware-Buqzuiaf.mjs} +1 -1
  63. package/dist/{middleware-An4DjES4.cjs → middleware-CWQ3Yi07.cjs} +2318 -623
  64. package/dist/{middleware-K1g6bEkV.mjs → middleware-Cg4EyWsq.mjs} +1639 -373
  65. package/dist/{middleware-nidH7VZH.js → middleware-iAJY2aU-.js} +2260 -556
  66. package/dist/{mod-CR8soWa9.d.ts → mod-B0hW12_O.d.cts} +26 -4
  67. package/dist/mod-C0F6kvgS.d.cts +182 -0
  68. package/dist/{mod-Cr3f-ACa.d.cts → mod-COIAjwRS.d.ts} +26 -2
  69. package/dist/{mod-CMEbIaNh.d.cts → mod-DFvNJcNb.d.ts} +56 -4
  70. package/dist/mod-vPYVoa5n.d.ts +182 -0
  71. package/dist/{mod-CLgIXe9w.d.ts → mod-yvIXFAEi.d.cts} +56 -6
  72. package/dist/mod.cjs +9 -6
  73. package/dist/mod.d.cts +12 -10
  74. package/dist/mod.d.ts +12 -12
  75. package/dist/mod.js +11 -11
  76. package/dist/mq-D-nlpY04.d.ts +208 -0
  77. package/dist/mq-D8uSFzxe.d.cts +208 -0
  78. package/dist/{negotiation-SQvQgUqe.mjs → negotiation-DDstyBvc.mjs} +29 -0
  79. package/dist/nodeinfo/client.test.mjs +4 -4
  80. package/dist/nodeinfo/handler.test.mjs +4 -4
  81. package/dist/nodeinfo/mod.d.cts +2 -1
  82. package/dist/nodeinfo/mod.d.ts +2 -3
  83. package/dist/nodeinfo/mod.js +3 -3
  84. package/dist/nodeinfo/types.test.mjs +3 -3
  85. package/dist/otel/exporter.test.mjs +28 -25
  86. package/dist/otel/mod.cjs +6 -5
  87. package/dist/otel/mod.d.cts +5 -3
  88. package/dist/otel/mod.d.ts +5 -5
  89. package/dist/otel/mod.js +8 -7
  90. package/dist/{outgoing-jsonld-CNmZLixq.mjs → outgoing-jsonld-L_DbOaFe.mjs} +2 -2
  91. package/dist/{owner-C-tqfvxn.mjs → owner-B4LgOmzV.mjs} +2 -2
  92. package/dist/{owner-CptqhsOy.d.cts → owner-CnngXDNJ.d.ts} +2 -1
  93. package/dist/{owner-74ARJ5TL.d.ts → owner-DEvZuyOE.d.cts} +2 -3
  94. package/dist/{proof-BlmRPzrK.mjs → proof-BoSGOq9q.mjs} +26 -8
  95. package/dist/{proof-Bbbwf8_x.js → proof-CzhZuawt.js} +378 -15
  96. package/dist/{proof-BW89QMVB.cjs → proof-DvMI-3vq.cjs} +432 -15
  97. package/dist/runtime/mod.d.cts +1 -0
  98. package/dist/runtime/mod.d.ts +1 -2
  99. package/dist/runtime/mod.js +3 -3
  100. package/dist/{send-05pJLcb6.mjs → send-ByK1Q7DK.mjs} +72 -26
  101. package/dist/sig/http.test.mjs +356 -33
  102. package/dist/sig/key.test.mjs +103 -6
  103. package/dist/sig/ld.test.mjs +696 -7
  104. package/dist/sig/mod.cjs +2 -2
  105. package/dist/sig/mod.d.cts +4 -3
  106. package/dist/sig/mod.d.ts +4 -5
  107. package/dist/sig/mod.js +4 -4
  108. package/dist/sig/owner.test.mjs +6 -6
  109. package/dist/sig/proof.test.mjs +169 -8
  110. package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BBjXFNOb.mjs} +5 -30
  111. package/dist/temporal-LpjyAjKb.mjs +95 -0
  112. package/dist/testing/mod.d.mts +110 -10
  113. package/dist/testing/mod.mjs +1 -2
  114. package/dist/{transformers-ve6e2xcg.js → transformers-BGMIq1cs.js} +2 -2
  115. package/dist/{types-hvL8ElAs.js → types-CAY3OdLq.js} +2 -2
  116. package/dist/utils/docloader.test.mjs +6 -6
  117. package/dist/utils/kv-cache.test.mjs +67 -2
  118. package/dist/utils/mod.cjs +1 -1
  119. package/dist/utils/mod.d.cts +2 -1
  120. package/dist/utils/mod.d.ts +2 -3
  121. package/dist/utils/mod.js +3 -3
  122. package/dist/vocab/cjs.test.mjs +1 -1
  123. package/dist/vocab/mod.d.cts +1 -0
  124. package/dist/vocab/mod.d.ts +1 -2
  125. package/dist/vocab/mod.js +2 -2
  126. package/package.json +16 -16
  127. package/dist/ld-DR78beiv.mjs +0 -279
  128. package/dist/middleware-BFBaR0mF.cjs +0 -4
  129. package/dist/mod-2d12ffz3.d.ts +0 -64
  130. package/dist/mod-D35TRn09.d.cts +0 -62
  131. package/dist/router-CrMLXoOr.mjs +0 -114
  132. package/dist/{activity-listener-ell7W1s9.mjs → activity-listener-tztVvlNb.mjs} +0 -0
  133. package/dist/{assert_equals-Ew3jOFa3.mjs → assert_equals-C-ZRDbaf.mjs} +0 -0
  134. package/dist/{client-D_1QpnWt.mjs → client-ByXmQhYD.mjs} +1 -1
  135. package/dist/{collection-D-HqUuA2.mjs → collection-Cc3DVAhE.mjs} +0 -0
  136. package/dist/{keycache-EGATflN-.mjs → keycache-BeU0LCII.mjs} +0 -0
  137. package/dist/{public-audience-DYFHzm_c.mjs → public-audience-Cvbr2Gzt.mjs} +1 -1
  138. /package/dist/{retry-bMXBL97A.mjs → retry-CXg_MBI-.mjs} +0 -0
@@ -1,8 +1,8 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
- import "urlpattern-polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";
3
3
  import { getLogger } from "@logtape/logtape";
4
4
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
5
- import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
5
+ import { SpanKind, SpanStatusCode, metrics, trace } from "@opentelemetry/api";
6
6
  import { encodeHex } from "byte-encodings/hex";
7
7
  import { Item, decodeDict, encodeDict, encodeItem } from "structured-field-values";
8
8
  import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
@@ -10,7 +10,7 @@ import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } fro
10
10
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
11
11
  //#region deno.json
12
12
  var name = "@fedify/fedify";
13
- var version = "2.3.0-dev.994+9071ca0a";
13
+ var version = "2.3.0-pr.809.37+0574ba5c";
14
14
  //#endregion
15
15
  //#region src/sig/accept.ts
16
16
  /**
@@ -151,6 +151,814 @@ function fulfillAcceptSignature(entry, localKeyId, localAlg) {
151
151
  };
152
152
  }
153
153
  //#endregion
154
+ //#region src/federation/metrics.ts
155
+ var FederationMetrics = class {
156
+ deliverySent;
157
+ deliveryPermanentFailure;
158
+ signatureVerificationFailure;
159
+ signatureVerificationDuration;
160
+ signatureKeyFetchDuration;
161
+ deliveryDuration;
162
+ inboxProcessingDuration;
163
+ httpServerRequestCount;
164
+ httpServerRequestDuration;
165
+ queueTaskEnqueued;
166
+ queueTaskStarted;
167
+ queueTaskCompleted;
168
+ queueTaskFailed;
169
+ queueTaskDuration;
170
+ queueTaskInFlight;
171
+ queueDepth;
172
+ fanoutRecipients;
173
+ inboxActivity;
174
+ outboxActivity;
175
+ circuitBreakerStateChange;
176
+ keyLookup;
177
+ keyLookupDuration;
178
+ documentFetch;
179
+ documentFetchDuration;
180
+ documentCache;
181
+ webFingerHandle;
182
+ webFingerHandleDuration;
183
+ collectionRequest;
184
+ collectionDispatchDuration;
185
+ collectionPageItems;
186
+ collectionTotalItems;
187
+ constructor(meterProvider) {
188
+ const meter = meterProvider.getMeter(name, version);
189
+ this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
190
+ description: "ActivityPub delivery attempts.",
191
+ unit: "{attempt}"
192
+ });
193
+ this.deliveryPermanentFailure = meter.createCounter("activitypub.delivery.permanent_failure", {
194
+ description: "ActivityPub deliveries abandoned as permanent failures.",
195
+ unit: "{failure}"
196
+ });
197
+ this.signatureVerificationFailure = meter.createCounter("activitypub.signature.verification_failure", {
198
+ description: "ActivityPub signature verification failures.",
199
+ unit: "{failure}"
200
+ });
201
+ this.signatureVerificationDuration = meter.createHistogram("activitypub.signature.verification.duration", {
202
+ description: "Duration of ActivityPub signature verification, including local key lookup and remote key fetches.",
203
+ unit: "ms",
204
+ advice: { explicitBucketBoundaries: [
205
+ .1,
206
+ .25,
207
+ .5,
208
+ 1,
209
+ 2.5,
210
+ 5,
211
+ 10,
212
+ 25,
213
+ 50,
214
+ 100,
215
+ 250,
216
+ 500,
217
+ 1e3
218
+ ] }
219
+ });
220
+ this.signatureKeyFetchDuration = meter.createHistogram("activitypub.signature.key_fetch.duration", {
221
+ description: "Duration of public key lookup performed during ActivityPub signature verification.",
222
+ unit: "ms"
223
+ });
224
+ this.deliveryDuration = meter.createHistogram("activitypub.delivery.duration", {
225
+ description: "Duration of ActivityPub delivery attempts.",
226
+ unit: "ms"
227
+ });
228
+ this.inboxProcessingDuration = meter.createHistogram("activitypub.inbox.processing_duration", {
229
+ description: "Duration of ActivityPub inbox listener processing.",
230
+ unit: "ms"
231
+ });
232
+ this.httpServerRequestCount = meter.createCounter("fedify.http.server.request.count", {
233
+ description: "HTTP requests handled by Federation.fetch().",
234
+ unit: "{request}"
235
+ });
236
+ this.httpServerRequestDuration = meter.createHistogram("fedify.http.server.request.duration", {
237
+ description: "Duration of HTTP requests handled by Federation.fetch().",
238
+ unit: "ms",
239
+ advice: { explicitBucketBoundaries: [
240
+ 5,
241
+ 10,
242
+ 25,
243
+ 50,
244
+ 75,
245
+ 100,
246
+ 250,
247
+ 500,
248
+ 750,
249
+ 1e3,
250
+ 2500,
251
+ 5e3,
252
+ 7500,
253
+ 1e4
254
+ ] }
255
+ });
256
+ this.queueTaskEnqueued = meter.createCounter("fedify.queue.task.enqueued", {
257
+ description: "Tasks Fedify enqueued for inbox, outbox, or fanout work.",
258
+ unit: "{task}"
259
+ });
260
+ this.queueTaskStarted = meter.createCounter("fedify.queue.task.started", {
261
+ description: "Tasks Fedify began processing as a queue worker.",
262
+ unit: "{task}"
263
+ });
264
+ this.queueTaskCompleted = meter.createCounter("fedify.queue.task.completed", {
265
+ description: "Queue tasks Fedify finished processing without throwing.",
266
+ unit: "{task}"
267
+ });
268
+ this.queueTaskFailed = meter.createCounter("fedify.queue.task.failed", {
269
+ description: "Queue tasks Fedify abandoned because processing threw.",
270
+ unit: "{task}"
271
+ });
272
+ this.queueTaskDuration = meter.createHistogram("fedify.queue.task.duration", {
273
+ description: "Duration of queue task processing in Fedify workers.",
274
+ unit: "ms",
275
+ advice: { explicitBucketBoundaries: [
276
+ 5,
277
+ 10,
278
+ 25,
279
+ 50,
280
+ 75,
281
+ 100,
282
+ 250,
283
+ 500,
284
+ 750,
285
+ 1e3,
286
+ 2500,
287
+ 5e3,
288
+ 7500,
289
+ 1e4
290
+ ] }
291
+ });
292
+ this.queueTaskInFlight = meter.createUpDownCounter("fedify.queue.task.in_flight", {
293
+ description: "Queue tasks currently being processed in this Fedify process.",
294
+ unit: "{task}"
295
+ });
296
+ this.queueDepth = meter.createObservableGauge("fedify.queue.depth", {
297
+ description: "Messages waiting in configured Fedify queues, as reported by the queue backend.",
298
+ unit: "{message}"
299
+ });
300
+ this.fanoutRecipients = meter.createHistogram("activitypub.fanout.recipients", {
301
+ description: "Number of recipient inboxes produced by an ActivityPub fanout task.",
302
+ unit: "{recipient}"
303
+ });
304
+ this.inboxActivity = meter.createCounter("activitypub.inbox.activity", {
305
+ description: "ActivityPub activities observed at the inbox lifecycle level: queued, processed, retried, rejected, or abandoned.",
306
+ unit: "{activity}"
307
+ });
308
+ this.outboxActivity = meter.createCounter("activitypub.outbox.activity", {
309
+ description: "ActivityPub activities observed at the outbox lifecycle level: queued, retried, or abandoned. Per-recipient delivery counters live on `activitypub.delivery.*`.",
310
+ unit: "{activity}"
311
+ });
312
+ this.circuitBreakerStateChange = meter.createCounter("activitypub.circuit_breaker.state_change", {
313
+ description: "Outbound ActivityPub delivery circuit breaker changes.",
314
+ unit: "{change}"
315
+ });
316
+ this.keyLookup = meter.createCounter("activitypub.key.lookup", {
317
+ description: "Public-key lookup attempts performed by Fedify, including both cache hits and remote fetches.",
318
+ unit: "{lookup}"
319
+ });
320
+ this.keyLookupDuration = meter.createHistogram("activitypub.key.lookup.duration", {
321
+ description: "Duration of public-key lookups performed by Fedify, including any remote fetch.",
322
+ unit: "ms",
323
+ advice: { explicitBucketBoundaries: [
324
+ 5,
325
+ 10,
326
+ 25,
327
+ 50,
328
+ 75,
329
+ 100,
330
+ 250,
331
+ 500,
332
+ 750,
333
+ 1e3,
334
+ 2500,
335
+ 5e3,
336
+ 7500,
337
+ 1e4
338
+ ] }
339
+ });
340
+ this.documentFetch = meter.createCounter("activitypub.document.fetch", {
341
+ description: "Remote JSON-LD document loader invocations made by Fedify-wrapped loaders.",
342
+ unit: "{fetch}"
343
+ });
344
+ this.documentFetchDuration = meter.createHistogram("activitypub.document.fetch.duration", {
345
+ description: "Duration of remote JSON-LD document loader invocations made by Fedify-wrapped loaders.",
346
+ unit: "ms",
347
+ advice: { explicitBucketBoundaries: [
348
+ 5,
349
+ 10,
350
+ 25,
351
+ 50,
352
+ 75,
353
+ 100,
354
+ 250,
355
+ 500,
356
+ 750,
357
+ 1e3,
358
+ 2500,
359
+ 5e3,
360
+ 7500,
361
+ 1e4
362
+ ] }
363
+ });
364
+ this.documentCache = meter.createCounter("activitypub.document.cache", {
365
+ description: "KV-backed document loader cache lookups, with `hit` or `miss` classification.",
366
+ unit: "{lookup}"
367
+ });
368
+ this.webFingerHandle = meter.createCounter("webfinger.handle", {
369
+ description: "Incoming WebFinger requests handled by Fedify, classified by terminal outcome.",
370
+ unit: "{request}"
371
+ });
372
+ this.webFingerHandleDuration = meter.createHistogram("webfinger.handle.duration", {
373
+ description: "Duration of incoming WebFinger request handling in Fedify.",
374
+ unit: "ms",
375
+ advice: { explicitBucketBoundaries: [
376
+ 5,
377
+ 10,
378
+ 25,
379
+ 50,
380
+ 75,
381
+ 100,
382
+ 250,
383
+ 500,
384
+ 750,
385
+ 1e3,
386
+ 2500,
387
+ 5e3,
388
+ 7500,
389
+ 1e4
390
+ ] }
391
+ });
392
+ this.collectionRequest = meter.createCounter("activitypub.collection.request", {
393
+ description: "ActivityPub collection and collection-page requests handled by Fedify.",
394
+ unit: "{request}"
395
+ });
396
+ this.collectionDispatchDuration = meter.createHistogram("activitypub.collection.dispatch.duration", {
397
+ description: "Duration of ActivityPub collection dispatcher callbacks.",
398
+ unit: "ms",
399
+ advice: { explicitBucketBoundaries: [
400
+ 5,
401
+ 10,
402
+ 25,
403
+ 50,
404
+ 75,
405
+ 100,
406
+ 250,
407
+ 500,
408
+ 750,
409
+ 1e3,
410
+ 2500,
411
+ 5e3,
412
+ 7500,
413
+ 1e4
414
+ ] }
415
+ });
416
+ this.collectionPageItems = meter.createHistogram("activitypub.collection.page.items", {
417
+ description: "Number of items Fedify materialized for an ActivityPub collection response.",
418
+ unit: "{item}"
419
+ });
420
+ this.collectionTotalItems = meter.createHistogram("activitypub.collection.total_items", {
421
+ description: "Total item count reported by ActivityPub collection counters.",
422
+ unit: "{item}"
423
+ });
424
+ }
425
+ recordDelivery(inbox, durationMs, success, activityType) {
426
+ const deliveryAttributes = {
427
+ "activitypub.remote.host": getRemoteHost(inbox),
428
+ "activitypub.delivery.success": success
429
+ };
430
+ if (activityType != null) deliveryAttributes["activitypub.activity.type"] = activityType;
431
+ this.deliverySent.add(1, deliveryAttributes);
432
+ this.deliveryDuration.record(durationMs, deliveryAttributes);
433
+ }
434
+ recordPermanentFailure(inbox, statusCode) {
435
+ this.deliveryPermanentFailure.add(1, {
436
+ "activitypub.remote.host": getRemoteHost(inbox),
437
+ "http.response.status_code": statusCode
438
+ });
439
+ }
440
+ recordSignatureVerificationFailure(reason, remoteHost) {
441
+ const attributes = { "activitypub.verification.failure_reason": reason };
442
+ if (remoteHost != null) attributes["activitypub.remote.host"] = remoteHost;
443
+ this.signatureVerificationFailure.add(1, attributes);
444
+ }
445
+ recordSignatureVerificationDuration(durationMs, kind, result, extra = {}) {
446
+ const attributes = {
447
+ "activitypub.signature.kind": kind,
448
+ "activitypub.signature.result": result
449
+ };
450
+ if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
451
+ if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
452
+ if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
453
+ if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
454
+ this.signatureVerificationDuration.record(durationMs, attributes);
455
+ }
456
+ recordSignatureKeyFetchDuration(durationMs, kind, result) {
457
+ this.signatureKeyFetchDuration.record(durationMs, {
458
+ "activitypub.signature.kind": kind,
459
+ "activitypub.signature.key_fetch.result": result
460
+ });
461
+ }
462
+ recordInboxProcessingDuration(activityType, durationMs) {
463
+ this.inboxProcessingDuration.record(durationMs, { "activitypub.activity.type": activityType });
464
+ }
465
+ recordHttpServerRequest(method, endpoint, durationMs, options = {}) {
466
+ const attributes = {
467
+ "http.request.method": normalizeHttpMethod(method),
468
+ "fedify.endpoint": endpoint
469
+ };
470
+ if (options.statusCode != null) attributes["http.response.status_code"] = options.statusCode;
471
+ if (options.routeTemplate != null) attributes["fedify.route.template"] = options.routeTemplate;
472
+ this.httpServerRequestCount.add(1, attributes);
473
+ this.httpServerRequestDuration.record(durationMs, attributes);
474
+ }
475
+ recordQueueTaskEnqueued(common, attempt) {
476
+ const attributes = buildQueueTaskAttributes(common);
477
+ attributes["fedify.queue.task.attempt"] = attempt;
478
+ this.queueTaskEnqueued.add(1, attributes);
479
+ }
480
+ recordQueueTaskStarted(common) {
481
+ this.queueTaskStarted.add(1, buildQueueTaskAttributes(common));
482
+ }
483
+ incrementQueueTaskInFlight(common) {
484
+ this.queueTaskInFlight.add(1, buildQueueTaskInFlightAttributes(common));
485
+ }
486
+ decrementQueueTaskInFlight(common) {
487
+ this.queueTaskInFlight.add(-1, buildQueueTaskInFlightAttributes(common));
488
+ }
489
+ recordQueueTaskOutcome(common, result, durationMs) {
490
+ const attributes = buildQueueTaskAttributes(common);
491
+ attributes["fedify.queue.task.result"] = result;
492
+ if (result === "completed") this.queueTaskCompleted.add(1, attributes);
493
+ else if (result === "failed") this.queueTaskFailed.add(1, attributes);
494
+ this.queueTaskDuration.record(durationMs, attributes);
495
+ }
496
+ recordFanoutRecipients(recipientCount, activityType) {
497
+ const attributes = {};
498
+ if (activityType != null) attributes["activitypub.activity.type"] = activityType;
499
+ this.fanoutRecipients.record(recipientCount, attributes);
500
+ }
501
+ recordInboxActivity(result, activityType) {
502
+ this.inboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
503
+ }
504
+ recordOutboxActivity(result, activityType) {
505
+ this.outboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
506
+ }
507
+ recordCircuitBreakerStateChange(remoteHost, state) {
508
+ this.circuitBreakerStateChange.add(1, {
509
+ "activitypub.remote.host": remoteHost,
510
+ "activitypub.circuit_breaker.state": state
511
+ });
512
+ }
513
+ recordKeyLookup(attrs) {
514
+ const attributes = {
515
+ "activitypub.lookup.kind": "public_key",
516
+ "activitypub.lookup.result": attrs.result,
517
+ "activitypub.cache.enabled": attrs.cacheEnabled
518
+ };
519
+ if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
520
+ if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
521
+ this.keyLookup.add(1, attributes);
522
+ this.keyLookupDuration.record(attrs.durationMs, attributes);
523
+ }
524
+ recordDocumentFetch(attrs) {
525
+ const attributes = {
526
+ "activitypub.lookup.kind": attrs.kind,
527
+ "activitypub.lookup.result": attrs.result
528
+ };
529
+ if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
530
+ if (attrs.cacheEnabled != null) attributes["activitypub.cache.enabled"] = attrs.cacheEnabled;
531
+ if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
532
+ this.documentFetch.add(1, attributes);
533
+ this.documentFetchDuration.record(attrs.durationMs, attributes);
534
+ }
535
+ recordDocumentCache(attrs) {
536
+ const attributes = {
537
+ "activitypub.lookup.kind": attrs.kind,
538
+ "activitypub.lookup.result": attrs.result
539
+ };
540
+ if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
541
+ this.documentCache.add(1, attributes);
542
+ }
543
+ recordWebFingerHandle(attrs) {
544
+ const attributes = { "webfinger.handle.result": attrs.result };
545
+ if (attrs.scheme != null) attributes["webfinger.resource.scheme"] = attrs.scheme;
546
+ if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
547
+ this.webFingerHandle.add(1, attributes);
548
+ this.webFingerHandleDuration.record(attrs.durationMs, attributes);
549
+ }
550
+ recordCollectionRequest(attrs) {
551
+ this.collectionRequest.add(1, buildCollectionAttributes(attrs));
552
+ }
553
+ recordCollectionDispatchDuration(durationMs, attrs) {
554
+ this.collectionDispatchDuration.record(durationMs, buildCollectionAttributes(attrs));
555
+ }
556
+ recordCollectionPageItems(itemCount, attrs) {
557
+ this.collectionPageItems.record(itemCount, buildCollectionAttributes(attrs));
558
+ }
559
+ recordCollectionTotalItems(totalItems, attrs) {
560
+ this.collectionTotalItems.record(totalItems, buildCollectionAttributes(attrs));
561
+ }
562
+ };
563
+ function buildCollectionAttributes(attrs) {
564
+ const attributes = {
565
+ "activitypub.collection.kind": attrs.kind,
566
+ "activitypub.collection.page": attrs.page,
567
+ "activitypub.collection.result": attrs.result,
568
+ "fedify.collection.dispatcher": attrs.dispatcher
569
+ };
570
+ if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
571
+ return attributes;
572
+ }
573
+ function buildActivityLifecycleAttributes(result, activityType) {
574
+ const attributes = { "activitypub.processing.result": result };
575
+ if (activityType != null) attributes["activitypub.activity.type"] = activityType;
576
+ return attributes;
577
+ }
578
+ function buildQueueTaskAttributes(common) {
579
+ const attributes = { "fedify.queue.role": common.role };
580
+ const backend = getQueueBackend(common.queue);
581
+ if (backend != null) attributes["fedify.queue.backend"] = backend;
582
+ const nativeRetrial = common.queue?.nativeRetrial;
583
+ if (typeof nativeRetrial === "boolean") attributes["fedify.queue.native_retrial"] = nativeRetrial;
584
+ if (common.activityType != null) attributes["activitypub.activity.type"] = common.activityType;
585
+ return attributes;
586
+ }
587
+ function buildQueueTaskInFlightAttributes(common) {
588
+ return buildQueueTaskAttributes({
589
+ role: common.role,
590
+ queue: common.queue
591
+ });
592
+ }
593
+ /**
594
+ * Returns the constructor name of the given message queue, when it is a
595
+ * meaningful identifier. Used as a best-effort `fedify.queue.backend`
596
+ * attribute on queue task metrics; returns `undefined` for plain object
597
+ * literals (whose constructor is `Object`) so the attribute does not appear
598
+ * with a non-informative value.
599
+ * @since 2.3.0
600
+ */
601
+ function getQueueBackend(queue) {
602
+ const name = queue?.constructor?.name;
603
+ if (name == null || name === "" || name === "Object") return void 0;
604
+ return name;
605
+ }
606
+ /**
607
+ * Registers a callback for observing queue backend depth.
608
+ * @since 2.3.0
609
+ */
610
+ function registerQueueDepthGauge(meterProvider, entries, options = {}) {
611
+ const uniqueQueues = /* @__PURE__ */ new Map();
612
+ for (const { role, queue } of entries) {
613
+ if (queue?.getDepth == null) continue;
614
+ const roles = uniqueQueues.get(queue);
615
+ if (roles == null) uniqueQueues.set(queue, [role]);
616
+ else if (!roles.includes(role)) roles.push(role);
617
+ }
618
+ if (uniqueQueues.size < 1) return;
619
+ const queueEntries = Array.from(uniqueQueues.entries());
620
+ getFederationMetrics(meterProvider).queueDepth.addCallback(async (observableResult) => {
621
+ await Promise.all(queueEntries.map(async ([queue, roles]) => {
622
+ let depth;
623
+ try {
624
+ depth = await queue.getDepth();
625
+ } catch {
626
+ return;
627
+ }
628
+ if (depth == null) return;
629
+ const attributes = buildQueueDepthAttributes(queue, roles, options);
630
+ observableResult.observe(depth.queued, {
631
+ ...attributes,
632
+ "fedify.queue.depth.state": "queued"
633
+ });
634
+ if (depth.ready != null) observableResult.observe(depth.ready, {
635
+ ...attributes,
636
+ "fedify.queue.depth.state": "ready"
637
+ });
638
+ if (depth.delayed != null) observableResult.observe(depth.delayed, {
639
+ ...attributes,
640
+ "fedify.queue.depth.state": "delayed"
641
+ });
642
+ }));
643
+ });
644
+ }
645
+ function buildQueueDepthAttributes(queue, roles, options) {
646
+ const sortedRoles = roles.toSorted();
647
+ const role = sortedRoles.length === 1 ? sortedRoles[0] : "shared";
648
+ const attributes = { "fedify.queue.role": role };
649
+ if (options.sourceId != null) attributes["fedify.federation.instance_id"] = options.sourceId;
650
+ if (role === "shared") attributes["fedify.queue.roles"] = sortedRoles.join(",");
651
+ const backend = getQueueBackend(queue);
652
+ if (backend != null) attributes["fedify.queue.backend"] = backend;
653
+ const nativeRetrial = queue.nativeRetrial;
654
+ if (typeof nativeRetrial === "boolean") attributes["fedify.queue.native_retrial"] = nativeRetrial;
655
+ return attributes;
656
+ }
657
+ /**
658
+ * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue and,
659
+ * for the initial attempt, also records
660
+ * `activitypub.outbox.activity{queued}`.
661
+ *
662
+ * Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
663
+ * outbox messages with the same metric attributes (role, queue, activity
664
+ * type, attempt), so they share this helper rather than each defining a local
665
+ * closure. Retry enqueues (attempt > 0) intentionally do not record a
666
+ * second `activitypub.outbox.activity{queued}`; retries are reported as
667
+ * `result=retried` from the retry-scheduling site, which has the failure
668
+ * context.
669
+ * @since 2.3.0
670
+ */
671
+ function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
672
+ const metrics = getFederationMetrics(meterProvider);
673
+ metrics.recordQueueTaskEnqueued({
674
+ role: "outbox",
675
+ queue: outboxQueue,
676
+ activityType: message.activityType
677
+ }, message.attempt);
678
+ if (message.attempt === 0) metrics.recordOutboxActivity("queued", message.activityType);
679
+ }
680
+ /**
681
+ * Records `activitypub.fanout.recipients` with the number of recipient
682
+ * inboxes a single fanout produced. The histogram is unitless count
683
+ * (one measurement per fanout enqueue). Recipient URLs are deliberately
684
+ * not recorded; only the activity type, when known.
685
+ * @since 2.3.0
686
+ */
687
+ function recordFanoutRecipients(meterProvider, recipientCount, activityType) {
688
+ getFederationMetrics(meterProvider).recordFanoutRecipients(recipientCount, activityType);
689
+ }
690
+ /**
691
+ * Records one `activitypub.inbox.activity` measurement. The
692
+ * `activitypub.processing.result` attribute is always present;
693
+ * `activitypub.activity.type` is recorded only when Fedify already knows
694
+ * the activity type.
695
+ * @since 2.3.0
696
+ */
697
+ function recordInboxActivity(meterProvider, result, activityType) {
698
+ getFederationMetrics(meterProvider).recordInboxActivity(result, activityType);
699
+ }
700
+ /**
701
+ * Records one `activitypub.outbox.activity` measurement. The
702
+ * `activitypub.processing.result` attribute is always present;
703
+ * `activitypub.activity.type` is recorded only when Fedify already knows
704
+ * the activity type (it is always known for outbox lifecycle events).
705
+ * @since 2.3.0
706
+ */
707
+ function recordOutboxActivity(meterProvider, result, activityType) {
708
+ getFederationMetrics(meterProvider).recordOutboxActivity(result, activityType);
709
+ }
710
+ /**
711
+ * Records one outbound delivery circuit breaker state transition.
712
+ * @since 2.3.0
713
+ */
714
+ function recordCircuitBreakerStateChange(meterProvider, remoteHost, state) {
715
+ getFederationMetrics(meterProvider).recordCircuitBreakerStateChange(remoteHost, state);
716
+ }
717
+ /**
718
+ * Records one measurement on `activitypub.key.lookup` (counter) and
719
+ * `activitypub.key.lookup.duration` (histogram) for a public-key lookup.
720
+ *
721
+ * `activitypub.lookup.kind` is always recorded as `public_key`; the result
722
+ * classification, remote host, HTTP status code (when an HTTP response was
723
+ * received), and `activitypub.cache.enabled` are recorded as attributes on
724
+ * both measurements. Full key URLs and key IDs are deliberately omitted to
725
+ * keep cardinality bounded.
726
+ * @since 2.3.0
727
+ */
728
+ function recordKeyLookup(meterProvider, attrs) {
729
+ getFederationMetrics(meterProvider).recordKeyLookup(attrs);
730
+ }
731
+ /**
732
+ * Records one measurement each on `activitypub.document.fetch` (counter)
733
+ * and `activitypub.document.fetch.duration` (histogram) for one remote
734
+ * JSON-LD document loader invocation, with bounded
735
+ * `activitypub.lookup.kind` and `activitypub.lookup.result` attributes
736
+ * plus the optional remote-host, cache-enabled, and HTTP status-code
737
+ * attributes. Counter and histogram are always recorded together so
738
+ * aggregate rate and latency views stay in sync.
739
+ * @since 2.3.0
740
+ */
741
+ function recordDocumentFetch(meterProvider, attrs) {
742
+ getFederationMetrics(meterProvider).recordDocumentFetch(attrs);
743
+ }
744
+ /**
745
+ * Records one `activitypub.document.cache` measurement, classifying the
746
+ * lookup as `hit` (the cache returned an entry) or `miss` (the cache was
747
+ * consulted and returned nothing, prompting a delegate fetch).
748
+ * @since 2.3.0
749
+ */
750
+ function recordDocumentCache(meterProvider, attrs) {
751
+ getFederationMetrics(meterProvider).recordDocumentCache(attrs);
752
+ }
753
+ /**
754
+ * Records one measurement on `webfinger.handle` (counter) and
755
+ * `webfinger.handle.duration` (histogram) for an incoming WebFinger
756
+ * request handled by Fedify. Counter and histogram are always recorded
757
+ * together, with `webfinger.handle.result` set to one of `resolved`,
758
+ * `invalid`, `not_found`, `tombstoned`, or `error`. The queried
759
+ * resource string is deliberately excluded; it remains on the
760
+ * `webfinger.handle` span for trace-level investigation.
761
+ * @since 2.3.0
762
+ */
763
+ function recordWebFingerHandle(meterProvider, attrs) {
764
+ getFederationMetrics(meterProvider).recordWebFingerHandle(attrs);
765
+ }
766
+ /**
767
+ * Records one `activitypub.collection.request` measurement for a
768
+ * collection or collection-page request handled by Fedify.
769
+ * @since 2.3.0
770
+ */
771
+ function recordCollectionRequest(meterProvider, attrs) {
772
+ getFederationMetrics(meterProvider).recordCollectionRequest(attrs);
773
+ }
774
+ /**
775
+ * Records one `activitypub.collection.dispatch.duration` measurement for a
776
+ * collection dispatcher callback invocation.
777
+ * @since 2.3.0
778
+ */
779
+ function recordCollectionDispatchDuration(meterProvider, durationMs, attrs) {
780
+ getFederationMetrics(meterProvider).recordCollectionDispatchDuration(durationMs, attrs);
781
+ }
782
+ /**
783
+ * Records one `activitypub.collection.page.items` measurement when Fedify
784
+ * has materialized collection items in memory.
785
+ * @since 2.3.0
786
+ */
787
+ function recordCollectionPageItems(meterProvider, itemCount, attrs) {
788
+ getFederationMetrics(meterProvider).recordCollectionPageItems(itemCount, attrs);
789
+ }
790
+ /**
791
+ * Records one `activitypub.collection.total_items` measurement when a
792
+ * collection counter has already reported a total item count.
793
+ * @since 2.3.0
794
+ */
795
+ function recordCollectionTotalItems(meterProvider, totalItems, attrs) {
796
+ getFederationMetrics(meterProvider).recordCollectionTotalItems(totalItems, attrs);
797
+ }
798
+ /**
799
+ * Classifies a thrown value from a key or document fetch into the bounded
800
+ * {@link LookupResult} taxonomy and, when an HTTP response was received,
801
+ * surfaces its status code.
802
+ *
803
+ * - `FetchError` with a `Response` whose status is `404` or `410`:
804
+ * `result=not_found` and the response status code.
805
+ * - `FetchError` with any other `Response`: `result=error` and the
806
+ * response status code.
807
+ * - `FetchError` without a `Response`: `result=network_error`.
808
+ * - An `AbortError` (typically from a cancelled fetch): `result=network_error`.
809
+ * - A bare `TypeError` (the shape native `fetch()` raises on DNS, connect,
810
+ * and TLS failures before any response is observed):
811
+ * `result=network_error`.
812
+ * - Any other value: `result=error`.
813
+ * @since 2.3.0
814
+ */
815
+ function classifyFetchError(error) {
816
+ if (error instanceof FetchError) {
817
+ if (error.response != null) {
818
+ const status = error.response.status;
819
+ return {
820
+ result: status === 404 || status === 410 ? "not_found" : "error",
821
+ statusCode: status
822
+ };
823
+ }
824
+ return { result: "network_error" };
825
+ }
826
+ if (isAbortError$1(error)) return { result: "network_error" };
827
+ if (error instanceof TypeError) return { result: "network_error" };
828
+ return { result: "error" };
829
+ }
830
+ /**
831
+ * Wraps a {@link DocumentLoader} so each invocation records one
832
+ * measurement on `activitypub.document.fetch` (counter) and one on
833
+ * `activitypub.document.fetch.duration` (histogram), classifying the
834
+ * outcome via {@link classifyFetchError} when the wrapped loader throws
835
+ * and as `fetched` on success. The wrapper rethrows whatever the
836
+ * wrapped loader throws so caller behavior is unchanged.
837
+ *
838
+ * The wrapper records the host of the requested URL, including any
839
+ * non-default port, on `activitypub.remote.host` when the URL parses; full
840
+ * URLs, paths, and query strings are deliberately excluded to keep
841
+ * cardinality bounded.
842
+ * HTTP status codes are recorded only when the failure carries a
843
+ * `Response` (currently, when the wrapped loader throws a
844
+ * {@link FetchError} with a non-`null` `response`).
845
+ * @since 2.3.0
846
+ */
847
+ function instrumentDocumentLoader(loader, options) {
848
+ const meterProvider = options.meterProvider;
849
+ if (meterProvider == null) return loader;
850
+ return async (url, opts) => {
851
+ const start = performance.now();
852
+ let remoteUrl;
853
+ try {
854
+ remoteUrl = new URL(url);
855
+ } catch {
856
+ remoteUrl = void 0;
857
+ }
858
+ try {
859
+ const result = await loader(url, opts);
860
+ recordDocumentFetch(meterProvider, {
861
+ durationMs: getDurationMs(start),
862
+ kind: options.kind,
863
+ result: "fetched",
864
+ remoteUrl,
865
+ cacheEnabled: options.cacheEnabled
866
+ });
867
+ return result;
868
+ } catch (error) {
869
+ const classified = classifyFetchError(error);
870
+ recordDocumentFetch(meterProvider, {
871
+ durationMs: getDurationMs(start),
872
+ kind: options.kind,
873
+ result: classified.result,
874
+ remoteUrl,
875
+ cacheEnabled: options.cacheEnabled,
876
+ statusCode: classified.statusCode
877
+ });
878
+ throw error;
879
+ }
880
+ };
881
+ }
882
+ /**
883
+ * Times an awaited public key fetch and records exactly one
884
+ * `activitypub.signature.key_fetch.duration` measurement, classifying the
885
+ * outcome as `hit`, `fetched`, or `error` based on the `cached` flag and
886
+ * whether the returned key is non-null. Errors thrown by the fetch are
887
+ * reported as `error` and rethrown, so verifier behavior is unchanged.
888
+ *
889
+ * Shared by the three signature verifiers (HTTP, Linked Data, Object
890
+ * Integrity Proofs); the only per-call variation is the
891
+ * `activitypub.signature.kind` attribute value.
892
+ * @since 2.3.0
893
+ */
894
+ async function measureSignatureKeyFetch(meterProvider, kind, fetch) {
895
+ const start = performance.now();
896
+ try {
897
+ const result = await fetch();
898
+ getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, result.key != null ? result.cached ? "hit" : "fetched" : "error");
899
+ return result;
900
+ } catch (error) {
901
+ getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, "error");
902
+ throw error;
903
+ }
904
+ }
905
+ /**
906
+ * Whether the given thrown value is an `AbortError`.
907
+ *
908
+ * `processQueuedTask` distinguishes aborted tasks (recorded as
909
+ * `fedify.queue.task.result=aborted`) from other failures so that backend
910
+ * shutdown signals do not inflate the `fedify.queue.task.failed` counter.
911
+ * @since 2.3.0
912
+ */
913
+ function isAbortError$1(error) {
914
+ if (error == null || typeof error !== "object") return false;
915
+ const name = error.name;
916
+ return typeof name === "string" && name === "AbortError";
917
+ }
918
+ const KNOWN_HTTP_METHODS = new Set([
919
+ "CONNECT",
920
+ "DELETE",
921
+ "GET",
922
+ "HEAD",
923
+ "OPTIONS",
924
+ "PATCH",
925
+ "POST",
926
+ "PUT",
927
+ "QUERY",
928
+ "TRACE"
929
+ ]);
930
+ function normalizeHttpMethod(method) {
931
+ const upper = method.toUpperCase();
932
+ return KNOWN_HTTP_METHODS.has(upper) ? upper : "_OTHER";
933
+ }
934
+ const federationMetrics = /* @__PURE__ */ new WeakMap();
935
+ /**
936
+ * Gets the cached Fedify metric instruments for a meter provider.
937
+ * @since 2.3.0
938
+ */
939
+ function getFederationMetrics(meterProvider = metrics.getMeterProvider()) {
940
+ let instruments = federationMetrics.get(meterProvider);
941
+ if (instruments == null) {
942
+ instruments = new FederationMetrics(meterProvider);
943
+ federationMetrics.set(meterProvider, instruments);
944
+ }
945
+ return instruments;
946
+ }
947
+ /**
948
+ * Gets the bounded remote host attribute value for a URL.
949
+ * @since 2.3.0
950
+ */
951
+ function getRemoteHost(url) {
952
+ return url.host;
953
+ }
954
+ /**
955
+ * Gets an elapsed duration in milliseconds from a `performance.now()` value.
956
+ * @since 2.3.0
957
+ */
958
+ function getDurationMs(start) {
959
+ return Math.max(0, performance.now() - start);
960
+ }
961
+ //#endregion
154
962
  //#region src/sig/key.ts
155
963
  /**
156
964
  * Checks if the given key is valid and supported. No-op if the key is valid,
@@ -451,24 +1259,48 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
451
1259
  };
452
1260
  }
453
1261
  async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, onFetchError) {
454
- const logger = getLogger([
455
- "fedify",
456
- "sig",
457
- "key"
458
- ]);
459
- const keyId = cacheKey.href;
460
- const keyCache = options.keyCache;
461
- const cached = await getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger);
462
- if (cached?.key === null && cached.cached) return await onCachedUnavailable(cacheKey, keyId, keyCache, logger);
463
- if (cached != null) return cached;
464
- logger.debug("Fetching key {keyId} to verify signature...", { keyId });
465
- let document;
1262
+ const start = performance.now();
1263
+ let outcome = { result: "error" };
466
1264
  try {
467
- document = (await (options.documentLoader ?? getDocumentLoader())(keyId)).document;
468
- } catch (error) {
469
- return await onFetchError(error, cacheKey, keyId, keyCache, logger);
1265
+ const logger = getLogger([
1266
+ "fedify",
1267
+ "sig",
1268
+ "key"
1269
+ ]);
1270
+ const keyId = cacheKey.href;
1271
+ const keyCache = options.keyCache;
1272
+ const cached = await getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger);
1273
+ if (cached?.key === null && cached.cached) {
1274
+ const cachedUnavailable = await onCachedUnavailable(cacheKey, keyId, keyCache, logger);
1275
+ outcome = { result: "hit" };
1276
+ return cachedUnavailable;
1277
+ }
1278
+ if (cached != null) {
1279
+ outcome = { result: "hit" };
1280
+ return cached;
1281
+ }
1282
+ logger.debug("Fetching key {keyId} to verify signature...", { keyId });
1283
+ let document;
1284
+ try {
1285
+ document = (await (options.documentLoader ?? getDocumentLoader())(keyId)).document;
1286
+ } catch (error) {
1287
+ const classified = classifyFetchError(error);
1288
+ const errored = await onFetchError(error, cacheKey, keyId, keyCache, logger);
1289
+ outcome = classified;
1290
+ return errored;
1291
+ }
1292
+ const resolved = await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
1293
+ outcome = { result: resolved.key != null ? "fetched" : "invalid" };
1294
+ return resolved;
1295
+ } finally {
1296
+ recordKeyLookup(options.meterProvider, {
1297
+ durationMs: getDurationMs(start),
1298
+ result: outcome.result,
1299
+ remoteUrl: cacheKey,
1300
+ cacheEnabled: options.keyCache != null,
1301
+ statusCode: outcome.statusCode
1302
+ });
470
1303
  }
471
- return await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
472
1304
  }
473
1305
  async function fetchKeyInternal(keyId, cls, options = {}) {
474
1306
  return await fetchKeyWithResult(typeof keyId === "string" ? new URL(keyId) : keyId, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
@@ -496,6 +1328,7 @@ async function fetchKeyInternal(keyId, cls, options = {}) {
496
1328
  //#endregion
497
1329
  //#region src/sig/http.ts
498
1330
  const DEFAULT_MAX_REDIRECTION = 20;
1331
+ const DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS = 100;
499
1332
  /**
500
1333
  * Signs a request using the given private key.
501
1334
  * @param request The request to sign.
@@ -798,6 +1631,27 @@ function parseKeyId(value) {
798
1631
  function getKeyFetchErrorName(error) {
799
1632
  return error.name || error.constructor.name || "Error";
800
1633
  }
1634
+ /**
1635
+ * Known draft-cavage `algorithm` parameter values, used to keep the
1636
+ * `http_signatures.algorithm` metric attribute on a bounded set. The header
1637
+ * field is attacker-controlled and not used to select the verification
1638
+ * algorithm, so unknown values are dropped from the metric to prevent
1639
+ * cardinality blow-up.
1640
+ */
1641
+ const DRAFT_KNOWN_ALGORITHMS = new Set([
1642
+ "ecdsa-sha256",
1643
+ "ecdsa-sha384",
1644
+ "ecdsa-sha512",
1645
+ "ed25519",
1646
+ "hs2019",
1647
+ "rsa-sha1",
1648
+ "rsa-sha256",
1649
+ "rsa-sha512"
1650
+ ]);
1651
+ function classifyHttpVerifyResult(result) {
1652
+ if (result.verified) return "verified";
1653
+ return result.reason.type === "noSignature" ? "missing" : "rejected";
1654
+ }
801
1655
  function recordVerificationResult(span, result) {
802
1656
  span.setAttribute("http_signatures.verified", result.verified);
803
1657
  if (result.verified === true) return;
@@ -841,27 +1695,37 @@ async function verifyRequestDetailed(request, options = {}) {
841
1695
  span.setAttribute(ATTR_URL_FULL, request.url);
842
1696
  for (const [name, value] of request.headers) span.setAttribute(ATTR_HTTP_REQUEST_HEADER(name), value);
843
1697
  }
1698
+ const start = performance.now();
1699
+ const metricsContext = {};
1700
+ let result;
1701
+ let threw = false;
844
1702
  try {
845
1703
  let spec = options.spec;
846
1704
  if (spec == null) spec = request.headers.has("Signature-Input") ? "rfc9421" : "draft-cavage-http-signatures-12";
847
- let result;
848
- if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, options);
849
- else result = await verifyRequestDraft(request, span, options);
1705
+ if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, metricsContext, options);
1706
+ else result = await verifyRequestDraft(request, span, metricsContext, options);
850
1707
  recordVerificationResult(span, result);
851
1708
  if (!result.verified) span.setStatus({ code: SpanStatusCode.ERROR });
852
1709
  return result;
853
1710
  } catch (error) {
1711
+ threw = true;
854
1712
  span.setStatus({
855
1713
  code: SpanStatusCode.ERROR,
856
1714
  message: String(error)
857
1715
  });
858
1716
  throw error;
859
1717
  } finally {
1718
+ const classified = threw ? "error" : classifyHttpVerifyResult(result);
1719
+ const failureReason = result != null && !result.verified && result.reason.type !== "noSignature" ? result.reason.type : void 0;
1720
+ getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "http", classified, {
1721
+ algorithm: metricsContext.algorithm,
1722
+ failureReason
1723
+ });
860
1724
  span.end();
861
1725
  }
862
1726
  });
863
1727
  }
864
- async function verifyRequestDraft(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
1728
+ async function verifyRequestDraft(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
865
1729
  const logger = getLogger([
866
1730
  "fedify",
867
1731
  "sig",
@@ -1009,13 +1873,18 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
1009
1873
  const keyIdUrl = parseKeyId(keyId);
1010
1874
  if (keyIdUrl == null) return invalidSignatureResult(null);
1011
1875
  span?.setAttribute("http_signatures.key_id", keyId);
1012
- if ("algorithm" in sigValues) span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
1013
- const { key, cached, fetchError } = await fetchKeyDetailed(keyIdUrl, CryptographicKey, {
1876
+ if ("algorithm" in sigValues) {
1877
+ span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
1878
+ const normalizedAlgorithm = sigValues.algorithm.toLowerCase();
1879
+ if (DRAFT_KNOWN_ALGORITHMS.has(normalizedAlgorithm)) metricsContext.algorithm = normalizedAlgorithm;
1880
+ }
1881
+ const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyIdUrl, CryptographicKey, {
1014
1882
  documentLoader,
1015
1883
  contextLoader,
1016
1884
  keyCache,
1017
- tracerProvider
1018
- });
1885
+ tracerProvider,
1886
+ meterProvider
1887
+ }));
1019
1888
  if (fetchError != null) return keyFetchErrorResult(keyIdUrl, fetchError);
1020
1889
  if (key == null) return invalidSignatureResult(keyIdUrl);
1021
1890
  const headerNames = headers.split(/\s+/g);
@@ -1037,7 +1906,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
1037
1906
  signature,
1038
1907
  message
1039
1908
  });
1040
- return await verifyRequestDetailed(originalRequest, {
1909
+ return await verifyRequestDraft(originalRequest, span, metricsContext, {
1041
1910
  documentLoader,
1042
1911
  contextLoader,
1043
1912
  timeWindow,
@@ -1045,7 +1914,9 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
1045
1914
  keyCache: {
1046
1915
  get: () => Promise.resolve(void 0),
1047
1916
  set: async (keyId, key) => await keyCache?.set(keyId, key)
1048
- }
1917
+ },
1918
+ meterProvider,
1919
+ tracerProvider
1049
1920
  });
1050
1921
  }
1051
1922
  logger.debug("Failed to verify with the fetched key {keyId}; signature {signature} is invalid. Check if the key is correct or if the signed message is correct. The message to sign is:\n{message}", {
@@ -1121,7 +1992,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
1121
1992
  }
1122
1993
  return false;
1123
1994
  }
1124
- async function verifyRequestRfc9421(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
1995
+ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
1125
1996
  const logger = getLogger([
1126
1997
  "fedify",
1127
1998
  "sig",
@@ -1155,9 +2026,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1155
2026
  return invalidSignatureResult(null);
1156
2027
  }
1157
2028
  let failure = noSignatureResult();
2029
+ let failureAlgorithm;
2030
+ const setFailure = (result, algorithm) => {
2031
+ failure = result;
2032
+ failureAlgorithm = algorithm;
2033
+ };
1158
2034
  for (const sigName of signatureNames) {
1159
2035
  if (!signatures[sigName]) {
1160
- failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
2036
+ setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
1161
2037
  continue;
1162
2038
  }
1163
2039
  const sigInput = signatureInputs[sigName];
@@ -1168,7 +2044,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1168
2044
  signatureName: sigName,
1169
2045
  signatureInput: signatureInputHeader
1170
2046
  });
1171
- failure = invalidSignatureResult(null);
2047
+ setFailure(invalidSignatureResult(null));
1172
2048
  continue;
1173
2049
  }
1174
2050
  if (!sigInput.created) {
@@ -1176,7 +2052,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1176
2052
  signatureName: sigName,
1177
2053
  signatureInput: signatureInputHeader
1178
2054
  });
1179
- failure = invalidSignatureResult(keyId);
2055
+ setFailure(invalidSignatureResult(keyId));
1180
2056
  continue;
1181
2057
  }
1182
2058
  const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
@@ -1188,14 +2064,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1188
2064
  created: signatureCreated.toString(),
1189
2065
  now: now.toString()
1190
2066
  });
1191
- failure = invalidSignatureResult(keyId);
2067
+ setFailure(invalidSignatureResult(keyId));
1192
2068
  continue;
1193
2069
  } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
1194
2070
  logger.debug("Failed to verify; signature created time is too far in the past.", {
1195
2071
  created: signatureCreated.toString(),
1196
2072
  now: now.toString()
1197
2073
  });
1198
- failure = invalidSignatureResult(keyId);
2074
+ setFailure(invalidSignatureResult(keyId));
1199
2075
  continue;
1200
2076
  }
1201
2077
  }
@@ -1203,34 +2079,35 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1203
2079
  const contentDigestHeader = request.headers.get("Content-Digest");
1204
2080
  if (!contentDigestHeader) {
1205
2081
  logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
1206
- failure = invalidSignatureResult(keyId);
2082
+ setFailure(invalidSignatureResult(keyId));
1207
2083
  continue;
1208
2084
  }
1209
2085
  if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
1210
2086
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
1211
- failure = invalidSignatureResult(keyId);
2087
+ setFailure(invalidSignatureResult(keyId));
1212
2088
  continue;
1213
2089
  }
1214
2090
  }
1215
2091
  span?.setAttribute("http_signatures.key_id", sigInput.keyId);
1216
2092
  span?.setAttribute("http_signatures.created", sigInput.created.toString());
1217
2093
  if (keyId == null) {
1218
- failure = invalidSignatureResult(null);
2094
+ setFailure(invalidSignatureResult(null));
1219
2095
  continue;
1220
2096
  }
1221
- const { key, cached, fetchError } = await fetchKeyDetailed(keyId, CryptographicKey, {
2097
+ const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, CryptographicKey, {
1222
2098
  documentLoader,
1223
2099
  contextLoader,
1224
2100
  keyCache,
1225
- tracerProvider
1226
- });
2101
+ tracerProvider,
2102
+ meterProvider
2103
+ }));
1227
2104
  if (fetchError != null) {
1228
- failure = keyFetchErrorResult(keyId, fetchError);
2105
+ setFailure(keyFetchErrorResult(keyId, fetchError));
1229
2106
  continue;
1230
2107
  }
1231
2108
  if (!key) {
1232
2109
  logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
1233
- failure = invalidSignatureResult(keyId);
2110
+ setFailure(invalidSignatureResult(keyId));
1234
2111
  continue;
1235
2112
  }
1236
2113
  let alg = sigInput.alg?.toLowerCase();
@@ -1242,12 +2119,13 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1242
2119
  }
1243
2120
  if (alg) span?.setAttribute("http_signatures.algorithm", alg);
1244
2121
  const algorithm = alg && rfc9421AlgorithmMap[alg];
2122
+ const candidateAlgorithm = algorithm ? alg : void 0;
1245
2123
  if (!algorithm) {
1246
2124
  logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
1247
2125
  algorithm: sigInput.alg,
1248
2126
  supported: Object.keys(rfc9421AlgorithmMap)
1249
2127
  });
1250
- failure = invalidSignatureResult(keyId);
2128
+ setFailure(invalidSignatureResult(keyId));
1251
2129
  continue;
1252
2130
  }
1253
2131
  let signatureBase;
@@ -1258,20 +2136,22 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1258
2136
  error,
1259
2137
  signatureInput: sigInput
1260
2138
  });
1261
- failure = invalidSignatureResult(keyId);
2139
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1262
2140
  continue;
1263
2141
  }
1264
2142
  const signatureBaseBytes = new TextEncoder().encode(signatureBase);
1265
2143
  span?.setAttribute("http_signatures.signature", encodeHex(sigBytes));
1266
2144
  try {
1267
- if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) return {
1268
- verified: true,
1269
- key,
1270
- signatureLabel: sigName
1271
- };
1272
- else if (cached) {
2145
+ if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) {
2146
+ metricsContext.algorithm = candidateAlgorithm;
2147
+ return {
2148
+ verified: true,
2149
+ key,
2150
+ signatureLabel: sigName
2151
+ };
2152
+ } else if (cached) {
1273
2153
  logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
1274
- return await verifyRequestDetailed(originalRequest, {
2154
+ return await verifyRequestRfc9421(originalRequest, span, metricsContext, {
1275
2155
  documentLoader,
1276
2156
  contextLoader,
1277
2157
  timeWindow,
@@ -1280,14 +2160,16 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1280
2160
  get: () => Promise.resolve(void 0),
1281
2161
  set: async (keyId, key) => await keyCache?.set(keyId, key)
1282
2162
  },
1283
- spec: "rfc9421"
2163
+ spec: "rfc9421",
2164
+ meterProvider,
2165
+ tracerProvider
1284
2166
  });
1285
2167
  } else {
1286
2168
  logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
1287
2169
  keyId: sigInput.keyId,
1288
2170
  signatureBase
1289
2171
  });
1290
- failure = invalidSignatureResult(keyId);
2172
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1291
2173
  }
1292
2174
  } catch (error) {
1293
2175
  logger.debug("Error during signature verification: {error}", {
@@ -1295,9 +2177,10 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1295
2177
  keyId: sigInput.keyId,
1296
2178
  algorithm: sigInput.alg
1297
2179
  });
1298
- failure = invalidSignatureResult(keyId);
2180
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1299
2181
  }
1300
2182
  }
2183
+ metricsContext.algorithm = failureAlgorithm;
1301
2184
  return failure;
1302
2185
  }
1303
2186
  /**
@@ -1324,6 +2207,59 @@ function createRedirectRequest(request, location, body) {
1324
2207
  cache: request.cache
1325
2208
  });
1326
2209
  }
2210
+ async function fetchDoubleKnockRequest(request, signedRequest, signal) {
2211
+ const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
2212
+ for (let attempt = 1;; attempt++) try {
2213
+ return await fetch(signedRequest, {
2214
+ redirect: "manual",
2215
+ signal
2216
+ });
2217
+ } catch (error) {
2218
+ const abortedSignal = getAbortedSignal(signal, request.signal, signedRequest.signal);
2219
+ if (abortedSignal != null) throw getAbortReason(abortedSignal);
2220
+ if (isAbortError(error)) throw error;
2221
+ if (attempt >= maxAttempts) throw createFetchError(request.url, error);
2222
+ await sleep(DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS, signal, request.signal, signedRequest.signal);
2223
+ }
2224
+ }
2225
+ function createFetchError(url, cause) {
2226
+ const error = new FetchError(url, cause instanceof Error ? cause.message : String(cause));
2227
+ error.cause = cause;
2228
+ return error;
2229
+ }
2230
+ function isAbortError(error) {
2231
+ return error instanceof Error && error.name === "AbortError";
2232
+ }
2233
+ async function sleep(ms, ...signals) {
2234
+ const abortSignals = signals.filter((signal) => signal != null);
2235
+ const abortedSignal = getAbortedSignal(...abortSignals);
2236
+ if (abortedSignal != null) throw getAbortReason(abortedSignal);
2237
+ if (abortSignals.length < 1) {
2238
+ await new Promise((resolve) => setTimeout(resolve, ms));
2239
+ return;
2240
+ }
2241
+ await new Promise((resolve, reject) => {
2242
+ const removeAbortListeners = () => {
2243
+ for (const signal of abortSignals) signal.removeEventListener("abort", handleAbort);
2244
+ };
2245
+ const timeout = setTimeout(() => {
2246
+ removeAbortListeners();
2247
+ resolve();
2248
+ }, ms);
2249
+ function handleAbort(event) {
2250
+ clearTimeout(timeout);
2251
+ removeAbortListeners();
2252
+ reject(getAbortReason(event.currentTarget));
2253
+ }
2254
+ for (const signal of abortSignals) signal.addEventListener("abort", handleAbort, { once: true });
2255
+ });
2256
+ }
2257
+ function getAbortedSignal(...signals) {
2258
+ return signals.find((signal) => signal?.aborted);
2259
+ }
2260
+ function getAbortReason(signal) {
2261
+ return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
2262
+ }
1327
2263
  /**
1328
2264
  * Performs a double-knock request to the given URL. For the details of
1329
2265
  * double-knocking, see
@@ -1350,10 +2286,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
1350
2286
  body
1351
2287
  });
1352
2288
  log?.(signedRequest);
1353
- let response = await fetch(signedRequest, {
1354
- redirect: "manual",
1355
- signal
1356
- });
2289
+ let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
1357
2290
  if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
1358
2291
  if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
1359
2292
  const redirectRequest = createRedirectRequest(request, response.headers.get("Location"), body);
@@ -1428,10 +2361,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
1428
2361
  body
1429
2362
  });
1430
2363
  log?.(signedRequest);
1431
- response = await fetch(signedRequest, {
1432
- redirect: "manual",
1433
- signal
1434
- });
2364
+ response = await fetchDoubleKnockRequest(request, signedRequest, signal);
1435
2365
  if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
1436
2366
  if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
1437
2367
  const redirectRequest = createRedirectRequest(request, response.headers.get("Location"), body);
@@ -1472,4 +2402,4 @@ function timingSafeEqual(a, b) {
1472
2402
  return result === 0;
1473
2403
  }
1474
2404
  //#endregion
1475
- export { version as _, verifyRequestDetailed as a, fetchKeyDetailed as c, validateCryptoKey as d, formatAcceptSignature as f, name as g, validateAcceptSignature as h, verifyRequest as i, generateCryptoKeyPair as l, parseAcceptSignature as m, parseRfc9421SignatureInput as n, exportJwk as o, fulfillAcceptSignature as p, signRequest as r, fetchKey as s, doubleKnock as t, importJwk as u };
2405
+ export { formatAcceptSignature as A, recordDocumentCache as C, recordOutboxEnqueue as D, recordOutboxActivity as E, version as F, parseAcceptSignature as M, validateAcceptSignature as N, recordWebFingerHandle as O, name as P, recordCollectionTotalItems as S, recordInboxActivity as T, measureSignatureKeyFetch as _, verifyRequestDetailed as a, recordCollectionPageItems as b, fetchKeyDetailed as c, validateCryptoKey as d, getDurationMs as f, isAbortError$1 as g, instrumentDocumentLoader as h, verifyRequest as i, fulfillAcceptSignature as j, registerQueueDepthGauge as k, generateCryptoKeyPair as l, getRemoteHost as m, parseRfc9421SignatureInput as n, exportJwk as o, getFederationMetrics as p, signRequest as r, fetchKey as s, doubleKnock as t, importJwk as u, recordCircuitBreakerStateChange as v, recordFanoutRecipients as w, recordCollectionRequest as x, recordCollectionDispatchDuration as y };