@fedify/fedify 2.5.0-dev.2327 → 2.5.0-dev.2339

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 (82) hide show
  1. package/dist/{builder-D1OTD3--.mjs → builder-4z5McYcY.mjs} +2 -2
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{compound-proof-DML23ZA8.mjs → compound-proof-B6xZTnTr.mjs} +2 -2
  4. package/dist/{deno-CzjcjZ80.mjs → deno-BnX-hlO-.mjs} +1 -1
  5. package/dist/{docloader-tMR7o09T.mjs → docloader-_tslGlJv.mjs} +2 -2
  6. package/dist/exporter-DThFfe7P.mjs +257 -0
  7. package/dist/federation/builder.test.mjs +1 -1
  8. package/dist/federation/compound-fanout.test.mjs +3 -3
  9. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  10. package/dist/federation/handler.test.mjs +224 -5
  11. package/dist/federation/hashlink-media.test.mjs +2 -2
  12. package/dist/federation/idempotency.test.mjs +2 -2
  13. package/dist/federation/inbox-report.test.mjs +5 -5
  14. package/dist/federation/metrics.test.mjs +1 -1
  15. package/dist/federation/middleware.test.mjs +101 -8
  16. package/dist/federation/mod.cjs +1 -1
  17. package/dist/federation/mod.js +1 -1
  18. package/dist/federation/outgoing-proof.test.mjs +5 -5
  19. package/dist/federation/portable-actor.test.mjs +3 -3
  20. package/dist/federation/portable-chain.test.mjs +3 -3
  21. package/dist/federation/portable-collection.test.mjs +4 -4
  22. package/dist/federation/portable-delivery.test.mjs +2 -2
  23. package/dist/federation/portable-gateway-keys.test.mjs +4 -4
  24. package/dist/federation/portable-inbox.test.mjs +4 -4
  25. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  26. package/dist/federation/portable-route-activity.test.mjs +2 -2
  27. package/dist/federation/portable.test.mjs +3 -3
  28. package/dist/federation/send.test.mjs +199 -3
  29. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  30. package/dist/federation/tasks/tasks.test.mjs +2 -2
  31. package/dist/federation/temporal.test.mjs +1 -1
  32. package/dist/federation/webfinger.test.mjs +1 -1
  33. package/dist/{federation-BHOSHFCr.cjs → federation-CV-CkIIZ.cjs} +22 -14
  34. package/dist/{http-CUhaXRsy.cjs → http-BJrxYYP3.cjs} +1 -1
  35. package/dist/{http-DzBwbsDL.mjs → http-BSmcgoaq.mjs} +4 -4
  36. package/dist/{http-gbfMWXuK.js → http-CVT3OEmZ.js} +1 -1
  37. package/dist/{key-CfJ7MJmp.mjs → key-DRLFC1um.mjs} +2 -2
  38. package/dist/{kv-cache-B3HjvmZE.mjs → kv-cache-DTY50c-3.mjs} +1 -1
  39. package/dist/{kv-cache-Y18nY4gZ.cjs → kv-cache-DnCGPsXH.cjs} +1 -1
  40. package/dist/{kv-cache-CyCsAOR6.js → kv-cache-Dv3joZCp.js} +1 -1
  41. package/dist/{ld-BzvgaYZi.mjs → ld-BiFf-T9F.mjs} +3 -3
  42. package/dist/{metrics-Dwkct8jJ.mjs → metrics-BuC9-u1Z.mjs} +1 -1
  43. package/dist/{middleware-BURmMr0W.mjs → middleware-A02EGx8T.mjs} +1 -1
  44. package/dist/{middleware-BFr_zLaE.mjs → middleware-BBSxgV0Y.mjs} +27 -23
  45. package/dist/{middleware-vJzuhHEr.js → middleware-CKEoEiBw.js} +22 -14
  46. package/dist/mod.cjs +4 -4
  47. package/dist/mod.js +4 -4
  48. package/dist/nodeinfo/handler.test.mjs +1 -1
  49. package/dist/otel/exporter.test.mjs +1 -253
  50. package/dist/{owner-CGXm_YAa.mjs → owner-BsgoTcNb.mjs} +3 -3
  51. package/dist/{portable-collection-DBAMRYNa.mjs → portable-collection-BiBpObws.mjs} +2 -2
  52. package/dist/{portable-collection-Cnl40K17.js → portable-collection-BzUoU2gg.js} +1 -1
  53. package/dist/{portable-collection-to2aHmLK.cjs → portable-collection-D3c6LAHN.cjs} +1 -1
  54. package/dist/{portable-key-DqFNrfnc.mjs → portable-key-CF6z6v-f.mjs} +1 -1
  55. package/dist/{proof-CImybMS5.mjs → proof-vlRUtAdS.mjs} +4 -4
  56. package/dist/{send-oReIfAwd.mjs → send-D5aMlvJH.mjs} +10 -6
  57. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  58. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  59. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  60. package/dist/sig/compound-proof.test.mjs +2 -2
  61. package/dist/sig/http.test.mjs +2 -2
  62. package/dist/sig/key.test.mjs +1 -1
  63. package/dist/sig/ld.test.mjs +2 -2
  64. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  65. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  66. package/dist/sig/mod.cjs +2 -2
  67. package/dist/sig/mod.js +2 -2
  68. package/dist/sig/owner.test.mjs +1 -1
  69. package/dist/sig/portable-collection.test.mjs +1 -1
  70. package/dist/sig/portable-interop.test.mjs +3 -3
  71. package/dist/sig/portable-key.test.mjs +6 -6
  72. package/dist/sig/proof.test.mjs +3 -3
  73. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  74. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  75. package/dist/sig/verification.test.mjs +3 -3
  76. package/dist/{temporal-DLaPDeOv.mjs → temporal-BE3xoktR.mjs} +1 -1
  77. package/dist/testing/kv.test.mjs +1 -1
  78. package/dist/utils/docloader.test.mjs +2 -2
  79. package/dist/utils/kv-cache.test.mjs +1 -1
  80. package/dist/utils/mod.cjs +1 -1
  81. package/dist/utils/mod.js +1 -1
  82. package/package.json +6 -6
@@ -2,7 +2,7 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { i as getPortableDid, l as isPortableId, n as getCanonicalPortableId } from "./portable-key-id-Ca30-hOa.mjs";
5
- import { n as version, t as name } from "./deno-CzjcjZ80.mjs";
5
+ import { n as version, t as name } from "./deno-BnX-hlO-.mjs";
6
6
  import { t as ActivityListenerSet } from "./activity-listener-C-BSbKI7.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
@@ -91,7 +91,7 @@ var FederationBuilderImpl = class {
91
91
  * specified.
92
92
  */
93
93
  async build(options) {
94
- const { FederationImpl } = await import("./middleware-BURmMr0W.mjs");
94
+ const { FederationImpl } = await import("./middleware-A02EGx8T.mjs");
95
95
  const f = new FederationImpl(options);
96
96
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
97
97
  f.router = this.router.clone();
@@ -5,7 +5,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import { t as assertInstanceOf } from "../assert_instance_of-mrXWWNY-.mjs";
7
7
  import { t as assert } from "../assert-D1Eafmd_.mjs";
8
- import { O as actorDehydrator, k as autoIdAssigner, n as FederationImpl } from "../middleware-BFr_zLaE.mjs";
8
+ import { O as actorDehydrator, k as autoIdAssigner, n as FederationImpl } from "../middleware-BBSxgV0Y.mjs";
9
9
  import { Follow, Person } from "@fedify/vocab";
10
10
  import { test } from "@fedify/fixture";
11
11
  //#region src/compat/transformers.test.ts
@@ -4,8 +4,8 @@ globalThis.addEventListener = () => {};
4
4
  import { a as getRawCanonicalPortableId, l as isPortableId, n as getCanonicalPortableId } from "./portable-key-id-Ca30-hOa.mjs";
5
5
  import { a as verificationObservation, t as observeAttempt } from "./verification-Dpfcw8ml.mjs";
6
6
  import { n as preloadedOnlyDocumentLoader } from "./public-audience--klBIOk1.mjs";
7
- import { c as getNormalizationContextLoader } from "./ld-BzvgaYZi.mjs";
8
- import { l as verifyPortableObjectProofPolicy, n as classifyFep2277CoreType, o as verifyMapLocalProof } from "./proof-CImybMS5.mjs";
7
+ import { c as getNormalizationContextLoader } from "./ld-BiFf-T9F.mjs";
8
+ import { l as verifyPortableObjectProofPolicy, n as classifyFep2277CoreType, o as verifyMapLocalProof } from "./proof-vlRUtAdS.mjs";
9
9
  import { parseIri, preloadedContexts } from "@fedify/vocab-runtime";
10
10
  import jsonld from "@fedify/vocab-runtime/jsonld";
11
11
  //#region src/sig/compound-proof.ts
@@ -3,6 +3,6 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  //#region deno.json
5
5
  var name = "@fedify/fedify";
6
- var version = "2.5.0-dev.2327+3a7ba490";
6
+ var version = "2.5.0-dev.2339+5e786861";
7
7
  //#endregion
8
8
  export { version as n, name as t };
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { c as validateCryptoKey } from "./key-CfJ7MJmp.mjs";
5
- import { n as doubleKnock } from "./http-DzBwbsDL.mjs";
4
+ import { c as validateCryptoKey } from "./key-DRLFC1um.mjs";
5
+ import { n as doubleKnock } from "./http-BSmcgoaq.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, resolveDocumentLoaderTimeout, validatePublicUrl, withDocumentLoaderTimeout } from "@fedify/vocab-runtime";
8
8
  import { curry } from "es-toolkit";
@@ -0,0 +1,257 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { getLogger } from "@logtape/logtape";
5
+ import { ExportResultCode } from "@opentelemetry/core";
6
+ //#region src/otel/exporter.ts
7
+ /**
8
+ * A SpanExporter that persists ActivityPub activity traces to a
9
+ * {@link KvStore}. This enables distributed tracing across multiple
10
+ * nodes in a Fedify deployment.
11
+ *
12
+ * The exporter captures activity data from OpenTelemetry span events
13
+ * (`activitypub.activity.received` and `activitypub.activity.sent`)
14
+ * and stores them in the KvStore with trace context preserved.
15
+ *
16
+ * @example Basic usage with MemoryKvStore
17
+ * ```typescript ignore
18
+ * import { MemoryKvStore } from "@fedify/fedify";
19
+ * import { FedifySpanExporter } from "@fedify/fedify/otel";
20
+ * import {
21
+ * BasicTracerProvider,
22
+ * SimpleSpanProcessor,
23
+ * } from "@opentelemetry/sdk-trace-base";
24
+ *
25
+ * const kv = new MemoryKvStore();
26
+ * const exporter = new FedifySpanExporter(kv, {
27
+ * ttl: Temporal.Duration.from({ hours: 1 }),
28
+ * });
29
+ *
30
+ * const provider = new BasicTracerProvider({
31
+ * spanProcessors: [new SimpleSpanProcessor(exporter)],
32
+ * });
33
+ * ```
34
+ *
35
+ * @example Querying stored traces
36
+ * ```typescript ignore
37
+ * import { MemoryKvStore } from "@fedify/fedify";
38
+ * import { FedifySpanExporter } from "@fedify/fedify/otel";
39
+ *
40
+ * const kv = new MemoryKvStore();
41
+ * const exporter = new FedifySpanExporter(kv);
42
+ * const traceId = "abc123";
43
+ *
44
+ * // Get all activities for a specific trace
45
+ * const activities = await exporter.getActivitiesByTraceId(traceId);
46
+ *
47
+ * // Get recent traces
48
+ * const recentTraces = await exporter.getRecentTraces({ limit: 100 });
49
+ * ```
50
+ *
51
+ * @since 1.10.0
52
+ */
53
+ var FedifySpanExporter = class {
54
+ #kv;
55
+ #ttl;
56
+ #keyPrefix;
57
+ /**
58
+ * Creates a new FedifySpanExporter.
59
+ *
60
+ * @param kv The KvStore to persist trace data to.
61
+ * @param options Configuration options.
62
+ */
63
+ constructor(kv, options) {
64
+ this.#kv = kv;
65
+ this.#ttl = options?.ttl;
66
+ this.#keyPrefix = options?.keyPrefix ?? ["fedify", "traces"];
67
+ }
68
+ /**
69
+ * Exports spans to the KvStore.
70
+ *
71
+ * @param spans The spans to export.
72
+ * @param resultCallback Callback to invoke with the export result.
73
+ */
74
+ export(spans, resultCallback) {
75
+ this.#exportAsync(spans).then(() => resultCallback({ code: ExportResultCode.SUCCESS })).catch((error) => {
76
+ getLogger([
77
+ "fedify",
78
+ "otel",
79
+ "exporter"
80
+ ]).error("Failed to export spans to KvStore: {error}", { error });
81
+ resultCallback({ code: ExportResultCode.FAILED });
82
+ });
83
+ }
84
+ async #exportAsync(spans) {
85
+ const storeOperations = [];
86
+ for (const span of spans) {
87
+ const records = this.#extractRecords(span);
88
+ for (const record of records) storeOperations.push(this.#storeRecord(record));
89
+ }
90
+ const rejected = (await Promise.allSettled(storeOperations)).filter((r) => r.status === "rejected");
91
+ if (rejected.length > 0) throw new AggregateError(rejected.map((r) => r.reason), "Failed to store one or more trace activity records.");
92
+ }
93
+ #extractRecords(span) {
94
+ const records = [];
95
+ const spanContext = span.spanContext();
96
+ const traceId = spanContext.traceId;
97
+ const spanId = spanContext.spanId;
98
+ const parentSpanId = span.parentSpanContext?.spanId;
99
+ for (const event of span.events) if (event.name === "activitypub.activity.received") {
100
+ const record = this.#extractInboundRecord(event, traceId, spanId, parentSpanId);
101
+ if (record != null) records.push(record);
102
+ } else if (event.name === "activitypub.activity.sent") {
103
+ const record = this.#extractOutboundRecord(event, traceId, spanId, parentSpanId);
104
+ if (record != null) records.push(record);
105
+ }
106
+ return records;
107
+ }
108
+ #extractInboundRecord(event, traceId, spanId, parentSpanId) {
109
+ const attrs = event.attributes;
110
+ if (attrs == null) return null;
111
+ const activityJson = attrs["activitypub.activity.json"];
112
+ if (typeof activityJson !== "string") return null;
113
+ let activityType = "Unknown";
114
+ let activityId;
115
+ let actorId;
116
+ try {
117
+ const activity = JSON.parse(activityJson);
118
+ activityType = activity.type ?? "Unknown";
119
+ activityId = activity.id;
120
+ if (typeof activity.actor === "string") actorId = activity.actor;
121
+ else if (activity.actor != null && typeof activity.actor.id === "string") actorId = activity.actor.id;
122
+ } catch {}
123
+ const verified = attrs["activitypub.activity.verified"];
124
+ const httpSigVerified = attrs["http_signatures.verified"];
125
+ const httpSigKeyId = attrs["http_signatures.key_id"];
126
+ const httpSigFailureReason = attrs["http_signatures.failure_reason"];
127
+ const httpSigKeyFetchStatus = attrs["http_signatures.key_fetch_status"];
128
+ const httpSigKeyFetchError = attrs["http_signatures.key_fetch_error"];
129
+ const ldSigVerified = attrs["ld_signatures.verified"];
130
+ let signatureDetails;
131
+ if (typeof httpSigVerified === "boolean" || typeof ldSigVerified === "boolean" || typeof httpSigFailureReason === "string") signatureDetails = {
132
+ httpSignaturesVerified: httpSigVerified === true,
133
+ httpSignaturesKeyId: typeof httpSigKeyId === "string" && httpSigKeyId !== "" ? httpSigKeyId : void 0,
134
+ httpSignaturesFailureReason: typeof httpSigFailureReason === "string" && httpSigFailureReason !== "" ? httpSigFailureReason : void 0,
135
+ httpSignaturesKeyFetchStatus: typeof httpSigKeyFetchStatus === "number" ? httpSigKeyFetchStatus : void 0,
136
+ httpSignaturesKeyFetchError: typeof httpSigKeyFetchError === "string" && httpSigKeyFetchError !== "" ? httpSigKeyFetchError : void 0,
137
+ ldSignaturesVerified: ldSigVerified === true
138
+ };
139
+ return {
140
+ traceId,
141
+ spanId,
142
+ parentSpanId,
143
+ direction: "inbound",
144
+ activityType,
145
+ activityId,
146
+ actorId,
147
+ activityJson,
148
+ verified: typeof verified === "boolean" ? verified : void 0,
149
+ signatureDetails,
150
+ timestamp: (/* @__PURE__ */ new Date(event.time[0] * 1e3 + event.time[1] / 1e6)).toISOString()
151
+ };
152
+ }
153
+ #extractOutboundRecord(event, traceId, spanId, parentSpanId) {
154
+ const attrs = event.attributes;
155
+ if (attrs == null) return null;
156
+ const activityJson = attrs["activitypub.activity.json"];
157
+ let activityType = "Unknown";
158
+ let activityId;
159
+ let actorId;
160
+ if (typeof activityJson === "string") try {
161
+ const activity = JSON.parse(activityJson);
162
+ activityType = activity.type ?? "Unknown";
163
+ activityId = activity.id;
164
+ if (typeof activity.actor === "string") actorId = activity.actor;
165
+ else if (activity.actor != null && typeof activity.actor.id === "string") actorId = activity.actor.id;
166
+ } catch {}
167
+ const inboxUrl = attrs["activitypub.inbox.url"];
168
+ const explicitActivityId = attrs["activitypub.activity.id"];
169
+ const explicitActivityType = attrs["activitypub.activity.type"];
170
+ const explicitActorId = attrs["activitypub.actor.id"];
171
+ return {
172
+ traceId,
173
+ spanId,
174
+ parentSpanId,
175
+ direction: "outbound",
176
+ activityType: typeof explicitActivityType === "string" && explicitActivityType !== "" ? explicitActivityType : activityType,
177
+ activityId: activityId ?? (typeof explicitActivityId === "string" && explicitActivityId !== "" ? explicitActivityId : void 0),
178
+ actorId: typeof explicitActorId === "string" && explicitActorId !== "" ? explicitActorId : actorId,
179
+ ...typeof activityJson === "string" ? { activityJson } : {},
180
+ timestamp: (/* @__PURE__ */ new Date(event.time[0] * 1e3 + event.time[1] / 1e6)).toISOString(),
181
+ inboxUrl: typeof inboxUrl === "string" ? inboxUrl : void 0
182
+ };
183
+ }
184
+ async #storeRecord(record) {
185
+ const options = this.#ttl != null ? { ttl: this.#ttl } : void 0;
186
+ const key = [
187
+ ...this.#keyPrefix,
188
+ record.traceId,
189
+ record.spanId
190
+ ];
191
+ await this.#kv.set(key, record, options);
192
+ await this.#updateTraceSummary(record, options);
193
+ }
194
+ async #setWithCasRetry(key, transform, options) {
195
+ if (this.#kv.cas != null) for (let attempt = 0; attempt < 3; attempt++) {
196
+ const existing = await this.#kv.get(key);
197
+ const newValue = transform(existing);
198
+ if (await this.#kv.cas(key, existing, newValue, options)) return;
199
+ }
200
+ const newValue = transform(await this.#kv.get(key));
201
+ await this.#kv.set(key, newValue, options);
202
+ }
203
+ async #updateTraceSummary(record, options) {
204
+ const summaryKey = [
205
+ ...this.#keyPrefix,
206
+ "_summaries",
207
+ record.traceId
208
+ ];
209
+ await this.#setWithCasRetry(summaryKey, (existing) => {
210
+ const activityCount = existing != null ? existing.activityCount + 1 : 1;
211
+ const activityTypes = existing != null ? existing.activityTypes.includes(record.activityType) ? existing.activityTypes : [...existing.activityTypes, record.activityType] : [record.activityType];
212
+ return {
213
+ traceId: existing?.traceId ?? record.traceId,
214
+ timestamp: existing?.timestamp ?? record.timestamp,
215
+ activityCount,
216
+ activityTypes
217
+ };
218
+ }, options);
219
+ }
220
+ /**
221
+ * Gets all activity records for a specific trace ID.
222
+ *
223
+ * @param traceId The trace ID to query.
224
+ * @returns An array of activity records belonging to the trace.
225
+ */
226
+ async getActivitiesByTraceId(traceId) {
227
+ const prefix = [...this.#keyPrefix, traceId];
228
+ const records = [];
229
+ for await (const entry of this.#kv.list(prefix)) records.push(entry.value);
230
+ return records;
231
+ }
232
+ /**
233
+ * Gets recent traces with summary information.
234
+ *
235
+ * @param options Options for the query.
236
+ * @returns An array of trace summaries.
237
+ */
238
+ async getRecentTraces(options) {
239
+ const summaryPrefix = [...this.#keyPrefix, "_summaries"];
240
+ const summaries = [];
241
+ for await (const entry of this.#kv.list(summaryPrefix)) summaries.push(entry.value);
242
+ summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
243
+ if (options?.limit != null) return summaries.slice(0, options.limit);
244
+ return summaries;
245
+ }
246
+ /**
247
+ * Forces the exporter to flush any buffered data.
248
+ * This is a no-op because we write directly to the KvStore without buffering.
249
+ */
250
+ async forceFlush() {}
251
+ /**
252
+ * Shuts down the exporter.
253
+ */
254
+ async shutdown() {}
255
+ };
256
+ //#endregion
257
+ export { FedifySpanExporter as t };
@@ -7,7 +7,7 @@ import { t as assertExists } from "../assert_exists-cffSpJ7X.mjs";
7
7
  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
- import { r as createFederationBuilder } from "../builder-D1OTD3--.mjs";
10
+ import { r as createFederationBuilder } from "../builder-4z5McYcY.mjs";
11
11
  import { DisallowedOperatorError, DisallowedVarSpecModifierError, DuplicateRouteVariableError, RouteTemplateOptionsNotMatchedError, RouterError } from "@fedify/uri-template";
12
12
  import { Activity, Note, Person } from "@fedify/vocab";
13
13
  import { test } from "@fedify/fixture";
@@ -6,11 +6,11 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import "../std__assert-DvMDRPes.mjs";
8
8
  import { t as assert } from "../assert-D1Eafmd_.mjs";
9
- import { n as exportJwk } from "../key-CfJ7MJmp.mjs";
9
+ import { n as exportJwk } from "../key-DRLFC1um.mjs";
10
10
  import { r as normalizeOutgoingActivityJsonLd } from "../outgoing-jsonld-4IJqPYos.mjs";
11
- import { a as signObject, d as verifyProof } from "../proof-CImybMS5.mjs";
11
+ import { a as signObject, d as verifyProof } from "../proof-vlRUtAdS.mjs";
12
12
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
13
- import { n as FederationImpl } from "../middleware-BFr_zLaE.mjs";
13
+ import { n as FederationImpl } from "../middleware-BBSxgV0Y.mjs";
14
14
  import { Create, DataIntegrityProof, Note } from "@fedify/vocab";
15
15
  import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
16
16
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -5,10 +5,10 @@ import { r as createRequestContext, t as createInboxContext } from "../context-8
5
5
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import "../std__assert-DvMDRPes.mjs";
8
- import { a as compactJsonLd, m as verifyCompactJsonLd, p as signJsonLd } from "../ld-BzvgaYZi.mjs";
9
- import { l as signRequest } from "../http-DzBwbsDL.mjs";
8
+ import { a as compactJsonLd, m as verifyCompactJsonLd, p as signJsonLd } from "../ld-BiFf-T9F.mjs";
9
+ import { l as signRequest } from "../http-BSmcgoaq.mjs";
10
10
  import { a as rsaPrivateKey3, c as rsaPublicKey3, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
11
- import { f as handleInbox, o as createFederation } from "../middleware-BFr_zLaE.mjs";
11
+ import { f as handleInbox, o as createFederation } from "../middleware-BBSxgV0Y.mjs";
12
12
  import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
13
13
  import { t as map_local_create_note_default } from "../map-local-create-note-B0SeXJms.mjs";
14
14
  import { Create, CryptographicKey, Multikey, Person, Update } from "@fedify/vocab";
@@ -6,20 +6,24 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import { t as assertGreaterOrEqual } from "../assert_greater_or_equal-BlafOF4-.mjs";
8
8
  import { t as assertInstanceOf } from "../assert_instance_of-mrXWWNY-.mjs";
9
+ import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
9
10
  import "../std__assert-DvMDRPes.mjs";
10
11
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
11
12
  import { t as assert } from "../assert-D1Eafmd_.mjs";
12
13
  import { r as parseAcceptSignature } from "../accept-CPVqki78.mjs";
13
- import { o as generateCryptoKeyPair } from "../key-CfJ7MJmp.mjs";
14
- import { a as compactJsonLd, p as signJsonLd } from "../ld-BzvgaYZi.mjs";
15
- import { a as signObject } from "../proof-CImybMS5.mjs";
16
- import { l as signRequest } from "../http-DzBwbsDL.mjs";
14
+ import { o as generateCryptoKeyPair } from "../key-DRLFC1um.mjs";
15
+ import { a as compactJsonLd, p as signJsonLd } from "../ld-BiFf-T9F.mjs";
16
+ import { a as signObject } from "../proof-vlRUtAdS.mjs";
17
+ import { l as signRequest } from "../http-BSmcgoaq.mjs";
17
18
  import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
18
- import { d as handleCustomCollection, f as handleInbox, g as respondWithObjectIfAcceptable, h as respondWithObject, l as handleActor, m as handleOutbox, o as createFederation, p as handleObject, u as handleCollection } from "../middleware-BFr_zLaE.mjs";
19
+ import { d as handleCustomCollection, f as handleInbox, g as respondWithObjectIfAcceptable, h as respondWithObject, l as handleActor, m as handleOutbox, o as createFederation, p as handleObject, u as handleCollection } from "../middleware-BBSxgV0Y.mjs";
19
20
  import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
21
+ import { t as FedifySpanExporter } from "../exporter-DThFfe7P.mjs";
22
+ import { configure, getLogger, reset, withContext } from "@logtape/logtape";
20
23
  import { Activity, Create, CryptographicKey, Multikey, Note, Person, Tombstone } from "@fedify/vocab";
21
24
  import { FetchError, parseIri } from "@fedify/vocab-runtime";
22
25
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
26
+ import { AsyncLocalStorage } from "node:async_hooks";
23
27
  //#region src/federation/handler.test.ts
24
28
  const QUOTE_CONTEXT_TERMS = {
25
29
  QuoteAuthorization: "https://w3id.org/fep/044f#QuoteAuthorization",
@@ -3879,6 +3883,221 @@ test("handleInbox() records OpenTelemetry span events", async () => {
3879
3883
  assertGreaterOrEqual(durations[0].value, 0);
3880
3884
  assertEquals(durations[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
3881
3885
  });
3886
+ test("handleInbox() aligns the LogTape context with its own span", async (t) => {
3887
+ async function buildSignedRequest(activityId, noteId) {
3888
+ const activity = new Create({
3889
+ id: new URL(activityId),
3890
+ actor: new URL("https://example.com/users/someone"),
3891
+ object: new Note({
3892
+ id: new URL(noteId),
3893
+ content: "Hello, world!"
3894
+ })
3895
+ });
3896
+ return await signRequest(new Request("https://example.com/users/someone/inbox", {
3897
+ method: "POST",
3898
+ headers: { "Content-Type": "application/activity+json" },
3899
+ body: JSON.stringify(await activity.toJsonLd())
3900
+ }), rsaPrivateKey3, new URL("https://example.com/users/someone#main-key"));
3901
+ }
3902
+ const actorDispatcher = (ctx, identifier) => {
3903
+ if (identifier !== "someone") return null;
3904
+ return new Person({
3905
+ id: ctx.getActorUri(identifier),
3906
+ name: "Someone",
3907
+ inbox: new URL("https://example.com/users/someone/inbox"),
3908
+ publicKey: rsaPublicKey2
3909
+ });
3910
+ };
3911
+ async function callHandleInbox(federation, kv, tracerProvider, listeners, signed) {
3912
+ const context = createRequestContext({
3913
+ federation,
3914
+ request: signed,
3915
+ url: new URL(signed.url),
3916
+ data: void 0,
3917
+ documentLoader: mockDocumentLoader,
3918
+ contextLoader: mockDocumentLoader,
3919
+ getActorUri(identifier) {
3920
+ return new URL(`https://example.com/users/${identifier}`);
3921
+ }
3922
+ });
3923
+ return await handleInbox(signed, {
3924
+ recipient: "someone",
3925
+ context,
3926
+ inboxContextFactory(_activity) {
3927
+ return createInboxContext({
3928
+ ...context,
3929
+ clone: void 0
3930
+ });
3931
+ },
3932
+ kv,
3933
+ kvPrefixes: {
3934
+ activityIdempotence: ["activityIdempotence"],
3935
+ publicKey: ["publicKey"],
3936
+ acceptSignatureNonce: ["acceptSignatureNonce"]
3937
+ },
3938
+ actorDispatcher,
3939
+ inboxListeners: listeners,
3940
+ inboxErrorHandler: void 0,
3941
+ onNotFound: (_request) => new Response("Not found", { status: 404 }),
3942
+ signatureTimeWindow: false,
3943
+ skipSignatureVerification: true,
3944
+ tracerProvider
3945
+ });
3946
+ }
3947
+ const records = [];
3948
+ await reset();
3949
+ try {
3950
+ await configure({
3951
+ sinks: { buffer: (record) => records.push(record) },
3952
+ filters: {},
3953
+ loggers: [{
3954
+ category: [],
3955
+ sinks: ["buffer"],
3956
+ lowestLevel: "debug"
3957
+ }, {
3958
+ category: ["logtape", "meta"],
3959
+ sinks: []
3960
+ }],
3961
+ contextLocalStorage: new AsyncLocalStorage()
3962
+ });
3963
+ const outerContext = {
3964
+ requestId: "outer-request",
3965
+ traceId: "outer0000000000000000000000sentinel",
3966
+ spanId: "outer00000sentinel"
3967
+ };
3968
+ await t.step("a listener failure log matches the inbox's own TraceActivityRecord, and the outer context is restored afterward", async () => {
3969
+ const [tracerProvider, exporter] = createTestTracerProvider();
3970
+ try {
3971
+ const kv = new MemoryKvStore();
3972
+ const federation = createFederation({
3973
+ kv,
3974
+ tracerProvider
3975
+ });
3976
+ const listeners = new ActivityListenerSet();
3977
+ listeners.add(Create, () => {
3978
+ throw new Error("listener boom");
3979
+ });
3980
+ const signed = await buildSignedRequest("https://example.com/activity/listener-failure", "https://example.com/note/listener-failure");
3981
+ await withContext(outerContext, async () => {
3982
+ assertEquals((await callHandleInbox(federation, kv, tracerProvider, listeners, signed)).status, 500);
3983
+ getLogger([
3984
+ "fedify",
3985
+ "federation",
3986
+ "handler.test"
3987
+ ]).info("after handled failure");
3988
+ });
3989
+ const span = exporter.getSpan("activitypub.inbox");
3990
+ assert(span != null);
3991
+ const { traceId, spanId } = span.spanContext();
3992
+ const fedifyExporter = new FedifySpanExporter(new MemoryKvStore());
3993
+ await new Promise((resolve) => {
3994
+ fedifyExporter.export([span], () => resolve());
3995
+ });
3996
+ const [record] = await fedifyExporter.getActivitiesByTraceId(traceId);
3997
+ assert(record != null);
3998
+ assertEquals(record.direction, "inbound");
3999
+ assertEquals(record.spanId, spanId);
4000
+ const failureLog = records.find((r) => String(r.rawMessage).startsWith("Failed to process the incoming activity"));
4001
+ assert(failureLog != null);
4002
+ assertEquals(failureLog.properties.spanId, record.spanId);
4003
+ assertEquals(failureLog.properties.traceId, record.traceId);
4004
+ assertEquals(failureLog.properties.requestId, "outer-request");
4005
+ const afterLog = records.find((r) => r.rawMessage === "after handled failure");
4006
+ assert(afterLog != null);
4007
+ assertEquals(afterLog.properties.requestId, outerContext.requestId);
4008
+ assertEquals(afterLog.properties.traceId, outerContext.traceId);
4009
+ assertEquals(afterLog.properties.spanId, outerContext.spanId);
4010
+ } finally {
4011
+ exporter.clear();
4012
+ records.length = 0;
4013
+ }
4014
+ });
4015
+ await t.step("outer context is restored after successful inbox processing", async () => {
4016
+ const [tracerProvider, exporter] = createTestTracerProvider();
4017
+ try {
4018
+ const kv = new MemoryKvStore();
4019
+ const federation = createFederation({
4020
+ kv,
4021
+ tracerProvider
4022
+ });
4023
+ const listeners = new ActivityListenerSet();
4024
+ let received = null;
4025
+ listeners.add(Create, (_ctx, activity) => {
4026
+ received = activity;
4027
+ });
4028
+ const signed = await buildSignedRequest("https://example.com/activity/listener-success", "https://example.com/note/listener-success");
4029
+ await withContext(outerContext, async () => {
4030
+ assertEquals((await callHandleInbox(federation, kv, tracerProvider, listeners, signed)).status, 202);
4031
+ getLogger([
4032
+ "fedify",
4033
+ "federation",
4034
+ "handler.test"
4035
+ ]).info("after success");
4036
+ });
4037
+ assert(received != null);
4038
+ const span = exporter.getSpan("activitypub.inbox");
4039
+ assert(span != null);
4040
+ const { spanId } = span.spanContext();
4041
+ const afterLog = records.find((r) => r.rawMessage === "after success");
4042
+ assert(afterLog != null);
4043
+ assertNotEquals(afterLog.properties.spanId, spanId);
4044
+ assertEquals(afterLog.properties.requestId, outerContext.requestId);
4045
+ assertEquals(afterLog.properties.traceId, outerContext.traceId);
4046
+ assertEquals(afterLog.properties.spanId, outerContext.spanId);
4047
+ } finally {
4048
+ exporter.clear();
4049
+ records.length = 0;
4050
+ }
4051
+ });
4052
+ await t.step("concurrent inbox requests do not leak context into each other", async () => {
4053
+ const [tracerProvider, exporter] = createTestTracerProvider();
4054
+ try {
4055
+ const kv = new MemoryKvStore();
4056
+ const federation = createFederation({
4057
+ kv,
4058
+ tracerProvider
4059
+ });
4060
+ let arrived = 0;
4061
+ let release = () => {};
4062
+ const gate = new Promise((resolve) => {
4063
+ release = resolve;
4064
+ });
4065
+ const listeners = new ActivityListenerSet();
4066
+ listeners.add(Create, async () => {
4067
+ arrived++;
4068
+ if (arrived >= 2) release();
4069
+ await gate;
4070
+ throw new Error("listener boom");
4071
+ });
4072
+ const [signedA, signedB] = await Promise.all([buildSignedRequest("https://example.com/activity/concurrent-a", "https://example.com/note/concurrent-a"), buildSignedRequest("https://example.com/activity/concurrent-b", "https://example.com/note/concurrent-b")]);
4073
+ const [resultA, resultB] = await Promise.allSettled([callHandleInbox(federation, kv, tracerProvider, listeners, signedA).finally(release), callHandleInbox(federation, kv, tracerProvider, listeners, signedB).finally(release)]);
4074
+ assert(resultA.status === "fulfilled");
4075
+ assert(resultB.status === "fulfilled");
4076
+ assertEquals(resultA.value.status, 500);
4077
+ assertEquals(resultB.value.status, 500);
4078
+ const spans = exporter.getSpans("activitypub.inbox");
4079
+ assertEquals(spans.length, 2);
4080
+ const spanA = spans.find((s) => s.attributes["activitypub.activity.id"] === "https://example.com/activity/concurrent-a");
4081
+ const spanB = spans.find((s) => s.attributes["activitypub.activity.id"] === "https://example.com/activity/concurrent-b");
4082
+ assert(spanA != null && spanB != null);
4083
+ assertNotEquals(spanA.spanContext().spanId, spanB.spanContext().spanId);
4084
+ const logA = records.find((r) => r.properties.activityId === "https://example.com/activity/concurrent-a" && String(r.rawMessage).startsWith("Failed to process the incoming activity"));
4085
+ const logB = records.find((r) => r.properties.activityId === "https://example.com/activity/concurrent-b" && String(r.rawMessage).startsWith("Failed to process the incoming activity"));
4086
+ assert(logA != null && logB != null);
4087
+ assertEquals(logA.properties.spanId, spanA.spanContext().spanId);
4088
+ assertEquals(logB.properties.spanId, spanB.spanContext().spanId);
4089
+ assertEquals(logA.properties.traceId, spanA.spanContext().traceId);
4090
+ assertEquals(logB.properties.traceId, spanB.spanContext().traceId);
4091
+ assertNotEquals(logA.properties.spanId, logB.properties.spanId);
4092
+ } finally {
4093
+ exporter.clear();
4094
+ records.length = 0;
4095
+ }
4096
+ });
4097
+ } finally {
4098
+ await reset();
4099
+ }
4100
+ });
3882
4101
  test("handleInbox() records fedify.queue.task.enqueued when queued", async () => {
3883
4102
  const [meterProvider, recorder] = createTestMeterProvider();
3884
4103
  const kv = new MemoryKvStore();
@@ -10,8 +10,8 @@ import "../std__assert-DvMDRPes.mjs";
10
10
  import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
11
11
  import { t as assert } from "../assert-D1Eafmd_.mjs";
12
12
  import { r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
13
- import { o as createFederation, x as parseHashlinkGatewayRequest } from "../middleware-BFr_zLaE.mjs";
14
- import { r as createFederationBuilder } from "../builder-D1OTD3--.mjs";
13
+ import { o as createFederation, x as parseHashlinkGatewayRequest } from "../middleware-BBSxgV0Y.mjs";
14
+ import { r as createFederationBuilder } from "../builder-4z5McYcY.mjs";
15
15
  import { n as RouterError } from "../router-u5O5KCQr.mjs";
16
16
  import { Note } from "@fedify/vocab";
17
17
  import { computeDigestMultibase, encodeMultibase, exportDidKey } from "@fedify/vocab-runtime";
@@ -4,9 +4,9 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import "../std__assert-DvMDRPes.mjs";
7
- import { a as signObject } from "../proof-CImybMS5.mjs";
7
+ import { a as signObject } from "../proof-vlRUtAdS.mjs";
8
8
  import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
9
- import { o as createFederation } from "../middleware-BFr_zLaE.mjs";
9
+ import { o as createFederation } from "../middleware-BBSxgV0Y.mjs";
10
10
  import { Create, Follow, Person } from "@fedify/vocab";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  //#region src/federation/idempotency.test.ts
@@ -2,12 +2,12 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
- import { p as signJsonLd } from "../ld-BzvgaYZi.mjs";
6
- import { a as signObject } from "../proof-CImybMS5.mjs";
7
- import { l as signRequest } from "../http-DzBwbsDL.mjs";
5
+ import { p as signJsonLd } from "../ld-BiFf-T9F.mjs";
6
+ import { a as signObject } from "../proof-vlRUtAdS.mjs";
7
+ import { l as signRequest } from "../http-BSmcgoaq.mjs";
8
8
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
9
- import { o as createFederation } from "../middleware-BFr_zLaE.mjs";
10
- import { r as createFederationBuilder } from "../builder-D1OTD3--.mjs";
9
+ import { o as createFederation } from "../middleware-BBSxgV0Y.mjs";
10
+ import { r as createFederationBuilder } from "../builder-4z5McYcY.mjs";
11
11
  import { Create, Person } from "@fedify/vocab";
12
12
  import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
13
13
  import { mockDocumentLoader, test } from "@fedify/fixture";