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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/{builder-BLCvt0zp.mjs → builder-C-zhu1PC.mjs} +82 -3
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-DNhLvscb.mjs → compound-proof-B6Mz3pzt.mjs} +2 -2
  6. package/dist/{context-ByflxsY9.d.cts → context-C1NqcIct.d.cts} +1061 -443
  7. package/dist/{context-uQUY5fQT.d.ts → context-xrqq4Hrq.d.ts} +1064 -446
  8. package/dist/{deno-DYkYmKp0.mjs → deno-CaPDSXDl.mjs} +1 -1
  9. package/dist/{docloader-B9RO5nh9.mjs → docloader-kYMkiXaB.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  13. package/dist/federation/handler.test.mjs +4 -4
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/inbox-actorless.test.mjs +5 -5
  17. package/dist/federation/inbox-report.test.mjs +220 -5
  18. package/dist/federation/inbox-view.test.mjs +1 -1
  19. package/dist/federation/inbox.test.mjs +1 -1
  20. package/dist/federation/metrics.test.mjs +1 -1
  21. package/dist/federation/middleware.test.mjs +7 -7
  22. package/dist/federation/mod.cjs +1 -1
  23. package/dist/federation/mod.d.cts +3 -3
  24. package/dist/federation/mod.d.ts +4 -4
  25. package/dist/federation/mod.js +1 -1
  26. package/dist/federation/mq.test.mjs +1 -1
  27. package/dist/federation/outbound-body.test.d.mts +2 -0
  28. package/dist/federation/outbound-body.test.mjs +149 -0
  29. package/dist/federation/outbound-capture.test.d.mts +2 -0
  30. package/dist/federation/outbound-capture.test.mjs +394 -0
  31. package/dist/federation/outbound-dispatch.test.d.mts +2 -0
  32. package/dist/federation/outbound-dispatch.test.mjs +368 -0
  33. package/dist/federation/outbound-document.test.d.mts +2 -0
  34. package/dist/federation/outbound-document.test.mjs +171 -0
  35. package/dist/federation/outbound-provenance.test.d.mts +2 -0
  36. package/dist/federation/outbound-provenance.test.mjs +543 -0
  37. package/dist/federation/outbound-report.test.d.mts +2 -0
  38. package/dist/federation/outbound-report.test.mjs +5475 -0
  39. package/dist/federation/outbound-workers.test.d.mts +2 -0
  40. package/dist/federation/outbound-workers.test.mjs +64 -0
  41. package/dist/federation/outbound-workflow.test.d.mts +2 -0
  42. package/dist/federation/outbound-workflow.test.mjs +154 -0
  43. package/dist/federation/outgoing-proof.test.mjs +4 -4
  44. package/dist/federation/portable-actor.test.mjs +3 -3
  45. package/dist/federation/portable-chain.test.mjs +3 -3
  46. package/dist/federation/portable-collection.test.mjs +4 -4
  47. package/dist/federation/portable-delivery.test.mjs +106 -7
  48. package/dist/federation/portable-gateway-keys.test.mjs +5 -5
  49. package/dist/federation/portable-inbox.test.mjs +430 -5
  50. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  51. package/dist/federation/portable-route-activity.test.mjs +2 -2
  52. package/dist/federation/portable.test.mjs +3 -3
  53. package/dist/federation/send.test.mjs +44 -3
  54. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  55. package/dist/federation/tasks/tasks.test.mjs +2 -2
  56. package/dist/federation/temporal.test.mjs +1 -1
  57. package/dist/federation/webfinger.test.mjs +1 -1
  58. package/dist/{federation-DzMdu4zU.cjs → federation-c-ulWHXG.cjs} +2358 -405
  59. package/dist/{http-EoFzzh1s.js → http-C12QVNZt.js} +62 -32
  60. package/dist/{http-BdTJujR0.cjs → http-DwL_g670.cjs} +62 -32
  61. package/dist/{http-TgeyuTDM.mjs → http-NYqvoigN.mjs} +64 -34
  62. package/dist/{inbox-ChkbRxCN.mjs → inbox-CkL5YgHZ.mjs} +90 -3
  63. package/dist/{inbox-view-BosBZdlt.mjs → inbox-view-IJCZG0_b.mjs} +1 -1
  64. package/dist/{key-DbLNT-vA.d.ts → key-dwwAg5bA.d.ts} +1 -1
  65. package/dist/{kv-cache-CMgOYiLU.js → kv-cache-1BO1Deou.js} +1 -1
  66. package/dist/{kv-cache-CTA2i08a.mjs → kv-cache-BdFnnrOU.mjs} +1 -1
  67. package/dist/{kv-cache-B0o18_Ni.cjs → kv-cache-CcGUsFoZ.cjs} +1 -1
  68. package/dist/{ld-CZsMAfOw.mjs → ld-CEOS8Pho.mjs} +2 -2
  69. package/dist/{ld-Inagww4R.d.ts → ld-o4LnhCYY.d.ts} +2 -2
  70. package/dist/{metrics-Kc9mP1LD.mjs → metrics-CvuJeWaJ.mjs} +1 -1
  71. package/dist/{metrics-DoRtI0qU.d.ts → metrics-DVKYY6K1.d.ts} +1 -1
  72. package/dist/{middleware-C1coPz8l.mjs → middleware-BPZ9KhAd.mjs} +1 -1
  73. package/dist/{middleware-B-pjIvQ8.js → middleware-CzcR7pNr.js} +2303 -351
  74. package/dist/{middleware-CIRoIVSU.mjs → middleware-DMrpt2wD.mjs} +1573 -325
  75. package/dist/{mod-B7j30IQf.d.ts → mod-CQC9xlLZ.d.ts} +3 -3
  76. package/dist/{mod-B4kBw1-8.d.ts → mod-D22ZsiKi.d.ts} +2 -2
  77. package/dist/{mod-BN0TTfyQ.d.ts → mod-DO0epJGH.d.ts} +1 -1
  78. package/dist/{mod-DF2gbN_d.d.cts → mod-TwiQgEf3.d.cts} +1 -1
  79. package/dist/mod.cjs +4 -4
  80. package/dist/mod.d.cts +4 -4
  81. package/dist/mod.d.ts +8 -8
  82. package/dist/mod.js +4 -4
  83. package/dist/nodeinfo/handler.test.mjs +1 -1
  84. package/dist/outbound-body-CckPA5gc.mjs +83 -0
  85. package/dist/outbound-capture-DVgnRzlf.mjs +184 -0
  86. package/dist/outbound-document-BMgwLXTY.mjs +179 -0
  87. package/dist/{owner-Dwz4fGUW.mjs → owner-BOyHaAlU.mjs} +3 -3
  88. package/dist/{portable-collection-BikiijSk.js → portable-collection-B4HFXlgN.js} +2 -2
  89. package/dist/{portable-collection-Oxfavx8f.mjs → portable-collection-BBSMcaDY.mjs} +2 -2
  90. package/dist/{portable-collection-Ci6vIhbR.cjs → portable-collection-D_zZTK4C.cjs} +2 -2
  91. package/dist/{portable-key--fWpTNDN.mjs → portable-key-CF8xK_lj.mjs} +1 -1
  92. package/dist/{proof-D6Kb0Qq9.mjs → proof-VrE-GaGv.mjs} +3 -3
  93. package/dist/{send-BFti0L3I.mjs → send-CY6agn9T.mjs} +153 -40
  94. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  95. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  96. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  97. package/dist/sig/compound-proof.test.mjs +2 -2
  98. package/dist/sig/http.test.mjs +38 -2
  99. package/dist/sig/key.test.mjs +1 -1
  100. package/dist/sig/ld.test.mjs +1 -1
  101. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  102. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  103. package/dist/sig/mod.cjs +2 -2
  104. package/dist/sig/mod.d.ts +3 -3
  105. package/dist/sig/mod.js +2 -2
  106. package/dist/sig/owner.test.mjs +1 -1
  107. package/dist/sig/portable-collection.test.mjs +1 -1
  108. package/dist/sig/portable-interop.test.mjs +3 -3
  109. package/dist/sig/portable-key.test.mjs +5 -5
  110. package/dist/sig/proof.test.mjs +3 -3
  111. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  112. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  113. package/dist/sig/verification.test.mjs +3 -3
  114. package/dist/{temporal-DlYL_raW.mjs → temporal-QO2pe6W4.mjs} +1 -1
  115. package/dist/testing/kv.test.mjs +1 -1
  116. package/dist/testing/mod.d.mts +1030 -412
  117. package/dist/utils/docloader.test.mjs +2 -2
  118. package/dist/utils/kv-cache.test.mjs +1 -1
  119. package/dist/utils/mod.cjs +1 -1
  120. package/dist/utils/mod.d.ts +1 -1
  121. package/dist/utils/mod.js +1 -1
  122. package/package.json +7 -7
  123. package/skills/fedify/SKILL.md +60 -1
@@ -416,6 +416,536 @@ interface GetKeyOwnerOptions {
416
416
  tracerProvider?: TracerProvider;
417
417
  }
418
418
  //#endregion
419
+ //#region src/federation/outbound-report.d.ts
420
+ /**
421
+ * The current producer's preparation result, not delivery completion.
422
+ * @since 2.5.0
423
+ */
424
+ type OutboundPreparationResult = {
425
+ /** The producer finished preparing its continuation. */readonly type: "prepared"; /** Fanout-ready work still needs worker-side delivery preparation. */
426
+ readonly preparedFor: "fanout" | "delivery"; /** Number of deduplicated destinations, not actors or successful sends. */
427
+ readonly destinationCount: number;
428
+ } | {
429
+ /** A deliberate no-send decision. */readonly type: "skipped"; /** The existing no-send decision made by this producer. */
430
+ readonly reason: "no-destinations" | "unsigned" | "forwarding-disabled" | "forwarding-unavailable" | "no-forwarding-targets";
431
+ } | {
432
+ /** Preparation failed before the producer reached readiness. */readonly type: "failed"; /** The operation that failed and its original thrown value. */
433
+ readonly failure: {
434
+ /** Local processing failure, or unavailable operation provenance. */readonly kind: "local" | "unknown"; /** The observed operation, not a diagnosis from the error message. */
435
+ readonly phase: "sender-resolution" | "key-resolution" | "recipient-resolution" | "transformation" | "validation" | "signing" | "destination-planning" | "serialization" | "request-preparation" | "unknown"; /** Original thrown value, including non-Error values. */
436
+ readonly error: unknown;
437
+ }; /** Whether a complete destination plan was established before failure. */
438
+ readonly planning: "not-started" | "incomplete" | "complete"; /** Confirmed planned obligations so far, not an audience estimate. */
439
+ readonly plannedDestinationCount: number;
440
+ };
441
+ /**
442
+ * Known Activity ID and type at a broadcast observation boundary.
443
+ *
444
+ * Fields are captured without serializing the Activity or loading contexts.
445
+ * Successful transformation refreshes both the ID and the Activity class type.
446
+ * A `started` event may have an empty reference, and later events may reflect
447
+ * Activity transformations that happened after that event.
448
+ * @since 2.5.0
449
+ */
450
+ type OutboundActivityReference = {
451
+ /** Activity identifier, when available at the observation boundary. */readonly id?: string; /** Activity class type IRI, when available at the observation boundary. */
452
+ readonly type?: string;
453
+ };
454
+ /**
455
+ * A send, original-payload forwarding, or portable replication producer lifecycle.
456
+ *
457
+ * Preparation results do not report queue acceptance or delivery completion.
458
+ * @since 2.5.0
459
+ */
460
+ type OutboundBroadcastEvent = OutboundSourceMetadata & {
461
+ /** Fresh opaque identity shared by producer events, independent of tracing. */readonly broadcastId: string; /** The outgoing operation being observed. */
462
+ readonly operation: "send" | "forward" | "portable-replication"; /** Immutable Activity identity facts captured by the send path. */
463
+ readonly activity: OutboundActivityReference;
464
+ } & ({
465
+ /** Entry before sender/recipient resolution or signing. */readonly stage: "started";
466
+ } | {
467
+ /** The current producer's preparation settled. */readonly stage: "preparation-finished"; /** Readiness, a deliberate skip, or the original preparation failure. */
468
+ readonly result: OutboundPreparationResult;
469
+ });
470
+ /**
471
+ * A producer-side destination obligation, not delivery completion.
472
+ *
473
+ * IDs group producer handoffs and their fanout expansion across queue tasks.
474
+ * Concrete sends use separate {@link OutboundAttemptReport} values; these
475
+ * events do not establish delivery completion or worker scheduling outcomes.
476
+ * An unvisited destination stays planned when another destination's preparation
477
+ * interrupts the producer.
478
+ * @since 2.5.0
479
+ */
480
+ type OutboundDeliveryEvent = OutboundSourceMetadata & {
481
+ /** The originating invocation, or unknown for legacy fanout work. */readonly broadcast: {
482
+ readonly type: "known";
483
+ readonly id: string;
484
+ } | {
485
+ readonly type: "unknown";
486
+ }; /** Opaque identity allocated after deduplication or a confirmed claim. */
487
+ readonly deliveryId: string; /** Planned destination, unchanged by concrete gateway selection. */
488
+ readonly destinationInbox: string; /** Canonical portable inbox when the selected route represents one. */
489
+ readonly portableInbox?: string;
490
+ } & ({
491
+ /** This destination was established by the existing plan. */readonly stage: "planned"; /** Known actors sharing the destination, not an exhaustive audience. */
492
+ readonly actorIds: readonly string[]; /** Whether the selected destination uses a shared inbox. */
493
+ readonly sharedInbox: boolean; /** Fallback routes share one obligation; replication targets do not. */
494
+ readonly mode: "ordinary" | "portable-fallback" | "portable-replication";
495
+ } | {
496
+ /** Local execution entry or enqueue settlement, not remote success. */readonly stage: "handoff"; /** Evidence from the actual producer handoff. */
497
+ readonly result: {
498
+ readonly type: "direct";
499
+ } | {
500
+ readonly type: "enqueued";
501
+ readonly queue: "fanout" | "outbox";
502
+ readonly queueMessageId: string;
503
+ } | {
504
+ readonly type: "enqueue-failed";
505
+ readonly acceptance: "unknown";
506
+ readonly error: unknown;
507
+ };
508
+ } | {
509
+ /** Destination-local setup failed before entry into concrete sending. */readonly stage: "preparation-failed"; /** The original local setup failure, without inventing an attempt. */
510
+ readonly failure: {
511
+ readonly kind: "local";
512
+ readonly phase: "request-preparation";
513
+ readonly error: unknown;
514
+ };
515
+ });
516
+ /**
517
+ * The failure classification and available error evidence for a send attempt.
518
+ * @since 2.5.0
519
+ */
520
+ type OutboundAttemptFailure = {
521
+ readonly kind: "http";
522
+ readonly phase: "response";
523
+ readonly error?: unknown;
524
+ } | {
525
+ readonly kind: "transport";
526
+ readonly phase: "validation" | "request";
527
+ readonly error: unknown;
528
+ } | {
529
+ readonly kind: "policy";
530
+ readonly phase: "validation";
531
+ readonly error: unknown;
532
+ } | {
533
+ readonly kind: "local";
534
+ readonly phase: "serialization" | "request-preparation" | "cleanup" | "signing" | "spec-lookup" | "post-response";
535
+ readonly error: unknown;
536
+ } | {
537
+ readonly kind: "unknown";
538
+ readonly phase: "unknown";
539
+ readonly error: unknown;
540
+ };
541
+ /** Immutable JSON data in a privacy projection. @since 2.5.0 */
542
+ type OutboundJsonValue = null | boolean | number | string | readonly OutboundJsonValue[] | {
543
+ readonly [key: string]: OutboundJsonValue;
544
+ };
545
+ /** Attempt input evidence, never proof of a particular exchange's wire body. @since 2.5.0 */
546
+ type OutboundSenderPayload = {
547
+ /** Concrete sender input, not a producer history or per-exchange wire body. */readonly stage: "sender-payload";
548
+ } & ({
549
+ /** A safe independent projection was established without extra processing. */readonly type: "available"; /** Always a projection; no signature/proof or exact-wire claim is retained. */
550
+ readonly representation: "privacy-projection"; /** At least one blind-recipient field was removed from the copied value. */
551
+ readonly redacted: boolean; /** Recursively immutable JSON data; transport/signing input is unchanged. */
552
+ readonly value: OutboundJsonValue;
553
+ } | {
554
+ /** No document value is exposed, including when export is disabled. */readonly type: "unavailable"; /** Opt-out, pre-snapshot failure, or unsupported privacy/context semantics. */
555
+ readonly reason: "disabled" | "not-materialized" | "privacy-filter-unavailable";
556
+ });
557
+ /** Result of an enabled response-body capture. Absence means disabled. @since 2.5.0 */
558
+ type OutboundBodyCaptureSummary = {
559
+ /** Bytes handed to the application, not bytes durably stored. */readonly bytesExposed: number;
560
+ } & ({
561
+ /** Selection, verified EOF, byte-cap uncertainty, or local lifetime end. */readonly type: "filtered" | "no-body" | "complete" | "limit-reached" | "deadline" | "callback-returned";
562
+ } | {
563
+ /** Application cancellation ended capture. */readonly type: "consumer-stopped"; /** Original consumer reason, including non-Error values. */
564
+ readonly reason: unknown;
565
+ } | {
566
+ /** Capture failed without changing received HTTP evidence. */readonly type: "failed"; /** Source reading versus application callback failure. */
567
+ readonly phase: "body" | "observer"; /** Original capture error; it does not become the attempt's failure. */
568
+ readonly error: unknown;
569
+ });
570
+ /** Bounded, revocable access to one selected response. @since 2.5.0 */
571
+ type OutboundResponseBodyEvent = Omit<OutboundHttpExchangeReport, "outcome" | "bodyCapture"> & {
572
+ /** Immutable response metadata, independently of eventual attempt outcome. */readonly response: Extract<OutboundHttpExchangeReport["outcome"], {
573
+ type: "response";
574
+ }>["response"]; /** Pull-driven bytes; access ends when the callback settles or its limits apply. */
575
+ readonly body: ReadableStream<Uint8Array>; /** Capture-only lifetime signal; aborting it does not cancel transport. */
576
+ readonly signal: AbortSignal;
577
+ };
578
+ /** Explicit opt-in response-body capture limits and consumer. @since 2.5.0 */
579
+ interface OutboundResponseBodyCapture<TContextData> {
580
+ /** Positive safe-integer exposure limit; exact cap without EOF stays uncertain. */
581
+ readonly maxBytes: number;
582
+ /** Positive finite absolute lifetime in milliseconds, rounded up; at most 2^31-1. */
583
+ readonly timeoutMs: number;
584
+ /** Actual response selector, defaulting to `all` only when enabled. */
585
+ readonly responses?: "all" | "non-success-http-responses";
586
+ /**
587
+ * Invoked after headers, before the terminal exchange report, with current data.
588
+ * Awaited within the absolute lifetime, including no-pull/slow-consumer time.
589
+ * Return/throw/cancel/cap/deadline revokes access even for retained locked readers.
590
+ * Errors cannot veto transport; ignoring the signal cannot be forcibly stopped.
591
+ */
592
+ readonly onBody: (context: Context<TContextData>, event: OutboundResponseBodyEvent) => void | Promise<void>;
593
+ }
594
+ /** Opt-in policy for one side of an actual HTTP exchange. @since 2.5.0 */
595
+ interface OutboundHeaderPolicy {
596
+ /** Literal HTTP names, normalized to lowercase and deduplicated at registration. */
597
+ readonly allow: readonly string[];
598
+ /**
599
+ * Additional names whose selected values are always `[redacted]`.
600
+ * Credentials, signatures and cookies remain protected even without this list.
601
+ */
602
+ readonly redact?: readonly string[];
603
+ /**
604
+ * Positive safe-integer UTF-8 budget for `name: value\r\n` after redaction.
605
+ * Whole oversized entries are omitted; later entries remain eligible.
606
+ */
607
+ readonly maxBytes: number;
608
+ }
609
+ /** Immutable selected header values, using the runtime representation. @since 2.5.0 */
610
+ interface OutboundHeaderSnapshot {
611
+ /** Lowercase names in lexical order; credentials/signatures/cookies are redacted. */
612
+ readonly entries: readonly (readonly [string, string])[];
613
+ /** At least one selected entry was omitted because it exceeded the budget. */
614
+ readonly limitReached: boolean;
615
+ }
616
+ /**
617
+ * Immutable HTTP response facts captured from a received response.
618
+ * @since 2.5.0
619
+ */
620
+ interface OutboundHttpResponseMetadata {
621
+ /** Actual HTTP status returned by fetch. */
622
+ readonly status: number;
623
+ /** Copied HTTP status text. */
624
+ readonly statusText: string;
625
+ /** Response URL when supplied by fetch. */
626
+ readonly url?: string;
627
+ }
628
+ /**
629
+ * Response evidence and finality established during a send attempt.
630
+ * @since 2.5.0
631
+ */
632
+ type OutboundAttemptResponse = {
633
+ /** The HTTP flow selected this response before follow-up work. */readonly type: "final"; /** Actual submission index of the selected response. */
634
+ readonly exchangeIndex: number; /** Frozen response facts, retained across later local failures. */
635
+ readonly metadata: OutboundHttpResponseMetadata;
636
+ } | {
637
+ /** No response headers were received, including zero-fetch attempts. */readonly type: "not-observed";
638
+ } | {
639
+ /** Continued past the response, or failed before deciding finality. */readonly type: "intermediate-only" | "undetermined"; /** Latest received response, without a finality claim. */
640
+ readonly lastObserved: {
641
+ readonly exchangeIndex: number;
642
+ readonly metadata: OutboundHttpResponseMetadata;
643
+ };
644
+ };
645
+ /**
646
+ * The terminal result of one concrete send.
647
+ *
648
+ * A send that fails before submitting HTTP still constitutes an attempt, with
649
+ * zero exchanges. Each gateway send and retry gets a fresh attempt identity.
650
+ * Final response evidence follows the HTTP flow's selection, not the last
651
+ * observed status; later cache/cleanup failures can still fail the execution.
652
+ * @since 2.5.0
653
+ */
654
+ type OutboundAttemptReport = OutboundSourceMetadata & {
655
+ /** Fresh opaque identity allocated at concrete sender entry. */readonly attemptId: string; /** The originating invocation, independent of tracing. */
656
+ readonly broadcast: OutboundDeliveryEvent["broadcast"]; /** Destination obligation shared with producer delivery events. */
657
+ readonly deliveryId: string; /** Original planned inbox; unavailable on legacy outbox tasks. */
658
+ readonly destinationInbox?: string; /** Canonical portable inbox when the planned destination represents one. */
659
+ readonly portableInbox?: string; /** Inbox selected for this invocation of the concrete sender. */
660
+ readonly inboxUrl: string; /** Actual fetch submissions, including challenges and redirects; may be zero. */
661
+ readonly httpExchangeCount: number; /** Opt-in independent sender input projection; unavailable states remain explicit. */
662
+ readonly senderPayload: OutboundSenderPayload; /** Current queue execution; absent on immediate sends. */
663
+ readonly queue?: {
664
+ readonly queueMessageId: string; /** Present when the queue message carries a Fedify retry counter. */
665
+ readonly queueAttempt?: number;
666
+ }; /** Settled execution result, separate from producer preparation. */
667
+ readonly outcome: {
668
+ readonly type: "succeeded"; /** Final response selected by the sender. */
669
+ readonly response: Extract<OutboundAttemptResponse, {
670
+ type: "final";
671
+ }>;
672
+ } | {
673
+ readonly type: "failed";
674
+ /**
675
+ * Bounded failure kind and operation phase, captured before broad wrapping.
676
+ * Unknown provenance stays unknown; exception names do not establish it.
677
+ * Final non-OK HTTP evidence remains primary alongside local diagnostics.
678
+ */
679
+ readonly failure: OutboundAttemptFailure; /** Exact value escaping the sender, possibly different from failure.error. */
680
+ readonly error: unknown; /** Explicit final or last-observed response evidence, including absence. */
681
+ readonly response: OutboundAttemptResponse; /** Additional failures, without replacing the primary evidence. */
682
+ readonly diagnostics?: readonly OutboundAttemptFailure[];
683
+ };
684
+ };
685
+ /**
686
+ * One actual fetch submission within a concrete send attempt.
687
+ *
688
+ * A redirect response remains an observed response even if a later hop fails.
689
+ * This report does not establish the attempt's final response. Metadata is
690
+ * copied before callback awaiting; no live Request/Response/Headers are exposed.
691
+ * Enabled body capture uses a separate revocable callback; reports hold summaries.
692
+ * Exchange callbacks run after selected capture ends and may precede attempt settlement.
693
+ * @since 2.5.0
694
+ */
695
+ type OutboundHttpExchangeReport = OutboundSourceMetadata & Pick<OutboundAttemptReport, "attemptId" | "broadcast" | "deliveryId" | "destinationInbox" | "portableInbox" | "inboxUrl" | "queue"> & {
696
+ /** Zero-based submission order within this attempt. */readonly exchangeIndex: number; /** Enabled capture result; absent when disabled or no response was received. */
697
+ readonly bodyCapture?: OutboundBodyCaptureSummary; /** Immutable facts of the request actually submitted. */
698
+ readonly request: {
699
+ readonly url: string;
700
+ readonly method: string; /** Selected header snapshot; absent when request header capture is disabled. */
701
+ readonly headers?: OutboundHeaderSnapshot;
702
+ }; /** A received response, including non-2xx, or the original fetch rejection. */
703
+ readonly outcome: {
704
+ readonly type: "response";
705
+ readonly response: OutboundHttpResponseMetadata & {
706
+ /** Selected snapshot; absent when response header capture is disabled. */readonly headers?: OutboundHeaderSnapshot;
707
+ };
708
+ } | {
709
+ readonly type: "failed";
710
+ readonly error: unknown;
711
+ };
712
+ };
713
+ /**
714
+ * A worker retry or deferral submission, separate from its enqueue outcome.
715
+ * @since 2.5.0
716
+ */
717
+ type OutboundWorkerSchedule = {
718
+ readonly action: "retry" | "defer"; /** Actual continuation task and Fedify retry counter. */
719
+ readonly queue: NonNullable<OutboundAttemptReport["queue"]>; /** Actual affected routes, copied independently of the planned delivery. */
720
+ readonly inboxUrls: readonly string[]; /** ISO 8601 duration passed to enqueue after delay normalization. */
721
+ readonly delay: string; /** Per-route ISO instants retained by the scheduled continuation, if any. */
722
+ readonly notBefore?: Readonly<Record<string, string>>; /** Local preparation failures that contributed to this actual action. */
723
+ readonly preparationFailures?: readonly OutboundWorkerPreparationDiagnostic[];
724
+ };
725
+ /**
726
+ * One local preparation failure associated with a worker route.
727
+ * @since 2.5.0
728
+ */
729
+ type OutboundWorkerPreparationDiagnostic = {
730
+ /** Route whose local setup produced the failure. */readonly inboxUrl: string; /** Existing worker preparation classification and original thrown value. */
731
+ readonly failure: Extract<OutboundWorkerReport["result"], {
732
+ type: "preparation-failed";
733
+ }>["failure"];
734
+ };
735
+ /**
736
+ * A child outbox task submitted by a fanout worker.
737
+ * @since 2.5.0
738
+ */
739
+ type OutboundWorkerChild = {
740
+ readonly action: "enqueue"; /** Actual child task identity and initial Fedify retry counter. */
741
+ readonly queue: NonNullable<OutboundAttemptReport["queue"]>; /** Routes submitted with this child task. */
742
+ readonly inboxUrls: readonly string[]; /** Stable child obligation, independent of its task identity. */
743
+ readonly delivery?: OutboundWorkerReport["delivery"];
744
+ };
745
+ /**
746
+ * An observed worker handoff, scheduling outcome, or local abandonment.
747
+ * @since 2.5.0
748
+ */
749
+ type OutboundWorkerAction = ((OutboundWorkerSchedule | OutboundWorkerChild) & {
750
+ readonly type: "scheduled";
751
+ }) | ((OutboundWorkerSchedule | OutboundWorkerChild) & {
752
+ readonly type: "action-failed";
753
+ readonly acceptance: "unknown";
754
+ readonly error: unknown;
755
+ }) | {
756
+ readonly type: "handoff";
757
+ readonly action: "direct"; /** Actual concrete sender target; entry does not establish HTTP success. */
758
+ readonly inboxUrls: readonly string[]; /** Planned obligation retained across gateway fallback targets. */
759
+ readonly delivery?: OutboundWorkerReport["delivery"];
760
+ } | {
761
+ readonly type: "abandoned";
762
+ readonly reason: "held-ttl-expired" | "permanent-failure" | "retry-policy-declined" | "queue-unavailable"; /** Routes locally abandoned; other routes of the delivery may continue. */
763
+ readonly inboxUrls: readonly string[]; /** Local preparation failures that contributed to this abandonment. */
764
+ readonly preparationFailures?: readonly OutboundWorkerPreparationDiagnostic[];
765
+ } | {
766
+ readonly type: "action-failed";
767
+ readonly action: "drop";
768
+ readonly inboxUrls: readonly string[];
769
+ readonly error: unknown;
770
+ };
771
+ /**
772
+ * One fanout or outbox worker execution after its local processing settles.
773
+ *
774
+ * Emitted after the local worker path and its instrumentation settle. A normal
775
+ * return can include handled send failures or abandonment; completion does not
776
+ * establish HTTP success or backend acknowledgment. Actions retain local route
777
+ * decisions and actual enqueue settlement, separately from attempt outcomes.
778
+ * A native rethrow does not establish a scheduled continuation or future replay.
779
+ * @since 2.5.0
780
+ */
781
+ type OutboundWorkerReport = OutboundSourceMetadata & {
782
+ /** Current outgoing queue role; inbox/custom-task workers are separate. */readonly role: "fanout" | "outbox"; /** Actual task and available Fedify retry counter for this execution. */
783
+ readonly queue: NonNullable<OutboundAttemptReport["queue"]>; /** Concrete sender entries, independent of enabled attempt/exchange callbacks. */
784
+ readonly attemptsStarted: number;
785
+ /**
786
+ * Observed enqueue settlement, concrete direct-entry, and local abandonment.
787
+ * Accepted actions survive later worker failure; rejected batches leave each
788
+ * submitted child uncertain. Empty when no action was observed by settlement.
789
+ */
790
+ readonly actions: readonly OutboundWorkerAction[];
791
+ /**
792
+ * Caught preparation failures retained when later routes succeed or when
793
+ * the worker exits without scheduling a continuation. Omitted when none.
794
+ */
795
+ readonly preparationFailures?: readonly OutboundWorkerPreparationDiagnostic[];
796
+ /**
797
+ * Outbox obligation, with unavailable legacy relationships omitted.
798
+ * Fanout has no single delivery; its child actions carry their own references.
799
+ */
800
+ readonly delivery?: Pick<OutboundAttemptReport, "broadcast" | "deliveryId" | "destinationInbox" | "portableInbox">;
801
+ /**
802
+ * Actual local settlement, separate from individual attempts and queue actions.
803
+ * Preparation failures identify the operation that produced the escaping error.
804
+ */
805
+ readonly result: {
806
+ readonly type: "completed";
807
+ } | {
808
+ readonly type: "failed";
809
+ readonly error: unknown;
810
+ } | {
811
+ readonly type: "preparation-failed";
812
+ readonly failure: {
813
+ readonly kind: "local";
814
+ readonly phase: "key-import" | "activity-parsing" | "context-preparation" | "validation" | "key-resolution" | "signing" | "serialization" | "key-export" | "request-preparation";
815
+ readonly error: unknown;
816
+ }; /** Exact error escaping the worker preparation operation. */
817
+ readonly error: unknown;
818
+ };
819
+ };
820
+ /**
821
+ * Optional observers of outgoing activity processing.
822
+ *
823
+ * Send, original-payload forwarding, and automatic portable replication producer
824
+ * events are observed, including outbox-context operations and fanout
825
+ * continuation handoffs. Concrete attempts and actual fetch exchanges include
826
+ * queue workers, signature challenges, redirects, and alternate gateways.
827
+ * Worker reports describe local fanout/outbox settlement and actual queue
828
+ * actions; they do not establish backend acknowledgment or future execution.
829
+ * Known IDs and provenance survive queue continuations; callback context data
830
+ * belongs to the current execution. Each callback is independently optional.
831
+ * Metadata callbacks are awaited without a default timeout. An enabled body
832
+ * consumer instead uses its explicit bounded capture lifetime.
833
+ * Their errors, including errors logging observer failures, do not replace the
834
+ * send's result.
835
+ * A metadata callback that never settles can delay execution indefinitely. Detached
836
+ * recording must handle its own rejection and shutdown; no persistence is
837
+ * guaranteed. Existing portable forwarding deadlines may return while
838
+ * background preparation, sending, and observer callbacks continue.
839
+ * @template TContextData Application context data.
840
+ * @since 2.5.0
841
+ */
842
+ interface OutboundObservers<TContextData> {
843
+ /**
844
+ * Opt-in immutable privacy projection of concrete sender input on attempt reports.
845
+ * Disabled by default. Unsupported context/data semantics yield no raw fallback;
846
+ * this is not exact wire evidence or proof that redirected requests kept a body.
847
+ */
848
+ readonly senderPayload?: boolean;
849
+ /**
850
+ * Independent opt-in body consumer; explicit byte/time limits are required.
851
+ * Shares one source reader with separately bounded diagnostics, without cloning
852
+ * or whole-body accumulation for the application. Finished reports hold summaries.
853
+ */
854
+ readonly responseBodyCapture?: OutboundResponseBodyCapture<TContextData>;
855
+ /**
856
+ * Opt-in actual request headers for exchange reports; disabled by default.
857
+ * Protected credentials, signatures, and cookies remain redacted even if allowed.
858
+ */
859
+ readonly requestHeaders?: OutboundHeaderPolicy;
860
+ /** Independent opt-in response header policy for exchange reports. */
861
+ readonly responseHeaders?: OutboundHeaderPolicy;
862
+ /**
863
+ * Observes terminal outgoing worker processing with current context/data.
864
+ * Errors are isolated; results do not replace attempt outcomes or establish
865
+ * future backend execution. Actions retain actual accepted/uncertain child
866
+ * handoffs and retry/defer/drop facts, separately from attempt outcomes.
867
+ */
868
+ readonly onWorker?: (context: Context<TContextData>, report: OutboundWorkerReport) => void | Promise<void>;
869
+ /**
870
+ * Observes each actual fetch after response headers or rejection, before
871
+ * subsequent challenge/redirect handling. Non-2xx responses are still response
872
+ * outcomes. Pre-fetch failures emit no exchange. Errors cannot veto transport.
873
+ */
874
+ readonly onHttpExchange?: (context: Context<TContextData>, report: OutboundHttpExchangeReport) => void | Promise<void>;
875
+ /**
876
+ * Observes settlement of concrete sends and forwarding, including workers
877
+ * and pre-fetch failures. Holds, drops, and failures before sender entry emit
878
+ * no attempt. Final HTTP evidence, local diagnostics, and the outward error
879
+ * remain distinct. Callback errors cannot replace the sender's result.
880
+ */
881
+ readonly onAttempt?: (context: Context<TContextData>, report: OutboundAttemptReport) => void | Promise<void>;
882
+ /**
883
+ * Observes invocation entry and the current producer's preparation result.
884
+ * The existing context is passed without loading data for observation.
885
+ * Returning or throwing cannot veto sending. Earlier immediate sends may
886
+ * already be running when preparation finishes; their outcomes are separate.
887
+ */
888
+ readonly onBroadcast?: (context: Context<TContextData>, event: OutboundBroadcastEvent) => void | Promise<void>;
889
+ /**
890
+ * Observes producer-side planning, handoffs, and destination preparation
891
+ * failures, including fanout continuation handoffs. Filtered recipients do
892
+ * not become planned deliveries. Concrete attempts and exchanges have their
893
+ * own callbacks. These events do not report remote success or worker
894
+ * scheduling outcomes.
895
+ */
896
+ readonly onDelivery?: (context: Context<TContextData>, event: OutboundDeliveryEvent) => void | Promise<void>;
897
+ }
898
+ /** URL facts captured independently of a mutable context URL. @since 2.5.0 */
899
+ interface OutboundLocalContext {
900
+ /** Scheme and authority of the context URL. */
901
+ readonly origin: string;
902
+ /** URL hostname with its optional port. */
903
+ readonly host: string;
904
+ /** URL hostname without a port. */
905
+ readonly hostname: string;
906
+ /** Configured canonical web origin, falling back to this context's origin. */
907
+ readonly canonicalOrigin: string;
908
+ }
909
+ /** The original routed HTTP inbox request that caused outgoing work. @since 2.5.0 */
910
+ interface OutboundInboxRequest {
911
+ /** Fresh server-generated identity, independent of Activity and trace IDs. */
912
+ readonly inboxRequestId: string;
913
+ /** Informational logging label; externally controlled and nonunique. */
914
+ readonly requestId: string;
915
+ /** Original request URL facts, preserved separately from current context. */
916
+ readonly context: OutboundLocalContext;
917
+ }
918
+ /** Explicit historical cause of outgoing work. @since 2.5.0 */
919
+ interface OutboundProvenance {
920
+ /**
921
+ * Exact application-owned correlation, up to 1,024 UTF-8 bytes; an empty
922
+ * string is valid. Never a Fedify deduplication key. Each new public call
923
+ * supplies its own value; queued continuations retain it.
924
+ */
925
+ readonly dispatchId?: string;
926
+ /** Absent for independent contexts, programmatic routing, and unknown work. */
927
+ readonly inboxRequest?: OutboundInboxRequest;
928
+ /** Original logging label for request-derived work without an inbox cause. */
929
+ readonly originatingRequestId?: string;
930
+ }
931
+ /**
932
+ * Observation time, optional tracing, original provenance, and current context.
933
+ * @since 2.5.0
934
+ */
935
+ interface OutboundSourceMetadata {
936
+ /** Unix milliseconds at observation; wall clocks do not ensure global ordering. */
937
+ readonly observedAt: number;
938
+ /** Valid IDs of this operation's own span, when one is available. */
939
+ readonly trace?: {
940
+ readonly traceId: string;
941
+ readonly spanId: string;
942
+ };
943
+ /** Immutable historical cause, distinct from this callback's context. */
944
+ readonly provenance: OutboundProvenance;
945
+ /** URL facts of the current callback context. */
946
+ readonly local: OutboundLocalContext;
947
+ }
948
+ //#endregion
419
949
  //#region src/federation/activity-listener.d.ts
420
950
  /**
421
951
  * Options for an inbox listener.
@@ -453,6 +983,13 @@ interface InboxListenerOptions {
453
983
  type InboxJsonValue = null | boolean | number | string | readonly InboxJsonValue[] | {
454
984
  readonly [key: string]: InboxJsonValue;
455
985
  };
986
+ /** Request-signal state at inbox report construction, separate from processing outcome. @since 2.5.0 */
987
+ type InboxRequestCancellation = {
988
+ readonly type: "not-aborted";
989
+ } | {
990
+ readonly type: "aborted"; /** Exact Request.signal reason, including non-Error values. */
991
+ readonly reason: unknown;
992
+ };
456
993
  /**
457
994
  * The final inbox authentication decision.
458
995
  * @since 2.4.0
@@ -529,6 +1066,31 @@ type InboxRequestOutcome = {
529
1066
  * @since 2.4.0
530
1067
  */
531
1068
  interface InboxRequestReport {
1069
+ /**
1070
+ * Fresh server-generated identity for one routed HTTP inbox request,
1071
+ * including preparation failures and duplicate deliveries. Preserved on
1072
+ * outgoing causal descendants; absent without a known routed source.
1073
+ * @since 2.5.0
1074
+ */
1075
+ readonly inboxRequestId?: string;
1076
+ /**
1077
+ * Original informational logging label; externally controlled and nonunique,
1078
+ * independent of inboxRequestId. Absent without a known routed source.
1079
+ * @since 2.5.0
1080
+ */
1081
+ readonly requestId?: string;
1082
+ /**
1083
+ * Original local URL facts from the routed request, independent of later contexts.
1084
+ * Absent without a known routed source.
1085
+ * @since 2.5.0
1086
+ */
1087
+ readonly local?: OutboundLocalContext;
1088
+ /**
1089
+ * Request.signal state immediately before report construction. An aborted request
1090
+ * does not imply processing stopped or retract confirmed processing/enqueue outcomes.
1091
+ * @since 2.5.0
1092
+ */
1093
+ readonly requestCancellation: InboxRequestCancellation;
532
1094
  /** The delivery route, including a portable gateway route. */
533
1095
  readonly inbox: {
534
1096
  readonly kind: "personal" | "shared" | "portable";
@@ -579,6 +1141,379 @@ interface PageItems<TItem> {
579
1141
  readonly items: readonly TItem[];
580
1142
  }
581
1143
  //#endregion
1144
+ //#region src/federation/inbox-authentication.d.ts
1145
+ /**
1146
+ * A party that Fedify authenticated for an incoming activity, independently
1147
+ * of the activity's declared `actor`.
1148
+ * @since 2.5.0
1149
+ */
1150
+ interface InboxPrincipal {
1151
+ /**
1152
+ * What {@link InboxPrincipal.id} is:
1153
+ *
1154
+ * - `"actor"`: an actor whose ownership of the verifying key Fedify
1155
+ * confirmed with the actor's own document.
1156
+ * - `"portable-authority"`: a DID whose verification method made a valid
1157
+ * Object Integrity Proof. It authenticates the [FEP-ef61] portable
1158
+ * actors of that DID, and nothing else.
1159
+ *
1160
+ * [FEP-ef61]: https://w3id.org/fep/ef61
1161
+ */
1162
+ readonly kind: "actor" | "portable-authority";
1163
+ /** The actor ID, or the DID if {@link kind} is `"portable-authority"`. */
1164
+ readonly id: URL;
1165
+ /**
1166
+ * How the principal was authenticated:
1167
+ *
1168
+ * - `"http-signatures"`: it signed the HTTP request.
1169
+ * - `"linked-data-signatures"`: it made the Linked Data Signature of the
1170
+ * activity.
1171
+ * - `"object-integrity-proofs"`: it made an Object Integrity Proof of the
1172
+ * activity.
1173
+ * - `"origin"`: the activity was fetched again from the actor's own web
1174
+ * origin, which happens only for `Context.routeActivity()`.
1175
+ */
1176
+ readonly mechanism: "http-signatures" | "linked-data-signatures" | "object-integrity-proofs" | "origin";
1177
+ /** The key or verification method that authenticated the principal. */
1178
+ readonly keyId: URL | null;
1179
+ }
1180
+ /**
1181
+ * Who Fedify authenticated for an incoming activity, and the interpretation of
1182
+ * the activity it authenticated. Inbox listeners receive it as
1183
+ * {@link InboxContext.authentication}.
1184
+ *
1185
+ * Authenticated principals are independent of the activity's declared
1186
+ * `actor`: an activity forwarded by a relay is signed by the relay in its
1187
+ * HTTP request, but its document may be signed by its author, and some
1188
+ * activities, such as [FEP-7aa9] `FeatureRequest`s sent by Mastodon, declare
1189
+ * no `actor` at all. A valid signature alone does not authorize anything;
1190
+ * use {@link isActorAuthenticated} to check whether a specific actor was
1191
+ * authenticated.
1192
+ *
1193
+ * [FEP-7aa9]: https://w3id.org/fep/7aa9
1194
+ * @since 2.5.0
1195
+ */
1196
+ interface InboxAuthenticationEvidence {
1197
+ /**
1198
+ * Whether signatures were verified at all. It is `false` only when
1199
+ * signature verification is skipped, or when there is no evidence, e.g.,
1200
+ * for an activity queued by an older version of Fedify. In that case all
1201
+ * the principal lists are empty.
1202
+ */
1203
+ readonly verified: boolean;
1204
+ /**
1205
+ * The principals that signed the HTTP request, i.e., the server that
1206
+ * delivered the activity, which may have forwarded it. It is empty when
1207
+ * the activity document was authenticated by its own signatures, so that
1208
+ * HTTP Signatures did not need to be verified.
1209
+ */
1210
+ readonly requestSigners: readonly InboxPrincipal[];
1211
+ /**
1212
+ * The principals that made the verified Linked Data Signature or Object
1213
+ * Integrity Proofs of the activity document. All of them are listed when
1214
+ * there are several proofs.
1215
+ */
1216
+ readonly documentSigners: readonly InboxPrincipal[];
1217
+ /**
1218
+ * The actors authenticated because `Context.routeActivity()` fetched the
1219
+ * activity again from their own web origin. It is always empty for
1220
+ * activities delivered to an inbox.
1221
+ */
1222
+ readonly originAuthenticated: readonly InboxPrincipal[];
1223
+ /**
1224
+ * The activity as Fedify interpreted it while authenticating it, in
1225
+ * expanded JSON-LD. Typed vocabulary objects omit some values, such as
1226
+ * blank node references and values they cannot decode, so authorization
1227
+ * that has to count every value of a property, e.g., that a property has
1228
+ * exactly one value, should inspect this instead. It is deeply frozen.
1229
+ * It is empty when there is no evidence.
1230
+ */
1231
+ readonly expandedActivity: readonly Record<string, unknown>[];
1232
+ /**
1233
+ * The base URL the activity was decoded with, e.g., the URL an activity
1234
+ * fetched by `Context.routeActivity()` was finally retrieved from, or
1235
+ * `null` if none was given. Relative IRIs left in
1236
+ * {@link expandedActivity} resolve against it, or against the activity's
1237
+ * own `@id` if it is `null`, as the decoder resolves them.
1238
+ */
1239
+ readonly baseUrl: URL | null;
1240
+ }
1241
+ /**
1242
+ * A {@link InboxPrincipal} as stored in a queued message.
1243
+ * The wire spellings remain independent of the public API spellings.
1244
+ * @internal
1245
+ */
1246
+ interface SerializedInboxPrincipal {
1247
+ readonly kind: "actor" | "portableAuthority";
1248
+ readonly id: string;
1249
+ readonly mechanism: "httpSignatures" | "linkedDataSignatures" | "objectIntegrityProofs" | "origin";
1250
+ readonly keyId: string | null;
1251
+ }
1252
+ /**
1253
+ * The principals of an {@link InboxAuthenticationEvidence} as stored in
1254
+ * a queued message. The expanded activity and the base URL are restored
1255
+ * from the queued verified view instead.
1256
+ * @internal
1257
+ */
1258
+ interface SerializedInboxAuthentication {
1259
+ readonly verified: boolean;
1260
+ readonly requestSigners: readonly SerializedInboxPrincipal[];
1261
+ readonly documentSigners: readonly SerializedInboxPrincipal[];
1262
+ readonly originAuthenticated: readonly SerializedInboxPrincipal[];
1263
+ }
1264
+ //#endregion
1265
+ //#region src/sig/ld.d.ts
1266
+ /**
1267
+ * How a context loader marked by {@link markEffectiveContextLoader} already
1268
+ * resolves contexts.
1269
+ * @internal
1270
+ */
1271
+ type EffectiveContextLoaderPolicy = "standard" | "normalization";
1272
+ //#endregion
1273
+ //#region src/federation/inbox-view.d.ts
1274
+ /**
1275
+ * A {@link ContextSnapshotEntry} as stored in a queued message. A response
1276
+ * that equals the one Fedify bundles for the URL is stored as a reference to
1277
+ * the bundled context, along with its hash for diagnostics.
1278
+ * @internal
1279
+ */
1280
+ type SerializedContextSnapshotEntry = {
1281
+ readonly policy: EffectiveContextLoaderPolicy;
1282
+ readonly url: string; /** The SHA-256 of the canonical JSON of the whole response. */
1283
+ readonly sha256: string;
1284
+ } & ({
1285
+ readonly bundled: true;
1286
+ } | {
1287
+ readonly response: RemoteDocument;
1288
+ });
1289
+ /**
1290
+ * The interpretation of an incoming activity that Fedify authenticated, as
1291
+ * stored in a queued message. The document itself is not copied; it is
1292
+ * a field of the message.
1293
+ * @internal
1294
+ */
1295
+ interface SerializedVerifiedActivityView {
1296
+ /** The format version. */
1297
+ readonly version: 1;
1298
+ /** The context loader policy the activity was parsed with. */
1299
+ readonly policy: EffectiveContextLoaderPolicy;
1300
+ /**
1301
+ * Which document the activity was parsed from, with its Linked Data
1302
+ * Signature detached: the `activity` of the message, i.e., the payload as
1303
+ * received, or its `normalizedActivity`, i.e., the payload compacted for
1304
+ * Linked Data Signatures.
1305
+ */
1306
+ readonly source: "activity" | "normalizedActivity";
1307
+ /** The `baseUrl` option the activity was parsed with. */
1308
+ readonly baseUrl: string | null;
1309
+ /** The context responses the interpretation used. */
1310
+ readonly contexts: readonly SerializedContextSnapshotEntry[];
1311
+ /** The digest of the expanded activity. */
1312
+ readonly expandedDigest: string;
1313
+ }
1314
+ //#endregion
1315
+ //#region src/federation/queue.d.ts
1316
+ /** Serializable grouping facts, separate from queue task identity. @internal */
1317
+ interface SerializedOutboundDelivery {
1318
+ readonly broadcast: {
1319
+ readonly type: "known";
1320
+ readonly id: string;
1321
+ } | {
1322
+ readonly type: "unknown";
1323
+ };
1324
+ readonly deliveryId: string;
1325
+ readonly destinationInbox: string;
1326
+ readonly portableInbox?: string;
1327
+ }
1328
+ interface SenderKeyJwkPair {
1329
+ readonly keyId: string;
1330
+ readonly privateKey: JsonWebKey;
1331
+ }
1332
+ /**
1333
+ * A message that represents a task to be processed by the background worker.
1334
+ * The concrete type of the message depends on the `type` property.
1335
+ *
1336
+ * Please do not depend on the concrete types of the messages, as they may
1337
+ * change in the future. You should treat the `Message` type as an opaque
1338
+ * type.
1339
+ * @since 1.6.0
1340
+ */
1341
+ type Message = FanoutMessage | OutboxMessage | InboxMessage | TaskMessage;
1342
+ interface FanoutMessage {
1343
+ /** Historical source facts; absent on legacy/independent messages. */
1344
+ readonly provenance?: OutboundProvenance;
1345
+ readonly type: "fanout";
1346
+ readonly id: ReturnType<typeof crypto.randomUUID>;
1347
+ readonly baseUrl: string;
1348
+ readonly keys: readonly SenderKeyJwkPair[];
1349
+ readonly inboxes: Readonly<Record<string, {
1350
+ readonly actorIds: readonly string[];
1351
+ readonly sharedInbox: boolean; /** Optional for continuations created before outbound observation. */
1352
+ readonly outbound?: SerializedOutboundDelivery;
1353
+ /**
1354
+ * The canonical form of the FEP-ef61 portable inbox that the inbox
1355
+ * stands for.
1356
+ * @internal
1357
+ */
1358
+ readonly portableInbox?: string;
1359
+ /**
1360
+ * The compatible identifiers of the portable inbox on each gateway to
1361
+ * deliver through, in order.
1362
+ * @internal
1363
+ */
1364
+ readonly gatewayInboxes?: readonly string[];
1365
+ }>>;
1366
+ readonly activity: unknown;
1367
+ readonly activityId?: string;
1368
+ readonly activityType: string;
1369
+ readonly collectionSync?: string;
1370
+ readonly orderingKey?: string;
1371
+ /**
1372
+ * Whether to apply outgoing JSON-LD wire-format normalization to queued
1373
+ * activities that already carry Object Integrity Proofs.
1374
+ *
1375
+ * `true` is used for proofs Fedify created before fanout, or when callers
1376
+ * explicitly request normalization for locally pre-signed activities.
1377
+ * `false`/`undefined` preserves existing proofs as-is.
1378
+ */
1379
+ readonly normalizeExistingProofs?: boolean;
1380
+ readonly traceContext: Readonly<Record<string, string>>;
1381
+ }
1382
+ interface OutboxMessage {
1383
+ /** Historical source facts; absent on legacy/independent messages. */
1384
+ readonly provenance?: OutboundProvenance;
1385
+ readonly type: "outbox";
1386
+ readonly id: ReturnType<typeof crypto.randomUUID>;
1387
+ /** Original delivery identity; absent on legacy/unobserved producers. */
1388
+ readonly outbound?: SerializedOutboundDelivery;
1389
+ readonly baseUrl: string;
1390
+ readonly keys: readonly SenderKeyJwkPair[];
1391
+ readonly activity: unknown;
1392
+ readonly activityId?: string;
1393
+ readonly activityType: string;
1394
+ readonly inbox: string;
1395
+ readonly sharedInbox: boolean;
1396
+ readonly actorIds?: readonly string[];
1397
+ readonly started: string;
1398
+ readonly attempt: number;
1399
+ readonly headers: Readonly<Record<string, string>>;
1400
+ readonly orderingKey?: string;
1401
+ /**
1402
+ * Whether this message is currently held by the outbound circuit breaker.
1403
+ * @internal
1404
+ */
1405
+ readonly circuitHeld?: true;
1406
+ /**
1407
+ * When Fedify first held this message because the remote host circuit was
1408
+ * open.
1409
+ * @internal
1410
+ */
1411
+ readonly circuitHeldSince?: string;
1412
+ /**
1413
+ * The canonical form of the FEP-ef61 portable inbox that {@link inbox}, its
1414
+ * compatible identifier on a gateway, stands for.
1415
+ * @internal
1416
+ */
1417
+ readonly portableInbox?: string;
1418
+ /**
1419
+ * The compatible identifiers of the FEP-ef61 portable inbox on each gateway
1420
+ * that is still to be tried, in order. {@link inbox} is the first one.
1421
+ * If there is more than one, the activity is delivered through the first
1422
+ * gateway that accepts it.
1423
+ * @internal
1424
+ */
1425
+ readonly gatewayInboxes?: readonly string[];
1426
+ /**
1427
+ * The times before which each of {@link gatewayInboxes} must not be tried
1428
+ * again, as their responses' `Retry-After` headers requested, keyed by
1429
+ * the inbox URL, in ISO 8601.
1430
+ * @internal
1431
+ */
1432
+ readonly gatewayNotBefore?: Readonly<Record<string, string>>;
1433
+ readonly traceContext: Readonly<Record<string, string>>;
1434
+ }
1435
+ /**
1436
+ * A message that carries a custom background task. Every field is
1437
+ * a string, number, or plain record so that the message survives both
1438
+ * JSON serialization and structured clone on every queue backend.
1439
+ * @since 2.4.0
1440
+ */
1441
+ interface TaskMessage {
1442
+ readonly type: "task";
1443
+ readonly id: ReturnType<typeof crypto.randomUUID>;
1444
+ readonly baseUrl: string;
1445
+ readonly taskName: string;
1446
+ /** devalue-encoded task data; vocab objects bridged to expanded JSON-LD. */
1447
+ readonly data: string;
1448
+ readonly started: string;
1449
+ readonly attempt: number;
1450
+ readonly orderingKey?: string;
1451
+ readonly traceContext: Readonly<Record<string, string>>;
1452
+ }
1453
+ interface InboxMessage {
1454
+ /** Historical source facts; absent on legacy/independent messages. */
1455
+ readonly provenance?: OutboundProvenance;
1456
+ readonly type: "inbox";
1457
+ readonly id: ReturnType<typeof crypto.randomUUID>;
1458
+ readonly baseUrl: string;
1459
+ readonly activity: unknown;
1460
+ /**
1461
+ * The normalized JSON-LD representation of a signed inbox activity that
1462
+ * Fedify already compacted successfully while accepting the request. Queue
1463
+ * workers can reuse this producer-side parse cache under stricter loader or
1464
+ * network constraints without changing the raw payload preserved for
1465
+ * forwarding.
1466
+ *
1467
+ * This may exist even when {@link ldSignatureVerified} is `false`, because
1468
+ * fallback-authenticated traffic and already-queued backlog items can still
1469
+ * depend on the cached normalized form to avoid re-fetching remote custom
1470
+ * contexts during worker processing.
1471
+ *
1472
+ * This is optional for backward compatibility with messages that were
1473
+ * queued by older Fedify versions or that were already in a queue before
1474
+ * upgrading.
1475
+ *
1476
+ * Fedify keeps this on the queued message itself instead of an external
1477
+ * sidecar because generic queue backends do not provide reliable lifecycle
1478
+ * guarantees for auxiliary storage across retries and redeliveries.
1479
+ *
1480
+ * @internal
1481
+ */
1482
+ readonly normalizedActivity?: unknown;
1483
+ /**
1484
+ * Whether the producer actually verified the Linked Data Signature before
1485
+ * queueing this message. This lets workers distinguish verified LDS replay
1486
+ * from other authenticated inbox traffic that merely happened to include a
1487
+ * signature block. This provenance marker is separate from the optional
1488
+ * normalizedActivity parse cache.
1489
+ *
1490
+ * `undefined` preserves backward compatibility with older queued messages
1491
+ * that predate this marker.
1492
+ *
1493
+ * @internal
1494
+ */
1495
+ readonly ldSignatureVerified?: boolean;
1496
+ /**
1497
+ * The interpretation of the activity that Fedify authenticated, i.e., which
1498
+ * document it was parsed from and every JSON-LD context response the parse
1499
+ * used, so that the worker and its retries dispatch the very same
1500
+ * interpretation without loading contexts again. Absent in messages queued
1501
+ * by older Fedify versions.
1502
+ * @internal
1503
+ */
1504
+ readonly verifiedView?: SerializedVerifiedActivityView;
1505
+ /**
1506
+ * Who Fedify authenticated for the activity. Absent in messages queued by
1507
+ * older Fedify versions.
1508
+ * @internal
1509
+ */
1510
+ readonly authentication?: SerializedInboxAuthentication;
1511
+ readonly started: string;
1512
+ readonly attempt: number;
1513
+ readonly identifier: string | null;
1514
+ readonly traceContext: Readonly<Record<string, string>>;
1515
+ }
1516
+ //#endregion
582
1517
  //#region src/federation/send.d.ts
583
1518
  /**
584
1519
  * A key pair for an actor who sends an activity.
@@ -613,7 +1548,9 @@ declare class SendActivityError extends Error {
613
1548
  * The response body from the inbox, if any. Note that this may be
614
1549
  * truncated to a maximum of 1 KiB to prevent excessive memory consumption
615
1550
  * when remote servers return large error pages (e.g., Cloudflare error pages).
616
- * If truncated, the string will end with `"… (truncated)"`.
1551
+ * If truncated, the string will end with `"… (truncated)"`. Diagnostic reading
1552
+ * has a separate one-second budget; a deadline retains any partial preview and
1553
+ * appends `"… (timed out)"`. This is independent of application body capture.
617
1554
  */
618
1555
  readonly responseBody: string;
619
1556
  /**
@@ -1334,238 +2271,67 @@ interface MessageQueue {
1334
2271
  *
1335
2272
  * @default `false`
1336
2273
  * @since 2.4.0
1337
- */
1338
- readonly nativeDeduplication?: boolean;
1339
- /**
1340
- * Whether {@link enqueueMany} accepts either every message or no messages.
1341
- * Backends that implement `enqueueMany()` by sending messages individually
1342
- * must set this to `false`, so Fedify does not use that method for a batch
1343
- * governed by one `deduplicationKey`. `true` by default when `enqueueMany`
1344
- * is implemented.
1345
- *
1346
- * @default `true`
1347
- * @since 2.4.0
1348
- */
1349
- readonly atomicEnqueueMany?: boolean;
1350
- /**
1351
- * The largest message, in bytes, that the backend can hold. Fedify compares
1352
- * it with a conservative estimate of a message's serialized size: the
1353
- * length of its JSON serialization encoded in UTF-8, plus 8 bytes for each
1354
- * JSON value in it, plus what encoding strings beyond Latin-1 as UTF-16
1355
- * adds, which also bounds binary serialization formats. When an
1356
- * incoming activity would make a larger inbox message, Fedify processes the
1357
- * activity right away instead of enqueueing it, as if there were no inbox
1358
- * queue. When omitted, messages are enqueued regardless of their size.
1359
- * @since 2.5.0
1360
- */
1361
- readonly maxMessageSize?: number;
1362
- /**
1363
- * Enqueues a message in the queue.
1364
- * @param message The message to enqueue.
1365
- * @param options Additional options for enqueuing the message.
1366
- */
1367
- enqueue(message: any, options?: MessageQueueEnqueueOptions): Promise<void>;
1368
- /**
1369
- * Enqueues multiple messages in the queue. This operation is optional,
1370
- * and may not be supported by all implementations. If not supported,
1371
- * Fedify will invoke {@link enqueue} for each message. An implementation
1372
- * that may accept only part of a batch before rejecting must declare
1373
- * {@link atomicEnqueueMany} `false`.
1374
- *
1375
- * @param messages The messages to enqueue.
1376
- * @param options Additional options for enqueuing the messages.
1377
- */
1378
- enqueueMany?: (messages: readonly any[], options?: MessageQueueEnqueueOptions) => Promise<void>;
1379
- /**
1380
- * Listens for messages in the queue.
1381
- * @param handler The handler for messages in the queue.
1382
- * @param options Additional options for listening to the message queue.
1383
- * @returns A promise that resolves when the listening is done. It never
1384
- * rejects, and is resolved when the signal is aborted. If no
1385
- * signal is provided, it never resolves.
1386
- */
1387
- listen(handler: (message: any) => Promise<void> | void, options?: MessageQueueListenOptions): Promise<void>;
1388
- /**
1389
- * Gets the number of messages waiting in the queue.
1390
- *
1391
- * This operation is optional, and may not be supported by all
1392
- * implementations. The returned counts exclude messages currently being
1393
- * handled by a worker.
1394
- *
1395
- * @since 2.3.0
1396
- */
1397
- getDepth?(): Promise<MessageQueueDepth>;
1398
- }
1399
- //#endregion
1400
- //#region src/federation/inbox-authentication.d.ts
1401
- /**
1402
- * A party that Fedify authenticated for an incoming activity, independently
1403
- * of the activity's declared `actor`.
1404
- * @since 2.5.0
1405
- */
1406
- interface InboxPrincipal {
1407
- /**
1408
- * What {@link InboxPrincipal.id} is:
1409
- *
1410
- * - `"actor"`: an actor whose ownership of the verifying key Fedify
1411
- * confirmed with the actor's own document.
1412
- * - `"portable-authority"`: a DID whose verification method made a valid
1413
- * Object Integrity Proof. It authenticates the [FEP-ef61] portable
1414
- * actors of that DID, and nothing else.
1415
- *
1416
- * [FEP-ef61]: https://w3id.org/fep/ef61
1417
- */
1418
- readonly kind: "actor" | "portable-authority";
1419
- /** The actor ID, or the DID if {@link kind} is `"portable-authority"`. */
1420
- readonly id: URL;
1421
- /**
1422
- * How the principal was authenticated:
1423
- *
1424
- * - `"http-signatures"`: it signed the HTTP request.
1425
- * - `"linked-data-signatures"`: it made the Linked Data Signature of the
1426
- * activity.
1427
- * - `"object-integrity-proofs"`: it made an Object Integrity Proof of the
1428
- * activity.
1429
- * - `"origin"`: the activity was fetched again from the actor's own web
1430
- * origin, which happens only for `Context.routeActivity()`.
1431
- */
1432
- readonly mechanism: "http-signatures" | "linked-data-signatures" | "object-integrity-proofs" | "origin";
1433
- /** The key or verification method that authenticated the principal. */
1434
- readonly keyId: URL | null;
1435
- }
1436
- /**
1437
- * Who Fedify authenticated for an incoming activity, and the interpretation of
1438
- * the activity it authenticated. Inbox listeners receive it as
1439
- * {@link InboxContext.authentication}.
1440
- *
1441
- * Authenticated principals are independent of the activity's declared
1442
- * `actor`: an activity forwarded by a relay is signed by the relay in its
1443
- * HTTP request, but its document may be signed by its author, and some
1444
- * activities, such as [FEP-7aa9] `FeatureRequest`s sent by Mastodon, declare
1445
- * no `actor` at all. A valid signature alone does not authorize anything;
1446
- * use {@link isActorAuthenticated} to check whether a specific actor was
1447
- * authenticated.
1448
- *
1449
- * [FEP-7aa9]: https://w3id.org/fep/7aa9
1450
- * @since 2.5.0
1451
- */
1452
- interface InboxAuthenticationEvidence {
1453
- /**
1454
- * Whether signatures were verified at all. It is `false` only when
1455
- * signature verification is skipped, or when there is no evidence, e.g.,
1456
- * for an activity queued by an older version of Fedify. In that case all
1457
- * the principal lists are empty.
1458
- */
1459
- readonly verified: boolean;
1460
- /**
1461
- * The principals that signed the HTTP request, i.e., the server that
1462
- * delivered the activity, which may have forwarded it. It is empty when
1463
- * the activity document was authenticated by its own signatures, so that
1464
- * HTTP Signatures did not need to be verified.
1465
- */
1466
- readonly requestSigners: readonly InboxPrincipal[];
1467
- /**
1468
- * The principals that made the verified Linked Data Signature or Object
1469
- * Integrity Proofs of the activity document. All of them are listed when
1470
- * there are several proofs.
1471
- */
1472
- readonly documentSigners: readonly InboxPrincipal[];
1473
- /**
1474
- * The actors authenticated because `Context.routeActivity()` fetched the
1475
- * activity again from their own web origin. It is always empty for
1476
- * activities delivered to an inbox.
1477
- */
1478
- readonly originAuthenticated: readonly InboxPrincipal[];
1479
- /**
1480
- * The activity as Fedify interpreted it while authenticating it, in
1481
- * expanded JSON-LD. Typed vocabulary objects omit some values, such as
1482
- * blank node references and values they cannot decode, so authorization
1483
- * that has to count every value of a property, e.g., that a property has
1484
- * exactly one value, should inspect this instead. It is deeply frozen.
1485
- * It is empty when there is no evidence.
1486
- */
1487
- readonly expandedActivity: readonly Record<string, unknown>[];
1488
- /**
1489
- * The base URL the activity was decoded with, e.g., the URL an activity
1490
- * fetched by `Context.routeActivity()` was finally retrieved from, or
1491
- * `null` if none was given. Relative IRIs left in
1492
- * {@link expandedActivity} resolve against it, or against the activity's
1493
- * own `@id` if it is `null`, as the decoder resolves them.
1494
- */
1495
- readonly baseUrl: URL | null;
1496
- }
1497
- /**
1498
- * A {@link InboxPrincipal} as stored in a queued message.
1499
- * The wire spellings remain independent of the public API spellings.
1500
- * @internal
1501
- */
1502
- interface SerializedInboxPrincipal {
1503
- readonly kind: "actor" | "portableAuthority";
1504
- readonly id: string;
1505
- readonly mechanism: "httpSignatures" | "linkedDataSignatures" | "objectIntegrityProofs" | "origin";
1506
- readonly keyId: string | null;
1507
- }
1508
- /**
1509
- * The principals of an {@link InboxAuthenticationEvidence} as stored in
1510
- * a queued message. The expanded activity and the base URL are restored
1511
- * from the queued verified view instead.
1512
- * @internal
1513
- */
1514
- interface SerializedInboxAuthentication {
1515
- readonly verified: boolean;
1516
- readonly requestSigners: readonly SerializedInboxPrincipal[];
1517
- readonly documentSigners: readonly SerializedInboxPrincipal[];
1518
- readonly originAuthenticated: readonly SerializedInboxPrincipal[];
1519
- }
1520
- //#endregion
1521
- //#region src/sig/ld.d.ts
1522
- /**
1523
- * How a context loader marked by {@link markEffectiveContextLoader} already
1524
- * resolves contexts.
1525
- * @internal
1526
- */
1527
- type EffectiveContextLoaderPolicy = "standard" | "normalization";
1528
- //#endregion
1529
- //#region src/federation/inbox-view.d.ts
1530
- /**
1531
- * A {@link ContextSnapshotEntry} as stored in a queued message. A response
1532
- * that equals the one Fedify bundles for the URL is stored as a reference to
1533
- * the bundled context, along with its hash for diagnostics.
1534
- * @internal
1535
- */
1536
- type SerializedContextSnapshotEntry = {
1537
- readonly policy: EffectiveContextLoaderPolicy;
1538
- readonly url: string; /** The SHA-256 of the canonical JSON of the whole response. */
1539
- readonly sha256: string;
1540
- } & ({
1541
- readonly bundled: true;
1542
- } | {
1543
- readonly response: RemoteDocument;
1544
- });
1545
- /**
1546
- * The interpretation of an incoming activity that Fedify authenticated, as
1547
- * stored in a queued message. The document itself is not copied; it is
1548
- * a field of the message.
1549
- * @internal
1550
- */
1551
- interface SerializedVerifiedActivityView {
1552
- /** The format version. */
1553
- readonly version: 1;
1554
- /** The context loader policy the activity was parsed with. */
1555
- readonly policy: EffectiveContextLoaderPolicy;
2274
+ */
2275
+ readonly nativeDeduplication?: boolean;
1556
2276
  /**
1557
- * Which document the activity was parsed from, with its Linked Data
1558
- * Signature detached: the `activity` of the message, i.e., the payload as
1559
- * received, or its `normalizedActivity`, i.e., the payload compacted for
1560
- * Linked Data Signatures.
2277
+ * Whether {@link enqueueMany} accepts either every message or no messages.
2278
+ * Backends that implement `enqueueMany()` by sending messages individually
2279
+ * must set this to `false`, so Fedify does not use that method for a batch
2280
+ * governed by one `deduplicationKey`. `true` by default when `enqueueMany`
2281
+ * is implemented.
2282
+ *
2283
+ * @default `true`
2284
+ * @since 2.4.0
1561
2285
  */
1562
- readonly source: "activity" | "normalizedActivity";
1563
- /** The `baseUrl` option the activity was parsed with. */
1564
- readonly baseUrl: string | null;
1565
- /** The context responses the interpretation used. */
1566
- readonly contexts: readonly SerializedContextSnapshotEntry[];
1567
- /** The digest of the expanded activity. */
1568
- readonly expandedDigest: string;
2286
+ readonly atomicEnqueueMany?: boolean;
2287
+ /**
2288
+ * The largest message, in bytes, that the backend can hold. Fedify compares
2289
+ * it with a conservative estimate of a message's serialized size: the
2290
+ * length of its JSON serialization encoded in UTF-8, plus 8 bytes for each
2291
+ * JSON value in it, plus what encoding strings beyond Latin-1 as UTF-16
2292
+ * adds, which also bounds binary serialization formats. When an
2293
+ * incoming activity would make a larger inbox message, Fedify processes the
2294
+ * activity right away instead of enqueueing it, as if there were no inbox
2295
+ * queue. When omitted, messages are enqueued regardless of their size.
2296
+ * @since 2.5.0
2297
+ */
2298
+ readonly maxMessageSize?: number;
2299
+ /**
2300
+ * Enqueues a message in the queue.
2301
+ * @param message The message to enqueue.
2302
+ * @param options Additional options for enqueuing the message.
2303
+ */
2304
+ enqueue(message: any, options?: MessageQueueEnqueueOptions): Promise<void>;
2305
+ /**
2306
+ * Enqueues multiple messages in the queue. This operation is optional,
2307
+ * and may not be supported by all implementations. If not supported,
2308
+ * Fedify will invoke {@link enqueue} for each message. An implementation
2309
+ * that may accept only part of a batch before rejecting must declare
2310
+ * {@link atomicEnqueueMany} `false`.
2311
+ *
2312
+ * @param messages The messages to enqueue.
2313
+ * @param options Additional options for enqueuing the messages.
2314
+ */
2315
+ enqueueMany?: (messages: readonly any[], options?: MessageQueueEnqueueOptions) => Promise<void>;
2316
+ /**
2317
+ * Listens for messages in the queue.
2318
+ * @param handler The handler for messages in the queue.
2319
+ * @param options Additional options for listening to the message queue.
2320
+ * @returns A promise that resolves when the listening is done. It never
2321
+ * rejects, and is resolved when the signal is aborted. If no
2322
+ * signal is provided, it never resolves.
2323
+ */
2324
+ listen(handler: (message: any) => Promise<void> | void, options?: MessageQueueListenOptions): Promise<void>;
2325
+ /**
2326
+ * Gets the number of messages waiting in the queue.
2327
+ *
2328
+ * This operation is optional, and may not be supported by all
2329
+ * implementations. The returned counts exclude messages currently being
2330
+ * handled by a worker.
2331
+ *
2332
+ * @since 2.3.0
2333
+ */
2334
+ getDepth?(): Promise<MessageQueueDepth>;
1569
2335
  }
1570
2336
  //#endregion
1571
2337
  //#region src/federation/retry.d.ts
@@ -1742,187 +2508,6 @@ interface TaskEnqueueOptions {
1742
2508
  readonly deduplicationKey?: string;
1743
2509
  }
1744
2510
  //#endregion
1745
- //#region src/federation/queue.d.ts
1746
- interface SenderKeyJwkPair {
1747
- readonly keyId: string;
1748
- readonly privateKey: JsonWebKey;
1749
- }
1750
- /**
1751
- * A message that represents a task to be processed by the background worker.
1752
- * The concrete type of the message depends on the `type` property.
1753
- *
1754
- * Please do not depend on the concrete types of the messages, as they may
1755
- * change in the future. You should treat the `Message` type as an opaque
1756
- * type.
1757
- * @since 1.6.0
1758
- */
1759
- type Message = FanoutMessage | OutboxMessage | InboxMessage | TaskMessage;
1760
- interface FanoutMessage {
1761
- readonly type: "fanout";
1762
- readonly id: ReturnType<typeof crypto.randomUUID>;
1763
- readonly baseUrl: string;
1764
- readonly keys: readonly SenderKeyJwkPair[];
1765
- readonly inboxes: Readonly<Record<string, {
1766
- readonly actorIds: readonly string[];
1767
- readonly sharedInbox: boolean;
1768
- /**
1769
- * The canonical form of the FEP-ef61 portable inbox that the inbox
1770
- * stands for.
1771
- * @internal
1772
- */
1773
- readonly portableInbox?: string;
1774
- /**
1775
- * The compatible identifiers of the portable inbox on each gateway to
1776
- * deliver through, in order.
1777
- * @internal
1778
- */
1779
- readonly gatewayInboxes?: readonly string[];
1780
- }>>;
1781
- readonly activity: unknown;
1782
- readonly activityId?: string;
1783
- readonly activityType: string;
1784
- readonly collectionSync?: string;
1785
- readonly orderingKey?: string;
1786
- /**
1787
- * Whether to apply outgoing JSON-LD wire-format normalization to queued
1788
- * activities that already carry Object Integrity Proofs.
1789
- *
1790
- * `true` is used for proofs Fedify created before fanout, or when callers
1791
- * explicitly request normalization for locally pre-signed activities.
1792
- * `false`/`undefined` preserves existing proofs as-is.
1793
- */
1794
- readonly normalizeExistingProofs?: boolean;
1795
- readonly traceContext: Readonly<Record<string, string>>;
1796
- }
1797
- interface OutboxMessage {
1798
- readonly type: "outbox";
1799
- readonly id: ReturnType<typeof crypto.randomUUID>;
1800
- readonly baseUrl: string;
1801
- readonly keys: readonly SenderKeyJwkPair[];
1802
- readonly activity: unknown;
1803
- readonly activityId?: string;
1804
- readonly activityType: string;
1805
- readonly inbox: string;
1806
- readonly sharedInbox: boolean;
1807
- readonly actorIds?: readonly string[];
1808
- readonly started: string;
1809
- readonly attempt: number;
1810
- readonly headers: Readonly<Record<string, string>>;
1811
- readonly orderingKey?: string;
1812
- /**
1813
- * Whether this message is currently held by the outbound circuit breaker.
1814
- * @internal
1815
- */
1816
- readonly circuitHeld?: true;
1817
- /**
1818
- * When Fedify first held this message because the remote host circuit was
1819
- * open.
1820
- * @internal
1821
- */
1822
- readonly circuitHeldSince?: string;
1823
- /**
1824
- * The canonical form of the FEP-ef61 portable inbox that {@link inbox}, its
1825
- * compatible identifier on a gateway, stands for.
1826
- * @internal
1827
- */
1828
- readonly portableInbox?: string;
1829
- /**
1830
- * The compatible identifiers of the FEP-ef61 portable inbox on each gateway
1831
- * that is still to be tried, in order. {@link inbox} is the first one.
1832
- * If there is more than one, the activity is delivered through the first
1833
- * gateway that accepts it.
1834
- * @internal
1835
- */
1836
- readonly gatewayInboxes?: readonly string[];
1837
- /**
1838
- * The times before which each of {@link gatewayInboxes} must not be tried
1839
- * again, as their responses' `Retry-After` headers requested, keyed by
1840
- * the inbox URL, in ISO 8601.
1841
- * @internal
1842
- */
1843
- readonly gatewayNotBefore?: Readonly<Record<string, string>>;
1844
- readonly traceContext: Readonly<Record<string, string>>;
1845
- }
1846
- /**
1847
- * A message that carries a custom background task. Every field is
1848
- * a string, number, or plain record so that the message survives both
1849
- * JSON serialization and structured clone on every queue backend.
1850
- * @since 2.4.0
1851
- */
1852
- interface TaskMessage {
1853
- readonly type: "task";
1854
- readonly id: ReturnType<typeof crypto.randomUUID>;
1855
- readonly baseUrl: string;
1856
- readonly taskName: string;
1857
- /** devalue-encoded task data; vocab objects bridged to expanded JSON-LD. */
1858
- readonly data: string;
1859
- readonly started: string;
1860
- readonly attempt: number;
1861
- readonly orderingKey?: string;
1862
- readonly traceContext: Readonly<Record<string, string>>;
1863
- }
1864
- interface InboxMessage {
1865
- readonly type: "inbox";
1866
- readonly id: ReturnType<typeof crypto.randomUUID>;
1867
- readonly baseUrl: string;
1868
- readonly activity: unknown;
1869
- /**
1870
- * The normalized JSON-LD representation of a signed inbox activity that
1871
- * Fedify already compacted successfully while accepting the request. Queue
1872
- * workers can reuse this producer-side parse cache under stricter loader or
1873
- * network constraints without changing the raw payload preserved for
1874
- * forwarding.
1875
- *
1876
- * This may exist even when {@link ldSignatureVerified} is `false`, because
1877
- * fallback-authenticated traffic and already-queued backlog items can still
1878
- * depend on the cached normalized form to avoid re-fetching remote custom
1879
- * contexts during worker processing.
1880
- *
1881
- * This is optional for backward compatibility with messages that were
1882
- * queued by older Fedify versions or that were already in a queue before
1883
- * upgrading.
1884
- *
1885
- * Fedify keeps this on the queued message itself instead of an external
1886
- * sidecar because generic queue backends do not provide reliable lifecycle
1887
- * guarantees for auxiliary storage across retries and redeliveries.
1888
- *
1889
- * @internal
1890
- */
1891
- readonly normalizedActivity?: unknown;
1892
- /**
1893
- * Whether the producer actually verified the Linked Data Signature before
1894
- * queueing this message. This lets workers distinguish verified LDS replay
1895
- * from other authenticated inbox traffic that merely happened to include a
1896
- * signature block. This provenance marker is separate from the optional
1897
- * normalizedActivity parse cache.
1898
- *
1899
- * `undefined` preserves backward compatibility with older queued messages
1900
- * that predate this marker.
1901
- *
1902
- * @internal
1903
- */
1904
- readonly ldSignatureVerified?: boolean;
1905
- /**
1906
- * The interpretation of the activity that Fedify authenticated, i.e., which
1907
- * document it was parsed from and every JSON-LD context response the parse
1908
- * used, so that the worker and its retries dispatch the very same
1909
- * interpretation without loading contexts again. Absent in messages queued
1910
- * by older Fedify versions.
1911
- * @internal
1912
- */
1913
- readonly verifiedView?: SerializedVerifiedActivityView;
1914
- /**
1915
- * Who Fedify authenticated for the activity. Absent in messages queued by
1916
- * older Fedify versions.
1917
- * @internal
1918
- */
1919
- readonly authentication?: SerializedInboxAuthentication;
1920
- readonly started: string;
1921
- readonly attempt: number;
1922
- readonly identifier: string | null;
1923
- readonly traceContext: Readonly<Record<string, string>>;
1924
- }
1925
- //#endregion
1926
2511
  //#region src/federation/middleware.d.ts
1927
2512
  /**
1928
2513
  * Configures the task queues for sending and receiving activities.
@@ -2058,6 +2643,31 @@ interface FederationStartQueueOptions {
2058
2643
  * @since 1.6.0
2059
2644
  */
2060
2645
  interface Federatable<TContextData> extends TaskRegistry<TContextData> {
2646
+ /**
2647
+ * Registers observers for outgoing activity processing.
2648
+ *
2649
+ * Each call replaces all previously registered outbound observers.
2650
+ * Pass an empty object to clear them. Fedify copies callbacks and capture policies;
2651
+ * later changes to the supplied object do not affect registration.
2652
+ * Available on builders and directly created federations. Building copies
2653
+ * the callbacks, and an active send keeps the callback selected at entry.
2654
+ * Observes send, original-payload forwarding, and automatic portable
2655
+ * replication preparation, destination planning, and handoffs, including
2656
+ * outbox-context operations and fanout continuation handoffs. Separate
2657
+ * callbacks report concrete send attempts and actual HTTP exchanges, including
2658
+ * queued retries and gateway fallback. Worker reports retain local execution
2659
+ * results and actual enqueue, retry, deferral, and abandonment actions,
2660
+ * separately from attempts and backend acknowledgment.
2661
+ * Known IDs, original inbox source, and
2662
+ * application correlation survive queue work; workers use current context data.
2663
+ * Header values, bounded response-body streaming and sender-payload projections
2664
+ * are independently opt-in. Body consumers require explicit byte/time limits;
2665
+ * other metadata callbacks have no default timeout.
2666
+ * This is separate from incoming C2S {@link Federatable.setOutboxListeners}.
2667
+ * @param observers The optional callbacks to register.
2668
+ * @since 2.5.0
2669
+ */
2670
+ setOutboundObservers(observers: OutboundObservers<TContextData>): void;
2061
2671
  /**
2062
2672
  * Registers a NodeInfo dispatcher.
2063
2673
  * @param path The URI path pattern for the NodeInfo dispatcher. The syntax
@@ -5165,6 +5775,14 @@ type ParseUriResult =
5165
5775
  * Options for {@link Context.sendActivity} method.
5166
5776
  */
5167
5777
  interface SendActivityOptions {
5778
+ /**
5779
+ * Application-owned correlation for this invocation, never a deduplication
5780
+ * key. Preserved exactly, including an empty string. At most 1,024 UTF-8
5781
+ * bytes; invalid types and oversized values are rejected before preparation.
5782
+ * Omission means no application dispatch ID for this new invocation.
5783
+ * @since 2.5.0
5784
+ */
5785
+ readonly dispatchId?: string;
5168
5786
  /**
5169
5787
  * Whether to prefer the shared inbox for the recipients.
5170
5788
  */