Support Person+Group actor #21

Open
opened 2026-03-10 16:41:02 +01:00 by blacklight · 0 comments
Owner

Group Actor for Threadiverse Compatibility

Status

Proposed

Context

Madblog currently publishes posts via a single Person actor. Threadiverse
platforms (Lemmy, kbin, Piefed) model communities as Group actors that
Announce (boost) content created by Person actors. When a remote user
follows a Lemmy community, they see Announce{Create{Article}} activities
from the Group in their timeline — this is the convention that all threadiverse
implementations rely on for content discovery.

Because Madblog publishes Create{Article} directly from a Person, threadiverse
instances cannot natively follow and display the blog as a "community". Adding
Group actor support would make Madblog blogs interoperable with the threadiverse
ecosystem alongside the existing Mastodon-compatible flow.

Reference: https://activitypub.space/post/1503

Design

Actor model

Introduce an optional Group mode where the blog exposes two actors:

Actor Type Purpose
Blog actor Group The primary discoverable actor; followers subscribe to this
Author actor Person Attributed author of articles; referenced in attributedTo
  • WebFinger resolves the blog username to the Group actor (this is
    what remote users follow).
  • The Person actor is a minimal, read-only profile (no inbox processing
    required — it exists so remote servers can dereference attributedTo).

Publishing flow

When Group mode is enabled, the publishing pipeline changes:

Current (Person mode):
  Person → Create{Article} → deliver to followers

Proposed (Group mode):
  1. Build Article with attributedTo = Person actor
  2. Group actor sends Announce{Create{Article}} to followers

The inner Create{Article} is wrapped in an Announce from the Group, which
is the pattern threadiverse implementations expect.

Endpoints

New endpoints required when Group mode is active:

Endpoint Description
GET /ap/author Person actor document for the blog author

The existing /ap/actor endpoint changes its type from Person to Group.
All other endpoints (inbox, outbox, followers, following, webfinger) continue
to operate on the primary (Group) actor.

Configuration

New Madblog config options:

# Actor type: "Person" (default, current behavior) or "Group" (threadiverse)
activitypub_actor_type: Group

# Author details for the Person actor (when actor_type is Group)
activitypub_author_username: fabio          # default: "author"
activitypub_author_name: Fabio Manganiello  # default: config.author
activitypub_author_icon_url: https://...    # default: config.author_photo

When activitypub_actor_type is Person (or unset), behavior is unchanged.

Migration path

Switching an existing blog from Person to Group is a breaking change for
existing followers — remote instances cache the actor type. Options:

  1. Recommended: Deploy as Group from the start for new blogs.
  2. Existing blogs: Publish an Update{Actor} changing the type to Group.
    Mastodon handles this gracefully (re-fetches the actor), but some
    implementations may not. A note in the changelog advising re-follow may
    be necessary.

Changes Required

Pubby

  1. Announce activity builder — Add OutboxProcessor.build_announce_activity()
    that wraps a Create activity in an Announce from the configured actor.

  2. publish_object support for Announce — Extend
    ActivityPubHandler.publish_object() to accept activity_type="Announce"
    (or a new publish_announce() method).

  3. Secondary actor endpoint — The Flask/FastAPI/Tornado adapters need a way
    to register a second read-only actor endpoint (e.g. /ap/author) that
    serves a minimal Person document. This could be a new
    bind_secondary_actor() helper, or a list of actor configs in
    bind_activitypub().

  4. Actor document generationget_actor_document() already uses
    self.actor_type from config, so setting type: Group in actor_config
    is sufficient for the primary actor. The secondary Person actor needs its
    own document builder (minimal: id, type, preferredUsername, name,
    icon, url — no inbox/outbox/keys required since it doesn't process
    activities).

Madblog

  1. Config — Add activitypub_actor_type and author-related fields to
    Config and init_config().

  2. Actor setup (_mixin.py) — When activitypub_actor_type == "Group":

    • Pass type: "Group" in the primary actor_config.
    • Create and register a secondary Person actor endpoint for the author.
  3. Publishing flow (_integration.py) — When Group mode is active:

    • Set attributedTo on the Article to the Person actor ID.
    • After building the Create{Article}, wrap it in an Announce from the
      Group and deliver that instead.
  4. Content negotiation (_mixin.py) — The AP JSON response for
    /article/... should include attributedTo pointing to the Person actor
    when in Group mode.

  5. Tests — New test class covering Group mode: actor document type,
    announce wrapping, author endpoint, webfinger resolution.

  6. Documentation — Update README with the new config options and a note
    about threadiverse compatibility.

Open Questions

  1. Should the Person actor have its own inbox? Threadiverse implementations
    may attempt to deliver replies to the attributedTo actor. If the Person
    has no inbox, those deliveries will fail silently. A lightweight option:
    share the Group's inbox and route incoming activities appropriately.

  2. Outbox representation — Should the Group's outbox contain the Announce
    activities, the inner Create activities, or both? Lemmy puts Announces in
    the community outbox.

  3. Backwards compatibility — Should Madblog support a "hybrid" mode that
    sends both Create (for Mastodon) and Announce (for threadiverse)? This
    would cause duplicate posts on Mastodon. The Announce-only approach is
    correct — Mastodon already handles Announce{Create{...}} from Group
    actors properly.

  4. Separate keypair for the Person actor? If the Person actor eventually
    needs to sign requests (e.g. for inbox delivery), it would need its own
    keypair. For the initial read-only implementation, the Group's key suffices.

References

# Group Actor for Threadiverse Compatibility ## Status Proposed ## Context Madblog currently publishes posts via a single **Person** actor. Threadiverse platforms (Lemmy, kbin, Piefed) model communities as **Group** actors that **Announce** (boost) content created by Person actors. When a remote user follows a Lemmy community, they see `Announce{Create{Article}}` activities from the Group in their timeline — this is the convention that all threadiverse implementations rely on for content discovery. Because Madblog publishes `Create{Article}` directly from a Person, threadiverse instances cannot natively follow and display the blog as a "community". Adding Group actor support would make Madblog blogs interoperable with the threadiverse ecosystem alongside the existing Mastodon-compatible flow. Reference: <https://activitypub.space/post/1503> ## Design ### Actor model Introduce an optional **Group mode** where the blog exposes two actors: | Actor | Type | Purpose | |---|---|---| | Blog actor | `Group` | The primary discoverable actor; followers subscribe to this | | Author actor | `Person` | Attributed author of articles; referenced in `attributedTo` | - **WebFinger** resolves the blog username to the **Group** actor (this is what remote users follow). - The **Person** actor is a minimal, read-only profile (no inbox processing required — it exists so remote servers can dereference `attributedTo`). ### Publishing flow When Group mode is enabled, the publishing pipeline changes: ``` Current (Person mode): Person → Create{Article} → deliver to followers Proposed (Group mode): 1. Build Article with attributedTo = Person actor 2. Group actor sends Announce{Create{Article}} to followers ``` The inner `Create{Article}` is wrapped in an `Announce` from the Group, which is the pattern threadiverse implementations expect. ### Endpoints New endpoints required when Group mode is active: | Endpoint | Description | |---|---| | `GET /ap/author` | Person actor document for the blog author | The existing `/ap/actor` endpoint changes its `type` from `Person` to `Group`. All other endpoints (inbox, outbox, followers, following, webfinger) continue to operate on the primary (Group) actor. ### Configuration New Madblog config options: ```yaml # Actor type: "Person" (default, current behavior) or "Group" (threadiverse) activitypub_actor_type: Group # Author details for the Person actor (when actor_type is Group) activitypub_author_username: fabio # default: "author" activitypub_author_name: Fabio Manganiello # default: config.author activitypub_author_icon_url: https://... # default: config.author_photo ``` When `activitypub_actor_type` is `Person` (or unset), behavior is unchanged. ### Migration path Switching an existing blog from Person to Group is a **breaking change** for existing followers — remote instances cache the actor type. Options: 1. **Recommended**: Deploy as Group from the start for new blogs. 2. **Existing blogs**: Publish an `Update{Actor}` changing the type to Group. Mastodon handles this gracefully (re-fetches the actor), but some implementations may not. A note in the changelog advising re-follow may be necessary. ## Changes Required ### Pubby 1. **Announce activity builder** — Add `OutboxProcessor.build_announce_activity()` that wraps a `Create` activity in an `Announce` from the configured actor. 2. **`publish_object` support for Announce** — Extend `ActivityPubHandler.publish_object()` to accept `activity_type="Announce"` (or a new `publish_announce()` method). 3. **Secondary actor endpoint** — The Flask/FastAPI/Tornado adapters need a way to register a second read-only actor endpoint (e.g. `/ap/author`) that serves a minimal Person document. This could be a new `bind_secondary_actor()` helper, or a list of actor configs in `bind_activitypub()`. 4. **Actor document generation** — `get_actor_document()` already uses `self.actor_type` from config, so setting `type: Group` in `actor_config` is sufficient for the primary actor. The secondary Person actor needs its own document builder (minimal: `id`, `type`, `preferredUsername`, `name`, `icon`, `url` — no inbox/outbox/keys required since it doesn't process activities). ### Madblog 1. **Config** — Add `activitypub_actor_type` and author-related fields to `Config` and `init_config()`. 2. **Actor setup** (`_mixin.py`) — When `activitypub_actor_type == "Group"`: - Pass `type: "Group"` in the primary `actor_config`. - Create and register a secondary Person actor endpoint for the author. 3. **Publishing flow** (`_integration.py`) — When Group mode is active: - Set `attributedTo` on the Article to the Person actor ID. - After building the `Create{Article}`, wrap it in an `Announce` from the Group and deliver that instead. 4. **Content negotiation** (`_mixin.py`) — The AP JSON response for `/article/...` should include `attributedTo` pointing to the Person actor when in Group mode. 5. **Tests** — New test class covering Group mode: actor document type, announce wrapping, author endpoint, webfinger resolution. 6. **Documentation** — Update README with the new config options and a note about threadiverse compatibility. ## Open Questions 1. **Should the Person actor have its own inbox?** Threadiverse implementations may attempt to deliver replies to the `attributedTo` actor. If the Person has no inbox, those deliveries will fail silently. A lightweight option: share the Group's inbox and route incoming activities appropriately. 2. **Outbox representation** — Should the Group's outbox contain the Announce activities, the inner Create activities, or both? Lemmy puts Announces in the community outbox. 3. **Backwards compatibility** — Should Madblog support a "hybrid" mode that sends both `Create` (for Mastodon) and `Announce` (for threadiverse)? This would cause duplicate posts on Mastodon. The Announce-only approach is correct — Mastodon already handles `Announce{Create{...}}` from Group actors properly. 4. **Separate keypair for the Person actor?** If the Person actor eventually needs to sign requests (e.g. for inbox delivery), it would need its own keypair. For the initial read-only implementation, the Group's key suffices. ## References - [ActivityPub Groups in Lemmy](https://join-lemmy.org/docs/contributors/05-federation.html) - [FEP-1b12: Group federation](https://codeberg.org/fediverse/fep/src/branch/main/fep/1b12/fep-1b12.md) - [Mastodon Group actor handling](https://docs.joinmastodon.org/spec/activitypub/#Group) - [Discussion thread](https://activitypub.space/post/1503)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
blacklight/madblog#21
No description provided.