@fedify/fedify 2.5.0-dev.2609 → 2.5.0-dev.2614

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 (123) hide show
  1. package/dist/{builder-BLCvt0zp.mjs → builder-B2H8GsyI.mjs} +82 -3
  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-DNhLvscb.mjs → compound-proof-BavXW-ow.mjs} +2 -2
  6. package/dist/{context-ByflxsY9.d.cts → context-C1NqcIct.d.cts} +1061 -443
  7. package/dist/{context-uQUY5fQT.d.ts → context-xrqq4Hrq.d.ts} +1064 -446
  8. package/dist/{deno-DYkYmKp0.mjs → deno-CRXki9-m.mjs} +1 -1
  9. package/dist/{docloader-B9RO5nh9.mjs → docloader-Ca6i6ddp.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  13. package/dist/federation/handler.test.mjs +4 -4
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/inbox-actorless.test.mjs +5 -5
  17. package/dist/federation/inbox-report.test.mjs +220 -5
  18. package/dist/federation/inbox-view.test.mjs +1 -1
  19. package/dist/federation/inbox.test.mjs +1 -1
  20. package/dist/federation/metrics.test.mjs +1 -1
  21. package/dist/federation/middleware.test.mjs +7 -7
  22. package/dist/federation/mod.cjs +1 -1
  23. package/dist/federation/mod.d.cts +3 -3
  24. package/dist/federation/mod.d.ts +4 -4
  25. package/dist/federation/mod.js +1 -1
  26. package/dist/federation/mq.test.mjs +1 -1
  27. package/dist/federation/outbound-body.test.d.mts +2 -0
  28. package/dist/federation/outbound-body.test.mjs +149 -0
  29. package/dist/federation/outbound-capture.test.d.mts +2 -0
  30. package/dist/federation/outbound-capture.test.mjs +394 -0
  31. package/dist/federation/outbound-dispatch.test.d.mts +2 -0
  32. package/dist/federation/outbound-dispatch.test.mjs +368 -0
  33. package/dist/federation/outbound-document.test.d.mts +2 -0
  34. package/dist/federation/outbound-document.test.mjs +171 -0
  35. package/dist/federation/outbound-provenance.test.d.mts +2 -0
  36. package/dist/federation/outbound-provenance.test.mjs +543 -0
  37. package/dist/federation/outbound-report.test.d.mts +2 -0
  38. package/dist/federation/outbound-report.test.mjs +5475 -0
  39. package/dist/federation/outbound-workers.test.d.mts +2 -0
  40. package/dist/federation/outbound-workers.test.mjs +64 -0
  41. package/dist/federation/outbound-workflow.test.d.mts +2 -0
  42. package/dist/federation/outbound-workflow.test.mjs +154 -0
  43. package/dist/federation/outgoing-proof.test.mjs +4 -4
  44. package/dist/federation/portable-actor.test.mjs +3 -3
  45. package/dist/federation/portable-chain.test.mjs +3 -3
  46. package/dist/federation/portable-collection.test.mjs +4 -4
  47. package/dist/federation/portable-delivery.test.mjs +106 -7
  48. package/dist/federation/portable-gateway-keys.test.mjs +5 -5
  49. package/dist/federation/portable-inbox.test.mjs +430 -5
  50. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  51. package/dist/federation/portable-route-activity.test.mjs +2 -2
  52. package/dist/federation/portable.test.mjs +3 -3
  53. package/dist/federation/send.test.mjs +44 -3
  54. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  55. package/dist/federation/tasks/tasks.test.mjs +2 -2
  56. package/dist/federation/temporal.test.mjs +1 -1
  57. package/dist/federation/webfinger.test.mjs +1 -1
  58. package/dist/{federation-DzMdu4zU.cjs → federation-DRl81KbJ.cjs} +2358 -405
  59. package/dist/{http-EoFzzh1s.js → http-C2H4DT6t.js} +62 -32
  60. package/dist/{http-BdTJujR0.cjs → http-DHSIMipE.cjs} +62 -32
  61. package/dist/{http-TgeyuTDM.mjs → http-DqOXkYbX.mjs} +64 -34
  62. package/dist/{inbox-ChkbRxCN.mjs → inbox-C4iW1g0F.mjs} +90 -3
  63. package/dist/{inbox-view-BosBZdlt.mjs → inbox-view-Bu_aSwFJ.mjs} +1 -1
  64. package/dist/{key-DbLNT-vA.d.ts → key-dwwAg5bA.d.ts} +1 -1
  65. package/dist/{kv-cache-CMgOYiLU.js → kv-cache-B-x_OrBo.js} +1 -1
  66. package/dist/{kv-cache-B0o18_Ni.cjs → kv-cache-CHR_BVyM.cjs} +1 -1
  67. package/dist/{kv-cache-CTA2i08a.mjs → kv-cache-DJsxu9nI.mjs} +1 -1
  68. package/dist/{ld-CZsMAfOw.mjs → ld-C5JOBBFj.mjs} +2 -2
  69. package/dist/{ld-Inagww4R.d.ts → ld-o4LnhCYY.d.ts} +2 -2
  70. package/dist/{metrics-Kc9mP1LD.mjs → metrics-CTteRnEr.mjs} +1 -1
  71. package/dist/{metrics-DoRtI0qU.d.ts → metrics-DVKYY6K1.d.ts} +1 -1
  72. package/dist/{middleware-B-pjIvQ8.js → middleware-B-tMVSUA.js} +2303 -351
  73. package/dist/{middleware-CIRoIVSU.mjs → middleware-BkRqMGPU.mjs} +1573 -325
  74. package/dist/{middleware-C1coPz8l.mjs → middleware-CPigKLnt.mjs} +1 -1
  75. package/dist/{mod-B7j30IQf.d.ts → mod-CQC9xlLZ.d.ts} +3 -3
  76. package/dist/{mod-B4kBw1-8.d.ts → mod-D22ZsiKi.d.ts} +2 -2
  77. package/dist/{mod-BN0TTfyQ.d.ts → mod-DO0epJGH.d.ts} +1 -1
  78. package/dist/{mod-DF2gbN_d.d.cts → mod-TwiQgEf3.d.cts} +1 -1
  79. package/dist/mod.cjs +4 -4
  80. package/dist/mod.d.cts +4 -4
  81. package/dist/mod.d.ts +8 -8
  82. package/dist/mod.js +4 -4
  83. package/dist/nodeinfo/handler.test.mjs +1 -1
  84. package/dist/outbound-body-CckPA5gc.mjs +83 -0
  85. package/dist/outbound-capture-DVgnRzlf.mjs +184 -0
  86. package/dist/outbound-document-BMgwLXTY.mjs +179 -0
  87. package/dist/{owner-Dwz4fGUW.mjs → owner-CKlRIkeF.mjs} +3 -3
  88. package/dist/{portable-collection-Oxfavx8f.mjs → portable-collection-B7RBHVGl.mjs} +2 -2
  89. package/dist/{portable-collection-BikiijSk.js → portable-collection-CByyPjLc.js} +2 -2
  90. package/dist/{portable-collection-Ci6vIhbR.cjs → portable-collection-CmLjeKsP.cjs} +2 -2
  91. package/dist/{portable-key--fWpTNDN.mjs → portable-key-Clg0IH8C.mjs} +1 -1
  92. package/dist/{proof-D6Kb0Qq9.mjs → proof-tAb5_Dq4.mjs} +3 -3
  93. package/dist/{send-BFti0L3I.mjs → send-DzfFREML.mjs} +153 -40
  94. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  95. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  96. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  97. package/dist/sig/compound-proof.test.mjs +2 -2
  98. package/dist/sig/http.test.mjs +38 -2
  99. package/dist/sig/key.test.mjs +1 -1
  100. package/dist/sig/ld.test.mjs +1 -1
  101. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  102. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  103. package/dist/sig/mod.cjs +2 -2
  104. package/dist/sig/mod.d.ts +3 -3
  105. package/dist/sig/mod.js +2 -2
  106. package/dist/sig/owner.test.mjs +1 -1
  107. package/dist/sig/portable-collection.test.mjs +1 -1
  108. package/dist/sig/portable-interop.test.mjs +3 -3
  109. package/dist/sig/portable-key.test.mjs +5 -5
  110. package/dist/sig/proof.test.mjs +3 -3
  111. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  112. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  113. package/dist/sig/verification.test.mjs +3 -3
  114. package/dist/{temporal-DlYL_raW.mjs → temporal-D9Nt8Fm4.mjs} +1 -1
  115. package/dist/testing/kv.test.mjs +1 -1
  116. package/dist/testing/mod.d.mts +1030 -412
  117. package/dist/utils/docloader.test.mjs +2 -2
  118. package/dist/utils/kv-cache.test.mjs +1 -1
  119. package/dist/utils/mod.cjs +1 -1
  120. package/dist/utils/mod.d.ts +1 -1
  121. package/dist/utils/mod.js +1 -1
  122. package/package.json +7 -7
  123. package/skills/fedify/SKILL.md +60 -1
@@ -9,6 +9,536 @@ import { Link as Link$1, LookupWebFingerOptions, ResourceDescriptor } from "@fed
9
9
  import { MeterProvider, Span, Tracer, TracerProvider } from "@opentelemetry/api";
10
10
  import { StandardSchemaV1 } from "@standard-schema/spec";
11
11
 
12
+ //#region src/federation/outbound-report.d.ts
13
+ /**
14
+ * The current producer's preparation result, not delivery completion.
15
+ * @since 2.5.0
16
+ */
17
+ type OutboundPreparationResult = {
18
+ /** The producer finished preparing its continuation. */readonly type: "prepared"; /** Fanout-ready work still needs worker-side delivery preparation. */
19
+ readonly preparedFor: "fanout" | "delivery"; /** Number of deduplicated destinations, not actors or successful sends. */
20
+ readonly destinationCount: number;
21
+ } | {
22
+ /** A deliberate no-send decision. */readonly type: "skipped"; /** The existing no-send decision made by this producer. */
23
+ readonly reason: "no-destinations" | "unsigned" | "forwarding-disabled" | "forwarding-unavailable" | "no-forwarding-targets";
24
+ } | {
25
+ /** Preparation failed before the producer reached readiness. */readonly type: "failed"; /** The operation that failed and its original thrown value. */
26
+ readonly failure: {
27
+ /** Local processing failure, or unavailable operation provenance. */readonly kind: "local" | "unknown"; /** The observed operation, not a diagnosis from the error message. */
28
+ readonly phase: "sender-resolution" | "key-resolution" | "recipient-resolution" | "transformation" | "validation" | "signing" | "destination-planning" | "serialization" | "request-preparation" | "unknown"; /** Original thrown value, including non-Error values. */
29
+ readonly error: unknown;
30
+ }; /** Whether a complete destination plan was established before failure. */
31
+ readonly planning: "not-started" | "incomplete" | "complete"; /** Confirmed planned obligations so far, not an audience estimate. */
32
+ readonly plannedDestinationCount: number;
33
+ };
34
+ /**
35
+ * Known Activity ID and type at a broadcast observation boundary.
36
+ *
37
+ * Fields are captured without serializing the Activity or loading contexts.
38
+ * Successful transformation refreshes both the ID and the Activity class type.
39
+ * A `started` event may have an empty reference, and later events may reflect
40
+ * Activity transformations that happened after that event.
41
+ * @since 2.5.0
42
+ */
43
+ type OutboundActivityReference = {
44
+ /** Activity identifier, when available at the observation boundary. */readonly id?: string; /** Activity class type IRI, when available at the observation boundary. */
45
+ readonly type?: string;
46
+ };
47
+ /**
48
+ * A send, original-payload forwarding, or portable replication producer lifecycle.
49
+ *
50
+ * Preparation results do not report queue acceptance or delivery completion.
51
+ * @since 2.5.0
52
+ */
53
+ type OutboundBroadcastEvent = OutboundSourceMetadata & {
54
+ /** Fresh opaque identity shared by producer events, independent of tracing. */readonly broadcastId: string; /** The outgoing operation being observed. */
55
+ readonly operation: "send" | "forward" | "portable-replication"; /** Immutable Activity identity facts captured by the send path. */
56
+ readonly activity: OutboundActivityReference;
57
+ } & ({
58
+ /** Entry before sender/recipient resolution or signing. */readonly stage: "started";
59
+ } | {
60
+ /** The current producer's preparation settled. */readonly stage: "preparation-finished"; /** Readiness, a deliberate skip, or the original preparation failure. */
61
+ readonly result: OutboundPreparationResult;
62
+ });
63
+ /**
64
+ * A producer-side destination obligation, not delivery completion.
65
+ *
66
+ * IDs group producer handoffs and their fanout expansion across queue tasks.
67
+ * Concrete sends use separate {@link OutboundAttemptReport} values; these
68
+ * events do not establish delivery completion or worker scheduling outcomes.
69
+ * An unvisited destination stays planned when another destination's preparation
70
+ * interrupts the producer.
71
+ * @since 2.5.0
72
+ */
73
+ type OutboundDeliveryEvent = OutboundSourceMetadata & {
74
+ /** The originating invocation, or unknown for legacy fanout work. */readonly broadcast: {
75
+ readonly type: "known";
76
+ readonly id: string;
77
+ } | {
78
+ readonly type: "unknown";
79
+ }; /** Opaque identity allocated after deduplication or a confirmed claim. */
80
+ readonly deliveryId: string; /** Planned destination, unchanged by concrete gateway selection. */
81
+ readonly destinationInbox: string; /** Canonical portable inbox when the selected route represents one. */
82
+ readonly portableInbox?: string;
83
+ } & ({
84
+ /** This destination was established by the existing plan. */readonly stage: "planned"; /** Known actors sharing the destination, not an exhaustive audience. */
85
+ readonly actorIds: readonly string[]; /** Whether the selected destination uses a shared inbox. */
86
+ readonly sharedInbox: boolean; /** Fallback routes share one obligation; replication targets do not. */
87
+ readonly mode: "ordinary" | "portable-fallback" | "portable-replication";
88
+ } | {
89
+ /** Local execution entry or enqueue settlement, not remote success. */readonly stage: "handoff"; /** Evidence from the actual producer handoff. */
90
+ readonly result: {
91
+ readonly type: "direct";
92
+ } | {
93
+ readonly type: "enqueued";
94
+ readonly queue: "fanout" | "outbox";
95
+ readonly queueMessageId: string;
96
+ } | {
97
+ readonly type: "enqueue-failed";
98
+ readonly acceptance: "unknown";
99
+ readonly error: unknown;
100
+ };
101
+ } | {
102
+ /** Destination-local setup failed before entry into concrete sending. */readonly stage: "preparation-failed"; /** The original local setup failure, without inventing an attempt. */
103
+ readonly failure: {
104
+ readonly kind: "local";
105
+ readonly phase: "request-preparation";
106
+ readonly error: unknown;
107
+ };
108
+ });
109
+ /**
110
+ * The failure classification and available error evidence for a send attempt.
111
+ * @since 2.5.0
112
+ */
113
+ type OutboundAttemptFailure = {
114
+ readonly kind: "http";
115
+ readonly phase: "response";
116
+ readonly error?: unknown;
117
+ } | {
118
+ readonly kind: "transport";
119
+ readonly phase: "validation" | "request";
120
+ readonly error: unknown;
121
+ } | {
122
+ readonly kind: "policy";
123
+ readonly phase: "validation";
124
+ readonly error: unknown;
125
+ } | {
126
+ readonly kind: "local";
127
+ readonly phase: "serialization" | "request-preparation" | "cleanup" | "signing" | "spec-lookup" | "post-response";
128
+ readonly error: unknown;
129
+ } | {
130
+ readonly kind: "unknown";
131
+ readonly phase: "unknown";
132
+ readonly error: unknown;
133
+ };
134
+ /** Immutable JSON data in a privacy projection. @since 2.5.0 */
135
+ type OutboundJsonValue = null | boolean | number | string | readonly OutboundJsonValue[] | {
136
+ readonly [key: string]: OutboundJsonValue;
137
+ };
138
+ /** Attempt input evidence, never proof of a particular exchange's wire body. @since 2.5.0 */
139
+ type OutboundSenderPayload = {
140
+ /** Concrete sender input, not a producer history or per-exchange wire body. */readonly stage: "sender-payload";
141
+ } & ({
142
+ /** A safe independent projection was established without extra processing. */readonly type: "available"; /** Always a projection; no signature/proof or exact-wire claim is retained. */
143
+ readonly representation: "privacy-projection"; /** At least one blind-recipient field was removed from the copied value. */
144
+ readonly redacted: boolean; /** Recursively immutable JSON data; transport/signing input is unchanged. */
145
+ readonly value: OutboundJsonValue;
146
+ } | {
147
+ /** No document value is exposed, including when export is disabled. */readonly type: "unavailable"; /** Opt-out, pre-snapshot failure, or unsupported privacy/context semantics. */
148
+ readonly reason: "disabled" | "not-materialized" | "privacy-filter-unavailable";
149
+ });
150
+ /** Result of an enabled response-body capture. Absence means disabled. @since 2.5.0 */
151
+ type OutboundBodyCaptureSummary = {
152
+ /** Bytes handed to the application, not bytes durably stored. */readonly bytesExposed: number;
153
+ } & ({
154
+ /** Selection, verified EOF, byte-cap uncertainty, or local lifetime end. */readonly type: "filtered" | "no-body" | "complete" | "limit-reached" | "deadline" | "callback-returned";
155
+ } | {
156
+ /** Application cancellation ended capture. */readonly type: "consumer-stopped"; /** Original consumer reason, including non-Error values. */
157
+ readonly reason: unknown;
158
+ } | {
159
+ /** Capture failed without changing received HTTP evidence. */readonly type: "failed"; /** Source reading versus application callback failure. */
160
+ readonly phase: "body" | "observer"; /** Original capture error; it does not become the attempt's failure. */
161
+ readonly error: unknown;
162
+ });
163
+ /** Bounded, revocable access to one selected response. @since 2.5.0 */
164
+ type OutboundResponseBodyEvent = Omit<OutboundHttpExchangeReport, "outcome" | "bodyCapture"> & {
165
+ /** Immutable response metadata, independently of eventual attempt outcome. */readonly response: Extract<OutboundHttpExchangeReport["outcome"], {
166
+ type: "response";
167
+ }>["response"]; /** Pull-driven bytes; access ends when the callback settles or its limits apply. */
168
+ readonly body: ReadableStream<Uint8Array>; /** Capture-only lifetime signal; aborting it does not cancel transport. */
169
+ readonly signal: AbortSignal;
170
+ };
171
+ /** Explicit opt-in response-body capture limits and consumer. @since 2.5.0 */
172
+ interface OutboundResponseBodyCapture<TContextData> {
173
+ /** Positive safe-integer exposure limit; exact cap without EOF stays uncertain. */
174
+ readonly maxBytes: number;
175
+ /** Positive finite absolute lifetime in milliseconds, rounded up; at most 2^31-1. */
176
+ readonly timeoutMs: number;
177
+ /** Actual response selector, defaulting to `all` only when enabled. */
178
+ readonly responses?: "all" | "non-success-http-responses";
179
+ /**
180
+ * Invoked after headers, before the terminal exchange report, with current data.
181
+ * Awaited within the absolute lifetime, including no-pull/slow-consumer time.
182
+ * Return/throw/cancel/cap/deadline revokes access even for retained locked readers.
183
+ * Errors cannot veto transport; ignoring the signal cannot be forcibly stopped.
184
+ */
185
+ readonly onBody: (context: Context<TContextData>, event: OutboundResponseBodyEvent) => void | Promise<void>;
186
+ }
187
+ /** Opt-in policy for one side of an actual HTTP exchange. @since 2.5.0 */
188
+ interface OutboundHeaderPolicy {
189
+ /** Literal HTTP names, normalized to lowercase and deduplicated at registration. */
190
+ readonly allow: readonly string[];
191
+ /**
192
+ * Additional names whose selected values are always `[redacted]`.
193
+ * Credentials, signatures and cookies remain protected even without this list.
194
+ */
195
+ readonly redact?: readonly string[];
196
+ /**
197
+ * Positive safe-integer UTF-8 budget for `name: value\r\n` after redaction.
198
+ * Whole oversized entries are omitted; later entries remain eligible.
199
+ */
200
+ readonly maxBytes: number;
201
+ }
202
+ /** Immutable selected header values, using the runtime representation. @since 2.5.0 */
203
+ interface OutboundHeaderSnapshot {
204
+ /** Lowercase names in lexical order; credentials/signatures/cookies are redacted. */
205
+ readonly entries: readonly (readonly [string, string])[];
206
+ /** At least one selected entry was omitted because it exceeded the budget. */
207
+ readonly limitReached: boolean;
208
+ }
209
+ /**
210
+ * Immutable HTTP response facts captured from a received response.
211
+ * @since 2.5.0
212
+ */
213
+ interface OutboundHttpResponseMetadata {
214
+ /** Actual HTTP status returned by fetch. */
215
+ readonly status: number;
216
+ /** Copied HTTP status text. */
217
+ readonly statusText: string;
218
+ /** Response URL when supplied by fetch. */
219
+ readonly url?: string;
220
+ }
221
+ /**
222
+ * Response evidence and finality established during a send attempt.
223
+ * @since 2.5.0
224
+ */
225
+ type OutboundAttemptResponse = {
226
+ /** The HTTP flow selected this response before follow-up work. */readonly type: "final"; /** Actual submission index of the selected response. */
227
+ readonly exchangeIndex: number; /** Frozen response facts, retained across later local failures. */
228
+ readonly metadata: OutboundHttpResponseMetadata;
229
+ } | {
230
+ /** No response headers were received, including zero-fetch attempts. */readonly type: "not-observed";
231
+ } | {
232
+ /** Continued past the response, or failed before deciding finality. */readonly type: "intermediate-only" | "undetermined"; /** Latest received response, without a finality claim. */
233
+ readonly lastObserved: {
234
+ readonly exchangeIndex: number;
235
+ readonly metadata: OutboundHttpResponseMetadata;
236
+ };
237
+ };
238
+ /**
239
+ * The terminal result of one concrete send.
240
+ *
241
+ * A send that fails before submitting HTTP still constitutes an attempt, with
242
+ * zero exchanges. Each gateway send and retry gets a fresh attempt identity.
243
+ * Final response evidence follows the HTTP flow's selection, not the last
244
+ * observed status; later cache/cleanup failures can still fail the execution.
245
+ * @since 2.5.0
246
+ */
247
+ type OutboundAttemptReport = OutboundSourceMetadata & {
248
+ /** Fresh opaque identity allocated at concrete sender entry. */readonly attemptId: string; /** The originating invocation, independent of tracing. */
249
+ readonly broadcast: OutboundDeliveryEvent["broadcast"]; /** Destination obligation shared with producer delivery events. */
250
+ readonly deliveryId: string; /** Original planned inbox; unavailable on legacy outbox tasks. */
251
+ readonly destinationInbox?: string; /** Canonical portable inbox when the planned destination represents one. */
252
+ readonly portableInbox?: string; /** Inbox selected for this invocation of the concrete sender. */
253
+ readonly inboxUrl: string; /** Actual fetch submissions, including challenges and redirects; may be zero. */
254
+ readonly httpExchangeCount: number; /** Opt-in independent sender input projection; unavailable states remain explicit. */
255
+ readonly senderPayload: OutboundSenderPayload; /** Current queue execution; absent on immediate sends. */
256
+ readonly queue?: {
257
+ readonly queueMessageId: string; /** Present when the queue message carries a Fedify retry counter. */
258
+ readonly queueAttempt?: number;
259
+ }; /** Settled execution result, separate from producer preparation. */
260
+ readonly outcome: {
261
+ readonly type: "succeeded"; /** Final response selected by the sender. */
262
+ readonly response: Extract<OutboundAttemptResponse, {
263
+ type: "final";
264
+ }>;
265
+ } | {
266
+ readonly type: "failed";
267
+ /**
268
+ * Bounded failure kind and operation phase, captured before broad wrapping.
269
+ * Unknown provenance stays unknown; exception names do not establish it.
270
+ * Final non-OK HTTP evidence remains primary alongside local diagnostics.
271
+ */
272
+ readonly failure: OutboundAttemptFailure; /** Exact value escaping the sender, possibly different from failure.error. */
273
+ readonly error: unknown; /** Explicit final or last-observed response evidence, including absence. */
274
+ readonly response: OutboundAttemptResponse; /** Additional failures, without replacing the primary evidence. */
275
+ readonly diagnostics?: readonly OutboundAttemptFailure[];
276
+ };
277
+ };
278
+ /**
279
+ * One actual fetch submission within a concrete send attempt.
280
+ *
281
+ * A redirect response remains an observed response even if a later hop fails.
282
+ * This report does not establish the attempt's final response. Metadata is
283
+ * copied before callback awaiting; no live Request/Response/Headers are exposed.
284
+ * Enabled body capture uses a separate revocable callback; reports hold summaries.
285
+ * Exchange callbacks run after selected capture ends and may precede attempt settlement.
286
+ * @since 2.5.0
287
+ */
288
+ type OutboundHttpExchangeReport = OutboundSourceMetadata & Pick<OutboundAttemptReport, "attemptId" | "broadcast" | "deliveryId" | "destinationInbox" | "portableInbox" | "inboxUrl" | "queue"> & {
289
+ /** Zero-based submission order within this attempt. */readonly exchangeIndex: number; /** Enabled capture result; absent when disabled or no response was received. */
290
+ readonly bodyCapture?: OutboundBodyCaptureSummary; /** Immutable facts of the request actually submitted. */
291
+ readonly request: {
292
+ readonly url: string;
293
+ readonly method: string; /** Selected header snapshot; absent when request header capture is disabled. */
294
+ readonly headers?: OutboundHeaderSnapshot;
295
+ }; /** A received response, including non-2xx, or the original fetch rejection. */
296
+ readonly outcome: {
297
+ readonly type: "response";
298
+ readonly response: OutboundHttpResponseMetadata & {
299
+ /** Selected snapshot; absent when response header capture is disabled. */readonly headers?: OutboundHeaderSnapshot;
300
+ };
301
+ } | {
302
+ readonly type: "failed";
303
+ readonly error: unknown;
304
+ };
305
+ };
306
+ /**
307
+ * A worker retry or deferral submission, separate from its enqueue outcome.
308
+ * @since 2.5.0
309
+ */
310
+ type OutboundWorkerSchedule = {
311
+ readonly action: "retry" | "defer"; /** Actual continuation task and Fedify retry counter. */
312
+ readonly queue: NonNullable<OutboundAttemptReport["queue"]>; /** Actual affected routes, copied independently of the planned delivery. */
313
+ readonly inboxUrls: readonly string[]; /** ISO 8601 duration passed to enqueue after delay normalization. */
314
+ readonly delay: string; /** Per-route ISO instants retained by the scheduled continuation, if any. */
315
+ readonly notBefore?: Readonly<Record<string, string>>; /** Local preparation failures that contributed to this actual action. */
316
+ readonly preparationFailures?: readonly OutboundWorkerPreparationDiagnostic[];
317
+ };
318
+ /**
319
+ * One local preparation failure associated with a worker route.
320
+ * @since 2.5.0
321
+ */
322
+ type OutboundWorkerPreparationDiagnostic = {
323
+ /** Route whose local setup produced the failure. */readonly inboxUrl: string; /** Existing worker preparation classification and original thrown value. */
324
+ readonly failure: Extract<OutboundWorkerReport["result"], {
325
+ type: "preparation-failed";
326
+ }>["failure"];
327
+ };
328
+ /**
329
+ * A child outbox task submitted by a fanout worker.
330
+ * @since 2.5.0
331
+ */
332
+ type OutboundWorkerChild = {
333
+ readonly action: "enqueue"; /** Actual child task identity and initial Fedify retry counter. */
334
+ readonly queue: NonNullable<OutboundAttemptReport["queue"]>; /** Routes submitted with this child task. */
335
+ readonly inboxUrls: readonly string[]; /** Stable child obligation, independent of its task identity. */
336
+ readonly delivery?: OutboundWorkerReport["delivery"];
337
+ };
338
+ /**
339
+ * An observed worker handoff, scheduling outcome, or local abandonment.
340
+ * @since 2.5.0
341
+ */
342
+ type OutboundWorkerAction = ((OutboundWorkerSchedule | OutboundWorkerChild) & {
343
+ readonly type: "scheduled";
344
+ }) | ((OutboundWorkerSchedule | OutboundWorkerChild) & {
345
+ readonly type: "action-failed";
346
+ readonly acceptance: "unknown";
347
+ readonly error: unknown;
348
+ }) | {
349
+ readonly type: "handoff";
350
+ readonly action: "direct"; /** Actual concrete sender target; entry does not establish HTTP success. */
351
+ readonly inboxUrls: readonly string[]; /** Planned obligation retained across gateway fallback targets. */
352
+ readonly delivery?: OutboundWorkerReport["delivery"];
353
+ } | {
354
+ readonly type: "abandoned";
355
+ readonly reason: "held-ttl-expired" | "permanent-failure" | "retry-policy-declined" | "queue-unavailable"; /** Routes locally abandoned; other routes of the delivery may continue. */
356
+ readonly inboxUrls: readonly string[]; /** Local preparation failures that contributed to this abandonment. */
357
+ readonly preparationFailures?: readonly OutboundWorkerPreparationDiagnostic[];
358
+ } | {
359
+ readonly type: "action-failed";
360
+ readonly action: "drop";
361
+ readonly inboxUrls: readonly string[];
362
+ readonly error: unknown;
363
+ };
364
+ /**
365
+ * One fanout or outbox worker execution after its local processing settles.
366
+ *
367
+ * Emitted after the local worker path and its instrumentation settle. A normal
368
+ * return can include handled send failures or abandonment; completion does not
369
+ * establish HTTP success or backend acknowledgment. Actions retain local route
370
+ * decisions and actual enqueue settlement, separately from attempt outcomes.
371
+ * A native rethrow does not establish a scheduled continuation or future replay.
372
+ * @since 2.5.0
373
+ */
374
+ type OutboundWorkerReport = OutboundSourceMetadata & {
375
+ /** Current outgoing queue role; inbox/custom-task workers are separate. */readonly role: "fanout" | "outbox"; /** Actual task and available Fedify retry counter for this execution. */
376
+ readonly queue: NonNullable<OutboundAttemptReport["queue"]>; /** Concrete sender entries, independent of enabled attempt/exchange callbacks. */
377
+ readonly attemptsStarted: number;
378
+ /**
379
+ * Observed enqueue settlement, concrete direct-entry, and local abandonment.
380
+ * Accepted actions survive later worker failure; rejected batches leave each
381
+ * submitted child uncertain. Empty when no action was observed by settlement.
382
+ */
383
+ readonly actions: readonly OutboundWorkerAction[];
384
+ /**
385
+ * Caught preparation failures retained when later routes succeed or when
386
+ * the worker exits without scheduling a continuation. Omitted when none.
387
+ */
388
+ readonly preparationFailures?: readonly OutboundWorkerPreparationDiagnostic[];
389
+ /**
390
+ * Outbox obligation, with unavailable legacy relationships omitted.
391
+ * Fanout has no single delivery; its child actions carry their own references.
392
+ */
393
+ readonly delivery?: Pick<OutboundAttemptReport, "broadcast" | "deliveryId" | "destinationInbox" | "portableInbox">;
394
+ /**
395
+ * Actual local settlement, separate from individual attempts and queue actions.
396
+ * Preparation failures identify the operation that produced the escaping error.
397
+ */
398
+ readonly result: {
399
+ readonly type: "completed";
400
+ } | {
401
+ readonly type: "failed";
402
+ readonly error: unknown;
403
+ } | {
404
+ readonly type: "preparation-failed";
405
+ readonly failure: {
406
+ readonly kind: "local";
407
+ readonly phase: "key-import" | "activity-parsing" | "context-preparation" | "validation" | "key-resolution" | "signing" | "serialization" | "key-export" | "request-preparation";
408
+ readonly error: unknown;
409
+ }; /** Exact error escaping the worker preparation operation. */
410
+ readonly error: unknown;
411
+ };
412
+ };
413
+ /**
414
+ * Optional observers of outgoing activity processing.
415
+ *
416
+ * Send, original-payload forwarding, and automatic portable replication producer
417
+ * events are observed, including outbox-context operations and fanout
418
+ * continuation handoffs. Concrete attempts and actual fetch exchanges include
419
+ * queue workers, signature challenges, redirects, and alternate gateways.
420
+ * Worker reports describe local fanout/outbox settlement and actual queue
421
+ * actions; they do not establish backend acknowledgment or future execution.
422
+ * Known IDs and provenance survive queue continuations; callback context data
423
+ * belongs to the current execution. Each callback is independently optional.
424
+ * Metadata callbacks are awaited without a default timeout. An enabled body
425
+ * consumer instead uses its explicit bounded capture lifetime.
426
+ * Their errors, including errors logging observer failures, do not replace the
427
+ * send's result.
428
+ * A metadata callback that never settles can delay execution indefinitely. Detached
429
+ * recording must handle its own rejection and shutdown; no persistence is
430
+ * guaranteed. Existing portable forwarding deadlines may return while
431
+ * background preparation, sending, and observer callbacks continue.
432
+ * @template TContextData Application context data.
433
+ * @since 2.5.0
434
+ */
435
+ interface OutboundObservers<TContextData> {
436
+ /**
437
+ * Opt-in immutable privacy projection of concrete sender input on attempt reports.
438
+ * Disabled by default. Unsupported context/data semantics yield no raw fallback;
439
+ * this is not exact wire evidence or proof that redirected requests kept a body.
440
+ */
441
+ readonly senderPayload?: boolean;
442
+ /**
443
+ * Independent opt-in body consumer; explicit byte/time limits are required.
444
+ * Shares one source reader with separately bounded diagnostics, without cloning
445
+ * or whole-body accumulation for the application. Finished reports hold summaries.
446
+ */
447
+ readonly responseBodyCapture?: OutboundResponseBodyCapture<TContextData>;
448
+ /**
449
+ * Opt-in actual request headers for exchange reports; disabled by default.
450
+ * Protected credentials, signatures, and cookies remain redacted even if allowed.
451
+ */
452
+ readonly requestHeaders?: OutboundHeaderPolicy;
453
+ /** Independent opt-in response header policy for exchange reports. */
454
+ readonly responseHeaders?: OutboundHeaderPolicy;
455
+ /**
456
+ * Observes terminal outgoing worker processing with current context/data.
457
+ * Errors are isolated; results do not replace attempt outcomes or establish
458
+ * future backend execution. Actions retain actual accepted/uncertain child
459
+ * handoffs and retry/defer/drop facts, separately from attempt outcomes.
460
+ */
461
+ readonly onWorker?: (context: Context<TContextData>, report: OutboundWorkerReport) => void | Promise<void>;
462
+ /**
463
+ * Observes each actual fetch after response headers or rejection, before
464
+ * subsequent challenge/redirect handling. Non-2xx responses are still response
465
+ * outcomes. Pre-fetch failures emit no exchange. Errors cannot veto transport.
466
+ */
467
+ readonly onHttpExchange?: (context: Context<TContextData>, report: OutboundHttpExchangeReport) => void | Promise<void>;
468
+ /**
469
+ * Observes settlement of concrete sends and forwarding, including workers
470
+ * and pre-fetch failures. Holds, drops, and failures before sender entry emit
471
+ * no attempt. Final HTTP evidence, local diagnostics, and the outward error
472
+ * remain distinct. Callback errors cannot replace the sender's result.
473
+ */
474
+ readonly onAttempt?: (context: Context<TContextData>, report: OutboundAttemptReport) => void | Promise<void>;
475
+ /**
476
+ * Observes invocation entry and the current producer's preparation result.
477
+ * The existing context is passed without loading data for observation.
478
+ * Returning or throwing cannot veto sending. Earlier immediate sends may
479
+ * already be running when preparation finishes; their outcomes are separate.
480
+ */
481
+ readonly onBroadcast?: (context: Context<TContextData>, event: OutboundBroadcastEvent) => void | Promise<void>;
482
+ /**
483
+ * Observes producer-side planning, handoffs, and destination preparation
484
+ * failures, including fanout continuation handoffs. Filtered recipients do
485
+ * not become planned deliveries. Concrete attempts and exchanges have their
486
+ * own callbacks. These events do not report remote success or worker
487
+ * scheduling outcomes.
488
+ */
489
+ readonly onDelivery?: (context: Context<TContextData>, event: OutboundDeliveryEvent) => void | Promise<void>;
490
+ }
491
+ /** URL facts captured independently of a mutable context URL. @since 2.5.0 */
492
+ interface OutboundLocalContext {
493
+ /** Scheme and authority of the context URL. */
494
+ readonly origin: string;
495
+ /** URL hostname with its optional port. */
496
+ readonly host: string;
497
+ /** URL hostname without a port. */
498
+ readonly hostname: string;
499
+ /** Configured canonical web origin, falling back to this context's origin. */
500
+ readonly canonicalOrigin: string;
501
+ }
502
+ /** The original routed HTTP inbox request that caused outgoing work. @since 2.5.0 */
503
+ interface OutboundInboxRequest {
504
+ /** Fresh server-generated identity, independent of Activity and trace IDs. */
505
+ readonly inboxRequestId: string;
506
+ /** Informational logging label; externally controlled and nonunique. */
507
+ readonly requestId: string;
508
+ /** Original request URL facts, preserved separately from current context. */
509
+ readonly context: OutboundLocalContext;
510
+ }
511
+ /** Explicit historical cause of outgoing work. @since 2.5.0 */
512
+ interface OutboundProvenance {
513
+ /**
514
+ * Exact application-owned correlation, up to 1,024 UTF-8 bytes; an empty
515
+ * string is valid. Never a Fedify deduplication key. Each new public call
516
+ * supplies its own value; queued continuations retain it.
517
+ */
518
+ readonly dispatchId?: string;
519
+ /** Absent for independent contexts, programmatic routing, and unknown work. */
520
+ readonly inboxRequest?: OutboundInboxRequest;
521
+ /** Original logging label for request-derived work without an inbox cause. */
522
+ readonly originatingRequestId?: string;
523
+ }
524
+ /**
525
+ * Observation time, optional tracing, original provenance, and current context.
526
+ * @since 2.5.0
527
+ */
528
+ interface OutboundSourceMetadata {
529
+ /** Unix milliseconds at observation; wall clocks do not ensure global ordering. */
530
+ readonly observedAt: number;
531
+ /** Valid IDs of this operation's own span, when one is available. */
532
+ readonly trace?: {
533
+ readonly traceId: string;
534
+ readonly spanId: string;
535
+ };
536
+ /** Immutable historical cause, distinct from this callback's context. */
537
+ readonly provenance: OutboundProvenance;
538
+ /** URL facts of the current callback context. */
539
+ readonly local: OutboundLocalContext;
540
+ }
541
+ //#endregion
12
542
  //#region src/federation/activity-listener.d.ts
13
543
  /**
14
544
  * Options for an inbox listener.
@@ -46,6 +576,13 @@ interface InboxListenerOptions {
46
576
  type InboxJsonValue = null | boolean | number | string | readonly InboxJsonValue[] | {
47
577
  readonly [key: string]: InboxJsonValue;
48
578
  };
579
+ /** Request-signal state at inbox report construction, separate from processing outcome. @since 2.5.0 */
580
+ type InboxRequestCancellation = {
581
+ readonly type: "not-aborted";
582
+ } | {
583
+ readonly type: "aborted"; /** Exact Request.signal reason, including non-Error values. */
584
+ readonly reason: unknown;
585
+ };
49
586
  /**
50
587
  * The final inbox authentication decision.
51
588
  * @since 2.4.0
@@ -122,6 +659,31 @@ type InboxRequestOutcome = {
122
659
  * @since 2.4.0
123
660
  */
124
661
  interface InboxRequestReport {
662
+ /**
663
+ * Fresh server-generated identity for one routed HTTP inbox request,
664
+ * including preparation failures and duplicate deliveries. Preserved on
665
+ * outgoing causal descendants; absent without a known routed source.
666
+ * @since 2.5.0
667
+ */
668
+ readonly inboxRequestId?: string;
669
+ /**
670
+ * Original informational logging label; externally controlled and nonunique,
671
+ * independent of inboxRequestId. Absent without a known routed source.
672
+ * @since 2.5.0
673
+ */
674
+ readonly requestId?: string;
675
+ /**
676
+ * Original local URL facts from the routed request, independent of later contexts.
677
+ * Absent without a known routed source.
678
+ * @since 2.5.0
679
+ */
680
+ readonly local?: OutboundLocalContext;
681
+ /**
682
+ * Request.signal state immediately before report construction. An aborted request
683
+ * does not imply processing stopped or retract confirmed processing/enqueue outcomes.
684
+ * @since 2.5.0
685
+ */
686
+ readonly requestCancellation: InboxRequestCancellation;
125
687
  /** The delivery route, including a portable gateway route. */
126
688
  readonly inbox: {
127
689
  readonly kind: "personal" | "shared" | "portable";
@@ -190,6 +752,414 @@ declare function digest(uris: Iterable<string | URL>): Promise<Uint8Array>;
190
752
  */
191
753
  declare function buildCollectionSynchronizationHeader(collectionId: string | URL, actorIds: Iterable<string | URL>): Promise<string>;
192
754
  //#endregion
755
+ //#region src/federation/inbox-authentication.d.ts
756
+ /**
757
+ * A party that Fedify authenticated for an incoming activity, independently
758
+ * of the activity's declared `actor`.
759
+ * @since 2.5.0
760
+ */
761
+ interface InboxPrincipal {
762
+ /**
763
+ * What {@link InboxPrincipal.id} is:
764
+ *
765
+ * - `"actor"`: an actor whose ownership of the verifying key Fedify
766
+ * confirmed with the actor's own document.
767
+ * - `"portable-authority"`: a DID whose verification method made a valid
768
+ * Object Integrity Proof. It authenticates the [FEP-ef61] portable
769
+ * actors of that DID, and nothing else.
770
+ *
771
+ * [FEP-ef61]: https://w3id.org/fep/ef61
772
+ */
773
+ readonly kind: "actor" | "portable-authority";
774
+ /** The actor ID, or the DID if {@link kind} is `"portable-authority"`. */
775
+ readonly id: URL;
776
+ /**
777
+ * How the principal was authenticated:
778
+ *
779
+ * - `"http-signatures"`: it signed the HTTP request.
780
+ * - `"linked-data-signatures"`: it made the Linked Data Signature of the
781
+ * activity.
782
+ * - `"object-integrity-proofs"`: it made an Object Integrity Proof of the
783
+ * activity.
784
+ * - `"origin"`: the activity was fetched again from the actor's own web
785
+ * origin, which happens only for `Context.routeActivity()`.
786
+ */
787
+ readonly mechanism: "http-signatures" | "linked-data-signatures" | "object-integrity-proofs" | "origin";
788
+ /** The key or verification method that authenticated the principal. */
789
+ readonly keyId: URL | null;
790
+ }
791
+ /**
792
+ * Who Fedify authenticated for an incoming activity, and the interpretation of
793
+ * the activity it authenticated. Inbox listeners receive it as
794
+ * {@link InboxContext.authentication}.
795
+ *
796
+ * Authenticated principals are independent of the activity's declared
797
+ * `actor`: an activity forwarded by a relay is signed by the relay in its
798
+ * HTTP request, but its document may be signed by its author, and some
799
+ * activities, such as [FEP-7aa9] `FeatureRequest`s sent by Mastodon, declare
800
+ * no `actor` at all. A valid signature alone does not authorize anything;
801
+ * use {@link isActorAuthenticated} to check whether a specific actor was
802
+ * authenticated.
803
+ *
804
+ * [FEP-7aa9]: https://w3id.org/fep/7aa9
805
+ * @since 2.5.0
806
+ */
807
+ interface InboxAuthenticationEvidence {
808
+ /**
809
+ * Whether signatures were verified at all. It is `false` only when
810
+ * signature verification is skipped, or when there is no evidence, e.g.,
811
+ * for an activity queued by an older version of Fedify. In that case all
812
+ * the principal lists are empty.
813
+ */
814
+ readonly verified: boolean;
815
+ /**
816
+ * The principals that signed the HTTP request, i.e., the server that
817
+ * delivered the activity, which may have forwarded it. It is empty when
818
+ * the activity document was authenticated by its own signatures, so that
819
+ * HTTP Signatures did not need to be verified.
820
+ */
821
+ readonly requestSigners: readonly InboxPrincipal[];
822
+ /**
823
+ * The principals that made the verified Linked Data Signature or Object
824
+ * Integrity Proofs of the activity document. All of them are listed when
825
+ * there are several proofs.
826
+ */
827
+ readonly documentSigners: readonly InboxPrincipal[];
828
+ /**
829
+ * The actors authenticated because `Context.routeActivity()` fetched the
830
+ * activity again from their own web origin. It is always empty for
831
+ * activities delivered to an inbox.
832
+ */
833
+ readonly originAuthenticated: readonly InboxPrincipal[];
834
+ /**
835
+ * The activity as Fedify interpreted it while authenticating it, in
836
+ * expanded JSON-LD. Typed vocabulary objects omit some values, such as
837
+ * blank node references and values they cannot decode, so authorization
838
+ * that has to count every value of a property, e.g., that a property has
839
+ * exactly one value, should inspect this instead. It is deeply frozen.
840
+ * It is empty when there is no evidence.
841
+ */
842
+ readonly expandedActivity: readonly Record<string, unknown>[];
843
+ /**
844
+ * The base URL the activity was decoded with, e.g., the URL an activity
845
+ * fetched by `Context.routeActivity()` was finally retrieved from, or
846
+ * `null` if none was given. Relative IRIs left in
847
+ * {@link expandedActivity} resolve against it, or against the activity's
848
+ * own `@id` if it is `null`, as the decoder resolves them.
849
+ */
850
+ readonly baseUrl: URL | null;
851
+ }
852
+ /**
853
+ * Options for {@link isActorAuthenticated}.
854
+ * @since 2.5.0
855
+ */
856
+ interface IsActorAuthenticatedOptions {
857
+ /**
858
+ * Which principals to consider:
859
+ *
860
+ * - `"effective"` (default): the document signers if there are any,
861
+ * otherwise the request signers if there are any, otherwise the actors
862
+ * authenticated by their origin.
863
+ * - `"document"`: the document signers only.
864
+ */
865
+ readonly scope?: "effective" | "document";
866
+ }
867
+ /**
868
+ * Checks whether the given evidence authenticates the given actor.
869
+ *
870
+ * Actor IDs are compared exactly, except that [FEP-ef61] portable IDs and
871
+ * compatible identifiers are compared by their canonical portable IDs, and
872
+ * a portable authority (a DID) authenticates the portable actors of that DID.
873
+ *
874
+ * @example
875
+ * ``` typescript
876
+ * federation
877
+ * .setInboxListeners("/users/{identifier}/inbox", "/inbox")
878
+ * .on(Follow, async (ctx, follow) => {
879
+ * if (follow.actorId == null) return;
880
+ * // The actor is always authenticated for activities that declare one,
881
+ * // but the same check works for any actor an activity names:
882
+ * isActorAuthenticated(ctx.authentication, follow.actorId);
883
+ * });
884
+ * ```
885
+ *
886
+ * [FEP-ef61]: https://w3id.org/fep/ef61
887
+ * @param evidence The authentication evidence, usually
888
+ * {@link InboxContext.authentication}.
889
+ * @param actorId The actor ID to check.
890
+ * @param options Options.
891
+ * @returns `true` if the actor is authenticated.
892
+ * @since 2.5.0
893
+ */
894
+ declare function isActorAuthenticated(evidence: InboxAuthenticationEvidence, actorId: URL, options?: IsActorAuthenticatedOptions): boolean;
895
+ /**
896
+ * A {@link InboxPrincipal} as stored in a queued message.
897
+ * The wire spellings remain independent of the public API spellings.
898
+ * @internal
899
+ */
900
+ interface SerializedInboxPrincipal {
901
+ readonly kind: "actor" | "portableAuthority";
902
+ readonly id: string;
903
+ readonly mechanism: "httpSignatures" | "linkedDataSignatures" | "objectIntegrityProofs" | "origin";
904
+ readonly keyId: string | null;
905
+ }
906
+ /**
907
+ * The principals of an {@link InboxAuthenticationEvidence} as stored in
908
+ * a queued message. The expanded activity and the base URL are restored
909
+ * from the queued verified view instead.
910
+ * @internal
911
+ */
912
+ interface SerializedInboxAuthentication {
913
+ readonly verified: boolean;
914
+ readonly requestSigners: readonly SerializedInboxPrincipal[];
915
+ readonly documentSigners: readonly SerializedInboxPrincipal[];
916
+ readonly originAuthenticated: readonly SerializedInboxPrincipal[];
917
+ }
918
+ //#endregion
919
+ //#region src/federation/inbox-view.d.ts
920
+ /**
921
+ * A {@link ContextSnapshotEntry} as stored in a queued message. A response
922
+ * that equals the one Fedify bundles for the URL is stored as a reference to
923
+ * the bundled context, along with its hash for diagnostics.
924
+ * @internal
925
+ */
926
+ type SerializedContextSnapshotEntry = {
927
+ readonly policy: EffectiveContextLoaderPolicy;
928
+ readonly url: string; /** The SHA-256 of the canonical JSON of the whole response. */
929
+ readonly sha256: string;
930
+ } & ({
931
+ readonly bundled: true;
932
+ } | {
933
+ readonly response: RemoteDocument;
934
+ });
935
+ /**
936
+ * The interpretation of an incoming activity that Fedify authenticated, as
937
+ * stored in a queued message. The document itself is not copied; it is
938
+ * a field of the message.
939
+ * @internal
940
+ */
941
+ interface SerializedVerifiedActivityView {
942
+ /** The format version. */
943
+ readonly version: 1;
944
+ /** The context loader policy the activity was parsed with. */
945
+ readonly policy: EffectiveContextLoaderPolicy;
946
+ /**
947
+ * Which document the activity was parsed from, with its Linked Data
948
+ * Signature detached: the `activity` of the message, i.e., the payload as
949
+ * received, or its `normalizedActivity`, i.e., the payload compacted for
950
+ * Linked Data Signatures.
951
+ */
952
+ readonly source: "activity" | "normalizedActivity";
953
+ /** The `baseUrl` option the activity was parsed with. */
954
+ readonly baseUrl: string | null;
955
+ /** The context responses the interpretation used. */
956
+ readonly contexts: readonly SerializedContextSnapshotEntry[];
957
+ /** The digest of the expanded activity. */
958
+ readonly expandedDigest: string;
959
+ }
960
+ //#endregion
961
+ //#region src/federation/queue.d.ts
962
+ /** Serializable grouping facts, separate from queue task identity. @internal */
963
+ interface SerializedOutboundDelivery {
964
+ readonly broadcast: {
965
+ readonly type: "known";
966
+ readonly id: string;
967
+ } | {
968
+ readonly type: "unknown";
969
+ };
970
+ readonly deliveryId: string;
971
+ readonly destinationInbox: string;
972
+ readonly portableInbox?: string;
973
+ }
974
+ interface SenderKeyJwkPair {
975
+ readonly keyId: string;
976
+ readonly privateKey: JsonWebKey;
977
+ }
978
+ /**
979
+ * A message that represents a task to be processed by the background worker.
980
+ * The concrete type of the message depends on the `type` property.
981
+ *
982
+ * Please do not depend on the concrete types of the messages, as they may
983
+ * change in the future. You should treat the `Message` type as an opaque
984
+ * type.
985
+ * @since 1.6.0
986
+ */
987
+ type Message = FanoutMessage | OutboxMessage | InboxMessage | TaskMessage;
988
+ interface FanoutMessage {
989
+ /** Historical source facts; absent on legacy/independent messages. */
990
+ readonly provenance?: OutboundProvenance;
991
+ readonly type: "fanout";
992
+ readonly id: ReturnType<typeof crypto.randomUUID>;
993
+ readonly baseUrl: string;
994
+ readonly keys: readonly SenderKeyJwkPair[];
995
+ readonly inboxes: Readonly<Record<string, {
996
+ readonly actorIds: readonly string[];
997
+ readonly sharedInbox: boolean; /** Optional for continuations created before outbound observation. */
998
+ readonly outbound?: SerializedOutboundDelivery;
999
+ /**
1000
+ * The canonical form of the FEP-ef61 portable inbox that the inbox
1001
+ * stands for.
1002
+ * @internal
1003
+ */
1004
+ readonly portableInbox?: string;
1005
+ /**
1006
+ * The compatible identifiers of the portable inbox on each gateway to
1007
+ * deliver through, in order.
1008
+ * @internal
1009
+ */
1010
+ readonly gatewayInboxes?: readonly string[];
1011
+ }>>;
1012
+ readonly activity: unknown;
1013
+ readonly activityId?: string;
1014
+ readonly activityType: string;
1015
+ readonly collectionSync?: string;
1016
+ readonly orderingKey?: string;
1017
+ /**
1018
+ * Whether to apply outgoing JSON-LD wire-format normalization to queued
1019
+ * activities that already carry Object Integrity Proofs.
1020
+ *
1021
+ * `true` is used for proofs Fedify created before fanout, or when callers
1022
+ * explicitly request normalization for locally pre-signed activities.
1023
+ * `false`/`undefined` preserves existing proofs as-is.
1024
+ */
1025
+ readonly normalizeExistingProofs?: boolean;
1026
+ readonly traceContext: Readonly<Record<string, string>>;
1027
+ }
1028
+ interface OutboxMessage {
1029
+ /** Historical source facts; absent on legacy/independent messages. */
1030
+ readonly provenance?: OutboundProvenance;
1031
+ readonly type: "outbox";
1032
+ readonly id: ReturnType<typeof crypto.randomUUID>;
1033
+ /** Original delivery identity; absent on legacy/unobserved producers. */
1034
+ readonly outbound?: SerializedOutboundDelivery;
1035
+ readonly baseUrl: string;
1036
+ readonly keys: readonly SenderKeyJwkPair[];
1037
+ readonly activity: unknown;
1038
+ readonly activityId?: string;
1039
+ readonly activityType: string;
1040
+ readonly inbox: string;
1041
+ readonly sharedInbox: boolean;
1042
+ readonly actorIds?: readonly string[];
1043
+ readonly started: string;
1044
+ readonly attempt: number;
1045
+ readonly headers: Readonly<Record<string, string>>;
1046
+ readonly orderingKey?: string;
1047
+ /**
1048
+ * Whether this message is currently held by the outbound circuit breaker.
1049
+ * @internal
1050
+ */
1051
+ readonly circuitHeld?: true;
1052
+ /**
1053
+ * When Fedify first held this message because the remote host circuit was
1054
+ * open.
1055
+ * @internal
1056
+ */
1057
+ readonly circuitHeldSince?: string;
1058
+ /**
1059
+ * The canonical form of the FEP-ef61 portable inbox that {@link inbox}, its
1060
+ * compatible identifier on a gateway, stands for.
1061
+ * @internal
1062
+ */
1063
+ readonly portableInbox?: string;
1064
+ /**
1065
+ * The compatible identifiers of the FEP-ef61 portable inbox on each gateway
1066
+ * that is still to be tried, in order. {@link inbox} is the first one.
1067
+ * If there is more than one, the activity is delivered through the first
1068
+ * gateway that accepts it.
1069
+ * @internal
1070
+ */
1071
+ readonly gatewayInboxes?: readonly string[];
1072
+ /**
1073
+ * The times before which each of {@link gatewayInboxes} must not be tried
1074
+ * again, as their responses' `Retry-After` headers requested, keyed by
1075
+ * the inbox URL, in ISO 8601.
1076
+ * @internal
1077
+ */
1078
+ readonly gatewayNotBefore?: Readonly<Record<string, string>>;
1079
+ readonly traceContext: Readonly<Record<string, string>>;
1080
+ }
1081
+ /**
1082
+ * A message that carries a custom background task. Every field is
1083
+ * a string, number, or plain record so that the message survives both
1084
+ * JSON serialization and structured clone on every queue backend.
1085
+ * @since 2.4.0
1086
+ */
1087
+ interface TaskMessage {
1088
+ readonly type: "task";
1089
+ readonly id: ReturnType<typeof crypto.randomUUID>;
1090
+ readonly baseUrl: string;
1091
+ readonly taskName: string;
1092
+ /** devalue-encoded task data; vocab objects bridged to expanded JSON-LD. */
1093
+ readonly data: string;
1094
+ readonly started: string;
1095
+ readonly attempt: number;
1096
+ readonly orderingKey?: string;
1097
+ readonly traceContext: Readonly<Record<string, string>>;
1098
+ }
1099
+ interface InboxMessage {
1100
+ /** Historical source facts; absent on legacy/independent messages. */
1101
+ readonly provenance?: OutboundProvenance;
1102
+ readonly type: "inbox";
1103
+ readonly id: ReturnType<typeof crypto.randomUUID>;
1104
+ readonly baseUrl: string;
1105
+ readonly activity: unknown;
1106
+ /**
1107
+ * The normalized JSON-LD representation of a signed inbox activity that
1108
+ * Fedify already compacted successfully while accepting the request. Queue
1109
+ * workers can reuse this producer-side parse cache under stricter loader or
1110
+ * network constraints without changing the raw payload preserved for
1111
+ * forwarding.
1112
+ *
1113
+ * This may exist even when {@link ldSignatureVerified} is `false`, because
1114
+ * fallback-authenticated traffic and already-queued backlog items can still
1115
+ * depend on the cached normalized form to avoid re-fetching remote custom
1116
+ * contexts during worker processing.
1117
+ *
1118
+ * This is optional for backward compatibility with messages that were
1119
+ * queued by older Fedify versions or that were already in a queue before
1120
+ * upgrading.
1121
+ *
1122
+ * Fedify keeps this on the queued message itself instead of an external
1123
+ * sidecar because generic queue backends do not provide reliable lifecycle
1124
+ * guarantees for auxiliary storage across retries and redeliveries.
1125
+ *
1126
+ * @internal
1127
+ */
1128
+ readonly normalizedActivity?: unknown;
1129
+ /**
1130
+ * Whether the producer actually verified the Linked Data Signature before
1131
+ * queueing this message. This lets workers distinguish verified LDS replay
1132
+ * from other authenticated inbox traffic that merely happened to include a
1133
+ * signature block. This provenance marker is separate from the optional
1134
+ * normalizedActivity parse cache.
1135
+ *
1136
+ * `undefined` preserves backward compatibility with older queued messages
1137
+ * that predate this marker.
1138
+ *
1139
+ * @internal
1140
+ */
1141
+ readonly ldSignatureVerified?: boolean;
1142
+ /**
1143
+ * The interpretation of the activity that Fedify authenticated, i.e., which
1144
+ * document it was parsed from and every JSON-LD context response the parse
1145
+ * used, so that the worker and its retries dispatch the very same
1146
+ * interpretation without loading contexts again. Absent in messages queued
1147
+ * by older Fedify versions.
1148
+ * @internal
1149
+ */
1150
+ readonly verifiedView?: SerializedVerifiedActivityView;
1151
+ /**
1152
+ * Who Fedify authenticated for the activity. Absent in messages queued by
1153
+ * older Fedify versions.
1154
+ * @internal
1155
+ */
1156
+ readonly authentication?: SerializedInboxAuthentication;
1157
+ readonly started: string;
1158
+ readonly attempt: number;
1159
+ readonly identifier: string | null;
1160
+ readonly traceContext: Readonly<Record<string, string>>;
1161
+ }
1162
+ //#endregion
193
1163
  //#region src/federation/send.d.ts
194
1164
  /**
195
1165
  * A key pair for an actor who sends an activity.
@@ -224,7 +1194,9 @@ declare class SendActivityError extends Error {
224
1194
  * The response body from the inbox, if any. Note that this may be
225
1195
  * truncated to a maximum of 1 KiB to prevent excessive memory consumption
226
1196
  * when remote servers return large error pages (e.g., Cloudflare error pages).
227
- * If truncated, the string will end with `"… (truncated)"`.
1197
+ * If truncated, the string will end with `"… (truncated)"`. Diagnostic reading
1198
+ * has a separate one-second budget; a deadline retains any partial preview and
1199
+ * appends `"… (timed out)"`. This is independent of application body capture.
228
1200
  */
229
1201
  readonly responseBody: string;
230
1202
  /**
@@ -1084,269 +2056,63 @@ declare class InProcessMessageQueue implements MessageQueue {
1084
2056
  * are not running in separate threads or processes. They are running in the
1085
2057
  * same process, but are scheduled to run in parallel. Hence, this is useful
1086
2058
  * for I/O-bound tasks, but not for CPU-bound tasks, which is okay for Fedify's
1087
- * workloads.
1088
- *
1089
- * When using `ParallelMessageQueue`, the ordering guarantee is preserved
1090
- * *only if* the underlying queue implementation delivers messages in a wrapper
1091
- * format that includes the `__fedify_ordering_key__` property. Currently,
1092
- * only `DenoKvMessageQueue` and `WorkersMessageQueue` use this format.
1093
- * For other queue implementations (e.g., `InProcessMessageQueue`,
1094
- * `RedisMessageQueue`, `PostgresMessageQueue`, `SqliteMessageQueue`,
1095
- * `AmqpMessageQueue`), the ordering key cannot be detected by
1096
- * `ParallelMessageQueue`, so ordering guarantees are handled by those
1097
- * implementations directly rather than at the `ParallelMessageQueue` level.
1098
- *
1099
- * Messages with the same ordering key will never be processed concurrently
1100
- * by different workers, ensuring sequential processing within each key.
1101
- * Messages with different ordering keys (or no ordering key) can still be
1102
- * processed in parallel.
1103
- *
1104
- * @since 1.0.0
1105
- */
1106
- declare class ParallelMessageQueue implements MessageQueue {
1107
- #private;
1108
- readonly queue: MessageQueue;
1109
- readonly workers: number;
1110
- /**
1111
- * Inherits the native retry capability from the wrapped queue.
1112
- * @since 1.7.0
1113
- */
1114
- readonly nativeRetrial?: boolean;
1115
- /**
1116
- * Inherits the native deduplication capability from the wrapped queue.
1117
- * @since 2.4.0
1118
- */
1119
- readonly nativeDeduplication?: boolean;
1120
- /**
1121
- * Inherits the atomic batch capability from the wrapped queue.
1122
- * @since 2.4.0
1123
- */
1124
- readonly atomicEnqueueMany?: boolean;
1125
- /**
1126
- * Inherits the message size limit from the wrapped queue.
1127
- * @since 2.5.0
1128
- */
1129
- readonly maxMessageSize?: number;
1130
- readonly getDepth?: () => Promise<MessageQueueDepth>;
1131
- /**
1132
- * Constructs a new {@link ParallelMessageQueue} with the given queue and
1133
- * number of workers.
1134
- * @param queue The message queue to use under the hood. Note that
1135
- * {@link ParallelMessageQueue} cannot be nested.
1136
- * @param workers The number of workers to process messages in parallel.
1137
- * @throws {TypeError} If the given queue is an instance of
1138
- * {@link ParallelMessageQueue}.
1139
- */
1140
- constructor(queue: MessageQueue, workers: number);
1141
- enqueue(message: any, options?: MessageQueueEnqueueOptions): Promise<void>;
1142
- enqueueMany(messages: readonly any[], options?: MessageQueueEnqueueOptions): Promise<void>;
1143
- listen(handler: (message: any) => Promise<void> | void, options?: MessageQueueListenOptions): Promise<void>;
1144
- }
1145
- //#endregion
1146
- //#region src/federation/inbox-authentication.d.ts
1147
- /**
1148
- * A party that Fedify authenticated for an incoming activity, independently
1149
- * of the activity's declared `actor`.
1150
- * @since 2.5.0
1151
- */
1152
- interface InboxPrincipal {
1153
- /**
1154
- * What {@link InboxPrincipal.id} is:
1155
- *
1156
- * - `"actor"`: an actor whose ownership of the verifying key Fedify
1157
- * confirmed with the actor's own document.
1158
- * - `"portable-authority"`: a DID whose verification method made a valid
1159
- * Object Integrity Proof. It authenticates the [FEP-ef61] portable
1160
- * actors of that DID, and nothing else.
1161
- *
1162
- * [FEP-ef61]: https://w3id.org/fep/ef61
1163
- */
1164
- readonly kind: "actor" | "portable-authority";
1165
- /** The actor ID, or the DID if {@link kind} is `"portable-authority"`. */
1166
- readonly id: URL;
1167
- /**
1168
- * How the principal was authenticated:
1169
- *
1170
- * - `"http-signatures"`: it signed the HTTP request.
1171
- * - `"linked-data-signatures"`: it made the Linked Data Signature of the
1172
- * activity.
1173
- * - `"object-integrity-proofs"`: it made an Object Integrity Proof of the
1174
- * activity.
1175
- * - `"origin"`: the activity was fetched again from the actor's own web
1176
- * origin, which happens only for `Context.routeActivity()`.
1177
- */
1178
- readonly mechanism: "http-signatures" | "linked-data-signatures" | "object-integrity-proofs" | "origin";
1179
- /** The key or verification method that authenticated the principal. */
1180
- readonly keyId: URL | null;
1181
- }
1182
- /**
1183
- * Who Fedify authenticated for an incoming activity, and the interpretation of
1184
- * the activity it authenticated. Inbox listeners receive it as
1185
- * {@link InboxContext.authentication}.
1186
- *
1187
- * Authenticated principals are independent of the activity's declared
1188
- * `actor`: an activity forwarded by a relay is signed by the relay in its
1189
- * HTTP request, but its document may be signed by its author, and some
1190
- * activities, such as [FEP-7aa9] `FeatureRequest`s sent by Mastodon, declare
1191
- * no `actor` at all. A valid signature alone does not authorize anything;
1192
- * use {@link isActorAuthenticated} to check whether a specific actor was
1193
- * authenticated.
1194
- *
1195
- * [FEP-7aa9]: https://w3id.org/fep/7aa9
1196
- * @since 2.5.0
1197
- */
1198
- interface InboxAuthenticationEvidence {
1199
- /**
1200
- * Whether signatures were verified at all. It is `false` only when
1201
- * signature verification is skipped, or when there is no evidence, e.g.,
1202
- * for an activity queued by an older version of Fedify. In that case all
1203
- * the principal lists are empty.
1204
- */
1205
- readonly verified: boolean;
1206
- /**
1207
- * The principals that signed the HTTP request, i.e., the server that
1208
- * delivered the activity, which may have forwarded it. It is empty when
1209
- * the activity document was authenticated by its own signatures, so that
1210
- * HTTP Signatures did not need to be verified.
1211
- */
1212
- readonly requestSigners: readonly InboxPrincipal[];
1213
- /**
1214
- * The principals that made the verified Linked Data Signature or Object
1215
- * Integrity Proofs of the activity document. All of them are listed when
1216
- * there are several proofs.
1217
- */
1218
- readonly documentSigners: readonly InboxPrincipal[];
1219
- /**
1220
- * The actors authenticated because `Context.routeActivity()` fetched the
1221
- * activity again from their own web origin. It is always empty for
1222
- * activities delivered to an inbox.
1223
- */
1224
- readonly originAuthenticated: readonly InboxPrincipal[];
1225
- /**
1226
- * The activity as Fedify interpreted it while authenticating it, in
1227
- * expanded JSON-LD. Typed vocabulary objects omit some values, such as
1228
- * blank node references and values they cannot decode, so authorization
1229
- * that has to count every value of a property, e.g., that a property has
1230
- * exactly one value, should inspect this instead. It is deeply frozen.
1231
- * It is empty when there is no evidence.
1232
- */
1233
- readonly expandedActivity: readonly Record<string, unknown>[];
1234
- /**
1235
- * The base URL the activity was decoded with, e.g., the URL an activity
1236
- * fetched by `Context.routeActivity()` was finally retrieved from, or
1237
- * `null` if none was given. Relative IRIs left in
1238
- * {@link expandedActivity} resolve against it, or against the activity's
1239
- * own `@id` if it is `null`, as the decoder resolves them.
1240
- */
1241
- readonly baseUrl: URL | null;
1242
- }
1243
- /**
1244
- * Options for {@link isActorAuthenticated}.
1245
- * @since 2.5.0
1246
- */
1247
- interface IsActorAuthenticatedOptions {
1248
- /**
1249
- * Which principals to consider:
1250
- *
1251
- * - `"effective"` (default): the document signers if there are any,
1252
- * otherwise the request signers if there are any, otherwise the actors
1253
- * authenticated by their origin.
1254
- * - `"document"`: the document signers only.
1255
- */
1256
- readonly scope?: "effective" | "document";
1257
- }
1258
- /**
1259
- * Checks whether the given evidence authenticates the given actor.
1260
- *
1261
- * Actor IDs are compared exactly, except that [FEP-ef61] portable IDs and
1262
- * compatible identifiers are compared by their canonical portable IDs, and
1263
- * a portable authority (a DID) authenticates the portable actors of that DID.
1264
- *
1265
- * @example
1266
- * ``` typescript
1267
- * federation
1268
- * .setInboxListeners("/users/{identifier}/inbox", "/inbox")
1269
- * .on(Follow, async (ctx, follow) => {
1270
- * if (follow.actorId == null) return;
1271
- * // The actor is always authenticated for activities that declare one,
1272
- * // but the same check works for any actor an activity names:
1273
- * isActorAuthenticated(ctx.authentication, follow.actorId);
1274
- * });
1275
- * ```
1276
- *
1277
- * [FEP-ef61]: https://w3id.org/fep/ef61
1278
- * @param evidence The authentication evidence, usually
1279
- * {@link InboxContext.authentication}.
1280
- * @param actorId The actor ID to check.
1281
- * @param options Options.
1282
- * @returns `true` if the actor is authenticated.
1283
- * @since 2.5.0
1284
- */
1285
- declare function isActorAuthenticated(evidence: InboxAuthenticationEvidence, actorId: URL, options?: IsActorAuthenticatedOptions): boolean;
1286
- /**
1287
- * A {@link InboxPrincipal} as stored in a queued message.
1288
- * The wire spellings remain independent of the public API spellings.
1289
- * @internal
1290
- */
1291
- interface SerializedInboxPrincipal {
1292
- readonly kind: "actor" | "portableAuthority";
1293
- readonly id: string;
1294
- readonly mechanism: "httpSignatures" | "linkedDataSignatures" | "objectIntegrityProofs" | "origin";
1295
- readonly keyId: string | null;
1296
- }
1297
- /**
1298
- * The principals of an {@link InboxAuthenticationEvidence} as stored in
1299
- * a queued message. The expanded activity and the base URL are restored
1300
- * from the queued verified view instead.
1301
- * @internal
1302
- */
1303
- interface SerializedInboxAuthentication {
1304
- readonly verified: boolean;
1305
- readonly requestSigners: readonly SerializedInboxPrincipal[];
1306
- readonly documentSigners: readonly SerializedInboxPrincipal[];
1307
- readonly originAuthenticated: readonly SerializedInboxPrincipal[];
1308
- }
1309
- //#endregion
1310
- //#region src/federation/inbox-view.d.ts
1311
- /**
1312
- * A {@link ContextSnapshotEntry} as stored in a queued message. A response
1313
- * that equals the one Fedify bundles for the URL is stored as a reference to
1314
- * the bundled context, along with its hash for diagnostics.
1315
- * @internal
1316
- */
1317
- type SerializedContextSnapshotEntry = {
1318
- readonly policy: EffectiveContextLoaderPolicy;
1319
- readonly url: string; /** The SHA-256 of the canonical JSON of the whole response. */
1320
- readonly sha256: string;
1321
- } & ({
1322
- readonly bundled: true;
1323
- } | {
1324
- readonly response: RemoteDocument;
1325
- });
1326
- /**
1327
- * The interpretation of an incoming activity that Fedify authenticated, as
1328
- * stored in a queued message. The document itself is not copied; it is
1329
- * a field of the message.
1330
- * @internal
2059
+ * workloads.
2060
+ *
2061
+ * When using `ParallelMessageQueue`, the ordering guarantee is preserved
2062
+ * *only if* the underlying queue implementation delivers messages in a wrapper
2063
+ * format that includes the `__fedify_ordering_key__` property. Currently,
2064
+ * only `DenoKvMessageQueue` and `WorkersMessageQueue` use this format.
2065
+ * For other queue implementations (e.g., `InProcessMessageQueue`,
2066
+ * `RedisMessageQueue`, `PostgresMessageQueue`, `SqliteMessageQueue`,
2067
+ * `AmqpMessageQueue`), the ordering key cannot be detected by
2068
+ * `ParallelMessageQueue`, so ordering guarantees are handled by those
2069
+ * implementations directly rather than at the `ParallelMessageQueue` level.
2070
+ *
2071
+ * Messages with the same ordering key will never be processed concurrently
2072
+ * by different workers, ensuring sequential processing within each key.
2073
+ * Messages with different ordering keys (or no ordering key) can still be
2074
+ * processed in parallel.
2075
+ *
2076
+ * @since 1.0.0
1331
2077
  */
1332
- interface SerializedVerifiedActivityView {
1333
- /** The format version. */
1334
- readonly version: 1;
1335
- /** The context loader policy the activity was parsed with. */
1336
- readonly policy: EffectiveContextLoaderPolicy;
2078
+ declare class ParallelMessageQueue implements MessageQueue {
2079
+ #private;
2080
+ readonly queue: MessageQueue;
2081
+ readonly workers: number;
1337
2082
  /**
1338
- * Which document the activity was parsed from, with its Linked Data
1339
- * Signature detached: the `activity` of the message, i.e., the payload as
1340
- * received, or its `normalizedActivity`, i.e., the payload compacted for
1341
- * Linked Data Signatures.
2083
+ * Inherits the native retry capability from the wrapped queue.
2084
+ * @since 1.7.0
1342
2085
  */
1343
- readonly source: "activity" | "normalizedActivity";
1344
- /** The `baseUrl` option the activity was parsed with. */
1345
- readonly baseUrl: string | null;
1346
- /** The context responses the interpretation used. */
1347
- readonly contexts: readonly SerializedContextSnapshotEntry[];
1348
- /** The digest of the expanded activity. */
1349
- readonly expandedDigest: string;
2086
+ readonly nativeRetrial?: boolean;
2087
+ /**
2088
+ * Inherits the native deduplication capability from the wrapped queue.
2089
+ * @since 2.4.0
2090
+ */
2091
+ readonly nativeDeduplication?: boolean;
2092
+ /**
2093
+ * Inherits the atomic batch capability from the wrapped queue.
2094
+ * @since 2.4.0
2095
+ */
2096
+ readonly atomicEnqueueMany?: boolean;
2097
+ /**
2098
+ * Inherits the message size limit from the wrapped queue.
2099
+ * @since 2.5.0
2100
+ */
2101
+ readonly maxMessageSize?: number;
2102
+ readonly getDepth?: () => Promise<MessageQueueDepth>;
2103
+ /**
2104
+ * Constructs a new {@link ParallelMessageQueue} with the given queue and
2105
+ * number of workers.
2106
+ * @param queue The message queue to use under the hood. Note that
2107
+ * {@link ParallelMessageQueue} cannot be nested.
2108
+ * @param workers The number of workers to process messages in parallel.
2109
+ * @throws {TypeError} If the given queue is an instance of
2110
+ * {@link ParallelMessageQueue}.
2111
+ */
2112
+ constructor(queue: MessageQueue, workers: number);
2113
+ enqueue(message: any, options?: MessageQueueEnqueueOptions): Promise<void>;
2114
+ enqueueMany(messages: readonly any[], options?: MessageQueueEnqueueOptions): Promise<void>;
2115
+ listen(handler: (message: any) => Promise<void> | void, options?: MessageQueueListenOptions): Promise<void>;
1350
2116
  }
1351
2117
  //#endregion
1352
2118
  //#region src/federation/handler.d.ts
@@ -1603,187 +2369,6 @@ interface TaskEnqueueOptions {
1603
2369
  */
1604
2370
  declare function createFederationBuilder<TContextData>(): FederationBuilder<TContextData>;
1605
2371
  //#endregion
1606
- //#region src/federation/queue.d.ts
1607
- interface SenderKeyJwkPair {
1608
- readonly keyId: string;
1609
- readonly privateKey: JsonWebKey;
1610
- }
1611
- /**
1612
- * A message that represents a task to be processed by the background worker.
1613
- * The concrete type of the message depends on the `type` property.
1614
- *
1615
- * Please do not depend on the concrete types of the messages, as they may
1616
- * change in the future. You should treat the `Message` type as an opaque
1617
- * type.
1618
- * @since 1.6.0
1619
- */
1620
- type Message = FanoutMessage | OutboxMessage | InboxMessage | TaskMessage;
1621
- interface FanoutMessage {
1622
- readonly type: "fanout";
1623
- readonly id: ReturnType<typeof crypto.randomUUID>;
1624
- readonly baseUrl: string;
1625
- readonly keys: readonly SenderKeyJwkPair[];
1626
- readonly inboxes: Readonly<Record<string, {
1627
- readonly actorIds: readonly string[];
1628
- readonly sharedInbox: boolean;
1629
- /**
1630
- * The canonical form of the FEP-ef61 portable inbox that the inbox
1631
- * stands for.
1632
- * @internal
1633
- */
1634
- readonly portableInbox?: string;
1635
- /**
1636
- * The compatible identifiers of the portable inbox on each gateway to
1637
- * deliver through, in order.
1638
- * @internal
1639
- */
1640
- readonly gatewayInboxes?: readonly string[];
1641
- }>>;
1642
- readonly activity: unknown;
1643
- readonly activityId?: string;
1644
- readonly activityType: string;
1645
- readonly collectionSync?: string;
1646
- readonly orderingKey?: string;
1647
- /**
1648
- * Whether to apply outgoing JSON-LD wire-format normalization to queued
1649
- * activities that already carry Object Integrity Proofs.
1650
- *
1651
- * `true` is used for proofs Fedify created before fanout, or when callers
1652
- * explicitly request normalization for locally pre-signed activities.
1653
- * `false`/`undefined` preserves existing proofs as-is.
1654
- */
1655
- readonly normalizeExistingProofs?: boolean;
1656
- readonly traceContext: Readonly<Record<string, string>>;
1657
- }
1658
- interface OutboxMessage {
1659
- readonly type: "outbox";
1660
- readonly id: ReturnType<typeof crypto.randomUUID>;
1661
- readonly baseUrl: string;
1662
- readonly keys: readonly SenderKeyJwkPair[];
1663
- readonly activity: unknown;
1664
- readonly activityId?: string;
1665
- readonly activityType: string;
1666
- readonly inbox: string;
1667
- readonly sharedInbox: boolean;
1668
- readonly actorIds?: readonly string[];
1669
- readonly started: string;
1670
- readonly attempt: number;
1671
- readonly headers: Readonly<Record<string, string>>;
1672
- readonly orderingKey?: string;
1673
- /**
1674
- * Whether this message is currently held by the outbound circuit breaker.
1675
- * @internal
1676
- */
1677
- readonly circuitHeld?: true;
1678
- /**
1679
- * When Fedify first held this message because the remote host circuit was
1680
- * open.
1681
- * @internal
1682
- */
1683
- readonly circuitHeldSince?: string;
1684
- /**
1685
- * The canonical form of the FEP-ef61 portable inbox that {@link inbox}, its
1686
- * compatible identifier on a gateway, stands for.
1687
- * @internal
1688
- */
1689
- readonly portableInbox?: string;
1690
- /**
1691
- * The compatible identifiers of the FEP-ef61 portable inbox on each gateway
1692
- * that is still to be tried, in order. {@link inbox} is the first one.
1693
- * If there is more than one, the activity is delivered through the first
1694
- * gateway that accepts it.
1695
- * @internal
1696
- */
1697
- readonly gatewayInboxes?: readonly string[];
1698
- /**
1699
- * The times before which each of {@link gatewayInboxes} must not be tried
1700
- * again, as their responses' `Retry-After` headers requested, keyed by
1701
- * the inbox URL, in ISO 8601.
1702
- * @internal
1703
- */
1704
- readonly gatewayNotBefore?: Readonly<Record<string, string>>;
1705
- readonly traceContext: Readonly<Record<string, string>>;
1706
- }
1707
- /**
1708
- * A message that carries a custom background task. Every field is
1709
- * a string, number, or plain record so that the message survives both
1710
- * JSON serialization and structured clone on every queue backend.
1711
- * @since 2.4.0
1712
- */
1713
- interface TaskMessage {
1714
- readonly type: "task";
1715
- readonly id: ReturnType<typeof crypto.randomUUID>;
1716
- readonly baseUrl: string;
1717
- readonly taskName: string;
1718
- /** devalue-encoded task data; vocab objects bridged to expanded JSON-LD. */
1719
- readonly data: string;
1720
- readonly started: string;
1721
- readonly attempt: number;
1722
- readonly orderingKey?: string;
1723
- readonly traceContext: Readonly<Record<string, string>>;
1724
- }
1725
- interface InboxMessage {
1726
- readonly type: "inbox";
1727
- readonly id: ReturnType<typeof crypto.randomUUID>;
1728
- readonly baseUrl: string;
1729
- readonly activity: unknown;
1730
- /**
1731
- * The normalized JSON-LD representation of a signed inbox activity that
1732
- * Fedify already compacted successfully while accepting the request. Queue
1733
- * workers can reuse this producer-side parse cache under stricter loader or
1734
- * network constraints without changing the raw payload preserved for
1735
- * forwarding.
1736
- *
1737
- * This may exist even when {@link ldSignatureVerified} is `false`, because
1738
- * fallback-authenticated traffic and already-queued backlog items can still
1739
- * depend on the cached normalized form to avoid re-fetching remote custom
1740
- * contexts during worker processing.
1741
- *
1742
- * This is optional for backward compatibility with messages that were
1743
- * queued by older Fedify versions or that were already in a queue before
1744
- * upgrading.
1745
- *
1746
- * Fedify keeps this on the queued message itself instead of an external
1747
- * sidecar because generic queue backends do not provide reliable lifecycle
1748
- * guarantees for auxiliary storage across retries and redeliveries.
1749
- *
1750
- * @internal
1751
- */
1752
- readonly normalizedActivity?: unknown;
1753
- /**
1754
- * Whether the producer actually verified the Linked Data Signature before
1755
- * queueing this message. This lets workers distinguish verified LDS replay
1756
- * from other authenticated inbox traffic that merely happened to include a
1757
- * signature block. This provenance marker is separate from the optional
1758
- * normalizedActivity parse cache.
1759
- *
1760
- * `undefined` preserves backward compatibility with older queued messages
1761
- * that predate this marker.
1762
- *
1763
- * @internal
1764
- */
1765
- readonly ldSignatureVerified?: boolean;
1766
- /**
1767
- * The interpretation of the activity that Fedify authenticated, i.e., which
1768
- * document it was parsed from and every JSON-LD context response the parse
1769
- * used, so that the worker and its retries dispatch the very same
1770
- * interpretation without loading contexts again. Absent in messages queued
1771
- * by older Fedify versions.
1772
- * @internal
1773
- */
1774
- readonly verifiedView?: SerializedVerifiedActivityView;
1775
- /**
1776
- * Who Fedify authenticated for the activity. Absent in messages queued by
1777
- * older Fedify versions.
1778
- * @internal
1779
- */
1780
- readonly authentication?: SerializedInboxAuthentication;
1781
- readonly started: string;
1782
- readonly attempt: number;
1783
- readonly identifier: string | null;
1784
- readonly traceContext: Readonly<Record<string, string>>;
1785
- }
1786
- //#endregion
1787
2372
  //#region src/federation/middleware.d.ts
1788
2373
  /**
1789
2374
  * Configures the task queues for sending and receiving activities.
@@ -1930,6 +2515,31 @@ interface FederationStartQueueOptions {
1930
2515
  * @since 1.6.0
1931
2516
  */
1932
2517
  interface Federatable<TContextData> extends TaskRegistry<TContextData> {
2518
+ /**
2519
+ * Registers observers for outgoing activity processing.
2520
+ *
2521
+ * Each call replaces all previously registered outbound observers.
2522
+ * Pass an empty object to clear them. Fedify copies callbacks and capture policies;
2523
+ * later changes to the supplied object do not affect registration.
2524
+ * Available on builders and directly created federations. Building copies
2525
+ * the callbacks, and an active send keeps the callback selected at entry.
2526
+ * Observes send, original-payload forwarding, and automatic portable
2527
+ * replication preparation, destination planning, and handoffs, including
2528
+ * outbox-context operations and fanout continuation handoffs. Separate
2529
+ * callbacks report concrete send attempts and actual HTTP exchanges, including
2530
+ * queued retries and gateway fallback. Worker reports retain local execution
2531
+ * results and actual enqueue, retry, deferral, and abandonment actions,
2532
+ * separately from attempts and backend acknowledgment.
2533
+ * Known IDs, original inbox source, and
2534
+ * application correlation survive queue work; workers use current context data.
2535
+ * Header values, bounded response-body streaming and sender-payload projections
2536
+ * are independently opt-in. Body consumers require explicit byte/time limits;
2537
+ * other metadata callbacks have no default timeout.
2538
+ * This is separate from incoming C2S {@link Federatable.setOutboxListeners}.
2539
+ * @param observers The optional callbacks to register.
2540
+ * @since 2.5.0
2541
+ */
2542
+ setOutboundObservers(observers: OutboundObservers<TContextData>): void;
1933
2543
  /**
1934
2544
  * Registers a NodeInfo dispatcher.
1935
2545
  * @param path The URI path pattern for the NodeInfo dispatcher. The syntax
@@ -5002,6 +5612,14 @@ type ParseUriResult = {
5002
5612
  * Options for {@link Context.sendActivity} method.
5003
5613
  */
5004
5614
  interface SendActivityOptions {
5615
+ /**
5616
+ * Application-owned correlation for this invocation, never a deduplication
5617
+ * key. Preserved exactly, including an empty string. At most 1,024 UTF-8
5618
+ * bytes; invalid types and oversized values are rejected before preparation.
5619
+ * Omission means no application dispatch ID for this new invocation.
5620
+ * @since 2.5.0
5621
+ */
5622
+ readonly dispatchId?: string;
5005
5623
  /**
5006
5624
  * Whether to prefer the shared inbox for the recipients.
5007
5625
  */
@@ -5163,4 +5781,4 @@ interface ActorKeyPair extends CryptoKeyPair {
5163
5781
  readonly multikey: Multikey;
5164
5782
  }
5165
5783
  //#endregion
5166
- export { respondWithObject as $, SendActivityError as $t, InboxChallengePolicy as A, CollectionCursor as At, createFederation as B, NodeInfoDispatcher as Bt, FederationBenchmarkOptions as C, parseCircuitBreakerKvState as Ct, FederationStartQueueOptions as D, ActorKeyPairsDispatcher as Dt, FederationOptions as E, ActorHandleMapper as Et, PortableInboxForwardingOptions as F, HashlinkMediaDispatcher as Ft, TaskEnqueueOptions as G, OutboxListenerErrorHandler as Gt, createFederationBuilder as H, ObjectDispatcher as Ht, Rfc6570Expression as I, HashlinkMediaRequest as It, CreateExponentialBackoffPolicyOptions as J, PortableCollectionOwnerMapper as Jt, TaskHandler as K, OutboxPermanentFailureHandler as Kt, FederationKvPrefixes as L, InboxErrorHandler as Lt, MediaUploaderSetters as M, CustomCollectionCounter as Mt, ObjectCallbackSetters as N, CustomCollectionCursor as Nt, IdempotencyKeyCallback as O, AuthorizePredicate as Ot, OutboxListenerSetters as P, CustomCollectionDispatcher as Pt, RespondWithObjectOptions as Q, WebFingerLinksDispatcher as Qt, FederationOrigin as R, InboxListener as Rt, Federation as S, normalizeCircuitBreakerOptions as St, FederationFetchOptions as T, ActorDispatcher as Tt, TaskDefinition as U, OutboxErrorHandler as Ut, Message as V, ObjectAuthorizePredicate as Vt, TaskDefinitionOptions as W, OutboxListener as Wt, RetryPolicy as X, UnverifiedActivityHandler as Xt, RetryContext as Y, SharedInboxKeyDispatcher as Yt, createExponentialBackoffPolicy as Z, UnverifiedActivityReason as Zt, ActorCallbackSetters as _, CircuitBreakerKvState as _t, GetObjectOptions as a, InboxAuthentication as an, InProcessMessageQueue as at, CustomCollectionCallbackSetters as b, CircuitBreakerStateChange as bt, IsSignedByAudienceOptions as c, InboxRequestOutcome as cn, MessageQueueDepth as ct, ParseUriResult as d, ParallelMessageQueue as dt, SenderKeyPair as en, respondWithObjectIfAcceptable as et, PortableRequest as f, CircuitBreaker as ft, SendActivityOptionsForCollection as g, CircuitBreakerFailurePolicy as gt, SendActivityOptions as h, CircuitBreakerCreateOptions as ht, GetActorOptions as i, ActivityTransformer as in, isActorAuthenticated as it, InboxListenerSetters as j, CollectionDispatcher as jt, IdempotencyStrategy as k, CollectionCounter as kt, OutboxContext as l, InboxRequestReport as ln, MessageQueueEnqueueOptions as lt, RouteActivityOptions as m, CircuitBreakerBeforeSendDecision as mt, Context as n, buildCollectionSynchronizationHeader as nn, InboxPrincipal as nt, GetSignedKeyOptions as o, InboxJsonValue as on, InProcessMessageQueueOptions as ot, RequestContext as p, CircuitBreakerActivityDrop as pt, TaskRegistry as q, PortableActorIdMapper as qt, ForwardActivityOptions as r, digest as rn, IsActorAuthenticatedOptions as rt, InboxContext as s, InboxRequestFinishedHandler as sn, MessageQueue as st, ActorKeyPair as t, PageItems as tn, InboxAuthenticationEvidence as tt, ParseUriOptions as u, InboxListenerOptions as un, MessageQueueListenOptions as ut, CollectionCallbackSetters as v, CircuitBreakerOptions as vt, FederationBuilder as w, ActorAliasMapper as wt, Federatable as x, NormalizedCircuitBreakerOptions as xt, ConstructorWithTypeId as y, CircuitBreakerState as yt, FederationQueueOptions as z, MediaUploaderCallback as zt };
5784
+ export { respondWithObjectIfAcceptable as $, IsActorAuthenticatedOptions as $t, InboxChallengePolicy as A, OutboundResponseBodyEvent as An, HashlinkMediaDispatcher as At, createFederation as B, OutboxListenerErrorHandler as Bt, FederationBenchmarkOptions as C, OutboundInboxRequest as Cn, AuthorizePredicate as Ct, FederationStartQueueOptions as D, OutboundPreparationResult as Dn, CustomCollectionCounter as Dt, FederationOptions as E, OutboundObservers as En, CollectionDispatcher as Et, PortableInboxForwardingOptions as F, OutboundWorkerPreparationDiagnostic as Fn, NodeInfoDispatcher as Ft, TaskHandler as G, UnverifiedActivityHandler as Gt, TaskDefinition as H, PortableActorIdMapper as Ht, Rfc6570Expression as I, OutboundWorkerReport as In, ObjectAuthorizePredicate as It, RetryContext as J, SendActivityError as Jt, TaskRegistry as K, UnverifiedActivityReason as Kt, FederationKvPrefixes as L, OutboundWorkerSchedule as Ln, ObjectDispatcher as Lt, MediaUploaderSetters as M, OutboundSourceMetadata as Mn, InboxErrorHandler as Mt, ObjectCallbackSetters as N, OutboundWorkerAction as Nn, InboxListener as Nt, IdempotencyKeyCallback as O, OutboundProvenance as On, CustomCollectionCursor as Ot, OutboxListenerSetters as P, OutboundWorkerChild as Pn, MediaUploaderCallback as Pt, respondWithObject as Q, InboxPrincipal as Qt, FederationOrigin as R, OutboxErrorHandler as Rt, Federation as S, OutboundHttpResponseMetadata as Sn, ActorKeyPairsDispatcher as St, FederationFetchOptions as T, OutboundLocalContext as Tn, CollectionCursor as Tt, TaskDefinitionOptions as U, PortableCollectionOwnerMapper as Ut, createFederationBuilder as V, OutboxPermanentFailureHandler as Vt, TaskEnqueueOptions as W, SharedInboxKeyDispatcher as Wt, createExponentialBackoffPolicy as X, Message as Xt, RetryPolicy as Y, SenderKeyPair as Yt, RespondWithObjectOptions as Z, InboxAuthenticationEvidence as Zt, ActorCallbackSetters as _, OutboundBroadcastEvent as _n, normalizeCircuitBreakerOptions as _t, GetObjectOptions as a, InboxAuthentication as an, MessageQueueListenOptions as at, CustomCollectionCallbackSetters as b, OutboundHeaderSnapshot as bn, ActorDispatcher as bt, IsSignedByAudienceOptions as c, InboxRequestFinishedHandler as cn, CircuitBreakerActivityDrop as ct, ParseUriResult as d, InboxListenerOptions as dn, CircuitBreakerFailurePolicy as dt, isActorAuthenticated as en, InProcessMessageQueue as et, PortableRequest as f, OutboundActivityReference as fn, CircuitBreakerKvState as ft, SendActivityOptionsForCollection as g, OutboundBodyCaptureSummary as gn, NormalizedCircuitBreakerOptions as gt, SendActivityOptions as h, OutboundAttemptResponse as hn, CircuitBreakerStateChange as ht, GetActorOptions as i, ActivityTransformer as in, MessageQueueEnqueueOptions as it, InboxListenerSetters as j, OutboundSenderPayload as jn, HashlinkMediaRequest as jt, IdempotencyStrategy as k, OutboundResponseBodyCapture as kn, CustomCollectionDispatcher as kt, OutboxContext as l, InboxRequestOutcome as ln, CircuitBreakerBeforeSendDecision as lt, RouteActivityOptions as m, OutboundAttemptReport as mn, CircuitBreakerState as mt, Context as n, buildCollectionSynchronizationHeader as nn, MessageQueue as nt, GetSignedKeyOptions as o, InboxJsonValue as on, ParallelMessageQueue as ot, RequestContext as p, OutboundAttemptFailure as pn, CircuitBreakerOptions as pt, CreateExponentialBackoffPolicyOptions as q, WebFingerLinksDispatcher as qt, ForwardActivityOptions as r, digest as rn, MessageQueueDepth as rt, InboxContext as s, InboxRequestCancellation as sn, CircuitBreaker as st, ActorKeyPair as t, PageItems as tn, InProcessMessageQueueOptions as tt, ParseUriOptions as u, InboxRequestReport as un, CircuitBreakerCreateOptions as ut, CollectionCallbackSetters as v, OutboundDeliveryEvent as vn, parseCircuitBreakerKvState as vt, FederationBuilder as w, OutboundJsonValue as wn, CollectionCounter as wt, Federatable as x, OutboundHttpExchangeReport as xn, ActorHandleMapper as xt, ConstructorWithTypeId as y, OutboundHeaderPolicy as yn, ActorAliasMapper as yt, FederationQueueOptions as z, OutboxListener as zt };