A community based topic aggregation platform built on atproto
1# Posts PRD: Forum Content System 2 3**Status:** ✅ Alpha CREATE Complete (2025-10-19) | Get/Update/Delete/Voting TODO 4**Owner:** Platform Team 5**Last Updated:** 2025-10-19 6 7## 🎯 Implementation Status 8 9### ✅ COMPLETED (Alpha - 2025-10-19) 10- **Post Creation:** Full write-forward to community PDS with real-time Jetstream indexing 11- **Handler Layer:** HTTP endpoint with authentication, validation, and security checks 12- **Service Layer:** Business logic with token refresh and community resolution 13- **Repository Layer:** PostgreSQL storage with proper indexing 14- **Jetstream Consumer:** Real-time indexing with security validation 15- **Database Migration:** Posts table created (migration 011) 16- **E2E Tests:** Live PDS + Jetstream integration tests passing 17- **at-identifier Support:** All 4 formats (DIDs, canonical, @-prefixed, scoped handles) 18 19### ⚠️ DEFERRED TO BETA 20- Content rules validation (text-only, image-only communities) - Governence 21- Post read operations (get, list) 22- Post update/edit operations 23- Post deletion 24- Voting system (upvotes/downvotes) 25- Derived characteristics indexing (embed_type, text_length, etc.) 26 27**See:** [IMPLEMENTATION_POST_CREATION.md](IMPLEMENTATION_POST_CREATION.md) for complete implementation details. 28 29--- 30 31## Overview 32 33Posts are the core content unit in Coves communities. Built on atProto, each post is stored in the **community's repository** and indexed by the AppView for discovery and interaction. Posts support rich text, embeds, voting, tagging, and federation with other atProto platforms. 34 35## Architecture 36 37### atProto Data Flow 38Posts follow the community-owned repository pattern, matching the V2 Communities architecture: 39 40``` 41User creates post → Written to COMMUNITY's PDS repository (using community credentials) → 42Firehose broadcasts event → AppView Jetstream consumer indexes → 43Post appears in feeds 44``` 45 46**Repository Structure:** 47``` 48Repository: at://did:plc:community789/social.coves.community.post.record/3k2a4b5c6d7e 49Owner: did:plc:community789 (community owns the post) 50Author: did:plc:user123 (tracked in record metadata) 51Hosted By: did:web:coves.social (instance manages community credentials) 52``` 53 54**Key Architectural Principles:** 55- ✅ Communities own posts (posts live in community repos, like traditional forums) 56- ✅ Author tracked in metadata (post.author field references user DID) 57- ✅ Communities are portable (migrate instance = posts move with community) 58- ✅ Matches V2 Communities pattern (community owns repository, instance manages credentials) 59- ✅ Write operations use community's PDS credentials (not user credentials) 60 61--- 62 63## Alpha Features (MVP - Ship First) 64 65### Content Rules Integration 66**Status:** Lexicon complete (2025-10-18), validation TODO 67**Priority:** CRITICAL - Required for community content policies 68 69Posts are validated against community-specific content rules at creation time. Communities can restrict: 70- Allowed embed types (images, video, external, record) 71- Text requirements (min/max length, required/optional) 72- Title requirements 73- Image count limits 74- Federated content policies 75 76**See:** [PRD_GOVERNANCE.md - Content Rules System](PRD_GOVERNANCE.md#content-rules-system) for full details. 77 78**Implementation checklist:** 79- [x] Lexicon: `contentRules` in `social.coves.community.profile` 80- [x] Lexicon: `postType` removed from `social.coves.community.post.create` 81- [ ] Validation: `ValidatePostAgainstRules()` service function 82- [ ] Handler: Integrate validation in post creation endpoint 83- [ ] AppView: Index derived characteristics (embed_type, text_length, etc.) 84- [ ] Tests: Validate content rule enforcement 85 86--- 87 88### Core Post Management 89**Status:** ✅ CREATE COMPLETE (2025-10-19) - Get/Update/Delete TODO 90**Priority:** CRITICAL - Posts are the foundation of the platform 91 92#### Create Post 93- [x] Lexicon: `social.coves.community.post.record` 94- [x] Lexicon: `social.coves.community.post.create` 95- [x] Removed `postType` enum in favor of content rules ✅ (2025-10-18) 96- [x] Removed `postType` from record and get lexicons ✅ (2025-10-18) 97- [x] **Handler:** `POST /xrpc/social.coves.community.post.create` ✅ (Alpha - see IMPLEMENTATION_POST_CREATION.md) 98 - ✅ Accept: community (DID/handle), title (optional), content, facets, embed, contentLabels 99 - ✅ Validate: User is authenticated, community exists, content within limits 100 - ✅ Write: Create record in **community's PDS repository** 101 - ✅ Return: AT-URI and CID of created post 102 - ⚠️ Content rules validation deferred to Beta 103- [x] **Service Layer:** `PostService.Create()`104 - ✅ Resolve community identifier to DID (supports all 4 at-identifier formats) 105 - ✅ Validate community exists and is not private 106 - ✅ Fetch community from AppView 107 - ⚠️ **Validate post against content rules** DEFERRED (see [PRD_GOVERNANCE.md](PRD_GOVERNANCE.md#content-rules-system)) 108 - ✅ Fetch community's PDS credentials with automatic token refresh 109 - ✅ Build post record with author DID, timestamp, content 110 -**Write to community's PDS** using community's access token 111 - ✅ Return URI/CID for AppView indexing 112- [x] **Validation:**113 - ✅ Community reference is valid (supports DIDs and handles) 114 - ✅ Content length ≤ 50,000 characters 115 - ✅ Title (if provided) ≤ 3,000 bytes 116 - ✅ ContentLabels are from known values (nsfw, spoiler, violence) 117 - ⚠️ **Content rules compliance:** DEFERRED TO BETA 118 - Check embed types against `allowedEmbedTypes` 119 - Verify `requireText` / `minTextLength` / `maxTextLength` 120 - Verify `requireTitle` if set 121 - Check image counts against `minImages` / `maxImages` 122 - Block federated posts if `allowFederated: false` 123 - Return `ContentRuleViolation` error if validation fails 124- [x] **E2E Test:** Create text post → Write to **community's PDS** → Index via Jetstream → Verify in AppView ✅ 125 126#### Get Post 127- [x] Lexicon: `social.coves.community.post.get`128- [ ] **Handler:** `GET /xrpc/social.coves.community.post.get?uri=at://...` 129 - Accept: AT-URI of post 130 - Return: Full post view with author, community, stats, viewer state 131- [ ] **Service Layer:** `PostService.Get(uri, viewerDID)` 132 - Fetch post from AppView PostgreSQL 133 - Join with user/community data 134 - Calculate stats (upvotes, downvotes, score, comment count) 135 - Include viewer state (vote status, saved status, tags) 136- [ ] **Repository:** `PostRepository.GetByURI()` 137 - Single query with JOINs for author, community, stats 138 - Handle missing posts gracefully (deleted or not indexed) 139- [ ] **E2E Test:** Get post by URI → Verify all fields populated 140 141#### Update Post 142- [x] Lexicon: `social.coves.community.post.update`143- [ ] **Handler:** `POST /xrpc/social.coves.community.post.update` 144 - Accept: uri, title, content, facets, embed, contentLabels, editNote 145 - Validate: User is post author, within 24-hour edit window 146 - Write: Update record in **community's PDS** 147 - Return: New CID 148- [ ] **Service Layer:** `PostService.Update()` 149 - Fetch existing post from AppView 150 - Verify authorship (post.author == authenticated user DID) 151 - Verify edit window (createdAt + 24 hours > now) 152 - Fetch community's PDS credentials (with token refresh) 153 - **Update record in community's PDS** using community's access token 154 - Track edit timestamp (editedAt field) 155- [ ] **Edit Window:** 24 hours from creation (hardcoded for Alpha) 156- [ ] **Edit Note:** Optional explanation field (stored in record) 157- [ ] **E2E Test:** Update post → Verify edit reflected in AppView 158 159#### Delete Post 160- [x] Lexicon: `social.coves.community.post.delete`161- [ ] **Handler:** `POST /xrpc/social.coves.community.post.delete` 162 - Accept: uri 163 - Validate: User is post author OR community moderator 164 - Write: Delete record from **community's PDS** 165- [ ] **Service Layer:** `PostService.Delete()` 166 - Verify authorship OR moderator permission 167 - Fetch community's PDS credentials 168 - **Delete from community's PDS** (broadcasts DELETE event to firehose) 169 - Consumer handles soft delete in AppView 170- [ ] **AppView Behavior:** Mark as deleted (soft delete), hide from feeds 171- [ ] **Moderator Delete:** Community moderators can delete any post in their community 172- [ ] **E2E Test:** Delete post → Verify hidden from queries 173 174--- 175 176### Post Content Features 177 178#### Rich Text Support 179- [x] Lexicon: Facets reference `social.coves.richtext.facet`180- [ ] **Supported Facets:** 181 - Mentions: `@user.bsky.social` → Links to user profile 182 - Links: `https://example.com` → Clickable URLs 183 - Community mentions: `!community@instance` → Links to community 184 - Hashtags: `#topic` → Tag-based discovery (Future) 185- [ ] **Implementation:** 186 - Store facets as JSON array in post record 187 - Validate byte ranges match content 188 - Render facets in AppView responses 189 190#### Embeds (Alpha Scope) 191- [x] Lexicon: Embed union type ✅ 192- [ ] **Alpha Support:** 193 - **Images:** Upload to community's PDS blob storage, reference in embed 194 - **External Links:** URL, title, description, thumbnail (client-fetched) 195 - **Quoted Posts:** Reference another post's AT-URI 196- [ ] **Defer to Beta:** 197 - Video embeds (requires video processing infrastructure) 198 199#### Content Labels 200- [x] Lexicon: Self-applied labels ✅ 201- [ ] **Alpha Labels:** 202 - `nsfw` - Not safe for work 203 - `spoiler` - Spoiler content (blur/hide by default) 204 - `violence` - Violent or graphic content 205- [ ] **Implementation:** 206 - Store as string array in post record 207 - AppView respects labels in feed filtering 208 - Client renders appropriate warnings/blurs 209 210--- 211 212### Voting System 213 214#### Upvotes & Downvotes 215- [x] Lexicon: `social.coves.interaction.vote`216- [ ] **Handler:** `POST /xrpc/social.coves.interaction.createVote` 217 - Accept: subject (post AT-URI), direction (up/down) 218 - Write: Create vote record in **user's repository** 219- [ ] **Handler:** `POST /xrpc/social.coves.interaction.deleteVote` 220 - Accept: voteUri (AT-URI of vote record) 221 - Write: Delete vote record from **user's repository** 222- [ ] **Vote Toggling:** 223 - Upvote → Upvote = Delete upvote 224 - Upvote → Downvote = Delete upvote + Create downvote 225 - No vote → Upvote = Create upvote 226- [ ] **Downvote Controls (Alpha):** 227 - Global default: Downvotes enabled 228 - Community-level toggle: `allowDownvotes` (Boolean in community.profile) 229 - Instance-level toggle: Environment variable `ALLOW_DOWNVOTES` (Future) 230- [ ] **AppView Indexing:** 231 - Consumer tracks vote CREATE/DELETE events 232 - Aggregate counts: upvotes, downvotes, score (upvotes - downvotes) 233 - Track viewer's vote state (for "already voted" UI) 234- [ ] **E2E Test:** Create vote → Index → Verify count updates → Delete vote → Verify count decrements 235 236**Note:** Votes live in user's repository (user owns their voting history), but posts live in community's repository. 237 238#### Vote Statistics 239- [x] Lexicon: `postStats` in post view ✅ 240- [ ] **Stats Fields:** 241 - `upvotes` - Total upvote count 242 - `downvotes` - Total downvote count (0 if community disables) 243 - `score` - Calculated score (upvotes - downvotes) 244 - `commentCount` - Total comments (placeholder for Beta) 245 - `shareCount` - Share tracking (Future) 246 - `tagCounts` - Aggregate tag counts (Future) 247 248--- 249 250### Jetstream Consumer (Indexing) 251 252#### Post Event Handling 253- [x] **Consumer:** `PostConsumer.HandlePostEvent()` ✅ (2025-10-19) 254 - ✅ Listen for `social.coves.community.post.record` CREATE from **community repositories** 255 - ✅ Parse post record, extract author DID and community DID (from AT-URI owner) 256 - ⚠️ **Derive post characteristics:** DEFERRED (embed_type, text_length, has_title, has_embed for content rules filtering) 257 - ✅ Insert in AppView PostgreSQL (CREATE only - UPDATE/DELETE deferred) 258 - ✅ Index: uri, cid, author_did, community_did, title, content, created_at, indexed_at 259 -**Security Validation:** 260 - ✅ Verify event.repo matches community DID (posts must come from community repos) 261 - ✅ Verify community exists in AppView (foreign key integrity) 262 - ✅ Verify author exists in AppView (foreign key integrity) 263 - ✅ Idempotent indexing for Jetstream replays 264 265#### Vote Event Handling 266- [ ] **Consumer:** `PostConsumer.HandleVoteEvent()` - DEFERRED TO BETA (voting system not yet implemented) 267 - Listen for `social.coves.interaction.vote` CREATE/DELETE from **user repositories** 268 - Parse subject URI (extract post) 269 - Increment/decrement vote counts atomically 270 - Track vote URI for viewer state queries 271 - **Validation:** Verify event.repo matches voter DID (votes must come from user repos) 272 273#### Error Handling 274- [x] Invalid community references → Reject post (foreign key enforcement) ✅ 275- [x] Invalid author references → Reject post (foreign key enforcement) ✅ 276- [x] Malformed records → Skip indexing, log error ✅ 277- [x] Duplicate events → Idempotent operations (unique constraint on URI) ✅ 278- [x] Posts from user repos → Reject (repository DID validation) ✅ 279 280--- 281 282### Database Schema 283 284#### Posts Table ✅ IMPLEMENTED (2025-10-19) 285 286**Migration:** [internal/db/migrations/011_create_posts_table.sql](../internal/db/migrations/011_create_posts_table.sql) 287 288```sql 289CREATE TABLE posts ( 290 id BIGSERIAL PRIMARY KEY, 291 uri TEXT UNIQUE NOT NULL, -- AT-URI (at://community_did/collection/rkey) 292 cid TEXT NOT NULL, -- Content ID 293 rkey TEXT NOT NULL, -- Record key (TID) 294 author_did TEXT NOT NULL, -- Author's DID (from record metadata) 295 community_did TEXT NOT NULL, -- Community DID (from AT-URI owner) 296 title TEXT, -- Post title (nullable) 297 content TEXT, -- Post content 298 content_facets JSONB, -- Rich text facets 299 embed JSONB, -- Embedded content 300 content_labels TEXT[], -- Self-applied labels 301 302 -- ⚠️ Derived characteristics DEFERRED TO BETA (for content rules filtering) 303 -- Will be added when content rules are implemented: 304 -- embed_type TEXT, -- images, video, external, record (NULL if no embed) 305 -- text_length INT NOT NULL DEFAULT 0, -- Character count of content 306 -- has_title BOOLEAN NOT NULL DEFAULT FALSE, 307 -- has_embed BOOLEAN NOT NULL DEFAULT FALSE, 308 309 created_at TIMESTAMPTZ NOT NULL, -- Author's timestamp 310 edited_at TIMESTAMPTZ, -- Last edit timestamp 311 indexed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- When indexed 312 deleted_at TIMESTAMPTZ, -- Soft delete 313 314 -- Stats (denormalized for performance) 315 upvote_count INT NOT NULL DEFAULT 0, 316 downvote_count INT NOT NULL DEFAULT 0, 317 score INT NOT NULL DEFAULT 0, -- upvote_count - downvote_count 318 comment_count INT NOT NULL DEFAULT 0, 319 320 CONSTRAINT fk_author FOREIGN KEY (author_did) REFERENCES users(did) ON DELETE CASCADE, 321 CONSTRAINT fk_community FOREIGN KEY (community_did) REFERENCES communities(did) ON DELETE CASCADE 322); 323 324-- ✅ Implemented indexes 325CREATE INDEX idx_posts_community_created ON posts(community_did, created_at DESC) WHERE deleted_at IS NULL; 326CREATE INDEX idx_posts_community_score ON posts(community_did, score DESC, created_at DESC) WHERE deleted_at IS NULL; 327CREATE INDEX idx_posts_author ON posts(author_did, created_at DESC); 328CREATE INDEX idx_posts_uri ON posts(uri); 329 330-- ⚠️ Deferred until content rules are implemented: 331-- CREATE INDEX idx_posts_embed_type ON posts(community_did, embed_type) WHERE deleted_at IS NULL; 332``` 333 334#### Votes Table 335```sql 336CREATE TABLE votes ( 337 id BIGSERIAL PRIMARY KEY, 338 uri TEXT UNIQUE NOT NULL, -- Vote record AT-URI (at://voter_did/collection/rkey) 339 cid TEXT NOT NULL, 340 rkey TEXT NOT NULL, 341 voter_did TEXT NOT NULL, -- User who voted (from AT-URI owner) 342 subject_uri TEXT NOT NULL, -- Post/comment AT-URI 343 direction TEXT NOT NULL CHECK (direction IN ('up', 'down')), 344 created_at TIMESTAMPTZ NOT NULL, 345 indexed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 346 deleted_at TIMESTAMPTZ, 347 348 CONSTRAINT fk_voter FOREIGN KEY (voter_did) REFERENCES users(did), 349 UNIQUE (voter_did, subject_uri, deleted_at) -- One active vote per user per subject 350); 351 352CREATE INDEX idx_votes_subject ON votes(subject_uri, direction) WHERE deleted_at IS NULL; 353CREATE INDEX idx_votes_voter_subject ON votes(voter_did, subject_uri) WHERE deleted_at IS NULL; 354``` 355 356--- 357 358## Alpha Blockers (Must Complete) 359 360### Critical Path 361- [x] **Post CREATE Endpoint:** ✅ COMPLETE (2025-10-19) 362 - ✅ Handler with authentication and validation 363 - ✅ Service layer with business logic 364 - ✅ Repository layer for database access 365 - ⚠️ Get, Update, Delete deferred to Beta 366- [x] **Community Credentials:** ✅ Use community's PDS credentials for post writes 367- [x] **Token Refresh Integration:** ✅ Reuse community token refresh logic for post operations 368- [x] **Jetstream Consumer:** ✅ Posts indexed in real-time (2025-10-19) 369 - ✅ CREATE operations indexed 370 - ✅ Security validation (repository ownership) 371 - ⚠️ UPDATE/DELETE deferred until those features exist 372 - ⚠️ Derived characteristics deferred to Beta 373- [x] **Database Migrations:** ✅ Posts table created (migration 011) 374- [x] **E2E Tests:** ✅ Full flow tested (handler → community PDS → Jetstream → AppView) 375 - ✅ Service layer tests (9 subtests) 376 - ✅ Repository tests (2 subtests) 377 - ✅ Handler security tests (10+ subtests) 378 - ✅ Live PDS + Jetstream E2E test 379- [x] **Community Integration:** ✅ Posts correctly reference communities via at-identifiers 380- [x] **at-identifier Support:** ✅ All 4 formats supported (DIDs, canonical, @-prefixed, scoped) 381- [ ] **Content Rules Validation:** ⚠️ DEFERRED TO BETA - Posts validated against community content rules 382- [ ] **Vote System:** - Upvote/downvote with community-level controls 383- [ ] **Moderator Permissions:** ⚠️ DEFERRED TO BETA - Community moderators can delete posts 384 385### Testing Requirements 386- [x] Create text post in community → Appears in AppView ✅ 387- [x] Post lives in community's repository (verify AT-URI owner) ✅ 388- [x] Post written to PDS → Broadcast to Jetstream → Indexed in AppView ✅ 389- [x] Handler security: Rejects client-provided authorDid ✅ 390- [x] Handler security: Requires authentication ✅ 391- [x] Handler security: Validates request body size ✅ 392- [x] Handler security: All 4 at-identifier formats accepted ✅ 393- [x] Consumer security: Rejects posts from wrong repository ✅ 394- [x] Consumer security: Verifies community and author exist ✅ 395- [ ] **Content rules validation:** Text-only community rejects image posts ⚠️ DEFERRED - Governence 396- [ ] **Content rules validation:** Image community rejects posts without images ⚠️ DEFERRED - Governence 397- [ ] **Content rules validation:** Post with too-short text rejected ⚠️ DEFERRED - Governence 398- [ ] **Content rules validation:** Federated post rejected if `allowFederated: false` ⚠️ DEFERRED - Governence 399- [ ] Update post within 24 hours → Edit reflected ⚠️ DEFERRED 400- [ ] Delete post as author → Hidden from queries ⚠️ DEFERRED 401- [ ] Delete post as moderator → Hidden from queries ⚠️ DEFERRED 402- [ ] Upvote post → Count increments 403- [ ] Downvote post → Count increments (if enabled) 404- [ ] Toggle vote → Counts update correctly ⚠️ DEFERRED - Governence 405- [ ] Community with downvotes disabled → Downvote returns error ⚠️ DEFERRED - Governence 406 407--- 408 409## Beta Features (Post-Alpha) 410 411### Advanced Post Types 412**Status:** Deferred - Simplify for Alpha 413**Rationale:** Text posts are sufficient for MVP, other types need more infrastructure 414 415- [ ] **Image Posts:** Full image upload/processing pipeline 416 - Multi-image support (up to 4 images) 417 - Upload to community's PDS blob storage 418 - Thumbnail generation 419 - Alt text requirements (accessibility) 420 421- [ ] **Video Posts:** Video hosting and processing 422 - Video upload to community's PDS blob storage 423 - Thumbnail extraction 424 - Format validation 425 - Streaming support 426 427- [ ] **Microblog Posts:** Bluesky federation integration 428 - Fetch Bluesky posts by AT-URI 429 - Display inline with native posts 430 - Track original author info 431 - Federation metadata 432 433- [ ] **Decision Point:** Remove "Article" type entirely? 434 - Obsoleted by planned RSS aggregation service 435 - LLMs will break down articles into digestible content 436 - May not need native article posting 437 438### Post Interaction Features 439 440#### Tagging System 441- [x] Lexicon: `social.coves.interaction.tag`442- [ ] **Known Tags:** helpful, insightful, spam, hostile, offtopic, misleading 443- [ ] **Community Custom Tags:** Communities define their own tags 444- [ ] **Aggregate Counts:** Track tag distribution on posts 445- [ ] **Moderation Integration:** High spam/hostile tags trigger tribunal review 446- [ ] **Reputation Impact:** Helpful/insightful tags boost author reputation 447- [ ] **Tag Storage:** Tags live in **user's repository** (users own their tags) 448 449#### Crossposting 450- [x] Lexicon: `social.coves.community.post.crosspost`451- [ ] **Crosspost Tracking:** Share post to multiple communities 452- [ ] **Implementation:** Create new post record in each community's repository 453- [ ] **Crosspost Chain:** Track all crosspost relationships 454- [ ] **Deduplication:** Show original + crosspost count (don't spam feeds) 455- [ ] **Rules:** Communities can disable crossposting 456 457#### Save Posts 458- [ ] **Lexicon:** Create `social.coves.actor.savedPost` record type 459- [ ] **Functionality:** Bookmark posts for later reading 460- [ ] **Private List:** Saved posts stored in **user's repository** 461- [ ] **AppView Query:** Endpoint to fetch user's saved posts 462 463### Post Search 464- [x] Lexicon: `social.coves.community.post.search`465- [ ] **Search Parameters:** 466 - Query string (q) 467 - Filter by community 468 - Filter by author 469 - Filter by post type 470 - Filter by tags 471 - Sort: relevance, new, top 472 - Timeframe: hour, day, week, month, year, all 473- [ ] **Implementation:** 474 - PostgreSQL full-text search (tsvector on title + content) 475 - Relevance ranking algorithm 476 - Pagination with cursor 477 478### Edit History 479- [ ] **Track Edits:** Store edit history in AppView (not in atProto record) 480- [ ] **Edit Diff:** Show what changed between versions 481- [ ] **Edit Log:** List all edits with timestamps and edit notes 482- [ ] **Revision Viewing:** View previous versions of post 483 484### Advanced Voting 485 486#### Vote Weight by Reputation 487- [ ] **Reputation Multiplier:** High-reputation users' votes count more 488- [ ] **Community-Specific:** Reputation calculated per-community 489- [ ] **Transparency:** Show vote weight in moderation logs (not public) 490 491#### Fuzzing & Vote Obfuscation 492- [ ] **Count Fuzzing:** Add noise to vote counts (prevent manipulation detection) 493- [ ] **Delay Display:** Don't show exact counts for new posts (first hour) 494- [ ] **Rate Limiting:** Prevent vote brigading 495 496--- 497 498## Future Features 499 500### Federation 501 502#### Bluesky Integration 503- [ ] **Display Bluesky Posts:** Show Bluesky posts in community feeds (microblog type) 504- [ ] **Original Author Info:** Track Bluesky user metadata 505- [ ] **No Native Commenting:** Users see Bluesky posts, can't comment (yet) 506- [ ] **Reference Storage:** Store Bluesky AT-URI, don't duplicate content 507 508#### ActivityPub Integration 509- [ ] **Lemmy/Mbin Posts:** Convert ActivityPub posts to Coves posts 510- [ ] **Bidirectional Sync:** Coves posts appear on Lemmy instances 511- [ ] **User Identity Mapping:** Assign DIDs to ActivityPub users 512- [ ] **Action Translation:** Upvotes ↔ ActivityPub likes 513 514### Advanced Features 515 516#### Post Scheduling 517- [ ] Schedule posts for future publishing 518- [ ] Edit scheduled posts before they go live 519- [ ] Cancel scheduled posts 520 521#### Post Templates 522- [ ] Communities define post templates 523- [ ] Auto-fill fields for common post types 524- [ ] Game threads, event announcements, etc. 525 526#### Polls 527- [ ] Create polls in posts 528- [ ] Multiple choice, ranked choice, approval voting 529- [ ] Time-limited voting windows 530- [ ] Results visualization 531 532#### Location-Based Posting 533- [x] Lexicon: `location` field in post record ✅ 534- [ ] **Geo-Tagging:** Attach coordinates to posts 535- [ ] **Community Rules:** Require location for certain posts (local events) 536- [ ] **Privacy:** User controls location precision 537- [ ] **Discovery:** Filter posts by location 538 539--- 540 541## Technical Decisions Log 542 543### 2025-10-18: Content Rules Over Post Type Enum 544**Decision:** Remove `postType` from post creation input; validate posts against community's `contentRules` instead 545 546**Rationale:** 547- `postType` enum forced users to explicitly select type (bad UX - app should infer from structure) 548- Structure-based validation is more flexible ("text required, images optional" vs rigid type categories) 549- Content rules are extensible without changing post lexicon 550- Enables both community restrictions (governance) AND user filtering (UI preferences) 551- Follows atProto philosophy: describe data structure, not UI intent 552 553**Implementation:** 554- Post creation no longer accepts `postType` parameter 555- Community profile contains optional `contentRules` object 556- Handler validates post structure against community's content rules 557- AppView indexes derived characteristics (embed_type, text_length, has_title, has_embed) 558- Validation error changed from `InvalidPostType` to `ContentRuleViolation` 559 560**Database Changes:** 561- Remove `post_type` enum column 562- Add derived fields: `embed_type`, `text_length`, `has_title`, `has_embed` 563- Add index on `embed_type` for filtering 564 565**Example Rules:** 566- Text-only community: `allowedEmbedTypes: []` + `requireText: true` 567- Image community: `allowedEmbedTypes: ["images"]` + `minImages: 1` 568- No restrictions: `contentRules: null` 569 570**See:** [PRD_GOVERNANCE.md - Content Rules System](PRD_GOVERNANCE.md#content-rules-system) 571 572--- 573 574### 2025-10-18: Posts Live in Community Repositories 575**Decision:** Posts are stored in community's repository, not user's repository 576 577**Rationale:** 578- **Matches V2 Communities Architecture:** Communities own their repositories 579- **Traditional Forum Model:** Community owns content, author tracked in metadata 580- **Simpler Permissions:** Use community credentials for all post writes 581- **Portability:** Posts migrate with community when changing instances 582- **Moderation:** Community has full control over content 583- **Reuses Token Refresh:** Can leverage existing community credential management 584 585**Implementation Details:** 586- Post AT-URI: `at://community_did/social.coves.community.post.record/tid` 587- Write operations use community's PDS credentials (encrypted, stored in AppView) 588- Author tracked in post record's `author` field (DID) 589- Moderators can delete any post in their community 590- Token refresh reuses community's refresh logic 591 592**Trade-offs vs User-Owned Posts:** 593- ❌ Users can't take posts when leaving community/instance 594- ❌ Less "web3" (content not user-owned) 595- ✅ Traditional forum UX (users expect community to own content) 596- ✅ Simpler implementation (one credential store per community) 597- ✅ Easier moderation (community has full control) 598- ✅ Posts move with community during migration 599 600**Comparison to Bluesky:** 601- Bluesky: Users own posts (posts in user repo) 602- Coves: Communities own posts (posts in community repo) 603- This is acceptable - different platforms, different models 604- Still atProto-compliant (just different ownership pattern) 605 606--- 607 608### 2025-10-18: Votes Live in User Repositories 609**Decision:** Vote records are stored in user's repository, not community's 610 611**Rationale:** 612- Users own their voting history (personal preference) 613- Matches Bluesky pattern (likes in user's repo) 614- Enables portable voting history across instances 615- User controls their own voting record 616 617**Implementation Details:** 618- Vote AT-URI: `at://user_did/social.coves.interaction.vote/tid` 619- Write operations use user's PDS credentials 620- Subject field references post AT-URI (in community's repo) 621- Consumer aggregates votes from all users into post stats 622 623--- 624 625### 2025-10-18: Simplify Post Types for Alpha 626**Decision:** Launch with text posts only, defer other embed types to Beta 627**Status:** SUPERSEDED by content rules approach (see above) 628 629**Rationale:** 630- Text posts are sufficient for forum discussions (core use case) 631- Image/video embeds require additional infrastructure (blob storage, processing) 632- Article format can be handled with long-form text posts 633- Microblog type is for Bluesky federation (not immediate priority) 634- Simplicity accelerates alpha launch 635 636**Updated Approach (2025-10-18):** 637- Post structure determines "type" (not explicit enum) 638- Communities use `contentRules` to restrict embed types 639- AppView derives `embed_type` from post structure for filtering 640- More flexible than rigid type system 641 642--- 643 644### 2025-10-18: Include Downvotes with Community Controls 645**Decision:** Support both upvotes and downvotes, with toggles to disable downvotes 646 647**Rationale:** 648- Downvotes provide valuable signal for content quality 649- Some communities prefer upvote-only (toxic negativity concerns) 650- Instance operators should have global control option 651- Reddit/HN have proven downvotes work with good moderation 652 653**Implementation:** 654- Community-level: `allowDownvotes` boolean in community profile 655- Instance-level: Environment variable `ALLOW_DOWNVOTES` (future) 656- Downvote attempts on disabled communities return error 657- Stats show 0 downvotes when disabled 658 659--- 660 661### 2025-10-18: 24-Hour Edit Window (Hardcoded for Alpha) 662**Decision:** Posts can be edited for 24 hours after creation 663 664**Rationale:** 665- Allows fixing typos and errors 666- Prevents historical revisionism (can't change old posts) 667- 24 hours balances flexibility with integrity 668- Future: Community-configurable edit windows 669 670**Future Enhancements:** 671- Edit history tracking (show what changed) 672- Community-specific edit windows (0-72 hours) 673- Moderator override (edit any post) 674 675--- 676 677### 2025-10-18: Comments Separate from Posts PRD 678**Decision:** Comments get their own dedicated PRD 679 680**Rationale:** 681- Comments are complex enough to warrant separate planning 682- Threaded replies, vote inheritance, moderation all need design 683- Posts are usable without comments (voting, tagging still work) 684- Allows shipping posts sooner 685 686**Scope Boundary:** 687- **Posts PRD:** Post CRUD, voting, tagging, search 688- **Comments PRD:** Comment threads, reply depth, sorting, moderation 689 690--- 691 692### 2025-10-18: Feeds Separate from Posts PRD 693**Decision:** Feed generation gets its own PRD 694 695**Rationale:** 696- Feed algorithms are complex (ranking, personalization, filtering) 697- Posts need to exist before feeds can be built 698- Feed work includes: Home feed, Community feed, All feed, read state tracking 699- Allows iterating on feed algorithms independently 700 701**Scope Boundary:** 702- **Posts PRD:** Post creation, indexing, retrieval 703- **Feeds PRD:** Feed generation, ranking algorithms, read state, personalization 704 705--- 706 707## Success Metrics 708 709### Alpha Launch Checklist ✅ COMPLETE (2025-10-19) 710- [x] Users can create text posts in communities ✅ 711- [x] Posts are stored in community's repository (verify AT-URI) ✅ 712- [x] Posts use community's PDS credentials for writes ✅ 713- [x] Posts are indexed from firehose within 1 second ✅ (real-time Jetstream) 714- [x] E2E tests cover full write-forward flow ✅ 715- [x] Database handles posts without performance issues ✅ 716- [x] Handler security tests passing (authentication, validation, body size) ✅ 717- [x] Consumer security validation (repository ownership, community/author checks) ✅ 718- [x] All 4 at-identifier formats supported ✅ 719 720### Beta Checklist (TODO) 721- [ ] Post editing works within 24-hour window ⚠️ DEFERRED 722- [ ] Upvote/downvote system functional ⚠️ DEFERRED 723- [ ] Community downvote toggle works ⚠️ DEFERRED 724- [ ] Post deletion soft-deletes and hides from queries ⚠️ DEFERRED 725- [ ] Moderators can delete posts in their community ⚠️ DEFERRED 726- [ ] Get post endpoint returns full post view with stats ⚠️ DEFERRED 727- [ ] Content rules validation working ⚠️ DEFERRED 728- [ ] Database handles 100,000+ posts (load testing) 729 730### Beta Goals 731- [ ] All post types supported (text, image, video, microblog) 732- [ ] Tagging system enables community moderation 733- [ ] Post search returns relevant results 734- [ ] Edit history tracked and viewable 735- [ ] Crossposting works across communities 736- [ ] Save posts feature functional 737 738### V1 Goals 739- [ ] Bluesky posts display inline (federation) 740- [ ] Vote fuzzing prevents manipulation 741- [ ] Reputation affects vote weight 742- [ ] Location-based posting for local communities 743- [ ] Post templates reduce friction for common posts 744 745--- 746 747## Related Documents 748 749- [PRD_COMMUNITIES.md](PRD_COMMUNITIES.md) - Community system (posts require communities) 750- [DOMAIN_KNOWLEDGE.md](DOMAIN_KNOWLEDGE.md) - Overall platform architecture 751- [PRD_GOVERNANCE.md](PRD_GOVERNANCE.md) - Moderation and tagging systems 752- **PRD_COMMENTS.md** (TODO) - Comment threading and replies 753- **PRD_FEEDS.md** (TODO) - Feed generation and ranking algorithms 754 755--- 756 757## Lexicon Summary 758 759### `social.coves.community.post.record` 760**Status:** ✅ Defined, implementation TODO 761**Last Updated:** 2025-10-18 (removed `postType` enum) 762 763**Required Fields:** 764- `community` - DID of community (owner of repository) 765- `createdAt` - Timestamp 766 767**Optional Fields:** 768- `title` - Post title (300 graphemes / 3000 bytes) 769- `content` - Post content (50,000 characters max) 770- `facets` - Rich text annotations 771- `embed` - Images, video, external links, quoted posts (union type) 772- `contentLabels` - Self-applied labels (nsfw, spoiler, violence) 773- `originalAuthor` - For microblog posts (federated author info) 774- `federatedFrom` - Reference to federated post 775- `location` - Geographic coordinates 776- `crosspostOf` - AT-URI of original post 777- `crosspostChain` - Array of crosspost URIs 778 779**Notes:** 780- Author DID is inferred from the creation context (authenticated user), not stored in record 781- Post "type" is derived from structure (has embed? what embed type? has title? text length?) 782- Community's `contentRules` validate post structure at creation time 783 784### `social.coves.community.post.create` (Procedure) 785**Status:** ✅ Defined, implementation TODO 786**Last Updated:** 2025-10-18 (removed `postType` parameter) 787 788**Input Parameters:** 789- `community` (required) - DID or handle of community to post in 790- `title` (optional) - Post title 791- `content` (optional) - Post content 792- `facets` (optional) - Rich text annotations 793- `embed` (optional) - Embedded content (images, video, external, post) 794- `contentLabels` (optional) - Self-applied labels 795- `originalAuthor` (optional) - For federated posts 796- `federatedFrom` (optional) - Reference to federated post 797- `location` (optional) - Geographic coordinates 798 799**Validation:** 800- Community exists and is accessible 801- Post structure complies with community's `contentRules` 802- Content within global limits (unless community sets stricter limits) 803 804**Errors:** 805- `CommunityNotFound` - Community doesn't exist 806- `NotAuthorized` - User not authorized to post 807- `Banned` - User is banned from community 808- `InvalidContent` - Content violates general rules 809- `ContentRuleViolation` - Post violates community's content rules 810 811--- 812 813### `social.coves.interaction.vote` 814**Status:** ✅ Defined, implementation TODO 815 816**Fields:** 817- `subject` - AT-URI of post/comment being voted on 818- `createdAt` - Timestamp 819 820**Note:** Direction (up/down) inferred from record creation/deletion pattern. Stored in user's repository (user owns votes). 821 822### `social.coves.interaction.tag` 823**Status:** ✅ Defined, deferred to Beta 824 825**Fields:** 826- `subject` - AT-URI of post/comment 827- `tag` - Tag string (known values: helpful, insightful, spam, hostile, offtopic, misleading) 828- `createdAt` - Timestamp 829 830**Note:** Tags live in user's repository (users own their tags). 831 832--- 833 834## References 835 836- atProto Lexicon Spec: https://atproto.com/specs/lexicon 837- atProto Repository Spec: https://atproto.com/specs/repository 838- Bluesky Post Record: https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/post.json 839- Rich Text Facets: https://atproto.com/specs/rich-text 840- Coves V2 Communities Architecture: [PRD_COMMUNITIES.md](PRD_COMMUNITIES.md)