A community based topic aggregation platform built on atproto

Communities PRD: Federated Forum System#

Status: Draft Owner: Platform Team Last Updated: 2025-10-07

Overview#

Coves communities are federated, instance-scoped forums built on atProto. Each community is identified by a scoped handle (!gaming@coves.social) and owned by a DID, enabling future portability and community governance.

Vision#

V1 (MVP): Instance-owned communities with scoped handles V2 (Post-Launch): Cross-instance discovery and moderation signal federation V3 (Future): Community-owned DIDs with migration capabilities via community voting

Core Principles#

  1. Scoped by default: All communities use !name@instance.com format
  2. DID-based ownership: Communities are owned by DIDs (initially instance, eventually community)
  3. Web DID compatible: Communities can use did:web for custom domains (e.g., !photography@lens.club)
  4. Federation-ready: Design for cross-instance discovery and moderation from day one
  5. Community sovereignty: Future path to community ownership and migration

Identity & Namespace#

Community Handle Format#

!{name}@{instance}

Examples:
!gaming@coves.social
!photography@lens.club
!golang@dev.forums
!my-book-club@personal.coves.io

DID Ownership#

V1: Instance-Owned

{
  "community": {
    "handle": "!gaming@coves.social",
    "did": "did:web:coves.social:community:gaming",
    "owner": "did:web:coves.social",
    "createdBy": "did:plc:user123",
    "hostedBy": "did:web:coves.social",
    "created": "2025-10-07T12:00:00Z"
  }
}

Future: Community-Owned

{
  "community": {
    "handle": "!gaming@coves.social",
    "did": "did:web:gaming.community",
    "owner": "did:web:gaming.community",
    "createdBy": "did:plc:user123",
    "hostedBy": "did:web:coves.social",
    "governance": {
      "type": "multisig",
      "votingEnabled": true
    }
  }
}

Why Scoped Names?#

  • No namespace conflicts: Each instance controls its own namespace
  • Clear ownership: @instance shows who hosts it
  • Decentralized: No global registry required
  • Web DID ready: Communities can become did:web and use custom domains
  • Fragmentation handled socially: Community governance and moderation quality drives membership

Visibility & Discoverability#

Visibility Tiers#

Public (Default)

  • Indexed by home instance
  • Appears in search results
  • Listed in community directory
  • Can be federated to other instances

Unlisted

  • Accessible via direct link
  • Not in search results
  • Not in public directory
  • Members can invite others

Private

  • Invite-only
  • Not discoverable
  • Not federated
  • Requires approval to join

Discovery Configuration#

type CommunityVisibility struct {
    Level                  string   // "public", "unlisted", "private"
    AllowExternalDiscovery bool     // Can other instances index this?
    AllowedInstances       []string // Whitelist (empty = all if public)
}

Examples:

// Public gaming community, federate everywhere
{
  "visibility": "public",
  "allowExternalDiscovery": true,
  "allowedInstances": []
}

// Book club, public on home instance only
{
  "visibility": "public",
  "allowExternalDiscovery": false,
  "allowedInstances": []
}

// Private beta testing community
{
  "visibility": "private",
  "allowExternalDiscovery": false,
  "allowedInstances": ["coves.social", "trusted.instance"]
}

Moderation & Federation#

Moderation Actions (Local Only)#

Communities can be moderated locally by the hosting instance:

type ModerationAction struct {
    CommunityDID string
    Action       string // "delist", "quarantine", "remove"
    Reason       string
    Instance     string
    Timestamp    time.Time
    BroadcastSignal bool // Share with network?
}

Action Types:

Delist

  • Removed from search/directory
  • Existing members can still access
  • Not deleted, just hidden

Quarantine

  • Visible with warning label
  • "This community may violate guidelines"
  • Can still be accessed with acknowledgment

Remove

  • Community hidden from instance AppView
  • Data still exists in firehose
  • Other instances can choose to ignore removal

Federation Reality#

What you can control:

  • What YOUR AppView indexes
  • What moderation signals you broadcast
  • What other instances' signals you honor

What you cannot control:

  • Self-hosted PDS/AppView can index anything
  • Other instances may ignore your moderation
  • Community data lives in firehose regardless

Moderation is local AppView filtering, not network-wide censorship.

Moderation Signal Federation (V2)#

Instances can subscribe to each other's moderation feeds:

{
  "moderationFeed": "did:web:coves.social:moderation",
  "action": "remove",
  "target": "did:web:coves.social:community:hate-speech",
  "reason": "Violates community guidelines",
  "timestamp": "2025-10-07T14:30:00Z",
  "evidence": "https://coves.social/moderation/case/123"
}

Other instances can:

  • Auto-apply trusted instance moderation
  • Show warnings based on signals
  • Ignore signals entirely

MVP (V1) Scope#

✅ Completed (2025-10-08)#

Core Functionality:

  • Create communities (instance-owned DID)
  • Scoped handle format (!name@instance)
  • Three visibility levels (public, unlisted, private)
  • Basic community metadata (name, description, rules)
  • Write-forward to PDS (communities as atProto records)
  • Jetstream consumer (index communities from firehose)

Technical Infrastructure:

  • Lexicon: social.coves.community.profile with did field (atProto compliant!)
  • DID format: did:plc:xxx (portable, federated)
  • PostgreSQL indexing for local communities
  • Service layer (business logic)
  • Repository layer (database)
  • Consumer layer (firehose indexing)
  • Environment config (IS_DEV_ENV, PLC_DIRECTORY_URL)

Critical Fixes:

  • Fixed record_uri bug (now points to correct repository location)
  • Added required did field to lexicon (atProto compliance)
  • Consumer correctly separates community DID from repository DID
  • E2E test passes (PDS write → firehose → AppView indexing)

🚧 In Progress#

API Endpoints (XRPC):

  • social.coves.community.create (handler exists, needs testing)
  • social.coves.community.get (handler exists, needs testing)
  • social.coves.community.list (handler exists, needs testing)
  • social.coves.community.search (handler exists, needs testing)
  • social.coves.community.subscribe (handler exists)
  • social.coves.community.unsubscribe (handler exists)

Subscriptions & Memberships:

  • Database schema (subscriptions, memberships tables)
  • Repository methods (subscribe, unsubscribe, list)
  • Consumer processing (index subscription events from firehose)
  • Membership tracking (convert subscription → membership on first post?)

⏳ TODO Before V1 Launch#

Critical Path:

  • Test all XRPC endpoints end-to-end
  • Implement OAuth middleware (protect create/update endpoints)
  • Add authorization checks (who can create/update/delete?)
  • Handle validation (prevent duplicate handles, validate DIDs)
  • Rate limiting (prevent community spam)

Community Discovery:

  • Community list endpoint (pagination, filtering)
  • Community search (full-text search on name/description)
  • Visibility enforcement (respect public/unlisted/private)
  • Federation config (respect allowExternalDiscovery)

Posts in Communities:

  • Extend social.coves.post lexicon with community field
  • Create post endpoint (require community membership?)
  • Feed generation (show posts in community)
  • Post consumer (index community posts from firehose)

Moderation (Basic):

  • Remove community from AppView (delist)
  • Quarantine community (show warning)
  • Moderation audit log
  • Admin endpoints (for instance operators)

Testing & Documentation:

  • Integration tests for all flows
  • API documentation (XRPC endpoints)
  • Deployment guide (PDS setup, environment config)
  • Migration guide (how to upgrade from test to production)

Out of Scope (V2+)#

  • Moderation signal federation
  • Community-owned DIDs
  • Migration/portability
  • Governance voting
  • Custom domain DIDs

Phase 2: Federation & Discovery#

Goals:

  • Cross-instance community search
  • Federated moderation signals
  • Trust networks between instances

Features:

// Cross-instance discovery
type FederationConfig struct {
    DiscoverPeers        []string // Other Coves instances to index
    TrustModerationFrom  []string // Auto-apply moderation signals
    ShareCommunitiesWith []string // Allow these instances to index ours
}

// Moderation trust network
type ModerationTrust struct {
    InstanceDID string
    TrustLevel  string // "auto-apply", "show-warning", "ignore"
    Categories  []string // Which violations to trust ("spam", "nsfw", etc)
}

User Experience:

Search: "golang"

Results:
!golang@coves.social (45k members)
  Hosted on coves.social
  [Join]

!golang@dev.forums (12k members)
  Hosted on dev.forums
  Focused on systems programming
  [Join]

!go@programming.zone (3k members)
  Hosted on programming.zone
  ⚠️ Flagged by trusted moderators
  [View Details]

Implementation Log#

2025-10-08: DID Architecture & atProto Compliance#

Major Decisions:

  1. Migrated from did:coves to did:plc

    • Communities now use proper PLC DIDs (portable across instances)
    • Added IS_DEV_ENV flag (dev = generate without PLC registration, prod = register)
    • Matches Bluesky's feed generator pattern
  2. Fixed Critical record_uri Bug

    • Problem: Consumer was setting community DID as repository owner
    • Fix: Correctly separate community DID (entity) from repository DID (storage)
    • Result: URIs now point to actual data location (federation works!)
  3. Added Required did Field to Lexicon

    • atProto research revealed communities MUST have their own DID field
    • Matches app.bsky.feed.generator pattern (service has DID, record stored elsewhere)
    • Enables future migration to community-owned repositories

Architecture Insights:

User Profile (Bluesky):
  at://did:plc:user123/app.bsky.actor.profile/self
  ↑ Repository location IS the identity
  No separate "did" field needed

Feed Generator (Bluesky):
  at://did:plc:creator456/app.bsky.feed.generator/cool-feed
  Record contains: {"did": "did:web:feedgen.service", ...}
  ↑ Service has own DID, record stored in creator's repo

Community (Coves V1):
  at://did:plc:instance123/social.coves.community.profile/rkey
  Record contains: {"did": "did:plc:community789", ...}
  ↑ Community has own DID, record stored in instance repo

Community (Coves V2 - Future):
  at://did:plc:community789/social.coves.community.profile/self
  Record contains: {"owner": "did:plc:instance123", ...}
  ↑ Community owns its own repo, instance manages it

Key Findings:

  1. Keypair Management: Coves can manage community keypairs (like Bluesky manages user keys)
  2. PDS Authentication: Can create PDS accounts for communities, Coves stores credentials
  3. Migration Path: Current V1 enables future V2 without breaking changes

Trade-offs:

  • V1 (Current): Simple, ships fast, limited portability
  • V2 (Future): Complex, true portability, matches atProto entity model

Decision: Ship V1 now, plan V2 migration.


CRITICAL: DID Architecture Decision (2025-10-08)#

Current State: Hybrid Approach#

V1 Implementation (Current):

Community DID:     did:plc:community789  (portable identity)
Repository:        at://did:plc:instance123/social.coves.community.profile/rkey
Owner:             did:plc:instance123   (instance manages it)

Record structure:
{
  "did": "did:plc:community789",        // Community's portable DID
  "owner": "did:plc:instance123",       // Instance owns the repository
  "hostedBy": "did:plc:instance123",    // Where it's currently hosted
  "createdBy": "did:plc:user456"        // User who created it
}

Why this matters:

  • ✅ Community has portable DID (can be referenced across network)
  • ✅ Record URI points to actual data location (federation works)
  • ✅ Clear separation: community identity ≠ storage location
  • ⚠️ Limited portability: Moving instances requires deleting/recreating record

V2 Option: True Community Repositories#

Future Architecture (under consideration):

Community DID:     did:plc:community789
Repository:        at://did:plc:community789/social.coves.community.profile/self
Owner:             did:plc:instance123 (in metadata, not repo owner)

Community gets:
- Own PDS account (managed by Coves backend)
- Own signing keypair (stored by Coves, like Bluesky stores user keys)
- Own repository (true data portability)

Benefits:

  • ✅ True portability: URI never changes when migrating
  • ✅ Matches atProto entity model (feed generators, labelers)
  • ✅ Community can move between instances via DID document update

Complexity:

  • Coves must generate keypairs for each community
  • Coves must create PDS accounts for each community
  • Coves must securely store community credentials
  • More infrastructure to manage

Decision: Start with V1 (current), plan for V2 migration path.

Migration Path V1 → V2#

When ready for true portability:

  1. Generate keypair for existing community
  2. Register community's DID document with PLC
  3. Create PDS account for community (Coves manages credentials)
  4. Migrate record from instance repo to community repo
  5. Update AppView to index from new location

The did field in records makes this migration possible!

Phase 3: Community Ownership#

Goals:

  • Transfer ownership from instance to community
  • Enable community governance
  • Allow migration between instances

Features:

Governance System:

type CommunityGovernance struct {
    Enabled       bool
    VotingPower   string // "one-person-one-vote", "reputation-weighted"
    QuorumPercent int    // % required for votes to pass
    Moderators    []string // DIDs with mod powers
}

Migration Flow:

1. Community votes on migration (e.g., from coves.social to gaming.forum)
2. Vote passes (66% threshold)
3. Community DID ownership transfers
4. New instance re-indexes community data from firehose
5. Handle updates: !gaming@gaming.forum
6. Old instance can keep archive or redirect

DID Transfer:

{
  "community": "!gaming@gaming.forum",
  "did": "did:web:gaming.community",
  "previousHost": "did:web:coves.social",
  "currentHost": "did:web:gaming.forum",
  "transferredAt": "2025-12-15T10:00:00Z",
  "governanceSignatures": ["sig1", "sig2", "sig3"]
}

Lexicon Design#

social.coves.community#

{
  "lexicon": 1,
  "id": "social.coves.community",
  "defs": {
    "main": {
      "type": "record",
      "key": "tid",
      "record": {
        "type": "object",
        "required": ["handle", "name", "createdAt"],
        "properties": {
          "handle": {
            "type": "string",
            "description": "Scoped handle (!name@instance)"
          },
          "name": {
            "type": "string",
            "maxLength": 64,
            "description": "Display name"
          },
          "description": {
            "type": "string",
            "maxLength": 3000
          },
          "rules": {
            "type": "array",
            "items": {"type": "string"}
          },
          "visibility": {
            "type": "string",
            "enum": ["public", "unlisted", "private"],
            "default": "public"
          },
          "federation": {
            "type": "object",
            "properties": {
              "allowExternalDiscovery": {"type": "boolean", "default": true},
              "allowedInstances": {
                "type": "array",
                "items": {"type": "string"}
              }
            }
          },
          "owner": {
            "type": "string",
            "description": "DID of community owner"
          },
          "createdBy": {
            "type": "string",
            "description": "DID of user who created community"
          },
          "hostedBy": {
            "type": "string",
            "description": "DID of hosting instance"
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          }
        }
      }
    }
  }
}

social.coves.post (Community Extension)#

{
  "properties": {
    "community": {
      "type": "string",
      "description": "DID of community this post belongs to"
    }
  }
}

Technical Architecture#

Data Flow#

User creates community
  ↓
PDS creates community record
  ↓
Firehose broadcasts creation
  ↓
AppView indexes community (if allowed)
  ↓
PostgreSQL stores community metadata
  ↓
Community appears in local search/directory

Database Schema (AppView)#

CREATE TABLE communities (
    id SERIAL PRIMARY KEY,
    did TEXT UNIQUE NOT NULL,
    handle TEXT UNIQUE NOT NULL, -- !name@instance
    name TEXT NOT NULL,
    description TEXT,
    rules JSONB,
    visibility TEXT NOT NULL DEFAULT 'public',
    federation_config JSONB,
    owner_did TEXT NOT NULL,
    created_by_did TEXT NOT NULL,
    hosted_by_did TEXT NOT NULL,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL,
    member_count INTEGER DEFAULT 0,
    post_count INTEGER DEFAULT 0
);

CREATE INDEX idx_communities_handle ON communities(handle);
CREATE INDEX idx_communities_visibility ON communities(visibility);
CREATE INDEX idx_communities_hosted_by ON communities(hosted_by_did);

CREATE TABLE community_moderation (
    id SERIAL PRIMARY KEY,
    community_did TEXT NOT NULL REFERENCES communities(did),
    action TEXT NOT NULL, -- 'delist', 'quarantine', 'remove'
    reason TEXT,
    instance_did TEXT NOT NULL,
    broadcast BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP NOT NULL
);

API Endpoints (XRPC)#

V1 (MVP)#

social.coves.community.create
social.coves.community.get
social.coves.community.update
social.coves.community.list
social.coves.community.search
social.coves.community.join
social.coves.community.leave

V3 (Governance)#

social.coves.community.transferOwnership
social.coves.community.proposeVote
social.coves.community.castVote
social.coves.community.migrate

Success Metrics#

V1 (MVP)#

  • Communities can be created with scoped handles
  • Posts can be made to communities
  • Community discovery works on local instance
  • All three visibility levels function correctly
  • Basic moderation (delist/remove) works

V2 (Federation)#

  • Cross-instance community search returns results
  • Moderation signals are broadcast and received
  • Trust networks prevent spam communities

V3 (Governance)#

  • Community ownership can be transferred
  • Voting system enables community decisions
  • Communities can migrate between instances

Security Considerations#

Every Operation Must:#

  • Validate DID ownership
  • Check community visibility settings
  • Verify instance authorization
  • Use parameterized queries
  • Rate limit community creation
  • Log moderation actions

Risks & Mitigations:#

Community Squatting

  • Risk: Instance creates popular names and sits on them
  • Mitigation: Activity requirements (auto-archive inactive communities)

Spam Communities

  • Risk: Bad actors create thousands of spam communities
  • Mitigation: Rate limits, moderation signals, trust networks

Migration Abuse

  • Risk: Community ownership stolen via fake votes
  • Mitigation: Governance thresholds, time locks, signature verification

Privacy Leaks

  • Risk: Private communities discovered via firehose
  • Mitigation: Encrypt sensitive metadata, only index allowed instances

Open Questions#

  1. Should we support community aliases? (e.g., !gaming!videogames)
  2. What's the minimum member count for community creation? (prevent spam)
  3. How do we handle abandoned communities? (creator leaves, no mods)
  4. Should communities have their own PDS? (advanced self-hosting)
  5. Cross-posting between communities? (one post in multiple communities)

Migration from V1 → V2 → V3#

V1 to V2 (Adding Federation)#

  • Backward compatible: All V1 communities work in V2
  • New fields added to lexicon (optional)
  • Existing communities opt-in to federation

V2 to V3 (Community Ownership)#

  • Instance can propose ownership transfer to community
  • Community votes to accept
  • DID ownership updates
  • No breaking changes to existing communities

References#

Approval & Sign-Off#

  • Product Lead Review
  • Engineering Lead Review
  • Security Review
  • Legal/Policy Review (especially moderation aspects)

Next Steps:

  1. Review and approve PRD
  2. Create V1 implementation tickets
  3. Design lexicon schema
  4. Build community creation flow
  5. Implement local discovery
  6. Write integration tests