@fedify/fedify 2.4.0-dev.2212 → 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 (92) hide show
  1. package/dist/{builder-CKAVFvcH.mjs → builder-Wx64uCV2.mjs} +10 -2
  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-BP5SOdXv.mjs → compound-proof-B647GUyL.mjs} +2 -2
  6. package/dist/{context-Bl_Y7T_3.d.cts → context-B6X-t8gI.d.cts} +1 -1
  7. package/dist/{context-DMd11IAu.d.ts → context-CtI307_A.d.ts} +1 -1
  8. package/dist/{deno-BKp9fYB3.mjs → deno-9flirCWb.mjs} +1 -1
  9. package/dist/{docloader-BjJreYQU.mjs → docloader-pjAbs1lc.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +37 -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 +5 -5
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/metrics.test.mjs +1 -1
  17. package/dist/federation/middleware.test.mjs +123 -7
  18. package/dist/federation/mod.cjs +1 -1
  19. package/dist/federation/mod.d.cts +3 -3
  20. package/dist/federation/mod.d.ts +3 -3
  21. package/dist/federation/mod.js +1 -1
  22. package/dist/federation/outgoing-proof.test.mjs +248 -6
  23. package/dist/federation/portable-actor.test.mjs +3 -3
  24. package/dist/federation/portable-chain.test.mjs +3 -3
  25. package/dist/federation/portable-collection.test.mjs +4 -4
  26. package/dist/federation/portable-delivery.test.mjs +2 -2
  27. package/dist/federation/portable-gateway-keys.test.mjs +4 -4
  28. package/dist/federation/portable-inbox.test.mjs +4 -4
  29. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  30. package/dist/federation/portable-route-activity.test.mjs +2 -2
  31. package/dist/federation/portable.test.mjs +80 -3
  32. package/dist/federation/send.test.mjs +3 -3
  33. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  34. package/dist/federation/tasks/tasks.test.mjs +2 -2
  35. package/dist/federation/temporal.test.mjs +1 -1
  36. package/dist/federation/webfinger.test.mjs +1 -1
  37. package/dist/{federation-B-0EXZPH.cjs → federation-DiycMKQx.cjs} +262 -36
  38. package/dist/{http-Di8yiDMY.js → http-CHqz_Jp6.js} +1 -1
  39. package/dist/{http-ByBlnY3D.cjs → http-DC5DyShJ.cjs} +1 -1
  40. package/dist/{http--IGUT1mg.mjs → http-DysQjjJe.mjs} +4 -4
  41. package/dist/{key-ChfV9C3v.mjs → key-TtgqklZ1.mjs} +2 -2
  42. package/dist/{kv-BhNbN8Ed.d.cts → kv-B2mzK5tl.d.cts} +3 -2
  43. package/dist/{kv-BhNbN8Ed.d.ts → kv-B2mzK5tl.d.ts} +3 -2
  44. package/dist/{kv-cache-DFIUR-E5.mjs → kv-cache-B7g5Tvl0.mjs} +1 -1
  45. package/dist/{kv-cache-DpwDPt9V.js → kv-cache-CgYK8siN.js} +1 -1
  46. package/dist/{kv-cache-BDwUzxBJ.cjs → kv-cache-CsNDrjVT.cjs} +1 -1
  47. package/dist/{ld-DBBVabBC.mjs → ld-CHYUee-s.mjs} +3 -3
  48. package/dist/{metrics-DPQKFkiJ.mjs → metrics-X1lpKWRK.mjs} +1 -1
  49. package/dist/{middleware-VXGLiHtY.js → middleware-D2scpP9Q.js} +262 -36
  50. package/dist/{middleware-BW7-tHAG.mjs → middleware-DoAk0Q3s.mjs} +251 -33
  51. package/dist/{middleware-G9zpxUsD.mjs → middleware-ndPtgYYI.mjs} +1 -1
  52. package/dist/{mod-C5Mwx5ty.d.ts → mod-B8MI1mpi.d.ts} +1 -1
  53. package/dist/{mod-BVNGHgVr.d.ts → mod-BhTaDR5H.d.ts} +1 -1
  54. package/dist/{mod-Cyy1Wahy.d.cts → mod-C7_2j7VT.d.cts} +1 -1
  55. package/dist/{mod-Dp3Ij7sw.d.cts → mod-DFL6IxYQ.d.cts} +1 -1
  56. package/dist/mod.cjs +4 -4
  57. package/dist/mod.d.cts +4 -4
  58. package/dist/mod.d.ts +4 -4
  59. package/dist/mod.js +4 -4
  60. package/dist/nodeinfo/handler.test.mjs +1 -1
  61. package/dist/otel/mod.d.cts +1 -1
  62. package/dist/otel/mod.d.ts +1 -1
  63. package/dist/{owner-BW5VsXG3.mjs → owner-BP_ouAvi.mjs} +3 -3
  64. package/dist/{portable-collection-FTPa5D91.js → portable-collection-4SnmDcTc.js} +1 -1
  65. package/dist/{portable-collection-CXcAlwei.cjs → portable-collection-Cz9K1Tas.cjs} +1 -1
  66. package/dist/{portable-collection-Ct99JsBe.mjs → portable-collection-SqxusPj7.mjs} +2 -2
  67. package/dist/{portable-key-U3lW06QJ.mjs → portable-key-DYr6MZUl.mjs} +1 -1
  68. package/dist/{proof-MFz2960e.mjs → proof-B-iLzyva.mjs} +4 -4
  69. package/dist/{send-Duj8xys1.mjs → send-D0nNms3Q.mjs} +3 -3
  70. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  71. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  72. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  73. package/dist/sig/compound-proof.test.mjs +2 -2
  74. package/dist/sig/http.test.mjs +2 -2
  75. package/dist/sig/key.test.mjs +1 -1
  76. package/dist/sig/ld.test.mjs +2 -2
  77. package/dist/sig/mod.cjs +2 -2
  78. package/dist/sig/mod.js +2 -2
  79. package/dist/sig/owner.test.mjs +1 -1
  80. package/dist/sig/portable-collection.test.mjs +1 -1
  81. package/dist/sig/portable-key.test.mjs +6 -6
  82. package/dist/sig/proof.test.mjs +5 -3
  83. package/dist/{temporal-CVcPKB1N.mjs → temporal-39Ixir8d.mjs} +1 -1
  84. package/dist/testing/kv.test.mjs +1 -1
  85. package/dist/testing/mod.d.mts +3 -2
  86. package/dist/utils/docloader.test.mjs +2 -2
  87. package/dist/utils/kv-cache.test.mjs +1 -1
  88. package/dist/utils/mod.cjs +1 -1
  89. package/dist/utils/mod.d.cts +1 -1
  90. package/dist/utils/mod.d.ts +1 -1
  91. package/dist/utils/mod.js +1 -1
  92. package/package.json +6 -6
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-ByBlnY3D.cjs");
2
+ const require_http = require("./http-DC5DyShJ.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
5
5
  let es_toolkit = require("es-toolkit");
@@ -1,9 +1,9 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BKp9fYB3.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DPQKFkiJ.mjs";
6
- import { c as validateCryptoKey, i as fetchKey, t as bypassKeyCacheReads } from "./key-ChfV9C3v.mjs";
4
+ import { n as version, t as name } from "./deno-9flirCWb.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-X1lpKWRK.mjs";
6
+ import { c as validateCryptoKey, i as fetchKey, t as bypassKeyCacheReads } from "./key-TtgqklZ1.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
9
9
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BKp9fYB3.mjs";
4
+ import { n as version, t as name } from "./deno-9flirCWb.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts
@@ -2,10 +2,10 @@ import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./rolldown-runtime-BC-Eo5Mj.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-DAtXl_Kc.js";
5
- import { $ as instrumentDocumentLoader, A as hasSignature, At as name, C as InvalidContextReferenceError, Ct as hasPortableActor, Dt as isPortableUri, E as compactJsonLd, Et as isPortableKeyId, F as verifyCompactJsonLd, G as validateCryptoKey, M as isClearlyMalformedContextReference, N as isInvalidUrlTypeError, O as detachSignature, Ot as isSameObjectId, P as signJsonLd, Q as getRemoteHost, R as wrapContextLoaderForJsonLd, S as normalizeOutgoingActivityJsonLd, St as getRawCanonicalPortableId, Tt as isPortableId, W as importJwk, X as getDurationMs, Z as getFederationMetrics, _ as verifyObject, at as recordCollectionTotalItems, b as verifyPortableObjectProofWithRoot, bt as getGatewayKeyBase, c as verifyRequestDetailed, ct as recordInboxActivity, d as InvalidPortableObjectIdError, dt as recordWebFingerHandle, et as isAbortError, f as classifyFep2277CoreType, ft as registerQueueDepthGauge, g as verifyMapLocalProof, h as signObject, i as selectRequestSignature, it as recordCollectionRequest, j as hasSignatureLike, jt as version, k as getNormalizationContextLoader, lt as recordOutboxActivity, m as hasProofLike, mt as formatAcceptSignature, n as listRequestSignatures, nt as recordCollectionDispatchDuration, o as validateMaxSignatures, pt as preloadedOnlyDocumentLoader, q as MAX_PORTABLE_KEY_TTL, r as parseRfc9421SignatureInput, rt as recordCollectionPageItems, s as verifyRequest, st as recordFanoutRecipients, t as doubleKnock, tt as recordCircuitBreakerStateChange, ut as recordOutboxEnqueue, v as verifyPortableObjectProof, vt as getAuthenticationOrigin, w as assertSafeJsonLd, wt as isCompatibleKeyId, xt as getPortableDid, y as verifyPortableObjectProofPolicy, yt as getCanonicalPortableId, z as exportJwk } from "./http-Di8yiDMY.js";
5
+ import { $ as instrumentDocumentLoader, A as hasSignature, At as name, C as InvalidContextReferenceError, Ct as hasPortableActor, Dt as isPortableUri, E as compactJsonLd, Et as isPortableKeyId, F as verifyCompactJsonLd, G as validateCryptoKey, M as isClearlyMalformedContextReference, N as isInvalidUrlTypeError, O as detachSignature, Ot as isSameObjectId, P as signJsonLd, Q as getRemoteHost, R as wrapContextLoaderForJsonLd, S as normalizeOutgoingActivityJsonLd, St as getRawCanonicalPortableId, Tt as isPortableId, W as importJwk, X as getDurationMs, Z as getFederationMetrics, _ as verifyObject, at as recordCollectionTotalItems, b as verifyPortableObjectProofWithRoot, bt as getGatewayKeyBase, c as verifyRequestDetailed, ct as recordInboxActivity, d as InvalidPortableObjectIdError, dt as recordWebFingerHandle, et as isAbortError, f as classifyFep2277CoreType, ft as registerQueueDepthGauge, g as verifyMapLocalProof, h as signObject, i as selectRequestSignature, it as recordCollectionRequest, j as hasSignatureLike, jt as version, k as getNormalizationContextLoader, lt as recordOutboxActivity, m as hasProofLike, mt as formatAcceptSignature, n as listRequestSignatures, nt as recordCollectionDispatchDuration, o as validateMaxSignatures, pt as preloadedOnlyDocumentLoader, q as MAX_PORTABLE_KEY_TTL, r as parseRfc9421SignatureInput, rt as recordCollectionPageItems, s as verifyRequest, st as recordFanoutRecipients, t as doubleKnock, tt as recordCircuitBreakerStateChange, ut as recordOutboxEnqueue, v as verifyPortableObjectProof, vt as getAuthenticationOrigin, w as assertSafeJsonLd, wt as isCompatibleKeyId, xt as getPortableDid, y as verifyPortableObjectProofPolicy, yt as getCanonicalPortableId, z as exportJwk } from "./http-CHqz_Jp6.js";
6
6
  import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-_S2_MeLF.js";
7
- import { n as doesActorOwnKey, r as getKeyOwner, t as verifyPortableObject } from "./portable-collection-FTPa5D91.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DpwDPt9V.js";
7
+ import { n as doesActorOwnKey, r as getKeyOwner, t as verifyPortableObject } from "./portable-collection-4SnmDcTc.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-CgYK8siN.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
11
11
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, PUBLIC_COLLECTION, Tombstone, getTypeId, isActor, lookupObject, traverseCollection } from "@fedify/vocab";
@@ -118,6 +118,7 @@ var FederationBuilderImpl = class {
118
118
  * Symbol registry for unique identification of unnamed symbols.
119
119
  */
120
120
  #symbolRegistry = /* @__PURE__ */ new Map();
121
+ #collectionNames = /* @__PURE__ */ new Map();
121
122
  constructor() {
122
123
  this.router = new Router();
123
124
  this.objectCallbacks = {};
@@ -147,6 +148,8 @@ var FederationBuilderImpl = class {
147
148
  f.objectTypeIds = { ...this.objectTypeIds };
148
149
  f.collectionCallbacks = { ...this.collectionCallbacks };
149
150
  f.collectionTypeIds = { ...this.collectionTypeIds };
151
+ f.#symbolRegistry = new Map(this.#symbolRegistry);
152
+ f.#collectionNames = new Map(this.#collectionNames);
150
153
  f.inboxPath = this.inboxPath;
151
154
  f.outboxPath = this.outboxPath;
152
155
  f.inboxCallbacks = this.inboxCallbacks == null ? void 0 : { ...this.inboxCallbacks };
@@ -678,6 +681,7 @@ var FederationBuilderImpl = class {
678
681
  if (this.collectionCallbacks[name] != null) throw new RouterError(`Collection dispatcher for ${strName} already set.`);
679
682
  assertPath(path);
680
683
  if (Router.variables(path).size < 1) throw new RouterError("Path for collection dispatcher must have at least one variable.");
684
+ this.#collectionNames.set(routeName, name);
681
685
  this.router.add(path, routeName);
682
686
  const callbacks = { dispatcher };
683
687
  this.collectionCallbacks[name] = callbacks;
@@ -719,6 +723,10 @@ var FederationBuilderImpl = class {
719
723
  const routeName = this.#uniqueCollectionId(name);
720
724
  return this.router.build(`collection:${routeName}`, values) ?? this.router.build(`orderedCollection:${routeName}`, values);
721
725
  }
726
+ /** Resolves a custom collection route to its original name. */
727
+ getCollectionName(routeName) {
728
+ return this.#collectionNames.get(routeName) ?? routeName.replace(/^(collection|orderedCollection):/, "");
729
+ }
722
730
  setOutboxPermanentFailureHandler(handler) {
723
731
  this.outboxPermanentFailureHandler = handler;
724
732
  }
@@ -1448,7 +1456,7 @@ const textEncoder = new TextEncoder();
1448
1456
  function isPortableIdValue(value) {
1449
1457
  return typeof value === "string" && isPortableId(value);
1450
1458
  }
1451
- function isJsonMap(value) {
1459
+ function isJsonMap$1(value) {
1452
1460
  return typeof value === "object" && value != null && !Array.isArray(value);
1453
1461
  }
1454
1462
  function isCompoundProofJsonObject(value) {
@@ -1514,7 +1522,7 @@ function addBytes(statistics, bytes, limits, path) {
1514
1522
  if (statistics.byteLength > limits.maxBytes) return limitExceeded("bytes", limits.maxBytes, statistics.byteLength, path);
1515
1523
  }
1516
1524
  function inspectJsonTree(root, limits) {
1517
- if (!isJsonMap(root)) return {
1525
+ if (!isJsonMap$1(root)) return {
1518
1526
  status: "unsupported",
1519
1527
  reason: {
1520
1528
  type: "invalidJsonTree",
@@ -1577,7 +1585,7 @@ function inspectJsonTree(root, limits) {
1577
1585
  insideProof: current.insideProof,
1578
1586
  insideContext: current.insideContext
1579
1587
  });
1580
- } else if (isJsonMap(value)) {
1588
+ } else if (isJsonMap$1(value)) {
1581
1589
  const prototype = Object.getPrototypeOf(value);
1582
1590
  if (seen.has(value) || !hasOnlyDataProperties(value) || prototype !== Object.prototype && prototype !== null) return {
1583
1591
  status: "unsupported",
@@ -1598,7 +1606,7 @@ function inspectJsonTree(root, limits) {
1598
1606
  if (hasDirectProof) {
1599
1607
  statistics.proofCount++;
1600
1608
  if (statistics.proofCount > limits.maxProofs) return limitExceeded("proofs", limits.maxProofs, statistics.proofCount, childPath(current.path, "proof"));
1601
- if (!isJsonMap(value.proof)) return {
1609
+ if (!isJsonMap$1(value.proof)) return {
1602
1610
  status: "unsupported",
1603
1611
  reason: {
1604
1612
  type: "unsupportedProofShape",
@@ -1773,7 +1781,7 @@ function inspectCompoundPortableObjectApplicability(json, limits) {
1773
1781
  iterator: value.values(),
1774
1782
  depth: frame.depth + 1
1775
1783
  });
1776
- } else if (isJsonMap(value)) {
1784
+ } else if (isJsonMap$1(value)) {
1777
1785
  mapCount++;
1778
1786
  if (mapCount > limits.maxMaps) return "indeterminate";
1779
1787
  if ([value.id, value["@id"]].some(isPortableIdValue)) return "present";
@@ -1869,7 +1877,7 @@ function containsCompoundPortableObject(json) {
1869
1877
  * @internal
1870
1878
  */
1871
1879
  function findUnsupportedCompoundProofShape(json) {
1872
- return findInJsonMaps(json, (map, path) => Object.hasOwn(map, "proof") && !isJsonMap(map.proof) ? childPath(path, "proof") : void 0) ?? null;
1880
+ return findInJsonMaps(json, (map, path) => Object.hasOwn(map, "proof") && !isJsonMap$1(map.proof) ? childPath(path, "proof") : void 0) ?? null;
1873
1881
  }
1874
1882
  /**
1875
1883
  * Discovers direct literal proof-bearing maps in an immutable JSON snapshot.
@@ -2071,13 +2079,13 @@ async function isEmbeddedPortableKey(parent, member, key, parentId) {
2071
2079
  }
2072
2080
  if (expanded.length !== 1 || hasGraphKeyword(expanded)) return false;
2073
2081
  const node = expanded[0];
2074
- if (!isJsonMap(node) || typeof node["@id"] !== "string" || getCanonicalPortableIdOf(node["@id"]) !== parentPortableId) return false;
2082
+ if (!isJsonMap$1(node) || typeof node["@id"] !== "string" || getCanonicalPortableIdOf(node["@id"]) !== parentPortableId) return false;
2075
2083
  const properties = Object.keys(node).filter((property) => property !== "@id" && property !== "@type");
2076
2084
  if (properties.length !== 1 || properties[0] !== `${SECURITY}${member}`) return false;
2077
2085
  const values = node[properties[0]];
2078
2086
  if (!Array.isArray(values) || values.length !== 1) return false;
2079
2087
  const expandedKey = values[0];
2080
- if (!isJsonMap(expandedKey) || expandedKey["@id"] !== literalIds[0] || Object.keys(expandedKey).some((property) => !KEY_PROPERTIES.has(property))) return false;
2088
+ if (!isJsonMap$1(expandedKey) || expandedKey["@id"] !== literalIds[0] || Object.keys(expandedKey).some((property) => !KEY_PROPERTIES.has(property))) return false;
2081
2089
  const types = expandedKey["@type"];
2082
2090
  if (types !== void 0 && (!Array.isArray(types) || types.some((type) => typeof type !== "string" || !KEY_TYPES.has(type)))) return false;
2083
2091
  const pem = expandedKey[`${SECURITY}publicKeyPem`];
@@ -2088,7 +2096,7 @@ async function isEmbeddedPortableKey(parent, member, key, parentId) {
2088
2096
  for (const property of [`${SECURITY}owner`, `${SECURITY}controller`]) {
2089
2097
  const owners = expandedKey[property];
2090
2098
  if (owners === void 0) continue;
2091
- 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;
2099
+ 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;
2092
2100
  }
2093
2101
  return true;
2094
2102
  }
@@ -2131,7 +2139,7 @@ function isSimpleContext(context) {
2131
2139
  }
2132
2140
  function hasGraphKeyword(value) {
2133
2141
  if (Array.isArray(value)) return value.some(hasGraphKeyword);
2134
- if (!isJsonMap(value)) return false;
2142
+ if (!isJsonMap$1(value)) return false;
2135
2143
  return Object.entries(value).some(([key, child]) => key === "@graph" || key === "@included" || key === "@reverse" || hasGraphKeyword(child));
2136
2144
  }
2137
2145
  /**
@@ -2141,7 +2149,7 @@ function hasGraphKeyword(value) {
2141
2149
  function isSingleStringValue(values, datatype) {
2142
2150
  if (!Array.isArray(values) || values.length !== 1) return false;
2143
2151
  const value = values[0];
2144
- if (!isJsonMap(value) || typeof value["@value"] !== "string") return false;
2152
+ if (!isJsonMap$1(value) || typeof value["@value"] !== "string") return false;
2145
2153
  const keys = Object.keys(value);
2146
2154
  return keys.length === 1 || datatype != null && keys.length === 2 && value["@type"] === datatype;
2147
2155
  }
@@ -2196,7 +2204,7 @@ async function verifyServedPortableObjects(json, options = {}) {
2196
2204
  const maps = collected.maps;
2197
2205
  const discovered = /* @__PURE__ */ new Map();
2198
2206
  const ids = maps.map((candidate) => candidate.id);
2199
- const rootId = isJsonMap(json) ? [json.id, json["@id"]].find(isPortableIdValue) : void 0;
2207
+ const rootId = isJsonMap$1(json) ? [json.id, json["@id"]].find(isPortableIdValue) : void 0;
2200
2208
  if (rootId != null) ids.push(rootId);
2201
2209
  for (const id of ids) discovered.set(id, (discovered.get(id) ?? 0) + 1);
2202
2210
  let hidden;
@@ -2241,7 +2249,7 @@ function findHiddenPortableObject(expanded, discovered) {
2241
2249
  pending.push(...value);
2242
2250
  continue;
2243
2251
  }
2244
- if (!isJsonMap(value)) continue;
2252
+ if (!isJsonMap$1(value)) continue;
2245
2253
  const id = value["@id"];
2246
2254
  if (isPortableIdValue(id) && Object.keys(value).some((key) => key !== "@id")) {
2247
2255
  const count = remaining.get(id) ?? 0;
@@ -2332,7 +2340,7 @@ function collectServedPortableMaps(json) {
2332
2340
  }
2333
2341
  async function checkServedPortableMap({ map, contexts }, options) {
2334
2342
  if (Object.hasOwn(map, "proof")) {
2335
- if (!isJsonMap(map.proof)) return "unsupportedProofShape";
2343
+ if (!isJsonMap$1(map.proof)) return "unsupportedProofShape";
2336
2344
  if (!Object.hasOwn(map, "@context")) return "missingContext";
2337
2345
  try {
2338
2346
  const policy = await verifyPortableObjectProofPolicy(map, await verifyMapLocalProof(map, options), options);
@@ -2353,11 +2361,185 @@ async function checkServedPortableMap({ map, contexts }, options) {
2353
2361
  } catch {
2354
2362
  return "unsupportedContext";
2355
2363
  }
2356
- if (expanded.length !== 1 || !isJsonMap(expanded[0])) return "invalidPortableObject";
2364
+ if (expanded.length !== 1 || !isJsonMap$1(expanded[0])) return "invalidPortableObject";
2357
2365
  const type = classifyFep2277CoreType(expanded[0]);
2358
2366
  return UNSECURED_CORE_TYPES.has(type) ? null : "missingProof";
2359
2367
  }
2360
2368
  //#endregion
2369
+ //#region src/federation/compatible-id-warning.ts
2370
+ /** Returns a verified first gateway, without dereferencing the actor. */
2371
+ async function getLocalFirstGateway(context, ownerId, did) {
2372
+ try {
2373
+ const parsed = context.parseUri(ownerId, { portable: true });
2374
+ if (parsed?.type !== "actor" || parsed.authority !== did) return null;
2375
+ return firstGateway(await context.getActor(parsed.identifier), did);
2376
+ } catch (error) {
2377
+ getLogger([
2378
+ "fedify",
2379
+ "federation",
2380
+ "object"
2381
+ ]).debug("Could not check the first gateway of {ownerId}: {error}", {
2382
+ ownerId: ownerId.href,
2383
+ error
2384
+ });
2385
+ return null;
2386
+ }
2387
+ }
2388
+ function firstGateway(actor, did) {
2389
+ if (actor?.id == null || getPortableDid(actor.id) !== did) return null;
2390
+ const gateway = actor.gateway;
2391
+ return gateway != null && isGatewayUrl(gateway) ? gateway : null;
2392
+ }
2393
+ /** Looks for an actor already embedded in the document being published. */
2394
+ function getEmbeddedFirstGateway(json, ownerId, did) {
2395
+ const owner = getCanonicalPortableId(ownerId);
2396
+ if (owner == null) return null;
2397
+ return visitJsonMaps(json, (map) => {
2398
+ const id = parseMapId(map, isJsonMap(json) ? json : null);
2399
+ if (id == null || getCanonicalPortableId(id) !== owner) return;
2400
+ const gateways = map.gateways ?? map["https://w3id.org/fep/ef61/gateways"];
2401
+ const first = Array.isArray(gateways) ? gateways[0] : isJsonMap(gateways) && Array.isArray(gateways["@list"]) ? gateways["@list"][0] : null;
2402
+ const address = typeof first === "string" ? first : isJsonMap(first) ? first.id ?? first["@id"] : null;
2403
+ if (typeof address !== "string") return;
2404
+ try {
2405
+ const gateway = new URL(address);
2406
+ if (getPortableDid(id) === did && isGatewayUrl(gateway)) return gateway;
2407
+ } catch (error) {
2408
+ if (!(error instanceof TypeError)) throw error;
2409
+ }
2410
+ }) ?? null;
2411
+ }
2412
+ function isJsonMap(value) {
2413
+ return value != null && typeof value === "object" && !Array.isArray(value);
2414
+ }
2415
+ /** Warns only when the publisher's actual first gateway is known. */
2416
+ function warnCompatibleId(id, did, gateway, kind) {
2417
+ if (!isCompatibleEf61Iri(id) || getPortableDid(id) !== did || id.origin === gateway.origin) return;
2418
+ getLogger([
2419
+ "fedify",
2420
+ "federation",
2421
+ kind === "activity" ? "outbox" : "object"
2422
+ ]).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.", {
2423
+ kind,
2424
+ id: id.href,
2425
+ gateway: gateway.origin
2426
+ });
2427
+ }
2428
+ /** Reads the root ID without dereferencing or expanding the document. */
2429
+ function getCompactRootId(json) {
2430
+ return isJsonMap(json) ? parseMapId(json, json) : null;
2431
+ }
2432
+ /** Checks an activity and its embedded objects against each one's owner. */
2433
+ async function warnCompatibleIdsInJson(json, context) {
2434
+ const root = isJsonMap(json) ? json : null;
2435
+ const rootOwners = root == null ? [] : ownerIds(root, "actor", root);
2436
+ const gateways = /* @__PURE__ */ new Map();
2437
+ const maps = [];
2438
+ visitJsonMaps(json, (map, path) => {
2439
+ maps.push({
2440
+ map,
2441
+ path
2442
+ });
2443
+ });
2444
+ for (const { map, path } of maps) {
2445
+ const id = parseMapId(map, root);
2446
+ if (id == null || !isCompatibleEf61Iri(id)) continue;
2447
+ const type = map.type ?? map["@type"];
2448
+ if ((Array.isArray(type) ? type : [type]).some((t) => typeof t === "string" && /(?:^|[#/:])(CryptographicKey|Multikey|JsonWebKey|Key)$/.test(t))) continue;
2449
+ const did = getPortableDid(id);
2450
+ if (did == null) continue;
2451
+ const owners = path === "" ? rootOwners : [
2452
+ ...ownerIds(map, "actor", root),
2453
+ ...ownerIds(map, "attributedTo", root),
2454
+ ...rootOwners
2455
+ ];
2456
+ for (const ownerId of owners) {
2457
+ if (getPortableDid(ownerId) !== did) continue;
2458
+ const key = getCanonicalPortableId(ownerId);
2459
+ if (key == null) continue;
2460
+ let gateway = gateways.get(key);
2461
+ if (gateway === void 0) {
2462
+ gateway = getEmbeddedFirstGateway(json, ownerId, did) ?? (context == null ? null : await getLocalFirstGateway(context, ownerId, did));
2463
+ gateways.set(key, gateway);
2464
+ }
2465
+ if (gateway != null) {
2466
+ warnCompatibleId(id, did, gateway, path === "" ? "activity" : "object");
2467
+ break;
2468
+ }
2469
+ }
2470
+ }
2471
+ }
2472
+ function ownerIds(map, term, root) {
2473
+ const iri = `https://www.w3.org/ns/activitystreams#${term}`;
2474
+ const values = [map[term], map[iri]];
2475
+ for (const context of [map["@context"], root?.["@context"]]) for (const [alias, definition] of localContextEntries(context)) {
2476
+ const mapped = typeof definition === "string" ? definition : isJsonMap(definition) ? definition["@id"] : null;
2477
+ if (mapped === iri || mapped === `as:${term}`) values.push(map[alias]);
2478
+ }
2479
+ return values.flatMap((value) => parseOwnerIds(value, root));
2480
+ }
2481
+ function localContextEntries(context) {
2482
+ if (Array.isArray(context)) return context.flatMap(localContextEntries);
2483
+ return isJsonMap(context) ? Object.entries(context) : [];
2484
+ }
2485
+ function parseOwnerIds(value, root) {
2486
+ if (Array.isArray(value)) return value.flatMap((item) => parseOwnerIds(item, root));
2487
+ if (isJsonMap(value)) {
2488
+ const id = parseMapId(value, root);
2489
+ return id == null ? [] : [id];
2490
+ }
2491
+ if (typeof value !== "string") return [];
2492
+ try {
2493
+ return [parseIri(value)];
2494
+ } catch (error) {
2495
+ if (error instanceof TypeError) return [];
2496
+ throw error;
2497
+ }
2498
+ }
2499
+ function parseMapId(map, root) {
2500
+ let raw = map["@id"] ?? map.id;
2501
+ 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") {
2502
+ raw = map[alias];
2503
+ break;
2504
+ }
2505
+ if (typeof raw !== "string") return null;
2506
+ try {
2507
+ return parseIri(raw);
2508
+ } catch (error) {
2509
+ if (error instanceof TypeError) return null;
2510
+ throw error;
2511
+ }
2512
+ }
2513
+ function visitJsonMaps(json, visit) {
2514
+ const seen = /* @__PURE__ */ new Set();
2515
+ const pending = [{
2516
+ value: json,
2517
+ path: ""
2518
+ }];
2519
+ while (pending.length > 0) {
2520
+ const { value, path } = pending.pop();
2521
+ if (value == null || typeof value !== "object" || seen.has(value)) continue;
2522
+ seen.add(value);
2523
+ if (Array.isArray(value)) {
2524
+ for (let i = value.length - 1; i >= 0; i--) pending.push({
2525
+ value: value[i],
2526
+ path: `${path}/${i}`
2527
+ });
2528
+ continue;
2529
+ }
2530
+ const map = value;
2531
+ const result = visit(map, path);
2532
+ if (result !== void 0) return result;
2533
+ for (const key of Object.keys(map).reverse()) {
2534
+ if (key === "proof" || key === "@context") continue;
2535
+ pending.push({
2536
+ value: map[key],
2537
+ path: `${path}/${key}`
2538
+ });
2539
+ }
2540
+ }
2541
+ }
2542
+ //#endregion
2361
2543
  //#region src/federation/audience.ts
2362
2544
  const AS_NAMESPACE = "https://www.w3.org/ns/activitystreams#";
2363
2545
  /**
@@ -4147,6 +4329,7 @@ async function handleObject(request, { values, context, objectDispatcher, author
4147
4329
  Vary: "Accept"
4148
4330
  }
4149
4331
  });
4332
+ await warnServedCompatibleObject(context, object, getCompactRootId(jsonLd));
4150
4333
  return new Response(JSON.stringify(jsonLd), { headers: {
4151
4334
  "Content-Type": "application/activity+json",
4152
4335
  Vary: "Accept"
@@ -4250,6 +4433,7 @@ async function handlePortableObject(request, { values, context, objectDispatcher
4250
4433
  return portableObjectInternalServerError(request);
4251
4434
  }
4252
4435
  }
4436
+ if (!tombstone) await warnServedCompatibleObject(context, object, servedId);
4253
4437
  const headers = new Headers({
4254
4438
  "Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
4255
4439
  Vary: "Accept"
@@ -4260,6 +4444,32 @@ async function handlePortableObject(request, { values, context, objectDispatcher
4260
4444
  headers
4261
4445
  });
4262
4446
  }
4447
+ async function warnServedCompatibleObject(context, object, serializedId) {
4448
+ try {
4449
+ if (isActor(object)) return;
4450
+ const id = typeof serializedId === "string" ? parseIri(serializedId) : serializedId ?? object.id;
4451
+ if (id == null || !isCompatibleEf61Iri(id)) return;
4452
+ const did = getPortableDid(id);
4453
+ if (did == null) return;
4454
+ const ownerIds = object instanceof Activity ? object.actorIds : object.attributionIds;
4455
+ for (const ownerId of ownerIds) {
4456
+ if (getPortableDid(ownerId) !== did) continue;
4457
+ const gateway = await getLocalFirstGateway(context, ownerId, did);
4458
+ if (gateway == null) continue;
4459
+ warnCompatibleId(id, did, gateway, object instanceof Activity ? "activity" : "object");
4460
+ break;
4461
+ }
4462
+ } catch (error) {
4463
+ getLogger([
4464
+ "fedify",
4465
+ "federation",
4466
+ "object"
4467
+ ]).debug("Could not check the first gateway of {objectId}: {error}", {
4468
+ objectId: object.id?.href,
4469
+ error
4470
+ });
4471
+ }
4472
+ }
4263
4473
  function isRequestedPortableObject(object, canonicalId) {
4264
4474
  return object.id != null && getCanonicalPortableId(object.id) === canonicalId;
4265
4475
  }
@@ -4363,13 +4573,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
4363
4573
  * @param parameters The parameters for handling the collection.
4364
4574
  * @returns A promise that resolves to an HTTP response.
4365
4575
  */
4366
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
4367
- const spanName = name$2.trim().replace(/\s+/g, "_");
4576
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
4577
+ const spanName = name$1.trim().replace(/\s+/g, "_");
4368
4578
  tracerProvider = tracerProvider ?? trace.getTracerProvider();
4369
4579
  const tracer = tracerProvider.getTracer(name, version);
4370
4580
  const cursor = new URL(request.url).searchParams.get("cursor");
4371
4581
  const metricBase = {
4372
- kind: getCollectionMetricKind(name$2),
4582
+ kind: getCollectionMetricKind(name$1),
4373
4583
  page: cursor != null,
4374
4584
  dispatcher: "built_in"
4375
4585
  };
@@ -4415,7 +4625,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4415
4625
  span.setStatus({ code: SpanStatusCode.ERROR });
4416
4626
  return await onNotFound(request);
4417
4627
  }
4418
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
4628
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
4419
4629
  itemCount = items.length;
4420
4630
  span.setAttribute("fedify.collection.items", itemCount);
4421
4631
  return items;
@@ -4457,7 +4667,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4457
4667
  } else {
4458
4668
  const uri = new URL(portable == null ? baseUri : pageBase);
4459
4669
  uri.searchParams.set("cursor", cursor);
4460
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
4670
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
4461
4671
  kind: SpanKind.SERVER,
4462
4672
  attributes: {
4463
4673
  "activitypub.collection.id": uri.href,
@@ -4473,7 +4683,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4473
4683
  span.setStatus({ code: SpanStatusCode.ERROR });
4474
4684
  return await onNotFound(request);
4475
4685
  }
4476
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
4686
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
4477
4687
  itemCount = items.length;
4478
4688
  span.setAttribute("fedify.collection.items", itemCount);
4479
4689
  return {
@@ -5664,8 +5874,8 @@ var CustomCollectionHandler = class {
5664
5874
  * @param CollectionPage The CollectionPage constructor.
5665
5875
  * @param filterPredicate Optional filter predicate for items.
5666
5876
  */
5667
- constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
5668
- this.name = name$1;
5877
+ constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
5878
+ this.name = name$2;
5669
5879
  this.values = values;
5670
5880
  this.context = context;
5671
5881
  this.callbacks = callbacks;
@@ -9411,10 +9621,10 @@ var FederationImpl = class extends FederationBuilderImpl {
9411
9621
  onNotFound
9412
9622
  });
9413
9623
  case "collection": {
9414
- const name = route.name.replace(/^collection:/, "");
9624
+ const name = this.getCollectionName(route.name);
9415
9625
  const callbacks = this.collectionCallbacks[name];
9416
9626
  return await handleCustomCollection(request, {
9417
- name,
9627
+ name: String(name),
9418
9628
  context,
9419
9629
  values: route.values,
9420
9630
  collectionCallbacks: callbacks,
@@ -9425,10 +9635,10 @@ var FederationImpl = class extends FederationBuilderImpl {
9425
9635
  });
9426
9636
  }
9427
9637
  case "orderedCollection": {
9428
- const name = route.name.replace(/^orderedCollection:/, "");
9638
+ const name = this.getCollectionName(route.name);
9429
9639
  const callbacks = this.collectionCallbacks[name];
9430
9640
  return await handleOrderedCollection(request, {
9431
- name,
9641
+ name: String(name),
9432
9642
  context,
9433
9643
  values: route.values,
9434
9644
  collectionCallbacks: callbacks,
@@ -10194,8 +10404,10 @@ var ContextImpl = class ContextImpl {
10194
10404
  const collectionRegex = new RegExp(`^(${["collection", "orderedCollection"].join("|")}):(.*)$`);
10195
10405
  const match = route.name.match(collectionRegex);
10196
10406
  if (match !== null) {
10197
- const [, type, name] = match;
10407
+ const [, type] = match;
10408
+ const name = this.federation.getCollectionName(route.name);
10198
10409
  const cls = this.federation.collectionTypeIds[name];
10410
+ if (cls == null) return null;
10199
10411
  return {
10200
10412
  type,
10201
10413
  name,
@@ -10496,17 +10708,26 @@ var ContextImpl = class ContextImpl {
10496
10708
  activityId: activity.id?.href,
10497
10709
  activity
10498
10710
  });
10711
+ const activityJsonLd = await activity.toJsonLd({
10712
+ format: "compact",
10713
+ contextLoader: this.contextLoader
10714
+ });
10715
+ try {
10716
+ await warnCompatibleIdsInJson(activityJsonLd, this instanceof RequestContextImpl && !this.isInActorDispatcher() ? this : void 0);
10717
+ } catch (error) {
10718
+ logger.debug("Could not check the first gateway of activity {activityId}: {error}", {
10719
+ activityId: activity.id?.href,
10720
+ error
10721
+ });
10722
+ }
10499
10723
  if (this.federation.fanoutQueue == null || options.immediate || options.fanout === "skip" || (options.fanout ?? "auto") === "auto" && globalThis.Object.keys(inboxes).length < FANOUT_THRESHOLD) {
10500
10724
  await this.federation.sendActivity(keys, inboxes, activity, {
10501
10725
  ...opts,
10726
+ activityJsonLd,
10502
10727
  normalizeExistingProofs: proofCreated || options.normalizeExistingProofs
10503
10728
  });
10504
10729
  return true;
10505
10730
  }
10506
- const activityJsonLd = await activity.toJsonLd({
10507
- format: "compact",
10508
- contextLoader: this.contextLoader
10509
- });
10510
10731
  assertSupportedCompoundProofShape(activityJsonLd, activity.id?.href);
10511
10732
  const keyJwkPairs = await Promise.all(keys.map(async ({ keyId, privateKey }) => ({
10512
10733
  keyId: keyId.href,
@@ -10598,7 +10819,7 @@ var ContextImpl = class ContextImpl {
10598
10819
  "inbox"
10599
10820
  ]);
10600
10821
  const contextLoader = options.contextLoader ?? this.contextLoader;
10601
- const json = await activity.toJsonLd({ contextLoader });
10822
+ let json = await activity.toJsonLd({ contextLoader });
10602
10823
  const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, {
10603
10824
  documentLoader: this.documentLoader,
10604
10825
  contextLoader: this.contextLoader,
@@ -10660,6 +10881,7 @@ var ContextImpl = class ContextImpl {
10660
10881
  }
10661
10882
  logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
10662
10883
  activity = fetched;
10884
+ json = await fetched.toJsonLd({ contextLoader });
10663
10885
  } else logger.debug("Object Integrity Proofs are verified.", {
10664
10886
  recipient,
10665
10887
  activity: json
@@ -10688,6 +10910,10 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
10688
10910
  #invokedFromActorDispatcher;
10689
10911
  #invokedFromObjectDispatcher;
10690
10912
  request;
10913
+ /** Whether a diagnostic actor lookup would re-enter the actor dispatcher. */
10914
+ isInActorDispatcher() {
10915
+ return this.#invokedFromActorDispatcher != null;
10916
+ }
10691
10917
  url = void 0;
10692
10918
  portableRequest;
10693
10919
  constructor(options) {