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>

Changed files
+68 -23
internal
+54
internal/atproto/lexicon/social/coves/community/profile.json
···
"ref": "#federationConfig",
"description": "Federation and discovery configuration"
},
"moderationType": {
"type": "string",
"knownValues": ["moderator", "sortition"],
···
"type": "boolean",
"default": true,
"description": "Whether other Coves instances can index and discover this community"
}
}
}
···
"ref": "#federationConfig",
"description": "Federation and discovery configuration"
},
+
"contentRules": {
+
"type": "ref",
+
"ref": "#contentRules",
+
"description": "Content posting rules and restrictions for this community"
+
},
"moderationType": {
"type": "string",
"knownValues": ["moderator", "sortition"],
···
"type": "boolean",
"default": true,
"description": "Whether other Coves instances can index and discover this community"
+
}
+
}
+
},
+
"contentRules": {
+
"type": "object",
+
"description": "Content posting rules and restrictions for this community. Rules are validated at post creation time.",
+
"properties": {
+
"allowedEmbedTypes": {
+
"type": "array",
+
"description": "Allowed embed types. Empty array = no embeds allowed. Null/undefined = all embed types allowed.",
+
"items": {
+
"type": "string",
+
"knownValues": ["images", "video", "external", "record"]
+
}
+
},
+
"requireText": {
+
"type": "boolean",
+
"default": false,
+
"description": "Whether posts must have text content (non-empty content field)"
+
},
+
"minTextLength": {
+
"type": "integer",
+
"minimum": 0,
+
"description": "Minimum character length for post content (0 = no minimum)"
+
},
+
"maxTextLength": {
+
"type": "integer",
+
"minimum": 1,
+
"description": "Maximum character length for post content (overrides global limit if lower)"
+
},
+
"requireTitle": {
+
"type": "boolean",
+
"default": false,
+
"description": "Whether posts must have a title"
+
},
+
"minImages": {
+
"type": "integer",
+
"minimum": 0,
+
"description": "Minimum number of images required (0 = no minimum). Only enforced if images embed is present."
+
},
+
"maxImages": {
+
"type": "integer",
+
"minimum": 1,
+
"description": "Maximum number of images allowed per post (overrides global limit if lower)"
+
},
+
"allowFederated": {
+
"type": "boolean",
+
"default": true,
+
"description": "Whether federated posts (e.g., from app.bsky) are allowed in this community"
}
}
}
+2 -2
internal/atproto/lexicon/social/coves/feed/getAll.json
···
"postType": {
"type": "string",
"enum": ["text", "article", "image", "video", "microblog"],
-
"description": "Filter by a single post type"
},
"postTypes": {
"type": "array",
···
"type": "string",
"enum": ["text", "article", "image", "video", "microblog"]
},
-
"description": "Filter by multiple post types"
},
"timeframe": {
"type": "string",
···
"postType": {
"type": "string",
"enum": ["text", "article", "image", "video", "microblog"],
+
"description": "Filter by a single post type (computed from embed structure)"
},
"postTypes": {
"type": "array",
···
"type": "string",
"enum": ["text", "article", "image", "video", "microblog"]
},
+
"description": "Filter by multiple post types (computed from embed structure)"
},
"timeframe": {
"type": "string",
+2 -2
internal/atproto/lexicon/social/coves/feed/getCommunity.json
···
"postType": {
"type": "string",
"enum": ["text", "article", "image", "video", "microblog"],
-
"description": "Filter by a single post type"
},
"postTypes": {
"type": "array",
···
"type": "string",
"enum": ["text", "article", "image", "video", "microblog"]
},
-
"description": "Filter by multiple post types"
},
"timeframe": {
"type": "string",
···
"postType": {
"type": "string",
"enum": ["text", "article", "image", "video", "microblog"],
+
"description": "Filter by a single post type (computed from embed structure)"
},
"postTypes": {
"type": "array",
···
"type": "string",
"enum": ["text", "article", "image", "video", "microblog"]
},
+
"description": "Filter by multiple post types (computed from embed structure)"
},
"timeframe": {
"type": "string",
+2 -2
internal/atproto/lexicon/social/coves/feed/getTimeline.json
···
"postType": {
"type": "string",
"enum": ["text", "article", "image", "video", "microblog"],
-
"description": "Filter by a single post type"
},
"postTypes": {
"type": "array",
···
"type": "string",
"enum": ["text", "article", "image", "video", "microblog"]
},
-
"description": "Filter by multiple post types"
},
"limit": {
"type": "integer",
···
"postType": {
"type": "string",
"enum": ["text", "article", "image", "video", "microblog"],
+
"description": "Filter by a single post type (computed from embed structure)"
},
"postTypes": {
"type": "array",
···
"type": "string",
"enum": ["text", "article", "image", "video", "microblog"]
},
+
"description": "Filter by multiple post types (computed from embed structure)"
},
"limit": {
"type": "integer",
+3 -8
internal/atproto/lexicon/social/coves/post/create.json
···
"encoding": "application/json",
"schema": {
"type": "object",
-
"required": ["community", "postType"],
"properties": {
"community": {
"type": "string",
"format": "at-identifier",
"description": "DID or handle of the community to post in"
-
},
-
"postType": {
-
"type": "string",
-
"enum": ["text", "article", "image", "video", "microblog"],
-
"description": "Type of post to create"
},
"title": {
"type": "string",
···
"description": "Post content violates community rules"
},
{
-
"name": "InvalidPostType",
-
"description": "Community does not allow this post type"
}
]
}
···
"encoding": "application/json",
"schema": {
"type": "object",
+
"required": ["community"],
"properties": {
"community": {
"type": "string",
"format": "at-identifier",
"description": "DID or handle of the community to post in"
},
"title": {
"type": "string",
···
"description": "Post content violates community rules"
},
{
+
"name": "ContentRuleViolation",
+
"description": "Post violates community content rules (e.g., embeds not allowed, text too short)"
}
]
}
+1 -5
internal/atproto/lexicon/social/coves/post/get.json
···
},
"postView": {
"type": "object",
-
"required": ["uri", "cid", "author", "record", "community", "postType", "createdAt", "indexedAt"],
"properties": {
"uri": {
"type": "string",
···
"community": {
"type": "ref",
"ref": "#communityRef"
-
},
-
"postType": {
-
"type": "string",
-
"enum": ["text", "image", "video", "article", "microblog"]
},
"title": {
"type": "string"
···
},
"postView": {
"type": "object",
+
"required": ["uri", "cid", "author", "record", "community", "createdAt", "indexedAt"],
"properties": {
"uri": {
"type": "string",
···
"community": {
"type": "ref",
"ref": "#communityRef"
},
"title": {
"type": "string"
+4 -4
internal/atproto/lexicon/social/coves/post/record.json
···
"key": "tid",
"record": {
"type": "object",
-
"required": ["$type", "community", "postType", "createdAt"],
"properties": {
"$type": {
"type": "string",
···
"format": "at-identifier",
"description": "DID or handle of the community this was posted to"
},
-
"postType": {
"type": "string",
-
"enum": ["text", "article", "image", "video", "microblog"],
-
"description": "Discriminator for post type to enable filtering and specialized rendering"
},
"title": {
"type": "string",
···
"key": "tid",
"record": {
"type": "object",
+
"required": ["$type", "community", "author", "createdAt"],
"properties": {
"$type": {
"type": "string",
···
"format": "at-identifier",
"description": "DID or handle of the community this was posted to"
},
+
"author": {
"type": "string",
+
"format": "did",
+
"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."
},
"title": {
"type": "string",