[mirror] Add outbox listeners for client-to-server posting#1
[mirror] Add outbox listeners for client-to-server posting#1yashwant86 wants to merge 64 commits intomm-base-688from
Conversation
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
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
Review feedback exposed two places where helper behavior still drifted from production semantics. This teaches the lint rule to follow named callbacks and actual context-bound delivery calls, and it updates @fedify/testing's outbox mock to mirror constructor-based dispatch, prototype lookup, ownership checks, and skipIfUnsigned handling. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The new outbox listener docs were still a little rough in places: the POST examples used a predictable bearer token placeholder, one lint example delivered to nobody, the collections guide interrupted its own code example, and the testing guide never showed the listener that postOutboxActivity exercises. This tightens those examples and converts outbox guide links to the reference style used elsewhere in the manual. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
Reviewing the forwardActivity follow-up exposed a few runtime gaps in the new outbox path. This starts the outbox worker automatically before queued forwarding, accepts reserved-expansion outbox listener paths in the builder like the public API does, and avoids logging the full posted activity payload when handler errors or unsupported activity types are reported. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The review thread turned up a few smaller cases where the new helper surfaces still drifted from runtime behavior. This teaches the delivery lint rule about defaulted context parameters, optional chaining, type-asserted context access, and template interpolation, and it brings @fedify/testing's outbox mock closer to runtime semantics for signed forwarding and actor ownership checks. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
One of the outbox guide follow-ups had slipped back to an inline link for the Collections cross-reference. This switches it back to the page's existing reference-style link so the manual stays consistent. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The queue auto-start regression test used an async listen() stub with no await, which tripped the repository's require-await lint rule during the final verification pass. Returning a resolved promise keeps the test behavior the same without the lint violation. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The forwardActivity helper was still a little rough around the edges. This switches it over to the same span-as-return structure used by sendActivity() and generates a single started timestamp for each queued forwarding batch so every message in the batch shares the same enqueue metadata. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The latest review round found a few remaining mismatches in the runtime outbox forwarding path. This preserves recipient actor IDs on queued forwardActivity() messages so permanent-failure handlers keep their context, stops logging the posted activity payload when the outbox error handler itself fails, and serializes the Logtape-based warning tests so parallel test execution does not race on global logger state. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The outbox delivery lint rule now strips template literal text while preserving interpolations, so the rule needs an explicit regression test for template strings that merely mention sendActivity and forwardActivity. This keeps the rule from regressing back to a raw-text false positive. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The mock outbox helper was still more permissive than the real POST /outbox flow. This teaches it to reject unknown owners, respect listener-side authorization and error hooks, and expand reserved URI variables in outbox paths. The testing manual now shows the actor registration and explicit delivery that this stricter mock expects, and it also fixes a small typo in the Fedify testing export comment. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The outbox delivery lint rule already ignored ordinary string literals, but it still dropped bracket-notation calls like ctx["sendActivity"](). This keeps delivery-method string literals intact while the rest of the string content is stripped, and adds regression coverage for that case alongside the recent template-literal check. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The latest review round mostly focused on how outbox validation errors surface to applications. This preserves advertised activity id/type in parse-failure contexts, gives missing-actor posts a distinct 400 message instead of lumping them in with true ownership mismatches, keeps the 405 outbox test aligned with the route configuration it exercises, and cleans up the plural wording in OutboxContext.forwardActivity(). fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The mock postOutboxActivity() path was still looser than the real outbox handler in a few ways. This makes it fail fast when no outbox listeners are configured, honors dispatcher-level authorization when listener-specific auth is unset, and routes owner-mismatch failures through the registered outbox error handler so mock-based tests track runtime behavior more closely. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The latest lint review batch found two conflicting edge cases in the regex-based delivery rule: bracket-notation delivery calls need to stay visible, but template-literal text that merely mentions ctx.sendActivity must not count as delivery. This teaches the string stripper to keep only the delivery method names that matter for bracket notation while blanking out unrelated template text, and it adds regression coverage for both bracket and template forms. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The latest review round found a few remaining gaps in the testing helper's POST /outbox simulation. This stops treating an empty listener map as uninitialized, makes unsupported activity types a no-op, keeps missing-actor failures distinct from true owner mismatches, and tightens the mock's RFC 6570 expansion and linked-data-signature checks so tests stay closer to runtime behavior. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
InboxListenerSet and OutboxListenerSet had become empty wrappers once the shared listener dispatch logic moved into ActivityListenerSet. This switches the inbox and outbox paths over to the shared type directly, removes the now-redundant outbox module, and keeps the listener set behavior in one place. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The mock POST /outbox helper was still returning too early when no listener matched the posted activity type. This now runs actor, authorization, and owner checks before falling back to a no-op for unsupported activities, and the related tests were tightened to cover that validation order without keeping a duplicate no-op case around. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The latest review round found a few remaining fidelity gaps in the @fedify/testing outbox mock. This tightens reserved URI-template expansion, broadens linked-data-signature detection beyond a single suite, fails fast when a matching listener would run without initialized context data, and records the raw forwarded payload so tests can tell forwardActivity() apart from sendActivity(). fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The mock's linked-data-signature helper was already behaving correctly, but the property access chain was harder to read than it needed to be. This rewrites the helper around explicit Record<string, unknown> intermediates so the narrowing is obvious and the intent is easier to follow. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The latest Deno formatter release changed how several example SVG and HTML files are wrapped. This keeps the worktree in sync with the new formatter output so repository-wide checks can pass again. fedify-dev#688 Assisted-by: OpenCode:gpt-5.4
MockFederation.postOutboxActivity() should expose the same posted body to body-aware authorization callbacks that runtime POST /outbox handling does. This serializes the posted activity into the mock Request before authorization runs and adds regressions for body-aware auth plus the remaining outbox path and signature-shape edge cases in the mock. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The shared forwarding helper only needs a cheap presence check before it warns about unsigned payloads. This adds a structural Data Integrity proof detector and uses it before the expensive Activity.fromJsonLd() path, while also preserving the warning metadata that the review thread called out. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
setOutboxListeners() was validating the template shape only after it had already registered the route. This left the builder in a broken state if an invalid path threw midway through registration. The validation now runs before the route is added, and a regression test covers retrying with a corrected path afterward. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The remaining mock review comments were both about keeping the testing
helper aligned with runtime outbox behavior. This wires the posted JSON
body into mock authorization, validates outbox dispatcher/listener path
compatibility in both call orders, and clarifies the mock error message
so it matches the fact that both {identifier} and {+identifier} are
allowed.
fedify-dev#430
Assisted-by: OpenCode:gpt-5.4
The latest review round turned up two cases where outbox forwarding was still too eager to count something as delivered: the unsigned check could still miss expanded proof shapes, and zero extracted inboxes were being treated as a successful forward. This broadens the structural proof detector and leaves forwardActivity() undelivered when no inboxes are found, with regressions for both cases. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The mock outbox helper still handed a reduced OutboxContext to onError(), and that diverged from runtime behavior in two ways: recovery handlers could not call forwardActivity(), and body-aware authorization callbacks still had to guess at the posted request shape. This reuses a fully wired mock OutboxContext for both success and error paths and adds regressions for error-handler forwarding and body-aware auth. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The latest review round found two gaps in forwarded outbox delivery. First, zero resolved inboxes were still being treated as a successful send, which could suppress the outbox warning even when nothing was delivered. Second, hasSignatureLike() still missed JSON-LD reference forms such as @id and signature arrays, which could make skipIfUnsigned too strict. This fixes both and adds regressions. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The temporary outbox path validator was still narrower than the
Rfc6570Expression type and rejected otherwise valid operator forms like
{/identifier} and {?identifier}. Using a temporary Router for the
validation keeps the builder state clean on failure and aligns the
runtime check with the actual route syntax the public type allows.
fedify-dev#430
Assisted-by: OpenCode:gpt-5.4
The outbox-listener span metadata still had a few inaccuracies. This fixes cc and bcc to use the correct recipient lists in span attributes and makes inbox-side forwardActivity() spans use the inbox category instead of being mislabeled as outbox work. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The @fedify/testing URI-template helpers were still narrower than the runtime builder and only handled simple and reserved identifier expansion. This teaches the mock helpers to support the full set of identifier operators Fedify accepts and adds regressions for the path, query, and dispatcher/listener compatibility cases. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The latest review round reopened two runtime details that needed to stay correct together: body-aware outbox authorization must not consume the request that onUnauthorized() receives, and the outbox/inbox telemetry must report the right recipient attributes and span names. This keeps a fresh unauthorized request clone in the handler and adds focused regressions for the updated telemetry behavior. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The outbox path surface had drifted into accepting RFC6570 operators that cannot ever match incoming POST /outbox requests because routing only looks at URL pathnames. This rejects query and fragment-style identifier operators in both the runtime builder and the testing mocks, and it keeps the mock path checks aligned with the same error semantics. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The outbox delivery lint rule was still analyzing .on(...) calls before later FunctionDeclaration bindings had been seen, which let hoisted listener declarations slip through without any delivery check. This defers reporting until Program:exit and adds a regression for that common declaration pattern. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The testing mock still expanded `{/identifier}` differently from the
runtime router by leaving `/` unescaped in path-segment expansion. This
switches that operator over to percent-encoding and updates the
regression so mock-built outbox URLs now match Router.build().
fedify-dev#430
Assisted-by: OpenCode:gpt-5.4
The latest review round surfaced two small correctness gaps in the
outbox listener path. First, the builder was only checking the set of
variable names, so repeated {identifier} expressions could slip past the
"one variable" rule. Second, the unauthorized-path request clones can
be created only after actor lookup succeeds, while still preserving
request.clone() semantics for body-aware authorization callbacks.
fedify-dev#430
Assisted-by: OpenCode:gpt-5.4
The last remaining worktree change was just the swapped regression expectations for reserved and path-segment identifier expansion in the mock tests. This puts the assertions back in line with the runtime Router.build() behavior so the Deno test suite stays green. fedify-dev#688 Assisted-by: OpenCode:gpt-5.4
The outbox handler was still returning bare 202 responses without the plain-text empty body used by the rest of the federation handlers. This makes both the successful listener path and the unsupported-activity path return an explicit empty text response and adds regression coverage for the header shape. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The structural proof detector used by the forwarding path still only looked under the compact `proof` key. Expanded JSON-LD can surface the same value under the full security vocabulary IRI, so this now checks both forms and adds a regression for that expanded representation. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The testing mock had one last reviewer-facing mismatch in its own surface area: the URI-template helper comment implied a single-variable limitation it did not actually have, and the outbox validation error did not explain that query and fragment expansion for identifier are what is being rejected. This aligns the docs and test expectations with the real behavior. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The outbox handler only needs a cloned Request after actor lookup succeeds, and only needs a second body-preserving clone at all when an authorization callback is configured. This moves the clones down to the point where they are actually needed while keeping the existing request.clone() behavior for body-aware authorization callbacks. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The structural proof detector used by forwarded delivery warnings still expected compact property names inside Data Integrity proofs. Expanded JSON-LD can surface those fields under their full security vocabulary IRIs, so this teaches hasProofLike() to read both forms and adds a regression for the expanded representation. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
The outbox warning path should not rely on an implementation-only property. This adds hasDeliveredActivity() to OutboxContext itself, threads a default implementation through the testing helpers, and keeps summarizeJsonActivity() explicitly narrowed through Record<string, unknown> so the handler remains type-safe. fedify-dev#430 Assisted-by: OpenCode:gpt-5.4
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
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
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
|
/review --force |
⚡ Risk Assessment —
|
| Files | Summary |
|---|---|
Outbox Listener Core Implementationpackages/fedify/src/federation/builder.ts, handler.ts, middleware.ts, context.ts, callback.ts |
Implements setOutboxListeners() API for registering client-to-server activity handlers. Adds OutboxContext with forwardActivity() and recipient management. Integrates outbox listener dispatch into POST /outbox request handling with error callbacks and span tracking. |
Activity Listener Abstractionpackages/fedify/src/federation/activity-listener.ts, inbox.ts |
Extracts common listener dispatch logic into reusable activity-listener module. Refactors inbox listener to use shared abstraction for consistency. |
Signature Detection Helperspackages/fedify/src/sig/ld.ts, proof.ts, mod.ts |
Adds hasSignatureLike() and hasProofLike() utility functions to detect JSON-LD signatures and proofs without full validation. Exports from sig module. |
ESLint Rule: Outbox Listener Deliverypackages/lint/src/rules/outbox-listener-delivery-required.tspackages/lint/src/index.tspackages/lint/src/mod.tspackages/lint/src/lib/const.ts |
Implements outbox-listener-delivery-required rule to enforce sendActivity() or forwardActivity() calls in outbox listeners. Uses regex pattern matching on listener source code to detect delivery method invocations. |
Test Infrastructure & Fixturespackages/testing/src/context.tspackages/testing/src/mock.tspackages/testing/src/mock.test.tspackages/testing/src/mod.tspackages/fedify/src/testing/context.tspackages/fedify/src/testing/mod.ts |
Adds createOutboxContext() helper for testing outbox listeners. Extends MockContext with postOutboxActivity(), setOutboxListeners(), and validateOutboxListenerPath() methods. Adds comprehensive outbox listener test coverage. |
Handler & Builder Testspackages/fedify/src/federation/handler.test.tspackages/fedify/src/federation/builder.test.tspackages/fedify/src/federation/middleware.test.tspackages/fedify/src/federation/inbox.test.tspackages/fedify/src/sig/ld.test.tspackages/fedify/src/sig/proof.test.ts |
Adds test coverage for handleOutbox(), outbox listener path validation, signature detection, and proof detection. Includes integration tests for outbox listener dispatch and error handling. |
Lint Rule Testspackages/lint/src/tests/outbox-listener-delivery-required.test.ts, integration.test.ts |
Comprehensive test suite for outbox-listener-delivery-required rule covering delivery method detection, edge cases, and integration scenarios. |
Documentationdocs/manual/outbox.mddocs/manual/lint.mddocs/manual/test.mddocs/manual/access-control.mddocs/manual/context.mddocs/manual/collections.mddocs/.vitepress/config.mtsCHANGES.md |
Adds outbox.md guide covering listener registration, context usage, and forwarding patterns. Documents outbox-listener-delivery-required lint rule. Updates access control, testing, and context documentation. Adds navigation link and changelog entries. |
Example Updatesexamples/rfc-9421-test/index.html |
Minor template literal formatting fix in example HTML. |
Sequence Diagram
sequenceDiagram
participant Client
participant Server as Federation Handler
participant Listener as Outbox Listener
participant Recipient
Client->>Server: POST /outbox (signed activity)
Server->>Server: Verify signature
Server->>Server: Parse activity
Server->>Listener: Dispatch with OutboxContext
Listener->>Server: forwardActivity(recipients)
Server->>Recipient: POST /inbox (activity)
Recipient-->>Server: 202 Accepted
Server-->>Client: 201 Created
Dig Deeper With Commands
/review <file-path> <function-optional>/chat <file-path> "<question>"/roast <file-path>
Runs only when explicitly triggered.
Mirror of upstream fedify-dev#688 for MergeMonkey vs competitor benchmark. Do not merge.
Summary by MergeMonkey