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#
- Scoped by default: All communities use
!name@instance.comformat - DID-based ownership: Communities are owned by DIDs (initially instance, eventually community)
- Web DID compatible: Communities can use
did:webfor custom domains (e.g.,!photography@lens.club) - Federation-ready: Design for cross-instance discovery and moderation from day one
- 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:
@instanceshows who hosts it - Decentralized: No global registry required
- Web DID ready: Communities can become
did:weband 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.profilewithdidfield (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_uribug (now points to correct repository location) - Added required
didfield 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.postlexicon withcommunityfield - 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:
-
Migrated from
did:covestodid:plc- Communities now use proper PLC DIDs (portable across instances)
- Added
IS_DEV_ENVflag (dev = generate without PLC registration, prod = register) - Matches Bluesky's feed generator pattern
-
Fixed Critical
record_uriBug- 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!)
-
Added Required
didField to Lexicon- atProto research revealed communities MUST have their own DID field
- Matches
app.bsky.feed.generatorpattern (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:
- Keypair Management: Coves can manage community keypairs (like Bluesky manages user keys)
- PDS Authentication: Can create PDS accounts for communities, Coves stores credentials
- 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:
- Generate keypair for existing community
- Register community's DID document with PLC
- Create PDS account for community (Coves manages credentials)
- Migrate record from instance repo to community repo
- 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#
- Should we support community aliases? (e.g.,
!gaming→!videogames) - What's the minimum member count for community creation? (prevent spam)
- How do we handle abandoned communities? (creator leaves, no mods)
- Should communities have their own PDS? (advanced self-hosting)
- 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#
- atProto Lexicon Spec: https://atproto.com/specs/lexicon
- DID Web Spec: https://w3c-ccg.github.io/did-method-web/
- Bluesky Handle System: https://atproto.com/specs/handle
- Coves Builder Guide:
/docs/CLAUDE-BUILD.md
Approval & Sign-Off#
- Product Lead Review
- Engineering Lead Review
- Security Review
- Legal/Policy Review (especially moderation aspects)
Next Steps:
- Review and approve PRD
- Create V1 implementation tickets
- Design lexicon schema
- Build community creation flow
- Implement local discovery
- Write integration tests