/clientapi/group had no way to see who is in a group or asks to join it, so a circle's owner could not answer a request from elsewhere. Now: - GET /clientapi/group/members: the members (local and remote, with their role) and the pending requests, for the group's owner and moderators only; - POST /clientapi/group/reject: declines a request, telling the asker's server with a Reject of its Follow; - POST /clientapi/group/remove: takes a member out (never the owner): a persona here stops following the group, one elsewhere gets a Reject of its Follow, as Mastodon removes a follower. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
327 lines
25 KiB
Markdown
327 lines
25 KiB
Markdown
# Federation
|
|
|
|
PrivaPub is an ActivityPub server written in C#. This document follows
|
|
[FEP-67ff](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md) and describes how it federates.
|
|
|
|
## Supported federation protocols and standards
|
|
|
|
- [ActivityPub](https://www.w3.org/TR/activitypub/) (server-to-server)
|
|
- [WebFinger](https://webfinger.net/)
|
|
- [HTTP Signatures](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures), `rsa-sha256` / `hs2019` with RSA keys
|
|
- [HTTP Message Signatures](https://www.rfc-editor.org/rfc/rfc9421) (RFC 9421) and Content-Digest (RFC 9530), verified on
|
|
deliveries and signed fetches: `rsa-v1_5-sha256` and `rsa-pss-sha512` with RSA keys
|
|
- [NodeInfo](https://nodeinfo.diaspora.software/) 2.0 and 2.1
|
|
|
|
## Tested against
|
|
|
|
End to end, in a private network on the workstation (`tools/pasture/`), each peer driven through its own client API
|
|
and every run starting clean, with signed fetches required (as privapub.thepra.dev runs):
|
|
- **GoToSocial 0.22.1**
|
|
- **Mastodon 4.7.3**
|
|
- **Misskey 2026.10**
|
|
- **Sharkey 2025.4.7**
|
|
- **Akkoma 3.20.1**
|
|
- **Lemmy 1.0.0-beta.2**, and **Lemmy 0.19.20** (built with the platform's roots, so it trusts the pasture's CA)
|
|
- **PeerTube 8.3.1**
|
|
- **Pixelfed 0.14.4**
|
|
- **WordPress 6 with ActivityPub 9.3.1**
|
|
- **Friendica 2026.05**
|
|
- **Mobilizon 5.2.4**
|
|
- **Gancio 1.28.2**
|
|
- **Funkwhale 2.0.11**
|
|
- **PieFed 1.7.17**
|
|
- **Mbin 1.10.1**
|
|
- **NodeBB 4.16.1**
|
|
- in the town only (a seeded community checked server by server): **Hollo 0.9.19**, **Iceshrimp.NET 2026.1.2-beta**,
|
|
**Pleroma 2.10.2**
|
|
|
|
Checked both ways, where the peer has the feature:
|
|
- follows, locked accounts included;
|
|
- every visibility, content warnings, replies and their notifications;
|
|
- likes, boosts and emoji reactions with their undos;
|
|
- direct messages;
|
|
- polls;
|
|
- quotes;
|
|
- images with alt text;
|
|
- edits with history and deletes;
|
|
- communities and circles;
|
|
- blocks and unfollows.
|
|
|
|
`docs/INTEROP.md` has each peer's evidence and what is still expected to fail. The platforms not yet in the pasture are
|
|
covered by unit tests written in their documents' shape.
|
|
|
|
## Supported FEPs
|
|
|
|
- [FEP-67ff: FEDERATION.md](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md)
|
|
- [FEP-f1d5: NodeInfo in Fediverse Software](https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md)
|
|
- [FEP-2c59: Discovery of a WebFinger address from an ActivityPub actor](https://codeberg.org/fediverse/fep/src/branch/main/fep/2c59/fep-2c59.md)
|
|
- [FEP-1b12: Group federation](https://codeberg.org/fediverse/fep/src/branch/main/fep/1b12/fep-1b12.md) (communities; see "Groups")
|
|
- [FEP-044f: Consent-respecting quote posts](https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md)
|
|
(`QuoteAuthorization` at `/parrot-licences/{id}`; checked with Mastodon both ways)
|
|
- [FEP-9967: Polls](https://codeberg.org/fediverse/fep/src/branch/main/fep/9967/fep-9967.md)
|
|
- [FEP-c0e0: Emoji reactions](https://codeberg.org/fediverse/fep/src/branch/main/fep/c0e0/fep-c0e0.md) (`EmojiReact`, and Misskey's
|
|
`Like` with content)
|
|
- [FEP-5feb: Search indexing consent](https://codeberg.org/fediverse/fep/src/branch/main/fep/5feb/fep-5feb.md) (`indexable`)
|
|
|
|
Planned (see `docs/ROADMAP.md`, phases P7 and P8, and the per-platform notes in `docs/INTEROP.md`): FEP-9098 (custom
|
|
emoji), FEP-7888 and FEP-f228 (threads), FEP-7628 (Move), FEP-8fcf (followers synchronisation), FEP-8967 (link
|
|
attachments), FEP-521a and FEP-8b32 (keys and integrity proofs), FEP-ae0c (relays).
|
|
|
|
## Actors
|
|
|
|
Every account is an *avatar*: one private login can own several, and they are deliberately unlinkable. Nothing in an
|
|
actor document, a collection, NodeInfo or a delivery relates two avatars of the same login.
|
|
|
|
| Thing | Address |
|
|
|---|---|
|
|
| Actor (Person, Group, Application) | `/peasants/{name}` (`/users/{name}` redirects) |
|
|
| Inbox | `/peasants/{name}/mouth` |
|
|
| Outbox | `/peasants/{name}/anus` |
|
|
| Shared inbox | `/human-centipede` |
|
|
| Followers / following | `/peasants/{name}/groupies`, `/peasants/{name}/stalking` |
|
|
| Objects | `/peasants/{name}/scribbles/{id}` |
|
|
| Activities | `/peasants/{name}/grunts/{id}` |
|
|
| Quote permissions (`QuoteAuthorization`) | `/peasants/{name}/parrot-licences/{id}` |
|
|
| Direct-message context | `/peasants/{name}/whispers/{id}` |
|
|
| Profile and post pages | `/@{name}`, `/@{name}/{id}` |
|
|
|
|
The names are the project's own and are stable; resolve actors through WebFinger, not by guessing a path.
|
|
|
|
- The key is `{actor}#main-key`, RSA 2048, served as SPKI PEM with `owner` set to the actor.
|
|
- `published` on an actor is a whole day, chosen at random up to two weeks before the account was made, so two
|
|
personas made on the same day do not share a date. `indexable` is `false` unless the persona turns it on, and
|
|
`discoverable` is `true` unless it turns that off (the deploy's own persona, @thepra, is undiscoverable).
|
|
- The instance actor is `/peasants/privapub` (type `Application`). It signs every fetch PrivaPub makes, so no avatar's key
|
|
is used to read another server's content. It also answers at the server's root (`/`) for a request that asks for
|
|
ActivityPub, as Lemmy's site actor does: PieFed sends a community's announces to the inbox of the Application at a
|
|
peer's root, and to `/inbox` when there is none.
|
|
|
|
## Groups
|
|
|
|
A group is either a **community** or a **circle**.
|
|
|
|
- A **community** follows [FEP-1b12](https://codeberg.org/fediverse/fep/src/branch/main/fep/1b12/fep-1b12.md). Posts
|
|
addressed to it (in `to`, `cc` or `audience`) are accepted according to its posting policy (followers, anyone, or
|
|
moderators only) and the group `Announce`s the whole activity, with `audience` set, to its followers. A new post is
|
|
also announced as an object so Mastodon shows it. Updates and deletes of community content are announced too. A
|
|
top-level post is a `Page` with a `name`. Members are counted at `/flock`; moderators are listed at `/wardens`, which
|
|
the actor's `attributedTo` points to, with `postingRestrictedToMods` as Lemmy expects. A mention of a community posts into it.
|
|
- A **circle** is private. Its actor is not discoverable and every follow is a request. Its posts are addressed to the
|
|
circle and its members collection, delivered to each member's own inbox and never announced. Each member's copy also
|
|
names that member in `cc`, so servers that keep only posts naming one of their accounts (Mastodon, GoToSocial) keep
|
|
it; it names no other member. The posts are served only to a signed request from a member, or from the instance actor
|
|
of a member's server, and that copy names the member (or the members on that server); anyone else gets 404. A
|
|
Mastodon member's replies reach only the people they mention. Its owner lets a request in with an `Accept` of the
|
|
Follow, and declines one or takes a member from elsewhere out with a `Reject` of it, as Mastodon removes a follower.
|
|
- Announces from **remote** groups (Lemmy communities) are followed through to the activity: the object is fetched
|
|
from its own origin, never taken from the announce. They are taken from a group someone here follows, and also when
|
|
they relay a vote on one of our posts in a thread of that group (Lemmy sends a vote to the community alone). A post
|
|
keeps the group its `audience` names, however it arrived.
|
|
- A post in a remote group followed here that the group's own server delivers itself, as its author's `Create` with the
|
|
group as its `audience` (Mbin sends a magazine's threads that way, with no announce), is kept as if announced: the
|
|
group's server speaks for posts in it. The same from another server is not.
|
|
- A remote community's **moderation** reaches the posts it holds. A removal (`Announce{Delete}`) is believed at once
|
|
from a community on the post's own server, which speaks for it (PieFed keeps serving a thread its moderator removed),
|
|
and from a community elsewhere once the post's origin answers it gone. A lock sent as it is (`Lock`, Mbin) is taken
|
|
from the post's own server only. A lock (`Announce{Lock}`, or `commentsEnabled: false` on the post) refuses replies,
|
|
ours included, until `Undo{Lock}`. A ban of a persona (`Announce{Block}` with the community as `target`) shows as
|
|
`blocked_by` on the community and refuses the persona's posts and replies there until the `Undo`.
|
|
|
|
## Activities
|
|
|
|
Received:
|
|
|
|
| Activity | Effect |
|
|
|---|---|
|
|
| `Follow` | follows an avatar or community; `Accept` is sent unless the community approves members by hand |
|
|
| `Accept{Follow}`, `Reject{Follow}` | completes or ends a follow an avatar requested |
|
|
| `Accept{Join}`, `Reject{Join}` | from the event's server: a persona's participation in the event is accepted or refused |
|
|
| `Undo{Follow, Like, Announce}` | reverses it |
|
|
| `Create{Note, Article, Page, Question, Video, Audio, Event, ChatMessage, …}` | stored when a local avatar follows the author, is addressed or mentioned, when it replies to a local post, or when it is addressed to a community the author follows; a public parent is fetched to complete the thread |
|
|
| `Update{Note}` | replaces the content; the previous version is kept |
|
|
| `Update{Person}` | refetches the actor |
|
|
| `Like` | counted and notified, on posts the liker could see |
|
|
| `Dislike` | counted as a downvote (Lemmy, PieFed, Mbin, Friendica); `Undo` takes it back |
|
|
| `EmojiReact`, `Like` with an emoji `content` | an emoji reaction (FEP-c0e0; Pleroma, Akkoma, Iceshrimp.NET, Misskey, Sharkey), Unicode or a custom emoji from its `tag`; a `Like` whose content is ❤ stays a favourite; `Undo` takes it back |
|
|
| `Join` | answered with `Ignore`: PrivaPub hosts no events of its own yet (FEP-8a8e) |
|
|
| `Announce` | counted and notified for local posts; shown to followers of the announcer, with the original refetched from its origin |
|
|
| `Delete` | deletes the object, or the actor and its follows; a deleted object id is remembered for 90 days, so a late `Create` cannot bring it back |
|
|
| `Flag` | becomes a report for this server's moderators |
|
|
| `Block` of a persona | the follows between them end, the blocker's posts and notifications are hidden from the persona, nothing of the persona's is addressed to the blocker, and the relationship says `blocked_by`; `Undo{Block}` lifts it |
|
|
|
|
Sent: `Follow`, `Undo{Follow}`, `Create{Note}`, `Create{Question}` and poll votes, `EmojiReact` and its `Undo`, `Update{Note}`, `Update{Person}`, `Delete{Tombstone}`, `Accept{Follow}`,
|
|
`Reject{Follow}`, `Like`, `Announce` and their `Undo`, `Flag`, `Join` and `Leave` of a remote event, and the replies to a
|
|
persona's posts passed on to its followers. A deleted post answers 410 with a `Tombstone`.
|
|
|
|
- **Attachments** are `Document`s with `mediaType`, `name` (alt text), `blurhash`, `focalPoint`, `width` and `height`.
|
|
Uploaded files have all metadata removed.
|
|
- **Pinned posts** are the actor's `featured` collection (`/trophies`); `featuredTags` is `/tattoos`.
|
|
- **Blocks are sent.** A blocked remote account receives `Block` from the blocking account (and `Reject{Follow}` if it
|
|
followed); an unblock sends `Undo{Block}`.
|
|
- **Reports** are sent as `Flag` by the instance actor, never by the reporting account.
|
|
- **Direct messages** go out as a `Note` addressed to their recipients, except to someone who writes to us as
|
|
`ChatMessage`s (Pleroma's type, which Lemmy 0.19 and Mbin take as their only private messages): a message to that one
|
|
account alone goes out as a `ChatMessage`, to it alone, without a mention in its text. The first message to an
|
|
account that never wrote to anyone here is still a `Note`, which Lemmy 0.19 and Mbin refuse (G-0008).
|
|
- **Replies are passed on** (inbox forwarding; owner decision 2026-10-05). A public or unlisted reply from another server
|
|
to a persona's public, unlisted or followers-only post goes on to the persona's followers as its author's server sent
|
|
and signed it, the way Mastodon forwards it: to every follower's server but the replier's own. Its `Update` and
|
|
`Delete` follow the same way. Nothing is passed on for a local-only post, a group's post or a reply that is not public
|
|
or unlisted. A server that receives it checks it as it checks any forwarded activity (Mastodon by the LD signature
|
|
or by reading the reply from its origin).
|
|
- **Events** (owner decision 2026-10-05). A persona joins another server's event with a `Join` and leaves it with a
|
|
`Leave`, both sent to the event's organiser only (`…/grunts/join-<id>`, `…/grunts/leave-<id>`). The organiser's
|
|
server answers `Accept` (Mobilizon at once for an open event) or `Reject`, which the persona sees as
|
|
`privapub.event.participation`: `pending`, `accepted` or `rejected`. An event that takes participants by invitation
|
|
or on another site (Mobilizon's `joinMode` `invite` or `external`) is refused before anything is sent (422). Who
|
|
takes part is public on the event's server.
|
|
|
|
A `Create`'s `Note` carries Mastodon's `content`, `contentMap`, `summary` and `sensitive`, plus `Mention` and `Hashtag`
|
|
tags.
|
|
|
|
**Polls** (FEP-9967) go out as a `Question` with `oneOf` or `anyOf`, each option's count in `replies.totalItems`,
|
|
`endTime`, `votersCount`, and `closed` once it has ended. Incoming votes are `Create{Note{name, inReplyTo}}` with no
|
|
content, one per choice, counted once per voter. Counts are refreshed with an `Update{Question}` at most every three
|
|
minutes. Our own votes on other servers' polls are sent the same way to the poll's author only, without `published`.
|
|
An incoming `Update` without a newer `updated` only refreshes counts and details; it is never recorded as an edit.
|
|
|
|
**Quotes** (FEP-044f, with the older keys):
|
|
- **Received.** A quote is read from `quote`, `quoteUrl`, `quoteUri`, `_misskey_quote` or a FEP-e232 `Link` tag.
|
|
- With a `quoteAuthorization`, the stamp is fetched and must be a `QuoteAuthorization` on the quoted author's origin,
|
|
attributed to them, naming exactly the two posts.
|
|
- A FEP-044f `quote` without a stamp is pending until an `Update` brings one.
|
|
- A quote made with the older keys only, by software that asks nobody, is shown when the quoted post is public.
|
|
- A `Delete` of a stamp revokes the quote.
|
|
- **Sent.** Our accounts quote with every key, plus the `RE:` fallback.
|
|
- When the quoted post states an `interactionPolicy.canQuote` that allows us, `quote` is included and a `QuoteRequest`
|
|
(with the quoting post as `instrument`) goes to the quoted author. The quote stays pending until their `Accept` brings a
|
|
stamp we can verify; the post is then updated with `quoteAuthorization`.
|
|
- Posts that state no policy are quoted the older way, without `quote`.
|
|
- Quoting posts are also delivered to the quoted author.
|
|
- **Quoting our accounts.** Our public and unlisted posts state `interactionPolicy.canQuote`. By default anyone may quote,
|
|
automatically (as on Mastodon); an account can choose followers only or nobody, and so can each post.
|
|
- A `QuoteRequest` is answered with `Accept{result}` naming a stamp at `/peasants/{name}/parrot-licences/{id}`, or
|
|
with `Reject`.
|
|
- A stamp is a `QuoteAuthorization` naming both posts. A revoked stamp answers 410 and is announced with
|
|
`Delete{stamp}`.
|
|
- Followers-only posts and DMs can never be quoted.
|
|
|
|
**Interaction policies** (GoToSocial's `canReply`, `canLike`, `canAnnounce`, with the older `always` and
|
|
`approvalRequired`):
|
|
- **Read** on every remote post; one left out means anyone, automatically. A persona is let in at once when the rule
|
|
names the public, the persona itself, the author's followers while it follows the author, or the accounts the author
|
|
follows while the author follows it.
|
|
- **Shut out:** the client API answers 422 to a reply, favourite or boost the rule does not allow at all.
|
|
- **Asked first:** a `ReplyRequest`, `LikeRequest` or `AnnounceRequest` (the interaction as its `instrument`) goes to the
|
|
author alone, and the interaction waits (`privapub.approval: pending`). The author's `Accept` brings an authorization
|
|
(`result`), which must be on the author's origin, attributed to them and naming the interaction and the post; only then
|
|
does the reply go out with `replyAuthorization`, the boost with `announceAuthorization` and the like with
|
|
`likeAuthorization`. A `Reject` leaves it ours alone (`rejected`), and takes a like or a boost back.
|
|
- **As a third party,** a reply that a post's policy does not let in at once is kept only with an authorization that
|
|
verifies the same way. A rule naming a collection we cannot list (followers, following) lets the reply in.
|
|
- **Shown** to clients as GoToSocial's `interaction_policy` (`can_favourite`, `can_reply`, `can_reblog`).
|
|
- Our own posts state only `canQuote`: anyone may reply to, like and boost them.
|
|
|
|
**Custom emoji** (`Emoji` tags) are read on posts, display names, bios and profile fields, at most 64 per object.
|
|
**Profiles** keep their header, profile fields, `manuallyApprovesFollowers`, `published`, `movedTo`, `indexable`,
|
|
`memorial` and avatar and header descriptions. A post's title becomes `name` and is also the first, bold line of `content`, because Mastodon does not show
|
|
`name`. A content warning without its own text uses the title, or "Content warning".
|
|
|
|
Received `summary` is a content warning only on a `Note` or `Question`, or when `sensitive` is `true`. On an `Article`,
|
|
`Page`, `Event`, `Video` or `Audio` it is an excerpt or description, kept as such and never hidden.
|
|
|
|
Visibility is expressed in `to`/`cc` the way Mastodon does it: public, unlisted, followers-only and direct. Inbound
|
|
followers-only posts are recognised by the author's own `followers` collection.
|
|
|
|
## Link previews
|
|
|
|
For a public post that links to a page, PrivaPub builds a preview card. It uses, in order:
|
|
1. a Link attachment's own `preview` (FEP-8967) or the post's own image, title and summary;
|
|
2. otherwise, the linked page, read once by the server between 0 and 60 seconds after the post arrives (never when
|
|
someone reads it), with an `Accept: text/html` request that reads at most 512 KB, through the same address checks as
|
|
every other fetch.
|
|
|
|
The page's OpenGraph and Twitter tags give the title, description and image; the result is cached per address for 7
|
|
days and shared by every account on the server. Images are served to clients only through PrivaPub's media proxy.
|
|
`Federation:FetchLinkPreviews=false` turns page fetching off.
|
|
|
|
## Server descriptions and the crawler
|
|
|
|
PrivaPub keeps statistics about servers, never about accounts (see `/stargazing` on the server).
|
|
- **Describing a server:** a server it exchanges activities with is described at most once a week, from:
|
|
- `/.well-known/nodeinfo` and the NodeInfo document it links;
|
|
- `/api/v2/instance`, falling back to `/api/v1/instance`.
|
|
|
|
These requests are unsigned, because they are not ActivityPub documents.
|
|
- **The crawler** is on at privapub.thepra.dev. It identifies as
|
|
`PrivaPub-Stargazer/<version> (+https://privapub.thepra.dev/stargazing)`.
|
|
- **What it reads:** `/robots.txt`, then the documents above and `/api/v1/instance/peers`, and nothing else: no
|
|
accounts, posts or directories.
|
|
- **How often:** one server a minute, each at most weekly.
|
|
- **Opting out:** in robots.txt, the crawler obeys the group `PrivaPub-Stargazer`, then `PrivaPub`, then `*`. A
|
|
robots.txt that answers with a server error or times out also keeps it out, as do this server's domain blocks.
|
|
|
|
## Local-only posts
|
|
|
|
Posts with a location (shown to nearby users of this server) never leave the server, in any form.
|
|
|
|
## Security rules a peer will notice
|
|
|
|
- **Signatures.** Inbox POSTs must be signed over `(request-target)`, `host`, `digest` and `date` (or `(created)`). The
|
|
date may be at most one hour old and fifteen minutes ahead. A bad signature gets 401, malformed input 400, an accepted
|
|
activity 202, too many requests 429. Activities are processed after the 202.
|
|
- **Origins.** An actor document is accepted only from the address it names as its `id`. A key only if its actor lists
|
|
it with `owner` set to the actor and on the actor's origin. An activity's `id`, and any object it creates, updates or
|
|
deletes, must be on its actor's origin. An embedded object from another origin is fetched from that origin.
|
|
- **Another account of the same server.** An object attributed to another account of the actor's own server (Mobilizon's
|
|
organiser creates, edits and deletes the group's event) is that server's to vouch for: a creation or an edit is
|
|
taken as the server has the object, a deletion once it answers 404 or 410. Attributed to an account elsewhere, it is
|
|
refused (400).
|
|
- **Forwarded activities** (ActivityPub's inbox forwarding: a thread's server passing on the replies in it, signed with
|
|
its own key) are answered 202 and believed only as far as their origin vouches for them: a created or edited post
|
|
is read again from its author's server, a deletion of a public or unlisted post is applied once that server answers
|
|
404 or 410, and anything else is dropped. LD signatures are not verified. A reply forwarded this way is kept when
|
|
someone here follows the author of the post it answers, as Mastodon does.
|
|
- **Follow requests** still unanswered are sent again after 15 minutes, an hour, 6 hours, a day, two and four days: a
|
|
server can take a Follow and lose its answer (Lemmy 1.0 sends nothing it queued for a server before it started
|
|
sending there), and one that holds the follow answers it again. Each is the same follow under a new id
|
|
(`…/follow-<id>-again-<n>`), since Lemmy ignores an id it has seen; an answer naming any of them counts.
|
|
- **Fetching.** All fetches are signed by the instance actor. They go only to public addresses, follow at most three
|
|
redirects and read at most 1 MB.
|
|
- **Threads.** A reply's missing parents are fetched, up to 10 levels. When someone here opens a public remote thread,
|
|
its replies are read from their servers, at most once an hour: the thread's `context` collection (FEP-7888) if it has
|
|
one, else its `replies` (PeerTube's `comments`) and theirs, two levels down; 5 pages and 100 posts at most. Only
|
|
public and unlisted replies are kept, each fetched from its own origin.
|
|
- **Reading our documents (SecureMode).** privapub.thepra.dev answers ActivityPub GETs only when they are signed, like
|
|
Mastodon's authorized fetch; the instance actor `/peasants/privapub` is the exception, since its key is needed first.
|
|
A browser asking for HTML is redirected to the public page instead.
|
|
- **Posts that are not public** (followers-only, direct, circle) are served to a signed request from someone they were
|
|
for, or from the instance actor of a server where someone they were for lives, and to nobody else (404). Once deleted
|
|
they answer those same readers 410. A direct message's `context`, `/peasants/{name}/whispers/{id}`, is an
|
|
`OrderedCollection` of the conversation's posts for its participants.
|
|
- **HTML.** Received HTML is sanitised to Mastodon's allowlist.
|
|
- **Keys we cannot fetch for now.** When a sender's key cannot be fetched because its server timed out or answered
|
|
5xx, the inbox answers 503 with `Retry-After: 300` rather than 401.
|
|
- **Activity ids.** `Create` and `Announce` ids dereference, a community's announces included, until the post they
|
|
carry is deleted (then 410); a persona's boost id sends a browser to the boosted post. `Follow`, `Like`, `Block` and
|
|
the `Accept`, `Reject` and `Undo` that answer them do not, because serving them would reveal who follows, likes and
|
|
blocks whom. Neither do `Update`, `Delete`, `EmojiReact`, `QuoteRequest` and its answers, `Flag`, `Ignore` or poll
|
|
votes. All of them are always sent with their object embedded. Every boost and every favourite is an activity of its
|
|
own (`announce-{boost}`, `like-{favourite}`), so one given again after its `Undo` is new to the server it reaches.
|
|
- **Hashtags.** A post's `Hashtag` links go to `/tags/{tag}`, a public page of this server's public posts with that tag.
|
|
- **Delivery.** Failed deliveries are retried with Mastodon's backoff (16 attempts). A host that keeps failing is paused,
|
|
starting at an hour and growing to a week. A server can also take a Follow (202) and drop it afterwards, as Pleroma
|
|
does when it cannot yet fetch our actor, so a follow request still unanswered is sent again, same activity, when the
|
|
persona follows once more, at most once an hour.
|
|
|
|
## Known limitations
|
|
|
|
- Our own posts carry no custom emoji: emoji are received and shown, not offered.
|
|
- Remote media is fetched through this server's proxy when a local client displays it. Video and audio are streamed
|
|
through it with byte ranges passed on, so a PeerTube file is never fetched whole for one viewer.
|
|
- Collections expose counts, not members: `/groupies` and `/stalking` give the same totals as the Mastodon API's
|
|
follower and following counts, and the outbox's `totalItems` is the persona's post count, though only public posts
|
|
are listed. `/api/v1/instance/peers` is empty: which servers this one talks to is not published.
|
|
- Only RSA keys are verified, under draft-cavage or RFC 9421; Ed25519 (FEP-521a) is planned. What PrivaPub sends is
|
|
signed with draft-cavage only, which every server reads.
|