A community based topic aggregation platform built on atproto
1# Federation PRD: Cross-Instance Posting (Beta) 2 3**Status:** Planning - Beta 4**Target:** Beta Release 5**Owner:** TBD 6**Last Updated:** 2025-11-16 7 8--- 9 10## Overview 11 12Enable Lemmy-style federation where users on any Coves instance can post to communities hosted on other instances, while maintaining community ownership and moderation control. 13 14### Problem Statement 15 16**Current (Alpha):** 17- Posts to communities require community credentials 18- Users can only post to communities on their home instance 19- No true federation across instances 20 21**Desired (Beta):** 22- User A@coves.social can post to !gaming@covesinstance.com 23- Communities maintain full moderation control 24- Content lives in community repositories (not user repos) 25- Seamless UX - users don't think about federation 26 27--- 28 29## Goals 30 31### Primary Goals 321. **Enable cross-instance posting** - Users can post to any community on any federated instance 332. **Preserve community ownership** - Posts live in community repos, not user repos 343. **atProto-native implementation** - Use `com.atproto.server.getServiceAuth` pattern 354. **Maintain security** - No compromise on auth, validation, or moderation 36 37### Non-Goals (Future Versions) 38- Automatic instance discovery (Beta: manual allowlist) 39- Cross-instance moderation delegation 40- Content mirroring/replication 41- User migration between instances 42 43--- 44 45## Technical Approach 46 47### Architecture: atProto Service Auth 48 49Use atProto's native service authentication delegation pattern: 50 51``` 52┌─────────────┐ ┌──────────────────┐ ┌─────────────┐ 53│ User A │ │ coves.social │ │ covesinstance│ 54│ @coves.soc │────────▶│ AppView │────────▶│ .com PDS │ 55└─────────────┘ (1) └──────────────────┘ (2) └─────────────┘ 56 JWT auth Request Service Auth Validate 57 │ │ 58 │◀──────────────────────┘ 59 │ (3) Scoped Token 60 61 62 ┌──────────────────┐ 63 │ covesinstance │ 64 │ .com PDS │ 65 │ Write Post │ 66 └──────────────────┘ 67 68 69 ┌──────────────────┐ 70 │ Firehose │ 71 │ (broadcasts) │ 72 └──────────────────┘ 73 74 ┌────────────┴────────────┐ 75 ▼ ▼ 76 ┌──────────────┐ ┌──────────────┐ 77 │ coves.social │ │covesinstance │ 78 │ AppView │ │ .com AppView│ 79 │ (indexes) │ │ (indexes) │ 80 └──────────────┘ └──────────────┘ 81``` 82 83### Flow Breakdown 84 85**Step 1: User Authentication (Unchanged)** 86- User authenticates with their home instance (coves.social) 87- Receives JWT token for API requests 88 89**Step 2: Service Auth Request (New)** 90- When posting to remote community, AppView requests service auth token 91- Endpoint: `POST {remote-pds}/xrpc/com.atproto.server.getServiceAuth` 92- Payload: 93 ```json 94 { 95 "aud": "did:plc:community123", // Community DID 96 "exp": 1234567890, // Token expiration 97 "lxm": "social.coves.community.post.create" // Authorized method 98 } 99 ``` 100 101**Step 3: Service Auth Validation (New - PDS Side)** 102- Remote PDS validates request: 103 - Is requesting service trusted? (instance allowlist) 104 - Is user banned from community? 105 - Does community allow remote posts? 106 - Rate limiting checks 107- Returns scoped token valid for specific community + operation 108 109**Step 4: Post Creation (Modified)** 110- AppView uses service auth token to write to remote PDS 111- Same `com.atproto.repo.createRecord` endpoint as current implementation 112- Post record written to community's repository 113 114**Step 5: Indexing (Unchanged)** 115- PDS broadcasts to firehose 116- All AppViews index via Jetstream consumers 117 118--- 119 120## Implementation Details 121 122### Phase 1: Service Detection (Local vs Remote) 123 124**File:** `internal/core/posts/service.go` 125 126```go 127func (s *postService) CreatePost(ctx context.Context, req CreatePostRequest) (*CreatePostResponse, error) { 128 // ... existing validation ... 129 130 community, err := s.communityService.GetByDID(ctx, communityDID) 131 if err != nil { 132 return nil, err 133 } 134 135 // NEW: Route based on community location 136 if s.isLocalCommunity(community) { 137 return s.createLocalPost(ctx, community, req) 138 } 139 return s.createFederatedPost(ctx, community, req) 140} 141 142func (s *postService) isLocalCommunity(community *communities.Community) bool { 143 localPDSHost := extractHost(s.pdsURL) 144 communityPDSHost := extractHost(community.PDSURL) 145 return localPDSHost == communityPDSHost 146} 147``` 148 149### Phase 2: Service Auth Client 150 151**New File:** `internal/atproto/service_auth/client.go` 152 153```go 154type ServiceAuthClient interface { 155 // RequestServiceAuth obtains a scoped token for writing to remote community 156 RequestServiceAuth(ctx context.Context, opts ServiceAuthOptions) (*ServiceAuthToken, error) 157} 158 159type ServiceAuthOptions struct { 160 RemotePDSURL string // Remote PDS endpoint 161 CommunityDID string // Target community DID 162 UserDID string // Author DID (for validation) 163 Method string // "social.coves.community.post.create" 164 ExpiresIn int // Token lifetime (seconds) 165} 166 167type ServiceAuthToken struct { 168 Token string // JWT token for auth 169 ExpiresAt time.Time // When token expires 170} 171 172func (c *serviceAuthClient) RequestServiceAuth(ctx context.Context, opts ServiceAuthOptions) (*ServiceAuthToken, error) { 173 endpoint := fmt.Sprintf("%s/xrpc/com.atproto.server.getServiceAuth", opts.RemotePDSURL) 174 175 payload := map[string]interface{}{ 176 "aud": opts.CommunityDID, 177 "exp": time.Now().Add(time.Duration(opts.ExpiresIn) * time.Second).Unix(), 178 "lxm": opts.Method, 179 } 180 181 // Sign request with our instance DID credentials 182 signedReq, err := c.signRequest(payload) 183 if err != nil { 184 return nil, fmt.Errorf("failed to sign service auth request: %w", err) 185 } 186 187 resp, err := c.httpClient.Post(endpoint, signedReq) 188 if err != nil { 189 return nil, fmt.Errorf("service auth request failed: %w", err) 190 } 191 192 return parseServiceAuthResponse(resp) 193} 194``` 195 196### Phase 3: Federated Post Creation 197 198**File:** `internal/core/posts/service.go` 199 200```go 201func (s *postService) createFederatedPost(ctx context.Context, community *communities.Community, req CreatePostRequest) (*CreatePostResponse, error) { 202 // 1. Request service auth token from remote PDS 203 token, err := s.serviceAuthClient.RequestServiceAuth(ctx, service_auth.ServiceAuthOptions{ 204 RemotePDSURL: community.PDSURL, 205 CommunityDID: community.DID, 206 UserDID: req.AuthorDID, 207 Method: "social.coves.community.post.create", 208 ExpiresIn: 300, // 5 minutes 209 }) 210 if err != nil { 211 // Handle specific errors 212 if isUnauthorized(err) { 213 return nil, ErrNotAuthorizedRemote 214 } 215 if isBanned(err) { 216 return nil, ErrBannedRemote 217 } 218 return nil, fmt.Errorf("failed to obtain service auth: %w", err) 219 } 220 221 // 2. Build post record (same as local) 222 postRecord := PostRecord{ 223 Type: "social.coves.community.post", 224 Community: community.DID, 225 Author: req.AuthorDID, 226 Title: req.Title, 227 Content: req.Content, 228 // ... other fields ... 229 CreatedAt: time.Now().UTC().Format(time.RFC3339), 230 } 231 232 // 3. Write to remote PDS using service auth token 233 uri, cid, err := s.createPostOnRemotePDS(ctx, community.PDSURL, community.DID, postRecord, token.Token) 234 if err != nil { 235 return nil, fmt.Errorf("failed to write to remote PDS: %w", err) 236 } 237 238 log.Printf("[FEDERATION] User %s posted to remote community %s: %s", 239 req.AuthorDID, community.DID, uri) 240 241 return &CreatePostResponse{ 242 URI: uri, 243 CID: cid, 244 }, nil 245} 246 247func (s *postService) createPostOnRemotePDS( 248 ctx context.Context, 249 pdsURL string, 250 communityDID string, 251 record PostRecord, 252 serviceAuthToken string, 253) (uri, cid string, err error) { 254 endpoint := fmt.Sprintf("%s/xrpc/com.atproto.repo.createRecord", pdsURL) 255 256 payload := map[string]interface{}{ 257 "repo": communityDID, 258 "collection": "social.coves.community.post", 259 "record": record, 260 } 261 262 jsonData, _ := json.Marshal(payload) 263 req, _ := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewBuffer(jsonData)) 264 265 // Use service auth token instead of community credentials 266 req.Header.Set("Authorization", "Bearer "+serviceAuthToken) 267 req.Header.Set("Content-Type", "application/json") 268 269 // ... execute request, parse response ... 270 return uri, cid, nil 271} 272``` 273 274### Phase 4: PDS Service Auth Validation (PDS Extension) 275 276**Note:** This requires extending the PDS. Options: 2771. Contribute to official atproto PDS 2782. Run modified PDS fork 2793. Use PDS middleware/proxy 280 281**Conceptual Implementation:** 282 283```go 284// PDS validates service auth requests before issuing tokens 285func (h *ServiceAuthHandler) HandleGetServiceAuth(w http.ResponseWriter, r *http.Request) { 286 var req ServiceAuthRequest 287 json.NewDecoder(r.Body).Decode(&req) 288 289 // 1. Verify requesting service is trusted 290 requestingDID := extractDIDFromJWT(r.Header.Get("Authorization")) 291 if !h.isTrustedInstance(requestingDID) { 292 writeError(w, http.StatusForbidden, "UntrustedInstance", "Instance not in allowlist") 293 return 294 } 295 296 // 2. Validate community exists on this PDS 297 community, err := h.getCommunityByDID(req.Aud) 298 if err != nil { 299 writeError(w, http.StatusNotFound, "CommunityNotFound", "Community not hosted here") 300 return 301 } 302 303 // 3. Check user not banned (query from AppView or local moderation records) 304 if h.isUserBanned(req.UserDID, req.Aud) { 305 writeError(w, http.StatusForbidden, "Banned", "User banned from community") 306 return 307 } 308 309 // 4. Check community settings (allows remote posts?) 310 if !community.AllowFederatedPosts { 311 writeError(w, http.StatusForbidden, "FederationDisabled", "Community doesn't accept federated posts") 312 return 313 } 314 315 // 5. Rate limiting (per user, per community, per instance) 316 if h.exceedsRateLimit(req.UserDID, req.Aud, requestingDID) { 317 writeError(w, http.StatusTooManyRequests, "RateLimited", "Too many requests") 318 return 319 } 320 321 // 6. Generate scoped token 322 token := h.issueServiceAuthToken(ServiceAuthTokenOptions{ 323 Audience: req.Aud, // Community DID 324 Subject: requestingDID, // Requesting instance DID 325 Method: req.Lxm, // Authorized method 326 ExpiresAt: time.Unix(req.Exp, 0), 327 Scopes: []string{"write:posts"}, 328 }) 329 330 json.NewEncoder(w).Encode(map[string]string{ 331 "token": token, 332 }) 333} 334``` 335 336--- 337 338## Database Schema Changes 339 340### New Table: `instance_federation` 341 342Tracks trusted instances and federation settings: 343 344```sql 345CREATE TABLE instance_federation ( 346 id SERIAL PRIMARY KEY, 347 instance_did TEXT NOT NULL UNIQUE, 348 instance_domain TEXT NOT NULL, 349 trust_level TEXT NOT NULL, -- 'trusted', 'limited', 'blocked' 350 allowed_methods TEXT[] NOT NULL DEFAULT '{}', 351 rate_limit_posts_per_hour INTEGER NOT NULL DEFAULT 100, 352 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 353 updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 354 notes TEXT 355); 356 357CREATE INDEX idx_instance_federation_did ON instance_federation(instance_did); 358CREATE INDEX idx_instance_federation_trust ON instance_federation(trust_level); 359``` 360 361### New Table: `federation_rate_limits` 362 363Track federated post rate limits: 364 365```sql 366CREATE TABLE federation_rate_limits ( 367 id SERIAL PRIMARY KEY, 368 user_did TEXT NOT NULL, 369 community_did TEXT NOT NULL, 370 instance_did TEXT NOT NULL, 371 window_start TIMESTAMPTZ NOT NULL, 372 post_count INTEGER NOT NULL DEFAULT 1, 373 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 374 updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 375 376 UNIQUE(user_did, community_did, instance_did, window_start) 377); 378 379CREATE INDEX idx_federation_rate_limits_lookup 380 ON federation_rate_limits(user_did, community_did, instance_did, window_start); 381``` 382 383### Update Table: `communities` 384 385Add federation settings: 386 387```sql 388ALTER TABLE communities 389ADD COLUMN allow_federated_posts BOOLEAN NOT NULL DEFAULT true, 390ADD COLUMN federation_mode TEXT NOT NULL DEFAULT 'open'; 391-- federation_mode: 'open' (any instance), 'allowlist' (trusted only), 'local' (no federation) 392``` 393 394--- 395 396## Security Considerations 397 398### 1. Instance Trust Model 399 400**Allowlist Approach (Beta):** 401- Manual approval of federated instances 402- Admin UI to manage instance trust levels 403- Default: block all, explicit allow 404 405**Trust Levels:** 406- `trusted` - Full federation, normal rate limits 407- `limited` - Federation allowed, strict rate limits 408- `blocked` - No federation 409 410### 2. User Ban Synchronization 411 412**Challenge:** Remote instance needs to check local bans 413 414**Options:** 4151. **Service auth validation** - PDS queries AppView for ban status 4162. **Ban records in PDS** - Moderation records stored in community repo 4173. **Cached ban list** - Remote instances cache ban lists (with TTL) 418 419**Beta Approach:** Option 1 (service auth validation queries AppView) 420 421### 3. Rate Limiting 422 423**Multi-level rate limits:** 424- Per user per community: 10 posts/hour 425- Per instance per community: 100 posts/hour 426- Per user across all communities: 50 posts/hour 427 428**Implementation:** In-memory + PostgreSQL for persistence 429 430### 4. Content Validation 431 432**Same validation as local posts:** 433- Lexicon validation 434- Content length limits 435- Embed validation 436- Label validation 437 438**Additional federation checks:** 439- Verify author DID is valid 440- Verify requesting instance signature 441- Verify token scopes match operation 442 443--- 444 445## API Changes 446 447### New Endpoint: `social.coves.federation.getTrustedInstances` 448 449**Purpose:** List instances this instance federates with 450 451**Lexicon:** 452```json 453{ 454 "lexicon": 1, 455 "id": "social.coves.federation.getTrustedInstances", 456 "defs": { 457 "main": { 458 "type": "query", 459 "output": { 460 "encoding": "application/json", 461 "schema": { 462 "type": "object", 463 "required": ["instances"], 464 "properties": { 465 "instances": { 466 "type": "array", 467 "items": { "$ref": "#instanceView" } 468 } 469 } 470 } 471 } 472 }, 473 "instanceView": { 474 "type": "object", 475 "required": ["did", "domain", "trustLevel"], 476 "properties": { 477 "did": { "type": "string" }, 478 "domain": { "type": "string" }, 479 "trustLevel": { "type": "string" }, 480 "allowedMethods": { "type": "array", "items": { "type": "string" } } 481 } 482 } 483 } 484} 485``` 486 487### Modified Endpoint: `social.coves.community.post.create` 488 489**Changes:** 490- No API contract changes 491- Internal routing: local vs federated 492- New error codes: 493 - `FederationFailed` - Remote instance unreachable 494 - `RemoteNotAuthorized` - Remote instance rejected auth 495 - `RemoteBanned` - User banned on remote community 496 497--- 498 499## User Experience 500 501### Happy Path: Cross-Instance Post 502 5031. User on coves.social navigates to !gaming@covesinstance.com 5042. Clicks "Create Post" 5053. Fills out post form (title, content, etc.) 5064. Clicks "Submit" 5075. **Behind the scenes:** 508 - coves.social requests service auth from covesinstance.com 509 - covesinstance.com validates and issues token 510 - coves.social writes post using token 511 - Post appears in feed within seconds (via firehose) 5126. **User sees:** Post published successfully 5137. Post appears in: 514 - covesinstance.com feeds (native community) 515 - coves.social discover/all feeds (indexed via firehose) 516 - User's profile on coves.social 517 518### Error Cases 519 520**User Banned:** 521- Error: "You are banned from !gaming@covesinstance.com" 522- Suggestion: "Contact community moderators for more information" 523 524**Instance Blocked:** 525- Error: "This community does not accept posts from your instance" 526- Suggestion: "Contact community administrators or create a local account" 527 528**Federation Unavailable:** 529- Error: "Unable to connect to covesinstance.com. Try again later." 530- Fallback: Allow saving as draft (future feature) 531 532**Rate Limited:** 533- Error: "You're posting too quickly. Please wait before posting again." 534- Show: Countdown until next post allowed 535 536--- 537 538## Testing Requirements 539 540### Unit Tests 541 5421. **Service Detection:** 543 - `isLocalCommunity()` correctly identifies local vs remote 544 - Handles edge cases (different ports, subdomains) 545 5462. **Service Auth Client:** 547 - Correctly formats service auth requests 548 - Handles token expiration 549 - Retries on transient failures 550 5513. **Federated Post Creation:** 552 - Uses service auth token instead of community credentials 553 - Falls back gracefully on errors 554 - Logs federation events 555 556### Integration Tests 557 5581. **Local Post (Regression):** 559 - Posting to local community still works 560 - No performance degradation 561 5622. **Federated Post:** 563 - User can post to remote community 564 - Service auth token requested correctly 565 - Post written to remote PDS 566 - Post indexed by both AppViews 567 5683. **Authorization Failures:** 569 - Banned users rejected at service auth stage 570 - Untrusted instances rejected 571 - Expired tokens rejected 572 5734. **Rate Limiting:** 574 - Per-user rate limits enforced 575 - Per-instance rate limits enforced 576 - Rate limit resets correctly 577 578### End-to-End Tests 579 5801. **Cross-Instance User Journey:** 581 - Set up two instances (instance-a, instance-b) 582 - Create community on instance-b 583 - User on instance-a posts to instance-b community 584 - Verify post appears on both instances 585 5862. **Moderation Enforcement:** 587 - Ban user on remote instance 588 - Verify user can't post from any instance 589 - Unban user 590 - Verify user can post again 591 5923. **Instance Blocklist:** 593 - Block instance-a on instance-b 594 - Verify users from instance-a can't post to instance-b communities 595 - Unblock instance-a 596 - Verify posting works again 597 598--- 599 600## Migration Path (Alpha → Beta) 601 602### Phase 1: Backend Implementation (No User Impact) 6031. Add service auth client 6042. Add local vs remote detection 6053. Deploy with feature flag `ENABLE_FEDERATION=false` 606 607### Phase 2: Database Migration 6081. Add federation tables 6092. Seed with initial trusted instances (manual) 6103. Add community federation flags (default: allow) 611 612### Phase 3: Soft Launch 6131. Enable federation for single test instance 6142. Monitor service auth requests/errors 6153. Validate rate limiting works 616 617### Phase 4: Beta Rollout 6181. Enable `ENABLE_FEDERATION=true` for all instances 6192. Admin UI for managing trusted instances 6203. Community settings for federation preferences 621 622### Phase 5: Documentation & Onboarding 6231. Instance operator guide: "How to federate with other instances" 6242. Community moderator guide: "Federation settings" 6253. User guide: "Posting across instances" 626 627--- 628 629## Metrics & Success Criteria 630 631### Performance Metrics 632- Service auth request latency: p95 < 200ms 633- Federated post creation time: p95 < 2 seconds (vs 500ms local) 634- Service auth token cache hit rate: > 80% 635 636### Adoption Metrics 637- % of posts that are federated: Target 20% by end of Beta 638- Number of federated instances: Target 5+ by end of Beta 639- Cross-instance engagement (comments, votes): Monitor trend 640 641### Reliability Metrics 642- Service auth success rate: > 99% 643- Federated post success rate: > 95% 644- Service auth token validation errors: < 1% 645 646### Security Metrics 647- Unauthorized access attempts: Monitor & alert 648- Rate limit triggers: Track per instance 649- Ban evasion attempts: Zero tolerance 650 651--- 652 653## Rollback Plan 654 655If federation causes critical issues: 656 6571. **Immediate:** Set `ENABLE_FEDERATION=false` via env var 6582. **Fallback:** All posts route through local-only flow 6593. **Investigation:** Review logs for service auth failures 6604. **Fix Forward:** Deploy patch, re-enable gradually 661 662**No data loss:** Posts are written to PDS, indexed via firehose regardless of federation method. 663 664--- 665 666## Open Questions 667 6681. **Instance Discovery:** How do users find communities on other instances? 669 - Beta: Manual (users share links) 670 - Future: Instance directory, community search across instances 671 6722. **Service Auth Token Caching:** Should AppViews cache service auth tokens? 673 - Pros: Reduce latency, fewer PDS requests 674 - Cons: Stale permissions, ban enforcement delay 675 - **Decision needed:** Cache with short TTL (5 minutes)? 676 6773. **PDS Implementation:** Who implements service auth validation? 678 - Option A: Contribute to official PDS (long timeline) 679 - Option B: Run forked PDS (maintenance burden) 680 - Option C: Proxy/middleware (added complexity) 681 - **Decision needed:** Start with Option B, migrate to Option A? 682 6834. **Federation Symmetry:** If instance-a trusts instance-b, does instance-b auto-trust instance-a? 684 - Beta: No (asymmetric trust) 685 - Future: Mutual federation agreements? 686 6875. **Cross-Instance Moderation:** Should bans propagate across instances? 688 - Beta: No (each instance decides) 689 - Future: Shared moderation lists? 690 691--- 692 693## Future Enhancements (Post-Beta) 694 6951. **Service Auth Token Caching:** Reduce latency for frequent posters 6962. **Batch Service Auth:** Request tokens for multiple communities at once 6973. **Instance Discovery API:** Automatic instance detection/registration 6984. **Federation Analytics:** Dashboard showing cross-instance activity 6995. **Moderation Sync:** Optional shared ban lists across trusted instances 7006. **Content Mirroring:** Cache federated posts locally for performance 7017. **User Migration:** Transfer account between instances 702 703--- 704 705## Resources 706 707### Documentation 708- [atProto Service Auth Spec](https://atproto.com/specs/service-auth) (hypothetical - check actual docs) 709- Lemmy Federation Architecture 710- Mastodon Federation Implementation 711 712### Code References 713- `internal/core/posts/service.go` - Post creation service 714- `internal/api/handlers/post/create.go` - Post creation handler 715- `internal/atproto/jetstream/` - Firehose consumers 716 717### Dependencies 718- atproto SDK (for service auth) 719- PDS v0.4+ (service auth support) 720- PostgreSQL 14+ (for federation tables) 721 722--- 723 724## Appendix A: Service Auth Request Example 725 726**Request to Remote PDS:** 727```http 728POST https://covesinstance.com/xrpc/com.atproto.server.getServiceAuth 729Authorization: Bearer {coves-social-instance-jwt} 730Content-Type: application/json 731 732{ 733 "aud": "did:plc:community123", 734 "exp": 1700000000, 735 "lxm": "social.coves.community.post.create" 736} 737``` 738 739**Response:** 740```http 741HTTP/1.1 200 OK 742Content-Type: application/json 743 744{ 745 "token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9..." 746} 747``` 748 749**Using Token to Create Post:** 750```http 751POST https://covesinstance.com/xrpc/com.atproto.repo.createRecord 752Authorization: Bearer {service-auth-token} 753Content-Type: application/json 754 755{ 756 "repo": "did:plc:community123", 757 "collection": "social.coves.community.post", 758 "record": { 759 "$type": "social.coves.community.post", 760 "community": "did:plc:community123", 761 "author": "did:plc:user456", 762 "title": "Hello from coves.social!", 763 "content": "This is a federated post", 764 "createdAt": "2024-11-16T12:00:00Z" 765 } 766} 767``` 768 769--- 770 771## Appendix B: Error Handling Matrix 772 773| Error Condition | HTTP Status | Error Code | User Message | Retry Strategy | 774|----------------|-------------|------------|--------------|----------------| 775| Instance not trusted | 403 | `UntrustedInstance` | "This community doesn't accept posts from your instance" | No retry | 776| User banned | 403 | `Banned` | "You are banned from this community" | No retry | 777| Rate limit exceeded | 429 | `RateLimited` | "Too many posts. Try again in X minutes" | Exponential backoff | 778| PDS unreachable | 503 | `ServiceUnavailable` | "Community temporarily unavailable" | Retry 3x with backoff | 779| Invalid token | 401 | `InvalidToken` | "Session expired. Please try again" | Refresh token & retry | 780| Community not found | 404 | `CommunityNotFound` | "Community not found" | No retry | 781| Service auth failed | 500 | `FederationFailed` | "Unable to connect. Try again later" | Retry 2x | 782 783--- 784 785**End of PRD**