From 0eaa5f2d5a12d3769cf84fc29f074f5bcdfd0f86 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 22 Apr 2026 18:06:42 +0900 Subject: [PATCH 1/9] Add Fedify agent skill bundled in @fedify/fedify Ship an Agent Skills document at packages/fedify/skills/fedify/SKILL.md so that AI coding agents working inside a consumer project get project-local guidance on how to use Fedify correctly, staying in sync with the installed version. The skill covers the Federation builder pattern, dispatcher callbacks for actor/object/inbox/outbox/collection endpoints, inbox listeners, Context and TContextData, the web framework integration packages, built-in WebFinger and NodeInfo endpoints, outgoing activity delivery and the message queue, vocabulary imports from @fedify/vocab (and away from the deprecated shims), key pair management, persistent storage adapters, LogTape categories, OpenTelemetry wiring, FEP lookup via a Codeberg clone, the fedify CLI, and a list of common mistakes distilled from the WARNING/CAUTION callouts across docs/. Declare the skill in packages/fedify/package.json through the agents field so tools such as skills-npm can discover it, and add "skills" to the files field so it ships in the npm tarball. https://github.com/fedify-dev/fedify/issues/711 Assisted-by: Claude Code:claude-opus-4-7 --- CHANGES.md | 13 + packages/fedify/package.json | 8 +- packages/fedify/skills/fedify/SKILL.md | 442 +++++++++++++++++++++++++ 3 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 packages/fedify/skills/fedify/SKILL.md diff --git a/CHANGES.md b/CHANGES.md index a2600fbc9..aedb2e40f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,15 @@ To be released. ### @fedify/fedify + - Shipped an [Agent Skills] bundle at *skills/fedify/* and declared it in + *package.json* through the `agents.skills` field. The skill teaches AI + coding agents how to *use* Fedify inside a consumer's project (builder + pattern, dispatchers, framework integrations, vocabulary, keys, queues + and storage, observability, CLI, and common pitfalls). Projects that + run a tool implementing the Agent Skills spec, such as [skills-npm], + will pick up the skill automatically from *node\_modules*, keeping the + guidance in sync with the installed Fedify version. [[#711], [#712]] + - Added `setOutboxListeners()` and `OutboxContext` for handling client-to-server `POST` requests to actor outboxes. Outbox listeners use application-defined authorization through `.authorize()`, catch activity @@ -33,10 +42,14 @@ To be released. `getAuthenticatedDocumentLoader()` now also respects `GetAuthenticatedDocumentLoaderOptions.maxRedirection`. +[Agent Skills]: https://agentskills.io/ +[skills-npm]: https://github.com/antfu/skills-npm [#430]: https://github.com/fedify-dev/fedify/issues/430 [#644]: https://github.com/fedify-dev/fedify/issues/644 [#680]: https://github.com/fedify-dev/fedify/pull/680 [#688]: https://github.com/fedify-dev/fedify/pull/688 +[#711]: https://github.com/fedify-dev/fedify/issues/711 +[#712]: https://github.com/fedify-dev/fedify/pull/712 ### @fedify/lint diff --git a/packages/fedify/package.json b/packages/fedify/package.json index 732a665ac..f9b8f9ef8 100644 --- a/packages/fedify/package.json +++ b/packages/fedify/package.json @@ -32,8 +32,14 @@ }, "type": "module", "files": [ - "dist" + "dist", + "skills" ], + "agents": { + "skills": [ + { "name": "fedify", "path": "./skills/fedify" } + ] + }, "module": "./dist/mod.js", "main": "./dist/mod.cjs", "types": "./dist/mod.d.ts", diff --git a/packages/fedify/skills/fedify/SKILL.md b/packages/fedify/skills/fedify/SKILL.md new file mode 100644 index 000000000..b84f1bf2d --- /dev/null +++ b/packages/fedify/skills/fedify/SKILL.md @@ -0,0 +1,442 @@ +--- +name: fedify +description: >- + Use this skill whenever writing JavaScript or TypeScript code that uses + Fedify to build an ActivityPub server, handle federation activities, + implement fediverse features, or integrate Fedify with a web framework + such as Hono, Express, Next.js, Nuxt, Fastify, Koa, NestJS, Astro, + SvelteKit, Fresh, h3, Elysia, or Cloudflare Workers. Covers the + `Federation` builder pattern, actor/inbox/outbox/collection dispatchers, + inbox listeners, vocabulary objects from `@fedify/vocab`, key pair + management, HTTP Signatures, Object Integrity Proofs, the `KvStore` and + `MessageQueue` interfaces, database adapter packages, structured logging + with LogTape, OpenTelemetry tracing, the `fedify` CLI toolchain, and + common mistakes. Also apply when the user mentions ActivityPub, + federation, fediverse, WebFinger, NodeInfo, FEPs, or Mastodon + interoperability, even if they do not name Fedify explicitly. +--- + +Fedify skill +============ + +Fedify is a TypeScript library for ActivityPub server applications. It +works across Deno, Node.js, and Bun. The library takes care of the fiddly +parts of the fediverse (HTTP Signatures, Object Integrity Proofs, +WebFinger, NodeInfo, JSON-LD, delivery queues) so application code can +stay focused on dispatchers and activity handlers. + +Always link into the full documentation at instead +of guessing. When the user asks about a specific API, treat + and as +authoritative; this skill only points the way. Do not invent APIs; +verify names against those docs or against the installed +`@fedify/fedify` types. + + +Builder pattern +--------------- + +Two entry points return a `Federation` object: + + - `createFederationBuilder()` returns a + `FederationBuilder`; register dispatchers and listeners, then + `await builder.build(options)` to get the `Federation`. Prefer this + in larger apps, especially when you need to split configuration + across files or avoid circular imports. Serverless runtimes such as + Cloudflare Workers *require* the builder: bindings are only available + per-request, so the `Federation` must be constructed inside the + request handler. + - `createFederation(options)` constructs the object + directly. Appropriate when everything fits in one module. + +`.build()` is asynchronous; always `await` it. See +. + +~~~~ typescript +import { createFederationBuilder, MemoryKvStore } from "@fedify/fedify"; + +const builder = createFederationBuilder(); +// ...register dispatchers on builder... +export const federation = await builder.build({ + kv: new MemoryKvStore(), // development only +}); +~~~~ + +> [!IMPORTANT] +> Production deployments *must* provide a real `queue` implementation. +> Without one, outgoing activities are sent synchronously and delivery +> becomes unreliable under load. See +> . + +> [!WARNING] +> Never set `allowPrivateAddress: true` outside tests. It disables the +> SSRF guard that blocks Fedify from fetching private or loopback +> addresses. See and +> . + + +Dispatchers +----------- + +Every route Fedify serves is driven by a dispatcher callback registered on +the builder (or `Federation` object). Do not hand-roll these routes in +the web framework; the dispatcher signatures encode the library's URI +template guarantees. + + - `setActorDispatcher(path, dispatcher)`: returns an + `ActorCallbackSetters` chain that also carries + `setKeyPairsDispatcher()`. + - `setObjectDispatcher(type, path, dispatcher)`: for individual + `Object` types such as `Note` or `Article`. + - `setInboxDispatcher(path, dispatcher)`: the inbox *collection* + endpoint. The inbox *listener* is a different API (see below). + - `setOutboxDispatcher(path, dispatcher)`. + - `setFollowingDispatcher(path, dispatcher)` / + `setFollowersDispatcher(path, dispatcher)` / + `setLikedDispatcher(path, dispatcher)` / + `setFeaturedDispatcher(path, dispatcher)` / + `setFeaturedTagsDispatcher(path, dispatcher)`. + - `setCollectionDispatcher()` and `setOrderedCollectionDispatcher()` + for custom collections. + - `setNodeInfoDispatcher(path, dispatcher)` and + `setWebFingerLinksDispatcher(dispatcher)` for protocol endpoints. + +Paths use URI templates. If an identifier can contain URI characters, +switch the template variable from `{identifier}` to `{+identifier}` to +avoid double-encoding. See . + +> [!WARNING] +> Simple expansion (`{identifier}`) percent-encodes reserved characters a +> second time. If actors or objects are keyed by URIs, use reserved +> expansion (`{+identifier}`). + +See , +, and +. + + +Inbox listeners +--------------- + +`setInboxListeners(inboxPath, sharedInboxPath?)` returns an +`InboxListenerSetters` object with: + + - `.on(ActivityType, handler)`: chainable, keyed by the *class* + (`Follow`, `Create`, `Undo`, etc.). + - `.onError(handler)`. + - `.onUnverifiedActivity(handler)`. + - `.setSharedKeyDispatcher(dispatcher)`. + - `.withIdempotency(strategy)`. + +> [!WARNING] +> Activities of a type that is not registered via `.on()` are silently +> ignored. To catch everything, register a listener for the base +> `Activity` class. + +See . + + +Context and `TContextData` +-------------------------- + +`Context` is the per-operation handle Fedify passes to +dispatchers and listeners. The `TContextData` generic carries +application state (database handles, request id, auth session). Treat it +as the single place to inject dependencies; do not reach for module-level +singletons inside handlers. + +`RequestContext` extends `Context` with +request-scoped helpers. + +Use `ctx.get…Uri()` helpers (for example `ctx.getActorUri(identifier)`) +to build canonical URIs instead of string-concatenating paths. + +> [!CAUTION] +> The `crossOrigin: "trust"` option on context methods and on vocabulary +> dereferencing disables the same-origin check. Only use it when the +> remote document is known to be trustworthy; it was the source of +> prior interop bugs. + +See and +. + + +Framework integrations +---------------------- + +Mount Fedify through the dedicated integration package for the target +framework. Do not translate requests manually; the integration handles +content negotiation, signature verification, and response streaming. + +| Framework | Package | +| ------------------ | -------------------- | +| Astro | *@fedify/astro* | +| Cloudflare Workers | *@fedify/cfworkers* | +| Elysia | *@fedify/elysia* | +| Express | *@fedify/express* | +| Fastify | *@fedify/fastify* | +| Fresh | *@fedify/fresh* | +| h3 | *@fedify/h3* | +| Hono | *@fedify/hono* | +| Koa | *@fedify/koa* | +| NestJS | *@fedify/nestjs* | +| Next.js | *@fedify/next* | +| Nuxt | *@fedify/nuxt* | +| SolidStart | *@fedify/solidstart* | +| SvelteKit | *@fedify/sveltekit* | + +Two more packages are frequently useful: *@fedify/debugger* for a local +ActivityPub dashboard, and *@fedify/relay* for relay implementations. + +See . + + +Built-in protocol endpoints +--------------------------- + +Fedify serves these endpoints automatically as soon as the federation +handler is mounted; do not reimplement them. + + - `/.well-known/webfinger` (WebFinger). Customize link output with + `setWebFingerLinksDispatcher()`. See + . + - `/.well-known/nodeinfo` and the versioned NodeInfo document. + Customize with `setNodeInfoDispatcher()`. See + . + + +Outgoing activities +------------------- + +`ctx.sendActivity(sender, recipients, activity, options?)` is the single +entry point for outbound delivery. Two overloads: + + - Explicit recipients: pass a single `Recipient` or an array. + - Fan-out: pass the literal `"followers"` to deliver to the sender's + `Followers` collection. + +Always route outbound activities through the queue in production; this is +the same `queue` provided to `createFederation()` or `.build()`. Without +a queue the call blocks until every recipient responds and failed +deliveries have no retry. + +> [!CAUTION] +> Do not derive an activity's `id` from `(actor, object)`. The same +> actor can send the same activity shape to the same object more than +> once (for example `Follow` → `Undo(Follow)` → `Follow` again), and +> those must be distinct activities. Use a fresh UUID or counter in the +> fragment. + +See . + + +Vocabulary imports +------------------ + +Import ActivityStreams and ActivityPub vocabulary types from +`@fedify/vocab`. The historical path `@fedify/fedify/vocab` is a +deprecated shim kept for backwards compatibility; new code should not use +it. Likewise, `@fedify/vocab-runtime` replaces the old +`@fedify/fedify/runtime` path, and `@fedify/webfinger` replaces the old +in-tree `src/webfinger`. + +> [!CAUTION] +> Several vocabulary classes collide with JavaScript globals (notably +> `Object`). When importing, either use a namespace import +> (`import * as vocab from "@fedify/vocab"`) or alias the individual +> class. + +`fromJsonLd()` and `toJsonLd()` are asynchronous; always `await` them. + +> [!WARNING] +> `crossOrigin: "trust"` on vocabulary deserialization trusts embedded +> objects without re-fetching. Treat it as you would +> `dangerouslySetInnerHTML`. + +See . + + +Key pair management +------------------- + +`setActorDispatcher(...).setKeyPairsDispatcher(dispatcher)` supplies the +actor's key pairs. Return *two* keys per actor: + + - An RSA-PKCS#1-v1.5 key for HTTP Signatures (Mastodon interop). + - An Ed25519 key for FEP-8b32 Object Integrity Proofs. + +Fedify signs outbound activities with whatever keys are available; for +interop with the widest set of peers, provide both. + +> [!WARNING] +> Private keys must live in secret storage. They are not configuration; +> do not check them into repositories, embed them in container images, +> or expose them via admin endpoints. + +See . + + +Persistent storage +------------------ + +Fedify defines two storage interfaces: `KvStore` (key/value cache and +idempotence) and `MessageQueue` (delivery plus inbox processing), both +re-exported from `@fedify/fedify`. Use the built-in `MemoryKvStore` only +in development or tests. + +| Package | `KvStore` | `MessageQueue` | +| ------------------ | --------- | -------------- | +| *@fedify/sqlite* | yes | yes | +| *@fedify/postgres* | yes | yes | +| *@fedify/mysql* | yes | yes | +| *@fedify/redis* | yes | yes | +| *@fedify/amqp* | no | yes | +| *@fedify/denokv* | yes | yes | + +> [!WARNING] +> `PostgresMessageQueue` and similar implementations require connection +> pooling sized for parallel consumers; a single shared connection will +> deadlock under `ParallelMessageQueue`. See +> . + +> [!WARNING] +> Do not load-balance worker nodes that drain the queue. Each worker +> should take traffic independently; putting them behind a load balancer +> breaks idempotency tracking. See . + +See and . + + +Observability +------------- + +### LogTape + +Fedify emits structured logs via [LogTape] under the following +categories. Configure LogTape once at application start (if this +project has a separate LogTape skill installed, defer to it for the +generic setup): + + - `fedify.compat.transformers` + - `fedify.federation`, `fedify.federation.actor`, + `fedify.federation.collection`, `fedify.federation.fanout`, + `fedify.federation.http`, `fedify.federation.inbox`, + `fedify.federation.outbox`, `fedify.federation.queue` + - `fedify.nodeinfo.client` + - `fedify.otel.exporter` + - `fedify.sig.http`, `fedify.sig.key`, `fedify.sig.ld`, + `fedify.sig.proof` + - `fedify.utils.docloader`, `fedify.utils.kv-cache` + - `fedify.webfinger.server` + +> [!CAUTION] +> Since LogTape 0.7.0, implicit contexts require explicit configuration. +> See . + +[LogTape]: https://logtape.org/ + +### OpenTelemetry + +Pass a `tracerProvider` in `FederationOptions` to have Fedify instrument +its internals. For trace persistence, `@fedify/fedify/otel` exports +`FedifySpanExporter`, which writes traces to a `KvStore` so the +*@fedify/debugger* dashboard can render them. + +> [!CAUTION] +> Initialize the OpenTelemetry SDK *before* importing Fedify. Later +> registration leaves earlier spans untraced. + +See and +. + + +Looking up FEPs +--------------- + +When the user references a Fediverse Enhancement Proposal (for example +`FEP-8fcf` or `FEP-1b12`), clone the proposals repository locally and +read the relevant file; Codeberg blocks web scraping and `WebFetch`-style +requests fail: + +~~~~ bash +git clone https://codeberg.org/fediverse/fep.git +~~~~ + +Files are under *fep/* keyed by the four-hex-digit identifier (for +example *fep/8fcf/fep-8fcf.md*). If the project is configured with the +[FEP MCP server], prefer that instead. + +[FEP MCP server]: https://github.com/dahlia/fep-mcp + + +CLI helpers +----------- + +The `fedify` CLI (distributed as *@fedify/cli*) covers bootstrapping and +debugging: + + - `fedify init`: scaffold a new project (pick web framework, package + manager, KV store, and message queue). + - `fedify lookup`: resolve a handle, URL, or WebFinger identifier and + print the dereferenced document. + - `fedify inbox`: spin up a temporary inbox with a tunnel to inspect + incoming activities from real peers. + - `fedify webfinger`, `fedify nodeinfo`, `fedify tunnel`, + `fedify relay`. + +> [!WARNING] +> `fedify inbox` and `fedify tunnel` are development tools. They open a +> public tunnel to your local process; do not run them against +> production data. + +See . + + +Common mistakes to avoid +------------------------ + + - Forgetting to `await builder.build(...)` or `await ctx.sendActivity(...)`. + Both are asynchronous. + - Hand-rolling `/.well-known/webfinger` or `/.well-known/nodeinfo` + routes; Fedify already serves them. + - Importing from the deprecated shims `@fedify/fedify/vocab`, + `@fedify/fedify/runtime`, or `@fedify/fedify/webfinger` instead of + the dedicated packages `@fedify/vocab`, `@fedify/vocab-runtime`, and + `@fedify/webfinger`. + - Omitting the `queue` option in production; outgoing delivery becomes + synchronous and unreliable. + - Running with `MemoryKvStore` in production; it evaporates on every + restart. + - Running behind a reverse proxy, a tunnel (`fedify tunnel`, ngrok, + Cloudflare Tunnel, Tailscale Funnel), or a load balancer without + propagating the original origin. Fedify reads `request.url`, so + without `X-Forwarded-*` handling it will mint actor IDs and activity + URLs using the internal origin (for example `http://localhost:3000`) + instead of the public `https://…` address that remote peers + dereference. Fix one of two ways: pin + `FederationOptions.origin` to the canonical URL, or pipe requests + through [x-forwarded-fetch] before they reach Fedify (gated on a + `BEHIND_PROXY` flag, since `X-Forwarded-Host` is spoofable from the + open internet). See . + - Enabling `allowPrivateAddress: true` outside tests; that disables the + SSRF guard. + - Using `crossOrigin: "trust"` without verifying the remote is + actually trusted. + - Registering inbox handlers only for specific activity types and + expecting delivery-level error handling; unregistered types are + silently dropped. Add a catch-all on `Activity` if needed. + - Wiring Fedify into a web framework by writing custom routes instead + of importing the matching `@fedify/` package. + - Load-balancing queue worker nodes; each worker must take traffic + independently. + - Using simple URI-template expansion (`{identifier}`) when identifiers + contain reserved URI characters; switch to `{+identifier}`. + - Deriving an activity's `id` from `(actor, object)`; the same pair + can legitimately produce multiple activities of the same shape. + - Returning `Tombstone` from an actor dispatcher without checking + `RequestContext.getActor({ tombstone: "passthrough" })` semantics; + see . + - Committing private keys, embedding them in bundles, or exposing them + through admin endpoints. + +[x-forwarded-fetch]: https://github.com/dahlia/x-forwarded-fetch From b6c89bbc043c064103fe22d6300328538cf94783 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 22 Apr 2026 18:22:01 +0900 Subject: [PATCH 2/9] Correct the builder-pattern wording in the Fedify skill The Fedify skill claimed that both createFederationBuilder() and createFederation() return a Federation, but the builder returns a FederationBuilder; only .build() resolves to Federation. Rephrase so the distinction is clear. Also soften the Cloudflare Workers guidance: the builder is the documented approach because dispatcher registration can happen at module load time, but technically createFederation() can also be constructed inside the fetch handler. Drop the "require" wording. https://github.com/fedify-dev/fedify/pull/712#discussion_r3122894035 https://github.com/fedify-dev/fedify/pull/712#discussion_r3122881352 Assisted-by: Claude Code:claude-opus-4-7 --- packages/fedify/skills/fedify/SKILL.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/fedify/skills/fedify/SKILL.md b/packages/fedify/skills/fedify/SKILL.md index b84f1bf2d..5cd8e5b69 100644 --- a/packages/fedify/skills/fedify/SKILL.md +++ b/packages/fedify/skills/fedify/SKILL.md @@ -36,18 +36,22 @@ verify names against those docs or against the installed Builder pattern --------------- -Two entry points return a `Federation` object: +Two entry points reach a `Federation` object: - `createFederationBuilder()` returns a - `FederationBuilder`; register dispatchers and listeners, then - `await builder.build(options)` to get the `Federation`. Prefer this - in larger apps, especially when you need to split configuration - across files or avoid circular imports. Serverless runtimes such as - Cloudflare Workers *require* the builder: bindings are only available - per-request, so the `Federation` must be constructed inside the - request handler. - - `createFederation(options)` constructs the object - directly. Appropriate when everything fits in one module. + `FederationBuilder`. Register dispatchers and + listeners on it, then `await builder.build(options)` to obtain the + `Federation`. Prefer this in larger apps, especially + when you need to split configuration across files or avoid circular + imports. In serverless runtimes such as Cloudflare Workers, + bindings are only available per-request, so the `Federation` must be + constructed inside the request handler; the builder pattern is the + documented approach there because dispatcher registration can happen + at module load time and only the asynchronous `.build(options)` call + runs per request. + - `createFederation(options)` returns a + `Federation` directly. Appropriate when everything + fits in one module. `.build()` is asynchronous; always `await` it. See . From ff5efc4c71d8f74090af540ffc51d800cef1fff8 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 22 Apr 2026 18:23:08 +0900 Subject: [PATCH 3/9] Stop claiming @fedify/fedify/webfinger is a deprecated shim The @fedify/fedify package only exports ./vocab and ./runtime as deprecated shims; ./webfinger has never been a subpath export. The path agents should avoid is the old in-tree src/webfinger, not a non-existent @fedify/fedify/webfinger entrypoint. Rephrase the common-mistakes bullet accordingly; the Vocabulary imports section already described this correctly. https://github.com/fedify-dev/fedify/pull/712#discussion_r3122894079 https://github.com/fedify-dev/fedify/pull/712#discussion_r3122881423 Assisted-by: Claude Code:claude-opus-4-7 --- packages/fedify/skills/fedify/SKILL.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/fedify/skills/fedify/SKILL.md b/packages/fedify/skills/fedify/SKILL.md index 5cd8e5b69..dc002c387 100644 --- a/packages/fedify/skills/fedify/SKILL.md +++ b/packages/fedify/skills/fedify/SKILL.md @@ -403,10 +403,10 @@ Common mistakes to avoid Both are asynchronous. - Hand-rolling `/.well-known/webfinger` or `/.well-known/nodeinfo` routes; Fedify already serves them. - - Importing from the deprecated shims `@fedify/fedify/vocab`, - `@fedify/fedify/runtime`, or `@fedify/fedify/webfinger` instead of - the dedicated packages `@fedify/vocab`, `@fedify/vocab-runtime`, and - `@fedify/webfinger`. + - Importing from the deprecated shims `@fedify/fedify/vocab` or + `@fedify/fedify/runtime`, or from the old in-tree `src/webfinger` + path, instead of the dedicated packages `@fedify/vocab`, + `@fedify/vocab-runtime`, and `@fedify/webfinger`. - Omitting the `queue` option in production; outgoing delivery becomes synchronous and unreliable. - Running with `MemoryKvStore` in production; it evaporates on every From e4bb0aeb9dbea4d8f2266d41d8b2917df9be910d Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 22 Apr 2026 18:23:31 +0900 Subject: [PATCH 4/9] Add @fedify/cfworkers to the storage adapter matrix @fedify/cfworkers ships both WorkersKvStore and WorkersMessageQueue but was missing from the KvStore/MessageQueue support table in the skill. Add the row so agents running on Cloudflare Workers see it as an option. https://github.com/fedify-dev/fedify/pull/712#discussion_r3122894062 Assisted-by: Claude Code:claude-opus-4-7 --- packages/fedify/skills/fedify/SKILL.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/fedify/skills/fedify/SKILL.md b/packages/fedify/skills/fedify/SKILL.md index dc002c387..7e7d9502b 100644 --- a/packages/fedify/skills/fedify/SKILL.md +++ b/packages/fedify/skills/fedify/SKILL.md @@ -288,14 +288,15 @@ idempotence) and `MessageQueue` (delivery plus inbox processing), both re-exported from `@fedify/fedify`. Use the built-in `MemoryKvStore` only in development or tests. -| Package | `KvStore` | `MessageQueue` | -| ------------------ | --------- | -------------- | -| *@fedify/sqlite* | yes | yes | -| *@fedify/postgres* | yes | yes | -| *@fedify/mysql* | yes | yes | -| *@fedify/redis* | yes | yes | -| *@fedify/amqp* | no | yes | -| *@fedify/denokv* | yes | yes | +| Package | `KvStore` | `MessageQueue` | +| ------------------- | --------- | -------------- | +| *@fedify/sqlite* | yes | yes | +| *@fedify/postgres* | yes | yes | +| *@fedify/mysql* | yes | yes | +| *@fedify/redis* | yes | yes | +| *@fedify/amqp* | no | yes | +| *@fedify/denokv* | yes | yes | +| *@fedify/cfworkers* | yes | yes | > [!WARNING] > `PostgresMessageQueue` and similar implementations require connection From d26abb29b941e1ba18c0d762785378da1c1c4841 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 22 Apr 2026 18:23:53 +0900 Subject: [PATCH 5/9] Document the sender-type restriction on sendActivity fan-out The sendActivity overload that takes the literal "followers" as the recipients parameter only accepts { identifier } or { username } as the sender. A raw SenderKeyPair or SenderKeyPair[] is rejected, because Fedify needs the actor identifier to resolve the followers collection URI. The skill previously just noted the "followers" shortcut without this restriction, which would let agents generate code that fails at runtime. https://github.com/fedify-dev/fedify/pull/712#discussion_r3122894051 Assisted-by: Claude Code:claude-opus-4-7 --- packages/fedify/skills/fedify/SKILL.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/fedify/skills/fedify/SKILL.md b/packages/fedify/skills/fedify/SKILL.md index 7e7d9502b..33969a1f4 100644 --- a/packages/fedify/skills/fedify/SKILL.md +++ b/packages/fedify/skills/fedify/SKILL.md @@ -215,9 +215,14 @@ Outgoing activities `ctx.sendActivity(sender, recipients, activity, options?)` is the single entry point for outbound delivery. Two overloads: - - Explicit recipients: pass a single `Recipient` or an array. + - Explicit recipients: pass a single `Recipient` or an array. The + `sender` may be a `SenderKeyPair`, a `SenderKeyPair[]`, or + `{ identifier }` / `{ username }`. - Fan-out: pass the literal `"followers"` to deliver to the sender's - `Followers` collection. + `Followers` collection. In this overload the `sender` must be + `{ identifier }` or `{ username }`; a raw `SenderKeyPair` or + `SenderKeyPair[]` is rejected because Fedify needs the actor + identifier to resolve the followers collection. Always route outbound activities through the queue in production; this is the same `queue` provided to `createFederation()` or `.build()`. Without From 417d12e74def946f2022eab801bc423b23295cd7 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 22 Apr 2026 18:38:29 +0900 Subject: [PATCH 6/9] Correct the inbox listener fallback wording Unregistered activity types are not "silently ignored" at the inbox. handleInbox returns HTTP 202 Accepted to the sender, and routeActivity in packages/fedify/src/federation/inbox.ts logs the activity at error level as an unsupported type before giving up. Reflect that so the skill does not mislead agents into thinking the request disappeared without a trace. https://github.com/fedify-dev/fedify/pull/712#discussion_r3122984509 Assisted-by: Claude Code:claude-opus-4-7 --- packages/fedify/skills/fedify/SKILL.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/fedify/skills/fedify/SKILL.md b/packages/fedify/skills/fedify/SKILL.md index 33969a1f4..2f7d2aa41 100644 --- a/packages/fedify/skills/fedify/SKILL.md +++ b/packages/fedify/skills/fedify/SKILL.md @@ -133,9 +133,10 @@ Inbox listeners - `.withIdempotency(strategy)`. > [!WARNING] -> Activities of a type that is not registered via `.on()` are silently -> ignored. To catch everything, register a listener for the base -> `Activity` class. +> Activities of a type that is not registered via `.on()` are answered +> with HTTP 202 and logged at error level as an unsupported activity, +> but never reach a listener. To catch everything, register a listener +> for the base `Activity` class. See . From 6ab329caeee521173d69f6cf8500caaca32db15e Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 22 Apr 2026 18:38:57 +0900 Subject: [PATCH 7/9] Italicise the src/webfinger file-path references Project Markdown convention wraps file paths in asterisks for italics; the two in-tree src/webfinger mentions were using backticks. Switch them to *src/webfinger* so the formatting lines up with the rest of the skill and the CONTRIBUTING.md Markdown guide. https://github.com/fedify-dev/fedify/pull/712#discussion_r3122981217 https://github.com/fedify-dev/fedify/pull/712#discussion_r3122981256 Assisted-by: Claude Code:claude-opus-4-7 --- packages/fedify/skills/fedify/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/fedify/skills/fedify/SKILL.md b/packages/fedify/skills/fedify/SKILL.md index 2f7d2aa41..759e98fa9 100644 --- a/packages/fedify/skills/fedify/SKILL.md +++ b/packages/fedify/skills/fedify/SKILL.md @@ -248,7 +248,7 @@ Import ActivityStreams and ActivityPub vocabulary types from deprecated shim kept for backwards compatibility; new code should not use it. Likewise, `@fedify/vocab-runtime` replaces the old `@fedify/fedify/runtime` path, and `@fedify/webfinger` replaces the old -in-tree `src/webfinger`. +in-tree *src/webfinger*. > [!CAUTION] > Several vocabulary classes collide with JavaScript globals (notably @@ -411,7 +411,7 @@ Common mistakes to avoid - Hand-rolling `/.well-known/webfinger` or `/.well-known/nodeinfo` routes; Fedify already serves them. - Importing from the deprecated shims `@fedify/fedify/vocab` or - `@fedify/fedify/runtime`, or from the old in-tree `src/webfinger` + `@fedify/fedify/runtime`, or from the old in-tree *src/webfinger* path, instead of the dedicated packages `@fedify/vocab`, `@fedify/vocab-runtime`, and `@fedify/webfinger`. - Omitting the `queue` option in production; outgoing delivery becomes From 3157439e7d18412b4854a940923c05dfb805b0b7 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 22 Apr 2026 18:51:14 +0900 Subject: [PATCH 8/9] Align the common-mistakes bullet with the inbox listener warning Commit 417d12e7 corrected the inbox listener WARNING to say that unregistered activity types are answered with HTTP 202 and logged as unsupported, but the common-mistakes bullet at the bottom of the skill still said "silently dropped", which contradicted the earlier wording. Bring the two descriptions in line. https://github.com/fedify-dev/fedify/pull/712#discussion_r3123023427 Assisted-by: Claude Code:claude-opus-4-7 --- packages/fedify/skills/fedify/SKILL.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/fedify/skills/fedify/SKILL.md b/packages/fedify/skills/fedify/SKILL.md index 759e98fa9..91b2cb00f 100644 --- a/packages/fedify/skills/fedify/SKILL.md +++ b/packages/fedify/skills/fedify/SKILL.md @@ -435,7 +435,9 @@ Common mistakes to avoid actually trusted. - Registering inbox handlers only for specific activity types and expecting delivery-level error handling; unregistered types are - silently dropped. Add a catch-all on `Activity` if needed. + answered with HTTP 202 and logged at error level as unsupported, + but never reach a listener. Add a catch-all on `Activity` if you + need to observe them. - Wiring Fedify into a web framework by writing custom routes instead of importing the matching `@fedify/` package. - Load-balancing queue worker nodes; each worker must take traffic From d5c25b0a845ce2f5e5e0591190b25a2571e8db97 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 22 Apr 2026 20:23:14 +0900 Subject: [PATCH 9/9] Link to the Markdown form of fedify.dev docs fedify.dev serves every documentation page as raw Markdown by appending .md to its path, and fedify.dev/llms.txt already uses that convention. Agents can read the Markdown directly without HTML rendering noise, so switch every fedify.dev/manual/... and fedify.dev/cli link in the skill to the .md form. Also add a short note in the intro explaining the convention and telling agents to strip the .md suffix when surfacing links to the end user, so the browser view still resolves to the HTML page. https://github.com/fedify-dev/fedify/pull/712#discussion_r3123396216 https://github.com/fedify-dev/fedify/pull/712#discussion_r3123539466 Assisted-by: Claude Code:claude-opus-4-7 --- packages/fedify/skills/fedify/SKILL.md | 71 ++++++++++++++------------ 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/packages/fedify/skills/fedify/SKILL.md b/packages/fedify/skills/fedify/SKILL.md index 91b2cb00f..15511576c 100644 --- a/packages/fedify/skills/fedify/SKILL.md +++ b/packages/fedify/skills/fedify/SKILL.md @@ -25,12 +25,19 @@ parts of the fediverse (HTTP Signatures, Object Integrity Proofs, WebFinger, NodeInfo, JSON-LD, delivery queues) so application code can stay focused on dispatchers and activity handlers. -Always link into the full documentation at instead -of guessing. When the user asks about a specific API, treat - and as -authoritative; this skill only points the way. Do not invent APIs; -verify names against those docs or against the installed -`@fedify/fedify` types. +Always link into the full documentation at +instead of guessing. Every docs page is also served as raw Markdown +by appending `.md` to its path, so + returns `text/markdown`. +This skill uses the `.md` form in every fedify.dev link below so you +can read the source directly without HTML rendering; when you present +a link *to the user*, strip the `.md` suffix so browsers render the +HTML page (so `https://fedify.dev/manual/federation.md` becomes +`https://fedify.dev/manual/federation`). The index at + and the full bundle at + are authoritative; this skill only +points the way. Do not invent APIs; verify names against those docs +or against the installed `@fedify/fedify` types. Builder pattern @@ -54,7 +61,7 @@ Two entry points reach a `Federation` object: fits in one module. `.build()` is asynchronous; always `await` it. See -. +. ~~~~ typescript import { createFederationBuilder, MemoryKvStore } from "@fedify/fedify"; @@ -70,13 +77,13 @@ export const federation = await builder.build({ > Production deployments *must* provide a real `queue` implementation. > Without one, outgoing activities are sent synchronously and delivery > becomes unreliable under load. See -> . +> . > [!WARNING] > Never set `allowPrivateAddress: true` outside tests. It disables the > SSRF guard that blocks Fedify from fetching private or loopback -> addresses. See and -> . +> addresses. See and +> . Dispatchers @@ -107,16 +114,16 @@ template guarantees. Paths use URI templates. If an identifier can contain URI characters, switch the template variable from `{identifier}` to `{+identifier}` to -avoid double-encoding. See . +avoid double-encoding. See . > [!WARNING] > Simple expansion (`{identifier}`) percent-encodes reserved characters a > second time. If actors or objects are keyed by URIs, use reserved > expansion (`{+identifier}`). -See , -, and -. +See , +, and +. Inbox listeners @@ -138,7 +145,7 @@ Inbox listeners > but never reach a listener. To catch everything, register a listener > for the base `Activity` class. -See . +See . Context and `TContextData` @@ -162,8 +169,8 @@ to build canonical URIs instead of string-concatenating paths. > remote document is known to be trustworthy; it was the source of > prior interop bugs. -See and -. +See and +. Framework integrations @@ -193,7 +200,7 @@ content negotiation, signature verification, and response streaming. Two more packages are frequently useful: *@fedify/debugger* for a local ActivityPub dashboard, and *@fedify/relay* for relay implementations. -See . +See . Built-in protocol endpoints @@ -204,10 +211,10 @@ handler is mounted; do not reimplement them. - `/.well-known/webfinger` (WebFinger). Customize link output with `setWebFingerLinksDispatcher()`. See - . + . - `/.well-known/nodeinfo` and the versioned NodeInfo document. Customize with `setNodeInfoDispatcher()`. See - . + . Outgoing activities @@ -237,7 +244,7 @@ deliveries have no retry. > those must be distinct activities. Use a fresh UUID or counter in the > fragment. -See . +See . Vocabulary imports @@ -263,7 +270,7 @@ in-tree *src/webfinger*. > objects without re-fetching. Treat it as you would > `dangerouslySetInnerHTML`. -See . +See . Key pair management @@ -283,7 +290,7 @@ interop with the widest set of peers, provide both. > do not check them into repositories, embed them in container images, > or expose them via admin endpoints. -See . +See . Persistent storage @@ -308,14 +315,14 @@ in development or tests. > `PostgresMessageQueue` and similar implementations require connection > pooling sized for parallel consumers; a single shared connection will > deadlock under `ParallelMessageQueue`. See -> . +> . > [!WARNING] > Do not load-balance worker nodes that drain the queue. Each worker > should take traffic independently; putting them behind a load balancer -> breaks idempotency tracking. See . +> breaks idempotency tracking. See . -See and . +See and . Observability @@ -342,7 +349,7 @@ generic setup): > [!CAUTION] > Since LogTape 0.7.0, implicit contexts require explicit configuration. -> See . +> See . [LogTape]: https://logtape.org/ @@ -357,8 +364,8 @@ its internals. For trace persistence, `@fedify/fedify/otel` exports > Initialize the OpenTelemetry SDK *before* importing Fedify. Later > registration leaves earlier spans untraced. -See and -. +See and +. Looking up FEPs @@ -400,7 +407,7 @@ debugging: > public tunnel to your local process; do not run them against > production data. -See . +See . Common mistakes to avoid @@ -428,7 +435,7 @@ Common mistakes to avoid `FederationOptions.origin` to the canonical URL, or pipe requests through [x-forwarded-fetch] before they reach Fedify (gated on a `BEHIND_PROXY` flag, since `X-Forwarded-Host` is spoofable from the - open internet). See . + open internet). See . - Enabling `allowPrivateAddress: true` outside tests; that disables the SSRF guard. - Using `crossOrigin: "trust"` without verifying the remote is @@ -448,7 +455,7 @@ Common mistakes to avoid can legitimately produce multiple activities of the same shape. - Returning `Tombstone` from an actor dispatcher without checking `RequestContext.getActor({ tombstone: "passthrough" })` semantics; - see . + see . - Committing private keys, embedding them in bundles, or exposing them through admin endpoints.