@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,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-ByBlnY3D.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-CXcAlwei.cjs");
7
- const require_kv_cache = require("./kv-cache-BDwUzxBJ.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");
@@ -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 _fedify_uri_template.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 _fedify_uri_template.RouterError(`Collection dispatcher for ${strName} already set.`);
679
682
  (0, _fedify_uri_template.assertPath)(path);
680
683
  if (_fedify_uri_template.Router.variables(path).size < 1) throw new _fedify_uri_template.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
  }
@@ -1449,7 +1457,7 @@ const textEncoder = new TextEncoder();
1449
1457
  function isPortableIdValue(value) {
1450
1458
  return typeof value === "string" && require_http.isPortableId(value);
1451
1459
  }
1452
- function isJsonMap(value) {
1460
+ function isJsonMap$1(value) {
1453
1461
  return typeof value === "object" && value != null && !Array.isArray(value);
1454
1462
  }
1455
1463
  function isCompoundProofJsonObject(value) {
@@ -1515,7 +1523,7 @@ function addBytes(statistics, bytes, limits, path) {
1515
1523
  if (statistics.byteLength > limits.maxBytes) return limitExceeded("bytes", limits.maxBytes, statistics.byteLength, path);
1516
1524
  }
1517
1525
  function inspectJsonTree(root, limits) {
1518
- if (!isJsonMap(root)) return {
1526
+ if (!isJsonMap$1(root)) return {
1519
1527
  status: "unsupported",
1520
1528
  reason: {
1521
1529
  type: "invalidJsonTree",
@@ -1578,7 +1586,7 @@ function inspectJsonTree(root, limits) {
1578
1586
  insideProof: current.insideProof,
1579
1587
  insideContext: current.insideContext
1580
1588
  });
1581
- } else if (isJsonMap(value)) {
1589
+ } else if (isJsonMap$1(value)) {
1582
1590
  const prototype = Object.getPrototypeOf(value);
1583
1591
  if (seen.has(value) || !hasOnlyDataProperties(value) || prototype !== Object.prototype && prototype !== null) return {
1584
1592
  status: "unsupported",
@@ -1599,7 +1607,7 @@ function inspectJsonTree(root, limits) {
1599
1607
  if (hasDirectProof) {
1600
1608
  statistics.proofCount++;
1601
1609
  if (statistics.proofCount > limits.maxProofs) return limitExceeded("proofs", limits.maxProofs, statistics.proofCount, childPath(current.path, "proof"));
1602
- if (!isJsonMap(value.proof)) return {
1610
+ if (!isJsonMap$1(value.proof)) return {
1603
1611
  status: "unsupported",
1604
1612
  reason: {
1605
1613
  type: "unsupportedProofShape",
@@ -1774,7 +1782,7 @@ function inspectCompoundPortableObjectApplicability(json, limits) {
1774
1782
  iterator: value.values(),
1775
1783
  depth: frame.depth + 1
1776
1784
  });
1777
- } else if (isJsonMap(value)) {
1785
+ } else if (isJsonMap$1(value)) {
1778
1786
  mapCount++;
1779
1787
  if (mapCount > limits.maxMaps) return "indeterminate";
1780
1788
  if ([value.id, value["@id"]].some(isPortableIdValue)) return "present";
@@ -1870,7 +1878,7 @@ function containsCompoundPortableObject(json) {
1870
1878
  * @internal
1871
1879
  */
1872
1880
  function findUnsupportedCompoundProofShape(json) {
1873
- 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;
1874
1882
  }
1875
1883
  /**
1876
1884
  * Discovers direct literal proof-bearing maps in an immutable JSON snapshot.
@@ -2072,13 +2080,13 @@ async function isEmbeddedPortableKey(parent, member, key, parentId) {
2072
2080
  }
2073
2081
  if (expanded.length !== 1 || hasGraphKeyword(expanded)) return false;
2074
2082
  const node = expanded[0];
2075
- 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;
2076
2084
  const properties = Object.keys(node).filter((property) => property !== "@id" && property !== "@type");
2077
2085
  if (properties.length !== 1 || properties[0] !== `${SECURITY}${member}`) return false;
2078
2086
  const values = node[properties[0]];
2079
2087
  if (!Array.isArray(values) || values.length !== 1) return false;
2080
2088
  const expandedKey = values[0];
2081
- 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;
2082
2090
  const types = expandedKey["@type"];
2083
2091
  if (types !== void 0 && (!Array.isArray(types) || types.some((type) => typeof type !== "string" || !KEY_TYPES.has(type)))) return false;
2084
2092
  const pem = expandedKey[`${SECURITY}publicKeyPem`];
@@ -2089,7 +2097,7 @@ async function isEmbeddedPortableKey(parent, member, key, parentId) {
2089
2097
  for (const property of [`${SECURITY}owner`, `${SECURITY}controller`]) {
2090
2098
  const owners = expandedKey[property];
2091
2099
  if (owners === void 0) continue;
2092
- 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;
2093
2101
  }
2094
2102
  return true;
2095
2103
  }
@@ -2132,7 +2140,7 @@ function isSimpleContext(context) {
2132
2140
  }
2133
2141
  function hasGraphKeyword(value) {
2134
2142
  if (Array.isArray(value)) return value.some(hasGraphKeyword);
2135
- if (!isJsonMap(value)) return false;
2143
+ if (!isJsonMap$1(value)) return false;
2136
2144
  return Object.entries(value).some(([key, child]) => key === "@graph" || key === "@included" || key === "@reverse" || hasGraphKeyword(child));
2137
2145
  }
2138
2146
  /**
@@ -2142,7 +2150,7 @@ function hasGraphKeyword(value) {
2142
2150
  function isSingleStringValue(values, datatype) {
2143
2151
  if (!Array.isArray(values) || values.length !== 1) return false;
2144
2152
  const value = values[0];
2145
- if (!isJsonMap(value) || typeof value["@value"] !== "string") return false;
2153
+ if (!isJsonMap$1(value) || typeof value["@value"] !== "string") return false;
2146
2154
  const keys = Object.keys(value);
2147
2155
  return keys.length === 1 || datatype != null && keys.length === 2 && value["@type"] === datatype;
2148
2156
  }
@@ -2197,7 +2205,7 @@ async function verifyServedPortableObjects(json, options = {}) {
2197
2205
  const maps = collected.maps;
2198
2206
  const discovered = /* @__PURE__ */ new Map();
2199
2207
  const ids = maps.map((candidate) => candidate.id);
2200
- 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;
2201
2209
  if (rootId != null) ids.push(rootId);
2202
2210
  for (const id of ids) discovered.set(id, (discovered.get(id) ?? 0) + 1);
2203
2211
  let hidden;
@@ -2242,7 +2250,7 @@ function findHiddenPortableObject(expanded, discovered) {
2242
2250
  pending.push(...value);
2243
2251
  continue;
2244
2252
  }
2245
- if (!isJsonMap(value)) continue;
2253
+ if (!isJsonMap$1(value)) continue;
2246
2254
  const id = value["@id"];
2247
2255
  if (isPortableIdValue(id) && Object.keys(value).some((key) => key !== "@id")) {
2248
2256
  const count = remaining.get(id) ?? 0;
@@ -2333,7 +2341,7 @@ function collectServedPortableMaps(json) {
2333
2341
  }
2334
2342
  async function checkServedPortableMap({ map, contexts }, options) {
2335
2343
  if (Object.hasOwn(map, "proof")) {
2336
- if (!isJsonMap(map.proof)) return "unsupportedProofShape";
2344
+ if (!isJsonMap$1(map.proof)) return "unsupportedProofShape";
2337
2345
  if (!Object.hasOwn(map, "@context")) return "missingContext";
2338
2346
  try {
2339
2347
  const policy = await require_http.verifyPortableObjectProofPolicy(map, await require_http.verifyMapLocalProof(map, options), options);
@@ -2354,11 +2362,185 @@ async function checkServedPortableMap({ map, contexts }, options) {
2354
2362
  } catch {
2355
2363
  return "unsupportedContext";
2356
2364
  }
2357
- if (expanded.length !== 1 || !isJsonMap(expanded[0])) return "invalidPortableObject";
2365
+ if (expanded.length !== 1 || !isJsonMap$1(expanded[0])) return "invalidPortableObject";
2358
2366
  const type = require_http.classifyFep2277CoreType(expanded[0]);
2359
2367
  return UNSECURED_CORE_TYPES.has(type) ? null : "missingProof";
2360
2368
  }
2361
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
2362
2544
  //#region src/federation/audience.ts
2363
2545
  const AS_NAMESPACE = "https://www.w3.org/ns/activitystreams#";
2364
2546
  /**
@@ -4153,6 +4335,7 @@ async function handleObject(request, { values, context, objectDispatcher, author
4153
4335
  Vary: "Accept"
4154
4336
  }
4155
4337
  });
4338
+ await warnServedCompatibleObject(context, object, getCompactRootId(jsonLd));
4156
4339
  return new Response(JSON.stringify(jsonLd), { headers: {
4157
4340
  "Content-Type": "application/activity+json",
4158
4341
  Vary: "Accept"
@@ -4256,6 +4439,7 @@ async function handlePortableObject(request, { values, context, objectDispatcher
4256
4439
  return portableObjectInternalServerError(request);
4257
4440
  }
4258
4441
  }
4442
+ if (!tombstone) await warnServedCompatibleObject(context, object, servedId);
4259
4443
  const headers = new Headers({
4260
4444
  "Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
4261
4445
  Vary: "Accept"
@@ -4266,6 +4450,32 @@ async function handlePortableObject(request, { values, context, objectDispatcher
4266
4450
  headers
4267
4451
  });
4268
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
+ }
4269
4479
  function isRequestedPortableObject(object, canonicalId) {
4270
4480
  return object.id != null && require_http.getCanonicalPortableId(object.id) === canonicalId;
4271
4481
  }
@@ -4369,13 +4579,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
4369
4579
  * @param parameters The parameters for handling the collection.
4370
4580
  * @returns A promise that resolves to an HTTP response.
4371
4581
  */
4372
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
4373
- 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, "_");
4374
4584
  tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
4375
4585
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
4376
4586
  const cursor = new URL(request.url).searchParams.get("cursor");
4377
4587
  const metricBase = {
4378
- kind: getCollectionMetricKind(name$2),
4588
+ kind: getCollectionMetricKind(name$1),
4379
4589
  page: cursor != null,
4380
4590
  dispatcher: "built_in"
4381
4591
  };
@@ -4421,7 +4631,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4421
4631
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
4422
4632
  return await onNotFound(request);
4423
4633
  }
4424
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
4634
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
4425
4635
  itemCount = items.length;
4426
4636
  span.setAttribute("fedify.collection.items", itemCount);
4427
4637
  return items;
@@ -4463,7 +4673,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4463
4673
  } else {
4464
4674
  const uri = new URL(portable == null ? baseUri : pageBase);
4465
4675
  uri.searchParams.set("cursor", cursor);
4466
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
4676
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
4467
4677
  kind: _opentelemetry_api.SpanKind.SERVER,
4468
4678
  attributes: {
4469
4679
  "activitypub.collection.id": uri.href,
@@ -4479,7 +4689,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4479
4689
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
4480
4690
  return await onNotFound(request);
4481
4691
  }
4482
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
4692
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
4483
4693
  itemCount = items.length;
4484
4694
  span.setAttribute("fedify.collection.items", itemCount);
4485
4695
  return {
@@ -5670,8 +5880,8 @@ var CustomCollectionHandler = class {
5670
5880
  * @param CollectionPage The CollectionPage constructor.
5671
5881
  * @param filterPredicate Optional filter predicate for items.
5672
5882
  */
5673
- constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
5674
- 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;
5675
5885
  this.values = values;
5676
5886
  this.context = context;
5677
5887
  this.callbacks = callbacks;
@@ -9508,10 +9718,10 @@ var FederationImpl = class extends FederationBuilderImpl {
9508
9718
  onNotFound
9509
9719
  });
9510
9720
  case "collection": {
9511
- const name = route.name.replace(/^collection:/, "");
9721
+ const name = this.getCollectionName(route.name);
9512
9722
  const callbacks = this.collectionCallbacks[name];
9513
9723
  return await handleCustomCollection(request, {
9514
- name,
9724
+ name: String(name),
9515
9725
  context,
9516
9726
  values: route.values,
9517
9727
  collectionCallbacks: callbacks,
@@ -9522,10 +9732,10 @@ var FederationImpl = class extends FederationBuilderImpl {
9522
9732
  });
9523
9733
  }
9524
9734
  case "orderedCollection": {
9525
- const name = route.name.replace(/^orderedCollection:/, "");
9735
+ const name = this.getCollectionName(route.name);
9526
9736
  const callbacks = this.collectionCallbacks[name];
9527
9737
  return await handleOrderedCollection(request, {
9528
- name,
9738
+ name: String(name),
9529
9739
  context,
9530
9740
  values: route.values,
9531
9741
  collectionCallbacks: callbacks,
@@ -10291,8 +10501,10 @@ var ContextImpl = class ContextImpl {
10291
10501
  const collectionRegex = new RegExp(`^(${["collection", "orderedCollection"].join("|")}):(.*)$`);
10292
10502
  const match = route.name.match(collectionRegex);
10293
10503
  if (match !== null) {
10294
- const [, type, name] = match;
10504
+ const [, type] = match;
10505
+ const name = this.federation.getCollectionName(route.name);
10295
10506
  const cls = this.federation.collectionTypeIds[name];
10507
+ if (cls == null) return null;
10296
10508
  return {
10297
10509
  type,
10298
10510
  name,
@@ -10593,17 +10805,26 @@ var ContextImpl = class ContextImpl {
10593
10805
  activityId: activity.id?.href,
10594
10806
  activity
10595
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
+ }
10596
10820
  if (this.federation.fanoutQueue == null || options.immediate || options.fanout === "skip" || (options.fanout ?? "auto") === "auto" && globalThis.Object.keys(inboxes).length < FANOUT_THRESHOLD) {
10597
10821
  await this.federation.sendActivity(keys, inboxes, activity, {
10598
10822
  ...opts,
10823
+ activityJsonLd,
10599
10824
  normalizeExistingProofs: proofCreated || options.normalizeExistingProofs
10600
10825
  });
10601
10826
  return true;
10602
10827
  }
10603
- const activityJsonLd = await activity.toJsonLd({
10604
- format: "compact",
10605
- contextLoader: this.contextLoader
10606
- });
10607
10828
  assertSupportedCompoundProofShape(activityJsonLd, activity.id?.href);
10608
10829
  const keyJwkPairs = await Promise.all(keys.map(async ({ keyId, privateKey }) => ({
10609
10830
  keyId: keyId.href,
@@ -10695,7 +10916,7 @@ var ContextImpl = class ContextImpl {
10695
10916
  "inbox"
10696
10917
  ]);
10697
10918
  const contextLoader = options.contextLoader ?? this.contextLoader;
10698
- const json = await activity.toJsonLd({ contextLoader });
10919
+ let json = await activity.toJsonLd({ contextLoader });
10699
10920
  const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, {
10700
10921
  documentLoader: this.documentLoader,
10701
10922
  contextLoader: this.contextLoader,
@@ -10757,6 +10978,7 @@ var ContextImpl = class ContextImpl {
10757
10978
  }
10758
10979
  logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
10759
10980
  activity = fetched;
10981
+ json = await fetched.toJsonLd({ contextLoader });
10760
10982
  } else logger.debug("Object Integrity Proofs are verified.", {
10761
10983
  recipient,
10762
10984
  activity: json
@@ -10785,6 +11007,10 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
10785
11007
  #invokedFromActorDispatcher;
10786
11008
  #invokedFromObjectDispatcher;
10787
11009
  request;
11010
+ /** Whether a diagnostic actor lookup would re-enter the actor dispatcher. */
11011
+ isInActorDispatcher() {
11012
+ return this.#invokedFromActorDispatcher != null;
11013
+ }
10788
11014
  url = void 0;
10789
11015
  portableRequest;
10790
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.2212+26ce9858";
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.2212+26ce9858";
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-BKp9fYB3.mjs";
6
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DPQKFkiJ.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-ChfV9C3v.mjs";
10
- import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-U3lW06QJ.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";
@@ -2,8 +2,8 @@ import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { c as isPortableActorDocument, d as isPortableUri, l as isPortableId, m as parseKeyIdString, s as isCompatibleKeyId } from "./portable-key-id-Ca30-hOa.mjs";
5
- import { n as version, t as name } from "./deno-BKp9fYB3.mjs";
6
- import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-DPQKFkiJ.mjs";
5
+ import { n as version, t as name } from "./deno-9flirCWb.mjs";
6
+ import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-X1lpKWRK.mjs";
7
7
  import { n as attachKeyOwnerEvidence, t as MAX_PORTABLE_KEY_TTL } from "./key-owner-evidence-APB6NpSk.mjs";
8
8
  import { getLogger } from "@logtape/logtape";
9
9
  import { CryptographicKey, Multikey, Object as Object$1, isActor } from "@fedify/vocab";
@@ -41,14 +41,15 @@ interface KvStore {
41
41
  /**
42
42
  * Gets the value for the given key.
43
43
  * @param key The key to get the value for.
44
- * @returns The value for the key, or `undefined` if the key does not exist.
44
+ * @returns The value for the key, including `null` if stored, or `undefined`
45
+ * if the key does not exist.
45
46
  * @template T The type of the value to get.
46
47
  */
47
48
  get<T = unknown>(key: KvKey): Promise<T | undefined>;
48
49
  /**
49
50
  * Sets the value for the given key.
50
51
  * @param key The key to set the value for.
51
- * @param value The value to set.
52
+ * @param value The value to set. `null` is a storable value.
52
53
  * @param options Additional options for setting the value.
53
54
  */
54
55
  set(key: KvKey, value: unknown, options?: KvStoreSetOptions): Promise<void>;
@@ -41,14 +41,15 @@ interface KvStore {
41
41
  /**
42
42
  * Gets the value for the given key.
43
43
  * @param key The key to get the value for.
44
- * @returns The value for the key, or `undefined` if the key does not exist.
44
+ * @returns The value for the key, including `null` if stored, or `undefined`
45
+ * if the key does not exist.
45
46
  * @template T The type of the value to get.
46
47
  */
47
48
  get<T = unknown>(key: KvKey): Promise<T | undefined>;
48
49
  /**
49
50
  * Sets the value for the given key.
50
51
  * @param key The key to set the value for.
51
- * @param value The value to set.
52
+ * @param value The value to set. `null` is a storable value.
52
53
  * @param options Additional options for setting the value.
53
54
  */
54
55
  set(key: KvKey, value: unknown, options?: KvStoreSetOptions): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { p as recordDocumentCache } from "./metrics-DPQKFkiJ.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-X1lpKWRK.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { preloadedContexts } from "@fedify/vocab-runtime";
7
7
  //#region src/utils/kv-cache.ts
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { G as validateCryptoKey, ot as recordDocumentCache, t as doubleKnock } from "./http-Di8yiDMY.js";
3
+ import { G as validateCryptoKey, ot as recordDocumentCache, t as doubleKnock } from "./http-CHqz_Jp6.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, resolveDocumentLoaderTimeout, validatePublicUrl, withDocumentLoaderTimeout } from "@fedify/vocab-runtime";
6
6
  import { curry } from "es-toolkit";