fix: send relay Follow object as the full Public IRI - #140
Merged
Conversation
relay.toot.io and relay.intahnet.co.uk both run YUKIMOCHI Activity-Relay, both auto-accept, and both Rejected us within 3.5 minutes of a fresh Follow. Its executeFollowing validates with a literal string comparison: case contains(activity.Object, "https://www.w3.org/ns/activitystreams#Public") We serialize PUBLIC_COLLECTION as the compacted CURIE "as:Public", so the comparison fails and it falls through to "only ...#Public is allowed to follow" and Rejects. Semantically as:Public is correct — the relay is string-matching a compacted term — but we're the ones being refused. Fedify applies this same rewrite to to/cc/bto/bcc/audience as of 2.2.0 (fedify-dev/fedify#710) but not to object. RelayFollow overrides toJsonLd so the rewrite lands before signing and the signed bytes match the wire bytes. Used on both the sent activity and the Follow object dispatcher, since the relay re-fetches the Follow URL to verify it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes relay subscription failures against YUKIMOCHI Activity-Relay instances by ensuring outgoing Follow activities serialize object as the full ActivityStreams Public IRI (https://www.w3.org/ns/activitystreams#Public) rather than the compacted JSON-LD CURIE (as:Public), which some relays reject via literal string comparison.
Changes:
- Introduces
RelayFollow, aFollowsubclass that rewritesobjectduringtoJsonLd()serialization to emit the full Public IRI. - Switches relay-follow creation paths to use
RelayFollow(both the sent activity and the Follow object dispatcher used for relay verification re-fetch). - Adds tests covering serialization behavior,
clone()preserving the subclass, and non-Public objects remaining untouched.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/lib/subscriptions.ts | Adds RelayFollow and the AS_PUBLIC constant to enforce full-IRI serialization for relay follows. |
| src/lib/federation.ts | Uses RelayFollow when generating relay subscription Follow activities and when serving Follow objects for relay verification. |
| src/lib/tests/subscriptions.test.ts | Adds unit tests validating RelayFollow serialization, clone behavior, and pinning upstream Follow compaction behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
relay.toot.ioandrelay.intahnet.co.ukweren't dead and weren't refusing us on policy — we were sending a Follow they can't parse.Cause
Both run YUKIMOCHI Activity-Relay, both auto-accept (toot.io lists 1,324 connected instances), and both Rejected within 3.5 minutes of a fresh, correctly-formatted Follow after #139 deployed. Its
executeFollowingvalidates with a literal string comparison:What we put on the wire:
"as:Public" != "https://www.w3.org/ns/activitystreams#Public"→ falls todefault→ Reject. Semantically we're right — the CURIE expands to that IRI — but the relay string-matches, so we lose.Fedify hit this class of bug against Lemmy and shipped the same rewrite in 2.2.0 (#710), but only for
to/cc/bto/bcc/audience, notobject. We're on 2.3.4, so there's nothing to upgrade to.Fix
RelayFollowoverridestoJsonLdto emit the full IRI. Doing it insidetoJsonLdrather than post-serialization keeps the Object Integrity Proof over the same bytes that go on the wire — the same ordering constraint #710 calls out.Applied in both places a relay Follow is produced: the sent activity, and the Follow object dispatcher (the relay re-fetches that URL to verify the subscription, and applies the same check).
Testing
clone()preserves the subclass (Fedify clones internally), non-Public objects untouched, and one asserting plainFollowstill compacts toas:Publicso the upstream behaviour this works around is pinned.Real verification is the relays themselves: after deploy, reset the two rows to pending and watch for Accept.