@fedify/fedify 2.4.0-dev.2103 → 2.4.0-dev.2115

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 (93) hide show
  1. package/dist/{builder-CHht75pX.mjs → builder-CZcBWrBd.mjs} +29 -5
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-DKKH_Nr1.mjs → compound-proof-CDuP9MIX.mjs} +3 -3
  6. package/dist/{context-mDTxTw-S.d.cts → context-SMNOiiY6.d.cts} +93 -11
  7. package/dist/{context-CfJE_oQu.d.ts → context-tOiJfBoD.d.ts} +93 -11
  8. package/dist/{deno-CkjiYK_Z.mjs → deno-CCABdw7r.mjs} +1 -1
  9. package/dist/{docloader-C1hAJnWc.mjs → docloader-Bby5g2iu.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  13. package/dist/federation/handler.test.mjs +118 -5
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/keycache.test.mjs +67 -1
  17. package/dist/federation/metrics.test.mjs +1 -1
  18. package/dist/federation/middleware.test.mjs +184 -7
  19. package/dist/federation/mod.cjs +1 -1
  20. package/dist/federation/mod.d.cts +3 -3
  21. package/dist/federation/mod.d.ts +3 -3
  22. package/dist/federation/mod.js +1 -1
  23. package/dist/federation/outgoing-proof.test.mjs +5 -5
  24. package/dist/federation/portable-actor.test.mjs +3 -3
  25. package/dist/federation/portable-gateway-keys.test.mjs +24 -7
  26. package/dist/federation/portable-inbox.test.mjs +4 -4
  27. package/dist/federation/portable.test.mjs +31 -4
  28. package/dist/federation/send.test.mjs +3 -3
  29. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  30. package/dist/federation/tasks/tasks.test.mjs +2 -2
  31. package/dist/federation/temporal.test.mjs +1 -1
  32. package/dist/federation/webfinger.test.mjs +1 -1
  33. package/dist/{federation-C1dL6SiT.cjs → federation-DYr384xz.cjs} +116 -15
  34. package/dist/{http-Cala2Grs.cjs → http-B4mQvNcv.cjs} +140 -55
  35. package/dist/{http-BU-8fUrz.mjs → http-BcLnr2do.mjs} +6 -12
  36. package/dist/{http-xlG-jRdB.d.cts → http-BrQgkJay.d.ts} +12 -7
  37. package/dist/{http-tTlTeFa0.js → http-Ce6_Lf7k.js} +140 -55
  38. package/dist/{http-n2v6DPxu.d.ts → http-D76Py0M4.d.cts} +12 -7
  39. package/dist/{key-I8gLBkcf.mjs → key-CkNxztNV.mjs} +107 -16
  40. package/dist/{keycache-BUDHB5rY.mjs → keycache-BInaECGj.mjs} +68 -0
  41. package/dist/{kv-cache-Bl5EBFrU.cjs → kv-cache-Dqp_E-1g.cjs} +1 -1
  42. package/dist/{kv-cache-CxQu_cd4.js → kv-cache-DwzXzchj.js} +1 -1
  43. package/dist/{kv-cache-DJFoGRWQ.mjs → kv-cache-iFaRIcIL.mjs} +1 -1
  44. package/dist/{ld-wiJbqJHa.mjs → ld-CnT0f1nU.mjs} +4 -7
  45. package/dist/{metrics-BCAa5-kE.mjs → metrics-em_7lwhG.mjs} +1 -1
  46. package/dist/{middleware-FnITXhtz.mjs → middleware-BpwKvOpU.mjs} +26 -17
  47. package/dist/{middleware-B6IVTVL0.js → middleware-DhIBngkj.js} +108 -7
  48. package/dist/{middleware-6p2dYEVA.mjs → middleware-U9IuzvDO.mjs} +1 -1
  49. package/dist/{mod-BYvPz_T0.d.ts → mod-COWgSo2v.d.ts} +1 -1
  50. package/dist/{mod-Coacostr.d.ts → mod-CTh7ijx5.d.ts} +1 -1
  51. package/dist/{mod-xGFCHqq_.d.cts → mod-Cr79cQlc.d.cts} +1 -1
  52. package/dist/{mod-CVZrXSdm.d.ts → mod-Cwdt_CWU.d.ts} +1 -1
  53. package/dist/{mod-C8LieBzF.d.cts → mod-DMdGFMEl.d.cts} +1 -1
  54. package/dist/{mod-B3OzOobN.d.cts → mod-DnElMslb.d.cts} +1 -1
  55. package/dist/mod.cjs +5 -5
  56. package/dist/mod.d.cts +7 -7
  57. package/dist/mod.d.ts +7 -7
  58. package/dist/mod.js +5 -5
  59. package/dist/nodeinfo/handler.test.mjs +1 -1
  60. package/dist/{owner-BJp47LxN.mjs → owner-9LiHmOv1.mjs} +4 -4
  61. package/dist/{owner-DCt8W_Cv.cjs → owner-BtoMCv6Z.cjs} +1 -1
  62. package/dist/{owner-j5yrbXS1.js → owner-Cb3ytrlq.js} +1 -1
  63. package/dist/{portable-key-B5oKE_wZ.mjs → portable-key-9ck61lI0.mjs} +5 -4
  64. package/dist/{portable-key-id-Bva5iT3W.mjs → portable-key-id-T7lecIbt.mjs} +3 -2
  65. package/dist/{proof-BE4T5zF6.mjs → proof-xdPYGPaA.mjs} +32 -31
  66. package/dist/{send-BFITuKHw.mjs → send-Bf8rSWch.mjs} +3 -3
  67. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  68. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  69. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  70. package/dist/sig/compound-proof.test.mjs +2 -2
  71. package/dist/sig/http.test.mjs +2 -2
  72. package/dist/sig/key.test.mjs +1 -1
  73. package/dist/sig/ld.test.mjs +2 -2
  74. package/dist/sig/mod.cjs +3 -3
  75. package/dist/sig/mod.d.cts +2 -2
  76. package/dist/sig/mod.d.ts +2 -2
  77. package/dist/sig/mod.js +3 -3
  78. package/dist/sig/owner.test.mjs +1 -1
  79. package/dist/sig/portable-collection.test.mjs +2 -2
  80. package/dist/sig/portable-key.test.mjs +301 -23
  81. package/dist/sig/proof.test.mjs +3 -3
  82. package/dist/{sig-CRTRoouH.cjs → sig-Bj3fHnv0.cjs} +2 -2
  83. package/dist/{sig-CJHdGk9w.js → sig-DN9pOglI.js} +2 -2
  84. package/dist/{temporal-CyDDbE4i.mjs → temporal-tAtwv6vX.mjs} +1 -1
  85. package/dist/testing/kv.test.mjs +1 -1
  86. package/dist/testing/mod.d.mts +91 -9
  87. package/dist/utils/docloader.test.mjs +2 -2
  88. package/dist/utils/kv-cache.test.mjs +1 -1
  89. package/dist/utils/mod.cjs +1 -1
  90. package/dist/utils/mod.d.cts +1 -1
  91. package/dist/utils/mod.d.ts +1 -1
  92. package/dist/utils/mod.js +1 -1
  93. package/package.json +7 -7
@@ -7,15 +7,15 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
7
7
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
10
- import { p as signJsonLd } from "../ld-wiJbqJHa.mjs";
11
- import { l as signRequest } from "../http-BU-8fUrz.mjs";
10
+ import { p as signJsonLd } from "../ld-CnT0f1nU.mjs";
11
+ import { l as signRequest } from "../http-BcLnr2do.mjs";
12
12
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
13
- import { f as handleInbox, n as FederationImpl, o as createFederation } from "../middleware-FnITXhtz.mjs";
13
+ import { f as handleInbox, n as FederationImpl, o as createFederation } from "../middleware-BpwKvOpU.mjs";
14
14
  import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
15
- import { t as KvKeyCache } from "../keycache-BUDHB5rY.mjs";
15
+ import { t as KvKeyCache } from "../keycache-BInaECGj.mjs";
16
16
  import { Create, CryptographicKey, Person } from "@fedify/vocab";
17
17
  import { mockDocumentLoader, test } from "@fedify/fixture";
18
- import { encodeMultibase, exportDidKey, parseIri } from "@fedify/vocab-runtime";
18
+ import { FetchError, encodeMultibase, exportDidKey, parseIri } from "@fedify/vocab-runtime";
19
19
  import serialize from "json-canon";
20
20
  //#region src/federation/portable-gateway-keys.test.ts
21
21
  const did = await exportDidKey(ed25519PublicKey.publicKey);
@@ -255,8 +255,8 @@ function createLoader(responses) {
255
255
  });
256
256
  };
257
257
  }
258
- async function deliver(body, gateways, { kv = new MemoryKvStore(), httpSignature = true, actor } = {}) {
259
- const documentLoader = createLoader({ [compatibleActorId(gateway)]: actor ?? await actorDocument(gateways) });
258
+ async function deliver(body, gateways, { kv = new MemoryKvStore(), httpSignature = true, actor, documentLoader } = {}) {
259
+ documentLoader ??= createLoader({ [compatibleActorId(gateway)]: actor ?? await actorDocument(gateways) });
260
260
  const unsigned = new Request("https://local.example/inbox", {
261
261
  method: "POST",
262
262
  body: JSON.stringify(body),
@@ -332,6 +332,23 @@ test("handleInbox() requires proofs of portable actors despite gateway signature
332
332
  dispatched: 1
333
333
  });
334
334
  });
335
+ test("handleInbox() fetches an unreachable gateway key once", async () => {
336
+ const fetched = [];
337
+ const documentLoader = (url) => {
338
+ if (url.replace(/#.*$/, "") !== compatibleActorId(gateway)) return mockDocumentLoader(url);
339
+ fetched.push(url);
340
+ return Promise.reject(new FetchError(url, "HTTP 503", new Response(null, { status: 503 })));
341
+ };
342
+ const kv = new MemoryKvStore();
343
+ for (let i = 0; i < 3; i++) {
344
+ const { status } = await deliver(await createJson(`unreachable-${i}`), [gateway], {
345
+ kv,
346
+ documentLoader
347
+ });
348
+ assertEquals(status, 401);
349
+ }
350
+ assertEquals(fetched.length, 1);
351
+ });
335
352
  test("handleInbox() does not need gateway signatures for portable actors", async () => {
336
353
  assertEquals(await deliver(await sign(await createJson("signed")), [otherGateway]), {
337
354
  status: 202,
@@ -8,11 +8,11 @@ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
8
8
  import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
9
9
  import "../std__assert-DvMDRPes.mjs";
10
10
  import { t as assert } from "../assert-D1Eafmd_.mjs";
11
- import { p as signJsonLd } from "../ld-wiJbqJHa.mjs";
12
- import { i as signObject } from "../proof-BE4T5zF6.mjs";
13
- import { f as verifyRequestDetailed, l as signRequest } from "../http-BU-8fUrz.mjs";
11
+ import { p as signJsonLd } from "../ld-CnT0f1nU.mjs";
12
+ import { i as signObject } from "../proof-xdPYGPaA.mjs";
13
+ import { f as verifyRequestDetailed, l as signRequest } from "../http-BcLnr2do.mjs";
14
14
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
15
- import { _ as coversDelivery, o as createFederation, v as forwardPortableInboxActivity, y as resolvePortableInboxForwardingOptions } from "../middleware-FnITXhtz.mjs";
15
+ import { _ as coversDelivery, o as createFederation, v as forwardPortableInboxActivity, y as resolvePortableInboxForwardingOptions } from "../middleware-BpwKvOpU.mjs";
16
16
  import { Create, CryptographicKey, Follow, Note, Person, Tombstone } from "@fedify/vocab";
17
17
  import { mockDocumentLoader, test } from "@fedify/fixture";
18
18
  import { exportDidKey, formatIri, parseIri } from "@fedify/vocab-runtime";
@@ -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 { i as signObject, s as verifyPortableObjectProof } from "../proof-BE4T5zF6.mjs";
12
- import { l as signRequest } from "../http-BU-8fUrz.mjs";
11
+ import { i as signObject, s as verifyPortableObjectProof } from "../proof-xdPYGPaA.mjs";
12
+ import { l as signRequest } from "../http-BcLnr2do.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-FnITXhtz.mjs";
14
+ import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-BpwKvOpU.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";
@@ -220,6 +220,18 @@ test("Federation.fetch() applies the FEP-ef61 proof policy to portable objects",
220
220
  inbox: parseIri(`ap+ef61://${did}/objects/unsigned-actor/inbox`),
221
221
  outbox: parseIri(`ap+ef61://${did}/objects/unsigned-actor/outbox`),
222
222
  gateways: [new URL("https://example.com/")]
223
+ })),
224
+ "tombstone": () => sign(new Tombstone({
225
+ id: parseIri(`ap+ef61://${did}/objects/tombstone`),
226
+ formerType: Note
227
+ })),
228
+ "unsigned-tombstone": () => Promise.resolve(new Tombstone({
229
+ id: parseIri(`ap+ef61://${did}/objects/unsigned-tombstone`),
230
+ formerType: Note
231
+ })),
232
+ "mismatched-tombstone": () => sign(new Tombstone({
233
+ id: parseIri(`ap+ef61://${did}/objects/other`),
234
+ formerType: Note
223
235
  }))
224
236
  };
225
237
  federation.setObjectDispatcher(Object$1, "/objects/{id}", (_ctx, { id }) => objects[id]?.() ?? null);
@@ -234,7 +246,10 @@ test("Federation.fetch() applies the FEP-ef61 proof policy to portable objects",
234
246
  "activity": 200,
235
247
  "actor": 200,
236
248
  "mutated-id": 404,
237
- "unsigned-actor": 500
249
+ "unsigned-actor": 500,
250
+ "tombstone": 200,
251
+ "unsigned-tombstone": 500,
252
+ "mismatched-tombstone": 404
238
253
  })) await t.step(`${id} → ${status}`, async () => {
239
254
  const response = await federation.fetch(gatewayRequest(`/objects/${id}`), { contextData: void 0 });
240
255
  assertEquals(response.status, status);
@@ -245,6 +260,18 @@ test("Federation.fetch() applies the FEP-ef61 proof policy to portable objects",
245
260
  assertEquals(response.status, 500);
246
261
  assertEquals(response.body, null);
247
262
  });
263
+ await t.step("serves a signed tombstone with its body", async () => {
264
+ const response = await federation.fetch(gatewayRequest("/objects/tombstone"), { contextData: void 0 });
265
+ assertEquals(response.status, 200);
266
+ const body = await response.json();
267
+ assertEquals(body.type, "Tombstone");
268
+ assertEquals(body.id, `ap+ef61://${did}/objects/tombstone`);
269
+ });
270
+ await t.step("responds to HEAD for a tombstone without a body", async () => {
271
+ const response = await federation.fetch(gatewayRequest("/objects/tombstone", { method: "HEAD" }), { contextData: void 0 });
272
+ assertEquals(response.status, 200);
273
+ assertEquals(response.body, null);
274
+ });
248
275
  });
249
276
  test("Federation.fetch() authorizes portable object requests", async (t) => {
250
277
  const federation = createTestFederation();
@@ -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-BU-8fUrz.mjs";
13
+ import { d as verifyRequest } from "../http-BcLnr2do.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-BJp47LxN.mjs";
16
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-BFITuKHw.mjs";
15
+ import { t as doesActorOwnKey } from "../owner-9LiHmOv1.mjs";
16
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-Bf8rSWch.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-dbCShlCb.mjs";
6
6
  import "../../testing-BWq1dCzx.mjs";
7
- import { o as createFederation } from "../../middleware-FnITXhtz.mjs";
7
+ import { o as createFederation } from "../../middleware-BpwKvOpU.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-dbCShlCb.mjs";
6
6
  import "../../testing-BWq1dCzx.mjs";
7
- import { o as createFederation } from "../../middleware-FnITXhtz.mjs";
8
- import { r as createFederationBuilder } from "../../builder-CHht75pX.mjs";
7
+ import { o as createFederation } from "../../middleware-BpwKvOpU.mjs";
8
+ import { r as createFederationBuilder } from "../../builder-CZcBWrBd.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-CyDDbE4i.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-tAtwv6vX.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-FnITXhtz.mjs";
9
+ import { o as createFederation, s as handleWebFinger } from "../middleware-BpwKvOpU.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";
@@ -1,10 +1,10 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http-Cala2Grs.cjs");
4
+ const require_http = require("./http-B4mQvNcv.cjs");
5
5
  const require_types = require("./types-Be2zbyeG.cjs");
6
- const require_owner = require("./owner-DCt8W_Cv.cjs");
7
- const require_kv_cache = require("./kv-cache-Bl5EBFrU.cjs");
6
+ const require_owner = require("./owner-BtoMCv6Z.cjs");
7
+ const require_kv_cache = require("./kv-cache-Dqp_E-1g.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
@@ -342,8 +342,8 @@ var FederationBuilderImpl = class {
342
342
  const variables = _fedify_uri_template.Router.variables(path);
343
343
  this.router.add(path, routeName);
344
344
  const callbacks = {
345
- dispatcher: (ctx, values) => {
346
- return this._getTracer().startActiveSpan("activitypub.dispatch_object", {
345
+ dispatcher: async (ctx, values) => {
346
+ const object = await this._getTracer().startActiveSpan("activitypub.dispatch_object", {
347
347
  kind: _opentelemetry_api.SpanKind.SERVER,
348
348
  attributes: {
349
349
  "fedify.object.type": cls.typeId.href,
@@ -366,6 +366,8 @@ var FederationBuilderImpl = class {
366
366
  span.end();
367
367
  }
368
368
  });
369
+ if (object instanceof _fedify_vocab.Tombstone) warnMismatchedTombstoneId(ctx, cls, values, object);
370
+ return object;
369
371
  },
370
372
  parameters: variables
371
373
  };
@@ -735,6 +737,28 @@ var FederationBuilderImpl = class {
735
737
  function createFederationBuilder() {
736
738
  return new FederationBuilderImpl();
737
739
  }
740
+ function warnMismatchedTombstoneId(context, cls, values, tombstone) {
741
+ const logger = (0, _logtape_logtape.getLogger)([
742
+ "fedify",
743
+ "federation",
744
+ "object"
745
+ ]);
746
+ if (tombstone.id == null) {
747
+ logger.warn("Object dispatcher for {class} returned a tombstone without an id property. Set the property with Context.getObjectUri().", {
748
+ class: cls.name,
749
+ values
750
+ });
751
+ return;
752
+ }
753
+ if (tombstone.id.protocol === "ap:" || tombstone.id.protocol === "ap+ef61:") return;
754
+ const expected = context.getObjectUri(cls, values);
755
+ if (tombstone.id.href !== expected.href) logger.warn("Object dispatcher for {class} returned a tombstone with an id property {tombstoneId} that does not match the object URI {objectUri}. Set the property with Context.getObjectUri().", {
756
+ class: cls.name,
757
+ values,
758
+ tombstoneId: tombstone.id.href,
759
+ objectUri: expected.href
760
+ });
761
+ }
738
762
  /**
739
763
  * Warns about an FEP-ef61 portable actor identified by a compatible identifier
740
764
  * that is malformed, or is not on the actor's first gateway, where FEP-ef61
@@ -2212,6 +2236,11 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
2212
2236
  //#region src/federation/keycache.ts
2213
2237
  const Temporal$9 = require_http.Temporal;
2214
2238
  const KEY_CACHE_GENERATION = "2";
2239
+ const COMPATIBLE_KEY_SEGMENT = "__compatible";
2240
+ const MAX_COMPATIBLE_KEY_TTL = Temporal$9.Duration.from({ hours: 1 });
2241
+ function isCompatibleKeyEntry(value) {
2242
+ return value != null && typeof value === "object" && "key" in value && "expires" in value && typeof value.expires === "number";
2243
+ }
2215
2244
  var KvKeyCache = class {
2216
2245
  kv;
2217
2246
  prefix;
@@ -2274,6 +2303,69 @@ var KvKeyCache = class {
2274
2303
  const serialized = await key.toJsonLd(this.options);
2275
2304
  await this.kv.set(this.#entryKey(keyId), serialized, { ttl: this.keyTtl });
2276
2305
  }
2306
+ /**
2307
+ * Returns the namespace of keys at FEP-ef61 compatible identifiers looked
2308
+ * up for the given purpose. Its entries carry the time they expire, which
2309
+ * is checked when they are read, so that an entry is never used after
2310
+ * the proof that vouched for its key expired, however late the underlying
2311
+ * store evicts it.
2312
+ * @param scope The purpose of the lookups.
2313
+ * @returns The namespace.
2314
+ * @internal
2315
+ */
2316
+ compatibleKeyScope(scope) {
2317
+ const now = this.options.now ?? (() => Temporal$9.Now.instant());
2318
+ const entryKey = (keyId) => [
2319
+ ...this.prefix,
2320
+ COMPATIBLE_KEY_SEGMENT,
2321
+ scope,
2322
+ keyId.href
2323
+ ];
2324
+ const isExpired = (entry) => entry.expires <= now().epochMilliseconds;
2325
+ const keyTtl = Math.min(this.keyTtl.total("millisecond"), MAX_COMPATIBLE_KEY_TTL.total("millisecond"));
2326
+ const unavailableKeyTtl = this.unavailableKeyTtl.total("millisecond");
2327
+ return {
2328
+ get: async (keyId) => {
2329
+ const entry = await this.kv.get(entryKey(keyId));
2330
+ if (entry === void 0) return void 0;
2331
+ if (!isCompatibleKeyEntry(entry) || isExpired(entry)) {
2332
+ await this.kv.delete(entryKey(keyId));
2333
+ return;
2334
+ }
2335
+ if (entry.key === null) return null;
2336
+ let key;
2337
+ try {
2338
+ key = await _fedify_vocab.CryptographicKey.fromJsonLd(entry.key, this.options);
2339
+ } catch {
2340
+ try {
2341
+ key = await _fedify_vocab.Multikey.fromJsonLd(entry.key, this.options);
2342
+ } catch {
2343
+ await this.kv.delete(entryKey(keyId));
2344
+ return;
2345
+ }
2346
+ }
2347
+ return isExpired(entry) ? void 0 : key;
2348
+ },
2349
+ set: async (keyId, key, options) => {
2350
+ const serialized = key == null ? null : await key.toJsonLd(this.options);
2351
+ const current = now();
2352
+ let expires = current.epochMilliseconds + (key == null ? unavailableKeyTtl : keyTtl);
2353
+ if (options?.expires != null) expires = Math.min(expires, options.expires.epochMilliseconds);
2354
+ const ttl = expires - current.epochMilliseconds;
2355
+ if (ttl <= 0) {
2356
+ await this.kv.delete(entryKey(keyId));
2357
+ return;
2358
+ }
2359
+ await this.kv.set(entryKey(keyId), {
2360
+ key: serialized,
2361
+ expires
2362
+ }, { ttl: Temporal$9.Duration.from({ milliseconds: ttl }) });
2363
+ },
2364
+ delete: async (keyId) => {
2365
+ await this.kv.delete(entryKey(keyId));
2366
+ }
2367
+ };
2368
+ }
2277
2369
  async getFetchError(keyId) {
2278
2370
  const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
2279
2371
  if (cached == null || typeof cached !== "object") return void 0;
@@ -3427,6 +3519,13 @@ async function handleObject(request, { values, context, objectDispatcher, author
3427
3519
  if (!await authorizePredicate(context, values)) return await onUnauthorized(request);
3428
3520
  }
3429
3521
  const jsonLd = await object.toJsonLd(context);
3522
+ if (object instanceof _fedify_vocab.Tombstone) return new Response(request.method === "HEAD" ? null : JSON.stringify(jsonLd), {
3523
+ status: 410,
3524
+ headers: {
3525
+ "Content-Type": "application/activity+json",
3526
+ Vary: "Accept"
3527
+ }
3528
+ });
3430
3529
  return new Response(JSON.stringify(jsonLd), { headers: {
3431
3530
  "Content-Type": "application/activity+json",
3432
3531
  Vary: "Accept"
@@ -3560,13 +3659,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
3560
3659
  * @param parameters The parameters for handling the collection.
3561
3660
  * @returns A promise that resolves to an HTTP response.
3562
3661
  */
3563
- async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
3564
- const spanName = name$1.trim().replace(/\s+/g, "_");
3662
+ async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
3663
+ const spanName = name$2.trim().replace(/\s+/g, "_");
3565
3664
  tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
3566
3665
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
3567
3666
  const cursor = new URL(request.url).searchParams.get("cursor");
3568
3667
  const metricBase = {
3569
- kind: getCollectionMetricKind(name$1),
3668
+ kind: getCollectionMetricKind(name$2),
3570
3669
  page: cursor != null,
3571
3670
  dispatcher: "built_in"
3572
3671
  };
@@ -3607,7 +3706,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
3607
3706
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
3608
3707
  return await onNotFound(request);
3609
3708
  }
3610
- const items = filterCollectionItems(page.items, name$1, filterPredicate);
3709
+ const items = filterCollectionItems(page.items, name$2, filterPredicate);
3611
3710
  itemCount = items.length;
3612
3711
  span.setAttribute("fedify.collection.items", itemCount);
3613
3712
  return items;
@@ -3647,7 +3746,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
3647
3746
  } else {
3648
3747
  const uri = new URL(baseUri);
3649
3748
  uri.searchParams.set("cursor", cursor);
3650
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
3749
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
3651
3750
  kind: _opentelemetry_api.SpanKind.SERVER,
3652
3751
  attributes: {
3653
3752
  "activitypub.collection.id": uri.href,
@@ -3663,7 +3762,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
3663
3762
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
3664
3763
  return await onNotFound(request);
3665
3764
  }
3666
- const items = filterCollectionItems(page.items, name$1, filterPredicate);
3765
+ const items = filterCollectionItems(page.items, name$2, filterPredicate);
3667
3766
  itemCount = items.length;
3668
3767
  span.setAttribute("fedify.collection.items", itemCount);
3669
3768
  return {
@@ -4851,8 +4950,8 @@ var CustomCollectionHandler = class {
4851
4950
  * @param CollectionPage The CollectionPage constructor.
4852
4951
  * @param filterPredicate Optional filter predicate for items.
4853
4952
  */
4854
- constructor(name$2, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
4855
- this.name = name$2;
4953
+ constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
4954
+ this.name = name$1;
4856
4955
  this.values = values;
4857
4956
  this.context = context;
4858
4957
  this.callbacks = callbacks;
@@ -9341,7 +9440,7 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
9341
9440
  if (actor instanceof _fedify_vocab.Tombstone && options?.tombstone !== "passthrough") return null;
9342
9441
  return actor;
9343
9442
  }
9344
- async getObject(cls, values) {
9443
+ async getObject(cls, values, options) {
9345
9444
  const callbacks = this.federation.objectCallbacks[cls.typeId.href];
9346
9445
  if (callbacks == null) throw new Error("No object dispatcher registered.");
9347
9446
  for (const param of callbacks.parameters) if (!(param in values)) throw new TypeError(`Missing parameter: ${param}`);
@@ -9351,13 +9450,15 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
9351
9450
  actorDispatcherClass: this.#invokedFromObjectDispatcher.cls.name,
9352
9451
  actorDispatcherValues: this.#invokedFromObjectDispatcher.values
9353
9452
  });
9354
- return await callbacks.dispatcher(new RequestContextImpl({
9453
+ const object = await callbacks.dispatcher(new RequestContextImpl({
9355
9454
  ...this,
9356
9455
  invokedFromObjectDispatcher: {
9357
9456
  cls,
9358
9457
  values
9359
9458
  }
9360
9459
  }), values);
9460
+ if (object instanceof _fedify_vocab.Tombstone && !(object instanceof cls) && options?.tombstone !== "passthrough") return null;
9461
+ return object;
9361
9462
  }
9362
9463
  #signedKey = void 0;
9363
9464
  async getSignedKey(options = {}) {