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)