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