Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,21 @@ To be released.
when an actor dispatcher's return value does not include a
`preferredUsername` property. [[#895], [#1022] by Jae-Hyuk-Jang\]

- Changed `outbox-listener-delivery-required` (`@fedify/lint`) to decide
whether a `ctx.sendActivity()`/`ctx.forwardActivity()` call actually
runs, instead of scanning the listener's source as a flat block of text.
It now reports a listener whose only delivery calls sit behind a dead
branch, after an unconditional `return`/`throw`, or inside a function
that is never used. When it cannot tell whether a delivery call runs, it
stays quiet: a function held under a name counts as used as soon as that
name is mentioned, however it is passed around, and an inline callback
counts wherever it is passed.
[[#900], [#1050] by Jae-Hyuk-Jang\]

[#895]: https://github.com/fedify-dev/fedify/issues/895
[#900]: https://github.com/fedify-dev/fedify/issues/900
[#1022]: https://github.com/fedify-dev/fedify/pull/1022
[#1050]: https://github.com/fedify-dev/fedify/pull/1050

### @fedify/mysql

Expand Down
15 changes: 15 additions & 0 deletions changes.d/lint/outbox-listener-path-aware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
links:
'#1050': https://github.com/fedify-dev/fedify/pull/1050
'#900': https://github.com/fedify-dev/fedify/issues/900
---
- Changed `outbox-listener-delivery-required` (`@fedify/lint`) to decide
whether a `ctx.sendActivity()`/`ctx.forwardActivity()` call actually
runs, instead of scanning the listener's source as a flat block of text.
It now reports a listener whose only delivery calls sit behind a dead
branch, after an unconditional `return`/`throw`, or inside a function
that is never used. When it cannot tell whether a delivery call runs, it
stays quiet: a function held under a name counts as used as soon as that
name is mentioned, however it is passed around, and an inline callback
counts wherever it is passed.
[[#900], [#1050] by Jae-Hyuk-Jang]
72 changes: 69 additions & 3 deletions docs/manual/lint.md
Original file line number Diff line number Diff line change
Expand Up @@ -752,13 +752,38 @@ Warns when an outbox listener body does not deliver the posted activity with
`ctx.sendActivity()` or `ctx.forwardActivity()`.

**When this rule applies:**
You've registered an outbox listener with `setOutboxListeners()`, but the
listener body never calls either delivery method.
You've registered an outbox listener with `setOutboxListeners()`, and the rule
can show that no path through the listener body calls either delivery method.
It follows the listener's own control flow (`if`/`else`, `try`/`catch`,
`switch`, loops), so a delivery call that sits in a dead branch, after an
unconditional `return`, or in a function that is never used does not count.

The rule reports only when it can account for every delivery call it can see
and show that each one does not run. When it cannot tell, it stays quiet: a
missed warning is the safe direction, while a warning on code that delivers is
not. In practice:

- A function held under a name counts as used as soon as that name is
mentioned anywhere in code that runs, however it is mentioned: called,
passed to another function, aliased, destructured from an object, or
reached through an array or a wrapper call. The rule does not follow the
value any further, so a function that is only logged or stored, and never
called, is not reported.
- An inline callback counts wherever it is passed, since the rule cannot show
that the receiving call never runs it.
- The rule reads only the listener body. A delivery call in a helper that
is declared outside the listener, or in another module, is not seen.

**Why it matters:**
Fedify does not federate client-to-server outbox posts automatically. If your
application intends to deliver a posted activity, the listener must choose an
explicit delivery path.
explicit delivery path, and that path must actually run.

The rule checks that a delivery call exists and can run, not that the delivery
completes, so a listener it accepts is not guaranteed to federate. A delivery
call that is never awaited is not reported;
[#1057] tracks a rule for
that.

~~~~ typescript twoslash
// @noErrors: 2345
Expand All @@ -773,6 +798,19 @@ federation
console.log(ctx.identifier, activity.id?.href);
});

// ❌ Bad: The delivery call is unreachable dead code
federation
.setOutboxListeners("/users/{identifier}/outbox")
.on(Activity, async (ctx, activity) => {
if (activity.id == null) return;
return;
await ctx.sendActivity(
{ identifier: ctx.identifier },
"followers",
activity,
);
});

// ✅ Good: Listener federates explicitly
federation
.setOutboxListeners("/users/{identifier}/outbox")
Expand All @@ -793,8 +831,36 @@ federation
"followers",
);
});

// ✅ Good: Delivery happens inside a helper that is actually called
federation
.setOutboxListeners("/users/{identifier}/outbox")
.on(Activity, async (ctx, activity) => {
async function deliver() {
await ctx.sendActivity(
{ identifier: ctx.identifier },
"followers",
activity,
);
}
await deliver();
});

// ✅ Good: A helper reached through a destructured property still counts
federation
.setOutboxListeners("/users/{identifier}/outbox")
.on(Activity, async (ctx, activity) => {
const handlers = {
deliver: () =>
ctx.sendActivity({ identifier: ctx.identifier }, "followers", activity),
};
const { deliver } = handlers;
await deliver();
});
~~~~

[#1057]: https://github.com/fedify-dev/fedify/issues/1057

### `media-uploader-object-uri-required`

Warns when a `setMediaUploader()` callback returns a value that is not derived
Expand Down
Loading
Loading