Skip to content

Latest commit

 

History

History
265 lines (202 loc) · 13.4 KB

File metadata and controls

265 lines (202 loc) · 13.4 KB

ActivityPub interoperability

Delivery retries and slow receivers

Activity-Relay v2.5.0 retries each relay fan-out target five times after the initial attempt and retains the shared activity body for the full retry horizon. Receiver-side ActivityPub implementations may synchronously fetch actors, keys, objects, parents, contexts, or collections before returning from /inbox; transient timeouts therefore no longer become immediate terminal loss at the relay. Retries remain at-least-once and a receiver must tolerate duplicate delivery when it commits an activity after the relay client has timed out.

Structured worker diagnostics record public identifiers, hashed body identity, attempt and elapsed-time data, receiver and origin domains, HTTP status, error classification, and bounded error responses. They intentionally exclude raw activity bodies, signatures, and key material.

This document records interoperability behavior validated for stable Activity-Relay releases through v2.5.1 and retained acceptance evidence for the 3.0 RC1/RC2 cycle. Candidate behavior is not promoted to stable merely because it is implemented or passes CI. Historical v2.4.0 and v2.5.0 results are retained for comparison; no matrix can guarantee that every version or configuration of every ActivityPub server behaves identically.

FEP-ae0c compatibility

Activity-Relay implements both relay families retrospectively described by FEP-ae0c, plus documented extensions for open publishers, broad server-actor compatibility, NodeBB embedded-Announce normalization, and authorized fetch.

The exact comparison, historical to versus cc visibility question, document-proof distinction, loop-prevention audit, and machine-readable characterization cases are maintained in FEP-AE0C-COMPATIBILITY.md. Activity-Relay 3.0 resolves the public-address question through explicit PUBLIC_ADDRESS_DISTRIBUTION_POLICY configuration while retaining the old behavior when that setting is omitted.

Public-address distribution policy

Activity-Relay 3.0 distinguishes an explicitly public primary audience from an activity that places the ActivityStreams Public collection only in cc.

PUBLIC_ADDRESS_DISTRIBUTION_POLICY accepts:

  • explicit_public_only: public fan-out requires Public in to; Public only in cc is acknowledged and publisher-accounted without relay fan-out.
  • public_and_unlisted: Public in either to or cc enters public fan-out, preserving the pre-3.0 behavior.

The fresh configuration example selects explicit_public_only. Omitted configuration falls back to public_and_unlisted for compatibility, and an upgrade retaining the old behavior should set that value explicitly. The broader value does not make followers-only or direct activities eligible. Explicitly relay-addressed LitePub traffic is evaluated through its own relationship path before the cc-only exclusion.

Subscription models

Activity-Relay supports both common relay subscription models:

  • Traditional relay subscribers register an inbox with the relay endpoint at /inbox.
  • Follower-style servers follow the relay actor at /actor and receive an Accept plus a reciprocal relay Follow.

Activity-Relay publishes its own relay actor as Application, with preferredUsername relay at /actor. This matches current Friendica relay discovery while preserving the established actor ID, collections, endpoints, and #main-key identity.

The relay actor may be followed by standards-style remote Application or Service actors at implementation-defined actor paths. Legacy /relay and /friendica server actors remain supported.

v2.4.0 validation matrix

The following paths were validated with real signed activities before the stable v2.4.0 release:

Publisher path Relay behavior Receiving software Result
Friendica public Create Validate, record, and fan out NodeBB Received and displayed
NodeBB category public embedded Announce Replace with one relay-signed Announce referencing the canonical object Friendica Fetched and displayed
NodeBB category public embedded Announce Replace with one relay-signed Announce referencing the canonical object Mastodon Fetched, imported, tagged, and displayed
NodeBB category public embedded Announce Replace with one relay-signed Announce referencing the canonical object Another NodeBB server Canonical object fetched

The NodeBB-to-Mastodon test was performed without relying on an indirect Friendica follow path.

v2.5.0 stable validation

The v2.5.0 line was exercised through an isolated relay after the Machinery v2 reliable-claims migration and again after the Friendica actor-profile correction:

Publisher or fetch path Receiving software or endpoint Result
NodeBB category public embedded Announce Mastodon Accepted, fetched, imported, and displayed
Mastodon public Create with ordinary fetch policy NodeBB Delivered and displayed
Mastodon public Create with secure mode enabled Friendica 2026.05 through Activity-Relay Relay registration, delivery, canonical-object and media retrieval, hashtags, and presentation passed
Unsigned canonical-object GET Mastodon secure-mode endpoint Rejected with HTTP 401 Request not signed
Relay-signed canonical-object GET using the deployed actor identity Same Mastodon secure-mode endpoint Returned HTTP 200 ActivityPub JSON for the intended object

The ordinary bidirectional NodeBB/Mastodon test ruled out another configured relay as the delivery path. The secure-mode control used the same relay actor key and HTTP-signature implementation as the deployed candidate. Friendica was freshly registered only after it fetched the corrected Application actor profile.

The production RC2 soak confirmed that new posts continued reaching a secure-mode Mastodon receiver. Relay-signed inbox deliveries were accepted, actor and key identity remained continuous, and no relay-specific signature, private-address, Redis, queue, worker, or resource regression was observed. Receiver-side 502 responses during the window were traced to a separate PHP-FPM incident and are not classified as an Activity-Relay failure.

NodeBB secure-mode canonical-object retest

NodeBB 4.14.x, including 4.14.5 testing, accepted the relay-signed delivery but returned HTTP 424 when the referenced Mastodon object required authorized fetch. Receiving-side evidence showed NodeBB's application-context canonical-object request was unsigned and the secure-mode server returned HTTP 401. The same object was returned when fetched with the Activity-Relay signature implementation.

NodeBB upstream addressed that application-actor path in commit 8e61543b0ae19fd741bd4175d478aab6c79982ca, released in the 4.15 line.

A fresh 4.15.1 retest passed during Activity-Relay 3.0 RC1 acceptance. The test used the stock upstream NodeBB 4.15.1 container and the canonical RC1 relay image with OUTBOUND_SIGNATURE_PROFILE: dual. A new public Mastodon post was delivered through the relay, NodeBB completed the canonical-object retrieval and displayed the post, the relay's NodeBB receiving-success counter advanced without a new failure, and neither side logged a corresponding 401, 403, or 424 error. The upstream application-actor signing fix is therefore treated as verified for this path.

The separate NodeBB category-actor RFC 9421 GET compatibility behavior below remains independently testable and is not implied by this result.

3.0 RC2 mixed-software and RFC 9421 acceptance

The RC2 acceptance topology exercised two independent relays without directly peering them. Friendica subscribed to both relays, while Mastodon and NodeBB used the test relay and WordPress used the production relay. A second WordPress author that was not configured as a Friendica "also me" identity provided an isolation control: the post reached Friendica through the production relay but did not appear on the test relay, Mastodon, or NodeBB. This is retained as a cross-relay isolation pass rather than evidence of relay-to-relay propagation.

The test relay was then temporarily forced to OUTBOUND_SIGNATURE_PROFILE: rfc9421 for a Mastodon canary. Both API and worker processes confirmed the forced profile. Friendica accepted the relay delivery with HTTP 202. Stock NodeBB 4.15.1 returned HTTP 400 for the same relay-generated Announce on all six bounded attempts, each recorded as signature_profile=rfc9421; the retry job then exhausted normally. This receiver-specific result is tracked upstream as NodeBB #14732 and is not treated as an Activity-Relay release blocker without evidence that the relay's RFC 9421 wire format is invalid. The test relay was returned to dual afterward.

Public embedded Announce normalization

Some servers, including NodeBB category actors, publish locally created content as a public Announce with an embedded Article or Note. For a same-domain embedded object, Activity-Relay creates one relay-authored Announce whose object is the embedded object's canonical ID and sends that same authenticated wrapper to every receiver style.

This keeps the HTTP signer and JSON activity actor aligned for strict receivers while preserving the original author, content, media, and tags when the receiver fetches the canonical object.

The relay deliberately does not fan out:

  • public Announce activities whose object is only a URL; or
  • public embedded Announce activities whose object belongs to another domain.

Those activities remain publisher-accounting events. This avoids relay loops and unintended amplification of ordinary boosts.

NodeBB category actor fetch negotiation

NodeBB 4.14.5 may return a generic HTTP 400 when an unknown remote client uses an RFC 9421 signed GET for a category actor, while returning the same valid ActivityPub Group actor for an unsigned or legacy-signed GET. In dual mode, Activity-Relay treats that exact 400 as a bounded compatibility signal for an unknown idempotent fetch and retries once with the legacy profile. It records a short-lived legacy preference only when the retry succeeds.

This compatibility path does not parse response text, does not apply to other status codes or transport failures, and never retries a delivery POST under a different signature profile.

Fragment-bearing actor key IDs

ActivityPub public-key identifiers commonly append a fragment such as #main-key to the actor URL. URI fragments are not transmitted as part of an HTTP request target. Before deriving an RFC 9421 @target-uri, Activity-Relay therefore removes Fragment and RawFragment from the request URL while preserving the original key ID used to locate and bind the published key.

This distinction is required for secure-mode Mastodon interoperability: signing the fragment-bearing identifier while sending the fragment-free actor request causes the receiver to reconstruct a different signature base and reject the request. The established legacy (request-target) profile was not affected because its request-target component already excludes the fragment.

Signatures and actor keys

Outbound HTTP signatures bind the signed Host value to the exact authority sent on the wire, including a non-default port.

The relay actor publishes publicKeyPem as X.509 SubjectPublicKeyInfo PEM:

-----BEGIN PUBLIC KEY-----

Inbound verification accepts both SubjectPublicKeyInfo and legacy PKCS#1 RSA public keys. Changing the public serialization does not rotate actor.pem, the relay actor ID, or the #main-key key ID.

Canonical objects, tags, and discovery

The relay-generated wrapper references the publisher's canonical object. The receiving server fetches that object and is responsible for importing its content, author, attachments, and ActivityStreams Hashtag entries.

A successful import does not guarantee that a post appears in every discovery surface. Local hashtag review, trend approval, moderation, language, and timeline settings may affect visibility independently of relay delivery.

Troubleshooting

Start with the public relay request status:

  • 202 Accepted means the relay accepted the signed activity for processing.
  • 400 Bad Request commonly indicates actor resolution, signature or digest verification, or JSON decoding failed. Version 2.5.0 logs the bounded failure reason with request metadata, but never logs request bodies, signatures, or key material.

For NodeBB specifically, verify that:

  1. the relay relationship is active;
  2. the topic belongs to a publicly readable positive-numbered category;
  3. the category actor URL returns a valid ActivityPub actor and public key;
  4. the canonical post URL returns the expected Article or Note; and
  5. retry records identify the relay inbox as their destination.

During pre-release validation, NodeBB 4.14.2 exposed two useful failure signatures:

  • Uncategorized topics could serialize a numeric audience such as "to": [-1], which is not a valid string-valued ActivityPub audience.
  • A category actor endpoint could fail if its configured icon file was absent, preventing the relay from resolving the signing key.

Check the current NodeBB release before assuming those version-specific issues remain unresolved.

When a receiver appears not to show a post, distinguish transport from local presentation: confirm the canonical object was fetched, then inspect the receiver's stored status and tags before concluding relay delivery failed.