@fedify/fedify 2.4.0-dev.2219 → 2.4.0-dev.2228

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 (73) hide show
  1. package/dist/{builder-C5rYBZ16.mjs → builder-Wx64uCV2.mjs} +2 -2
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{compound-proof-D0xY7MBs.mjs → compound-proof-B647GUyL.mjs} +2 -2
  4. package/dist/{deno-Dj1TFhf4.mjs → deno-9flirCWb.mjs} +1 -1
  5. package/dist/{docloader-CzyJe90o.mjs → docloader-pjAbs1lc.mjs} +2 -2
  6. package/dist/federation/builder.test.mjs +1 -1
  7. package/dist/federation/compound-fanout.test.mjs +3 -3
  8. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  9. package/dist/federation/handler.test.mjs +5 -5
  10. package/dist/federation/hashlink-media.test.mjs +2 -2
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/metrics.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +7 -7
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.js +1 -1
  16. package/dist/federation/outgoing-proof.test.mjs +248 -6
  17. package/dist/federation/portable-actor.test.mjs +3 -3
  18. package/dist/federation/portable-chain.test.mjs +3 -3
  19. package/dist/federation/portable-collection.test.mjs +4 -4
  20. package/dist/federation/portable-delivery.test.mjs +2 -2
  21. package/dist/federation/portable-gateway-keys.test.mjs +4 -4
  22. package/dist/federation/portable-inbox.test.mjs +4 -4
  23. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  24. package/dist/federation/portable-route-activity.test.mjs +2 -2
  25. package/dist/federation/portable.test.mjs +80 -3
  26. package/dist/federation/send.test.mjs +3 -3
  27. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  28. package/dist/federation/tasks/tasks.test.mjs +2 -2
  29. package/dist/federation/temporal.test.mjs +1 -1
  30. package/dist/federation/webfinger.test.mjs +1 -1
  31. package/dist/{federation-DC1yfb-8.cjs → federation-DiycMKQx.cjs} +245 -30
  32. package/dist/{http-Dx7yMmfw.js → http-CHqz_Jp6.js} +1 -1
  33. package/dist/{http-Cuk0w0rp.cjs → http-DC5DyShJ.cjs} +1 -1
  34. package/dist/{http-D-DpP4sd.mjs → http-DysQjjJe.mjs} +4 -4
  35. package/dist/{key-Djyt1BFx.mjs → key-TtgqklZ1.mjs} +2 -2
  36. package/dist/{kv-cache-ecUXfCqW.mjs → kv-cache-B7g5Tvl0.mjs} +1 -1
  37. package/dist/{kv-cache-CUZOCSKZ.js → kv-cache-CgYK8siN.js} +1 -1
  38. package/dist/{kv-cache-BmLTVLXc.cjs → kv-cache-CsNDrjVT.cjs} +1 -1
  39. package/dist/{ld-CNVWdooi.mjs → ld-CHYUee-s.mjs} +3 -3
  40. package/dist/{metrics-BnYleVpg.mjs → metrics-X1lpKWRK.mjs} +1 -1
  41. package/dist/{middleware-BdzcazaN.js → middleware-D2scpP9Q.js} +245 -30
  42. package/dist/{middleware-C2_LcbL3.mjs → middleware-DoAk0Q3s.mjs} +242 -27
  43. package/dist/{middleware-Bh592bk8.mjs → middleware-ndPtgYYI.mjs} +1 -1
  44. package/dist/mod.cjs +4 -4
  45. package/dist/mod.js +4 -4
  46. package/dist/nodeinfo/handler.test.mjs +1 -1
  47. package/dist/{owner-D_BDPwm2.mjs → owner-BP_ouAvi.mjs} +3 -3
  48. package/dist/{portable-collection-C0c0rUN9.js → portable-collection-4SnmDcTc.js} +1 -1
  49. package/dist/{portable-collection-DwJ8YJP2.cjs → portable-collection-Cz9K1Tas.cjs} +1 -1
  50. package/dist/{portable-collection-hcONGyUs.mjs → portable-collection-SqxusPj7.mjs} +2 -2
  51. package/dist/{portable-key-B880yVlC.mjs → portable-key-DYr6MZUl.mjs} +1 -1
  52. package/dist/{proof-z6Prgi5I.mjs → proof-B-iLzyva.mjs} +4 -4
  53. package/dist/{send-Cw545L_2.mjs → send-D0nNms3Q.mjs} +3 -3
  54. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  55. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  56. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  57. package/dist/sig/compound-proof.test.mjs +2 -2
  58. package/dist/sig/http.test.mjs +2 -2
  59. package/dist/sig/key.test.mjs +1 -1
  60. package/dist/sig/ld.test.mjs +2 -2
  61. package/dist/sig/mod.cjs +2 -2
  62. package/dist/sig/mod.js +2 -2
  63. package/dist/sig/owner.test.mjs +1 -1
  64. package/dist/sig/portable-collection.test.mjs +1 -1
  65. package/dist/sig/portable-key.test.mjs +6 -6
  66. package/dist/sig/proof.test.mjs +5 -3
  67. package/dist/{temporal-Cy-8WCod.mjs → temporal-39Ixir8d.mjs} +1 -1
  68. package/dist/testing/kv.test.mjs +1 -1
  69. package/dist/utils/docloader.test.mjs +2 -2
  70. package/dist/utils/kv-cache.test.mjs +1 -1
  71. package/dist/utils/mod.cjs +1 -1
  72. package/dist/utils/mod.js +1 -1
  73. package/package.json +7 -7
@@ -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-z6Prgi5I.mjs";
12
- import { l as signRequest } from "../http-D-DpP4sd.mjs";
11
+ import { a as signObject, c as verifyPortableObjectProof } from "../proof-B-iLzyva.mjs";
12
+ import { l as signRequest } from "../http-DysQjjJe.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-C2_LcbL3.mjs";
14
+ import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-DoAk0Q3s.mjs";
15
15
  import { configure, reset } from "@logtape/logtape";
16
16
  import { Collection, Create, Note, Object as Object$1, PUBLIC_COLLECTION, Person, Tombstone, lookupObject } from "@fedify/vocab";
17
17
  import { exportDidKey, formatIri, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
@@ -865,6 +865,83 @@ async function captureWarnings(run) {
865
865
  }
866
866
  return records.filter((record) => record.category[0] === "fedify");
867
867
  }
868
+ test("object responses warn about compatible IDs off the owner's first gateway", async () => {
869
+ const federation = createTestFederation();
870
+ const ownerId = parseIri(`ap://${did}/users/alice`);
871
+ const gateways = [new URL("https://primary.example"), new URL("https://secondary.example")];
872
+ const secondary = await sign(new Note({
873
+ id: toCompatibleEf61Id(parseIri(`ap://${did}/notes/secondary`), gateways[1]),
874
+ attribution: ownerId,
875
+ to: PUBLIC_COLLECTION,
876
+ content: "Hello"
877
+ }));
878
+ let actorCalls = 0;
879
+ let actorLookupFails = false;
880
+ federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => {
881
+ actorCalls++;
882
+ if (actorLookupFails) throw new Error("Actor storage unavailable");
883
+ return identifier === "alice" ? new Person({
884
+ id: ownerId,
885
+ gateways
886
+ }) : null;
887
+ });
888
+ federation.setObjectDispatcher(Note, "/notes/{id}", async (_ctx, values) => {
889
+ if (values.id === "secondary") return secondary;
890
+ const note = new Note({
891
+ id: values.id === "portable" ? parseIri(`ap://${did}/notes/portable`) : values.id === "ordinary" ? new URL("https://example.com/notes/ordinary") : toCompatibleEf61Id(parseIri(`ap://${did}/notes/${values.id}`), values.id === "secondary" || values.id === "retained" ? gateways[1] : gateways[0]),
892
+ attribution: ownerId,
893
+ to: PUBLIC_COLLECTION,
894
+ content: "Hello"
895
+ });
896
+ if (values.id === "retained") {
897
+ const raw = await (await sign(note)).toJsonLd({
898
+ format: "compact",
899
+ contextLoader: mockDocumentLoader
900
+ });
901
+ const parsed = await Note.fromJsonLd(raw, {
902
+ contextLoader: mockDocumentLoader,
903
+ documentLoader: mockDocumentLoader
904
+ });
905
+ parsed.id.hostname = "primary.example";
906
+ return parsed;
907
+ }
908
+ return note;
909
+ });
910
+ federation.setObjectDispatcher(Create, "/activities/{id}", (_ctx, values) => new Create({
911
+ id: toCompatibleEf61Id(parseIri(`ap://${did}/activities/${values.id}`), gateways[1]),
912
+ actor: ownerId
913
+ }));
914
+ for (const [id, expected] of [
915
+ ["secondary", 1],
916
+ ["retained", 1],
917
+ ["primary", 0],
918
+ ["portable", 0],
919
+ ["ordinary", 0]
920
+ ]) assertEquals((await captureWarnings(async () => {
921
+ const response = await federation.fetch(new Request(`https://example.com/notes/${id}`, { headers: { Accept: ACCEPT } }), { contextData: void 0 });
922
+ assertEquals(response.status, 200);
923
+ const body = await response.json();
924
+ assertEquals(body.type, "Note");
925
+ if (id === "retained") assertEquals(body.id, `https://secondary.example/.well-known/apgateway/${did}/notes/retained`);
926
+ })).filter((r) => r.properties.kind === "object").length, expected, id);
927
+ assertEquals((await captureWarnings(async () => {
928
+ assertEquals((await federation.fetch(new Request("https://example.com/activities/1", { headers: { Accept: ACCEPT } }), { contextData: void 0 })).status, 200);
929
+ })).filter((r) => r.properties.kind === "activity").length, 1);
930
+ for (const id of ["secondary", "retained"]) assertEquals((await captureWarnings(async () => {
931
+ assertEquals((await federation.fetch(gatewayRequest(`/notes/${id}`), { contextData: void 0 })).status, 200);
932
+ })).filter((r) => r.properties.kind === "object").length, 1);
933
+ actorLookupFails = true;
934
+ const expectedBody = await secondary.toJsonLd({
935
+ format: "compact",
936
+ contextLoader: mockDocumentLoader
937
+ });
938
+ for (const request of [new Request("https://example.com/notes/secondary", { headers: { Accept: ACCEPT } }), gatewayRequest("/notes/secondary")]) assertEquals((await captureWarnings(async () => {
939
+ const response = await federation.fetch(request, { contextData: void 0 });
940
+ assertEquals(response.status, 200);
941
+ assertEquals(await response.json(), expectedBody);
942
+ })).filter((r) => r.properties.kind != null), []);
943
+ assertEquals(actorCalls, 8);
944
+ });
868
945
  test("Federation.fetch() serves compatible-ID actors", async (t) => {
869
946
  const federation = createTestFederation();
870
947
  const actorGateways = {
@@ -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 { f as verifyRequest } from "../http-D-DpP4sd.mjs";
13
+ import { f as verifyRequest } from "../http-DysQjjJe.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-D_BDPwm2.mjs";
16
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-Cw545L_2.mjs";
15
+ import { t as doesActorOwnKey } from "../owner-BP_ouAvi.mjs";
16
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-D0nNms3Q.mjs";
17
17
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
18
18
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
19
19
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
@@ -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-t8xDeJZ5.mjs";
7
- import { o as createFederation } from "../../middleware-C2_LcbL3.mjs";
7
+ import { o as createFederation } from "../../middleware-DoAk0Q3s.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-t8xDeJZ5.mjs";
7
- import { o as createFederation } from "../../middleware-C2_LcbL3.mjs";
8
- import { r as createFederationBuilder } from "../../builder-C5rYBZ16.mjs";
7
+ import { o as createFederation } from "../../middleware-DoAk0Q3s.mjs";
8
+ import { r as createFederationBuilder } from "../../builder-Wx64uCV2.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-Cy-8WCod.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-39Ixir8d.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-C2_LcbL3.mjs";
9
+ import { o as createFederation, s as handleWebFinger } from "../middleware-DoAk0Q3s.mjs";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
11
  import { parseIri } from "@fedify/vocab-runtime";
12
12
  import { createTestMeterProvider, test } from "@fedify/fixture";
@@ -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-Cuk0w0rp.cjs");
4
+ const require_http = require("./http-DC5DyShJ.cjs");
5
5
  const require_types = require("./types-poIao40f.cjs");
6
- const require_portable_collection = require("./portable-collection-DwJ8YJP2.cjs");
7
- const require_kv_cache = require("./kv-cache-BmLTVLXc.cjs");
6
+ const require_portable_collection = require("./portable-collection-Cz9K1Tas.cjs");
7
+ const require_kv_cache = require("./kv-cache-CsNDrjVT.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");
@@ -1457,7 +1457,7 @@ const textEncoder = new TextEncoder();
1457
1457
  function isPortableIdValue(value) {
1458
1458
  return typeof value === "string" && require_http.isPortableId(value);
1459
1459
  }
1460
- function isJsonMap(value) {
1460
+ function isJsonMap$1(value) {
1461
1461
  return typeof value === "object" && value != null && !Array.isArray(value);
1462
1462
  }
1463
1463
  function isCompoundProofJsonObject(value) {
@@ -1523,7 +1523,7 @@ function addBytes(statistics, bytes, limits, path) {
1523
1523
  if (statistics.byteLength > limits.maxBytes) return limitExceeded("bytes", limits.maxBytes, statistics.byteLength, path);
1524
1524
  }
1525
1525
  function inspectJsonTree(root, limits) {
1526
- if (!isJsonMap(root)) return {
1526
+ if (!isJsonMap$1(root)) return {
1527
1527
  status: "unsupported",
1528
1528
  reason: {
1529
1529
  type: "invalidJsonTree",
@@ -1586,7 +1586,7 @@ function inspectJsonTree(root, limits) {
1586
1586
  insideProof: current.insideProof,
1587
1587
  insideContext: current.insideContext
1588
1588
  });
1589
- } else if (isJsonMap(value)) {
1589
+ } else if (isJsonMap$1(value)) {
1590
1590
  const prototype = Object.getPrototypeOf(value);
1591
1591
  if (seen.has(value) || !hasOnlyDataProperties(value) || prototype !== Object.prototype && prototype !== null) return {
1592
1592
  status: "unsupported",
@@ -1607,7 +1607,7 @@ function inspectJsonTree(root, limits) {
1607
1607
  if (hasDirectProof) {
1608
1608
  statistics.proofCount++;
1609
1609
  if (statistics.proofCount > limits.maxProofs) return limitExceeded("proofs", limits.maxProofs, statistics.proofCount, childPath(current.path, "proof"));
1610
- if (!isJsonMap(value.proof)) return {
1610
+ if (!isJsonMap$1(value.proof)) return {
1611
1611
  status: "unsupported",
1612
1612
  reason: {
1613
1613
  type: "unsupportedProofShape",
@@ -1782,7 +1782,7 @@ function inspectCompoundPortableObjectApplicability(json, limits) {
1782
1782
  iterator: value.values(),
1783
1783
  depth: frame.depth + 1
1784
1784
  });
1785
- } else if (isJsonMap(value)) {
1785
+ } else if (isJsonMap$1(value)) {
1786
1786
  mapCount++;
1787
1787
  if (mapCount > limits.maxMaps) return "indeterminate";
1788
1788
  if ([value.id, value["@id"]].some(isPortableIdValue)) return "present";
@@ -1878,7 +1878,7 @@ function containsCompoundPortableObject(json) {
1878
1878
  * @internal
1879
1879
  */
1880
1880
  function findUnsupportedCompoundProofShape(json) {
1881
- return findInJsonMaps(json, (map, path) => Object.hasOwn(map, "proof") && !isJsonMap(map.proof) ? childPath(path, "proof") : void 0) ?? null;
1881
+ return findInJsonMaps(json, (map, path) => Object.hasOwn(map, "proof") && !isJsonMap$1(map.proof) ? childPath(path, "proof") : void 0) ?? null;
1882
1882
  }
1883
1883
  /**
1884
1884
  * Discovers direct literal proof-bearing maps in an immutable JSON snapshot.
@@ -2080,13 +2080,13 @@ async function isEmbeddedPortableKey(parent, member, key, parentId) {
2080
2080
  }
2081
2081
  if (expanded.length !== 1 || hasGraphKeyword(expanded)) return false;
2082
2082
  const node = expanded[0];
2083
- if (!isJsonMap(node) || typeof node["@id"] !== "string" || getCanonicalPortableIdOf(node["@id"]) !== parentPortableId) return false;
2083
+ if (!isJsonMap$1(node) || typeof node["@id"] !== "string" || getCanonicalPortableIdOf(node["@id"]) !== parentPortableId) return false;
2084
2084
  const properties = Object.keys(node).filter((property) => property !== "@id" && property !== "@type");
2085
2085
  if (properties.length !== 1 || properties[0] !== `${SECURITY}${member}`) return false;
2086
2086
  const values = node[properties[0]];
2087
2087
  if (!Array.isArray(values) || values.length !== 1) return false;
2088
2088
  const expandedKey = values[0];
2089
- if (!isJsonMap(expandedKey) || expandedKey["@id"] !== literalIds[0] || Object.keys(expandedKey).some((property) => !KEY_PROPERTIES.has(property))) return false;
2089
+ if (!isJsonMap$1(expandedKey) || expandedKey["@id"] !== literalIds[0] || Object.keys(expandedKey).some((property) => !KEY_PROPERTIES.has(property))) return false;
2090
2090
  const types = expandedKey["@type"];
2091
2091
  if (types !== void 0 && (!Array.isArray(types) || types.some((type) => typeof type !== "string" || !KEY_TYPES.has(type)))) return false;
2092
2092
  const pem = expandedKey[`${SECURITY}publicKeyPem`];
@@ -2097,7 +2097,7 @@ async function isEmbeddedPortableKey(parent, member, key, parentId) {
2097
2097
  for (const property of [`${SECURITY}owner`, `${SECURITY}controller`]) {
2098
2098
  const owners = expandedKey[property];
2099
2099
  if (owners === void 0) continue;
2100
- if (!Array.isArray(owners) || owners.length !== 1 || !isJsonMap(owners[0]) || Object.keys(owners[0]).length !== 1 || typeof owners[0]["@id"] !== "string" || getCanonicalPortableIdOf(owners[0]["@id"]) !== parentPortableId) return false;
2100
+ if (!Array.isArray(owners) || owners.length !== 1 || !isJsonMap$1(owners[0]) || Object.keys(owners[0]).length !== 1 || typeof owners[0]["@id"] !== "string" || getCanonicalPortableIdOf(owners[0]["@id"]) !== parentPortableId) return false;
2101
2101
  }
2102
2102
  return true;
2103
2103
  }
@@ -2140,7 +2140,7 @@ function isSimpleContext(context) {
2140
2140
  }
2141
2141
  function hasGraphKeyword(value) {
2142
2142
  if (Array.isArray(value)) return value.some(hasGraphKeyword);
2143
- if (!isJsonMap(value)) return false;
2143
+ if (!isJsonMap$1(value)) return false;
2144
2144
  return Object.entries(value).some(([key, child]) => key === "@graph" || key === "@included" || key === "@reverse" || hasGraphKeyword(child));
2145
2145
  }
2146
2146
  /**
@@ -2150,7 +2150,7 @@ function hasGraphKeyword(value) {
2150
2150
  function isSingleStringValue(values, datatype) {
2151
2151
  if (!Array.isArray(values) || values.length !== 1) return false;
2152
2152
  const value = values[0];
2153
- if (!isJsonMap(value) || typeof value["@value"] !== "string") return false;
2153
+ if (!isJsonMap$1(value) || typeof value["@value"] !== "string") return false;
2154
2154
  const keys = Object.keys(value);
2155
2155
  return keys.length === 1 || datatype != null && keys.length === 2 && value["@type"] === datatype;
2156
2156
  }
@@ -2205,7 +2205,7 @@ async function verifyServedPortableObjects(json, options = {}) {
2205
2205
  const maps = collected.maps;
2206
2206
  const discovered = /* @__PURE__ */ new Map();
2207
2207
  const ids = maps.map((candidate) => candidate.id);
2208
- const rootId = isJsonMap(json) ? [json.id, json["@id"]].find(isPortableIdValue) : void 0;
2208
+ const rootId = isJsonMap$1(json) ? [json.id, json["@id"]].find(isPortableIdValue) : void 0;
2209
2209
  if (rootId != null) ids.push(rootId);
2210
2210
  for (const id of ids) discovered.set(id, (discovered.get(id) ?? 0) + 1);
2211
2211
  let hidden;
@@ -2250,7 +2250,7 @@ function findHiddenPortableObject(expanded, discovered) {
2250
2250
  pending.push(...value);
2251
2251
  continue;
2252
2252
  }
2253
- if (!isJsonMap(value)) continue;
2253
+ if (!isJsonMap$1(value)) continue;
2254
2254
  const id = value["@id"];
2255
2255
  if (isPortableIdValue(id) && Object.keys(value).some((key) => key !== "@id")) {
2256
2256
  const count = remaining.get(id) ?? 0;
@@ -2341,7 +2341,7 @@ function collectServedPortableMaps(json) {
2341
2341
  }
2342
2342
  async function checkServedPortableMap({ map, contexts }, options) {
2343
2343
  if (Object.hasOwn(map, "proof")) {
2344
- if (!isJsonMap(map.proof)) return "unsupportedProofShape";
2344
+ if (!isJsonMap$1(map.proof)) return "unsupportedProofShape";
2345
2345
  if (!Object.hasOwn(map, "@context")) return "missingContext";
2346
2346
  try {
2347
2347
  const policy = await require_http.verifyPortableObjectProofPolicy(map, await require_http.verifyMapLocalProof(map, options), options);
@@ -2362,11 +2362,185 @@ async function checkServedPortableMap({ map, contexts }, options) {
2362
2362
  } catch {
2363
2363
  return "unsupportedContext";
2364
2364
  }
2365
- if (expanded.length !== 1 || !isJsonMap(expanded[0])) return "invalidPortableObject";
2365
+ if (expanded.length !== 1 || !isJsonMap$1(expanded[0])) return "invalidPortableObject";
2366
2366
  const type = require_http.classifyFep2277CoreType(expanded[0]);
2367
2367
  return UNSECURED_CORE_TYPES.has(type) ? null : "missingProof";
2368
2368
  }
2369
2369
  //#endregion
2370
+ //#region src/federation/compatible-id-warning.ts
2371
+ /** Returns a verified first gateway, without dereferencing the actor. */
2372
+ async function getLocalFirstGateway(context, ownerId, did) {
2373
+ try {
2374
+ const parsed = context.parseUri(ownerId, { portable: true });
2375
+ if (parsed?.type !== "actor" || parsed.authority !== did) return null;
2376
+ return firstGateway(await context.getActor(parsed.identifier), did);
2377
+ } catch (error) {
2378
+ (0, _logtape_logtape.getLogger)([
2379
+ "fedify",
2380
+ "federation",
2381
+ "object"
2382
+ ]).debug("Could not check the first gateway of {ownerId}: {error}", {
2383
+ ownerId: ownerId.href,
2384
+ error
2385
+ });
2386
+ return null;
2387
+ }
2388
+ }
2389
+ function firstGateway(actor, did) {
2390
+ if (actor?.id == null || require_http.getPortableDid(actor.id) !== did) return null;
2391
+ const gateway = actor.gateway;
2392
+ return gateway != null && (0, _fedify_vocab_runtime.isGatewayUrl)(gateway) ? gateway : null;
2393
+ }
2394
+ /** Looks for an actor already embedded in the document being published. */
2395
+ function getEmbeddedFirstGateway(json, ownerId, did) {
2396
+ const owner = require_http.getCanonicalPortableId(ownerId);
2397
+ if (owner == null) return null;
2398
+ return visitJsonMaps(json, (map) => {
2399
+ const id = parseMapId(map, isJsonMap(json) ? json : null);
2400
+ if (id == null || require_http.getCanonicalPortableId(id) !== owner) return;
2401
+ const gateways = map.gateways ?? map["https://w3id.org/fep/ef61/gateways"];
2402
+ const first = Array.isArray(gateways) ? gateways[0] : isJsonMap(gateways) && Array.isArray(gateways["@list"]) ? gateways["@list"][0] : null;
2403
+ const address = typeof first === "string" ? first : isJsonMap(first) ? first.id ?? first["@id"] : null;
2404
+ if (typeof address !== "string") return;
2405
+ try {
2406
+ const gateway = new URL(address);
2407
+ if (require_http.getPortableDid(id) === did && (0, _fedify_vocab_runtime.isGatewayUrl)(gateway)) return gateway;
2408
+ } catch (error) {
2409
+ if (!(error instanceof TypeError)) throw error;
2410
+ }
2411
+ }) ?? null;
2412
+ }
2413
+ function isJsonMap(value) {
2414
+ return value != null && typeof value === "object" && !Array.isArray(value);
2415
+ }
2416
+ /** Warns only when the publisher's actual first gateway is known. */
2417
+ function warnCompatibleId(id, did, gateway, kind) {
2418
+ if (!(0, _fedify_vocab_runtime_internal_portable_dereference.isCompatibleEf61Iri)(id) || require_http.getPortableDid(id) !== did || id.origin === gateway.origin) return;
2419
+ (0, _logtape_logtape.getLogger)([
2420
+ "fedify",
2421
+ "federation",
2422
+ kind === "activity" ? "outbox" : "object"
2423
+ ]).warn("The {kind} {id} has an FEP-ef61 compatible identifier on another gateway than its owner's first gateway, {gateway}. FEP-ef61 requires publishers to use the first gateway in the actor's gateways.", {
2424
+ kind,
2425
+ id: id.href,
2426
+ gateway: gateway.origin
2427
+ });
2428
+ }
2429
+ /** Reads the root ID without dereferencing or expanding the document. */
2430
+ function getCompactRootId(json) {
2431
+ return isJsonMap(json) ? parseMapId(json, json) : null;
2432
+ }
2433
+ /** Checks an activity and its embedded objects against each one's owner. */
2434
+ async function warnCompatibleIdsInJson(json, context) {
2435
+ const root = isJsonMap(json) ? json : null;
2436
+ const rootOwners = root == null ? [] : ownerIds(root, "actor", root);
2437
+ const gateways = /* @__PURE__ */ new Map();
2438
+ const maps = [];
2439
+ visitJsonMaps(json, (map, path) => {
2440
+ maps.push({
2441
+ map,
2442
+ path
2443
+ });
2444
+ });
2445
+ for (const { map, path } of maps) {
2446
+ const id = parseMapId(map, root);
2447
+ if (id == null || !(0, _fedify_vocab_runtime_internal_portable_dereference.isCompatibleEf61Iri)(id)) continue;
2448
+ const type = map.type ?? map["@type"];
2449
+ if ((Array.isArray(type) ? type : [type]).some((t) => typeof t === "string" && /(?:^|[#/:])(CryptographicKey|Multikey|JsonWebKey|Key)$/.test(t))) continue;
2450
+ const did = require_http.getPortableDid(id);
2451
+ if (did == null) continue;
2452
+ const owners = path === "" ? rootOwners : [
2453
+ ...ownerIds(map, "actor", root),
2454
+ ...ownerIds(map, "attributedTo", root),
2455
+ ...rootOwners
2456
+ ];
2457
+ for (const ownerId of owners) {
2458
+ if (require_http.getPortableDid(ownerId) !== did) continue;
2459
+ const key = require_http.getCanonicalPortableId(ownerId);
2460
+ if (key == null) continue;
2461
+ let gateway = gateways.get(key);
2462
+ if (gateway === void 0) {
2463
+ gateway = getEmbeddedFirstGateway(json, ownerId, did) ?? (context == null ? null : await getLocalFirstGateway(context, ownerId, did));
2464
+ gateways.set(key, gateway);
2465
+ }
2466
+ if (gateway != null) {
2467
+ warnCompatibleId(id, did, gateway, path === "" ? "activity" : "object");
2468
+ break;
2469
+ }
2470
+ }
2471
+ }
2472
+ }
2473
+ function ownerIds(map, term, root) {
2474
+ const iri = `https://www.w3.org/ns/activitystreams#${term}`;
2475
+ const values = [map[term], map[iri]];
2476
+ for (const context of [map["@context"], root?.["@context"]]) for (const [alias, definition] of localContextEntries(context)) {
2477
+ const mapped = typeof definition === "string" ? definition : isJsonMap(definition) ? definition["@id"] : null;
2478
+ if (mapped === iri || mapped === `as:${term}`) values.push(map[alias]);
2479
+ }
2480
+ return values.flatMap((value) => parseOwnerIds(value, root));
2481
+ }
2482
+ function localContextEntries(context) {
2483
+ if (Array.isArray(context)) return context.flatMap(localContextEntries);
2484
+ return isJsonMap(context) ? Object.entries(context) : [];
2485
+ }
2486
+ function parseOwnerIds(value, root) {
2487
+ if (Array.isArray(value)) return value.flatMap((item) => parseOwnerIds(item, root));
2488
+ if (isJsonMap(value)) {
2489
+ const id = parseMapId(value, root);
2490
+ return id == null ? [] : [id];
2491
+ }
2492
+ if (typeof value !== "string") return [];
2493
+ try {
2494
+ return [(0, _fedify_vocab_runtime.parseIri)(value)];
2495
+ } catch (error) {
2496
+ if (error instanceof TypeError) return [];
2497
+ throw error;
2498
+ }
2499
+ }
2500
+ function parseMapId(map, root) {
2501
+ let raw = map["@id"] ?? map.id;
2502
+ for (const context of [map["@context"], root?.["@context"]]) for (const [alias, definition] of localContextEntries(context)) if ((typeof definition === "string" ? definition : isJsonMap(definition) ? definition["@id"] : null) === "@id" && typeof map[alias] === "string") {
2503
+ raw = map[alias];
2504
+ break;
2505
+ }
2506
+ if (typeof raw !== "string") return null;
2507
+ try {
2508
+ return (0, _fedify_vocab_runtime.parseIri)(raw);
2509
+ } catch (error) {
2510
+ if (error instanceof TypeError) return null;
2511
+ throw error;
2512
+ }
2513
+ }
2514
+ function visitJsonMaps(json, visit) {
2515
+ const seen = /* @__PURE__ */ new Set();
2516
+ const pending = [{
2517
+ value: json,
2518
+ path: ""
2519
+ }];
2520
+ while (pending.length > 0) {
2521
+ const { value, path } = pending.pop();
2522
+ if (value == null || typeof value !== "object" || seen.has(value)) continue;
2523
+ seen.add(value);
2524
+ if (Array.isArray(value)) {
2525
+ for (let i = value.length - 1; i >= 0; i--) pending.push({
2526
+ value: value[i],
2527
+ path: `${path}/${i}`
2528
+ });
2529
+ continue;
2530
+ }
2531
+ const map = value;
2532
+ const result = visit(map, path);
2533
+ if (result !== void 0) return result;
2534
+ for (const key of Object.keys(map).reverse()) {
2535
+ if (key === "proof" || key === "@context") continue;
2536
+ pending.push({
2537
+ value: map[key],
2538
+ path: `${path}/${key}`
2539
+ });
2540
+ }
2541
+ }
2542
+ }
2543
+ //#endregion
2370
2544
  //#region src/federation/audience.ts
2371
2545
  const AS_NAMESPACE = "https://www.w3.org/ns/activitystreams#";
2372
2546
  /**
@@ -4161,6 +4335,7 @@ async function handleObject(request, { values, context, objectDispatcher, author
4161
4335
  Vary: "Accept"
4162
4336
  }
4163
4337
  });
4338
+ await warnServedCompatibleObject(context, object, getCompactRootId(jsonLd));
4164
4339
  return new Response(JSON.stringify(jsonLd), { headers: {
4165
4340
  "Content-Type": "application/activity+json",
4166
4341
  Vary: "Accept"
@@ -4264,6 +4439,7 @@ async function handlePortableObject(request, { values, context, objectDispatcher
4264
4439
  return portableObjectInternalServerError(request);
4265
4440
  }
4266
4441
  }
4442
+ if (!tombstone) await warnServedCompatibleObject(context, object, servedId);
4267
4443
  const headers = new Headers({
4268
4444
  "Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
4269
4445
  Vary: "Accept"
@@ -4274,6 +4450,32 @@ async function handlePortableObject(request, { values, context, objectDispatcher
4274
4450
  headers
4275
4451
  });
4276
4452
  }
4453
+ async function warnServedCompatibleObject(context, object, serializedId) {
4454
+ try {
4455
+ if ((0, _fedify_vocab.isActor)(object)) return;
4456
+ const id = typeof serializedId === "string" ? (0, _fedify_vocab_runtime.parseIri)(serializedId) : serializedId ?? object.id;
4457
+ if (id == null || !(0, _fedify_vocab_runtime_internal_portable_dereference.isCompatibleEf61Iri)(id)) return;
4458
+ const did = require_http.getPortableDid(id);
4459
+ if (did == null) return;
4460
+ const ownerIds = object instanceof _fedify_vocab.Activity ? object.actorIds : object.attributionIds;
4461
+ for (const ownerId of ownerIds) {
4462
+ if (require_http.getPortableDid(ownerId) !== did) continue;
4463
+ const gateway = await getLocalFirstGateway(context, ownerId, did);
4464
+ if (gateway == null) continue;
4465
+ warnCompatibleId(id, did, gateway, object instanceof _fedify_vocab.Activity ? "activity" : "object");
4466
+ break;
4467
+ }
4468
+ } catch (error) {
4469
+ (0, _logtape_logtape.getLogger)([
4470
+ "fedify",
4471
+ "federation",
4472
+ "object"
4473
+ ]).debug("Could not check the first gateway of {objectId}: {error}", {
4474
+ objectId: object.id?.href,
4475
+ error
4476
+ });
4477
+ }
4478
+ }
4277
4479
  function isRequestedPortableObject(object, canonicalId) {
4278
4480
  return object.id != null && require_http.getCanonicalPortableId(object.id) === canonicalId;
4279
4481
  }
@@ -4377,13 +4579,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
4377
4579
  * @param parameters The parameters for handling the collection.
4378
4580
  * @returns A promise that resolves to an HTTP response.
4379
4581
  */
4380
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
4381
- const spanName = name$2.trim().replace(/\s+/g, "_");
4582
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
4583
+ const spanName = name$1.trim().replace(/\s+/g, "_");
4382
4584
  tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
4383
4585
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
4384
4586
  const cursor = new URL(request.url).searchParams.get("cursor");
4385
4587
  const metricBase = {
4386
- kind: getCollectionMetricKind(name$2),
4588
+ kind: getCollectionMetricKind(name$1),
4387
4589
  page: cursor != null,
4388
4590
  dispatcher: "built_in"
4389
4591
  };
@@ -4429,7 +4631,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4429
4631
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
4430
4632
  return await onNotFound(request);
4431
4633
  }
4432
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
4634
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
4433
4635
  itemCount = items.length;
4434
4636
  span.setAttribute("fedify.collection.items", itemCount);
4435
4637
  return items;
@@ -4471,7 +4673,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4471
4673
  } else {
4472
4674
  const uri = new URL(portable == null ? baseUri : pageBase);
4473
4675
  uri.searchParams.set("cursor", cursor);
4474
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
4676
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
4475
4677
  kind: _opentelemetry_api.SpanKind.SERVER,
4476
4678
  attributes: {
4477
4679
  "activitypub.collection.id": uri.href,
@@ -4487,7 +4689,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4487
4689
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
4488
4690
  return await onNotFound(request);
4489
4691
  }
4490
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
4692
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
4491
4693
  itemCount = items.length;
4492
4694
  span.setAttribute("fedify.collection.items", itemCount);
4493
4695
  return {
@@ -5678,8 +5880,8 @@ var CustomCollectionHandler = class {
5678
5880
  * @param CollectionPage The CollectionPage constructor.
5679
5881
  * @param filterPredicate Optional filter predicate for items.
5680
5882
  */
5681
- constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
5682
- this.name = name$1;
5883
+ constructor(name$2, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
5884
+ this.name = name$2;
5683
5885
  this.values = values;
5684
5886
  this.context = context;
5685
5887
  this.callbacks = callbacks;
@@ -10603,17 +10805,26 @@ var ContextImpl = class ContextImpl {
10603
10805
  activityId: activity.id?.href,
10604
10806
  activity
10605
10807
  });
10808
+ const activityJsonLd = await activity.toJsonLd({
10809
+ format: "compact",
10810
+ contextLoader: this.contextLoader
10811
+ });
10812
+ try {
10813
+ await warnCompatibleIdsInJson(activityJsonLd, this instanceof RequestContextImpl && !this.isInActorDispatcher() ? this : void 0);
10814
+ } catch (error) {
10815
+ logger.debug("Could not check the first gateway of activity {activityId}: {error}", {
10816
+ activityId: activity.id?.href,
10817
+ error
10818
+ });
10819
+ }
10606
10820
  if (this.federation.fanoutQueue == null || options.immediate || options.fanout === "skip" || (options.fanout ?? "auto") === "auto" && globalThis.Object.keys(inboxes).length < FANOUT_THRESHOLD) {
10607
10821
  await this.federation.sendActivity(keys, inboxes, activity, {
10608
10822
  ...opts,
10823
+ activityJsonLd,
10609
10824
  normalizeExistingProofs: proofCreated || options.normalizeExistingProofs
10610
10825
  });
10611
10826
  return true;
10612
10827
  }
10613
- const activityJsonLd = await activity.toJsonLd({
10614
- format: "compact",
10615
- contextLoader: this.contextLoader
10616
- });
10617
10828
  assertSupportedCompoundProofShape(activityJsonLd, activity.id?.href);
10618
10829
  const keyJwkPairs = await Promise.all(keys.map(async ({ keyId, privateKey }) => ({
10619
10830
  keyId: keyId.href,
@@ -10796,6 +11007,10 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
10796
11007
  #invokedFromActorDispatcher;
10797
11008
  #invokedFromObjectDispatcher;
10798
11009
  request;
11010
+ /** Whether a diagnostic actor lookup would re-enter the actor dispatcher. */
11011
+ isInActorDispatcher() {
11012
+ return this.#invokedFromActorDispatcher != null;
11013
+ }
10799
11014
  url = void 0;
10800
11015
  portableRequest;
10801
11016
  constructor(options) {
@@ -14,7 +14,7 @@ import serialize from "json-canon";
14
14
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
15
15
  //#region deno.json
16
16
  var name = "@fedify/fedify";
17
- var version = "2.4.0-dev.2219+eb70f060";
17
+ var version = "2.4.0-dev.2228+6d017d55";
18
18
  //#endregion
19
19
  //#region src/sig/portable-key-id.ts
20
20
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -16,7 +16,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
16
16
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
17
17
  //#region deno.json
18
18
  var name = "@fedify/fedify";
19
- var version = "2.4.0-dev.2219+eb70f060";
19
+ var version = "2.4.0-dev.2228+6d017d55";
20
20
  //#endregion
21
21
  //#region src/sig/portable-key-id.ts
22
22
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -2,12 +2,12 @@ import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { m as parseKeyIdString } from "./portable-key-id-Ca30-hOa.mjs";
5
- import { n as version, t as name } from "./deno-Dj1TFhf4.mjs";
6
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-BnYleVpg.mjs";
5
+ import { n as version, t as name } from "./deno-9flirCWb.mjs";
6
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-X1lpKWRK.mjs";
7
7
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPVqki78.mjs";
8
8
  import { a as promoteKeyOwnerEvidence } from "./key-owner-evidence-APB6NpSk.mjs";
9
- import { a as fetchKeyDetailed, c as validateCryptoKey, t as bypassKeyCacheReads } from "./key-Djyt1BFx.mjs";
10
- import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-B880yVlC.mjs";
9
+ import { a as fetchKeyDetailed, c as validateCryptoKey, t as bypassKeyCacheReads } from "./key-TtgqklZ1.mjs";
10
+ import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-DYr6MZUl.mjs";
11
11
  import { getLogger } from "@logtape/logtape";
12
12
  import { CryptographicKey } from "@fedify/vocab";
13
13
  import { SpanStatusCode, trace } from "@opentelemetry/api";