Skip to content

Follow delivery functions assigned by a called local setup helper #1126

Description

@dahlia

Why

A local helper can replace an object's delivery function, but the outbox delivery rules still inspect the original function when the listener calls it:

federation
  .setOutboxListeners("/users/{identifier}/outbox")
  .on(Activity, async (ctx, activity) => {
    const target = { deliver: async () => {} };
    const setup = () => {
      target.deliver = async () => {
        await ctx.sendActivity(
          { identifier: ctx.identifier }, "followers", activity,
        );
      };
    };
    setup();
    await target.deliver();
  });

setup() runs synchronously and replaces the empty function before target.deliver() is called. The listener delivers, but outbox-listener-delivery-required reports it as undelivered. Removing await from ctx.sendActivity() also makes outbox-listener-delivery-not-awaited miss the dropped promise.

Both failures were reproduced on Linux with Deno 2.9.5 (Deno lint) and Node.js 22.23.2 (ESLint), using the @fedify/lint 2.4.0 development checkout at 4abb5ee4 on the #1088 branch. The assignment behavior predates that PR.

In packages/lint/src/lib/reachability.ts, walkUsedScopes() creates a function map for each scope. The assignment updates setup's function map, but walkUsedScopes() does not carry that update back to the caller. The later call still resolves to the initial empty function. This differs from #1054: the helper is inside the listener, rather than at module scope.

Scope

Handle a directly called local setup helper that writes a delivery function to an enclosing object before the listener calls that function. Both outbox delivery rules should scan the installed function. Preserve the order of the setup and delivery calls; an uncalled setup helper, or one called only after the empty function is used, should not make the listener count as delivering.

A local documentation follow-up to #1088 (960f8552) records this limitation under outbox-listener-delivery-required and outbox-listener-delivery-not-awaited in docs/manual/lint.md. After implementing the fix, update both sections to remove or narrow the limitation and adjust the workaround to match the supported behavior.

Preserving functions already on the object is tracked separately in #1125. No module-scope helpers, cross-file analysis, or type information. Start with the straight-line synchronous setup shown above; asynchronous setup and writes whose execution cannot be established are outside this issue's initial scope.

Suggested checks

Test the example in both Deno lint and ESLint, with an awaited delivery and a dropped delivery promise. Add cases where setup is never called, is called only after target.deliver(), or writes to a local object that shadows the listener's target. Those cases should still report missing delivery, and should not report a dropped promise in an uninvoked delivery function.

Activity

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

Metadata

Metadata

Assignees

Type

Fields

Priority

None yet

Effort

None yet

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions