@fedify/fedify 2.4.0-dev.2169 → 2.4.0-dev.2193

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 (103) hide show
  1. package/dist/{builder-B2d_Pt0j.mjs → builder-0onlMY4u.mjs} +26 -6
  2. package/dist/{circuit-breaker-C5NqoLBJ.mjs → circuit-breaker-XJ53md4S.mjs} +16 -11
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{compound-proof-D28gMFqP.mjs → compound-proof-B-Fk6Slo.mjs} +3 -3
  7. package/dist/{context-BykNUf04.d.ts → context-9T--NSo4.d.ts} +55 -1
  8. package/dist/{context-L0Fnib9j.d.cts → context-CDcAgm4l.d.cts} +55 -1
  9. package/dist/{deno-DU8dvnWk.mjs → deno-DB_Sh0LK.mjs} +1 -1
  10. package/dist/{docloader-CghLH7Zi.mjs → docloader-DpWYYY6a.mjs} +25 -17
  11. package/dist/federation/builder.test.mjs +1 -1
  12. package/dist/federation/circuit-breaker.test.mjs +50 -1
  13. package/dist/federation/compound-fanout.test.mjs +3 -3
  14. package/dist/federation/handler-compound-proof.test.mjs +52 -3
  15. package/dist/federation/handler.test.mjs +5 -5
  16. package/dist/federation/hashlink-media.test.mjs +2 -2
  17. package/dist/federation/idempotency.test.mjs +2 -2
  18. package/dist/federation/keycache.test.mjs +1 -1
  19. package/dist/federation/metrics.test.mjs +1 -1
  20. package/dist/federation/middleware.test.mjs +182 -13
  21. package/dist/federation/mod.cjs +1 -1
  22. package/dist/federation/mod.d.cts +2 -2
  23. package/dist/federation/mod.d.ts +2 -2
  24. package/dist/federation/mod.js +1 -1
  25. package/dist/federation/outgoing-proof.test.mjs +5 -5
  26. package/dist/federation/portable-actor.test.mjs +3 -3
  27. package/dist/federation/portable-chain.test.mjs +3 -3
  28. package/dist/federation/portable-collection.test.mjs +4 -4
  29. package/dist/federation/portable-gateway-keys.test.mjs +6 -6
  30. package/dist/federation/portable-inbox.test.mjs +35 -5
  31. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  32. package/dist/federation/portable-route-activity.test.d.mts +2 -0
  33. package/dist/federation/portable-route-activity.test.mjs +259 -0
  34. package/dist/federation/portable.test.mjs +49 -5
  35. package/dist/federation/send.test.mjs +3 -3
  36. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  37. package/dist/federation/tasks/tasks.test.mjs +2 -2
  38. package/dist/federation/temporal.test.mjs +1 -1
  39. package/dist/federation/webfinger.test.mjs +1 -1
  40. package/dist/{federation-DrNgkLfJ.cjs → federation-BsGo0EH2.cjs} +76 -35
  41. package/dist/{http-5CJBdU8n.cjs → http-BEivI0TS.cjs} +279 -87
  42. package/dist/{http-BGpexPO7.js → http-BGmxtpo2.js} +263 -89
  43. package/dist/{http-Cok5-vIC.d.ts → http-BrwKINQn.d.cts} +29 -1
  44. package/dist/{http-DC1cqAQ2.d.cts → http-DGx0KOws.d.ts} +29 -1
  45. package/dist/{http-Bd6rZlJX.mjs → http-DQf9Mw4V.mjs} +180 -92
  46. package/dist/{key-gIhVZO3p.mjs → key-DA3Yx8p2.mjs} +4 -4
  47. package/dist/{key-owner-evidence-Bo3yaeL5.mjs → key-owner-evidence-APB6NpSk.mjs} +1 -1
  48. package/dist/{keycache-PiOM9pM0.mjs → keycache-Da8A8mZa.mjs} +1 -1
  49. package/dist/{kv-cache-YRi_g8GB.cjs → kv-cache-1hafSoqH.cjs} +23 -15
  50. package/dist/{kv-cache-iR55fqlk.js → kv-cache-DduYhpee.js} +24 -16
  51. package/dist/{kv-cache-D0qjSeWY.mjs → kv-cache-OFBvrKh3.mjs} +1 -1
  52. package/dist/{ld-BsTkRrwF.mjs → ld-C15yzuY6.mjs} +3 -3
  53. package/dist/{metrics-jUDFfVgi.mjs → metrics-B1H23mlq.mjs} +1 -1
  54. package/dist/{middleware-Dq-OCmb6.mjs → middleware-BZQnf0nI.mjs} +1 -1
  55. package/dist/{middleware-Coqgp-K0.mjs → middleware-BqqycC0P.mjs} +52 -36
  56. package/dist/{middleware-DlxMxVp5.js → middleware-ClaUFaLb.js} +77 -36
  57. package/dist/{mod-DK0y9fd-.d.ts → mod-B3OBLj5g.d.ts} +1 -1
  58. package/dist/{mod-B5z_q9TH.d.ts → mod-C5Mwx5ty.d.ts} +5 -1
  59. package/dist/{mod-CPPqkyV_.d.cts → mod-Cy9VNHVw.d.ts} +14 -3
  60. package/dist/{mod-BLOXGs6m.d.cts → mod-D58KCqUg.d.cts} +1 -1
  61. package/dist/{mod--jv6vyrP.d.ts → mod-DoTWIc7F.d.cts} +14 -3
  62. package/dist/{mod-BA_F2x53.d.cts → mod-Dp3Ij7sw.d.cts} +5 -1
  63. package/dist/mod.cjs +4 -4
  64. package/dist/mod.d.cts +5 -5
  65. package/dist/mod.d.ts +5 -5
  66. package/dist/mod.js +4 -4
  67. package/dist/nodeinfo/handler.test.mjs +1 -1
  68. package/dist/{owner-D3GoetXS.mjs → owner-BVpZBM-V.mjs} +5 -5
  69. package/dist/{portable-collection-CnlCamup.cjs → portable-collection-BFSShzwh.cjs} +1 -1
  70. package/dist/{portable-collection-Bkdh_WpL.js → portable-collection-D535cSz_.js} +1 -1
  71. package/dist/{portable-collection-C60_acmx.mjs → portable-collection-Dn2GdZdI.mjs} +2 -2
  72. package/dist/{portable-key-DyF5AnZo.mjs → portable-key-Bmwmq7Y6.mjs} +2 -2
  73. package/dist/{portable-key-id-Bg259c4Z.mjs → portable-key-id-Ca30-hOa.mjs} +45 -1
  74. package/dist/{proof-CQAYqWdj.mjs → proof-DrJWKeOl.mjs} +49 -7
  75. package/dist/{send-DjkQrxt5.mjs → send-CT3a-0Vd.mjs} +3 -3
  76. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  77. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  78. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  79. package/dist/sig/compound-proof.test.mjs +2 -2
  80. package/dist/sig/http.test.mjs +495 -23
  81. package/dist/sig/key-owner-evidence.test.mjs +2 -2
  82. package/dist/sig/key.test.mjs +1 -1
  83. package/dist/sig/ld.test.mjs +2 -2
  84. package/dist/sig/mod.cjs +2 -2
  85. package/dist/sig/mod.d.cts +2 -2
  86. package/dist/sig/mod.d.ts +2 -2
  87. package/dist/sig/mod.js +2 -2
  88. package/dist/sig/owner.test.mjs +1 -1
  89. package/dist/sig/portable-collection.test.mjs +1 -1
  90. package/dist/sig/portable-key-id.test.d.mts +2 -0
  91. package/dist/sig/portable-key-id.test.mjs +67 -0
  92. package/dist/sig/portable-key.test.mjs +7 -7
  93. package/dist/sig/proof.test.mjs +141 -7
  94. package/dist/{temporal-oRKP803M.mjs → temporal-DNqluyEc.mjs} +1 -1
  95. package/dist/testing/kv.test.mjs +1 -1
  96. package/dist/testing/mod.d.mts +52 -0
  97. package/dist/utils/docloader.test.mjs +93 -2
  98. package/dist/utils/kv-cache.test.mjs +1 -1
  99. package/dist/utils/mod.cjs +1 -1
  100. package/dist/utils/mod.d.cts +1 -1
  101. package/dist/utils/mod.d.ts +1 -1
  102. package/dist/utils/mod.js +1 -1
  103. package/package.json +7 -7
@@ -0,0 +1,259 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
+ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
+ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
7
+ import "../std__assert-DvMDRPes.mjs";
8
+ import { t as assert } from "../assert-D1Eafmd_.mjs";
9
+ import { a as signObject } from "../proof-DrJWKeOl.mjs";
10
+ import { n as ed25519PrivateKey, r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
11
+ import { o as createFederation } from "../middleware-BqqycC0P.mjs";
12
+ import { Create, Note } from "@fedify/vocab";
13
+ import { mockDocumentLoader, test } from "@fedify/fixture";
14
+ import { exportDidKey, formatIri, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
15
+ //#region src/federation/portable-route-activity.test.ts
16
+ const did = await exportDidKey(ed25519PublicKey.publicKey);
17
+ const keyId = new URL(`${did}#${did.slice(8)}`);
18
+ const otherKeyPair = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
19
+ const otherDid = await exportDidKey(otherKeyPair.publicKey);
20
+ const otherKeyId = new URL(`${otherDid}#${otherDid.slice(8)}`);
21
+ const GATEWAY = "https://gw.example";
22
+ function portableId(authority, path) {
23
+ return parseIri(`ap+ef61://${authority}${path}`);
24
+ }
25
+ function compatibleId(authority, path, gateway = GATEWAY) {
26
+ return new URL(`${gateway}/.well-known/apgateway/${authority}${path}`);
27
+ }
28
+ async function sign(activity, signer) {
29
+ return await signObject(activity, signer === "did" ? ed25519PrivateKey : otherKeyPair.privateKey, signer === "did" ? keyId : otherKeyId, { contextLoader: mockDocumentLoader });
30
+ }
31
+ /**
32
+ * Routes a `Create` activity with the given ID through
33
+ * `Context.routeActivity()`. Unless `activity` is an `Activity`, the routed
34
+ * activity has no proof, so that it is trusted only if it can be dereferenced
35
+ * by its ID, which serves `served`.
36
+ * @returns Whether the activity was routed, and the IDs of the actors of
37
+ * the activities that reached the inbox listener.
38
+ */
39
+ async function route(activity, { served, actors } = {}) {
40
+ const documents = /* @__PURE__ */ new Map();
41
+ if (served != null && activity instanceof URL) {
42
+ const document = await served.toJsonLd({ contextLoader: mockDocumentLoader });
43
+ documents.set(activity.href, document);
44
+ if (activity.protocol === "ap+ef61:") {
45
+ documents.set(formatIri(activity), document);
46
+ documents.set(toCompatibleEf61Id(activity, GATEWAY).href, document);
47
+ }
48
+ }
49
+ const documentLoader = (url, options) => {
50
+ if (documents.has(url)) return Promise.resolve({
51
+ contextUrl: null,
52
+ documentUrl: url,
53
+ document: documents.get(url)
54
+ });
55
+ return mockDocumentLoader(url, options);
56
+ };
57
+ const federation = createFederation({
58
+ kv: new MemoryKvStore(),
59
+ documentLoaderFactory: () => documentLoader,
60
+ contextLoaderFactory: () => mockDocumentLoader
61
+ });
62
+ const received = [];
63
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (_ctx, create) => {
64
+ received.push(...create.actorIds.map((id) => id.href));
65
+ });
66
+ return {
67
+ routed: await federation.createContext(new URL("https://example.com/")).routeActivity(null, activity instanceof URL ? new Create({
68
+ id: activity,
69
+ actors: [...actors ?? served?.actorIds ?? []]
70
+ }) : activity),
71
+ received
72
+ };
73
+ }
74
+ async function portableCreate(id, actors, signer = "did") {
75
+ return await sign(new Create({
76
+ id,
77
+ actors: [...actors],
78
+ object: new Note({ content: "Hello" })
79
+ }), signer);
80
+ }
81
+ test("Context.routeActivity() rejects portable activities attributed to other DIDs", async (t) => {
82
+ const cases = [
83
+ [
84
+ "ap+ef61: IDs",
85
+ portableId(did, "/activities/1"),
86
+ [portableId(otherDid, "/actor")]
87
+ ],
88
+ [
89
+ "compatible identifiers on one gateway",
90
+ compatibleId(did, "/activities/1"),
91
+ [compatibleId(otherDid, "/actor")]
92
+ ],
93
+ [
94
+ "compatible activity ID and ap+ef61: actor ID",
95
+ compatibleId(did, "/activities/1"),
96
+ [portableId(otherDid, "/actor")]
97
+ ],
98
+ [
99
+ "ap+ef61: activity ID and compatible actor ID",
100
+ portableId(did, "/activities/1"),
101
+ [compatibleId(otherDid, "/actor")]
102
+ ],
103
+ [
104
+ "one of multiple actors with another DID",
105
+ portableId(did, "/activities/1"),
106
+ [portableId(did, "/actor"), portableId(otherDid, "/actor")]
107
+ ],
108
+ [
109
+ "ap+ef61: activity ID and HTTP(S) actor ID",
110
+ portableId(did, "/activities/1"),
111
+ [new URL("https://example.com/person")]
112
+ ],
113
+ [
114
+ "compatible activity ID and HTTP(S) actor ID on the gateway",
115
+ compatibleId(did, "/activities/1"),
116
+ [new URL(`${GATEWAY}/users/alice`)]
117
+ ]
118
+ ];
119
+ for (const [name, id, actors] of cases) await t.step(name, async () => {
120
+ const served = await portableCreate(id, actors);
121
+ assertEquals(await route(id, { served }), {
122
+ routed: false,
123
+ received: []
124
+ });
125
+ assertEquals(await route(served), {
126
+ routed: false,
127
+ received: []
128
+ });
129
+ });
130
+ });
131
+ test("Context.routeActivity() accepts portable activities of the same DID", async (t) => {
132
+ const cases = [
133
+ [
134
+ "ap+ef61: IDs",
135
+ portableId(did, "/activities/1"),
136
+ [portableId(did, "/actor")]
137
+ ],
138
+ [
139
+ "compatible identifiers on one gateway",
140
+ compatibleId(did, "/activities/1"),
141
+ [compatibleId(did, "/actor")]
142
+ ],
143
+ [
144
+ "compatible identifiers on different gateways",
145
+ compatibleId(did, "/activities/1"),
146
+ [compatibleId(did, "/actor", "https://gw2.example")]
147
+ ],
148
+ [
149
+ "compatible activity ID and ap+ef61: actor ID",
150
+ compatibleId(did, "/activities/1"),
151
+ [portableId(did, "/actor")]
152
+ ],
153
+ [
154
+ "ap+ef61: activity ID and compatible actor ID",
155
+ portableId(did, "/activities/1"),
156
+ [compatibleId(did, "/actor")]
157
+ ],
158
+ [
159
+ "actor ID with a location hint",
160
+ portableId(did, "/activities/1"),
161
+ [portableId(did, `/actor?@gateway=${encodeURIComponent(GATEWAY)}`)]
162
+ ]
163
+ ];
164
+ for (const [name, id, actors] of cases) await t.step(name, async () => {
165
+ const served = await portableCreate(id, actors);
166
+ const expected = {
167
+ routed: true,
168
+ received: actors.map((actor) => actor.href)
169
+ };
170
+ assertEquals(await route(id, { served }), expected);
171
+ assertEquals(await route(served), expected);
172
+ });
173
+ });
174
+ test("Context.routeActivity() compares portable activity IDs canonically", async (t) => {
175
+ const id = portableId(did, "/activities/1");
176
+ const actors = [portableId(did, "/actor")];
177
+ await t.step("compatible ID serving an ap+ef61: ID", async () => {
178
+ const served = await portableCreate(id, actors);
179
+ const { routed } = await route(compatibleId(did, "/activities/1"), { served });
180
+ assert(routed);
181
+ });
182
+ await t.step("ap+ef61: ID serving a compatible ID", async () => {
183
+ const served = await portableCreate(compatibleId(did, "/activities/1"), actors);
184
+ const { routed } = await route(id, { served });
185
+ assert(routed);
186
+ });
187
+ await t.step("ap+ef61: ID with a location hint", async () => {
188
+ const served = await portableCreate(id, actors);
189
+ const { routed } = await route(portableId(did, `/activities/1?@gateway=${encodeURIComponent(GATEWAY)}`), { served });
190
+ assert(routed);
191
+ });
192
+ await t.step("another path of the same DID", async () => {
193
+ const served = await portableCreate(portableId(did, "/activities/2"), actors);
194
+ const { routed } = await route(id, { served });
195
+ assertFalse(routed);
196
+ });
197
+ await t.step("another fragment of the same DID", async () => {
198
+ const served = await portableCreate(portableId(did, "/activities/1#other"), actors);
199
+ const { routed } = await route(id, { served });
200
+ assertFalse(routed);
201
+ });
202
+ });
203
+ test("Context.routeActivity() rejects proofs of other DIDs for portable actors", async () => {
204
+ const id = portableId(did, "/activities/1");
205
+ const actors = [portableId(did, "/actor")];
206
+ assertEquals(await route(await portableCreate(id, actors, "otherDid")), {
207
+ routed: false,
208
+ received: []
209
+ });
210
+ assertEquals(await route(await portableCreate(id, actors)), {
211
+ routed: true,
212
+ received: actors.map((actor) => actor.href)
213
+ });
214
+ });
215
+ test("Context.routeActivity() keeps comparing web origins of HTTP(S) IDs", async (t) => {
216
+ const cases = [
217
+ [
218
+ "same origin",
219
+ new URL("https://remote.example/activities/1"),
220
+ new URL("https://remote.example/users/alice"),
221
+ true
222
+ ],
223
+ [
224
+ "same origin with an explicit default port",
225
+ new URL("https://remote.example/activities/1"),
226
+ new URL("https://remote.example:443/users/alice"),
227
+ true
228
+ ],
229
+ [
230
+ "another origin",
231
+ new URL("https://remote.example/activities/1"),
232
+ new URL("https://other.example/users/alice"),
233
+ false
234
+ ],
235
+ [
236
+ "another port",
237
+ new URL("https://remote.example/activities/1"),
238
+ new URL("https://remote.example:8443/users/alice"),
239
+ false
240
+ ],
241
+ [
242
+ "compatible actor ID on the same host",
243
+ new URL("https://remote.example/activities/1"),
244
+ compatibleId(did, "/actor", "https://remote.example"),
245
+ false
246
+ ]
247
+ ];
248
+ for (const [name, id, actor, expected] of cases) await t.step(name, async () => {
249
+ const served = new Create({
250
+ id,
251
+ actor
252
+ });
253
+ const { routed, received } = await route(id, { served });
254
+ assertEquals(routed, expected);
255
+ assertEquals(received, expected ? [actor.href] : []);
256
+ });
257
+ });
258
+ //#endregion
259
+ export {};
@@ -8,10 +8,10 @@ import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
10
10
  import { t as assert } from "../assert-D1Eafmd_.mjs";
11
- import { a as signObject, c as verifyPortableObjectProof } from "../proof-CQAYqWdj.mjs";
12
- import { l as signRequest } from "../http-Bd6rZlJX.mjs";
11
+ import { a as signObject, c as verifyPortableObjectProof } from "../proof-DrJWKeOl.mjs";
12
+ import { l as signRequest } from "../http-DQf9Mw4V.mjs";
13
13
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
14
- import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-Coqgp-K0.mjs";
14
+ import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-BqqycC0P.mjs";
15
15
  import { configure, reset } from "@logtape/logtape";
16
16
  import { Collection, Create, Note, Object as Object$1, Person, Tombstone, lookupObject } from "@fedify/vocab";
17
17
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -215,6 +215,11 @@ test("Federation.fetch() applies the FEP-ef61 proof policy to portable objects",
215
215
  object.id.pathname = "/objects/mutated-id";
216
216
  return object;
217
217
  },
218
+ "actor-without-gateways": () => sign(new Person({
219
+ id: parseIri(`ap+ef61://${did}/objects/actor-without-gateways`),
220
+ inbox: parseIri(`ap+ef61://${did}/objects/actor-without-gateways/inbox`),
221
+ outbox: parseIri(`ap+ef61://${did}/objects/actor-without-gateways/outbox`)
222
+ })),
218
223
  "unsigned-actor": () => Promise.resolve(new Person({
219
224
  id: parseIri(`ap+ef61://${did}/objects/unsigned-actor`),
220
225
  inbox: parseIri(`ap+ef61://${did}/objects/unsigned-actor/inbox`),
@@ -234,6 +239,7 @@ test("Federation.fetch() applies the FEP-ef61 proof policy to portable objects",
234
239
  "activity": 200,
235
240
  "actor": 200,
236
241
  "mutated-id": 404,
242
+ "actor-without-gateways": 500,
237
243
  "unsigned-actor": 500
238
244
  })) await t.step(`${id} → ${status}`, async () => {
239
245
  const response = await federation.fetch(gatewayRequest(`/objects/${id}`), { contextData: void 0 });
@@ -716,9 +722,11 @@ test("Federation.fetch() serves compatible-ID actors", async (t) => {
716
722
  })).map((r) => r.properties.gateway), ["https://primary.example"]);
717
723
  });
718
724
  await t.step("warns if the actor has no gateways", async () => {
719
- assertEquals((await captureWarnings(async () => {
725
+ const records = await captureWarnings(async () => {
720
726
  assertEquals((await federation.fetch(new Request("https://example.com/users/nogateway", { headers: { Accept: ACCEPT } }), { contextData: void 0 })).status, 200);
721
- })).map((r) => r.properties.actorId), [gatewayUrl("/actors/nogateway")]);
727
+ });
728
+ assertEquals(records.map((r) => r.properties.actorId), [gatewayUrl("/actors/nogateway")]);
729
+ assert(String(records[0].rawMessage).includes("gateways property"));
722
730
  });
723
731
  await t.step("warns if the ID is a malformed compatible identifier", async () => {
724
732
  const records = await captureWarnings(async () => {
@@ -728,5 +736,41 @@ test("Federation.fetch() serves compatible-ID actors", async (t) => {
728
736
  assert(String(records[0].rawMessage).includes("malformed"));
729
737
  });
730
738
  });
739
+ test("Federation.fetch() warns about portable actors without valid gateways", async (t) => {
740
+ const federation = createTestFederation();
741
+ const actors = {
742
+ valid: (ctx) => new Person({
743
+ id: ctx.getPortableObjectUri(Person, { name: "valid" }, did),
744
+ gateways: [new URL("https://example.com"), new URL("https://other.example/")]
745
+ }),
746
+ empty: (ctx) => new Person({ id: ctx.getPortableObjectUri(Person, { name: "empty" }, did) }),
747
+ mutated: (ctx) => {
748
+ const actor = new Person({
749
+ id: ctx.getPortableObjectUri(Person, { name: "mutated" }, did),
750
+ gateways: [new URL("https://example.com"), new URL("https://other.example/")]
751
+ });
752
+ actor.gateways[1].pathname = "/gateway";
753
+ return actor;
754
+ },
755
+ https: (ctx) => new Person({ id: ctx.getActorUri("https") }),
756
+ tombstone: (ctx) => new Tombstone({ id: ctx.getPortableObjectUri(Person, { name: "tombstone" }, did) })
757
+ };
758
+ federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => actors[identifier]?.(ctx) ?? null);
759
+ federation.setObjectDispatcher(Person, "/actors/{name}", () => null);
760
+ const expected = {
761
+ valid: [],
762
+ empty: [`ap+ef61://${did}/actors/empty`],
763
+ mutated: [`ap+ef61://${did}/actors/mutated`],
764
+ https: [],
765
+ tombstone: []
766
+ };
767
+ for (const [identifier, actorIds] of globalThis.Object.entries(expected)) await t.step(identifier, async () => {
768
+ const actorRecords = (await captureWarnings(async () => {
769
+ await federation.fetch(new Request(`https://example.com/users/${identifier}`, { headers: { Accept: ACCEPT } }), { contextData: void 0 });
770
+ })).filter((r) => r.category.join(".") === "fedify.federation.actor");
771
+ assertEquals(actorRecords.map((r) => r.properties.actorId), actorIds.map((id) => parseIri(id).href));
772
+ for (const record of actorRecords) assert(String(record.rawMessage).includes("gateways property"));
773
+ });
774
+ });
731
775
  //#endregion
732
776
  export {};
@@ -10,10 +10,10 @@ import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
10
10
  import "../std__assert-DvMDRPes.mjs";
11
11
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
12
12
  import { t as assert } from "../assert-D1Eafmd_.mjs";
13
- import { d as verifyRequest } from "../http-Bd6rZlJX.mjs";
13
+ import { f as verifyRequest } from "../http-DQf9Mw4V.mjs";
14
14
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
15
- import { t as doesActorOwnKey } from "../owner-D3GoetXS.mjs";
16
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-DjkQrxt5.mjs";
15
+ import { t as doesActorOwnKey } from "../owner-BVpZBM-V.mjs";
16
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-CT3a-0Vd.mjs";
17
17
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
18
18
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
19
19
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
5
5
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-IG3pHfvZ.mjs";
6
6
  import "../../testing-C1itIjqW.mjs";
7
- import { o as createFederation } from "../../middleware-Coqgp-K0.mjs";
7
+ import { o as createFederation } from "../../middleware-BqqycC0P.mjs";
8
8
  import { n as ParallelMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
9
9
  import { configure, reset } from "@logtape/logtape";
10
10
  import { delay } from "es-toolkit";
@@ -4,8 +4,8 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
5
5
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-IG3pHfvZ.mjs";
6
6
  import "../../testing-C1itIjqW.mjs";
7
- import { o as createFederation } from "../../middleware-Coqgp-K0.mjs";
8
- import { r as createFederationBuilder } from "../../builder-B2d_Pt0j.mjs";
7
+ import { o as createFederation } from "../../middleware-BqqycC0P.mjs";
8
+ import { r as createFederationBuilder } from "../../builder-0onlMY4u.mjs";
9
9
  import { t as TaskCodec } from "../../codec-C6rTrpIp.mjs";
10
10
  import { t as InProcessMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
11
11
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
6
  import { t as assert } from "../assert-D1Eafmd_.mjs";
7
- import { t as hasMalformedKnownTemporalLiteral } from "../temporal-oRKP803M.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-DNqluyEc.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/federation/temporal.test.ts
10
10
  test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
@@ -6,7 +6,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
- import { o as createFederation, s as handleWebFinger } from "../middleware-Coqgp-K0.mjs";
9
+ import { o as createFederation, s as handleWebFinger } from "../middleware-BqqycC0P.mjs";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
11
  import { createTestMeterProvider, test } from "@fedify/fixture";
12
12
  import { parseIri } from "@fedify/vocab-runtime";