@fedify/fedify 2.3.0-dev.1114 → 2.3.0-dev.1119

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 (45) hide show
  1. package/dist/{builder-YlEusQth.mjs → builder-Ond_h57y.mjs} +2 -2
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{deno-CF3jMgip.mjs → deno-DVsHS7rA.mjs} +1 -1
  4. package/dist/{docloader-BENj6vQ4.mjs → docloader-WsWfKaE5.mjs} +2 -2
  5. package/dist/federation/builder.test.mjs +1 -1
  6. package/dist/federation/handler.test.mjs +2 -2
  7. package/dist/federation/idempotency.test.mjs +2 -2
  8. package/dist/federation/metrics.test.d.mts +2 -0
  9. package/dist/federation/metrics.test.mjs +107 -0
  10. package/dist/federation/middleware.test.mjs +386 -6
  11. package/dist/federation/mod.cjs +1 -1
  12. package/dist/federation/mod.js +1 -1
  13. package/dist/federation/send.test.mjs +3 -3
  14. package/dist/federation/webfinger.test.mjs +1 -1
  15. package/dist/{http-CpzZ9zsb.js → http-CouJSFVK.js} +73 -5
  16. package/dist/{http-CKCgOPkX.cjs → http-CubOB9wq.cjs} +90 -4
  17. package/dist/{http-BmOZYc-8.mjs → http-DUV8ysti.mjs} +3 -3
  18. package/dist/{key-B4I8H5Lc.mjs → key-BoWaYRHm.mjs} +1 -1
  19. package/dist/{kv-cache-Wc5ezcVW.js → kv-cache-DBNpsneh.js} +1 -1
  20. package/dist/{kv-cache-DY-XWOqM.cjs → kv-cache-Dz31ATUT.cjs} +1 -1
  21. package/dist/{ld-B5D5THhl.mjs → ld-B5K1mSuG.mjs} +3 -3
  22. package/dist/{metrics-ek3ilf6c.mjs → metrics-C4attqv0.mjs} +73 -5
  23. package/dist/{middleware-EI7OU6BR.mjs → middleware-BDKFRjue.mjs} +1 -1
  24. package/dist/{middleware-EqTYPG4F.cjs → middleware-CmsDtIHI.cjs} +33 -14
  25. package/dist/{middleware-CuZbBw-N.js → middleware-Dtjz-hSk.js} +33 -14
  26. package/dist/{middleware-DlcecZMq.mjs → middleware-t0jC8I99.mjs} +40 -21
  27. package/dist/mod.cjs +4 -4
  28. package/dist/mod.js +4 -4
  29. package/dist/nodeinfo/handler.test.mjs +1 -1
  30. package/dist/{owner-DO810N24.mjs → owner-hDxI0ufu.mjs} +2 -2
  31. package/dist/{proof-DIoqrKnX.cjs → proof-BUWfVr6Q.cjs} +1 -1
  32. package/dist/{proof-BgfyWv7b.mjs → proof-DhVuz4bc.mjs} +3 -3
  33. package/dist/{proof-Vd8-1EWh.js → proof-n60t8o9P.js} +1 -1
  34. package/dist/{send-CAYXdUTk.mjs → send-BPhyR5Oo.mjs} +3 -3
  35. package/dist/sig/http.test.mjs +2 -2
  36. package/dist/sig/key.test.mjs +1 -1
  37. package/dist/sig/ld.test.mjs +2 -2
  38. package/dist/sig/mod.cjs +2 -2
  39. package/dist/sig/mod.js +2 -2
  40. package/dist/sig/owner.test.mjs +1 -1
  41. package/dist/sig/proof.test.mjs +1 -1
  42. package/dist/utils/docloader.test.mjs +2 -2
  43. package/dist/utils/mod.cjs +1 -1
  44. package/dist/utils/mod.js +1 -1
  45. package/package.json +5 -5
@@ -5,7 +5,7 @@ import { r as createRequestContext } from "../context-BAE7AKLA.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
6
  import "../std__assert-BTEgfoJo.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-QHE0oeM3.mjs";
8
- import { o as createFederation, s as handleWebFinger } from "../middleware-DlcecZMq.mjs";
8
+ import { o as createFederation, s as handleWebFinger } from "../middleware-t0jC8I99.mjs";
9
9
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
10
10
  import { test } from "@fedify/fixture";
11
11
  //#region src/federation/webfinger.test.ts
@@ -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.1114+15a3316d";
13
+ var version = "2.3.0-dev.1119+6cc02662";
14
14
  //#endregion
15
15
  //#region src/sig/accept.ts
16
16
  /**
@@ -168,6 +168,9 @@ var FederationMetrics = class {
168
168
  queueTaskFailed;
169
169
  queueTaskDuration;
170
170
  queueTaskInFlight;
171
+ fanoutRecipients;
172
+ inboxActivity;
173
+ outboxActivity;
171
174
  constructor(meterProvider) {
172
175
  const meter = meterProvider.getMeter(name, version);
173
176
  this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
@@ -262,6 +265,18 @@ var FederationMetrics = class {
262
265
  description: "Queue tasks currently being processed in this Fedify process.",
263
266
  unit: "{task}"
264
267
  });
268
+ this.fanoutRecipients = meter.createHistogram("activitypub.fanout.recipients", {
269
+ description: "Number of recipient inboxes produced by an ActivityPub fanout task.",
270
+ unit: "{recipient}"
271
+ });
272
+ this.inboxActivity = meter.createCounter("activitypub.inbox.activity", {
273
+ description: "ActivityPub activities observed at the inbox lifecycle level: queued, processed, retried, rejected, or abandoned.",
274
+ unit: "{activity}"
275
+ });
276
+ this.outboxActivity = meter.createCounter("activitypub.outbox.activity", {
277
+ description: "ActivityPub activities observed at the outbox lifecycle level: queued, retried, or abandoned. Per-recipient delivery counters live on `activitypub.delivery.*`.",
278
+ unit: "{activity}"
279
+ });
265
280
  }
266
281
  recordDelivery(inbox, durationMs, success, activityType) {
267
282
  const deliveryAttributes = {
@@ -334,7 +349,23 @@ var FederationMetrics = class {
334
349
  else if (result === "failed") this.queueTaskFailed.add(1, attributes);
335
350
  this.queueTaskDuration.record(durationMs, attributes);
336
351
  }
352
+ recordFanoutRecipients(recipientCount, activityType) {
353
+ const attributes = {};
354
+ if (activityType != null) attributes["activitypub.activity.type"] = activityType;
355
+ this.fanoutRecipients.record(recipientCount, attributes);
356
+ }
357
+ recordInboxActivity(result, activityType) {
358
+ this.inboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
359
+ }
360
+ recordOutboxActivity(result, activityType) {
361
+ this.outboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
362
+ }
337
363
  };
364
+ function buildActivityLifecycleAttributes(result, activityType) {
365
+ const attributes = { "activitypub.processing.result": result };
366
+ if (activityType != null) attributes["activitypub.activity.type"] = activityType;
367
+ return attributes;
368
+ }
338
369
  function buildQueueTaskAttributes(common) {
339
370
  const attributes = { "fedify.queue.role": common.role };
340
371
  const backend = getQueueBackend(common.queue);
@@ -364,20 +395,57 @@ function getQueueBackend(queue) {
364
395
  return name;
365
396
  }
366
397
  /**
367
- * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue.
398
+ * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue and,
399
+ * for the initial attempt, also records
400
+ * `activitypub.outbox.activity{queued}`.
368
401
  *
369
402
  * Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
370
403
  * outbox messages with the same metric attributes (role, queue, activity
371
404
  * type, attempt), so they share this helper rather than each defining a local
372
- * closure.
405
+ * closure. Retry enqueues (attempt > 0) intentionally do not record a
406
+ * second `activitypub.outbox.activity{queued}`; retries are reported as
407
+ * `result=retried` from the retry-scheduling site, which has the failure
408
+ * context.
373
409
  * @since 2.3.0
374
410
  */
375
411
  function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
376
- getFederationMetrics(meterProvider).recordQueueTaskEnqueued({
412
+ const metrics = getFederationMetrics(meterProvider);
413
+ metrics.recordQueueTaskEnqueued({
377
414
  role: "outbox",
378
415
  queue: outboxQueue,
379
416
  activityType: message.activityType
380
417
  }, message.attempt);
418
+ if (message.attempt === 0) metrics.recordOutboxActivity("queued", message.activityType);
419
+ }
420
+ /**
421
+ * Records `activitypub.fanout.recipients` with the number of recipient
422
+ * inboxes a single fanout produced. The histogram is unitless count
423
+ * (one measurement per fanout enqueue). Recipient URLs are deliberately
424
+ * not recorded; only the activity type, when known.
425
+ * @since 2.3.0
426
+ */
427
+ function recordFanoutRecipients(meterProvider, recipientCount, activityType) {
428
+ getFederationMetrics(meterProvider).recordFanoutRecipients(recipientCount, activityType);
429
+ }
430
+ /**
431
+ * Records one `activitypub.inbox.activity` measurement. The
432
+ * `activitypub.processing.result` attribute is always present;
433
+ * `activitypub.activity.type` is recorded only when Fedify already knows
434
+ * the activity type.
435
+ * @since 2.3.0
436
+ */
437
+ function recordInboxActivity(meterProvider, result, activityType) {
438
+ getFederationMetrics(meterProvider).recordInboxActivity(result, activityType);
439
+ }
440
+ /**
441
+ * Records one `activitypub.outbox.activity` measurement. The
442
+ * `activitypub.processing.result` attribute is always present;
443
+ * `activitypub.activity.type` is recorded only when Fedify already knows
444
+ * the activity type (it is always known for outbox lifecycle events).
445
+ * @since 2.3.0
446
+ */
447
+ function recordOutboxActivity(meterProvider, result, activityType) {
448
+ getFederationMetrics(meterProvider).recordOutboxActivity(result, activityType);
381
449
  }
382
450
  /**
383
451
  * Times an awaited public key fetch and records exactly one
@@ -1876,4 +1944,4 @@ function timingSafeEqual(a, b) {
1876
1944
  return result === 0;
1877
1945
  }
1878
1946
  //#endregion
1879
- export { version as C, name as S, recordOutboxEnqueue as _, verifyRequestDetailed as a, parseAcceptSignature as b, fetchKeyDetailed as c, validateCryptoKey as d, getDurationMs as f, measureSignatureKeyFetch as g, isAbortError$1 as h, verifyRequest as i, 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, formatAcceptSignature as v, validateAcceptSignature as x, fulfillAcceptSignature as y };
1947
+ export { parseAcceptSignature as C, version as E, fulfillAcceptSignature as S, name as T, recordFanoutRecipients as _, verifyRequestDetailed as a, recordOutboxEnqueue as b, fetchKeyDetailed as c, validateCryptoKey as d, getDurationMs as f, measureSignatureKeyFetch as g, isAbortError$1 as h, verifyRequest as i, 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, recordInboxActivity as v, validateAcceptSignature as w, formatAcceptSignature as x, recordOutboxActivity as y };
@@ -11,7 +11,7 @@ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conve
11
11
  let byte_encodings_base64 = require("byte-encodings/base64");
12
12
  //#region deno.json
13
13
  var name = "@fedify/fedify";
14
- var version = "2.3.0-dev.1114+15a3316d";
14
+ var version = "2.3.0-dev.1119+6cc02662";
15
15
  //#endregion
16
16
  //#region src/sig/accept.ts
17
17
  /**
@@ -169,6 +169,9 @@ var FederationMetrics = class {
169
169
  queueTaskFailed;
170
170
  queueTaskDuration;
171
171
  queueTaskInFlight;
172
+ fanoutRecipients;
173
+ inboxActivity;
174
+ outboxActivity;
172
175
  constructor(meterProvider) {
173
176
  const meter = meterProvider.getMeter(name, version);
174
177
  this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
@@ -263,6 +266,18 @@ var FederationMetrics = class {
263
266
  description: "Queue tasks currently being processed in this Fedify process.",
264
267
  unit: "{task}"
265
268
  });
269
+ this.fanoutRecipients = meter.createHistogram("activitypub.fanout.recipients", {
270
+ description: "Number of recipient inboxes produced by an ActivityPub fanout task.",
271
+ unit: "{recipient}"
272
+ });
273
+ this.inboxActivity = meter.createCounter("activitypub.inbox.activity", {
274
+ description: "ActivityPub activities observed at the inbox lifecycle level: queued, processed, retried, rejected, or abandoned.",
275
+ unit: "{activity}"
276
+ });
277
+ this.outboxActivity = meter.createCounter("activitypub.outbox.activity", {
278
+ description: "ActivityPub activities observed at the outbox lifecycle level: queued, retried, or abandoned. Per-recipient delivery counters live on `activitypub.delivery.*`.",
279
+ unit: "{activity}"
280
+ });
266
281
  }
267
282
  recordDelivery(inbox, durationMs, success, activityType) {
268
283
  const deliveryAttributes = {
@@ -335,7 +350,23 @@ var FederationMetrics = class {
335
350
  else if (result === "failed") this.queueTaskFailed.add(1, attributes);
336
351
  this.queueTaskDuration.record(durationMs, attributes);
337
352
  }
353
+ recordFanoutRecipients(recipientCount, activityType) {
354
+ const attributes = {};
355
+ if (activityType != null) attributes["activitypub.activity.type"] = activityType;
356
+ this.fanoutRecipients.record(recipientCount, attributes);
357
+ }
358
+ recordInboxActivity(result, activityType) {
359
+ this.inboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
360
+ }
361
+ recordOutboxActivity(result, activityType) {
362
+ this.outboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
363
+ }
338
364
  };
365
+ function buildActivityLifecycleAttributes(result, activityType) {
366
+ const attributes = { "activitypub.processing.result": result };
367
+ if (activityType != null) attributes["activitypub.activity.type"] = activityType;
368
+ return attributes;
369
+ }
339
370
  function buildQueueTaskAttributes(common) {
340
371
  const attributes = { "fedify.queue.role": common.role };
341
372
  const backend = getQueueBackend(common.queue);
@@ -365,20 +396,57 @@ function getQueueBackend(queue) {
365
396
  return name;
366
397
  }
367
398
  /**
368
- * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue.
399
+ * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue and,
400
+ * for the initial attempt, also records
401
+ * `activitypub.outbox.activity{queued}`.
369
402
  *
370
403
  * Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
371
404
  * outbox messages with the same metric attributes (role, queue, activity
372
405
  * type, attempt), so they share this helper rather than each defining a local
373
- * closure.
406
+ * closure. Retry enqueues (attempt > 0) intentionally do not record a
407
+ * second `activitypub.outbox.activity{queued}`; retries are reported as
408
+ * `result=retried` from the retry-scheduling site, which has the failure
409
+ * context.
374
410
  * @since 2.3.0
375
411
  */
376
412
  function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
377
- getFederationMetrics(meterProvider).recordQueueTaskEnqueued({
413
+ const metrics = getFederationMetrics(meterProvider);
414
+ metrics.recordQueueTaskEnqueued({
378
415
  role: "outbox",
379
416
  queue: outboxQueue,
380
417
  activityType: message.activityType
381
418
  }, message.attempt);
419
+ if (message.attempt === 0) metrics.recordOutboxActivity("queued", message.activityType);
420
+ }
421
+ /**
422
+ * Records `activitypub.fanout.recipients` with the number of recipient
423
+ * inboxes a single fanout produced. The histogram is unitless count
424
+ * (one measurement per fanout enqueue). Recipient URLs are deliberately
425
+ * not recorded; only the activity type, when known.
426
+ * @since 2.3.0
427
+ */
428
+ function recordFanoutRecipients(meterProvider, recipientCount, activityType) {
429
+ getFederationMetrics(meterProvider).recordFanoutRecipients(recipientCount, activityType);
430
+ }
431
+ /**
432
+ * Records one `activitypub.inbox.activity` measurement. The
433
+ * `activitypub.processing.result` attribute is always present;
434
+ * `activitypub.activity.type` is recorded only when Fedify already knows
435
+ * the activity type.
436
+ * @since 2.3.0
437
+ */
438
+ function recordInboxActivity(meterProvider, result, activityType) {
439
+ getFederationMetrics(meterProvider).recordInboxActivity(result, activityType);
440
+ }
441
+ /**
442
+ * Records one `activitypub.outbox.activity` measurement. The
443
+ * `activitypub.processing.result` attribute is always present;
444
+ * `activitypub.activity.type` is recorded only when Fedify already knows
445
+ * the activity type (it is always known for outbox lifecycle events).
446
+ * @since 2.3.0
447
+ */
448
+ function recordOutboxActivity(meterProvider, result, activityType) {
449
+ getFederationMetrics(meterProvider).recordOutboxActivity(result, activityType);
382
450
  }
383
451
  /**
384
452
  * Times an awaited public key fetch and records exactly one
@@ -1973,6 +2041,24 @@ Object.defineProperty(exports, "parseRfc9421SignatureInput", {
1973
2041
  return parseRfc9421SignatureInput;
1974
2042
  }
1975
2043
  });
2044
+ Object.defineProperty(exports, "recordFanoutRecipients", {
2045
+ enumerable: true,
2046
+ get: function() {
2047
+ return recordFanoutRecipients;
2048
+ }
2049
+ });
2050
+ Object.defineProperty(exports, "recordInboxActivity", {
2051
+ enumerable: true,
2052
+ get: function() {
2053
+ return recordInboxActivity;
2054
+ }
2055
+ });
2056
+ Object.defineProperty(exports, "recordOutboxActivity", {
2057
+ enumerable: true,
2058
+ get: function() {
2059
+ return recordOutboxActivity;
2060
+ }
2061
+ });
1976
2062
  Object.defineProperty(exports, "recordOutboxEnqueue", {
1977
2063
  enumerable: true,
1978
2064
  get: function() {
@@ -1,10 +1,10 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CF3jMgip.mjs";
5
- import { a as measureSignatureKeyFetch, n as getFederationMetrics, t as getDurationMs } from "./metrics-ek3ilf6c.mjs";
4
+ import { n as version, t as name } from "./deno-DVsHS7rA.mjs";
5
+ import { a as measureSignatureKeyFetch, n as getFederationMetrics, t as getDurationMs } from "./metrics-C4attqv0.mjs";
6
6
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CgDcxvjV.mjs";
7
- import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-B4I8H5Lc.mjs";
7
+ import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-BoWaYRHm.mjs";
8
8
  import { getLogger } from "@logtape/logtape";
9
9
  import { CryptographicKey } from "@fedify/vocab";
10
10
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CF3jMgip.mjs";
4
+ import { n as version, t as name } from "./deno-DVsHS7rA.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
7
7
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { d as validateCryptoKey, t as doubleKnock } from "./http-CpzZ9zsb.js";
3
+ import { d as validateCryptoKey, t as doubleKnock } from "./http-CouJSFVK.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { curry } from "es-toolkit";
6
6
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
@@ -1,7 +1,7 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-CKCgOPkX.cjs");
4
+ const require_http = require("./http-CubOB9wq.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
@@ -1,9 +1,9 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CF3jMgip.mjs";
5
- import { a as measureSignatureKeyFetch, n as getFederationMetrics, t as getDurationMs } from "./metrics-ek3ilf6c.mjs";
6
- import { n as fetchKey, o as validateCryptoKey } from "./key-B4I8H5Lc.mjs";
4
+ import { n as version, t as name } from "./deno-DVsHS7rA.mjs";
5
+ import { a as measureSignatureKeyFetch, n as getFederationMetrics, t as getDurationMs } from "./metrics-C4attqv0.mjs";
6
+ import { n as fetchKey, o as validateCryptoKey } from "./key-BoWaYRHm.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
9
9
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CF3jMgip.mjs";
4
+ import { n as version, t as name } from "./deno-DVsHS7rA.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  //#region src/federation/metrics.ts
7
7
  var FederationMetrics = class {
@@ -20,6 +20,9 @@ var FederationMetrics = class {
20
20
  queueTaskFailed;
21
21
  queueTaskDuration;
22
22
  queueTaskInFlight;
23
+ fanoutRecipients;
24
+ inboxActivity;
25
+ outboxActivity;
23
26
  constructor(meterProvider) {
24
27
  const meter = meterProvider.getMeter(name, version);
25
28
  this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
@@ -114,6 +117,18 @@ var FederationMetrics = class {
114
117
  description: "Queue tasks currently being processed in this Fedify process.",
115
118
  unit: "{task}"
116
119
  });
120
+ this.fanoutRecipients = meter.createHistogram("activitypub.fanout.recipients", {
121
+ description: "Number of recipient inboxes produced by an ActivityPub fanout task.",
122
+ unit: "{recipient}"
123
+ });
124
+ this.inboxActivity = meter.createCounter("activitypub.inbox.activity", {
125
+ description: "ActivityPub activities observed at the inbox lifecycle level: queued, processed, retried, rejected, or abandoned.",
126
+ unit: "{activity}"
127
+ });
128
+ this.outboxActivity = meter.createCounter("activitypub.outbox.activity", {
129
+ description: "ActivityPub activities observed at the outbox lifecycle level: queued, retried, or abandoned. Per-recipient delivery counters live on `activitypub.delivery.*`.",
130
+ unit: "{activity}"
131
+ });
117
132
  }
118
133
  recordDelivery(inbox, durationMs, success, activityType) {
119
134
  const deliveryAttributes = {
@@ -186,7 +201,23 @@ var FederationMetrics = class {
186
201
  else if (result === "failed") this.queueTaskFailed.add(1, attributes);
187
202
  this.queueTaskDuration.record(durationMs, attributes);
188
203
  }
204
+ recordFanoutRecipients(recipientCount, activityType) {
205
+ const attributes = {};
206
+ if (activityType != null) attributes["activitypub.activity.type"] = activityType;
207
+ this.fanoutRecipients.record(recipientCount, attributes);
208
+ }
209
+ recordInboxActivity(result, activityType) {
210
+ this.inboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
211
+ }
212
+ recordOutboxActivity(result, activityType) {
213
+ this.outboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
214
+ }
189
215
  };
216
+ function buildActivityLifecycleAttributes(result, activityType) {
217
+ const attributes = { "activitypub.processing.result": result };
218
+ if (activityType != null) attributes["activitypub.activity.type"] = activityType;
219
+ return attributes;
220
+ }
190
221
  function buildQueueTaskAttributes(common) {
191
222
  const attributes = { "fedify.queue.role": common.role };
192
223
  const backend = getQueueBackend(common.queue);
@@ -216,20 +247,57 @@ function getQueueBackend(queue) {
216
247
  return name;
217
248
  }
218
249
  /**
219
- * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue.
250
+ * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue and,
251
+ * for the initial attempt, also records
252
+ * `activitypub.outbox.activity{queued}`.
220
253
  *
221
254
  * Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
222
255
  * outbox messages with the same metric attributes (role, queue, activity
223
256
  * type, attempt), so they share this helper rather than each defining a local
224
- * closure.
257
+ * closure. Retry enqueues (attempt > 0) intentionally do not record a
258
+ * second `activitypub.outbox.activity{queued}`; retries are reported as
259
+ * `result=retried` from the retry-scheduling site, which has the failure
260
+ * context.
225
261
  * @since 2.3.0
226
262
  */
227
263
  function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
228
- getFederationMetrics(meterProvider).recordQueueTaskEnqueued({
264
+ const metrics = getFederationMetrics(meterProvider);
265
+ metrics.recordQueueTaskEnqueued({
229
266
  role: "outbox",
230
267
  queue: outboxQueue,
231
268
  activityType: message.activityType
232
269
  }, message.attempt);
270
+ if (message.attempt === 0) metrics.recordOutboxActivity("queued", message.activityType);
271
+ }
272
+ /**
273
+ * Records `activitypub.fanout.recipients` with the number of recipient
274
+ * inboxes a single fanout produced. The histogram is unitless count
275
+ * (one measurement per fanout enqueue). Recipient URLs are deliberately
276
+ * not recorded; only the activity type, when known.
277
+ * @since 2.3.0
278
+ */
279
+ function recordFanoutRecipients(meterProvider, recipientCount, activityType) {
280
+ getFederationMetrics(meterProvider).recordFanoutRecipients(recipientCount, activityType);
281
+ }
282
+ /**
283
+ * Records one `activitypub.inbox.activity` measurement. The
284
+ * `activitypub.processing.result` attribute is always present;
285
+ * `activitypub.activity.type` is recorded only when Fedify already knows
286
+ * the activity type.
287
+ * @since 2.3.0
288
+ */
289
+ function recordInboxActivity(meterProvider, result, activityType) {
290
+ getFederationMetrics(meterProvider).recordInboxActivity(result, activityType);
291
+ }
292
+ /**
293
+ * Records one `activitypub.outbox.activity` measurement. The
294
+ * `activitypub.processing.result` attribute is always present;
295
+ * `activitypub.activity.type` is recorded only when Fedify already knows
296
+ * the activity type (it is always known for outbox lifecycle events).
297
+ * @since 2.3.0
298
+ */
299
+ function recordOutboxActivity(meterProvider, result, activityType) {
300
+ getFederationMetrics(meterProvider).recordOutboxActivity(result, activityType);
233
301
  }
234
302
  /**
235
303
  * Times an awaited public key fetch and records exactly one
@@ -311,4 +379,4 @@ function getDurationMs(start) {
311
379
  return Math.max(0, performance.now() - start);
312
380
  }
313
381
  //#endregion
314
- export { measureSignatureKeyFetch as a, isAbortError as i, getFederationMetrics as n, recordOutboxEnqueue as o, getRemoteHost as r, getDurationMs as t };
382
+ export { measureSignatureKeyFetch as a, recordOutboxActivity as c, isAbortError as i, recordOutboxEnqueue as l, getFederationMetrics as n, recordFanoutRecipients as o, getRemoteHost as r, recordInboxActivity as s, getDurationMs as t };
@@ -1,5 +1,5 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-DlcecZMq.mjs";
4
+ import { n as FederationImpl } from "./middleware-t0jC8I99.mjs";
5
5
  export { FederationImpl };
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-CKCgOPkX.cjs");
6
- const require_proof = require("./proof-DIoqrKnX.cjs");
5
+ const require_http = require("./http-CubOB9wq.cjs");
6
+ const require_proof = require("./proof-BUWfVr6Q.cjs");
7
7
  const require_types = require("./types-KC4QAoxe.cjs");
8
- const require_kv_cache = require("./kv-cache-DY-XWOqM.cjs");
8
+ const require_kv_cache = require("./kv-cache-Dz31ATUT.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -817,6 +817,7 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
817
817
  code: _opentelemetry_api.SpanStatusCode.UNSET,
818
818
  message: `Activity ${activity.id?.href} has already been processed.`
819
819
  });
820
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
820
821
  return "alreadyProcessed";
821
822
  }
822
823
  }
@@ -826,6 +827,7 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
826
827
  code: _opentelemetry_api.SpanStatusCode.ERROR,
827
828
  message: "Missing actor."
828
829
  });
830
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
829
831
  return "missingActor";
830
832
  }
831
833
  span.setAttribute("activitypub.actor.id", activity.actorId.href);
@@ -861,6 +863,7 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
861
863
  queue,
862
864
  activityType: (0, _fedify_vocab.getTypeId)(activity).href
863
865
  }, 0);
866
+ require_http.recordInboxActivity(meterProvider, "queued", (0, _fedify_vocab.getTypeId)(activity).href);
864
867
  logger.info("Activity {activityId} is enqueued.", {
865
868
  activityId: activity.id?.href,
866
869
  activity: json,
@@ -880,6 +883,7 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
880
883
  code: _opentelemetry_api.SpanStatusCode.UNSET,
881
884
  message: `Unsupported activity type: ${(0, _fedify_vocab.getTypeId)(activity).href}`
882
885
  });
886
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
883
887
  span.end();
884
888
  return "unsupportedActivity";
885
889
  }
@@ -889,7 +893,7 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
889
893
  const activityType = (0, _fedify_vocab.getTypeId)(activity).href;
890
894
  const started = performance.now();
891
895
  try {
892
- await listener(inboxContextFactory(recipient, json, activity?.id?.href, activityType), activity);
896
+ await listener(inboxContextFactory(recipient, json, activity.id?.href, activityType), activity);
893
897
  } finally {
894
898
  require_http.getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, require_http.getDurationMs(started));
895
899
  }
@@ -914,9 +918,11 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
914
918
  code: _opentelemetry_api.SpanStatusCode.ERROR,
915
919
  message: String(error)
916
920
  });
921
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
917
922
  span.end();
918
923
  return "error";
919
924
  }
925
+ require_http.recordInboxActivity(meterProvider, "processed", (0, _fedify_vocab.getTypeId)(activity).href);
920
926
  if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
921
927
  logger.info("Activity {activityId} has been processed.", {
922
928
  activityId: activity.id?.href,
@@ -3233,6 +3239,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3233
3239
  });
3234
3240
  }
3235
3241
  }
3242
+ require_http.recordOutboxActivity(this.meterProvider, "abandoned", message.activityType);
3236
3243
  return;
3237
3244
  }
3238
3245
  if (this.outboxQueue?.nativeRetrial) {
@@ -3263,11 +3270,15 @@ var FederationImpl = class extends FederationBuilderImpl {
3263
3270
  queue: outboxQueue,
3264
3271
  activityType: retryMessage.activityType
3265
3272
  }, retryMessage.attempt);
3273
+ require_http.recordOutboxActivity(this.meterProvider, "retried", retryMessage.activityType);
3266
3274
  }
3267
- } else logger.error("Failed to send activity {activityId} to {inbox} after {attempt} attempts; giving up:\n{error}", {
3268
- ...logData,
3269
- error
3270
- });
3275
+ } else {
3276
+ logger.error("Failed to send activity {activityId} to {inbox} after {attempt} attempts; giving up:\n{error}", {
3277
+ ...logData,
3278
+ error
3279
+ });
3280
+ require_http.recordOutboxActivity(this.meterProvider, "abandoned", message.activityType);
3281
+ }
3271
3282
  return;
3272
3283
  }
3273
3284
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
@@ -3302,6 +3313,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3302
3313
  activity: message.activity,
3303
3314
  recipient: message.identifier
3304
3315
  });
3316
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
3305
3317
  return;
3306
3318
  }
3307
3319
  }
@@ -3318,6 +3330,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3318
3330
  code: _opentelemetry_api.SpanStatusCode.ERROR,
3319
3331
  message: `Unsupported activity type: ${activityType}`
3320
3332
  });
3333
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
3321
3334
  span.end();
3322
3335
  return;
3323
3336
  }
@@ -3330,6 +3343,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3330
3343
  } finally {
3331
3344
  require_http.getFederationMetrics(this.meterProvider).recordInboxProcessingDuration(activityType, require_http.getDurationMs(started));
3332
3345
  }
3346
+ require_http.recordInboxActivity(this.meterProvider, "processed", activityType);
3333
3347
  } catch (error) {
3334
3348
  try {
3335
3349
  await this.inboxErrorHandler?.(context, error);
@@ -3380,13 +3394,17 @@ var FederationImpl = class extends FederationBuilderImpl {
3380
3394
  queue: inboxQueue,
3381
3395
  activityType
3382
3396
  }, retryMessage.attempt);
3397
+ require_http.recordInboxActivity(this.meterProvider, "retried", activityType);
3383
3398
  }
3384
- } else logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
3385
- error,
3386
- activityId: activity.id?.href,
3387
- activity: message.activity,
3388
- recipient: message.identifier
3389
- });
3399
+ } else {
3400
+ logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
3401
+ error,
3402
+ activityId: activity.id?.href,
3403
+ activity: message.activity,
3404
+ recipient: message.identifier
3405
+ });
3406
+ require_http.recordInboxActivity(this.meterProvider, "abandoned", activityType);
3407
+ }
3390
3408
  span.setStatus({
3391
3409
  code: _opentelemetry_api.SpanStatusCode.ERROR,
3392
3410
  message: String(error)
@@ -4413,6 +4431,7 @@ var ContextImpl = class ContextImpl {
4413
4431
  queue: this.federation.fanoutQueue,
4414
4432
  activityType: message.activityType
4415
4433
  }, 0);
4434
+ require_http.recordFanoutRecipients(this.federation.meterProvider, globalThis.Object.keys(message.inboxes).length, message.activityType);
4416
4435
  return true;
4417
4436
  }
4418
4437
  async *getFollowers(identifier) {