A community based topic aggregation platform built on atproto

feat(lexicon): add post record and XRPC definitions

Add social.coves.post lexicon definitions:

1. social.coves.post.record
- Post record schema for community repositories
- Fields: community (at-identifier), author (did), title, content
- Rich text support: facets for mentions/links
- Embed support: images, video, external, record
- Content labels: nsfw, spoiler, violence
- Federation fields: originalAuthor, federatedFrom (future)
- Location support (future)
- Author field REQUIRED (added after PR review)

2. social.coves.post.create
- XRPC procedure for post creation
- Input: matches record schema (minus author - server-populated)
- Output: uri (AT-URI), cid (content ID)
- Errors: InvalidRequest, AuthRequired, NotAuthorized, Banned

3. social.coves.post.get
- XRPC query for fetching single post (future)
- Input: uri (AT-URI)
- Output: post view with stats

Update community profile and feed lexicons:
- Reference post record type
- Update descriptions for post integration

All lexicons follow atProto conventions and use at-identifier
format for community references (supports DIDs and handles).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+68 -23
+54
internal/atproto/lexicon/social/coves/community/profile.json
··· 73 73 "ref": "#federationConfig", 74 74 "description": "Federation and discovery configuration" 75 75 }, 76 + "contentRules": { 77 + "type": "ref", 78 + "ref": "#contentRules", 79 + "description": "Content posting rules and restrictions for this community" 80 + }, 76 81 "moderationType": { 77 82 "type": "string", 78 83 "knownValues": ["moderator", "sortition"], ··· 123 128 "type": "boolean", 124 129 "default": true, 125 130 "description": "Whether other Coves instances can index and discover this community" 131 + } 132 + } 133 + }, 134 + "contentRules": { 135 + "type": "object", 136 + "description": "Content posting rules and restrictions for this community. Rules are validated at post creation time.", 137 + "properties": { 138 + "allowedEmbedTypes": { 139 + "type": "array", 140 + "description": "Allowed embed types. Empty array = no embeds allowed. Null/undefined = all embed types allowed.", 141 + "items": { 142 + "type": "string", 143 + "knownValues": ["images", "video", "external", "record"] 144 + } 145 + }, 146 + "requireText": { 147 + "type": "boolean", 148 + "default": false, 149 + "description": "Whether posts must have text content (non-empty content field)" 150 + }, 151 + "minTextLength": { 152 + "type": "integer", 153 + "minimum": 0, 154 + "description": "Minimum character length for post content (0 = no minimum)" 155 + }, 156 + "maxTextLength": { 157 + "type": "integer", 158 + "minimum": 1, 159 + "description": "Maximum character length for post content (overrides global limit if lower)" 160 + }, 161 + "requireTitle": { 162 + "type": "boolean", 163 + "default": false, 164 + "description": "Whether posts must have a title" 165 + }, 166 + "minImages": { 167 + "type": "integer", 168 + "minimum": 0, 169 + "description": "Minimum number of images required (0 = no minimum). Only enforced if images embed is present." 170 + }, 171 + "maxImages": { 172 + "type": "integer", 173 + "minimum": 1, 174 + "description": "Maximum number of images allowed per post (overrides global limit if lower)" 175 + }, 176 + "allowFederated": { 177 + "type": "boolean", 178 + "default": true, 179 + "description": "Whether federated posts (e.g., from app.bsky) are allowed in this community" 126 180 } 127 181 } 128 182 }
+2 -2
internal/atproto/lexicon/social/coves/feed/getAll.json
··· 17 17 "postType": { 18 18 "type": "string", 19 19 "enum": ["text", "article", "image", "video", "microblog"], 20 - "description": "Filter by a single post type" 20 + "description": "Filter by a single post type (computed from embed structure)" 21 21 }, 22 22 "postTypes": { 23 23 "type": "array", ··· 25 25 "type": "string", 26 26 "enum": ["text", "article", "image", "video", "microblog"] 27 27 }, 28 - "description": "Filter by multiple post types" 28 + "description": "Filter by multiple post types (computed from embed structure)" 29 29 }, 30 30 "timeframe": { 31 31 "type": "string",
+2 -2
internal/atproto/lexicon/social/coves/feed/getCommunity.json
··· 23 23 "postType": { 24 24 "type": "string", 25 25 "enum": ["text", "article", "image", "video", "microblog"], 26 - "description": "Filter by a single post type" 26 + "description": "Filter by a single post type (computed from embed structure)" 27 27 }, 28 28 "postTypes": { 29 29 "type": "array", ··· 31 31 "type": "string", 32 32 "enum": ["text", "article", "image", "video", "microblog"] 33 33 }, 34 - "description": "Filter by multiple post types" 34 + "description": "Filter by multiple post types (computed from embed structure)" 35 35 }, 36 36 "timeframe": { 37 37 "type": "string",
+2 -2
internal/atproto/lexicon/social/coves/feed/getTimeline.json
··· 11 11 "postType": { 12 12 "type": "string", 13 13 "enum": ["text", "article", "image", "video", "microblog"], 14 - "description": "Filter by a single post type" 14 + "description": "Filter by a single post type (computed from embed structure)" 15 15 }, 16 16 "postTypes": { 17 17 "type": "array", ··· 19 19 "type": "string", 20 20 "enum": ["text", "article", "image", "video", "microblog"] 21 21 }, 22 - "description": "Filter by multiple post types" 22 + "description": "Filter by multiple post types (computed from embed structure)" 23 23 }, 24 24 "limit": { 25 25 "type": "integer",
+3 -8
internal/atproto/lexicon/social/coves/post/create.json
··· 9 9 "encoding": "application/json", 10 10 "schema": { 11 11 "type": "object", 12 - "required": ["community", "postType"], 12 + "required": ["community"], 13 13 "properties": { 14 14 "community": { 15 15 "type": "string", 16 16 "format": "at-identifier", 17 17 "description": "DID or handle of the community to post in" 18 - }, 19 - "postType": { 20 - "type": "string", 21 - "enum": ["text", "article", "image", "video", "microblog"], 22 - "description": "Type of post to create" 23 18 }, 24 19 "title": { 25 20 "type": "string", ··· 114 109 "description": "Post content violates community rules" 115 110 }, 116 111 { 117 - "name": "InvalidPostType", 118 - "description": "Community does not allow this post type" 112 + "name": "ContentRuleViolation", 113 + "description": "Post violates community content rules (e.g., embeds not allowed, text too short)" 119 114 } 120 115 ] 121 116 }
+1 -5
internal/atproto/lexicon/social/coves/post/get.json
··· 32 32 }, 33 33 "postView": { 34 34 "type": "object", 35 - "required": ["uri", "cid", "author", "record", "community", "postType", "createdAt", "indexedAt"], 35 + "required": ["uri", "cid", "author", "record", "community", "createdAt", "indexedAt"], 36 36 "properties": { 37 37 "uri": { 38 38 "type": "string", ··· 53 53 "community": { 54 54 "type": "ref", 55 55 "ref": "#communityRef" 56 - }, 57 - "postType": { 58 - "type": "string", 59 - "enum": ["text", "image", "video", "article", "microblog"] 60 56 }, 61 57 "title": { 62 58 "type": "string"
+4 -4
internal/atproto/lexicon/social/coves/post/record.json
··· 8 8 "key": "tid", 9 9 "record": { 10 10 "type": "object", 11 - "required": ["$type", "community", "postType", "createdAt"], 11 + "required": ["$type", "community", "author", "createdAt"], 12 12 "properties": { 13 13 "$type": { 14 14 "type": "string", ··· 20 20 "format": "at-identifier", 21 21 "description": "DID or handle of the community this was posted to" 22 22 }, 23 - "postType": { 23 + "author": { 24 24 "type": "string", 25 - "enum": ["text", "article", "image", "video", "microblog"], 26 - "description": "Discriminator for post type to enable filtering and specialized rendering" 25 + "format": "did", 26 + "description": "DID of the user who created this post. Server-populated from authenticated session; clients MUST NOT provide this field. Required for attribution, moderation, and accountability." 27 27 }, 28 28 "title": { 29 29 "type": "string",