Skip to content

Add outbox listeners for client-to-server posting#688

Merged
dahlia merged 64 commits intofedify-dev:mainfrom
dahlia:issue-430-outbox-listeners
Apr 18, 2026
Merged

Add outbox listeners for client-to-server posting#688
dahlia merged 64 commits intofedify-dev:mainfrom
dahlia:issue-430-outbox-listeners

Conversation

@dahlia
Copy link
Copy Markdown
Member

@dahlia dahlia commented Apr 17, 2026

Summary

Issue #430 narrowed the problem to one concrete piece of C2S support: handling POST /outbox through Fedify's routing surface. This PR implements that agreed surface without trying to land full server-side C2S semantics at once, so applications can keep authorization, persistence, recipient selection, and delivery policy in their own code.

Closes #430.

Changes

  • Adds setOutboxListeners() for actor outboxes, with typed .on(), .authorize(), and .onError() hooks
  • Adds OutboxContext.identifier and OutboxContext.forwardActivity() for POST /outbox handlers
  • Rejects posted activities whose actor does not match the addressed outbox owner
  • Falls back to the existing outbox dispatcher authorization predicate when listener-specific authorization is not configured
  • Keeps delivery explicit through ctx.sendActivity() or ctx.forwardActivity() instead of auto-federating after the handler returns
  • Preserves client-supplied signed JSON-LD when ctx.forwardActivity() relays the posted payload as-is
  • Logs a runtime warning when an outbox listener returns without delivering the posted activity
  • Adds the outbox-listener-delivery-required rule to packages/lint/ so missing delivery calls are caught statically, including named callbacks and chained registration forms
  • Adds createOutboxContext() and postOutboxActivity() support to packages/testing/, and aligns the mock outbox dispatch and ownership checks with runtime behavior
  • Documents the new flow in docs/manual/outbox.md and updates related guides and CHANGES.md

Testing

  • mise run check
  • mise run test
  • pnpm build in docs/

dahlia added 7 commits April 17, 2026 16:59
Add typed outbox listeners for POST requests to actor outboxes so
Fedify applications can handle client-to-server activities with the
same routing model as inbox listeners. Keep authorization
application-defined, leave federation delivery explicit through
ctx.sendActivity(), and mirror the new outbox context helpers in the
testing packages.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
Add a runtime warning when an outbox listener returns without calling
ctx.sendActivity(), since client-to-server posts are not federated
automatically. Also add an @fedify/lint rule and integration tests so
applications can catch the omission before it reaches runtime.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
Document how to handle POST requests to actor outboxes with
setOutboxListeners(), including explicit federation through
ctx.sendActivity(), authorization hooks, testing helpers, and the new
lint warning. Also add changelog entries for the new APIs and predict
this branch's pull request number for the release notes.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
Reject client-posted activities whose actor does not match the local
outbox owner before any listener runs, and add regression tests for
the mismatch. Also make the testing mocks able to execute registered
outbox listeners through postOutboxActivity(), and update the docs and
changelog to match the stronger behavior.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
Update the outbox middleware tests to use posted activities whose actor
matches the addressed local outbox owner, so the new ownership guard is
exercised intentionally instead of tripping the happy-path fixtures.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
Tighten the outbox listener lint rule and testing helpers so the
follow-up self-review fixes behave the same way in docs, mocks, and
static analysis. This avoids false positives from comments or
strings, rejects duplicate mock listeners, prefers the most specific
mock listener, and removes a misleading no-op catch-all example.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
Update the unreleased outbox listener changelog entries to point at
the current next pull request number. The earlier placeholder became
stale after newer issues and pull requests were created.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds server-side handling for client-to-server POST /outbox: new setOutboxListeners() API, OutboxContext, listener dispatching and delivery APIs (ctx.sendActivity() / ctx.forwardActivity()), delivery-tracking/runtime warning, test helpers, and a lint rule enforcing explicit delivery in listeners.

Changes

Cohort / File(s) Summary
Core handler & middleware
packages/fedify/src/federation/handler.ts, packages/fedify/src/federation/middleware.ts
Implements POST /outbox flow: actor resolution, authorize predicate, body parsing/validation, ownership checks, listener dispatch, delivery tracking/warning, error handling, and shared forwarding/send helpers.
Builder, API & types
packages/fedify/src/federation/builder.ts, packages/fedify/src/federation/federation.ts, packages/fedify/src/federation/callback.ts, packages/fedify/src/federation/context.ts, packages/fedify/src/federation/outbox.ts
Adds setOutboxListeners() and OutboxListenerSetters, exports OutboxContext and new callback/error types, and introduces OutboxListenerSet for dispatch.
Runtime context implementations
packages/fedify/src/federation/middleware.ts, packages/fedify/src/federation/outbox.ts
Adds OutboxContextImpl, ContextImpl.toOutboxContext(), delivery-state tracking (hasDeliveredActivity()), and centralizes forwarding logic reused by inbox/outbox.
Testing utilities & mocks
packages/fedify/src/testing/context.ts, packages/fedify/src/testing/mod.ts, packages/testing/src/context.ts, packages/testing/src/mock.ts, packages/testing/src/mock.test.ts, packages/testing/src/mod.ts
Adds createOutboxContext(), mock setOutboxListeners() and postOutboxActivity(); updates mocks to simulate forwarding, signature handling, and outbox dispatch for tests.
Tests (unit & integration)
packages/fedify/src/federation/handler.test.ts, packages/fedify/src/federation/middleware.test.ts, packages/fedify/src/federation/builder.test.ts, packages/testing/src/mock.test.ts, packages/lint/src/tests/...
New/extended tests for routing, authz, parsing, ownership validation, listener dispatch, delivery/queue behavior, and lint-rule scenarios.
Lint rule & wiring
packages/lint/src/rules/outbox-listener-delivery-required.ts, packages/lint/src/index.ts, packages/lint/src/mod.ts, packages/lint/src/lib/const.ts
Adds outbox-listener-delivery-required rule that detects missing ctx.sendActivity()/ctx.forwardActivity() in outbox listeners and registers it in the plugin.
Docs & site config
docs/manual/outbox.md, docs/manual/access-control.md, docs/manual/collections.md, docs/manual/context.md, docs/manual/lint.md, docs/manual/test.md, docs/.vitepress/config.mts
Adds Outbox manual and documentation updates: usage, authorization, delivery semantics, testing examples, lint guidance, and nav entry.
Exports & re-exports
packages/fedify/src/federation/callback.ts, packages/fedify/src/testing/mod.ts, packages/testing/src/mod.ts
Extends public exports for outbox-related types and test helpers (OutboxContext, createOutboxContext, etc.).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Middleware as Federation Middleware
    participant Handler as handleOutbox()
    participant Dispatcher as Actor Dispatcher
    participant AuthZ as Authorize Predicate
    participant Listeners as OutboxListenerSet
    participant Listener as Listener Handler
    participant Delivery as ctx.sendActivity()/ctx.forwardActivity()

    Client->>Middleware: POST /users/{id}/outbox
    Middleware->>Handler: handleOutbox(request, params)
    Handler->>Dispatcher: resolve actor for {id}
    alt actor not found
        Handler-->>Client: 404 Not Found
    else
        Handler->>AuthZ: run authorizePredicate(ctx)
        alt authorization fails
            Handler-->>Client: 401 Unauthorized
        else
            Handler->>Handler: parse JSON → Activity
            alt invalid JSON/Activity
                Handler-->>Client: 400 Bad Request
            else
                Handler->>Handler: validate activity.actor == outbox owner
                alt actor mismatch
                    Handler-->>Client: 400 Bad Request
                else
                    Handler->>Listeners: dispatchWithClass(activity)
                    alt no listener
                        Handler-->>Client: 202 Accepted
                    else
                        Listeners-->>Handler: (class, listener)
                        Handler->>Listener: invoke (OutboxContext, activity)
                        Listener->>Delivery: call sendActivity/forwardActivity
                        alt listener returns without delivery
                            Handler->>Handler: emit runtime warning
                        end
                        Handler-->>Client: 202 Accepted
                    end
                end
            end
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested labels

type/enhancement, component/actor

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title "Add outbox listeners for client-to-server posting" clearly describes the main feature being added, matching the primary objective from issue #430.
Description check ✅ Passed The description comprehensively details the implementation and aligns with the changeset: API surface (setOutboxListeners), OutboxContext additions, delivery semantics, lint rules, testing helpers, and documentation updates.
Linked Issues check ✅ Passed All coding requirements from #430 are met: setOutboxListeners() API with typed hooks [builder.ts, federation.ts], actor ownership validation [handler.ts], custom authorization via authorize() predicate [handler.ts, callback.ts], explicit delivery via sendActivity/forwardActivity [middleware.ts, context.ts], and application control over persistence/delivery [federation.ts, handler.ts].
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the setOutboxListeners() feature and its supporting infrastructure. Documentation, testing utilities, and lint rules are all necessary complements to the core feature without any extraneous modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@issues-auto-labeler issues-auto-labeler Bot added activitypub/compliance Specification compliance component/federation Federation object related component/outbox Outbox related component/testing Testing utilities (@fedify/testing) labels Apr 17, 2026
@dahlia dahlia requested review from 2chanhaeng and Copilot April 17, 2026 11:30
@dahlia dahlia self-assigned this Apr 17, 2026
@dahlia dahlia added type/feature Requests for new features and removed component/testing Testing utilities (@fedify/testing) labels Apr 17, 2026
@dahlia dahlia added this to the Fedify 2.2 milestone Apr 17, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for client-to-server ActivityPub POST requests to actor outboxes by adding setOutboxListeners() and OutboxContext. It includes a new lint rule to ensure activities are explicitly federated, comprehensive documentation, and enhanced testing utilities. Feedback was provided to optimize the outbox handler by removing an unnecessary request cloning operation during JSON parsing.

Comment thread packages/fedify/src/federation/handler.ts Outdated
@dahlia
Copy link
Copy Markdown
Member Author

dahlia commented Apr 17, 2026

@codex review

@dahlia
Copy link
Copy Markdown
Member Author

dahlia commented Apr 17, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for client-to-server ActivityPub POST requests to actor outboxes through the new setOutboxListeners() method and OutboxContext. The implementation includes core handler logic, middleware integration, and a new lint rule, outbox-listener-send-activity-required, which ensures that listeners explicitly federate activities using ctx.sendActivity(). Additionally, the PR provides testing utilities such as createOutboxContext() and postOutboxActivity(), alongside updated documentation. Feedback was provided to optimize the handleOutbox function by removing an unnecessary request clone during JSON parsing to improve efficiency.

Comment thread packages/fedify/src/federation/handler.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/manual/access-control.md`:
- Around line 114-117: The example currently compares the incoming header to
`Bearer ${identifier}` directly inside the .authorize callback
(ctx.request.headers.get("authorization")), which is insecure; replace this with
a real server-side validation or clearly annotate it as illustrative only: call
a verification helper such as verifyToken(token) or
authService.getUserFromToken(token) inside the authorize callback and then check
the verified subject/id against identifier (e.g., const user = await
verifyToken(token); return user?.id === identifier), or else add an explicit
comment near .authorize explaining the snippet is placeholder-only and must be
replaced with proper server-side token verification.

In `@docs/manual/collections.md`:
- Around line 34-37: Move the TIP block so it appears after the code example
introduced by the sentence referencing ~Federatable.setOutboxDispatcher();
specifically, relocate the existing [!TIP] about
~Federatable.setOutboxListeners() to immediately after the code fence (or at the
end of the Outbox section) so the colon-led sentence remains contiguous with its
code example while still keeping the cross-reference to Outbox listeners.

In `@docs/manual/lint.md`:
- Around line 626-635: The example listener currently calls ctx.sendActivity
with an empty recipients array (ctx.sendActivity(..., [] , ...)), which
federates to no one; update the example to pass a realistic recipient source
such as "followers" or the activity's intended audiences (e.g., activity.to/cc)
so the outbox post is actually delivered—locate the
federation.setOutboxListeners(...).on(Activity, async (ctx, activity) => { await
ctx.sendActivity({ identifier: ctx.identifier }, /* recipients */ , activity) })
snippet and replace the [] recipients argument with "followers" or an expression
that derives recipients from the activity.

In `@docs/manual/outbox.md`:
- Around line 15-17: Convert the inline links in this page (e.g., the inline
backtick links "POST /outbox", "GET /outbox" and the "./collections.md#outbox"
link) to reference-style links and add the corresponding reference definitions
at the end of the section where each is first used; replace the inline
occurrences with reference labels (for example: [POST /outbox][post-outbox],
[GET /outbox][get-outbox], [Collections - Outbox][collections-outbox]) and
append the link definitions (e.g., [post-outbox]: /outbox (POST) or appropriate
URL, [get-outbox]: /outbox (GET), [collections-outbox]: ./collections.md#outbox)
at the end of that section, and apply the same change to the other inline links
noted (the occurrences around lines 74-75, 121-122, 165-166).

In `@docs/manual/test.md`:
- Around line 300-317: The example shows createFederation and postOutboxActivity
but never registers an outbox listener; update the snippet to register an outbox
listener on the federation instance (using the testing library's outbox listener
registration API) before calling federation.postOutboxActivity, showing a
minimal handler that receives the activity (e.g., logs or asserts the received
activity/id and actor) so readers can see what postOutboxActivity is intended to
drive; keep references to createFederation and postOutboxActivity so the change
is easy to locate.

In `@packages/fedify/src/federation/builder.ts`:
- Around line 729-734: The error message in the setOutboxDispatcher code path is
reversed: when this.router.has("outbox") and the new dispatcher path (path) does
not equal this.outboxPath, change the RouterError text to indicate the
dispatcher path is wrong (e.g., "Outbox dispatcher path must match outbox
listener path.") so it mirrors setInboxDispatcher and the reciprocal message in
setOutboxListeners; update the message in the block that checks
this.router.has("outbox") inside setOutboxDispatcher to reference the dispatcher
matching the listener.

In `@packages/fedify/src/federation/handler.ts`:
- Around line 586-603: The actor-mismatch rejection currently logs and returns a
400 directly; modify this branch to call the existing outboxErrorHandler so this
rejection is processed consistently with the other 400 paths. Specifically, when
activity.actorIds is empty or any actorId.href !== expectedActorId.href,
construct a descriptive Error (or a new ActorMismatchError) including
identifier, activity.id, expectedActorId.href and actorIds, then pass that error
and the same request/context into outboxErrorHandler (the same signature used at
lines handling JSON/activity parse failures) and return its Response; remove the
direct Response return so all observability/metrics flow through
outboxErrorHandler while preserving the current log/context information.
- Around line 662-671: The handler currently returns 202 with no Location and
discards the listener result (see the listener invocation around line 619),
which breaks ActivityPub C2S §6.2; change the outbox handler in
federation/handler.ts so the listener (the function invoked at/near line 619)
may return a URL or a Response indicating the canonical activity id, then in the
handler inspect that return value: if a URL or Response with a Location is
provided, respond with 201 Created and set the Location header to the canonical
id (and include the Response body if returned), otherwise keep the async
fallback of 202 Accepted; update the listener type/signature accordingly (allow
URL | Response) and ensure callers propagate the canonical id back to the
handler so compliant clients receive the Location header.

In `@packages/fedify/src/federation/middleware.ts`:
- Around line 3196-3208: The sendActivity override sets `#sendActivityState.sent`
to true synchronously before awaiting super.sendActivity, so hasSentActivity()
can be true even if send fails; change the method to await
super.sendActivity(...) first and only set `#sendActivityState.sent` = true after
the await completes successfully (do not catch/suppress errors that would
indicate a failed send), keeping the original method signature and returning the
Promise result so callers still observe rejections from Context.sendActivity;
reference the sendActivity override, `#sendActivityState.sent`,
super.sendActivity(), hasSentActivity(), and handleOutbox for locating the
change.

In `@packages/lint/src/rules/outbox-listener-send-activity-required.ts`:
- Around line 81-84: The rule currently skips non-inline listeners (it only
checks node.arguments[1] when isFunction(listener)), so patterns like
.on(Activity, handler) where handler is an Identifier or const arrow are
ignored; modify the logic to resolve referenced listener callbacks before
skipping: if listener is an Identifier (or MemberExpression referencing a local
const), use the ESLint scope utilities (e.g., context.getScope() / findVariable)
to find the variable or function declaration/initializer, extract the underlying
FunctionExpression/ArrowFunctionExpression or FunctionDeclaration AST node, and
pass that resolved node into listenerCallsSendActivity (fall back to original
behavior if resolution fails). Update references to listener, isFunction, and
listenerCallsSendActivity accordingly so named symbols are used to locate and
test the resolved callback.

In `@packages/lint/src/tests/integration.test.ts`:
- Around line 182-215: Add integration tests that cover fluent/chained
registration forms so the rule detects handlers attached via chained calls;
extend the existing cases for setOutboxListeners().on by adding analogous tests
that call setOutboxListeners(...).authorize(...).on(...) and
setOutboxListeners(...).onError(...).on(...) (both with an explicit
ctx.sendActivity and with the missing sendActivity case) so the linter rule is
exercised against chained invocation patterns (look for setOutboxListeners,
authorize, onError, and on in the new test strings).

In `@packages/testing/src/mock.ts`:
- Around line 366-376: The mock's setOutboxListeners/on currently keys listeners
by type.name (using type.name and activity.constructor.name), which causes
collisions and differs from runtime behavior; change it to store and match
listeners by the actual constructor function (use the type itself as the key in
this.outboxListeners and compare by identity while walking prototypes rather
than using type.name strings), and update the corresponding removal/dispatch
logic (the on(...) and any unregister/emit code around outboxListeners) so it
looks up listeners by constructor identity and checks prototype chain
matches—refer to setOutboxListeners, its on method, and the outboxListeners Map
when making the fix.
- Around line 492-521: postOutboxActivity currently dispatches to listeners
without verifying the activity's actor belongs to the addressed identifier; add
the same ownership guard used in the real outbox POST flow before any listener
runs: extract the actor from the Activity (e.g. activity.actor), normalize it to
a string, and verify it corresponds to the provided identifier (or is owned by
that actor URL/actor id as production does); if the check fails, throw the same
kind of rejection/error (e.g. new Error("Actor does not belong to the addressed
identifier") or the project's standard error) so listeners are never invoked for
outbox ownership violations (refer to postOutboxActivity, createOutboxContext
and outboxListeners to locate where to insert the guard).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9acc22d0-bec0-4dda-b451-d968a958609f

📥 Commits

Reviewing files that changed from the base of the PR and between 06d31be and 730a7f7.

📒 Files selected for processing (30)
  • CHANGES.md
  • docs/.vitepress/config.mts
  • docs/manual/access-control.md
  • docs/manual/collections.md
  • docs/manual/context.md
  • docs/manual/lint.md
  • docs/manual/outbox.md
  • docs/manual/test.md
  • packages/fedify/src/federation/builder.test.ts
  • packages/fedify/src/federation/builder.ts
  • packages/fedify/src/federation/callback.ts
  • packages/fedify/src/federation/context.ts
  • packages/fedify/src/federation/federation.ts
  • packages/fedify/src/federation/handler.test.ts
  • packages/fedify/src/federation/handler.ts
  • packages/fedify/src/federation/middleware.test.ts
  • packages/fedify/src/federation/middleware.ts
  • packages/fedify/src/federation/outbox.ts
  • packages/fedify/src/testing/context.ts
  • packages/fedify/src/testing/mod.ts
  • packages/lint/src/index.ts
  • packages/lint/src/lib/const.ts
  • packages/lint/src/mod.ts
  • packages/lint/src/rules/outbox-listener-send-activity-required.ts
  • packages/lint/src/tests/integration.test.ts
  • packages/lint/src/tests/outbox-listener-send-activity-required.test.ts
  • packages/testing/src/context.ts
  • packages/testing/src/mock.test.ts
  • packages/testing/src/mock.ts
  • packages/testing/src/mod.ts

Comment thread docs/manual/access-control.md
Comment thread docs/manual/collections.md Outdated
Comment thread docs/manual/lint.md
Comment thread docs/manual/outbox.md
Comment thread docs/manual/test.md
Comment thread packages/fedify/src/federation/middleware.ts
Comment thread packages/lint/src/rules/outbox-listener-send-activity-required.ts Outdated
Comment thread packages/lint/src/tests/integration.test.ts
Comment thread packages/testing/src/mock.ts Outdated
Comment thread packages/testing/src/mock.ts
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 17, 2026

Codecov Report

❌ Patch coverage is 81.40351% with 265 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...int/src/rules/outbox-listener-delivery-required.ts 66.21% 103 Missing and 21 partials ⚠️
packages/fedify/src/federation/handler.ts 64.84% 64 Missing and 13 partials ⚠️
packages/fedify/src/federation/middleware.ts 94.87% 0 Missing and 19 partials ⚠️
packages/testing/src/mock.ts 93.60% 6 Missing and 8 partials ⚠️
packages/fedify/src/federation/builder.ts 85.54% 9 Missing and 3 partials ⚠️
packages/fedify/src/testing/context.ts 55.55% 8 Missing ⚠️
packages/testing/src/context.ts 76.00% 6 Missing ⚠️
packages/fedify/src/sig/proof.ts 90.90% 2 Missing and 2 partials ⚠️
...ackages/fedify/src/federation/activity-listener.ts 97.50% 0 Missing and 1 partial ⚠️
Files with missing lines Coverage Δ
packages/fedify/src/federation/inbox.ts 74.44% <100.00%> (-3.69%) ⬇️
packages/fedify/src/sig/ld.ts 89.43% <100.00%> (+3.20%) ⬆️
packages/fedify/src/sig/mod.ts 100.00% <100.00%> (ø)
packages/lint/src/index.ts 100.00% <100.00%> (ø)
packages/lint/src/lib/const.ts 100.00% <100.00%> (ø)
packages/lint/src/mod.ts 100.00% <100.00%> (ø)
packages/testing/src/mod.ts 100.00% <100.00%> (ø)
...ackages/fedify/src/federation/activity-listener.ts 97.50% <97.50%> (ø)
packages/fedify/src/sig/proof.ts 70.93% <90.90%> (+3.18%) ⬆️
packages/testing/src/context.ts 58.49% <76.00%> (+4.47%) ⬆️
... and 6 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 730a7f7ccf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/fedify/src/federation/middleware.ts Outdated
Comment thread packages/testing/src/mock.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a first-class routing surface for ActivityPub client-to-server POST requests to actor outboxes, via typed “outbox listeners”, while keeping authorization, persistence, and delivery policy application-defined.

Changes:

  • Adds setOutboxListeners() with .on(), .authorize(), and .onError() hooks and introduces OutboxContext.identifier.
  • Implements POST /outbox dispatch, including actor/outbox-owner matching and a runtime warning when listeners don’t call ctx.sendActivity().
  • Adds a new @fedify/lint rule plus @fedify/testing helpers (createOutboxContext(), postOutboxActivity()) and documents the flow.

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/testing/src/mod.ts Re-exports createOutboxContext from testing API surface.
packages/testing/src/mock.ts Adds mock setOutboxListeners() and postOutboxActivity() to exercise outbox listeners in tests.
packages/testing/src/mock.test.ts Adds coverage for outbox listener dispatch + identifier propagation in testing mock.
packages/testing/src/context.ts Adds createOutboxContext() helper for the testing package.
packages/lint/src/tests/outbox-listener-send-activity-required.test.ts Adds rule-level tests for new outbox listener lint rule.
packages/lint/src/tests/integration.test.ts Adds integration coverage for the new lint rule.
packages/lint/src/rules/outbox-listener-send-activity-required.ts Introduces lint rule enforcing explicit ctx.sendActivity() in outbox listeners.
packages/lint/src/mod.ts Registers the new rule in the Deno lint plugin.
packages/lint/src/lib/const.ts Adds new RULE_ID constant.
packages/lint/src/index.ts Registers the new rule in the ESLint plugin export.
packages/fedify/src/testing/mod.ts Exposes createOutboxContext() from @fedify/fedify/testing.
packages/fedify/src/testing/context.ts Adds createOutboxContext() helper for fedify’s internal testing utilities.
packages/fedify/src/federation/outbox.ts Adds OutboxListenerSet with prototype-chain dispatch.
packages/fedify/src/federation/middleware.ts Routes POST /outbox to handleOutbox() and adds OutboxContextImpl with send-tracking.
packages/fedify/src/federation/middleware.test.ts Adds end-to-end middleware tests for outbox listeners, auth, and warning behavior.
packages/fedify/src/federation/handler.ts Implements handleOutbox() for parsing, actor validation, dispatching, and error handling.
packages/fedify/src/federation/handler.test.ts Adds unit tests for handleOutbox() behavior (auth, invalid JSON, actor mismatch, error handler).
packages/fedify/src/federation/federation.ts Adds public types/API: setOutboxListeners() and OutboxListenerSetters.
packages/fedify/src/federation/context.ts Adds OutboxContext interface and identifier.
packages/fedify/src/federation/callback.ts Adds OutboxListener and OutboxListenerErrorHandler callback types.
packages/fedify/src/federation/builder.ts Adds builder support for outbox listeners and wires listener path validation/clone.
packages/fedify/src/federation/builder.test.ts Adds builder tests for outbox listener routing + path validation.
docs/manual/test.md Documents testing helpers for outbox listeners (createOutboxContext, postOutboxActivity).
docs/manual/outbox.md New manual page describing outbox listeners, authorization, explicit delivery, and errors.
docs/manual/lint.md Documents outbox-listener-send-activity-required rule.
docs/manual/context.md Adds outbox listeners to list of Context consumers.
docs/manual/collections.md Notes relationship between outbox collection (GET) and outbox listeners (POST).
docs/manual/access-control.md Documents authorization hook for POST /outbox.
docs/.vitepress/config.mts Adds “Outbox listeners” to manual navigation sidebar.
CHANGES.md Adds changelog entries for fedify/lint/testing additions.

Comment thread packages/lint/src/rules/outbox-listener-send-activity-required.ts Outdated
Comment thread packages/testing/src/mock.ts Outdated
Comment thread packages/fedify/src/federation/federation.ts Outdated
dahlia added 3 commits April 17, 2026 21:15
Outbox listeners need the same escape hatch that inbox listeners have
when they must relay a signed activity without round-tripping it
through Fedify's vocabulary objects. This adds
OutboxContext.forwardActivity(), reuses the existing forwarding path
for raw posted JSON-LD, and treats explicit forwarding as delivery so
outbox warnings only fire when nothing was actually sent.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
Now that outbox listeners can either send a new activity or forward the
posted payload as-is, the surrounding rule names, docs, and testing
helpers need to say "delivery" rather than "sendActivity". This
renames the lint rule, updates the manuals and changelog, and teaches
@fedify/testing's outbox mocks about forwardActivity and
skipIfUnsigned.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
Outbox listener POST handling had a few edge cases that diverged from the
existing outbox dispatcher flow. This reuses dispatcher authorization
when listener-specific authorization is unset, routes actor-mismatch
rejections through the outbox error hook for consistent observability,
removes an unnecessary request clone while parsing JSON, and clarifies
mismatch wording in the builder and public JSDoc.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
dahlia added 2 commits April 18, 2026 20:40
Linked Data Signature payloads can expose `signature.type` as either a
string or an array of strings after compaction and expansion. The quick
signature detector now accepts both forms so skipIfUnsigned does not
misclassify signed payloads, and a regression covers the array case.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
Outbox listeners already reject query and fragment identifier operators
because the router only matches URL pathnames. This applies the same
validation to setOutboxDispatcher() so both entry points enforce the
same routable pathname-only subset, with a regression for the query case.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
@dahlia dahlia requested a review from Copilot April 18, 2026 12:00
@dahlia
Copy link
Copy Markdown
Member Author

dahlia commented Apr 18, 2026

@codex review

@dahlia
Copy link
Copy Markdown
Member Author

dahlia commented Apr 18, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces client-to-server ActivityPub outbox listeners, allowing developers to handle POST requests to actor outboxes using typed listeners and custom authorization logic. Key additions include the setOutboxListeners() method, an OutboxContext for activity delivery and forwarding, and a new lint rule to ensure activities are explicitly federated. The PR also provides comprehensive testing support for outbox activities in the @fedify/testing package. I have no feedback to provide.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 45 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a98a33705

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/testing/src/mock.ts
Comment thread packages/testing/src/mock.ts Outdated
The remaining @fedify/testing gaps were both in the mock POST /outbox
context. This makes outbox path validation reject non-absolute
templates like the runtime router does, and it wires sendActivity() and
forwardActivity() into the mock outbox delivery state so tests see the
same hasDeliveredActivity() behavior as runtime.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
@dahlia dahlia requested a review from Copilot April 18, 2026 12:21
@dahlia
Copy link
Copy Markdown
Member Author

dahlia commented Apr 18, 2026

@codex review

@dahlia
Copy link
Copy Markdown
Member Author

dahlia commented Apr 18, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements support for client-to-server ActivityPub outbox POST requests by adding setOutboxListeners() and the OutboxContext API. It also introduces a new lint rule, outbox-listener-delivery-required, to ensure activities are explicitly delivered, and provides comprehensive testing utilities and documentation. Feedback identifies a bug in the outbox handler where the request body is consumed before the listener can access it if no authorization predicate is defined; always cloning the request is recommended to ensure the body remains available for the listener.

Comment thread packages/fedify/src/federation/handler.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 45 changed files in this pull request and generated 1 comment.

Comment thread packages/testing/src/mock.ts Outdated
Copy link
Copy Markdown
Contributor

@2chanhaeng 2chanhaeng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I commented suggestions about some duplications.

Comment thread packages/fedify/src/federation/middleware.ts Outdated
Comment thread packages/lint/src/tests/integration.test.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6e3de857f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/fedify/src/federation/handler.ts
dahlia added 2 commits April 18, 2026 21:52
The remaining outbox handler review comments were all about request and
queue control flow. Authorization now runs before actor resolution so
write endpoints do not leak account existence, body-aware auth still
gets a readable Request clone, and the queue enqueue branch has been
flattened to the simpler equivalent structure the review suggested.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
The mock outbox helper was still checking skipIfUnsigned against the
parsed Activity instance instead of the raw posted JSON-LD. That could
diverge from runtime when proof information only survived in the raw
payload shape, so the mock now uses hasProofLike(rawActivity) and adds
an expanded-proof regression for that path.

fedify-dev#430

Assisted-by: OpenCode:gpt-5.4
The current staged changes are both follow-ups to earlier outbox review
fixes. One middleware regression now expects the 401 returned by
auth-before-actor handling, and the mock proof-shape regression uses a
normal Create instance with an overridden raw JSON-LD view instead of
trying to deserialize an intentionally shape-only proof payload.

fedify-dev#688

Assisted-by: OpenCode:gpt-5.4
@dahlia
Copy link
Copy Markdown
Member Author

dahlia commented Apr 18, 2026

@codex review

@dahlia
Copy link
Copy Markdown
Member Author

dahlia commented Apr 18, 2026

/gemini review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 45 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements client-to-server ActivityPub support by introducing outbox listeners and the OutboxContext interface. It adds the setOutboxListeners() method to the Federation builder, a dedicated handleOutbox handler, and a new lint rule, outbox-listener-delivery-required, to ensure activities are explicitly federated. The changes also include testing utilities for outbox activities, extensive documentation updates, and a fix for OpenTelemetry span attributes regarding cc and bcc identifiers. I have no feedback to provide.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

activitypub/compliance Specification compliance component/federation Federation object related component/outbox Outbox related type/feature Requests for new features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Outbox listeners — Client-to-Server interactions

3 participants