@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
@@ -2,7 +2,7 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError, t as Router } from "./router-BT_F5748.mjs";
5
- import { n as version, t as name } from "./deno-CF3jMgip.mjs";
5
+ import { n as version, t as name } from "./deno-DVsHS7rA.mjs";
6
6
  import { t as ActivityListenerSet } from "./activity-listener-BeTGV3wc.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Tombstone, getTypeId } from "@fedify/vocab";
@@ -59,7 +59,7 @@ var FederationBuilderImpl = class {
59
59
  this.collectionTypeIds = {};
60
60
  }
61
61
  async build(options) {
62
- const { FederationImpl } = await import("./middleware-EI7OU6BR.mjs");
62
+ const { FederationImpl } = await import("./middleware-BDKFRjue.mjs");
63
63
  const f = new FederationImpl(options);
64
64
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
65
65
  f.router = this.router.clone();
@@ -5,7 +5,7 @@ import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
5
  import { t as assertInstanceOf } from "../assert_instance_of-C4Ri6VuN.mjs";
6
6
  import { t as assert } from "../assert-DikXweDx.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-QHE0oeM3.mjs";
8
- import { n as FederationImpl, v as actorDehydrator, y as autoIdAssigner } from "../middleware-DlcecZMq.mjs";
8
+ import { n as FederationImpl, v as actorDehydrator, y as autoIdAssigner } from "../middleware-t0jC8I99.mjs";
9
9
  import { Follow, Person } from "@fedify/vocab";
10
10
  import { test } from "@fedify/fixture";
11
11
  //#region src/compat/transformers.test.ts
@@ -3,6 +3,6 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  //#region deno.json
5
5
  var name = "@fedify/fedify";
6
- var version = "2.3.0-dev.1114+15a3316d";
6
+ var version = "2.3.0-dev.1119+6cc02662";
7
7
  //#endregion
8
8
  export { version as n, name as t };
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { o as validateCryptoKey } from "./key-B4I8H5Lc.mjs";
5
- import { n as doubleKnock } from "./http-BmOZYc-8.mjs";
4
+ import { o as validateCryptoKey } from "./key-BoWaYRHm.mjs";
5
+ import { n as doubleKnock } from "./http-DUV8ysti.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { curry } from "es-toolkit";
8
8
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, validatePublicUrl } from "@fedify/vocab-runtime";
@@ -6,7 +6,7 @@ import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
6
  import { r as assertExists } from "../std__assert-BTEgfoJo.mjs";
7
7
  import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
8
8
  import { t as MemoryKvStore } from "../kv-QHE0oeM3.mjs";
9
- import { r as createFederationBuilder } from "../builder-YlEusQth.mjs";
9
+ import { r as createFederationBuilder } from "../builder-Ond_h57y.mjs";
10
10
  import { Activity, Note, Person } from "@fedify/vocab";
11
11
  import { test } from "@fedify/fixture";
12
12
  //#region src/federation/builder.test.ts
@@ -8,10 +8,10 @@ import { n as assertGreaterOrEqual } from "../assert_rejects-DQP-q39h.mjs";
8
8
  import { t as assertInstanceOf } from "../assert_instance_of-C4Ri6VuN.mjs";
9
9
  import { t as assert } from "../assert-DikXweDx.mjs";
10
10
  import { r as parseAcceptSignature } from "../accept-CgDcxvjV.mjs";
11
- import { s as signRequest } from "../http-BmOZYc-8.mjs";
11
+ import { s as signRequest } from "../http-DUV8ysti.mjs";
12
12
  import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-CSYsOMFG.mjs";
13
13
  import { t as MemoryKvStore } from "../kv-QHE0oeM3.mjs";
14
- import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-DlcecZMq.mjs";
14
+ import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-t0jC8I99.mjs";
15
15
  import { t as ActivityListenerSet } from "../activity-listener-BeTGV3wc.mjs";
16
16
  import { Activity, Create, Note, Person, Tombstone } from "@fedify/vocab";
17
17
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
@@ -4,9 +4,9 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
5
  import "../std__assert-BTEgfoJo.mjs";
6
6
  import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-CSYsOMFG.mjs";
7
- import { r as signObject } from "../proof-BgfyWv7b.mjs";
7
+ import { r as signObject } from "../proof-DhVuz4bc.mjs";
8
8
  import { t as MemoryKvStore } from "../kv-QHE0oeM3.mjs";
9
- import { o as createFederation } from "../middleware-DlcecZMq.mjs";
9
+ import { o as createFederation } from "../middleware-t0jC8I99.mjs";
10
10
  import { Create, Follow, Person } from "@fedify/vocab";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  //#region src/federation/idempotency.test.ts
@@ -0,0 +1,2 @@
1
+ import { Temporal } from "@js-temporal/polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";
@@ -0,0 +1,107 @@
1
+ import "@js-temporal/polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
+ import "../std__assert-BTEgfoJo.mjs";
6
+ import { c as recordOutboxActivity, l as recordOutboxEnqueue, o as recordFanoutRecipients, s as recordInboxActivity } from "../metrics-C4attqv0.mjs";
7
+ import { createTestMeterProvider, test } from "@fedify/fixture";
8
+ //#region src/federation/metrics.test.ts
9
+ const noopQueue = {
10
+ enqueue() {
11
+ return Promise.resolve();
12
+ },
13
+ listen() {
14
+ return Promise.resolve();
15
+ }
16
+ };
17
+ test("recordFanoutRecipients() records the recipient count with activity type", () => {
18
+ const [meterProvider, recorder] = createTestMeterProvider();
19
+ recordFanoutRecipients(meterProvider, 7, "https://www.w3.org/ns/activitystreams#Create");
20
+ const measurements = recorder.getMeasurements("activitypub.fanout.recipients");
21
+ assertEquals(measurements.length, 1);
22
+ assertEquals(measurements[0].type, "histogram");
23
+ assertEquals(measurements[0].value, 7);
24
+ assertEquals(measurements[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
25
+ });
26
+ test("recordFanoutRecipients() omits activity type when unknown", () => {
27
+ const [meterProvider, recorder] = createTestMeterProvider();
28
+ recordFanoutRecipients(meterProvider, 0);
29
+ const measurements = recorder.getMeasurements("activitypub.fanout.recipients");
30
+ assertEquals(measurements.length, 1);
31
+ assertEquals(measurements[0].value, 0);
32
+ assertEquals("activitypub.activity.type" in measurements[0].attributes, false);
33
+ });
34
+ test("recordInboxActivity() records counter with result and activity type", () => {
35
+ const [meterProvider, recorder] = createTestMeterProvider();
36
+ for (const result of [
37
+ "queued",
38
+ "processed",
39
+ "retried",
40
+ "rejected",
41
+ "abandoned"
42
+ ]) recordInboxActivity(meterProvider, result, "https://www.w3.org/ns/activitystreams#Follow");
43
+ const measurements = recorder.getMeasurements("activitypub.inbox.activity");
44
+ assertEquals(measurements.length, 5);
45
+ for (const m of measurements) {
46
+ assertEquals(m.type, "counter");
47
+ assertEquals(m.value, 1);
48
+ assertEquals(m.attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Follow");
49
+ }
50
+ assertEquals(measurements.map((m) => m.attributes["activitypub.processing.result"]), [
51
+ "queued",
52
+ "processed",
53
+ "retried",
54
+ "rejected",
55
+ "abandoned"
56
+ ]);
57
+ });
58
+ test("recordInboxActivity() omits activity type when unknown", () => {
59
+ const [meterProvider, recorder] = createTestMeterProvider();
60
+ recordInboxActivity(meterProvider, "rejected");
61
+ const measurements = recorder.getMeasurements("activitypub.inbox.activity");
62
+ assertEquals(measurements.length, 1);
63
+ assertEquals(measurements[0].attributes["activitypub.processing.result"], "rejected");
64
+ assertEquals("activitypub.activity.type" in measurements[0].attributes, false);
65
+ });
66
+ test("recordOutboxEnqueue() also records activitypub.outbox.activity{queued} on initial enqueue", () => {
67
+ const [meterProvider, recorder] = createTestMeterProvider();
68
+ recordOutboxEnqueue(meterProvider, noopQueue, {
69
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
70
+ attempt: 0
71
+ });
72
+ const queued = recorder.getMeasurements("activitypub.outbox.activity");
73
+ assertEquals(queued.length, 1);
74
+ assertEquals(queued[0].type, "counter");
75
+ assertEquals(queued[0].attributes["activitypub.processing.result"], "queued");
76
+ assertEquals(queued[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
77
+ });
78
+ test("recordOutboxEnqueue() does not record outbox.activity{queued} on retry enqueues", () => {
79
+ const [meterProvider, recorder] = createTestMeterProvider();
80
+ recordOutboxEnqueue(meterProvider, noopQueue, {
81
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
82
+ attempt: 1
83
+ });
84
+ assertEquals(recorder.getMeasurements("activitypub.outbox.activity").length, 0);
85
+ });
86
+ test("recordOutboxActivity() records counter with result and activity type", () => {
87
+ const [meterProvider, recorder] = createTestMeterProvider();
88
+ for (const result of [
89
+ "queued",
90
+ "retried",
91
+ "abandoned"
92
+ ]) recordOutboxActivity(meterProvider, result, "https://www.w3.org/ns/activitystreams#Announce");
93
+ const measurements = recorder.getMeasurements("activitypub.outbox.activity");
94
+ assertEquals(measurements.length, 3);
95
+ for (const m of measurements) {
96
+ assertEquals(m.type, "counter");
97
+ assertEquals(m.value, 1);
98
+ assertEquals(m.attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Announce");
99
+ }
100
+ assertEquals(measurements.map((m) => m.attributes["activitypub.processing.result"]), [
101
+ "queued",
102
+ "retried",
103
+ "abandoned"
104
+ ]);
105
+ });
106
+ //#endregion
107
+ export {};
@@ -11,14 +11,14 @@ import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
11
11
  import { t as assertStrictEquals } from "../assert_strict_equals-Dmjbg-bA.mjs";
12
12
  import { t as assert } from "../assert-DikXweDx.mjs";
13
13
  import { t as esm_default } from "../esm-sdtqOUPu.mjs";
14
- import { l as verifyRequest, s as signRequest } from "../http-BmOZYc-8.mjs";
14
+ import { l as verifyRequest, s as signRequest } from "../http-DUV8ysti.mjs";
15
15
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CSYsOMFG.mjs";
16
- import { t as getAuthenticatedDocumentLoader } from "../docloader-BENj6vQ4.mjs";
17
- import { a as signJsonLd, o as verifyJsonLd, r as detachSignature } from "../ld-B5D5THhl.mjs";
18
- import { t as doesActorOwnKey } from "../owner-DO810N24.mjs";
19
- import { i as verifyObject, r as signObject } from "../proof-BgfyWv7b.mjs";
16
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-WsWfKaE5.mjs";
17
+ import { a as signJsonLd, o as verifyJsonLd, r as detachSignature } from "../ld-B5K1mSuG.mjs";
18
+ import { t as doesActorOwnKey } from "../owner-hDxI0ufu.mjs";
19
+ import { i as verifyObject, r as signObject } from "../proof-DhVuz4bc.mjs";
20
20
  import { t as MemoryKvStore } from "../kv-QHE0oeM3.mjs";
21
- import { i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-DlcecZMq.mjs";
21
+ import { i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-t0jC8I99.mjs";
22
22
  import { configure, reset } from "@logtape/logtape";
23
23
  import * as vocab from "@fedify/vocab";
24
24
  import { getTypeId, lookupObject } from "@fedify/vocab";
@@ -2287,6 +2287,214 @@ test("FederationImpl.processQueuedTask()", async (t) => {
2287
2287
  attempt: 1
2288
2288
  }]);
2289
2289
  });
2290
+ await t.step("records activitypub.outbox.activity retry on transient failure", async () => {
2291
+ const kv = new MemoryKvStore();
2292
+ const [meterProvider, recorder] = createTestMeterProvider();
2293
+ await new FederationImpl({
2294
+ kv,
2295
+ meterProvider,
2296
+ queue: {
2297
+ enqueue(_message, _options) {
2298
+ return Promise.resolve();
2299
+ },
2300
+ listen(_handler, _options) {
2301
+ return Promise.resolve();
2302
+ }
2303
+ }
2304
+ }).processQueuedTask(void 0, {
2305
+ type: "outbox",
2306
+ id: crypto.randomUUID(),
2307
+ baseUrl: "https://example.com",
2308
+ keys: [],
2309
+ activity: {
2310
+ "@context": "https://www.w3.org/ns/activitystreams",
2311
+ type: "Create",
2312
+ actor: "https://example.com/users/alice",
2313
+ object: {
2314
+ type: "Note",
2315
+ content: "test"
2316
+ }
2317
+ },
2318
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
2319
+ inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
2320
+ sharedInbox: false,
2321
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2322
+ attempt: 0,
2323
+ headers: {},
2324
+ traceContext: {}
2325
+ });
2326
+ const outboxLifecycle = recorder.getMeasurements("activitypub.outbox.activity");
2327
+ assertEquals(outboxLifecycle.length, 1);
2328
+ assertEquals(outboxLifecycle[0].type, "counter");
2329
+ assertEquals(outboxLifecycle[0].attributes["activitypub.processing.result"], "retried");
2330
+ assertEquals(outboxLifecycle[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
2331
+ });
2332
+ await t.step("records activitypub.outbox.activity abandoned when retry policy gives up", async () => {
2333
+ const kv = new MemoryKvStore();
2334
+ const [meterProvider, recorder] = createTestMeterProvider();
2335
+ await new FederationImpl({
2336
+ kv,
2337
+ meterProvider,
2338
+ queue: {
2339
+ enqueue(_message, _options) {
2340
+ return Promise.resolve();
2341
+ },
2342
+ listen(_handler, _options) {
2343
+ return Promise.resolve();
2344
+ }
2345
+ },
2346
+ outboxRetryPolicy: () => null
2347
+ }).processQueuedTask(void 0, {
2348
+ type: "outbox",
2349
+ id: crypto.randomUUID(),
2350
+ baseUrl: "https://example.com",
2351
+ keys: [],
2352
+ activity: {
2353
+ "@context": "https://www.w3.org/ns/activitystreams",
2354
+ type: "Follow",
2355
+ actor: "https://example.com/users/alice",
2356
+ object: "https://remote.example/users/bob"
2357
+ },
2358
+ activityType: "https://www.w3.org/ns/activitystreams#Follow",
2359
+ inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
2360
+ sharedInbox: false,
2361
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2362
+ attempt: 0,
2363
+ headers: {},
2364
+ traceContext: {}
2365
+ });
2366
+ const outboxLifecycle = recorder.getMeasurements("activitypub.outbox.activity");
2367
+ assertEquals(outboxLifecycle.length, 1);
2368
+ assertEquals(outboxLifecycle[0].type, "counter");
2369
+ assertEquals(outboxLifecycle[0].attributes["activitypub.processing.result"], "abandoned");
2370
+ assertEquals(outboxLifecycle[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Follow");
2371
+ });
2372
+ await t.step("records activitypub.inbox.activity processed on successful queued dispatch", async () => {
2373
+ const kv = new MemoryKvStore();
2374
+ const [meterProvider, recorder] = createTestMeterProvider();
2375
+ const federation = new FederationImpl({
2376
+ kv,
2377
+ meterProvider,
2378
+ queue: {
2379
+ enqueue(_message, _options) {
2380
+ return Promise.resolve();
2381
+ },
2382
+ listen(_handler, _options) {
2383
+ return Promise.resolve();
2384
+ }
2385
+ }
2386
+ });
2387
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {});
2388
+ await federation.processQueuedTask(void 0, {
2389
+ type: "inbox",
2390
+ id: crypto.randomUUID(),
2391
+ baseUrl: "https://example.com",
2392
+ activity: {
2393
+ "@context": "https://www.w3.org/ns/activitystreams",
2394
+ type: "Create",
2395
+ id: "https://example.com/activities/queued-processed",
2396
+ actor: "https://remote.example/users/alice",
2397
+ object: {
2398
+ type: "Note",
2399
+ content: "Hello world"
2400
+ }
2401
+ },
2402
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2403
+ attempt: 0,
2404
+ identifier: null,
2405
+ traceContext: {}
2406
+ });
2407
+ const inboxLifecycle = recorder.getMeasurements("activitypub.inbox.activity");
2408
+ assertEquals(inboxLifecycle.length, 1);
2409
+ assertEquals(inboxLifecycle[0].type, "counter");
2410
+ assertEquals(inboxLifecycle[0].attributes["activitypub.processing.result"], "processed");
2411
+ assertEquals(inboxLifecycle[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
2412
+ });
2413
+ await t.step("records activitypub.inbox.activity retried on transient listener failure", async () => {
2414
+ const kv = new MemoryKvStore();
2415
+ const [meterProvider, recorder] = createTestMeterProvider();
2416
+ const federation = new FederationImpl({
2417
+ kv,
2418
+ meterProvider,
2419
+ queue: {
2420
+ enqueue(_message, _options) {
2421
+ return Promise.resolve();
2422
+ },
2423
+ listen(_handler, _options) {
2424
+ return Promise.resolve();
2425
+ }
2426
+ }
2427
+ });
2428
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
2429
+ throw new Error("Intended error for testing");
2430
+ });
2431
+ await federation.processQueuedTask(void 0, {
2432
+ type: "inbox",
2433
+ id: crypto.randomUUID(),
2434
+ baseUrl: "https://example.com",
2435
+ activity: {
2436
+ "@context": "https://www.w3.org/ns/activitystreams",
2437
+ type: "Create",
2438
+ id: "https://example.com/activities/queued-retried",
2439
+ actor: "https://remote.example/users/alice",
2440
+ object: {
2441
+ type: "Note",
2442
+ content: "Hello world"
2443
+ }
2444
+ },
2445
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2446
+ attempt: 0,
2447
+ identifier: null,
2448
+ traceContext: {}
2449
+ });
2450
+ const inboxLifecycle = recorder.getMeasurements("activitypub.inbox.activity");
2451
+ assertEquals(inboxLifecycle.length, 1);
2452
+ assertEquals(inboxLifecycle[0].attributes["activitypub.processing.result"], "retried");
2453
+ assertEquals(inboxLifecycle[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
2454
+ });
2455
+ await t.step("records activitypub.inbox.activity abandoned when retry policy gives up", async () => {
2456
+ const kv = new MemoryKvStore();
2457
+ const [meterProvider, recorder] = createTestMeterProvider();
2458
+ const federation = new FederationImpl({
2459
+ kv,
2460
+ meterProvider,
2461
+ queue: {
2462
+ enqueue(_message, _options) {
2463
+ return Promise.resolve();
2464
+ },
2465
+ listen(_handler, _options) {
2466
+ return Promise.resolve();
2467
+ }
2468
+ },
2469
+ inboxRetryPolicy: () => null
2470
+ });
2471
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
2472
+ throw new Error("Intended error for testing");
2473
+ });
2474
+ await federation.processQueuedTask(void 0, {
2475
+ type: "inbox",
2476
+ id: crypto.randomUUID(),
2477
+ baseUrl: "https://example.com",
2478
+ activity: {
2479
+ "@context": "https://www.w3.org/ns/activitystreams",
2480
+ type: "Create",
2481
+ id: "https://example.com/activities/queued-abandoned",
2482
+ actor: "https://remote.example/users/alice",
2483
+ object: {
2484
+ type: "Note",
2485
+ content: "Hello world"
2486
+ }
2487
+ },
2488
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2489
+ attempt: 0,
2490
+ identifier: null,
2491
+ traceContext: {}
2492
+ });
2493
+ const inboxLifecycle = recorder.getMeasurements("activitypub.inbox.activity");
2494
+ assertEquals(inboxLifecycle.length, 1);
2495
+ assertEquals(inboxLifecycle[0].attributes["activitypub.processing.result"], "abandoned");
2496
+ assertEquals(inboxLifecycle[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
2497
+ });
2290
2498
  await t.step("records queued inbox processing duration", async () => {
2291
2499
  const kv = new MemoryKvStore();
2292
2500
  const [meterProvider, recorder] = createTestMeterProvider();
@@ -2447,6 +2655,10 @@ test("FederationImpl.processQueuedTask() permanent failure", async (t) => {
2447
2655
  assertEquals(failures[0].value, 1);
2448
2656
  assertEquals(failures[0].attributes["activitypub.remote.host"], "gone.example");
2449
2657
  assertEquals(failures[0].attributes["http.response.status_code"], 410);
2658
+ const abandoned = recorder.getMeasurements("activitypub.outbox.activity");
2659
+ assertEquals(abandoned.length, 1);
2660
+ assertEquals(abandoned[0].attributes["activitypub.processing.result"], "abandoned");
2661
+ assertEquals(abandoned[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
2450
2662
  const events = exporter.getEvents("activitypub.outbox", "activitypub.delivery.failed");
2451
2663
  assertEquals(events.length, 1);
2452
2664
  assertEquals(events[0].attributes?.["activitypub.remote.host"], "gone.example");
@@ -3414,6 +3626,66 @@ test("ContextImpl.sendActivity()", async (t) => {
3414
3626
  });
3415
3627
  esm_default.hardReset();
3416
3628
  });
3629
+ test("ContextImpl.sendActivity() records fanout recipient metrics", async () => {
3630
+ const kv = new MemoryKvStore();
3631
+ const [meterProvider, recorder] = createTestMeterProvider();
3632
+ const queue = {
3633
+ messages: [],
3634
+ enqueue(message) {
3635
+ this.messages.push(message);
3636
+ return Promise.resolve();
3637
+ },
3638
+ async listen() {}
3639
+ };
3640
+ const federation = new FederationImpl({
3641
+ kv,
3642
+ contextLoaderFactory: () => mockDocumentLoader,
3643
+ queue,
3644
+ meterProvider
3645
+ });
3646
+ federation.setActorDispatcher("/{identifier}", async (ctx, identifier) => {
3647
+ if (identifier !== "john") return null;
3648
+ const keys = await ctx.getActorKeyPairs(identifier);
3649
+ return new vocab.Person({
3650
+ id: ctx.getActorUri(identifier),
3651
+ preferredUsername: "john",
3652
+ publicKey: keys[0].cryptographicKey,
3653
+ assertionMethods: keys.map((k) => k.multikey)
3654
+ });
3655
+ }).setKeyPairsDispatcher((_ctx, identifier) => {
3656
+ if (identifier !== "john") return [];
3657
+ return [{
3658
+ privateKey: rsaPrivateKey2,
3659
+ publicKey: rsaPublicKey2.publicKey
3660
+ }, {
3661
+ privateKey: ed25519PrivateKey,
3662
+ publicKey: ed25519PublicKey.publicKey
3663
+ }];
3664
+ });
3665
+ const ctx = new ContextImpl({
3666
+ data: void 0,
3667
+ federation,
3668
+ url: new URL("https://example.com/"),
3669
+ documentLoader: mockDocumentLoader,
3670
+ contextLoader: mockDocumentLoader
3671
+ });
3672
+ const activity = new vocab.Create({
3673
+ id: new URL("https://example.com/activity/1"),
3674
+ actor: new URL("https://example.com/person")
3675
+ });
3676
+ const recipients = Array.from({ length: 7 }, (_, i) => ({
3677
+ id: new URL(`https://example${i + 1}.com/recipient`),
3678
+ inboxId: new URL(`https://example${i + 1}.com/inbox`)
3679
+ }));
3680
+ await ctx.sendActivity({ username: "john" }, recipients, activity, { fanout: "force" });
3681
+ assertEquals(queue.messages.length, 1);
3682
+ assertEquals(queue.messages[0].type, "fanout");
3683
+ const measurements = recorder.getMeasurements("activitypub.fanout.recipients");
3684
+ assertEquals(measurements.length, 1);
3685
+ assertEquals(measurements[0].type, "histogram");
3686
+ assertEquals(measurements[0].value, recipients.length);
3687
+ assertEquals(measurements[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
3688
+ });
3417
3689
  test({
3418
3690
  name: "ContextImpl.routeActivity()",
3419
3691
  permissions: {
@@ -3525,6 +3797,114 @@ test({
3525
3797
  assertEquals(enqueuedMetrics[0].attributes["fedify.queue.task.attempt"], 0);
3526
3798
  }
3527
3799
  });
3800
+ test({
3801
+ name: "ContextImpl.routeActivity() records inbox.activity lifecycle metrics",
3802
+ permissions: {
3803
+ env: true,
3804
+ read: true
3805
+ },
3806
+ async fn() {
3807
+ const [meterProvider, recorder] = createTestMeterProvider();
3808
+ const federation = new FederationImpl({
3809
+ kv: new MemoryKvStore(),
3810
+ meterProvider,
3811
+ queue: {
3812
+ enqueue() {
3813
+ return Promise.resolve();
3814
+ },
3815
+ listen() {
3816
+ return Promise.resolve();
3817
+ }
3818
+ }
3819
+ });
3820
+ federation.setInboxListeners("/u/{identifier}/i", "/i");
3821
+ const ctx = new ContextImpl({
3822
+ url: new URL("https://example.com/"),
3823
+ federation,
3824
+ data: void 0,
3825
+ documentLoader: mockDocumentLoader,
3826
+ contextLoader: documentLoader
3827
+ });
3828
+ const signedOffer = await signObject(new vocab.Offer({
3829
+ id: new URL("https://example.com/offer-queued"),
3830
+ actor: new URL("https://example.com/person2")
3831
+ }), ed25519PrivateKey, ed25519Multikey.id);
3832
+ assert(await ctx.routeActivity(null, signedOffer));
3833
+ const queued = recorder.getMeasurements("activitypub.inbox.activity");
3834
+ assertEquals(queued.length, 1);
3835
+ assertEquals(queued[0].type, "counter");
3836
+ assertEquals(queued[0].attributes["activitypub.processing.result"], "queued");
3837
+ assertEquals(queued[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Offer");
3838
+ }
3839
+ });
3840
+ test({
3841
+ name: "ContextImpl.routeActivity() records inbox.activity processed without queue",
3842
+ permissions: {
3843
+ env: true,
3844
+ read: true
3845
+ },
3846
+ async fn() {
3847
+ const [meterProvider, recorder] = createTestMeterProvider();
3848
+ const federation = new FederationImpl({
3849
+ kv: new MemoryKvStore(),
3850
+ meterProvider
3851
+ });
3852
+ federation.setInboxListeners("/u/{identifier}/i", "/i").on(vocab.Offer, () => {});
3853
+ const ctx = new ContextImpl({
3854
+ url: new URL("https://example.com/"),
3855
+ federation,
3856
+ data: void 0,
3857
+ documentLoader: mockDocumentLoader,
3858
+ contextLoader: documentLoader
3859
+ });
3860
+ const signedOffer = await signObject(new vocab.Offer({
3861
+ id: new URL("https://example.com/offer-processed"),
3862
+ actor: new URL("https://example.com/person2")
3863
+ }), ed25519PrivateKey, ed25519Multikey.id);
3864
+ assert(await ctx.routeActivity(null, signedOffer));
3865
+ const processed = recorder.getMeasurements("activitypub.inbox.activity");
3866
+ assertEquals(processed.length, 1);
3867
+ assertEquals(processed[0].attributes["activitypub.processing.result"], "processed");
3868
+ assertEquals(processed[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Offer");
3869
+ }
3870
+ });
3871
+ test({
3872
+ name: "ContextImpl.routeActivity() records inbox.activity rejected for unsupported type and duplicates",
3873
+ permissions: {
3874
+ env: true,
3875
+ read: true
3876
+ },
3877
+ async fn() {
3878
+ const [meterProvider, recorder] = createTestMeterProvider();
3879
+ const federation = new FederationImpl({
3880
+ kv: new MemoryKvStore(),
3881
+ meterProvider
3882
+ });
3883
+ federation.setInboxListeners("/u/{identifier}/i", "/i").on(vocab.Offer, () => {});
3884
+ const ctx = new ContextImpl({
3885
+ url: new URL("https://example.com/"),
3886
+ federation,
3887
+ data: void 0,
3888
+ documentLoader: mockDocumentLoader,
3889
+ contextLoader: documentLoader
3890
+ });
3891
+ const signedCreate = await signObject(new vocab.Create({
3892
+ id: new URL("https://example.com/create-unsupported"),
3893
+ actor: new URL("https://example.com/person2")
3894
+ }), ed25519PrivateKey, ed25519Multikey.id);
3895
+ assert(await ctx.routeActivity(null, signedCreate));
3896
+ const dupOffer = await signObject(new vocab.Offer({
3897
+ id: new URL("https://example.com/offer-duplicate"),
3898
+ actor: new URL("https://example.com/person2")
3899
+ }), ed25519PrivateKey, ed25519Multikey.id);
3900
+ assert(await ctx.routeActivity(null, dupOffer));
3901
+ assert(await ctx.routeActivity(null, dupOffer));
3902
+ const rejected = recorder.getMeasurements("activitypub.inbox.activity").filter((m) => m.attributes["activitypub.processing.result"] === "rejected");
3903
+ assertEquals(rejected.length, 2);
3904
+ assertEquals(rejected[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
3905
+ assertEquals(rejected[1].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Offer");
3906
+ }
3907
+ });
3528
3908
  test("ContextImpl.getCollectionUri()", () => {
3529
3909
  const federation = new FederationImpl({ kv: new MemoryKvStore() });
3530
3910
  const base = "https://example.com";
@@ -2,7 +2,7 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("../chunk-DDcVe30Y.cjs");
5
- const require_middleware = require("../middleware-EqTYPG4F.cjs");
5
+ const require_middleware = require("../middleware-CmsDtIHI.cjs");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  //#region src/federation/kv.ts
8
8
  /**
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "../middleware-CuZbBw-N.js";
3
+ import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "../middleware-Dtjz-hSk.js";
4
4
  import { isEqual } from "es-toolkit";
5
5
  //#region src/federation/kv.ts
6
6
  /**
@@ -9,10 +9,10 @@ import { t as assertInstanceOf } from "../assert_instance_of-C4Ri6VuN.mjs";
9
9
  import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
10
10
  import { t as assert } from "../assert-DikXweDx.mjs";
11
11
  import { t as esm_default } from "../esm-sdtqOUPu.mjs";
12
- import { l as verifyRequest } from "../http-BmOZYc-8.mjs";
12
+ import { l as verifyRequest } from "../http-DUV8ysti.mjs";
13
13
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CSYsOMFG.mjs";
14
- import { t as doesActorOwnKey } from "../owner-DO810N24.mjs";
15
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-CAYXdUTk.mjs";
14
+ import { t as doesActorOwnKey } from "../owner-hDxI0ufu.mjs";
15
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-BPhyR5Oo.mjs";
16
16
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
17
17
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
18
18
  //#region ../../node_modules/.pnpm/@opentelemetry+sdk-metrics@2.7.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/sdk-metrics/build/src/export/AggregationTemporality.js