Fedify 2.3.0
Released on June 25, 2026.
@fedify/fedify
-
Added
mapActorAlias()method toActorCallbackSettersinterface to support fixed-path actor dispatchers. This is useful for exposing a single, instance-level actor at a fixed path, such as/actorfor a relay or/botfor a bot, without leaking a sentinel identifier into the actor's URI. [#752, #753] -
Added optional
MessageQueue.getDepth()support, using the newMessageQueueDepthreturn type, for reporting queue backlog depth.InProcessMessageQueuecan now report queued messages, including ready and delayed counts, andParallelMessageQueuedelegates depth reporting to its wrapped queue when supported. [#735, #748] -
Added OpenTelemetry metrics for ActivityPub delivery attempts, permanent delivery failures, inbox listener processing duration, and HTTP Signature verification failures. Applications can pass the new
meterProvideroption tocreateFederation(), andContext.meterProviderexposes the provider available to request, inbox, and outbox code. [#316, #619, #755] -
Added the
activitypub.delivery.failedspan event to queued outbox delivery spans so retry and permanent-failure decisions include the remote host, attempt number, and HTTP status code when available. [#316, #619, #755] -
Breaking change: Changed the
activitypub.activity.sentspan event to record delivery metadata (activitypub.inbox.urlandactivitypub.activity.id) instead of the fullactivitypub.activity.jsonpayload.FedifySpanExporternow stores outbound records from those attributes, andTraceActivityRecord.activityJsonis present only when the span event includes full activity JSON. [#316, #619, #755] -
Added two OpenTelemetry histograms for signature verification:
activitypub.signature.verification.durationmeasures end-to-end verification time for HTTP Signatures, Linked Data Signatures, and Object Integrity Proofs (including local key lookup and remote key fetches), andactivitypub.signature.key_fetch.durationmeasures public key lookup duration separately so operators can isolate non-fetch verification work. Both instruments carryactivitypub.signature.kind(http,linked_data, orobject_integrity) and bounded result attributes; the verification histogram additionally carries spec-boundedhttp_signatures.algorithm,ld_signatures.type, orobject_integrity_proofs.cryptosuitewhen known, plushttp_signatures.failure_reasonon rejected HTTP rows. [#316, #737, #769] -
Added OpenTelemetry HTTP server metrics for inbound requests handled by
Federation.fetch():fedify.http.server.request.count(Counter) andfedify.http.server.request.duration(Histogram). Both instruments carryhttp.request.method,fedify.endpoint, optionalhttp.response.status_code, and optionalfedify.route.templateattributes so that operators can monitor aggregate request rate, latency, and status-code error rate even when traces are sampled. Attributes deliberately exclude raw URLs, query strings, and identifier values to keep cardinality bounded. [#316, #736, #757] -
Added OpenTelemetry metrics for ActivityPub collection requests handled by
Federation.fetch()and custom collection handlers:activitypub.collection.request(counter)activitypub.collection.dispatch.duration(histogram)activitypub.collection.page.items(histogram)activitypub.collection.total_items(histogram)
The metrics expose bounded collection dimensions:
activitypub.collection.kind,activitypub.collection.page,activitypub.collection.result,fedify.collection.dispatcher, and optionalhttp.response.status_code. Built-in collections are classified asinbox,outbox,following,followers,liked,featured, orfeatured_tags; application-defined collection routes are collapsed intocustom. Collection IDs, cursors, custom route names, actor identifiers, and full URLs are deliberately excluded so dashboards can aggregate collection rate, latency, item counts, andtotalItemsvalues without attacker-controlled cardinality. [#316, #741, #777] -
Added OpenTelemetry queue task metrics covering Fedify's enqueue and worker boundaries for inbox, outbox, and fanout work:
fedify.queue.task.enqueued(counter)fedify.queue.task.started(counter)fedify.queue.task.completed(counter)fedify.queue.task.failed(counter)fedify.queue.task.duration(histogram)fedify.queue.task.in_flight(up/down counter, process local)
Instruments carry
fedify.queue.role, best-effortfedify.queue.backend(the queue implementation's constructor name), andfedify.queue.native_retrial. The enqueue/started/completed/ failed/duration instruments additionally carryactivitypub.activity.typewhenever Fedify knows the activity type for the queued message; the in-flight up/down counter deliberately omits per-message attributes so that increment and decrement operations always pair up cleanly per attribute series. Enqueue measurements additionally carryfedify.queue.task.attemptfor retries, and the completion-side instruments carryfedify.queue.task.result(completed,failed, oraborted). Together withMessageQueue.getDepth()reporting, these metrics let operators distinguish a slow-draining queue from a queue that sees less traffic. [#316, #740, #759] -
Added OpenTelemetry metrics for ActivityPub fanout and activity lifecycle events, complementing the per-recipient
activitypub.delivery.*counters and the per-taskfedify.queue.task.*metrics with an activity-level view of inbox and outbox pressure:activitypub.fanout.recipients(histogram) records the number of recipient inboxes produced by a single fanout enqueue.activitypub.inbox.activity(counter) classifies an inbound activity via the newactivitypub.processing.resultattribute asqueued,processed,retried,rejected, orabandoned.activitypub.outbox.activity(counter) classifies an outbound activity asqueued,retried, orabandoned. Per-recipientsent/failedrows remain onactivitypub.delivery.sentandactivitypub.delivery.permanent_failureand are not duplicated.
The lifecycle counters cover only Fedify-managed events: queue backends with
nativeRetrialdefer retry handling and therefore do not recordretriedorabandoned. Recipient URLs, actor IDs, and other high-cardinality identifiers are deliberately excluded from the fanout histogram. [#316, #742, #770] -
Added OpenTelemetry metrics for public key lookups, remote JSON-LD document fetches, and
lookupObject()calls so operators can observe how often Fedify hits the cache, how long remote fetches take, and howlookupObject()resolutions split between actors, non-actor objects, and unresolved lookups:activitypub.key.lookup(counter) andactivitypub.key.lookup.duration(histogram) cover every public key lookup performed byfetchKey()/fetchKeyDetailed(), including signature verification paths.activitypub.document.fetch(counter) andactivitypub.document.fetch.duration(histogram) cover every Fedify-wrapped document or context loader invocation, including the authenticated loader.activitypub.document.cache(counter) recordshitormissfor eachkvCache()-backed cache lookup.activitypub.object.lookup(counter) records the parsed-result classification of everylookupObject()call asactor,object, orother.
Instruments share an
activitypub.lookup.kindand (where applicable)activitypub.lookup.resultattribute drawn from small, spec-bounded enumerations.activitypub.remote.hostrecords the URL host, including any non-default port;http.response.status_codeis recorded when an HTTP response was observed;activitypub.cache.enabledis recorded on the key and document fetch metrics whenever Fedify can confidently report the cache layer's presence. Key IDs, actor IDs, object IDs, JSON-LD context URLs, full URLs, and fediverse handles are deliberately excluded so attacker-controlled remotes cannot inflate metric cardinality. The existingactivitypub.signature.key_fetch.durationhistogram (introduced in Fedify 2.3 for signature-scoped key-fetch latency, sliced byactivitypub.signature.kind) remains in place; the newactivitypub.key.lookup.durationis the general-purpose histogram that covers non-signature key fetches as well and addshttp.response.status_codeand a richeractivitypub.lookup.resulttaxonomy. [#316, #738, #771] -
Added OpenTelemetry metrics for the WebFinger and actor handle discovery paths so operators can graph aggregate discovery rate, latency, and outcome mix without sampling spans:
webfinger.lookup(counter) andwebfinger.lookup.duration(histogram) cover outgoinglookupWebFinger()calls.webfinger.handle(counter) andwebfinger.handle.duration(histogram) cover incoming WebFinger requests handled byFederation.fetch().activitypub.actor.discovery(counter) andactivitypub.actor.discovery.duration(histogram) covergetActorHandle()actor handle discovery.
Each family carries a bounded result attribute (
webfinger.lookup.result,webfinger.handle.result, oractivitypub.actor.discovery.result) so operators can slice discovery failures by terminal outcome (found / not_found / invalid / network_error / error for outgoing lookups; resolved / invalid / not_found / tombstoned / error for incoming requests; resolved / not_found / error for actor discovery).webfinger.resource.schemeis bucketed to a small allow list (acct,http,https,mailto, orother) so an attacker-controlled query string cannot inflate metric cardinality;activitypub.remote.hostrecords the URL host, including any non-default port. Full resource URIs, lookup URLs, and handle strings are deliberately excluded; they remain on the corresponding spans (webfinger.lookup,webfinger.handle,activitypub.get_actor_handle) for trace-level investigation.lookupWebFinger()andgetActorHandle()follow the opt-inlookupObject()pattern: omitting the newmeterProvideroption emits no measurement. Applications that pass ameterProvidertocreateFederation()get the inboundwebfinger.handlefamily and the federation-boundContext.lookupWebFinger()family wired up automatically. DirectgetActorHandle()calls remain opt-in: passmeterProviderthroughGetActorHandleOptionsto enable the discovery metrics, and the option is forwarded into the nested WebFinger lookups so one discovery emits both the discovery measurement and the underlyingwebfinger.lookupmeasurements (one for the actor ID host, plus a second for the alias host when cross-origin verification runs). [#316, #739, #772] -
Added an outbound delivery circuit breaker for queued outbox delivery. Fedify now tracks consecutive network and HTTP 5xx delivery failures per remote host (including any non-default port), stores the state in the configured
KvStore, and requeues messages held by an open circuit instead of repeatedly sending to an unreachable server. The circuit breaker is enabled by default for queued outbox delivery and can be disabled withcircuitBreaker: false; applications can customize the failure policy, recovery delay, held activity TTL, release interval, and state/drop callbacks. HTTP 429 responses do not count as circuit failures andRetry-Afteris respected when present. State changes are exposed throughactivitypub.circuit_breaker.state_changemetrics andactivitypub.circuit_breaker.state_changespan events, and expired held activities call the outbox permanent failure handler withreason: "circuit-breaker-ttl". [#620, #778] -
Added
benchmarkModetocreateFederation()andFederationBuilder.build()for cooperative federation benchmarking. When enabled, Fedify exposesGET /.well-known/fedify/bench/statsfor in-process OpenTelemetry metric snapshots andPOST /.well-known/fedify/bench/triggerfor drivingsendActivity()to server-configured benchmark sink recipients. Benchmark mode also defaultsallowPrivateAddresstotruewhen built-in loaders are used, defaultssignatureTimeWindowtofalse, reports queue depth through the newfedify.queue.depthgauge, and adds explicit low-latency buckets to the signature verification duration histogram. [#744, #782, #787] -
Replaced Fedify's internal federation routing with @fedify/uri-template for stricter RFC 6570 URI Template expansion and matching. The deprecated
Routerexport from @fedify/fedify remains available for compatibility. [#418, #758 by ChanHaeng Lee] -
Significantly sped up TypeScript type-checking by simplifying the internal
pathparameter types of thesetObjectDispatcher(),setCollectionDispatcher(), andsetOrderedCollectionDispatcher()methods. These methods previously expandedpathinto thousands of RFC 6570 template-literal variants, which dominated type-checking time; a full codebase type check now completes in roughly 13 seconds instead of around 99 seconds. The public dispatcher method signatures and runtime path validation are unchanged. This is a partial fix for #613 that targets the dispatcher overload hot path; other contributors tocheck-allcost may remain. [#613, #800 by ChanHaeng Lee]
@fedify/cli
-
Added the
--skip-installoption tofedify init, following the corresponding@fedify/initupdate, which skips automatic dependency installation after scaffolding. [#720, #776 by fru1tworld] -
Switched Node.js and Bun projects generated by
fedify initfrom Biome plus ESLint to Oxfmt plus Oxlint. New projects now get .oxfmtrc.json, .oxlintrc.json, Oxc editor recommendations, and package scripts forformat,format:check, andlint; the Oxlint config loads Fedify's rules through@fedify/lint/oxlint. [#703, #818] -
Added the
fedify benchcommand for benchmarking Fedify federation workloads. It acts as a synthetic remote actor that drives ActivityPub-specific load (signed inbox deliveries and WebFinger lookups) against a cooperativebenchmarkModetarget and reports latency, throughput, success rate, and errors, reading server-side metrics from the target's stats endpoint. Benchmarks are described by a YAML or JSON scenario suite validated against a published JSON Schema, with anexpectblock per scenario that gates a run for CI. The command refuses public non-benchmarkModetargets without an explicit unsafe override, supports discovery-aware--dry-runplanning, and ships with a local benchmark fixture used by the scenario tests. [#744, #783, #784, #791] -
Added
actor,object,fanout,failure, andmixedscenario runners tofedify bench. Read scenarios can now benchmark actor and object document fetches, including authenticated GET requests; fanout scenarios drive the benchmark trigger endpoint and wait for queue task drain; failure scenarios report expected fault outcomes as successes; and mixed scenarios run weighted child scenario blends. Thecollectionscenario type remains reserved but not executable. Fanout and remote failure scenarios can setsinkBaseto generate deterministic benchmark sink inbox URLs for targets that keeptriggerSinksallowlisting enabled. This change is published as benchmark scenario schema version 2. [#744, #785, #801, #802] -
Hardened
fedify benchsafety planning and preflight checks. The command now resolves inbox destinations before load generation, treats unreadable or malformed resolver output conservatively, applies suite defaults consistently, and keeps the local benchmark fixture inside the CLI package for regression coverage. [#744, #795] -
Added
fedify bench comparefor CI-friendly performance regression gates. The command checks out base and head refs into temporary worktrees, starts the benchmark target for each ref, runs the same suite, and fails when the head regresses beyond--max-regressionplus the measured per-run noise band. Benchmark scenarios now run three times by default and aggregate repeated runs with median latency/throughput and pessimistic correctness results. This change is published as benchmark report schema version 3 and comparison report schema version 1. [#744, #786, #804]
@fedify/backfill
- Added @fedify/backfill for reconstructing ActivityPub conversations. It supports FEP-f228 context collections containing post-like objects or
Createactivities, optional reply-tree traversal, ordered hybrid strategies, shared safety budgets, deduplication, and traversal-local document caching. [#275, #779, #801, #807, #816, #820 by Jiwon Kwon]
@fedify/fixture
- Added
createTestMeterProvider()andTestMetricRecorderhelpers for asserting OpenTelemetry metric measurements in runtime-agnostic tests. [#316, #619, #755]
@fedify/testing
- Added a
meterProvideroption tocreateFederation()so mock contexts can expose a test OpenTelemetry meter provider. [#316, #619, #755]
@fedify/uri-template
- Added @fedify/uri-template, a dependency-free RFC 6570 URI Template implementation for expansion, variable extraction, and round-trip route matching. This package replaces Fedify's previous direct use of url-template and uri-template-router. [#418, #758 by ChanHaeng Lee]
@fedify/amqp
- Added
AmqpMessageQueue.getDepth()for reporting queued, ready, and delayed message counts. Delayed counts include queues created or tracked by the sameAmqpMessageQueueinstance. [#735, #748]
@fedify/mysql
- Added
MysqlMessageQueue.getDepth()for reporting queued, ready, and delayed message counts. [#735, #748]
@fedify/postgres
- Added
PostgresMessageQueue.getDepth()for reporting queued, ready, and delayed message counts. [#735, #748]
@fedify/redis
- Added
RedisMessageQueue.getDepth()for reporting queued, ready, and delayed message counts. [#735, #748]
@fedify/sqlite
- Added
SqliteMessageQueue.getDepth()for reporting queued, ready, and delayed message counts. [#735, #748]
@fedify/init
-
Added a
--skip-installoption tofedify initthat skips automatic dependency installation after scaffolding. This is useful for CI environments, monorepo workspaces that install dependencies from the root, or when you want to inspect the generated files before installing. [#720, #776 by fru1tworld] -
Switched generated Node.js and Bun projects from Biome plus ESLint to Oxfmt plus Oxlint. New projects now get .oxfmtrc.json, .oxlintrc.json, Oxc editor recommendations, and package scripts for
format,format:check, andlint; the Oxlint config loads Fedify's rules through@fedify/lint/oxlint. [#703, #818]
@fedify/lint
- Added official Oxlint support through a new
@fedify/lint/oxlintsubpath export, which exposes Fedify's lint rules in the shape Oxlint's JS plugin API expects. Previously, using@fedify/lintfrom Oxlint required a local wrapper module to re-export the plugin object as the default export; the new entrypoint removes that friction. The rules are reused verbatim from the ESLint plugin, and the existing Deno and ESLint root exports are unchanged. Note that Oxlint's JS plugin support is still alpha upstream. [#702, #760 by NyanRus]
@fedify/vocab-runtime
- Added
PropertyPreprocessor,PropertyPreprocessorContext, andJsontypes for normalizing wire-level JSON-LD property values before the generated range decoder runs. [#792]
@fedify/vocab
-
Explicit ActivityStreams
Linkobjects iniconandimageproperties are now normalized toImageduring decoding via the new exportednormalizeLinkToImage()preprocessor. The publicImage-oriented TypeScript API is unchanged. [#790, #792] -
The generated
fromJsonLd()methods no longer resolve blank node identifiers (_:b0) againstoptions.baseUrl; blank nodes are left asnullin the resulting instance'sidfield. [#792] -
Added the second-stage vocabulary types for FEP-0837, economic resource coordination in federated networks. [#775, #817 by Samuel Brinkmann]
- Added
Agreementclass, representing the agreement reached between parties responding to aProposal, wrapped in anOfferand finalized as theresultof anAccept. - Added
Commitmentclass, representing a promised economic transaction that references anIntentviasatisfiesand carries the committed quantity viaresourceQuantity.
- Added
@fedify/vocab-tools
-
Property schemas now support a
preprocessorsfield that lists module/function pairs. Generated decoders statically import and run these preprocessors for each expanded JSON-LD property value before falling back to the normal range decoder. [#792] -
The generated base class now stores the
baseUrlfromfromJsonLd()as a protected_baseUrlfield. This URL is used to resolve relative URIs when cached embedded property documents are re-parsed lazily by accessors likegetIcon(), so that callers do not need to pass an explicitbaseUrl. The stored URL is defensively copied so that mutation of the caller's originalURLobject does not affect later resolution. [#792]
Documentation and examples
-
Rebuilt the documentation home page on VitePress 2 with a custom Fedify landing page, package-manager-specific installation commands, richer feature sections, linked fediverse software logos, Sovereign Tech Agency credit, and a generated Open Graph image. [#809]
-
Added a production monitoring guide that turns Fedify's OpenTelemetry metrics into practical Prometheus queries, dashboard panels, and alerting rules for federation health, queue backlog, delivery failures, signature verification failures, and circuit breaker state. [#813]
-
Added a runnable monitoring example at examples/monitoring/ with an OpenTelemetry Collector, Prometheus, Grafana dashboard provisioning, Prometheus alert rules, synthetic Fedify-shaped metrics, validation checks, and an optional Docker Compose smoke test. [#814]
Claude Code plugin
-
Added a Claude Code plugin at claude-plugin/, installable with:
/plugin marketplace add fedify-dev/fedify /plugin install fedify@fedifyThe plugin provides six slash commands (
/fedify:fedify,/fedify:docs,/fedify:actor,/fedify:inbox,/fedify:migration,/fedify:fep) and two specialized agents (fedify-reviewerandfedify-debugger). The Agent Skills bundle lives canonically in claude-plugin/skills/fedify/ and is referenced from packages/fedify/skills/fedify/ via a symlink; theprepackscript resolves the symlink to real files before packing so the published npm tarball is self-contained. [#489, #756]